1 # -*- coding: UTF-8 -*- |
|
2 # Copyright (c) 2007 - 2010, Pascal Volk |
|
3 # See COPYING for distribution information. |
|
4 |
|
5 """ |
|
6 VirtualMailManager.Handler |
|
7 |
|
8 A wrapper class. It wraps round all other classes and does some |
|
9 dependencies checks. |
|
10 |
|
11 Additionally it communicates with the PostgreSQL database, creates |
|
12 or deletes directories of domains or users. |
|
13 """ |
|
14 |
|
15 import os |
|
16 import re |
|
17 |
|
18 from shutil import rmtree |
|
19 from subprocess import Popen, PIPE |
|
20 |
|
21 from pyPgSQL import PgSQL # python-pgsql - http://pypgsql.sourceforge.net |
|
22 |
|
23 from VirtualMailManager.Account import Account |
|
24 from VirtualMailManager.Alias import Alias |
|
25 from VirtualMailManager.AliasDomain import AliasDomain |
|
26 from VirtualMailManager.common import exec_ok |
|
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 |
|
34 from VirtualMailManager.Domain import Domain, get_gid |
|
35 from VirtualMailManager.EmailAddress import EmailAddress |
|
36 from VirtualMailManager.errors import \ |
|
37 DomainError, NotRootError, PermissionError, VMMError |
|
38 from VirtualMailManager.mailbox import new as new_mailbox |
|
39 from VirtualMailManager.pycompat import any |
|
40 from VirtualMailManager.Relocated import Relocated |
|
41 from VirtualMailManager.Transport import Transport |
|
42 |
|
43 |
|
44 _ = lambda msg: msg |
|
45 |
|
46 CFG_FILE = 'vmm.cfg' |
|
47 CFG_PATH = '/root:/usr/local/etc:/etc' |
|
48 RE_DOMAIN_SEARCH = """^[a-z0-9-\.]+$""" |
|
49 TYPE_ACCOUNT = 0x1 |
|
50 TYPE_ALIAS = 0x2 |
|
51 TYPE_RELOCATED = 0x4 |
|
52 OTHER_TYPES = { |
|
53 TYPE_ACCOUNT: (_(u'an account'), ACCOUNT_EXISTS), |
|
54 TYPE_ALIAS: (_(u'an alias'), ALIAS_EXISTS), |
|
55 TYPE_RELOCATED: (_(u'a relocated user'), RELOCATED_EXISTS), |
|
56 } |
|
57 |
|
58 |
|
59 class Handler(object): |
|
60 """Wrapper class to simplify the access on all the stuff from |
|
61 VirtualMailManager""" |
|
62 __slots__ = ('_cfg', '_cfg_fname', '_dbh', '_warnings') |
|
63 |
|
64 def __init__(self, skip_some_checks=False): |
|
65 """Creates a new Handler instance. |
|
66 |
|
67 ``skip_some_checks`` : bool |
|
68 When a derived class knows how to handle all checks this |
|
69 argument may be ``True``. By default it is ``False`` and |
|
70 all checks will be performed. |
|
71 |
|
72 Throws a NotRootError if your uid is greater 0. |
|
73 """ |
|
74 self._cfg_fname = '' |
|
75 self._warnings = [] |
|
76 self._cfg = None |
|
77 self._dbh = None |
|
78 |
|
79 if os.geteuid(): |
|
80 raise NotRootError(_(u"You are not root.\n\tGood bye!\n"), |
|
81 CONF_NOPERM) |
|
82 if self._check_cfg_file(): |
|
83 self._cfg = Cfg(self._cfg_fname) |
|
84 self._cfg.load() |
|
85 if not skip_some_checks: |
|
86 self._cfg.check() |
|
87 self._chkenv() |
|
88 |
|
89 def _find_cfg_file(self): |
|
90 """Search the CFG_FILE in CFG_PATH. |
|
91 Raise a VMMError when no vmm.cfg could be found. |
|
92 """ |
|
93 for path in CFG_PATH.split(':'): |
|
94 tmp = os.path.join(path, CFG_FILE) |
|
95 if os.path.isfile(tmp): |
|
96 self._cfg_fname = tmp |
|
97 break |
|
98 if not self._cfg_fname: |
|
99 raise VMMError(_(u"Could not find '%(cfg_file)s' in: " |
|
100 u"'%(cfg_path)s'") % {'cfg_file': CFG_FILE, |
|
101 'cfg_path': CFG_PATH}, CONF_NOFILE) |
|
102 |
|
103 def _check_cfg_file(self): |
|
104 """Checks the configuration file, returns bool""" |
|
105 self._find_cfg_file() |
|
106 fstat = os.stat(self._cfg_fname) |
|
107 fmode = int(oct(fstat.st_mode & 0777)) |
|
108 if fmode % 100 and fstat.st_uid != fstat.st_gid or \ |
|
109 fmode % 10 and fstat.st_uid == fstat.st_gid: |
|
110 raise PermissionError(_(u"wrong permissions for '%(file)s': " |
|
111 u"%(perms)s\n`chmod 0600 %(file)s` would " |
|
112 u"be great.") % {'file': self._cfg_fname, |
|
113 'perms': fmode}, CONF_WRONGPERM) |
|
114 else: |
|
115 return True |
|
116 |
|
117 def _chkenv(self): |
|
118 """Make sure our base_directory is a directory and that all |
|
119 required executables exists and are executable. |
|
120 If not, a VMMError will be raised""" |
|
121 basedir = self._cfg.dget('misc.base_directory') |
|
122 if not os.path.exists(basedir): |
|
123 old_umask = os.umask(0006) |
|
124 os.makedirs(basedir, 0771) |
|
125 os.chown(basedir, 0, 0) |
|
126 os.umask(old_umask) |
|
127 elif not os.path.isdir(basedir): |
|
128 raise VMMError(_(u"'%(path)s' is not a directory.\n(%(cfg_file)s: " |
|
129 u"section 'misc', option 'base_directory')") % |
|
130 {'path': basedir, 'cfg_file': self._cfg_fname}, |
|
131 NO_SUCH_DIRECTORY) |
|
132 for opt, val in self._cfg.items('bin'): |
|
133 try: |
|
134 exec_ok(val) |
|
135 except VMMError, err: |
|
136 if err.code is NO_SUCH_BINARY: |
|
137 raise VMMError(_(u"'%(binary)s' doesn't exist.\n" |
|
138 u"(%(cfg_file)s: section 'bin', option " |
|
139 u"'%(option)s')") % {'binary': val, |
|
140 'cfg_file': self._cfg_fname, 'option': opt}, |
|
141 err.code) |
|
142 elif err.code is NOT_EXECUTABLE: |
|
143 raise VMMError(_(u"'%(binary)s' is not executable.\n" |
|
144 u"(%(cfg_file)s: section 'bin', option " |
|
145 u"'%(option)s')") % {'binary': val, |
|
146 'cfg_file': self._cfg_fname, 'option': opt}, |
|
147 err.code) |
|
148 else: |
|
149 raise |
|
150 |
|
151 def _db_connect(self): |
|
152 """Creates a pyPgSQL.PgSQL.connection instance.""" |
|
153 if self._dbh is None or (isinstance(self._dbh, PgSQL.Connection) and |
|
154 not self._dbh._isOpen): |
|
155 try: |
|
156 self._dbh = PgSQL.connect( |
|
157 database=self._cfg.dget('database.name'), |
|
158 user=self._cfg.pget('database.user'), |
|
159 host=self._cfg.dget('database.host'), |
|
160 password=self._cfg.pget('database.pass'), |
|
161 client_encoding='utf8', unicode_results=True) |
|
162 dbc = self._dbh.cursor() |
|
163 dbc.execute("SET NAMES 'UTF8'") |
|
164 dbc.close() |
|
165 except PgSQL.libpq.DatabaseError, err: |
|
166 raise VMMError(str(err), DATABASE_ERROR) |
|
167 |
|
168 def _chk_other_address_types(self, address, exclude): |
|
169 """Checks if the EmailAddress *address* is known as `TYPE_ACCOUNT`, |
|
170 `TYPE_ALIAS` or `TYPE_RELOCATED`, but not as the `TYPE_*` specified |
|
171 by *exclude*. If the *address* is known as one of the `TYPE_*`s |
|
172 the according `TYPE_*` constant will be returned. Otherwise 0 will |
|
173 be returned.""" |
|
174 assert exclude in (TYPE_ACCOUNT, TYPE_ALIAS, TYPE_RELOCATED) and \ |
|
175 isinstance(address, EmailAddress) |
|
176 if exclude is not TYPE_ACCOUNT: |
|
177 account = Account(self._dbh, address) |
|
178 if account: |
|
179 return TYPE_ACCOUNT |
|
180 if exclude is not TYPE_ALIAS: |
|
181 alias = Alias(self._dbh, address) |
|
182 if alias: |
|
183 return TYPE_ALIAS |
|
184 if exclude is not TYPE_RELOCATED: |
|
185 relocated = Relocated(self._dbh, address) |
|
186 if relocated: |
|
187 return TYPE_RELOCATED |
|
188 return 0 |
|
189 |
|
190 def _is_other_address(self, address, exclude): |
|
191 """Checks if *address* is known for an Account (TYPE_ACCOUNT), |
|
192 Alias (TYPE_ALIAS) or Relocated (TYPE_RELOCATED), except for |
|
193 *exclude*. Returns `False` if the address is not known for other |
|
194 types. |
|
195 |
|
196 Raises a `VMMError` if the address is known. |
|
197 """ |
|
198 other = self._chk_other_address_types(address, exclude) |
|
199 if not other: |
|
200 return False |
|
201 msg = _(u"There is already %(a_type)s with the address '%(address)s'.") |
|
202 raise VMMError(msg % {'a_type': OTHER_TYPES[other][0], |
|
203 'address': address}, OTHER_TYPES[other][1]) |
|
204 |
|
205 def _get_account(self, address): |
|
206 """Return an Account instances for the given address (str).""" |
|
207 address = EmailAddress(address) |
|
208 self._db_connect() |
|
209 return Account(self._dbh, address) |
|
210 |
|
211 def _get_alias(self, address): |
|
212 """Return an Alias instances for the given address (str).""" |
|
213 address = EmailAddress(address) |
|
214 self._db_connect() |
|
215 return Alias(self._dbh, address) |
|
216 |
|
217 def _get_relocated(self, address): |
|
218 """Return a Relocated instances for the given address (str).""" |
|
219 address = EmailAddress(address) |
|
220 self._db_connect() |
|
221 return Relocated(self._dbh, address) |
|
222 |
|
223 def _get_domain(self, domainname): |
|
224 """Return a Domain instances for the given domain name (str).""" |
|
225 self._db_connect() |
|
226 return Domain(self._dbh, domainname) |
|
227 |
|
228 def _get_disk_usage(self, directory): |
|
229 """Estimate file space usage for the given directory. |
|
230 |
|
231 Keyword arguments: |
|
232 directory -- the directory to summarize recursively disk usage for |
|
233 """ |
|
234 if self._isdir(directory): |
|
235 return Popen([self._cfg.dget('bin.du'), "-hs", directory], |
|
236 stdout=PIPE).communicate()[0].split('\t')[0] |
|
237 else: |
|
238 return 0 |
|
239 |
|
240 def _isdir(self, directory): |
|
241 """Check if `directory` is a directory. Returns bool. |
|
242 When `directory` isn't a directory, a warning will be appended to |
|
243 _warnings.""" |
|
244 isdir = os.path.isdir(directory) |
|
245 if not isdir: |
|
246 self._warnings.append(_('No such directory: %s') % directory) |
|
247 return isdir |
|
248 |
|
249 def _make_domain_dir(self, domain): |
|
250 """Create a directory for the `domain` and its accounts.""" |
|
251 cwd = os.getcwd() |
|
252 hashdir, domdir = domain.directory.split(os.path.sep)[-2:] |
|
253 os.chdir(self._cfg.dget('misc.base_directory')) |
|
254 if not os.path.isdir(hashdir): |
|
255 os.mkdir(hashdir, 0711) |
|
256 os.chown(hashdir, 0, 0) |
|
257 os.mkdir(os.path.join(hashdir, domdir), |
|
258 self._cfg.dget('domain.directory_mode')) |
|
259 os.chown(domain.directory, 0, domain.gid) |
|
260 os.chdir(cwd) |
|
261 |
|
262 def _make_home(self, account): |
|
263 """Create a home directory for the new Account *account*.""" |
|
264 os.umask(0007) |
|
265 os.chdir(account.domain_directory) |
|
266 os.mkdir('%s' % account.uid, self._cfg.dget('account.directory_mode')) |
|
267 os.chown('%s' % account.uid, account.uid, account.gid) |
|
268 |
|
269 def _delete_home(self, domdir, uid, gid): |
|
270 """Delete a user's home directory.""" |
|
271 if uid > 0 and gid > 0: |
|
272 userdir = '%s' % uid |
|
273 if userdir.count('..') or domdir.count('..'): |
|
274 raise VMMError(_(u'Found ".." in home directory path.'), |
|
275 FOUND_DOTS_IN_PATH) |
|
276 if os.path.isdir(domdir): |
|
277 os.chdir(domdir) |
|
278 if os.path.isdir(userdir): |
|
279 mdstat = os.stat(userdir) |
|
280 if (mdstat.st_uid, mdstat.st_gid) != (uid, gid): |
|
281 raise VMMError(_(u'Detected owner/group mismatch in ' |
|
282 u'home directory.'), |
|
283 MAILDIR_PERM_MISMATCH) |
|
284 rmtree(userdir, ignore_errors=True) |
|
285 else: |
|
286 raise VMMError(_(u"No such directory: %s") % |
|
287 os.path.join(domdir, userdir), |
|
288 NO_SUCH_DIRECTORY) |
|
289 |
|
290 def _delete_domain_dir(self, domdir, gid): |
|
291 """Delete a domain's directory.""" |
|
292 if gid > 0: |
|
293 if not self._isdir(domdir): |
|
294 return |
|
295 basedir = self._cfg.dget('misc.base_directory') |
|
296 domdirdirs = domdir.replace(basedir + '/', '').split('/') |
|
297 domdirparent = os.path.join(basedir, domdirdirs[0]) |
|
298 if basedir.count('..') or domdir.count('..'): |
|
299 raise VMMError(_(u'Found ".." in domain directory path.'), |
|
300 FOUND_DOTS_IN_PATH) |
|
301 if os.path.isdir(domdirparent): |
|
302 os.chdir(domdirparent) |
|
303 if os.lstat(domdirdirs[1]).st_gid != gid: |
|
304 raise VMMError(_(u'Detected group mismatch in domain ' |
|
305 u'directory.'), DOMAINDIR_GROUP_MISMATCH) |
|
306 rmtree(domdirdirs[1], ignore_errors=True) |
|
307 |
|
308 def has_warnings(self): |
|
309 """Checks if warnings are present, returns bool.""" |
|
310 return bool(len(self._warnings)) |
|
311 |
|
312 def get_warnings(self): |
|
313 """Returns a list with all available warnings and resets all |
|
314 warnings. |
|
315 """ |
|
316 ret_val = self._warnings[:] |
|
317 del self._warnings[:] |
|
318 return ret_val |
|
319 |
|
320 def cfg_dget(self, option): |
|
321 """Get the configured value of the *option* (section.option). |
|
322 When the option was not configured its default value will be |
|
323 returned.""" |
|
324 return self._cfg.dget(option) |
|
325 |
|
326 def cfg_pget(self, option): |
|
327 """Get the configured value of the *option* (section.option).""" |
|
328 return self._cfg.pget(option) |
|
329 |
|
330 def cfg_install(self): |
|
331 """Installs the cfg_dget method as ``cfg_dget`` into the built-in |
|
332 namespace.""" |
|
333 import __builtin__ |
|
334 assert 'cfg_dget' not in __builtin__.__dict__ |
|
335 __builtin__.__dict__['cfg_dget'] = self._cfg.dget |
|
336 |
|
337 def domain_add(self, domainname, transport=None): |
|
338 """Wrapper around Domain.set_transport() and Domain.save()""" |
|
339 dom = self._get_domain(domainname) |
|
340 if transport is None: |
|
341 dom.set_transport(Transport(self._dbh, |
|
342 transport=self._cfg.dget('misc.transport'))) |
|
343 else: |
|
344 dom.set_transport(Transport(self._dbh, transport=transport)) |
|
345 dom.set_directory(self._cfg.dget('misc.base_directory')) |
|
346 dom.save() |
|
347 self._make_domain_dir(dom) |
|
348 |
|
349 def domain_transport(self, domainname, transport, force=None): |
|
350 """Wrapper around Domain.update_transport()""" |
|
351 if force is not None and force != 'force': |
|
352 raise DomainError(_(u"Invalid argument: '%s'") % force, |
|
353 INVALID_ARGUMENT) |
|
354 dom = self._get_domain(domainname) |
|
355 trsp = Transport(self._dbh, transport=transport) |
|
356 if force is None: |
|
357 dom.update_transport(trsp) |
|
358 else: |
|
359 dom.update_transport(trsp, force=True) |
|
360 |
|
361 def domain_delete(self, domainname, force=None): |
|
362 """Wrapper around Domain.delete()""" |
|
363 if force and force not in ('deluser', 'delalias', 'delall'): |
|
364 raise DomainError(_(u"Invalid argument: '%s'") % force, |
|
365 INVALID_ARGUMENT) |
|
366 dom = self._get_domain(domainname) |
|
367 gid = dom.gid |
|
368 domdir = dom.directory |
|
369 if self._cfg.dget('domain.force_deletion') or force == 'delall': |
|
370 dom.delete(True, True) |
|
371 elif force == 'deluser': |
|
372 dom.delete(deluser=True) |
|
373 elif force == 'delalias': |
|
374 dom.delete(delalias=True) |
|
375 else: |
|
376 dom.delete() |
|
377 if self._cfg.dget('domain.delete_directory'): |
|
378 self._delete_domain_dir(domdir, gid) |
|
379 |
|
380 def domain_info(self, domainname, details=None): |
|
381 """Wrapper around Domain.get_info(), Domain.get_accounts(), |
|
382 Domain.get_aliase_names(), Domain.get_aliases() and |
|
383 Domain.get_relocated.""" |
|
384 if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full', |
|
385 'relocated']: |
|
386 raise VMMError(_(u'Invalid argument: ā%sā') % details, |
|
387 INVALID_ARGUMENT) |
|
388 dom = self._get_domain(domainname) |
|
389 dominfo = dom.get_info() |
|
390 if dominfo['domainname'].startswith('xn--'): |
|
391 dominfo['domainname'] += ' (%s)' % \ |
|
392 dominfo['domainname'].decode('idna') |
|
393 if details is None: |
|
394 return dominfo |
|
395 elif details == 'accounts': |
|
396 return (dominfo, dom.get_accounts()) |
|
397 elif details == 'aliasdomains': |
|
398 return (dominfo, dom.get_aliase_names()) |
|
399 elif details == 'aliases': |
|
400 return (dominfo, dom.get_aliases()) |
|
401 elif details == 'relocated': |
|
402 return(dominfo, dom.get_relocated()) |
|
403 else: |
|
404 return (dominfo, dom.get_aliase_names(), dom.get_accounts(), |
|
405 dom.get_aliases(), dom.get_relocated()) |
|
406 |
|
407 def aliasdomain_add(self, aliasname, domainname): |
|
408 """Adds an alias domain to the domain. |
|
409 |
|
410 Arguments: |
|
411 |
|
412 `aliasname` : basestring |
|
413 The name of the alias domain |
|
414 `domainname` : basestring |
|
415 The name of the target domain |
|
416 """ |
|
417 dom = self._get_domain(domainname) |
|
418 alias_dom = AliasDomain(self._dbh, aliasname) |
|
419 alias_dom.set_destination(dom) |
|
420 alias_dom.save() |
|
421 |
|
422 def aliasdomain_info(self, aliasname): |
|
423 """Returns a dict (keys: "alias" and "domain") with the names of |
|
424 the alias domain and its primary domain.""" |
|
425 self._db_connect() |
|
426 alias_dom = AliasDomain(self._dbh, aliasname) |
|
427 return alias_dom.info() |
|
428 |
|
429 def aliasdomain_switch(self, aliasname, domainname): |
|
430 """Modifies the target domain of an existing alias domain. |
|
431 |
|
432 Arguments: |
|
433 |
|
434 `aliasname` : basestring |
|
435 The name of the alias domain |
|
436 `domainname` : basestring |
|
437 The name of the new target domain |
|
438 """ |
|
439 dom = self._get_domain(domainname) |
|
440 alias_dom = AliasDomain(self._dbh, aliasname) |
|
441 alias_dom.set_destination(dom) |
|
442 alias_dom.switch() |
|
443 |
|
444 def aliasdomain_delete(self, aliasname): |
|
445 """Deletes the given alias domain. |
|
446 |
|
447 Argument: |
|
448 |
|
449 `aliasname` : basestring |
|
450 The name of the alias domain |
|
451 """ |
|
452 self._db_connect() |
|
453 alias_dom = AliasDomain(self._dbh, aliasname) |
|
454 alias_dom.delete() |
|
455 |
|
456 def domain_list(self, pattern=None): |
|
457 """Wrapper around function search() from module Domain.""" |
|
458 from VirtualMailManager.Domain import search |
|
459 like = False |
|
460 if pattern and (pattern.startswith('%') or pattern.endswith('%')): |
|
461 like = True |
|
462 if not re.match(RE_DOMAIN_SEARCH, pattern.strip('%')): |
|
463 raise VMMError(_(u"The pattern '%s' contains invalid " |
|
464 u"characters.") % pattern, DOMAIN_INVALID) |
|
465 self._db_connect() |
|
466 return search(self._dbh, pattern=pattern, like=like) |
|
467 |
|
468 def user_add(self, emailaddress, password): |
|
469 """Wrapper around Account.set_password() and Account.save().""" |
|
470 acc = self._get_account(emailaddress) |
|
471 acc.set_password(password) |
|
472 acc.save() |
|
473 oldpwd = os.getcwd() |
|
474 self._make_home(acc) |
|
475 mailbox = new_mailbox(acc) |
|
476 mailbox.create() |
|
477 folders = self._cfg.dget('mailbox.folders').split(':') |
|
478 if any(folders): |
|
479 bad = mailbox.add_boxes(folders, |
|
480 self._cfg.dget('mailbox.subscribe')) |
|
481 if bad: |
|
482 self._warnings.append(_(u"Skipped mailbox folders:") + |
|
483 '\n\t- ' + '\n\t- '.join(bad)) |
|
484 os.chdir(oldpwd) |
|
485 |
|
486 def alias_add(self, aliasaddress, *targetaddresses): |
|
487 """Creates a new `Alias` entry for the given *aliasaddress* with |
|
488 the given *targetaddresses*.""" |
|
489 alias = self._get_alias(aliasaddress) |
|
490 destinations = [EmailAddress(address) for address in targetaddresses] |
|
491 warnings = [] |
|
492 destinations = alias.add_destinations(destinations, warnings) |
|
493 if warnings: |
|
494 self._warnings.append(_('Ignored destination addresses:')) |
|
495 self._warnings.extend((' * %s' % w for w in warnings)) |
|
496 for destination in destinations: |
|
497 if get_gid(self._dbh, destination.domainname) and \ |
|
498 not self._chk_other_address_types(destination, TYPE_RELOCATED): |
|
499 self._warnings.append(_(u"The destination account/alias '%s' " |
|
500 u"doesn't exist.") % destination) |
|
501 |
|
502 def user_delete(self, emailaddress, force=None): |
|
503 """Wrapper around Account.delete(...)""" |
|
504 if force not in (None, 'delalias'): |
|
505 raise VMMError(_(u"Invalid argument: '%s'") % force, |
|
506 INVALID_ARGUMENT) |
|
507 acc = self._get_account(emailaddress) |
|
508 if not acc: |
|
509 raise VMMError(_(u"The account '%s' doesn't exist.") % |
|
510 acc.address, NO_SUCH_ACCOUNT) |
|
511 uid = acc.uid |
|
512 gid = acc.gid |
|
513 dom_dir = acc.domain_directory |
|
514 acc_dir = acc.home |
|
515 acc.delete(bool(force)) |
|
516 if self._cfg.dget('account.delete_directory'): |
|
517 try: |
|
518 self._delete_home(dom_dir, uid, gid) |
|
519 except VMMError, err: |
|
520 if err.code in (FOUND_DOTS_IN_PATH, MAILDIR_PERM_MISMATCH, |
|
521 NO_SUCH_DIRECTORY): |
|
522 warning = _(u"""\ |
|
523 The account has been successfully deleted from the database. |
|
524 But an error occurred while deleting the following directory: |
|
525 ā%(directory)sā |
|
526 Reason: %(reason)s""") % \ |
|
527 {'directory': acc_dir, 'reason': err.msg} |
|
528 self._warnings.append(warning) |
|
529 else: |
|
530 raise |
|
531 |
|
532 def alias_info(self, aliasaddress): |
|
533 """Returns an iterator object for all destinations (`EmailAddress` |
|
534 instances) for the `Alias` with the given *aliasaddress*.""" |
|
535 alias = self._get_alias(aliasaddress) |
|
536 if alias: |
|
537 return alias.get_destinations() |
|
538 if not self._is_other_address(alias.address, TYPE_ALIAS): |
|
539 raise VMMError(_(u"The alias '%s' doesn't exist.") % |
|
540 alias.address, NO_SUCH_ALIAS) |
|
541 |
|
542 def alias_delete(self, aliasaddress, targetaddress=None): |
|
543 """Deletes the `Alias` *aliasaddress* with all its destinations from |
|
544 the database. If *targetaddress* is not ``None``, only this |
|
545 destination will be removed from the alias.""" |
|
546 alias = self._get_alias(aliasaddress) |
|
547 if targetaddress is None: |
|
548 alias.delete() |
|
549 else: |
|
550 alias.del_destination(EmailAddress(targetaddress)) |
|
551 |
|
552 def user_info(self, emailaddress, details=None): |
|
553 """Wrapper around Account.get_info(...)""" |
|
554 if details not in (None, 'du', 'aliases', 'full'): |
|
555 raise VMMError(_(u"Invalid argument: '%s'") % details, |
|
556 INVALID_ARGUMENT) |
|
557 acc = self._get_account(emailaddress) |
|
558 if not acc: |
|
559 if not self._is_other_address(acc.address, TYPE_ACCOUNT): |
|
560 raise VMMError(_(u"The account '%s' doesn't exist.") % |
|
561 acc.address, NO_SUCH_ACCOUNT) |
|
562 info = acc.get_info() |
|
563 if self._cfg.dget('account.disk_usage') or details in ('du', 'full'): |
|
564 path = os.path.join(acc.home, acc.mail_location.directory) |
|
565 info['disk usage'] = self._get_disk_usage(path) |
|
566 if details in (None, 'du'): |
|
567 return info |
|
568 if details in ('aliases', 'full'): |
|
569 return (info, acc.get_aliases()) |
|
570 return info |
|
571 |
|
572 def user_by_uid(self, uid): |
|
573 """Search for an Account by its *uid*. |
|
574 Returns a dict (address, uid and gid) if a user could be found.""" |
|
575 from VirtualMailManager.Account import get_account_by_uid |
|
576 self._db_connect() |
|
577 return get_account_by_uid(uid, self._dbh) |
|
578 |
|
579 def user_password(self, emailaddress, password): |
|
580 """Wrapper for Account.modify('password' ...).""" |
|
581 if not isinstance(password, basestring) or not password: |
|
582 raise VMMError(_(u"Could not accept password: '%s'") % password, |
|
583 INVALID_ARGUMENT) |
|
584 acc = self._get_account(emailaddress) |
|
585 if not acc: |
|
586 raise VMMError(_(u"The account '%s' doesn't exist.") % |
|
587 acc.address, NO_SUCH_ACCOUNT) |
|
588 acc.modify('password', password) |
|
589 |
|
590 def user_name(self, emailaddress, name): |
|
591 """Wrapper for Account.modify('name', ...).""" |
|
592 if not isinstance(name, basestring) or not name: |
|
593 raise VMMError(_(u"Could not accept name: '%s'") % name, |
|
594 INVALID_ARGUMENT) |
|
595 acc = self._get_account(emailaddress) |
|
596 if not acc: |
|
597 raise VMMError(_(u"The account '%s' doesn't exist.") % |
|
598 acc.address, NO_SUCH_ACCOUNT) |
|
599 acc.modify('name', name) |
|
600 |
|
601 def user_transport(self, emailaddress, transport): |
|
602 """Wrapper for Account.modify('transport', ...).""" |
|
603 if not isinstance(transport, basestring) or not transport: |
|
604 raise VMMError(_(u"Could not accept transport: '%s'") % transport, |
|
605 INVALID_ARGUMENT) |
|
606 acc = self._get_account(emailaddress) |
|
607 if not acc: |
|
608 raise VMMError(_(u"The account '%s' doesn't exist.") % |
|
609 acc.address, NO_SUCH_ACCOUNT) |
|
610 acc.modify('transport', transport) |
|
611 |
|
612 def user_disable(self, emailaddress, service=None): |
|
613 """Wrapper for Account.disable(service)""" |
|
614 if service not in (None, 'all', 'imap', 'pop3', 'smtp', 'sieve'): |
|
615 raise VMMError(_(u"Could not accept service: '%s'") % service, |
|
616 INVALID_ARGUMENT) |
|
617 acc = self._get_account(emailaddress) |
|
618 if not acc: |
|
619 raise VMMError(_(u"The account '%s' doesn't exist.") % |
|
620 acc.address, NO_SUCH_ACCOUNT) |
|
621 acc.disable(service) |
|
622 |
|
623 def user_enable(self, emailaddress, service=None): |
|
624 """Wrapper for Account.enable(service)""" |
|
625 if service not in (None, 'all', 'imap', 'pop3', 'smtp', 'sieve'): |
|
626 raise VMMError(_(u"Could not accept service: '%s'") % service, |
|
627 INVALID_ARGUMENT) |
|
628 acc = self._get_account(emailaddress) |
|
629 if not acc: |
|
630 raise VMMError(_(u"The account '%s' doesn't exist.") % |
|
631 acc.address, NO_SUCH_ACCOUNT) |
|
632 acc.enable(service) |
|
633 |
|
634 def relocated_add(self, emailaddress, targetaddress): |
|
635 """Creates a new `Relocated` entry in the database. If there is |
|
636 already a relocated user with the given *emailaddress*, only the |
|
637 *targetaddress* for the relocated user will be updated.""" |
|
638 relocated = self._get_relocated(emailaddress) |
|
639 relocated.set_destination(EmailAddress(targetaddress)) |
|
640 |
|
641 def relocated_info(self, emailaddress): |
|
642 """Returns the target address of the relocated user with the given |
|
643 *emailaddress*.""" |
|
644 relocated = self._get_relocated(emailaddress) |
|
645 if relocated: |
|
646 return relocated.get_info() |
|
647 if not self._is_other_address(relocated.address, TYPE_RELOCATED): |
|
648 raise VMMError(_(u"The relocated user '%s' doesn't exist.") % |
|
649 relocated.address, NO_SUCH_RELOCATED) |
|
650 |
|
651 def relocated_delete(self, emailaddress): |
|
652 """Deletes the relocated user with the given *emailaddress* from |
|
653 the database.""" |
|
654 relocated = self._get_relocated(emailaddress) |
|
655 relocated.delete() |
|
656 |
|
657 del _ |
|