From 89b7b67a13ebb7965cc7f13ad0595e2194a2d34c Mon Sep 17 00:00:00 2001 From: Jannis Hoffmann Date: Wed, 3 Jul 2024 15:48:04 +0200 Subject: add sqmail-4.2.29a --- src/spfdnsip.c | 406 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 406 insertions(+) create mode 100644 src/spfdnsip.c (limited to 'src/spfdnsip.c') diff --git a/src/spfdnsip.c b/src/spfdnsip.c new file mode 100644 index 0000000..e9cf9ee --- /dev/null +++ b/src/spfdnsip.c @@ -0,0 +1,406 @@ +#include +#include "stralloc.h" +#include "alloc.h" +#include "ip.h" +#include "ipalloc.h" +#include "ipme.h" +#include "str.h" +#include "fmt.h" +#include "scan.h" +#include "byte.h" +#include "now.h" +#include "dns.h" +#include "case.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; + } + +} -- cgit v1.2.3