aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmail-remote/smtproutes.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmail-remote/smtproutes.c')
-rw-r--r--src/qmail-remote/smtproutes.c240
1 files changed, 240 insertions, 0 deletions
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 <skalibs/bsdsnowflake.h>
+
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include <skalibs/stat.h>
+#include <skalibs/posixplz.h>
+#include <skalibs/uint16.h>
+#include <skalibs/buffer.h>
+#include <skalibs/cdb.h>
+#include <skalibs/cdbmake.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbtime.h>
+#include <skalibs/djbunix.h>
+
+#include <smtpd-starttls-proxy/config.h>
+#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) ;
+}