49 |
49 |
50 _ = lambda msg: msg |
50 _ = lambda msg: msg |
51 cfg_dget = lambda option: None |
51 cfg_dget = lambda option: None |
52 _sys_rand = SystemRandom() |
52 _sys_rand = SystemRandom() |
53 _choice = _sys_rand.choice |
53 _choice = _sys_rand.choice |
54 _get_salt = lambda s_len: ''.join(_choice(SALTCHARS) for x in xrange(s_len)) |
54 _get_salt = lambda s_len: ''.join(_choice(SALTCHARS) for x in range(s_len)) |
55 |
55 |
56 |
56 |
57 def _dovecotpw(password, scheme, encoding): |
57 def _dovecotpw(password, scheme, encoding): |
58 """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns |
58 """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns |
59 the hashed password: {scheme[.encoding]}hash |
59 the hashed password: {scheme[.encoding]}hash |
79 """Returns an new MD4-hash object if supported by the hashlib or |
79 """Returns an new MD4-hash object if supported by the hashlib or |
80 provided by PyCrypto - otherwise `None`. |
80 provided by PyCrypto - otherwise `None`. |
81 """ |
81 """ |
82 try: |
82 try: |
83 return hashlib.new('md4') |
83 return hashlib.new('md4') |
84 except ValueError, err: |
84 except ValueError as err: |
85 if str(err) == 'unsupported hash type': |
85 if str(err) == 'unsupported hash type': |
86 try: |
86 try: |
87 from Crypto.Hash import MD4 |
87 from Crypto.Hash import MD4 |
88 return MD4.new() |
88 return MD4.new() |
89 except ImportError: |
89 except ImportError: |
328 the used Dovecot version and features of the libc). |
328 the used Dovecot version and features of the libc). |
329 `encodings` is a tuple with all usable encoding suffixes. The tuple may |
329 `encodings` is a tuple with all usable encoding suffixes. The tuple may |
330 be empty. |
330 be empty. |
331 """ |
331 """ |
332 dcv = cfg_dget('misc.dovecot_version') |
332 dcv = cfg_dget('misc.dovecot_version') |
333 schemes = (k for (k, v) in _scheme_info.iteritems() if v[1] <= dcv) |
333 schemes = (k for (k, v) in _scheme_info.items() if v[1] <= dcv) |
334 encodings = ('.B64', '.BASE64', '.HEX') if dcv >= 0x10100a01 else () |
334 encodings = ('.B64', '.BASE64', '.HEX') if dcv >= 0x10100a01 else () |
335 return schemes, encodings |
335 return schemes, encodings |
336 |
336 |
337 |
337 |
338 def verify_scheme(scheme): |
338 def verify_scheme(scheme): |
348 Raises a `VMMError` if the password scheme: |
348 Raises a `VMMError` if the password scheme: |
349 * is unknown |
349 * is unknown |
350 * depends on a newer Dovecot version |
350 * depends on a newer Dovecot version |
351 * has a unknown encoding suffix |
351 * has a unknown encoding suffix |
352 """ |
352 """ |
353 assert isinstance(scheme, basestring), 'Not a str/unicode: %r' % scheme |
353 assert isinstance(scheme, str), 'Not a str/unicode: %r' % scheme |
354 scheme_encoding = scheme.upper().split('.') |
354 scheme_encoding = scheme.upper().split('.') |
355 scheme = scheme_encoding[0] |
355 scheme = scheme_encoding[0] |
356 if scheme not in _scheme_info: |
356 if scheme not in _scheme_info: |
357 raise VMMError(_(u"Unsupported password scheme: '%s'") % scheme, |
357 raise VMMError(_("Unsupported password scheme: '%s'") % scheme, |
358 VMM_ERROR) |
358 VMM_ERROR) |
359 if cfg_dget('misc.dovecot_version') < _scheme_info[scheme][1]: |
359 if cfg_dget('misc.dovecot_version') < _scheme_info[scheme][1]: |
360 raise VMMError(_(u"The password scheme '%(scheme)s' requires Dovecot " |
360 raise VMMError(_("The password scheme '%(scheme)s' requires Dovecot " |
361 u">= v%(version)s.") % {'scheme': scheme, |
361 ">= v%(version)s.") % {'scheme': scheme, |
362 'version': version_str(_scheme_info[scheme][1])}, |
362 'version': version_str(_scheme_info[scheme][1])}, |
363 VMM_ERROR) |
363 VMM_ERROR) |
364 if len(scheme_encoding) > 1: |
364 if len(scheme_encoding) > 1: |
365 if cfg_dget('misc.dovecot_version') < 0x10100a01: |
365 if cfg_dget('misc.dovecot_version') < 0x10100a01: |
366 raise VMMError(_(u'Encoding suffixes for password schemes require ' |
366 raise VMMError(_('Encoding suffixes for password schemes require ' |
367 u'Dovecot >= v1.1.alpha1.'), VMM_ERROR) |
367 'Dovecot >= v1.1.alpha1.'), VMM_ERROR) |
368 if scheme_encoding[1] not in ('B64', 'BASE64', 'HEX'): |
368 if scheme_encoding[1] not in ('B64', 'BASE64', 'HEX'): |
369 raise VMMError(_(u"Unsupported password encoding: '%s'") % |
369 raise VMMError(_("Unsupported password encoding: '%s'") % |
370 scheme_encoding[1], VMM_ERROR) |
370 scheme_encoding[1], VMM_ERROR) |
371 encoding = scheme_encoding[1] |
371 encoding = scheme_encoding[1] |
372 else: |
372 else: |
373 encoding = None |
373 encoding = None |
374 return scheme, encoding |
374 return scheme, encoding |
379 |
379 |
380 If no *scheme* is given the password scheme from the configuration will |
380 If no *scheme* is given the password scheme from the configuration will |
381 be used for the hash generation. When 'DIGEST-MD5' is used as scheme, |
381 be used for the hash generation. When 'DIGEST-MD5' is used as scheme, |
382 also an EmailAddress instance must be given as *user* argument. |
382 also an EmailAddress instance must be given as *user* argument. |
383 """ |
383 """ |
384 if not isinstance(password, basestring): |
384 if not isinstance(password, str): |
385 raise TypeError('Password is not a string: %r' % password) |
385 raise TypeError('Password is not a string: %r' % password) |
386 if isinstance(password, unicode): |
386 if isinstance(password, str): |
387 password = password.encode(ENCODING) |
387 password = password.encode(ENCODING) |
388 password = password.strip() |
388 password = password.strip() |
389 if not password: |
389 if not password: |
390 raise ValueError("Could not accept empty password.") |
390 raise ValueError("Could not accept empty password.") |
391 if scheme is None: |
391 if scheme is None: |