# coding: utf-8 # Copyright © 2011-2012 Julian Mehnle , # Copyright © 2011-2013 Scott Kitterman # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Package for parsing ``Authentication-Results`` headers as defined in RFC 5451. Optional support for authentication methods defined in RFCs 5617, 6008, 6212, 7489 and draft-ietf-dmarc-arc-protocol-05. >>> import authres >>> str(authres.AuthenticationResultsHeader('test.example.org', version=1)) 'Authentication-Results: test.example.org 1; none' Non-RFC example of no authentication with comment: >>> import authres >>> str(authres.AuthenticationResultsHeader(authserv_id = 'test.example.org', ... results = [authres.NoneAuthenticationResult(comment = 'SPF not checked for localhost')])) 'Authentication-Results: test.example.org; none (SPF not checked for localhost)' >>> import authres >>> arobj = authres.AuthenticationResultsHeader.parse('Authentication-Results: example.com; spf=pass smtp.mailfrom=example.net') >>> str(arobj.authserv_id) 'example.com' >>> str(arobj.results[0]) 'spf=pass smtp.mailfrom=example.net' >>> str(arobj.results[0].method) 'spf' >>> str(arobj.results[0].result) 'pass' >>> str(arobj.results[0].smtp_mailfrom) 'example.net' >>> str(arobj.results[0].smtp_helo) 'None' >>> str(arobj.results[0].reason) 'None' >>> str(arobj.results[0].properties[0].type) 'smtp' >>> str(arobj.results[0].properties[0].name) 'mailfrom' >>> str(arobj.results[0].properties[0].value) 'example.net' # Missing parsing header comment. #FIXME >>> import authres >>> arobj = authres.AuthenticationResultsHeader.parse('Authentication-Results: example.com; auth=pass (cram-md5) smtp.auth=sender@example.net; spf=pass smtp.mailfrom=example.net') >>> str(arobj.authserv_id) 'example.com' >>> str(arobj.results[0]) 'auth=pass smtp.auth=sender@example.net' >>> str(arobj.results[0].method) 'auth' >>> str(arobj.results[0].result) 'pass' >>> str(arobj.results[0].smtp_auth) 'sender@example.net' >>> str(arobj.results[0].properties[0].type) 'smtp' >>> str(arobj.results[0].properties[0].name) 'auth' >>> str(arobj.results[0].properties[0].value) 'sender@example.net' >>> str(arobj.results[1]) 'spf=pass smtp.mailfrom=example.net' >>> str(arobj.results[1].method) 'spf' >>> str(arobj.results[1].result) 'pass' >>> str(arobj.results[1].smtp_mailfrom) 'example.net' >>> str(arobj.results[1].properties[0].type) 'smtp' >>> str(arobj.results[1].properties[0].name) 'mailfrom' >>> str(arobj.results[1].properties[0].value) 'example.net' >>> import authres >>> arobj = authres.AuthenticationResultsHeader.parse('Authentication-Results: example.com; sender-id=pass header.from=example.com') >>> str(arobj.authserv_id) 'example.com' >>> str(arobj.results[0]) 'sender-id=pass header.from=example.com' >>> str(arobj.results[0].method) 'sender-id' >>> str(arobj.results[0].result) 'pass' >>> str(arobj.results[0].header_from) 'example.com' >>> try: ... str(arobj.results[0].smtp_mailfrom) ... except AttributeError as x: ... print(x) 'SenderIDAuthenticationResult' object has no attribute 'smtp_mailfrom' >>> str(arobj.results[0].properties[0].type) 'header' >>> str(arobj.results[0].properties[0].name) 'from' >>> str(arobj.results[0].properties[0].value) 'example.com' # Missing parsing header comment. #FIXME >>> import authres >>> arobj = authres.AuthenticationResultsHeader.parse('Authentication-Results: example.com; sender-id=fail header.from=example.com; dkim=pass (good signature) header.i=sender@example.com') >>> str(arobj.authserv_id) 'example.com' >>> str(arobj.results[0]) 'sender-id=fail header.from=example.com' >>> str(arobj.results[0].method) 'sender-id' >>> str(arobj.results[0].result) 'fail' >>> str(arobj.results[0].header_from) 'example.com' >>> str(arobj.results[0].properties[0].type) 'header' >>> str(arobj.results[0].properties[0].name) 'from' >>> str(arobj.results[0].properties[0].value) 'example.com' >>> str(arobj.results[1]) 'dkim=pass header.i=sender@example.com' >>> str(arobj.results[1].method) 'dkim' >>> str(arobj.results[1].result) 'pass' >>> str(arobj.results[1].header_i) 'sender@example.com' >>> str(arobj.results[1].properties[0].type) 'header' >>> str(arobj.results[1].properties[0].name) 'i' >>> str(arobj.results[1].properties[0].value) 'sender@example.com' RFC 5451 B.6(1) modified to use d= instead of i= >>> import authres >>> dar_pass = authres.DKIMAuthenticationResult(result = 'pass', result_comment = 'good signature', ... header_d = 'mail-router.example.net') >>> dar_fail = authres.DKIMAuthenticationResult(result = 'fail', ... header_d = 'newyork.example.com', result_comment = 'bad signature') >>> str(authres.AuthenticationResultsHeader(authserv_id = 'example.com', ... results = [dar_pass, dar_fail])) 'Authentication-Results: example.com; dkim=pass (good signature) header.d=mail-router.example.net; dkim=fail (bad signature) header.d=newyork.example.com' >>> dar_pass.match_signature('mail-router.example.net') True >>> dar_fail.match_signature('mail-router.example.net') False RFC 5451 B.6(1) modified to use d= instead of i= with header.a and header.s added >>> import authres >>> dsr_pass = authres.DKIMAuthenticationResult(result = 'pass', result_comment = 'good signature', ... header_d = 'mail-router.example.net', header_a = 'rsa-sha256', header_s = 'default') >>> dsr_fail = authres.DKIMAuthenticationResult(result = 'fail', ... header_d = 'newyork.example.com', result_comment = 'bad signature') >>> str(authres.AuthenticationResultsHeader(authserv_id = 'example.com', ... results = [dsr_pass, dsr_fail])) 'Authentication-Results: example.com; dkim=pass (good signature) header.d=mail-router.example.net header.a=rsa-sha256 header.s=default; dkim=fail (bad signature) header.d=newyork.example.com' >>> dsr_pass.match_signature_algorithm('mail-router.example.net', 'rsa-sha256') True >>> dsr_fail.match_signature_algorithm('mail-router.example.net', 'rsa-sha256') False Header from dcrup testing >>> import authres >>> dss_pass = authres.DKIMAuthenticationResult(result = 'pass', result_comment = 'Good 256 bit ed25519-sha256 signature.', ... header_d = 'example.com', header_i = '@example.com', header_a = 'ed25519-sha256') >>> dss_fail = authres.DKIMAuthenticationResult(result = 'fail', result_comment = 'Bad 1024 bit rsa-sha256 signature.', ... header_d = 'example.com', header_i = '@example.com', header_a = 'rsa-sha256') >>> str(authres.AuthenticationResultsHeader(authserv_id = 'relay02.example.org', ... results = [dss_pass, dss_fail])) 'Authentication-Results: relay02.example.org; dkim=pass (Good 256 bit ed25519-sha256 signature.) header.d=example.com header.i=@example.com header.a=ed25519-sha256; dkim=fail (Bad 1024 bit rsa-sha256 signature.) header.d=example.com header.i=@example.com header.a=rsa-sha256' # Missing parsing header comment. #FIXME >>> import authres >>> arobj = authres.AuthenticationResultsHeader.parse('Authentication-Results: example.com; dkim=pass (good signature) header.i=@mail-router.example.net; dkim=fail (bad signature) header.i=@newyork.example.com') >>> str(arobj.results[0]) 'dkim=pass header.i=@mail-router.example.net' >>> str(arobj.results[0].method) 'dkim' >>> str(arobj.results[0].result) 'pass' >>> str(arobj.results[0].header_i) '@mail-router.example.net' >>> str(arobj.results[0].properties[0].type) 'header' >>> str(arobj.results[0].properties[0].name) 'i' >>> str(arobj.results[0].properties[0].value) '@mail-router.example.net' >>> str(arobj.results[1]) 'dkim=fail header.i=@newyork.example.com' >>> str(arobj.results[1].method) 'dkim' >>> str(arobj.results[1].result) 'fail' >>> str(arobj.results[1].header_i) '@newyork.example.com' >>> str(arobj.results[1].properties[0].type) 'header' >>> str(arobj.results[1].properties[0].name) 'i' >>> str(arobj.results[1].properties[0].value) '@newyork.example.com' RFC 5451bis C.6(1) modified to use d= instead of i= >>> import authres >>> dar_pass = authres.DKIMAuthenticationResult(result = 'pass', reason = 'good signature', ... header_d = 'mail-router.example.net') >>> dar_fail = authres.DKIMAuthenticationResult(result = 'fail', ... header_d = 'newyork.example.com', reason = 'bad signature') >>> str(authres.AuthenticationResultsHeader(authserv_id = 'example.com', ... results = [dar_pass, dar_fail])) 'Authentication-Results: example.com; dkim=pass reason="good signature" header.d=mail-router.example.net; dkim=fail reason="bad signature" header.d=newyork.example.com' >>> dar_pass.match_signature('mail-router.example.net') True >>> dar_fail.match_signature('mail-router.example.net') False # Missing parsing header comment. #FIXME >>> import authres >>> arobj = authres.AuthenticationResultsHeader.parse('Authentication-Results: example.com; dkim=pass reason="good signature" header.i=@mail-router.example.net; dkim=fail reason="bad signature" header.i=@newyork.example.com') >>> str(arobj.results[0]) 'dkim=pass reason="good signature" header.i=@mail-router.example.net' >>> str(arobj.results[0].method) 'dkim' >>> str(arobj.results[0].result) 'pass' >>> str(arobj.results[0].reason) 'good signature' >>> str(arobj.results[0].header_i) '@mail-router.example.net' >>> str(arobj.results[0].properties[0].type) 'header' >>> str(arobj.results[0].properties[0].name) 'i' >>> str(arobj.results[0].properties[0].value) '@mail-router.example.net' >>> str(arobj.results[1]) 'dkim=fail reason="bad signature" header.i=@newyork.example.com' >>> str(arobj.results[1].method) 'dkim' >>> str(arobj.results[1].result) 'fail' >>> str(arobj.results[1].reason) 'bad signature' >>> str(arobj.results[1].header_i) '@newyork.example.com' >>> str(arobj.results[1].properties[0].type) 'header' >>> str(arobj.results[1].properties[0].name) 'i' >>> str(arobj.results[1].properties[0].value) '@newyork.example.com' RFC 5451 B.6(2) >>> import authres >>> str(authres.AuthenticationResultsHeader(authserv_id = 'example.net', ... results = [authres.DKIMAuthenticationResult(result = 'pass', result_comment = 'good signature', ... header_i = '@newyork.example.com')])) 'Authentication-Results: example.net; dkim=pass (good signature) header.i=@newyork.example.com' # Missing parsing header comment. #FIXME import authres >>> arobj = authres.AuthenticationResultsHeader.parse('Authentication-Results: example.net; dkim=pass (good signature) header.i=@newyork.example.com') >>> str(arobj.results[0]) 'dkim=pass header.i=@newyork.example.com' >>> str(arobj.results[0].method) 'dkim' >>> str(arobj.results[0].result) 'pass' >>> str(arobj.results[0].header_i) '@newyork.example.com' >>> str(arobj.results[0].properties[0].type) 'header' >>> str(arobj.results[0].properties[0].name) 'i' >>> str(arobj.results[0].properties[0].value) '@newyork.example.com' RFC 6008 A.1 >>> import authres >>> import authres.dkim_b >>> authres_context = authres.FeatureContext(authres.dkim_b) >>> dar_b = authres.dkim_b.DKIMAuthenticationResult(result = 'pass', result_comment = 'good signature', ... header_d = 'newyork.example.com', header_b = 'oINEO8hg') >>> str(authres_context.header(authserv_id = 'mail-router.example.net', ... results = [dar_b, authres.dkim_b.DKIMAuthenticationResult(result = 'fail', ... header_d = 'newyork.example.com', result_comment = 'bad signature', header_b = 'EToRSuvU')])) 'Authentication-Results: mail-router.example.net; dkim=pass (good signature) header.d=newyork.example.com header.b=oINEO8hg; dkim=fail (bad signature) header.d=newyork.example.com header.b=EToRSuvU' RFC 6008 section 4 >>> authres.dkim_b.DKIMAuthenticationResult.match_signature(dar_b,'newyork.example.com','oINEO8h') False >>> authres.dkim_b.DKIMAuthenticationResult.match_signature(dar_b,'newyork.example.com','oINEO8hg') True >>> authres.dkim_b.DKIMAuthenticationResult.match_signature(dar_b,'newyork.example.com','oINEO8hq') False >>> authres.dkim_b.DKIMAuthenticationResult.match_signature(dar_b,'newyork.example.com','oINEO8hq1') False >>> dar_b2 = authres.dkim_b.DKIMAuthenticationResult(result = 'pass', result_comment = 'good signature', ... header_d = 'newyork.example.com', header_b = 'oINEO8') >>> authres.dkim_b.DKIMAuthenticationResult.match_signature(dar_b2,'newyork.example.com','oINEO8') True >>> authres.dkim_b.DKIMAuthenticationResult.match_signature(dar_b2,'newyork.example.com','oINEO8hq') False >>> authres.dkim_b.DKIMAuthenticationResult.match_signature(dar_b2,'newyork.example.com', None) False >>> authres.dkim_b.DKIMAuthenticationResult.match_signature(dar_b2,'newyork.example.com', None) False >>> dar_b_none = authres.dkim_b.DKIMAuthenticationResult(result = 'pass', result_comment = 'good signature', ... header_d = 'newyork.example.com') >>> authres.dkim_b.DKIMAuthenticationResult.match_signature(dar_b_none,'newyork.example.com', None) True >>> authres.dkim_b.DKIMAuthenticationResult.match_signature(dar_b_none,'newyork.example.com', None, strict=True) False >>> authres.dkim_b.DKIMAuthenticationResult.match_signature(dar_b_none,'newyork.example.com','oINEO8hq') True >>> authres.dkim_b.DKIMAuthenticationResult.match_signature(dar_b_none,'newjersey.example.com', None) False # Missing parsing header comment. #FIXME >>> arobj = authres_context.parse('Authentication-Results: mail-router.example.net; dkim=pass (good signature) header.d=newyork.example.com header.b=oINEO8hg; dkim=fail (bad signature) header.d=newyork.example.com header.b=EToRSuvU') >>> str(arobj.results[0]) 'dkim=pass header.d=newyork.example.com header.b=oINEO8hg' >>> str(arobj.results[0].method) 'dkim' >>> str(arobj.results[0].result) 'pass' >>> str(arobj.results[0].header_d) 'newyork.example.com' >>> str(arobj.results[0].properties[0].type) 'header' >>> str(arobj.results[0].properties[0].name) 'd' >>> str(arobj.results[0].properties[0].value) 'newyork.example.com' >>> str(arobj.results[0].header_b) 'oINEO8hg' >>> str(arobj.results[0].properties[1].type) 'header' >>> str(arobj.results[0].properties[1].name) 'b' >>> str(arobj.results[0].properties[1].value) 'oINEO8hg' >>> str(arobj.results[1].method) 'dkim' >>> str(arobj.results[1].result) 'fail' >>> str(arobj.results[1].header_d) 'newyork.example.com' >>> str(arobj.results[1].properties[0].type) 'header' >>> str(arobj.results[1].properties[0].name) 'd' >>> str(arobj.results[1].properties[0].value) 'newyork.example.com' >>> str(arobj.results[1].header_b) 'EToRSuvU' >>> str(arobj.results[1].properties[1].type) 'header' >>> str(arobj.results[1].properties[1].name) 'b' >>> str(arobj.results[1].properties[1].value) 'EToRSuvU' # RFC 5617 (based on RFC text, no examples provided) >>> import authres >>> import authres.dkim_adsp >>> authres_context = authres.FeatureContext(authres.dkim_adsp) >>> str(authres_context.header(authserv_id = 'example.com', ... results = [authres.DKIMAuthenticationResult(result = 'fail', result_comment = 'bad signature', ... header_d = 'bank.example.net'), authres.dkim_adsp.DKIMADSPAuthenticationResult(result = 'discard', ... header_from = 'phish@bank.example.com', result_comment = 'From domain and d= domain match')])) 'Authentication-Results: example.com; dkim=fail (bad signature) header.d=bank.example.net; dkim-adsp=discard (From domain and d= domain match) header.from=phish@bank.example.com' # Missing parsing header comment. #FIXME >>> arobj = authres_context.parse('Authentication-Results: example.com; dkim=fail (bad signature) header.d=bank.example.net; dkim-adsp=discard (From domain and d= domain match) header.from=phish@bank.example.com') >>> str(arobj.results[1]) 'dkim-adsp=discard header.from=phish@bank.example.com' >>> str(arobj.results[1].method) 'dkim-adsp' >>> str(arobj.results[1].result) 'discard' >>> str(arobj.results[1].header_from) 'phish@bank.example.com' >>> str(arobj.results[1].properties[0].type) 'header' >>> str(arobj.results[1].properties[0].name) 'from' >>> str(arobj.results[1].properties[0].value) 'phish@bank.example.com' RFC 6212 A.1 >>> import authres >>> import authres.dkim_b, authres.vbr >>> authres_context = authres.FeatureContext(authres.dkim_b, authres.vbr) >>> str(authres_context.header(authserv_id = 'mail-router.example.net', ... results = [authres.dkim_b.DKIMAuthenticationResult(result = 'pass', result_comment = 'good signature', ... header_d = 'newyork.example.com', header_b = 'oINEO8hg'), authres.vbr.VBRAuthenticationResult(result = 'pass', ... header_md = 'newyork.example.com', result_comment = 'voucher.example.net', ... header_mv = 'voucher.example.org')])) 'Authentication-Results: mail-router.example.net; dkim=pass (good signature) header.d=newyork.example.com header.b=oINEO8hg; vbr=pass (voucher.example.net) header.md=newyork.example.com header.mv=voucher.example.org' # Missing parsing header comment. #FIXME >>> arobj = authres_context.parse('Authentication-Results: mail-router.example.net; dkim=pass (good signature) header.d=newyork.example.com header.b=oINEO8hg; vbr=pass (voucher.example.net) header.md=newyork.example.com header.mv=voucher.example.org') >>> str(arobj.results[1]) 'vbr=pass header.md=newyork.example.com header.mv=voucher.example.org' >>> str(arobj.results[1].method) 'vbr' >>> str(arobj.results[1].result) 'pass' >>> str(arobj.results[1].header_md) 'newyork.example.com' >>> str(arobj.results[1].properties[0].type) 'header' >>> str(arobj.results[1].properties[0].name) 'md' >>> str(arobj.results[1].properties[0].value) 'newyork.example.com' >>> str(arobj.results[1].header_mv) 'voucher.example.org' >>> str(arobj.results[1].properties[1].type) 'header' >>> str(arobj.results[1].properties[1].name) 'mv' >>> str(arobj.results[1].properties[1].value) 'voucher.example.org' # RFC 7489 DMARC example from opendmarc >>> import authres >>> import authres.dmarc >>> new_context = authres.FeatureContext(authres.dmarc) >>> str(new_context.header(authserv_id = 'mail-router.example.net', ... results = [authres.dmarc.DMARCAuthenticationResult(result = 'pass', ... header_from = 'example.com')])) 'Authentication-Results: mail-router.example.net; dmarc=pass header.from=example.com' # Missing parsing header comment. #FIXME >>> newarobj = new_context.parse('Authentication-Results: mail-router.example.net; dmarc=pass header.from=example.com') >>> str(newarobj.results[0]) 'dmarc=pass header.from=example.com' >>> str(newarobj.results[0].method) 'dmarc' >>> str(newarobj.results[0].result) 'pass' >>> str(newarobj.results[0].header_from) 'example.com' >>> str(newarobj.results[0].properties[0].type) 'header' >>> str(newarobj.results[0].properties[0].name) 'from' >>> str(newarobj.results[0].properties[0].value) 'example.com' # Non-RFC DMARC example with policy included >>> import authres >>> import authres.dmarc >>> new_context = authres.FeatureContext(authres.dmarc) >>> str(new_context.header(authserv_id='mail-router.example.net', ... results = [authres.dmarc.DMARCAuthenticationResult(result='pass', policy='none', ... header_from='example.com')])) 'Authentication-Results: mail-router.example.net; dmarc=pass header.from=example.com policy.dmarc=none' # Non-RFC - SPF with localpart in pvalue >>> import authres >>> arobj = authres.AuthenticationResultsHeader.parse('Authentication-Results: example.com; spf=pass smtp.mailfrom=authenticated@example.net') >>> str(arobj.authserv_id) 'example.com' >>> str(arobj.results[0]) 'spf=pass smtp.mailfrom=authenticated@example.net' >>> str(arobj.results[0].method) 'spf' >>> str(arobj.results[0].result) 'pass' >>> str(arobj.results[0].smtp_mailfrom) 'authenticated@example.net' >>> str(arobj.results[0].smtp_helo) 'None' >>> str(arobj.results[0].reason) 'None' >>> str(arobj.results[0].properties[0].type) 'smtp' >>> str(arobj.results[0].properties[0].name) 'mailfrom' >>> str(arobj.results[0].properties[0].value) 'authenticated@example.net' # None RFC - Separate reporting of SPF Mail From and HELO results >>> import authres >>> arobj = authres.AuthenticationResultsHeader.parse('Authentication-Results: example.com; spf=pass smtp.mailfrom=authenticated@example.net; spf=none smtp.helo=mailserver.example.net') >>> str(arobj.authserv_id) 'example.com' >>> str(arobj.results[0]) 'spf=pass smtp.mailfrom=authenticated@example.net' >>> str(arobj.results[0].method) 'spf' >>> str(arobj.results[0].result) 'pass' >>> str(arobj.results[0].smtp_mailfrom) 'authenticated@example.net' >>> str(arobj.results[0].smtp_helo) 'None' >>> str(arobj.results[0].reason) 'None' >>> str(arobj.results[0].properties[0].type) 'smtp' >>> str(arobj.results[0].properties[0].name) 'mailfrom' >>> str(arobj.results[0].properties[0].value) 'authenticated@example.net' >>> str(arobj.results[1]) 'spf=none smtp.helo=mailserver.example.net' >>> str(arobj.results[1].method) 'spf' >>> str(arobj.results[1].result) 'none' >>> str(arobj.results[1].smtp_mailfrom) 'None' >>> str(arobj.results[1].smtp_helo) 'mailserver.example.net' >>> str(arobj.results[1].reason) 'None' >>> str(arobj.results[1].properties[0].type) 'smtp' >>> str(arobj.results[1].properties[0].name) 'helo' >>> str(arobj.results[1].properties[0].value) 'mailserver.example.net' # Create header field with multiple SPF results >>> import authres >>> mfrom_pass = authres.SPFAuthenticationResult(result = 'pass', ... smtp_mailfrom = 'authenticated@example.net') >>> helo_none = authres.SPFAuthenticationResult(result = 'none', ... smtp_helo = 'mailserver.example.net', reason = 'No SPF record for HELO') >>> str(authres.AuthenticationResultsHeader(authserv_id = 'example.com', ... results = [mfrom_pass, helo_none])) 'Authentication-Results: example.com; spf=pass smtp.mailfrom=authenticated@example.net; spf=none reason="No SPF record for HELO" smtp.helo=mailserver.example.net' # Create header field with ARC results (draft-ietf-dmarc-arc-protocol-18) >>> import authres >>> import authres.arc >>> arc_pass = authres.arc.ARCAuthenticationResult(result = 'pass', ... header_ams_d = 'example.net', header_ams_s='valimail2016', header_as_d="example.com", header_as_s="valimail2017", header_oldest_pass='1', smtp_remote_ip='203.0.113.1') >>> str(authres.AuthenticationResultsHeader(authserv_id = 'example.com', ... results = [arc_pass])) 'Authentication-Results: example.com; arc=pass header.ams-d=example.net header.ams-s=valimail2016 header.as-d=example.com header.as-s=valimail2017 header.oldest-pass=1 smtp.remote-ip=203.0.113.1' # Parsing IP6 address. >>> arobj = authres_context.parse('Authentication-Results: mail.bmsi.com; iprev=pass policy.iprev="2001:748:100:40::2:2" (mout0.freenet.de); spf=none smtp.mailfrom=markuslaudi@freenet.de') >>> str(arobj.results[0]) 'iprev=pass policy.iprev="2001:748:100:40::2:2"' >>> arobj.results[0].policy_iprev '2001:748:100:40::2:2' # Parsing iprev with unquoted IP6 address should fail. >>> try: ... arobj = authres.AuthenticationResultsHeader.parse('Authentication-Results: mail.bmsi.com; iprev=pass policy.iprev=2001:748:100:40::2:2 (mout0.freenet.de); spf=none smtp.mailfrom=markuslaudi@freenet.de') ... except authres.SyntaxError as x: ... print(x) Syntax error: Expected end of text at: :748:100:40::2:2 (mout0.freenet.de); spf... # Generating iprev with IP6 address. >>> iprev_pass = authres.IPRevAuthenticationResult(result = 'pass', policy_iprev = '2001:db8:ea1::dead:beef', policy_iprev_comment='yummy.example.net') >>> str(authres.AuthenticationResultsHeader(authserv_id='example.com',results = [iprev_pass])) 'Authentication-Results: example.com; iprev=pass policy.iprev="2001:db8:ea1::dead:beef" (yummy.example.net)' # RFC 7281, Authentication-Results Registration for S/MIME Signature Verification >>> import authres.smime >>> mimearobj = authres_context.parse('Authentication-Results: example.net; smime=fail (certificate is revoked by CRL) body.smime-identifier=aliceDss@example.com body.smime-part=2') >>> str(mimearobj.results[0]) 'smime=fail body.smime-identifier=aliceDss@example.com body.smime-part=2' >>> str(mimearobj.authserv_id) 'example.net' >>> str(mimearobj.results[0].method) 'smime' >>> str(mimearobj.results[0].result) 'fail' >>> str(mimearobj.results[0].reason) 'None' >>> str(mimearobj.results[0].properties[0].type) 'body' >>> str(mimearobj.results[0].properties[0].name) 'smime-identifier' >>> str(mimearobj.results[0].properties[0].value) 'aliceDss@example.com' >>> str(mimearobj.results[0].properties[1].type) 'body' >>> str(mimearobj.results[0].properties[1].name) 'smime-part' >>> str(mimearobj.results[0].properties[1].value) '2' >>> import authres.smime >>> smime_fail = authres.smime.SMIMEAuthenticationResult(result = 'fail', result_comment = 'certificate is revoked by CRL', body_smime_identifier = 'aliceDss@example.com', body_smime_part = '2') >>> str(authres.AuthenticationResultsHeader(authserv_id='example.net',results = [smime_fail])) 'Authentication-Results: example.net; smime=fail (certificate is revoked by CRL) body.smime-identifier=aliceDss@example.com body.smime-part=2' """ # RFC 7293, The Require-Recipient-Valid-Since Header Field and SMTP Service Extension, header field types # Example 12.3 from RFC 7293 >>> import authres.rrvs >>> rrvsarobj = authres_context.parse('Authentication-Results: mx.example.com; rrvs=pass smtp.rcptto=user@example.com') >>> str(rrvsarobj.authserv_id) 'mx.example.com' >>> str(rrvsarobj.results[0].method) 'rrvs' >>> str(rrvsarobj.results[0].result) 'pass' >>> str(rrvsarobj.results[0].reason) 'None' >>> str(rrvsarobj.results[0].properties[0].type) 'smtp' >>> str(rrvsarobj.results[0].properties[0].name) 'rcptto' >>> str(rrvsarobj.results[0].properties[0].value) 'user@example.com' >>> import authres.rrvs >>> rrvs_fail = authres.rrvs.RRVSAuthenticationResult(result = 'fail', result_comment = 'Mail box expired.', smtp_rrvs = 'user@example.com', smtp_rrvs_comment = "These are not the droids you're looking for.") >>> str(authres.AuthenticationResultsHeader(authserv_id='example.net',results = [rrvs_fail])) "Authentication-Results: example.net; rrvs=fail (Mail box expired.) smtp.rrvs=user@example.com (These are not the droids you're looking for.)" # New for RFC 7601 SMTP Auth Mail From >>> import authres >>> arobj = authres.AuthenticationResultsHeader.parse('Authentication-Results: example.com; auth=pass smtp.mailfrom=sender@example.net') >>> str(arobj.authserv_id) 'example.com' >>> str(arobj.results[0]) 'auth=pass smtp.mailfrom=sender@example.net' >>> str(arobj.results[0].method) 'auth' >>> str(arobj.results[0].result) 'pass' >>> str(arobj.results[0].smtp_mailfrom) 'sender@example.net' >>> str(arobj.results[0].properties[0].type) 'smtp' >>> str(arobj.results[0].properties[0].name) 'mailfrom' >>> str(arobj.results[0].properties[0].value) 'sender@example.net' # Create header field with RFC 7601 SMTP Auth Mail From >>> import authres >>> mfrom_auth = authres.SMTPAUTHAuthenticationResult(result = 'pass', ... smtp_mailfrom = 'mailauth@example.net') >>> str(authres.AuthenticationResultsHeader(authserv_id = 'example.com', ... results = [mfrom_auth])) 'Authentication-Results: example.com; auth=pass smtp.mailfrom=mailauth@example.net' # Ignore unknown method (RFC 7601 2.7.6 SHOULD) >>> import authres >>> arobj = authres.AuthenticationResultsHeader.parse('Authentication-Results: mail.example.org; tls=pass smtp.TLSversion=TLSv1.2 smtp.TLScyper=ECDHE-RSA-CHACHA20-POLY1305 smtp.TLSbits=256') >>> str(arobj.authserv_id) 'mail.example.org' >>> str(arobj.results[0]) 'tls=pass smtp.tlsversion=TLSv1.2 smtp.tlscyper=ECDHE-RSA-CHACHA20-POLY1305 smtp.tlsbits=256' >>> str(arobj.results[0].method) 'tls' >>> str(arobj.results[0].result) 'pass' >>> try: str(arobj.results[0].smtp_mailfrom) ... except AttributeError as e: print(e) 'AuthenticationResult' object has no attribute 'smtp_mailfrom' >>> str(arobj.results[0].properties[0].type) 'smtp' >>> str(arobj.results[0].properties[0].name) 'tlsversion' >>> str(arobj.results[0].properties[0].value) 'TLSv1.2' # Invalid ptype (error) and unknown method (OK) >>> import authres >>> try: arobj = authres.AuthenticationResultsHeader.parse('Authentication-Results: mail.example.org; x-tls=pass version=TLSv1.2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256') ... except authres.SyntaxError as e: print(e) >>> str(arobj.authserv_id) 'mail.example.org' >>> str(arobj.results[0]) 'x-tls=pass' # Valid ptype (OK), unknown method (OK), unknown property (OKish) >>> import authres >>> try: arobj = authres.AuthenticationResultsHeader.parse('Authentication-Results: mail.example.org; x-tls=pass smtp.tlsversion=TLSv1.2') ... except authres.SyntaxError as e: print(e) >>> str(arobj.authserv_id) 'mail.example.org' >>> str(arobj.results[0]) 'x-tls=pass smtp.tlsversion=TLSv1.2' >>> str(arobj.results[0].method) 'x-tls' >>> str(arobj.results[0].result) 'pass' >>> try: str(arobj.results[0].smtp_mailfrom) ... except AttributeError as e: print(e) 'AuthenticationResult' object has no attribute 'smtp_mailfrom' >>> try: str(arobj.results[0].smtp_tlsversion) ... except AttributeError as e: print(e) 'AuthenticationResult' object has no attribute 'smtp_tlsversion' >>> str(arobj.results[0].properties[0].type) 'smtp' >>> str(arobj.results[0].properties[0].name) 'tlsversion' >>> str(arobj.results[0].properties[0].value) 'TLSv1.2' # Parse multiple result header field and toss out unknown ptype >>> import authres >>> arobj = authres.AuthenticationResultsHeader.parse('Authentication-Results: mail.example.org; arc=none (no signatures found); dkim=pass (1024-bit rsa key sha256) header.d=example.net header.i=@example.net header.b=Qgi/FoC0; dmarc=none (p=none) header.from=example.net; spf=none smtp.mailfrom=test@example.net smtp.helo=mail-wm0-x232.example.com; x-tls=pass version=TLSv1.2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256') >>> for result in arobj.results: ... result.method ... result.result 'arc' 'none' 'dkim' 'pass' 'dmarc' 'none' 'spf' 'none' 'x-tls' 'pass' # Repeat authserv_id for each result (invalid) >>> import authres >>> try: arobj = authres.AuthenticationResultsHeader.parse('authentication-results: thehesiod.com; dkim=none (message not signed) header.d=none;thehesiod.com; dmarc=none action=none header.from=hotmail.com;') ... except authres.SyntaxError as e: print(e) Syntax error: Expected "=" at: ; dmarc=none action=none header.from=hot... # vim:sw=4 sts=4