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