/* ISC license. */ #include #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 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[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][9] = { { 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 ; }