Example #1
0
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()
Example #2
0
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()
Example #3
0
 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)
Example #4
0
 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)
Example #5
0
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
Example #6
0
 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))
Example #7
0
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
Example #8
0
 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)
Example #9
0
    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)
Example #10
0
 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
Example #11
0
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
Example #12
0
  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))
Example #13
0
 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')
Example #14
0
 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')
Example #15
0
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'
Example #16
0
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
Example #17
0
 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
Example #18
0
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)
Example #19
0
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)
Example #20
0
    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
Example #21
0
    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())
Example #22
0
  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
Example #23
0
    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()))
Example #24
0
    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.')
Example #25
0
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)