VirtualMailManager/password.py
branchv0.6.x
changeset 284 ec1966828246
parent 274 45ec5c3cfef4
child 287 1e77dd639fa3
equal deleted inserted replaced
283:ea6d052de24a 284:ec1966828246
    11         hashed_password = pwhash(password[, scheme][, user])
    11         hashed_password = pwhash(password[, scheme][, user])
    12         random_password = randompw()
    12         random_password = randompw()
    13 """
    13 """
    14 
    14 
    15 from crypt import crypt
    15 from crypt import crypt
    16 from random import choice, shuffle
    16 from random import SystemRandom
    17 from subprocess import Popen, PIPE
    17 from subprocess import Popen, PIPE
    18 
    18 
    19 try:
    19 try:
    20     import hashlib
    20     import hashlib
    21 except ImportError:
    21 except ImportError:
    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 cfg_dget = lambda option: None
    38 _get_salt = lambda s_len: ''.join(choice(SALTCHARS) for x in xrange(s_len))
    38 _sys_rand = SystemRandom()
       
    39 _get_salt = lambda salt_len: ''.join(_sys_rand.sample(SALTCHARS, salt_len))
       
    40 
       
    41 
       
    42 def _test_crypt_algorithms():
       
    43     """Check for Blowfish/SHA-256/SHA-512 support in crypt.crypt()."""
       
    44     blowfish_ = sha256_ = sha512_ = False
       
    45     _blowfish = '$2a$04$0123456789abcdefABCDE.N.drYX5yIAL1LkTaaZotW3yI0hQhZru'
       
    46     _sha256 = '$5$rounds=1000$0123456789abcdef$K/DksR0DT01hGc8g/kt9McEgrbFMKi\
       
    47 9qrb1jehe7hn4'
       
    48     _sha512 = '$6$rounds=1000$0123456789abcdef$ZIAd5WqfyLkpvsVCVUU1GrvqaZTqvh\
       
    49 JoouxdSqJO71l9Ld3tVrfOatEjarhghvEYADkq//LpDnTeO90tcbtHR1'
       
    50 
       
    51     if crypt('08/15!test~4711', '$2a$04$0123456789abcdefABCDEF$') == _blowfish:
       
    52         blowfish_ = True
       
    53     if crypt('08/15!test~4711', '$5$rounds=1000$0123456789abcdef$') == _sha256:
       
    54         sha256_ = True
       
    55     if crypt('08/15!test~4711', '$6$rounds=1000$0123456789abcdef$') == _sha512:
       
    56         sha512_ = True
       
    57     return blowfish_, sha256_, sha512_
       
    58 
       
    59 CRYPT_BLOWFISH, CRYPT_SHA256, CRYPT_SHA512 = _test_crypt_algorithms()
    39 
    60 
    40 
    61 
    41 def _dovecotpw(password, scheme, encoding):
    62 def _dovecotpw(password, scheme, encoding):
    42     """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns
    63     """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns
    43     the hashed password: {scheme[.encoding]}hash
    64     the hashed password: {scheme[.encoding]}hash
   108             password = password.encode('base64').replace('\n', '')
   129             password = password.encode('base64').replace('\n', '')
   109         return _format_digest(password, scheme, encoding)
   130         return _format_digest(password, scheme, encoding)
   110     return get_unicode('{%s}%s' % (scheme, password))
   131     return get_unicode('{%s}%s' % (scheme, password))
   111 
   132 
   112 
   133 
       
   134 def _get_crypt_blowfish_salt():
       
   135     """Generates a salt for Blowfish crypt."""
       
   136     rounds = cfg_dget('misc.crypt_blowfish_rounds')
       
   137     if rounds < 4:
       
   138         rounds = 4
       
   139     elif rounds > 31:
       
   140         rounds = 31
       
   141     return '$2a$%02d$%s$' % (rounds, _get_salt(22))
       
   142 
       
   143 
       
   144 def _get_crypt_shaxxx_salt(crypt_id):
       
   145     """Generates a salt for crypt using the SHA-256 or SHA-512 encryption
       
   146     method.
       
   147     *crypt_id* must be either `5` (SHA-256) or `6` (SHA1-512).
       
   148     """
       
   149     assert crypt_id in (5, 6), 'invalid crypt id: %r' % crypt_id
       
   150     if crypt_id is 6:
       
   151         rounds = cfg_dget('misc.crypt_sha512_rounds')
       
   152     else:
       
   153         rounds = cfg_dget('misc.crypt_sha256_rounds')
       
   154     if rounds < 1000:
       
   155         rounds = 1000
       
   156     elif rounds > 999999999:
       
   157         rounds = 999999999
       
   158     return '$%d$rounds=%d$%s$' % (crypt_id, rounds, _get_salt(16))
       
   159 
       
   160 
   113 def _crypt_hash(password, scheme, encoding):
   161 def _crypt_hash(password, scheme, encoding):
   114     """Generates (encoded) CRYPT/MD5/MD5-CRYPT hashes."""
   162     """Generates (encoded) CRYPT/MD5/MD5-CRYPT hashes."""
   115     if scheme == 'CRYPT':
   163     if scheme == 'CRYPT':
   116         salt = _get_salt(2)
   164         if CRYPT_BLOWFISH and cfg_dget('misc.crypt_blowfish_rounds'):
       
   165             salt = _get_crypt_blowfish_salt()
       
   166         elif CRYPT_SHA512 and cfg_dget('misc.crypt_sha512_rounds'):
       
   167             salt = _get_crypt_shaxxx_salt(6)
       
   168         elif CRYPT_SHA256 and cfg_dget('misc.crypt_sha256_rounds'):
       
   169             salt = _get_crypt_shaxxx_salt(5)
       
   170         else:
       
   171             salt = _get_salt(2)
   117     else:
   172     else:
   118         salt = '$1$%s$' % _get_salt(8)
   173         salt = '$1$%s$' % _get_salt(8)
   119     encrypted = crypt(password, salt)
   174     encrypted = crypt(password, salt)
   120     if encoding:
   175     if encoding:
   121         if encoding == 'HEX':
   176         if encoding == 'HEX':
   122             encrypted = encrypted.encode('hex')
   177             encrypted = encrypted.encode('hex')
   123         else:
   178         else:
   124             encrypted = encrypted.encode('base64').rstrip()
   179             encrypted = encrypted.encode('base64').replace('\n', '')
   125     return _format_digest(encrypted, scheme, encoding)
   180     return _format_digest(encrypted, scheme, encoding)
   126 
   181 
   127 
   182 
   128 def _md4_hash(password, scheme, encoding):
   183 def _md4_hash(password, scheme, encoding):
   129     """Generates encoded PLAIN-MD4 hashes."""
   184     """Generates encoded PLAIN-MD4 hashes."""
   333     """Generates a plain text random password.
   388     """Generates a plain text random password.
   334 
   389 
   335     The length of the password can be configured in the ``vmm.cfg``
   390     The length of the password can be configured in the ``vmm.cfg``
   336     (account.password_length).
   391     (account.password_length).
   337     """
   392     """
   338     pw_chars = list(PASSWDCHARS)
       
   339     shuffle(pw_chars)
       
   340     pw_len = cfg_dget('account.password_length')
   393     pw_len = cfg_dget('account.password_length')
   341     if pw_len < 8:
   394     if pw_len < 8:
   342         pw_len = 8
   395         pw_len = 8
   343     return ''.join(choice(pw_chars) for x in xrange(pw_len))
   396     return ''.join(_sys_rand.sample(PASSWDCHARS, pw_len))
   344 
   397 
   345 del _, cfg_dget
   398 del _, cfg_dget, _test_crypt_algorithms