1 # This program is free software; you can redistribute it and/or modify
2 # it under the terms of the GNU General Public License as published by
3 # the Free Software Foundation; either version 2 of the License, or
4 # (at your option) any later version.
6 # This program is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # GNU Library General Public License for more details.
11 # You should have received a copy of the GNU General Public License
12 # along with this program; if not, write to the Free Software
13 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
14 # Copyright 2002 Duke University
15 # filched from yum - menno smits wrote this - he rocks
22 from ConfigParser
import NoSectionError
, NoOptionError
, ConfigParser
23 from ConfigParser
import ParsingError
26 CONFIG_FILE
= "/etc/certmaster/certmaster.conf"
28 class ConfigError(exceptions
.Exception):
29 def __init__(self
, value
=None):
30 exceptions
.Exception.__init
__(self
)
33 return "%s" %(self
.value
,)
38 This class handles a single Yum configuration file option. Create
39 subclasses for each type of supported configuration option.
41 Python descriptor foo (__get__ and __set__) is used to make option
42 definition easy and consise.
45 def __init__(self
, default
=None):
48 self
.default
= default
50 def _setattrname(self
):
51 '''Calculate the internal attribute name used to store option state in
52 configuration instances.
54 self
._attrname
= '__opt%d' % id(self
)
56 def __get__(self
, obj
, objtype
):
57 '''Called when the option is read (via the descriptor protocol).
59 @param obj: The configuration instance to modify.
60 @param objtype: The type of the config instance (not used).
61 @return: The parsed option value or the default value if the value
62 was not set in the configuration file.
67 return getattr(obj
, self
._attrname
, None)
69 def __set__(self
, obj
, value
):
70 '''Called when the option is set (via the descriptor protocol).
72 @param obj: The configuration instance to modify.
73 @param value: The value to set the option to.
76 # Only try to parse if its a string
77 if isinstance(value
, basestring
):
79 value
= self
.parse(value
)
81 # Add the field name onto the error
82 raise ValueError('Error parsing %r: %s' % (value
, str(e
)))
84 setattr(obj
, self
._attrname
, value
)
86 def setup(self
, obj
, name
):
87 '''Initialise the option for a config instance.
88 This must be called before the option can be set or retrieved.
90 @param obj: BaseConfig (or subclass) instance.
91 @param name: Name of the option.
93 setattr(obj
, self
._attrname
, copy
.copy(self
.default
))
96 '''Return a safe copy of this Option instance
103 '''Parse the string value to the Option's native value.
105 @param s: Raw string value to parse.
106 @return: Validated native value.
108 Will raise ValueError if there was a problem parsing the string.
109 Subclasses should override this.
113 def tostring(self
, value
):
114 '''Convert the Option's native value to a string value.
116 @param value: Native option value.
117 @return: String representation of input.
119 This does the opposite of the parse() method above.
120 Subclasses should override this.
125 def Inherit(option_obj
):
126 '''Clone an Option instance for the purposes of inheritance. The returned
127 instance has all the same properties as the input Option and shares items
128 such as the default value. Use this to avoid redefinition of reused
131 @param option_obj: Option instance to inherit.
132 @return: New Option instance inherited from the input.
134 new_option
= option_obj
.clone()
135 new_option
.inherit
= True
139 class ListOption(Option
):
141 def __init__(self
, default
=None):
144 super(ListOption
, self
).__init
__(default
)
147 """Converts a string from the config file to a workable list
149 Commas and spaces are used as separators for the list
151 # we need to allow for the '\n[whitespace]' continuation - easier
152 # to sub the \n with a space and then read the lines
153 s
= s
.replace('\n', ' ')
154 s
= s
.replace(',', ' ')
157 def tostring(self
, value
):
158 return '\n '.join(value
)
161 class UrlOption(Option
):
163 This option handles lists of URLs with validation of the URL scheme.
166 def __init__(self
, default
=None, schemes
=('http', 'ftp', 'file', 'https'),
168 super(UrlOption
, self
).__init
__(default
)
169 self
.schemes
= schemes
170 self
.allow_none
= allow_none
172 def parse(self
, url
):
175 # Handle the "_none_" special case
176 if url
.lower() == '_none_':
180 raise ValueError('"_none_" is not a valid value')
182 # Check that scheme is valid
183 (s
,b
,p
,q
,f
,o
) = urlparse
.urlparse(url
)
184 if s
not in self
.schemes
:
185 raise ValueError('URL must be %s not "%s"' % (self
._schemelist
(), s
))
189 def _schemelist(self
):
190 '''Return a user friendly list of the allowed schemes
192 if len(self
.schemes
) < 1:
194 elif len(self
.schemes
) == 1:
195 return self
.schemes
[0]
197 return '%s or %s' % (', '.join(self
.schemes
[:-1]), self
.schemes
[-1])
200 class UrlListOption(ListOption
):
202 Option for handling lists of URLs with validation of the URL scheme.
205 def __init__(self
, default
=None, schemes
=('http', 'ftp', 'file', 'https')):
206 super(UrlListOption
, self
).__init
__(default
)
208 # Hold a UrlOption instance to assist with parsing
209 self
._urloption
= UrlOption(schemes
=schemes
)
213 for url
in super(UrlListOption
, self
).parse(s
):
214 out
.append(self
._urloption
.parse(url
))
218 class IntOption(Option
):
222 except (ValueError, TypeError), e
:
223 raise ValueError('invalid integer value')
226 class BoolOption(Option
):
229 if s
in ('0', 'no', 'false'):
231 elif s
in ('1', 'yes', 'true'):
234 raise ValueError('invalid boolean value')
236 def tostring(self
, value
):
243 class FloatOption(Option
):
246 return float(s
.strip())
247 except (ValueError, TypeError):
248 raise ValueError('invalid float value')
251 class SelectionOption(Option
):
252 '''Handles string values where only specific values are allowed
254 def __init__(self
, default
=None, allowed
=()):
255 super(SelectionOption
, self
).__init
__(default
)
256 self
._allowed
= allowed
259 if s
not in self
._allowed
:
260 raise ValueError('"%s" is not an allowed value' % s
)
263 class BytesOption(Option
):
265 # Multipliers for unit symbols
273 """Parse a friendly bandwidth option to bytes
275 The input should be a string containing a (possibly floating point)
276 number followed by an optional single character unit. Valid units are
277 'k', 'M', 'G'. Case is ignored.
279 Valid inputs: 100, 123M, 45.6k, 12.4G, 100K, 786.3, 0
280 Invalid inputs: -10, -0.1, 45.6L, 123Mb
282 Return value will always be an integer
286 ValueError will be raised if the option couldn't be parsed.
289 raise ValueError("no value specified")
294 mult
= self
.MULTS
.get(unit
, None)
296 raise ValueError("unknown unit '%s'" % unit
)
304 raise ValueError("couldn't convert '%s' to number" % n
)
307 raise ValueError("bytes value may not be negative")
312 class ThrottleOption(BytesOption
):
315 """Get a throttle option.
317 Input may either be a percentage or a "friendly bandwidth value" as
318 accepted by the BytesOption.
320 Valid inputs: 100, 50%, 80.5%, 123M, 45.6k, 12.4G, 100K, 786.0, 0
321 Invalid inputs: 100.1%, -4%, -500
323 Return value will be a int if a bandwidth value was specified or a
324 float if a percentage was given.
326 ValueError will be raised if input couldn't be parsed.
329 raise ValueError("no value specified")
336 raise ValueError("couldn't convert '%s' to number" % n
)
338 raise ValueError("percentage is out of range")
341 return BytesOption
.parse(self
, s
)
344 class BaseConfig(object):
346 Base class for storing configuration definitions. Subclass when creating
353 for name
in self
.iterkeys():
354 option
= self
.optionobj(name
)
355 option
.setup(self
, name
)
359 out
.append('[%s]' % self
._section
)
360 for name
, value
in self
.iteritems():
361 out
.append('%s: %r' % (name
, value
))
362 return '\n'.join(out
)
364 def populate(self
, parser
, section
, parent
=None):
365 '''Set option values from a INI file section.
367 @param parser: ConfParser instance (or subclass)
368 @param section: INI file section to read use.
369 @param parent: Optional parent BaseConfig (or subclass) instance to use
370 when doing option value inheritance.
373 self
._section
= section
375 for name
in self
.iterkeys():
376 option
= self
.optionobj(name
)
379 value
= parser
.get(section
, name
)
380 except (NoSectionError
, NoOptionError
):
381 # No matching option in this section, try inheriting
382 if parent
and option
.inherit
:
383 value
= getattr(parent
, name
)
385 if value
is not None:
386 setattr(self
, name
, value
)
388 def optionobj(cls
, name
):
389 '''Return the Option instance for the given name
391 obj
= getattr(cls
, name
, None)
392 if isinstance(obj
, Option
):
396 optionobj
= classmethod(optionobj
)
398 def isoption(cls
, name
):
399 '''Return True if the given name refers to a defined option
406 isoption
= classmethod(isoption
)
409 '''Yield the names of all defined options in the instance.
411 for name
, item
in self
.iteritems():
415 '''Yield (name, value) pairs for every option in the instance.
417 The value returned is the parsed, validated option value.
419 # Use dir() so that we see inherited options too
420 for name
in dir(self
):
421 if self
.isoption(name
):
422 yield (name
, getattr(self
, name
))
424 def write(self
, fileobj
, section
=None, always
=()):
425 '''Write out the configuration to a file-like object
427 @param fileobj: File-like object to write to
428 @param section: Section name to use. If not-specified the section name
429 used during parsing will be used.
430 @param always: A sequence of option names to always write out.
431 Options not listed here will only be written out if they are at
432 non-default values. Set to None to dump out all options.
434 # Write section heading
436 if self
._section
is None:
437 raise ValueError("not populated, don't know section")
438 section
= self
._section
440 # Updated the ConfigParser with the changed values
441 cfgOptions
= self
.cfg
.options(section
)
442 for name
,value
in self
.iteritems():
443 option
= self
.optionobj(name
)
444 if always
is None or name
in always
or option
.default
!= value
or name
in cfgOptions
:
445 self
.cfg
.set(section
,name
, option
.tostring(value
))
446 # write the updated ConfigParser to the fileobj.
447 self
.cfg
.write(fileobj
)
449 def getConfigOption(self
, option
, default
=None):
450 warnings
.warn('getConfigOption() will go away in a future version of certmaster.\n'
451 'Please access option values as attributes or using getattr().',
453 if hasattr(self
, option
):
454 return getattr(self
, option
)
457 def setConfigOption(self
, option
, value
):
458 warnings
.warn('setConfigOption() will go away in a future version of certmaster.\n'
459 'Please set option values as attributes or using setattr().',
461 if hasattr(self
, option
):
462 setattr(self
, option
, value
)
464 raise ConfigError
, 'No such option %s' % option
467 def read_config(config_file
, BaseConfigDerived
):
468 confparser
= ConfigParser()
469 opts
= BaseConfigDerived()
470 if os
.path
.exists(config_file
):
472 confparser
.read(config_file
)
473 except ParsingError
, e
:
474 print >> sys
.stderr
, "Error reading config file: %s" % e
476 opts
.populate(confparser
, 'main')
478 ## build up the cas structure
481 ## Add the default items when just using a single ca
482 opts
.ca
[''] = BaseConfigDerived()
483 opts
.ca
[''].hash_function
= None
484 opts
.ca
[''].populate(confparser
,'main')
486 if opts
.ca
[''].hash_function
== 'sha1':
487 log
.warning('hash_function value of sha1 is deprecated', DeprecationWarning)
488 elif opts
.ca
[''].hash_function
== 'md5':
489 print >> sys
.stderr
, "Error: hash_function of md5 is not supported"
491 ## Add additonal ca sections
492 sections
= confparser
.sections()
493 for a_section
in sections
:
494 if a_section
.startswith('ca:'):
495 ca_name
= a_section
[3:]
496 opts
.ca
[ca_name
] = BaseConfigDerived()
497 opts
.ca
[ca_name
].hash_function
= None
498 opts
.ca
[ca_name
].populate(confparser
,a_section
)
499 opts
.ca
[ca_name
].cakey
= None
500 opts
.ca
[ca_name
].cacert
= None
502 if opts
.ca
[ca_name
].hash_function
== 'sha1':
503 warnings
.warn('hash_function value of sha1 is deprecated in ca:%s section' % ca_name
, DeprecationWarning)
504 elif opts
.ca
[ca_name
].hash_function
== 'md5':
505 print >> sys
.stderr
, "Error: hash_function of md5 is not supported in ca:% section" % ca_name