37 |
37 |
38 _ = lambda msg: msg |
38 _ = lambda msg: msg |
39 cfg_dget = lambda option: None |
39 cfg_dget = lambda option: None |
40 _sys_rand = SystemRandom() |
40 _sys_rand = SystemRandom() |
41 _get_salt = lambda salt_len: ''.join(_sys_rand.sample(SALTCHARS, salt_len)) |
41 _get_salt = lambda salt_len: ''.join(_sys_rand.sample(SALTCHARS, salt_len)) |
42 |
|
43 |
|
44 def _test_crypt_algorithms(): |
|
45 """Check for Blowfish/SHA-256/SHA-512 support in crypt.crypt().""" |
|
46 blowfish_ = sha256_ = sha512_ = False |
|
47 _blowfish = '$2a$04$0123456789abcdefABCDE.N.drYX5yIAL1LkTaaZotW3yI0hQhZru' |
|
48 _sha256 = '$5$rounds=1000$0123456789abcdef$K/DksR0DT01hGc8g/kt9McEgrbFMKi\ |
|
49 9qrb1jehe7hn4' |
|
50 _sha512 = '$6$rounds=1000$0123456789abcdef$ZIAd5WqfyLkpvsVCVUU1GrvqaZTqvh\ |
|
51 JoouxdSqJO71l9Ld3tVrfOatEjarhghvEYADkq//LpDnTeO90tcbtHR1' |
|
52 |
|
53 if crypt('08/15!test~4711', '$2a$04$0123456789abcdefABCDEF$') == _blowfish: |
|
54 blowfish_ = True |
|
55 if crypt('08/15!test~4711', '$5$rounds=1000$0123456789abcdef$') == _sha256: |
|
56 sha256_ = True |
|
57 if crypt('08/15!test~4711', '$6$rounds=1000$0123456789abcdef$') == _sha512: |
|
58 sha512_ = True |
|
59 return blowfish_, sha256_, sha512_ |
|
60 |
|
61 CRYPT_BLOWFISH, CRYPT_SHA256, CRYPT_SHA512 = _test_crypt_algorithms() |
|
62 |
42 |
63 |
43 |
64 def _dovecotpw(password, scheme, encoding): |
44 def _dovecotpw(password, scheme, encoding): |
65 """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns |
45 """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns |
66 the hashed password: {scheme[.encoding]}hash |
46 the hashed password: {scheme[.encoding]}hash |
141 elif rounds > 31: |
121 elif rounds > 31: |
142 rounds = 31 |
122 rounds = 31 |
143 return '$2a$%02d$%s' % (rounds, _get_salt(22)) |
123 return '$2a$%02d$%s' % (rounds, _get_salt(22)) |
144 |
124 |
145 |
125 |
146 def _get_crypt_shaxxx_salt(crypt_id): |
126 def _get_crypt_sha2_salt(crypt_id): |
147 """Generates a salt for crypt using the SHA-256 or SHA-512 encryption |
127 """Generates a salt for crypt using the SHA-256 or SHA-512 encryption |
148 method. |
128 method. |
149 *crypt_id* must be either `5` (SHA-256) or `6` (SHA1-512). |
129 *crypt_id* must be either `5` (SHA-256) or `6` (SHA1-512). |
150 """ |
130 """ |
151 assert crypt_id in (5, 6), 'invalid crypt id: %r' % crypt_id |
131 assert crypt_id in (5, 6), 'invalid crypt id: %r' % crypt_id |
155 rounds = cfg_dget('misc.crypt_sha256_rounds') |
135 rounds = cfg_dget('misc.crypt_sha256_rounds') |
156 if rounds < 1000: |
136 if rounds < 1000: |
157 rounds = 1000 |
137 rounds = 1000 |
158 elif rounds > 999999999: |
138 elif rounds > 999999999: |
159 rounds = 999999999 |
139 rounds = 999999999 |
|
140 if rounds == 5000: |
|
141 return '$%d$%s' % (crypt_id, _get_salt(16)) |
160 return '$%d$rounds=%d$%s' % (crypt_id, rounds, _get_salt(16)) |
142 return '$%d$rounds=%d$%s' % (crypt_id, rounds, _get_salt(16)) |
161 |
143 |
162 |
144 |
163 def _crypt_hash(password, scheme, encoding): |
145 def _crypt_hash(password, scheme, encoding): |
164 """Generates (encoded) CRYPT/MD5/MD5-CRYPT hashes.""" |
146 """Generates (encoded) CRYPT/MD5/{BLF,MD5,SHA{256,512}}-CRYPT hashes.""" |
165 if scheme == 'CRYPT': |
147 if scheme == 'CRYPT': |
166 if CRYPT_BLOWFISH and cfg_dget('misc.crypt_blowfish_rounds'): |
148 salt = _get_salt(2) |
167 salt = _get_crypt_blowfish_salt() |
149 elif scheme == 'BLF-CRYPT': |
168 elif CRYPT_SHA512 and cfg_dget('misc.crypt_sha512_rounds'): |
150 salt = _get_crypt_blowfish_salt() |
169 salt = _get_crypt_shaxxx_salt(6) |
151 elif scheme in ('MD5-CRYPT', 'MD5'): |
170 elif CRYPT_SHA256 and cfg_dget('misc.crypt_sha256_rounds'): |
|
171 salt = _get_crypt_shaxxx_salt(5) |
|
172 else: |
|
173 salt = _get_salt(2) |
|
174 else: |
|
175 salt = '$1$%s' % _get_salt(8) |
152 salt = '$1$%s' % _get_salt(8) |
|
153 elif scheme == 'SHA256-CRYPT': |
|
154 salt = _get_crypt_sha2_salt(5) |
|
155 else: |
|
156 salt = _get_crypt_sha2_salt(6) |
176 encrypted = crypt(password, salt) |
157 encrypted = crypt(password, salt) |
177 if encoding: |
158 if encoding: |
178 if encoding == 'HEX': |
159 if encoding == 'HEX': |
179 encrypted = encrypted.encode('hex') |
160 encrypted = encrypted.encode('hex') |
180 else: |
161 else: |
181 encrypted = encrypted.encode('base64').replace('\n', '') |
162 encrypted = encrypted.encode('base64').replace('\n', '') |
|
163 if scheme in ('BLF-CRYPT', 'SHA256-CRYPT', 'SHA512-CRYPT') and \ |
|
164 cfg_dget('misc.dovecot_version') < 0x20000b06: |
|
165 scheme = 'CRYPT' |
182 return _format_digest(encrypted, scheme, encoding) |
166 return _format_digest(encrypted, scheme, encoding) |
183 |
167 |
184 |
168 |
185 def _md4_hash(password, scheme, encoding): |
169 def _md4_hash(password, scheme, encoding): |
186 """Generates encoded PLAIN-MD4 hashes.""" |
170 """Generates encoded PLAIN-MD4 hashes.""" |
361 * has a unknown encoding suffix |
345 * has a unknown encoding suffix |
362 """ |
346 """ |
363 assert isinstance(scheme, basestring), 'Not a str/unicode: %r' % scheme |
347 assert isinstance(scheme, basestring), 'Not a str/unicode: %r' % scheme |
364 scheme_encoding = scheme.upper().split('.') |
348 scheme_encoding = scheme.upper().split('.') |
365 scheme = scheme_encoding[0] |
349 scheme = scheme_encoding[0] |
366 if not scheme in _scheme_info: |
350 if scheme not in _scheme_info: |
367 raise VMMError(_(u"Unsupported password scheme: '%s'") % scheme, |
351 raise VMMError(_(u"Unsupported password scheme: '%s'") % scheme, |
368 VMM_ERROR) |
352 VMM_ERROR) |
369 if cfg_dget('misc.dovecot_version') < _scheme_info[scheme][1]: |
353 if cfg_dget('misc.dovecot_version') < _scheme_info[scheme][1]: |
370 raise VMMError(_(u"The password scheme '%(scheme)s' requires Dovecot " |
354 raise VMMError(_(u"The password scheme '%(scheme)s' requires Dovecot " |
371 u">= v%(version)s") % {'scheme': scheme, |
355 u">= v%(version)s") % {'scheme': scheme, |
416 pw_len = cfg_dget('account.password_length') |
400 pw_len = cfg_dget('account.password_length') |
417 if pw_len < 8: |
401 if pw_len < 8: |
418 pw_len = 8 |
402 pw_len = 8 |
419 return ''.join(_sys_rand.sample(PASSWDCHARS, pw_len)) |
403 return ''.join(_sys_rand.sample(PASSWDCHARS, pw_len)) |
420 |
404 |
|
405 |
|
406 def _test_crypt_algorithms(): |
|
407 """Check for Blowfish/SHA-256/SHA-512 support in crypt.crypt().""" |
|
408 _blowfish = '$2a$04$0123456789abcdefABCDE.N.drYX5yIAL1LkTaaZotW3yI0hQhZru' |
|
409 _sha256 = '$5$rounds=1000$0123456789abcdef$K/DksR0DT01hGc8g/kt9McEgrbFMKi\ |
|
410 9qrb1jehe7hn4' |
|
411 _sha512 = '$6$rounds=1000$0123456789abcdef$ZIAd5WqfyLkpvsVCVUU1GrvqaZTqvh\ |
|
412 JoouxdSqJO71l9Ld3tVrfOatEjarhghvEYADkq//LpDnTeO90tcbtHR1' |
|
413 |
|
414 if crypt('08/15!test~4711', '$2a$04$0123456789abcdefABCDEF$') == _blowfish: |
|
415 _scheme_info['BLF-CRYPT'] = (_crypt_hash, 0x10000f00) |
|
416 if crypt('08/15!test~4711', '$5$rounds=1000$0123456789abcdef$') == _sha256: |
|
417 _scheme_info['SHA256-CRYPT'] = (_crypt_hash, 0x10000f00) |
|
418 if crypt('08/15!test~4711', '$6$rounds=1000$0123456789abcdef$') == _sha512: |
|
419 _scheme_info['SHA512-CRYPT'] = (_crypt_hash, 0x10000f00) |
|
420 |
|
421 _test_crypt_algorithms() |
421 del _, cfg_dget, _test_crypt_algorithms |
422 del _, cfg_dget, _test_crypt_algorithms |