19 try: |
19 try: |
20 import hashlib |
20 import hashlib |
21 except ImportError: |
21 except ImportError: |
22 from VirtualMailManager.pycompat import hashlib |
22 from VirtualMailManager.pycompat import hashlib |
23 |
23 |
24 from VirtualMailManager import ENCODING, Configuration |
24 from VirtualMailManager import ENCODING |
25 from VirtualMailManager.EmailAddress import EmailAddress |
25 from VirtualMailManager.EmailAddress import EmailAddress |
26 from VirtualMailManager.common import get_unicode, version_str |
26 from VirtualMailManager.common import get_unicode, version_str |
27 from VirtualMailManager.constants.ERROR import VMM_ERROR |
27 from VirtualMailManager.constants.ERROR import VMM_ERROR |
28 from VirtualMailManager.errors import VMMError |
28 from VirtualMailManager.errors import VMMError |
29 |
29 |
32 PASSWDCHARS = '._-+#*23456789abcdefghikmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ' |
32 PASSWDCHARS = '._-+#*23456789abcdefghikmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ' |
33 DEFAULT_B64 = (None, 'B64', 'BASE64') |
33 DEFAULT_B64 = (None, 'B64', 'BASE64') |
34 DEFAULT_HEX = (None, 'HEX') |
34 DEFAULT_HEX = (None, 'HEX') |
35 |
35 |
36 _ = lambda msg: msg |
36 _ = lambda msg: msg |
|
37 cfg_dget = lambda option: None |
37 _get_salt = lambda s_len: ''.join(choice(SALTCHARS) for x in xrange(s_len)) |
38 _get_salt = lambda s_len: ''.join(choice(SALTCHARS) for x in xrange(s_len)) |
38 |
39 |
39 |
40 |
40 def _dovecotpw(password, scheme, encoding): |
41 def _dovecotpw(password, scheme, encoding): |
41 """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns |
42 """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns |
42 the hashed password: {scheme[.encoding]}hash |
43 the hashed password: {scheme[.encoding]}hash |
43 """ |
44 """ |
44 if encoding: |
45 if encoding: |
45 scheme = '.'.join((scheme, encoding)) |
46 scheme = '.'.join((scheme, encoding)) |
46 cmd_args = [Configuration.dget('bin.dovecotpw'), '-s', scheme, '-p', |
47 cmd_args = [cfg_dget('bin.dovecotpw'), '-s', scheme, '-p', |
47 get_unicode(password)] |
48 get_unicode(password)] |
48 if Configuration.dget('misc.dovecot_version') >= 0x20000a01: |
49 if cfg_dget('misc.dovecot_version') >= 0x20000a01: |
49 cmd_args.insert(1, 'pw') |
50 cmd_args.insert(1, 'pw') |
50 process = Popen(cmd_args, stdout=PIPE, stderr=PIPE) |
51 process = Popen(cmd_args, stdout=PIPE, stderr=PIPE) |
51 stdout, stderr = process.communicate() |
52 stdout, stderr = process.communicate() |
52 if process.returncode: |
53 if process.returncode: |
53 raise VMMError(stderr.strip(), VMM_ERROR) |
54 raise VMMError(stderr.strip(), VMM_ERROR) |
141 # empty auth_realms setting in dovecot.conf and user@domain.tld |
142 # empty auth_realms setting in dovecot.conf and user@domain.tld |
142 # usernames. So we have to generate different hashes for different |
143 # usernames. So we have to generate different hashes for different |
143 # versions. See also: |
144 # versions. See also: |
144 # http://dovecot.org/list/dovecot-news/2009-March/000103.html |
145 # http://dovecot.org/list/dovecot-news/2009-March/000103.html |
145 # http://hg.dovecot.org/dovecot-1.1/rev/2b0043ba89ae |
146 # http://hg.dovecot.org/dovecot-1.1/rev/2b0043ba89ae |
146 if Configuration.dget('misc.dovecot_version') >= 0x1010cf00: |
147 if cfg_dget('misc.dovecot_version') >= 0x1010cf00: |
147 md5.update('%s:%s:' % (user.localpart, user.domainname)) |
148 md5.update('%s:%s:' % (user.localpart, user.domainname)) |
148 else: |
149 else: |
149 md5.update('%s::' % user) |
150 md5.update('%s::' % user) |
150 md5.update(password) |
151 md5.update(password) |
151 if (scheme in ('PLAIN-MD5', 'DIGEST-MD5') and encoding in DEFAULT_HEX) \ |
152 if (scheme in ('PLAIN-MD5', 'DIGEST-MD5') and encoding in DEFAULT_HEX) \ |
288 |
289 |
289 If no *scheme* is given the password scheme from the configuration will |
290 If no *scheme* is given the password scheme from the configuration will |
290 be used for the hash generation. When 'DIGEST-MD5' is used as scheme, |
291 be used for the hash generation. When 'DIGEST-MD5' is used as scheme, |
291 also an EmailAddress instance must be given as *user* argument. |
292 also an EmailAddress instance must be given as *user* argument. |
292 """ |
293 """ |
293 assert Configuration is not None |
|
294 if not isinstance(password, basestring): |
294 if not isinstance(password, basestring): |
295 raise TypeError('Password is not a string: %r' % password) |
295 raise TypeError('Password is not a string: %r' % password) |
296 if isinstance(password, unicode): |
296 if isinstance(password, unicode): |
297 password = password.encode(ENCODING) |
297 password = password.encode(ENCODING) |
298 password = password.strip() |
298 password = password.strip() |
299 if not password: |
299 if not password: |
300 raise ValueError("Couldn't accept empty password.") |
300 raise ValueError("Couldn't accept empty password.") |
301 if scheme is None: |
301 if scheme is None: |
302 scheme = Configuration.dget('misc.password_scheme') |
302 scheme = cfg_dget('misc.password_scheme') |
303 scheme_encoding = scheme.split('.') |
303 scheme_encoding = scheme.split('.') |
304 scheme = scheme_encoding[0].upper() |
304 scheme = scheme_encoding[0].upper() |
305 if not scheme in _scheme_info: |
305 if not scheme in _scheme_info: |
306 raise VMMError(_(u"Unsupported password scheme: '%s'") % scheme, |
306 raise VMMError(_(u"Unsupported password scheme: '%s'") % scheme, |
307 VMM_ERROR) |
307 VMM_ERROR) |
308 if Configuration.dget('misc.dovecot_version') < _scheme_info[scheme][1]: |
308 if cfg_dget('misc.dovecot_version') < _scheme_info[scheme][1]: |
309 raise VMMError(_(u"The scheme '%s' requires Dovecot >= v%s") % |
309 raise VMMError(_(u"The scheme '%s' requires Dovecot >= v%s") % |
310 (scheme, version_str(_scheme_info[scheme][1])), |
310 (scheme, version_str(_scheme_info[scheme][1])), |
311 VMM_ERROR) |
311 VMM_ERROR) |
312 if len(scheme_encoding) > 1: |
312 if len(scheme_encoding) > 1: |
313 if Configuration.dget('misc.dovecot_version') < 0x10100a01: |
313 if cfg_dget('misc.dovecot_version') < 0x10100a01: |
314 raise VMMError(_(u'Encoding suffixes for password schemes require \ |
314 raise VMMError(_(u'Encoding suffixes for password schemes require \ |
315 Dovecot >= v1.1.alpha1'), |
315 Dovecot >= v1.1.alpha1'), |
316 VMM_ERROR) |
316 VMM_ERROR) |
317 if scheme_encoding[1].upper() not in ('B64', 'BASE64', 'HEX'): |
317 if scheme_encoding[1].upper() not in ('B64', 'BASE64', 'HEX'): |
318 raise ValueError('Unsupported encoding: %r' % scheme_encoding[1]) |
318 raise ValueError('Unsupported encoding: %r' % scheme_encoding[1]) |
329 """Generates a plain text random password. |
329 """Generates a plain text random password. |
330 |
330 |
331 The length of the password can be configured in the ``vmm.cfg`` |
331 The length of the password can be configured in the ``vmm.cfg`` |
332 (account.password_length). |
332 (account.password_length). |
333 """ |
333 """ |
334 assert Configuration is not None |
|
335 pw_chars = list(PASSWDCHARS) |
334 pw_chars = list(PASSWDCHARS) |
336 shuffle(pw_chars) |
335 shuffle(pw_chars) |
337 pw_len = Configuration.dget('account.password_length') |
336 pw_len = cfg_dget('account.password_length') |
338 if pw_len < 8: |
337 if pw_len < 8: |
339 pw_len = 8 |
338 pw_len = 8 |
340 return ''.join(choice(pw_chars) for x in xrange(pw_len)) |
339 return ''.join(choice(pw_chars) for x in xrange(pw_len)) |
341 |
340 |
342 del _ |
341 del _, cfg_dget |