From 541b3256968eae128abd35e762f3e3a81885fa7c Mon Sep 17 00:00:00 2001 From: Laurent Bercot Date: Fri, 30 Jan 2026 11:07:43 +0000 Subject: Start some new work --- src/qmail-smtpc/deps-exe/qmail-smtpc | 3 + src/qmail-smtpc/deps-lib/qmailr | 3 + src/qmail-smtpc/qmail-smtpc.c | 11 ++ src/qmail-smtpc/qmail-smtpc.h | 9 ++ src/qmail-smtpc/qmailr.h | 27 +++++ src/qmail-smtpc/qmailr_error.c | 38 +++++++ src/qmail-smtpc/qmailr_tcpto.c | 167 +++++++++++++++++++++++++++ src/qmail-smtpc/smtproutes.c | 212 +++++++++++++++++++++++++++++++++++ 8 files changed, 470 insertions(+) create mode 100644 src/qmail-smtpc/deps-exe/qmail-smtpc create mode 100644 src/qmail-smtpc/deps-lib/qmailr create mode 100644 src/qmail-smtpc/qmail-smtpc.c create mode 100644 src/qmail-smtpc/qmail-smtpc.h create mode 100644 src/qmail-smtpc/qmailr.h create mode 100644 src/qmail-smtpc/qmailr_error.c create mode 100644 src/qmail-smtpc/qmailr_tcpto.c create mode 100644 src/qmail-smtpc/smtproutes.c (limited to 'src/qmail-smtpc') diff --git a/src/qmail-smtpc/deps-exe/qmail-smtpc b/src/qmail-smtpc/deps-exe/qmail-smtpc new file mode 100644 index 0000000..98605d9 --- /dev/null +++ b/src/qmail-smtpc/deps-exe/qmail-smtpc @@ -0,0 +1,3 @@ +smtproutes.o +libqmailr.a.xyzzy +-lskarnet diff --git a/src/qmail-smtpc/deps-lib/qmailr b/src/qmail-smtpc/deps-lib/qmailr new file mode 100644 index 0000000..294fde8 --- /dev/null +++ b/src/qmail-smtpc/deps-lib/qmailr @@ -0,0 +1,3 @@ +qmailr_error.o +qmailr_tcpto.o +-lskarnet diff --git a/src/qmail-smtpc/qmail-smtpc.c b/src/qmail-smtpc/qmail-smtpc.c new file mode 100644 index 0000000..b6a7c47 --- /dev/null +++ b/src/qmail-smtpc/qmail-smtpc.c @@ -0,0 +1,11 @@ +/* ISC license. */ + +#include + +#include +#include "qmailr.h" + +int main (int argc, char const *const *argv) +{ + _exit(0) ; +} diff --git a/src/qmail-smtpc/qmail-smtpc.h b/src/qmail-smtpc/qmail-smtpc.h new file mode 100644 index 0000000..7c178bb --- /dev/null +++ b/src/qmail-smtpc/qmail-smtpc.h @@ -0,0 +1,9 @@ +/* ISC license. */ + +#include + + +/* smtproutes */ + +extern int smtproutes_init (cdb *) ; + diff --git a/src/qmail-smtpc/qmailr.h b/src/qmail-smtpc/qmailr.h new file mode 100644 index 0000000..6ee9bfc --- /dev/null +++ b/src/qmail-smtpc/qmailr.h @@ -0,0 +1,27 @@ +/* ISC license. */ + +#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_tcpto */ + +extern int qmailr_tcpto_match (char const *, int) ; +extern int qmailr_tcpto_update (char const *, int, int) ; + diff --git a/src/qmail-smtpc/qmailr_error.c b/src/qmail-smtpc/qmailr_error.c new file mode 100644 index 0000000..fcdce54 --- /dev/null +++ b/src/qmail-smtpc/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-smtpc/qmailr_tcpto.c b/src/qmail-smtpc/qmailr_tcpto.c new file mode 100644 index 0000000..fc8841b --- /dev/null +++ b/src/qmail-smtpc/qmailr_tcpto.c @@ -0,0 +1,167 @@ +/* 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). +*/ + +static int memcmp4 (void const *a, void const *b) +{ + return memcmp(a, b, 4) ; +} + +static int memcmp16 (void const *a, void const *b) +{ + return memcmp(a, b, 16) ; +} + +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 ? &memcmp16 : &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 ? &memcmp16 : &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 ? &memcmp16 : &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/smtproutes.c b/src/qmail-smtpc/smtproutes.c new file mode 100644 index 0000000..08829c9 --- /dev/null +++ b/src/qmail-smtpc/smtproutes.c @@ -0,0 +1,212 @@ +/* ISC license. */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "qmailr.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 lock feels a bit too big, + the crit section can probably be made smaller, but the + current behaviour is safe and avoids retry heuristics. +*/ + +/* + Key to the control/smtproutes parser: + + 0 1 2 3 4 5 +st\ev EOF # \n : 0-9 other + +0 h n n +START END COMMENT START RELAY HOST HOST + +1 +COMMENT END COMMENT START COMMENT COMMENT COMMENT + +2 n h n n +HOST X HOST X RELAY HOST HOST + +3 ra n ra r n n +RELAY END RELAY START PORT RELAY RELAY + +4 pa pa n +PORT END X START X PORT X + +END=5, X=6 + +0x08 n push character +0x10 h compute host length +0x20 r compute relay length +0x40 p compute port +0x80 a add route entry +*/ + +static inline uint8_t cclass (char c) +{ + switch (c) + { + case 0 : return 0 ; + case '#' : return 1 ; + case '\n' : return 2 ; + case ':' : return 3 ; + case '0' : + case '1' : + case '2' : + case '3' : + case '4' : + case '5' : + case '6' : + case '7' : + case '8' : + case '9' : return 4 ; + default : break ; + } + return 5 ; +} + +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 uint8_t const table[5][6] = + { + { 0x05, 0x01, 0x00, 0x13, 0x0a, 0x0a }, + { 0x05, 0x01, 0x00, 0x01, 0x01, 0x01 }, + { 0x06, 0x0a, 0x06, 0x13, 0x0a, 0x0a }, + { 0xa5, 0x0b, 0xa0, 0x24, 0x0b, 0x0b }, + { 0xc5, 0x06, 0xc0, 0x06, 0x0c, 0x06 } + } ; + 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 < 5) + { + char c = getnext(&b) ; + uint8_t val = table[state][cclass(c)] ; + state = val & 0x07 ; + if (val & 0x08) + { + if (!stralloc_catb(&sa, &c, 1)) qmailr_tempsys("Unable to grow stralloc") ; + } + if (val & 0x10) + { + relaypos = sa.len + 1 ; + if (!stralloc_catb(&sa, "\0\0\31", 3)) qmailr_tempsys("Unable to grow stralloc") ; + } + if (val & 0x20) + { + if (!stralloc_0(&sa)) qmailr_tempsys("Unable to grow stralloc") ; + relayend = sa.len ; + } + if (val & 0x40) + { + 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 & 0x80) + { + if (!cdbmake_add(&cm, sa.s, relaypos, sa.s + relaypos, relayend - relaypos)) + qmailr_tempsys("Unable to cdbmake_add") ; + sa.len = 0 ; + } + } + if (state != 5) qmailr_temp("Syntax error in control/smtproutes") ; + stralloc_free(&sa) ; + if (!cdbmake_finish(&cm)) qmailr_tempsys("Unable to cdbmake_finish") ; +} + +int smtproutes_init (cdb *c) +{ + 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(c, 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 ; +} -- cgit v1.3.1