#include #include "byte.h" #include "case.h" #include "ip.h" #include "scan.h" #include "str.h" #include "stralloc.h" #include "dns.h" #include "ipalloc.h" #include "now.h" #include "spf.h" // shared by spf.c + spfdnsip.c extern stralloc dnsname; extern char ip4remote[4]; extern char ip6remote[16]; extern int flagip6; /** @brief match_ip compares IPv4/IPv6 addreses up to prefix length @param input: ip_address1,prefix length, ip_address2 @return 1 ok; 0 failure */ int match_ip4(unsigned char ip1[4], int prefix, char ip2[4]) { stralloc iptest1 = {0}; stralloc iptest2 = {0}; if (flagip6) return 0; if (ip4_bytestring(&iptest1, ip1, prefix) == prefix) if (ip4_bytestring(&iptest2, ip2, prefix) == prefix) if (byte_diff(iptest1.s, prefix, iptest2.s)) return 0; return 1; } int match_ip6(unsigned char ip1[16], int prefix, char ip2[16]) { stralloc iptest1 = {0}; stralloc iptest2 = {0}; if (!flagip6) return 0; if (ip6_bytestring(&iptest1, ip1, prefix) == prefix) if (ip6_bytestring(&iptest2, ip2, prefix) == prefix) if (byte_diff(iptest1.s, prefix, iptest2.s)) return 0; return 1; } /** @brief get_prefix return integer value of prefix length @param input: pointer to prefix @return (int) length of prefix */ int get_prefix(char *prefix) { unsigned long r; int pos; if (!prefix || *prefix == '0') { if (flagip6 == 0) return 32; if (flagip6 == 1) return 128; } pos = scan_ulong(prefix, &r); if (!pos || (prefix[pos] && !(prefix[pos] == '/'))) return SPF_SYNTAX; if (flagip6 == 0 && r > 32) return SPF_SYNTAX; if (flagip6 == 1 && r > 128) return SPF_SYNTAX; return r; } /* DNS Record: -------------------------------------- Fetch multiple SPF TXT RRs */ /** @brief spf_records get TXT records for domain and extract SPF information @param input: pointer stralloc domain output: pointer to stralloc spf records @return SPF_OK, SPF_NONE; SPF_MULTIRR, SPF_DNSSOFT, SPF_NOMEM */ int spf_records(stralloc *spfrec, stralloc *domain) { static stralloc out = {0}; static stralloc spf = {0}; int i, k; int begin; int r = 0; begin = -1; DNS_INIT r = dns_txt(&out, (const stralloc *)domain); switch (r) { case DNS_MEM: return SPF_NOMEM; case DNS_ERR: return SPF_DNSSOFT; /* return 2main */ case DNS_NXD: return SPF_NONE; } r = SPF_NONE; for (k = 0; k < out.len; ++k) { if (case_starts(out.s + k, "v=spf1")) { begin = k; break; } } if (begin >= 0) { if (case_starts(out.s + k + 6, "v=spf1")) return SPF_MULTIRR; /* return 2main */ if (!stralloc_copys(&spf, "")) return SPF_NOMEM; for (i = begin; i < out.len; ++i) { if (out.s[i] == '\r' || out.s[i] == '\n' || out.s[i] == '\0') break; if (!stralloc_append(&spf, out.s + i)) return SPF_NOMEM; } if (!stralloc_0(&spf)) return SPF_NOMEM; if (!stralloc_copys(spfrec, spf.s)) return SPF_NOMEM; r = SPF_OK; } return r; } /* Mechanisms: -------------------------------------- Lookup functions */ /** @brief spf_a (a; a:fqdns; a:fqdns/56) compares A + AAAA records for SPF info and client host @param input: pointer to spfspecification, pointer to prefix @return SPF_OK, SPF_NONE; SPF_DNSSOFT, SPF_NOMEM */ int spf_a(char *spfspec, char *prefix) { stralloc sa = {0}; stralloc ip = {0}; int ipprefix, r, j; ipprefix = get_prefix(prefix); if (ipprefix < 0) return SPF_SYNTAX; if (!stralloc_copys(&sa, spfspec)) return SPF_NOMEM; if (!stralloc_readyplus(&ip, 0)) return SPF_NOMEM; if (!spf_info("MA/AAAA=", spfspec)) return SPF_NOMEM; DNS_INIT switch (dns_ip4(&ip, &sa)) { case DNS_MEM: return SPF_NOMEM; case DNS_ERR: r = SPF_DNSSOFT; break; case DNS_NXD: r = SPF_NONE; break; default: r = SPF_NONE; for (j = 0; j + 4 <= ip.len; j += 4) if (match_ip4(ip.s + j, ipprefix, ip4remote)) return SPF_OK; } switch (dns_ip6(&ip, &sa)) { case DNS_MEM: return SPF_NOMEM; case DNS_ERR: r = SPF_DNSSOFT; break; case DNS_NXD: r = SPF_NONE; break; default: r = SPF_NONE; for (j = 0; j + 16 <= ip.len; j += 16) if (match_ip6(ip.s + j, ipprefix, ip6remote)) return SPF_OK; } return r; } /** @brief spf_mx (mx; mx:domain; mx:domain/24) compares MX records for SPF info and client host @param input: pointer to spfspecification, pointer to prefix @return SPF_OK, SPF_NONE; SPF_DNSSOFT, SPF_NOMEM */ int spf_mx(char *spfspec, char *prefix) { stralloc sa = {0}; ipalloc ia = {0}; unsigned long random; int ipprefix; int j, r; ipprefix = get_prefix(prefix); if (ipprefix < 0) return SPF_SYNTAX; random = now() + (getpid() << 16); if (!stralloc_copys(&sa, spfspec)) return SPF_NOMEM; if (!spf_info("MMX=", spfspec)) return SPF_NOMEM; switch (dns_mxip(&ia, &sa, random)) { case DNS_MEM: return SPF_NOMEM; case DNS_ERR: return SPF_DNSSOFT; default: r = SPF_NONE; for (j = 0; j < ia.len; ++j) { if (byte_diff(ip6remote, 16, V6localnet) && !ip6_isv4mapped(ip6remote)) { if (match_ip6(ia.ix[j].addr.ip6.d, ipprefix, ip6remote)) return SPF_OK; } if (byte_diff(ip4remote, 4, V4localnet)) { if (match_ip4(ia.ix[j].addr.ip4.d, ipprefix, ip4remote)) return SPF_OK; } } } return r; } /** @brief spf_ptr (ptr; ptr:fqdn) compares PTR records from SPF info and client host @param input: pointer to spfspecification; prefix not used @return SPF_OK, SPF_NONE; SPF_DNSSOFT, SPF_NOMEM */ int spf_ptr(char *spfspec, char *prefix) { stralloc fqdn = {0}; stralloc out = {0}; stralloc ip = {0}; int slen = str_len(spfspec); int rc, r; int k = 0; int pos; int l = 0; /* we didn't find host with the matching IP before */ if (dnsname.len == 7 && str_equal(dnsname.s, "unknown")) return SPF_NONE; if (!spf_info("MPTR=", spfspec)) return SPF_NOMEM; /* the hostname found will probably be the same as before */ while (dnsname.len) { pos = dnsname.len - slen; if (pos < 0) break; if (pos > 0 && dnsname.s[pos - 1] != '.') break; if (case_diffb(dnsname.s + pos, slen, spfspec)) break; return SPF_OK; } /* ok, either it's the first test or it's a very weired setup Assumptions: ip -> inverse DNS name (only one!) inverse DNS name -> (same) ip (only one!) */ if (!stralloc_readyplus(&fqdn, 255)) return SPF_NOMEM; if (!stralloc_readyplus(&out, 255)) return SPF_NOMEM; if (!stralloc_readyplus(&ip, 32)) return SPF_NOMEM; if (flagip6) { rc = dns_name6(&out, ip6remote); // usually: 2. . .ip6.addr => only one switch (rc) { case DNS_MEM: return SPF_NOMEM; case DNS_COM: r = SPF_DNSSOFT; break; case DNS_ERR: r = SPF_NONE; break; case DNS_NXD: r = SPF_NONE; break; default: r = SPF_NONE; l++; if (l > LOOKUP_LIMIT) { r = SPF_ERROR; break; } switch (dns_ip6(&ip, &out)) { // theoretical more IPs cound be retrieved case DNS_MEM: return SPF_NOMEM; case DNS_ERR: r = SPF_DNSSOFT; break; case DNS_NXD: r = SPF_NONE; break; default: r = SPF_NONE; for (k = 0; k + 16 <= ip.len; k += 16) { if (k > 32 * LOOKUP_LIMIT) { r = SPF_ERROR; break; } if (match_ip6(ip.s + k, 128, ip6remote)) { if (!dnsname.len) if (!stralloc_copy(&dnsname, &out)) return SPF_NOMEM; pos = out.len - slen; if (pos < 0) continue; if (pos > 0 && out.s[pos - 1] != '.') continue; if (case_diffb(out.s + pos, slen, spfspec)) continue; if (!stralloc_copy(&dnsname, &out)) return SPF_NOMEM; r = SPF_OK; } } } } } else { // IP4 branch rc = dns_name4(&out, ip4remote); // usual answer: d.c.b.e.in-arpa.addr for IP4 a.b.c.d => only one switch (rc) { case DNS_MEM: return SPF_NOMEM; case DNS_ERR: r = SPF_DNSSOFT; break; case DNS_NXD: r = SPF_NONE; break; default: r = SPF_NONE; l++; if (l > LOOKUP_LIMIT) { r = SPF_ERROR; break; } switch (dns_ip4(&ip, &out)) { case DNS_MEM: return SPF_NOMEM; case DNS_ERR: r = SPF_DNSSOFT; break; case DNS_NXD: r = SPF_NONE; break; default: r = SPF_NONE; for (k = 0; k + 4 <= ip.len; k += 4) { if (k > 32 * LOOKUP_LIMIT) { r = SPF_ERROR; break; } if (match_ip4(ip.s + k, 32, ip4remote)) { if (!dnsname.len) if (!stralloc_copy(&dnsname, &out)) return SPF_NOMEM; pos = out.len - slen; if (pos < 0) continue; if (pos > 0 && out.s[pos - 1] != '.') continue; if (case_diffb(out.s + pos, slen, spfspec)) continue; if (!stralloc_copy(&dnsname, &out)) return SPF_NOMEM; r = SPF_OK; } } } } } if (!dnsname.len) if (!stralloc_copys(&dnsname, "unknown")) return SPF_NOMEM; return r; } /** @brief spf_ip4 (ip4; ip4:fqdn; ip4:fqdn/24) compares A records for SPF info and client host @param input: pointer to spfspecification, pointer to prefix @return SPF_OK, SPF_NONE; SPF_DNSSOFT, SPF_NOMEM */ int spf_ip4(char *spfspec, char *prefix) { char spfip[4]; if (flagip6) return SPF_NONE; int ipprefix = get_prefix(prefix); if (ipprefix < 0) return SPF_SYNTAX; if (!ip4_scan(spfspec, spfip)) return SPF_SYNTAX; if (!spf_info("MIPv4=", spfspec)) return SPF_NOMEM; if (!match_ip4(spfip, ipprefix, ip4remote)) return SPF_NONE; return SPF_OK; } /** @brief spf_ip6 (ip6; ip6:fqdn; ip6:fqdn/56) compares AAAA records for SPF info and client host @param input: pointer to spfspecification, pointer to prefix @return SPF_OK, SPF_NONE; SPF_DNSSOFT, SPF_NOMEM */ int spf_ip6(char *spfspec, char *prefix) { char spfip[16]; if (!flagip6) return SPF_NONE; int ipprefix = get_prefix(prefix); if (ipprefix < 0) return SPF_SYNTAX; if (!ip6_scan(spfspec, spfip)) return SPF_SYNTAX; if (!spf_info("MIPv6=", spfspec)) return SPF_NOMEM; if (!match_ip6(spfip, ipprefix, ip6remote)) return SPF_NONE; return SPF_OK; } /** @brief spf_exists (exists; exists:fqdn) simply looks for a A records only for SPF info and client host @param input: pointer to spfspecification, prefix not used @return SPF_OK, SPF_NONE; SPF_DNSSOFT, SPF_NOMEM */ int spf_exists(char *spfspec, char *prefix) { stralloc sa = {0}; stralloc ip = {0}; if (!stralloc_copys(&sa, spfspec)) return SPF_NOMEM; if (!spf_info("MExists=", spfspec)) return SPF_NOMEM; switch (dns_ip4(&ip, &sa)) { case DNS_MEM: return SPF_NOMEM; case DNS_ERR: return SPF_DNSSOFT; case DNS_NXD: return SPF_NONE; default: return SPF_OK; } }