VirtualMailManager/Handler.py
branchv0.6.x
changeset 190 1903d4ce97d7
parent 188 cf1b5f22dbd2
child 191 db77501aeaed
equal deleted inserted replaced
189:e63853509ad0 190:1903d4ce97d7
    23 import VirtualMailManager.constants.ERROR as ERR
    23 import VirtualMailManager.constants.ERROR as ERR
    24 from VirtualMailManager import ENCODING, ace2idna, exec_ok
    24 from VirtualMailManager import ENCODING, ace2idna, exec_ok
    25 from VirtualMailManager.Account import Account
    25 from VirtualMailManager.Account import Account
    26 from VirtualMailManager.Alias import Alias
    26 from VirtualMailManager.Alias import Alias
    27 from VirtualMailManager.AliasDomain import AliasDomain
    27 from VirtualMailManager.AliasDomain import AliasDomain
       
    28 from VirtualMailManager.Config import Config as Cfg
    28 from VirtualMailManager.Domain import Domain
    29 from VirtualMailManager.Domain import Domain
    29 from VirtualMailManager.EmailAddress import EmailAddress
    30 from VirtualMailManager.EmailAddress import EmailAddress
    30 from VirtualMailManager.Exceptions import *
    31 from VirtualMailManager.Exceptions import *
    31 from VirtualMailManager.Relocated import Relocated
    32 from VirtualMailManager.Relocated import Relocated
    32 from VirtualMailManager.ext.Postconf import Postconf
    33 from VirtualMailManager.ext.Postconf import Postconf
    33 
    34 
       
    35 
    34 SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    36 SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    35 RE_DOMAIN_SRCH = """^[a-z0-9-\.]+$"""
    37 RE_DOMAIN_SRCH = """^[a-z0-9-\.]+$"""
    36 RE_LOCALPART = """[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]"""
    38 RE_LOCALPART = """[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]"""
    37 RE_MBOX_NAMES = """^[\x20-\x25\x27-\x7E]*$"""
    39 RE_MBOX_NAMES = """^[\x20-\x25\x27-\x7E]*$"""
    38 
    40 
       
    41 
    39 class Handler(object):
    42 class Handler(object):
    40     """Wrapper class to simplify the access on all the stuff from
    43     """Wrapper class to simplify the access on all the stuff from
    41     VirtualMailManager"""
    44     VirtualMailManager"""
    42     __slots__ = ('__Cfg', '__cfgFileName', '__dbh', '__scheme', '__warnings',
    45     __slots__ = ('_Cfg', '_cfgFileName', '__dbh', '_scheme', '__warnings',
    43                  '_postconf')
    46                  '_postconf')
    44     def __init__(self, config_type='default'):
    47     def __init__(self, skip_some_checks=False):
    45         """Creates a new Handler instance.
    48         """Creates a new Handler instance.
    46 
    49 
    47         Accepted ``config_type``s are 'default' and 'cli'.
    50         ``skip_some_checks`` : bool
       
    51             When a derived class knows how to handle all checks this
       
    52             argument may be ``True``. By default it is ``False`` and
       
    53             all checks will be performed.
    48 
    54 
    49         Throws a VMMNotRootException if your uid is greater 0.
    55         Throws a VMMNotRootException if your uid is greater 0.
    50         """
    56         """
    51         self.__cfgFileName = ''
    57         self._cfgFileName = ''
    52         self.__warnings = []
    58         self.__warnings = []
    53         self.__Cfg = None
    59         self._Cfg = None
    54         self.__dbh = None
    60         self.__dbh = None
    55 
       
    56         if config_type == 'default':
       
    57             from VirtualMailManager.Config import Config as Cfg
       
    58         elif config_type == 'cli':
       
    59             from VirtualMailManager.cli.CliConfig import CliConfig as Cfg
       
    60             from VirtualMailManager.cli import read_pass
       
    61         else:
       
    62             raise ValueError('invalid config_type: %r' % config_type)
       
    63 
    61 
    64         if os.geteuid():
    62         if os.geteuid():
    65             raise VMMNotRootException(_(u"You are not root.\n\tGood bye!\n"),
    63             raise VMMNotRootException(_(u"You are not root.\n\tGood bye!\n"),
    66                 ERR.CONF_NOPERM)
    64                 ERR.CONF_NOPERM)
    67         if self.__chkCfgFile():
    65         if self.__chkCfgFile():
    68             self.__Cfg = Cfg(self.__cfgFileName)
    66             self._Cfg = Cfg(self._cfgFileName)
    69             self.__Cfg.load()
    67             self._Cfg.load()
    70         if not os.sys.argv[1] in ('cf','configure','h','help','v','version'):
    68         if not skip_some_checks:
    71             self.__Cfg.check()
    69             self._Cfg.check()
    72             self.__chkenv()
    70             self._chkenv()
    73             self.__scheme = self.__Cfg.dget('misc.password_scheme')
    71             self._scheme = self._Cfg.dget('misc.password_scheme')
    74             self._postconf = Postconf(self.__Cfg.dget('bin.postconf'))
    72             self._postconf = Postconf(self._Cfg.dget('bin.postconf'))
    75 
    73 
    76     def __findCfgFile(self):
    74     def __findCfgFile(self):
    77         for path in ['/root', '/usr/local/etc', '/etc']:
    75         for path in ['/root', '/usr/local/etc', '/etc']:
    78             tmp = os.path.join(path, 'vmm.cfg')
    76             tmp = os.path.join(path, 'vmm.cfg')
    79             if os.path.isfile(tmp):
    77             if os.path.isfile(tmp):
    80                 self.__cfgFileName = tmp
    78                 self._cfgFileName = tmp
    81                 break
    79                 break
    82         if not len(self.__cfgFileName):
    80         if not len(self._cfgFileName):
    83             raise VMMException(
    81             raise VMMException(
    84                 _(u"No “vmm.cfg” found in: /root:/usr/local/etc:/etc"),
    82                 _(u"No “vmm.cfg” found in: /root:/usr/local/etc:/etc"),
    85                 ERR.CONF_NOFILE)
    83                 ERR.CONF_NOFILE)
    86 
    84 
    87     def __chkCfgFile(self):
    85     def __chkCfgFile(self):
    88         """Checks the configuration file, returns bool"""
    86         """Checks the configuration file, returns bool"""
    89         self.__findCfgFile()
    87         self.__findCfgFile()
    90         fstat = os.stat(self.__cfgFileName)
    88         fstat = os.stat(self._cfgFileName)
    91         fmode = int(oct(fstat.st_mode & 0777))
    89         fmode = int(oct(fstat.st_mode & 0777))
    92         if fmode % 100 and fstat.st_uid != fstat.st_gid or \
    90         if fmode % 100 and fstat.st_uid != fstat.st_gid or \
    93             fmode % 10 and fstat.st_uid == fstat.st_gid:
    91             fmode % 10 and fstat.st_uid == fstat.st_gid:
    94               raise VMMPermException(_(
    92               raise VMMPermException(_(
    95                     u'fix permissions (%(perms)s) for “%(file)s”\n\
    93                     u'fix permissions (%(perms)s) for “%(file)s”\n\
    96 `chmod 0600 %(file)s` would be great.') % {'file':
    94 `chmod 0600 %(file)s` would be great.') % {'file':
    97                     self.__cfgFileName, 'perms': fmode}, ERR.CONF_WRONGPERM)
    95                     self._cfgFileName, 'perms': fmode}, ERR.CONF_WRONGPERM)
    98         else:
    96         else:
    99             return True
    97             return True
   100 
    98 
   101     def __chkenv(self):
    99     def _chkenv(self):
   102         """"""
   100         """"""
   103         basedir = self.__Cfg.dget('misc.base_directory')
   101         basedir = self._Cfg.dget('misc.base_directory')
   104         if not os.path.exists(basedir):
   102         if not os.path.exists(basedir):
   105             old_umask = os.umask(0006)
   103             old_umask = os.umask(0006)
   106             os.makedirs(basedir, 0771)
   104             os.makedirs(basedir, 0771)
   107             os.chown(basedir, 0, self.__Cfg.dget('misc.gid_mail'))
   105             os.chown(basedir, 0, self._Cfg.dget('misc.gid_mail'))
   108             os.umask(old_umask)
   106             os.umask(old_umask)
   109         elif not os.path.isdir(basedir):
   107         elif not os.path.isdir(basedir):
   110             raise VMMException(_(u'“%s” is not a directory.\n\
   108             raise VMMException(_(u'“%s” is not a directory.\n\
   111 (vmm.cfg: section "misc", option "base_directory")') %
   109 (vmm.cfg: section "misc", option "base_directory")') %
   112                                  basedir, ERR.NO_SUCH_DIRECTORY)
   110                                  basedir, ERR.NO_SUCH_DIRECTORY)
   113         for opt, val in self.__Cfg.items('bin'):
   111         for opt, val in self._Cfg.items('bin'):
   114             try:
   112             try:
   115                 exec_ok(val)
   113                 exec_ok(val)
   116             except VMMException, e:
   114             except VMMException, e:
   117                 code = e.code()
   115                 code = e.code()
   118                 if code is ERR.NO_SUCH_BINARY:
   116                 if code is ERR.NO_SUCH_BINARY:
   130         """Creates a pyPgSQL.PgSQL.connection instance."""
   128         """Creates a pyPgSQL.PgSQL.connection instance."""
   131         if self.__dbh is None or (isinstance(self.__dbh, PgSQL.Connection) and
   129         if self.__dbh is None or (isinstance(self.__dbh, PgSQL.Connection) and
   132                                   not self.__dbh._isOpen):
   130                                   not self.__dbh._isOpen):
   133             try:
   131             try:
   134                 self.__dbh = PgSQL.connect(
   132                 self.__dbh = PgSQL.connect(
   135                         database=self.__Cfg.dget('database.name'),
   133                         database=self._Cfg.dget('database.name'),
   136                         user=self.__Cfg.pget('database.user'),
   134                         user=self._Cfg.pget('database.user'),
   137                         host=self.__Cfg.dget('database.host'),
   135                         host=self._Cfg.dget('database.host'),
   138                         password=self.__Cfg.pget('database.pass'),
   136                         password=self._Cfg.pget('database.pass'),
   139                         client_encoding='utf8', unicode_results=True)
   137                         client_encoding='utf8', unicode_results=True)
   140                 dbc = self.__dbh.cursor()
   138                 dbc = self.__dbh.cursor()
   141                 dbc.execute("SET NAMES 'UTF8'")
   139                 dbc.execute("SET NAMES 'UTF8'")
   142                 dbc.close()
   140                 dbc.close()
   143             except PgSQL.libpq.DatabaseError, e:
   141             except PgSQL.libpq.DatabaseError, e:
   197             destination = EmailAddress(destination)
   195             destination = EmailAddress(destination)
   198         return Relocated(self.__dbh, address, destination)
   196         return Relocated(self.__dbh, address, destination)
   199 
   197 
   200     def __getDomain(self, domainname, transport=None):
   198     def __getDomain(self, domainname, transport=None):
   201         if transport is None:
   199         if transport is None:
   202             transport = self.__Cfg.dget('misc.transport')
   200             transport = self._Cfg.dget('misc.transport')
   203         self.__dbConnect()
   201         self.__dbConnect()
   204         return Domain(self.__dbh, domainname,
   202         return Domain(self.__dbh, domainname,
   205                 self.__Cfg.dget('misc.base_directory'), transport)
   203                 self._Cfg.dget('misc.base_directory'), transport)
   206 
   204 
   207     def __getDiskUsage(self, directory):
   205     def __getDiskUsage(self, directory):
   208         """Estimate file space usage for the given directory.
   206         """Estimate file space usage for the given directory.
   209 
   207 
   210         Keyword arguments:
   208         Keyword arguments:
   211         directory -- the directory to summarize recursively disk usage for
   209         directory -- the directory to summarize recursively disk usage for
   212         """
   210         """
   213         if self.__isdir(directory):
   211         if self.__isdir(directory):
   214             return Popen([self.__Cfg.dget('bin.du'), "-hs", directory],
   212             return Popen([self._Cfg.dget('bin.du'), "-hs", directory],
   215                 stdout=PIPE).communicate()[0].split('\t')[0]
   213                 stdout=PIPE).communicate()[0].split('\t')[0]
   216         else:
   214         else:
   217             return 0
   215             return 0
   218 
   216 
   219     def __isdir(self, directory):
   217     def __isdir(self, directory):
   222             self.__warnings.append(_('No such directory: %s') % directory)
   220             self.__warnings.append(_('No such directory: %s') % directory)
   223         return isdir
   221         return isdir
   224 
   222 
   225     def __makedir(self, directory, mode=None, uid=None, gid=None):
   223     def __makedir(self, directory, mode=None, uid=None, gid=None):
   226         if mode is None:
   224         if mode is None:
   227             mode = self.__Cfg.dget('account.directory_mode')
   225             mode = self._Cfg.dget('account.directory_mode')
   228         if uid is None:
   226         if uid is None:
   229             uid = 0
   227             uid = 0
   230         if gid is None:
   228         if gid is None:
   231             gid = 0
   229             gid = 0
   232         os.makedirs(directory, mode)
   230         os.makedirs(directory, mode)
   233         os.chown(directory, uid, gid)
   231         os.chown(directory, uid, gid)
   234 
   232 
   235     def __domDirMake(self, domdir, gid):
   233     def __domDirMake(self, domdir, gid):
   236         os.umask(0006)
   234         os.umask(0006)
   237         oldpwd = os.getcwd()
   235         oldpwd = os.getcwd()
   238         basedir = self.__Cfg.dget('misc.base_directory')
   236         basedir = self._Cfg.dget('misc.base_directory')
   239         domdirdirs = domdir.replace(basedir+'/', '').split('/')
   237         domdirdirs = domdir.replace(basedir+'/', '').split('/')
   240 
   238 
   241         os.chdir(basedir)
   239         os.chdir(basedir)
   242         if not os.path.isdir(domdirdirs[0]):
   240         if not os.path.isdir(domdirdirs[0]):
   243             self.__makedir(domdirdirs[0], 489, 0,
   241             self.__makedir(domdirdirs[0], 489, 0,
   244                            self.__Cfg.dget('misc.gid_mail'))
   242                            self._Cfg.dget('misc.gid_mail'))
   245         os.chdir(domdirdirs[0])
   243         os.chdir(domdirdirs[0])
   246         os.umask(0007)
   244         os.umask(0007)
   247         self.__makedir(domdirdirs[1], self.__Cfg.dget('domain.directory_mode'),
   245         self.__makedir(domdirdirs[1], self._Cfg.dget('domain.directory_mode'),
   248                        0, gid)
   246                        0, gid)
   249         os.chdir(oldpwd)
   247         os.chdir(oldpwd)
   250 
   248 
   251     def __subscribeFL(self, folderlist, uid, gid):
   249     def __subscribeFL(self, folderlist, uid, gid):
   252         fname = os.path.join(self.__Cfg.dget('maildir.name'), 'subscriptions')
   250         fname = os.path.join(self._Cfg.dget('maildir.name'), 'subscriptions')
   253         sf = file(fname, 'w')
   251         sf = file(fname, 'w')
   254         for f in folderlist:
   252         for f in folderlist:
   255             sf.write(f+'\n')
   253             sf.write(f+'\n')
   256         sf.flush()
   254         sf.flush()
   257         sf.close()
   255         sf.close()
   268         """
   266         """
   269         os.umask(0007)
   267         os.umask(0007)
   270         oldpwd = os.getcwd()
   268         oldpwd = os.getcwd()
   271         os.chdir(domdir)
   269         os.chdir(domdir)
   272 
   270 
   273         maildir = self.__Cfg.dget('maildir.name')
   271         maildir = self._Cfg.dget('maildir.name')
   274         folders = [maildir]
   272         folders = [maildir]
   275         for folder in self.__Cfg.dget('maildir.folders').split(':'):
   273         for folder in self._Cfg.dget('maildir.folders').split(':'):
   276             folder = folder.strip()
   274             folder = folder.strip()
   277             if len(folder) and not folder.count('..')\
   275             if len(folder) and not folder.count('..')\
   278             and re.match(RE_MBOX_NAMES, folder):
   276             and re.match(RE_MBOX_NAMES, folder):
   279                 folders.append('%s/.%s' % (maildir, folder))
   277                 folders.append('%s/.%s' % (maildir, folder))
   280         subdirs = ['cur', 'new', 'tmp']
   278         subdirs = ['cur', 'new', 'tmp']
   281         mode = self.__Cfg.dget('account.directory_mode')
   279         mode = self._Cfg.dget('account.directory_mode')
   282 
   280 
   283         self.__makedir('%s' % uid, mode, uid, gid)
   281         self.__makedir('%s' % uid, mode, uid, gid)
   284         os.chdir('%s' % uid)
   282         os.chdir('%s' % uid)
   285         for folder in folders:
   283         for folder in folders:
   286             self.__makedir(folder, mode, uid, gid)
   284             self.__makedir(folder, mode, uid, gid)
   311 
   309 
   312     def __domDirDelete(self, domdir, gid):
   310     def __domDirDelete(self, domdir, gid):
   313         if gid > 0:
   311         if gid > 0:
   314             if not self.__isdir(domdir):
   312             if not self.__isdir(domdir):
   315                 return
   313                 return
   316             basedir = self.__Cfg.dget('misc.base_directory')
   314             basedir = self._Cfg.dget('misc.base_directory')
   317             domdirdirs = domdir.replace(basedir+'/', '').split('/')
   315             domdirdirs = domdir.replace(basedir+'/', '').split('/')
   318             domdirparent = os.path.join(basedir, domdirdirs[0])
   316             domdirparent = os.path.join(basedir, domdirdirs[0])
   319             if basedir.count('..') or domdir.count('..'):
   317             if basedir.count('..') or domdir.count('..'):
   320                 raise VMMException(_(u'Found ".." in domain directory path.'),
   318                 raise VMMException(_(u'Found ".." in domain directory path.'),
   321                         ERR.FOUND_DOTS_IN_PATH)
   319                         ERR.FOUND_DOTS_IN_PATH)
   328                 rmtree(domdirdirs[1], ignore_errors=True)
   326                 rmtree(domdirdirs[1], ignore_errors=True)
   329 
   327 
   330     def __getSalt(self):
   328     def __getSalt(self):
   331         from random import choice
   329         from random import choice
   332         salt = None
   330         salt = None
   333         if self.__scheme == 'CRYPT':
   331         if self._scheme == 'CRYPT':
   334             salt = '%s%s' % (choice(SALTCHARS), choice(SALTCHARS))
   332             salt = '%s%s' % (choice(SALTCHARS), choice(SALTCHARS))
   335         elif self.__scheme in ['MD5', 'MD5-CRYPT']:
   333         elif self._scheme in ['MD5', 'MD5-CRYPT']:
   336             salt = '$1$%s$' % ''.join([choice(SALTCHARS) for x in xrange(8)])
   334             salt = '$1$%s$' % ''.join([choice(SALTCHARS) for x in xrange(8)])
   337         return salt
   335         return salt
   338 
   336 
   339     def __pwCrypt(self, password):
   337     def __pwCrypt(self, password):
   340         # for: CRYPT, MD5 and MD5-CRYPT
   338         # for: CRYPT, MD5 and MD5-CRYPT
   349         return standard_b64encode(sha1.digest())
   347         return standard_b64encode(sha1.digest())
   350 
   348 
   351     def __pwMD5(self, password, emailaddress=None):
   349     def __pwMD5(self, password, emailaddress=None):
   352         import md5
   350         import md5
   353         _md5 = md5.new(password)
   351         _md5 = md5.new(password)
   354         if self.__scheme == 'LDAP-MD5':
   352         if self._scheme == 'LDAP-MD5':
   355             from base64 import standard_b64encode
   353             from base64 import standard_b64encode
   356             return standard_b64encode(_md5.digest())
   354             return standard_b64encode(_md5.digest())
   357         elif self.__scheme == 'PLAIN-MD5':
   355         elif self._scheme == 'PLAIN-MD5':
   358             return _md5.hexdigest()
   356             return _md5.hexdigest()
   359         elif self.__scheme == 'DIGEST-MD5' and emailaddress is not None:
   357         elif self._scheme == 'DIGEST-MD5' and emailaddress is not None:
   360             # use an empty realm - works better with usenames like user@dom
   358             # use an empty realm - works better with usenames like user@dom
   361             _md5 = md5.new('%s::%s' % (emailaddress, password))
   359             _md5 = md5.new('%s::%s' % (emailaddress, password))
   362             return _md5.hexdigest()
   360             return _md5.hexdigest()
   363 
   361 
   364     def __pwMD4(self, password):
   362     def __pwMD4(self, password):
   367         _md4 = MD4.new(password)
   365         _md4 = MD4.new(password)
   368         return _md4.hexdigest()
   366         return _md4.hexdigest()
   369 
   367 
   370     def __pwhash(self, password, scheme=None, user=None):
   368     def __pwhash(self, password, scheme=None, user=None):
   371         if scheme is not None:
   369         if scheme is not None:
   372             self.__scheme = scheme
   370             self._scheme = scheme
   373         if self.__scheme in ['CRYPT', 'MD5', 'MD5-CRYPT']:
   371         if self._scheme in ['CRYPT', 'MD5', 'MD5-CRYPT']:
   374             return '{%s}%s' % (self.__scheme, self.__pwCrypt(password))
   372             return '{%s}%s' % (self._scheme, self.__pwCrypt(password))
   375         elif self.__scheme in ['SHA', 'SHA1']:
   373         elif self._scheme in ['SHA', 'SHA1']:
   376             return '{%s}%s' % (self.__scheme, self.__pwSHA1(password))
   374             return '{%s}%s' % (self._scheme, self.__pwSHA1(password))
   377         elif self.__scheme in ['PLAIN-MD5', 'LDAP-MD5', 'DIGEST-MD5']:
   375         elif self._scheme in ['PLAIN-MD5', 'LDAP-MD5', 'DIGEST-MD5']:
   378             return '{%s}%s' % (self.__scheme, self.__pwMD5(password, user))
   376             return '{%s}%s' % (self._scheme, self.__pwMD5(password, user))
   379         elif self.__scheme == 'MD4':
   377         elif self._scheme == 'MD4':
   380             return '{%s}%s' % (self.__scheme, self.__pwMD4(password))
   378             return '{%s}%s' % (self._scheme, self.__pwMD4(password))
   381         elif self.__scheme in ['SMD5', 'SSHA', 'CRAM-MD5', 'HMAC-MD5',
   379         elif self._scheme in ['SMD5', 'SSHA', 'CRAM-MD5', 'HMAC-MD5',
   382                 'LANMAN', 'NTLM', 'RPA']:
   380                 'LANMAN', 'NTLM', 'RPA']:
   383             return Popen([self.__Cfg.dget('bin.dovecotpw'), '-s',
   381             return Popen([self._Cfg.dget('bin.dovecotpw'), '-s',
   384                 self.__scheme,'-p',password],stdout=PIPE).communicate()[0][:-1]
   382                 self._scheme,'-p',password],stdout=PIPE).communicate()[0][:-1]
   385         else:
   383         else:
   386             return '{%s}%s' % (self.__scheme, password)
   384             return '{%s}%s' % (self._scheme, password)
   387 
   385 
   388     def hasWarnings(self):
   386     def hasWarnings(self):
   389         """Checks if warnings are present, returns bool."""
   387         """Checks if warnings are present, returns bool."""
   390         return bool(len(self.__warnings))
   388         return bool(len(self.__warnings))
   391 
   389 
   392     def getWarnings(self):
   390     def getWarnings(self):
   393         """Returns a list with all available warnings."""
   391         """Returns a list with all available warnings."""
   394         return self.__warnings
   392         return self.__warnings
   395 
   393 
   396     def cfgDget(self, option):
   394     def cfgDget(self, option):
   397         return self.__Cfg.dget(option)
   395         return self._Cfg.dget(option)
   398 
   396 
   399     def cfgPget(self, option):
   397     def cfgPget(self, option):
   400         return self.__Cfg.pget(option)
   398         return self._Cfg.pget(option)
   401 
       
   402     def cfgSet(self, option, value):
       
   403         return self.__Cfg.set(option, value)
       
   404 
       
   405     def configure(self, section=None):
       
   406         """Starts interactive configuration.
       
   407 
       
   408         Configures in interactive mode options in the given section.
       
   409         If no section is given (default) all options from all sections
       
   410         will be prompted.
       
   411 
       
   412         Keyword arguments:
       
   413         section -- the section to configure (default None):
       
   414         """
       
   415         if section is None:
       
   416             self.__Cfg.configure(self.__Cfg.sections())
       
   417         elif self.__Cfg.has_section(section):
       
   418             self.__Cfg.configure([section])
       
   419         else:
       
   420             raise VMMException(_(u"Invalid section: “%s”") % section,
       
   421                                ERR.INVALID_SECTION)
       
   422 
   399 
   423     def domainAdd(self, domainname, transport=None):
   400     def domainAdd(self, domainname, transport=None):
   424         dom = self.__getDomain(domainname, transport)
   401         dom = self.__getDomain(domainname, transport)
   425         dom.save()
   402         dom.save()
   426         self.__domDirMake(dom.getDir(), dom.getID())
   403         self.__domDirMake(dom.getDir(), dom.getID())
   440             raise VMMDomainException(_(u"Invalid argument: “%s”") % force,
   417             raise VMMDomainException(_(u"Invalid argument: “%s”") % force,
   441                 ERR.INVALID_OPTION)
   418                 ERR.INVALID_OPTION)
   442         dom = self.__getDomain(domainname)
   419         dom = self.__getDomain(domainname)
   443         gid = dom.getID()
   420         gid = dom.getID()
   444         domdir = dom.getDir()
   421         domdir = dom.getDir()
   445         if self.__Cfg.dget('domain.force_deletion') or force == 'delall':
   422         if self._Cfg.dget('domain.force_deletion') or force == 'delall':
   446             dom.delete(True, True)
   423             dom.delete(True, True)
   447         elif force == 'deluser':
   424         elif force == 'deluser':
   448             dom.delete(delUser=True)
   425             dom.delete(delUser=True)
   449         elif force == 'delalias':
   426         elif force == 'delalias':
   450             dom.delete(delAlias=True)
   427             dom.delete(delAlias=True)
   451         else:
   428         else:
   452             dom.delete()
   429             dom.delete()
   453         if self.__Cfg.dget('domain.delete_directory'):
   430         if self._Cfg.dget('domain.delete_directory'):
   454             self.__domDirDelete(domdir, gid)
   431             self.__domDirDelete(domdir, gid)
   455 
   432 
   456     def domainInfo(self, domainname, details=None):
   433     def domainInfo(self, domainname, details=None):
   457         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
   434         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
   458                 'relocated', 'detailed']:
   435                 'relocated', 'detailed']:
   536                     pattern, ERR.DOMAIN_INVALID)
   513                     pattern, ERR.DOMAIN_INVALID)
   537         self.__dbConnect()
   514         self.__dbConnect()
   538         return search(self.__dbh, pattern=pattern, like=like)
   515         return search(self.__dbh, pattern=pattern, like=like)
   539 
   516 
   540     def userAdd(self, emailaddress, password):
   517     def userAdd(self, emailaddress, password):
   541         acc = self.__getAccount(emailaddress, password)
   518         if password is None or (isinstance(password, basestring) and
   542         if password is None:
   519                                 not len(password)):
   543             password = read_pass()
   520             raise ValueError('could not accept password: %r' % password)
   544             acc.setPassword(self.__pwhash(password))
   521         acc = self.__getAccount(emailaddress, self.__pwhash(password))
   545         acc.save(self.__Cfg.dget('maildir.name'),
   522         acc.save(self._Cfg.dget('maildir.name'),
   546                  self.__Cfg.dget('misc.dovecot_version'),
   523                  self._Cfg.dget('misc.dovecot_version'),
   547                  self.__Cfg.dget('account.smtp'),
   524                  self._Cfg.dget('account.smtp'),
   548                  self.__Cfg.dget('account.pop3'),
   525                  self._Cfg.dget('account.pop3'),
   549                  self.__Cfg.dget('account.imap'),
   526                  self._Cfg.dget('account.imap'),
   550                  self.__Cfg.dget('account.sieve'))
   527                  self._Cfg.dget('account.sieve'))
   551         self.__mailDirMake(acc.getDir('domain'), acc.getUID(), acc.getGID())
   528         self.__mailDirMake(acc.getDir('domain'), acc.getUID(), acc.getGID())
   552 
   529 
   553     def aliasAdd(self, aliasaddress, targetaddress):
   530     def aliasAdd(self, aliasaddress, targetaddress):
   554         alias = self.__getAlias(aliasaddress, targetaddress)
   531         alias = self.__getAlias(aliasaddress, targetaddress)
   555         alias.save(long(self._postconf.read('virtual_alias_expansion_limit')))
   532         alias.save(long(self._postconf.read('virtual_alias_expansion_limit')))
   566                     ERR.INVALID_AGUMENT)
   543                     ERR.INVALID_AGUMENT)
   567         acc = self.__getAccount(emailaddress)
   544         acc = self.__getAccount(emailaddress)
   568         uid = acc.getUID()
   545         uid = acc.getUID()
   569         gid = acc.getGID()
   546         gid = acc.getGID()
   570         acc.delete(force)
   547         acc.delete(force)
   571         if self.__Cfg.dget('account.delete_directory'):
   548         if self._Cfg.dget('account.delete_directory'):
   572             try:
   549             try:
   573                 self.__userDirDelete(acc.getDir('domain'), uid, gid)
   550                 self.__userDirDelete(acc.getDir('domain'), uid, gid)
   574             except VMMException, e:
   551             except VMMException, e:
   575                 if e.code() in [ERR.FOUND_DOTS_IN_PATH,
   552                 if e.code() in [ERR.FOUND_DOTS_IN_PATH,
   576                         ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]:
   553                         ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]:
   594     def userInfo(self, emailaddress, details=None):
   571     def userInfo(self, emailaddress, details=None):
   595         if details not in (None, 'du', 'aliases', 'full'):
   572         if details not in (None, 'du', 'aliases', 'full'):
   596             raise VMMException(_(u'Invalid argument: “%s”') % details,
   573             raise VMMException(_(u'Invalid argument: “%s”') % details,
   597                                ERR.INVALID_AGUMENT)
   574                                ERR.INVALID_AGUMENT)
   598         acc = self.__getAccount(emailaddress)
   575         acc = self.__getAccount(emailaddress)
   599         info = acc.getInfo(self.__Cfg.dget('misc.dovecot_version'))
   576         info = acc.getInfo(self._Cfg.dget('misc.dovecot_version'))
   600         if self.__Cfg.dget('account.disk_usage') or details in ('du', 'full'):
   577         if self._Cfg.dget('account.disk_usage') or details in ('du', 'full'):
   601             info['disk usage'] = self.__getDiskUsage('%(maildir)s' % info)
   578             info['disk usage'] = self.__getDiskUsage('%(maildir)s' % info)
   602             if details in (None, 'du'):
   579             if details in (None, 'du'):
   603                 return info
   580                 return info
   604         if details in ('aliases', 'full'):
   581         if details in ('aliases', 'full'):
   605             return (info, acc.getAliases())
   582             return (info, acc.getAliases())
   609         from Handler.Account import getAccountByID
   586         from Handler.Account import getAccountByID
   610         self.__dbConnect()
   587         self.__dbConnect()
   611         return getAccountByID(uid, self.__dbh)
   588         return getAccountByID(uid, self.__dbh)
   612 
   589 
   613     def userPassword(self, emailaddress, password):
   590     def userPassword(self, emailaddress, password):
       
   591         if password is None or (isinstance(password, basestring) and
       
   592                                 not len(password)):
       
   593             raise ValueError('could not accept password: %r' % password)
   614         acc = self.__getAccount(emailaddress)
   594         acc = self.__getAccount(emailaddress)
   615         if acc.getUID() == 0:
   595         if acc.getUID() == 0:
   616            raise VMMException(_(u"Account doesn't exist"), ERR.NO_SUCH_ACCOUNT)
   596            raise VMMException(_(u"Account doesn't exist"), ERR.NO_SUCH_ACCOUNT)
   617         if password is None:
       
   618             password = read_pass()
       
   619         acc.modify('password', self.__pwhash(password, user=emailaddress))
   597         acc.modify('password', self.__pwhash(password, user=emailaddress))
   620 
   598 
   621     def userName(self, emailaddress, name):
   599     def userName(self, emailaddress, name):
   622         acc = self.__getAccount(emailaddress)
   600         acc = self.__getAccount(emailaddress)
   623         acc.modify('name', name)
   601         acc.modify('name', name)
   632             self.__warnings.append(_(u'\
   610             self.__warnings.append(_(u'\
   633 The service name “managesieve” is deprecated and will be removed\n\
   611 The service name “managesieve” is deprecated and will be removed\n\
   634    in a future release.\n\
   612    in a future release.\n\
   635    Please use the service name “sieve” instead.'))
   613    Please use the service name “sieve” instead.'))
   636         acc = self.__getAccount(emailaddress)
   614         acc = self.__getAccount(emailaddress)
   637         acc.disable(self.__Cfg.dget('misc.dovecot_version'), service)
   615         acc.disable(self._Cfg.dget('misc.dovecot_version'), service)
   638 
   616 
   639     def userEnable(self, emailaddress, service=None):
   617     def userEnable(self, emailaddress, service=None):
   640         if service == 'managesieve':
   618         if service == 'managesieve':
   641             service = 'sieve'
   619             service = 'sieve'
   642             self.__warnings.append(_(u'\
   620             self.__warnings.append(_(u'\
   643 The service name “managesieve” is deprecated and will be removed\n\
   621 The service name “managesieve” is deprecated and will be removed\n\
   644    in a future release.\n\
   622    in a future release.\n\
   645    Please use the service name “sieve” instead.'))
   623    Please use the service name “sieve” instead.'))
   646         acc = self.__getAccount(emailaddress)
   624         acc = self.__getAccount(emailaddress)
   647         acc.enable(self.__Cfg.dget('misc.dovecot_version'), service)
   625         acc.enable(self._Cfg.dget('misc.dovecot_version'), service)
   648 
   626 
   649     def relocatedAdd(self, emailaddress, targetaddress):
   627     def relocatedAdd(self, emailaddress, targetaddress):
   650         relocated = self.__getRelocated(emailaddress, targetaddress)
   628         relocated = self.__getRelocated(emailaddress, targetaddress)
   651         relocated.save()
   629         relocated.save()
   652 
   630