HEX
Server: Apache/2.4.62 (Unix) OpenSSL/1.1.1k
System: Linux ns565604.ip-54-39-133.net 4.18.0-553.50.1.el8_10.x86_64 #1 SMP Tue Apr 15 08:09:22 EDT 2025 x86_64
User: greer489 (1034)
PHP: 8.3.19
Disabled: NONE
Upload Files
File: //lib/python3.6/site-packages/__pycache__/spf.cpython-36.opt-1.pyc
3

�f�_\7�@@s�ddlmZdZdZdZdZdZddlZddlZddl	Z	ddl
Z
ddlZyddlj
ZWnddlZYnXddlZddlmZydd	lmZWn ek
r�dd	lmZYnXy ddlZeekr�dd
lmZWnNek
�r yddlZdd
lmZWnek
�red�YnXYnXd�dd�Zd�dd�ZyFddlZddlZddl Ze!ej"d��svdej"_#ej"j#ej"j$d<eZ%WnRddl&Z&e!e&j'd��s�de&j'_#de&j'j(d<e&j)j*j+e&j)j*_,e&j-�eZ%YnXej.dej/�Z0ej.dej/�Z1dZ2ej.e2�Z3ej.d�Z4ej.d�Z5ej.d�Z6ej.d�Z7dj8dgd�Z9ej.e9d�Z:ej.d e2ej/�Z;ej.d!d"d#iej/�Z<ej.d$d%e9d&d'�ej/�Z=d(d(d)�Z>d*d+d,d-d*d+d.d/d,d-d0d1d2d3d.d4�Z?d5d6d7d8d9d:d;d<d=d>d?�
Z@dZAd@ZBdAZCdBZDdBZEdBZFdBZGd
ZHd
ZIdCZJd�ZKdFdIdIdJdKdL�ZLGdMdN�dNeM�ZNGdOdP�dPeM�ZOGdQdR�dReM�ZPddeIdSd
fdTdU�ZQd�dVdW�ZRGdXdY�dYeS�ZTdZd[�ZUd\d]�ZVd^d_�ZWd`da�ZXdbdc�ZYddde�ZZd�dfdg�Z[d�dhdi�Z\ej]ddCk�r�djdk�Z^ndldk�Z^dmdn�Z_e`dok�r�ddlaZay&eajaejbdpd�dqdrdsdtg�\ZcZbWnDeajdk
�rPZez$eeee��ee�ejfdC�WYddZe[eXnXdSZgdZhxFecD]>\ZiZjeid�k�rvdZgeid�k�r�ekej�Zhneid�k�r`ee��q`Weleb�dk�r�ee�e_��neleb�dpk�rZy*eTd{d|d}e	jm�d~�Zneenjoebd��WnZeOk
�r(Zpzedep�WYddZp[pXn.ePk
�rTZpzed�ep�WYddZp[pXnX�njeleb�d�k�reb\ZqZrZseTeqerese	jm�egehd��ZnenjR�Zted�etenju�etdd0k�r�ed�enjv�enju�enjw�r�enjwjx�r�ed�enjwjx�enjy�r�x�enjyD]Zzeez��q�Wn�eleb�dk�r�ebdpd�\ZqZrZseTeqerese	jm�dSegd��ZnenjRebd�Zted�etenju�etdd0k�rxed�enjv�enju�enjw�r�enjwjx�r�ed�enjwjx�enjy�r�x"enjyD]Zzeez��q�Wnee�dS)��)�print_functionz,Terence Way, Stuart Gathman, Scott Kittermanzpyspf@openspf.orgz2.0.14�spfa�To check an incoming mail request:
    % python spf.py [-v] {ip} {sender} {helo}
    % python spf.py 69.55.226.139 tway@optsw.com mx1.wayforward.net

To test an SPF record:
    % python spf.py [-v] "v=spf1..." {ip} {sender} {helo}
    % python spf.py "v=spf1 +mx +ip4:10.0.0.1 -all" 10.0.0.1 tway@foo.com a    

To fetch an SPF record:
    % python spf.py {domain}
    % python spf.py wayforward.net

To test this script (and to output this usage message):
    % python spf.py
N)�reduce)�Message)�Bytesz;ipaddr module required: http://code.google.com/p/ipaddr-py/T�cCs�y�tj|||d�}|j�}|jddkr�|dkr8td��ytj||d|d�}|j�}Wn4tjk
r�}ztdt|���WYdd}~XnX|jd	d
kr�|jd	dkr�td|jd
dt|jd	���dd�|j	D�dd�|j
D�Stk
�r}ztdt|���WYdd}~Xnhtk
�rP}ztdt|���WYdd}~Xn6tjk
�r�}ztdt|���WYdd}~XnXdS)N)�qtype�timeoutZtcT�zNDNS: Truncated UDP Reply, SPF records should fit in a UDP packet, retrying TCPZtcp)rZprotocolr	zDNS: TCP Fallback error: Zrcoder�zError: Zstatusz	  RCODE: cSs$g|]}|d|df|df�qS)�name�typename�data�)�.0�arr�/usr/lib/python3.6/spf.py�
<listcomp>rsz#DNSLookup_pydns.<locals>.<listcomp>cSs$g|]}|d|df|df�qS)rr
rr)rrrrrrtszDNS )�DNSZ
DnsRequest�req�header�AmbiguityWarningZDNSError�	TempError�str�IOError�answersZ
additional�AttributeError)rr�strictr	rZresp�xrrr�DNSLookup_pydns]s."$  r�cCsVg}y�tjj||�}x�|D]�}|dks.|dkrD|j||f|jf�q|dkrh|j||f|j|jff�q|dkr�|j||f|jjd�f�q|dks�|dkr|j||f|j	f�qWWn�tjj
k
r�Yn�tjjk
r�Ynntjj
k
�r}ztdt|���WYdd}~Xn8tjjk
�rP}ztdt|���WYdd}~XnX|S)	N�A�AAAA�MX�PTRT�TXT�SPFzDNS )�dnsZresolver�query�appendZaddressZ
preferenceZexchange�targetZto_textZstringsZNoAnswerZNXDOMAINZ	exceptionZTimeoutrrZ
NoNameservers)rrZtcpfallbackr	ZretValrZrdatarrrr�DNSLookup_dnspython~s*
 "r+r&�cs^v=spf1$|^v=spf1 z^([a-z][a-z0-9_\-\.]*)=z%(%|_|-|(\{[^\}]*\}))z(?<!%)%[^{%_-]|%$z([0-9]*)(r?)([^0-9a-zA-Z]*)z//(0|[1-9]\d*)$z/(0|[1-9]\d*)$z\.z%(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])��$zA\.(?:[0-9a-z]*[a-z][0-9a-z]*|[0-9a-z]+-[0-9a-z-]*[0-9a-z])\.?$|%sz%(atext)s+([.]%(atext)s+)*$Zatextz[0-9a-z!#$%&'*+/=?^_`{}|~-]a�(?:%(hex4)s:){6}%(ls32)s$|::(?:%(hex4)s:){5}%(ls32)s$|(?:%(hex4)s)?::(?:%(hex4)s:){4}%(ls32)s$|(?:(?:%(hex4)s:){0,1}%(hex4)s)?::(?:%(hex4)s:){3}%(ls32)s$|(?:(?:%(hex4)s:){0,2}%(hex4)s)?::(?:%(hex4)s:){2}%(ls32)s$|(?:(?:%(hex4)s:){0,3}%(hex4)s)?::%(hex4)s:%(ls32)s$|(?:(?:%(hex4)s:){0,4}%(hex4)s)?::%(ls32)s$|(?:(?:%(hex4)s:){0,5}%(hex4)s)?::%(hex4)s$|(?:(?:%(hex4)s:){0,6}%(hex4)s)?::$z"(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|%s)z
