Exemple #1
0
 def fetch_addrs(self, resolver, name, arecs, a4recs):
   aco = resolver.aquery(name, "A", raise_on_no_answer=False)
   a4co = resolver.aquery(name, "AAAA", raise_on_no_answer=False)
   co = asyncio.gather(aco, a4co)
   (aans, a4ans) = yield from co
   if aans.rrset != None:
     for rdata in aans:
       arecs.append(rdata.address)
   if a4ans.rrset != None:
     for rdata in a4ans:
       a4recs.append(rdata.address)
Exemple #2
0
def fetch_addrs(resolver, name, arecs, a4recs, lim):
  if lim and lim[0] == 0:
    raise TooManyQueries
  lim[0] = lim[0] - 1
  cos = []
  if arecs:
    cos.append(resolver.aquery(name, "A", raise_on_no_answer=False))
  if a4recs:
    cos.append(resolver.aquery(name, "AAAA", raise_on_no_answer=False))
  results = yield from asyncio.gather(*cos)
  i = 0
  if arecs:
    aans = results[0]
    i = i + 1
    if aans.rrset != None:
      for rdata in aans:
        arecs.append(rdata.address)
  if a4recs:
    a4ans = results[i]
    if a4ans.rrset != None:
      for rdata in a4ans:
        a4recs.append(rdata.address)
Exemple #3
0
def fetch_addrs(resolver, name, arecs, a4recs, lim):
    if lim and lim[0] == 0:
        raise TooManyQueries
    lim[0] = lim[0] - 1
    cos = []
    if arecs:
        cos.append(resolver.aquery(name, "A", raise_on_no_answer=False))
    if a4recs:
        cos.append(resolver.aquery(name, "AAAA", raise_on_no_answer=False))
    results = yield from asyncio.gather(*cos)
    i = 0
    if arecs:
        aans = results[0]
        i = i + 1
        if aans.rrset != None:
            for rdata in aans:
                arecs.append(rdata.address)
    if a4recs:
        a4ans = results[i]
        if a4ans.rrset != None:
            for rdata in a4ans:
                a4recs.append(rdata.address)
Exemple #4
0
def get_exchanger_list(domain, resolver, family=socket.AF_UNSPEC, lim=None, implicit=True):
  resolver = dns.resolver.Resolver()
  resolver.use_edns(0, 0, 1410)
  mxs = {}
  answer = None
  addressable = False
  arecs = None
  a4recs = None

  if family == socket.AF_UNSPEC or family == socket.AF_INET:
    arecs = []
  if family == socket.AF_UNSPEC or family == socket.AF_INET6:
    a4recs = []

  if lim:
    if lim[0] == 0:
      raise TooManyQueries
    else:
      lim[0] = lim[0] - 1
  try:
    answer = yield from resolver.aquery(domain, "MX")

  except dns.resolver.NoAnswer:
    if not implicit:
      return None
    
    # No answer means there's no MX record, so look for an A or
    # AAAA record.
    
    yield from fetch_addrs(resolver, domain, arecs, a4recs, lim)
    if ((arecs and len(arecs) > 0) or
        a4recs and len(a4recs) > 0):
      mxs = { 0: [ { "exchange" : domain,
                     "a": arecs, "aaaa": a4recs } ] }
      addressable = True

  except (NXDOMAIN, TooManyQueries):
    raise
  
  except:
    return None

  else:
    for mx in answer:
      if mx.rdtype == dns.rdatatype.MX:
        # If exchange addresses were included in the additional
        # section, use those.
        # XXX for SPF, relying on the additional section may be a mistake:
        # what if it includes some, but not all, relevant data?
        for rrset in answer.response.additional:
          if rrset.name == mx.exchange:
            if rrset.rdtype == dns.rdatatype.A and arecs != None:
              for rdata in rrset:
                arecs.append(rdata.address)
                addressable = True
            elif rrset.rdtype == dns.rdatatype.AAAA and a4recs != None:
              for rdata in rrset:
                a4recs.append(rdata.address)
                addressable = True
        # Otherwise, fetch A and/or AAAA records for exchange
        if not addressable:
          yield from fetch_addrs(resolver, mx.exchange, arecs, a4recs, lim)
        if ((arecs and len(arecs) > 0) or
            a4recs and len(a4recs) > 0):
          entry = { "exchange": mx.exchange,
                    "a": arecs, "aaaa": a4recs}
          if mx.preference in mxs:
            mxs[mx.preference].append(entry)
          else:
            mxs[mx.preference] = [entry]
          addressable = True

  # If we didn't get a single server IP address either out of the
  # MX query chain or the A/AAAA query on the name if there was no
  # MX, then we can't deliver to this address.
  if not addressable:
    return None

  return mxs
Exemple #5
0
  def get_exchanger_list(self):
    resolver = dns.resolver.Resolver()
    resolver.use_edns(0, 0, 1410)
    mxs = {}
    answer = None
    addressable = False
    try:
      answer = yield from resolver.aquery(domain, "MX")
      
    except dns.resolver.NoAnswer:
      # No answer means there's no MX record, so look for an A or
      # AAAA record.
      arecs = []
      a4recs = []
      yield from self.fetch_addrs(resolver, domain, arecs, a4recs)
      if len(arecs) > 0 or len(a4recs) > 0:
        mxs = { 0: [ { "exchange" : domain,
                       "a": arecs, "aaaa": a4recs } ] }
        addressable = True

    except NXDOMAIN:
      self.push("550 no such domain.")
      syslog.syslog(syslog.LOG_INFO, "550 no such domain: %s" % domain)
      print("550 no such domain: %s" % domain)
      return False

    except:
      return True

    else:
      for mx in answer:
        if mx.rdtype == dns.rdatatype.MX:
          arecs = []
          a4recs = []
          # If exchange addresses were included in the additional
          # section, use those.
          for rrset in answer.response.additional:
            if rrset.name == mx.exchange:
              if rrset.rdtype == dns.rdatatype.A:
                for rdata in rrset:
                  arecs.append(rdata.address)
              elif rrset.rdtype == dns.rdatatype.AAAA:
                for rdata in rrset:
                  a4recs.append(rdata.address)
          # Otherwise, fetch A and/or AAAA records for exchange
          if len(arecs) == 0 and len(a4recs) == 0:
            yield from self.fetch_addrs(resolver, mx.exchange, arecs, a4recs)
          if len(arecs) > 0 or len(a4recs) > 0:
            entry = { "exchange": mx.exchange,
                      "a": arecs, "aaaa": a4recs}
            if mx.preference in mxs:
              mxs[mx.preference].append(entry)
            else:
              mxs[mx.preference] = [entry]
            addressable = True

    # If we didn't get a single server IP address either out of the
    # MX query chain or the A/AAAA query on the name if there was no
    # MX, then we can't deliver to this address.
    if not addressable:
      raise smtp.PermanentFailure(code=550,
                                  data=["no exchanger/address for domain"])
          
    return mxs
Exemple #6
0
def get_exchanger_list(domain,
                       resolver,
                       family=socket.AF_UNSPEC,
                       lim=None,
                       implicit=True):
    resolver = dns.resolver.Resolver()
    resolver.use_edns(0, 0, 1410)
    mxs = {}
    answer = None
    addressable = False
    arecs = None
    a4recs = None

    if family == socket.AF_UNSPEC or family == socket.AF_INET:
        arecs = []
    if family == socket.AF_UNSPEC or family == socket.AF_INET6:
        a4recs = []

    if lim:
        if lim[0] == 0:
            raise TooManyQueries
        else:
            lim[0] = lim[0] - 1
    try:
        answer = yield from resolver.aquery(domain, "MX")

    except dns.resolver.NoAnswer:
        if not implicit:
            return None

        # No answer means there's no MX record, so look for an A or
        # AAAA record.

        yield from fetch_addrs(resolver, domain, arecs, a4recs, lim)
        if ((arecs and len(arecs) > 0) or a4recs and len(a4recs) > 0):
            mxs = {0: [{"exchange": domain, "a": arecs, "aaaa": a4recs}]}
            addressable = True

    except (NXDOMAIN, TooManyQueries):
        raise

    except:
        return None

    else:
        for mx in answer:
            if mx.rdtype == dns.rdatatype.MX:
                # If exchange addresses were included in the additional
                # section, use those.
                # XXX for SPF, relying on the additional section may be a mistake:
                # what if it includes some, but not all, relevant data?
                for rrset in answer.response.additional:
                    if rrset.name == mx.exchange:
                        if rrset.rdtype == dns.rdatatype.A and arecs != None:
                            for rdata in rrset:
                                arecs.append(rdata.address)
                                addressable = True
                        elif rrset.rdtype == dns.rdatatype.AAAA and a4recs != None:
                            for rdata in rrset:
                                a4recs.append(rdata.address)
                                addressable = True
                # Otherwise, fetch A and/or AAAA records for exchange
                if not addressable:
                    yield from fetch_addrs(resolver, mx.exchange, arecs,
                                           a4recs, lim)
                if ((arecs and len(arecs) > 0) or a4recs and len(a4recs) > 0):
                    entry = {
                        "exchange": mx.exchange,
                        "a": arecs,
                        "aaaa": a4recs
                    }
                    if mx.preference in mxs:
                        mxs[mx.preference].append(entry)
                    else:
                        mxs[mx.preference] = [entry]
                    addressable = True

    # If we didn't get a single server IP address either out of the
    # MX query chain or the A/AAAA query on the name if there was no
    # MX, then we can't deliver to this address.
    if not addressable:
        return None

    return mxs
