# Copyright (C) 1997 Alejandro Escalante Medina and others
# Use of this software is subject to the terms of the GNU General
# Public License

# This module manages apache configuration files

from Conf import Conf
import string
import regex
# Version 0.1: rewrote to use Conf.py
# Version 0.2: wrote ApacheConf generic class, fixed some errors

__version__ = "0.2"

# Path to apache config files, please end with a slash.
apache_path = "/etc/httpd/conf/"

# Function to build a hash of valid directives
def getvalidkeys(type):
	if type == "httpd":
		hash = { "ServerType" : "m[standalone|inetd]",
			 "Port" : "n",
			 "User" : "s",
			 "Group" : "s",
			 "ServerAdmin" : "s",
			 "ServerRoot" : "D",
			 "BindAddress" : "s",
			 "ErrorLog" : "f",
			 "TransferLog" : "f",
			 "RefererLog" : "f",
			 "AgentLog" : "f",
			 "PidFile" : "f",
			 "ScoreBoardFile" : "f",
			 "ServerName" : "s",
			 'HostnameLookups' : 'b',
			 "CacheNegotiatedDocs" : "s",
			 "Timeout" : "n",
			 "KeepAlive" : "n",
			 "KeepAliveTimeout" : "n",
			 "MaxKeepAliveRequests" : "n",
			 "MinSpareServers" : "n",
			 "MaxSpareServers" : "n",
			 "StartServers" : "n",
			 "MaxClients" : "n",
			 "MaxRequestsPerChild" : "n",
			 "ProxyRequests" : "b",
			 "CacheRoot" : "D",
			 "CacheSize" : "n",
			 "CacheInterval" : "n",
			 "CacheMaxExpire" : "n",
			 "CacheLastModifiedFactor" : "n",
			 "CacheDefaultExpire" : "n",
			 "NoCache" : "s",
			 "Listen" : "s",
			 "BrowserMatch" : 's'
			 }
	elif type == "access":
		hash = {
			}
	elif type == "VirtualHost":
		hash = { "ServerAdmin" : "s",
				 "DocumentRoot" : "D",
				 "ServerName" : "s",
				 "ErrorLog" : "f",
				 "TransferLog" : "f",
			 "AgentLog":"f",
			 "RefererLog":'f'
			   }
	elif type == "Directory":
		hash = { "Options" : "s",
				 "order" : "m[allow,deny|deny,allow]",
				 "allow" : "s",
				 "AllowOverride" : "s",
				 "SetHandler" : "s",
			   }
	elif type == "Location":
		hash = { "SetHandler" : "s",
				 "order" : "m[allow,deny|deny,allow]",
				 "deny" : "s",
				 "allow" : "s",
				 "SetHandler" : "s",
			   }
	elif type == 'srm':
	        hash = { 'DocumentRoot':'D', 'UserDir':'s',
			 'FancyIndexing':'b', 'DirectoryIndex':'s',
			 'LanguagePriority':'s','ReadmeName':'s',
			 }
	return hash

def gethelpstring(s):
    hash = {'ServerName':'This specifies the hostname of the web server. It is commonly used if the machine\'s hostname is foo.bar.com, for example, but you want the web server to realize that it is also www.bar.com',
	    'ServerAdmin':'The e-mail address of the administrator of this web server',
	    'DocumentRoot':'The directory under which documents to be served by the web server can be found',
	    'order':'The order in which the allow and deny access control rules will be checked',
	    'User':'The user whose identity the web server will assume. You can also specify a numeric UID by prefacing this with a #. DO NOT set this to root.',
	    'Group':'The group whose identity the web server will assume. You can also specify a numeric GID by prefacing this with a #',
	    'ServerRoot':'The directory under which the configuration and log files are found.',
	    'ErrorLog':'The file to which errors will be logged.',
	    'TransferLog':'The file to which all web requests will be logged.',
	    'AgentLog':'The file in which the browsers that people use for each request will be logged.',
	    'RefererLog':'The page that referred a user to generate this request will be logged here.',
	    'StartServers':'The number of server processes that will be started when the server is started.'
	}
    if hash.has_key(s):
	return hash[s]
    else:
	return None

# Function to validate information
# Types allowed:
# s - string of anything
# s[a|b] - string that can be a or b
# c - one character
# n - numeric
# f - filename
# i - IP address
# d - domain name
# D - Directory
# a - address
# b - boolean (On or Off)
# m[a|b|c|d] - multiple choice: a, b, c, d

