a fWcF@sdZddlmZddlZddlZeeZddlZddl Z ddl m Z ddl m Z ddlmZddl mZmZmZddlmZddlmZddlmZmZmZmZmZmZmZmZmZm Z m!Z!dd l"m#Z#m$Z$m%Z%m&Z&m'Z'm(Z(m)Z)dd l*m+Z+m,Z,m-Z-m.Z.m/Z/m0Z0m1Z1m2Z2m0Z0m3Z3m4Z4m5Z5dd l6m7Z7m8Z8gd Z9e$Z:e#Z;e%Zd dZ?dRddZ@ddZAddZBe-dZCe-dZDddZEddZFeCdfddZGeCdddfdd ZHdSd"d#ZIe-dfd$d%ZJe-ddfd&d'ZKdTd+d,ZLdUd-d.ZMdVd0d1ZNGd2d3d3eZOGd4d5d5eOZPGd6d7d7eOZQGd8d9d9eQZRGd:d;d;eQZSGdd?d?eQZUGd@dAdAeQZVGdBdCdCeQZWGdDdEdEeWZXGdFdGdGeQZYGdHdIdIeQZZe [Z\GdJdKdKeZ]GdLdMdMe]Z^GdNdOdOe]eQZ_GdPdQdQe`ZadS)WzRpasslib.handler - code for implementing handlers, and global registry for handlers)with_statementN)warn)MissingBackendErrorPasslibConfigWarningPasslibHashWarning) PasswordHash)get_crypt_handler) consteq getrandstr getrandbytesrng to_native_stris_crypt_handler to_unicodeMAX_PASSWORD_SIZEaccepts_keywordas_boolupdate_mixin_classes) BASE64_CHARS HASH64_CHARSPADDED_BASE64_CHARS HEX_CHARSUPPER_HEX_CHARSLOWER_HEX_CHARSALL_BYTE_VALUES) join_byte_valuesirangeunative_string_types uascii_to_str join_unicodeunicode str_to_uasciir unicode_or_bytes_typesPY2 int_types) classpropertydeprecated_method) parse_mc2 parse_mc3 render_mc2 render_mc3GenericHandler StaticHandlerHasUserContextHasRawChecksum HasManyIdentsHasSalt HasRawSalt HasRoundsHasManyBackends PrefixWrappercCs0|r(|r(ddl}t||t|dSdSdS)zhelper for bitsize() methodsrN)mathintloglen)countcharsr7r=:/usr/lib/python3.9/site-packages/passlib/utils/handlers.py_bitsizeJsr?cCsdt}| }zL|rT|jdd}|ds6|dsDtd|W~S|d7}|j}q|W~S~0dS)zi try to guess stacklevel for application warning. looks for first frame not part of passlib. __name__zpasslib.tests.zpasslib.r@N)inspectZ currentframe f_globalsget startswithmaxf_back)startframer;namer=r=r>guess_app_stacklevelRs rLcCs"tdt|jdttdddS)Nzpassing settings to %(handler)s.hash() is deprecated, and won't be supported in Passlib 2.0; use '%(handler)s.using(**settings).hash(secret)' instead)handlerr6) stacklevel)rdictrKDeprecationWarningrLrMkwdsr=r=r>warn_hash_settings_deprecationds  rScs&t|jtfddtDS)z helper to extract settings kwds from mix of context & settings kwds. pops settings keys from kwds, returns them as a dict. c3s$|]}|vr||fVqdSN)pop.0keyZ context_keysrRr=r> oz(extract_settings_kwds..)set context_kwdsrOlistrQr=rYr>extract_settings_kwdsis r_$0cCs0t|tst|dt|tkr,ttdS)z%ensure secret has correct type & sizesecretN) isinstancer#excExpectedStringErrorr:rZPasswordSizeError)rbr=r=r>validate_secretws   rfcCsTt|tr|St|trDz |dWSty@|dYS0n t|ddS)z+convert hash to unicode for identify methodutf-8zlatin-1hashN)rcr!bytesdecodeUnicodeDecodeErrorrdrerhr=r=r>to_unicode_for_identify~s    rmcCst|dd}t|tsJ||s.t|t|tsr(s       r( c Cst|dd}t|tsJ||s.t|t|tsr)s(         r)valuecCsT|tr$|tkr$t|d|n,|r2t||S|durLt|d|n|SdS)a helper to parse an integer config field :arg source: unicode source string :param base: numeric base :param default: optional default if source is empty :param param: name of variable, for error msgs :param handler: handler class, for error msgs zzero-padded %s fieldNzempty %s field)rFryrdrqr8)sourcebasedefaultparamrMr=r=r> parse_ints  rcCs&|r||||g}n||g}tt|S)aformat hash using 2-part modular crypt format; inverse of parse_mc2() returns native string with format :samp:`{ident}{salt}[${checksum}]`, such as used by md5_crypt. :arg ident: identifier prefix (unicode) :arg salt: encoded salt (unicode) :arg checksum: encoded checksum (unicode or None) :param sep: separator char (unicode, defaults to ``$``) :returns: config or hash (native str) )rr )identruchecksumrsrtr=r=r>r* sr*cCsj|durtd}n*|dkr(td|}n|dks4Jt|}|rR||||||g}n ||||g}tt|S)a.format hash using 3-part modular crypt format; inverse of parse_mc3() returns native string with format :samp:`{ident}[{rounds}$]{salt}[${checksum}]`, such as used by sha1_crypt. :arg ident: identifier prefix (unicode) :arg rounds: rounds field (int or None) :arg salt: encoded salt (unicode) :arg checksum: encoded checksum (unicode or None) :param sep: separator char (unicode, defaults to ``$``) :param rounds_base: base to encode rounds value (defaults to base 10) :returns: config or hash (native str) NrBz%xrw)rr!rr )rr|rurrsrzrtr=r=r>r+!s   r+?*cCsv|dur dSt|tsDt|tr mask_value?s   rcCs>|dusJd|j|f|||ks:Jd|j||fdS)z assert helper that quickly validates default value. designed to get out of the way and reduce overhead when asserts are stripped. Nz%s lacks default %sz%s: invalid default %s: %rT)rK)rMrZnormrr=r=r>validate_default_valueas rFcCst|tst|d|||krPd|j|||f}|rHt|tj|}nt||r||krd|j|||f}|rt|tj|}nt||S)a helper to normalize and validate an integer value (e.g. rounds, salt_size) :arg value: value provided to constructor :arg default: default value if none provided. if set to ``None``, value is required. :arg param: name of parameter (xxx: move to first arg?) :param min: minimum value (defaults to 1) :param max: maximum value (default ``None`` means no maximum) :returns: validated value Zintegerz+%s: %s (%d) is too low, must be at least %dz0%s: %s (%d) is too large, cannot be more than %d)rcr%rdExpectedTypeErrorrKrr ValueError)rMr}rrGrrelaxedmsgr=r=r> norm_integerjs    rc@s"eZdZdZdZedddZdS)MinimalHandlerz helper class for implementing hash handlers. provides nothing besides a base implementation of the .using() subclass constructor. FcCs,|j}|jsd|}t||ft|jddS)NzT) __module__ _configured)rArtyperOr)clsrrKr=r=r>usingszMinimalHandler.usingN)F)rAr __qualname____doc__r classmethodrr=r=r=r>rs rcs:eZdZdZdZdZedfdd ZeddZZ S) TruncateMixina PasswordHash mixin which provides a method that will check if secret would be truncated, and can be configured to throw an error. .. warning:: Hashers using this mixin will generally need to override the default PasswordHash.truncate_error policy of "True", and will similarly want to override .truncate_verify_reject as well. TODO: This should be done explicitly, but for now this mixin sets these flags implicitly. FNc s<tt|jfi|}|dur8t|dd}|dur8||_|S)Ntruncate_errorr)superrrrr)rrrRsubcls __class__r=r>rs  zTruncateMixin.usingcCs4|jdusJd|jr0t||jkr0t|dS)z make sure secret won't be truncated. NOTE: this should only be called for .hash(), not for .verify(), which should honor the .truncate_verify_reject policy. Nz%truncate_size must be set by subclass) truncate_sizerr:rdZPasswordTruncateError)rrbr=r=r>_check_truncate_policysz$TruncateMixin._check_truncate_policy)N) rArrrrtruncate_verify_rejectrrr __classcell__r=r=rr>rsrcseZdZdZdZdZdZdZdZdZ dZ dZ d+fdd Z d,ddZ ed d Zed d Zd dZeddZddZeddZeddZedddeddZedddeddZed-ddZd.d d!ZdZd"Zd#Zed$d%Z ed/d'd(Z!efd)d*Z"Z#S)0r,a4helper class for implementing hash handlers. GenericHandler-derived classes will have (at least) the following constructor options, though others may be added by mixins and by the class itself: :param checksum: this should contain the digest portion of a parsed hash (mainly provided when the constructor is called by :meth:`from_string()`). defaults to ``None``. :param use_defaults: If ``False`` (the default), a :exc:`TypeError` should be thrown if any settings required by the handler were not explicitly provided. If ``True``, the handler should attempt to provide a default for any missing values. This means generate missing salts, fill in default cost parameters, etc. This is typically only set to ``True`` when the constructor is called by :meth:`hash`, allowing user-provided values to be handled in a more permissive manner. :param relaxed: If ``False`` (the default), a :exc:`ValueError` should be thrown if any settings are out of bounds or otherwise invalid. If ``True``, they should be corrected if possible, and a warning issue. If not possible, only then should an error be raised. (e.g. under ``relaxed=True``, rounds values will be clamped to min/max rounds). This is mainly used when parsing the config strings of certain hashes, whose specifications implementations to be tolerant of incorrect values in salt strings. Class Attributes ================ .. attribute:: ident [optional] If this attribute is filled in, the default :meth:`identify` method will use it as a identifying prefix that can be used to recognize instances of this handler's hash. Filling this out is recommended for speed. This should be a unicode str. .. attribute:: _hash_regex [optional] If this attribute is filled in, the default :meth:`identify` method will use it to recognize instances of the hash. If :attr:`ident` is specified, this will be ignored. This should be a unique regex object. .. attribute:: checksum_size [optional] Specifies the number of characters that should be expected in the checksum string. If omitted, no check will be performed. .. attribute:: checksum_chars [optional] A string listing all the characters allowed in the checksum string. If omitted, no check will be performed. This should be a unicode str. .. attribute:: _stub_checksum Placeholder checksum that will be used by genconfig() in lieu of actually generating a hash for the empty string. This should be a string of the same datatype as :attr:`checksum`. Instance Attributes =================== .. attribute:: checksum The checksum string provided to the constructor (after passing it through :meth:`_norm_checksum`). Required Subclass Methods ========================= The following methods must be provided by handler subclass: .. automethod:: from_string .. automethod:: to_string .. automethod:: _calc_checksum Default Methods =============== The following methods have default implementations that should work for most cases, though they may be overridden if the hash subclass needs to: .. automethod:: _norm_checksum .. automethod:: genconfig .. automethod:: genhash .. automethod:: identify .. automethod:: hash .. automethod:: verify Nr=Fc s4||_tt|jfi||dur0|||_dSrT) use_defaultsrr,__init___norm_checksumr)selfrrrRrr=r>rkszGenericHandler.__init__cs|j}|r$t|ts`t|ddnrZr[z0GenericHandler._norm_checksum..z!invalid characters in %s checksum)_checksum_is_bytesrcrirdrr!rrrj checksum_sizer:ZChecksumSizeErrorchecksum_charsanyrrK)rrrrccr=rr>rts"     zGenericHandler._norm_checksumcCsnt|}|sdS|j}|dur(||S|j}|durD||duSz||WdStyhYdS0dS)NFT)rmrrF _hash_regexmatch from_stringr)rrhrpatr=r=r>identifys   zGenericHandler.identifycKstd|fdS)aX return parsed instance from hash/configuration string :param \\*\\*context: context keywords to pass to constructor (if applicable). :raises ValueError: if hash is incorrectly formatted :returns: hash parsed into components, for formatting / calculating checksum. %s must implement from_string()NNotImplementedError)rrhcontextr=r=r>rszGenericHandler.from_stringcCstd|jfdS)zrender instance to hash or configuration string :returns: hash string with salt & digest included. should return native string type (ascii-bytes under python 2, unicode under python 3) rNrrrr=r=r> to_strings zGenericHandler.to_stringcCsn|jr,|jrd|jS|jr,|jd|jSt|trd|j}|jpDd|_z|dW||_S||_0|dS)zi placeholder used by default .genconfig() so it can avoid expense of calculating digest. rr@rB)rrrrcr3r| min_rounds_calc_checksum)rorigr=r=r>_stub_checksums    zGenericHandler._stub_checksumcCstd|jfdS)agiven secret; calcuate and return encoded checksum portion of hash string, taking config from object state calc checksum implementations may assume secret is always either unicode or bytes, checks are performed by verify/etc. "%s must implement _calc_checksum()Nrrrbr=r=r>rszGenericHandler._calc_checksumcKsh|r:t||}|r:t|||jfi|j|fi|St||fddi|}|||_|SNrT)r_rSrrhrfrrr)rrbrRsettingsrr=r=r>rhs   zGenericHandler.hashcKsBt||j|fi|}|j}|dur2t|t|||SrT)rfrrrdZMissingDigestErrorr r)rrbrhrrrvr=r=r>verifys  zGenericHandler.verify1.72.0 deprecatedremovedcKsLt||}|r*|jfi|jfi|S|fddi|}|j|_|Sr)r_r genconfigrrr)rrRrrr=r=r>rs  zGenericHandler.genconfigcKs>|durtdt||j|fi|}|||_|S)Nconfig must be string) TypeErrorrfrrrr)rrbconfigrrr=r=r>genhash+s  zGenericHandler.genhashcKs,||}t||sJ|jfd|i|S)Nrb)rrc_calc_needs_update)rrhrbrRrr=r=r> needs_update9s zGenericHandler.needs_updatecCsdS)z; internal helper for :meth:`needs_update`. Fr=rr=r=r>rAsz!GenericHandler._calc_needs_update) salt_sizer)rurcstfddjDS)z helper for :meth:`parsehash` -- returns list of attributes which should be extracted by parse_hash() from hasher object. default implementation just takes setting_kwds, and excludes _unparsed_settings c3s|]}|jvr|VqdSrT)_unparsed_settingsrVrr=r>rZ_r[z2GenericHandler._parsed_settings..)tuple setting_kwdsrr=rr>_parsed_settingsWszGenericHandler._parsed_settingsTcs|tjtfddjD}|rLjdurLj|d<|r|dur\t}jD]}||vrb|||||<qb|S)a[experimental method] parse hash into dictionary of settings. this essentially acts as the inverse of :meth:`hash`: for most cases, if ``hash = cls.hash(secret, **opts)``, then ``cls.parsehash(hash)`` will return a dict matching the original options (with the extra keyword *checksum*). this method may not work correctly for all hashes, and may not be available on some few. its interface may change in future releases, if it's kept around at all. :arg hash: hash to parse :param checksum: include checksum keyword? (defaults to True) :param sanitize: mask data for sensitive fields? (defaults to False) c3s:|]2}|vs$t|t|kr|t|fVqdSrTgetattrrVZUNSETalwaysrrr=r>rZ}sz+GenericHandler.parsehash..NrT)robject_always_parse_settingsrOrrr_unsafe_settings)rrhrZsanitizerRrXr=rr> parsehashas   zGenericHandler.parsehashc s`ztt|jfi|}Wnty0i}Yn0|jrrs   zGenericHandler.bitsize)NF)F)N)N)TF)$rArrrrr]rrrrrrrrrrrrpropertyrrrhrr'rrrrrrrr&rrrrr=r=rr>r,sRo '            'r,c@sHeZdZdZdZedZeddZeddZ dd Z d Z d d Z d S) r-a;GenericHandler mixin for classes which have no settings. This mixin assumes the entirety of the hash ise stored in the :attr:`checksum` attribute; that the hash has no rounds, salt, etc. This class provides the following: * a default :meth:`genconfig` that always returns None. * a default :meth:`from_string` and :meth:`to_string` that store the entire hash within :attr:`checksum`, after optionally stripping a constant prefix. All that is required by subclasses is an implementation of the :meth:`_calc_checksum` method. r=rBcKsXt|dd}||}|j}|rF||r<|t|d}n t||fd|i|S)Nrnrhr)r _norm_hash _hash_prefixrFr:rdro)rrhrrrr=r=r>rs    zStaticHandler.from_stringcCs|S)z1helper for subclasses to normalize case if neededr=rrhr=r=r>rszStaticHandler._norm_hashcCst|j|jSrT)rrrrr=r=r>rszStaticHandler.to_stringNc sjjtksJj}|durRfdd}tjdft|jd}_tfddjD}z|j|dfi|}WnBty}z*t |dkrt d fnWYd}~n d}~00t d t t |S) z{given secret; calcuate and return encoded checksum portion of hash string, taking config from object state NcstdfdS)Nrrrrr=r>innersz+StaticHandler._calc_checksum..innerZ_wrapper)rrc3s|]}|t|fVqdSrTr)rWkrr=r>rZr[z/StaticHandler._calc_checksum..rrz%r should be updated to implement StaticHandler._calc_checksum() instead of StaticHandler.genhash(), support for the latter style will be removed in Passlib 1.8)rrrA_StaticHandler__cc_compat_hackrrOr]rrstrrrrPr")rrbZ wrapper_clsrrrherrr=)rrr>rs.     zStaticHandler._calc_checksum) rArrrrrrrrrrrrr=r=r=r>r-s  r-cs*eZdZdZdZdZdfdd ZZS)HasEncodingContextz?helper for classes which require knowledge of the encoding used)encodingrgNc s&tt|jfi||p|j|_dSrT)rrrdefault_encodingr)rrrRrr=r>rszHasEncodingContext.__init__)N)rArrrr]rrrr=r=rr>rsrcsheZdZdZdZdfdd Zedfdd Zedfdd Ze d d d edfd d Z Z S)r.z7helper for classes which require a user context keyword)userNc s tt|jfi|||_dSrT)rr.rr)rrrRrr=r>rszHasUserContext.__init__c stt|j|fd|i|SNr)rr.rh)rrbrrrr=r>rh szHasUserContext.hashc stt|j||fd|i|Sr)rr.r)rrbrhrrrr=r>rszHasUserContext.verifyrrrc stt|j||fd|i|Sr)rr.r)rrbrrrrr=r>rszHasUserContext.genhash)N)N)N)N) rArrrr]rrrhrr'rrr=r=rr>r.s r.c@seZdZdZdZdS)r/zqmixin for classes which work with decoded checksum bytes .. todo:: document this class's usage TN)rArrrrr=r=r=r>r/%s r/csheZdZdZdZdZdZdZed fdd Z dfdd Z eddZ ed d Z ed d Z ZS)r0a mixin for hashes which use multiple prefix identifiers For the hashes which may use multiple identifier prefixes, this mixin adds an ``ident`` keyword to constructor. Any value provided is passed through the :meth:`norm_idents` method, which takes care of validating the identifier, as well as allowing aliases for easier specification of the identifiers by the user. .. todo:: document this class's usage Class Methods ============= .. todo:: document using() and needs_update() options Nc sN|dur|durtd|}tt|jfi|}|durJ||ddj|_|S)a4 This mixin adds support for the following :meth:`~passlib.ifc.PasswordHash.using` keywords: :param default_ident: default identifier that will be used by resulting customized hasher. :param ident: supported as alternate alias for **default_ident**. Nz2'default_ident' and 'ident' are mutually exclusiveT)rr)rrr0rr default_ident)rrrrRrrr=r>r^s zHasManyIdents.usingc s`tt|jfi||dur*||}n,|jrN|j}t|||jddsVJntd||_dS)Nrrzno ident specified) rr0r _norm_identrrrrr)rrrRrr=r>r|s zHasManyIdents.__init__cCsz|dus Jt|tr |d}|j}||vr2|S|j}|rhz ||}WntyZYn0||vrh|Std|fdS)zD helper which normalizes & validates 'ident' value. Nrnzinvalid ident: %r)rcrirj ident_values ident_aliasesKeyErrorr)rrZiviar}r=r=r>rs     zHasManyIdents._norm_identcCst|}||jSrT)rmrFrrr=r=r>rszHasManyIdents.identifycCsHt|dd}|jD]&}||r||t|dfSqt|dS)zDextract ident prefix from hash, helper for subclasses' from_string()rnrhN)rrrFr:rdro)rrhrr=r=r> _parse_idents    zHasManyIdents._parse_ident)NN)N)rArrrrrrrrrrrrrrr=r=rr>r06s    r0cseZdZdZdZdZdZeddZeddZ dZ d Z dZ e dfd d Ze dddZdfdd ZddZe dddZeddZe ddZe d fdd ZZS)!r1a2 mixin for validating salts. This :class:`GenericHandler` mixin adds a ``salt`` keyword to the class constuctor; any value provided is passed through the :meth:`_norm_salt` method, which takes care of validating salt length and content, as well as generating new salts if one it not provided. :param salt: optional salt string :param salt_size: optional size of salt (only used if no salt provided); defaults to :attr:`default_salt_size`. Class Attributes ================ In order for :meth:`!_norm_salt` to do its job, the following attributes should be provided by the handler subclass: .. attribute:: min_salt_size The minimum number of characters allowed in a salt string. An :exc:`ValueError` will be throw if the provided salt is too small. Defaults to ``0``. .. attribute:: max_salt_size The maximum number of characters allowed in a salt string. By default an :exc:`ValueError` will be throw if the provided salt is too large; but if ``relaxed=True``, it will be clipped and a warning issued instead. Defaults to ``None``, for no maximum. .. attribute:: default_salt_size [required] If no salt is provided, this should specify the size of the salt that will be generated by :meth:`_generate_salt`. By default this will fall back to :attr:`max_salt_size`. .. attribute:: salt_chars A string containing all the characters which are allowed in the salt string. An :exc:`ValueError` will be throw if any other characters are encountered. May be set to ``None`` to skip this check (but see in :attr:`default_salt_chars`). .. attribute:: default_salt_chars [required] This attribute controls the set of characters use to generate *new* salt strings. By default, it mirrors :attr:`salt_chars`. If :attr:`!salt_chars` is ``None``, this attribute must be specified in order to generate new salts. Aside from that purpose, the main use of this attribute is for hashes which wish to generate salts from a restricted subset of :attr:`!salt_chars`; such as accepting all characters, but only using a-z. Instance Attributes =================== .. attribute:: salt This instance attribute will be filled in with the salt provided to the constructor (as adapted by :meth:`_norm_salt`) Subclassable Methods ==================== .. automethod:: _norm_salt .. automethod:: _generate_salt rNcCs|jS)z/default salt size (defaults to *max_salt_size*)) max_salt_sizerr=r=r>default_salt_sizeszHasSalt.default_salt_sizecCs|jS)zDcharset used to generate new salt strings (defaults to *salt_chars*)) salt_charsrr=r=r>default_salt_charsszHasSalt.default_salt_charsFr<c s|dur|durtd|}tt|jfi|}|d}|durht|trVt|}|j|d|d|_ dur|j |dt fdd|_ |S)NzB'salt_size' and 'default_salt_size' aliases are mutually exclusiverrrrrcsSrTr=r=rur=r>Gr[zHasSalt.using..) rrr1rrErcrr8_clip_to_valid_salt_sizer _norm_salt staticmethod_generate_salt)rrrrurRrrrrr>r*s"  z HasSalt.usingrTcCs|j}|j}||krJ||krFd|j|||f}|r>t|tnt||S||krd|j|||f}|rxt|t|}nt||r||krd|j|||f}|rt|t|}nt||S)a internal helper -- clip salt size value to handler's absolute limits (min_salt_size / max_salt_size) :param relaxed: if ``True`` (the default), issues PasslibHashWarning is rounds are outside allowed range. if ``False``, raises a ValueError instead. :param param: optional name of parameter to insert into error/warning messages. :returns: clipped rounds value z%s: %s (%d) must be exactly %dz$%s: %s (%r) below min_salt_size (%d)z$%s: %s (%r) above max_salt_size (%d)) min_salt_sizerrKrrr)rrrrmnmxrr=r=r>rLs,    z HasSalt._clip_to_valid_salt_sizec shtt|jfi||dur*||}n4|jrV|}|||ks^Jd|fntd||_dS)Nzgenerated invalid salt: %rzno salt specified) rr1r _parse_saltrr r rru)rrurRrr=r>rs zHasSalt.__init__cCs ||SrT)r )rrur=r=r>rszHasSalt._parse_saltcs,|jr t|tst|ddnht|tsVt|trHts<|rH|d}nt|dd|jdurt fdd|Drt d|j |j }|rt ||krd |j ||jkrd nd ||jf}t ||j}|r(t ||kr(d |j ||krd nd ||jf}|r t|t|||}nt ||S)aThelper to normalize & validate user-provided salt string :arg salt: salt string :raises TypeError: If salt not correct type. :raises ValueError: * if salt contains chars that aren't in :attr:`salt_chars`. * if salt contains less than :attr:`min_salt_size` characters. * if ``relaxed=False`` and salt has more than :attr:`max_salt_size` characters (if ``relaxed=True``, the salt is truncated and a warning is issued instead). :returns: normalized salt rirurnr!Nc3s|]}|vVqdSrTr=rZscr=r>rZr[z%HasSalt._norm_salt..zinvalid characters in %s saltz%salt too small (%s requires %s %d %s)Zexactlyz>=z%salt too large (%s requires %s %d %s)z<=)_salt_is_bytesrcrirdrr!r$rjrrrrKr r:r _salt_unitrr_truncate_salt)rrurr rrr=rr>r s4    zHasSalt._norm_saltcCs |d|SrTr=)rurr=r=r>rszHasSalt._truncate_saltcCstt|j|jS)zU helper method for _init_salt(); generates a new random salt string. )r r rrrr=r=r>r szHasSalt._generate_saltc s8tt|jfi|}|dur$|j}t||j|d<|S)rNru)rr1rrr?r)rrrRrrr=r>rs zHasSalt.bitsize)NNN)rT)N)F)N)rArrrr rrr&rrrrrurrrrrr r rr rrr=r=rr>r1s8L  ! 3  9  r1c@s(eZdZdZeZdZdZeddZ dS)r2zmixin for classes which use decoded salt parameter A variant of :class:`!HasSalt` which takes in decoded bytes instead of an encoded string. .. todo:: document this class's usage TricCs|jdtfvsJtt|jSrT)rrr r rrr=r=r>r szHasRawSalt._generate_saltN) rArrrrrrrrr r=r=r=r>r2s  r2cseZdZdZdZdZdZdZdZdZ dZ dZ dZ e dfdd Ze dd Ze d d Zdfd d ZddZe dddZe ddZfddZe dfdd ZZS)r3a3mixin for validating rounds parameter This :class:`GenericHandler` mixin adds a ``rounds`` keyword to the class constuctor; any value provided is passed through the :meth:`_norm_rounds` method, which takes care of validating the number of rounds. :param rounds: optional number of rounds hash should use Class Attributes ================ In order for :meth:`!_norm_rounds` to do its job, the following attributes must be provided by the handler subclass: .. attribute:: min_rounds The minimum number of rounds allowed. A :exc:`ValueError` will be thrown if the rounds value is too small. Defaults to ``0``. .. attribute:: max_rounds The maximum number of rounds allowed. A :exc:`ValueError` will be thrown if the rounds value is larger than this. Defaults to ``None`` which indicates no limit to the rounds value. .. attribute:: default_rounds If no rounds value is provided to constructor, this value will be used. If this is not specified, a rounds value *must* be specified by the application. .. attribute:: rounds_cost [required] The ``rounds`` parameter typically encodes a cpu-time cost for calculating a hash. This should be set to ``"linear"`` (the default) or ``"log2"``, depending on how the rounds value relates to the actual amount of time that will be required. Class Methods ============= .. todo:: document using() and needs_update() options Instance Attributes =================== .. attribute:: rounds This instance attribute will be filled in with the rounds value provided to the constructor (as adapted by :meth:`_norm_rounds`) Subclassable Methods ==================== .. automethod:: _norm_rounds rNZlinear)min_desired_roundsmax_desired_roundsr max_roundsr{ vary_roundsc  s|dur|durtd|}|dur8|dur4td|}|durd|durL|}|durX|}|durd|}tt|jfi|} |d} |durd} |j}n(d} t|trt|}| j |d| d| _|dur|j }nbt|trt|}|r ||kr d| j ||f} | rt | nt | t|}| j |d | d| _ |durt|trPt|}|rv||krvt d | j ||fn$|r||krt d | j ||f| j |d | d| _| jdur| | j| _|durt|tr|d rt|ddd}nd|vrt|}nt|}|dkr8t d| j |fn>t|trb|dkrvt d| j |fnt|tsvtd|rt dt|| _| S)NzD'min_rounds' and 'min_desired_rounds' aliases are mutually exclusivezD'max_rounds' and 'max_desired_rounds' aliases are mutually exclusiverFTrrz9%s: max_desired_rounds (%r) below min_desired_rounds (%r)rz5%s: default_rounds (%r) below min_desired_rounds (%r)z5%s: default_rounds (%r) above max_desired_rounds (%r)r{%g{Gz?.rz%s: vary_rounds (%r) below 0r@z%s: vary_rounds (%r) above 1.0z vary_rounds must be int or floatz\The 'vary_rounds' option is deprecated as of Passlib 1.7, and will be removed in Passlib 2.0)rrr3rrErrcrr8 _norm_roundsrrKrrrr{_clip_to_desired_roundsendswithfloatr) rrrr{rrrr|rRrrZexplicit_min_roundsrrr=r>rWs                    zHasRounds.usingcCs0|jpd}||kr|S|j}|r,||kr,|S|S)z| helper for :meth:`_generate_rounds` -- clips rounds value to desired min/max set by class (if any) r)rr)rr|ZmndZmxdr=r=r>rs  z!HasRounds._clip_to_desired_roundscCs|sJ|j}dd}t|tr`d|kr4dks:nJ|jdkrTd|>}dd}t||}|dkrrt|tsvJ|||d}|||d}||||fS) z helper for :meth:`_generate_rounds` -- returns range for vary rounds generation. :returns: (lower, upper) limits suitable for random.randint() cSs|SrTr=r}upperr=r=r>linear_to_nativesz;HasRounds._calc_vary_rounds_range..linear_to_nativerr@log2cSs:|dkr dS|r tt|dSttt|dSdS)Nrr6)r8r7r9Zceilrr=r=r>r!s FT)rrcr rounds_costr8r%r)rr{rr!lowerr r=r=r>_calc_vary_rounds_ranges    z!HasRounds._calc_vary_rounds_rangec shtt|jfi||dur*||}n4|jrV|}|||ks^Jd|fntd||_dS)Nzgenerated invalid rounds: %rzno rounds specified) rr3r _parse_roundsr_generate_roundsrrr|)rr|rRrr=r>rs zHasRounds.__init__cCs ||SrT)r)rr|r=r=r>r& szHasRounds._parse_roundsFr|cCst|||j|j||dS)a helper for normalizing rounds value. :arg rounds: an integer cost parameter. :param relaxed: if ``True`` (the default), issues PasslibHashWarning is rounds are outside allowed range. if ``False``, raises a ValueError instead. :param param: optional name of parameter to insert into error/warning messages. :raises TypeError: * if ``use_defaults=False`` and no rounds is specified * if rounds is not an integer. :raises ValueError: * if rounds is ``None`` and class does not specify a value for :attr:`default_rounds`. * if ``relaxed=False`` and rounds is outside bounds of :attr:`min_rounds` and :attr:`max_rounds` (if ``relaxed=True``, the rounds value will be clamped, and a warning issued). :returns: normalized rounds value r)rrr)rr|rrr=r=r>rszHasRounds._norm_roundscCsd|j}|durtd|jf|jr`||\}}||krF|ksLnJ||kr`t||}|S)z internal helper for :meth:`_norm_rounds` -- returns default rounds value, incorporating vary_rounds, and any other limitations hash may place on rounds parameter. Nz,%s rounds value must be specified explicitly)r{rrKrr%r Zrandint)rr|r$r r=r=r>r'1s zHasRounds._generate_roundsc sF|j}|r|j|krdS|j}|r0|j|kr0dStt|jfi|SzR mark hash as needing update if rounds is outside desired bounds. T)rr|rrr3r)rrRrrrr=r>rJszHasRounds._calc_needs_update皙?c s\tt|jfi|}|jdkrXddl}|dur6|j}tdtd|||d|d<|S)rr"rNr@r6r|) rr3rr#r7r{rGr8r9)rr|rrRrr7rr=r>rYs "zHasRounds.bitsize)NNNNNNN)N)Fr|)Nr))rArrrrrr#Zusing_rounds_kwdsrrr{rr|rrrr%rr&rr'rrrr=r=rr>r3s8>k  *    r3csReZdZdZdZed fdd Zdfdd Zedd d Zfd d Z Z S)ParallelismMixinzH mixin which provides common behavior for 'parallelism' setting r@Nc sJtt|jfi|}|durFt|tr0t|}|j||dd|_|S)Nrr) rr*rrcrr8_norm_parallelismrE parallelism)rr,rRrrr=r>rs  zParallelismMixin.usingc sHtt|jfi||dur8t||j|jddsDJn |||_dS)Nr,r)rr*rrr,r+)rr,rRrr=r>rs   zParallelismMixin.__init__FcCst||dd|dS)Nr@r,)rrr)r)rr,rr=r=r>r+sz"ParallelismMixin._norm_parallelismc s*|jt|jkrdStt|jfi|Sr()r,rrr*r)rrRrr=r>rsz#ParallelismMixin._calc_needs_update)N)N)F) rArrrr,rrrr+rrr=r=rr>r*vs   r*c@s|eZdZdZdZdZdZdZdZe ddZ e dddZ e dd d Z e d d Z e d dZe ddZe ddZdS) BackendMixina PasswordHash mixin which provides generic framework for supporting multiple backends within the class. Public API ---------- .. attribute:: backends This attribute should be a tuple containing the names of the backends which are supported. Two common names are ``"os_crypt"`` (if backend uses :mod:`crypt`), and ``"builtin"`` (if the backend is a pure-python fallback). .. automethod:: get_backend .. automethod:: set_backend .. automethod:: has_backend .. warning:: :meth:`set_backend` is intended to be called during application startup -- it affects global state, and switching backends is not guaranteed threadsafe. Private API (Subclass Hooks) ---------------------------- Subclasses should set the :attr:`!backends` attribute to a tuple of the backends they wish to support. They should also define one method: .. classmethod:: _load_backend_{name}(dryrun=False) One copy of this method should be defined for each :samp:`name` within :attr:`!backends`. It will be called in order to load the backend, and should take care of whatever is needed to enable the backend. This may include importing modules, running tests, issuing warnings, etc. :param name: [Optional] name of backend. :param dryrun: [Optional] True/False if currently performing a "dry run". if True, the method should perform all setup actions *except* switching the class over to the new backend. :raises passlib.exc.PasslibSecurityError: if the backend is available, but cannot be loaded due to a security issue. :returns: False if backend not available, True if backend loaded. .. warning:: Due to the way passlib's internals are arranged, backends should generally store stateful data at the class level (not the module level), and be prepared to be called on subclasses which may be set to a different backend from their parent. (Idempotent module-level data such as lazy imports are fine). .. automethod:: _finalize_backend .. versionadded:: 1.7 NFcCs"|js||jsJd|jS)a Return name of currently active backend. if no backend has been loaded, loads and returns name of default backend. :raises passlib.exc.MissingBackendError: if no backends are available. :returns: name of active backend z.set_backend() failed to load a default backend)_BackendMixin__backend set_backendrr=r=r> get_backends zBackendMixin.get_backendrc Cs6z|j|ddWdStjtjfy0YdS0dS)a Check if support is currently available for specified backend. :arg name: name of backend to check for. can be any string accepted by :meth:`set_backend`. :raises ValueError: if backend name is unknown :returns: * ``True`` if backend is available. * ``False`` if it's available / can't be loaded. * ``None`` if it's present, but won't load due to a security issue. TdryrunFN)r/rdrPasslibSecurityErrorrrKr=r=r> has_backend,s zBackendMixin.has_backendc Cs|dkr|js|r"||jkr"|jS|}||ur@|j||dS|dksP|dkrd}|jD]n}z|j||dWStjyYqZYqZtjy}z$|dur|}WYd}~qZWYd}~qZd}~00qZ|durd|j}|jr||j7}t|}|||jvrt ||t d|j |j f}z(||_ ||_ | ||W|\|_ |_ n|\|_ |_ 0|sh||_|WdS1s0YdS)a Load specified backend. :arg name: name of backend to load, can be any of the following: * ``"any"`` -- use current backend if one is loaded, otherwise load the first available backend. * ``"default"`` -- use the first available backend. * any string in :attr:`backends`, loads specified backend. :param dryrun: If True, this perform all setup actions *except* switching over to the new backend. (this flag is used to implement :meth:`has_backend`). .. versionadded:: 1.7 :raises ValueError: If backend name is unknown. :raises passlib.exc.MissingBackendError: If specific backend is missing; or in the case of ``"any"`` / ``"default"``, if *no* backends are available. :raises passlib.exc.PasslibSecurityError: If ``"any"`` or ``"default"`` was specified, but the only backend available has a PasslibSecurityError. rr1rNz%s: no backends available)r._get_backend_ownerr/backendsrdrr3rK_no_backend_suggestionZUnknownBackendError _backend_lock_pending_backend_pending_dry_run _set_backend)rrKr2ownerZ default_errorrrrr=r=r>r/CsD" $      zBackendMixin.set_backendcCs|S)z return class that set_backend() should actually be modifying. for SubclassBackendMixin, this may not always be the class that was invoked. r=rr=r=r>r6szBackendMixin._get_backend_ownercCsx||}i}t|dr ||d<t|dr2||d<|fi|}|dur^td|j|fn|durttd|fdS)a% Internal method invoked by :meth:`set_backend`. handles actual loading of specified backend. global _backend_lock will be held for duration of this method, and _pending_dry_run & _pending_backend will also be set. should return True / False. rKr2Fz%s: backend not available: %sTz-backend loaders must return True or False: %rN)_get_backend_loaderrrdrrKAssertionError)rrKr2loaderrRokr=r=r>r<s   zBackendMixin._set_backendcCs tddS)af Hook called to get the specified backend's loader. Should return callable which optionally takes ``"name"`` and/or ``"dryrun"`` keywords. Callable should return True if backend initialized successfully. If backend can't be loaded, callable should return False OR raise MissingBackendError directly. zimplement in subclassNrr4r=r=r>r>s z BackendMixin._get_backend_loadercCs:|jrtd|j|jf||js6td|jdS)zW helper for subclasses to create stub methods which auto-load backend. z7%s: _finalize_backend(%r) failed to replace lazy loaderz2%s: set_backend() failed to load a default backendN)r.r?rKr/rr=r=r>_stub_requires_backends z#BackendMixin._stub_requires_backend)r)rF)rArrrr7r.r8r:r;rr0r5r/r6r<r>rBr=r=r=r>r-s(E   S   r-csDeZdZdZdZdZeddZefddZedd Z Z S) SubclassBackendMixina variant of BackendMixin which allows backends to be implemented as separate mixin classes, and dynamically switches them out. backend classes should implement a _load_backend() classmethod, which will be invoked with an optional 'dryrun' keyword, and should return True or False. _load_backend() will be invoked with ``cls`` equal to the mixin, *not* the overall class. .. versionadded:: 1.7 FNcCs:|jstd|jD]}|jdr|SqtddS)z return base class that we're actually switching backends on (needed in since backends frequently modify class attrs, and .set_backend may be called from a subclass). z_backend_mixin_target not set_backend_mixin_targetz5expected to find class w/ '_backend_mixin_target' setN)rDr?__mro____dict__rE)rrr=r=r>r6s    z'SubclassBackendMixin._get_backend_ownercsntt|||||us&Jd|j}|s8Jd||}t|tsRJdt|||dt|ddS)Nz(_finalize_backend() not invoked on owner _backend_mixin_map not specifiedzinvalid mixin classT)addremoveappendbeforer2)rrCr<r6_backend_mixin_map issubclassrvalues)rrKr2Z mixin_mapZ mixin_clsrr=r>r< s z!SubclassBackendMixin._set_backendcCs|jsJd|j|jS)NrG)rLZ_load_backend_mixinr4r=r=r>r> sz(SubclassBackendMixin._get_backend_loader) rArrrrDrLrr6r<r>rr=r=rr>rCs rCc@sDeZdZdZddZddZeddZedd Zed d Z d S) r4a GenericHandler mixin which provides selecting from multiple backends. .. todo:: finish documenting this class's usage For hashes which need to select from multiple backends, depending on the host environment, this class offers a way to specify alternate :meth:`_calc_checksum` methods, and will dynamically chose the best one at runtime. .. versionchanged:: 1.7 This class now derives from :class:`BackendMixin`, which abstracts out a more generic framework for supporting multiple backends. The public api (:meth:`!get_backend`, :meth:`!has_backend`, :meth:`!set_backend`) is roughly the same. Private API (Subclass Hooks) ---------------------------- As of version 1.7, classes should implement :meth:`!_load_backend_{name}`, per :class:`BackendMixin`. This hook should invoke :meth:`!_set_calc_checksum_backcend` to install it's backend method. .. deprecated:: 1.7 The following api is deprecated, and will be removed in Passlib 2.0: .. attribute:: _has_backend_{name} private class attribute checked by :meth:`has_backend` to see if a specific backend is available, it should be either ``True`` or ``False``. One of these should be provided by the subclass for each backend listed in :attr:`backends`. .. classmethod:: _calc_checksum_{name} private class method that should implement :meth:`_calc_checksum` for a given backend. it will only be called if the backend has been selected by :meth:`set_backend`. One of these should be provided by the subclass for each backend listed in :attr:`backends`. cCs ||S)z$wrapper for backend, for common code)_calc_checksum_backendrr=r=r>rU szHasManyBackends._calc_checksumcCs|||S)z stub for _calc_checksum_backend() -- should load backend if one hasn't been loaded; if one has been loaded, this method should have been monkeypatched by _finalize_backend(). )rBrOrr=r=r>rO\ sz&HasManyBackends._calc_checksum_backendcsNtdd}|dur(fdd}n"tdrJJdjf|S)zp subclassed to support legacy 1.6 HasManyBackends api. (will be removed in passlib 2.0) Z_load_backend_Ncs SrT)%_HasManyBackends__load_legacy_backendr=r4r=r>r@r sz3HasManyBackends._get_backend_loader..loader _has_backend_z?%s: can't specify both ._load_backend_%s() and ._has_backend_%s)rhasattrrK)rrKr@r=r4r>r>h s z#HasManyBackends._get_backend_loadercCsLt|d|}td|j||ft|rDt|d|}||dSdSdS)NrQz%s: support for ._has_backend_%s is deprecated as of Passlib 1.7, and will be removed in Passlib 1.9/2.0, please implement ._load_backend_%s() insteadZ_calc_checksum_TF)rrrKrP_set_calc_checksum_backend)rrKr}funcr=r=r>Z__load_legacy_backend| s  z%HasManyBackends.__load_legacy_backendcCs>|j}|sJdt|s.td|j||f|js:||_dS)zl helper used by subclasses to validate & set backend-specific calc checksum helper. z*should only be called during set_backend()z,%s: backend %r returned invalid callable: %rN)r:callable RuntimeErrorrKr;rO)rrTZbackendr=r=r>rS s  z*HasManyBackends._set_calc_checksum_backendN) rArrrrrOrr>rPrSr=r=r=r>r4% s/   r4c@seZdZdZdZededdddfddZdZdZdd Z d d Z e e Z dZ e d d ZdZe ddZdZddZddZddZddZddZddZdZddZdd Zd!d"Zed#d$d%d&d'Zed#d$d%d(d)Zed#d$d*d+d,d-Zd.d/Z d0d1Z!dS)2r5aUwraps another handler, adding a constant prefix. instances of this class wrap another password hash handler, altering the constant prefix that's prepended to the wrapped handlers' hashes. this is used mainly by the :doc:`ldap crypt ` handlers; such as :class:`~passlib.hash.ldap_md5_crypt` which wraps :class:`~passlib.hash.md5_crypt` and adds a ``{CRYPT}`` prefix. usage:: myhandler = PrefixWrapper("myhandler", "md5_crypt", prefix="$mh$", orig_prefix="$1$") :param name: name to assign to handler :param wrapped: handler object or name of registered handler :param prefix: identifying prefix to prepend to all hashes :param orig_prefix: prefix to strip (defaults to ''). :param lazy: if True and wrapped handler is specified by name, don't look it up until needed. r=rBFNcCs||_t|tr|d}||_t|tr4|d}||_|rD||_t|drZ||n||_ |sl| |dur|dur|r|}nt dt|tr|d}|dt ||dt |krt d||_ dS)NrnrKTzno prefix specifiedzident must agree with prefix)rKrcrirjrr orig_prefixrrR _set_wrapped _wrapped_name _get_wrappedrr:_ident)rrKwrappedrrrWZlazydocrr=r=r>r s0         zPrefixWrapper.__init__cCs.d|jvr$|jr$td|jftj||_dS)NrzkPrefixWrapper: 'orig_prefix' option may not work correctly for handlers which have multiple identifiers: %r)rrWrrKrdZPasslibRuntimeWarning_wrapped_handlerrrMr=r=r>rX szPrefixWrapper._set_wrappedcCs&|j}|dur"t|j}|||SrT)r^rrYrXr_r=r=r>rZ s   zPrefixWrapper._get_wrappedcCsF|j}|durBd}|js<|j}t|dd}|dur<||}||_|S)NFr)r[rWr\r _wrap_hash)rr}r\rr=r=r>r s  zPrefixWrapper.identcsNj}|durJd}jsDj}t|dd}|rDtfdd|D}|_|S)NFrc3s|]}|VqdSrT)r`)rWrrr=r>rZ r[z-PrefixWrapper.ident_values..) _ident_valuesrWr\rr)rr}r\Zidentsr=rr>r s zPrefixWrapper.ident_values)rr]r{rrr#rrrrr rrrr7r5r0r/Z is_disabledrrrrcCsVt|jp |jg}|jr(|d|j|jr>|d|jd|}d|j|fS)Nz prefix=%rzorig_prefix=%rz, zPrefixWrapper(%r, %s))reprrYr^rrrJrWjoinrK)rargsr=r=r>__repr__& s zPrefixWrapper.__repr__csBtt|j}||j|j|fdd|jDt|S)Nc3s|]}t|r|VqdSrT)rR)rWattrr\r=r>rZ3 s z(PrefixWrapper.__dir__..)r\dirrupdaterFr\ _proxy_attrsr^)rattrsr=rgr>__dir__/ s zPrefixWrapper.__dir__cCs(||jvrt|j|Std|fdS)zFproxy most attributes from wrapped class (e.g. rounds, salt size, etc)zmissing attribute: %rN)rjrr\r)rrfr=r=r> __getattr__9 s  zPrefixWrapper.__getattr__cCs>||jvr0|jr0|j}t||r0t|||dSt|||SrT)rj _derived_fromr\rRsetattrr __setattr__)rrfr}r\r=r=r>rp? s   zPrefixWrapper.__setattr__cCs0|j}||st||j|t|dS)z4given hash belonging to wrapper, return orig versionN)rrrFrdrorWr:)rrhrrr=r=r> _unwrap_hashL s  zPrefixWrapper._unwrap_hashcCsNt|tr|d}|j}||s0t|j|j|t |d}t |S)z0given orig hash; return one belonging to wrapperrnN) rcrirjrWrFrdror\rrr:r)rrhrWr\r=r=r>r`U s    zPrefixWrapper._wrap_hashcKs^|jjfi|}||jus Jt|j||j|jd}||_|jD]}t||t ||qB|S)N)rrrW) r\rr5rKrrrWrn_using_clone_attrsror)rrRrwrapperrfr=r=r>rd s zPrefixWrapper.usingcKs||}|jj|fi|SrT)rqr\r)rrhrRr=r=r>ro s zPrefixWrapper.needs_updatecCs.t|}||jsdS||}|j|S)NF)rmrFrrrqr\r)rrhr=r=r>rs s   zPrefixWrapper.identifyrrrcKs,|jjfi|}|dur"td||S)Nz+.genconfig() must return a string, not None)r\rrVr`)rrRrr=r=r>rz szPrefixWrapper.genconfigcKs:|durt|dd}||}||jj||fi|S)Nrnz config/hash)rrqr`r\r)rrbrrRr=r=r>r s  zPrefixWrapper.genhashz.hash())rrZ replacementcKs|j|fi|SrTrlrrbrRr=r=r>encrypt szPrefixWrapper.encryptcKs||jj|fi|SrT)r`r\rhrtr=r=r>rh szPrefixWrapper.hashcKs,t|dd}||}|jj||fi|S)Nrnrh)rrqr\r)rrbrhrRr=r=r>r s  zPrefixWrapper.verify)"rArrrrrrrrYr^rXrZrr\r[rrarrjrerlrmrprqr`rnrrrr'rrrurhrr=r=r=r>r5 sF                r5)r@)rwNr}N)rrr)r})r@Nr}F)brZ __future__rrCZloggingZ getLoggerrAr9r7 threadingwarningsrZ passlib.excrdZ passlib.ifcZifcrrrrZpasslib.registryrZ passlib.utilsr r r r r rrrrrrrrrrrrrrZpasslib.utils.compatrrrrrr r!r"r#r$r%Zpasslib.utils.decorr&r'__all__Z H64_CHARSZ B64_CHARSZPADDED_B64_CHARSZ UC_HEX_CHARSZ LC_HEX_CHARSr?rLrSr_Z_UDOLLARryrfrmr(r)rr*r+rrrrrr,r-rr.r/r0r1r2r3r*RLockr9r-rCr4rr5r=r=r=r>s    4$8   K  "  '!*BZ ' 'yHN|