VirtualMailManager/handler.py
branchv0.7.x
changeset 643 df1e3b67882a
parent 638 0de0b9e75c9f
child 648 9cf2cf762e26
equal deleted inserted replaced
642:4cd9d0a9f42f 643:df1e3b67882a
    48 
    48 
    49 CFG_FILE = 'vmm.cfg'
    49 CFG_FILE = 'vmm.cfg'
    50 CFG_PATH = '/root:/usr/local/etc:/etc'
    50 CFG_PATH = '/root:/usr/local/etc:/etc'
    51 RE_DOMAIN_SEARCH = """^[a-z0-9-\.]+$"""
    51 RE_DOMAIN_SEARCH = """^[a-z0-9-\.]+$"""
    52 OTHER_TYPES = {
    52 OTHER_TYPES = {
    53     TYPE_ACCOUNT: (_(u'an account'), ACCOUNT_EXISTS),
    53     TYPE_ACCOUNT: (_('an account'), ACCOUNT_EXISTS),
    54     TYPE_ALIAS: (_(u'an alias'), ALIAS_EXISTS),
    54     TYPE_ALIAS: (_('an alias'), ALIAS_EXISTS),
    55     TYPE_RELOCATED: (_(u'a relocated user'), RELOCATED_EXISTS),
    55     TYPE_RELOCATED: (_('a relocated user'), RELOCATED_EXISTS),
    56 }
    56 }
    57 
    57 
    58 
    58 
    59 class Handler(object):
    59 class Handler(object):
    60     """Wrapper class to simplify the access on all the stuff from
    60     """Wrapper class to simplify the access on all the stuff from
    76         self._cfg = None
    76         self._cfg = None
    77         self._dbh = None
    77         self._dbh = None
    78         self._db_connect = None
    78         self._db_connect = None
    79 
    79 
    80         if os.geteuid():
    80         if os.geteuid():
    81             raise NotRootError(_(u"You are not root.\n\tGood bye!\n"),
    81             raise NotRootError(_("You are not root.\n\tGood bye!\n"),
    82                                CONF_NOPERM)
    82                                CONF_NOPERM)
    83         if self._check_cfg_file():
    83         if self._check_cfg_file():
    84             self._cfg = Cfg(self._cfg_fname)
    84             self._cfg = Cfg(self._cfg_fname)
    85             self._cfg.load()
    85             self._cfg.load()
    86         if not skip_some_checks:
    86         if not skip_some_checks:
    96             tmp = os.path.join(path, CFG_FILE)
    96             tmp = os.path.join(path, CFG_FILE)
    97             if os.path.isfile(tmp):
    97             if os.path.isfile(tmp):
    98                 self._cfg_fname = tmp
    98                 self._cfg_fname = tmp
    99                 break
    99                 break
   100         if not self._cfg_fname:
   100         if not self._cfg_fname:
   101             raise VMMError(_(u"Could not find '%(cfg_file)s' in: "
   101             raise VMMError(_("Could not find '%(cfg_file)s' in: "
   102                              u"'%(cfg_path)s'") % {'cfg_file': CFG_FILE,
   102                              "'%(cfg_path)s'") % {'cfg_file': CFG_FILE,
   103                            'cfg_path': CFG_PATH}, CONF_NOFILE)
   103                            'cfg_path': CFG_PATH}, CONF_NOFILE)
   104 
   104 
   105     def _check_cfg_file(self):
   105     def _check_cfg_file(self):
   106         """Checks the configuration file, returns bool"""
   106         """Checks the configuration file, returns bool"""
   107         self._find_cfg_file()
   107         self._find_cfg_file()
   108         fstat = os.stat(self._cfg_fname)
   108         fstat = os.stat(self._cfg_fname)
   109         fmode = int(oct(fstat.st_mode & 0777))
   109         fmode = int(oct(fstat.st_mode & 0o777))
   110         if fmode % 100 and fstat.st_uid != fstat.st_gid or \
   110         if fmode % 100 and fstat.st_uid != fstat.st_gid or \
   111            fmode % 10 and fstat.st_uid == fstat.st_gid:
   111            fmode % 10 and fstat.st_uid == fstat.st_gid:
   112             # TP: Please keep the backticks around the command. `chmod 0600 …`
   112             # TP: Please keep the backticks around the command. `chmod 0600 …`
   113             raise PermissionError(_(u"wrong permissions for '%(file)s': "
   113             raise PermissionError(_("wrong permissions for '%(file)s': "
   114                                     u"%(perms)s\n`chmod 0600 %(file)s` would "
   114                                     "%(perms)s\n`chmod 0600 %(file)s` would "
   115                                     u"be great.") % {'file': self._cfg_fname,
   115                                     "be great.") % {'file': self._cfg_fname,
   116                                   'perms': fmode}, CONF_WRONGPERM)
   116                                   'perms': fmode}, CONF_WRONGPERM)
   117         else:
   117         else:
   118             return True
   118             return True
   119 
   119 
   120     def _chkenv(self):
   120     def _chkenv(self):
   122         required executables exists and are executable.
   122         required executables exists and are executable.
   123         If not, a VMMError will be raised"""
   123         If not, a VMMError will be raised"""
   124         dir_created = False
   124         dir_created = False
   125         basedir = self._cfg.dget('misc.base_directory')
   125         basedir = self._cfg.dget('misc.base_directory')
   126         if not os.path.exists(basedir):
   126         if not os.path.exists(basedir):
   127             old_umask = os.umask(0006)
   127             old_umask = os.umask(0o006)
   128             os.makedirs(basedir, 0771)
   128             os.makedirs(basedir, 0o771)
   129             os.chown(basedir, 0, 0)
   129             os.chown(basedir, 0, 0)
   130             os.umask(old_umask)
   130             os.umask(old_umask)
   131             dir_created = True
   131             dir_created = True
   132         if not dir_created and not lisdir(basedir):
   132         if not dir_created and not lisdir(basedir):
   133             raise VMMError(_(u"'%(path)s' is not a directory.\n(%(cfg_file)s: "
   133             raise VMMError(_("'%(path)s' is not a directory.\n(%(cfg_file)s: "
   134                              u"section 'misc', option 'base_directory')") %
   134                              "section 'misc', option 'base_directory')") %
   135                            {'path': basedir, 'cfg_file': self._cfg_fname},
   135                            {'path': basedir, 'cfg_file': self._cfg_fname},
   136                            NO_SUCH_DIRECTORY)
   136                            NO_SUCH_DIRECTORY)
   137         for opt, val in self._cfg.items('bin'):
   137         for opt, val in self._cfg.items('bin'):
   138             try:
   138             try:
   139                 exec_ok(val)
   139                 exec_ok(val)
   140             except VMMError, err:
   140             except VMMError as err:
   141                 if err.code in (NO_SUCH_BINARY, NOT_EXECUTABLE):
   141                 if err.code in (NO_SUCH_BINARY, NOT_EXECUTABLE):
   142                     raise VMMError(err.msg + _(u"\n(%(cfg_file)s: section "
   142                     raise VMMError(err.msg + _("\n(%(cfg_file)s: section "
   143                                    u"'bin', option '%(option)s')") %
   143                                    "'bin', option '%(option)s')") %
   144                                    {'cfg_file': self._cfg_fname,
   144                                    {'cfg_file': self._cfg_fname,
   145                                     'option': opt}, err.code)
   145                                     'option': opt}, err.code)
   146                 else:
   146                 else:
   147                     raise
   147                     raise
   148 
   148 
   151         global _db_mod
   151         global _db_mod
   152         if self._cfg.dget('database.module').lower() == 'psycopg2':
   152         if self._cfg.dget('database.module').lower() == 'psycopg2':
   153             try:
   153             try:
   154                 _db_mod = __import__('psycopg2')
   154                 _db_mod = __import__('psycopg2')
   155             except ImportError:
   155             except ImportError:
   156                 raise VMMError(_(u"Unable to import database module '%s'.") %
   156                 raise VMMError(_("Unable to import database module '%s'.") %
   157                                'psycopg2', VMM_ERROR)
   157                                'psycopg2', VMM_ERROR)
   158             self._db_connect = self._psycopg2_connect
   158             self._db_connect = self._psycopg2_connect
   159         else:
   159         else:
   160             try:
   160             try:
   161                 tmp = __import__('pyPgSQL', globals(), locals(), ['PgSQL'])
   161                 tmp = __import__('pyPgSQL', globals(), locals(), ['PgSQL'])
   162             except ImportError:
   162             except ImportError:
   163                 raise VMMError(_(u"Unable to import database module '%s'.") %
   163                 raise VMMError(_("Unable to import database module '%s'.") %
   164                                'pyPgSQL', VMM_ERROR)
   164                                'pyPgSQL', VMM_ERROR)
   165             _db_mod = tmp.PgSQL
   165             _db_mod = tmp.PgSQL
   166             self._db_connect = self._pypgsql_connect
   166             self._db_connect = self._pypgsql_connect
   167 
   167 
   168     def _pypgsql_connect(self):
   168     def _pypgsql_connect(self):
   178                         password=self._cfg.pget('database.pass'),
   178                         password=self._cfg.pget('database.pass'),
   179                         client_encoding='utf8', unicode_results=True)
   179                         client_encoding='utf8', unicode_results=True)
   180                 dbc = self._dbh.cursor()
   180                 dbc = self._dbh.cursor()
   181                 dbc.execute("SET NAMES 'UTF8'")
   181                 dbc.execute("SET NAMES 'UTF8'")
   182                 dbc.close()
   182                 dbc.close()
   183             except _db_mod.libpq.DatabaseError, err:
   183             except _db_mod.libpq.DatabaseError as err:
   184                 raise VMMError(str(err), DATABASE_ERROR)
   184                 raise VMMError(str(err), DATABASE_ERROR)
   185 
   185 
   186     def _psycopg2_connect(self):
   186     def _psycopg2_connect(self):
   187         """Return a new psycopg2 connection object."""
   187         """Return a new psycopg2 connection object."""
   188         if self._dbh is None or \
   188         if self._dbh is None or \
   199                 self._dbh.set_client_encoding('utf8')
   199                 self._dbh.set_client_encoding('utf8')
   200                 _db_mod.extensions.register_type(_db_mod.extensions.UNICODE)
   200                 _db_mod.extensions.register_type(_db_mod.extensions.UNICODE)
   201                 dbc = self._dbh.cursor()
   201                 dbc = self._dbh.cursor()
   202                 dbc.execute("SET NAMES 'UTF8'")
   202                 dbc.execute("SET NAMES 'UTF8'")
   203                 dbc.close()
   203                 dbc.close()
   204             except _db_mod.DatabaseError, err:
   204             except _db_mod.DatabaseError as err:
   205                 raise VMMError(str(err), DATABASE_ERROR)
   205                 raise VMMError(str(err), DATABASE_ERROR)
   206 
   206 
   207     def _chk_other_address_types(self, address, exclude):
   207     def _chk_other_address_types(self, address, exclude):
   208         """Checks if the EmailAddress *address* is known as `TYPE_ACCOUNT`,
   208         """Checks if the EmailAddress *address* is known as `TYPE_ACCOUNT`,
   209         `TYPE_ALIAS` or `TYPE_RELOCATED`, but not as the `TYPE_*` specified
   209         `TYPE_ALIAS` or `TYPE_RELOCATED`, but not as the `TYPE_*` specified
   237         other = self._chk_other_address_types(address, exclude)
   237         other = self._chk_other_address_types(address, exclude)
   238         if not other:
   238         if not other:
   239             return False
   239             return False
   240         # TP: %(a_type)s will be one of: 'an account', 'an alias' or
   240         # TP: %(a_type)s will be one of: 'an account', 'an alias' or
   241         # 'a relocated user'
   241         # 'a relocated user'
   242         msg = _(u"There is already %(a_type)s with the address '%(address)s'.")
   242         msg = _("There is already %(a_type)s with the address '%(address)s'.")
   243         raise VMMError(msg % {'a_type': OTHER_TYPES[other][0],
   243         raise VMMError(msg % {'a_type': OTHER_TYPES[other][0],
   244                               'address': address}, OTHER_TYPES[other][1])
   244                               'address': address}, OTHER_TYPES[other][1])
   245 
   245 
   246     def _get_account(self, address):
   246     def _get_account(self, address):
   247         """Return an Account instances for the given address (str)."""
   247         """Return an Account instances for the given address (str)."""
   290         """Create a directory for the `domain` and its accounts."""
   290         """Create a directory for the `domain` and its accounts."""
   291         cwd = os.getcwd()
   291         cwd = os.getcwd()
   292         hashdir, domdir = domain.directory.split(os.path.sep)[-2:]
   292         hashdir, domdir = domain.directory.split(os.path.sep)[-2:]
   293         dir_created = False
   293         dir_created = False
   294         os.chdir(self._cfg.dget('misc.base_directory'))
   294         os.chdir(self._cfg.dget('misc.base_directory'))
   295         old_umask = os.umask(0022)
   295         old_umask = os.umask(0o022)
   296         if not os.path.exists(hashdir):
   296         if not os.path.exists(hashdir):
   297             os.mkdir(hashdir, 0711)
   297             os.mkdir(hashdir, 0o711)
   298             os.chown(hashdir, 0, 0)
   298             os.chown(hashdir, 0, 0)
   299             dir_created = True
   299             dir_created = True
   300         if not dir_created and not lisdir(hashdir):
   300         if not dir_created and not lisdir(hashdir):
   301             raise VMMError(_(u"'%s' is not a directory.") % hashdir,
   301             raise VMMError(_("'%s' is not a directory.") % hashdir,
   302                            NO_SUCH_DIRECTORY)
   302                            NO_SUCH_DIRECTORY)
   303         if os.path.exists(domain.directory):
   303         if os.path.exists(domain.directory):
   304             raise VMMError(_(u"The file/directory '%s' already exists.") %
   304             raise VMMError(_("The file/directory '%s' already exists.") %
   305                            domain.directory, VMM_ERROR)
   305                            domain.directory, VMM_ERROR)
   306         os.mkdir(os.path.join(hashdir, domdir),
   306         os.mkdir(os.path.join(hashdir, domdir),
   307                  self._cfg.dget('domain.directory_mode'))
   307                  self._cfg.dget('domain.directory_mode'))
   308         os.chown(domain.directory, 0, domain.gid)
   308         os.chown(domain.directory, 0, domain.gid)
   309         os.umask(old_umask)
   309         os.umask(old_umask)
   312     def _make_home(self, account):
   312     def _make_home(self, account):
   313         """Create a home directory for the new Account *account*."""
   313         """Create a home directory for the new Account *account*."""
   314         domdir = account.domain.directory
   314         domdir = account.domain.directory
   315         if not lisdir(domdir):
   315         if not lisdir(domdir):
   316             self._make_domain_dir(account.domain)
   316             self._make_domain_dir(account.domain)
   317         os.umask(0007)
   317         os.umask(0o007)
   318         uid = account.uid
   318         uid = account.uid
   319         os.chdir(domdir)
   319         os.chdir(domdir)
   320         os.mkdir('%s' % uid, self._cfg.dget('account.directory_mode'))
   320         os.mkdir('%s' % uid, self._cfg.dget('account.directory_mode'))
   321         os.chown('%s' % uid, uid, account.gid)
   321         os.chown('%s' % uid, uid, account.gid)
   322 
   322 
   329         folders = self._cfg.dget('mailbox.folders').split(':')
   329         folders = self._cfg.dget('mailbox.folders').split(':')
   330         if any(folders):
   330         if any(folders):
   331             bad = mailbox.add_boxes(folders,
   331             bad = mailbox.add_boxes(folders,
   332                                     self._cfg.dget('mailbox.subscribe'))
   332                                     self._cfg.dget('mailbox.subscribe'))
   333             if bad:
   333             if bad:
   334                 self._warnings.append(_(u"Skipped mailbox folders:") +
   334                 self._warnings.append(_("Skipped mailbox folders:") +
   335                                       '\n\t- ' + '\n\t- '.join(bad))
   335                                       '\n\t- ' + '\n\t- '.join(bad))
   336         os.chdir(oldpwd)
   336         os.chdir(oldpwd)
   337 
   337 
   338     def _delete_home(self, domdir, uid, gid):
   338     def _delete_home(self, domdir, uid, gid):
   339         """Delete a user's home directory.
   339         """Delete a user's home directory.
   346         `uid` : int/long
   346         `uid` : int/long
   347           The user's UID (commonly AccountObj.uid)
   347           The user's UID (commonly AccountObj.uid)
   348         `gid` : int/long
   348         `gid` : int/long
   349           The user's GID (commonly AccountObj.gid)
   349           The user's GID (commonly AccountObj.gid)
   350         """
   350         """
   351         assert all(isinstance(xid, (long, int)) for xid in (uid, gid)) and \
   351         assert all(isinstance(xid, int) for xid in (uid, gid)) and \
   352                 isinstance(domdir, basestring)
   352                 isinstance(domdir, str)
   353         if uid < MIN_UID or gid < MIN_GID:
   353         if uid < MIN_UID or gid < MIN_GID:
   354             raise VMMError(_(u"UID '%(uid)u' and/or GID '%(gid)u' are less "
   354             raise VMMError(_("UID '%(uid)u' and/or GID '%(gid)u' are less "
   355                              u"than %(min_uid)u/%(min_gid)u.") % {'uid': uid,
   355                              "than %(min_uid)u/%(min_gid)u.") % {'uid': uid,
   356                            'gid': gid, 'min_gid': MIN_GID, 'min_uid': MIN_UID},
   356                            'gid': gid, 'min_gid': MIN_GID, 'min_uid': MIN_UID},
   357                            MAILDIR_PERM_MISMATCH)
   357                            MAILDIR_PERM_MISMATCH)
   358         if domdir.count('..'):
   358         if domdir.count('..'):
   359             raise VMMError(_(u'Found ".." in domain directory path: %s') %
   359             raise VMMError(_('Found ".." in domain directory path: %s') %
   360                            domdir, FOUND_DOTS_IN_PATH)
   360                            domdir, FOUND_DOTS_IN_PATH)
   361         if not lisdir(domdir):
   361         if not lisdir(domdir):
   362             raise VMMError(_(u"No such directory: %s") % domdir,
   362             raise VMMError(_("No such directory: %s") % domdir,
   363                            NO_SUCH_DIRECTORY)
   363                            NO_SUCH_DIRECTORY)
   364         os.chdir(domdir)
   364         os.chdir(domdir)
   365         userdir = '%s' % uid
   365         userdir = '%s' % uid
   366         if not lisdir(userdir):
   366         if not lisdir(userdir):
   367             self._warnings.append(_(u"No such directory: %s") %
   367             self._warnings.append(_("No such directory: %s") %
   368                                   os.path.join(domdir, userdir))
   368                                   os.path.join(domdir, userdir))
   369             return
   369             return
   370         mdstat = os.lstat(userdir)
   370         mdstat = os.lstat(userdir)
   371         if (mdstat.st_uid, mdstat.st_gid) != (uid, gid):
   371         if (mdstat.st_uid, mdstat.st_gid) != (uid, gid):
   372             raise VMMError(_(u'Detected owner/group mismatch in home '
   372             raise VMMError(_('Detected owner/group mismatch in home '
   373                              u'directory.'), MAILDIR_PERM_MISMATCH)
   373                              'directory.'), MAILDIR_PERM_MISMATCH)
   374         rmtree(userdir, ignore_errors=True)
   374         rmtree(userdir, ignore_errors=True)
   375 
   375 
   376     def _delete_domain_dir(self, domdir, gid):
   376     def _delete_domain_dir(self, domdir, gid):
   377         """Delete a domain's directory.
   377         """Delete a domain's directory.
   378 
   378 
   381         `domdir` : basestring
   381         `domdir` : basestring
   382           The domain's directory (commonly DomainObj.directory)
   382           The domain's directory (commonly DomainObj.directory)
   383         `gid` : int/long
   383         `gid` : int/long
   384           The domain's GID (commonly DomainObj.gid)
   384           The domain's GID (commonly DomainObj.gid)
   385         """
   385         """
   386         assert isinstance(domdir, basestring) and isinstance(gid, (long, int))
   386         assert isinstance(domdir, str) and isinstance(gid, int)
   387         if gid < MIN_GID:
   387         if gid < MIN_GID:
   388             raise VMMError(_(u"GID '%(gid)u' is less than '%(min_gid)u'.") %
   388             raise VMMError(_("GID '%(gid)u' is less than '%(min_gid)u'.") %
   389                            {'gid': gid, 'min_gid': MIN_GID},
   389                            {'gid': gid, 'min_gid': MIN_GID},
   390                            DOMAINDIR_GROUP_MISMATCH)
   390                            DOMAINDIR_GROUP_MISMATCH)
   391         if domdir.count('..'):
   391         if domdir.count('..'):
   392             raise VMMError(_(u'Found ".." in domain directory path: %s') %
   392             raise VMMError(_('Found ".." in domain directory path: %s') %
   393                            domdir, FOUND_DOTS_IN_PATH)
   393                            domdir, FOUND_DOTS_IN_PATH)
   394         if not lisdir(domdir):
   394         if not lisdir(domdir):
   395             self._warnings.append(_('No such directory: %s') % domdir)
   395             self._warnings.append(_('No such directory: %s') % domdir)
   396             return
   396             return
   397         dirst = os.lstat(domdir)
   397         dirst = os.lstat(domdir)
   398         if dirst.st_gid != gid:
   398         if dirst.st_gid != gid:
   399             raise VMMError(_(u'Detected group mismatch in domain directory: '
   399             raise VMMError(_('Detected group mismatch in domain directory: '
   400                              u'%s') % domdir, DOMAINDIR_GROUP_MISMATCH)
   400                              '%s') % domdir, DOMAINDIR_GROUP_MISMATCH)
   401         rmtree(domdir, ignore_errors=True)
   401         rmtree(domdir, ignore_errors=True)
   402 
   402 
   403     def has_warnings(self):
   403     def has_warnings(self):
   404         """Checks if warnings are present, returns bool."""
   404         """Checks if warnings are present, returns bool."""
   405         return bool(len(self._warnings))
   405         return bool(len(self._warnings))
   423         return self._cfg.pget(option)
   423         return self._cfg.pget(option)
   424 
   424 
   425     def cfg_install(self):
   425     def cfg_install(self):
   426         """Installs the cfg_dget method as ``cfg_dget`` into the built-in
   426         """Installs the cfg_dget method as ``cfg_dget`` into the built-in
   427         namespace."""
   427         namespace."""
   428         import __builtin__
   428         import builtins
   429         assert 'cfg_dget' not in __builtin__.__dict__
   429         assert 'cfg_dget' not in builtins.__dict__
   430         __builtin__.__dict__['cfg_dget'] = self._cfg.dget
   430         builtins.__dict__['cfg_dget'] = self._cfg.dget
   431 
   431 
   432     def domain_add(self, domainname, transport=None):
   432     def domain_add(self, domainname, transport=None):
   433         """Wrapper around Domain's set_quotalimit, set_transport and save."""
   433         """Wrapper around Domain's set_quotalimit, set_transport and save."""
   434         dom = self._get_domain(domainname)
   434         dom = self._get_domain(domainname)
   435         if transport is None:
   435         if transport is None:
   436             dom.set_transport(Transport(self._dbh,
   436             dom.set_transport(Transport(self._dbh,
   437                               transport=self._cfg.dget('domain.transport')))
   437                               transport=self._cfg.dget('domain.transport')))
   438         else:
   438         else:
   439             dom.set_transport(Transport(self._dbh, transport=transport))
   439             dom.set_transport(Transport(self._dbh, transport=transport))
   440         dom.set_quotalimit(QuotaLimit(self._dbh,
   440         dom.set_quotalimit(QuotaLimit(self._dbh,
   441                            bytes=long(self._cfg.dget('domain.quota_bytes')),
   441                            bytes=int(self._cfg.dget('domain.quota_bytes')),
   442                            messages=self._cfg.dget('domain.quota_messages')))
   442                            messages=self._cfg.dget('domain.quota_messages')))
   443         dom.set_serviceset(ServiceSet(self._dbh,
   443         dom.set_serviceset(ServiceSet(self._dbh,
   444                                       imap=self._cfg.dget('domain.imap'),
   444                                       imap=self._cfg.dget('domain.imap'),
   445                                       pop3=self._cfg.dget('domain.pop3'),
   445                                       pop3=self._cfg.dget('domain.pop3'),
   446                                       sieve=self._cfg.dget('domain.sieve'),
   446                                       sieve=self._cfg.dget('domain.sieve'),
   449         dom.save()
   449         dom.save()
   450         self._make_domain_dir(dom)
   450         self._make_domain_dir(dom)
   451 
   451 
   452     def domain_quotalimit(self, domainname, bytes_, messages=0, force=None):
   452     def domain_quotalimit(self, domainname, bytes_, messages=0, force=None):
   453         """Wrapper around Domain.update_quotalimit()."""
   453         """Wrapper around Domain.update_quotalimit()."""
   454         if not all(isinstance(i, (int, long)) for i in (bytes_, messages)):
   454         if not all(isinstance(i, int) for i in (bytes_, messages)):
   455             raise TypeError("'bytes_' and 'messages' have to be "
   455             raise TypeError("'bytes_' and 'messages' have to be "
   456                             "integers or longs.")
   456                             "integers or longs.")
   457         if force is not None and force != 'force':
   457         if force is not None and force != 'force':
   458             raise DomainError(_(u"Invalid argument: '%s'") % force,
   458             raise DomainError(_("Invalid argument: '%s'") % force,
   459                               INVALID_ARGUMENT)
   459                               INVALID_ARGUMENT)
   460         dom = self._get_domain(domainname)
   460         dom = self._get_domain(domainname)
   461         quotalimit = QuotaLimit(self._dbh, bytes=bytes_, messages=messages)
   461         quotalimit = QuotaLimit(self._dbh, bytes=bytes_, messages=messages)
   462         if force is None:
   462         if force is None:
   463             dom.update_quotalimit(quotalimit)
   463             dom.update_quotalimit(quotalimit)
   466 
   466 
   467     def domain_services(self, domainname, force=None, *services):
   467     def domain_services(self, domainname, force=None, *services):
   468         """Wrapper around Domain.update_serviceset()."""
   468         """Wrapper around Domain.update_serviceset()."""
   469         kwargs = dict.fromkeys(SERVICES, False)
   469         kwargs = dict.fromkeys(SERVICES, False)
   470         if force is not None and force != 'force':
   470         if force is not None and force != 'force':
   471             raise DomainError(_(u"Invalid argument: '%s'") % force,
   471             raise DomainError(_("Invalid argument: '%s'") % force,
   472                               INVALID_ARGUMENT)
   472                               INVALID_ARGUMENT)
   473         for service in set(services):
   473         for service in set(services):
   474             if service not in SERVICES:
   474             if service not in SERVICES:
   475                 raise DomainError(_(u"Unknown service: '%s'") % service,
   475                 raise DomainError(_("Unknown service: '%s'") % service,
   476                                   UNKNOWN_SERVICE)
   476                                   UNKNOWN_SERVICE)
   477             kwargs[service] = True
   477             kwargs[service] = True
   478 
   478 
   479         dom = self._get_domain(domainname)
   479         dom = self._get_domain(domainname)
   480         serviceset = ServiceSet(self._dbh, **kwargs)
   480         serviceset = ServiceSet(self._dbh, **kwargs)
   481         dom.update_serviceset(serviceset, (True, False)[not force])
   481         dom.update_serviceset(serviceset, (True, False)[not force])
   482 
   482 
   483     def domain_transport(self, domainname, transport, force=None):
   483     def domain_transport(self, domainname, transport, force=None):
   484         """Wrapper around Domain.update_transport()"""
   484         """Wrapper around Domain.update_transport()"""
   485         if force is not None and force != 'force':
   485         if force is not None and force != 'force':
   486             raise DomainError(_(u"Invalid argument: '%s'") % force,
   486             raise DomainError(_("Invalid argument: '%s'") % force,
   487                               INVALID_ARGUMENT)
   487                               INVALID_ARGUMENT)
   488         dom = self._get_domain(domainname)
   488         dom = self._get_domain(domainname)
   489         trsp = Transport(self._dbh, transport=transport)
   489         trsp = Transport(self._dbh, transport=transport)
   490         if force is None:
   490         if force is None:
   491             dom.update_transport(trsp)
   491             dom.update_transport(trsp)
   515         """Wrapper around Domain.get_info(), Domain.get_accounts(),
   515         """Wrapper around Domain.get_info(), Domain.get_accounts(),
   516         Domain.get_aliase_names(), Domain.get_aliases() and
   516         Domain.get_aliase_names(), Domain.get_aliases() and
   517         Domain.get_relocated."""
   517         Domain.get_relocated."""
   518         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
   518         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
   519                            'relocated', 'catchall']:
   519                            'relocated', 'catchall']:
   520             raise VMMError(_(u"Invalid argument: '%s'") % details,
   520             raise VMMError(_("Invalid argument: '%s'") % details,
   521                            INVALID_ARGUMENT)
   521                            INVALID_ARGUMENT)
   522         dom = self._get_domain(domainname)
   522         dom = self._get_domain(domainname)
   523         dominfo = dom.get_info()
   523         dominfo = dom.get_info()
   524         if dominfo['domain name'].startswith('xn--'):
   524         if dominfo['domain name'].startswith('xn--'):
   525             dominfo['domain name'] += ' (%s)' % \
   525             dominfo['domain name'] += ' (%s)' % \
   594         from VirtualMailManager.domain import search
   594         from VirtualMailManager.domain import search
   595         like = False
   595         like = False
   596         if pattern and (pattern.startswith('%') or pattern.endswith('%')):
   596         if pattern and (pattern.startswith('%') or pattern.endswith('%')):
   597             like = True
   597             like = True
   598             if not re.match(RE_DOMAIN_SEARCH, pattern.strip('%')):
   598             if not re.match(RE_DOMAIN_SEARCH, pattern.strip('%')):
   599                 raise VMMError(_(u"The pattern '%s' contains invalid "
   599                 raise VMMError(_("The pattern '%s' contains invalid "
   600                                  u"characters.") % pattern, DOMAIN_INVALID)
   600                                  "characters.") % pattern, DOMAIN_INVALID)
   601         self._db_connect()
   601         self._db_connect()
   602         return search(self._dbh, pattern=pattern, like=like)
   602         return search(self._dbh, pattern=pattern, like=like)
   603 
   603 
   604     def address_list(self, typelimit, pattern=None):
   604     def address_list(self, typelimit, pattern=None):
   605         """TODO"""
   605         """TODO"""
   615                 dpattern = parts[1]
   615                 dpattern = parts[1]
   616                 dlike = dpattern.startswith('%') or dpattern.endswith('%')
   616                 dlike = dpattern.startswith('%') or dpattern.endswith('%')
   617 
   617 
   618                 checkp = lpattern.strip('%') if llike else lpattern
   618                 checkp = lpattern.strip('%') if llike else lpattern
   619                 if len(checkp) > 0 and re.search(RE_LOCALPART, checkp):
   619                 if len(checkp) > 0 and re.search(RE_LOCALPART, checkp):
   620                     raise VMMError(_(u"The pattern '%s' contains invalid "
   620                     raise VMMError(_("The pattern '%s' contains invalid "
   621                                      u"characters.") % pattern,
   621                                      "characters.") % pattern,
   622                                    LOCALPART_INVALID)
   622                                    LOCALPART_INVALID)
   623             else:
   623             else:
   624                 # else just match on domains
   624                 # else just match on domains
   625                 # (or should that be local part, I don't know…)
   625                 # (or should that be local part, I don't know…)
   626                 dpattern = parts[0]
   626                 dpattern = parts[0]
   627                 dlike = dpattern.startswith('%') or dpattern.endswith('%')
   627                 dlike = dpattern.startswith('%') or dpattern.endswith('%')
   628 
   628 
   629             checkp = dpattern.strip('%') if dlike else dpattern
   629             checkp = dpattern.strip('%') if dlike else dpattern
   630             if len(checkp) > 0 and not re.match(RE_DOMAIN_SEARCH, checkp):
   630             if len(checkp) > 0 and not re.match(RE_DOMAIN_SEARCH, checkp):
   631                 raise VMMError(_(u"The pattern '%s' contains invalid "
   631                 raise VMMError(_("The pattern '%s' contains invalid "
   632                                  u"characters.") % pattern, DOMAIN_INVALID)
   632                                  "characters.") % pattern, DOMAIN_INVALID)
   633         self._db_connect()
   633         self._db_connect()
   634         from VirtualMailManager.common import search_addresses
   634         from VirtualMailManager.common import search_addresses
   635         return search_addresses(self._dbh, typelimit=typelimit,
   635         return search_addresses(self._dbh, typelimit=typelimit,
   636                                 lpattern=lpattern, llike=llike,
   636                                 lpattern=lpattern, llike=llike,
   637                                 dpattern=dpattern, dlike=dlike)
   637                                 dpattern=dpattern, dlike=dlike)
   638 
   638 
   639     def user_add(self, emailaddress, password):
   639     def user_add(self, emailaddress, password):
   640         """Wrapper around Account.set_password() and Account.save()."""
   640         """Wrapper around Account.set_password() and Account.save()."""
   641         acc = self._get_account(emailaddress)
   641         acc = self._get_account(emailaddress)
   642         if acc:
   642         if acc:
   643             raise VMMError(_(u"The account '%s' already exists.") %
   643             raise VMMError(_("The account '%s' already exists.") %
   644                            acc.address, ACCOUNT_EXISTS)
   644                            acc.address, ACCOUNT_EXISTS)
   645         self._is_other_address(acc.address, TYPE_ACCOUNT)
   645         self._is_other_address(acc.address, TYPE_ACCOUNT)
   646         acc.set_password(password)
   646         acc.set_password(password)
   647         acc.save()
   647         acc.save()
   648         self._make_account_dirs(acc)
   648         self._make_account_dirs(acc)
   661             self._warnings.append(_('Ignored destination addresses:'))
   661             self._warnings.append(_('Ignored destination addresses:'))
   662             self._warnings.extend(('  * %s' % w for w in warnings))
   662             self._warnings.extend(('  * %s' % w for w in warnings))
   663         for destination in destinations:
   663         for destination in destinations:
   664             if destination.gid and \
   664             if destination.gid and \
   665                not self._chk_other_address_types(destination, TYPE_RELOCATED):
   665                not self._chk_other_address_types(destination, TYPE_RELOCATED):
   666                 self._warnings.append(_(u"The destination account/alias '%s' "
   666                 self._warnings.append(_("The destination account/alias '%s' "
   667                                         u"does not exist.") % destination)
   667                                         "does not exist.") % destination)
   668 
   668 
   669     def user_delete(self, emailaddress, force=False):
   669     def user_delete(self, emailaddress, force=False):
   670         """Wrapper around Account.delete(...)"""
   670         """Wrapper around Account.delete(...)"""
   671         if not isinstance(force, bool):
   671         if not isinstance(force, bool):
   672             raise TypeError('force must be a bool')
   672             raise TypeError('force must be a bool')
   673         acc = self._get_account(emailaddress)
   673         acc = self._get_account(emailaddress)
   674         if not acc:
   674         if not acc:
   675             raise VMMError(_(u"The account '%s' does not exist.") %
   675             raise VMMError(_("The account '%s' does not exist.") %
   676                            acc.address, NO_SUCH_ACCOUNT)
   676                            acc.address, NO_SUCH_ACCOUNT)
   677         uid = acc.uid
   677         uid = acc.uid
   678         gid = acc.gid
   678         gid = acc.gid
   679         dom_dir = acc.domain.directory
   679         dom_dir = acc.domain.directory
   680         acc_dir = acc.home
   680         acc_dir = acc.home
   681         acc.delete(force)
   681         acc.delete(force)
   682         if self._cfg.dget('account.delete_directory'):
   682         if self._cfg.dget('account.delete_directory'):
   683             try:
   683             try:
   684                 self._delete_home(dom_dir, uid, gid)
   684                 self._delete_home(dom_dir, uid, gid)
   685             except VMMError, err:
   685             except VMMError as err:
   686                 if err.code in (FOUND_DOTS_IN_PATH, MAILDIR_PERM_MISMATCH,
   686                 if err.code in (FOUND_DOTS_IN_PATH, MAILDIR_PERM_MISMATCH,
   687                                 NO_SUCH_DIRECTORY):
   687                                 NO_SUCH_DIRECTORY):
   688                     warning = _(u"""\
   688                     warning = _("""\
   689 The account has been successfully deleted from the database.
   689 The account has been successfully deleted from the database.
   690     But an error occurred while deleting the following directory:
   690     But an error occurred while deleting the following directory:
   691     '%(directory)s'
   691     '%(directory)s'
   692     Reason: %(reason)s""") % {'directory': acc_dir, 'reason': err.msg}
   692     Reason: %(reason)s""") % {'directory': acc_dir, 'reason': err.msg}
   693                     self._warnings.append(warning)
   693                     self._warnings.append(warning)
   699         instances) for the `Alias` with the given *aliasaddress*."""
   699         instances) for the `Alias` with the given *aliasaddress*."""
   700         alias = self._get_alias(aliasaddress)
   700         alias = self._get_alias(aliasaddress)
   701         if alias:
   701         if alias:
   702             return alias.get_destinations()
   702             return alias.get_destinations()
   703         if not self._is_other_address(alias.address, TYPE_ALIAS):
   703         if not self._is_other_address(alias.address, TYPE_ALIAS):
   704             raise VMMError(_(u"The alias '%s' does not exist.") %
   704             raise VMMError(_("The alias '%s' does not exist.") %
   705                            alias.address, NO_SUCH_ALIAS)
   705                            alias.address, NO_SUCH_ALIAS)
   706 
   706 
   707     def alias_delete(self, aliasaddress, targetaddresses=None):
   707     def alias_delete(self, aliasaddress, targetaddresses=None):
   708         """Deletes the `Alias` *aliasaddress* with all its destinations from
   708         """Deletes the `Alias` *aliasaddress* with all its destinations from
   709         the database. If *targetaddresses* is not ``None``, only the given
   709         the database. If *targetaddresses* is not ``None``, only the given
   716             destinations = [DestinationEmailAddress(addr, self._dbh)
   716             destinations = [DestinationEmailAddress(addr, self._dbh)
   717                             for addr in targetaddresses]
   717                             for addr in targetaddresses]
   718             warnings = []
   718             warnings = []
   719             try:
   719             try:
   720                 alias.del_destinations(destinations, warnings)
   720                 alias.del_destinations(destinations, warnings)
   721             except VMMError, err:
   721             except VMMError as err:
   722                 error = err
   722                 error = err
   723             if warnings:
   723             if warnings:
   724                 self._warnings.append(_('Ignored destination addresses:'))
   724                 self._warnings.append(_('Ignored destination addresses:'))
   725                 self._warnings.extend(('  * %s' % w for w in warnings))
   725                 self._warnings.extend(('  * %s' % w for w in warnings))
   726             if error:
   726             if error:
   738             self._warnings.append(_('Ignored destination addresses:'))
   738             self._warnings.append(_('Ignored destination addresses:'))
   739             self._warnings.extend(('  * %s' % w for w in warnings))
   739             self._warnings.extend(('  * %s' % w for w in warnings))
   740         for destination in destinations:
   740         for destination in destinations:
   741             if destination.gid and \
   741             if destination.gid and \
   742                not self._chk_other_address_types(destination, TYPE_RELOCATED):
   742                not self._chk_other_address_types(destination, TYPE_RELOCATED):
   743                 self._warnings.append(_(u"The destination account/alias '%s' "
   743                 self._warnings.append(_("The destination account/alias '%s' "
   744                                         u"does not exist.") % destination)
   744                                         "does not exist.") % destination)
   745 
   745 
   746     def catchall_info(self, domain):
   746     def catchall_info(self, domain):
   747         """Returns an iterator object for all destinations (`EmailAddress`
   747         """Returns an iterator object for all destinations (`EmailAddress`
   748         instances) for the `CatchallAlias` with the given *domain*."""
   748         instances) for the `CatchallAlias` with the given *domain*."""
   749         return self._get_catchall(domain).get_destinations()
   749         return self._get_catchall(domain).get_destinations()
   760             destinations = [DestinationEmailAddress(addr, self._dbh)
   760             destinations = [DestinationEmailAddress(addr, self._dbh)
   761                             for addr in targetaddresses]
   761                             for addr in targetaddresses]
   762             warnings = []
   762             warnings = []
   763             try:
   763             try:
   764                 catchall.del_destinations(destinations, warnings)
   764                 catchall.del_destinations(destinations, warnings)
   765             except VMMError, err:
   765             except VMMError as err:
   766                 error = err
   766                 error = err
   767             if warnings:
   767             if warnings:
   768                 self._warnings.append(_('Ignored destination addresses:'))
   768                 self._warnings.append(_('Ignored destination addresses:'))
   769                 self._warnings.extend(('  * %s' % w for w in warnings))
   769                 self._warnings.extend(('  * %s' % w for w in warnings))
   770             if error:
   770             if error:
   771                 raise error
   771                 raise error
   772 
   772 
   773     def user_info(self, emailaddress, details=None):
   773     def user_info(self, emailaddress, details=None):
   774         """Wrapper around Account.get_info(...)"""
   774         """Wrapper around Account.get_info(...)"""
   775         if details not in (None, 'du', 'aliases', 'full'):
   775         if details not in (None, 'du', 'aliases', 'full'):
   776             raise VMMError(_(u"Invalid argument: '%s'") % details,
   776             raise VMMError(_("Invalid argument: '%s'") % details,
   777                            INVALID_ARGUMENT)
   777                            INVALID_ARGUMENT)
   778         acc = self._get_account(emailaddress)
   778         acc = self._get_account(emailaddress)
   779         if not acc:
   779         if not acc:
   780             if not self._is_other_address(acc.address, TYPE_ACCOUNT):
   780             if not self._is_other_address(acc.address, TYPE_ACCOUNT):
   781                 raise VMMError(_(u"The account '%s' does not exist.") %
   781                 raise VMMError(_("The account '%s' does not exist.") %
   782                                acc.address, NO_SUCH_ACCOUNT)
   782                                acc.address, NO_SUCH_ACCOUNT)
   783         info = acc.get_info()
   783         info = acc.get_info()
   784         if self._cfg.dget('account.disk_usage') or details in ('du', 'full'):
   784         if self._cfg.dget('account.disk_usage') or details in ('du', 'full'):
   785             path = os.path.join(acc.home, acc.mail_location.directory)
   785             path = os.path.join(acc.home, acc.mail_location.directory)
   786             info['disk usage'] = self._get_disk_usage(path)
   786             info['disk usage'] = self._get_disk_usage(path)
   797         self._db_connect()
   797         self._db_connect()
   798         return get_account_by_uid(uid, self._dbh)
   798         return get_account_by_uid(uid, self._dbh)
   799 
   799 
   800     def user_password(self, emailaddress, password):
   800     def user_password(self, emailaddress, password):
   801         """Wrapper for Account.modify('password' ...)."""
   801         """Wrapper for Account.modify('password' ...)."""
   802         if not isinstance(password, basestring) or not password:
   802         if not isinstance(password, str) or not password:
   803             raise VMMError(_(u"Could not accept password: '%s'") % password,
   803             raise VMMError(_("Could not accept password: '%s'") % password,
   804                            INVALID_ARGUMENT)
   804                            INVALID_ARGUMENT)
   805         acc = self._get_account(emailaddress)
   805         acc = self._get_account(emailaddress)
   806         if not acc:
   806         if not acc:
   807             raise VMMError(_(u"The account '%s' does not exist.") %
   807             raise VMMError(_("The account '%s' does not exist.") %
   808                            acc.address, NO_SUCH_ACCOUNT)
   808                            acc.address, NO_SUCH_ACCOUNT)
   809         acc.modify('password', password)
   809         acc.modify('password', password)
   810 
   810 
   811     def user_name(self, emailaddress, name):
   811     def user_name(self, emailaddress, name):
   812         """Wrapper for Account.modify('name', ...)."""
   812         """Wrapper for Account.modify('name', ...)."""
   813         acc = self._get_account(emailaddress)
   813         acc = self._get_account(emailaddress)
   814         if not acc:
   814         if not acc:
   815             raise VMMError(_(u"The account '%s' does not exist.") %
   815             raise VMMError(_("The account '%s' does not exist.") %
   816                            acc.address, NO_SUCH_ACCOUNT)
   816                            acc.address, NO_SUCH_ACCOUNT)
   817         acc.modify('name', name)
   817         acc.modify('name', name)
   818 
   818 
   819     def user_note(self, emailaddress, note):
   819     def user_note(self, emailaddress, note):
   820         """Wrapper for Account.modify('note', ...)."""
   820         """Wrapper for Account.modify('note', ...)."""
   821         acc = self._get_account(emailaddress)
   821         acc = self._get_account(emailaddress)
   822         if not acc:
   822         if not acc:
   823             raise VMMError(_(u"The account '%s' does not exist.") %
   823             raise VMMError(_("The account '%s' does not exist.") %
   824                            acc.address, NO_SUCH_ACCOUNT)
   824                            acc.address, NO_SUCH_ACCOUNT)
   825         acc.modify('note', note)
   825         acc.modify('note', note)
   826 
   826 
   827     def user_quotalimit(self, emailaddress, bytes_, messages=0):
   827     def user_quotalimit(self, emailaddress, bytes_, messages=0):
   828         """Wrapper for Account.update_quotalimit(QuotaLimit)."""
   828         """Wrapper for Account.update_quotalimit(QuotaLimit)."""
   829         acc = self._get_account(emailaddress)
   829         acc = self._get_account(emailaddress)
   830         if not acc:
   830         if not acc:
   831             raise VMMError(_(u"The account '%s' does not exist.") %
   831             raise VMMError(_("The account '%s' does not exist.") %
   832                         acc.address, NO_SUCH_ACCOUNT)
   832                         acc.address, NO_SUCH_ACCOUNT)
   833         if bytes_ == 'domain':
   833         if bytes_ == 'domain':
   834             quotalimit = None
   834             quotalimit = None
   835         else:
   835         else:
   836             if not all(isinstance(i, (int, long)) for i in (bytes_, messages)):
   836             if not all(isinstance(i, int) for i in (bytes_, messages)):
   837                 raise TypeError("'bytes_' and 'messages' have to be "
   837                 raise TypeError("'bytes_' and 'messages' have to be "
   838                                 "integers or longs.")
   838                                 "integers or longs.")
   839             quotalimit = QuotaLimit(self._dbh, bytes=bytes_,
   839             quotalimit = QuotaLimit(self._dbh, bytes=bytes_,
   840                                     messages=messages)
   840                                     messages=messages)
   841         acc.update_quotalimit(quotalimit)
   841         acc.update_quotalimit(quotalimit)
   842 
   842 
   843     def user_transport(self, emailaddress, transport):
   843     def user_transport(self, emailaddress, transport):
   844         """Wrapper for Account.update_transport(Transport)."""
   844         """Wrapper for Account.update_transport(Transport)."""
   845         if not isinstance(transport, basestring) or not transport:
   845         if not isinstance(transport, str) or not transport:
   846             raise VMMError(_(u"Could not accept transport: '%s'") % transport,
   846             raise VMMError(_("Could not accept transport: '%s'") % transport,
   847                            INVALID_ARGUMENT)
   847                            INVALID_ARGUMENT)
   848         acc = self._get_account(emailaddress)
   848         acc = self._get_account(emailaddress)
   849         if not acc:
   849         if not acc:
   850             raise VMMError(_(u"The account '%s' does not exist.") %
   850             raise VMMError(_("The account '%s' does not exist.") %
   851                            acc.address, NO_SUCH_ACCOUNT)
   851                            acc.address, NO_SUCH_ACCOUNT)
   852         transport = None if transport == 'domain' \
   852         transport = None if transport == 'domain' \
   853                          else Transport(self._dbh, transport=transport)
   853                          else Transport(self._dbh, transport=transport)
   854         acc.update_transport(transport)
   854         acc.update_transport(transport)
   855 
   855 
   856     def user_services(self, emailaddress, *services):
   856     def user_services(self, emailaddress, *services):
   857         """Wrapper around Account.update_serviceset()."""
   857         """Wrapper around Account.update_serviceset()."""
   858         acc = self._get_account(emailaddress)
   858         acc = self._get_account(emailaddress)
   859         if not acc:
   859         if not acc:
   860             raise VMMError(_(u"The account '%s' does not exist.") %
   860             raise VMMError(_("The account '%s' does not exist.") %
   861                         acc.address, NO_SUCH_ACCOUNT)
   861                         acc.address, NO_SUCH_ACCOUNT)
   862         if len(services) == 1 and services[0] == 'domain':
   862         if len(services) == 1 and services[0] == 'domain':
   863             serviceset = None
   863             serviceset = None
   864         else:
   864         else:
   865             kwargs = dict.fromkeys(SERVICES, False)
   865             kwargs = dict.fromkeys(SERVICES, False)
   866             for service in set(services):
   866             for service in set(services):
   867                 if service not in SERVICES:
   867                 if service not in SERVICES:
   868                     raise VMMError(_(u"Unknown service: '%s'") % service,
   868                     raise VMMError(_("Unknown service: '%s'") % service,
   869                                 UNKNOWN_SERVICE)
   869                                 UNKNOWN_SERVICE)
   870                 kwargs[service] = True
   870                 kwargs[service] = True
   871             serviceset = ServiceSet(self._dbh, **kwargs)
   871             serviceset = ServiceSet(self._dbh, **kwargs)
   872         acc.update_serviceset(serviceset)
   872         acc.update_serviceset(serviceset)
   873 
   873 
   880             self._is_other_address(relocated.address, TYPE_RELOCATED)
   880             self._is_other_address(relocated.address, TYPE_RELOCATED)
   881         destination = DestinationEmailAddress(targetaddress, self._dbh)
   881         destination = DestinationEmailAddress(targetaddress, self._dbh)
   882         relocated.set_destination(destination)
   882         relocated.set_destination(destination)
   883         if destination.gid and \
   883         if destination.gid and \
   884            not self._chk_other_address_types(destination, TYPE_RELOCATED):
   884            not self._chk_other_address_types(destination, TYPE_RELOCATED):
   885             self._warnings.append(_(u"The destination account/alias '%s' "
   885             self._warnings.append(_("The destination account/alias '%s' "
   886                                     u"does not exist.") % destination)
   886                                     "does not exist.") % destination)
   887 
   887 
   888     def relocated_info(self, emailaddress):
   888     def relocated_info(self, emailaddress):
   889         """Returns the target address of the relocated user with the given
   889         """Returns the target address of the relocated user with the given
   890         *emailaddress*."""
   890         *emailaddress*."""
   891         relocated = self._get_relocated(emailaddress)
   891         relocated = self._get_relocated(emailaddress)
   892         if relocated:
   892         if relocated:
   893             return relocated.get_info()
   893             return relocated.get_info()
   894         if not self._is_other_address(relocated.address, TYPE_RELOCATED):
   894         if not self._is_other_address(relocated.address, TYPE_RELOCATED):
   895             raise VMMError(_(u"The relocated user '%s' does not exist.") %
   895             raise VMMError(_("The relocated user '%s' does not exist.") %
   896                            relocated.address, NO_SUCH_RELOCATED)
   896                            relocated.address, NO_SUCH_RELOCATED)
   897 
   897 
   898     def relocated_delete(self, emailaddress):
   898     def relocated_delete(self, emailaddress):
   899         """Deletes the relocated user with the given *emailaddress* from
   899         """Deletes the relocated user with the given *emailaddress* from
   900         the database."""
   900         the database."""