def main(argv): parser = PerlOptionParser() parser.add_option("--file",dest="file") parser.add_option("--ip",dest="ip") parser.add_option("--sender",dest="sender") parser.add_option("--helo",dest="hello_name") parser.add_option("--local",dest="local_policy") parser.add_option("--rcpt-to",dest="rcpt") parser.add_option("--default-explanation",dest="explanation") parser.add_option("--sanitize",type="int",dest="sanitize") parser.add_option("--debug",type="int",dest="debug") opts,args = parser.parse_args(argv) if opts.ip: q = spf.query(opts.ip,opts.sender,opts.hello_name,local=opts.local_policy) if opts.explanation: q.set_default_explanation(opts.explanation) format(q) if opts.file: if opts.file == '0': fp = sys.stdin else: fp = open(opts.file,'r') for ln in fp: ip,sender,helo,rcpt = ln.split(None,3) q = spf.query(ip,sender,helo,local=opts.local_policy) if opts.explanation: q.set_default_explanation(opts.explanation) format(q) fp.close()
def main(argv): parser = PerlOptionParser() parser.add_option("--file", dest="file") parser.add_option("--ip", dest="ip") parser.add_option("--sender", dest="sender") parser.add_option("--helo", dest="hello_name") parser.add_option("--local", dest="local_policy") parser.add_option("--rcpt-to", dest="rcpt") parser.add_option("--default-explanation", dest="explanation") parser.add_option("--sanitize", type="int", dest="sanitize") parser.add_option("--debug", type="int", dest="debug") opts, args = parser.parse_args(argv) if opts.ip: q = spf.query(opts.ip, opts.sender, opts.hello_name, local=opts.local_policy) if opts.explanation: q.set_default_explanation(opts.explanation) format(q) if opts.file: if opts.file == '0': fp = sys.stdin else: fp = open(opts.file, 'r') for ln in fp: ip, sender, helo, rcpt = ln.split(None, 3) q = spf.query(ip, sender, helo, local=opts.local_policy) if opts.explanation: q.set_default_explanation(opts.explanation) format(q) fp.close()
def testInvalidSPF(self): i, s, h = '1.2.3.4', 'sender@domain', 'helo' q = spf.query(i=i, s=s, h=h, receiver='localhost', strict=False) res, code, txt = q.check('v=spf1...') self.assertEquals('none', res) q = spf.query(i=i, s=s, h=h, receiver='localhost', strict=2) res, code, txt = q.check('v=spf1...') self.assertEquals('ambiguous', res)
def testInvalidSPF(self): i, s, h = '1.2.3.4','sender@domain','helo' q = spf.query(i=i, s=s, h=h, receiver='localhost', strict=False) res,code,txt = q.check('v=spf1...') self.assertEquals('none',res) q = spf.query(i=i, s=s, h=h, receiver='localhost', strict=2) res,code,txt = q.check('v=spf1...') self.assertEquals('ambiguous',res)
def check_spf(params, guess): '''Check the SPF record of the sending address. Try Best Guess when the domain has no SPF record. Returns 1 when the SPF result is in ['fail', 'softfail'], returns 0 else. @type params: dict @param params: the params from Postfix @type guess: int @param guess: 1 if use 'best guess', 0 if not @rtype: int @return: 1 if bad SPF, 0 else ''' score = 0 try: s = spf.query(params['client_address'], params['sender'], params['helo_name']) r = s.check() if r[0] in ['fail', 'softfail']: score = 1 elif r[0] in ['pass']: score = 0 elif guess > 0 and r[0] in ['none']: r = s.best_guess() if r[0] in ['fail', 'softfail']: score = 1 elif r[0] in ['pass']: score = 0 except: # DNS Errors, yay... print 'something went wrong in check_spf()' return score
def runTest(self): global zonedata t = self._spftest zonedata = t.scenario.zonedata q = spf.query(i=t.host, s=t.mailfrom, h=t.helo, strict=t.strict) q.set_default_explanation('DEFAULT') res, code, exp = q.check() #print q.mechanism if res in oldresults: res = oldresults[res] ok = True msg = '' if res != t.result and res not in t.result: if verbose: msg += ' '.join((t.result, '!=', res)) + '\n' ok = False elif res != t.result and res != t.result[0]: self.warn("WARN: %s in %s, %s: %s preferred to %s" % (t.id, t.scenario.filename, t.spec, t.result[0], res)) if t.explanation is not None and t.explanation != exp: if verbose: msg += ' '.join((t.explanation, '!=', exp)) + '\n' ok = False if t.header: self.assertEqual(t.header, q.get_header(res, receiver=t.receiver)) if q.perm_error and t.bestguess is not None \ and q.perm_error.ext[0] != t.bestguess: ok = False if not ok: if verbose and not t.explanation: msg += exp + '\n' if verbose > 1: msg += t.scenario.zonedata self.fail(msg + "%s in %s failed, %s" % (t.id, t.scenario.filename, t.spec))
def auth_spfplflow(dd, spfid): # info('info','SPF Validation process tests SPF record if it is syntactically correct \n' \ # 'according to RFC 7208 compliant library pyspf. To present the whole path \n' \ # 'this test additionally follow all "include" mechanisms using DNS query to \n' \ # 'retrieve all external SPF policies.' \ # , adj='l') # print fpr('SPF identity type : %s' % spfid) fpr('SPF identity value : %s' % dd['id'].get(spfid)) if spfid == 'mfrom': local_part, domain = spf.split_email(s=dd['id'].get('mfrom'), h=dd['id'].get('helo')) if spfid == 'helo': local_part, domain = spf.split_email(s='', h=dd['id'].get('helo')) fpr('Domain: %s' % domain) print try: q = spf.query(i=dd.get('ip'), s=dd['id'].get('mfrom'), h=dd['id'].get('helo'), timeout=20, verbose=False, querytime=0) except Exception, e: fpr.err('Err: %s' % e) waitin() return
def runTest(self,tests): global zonedata passed,failed = 0,0 for t in tests: zonedata = t.scenario.zonedata q = spf.query(i=t.host, s=t.mailfrom, h=t.helo, strict=t.strict) q.set_default_explanation('DEFAULT') res,code,exp = q.check() if res in oldresults: res = oldresults[res] ok = True if res != t.result and res not in t.result: if verbose: print(t.result,'!=',res) ok = False elif res != t.result and res != t.result[0]: print("WARN: %s in %s, %s: %s preferred to %s" % ( t.id,t.scenario.filename,t.spec,t.result[0],res)) if t.explanation is not None and t.explanation != exp: if verbose: print(t.explanation,'!=',exp) ok = False if t.header: self.assertEqual(t.header,q.get_header(res,receiver=t.receiver)) if ok: passed += 1 else: failed += 1 print("%s in %s failed, %s" % (t.id,t.scenario.filename,t.spec)) if verbose and not t.explanation: print(exp) if verbose > 1: print(t.scenario.zonedata) if failed: print("%d passed" % passed,"%d failed" % failed)
def check_syntax_spf(self, spf_text, domain): ''' Given an SPF record, it will check its syntax. :param spf_text: String, the SPF record to test :param domain: String, domain name tested :return: Boolean, SPF syntax OK? True:False String, code returned by the check() function (250, 500,..) String, if failed, description of the syntax error. ''' try: check_result, check_code, check_description = (None, None, None) q = spf.query(s='postmaster@%s' % domain, h=domain, i='127.0.0.1') check_result, check_code, check_description = q.check(spf=spf_text) self.logger.debug( "SPF-SYNTAX-CHECK: %s %s %s" % (str(check_result), str(check_code), str(check_description))) if check_result in ["none", "permerror", "temperror"]: return False, check_code, check_description else: return True, check_code, check_description except Exception as error: self.logger.error("SPF-Text Syntax-Error " + str(spf_text) + ". " + str(error)) return False, str( check_code), "SPF-Text Syntax-Error " + str(error)
def runTest(self, tests): global zonedata passed, failed = 0, 0 for t in tests: zonedata = t.scenario.zonedata q = spf.query(i=t.host, s=t.mailfrom, h=t.helo, strict=t.strict) q.set_default_explanation('DEFAULT') res, code, exp = q.check() if res in oldresults: res = oldresults[res] ok = True if res != t.result and res not in t.result: if verbose: print t.result, '!=', res ok = False elif res != t.result and res != t.result[0]: print "WARN: %s in %s, %s: %s preferred to %s" % ( t.id, t.scenario.filename, t.spec, t.result[0], res) if t.explanation is not None and t.explanation != exp: if verbose: print t.explanation, '!=', exp ok = False if t.header: self.assertEqual(t.header, q.get_header(res, receiver=t.receiver)) if ok: passed += 1 else: failed += 1 print "%s in %s failed, %s" % (t.id, t.scenario.filename, t.spec) if verbose and not t.explanation: print exp if verbose > 1: print t.scenario.zonedata if failed: print "%d passed" % passed, "%d failed" % failed
def check_spf(params, guess): '''Check the SPF record of the sending address. Try Best Guess when the domain has no SPF record. Returns 1 when the SPF result is in ['fail', 'softfail'], returns 0 else. @type params: dict @param params: the params from Postfix @type guess: int @param guess: 1 if use 'best guess', 0 if not @rtype: int @return: 1 if bad SPF, 0 else ''' score = 0 try: s = spf.query(params['client_address'], params['sender'], params['helo_name']) r = s.check() if r[0] in ['fail', 'softfail']: score = 1 elif r[0] in ['pass']: score = 0 elif guess > 0 and r[0] in ['none']: r = s.best_guess() if r[0] in ['fail', 'softfail']: score = 1 elif r[0] in ['pass']: score = 0 except: # DNS Errors, yay... print('something went wrong in check_spf()') return score
def runTest(self): global zonedata t = self._spftest zonedata = t.scenario.zonedata q = spf.query(i=t.host, s=t.mailfrom, h=t.helo, strict=t.strict) q.set_default_explanation('DEFAULT') res,code,exp = q.check() #print q.mechanism if res in oldresults: res = oldresults[res] ok = True msg = '' if res != t.result and res not in t.result: if verbose: msg += ' '.join((t.result,'!=',res))+'\n' ok = False elif res != t.result and res != t.result[0]: self.warn("WARN: %s in %s, %s: %s preferred to %s" % ( t.id,t.scenario.filename,t.spec,t.result[0],res)) if t.explanation is not None and t.explanation != exp: if verbose: msg += ' '.join((t.explanation,'!=',exp))+'\n' ok = False if t.header: self.assertEqual(t.header,q.get_header(res,receiver=t.receiver)) if not ok: if verbose and not t.explanation: msg += exp+'\n' if verbose > 1: msg += t.scenario.zonedata self.fail(msg+"%s in %s failed, %s" % (t.id,t.scenario.filename,t.spec))
def __init__(self,q=None): self.spf = [] self.action = '-all' self.has_servers = None self.spf_entry = None if q: self.spf_query = q else: self.spf_query = spf.query(i='127.0.0.1', s='localhost', h='unknown')
def message_has_valid_spf(sender, msg): received = msg.get_all('Received', []) if len(received) == 0: return False pattern = re.compile('\s*from\s+[^\s]+\s+\(([^\s]+\s+)?\[([^]]+)\]\)', re.IGNORECASE) match = pattern.search(received[0]) if match is None: return False sender_ipaddr = match.group(2) q = spf.query(sender_ipaddr, sender, None) res, code, txt = q.check() return res == 'pass'
def auth_spf(dd, spfid='all'): #TODO: validate value ip, helo (fqdn), mfrom (isemail) fpr('SPF Values') fpr('_' * (tc - 4) + '\n\n') fpr(' id MAIL FROM : %s' % dd['id'].get('mfrom')) fpr(' id HELO/EHLO : %s' % dd['id'].get('helo')) fpr.info(' id PRA : %s' % dd['id'].get('pra')) fpr(' Client IP : %s' % dd.get('ip')) fpr('_' * (tc - 4) + '\n\n') try: #if spfid == 'mfrom': qs = spf.query( i=dd.get('ip'), s=dd['id'].get('mfrom'), #s='', h=dd['id'].get('helo'), timeout=20, verbose=False, querytime=0) #if spfid == 'helo': qh = spf.query( i=dd.get('ip'), # s=dd['id'].get('mfrom'), s='', h=dd['id'].get('helo'), timeout=20, verbose=False, querytime=0) except Exception, e: fpr.err('Err: %s' % e) waitin() return
def check_spf(self): q = spf.query(s=self.canon_from, h=self.helodomain, i=self.connectip) res, code, txt = q.check() self.spfresult = res if not q.ident == 'helo': self.arresults.append( authres.SPFAuthenticationResult(result=res, smtp_mailfrom=self.canon_from, smtp_mailfrom_comment=txt)) else: self.arresults.append( authres.SPFAuthenticationResult(result=res, smtp_helo=self.helodomain, smtp_helo_comment=txt)) return
def check_spf_record(record_text, expected_result, domain): """Test to see if an SPF record is valid and correct. The record is tested by checking the response when we query if it allows us to send mail from an IP that is known not to be a mail server that appears in the MX records for ANY domain. Parameters ---------- record_text : str The text of the SPF record to be tested. expected_result : str The expected result of the test. domain : trustymail.Domain The Domain object corresponding to the SPF record being tested. Any errors will be logged to this object. """ try: # Here I am using the IP address for c1b1.ncats.cyber.dhs.gov # (64.69.57.18) since it (1) has a valid PTR record and (2) is not # listed by anyone as a valid mail server. # # I'm actually temporarily using an IP that virginia.edu resolves to # until we resolve why Google DNS does not return the same PTR records # as the CAL DNS does for 64.69.57.18. query = spf.query('128.143.22.36', 'email_wizard@' + domain.domain_name, domain.domain_name, strict=2) response = query.check() response_type = response[0] if response_type == 'temperror' or response_type == 'permerror' or response_type == 'ambiguous': handle_error( '[SPF]', domain, 'SPF query returned {}: {}'.format(response_type, response[2])) elif response_type == expected_result: # Everything checks out. The SPF syntax seems valid domain.valid_spf = True else: domain.valid_spf = False msg = 'Result unexpectedly differs: Expected [{}] - actual [{}]'.format( expected_result, response_type) handle_error('[SPF]', domain, msg) except spf.AmbiguityWarning as error: handle_syntax_error('[SPF]', domain, error)
def parse_spfHeaders(h): if h: print h try: q = spf.query('0.0.0.0', '', '') q.mechanism = 'unknown' p = q.parse_header(h) ph = q.get_header(q.result, **p) print print p['identity'] fpr.blue(str(p)) fpr.warn(str(ph)) except Exception, e: fpr.err("Error: %s" % e)
def check_spf(self): receiver = self.receiver for tf in self.conf.trusted_forwarder: q = spf.query(self.connectip, '', tf, receiver=receiver, strict=False) res, code, txt = q.check() if res == 'pass': self.log("TRUSTED_FORWARDER:", tf) break else: q = spf.query(self.connectip, self.canon_from, self.hello_name, receiver=receiver, strict=False) q.set_default_explanation( 'SPF fail: see http://openspf.org/why.html?sender=%s&ip=%s' % (q.s, q.i)) res, code, txt = q.check() if res not in ('pass', 'temperror'): if self.mailfrom != '<>': # check hello name via spf unless spf pass h = spf.query(self.connectip, '', self.hello_name, receiver=receiver) hres, hcode, htxt = h.check() with SPFPolicy(self.hello_name, self.conf) as hp: policy = hp.getPolicy('helo-%s:' % hres) #print 'helo-%s:%s %s'%(hres,self.hello_name,policy) if not policy: if hres in ('deny', 'fail', 'neutral', 'softfail'): policy = 'REJECT' else: policy = 'OK' if policy != 'OK': self.log('REJECT: hello SPF: %s 550 %s' % (hres, htxt)) self.setreply( '550', '5.7.1', htxt, "The hostname given in your MTA's HELO response is not listed", "as a legitimate MTA in the SPF records for your domain. If you", "get this bounce, the message was not in fact a forgery, and you", "should IMMEDIATELY notify your email administrator of the problem." ) return Milter.REJECT else: hres, hcode, htxt = res, code, txt else: hres = None with SPFPolicy(q.s, self.conf) as p: if res == 'fail': policy = p.getPolicy('spf-fail:') if not policy or policy == 'REJECT': self.log('REJECT: SPF %s %i %s' % (res, code, txt)) self.setreply(str(code), '5.7.1', txt) # A proper SPF fail error message would read: # forger.biz [1.2.3.4] is not allowed to send mail with the domain # "forged.org" in the sender address. Contact <*****@*****.**>. return Milter.REJECT elif res == 'softfail': policy = p.getPolicy('spf-softfail:') if policy and policy == 'REJECT': self.log('REJECT: SPF %s %i %s' % (res, code, txt)) self.setreply(str(code), '5.7.1', txt) # A proper SPF fail error message would read: # forger.biz [1.2.3.4] is not allowed to send mail with the domain # "forged.org" in the sender address. Contact <*****@*****.**>. return Milter.REJECT elif res == 'permerror': policy = p.getPolicy('spf-permerror:') if not policy or policy == 'REJECT': self.log('REJECT: SPF %s %i %s' % (res, code, txt)) # latest SPF draft recommends 5.5.2 instead of 5.7.1 self.setreply( str(code), '5.5.2', txt, 'There is a fatal syntax error in the SPF record for %s' % q.o, 'We cannot accept mail from %s until this is corrected.' % q.o) return Milter.REJECT elif res == 'temperror': policy = p.getPolicy('spf-temperror:') if not policy or policy == 'REJECT': self.log('TEMPFAIL: SPF %s %i %s' % (res, code, txt)) self.setreply(str(code), '4.3.0', txt) return Milter.TEMPFAIL elif res == 'neutral' or res == 'none': policy = p.getPolicy('spf-neutral:') if policy and policy == 'REJECT': self.log('REJECT NEUTRAL:', q.s) self.setreply( '550', '5.7.1', "%s requires an SPF PASS to accept mail from %s. [http://openspf.org]" % (receiver, q.s)) return Milter.REJECT elif res == 'pass': policy = p.getPolicy('spf-pass:'******'REJECT': self.log('REJECT PASS:'******'550', '5.7.1', "%s has been blacklisted by %s." % (q.s, receiver)) return Milter.REJECT self.add_header('Received-SPF', q.get_header(res, receiver), 0) if hres and q.h != q.o: self.add_header('X-Hello-SPF', hres, 0) return Milter.CONTINUE
name,val = ln.split(':',1) msg.add_header(name,(val % v.__dict__).strip()) msg.set_payload(body % v.__dict__) # add headers if missing from old template if 'to' not in msg: msg.add_header('To',v.sender) if 'from' not in msg: msg.add_header('From','postmaster@%s'%v.receiver) if 'auto-submitted' not in msg: msg.add_header('Auto-Submitted','auto-generated') return msg if __name__ == '__main__': import spf q = spf.query('192.168.9.50', '[email protected]', 'red.example.com',receiver='mail.example.com') q.result = 'softfail' q.perm_error = None msg = create_msg(q,['*****@*****.**'],None, """From: postmaster@%(receiver)s To: %(sender)s Subject: Test Test DSN template """ ) print msg.as_string() # print send_dsn(f,msg.as_string()) # print send_dsn(q.s,'mail.example.com',msg.as_string())
def check_spf(self): receiver = self.receiver for tf in self.conf.trusted_forwarder: q = spf.query(self.connectip,'',tf,receiver=receiver,strict=False) res,code,txt = q.check() if res == 'pass': self.log("TRUSTED_FORWARDER:",tf) break else: q = spf.query(self.connectip,self.canon_from,self.hello_name, receiver=receiver,strict=False) q.set_default_explanation( 'SPF fail: see http://openspf.org/why.html?sender=%s&ip=%s' % (q.s,q.i)) res,code,txt = q.check() if res not in ('pass','temperror'): if self.mailfrom != '<>': # check hello name via spf unless spf pass h = spf.query(self.connectip,'',self.hello_name,receiver=receiver) hres,hcode,htxt = h.check() with SPFPolicy(self.hello_name,self.conf) as hp: policy = hp.getPolicy('helo-%s:'%hres) #print 'helo-%s:%s %s'%(hres,self.hello_name,policy) if not policy: if hres in ('deny','fail','neutral','softfail'): policy = 'REJECT' else: policy = 'OK' if policy != 'OK': self.log('REJECT: hello SPF: %s 550 %s' % (hres,htxt)) self.setreply('550','5.7.1',htxt, "The hostname given in your MTA's HELO response is not listed", "as a legitimate MTA in the SPF records for your domain. If you", "get this bounce, the message was not in fact a forgery, and you", "should IMMEDIATELY notify your email administrator of the problem." ) return Milter.REJECT else: hres,hcode,htxt = res,code,txt else: hres = None with SPFPolicy(q.s,self.conf) as p: if res == 'fail': policy = p.getPolicy('spf-fail:') if not policy or policy == 'REJECT': self.log('REJECT: SPF %s %i %s' % (res,code,txt)) self.setreply(str(code),'5.7.1',txt) # A proper SPF fail error message would read: # forger.biz [1.2.3.4] is not allowed to send mail with the domain # "forged.org" in the sender address. Contact <*****@*****.**>. return Milter.REJECT elif res == 'softfail': policy = p.getPolicy('spf-softfail:') if policy and policy == 'REJECT': self.log('REJECT: SPF %s %i %s' % (res,code,txt)) self.setreply(str(code),'5.7.1',txt) # A proper SPF fail error message would read: # forger.biz [1.2.3.4] is not allowed to send mail with the domain # "forged.org" in the sender address. Contact <*****@*****.**>. return Milter.REJECT elif res == 'permerror': policy = p.getPolicy('spf-permerror:') if not policy or policy == 'REJECT': self.log('REJECT: SPF %s %i %s' % (res,code,txt)) # latest SPF draft recommends 5.5.2 instead of 5.7.1 self.setreply(str(code),'5.5.2',txt, 'There is a fatal syntax error in the SPF record for %s' % q.o, 'We cannot accept mail from %s until this is corrected.' % q.o ) return Milter.REJECT elif res == 'temperror': policy = p.getPolicy('spf-temperror:') if not policy or policy == 'REJECT': self.log('TEMPFAIL: SPF %s %i %s' % (res,code,txt)) self.setreply(str(code),'4.3.0',txt) return Milter.TEMPFAIL elif res == 'neutral' or res == 'none': policy = p.getPolicy('spf-neutral:') if policy and policy == 'REJECT': self.log('REJECT NEUTRAL:',q.s) self.setreply('550','5.7.1', "%s requires an SPF PASS to accept mail from %s. [http://openspf.org]" % (receiver,q.s)) return Milter.REJECT elif res == 'pass': policy = p.getPolicy('spf-pass:'******'REJECT': self.log('REJECT PASS:'******'550','5.7.1', "%s has been blacklisted by %s." % (q.s,receiver)) return Milter.REJECT self.add_header('Received-SPF',q.get_header(res,receiver),0) if hres and q.h != q.o: self.add_header('X-Hello-SPF',hres,0) return Milter.CONTINUE
name,val = ln.split(':',1) msg.add_header(name,(val % v.__dict__).strip()) msg.set_payload(body % v.__dict__) # add headers if missing from old template if 'to' not in msg: msg.add_header('To',v.sender) if 'from' not in msg: msg.add_header('From','postmaster@%s'%v.receiver) if 'auto-submitted' not in msg: msg.add_header('Auto-Submitted','auto-generated') return msg if __name__ == '__main__': import spf q = spf.query('192.168.9.50', '[email protected]', 'red.example.com',receiver='mail.example.com') q.result = 'softfail' q.perm_error = None msg = create_msg(q,['*****@*****.**'],None, """From: postmaster@%(receiver)s To: %(sender)s Subject: Test Test DSN template """ ) print(msg.as_string()) # print(send_dsn(f,msg.as_string())) # print(send_dsn(q.s,'mail.example.com',msg.as_string()))
def main(self, *args): """ Main function """ if not self.host: raise NoHostName('No host name specified.') errors = [] spf_record = None txt_record = None spf_valid = False self._check_stop() try: # get all name servers r = Resolver() r.lifetime = self.DNS_TIMEOUT name_servers = r.query(self.host, 'NS') name_servers = map(lambda x: str(x), name_servers) self._check_stop() ns_list = [] for name_server in name_servers: if name_server[-1] == '.': name_server = name_server[:-1] ns_list.append(gethostbyname(name_server)) try: # check TXT record type for SPF records r = Resolver() r.lifetime = self.DNS_TIMEOUT r.nameservers = ns_list txt_records = r.query(self.host, 'TXT') txt_records = map(lambda x: str(x), txt_records) for txt in txt_records: txt = str(txt) # remove quotes if txt[0] == '"' and txt[-1] == '"': txt = txt[1:-1] if txt.startswith('v=spf1'): txt_record = txt break except NoAnswer: pass self._check_stop() try: # check SPF record type for SPF records r = Resolver() r.lifetime = self.DNS_TIMEOUT r.nameservers = ns_list spf_records = r.query(self.host, 'SPF') spf_records = map(lambda x: str(x), spf_records) if len(spf_records) > 0: spf_record = str(spf_records[0]) # remove quotes if spf_record[0] == '"' and spf_record[-1] == '"': spf_record = spf_record[1:-1] except NoAnswer: pass self._check_stop() # validate the SPF record if spf_record or txt_record: try: record = txt_record if spf_record: record = spf_record # get MX records r = Resolver() r.lifetime = self.DNS_TIMEOUT r.nameservers = ns_list mx_records = r.query(self.host, 'MX') mx_records = map(lambda x: str(x), mx_records) self._check_stop() mx_ip = None if mx_records and len(mx_records) > 0: mx_server = str(mx_records[0]).split(' ')[1] mx_ip = gethostbyname(mx_server) self._check_stop() spf_query = spf.query(i=mx_ip, h=None, s='admin@%s' % self.host, timeout=self.DNS_TIMEOUT) result = spf_query.check(record) if result[0] == 'pass': spf_valid = True except TaskTimeout: raise except: pass except NoAnswer: self._write_result('No answer from name server.') except NoNameservers: self._write_result('No name servers.') except NXDOMAIN: self._write_result('Host not found.') except Timeout: self._write_result('DNS request timeout.') except DNSException: self._write_result('DNS error.') self._check_stop() if spf_record or txt_record: if spf_record: self._write_result('SPF "%s"' % spf_record) if txt_record: self._write_result('TXT "%s"' % txt_record) if spf_valid: self._write_result('SPF record is valid.') else: self._write_result('SPF record is invalid.') if (spf_record and not txt_record) or (not spf_record and txt_record): errors.append( 'It\'s recommended to have SPF records of both SPF and TXT record types, RFC 4408.' ) elif spf_record != txt_record: errors.append( 'SPF record contents should be identical for TXT and SPF record types.' ) if errors: self._write_result('\n'.join(errors)) else: self._write_result('No SPF records.')
def spf_scan(domain): try: # for record in resolver.query(domain.domain_name, 'TXT'): for record in dnslookup(domain.domain_name, 'TXT'): record_text = record_to_str(record) if record_text.startswith("\""): record_text = record_text[1:-1] if not record_text.startswith("v=spf1"): # Not an spf record, ignore it. continue domain.spf.append(record_text) # From the found record grab the specific result when something doesn't match. # Definitions of result come from https://www.ietf.org/rfc/rfc4408.txt if record_text.endswith("-all"): result = 'fail' elif record_text.endswith("?all"): result = "neutral" elif record_text.endswith("~all"): result = "softfail" elif record_text.endswith("all") or record_text.endswith("+all"): result = "pass" else: result = "neutral" try: query = spf.query("127.0.0.1", "email_wizard@" + domain.domain_name, domain.domain_name, strict=2) response = query.check() except spf.AmbiguityWarning as error: logging.debug("\t" + error.msg) domain.syntax_errors.append(error.msg) continue if response[0] == 'temperror': logging.debug(response[2]) elif response[0] == 'permerror': logging.debug("\t" + response[2]) domain.syntax_errors.append(response[2]) elif response[0] == 'ambiguous': logging.debug("\t" + response[2]) domain.syntax_errors.append(response[2]) elif response[0] == result: # Everything checks out the SPF syntax seems valid. domain.valid_spf = True continue else: domain.valid_spf = False logging.debug( "\tResult Differs: Expected [{0}] - Actual [{1}]".format( result, response[0])) domain.errors.append( "Result Differs: Expected [{0}] - Actual [{1}]".format( result, response[0])) except DNSError as error: handle_error("[SPF]", domain, error)