summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimo Sorce <ssorce@redhat.com>2007-09-06 17:57:54 -0400
committerSimo Sorce <ssorce@redhat.com>2007-09-06 17:57:54 -0400
commit566018f4d48f18fd6bdb3ad481e92c865b2a41e3 (patch)
tree2340b25cf73cf97e5e80b46d20fba5274fced707
parent584baa7ee21f22db6978efc89de1f1491768fab5 (diff)
downloadfreeipa-566018f4d48f18fd6bdb3ad481e92c865b2a41e3.tar.gz
freeipa-566018f4d48f18fd6bdb3ad481e92c865b2a41e3.tar.xz
freeipa-566018f4d48f18fd6bdb3ad481e92c865b2a41e3.zip
Better file parsing routines,
also switch to recreate ldap.conf and krb5.conf from scratch on clients, avoid nasty failures in case the original files contained strange directives
-rw-r--r--ipa-client/ipa-install/ipa-client-install80
-rw-r--r--ipa-client/ipaclient/ipachangeconf.py460
2 files changed, 385 insertions, 155 deletions
diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install
index f32cc2d6e..c1f3ed5be 100644
--- a/ipa-client/ipa-install/ipa-client-install
+++ b/ipa-client/ipa-install/ipa-client-install
@@ -31,6 +31,7 @@ from optparse import OptionParser
import ipaclient.ipadiscovery
import ipaclient.ipachangeconf
from ipa.ipautil import run
+import shutil
def parse_options():
parser = OptionParser(version=VERSION)
@@ -123,21 +124,19 @@ def main():
# Configure ldap.conf
ldapconf = ipaclient.ipachangeconf.IPAChangeConf("IPA Installer")
- opts = [{'name':'host', 'action':'comment'},
- {'name':'port', 'action':'comment'},
- {'name':'binddn', 'action':'comment'},
- {'name':'bindpw', 'action':'comment'},
- {'name':'rootbinddn', 'action':'comment'},
- {'name':'nss_base_passwd', 'value':ds.getBaseDN()+'?sub', 'action':'set'},
- {'name':'nss_base_group', 'value':ds.getBaseDN()+'?sub', 'action':'set'},
- {'name':'base', 'value':ds.getBaseDN(), 'action':'set'},
- {'name':'ldap_version', 'value':'3', 'action':'set'}]
- if dnsok and not options.force:
- opts.insert(0, {'name':'uri', 'action':'comment'})
- else:
- opts.append({'name':'uri', 'value':'ldap://'+ds.getServerName(), 'action':'set'})
ldapconf.setOptionAssignment(" ")
- ldapconf.changeConf("/etc/ldap.conf", opts)
+
+ opts = [{'name':'comment', 'type':'comment', 'value':'File modified by ipa-client-install'},
+ {'name':'empty', 'type':'empty'},
+ {'name':'nss_base_passwd', 'type':'option', 'value':ds.getBaseDN()+'?sub'},
+ {'name':'nss_base_group', 'type':'option', 'value':ds.getBaseDN()+'?sub'},
+ {'name':'base', 'type':'option', 'value':ds.getBaseDN()},
+ {'name':'ldap_version', 'type':'option', 'value':'3'}]
+ if not dnsok or options.force:
+ opts.append({'name':'uri', 'type':'option', 'value':'ldap://'+ds.getServerName()})
+
+ opts.append({'name':'empty', 'type':'empty'})
+ ldapconf.newConf("/etc/ldap.conf", opts)
#Check if kerberos is already configured properly
krbctx = krbV.default_context()
@@ -149,33 +148,52 @@ def main():
krbconf = ipaclient.ipachangeconf.IPAChangeConf("IPA Installer")
krbconf.setOptionAssignment(" = ")
krbconf.setSectionNameDelimiters(("[","]"))
+ krbconf.setSubSectionDelimiters(("{","}"))
+ krbconf.setIndent((""," "," "))
+
+ opts = [{'name':'comment', 'type':'comment', 'value':'File modified by ipa-client-install'},
+ {'name':'empty', 'type':'empty'}]
#[libdefaults]
- opts = [{'name':'default_realm', 'value':ds.getRealmName(), 'action':'set'},
- {'name':'ticket_lifetime', 'value':'24h', 'action':'set'},
- {'name':'forwardable', 'value':'yes', 'action':'set'}]
+ libopts = [{'name':'default_realm', 'type':'option', 'value':ds.getRealmName()}]
if dnsok and not options.force:
- opts.insert(1, {'name':'dns_lookup_realm', 'value':'true', 'action':'set'})
- opts.insert(2, {'name':'dns_lookup_kdc', 'value':'true', 'action':'set'})
+ libopts.append({'name':'dns_lookup_realm', 'type':'option', 'value':'true'})
+ libopts.append({'name':'dns_lookup_kdc', 'type':'option', 'value':'true'})
else:
- opts.insert(1, {'name':'dns_lookup_realm', 'value':'false', 'action':'set'})
- opts.insert(2, {'name':'dns_lookup_kdc', 'value':'false', 'action':'set'})
- krbconf.changeConf("/etc/krb5.conf", opts, "libdefaults");
+ libopts.append({'name':'dns_lookup_realm', 'type':'option', 'value':'false'})
+ libopts.append({'name':'dns_lookup_kdc', 'type':'option', 'value':'false'})
+ libopts.append({'name':'ticket_lifetime', 'type':'option', 'value':'24h'})
+ libopts.append({'name':'forwardable', 'type':'option', 'value':'yes'})
+
+ opts.append({'name':'libdefaults', 'type':'section', 'value':libopts})
+ opts.append({'name':'empty', 'type':'empty'})
#the following are necessary only if DNS discovery does not work
if not dnsok or options.force:
#[realms]
- opts = [{'name':ds.getRealmName(), 'value':'{', 'action':'set'},
- {'name':'kdc', 'value':ds.getServerName()+':88', 'action':'set'},
- {'name':'admin_server', 'value':ds.getServerName()+':749', 'action':'set'},
- # adding '\n}' is a dirty hack because we still don't have subsections support
- {'name':'default_domain', 'value':ds.getDomainName()+'\n}', 'action':'set'}]
- krbconf.changeConf("/etc/krb5.conf", opts, "realms");
+ kropts =[{'name':'kdc', 'type':'option', 'value':ds.getServerName()+':88'},
+ {'name':'admin_server', 'type':'option', 'value':ds.getServerName()+':749'},
+ {'name':'default_domain', 'type':'option', 'value':ds.getDomainName()}]
+ ropts = [{'name':ds.getRealmName(), 'type':'subsection', 'value':kropts}]
+ opts.append({'name':'realms', 'type':'section', 'value':ropts})
+ opts.append({'name':'empty', 'type':'empty'})
#[domain_realm]
- opts = [{'name':'.'+ds.getDomainName(), 'value':ds.getRealmName(), 'action':'set'},
- {'name':ds.getDomainName(), 'value':ds.getRealmName(), 'action':'set'}]
- krbconf.changeConf("/etc/krb5.conf", opts, "domain_realm");
+ dropts = [{'name':'.'+ds.getDomainName(), 'type':'option', 'value':ds.getRealmName()},
+ {'name':ds.getDomainName(), 'type':'option', 'value':ds.getRealmName()}]
+ opts.append({'name':'domain_realm', 'type':'section', 'value':dropts})
+ opts.append({'name':'empty', 'type':'empty'})
+
+ #[appdefaults]
+ pamopts = [{'name':'debug', 'type':'option', 'value':'false'},
+ {'name':'ticket_lifetime', 'type':'option', 'value':'36000'},
+ {'name':'renew_lifetime', 'type':'option', 'value':'36000'},
+ {'name':'forwardable', 'type':'option', 'value':'true'},
+ {'name':'krb4_convert', 'type':'option', 'value':'false'}]
+ appopts = [{'name':'pam', 'type':'subsection', 'value':pamopts}]
+ opts.append({'name':'appdefaults', 'type':'section', 'value':appopts})
+
+ krbconf.newConf("/etc/krb5.conf", opts);
#Modify nsswitch to add nss_ldap
run(["/usr/sbin/authconfig", "--enableldap", "--update"])
diff --git a/ipa-client/ipaclient/ipachangeconf.py b/ipa-client/ipaclient/ipachangeconf.py
index 646e0424e..38e5f9973 100644
--- a/ipa-client/ipaclient/ipachangeconf.py
+++ b/ipa-client/ipaclient/ipachangeconf.py
@@ -23,21 +23,22 @@ import fcntl
import os
import string
import time
+import shutil
def openLocked(filename, perms):
- fd = -1
- try:
- fd = os.open(filename, os.O_RDWR | os.O_CREAT, perms)
-
- fcntl.lockf(fd, fcntl.LOCK_EX)
- except OSError, (errno, strerr):
- if fd != -1:
- try:
- os.close(fd)
- except OSError:
- pass
- raise IOError(errno, strerr)
- return os.fdopen(fd, "r+")
+ fd = -1
+ try:
+ fd = os.open(filename, os.O_RDWR | os.O_CREAT, perms)
+
+ fcntl.lockf(fd, fcntl.LOCK_EX)
+ except OSError, (errno, strerr):
+ if fd != -1:
+ try:
+ os.close(fd)
+ except OSError:
+ pass
+ raise IOError(errno, strerr)
+ return os.fdopen(fd, "r+")
#TODO: add subsection as a concept
@@ -49,44 +50,43 @@ class IPAChangeConf:
def __init__(self, name):
self.progname = name
- self.optpre = ("",)
- self.doptpre = self.optpre[0]
- self.assign = (" = ",)
+ self.indent = ("","","")
+ self.assign = (" = ","=")
self.dassign = self.assign[0]
self.comment = ("#",)
self.dcomment = self.comment[0]
self.eol = ("\n",)
self.deol = self.eol[0]
- #self.sectnamdel = ("[","]")
- self.sectnamdel = ()
- self.newsection = False
+ self.sectnamdel = ("[","]")
+ self.subsectdel = ("{","}")
def setProgName(self, name):
self.progname = name
- def setOptionPrefix(self, prefix):
- if type(prefix) is list:
- self.optpre = prefix
+ def setIndent(self, indent):
+ if type(indent) is tuple:
+ self.indent = indent
+ elif type(indent) is str:
+ self.indent = (indent, )
else:
- self.optpre = (prefix, )
- self.doptpre = self.optpre[0]
+ raise ValueError, 'Indent must be a list of strings'
def setOptionAssignment(self, assign):
- if type(assign) is list:
+ if type(assign) is tuple:
self.assign = assign
else:
self.assign = (assign, )
self.dassign = self.assign[0]
def setCommentPrefix(self, comment):
- if type(comment) is list:
+ if type(comment) is tuple:
self.comment = comment
else:
self.comment = (comment, )
self.dcomment = self.comment[0]
def setEndLine(self, eol):
- if type(eol) is list:
+ if type(eol) is tuple:
self.eol = eol
else:
self.eol = (eol, )
@@ -95,56 +95,19 @@ class IPAChangeConf:
def setSectionNameDelimiters(self, delims):
self.sectnamdel = delims
- def confDump(self, options):
- output = ""
-
- #pre conf options delimiter
- output += self.deol
- output += self.dcomment+"["+self.progname+"]--start-line--"+self.deol
- output += self.dcomment+" Generated by authconfig on " + time.strftime("%Y/%m/%d %H:%M:%S") + self.deol
- output += self.dcomment+" DO NOT EDIT THIS SECTION (delimited by --start-line--/--end-line--)"+self.deol
- output += self.dcomment+" Any modification may be deleted or altered by authconfig in future"+self.deol
- output += self.deol
-
- if self.newsection:
- output += getSectionLine(section)
-
- #set options
- for opt in options:
- if opt['action'] == "set":
- output += self.doptpre+opt['name']+self.dassign+opt['value']+self.deol
-
- #post conf options delimiter
- output += self.deol
- output += self.dcomment+"["+self.progname+"]--end-line--"+self.deol
- output += self.deol
-
- return output
-
- def matchAutoEnd(self, line):
- if line.endswith("]--end-line--"):
- return True
- return False
-
- def matchAutoStart(self, line):
- if line.endswith("]--start-line--"):
- return True
- return False
+ def setSubSectionDelimiters(self, delims):
+ self.subsectdel = delims
def matchComment(self, line):
for v in self.comment:
- if line.strip().startswith(v):
- return True
+ if line.lstrip().startswith(v):
+ return line.lstrip()[len(v):]
return False
- def matchLineOption(self, line, opt):
- parts = line.split(self.dassign, 1)
- if len(parts) < 2:
- return False
- elif parts[0].split() == opt['name'].split():
+ def matchEmpty(self, line):
+ if line.strip() == "":
return True
- else:
- return False
+ return False
def matchSection(self, line):
cl = "".join(line.strip().split()).lower()
@@ -156,88 +119,337 @@ class IPAChangeConf:
return False
return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])]
+ def matchSubSection(self, line):
+ if self.matchComment(line):
+ return False
+
+ parts = line.split(self.dassign, 1)
+ if len(parts) < 2:
+ return False
+
+ if parts[1].strip() == self.subsectdel[0]:
+ return parts[0].strip()
+
+ return False
+
+ def matchSubSectionEnd(self, line):
+ if self.matchComment(line):
+ return False
+
+ if line.strip() == self.subsectdel[1]:
+ return True
+
+ return False
+
def getSectionLine(self, section):
if len(self.sectnamdel) != 2:
return section
return self.sectnamdel[0]+section+self.sectnamdel[1]+self.deol
- def checkLineOption(self, line, options):
+ def dump(self, options, level=0):
output = ""
+ if level >= len(self.indent):
+ level = len(self.indent)-1
+
+ for o in options:
+ if o['type'] == "section":
+ output += self.sectnamdel[0]+o['name']+self.sectnamdel[1]+self.deol
+ output += self.dump(o['value'], level+1)
+ continue
+ if o['type'] == "subsection":
+ output += self.indent[level]+o['name']+self.dassign+self.subsectdel[0]+self.deol
+ output += self.dump(o['value'], level+1)
+ output += self.indent[level]+self.subsectdel[1]+self.deol
+ continue
+ if o['type'] == "option":
+ output += self.indent[level]+o['name']+self.dassign+o['value']+self.deol
+ continue
+ if o['type'] == "comment":
+ output += self.dcomment+o['value']+self.deol
+ continue
+ if o['type'] == "empty":
+ output += self.deol
+ continue
+ raise SyntaxError, 'Unknown type: ['+o['type']+']'
- # Check if this is a setting we care about.
- for opt in options:
- if self.matchLineOption(line.strip(), opt):
- output = self.dcomment
- break
+ return output
+
+ def parseLine(self, line):
+
+ if self.matchEmpty(line):
+ return {'name':'empty', 'type':'empty'}
+
+ value = self.matchComment(line)
+ if value:
+ return {'name':'comment', 'type':'comment', 'value':value.rstrip()}
+
+ parts = line.split(self.dassign, 1)
+ if len(parts) < 2:
+ raise SyntaxError, 'Syntax Error: Unknown line format'
+
+ return {'name':parts[0].strip(), 'type':'option', 'value':parts[1].rstrip()}
+
+ def findOpts(self, opts, type, name, exclude_sections=False):
+
+ num = 0
+ for o in opts:
+ if o['type'] == type and o['name'] == name:
+ return (num, o)
+ if exclude_sections and (o['type'] == "section" or o['type'] == "subsection"):
+ return (num, None)
+ num += 1
+ return (num, None)
+
+ def commentOpts(self, inopts, level = 0):
+
+ opts = []
+
+ if level >= len(self.indent):
+ level = len(self.indent)-1
+
+ for o in inopts:
+ if o['type'] == 'section':
+ no = self.commentOpts(o['value'], level+1)
+ val = self.dcomment+self.sectnamdel[0]+o['name']+self.sectnamdel[1]
+ opts.append({'name':'comment', 'type':'comment', 'value':val})
+ for n in no:
+ opts.append(n)
+ continue
+ if o['type'] == 'subsection':
+ no = self.commentOpts(o['value'], level+1)
+ val = self.indent[level]+o['name']+self.dassign+self.subsectdel[0]
+ opts.append({'name':'comment', 'type':'comment', 'value':val})
+ for n in no:
+ opts.append(n)
+ val = self.indent[level]+self.subsectdel[1]
+ opts.append({'name':'comment', 'type':'comment', 'value':val})
+ continue
+ if o['type'] == 'option':
+ val = self.indent[level]+o['name']+self.dassign+o['value']
+ opts.append({'name':'comment', 'type':'comment', 'value':val})
+ continue
+ if o['type'] == 'comment':
+ opts.append(o)
+ continue
+ if o['type'] == 'empty':
+ opts.append({'name':'comment', 'type':'comment', 'value':''})
+ continue
+ raise SyntaxError, 'Unknown type: ['+o['type']+']'
+
+ return opts
+
+ def mergeOld(self, oldopts, newopts):
+
+ opts = []
+
+ for o in oldopts:
+ if o['type'] == "section" or o['type'] == "subsection":
+ (num, no) = self.findOpts(newopts, o['type'], o['name'])
+ if not no:
+ opts.append(o)
+ continue
+ if no['action'] == "set":
+ mo = self.mergeOld(o['value'], no['value'])
+ opts.append({'name':o['name'], 'type':o['type'], 'value':mo})
+ continue
+ if no['action'] == "comment":
+ co = self.commentOpts(o['value'])
+ for c in co:
+ opts.append(c)
+ continue
+ if no['action'] == "remove":
+ continue
+ raise SyntaxError, 'Unknown action: ['+no['action']+']'
- output += line
- return output;
+ if o['type'] == "comment" or o['type'] == "empty":
+ opts.append(o)
+ continue
+
+ if o['type'] == "option":
+ (num, no) = self.findOpts(newopts, 'option', o['name'], True)
+ if not no:
+ opts.append(o)
+ continue
+ if no['action'] == 'comment' or no['action'] == 'remove':
+ if no['value'] != None and o['value'] != no['value']:
+ opts.append(o)
+ continue
+ if no['action'] == 'comment':
+ opts.append({'name':'comment', 'type':'comment',
+ 'value':self.dcomment+o['name']+self.dassign+o['value']})
+ continue
+ if no['action'] == 'set':
+ opts.append(no)
+ continue
+ raise SyntaxError, 'Unknown action: ['+o['action']+']'
+
+ raise SyntaxError, 'Unknown type: ['+o['type']+']'
+
+ return opts
+
+ def mergeNew(self, opts, newopts):
+
+ cline = 0
+
+ for no in newopts:
+
+ if no['type'] == "section" or no['type'] == "subsection":
+ (num, o) = self.findOpts(opts, no['type'], no['name'])
+ if not o:
+ if no['action'] == 'set':
+ opts.append(no)
+ continue
+ if no['action'] == "set":
+ self.mergeNew(o['value'], no['value'])
+ continue
+ cline = num+1
+ continue
+
+ if no['type'] == "option":
+ (num, o) = self.findOpts(opts, no['type'], no['name'], True)
+ if not o:
+ if no['action'] == 'set':
+ opts.append(no)
+ continue
+ cline = num+1
+ continue
+
+ if no['type'] == "comment" or no['type'] == "empty":
+ opts.insert(cline, no)
+ cline += 1
+ continue
+
+ raise SyntaxError, 'Unknown type: ['+no['type']+']'
+
+
+ def merge(self, oldopts, newopts):
+
+ #Use a two pass strategy
+ #First we create a new opts tree from oldopts removing/commenting
+ # the options as indicated by the contents of newopts
+ #Second we fill in the new opts tree with options as indicated
+ # in the newopts tree (this is becaus eentire (sub)sections may
+ # exist in the newopts that do not exist in oldopts)
+
+ opts = self.mergeOld(oldopts, newopts)
+ self.mergeNew(opts, newopts)
+ return opts
+
+ #TODO: Make parse() recursive?
+ def parse(self, f):
+
+ opts = []
+ sectopts = []
+ section = None
+ subsectopts = []
+ subsection = None
+ curopts = opts
+ fatheropts = opts
+
+ # Read in the old file.
+ for line in f:
+
+ # It's a section start.
+ value = self.matchSection(line)
+ if value:
+ if section is not None:
+ opts.append({'name':section, 'type':'section', 'value':sectopts})
+ sectopts = []
+ curopts = sectopts
+ fatheropts = sectopts
+ section = value
+ continue
+
+ # It's a subsection start.
+ value = self.matchSubSection(line)
+ if value:
+ if subsection is not None:
+ raise SyntaxError, 'nested subsections are not supported yet'
+ subsectopts = []
+ curopts = subsectopts
+ subsection = value
+ continue
+
+ value = self.matchSubSectionEnd(line)
+ if value:
+ if subsection is None:
+ raise SyntaxError, 'Unmatched end subsection terminator found'
+ fatheropts.append({'name':subsection, 'type':'subsection', 'value':subsectopts})
+ subsection = None
+ curopts = fatheropts
+ continue
+
+ # Copy anything else as is.
+ curopts.append(self.parseLine(line))
+
+ #Add last section if any
+ if len(sectopts) is not 0:
+ opts.append({'name':section, 'type':'section', 'value':sectopts})
+
+ return opts
# Write settings to configuration file
# file is a path
# options is a set of dictionaries in the form:
# [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}]
# section is a section name like 'global'
- def changeConf(self, file, options, section=None):
+ def changeConf(self, file, newopts):
autosection = False
- savedsection = None
+ savedsection = None
done = False
output = ""
f = None
try:
- f = openLocked(file, 0644)
+ #Do not catch an unexisting file error, we want to fail in that case
+ shutil.copy2(file, file+".ipabkp")
- # Read in the old file.
- for line in f:
+ f = openLocked(file, 0644)
- if autosection:
- if self.matchAutoEnd(line):
- autosection = False
- #skip all previous auto-generated lines
- continue
+ oldopts = self.parse(f)
- if self.matchAutoStart(line):
- autosection = True
- continue
+ options = self.merge(oldopts, newopts)
- # If it's a comment, just pass it through.
- if self.matchComment(line):
- output += line
- continue
+ output = self.dump(options)
- # If it's a section start, note the section name.
- value = self.matchSection(line)
- if value:
-
- # Here we are at start if a new section, if the previous one matches
- # the specified section, dump here before starting the new one
- # the comparison with section == None is intentional and matches the
- # request to dump the options at the end of an implicit initial
- # unmarked section before any section is started
- if savedsection == section:
- output += self.confDump(options)
- done = True
-
- savedsection = value
- output += line
- continue
-
- # Comment out options we care about.
- if savedsection == section:
- output += self.checkLineOption(line, options)
- continue
+ # Write it out and close it.
+ f.seek(0)
+ f.truncate(0)
+ f.write(output)
+ finally:
+ try:
+ if f:
+ f.close()
+ except IOError:
+ pass
+ return True
- # Copy anything else as is.
- output += line
+ # Write settings to new file, backup old
+ # file is a path
+ # options is a set of dictionaries in the form:
+ # [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}]
+ # section is a section name like 'global'
+ def newConf(self, file, options):
+ autosection = False
+ savedsection = None
+ done = False
+ output = ""
+ f = None
+ try:
+ try:
+ shutil.copy2(file, file+".ipabkp")
+ except IOError, err:
+ if err.errno == 2:
+ # The orign file did not exist
+ pass
- if not done:
- if section:
- self.newsection = True
- output += self.confDump(options)
+ f = openLocked(file, 0644)
- # Write it out and close it.
+ # Trunkate
f.seek(0)
f.truncate(0)
+
+ output = self.dump(options)
+
f.write(output)
finally:
try: