VirtualMailManager/Handler.py
branchv0.6.x
changeset 216 0c8c053b451c
parent 215 33f727efa7c4
child 221 371ae0b4443d
equal deleted inserted replaced
215:33f727efa7c4 216:0c8c053b451c
    26 from VirtualMailManager.Alias import Alias
    26 from VirtualMailManager.Alias import Alias
    27 from VirtualMailManager.AliasDomain import AliasDomain
    27 from VirtualMailManager.AliasDomain import AliasDomain
    28 from VirtualMailManager.Config import Config as Cfg
    28 from VirtualMailManager.Config import Config as Cfg
    29 from VirtualMailManager.Domain import Domain
    29 from VirtualMailManager.Domain import Domain
    30 from VirtualMailManager.EmailAddress import EmailAddress
    30 from VirtualMailManager.EmailAddress import EmailAddress
    31 from VirtualMailManager.Exceptions import *
    31 from VirtualMailManager.errors import VMMError, AliasError
    32 from VirtualMailManager.Relocated import Relocated
    32 from VirtualMailManager.Relocated import Relocated
    33 from VirtualMailManager.ext.Postconf import Postconf
    33 from VirtualMailManager.ext.Postconf import Postconf
    34 
    34 
    35 
    35 
    36 SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    36 SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    50         ``skip_some_checks`` : bool
    50         ``skip_some_checks`` : bool
    51             When a derived class knows how to handle all checks this
    51             When a derived class knows how to handle all checks this
    52             argument may be ``True``. By default it is ``False`` and
    52             argument may be ``True``. By default it is ``False`` and
    53             all checks will be performed.
    53             all checks will be performed.
    54 
    54 
    55         Throws a VMMNotRootException if your uid is greater 0.
    55         Throws a NotRootError if your uid is greater 0.
    56         """
    56         """
    57         self._cfgFileName = ''
    57         self._cfgFileName = ''
    58         self.__warnings = []
    58         self.__warnings = []
    59         self._Cfg = None
    59         self._Cfg = None
    60         self._dbh = None
    60         self._dbh = None
    61 
    61 
    62         if os.geteuid():
    62         if os.geteuid():
    63             raise VMMNotRootException(_(u"You are not root.\n\tGood bye!\n"),
    63             raise NotRootError(_(u"You are not root.\n\tGood bye!\n"),
    64                 ERR.CONF_NOPERM)
    64                 ERR.CONF_NOPERM)
    65         if self.__chkCfgFile():
    65         if self.__chkCfgFile():
    66             self._Cfg = Cfg(self._cfgFileName)
    66             self._Cfg = Cfg(self._cfgFileName)
    67             self._Cfg.load()
    67             self._Cfg.load()
    68         if not skip_some_checks:
    68         if not skip_some_checks:
    76             tmp = os.path.join(path, 'vmm.cfg')
    76             tmp = os.path.join(path, 'vmm.cfg')
    77             if os.path.isfile(tmp):
    77             if os.path.isfile(tmp):
    78                 self._cfgFileName = tmp
    78                 self._cfgFileName = tmp
    79                 break
    79                 break
    80         if not len(self._cfgFileName):
    80         if not len(self._cfgFileName):
    81             raise VMMException(
    81             raise VMMError(
    82                 _(u"No “vmm.cfg” found in: /root:/usr/local/etc:/etc"),
    82                 _(u"No “vmm.cfg” found in: /root:/usr/local/etc:/etc"),
    83                 ERR.CONF_NOFILE)
    83                 ERR.CONF_NOFILE)
    84 
    84 
    85     def __chkCfgFile(self):
    85     def __chkCfgFile(self):
    86         """Checks the configuration file, returns bool"""
    86         """Checks the configuration file, returns bool"""
    87         self.__findCfgFile()
    87         self.__findCfgFile()
    88         fstat = os.stat(self._cfgFileName)
    88         fstat = os.stat(self._cfgFileName)
    89         fmode = int(oct(fstat.st_mode & 0777))
    89         fmode = int(oct(fstat.st_mode & 0777))
    90         if fmode % 100 and fstat.st_uid != fstat.st_gid or \
    90         if fmode % 100 and fstat.st_uid != fstat.st_gid or \
    91             fmode % 10 and fstat.st_uid == fstat.st_gid:
    91             fmode % 10 and fstat.st_uid == fstat.st_gid:
    92                 raise VMMPermException(_(
    92                 raise PermissionError(_(
    93                     u'fix permissions (%(perms)s) for “%(file)s”\n\
    93                     u'fix permissions (%(perms)s) for “%(file)s”\n\
    94 `chmod 0600 %(file)s` would be great.') % {'file':
    94 `chmod 0600 %(file)s` would be great.') % {'file':
    95                     self._cfgFileName, 'perms': fmode}, ERR.CONF_WRONGPERM)
    95                     self._cfgFileName, 'perms': fmode}, ERR.CONF_WRONGPERM)
    96         else:
    96         else:
    97             return True
    97             return True
   103             old_umask = os.umask(0006)
   103             old_umask = os.umask(0006)
   104             os.makedirs(basedir, 0771)
   104             os.makedirs(basedir, 0771)
   105             os.chown(basedir, 0, self._Cfg.dget('misc.gid_mail'))
   105             os.chown(basedir, 0, self._Cfg.dget('misc.gid_mail'))
   106             os.umask(old_umask)
   106             os.umask(old_umask)
   107         elif not os.path.isdir(basedir):
   107         elif not os.path.isdir(basedir):
   108             raise VMMException(_(u'“%s” is not a directory.\n\
   108             raise VMMError(_(u'“%s” is not a directory.\n\
   109 (vmm.cfg: section "misc", option "base_directory")') %
   109 (vmm.cfg: section "misc", option "base_directory")') %
   110                                  basedir, ERR.NO_SUCH_DIRECTORY)
   110                                  basedir, ERR.NO_SUCH_DIRECTORY)
   111         for opt, val in self._Cfg.items('bin'):
   111         for opt, val in self._Cfg.items('bin'):
   112             try:
   112             try:
   113                 exec_ok(val)
   113                 exec_ok(val)
   114             except VMMException, e:
   114             except VMMError, e:
   115                 code = e.code()
   115                 if e.code is ERR.NO_SUCH_BINARY:
   116                 if code is ERR.NO_SUCH_BINARY:
   116                     raise VMMError(_(u'“%(binary)s” doesn\'t exist.\n\
   117                     raise VMMException(_(u'“%(binary)s” doesn\'t exist.\n\
       
   118 (vmm.cfg: section "bin", option "%(option)s")') %
   117 (vmm.cfg: section "bin", option "%(option)s")') %
   119                                        {'binary': val, 'option': opt},
   118                                        {'binary': val, 'option': opt},
   120                                        ERR.NO_SUCH_BINARY)
   119                                        ERR.NO_SUCH_BINARY)
   121                 elif code is ERR.NOT_EXECUTABLE:
   120                 elif e.code is ERR.NOT_EXECUTABLE:
   122                     raise VMMException(_(u'“%(binary)s” is not executable.\
   121                     raise VMMError(_(u'“%(binary)s” is not executable.\
   123 \n(vmm.cfg: section "bin", option "%(option)s")') %
   122 \n(vmm.cfg: section "bin", option "%(option)s")') %
   124                                        {'binary': val, 'option': opt},
   123                                        {'binary': val, 'option': opt},
   125                                        ERR.NOT_EXECUTABLE)
   124                                        ERR.NOT_EXECUTABLE)
   126                 else:
   125                 else:
   127                     raise
   126                     raise
   139                         client_encoding='utf8', unicode_results=True)
   138                         client_encoding='utf8', unicode_results=True)
   140                 dbc = self._dbh.cursor()
   139                 dbc = self._dbh.cursor()
   141                 dbc.execute("SET NAMES 'UTF8'")
   140                 dbc.execute("SET NAMES 'UTF8'")
   142                 dbc.close()
   141                 dbc.close()
   143             except PgSQL.libpq.DatabaseError, e:
   142             except PgSQL.libpq.DatabaseError, e:
   144                 raise VMMException(str(e), ERR.DATABASE_ERROR)
   143                 raise VMMError(str(e), ERR.DATABASE_ERROR)
   145 
   144 
   146     def _exists(dbh, query):
   145     def _exists(dbh, query):
   147         dbc = dbh.cursor()
   146         dbc = dbh.cursor()
   148         dbc.execute(query)
   147         dbc.execute(query)
   149         gid = dbc.fetchone()
   148         gid = dbc.fetchone()
   296 
   295 
   297     def __userDirDelete(self, domdir, uid, gid):
   296     def __userDirDelete(self, domdir, uid, gid):
   298         if uid > 0 and gid > 0:
   297         if uid > 0 and gid > 0:
   299             userdir = '%s' % uid
   298             userdir = '%s' % uid
   300             if userdir.count('..') or domdir.count('..'):
   299             if userdir.count('..') or domdir.count('..'):
   301                 raise VMMException(_(u'Found ".." in home directory path.'),
   300                 raise VMMError(_(u'Found ".." in home directory path.'),
   302                                    ERR.FOUND_DOTS_IN_PATH)
   301                                    ERR.FOUND_DOTS_IN_PATH)
   303             if os.path.isdir(domdir):
   302             if os.path.isdir(domdir):
   304                 os.chdir(domdir)
   303                 os.chdir(domdir)
   305                 if os.path.isdir(userdir):
   304                 if os.path.isdir(userdir):
   306                     mdstat = os.stat(userdir)
   305                     mdstat = os.stat(userdir)
   307                     if (mdstat.st_uid, mdstat.st_gid) != (uid, gid):
   306                     if (mdstat.st_uid, mdstat.st_gid) != (uid, gid):
   308                         raise VMMException(_(
   307                         raise VMMError(_(
   309                           u'Detected owner/group mismatch in home directory.'),
   308                           u'Detected owner/group mismatch in home directory.'),
   310                           ERR.MAILDIR_PERM_MISMATCH)
   309                           ERR.MAILDIR_PERM_MISMATCH)
   311                     rmtree(userdir, ignore_errors=True)
   310                     rmtree(userdir, ignore_errors=True)
   312                 else:
   311                 else:
   313                     raise VMMException(_(u"No such directory: %s") %
   312                     raise VMMError(_(u"No such directory: %s") %
   314                         os.path.join(domdir, userdir), ERR.NO_SUCH_DIRECTORY)
   313                         os.path.join(domdir, userdir), ERR.NO_SUCH_DIRECTORY)
   315 
   314 
   316     def __domDirDelete(self, domdir, gid):
   315     def __domDirDelete(self, domdir, gid):
   317         if gid > 0:
   316         if gid > 0:
   318             if not self.__isdir(domdir):
   317             if not self.__isdir(domdir):
   319                 return
   318                 return
   320             basedir = self._Cfg.dget('misc.base_directory')
   319             basedir = self._Cfg.dget('misc.base_directory')
   321             domdirdirs = domdir.replace(basedir + '/', '').split('/')
   320             domdirdirs = domdir.replace(basedir + '/', '').split('/')
   322             domdirparent = os.path.join(basedir, domdirdirs[0])
   321             domdirparent = os.path.join(basedir, domdirdirs[0])
   323             if basedir.count('..') or domdir.count('..'):
   322             if basedir.count('..') or domdir.count('..'):
   324                 raise VMMException(_(u'Found ".." in domain directory path.'),
   323                 raise VMMError(_(u'Found ".." in domain directory path.'),
   325                         ERR.FOUND_DOTS_IN_PATH)
   324                         ERR.FOUND_DOTS_IN_PATH)
   326             if os.path.isdir(domdirparent):
   325             if os.path.isdir(domdirparent):
   327                 os.chdir(domdirparent)
   326                 os.chdir(domdirparent)
   328                 if os.lstat(domdirdirs[1]).st_gid != gid:
   327                 if os.lstat(domdirdirs[1]).st_gid != gid:
   329                     raise VMMException(_(
   328                     raise VMMError(_(
   330                         u'Detected group mismatch in domain directory.'),
   329                         u'Detected group mismatch in domain directory.'),
   331                         ERR.DOMAINDIR_GROUP_MISMATCH)
   330                         ERR.DOMAINDIR_GROUP_MISMATCH)
   332                 rmtree(domdirdirs[1], ignore_errors=True)
   331                 rmtree(domdirdirs[1], ignore_errors=True)
   333 
   332 
   334     def __getSalt(self):
   333     def __getSalt(self):
   409         dom.save()
   408         dom.save()
   410         self.__domDirMake(dom.getDir(), dom.getID())
   409         self.__domDirMake(dom.getDir(), dom.getID())
   411 
   410 
   412     def domainTransport(self, domainname, transport, force=None):
   411     def domainTransport(self, domainname, transport, force=None):
   413         if force is not None and force != 'force':
   412         if force is not None and force != 'force':
   414             raise VMMDomainException(_(u"Invalid argument: “%s”") % force,
   413             raise DomainError(_(u"Invalid argument: “%s”") % force,
   415                 ERR.INVALID_OPTION)
   414                 ERR.INVALID_OPTION)
   416         dom = self.__getDomain(domainname, None)
   415         dom = self.__getDomain(domainname, None)
   417         if force is None:
   416         if force is None:
   418             dom.updateTransport(transport)
   417             dom.updateTransport(transport)
   419         else:
   418         else:
   420             dom.updateTransport(transport, force=True)
   419             dom.updateTransport(transport, force=True)
   421 
   420 
   422     def domainDelete(self, domainname, force=None):
   421     def domainDelete(self, domainname, force=None):
   423         if not force is None and force not in ['deluser', 'delalias',
   422         if not force is None and force not in ['deluser', 'delalias',
   424                                                'delall']:
   423                                                'delall']:
   425                 raise VMMDomainException(_(u'Invalid argument: “%s”') %
   424                 raise DomainError(_(u'Invalid argument: “%s”') %
   426                                          force, ERR.INVALID_OPTION)
   425                                          force, ERR.INVALID_OPTION)
   427         dom = self.__getDomain(domainname)
   426         dom = self.__getDomain(domainname)
   428         gid = dom.getID()
   427         gid = dom.getID()
   429         domdir = dom.getDir()
   428         domdir = dom.getDir()
   430         if self._Cfg.dget('domain.force_deletion') or force == 'delall':
   429         if self._Cfg.dget('domain.force_deletion') or force == 'delall':
   439             self.__domDirDelete(domdir, gid)
   438             self.__domDirDelete(domdir, gid)
   440 
   439 
   441     def domainInfo(self, domainname, details=None):
   440     def domainInfo(self, domainname, details=None):
   442         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
   441         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
   443                            'relocated']:
   442                            'relocated']:
   444             raise VMMException(_(u'Invalid argument: “%s”') % details,
   443             raise VMMError(_(u'Invalid argument: “%s”') % details,
   445                                ERR.INVALID_AGUMENT)
   444                                ERR.INVALID_AGUMENT)
   446         dom = self.__getDomain(domainname)
   445         dom = self.__getDomain(domainname)
   447         dominfo = dom.getInfo()
   446         dominfo = dom.getInfo()
   448         if dominfo['domainname'].startswith('xn--'):
   447         if dominfo['domainname'].startswith('xn--'):
   449             dominfo['domainname'] += ' (%s)' % ace2idna(dominfo['domainname'])
   448             dominfo['domainname'] += ' (%s)' % ace2idna(dominfo['domainname'])
   509                 elif pattern.startswith('%'):
   508                 elif pattern.startswith('%'):
   510                     domain = pattern[1:]
   509                     domain = pattern[1:]
   511                 elif pattern.endswith('%'):
   510                 elif pattern.endswith('%'):
   512                     domain = pattern[:-1]
   511                     domain = pattern[:-1]
   513                 if not re.match(RE_DOMAIN_SRCH, domain):
   512                 if not re.match(RE_DOMAIN_SRCH, domain):
   514                     raise VMMException(
   513                     raise VMMError(
   515                     _(u"The pattern “%s” contains invalid characters.") %
   514                     _(u"The pattern “%s” contains invalid characters.") %
   516                     pattern, ERR.DOMAIN_INVALID)
   515                     pattern, ERR.DOMAIN_INVALID)
   517         self.__dbConnect()
   516         self.__dbConnect()
   518         return search(self._dbh, pattern=pattern, like=like)
   517         return search(self._dbh, pattern=pattern, like=like)
   519 
   518 
   544                 _(u"The destination account/alias “%s” doesn't exist.") %
   543                 _(u"The destination account/alias “%s” doesn't exist.") %
   545                                    destination)
   544                                    destination)
   546 
   545 
   547     def userDelete(self, emailaddress, force=None):
   546     def userDelete(self, emailaddress, force=None):
   548         if force not in [None, 'delalias']:
   547         if force not in [None, 'delalias']:
   549             raise VMMException(_(u"Invalid argument: “%s”") % force,
   548             raise VMMError(_(u"Invalid argument: “%s”") % force,
   550                     ERR.INVALID_AGUMENT)
   549                     ERR.INVALID_AGUMENT)
   551         acc = self.__getAccount(emailaddress)
   550         acc = self.__getAccount(emailaddress)
   552         uid = acc.getUID()
   551         uid = acc.getUID()
   553         gid = acc.getGID()
   552         gid = acc.getGID()
   554         acc.delete(force)
   553         acc.delete(force)
   555         if self._Cfg.dget('account.delete_directory'):
   554         if self._Cfg.dget('account.delete_directory'):
   556             try:
   555             try:
   557                 self.__userDirDelete(acc.getDir('domain'), uid, gid)
   556                 self.__userDirDelete(acc.getDir('domain'), uid, gid)
   558             except VMMException, e:
   557             except VMMError, e:
   559                 if e.code() in [ERR.FOUND_DOTS_IN_PATH,
   558                 if e.code in [ERR.FOUND_DOTS_IN_PATH,
   560                         ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]:
   559                         ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]:
   561                     warning = _(u"""\
   560                     warning = _(u"""\
   562 The account has been successfully deleted from the database.
   561 The account has been successfully deleted from the database.
   563     But an error occurred while deleting the following directory:
   562     But an error occurred while deleting the following directory:
   564     “%(directory)s”
   563     “%(directory)s”
   565     Reason: %(reason)s""") % \
   564     Reason: %(reason)s""") % \
   566                     {'directory': acc.getDir('home'), 'reason': e.msg()}
   565                     {'directory': acc.getDir('home'), 'reason': e.msg}
   567                     self.__warnings.append(warning)
   566                     self.__warnings.append(warning)
   568                 else:
   567                 else:
   569                     raise
   568                     raise
   570 
   569 
   571     def aliasInfo(self, aliasaddress):
   570     def aliasInfo(self, aliasaddress):
   572         """Returns an iterator object for all destinations (`EmailAddress`
   571         """Returns an iterator object for all destinations (`EmailAddress`
   573         instances) for the `Alias` with the given *aliasaddress*."""
   572         instances) for the `Alias` with the given *aliasaddress*."""
   574         alias = self.__getAlias(aliasaddress)
   573         alias = self.__getAlias(aliasaddress)
   575         try:
   574         try:
   576             return alias.get_destinations()
   575             return alias.get_destinations()
   577         except VMMAliasException, e:
   576         except AliasError, e:
   578             if e.code() == ERR.NO_SUCH_ALIAS:
   577             if e.code == ERR.NO_SUCH_ALIAS:
   579                 if Handler.accountExists(self._dbh, alias._addr):
   578                 if Handler.accountExists(self._dbh, alias._addr):
   580                     raise VMMException(
   579                     raise VMMError(
   581                         _(u'There is already an account with address “%s”.') %
   580                         _(u'There is already an account with address “%s”.') %
   582                                        aliasaddress, ERR.ACCOUNT_EXISTS)
   581                                        aliasaddress, ERR.ACCOUNT_EXISTS)
   583                 if Handler.relocatedExists(self._dbh, alias._addr):
   582                 if Handler.relocatedExists(self._dbh, alias._addr):
   584                     raise VMMException(_(u'There is already a relocated user \
   583                     raise VMMError(_(u'There is already a relocated user \
   585 with the address “%s”.') %
   584 with the address “%s”.') %
   586                                        aliasaddress, ERR.RELOCATED_EXISTS)
   585                                        aliasaddress, ERR.RELOCATED_EXISTS)
   587                 raise
   586                 raise
   588             else:
   587             else:
   589                 raise
   588                 raise
   598         else:
   597         else:
   599             alias.del_destination(EmailAddress(targetaddress))
   598             alias.del_destination(EmailAddress(targetaddress))
   600 
   599 
   601     def userInfo(self, emailaddress, details=None):
   600     def userInfo(self, emailaddress, details=None):
   602         if details not in (None, 'du', 'aliases', 'full'):
   601         if details not in (None, 'du', 'aliases', 'full'):
   603             raise VMMException(_(u'Invalid argument: “%s”') % details,
   602             raise VMMError(_(u'Invalid argument: “%s”') % details,
   604                                ERR.INVALID_AGUMENT)
   603                                ERR.INVALID_AGUMENT)
   605         acc = self.__getAccount(emailaddress)
   604         acc = self.__getAccount(emailaddress)
   606         info = acc.getInfo(self._Cfg.dget('misc.dovecot_version'))
   605         info = acc.getInfo(self._Cfg.dget('misc.dovecot_version'))
   607         if self._Cfg.dget('account.disk_usage') or details in ('du', 'full'):
   606         if self._Cfg.dget('account.disk_usage') or details in ('du', 'full'):
   608             info['disk usage'] = self.__getDiskUsage('%(maildir)s' % info)
   607             info['disk usage'] = self.__getDiskUsage('%(maildir)s' % info)
   621         if password is None or (isinstance(password, basestring) and
   620         if password is None or (isinstance(password, basestring) and
   622                                 not len(password)):
   621                                 not len(password)):
   623             raise ValueError('could not accept password: %r' % password)
   622             raise ValueError('could not accept password: %r' % password)
   624         acc = self.__getAccount(emailaddress)
   623         acc = self.__getAccount(emailaddress)
   625         if acc.getUID() == 0:
   624         if acc.getUID() == 0:
   626             raise VMMException(_(u"Account doesn't exist"),
   625             raise VMMError(_(u"Account doesn't exist"),
   627                                ERR.NO_SUCH_ACCOUNT)
   626                                ERR.NO_SUCH_ACCOUNT)
   628         acc.modify('password', self.__pwhash(password, user=emailaddress))
   627         acc.modify('password', self.__pwhash(password, user=emailaddress))
   629 
   628 
   630     def userName(self, emailaddress, name):
   629     def userName(self, emailaddress, name):
   631         acc = self.__getAccount(emailaddress)
   630         acc = self.__getAccount(emailaddress)