def isvalid(ts, v):
	if ts == '':
		return 1
	if ts[0] == 's':
		# String
		if len(ts) > 2:
			opt = string.split(ts[2:-1], "|")
			if v in opt:
				return 1
			else:
				return 0
		else:
			return 1
	elif ts[0] == 'c':
		# Char
		if len(v) > 1:
			return 0
		if len(ts) > 2:
			if v in ts[2:-1]:
				return 1
			else:
				return 0
		else:
			return 1
	elif ts[0] == 'n':
		# Numeric
		try:
			v = string.atof(v)
			if len(ts) > 2:
				(min, max) = tuple(string.split(ts[2:-1], ","))
				min = string.atof(min)
				max = string.atof(max)
				if v >= min and v <= max:
					return 1
				else:
					return 0
			else:
				return 1
		except ValueError:
			return 0
	elif ts[0] == 'D':
		# Path
		if len(ts) > 2:
			return os.path.isdir(v)
		else:
			return 1
		
	elif ts[0] == 'f':
		# Filename
		if len(ts) > 2:
			return os.path.isfile(v)
		else:
			return 1
	elif ts[0] == 'i':
		# ip address
		if regex.match("[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+", v) == len(v):
			return 1
		else:
			return 0
	elif ts[0] == 'd':
		# domain
		if regex.match("\([a-zA-Z]+\w*\.?\)+", v) == len(v):
			return 1
		else:
			return 0
	elif ts[0] == 'a':
		# address
		if regex.match("[a-zA-Z]+\w*@\([a-zA-Z]+\w*\.?\)+", v) == len(v):
			return 1
		else:
			return 0
	elif ts[0] == 'b':
		# Boolean
		if(v == 'On' or v == 'Off' or v == 'on' or v == 'off'):
			return 1
		else:
			return 0
	elif ts[0] == 'm':
		# Multiple choice :)
		opt = string.split(ts[2:-1], "|")
		if v in opt:
			return 1
		else:
			return 0
	else:
		raise TypeError, "Type string unknown"

#
# Class for manipulating compounds (<Directive> ... </Directive>)
#

class Compound:
	def __init__(self, master, type, name):
		self.validkeys = getvalidkeys(type)
		self.vars = {}
		self.master = master
		self.type = type
		self.name = name
		self.master.rewind()
		found = 0
		while self.master.findlinewithfield(0, "<" + self.type):
			var = self.master.getfields()
			if var[1][:-1] == name:
				found = 1
				break
			self.master.nextline()
		if found:
			self.master.nextline()
			while self.master.findnextcodeline():
				var = self.master.getfields()
				if var[0][0] <> "<":
					if var[0] in self.validkeys.keys():
						self.vars[var[0]] = string.join(var[1:], " ")
				else:
					break
				self.master.nextline()
		else:
			self.master.insertline("</" + self.type + ">")
			self.master.insertline("<" + self.type + " " + name + ">")
		self.master.rewind()
	def __getitem__(self, varname):
		# Validation stuff
		if self.vars.has_key(varname):
			return self.vars[varname]
		else:
			if varname in self.validkeys.keys():
				return ''
			else:
				raise TypeError, "Invalid directive " + varname
	def __setitem__(self, varname, value):
		# Validation stuff
		if not varname in self.validkeys.keys():
			raise TypeError, "Invalid directive " + var[0]
			return
		if not isvalid(self.validkeys[varname], value):
			raise TypeError, varname + " with value " + str(value) + " needs type " + self.validkeys[varname]
		self.master.rewind()
		self.master.findnextline("<" + self.type + " " + self.name + ">")
		self.master.nextline()
		found = 0
		# Again, set *every* instance of varname to value to avoid surprises
		while 1:
			var = self.master.getfields()
			if len(var[0]) > 0:
				if var[0][0] <> "<":
					if var[0] == varname:
						self.master.setline(varname + " " + value)
						found = 1
				else:
					 break
			self.master.nextline()
		if not found:
			self.master.insertline(varname + " " + value)
		self.vars[varname] = value
	def __delitem__(self, varname):
		# Validation stuff
		if not varname in self.validkeys.keys():
			raise TypeError, "Invalid directive " + varname
			return
		self.master.rewind()
		self.master.findnextline("<" + self.type + " " + self.name + ">")
		self.master.nextline()
		# Again, delete *every* instance...
		while 1:
			var = self.master.getfields()
			if len(var[0]) > 0:
				if var[0][0] <> "<":
					if var[0] == varname:
						self.master.deleteline()
					else:
						self.master.nextline()
				else:
					break
		del self.vars[varname]

#
# Class for manipulating array-like directives (ScriptAlias, etc)
#

