VMM/Account: some modifications and small improvements in class Account.
- replaced the tid by a Transport instance
- check mailbox format dependencies in _repare()
- reset all attributes when the Account was deleted
- don't select information, we have already, from the db
- added __nonzero__() method
# -*- coding: UTF-8 -*-# Copyright (c) 2010, Pascal Volk# See COPYING for distribution information.""" VirtualMailManager.password VirtualMailManager's password module to generate password hashes from passwords or random passwords. There are two functions: hashed_password = pwhash(password[, scheme][, user]) random_password = randompw()"""fromcryptimportcryptfromrandomimportchoice,shufflefromsubprocessimportPopen,PIPEtry:importhashlibexceptImportError:fromVirtualMailManager.pycompatimporthashlibfromVirtualMailManagerimportENCODINGfromVirtualMailManager.EmailAddressimportEmailAddressfromVirtualMailManager.commonimportget_unicode,version_strfromVirtualMailManager.constants.ERRORimportVMM_ERRORfromVirtualMailManager.errorsimportVMMErrorCOMPAT=hasattr(hashlib,'compat')SALTCHARS='./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'PASSWDCHARS='._-+#*23456789abcdefghikmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'DEFAULT_B64=(None,'B64','BASE64')DEFAULT_HEX=(None,'HEX')_=lambdamsg:msgcfg_dget=lambdaoption:None_get_salt=lambdas_len:''.join(choice(SALTCHARS)forxinxrange(s_len))def_dovecotpw(password,scheme,encoding):"""Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns the hashed password: {scheme[.encoding]}hash """ifencoding:scheme='.'.join((scheme,encoding))cmd_args=[cfg_dget('bin.dovecotpw'),'-s',scheme,'-p',get_unicode(password)]ifcfg_dget('misc.dovecot_version')>=0x20000a01:cmd_args.insert(1,'pw')process=Popen(cmd_args,stdout=PIPE,stderr=PIPE)stdout,stderr=process.communicate()ifprocess.returncode:raiseVMMError(stderr.strip(),VMM_ERROR)hashed=stdout.strip()ifnothashed.startswith('{%s}'%scheme):raiseVMMError('Unexpected result from %s: %s'%(cfg_dget('bin.dovecotpw'),hashed),VMM_ERROR)returnhasheddef_md4_new():"""Returns an new MD4-hash object if supported by the hashlib or provided by PyCrypto - other `None`. """try:returnhashlib.new('md4')exceptValueError,err:ifstr(err)=='unsupported hash type':ifnotCOMPAT:try:fromCrypto.HashimportMD4returnMD4.new()exceptImportError:returnNoneelse:raisedef_sha256_new(data=''):"""Returns a new sha256 object from the hashlib. Returns `None` if the PyCrypto in pycompat.hashlib is too old."""ifnotCOMPAT:returnhashlib.sha256(data)try:returnhashlib.new('sha256',data)exceptValueError,err:ifstr(err)=='unsupported hash type':returnNoneelse:raisedef_format_digest(digest,scheme,encoding):"""Formats the arguments to a string: {scheme[.encoding]}digest."""ifnotencoding:return'{%s}%s'%(scheme,digest)return'{%s.%s}%s'%(scheme,encoding,digest)def_clear_hash(password,scheme,encoding):"""Generates a (encoded) CLEARTEXT/PLAIN 'hash'."""ifencoding:ifencoding=='HEX':password=password.encode('hex')else:password=password.encode('base64').replace('\n','')return_format_digest(password,scheme,encoding)returnget_unicode('{%s}%s'%(scheme,password))def_crypt_hash(password,scheme,encoding):"""Generates (encoded) CRYPT/MD5/MD5-CRYPT hashes."""ifscheme=='CRYPT':salt=_get_salt(2)else:salt='$1$%s$'%_get_salt(8)encrypted=crypt(password,salt)ifencoding:ifencoding=='HEX':encrypted=encrypted.encode('hex')else:encrypted=encrypted.encode('base64').rstrip()return_format_digest(encrypted,scheme,encoding)def_md4_hash(password,scheme,encoding):"""Generates encoded PLAIN-MD4 hashes."""md4=_md4_new()ifmd4:md4.update(password)ifencodinginDEFAULT_HEX:digest=md4.hexdigest()else:digest=md4.digest().encode('base64').rstrip()return_format_digest(digest,scheme,encoding)return_dovecotpw(password,scheme,encoding)def_md5_hash(password,scheme,encoding,user=None):"""Generates DIGEST-MD5 aka PLAIN-MD5 and LDAP-MD5 hashes."""md5=hashlib.md5()ifscheme=='DIGEST-MD5':# Prior to Dovecot v1.1.12/v1.2.beta2 there was a problem with a# empty auth_realms setting in dovecot.conf and user@domain.tld# usernames. So we have to generate different hashes for different# versions. See also:# http://dovecot.org/list/dovecot-news/2009-March/000103.html# http://hg.dovecot.org/dovecot-1.1/rev/2b0043ba89aeifcfg_dget('misc.dovecot_version')>=0x1010cf00:md5.update('%s:%s:'%(user.localpart,user.domainname))else:md5.update('%s::'%user)md5.update(password)if(schemein('PLAIN-MD5','DIGEST-MD5')andencodinginDEFAULT_HEX) \or(scheme=='LDAP-MD5'andencoding=='HEX'):digest=md5.hexdigest()else:digest=md5.digest().encode('base64').rstrip()return_format_digest(digest,scheme,encoding)def_ntlm_hash(password,scheme,encoding):"""Generates NTLM hashes."""md4=_md4_new()ifmd4:password=''.join('%s\x00'%cforcinpassword)md4.update(password)ifencodinginDEFAULT_HEX:digest=md4.hexdigest()else:digest=md4.digest().encode('base64').rstrip()return_format_digest(digest,scheme,encoding)return_dovecotpw(password,scheme,encoding)def_sha1_hash(password,scheme,encoding):"""Generates SHA1 aka SHA hashes."""sha1=hashlib.sha1(password)ifencodinginDEFAULT_B64:digest=sha1.digest().encode('base64').rstrip()else:digest=sha1.hexdigest()return_format_digest(digest,scheme,encoding)def_sha256_hash(password,scheme,encoding):"""Generates SHA256 hashes."""sha256=_sha256_new(password)ifsha256:ifencodinginDEFAULT_B64:digest=sha256.digest().encode('base64').rstrip()else:digest=sha256.hexdigest()return_format_digest(digest,scheme,encoding)return_dovecotpw(password,scheme,encoding)def_sha512_hash(password,scheme,encoding):"""Generates SHA512 hashes."""ifnotCOMPAT:sha512=hashlib.sha512(password)ifencodinginDEFAULT_B64:digest=sha512.digest().encode('base64').replace('\n','')else:digest=sha512.hexdigest()return_format_digest(digest,scheme,encoding)return_dovecotpw(password,scheme,encoding)def_smd5_hash(password,scheme,encoding):"""Generates SMD5 (salted PLAIN-MD5) hashes."""md5=hashlib.md5(password)salt=_get_salt(4)md5.update(salt)ifencodinginDEFAULT_B64:digest=(md5.digest()+salt).encode('base64').rstrip()else:digest=md5.hexdigest()+salt.encode('hex')return_format_digest(digest,scheme,encoding)def_ssha1_hash(password,scheme,encoding):"""Generates SSHA (salted SHA/SHA1) hashes."""sha1=hashlib.sha1(password)salt=_get_salt(4)sha1.update(salt)ifencodinginDEFAULT_B64:digest=(sha1.digest()+salt).encode('base64').rstrip()else:digest=sha1.hexdigest()+salt.encode('hex')return_format_digest(digest,scheme,encoding)def_ssha256_hash(password,scheme,encoding):"""Generates SSHA256 (salted SHA256) hashes."""sha256=_sha256_new(password)ifsha256:salt=_get_salt(4)sha256.update(salt)ifencodinginDEFAULT_B64:digest=(sha256.digest()+salt).encode('base64').rstrip()else:digest=sha256.hexdigest()+salt.encode('hex')return_format_digest(digest,scheme,encoding)return_dovecotpw(password,scheme,encoding)def_ssha512_hash(password,scheme,encoding):"""Generates SSHA512 (salted SHA512) hashes."""ifnotCOMPAT:salt=_get_salt(4)sha512=hashlib.sha512(password+salt)ifencodinginDEFAULT_B64:digest=(sha512.digest()+salt).encode('base64').replace('\n','')else:digest=sha512.hexdigest()+salt.encode('hex')return_format_digest(digest,scheme,encoding)return_dovecotpw(password,scheme,encoding)_scheme_info={'CLEARTEXT':(_clear_hash,0x10000f00),'CRAM-MD5':(_dovecotpw,0x10000f00),'CRYPT':(_crypt_hash,0x10000f00),'DIGEST-MD5':(_md5_hash,0x10000f00),'HMAC-MD5':(_dovecotpw,0x10000f00),'LANMAN':(_dovecotpw,0x10000f00),'LDAP-MD5':(_md5_hash,0x10000f00),'MD5':(_crypt_hash,0x10000f00),'MD5-CRYPT':(_crypt_hash,0x10000f00),'NTLM':(_ntlm_hash,0x10000f00),'OTP':(_dovecotpw,0x10100a01),'PLAIN':(_clear_hash,0x10000f00),'PLAIN-MD4':(_md4_hash,0x10000f00),'PLAIN-MD5':(_md5_hash,0x10000f00),'RPA':(_dovecotpw,0x10000f00),'SHA':(_sha1_hash,0x10000f00),'SHA1':(_sha1_hash,0x10000f00),'SHA256':(_sha256_hash,0x10100a01),'SHA512':(_sha512_hash,0x20000b03),'SKEY':(_dovecotpw,0x10100a01),'SMD5':(_smd5_hash,0x10000f00),'SSHA':(_ssha1_hash,0x10000f00),'SSHA256':(_ssha256_hash,0x10200a04),'SSHA512':(_ssha512_hash,0x20000b03),}defpwhash(password,scheme=None,user=None):"""Generates a password hash from the plain text *password* string. If no *scheme* is given the password scheme from the configuration will be used for the hash generation. When 'DIGEST-MD5' is used as scheme, also an EmailAddress instance must be given as *user* argument. """ifnotisinstance(password,basestring):raiseTypeError('Password is not a string: %r'%password)ifisinstance(password,unicode):password=password.encode(ENCODING)password=password.strip()ifnotpassword:raiseValueError("Couldn't accept empty password.")ifschemeisNone:scheme=cfg_dget('misc.password_scheme')scheme_encoding=scheme.split('.')scheme=scheme_encoding[0].upper()ifnotschemein_scheme_info:raiseVMMError(_(u"Unsupported password scheme: '%s'")%scheme,VMM_ERROR)ifcfg_dget('misc.dovecot_version')<_scheme_info[scheme][1]:raiseVMMError(_(u"The scheme '%s' requires Dovecot >= v%s")%(scheme,version_str(_scheme_info[scheme][1])),VMM_ERROR)iflen(scheme_encoding)>1:ifcfg_dget('misc.dovecot_version')<0x10100a01:raiseVMMError(_(u'Encoding suffixes for password schemes require \Dovecot >= v1.1.alpha1'),VMM_ERROR)ifscheme_encoding[1].upper()notin('B64','BASE64','HEX'):raiseValueError('Unsupported encoding: %r'%scheme_encoding[1])encoding=scheme_encoding[1].upper()else:encoding=Noneifscheme=='DIGEST-MD5':assertisinstance(user,EmailAddress)return_md5_hash(password,scheme,encoding,user)return_scheme_info[scheme][0](password,scheme,encoding)defrandompw():"""Generates a plain text random password. The length of the password can be configured in the ``vmm.cfg`` (account.password_length). """pw_chars=list(PASSWDCHARS)shuffle(pw_chars)pw_len=cfg_dget('account.password_length')ifpw_len<8:pw_len=8return''.join(choice(pw_chars)forxinxrange(pw_len))del_,cfg_dget