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: |
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() |
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'))) |
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 |