38 re.compile(RE_DOMAIN) |
39 re.compile(RE_DOMAIN) |
39 |
40 |
40 ENCODING_IN = sys.getfilesystemencoding() |
41 ENCODING_IN = sys.getfilesystemencoding() |
41 ENCODING_OUT = sys.stdout.encoding or sys.getfilesystemencoding() |
42 ENCODING_OUT = sys.stdout.encoding or sys.getfilesystemencoding() |
42 |
43 |
|
44 gettext.bindtextdomain('vmm', '/usr/local/share/locale') |
|
45 gettext.textdomain('vmm') |
|
46 _ = gettext.gettext |
|
47 |
43 class VirtualMailManager: |
48 class VirtualMailManager: |
44 """The main class for vmm""" |
49 """The main class for vmm""" |
45 def __init__(self): |
50 def __init__(self): |
46 """Creates a new VirtualMailManager instance. |
51 """Creates a new VirtualMailManager instance. |
47 Throws a VMMNotRootException if your uid is greater 0. |
52 Throws a VMMNotRootException if your uid is greater 0. |
48 """ |
53 """ |
49 self.__cfgFileName = '/usr/local/etc/vmm.cfg' |
54 self.__cfgFileName = '/usr/local/etc/vmm.cfg' |
50 self.__permWarnMsg = "fix permissions for '%s'\n`chmod 0600 %s` would\ |
55 self.__permWarnMsg = _("fix permissions for '%s'\n`chmod 0600 %s` would\ |
51 be great." % (self.__cfgFileName, self.__cfgFileName) |
56 be great.") % (self.__cfgFileName, self.__cfgFileName) |
52 self.__warnings = [] |
57 self.__warnings = [] |
53 self.__Cfg = None |
58 self.__Cfg = None |
54 self.__dbh = None |
59 self.__dbh = None |
55 |
60 |
56 if os.geteuid(): |
61 if os.geteuid(): |
57 raise VMMNotRootException(("You are not root.\n\tGood bye!\n", |
62 raise VMMNotRootException((_("You are not root.\n\tGood bye!\n"), |
58 ERR.CONF_NOPERM)) |
63 ERR.CONF_NOPERM)) |
59 if self.__chkCfgFile(): |
64 if self.__chkCfgFile(): |
60 self.__Cfg = Cfg(self.__cfgFileName) |
65 self.__Cfg = Cfg(self.__cfgFileName) |
61 self.__Cfg.load() |
66 self.__Cfg.load() |
62 self.__Cfg.check() |
67 self.__Cfg.check() |
66 self.__chkenv() |
71 self.__chkenv() |
67 |
72 |
68 def __chkCfgFile(self): |
73 def __chkCfgFile(self): |
69 """Checks the configuration file, returns bool""" |
74 """Checks the configuration file, returns bool""" |
70 if not os.path.isfile(self.__cfgFileName): |
75 if not os.path.isfile(self.__cfgFileName): |
71 raise VMMException(("The file »%s« does not exists." % |
76 raise VMMException((_("The file »%s« does not exists.") % |
72 self.__cfgFileName, ERR.CONF_NOFILE)) |
77 self.__cfgFileName, ERR.CONF_NOFILE)) |
73 fstat = os.stat(self.__cfgFileName) |
78 fstat = os.stat(self.__cfgFileName) |
74 try: |
79 try: |
75 fmode = self.__getFileMode() |
80 fmode = self.__getFileMode() |
76 except: |
81 except: |
88 os.makedirs(self.__Cfg.get('domdir', 'base'), 0771) |
93 os.makedirs(self.__Cfg.get('domdir', 'base'), 0771) |
89 os.chown(self.__Cfg.get('domdir', 'base'), 0, |
94 os.chown(self.__Cfg.get('domdir', 'base'), 0, |
90 self.__Cfg.getint('misc', 'gid_mail')) |
95 self.__Cfg.getint('misc', 'gid_mail')) |
91 os.umask(old_umask) |
96 os.umask(old_umask) |
92 elif not os.path.isdir(self.__Cfg.get('domdir', 'base')): |
97 elif not os.path.isdir(self.__Cfg.get('domdir', 'base')): |
93 raise VMMException(('%s is not a directory' % |
98 raise VMMException((_('%s is not a directory') % |
94 self.__Cfg.get('domdir', 'base'), ERR.NO_SUCH_DIRECTORY)) |
99 self.__Cfg.get('domdir', 'base'), ERR.NO_SUCH_DIRECTORY)) |
95 for opt, val in self.__Cfg.items('bin'): |
100 for opt, val in self.__Cfg.items('bin'): |
96 if not os.path.exists(val): |
101 if not os.path.exists(val): |
97 raise VMMException(("%s doesn't exists." % val, |
102 raise VMMException((_("%s doesn't exists.") % val, |
98 ERR.NO_SUCH_BINARY)) |
103 ERR.NO_SUCH_BINARY)) |
99 elif not os.access(val, os.X_OK): |
104 elif not os.access(val, os.X_OK): |
100 raise VMMException(("%s is not executable." % val, |
105 raise VMMException((_("%s is not executable.") % val, |
101 ERR.NOT_EXECUTABLE)) |
106 ERR.NOT_EXECUTABLE)) |
102 |
107 |
103 def __getFileMode(self): |
108 def __getFileMode(self): |
104 """Determines the file access mode from file __cfgFileName, |
109 """Determines the file access mode from file __cfgFileName, |
105 returns int. |
110 returns int. |
129 |
134 |
130 Keyword arguments: |
135 Keyword arguments: |
131 localpart -- the e-mail address that should be validated (str) |
136 localpart -- the e-mail address that should be validated (str) |
132 """ |
137 """ |
133 if len(localpart) > 64: |
138 if len(localpart) > 64: |
134 raise VMMException(('The local part is too long', |
139 raise VMMException((_('The local part is too long'), |
135 ERR.LOCALPART_TOO_LONG)) |
140 ERR.LOCALPART_TOO_LONG)) |
136 if re.compile(RE_LOCALPART).search(localpart): |
141 if re.compile(RE_LOCALPART).search(localpart): |
137 raise VMMException(( |
142 raise VMMException(( |
138 'The local part »%s« contains invalid characters.' % localpart, |
143 _('The local part »%s« contains invalid characters.') % |
139 ERR.LOCALPART_INVALID)) |
144 localpart, ERR.LOCALPART_INVALID)) |
140 return localpart |
145 return localpart |
141 |
146 |
142 def __idn2ascii(self, domainname): |
147 def __idn2ascii(self, domainname): |
143 """Converts an idn domainname in punycode. |
148 """Converts an idn domainname in punycode. |
144 |
149 |
172 domainname -- the domain name that should be validated |
177 domainname -- the domain name that should be validated |
173 """ |
178 """ |
174 if not re.match(RE_ASCII_CHARS, domainname): |
179 if not re.match(RE_ASCII_CHARS, domainname): |
175 domainname = self.__idn2ascii(domainname) |
180 domainname = self.__idn2ascii(domainname) |
176 if len(domainname) > 255: |
181 if len(domainname) > 255: |
177 raise VMMException(('The domain name is too long.', |
182 raise VMMException((_('The domain name is too long.'), |
178 ERR.DOMAIN_TOO_LONG)) |
183 ERR.DOMAIN_TOO_LONG)) |
179 if not re.match(RE_DOMAIN, domainname): |
184 if not re.match(RE_DOMAIN, domainname): |
180 raise VMMException(('The domain name is invalid.', |
185 raise VMMException((_('The domain name is invalid.'), |
181 ERR.DOMAIN_INVALID)) |
186 ERR.DOMAIN_INVALID)) |
182 return domainname |
187 return domainname |
183 |
188 |
184 def __chkEmailAddress(self, address): |
189 def __chkEmailAddress(self, address): |
185 try: |
190 try: |
186 localpart, domain = address.split('@') |
191 localpart, domain = address.split('@') |
187 except ValueError: |
192 except ValueError: |
188 raise VMMException(("Missing '@' sign in e-mail address »%s«." % |
193 raise VMMException((_("Missing '@' sign in e-mail address »%s«.") % |
189 address, ERR.INVALID_ADDRESS)) |
194 address, ERR.INVALID_ADDRESS)) |
190 except AttributeError: |
195 except AttributeError: |
191 raise VMMException(("»%s« looks not like an e-mail address." % |
196 raise VMMException((_("»%s« looks not like an e-mail address.") % |
192 address, ERR.INVALID_ADDRESS)) |
197 address, ERR.INVALID_ADDRESS)) |
193 domain = self.__chkDomainname(domain) |
198 domain = self.__chkDomainname(domain) |
194 localpart = self.__chkLocalpart(localpart) |
199 localpart = self.__chkLocalpart(localpart) |
195 return '%s@%s' % (localpart, domain) |
200 return '%s@%s' % (localpart, domain) |
196 |
201 |
281 |
286 |
282 def __maildirdelete(self, domdir, uid, gid): |
287 def __maildirdelete(self, domdir, uid, gid): |
283 if uid > 0 and gid > 0: |
288 if uid > 0 and gid > 0: |
284 maildir = '%s' % uid |
289 maildir = '%s' % uid |
285 if maildir.count('..') or domdir.count('..'): |
290 if maildir.count('..') or domdir.count('..'): |
286 raise VMMException(('FATAL: ".." in maildir path detected.', |
291 raise VMMException((_('FATAL: ".." in maildir path detected.'), |
287 ERR.FOUND_DOTS_IN_PATH)) |
292 ERR.FOUND_DOTS_IN_PATH)) |
288 if os.path.isdir(domdir): |
293 if os.path.isdir(domdir): |
289 os.chdir(domdir) |
294 os.chdir(domdir) |
290 if os.path.isdir(maildir): |
295 if os.path.isdir(maildir): |
291 mdstat = os.stat(maildir) |
296 mdstat = os.stat(maildir) |
292 if (mdstat.st_uid, mdstat.st_gid) != (uid, gid): |
297 if (mdstat.st_uid, mdstat.st_gid) != (uid, gid): |
293 raise VMMException( |
298 raise VMMException(( |
294 ('FATAL: owner/group mismatch in maildir detected', |
299 _('FATAL: owner/group mismatch in maildir detected'), |
295 ERR.MAILDIR_PERM_MISMATCH)) |
300 ERR.MAILDIR_PERM_MISMATCH)) |
296 rmtree(maildir, ignore_errors=True) |
301 rmtree(maildir, ignore_errors=True) |
297 |
302 |
298 def __domdirdelete(self, domdir, gid): |
303 def __domdirdelete(self, domdir, gid): |
299 if gid > 0: |
304 if gid > 0: |
300 basedir = '%s' % self.__Cfg.get('domdir', 'base') |
305 basedir = '%s' % self.__Cfg.get('domdir', 'base') |
301 domdirdirs = domdir.replace(basedir+'/', '').split('/') |
306 domdirdirs = domdir.replace(basedir+'/', '').split('/') |
302 if basedir.count('..') or domdir.count('..'): |
307 if basedir.count('..') or domdir.count('..'): |
303 raise VMMException( |
308 raise VMMException( |
304 ('FATAL: ".." in domain directory path detected.', |
309 (_('FATAL: ".." in domain directory path detected.'), |
305 ERR.FOUND_DOTS_IN_PATH)) |
310 ERR.FOUND_DOTS_IN_PATH)) |
306 if os.path.isdir('%s/%s' % (basedir, domdirdirs[0])): |
311 if os.path.isdir('%s/%s' % (basedir, domdirdirs[0])): |
307 os.chdir('%s/%s' % (basedir, domdirdirs[0])) |
312 os.chdir('%s/%s' % (basedir, domdirdirs[0])) |
308 if os.lstat(domdirdirs[1]).st_gid != gid: |
313 if os.lstat(domdirdirs[1]).st_gid != gid: |
309 raise VMMException( |
314 raise VMMException( |
310 ('FATAL: group mismatch in domain directory detected', |
315 (_('FATAL: group mismatch in domain directory detected'), |
311 ERR.DOMAINDIR_GROUP_MISMATCH)) |
316 ERR.DOMAINDIR_GROUP_MISMATCH)) |
312 rmtree(domdirdirs[1], ignore_errors=True) |
317 rmtree(domdirdirs[1], ignore_errors=True) |
313 |
318 |
314 def __getSalt(self): |
319 def __getSalt(self): |
315 from random import choice |
320 from random import choice |
383 def setupIsDone(self): |
388 def setupIsDone(self): |
384 """Checks if vmm is configured, returns bool""" |
389 """Checks if vmm is configured, returns bool""" |
385 try: |
390 try: |
386 return self.__Cfg.getboolean('config', 'done') |
391 return self.__Cfg.getboolean('config', 'done') |
387 except ValueError, e: |
392 except ValueError, e: |
388 raise VMMConfigException('Configurtion error: "'+str(e) |
393 raise VMMConfigException(_("""Configurtion error: "%s" |
389 +'"\n(in section "Connfig", option "done")' |
394 (in section "connfig", option "done")' |
390 +'\nsee also: vmm.cfg(5)\n') |
395 see also: vmm.cfg(5)\n""") % str(e)) |
391 |
396 |
392 def configure(self, section=None): |
397 def configure(self, section=None): |
393 """Starts interactive configuration. |
398 """Starts interactive configuration. |
394 |
399 |
395 Configures in interactive mode options in the given section. |
400 Configures in interactive mode options in the given section. |
416 dom.save() |
421 dom.save() |
417 self.__domdirmake(dom.getDir(), dom.getID()) |
422 self.__domdirmake(dom.getDir(), dom.getID()) |
418 |
423 |
419 def domain_transport(self, domainname, transport, force=None): |
424 def domain_transport(self, domainname, transport, force=None): |
420 if force is not None and force != 'force': |
425 if force is not None and force != 'force': |
421 raise VMMDomainException(('Invalid argument: »%s«' % force, |
426 raise VMMDomainException((_('Invalid argument: »%s«') % force, |
422 ERR.INVALID_OPTION)) |
427 ERR.INVALID_OPTION)) |
423 dom = self.__getDomain(domainname, None) |
428 dom = self.__getDomain(domainname, None) |
424 if force is None: |
429 if force is None: |
425 dom.updateTransport(transport) |
430 dom.updateTransport(transport) |
426 else: |
431 else: |
427 dom.updateTransport(transport, force=True) |
432 dom.updateTransport(transport, force=True) |
428 |
433 |
429 def domain_delete(self, domainname, force=None): |
434 def domain_delete(self, domainname, force=None): |
430 if not force is None and force not in ['deluser','delalias','delall']: |
435 if not force is None and force not in ['deluser','delalias','delall']: |
431 raise VMMDomainException(('Invalid argument: »%s«' % force, |
436 raise VMMDomainException((_('Invalid argument: »%s«') % force, |
432 ERR.INVALID_OPTION)) |
437 ERR.INVALID_OPTION)) |
433 dom = self.__getDomain(domainname) |
438 dom = self.__getDomain(domainname) |
434 gid = dom.getID() |
439 gid = dom.getID() |
435 domdir = dom.getDir() |
440 domdir = dom.getDir() |
436 if self.__Cfg.getboolean('misc', 'forcedel') or force == 'delall': |
441 if self.__Cfg.getboolean('misc', 'forcedel') or force == 'delall': |
455 if detailed is None: |
460 if detailed is None: |
456 return dominfo |
461 return dominfo |
457 elif detailed == 'detailed': |
462 elif detailed == 'detailed': |
458 return dominfo, dom.getAccounts(), dom.getAliases() |
463 return dominfo, dom.getAccounts(), dom.getAliases() |
459 else: |
464 else: |
460 raise VMMDomainException(('Invalid argument: »%s«' % detailed, |
465 raise VMMDomainException(('%s: »%s«' % (_('Invalid argument'), |
461 ERR.INVALID_OPTION)) |
466 detailed), ERR.INVALID_OPTION)) |
462 |
467 |
463 def user_add(self, emailaddress, password): |
468 def user_add(self, emailaddress, password): |
464 acc = self.__getAccount(emailaddress, password) |
469 acc = self.__getAccount(emailaddress, password) |
465 acc.save(self.__Cfg.get('maildir', 'folder'), |
470 acc.save(self.__Cfg.get('maildir', 'folder'), |
466 self.__Cfg.getboolean('services', 'smtp'), |
471 self.__Cfg.getboolean('services', 'smtp'), |