VirtualMailManager/cli/handler.py
branchv0.6.x
changeset 186 18757fd45e60
parent 185 6e1ef32fbd82
child 187 38b9a9859749
equal deleted inserted replaced
185:6e1ef32fbd82 186:18757fd45e60
     1 # -*- coding: UTF-8 -*-
       
     2 # Copyright (c) 2007 - 2010, Pascal Volk
       
     3 # See COPYING for distribution information.
       
     4 
       
     5 """
       
     6    VirtualMailManager.Handler
       
     7 
       
     8    A wrapper class. It wraps round all other classes and does some
       
     9    dependencies checks.
       
    10 
       
    11    Additionally it communicates with the PostgreSQL database, creates
       
    12    or deletes directories of domains or users.
       
    13 """
       
    14 
       
    15 import os
       
    16 import re
       
    17 
       
    18 from shutil import rmtree
       
    19 from subprocess import Popen, PIPE
       
    20 
       
    21 from pyPgSQL import PgSQL # python-pgsql - http://pypgsql.sourceforge.net
       
    22 
       
    23 import VirtualMailManager.constants.ERROR as ERR
       
    24 from VirtualMailManager import ENCODING, ace2idna, exec_ok, read_pass
       
    25 from VirtualMailManager.Account import Account
       
    26 from VirtualMailManager.Alias import Alias
       
    27 from VirtualMailManager.AliasDomain import AliasDomain
       
    28 from VirtualMailManager.Config import Config as Cfg
       
    29 from VirtualMailManager.Domain import Domain
       
    30 from VirtualMailManager.EmailAddress import EmailAddress
       
    31 from VirtualMailManager.Exceptions import *
       
    32 from VirtualMailManager.Relocated import Relocated
       
    33 from VirtualMailManager.ext.Postconf import Postconf
       
    34 
       
    35 SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
       
    36 RE_DOMAIN_SRCH = """^[a-z0-9-\.]+$"""
       
    37 RE_LOCALPART = """[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]"""
       
    38 RE_MBOX_NAMES = """^[\x20-\x25\x27-\x7E]*$"""
       
    39 
       
    40 class Handler(object):
       
    41     """Wrapper class to simplify the access on all the stuff from
       
    42     VirtualMailManager"""
       
    43     # TODO: accept a LazyConfig object as argument
       
    44     __slots__ = ('__Cfg', '__cfgFileName', '__dbh', '__scheme', '__warnings',
       
    45                  '_postconf')
       
    46     def __init__(self):
       
    47         """Creates a new Handler instance.
       
    48         Throws a VMMNotRootException if your uid is greater 0.
       
    49         """
       
    50         self.__cfgFileName = ''
       
    51         self.__warnings = []
       
    52         self.__Cfg = None
       
    53         self.__dbh = None
       
    54 
       
    55         if os.geteuid():
       
    56             raise VMMNotRootException(_(u"You are not root.\n\tGood bye!\n"),
       
    57                 ERR.CONF_NOPERM)
       
    58         if self.__chkCfgFile():
       
    59             self.__Cfg = Cfg(self.__cfgFileName)
       
    60             self.__Cfg.load()
       
    61         if not os.sys.argv[1] in ('cf','configure','h','help','v','version'):
       
    62             self.__Cfg.check()
       
    63             self.__chkenv()
       
    64             self.__scheme = self.__Cfg.dget('misc.password_scheme')
       
    65             self._postconf = Postconf(self.__Cfg.dget('bin.postconf'))
       
    66 
       
    67     def __findCfgFile(self):
       
    68         for path in ['/root', '/usr/local/etc', '/etc']:
       
    69             tmp = os.path.join(path, 'vmm.cfg')
       
    70             if os.path.isfile(tmp):
       
    71                 self.__cfgFileName = tmp
       
    72                 break
       
    73         if not len(self.__cfgFileName):
       
    74             raise VMMException(
       
    75                 _(u"No “vmm.cfg” found in: /root:/usr/local/etc:/etc"),
       
    76                 ERR.CONF_NOFILE)
       
    77 
       
    78     def __chkCfgFile(self):
       
    79         """Checks the configuration file, returns bool"""
       
    80         self.__findCfgFile()
       
    81         fstat = os.stat(self.__cfgFileName)
       
    82         fmode = int(oct(fstat.st_mode & 0777))
       
    83         if fmode % 100 and fstat.st_uid != fstat.st_gid \
       
    84         or fmode % 10 and fstat.st_uid == fstat.st_gid:
       
    85             raise VMMPermException(_(
       
    86                 u'fix permissions (%(perms)s) for “%(file)s”\n\
       
    87 `chmod 0600 %(file)s` would be great.') % {'file':
       
    88                 self.__cfgFileName, 'perms': fmode}, ERR.CONF_WRONGPERM)
       
    89         else:
       
    90             return True
       
    91 
       
    92     def __chkenv(self):
       
    93         """"""
       
    94         basedir = self.__Cfg.dget('misc.base_directory')
       
    95         if not os.path.exists(basedir):
       
    96             old_umask = os.umask(0006)
       
    97             os.makedirs(basedir, 0771)
       
    98             os.chown(basedir, 0, self.__Cfg.dget('misc.gid_mail'))
       
    99             os.umask(old_umask)
       
   100         elif not os.path.isdir(basedir):
       
   101             raise VMMException(_(u'“%s” is not a directory.\n\
       
   102 (vmm.cfg: section "misc", option "base_directory")') %
       
   103                                  basedir, ERR.NO_SUCH_DIRECTORY)
       
   104         for opt, val in self.__Cfg.items('bin'):
       
   105             try:
       
   106                 exec_ok(val)
       
   107             except VMMException, e:
       
   108                 code = e.code()
       
   109                 if code is ERR.NO_SUCH_BINARY:
       
   110                     raise VMMException(_(u'“%(binary)s” doesn\'t exist.\n\
       
   111 (vmm.cfg: section "bin", option "%(option)s")') %{'binary': val,'option': opt},
       
   112                                        ERR.NO_SUCH_BINARY)
       
   113                 elif code is ERR.NOT_EXECUTABLE:
       
   114                     raise VMMException(_(u'“%(binary)s” is not executable.\n\
       
   115 (vmm.cfg: section "bin", option "%(option)s")') %{'binary': val,'option': opt},
       
   116                                        ERR.NOT_EXECUTABLE)
       
   117                 else:
       
   118                     raise
       
   119 
       
   120     def __dbConnect(self):
       
   121         """Creates a pyPgSQL.PgSQL.connection instance."""
       
   122         if self.__dbh is None or (isinstance(self.__dbh, PgSQL.Connection) and
       
   123                                   not self.__dbh._isOpen):
       
   124             try:
       
   125                 self.__dbh = PgSQL.connect(
       
   126                         database=self.__Cfg.dget('database.name'),
       
   127                         user=self.__Cfg.pget('database.user'),
       
   128                         host=self.__Cfg.dget('database.host'),
       
   129                         password=self.__Cfg.pget('database.pass'),
       
   130                         client_encoding='utf8', unicode_results=True)
       
   131                 dbc = self.__dbh.cursor()
       
   132                 dbc.execute("SET NAMES 'UTF8'")
       
   133                 dbc.close()
       
   134             except PgSQL.libpq.DatabaseError, e:
       
   135                 raise VMMException(str(e), ERR.DATABASE_ERROR)
       
   136 
       
   137     def _exists(dbh, query):
       
   138         dbc = dbh.cursor()
       
   139         dbc.execute(query)
       
   140         gid = dbc.fetchone()
       
   141         dbc.close()
       
   142         if gid is None:
       
   143             return False
       
   144         else:
       
   145             return True
       
   146     _exists = staticmethod(_exists)
       
   147 
       
   148     def accountExists(dbh, address):
       
   149         sql = "SELECT gid FROM users WHERE gid = (SELECT gid FROM domain_name\
       
   150  WHERE domainname = '%s') AND local_part = '%s'" % (address._domainname,
       
   151             address._localpart)
       
   152         return Handler._exists(dbh, sql)
       
   153     accountExists = staticmethod(accountExists)
       
   154 
       
   155     def aliasExists(dbh, address):
       
   156         sql = "SELECT DISTINCT gid FROM alias WHERE gid = (SELECT gid FROM\
       
   157  domain_name WHERE domainname = '%s') AND address = '%s'" %\
       
   158             (address._domainname, address._localpart)
       
   159         return Handler._exists(dbh, sql)
       
   160     aliasExists = staticmethod(aliasExists)
       
   161 
       
   162     def relocatedExists(dbh, address):
       
   163         sql = "SELECT gid FROM relocated WHERE gid = (SELECT gid FROM\
       
   164  domain_name WHERE domainname = '%s') AND address = '%s'" %\
       
   165             (address._domainname, address._localpart)
       
   166         return Handler._exists(dbh, sql)
       
   167     relocatedExists = staticmethod(relocatedExists)
       
   168 
       
   169 
       
   170     def __getAccount(self, address, password=None):
       
   171         self.__dbConnect()
       
   172         address = EmailAddress(address)
       
   173         if not password is None:
       
   174             password = self.__pwhash(password)
       
   175         return Account(self.__dbh, address, password)
       
   176 
       
   177     def __getAlias(self, address, destination=None):
       
   178         self.__dbConnect()
       
   179         address = EmailAddress(address)
       
   180         if destination is not None:
       
   181             destination = EmailAddress(destination)
       
   182         return Alias(self.__dbh, address, destination)
       
   183 
       
   184     def __getRelocated(self,address, destination=None):
       
   185         self.__dbConnect()
       
   186         address = EmailAddress(address)
       
   187         if destination is not None:
       
   188             destination = EmailAddress(destination)
       
   189         return Relocated(self.__dbh, address, destination)
       
   190 
       
   191     def __getDomain(self, domainname, transport=None):
       
   192         if transport is None:
       
   193             transport = self.__Cfg.dget('misc.transport')
       
   194         self.__dbConnect()
       
   195         return Domain(self.__dbh, domainname,
       
   196                 self.__Cfg.dget('misc.base_directory'), transport)
       
   197 
       
   198     def __getDiskUsage(self, directory):
       
   199         """Estimate file space usage for the given directory.
       
   200 
       
   201         Keyword arguments:
       
   202         directory -- the directory to summarize recursively disk usage for
       
   203         """
       
   204         if self.__isdir(directory):
       
   205             return Popen([self.__Cfg.dget('bin.du'), "-hs", directory],
       
   206                 stdout=PIPE).communicate()[0].split('\t')[0]
       
   207         else:
       
   208             return 0
       
   209 
       
   210     def __isdir(self, directory):
       
   211         isdir = os.path.isdir(directory)
       
   212         if not isdir:
       
   213             self.__warnings.append(_('No such directory: %s') % directory)
       
   214         return isdir
       
   215 
       
   216     def __makedir(self, directory, mode=None, uid=None, gid=None):
       
   217         if mode is None:
       
   218             mode = self.__Cfg.dget('account.directory_mode')
       
   219         if uid is None:
       
   220             uid = 0
       
   221         if gid is None:
       
   222             gid = 0
       
   223         os.makedirs(directory, mode)
       
   224         os.chown(directory, uid, gid)
       
   225 
       
   226     def __domDirMake(self, domdir, gid):
       
   227         os.umask(0006)
       
   228         oldpwd = os.getcwd()
       
   229         basedir = self.__Cfg.dget('misc.base_directory')
       
   230         domdirdirs = domdir.replace(basedir+'/', '').split('/')
       
   231 
       
   232         os.chdir(basedir)
       
   233         if not os.path.isdir(domdirdirs[0]):
       
   234             self.__makedir(domdirdirs[0], 489, 0,
       
   235                            self.__Cfg.dget('misc.gid_mail'))
       
   236         os.chdir(domdirdirs[0])
       
   237         os.umask(0007)
       
   238         self.__makedir(domdirdirs[1], self.__Cfg.dget('domain.directory_mode'),
       
   239                        0, gid)
       
   240         os.chdir(oldpwd)
       
   241 
       
   242     def __subscribeFL(self, folderlist, uid, gid):
       
   243         fname = os.path.join(self.__Cfg.dget('maildir.name'), 'subscriptions')
       
   244         sf = file(fname, 'w')
       
   245         for f in folderlist:
       
   246             sf.write(f+'\n')
       
   247         sf.flush()
       
   248         sf.close()
       
   249         os.chown(fname, uid, gid)
       
   250         os.chmod(fname, 384)
       
   251 
       
   252     def __mailDirMake(self, domdir, uid, gid):
       
   253         """Creates maildirs and maildir subfolders.
       
   254 
       
   255         Keyword arguments:
       
   256         domdir -- the path to the domain directory
       
   257         uid -- user id from the account
       
   258         gid -- group id from the account
       
   259         """
       
   260         os.umask(0007)
       
   261         oldpwd = os.getcwd()
       
   262         os.chdir(domdir)
       
   263 
       
   264         maildir = self.__Cfg.dget('maildir.name')
       
   265         folders = [maildir]
       
   266         for folder in self.__Cfg.dget('maildir.folders').split(':'):
       
   267             folder = folder.strip()
       
   268             if len(folder) and not folder.count('..')\
       
   269             and re.match(RE_MBOX_NAMES, folder):
       
   270                 folders.append('%s/.%s' % (maildir, folder))
       
   271         subdirs = ['cur', 'new', 'tmp']
       
   272         mode = self.__Cfg.dget('account.directory_mode')
       
   273 
       
   274         self.__makedir('%s' % uid, mode, uid, gid)
       
   275         os.chdir('%s' % uid)
       
   276         for folder in folders:
       
   277             self.__makedir(folder, mode, uid, gid)
       
   278             for subdir in subdirs:
       
   279                 self.__makedir(os.path.join(folder, subdir), mode, uid, gid)
       
   280         self.__subscribeFL([f.replace(maildir+'/.', '') for f in folders[1:]],
       
   281                 uid, gid)
       
   282         os.chdir(oldpwd)
       
   283 
       
   284     def __userDirDelete(self, domdir, uid, gid):
       
   285         if uid > 0 and gid > 0:
       
   286             userdir = '%s' % uid
       
   287             if userdir.count('..') or domdir.count('..'):
       
   288                 raise VMMException(_(u'Found ".." in home directory path.'),
       
   289                     ERR.FOUND_DOTS_IN_PATH)
       
   290             if os.path.isdir(domdir):
       
   291                 os.chdir(domdir)
       
   292                 if os.path.isdir(userdir):
       
   293                     mdstat = os.stat(userdir)
       
   294                     if (mdstat.st_uid, mdstat.st_gid) != (uid, gid):
       
   295                         raise VMMException(
       
   296                          _(u'Detected owner/group mismatch in home directory.'),
       
   297                          ERR.MAILDIR_PERM_MISMATCH)
       
   298                     rmtree(userdir, ignore_errors=True)
       
   299                 else:
       
   300                     raise VMMException(_(u"No such directory: %s") %
       
   301                         os.path.join(domdir, userdir), ERR.NO_SUCH_DIRECTORY)
       
   302 
       
   303     def __domDirDelete(self, domdir, gid):
       
   304         if gid > 0:
       
   305             if not self.__isdir(domdir):
       
   306                 return
       
   307             basedir = self.__Cfg.dget('misc.base_directory')
       
   308             domdirdirs = domdir.replace(basedir+'/', '').split('/')
       
   309             domdirparent = os.path.join(basedir, domdirdirs[0])
       
   310             if basedir.count('..') or domdir.count('..'):
       
   311                 raise VMMException(_(u'Found ".." in domain directory path.'),
       
   312                         ERR.FOUND_DOTS_IN_PATH)
       
   313             if os.path.isdir(domdirparent):
       
   314                 os.chdir(domdirparent)
       
   315                 if os.lstat(domdirdirs[1]).st_gid != gid:
       
   316                     raise VMMException(_(
       
   317                         u'Detected group mismatch in domain directory.'),
       
   318                         ERR.DOMAINDIR_GROUP_MISMATCH)
       
   319                 rmtree(domdirdirs[1], ignore_errors=True)
       
   320 
       
   321     def __getSalt(self):
       
   322         from random import choice
       
   323         salt = None
       
   324         if self.__scheme == 'CRYPT':
       
   325             salt = '%s%s' % (choice(SALTCHARS), choice(SALTCHARS))
       
   326         elif self.__scheme in ['MD5', 'MD5-CRYPT']:
       
   327             salt = '$1$%s$' % ''.join([choice(SALTCHARS) for x in xrange(8)])
       
   328         return salt
       
   329 
       
   330     def __pwCrypt(self, password):
       
   331         # for: CRYPT, MD5 and MD5-CRYPT
       
   332         from crypt import crypt
       
   333         return crypt(password, self.__getSalt())
       
   334 
       
   335     def __pwSHA1(self, password):
       
   336         # for: SHA/SHA1
       
   337         import sha
       
   338         from base64 import standard_b64encode
       
   339         sha1 = sha.new(password)
       
   340         return standard_b64encode(sha1.digest())
       
   341 
       
   342     def __pwMD5(self, password, emailaddress=None):
       
   343         import md5
       
   344         _md5 = md5.new(password)
       
   345         if self.__scheme == 'LDAP-MD5':
       
   346             from base64 import standard_b64encode
       
   347             return standard_b64encode(_md5.digest())
       
   348         elif self.__scheme == 'PLAIN-MD5':
       
   349             return _md5.hexdigest()
       
   350         elif self.__scheme == 'DIGEST-MD5' and emailaddress is not None:
       
   351             # use an empty realm - works better with usenames like user@dom
       
   352             _md5 = md5.new('%s::%s' % (emailaddress, password))
       
   353             return _md5.hexdigest()
       
   354 
       
   355     def __pwMD4(self, password):
       
   356         # for: PLAIN-MD4
       
   357         from Crypto.Hash import MD4
       
   358         _md4 = MD4.new(password)
       
   359         return _md4.hexdigest()
       
   360 
       
   361     def __pwhash(self, password, scheme=None, user=None):
       
   362         if scheme is not None:
       
   363             self.__scheme = scheme
       
   364         if self.__scheme in ['CRYPT', 'MD5', 'MD5-CRYPT']:
       
   365             return '{%s}%s' % (self.__scheme, self.__pwCrypt(password))
       
   366         elif self.__scheme in ['SHA', 'SHA1']:
       
   367             return '{%s}%s' % (self.__scheme, self.__pwSHA1(password))
       
   368         elif self.__scheme in ['PLAIN-MD5', 'LDAP-MD5', 'DIGEST-MD5']:
       
   369             return '{%s}%s' % (self.__scheme, self.__pwMD5(password, user))
       
   370         elif self.__scheme == 'MD4':
       
   371             return '{%s}%s' % (self.__scheme, self.__pwMD4(password))
       
   372         elif self.__scheme in ['SMD5', 'SSHA', 'CRAM-MD5', 'HMAC-MD5',
       
   373                 'LANMAN', 'NTLM', 'RPA']:
       
   374             return Popen([self.__Cfg.dget('bin.dovecotpw'), '-s',
       
   375                 self.__scheme,'-p',password],stdout=PIPE).communicate()[0][:-1]
       
   376         else:
       
   377             return '{%s}%s' % (self.__scheme, password)
       
   378 
       
   379     def hasWarnings(self):
       
   380         """Checks if warnings are present, returns bool."""
       
   381         return bool(len(self.__warnings))
       
   382 
       
   383     def getWarnings(self):
       
   384         """Returns a list with all available warnings."""
       
   385         return self.__warnings
       
   386 
       
   387     def cfgDget(self, option):
       
   388         return self.__Cfg.dget(option)
       
   389 
       
   390     def cfgPget(self, option):
       
   391         return self.__Cfg.pget(option)
       
   392 
       
   393     def cfgSet(self, option, value):
       
   394         return self.__Cfg.set(option, value)
       
   395 
       
   396     def configure(self, section=None):
       
   397         """Starts interactive configuration.
       
   398 
       
   399         Configures in interactive mode options in the given section.
       
   400         If no section is given (default) all options from all sections
       
   401         will be prompted.
       
   402 
       
   403         Keyword arguments:
       
   404         section -- the section to configure (default None):
       
   405         """
       
   406         if section is None:
       
   407             self.__Cfg.configure(self.__Cfg.getsections())
       
   408         elif self.__Cfg.has_section(section):
       
   409             self.__Cfg.configure([section])
       
   410         else:
       
   411             raise VMMException(_(u"Invalid section: “%s”") % section,
       
   412                                ERR.INVALID_SECTION)
       
   413 
       
   414     def domainAdd(self, domainname, transport=None):
       
   415         dom = self.__getDomain(domainname, transport)
       
   416         dom.save()
       
   417         self.__domDirMake(dom.getDir(), dom.getID())
       
   418 
       
   419     def domainTransport(self, domainname, transport, force=None):
       
   420         if force is not None and force != 'force':
       
   421             raise VMMDomainException(_(u"Invalid argument: “%s”") % force,
       
   422                 ERR.INVALID_OPTION)
       
   423         dom = self.__getDomain(domainname, None)
       
   424         if force is None:
       
   425             dom.updateTransport(transport)
       
   426         else:
       
   427             dom.updateTransport(transport, force=True)
       
   428 
       
   429     def domainDelete(self, domainname, force=None):
       
   430         if not force is None and force not in ['deluser','delalias','delall']:
       
   431             raise VMMDomainException(_(u"Invalid argument: “%s”") % force,
       
   432                 ERR.INVALID_OPTION)
       
   433         dom = self.__getDomain(domainname)
       
   434         gid = dom.getID()
       
   435         domdir = dom.getDir()
       
   436         if self.__Cfg.dget('domain.force_deletion') or force == 'delall':
       
   437             dom.delete(True, True)
       
   438         elif force == 'deluser':
       
   439             dom.delete(delUser=True)
       
   440         elif force == 'delalias':
       
   441             dom.delete(delAlias=True)
       
   442         else:
       
   443             dom.delete()
       
   444         if self.__Cfg.dget('domain.delete_directory'):
       
   445             self.__domDirDelete(domdir, gid)
       
   446 
       
   447     def domainInfo(self, domainname, details=None):
       
   448         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
       
   449                 'relocated', 'detailed']:
       
   450             raise VMMException(_(u'Invalid argument: “%s”') % details,
       
   451                     ERR.INVALID_AGUMENT)
       
   452         if details == 'detailed':
       
   453             details = 'full'
       
   454             self.__warnings.append(_(u'\
       
   455 The keyword “detailed” is deprecated and will be removed in a future release.\n\
       
   456    Please use the keyword “full” to get full details.'))
       
   457         dom = self.__getDomain(domainname)
       
   458         dominfo = dom.getInfo()
       
   459         if dominfo['domainname'].startswith('xn--'):
       
   460             dominfo['domainname'] += ' (%s)' % ace2idna(dominfo['domainname'])
       
   461         if details is None:
       
   462             return dominfo
       
   463         elif details == 'accounts':
       
   464             return (dominfo, dom.getAccounts())
       
   465         elif details == 'aliasdomains':
       
   466             return (dominfo, dom.getAliaseNames())
       
   467         elif details == 'aliases':
       
   468             return (dominfo, dom.getAliases())
       
   469         elif details == 'relocated':
       
   470             return(dominfo, dom.getRelocated())
       
   471         else:
       
   472             return (dominfo, dom.getAliaseNames(), dom.getAccounts(),
       
   473                     dom.getAliases(), dom.getRelocated())
       
   474 
       
   475     def aliasDomainAdd(self, aliasname, domainname):
       
   476         """Adds an alias domain to the domain.
       
   477 
       
   478         Keyword arguments:
       
   479         aliasname -- the name of the alias domain (str)
       
   480         domainname -- name of the target domain (str)
       
   481         """
       
   482         dom = self.__getDomain(domainname)
       
   483         aliasDom = AliasDomain(self.__dbh, aliasname, dom)
       
   484         aliasDom.save()
       
   485 
       
   486     def aliasDomainInfo(self, aliasname):
       
   487         self.__dbConnect()
       
   488         aliasDom = AliasDomain(self.__dbh, aliasname, None)
       
   489         return aliasDom.info()
       
   490 
       
   491     def aliasDomainSwitch(self, aliasname, domainname):
       
   492         """Modifies the target domain of an existing alias domain.
       
   493 
       
   494         Keyword arguments:
       
   495         aliasname -- the name of the alias domain (str)
       
   496         domainname -- name of the new target domain (str)
       
   497         """
       
   498         dom = self.__getDomain(domainname)
       
   499         aliasDom = AliasDomain(self.__dbh, aliasname, dom)
       
   500         aliasDom.switch()
       
   501 
       
   502     def aliasDomainDelete(self, aliasname):
       
   503         """Deletes the specified alias domain.
       
   504 
       
   505         Keyword arguments:
       
   506         aliasname -- the name of the alias domain (str)
       
   507         """
       
   508         self.__dbConnect()
       
   509         aliasDom = AliasDomain(self.__dbh, aliasname, None)
       
   510         aliasDom.delete()
       
   511 
       
   512     def domainList(self, pattern=None):
       
   513         from Domain import search
       
   514         like = False
       
   515         if pattern is not None:
       
   516             if pattern.startswith('%') or pattern.endswith('%'):
       
   517                 like = True
       
   518                 if pattern.startswith('%') and pattern.endswith('%'):
       
   519                     domain = pattern[1:-1]
       
   520                 elif pattern.startswith('%'):
       
   521                     domain = pattern[1:]
       
   522                 elif pattern.endswith('%'):
       
   523                     domain = pattern[:-1]
       
   524                 if not re.match(RE_DOMAIN_SRCH, domain):
       
   525                     raise VMMException(
       
   526                     _(u"The pattern “%s” contains invalid characters.") %
       
   527                     pattern, ERR.DOMAIN_INVALID)
       
   528         self.__dbConnect()
       
   529         return search(self.__dbh, pattern=pattern, like=like)
       
   530 
       
   531     def userAdd(self, emailaddress, password):
       
   532         acc = self.__getAccount(emailaddress, password)
       
   533         if password is None:
       
   534             password = read_pass()
       
   535             acc.setPassword(self.__pwhash(password))
       
   536         acc.save(self.__Cfg.dget('maildir.name'),
       
   537                  self.__Cfg.dget('misc.dovecot_version'),
       
   538                  self.__Cfg.dget('account.smtp'),
       
   539                  self.__Cfg.dget('account.pop3'),
       
   540                  self.__Cfg.dget('account.imap'),
       
   541                  self.__Cfg.dget('account.sieve'))
       
   542         self.__mailDirMake(acc.getDir('domain'), acc.getUID(), acc.getGID())
       
   543 
       
   544     def aliasAdd(self, aliasaddress, targetaddress):
       
   545         alias = self.__getAlias(aliasaddress, targetaddress)
       
   546         alias.save(long(self._postconf.read('virtual_alias_expansion_limit')))
       
   547         gid = self.__getDomain(alias._dest._domainname).getID()
       
   548         if gid > 0 and not Handler.accountExists(self.__dbh,
       
   549         alias._dest) and not Handler.aliasExists(self.__dbh,
       
   550         alias._dest):
       
   551             self.__warnings.append(
       
   552                 _(u"The destination account/alias “%s” doesn't exist.")%\
       
   553                         alias._dest)
       
   554 
       
   555     def userDelete(self, emailaddress, force=None):
       
   556         if force not in [None, 'delalias']:
       
   557             raise VMMException(_(u"Invalid argument: “%s”") % force,
       
   558                     ERR.INVALID_AGUMENT)
       
   559         acc = self.__getAccount(emailaddress)
       
   560         uid = acc.getUID()
       
   561         gid = acc.getGID()
       
   562         acc.delete(force)
       
   563         if self.__Cfg.dget('account.delete_directory'):
       
   564             try:
       
   565                 self.__userDirDelete(acc.getDir('domain'), uid, gid)
       
   566             except VMMException, e:
       
   567                 if e.code() in [ERR.FOUND_DOTS_IN_PATH,
       
   568                         ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]:
       
   569                     warning = _(u"""\
       
   570 The account has been successfully deleted from the database.
       
   571     But an error occurred while deleting the following directory:
       
   572     “%(directory)s”
       
   573     Reason: %(reason)s""") % {'directory': acc.getDir('home'),'reason': e.msg()}
       
   574                     self.__warnings.append(warning)
       
   575                 else:
       
   576                     raise e
       
   577 
       
   578     def aliasInfo(self, aliasaddress):
       
   579         alias = self.__getAlias(aliasaddress)
       
   580         return alias.getInfo()
       
   581 
       
   582     def aliasDelete(self, aliasaddress, targetaddress=None):
       
   583         alias = self.__getAlias(aliasaddress, targetaddress)
       
   584         alias.delete()
       
   585 
       
   586     def userInfo(self, emailaddress, details=None):
       
   587         if details not in (None, 'du', 'aliases', 'full'):
       
   588             raise VMMException(_(u'Invalid argument: “%s”') % details,
       
   589                                ERR.INVALID_AGUMENT)
       
   590         acc = self.__getAccount(emailaddress)
       
   591         info = acc.getInfo(self.__Cfg.dget('misc.dovecot_version'))
       
   592         if self.__Cfg.dget('account.disk_usage') or details in ('du', 'full'):
       
   593             info['disk usage'] = self.__getDiskUsage('%(maildir)s' % info)
       
   594             if details in (None, 'du'):
       
   595                 return info
       
   596         if details in ('aliases', 'full'):
       
   597             return (info, acc.getAliases())
       
   598         return info
       
   599 
       
   600     def userByID(self, uid):
       
   601         from Handler.Account import getAccountByID
       
   602         self.__dbConnect()
       
   603         return getAccountByID(uid, self.__dbh)
       
   604 
       
   605     def userPassword(self, emailaddress, password):
       
   606         acc = self.__getAccount(emailaddress)
       
   607         if acc.getUID() == 0:
       
   608            raise VMMException(_(u"Account doesn't exist"), ERR.NO_SUCH_ACCOUNT)
       
   609         if password is None:
       
   610             password = read_pass()
       
   611         acc.modify('password', self.__pwhash(password, user=emailaddress))
       
   612 
       
   613     def userName(self, emailaddress, name):
       
   614         acc = self.__getAccount(emailaddress)
       
   615         acc.modify('name', name)
       
   616 
       
   617     def userTransport(self, emailaddress, transport):
       
   618         acc = self.__getAccount(emailaddress)
       
   619         acc.modify('transport', transport)
       
   620 
       
   621     def userDisable(self, emailaddress, service=None):
       
   622         if service == 'managesieve':
       
   623             service = 'sieve'
       
   624             self.__warnings.append(_(u'\
       
   625 The service name “managesieve” is deprecated and will be removed\n\
       
   626    in a future release.\n\
       
   627    Please use the service name “sieve” instead.'))
       
   628         acc = self.__getAccount(emailaddress)
       
   629         acc.disable(self.__Cfg.dget('misc.dovecot_version'), service)
       
   630 
       
   631     def userEnable(self, emailaddress, service=None):
       
   632         if service == 'managesieve':
       
   633             service = 'sieve'
       
   634             self.__warnings.append(_(u'\
       
   635 The service name “managesieve” is deprecated and will be removed\n\
       
   636    in a future release.\n\
       
   637    Please use the service name “sieve” instead.'))
       
   638         acc = self.__getAccount(emailaddress)
       
   639         acc.enable(self.__Cfg.dget('misc.dovecot_version'), service)
       
   640 
       
   641     def relocatedAdd(self, emailaddress, targetaddress):
       
   642         relocated = self.__getRelocated(emailaddress, targetaddress)
       
   643         relocated.save()
       
   644 
       
   645     def relocatedInfo(self, emailaddress):
       
   646         relocated = self.__getRelocated(emailaddress)
       
   647         return relocated.getInfo()
       
   648 
       
   649     def relocatedDelete(self, emailaddress):
       
   650         relocated = self.__getRelocated(emailaddress)
       
   651         relocated.delete()
       
   652 
       
   653     def __del__(self):
       
   654         if isinstance(self.__dbh, PgSQL.Connection) and self.__dbh._isOpen:
       
   655             self.__dbh.close()