class Array:
	def __init__(self, master, name):
		self.master = master
		self.name = name
	def __len__(self):
		c = 0
		self.master.rewind()
		while 1:
			if self.master.findlinewithfield(0, self.name):
				self.master.nextline()
				c = c + 1
			else:
				break
		return c
	def __getitem__(self, key):
		if key >= len(self):
			raise IndexError
		self.master.rewind()
		for i in range(key + 1):
			self.master.findlinewithfield(0, self.name)
			if i == key:
				break
			self.master.nextline()
		return string.join(self.master.getfields()[1:], " ")
	def __setitem__(self, key, value):
		if key >= len(self):
			raise IndexError
		self.master.rewind()
		for i in range(key + 1):
			self.master.findlinewithfield(0, self.name)
			if i == key:
				break
			self.master.nextline()
		self.master.changefield(1, value)
	def __delitem__(self, key):
		if key >= len(self):
			raise IndexError
		self.master.rewind()
		for i in range(key + 1):
			self.master.findlinewithfield(0, self.name)
			if i == key:
				break
			self.master.nextline()
		self.master.deleteline()
 	def __repr__(self):
 		r = []
 		self.master.rewind()
 		while 1:
 			if self.master.findlinewithfield(0, self.name):
 				r.append("'" + string.join(self.master.getfields()[1:], " ") + "'")
 				self.master.nextline()
 			else:
 				break
 		return "[" + string.join(r, ", ") + "]"
	def append(self, value):
		self.master.fsf()
		self.master.insertline(self.name + " " + value)

#
# Generic class for apache config classes
#

class ApacheConf(Conf):
	def __init__(self, fn):
		self.validkeys = getvalidkeys(fn)
		Conf.__init__(self, apache_path + fn + ".conf", "#", "\t ", " ")
	def read(self):
		Conf.read(self)
		self.initvars()
	def initvars(self):
		self.vars = {}
		self.rewind()
		while self.findnextcodeline():
			var = self.getfields()
			if var[0][0] <> "<":
			    if var[0] in self.validkeys.keys():
					self.vars[var[0]] = string.join(var[1:], " ")
			else:
				self.nextline()
				self.findlinewithfield(0, var[0][0] + '/' + var[0][1:])
			self.nextline()
		self.rewind()
	def __getitem__(self, varname):
		# Validation stuff
		if self.vars.has_key(varname):
			return self.vars[varname]
		else:
			if varname in self.validkeys.keys():
				return ''
			else:
				raise TypeError, "Invalid directive " + varname
	def __setitem__(self, varname, value):
		# Validation stuff
		if not varname in self.validkeys.keys():
			raise TypeError, "Invalid directive " + var[0]
			return
		if not isvalid(self.validkeys[varname], value):
			raise TypeError, varname + " with value " + str(value) + " needs type " + self.validkeys[varname]
		# set *every* instance of varname to value to avoid surprises
		self.rewind()
		missing=1
		while self.findnextcodeline():
			f = self.getfields()
			if f[0] == varname:
				self.setline(varname + " " + value)
				missing = 0
			elif f[0][0] == '<':
				self.findlinewithfield(0, '</' + f[0][1:])
			self.nextline()
		if missing:
			self.fsf()
			self.insertline(varname + " " + value)
		self.vars[varname] = value
	def __delitem__(self, varname):
		# Validation stuff
		if not varname in self.validkeys.keys():
			raise TypeError, "Invalid directive " + varname
			return
		# delete *every* instance...
		self.rewind()
		while self.findnextline('[\t ]*' + varname):
			if self.getfields()[0] == varname:
				self.deleteline()
		del self.vars[varname]
	def compound_del(self, compound, name):
		self.rewind()
		if self.findnextline("<" + compound + " " + name + ">"):
			self.deleteline()
			while 1:
				c = self.getline()
				self.deleteline()
				if c:
					if c[0] == "<":
						break
	def compound_list(self, compound):
		self.rewind()
		l = []
		while self.findlinewithfield(0, "<" + compound):
			l.append(self.getfields()[1][:-1])
			self.nextline()
		return l

#
# The actual classes
#

class httpd(ApacheConf):
	def __init__(self):
		ApacheConf.__init__(self, "httpd")
	def VirtualHost(self, name):
		return Compound(self, "VirtualHost", name)
	def virtualhost_del(self, name):
		ApacheConf.compound_del(self, "VirtualHost", name)
	def virtualhosts(self):
		return ApacheConf.compound_list(self, "VirtualHost")

class srm(ApacheConf):
	def __init__(self):
		ApacheConf.__init__(self, 'srm')
		self.vars["ScriptAlias"] = Array(self, "ScriptAlias")
		self.vars["Alias"] = Array(self, "Alias")
		self.vars["AddHandler"] = Array(self, "AddHandler")

class access(ApacheConf):
	def __init__(self):
		ApacheConf.__init__(self, "access")
	def Directory(self, name):
		return Compound(self, "Directory", name)
	def directory_del(self, name):
		ApacheConf.compound_del(self, "Directory", name)
	def directories(self):
		return ApacheConf.compound_list(self, "Directory")
	def Location(self, name):
		return Compound(self, "Location", name)
	def location_del(self, name):
		ApacheConf.compound_del(self, "Location", name)
	def locations(self):
		return ApacheConf.compound_list(self, "Location")

