VirtualMailManager/VirtualMailManager.py
changeset 133 2d5c4745efec
parent 132 fa22bd13b4d1
child 136 fb61f64e6351
equal deleted inserted replaced
132:fa22bd13b4d1 133:2d5c4745efec
    69             if os.path.isfile(tmp):
    69             if os.path.isfile(tmp):
    70                 self.__cfgFileName = tmp
    70                 self.__cfgFileName = tmp
    71                 break
    71                 break
    72         if not len(self.__cfgFileName):
    72         if not len(self.__cfgFileName):
    73             raise VMMException(
    73             raise VMMException(
    74                 _(u"No »vmm.cfg« found in: /root:/usr/local/etc:/etc"),
    74                 _(u"No “vmm.cfg” found in: /root:/usr/local/etc:/etc"),
    75                 ERR.CONF_NOFILE)
    75                 ERR.CONF_NOFILE)
    76 
    76 
    77     def __chkCfgFile(self):
    77     def __chkCfgFile(self):
    78         """Checks the configuration file, returns bool"""
    78         """Checks the configuration file, returns bool"""
    79         self.__findCfgFile()
    79         self.__findCfgFile()
    80         fstat = os.stat(self.__cfgFileName)
    80         fstat = os.stat(self.__cfgFileName)
    81         fmode = int(oct(fstat.st_mode & 0777))
    81         fmode = int(oct(fstat.st_mode & 0777))
    82         if fmode % 100 and fstat.st_uid != fstat.st_gid \
    82         if fmode % 100 and fstat.st_uid != fstat.st_gid \
    83         or fmode % 10 and fstat.st_uid == fstat.st_gid:
    83         or fmode % 10 and fstat.st_uid == fstat.st_gid:
    84             raise VMMPermException(_(
    84             raise VMMPermException(_(
    85                 u'fix permissions (%(perms)s) for »%(file)s«\n\
    85                 u'fix permissions (%(perms)s) for “%(file)s”\n\
    86 `chmod 0600 %(file)s` would be great.') % {'file':
    86 `chmod 0600 %(file)s` would be great.') % {'file':
    87                 self.__cfgFileName, 'perms': fmode}, ERR.CONF_WRONGPERM)
    87                 self.__cfgFileName, 'perms': fmode}, ERR.CONF_WRONGPERM)
    88         else:
    88         else:
    89             return True
    89             return True
    90 
    90 
    95             os.makedirs(self.__Cfg.get('domdir', 'base'), 0771)
    95             os.makedirs(self.__Cfg.get('domdir', 'base'), 0771)
    96             os.chown(self.__Cfg.get('domdir', 'base'), 0,
    96             os.chown(self.__Cfg.get('domdir', 'base'), 0,
    97                     self.__Cfg.getint('misc', 'gid_mail'))
    97                     self.__Cfg.getint('misc', 'gid_mail'))
    98             os.umask(old_umask)
    98             os.umask(old_umask)
    99         elif not os.path.isdir(self.__Cfg.get('domdir', 'base')):
    99         elif not os.path.isdir(self.__Cfg.get('domdir', 'base')):
   100             raise VMMException(_(u'»%s« is not a directory.\n\
   100             raise VMMException(_(u'“%s” is not a directory.\n\
   101 (vmm.cfg: section "domdir", option "base")') %
   101 (vmm.cfg: section "domdir", option "base")') %
   102                 self.__Cfg.get('domdir', 'base'), ERR.NO_SUCH_DIRECTORY)
   102                 self.__Cfg.get('domdir', 'base'), ERR.NO_SUCH_DIRECTORY)
   103         for opt, val in self.__Cfg.items('bin'):
   103         for opt, val in self.__Cfg.items('bin'):
   104             if not os.path.exists(val):
   104             if not os.path.exists(val):
   105                 raise VMMException(_(u'»%(binary)s« doesn\'t exists.\n\
   105                 raise VMMException(_(u'“%(binary)s” doesn\'t exists.\n\
   106 (vmm.cfg: section "bin", option "%(option)s")') %{'binary': val,'option': opt},
   106 (vmm.cfg: section "bin", option "%(option)s")') %{'binary': val,'option': opt},
   107                     ERR.NO_SUCH_BINARY)
   107                     ERR.NO_SUCH_BINARY)
   108             elif not os.access(val, os.X_OK):
   108             elif not os.access(val, os.X_OK):
   109                 raise VMMException(_(u'»%(binary)s« is not executable.\n\
   109                 raise VMMException(_(u'“%(binary)s” is not executable.\n\
   110 (vmm.cfg: section "bin", option "%(option)s")') %{'binary': val,'option': opt},
   110 (vmm.cfg: section "bin", option "%(option)s")') %{'binary': val,'option': opt},
   111                     ERR.NOT_EXECUTABLE)
   111                     ERR.NOT_EXECUTABLE)
   112 
   112 
   113     def __dbConnect(self):
   113     def __dbConnect(self):
   114         """Creates a pyPgSQL.PgSQL.connection instance."""
   114         """Creates a pyPgSQL.PgSQL.connection instance."""
   164             domainname = VirtualMailManager.idn2ascii(domainname)
   164             domainname = VirtualMailManager.idn2ascii(domainname)
   165         if len(domainname) > 255:
   165         if len(domainname) > 255:
   166             raise VMMException(_(u'The domain name is too long.'),
   166             raise VMMException(_(u'The domain name is too long.'),
   167                 ERR.DOMAIN_TOO_LONG)
   167                 ERR.DOMAIN_TOO_LONG)
   168         if not re.match(RE_DOMAIN, domainname):
   168         if not re.match(RE_DOMAIN, domainname):
   169             raise VMMException(_(u'The domain name »%s« is invalid.') %\
   169             raise VMMException(_(u'The domain name “%s” is invalid.') %\
   170                     domainname, ERR.DOMAIN_INVALID)
   170                     domainname, ERR.DOMAIN_INVALID)
   171         return domainname
   171         return domainname
   172     chkDomainname = staticmethod(chkDomainname)
   172     chkDomainname = staticmethod(chkDomainname)
   173 
   173 
   174     def _exists(dbh, query):
   174     def _exists(dbh, query):
   472         if section is None:
   472         if section is None:
   473             self.__Cfg.configure(self.__cfgSections)
   473             self.__Cfg.configure(self.__cfgSections)
   474         elif section in self.__cfgSections:
   474         elif section in self.__cfgSections:
   475             self.__Cfg.configure([section])
   475             self.__Cfg.configure([section])
   476         else:
   476         else:
   477             raise VMMException(_(u"Invalid section: '%s'") % section,
   477             raise VMMException(_(u"Invalid section: “%s”") % section,
   478                 ERR.INVALID_SECTION)
   478                 ERR.INVALID_SECTION)
   479 
   479 
   480     def domainAdd(self, domainname, transport=None):
   480     def domainAdd(self, domainname, transport=None):
   481         dom = self.__getDomain(domainname, transport)
   481         dom = self.__getDomain(domainname, transport)
   482         dom.save()
   482         dom.save()
   483         self.__domDirMake(dom.getDir(), dom.getID())
   483         self.__domDirMake(dom.getDir(), dom.getID())
   484 
   484 
   485     def domainTransport(self, domainname, transport, force=None):
   485     def domainTransport(self, domainname, transport, force=None):
   486         if force is not None and force != 'force':
   486         if force is not None and force != 'force':
   487             raise VMMDomainException(_(u"Invalid argument: '%s'") % force,
   487             raise VMMDomainException(_(u"Invalid argument: “%s”") % force,
   488                 ERR.INVALID_OPTION)
   488                 ERR.INVALID_OPTION)
   489         dom = self.__getDomain(domainname, None)
   489         dom = self.__getDomain(domainname, None)
   490         if force is None:
   490         if force is None:
   491             dom.updateTransport(transport)
   491             dom.updateTransport(transport)
   492         else:
   492         else:
   493             dom.updateTransport(transport, force=True)
   493             dom.updateTransport(transport, force=True)
   494 
   494 
   495     def domainDelete(self, domainname, force=None):
   495     def domainDelete(self, domainname, force=None):
   496         if not force is None and force not in ['deluser','delalias','delall']:
   496         if not force is None and force not in ['deluser','delalias','delall']:
   497             raise VMMDomainException(_(u"Invalid argument: »%s«") % force,
   497             raise VMMDomainException(_(u"Invalid argument: “%s”") % force,
   498                 ERR.INVALID_OPTION)
   498                 ERR.INVALID_OPTION)
   499         dom = self.__getDomain(domainname)
   499         dom = self.__getDomain(domainname)
   500         gid = dom.getID()
   500         gid = dom.getID()
   501         domdir = dom.getDir()
   501         domdir = dom.getDir()
   502         if self.__Cfg.getboolean('misc', 'forcedel') or force == 'delall':
   502         if self.__Cfg.getboolean('misc', 'forcedel') or force == 'delall':
   511             self.__domDirDelete(domdir, gid)
   511             self.__domDirDelete(domdir, gid)
   512 
   512 
   513     def domainInfo(self, domainname, details=None):
   513     def domainInfo(self, domainname, details=None):
   514         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
   514         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
   515                 'relocated', 'detailed']:
   515                 'relocated', 'detailed']:
   516             raise VMMException(_(u'Invalid argument: »%s«') % details,
   516             raise VMMException(_(u'Invalid argument: “%s”') % details,
   517                     ERR.INVALID_AGUMENT)
   517                     ERR.INVALID_AGUMENT)
   518         if details == 'detailed':
   518         if details == 'detailed':
   519             details = 'full'
   519             details = 'full'
   520             self.__warnings.append(_(u'\
   520             self.__warnings.append(_(u'\
   521 The keyword »detailed« is deprecated and will be removed in a future release.\n\
   521 The keyword “detailed” is deprecated and will be removed in a future release.\n\
   522    Please use the keyword »full« to get full details.'))
   522    Please use the keyword “full” to get full details.'))
   523         dom = self.__getDomain(domainname)
   523         dom = self.__getDomain(domainname)
   524         dominfo = dom.getInfo()
   524         dominfo = dom.getInfo()
   525         if dominfo['domainname'].startswith('xn--'):
   525         if dominfo['domainname'].startswith('xn--'):
   526             dominfo['domainname'] += ' (%s)'\
   526             dominfo['domainname'] += ' (%s)'\
   527                 % VirtualMailManager.ace2idna(dominfo['domainname'])
   527                 % VirtualMailManager.ace2idna(dominfo['domainname'])
   588                     domain = pattern[1:]
   588                     domain = pattern[1:]
   589                 elif pattern.endswith('%'):
   589                 elif pattern.endswith('%'):
   590                     domain = pattern[:-1]
   590                     domain = pattern[:-1]
   591                 if not re.match(RE_DOMAIN_SRCH, domain):
   591                 if not re.match(RE_DOMAIN_SRCH, domain):
   592                     raise VMMException(
   592                     raise VMMException(
   593                     _(u"The pattern »%s« contains invalid characters.") %
   593                     _(u"The pattern “%s” contains invalid characters.") %
   594                     pattern, ERR.DOMAIN_INVALID)
   594                     pattern, ERR.DOMAIN_INVALID)
   595         self.__dbConnect()
   595         self.__dbConnect()
   596         return search(self.__dbh, pattern=pattern, like=like)
   596         return search(self.__dbh, pattern=pattern, like=like)
   597 
   597 
   598     def userAdd(self, emailaddress, password):
   598     def userAdd(self, emailaddress, password):
   614         gid = self.__getDomain(alias._dest._domainname).getID()
   614         gid = self.__getDomain(alias._dest._domainname).getID()
   615         if gid > 0 and not VirtualMailManager.accountExists(self.__dbh,
   615         if gid > 0 and not VirtualMailManager.accountExists(self.__dbh,
   616         alias._dest) and not VirtualMailManager.aliasExists(self.__dbh,
   616         alias._dest) and not VirtualMailManager.aliasExists(self.__dbh,
   617         alias._dest):
   617         alias._dest):
   618             self.__warnings.append(
   618             self.__warnings.append(
   619                 _(u"The destination account/alias »%s« doesn't exists yet.")%\
   619                 _(u"The destination account/alias “%s” doesn't exists yet.")%\
   620                         alias._dest)
   620                         alias._dest)
   621 
   621 
   622     def userDelete(self, emailaddress, force=None):
   622     def userDelete(self, emailaddress, force=None):
   623         if force not in [None, 'delalias']:
   623         if force not in [None, 'delalias']:
   624             raise VMMException(_(u"Invalid argument: »%s«") % force,
   624             raise VMMException(_(u"Invalid argument: “%s”") % force,
   625                     ERR.INVALID_AGUMENT)
   625                     ERR.INVALID_AGUMENT)
   626         acc = self.__getAccount(emailaddress)
   626         acc = self.__getAccount(emailaddress)
   627         uid = acc.getUID()
   627         uid = acc.getUID()
   628         gid = acc.getGID()
   628         gid = acc.getGID()
   629         acc.delete(force)
   629         acc.delete(force)
   634                 if e.code() in [ERR.FOUND_DOTS_IN_PATH,
   634                 if e.code() in [ERR.FOUND_DOTS_IN_PATH,
   635                         ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]:
   635                         ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]:
   636                     warning = _(u"""\
   636                     warning = _(u"""\
   637 The account has been successfully deleted from the database.
   637 The account has been successfully deleted from the database.
   638     But an error occurred while deleting the following directory:
   638     But an error occurred while deleting the following directory:
   639     »%(directory)s«
   639     “%(directory)s”
   640     Reason: %(raeson)s""") % {'directory': acc.getDir('home'),'raeson': e.msg()}
   640     Reason: %(raeson)s""") % {'directory': acc.getDir('home'),'raeson': e.msg()}
   641                     self.__warnings.append(warning)
   641                     self.__warnings.append(warning)
   642                 else:
   642                 else:
   643                     raise e
   643                     raise e
   644 
   644 
   650         alias = self.__getAlias(aliasaddress, targetaddress)
   650         alias = self.__getAlias(aliasaddress, targetaddress)
   651         alias.delete()
   651         alias.delete()
   652 
   652 
   653     def userInfo(self, emailaddress, details=None):
   653     def userInfo(self, emailaddress, details=None):
   654         if details not in [None, 'du', 'aliases', 'full']:
   654         if details not in [None, 'du', 'aliases', 'full']:
   655             raise VMMException(_(u'Invalid argument: »%s«') % details,
   655             raise VMMException(_(u'Invalid argument: “%s”') % details,
   656                     ERR.INVALID_AGUMENT)
   656                     ERR.INVALID_AGUMENT)
   657         acc = self.__getAccount(emailaddress)
   657         acc = self.__getAccount(emailaddress)
   658         info = acc.getInfo(self.__Cfg.getint('misc', 'dovecotvers'))
   658         info = acc.getInfo(self.__Cfg.getint('misc', 'dovecotvers'))
   659         if self.__Cfg.getboolean('maildir', 'diskusage')\
   659         if self.__Cfg.getboolean('maildir', 'diskusage')\
   660         or details in ['du', 'full']:
   660         or details in ['du', 'full']:
   688 
   688 
   689     def userDisable(self, emailaddress, service=None):
   689     def userDisable(self, emailaddress, service=None):
   690         if service == 'managesieve':
   690         if service == 'managesieve':
   691             service = 'sieve'
   691             service = 'sieve'
   692             self.__warnings.append(_(u'\
   692             self.__warnings.append(_(u'\
   693 The service name »managesieve« is deprecated and will be removed\n\
   693 The service name “managesieve” is deprecated and will be removed\n\
   694    in a future release.\n\
   694    in a future release.\n\
   695    Please use the service name »sieve« instead.'))
   695    Please use the service name “sieve” instead.'))
   696         acc = self.__getAccount(emailaddress)
   696         acc = self.__getAccount(emailaddress)
   697         acc.disable(self.__Cfg.getint('misc', 'dovecotvers'), service)
   697         acc.disable(self.__Cfg.getint('misc', 'dovecotvers'), service)
   698 
   698 
   699     def userEnable(self, emailaddress, service=None):
   699     def userEnable(self, emailaddress, service=None):
   700         if service == 'managesieve':
   700         if service == 'managesieve':
   701             service = 'sieve'
   701             service = 'sieve'
   702             self.__warnings.append(_(u'\
   702             self.__warnings.append(_(u'\
   703 The service name »managesieve« is deprecated and will be removed\n\
   703 The service name “managesieve” is deprecated and will be removed\n\
   704    in a future release.\n\
   704    in a future release.\n\
   705    Please use the service name »sieve« instead.'))
   705    Please use the service name “sieve” instead.'))
   706         acc = self.__getAccount(emailaddress)
   706         acc = self.__getAccount(emailaddress)
   707         acc.enable(self.__Cfg.getint('misc', 'dovecotvers'), service)
   707         acc.enable(self.__Cfg.getint('misc', 'dovecotvers'), service)
   708 
   708 
   709     def relocatedAdd(self, emailaddress, targetaddress):
   709     def relocatedAdd(self, emailaddress, targetaddress):
   710         relocated = self.__getRelocated(emailaddress, targetaddress)
   710         relocated = self.__getRelocated(emailaddress, targetaddress)