VirtualMailManager/password.py
branchv0.6.x
changeset 272 446483386914
parent 268 beb8f4421f92
child 274 45ec5c3cfef4
equal deleted inserted replaced
271:e915d4725706 272:446483386914
    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