[0-9a-f]{1,4})Zls32Zhex4�.)�l�s�pass�fail�neutral�softfail�	permerror�	temperror�none�local�trusted�	ambiguous)�+�-�?�~r2r3r6�errorr4r5r8r9r:r;�unknownzsender SPF authorizedzSPF fail - not authorizedzpermanent error in processingz!temporary DNS error in processingz)domain owner discourages use of this hostz#access neither permitted nor denied�z!No SPF result due to local policyz$No SPF check - trusted-forwarder.orgzNo error, but results may vary)
r2r3r6r7r5r4r8r9r:r;zv=spf1 a/24 mx/24 ptrz.v=spf1 ?include:spf.trusted-forwarder.org -all�
�r�mx�ptr�exists�include�ip4�ip6�all)Zprt�ipZipv4Zipv6zall.c@s"eZdZdZddd�Zdd�ZdS)rzSPF Warning - ambiguous resultsNcCs$tj|||�||_||_||_dS)N)�	Exception�__init__�msg�mech�ext)�selfrOrPrQrrrrN
szAmbiguityWarning.__init__cCs|jrd|j|jfS|jS)Nz%s: %s)rPrO)rRrrr�__str__szAmbiguityWarning.__str__)NN)�__name__�
__module__�__qualname__�__doc__rNrSrrrrrs
rc@s"eZdZdZddd�Zdd�ZdS)rzTemporary SPF errorNcCs(tj|||�t|�|_||_||_dS)N)rMrNrrOrPrQ)rRrOrPrQrrrrNs
zTempError.__init__cCs|jrd|j|jfS|jS)Nz%s: %s)rPrO)rRrrrrSszTempError.__str__)NN)rTrUrVrWrNrSrrrrrs
rc@s"eZdZdZddd�Zdd�ZdS)�	PermErrorzPermanent SPF errorNcCs$tj|||�||_||_||_dS)N)rMrNrOrPrQ)rRrOrPrQrrrrN%szPermError.__init__cCs|jrd|j|jfS|jS)Nz%s: %s)rPrO)rRrrrrS*szPermError.__str__)NN)rTrUrVrWrNrSrrrrrX#s
rXFc
Cs*t||||||||d�j�\}}	}
||
fS)a&Test an incoming MAIL FROM:<s>, from a client with ip address i.
    h is the HELO/EHLO domain name.  This is the RFC4408/7208 compliant
    pySPF2.0 interface.  The interface returns an SPF result and explanation
    only.  SMTP response codes are not returned since neither RFC 4408 nor RFC
    7208 does specify receiver policy.  Applications updated for RFC 4408 and
    RFC 7208 should use this interface.  The maximum time, in seconds, this
    function is allowed to run before a TempError is returned is controlled by
    querytime.  When set to 0 the timeout parameter (default 20 seconds)
    controls the time allowed for each DNS lookup.  When set to a non-zero
    value, it total time for all processing related to the SPF check is
    limited to querytime (default 20 seconds as recommended in RFC 7208,
    paragraph 4.6.4).

    Returns (result, explanation) where result in
    ['pass', 'permerror', 'fail', 'temperror', 'softfail', 'none', 'neutral' ].

    Example:
    #>>> check2(i='61.51.192.42', s='liukebing@bcc.com', h='bmsi.com')

    )�ir1�hr9�receiverr	�verbose�	querytime)r(�check)rYr1rZr9r[r	r\r]�res�_�exprrr�check2/s
rbc	CsFt||||||d�j�\}}}|dkr,d}n|dkr<|dk|||fS)a?Test an incoming MAIL FROM:<s>, from a client with ip address i.
    h is the HELO/EHLO domain name.  This is the pre-RFC SPF Classic interface.
    Applications written for pySPF 1.6/1.7 can use this interface to allow
    pySPF2 to be a drop in replacement for older versions.  With the exception
    of result codes, performance in RFC 4408 compliant.

    Returns (result, code, explanation) where result in
    ['pass', 'unknown', 'fail', 'error', 'softfail', 'none', 'neutral' ].

    Example:
    #>>> check(i='61.51.192.42', s='liukebing@bcc.com', h='bmsi.com')

    )rYr1rZr9r[r\r6rAZtempfailr@)r(r^)	rYr1rZr9r[r\r_�coderarrrr^Hsr^c@s:eZdZdZdddeddfdd�Zdd	�Zd
d�Zdd
�Zdd�Z	dd�Z
efdd�ZdHdd�Z
dd�Zdd�Zdd�Zdd�Zdd�Zd d!�Zd"d#�ZdId$d%�Zd&d'�ZdJd)d*�Zd+d,�ZdKd.d/�Zd0d1�Zd2d3�ZdLddMddNddOddPddQddRddSdiZdTd9d:�Zd;d<�Zd=d>�Zd?d@�Z dAdB�Z!dUdDdE�Z"dFdG�Z#dS)Vr(ajA query object keeps the relevant information about a single SPF
    query:

    i: ip address of SMTP client in dotted notation
    s: sender declared in MAIL FROM:<>
    l: local part of sender s
    d: current domain, initially domain part of sender s
    h: EHLO/HELO domain
    v: 'in-addr' for IPv4 clients and 'ip6' for IPv6 clients
    t: current timestamp
    p: SMTP client domain name
    o: domain part of sender s
    r: receiver
    c: pretty ip address (different from i for IPv6)

    This is also, by design, the same variables used in SPF macro
    expansion.

    Also keeps cache: DNS cache.  
    NTFrc

Cs�|||_|_|r*|r*d||_d|_nd|_t||�\|_|_tttj���|_	|j|_
d|_|rn||_nd|_i|_
tt�|_tt�|_||_d|_d|_||_||_|	|_|	dkr�|	|_d|_d|_|r�|j|�d|_||_d|_dS)Nzpostmaster@�helo�mailfromrArT)r1rZ�ident�split_emailr0�or�int�time�t�d�p�r�cache�dict�EXPLANATIONS�defexps�exps�libspf_local�lookups�void_lookupsrr	r]Ztimer�ipaddr�set_ip�default_modifierr\�authserv)
rRrYr1rZr9r[rr	r\r]rrrrNss<




	zquery.__init__cCstd|||f�dS)Nz%s: %s "%s")�print)rRrPrlrrrr�log�sz	query.logcCsFd|_|j�dkrg|_d}n�|j�dkr6g|_d}n�y6ytj|�|_Wn tk
rhtj|�|_YnXWn.tk
r�}ztt	|���WYdd}~XnX|jj
dkr�|jjr�tj|jj�|_d}q�d}nd}t	|j�|_
|�rd|_d|_|j�rd	jt|jjjd
d�j���|_d|_n$d
|_d|_|j�r<|jj|_d|_dS)z$Set connect ip, and ip6 or ip4 mode.F�listZlist6TN�r"rJr/�:rB�r!zin-addr� )�iplist�lower�	ipaddressZ
ip_addressrwrZ	IPAddress�
ValueErrorrXr�versionZipv4_mappedZIPv4Address�cr!�v�joinr}Zexploded�replace�upperrY�cidrmax)rRrYrJrrrrrx�s@ 
zquery.set_ipcCs.|j}|j}xdD]}|||<|||<qWdS)Nr5r3r6)r5r3r6)rsrr)rRrarsrrrYrrr�set_default_explanation�s

zquery.set_default_explanationcCs |j}xdD]}|||<qWdS)Nr5r3r6)r5r3r6)rs)rRrarsrYrrr�set_explanation�s
zquery.set_explanationcCsh|jsb|j�}|sd|_nH|j|kr.|j|_n4d|j}x(|D]}|j|�r>||_Pq>W|d|_|jS)NrAr/r)rm�validated_ptrsrl�endswith)rRrmZsfxrlrrr�getp�s





z
query.getpcCshtj|j�drdS|j}|j|�\}}}|dkr^|jrP|jjrP|jj\}}}nd	\}}||_|||fS)
z�Return a best guess based on a default SPF record.
    >>> q = query('1.2.3.4','','SUPERVISION1',receiver='example.com')
    >>> q.best_guess()[0]
    'none'
        r
