HEX
Server: Apache/2.4.65 (Unix) OpenSSL/3.5.5
System: Linux cpanel.ns-taberra.com 5.14.0-681.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Feb 11 20:19:22 UTC 2026 x86_64
User: asif201 (1002)
PHP: 8.3.0
Disabled: NONE
Upload Files
File: //lib/python3.9/site-packages/__pycache__/spf.cpython-39.pyc
a

���bn7�@s�ddlmZdZdZdZdZdZddlZddlZddl	Z	ddl
Z
ddlZzddlm
ZWnddlZYn0ddlZddlmZzdd	lmZWney�dd	lmZYn0z ddlZeeur�dd
lmZWnJe�yzddlZdd
lmZWne�yed�Yn0Yn0ddd�Zd�dd�ZzFddlZddlZddl Ze!ej"d��srdej"_#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&�-�eZ%Yn0e�.dej/�Z0e�.dej/�Z1dZ2e�.e2�Z3e�.d�Z4e�.d�Z5e�.d�Z6e�.d�Z7d�8dgd�Z9e�.e9d�Z:e�.d e2ej/�Z;e�.d!d"d#iej/�Z<e�.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
ZIdCZJdDZKdEdFdFdGdHdI�ZLGdJdK�dKeM�ZNGdLdM�dMeM�ZOGdNdO�dOeM�ZPddeIdPd
fdQdR�ZQd�dSdT�ZRGdUdV�dVeS�ZTdWdX�ZUdYdZ�ZVd[d\�ZWd]d^�ZXd_d`�ZYdadb�ZZd�dcdd�Z[d�dedf�Z\ej]ddCk�r�dgdh�Z^ndidh�Z^djdk�Z_e`dlk�r�ddlaZaz$ea�aejbdmd�dngdo��\ZcZbWnFeajd�yLZez*eeee��ee�e�fdC�WYdZe[en
dZe[e00dPZgdZhecD]>\ZiZjeidpv�rpdZgeidqv�r�ekej�Zhneidrv�rZee��qZeleb�dk�r�ee�e_��n�eleb�dmk�rVz*eTdsdtdue	�m�dv�Zneen�oebd��Wn^eO�y"Zpzedwep�WYdZp[pn8dZp[p0eP�yPZpzedxep�WYdZp[pn
dZp[p00�nbeleb�dyk�r�eb\ZqZrZseTeqerese	�m�egehdz�Znen�R�Zted{etenju�etdd0k�r�ed|en�v�enju�enjw�r�enjwjx�r�ed}enjwjx�enjy�r�enjyD]Zzeez��q�n�eleb�dk�r�ebdmd�\ZqZrZseTeqerese	�m�dPegd~�Znen�Rebd�Zted{etenju�etdd0k�rped|en�v�enju�enjw�r�enjwjx�r�ed}enjwjx�enjy�r�enjyD]Zzeez��q�nee�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�c
Cs�z�tj|||d�}|��}|jddkr�|dkr8td��ztj||d|d�}|��}Wn6tjy�}ztdt|���WYd}~n
d}~00|jd	d
kr�|jd	dkr�td|jd
dt|jd	���dd�|j	D�dd�|j
D�WSt�y$}ztdt|���WYd}~ntd}~0t�yX}ztdt|���WYd}~n@d}~0tj�y�}ztdt|���WYd}~n
d}~00dS)N)�qtype�timeoutZtcT�zNDNS: Truncated UDP Reply, SPF records should fit in a UDP packet, retrying TCPZtcp)r	Zprotocolr
zDNS: TCP Fallback error: Zrcoder�zError: �statusz	  RCODE: cSs$g|]}|d|df|df�qS��name�typename�data���.0�arr�'/usr/lib/python3.9/site-packages/spf.py�
<listcomp>rs�z#DNSLookup_pydns.<locals>.<listcomp>cSs$g|]}|d|df|df�qSrrrrrrrts��DNS )�DNSZ
DnsRequest�req�header�AmbiguityWarningZDNSError�	TempError�str�IOError�answers�
additional�AttributeError)rr	�strictr
rZresp�xrrr�DNSLookup_pydns]s4&$���$$r%�c
CsVg}z�tjj|||d�}|D]�}|dks0|dkrF|�||f|jf�q|dkrj|�||f|j|jff�q|dkr�|�||f|j�d�f�q|dks�|dkr|�||f|j	f�qWn�tjj
y�Yn�tjjy�Ynrtjj
�y}ztd	t|���WYd}~nBd}~0tjj�yP}ztd	t|���WYd}~n
d}~00|S)
N)Zlifetime�A�AAAA�MX�PTRT�TXT�SPFr)�dnsZresolver�query�appendZaddressZ
preferenceZexchange�targetZto_text�stringsZNoAnswerZNXDOMAIN�	exceptionZTimeoutrrZ
NoNameservers)rr	Ztcpfallbackr
ZretValr Zrdatar$rrr�DNSLookup_dnspython~s*$&r3r,�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)�+�-�?�~r:r;r>�errorr<r=r@rArBrC�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)
r:r;r>r?r=r<r@rArBrCzv=spf1 a/24 mx/24 ptrz.v=spf1 ?include:spf.trusted-forwarder.org -all�
�)r�mx�ptr�exists�include�ip4�ip6�allrNrQrRrS)Zprt�ipZipv4Zipv6zall.c@s"eZdZdZddd�Zdd�ZdS)rzSPF Warning - ambiguous resultsNcCs$t�|||�||_||_||_dS�N��	Exception�__init__�msg�mech�ext��selfrYrZr[rrrrX
szAmbiguityWarning.__init__cCs|jrd|j|jfS|jS�N�%s: %s�rZrY�r]rrr�__str__szAmbiguityWarning.__str__)NN��__name__�
__module__�__qualname__�__doc__rXrbrrrrrs
rc@s"eZdZdZddd�Zdd�ZdS)rzTemporary SPF errorNcCs(t�|||�t|�|_||_||_dSrU)rWrXrrYrZr[r\rrrrXs
zTempError.__init__cCs|jrd|j|jfS|jSr^r`rarrrrbszTempError.__str__)NNrcrrrrrs
rc@s"eZdZdZddd�Zdd�ZdS)�	PermErrorzPermanent SPF errorNcCs$t�|||�||_||_||_dSrUrVr\rrrrX%szPermError.__init__cCs|jrd|j|jfS|jSr^r`rarrrrb*szPermError.__str__)NNrcrrrrrh#s
rhFc
Cs*t||||||||d���\}}	}
||
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')

    )�ir9�hrA�receiverr
�verbose�	querytime�r.�check)rir9rjrArkr
rlrm�res�_�exprrr�check2/s
�rsc	CsFt||||||d���\}}}|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')

    )rir9rjrArkrlr>rIZtempfailrHrn)	rir9rjrArkrlrp�coderrrrrroHs�roc	@s,eZdZdZdddeddfdd�Zdd	�Zd
d�Zdd
�Zdd�Z	dd�Z
efdd�ZdDdd�Z
dd�Zdd�Zdd�Zdd�Zdd�Zd d!�Zd"d#�ZdEd$d%�Zd&d'�ZdFd)d*�Zd+d,�ZdGd.d/�Zd0d1�Zd2d3�Zddddddddd4�ZdHd5d6�Zd7d8�Zd9d:�Zd;d<�Z d=d>�Z!dId@dA�Z"dBdC�Z#dS)Jr.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�|||_|_|s(|r(d||_d|_nd|_t||�\|_|_ttt����|_	|j|_
d|_|rl||_nd|_i|_
tt�|_tt�|_||_d|_d|_||_||_|	|_|	dkr�|	|_d|_d|_|r�|�|�d|_||_d|_dS)Nzpostmaster@�helo�mailfromrIrT)r9rj�ident�split_emailr8�or�int�time�t�d�p�r�cache�dict�EXPLANATIONS�defexps�exps�libspf_local�lookups�void_lookupsr#r
rmZtimer�ipaddr�set_ip�default_modifierrl�authserv)
r]rir9rjrArkr#r
rlrmrrrrXss<



	zquery.__init__cCstd|||f�dS)Nz%s: %s "%s")�print)r]rZr}rrrr�log�sz	query.logc
CsFd|_|��dkrg|_d}n�|��dkr6g|_d}n�z4zt�|�|_Wntyft�|�|_Yn0Wn0ty�}ztt	|���WYd}~n
d}~00|jj
dkr�|jjr�t�|jj�|_d}q�d}nd}t	|j�|_
|�rd|_d|_|j�rd	�t|jj�d
d�����|_d|_n$d
|_d|_|j�r<|jj|_d|_dS)z$Set connect ip, and ip6 or ip4 mode.F�listZlist6TN�r(rRr7�:rJ�r'�in-addr� )�iplist�lower�	ipaddressZ
ip_addressr�r"Z	IPAddress�
ValueErrorrhr�versionZipv4_mappedZIPv4Address�cr'�v�joinr�Zexploded�replace�upperri�cidrmax)r]rirRr$rrrr��s@" 
zquery.set_ipcCs*|j}|j}dD]}|||<|||<qdS�N)r=r;r>)r�r�)r]rrr�r�rirrr�set_default_explanation�s
zquery.set_default_explanationcCs|j}dD]}|||<q
dSr�)r�)r]rrr�rirrr�set_explanation�szquery.set_explanationcCsf|js`|��}|sd|_nF|j|vr.|j|_n2d|j}|D]}|�|�r<||_q`q<|d|_|jS)NrIr7r)r~�validated_ptrsr}�endswith)r]r~Zsfxr}rrr�getp�s




z
query.getpcCsht�|j�drdS|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@�rJr>)r<r�)�	RE_TOPLAB�splitr}�
perm_errorror[)r]rZperr��errr�
best_guess�szquery.best_guessc
CsTg|_d|_d|_d|_i|_z|d|_|sL|�|j�}|jrL|�	d|j|�|r\|�
dd�}|jrr|rrt||j�}|�
||jd�}|jr�||j_|j�|WSty�}z:|j|_|jr�|j�|j�dddt|�fWYd}~Sd}~0t�yN}zJ|j�s
||_|j|_|j�r(|j�|j�d	d
dt|�fWYd}~Sd}~00dS)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�
� r?i�zSPF Temporary Error: r>�&zSPF Permanent Error: )rZr��	mechanismr��optionsr��dns_spfr}rlr�r�r��insert_libspf_local_policy�check1r[rrY�probr/rrh)r]r�rcr$rrrro�s@F
�&zquery.checkc
Cs�|tkr|jrtd��td��z2z$|j|}|_|�||�W||_WS||_0WnNty�}z6|j|_|j	r||j	�
|j	�ddd|fWYd}~Sd}~00dS)NzToo many levels of recursionrCrzSPF Ambiguity Warning: %s)�
MAX_RECURSIONr#�AssertionErrorrhr}�check0rrYr�rZr/)r]r�domain�	recursion�tmpr$rrrr�os �zquery.check1c
GsR|jrt|��|jsLzt|��Wn*tyJ}z||_WYd}~n
d}~00|jSrU)r#rhr�)r]rYr$rrr�
note_error�szquery.note_errorcCs"t�|�drtd|��|�|�S)zvalidate and expand domain-specr�zInvalid domain found (use FQDN))r�r�rh�expand)r]�argrrr�
expand_domain�s
zquery.expand_domaincCs�|�d�r"|�d|�|dd�}t||j�\}}}}|r^t�|d�}|rZ|dd�}nd}|tvrz|�d|�t|}|d	kr�t�|�r�|�d
|�}d}|dvr�|dur�d
}n|d
kr�t	d|��|dur�d}n|dkr�t	d|��|j
dkr�|}�n|dk�st�|��r||dk�r,|�d|�d|}}|du�r@t	d|��|du�rPd
}n|d
k�rdt	d|��t�|��st	d|��n�|dk�r�|du�r�t	d|��|du�r�d}n|dk�r�t	d|��t�|��st	d|��n.|du�s�|du�r�|tv�r�t	d|��|j
}|dv�r�|dk�r(|�s(t	d|��|�|�}|�sBt	d|��|dk�rv||jk�rv|dk�rlt	d|��t	d|��|||||fS|dk�r�|�d��r�|�d |�|tv�r�|||||fS|dd�tv�r�|�d!|�}n|�d|�}|||||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�rrr:zUnknown mechanism foundrz'Use the ip4 mechanism for ip4 addressesrQ)rrMr�zInvalid IP4 CIDR lengthr�zInvalid IP6 CIDR lengthrRzMissing IP4zDual CIDR not allowedzInvalid IP4 addresszInvalid IP6 addresszCIDR not allowed)rrMrNrOrPrOzimplicit exists not allowedz
empty domain:rPzinclude has trivial recursionz include mechanism missing domainrSr�z>Invalid all mechanism format - only qualifier allowed with allz0Unknown qualifier, RFC 4408 para 4.6.1, found in)r�r��parse_mechanismr}�RESULTS�get�COMMON_MISTAKES�RE_IP4�matchrhr��RE_IP6�ALL_MECHANISMSr�r��count)r]rZ�mr��
cidrlengthZcidr6length�resultr$rrr�validate_mechanism�s�F
�

























�
�zquery.validate_mechanismc	Cs�|sddtdfS|�d�}|d��dkrP|jdkrBtd|j��ddtdfSdd	�|dd
�D�}|j}d
}d}g}g}|D�]�}t�|�dd
�}	t|	�dkr�|�	|�
|��q�|	\}
}|
|vr�|
d
kr�td|��|�d|
|�|�	|
�|
dk�rP|�std|��|�
|�}|�rz$|�|�}|�r>|�s>|�|�WnYn0q�|
d
k�r~|��|�
|�}|�std|��q�|
dk�r�|jdk�r�td��|j�s|j�r|�|�}t�||�}q�|
dk�r�|�s|�d�D]}
|
�r�d|j|
<�q�q�|�|	d�q�|D�](\}}	}}}|	dk�r�|��|�|�}|j�rN|�d||�|�|||d�\}}}|dk�rv�q�|dk�r�|�d||�d}�q�q|	dk�r��q��q|	dk�r�|��z"t|�|d��dk�r�W�q�Wnt�y�Yn0�q|	dk�r2|��|�|�||j�|��r4�q��q|	dk�r`|��|�|�|�|��r4�q�n�|	d k�r�|jd!k�r4z|�|g|��r�W�q�Wn tj �y�td"|��Yn0n~|	d#k�r|jd#k�r4z|�|g|��r�W�q�Wn tj �ytd"|��Yn0n(|	d$k�r|��t!|�"�|��r�q��q|�r�|�|�}|�sXtd%|��|j�rn|�d
||�|�s�t#|j$�|_i|_|�|||�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']
        r@r�r�rzv=spf1rzInvalid SPF record incSsg|]}|r|�qSrr)rrZrrrrW�z query.check0.<locals>.<listcomp>Nr<rL�redirectz"redirect= MUST appear at most oncez%s= MUST appear at most oncerrzexp has empty domain-spec:zredirect has empty domain:�defaultz"The default= modifier is obsolete.�opr7TrPr:z+No valid SPF record for included domain: %srSrOr'rrMrQr�zsyntax errorrRrNz!redirect domain has no SPF recordr;r�)&r�r�r�r#rr}r��RE_MODIFIER�lenr/r�rhr�r��get_explanationr��
check_lookupsr�r�r�r�r�r�rlr�r��dns_a�	cidrmatchr'�dns_mxr��socketrH�domainmatchr�r�r�r�)r]rr�r�r�r�Zmechs�	modifiersrZr��modr�rrr�r�r�r}rprtZtxtZredirect_recordrrrr�=s�

















�











�
zquery.check0cCsB|jd|_|jtdkr*tdtd��|jtkr>|�d�dS)Nrr5zMore than %d DNS lookupszToo many DNS lookups)r��
MAX_LOOKUPrhr�rarrrr��s

zquery.check_lookupscCsv|r`z:|j|dd�}t|�dkr<t|jt|d�dd��WSWqrty\|jdkrX�Yqr0n|jdkrrtd��dS)	zExpand an explanation.T��ignore_voidrrF)�stripdotzEmpty domain-spec on exp=N)�dns_txtr�rr��to_asciirhr#)r]�specrrrrr��s 

zquery.get_explanationcCs�|�d�dkr6t}|�d�D]}|�|�rtd|��qd}d}t�|�D�]}||||���7}||��|���}|dkr�|d7}n�|dkr�|d7}n�|d	kr�|d
7}n�|d�	�}	|	dkr�|�
�n|	d
vr�|r�td|��t||	|�}
|
�rB|
|k�rtd|��t|
|dd�t
�|	��}|	|dk�r:t�|d�}||7}|��}qH|||d�7}|�rz|�d��rz|dd�}|�d�dk�r�t|�dk�r�||�d�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'

        �%rr7zinvalid-macro-char rJz%%z%_r�z%-z%20rLr~Zcrtz&c,r,t macros allowed in exp= text onlyzUnknown Macro Encounteredrr�rGN�r)�find�RE_INVALID_MACROr��searchrh�RE_CHAR�finditer�start�endr�r��getattr�
expand_one�JOINERSr��urllibparse�quoter�r�r��index)r]r9r�Zregex�labelr�r�riZmacroZletter�	expansionr�rrrr�sNW




�


zquery.expandc
Cs�|�d�D]}|rt|�dkr
dSq
dd�|�|�D�}t|�dkr`|jrXtd|j�td��t|�dkr�|jd	kr�t|d
�S|jdk�rJzdd�|j|dd
d�D�}Wn:t	y�}z"|jdkr�t	|��g}WYd}~n
d}~00t|�dkr�td��t|�dk�rJ|jdk�r>t|�dk�r>|d
|d
k�r>t
d��t|d
�St|�dk�rdt|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.
        r7�?NcSsg|]}t�|�r|�qSr��RE_SPFr��rr|rrrr�r�z!query.dns_spf.<locals>.<listcomp>rzcache=z'Two or more type TXT spf records found.rLrcSsg|]}t�|�r|�qSrr�r�rrrr�s
�r,Tr�z'Two or more type SPF spf records found.zLv=spf1 records of both type TXT and SPF (type 99) present, but not identicalcSsg|]}t�|�r|�qSrr�r�rrrr�s
�z._spf.)r�r�r�rlr�r�rhr#r�rr�DELEGATE)r]r�r�r�br$rrrr��s>,��z
query.dns_spfr+cCsv|rrzJ|j|||d�}|rLdd�|D�}t|dt�r<|WSdd�|D�WSWn"typtd||f��Yn0gS)z,Get a list of TXT records for a domain name.r�cSs&g|]}|r|ddd��|��qS)rN)r�rrrrr�r�z!query.dns_txt.<locals>.<listcomp>rcSsg|]}|�d��qS)zutf-8)�encode�rr9rrrr�r�z.Non-ascii characters found in %s record for %s)r-�
isinstance�bytes�UnicodeErrorrh)r]�
domainnameZrrr�Zdns_listrrrrr��s�z
query.dns_txtcsz��|d�}�jrPt}t|�tkr.tdt���jdkrXt|�dkrXtd|��ntd}|���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 returnedrrz$No MX records found for mx mechanismr5cs(g|] }��|d�j�D]}|�qqS)r)r�r')rrMrrarrr�r�z query.dns_mx.<locals>.<listcomp>N)r-r#�MAX_MXr�rhr�sort)r]rZmxnames�maxrrarr��s�
�zquery.dns_mxr'cCsZ|sgS|�||�}|jdkr8t|�dkr8td||��|dkrVtturVdd�|D�S|S)z5Get a list of IP addresses for a domainname.
        rrzNo %s records found forr(cSsg|]}t|��qSrr�rrTrrrr�r�zquery.dns_a.<locals>.<listcomp>)r-r#r�rr�r)r]rr'rrrrr��s�zquery.dns_acs��jrzt}�jdkr�zJ���j�}t|�|krDd|}t|�j��nt|�dkr\td�j��Wq�td�j��Yq�0ntd}�j���fdd����j�d|�D�S)	z=Figure out the validated PTR domain names for the connect IP.rz!More than %d PTR records returnedrz&No PTR records found for ptr mechanismr5cs&g|]}����|�j���r|�qSr)r�r�r')rr~�r�r]rrrs�z(query.validated_ptrs.<locals>.<listcomp>N)r#�MAX_PTR�dns_ptrrir�rr�r�)r]rZptrnamesZwarningrrrr��s&
��zquery.validated_ptrscCs|�dt|�|jfd�S)z-Get a list of domain names for an IP address.z
%s.%s.arpar*)r-�reverse_dotsr�)r]rirrrr	sz
query.dns_ptr))r)r')r)r))�CNAMEr')r'r')r(r()r*r*)r+r+)r,r,cCs�|std��t|�}|�d�r*|dd�}tdd�|�d�d�sDgS|��}|j�||fg�}|rf|S|df}|j�|�}|jo�|�	d	�}|r�|d
}�n&t
j}	|jd
kr�t
d��|j|jkr�|jd
kr�|j}
n|j}
t��}t|||j|
�D]�\}}
|�rtd||
�|d
��|d
f}||k�rH|
}|j�||fg�}|�rH�q�|d
dk�sf||d
f|	vr�|�rxtd||
�|j�|g��|
�q�|j�||fg�}|jd
k�r�|jt��||_|�sJ|�rJ|�s�i}nt|�tk�r�tdt��|||<|���d�|v�r&|jd
k�rJtd|��n$|j|||d�}|�rJ||j||f<|�s||�s||jd
7_|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 queryr7Nr�cSs |odt|�kodkSS)Nr�@)r�)r$�yrrr�<lambda>:r�zquery.dns.<locals>.<lambda>Tr
zcname.rz)DNS Error: exceeded max query lookup timezresult=rz	addcache=z Length of CNAME chain exceeds %dz
CNAME loop)�cnamesz Void lookup limit of %d exceeded)rWrr�rr�r�r�r�rl�
startswithr.�
SAFE2CACHErmrr
r{�	DNSLookupr#r��
setdefaultr/r��	MAX_CNAMErh�rstriprr-r��MAX_VOID_LOOKUPS)r]rr	rr�r�ZcnamekZcname�debugZ
safe2cacher
Ztimethen�kr�rrrr-"sd

z	query.dnsc
Cs(z�zndd�|D�D]Z}|j|d�}t|jt�rF|�|j�rlWWdSq||jkr^|j�|�q|j�|j�qWn|t	y�dd�|D�D]Z}|j|d�}t|jt�r�|�|j�r�YWdSq�||jkr�|j�|�q�|j�|j�q�Yn0Wn2t
�y"}ztt|���WYd}~n
d}~00dS)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|]}t�|��qSr)r�Z
ip_networkrrrrr�r�z#query.cidrmatch.<locals>.<listcomp>)Z
new_prefixTcSsg|]}tj|dd��qS)F)r#)r�Z	IPNetworkrrrrr�r�NF)
Zsupernetr�r��bool�__contains__r�r�r/rTr"r�rhr)r]Zipaddrs�nZnetwrkZnetworkr$rrrr�ss,

"zquery.cidrmatchcCs�ddl}d�dd�|�d�D��}|j�|�}|jD]h}|jdkr4|j|_|j	|_	|j
djdkr~|j
dj|_
|j
dj|_|j
djd	kr4|j
dj|_q4dS)
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|]}|��VqdSrU)�stripr�rrr�	<genexpr>�r�z(query.parse_header_ar.<locals>.<genexpr>r�rrvru)�authresr�r��AuthenticationResultsHeader�parse�results�method�authserv_idr�r�Z
propertiesr�valuer}r9rj)r]�valrZarobjZresobjrrr�parse_header_ar�s

zquery.parse_header_arcCsT|�dd�}|d��|_d|_t|�dkr0dS|d}|�d�rx|�d�}|dkrZ|jS|d|�|_||dd�}t�}|�	dd	|�i}|j
dd
�D]�\}}|dkr�|�|�q�|dkr�||_q�|d
kr�||_
q�|dkr�||_q�|dkr�||_q�|dk�r||_q�|dk�r||_q�|�d�r�|||dd�<q�t|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'
        NrrrLr@�(�)zReceived-SPFz; )rz	client-ipz
envelope-fromrurk�problemr��identityzx-)r�r�r�r�r�rr��commentrZ
add_headerZ
get_paramsr�r9rjrrZrwrxr8ry)r]r$r�posrYr~rr�rrr�parse_header_spf�s2

zquery.parse_header_spfcCs"|�d�r|�|�S|�|�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)rr%r,)r]r$rrr�parse_header�s 

zquery.parse_headerrcKs�|dkr|std��ddl}|s&|j}|j}t|j�}ddddd	d
dd�}	|j}
|
d
kr^d}n
t|j�}|	|}|dkr�|jr�td�	|j��}
nd}
t|j
�}t|d�r�|j}nd||�
|�f}d||fg}|dk�rhdD],}t�|}|r�|�d|�dd�|f�q�tt|����D].\}}|�r|�d|�dd�t|�f��q|�dd|
f�d�	|�S|dk�r�|�r�t|j||j|||jd�|j|j|j|j|�d�gd��St|j||j|||jd�|j|j|j|�d�gd��Sntd �|���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
        rzKauthserv-id missing for Authentication Results header type, see RFC5451 2.3rNZPassZNeutralZFailZSoftFail�Nonerrh)r:r<r;r=r@r?r>rur>r�r*r_z%s (%s)r)�	client_ip�
envelope_fromrurkr(r�z%s=%s;rqrEzx-%s=%s;z%s=%sr)z@sender={0}; helo={1}; client-ip={2}; receiver={3}; mechanism={4})r��result_commentZ
smtp_mailfromZsmtp_mailfrom_comment)r"r z6sender={0}; client-ip={1}; receiver={2}; mechanism={3})r�r1Z	smtp_heloZsmtp_helo_commentz Unknown results header type: {0})�SyntaxErrorrrr��quote_valuerjrwr9rZr�r��hasattrr*�get_header_comment�localsr/r��sortedr��itemsrrZSPFAuthenticationResultr}�format)r]rprkZheader_typeZaidZkvrr/ruZresmapr)r0�tagr(r�r*rr�rrr�
get_headersh1
�




 (

���
���
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.  r:z.domain of %s designates %s as permitted senderr=zDtransitioning domain of %s does not designate %s as permitted senderr<z2%s is neither permitted nor denied by domain of %sr@r>z.permanent error in processing domain of %s: %sr?z1temporary error in processing during lookup of %sr;z6domain of %s does not designate %s as permitted senderz'invalid SPF result for header comment: N)ryr�r�r�)r]rpZsenderrrrr5�sF�������������zquery.get_header_comment)N)T)r+F)r')NF)NrN)$rdrerfrg�MAX_PER_LOOKUP_TIMErXr�r�r�r�r��DEFAULT_SPFr�ror�r�r�r�r�r�r�r�r�r�r�r�r�rrr-r�r%r,r-r;r5rrrrr.^sX�
1(
p",
9


	�
Q7!/%
ir.cCsL|sd|fS|�dd�}|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�@rrrJrLN)r�r��tuple)r9rj�partsrrrrx�srxcCs:|dust�|�r|Sd|�dd��dd��dd�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��r9rrrr3�s��r3cCs�t�|�}t|�dkr.|dt|d�}}nd}t�|�}t|�dkr`|dt|d�}}nd}|�dd�}t|�dkr�|��}|dkr�d}||||fS|d��|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)
    rrrNr�rLrO)�RE_DUAL_CIDRr�r�rz�RE_CIDRr�)rr}rZcidr6Zcidrrrrr��s"

r�cCs|�d�}|��d�|�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'
    r7)r��reverser�)rrrrrr	s

r	cCs:|��}|D](}|��}||ks.|�d|�rdSqdS)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

    r7TF)r�r�)ZptrsZdomainsuffixrNrrrr�sr�cCsh|s|St�|�dd�\}}}|s(d}t|||�}|r@|��|r^|t|�ddd�}d�|�S)Nrr5r7rLrJ)�RE_ARGSr�rHrzr�)r�r�joinerZlnrH�
delimitersrrrr�6sr�cCs\gd}}|D]>}||vrD|�|�d}|r8|�|�qL|�|�q||7}q|�|�|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']
    rJ)r/)rrKrJr��elementr�rrrr�As



r�cCsv|s|S|��dd�}|rn|��|D]@}t�|d�s(|�|�}|g|||�<|��d�|�}qnq(|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'
    rNrr�zv=spf1 )r�rHr�r�r�r�)ZspftxtrArrZ�whererrrr�^s

r�cCs2z|�d�WSty,tdt|���Yn0dS�z*Raise PermError if arg is not 7-bit ascii.�asciizNon-ascii characters foundN)r�rrh�reprrErrrr��sr�cCs2z|�d�WSty,tdt|���Yn0dSrN)�decoderrhrPrErrrr��scCsddl}ddl}|�|�S)Nr)�doctestrZtestmod)rRrrrr�_test�srS�__main__rzhvs:)�helprlr#)z-vz	--verbose)z-sz--strict)z-hz--helpz	127.0.0.1Z	localhostrI)rir9rjrkzTemporary DNS error: zPermError: r)rir9rjrkrlr#zresult:zguessed:zlax:)rir9rjrkr#rl)Tr)Tr&)NNF)N)N){Z
__future__r�
__author__Z	__email__�__version__ZMODULEZUSAGE�re�sysr��structr{�urllib.parserr�Zurllib�	functoolsrZ
email.messager�ImportErrorZ
email.Messager�r�rrr�r�r%r3r-Zdns.resolverZ
dns.exceptionr4Z	rdatatyper,Z_by_textrr�TypeZtypemapZLibZ
RRunpackerZ
getTXTdataZ
getSPFdataZDiscoverNameServers�compile�
IGNORECASEr�r�ZPAT_CHARr�r�rIrFrGr�ZPAT_IP4r�r�rDr�r�r�r�r�r=ZTRUSTED_FORWARDERSr�rrrr�r<rr�r�rWrrrhrsro�objectr.rxr3r�r	r�r�r�r��version_infor�rSrd�getopt�argvZopts�GetoptError�err�exitrlr#ryrrzr�Zgethostname�qr�r$rir9rjrr�r�r�r[r�rTrrrr�<module>s�+
!






����
���
��
�
O!4

3

� 




�$
�
