summaryrefslogtreecommitdiffstats
path: root/ipa-client/ipaclient
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 /ipa-client/ipaclient
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
Diffstat (limited to 'ipa-client/ipaclient')
-rw-r--r--ipa-client/ipaclient/ipachangeconf.py460
1 files changed, 336 insertions, 124 deletions
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: