~ubuntu-branches/ubuntu/saucy/sssd/saucy

« back to all changes in this revision

Viewing changes to src/config/ipachangeconf.py

  • Committer: Stéphane Graber
  • Date: 2011-06-15 16:23:14 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: stgraber@ubuntu.com-20110615162314-rbhoppnpaxfqo5q7
Merge 1.5.8

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# ipachangeconf - configuration file manipulation classes and functions
 
3
# partially based on authconfig code
 
4
# Copyright (c) 1999-2007 Red Hat, Inc.
 
5
# Author: Simo Sorce <ssorce@redhat.com>
 
6
#
 
7
# This is free software; you can redistribute it and/or modify it
 
8
# under the terms of the GNU General Public License as published by
 
9
# the Free Software Foundation; version 2 only
 
10
#
 
11
# This program is distributed in the hope that it will be useful, but
 
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
14
# General Public License for more details.
 
15
#
 
16
# You should have received a copy of the GNU General Public License
 
17
# along with this program; if not, write to the Free Software
 
18
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
19
#
 
20
 
 
21
import fcntl
 
22
import os
 
23
import string
 
24
import time
 
25
import shutil
 
26
import re
 
27
 
 
28
def openLocked(filename, perms, create = True):
 
29
    fd = -1
 
30
 
 
31
    flags = os.O_RDWR
 
32
    if create:
 
33
        flags = flags | os.O_CREAT
 
34
 
 
35
    try:
 
36
        fd = os.open(filename, flags, perms)
 
37
        fcntl.lockf(fd, fcntl.LOCK_EX)
 
38
    except OSError, (errno, strerr):
 
39
        if fd != -1:
 
40
            try:
 
41
                os.close(fd)
 
42
            except OSError:
 
43
                pass
 
44
        raise IOError(errno, strerr)
 
45
    return os.fdopen(fd, "r+")
 
46
 
 
47
 
 
48
    #TODO: add subsection as a concept
 
49
    #      (ex. REALM.NAME = { foo = x bar = y } )
 
50
    #TODO: put section delimiters as separating element of the list
 
51
    #      so that we can process multiple sections in one go
 
52
    #TODO: add a comment all but provided options as a section option
 
53
class IPAChangeConf:
 
54
 
 
55
    def __init__(self, name):
 
56
        self.progname = name
 
57
        self.indent = ("","","")
 
58
        self.assign = (" = ","=")
 
59
        self.dassign = self.assign[0]
 
60
        self.comment = ("#",)
 
61
        self.dcomment = self.comment[0]
 
62
        self.eol = ("\n",)
 
63
        self.deol = self.eol[0]
 
64
        self.sectnamdel = ("[","]")
 
65
        self.subsectdel = ("{","}")
 
66
        self.backup_suffix = ".ipabkp"
 
67
 
 
68
    def setProgName(self, name):
 
69
        self.progname = name
 
70
 
 
71
    def setIndent(self, indent):
 
72
        if type(indent) is tuple:
 
73
            self.indent = indent
 
74
        elif type(indent) is str:
 
75
            self.indent = (indent, )
 
76
        else:
 
77
           raise ValueError, 'Indent must be a list of strings'
 
78
 
 
79
    def setOptionAssignment(self, assign):
 
80
        if type(assign) is tuple:
 
81
            self.assign = assign
 
82
        else:
 
83
            self.assign = (assign, )
 
84
        self.dassign = self.assign[0]
 
85
 
 
86
    def setCommentPrefix(self, comment):
 
87
        if type(comment) is tuple:
 
88
            self.comment = comment
 
89
        else:
 
90
            self.comment = (comment, )
 
91
        self.dcomment = self.comment[0]
 
92
 
 
93
    def setEndLine(self, eol):
 
94
        if type(eol) is tuple:
 
95
            self.eol = eol
 
96
        else:
 
97
            self.eol = (eol, )
 
98
        self.deol = self.eol[0]
 
99
 
 
100
    def setSectionNameDelimiters(self, delims):
 
101
        self.sectnamdel = delims
 
102
 
 
103
    def setSubSectionDelimiters(self, delims):
 
104
        self.subsectdel = delims
 
105
 
 
106
    def matchComment(self, line):
 
107
        for v in self.comment:
 
108
            if line.lstrip().startswith(v):
 
109
                return line.lstrip()[len(v):]
 
110
        return False
 
111
 
 
112
    def matchEmpty(self, line):
 
113
        if line.strip() == "":
 
114
            return True
 
115
        return False
 
116
 
 
117
    def matchSection(self, line):
 
118
        cl = "".join(line.strip().split())
 
119
        if len(self.sectnamdel) != 2:
 
120
            return False
 
121
        if not cl.startswith(self.sectnamdel[0]):
 
122
            return False
 
123
        if not cl.endswith(self.sectnamdel[1]):
 
124
            return False
 
125
        return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])]
 
126
 
 
127
    def matchSubSection(self, line):
 
128
        if self.matchComment(line):
 
129
            return False
 
130
 
 
131
        parts = line.split(self.dassign, 1)
 
132
        if len(parts) < 2:
 
133
            return False
 
134
 
 
135
        if parts[1].strip() == self.subsectdel[0]:
 
136
            return parts[0].strip()
 
137
 
 
138
        return False
 
139
 
 
140
    def matchSubSectionEnd(self, line):
 
141
        if self.matchComment(line):
 
142
            return False
 
143
 
 
144
        if line.strip() == self.subsectdel[1]:
 
145
            return True
 
146
 
 
147
        return False
 
148
 
 
149
    def getSectionLine(self, section):
 
150
        if len(self.sectnamdel) != 2:
 
151
            return section
 
152
        return self.sectnamdel[0]+section+self.sectnamdel[1]+self.deol
 
153
 
 
154
    def dump(self, options, level=0):
 
155
        output = ""
 
156
        if level >= len(self.indent):
 
157
            level = len(self.indent)-1
 
158
 
 
159
        for o in options:
 
160
            if o['type'] == "section":
 
161
                output += self.sectnamdel[0]+o['name']+self.sectnamdel[1]+self.deol
 
162
                output += self.dump(o['value'], level+1)
 
163
                continue
 
164
            if o['type'] == "subsection":
 
165
                output += self.indent[level]+o['name']+self.dassign+self.subsectdel[0]+self.deol
 
166
                output += self.dump(o['value'], level+1)
 
167
                output += self.indent[level]+self.subsectdel[1]+self.deol
 
168
                continue
 
169
            if o['type'] == "option":
 
170
                output += self.indent[level]+o['name']+self.dassign+o['value']+self.deol
 
171
                continue
 
172
            if o['type'] == "comment":
 
173
                output += self.dcomment+o['value']+self.deol
 
174
                continue
 
175
            if o['type'] == "empty":
 
176
                output += self.deol
 
177
                continue
 
178
            raise SyntaxError, 'Unknown type: ['+o['type']+']'
 
179
 
 
180
        return output
 
181
 
 
182
    def parseLine(self, line):
 
183
 
 
184
        if self.matchEmpty(line):
 
185
            return {'name':'empty', 'type':'empty'}
 
186
 
 
187
        value = self.matchComment(line)
 
188
        if value:
 
189
            return {'name':'comment', 'type':'comment', 'value':value.rstrip()}
 
190
 
 
191
        parts = line.split(self.dassign, 1)
 
192
        if len(parts) < 2:
 
193
            raise SyntaxError, 'Syntax Error: Unknown line format'
 
194
 
 
195
        return {'name':parts[0].strip(), 'type':'option', 'value':parts[1].rstrip()}
 
196
 
 
197
    def findOpts(self, opts, type, name, exclude_sections=False):
 
198
 
 
199
        num = 0
 
200
        for o in opts:
 
201
            if o['type'] == type and o['name'] == name:
 
202
                return (num, o)
 
203
            if exclude_sections and (o['type'] == "section" or o['type'] == "subsection"):
 
204
                return (num, None)
 
205
            num += 1
 
206
        return (num, None)
 
207
 
 
208
    def commentOpts(self, inopts, level = 0):
 
209
 
 
210
        opts = []
 
211
 
 
212
        if level >= len(self.indent):
 
213
            level = len(self.indent)-1
 
214
 
 
215
        for o in inopts:
 
216
            if o['type'] == 'section':
 
217
                no = self.commentOpts(o['value'], level+1)
 
218
                val = self.dcomment+self.sectnamdel[0]+o['name']+self.sectnamdel[1]
 
219
                opts.append({'name':'comment', 'type':'comment', 'value':val})
 
220
                for n in no:
 
221
                    opts.append(n)
 
222
                continue
 
223
            if o['type'] == 'subsection':
 
224
                no = self.commentOpts(o['value'], level+1)
 
225
                val = self.indent[level]+o['name']+self.dassign+self.subsectdel[0]
 
226
                opts.append({'name':'comment', 'type':'comment', 'value':val})
 
227
                for n in no:
 
228
                    opts.append(n)
 
229
                val = self.indent[level]+self.subsectdel[1]
 
230
                opts.append({'name':'comment', 'type':'comment', 'value':val})
 
231
                continue
 
232
            if o['type'] == 'option':
 
233
                val = self.indent[level]+o['name']+self.dassign+o['value']
 
234
                opts.append({'name':'comment', 'type':'comment', 'value':val})
 
235
                continue
 
236
            if o['type'] == 'comment':
 
237
                opts.append(o)
 
238
                continue
 
239
            if o['type'] == 'empty':
 
240
                opts.append({'name':'comment', 'type':'comment', 'value':''})
 
241
                continue
 
242
            raise SyntaxError, 'Unknown type: ['+o['type']+']'
 
243
 
 
244
        return opts
 
245
 
 
246
    def mergeOld(self, oldopts, newopts):
 
247
 
 
248
        opts = []
 
249
 
 
250
        for o in oldopts:
 
251
            if o['type'] == "section" or o['type'] == "subsection":
 
252
                (num, no) = self.findOpts(newopts, o['type'], o['name'])
 
253
                if not no:
 
254
                    opts.append(o)
 
255
                    continue
 
256
                if no['action'] == "set":
 
257
                    mo = self.mergeOld(o['value'], no['value'])
 
258
                    opts.append({'name':o['name'], 'type':o['type'], 'value':mo})
 
259
                    continue
 
260
                if no['action'] == "comment":
 
261
                    co = self.commentOpts(o['value'])
 
262
                    for c in co:
 
263
                        opts.append(c)
 
264
                    continue
 
265
                if no['action'] == "remove":
 
266
                    continue
 
267
                raise SyntaxError, 'Unknown action: ['+no['action']+']'
 
268
 
 
269
            if o['type'] == "comment" or o['type'] == "empty":
 
270
                 opts.append(o)
 
271
                 continue
 
272
 
 
273
            if o['type'] == "option":
 
274
                (num, no) = self.findOpts(newopts, 'option', o['name'], True)
 
275
                if not no:
 
276
                    opts.append(o)
 
277
                    continue
 
278
                if no['action'] == 'comment' or no['action'] == 'remove':
 
279
                    if no['value'] != None and o['value'] != no['value']:
 
280
                        opts.append(o)
 
281
                        continue
 
282
                    if no['action'] == 'comment':
 
283
                       opts.append({'name':'comment', 'type':'comment',
 
284
                                    'value':self.dcomment+o['name']+self.dassign+o['value']})
 
285
                    continue
 
286
                if no['action'] == 'set':
 
287
                    opts.append(no)
 
288
                    continue
 
289
                raise SyntaxError, 'Unknown action: ['+o['action']+']'
 
290
 
 
291
            raise SyntaxError, 'Unknown type: ['+o['type']+']'
 
292
 
 
293
        return opts
 
294
 
 
295
    def mergeNew(self, opts, newopts):
 
296
 
 
297
        cline = 0
 
298
 
 
299
        for no in newopts:
 
300
 
 
301
            if no['type'] == "section" or no['type'] == "subsection":
 
302
                (num, o) = self.findOpts(opts, no['type'], no['name'])
 
303
                if not o:
 
304
                    if no['action'] == 'set':
 
305
                        opts.append(no)
 
306
                    continue
 
307
                if no['action'] == "set":
 
308
                    self.mergeNew(o['value'], no['value'])
 
309
                    continue
 
310
                cline = num+1
 
311
                continue
 
312
 
 
313
            if no['type'] == "option":
 
314
                (num, o) = self.findOpts(opts, no['type'], no['name'], True)
 
315
                if not o:
 
316
                    if no['action'] == 'set':
 
317
                        opts.append(no)
 
318
                    continue
 
319
                cline = num+1
 
320
                continue
 
321
 
 
322
            if no['type'] == "comment" or no['type'] == "empty":
 
323
                opts.insert(cline, no)
 
324
                cline += 1
 
325
                continue
 
326
 
 
327
            raise SyntaxError, 'Unknown type: ['+no['type']+']'
 
328
 
 
329
 
 
330
    def merge(self, oldopts, newopts):
 
331
 
 
332
        #Use a two pass strategy
 
333
        #First we create a new opts tree from oldopts removing/commenting
 
334
        #  the options as indicated by the contents of newopts
 
335
        #Second we fill in the new opts tree with options as indicated
 
336
        #  in the newopts tree (this is becaus eentire (sub)sections may
 
337
        #  exist in the newopts that do not exist in oldopts)
 
338
 
 
339
        opts = self.mergeOld(oldopts, newopts)
 
340
        self.mergeNew(opts, newopts)
 
341
        return opts
 
342
 
 
343
    #TODO: Make parse() recursive?
 
344
    def parse(self, f):
 
345
 
 
346
        opts = []
 
347
        sectopts = []
 
348
        section = None
 
349
        subsectopts = []
 
350
        subsection = None
 
351
        curopts = opts
 
352
        fatheropts = opts
 
353
 
 
354
        # Read in the old file.
 
355
        for line in f:
 
356
 
 
357
            # It's a section start.
 
358
            value = self.matchSection(line)
 
359
            if value:
 
360
                if section is not None:
 
361
                    opts.append({'name':section, 'type':'section', 'value':sectopts})
 
362
                sectopts = []
 
363
                curopts = sectopts
 
364
                fatheropts = sectopts
 
365
                section = value
 
366
                continue
 
367
 
 
368
            # It's a subsection start.
 
369
            value = self.matchSubSection(line)
 
370
            if value:
 
371
                if subsection is not None:
 
372
                    raise SyntaxError, 'nested subsections are not supported yet'
 
373
                subsectopts = []
 
374
                curopts = subsectopts
 
375
                subsection = value
 
376
                continue
 
377
 
 
378
            value = self.matchSubSectionEnd(line)
 
379
            if value:
 
380
                if subsection is None:
 
381
                    raise SyntaxError, 'Unmatched end subsection terminator found'
 
382
                fatheropts.append({'name':subsection, 'type':'subsection', 'value':subsectopts})
 
383
                subsection = None
 
384
                curopts = fatheropts
 
385
                continue
 
386
 
 
387
            # Copy anything else as is.
 
388
            curopts.append(self.parseLine(line))
 
389
 
 
390
        #Add last section if any
 
391
        if len(sectopts) is not 0:
 
392
            opts.append({'name':section, 'type':'section', 'value':sectopts})
 
393
 
 
394
        return opts
 
395
 
 
396
    # Write settings to configuration file
 
397
    # file is a path
 
398
    # options is a set of dictionaries in the form:
 
399
    #     [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}]
 
400
    # section is a section name like 'global'
 
401
    def changeConf(self, file, newopts):
 
402
        autosection = False
 
403
        savedsection = None
 
404
        done = False
 
405
        output = ""
 
406
        f = None
 
407
        try:
 
408
            #Do not catch an unexisting file error, we want to fail in that case
 
409
            shutil.copy2(file, file+self.backup_suffix)
 
410
 
 
411
            f = openLocked(file, 0644)
 
412
 
 
413
            oldopts = self.parse(f)
 
414
 
 
415
            options = self.merge(oldopts, newopts)
 
416
 
 
417
            output = self.dump(options)
 
418
 
 
419
            # Write it out and close it.
 
420
            f.seek(0)
 
421
            f.truncate(0)
 
422
            f.write(output)
 
423
        finally:
 
424
            try:
 
425
                if f:
 
426
                    f.close()
 
427
            except IOError:
 
428
                pass
 
429
        return True
 
430
 
 
431
    # Write settings to new file, backup old
 
432
    # file is a path
 
433
    # options is a set of dictionaries in the form:
 
434
    #     [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}]
 
435
    # section is a section name like 'global'
 
436
    def newConf(self, file, options):
 
437
        autosection = False
 
438
        savedsection = None
 
439
        done = False
 
440
        output = ""
 
441
        f = None
 
442
        try:
 
443
            try:
 
444
                shutil.copy2(file, file+self.backup_suffix)
 
445
            except IOError, err:
 
446
                if err.errno == 2:
 
447
                    # The orign file did not exist
 
448
                    pass
 
449
 
 
450
            f = openLocked(file, 0644)
 
451
 
 
452
            # Trunkate
 
453
            f.seek(0)
 
454
            f.truncate(0)
 
455
 
 
456
            output = self.dump(options)
 
457
 
 
458
            f.write(output)
 
459
        finally:
 
460
            try:
 
461
                if f:
 
462
                    f.close()
 
463
            except IOError:
 
464
                pass
 
465
        return True
 
466
 
 
467
# A SSSD-specific subclass of IPAChangeConf
 
468
class SSSDChangeConf(IPAChangeConf):
 
469
    OPTCRE = re.compile(
 
470
            r'(?P<option>[^:=\s][^:=]*)'          # very permissive!
 
471
            r'\s*=\s*'                            # any number of space/tab,
 
472
                                                  # followed by separator
 
473
                                                  # followed by any # space/tab
 
474
            r'(?P<value>.*)$'                     # everything up to eol
 
475
            )
 
476
 
 
477
    def __init__(self):
 
478
        IPAChangeConf.__init__(self, "SSSD")
 
479
        self.comment = ("#",";")
 
480
        self.backup_suffix = ".bak"
 
481
        self.opts = []
 
482
 
 
483
    def parseLine(self, line):
 
484
        """
 
485
        Overrides IPAChangeConf parseLine so that lines are splitted
 
486
        using any separator in self.assign, not just the default one
 
487
        """
 
488
 
 
489
        if self.matchEmpty(line):
 
490
            return {'name':'empty', 'type':'empty'}
 
491
 
 
492
        value = self.matchComment(line)
 
493
        if value:
 
494
            return {'name':'comment', 'type':'comment', 'value':value.rstrip()}
 
495
 
 
496
        mo = self.OPTCRE.match(line)
 
497
        if not mo:
 
498
            raise SyntaxError, 'Syntax Error: Unknown line format'
 
499
 
 
500
        try:
 
501
            name, value = mo.group('option', 'value')
 
502
        except IndexError:
 
503
            raise SyntaxError, 'Syntax Error: Unknown line format'
 
504
 
 
505
        return {'name':name.strip(), 'type':'option', 'value':value.strip()}
 
506
 
 
507
    def readfp(self, fd):
 
508
        self.opts.extend(self.parse(fd))
 
509
 
 
510
    def read(self, filename):
 
511
        fd = open(filename, 'r')
 
512
        self.readfp(fd)
 
513
        fd.close()
 
514
 
 
515
    def get(self, section, name):
 
516
        index, item = self.get_option_index(section, name)
 
517
        if item:
 
518
            return item['value']
 
519
 
 
520
    def set(self, section, name, value):
 
521
        modkw = { 'type'  : 'section',
 
522
                  'name'  : section,
 
523
                  'value' : [{
 
524
                        'type'  : 'option',
 
525
                        'name'  : name,
 
526
                        'value' : value,
 
527
                        'action': 'set',
 
528
                            }],
 
529
                   'action': 'set',
 
530
                }
 
531
        self.opts = self.merge(self.opts, [ modkw ])
 
532
 
 
533
    def add_section(self, name, optkw, index=0):
 
534
        optkw.append({'type':'empty', 'value':'empty'})
 
535
        addkw = { 'type'   : 'section',
 
536
                   'name'   : name,
 
537
                   'value'  : optkw,
 
538
                }
 
539
        self.opts.insert(index, addkw)
 
540
 
 
541
    def delete_section(self, name):
 
542
        self.delete_option('section', name)
 
543
 
 
544
    def sections(self):
 
545
        return [ o for o in self.opts if o['type'] == 'section' ]
 
546
 
 
547
    def has_section(self, section):
 
548
        return len([ o for o in self.opts if o['type'] == 'section' if o['name'] == section ]) > 0
 
549
 
 
550
    def options(self, section):
 
551
        for opt in self.opts:
 
552
            if opt['type'] == 'section' and opt['name'] == section:
 
553
                return opt['value']
 
554
 
 
555
    def delete_option(self, type, name, exclude_sections=False):
 
556
        return self.delete_option_subtree(self.opts, type, name)
 
557
 
 
558
    def delete_option_subtree(self, subtree, type, name, exclude_sections=False):
 
559
        index, item = self.findOpts(subtree, type, name, exclude_sections)
 
560
        if item:
 
561
            del subtree[index]
 
562
        return index
 
563
 
 
564
    def has_option(self, section, name):
 
565
        index, item = self.get_option_index(section, name)
 
566
        if index != -1 and item != None:
 
567
            return True
 
568
        return False
 
569
 
 
570
    def strip_comments_empty(self, optlist):
 
571
        retlist = []
 
572
        for opt in optlist:
 
573
            if opt['type'] in ('comment', 'empty'):
 
574
                continue
 
575
            retlist.append(opt)
 
576
        return retlist
 
577
 
 
578
    def get_option_index(self, parent_name, name, type='option'):
 
579
        subtree = None
 
580
        if parent_name:
 
581
            pindex, pdata = self.findOpts(self.opts, 'section', parent_name)
 
582
            if not pdata:
 
583
                return (-1, None)
 
584
            subtree = pdata['value']
 
585
        else:
 
586
            subtree = self.opts
 
587
        return self.findOpts(subtree, type, name)
 
588