r8�rBr6r4���)r8r�rB)r4r�)�	RE_TOPLAB�splitrl�
perm_errorr^rQ)rRrZpernr��errr�
best_guess�szquery.best_guesscCsBg|_d|_d|_d|_i|_yzd|_|sL|j|j�}|jrL|j	d|j|�|r\|j
dd�}|jrr|rrt||j�}|j
||jd�}|jr�||j_|j�|Stk
r�}z.|j|_|jr�|jj|j�dddt|�fSd}~Xn\tk
�r<}z>|j�s||_|j|_|j�r |jj|j�d	d
dt|�fSd}~XnXdS)a�

    Returns (result, mta-status-code, explanation) where result
    in ['fail', 'softfail', 'neutral' 'permerror', 'pass', 'temperror', 'none']

    Examples:
    >>> q = query(s='strong-bad@email.example.com',
    ...           h='mx.example.org', i='192.0.2.3')
    >>> q.check(spf='v=spf1 ?all')
    ('neutral', 250, 'access neither permitted nor denied')

    >>> q.check(spf='v=spf1 redirect=controlledmail.com exp=_exp.controlledmail.com')
    ('fail', 550, 'SPF fail - not authorized')
    
    >>> q.check(spf='v=spf1 ip4:192.0.0.0/8 ?all moo')
    ('permerror', 550, 'SPF Permanent Error: Unknown mechanism found: moo')

    >>> q.check(spf='v=spf1 ip4:192.0.0.n ?all')
    ('permerror', 550, 'SPF Permanent Error: Invalid IP4 address: ip4:192.0.0.n')

    >>> q.check(spf='v=spf1 ip4:192.0.2.3 ip4:192.0.0.n ?all')
    ('permerror', 550, 'SPF Permanent Error: Invalid IP4 address: ip4:192.0.0.n')

    >>> q.check(spf='v=spf1 ip6:2001:db8:ZZZZ:: ?all')
    ('permerror', 550, 'SPF Permanent Error: Invalid IP6 address: ip6:2001:db8:ZZZZ::')

    >>> q.check(spf='v=spf1 =a ?all moo')
    ('permerror', 550, 'SPF Permanent Error: Unknown qualifier, RFC 4408 para 4.6.1, found in: =a')

    >>> q.check(spf='v=spf1 ip4:192.0.0.0/8 ~all')
    ('pass', 250, 'sender SPF authorized')

    >>> q.check(spf='v=spf1 ip4:192.0.0.0/8 -all moo=')
    ('pass', 250, 'sender SPF authorized')

    >>> q.check(spf='v=spf1 ip4:192.0.0.0/8 -all match.sub-domains_9=yes')
    ('pass', 250, 'sender SPF authorized')

    >>> q.strict = False
    >>> q.check(spf='v=spf1 ip4:192.0.0.0/8 -all moo')
    ('permerror', 550, 'SPF Permanent Error: Unknown mechanism found: moo')
    >>> q.perm_error.ext
    ('pass', 250, 'sender SPF authorized')

    >>> q.strict = True
    >>> q.check(spf='v=spf1 ip4:192.1.0.0/16 moo -all')
    ('permerror', 550, 'SPF Permanent Error: Unknown mechanism found: moo')

    >>> q.check(spf='v=spf1 ip4:192.1.0.0/16 ~all')
    ('softfail', 250, 'domain owner discourages use of this host')

    >>> q.check(spf='v=spf1 -ip4:192.1.0.0/6 ~all')
    ('fail', 550, 'SPF fail - not authorized')

    # Assumes DNS available
    >>> q.check()
    ('none', 250, '')

    >>> q.check(spf='v=spf1 ip4:1.2.3.4 -a:example.net -all')
    ('fail', 550, 'SPF fail - not authorized')
    >>> q.libspf_local='ip4:192.0.2.3 a:example.org'
    >>> q.check(spf='v=spf1 ip4:1.2.3.4 -a:example.net -all')
    ('pass', 250, 'sender SPF authorized')

    >>> q.check(spf='v=spf1 ip4:1.2.3.4 -all exp=_exp.controlledmail.com')
    ('fail', 550, 'Controlledmail.com does not send mail from itself.')
    
    >>> q.check(spf='v=spf1 ip4:1.2.3.4 ?all exp=_exp.controlledmail.com')
    ('neutral', 250, 'access neither permitted nor denied')
        Nr�top�
� r7i�zSPF Temporary Error: r6i&zSPF Permanent Error: )rPr��	mechanismrv�optionsru�dns_spfrlr\r|r�rt�insert_libspf_local_policy�check1rQrrO�probr)rrX)rRrZrcrrrrr^�sBF

zquery.checkcCs�|tkr|jrtd��td��y(z|j|}|_|j||�S||_XWnFtk
r�}z*|j|_|j	rt|j	j
|j	�ddd|fSd}~XnXdS)NzToo many levels of recursionr;rzSPF Ambiguity Warning: %s)�
MAX_RECURSIONr�AssertionErrorrXrl�check0rrOr�rPr))rRr�domain�	recursionZtmprrrrr�oszquery.check1cGsP|jrt|��|jsJyt|��Wn(tk
rH}z||_WYdd}~XnX|jS)N)rrXr�)rRrOrrrr�
note_error�szquery.note_errorcCs"tj|�drtd|��|j|�S)zvalidate and expand domain-specr
zInvalid domain found (use FQDN)r�)r�r�rX�expand)rR�argrrr�
expand_domain�s
zquery.expand_domaincCs�|jd�r"|jd|�|dd!�}t||j�\}}}}|r^tj|d�}|rZ|dd�}nd}|tkrz|jd|�t|}|dkr�tj|�r�|jd	|�}d
}|d"kr�|dkr�d}n|dkr�t	d
|��|dkr�d}n|dkr�t	d|��|j
dkr�|}�n|d
k�stj|��r||d
k�r,|jd|�d
|}}|dk	�r@t	d|��|dk�rPd}n|dk�rdt	d
|��tj|��st	d|��n�|dk�r�|dk	�r�t	d|��|dk�r�d}n|dk�r�t	d|��tj|��st	d|��n.|dk	�s�|dk	�r�|tk�r�t	d|��|j
}|d#k�r�|dk�r*|�r*t	d|��|j|�}|�sDt	d|��|dk�rx||jk�rx|dk�rnt	d|��t	d|��|||||fS|dk�r�|jd��r�|jd|�|tk�r�|||||fS|dd�tk�r�|jd |�}n|jd|�}|||||fS)$a
Parse and validate a mechanism.
    Returns mech,m,arg,cidrlength,result

    Examples:
    >>> q = query(s='strong-bad@email.example.com.',
    ...           h='mx.example.org', i='192.0.2.3')
    >>> q.validate_mechanism('A')
    ('A', 'a', 'email.example.com', 32, 'pass')

    >>> q = query(s='strong-bad@email.example.com',
    ...           h='mx.example.org', i='192.0.2.3')
    >>> q.validate_mechanism('A//64')
    ('A//64', 'a', 'email.example.com', 32, 'pass')

    >>> q.validate_mechanism('A/24//64')
    ('A/24//64', 'a', 'email.example.com', 24, 'pass')
    
    >>> q.validate_mechanism('?mx:%{d}/27')
    ('?mx:%{d}/27', 'mx', 'email.example.com', 27, 'neutral')

    >>> try: q.validate_mechanism('ip4:1.2.3.4/247')
    ... except PermError as x: print(x)
    Invalid IP4 CIDR length: ip4:1.2.3.4/247
    
    >>> try: q.validate_mechanism('ip4:1.2.3.4/33')
    ... except PermError as x: print(x)
    Invalid IP4 CIDR length: ip4:1.2.3.4/33

    >>> try: q.validate_mechanism('a:example.com:8080')
    ... except PermError as x: print(x)
    Invalid domain found (use FQDN): example.com:8080
    
    >>> try: q.validate_mechanism('ip4:1.2.3.444/24')
    ... except PermError as x: print(x)
    Invalid IP4 address: ip4:1.2.3.444/24
    
    >>> try: q.validate_mechanism('ip4:1.2.03.4/24')
    ... except PermError as x: print(x)
    Invalid IP4 address: ip4:1.2.03.4/24
    
    >>> try: q.validate_mechanism('-all:3030')
    ... except PermError as x: print(x)
    Invalid all mechanism format - only qualifier allowed with all: -all:3030

    >>> q.validate_mechanism('-mx:%%%_/.Clara.de/27')
    ('-mx:%%%_/.Clara.de/27', 'mx', '% /.Clara.de', 27, 'fail')

    >>> q.validate_mechanism('~exists:%{i}.%{s1}.100/86400.rate.%{d}')
    ('~exists:%{i}.%{s1}.100/86400.rate.%{d}', 'exists', '192.0.2.3.com.100/86400.rate.email.example.com', 32, 'softfail')

    >>> q.validate_mechanism('a:mail.example.com.')
    ('a:mail.example.com.', 'a', 'mail.example.com', 32, 'pass')

    >>> try: q.validate_mechanism('a:mail.example.com,')
    ... except PermError as x: print(x)
    Do not separate mechnisms with commas: a:mail.example.com,

    >>> q = query(s='strong-bad@email.example.com',
    ...           h='mx.example.org', i='2001:db8:1234::face:b007')    
    >>> q.validate_mechanism('A//64')
    ('A//64', 'a', 'email.example.com', 64, 'pass')

    >>> q.validate_mechanism('A/16')
    ('A/16', 'a', 'email.example.com', 128, 'pass')

    >>> q.validate_mechanism('A/16//48')
    ('A/16//48', 'a', 'email.example.com', 48, 'pass')

    �,z%Do not separate mechnisms with commasNr
rr2zUnknown mechanism foundrz'Use the ip4 mechanism for ip4 addressesrIrEr�zInvalid IP4 CIDR lengthr�zInvalid IP6 CIDR lengthrJzMissing IP4zDual CIDR not allowedzInvalid IP4 addresszInvalid IP6 addresszCIDR not allowedrFrGrHzimplicit exists not allowedz
empty domain:zinclude has trivial recursionz include mechanism missing domainrKrz>Invalid all mechanism format - only qualifier allowed with allz0Unknown qualifier, RFC 4408 para 4.6.1, found inr�)rrE)rrErFrGrH)r�r��parse_mechanismrl�RESULTS�get�COMMON_MISTAKES�RE_IP4�matchrXr��RE_IP6�ALL_MECHANISMSr�r��count)rRrP�mr��
cidrlengthZcidr6length�resultrrrr�validate_mechanism�s�F




























zquery.validate_mechanismcCs�|sddtdfS|jd�}|dj�dkrP|jdkrBtd|j��ddtdfSdd	�|dd
�D�}|j}d
}d}g}g}�x�|D�]�}tj|�dd
�}	t|	�dkr�|j	|j
|��q�|	\}
}|
|kr�|
d
kr�td|��|jd|
|�|j	|
�|
dk�rV|�std|��|j
|�}|�ry&|j|�}|�rD|�rD|j|�WnYnXq�|
d
k�r�|j�|j
|�}|�std|��q�|
dk�r�|jdk�r�td��|j�r|j�r|j|�}tj||�}q�|
dk�r|�sx4|jd�D]}
|
�r�d|j|
<�q�Wq�|j|	d�q�W�xt|D�]\}}	}}}|	dk�r�|j�|j|�}|j�r`|jd||�|j|||d�\}}}|dk�r�P|dk�r�|jd||�d}�q�q|	dk�r�P�q|	dk�r|j�yt|j|d��dk�r�PWntk
�r�YnX�q|	dk�r2|j�|j|j||j�|��r$Pn�|	dk�r\|j�|j|j|�|��r$Pn�|	d k�r�|jd!k�r$y|j|g|��r�PWn"tj k
�r�td"|��YnXnv|	d#k�r|jd#k�r$y|j|g|��r�PWn"tj k
�r�td"|��YnXn$|	d$k�r|j�t!|j"�|��rP�qW|�r�|j|�}|�sJtd%|��|j�r`|jd
||�|�sxt#|j$�|_i|_|j|||�S|}d
}|�s�||_%|d&k�r�|d'||fS|d||fSd
S)(z�Test this query information against SPF text.

        Returns (result, mta-status-code, explanation) where
        result in ['fail', 'unknown', 'pass', 'none']
        r8r�r�rzv=spf1r
zInvalid SPF record incSsg|]}|r|�qSrr)rrPrrrrWsz query.check0.<locals>.<listcomp>Nr4rD�redirectz"redirect= MUST appear at most oncez%s= MUST appear at most oncerazexp has empty domain-spec:zredirect has empty domain:�defaultz"The default= modifier is obsolete.�opr/TrHr2z+No valid SPF record for included domain: %srKrGr!rrErIzin-addrzsyntax errorrJrFz!redirect domain has no SPF recordr3i&)&rqr�r�rrrlrs�RE_MODIFIER�lenr)r�rXr�r��get_explanationr��
check_lookupsryr�r�r�r�r�r\r|r��dns_a�	cidrmatchr!�dns_mxr��socketr@�domainmatchr�rprrr�)rRrr�rsr�r�ZmechsZ	modifiersrPr��modr�rar�r�r�rlr_rcZtxtZredirect_recordrrrr�=s�


























zquery.check0cCsB|jd|_|jtdkr*tdtd��|jtkr>|jd�dS)Nr
r-zMore than %d DNS lookupszToo many DNS lookups)ru�
MAX_LOOKUPrXr�)rRrrrr��s