Exemple #7
0
  def check_host_worker(ipaddr, domain, sender, debug=False):
    limiter[0] = limiter[0] + 1
    if limiter[0] > 10:
      if debug:
        print("DNS query limiter stopped search.")
      return "permerror"

    # RFC 7208 section 4.3: check for invalid domain name
    try:
      name = dns.name.from_text(domain)
    except:
      if debug:
        print("failure:", domain, " is not a valid domain name")
      return "permerror"
    # RFC 7208 section 4.3: check for existence of local-part
    # We are assuming that the caller has produced some
    # vaguely valid sender, either from the MAIL FROM:
    # or HELO/EHLO.   For validating From: headers,
    # combining the source IP address and sender isn't
    # sufficient to say that the use isn't permitted, but
    # it is sufficient to say that the use _is_ permitted.
    if '@' not in sender:
      try:
        sdo = dns.name.from_text(sender)
      except:
        if debug:
          print("failure:", sender, " is not a valid domain name")
        return "permerror"
      sender = "postmaster@" + sender
    else:
      parts = sender.split("@")
      if len(parts) != 2:
        return "permerror"
      if parts[0] == '':
        sender = "postmaster@" + parts[1]
      try:
        sdo = dns.name.from_text(parts[1])
      except:
        if debug:
          print("failure:", parts[1], " is not a valid domain name")
        return "permerror"
      
    spfs = []
    try:
      spfRecord = yield from resolver.aquery(domain, "TXT", raise_on_no_answer=True)
    except dns.resolver.NoAnswer:
      if debug:
        print(domain, "IN TXT: No Answer")
      return None
    except Exception as x:
      if debug:
        print(domain, "IN TXT:", str(x))
      return "temperror"
    else:
      for rr in spfRecord:
        text = "".join(rr.strings)
        if text.startswith("v=spf1 "):
          spfs.append(text[7:])
    if len(spfs) == 0:
      if debug:
        print(domain, "IN TXT: No valid SPF record")
      return None
    if len(spfs) > 1:
      if debug:
        print("More than one SPF record found.")
      return "permerror"

    # Terms are separated by exactly one space.
    # terms = *( 1*SP ( directive / modifier ) )
    if debug:
      print(spfs[0])
    terms = spfs[0].split(" ")
    redirect = None
    modifiers = {}
    directives = []
    for term in terms:
      if len(term) == 0:
        if debug:
          print("syntax error: zero-length term")
        return "permerror"
      # Eliminate the possibility that this is a modifier first,
      # because they are easy to detect.
      elif "=" in term:
        sides = term.split("=")
        if len(sides) != 2:
          if debug:
            print("bogus modifier")
          return "permerror"
        # modifiers can only appear once.
        if sides[0] in modifiers:
          if debug:
            print("Duplicate modifier:", sides[0])
          return "permerror"
        exp = False
        if sides[0] == "exp":
          exp = True
        expansion = macro_expand(sides[1], ipaddr, domain, sender, exp)
        if expansion == None:
          return "permerror"
        modifiers[sides[0]] = sides[1]
      else:
        # By default, then, this is a directive.
        directive = Directive(check_host_worker, resolver, debug)
        if directive.validate(term, ipaddr, domain, sender) == "permerror":
          return "permerror"
        directives.append(directive)
    for directive in directives:
      status = yield from directive.process(ipaddr, domain, sender)
      # If this directive matched, don't process any later directives.
      if directive.matched:
        if debug:
          print("Matched:", directive.name)
        return status
      # If this directive returned a definite answer, return that answer
      # (e.g., tempfail, etc.)
      if status != None and status != "neutral":
        if debug:
          print("Status:", status)
        return status
    # Since we survived the directives, try the modifiers.
    for modifier in modifiers:
      if modifier == "exp":
        # We could really care less.
        pass
      elif modifier == "redirect":
        if debug:
          print("Redirect:", modifiers[modifier])
        status = yield from check_host_worker(ipaddr, modifiers[modifier], sender, debug)
        if debug:
          print("Status:", status)
        return status
    return "neutral"
Exemple #8
0
    def get_connection(self, user, domain):
        # We're already connected.   Just return the connection.
        if domain in self.connections:
            connection = self.connections[domain]
            if self.connections[domain] not in self.connection_list:
                status = yield from self.send_rcptto(connection,
                                                     user + "@" + domain)

                if status:
                    self.connections[user + "@" + domain] = connection
                    self.connection_list.append(connection)
                else:
                    print("bad status after send_rcptto.")
                return status
            return True

        resolver = dns.resolver.Resolver()
        resolver.use_edns(0, 0, 1410)
        mxs = {}
        answer = None
        addressable = False
        try:
            answer = yield from resolver.aquery(domain, "MX")

        except dns.resolver.NoAnswer:
            # No answer means there's no MX record, so look for an A or
            # AAAA record.
            arecs = []
            a4recs = []
            yield from self.fetch_addrs(resolver, domain, arecs, a4recs)
            if len(arecs) > 0 or len(a4recs) > 0:
                mxs = {0: [{"exchange": domain, "a": arecs, "aaaa": a4recs}]}
                addressable = True

        except NXDOMAIN:
            self.push("550 no such domain.")
            syslog.syslog(syslog.LOG_INFO, "550 no such domain: %s" % domain)
            print("550 no such domain: %s" % domain)
            return False

        except:
            # Temporary failure; we just have to stash the message for this
            # address.
            self.connections[user + "@" + domain] = None
            self.connections[domain] = None
            return True

        else:
            for mx in answer:
                if mx.rdtype == dns.rdatatype.MX:
                    arecs = []
                    a4recs = []
                    # If exchange addresses were included in the additional
                    # section, use those.
                    for rrset in answer.response.additional:
                        if rrset.name == mx.exchange:
                            if rrset.rdtype == dns.rdatatype.A:
                                for rdata in rrset:
                                    arecs.append(rdata.address)
                            elif rrset.rdtype == dns.rdatatype.AAAA:
                                for rdata in rrset:
                                    a4recs.append(rdata.address)
                    # Otherwise, fetch A and/or AAAA records for exchange
                    if len(arecs) == 0 and len(a4recs) == 0:
                        yield from self.fetch_addrs(resolver, mx.exchange,
                                                    arecs, a4recs)
                    if len(arecs) > 0 or len(a4recs) > 0:
                        entry = {
                            "exchange": mx.exchange,
                            "a": arecs,
                            "aaaa": a4recs
                        }
                        if mx.preference in mxs:
                            mxs[mx.preference].append(entry)
                        else:
                            mxs[mx.preference] = [entry]
                        addressable = True

        # If we didn't get a single server IP address either out of the
        # MX query chain or the A/AAAA query on the name if there was no
        # MX, then we can't deliver to this address.
        if not addressable:
            self.push("550 no exchanger or addresses for domain.")
            syslog.syslog(syslog.LOG_INFO,
                          "550 no exchanger or addresses for: %s" % domain)
            print("550 no exchanger or addresses for: %s" % domain)
            return False

        # Our task now is to get a connection to the most preferable
        # Mail Exchanger (MX) we can reach.
        # Make a list of all the addresses to try, in order of preference.
        # We prefer IPv6 for the first attempt, but interleave IPv6 and
        # IPv4 addresses in case one transport is working and the other
        # is not.   The interleaving is per-exchange, so we always try
        # exchanges in order of preference and, among exchanges with the
        # same preference, one exchange at a time.

        addrs = []
        preferences = list(mxs.keys())
        preferences.sort()
        # Iterate across preference levels
        for pref in preferences:
            exchanges = mxs[pref]
            # Iterate across exchanges at a given preference level
            for exchange in exchanges:
                arecs = exchange['a']
                qrecs = exchange['aaaa']
                name = exchange['exchange']
                # Interleave the IPv6 and IPv4 addresses for this exchange.
                lim = max(len(arecs), len(qrecs))
                for i in range(0, lim):
                    if i < len(qrecs):
                        addrs.append((qrecs[i], socket.AF_INET6, name))
                    if i < len(arecs):
                        addrs.append((arecs[i], socket.AF_INET, name))

        # Time is of the essence here, because the mail user agent is
        # waiting, and we want to give the user quick feedback, but we
        # also want to follow the rules and not boost our spam score
        # by delivering to a low-preference MX, so we allow about five
        # seconds to complete a connection rather than the usual 90
        # seconds.   We start connecting every five seconds, and take
        # the first connection that completes, dropping the others.
        # It should be rare that a connection takes longer than five
        # seconds to complete if the exchange is reachable.
        connection = yield from self.connect_to_addresses(addrs, 5)

        if connection != None:
            status = yield from self.send_rcptto(connection,
                                                 user + "@" + domain)

            if status:
                self.connections[user + "@" + domain] = connection
                self.connections[domain] = connection
                self.connection_list.append(connection)
            else:
                print("horked in send_rcptto")
            return status
        print("no connection returned.")
        return False
