VirtualMailManager/Handler.py
branchv0.6.x
changeset 191 db77501aeaed
parent 190 1903d4ce97d7
child 192 0854fb9f3bc5
equal deleted inserted replaced
190:1903d4ce97d7 191:db77501aeaed
    42 class Handler(object):
    42 class Handler(object):
    43     """Wrapper class to simplify the access on all the stuff from
    43     """Wrapper class to simplify the access on all the stuff from
    44     VirtualMailManager"""
    44     VirtualMailManager"""
    45     __slots__ = ('_Cfg', '_cfgFileName', '__dbh', '_scheme', '__warnings',
    45     __slots__ = ('_Cfg', '_cfgFileName', '__dbh', '_scheme', '__warnings',
    46                  '_postconf')
    46                  '_postconf')
       
    47 
    47     def __init__(self, skip_some_checks=False):
    48     def __init__(self, skip_some_checks=False):
    48         """Creates a new Handler instance.
    49         """Creates a new Handler instance.
    49 
    50 
    50         ``skip_some_checks`` : bool
    51         ``skip_some_checks`` : bool
    51             When a derived class knows how to handle all checks this
    52             When a derived class knows how to handle all checks this
    87         self.__findCfgFile()
    88         self.__findCfgFile()
    88         fstat = os.stat(self._cfgFileName)
    89         fstat = os.stat(self._cfgFileName)
    89         fmode = int(oct(fstat.st_mode & 0777))
    90         fmode = int(oct(fstat.st_mode & 0777))
    90         if fmode % 100 and fstat.st_uid != fstat.st_gid or \
    91         if fmode % 100 and fstat.st_uid != fstat.st_gid or \
    91             fmode % 10 and fstat.st_uid == fstat.st_gid:
    92             fmode % 10 and fstat.st_uid == fstat.st_gid:
    92               raise VMMPermException(_(
    93                 raise VMMPermException(_(
    93                     u'fix permissions (%(perms)s) for “%(file)s”\n\
    94                     u'fix permissions (%(perms)s) for “%(file)s”\n\
    94 `chmod 0600 %(file)s` would be great.') % {'file':
    95 `chmod 0600 %(file)s` would be great.') % {'file':
    95                     self._cfgFileName, 'perms': fmode}, ERR.CONF_WRONGPERM)
    96                     self._cfgFileName, 'perms': fmode}, ERR.CONF_WRONGPERM)
    96         else:
    97         else:
    97             return True
    98             return True
   113                 exec_ok(val)
   114                 exec_ok(val)
   114             except VMMException, e:
   115             except VMMException, e:
   115                 code = e.code()
   116                 code = e.code()
   116                 if code is ERR.NO_SUCH_BINARY:
   117                 if code is ERR.NO_SUCH_BINARY:
   117                     raise VMMException(_(u'“%(binary)s” doesn\'t exist.\n\
   118                     raise VMMException(_(u'“%(binary)s” doesn\'t exist.\n\
   118 (vmm.cfg: section "bin", option "%(option)s")') %{'binary': val,'option': opt},
   119 (vmm.cfg: section "bin", option "%(option)s")') %
       
   120                                        {'binary': val, 'option': opt},
   119                                        ERR.NO_SUCH_BINARY)
   121                                        ERR.NO_SUCH_BINARY)
   120                 elif code is ERR.NOT_EXECUTABLE:
   122                 elif code is ERR.NOT_EXECUTABLE:
   121                     raise VMMException(_(u'“%(binary)s” is not executable.\n\
   123                     raise VMMException(_(u'“%(binary)s” is not executable.\
   122 (vmm.cfg: section "bin", option "%(option)s")') %{'binary': val,'option': opt},
   124 \n(vmm.cfg: section "bin", option "%(option)s")') %
       
   125                                        {'binary': val, 'option': opt},
   123                                        ERR.NOT_EXECUTABLE)
   126                                        ERR.NOT_EXECUTABLE)
   124                 else:
   127                 else:
   125                     raise
   128                     raise
   126 
   129 
   127     def __dbConnect(self):
   130     def __dbConnect(self):
   160     accountExists = staticmethod(accountExists)
   163     accountExists = staticmethod(accountExists)
   161 
   164 
   162     def aliasExists(dbh, address):
   165     def aliasExists(dbh, address):
   163         sql = "SELECT DISTINCT gid FROM alias WHERE gid = (SELECT gid FROM\
   166         sql = "SELECT DISTINCT gid FROM alias WHERE gid = (SELECT gid FROM\
   164  domain_name WHERE domainname = '%s') AND address = '%s'" % (
   167  domain_name WHERE domainname = '%s') AND address = '%s'" % (
   165                                         address._domainname, address._localpart)
   168                 address._domainname, address._localpart)
   166         return Handler._exists(dbh, sql)
   169         return Handler._exists(dbh, sql)
   167     aliasExists = staticmethod(aliasExists)
   170     aliasExists = staticmethod(aliasExists)
   168 
   171 
   169     def relocatedExists(dbh, address):
   172     def relocatedExists(dbh, address):
   170         sql = "SELECT gid FROM relocated WHERE gid = (SELECT gid FROM\
   173         sql = "SELECT gid FROM relocated WHERE gid = (SELECT gid FROM\
   171  domain_name WHERE domainname = '%s') AND address = '%s'" % (
   174  domain_name WHERE domainname = '%s') AND address = '%s'" % (
   172                                         address._domainname, address._localpart)
   175                 address._domainname, address._localpart)
   173         return Handler._exists(dbh, sql)
   176         return Handler._exists(dbh, sql)
   174     relocatedExists = staticmethod(relocatedExists)
   177     relocatedExists = staticmethod(relocatedExists)
   175 
       
   176 
   178 
   177     def __getAccount(self, address, password=None):
   179     def __getAccount(self, address, password=None):
   178         self.__dbConnect()
   180         self.__dbConnect()
   179         address = EmailAddress(address)
   181         address = EmailAddress(address)
   180         if not password is None:
   182         if not password is None:
   186         address = EmailAddress(address)
   188         address = EmailAddress(address)
   187         if destination is not None:
   189         if destination is not None:
   188             destination = EmailAddress(destination)
   190             destination = EmailAddress(destination)
   189         return Alias(self.__dbh, address, destination)
   191         return Alias(self.__dbh, address, destination)
   190 
   192 
   191     def __getRelocated(self,address, destination=None):
   193     def __getRelocated(self, address, destination=None):
   192         self.__dbConnect()
   194         self.__dbConnect()
   193         address = EmailAddress(address)
   195         address = EmailAddress(address)
   194         if destination is not None:
   196         if destination is not None:
   195             destination = EmailAddress(destination)
   197             destination = EmailAddress(destination)
   196         return Relocated(self.__dbh, address, destination)
   198         return Relocated(self.__dbh, address, destination)
   232 
   234 
   233     def __domDirMake(self, domdir, gid):
   235     def __domDirMake(self, domdir, gid):
   234         os.umask(0006)
   236         os.umask(0006)
   235         oldpwd = os.getcwd()
   237         oldpwd = os.getcwd()
   236         basedir = self._Cfg.dget('misc.base_directory')
   238         basedir = self._Cfg.dget('misc.base_directory')
   237         domdirdirs = domdir.replace(basedir+'/', '').split('/')
   239         domdirdirs = domdir.replace(basedir + '/', '').split('/')
   238 
   240 
   239         os.chdir(basedir)
   241         os.chdir(basedir)
   240         if not os.path.isdir(domdirdirs[0]):
   242         if not os.path.isdir(domdirdirs[0]):
   241             self.__makedir(domdirdirs[0], 489, 0,
   243             self.__makedir(domdirdirs[0], 489, 0,
   242                            self._Cfg.dget('misc.gid_mail'))
   244                            self._Cfg.dget('misc.gid_mail'))
   244         os.umask(0007)
   246         os.umask(0007)
   245         self.__makedir(domdirdirs[1], self._Cfg.dget('domain.directory_mode'),
   247         self.__makedir(domdirdirs[1], self._Cfg.dget('domain.directory_mode'),
   246                        0, gid)
   248                        0, gid)
   247         os.chdir(oldpwd)
   249         os.chdir(oldpwd)
   248 
   250 
   249     def __subscribeFL(self, folderlist, uid, gid):
   251     def __subscribe(self, folderlist, uid, gid):
       
   252         """Creates a subscriptions file with the mailboxes from `folderlist`"""
   250         fname = os.path.join(self._Cfg.dget('maildir.name'), 'subscriptions')
   253         fname = os.path.join(self._Cfg.dget('maildir.name'), 'subscriptions')
   251         sf = file(fname, 'w')
   254         sf = open(fname, 'w')
   252         for f in folderlist:
   255         for f in folderlist:
   253             sf.write(f+'\n')
   256             sf.write('%s\n' % f)
   254         sf.flush()
   257         sf.flush()
   255         sf.close()
   258         sf.close()
   256         os.chown(fname, uid, gid)
   259         os.chown(fname, uid, gid)
   257         os.chmod(fname, 384)
   260         os.chmod(fname, 384)
   258 
   261 
   268         oldpwd = os.getcwd()
   271         oldpwd = os.getcwd()
   269         os.chdir(domdir)
   272         os.chdir(domdir)
   270 
   273 
   271         maildir = self._Cfg.dget('maildir.name')
   274         maildir = self._Cfg.dget('maildir.name')
   272         folders = [maildir]
   275         folders = [maildir]
       
   276         append = folders.append
   273         for folder in self._Cfg.dget('maildir.folders').split(':'):
   277         for folder in self._Cfg.dget('maildir.folders').split(':'):
   274             folder = folder.strip()
   278             folder = folder.strip()
   275             if len(folder) and not folder.count('..')\
   279             if len(folder) and not folder.count('..'):
   276             and re.match(RE_MBOX_NAMES, folder):
   280                 if re.match(RE_MBOX_NAMES, folder):
   277                 folders.append('%s/.%s' % (maildir, folder))
   281                     append('%s/.%s' % (maildir, folder))
       
   282                 else:
       
   283                     self.__warnings.append(_('Skipped mailbox folder: %r') %
       
   284                                            folder)
       
   285             else:
       
   286                 self.__warnings.append(_('Skipped mailbox folder: %r') %
       
   287                                        folder)
       
   288 
   278         subdirs = ['cur', 'new', 'tmp']
   289         subdirs = ['cur', 'new', 'tmp']
   279         mode = self._Cfg.dget('account.directory_mode')
   290         mode = self._Cfg.dget('account.directory_mode')
   280 
   291 
   281         self.__makedir('%s' % uid, mode, uid, gid)
   292         self.__makedir('%s' % uid, mode, uid, gid)
   282         os.chdir('%s' % uid)
   293         os.chdir('%s' % uid)
   283         for folder in folders:
   294         for folder in folders:
   284             self.__makedir(folder, mode, uid, gid)
   295             self.__makedir(folder, mode, uid, gid)
   285             for subdir in subdirs:
   296             for subdir in subdirs:
   286                 self.__makedir(os.path.join(folder, subdir), mode, uid, gid)
   297                 self.__makedir(os.path.join(folder, subdir), mode, uid, gid)
   287         self.__subscribeFL([f.replace(maildir+'/.', '') for f in folders[1:]],
   298         self.__subscribe((f.replace(maildir + '/.', '') for f in folders[1:]),
   288                 uid, gid)
   299                          uid, gid)
   289         os.chdir(oldpwd)
   300         os.chdir(oldpwd)
   290 
   301 
   291     def __userDirDelete(self, domdir, uid, gid):
   302     def __userDirDelete(self, domdir, uid, gid):
   292         if uid > 0 and gid > 0:
   303         if uid > 0 and gid > 0:
   293             userdir = '%s' % uid
   304             userdir = '%s' % uid
   294             if userdir.count('..') or domdir.count('..'):
   305             if userdir.count('..') or domdir.count('..'):
   295                 raise VMMException(_(u'Found ".." in home directory path.'),
   306                 raise VMMException(_(u'Found ".." in home directory path.'),
   296                     ERR.FOUND_DOTS_IN_PATH)
   307                                    ERR.FOUND_DOTS_IN_PATH)
   297             if os.path.isdir(domdir):
   308             if os.path.isdir(domdir):
   298                 os.chdir(domdir)
   309                 os.chdir(domdir)
   299                 if os.path.isdir(userdir):
   310                 if os.path.isdir(userdir):
   300                     mdstat = os.stat(userdir)
   311                     mdstat = os.stat(userdir)
   301                     if (mdstat.st_uid, mdstat.st_gid) != (uid, gid):
   312                     if (mdstat.st_uid, mdstat.st_gid) != (uid, gid):
   302                         raise VMMException(
   313                         raise VMMException(_(
   303                          _(u'Detected owner/group mismatch in home directory.'),
   314                           u'Detected owner/group mismatch in home directory.'),
   304                          ERR.MAILDIR_PERM_MISMATCH)
   315                           ERR.MAILDIR_PERM_MISMATCH)
   305                     rmtree(userdir, ignore_errors=True)
   316                     rmtree(userdir, ignore_errors=True)
   306                 else:
   317                 else:
   307                     raise VMMException(_(u"No such directory: %s") %
   318                     raise VMMException(_(u"No such directory: %s") %
   308                         os.path.join(domdir, userdir), ERR.NO_SUCH_DIRECTORY)
   319                         os.path.join(domdir, userdir), ERR.NO_SUCH_DIRECTORY)
   309 
   320 
   310     def __domDirDelete(self, domdir, gid):
   321     def __domDirDelete(self, domdir, gid):
   311         if gid > 0:
   322         if gid > 0:
   312             if not self.__isdir(domdir):
   323             if not self.__isdir(domdir):
   313                 return
   324                 return
   314             basedir = self._Cfg.dget('misc.base_directory')
   325             basedir = self._Cfg.dget('misc.base_directory')
   315             domdirdirs = domdir.replace(basedir+'/', '').split('/')
   326             domdirdirs = domdir.replace(basedir + '/', '').split('/')
   316             domdirparent = os.path.join(basedir, domdirdirs[0])
   327             domdirparent = os.path.join(basedir, domdirdirs[0])
   317             if basedir.count('..') or domdir.count('..'):
   328             if basedir.count('..') or domdir.count('..'):
   318                 raise VMMException(_(u'Found ".." in domain directory path.'),
   329                 raise VMMException(_(u'Found ".." in domain directory path.'),
   319                         ERR.FOUND_DOTS_IN_PATH)
   330                         ERR.FOUND_DOTS_IN_PATH)
   320             if os.path.isdir(domdirparent):
   331             if os.path.isdir(domdirparent):
   376             return '{%s}%s' % (self._scheme, self.__pwMD5(password, user))
   387             return '{%s}%s' % (self._scheme, self.__pwMD5(password, user))
   377         elif self._scheme == 'MD4':
   388         elif self._scheme == 'MD4':
   378             return '{%s}%s' % (self._scheme, self.__pwMD4(password))
   389             return '{%s}%s' % (self._scheme, self.__pwMD4(password))
   379         elif self._scheme in ['SMD5', 'SSHA', 'CRAM-MD5', 'HMAC-MD5',
   390         elif self._scheme in ['SMD5', 'SSHA', 'CRAM-MD5', 'HMAC-MD5',
   380                 'LANMAN', 'NTLM', 'RPA']:
   391                 'LANMAN', 'NTLM', 'RPA']:
   381             return Popen([self._Cfg.dget('bin.dovecotpw'), '-s',
   392             return Popen([self._Cfg.dget('bin.dovecotpw'),
   382                 self._scheme,'-p',password],stdout=PIPE).communicate()[0][:-1]
   393                          '-s', self._scheme, '-p', password],
       
   394                          stdout=PIPE).communicate()[0][:-1]
   383         else:
   395         else:
   384             return '{%s}%s' % (self._scheme, password)
   396             return '{%s}%s' % (self._scheme, password)
   385 
   397 
   386     def hasWarnings(self):
   398     def hasWarnings(self):
   387         """Checks if warnings are present, returns bool."""
   399         """Checks if warnings are present, returns bool."""
   411             dom.updateTransport(transport)
   423             dom.updateTransport(transport)
   412         else:
   424         else:
   413             dom.updateTransport(transport, force=True)
   425             dom.updateTransport(transport, force=True)
   414 
   426 
   415     def domainDelete(self, domainname, force=None):
   427     def domainDelete(self, domainname, force=None):
   416         if not force is None and force not in ['deluser','delalias','delall']:
   428         if not force is None and force not in ['deluser', 'delalias',
   417             raise VMMDomainException(_(u"Invalid argument: “%s”") % force,
   429                                                'delall']:
   418                 ERR.INVALID_OPTION)
   430                 raise VMMDomainException(_(u'Invalid argument: “%s”') %
       
   431                                          force, ERR.INVALID_OPTION)
   419         dom = self.__getDomain(domainname)
   432         dom = self.__getDomain(domainname)
   420         gid = dom.getID()
   433         gid = dom.getID()
   421         domdir = dom.getDir()
   434         domdir = dom.getDir()
   422         if self._Cfg.dget('domain.force_deletion') or force == 'delall':
   435         if self._Cfg.dget('domain.force_deletion') or force == 'delall':
   423             dom.delete(True, True)
   436             dom.delete(True, True)
   430         if self._Cfg.dget('domain.delete_directory'):
   443         if self._Cfg.dget('domain.delete_directory'):
   431             self.__domDirDelete(domdir, gid)
   444             self.__domDirDelete(domdir, gid)
   432 
   445 
   433     def domainInfo(self, domainname, details=None):
   446     def domainInfo(self, domainname, details=None):
   434         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
   447         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
   435                 'relocated', 'detailed']:
   448                            'relocated']:
   436             raise VMMException(_(u'Invalid argument: “%s”') % details,
   449             raise VMMException(_(u'Invalid argument: “%s”') % details,
   437                     ERR.INVALID_AGUMENT)
   450                                ERR.INVALID_AGUMENT)
   438         if details == 'detailed':
       
   439             details = 'full'
       
   440             self.__warnings.append(_(u'\
       
   441 The keyword “detailed” is deprecated and will be removed in a future release.\n\
       
   442    Please use the keyword “full” to get full details.'))
       
   443         dom = self.__getDomain(domainname)
   451         dom = self.__getDomain(domainname)
   444         dominfo = dom.getInfo()
   452         dominfo = dom.getInfo()
   445         if dominfo['domainname'].startswith('xn--'):
   453         if dominfo['domainname'].startswith('xn--'):
   446             dominfo['domainname'] += ' (%s)' % ace2idna(dominfo['domainname'])
   454             dominfo['domainname'] += ' (%s)' % ace2idna(dominfo['domainname'])
   447         if details is None:
   455         if details is None:
   532         alias.save(long(self._postconf.read('virtual_alias_expansion_limit')))
   540         alias.save(long(self._postconf.read('virtual_alias_expansion_limit')))
   533         gid = self.__getDomain(alias._dest._domainname).getID()
   541         gid = self.__getDomain(alias._dest._domainname).getID()
   534         if gid > 0 and (not Handler.accountExists(self.__dbh, alias._dest) and
   542         if gid > 0 and (not Handler.accountExists(self.__dbh, alias._dest) and
   535                         not Handler.aliasExists(self.__dbh, alias._dest)):
   543                         not Handler.aliasExists(self.__dbh, alias._dest)):
   536             self.__warnings.append(
   544             self.__warnings.append(
   537                     _(u"The destination account/alias “%s” doesn't exist.") %
   545                 _(u"The destination account/alias “%s” doesn't exist.") %
   538                                    alias._dest)
   546                                    alias._dest)
   539 
   547 
   540     def userDelete(self, emailaddress, force=None):
   548     def userDelete(self, emailaddress, force=None):
   541         if force not in [None, 'delalias']:
   549         if force not in [None, 'delalias']:
   542             raise VMMException(_(u"Invalid argument: “%s”") % force,
   550             raise VMMException(_(u"Invalid argument: “%s”") % force,
   553                         ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]:
   561                         ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]:
   554                     warning = _(u"""\
   562                     warning = _(u"""\
   555 The account has been successfully deleted from the database.
   563 The account has been successfully deleted from the database.
   556     But an error occurred while deleting the following directory:
   564     But an error occurred while deleting the following directory:
   557     “%(directory)s”
   565     “%(directory)s”
   558     Reason: %(reason)s""") % {'directory': acc.getDir('home'),'reason': e.msg()}
   566     Reason: %(reason)s""") %
       
   567                     {'directory': acc.getDir('home'), 'reason': e.msg()}
   559                     self.__warnings.append(warning)
   568                     self.__warnings.append(warning)
   560                 else:
   569                 else:
   561                     raise e
   570                     raise
   562 
   571 
   563     def aliasInfo(self, aliasaddress):
   572     def aliasInfo(self, aliasaddress):
   564         alias = self.__getAlias(aliasaddress)
   573         alias = self.__getAlias(aliasaddress)
   565         return alias.getInfo()
   574         return alias.getInfo()
   566 
   575 
   591         if password is None or (isinstance(password, basestring) and
   600         if password is None or (isinstance(password, basestring) and
   592                                 not len(password)):
   601                                 not len(password)):
   593             raise ValueError('could not accept password: %r' % password)
   602             raise ValueError('could not accept password: %r' % password)
   594         acc = self.__getAccount(emailaddress)
   603         acc = self.__getAccount(emailaddress)
   595         if acc.getUID() == 0:
   604         if acc.getUID() == 0:
   596            raise VMMException(_(u"Account doesn't exist"), ERR.NO_SUCH_ACCOUNT)
   605             raise VMMException(_(u"Account doesn't exist"),
       
   606                                ERR.NO_SUCH_ACCOUNT)
   597         acc.modify('password', self.__pwhash(password, user=emailaddress))
   607         acc.modify('password', self.__pwhash(password, user=emailaddress))
   598 
   608 
   599     def userName(self, emailaddress, name):
   609     def userName(self, emailaddress, name):
   600         acc = self.__getAccount(emailaddress)
   610         acc = self.__getAccount(emailaddress)
   601         acc.modify('name', name)
   611         acc.modify('name', name)