63 self.__chkenv() |
63 self.__chkenv() |
64 |
64 |
65 def __chkCfgFile(self): |
65 def __chkCfgFile(self): |
66 """Checks the configuration file, returns bool""" |
66 """Checks the configuration file, returns bool""" |
67 if not os.path.isfile(self.__cfgFileName): |
67 if not os.path.isfile(self.__cfgFileName): |
68 raise VMMException((_(u"The file »%s« does not exists.") % |
68 raise VMMException(_(u"The file »%s« does not exists.") % |
69 self.__cfgFileName, ERR.CONF_NOFILE)) |
69 self.__cfgFileName, ERR.CONF_NOFILE) |
70 fstat = os.stat(self.__cfgFileName) |
70 fstat = os.stat(self.__cfgFileName) |
71 try: |
71 fmode = int(oct(fstat.st_mode & 0777)) |
72 fmode = self.__getFileMode() |
|
73 except: |
|
74 raise |
|
75 if fmode % 100 and fstat.st_uid != fstat.st_gid \ |
72 if fmode % 100 and fstat.st_uid != fstat.st_gid \ |
76 or fmode % 10 and fstat.st_uid == fstat.st_gid: |
73 or fmode % 10 and fstat.st_uid == fstat.st_gid: |
77 raise VMMPermException((self.__permWarnMsg, ERR.CONF_ERROR)) |
74 raise VMMPermException(self.__permWarnMsg, ERR.CONF_ERROR) |
78 else: |
75 else: |
79 return True |
76 return True |
80 |
77 |
81 def __chkenv(self): |
78 def __chkenv(self): |
82 """""" |
79 """""" |
85 os.makedirs(self.__Cfg.get('domdir', 'base'), 0771) |
82 os.makedirs(self.__Cfg.get('domdir', 'base'), 0771) |
86 os.chown(self.__Cfg.get('domdir', 'base'), 0, |
83 os.chown(self.__Cfg.get('domdir', 'base'), 0, |
87 self.__Cfg.getint('misc', 'gid_mail')) |
84 self.__Cfg.getint('misc', 'gid_mail')) |
88 os.umask(old_umask) |
85 os.umask(old_umask) |
89 elif not os.path.isdir(self.__Cfg.get('domdir', 'base')): |
86 elif not os.path.isdir(self.__Cfg.get('domdir', 'base')): |
90 raise VMMException((_(u'»%s« is not a directory.\n\ |
87 raise VMMException(_(u'»%s« is not a directory.\n\ |
91 (vmm.cfg: section "domdir", option "base")') % |
88 (vmm.cfg: section "domdir", option "base")') % |
92 self.__Cfg.get('domdir', 'base'), ERR.NO_SUCH_DIRECTORY)) |
89 self.__Cfg.get('domdir', 'base'), ERR.NO_SUCH_DIRECTORY) |
93 for opt, val in self.__Cfg.items('bin'): |
90 for opt, val in self.__Cfg.items('bin'): |
94 if not os.path.exists(val): |
91 if not os.path.exists(val): |
95 raise VMMException((_(u'»%s« doesn\'t exists.\n\ |
92 raise VMMException(_(u'»%s« doesn\'t exists.\n\ |
96 (vmm.cfg: section "bin", option "%s")') % (val, opt), ERR.NO_SUCH_BINARY)) |
93 (vmm.cfg: section "bin", option "%s")') % |
|
94 (val, opt), ERR.NO_SUCH_BINARY) |
97 elif not os.access(val, os.X_OK): |
95 elif not os.access(val, os.X_OK): |
98 raise VMMException((_(u'»%s« is not executable.\n\ |
96 raise VMMException(_(u'»%s« is not executable.\n\ |
99 (vmm.cfg: section "bin", option "%s")') % (val, opt), ERR.NOT_EXECUTABLE)) |
97 (vmm.cfg: section "bin", option "%s")') % |
100 |
98 (val, opt), ERR.NOT_EXECUTABLE) |
101 def __getFileMode(self): |
|
102 """Determines the file access mode from file __cfgFileName, |
|
103 returns int. |
|
104 """ |
|
105 try: |
|
106 return int(oct(os.stat(self.__cfgFileName).st_mode & 0777)) |
|
107 except: |
|
108 raise |
|
109 |
99 |
110 def __dbConnect(self): |
100 def __dbConnect(self): |
111 """Creates a pyPgSQL.PgSQL.connection instance.""" |
101 """Creates a pyPgSQL.PgSQL.connection instance.""" |
112 try: |
102 try: |
113 self.__dbh = PgSQL.connect( |
103 self.__dbh = PgSQL.connect( |
118 client_encoding='utf8', unicode_results=True) |
108 client_encoding='utf8', unicode_results=True) |
119 dbc = self.__dbh.cursor() |
109 dbc = self.__dbh.cursor() |
120 dbc.execute("SET NAMES 'UTF8'") |
110 dbc.execute("SET NAMES 'UTF8'") |
121 dbc.close() |
111 dbc.close() |
122 except PgSQL.libpq.DatabaseError, e: |
112 except PgSQL.libpq.DatabaseError, e: |
123 raise VMMException((str(e), ERR.DATABASE_ERROR)) |
113 raise VMMException(str(e), ERR.DATABASE_ERROR) |
124 |
114 |
125 def chkLocalpart(localpart): |
115 def chkLocalpart(localpart): |
126 """Validates the local part of an e-mail address. |
116 """Validates the local part of an e-mail address. |
127 |
117 |
128 Keyword arguments: |
118 Keyword arguments: |
129 localpart -- the e-mail address that should be validated (str) |
119 localpart -- the e-mail address that should be validated (str) |
130 """ |
120 """ |
131 if len(localpart) < 1: |
121 if len(localpart) < 1: |
132 raise VMMException((_(u'No localpart specified.'), |
122 raise VMMException(_(u'No localpart specified.'), |
133 ERR.LOCALPART_INVALID)) |
123 ERR.LOCALPART_INVALID) |
134 if len(localpart) > 64: |
124 if len(localpart) > 64: |
135 raise VMMException((_(u'The local part »%s« is too long') % |
125 raise VMMException(_(u'The local part »%s« is too long') % |
136 localpart, ERR.LOCALPART_TOO_LONG)) |
126 localpart, ERR.LOCALPART_TOO_LONG) |
137 ic = re.compile(RE_LOCALPART).findall(localpart) |
127 ic = re.compile(RE_LOCALPART).findall(localpart) |
138 if len(ic): |
128 if len(ic): |
139 ichrs = '' |
129 ichrs = '' |
140 for c in set(ic): |
130 for c in set(ic): |
141 ichrs += u"»%s« " % c |
131 ichrs += u"»%s« " % c |
142 raise VMMException(( |
132 raise VMMException( |
143 _(u"The local part »%s« contains invalid characters: %s") % |
133 _(u"The local part »%s« contains invalid characters: %s") % |
144 (localpart, ichrs), ERR.LOCALPART_INVALID)) |
134 (localpart, ichrs), ERR.LOCALPART_INVALID) |
145 return localpart |
135 return localpart |
146 chkLocalpart = staticmethod(chkLocalpart) |
136 chkLocalpart = staticmethod(chkLocalpart) |
147 |
137 |
148 def idn2ascii(domainname): |
138 def idn2ascii(domainname): |
149 """Converts an idn domainname in punycode. |
139 """Converts an idn domainname in punycode. |
182 """ |
172 """ |
183 re.compile(RE_ASCII_CHARS) |
173 re.compile(RE_ASCII_CHARS) |
184 if not re.match(RE_ASCII_CHARS, domainname): |
174 if not re.match(RE_ASCII_CHARS, domainname): |
185 domainname = VirtualMailManager.idn2ascii(domainname) |
175 domainname = VirtualMailManager.idn2ascii(domainname) |
186 if len(domainname) > 255: |
176 if len(domainname) > 255: |
187 raise VMMException((_(u'The domain name is too long.'), |
177 raise VMMException(_(u'The domain name is too long.'), |
188 ERR.DOMAIN_TOO_LONG)) |
178 ERR.DOMAIN_TOO_LONG) |
189 re.compile(RE_DOMAIN) |
179 re.compile(RE_DOMAIN) |
190 if not re.match(RE_DOMAIN, domainname): |
180 if not re.match(RE_DOMAIN, domainname): |
191 raise VMMException((_(u'The domain name is invalid.'), |
181 raise VMMException(_(u'The domain name is invalid.'), |
192 ERR.DOMAIN_INVALID)) |
182 ERR.DOMAIN_INVALID) |
193 return domainname |
183 return domainname |
194 chkDomainname = staticmethod(chkDomainname) |
184 chkDomainname = staticmethod(chkDomainname) |
195 |
185 |
196 def chkEmailAddress(address): |
186 def chkEmailAddress(address): |
197 try: |
187 try: |
198 localpart, domain = address.split('@') |
188 localpart, domain = address.split('@') |
199 except ValueError: |
189 except ValueError: |
200 raise VMMException((_(u"Missing '@' sign in e-mail address »%s«.") % |
190 raise VMMException(_(u"Missing '@' sign in e-mail address »%s«.") % |
201 address, ERR.INVALID_ADDRESS)) |
191 address, ERR.INVALID_ADDRESS) |
202 except AttributeError: |
192 except AttributeError: |
203 raise VMMException((_(u"»%s« looks not like an e-mail address.") % |
193 raise VMMException(_(u"»%s« looks not like an e-mail address.") % |
204 address, ERR.INVALID_ADDRESS)) |
194 address, ERR.INVALID_ADDRESS) |
205 domain = VirtualMailManager.chkDomainname(domain) |
195 domain = VirtualMailManager.chkDomainname(domain) |
206 localpart = VirtualMailManager.chkLocalpart(localpart) |
196 localpart = VirtualMailManager.chkLocalpart(localpart) |
207 return '%s@%s' % (localpart, domain) |
197 return '%s@%s' % (localpart, domain) |
208 chkEmailAddress = staticmethod(chkEmailAddress) |
198 chkEmailAddress = staticmethod(chkEmailAddress) |
209 |
199 |
317 |
307 |
318 def __maildirdelete(self, domdir, uid, gid): |
308 def __maildirdelete(self, domdir, uid, gid): |
319 if uid > 0 and gid > 0: |
309 if uid > 0 and gid > 0: |
320 maildir = '%s' % uid |
310 maildir = '%s' % uid |
321 if maildir.count('..') or domdir.count('..'): |
311 if maildir.count('..') or domdir.count('..'): |
322 raise VMMException((_(u'Found ".." in maildir path.'), |
312 raise VMMException(_(u'Found ".." in maildir path.'), |
323 ERR.FOUND_DOTS_IN_PATH)) |
313 ERR.FOUND_DOTS_IN_PATH) |
324 if os.path.isdir(domdir): |
314 if os.path.isdir(domdir): |
325 os.chdir(domdir) |
315 os.chdir(domdir) |
326 if os.path.isdir(maildir): |
316 if os.path.isdir(maildir): |
327 mdstat = os.stat(maildir) |
317 mdstat = os.stat(maildir) |
328 if (mdstat.st_uid, mdstat.st_gid) != (uid, gid): |
318 if (mdstat.st_uid, mdstat.st_gid) != (uid, gid): |
329 raise VMMException(( |
319 raise VMMException( |
330 _(u'Owner/group mismatch in maildir detected.'), |
320 _(u'Owner/group mismatch in maildir detected.'), |
331 ERR.MAILDIR_PERM_MISMATCH)) |
321 ERR.MAILDIR_PERM_MISMATCH) |
332 rmtree(maildir, ignore_errors=True) |
322 rmtree(maildir, ignore_errors=True) |
333 else: |
323 else: |
334 raise VMMException((_(u"No such directory: %s/%s") % |
324 raise VMMException(_(u"No such directory: %s/%s") % |
335 (domdir, uid), ERR.NO_SUCH_DIRECTORY)) |
325 (domdir, uid), ERR.NO_SUCH_DIRECTORY) |
336 |
326 |
337 def __domdirdelete(self, domdir, gid): |
327 def __domdirdelete(self, domdir, gid): |
338 if gid > 0: |
328 if gid > 0: |
339 if not self.__isdir(domdir): |
329 if not self.__isdir(domdir): |
340 return |
330 return |
341 basedir = '%s' % self.__Cfg.get('domdir', 'base') |
331 basedir = '%s' % self.__Cfg.get('domdir', 'base') |
342 domdirdirs = domdir.replace(basedir+'/', '').split('/') |
332 domdirdirs = domdir.replace(basedir+'/', '').split('/') |
343 if basedir.count('..') or domdir.count('..'): |
333 if basedir.count('..') or domdir.count('..'): |
344 raise VMMException( |
334 raise VMMException( |
345 (_(u'FATAL: ".." in domain directory path detected.'), |
335 _(u'FATAL: ".." in domain directory path detected.'), |
346 ERR.FOUND_DOTS_IN_PATH)) |
336 ERR.FOUND_DOTS_IN_PATH) |
347 if os.path.isdir('%s/%s' % (basedir, domdirdirs[0])): |
337 if os.path.isdir('%s/%s' % (basedir, domdirdirs[0])): |
348 os.chdir('%s/%s' % (basedir, domdirdirs[0])) |
338 os.chdir('%s/%s' % (basedir, domdirdirs[0])) |
349 if os.lstat(domdirdirs[1]).st_gid != gid: |
339 if os.lstat(domdirdirs[1]).st_gid != gid: |
350 raise VMMException( |
340 raise VMMException( |
351 (_(u'FATAL: group mismatch in domain directory detected'), |
341 _(u'FATAL: group mismatch in domain directory detected'), |
352 ERR.DOMAINDIR_GROUP_MISMATCH)) |
342 ERR.DOMAINDIR_GROUP_MISMATCH) |
353 rmtree(domdirdirs[1], ignore_errors=True) |
343 rmtree(domdirdirs[1], ignore_errors=True) |
354 |
344 |
355 def __getSalt(self): |
345 def __getSalt(self): |
356 from random import choice |
346 from random import choice |
357 salt = None |
347 salt = None |
434 """Checks if vmm is configured, returns bool""" |
424 """Checks if vmm is configured, returns bool""" |
435 try: |
425 try: |
436 return self.__Cfg.getboolean('config', 'done') |
426 return self.__Cfg.getboolean('config', 'done') |
437 except ValueError, e: |
427 except ValueError, e: |
438 raise VMMConfigException(_(u"""Configurtion error: "%s" |
428 raise VMMConfigException(_(u"""Configurtion error: "%s" |
439 (in section "connfig", option "done") see also: vmm.cfg(5)\n""") % |
429 (in section "connfig", option "done") see also: vmm.cfg(5)\n""") % str(e), |
440 str(e)) |
430 ERR.CONF_ERROR) |
441 |
431 |
442 def configure(self, section=None): |
432 def configure(self, section=None): |
443 """Starts interactive configuration. |
433 """Starts interactive configuration. |
444 |
434 |
445 Configures in interactive mode options in the given section. |
435 Configures in interactive mode options in the given section. |
448 |
438 |
449 Keyword arguments: |
439 Keyword arguments: |
450 section -- the section to configure (default None): |
440 section -- the section to configure (default None): |
451 'database', 'maildir', 'bin' or 'misc' |
441 'database', 'maildir', 'bin' or 'misc' |
452 """ |
442 """ |
453 try: |
443 if section is None: |
454 if not section: |
444 self.__Cfg.configure(self.__cfgSections) |
455 self.__Cfg.configure(self.__cfgSections) |
445 elif section in self.__cfgSections: |
456 elif section not in self.__cfgSections: |
446 self.__Cfg.configure([section]) |
457 raise VMMException((_(u"Invalid section: '%s'") % section, |
447 else: |
458 ERR.INVALID_SECTION)) |
448 raise VMMException(_(u"Invalid section: '%s'") % section, |
459 else: |
449 ERR.INVALID_SECTION) |
460 self.__Cfg.configure([section]) |
|
461 except: |
|
462 raise |
|
463 |
450 |
464 def domain_add(self, domainname, transport=None): |
451 def domain_add(self, domainname, transport=None): |
465 dom = self.__getDomain(domainname, transport) |
452 dom = self.__getDomain(domainname, transport) |
466 dom.save() |
453 dom.save() |
467 self.__domdirmake(dom.getDir(), dom.getID()) |
454 self.__domdirmake(dom.getDir(), dom.getID()) |
468 |
455 |
469 def domain_transport(self, domainname, transport, force=None): |
456 def domain_transport(self, domainname, transport, force=None): |
470 if force is not None and force != 'force': |
457 if force is not None and force != 'force': |
471 raise VMMDomainException((_(u"Invalid argument: '%s'") % force, |
458 raise VMMDomainException(_(u"Invalid argument: '%s'") % force, |
472 ERR.INVALID_OPTION)) |
459 ERR.INVALID_OPTION) |
473 dom = self.__getDomain(domainname, None) |
460 dom = self.__getDomain(domainname, None) |
474 if force is None: |
461 if force is None: |
475 dom.updateTransport(transport) |
462 dom.updateTransport(transport) |
476 else: |
463 else: |
477 dom.updateTransport(transport, force=True) |
464 dom.updateTransport(transport, force=True) |
478 |
465 |
479 def domain_delete(self, domainname, force=None): |
466 def domain_delete(self, domainname, force=None): |
480 if not force is None and force not in ['deluser','delalias','delall']: |
467 if not force is None and force not in ['deluser','delalias','delall']: |
481 raise VMMDomainException((_(u"Invalid argument: »%s«") % force, |
468 raise VMMDomainException(_(u"Invalid argument: »%s«") % force, |
482 ERR.INVALID_OPTION)) |
469 ERR.INVALID_OPTION) |
483 dom = self.__getDomain(domainname) |
470 dom = self.__getDomain(domainname) |
484 gid = dom.getID() |
471 gid = dom.getID() |
485 domdir = dom.getDir() |
472 domdir = dom.getDir() |
486 if self.__Cfg.getboolean('misc', 'forcedel') or force == 'delall': |
473 if self.__Cfg.getboolean('misc', 'forcedel') or force == 'delall': |
487 dom.delete(True, True) |
474 dom.delete(True, True) |
506 return dominfo |
493 return dominfo |
507 elif detailed == 'detailed': |
494 elif detailed == 'detailed': |
508 return (dominfo, dom.getAliaseNames(), dom.getAccounts(), |
495 return (dominfo, dom.getAliaseNames(), dom.getAccounts(), |
509 dom.getAliases()) |
496 dom.getAliases()) |
510 else: |
497 else: |
511 raise VMMDomainException((_(u'Invalid argument: »%s«') % detailed, |
498 raise VMMDomainException(_(u'Invalid argument: »%s«') % detailed, |
512 ERR.INVALID_OPTION)) |
499 ERR.INVALID_OPTION) |
513 |
500 |
514 def domain_alias_add(self, aliasname, domainname): |
501 def domain_alias_add(self, aliasname, domainname): |
515 """Adds an alias name to the domain. |
502 """Adds an alias name to the domain. |
516 |
503 |
517 Keyword arguments: |
504 Keyword arguments: |
547 domain = pattern[1:] |
534 domain = pattern[1:] |
548 elif pattern.endswith('%'): |
535 elif pattern.endswith('%'): |
549 domain = pattern[:-1] |
536 domain = pattern[:-1] |
550 re.compile(RE_DOMAIN_SRCH) |
537 re.compile(RE_DOMAIN_SRCH) |
551 if not re.match(RE_DOMAIN_SRCH, domain): |
538 if not re.match(RE_DOMAIN_SRCH, domain): |
552 raise VMMException(( |
539 raise VMMException( |
553 _(u"The pattern '%s' contains invalid characters.") % |
540 _(u"The pattern '%s' contains invalid characters.") % |
554 pattern, ERR.DOMAIN_INVALID)) |
541 pattern, ERR.DOMAIN_INVALID) |
555 else: |
542 else: |
556 pattern = VirtualMailManager.chkDomainname(pattern) |
543 pattern = VirtualMailManager.chkDomainname(pattern) |
557 # XXX chk by domain if not like |
544 # XXX chk by domain if not like |
558 self.__dbConnect() |
545 self.__dbConnect() |
559 return search(self.__dbh, pattern=pattern, like=like) |
546 return search(self.__dbh, pattern=pattern, like=like) |
580 gid = acc.getGID() |
567 gid = acc.getGID() |
581 acc.delete() |
568 acc.delete() |
582 if self.__Cfg.getboolean('maildir', 'delete'): |
569 if self.__Cfg.getboolean('maildir', 'delete'): |
583 try: |
570 try: |
584 self.__maildirdelete(acc.getDir('domain'), uid, gid) |
571 self.__maildirdelete(acc.getDir('domain'), uid, gid) |
585 except (VMMException), e: |
572 except VMMException, e: |
586 if e[0][1] in [ERR.FOUND_DOTS_IN_PATH, |
573 if e.code() in [ERR.FOUND_DOTS_IN_PATH, |
587 ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]: |
574 ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]: |
588 warning = _(u"""\ |
575 warning = _(u"""\ |
589 The account has been successfully deleted from the database. |
576 The account has been successfully deleted from the database. |
590 But an error occurred while deleting the following directory: |
577 But an error occurred while deleting the following directory: |
591 »%s« |
578 »%s« |
592 Reason: %s""") % (acc.getDir('home'), e[0][0]) |
579 Reason: %s""") % (acc.getDir('home'), e.msg()) |
593 self.__warnings.append(warning) |
580 self.__warnings.append(warning) |
594 else: |
581 else: |
595 raise e |
582 raise e |
596 |
583 |
597 def alias_info(self, aliasaddress): |
584 def alias_info(self, aliasaddress): |
615 return getAccountByID(uid, self.__dbh) |
602 return getAccountByID(uid, self.__dbh) |
616 |
603 |
617 def user_password(self, emailaddress, password): |
604 def user_password(self, emailaddress, password): |
618 acc = self.__getAccount(emailaddress) |
605 acc = self.__getAccount(emailaddress) |
619 if acc.getUID() == 0: |
606 if acc.getUID() == 0: |
620 raise VMMException((_(u"Account doesn't exists"), |
607 raise VMMException(_(u"Account doesn't exists"), |
621 ERR.NO_SUCH_ACCOUNT)) |
608 ERR.NO_SUCH_ACCOUNT) |
622 if password is None: |
609 if password is None: |
623 password = self._readpass() |
610 password = self._readpass() |
624 acc.modify('password', self.__pwhash(password)) |
611 acc.modify('password', self.__pwhash(password)) |
625 |
612 |
626 def user_name(self, emailaddress, name): |
613 def user_name(self, emailaddress, name): |