VirtualMailManager/Handler.py
branchv0.6.x
changeset 318 4dc2edf02d11
parent 316 31d8931dc535
child 319 f4956b4ceba1
equal deleted inserted replaced
317:d619e97a8f18 318:4dc2edf02d11
    41 from VirtualMailManager.Transport import Transport
    41 from VirtualMailManager.Transport import Transport
    42 
    42 
    43 
    43 
    44 _ = lambda msg: msg
    44 _ = lambda msg: msg
    45 
    45 
       
    46 CFG_FILE = 'vmm.cfg'
       
    47 CFG_PATH = '/root:/usr/local/etc:/etc'
    46 RE_DOMAIN_SEARCH = """^[a-z0-9-\.]+$"""
    48 RE_DOMAIN_SEARCH = """^[a-z0-9-\.]+$"""
    47 TYPE_ACCOUNT = 0x1
    49 TYPE_ACCOUNT = 0x1
    48 TYPE_ALIAS = 0x2
    50 TYPE_ALIAS = 0x2
    49 TYPE_RELOCATED = 0x4
    51 TYPE_RELOCATED = 0x4
    50 OTHER_TYPES = {
    52 OTHER_TYPES = {
    55 
    57 
    56 
    58 
    57 class Handler(object):
    59 class Handler(object):
    58     """Wrapper class to simplify the access on all the stuff from
    60     """Wrapper class to simplify the access on all the stuff from
    59     VirtualMailManager"""
    61     VirtualMailManager"""
    60     __slots__ = ('_Cfg', '_cfgFileName', '_dbh', '__warnings')
    62     __slots__ = ('_cfg', '_cfg_fname', '_dbh', '__warnings')
    61 
    63 
    62     def __init__(self, skip_some_checks=False):
    64     def __init__(self, skip_some_checks=False):
    63         """Creates a new Handler instance.
    65         """Creates a new Handler instance.
    64 
    66 
    65         ``skip_some_checks`` : bool
    67         ``skip_some_checks`` : bool
    67             argument may be ``True``. By default it is ``False`` and
    69             argument may be ``True``. By default it is ``False`` and
    68             all checks will be performed.
    70             all checks will be performed.
    69 
    71 
    70         Throws a NotRootError if your uid is greater 0.
    72         Throws a NotRootError if your uid is greater 0.
    71         """
    73         """
    72         self._cfgFileName = ''
    74         self._cfg_fname = ''
    73         self.__warnings = []
    75         self.__warnings = []
    74         self._Cfg = None
    76         self._cfg = None
    75         self._dbh = None
    77         self._dbh = None
    76 
    78 
    77         if os.geteuid():
    79         if os.geteuid():
    78             raise NotRootError(_(u"You are not root.\n\tGood bye!\n"),
    80             raise NotRootError(_(u"You are not root.\n\tGood bye!\n"),
    79                                CONF_NOPERM)
    81                                CONF_NOPERM)
    80         if self.__chkCfgFile():
    82         if self.__check_cfg_file():
    81             self._Cfg = Cfg(self._cfgFileName)
    83             self._cfg = Cfg(self._cfg_fname)
    82             self._Cfg.load()
    84             self._cfg.load()
    83         if not skip_some_checks:
    85         if not skip_some_checks:
    84             self._Cfg.check()
    86             self._cfg.check()
    85             self._chkenv()
    87             self._chkenv()
    86 
    88 
    87     def __findCfgFile(self):
    89     def __find_cfg_file(self):
    88         for path in ['/root', '/usr/local/etc', '/etc']:
    90         """Search the CFG_FILE in CFG_PATH.
    89             tmp = os.path.join(path, 'vmm.cfg')
    91         Raise a VMMError when no vmm.cfg could be found.
       
    92         """
       
    93         for path in CFG_PATH.split(':'):
       
    94             tmp = os.path.join(path, CFG_FILE)
    90             if os.path.isfile(tmp):
    95             if os.path.isfile(tmp):
    91                 self._cfgFileName = tmp
    96                 self._cfg_fname = tmp
    92                 break
    97                 break
    93         if not len(self._cfgFileName):
    98         if not self._cfg_fname:
    94             raise VMMError(_(u"No 'vmm.cfg' found in: "
    99             raise VMMError(_(u"Could not find '%(cfg_file)s' in: "
    95                              u"/root:/usr/local/etc:/etc"), CONF_NOFILE)
   100                              u"'%(cfg_path)s'") % {'cfg_file': CFG_FILE,
    96 
   101                            'cfg_path' : CFG_PATH}, CONF_NOFILE)
    97     def __chkCfgFile(self):
   102 
       
   103     def __check_cfg_file(self):
    98         """Checks the configuration file, returns bool"""
   104         """Checks the configuration file, returns bool"""
    99         self.__findCfgFile()
   105         self.__find_cfg_file()
   100         fstat = os.stat(self._cfgFileName)
   106         fstat = os.stat(self._cfg_fname)
   101         fmode = int(oct(fstat.st_mode & 0777))
   107         fmode = int(oct(fstat.st_mode & 0777))
   102         if fmode % 100 and fstat.st_uid != fstat.st_gid or \
   108         if fmode % 100 and fstat.st_uid != fstat.st_gid or \
   103            fmode % 10 and fstat.st_uid == fstat.st_gid:
   109            fmode % 10 and fstat.st_uid == fstat.st_gid:
   104             raise PermissionError(_(u"wrong permissions for '%(file)s': "
   110             raise PermissionError(_(u"wrong permissions for '%(file)s': "
   105                                     u"%(perms)s\n`chmod 0600 %(file)s` would "
   111                                     u"%(perms)s\n`chmod 0600 %(file)s` would "
   106                                     u"be great.") % {'file': self._cfgFileName,
   112                                     u"be great.") % {'file': self._cfg_fname,
   107                                   'perms': fmode}, CONF_WRONGPERM)
   113                                   'perms': fmode}, CONF_WRONGPERM)
   108         else:
   114         else:
   109             return True
   115             return True
   110 
   116 
   111     def _chkenv(self):
   117     def _chkenv(self):
   112         """"""
   118         """Make sure our base_directory is a directory and that all
   113         basedir = self._Cfg.dget('misc.base_directory')
   119         required executables exists and are executable.
       
   120         If not, a VMMError will be raised"""
       
   121         basedir = self._cfg.dget('misc.base_directory')
   114         if not os.path.exists(basedir):
   122         if not os.path.exists(basedir):
   115             old_umask = os.umask(0006)
   123             old_umask = os.umask(0006)
   116             os.makedirs(basedir, 0771)
   124             os.makedirs(basedir, 0771)
   117             os.chown(basedir, 0, 0)
   125             os.chown(basedir, 0, 0)
   118             os.umask(old_umask)
   126             os.umask(old_umask)
   119         elif not os.path.isdir(basedir):
   127         elif not os.path.isdir(basedir):
   120             raise VMMError(_(u"'%s' is not a directory.\n(vmm.cfg: section "
   128             raise VMMError(_(u"'%(path)s' is not a directory.\n(%(cfg_file)s: "
   121                              u"'misc', option 'base_directory')") % basedir,
   129                              u"section 'misc', option 'base_directory')") %
       
   130                            {'path': basedir, 'cfg_file': self._cfg_fname},
   122                            NO_SUCH_DIRECTORY)
   131                            NO_SUCH_DIRECTORY)
   123         for opt, val in self._Cfg.items('bin'):
   132         for opt, val in self._cfg.items('bin'):
   124             try:
   133             try:
   125                 exec_ok(val)
   134                 exec_ok(val)
   126             except VMMError, err:
   135             except VMMError, err:
   127                 if err.code is NO_SUCH_BINARY:
   136                 if err.code is NO_SUCH_BINARY:
   128                     raise VMMError(_(u"'%(binary)s' doesn't exist.\n(vmm.cfg: "
   137                     raise VMMError(_(u"'%(binary)s' doesn't exist.\n"
   129                                      u"section 'bin', option '%(option)s')") %
   138                                      u"(%(cfg_file)s: section 'bin', option "
   130                                    {'binary': val, 'option': opt}, err.code)
   139                                      u"'%(option)s')") % {'binary': val,
       
   140                                    'cfg_file': self._cfg_fname, 'option': opt},
       
   141                                    err.code)
   131                 elif err.code is NOT_EXECUTABLE:
   142                 elif err.code is NOT_EXECUTABLE:
   132                     raise VMMError(_(u"'%(binary)s' is not executable.\n"
   143                     raise VMMError(_(u"'%(binary)s' is not executable.\n"
   133                                      u"(vmm.cfg: section 'bin', option "
   144                                      u"(%(cfg_file)s: section 'bin', option "
   134                                      u"'%(option)s')") % {'binary': val,
   145                                      u"'%(option)s')") % {'binary': val,
   135                                    'option': opt}, err.code)
   146                                    'cfg_file': self._cfg_fname, 'option': opt},
       
   147                                    err.code)
   136                 else:
   148                 else:
   137                     raise
   149                     raise
   138 
   150 
   139     def __dbConnect(self):
   151     def __dbConnect(self):
   140         """Creates a pyPgSQL.PgSQL.connection instance."""
   152         """Creates a pyPgSQL.PgSQL.connection instance."""
   141         if self._dbh is None or (isinstance(self._dbh, PgSQL.Connection) and
   153         if self._dbh is None or (isinstance(self._dbh, PgSQL.Connection) and
   142                                   not self._dbh._isOpen):
   154                                   not self._dbh._isOpen):
   143             try:
   155             try:
   144                 self._dbh = PgSQL.connect(
   156                 self._dbh = PgSQL.connect(
   145                         database=self._Cfg.dget('database.name'),
   157                         database=self._cfg.dget('database.name'),
   146                         user=self._Cfg.pget('database.user'),
   158                         user=self._cfg.pget('database.user'),
   147                         host=self._Cfg.dget('database.host'),
   159                         host=self._cfg.dget('database.host'),
   148                         password=self._Cfg.pget('database.pass'),
   160                         password=self._cfg.pget('database.pass'),
   149                         client_encoding='utf8', unicode_results=True)
   161                         client_encoding='utf8', unicode_results=True)
   150                 dbc = self._dbh.cursor()
   162                 dbc = self._dbh.cursor()
   151                 dbc.execute("SET NAMES 'UTF8'")
   163                 dbc.execute("SET NAMES 'UTF8'")
   152                 dbc.close()
   164                 dbc.close()
   153             except PgSQL.libpq.DatabaseError, e:
   165             except PgSQL.libpq.DatabaseError, e:
   214 
   226 
   215         Keyword arguments:
   227         Keyword arguments:
   216         directory -- the directory to summarize recursively disk usage for
   228         directory -- the directory to summarize recursively disk usage for
   217         """
   229         """
   218         if self.__isdir(directory):
   230         if self.__isdir(directory):
   219             return Popen([self._Cfg.dget('bin.du'), "-hs", directory],
   231             return Popen([self._cfg.dget('bin.du'), "-hs", directory],
   220                 stdout=PIPE).communicate()[0].split('\t')[0]
   232                 stdout=PIPE).communicate()[0].split('\t')[0]
   221         else:
   233         else:
   222             return 0
   234             return 0
   223 
   235 
   224     def __isdir(self, directory):
   236     def __isdir(self, directory):
   228         return isdir
   240         return isdir
   229 
   241 
   230     def __make_domain_dir(self, domain):
   242     def __make_domain_dir(self, domain):
   231         cwd = os.getcwd()
   243         cwd = os.getcwd()
   232         hashdir, domdir = domain.directory.split(os.path.sep)[-2:]
   244         hashdir, domdir = domain.directory.split(os.path.sep)[-2:]
   233         os.chdir(self._Cfg.dget('misc.base_directory'))
   245         os.chdir(self._cfg.dget('misc.base_directory'))
   234         if not os.path.isdir(hashdir):
   246         if not os.path.isdir(hashdir):
   235             os.mkdir(hashdir, 0711)
   247             os.mkdir(hashdir, 0711)
   236             os.chown(hashdir, 0, 0)
   248             os.chown(hashdir, 0, 0)
   237         os.mkdir(os.path.join(hashdir, domdir),
   249         os.mkdir(os.path.join(hashdir, domdir),
   238                  self._Cfg.dget('domain.directory_mode'))
   250                  self._cfg.dget('domain.directory_mode'))
   239         os.chown(domain.directory, 0, domain.gid)
   251         os.chown(domain.directory, 0, domain.gid)
   240         os.chdir(cwd)
   252         os.chdir(cwd)
   241 
   253 
   242     def __make_home(self, account):
   254     def __make_home(self, account):
   243         """Create a home directory for the new Account *account*."""
   255         """Create a home directory for the new Account *account*."""
   244         os.umask(0007)
   256         os.umask(0007)
   245         os.chdir(account.domain_directory)
   257         os.chdir(account.domain_directory)
   246         os.mkdir('%s' % account.uid, self._Cfg.dget('account.directory_mode'))
   258         os.mkdir('%s' % account.uid, self._cfg.dget('account.directory_mode'))
   247         os.chown('%s' % account.uid, account.uid, account.gid)
   259         os.chown('%s' % account.uid, account.uid, account.gid)
   248 
   260 
   249     def __userDirDelete(self, domdir, uid, gid):
   261     def __userDirDelete(self, domdir, uid, gid):
   250         if uid > 0 and gid > 0:
   262         if uid > 0 and gid > 0:
   251             userdir = '%s' % uid
   263             userdir = '%s' % uid
   268 
   280 
   269     def __domDirDelete(self, domdir, gid):
   281     def __domDirDelete(self, domdir, gid):
   270         if gid > 0:
   282         if gid > 0:
   271             if not self.__isdir(domdir):
   283             if not self.__isdir(domdir):
   272                 return
   284                 return
   273             basedir = self._Cfg.dget('misc.base_directory')
   285             basedir = self._cfg.dget('misc.base_directory')
   274             domdirdirs = domdir.replace(basedir + '/', '').split('/')
   286             domdirdirs = domdir.replace(basedir + '/', '').split('/')
   275             domdirparent = os.path.join(basedir, domdirdirs[0])
   287             domdirparent = os.path.join(basedir, domdirdirs[0])
   276             if basedir.count('..') or domdir.count('..'):
   288             if basedir.count('..') or domdir.count('..'):
   277                 raise VMMError(_(u'Found ".." in domain directory path.'),
   289                 raise VMMError(_(u'Found ".." in domain directory path.'),
   278                                FOUND_DOTS_IN_PATH)
   290                                FOUND_DOTS_IN_PATH)
   298 
   310 
   299     def cfg_dget(self, option):
   311     def cfg_dget(self, option):
   300         """Get the configured value of the *option* (section.option).
   312         """Get the configured value of the *option* (section.option).
   301         When the option was not configured its default value will be
   313         When the option was not configured its default value will be
   302         returned."""
   314         returned."""
   303         return self._Cfg.dget(option)
   315         return self._cfg.dget(option)
   304 
   316 
   305     def cfg_pget(self, option):
   317     def cfg_pget(self, option):
   306         """Get the configured value of the *option* (section.option)."""
   318         """Get the configured value of the *option* (section.option)."""
   307         return self._Cfg.pget(option)
   319         return self._cfg.pget(option)
   308 
   320 
   309     def cfg_install(self):
   321     def cfg_install(self):
   310         """Installs the cfg_dget method as ``cfg_dget`` into the built-in
   322         """Installs the cfg_dget method as ``cfg_dget`` into the built-in
   311         namespace."""
   323         namespace."""
   312         import __builtin__
   324         import __builtin__
   313         assert 'cfg_dget' not in __builtin__.__dict__
   325         assert 'cfg_dget' not in __builtin__.__dict__
   314         __builtin__.__dict__['cfg_dget'] = self._Cfg.dget
   326         __builtin__.__dict__['cfg_dget'] = self._cfg.dget
   315 
   327 
   316     def domainAdd(self, domainname, transport=None):
   328     def domainAdd(self, domainname, transport=None):
   317         dom = self.__getDomain(domainname)
   329         dom = self.__getDomain(domainname)
   318         if transport is None:
   330         if transport is None:
   319             dom.set_transport(Transport(self._dbh,
   331             dom.set_transport(Transport(self._dbh,
   320                               transport=self._Cfg.dget('misc.transport')))
   332                               transport=self._cfg.dget('misc.transport')))
   321         else:
   333         else:
   322             dom.set_transport(Transport(self._dbh, transport=transport))
   334             dom.set_transport(Transport(self._dbh, transport=transport))
   323         dom.set_directory(self._Cfg.dget('misc.base_directory'))
   335         dom.set_directory(self._cfg.dget('misc.base_directory'))
   324         dom.save()
   336         dom.save()
   325         self.__make_domain_dir(dom)
   337         self.__make_domain_dir(dom)
   326 
   338 
   327     def domainTransport(self, domainname, transport, force=None):
   339     def domainTransport(self, domainname, transport, force=None):
   328         if force is not None and force != 'force':
   340         if force is not None and force != 'force':
   340             raise DomainError(_(u"Invalid argument: '%s'") % force,
   352             raise DomainError(_(u"Invalid argument: '%s'") % force,
   341                               INVALID_ARGUMENT)
   353                               INVALID_ARGUMENT)
   342         dom = self.__getDomain(domainname)
   354         dom = self.__getDomain(domainname)
   343         gid = dom.gid
   355         gid = dom.gid
   344         domdir = dom.directory
   356         domdir = dom.directory
   345         if self._Cfg.dget('domain.force_deletion') or force == 'delall':
   357         if self._cfg.dget('domain.force_deletion') or force == 'delall':
   346             dom.delete(True, True)
   358             dom.delete(True, True)
   347         elif force == 'deluser':
   359         elif force == 'deluser':
   348             dom.delete(deluser=True)
   360             dom.delete(deluser=True)
   349         elif force == 'delalias':
   361         elif force == 'delalias':
   350             dom.delete(delalias=True)
   362             dom.delete(delalias=True)
   351         else:
   363         else:
   352             dom.delete()
   364             dom.delete()
   353         if self._Cfg.dget('domain.delete_directory'):
   365         if self._cfg.dget('domain.delete_directory'):
   354             self.__domDirDelete(domdir, gid)
   366             self.__domDirDelete(domdir, gid)
   355 
   367 
   356     def domainInfo(self, domainname, details=None):
   368     def domainInfo(self, domainname, details=None):
   357         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
   369         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
   358                            'relocated']:
   370                            'relocated']:
   444         acc.save()
   456         acc.save()
   445         oldpwd = os.getcwd()
   457         oldpwd = os.getcwd()
   446         self.__make_home(acc)
   458         self.__make_home(acc)
   447         mailbox = new_mailbox(acc)
   459         mailbox = new_mailbox(acc)
   448         mailbox.create()
   460         mailbox.create()
   449         folders = self._Cfg.dget('mailbox.folders').split(':')
   461         folders = self._cfg.dget('mailbox.folders').split(':')
   450         if any(folders):
   462         if any(folders):
   451             bad = mailbox.add_boxes(folders,
   463             bad = mailbox.add_boxes(folders,
   452                                     self._Cfg.dget('mailbox.subscribe'))
   464                                     self._cfg.dget('mailbox.subscribe'))
   453             if bad:
   465             if bad:
   454                 self.__warnings.append(_(u"Skipped mailbox folders:") +
   466                 self.__warnings.append(_(u"Skipped mailbox folders:") +
   455                                        '\n\t- ' + '\n\t- '.join(bad))
   467                                        '\n\t- ' + '\n\t- '.join(bad))
   456         os.chdir(oldpwd)
   468         os.chdir(oldpwd)
   457 
   469 
   483         uid = acc.uid
   495         uid = acc.uid
   484         gid = acc.gid
   496         gid = acc.gid
   485         dom_dir = acc.domain_directory
   497         dom_dir = acc.domain_directory
   486         acc_dir = acc.home
   498         acc_dir = acc.home
   487         acc.delete(bool(force))
   499         acc.delete(bool(force))
   488         if self._Cfg.dget('account.delete_directory'):
   500         if self._cfg.dget('account.delete_directory'):
   489             try:
   501             try:
   490                 self.__userDirDelete(dom_dir, uid, gid)
   502                 self.__userDirDelete(dom_dir, uid, gid)
   491             except VMMError, err:
   503             except VMMError, err:
   492                 if err.code in (FOUND_DOTS_IN_PATH, MAILDIR_PERM_MISMATCH,
   504                 if err.code in (FOUND_DOTS_IN_PATH, MAILDIR_PERM_MISMATCH,
   493                                 NO_SUCH_DIRECTORY):
   505                                 NO_SUCH_DIRECTORY):
   530         if not acc:
   542         if not acc:
   531             if not self._is_other_address(acc.address, TYPE_ACCOUNT):
   543             if not self._is_other_address(acc.address, TYPE_ACCOUNT):
   532                 raise VMMError(_(u"The account '%s' doesn't exist.") %
   544                 raise VMMError(_(u"The account '%s' doesn't exist.") %
   533                                acc.address, NO_SUCH_ACCOUNT)
   545                                acc.address, NO_SUCH_ACCOUNT)
   534         info = acc.get_info()
   546         info = acc.get_info()
   535         if self._Cfg.dget('account.disk_usage') or details in ('du', 'full'):
   547         if self._cfg.dget('account.disk_usage') or details in ('du', 'full'):
   536             path = os.path.join(acc.home, acc.mail_location.directory)
   548             path = os.path.join(acc.home, acc.mail_location.directory)
   537             info['disk usage'] = self.__getDiskUsage(path)
   549             info['disk usage'] = self.__getDiskUsage(path)
   538             if details in (None, 'du'):
   550             if details in (None, 'du'):
   539                 return info
   551                 return info
   540         if details in ('aliases', 'full'):
   552         if details in ('aliases', 'full'):