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.Config import Config as Cfg |
29 from VirtualMailManager.Domain import Domain |
29 from VirtualMailManager.Domain import Domain |
30 from VirtualMailManager.EmailAddress import EmailAddress |
30 from VirtualMailManager.EmailAddress import EmailAddress |
31 from VirtualMailManager.Exceptions import * |
31 from VirtualMailManager.errors import VMMError, AliasError |
32 from VirtualMailManager.Relocated import Relocated |
32 from VirtualMailManager.Relocated import Relocated |
33 from VirtualMailManager.ext.Postconf import Postconf |
33 from VirtualMailManager.ext.Postconf import Postconf |
34 |
34 |
35 |
35 |
36 SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' |
36 SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' |
50 ``skip_some_checks`` : bool |
50 ``skip_some_checks`` : bool |
51 When a derived class knows how to handle all checks this |
51 When a derived class knows how to handle all checks this |
52 argument may be ``True``. By default it is ``False`` and |
52 argument may be ``True``. By default it is ``False`` and |
53 all checks will be performed. |
53 all checks will be performed. |
54 |
54 |
55 Throws a VMMNotRootException if your uid is greater 0. |
55 Throws a NotRootError if your uid is greater 0. |
56 """ |
56 """ |
57 self._cfgFileName = '' |
57 self._cfgFileName = '' |
58 self.__warnings = [] |
58 self.__warnings = [] |
59 self._Cfg = None |
59 self._Cfg = None |
60 self._dbh = None |
60 self._dbh = None |
61 |
61 |
62 if os.geteuid(): |
62 if os.geteuid(): |
63 raise VMMNotRootException(_(u"You are not root.\n\tGood bye!\n"), |
63 raise NotRootError(_(u"You are not root.\n\tGood bye!\n"), |
64 ERR.CONF_NOPERM) |
64 ERR.CONF_NOPERM) |
65 if self.__chkCfgFile(): |
65 if self.__chkCfgFile(): |
66 self._Cfg = Cfg(self._cfgFileName) |
66 self._Cfg = Cfg(self._cfgFileName) |
67 self._Cfg.load() |
67 self._Cfg.load() |
68 if not skip_some_checks: |
68 if not skip_some_checks: |
76 tmp = os.path.join(path, 'vmm.cfg') |
76 tmp = os.path.join(path, 'vmm.cfg') |
77 if os.path.isfile(tmp): |
77 if os.path.isfile(tmp): |
78 self._cfgFileName = tmp |
78 self._cfgFileName = tmp |
79 break |
79 break |
80 if not len(self._cfgFileName): |
80 if not len(self._cfgFileName): |
81 raise VMMException( |
81 raise VMMError( |
82 _(u"No “vmm.cfg” found in: /root:/usr/local/etc:/etc"), |
82 _(u"No “vmm.cfg” found in: /root:/usr/local/etc:/etc"), |
83 ERR.CONF_NOFILE) |
83 ERR.CONF_NOFILE) |
84 |
84 |
85 def __chkCfgFile(self): |
85 def __chkCfgFile(self): |
86 """Checks the configuration file, returns bool""" |
86 """Checks the configuration file, returns bool""" |
87 self.__findCfgFile() |
87 self.__findCfgFile() |
88 fstat = os.stat(self._cfgFileName) |
88 fstat = os.stat(self._cfgFileName) |
89 fmode = int(oct(fstat.st_mode & 0777)) |
89 fmode = int(oct(fstat.st_mode & 0777)) |
90 if fmode % 100 and fstat.st_uid != fstat.st_gid or \ |
90 if fmode % 100 and fstat.st_uid != fstat.st_gid or \ |
91 fmode % 10 and fstat.st_uid == fstat.st_gid: |
91 fmode % 10 and fstat.st_uid == fstat.st_gid: |
92 raise VMMPermException(_( |
92 raise PermissionError(_( |
93 u'fix permissions (%(perms)s) for “%(file)s”\n\ |
93 u'fix permissions (%(perms)s) for “%(file)s”\n\ |
94 `chmod 0600 %(file)s` would be great.') % {'file': |
94 `chmod 0600 %(file)s` would be great.') % {'file': |
95 self._cfgFileName, 'perms': fmode}, ERR.CONF_WRONGPERM) |
95 self._cfgFileName, 'perms': fmode}, ERR.CONF_WRONGPERM) |
96 else: |
96 else: |
97 return True |
97 return True |
103 old_umask = os.umask(0006) |
103 old_umask = os.umask(0006) |
104 os.makedirs(basedir, 0771) |
104 os.makedirs(basedir, 0771) |
105 os.chown(basedir, 0, self._Cfg.dget('misc.gid_mail')) |
105 os.chown(basedir, 0, self._Cfg.dget('misc.gid_mail')) |
106 os.umask(old_umask) |
106 os.umask(old_umask) |
107 elif not os.path.isdir(basedir): |
107 elif not os.path.isdir(basedir): |
108 raise VMMException(_(u'“%s” is not a directory.\n\ |
108 raise VMMError(_(u'“%s” is not a directory.\n\ |
109 (vmm.cfg: section "misc", option "base_directory")') % |
109 (vmm.cfg: section "misc", option "base_directory")') % |
110 basedir, ERR.NO_SUCH_DIRECTORY) |
110 basedir, ERR.NO_SUCH_DIRECTORY) |
111 for opt, val in self._Cfg.items('bin'): |
111 for opt, val in self._Cfg.items('bin'): |
112 try: |
112 try: |
113 exec_ok(val) |
113 exec_ok(val) |
114 except VMMException, e: |
114 except VMMError, e: |
115 code = e.code() |
115 if e.code is ERR.NO_SUCH_BINARY: |
116 if code is ERR.NO_SUCH_BINARY: |
116 raise VMMError(_(u'“%(binary)s” doesn\'t exist.\n\ |
117 raise VMMException(_(u'“%(binary)s” doesn\'t exist.\n\ |
|
118 (vmm.cfg: section "bin", option "%(option)s")') % |
117 (vmm.cfg: section "bin", option "%(option)s")') % |
119 {'binary': val, 'option': opt}, |
118 {'binary': val, 'option': opt}, |
120 ERR.NO_SUCH_BINARY) |
119 ERR.NO_SUCH_BINARY) |
121 elif code is ERR.NOT_EXECUTABLE: |
120 elif e.code is ERR.NOT_EXECUTABLE: |
122 raise VMMException(_(u'“%(binary)s” is not executable.\ |
121 raise VMMError(_(u'“%(binary)s” is not executable.\ |
123 \n(vmm.cfg: section "bin", option "%(option)s")') % |
122 \n(vmm.cfg: section "bin", option "%(option)s")') % |
124 {'binary': val, 'option': opt}, |
123 {'binary': val, 'option': opt}, |
125 ERR.NOT_EXECUTABLE) |
124 ERR.NOT_EXECUTABLE) |
126 else: |
125 else: |
127 raise |
126 raise |
139 client_encoding='utf8', unicode_results=True) |
138 client_encoding='utf8', unicode_results=True) |
140 dbc = self._dbh.cursor() |
139 dbc = self._dbh.cursor() |
141 dbc.execute("SET NAMES 'UTF8'") |
140 dbc.execute("SET NAMES 'UTF8'") |
142 dbc.close() |
141 dbc.close() |
143 except PgSQL.libpq.DatabaseError, e: |
142 except PgSQL.libpq.DatabaseError, e: |
144 raise VMMException(str(e), ERR.DATABASE_ERROR) |
143 raise VMMError(str(e), ERR.DATABASE_ERROR) |
145 |
144 |
146 def _exists(dbh, query): |
145 def _exists(dbh, query): |
147 dbc = dbh.cursor() |
146 dbc = dbh.cursor() |
148 dbc.execute(query) |
147 dbc.execute(query) |
149 gid = dbc.fetchone() |
148 gid = dbc.fetchone() |
296 |
295 |
297 def __userDirDelete(self, domdir, uid, gid): |
296 def __userDirDelete(self, domdir, uid, gid): |
298 if uid > 0 and gid > 0: |
297 if uid > 0 and gid > 0: |
299 userdir = '%s' % uid |
298 userdir = '%s' % uid |
300 if userdir.count('..') or domdir.count('..'): |
299 if userdir.count('..') or domdir.count('..'): |
301 raise VMMException(_(u'Found ".." in home directory path.'), |
300 raise VMMError(_(u'Found ".." in home directory path.'), |
302 ERR.FOUND_DOTS_IN_PATH) |
301 ERR.FOUND_DOTS_IN_PATH) |
303 if os.path.isdir(domdir): |
302 if os.path.isdir(domdir): |
304 os.chdir(domdir) |
303 os.chdir(domdir) |
305 if os.path.isdir(userdir): |
304 if os.path.isdir(userdir): |
306 mdstat = os.stat(userdir) |
305 mdstat = os.stat(userdir) |
307 if (mdstat.st_uid, mdstat.st_gid) != (uid, gid): |
306 if (mdstat.st_uid, mdstat.st_gid) != (uid, gid): |
308 raise VMMException(_( |
307 raise VMMError(_( |
309 u'Detected owner/group mismatch in home directory.'), |
308 u'Detected owner/group mismatch in home directory.'), |
310 ERR.MAILDIR_PERM_MISMATCH) |
309 ERR.MAILDIR_PERM_MISMATCH) |
311 rmtree(userdir, ignore_errors=True) |
310 rmtree(userdir, ignore_errors=True) |
312 else: |
311 else: |
313 raise VMMException(_(u"No such directory: %s") % |
312 raise VMMError(_(u"No such directory: %s") % |
314 os.path.join(domdir, userdir), ERR.NO_SUCH_DIRECTORY) |
313 os.path.join(domdir, userdir), ERR.NO_SUCH_DIRECTORY) |
315 |
314 |
316 def __domDirDelete(self, domdir, gid): |
315 def __domDirDelete(self, domdir, gid): |
317 if gid > 0: |
316 if gid > 0: |
318 if not self.__isdir(domdir): |
317 if not self.__isdir(domdir): |
319 return |
318 return |
320 basedir = self._Cfg.dget('misc.base_directory') |
319 basedir = self._Cfg.dget('misc.base_directory') |
321 domdirdirs = domdir.replace(basedir + '/', '').split('/') |
320 domdirdirs = domdir.replace(basedir + '/', '').split('/') |
322 domdirparent = os.path.join(basedir, domdirdirs[0]) |
321 domdirparent = os.path.join(basedir, domdirdirs[0]) |
323 if basedir.count('..') or domdir.count('..'): |
322 if basedir.count('..') or domdir.count('..'): |
324 raise VMMException(_(u'Found ".." in domain directory path.'), |
323 raise VMMError(_(u'Found ".." in domain directory path.'), |
325 ERR.FOUND_DOTS_IN_PATH) |
324 ERR.FOUND_DOTS_IN_PATH) |
326 if os.path.isdir(domdirparent): |
325 if os.path.isdir(domdirparent): |
327 os.chdir(domdirparent) |
326 os.chdir(domdirparent) |
328 if os.lstat(domdirdirs[1]).st_gid != gid: |
327 if os.lstat(domdirdirs[1]).st_gid != gid: |
329 raise VMMException(_( |
328 raise VMMError(_( |
330 u'Detected group mismatch in domain directory.'), |
329 u'Detected group mismatch in domain directory.'), |
331 ERR.DOMAINDIR_GROUP_MISMATCH) |
330 ERR.DOMAINDIR_GROUP_MISMATCH) |
332 rmtree(domdirdirs[1], ignore_errors=True) |
331 rmtree(domdirdirs[1], ignore_errors=True) |
333 |
332 |
334 def __getSalt(self): |
333 def __getSalt(self): |
409 dom.save() |
408 dom.save() |
410 self.__domDirMake(dom.getDir(), dom.getID()) |
409 self.__domDirMake(dom.getDir(), dom.getID()) |
411 |
410 |
412 def domainTransport(self, domainname, transport, force=None): |
411 def domainTransport(self, domainname, transport, force=None): |
413 if force is not None and force != 'force': |
412 if force is not None and force != 'force': |
414 raise VMMDomainException(_(u"Invalid argument: “%s”") % force, |
413 raise DomainError(_(u"Invalid argument: “%s”") % force, |
415 ERR.INVALID_OPTION) |
414 ERR.INVALID_OPTION) |
416 dom = self.__getDomain(domainname, None) |
415 dom = self.__getDomain(domainname, None) |
417 if force is None: |
416 if force is None: |
418 dom.updateTransport(transport) |
417 dom.updateTransport(transport) |
419 else: |
418 else: |
420 dom.updateTransport(transport, force=True) |
419 dom.updateTransport(transport, force=True) |
421 |
420 |
422 def domainDelete(self, domainname, force=None): |
421 def domainDelete(self, domainname, force=None): |
423 if not force is None and force not in ['deluser', 'delalias', |
422 if not force is None and force not in ['deluser', 'delalias', |
424 'delall']: |
423 'delall']: |
425 raise VMMDomainException(_(u'Invalid argument: “%s”') % |
424 raise DomainError(_(u'Invalid argument: “%s”') % |
426 force, ERR.INVALID_OPTION) |
425 force, ERR.INVALID_OPTION) |
427 dom = self.__getDomain(domainname) |
426 dom = self.__getDomain(domainname) |
428 gid = dom.getID() |
427 gid = dom.getID() |
429 domdir = dom.getDir() |
428 domdir = dom.getDir() |
430 if self._Cfg.dget('domain.force_deletion') or force == 'delall': |
429 if self._Cfg.dget('domain.force_deletion') or force == 'delall': |
439 self.__domDirDelete(domdir, gid) |
438 self.__domDirDelete(domdir, gid) |
440 |
439 |
441 def domainInfo(self, domainname, details=None): |
440 def domainInfo(self, domainname, details=None): |
442 if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full', |
441 if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full', |
443 'relocated']: |
442 'relocated']: |
444 raise VMMException(_(u'Invalid argument: “%s”') % details, |
443 raise VMMError(_(u'Invalid argument: “%s”') % details, |
445 ERR.INVALID_AGUMENT) |
444 ERR.INVALID_AGUMENT) |
446 dom = self.__getDomain(domainname) |
445 dom = self.__getDomain(domainname) |
447 dominfo = dom.getInfo() |
446 dominfo = dom.getInfo() |
448 if dominfo['domainname'].startswith('xn--'): |
447 if dominfo['domainname'].startswith('xn--'): |
449 dominfo['domainname'] += ' (%s)' % ace2idna(dominfo['domainname']) |
448 dominfo['domainname'] += ' (%s)' % ace2idna(dominfo['domainname']) |
509 elif pattern.startswith('%'): |
508 elif pattern.startswith('%'): |
510 domain = pattern[1:] |
509 domain = pattern[1:] |
511 elif pattern.endswith('%'): |
510 elif pattern.endswith('%'): |
512 domain = pattern[:-1] |
511 domain = pattern[:-1] |
513 if not re.match(RE_DOMAIN_SRCH, domain): |
512 if not re.match(RE_DOMAIN_SRCH, domain): |
514 raise VMMException( |
513 raise VMMError( |
515 _(u"The pattern “%s” contains invalid characters.") % |
514 _(u"The pattern “%s” contains invalid characters.") % |
516 pattern, ERR.DOMAIN_INVALID) |
515 pattern, ERR.DOMAIN_INVALID) |
517 self.__dbConnect() |
516 self.__dbConnect() |
518 return search(self._dbh, pattern=pattern, like=like) |
517 return search(self._dbh, pattern=pattern, like=like) |
519 |
518 |
544 _(u"The destination account/alias “%s” doesn't exist.") % |
543 _(u"The destination account/alias “%s” doesn't exist.") % |
545 destination) |
544 destination) |
546 |
545 |
547 def userDelete(self, emailaddress, force=None): |
546 def userDelete(self, emailaddress, force=None): |
548 if force not in [None, 'delalias']: |
547 if force not in [None, 'delalias']: |
549 raise VMMException(_(u"Invalid argument: “%s”") % force, |
548 raise VMMError(_(u"Invalid argument: “%s”") % force, |
550 ERR.INVALID_AGUMENT) |
549 ERR.INVALID_AGUMENT) |
551 acc = self.__getAccount(emailaddress) |
550 acc = self.__getAccount(emailaddress) |
552 uid = acc.getUID() |
551 uid = acc.getUID() |
553 gid = acc.getGID() |
552 gid = acc.getGID() |
554 acc.delete(force) |
553 acc.delete(force) |
555 if self._Cfg.dget('account.delete_directory'): |
554 if self._Cfg.dget('account.delete_directory'): |
556 try: |
555 try: |
557 self.__userDirDelete(acc.getDir('domain'), uid, gid) |
556 self.__userDirDelete(acc.getDir('domain'), uid, gid) |
558 except VMMException, e: |
557 except VMMError, e: |
559 if e.code() in [ERR.FOUND_DOTS_IN_PATH, |
558 if e.code in [ERR.FOUND_DOTS_IN_PATH, |
560 ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]: |
559 ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]: |
561 warning = _(u"""\ |
560 warning = _(u"""\ |
562 The account has been successfully deleted from the database. |
561 The account has been successfully deleted from the database. |
563 But an error occurred while deleting the following directory: |
562 But an error occurred while deleting the following directory: |
564 “%(directory)s” |
563 “%(directory)s” |
565 Reason: %(reason)s""") % \ |
564 Reason: %(reason)s""") % \ |
566 {'directory': acc.getDir('home'), 'reason': e.msg()} |
565 {'directory': acc.getDir('home'), 'reason': e.msg} |
567 self.__warnings.append(warning) |
566 self.__warnings.append(warning) |
568 else: |
567 else: |
569 raise |
568 raise |
570 |
569 |
571 def aliasInfo(self, aliasaddress): |
570 def aliasInfo(self, aliasaddress): |
572 """Returns an iterator object for all destinations (`EmailAddress` |
571 """Returns an iterator object for all destinations (`EmailAddress` |
573 instances) for the `Alias` with the given *aliasaddress*.""" |
572 instances) for the `Alias` with the given *aliasaddress*.""" |
574 alias = self.__getAlias(aliasaddress) |
573 alias = self.__getAlias(aliasaddress) |
575 try: |
574 try: |
576 return alias.get_destinations() |
575 return alias.get_destinations() |
577 except VMMAliasException, e: |
576 except AliasError, e: |
578 if e.code() == ERR.NO_SUCH_ALIAS: |
577 if e.code == ERR.NO_SUCH_ALIAS: |
579 if Handler.accountExists(self._dbh, alias._addr): |
578 if Handler.accountExists(self._dbh, alias._addr): |
580 raise VMMException( |
579 raise VMMError( |
581 _(u'There is already an account with address “%s”.') % |
580 _(u'There is already an account with address “%s”.') % |
582 aliasaddress, ERR.ACCOUNT_EXISTS) |
581 aliasaddress, ERR.ACCOUNT_EXISTS) |
583 if Handler.relocatedExists(self._dbh, alias._addr): |
582 if Handler.relocatedExists(self._dbh, alias._addr): |
584 raise VMMException(_(u'There is already a relocated user \ |
583 raise VMMError(_(u'There is already a relocated user \ |
585 with the address “%s”.') % |
584 with the address “%s”.') % |
586 aliasaddress, ERR.RELOCATED_EXISTS) |
585 aliasaddress, ERR.RELOCATED_EXISTS) |
587 raise |
586 raise |
588 else: |
587 else: |
589 raise |
588 raise |
598 else: |
597 else: |
599 alias.del_destination(EmailAddress(targetaddress)) |
598 alias.del_destination(EmailAddress(targetaddress)) |
600 |
599 |
601 def userInfo(self, emailaddress, details=None): |
600 def userInfo(self, emailaddress, details=None): |
602 if details not in (None, 'du', 'aliases', 'full'): |
601 if details not in (None, 'du', 'aliases', 'full'): |
603 raise VMMException(_(u'Invalid argument: “%s”') % details, |
602 raise VMMError(_(u'Invalid argument: “%s”') % details, |
604 ERR.INVALID_AGUMENT) |
603 ERR.INVALID_AGUMENT) |
605 acc = self.__getAccount(emailaddress) |
604 acc = self.__getAccount(emailaddress) |
606 info = acc.getInfo(self._Cfg.dget('misc.dovecot_version')) |
605 info = acc.getInfo(self._Cfg.dget('misc.dovecot_version')) |
607 if self._Cfg.dget('account.disk_usage') or details in ('du', 'full'): |
606 if self._Cfg.dget('account.disk_usage') or details in ('du', 'full'): |
608 info['disk usage'] = self.__getDiskUsage('%(maildir)s' % info) |
607 info['disk usage'] = self.__getDiskUsage('%(maildir)s' % info) |
621 if password is None or (isinstance(password, basestring) and |
620 if password is None or (isinstance(password, basestring) and |
622 not len(password)): |
621 not len(password)): |
623 raise ValueError('could not accept password: %r' % password) |
622 raise ValueError('could not accept password: %r' % password) |
624 acc = self.__getAccount(emailaddress) |
623 acc = self.__getAccount(emailaddress) |
625 if acc.getUID() == 0: |
624 if acc.getUID() == 0: |
626 raise VMMException(_(u"Account doesn't exist"), |
625 raise VMMError(_(u"Account doesn't exist"), |
627 ERR.NO_SUCH_ACCOUNT) |
626 ERR.NO_SUCH_ACCOUNT) |
628 acc.modify('password', self.__pwhash(password, user=emailaddress)) |
627 acc.modify('password', self.__pwhash(password, user=emailaddress)) |
629 |
628 |
630 def userName(self, emailaddress, name): |
629 def userName(self, emailaddress, name): |
631 acc = self.__getAccount(emailaddress) |
630 acc = self.__getAccount(emailaddress) |