18 from shutil import rmtree |
18 from shutil import rmtree |
19 from subprocess import Popen, PIPE |
19 from subprocess import Popen, PIPE |
20 |
20 |
21 from pyPgSQL import PgSQL # python-pgsql - http://pypgsql.sourceforge.net |
21 from pyPgSQL import PgSQL # python-pgsql - http://pypgsql.sourceforge.net |
22 |
22 |
23 import VirtualMailManager.constants.ERROR as ERR |
|
24 from VirtualMailManager.Account import Account |
23 from VirtualMailManager.Account import Account |
25 from VirtualMailManager.Alias import Alias |
24 from VirtualMailManager.Alias import Alias |
26 from VirtualMailManager.AliasDomain import AliasDomain |
25 from VirtualMailManager.AliasDomain import AliasDomain |
27 from VirtualMailManager.common import exec_ok |
26 from VirtualMailManager.common import exec_ok |
28 from VirtualMailManager.Config import Config as Cfg |
27 from VirtualMailManager.Config import Config as Cfg |
|
28 from VirtualMailManager.constants import \ |
|
29 ACCOUNT_EXISTS, ALIAS_EXISTS, CONF_NOFILE, CONF_NOPERM, CONF_WRONGPERM, \ |
|
30 DATABASE_ERROR, DOMAINDIR_GROUP_MISMATCH, DOMAIN_INVALID, \ |
|
31 FOUND_DOTS_IN_PATH, INVALID_ARGUMENT, MAILDIR_PERM_MISMATCH, \ |
|
32 NOT_EXECUTABLE, NO_SUCH_ACCOUNT, NO_SUCH_ALIAS, NO_SUCH_BINARY, \ |
|
33 NO_SUCH_DIRECTORY, NO_SUCH_RELOCATED, RELOCATED_EXISTS |
29 from VirtualMailManager.Domain import Domain, get_gid |
34 from VirtualMailManager.Domain import Domain, get_gid |
30 from VirtualMailManager.EmailAddress import EmailAddress |
35 from VirtualMailManager.EmailAddress import EmailAddress |
31 from VirtualMailManager.errors import \ |
36 from VirtualMailManager.errors import \ |
32 DomainError, NotRootError, PermissionError, VMMError |
37 DomainError, NotRootError, PermissionError, VMMError |
33 from VirtualMailManager.mailbox import new as new_mailbox |
38 from VirtualMailManager.mailbox import new as new_mailbox |
112 os.chown(basedir, 0, 0) |
117 os.chown(basedir, 0, 0) |
113 os.umask(old_umask) |
118 os.umask(old_umask) |
114 elif not os.path.isdir(basedir): |
119 elif not os.path.isdir(basedir): |
115 raise VMMError(_(u"'%s' is not a directory.\n(vmm.cfg: section " |
120 raise VMMError(_(u"'%s' is not a directory.\n(vmm.cfg: section " |
116 u"'misc', option 'base_directory')") % basedir, |
121 u"'misc', option 'base_directory')") % basedir, |
117 ERR.NO_SUCH_DIRECTORY) |
122 NO_SUCH_DIRECTORY) |
118 for opt, val in self._Cfg.items('bin'): |
123 for opt, val in self._Cfg.items('bin'): |
119 try: |
124 try: |
120 exec_ok(val) |
125 exec_ok(val) |
121 except VMMError, err: |
126 except VMMError, err: |
122 if err.code is ERR.NO_SUCH_BINARY: |
127 if err.code is NO_SUCH_BINARY: |
123 raise VMMError(_(u"'%(binary)s' doesn't exist.\n(vmm.cfg: " |
128 raise VMMError(_(u"'%(binary)s' doesn't exist.\n(vmm.cfg: " |
124 u"section 'bin', option '%(option)s')") % |
129 u"section 'bin', option '%(option)s')") % |
125 {'binary': val, 'option': opt}, err.code) |
130 {'binary': val, 'option': opt}, err.code) |
126 elif err.code is ERR.NOT_EXECUTABLE: |
131 elif err.code is NOT_EXECUTABLE: |
127 raise VMMError(_(u"'%(binary)s' is not executable.\n" |
132 raise VMMError(_(u"'%(binary)s' is not executable.\n" |
128 u"(vmm.cfg: section 'bin', option " |
133 u"(vmm.cfg: section 'bin', option " |
129 u"'%(option)s')") % {'binary': val, |
134 u"'%(option)s')") % {'binary': val, |
130 'option': opt}, err.code) |
135 'option': opt}, err.code) |
131 else: |
136 else: |
144 client_encoding='utf8', unicode_results=True) |
149 client_encoding='utf8', unicode_results=True) |
145 dbc = self._dbh.cursor() |
150 dbc = self._dbh.cursor() |
146 dbc.execute("SET NAMES 'UTF8'") |
151 dbc.execute("SET NAMES 'UTF8'") |
147 dbc.close() |
152 dbc.close() |
148 except PgSQL.libpq.DatabaseError, e: |
153 except PgSQL.libpq.DatabaseError, e: |
149 raise VMMError(str(e), ERR.DATABASE_ERROR) |
154 raise VMMError(str(e), DATABASE_ERROR) |
150 |
155 |
151 def _chk_other_address_types(self, address, exclude): |
156 def _chk_other_address_types(self, address, exclude): |
152 """Checks if the EmailAddress *address* is known as `TYPE_ACCOUNT`, |
157 """Checks if the EmailAddress *address* is known as `TYPE_ACCOUNT`, |
153 `TYPE_ALIAS` or `TYPE_RELOCATED`, but not as the `TYPE_*` specified |
158 `TYPE_ALIAS` or `TYPE_RELOCATED`, but not as the `TYPE_*` specified |
154 by *exclude*. If the *address* is known as one of the `TYPE_*`s |
159 by *exclude*. If the *address* is known as one of the `TYPE_*`s |
244 def __userDirDelete(self, domdir, uid, gid): |
249 def __userDirDelete(self, domdir, uid, gid): |
245 if uid > 0 and gid > 0: |
250 if uid > 0 and gid > 0: |
246 userdir = '%s' % uid |
251 userdir = '%s' % uid |
247 if userdir.count('..') or domdir.count('..'): |
252 if userdir.count('..') or domdir.count('..'): |
248 raise VMMError(_(u'Found ".." in home directory path.'), |
253 raise VMMError(_(u'Found ".." in home directory path.'), |
249 ERR.FOUND_DOTS_IN_PATH) |
254 FOUND_DOTS_IN_PATH) |
250 if os.path.isdir(domdir): |
255 if os.path.isdir(domdir): |
251 os.chdir(domdir) |
256 os.chdir(domdir) |
252 if os.path.isdir(userdir): |
257 if os.path.isdir(userdir): |
253 mdstat = os.stat(userdir) |
258 mdstat = os.stat(userdir) |
254 if (mdstat.st_uid, mdstat.st_gid) != (uid, gid): |
259 if (mdstat.st_uid, mdstat.st_gid) != (uid, gid): |
255 raise VMMError(_( |
260 raise VMMError(_(u'Detected owner/group mismatch in ' |
256 u'Detected owner/group mismatch in home directory.'), |
261 u'home directory.'), |
257 ERR.MAILDIR_PERM_MISMATCH) |
262 MAILDIR_PERM_MISMATCH) |
258 rmtree(userdir, ignore_errors=True) |
263 rmtree(userdir, ignore_errors=True) |
259 else: |
264 else: |
260 raise VMMError(_(u"No such directory: %s") % |
265 raise VMMError(_(u"No such directory: %s") % |
261 os.path.join(domdir, userdir), ERR.NO_SUCH_DIRECTORY) |
266 os.path.join(domdir, userdir), |
|
267 NO_SUCH_DIRECTORY) |
262 |
268 |
263 def __domDirDelete(self, domdir, gid): |
269 def __domDirDelete(self, domdir, gid): |
264 if gid > 0: |
270 if gid > 0: |
265 if not self.__isdir(domdir): |
271 if not self.__isdir(domdir): |
266 return |
272 return |
267 basedir = self._Cfg.dget('misc.base_directory') |
273 basedir = self._Cfg.dget('misc.base_directory') |
268 domdirdirs = domdir.replace(basedir + '/', '').split('/') |
274 domdirdirs = domdir.replace(basedir + '/', '').split('/') |
269 domdirparent = os.path.join(basedir, domdirdirs[0]) |
275 domdirparent = os.path.join(basedir, domdirdirs[0]) |
270 if basedir.count('..') or domdir.count('..'): |
276 if basedir.count('..') or domdir.count('..'): |
271 raise VMMError(_(u'Found ".." in domain directory path.'), |
277 raise VMMError(_(u'Found ".." in domain directory path.'), |
272 ERR.FOUND_DOTS_IN_PATH) |
278 FOUND_DOTS_IN_PATH) |
273 if os.path.isdir(domdirparent): |
279 if os.path.isdir(domdirparent): |
274 os.chdir(domdirparent) |
280 os.chdir(domdirparent) |
275 if os.lstat(domdirdirs[1]).st_gid != gid: |
281 if os.lstat(domdirdirs[1]).st_gid != gid: |
276 raise VMMError(_( |
282 raise VMMError(_(u'Detected group mismatch in domain ' |
277 u'Detected group mismatch in domain directory.'), |
283 u'directory.'), DOMAINDIR_GROUP_MISMATCH) |
278 ERR.DOMAINDIR_GROUP_MISMATCH) |
|
279 rmtree(domdirdirs[1], ignore_errors=True) |
284 rmtree(domdirdirs[1], ignore_errors=True) |
280 |
285 |
281 def hasWarnings(self): |
286 def hasWarnings(self): |
282 """Checks if warnings are present, returns bool.""" |
287 """Checks if warnings are present, returns bool.""" |
283 return bool(len(self.__warnings)) |
288 return bool(len(self.__warnings)) |
319 dom.save() |
324 dom.save() |
320 self.__make_domain_dir(dom) |
325 self.__make_domain_dir(dom) |
321 |
326 |
322 def domainTransport(self, domainname, transport, force=None): |
327 def domainTransport(self, domainname, transport, force=None): |
323 if force is not None and force != 'force': |
328 if force is not None and force != 'force': |
324 raise DomainError(_(u"Invalid argument: “%s”") % force, |
329 raise DomainError(_(u"Invalid argument: '%s'") % force, |
325 ERR.INVALID_OPTION) |
330 INVALID_ARGUMENT) |
326 dom = self.__getDomain(domainname) |
331 dom = self.__getDomain(domainname) |
327 trsp = Transport(self._dbh, transport=transport) |
332 trsp = Transport(self._dbh, transport=transport) |
328 if force is None: |
333 if force is None: |
329 dom.update_transport(trsp) |
334 dom.update_transport(trsp) |
330 else: |
335 else: |
331 dom.update_transport(trsp, force=True) |
336 dom.update_transport(trsp, force=True) |
332 |
337 |
333 def domainDelete(self, domainname, force=None): |
338 def domainDelete(self, domainname, force=None): |
334 if force and force not in ('deluser', 'delalias', 'delall'): |
339 if force and force not in ('deluser', 'delalias', 'delall'): |
335 raise DomainError(_(u"Invalid argument: '%s'") % force, |
340 raise DomainError(_(u"Invalid argument: '%s'") % force, |
336 ERR.INVALID_OPTION) |
341 INVALID_ARGUMENT) |
337 dom = self.__getDomain(domainname) |
342 dom = self.__getDomain(domainname) |
338 gid = dom.gid |
343 gid = dom.gid |
339 domdir = dom.directory |
344 domdir = dom.directory |
340 if self._Cfg.dget('domain.force_deletion') or force == 'delall': |
345 if self._Cfg.dget('domain.force_deletion') or force == 'delall': |
341 dom.delete(True, True) |
346 dom.delete(True, True) |
468 |
473 |
469 def user_delete(self, emailaddress, force=None): |
474 def user_delete(self, emailaddress, force=None): |
470 """Wrapper around Account.delete(...)""" |
475 """Wrapper around Account.delete(...)""" |
471 if force not in (None, 'delalias'): |
476 if force not in (None, 'delalias'): |
472 raise VMMError(_(u"Invalid argument: '%s'") % force, |
477 raise VMMError(_(u"Invalid argument: '%s'") % force, |
473 ERR.INVALID_AGUMENT) |
478 INVALID_ARGUMENT) |
474 acc = self.__getAccount(emailaddress) |
479 acc = self.__getAccount(emailaddress) |
475 if not acc: |
480 if not acc: |
476 raise VMMError(_(u"The account '%s' doesn't exist.") % |
481 raise VMMError(_(u"The account '%s' doesn't exist.") % |
477 acc.address, ERR.NO_SUCH_ACCOUNT) |
482 acc.address, NO_SUCH_ACCOUNT) |
478 uid = acc.uid |
483 uid = acc.uid |
479 gid = acc.gid |
484 gid = acc.gid |
480 dom_dir = acc.domain_directory |
485 dom_dir = acc.domain_directory |
481 acc_dir = acc.home |
486 acc_dir = acc.home |
482 acc.delete(bool(force)) |
487 acc.delete(bool(force)) |
483 if self._Cfg.dget('account.delete_directory'): |
488 if self._Cfg.dget('account.delete_directory'): |
484 try: |
489 try: |
485 self.__userDirDelete(dom_dir, uid, gid) |
490 self.__userDirDelete(dom_dir, uid, gid) |
486 except VMMError, err: |
491 except VMMError, err: |
487 if err.code in (ERR.FOUND_DOTS_IN_PATH, |
492 if err.code in (FOUND_DOTS_IN_PATH, MAILDIR_PERM_MISMATCH, |
488 ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY): |
493 NO_SUCH_DIRECTORY): |
489 warning = _(u"""\ |
494 warning = _(u"""\ |
490 The account has been successfully deleted from the database. |
495 The account has been successfully deleted from the database. |
491 But an error occurred while deleting the following directory: |
496 But an error occurred while deleting the following directory: |
492 “%(directory)s” |
497 “%(directory)s” |
493 Reason: %(reason)s""") % \ |
498 Reason: %(reason)s""") % \ |
518 |
523 |
519 def user_info(self, emailaddress, details=None): |
524 def user_info(self, emailaddress, details=None): |
520 """Wrapper around Account.get_info(...)""" |
525 """Wrapper around Account.get_info(...)""" |
521 if details not in (None, 'du', 'aliases', 'full'): |
526 if details not in (None, 'du', 'aliases', 'full'): |
522 raise VMMError(_(u"Invalid argument: '%s'") % details, |
527 raise VMMError(_(u"Invalid argument: '%s'") % details, |
523 ERR.INVALID_AGUMENT) |
528 INVALID_ARGUMENT) |
524 acc = self.__getAccount(emailaddress) |
529 acc = self.__getAccount(emailaddress) |
525 if not acc: |
530 if not acc: |
526 if not self._is_other_address(acc.address, TYPE_ACCOUNT): |
531 if not self._is_other_address(acc.address, TYPE_ACCOUNT): |
527 raise VMMError(_(u"The account '%s' doesn't exist.") % |
532 raise VMMError(_(u"The account '%s' doesn't exist.") % |
528 acc.address, ERR.NO_SUCH_ACCOUNT) |
533 acc.address, NO_SUCH_ACCOUNT) |
529 info = acc.get_info() |
534 info = acc.get_info() |
530 if self._Cfg.dget('account.disk_usage') or details in ('du', 'full'): |
535 if self._Cfg.dget('account.disk_usage') or details in ('du', 'full'): |
531 path = os.path.join(acc.home, acc.mail_location.directory) |
536 path = os.path.join(acc.home, acc.mail_location.directory) |
532 info['disk usage'] = self.__getDiskUsage(path) |
537 info['disk usage'] = self.__getDiskUsage(path) |
533 if details in (None, 'du'): |
538 if details in (None, 'du'): |
545 |
550 |
546 def user_password(self, emailaddress, password): |
551 def user_password(self, emailaddress, password): |
547 """Wrapper for Account.modify('password' ...).""" |
552 """Wrapper for Account.modify('password' ...).""" |
548 if not isinstance(password, basestring) or not password: |
553 if not isinstance(password, basestring) or not password: |
549 raise VMMError(_(u"Could not accept password: '%s'") % password, |
554 raise VMMError(_(u"Could not accept password: '%s'") % password, |
550 ERR.INVALID_AGUMENT) |
555 INVALID_ARGUMENT) |
551 acc = self.__getAccount(emailaddress) |
556 acc = self.__getAccount(emailaddress) |
552 if not acc: |
557 if not acc: |
553 raise VMMError(_(u"The account '%s' doesn't exist.") % |
558 raise VMMError(_(u"The account '%s' doesn't exist.") % |
554 acc.address, ERR.NO_SUCH_ACCOUNT) |
559 acc.address, NO_SUCH_ACCOUNT) |
555 acc.modify('password', password) |
560 acc.modify('password', password) |
556 |
561 |
557 def user_name(self, emailaddress, name): |
562 def user_name(self, emailaddress, name): |
558 """Wrapper for Account.modify('name', ...).""" |
563 """Wrapper for Account.modify('name', ...).""" |
559 if not isinstance(name, basestring) or not name: |
564 if not isinstance(name, basestring) or not name: |
560 raise VMMError(_(u"Could not accept name: '%s'") % name, |
565 raise VMMError(_(u"Could not accept name: '%s'") % name, |
561 ERR.INVALID_AGUMENT) |
566 INVALID_ARGUMENT) |
562 acc = self.__getAccount(emailaddress) |
567 acc = self.__getAccount(emailaddress) |
563 if not acc: |
568 if not acc: |
564 raise VMMError(_(u"The account '%s' doesn't exist.") % |
569 raise VMMError(_(u"The account '%s' doesn't exist.") % |
565 acc.address, ERR.NO_SUCH_ACCOUNT) |
570 acc.address, NO_SUCH_ACCOUNT) |
566 acc.modify('name', name) |
571 acc.modify('name', name) |
567 |
572 |
568 def user_transport(self, emailaddress, transport): |
573 def user_transport(self, emailaddress, transport): |
569 """Wrapper for Account.modify('transport', ...).""" |
574 """Wrapper for Account.modify('transport', ...).""" |
570 if not isinstance(transport, basestring) or not transport: |
575 if not isinstance(transport, basestring) or not transport: |
571 raise VMMError(_(u"Could not accept transport: '%s'") % transport, |
576 raise VMMError(_(u"Could not accept transport: '%s'") % transport, |
572 ERR.INVALID_AGUMENT) |
577 INVALID_ARGUMENT) |
573 acc = self.__getAccount(emailaddress) |
578 acc = self.__getAccount(emailaddress) |
574 if not acc: |
579 if not acc: |
575 raise VMMError(_(u"The account '%s' doesn't exist.") % |
580 raise VMMError(_(u"The account '%s' doesn't exist.") % |
576 acc.address, ERR.NO_SUCH_ACCOUNT) |
581 acc.address, NO_SUCH_ACCOUNT) |
577 acc.modify('transport', transport) |
582 acc.modify('transport', transport) |
578 |
583 |
579 def user_disable(self, emailaddress, service=None): |
584 def user_disable(self, emailaddress, service=None): |
580 """Wrapper for Account.disable(service)""" |
585 """Wrapper for Account.disable(service)""" |
581 if service not in (None, 'all', 'imap', 'pop3', 'smtp', 'sieve'): |
586 if service not in (None, 'all', 'imap', 'pop3', 'smtp', 'sieve'): |
582 raise VMMError(_(u"Could not accept service: '%s'") % service, |
587 raise VMMError(_(u"Could not accept service: '%s'") % service, |
583 ERR.INVALID_AGUMENT) |
588 INVALID_ARGUMENT) |
584 acc = self.__getAccount(emailaddress) |
589 acc = self.__getAccount(emailaddress) |
585 if not acc: |
590 if not acc: |
586 raise VMMError(_(u"The account '%s' doesn't exist.") % |
591 raise VMMError(_(u"The account '%s' doesn't exist.") % |
587 acc.address, ERR.NO_SUCH_ACCOUNT) |
592 acc.address, NO_SUCH_ACCOUNT) |
588 acc.disable(service) |
593 acc.disable(service) |
589 |
594 |
590 def user_enable(self, emailaddress, service=None): |
595 def user_enable(self, emailaddress, service=None): |
591 """Wrapper for Account.enable(service)""" |
596 """Wrapper for Account.enable(service)""" |
592 if service not in (None, 'all', 'imap', 'pop3', 'smtp', 'sieve'): |
597 if service not in (None, 'all', 'imap', 'pop3', 'smtp', 'sieve'): |
593 raise VMMError(_(u"Could not accept service: '%s'") % service, |
598 raise VMMError(_(u"Could not accept service: '%s'") % service, |
594 ERR.INVALID_AGUMENT) |
599 INVALID_ARGUMENT) |
595 acc = self.__getAccount(emailaddress) |
600 acc = self.__getAccount(emailaddress) |
596 if not acc: |
601 if not acc: |
597 raise VMMError(_(u"The account '%s' doesn't exist.") % |
602 raise VMMError(_(u"The account '%s' doesn't exist.") % |
598 acc.address, ERR.NO_SUCH_ACCOUNT) |
603 acc.address, NO_SUCH_ACCOUNT) |
599 acc.enable(service) |
604 acc.enable(service) |
600 |
605 |
601 def relocatedAdd(self, emailaddress, targetaddress): |
606 def relocatedAdd(self, emailaddress, targetaddress): |
602 """Creates a new `Relocated` entry in the database. If there is |
607 """Creates a new `Relocated` entry in the database. If there is |
603 already a relocated user with the given *emailaddress*, only the |
608 already a relocated user with the given *emailaddress*, only the |