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/smtproutes.c | 212 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 src/qmail-smtpc/smtproutes.c (limited to 'src/qmail-smtpc/smtproutes.c') 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