VirtualMailManager/password.py
branchv0.7.x
changeset 651 6937cb38db71
parent 643 df1e3b67882a
child 655 2bf68600e914
equal deleted inserted replaced
650:429ba58bc302 651:6937cb38db71
    15         schemes, encodings = list_schemes()
    15         schemes, encodings = list_schemes()
    16 """
    16 """
    17 
    17 
    18 import hashlib
    18 import hashlib
    19 
    19 
       
    20 from base64 import b64encode
       
    21 from binascii import b2a_hex
    20 from crypt import crypt
    22 from crypt import crypt
    21 from random import SystemRandom
    23 from random import SystemRandom
    22 from subprocess import Popen, PIPE
    24 from subprocess import Popen, PIPE
    23 
    25 
    24 from VirtualMailManager import ENCODING
    26 from VirtualMailManager import ENCODING
    65     if cfg_dget('misc.dovecot_version') >= 0x20000a01:
    67     if cfg_dget('misc.dovecot_version') >= 0x20000a01:
    66         cmd_args.insert(1, 'pw')
    68         cmd_args.insert(1, 'pw')
    67     process = Popen(cmd_args, stdout=PIPE, stderr=PIPE)
    69     process = Popen(cmd_args, stdout=PIPE, stderr=PIPE)
    68     stdout, stderr = process.communicate()
    70     stdout, stderr = process.communicate()
    69     if process.returncode:
    71     if process.returncode:
    70         raise VMMError(stderr.strip(), VMM_ERROR)
    72         raise VMMError(stderr.strip().decode(ENCODING), VMM_ERROR)
    71     hashed = stdout.strip()
    73     hashed = stdout.strip().decode(ENCODING)
    72     if not hashed.startswith('{%s}' % scheme):
    74     if not hashed.startswith('{%s}' % scheme):
    73         raise VMMError('Unexpected result from %s: %s' %
    75         raise VMMError('Unexpected result from %s: %s' %
    74                        (cfg_dget('bin.dovecotpw'), hashed), VMM_ERROR)
    76                        (cfg_dget('bin.dovecotpw'), hashed), VMM_ERROR)
    75     return hashed
    77     return hashed
    76 
    78 
    99     return '{%s.%s}%s' % (scheme, encoding, digest)
   101     return '{%s.%s}%s' % (scheme, encoding, digest)
   100 
   102 
   101 
   103 
   102 def _clear_hash(password, scheme, encoding):
   104 def _clear_hash(password, scheme, encoding):
   103     """Generates a (encoded) CLEARTEXT/PLAIN 'hash'."""
   105     """Generates a (encoded) CLEARTEXT/PLAIN 'hash'."""
       
   106     password = password.decode(ENCODING)
   104     if encoding:
   107     if encoding:
   105         if encoding == 'HEX':
   108         if encoding == 'HEX':
   106             password = password.encode('hex')
   109             password = b2a_hex(password.encode()).decode()
   107         else:
   110         else:
   108             password = password.encode('base64').replace('\n', '')
   111             password = b64encode(password.encode()).decode()
   109         return _format_digest(password, scheme, encoding)
   112         return _format_digest(password, scheme, encoding)
   110     return get_unicode('{%s}%s' % (scheme, password))
   113     return '{%s}%s' % (scheme, password)
   111 
   114 
   112 
   115 
   113 def _get_crypt_blowfish_salt():
   116 def _get_crypt_blowfish_salt():
   114     """Generates a salt for Blowfish crypt."""
   117     """Generates a salt for Blowfish crypt."""
   115     rounds = cfg_dget('misc.crypt_blowfish_rounds')
   118     rounds = cfg_dget('misc.crypt_blowfish_rounds')
   152         salt = '$%d$%s' % (CRYPT_ID_MD5, _get_salt(CRYPT_MD5_SALT_LEN))
   155         salt = '$%d$%s' % (CRYPT_ID_MD5, _get_salt(CRYPT_MD5_SALT_LEN))
   153     elif scheme == 'SHA256-CRYPT':
   156     elif scheme == 'SHA256-CRYPT':
   154         salt = _get_crypt_sha2_salt(CRYPT_ID_SHA256)
   157         salt = _get_crypt_sha2_salt(CRYPT_ID_SHA256)
   155     else:
   158     else:
   156         salt = _get_crypt_sha2_salt(CRYPT_ID_SHA512)
   159         salt = _get_crypt_sha2_salt(CRYPT_ID_SHA512)
   157     encrypted = crypt(password, salt)
   160     encrypted = crypt(password.decode(ENCODING), salt)
   158     if encoding:
   161     if encoding:
   159         if encoding == 'HEX':
   162         if encoding == 'HEX':
   160             encrypted = encrypted.encode('hex')
   163             encrypted = b2a_hex(encrypted.encode()).decode()
   161         else:
   164         else:
   162             encrypted = encrypted.encode('base64').replace('\n', '')
   165             encrypted = b64encode(encrypted.encode()).decode()
   163     if scheme in ('BLF-CRYPT', 'SHA256-CRYPT', 'SHA512-CRYPT') and \
   166     if scheme in ('BLF-CRYPT', 'SHA256-CRYPT', 'SHA512-CRYPT') and \
   164        cfg_dget('misc.dovecot_version') < 0x20000b06:
   167        cfg_dget('misc.dovecot_version') < 0x20000b06:
   165         scheme = 'CRYPT'
   168         scheme = 'CRYPT'
   166     return _format_digest(encrypted, scheme, encoding)
   169     return _format_digest(encrypted, scheme, encoding)
   167 
   170 
   172     if md4:
   175     if md4:
   173         md4.update(password)
   176         md4.update(password)
   174         if encoding in DEFAULT_HEX:
   177         if encoding in DEFAULT_HEX:
   175             digest = md4.hexdigest()
   178             digest = md4.hexdigest()
   176         else:
   179         else:
   177             digest = md4.digest().encode('base64').rstrip()
   180             digest = b64encode(md4.digest()).decode()
   178         return _format_digest(digest, scheme, encoding)
   181         return _format_digest(digest, scheme, encoding)
   179     return _dovecotpw(password, scheme, encoding)
   182     return _dovecotpw(password, scheme, encoding)
   180 
   183 
   181 
   184 
   182 def _md5_hash(password, scheme, encoding, user=None):
   185 def _md5_hash(password, scheme, encoding, user=None):
   188         #  usernames. So we have to generate different hashes for different
   191         #  usernames. So we have to generate different hashes for different
   189         #  versions. See also:
   192         #  versions. See also:
   190         #       http://dovecot.org/list/dovecot-news/2009-March/000103.html
   193         #       http://dovecot.org/list/dovecot-news/2009-March/000103.html
   191         #       http://hg.dovecot.org/dovecot-1.1/rev/2b0043ba89ae
   194         #       http://hg.dovecot.org/dovecot-1.1/rev/2b0043ba89ae
   192         if cfg_dget('misc.dovecot_version') >= 0x1010cf00:
   195         if cfg_dget('misc.dovecot_version') >= 0x1010cf00:
   193             md5.update('%s:%s:' % (user.localpart, user.domainname))
   196             md5.update(user.localpart.encode() + b':' +
       
   197                        user.domainname.encode() + b':')
   194         else:
   198         else:
   195             md5.update('%s::' % user)
   199             md5.update('%s::' % user)
   196     md5.update(password)
   200     md5.update(password)
   197     if (scheme in ('PLAIN-MD5', 'DIGEST-MD5') and encoding in DEFAULT_HEX) or \
   201     if (scheme in ('PLAIN-MD5', 'DIGEST-MD5') and encoding in DEFAULT_HEX) or \
   198        (scheme == 'LDAP-MD5' and encoding == 'HEX'):
   202        (scheme == 'LDAP-MD5' and encoding == 'HEX'):
   199         digest = md5.hexdigest()
   203         digest = md5.hexdigest()
   200     else:
   204     else:
   201         digest = md5.digest().encode('base64').rstrip()
   205         digest = b64encode(md5.digest()).decode()
   202     return _format_digest(digest, scheme, encoding)
   206     return _format_digest(digest, scheme, encoding)
   203 
   207 
   204 
   208 
   205 def _ntlm_hash(password, scheme, encoding):
   209 def _ntlm_hash(password, scheme, encoding):
   206     """Generates NTLM hashes."""
   210     """Generates NTLM hashes."""
   207     md4 = _md4_new()
   211     md4 = _md4_new()
   208     if md4:
   212     if md4:
   209         password = ''.join('%s\x00' % c for c in password)
   213         password = b''.join(bytes(x)
       
   214                             for x in zip(password, bytes(len(password))))
   210         md4.update(password)
   215         md4.update(password)
   211         if encoding in DEFAULT_HEX:
   216         if encoding in DEFAULT_HEX:
   212             digest = md4.hexdigest()
   217             digest = md4.hexdigest()
   213         else:
   218         else:
   214             digest = md4.digest().encode('base64').rstrip()
   219             digest = b64encode(md4.digest()).decode()
   215         return _format_digest(digest, scheme, encoding)
   220         return _format_digest(digest, scheme, encoding)
   216     return _dovecotpw(password, scheme, encoding)
   221     return _dovecotpw(password, scheme, encoding)
   217 
   222 
   218 
   223 
   219 def _sha1_hash(password, scheme, encoding):
   224 def _sha1_hash(password, scheme, encoding):
   220     """Generates SHA1 aka SHA hashes."""
   225     """Generates SHA1 aka SHA hashes."""
   221     sha1 = hashlib.sha1(password)
   226     sha1 = hashlib.sha1(password)
   222     if encoding in DEFAULT_B64:
   227     if encoding in DEFAULT_B64:
   223         digest = sha1.digest().encode('base64').rstrip()
   228         digest = b64encode(sha1.digest()).decode()
   224     else:
   229     else:
   225         digest = sha1.hexdigest()
   230         digest = sha1.hexdigest()
   226     return _format_digest(digest, scheme, encoding)
   231     return _format_digest(digest, scheme, encoding)
   227 
   232 
   228 
   233 
   229 def _sha256_hash(password, scheme, encoding):
   234 def _sha256_hash(password, scheme, encoding):
   230     """Generates SHA256 hashes."""
   235     """Generates SHA256 hashes."""
   231     sha256 = hashlib.sha256(password)
   236     sha256 = hashlib.sha256(password)
   232     if encoding in DEFAULT_B64:
   237     if encoding in DEFAULT_B64:
   233         digest = sha256.digest().encode('base64').rstrip()
   238         digest = b64encode(sha256.digest()).decode()
   234     else:
   239     else:
   235         digest = sha256.hexdigest()
   240         digest = sha256.hexdigest()
   236     return _format_digest(digest, scheme, encoding)
   241     return _format_digest(digest, scheme, encoding)
   237 
   242 
   238 
   243 
   239 def _sha512_hash(password, scheme, encoding):
   244 def _sha512_hash(password, scheme, encoding):
   240     """Generates SHA512 hashes."""
   245     """Generates SHA512 hashes."""
   241     sha512 = hashlib.sha512(password)
   246     sha512 = hashlib.sha512(password)
   242     if encoding in DEFAULT_B64:
   247     if encoding in DEFAULT_B64:
   243         digest = sha512.digest().encode('base64').replace('\n', '')
   248         digest = b64encode(sha512.digest()).decode()
   244     else:
   249     else:
   245         digest = sha512.hexdigest()
   250         digest = sha512.hexdigest()
   246     return _format_digest(digest, scheme, encoding)
   251     return _format_digest(digest, scheme, encoding)
   247 
   252 
   248 
   253 
   249 def _smd5_hash(password, scheme, encoding):
   254 def _smd5_hash(password, scheme, encoding):
   250     """Generates SMD5 (salted PLAIN-MD5) hashes."""
   255     """Generates SMD5 (salted PLAIN-MD5) hashes."""
   251     md5 = hashlib.md5(password)
   256     md5 = hashlib.md5(password)
   252     salt = _get_salt(SALTED_ALGO_SALT_LEN)
   257     salt = _get_salt(SALTED_ALGO_SALT_LEN).encode()
   253     md5.update(salt)
   258     md5.update(salt)
   254     if encoding in DEFAULT_B64:
   259     if encoding in DEFAULT_B64:
   255         digest = (md5.digest() + salt).encode('base64').rstrip()
   260         digest = b64encode(md5.digest() + salt).decode()
   256     else:
   261     else:
   257         digest = md5.hexdigest() + salt.encode('hex')
   262         digest = md5.hexdigest() + b2a_hex(salt).decode()
   258     return _format_digest(digest, scheme, encoding)
   263     return _format_digest(digest, scheme, encoding)
   259 
   264 
   260 
   265 
   261 def _ssha1_hash(password, scheme, encoding):
   266 def _ssha1_hash(password, scheme, encoding):
   262     """Generates SSHA (salted SHA/SHA1) hashes."""
   267     """Generates SSHA (salted SHA/SHA1) hashes."""
   263     sha1 = hashlib.sha1(password)
   268     sha1 = hashlib.sha1(password)
   264     salt = _get_salt(SALTED_ALGO_SALT_LEN)
   269     salt = _get_salt(SALTED_ALGO_SALT_LEN).encode()
   265     sha1.update(salt)
   270     sha1.update(salt)
   266     if encoding in DEFAULT_B64:
   271     if encoding in DEFAULT_B64:
   267         digest = (sha1.digest() + salt).encode('base64').rstrip()
   272         digest = b64encode(sha1.digest() + salt).decode()
   268     else:
   273     else:
   269         digest = sha1.hexdigest() + salt.encode('hex')
   274         digest = sha1.hexdigest() + b2a_hex(salt).decode()
   270     return _format_digest(digest, scheme, encoding)
   275     return _format_digest(digest, scheme, encoding)
   271 
   276 
   272 
   277 
   273 def _ssha256_hash(password, scheme, encoding):
   278 def _ssha256_hash(password, scheme, encoding):
   274     """Generates SSHA256 (salted SHA256) hashes."""
   279     """Generates SSHA256 (salted SHA256) hashes."""
   275     sha256 = hashlib.sha256(password)
   280     sha256 = hashlib.sha256(password)
   276     salt = _get_salt(SALTED_ALGO_SALT_LEN)
   281     salt = _get_salt(SALTED_ALGO_SALT_LEN).encode()
   277     sha256.update(salt)
   282     sha256.update(salt)
   278     if encoding in DEFAULT_B64:
   283     if encoding in DEFAULT_B64:
   279         digest = (sha256.digest() + salt).encode('base64').rstrip()
   284         digest = b64encode(sha256.digest() + salt).decode()
   280     else:
   285     else:
   281         digest = sha256.hexdigest() + salt.encode('hex')
   286         digest = sha256.hexdigest() + b2a_hex(salt).decode()
   282     return _format_digest(digest, scheme, encoding)
   287     return _format_digest(digest, scheme, encoding)
   283 
   288 
   284 
   289 
   285 def _ssha512_hash(password, scheme, encoding):
   290 def _ssha512_hash(password, scheme, encoding):
   286     """Generates SSHA512 (salted SHA512) hashes."""
   291     """Generates SSHA512 (salted SHA512) hashes."""
   287     salt = _get_salt(SALTED_ALGO_SALT_LEN)
   292     salt = _get_salt(SALTED_ALGO_SALT_LEN).encode()
   288     sha512 = hashlib.sha512(password + salt)
   293     sha512 = hashlib.sha512(password + salt)
   289     if encoding in DEFAULT_B64:
   294     if encoding in DEFAULT_B64:
   290         digest = (sha512.digest() + salt).encode('base64').replace('\n', '')
   295         digest = b64encode(sha512.digest() + salt).decode()
   291     else:
   296     else:
   292         digest = sha512.hexdigest() + salt.encode('hex')
   297         digest = sha512.hexdigest() + b2a_hex(salt).decode()
   293     return _format_digest(digest, scheme, encoding)
   298     return _format_digest(digest, scheme, encoding)
   294 
   299 
   295 _scheme_info = {
   300 _scheme_info = {
   296     'CLEARTEXT': (_clear_hash, 0x10000f00),
   301     'CLEARTEXT': (_clear_hash, 0x10000f00),
   297     'CRAM-MD5': (_dovecotpw, 0x10000f00),
   302     'CRAM-MD5': (_dovecotpw, 0x10000f00),