VirtualMailManager/Handler.py
branchv0.6.x
changeset 319 f4956b4ceba1
parent 318 4dc2edf02d11
equal deleted inserted replaced
318:4dc2edf02d11 319:f4956b4ceba1
    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
    61     VirtualMailManager"""
    61     VirtualMailManager"""
    62     __slots__ = ('_cfg', '_cfg_fname', '_dbh', '__warnings')
    62     __slots__ = ('_cfg', '_cfg_fname', '_dbh', '_warnings')
    63 
    63 
    64     def __init__(self, skip_some_checks=False):
    64     def __init__(self, skip_some_checks=False):
    65         """Creates a new Handler instance.
    65         """Creates a new Handler instance.
    66 
    66 
    67         ``skip_some_checks`` : bool
    67         ``skip_some_checks`` : bool
    70             all checks will be performed.
    70             all checks will be performed.
    71 
    71 
    72         Throws a NotRootError if your uid is greater 0.
    72         Throws a NotRootError if your uid is greater 0.
    73         """
    73         """
    74         self._cfg_fname = ''
    74         self._cfg_fname = ''
    75         self.__warnings = []
    75         self._warnings = []
    76         self._cfg = None
    76         self._cfg = None
    77         self._dbh = None
    77         self._dbh = None
    78 
    78 
    79         if os.geteuid():
    79         if os.geteuid():
    80             raise NotRootError(_(u"You are not root.\n\tGood bye!\n"),
    80             raise NotRootError(_(u"You are not root.\n\tGood bye!\n"),
    81                                CONF_NOPERM)
    81                                CONF_NOPERM)
    82         if self.__check_cfg_file():
    82         if self._check_cfg_file():
    83             self._cfg = Cfg(self._cfg_fname)
    83             self._cfg = Cfg(self._cfg_fname)
    84             self._cfg.load()
    84             self._cfg.load()
    85         if not skip_some_checks:
    85         if not skip_some_checks:
    86             self._cfg.check()
    86             self._cfg.check()
    87             self._chkenv()
    87             self._chkenv()
    88 
    88 
    89     def __find_cfg_file(self):
    89     def _find_cfg_file(self):
    90         """Search the CFG_FILE in CFG_PATH.
    90         """Search the CFG_FILE in CFG_PATH.
    91         Raise a VMMError when no vmm.cfg could be found.
    91         Raise a VMMError when no vmm.cfg could be found.
    92         """
    92         """
    93         for path in CFG_PATH.split(':'):
    93         for path in CFG_PATH.split(':'):
    94             tmp = os.path.join(path, CFG_FILE)
    94             tmp = os.path.join(path, CFG_FILE)
    96                 self._cfg_fname = tmp
    96                 self._cfg_fname = tmp
    97                 break
    97                 break
    98         if not self._cfg_fname:
    98         if not self._cfg_fname:
    99             raise VMMError(_(u"Could not find '%(cfg_file)s' in: "
    99             raise VMMError(_(u"Could not find '%(cfg_file)s' in: "
   100                              u"'%(cfg_path)s'") % {'cfg_file': CFG_FILE,
   100                              u"'%(cfg_path)s'") % {'cfg_file': CFG_FILE,
   101                            'cfg_path' : CFG_PATH}, CONF_NOFILE)
   101                            'cfg_path': CFG_PATH}, CONF_NOFILE)
   102 
   102 
   103     def __check_cfg_file(self):
   103     def _check_cfg_file(self):
   104         """Checks the configuration file, returns bool"""
   104         """Checks the configuration file, returns bool"""
   105         self.__find_cfg_file()
   105         self._find_cfg_file()
   106         fstat = os.stat(self._cfg_fname)
   106         fstat = os.stat(self._cfg_fname)
   107         fmode = int(oct(fstat.st_mode & 0777))
   107         fmode = int(oct(fstat.st_mode & 0777))
   108         if fmode % 100 and fstat.st_uid != fstat.st_gid or \
   108         if fmode % 100 and fstat.st_uid != fstat.st_gid or \
   109            fmode % 10 and fstat.st_uid == fstat.st_gid:
   109            fmode % 10 and fstat.st_uid == fstat.st_gid:
   110             raise PermissionError(_(u"wrong permissions for '%(file)s': "
   110             raise PermissionError(_(u"wrong permissions for '%(file)s': "
   146                                    'cfg_file': self._cfg_fname, 'option': opt},
   146                                    'cfg_file': self._cfg_fname, 'option': opt},
   147                                    err.code)
   147                                    err.code)
   148                 else:
   148                 else:
   149                     raise
   149                     raise
   150 
   150 
   151     def __dbConnect(self):
   151     def _db_connect(self):
   152         """Creates a pyPgSQL.PgSQL.connection instance."""
   152         """Creates a pyPgSQL.PgSQL.connection instance."""
   153         if self._dbh is None or (isinstance(self._dbh, PgSQL.Connection) and
   153         if self._dbh is None or (isinstance(self._dbh, PgSQL.Connection) and
   154                                   not self._dbh._isOpen):
   154                                   not self._dbh._isOpen):
   155             try:
   155             try:
   156                 self._dbh = PgSQL.connect(
   156                 self._dbh = PgSQL.connect(
   160                         password=self._cfg.pget('database.pass'),
   160                         password=self._cfg.pget('database.pass'),
   161                         client_encoding='utf8', unicode_results=True)
   161                         client_encoding='utf8', unicode_results=True)
   162                 dbc = self._dbh.cursor()
   162                 dbc = self._dbh.cursor()
   163                 dbc.execute("SET NAMES 'UTF8'")
   163                 dbc.execute("SET NAMES 'UTF8'")
   164                 dbc.close()
   164                 dbc.close()
   165             except PgSQL.libpq.DatabaseError, e:
   165             except PgSQL.libpq.DatabaseError, err:
   166                 raise VMMError(str(e), DATABASE_ERROR)
   166                 raise VMMError(str(err), DATABASE_ERROR)
   167 
   167 
   168     def _chk_other_address_types(self, address, exclude):
   168     def _chk_other_address_types(self, address, exclude):
   169         """Checks if the EmailAddress *address* is known as `TYPE_ACCOUNT`,
   169         """Checks if the EmailAddress *address* is known as `TYPE_ACCOUNT`,
   170         `TYPE_ALIAS` or `TYPE_RELOCATED`, but not as the `TYPE_*` specified
   170         `TYPE_ALIAS` or `TYPE_RELOCATED`, but not as the `TYPE_*` specified
   171         by *exclude*.  If the *address* is known as one of the `TYPE_*`s
   171         by *exclude*.  If the *address* is known as one of the `TYPE_*`s
   200             return False
   200             return False
   201         msg = _(u"There is already %(a_type)s with the address '%(address)s'.")
   201         msg = _(u"There is already %(a_type)s with the address '%(address)s'.")
   202         raise VMMError(msg % {'a_type': OTHER_TYPES[other][0],
   202         raise VMMError(msg % {'a_type': OTHER_TYPES[other][0],
   203                               'address': address}, OTHER_TYPES[other][1])
   203                               'address': address}, OTHER_TYPES[other][1])
   204 
   204 
   205     def __getAccount(self, address):
   205     def _get_account(self, address):
       
   206         """Return an Account instances for the given address (str)."""
   206         address = EmailAddress(address)
   207         address = EmailAddress(address)
   207         self.__dbConnect()
   208         self._db_connect()
   208         return Account(self._dbh, address)
   209         return Account(self._dbh, address)
   209 
   210 
   210     def __getAlias(self, address):
   211     def _get_alias(self, address):
       
   212         """Return an Alias instances for the given address (str)."""
   211         address = EmailAddress(address)
   213         address = EmailAddress(address)
   212         self.__dbConnect()
   214         self._db_connect()
   213         return Alias(self._dbh, address)
   215         return Alias(self._dbh, address)
   214 
   216 
   215     def __getRelocated(self, address):
   217     def _get_relocated(self, address):
       
   218         """Return a Relocated instances for the given address (str)."""
   216         address = EmailAddress(address)
   219         address = EmailAddress(address)
   217         self.__dbConnect()
   220         self._db_connect()
   218         return Relocated(self._dbh, address)
   221         return Relocated(self._dbh, address)
   219 
   222 
   220     def __getDomain(self, domainname):
   223     def _get_domain(self, domainname):
   221         self.__dbConnect()
   224         """Return a Domain instances for the given domain name (str)."""
       
   225         self._db_connect()
   222         return Domain(self._dbh, domainname)
   226         return Domain(self._dbh, domainname)
   223 
   227 
   224     def __getDiskUsage(self, directory):
   228     def _get_disk_usage(self, directory):
   225         """Estimate file space usage for the given directory.
   229         """Estimate file space usage for the given directory.
   226 
   230 
   227         Keyword arguments:
   231         Keyword arguments:
   228         directory -- the directory to summarize recursively disk usage for
   232         directory -- the directory to summarize recursively disk usage for
   229         """
   233         """
   230         if self.__isdir(directory):
   234         if self._isdir(directory):
   231             return Popen([self._cfg.dget('bin.du'), "-hs", directory],
   235             return Popen([self._cfg.dget('bin.du'), "-hs", directory],
   232                 stdout=PIPE).communicate()[0].split('\t')[0]
   236                 stdout=PIPE).communicate()[0].split('\t')[0]
   233         else:
   237         else:
   234             return 0
   238             return 0
   235 
   239 
   236     def __isdir(self, directory):
   240     def _isdir(self, directory):
       
   241         """Check if `directory` is a directory. Returns bool.
       
   242         When `directory` isn't a directory, a warning will be appended to
       
   243         _warnings."""
   237         isdir = os.path.isdir(directory)
   244         isdir = os.path.isdir(directory)
   238         if not isdir:
   245         if not isdir:
   239             self.__warnings.append(_('No such directory: %s') % directory)
   246             self._warnings.append(_('No such directory: %s') % directory)
   240         return isdir
   247         return isdir
   241 
   248 
   242     def __make_domain_dir(self, domain):
   249     def _make_domain_dir(self, domain):
       
   250         """Create a directory for the `domain` and its accounts."""
   243         cwd = os.getcwd()
   251         cwd = os.getcwd()
   244         hashdir, domdir = domain.directory.split(os.path.sep)[-2:]
   252         hashdir, domdir = domain.directory.split(os.path.sep)[-2:]
   245         os.chdir(self._cfg.dget('misc.base_directory'))
   253         os.chdir(self._cfg.dget('misc.base_directory'))
   246         if not os.path.isdir(hashdir):
   254         if not os.path.isdir(hashdir):
   247             os.mkdir(hashdir, 0711)
   255             os.mkdir(hashdir, 0711)
   249         os.mkdir(os.path.join(hashdir, domdir),
   257         os.mkdir(os.path.join(hashdir, domdir),
   250                  self._cfg.dget('domain.directory_mode'))
   258                  self._cfg.dget('domain.directory_mode'))
   251         os.chown(domain.directory, 0, domain.gid)
   259         os.chown(domain.directory, 0, domain.gid)
   252         os.chdir(cwd)
   260         os.chdir(cwd)
   253 
   261 
   254     def __make_home(self, account):
   262     def _make_home(self, account):
   255         """Create a home directory for the new Account *account*."""
   263         """Create a home directory for the new Account *account*."""
   256         os.umask(0007)
   264         os.umask(0007)
   257         os.chdir(account.domain_directory)
   265         os.chdir(account.domain_directory)
   258         os.mkdir('%s' % account.uid, self._cfg.dget('account.directory_mode'))
   266         os.mkdir('%s' % account.uid, self._cfg.dget('account.directory_mode'))
   259         os.chown('%s' % account.uid, account.uid, account.gid)
   267         os.chown('%s' % account.uid, account.uid, account.gid)
   260 
   268 
   261     def __userDirDelete(self, domdir, uid, gid):
   269     def _delete_home(self, domdir, uid, gid):
       
   270         """Delete a user's home directory."""
   262         if uid > 0 and gid > 0:
   271         if uid > 0 and gid > 0:
   263             userdir = '%s' % uid
   272             userdir = '%s' % uid
   264             if userdir.count('..') or domdir.count('..'):
   273             if userdir.count('..') or domdir.count('..'):
   265                 raise VMMError(_(u'Found ".." in home directory path.'),
   274                 raise VMMError(_(u'Found ".." in home directory path.'),
   266                                FOUND_DOTS_IN_PATH)
   275                                FOUND_DOTS_IN_PATH)
   276                 else:
   285                 else:
   277                     raise VMMError(_(u"No such directory: %s") %
   286                     raise VMMError(_(u"No such directory: %s") %
   278                                    os.path.join(domdir, userdir),
   287                                    os.path.join(domdir, userdir),
   279                                    NO_SUCH_DIRECTORY)
   288                                    NO_SUCH_DIRECTORY)
   280 
   289 
   281     def __domDirDelete(self, domdir, gid):
   290     def _delete_domain_dir(self, domdir, gid):
       
   291         """Delete a domain's directory."""
   282         if gid > 0:
   292         if gid > 0:
   283             if not self.__isdir(domdir):
   293             if not self._isdir(domdir):
   284                 return
   294                 return
   285             basedir = self._cfg.dget('misc.base_directory')
   295             basedir = self._cfg.dget('misc.base_directory')
   286             domdirdirs = domdir.replace(basedir + '/', '').split('/')
   296             domdirdirs = domdir.replace(basedir + '/', '').split('/')
   287             domdirparent = os.path.join(basedir, domdirdirs[0])
   297             domdirparent = os.path.join(basedir, domdirdirs[0])
   288             if basedir.count('..') or domdir.count('..'):
   298             if basedir.count('..') or domdir.count('..'):
   293                 if os.lstat(domdirdirs[1]).st_gid != gid:
   303                 if os.lstat(domdirdirs[1]).st_gid != gid:
   294                     raise VMMError(_(u'Detected group mismatch in domain '
   304                     raise VMMError(_(u'Detected group mismatch in domain '
   295                                      u'directory.'), DOMAINDIR_GROUP_MISMATCH)
   305                                      u'directory.'), DOMAINDIR_GROUP_MISMATCH)
   296                 rmtree(domdirdirs[1], ignore_errors=True)
   306                 rmtree(domdirdirs[1], ignore_errors=True)
   297 
   307 
   298     def hasWarnings(self):
   308     def has_warnings(self):
   299         """Checks if warnings are present, returns bool."""
   309         """Checks if warnings are present, returns bool."""
   300         return bool(len(self.__warnings))
   310         return bool(len(self._warnings))
   301 
   311 
   302     def getWarnings(self):
   312     def get_warnings(self):
   303         """Returns a list with all available warnings and resets all
   313         """Returns a list with all available warnings and resets all
   304         warnings.
   314         warnings.
   305 
   315         """
   306         """
   316         ret_val = self._warnings[:]
   307         ret_val = self.__warnings[:]
   317         del self._warnings[:]
   308         del self.__warnings[:]
       
   309         return ret_val
   318         return ret_val
   310 
   319 
   311     def cfg_dget(self, option):
   320     def cfg_dget(self, option):
   312         """Get the configured value of the *option* (section.option).
   321         """Get the configured value of the *option* (section.option).
   313         When the option was not configured its default value will be
   322         When the option was not configured its default value will be
   323         namespace."""
   332         namespace."""
   324         import __builtin__
   333         import __builtin__
   325         assert 'cfg_dget' not in __builtin__.__dict__
   334         assert 'cfg_dget' not in __builtin__.__dict__
   326         __builtin__.__dict__['cfg_dget'] = self._cfg.dget
   335         __builtin__.__dict__['cfg_dget'] = self._cfg.dget
   327 
   336 
   328     def domainAdd(self, domainname, transport=None):
   337     def domain_add(self, domainname, transport=None):
   329         dom = self.__getDomain(domainname)
   338         """Wrapper around Domain.set_transport() and Domain.save()"""
       
   339         dom = self._get_domain(domainname)
   330         if transport is None:
   340         if transport is None:
   331             dom.set_transport(Transport(self._dbh,
   341             dom.set_transport(Transport(self._dbh,
   332                               transport=self._cfg.dget('misc.transport')))
   342                               transport=self._cfg.dget('misc.transport')))
   333         else:
   343         else:
   334             dom.set_transport(Transport(self._dbh, transport=transport))
   344             dom.set_transport(Transport(self._dbh, transport=transport))
   335         dom.set_directory(self._cfg.dget('misc.base_directory'))
   345         dom.set_directory(self._cfg.dget('misc.base_directory'))
   336         dom.save()
   346         dom.save()
   337         self.__make_domain_dir(dom)
   347         self._make_domain_dir(dom)
   338 
   348 
   339     def domainTransport(self, domainname, transport, force=None):
   349     def domain_transport(self, domainname, transport, force=None):
       
   350         """Wrapper around Domain.update_transport()"""
   340         if force is not None and force != 'force':
   351         if force is not None and force != 'force':
   341             raise DomainError(_(u"Invalid argument: '%s'") % force,
   352             raise DomainError(_(u"Invalid argument: '%s'") % force,
   342                               INVALID_ARGUMENT)
   353                               INVALID_ARGUMENT)
   343         dom = self.__getDomain(domainname)
   354         dom = self._get_domain(domainname)
   344         trsp = Transport(self._dbh, transport=transport)
   355         trsp = Transport(self._dbh, transport=transport)
   345         if force is None:
   356         if force is None:
   346             dom.update_transport(trsp)
   357             dom.update_transport(trsp)
   347         else:
   358         else:
   348             dom.update_transport(trsp, force=True)
   359             dom.update_transport(trsp, force=True)
   349 
   360 
   350     def domainDelete(self, domainname, force=None):
   361     def domain_delete(self, domainname, force=None):
       
   362         """Wrapper around Domain.delete()"""
   351         if force and force not in ('deluser', 'delalias', 'delall'):
   363         if force and force not in ('deluser', 'delalias', 'delall'):
   352             raise DomainError(_(u"Invalid argument: '%s'") % force,
   364             raise DomainError(_(u"Invalid argument: '%s'") % force,
   353                               INVALID_ARGUMENT)
   365                               INVALID_ARGUMENT)
   354         dom = self.__getDomain(domainname)
   366         dom = self._get_domain(domainname)
   355         gid = dom.gid
   367         gid = dom.gid
   356         domdir = dom.directory
   368         domdir = dom.directory
   357         if self._cfg.dget('domain.force_deletion') or force == 'delall':
   369         if self._cfg.dget('domain.force_deletion') or force == 'delall':
   358             dom.delete(True, True)
   370             dom.delete(True, True)
   359         elif force == 'deluser':
   371         elif force == 'deluser':
   361         elif force == 'delalias':
   373         elif force == 'delalias':
   362             dom.delete(delalias=True)
   374             dom.delete(delalias=True)
   363         else:
   375         else:
   364             dom.delete()
   376             dom.delete()
   365         if self._cfg.dget('domain.delete_directory'):
   377         if self._cfg.dget('domain.delete_directory'):
   366             self.__domDirDelete(domdir, gid)
   378             self._delete_domain_dir(domdir, gid)
   367 
   379 
   368     def domainInfo(self, domainname, details=None):
   380     def domain_info(self, domainname, details=None):
       
   381         """Wrapper around Domain.get_info(), Domain.get_accounts(),
       
   382         Domain.get_aliase_names(), Domain.get_aliases() and
       
   383         Domain.get_relocated."""
   369         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
   384         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
   370                            'relocated']:
   385                            'relocated']:
   371             raise VMMError(_(u'Invalid argument: ā€œ%sā€') % details,
   386             raise VMMError(_(u'Invalid argument: ā€œ%sā€') % details,
   372                            INVALID_ARGUMENT)
   387                            INVALID_ARGUMENT)
   373         dom = self.__getDomain(domainname)
   388         dom = self._get_domain(domainname)
   374         dominfo = dom.get_info()
   389         dominfo = dom.get_info()
   375         if dominfo['domainname'].startswith('xn--'):
   390         if dominfo['domainname'].startswith('xn--'):
   376             dominfo['domainname'] += ' (%s)' % \
   391             dominfo['domainname'] += ' (%s)' % \
   377                                      dominfo['domainname'].decode('idna')
   392                                      dominfo['domainname'].decode('idna')
   378         if details is None:
   393         if details is None:
   387             return(dominfo, dom.get_relocated())
   402             return(dominfo, dom.get_relocated())
   388         else:
   403         else:
   389             return (dominfo, dom.get_aliase_names(), dom.get_accounts(),
   404             return (dominfo, dom.get_aliase_names(), dom.get_accounts(),
   390                     dom.get_aliases(), dom.get_relocated())
   405                     dom.get_aliases(), dom.get_relocated())
   391 
   406 
   392     def aliasDomainAdd(self, aliasname, domainname):
   407     def aliasdomain_add(self, aliasname, domainname):
   393         """Adds an alias domain to the domain.
   408         """Adds an alias domain to the domain.
   394 
   409 
   395         Arguments:
   410         Arguments:
   396 
   411 
   397         `aliasname` : basestring
   412         `aliasname` : basestring
   398           The name of the alias domain
   413           The name of the alias domain
   399         `domainname` : basestring
   414         `domainname` : basestring
   400           The name of the target domain
   415           The name of the target domain
   401         """
   416         """
   402         dom = self.__getDomain(domainname)
   417         dom = self._get_domain(domainname)
   403         aliasDom = AliasDomain(self._dbh, aliasname)
   418         alias_dom = AliasDomain(self._dbh, aliasname)
   404         aliasDom.set_destination(dom)
   419         alias_dom.set_destination(dom)
   405         aliasDom.save()
   420         alias_dom.save()
   406 
   421 
   407     def aliasDomainInfo(self, aliasname):
   422     def aliasdomain_info(self, aliasname):
   408         """Returns a dict (keys: "alias" and "domain") with the names of
   423         """Returns a dict (keys: "alias" and "domain") with the names of
   409         the alias domain and its primary domain."""
   424         the alias domain and its primary domain."""
   410         self.__dbConnect()
   425         self._db_connect()
   411         aliasDom = AliasDomain(self._dbh, aliasname)
   426         alias_dom = AliasDomain(self._dbh, aliasname)
   412         return aliasDom.info()
   427         return alias_dom.info()
   413 
   428 
   414     def aliasDomainSwitch(self, aliasname, domainname):
   429     def aliasdomain_switch(self, aliasname, domainname):
   415         """Modifies the target domain of an existing alias domain.
   430         """Modifies the target domain of an existing alias domain.
   416 
   431 
   417         Arguments:
   432         Arguments:
   418 
   433 
   419         `aliasname` : basestring
   434         `aliasname` : basestring
   420           The name of the alias domain
   435           The name of the alias domain
   421         `domainname` : basestring
   436         `domainname` : basestring
   422           The name of the new target domain
   437           The name of the new target domain
   423         """
   438         """
   424         dom = self.__getDomain(domainname)
   439         dom = self._get_domain(domainname)
   425         aliasDom = AliasDomain(self._dbh, aliasname)
   440         alias_dom = AliasDomain(self._dbh, aliasname)
   426         aliasDom.set_destination(dom)
   441         alias_dom.set_destination(dom)
   427         aliasDom.switch()
   442         alias_dom.switch()
   428 
   443 
   429     def aliasDomainDelete(self, aliasname):
   444     def aliasdomain_delete(self, aliasname):
   430         """Deletes the given alias domain.
   445         """Deletes the given alias domain.
   431 
   446 
   432         Argument:
   447         Argument:
   433 
   448 
   434         `aliasname` : basestring
   449         `aliasname` : basestring
   435           The name of the alias domain
   450           The name of the alias domain
   436         """
   451         """
   437         self.__dbConnect()
   452         self._db_connect()
   438         aliasDom = AliasDomain(self._dbh, aliasname)
   453         alias_dom = AliasDomain(self._dbh, aliasname)
   439         aliasDom.delete()
   454         alias_dom.delete()
   440 
   455 
   441     def domainList(self, pattern=None):
   456     def domain_list(self, pattern=None):
       
   457         """Wrapper around function search() from module Domain."""
   442         from VirtualMailManager.Domain import search
   458         from VirtualMailManager.Domain import search
   443         like = False
   459         like = False
   444         if pattern and (pattern.startswith('%') or pattern.endswith('%')):
   460         if pattern and (pattern.startswith('%') or pattern.endswith('%')):
   445             like = True
   461             like = True
   446             if not re.match(RE_DOMAIN_SEARCH, pattern.strip('%')):
   462             if not re.match(RE_DOMAIN_SEARCH, pattern.strip('%')):
   447                 raise VMMError(_(u"The pattern '%s' contains invalid "
   463                 raise VMMError(_(u"The pattern '%s' contains invalid "
   448                                  u"characters.") % pattern, DOMAIN_INVALID)
   464                                  u"characters.") % pattern, DOMAIN_INVALID)
   449         self.__dbConnect()
   465         self._db_connect()
   450         return search(self._dbh, pattern=pattern, like=like)
   466         return search(self._dbh, pattern=pattern, like=like)
   451 
   467 
   452     def user_add(self, emailaddress, password):
   468     def user_add(self, emailaddress, password):
   453         """Wrapper around Account.set_password() and Account.save()."""
   469         """Wrapper around Account.set_password() and Account.save()."""
   454         acc = self.__getAccount(emailaddress)
   470         acc = self._get_account(emailaddress)
   455         acc.set_password(password)
   471         acc.set_password(password)
   456         acc.save()
   472         acc.save()
   457         oldpwd = os.getcwd()
   473         oldpwd = os.getcwd()
   458         self.__make_home(acc)
   474         self._make_home(acc)
   459         mailbox = new_mailbox(acc)
   475         mailbox = new_mailbox(acc)
   460         mailbox.create()
   476         mailbox.create()
   461         folders = self._cfg.dget('mailbox.folders').split(':')
   477         folders = self._cfg.dget('mailbox.folders').split(':')
   462         if any(folders):
   478         if any(folders):
   463             bad = mailbox.add_boxes(folders,
   479             bad = mailbox.add_boxes(folders,
   464                                     self._cfg.dget('mailbox.subscribe'))
   480                                     self._cfg.dget('mailbox.subscribe'))
   465             if bad:
   481             if bad:
   466                 self.__warnings.append(_(u"Skipped mailbox folders:") +
   482                 self._warnings.append(_(u"Skipped mailbox folders:") +
   467                                        '\n\t- ' + '\n\t- '.join(bad))
   483                                       '\n\t- ' + '\n\t- '.join(bad))
   468         os.chdir(oldpwd)
   484         os.chdir(oldpwd)
   469 
   485 
   470     def aliasAdd(self, aliasaddress, *targetaddresses):
   486     def alias_add(self, aliasaddress, *targetaddresses):
   471         """Creates a new `Alias` entry for the given *aliasaddress* with
   487         """Creates a new `Alias` entry for the given *aliasaddress* with
   472         the given *targetaddresses*."""
   488         the given *targetaddresses*."""
   473         alias = self.__getAlias(aliasaddress)
   489         alias = self._get_alias(aliasaddress)
   474         destinations = [EmailAddress(address) for address in targetaddresses]
   490         destinations = [EmailAddress(address) for address in targetaddresses]
   475         warnings = []
   491         warnings = []
   476         destinations = alias.add_destinations(destinations, warnings)
   492         destinations = alias.add_destinations(destinations, warnings)
   477         if warnings:
   493         if warnings:
   478             self.__warnings.append(_('Ignored destination addresses:'))
   494             self._warnings.append(_('Ignored destination addresses:'))
   479             self.__warnings.extend(('  * %s' % w for w in warnings))
   495             self._warnings.extend(('  * %s' % w for w in warnings))
   480         for destination in destinations:
   496         for destination in destinations:
   481             if get_gid(self._dbh, destination.domainname) and \
   497             if get_gid(self._dbh, destination.domainname) and \
   482                not self._chk_other_address_types(destination, TYPE_RELOCATED):
   498                not self._chk_other_address_types(destination, TYPE_RELOCATED):
   483                 self.__warnings.append(_(u"The destination account/alias '%s' "
   499                 self._warnings.append(_(u"The destination account/alias '%s' "
   484                                          u"doesn't exist.") % destination)
   500                                         u"doesn't exist.") % destination)
   485 
   501 
   486     def user_delete(self, emailaddress, force=None):
   502     def user_delete(self, emailaddress, force=None):
   487         """Wrapper around Account.delete(...)"""
   503         """Wrapper around Account.delete(...)"""
   488         if force not in (None, 'delalias'):
   504         if force not in (None, 'delalias'):
   489             raise VMMError(_(u"Invalid argument: '%s'") % force,
   505             raise VMMError(_(u"Invalid argument: '%s'") % force,
   490                            INVALID_ARGUMENT)
   506                            INVALID_ARGUMENT)
   491         acc = self.__getAccount(emailaddress)
   507         acc = self._get_account(emailaddress)
   492         if not acc:
   508         if not acc:
   493             raise VMMError(_(u"The account '%s' doesn't exist.") %
   509             raise VMMError(_(u"The account '%s' doesn't exist.") %
   494                            acc.address, NO_SUCH_ACCOUNT)
   510                            acc.address, NO_SUCH_ACCOUNT)
   495         uid = acc.uid
   511         uid = acc.uid
   496         gid = acc.gid
   512         gid = acc.gid
   497         dom_dir = acc.domain_directory
   513         dom_dir = acc.domain_directory
   498         acc_dir = acc.home
   514         acc_dir = acc.home
   499         acc.delete(bool(force))
   515         acc.delete(bool(force))
   500         if self._cfg.dget('account.delete_directory'):
   516         if self._cfg.dget('account.delete_directory'):
   501             try:
   517             try:
   502                 self.__userDirDelete(dom_dir, uid, gid)
   518                 self._delete_home(dom_dir, uid, gid)
   503             except VMMError, err:
   519             except VMMError, err:
   504                 if err.code in (FOUND_DOTS_IN_PATH, MAILDIR_PERM_MISMATCH,
   520                 if err.code in (FOUND_DOTS_IN_PATH, MAILDIR_PERM_MISMATCH,
   505                                 NO_SUCH_DIRECTORY):
   521                                 NO_SUCH_DIRECTORY):
   506                     warning = _(u"""\
   522                     warning = _(u"""\
   507 The account has been successfully deleted from the database.
   523 The account has been successfully deleted from the database.
   508     But an error occurred while deleting the following directory:
   524     But an error occurred while deleting the following directory:
   509     ā€œ%(directory)sā€
   525     ā€œ%(directory)sā€
   510     Reason: %(reason)s""") % \
   526     Reason: %(reason)s""") % \
   511                                 {'directory': acc_dir, 'reason': err.msg}
   527                                 {'directory': acc_dir, 'reason': err.msg}
   512                     self.__warnings.append(warning)
   528                     self._warnings.append(warning)
   513                 else:
   529                 else:
   514                     raise
   530                     raise
   515 
   531 
   516     def aliasInfo(self, aliasaddress):
   532     def alias_info(self, aliasaddress):
   517         """Returns an iterator object for all destinations (`EmailAddress`
   533         """Returns an iterator object for all destinations (`EmailAddress`
   518         instances) for the `Alias` with the given *aliasaddress*."""
   534         instances) for the `Alias` with the given *aliasaddress*."""
   519         alias = self.__getAlias(aliasaddress)
   535         alias = self._get_alias(aliasaddress)
   520         if alias:
   536         if alias:
   521             return alias.get_destinations()
   537             return alias.get_destinations()
   522         if not self._is_other_address(alias.address, TYPE_ALIAS):
   538         if not self._is_other_address(alias.address, TYPE_ALIAS):
   523             raise VMMError(_(u"The alias '%s' doesn't exist.") %
   539             raise VMMError(_(u"The alias '%s' doesn't exist.") %
   524                            alias.address, NO_SUCH_ALIAS)
   540                            alias.address, NO_SUCH_ALIAS)
   525 
   541 
   526     def aliasDelete(self, aliasaddress, targetaddress=None):
   542     def alias_delete(self, aliasaddress, targetaddress=None):
   527         """Deletes the `Alias` *aliasaddress* with all its destinations from
   543         """Deletes the `Alias` *aliasaddress* with all its destinations from
   528         the database. If *targetaddress* is not ``None``, only this
   544         the database. If *targetaddress* is not ``None``, only this
   529         destination will be removed from the alias."""
   545         destination will be removed from the alias."""
   530         alias = self.__getAlias(aliasaddress)
   546         alias = self._get_alias(aliasaddress)
   531         if targetaddress is None:
   547         if targetaddress is None:
   532             alias.delete()
   548             alias.delete()
   533         else:
   549         else:
   534             alias.del_destination(EmailAddress(targetaddress))
   550             alias.del_destination(EmailAddress(targetaddress))
   535 
   551 
   536     def user_info(self, emailaddress, details=None):
   552     def user_info(self, emailaddress, details=None):
   537         """Wrapper around Account.get_info(...)"""
   553         """Wrapper around Account.get_info(...)"""
   538         if details not in (None, 'du', 'aliases', 'full'):
   554         if details not in (None, 'du', 'aliases', 'full'):
   539             raise VMMError(_(u"Invalid argument: '%s'") % details,
   555             raise VMMError(_(u"Invalid argument: '%s'") % details,
   540                            INVALID_ARGUMENT)
   556                            INVALID_ARGUMENT)
   541         acc = self.__getAccount(emailaddress)
   557         acc = self._get_account(emailaddress)
   542         if not acc:
   558         if not acc:
   543             if not self._is_other_address(acc.address, TYPE_ACCOUNT):
   559             if not self._is_other_address(acc.address, TYPE_ACCOUNT):
   544                 raise VMMError(_(u"The account '%s' doesn't exist.") %
   560                 raise VMMError(_(u"The account '%s' doesn't exist.") %
   545                                acc.address, NO_SUCH_ACCOUNT)
   561                                acc.address, NO_SUCH_ACCOUNT)
   546         info = acc.get_info()
   562         info = acc.get_info()
   547         if self._cfg.dget('account.disk_usage') or details in ('du', 'full'):
   563         if self._cfg.dget('account.disk_usage') or details in ('du', 'full'):
   548             path = os.path.join(acc.home, acc.mail_location.directory)
   564             path = os.path.join(acc.home, acc.mail_location.directory)
   549             info['disk usage'] = self.__getDiskUsage(path)
   565             info['disk usage'] = self._get_disk_usage(path)
   550             if details in (None, 'du'):
   566             if details in (None, 'du'):
   551                 return info
   567                 return info
   552         if details in ('aliases', 'full'):
   568         if details in ('aliases', 'full'):
   553             return (info, acc.get_aliases())
   569             return (info, acc.get_aliases())
   554         return info
   570         return info
   555 
   571 
   556     def user_by_uid(self, uid):
   572     def user_by_uid(self, uid):
   557         """Search for an Account by its *uid*.
   573         """Search for an Account by its *uid*.
   558         Returns a dict (address, uid and gid) if a user could be found."""
   574         Returns a dict (address, uid and gid) if a user could be found."""
   559         from VirtualMailManager.Account import get_account_by_uid
   575         from VirtualMailManager.Account import get_account_by_uid
   560         self.__dbConnect()
   576         self._db_connect()
   561         return get_account_by_uid(uid, self._dbh)
   577         return get_account_by_uid(uid, self._dbh)
   562 
   578 
   563     def user_password(self, emailaddress, password):
   579     def user_password(self, emailaddress, password):
   564         """Wrapper for Account.modify('password' ...)."""
   580         """Wrapper for Account.modify('password' ...)."""
   565         if not isinstance(password, basestring) or not password:
   581         if not isinstance(password, basestring) or not password:
   566             raise VMMError(_(u"Could not accept password: '%s'") % password,
   582             raise VMMError(_(u"Could not accept password: '%s'") % password,
   567                            INVALID_ARGUMENT)
   583                            INVALID_ARGUMENT)
   568         acc = self.__getAccount(emailaddress)
   584         acc = self._get_account(emailaddress)
   569         if not acc:
   585         if not acc:
   570             raise VMMError(_(u"The account '%s' doesn't exist.") %
   586             raise VMMError(_(u"The account '%s' doesn't exist.") %
   571                            acc.address, NO_SUCH_ACCOUNT)
   587                            acc.address, NO_SUCH_ACCOUNT)
   572         acc.modify('password', password)
   588         acc.modify('password', password)
   573 
   589 
   574     def user_name(self, emailaddress, name):
   590     def user_name(self, emailaddress, name):
   575         """Wrapper for Account.modify('name', ...)."""
   591         """Wrapper for Account.modify('name', ...)."""
   576         if not isinstance(name, basestring) or not name:
   592         if not isinstance(name, basestring) or not name:
   577             raise VMMError(_(u"Could not accept name: '%s'") % name,
   593             raise VMMError(_(u"Could not accept name: '%s'") % name,
   578                            INVALID_ARGUMENT)
   594                            INVALID_ARGUMENT)
   579         acc = self.__getAccount(emailaddress)
   595         acc = self._get_account(emailaddress)
   580         if not acc:
   596         if not acc:
   581             raise VMMError(_(u"The account '%s' doesn't exist.") %
   597             raise VMMError(_(u"The account '%s' doesn't exist.") %
   582                            acc.address, NO_SUCH_ACCOUNT)
   598                            acc.address, NO_SUCH_ACCOUNT)
   583         acc.modify('name', name)
   599         acc.modify('name', name)
   584 
   600 
   585     def user_transport(self, emailaddress, transport):
   601     def user_transport(self, emailaddress, transport):
   586         """Wrapper for Account.modify('transport', ...)."""
   602         """Wrapper for Account.modify('transport', ...)."""
   587         if not isinstance(transport, basestring) or not transport:
   603         if not isinstance(transport, basestring) or not transport:
   588             raise VMMError(_(u"Could not accept transport: '%s'") % transport,
   604             raise VMMError(_(u"Could not accept transport: '%s'") % transport,
   589                            INVALID_ARGUMENT)
   605                            INVALID_ARGUMENT)
   590         acc = self.__getAccount(emailaddress)
   606         acc = self._get_account(emailaddress)
   591         if not acc:
   607         if not acc:
   592             raise VMMError(_(u"The account '%s' doesn't exist.") %
   608             raise VMMError(_(u"The account '%s' doesn't exist.") %
   593                            acc.address, NO_SUCH_ACCOUNT)
   609                            acc.address, NO_SUCH_ACCOUNT)
   594         acc.modify('transport', transport)
   610         acc.modify('transport', transport)
   595 
   611 
   596     def user_disable(self, emailaddress, service=None):
   612     def user_disable(self, emailaddress, service=None):
   597         """Wrapper for Account.disable(service)"""
   613         """Wrapper for Account.disable(service)"""
   598         if service not in (None, 'all', 'imap', 'pop3', 'smtp', 'sieve'):
   614         if service not in (None, 'all', 'imap', 'pop3', 'smtp', 'sieve'):
   599             raise VMMError(_(u"Could not accept service: '%s'") % service,
   615             raise VMMError(_(u"Could not accept service: '%s'") % service,
   600                            INVALID_ARGUMENT)
   616                            INVALID_ARGUMENT)
   601         acc = self.__getAccount(emailaddress)
   617         acc = self._get_account(emailaddress)
   602         if not acc:
   618         if not acc:
   603             raise VMMError(_(u"The account '%s' doesn't exist.") %
   619             raise VMMError(_(u"The account '%s' doesn't exist.") %
   604                            acc.address, NO_SUCH_ACCOUNT)
   620                            acc.address, NO_SUCH_ACCOUNT)
   605         acc.disable(service)
   621         acc.disable(service)
   606 
   622 
   607     def user_enable(self, emailaddress, service=None):
   623     def user_enable(self, emailaddress, service=None):
   608         """Wrapper for Account.enable(service)"""
   624         """Wrapper for Account.enable(service)"""
   609         if service not in (None, 'all', 'imap', 'pop3', 'smtp', 'sieve'):
   625         if service not in (None, 'all', 'imap', 'pop3', 'smtp', 'sieve'):
   610             raise VMMError(_(u"Could not accept service: '%s'") % service,
   626             raise VMMError(_(u"Could not accept service: '%s'") % service,
   611                            INVALID_ARGUMENT)
   627                            INVALID_ARGUMENT)
   612         acc = self.__getAccount(emailaddress)
   628         acc = self._get_account(emailaddress)
   613         if not acc:
   629         if not acc:
   614             raise VMMError(_(u"The account '%s' doesn't exist.") %
   630             raise VMMError(_(u"The account '%s' doesn't exist.") %
   615                            acc.address, NO_SUCH_ACCOUNT)
   631                            acc.address, NO_SUCH_ACCOUNT)
   616         acc.enable(service)
   632         acc.enable(service)
   617 
   633 
   618     def relocatedAdd(self, emailaddress, targetaddress):
   634     def relocated_add(self, emailaddress, targetaddress):
   619         """Creates a new `Relocated` entry in the database. If there is
   635         """Creates a new `Relocated` entry in the database. If there is
   620         already a relocated user with the given *emailaddress*, only the
   636         already a relocated user with the given *emailaddress*, only the
   621         *targetaddress* for the relocated user will be updated."""
   637         *targetaddress* for the relocated user will be updated."""
   622         relocated = self.__getRelocated(emailaddress)
   638         relocated = self._get_relocated(emailaddress)
   623         relocated.set_destination(EmailAddress(targetaddress))
   639         relocated.set_destination(EmailAddress(targetaddress))
   624 
   640 
   625     def relocatedInfo(self, emailaddress):
   641     def relocated_info(self, emailaddress):
   626         """Returns the target address of the relocated user with the given
   642         """Returns the target address of the relocated user with the given
   627         *emailaddress*."""
   643         *emailaddress*."""
   628         relocated = self.__getRelocated(emailaddress)
   644         relocated = self._get_relocated(emailaddress)
   629         if relocated:
   645         if relocated:
   630             return relocated.get_info()
   646             return relocated.get_info()
   631         if not self._is_other_address(relocated.address, TYPE_RELOCATED):
   647         if not self._is_other_address(relocated.address, TYPE_RELOCATED):
   632             raise VMMError(_(u"The relocated user '%s' doesn't exist.") %
   648             raise VMMError(_(u"The relocated user '%s' doesn't exist.") %
   633                            relocated.address, NO_SUCH_RELOCATED)
   649                            relocated.address, NO_SUCH_RELOCATED)
   634 
   650 
   635     def relocatedDelete(self, emailaddress):
   651     def relocated_delete(self, emailaddress):
   636         """Deletes the relocated user with the given *emailaddress* from
   652         """Deletes the relocated user with the given *emailaddress* from
   637         the database."""
   653         the database."""
   638         relocated = self.__getRelocated(emailaddress)
   654         relocated = self._get_relocated(emailaddress)
   639         relocated.delete()
   655         relocated.delete()
   640 
   656 
   641     def __del__(self):
       
   642         if isinstance(self._dbh, PgSQL.Connection) and self._dbh._isOpen:
       
   643             self._dbh.close()
       
   644 
       
   645 del _
   657 del _