|
1 # -*- coding: UTF-8 -*- |
|
2 # Copyright (c) 2007 - 2012, Pascal Volk |
|
3 # See COPYING for distribution information. |
|
4 """ |
|
5 VirtualMailManager.cli.subcommands |
|
6 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
7 |
|
8 VirtualMailManager's cli subcommands. |
|
9 """ |
|
10 |
|
11 import locale |
|
12 import os |
|
13 |
|
14 from textwrap import TextWrapper |
|
15 from time import strftime, strptime |
|
16 |
|
17 from VirtualMailManager import ENCODING |
|
18 from VirtualMailManager.cli import get_winsize, prog, w_err, w_std |
|
19 from VirtualMailManager.common import human_size, size_in_bytes, \ |
|
20 version_str, format_domain_default |
|
21 from VirtualMailManager.constants import __copyright__, __date__, \ |
|
22 __version__, ACCOUNT_EXISTS, ALIAS_EXISTS, ALIASDOMAIN_ISDOMAIN, \ |
|
23 DOMAIN_ALIAS_EXISTS, INVALID_ARGUMENT, EX_MISSING_ARGS, \ |
|
24 RELOCATED_EXISTS, TYPE_ACCOUNT, TYPE_ALIAS, TYPE_RELOCATED |
|
25 from VirtualMailManager.errors import VMMError |
|
26 from VirtualMailManager.password import list_schemes |
|
27 from VirtualMailManager.serviceset import SERVICES |
|
28 |
|
29 __all__ = ( |
|
30 'Command', 'RunContext', 'cmd_map', 'usage', 'alias_add', 'alias_delete', |
|
31 'alias_info', 'aliasdomain_add', 'aliasdomain_delete', 'aliasdomain_info', |
|
32 'aliasdomain_switch', 'catchall_add', 'catchall_info', 'catchall_delete', |
|
33 'config_get', 'config_set', 'configure', |
|
34 'domain_add', 'domain_delete', 'domain_info', 'domain_quota', |
|
35 'domain_services', 'domain_transport', 'domain_note', 'get_user', 'help_', |
|
36 'list_domains', 'list_pwschemes', 'list_users', 'list_aliases', |
|
37 'list_relocated', 'list_addresses', 'relocated_add', 'relocated_delete', |
|
38 'relocated_info', 'user_add', 'user_delete', 'user_info', 'user_name', |
|
39 'user_password', 'user_quota', 'user_services', 'user_transport', |
|
40 'user_note', 'version', |
|
41 ) |
|
42 |
|
43 _ = lambda msg: msg |
|
44 txt_wrpr = TextWrapper(width=get_winsize()[1] - 1) |
|
45 cmd_map = {} |
|
46 |
|
47 |
|
48 class Command(object): |
|
49 """Container class for command information.""" |
|
50 __slots__ = ('name', 'alias', 'func', 'args', 'descr') |
|
51 |
|
52 def __init__(self, name, alias, func, args, descr): |
|
53 """Create a new Command instance. |
|
54 |
|
55 Arguments: |
|
56 |
|
57 `name` : str |
|
58 the command name, e.g. ``addalias`` |
|
59 `alias` : str |
|
60 the command's short alias, e.g. ``aa`` |
|
61 `func` : callable |
|
62 the function to handle the command |
|
63 `args` : str |
|
64 argument placeholders, e.g. ``aliasaddress`` |
|
65 `descr` : str |
|
66 short description of the command |
|
67 """ |
|
68 self.name = name |
|
69 self.alias = alias |
|
70 self.func = func |
|
71 self.args = args |
|
72 self.descr = descr |
|
73 |
|
74 @property |
|
75 def usage(self): |
|
76 """the command's usage info.""" |
|
77 return u'%s %s %s' % (prog, self.name, self.args) |
|
78 |
|
79 |
|
80 class RunContext(object): |
|
81 """Contains all information necessary to run a subcommand.""" |
|
82 __slots__ = ('argc', 'args', 'cget', 'hdlr', 'scmd') |
|
83 plan_a_b = _(u'Plan A failed ... trying Plan B: %(subcommand)s %(object)s') |
|
84 |
|
85 def __init__(self, argv, handler, command): |
|
86 """Create a new RunContext""" |
|
87 self.argc = len(argv) |
|
88 self.args = [unicode(arg, ENCODING) for arg in argv] |
|
89 self.cget = handler.cfg_dget |
|
90 self.hdlr = handler |
|
91 self.scmd = command |
|
92 |
|
93 |
|
94 def alias_add(ctx): |
|
95 """create a new alias e-mail address""" |
|
96 if ctx.argc < 3: |
|
97 usage(EX_MISSING_ARGS, _(u'Missing alias address and destination.'), |
|
98 ctx.scmd) |
|
99 elif ctx.argc < 4: |
|
100 usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd) |
|
101 ctx.hdlr.alias_add(ctx.args[2].lower(), *ctx.args[3:]) |
|
102 |
|
103 |
|
104 def alias_delete(ctx): |
|
105 """delete the specified alias e-mail address or one of its destinations""" |
|
106 if ctx.argc < 3: |
|
107 usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd) |
|
108 elif ctx.argc < 4: |
|
109 ctx.hdlr.alias_delete(ctx.args[2].lower()) |
|
110 else: |
|
111 ctx.hdlr.alias_delete(ctx.args[2].lower(), ctx.args[3]) |
|
112 |
|
113 |
|
114 def alias_info(ctx): |
|
115 """show the destination(s) of the specified alias""" |
|
116 if ctx.argc < 3: |
|
117 usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd) |
|
118 address = ctx.args[2].lower() |
|
119 try: |
|
120 _print_aliase_info(address, ctx.hdlr.alias_info(address)) |
|
121 except VMMError, err: |
|
122 if err.code is ACCOUNT_EXISTS: |
|
123 w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo', |
|
124 'object': address}) |
|
125 ctx.scmd = ctx.args[1] = 'userinfo' |
|
126 user_info(ctx) |
|
127 elif err.code is RELOCATED_EXISTS: |
|
128 w_err(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo', |
|
129 'object': address}) |
|
130 ctx.scmd = ctx.args[1] = 'relocatedinfo' |
|
131 relocated_info(ctx) |
|
132 else: |
|
133 raise |
|
134 |
|
135 |
|
136 def aliasdomain_add(ctx): |
|
137 """create a new alias for an existing domain""" |
|
138 if ctx.argc < 3: |
|
139 usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination ' |
|
140 u'domain name.'), ctx.scmd) |
|
141 elif ctx.argc < 4: |
|
142 usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'), |
|
143 ctx.scmd) |
|
144 ctx.hdlr.aliasdomain_add(ctx.args[2].lower(), ctx.args[3].lower()) |
|
145 |
|
146 |
|
147 def aliasdomain_delete(ctx): |
|
148 """delete the specified alias domain""" |
|
149 if ctx.argc < 3: |
|
150 usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd) |
|
151 ctx.hdlr.aliasdomain_delete(ctx.args[2].lower()) |
|
152 |
|
153 |
|
154 def aliasdomain_info(ctx): |
|
155 """show the destination of the given alias domain""" |
|
156 if ctx.argc < 3: |
|
157 usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd) |
|
158 try: |
|
159 _print_aliasdomain_info(ctx.hdlr.aliasdomain_info(ctx.args[2].lower())) |
|
160 except VMMError, err: |
|
161 if err.code is ALIASDOMAIN_ISDOMAIN: |
|
162 w_err(0, ctx.plan_a_b % {'subcommand': u'domaininfo', |
|
163 'object': ctx.args[2].lower()}) |
|
164 ctx.scmd = ctx.args[1] = 'domaininfo' |
|
165 domain_info(ctx) |
|
166 else: |
|
167 raise |
|
168 |
|
169 |
|
170 def aliasdomain_switch(ctx): |
|
171 """assign the given alias domain to an other domain""" |
|
172 if ctx.argc < 3: |
|
173 usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination ' |
|
174 u'domain name.'), ctx.scmd) |
|
175 elif ctx.argc < 4: |
|
176 usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'), |
|
177 ctx.scmd) |
|
178 ctx.hdlr.aliasdomain_switch(ctx.args[2].lower(), ctx.args[3].lower()) |
|
179 |
|
180 |
|
181 def catchall_add(ctx): |
|
182 """create a new catchall alias e-mail address""" |
|
183 if ctx.argc < 3: |
|
184 usage(EX_MISSING_ARGS, _(u'Missing domain and destination.'), |
|
185 ctx.scmd) |
|
186 elif ctx.argc < 4: |
|
187 usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd) |
|
188 ctx.hdlr.catchall_add(ctx.args[2].lower(), *ctx.args[3:]) |
|
189 |
|
190 |
|
191 def catchall_delete(ctx): |
|
192 """delete the specified destination or all of the catchall destination""" |
|
193 if ctx.argc < 3: |
|
194 usage(EX_MISSING_ARGS, _(u'Missing domain.'), ctx.scmd) |
|
195 elif ctx.argc < 4: |
|
196 ctx.hdlr.catchall_delete(ctx.args[2].lower()) |
|
197 else: |
|
198 ctx.hdlr.catchall_delete(ctx.args[2].lower(), ctx.args[3]) |
|
199 |
|
200 |
|
201 def catchall_info(ctx): |
|
202 """show the catchall destination(s) of the specified domain""" |
|
203 if ctx.argc < 3: |
|
204 usage(EX_MISSING_ARGS, _(u'Missing domain.'), ctx.scmd) |
|
205 address = ctx.args[2].lower() |
|
206 _print_catchall_info(address, ctx.hdlr.catchall_info(address)) |
|
207 |
|
208 |
|
209 def config_get(ctx): |
|
210 """show the actual value of the configuration option""" |
|
211 if ctx.argc < 3: |
|
212 usage(EX_MISSING_ARGS, _(u"Missing option name."), ctx.scmd) |
|
213 |
|
214 noop = lambda option: option |
|
215 opt_formater = { |
|
216 'misc.dovecot_version': version_str, |
|
217 'domain.quota_bytes': human_size, |
|
218 } |
|
219 |
|
220 option = ctx.args[2].lower() |
|
221 w_std('%s = %s' % (option, opt_formater.get(option, |
|
222 noop)(ctx.cget(option)))) |
|
223 |
|
224 |
|
225 def config_set(ctx): |
|
226 """set a new value for the configuration option""" |
|
227 if ctx.argc < 3: |
|
228 usage(EX_MISSING_ARGS, _(u'Missing option and new value.'), ctx.scmd) |
|
229 if ctx.argc < 4: |
|
230 usage(EX_MISSING_ARGS, _(u'Missing new configuration value.'), |
|
231 ctx.scmd) |
|
232 ctx.hdlr.cfg_set(ctx.args[2].lower(), ctx.args[3]) |
|
233 |
|
234 |
|
235 def configure(ctx): |
|
236 """start interactive configuration modus""" |
|
237 if ctx.argc < 3: |
|
238 ctx.hdlr.configure() |
|
239 else: |
|
240 ctx.hdlr.configure(ctx.args[2].lower()) |
|
241 |
|
242 |
|
243 def domain_add(ctx): |
|
244 """create a new domain""" |
|
245 if ctx.argc < 3: |
|
246 usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd) |
|
247 elif ctx.argc < 4: |
|
248 ctx.hdlr.domain_add(ctx.args[2].lower()) |
|
249 else: |
|
250 ctx.hdlr.domain_add(ctx.args[2].lower(), ctx.args[3]) |
|
251 if ctx.cget('domain.auto_postmaster'): |
|
252 w_std(_(u'Creating account for postmaster@%s') % ctx.args[2].lower()) |
|
253 ctx.scmd = 'useradd' |
|
254 ctx.args = [prog, ctx.scmd, u'postmaster@' + ctx.args[2].lower()] |
|
255 ctx.argc = 3 |
|
256 user_add(ctx) |
|
257 |
|
258 |
|
259 def domain_delete(ctx): |
|
260 """delete the given domain and all its alias domains""" |
|
261 if ctx.argc < 3: |
|
262 usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd) |
|
263 elif ctx.argc < 4: |
|
264 ctx.hdlr.domain_delete(ctx.args[2].lower()) |
|
265 elif ctx.args[3].lower() == 'force': |
|
266 ctx.hdlr.domain_delete(ctx.args[2].lower(), True) |
|
267 else: |
|
268 usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3], |
|
269 ctx.scmd) |
|
270 |
|
271 |
|
272 def domain_info(ctx): |
|
273 """display information about the given domain""" |
|
274 if ctx.argc < 3: |
|
275 usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd) |
|
276 if ctx.argc < 4: |
|
277 details = None |
|
278 else: |
|
279 details = ctx.args[3].lower() |
|
280 if details not in ('accounts', 'aliasdomains', 'aliases', 'full', |
|
281 'relocated', 'catchall'): |
|
282 usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details, |
|
283 ctx.scmd) |
|
284 try: |
|
285 info = ctx.hdlr.domain_info(ctx.args[2].lower(), details) |
|
286 except VMMError, err: |
|
287 if err.code is DOMAIN_ALIAS_EXISTS: |
|
288 w_err(0, ctx.plan_a_b % {'subcommand': u'aliasdomaininfo', |
|
289 'object': ctx.args[2].lower()}) |
|
290 ctx.scmd = ctx.args[1] = 'aliasdomaininfo' |
|
291 aliasdomain_info(ctx) |
|
292 else: |
|
293 raise |
|
294 else: |
|
295 q_limit = u'Storage: %(bytes)s; Messages: %(messages)s' |
|
296 if not details: |
|
297 info['bytes'] = human_size(info['bytes']) |
|
298 info['messages'] = locale.format('%d', info['messages'], True) |
|
299 info['quota limit/user'] = q_limit % info |
|
300 _print_info(ctx, info, _(u'Domain')) |
|
301 else: |
|
302 info[0]['bytes'] = human_size(info[0]['bytes']) |
|
303 info[0]['messages'] = locale.format('%d', info[0]['messages'], |
|
304 True) |
|
305 info[0]['quota limit/user'] = q_limit % info[0] |
|
306 _print_info(ctx, info[0], _(u'Domain')) |
|
307 if details == u'accounts': |
|
308 _print_list(info[1], _(u'accounts')) |
|
309 elif details == u'aliasdomains': |
|
310 _print_list(info[1], _(u'alias domains')) |
|
311 elif details == u'aliases': |
|
312 _print_list(info[1], _(u'aliases')) |
|
313 elif details == u'relocated': |
|
314 _print_list(info[1], _(u'relocated users')) |
|
315 elif details == u'catchall': |
|
316 _print_list(info[1], _(u'catch-all destinations')) |
|
317 else: |
|
318 _print_list(info[1], _(u'alias domains')) |
|
319 _print_list(info[2], _(u'accounts')) |
|
320 _print_list(info[3], _(u'aliases')) |
|
321 _print_list(info[4], _(u'relocated users')) |
|
322 _print_list(info[5], _(u'catch-all destinations')) |
|
323 |
|
324 |
|
325 def domain_quota(ctx): |
|
326 """update the quota limit of the specified domain""" |
|
327 if ctx.argc < 3: |
|
328 usage(EX_MISSING_ARGS, _(u'Missing domain name and storage value.'), |
|
329 ctx.scmd) |
|
330 if ctx.argc < 4: |
|
331 usage(EX_MISSING_ARGS, _(u'Missing storage value.'), ctx.scmd) |
|
332 messages = 0 |
|
333 force = None |
|
334 try: |
|
335 bytes_ = size_in_bytes(ctx.args[3]) |
|
336 except (ValueError, TypeError): |
|
337 usage(INVALID_ARGUMENT, _(u"Invalid storage value: '%s'") % |
|
338 ctx.args[3], ctx.scmd) |
|
339 if ctx.argc < 5: |
|
340 pass |
|
341 elif ctx.argc < 6: |
|
342 try: |
|
343 messages = int(ctx.args[4]) |
|
344 except ValueError: |
|
345 if ctx.args[4].lower() != 'force': |
|
346 usage(INVALID_ARGUMENT, |
|
347 _(u"Neither a valid number of messages nor the keyword " |
|
348 u"'force': '%s'") % ctx.args[4], ctx.scmd) |
|
349 force = 'force' |
|
350 else: |
|
351 try: |
|
352 messages = int(ctx.args[4]) |
|
353 except ValueError: |
|
354 usage(INVALID_ARGUMENT, |
|
355 _(u"Not a valid number of messages: '%s'") % ctx.args[4], |
|
356 ctx.scmd) |
|
357 if ctx.args[5].lower() != 'force': |
|
358 usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[5], |
|
359 ctx.scmd) |
|
360 force = 'force' |
|
361 ctx.hdlr.domain_quotalimit(ctx.args[2].lower(), bytes_, messages, force) |
|
362 |
|
363 |
|
364 def domain_services(ctx): |
|
365 """allow all named service and block the uncredited.""" |
|
366 if ctx.argc < 3: |
|
367 usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd) |
|
368 services = [] |
|
369 force = False |
|
370 if ctx.argc is 3: |
|
371 pass |
|
372 elif ctx.argc is 4: |
|
373 arg = ctx.args[3].lower() |
|
374 if arg in SERVICES: |
|
375 services.append(arg) |
|
376 elif arg == 'force': |
|
377 force = True |
|
378 else: |
|
379 usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % arg, |
|
380 ctx.scmd) |
|
381 else: |
|
382 services.extend([service.lower() for service in ctx.args[3:-1]]) |
|
383 arg = ctx.args[-1].lower() |
|
384 if arg == 'force': |
|
385 force = True |
|
386 else: |
|
387 services.append(arg) |
|
388 unknown = [service for service in services if service not in SERVICES] |
|
389 if unknown: |
|
390 usage(INVALID_ARGUMENT, _(u'Invalid service arguments: %s') % |
|
391 ' '.join(unknown), ctx.scmd) |
|
392 ctx.hdlr.domain_services(ctx.args[2].lower(), (None, 'force')[force], |
|
393 *services) |
|
394 |
|
395 |
|
396 def domain_transport(ctx): |
|
397 """update the transport of the specified domain""" |
|
398 if ctx.argc < 3: |
|
399 usage(EX_MISSING_ARGS, _(u'Missing domain name and new transport.'), |
|
400 ctx.scmd) |
|
401 if ctx.argc < 4: |
|
402 usage(EX_MISSING_ARGS, _(u'Missing new transport.'), ctx.scmd) |
|
403 if ctx.argc < 5: |
|
404 ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3]) |
|
405 else: |
|
406 force = ctx.args[4].lower() |
|
407 if force != 'force': |
|
408 usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % force, |
|
409 ctx.scmd) |
|
410 ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3], force) |
|
411 |
|
412 |
|
413 def domain_note(ctx): |
|
414 """update the note of the given domain""" |
|
415 if ctx.argc < 3: |
|
416 usage(EX_MISSING_ARGS, _(u'Missing domain name.'), |
|
417 ctx.scmd) |
|
418 elif ctx.argc < 4: |
|
419 note = None |
|
420 else: |
|
421 note = ' '.join(ctx.args[3:]) |
|
422 ctx.hdlr.domain_note(ctx.args[2].lower(), note) |
|
423 |
|
424 |
|
425 def get_user(ctx): |
|
426 """get the address of the user with the given UID""" |
|
427 if ctx.argc < 3: |
|
428 usage(EX_MISSING_ARGS, _(u'Missing UID.'), ctx.scmd) |
|
429 _print_info(ctx, ctx.hdlr.user_by_uid(ctx.args[2]), _(u'Account')) |
|
430 |
|
431 |
|
432 def help_(ctx): |
|
433 """print help messages.""" |
|
434 if ctx.argc > 2: |
|
435 hlptpc = ctx.args[2].lower() |
|
436 if hlptpc in cmd_map: |
|
437 topic = hlptpc |
|
438 else: |
|
439 for scmd in cmd_map.itervalues(): |
|
440 if scmd.alias == hlptpc: |
|
441 topic = scmd.name |
|
442 break |
|
443 else: |
|
444 usage(INVALID_ARGUMENT, _(u"Unknown help topic: '%s'") % |
|
445 ctx.args[2], ctx.scmd) |
|
446 # FIXME |
|
447 w_err(1, "'help %s' not yet implemented." % topic, 'see also: vmm(1)') |
|
448 |
|
449 old_ii = txt_wrpr.initial_indent |
|
450 old_si = txt_wrpr.subsequent_indent |
|
451 txt_wrpr.initial_indent = ' ' |
|
452 # len(max(_overview.iterkeys(), key=len)) #Py25 |
|
453 txt_wrpr.subsequent_indent = 20 * ' ' |
|
454 order = cmd_map.keys() |
|
455 order.sort() |
|
456 |
|
457 w_std(_(u'List of available subcommands:') + '\n') |
|
458 for key in order: |
|
459 w_std('\n'.join(txt_wrpr.wrap('%-18s %s' % (key, cmd_map[key].descr)))) |
|
460 |
|
461 txt_wrpr.initial_indent = old_ii |
|
462 txt_wrpr.subsequent_indent = old_si |
|
463 txt_wrpr.initial_indent = '' |
|
464 |
|
465 |
|
466 def list_domains(ctx): |
|
467 """list all domains / search domains by pattern""" |
|
468 matching = ctx.argc > 2 |
|
469 if matching: |
|
470 gids, domains = ctx.hdlr.domain_list(ctx.args[2].lower()) |
|
471 else: |
|
472 gids, domains = ctx.hdlr.domain_list() |
|
473 _print_domain_list(gids, domains, matching) |
|
474 |
|
475 |
|
476 def list_pwschemes(ctx_unused): |
|
477 """Prints all usable password schemes and password encoding suffixes.""" |
|
478 # TODO: Remove trailing colons from keys. |
|
479 # For now it is to late, the translators has stared their work |
|
480 keys = (_(u'Usable password schemes:'), _(u'Usable encoding suffixes:')) |
|
481 old_ii, old_si = txt_wrpr.initial_indent, txt_wrpr.subsequent_indent |
|
482 txt_wrpr.initial_indent = txt_wrpr.subsequent_indent = '\t' |
|
483 txt_wrpr.width = txt_wrpr.width - 8 |
|
484 |
|
485 for key, value in zip(keys, list_schemes()): |
|
486 if key.endswith(':'): # who knows … (see TODO above) |
|
487 #key = key.rpartition(':')[0] |
|
488 key = key[:-1] # This one is for Py24 |
|
489 w_std(key, len(key) * '-') |
|
490 w_std('\n'.join(txt_wrpr.wrap(' '.join(value))), '') |
|
491 |
|
492 txt_wrpr.initial_indent, txt_wrpr.subsequent_indent = old_ii, old_si |
|
493 txt_wrpr.width = txt_wrpr.width + 8 |
|
494 |
|
495 |
|
496 def list_addresses(ctx, limit=None): |
|
497 """List all addresses / search addresses by pattern. The output can be |
|
498 limited with TYPE_ACCOUNT, TYPE_ALIAS and TYPE_RELOCATED, which can be |
|
499 bitwise ORed as a combination. Not specifying a limit is the same as |
|
500 combining all three.""" |
|
501 if limit is None: |
|
502 limit = TYPE_ACCOUNT | TYPE_ALIAS | TYPE_RELOCATED |
|
503 matching = ctx.argc > 2 |
|
504 if matching: |
|
505 gids, addresses = ctx.hdlr.address_list(limit, ctx.args[2].lower()) |
|
506 else: |
|
507 gids, addresses = ctx.hdlr.address_list(limit) |
|
508 _print_address_list(limit, gids, addresses, matching) |
|
509 |
|
510 |
|
511 def list_users(ctx): |
|
512 """list all user accounts / search user accounts by pattern""" |
|
513 return list_addresses(ctx, TYPE_ACCOUNT) |
|
514 |
|
515 def list_aliases(ctx): |
|
516 """list all aliases / search aliases by pattern""" |
|
517 return list_addresses(ctx, TYPE_ALIAS) |
|
518 |
|
519 def list_relocated(ctx): |
|
520 """list all relocated records / search relocated records by pattern""" |
|
521 return list_addresses(ctx, TYPE_RELOCATED) |
|
522 |
|
523 |
|
524 def relocated_add(ctx): |
|
525 """create a new record for a relocated user""" |
|
526 if ctx.argc < 3: |
|
527 usage(EX_MISSING_ARGS, |
|
528 _(u'Missing relocated address and destination.'), ctx.scmd) |
|
529 elif ctx.argc < 4: |
|
530 usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd) |
|
531 ctx.hdlr.relocated_add(ctx.args[2].lower(), ctx.args[3]) |
|
532 |
|
533 |
|
534 def relocated_delete(ctx): |
|
535 """delete the record of the relocated user""" |
|
536 if ctx.argc < 3: |
|
537 usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd) |
|
538 ctx.hdlr.relocated_delete(ctx.args[2].lower()) |
|
539 |
|
540 |
|
541 def relocated_info(ctx): |
|
542 """print information about a relocated user""" |
|
543 if ctx.argc < 3: |
|
544 usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd) |
|
545 relocated = ctx.args[2].lower() |
|
546 try: |
|
547 _print_relocated_info(addr=relocated, |
|
548 dest=ctx.hdlr.relocated_info(relocated)) |
|
549 except VMMError, err: |
|
550 if err.code is ACCOUNT_EXISTS: |
|
551 w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo', |
|
552 'object': relocated}) |
|
553 ctx.scmd = ctx.args[1] = 'userinfoi' |
|
554 user_info(ctx) |
|
555 elif err.code is ALIAS_EXISTS: |
|
556 w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo', |
|
557 'object': relocated}) |
|
558 ctx.scmd = ctx.args[1] = 'aliasinfo' |
|
559 alias_info(ctx) |
|
560 else: |
|
561 raise |
|
562 |
|
563 |
|
564 def user_add(ctx): |
|
565 """create a new e-mail user with the given address""" |
|
566 if ctx.argc < 3: |
|
567 usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd) |
|
568 elif ctx.argc < 4: |
|
569 password = None |
|
570 else: |
|
571 password = ctx.args[3] |
|
572 gen_pass = ctx.hdlr.user_add(ctx.args[2].lower(), password) |
|
573 if ctx.argc < 4 and gen_pass: |
|
574 w_std(_(u"Generated password: %s") % gen_pass) |
|
575 |
|
576 |
|
577 def user_delete(ctx): |
|
578 """delete the specified user""" |
|
579 if ctx.argc < 3: |
|
580 usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd) |
|
581 elif ctx.argc < 4: |
|
582 ctx.hdlr.user_delete(ctx.args[2].lower()) |
|
583 elif ctx.args[3].lower() == 'force': |
|
584 ctx.hdlr.user_delete(ctx.args[2].lower(), True) |
|
585 else: |
|
586 usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3], |
|
587 ctx.scmd) |
|
588 |
|
589 |
|
590 def user_info(ctx): |
|
591 """display information about the given address""" |
|
592 if ctx.argc < 3: |
|
593 usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd) |
|
594 if ctx.argc < 4: |
|
595 details = None |
|
596 else: |
|
597 details = ctx.args[3].lower() |
|
598 if details not in ('aliases', 'du', 'full'): |
|
599 usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details, |
|
600 ctx.scmd) |
|
601 try: |
|
602 info = ctx.hdlr.user_info(ctx.args[2].lower(), details) |
|
603 except VMMError, err: |
|
604 if err.code is ALIAS_EXISTS: |
|
605 w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo', |
|
606 'object': ctx.args[2].lower()}) |
|
607 ctx.scmd = ctx.args[1] = 'aliasinfo' |
|
608 alias_info(ctx) |
|
609 elif err.code is RELOCATED_EXISTS: |
|
610 w_err(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo', |
|
611 'object': ctx.args[2].lower()}) |
|
612 ctx.scmd = ctx.args[1] = 'relocatedinfo' |
|
613 relocated_info(ctx) |
|
614 else: |
|
615 raise |
|
616 else: |
|
617 if details in (None, 'du'): |
|
618 info['quota storage'] = _format_quota_usage(info['ql_bytes'], |
|
619 info['uq_bytes'], True, info['ql_domaindefault']) |
|
620 info['quota messages'] = _format_quota_usage(info['ql_messages'], |
|
621 info['uq_messages'], domaindefault=info['ql_domaindefault']) |
|
622 _print_info(ctx, info, _(u'Account')) |
|
623 else: |
|
624 info[0]['quota storage'] = _format_quota_usage(info[0]['ql_bytes'], |
|
625 info[0]['uq_bytes'], True, info[0]['ql_domaindefault']) |
|
626 info[0]['quota messages'] = \ |
|
627 _format_quota_usage(info[0]['ql_messages'], |
|
628 info[0]['uq_messages'], |
|
629 domaindefault=info[0]['ql_domaindefault']) |
|
630 _print_info(ctx, info[0], _(u'Account')) |
|
631 _print_list(info[1], _(u'alias addresses')) |
|
632 |
|
633 |
|
634 def user_name(ctx): |
|
635 """set or update the real name for an address""" |
|
636 if ctx.argc < 3: |
|
637 usage(EX_MISSING_ARGS, _(u"Missing e-mail address and user's name."), |
|
638 ctx.scmd) |
|
639 elif ctx.argc < 4: |
|
640 name = None |
|
641 else: |
|
642 name = ctx.args[3] |
|
643 ctx.hdlr.user_name(ctx.args[2].lower(), name) |
|
644 |
|
645 |
|
646 def user_password(ctx): |
|
647 """update the password for the given address""" |
|
648 if ctx.argc < 3: |
|
649 usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd) |
|
650 elif ctx.argc < 4: |
|
651 password = None |
|
652 else: |
|
653 password = ctx.args[3] |
|
654 ctx.hdlr.user_password(ctx.args[2].lower(), password) |
|
655 |
|
656 |
|
657 def user_note(ctx): |
|
658 """update the note of the given address""" |
|
659 if ctx.argc < 3: |
|
660 usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), |
|
661 ctx.scmd) |
|
662 elif ctx.argc < 4: |
|
663 note = None |
|
664 else: |
|
665 note = ' '.join(ctx.args[3:]) |
|
666 ctx.hdlr.user_note(ctx.args[2].lower(), note) |
|
667 |
|
668 |
|
669 def user_quota(ctx): |
|
670 """update the quota limit for the given address""" |
|
671 if ctx.argc < 3: |
|
672 usage(EX_MISSING_ARGS, _(u'Missing e-mail address and storage value.'), |
|
673 ctx.scmd) |
|
674 elif ctx.argc < 4: |
|
675 usage(EX_MISSING_ARGS, _(u'Missing storage value.'), ctx.scmd) |
|
676 if ctx.args[3] != 'domain': |
|
677 try: |
|
678 bytes_ = size_in_bytes(ctx.args[3]) |
|
679 except (ValueError, TypeError): |
|
680 usage(INVALID_ARGUMENT, _(u"Invalid storage value: '%s'") % |
|
681 ctx.args[3], ctx.scmd) |
|
682 else: |
|
683 bytes_ = ctx.args[3] |
|
684 if ctx.argc < 5: |
|
685 messages = 0 |
|
686 else: |
|
687 try: |
|
688 messages = int(ctx.args[4]) |
|
689 except ValueError: |
|
690 usage(INVALID_ARGUMENT, |
|
691 _(u"Not a valid number of messages: '%s'") % ctx.args[4], |
|
692 ctx.scmd) |
|
693 ctx.hdlr.user_quotalimit(ctx.args[2].lower(), bytes_, messages) |
|
694 |
|
695 |
|
696 def user_services(ctx): |
|
697 """allow all named service and block the uncredited.""" |
|
698 if ctx.argc < 3: |
|
699 usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd) |
|
700 services = [] |
|
701 if ctx.argc >= 4: |
|
702 services.extend([service.lower() for service in ctx.args[3:]]) |
|
703 unknown = [service for service in services if service not in SERVICES] |
|
704 if unknown and ctx.args[3] != 'domain': |
|
705 usage(INVALID_ARGUMENT, _(u'Invalid service arguments: %s') % |
|
706 ' '.join(unknown), ctx.scmd) |
|
707 ctx.hdlr.user_services(ctx.args[2].lower(), *services) |
|
708 |
|
709 |
|
710 def user_transport(ctx): |
|
711 """update the transport of the given address""" |
|
712 if ctx.argc < 3: |
|
713 usage(EX_MISSING_ARGS, _(u'Missing e-mail address and transport.'), |
|
714 ctx.scmd) |
|
715 if ctx.argc < 4: |
|
716 usage(EX_MISSING_ARGS, _(u'Missing transport.'), ctx.scmd) |
|
717 ctx.hdlr.user_transport(ctx.args[2].lower(), ctx.args[3]) |
|
718 |
|
719 |
|
720 def usage(errno, errmsg, subcommand=None): |
|
721 """print usage message for the given command or all commands. |
|
722 When errno > 0, sys,exit(errno) will interrupt the program. |
|
723 """ |
|
724 if subcommand and subcommand in cmd_map: |
|
725 w_err(errno, _(u"Error: %s") % errmsg, |
|
726 _(u"usage: ") + cmd_map[subcommand].usage) |
|
727 |
|
728 # TP: Please adjust translated words like the original text. |
|
729 # (It's a table header.) Extract from usage text: |
|
730 # usage: vmm subcommand arguments |
|
731 # short long |
|
732 # subcommand arguments |
|
733 # |
|
734 # da domainadd fqdn [transport] |
|
735 # dd domaindelete fqdn [force] |
|
736 u_head = _(u"""usage: %s subcommand arguments |
|
737 short long |
|
738 subcommand arguments\n""") % prog |
|
739 order = cmd_map.keys() |
|
740 order.sort() |
|
741 w_err(0, u_head) |
|
742 for key in order: |
|
743 scmd = cmd_map[key] |
|
744 w_err(0, ' %-5s %-19s %s' % (scmd.alias, scmd.name, scmd.args)) |
|
745 w_err(errno, '', _(u"Error: %s") % errmsg) |
|
746 |
|
747 |
|
748 def version(ctx_unused): |
|
749 """Write version and copyright information to stdout.""" |
|
750 w_std('%s, %s %s (%s %s)\nPython %s %s %s\n\n%s\n%s %s' % (prog, |
|
751 # TP: The words 'from', 'version' and 'on' are used in |
|
752 # the version information, e.g.: |
|
753 # vmm, version 0.5.2 (from 09/09/09) |
|
754 # Python 2.5.4 on FreeBSD |
|
755 _(u'version'), __version__, _(u'from'), |
|
756 strftime(locale.nl_langinfo(locale.D_FMT), |
|
757 strptime(__date__, '%Y-%m-%d')).decode(ENCODING, 'replace'), |
|
758 os.sys.version.split()[0], _(u'on'), os.uname()[0], |
|
759 __copyright__, prog, |
|
760 _(u'is free software and comes with ABSOLUTELY NO WARRANTY.'))) |
|
761 |
|
762 |
|
763 def update_cmd_map(): |
|
764 """Update the cmd_map, after gettext's _ was installed.""" |
|
765 cmd = Command |
|
766 cmd_map.update({ |
|
767 # Account commands |
|
768 'getuser': cmd('getuser', 'gu', get_user, 'uid', |
|
769 _(u'get the address of the user with the given UID')), |
|
770 'useradd': cmd('useradd', 'ua', user_add, 'address [password]', |
|
771 _(u'create a new e-mail user with the given address')), |
|
772 'userdelete': cmd('userdelete', 'ud', user_delete, 'address [force]', |
|
773 _(u'delete the specified user')), |
|
774 'userinfo': cmd('userinfo', 'ui', user_info, 'address [details]', |
|
775 _(u'display information about the given address')), |
|
776 'username': cmd('username', 'un', user_name, 'address name', |
|
777 _(u'set or update the real name for an address')), |
|
778 'userpassword': cmd('userpassword', 'up', user_password, |
|
779 'address [password]', |
|
780 _(u'update the password for the given address')), |
|
781 'userquota': cmd('userquota', 'uq', user_quota, |
|
782 'address storage [messages] | address domain', |
|
783 _(u'update the quota limit for the given address')), |
|
784 'userservices': cmd('userservices', 'us', user_services, |
|
785 'address [service ...] | address domain', |
|
786 _(u'enables the specified services and disables all ' |
|
787 u'not specified services')), |
|
788 'usertransport': cmd('usertransport', 'ut', user_transport, |
|
789 'address transport | address domain', |
|
790 _(u'update the transport of the given address')), |
|
791 'usernote': cmd('usernote', 'uo', user_note, |
|
792 'address note', |
|
793 _(u'update the note of the given address')), |
|
794 # Alias commands |
|
795 'aliasadd': cmd('aliasadd', 'aa', alias_add, 'address destination ...', |
|
796 _(u'create a new alias e-mail address with one or more ' |
|
797 u'destinations')), |
|
798 'aliasdelete': cmd('aliasdelete', 'ad', alias_delete, |
|
799 'address [destination]', |
|
800 _(u'delete the specified alias e-mail address or one ' |
|
801 u'of its destinations')), |
|
802 'aliasinfo': cmd('aliasinfo', 'ai', alias_info, 'address', |
|
803 _(u'show the destination(s) of the specified alias')), |
|
804 # AliasDomain commands |
|
805 'aliasdomainadd': cmd('aliasdomainadd', 'ada', aliasdomain_add, |
|
806 'fqdn destination', |
|
807 _(u'create a new alias for an existing domain')), |
|
808 'aliasdomaindelete': cmd('aliasdomaindelete', 'add', aliasdomain_delete, |
|
809 'fqdn', _(u'delete the specified alias domain')), |
|
810 'aliasdomaininfo': cmd('aliasdomaininfo', 'adi', aliasdomain_info, 'fqdn', |
|
811 _(u'show the destination of the given alias domain')), |
|
812 'aliasdomainswitch': cmd('aliasdomainswitch', 'ads', aliasdomain_switch, |
|
813 'fqdn destination', _(u'assign the given alias ' |
|
814 'domain to an other domain')), |
|
815 # CatchallAlias commands |
|
816 'catchalladd': cmd('catchalladd', 'caa', catchall_add, |
|
817 'fqdn destination ...', |
|
818 _(u'add one or more catch-all destinations for a ' |
|
819 u'domain')), |
|
820 'catchalldelete': cmd('catchalldelete', 'cad', catchall_delete, |
|
821 'fqdn [destination]', |
|
822 _(u'delete the specified catch-all destination or all ' |
|
823 u'of a domain\'s destinations')), |
|
824 'catchallinfo': cmd('catchallinfo', 'cai', catchall_info, 'fqdn', |
|
825 _(u'show the catch-all destination(s) of the specified domain')), |
|
826 # Domain commands |
|
827 'domainadd': cmd('domainadd', 'da', domain_add, 'fqdn [transport]', |
|
828 _(u'create a new domain')), |
|
829 'domaindelete': cmd('domaindelete', 'dd', domain_delete, 'fqdn [force]', |
|
830 _(u'delete the given domain and all its alias domains')), |
|
831 'domaininfo': cmd('domaininfo', 'di', domain_info, 'fqdn [details]', |
|
832 _(u'display information about the given domain')), |
|
833 'domainquota': cmd('domainquota', 'dq', domain_quota, |
|
834 'fqdn storage [messages] [force]', |
|
835 _(u'update the quota limit of the specified domain')), |
|
836 'domainservices': cmd('domainservices', 'ds', domain_services, |
|
837 'fqdn [service ...] [force]', |
|
838 _(u'enables the specified services and disables all ' |
|
839 u'not specified services of the given domain')), |
|
840 'domaintransport': cmd('domaintransport', 'dt', domain_transport, |
|
841 'fqdn transport [force]', |
|
842 _(u'update the transport of the specified domain')), |
|
843 'domainnote': cmd('domainnote', 'do', domain_note, |
|
844 'fqdn note', |
|
845 _(u'update the note of the given domain')), |
|
846 # List commands |
|
847 'listdomains': cmd('listdomains', 'ld', list_domains, '[pattern]', |
|
848 _(u'list all domains or search for domains by pattern')), |
|
849 'listaddresses': cmd('listaddresses', 'll', list_addresses, '[pattern]', |
|
850 _(u'list all addresses or search for addresses by pattern')), |
|
851 'listusers': cmd('listusers', 'lu', list_users, '[pattern]', |
|
852 _(u'list all user accounts or search for accounts by pattern')), |
|
853 'listaliases': cmd('listaliases', 'la', list_aliases, '[pattern]', |
|
854 _(u'list all aliases or search for aliases by pattern')), |
|
855 'listrelocated': cmd('listrelocated', 'lr', list_relocated, '[pattern]', |
|
856 _(u'list all relocated entries or search for entries by pattern')), |
|
857 # Relocated commands |
|
858 'relocatedadd': cmd('relocatedadd', 'ra', relocated_add, |
|
859 'address newaddress', |
|
860 _(u'create a new record for a relocated user')), |
|
861 'relocateddelete': cmd('relocateddelete', 'rd', relocated_delete, |
|
862 'address', |
|
863 _(u'delete the record of the relocated user')), |
|
864 'relocatedinfo': cmd('relocatedinfo', 'ri', relocated_info, 'address', |
|
865 _(u'print information about a relocated user')), |
|
866 # cli commands |
|
867 'configget': cmd('configget', 'cg', config_get, 'option', |
|
868 _('show the actual value of the configuration option')), |
|
869 'configset': cmd('configset', 'cs', config_set, 'option value', |
|
870 _('set a new value for the configuration option')), |
|
871 'configure': cmd('configure', 'cf', configure, '[section]', |
|
872 _(u'start interactive configuration modus')), |
|
873 'listpwschemes': cmd('listpwschemes', 'lp', list_pwschemes, '', |
|
874 _(u'lists all usable password schemes and password ' |
|
875 u'encoding suffixes')), |
|
876 'help': cmd('help', 'h', help_, '[subcommand]', |
|
877 _(u'show a help overview or help for the given subcommand')), |
|
878 'version': cmd('version', 'v', version, '', |
|
879 _(u'show version and copyright information')), |
|
880 }) |
|
881 |
|
882 |
|
883 def _get_order(ctx): |
|
884 """returns a tuple with (key, 1||0) tuples. Used by functions, which |
|
885 get a dict from the handler.""" |
|
886 order = () |
|
887 if ctx.scmd == 'domaininfo': |
|
888 order = ((u'domain name', 0), (u'gid', 1), (u'domain directory', 0), |
|
889 (u'quota limit/user', 0), (u'active services', 0), |
|
890 (u'transport', 0), (u'alias domains', 0), (u'accounts', 0), |
|
891 (u'aliases', 0), (u'relocated', 0), (u'catch-all dests', 0)) |
|
892 elif ctx.scmd == 'userinfo': |
|
893 if ctx.argc == 4 and ctx.args[3] != u'aliases' or \ |
|
894 ctx.cget('account.disk_usage'): |
|
895 order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1), |
|
896 (u'home', 0), (u'mail_location', 0), |
|
897 (u'quota storage', 0), (u'quota messages', 0), |
|
898 (u'disk usage', 0), (u'transport', 0), (u'smtp', 1), |
|
899 (u'pop3', 1), (u'imap', 1), (u'sieve', 1)) |
|
900 else: |
|
901 order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1), |
|
902 (u'home', 0), (u'mail_location', 0), |
|
903 (u'quota storage', 0), (u'quota messages', 0), |
|
904 (u'transport', 0), (u'smtp', 1), (u'pop3', 1), |
|
905 (u'imap', 1), (u'sieve', 1)) |
|
906 elif ctx.scmd == 'getuser': |
|
907 order = ((u'uid', 1), (u'gid', 1), (u'address', 0)) |
|
908 return order |
|
909 |
|
910 |
|
911 def _format_quota_usage(limit, used, human=False, domaindefault=False): |
|
912 """Put quota's limit / usage / percentage in a formatted string.""" |
|
913 if human: |
|
914 q_usage = { |
|
915 'used': human_size(used), |
|
916 'limit': human_size(limit), |
|
917 } |
|
918 else: |
|
919 q_usage = { |
|
920 'used': locale.format('%d', used, True), |
|
921 'limit': locale.format('%d', limit, True), |
|
922 } |
|
923 if limit: |
|
924 q_usage['percent'] = locale.format('%6.2f', 100. / limit * used, True) |
|
925 else: |
|
926 q_usage['percent'] = locale.format('%6.2f', 0, True) |
|
927 # Py25: fmt = format_domain_default if domaindefault else lambda s: s |
|
928 if domaindefault: |
|
929 fmt = format_domain_default |
|
930 else: |
|
931 fmt = lambda s: s |
|
932 return fmt(_(u'[%(percent)s%%] %(used)s/%(limit)s') % q_usage) |
|
933 |
|
934 |
|
935 def _print_info(ctx, info, title): |
|
936 """Print info dicts.""" |
|
937 # TP: used in e.g. 'Domain information' or 'Account information' |
|
938 msg = u'%s %s' % (title, _(u'information')) |
|
939 w_std(msg, u'-' * len(msg)) |
|
940 for key, upper in _get_order(ctx): |
|
941 if upper: |
|
942 w_std(u'\t%s: %s' % (key.upper().ljust(17, u'.'), info[key])) |
|
943 else: |
|
944 w_std(u'\t%s: %s' % (key.title().ljust(17, u'.'), info[key])) |
|
945 print |
|
946 note = info.get('note', None) |
|
947 if note is not None: |
|
948 _print_note(note) |
|
949 |
|
950 def _print_note(note): |
|
951 msg = _(u'Note') |
|
952 w_std(msg, u'-' * len(msg)) |
|
953 old_ii = txt_wrpr.initial_indent |
|
954 old_si = txt_wrpr.subsequent_indent |
|
955 txt_wrpr.initial_indent = txt_wrpr.subsequent_indent = '\t' |
|
956 txt_wrpr.width -= 8 |
|
957 for para in note.split('\n'): |
|
958 w_std(txt_wrpr.fill(para)) |
|
959 txt_wrpr.width += 8 |
|
960 txt_wrpr.subsequent_indent = old_si |
|
961 txt_wrpr.initial_indent = old_ii |
|
962 |
|
963 def _print_list(alist, title): |
|
964 """Print a list.""" |
|
965 # TP: used in e.g. 'Existing alias addresses' or 'Existing accounts' |
|
966 msg = u'%s %s' % (_(u'Existing'), title) |
|
967 w_std(msg, u'-' * len(msg)) |
|
968 if alist: |
|
969 if title != _(u'alias domains'): |
|
970 w_std(*(u'\t%s' % item for item in alist)) |
|
971 else: |
|
972 for domain in alist: |
|
973 if not domain.startswith('xn--'): |
|
974 w_std(u'\t%s' % domain) |
|
975 else: |
|
976 w_std(u'\t%s (%s)' % (domain, domain.decode('idna'))) |
|
977 print |
|
978 else: |
|
979 w_std(_(u'\tNone'), '') |
|
980 |
|
981 |
|
982 def _print_aliase_info(alias, destinations): |
|
983 """Print the alias address and all its destinations""" |
|
984 title = _(u'Alias information') |
|
985 w_std(title, u'-' * len(title)) |
|
986 w_std(_(u'\tMail for %s will be redirected to:') % alias) |
|
987 w_std(*(u'\t * %s' % dest for dest in destinations)) |
|
988 print |
|
989 |
|
990 |
|
991 def _print_catchall_info(domain, destinations): |
|
992 """Print the catchall destinations of a domain""" |
|
993 title = _(u'Catch-all information') |
|
994 w_std(title, u'-' * len(title)) |
|
995 w_std(_(u'\tMail to unknown localparts in domain %s will be sent to:') |
|
996 % domain) |
|
997 w_std(*(u'\t * %s' % dest for dest in destinations)) |
|
998 print |
|
999 |
|
1000 |
|
1001 def _print_relocated_info(**kwargs): |
|
1002 """Print the old and new addresses of a relocated user.""" |
|
1003 title = _(u'Relocated information') |
|
1004 w_std(title, u'-' * len(title)) |
|
1005 w_std(_(u"\tUser '%(addr)s' has moved to '%(dest)s'") % kwargs, '') |
|
1006 |
|
1007 |
|
1008 def _format_domain(domain, main=True): |
|
1009 """format (prefix/convert) the domain name.""" |
|
1010 if domain.startswith('xn--'): |
|
1011 domain = u'%s (%s)' % (domain, domain.decode('idna')) |
|
1012 if main: |
|
1013 return u'\t[+] %s' % domain |
|
1014 return u'\t[-] %s' % domain |
|
1015 |
|
1016 |
|
1017 def _print_domain_list(dids, domains, matching): |
|
1018 """Print a list of (matching) domains/alias domains.""" |
|
1019 if matching: |
|
1020 title = _(u'Matching domains') |
|
1021 else: |
|
1022 title = _(u'Existing domains') |
|
1023 w_std(title, '-' * len(title)) |
|
1024 if domains: |
|
1025 for did in dids: |
|
1026 if domains[did][0] is not None: |
|
1027 w_std(_format_domain(domains[did][0])) |
|
1028 if len(domains[did]) > 1: |
|
1029 w_std(*(_format_domain(a, False) for a in domains[did][1:])) |
|
1030 else: |
|
1031 w_std(_('\tNone')) |
|
1032 print |
|
1033 |
|
1034 |
|
1035 def _print_address_list(which, dids, addresses, matching): |
|
1036 """Print a list of (matching) addresses.""" |
|
1037 _trans = { TYPE_ACCOUNT : _('user accounts') |
|
1038 , TYPE_ALIAS : _('aliases') |
|
1039 , TYPE_RELOCATED : _('relocated entries') |
|
1040 , TYPE_ACCOUNT | TYPE_ALIAS |
|
1041 : _('user accounts and aliases') |
|
1042 , TYPE_ACCOUNT | TYPE_RELOCATED |
|
1043 : _('user accounts and relocated entries') |
|
1044 , TYPE_ALIAS | TYPE_RELOCATED |
|
1045 : _('aliases and relocated entries') |
|
1046 , TYPE_ACCOUNT | TYPE_ALIAS | TYPE_RELOCATED : _('addresses') |
|
1047 } |
|
1048 try: |
|
1049 if matching: |
|
1050 title = _(u'Matching %s') % _trans[which] |
|
1051 else: |
|
1052 title = _(u'Existing %s') % _trans[which] |
|
1053 w_std(title, '-' * len(title)) |
|
1054 except KeyError: |
|
1055 raise VMMError(_("Invalid address type for list: '%s'") % which, |
|
1056 INVALID_ARGUMENT) |
|
1057 if addresses: |
|
1058 if which & (which - 1) == 0: |
|
1059 # only one type is requested, so no type indicator |
|
1060 _trans = { TYPE_ACCOUNT : _('') |
|
1061 , TYPE_ALIAS : _('') |
|
1062 , TYPE_RELOCATED : _('') |
|
1063 } |
|
1064 else: |
|
1065 _trans = { TYPE_ACCOUNT : _('u') |
|
1066 , TYPE_ALIAS : _('a') |
|
1067 , TYPE_RELOCATED : _('r') |
|
1068 } |
|
1069 for did in dids: |
|
1070 for addr, atype, aliasdomain in addresses[did]: |
|
1071 if aliasdomain: |
|
1072 leader = '[%s-]' % _trans[atype] |
|
1073 else: |
|
1074 leader = '[%s+]' % _trans[atype] |
|
1075 w_std('\t%s %s' % (leader, addr)) |
|
1076 else: |
|
1077 w_std(_('\tNone')) |
|
1078 print |
|
1079 |
|
1080 |
|
1081 def _print_aliasdomain_info(info): |
|
1082 """Print alias domain information.""" |
|
1083 title = _(u'Alias domain information') |
|
1084 for key in ('alias', 'domain'): |
|
1085 if info[key].startswith('xn--'): |
|
1086 info[key] = u'%s (%s)' % (info[key], info[key].decode('idna')) |
|
1087 w_std(title, '-' * len(title), |
|
1088 _('\tThe alias domain %(alias)s belongs to:\n\t * %(domain)s') % |
|
1089 info, '') |
|
1090 |
|
1091 del _ |