zquery.check_lookupscCsv|r`y8|j|dd�}t|�dkr:t|jt|d�dd��SWqrtk
r\|jdkrX�YqrXn|jdkrrtd��dS)	zExpand an explanation.T)�ignore_voidr
rF)�stripdotzEmpty domain-spec on exp=N)�dns_txtr�rr��to_asciirXr)rR�specrrrrr��s

zquery.get_explanationcCs�|jd�dkr:t}x&|jd�D]}|j|�rtd|��qWd}d}�xtj|�D�]}||||j��7}||j�|j��}|dkr�|d7}n�|dkr�|d7}n�|d	kr�|d
7}n�|dj	�}	|	dkr�|j
�n|	d
kr�|r�td|��t||	|�}
|
�rJ|
|k�rtd|��t|
|dd�t
j|	��}|	|dk�rBtj|d�}||7}|j�}qPW|||d�7}|�r�|jd��r�|dd�}|jd�dk�r�t|�dk�r�||jd�dd�}|S)a%Do SPF RFC macro expansion.

        Examples:
        >>> q = query(s='strong-bad@email.example.com',
        ...           h='mx.example.org', i='192.0.2.3')
        >>> q.p = 'mx.example.org'
        >>> q.r = 'example.net'

        >>> q.expand('%{d}')
        'email.example.com'

        >>> q.expand('%{d4}')
        'email.example.com'

        >>> q.expand('%{d3}')
        'email.example.com'

        >>> q.expand('%{d2}')
        'example.com'

        >>> q.expand('%{d1}')
        'com'

        >>> q.expand('%{p}')
        'mx.example.org'

        >>> q.expand('%{p2}')
        'example.org'

        >>> q.expand('%{dr}')
        'com.example.email'
    
        >>> q.expand('%{d2r}')
        'example.email'

        >>> q.expand('%{l}')
        'strong-bad'

        >>> q.expand('%{l-}')
        'strong.bad'

        >>> q.expand('%{lr}')
        'strong-bad'

        >>> q.expand('%{lr-}')
        'bad.strong'

        >>> q.expand('%{l1r-}')
        'strong'

        >>> q.expand('%{c}',stripdot=False)
        '192.0.2.3'

        >>> q.expand('%{r}',stripdot=False)
        'example.net'

        >>> q.expand('%{ir}.%{v}._spf.%{d2}')
        '3.2.0.192.in-addr._spf.example.com'

        >>> q.expand('%{lr-}.lp._spf.%{d2}')
        'bad.strong.lp._spf.example.com'

        >>> q.expand('%{lr-}.lp.%{ir}.%{v}._spf.%{d2}')
        'bad.strong.lp.3.2.0.192.in-addr._spf.example.com'

        >>> q.expand('%{ir}.%{v}.%{l1r-}.lp._spf.%{d2}')
        '3.2.0.192.in-addr.strong.lp._spf.example.com'

        >>> try: q.expand('%(ir).%{v}.%{l1r-}.lp._spf.%{d2}')
        ... except PermError as x: print(x)
        invalid-macro-char : %(ir)

        >>> q.expand('%{p2}.trusted-domains.example.net')
        'example.org.trusted-domains.example.net'

        >>> q.expand('%{p2}.trusted-domains.example.net.')
        'example.org.trusted-domains.example.net'

        >>> q = query(s='@email.example.com',
        ...           h='mx.example.org', i='192.0.2.3')
        >>> q.p = 'mx.example.org'
        >>> q.expand('%{l}')
        'postmaster'

        �%rr/zinvalid-macro-char rBz%%z%_r�z%-z%20rDrmZcrtz&c,r,t macros allowed in exp= text onlyzUnknown Macro Encounteredrr
r?N�r�r�)�find�RE_INVALID_MACROr��searchrX�RE_CHAR�finditer�start�endr�r��getattr�
expand_one�JOINERSr��urllibparseZquoter�r�r��index)rRr1r�Zregex�labelr�r�rYZmacroZletter�	expansionr�rrrr�sLW






zquery.expandcCs�x(|jd�D]}|s"t|�dkrdSqWdd�|j|�D�}t|�dkrd|jr\td|j�td��t|�dkr�|jd	kr�t|d
�S|jdk�rLydd�|j|dd
d�D�}Wn8t	k
r�}z|jdkr�t	|��g}WYdd}~XnXt|�dkr�td��t|�dk�rL|jdk�r@t|�dk�r@|d
|d
k�r@t
d��t|d
�St|�dk�rft|d
�St�r�dd�|j|dtd
d�D�}t|�dk�r�t|d
�SdS)z�Get the SPF record recorded in DNS for a specific domain
        name.  Returns None if not found, or if more than one record
        is found.
        r/�?NcSsg|]}tj|�r|�qSr)�RE_SPFr�)rrkrrrr�sz!query.dns_spf.<locals>.<listcomp>r
zcache=z'Two or more type TXT spf records found.rDrcSsg|]}tj|�r|�qSr)r�r�)rrkrrrr�sr&T)r�z'Two or more type SPF spf records found.zLv=spf1 records of both type TXT and SPF (type 99) present, but not identicalcSsg|]}tj|�r|�qSr)r�r�)rrkrrrr�sz._spf.)r�r�r�r\r{rorXrr�rr�DELEGATE)rRr�r�r�brrrrr��s@
,z
query.dns_spfr%cCst|rpyF|j|||d�}|rHdd�|D�}t|dt�r:|Sdd�|D�SWn$tk
rntd||f��YnXgS)z,Get a list of TXT records for a domain name.)r�cSs&g|]}|r|ddd�j|��qS)rN)r�)rrrrrr�sz!query.dns_txt.<locals>.<listcomp>rcSsg|]}|jd��qS)zutf-8)�encode)rr1rrrr�sz.Non-ascii characters found in %s record for %s)r'�
isinstance�bytes�UnicodeErrorrX)rR�
domainnameZrrr�Zdns_listrrrrr��sz
query.dns_txtcsz�j|d�}�jrPt}t|�tkr.tdt���jdkrXt|�dkrXtd|��ntd}|j��fdd�|d	|�D�S)
zSGet a list of IP addresses for all MX exchanges for a
        domain name.
        r#z More than %d MX records returnedr
rz$No MX records found for mx mechanismr-cs(g|] }�j|d�j�D]}|�qqS)r
)r�r!)rrEr)rRrrr�sz query.dns_mx.<locals>.<listcomp>N)r'r�MAX_MXr�rXr�sort)rRr�Zmxnames�maxr)rRrr��s


zquery.dns_mxr!cCsZ|sgS|j||�}|jdkr8t|�dkr8td||��|dkrVttkrVdd�|D�S|S)z5Get a list of IP addresses for a domainname.
        r
rzNo %s records found forr"cSsg|]}t|��qSr)r)rrLrrrr�szquery.dns_a.<locals>.<listcomp>)r'rr�rr�r)rRr�r!rnrrrr��szquery.dns_ac
s��jrzt}�jdkr�yJ�j�j�}t|�|krDd|}t|�j��nt|�dkr\td�j��Wq�td�j��Yq�Xntd}�j���fdd��j�j�d|�D�S)	z=Figure out the validated PTR domain names for the connect IP.r
z!More than %d PTR records returnedrz&No PTR records found for ptr mechanismr-cs&g|]}�j�j|�j���r|�qSr)r�r�r!)rrm)r�rRrrrsz(query.validated_ptrs.<locals>.<listcomp>N)r�MAX_PTR�dns_ptrrYr�rr�r�)rRr�ZptrnamesZwarningr)r�rRrr��s"
zquery.validated_ptrscCs|jdt|�|jfd�S)z-Get a list of domain names for an IP address.z
%s.%s.arpar$)r'�reverse_dotsr�)rRrYrrrr�	sz
query.dns_ptrr#�CNAMEr"r$r&cCs�|std��t|�}|jd�r*|dd�}tdd�|jd�d�sDgS|j�}|jj||fg�}|rf|S|df}|jj|�}|jo�|j	d	�}|r�|d
}�n&t
j}	|jd
kr�t
d��|j|jkr�|jd
kr�|j}
n|j}
tj�}x�t|||j|
�D]�\}}
|�r
td||
�|d
j�|df}||k�rF|
}|jj||fg�}|�rFP|ddk�sd||df|	kr�|�rvtd
||
�|jj|g�j|
�q�W|jj||fg�}|jd
k�r�|jtj�||_|�rL|�rL|�s�i}nt|�tk�r�tdt��|||<|j�jd�|k�r(|jdk�rLtd|��n$|j|||d�}|�rL||j||f<|�r�|�r�|jd7_|jtk�r�tdt��|S)a�DNS query.

        If the result is in cache, return that.  Otherwise pull the
        result from DNS, and cache ALL answers, so additional info
        is available for further queries later.

        CNAMEs are followed.

        If there is no data, [] is returned.

        pre: qtype in ['A', 'AAAA', 'MX', 'PTR', 'TXT', 'SPF']
        post: isinstance(__return__, types.ListType)

        Examples:
        >>> c = query(s='strong-bad@email.example.com',
        ...           h='parallel.kitterman.org',i='192.0.2.123')
        >>> "".join( chr(x) for x in bytearray(c.dns('parallel.kitterman.org', 'TXT')[0][0]) )
        'v=spf1 include:long.kitterman.org include:cname.kitterman.org -all'
        z
Invalid queryr/Nr
cSs |odt|�kodkSS)Nr�@)r�)r�yrrr�<lambda>:szquery.dns.<locals>.<lambda>Tr�zcname.rz)DNS Error: exceeded max query lookup timezresult=z	addcache=z Length of CNAME chain exceeds %dz
CNAME loop)�cnamesz Void lookup limit of %d exceededr�)rMrr�rr�r�ror�r\�
startswithr(�
SAFE2CACHEr]rr	rj�	DNSLookuprr{�
setdefaultr)r��	MAX_CNAMErX�rstriprr'rv�MAX_VOID_LOOKUPS)rRrrr�r�r�ZcnamekZcname�debugZ
safe2cacher	Ztimethen�kr�rrrr'"sp


z	query.dnscCs$y�ylxfdd�|D�D]T}|j|d�}t|jt�rB|j|j�rhdSq||jkrZ|jj|�q|jj|j�qWWn|t	k
r�xfdd�|D�D]T}|j|d�}t|jt�r�|j|j�r�dSq�||jkr�|jj|�q�|jj|j�q�WYnXWn0t
k
�r}ztt|���WYdd}~XnXdS)aBMatch connect IP against a CIDR network of other IP addresses.

        Examples:
        >>> c = query(s='strong-bad@email.example.com',
        ...           h='mx.example.org', i='192.0.2.3')
        >>> c.p = 'mx.example.org'
        >>> c.r = 'example.com'

        >>> c.cidrmatch(['192.0.2.3'],32)
        True
        >>> c.cidrmatch(['192.0.2.2'],32)
        False
        >>> c.cidrmatch(['192.0.2.2'],31)
        True

        >>> six = query(s='strong-bad@email.example.com',
        ...           h='mx.example.org', i='2001:0db8:0:0:0:0:0:0001')
        >>> six.p = 'mx.example.org'
        >>> six.r = 'example.com'

        >>> six.cidrmatch(['2001:0DB8::'],127)
        True
        >>> six.cidrmatch(['2001:0DB8::'],128)
        False
        >>> six.cidrmatch(['2001:0DB8:0:0:0:0:0:0001'],128)
        True
        cSsg|]}tj|��qSr)r�Z
ip_network)rrLrrrr�sz#query.cidrmatch.<locals>.<listcomp>)Z
new_prefixTcSsg|]}tj|dd��qS)F)r)r�Z	IPNetwork)rrLrrrr�sNF)
Zsupernetr�r��bool�__contains__rwr�r)rLrr�rXr)rRZipaddrs�nZnetwrkZnetworkrrrrr�ss,

zquery.cidrmatchcCs�ddl}djdd�|jd�D��}|jj|�}xr|jD]h}|jdkr6|j|_|j	|_	|j
djdkr�|j
dj|_
|j
dj|_|j
djd	kr6|j
dj|_q6WdS)
acSet SPF values from RFC 5451 Authentication Results header.
        
        Useful when SPF has already been run on a trusted gateway machine.

        Expects the entire header as an input.

        Examples:
        >>> q = query('192.0.2.3','strong-bad@email.example.com','mx.example.org')
        >>> q.mechanism = 'unknown'
        >>> p = q.parse_header_ar('''Authentication-Results: bmsi.com; spf=neutral \n     (abuse@kitterman.com: 192.0.2.3 is neither permitted nor denied by domain of email.example.com) \n     smtp.mailfrom=email.example.com \n    (sender=strong-bad@email.example.com; helo=mx.example.org; client-ip=192.0.2.3; receiver=abuse@kitterman.com; mechanism=?all)''')
        >>> q.get_header(q.result, header_type='authres', aid='bmsi.com')
        'Authentication-Results: bmsi.com; spf=neutral (unknown: 192.0.2.3 is neither permitted nor denied by domain of email.example.com) smtp.mailfrom=email.example.com (sender=email.example.com; helo=mx.example.org; client-ip=192.0.2.3; receiver=unknown; mechanism=unknown)'
        >>> p = q.parse_header_ar('''Authentication-Results: bmsi.com; spf=None (mail.bmsi.com: test; client-ip=163.247.46.150) smtp.mailfrom=admin@squiebras.cl (helo=mail.squiebras.cl; receiver=mail.bmsi.com;\n mechanism=mx/24)''')
        >>> q.get_header(q.result, header_type='authres', aid='bmsi.com')
        'Authentication-Results: bmsi.com; spf=none (unknown: 192.0.2.3 is neither permitted nor denied by domain of email.example.com) smtp.mailfrom=admin@squiebras.cl (sender=admin@squiebras.cl; helo=mx.example.org; client-ip=192.0.2.3; receiver=unknown; mechanism=unknown)'
        rNr�css|]}|j�VqdS)N)�strip)rr1rrr�	<genexpr>�sz(query.parse_header_ar.<locals>.<genexpr>r�rrerd)�authresr�r��AuthenticationResultsHeader�parse�results�method�authserv_idrzr�Z
propertiesr�valuerlr1rZ)rR�valr�ZarobjZresobjrrr�parse_header_ar�s
zquery.parse_header_arcCsX|jdd�}|dj�|_d|_t|�dkr0dS|d}|jd�rx|jd�}|dkrZ|jS|d|�|_||dd�}t�}|j	dd	|�i}x�|j
dd
�D]�\}}|dkr�|j|�q�|dkr�||_q�|d
kr�||_
q�|dkr�||_q�|dkr�||_q�|dk�r||_q�|dk�r ||_q�|jd�r�|||dd�<q�Wt|j|j
�\|_|_|S)a�Set SPF values from Received-SPF header.
        
        Useful when SPF has already been run on a trusted gateway machine.

        Examples:
        >>> q = query('0.0.0.0','','')
        >>> p = q.parse_header_spf('''Pass (test) client-ip=70.98.79.77;
        ... envelope-from="evelyn@subjectsthum.com"; helo=mail.subjectsthum.com;
        ... receiver=mail.bmsi.com; mechanism=a; identity=mailfrom''')
        >>> q.get_header(q.result)
        'Pass (test) client-ip=70.98.79.77; envelope-from="evelyn@subjectsthum.com"; helo=mail.subjectsthum.com; receiver=mail.bmsi.com; mechanism=a; identity=mailfrom'
        >>> o = q.parse_header_spf('''None (mail.bmsi.com: test)
        ... client-ip=163.247.46.150; envelope-from="admin@squiebras.cl";
        ... helo=mail.squiebras.cl; receiver=mail.bmsi.com; mechanism=mx/24;
        ... x-bestguess=pass; x-helo-spf=neutral; identity=mailfrom''')
        >>> q.get_header(q.result,**o)
        'None (mail.bmsi.com: test) client-ip=163.247.46.150; envelope-from="admin@squiebras.cl"; helo=mail.squiebras.cl; receiver=mail.bmsi.com; mechanism=mx/24; x-bestguess=pass; x-helo-spf=neutral; identity=mailfrom'
        >>> o['bestguess']
        'pass'
        Nr
rrDr8�(�)zReceived-SPFz; )rz	client-ipz
envelope-fromrdr[�problemr��identityzx-)r�r�r�r�r�r�r��commentrZ
add_headerZ
get_paramsrxr1rZrnrPrfrgr0rh)rRrr�posrOrmr�r�rrr�parse_header_spf�sD




zquery.parse_header_spfcCs"|jd�r|j|�S|j|�SdS)a�
Set SPF values from Received-SPF or RFC 5451 Authentication Results header.
        
        Useful when SPF has already been run on a trusted gateway machine. Auto
        detects the header type and parses it. Use parse_header_spf or parse_header_ar
        for each type if required.

        Examples:
        >>> q = query('0.0.0.0','','')
        >>> p = q.parse_header('''Pass (test) client-ip=70.98.79.77;
        ... envelope-from="evelyn@subjectsthum.com"; helo=mail.subjectsthum.com;
        ... receiver=mail.bmsi.com; mechanism=a; identity=mailfrom''')
        >>> q.get_header(q.result)
        'Pass (test) client-ip=70.98.79.77; envelope-from="evelyn@subjectsthum.com"; helo=mail.subjectsthum.com; receiver=mail.bmsi.com; mechanism=a; identity=mailfrom'
        >>> r = q.parse_header('''None (mail.bmsi.com: test)
        ... client-ip=163.247.46.150; envelope-from="admin@squiebras.cl";
        ... helo=mail.squiebras.cl; receiver=mail.bmsi.com; mechanism=mx/24;
        ... x-bestguess=pass; x-helo-spf=neutral; identity=mailfrom''')
        >>> q.get_header(q.result,**r)
        'None (mail.bmsi.com: test) client-ip=163.247.46.150; envelope-from="admin@squiebras.cl"; helo=mail.squiebras.cl; receiver=mail.bmsi.com; mechanism=mx/24; x-bestguess=pass; x-helo-spf=neutral; identity=mailfrom'
        >>> r['bestguess']
        'pass'
        >>> q = query('192.0.2.3','strong-bad@email.example.com','mx.example.org')
        >>> q.mechanism = 'unknown'
        >>> p = q.parse_header_ar('''Authentication-Results: bmsi.com; spf=neutral \n     (abuse@kitterman.com: 192.0.2.3 is neither permitted nor denied by domain of email.example.com) \n     smtp.mailfrom=email.example.com \n     (sender=strong-bad@email.example.com; helo=mx.example.org; client-ip=192.0.2.3; receiver=abuse@kitterman.com; mechanism=?all)''')
        >>> q.get_header(q.result, header_type='authres', aid='bmsi.com')
        'Authentication-Results: bmsi.com; spf=neutral (unknown: 192.0.2.3 is neither permitted nor denied by domain of email.example.com) smtp.mailfrom=email.example.com (sender=email.example.com; helo=mx.example.org; client-ip=192.0.2.3; receiver=unknown; mechanism=unknown)'
        >>> p = q.parse_header_ar('''Authentication-Results: bmsi.com; spf=None (mail.bmsi.com: test; client-ip=163.247.46.150) smtp.mailfrom=admin@squiebras.cl (helo=mail.squiebras.cl; receiver=mail.bmsi.com; mechanism=mx/24)''')
        >>> q.get_header(q.result, header_type='authres', aid='bmsi.com')
        'Authentication-Results: bmsi.com; spf=none (unknown: 192.0.2.3 is neither permitted nor denied by domain of email.example.com) smtp.mailfrom=admin@squiebras.cl (sender=admin@squiebras.cl; helo=mx.example.org; client-ip=192.0.2.3; receiver=unknown; mechanism=unknown)'
        zAuthentication-Results:N)r�rr
)rRrrrr�parse_header�s 

zquery.parse_headerrc
Ks|dkr|std��ddl}|s&|j}|j}t|j�}ddddd	d
dd�}	|j}
|
d
kr^d}n
t|j�}|	|}|dkr�|jr�tdj	|j��}
nd}
t|j
�}t|d�r�|j}nd||j
|�f}d||fg}|dk�rpx4d%D],}t�|}|r�|jd|jdd�|f�q�WxBtt|j���D].\}}|�r"|jd|jdd�t|�f��q"W|jdd|
f�dj	|�S|dk�r�|�r�t|j||j|||jdj|j|j|j|j|�d �gd!��St|j||j|||jd"j|j|j|j|�d#�gd!��Sntd$j|���dS)&aD

        Generate Received-SPF or Authentication Results header based on the
         last lookup.

        >>> q = query(s='strong-bad@email.example.com', h='mx.example.org',
        ...           i='192.0.2.3')
        >>> q.r='abuse@kitterman.com'
        >>> q.check(spf='v=spf1 ?all')
        ('neutral', 250, 'access neither permitted nor denied')
        >>> q.get_header('neutral')
        'Neutral (abuse@kitterman.com: 192.0.2.3 is neither permitted nor denied by domain of email.example.com) client-ip=192.0.2.3; envelope-from="strong-bad@email.example.com"; helo=mx.example.org; receiver=abuse@kitterman.com; mechanism=?all; identity=mailfrom'

        >>> q.check(spf='v=spf1 redirect=controlledmail.com exp=_exp.controlledmail.com')
        ('fail', 550, 'SPF fail - not authorized')
        >>> q.get_header('fail')
        'Fail (abuse@kitterman.com: domain of email.example.com does not designate 192.0.2.3 as permitted sender) client-ip=192.0.2.3; envelope-from="strong-bad@email.example.com"; helo=mx.example.org; receiver=abuse@kitterman.com; mechanism=-all; identity=mailfrom'
    
        >>> q.check(spf='v=spf1 ip4:192.0.0.0/8 ?all moo')
        ('permerror', 550, 'SPF Permanent Error: Unknown mechanism found: moo')
        >>> q.get_header('permerror')
        'PermError (abuse@kitterman.com: permanent error in processing domain of email.example.com: Unknown mechanism found) client-ip=192.0.2.3; envelope-from="strong-bad@email.example.com"; helo=mx.example.org; receiver=abuse@kitterman.com; problem=moo; identity=mailfrom'

        >>> q.check(spf='v=spf1 ip4:192.0.0.0/8 ~all')
        ('pass', 250, 'sender SPF authorized')
        >>> q.get_header('pass')
        'Pass (abuse@kitterman.com: domain of email.example.com designates 192.0.2.3 as permitted sender) client-ip=192.0.2.3; envelope-from="strong-bad@email.example.com"; helo=mx.example.org; receiver=abuse@kitterman.com; mechanism="ip4:192.0.0.0/8"; identity=mailfrom'

        >>> q.check(spf='v=spf1 ?all')
        ('neutral', 250, 'access neither permitted nor denied')
        >>> q.get_header('neutral', header_type = 'authres', aid='bmsi.com')
        'Authentication-Results: bmsi.com; spf=neutral (abuse@kitterman.com: 192.0.2.3 is neither permitted nor denied by domain of email.example.com) smtp.mailfrom=email.example.com (sender=strong-bad@email.example.com; helo=mx.example.org; client-ip=192.0.2.3; receiver=abuse@kitterman.com; mechanism=?all)'

        >>> p = query(s='strong-bad@email.example.com', h='mx.example.org',
        ...           i='192.0.2.3')
        >>> p.r='abuse@kitterman.com'
        >>> p.check(spf='v=spf1 redirect=controlledmail.com exp=_exp.controlledmail.com')
        ('fail', 550, 'SPF fail - not authorized')
        >>> p.ident = 'helo'
        >>> p.get_header('fail', header_type = 'authres', aid='bmsi.com')
        'Authentication-Results: bmsi.com; spf=fail (abuse@kitterman.com: domain of email.example.com does not designate 192.0.2.3 as permitted sender) smtp.helo=mx.example.org (sender=strong-bad@email.example.com; client-ip=192.0.2.3; receiver=abuse@kitterman.com; mechanism=-all)'

        >>> q.check(spf='v=spf1 ?all')
        ('neutral', 250, 'access neither permitted nor denied')
        >>> try: q.get_header('neutral', header_type = 'dkim')
        ... except SyntaxError as x: print(x)
        Unknown results header type: dkim
        r�zKauthserv-id missing for Authentication Results header type, see RFC5451 2.3rNZPassZNeutralZFailZSoftFail�NonerrX)r2r4r3r5r8r7r6rdr6r�rz%s: %sz%s (%s)r�	client_ip�
envelope_fromr[r	r�z%s=%s;r`r=zx-%s=%s;z%s=%sr
z@sender={0}; helo={1}; client-ip={2}; receiver={3}; mechanism={4})r��result_commentZ
smtp_mailfromZsmtp_mailfrom_comment)rrz6sender={0}; client-ip={1}; receiver={2}; mechanism={3})r�rZ	smtp_heloZsmtp_helo_commentz Unknown results header type: {0})rrrdr[r	r�)�SyntaxErrorr�rnr��quote_valuerZrfr1rPr�r��hasattrr�get_header_comment�localsr)r��sortedr}�itemsrr�ZSPFAuthenticationResultrl�format)rRr_r[Zheader_typeZaidZkvr�rrdZresmapr
r�tagr	r�rr�r�rrr�
get_headers`1






$

zquery.get_headercCs�|j}|dkrd||jfS|dkr2d||jfS|dkrHd|j|fS|dkr^d|j|fS|dkrtd	||jfS|d
kr�d|S|dkr�d
||jfStd|��dS)z)Return comment for Received-SPF header.  r2z.domain of %s designates %s as permitted senderr5zDtransitioning domain of %s does not designate %s as permitted senderr4z2%s is neither permitted nor denied by domain of %sr8r6z.permanent error in processing domain of %s: %sr7z1temporary error in processing during lookup of %sr3z6domain of %s does not designate %s as permitted senderz'invalid SPF result for header comment: N)rhr�r�r�)rRr_Zsenderrrrr�s,zquery.get_header_comment)N)T)r%F)r!)r#r!)r#r#)r�r!)r!r!)r"r")r$r$)r%r%)r&r&)NF)NrN)$rTrUrVrW�MAX_PER_LOOKUP_TIMErNr|rxr�r�r��DEFAULT_SPFr�r^r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r'r�rr
rrrrrrrr(^sT0(
p",
9


		
Q7!/%
ir(cCsL|sd|fS|jdd�}|ddkr,d|d<t|�dkr@t|�Sd|fSdS)atGiven a sender email s and a HELO domain h, create a valid tuple
    (l, d) local-part and domain-part.

    Examples:
    >>> split_email('', 'wayforward.net')
    ('postmaster', 'wayforward.net')

    >>> split_email('foo.com', 'wayforward.net')
    ('postmaster', 'foo.com')

    >>> split_email('terry@wayforward.net', 'optsw.com')
    ('terry', 'wayforward.net')
    Z
postmaster�@r
rrBrDN)r�r��tuple)r1rZ�partsrrrrg�srgcCs:|dkstj|�r|Sd|jdd�jdd�jdd�dS)aQuote the value for a key-value pair in Received-SPF header field
    if needed.  No quoting needed for a dot-atom value.

    Examples:
    >>> quote_value('foo@bar.com')
    '"foo@bar.com"'
    
    >>> quote_value('mail.example.com')
    'mail.example.com'

    >>> quote_value('A:1.2.3.4')
    '"A:1.2.3.4"'

    >>> quote_value('abc"def')
    '"abc\\"def"'

    >>> quote_value(r'abc\def')
    '"abc\\\\def"'

    >>> quote_value('abc..def')
    '"abc..def"'

    >>> quote_value('')
    '""'

    >>> quote_value(None)
    N�"�\z\\z\"�z\x00)�RE_DOT_ATOMr�r�)r1rrrr�srcCs�tj|�}t|�dkr.|dt|d�}}nd}tj|�}t|�dkr`|dt|d�}}nd}|jdd�}t|�dkr�|j�}|dkr�d}||||fS|dj�|d||fS)a�Breaks A, MX, IP4, and PTR mechanisms into a (name, domain,
    cidr,cidr6) tuple.  The domain portion defaults to d if not present,
    the cidr defaults to 32 if not present.

    Examples:
    >>> parse_mechanism('a', 'foo.com')
    ('a', 'foo.com', None, None)

    >>> parse_mechanism('exists','foo.com')
    ('exists', None, None, None)

    >>> parse_mechanism('a:bar.com', 'foo.com')
    ('a', 'bar.com', None, None)

    >>> parse_mechanism('a/24', 'foo.com')
    ('a', 'foo.com', 24, None)

    >>> parse_mechanism('A:foo:bar.com/16//48', 'foo.com')
    ('a', 'foo:bar.com', 16, 48)

    >>> parse_mechanism('-exists:%{i}.%{s1}.100/86400.rate.%{d}','foo.com')
    ('-exists', '%{i}.%{s1}.100/86400.rate.%{d}', None, None)

    >>> parse_mechanism('mx:%%%_/.Claranet.de/27','foo.com')
    ('mx', '%%%_/.Claranet.de', 27, None)

    >>> parse_mechanism('mx:%{d}//97','foo.com')
    ('mx', '%{d}', None, 97)

    >>> parse_mechanism('iP4:192.0.0.0/8','foo.com')
    ('ip4', '192.0.0.0', 8, None)
    rrr
