diff -r 7ece710c559d -r 191d5a5adc4a VirtualMailManager/VirtualMailManager.py --- a/VirtualMailManager/VirtualMailManager.py Sat Aug 16 02:48:36 2008 +0000 +++ b/VirtualMailManager/VirtualMailManager.py Mon Aug 18 01:56:31 2008 +0000 @@ -16,7 +16,6 @@ import os import re import sys -import gettext from encodings.idna import ToASCII, ToUnicode from getpass import getpass from shutil import rmtree @@ -38,13 +37,6 @@ RE_LOCALPART = """[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]""" RE_MAILLOCATION = """^[\w]{1,20}$""" -ENCODING_IN = sys.getfilesystemencoding() -ENCODING_OUT = sys.stdout.encoding or sys.getfilesystemencoding() - -gettext.bindtextdomain('vmm', '/usr/local/share/locale') -gettext.textdomain('vmm') -_ = gettext.gettext - class VirtualMailManager: """The main class for vmm""" def __init__(self): @@ -52,14 +44,14 @@ Throws a VMMNotRootException if your uid is greater 0. """ self.__cfgFileName = '/usr/local/etc/vmm.cfg' - self.__permWarnMsg = _("fix permissions for '%s'\n`chmod 0600 %s` would\ - be great.") % (self.__cfgFileName, self.__cfgFileName) + self.__permWarnMsg = _(u"fix permissions for »%s«\n`chmod 0600 %s`\ + would be great.") % (self.__cfgFileName, self.__cfgFileName) self.__warnings = [] self.__Cfg = None self.__dbh = None if os.geteuid(): - raise VMMNotRootException((_("You are not root.\n\tGood bye!\n"), + raise VMMNotRootException((_(u"You are not root.\n\tGood bye!\n"), ERR.CONF_NOPERM)) if self.__chkCfgFile(): self.__Cfg = Cfg(self.__cfgFileName) @@ -73,7 +65,7 @@ def __chkCfgFile(self): """Checks the configuration file, returns bool""" if not os.path.isfile(self.__cfgFileName): - raise VMMException((_(u"The file '%s' does not exists.") % + raise VMMException((_(u"The file »%s« does not exists.") % self.__cfgFileName, ERR.CONF_NOFILE)) fstat = os.stat(self.__cfgFileName) try: @@ -95,15 +87,16 @@ self.__Cfg.getint('misc', 'gid_mail')) os.umask(old_umask) elif not os.path.isdir(self.__Cfg.get('domdir', 'base')): - raise VMMException((_('%s is not a directory') % + raise VMMException((_(u'»%s« is not a directory.\n\ +(vmm.cfg: section "domdir", option "base")') % self.__Cfg.get('domdir', 'base'), ERR.NO_SUCH_DIRECTORY)) for opt, val in self.__Cfg.items('bin'): if not os.path.exists(val): - raise VMMException((_("%s doesn't exists.") % val, - ERR.NO_SUCH_BINARY)) + raise VMMException((_(u'»%s« doesn\'t exists.\n\ +(vmm.cfg: section "bin", option "%s")') % (val, opt), ERR.NO_SUCH_BINARY)) elif not os.access(val, os.X_OK): - raise VMMException((_("%s is not executable.") % val, - ERR.NOT_EXECUTABLE)) + raise VMMException((_(u'»%s« is not executable.\n\ +(vmm.cfg: section "bin", option "%s")') % (val, opt), ERR.NOT_EXECUTABLE)) def __getFileMode(self): """Determines the file access mode from file __cfgFileName, @@ -129,22 +122,30 @@ except PgSQL.libpq.DatabaseError, e: raise VMMException((str(e), ERR.DATABASE_ERROR)) - def __chkLocalpart(self, localpart): + def chkLocalpart(localpart): """Validates the local part of an e-mail address. Keyword arguments: localpart -- the e-mail address that should be validated (str) """ + if len(localpart) < 1: + raise VMMException((_(u'No localpart specified.'), + ERR.LOCALPART_INVALID)) if len(localpart) > 64: - raise VMMException((_('The local part is too long'), - ERR.LOCALPART_TOO_LONG)) - if re.compile(RE_LOCALPART).search(localpart): + raise VMMException((_(u'The local part »%s« is too long') % + localpart, ERR.LOCALPART_TOO_LONG)) + ic = re.compile(RE_LOCALPART).findall(localpart) + if len(ic): + ichrs = '' + for c in set(ic): + ichrs += u"»%s« " % c raise VMMException(( - _(u"The local part '%s' contains invalid characters.") % - localpart, ERR.LOCALPART_INVALID)) + _(u"The local part »%s« contains invalid characters: %s") % + (localpart, ichrs), ERR.LOCALPART_INVALID)) return localpart + chkLocalpart = staticmethod(chkLocalpart) - def idn2ascii(self, domainname): + def idn2ascii(domainname): """Converts an idn domainname in punycode. Keyword arguments: @@ -154,10 +155,12 @@ for label in domainname.split('.'): if len(label) == 0: continue - tmp.append(ToASCII(unicode(label, ENCODING_IN))) + #tmp.append(ToASCII(unicode(label, ENCODING_IN))) + tmp.append(ToASCII(label)) return '.'.join(tmp) + idn2ascii = staticmethod(idn2ascii) - def ace2idna(self, domainname): + def ace2idna(domainname): """Convertis a domainname from ACE according to IDNA Keyword arguments: @@ -169,8 +172,9 @@ continue tmp.append(ToUnicode(label)) return '.'.join(tmp) + ace2idna = staticmethod(ace2idna) - def __chkDomainname(self, domainname): + def chkDomainname(domainname): """Validates the domain name of an e-mail address. Keyword arguments: @@ -178,31 +182,32 @@ """ re.compile(RE_ASCII_CHARS) if not re.match(RE_ASCII_CHARS, domainname): - domainname = self.idn2ascii(domainname) + domainname = VirtualMailManager.idn2ascii(domainname) if len(domainname) > 255: - raise VMMException((_('The domain name is too long.'), + raise VMMException((_(u'The domain name is too long.'), ERR.DOMAIN_TOO_LONG)) re.compile(RE_DOMAIN) if not re.match(RE_DOMAIN, domainname): - raise VMMException((_('The domain name is invalid.'), + raise VMMException((_(u'The domain name is invalid.'), ERR.DOMAIN_INVALID)) return domainname + chkDomainname = staticmethod(chkDomainname) - def __chkEmailAddress(self, address): + def chkEmailAddress(address): try: localpart, domain = address.split('@') except ValueError: - raise VMMException((_(u"Missing '@' sign in e-mail address '%s'.") % + raise VMMException((_(u"Missing '@' sign in e-mail address »%s«.") % address, ERR.INVALID_ADDRESS)) except AttributeError: - raise VMMException((_(u"'%s' looks not like an e-mail address.") % + raise VMMException((_(u"»%s« looks not like an e-mail address.") % address, ERR.INVALID_ADDRESS)) - domain = self.__chkDomainname(domain) - localpart = self.__chkLocalpart(localpart) + domain = VirtualMailManager.chkDomainname(domain) + localpart = VirtualMailManager.chkLocalpart(localpart) return '%s@%s' % (localpart, domain) + chkEmailAddress = staticmethod(chkEmailAddress) def __getAccount(self, address, password=None): - address = self.__chkEmailAddress(address) self.__dbConnect() if not password is None: password = self.__pwhash(password) @@ -224,17 +229,17 @@ return clear0 def __getAlias(self, address, destination=None): - address = self.__chkEmailAddress(address) + address = VirtualMailManager.chkEmailAddress(address) if not destination is None: if destination.count('@'): - destination = self.__chkEmailAddress(destination) + destination = VirtualMailManager.chkEmailAddress(destination) else: - destination = self.__chkLocalpart(destination) + destination = VirtualMailManager.chkLocalpart(destination) self.__dbConnect() return Alias(self.__dbh, address, destination) def __getDomain(self, domainname, transport=None): - domainname = self.__chkDomainname(domainname) + domainname = VirtualMailManager.chkDomainname(domainname) if transport is None: transport = self.__Cfg.get('misc', 'transport') self.__dbConnect() @@ -314,7 +319,7 @@ if uid > 0 and gid > 0: maildir = '%s' % uid if maildir.count('..') or domdir.count('..'): - raise VMMException((_('FATAL: ".." in maildir path detected.'), + raise VMMException((_(u'Found ".." in maildir path.'), ERR.FOUND_DOTS_IN_PATH)) if os.path.isdir(domdir): os.chdir(domdir) @@ -322,12 +327,12 @@ mdstat = os.stat(maildir) if (mdstat.st_uid, mdstat.st_gid) != (uid, gid): raise VMMException(( - _('FATAL: owner/group mismatch in maildir detected'), - ERR.MAILDIR_PERM_MISMATCH)) + _(u'Owner/group mismatch in maildir detected.'), + ERR.MAILDIR_PERM_MISMATCH)) rmtree(maildir, ignore_errors=True) else: - self.__warnings.append(_('No such directory: %s/%s') % - (domdir,uid)) + raise VMMException((_(u"No such directory: %s/%s") % + (domdir, uid), ERR.NO_SUCH_DIRECTORY)) def __domdirdelete(self, domdir, gid): if gid > 0: @@ -337,13 +342,13 @@ domdirdirs = domdir.replace(basedir+'/', '').split('/') if basedir.count('..') or domdir.count('..'): raise VMMException( - (_('FATAL: ".." in domain directory path detected.'), + (_(u'FATAL: ".." in domain directory path detected.'), ERR.FOUND_DOTS_IN_PATH)) if os.path.isdir('%s/%s' % (basedir, domdirdirs[0])): os.chdir('%s/%s' % (basedir, domdirdirs[0])) if os.lstat(domdirdirs[1]).st_gid != gid: raise VMMException( - (_('FATAL: group mismatch in domain directory detected'), + (_(u'FATAL: group mismatch in domain directory detected'), ERR.DOMAINDIR_GROUP_MISMATCH)) rmtree(domdirdirs[1], ignore_errors=True) @@ -430,9 +435,9 @@ try: return self.__Cfg.getboolean('config', 'done') except ValueError, e: - raise VMMConfigException(_("""Configurtion error: "%s" -(in section "connfig", option "done")' -see also: vmm.cfg(5)\n""") % str(e)) + raise VMMConfigException(_(u"""Configurtion error: "%s" +(in section "connfig", option "done") see also: vmm.cfg(5)\n""") % + str(e)) def configure(self, section=None): """Starts interactive configuration. @@ -473,7 +478,7 @@ def domain_delete(self, domainname, force=None): if not force is None and force not in ['deluser','delalias','delall']: - raise VMMDomainException((_(u"Invalid argument: '%s'") % force, + raise VMMDomainException((_(u"Invalid argument: »%s«") % force, ERR.INVALID_OPTION)) dom = self.__getDomain(domainname) gid = dom.getID() @@ -494,7 +499,7 @@ dominfo = dom.getInfo() if dominfo['domainname'].startswith('xn--'): dominfo['domainname'] += ' (%s)'\ - % self.ace2idna(dominfo['domainname']) + % VirtualMailManager.ace2idna(dominfo['domainname']) if dominfo['aliases'] is None: dominfo['aliases'] = 0 if detailed is None: @@ -503,8 +508,8 @@ return (dominfo, dom.getAliaseNames(), dom.getAccounts(), dom.getAliases()) else: - raise VMMDomainException(("%s: '%s'" % (_('Invalid argument'), - detailed), ERR.INVALID_OPTION)) + raise VMMDomainException((_(u'Invalid argument: »%s«') % detailed, + ERR.INVALID_OPTION)) def domain_alias_add(self, aliasname, domainname): """Adds an alias name to the domain. @@ -514,7 +519,8 @@ domainname -- name of the target domain (str) """ dom = self.__getDomain(domainname) - aliasname = self.__chkDomainname(aliasname) + # XXX chk by DomainAlias!!! + aliasname = VirtualMailManager.chkDomainname(aliasname) dom.saveAlias(aliasname) def domain_alias_delete(self, aliasname): @@ -524,7 +530,8 @@ aliasname -- the alias name of the domain (str) """ from Domain import deleteAlias - aliasname = self.__chkDomainname(aliasname) + aliasname = VirtualMailManager.chkDomainname(aliasname) + # XXX chk by DomainAlias!!! self.__dbConnect() deleteAlias(self.__dbh, aliasname) @@ -546,7 +553,8 @@ _(u"The pattern '%s' contains invalid characters.") % pattern, ERR.DOMAIN_INVALID)) else: - pattern = self.__chkDomainname(pattern) + pattern = VirtualMailManager.chkDomainname(pattern) + # XXX chk by domain if not like self.__dbConnect() return search(self.__dbh, pattern=pattern, like=like) @@ -572,7 +580,19 @@ gid = acc.getGID() acc.delete() if self.__Cfg.getboolean('maildir', 'delete'): - self.__maildirdelete(acc.getDir('domain'), uid, gid) + try: + self.__maildirdelete(acc.getDir('domain'), uid, gid) + except (VMMException), e: + if e[0][1] in [ERR.FOUND_DOTS_IN_PATH, + ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]: + warning = _(u"""\ +The account has been successfully deleted from the database. + But an error occurred while deleting the following directory: + »%s« + Reason: %s""") % (acc.getDir('home'), e[0][0]) + self.__warnings.append(warning) + else: + raise e def alias_info(self, aliasaddress): alias = self.__getAlias(aliasaddress) @@ -597,7 +617,8 @@ def user_password(self, emailaddress, password): acc = self.__getAccount(emailaddress) if acc.getUID() == 0: - raise VMMException((_("Account doesn't exists"),ERR.NO_SUCH_ACCOUNT)) + raise VMMException((_(u"Account doesn't exists"), + ERR.NO_SUCH_ACCOUNT)) if password is None: password = self._readpass() acc.modify('password', self.__pwhash(password))