From 5ca4a0e5b65066f41af7c670e8200b0d0eba41dd Mon Sep 17 00:00:00 2001 From: Laurent Bercot Date: Fri, 6 Feb 2026 02:16:08 +0000 Subject: Just name it qmail-remote after all --- package/deps.mak | 26 +-- package/modes | 2 +- package/targets.mak | 2 +- src/qmail-remote/deps-exe/qmail-remote | 6 + src/qmail-remote/deps-lib/qmailr | 6 + src/qmail-remote/dns.c | 315 +++++++++++++++++++++++++++++++++ src/qmail-remote/qmail-remote.c | 86 +++++++++ src/qmail-remote/qmail-remote.h | 46 +++++ src/qmail-remote/qmailr.h | 66 +++++++ src/qmail-remote/qmailr_control.c | 82 +++++++++ src/qmail-remote/qmailr_error.c | 38 ++++ src/qmail-remote/qmailr_tcpto.c | 157 ++++++++++++++++ src/qmail-remote/qmailr_tls.c | 47 +++++ src/qmail-remote/qmailr_utils.c | 60 +++++++ src/qmail-remote/smtproutes.c | 240 +++++++++++++++++++++++++ src/qmail-smtpc/deps-exe/qmail-smtpc | 6 - src/qmail-smtpc/deps-lib/qmailr | 6 - src/qmail-smtpc/dns.c | 315 --------------------------------- src/qmail-smtpc/qmail-smtpc.c | 86 --------- src/qmail-smtpc/qmail-smtpc.h | 46 ----- src/qmail-smtpc/qmailr.h | 66 ------- src/qmail-smtpc/qmailr_control.c | 82 --------- src/qmail-smtpc/qmailr_error.c | 38 ---- src/qmail-smtpc/qmailr_tcpto.c | 157 ---------------- src/qmail-smtpc/qmailr_tls.c | 47 ----- src/qmail-smtpc/qmailr_utils.c | 60 ------- src/qmail-smtpc/smtproutes.c | 240 ------------------------- 27 files changed, 1164 insertions(+), 1164 deletions(-) create mode 100644 src/qmail-remote/deps-exe/qmail-remote create mode 100644 src/qmail-remote/deps-lib/qmailr create mode 100644 src/qmail-remote/dns.c create mode 100644 src/qmail-remote/qmail-remote.c create mode 100644 src/qmail-remote/qmail-remote.h create mode 100644 src/qmail-remote/qmailr.h create mode 100644 src/qmail-remote/qmailr_control.c create mode 100644 src/qmail-remote/qmailr_error.c create mode 100644 src/qmail-remote/qmailr_tcpto.c create mode 100644 src/qmail-remote/qmailr_tls.c create mode 100644 src/qmail-remote/qmailr_utils.c create mode 100644 src/qmail-remote/smtproutes.c delete mode 100644 src/qmail-smtpc/deps-exe/qmail-smtpc delete mode 100644 src/qmail-smtpc/deps-lib/qmailr delete mode 100644 src/qmail-smtpc/dns.c delete mode 100644 src/qmail-smtpc/qmail-smtpc.c delete mode 100644 src/qmail-smtpc/qmail-smtpc.h delete mode 100644 src/qmail-smtpc/qmailr.h delete mode 100644 src/qmail-smtpc/qmailr_control.c delete mode 100644 src/qmail-smtpc/qmailr_error.c delete mode 100644 src/qmail-smtpc/qmailr_tcpto.c delete mode 100644 src/qmail-smtpc/qmailr_tls.c delete mode 100644 src/qmail-smtpc/qmailr_utils.c delete mode 100644 src/qmail-smtpc/smtproutes.c diff --git a/package/deps.mak b/package/deps.mak index e60d782..1821dba 100644 --- a/package/deps.mak +++ b/package/deps.mak @@ -2,24 +2,24 @@ # This file has been generated by tools/gen-deps.sh # -src/qmail-smtpc/qmail-smtpc.h: src/qmail-smtpc/qmailr.h -src/qmail-smtpc/dns.o src/qmail-smtpc/dns.lo: src/qmail-smtpc/dns.c src/qmail-smtpc/qmail-smtpc.h src/qmail-smtpc/qmailr.h -src/qmail-smtpc/qmail-smtpc.o src/qmail-smtpc/qmail-smtpc.lo: src/qmail-smtpc/qmail-smtpc.c src/qmail-smtpc/qmail-smtpc.h src/qmail-smtpc/qmailr.h src/include/smtpd-starttls-proxy/config.h -src/qmail-smtpc/qmailr_control.o src/qmail-smtpc/qmailr_control.lo: src/qmail-smtpc/qmailr_control.c src/qmail-smtpc/qmailr.h -src/qmail-smtpc/qmailr_error.o src/qmail-smtpc/qmailr_error.lo: src/qmail-smtpc/qmailr_error.c src/include/smtpd-starttls-proxy/config.h -src/qmail-smtpc/qmailr_tcpto.o src/qmail-smtpc/qmailr_tcpto.lo: src/qmail-smtpc/qmailr_tcpto.c src/qmail-smtpc/qmailr.h src/include/smtpd-starttls-proxy/config.h -src/qmail-smtpc/qmailr_tls.o src/qmail-smtpc/qmailr_tls.lo: src/qmail-smtpc/qmailr_tls.c src/qmail-smtpc/qmailr.h src/include/smtpd-starttls-proxy/config.h -src/qmail-smtpc/qmailr_utils.o src/qmail-smtpc/qmailr_utils.lo: src/qmail-smtpc/qmailr_utils.c src/qmail-smtpc/qmailr.h -src/qmail-smtpc/smtproutes.o src/qmail-smtpc/smtproutes.lo: src/qmail-smtpc/smtproutes.c src/qmail-smtpc/qmail-smtpc.h src/qmail-smtpc/qmailr.h src/include/smtpd-starttls-proxy/config.h +src/qmail-remote/qmail-remote.h: src/qmail-remote/qmailr.h +src/qmail-remote/dns.o src/qmail-remote/dns.lo: src/qmail-remote/dns.c src/qmail-remote/qmail-remote.h src/qmail-remote/qmailr.h +src/qmail-remote/qmail-remote.o src/qmail-remote/qmail-remote.lo: src/qmail-remote/qmail-remote.c src/qmail-remote/qmail-remote.h src/qmail-remote/qmailr.h src/include/smtpd-starttls-proxy/config.h +src/qmail-remote/qmailr_control.o src/qmail-remote/qmailr_control.lo: src/qmail-remote/qmailr_control.c src/qmail-remote/qmailr.h +src/qmail-remote/qmailr_error.o src/qmail-remote/qmailr_error.lo: src/qmail-remote/qmailr_error.c src/include/smtpd-starttls-proxy/config.h +src/qmail-remote/qmailr_tcpto.o src/qmail-remote/qmailr_tcpto.lo: src/qmail-remote/qmailr_tcpto.c src/qmail-remote/qmailr.h src/include/smtpd-starttls-proxy/config.h +src/qmail-remote/qmailr_tls.o src/qmail-remote/qmailr_tls.lo: src/qmail-remote/qmailr_tls.c src/qmail-remote/qmailr.h src/include/smtpd-starttls-proxy/config.h +src/qmail-remote/qmailr_utils.o src/qmail-remote/qmailr_utils.lo: src/qmail-remote/qmailr_utils.c src/qmail-remote/qmailr.h +src/qmail-remote/smtproutes.o src/qmail-remote/smtproutes.lo: src/qmail-remote/smtproutes.c src/qmail-remote/qmail-remote.h src/qmail-remote/qmailr.h src/include/smtpd-starttls-proxy/config.h src/smtpd-starttls-proxy/smtpd-starttls-proxy-io.o src/smtpd-starttls-proxy/smtpd-starttls-proxy-io.lo: src/smtpd-starttls-proxy/smtpd-starttls-proxy-io.c ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),) -libqmailr.a.xyzzy: src/qmail-smtpc/qmailr_control.o src/qmail-smtpc/qmailr_error.o src/qmail-smtpc/qmailr_tcpto.o src/qmail-smtpc/qmailr_tls.o src/qmail-smtpc/qmailr_utils.o +libqmailr.a.xyzzy: src/qmail-remote/qmailr_control.o src/qmail-remote/qmailr_error.o src/qmail-remote/qmailr_tcpto.o src/qmail-remote/qmailr_tls.o src/qmail-remote/qmailr_utils.o else -libqmailr.a.xyzzy:src/qmail-smtpc/qmailr_control.lo src/qmail-smtpc/qmailr_error.lo src/qmail-smtpc/qmailr_tcpto.lo src/qmail-smtpc/qmailr_tls.lo src/qmail-smtpc/qmailr_utils.lo +libqmailr.a.xyzzy:src/qmail-remote/qmailr_control.lo src/qmail-remote/qmailr_error.lo src/qmail-remote/qmailr_tcpto.lo src/qmail-remote/qmailr_tls.lo src/qmail-remote/qmailr_utils.lo endif -qmail-smtpc: EXTRA_LIBS := -qmail-smtpc: src/qmail-smtpc/qmail-smtpc.o src/qmail-smtpc/dns.o src/qmail-smtpc/smtproutes.o libqmailr.a.xyzzy -lskadns -ls6dns -lskarnet +qmail-remote: EXTRA_LIBS := +qmail-remote: src/qmail-remote/qmail-remote.o src/qmail-remote/dns.o src/qmail-remote/smtproutes.o libqmailr.a.xyzzy -lskadns -ls6dns -lskarnet smtpd-starttls-proxy-io: EXTRA_LIBS := ${SOCKET_LIB} ${SYSCLOCK_LIB} smtpd-starttls-proxy-io: src/smtpd-starttls-proxy/smtpd-starttls-proxy-io.o -lskarnet INTERNAL_LIBS := libqmailr.a.xyzzy diff --git a/package/modes b/package/modes index e22534f..c005314 100644 --- a/package/modes +++ b/package/modes @@ -1,2 +1,2 @@ smtpd-starttls-proxy-io 0755 -qmail-smtpc 0755 +qmail-remote 0755 diff --git a/package/targets.mak b/package/targets.mak index c4707f9..917dfad 100644 --- a/package/targets.mak +++ b/package/targets.mak @@ -1,6 +1,6 @@ BIN_TARGETS := \ smtpd-starttls-proxy-io \ -qmail-smtpc +qmail-remote LIBEXEC_TARGETS := diff --git a/src/qmail-remote/deps-exe/qmail-remote b/src/qmail-remote/deps-exe/qmail-remote new file mode 100644 index 0000000..4484f9b --- /dev/null +++ b/src/qmail-remote/deps-exe/qmail-remote @@ -0,0 +1,6 @@ +dns.o +smtproutes.o +libqmailr.a.xyzzy +-lskadns +-ls6dns +-lskarnet diff --git a/src/qmail-remote/deps-lib/qmailr b/src/qmail-remote/deps-lib/qmailr new file mode 100644 index 0000000..c1095ed --- /dev/null +++ b/src/qmail-remote/deps-lib/qmailr @@ -0,0 +1,6 @@ +qmailr_control.o +qmailr_error.o +qmailr_tcpto.o +qmailr_tls.o +qmailr_utils.o +-lskarnet diff --git a/src/qmail-remote/dns.c b/src/qmail-remote/dns.c new file mode 100644 index 0000000..4649fc7 --- /dev/null +++ b/src/qmail-remote/dns.c @@ -0,0 +1,315 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "qmailr.h" +#include "qmail-remote.h" + +typedef struct cnameinfo_s cnameinfo, *cnameinfo_ref ; +struct cnameinfo_s +{ + stralloc sa ; + size_t atpos ; + uint16_t id ; + uint16_t count ; +} ; + +typedef struct mxipinfo_s mxipinfo, mxipinfo_ref ; +struct mxipinfo_s +{ + stralloc ip4 ; + stralloc ip6 ; + uint16_t id4 ; + uint16_t id6 ; +} ; +#define MXIPINFO_ZERO { .ip4 = STRALLOC_ZERO, .ip6 = STRALLOC_ZERO, .id4 = UINT16_MAX, .id6 = UINT16_MAX } + +static int mx_cmp (void const *a, void const *b) +{ + s6dns_message_rr_mx_t const *aa = a ; + s6dns_message_rr_mx_t const *bb = b ; + return aa->preference < bb-> preference ? -1 : aa->preference > bb->preference ; +} + +static unsigned int use_host_as_mx (skadns_t *a, char const *host, genalloc *mxip, tain const *deadline) +{ + unsigned int newreqs = 0 ; + mxipinfo info = MXIPINFO_ZERO ; + s6dns_domain_t q ; + if (!s6dns_domain_fromstring_noqualify_encode(&q, host, strlen(host))) + qmailr_tempsys("Unable to DNS-encode host domain") ; + if (!skadns_send_g(a, &info.id4, &q, S6DNS_T_A, deadline, deadline)) + qmailr_tempsys("Unable to send A DNS query") ; + newreqs++ ; +#ifdef SKALIBS_IPV6_ENABLED + if (!skadns_send_g(a, &info.id6, &q, S6DNS_T_AAAA, deadline, deadline)) + qmailr_tempsys("Unable to send AAAA DNS query") ; + newreqs++ ; +#endif + if (!genalloc_catb(mxipinfo, mxip, &info, 1)) dienomem() ; + return newreqs ; +} + + /* + The point of this monster here is to do all the DNS resolutions in parallel, + to avoid compounding network latency. One of the many things that could never + be done by patching qmail-remote. + 1 sender + n-1 recipients are given in eaddr. + - loop around CNAME until we get the canonical name, for the n eaddrs + - either lookup the MX for the host then find all the A and AAAAs of all the MXes, + or get the A and AAAAs of the host directly (if smtproutes) + - do not keep the As and AAAAs listed in ipme + - sort the set of IPs by MX preference + When done, quote all the boxnames in eaddr. + Shove everything in storage and return the indices: + in eaddrpos for sender+recipients, in mxipind for the IPs to connect to. + + Also, fuck DNS for requiring so many small allocations and data copies. + + Also, fuck DNS. + */ + +void dns_stuff (char const *host, char const *const *eaddr, unsigned int n, size_t *eaddrpos, genalloc *mxipind, stralloc *storage, unsigned int timeoutdns, char const *ipme4, unsigned int n4, char const *ipme6, unsigned int n6, uint32_t flags) +{ + skadns_t a = SKADNS_ZERO ; + genalloc mxipi = GENALLOC_ZERO ; /* mxipinfo */ + tain deadline ; + unsigned int pending = 0 ; + uint16_t mxn = 0 ; + uint16_t mxid = UINT16_MAX ; + cnameinfo cnames[n] ; + + tain_addsec_g(&deadline, timeoutdns) ; + if (!skadns_startf_g(&a, &deadline)) + qmailr_tempsys("Unable to start asynchronous DNS helper") ; + + for (unsigned int i = 0 ; i < n ; i++) + { + char const *at = strrchr(eaddr[i], '@') ; + cnames[i].sa = stralloc_zero ; + if (at) + { + s6dns_domain_t q ; + size_t len = strlen(at+1) ; + cnames[i].atpos = at - eaddr[i] ; + if (!stralloc_catb(&cnames[i].sa, at+1, len)) dienomem() ; + if (!s6dns_domain_fromstring_noqualify_encode(&q, at+1, len)) + qmailr_tempsys("Unable to DNS-encode recipient domain") ; + if (!skadns_send_g(&a, &cnames[i].id, &q, S6DNS_T_CNAME, &deadline, &deadline)) + qmailr_tempsys("Unable to send CNAME DNS query") ; + cnames[i].count = 1 ; + pending++ ; + } + else + { + cnames[i].id = UINT16_MAX ; + cnames[i].count = 0 ; + cnames[i].atpos = strlen(eaddr[i]) ; + } + } + + if (flags & 1) + { + s6dns_domain_t q ; + if (!s6dns_domain_fromstring_noqualify_encode(&q, host, strlen(host))) + qmailr_tempsys("Unable to DNS-encode host domain") ; + if (!skadns_send_g(&a, &mxid, &q, S6DNS_T_MX, &deadline, &deadline)) + qmailr_tempsys("Unable to send MX DNS query") ; + pending++ ; + } + else + { + mxn = 1 ; + pending += use_host_as_mx(&a, host, &mxipi, &deadline) ; + } + + while (pending) + { + uint16_t *ids ; + iopause_fd x = { .fd = skadns_fd(&a), .events = IOPAUSE_READ } ; + int r = iopause_g(&x, 1, &deadline) ; + if (r == -1) qmailr_tempsys("Unable to iopause") ; + if (!r) qmailr_tempsys("Timed out waiting for DNS") ; + r = skadns_update(&a) ; + if (r == -1) qmailr_tempsys("Unable to read DNS answers") ; + ids = genalloc_s(uint16_t, &a.list) ; + for (size_t j = 0 ; j < genalloc_len(uint16_t, &a.list) ; j++) + { + char const *packet = skadns_packet(&a, ids[j]) ; + uint16_t packetlen = skadns_packetlen(&a, ids[j]) ; + if (!packet) qmailr_tempsys("DNS packet reading error") ; + + if (ids[j] == mxid) /* return from MX query */ + { + s6dns_message_header_t h ; + genalloc mxes = GENALLOC_ZERO ; /* s6dns_message_rr_mx_t */ + r = s6dns_message_parse(&h, packet, packetlen, &s6dns_message_parse_answer_mx, &mxes) ; + if (r == -1) qmailr_tempsys("DNS packet parsing error") ; + if (!r) + { + if (errno == EBUSY || errno == EIO) qmailr_temp("Temporary DNS error while resolving MX") ; + else qmailr_perm("DNS CNAME resolution error") ; + } + skadns_release(&a, ids[j]) ; + pending-- ; + mxid = UINT16_MAX ; + if (r >= 2) /* we have MXes, ask for their IPs */ + { + s6dns_message_rr_mx_t *mxs = genalloc_s(s6dns_message_rr_mx_t, &mxes) ; + mxn = genalloc_len(s6dns_message_rr_mx_t, &mxes) ; + if (!genalloc_readyplus(mxipinfo, &mxipi, mxn)) dienomem() ; + qsort(mxs, mxn, sizeof(s6dns_message_rr_mx_t), &mx_cmp) ; + for (unsigned int i = 0 ; i < mxn ; i++) + { + mxipinfo *p = genalloc_s(mxipinfo, &mxipi) + i ; + p->ip4 = p->ip6 = stralloc_zero ; + if (!skadns_send_g(&a, &p->id4, &mxs[i].exchange, S6DNS_T_A, &deadline, &deadline)) + qmailr_tempsys("Unable to send A DNS query") ; + pending++ ; +#ifdef SKALIBS_IPV6_ENABLED + if (!skadns_send_g(&a, &p->id6, &mxs[i].exchange, S6DNS_T_AAAA, &deadline, &deadline)) + qmailr_tempsys("Unable to send AAAA DNS query") ; + pending++ ; +#endif + } + genalloc_free(s6dns_message_rr_mx_t, &mxes) ; + } + else + { + mxn = 1 ; + pending += use_host_as_mx(&a, host, &mxipi, &deadline) ; + } + continue ; + } + + for (unsigned int i = 0 ; i < n ; i++) if (ids[j] == cnames[i].id) /* return from CNAME query */ + { + s6dns_message_header_t h ; + s6dns_dpag_t dlist = { .ds = GENALLOC_ZERO, .rtype = S6DNS_T_CNAME } ; + r = s6dns_message_parse(&h, packet, packetlen, &s6dns_message_parse_answer_domain, &dlist) ; + if (r == -1) qmailr_tempsys("DNS packet parsing error") ; + if (!r) + { + if (errno == EBUSY || errno == EIO) qmailr_temp("Temporary DNS error while resolving CNAME") ; + else qmailr_perm("DNS CNAME resolution error") ; + } + skadns_release(&a, ids[j]) ; + pending-- ; + if (r >= 2) /* it's a CNAME, loop on it */ + { + s6dns_domain_t *domain = genalloc_s(s6dns_domain_t, &dlist.ds) ; + if (cnames[i].count++ >= 100) qmailr_temp("DNS CNAME loop") ; + if (!skadns_send_g(&a, &cnames[i].id, domain, S6DNS_T_CNAME, &deadline, &deadline)) + qmailr_tempsys("Unable to send CNAME DNS query") ; + pending++ ; + if (!stralloc_ready(&cnames[i].sa, 256)) dienomem() ; + s6dns_domain_decode(domain) ; + cnames[i].sa.len = s6dns_domain_tostring(cnames[i].sa.s, 256, domain) ; + genalloc_free(s6dns_domain_t, &dlist.ds) ; + } + else cnames[i].id = UINT16_MAX ; /* that's the canonical host in cnames[i].sa */ + continue ; + } + + for (unsigned int i = 0 ; i < mxn ; i++) + { + mxipinfo *p = genalloc_s(mxipinfo, &mxipi) + i ; + if (ids[j] == p->id4) + { + s6dns_message_header_t h ; + r = s6dns_message_parse(&h, packet, packetlen, &s6dns_message_parse_answer_a, &p->ip4) ; + if (r == -1) qmailr_tempsys("DNS packet parsing error") ; + if (!r) + { + if (errno == EBUSY || errno == EIO) qmailr_temp("Temporary DNS error while resolving A") ; + else qmailr_perm("DNS A resolution error") ; + } + skadns_release(&a, ids[j]) ; + pending-- ; + p->id4 = UINT16_MAX ; + for (unsigned int k = 0 ; k < p->ip4.len ; k += 4) + { + if (bsearch(p->ip4.s + k, ipme4, n4, 4, &qmailr_memcmp4)) + { + memmove(p->ip4.s + k, p->ip4.s + p->ip4.len - 4, 4) ; + p->ip4.len -= 4 ; + k -= 4 ; + } + } + } +#ifdef SKALIBS_IPV6_ENABLED + else if (ids[j] == p->id6) + { + s6dns_message_header_t h ; + r = s6dns_message_parse(&h, packet, packetlen, &s6dns_message_parse_answer_aaaa, &p->ip6) ; + if (r == -1) qmailr_tempsys("DNS packet parsing error") ; + if (!r) + { + if (errno == EBUSY || errno == EIO) qmailr_temp("Temporary DNS error while resolving AAAA") ; + else qmailr_perm("DNS AAAA resolution error") ; + } + skadns_release(&a, ids[j]) ; + pending-- ; + p->id6 = UINT16_MAX ; + for (unsigned int k = 0 ; k < p->ip6.len ; k += 16) + { + if (bsearch(p->ip6.s + k, ipme6, n6, 16, &qmailr_memcmp16)) + { + memmove(p->ip6.s + k, p->ip6.s + p->ip6.len - 16, 16) ; + p->ip6.len -= 16 ; + k -= 16 ; + } + } + } +#endif + } + } + } + skadns_end(&a) ; /* we done buddy */ + + for (unsigned int i = 0 ; i < n ; i++) + { + eaddrpos[i] = storage->len ; + if (!qmailr_box_encode(eaddr[i], cnames[i].atpos, storage)) dienomem() ; + if (cnames[i].count) + { + if (!stralloc_catb(storage, "@", 1)) dienomem() ; + if (!stralloc_catb(storage, cnames[i].sa.s, cnames[i].sa.len)) dienomem() ; + stralloc_free(&cnames[i].sa) ; + } + if (!stralloc_0(storage)) dienomem() ; + } + + if (!genalloc_readyplus(mxip, mxipind, mxn)) dienomem() ; + for (unsigned int i = 0 ; i < mxn ; i++) + { + mxip data ; + mxipinfo *p = genalloc_s(mxipinfo, &mxipi) + i ; + data.n4 = p->ip4.len >> 2 ; + data.pos4 = storage->len ; + if (!stralloc_catb(storage, p->ip4.s, p->ip4.len)) dienomem() ; + stralloc_free(&p->ip4) ; +#ifdef SKALIBS_IPV6_ENABLED + data.n6 = p->ip6.len >> 4 ; + data.pos6 = storage->len ; + if (!stralloc_catb(storage, p->ip6.s, p->ip6.len)) dienomem() ; + stralloc_free(&p->ip6) ; + genalloc_catb(mxip, mxipind, &data, 1) ; +#endif + } + genalloc_free(mxipinfo, &mxipi) ; +} diff --git a/src/qmail-remote/qmail-remote.c b/src/qmail-remote/qmail-remote.c new file mode 100644 index 0000000..870c797 --- /dev/null +++ b/src/qmail-remote/qmail-remote.c @@ -0,0 +1,86 @@ +/* ISC license. */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include "qmailr.h" +#include "qmail-remote.h" + +#define dieusage() qmailr_perm("qmail-remote was invoked improperly") + +int main (int argc, char const *const *argv) +{ + stralloc storage = STRALLOC_ZERO ; + stralloc ipme4 = STRALLOC_ZERO ; + stralloc ipme6 = STRALLOC_ZERO ; + qmailr_tls qt = QMAILR_TLS_ZERO ; + smtproutes routes = SMTPROUTES_ZERO ; + unsigned int timeoutconnect = 60, timeoutremote = 1200 ; + char const *host ; + size_t mepos, helopos, hostpos = 0 ; + uint16_t port = 25 ; + int r ; + + if (argc-- < 4) dieusage() ; + argv++ ; + if (chdir(SMTPD_STARTTLS_PROXY_QMAIL_HOME) == -1) qmailr_temp("Unable to chdir to " SMTPD_STARTTLS_PROXY_QMAIL_HOME) ; + if (sig_altignore(SIGPIPE) == -1) qmailr_tempsys("Unable to ignore SIGPIPE") ; + host = *argv++ ; argc-- ; + tain_now_set_stopwatch_g() ; + + + /* init control */ + + r = qmailr_control_read("control/me", &storage, &mepos) ; + if (r == -1) qmailr_tempsys("Unable to read control/me") ; + else if (!r) qmailr_temp("Invalid control/me") ; + + r = qmailr_control_read("control/helohost", &storage, &helopos) ; + if (r == -1) qmailr_tempsys("Unable to read control/helohost") ; + else if (!r) helopos = mepos ; + + r = qmailr_control_readint("control/timeoutconnect", &timeoutconnect, &storage) ; + if (r == -1) qmailr_tempsys("Unable to read control/timeoutconnect") ; + r = qmailr_control_readint("control/timeoutremote", &timeoutremote, &storage) ; + if (r == -1) qmailr_tempsys("Unable to read control/timeoutremote") ; + + if (!qmailr_control_readiplist("control/ipme", &ipme4, &ipme6)) + qmailr_tempsys("Unable to read control/ipme") ; + stralloc_shrink(&ipme4) ; + stralloc_shrink(&ipme6) ; + qsort(ipme4.s, ipme4.len >> 2, 4, &qmailr_memcmp4) ; + qsort(ipme6.s, ipme6.len >> 4, 16, &qmailr_memcmp16) ; + + if (!qmailr_tls_init(&qt, &storage)) + qmailr_tempsys("Unable to read TLS control files") ; + + if (smtproutes_init(&routes)) + { + if (!smtproutes_match(&routes, host, &storage, &hostpos, &port)) + { + size_t hostlen = strlen(host) ; + for (size_t i = 0 ; i < hostlen ; i++) if (host[i] == '.') + if (smtproutes_match(&routes, argv[1], &storage, &hostpos, &port)) break ; + if (!hostpos) smtproutes_match(&routes, "", &storage, &hostpos, &port) ; + } + smtproutes_free(&routes) ; + } + + { + genalloc mxipind = GENALLOC_ZERO ; + size_t eaddrpos[argc] ; + dns_stuff(hostpos ? storage.s + hostpos : host, argv, argc, eaddrpos, &mxipind, &storage, timeoutconnect, ipme4.s, ipme4.len >> 2, ipme6.s, ipme6.len >> 4, !hostpos) ; + + } + + + _exit(0) ; +} diff --git a/src/qmail-remote/qmail-remote.h b/src/qmail-remote/qmail-remote.h new file mode 100644 index 0000000..8059344 --- /dev/null +++ b/src/qmail-remote/qmail-remote.h @@ -0,0 +1,46 @@ +/* ISC license. */ + +#ifndef QMAIL_REMOTE_H +#define QMAIL_REMOTE_H + +#include +#include + +#include +#include +#include + +#include "qmailr.h" + +#define dienomem() qmailr_tempsys("Unable to grow stralloc") + + +/* dns */ + +typedef struct mxip_s mxip, *mxip_ref ; +struct mxip_s +{ + size_t pos4 ; + size_t pos6 ; + uint16_t n4 ; + uint16_t n6 ; +} ; +#define MXIP_ZERO { 0 } + +extern void dns_stuff (char const *, char const *const *, unsigned int, size_t *, genalloc *, stralloc *, unsigned int, char const *, unsigned int, char const *, unsigned int, uint32_t) ; + + +/* smtproutes */ + +typedef struct smtproutes_s smtproutes ; +struct smtproutes_s +{ + cdb map ; +} ; +#define SMTPROUTES_ZERO { .map = CDB_ZERO } + +extern int smtproutes_init (smtproutes *) ; +extern int smtproutes_match (smtproutes const *, char const *, stralloc *, size_t *, uint16_t *) ; +extern void smtproutes_free (smtproutes *) ; + +#endif diff --git a/src/qmail-remote/qmailr.h b/src/qmail-remote/qmailr.h new file mode 100644 index 0000000..08c4f41 --- /dev/null +++ b/src/qmail-remote/qmailr.h @@ -0,0 +1,66 @@ +/* ISC license. */ + +#ifndef QMAILR_H +#define QMAILR_H + +#include +#include + +#include +#include +#include + + +/* qmailr_error */ + +extern void qmailr_diev (int, char const *const *, unsigned int) gccattr_noreturn ; +extern void qmailr_dievsys (char const *const *, unsigned int) gccattr_noreturn ; +extern void qmailr_die (int, char const *) gccattr_noreturn ; +extern void qmailr_diesys (char const *) gccattr_noreturn ; + +#define qmailr_temp(s) qmailr_die(0, (s)) +#define qmailr_tempv(v, n) qmailr_diev(0, (v), n) +#define qmailr_tempsys(s) qmailr_diesys(s) +#define qmailr_tempvsys(v, n) qmailr_dievsys(v, n) +#define qmailr_perm(s) qmailr_die(1, (s)) +#define qmailr_permv(v, n) qmailr_diev(1, (v), n) + + +/* qmailr_utils */ + +extern int qmailr_memcmp4 (void const *, void const *) ; +extern int qmailr_memcmp16 (void const *, void const *) ; +extern int qmailr_box_encode (char const *, size_t, stralloc *) ; + + +/* qmailr_tcpto */ + +extern int qmailr_tcpto_match (char const *, int) ; +extern int qmailr_tcpto_update (char const *, int, int) ; + + +/* qmailr_control */ + +extern int qmailr_control_read (char const *, stralloc *, size_t *) ; +extern int qmailr_control_readint (char const *file, unsigned int *, stralloc *) ; +extern int qmailr_control_readiplist (char const *, stralloc *, stralloc *) ; + + +/* qmailr_tls */ + +typedef struct qmailr_tls_s qmailr_tls, *qmailr_tls_ref ; +struct qmailr_tls_s +{ + size_t tapos ; + size_t certpos ; + size_t keypos ; + uint8_t strictness : 2 ; + uint8_t flagtls : 1 ; + uint8_t flagtadir : 1 ; + uint8_t flagclientcert : 1 ; +} ; +#define QMAILR_TLS_ZERO { 0 } + +extern int qmailr_tls_init (qmailr_tls *, stralloc *) ; + +#endif diff --git a/src/qmail-remote/qmailr_control.c b/src/qmail-remote/qmailr_control.c new file mode 100644 index 0000000..706b0d9 --- /dev/null +++ b/src/qmail-remote/qmailr_control.c @@ -0,0 +1,82 @@ +/* ISC license. */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "qmailr.h" + +#include + +int qmailr_control_read (char const *file, stralloc *sa, size_t *pos) +{ + int fd = openc_readb(file) ; + if (fd == -1) return errno == ENOENT ? (errno = 0, 0) : -1 ; + + char buf[4096] ; + size_t r = allread(fd, buf, 4096) ; + fd_close(fd) ; + if (r == 4096) return (errno = ENAMETOOLONG, -1) ; + if (!r) return 0 ; + if (buf[r-1] == '\n') r-- ; + if (!r) return 0 ; + if (!stralloc_readyplus(sa, r+1)) return -1 ; + *pos = sa->len ; + stralloc_catb(sa, buf, r) ; stralloc_0(sa) ; + return 1 ; +} + +int qmailr_control_readint (char const *file, unsigned int *x, stralloc *sa) +{ + size_t pos ; + int r = qmailr_control_read(file, sa, &pos) ; + if (r <= 0) return r ; + sa->len = pos ; + if (!uint0_scan(sa->s + sa->len, x)) return (errno = EPROTO, 0) ; + return 1 ; +} + +int qmailr_control_readiplist (char const *file, stralloc *ip4, stralloc *ip6) +{ + int fd = openc_readb(file) ; + if (fd == -1) return errno == ENOENT ? (errno = 0, 0) : -1 ; + + size_t pos4 = ip4->len, pos6 = ip6->len ; + char buf[4096] ; + buffer b = BUFFER_INIT(&buffer_read, fd, buf, 4096) ; + + for (;;) + { + char line[128] ; + char ip[16] ; + size_t len = 0 ; + int r = getlnmax(&b, line, 127, &len, '\n') ; + if (r == -1) goto err ; + if (!r) break ; + if (!len) continue ; + if (line[len-1] != '\n') line[len++] = '\n' ; + if (ip6_scan(line, ip) == len-1) + { + if (!stralloc_catb(ip6, ip, 16)) goto err ; + } + else if (ip4_scan(line, ip) == len-1) + { + if (!stralloc_catb(ip4, ip, 4)) goto err ; + } + else goto errinval ; + } + return 1 ; + + errinval: + errno = EINVAL ; + err: + ip4->len = pos4 ; ip6->len = pos6 ; + return 0 ; +} diff --git a/src/qmail-remote/qmailr_error.c b/src/qmail-remote/qmailr_error.c new file mode 100644 index 0000000..fcdce54 --- /dev/null +++ b/src/qmail-remote/qmailr_error.c @@ -0,0 +1,38 @@ +/* ISC license. */ + +#include +#include +#include + +#include + +#include + +void qmailr_diev (int permanent, char const *const *v, unsigned int n) +{ + buffer_put(buffer_1small, permanent ? "D" : "Z", 1) ; + while (n--) buffer_puts(buffer_1small, *v++) ; + buffer_putflush(buffer_1small, "\n", 2) ; + _exit(0) ; +} + +void qmailr_dievsys (char const *const *v, unsigned int n) +{ + char const *se = strerror(errno) ; + buffer_put(buffer_1small, "Z", 1) ; + while (n--) buffer_puts(buffer_1small, *v++) ; + buffer_put(buffer_1small, ": ", 2) ; + buffer_puts(buffer_1small, se) ; + buffer_putflush(buffer_1small, "\n", 2) ; + _exit(0) ; +} + +void qmailr_die (int permanent, char const *msg) +{ + qmailr_diev(permanent, &msg, 1) ; +} + +void qmailr_diesys (char const *msg) +{ + qmailr_dievsys(&msg, 1) ; +} diff --git a/src/qmail-remote/qmailr_tcpto.c b/src/qmail-remote/qmailr_tcpto.c new file mode 100644 index 0000000..f90d1d0 --- /dev/null +++ b/src/qmail-remote/qmailr_tcpto.c @@ -0,0 +1,157 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include "qmailr.h" + +#include + + +/* + tcpto implementation, should be compatible with qmail-tcpto. + Assumes the 4 unused bytes at the end of a record are there to + accommodate 64-bit time_t. Which we use. But qmail-tcpto does + not, so you should patch that before 2038. + Has ipv6 support, storing v6 records in a different file. + Unlike qmail's tcpto, we assume the records are sorted by IP. + This makes it easy to look for a record with bsearch. We + keep records sorted with every modification, and we aggressively + cut empty ones from the file. + For the match function, not sure what is faster between mmapping and + simple reading. Currently we mmap to save private/dirty RAM, but + that holds the lock longer; it should be ok because we switched to + a shared lock for this (unsure why djb didn't). +*/ + +int qmailr_tcpto_match (char const *ip, int is6) +{ + char const *file = is6 ? SMTPD_STARTTLS_PROXY_QMAIL_HOME "/run/qmail-remote/tcpto6" : SMTPD_STARTTLS_PROXY_QMAIL_HOME "/queue/lock/tcpto" ; + uint32_t iplen = is6 ? 16 : 4 ; + uint32_t width = iplen + 12 ; + int r = 0 ; + char const *p ; + cdb c ; /* XXX: not a cdb, we're just using the mmap wrapper */ + int fd = openc_read(file) ; + + if (fd == -1) return -1 ; + if (fd_lock(fd, 0, 0) == -1) goto err ; + if (!cdb_init_fromfd(&c, fd)) goto err ; + if (c.size % width) goto errproto ; + p = bsearch(ip, c.map, c.size / width, width, is6 ? &qmailr_memcmp16 : &qmailr_memcmp4) ; + if (p) + { + if (p[iplen] >= 2) + { + tai when ; + uint64_t x ; + uint64_unpack(p + iplen + 4, &x) ; + tai_u64(&when, x) ; + tai_sub(&when, tain_secp(&STAMP), &when) ; + r = tai_sec(&when) < ((60 + (getpid() & 31)) << 6) ; /* don't ask me, ask djb */ + } + } + cdb_free(&c) ; + fd_close(fd) ; + return r ; + + errproto: + errno = EPROTO ; + err: + fd_close(fd) ; + return -1 ; +} + +int qmailr_tcpto_update (char const *ip, int is6, int problem) +{ + char const *file = is6 ? SMTPD_STARTTLS_PROXY_QMAIL_HOME "/run/qmail-remote/tcpto6" : SMTPD_STARTTLS_PROXY_QMAIL_HOME "/queue/lock/tcpto" ; + uint32_t iplen = is6 ? 16 : 4 ; + uint32_t width = iplen + 12 ; + uint32_t n ; + char *p = 0 ; + struct stat st ; + int fdr ; + int fdw = openc_create(file) ; + + if (fdw == -1) return 0 ; + if (fd_lock(fdw, 1, 0) == -1) goto err ; + fdr = openc_read(file) ; + if (fdr == -1) goto err ; + if (fstat(fdr, &st) == -1) goto err0 ; + if (st.st_size % width) goto errproto ; + n = st.st_size / width ; + + { + char buf[(n+1) * width] ; /* relax, it won't bite */ + if (n) + { + if (allread(fdr, buf, st.st_size) < st.st_size) goto err0 ; + memset(buf + st.st_size, 0, width) ; + p = bsearch(ip, buf, n, width, is6 ? &qmailr_memcmp16 : &qmailr_memcmp4) ; + if (p) + { + if (problem) + { + tai when ; + uint64_t x ; + uint64_unpack(p + iplen + 4, &x) ; + tai_u64(&when, x) ; + tai_sub(&when, tain_secp(&STAMP), &when) ; + if (tai_sec(&when) < 120) p = 0 ; + else + { + if (++p[iplen] > 10) p[iplen] = 10 ; + x = tai_sec(tain_secp(&STAMP)) - TAI_MAGIC ; + uint64_pack(p + iplen + 4, x) ; + } + } + else p[iplen] = 0 ; + } + } + else if (problem) + { + uint64_t x = tai_sec(tain_secp(&STAMP)) - TAI_MAGIC ; + p = buf + n++ * width ; + memcpy(p, ip, iplen) ; + p[iplen] = 1 ; + memset(p + iplen + 1, 0, 3) ; + uint64_pack(p + iplen + 4, x) ; + } + fd_close(fdr) ; + + if (p) + { + for (uint32_t i = 0 ; i < n ; i++) + if (!buf[i * width + iplen]) + memcpy(buf + i * width, buf + --n * width, width) ; + if (n) + { + qsort(buf, n, width, is6 ? &qmailr_memcmp16 : &qmailr_memcmp4) ; + if (allwrite(fdw, buf, n * width) < n * width) goto err ; + } + if (ftruncate(fdw, n * width) == -1) goto err ; + } + } + + fd_close(fdw) ; + return 1 ; + + errproto: + errno = EPROTO ; + err0: + fd_close(fdr) ; + err: + fd_close(fdw) ; + return 0 ; +} diff --git a/src/qmail-remote/qmailr_tls.c b/src/qmail-remote/qmailr_tls.c new file mode 100644 index 0000000..6e09a82 --- /dev/null +++ b/src/qmail-remote/qmailr_tls.c @@ -0,0 +1,47 @@ +/* ISC license. */ + +#include + +#include +#include "qmailr.h" + +int qmailr_tls_init (qmailr_tls *qt, stralloc *sa) +{ + static char const *tafile = SMTPD_STARTTLS_PROXY_QMAIL_HOME "/control/trustanchors" ; + static char const *certfile = SMTPD_STARTTLS_PROXY_QMAIL_HOME "/control/clientcert" ; + static char const *keyfile = SMTPD_STARTTLS_PROXY_QMAIL_HOME "/control/clientkey" ; + static char const *strictfile = SMTPD_STARTTLS_PROXY_QMAIL_HOME "/control/tlsstrictness" ; + + qmailr_tls tmp = QMAILR_TLS_ZERO ; + size_t sabase = sa->len ; + int r = qmailr_control_read(tafile, sa, &tmp.tapos) ; + if (r == -1) return 0 ; + if (r) + { + unsigned int strictness = 0 ; + tmp.flagtls = 1 ; + if (sa->s[sa->len - 2] == '/') + { + sa->s[--sa->len - 1] = 0 ; + tmp.flagtadir = 1 ; + } + r = qmailr_control_read(certfile, sa, &tmp.certpos) ; + if (r == -1) goto err ; + if (r) + { + r = qmailr_control_read(keyfile, sa, &tmp.keypos) ; + if (r == -1) goto err ; + if (r) tmp.flagclientcert = 1 ; + } + r = qmailr_control_readint(strictfile, &strictness, sa) ; + if (r == -1) goto err ; + tmp.strictness = strictness & 3 ; + } + + *qt = tmp ; + return 1 ; + + err: + sa->len = sabase ; + return 0 ; +} diff --git a/src/qmail-remote/qmailr_utils.c b/src/qmail-remote/qmailr_utils.c new file mode 100644 index 0000000..deeff46 --- /dev/null +++ b/src/qmail-remote/qmailr_utils.c @@ -0,0 +1,60 @@ +/* ISC license. */ + +#include + +#include +#include + +#include "qmailr.h" + +int qmailr_memcmp4 (void const *a, void const *b) +{ + return memcmp(a, b, 4) ; +} + +int qmailr_memcmp16 (void const *a, void const *b) +{ + return memcmp(a, b, 16) ; +} + +static inline int needsquoting (char const *s, size_t len) +{ + static unsigned char const badchar[32] = + { + 0xff, 0xff, 0xff, 0xff, + 0x05, 0x13, 0x00, 0x5c, + 0x01, 0x00, 0x00, 0x38, + 0x00, 0x00, 0x00, 0x80, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + } ; + if (!len) return 1 ; + if (s[0] == '.' || s[len - 1] == '.') return 1 ; + for (size_t i = 0 ; i < len ; i++) + { + if (bitarray_peek(badchar, (unsigned char)s[i])) return 1 ; + if (i + 2 < len && s[i] == '.' && s[i+1] == '.') return 1 ; + } + return 0 ; +} + +int qmailr_box_encode (char const *s, size_t len, stralloc *storage) +{ + if (needsquoting(s, len)) + { + size_t j = storage->len ; + if (!stralloc_readyplus(storage, 2 + (len << 1))) return 0 ; + storage->s[j++] = '"' ; + for (size_t i = 0 ; i < len ; i++) + { + if (strchr("\"\\\r\n", s[i])) storage->s[j++] = '\\' ; + storage->s[j++] = s[i] ; + } + storage->s[j++] = '"' ; + storage->len = j ; + } + else if (!stralloc_catb(storage, s, len)) return 0 ; + return 1 ; +} diff --git a/src/qmail-remote/smtproutes.c b/src/qmail-remote/smtproutes.c new file mode 100644 index 0000000..7467031 --- /dev/null +++ b/src/qmail-remote/smtproutes.c @@ -0,0 +1,240 @@ +/* ISC license. */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "qmailr.h" +#include "qmail-remote.h" + + +/* + qmail-remote uses a "constmap" for smtproutes, which is + basically a cdb in RAM. Every instance of qmail-remote + parses control/smtproutes to make the constmap. + We replace it with a real cdb, stored in the filesystem. + It saves CPU (N-1 instances of qmail-remote use the cdb + directly) and RAM (the cdb is read-only and shared). + The cdb is updated whenever control/smtproutes is newer. + We have to lock around the test to avoid several + concurrent compilations; the current lock feels too big, + the crit section can probably be made smaller, but the + behaviour is safe and avoids retry heuristics. +*/ + +/* + Key to the control/smtproutes parser + [host]:[relay[:port]] + An ip in square brackets is acceptable in host and relay, even ipv6 + + + 0 1 2 3 4 5 6 7 8 9 +st\ev EOF # \n : [ ] 0-9 a-f other special + +0 h n n n +START END COMMENT START RELAY QHOST X HOST HOST HOST X + +1 +COMMENT END COMMENT START COMMENT COMMENT COMMENT COMMENT COMMENT COMMENT COMMENT + +2 n n n +QHOST X X X QHOST X EHOST QHOST QHOST X X + +3 n h n n n +HOST X HOST X RELAY X X HOST HOST HOST X + +4 h +EHOST X X X RELAY X X X X X X + +5 ra n ra r n n n +RELAY END INRELAY START PORT QRELAY X INRELAY INRELAY INRELAY X + +6 n n n +QRELAY X X X QRELAY X ERELAY QRELAY QRELAY X X + +7 ra ra r n n n +INRELAY END X START PORT X X INRELAY INRELAY INRELAY X + +8 ra ra r +ERELAY END X START PORT X X X X X X + +9 pa pa n +PORT END X START X X X PORT X X X + +END=a, X=b + +0x0100 n push character +0x0200 h compute host length +0x0400 r compute relay length +0x0800 p compute port +0x1000 a add route entry +*/ + +static inline uint8_t cclass (char c) +{ + static uint8_t const table[128] = "0999999999299999999999999999999998918889999898786666666666399898877777788888888888888888884958898888888888888888888888888899999" ; + return c & 0x80 ? 9 : table[(uint8_t)c] - '0' ; +} + +static inline char getnext (buffer *b) +{ + char c ; + ssize_t r = buffer_get(b, &c, 1) ; + if (r == -1) qmailr_tempsys("Unable to read from control/smtproutes") ; + return r ? c : 0 ; +} + +static inline void smtproutes_compile (int fdr, int fdw) +{ + static uint16_t const table[10][10] = + { + { 0x000a, 0x0001, 0x0000, 0x0205, 0x0002, 0x000b, 0x0103, 0x0103, 0x0103, 0x000b }, + { 0x000a, 0x0001, 0x0000, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001 }, + { 0x000b, 0x000b, 0x000b, 0x0102, 0x000b, 0x0004, 0x0102, 0x0102, 0x0102, 0x000b }, + { 0x000b, 0x0103, 0x000b, 0x0205, 0x000b, 0x000b, 0x0103, 0x0103, 0x0103, 0x000b }, + { 0x000b, 0x000b, 0x000b, 0x0205, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b }, + { 0x140a, 0x0107, 0x1400, 0x0409, 0x0006, 0x000b, 0x0107, 0x0107, 0x0107, 0x000b }, + { 0x000b, 0x000b, 0x000b, 0x0106, 0x000b, 0x0008, 0x0106, 0x0106, 0x0106, 0x000b }, + { 0x140a, 0x000b, 0x1400, 0x0409, 0x000b, 0x000b, 0x0107, 0x0107, 0x0107, 0x000b }, + { 0x140a, 0x000b, 0x1400, 0x0409, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b }, + { 0x180a, 0x000b, 0x1800, 0x000b, 0x000b, 0x000b, 0x0109, 0x000b, 0x000b, 0x000b } + } ; + cdbmaker cm = CDBMAKER_ZERO ; + stralloc sa = STRALLOC_ZERO ; + char buf[2048] ; + buffer b = BUFFER_INIT(&buffer_read, fdr, buf, 2048) ; + uint32_t relaypos = 0, relayend = 0 ; + uint8_t state = 0 ; + if (!cdbmake_start(&cm, fdw)) qmailr_tempsys("Unable to cdbmake_start") ; + + while (state < 0x0a) + { + char c = getnext(&b) ; + uint16_t val = table[state][cclass(c)] ; + state = val & 0x000f ; + if (val & 0x0100) + { + if (!stralloc_catb(&sa, &c, 1)) qmailr_tempsys("Unable to grow stralloc") ; + } + if (val & 0x0200) + { + relaypos = sa.len + 1 ; + if (!stralloc_catb(&sa, "\0\0\31", 3)) qmailr_tempsys("Unable to grow stralloc") ; + } + if (val & 0x0400) + { + if (!stralloc_0(&sa)) qmailr_tempsys("Unable to grow stralloc") ; + relayend = sa.len ; + } + if (val & 0x0800) + { + uint16_t port ; + if (!stralloc_0(&sa)) qmailr_tempsys("Unable to grow stralloc") ; + if (!uint160_scan(sa.s + relayend, &port)) qmailr_temp("Invalid port in control/smtproutes") ; + uint16_pack_big(sa.s + relaypos, port) ; + } + if (val & 0x0100) + { + if (relaypos > 1 || relayend > 2 + relaypos) + if (!cdbmake_add(&cm, sa.s, relaypos, sa.s + relaypos, relayend - relaypos)) + qmailr_tempsys("Unable to cdbmake_add") ; + sa.len = 0 ; + } + } + if (state != 0x0a) qmailr_temp("Syntax error in control/smtproutes") ; + stralloc_free(&sa) ; + if (!cdbmake_finish(&cm)) qmailr_tempsys("Unable to cdbmake_finish") ; +} + +int smtproutes_init (smtproutes *routes) +{ + static char const *cdbfile = SMTPD_STARTTLS_PROXY_QMAIL_HOME "/run/qmail-remote/smtproutes.cdb" ; + static char const *lckfile = SMTPD_STARTTLS_PROXY_QMAIL_HOME "/run/qmail-remote/smtproutes.lock" ; + static char const *txtfile = SMTPD_STARTTLS_PROXY_QMAIL_HOME "/control/smtproutes" ; + static size_t const cdblen = sizeof(cdbfile) - 1 ; + int fdl = openc_create(lckfile) ; + if (fdl == -1) qmailr_tempsys("Unable to open run/qmail-remote/smtproutes.lock") ; + if (fd_lock(fdl, 1, 0) == -1) qmailr_tempsys("Unable to lock run/qmail-remote/smtproutes.lock") ; + + int fdc = openc_read(cdbfile) ; + if (fdc >= 0) + { + struct stat stc, str ; + if (fstat(fdc, &stc) == -1) qmailr_tempsys("Unable to fstat run/qmail-remote/smtproutes.cdb") ; + if (stat(txtfile, &str) == -1) + { + if (errno != ENOENT) qmailr_tempsys("Unable to fstat control/smtproutes") ; + unlink_void(cdbfile) ; + fd_close(fdc) ; + goto zero ; + } + if (timespec_cmp(&stc.st_mtim, &str.st_mtim) > 0) goto useit ; + fd_close(fdc) ; + } + + int fdr = openc_read(txtfile) ; + if (fdr == -1) + { + if (errno != ENOENT) qmailr_tempsys("Unable to open control/smtproutes") ; + goto zero ; + } + + { + char tmp[cdblen + 8] ; + memcpy(tmp, cdbfile, cdblen) ; + memcpy(tmp + cdblen, ":XXXXXX", 8) ; + fdc = mkstemp(tmp) ; + if (fdc == -1) qmailr_tempsys("Unable to mkstemp") ; + smtproutes_compile(fdr, fdc) ; + if (lseek(fdc, 0, SEEK_SET) == -1) qmailr_tempsys("Unable to lseek") ; + if (fsync(fdc) == -1) qmailr_tempsys("Unable to fsync run/qmail-remote/smtproutes.cdb") ; + fd_close(fdr) ; + if (rename(tmp, cdbfile) == -1) unlink_void(tmp) ; + } + + useit: + if (!cdb_init_fromfd(&routes->map, fdc)) qmailr_tempsys("Unable to mmap run/qmail-remote/smtproutes.cdb") ; + fd_close(fdc) ; + fd_close(fdl) ; + return 1 ; + + zero: + fd_close(fdl) ; + errno = 0 ; + return 0 ; +} + +int smtproutes_match (smtproutes const *routes, char const *s, stralloc *sa, size_t *pos, uint16_t *port) +{ + cdb_data data ; + int r = cdb_find(&routes->map, &data, s, strlen(s)) ; + if (r == -1) qmailr_temp("Invalid run/qmail-remote/smtproutes.cdb") ; + if (!r) return 0 ; + if (data.len < 3) return 0 ; + if (data.s[data.len - 1]) qmailr_temp("Invalid run/qmail-remote/smtproutes.cdb") ; + *pos = sa->len ; + uint16_unpack_big(data.s, port) ; + if (!stralloc_catb(sa, data.s + 2, data.len - 2)) qmailr_tempsys("Unable to grow stralloc") ; + return 1 ; +} + +void smtproutes_free (smtproutes *routes) +{ + cdb_free(&routes->map) ; +} diff --git a/src/qmail-smtpc/deps-exe/qmail-smtpc b/src/qmail-smtpc/deps-exe/qmail-smtpc deleted file mode 100644 index 4484f9b..0000000 --- a/src/qmail-smtpc/deps-exe/qmail-smtpc +++ /dev/null @@ -1,6 +0,0 @@ -dns.o -smtproutes.o -libqmailr.a.xyzzy --lskadns --ls6dns --lskarnet diff --git a/src/qmail-smtpc/deps-lib/qmailr b/src/qmail-smtpc/deps-lib/qmailr deleted file mode 100644 index c1095ed..0000000 --- a/src/qmail-smtpc/deps-lib/qmailr +++ /dev/null @@ -1,6 +0,0 @@ -qmailr_control.o -qmailr_error.o -qmailr_tcpto.o -qmailr_tls.o -qmailr_utils.o --lskarnet diff --git a/src/qmail-smtpc/dns.c b/src/qmail-smtpc/dns.c deleted file mode 100644 index 90aade4..0000000 --- a/src/qmail-smtpc/dns.c +++ /dev/null @@ -1,315 +0,0 @@ -/* ISC license. */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "qmailr.h" -#include "qmail-smtpc.h" - -typedef struct cnameinfo_s cnameinfo, *cnameinfo_ref ; -struct cnameinfo_s -{ - stralloc sa ; - size_t atpos ; - uint16_t id ; - uint16_t count ; -} ; - -typedef struct mxipinfo_s mxipinfo, mxipinfo_ref ; -struct mxipinfo_s -{ - stralloc ip4 ; - stralloc ip6 ; - uint16_t id4 ; - uint16_t id6 ; -} ; -#define MXIPINFO_ZERO { .ip4 = STRALLOC_ZERO, .ip6 = STRALLOC_ZERO, .id4 = UINT16_MAX, .id6 = UINT16_MAX } - -static int mx_cmp (void const *a, void const *b) -{ - s6dns_message_rr_mx_t const *aa = a ; - s6dns_message_rr_mx_t const *bb = b ; - return aa->preference < bb-> preference ? -1 : aa->preference > bb->preference ; -} - -static unsigned int use_host_as_mx (skadns_t *a, char const *host, genalloc *mxip, tain const *deadline) -{ - unsigned int newreqs = 0 ; - mxipinfo info = MXIPINFO_ZERO ; - s6dns_domain_t q ; - if (!s6dns_domain_fromstring_noqualify_encode(&q, host, strlen(host))) - qmailr_tempsys("Unable to DNS-encode host domain") ; - if (!skadns_send_g(a, &info.id4, &q, S6DNS_T_A, deadline, deadline)) - qmailr_tempsys("Unable to send A DNS query") ; - newreqs++ ; -#ifdef SKALIBS_IPV6_ENABLED - if (!skadns_send_g(a, &info.id6, &q, S6DNS_T_AAAA, deadline, deadline)) - qmailr_tempsys("Unable to send AAAA DNS query") ; - newreqs++ ; -#endif - if (!genalloc_catb(mxipinfo, mxip, &info, 1)) dienomem() ; - return newreqs ; -} - - /* - The point of this monster here is to do all the DNS resolutions in parallel, - to avoid compounding network latency. One of the many things that could never - be done by patching qmail-remote. - 1 sender + n-1 recipients are given in eaddr. - - loop around CNAME until we get the canonical name, for the n eaddrs - - either lookup the MX for the host then find all the A and AAAAs of all the MXes, - or get the A and AAAAs of the host directly (if smtproutes) - - do not keep the As and AAAAs listed in ipme - - sort the set of IPs by MX preference - When done, quote all the boxnames in eaddr. - Shove everything in storage and return the indices: - in eaddrpos for sender+recipients, in mxipind for the IPs to connect to. - - Also, fuck DNS for requiring so many small allocations and data copies. - - Also, fuck DNS. - */ - -void dns_stuff (char const *host, char const *const *eaddr, unsigned int n, size_t *eaddrpos, genalloc *mxipind, stralloc *storage, unsigned int timeoutdns, char const *ipme4, unsigned int n4, char const *ipme6, unsigned int n6, uint32_t flags) -{ - skadns_t a = SKADNS_ZERO ; - genalloc mxipi = GENALLOC_ZERO ; /* mxipinfo */ - tain deadline ; - unsigned int pending = 0 ; - uint16_t mxn = 0 ; - uint16_t mxid = UINT16_MAX ; - cnameinfo cnames[n] ; - - tain_addsec_g(&deadline, timeoutdns) ; - if (!skadns_startf_g(&a, &deadline)) - qmailr_tempsys("Unable to start asynchronous DNS helper") ; - - for (unsigned int i = 0 ; i < n ; i++) - { - char const *at = strrchr(eaddr[i], '@') ; - cnames[i].sa = stralloc_zero ; - if (at) - { - s6dns_domain_t q ; - size_t len = strlen(at+1) ; - cnames[i].atpos = at - eaddr[i] ; - if (!stralloc_catb(&cnames[i].sa, at+1, len)) dienomem() ; - if (!s6dns_domain_fromstring_noqualify_encode(&q, at+1, len)) - qmailr_tempsys("Unable to DNS-encode recipient domain") ; - if (!skadns_send_g(&a, &cnames[i].id, &q, S6DNS_T_CNAME, &deadline, &deadline)) - qmailr_tempsys("Unable to send CNAME DNS query") ; - cnames[i].count = 1 ; - pending++ ; - } - else - { - cnames[i].id = UINT16_MAX ; - cnames[i].count = 0 ; - cnames[i].atpos = strlen(eaddr[i]) ; - } - } - - if (flags & 1) - { - s6dns_domain_t q ; - if (!s6dns_domain_fromstring_noqualify_encode(&q, host, strlen(host))) - qmailr_tempsys("Unable to DNS-encode host domain") ; - if (!skadns_send_g(&a, &mxid, &q, S6DNS_T_MX, &deadline, &deadline)) - qmailr_tempsys("Unable to send MX DNS query") ; - pending++ ; - } - else - { - mxn = 1 ; - pending += use_host_as_mx(&a, host, &mxipi, &deadline) ; - } - - while (pending) - { - uint16_t *ids ; - iopause_fd x = { .fd = skadns_fd(&a), .events = IOPAUSE_READ } ; - int r = iopause_g(&x, 1, &deadline) ; - if (r == -1) qmailr_tempsys("Unable to iopause") ; - if (!r) qmailr_tempsys("Timed out waiting for DNS") ; - r = skadns_update(&a) ; - if (r == -1) qmailr_tempsys("Unable to read DNS answers") ; - ids = genalloc_s(uint16_t, &a.list) ; - for (size_t j = 0 ; j < genalloc_len(uint16_t, &a.list) ; j++) - { - char const *packet = skadns_packet(&a, ids[j]) ; - uint16_t packetlen = skadns_packetlen(&a, ids[j]) ; - if (!packet) qmailr_tempsys("DNS packet reading error") ; - - if (ids[j] == mxid) /* return from MX query */ - { - s6dns_message_header_t h ; - genalloc mxes = GENALLOC_ZERO ; /* s6dns_message_rr_mx_t */ - r = s6dns_message_parse(&h, packet, packetlen, &s6dns_message_parse_answer_mx, &mxes) ; - if (r == -1) qmailr_tempsys("DNS packet parsing error") ; - if (!r) - { - if (errno == EBUSY || errno == EIO) qmailr_temp("Temporary DNS error while resolving MX") ; - else qmailr_perm("DNS CNAME resolution error") ; - } - skadns_release(&a, ids[j]) ; - pending-- ; - mxid = UINT16_MAX ; - if (r >= 2) /* we have MXes, ask for their IPs */ - { - s6dns_message_rr_mx_t *mxs = genalloc_s(s6dns_message_rr_mx_t, &mxes) ; - mxn = genalloc_len(s6dns_message_rr_mx_t, &mxes) ; - if (!genalloc_readyplus(mxipinfo, &mxipi, mxn)) dienomem() ; - qsort(mxs, mxn, sizeof(s6dns_message_rr_mx_t), &mx_cmp) ; - for (unsigned int i = 0 ; i < mxn ; i++) - { - mxipinfo *p = genalloc_s(mxipinfo, &mxipi) + i ; - p->ip4 = p->ip6 = stralloc_zero ; - if (!skadns_send_g(&a, &p->id4, &mxs[i].exchange, S6DNS_T_A, &deadline, &deadline)) - qmailr_tempsys("Unable to send A DNS query") ; - pending++ ; -#ifdef SKALIBS_IPV6_ENABLED - if (!skadns_send_g(&a, &p->id6, &mxs[i].exchange, S6DNS_T_AAAA, &deadline, &deadline)) - qmailr_tempsys("Unable to send AAAA DNS query") ; - pending++ ; -#endif - } - genalloc_free(s6dns_message_rr_mx_t, &mxes) ; - } - else - { - mxn = 1 ; - pending += use_host_as_mx(&a, host, &mxipi, &deadline) ; - } - continue ; - } - - for (unsigned int i = 0 ; i < n ; i++) if (ids[j] == cnames[i].id) /* return from CNAME query */ - { - s6dns_message_header_t h ; - s6dns_dpag_t dlist = { .ds = GENALLOC_ZERO, .rtype = S6DNS_T_CNAME } ; - r = s6dns_message_parse(&h, packet, packetlen, &s6dns_message_parse_answer_domain, &dlist) ; - if (r == -1) qmailr_tempsys("DNS packet parsing error") ; - if (!r) - { - if (errno == EBUSY || errno == EIO) qmailr_temp("Temporary DNS error while resolving CNAME") ; - else qmailr_perm("DNS CNAME resolution error") ; - } - skadns_release(&a, ids[j]) ; - pending-- ; - if (r >= 2) /* it's a CNAME, loop on it */ - { - s6dns_domain_t *domain = genalloc_s(s6dns_domain_t, &dlist.ds) ; - if (cnames[i].count++ >= 100) qmailr_temp("DNS CNAME loop") ; - if (!skadns_send_g(&a, &cnames[i].id, domain, S6DNS_T_CNAME, &deadline, &deadline)) - qmailr_tempsys("Unable to send CNAME DNS query") ; - pending++ ; - if (!stralloc_ready(&cnames[i].sa, 256)) dienomem() ; - s6dns_domain_decode(domain) ; - cnames[i].sa.len = s6dns_domain_tostring(cnames[i].sa.s, 256, domain) ; - genalloc_free(s6dns_domain_t, &dlist.ds) ; - } - else cnames[i].id = UINT16_MAX ; /* that's the canonical host in cnames[i].sa */ - continue ; - } - - for (unsigned int i = 0 ; i < mxn ; i++) - { - mxipinfo *p = genalloc_s(mxipinfo, &mxipi) + i ; - if (ids[j] == p->id4) - { - s6dns_message_header_t h ; - r = s6dns_message_parse(&h, packet, packetlen, &s6dns_message_parse_answer_a, &p->ip4) ; - if (r == -1) qmailr_tempsys("DNS packet parsing error") ; - if (!r) - { - if (errno == EBUSY || errno == EIO) qmailr_temp("Temporary DNS error while resolving A") ; - else qmailr_perm("DNS A resolution error") ; - } - skadns_release(&a, ids[j]) ; - pending-- ; - p->id4 = UINT16_MAX ; - for (unsigned int k = 0 ; k < p->ip4.len ; k += 4) - { - if (bsearch(p->ip4.s + k, ipme4, n4, 4, &qmailr_memcmp4)) - { - memmove(p->ip4.s + k, p->ip4.s + p->ip4.len - 4, 4) ; - p->ip4.len -= 4 ; - k -= 4 ; - } - } - } -#ifdef SKALIBS_IPV6_ENABLED - else if (ids[j] == p->id6) - { - s6dns_message_header_t h ; - r = s6dns_message_parse(&h, packet, packetlen, &s6dns_message_parse_answer_aaaa, &p->ip6) ; - if (r == -1) qmailr_tempsys("DNS packet parsing error") ; - if (!r) - { - if (errno == EBUSY || errno == EIO) qmailr_temp("Temporary DNS error while resolving AAAA") ; - else qmailr_perm("DNS AAAA resolution error") ; - } - skadns_release(&a, ids[j]) ; - pending-- ; - p->id6 = UINT16_MAX ; - for (unsigned int k = 0 ; k < p->ip6.len ; k += 16) - { - if (bsearch(p->ip6.s + k, ipme6, n6, 16, &qmailr_memcmp16)) - { - memmove(p->ip6.s + k, p->ip6.s + p->ip6.len - 16, 16) ; - p->ip6.len -= 16 ; - k -= 16 ; - } - } - } -#endif - } - } - } - skadns_end(&a) ; /* we done buddy */ - - for (unsigned int i = 0 ; i < n ; i++) - { - eaddrpos[i] = storage->len ; - if (!qmailr_box_encode(eaddr[i], cnames[i].atpos, storage)) dienomem() ; - if (cnames[i].count) - { - if (!stralloc_catb(storage, "@", 1)) dienomem() ; - if (!stralloc_catb(storage, cnames[i].sa.s, cnames[i].sa.len)) dienomem() ; - stralloc_free(&cnames[i].sa) ; - } - if (!stralloc_0(storage)) dienomem() ; - } - - if (!genalloc_readyplus(mxip, mxipind, mxn)) dienomem() ; - for (unsigned int i = 0 ; i < mxn ; i++) - { - mxip data ; - mxipinfo *p = genalloc_s(mxipinfo, &mxipi) + i ; - data.n4 = p->ip4.len >> 2 ; - data.pos4 = storage->len ; - if (!stralloc_catb(storage, p->ip4.s, p->ip4.len)) dienomem() ; - stralloc_free(&p->ip4) ; -#ifdef SKALIBS_IPV6_ENABLED - data.n6 = p->ip6.len >> 4 ; - data.pos6 = storage->len ; - if (!stralloc_catb(storage, p->ip6.s, p->ip6.len)) dienomem() ; - stralloc_free(&p->ip6) ; - genalloc_catb(mxip, mxipind, &data, 1) ; -#endif - } - genalloc_free(mxipinfo, &mxipi) ; -} diff --git a/src/qmail-smtpc/qmail-smtpc.c b/src/qmail-smtpc/qmail-smtpc.c deleted file mode 100644 index 0e0b11b..0000000 --- a/src/qmail-smtpc/qmail-smtpc.c +++ /dev/null @@ -1,86 +0,0 @@ -/* ISC license. */ - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include "qmailr.h" -#include "qmail-smtpc.h" - -#define dieusage() qmailr_perm("qmail-remote was invoked improperly") - -int main (int argc, char const *const *argv) -{ - stralloc storage = STRALLOC_ZERO ; - stralloc ipme4 = STRALLOC_ZERO ; - stralloc ipme6 = STRALLOC_ZERO ; - qmailr_tls qt = QMAILR_TLS_ZERO ; - smtproutes routes = SMTPROUTES_ZERO ; - unsigned int timeoutconnect = 60, timeoutremote = 1200 ; - char const *host ; - size_t mepos, helopos, hostpos = 0 ; - uint16_t port = 25 ; - int r ; - - if (argc-- < 4) dieusage() ; - argv++ ; - if (chdir(SMTPD_STARTTLS_PROXY_QMAIL_HOME) == -1) qmailr_temp("Unable to chdir to " SMTPD_STARTTLS_PROXY_QMAIL_HOME) ; - if (sig_altignore(SIGPIPE) == -1) qmailr_tempsys("Unable to ignore SIGPIPE") ; - host = *argv++ ; argc-- ; - tain_now_set_stopwatch_g() ; - - - /* init control */ - - r = qmailr_control_read("control/me", &storage, &mepos) ; - if (r == -1) qmailr_tempsys("Unable to read control/me") ; - else if (!r) qmailr_temp("Invalid control/me") ; - - r = qmailr_control_read("control/helohost", &storage, &helopos) ; - if (r == -1) qmailr_tempsys("Unable to read control/helohost") ; - else if (!r) helopos = mepos ; - - r = qmailr_control_readint("control/timeoutconnect", &timeoutconnect, &storage) ; - if (r == -1) qmailr_tempsys("Unable to read control/timeoutconnect") ; - r = qmailr_control_readint("control/timeoutremote", &timeoutremote, &storage) ; - if (r == -1) qmailr_tempsys("Unable to read control/timeoutremote") ; - - if (!qmailr_control_readiplist("control/ipme", &ipme4, &ipme6)) - qmailr_tempsys("Unable to read control/ipme") ; - stralloc_shrink(&ipme4) ; - stralloc_shrink(&ipme6) ; - qsort(ipme4.s, ipme4.len >> 2, 4, &qmailr_memcmp4) ; - qsort(ipme6.s, ipme6.len >> 4, 16, &qmailr_memcmp16) ; - - if (!qmailr_tls_init(&qt, &storage)) - qmailr_tempsys("Unable to read TLS control files") ; - - if (smtproutes_init(&routes)) - { - if (!smtproutes_match(&routes, host, &storage, &hostpos, &port)) - { - size_t hostlen = strlen(host) ; - for (size_t i = 0 ; i < hostlen ; i++) if (host[i] == '.') - if (smtproutes_match(&routes, argv[1], &storage, &hostpos, &port)) break ; - if (!hostpos) smtproutes_match(&routes, "", &storage, &hostpos, &port) ; - } - smtproutes_free(&routes) ; - } - - { - genalloc mxipind = GENALLOC_ZERO ; - size_t eaddrpos[argc] ; - dns_stuff(hostpos ? storage.s + hostpos : host, argv, argc, eaddrpos, &mxipind, &storage, timeoutconnect, ipme4.s, ipme4.len >> 2, ipme6.s, ipme6.len >> 4, !hostpos) ; - - } - - - _exit(0) ; -} diff --git a/src/qmail-smtpc/qmail-smtpc.h b/src/qmail-smtpc/qmail-smtpc.h deleted file mode 100644 index 29aa792..0000000 --- a/src/qmail-smtpc/qmail-smtpc.h +++ /dev/null @@ -1,46 +0,0 @@ -/* ISC license. */ - -#ifndef QMAIL_SMTPC_H -#define QMAIL_SMTPC_H - -#include -#include - -#include -#include -#include - -#include "qmailr.h" - -#define dienomem() qmailr_tempsys("Unable to grow stralloc") - - -/* dns */ - -typedef struct mxip_s mxip, *mxip_ref ; -struct mxip_s -{ - size_t pos4 ; - size_t pos6 ; - uint16_t n4 ; - uint16_t n6 ; -} ; -#define MXIP_ZERO { 0 } - -extern void dns_stuff (char const *, char const *const *, unsigned int, size_t *, genalloc *, stralloc *, unsigned int, char const *, unsigned int, char const *, unsigned int, uint32_t) ; - - -/* smtproutes */ - -typedef struct smtproutes_s smtproutes ; -struct smtproutes_s -{ - cdb map ; -} ; -#define SMTPROUTES_ZERO { .map = CDB_ZERO } - -extern int smtproutes_init (smtproutes *) ; -extern int smtproutes_match (smtproutes const *, char const *, stralloc *, size_t *, uint16_t *) ; -extern void smtproutes_free (smtproutes *) ; - -#endif diff --git a/src/qmail-smtpc/qmailr.h b/src/qmail-smtpc/qmailr.h deleted file mode 100644 index 08c4f41..0000000 --- a/src/qmail-smtpc/qmailr.h +++ /dev/null @@ -1,66 +0,0 @@ -/* ISC license. */ - -#ifndef QMAILR_H -#define QMAILR_H - -#include -#include - -#include -#include -#include - - -/* qmailr_error */ - -extern void qmailr_diev (int, char const *const *, unsigned int) gccattr_noreturn ; -extern void qmailr_dievsys (char const *const *, unsigned int) gccattr_noreturn ; -extern void qmailr_die (int, char const *) gccattr_noreturn ; -extern void qmailr_diesys (char const *) gccattr_noreturn ; - -#define qmailr_temp(s) qmailr_die(0, (s)) -#define qmailr_tempv(v, n) qmailr_diev(0, (v), n) -#define qmailr_tempsys(s) qmailr_diesys(s) -#define qmailr_tempvsys(v, n) qmailr_dievsys(v, n) -#define qmailr_perm(s) qmailr_die(1, (s)) -#define qmailr_permv(v, n) qmailr_diev(1, (v), n) - - -/* qmailr_utils */ - -extern int qmailr_memcmp4 (void const *, void const *) ; -extern int qmailr_memcmp16 (void const *, void const *) ; -extern int qmailr_box_encode (char const *, size_t, stralloc *) ; - - -/* qmailr_tcpto */ - -extern int qmailr_tcpto_match (char const *, int) ; -extern int qmailr_tcpto_update (char const *, int, int) ; - - -/* qmailr_control */ - -extern int qmailr_control_read (char const *, stralloc *, size_t *) ; -extern int qmailr_control_readint (char const *file, unsigned int *, stralloc *) ; -extern int qmailr_control_readiplist (char const *, stralloc *, stralloc *) ; - - -/* qmailr_tls */ - -typedef struct qmailr_tls_s qmailr_tls, *qmailr_tls_ref ; -struct qmailr_tls_s -{ - size_t tapos ; - size_t certpos ; - size_t keypos ; - uint8_t strictness : 2 ; - uint8_t flagtls : 1 ; - uint8_t flagtadir : 1 ; - uint8_t flagclientcert : 1 ; -} ; -#define QMAILR_TLS_ZERO { 0 } - -extern int qmailr_tls_init (qmailr_tls *, stralloc *) ; - -#endif diff --git a/src/qmail-smtpc/qmailr_control.c b/src/qmail-smtpc/qmailr_control.c deleted file mode 100644 index 706b0d9..0000000 --- a/src/qmail-smtpc/qmailr_control.c +++ /dev/null @@ -1,82 +0,0 @@ -/* ISC license. */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "qmailr.h" - -#include - -int qmailr_control_read (char const *file, stralloc *sa, size_t *pos) -{ - int fd = openc_readb(file) ; - if (fd == -1) return errno == ENOENT ? (errno = 0, 0) : -1 ; - - char buf[4096] ; - size_t r = allread(fd, buf, 4096) ; - fd_close(fd) ; - if (r == 4096) return (errno = ENAMETOOLONG, -1) ; - if (!r) return 0 ; - if (buf[r-1] == '\n') r-- ; - if (!r) return 0 ; - if (!stralloc_readyplus(sa, r+1)) return -1 ; - *pos = sa->len ; - stralloc_catb(sa, buf, r) ; stralloc_0(sa) ; - return 1 ; -} - -int qmailr_control_readint (char const *file, unsigned int *x, stralloc *sa) -{ - size_t pos ; - int r = qmailr_control_read(file, sa, &pos) ; - if (r <= 0) return r ; - sa->len = pos ; - if (!uint0_scan(sa->s + sa->len, x)) return (errno = EPROTO, 0) ; - return 1 ; -} - -int qmailr_control_readiplist (char const *file, stralloc *ip4, stralloc *ip6) -{ - int fd = openc_readb(file) ; - if (fd == -1) return errno == ENOENT ? (errno = 0, 0) : -1 ; - - size_t pos4 = ip4->len, pos6 = ip6->len ; - char buf[4096] ; - buffer b = BUFFER_INIT(&buffer_read, fd, buf, 4096) ; - - for (;;) - { - char line[128] ; - char ip[16] ; - size_t len = 0 ; - int r = getlnmax(&b, line, 127, &len, '\n') ; - if (r == -1) goto err ; - if (!r) break ; - if (!len) continue ; - if (line[len-1] != '\n') line[len++] = '\n' ; - if (ip6_scan(line, ip) == len-1) - { - if (!stralloc_catb(ip6, ip, 16)) goto err ; - } - else if (ip4_scan(line, ip) == len-1) - { - if (!stralloc_catb(ip4, ip, 4)) goto err ; - } - else goto errinval ; - } - return 1 ; - - errinval: - errno = EINVAL ; - err: - ip4->len = pos4 ; ip6->len = pos6 ; - return 0 ; -} diff --git a/src/qmail-smtpc/qmailr_error.c b/src/qmail-smtpc/qmailr_error.c deleted file mode 100644 index fcdce54..0000000 --- a/src/qmail-smtpc/qmailr_error.c +++ /dev/null @@ -1,38 +0,0 @@ -/* ISC license. */ - -#include -#include -#include - -#include - -#include - -void qmailr_diev (int permanent, char const *const *v, unsigned int n) -{ - buffer_put(buffer_1small, permanent ? "D" : "Z", 1) ; - while (n--) buffer_puts(buffer_1small, *v++) ; - buffer_putflush(buffer_1small, "\n", 2) ; - _exit(0) ; -} - -void qmailr_dievsys (char const *const *v, unsigned int n) -{ - char const *se = strerror(errno) ; - buffer_put(buffer_1small, "Z", 1) ; - while (n--) buffer_puts(buffer_1small, *v++) ; - buffer_put(buffer_1small, ": ", 2) ; - buffer_puts(buffer_1small, se) ; - buffer_putflush(buffer_1small, "\n", 2) ; - _exit(0) ; -} - -void qmailr_die (int permanent, char const *msg) -{ - qmailr_diev(permanent, &msg, 1) ; -} - -void qmailr_diesys (char const *msg) -{ - qmailr_dievsys(&msg, 1) ; -} diff --git a/src/qmail-smtpc/qmailr_tcpto.c b/src/qmail-smtpc/qmailr_tcpto.c deleted file mode 100644 index f90d1d0..0000000 --- a/src/qmail-smtpc/qmailr_tcpto.c +++ /dev/null @@ -1,157 +0,0 @@ -/* ISC license. */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include "qmailr.h" - -#include - - -/* - tcpto implementation, should be compatible with qmail-tcpto. - Assumes the 4 unused bytes at the end of a record are there to - accommodate 64-bit time_t. Which we use. But qmail-tcpto does - not, so you should patch that before 2038. - Has ipv6 support, storing v6 records in a different file. - Unlike qmail's tcpto, we assume the records are sorted by IP. - This makes it easy to look for a record with bsearch. We - keep records sorted with every modification, and we aggressively - cut empty ones from the file. - For the match function, not sure what is faster between mmapping and - simple reading. Currently we mmap to save private/dirty RAM, but - that holds the lock longer; it should be ok because we switched to - a shared lock for this (unsure why djb didn't). -*/ - -int qmailr_tcpto_match (char const *ip, int is6) -{ - char const *file = is6 ? SMTPD_STARTTLS_PROXY_QMAIL_HOME "/run/qmail-remote/tcpto6" : SMTPD_STARTTLS_PROXY_QMAIL_HOME "/queue/lock/tcpto" ; - uint32_t iplen = is6 ? 16 : 4 ; - uint32_t width = iplen + 12 ; - int r = 0 ; - char const *p ; - cdb c ; /* XXX: not a cdb, we're just using the mmap wrapper */ - int fd = openc_read(file) ; - - if (fd == -1) return -1 ; - if (fd_lock(fd, 0, 0) == -1) goto err ; - if (!cdb_init_fromfd(&c, fd)) goto err ; - if (c.size % width) goto errproto ; - p = bsearch(ip, c.map, c.size / width, width, is6 ? &qmailr_memcmp16 : &qmailr_memcmp4) ; - if (p) - { - if (p[iplen] >= 2) - { - tai when ; - uint64_t x ; - uint64_unpack(p + iplen + 4, &x) ; - tai_u64(&when, x) ; - tai_sub(&when, tain_secp(&STAMP), &when) ; - r = tai_sec(&when) < ((60 + (getpid() & 31)) << 6) ; /* don't ask me, ask djb */ - } - } - cdb_free(&c) ; - fd_close(fd) ; - return r ; - - errproto: - errno = EPROTO ; - err: - fd_close(fd) ; - return -1 ; -} - -int qmailr_tcpto_update (char const *ip, int is6, int problem) -{ - char const *file = is6 ? SMTPD_STARTTLS_PROXY_QMAIL_HOME "/run/qmail-remote/tcpto6" : SMTPD_STARTTLS_PROXY_QMAIL_HOME "/queue/lock/tcpto" ; - uint32_t iplen = is6 ? 16 : 4 ; - uint32_t width = iplen + 12 ; - uint32_t n ; - char *p = 0 ; - struct stat st ; - int fdr ; - int fdw = openc_create(file) ; - - if (fdw == -1) return 0 ; - if (fd_lock(fdw, 1, 0) == -1) goto err ; - fdr = openc_read(file) ; - if (fdr == -1) goto err ; - if (fstat(fdr, &st) == -1) goto err0 ; - if (st.st_size % width) goto errproto ; - n = st.st_size / width ; - - { - char buf[(n+1) * width] ; /* relax, it won't bite */ - if (n) - { - if (allread(fdr, buf, st.st_size) < st.st_size) goto err0 ; - memset(buf + st.st_size, 0, width) ; - p = bsearch(ip, buf, n, width, is6 ? &qmailr_memcmp16 : &qmailr_memcmp4) ; - if (p) - { - if (problem) - { - tai when ; - uint64_t x ; - uint64_unpack(p + iplen + 4, &x) ; - tai_u64(&when, x) ; - tai_sub(&when, tain_secp(&STAMP), &when) ; - if (tai_sec(&when) < 120) p = 0 ; - else - { - if (++p[iplen] > 10) p[iplen] = 10 ; - x = tai_sec(tain_secp(&STAMP)) - TAI_MAGIC ; - uint64_pack(p + iplen + 4, x) ; - } - } - else p[iplen] = 0 ; - } - } - else if (problem) - { - uint64_t x = tai_sec(tain_secp(&STAMP)) - TAI_MAGIC ; - p = buf + n++ * width ; - memcpy(p, ip, iplen) ; - p[iplen] = 1 ; - memset(p + iplen + 1, 0, 3) ; - uint64_pack(p + iplen + 4, x) ; - } - fd_close(fdr) ; - - if (p) - { - for (uint32_t i = 0 ; i < n ; i++) - if (!buf[i * width + iplen]) - memcpy(buf + i * width, buf + --n * width, width) ; - if (n) - { - qsort(buf, n, width, is6 ? &qmailr_memcmp16 : &qmailr_memcmp4) ; - if (allwrite(fdw, buf, n * width) < n * width) goto err ; - } - if (ftruncate(fdw, n * width) == -1) goto err ; - } - } - - fd_close(fdw) ; - return 1 ; - - errproto: - errno = EPROTO ; - err0: - fd_close(fdr) ; - err: - fd_close(fdw) ; - return 0 ; -} diff --git a/src/qmail-smtpc/qmailr_tls.c b/src/qmail-smtpc/qmailr_tls.c deleted file mode 100644 index 6e09a82..0000000 --- a/src/qmail-smtpc/qmailr_tls.c +++ /dev/null @@ -1,47 +0,0 @@ -/* ISC license. */ - -#include - -#include -#include "qmailr.h" - -int qmailr_tls_init (qmailr_tls *qt, stralloc *sa) -{ - static char const *tafile = SMTPD_STARTTLS_PROXY_QMAIL_HOME "/control/trustanchors" ; - static char const *certfile = SMTPD_STARTTLS_PROXY_QMAIL_HOME "/control/clientcert" ; - static char const *keyfile = SMTPD_STARTTLS_PROXY_QMAIL_HOME "/control/clientkey" ; - static char const *strictfile = SMTPD_STARTTLS_PROXY_QMAIL_HOME "/control/tlsstrictness" ; - - qmailr_tls tmp = QMAILR_TLS_ZERO ; - size_t sabase = sa->len ; - int r = qmailr_control_read(tafile, sa, &tmp.tapos) ; - if (r == -1) return 0 ; - if (r) - { - unsigned int strictness = 0 ; - tmp.flagtls = 1 ; - if (sa->s[sa->len - 2] == '/') - { - sa->s[--sa->len - 1] = 0 ; - tmp.flagtadir = 1 ; - } - r = qmailr_control_read(certfile, sa, &tmp.certpos) ; - if (r == -1) goto err ; - if (r) - { - r = qmailr_control_read(keyfile, sa, &tmp.keypos) ; - if (r == -1) goto err ; - if (r) tmp.flagclientcert = 1 ; - } - r = qmailr_control_readint(strictfile, &strictness, sa) ; - if (r == -1) goto err ; - tmp.strictness = strictness & 3 ; - } - - *qt = tmp ; - return 1 ; - - err: - sa->len = sabase ; - return 0 ; -} diff --git a/src/qmail-smtpc/qmailr_utils.c b/src/qmail-smtpc/qmailr_utils.c deleted file mode 100644 index deeff46..0000000 --- a/src/qmail-smtpc/qmailr_utils.c +++ /dev/null @@ -1,60 +0,0 @@ -/* ISC license. */ - -#include - -#include -#include - -#include "qmailr.h" - -int qmailr_memcmp4 (void const *a, void const *b) -{ - return memcmp(a, b, 4) ; -} - -int qmailr_memcmp16 (void const *a, void const *b) -{ - return memcmp(a, b, 16) ; -} - -static inline int needsquoting (char const *s, size_t len) -{ - static unsigned char const badchar[32] = - { - 0xff, 0xff, 0xff, 0xff, - 0x05, 0x13, 0x00, 0x5c, - 0x01, 0x00, 0x00, 0x38, - 0x00, 0x00, 0x00, 0x80, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - } ; - if (!len) return 1 ; - if (s[0] == '.' || s[len - 1] == '.') return 1 ; - for (size_t i = 0 ; i < len ; i++) - { - if (bitarray_peek(badchar, (unsigned char)s[i])) return 1 ; - if (i + 2 < len && s[i] == '.' && s[i+1] == '.') return 1 ; - } - return 0 ; -} - -int qmailr_box_encode (char const *s, size_t len, stralloc *storage) -{ - if (needsquoting(s, len)) - { - size_t j = storage->len ; - if (!stralloc_readyplus(storage, 2 + (len << 1))) return 0 ; - storage->s[j++] = '"' ; - for (size_t i = 0 ; i < len ; i++) - { - if (strchr("\"\\\r\n", s[i])) storage->s[j++] = '\\' ; - storage->s[j++] = s[i] ; - } - storage->s[j++] = '"' ; - storage->len = j ; - } - else if (!stralloc_catb(storage, s, len)) return 0 ; - return 1 ; -} diff --git a/src/qmail-smtpc/smtproutes.c b/src/qmail-smtpc/smtproutes.c deleted file mode 100644 index d5b1fac..0000000 --- a/src/qmail-smtpc/smtproutes.c +++ /dev/null @@ -1,240 +0,0 @@ -/* ISC license. */ - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "qmailr.h" -#include "qmail-smtpc.h" - - -/* - qmail-remote uses a "constmap" for smtproutes, which is - basically a cdb in RAM. Every instance of qmail-remote - parses control/smtproutes to make the constmap. - We replace it with a real cdb, stored in the filesystem. - It saves CPU (N-1 instances of qmail-remote use the cdb - directly) and RAM (the cdb is read-only and shared). - The cdb is updated whenever control/smtproutes is newer. - We have to lock around the test to avoid several - concurrent compilations; the current lock feels too big, - the crit section can probably be made smaller, but the - behaviour is safe and avoids retry heuristics. -*/ - -/* - Key to the control/smtproutes parser - [host]:[relay[:port]] - An ip in square brackets is acceptable in host and relay, even ipv6 - - - 0 1 2 3 4 5 6 7 8 9 -st\ev EOF # \n : [ ] 0-9 a-f other special - -0 h n n n -START END COMMENT START RELAY QHOST X HOST HOST HOST X - -1 -COMMENT END COMMENT START COMMENT COMMENT COMMENT COMMENT COMMENT COMMENT COMMENT - -2 n n n -QHOST X X X QHOST X EHOST QHOST QHOST X X - -3 n h n n n -HOST X HOST X RELAY X X HOST HOST HOST X - -4 h -EHOST X X X RELAY X X X X X X - -5 ra n ra r n n n -RELAY END INRELAY START PORT QRELAY X INRELAY INRELAY INRELAY X - -6 n n n -QRELAY X X X QRELAY X ERELAY QRELAY QRELAY X X - -7 ra ra r n n n -INRELAY END X START PORT X X INRELAY INRELAY INRELAY X - -8 ra ra r -ERELAY END X START PORT X X X X X X - -9 pa pa n -PORT END X START X X X PORT X X X - -END=a, X=b - -0x0100 n push character -0x0200 h compute host length -0x0400 r compute relay length -0x0800 p compute port -0x1000 a add route entry -*/ - -static inline uint8_t cclass (char c) -{ - static uint8_t const table[128] = "0999999999299999999999999999999998918889999898786666666666399898877777788888888888888888884958898888888888888888888888888899999" ; - return c & 0x80 ? 9 : table[(uint8_t)c] - '0' ; -} - -static inline char getnext (buffer *b) -{ - char c ; - ssize_t r = buffer_get(b, &c, 1) ; - if (r == -1) qmailr_tempsys("Unable to read from control/smtproutes") ; - return r ? c : 0 ; -} - -static inline void smtproutes_compile (int fdr, int fdw) -{ - static uint16_t const table[10][10] = - { - { 0x000a, 0x0001, 0x0000, 0x0205, 0x0002, 0x000b, 0x0103, 0x0103, 0x0103, 0x000b }, - { 0x000a, 0x0001, 0x0000, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001 }, - { 0x000b, 0x000b, 0x000b, 0x0102, 0x000b, 0x0004, 0x0102, 0x0102, 0x0102, 0x000b }, - { 0x000b, 0x0103, 0x000b, 0x0205, 0x000b, 0x000b, 0x0103, 0x0103, 0x0103, 0x000b }, - { 0x000b, 0x000b, 0x000b, 0x0205, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b }, - { 0x140a, 0x0107, 0x1400, 0x0409, 0x0006, 0x000b, 0x0107, 0x0107, 0x0107, 0x000b }, - { 0x000b, 0x000b, 0x000b, 0x0106, 0x000b, 0x0008, 0x0106, 0x0106, 0x0106, 0x000b }, - { 0x140a, 0x000b, 0x1400, 0x0409, 0x000b, 0x000b, 0x0107, 0x0107, 0x0107, 0x000b }, - { 0x140a, 0x000b, 0x1400, 0x0409, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b }, - { 0x180a, 0x000b, 0x1800, 0x000b, 0x000b, 0x000b, 0x0109, 0x000b, 0x000b, 0x000b } - } ; - cdbmaker cm = CDBMAKER_ZERO ; - stralloc sa = STRALLOC_ZERO ; - char buf[2048] ; - buffer b = BUFFER_INIT(&buffer_read, fdr, buf, 2048) ; - uint32_t relaypos = 0, relayend = 0 ; - uint8_t state = 0 ; - if (!cdbmake_start(&cm, fdw)) qmailr_tempsys("Unable to cdbmake_start") ; - - while (state < 0x0a) - { - char c = getnext(&b) ; - uint16_t val = table[state][cclass(c)] ; - state = val & 0x000f ; - if (val & 0x0100) - { - if (!stralloc_catb(&sa, &c, 1)) qmailr_tempsys("Unable to grow stralloc") ; - } - if (val & 0x0200) - { - relaypos = sa.len + 1 ; - if (!stralloc_catb(&sa, "\0\0\31", 3)) qmailr_tempsys("Unable to grow stralloc") ; - } - if (val & 0x0400) - { - if (!stralloc_0(&sa)) qmailr_tempsys("Unable to grow stralloc") ; - relayend = sa.len ; - } - if (val & 0x0800) - { - uint16_t port ; - if (!stralloc_0(&sa)) qmailr_tempsys("Unable to grow stralloc") ; - if (!uint160_scan(sa.s + relayend, &port)) qmailr_temp("Invalid port in control/smtproutes") ; - uint16_pack_big(sa.s + relaypos, port) ; - } - if (val & 0x0100) - { - if (relaypos > 1 || relayend > 2 + relaypos) - if (!cdbmake_add(&cm, sa.s, relaypos, sa.s + relaypos, relayend - relaypos)) - qmailr_tempsys("Unable to cdbmake_add") ; - sa.len = 0 ; - } - } - if (state != 0x0a) qmailr_temp("Syntax error in control/smtproutes") ; - stralloc_free(&sa) ; - if (!cdbmake_finish(&cm)) qmailr_tempsys("Unable to cdbmake_finish") ; -} - -int smtproutes_init (smtproutes *routes) -{ - static char const *cdbfile = SMTPD_STARTTLS_PROXY_QMAIL_HOME "/run/qmail-remote/smtproutes.cdb" ; - static char const *lckfile = SMTPD_STARTTLS_PROXY_QMAIL_HOME "/run/qmail-remote/smtproutes.lock" ; - static char const *txtfile = SMTPD_STARTTLS_PROXY_QMAIL_HOME "/control/smtproutes" ; - static size_t const cdblen = sizeof(cdbfile) - 1 ; - int fdl = openc_create(lckfile) ; - if (fdl == -1) qmailr_tempsys("Unable to open run/qmail-remote/smtproutes.lock") ; - if (fd_lock(fdl, 1, 0) == -1) qmailr_tempsys("Unable to lock run/qmail-remote/smtproutes.lock") ; - - int fdc = openc_read(cdbfile) ; - if (fdc >= 0) - { - struct stat stc, str ; - if (fstat(fdc, &stc) == -1) qmailr_tempsys("Unable to fstat run/qmail-remote/smtproutes.cdb") ; - if (stat(txtfile, &str) == -1) - { - if (errno != ENOENT) qmailr_tempsys("Unable to fstat control/smtproutes") ; - unlink_void(cdbfile) ; - fd_close(fdc) ; - goto zero ; - } - if (timespec_cmp(&stc.st_mtim, &str.st_mtim) > 0) goto useit ; - fd_close(fdc) ; - } - - int fdr = openc_read(txtfile) ; - if (fdr == -1) - { - if (errno != ENOENT) qmailr_tempsys("Unable to open control/smtproutes") ; - goto zero ; - } - - { - char tmp[cdblen + 8] ; - memcpy(tmp, cdbfile, cdblen) ; - memcpy(tmp + cdblen, ":XXXXXX", 8) ; - fdc = mkstemp(tmp) ; - if (fdc == -1) qmailr_tempsys("Unable to mkstemp") ; - smtproutes_compile(fdr, fdc) ; - if (lseek(fdc, 0, SEEK_SET) == -1) qmailr_tempsys("Unable to lseek") ; - if (fsync(fdc) == -1) qmailr_tempsys("Unable to fsync run/qmail-remote/smtproutes.cdb") ; - fd_close(fdr) ; - if (rename(tmp, cdbfile) == -1) unlink_void(tmp) ; - } - - useit: - if (!cdb_init_fromfd(&routes->map, fdc)) qmailr_tempsys("Unable to mmap run/qmail-remote/smtproutes.cdb") ; - fd_close(fdc) ; - fd_close(fdl) ; - return 1 ; - - zero: - fd_close(fdl) ; - errno = 0 ; - return 0 ; -} - -int smtproutes_match (smtproutes const *routes, char const *s, stralloc *sa, size_t *pos, uint16_t *port) -{ - cdb_data data ; - int r = cdb_find(&routes->map, &data, s, strlen(s)) ; - if (r == -1) qmailr_temp("Invalid run/qmail-remote/smtproutes.cdb") ; - if (!r) return 0 ; - if (data.len < 3) return 0 ; - if (data.s[data.len - 1]) qmailr_temp("Invalid run/qmail-remote/smtproutes.cdb") ; - *pos = sa->len ; - uint16_unpack_big(data.s, port) ; - if (!stralloc_catb(sa, data.s + 2, data.len - 2)) qmailr_tempsys("Unable to grow stralloc") ; - return 1 ; -} - -void smtproutes_free (smtproutes *routes) -{ - cdb_free(&routes->map) ; -} -- cgit v1.3.1