NrrDrG)�RE_DUAL_CIDRr�r�ri�RE_CIDRr�)rrlrZcidr6Zcidrrrrr��s"

r�cCs|jd�}|j�dj|�S)z�Reverse dotted IP addresses or domain names.

    Example:
    >>> reverse_dots('192.168.0.145')
    '145.0.168.192'

    >>> reverse_dots('email.example.com')
    'com.example.email'
    r/)r��reverser�)rrrrrr�s

r�cCs<|j�}x.|D]&}|j�}||ks0|jd|�rdSqWdS)agrep for a given domain suffix against a list of validated PTR
    domain names.

    Examples:
    >>> domainmatch(['FOO.COM'], 'foo.com')
    1

    >>> domainmatch(['moo.foo.com'], 'FOO.COM')
    1

    >>> domainmatch(['moo.bar.com'], 'foo.com')
    0

    r/TF)r�r�)ZptrsZdomainsuffixrFrrrr�s
r�cCsh|s|Stj|�dd�\}}}|s(d}t|||�}|r@|j�|r^|t|�ddd�}dj|�S)Nr
r-r/rDrB)�RE_ARGSr�r(rir�)r�r�joiner�lnr(�
delimitersrrrr�6sr�cCs`gd}}xF|D]>}||krF|j|�d}|r:|j|�qN|j|�q||7}qW|j|�|S)a�Split a string into pieces by a set of delimiter characters.  The
    resulting list is delimited by joiner, or the original delimiter if
    joiner is not specified.

    Examples:
    >>> split('192.168.0.45', '.')
    ['192', '.', '168', '.', '0', '.', '45']

    >>> split('terry@wayforward.net', '@.')
    ['terry', '@', 'wayforward', '.', 'net']

    >>> split('terry@wayforward.net', '@.', '.')
    ['terry', '.', 'wayforward', '.', 'net']
    rB)r))rr,r*r��elementr�rrrr�As



r�cCsx|s|S|j�dd�}|rp|j�xJ|D]>}tj|d�s*|j|�}|g|||�<|j�dj|�}Pq*W|Sd|S)a�Returns spftxt with local inserted just before last non-fail
    mechanism.  This is how the libspf{2} libraries handle "local-policy".
    
    Examples:
    >>> insert_libspf_local_policy('v=spf1 -all')
    'v=spf1 -all'
    >>> insert_libspf_local_policy('v=spf1 -all','mx')
    'v=spf1 -all'
    >>> insert_libspf_local_policy('v=spf1','a mx ptr')
    'v=spf1 a mx ptr'
    >>> insert_libspf_local_policy('v=spf1 mx -all','a ptr')
    'v=spf1 mx a ptr -all'
    >>> insert_libspf_local_policy('v=spf1 mx -include:foo.co +all','a ptr')
    'v=spf1 mx a ptr -include:foo.co +all'

    # FIXME: is this right?  If so, "last non-fail" is a bogus description.
    >>> insert_libspf_local_policy('v=spf1 mx ?include:foo.co +all','a ptr')
    'v=spf1 mx a ptr ?include:foo.co +all'
    >>> spf='v=spf1 ip4:1.2.3.4 -a:example.net -all'
    >>> local='ip4:192.0.2.3 a:example.org'
    >>> insert_libspf_local_policy(spf,local)
    'v=spf1 ip4:1.2.3.4 ip4:192.0.2.3 a:example.org -a:example.net -all'
    r
Nrr�zv=spf1 )r�r(r�r�r�r�)Zspftxtr9rrP�whererrrr�^s


r�cCs2y
|jd�Stk
r,tdt|���YnXdS)z*Raise PermError if arg is not 7-bit ascii.�asciizNon-ascii characters foundN)r�r�rX�repr)r1rrrr��s
r�cCs2y
|jd�Stk
r,tdt|���YnXdS)z*Raise PermError if arg is not 7-bit ascii.r/zNon-ascii characters foundN)�decoder�rXr0)r1rrrr��s
cCsddl}ddl}|j|�S)Nr)�doctestrZtestmod)r2rrrr�_test�sr3�__main__r
zhvs:�helpr\r�-v�	--verbose�-s�--strict�-h�--helpz	127.0.0.1Z	localhostrA)rYr1rZr[zTemporary DNS error: zPermError: r)rYr1rZr[r\rzresult:zguessed:zlax:)rYr1rZr[rr\)Tr)Tr )rrErFrGrHrIrJrK)NNF)N)N)r6r7)r8r9)r:r;){Z
__future__r�
__author__Z	__email__�__version__ZMODULEZUSAGE�re�sysr��structrjZurllib.parserr�Zurllib�	functoolsrZ
email.messager�ImportErrorZ
email.Messager�r�rrrwr{rr+r'Zdns.resolverZ
dns.exceptionrZ	rdatatyper&Z_by_textr�rZTypeZtypemapZLibZ
RRunpackerZ
getTXTdataZ
getSPFdataZDiscoverNameServers�compile�
IGNORECASEr�r�ZPAT_CHARr�r�r)r&r'r�ZPAT_IP4r�r�r%r�r�r�rqr�rZTRUSTED_FORWARDERSr�r�r�r�r�rr�r�r�rMrrrXrbr^�objectr(rgrr�r�r�r�r�r��version_infor�r3rTZgetopt�argvZoptsZGetoptError�err�exitr\rrhrrir�Zgethostname�qr�rrYr1rZrnr�r�r�rQr�rLrrrr�<module>sh+
!










O!4

3