Exemple #9
0
def check_domain(domain):
  try:
    result = yield from resolver.aquery(domain, "txt", raise_on_no_answer=True)
  except:
    return None
  return domain
Exemple #10
0
    def check_host_worker(ipaddr, domain, sender, debug=False):
        limiter[0] = limiter[0] + 1
        if limiter[0] > 10:
            if debug:
                print("DNS query limiter stopped search.")
            return "permerror"

        # RFC 7208 section 4.3: check for invalid domain name
        try:
            name = dns.name.from_text(domain)
        except:
            if debug:
                print("failure:", domain, " is not a valid domain name")
            return "permerror"
        # RFC 7208 section 4.3: check for existence of local-part
        # We are assuming that the caller has produced some
        # vaguely valid sender, either from the MAIL FROM:
        # or HELO/EHLO.   For validating From: headers,
        # combining the source IP address and sender isn't
        # sufficient to say that the use isn't permitted, but
        # it is sufficient to say that the use _is_ permitted.
        if '@' not in sender:
            try:
                sdo = dns.name.from_text(sender)
            except:
                if debug:
                    print("failure:", sender, " is not a valid domain name")
                return "permerror"
            sender = "postmaster@" + sender
        else:
            parts = sender.split("@")
            if len(parts) != 2:
                return "permerror"
            if parts[0] == '':
                sender = "postmaster@" + parts[1]
            try:
                sdo = dns.name.from_text(parts[1])
            except:
                if debug:
                    print("failure:", parts[1], " is not a valid domain name")
                return "permerror"

        spfs = []
        try:
            spfRecord = yield from resolver.aquery(domain,
                                                   "TXT",
                                                   raise_on_no_answer=True)
        except dns.resolver.NoAnswer:
            if debug:
                print(domain, "IN TXT: No Answer")
            return None
        except Exception as x:
            if debug:
                print(domain, "IN TXT:", str(x))
            return "temperror"
        else:
            for rr in spfRecord:
                text = "".join(rr.strings)
                if text.startswith("v=spf1 "):
                    spfs.append(text[7:])
        if len(spfs) == 0:
            if debug:
                print(domain, "IN TXT: No valid SPF record")
            return None
        if len(spfs) > 1:
            if debug:
                print("More than one SPF record found.")
            return "permerror"

        # Terms are separated by exactly one space.
        # terms = *( 1*SP ( directive / modifier ) )
        if debug:
            print(spfs[0])
        terms = spfs[0].split(" ")
        redirect = None
        modifiers = {}
        directives = []
        for term in terms:
            if len(term) == 0:
                if debug:
                    print("syntax error: zero-length term")
                return "permerror"
            # Eliminate the possibility that this is a modifier first,
            # because they are easy to detect.
            elif "=" in term:
                sides = term.split("=")
                if len(sides) != 2:
                    if debug:
                        print("bogus modifier")
                    return "permerror"
                # modifiers can only appear once.
                if sides[0] in modifiers:
                    if debug:
                        print("Duplicate modifier:", sides[0])
                    return "permerror"
                exp = False
                if sides[0] == "exp":
                    exp = True
                expansion = macro_expand(sides[1], ipaddr, domain, sender, exp)
                if expansion == None:
                    return "permerror"
                modifiers[sides[0]] = sides[1]
            else:
                # By default, then, this is a directive.
                directive = Directive(check_host_worker, resolver, debug)
                if directive.validate(term, ipaddr, domain,
                                      sender) == "permerror":
                    return "permerror"
                directives.append(directive)
        for directive in directives:
            status = yield from directive.process(ipaddr, domain, sender)
            # If this directive matched, don't process any later directives.
            if directive.matched:
                if debug:
                    print("Matched:", directive.name)
                return status
            # If this directive returned a definite answer, return that answer
            # (e.g., tempfail, etc.)
            if status != None and status != "neutral":
                if debug:
                    print("Status:", status)
                return status
        # Since we survived the directives, try the modifiers.
        for modifier in modifiers:
            if modifier == "exp":
                # We could really care less.
                pass
            elif modifier == "redirect":
                if debug:
                    print("Redirect:", modifiers[modifier])
                status = yield from check_host_worker(ipaddr,
                                                      modifiers[modifier],
                                                      sender, debug)
                if debug:
                    print("Status:", status)
                return status
        return "neutral"