"Fossies" - the Fresh Open Source Software Archive

Member "getmail-5.16/getmailcore/baseclasses.py" (31 Oct 2021, 14670 Bytes) of package /linux/misc/getmail-5.16.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "baseclasses.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 5.15_vs_5.16.

    1 #!/usr/bin/env python2
    2 '''Base classes used elsewhere in the package.
    3 
    4 '''
    5 
    6 __all__ = [
    7     'ConfigurableBase',
    8     'ForkingBase',
    9     'ConfInstance',
   10     'ConfString',
   11     'ConfBool',
   12     'ConfInt',
   13     'ConfTupleOfStrings',
   14     'ConfTupleOfUnicode',
   15     'ConfTupleOfTupleOfStrings',
   16     'ConfPassword',
   17     'ConfDirectory',
   18     'ConfFile',
   19     'ConfMaildirPath',
   20     'ConfMboxPath',
   21 ]
   22 
   23 import sys
   24 import os
   25 import time
   26 import signal
   27 import types
   28 
   29 from getmailcore.exceptions import *
   30 from getmailcore.compatibility import *
   31 import getmailcore.logging
   32 from getmailcore.utilities import eval_bool, expand_user_vars
   33 
   34 #
   35 # Base classes
   36 #
   37 
   38 
   39 class ConfItem:
   40     securevalue = False
   41     def __init__(self, name, dtype, default=None, required=True):
   42         self.log = getmailcore.logging.Logger()
   43         self.name = name
   44         self.dtype = dtype
   45         self.default = default
   46         self.required = required
   47 
   48     def validate(self, configuration, val=None):
   49         if val is None:
   50             # If not passed in by subclass
   51             val = configuration.get(self.name, None)
   52         if val is None:
   53             # Not provided.
   54             if self.required:
   55                 raise getmailConfigurationError(
   56                     '%s: missing required configuration parameter' % self.name
   57                 )
   58             # Use default.
   59             return self.default
   60         if type(val) is not self.dtype and val != self.default:
   61             # Got value, but not of expected type.  Try to convert.
   62             if self.securevalue:
   63                 self.log.debug('converting %s to type %s\n'
   64                                % (self.name, self.dtype))
   65             else:
   66                 self.log.debug('converting %s (%s) to type %s\n'
   67                                % (self.name, val, self.dtype))
   68 
   69             try:
   70                 if self.dtype == bool:
   71                     val = eval_bool(val)
   72                 else:
   73                     val = self.dtype(eval(val))
   74             except (ValueError, SyntaxError, TypeError), o:
   75                 raise getmailConfigurationError(
   76                     '%s: configuration value (%s) not of required type %s (%s)'
   77                     % (self.name, val, self.dtype, o)
   78                 )
   79         return val
   80 
   81 class ConfInstance(ConfItem):
   82     def __init__(self, name, default=None, required=True):
   83         ConfItem.__init__(self, name, types.InstanceType, default=default,
   84                           required=required)
   85 
   86 class ConfString(ConfItem):
   87     def __init__(self, name, default=None, required=True):
   88         ConfItem.__init__(self, name, str, default=default, required=required)
   89 
   90 class ConfBool(ConfItem):
   91     def __init__(self, name, default=None, required=True):
   92         ConfItem.__init__(self, name, bool, default=default, required=required)
   93 
   94 class ConfInt(ConfItem):
   95     def __init__(self, name, default=None, required=True):
   96         ConfItem.__init__(self, name, int, default=default, required=required)
   97 
   98 class ConfTupleOfStrings(ConfString):
   99     def __init__(self, name, default=None, required=True):
  100         ConfString.__init__(self, name, default=default, required=required)
  101 
  102     def validate(self, configuration):
  103         val = ConfItem.validate(self, configuration)
  104         try:
  105             if not val:
  106                 val = '()'
  107             tup = eval(val)
  108             if type(tup) != tuple:
  109                 raise ValueError('not a tuple')
  110             val = tup
  111         except (ValueError, SyntaxError), o:
  112             raise getmailConfigurationError(
  113                 '%s: incorrect format (%s)' % (self.name, o)
  114             )
  115         result = [str(item) for item in val]
  116         return tuple(result)
  117 
  118 class ConfTupleOfUnicode(ConfString):
  119     def __init__(self, name, default=None, required=True, allow_specials=()):
  120         ConfString.__init__(self, name, default=default, required=required)
  121         self.specials = allow_specials
  122 
  123     def validate(self, configuration):
  124         _locals = dict([(v, v) for v in self.specials])
  125         val = ConfItem.validate(self, configuration)
  126         try:
  127             if not val:
  128                 val = '()'
  129             tup = eval(val, {}, _locals)
  130             if tup in self.specials:
  131                 val = [tup]
  132             else:
  133                 if type(tup) != tuple:
  134                     raise ValueError('not a tuple')
  135                 vals = []
  136                 for item in tup:
  137                     item = str(item)
  138                     try:
  139                         vals.append(item.decode('ascii'))
  140                     except UnicodeError, o:
  141                         try:
  142                             vals.append(item.decode('utf-8'))
  143                         except UnicodeError, o:
  144                             raise ValueError('not ascii or utf-8: %s' % item)
  145                 val = vals
  146         except (ValueError, SyntaxError), o:
  147             raise getmailConfigurationError(
  148                 '%s: incorrect format (%s)' % (self.name, o)
  149             )
  150         return tuple(val)
  151 
  152 class ConfTupleOfTupleOfStrings(ConfString):
  153     def __init__(self, name, default=None, required=True):
  154         ConfString.__init__(self, name, default=default, required=required)
  155 
  156     def validate(self, configuration):
  157         val = ConfItem.validate(self, configuration)
  158         try:
  159             if not val:
  160                 val = '()'
  161             tup = eval(val)
  162             if type(tup) != tuple:
  163                 raise ValueError('not a tuple')
  164             val = tup
  165         except (ValueError, SyntaxError), o:
  166             raise getmailConfigurationError(
  167                 '%s: incorrect format (%s)' % (self.name, o)
  168             )
  169         for tup in val:
  170             if type(tup) != tuple:
  171                 raise ValueError('contained value "%s" not a tuple' % tup)
  172             if len(tup) != 2:
  173                 raise ValueError('contained value "%s" not length 2' % tup)
  174             for part in tup:
  175                 if type(part) != str:
  176                     raise ValueError('contained value "%s" has non-string part '
  177                                      '"%s"' % (tup, part))
  178 
  179         return val
  180 
  181 class ConfPassword(ConfString):
  182     securevalue = True
  183 
  184 class ConfDirectory(ConfString):
  185     def __init__(self, name, default=None, required=True):
  186         ConfString.__init__(self, name, default=default, required=required)
  187 
  188     def validate(self, configuration):
  189         val = ConfString.validate(self, configuration)
  190         if val is None:
  191             return None
  192         val = expand_user_vars(val)
  193         if not os.path.isdir(val):
  194             raise getmailConfigurationError(
  195                 '%s: specified directory "%s" does not exist' % (self.name, val)
  196             )
  197         return val
  198 
  199 class ConfFile(ConfString):
  200     def __init__(self, name, default=None, required=True):
  201         ConfString.__init__(self, name, default=default, required=required)
  202 
  203     def validate(self, configuration):
  204         val = ConfString.validate(self, configuration)
  205         if val is None:
  206             return None
  207         val = expand_user_vars(val)
  208         if not os.path.isfile(val):
  209             raise getmailConfigurationError(
  210                 '%s: specified file "%s" does not exist' % (self.name, val)
  211             )
  212         return val
  213 
  214 class ConfMaildirPath(ConfDirectory):
  215     def validate(self, configuration):
  216         val = ConfDirectory.validate(self, configuration)
  217         if val is None:
  218             return None
  219         if not val.endswith('/'):
  220             raise getmailConfigurationError(
  221                 '%s: maildir must end with "/"' % self.name
  222             )
  223         for subdir in ('cur', 'new', 'tmp'):
  224             subdirpath = os.path.join(val, subdir)
  225             if not os.path.isdir(subdirpath):
  226                 raise getmailConfigurationError(
  227                     '%s: maildir subdirectory "%s" does not exist'
  228                     % (self.name, subdirpath)
  229                 )
  230         return val
  231 
  232 class ConfMboxPath(ConfString):
  233     def __init__(self, name, default=None, required=True):
  234         ConfString.__init__(self, name, default=default, required=required)
  235 
  236     def validate(self, configuration):
  237         val = ConfString.validate(self, configuration)
  238         if val is None:
  239             return None
  240         val = expand_user_vars(val)
  241         if not os.path.isfile(val):
  242             raise getmailConfigurationError(
  243                 '%s: specified mbox file "%s" does not exist' % (self.name, val)
  244             )
  245         fd = os.open(val, os.O_RDWR)
  246         status_old = os.fstat(fd)
  247         f = os.fdopen(fd, 'r+b')
  248         # Check if it _is_ an mbox file.  mbox files must start with "From "
  249         # in their first line, or are 0-length files.
  250         f.seek(0, 0)
  251         first_line = f.readline()
  252         if first_line and first_line[:5] != 'From ':
  253             # Not an mbox file; abort here
  254             raise getmailConfigurationError('%s: not an mboxrd file' % val)
  255         # Reset atime and mtime
  256         try:
  257             os.utime(val, (status_old.st_atime, status_old.st_mtime))
  258         except OSError, o:
  259             # Not root or owner; readers will not be able to reliably
  260             # detect new mail.  But you shouldn't be delivering to
  261             # other peoples' mboxes unless you're root, anyways.
  262             pass
  263 
  264         return val
  265 
  266 
  267 #######################################
  268 class ConfigurableBase(object):
  269     '''Base class for user-configurable classes.
  270 
  271     Sub-classes must provide the following data attributes and methods:
  272 
  273       _confitems - a tuple of dictionaries representing the parameters the class
  274                    takes.  Each dictionary should contain the following key,
  275                    value pairs:
  276                      - name - parameter name
  277                      - type - a type function to compare the parameter value
  278                        against (i.e. str, int, bool)
  279                      - default - optional default value.  If not preseent, the
  280                        parameter is required.
  281     '''
  282 
  283     def __init__(self, **args):
  284         self.log = getmailcore.logging.Logger()
  285         self.log.trace()
  286         self.conf = {}
  287         allowed_params = set([item.name for item in self._confitems])
  288         for (name, value) in args.items():
  289             if not name in allowed_params:
  290                 self.log.warning('Warning: ignoring unknown parameter "%s" '
  291                                  '(value: %s)\n' % (name, value))
  292                 continue
  293             if name.lower() == 'password':
  294                 self.log.trace('setting %s to * (%s)\n' % (name, type(value)))
  295             else:
  296                 self.log.trace('setting %s to "%s" (%s)\n'
  297                                % (name, value, type(value)))
  298             self.conf[name] = value
  299         self.__confchecked = False
  300         self.checkconf()
  301 
  302     def checkconf(self):
  303         self.log.trace()
  304         if self.__confchecked:
  305             return
  306         for item in self._confitems:
  307             # New class-based configuration item
  308             self.log.trace('checking %s\n' % item.name)
  309             self.conf[item.name] = item.validate(self.conf)
  310         unknown_params = frozenset(self.conf.keys()).difference(
  311             frozenset([item.name for item in self._confitems])
  312         )
  313         for param in sorted(list(unknown_params), key=str.lower):
  314             self.log.warning('Warning: ignoring unknown parameter "%s" '
  315                              '(value: %s)\n' % (param, self.conf[param]))
  316         self.__confchecked = True
  317         self.log.trace('done\n')
  318 
  319     def _confstring(self):
  320         self.log.trace()
  321         confstring = ''
  322         names = self.conf.keys()
  323         names.sort()
  324         for name in names:
  325             if name.lower() == 'configparser':
  326                 continue
  327             if confstring:
  328                 confstring += ', '
  329             if name.lower() == 'password':
  330                 confstring += '%s="*"' % name
  331             else:
  332                 confstring += '%s="%s"' % (name, self.conf[name])
  333         return confstring
  334 
  335 #######################################
  336 class ForkingBase(object):
  337     '''Base class for classes which fork children and wait for them to exit.
  338 
  339     Sub-classes must provide the following data attributes and methods:
  340 
  341         log - an object of type getmailcore.logging.Logger()
  342 
  343     '''
  344     def _child_handler(self, sig, stackframe):
  345         self.log.trace('handler called for signal %s' % sig)
  346         try:
  347             pid, r = os.wait()
  348         except OSError, o:
  349             # No children on SIGCHLD.  Can't happen?
  350             self.log.warning('handler called, but no children (%s)' % o)
  351             return
  352         signal.signal(signal.SIGCHLD, self.__orig_handler)
  353         self.__child_pid = pid
  354         self.__child_status = r
  355         self.log.trace('handler reaped child %s with status %s' % (pid, r))
  356         self.__child_exited = True
  357 
  358     def _prepare_child(self):
  359         self.log.trace('')
  360         self.__child_exited = False
  361         self.__child_pid = None
  362         self.__child_status = None
  363         self.__orig_handler = signal.signal(signal.SIGCHLD, self._child_handler)
  364 
  365     def _wait_for_child(self, childpid):
  366         while not self.__child_exited:
  367             # Could implement a maximum wait time here
  368             self.log.trace('waiting for child %d' % childpid)
  369             time.sleep(1.0)
  370             #raise getmailDeliveryError('failed waiting for commands %s %d (%s)'
  371             #                           % (self.conf['command'], childpid, o))
  372         if self.__child_pid != childpid:
  373             #self.log.error('got child pid %d, not %d' % (pid, childpid))
  374             raise getmailOperationError(
  375                 'got child pid %d, not %d'
  376                 % (self.__child_pid, childpid)
  377             )
  378         if os.WIFSTOPPED(self.__child_status):
  379             raise getmailOperationError(
  380                 'child pid %d stopped by signal %d'
  381                 % (self.__child_pid, os.WSTOPSIG(self.__child_status))
  382             )
  383         if os.WIFSIGNALED(self.__child_status):
  384             raise getmailOperationError(
  385                 'child pid %d killed by signal %d'
  386                 % (self.__child_pid, os.WTERMSIG(self.__child_status))
  387             )
  388         if not os.WIFEXITED(self.__child_status):
  389             raise getmailOperationError('child pid %d failed to exit'
  390                                         % self.__child_pid)
  391         exitcode = os.WEXITSTATUS(self.__child_status)
  392 
  393         return exitcode
  394 
  395 
  396 # For Python 2.3, which lacks the sorted() builtin
  397 if sys.hexversion < 0x02040000:
  398     def sorted(l, key=lambda x: x):
  399         lst = [(key(item), item) for item in l]
  400         lst.sort()
  401         return [val for (unused, val) in lst]
  402     __all__.append('sorted')