69 if os.path.isfile(tmp): |
69 if os.path.isfile(tmp): |
70 self.__cfgFileName = tmp |
70 self.__cfgFileName = tmp |
71 break |
71 break |
72 if not len(self.__cfgFileName): |
72 if not len(self.__cfgFileName): |
73 raise VMMException( |
73 raise VMMException( |
74 _(u"No »vmm.cfg« found in: /root:/usr/local/etc:/etc"), |
74 _(u"No “vmm.cfg” found in: /root:/usr/local/etc:/etc"), |
75 ERR.CONF_NOFILE) |
75 ERR.CONF_NOFILE) |
76 |
76 |
77 def __chkCfgFile(self): |
77 def __chkCfgFile(self): |
78 """Checks the configuration file, returns bool""" |
78 """Checks the configuration file, returns bool""" |
79 self.__findCfgFile() |
79 self.__findCfgFile() |
80 fstat = os.stat(self.__cfgFileName) |
80 fstat = os.stat(self.__cfgFileName) |
81 fmode = int(oct(fstat.st_mode & 0777)) |
81 fmode = int(oct(fstat.st_mode & 0777)) |
82 if fmode % 100 and fstat.st_uid != fstat.st_gid \ |
82 if fmode % 100 and fstat.st_uid != fstat.st_gid \ |
83 or fmode % 10 and fstat.st_uid == fstat.st_gid: |
83 or fmode % 10 and fstat.st_uid == fstat.st_gid: |
84 raise VMMPermException(_( |
84 raise VMMPermException(_( |
85 u'fix permissions (%(perms)s) for »%(file)s«\n\ |
85 u'fix permissions (%(perms)s) for “%(file)s”\n\ |
86 `chmod 0600 %(file)s` would be great.') % {'file': |
86 `chmod 0600 %(file)s` would be great.') % {'file': |
87 self.__cfgFileName, 'perms': fmode}, ERR.CONF_WRONGPERM) |
87 self.__cfgFileName, 'perms': fmode}, ERR.CONF_WRONGPERM) |
88 else: |
88 else: |
89 return True |
89 return True |
90 |
90 |
95 os.makedirs(self.__Cfg.get('domdir', 'base'), 0771) |
95 os.makedirs(self.__Cfg.get('domdir', 'base'), 0771) |
96 os.chown(self.__Cfg.get('domdir', 'base'), 0, |
96 os.chown(self.__Cfg.get('domdir', 'base'), 0, |
97 self.__Cfg.getint('misc', 'gid_mail')) |
97 self.__Cfg.getint('misc', 'gid_mail')) |
98 os.umask(old_umask) |
98 os.umask(old_umask) |
99 elif not os.path.isdir(self.__Cfg.get('domdir', 'base')): |
99 elif not os.path.isdir(self.__Cfg.get('domdir', 'base')): |
100 raise VMMException(_(u'»%s« is not a directory.\n\ |
100 raise VMMException(_(u'“%s” is not a directory.\n\ |
101 (vmm.cfg: section "domdir", option "base")') % |
101 (vmm.cfg: section "domdir", option "base")') % |
102 self.__Cfg.get('domdir', 'base'), ERR.NO_SUCH_DIRECTORY) |
102 self.__Cfg.get('domdir', 'base'), ERR.NO_SUCH_DIRECTORY) |
103 for opt, val in self.__Cfg.items('bin'): |
103 for opt, val in self.__Cfg.items('bin'): |
104 if not os.path.exists(val): |
104 if not os.path.exists(val): |
105 raise VMMException(_(u'»%(binary)s« doesn\'t exists.\n\ |
105 raise VMMException(_(u'“%(binary)s” doesn\'t exists.\n\ |
106 (vmm.cfg: section "bin", option "%(option)s")') %{'binary': val,'option': opt}, |
106 (vmm.cfg: section "bin", option "%(option)s")') %{'binary': val,'option': opt}, |
107 ERR.NO_SUCH_BINARY) |
107 ERR.NO_SUCH_BINARY) |
108 elif not os.access(val, os.X_OK): |
108 elif not os.access(val, os.X_OK): |
109 raise VMMException(_(u'»%(binary)s« is not executable.\n\ |
109 raise VMMException(_(u'“%(binary)s” is not executable.\n\ |
110 (vmm.cfg: section "bin", option "%(option)s")') %{'binary': val,'option': opt}, |
110 (vmm.cfg: section "bin", option "%(option)s")') %{'binary': val,'option': opt}, |
111 ERR.NOT_EXECUTABLE) |
111 ERR.NOT_EXECUTABLE) |
112 |
112 |
113 def __dbConnect(self): |
113 def __dbConnect(self): |
114 """Creates a pyPgSQL.PgSQL.connection instance.""" |
114 """Creates a pyPgSQL.PgSQL.connection instance.""" |
164 domainname = VirtualMailManager.idn2ascii(domainname) |
164 domainname = VirtualMailManager.idn2ascii(domainname) |
165 if len(domainname) > 255: |
165 if len(domainname) > 255: |
166 raise VMMException(_(u'The domain name is too long.'), |
166 raise VMMException(_(u'The domain name is too long.'), |
167 ERR.DOMAIN_TOO_LONG) |
167 ERR.DOMAIN_TOO_LONG) |
168 if not re.match(RE_DOMAIN, domainname): |
168 if not re.match(RE_DOMAIN, domainname): |
169 raise VMMException(_(u'The domain name »%s« is invalid.') %\ |
169 raise VMMException(_(u'The domain name “%s” is invalid.') %\ |
170 domainname, ERR.DOMAIN_INVALID) |
170 domainname, ERR.DOMAIN_INVALID) |
171 return domainname |
171 return domainname |
172 chkDomainname = staticmethod(chkDomainname) |
172 chkDomainname = staticmethod(chkDomainname) |
173 |
173 |
174 def _exists(dbh, query): |
174 def _exists(dbh, query): |
472 if section is None: |
472 if section is None: |
473 self.__Cfg.configure(self.__cfgSections) |
473 self.__Cfg.configure(self.__cfgSections) |
474 elif section in self.__cfgSections: |
474 elif section in self.__cfgSections: |
475 self.__Cfg.configure([section]) |
475 self.__Cfg.configure([section]) |
476 else: |
476 else: |
477 raise VMMException(_(u"Invalid section: '%s'") % section, |
477 raise VMMException(_(u"Invalid section: “%s”") % section, |
478 ERR.INVALID_SECTION) |
478 ERR.INVALID_SECTION) |
479 |
479 |
480 def domainAdd(self, domainname, transport=None): |
480 def domainAdd(self, domainname, transport=None): |
481 dom = self.__getDomain(domainname, transport) |
481 dom = self.__getDomain(domainname, transport) |
482 dom.save() |
482 dom.save() |
483 self.__domDirMake(dom.getDir(), dom.getID()) |
483 self.__domDirMake(dom.getDir(), dom.getID()) |
484 |
484 |
485 def domainTransport(self, domainname, transport, force=None): |
485 def domainTransport(self, domainname, transport, force=None): |
486 if force is not None and force != 'force': |
486 if force is not None and force != 'force': |
487 raise VMMDomainException(_(u"Invalid argument: '%s'") % force, |
487 raise VMMDomainException(_(u"Invalid argument: “%s”") % force, |
488 ERR.INVALID_OPTION) |
488 ERR.INVALID_OPTION) |
489 dom = self.__getDomain(domainname, None) |
489 dom = self.__getDomain(domainname, None) |
490 if force is None: |
490 if force is None: |
491 dom.updateTransport(transport) |
491 dom.updateTransport(transport) |
492 else: |
492 else: |
493 dom.updateTransport(transport, force=True) |
493 dom.updateTransport(transport, force=True) |
494 |
494 |
495 def domainDelete(self, domainname, force=None): |
495 def domainDelete(self, domainname, force=None): |
496 if not force is None and force not in ['deluser','delalias','delall']: |
496 if not force is None and force not in ['deluser','delalias','delall']: |
497 raise VMMDomainException(_(u"Invalid argument: »%s«") % force, |
497 raise VMMDomainException(_(u"Invalid argument: “%s”") % force, |
498 ERR.INVALID_OPTION) |
498 ERR.INVALID_OPTION) |
499 dom = self.__getDomain(domainname) |
499 dom = self.__getDomain(domainname) |
500 gid = dom.getID() |
500 gid = dom.getID() |
501 domdir = dom.getDir() |
501 domdir = dom.getDir() |
502 if self.__Cfg.getboolean('misc', 'forcedel') or force == 'delall': |
502 if self.__Cfg.getboolean('misc', 'forcedel') or force == 'delall': |
511 self.__domDirDelete(domdir, gid) |
511 self.__domDirDelete(domdir, gid) |
512 |
512 |
513 def domainInfo(self, domainname, details=None): |
513 def domainInfo(self, domainname, details=None): |
514 if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full', |
514 if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full', |
515 'relocated', 'detailed']: |
515 'relocated', 'detailed']: |
516 raise VMMException(_(u'Invalid argument: »%s«') % details, |
516 raise VMMException(_(u'Invalid argument: “%s”') % details, |
517 ERR.INVALID_AGUMENT) |
517 ERR.INVALID_AGUMENT) |
518 if details == 'detailed': |
518 if details == 'detailed': |
519 details = 'full' |
519 details = 'full' |
520 self.__warnings.append(_(u'\ |
520 self.__warnings.append(_(u'\ |
521 The keyword »detailed« is deprecated and will be removed in a future release.\n\ |
521 The keyword “detailed” is deprecated and will be removed in a future release.\n\ |
522 Please use the keyword »full« to get full details.')) |
522 Please use the keyword “full” to get full details.')) |
523 dom = self.__getDomain(domainname) |
523 dom = self.__getDomain(domainname) |
524 dominfo = dom.getInfo() |
524 dominfo = dom.getInfo() |
525 if dominfo['domainname'].startswith('xn--'): |
525 if dominfo['domainname'].startswith('xn--'): |
526 dominfo['domainname'] += ' (%s)'\ |
526 dominfo['domainname'] += ' (%s)'\ |
527 % VirtualMailManager.ace2idna(dominfo['domainname']) |
527 % VirtualMailManager.ace2idna(dominfo['domainname']) |
588 domain = pattern[1:] |
588 domain = pattern[1:] |
589 elif pattern.endswith('%'): |
589 elif pattern.endswith('%'): |
590 domain = pattern[:-1] |
590 domain = pattern[:-1] |
591 if not re.match(RE_DOMAIN_SRCH, domain): |
591 if not re.match(RE_DOMAIN_SRCH, domain): |
592 raise VMMException( |
592 raise VMMException( |
593 _(u"The pattern »%s« contains invalid characters.") % |
593 _(u"The pattern “%s” contains invalid characters.") % |
594 pattern, ERR.DOMAIN_INVALID) |
594 pattern, ERR.DOMAIN_INVALID) |
595 self.__dbConnect() |
595 self.__dbConnect() |
596 return search(self.__dbh, pattern=pattern, like=like) |
596 return search(self.__dbh, pattern=pattern, like=like) |
597 |
597 |
598 def userAdd(self, emailaddress, password): |
598 def userAdd(self, emailaddress, password): |
614 gid = self.__getDomain(alias._dest._domainname).getID() |
614 gid = self.__getDomain(alias._dest._domainname).getID() |
615 if gid > 0 and not VirtualMailManager.accountExists(self.__dbh, |
615 if gid > 0 and not VirtualMailManager.accountExists(self.__dbh, |
616 alias._dest) and not VirtualMailManager.aliasExists(self.__dbh, |
616 alias._dest) and not VirtualMailManager.aliasExists(self.__dbh, |
617 alias._dest): |
617 alias._dest): |
618 self.__warnings.append( |
618 self.__warnings.append( |
619 _(u"The destination account/alias »%s« doesn't exists yet.")%\ |
619 _(u"The destination account/alias “%s” doesn't exists yet.")%\ |
620 alias._dest) |
620 alias._dest) |
621 |
621 |
622 def userDelete(self, emailaddress, force=None): |
622 def userDelete(self, emailaddress, force=None): |
623 if force not in [None, 'delalias']: |
623 if force not in [None, 'delalias']: |
624 raise VMMException(_(u"Invalid argument: »%s«") % force, |
624 raise VMMException(_(u"Invalid argument: “%s”") % force, |
625 ERR.INVALID_AGUMENT) |
625 ERR.INVALID_AGUMENT) |
626 acc = self.__getAccount(emailaddress) |
626 acc = self.__getAccount(emailaddress) |
627 uid = acc.getUID() |
627 uid = acc.getUID() |
628 gid = acc.getGID() |
628 gid = acc.getGID() |
629 acc.delete(force) |
629 acc.delete(force) |
634 if e.code() in [ERR.FOUND_DOTS_IN_PATH, |
634 if e.code() in [ERR.FOUND_DOTS_IN_PATH, |
635 ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]: |
635 ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]: |
636 warning = _(u"""\ |
636 warning = _(u"""\ |
637 The account has been successfully deleted from the database. |
637 The account has been successfully deleted from the database. |
638 But an error occurred while deleting the following directory: |
638 But an error occurred while deleting the following directory: |
639 »%(directory)s« |
639 “%(directory)s” |
640 Reason: %(raeson)s""") % {'directory': acc.getDir('home'),'raeson': e.msg()} |
640 Reason: %(raeson)s""") % {'directory': acc.getDir('home'),'raeson': e.msg()} |
641 self.__warnings.append(warning) |
641 self.__warnings.append(warning) |
642 else: |
642 else: |
643 raise e |
643 raise e |
644 |
644 |
650 alias = self.__getAlias(aliasaddress, targetaddress) |
650 alias = self.__getAlias(aliasaddress, targetaddress) |
651 alias.delete() |
651 alias.delete() |
652 |
652 |
653 def userInfo(self, emailaddress, details=None): |
653 def userInfo(self, emailaddress, details=None): |
654 if details not in [None, 'du', 'aliases', 'full']: |
654 if details not in [None, 'du', 'aliases', 'full']: |
655 raise VMMException(_(u'Invalid argument: »%s«') % details, |
655 raise VMMException(_(u'Invalid argument: “%s”') % details, |
656 ERR.INVALID_AGUMENT) |
656 ERR.INVALID_AGUMENT) |
657 acc = self.__getAccount(emailaddress) |
657 acc = self.__getAccount(emailaddress) |
658 info = acc.getInfo(self.__Cfg.getint('misc', 'dovecotvers')) |
658 info = acc.getInfo(self.__Cfg.getint('misc', 'dovecotvers')) |
659 if self.__Cfg.getboolean('maildir', 'diskusage')\ |
659 if self.__Cfg.getboolean('maildir', 'diskusage')\ |
660 or details in ['du', 'full']: |
660 or details in ['du', 'full']: |
688 |
688 |
689 def userDisable(self, emailaddress, service=None): |
689 def userDisable(self, emailaddress, service=None): |
690 if service == 'managesieve': |
690 if service == 'managesieve': |
691 service = 'sieve' |
691 service = 'sieve' |
692 self.__warnings.append(_(u'\ |
692 self.__warnings.append(_(u'\ |
693 The service name »managesieve« is deprecated and will be removed\n\ |
693 The service name “managesieve” is deprecated and will be removed\n\ |
694 in a future release.\n\ |
694 in a future release.\n\ |
695 Please use the service name »sieve« instead.')) |
695 Please use the service name “sieve” instead.')) |
696 acc = self.__getAccount(emailaddress) |
696 acc = self.__getAccount(emailaddress) |
697 acc.disable(self.__Cfg.getint('misc', 'dovecotvers'), service) |
697 acc.disable(self.__Cfg.getint('misc', 'dovecotvers'), service) |
698 |
698 |
699 def userEnable(self, emailaddress, service=None): |
699 def userEnable(self, emailaddress, service=None): |
700 if service == 'managesieve': |
700 if service == 'managesieve': |
701 service = 'sieve' |
701 service = 'sieve' |
702 self.__warnings.append(_(u'\ |
702 self.__warnings.append(_(u'\ |
703 The service name »managesieve« is deprecated and will be removed\n\ |
703 The service name “managesieve” is deprecated and will be removed\n\ |
704 in a future release.\n\ |
704 in a future release.\n\ |
705 Please use the service name »sieve« instead.')) |
705 Please use the service name “sieve” instead.')) |
706 acc = self.__getAccount(emailaddress) |
706 acc = self.__getAccount(emailaddress) |
707 acc.enable(self.__Cfg.getint('misc', 'dovecotvers'), service) |
707 acc.enable(self.__Cfg.getint('misc', 'dovecotvers'), service) |
708 |
708 |
709 def relocatedAdd(self, emailaddress, targetaddress): |
709 def relocatedAdd(self, emailaddress, targetaddress): |
710 relocated = self.__getRelocated(emailaddress, targetaddress) |
710 relocated = self.__getRelocated(emailaddress, targetaddress) |