diff options
27 files changed, 556 insertions, 28 deletions
@@ -1,9 +1,9 @@ /*.pc *.o -*.a +*.a.xyzzy *.lo -*.so -*.so.* +*.so.xyzzy /config.mak /src/include/smtpd-starttls-proxy/config.h /smtpd-starttls-proxy-io +/qmail-smtpc @@ -1,4 +1,4 @@ -Copyright (c) 2021-2025 Laurent Bercot <ska-skaware@skarnet.org> +Copyright (c) 2021-2026 Laurent Bercot <ska-skaware@skarnet.org> Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -6,10 +6,9 @@ Build Instructions - A POSIX-compliant C development environment - GNU make version 3.81 or later - - skalibs version 2.14.5.0 or later: https://skarnet.org/software/skalibs/ - - s6 version 2.13.3.0 or later: https://skarnet.org/software/s6/ - - (optional but recommended): s6-networking version 2.7.2.0 or later: - https://skarnet.org/software/s6-networking/ + - skalibs version 2.14.5.1 or later: https://skarnet.org/software/skalibs/ + - s6 version 2.14.0.1 or later: https://skarnet.org/software/s6/ + - s6-networking version 2.7.2.1 or later: https://skarnet.org/software/s6-networking/ This software will run on any operating system that implements POSIX.1-2024, available at: @@ -24,6 +24,7 @@ PC_TARGETS := LIB_DEFS := BIN_SYMLINKS := TEST_BINS := +EXTRA_INSTALL := -include config.mak include package/targets.mak @@ -42,6 +43,7 @@ endif lib$(2).pc: exec env \ + prefix="$(prefix)" \ library="$(2)" \ includedir="$(includedir)" \ dynlibdir="$(dynlibdir)" \ @@ -123,7 +125,7 @@ ifneq ($(strip $(ALL_BINS)$(SHARED_LIBS)),) exec $(STRIP) -R .note -R .comment $(ALL_BINS) $(SHARED_LIBS) endif -install: install-dynlib install-libexec install-bin install-symlinks install-lib install-include install-pkgconfig +install: install-dynlib install-libexec install-bin install-symlinks install-lib install-include install-pkgconfig $(EXTRA_INSTALL) install-dynlib: $(SHARED_LIBS:lib%.$(SHLIB_EXT).xyzzy=$(DESTDIR)$(dynlibdir)/lib%.$(SHLIB_EXT)) install-libexec: $(LIBEXEC_TARGETS:%=$(DESTDIR)$(libexecdir)/%) install-bin: $(BIN_TARGETS:%=$(DESTDIR)$(bindir)/%) @@ -1,5 +1,11 @@ Changelog for smtpd-starttls-proxy. +In 0.1.0.0 +---------- + + - New binary: qmail-remote. + + In 0.0.2.1 ---------- @@ -22,4 +22,3 @@ STARTTLS on behalf of the server. Please use the <skaware at list.skarnet.org> mailing-list for questions about smtpd-starttls-proxy. - @@ -48,6 +48,8 @@ Optional features: --enable-pkgconfig Build and install .pc files for pkg-config [disabled] --enable-slashpackage[=ROOT] assume /package installation at ROOT [disabled] --enable-absolute-paths hardcode absolute BINDIR/foobar paths in binaries [disabled] + --with-qmail-home=DIR qmail installation directory [/var/qmail] + --with-qmailr-ids=uid:gid qmail-remote user:group [qmailr:qmail] EOF exit 0 @@ -168,7 +170,8 @@ deplibpath='' vpaths='' vpathd='' build= - +qmailhome=/var/qmail +qmailrids=qmailr:qmail for arg ; do case "$arg" in @@ -206,7 +209,8 @@ for arg ; do --disable-slashpackage) sproot= ; slashpackage=false ;; --enable-absolute-paths|--enable-absolute-paths=yes) abspath=true ;; --disable-absolute-paths|--enable-absolute-paths=no) abspath=false ;; - + --with-qmail-home=*) qmailhome=${arg#*=} ;; + --with-qmailr-ids=*) qmailrids=${arg#*=} ;; --enable-*|--disable-*|--with-*|--without-*|--*dir=*) ;; --enable-*|--disable-*|--with-*|--without-*|--*dir=*) ;; --host=*|--target=*) target=${arg#*=} ;; @@ -235,7 +239,7 @@ fi # Expand installation directories stripdir prefix -for i in exec_prefix dynlibdir libexecdir bindir libdir includedir sysconfdir pkgconfdir sysdeps sproot ; do +for i in exec_prefix dynlibdir libexecdir bindir libdir includedir sysconfdir pkgconfdir sysdeps sproot qmailhome ; do eval tmp=\${$i} eval $i=$tmp stripdir $i @@ -512,6 +516,8 @@ if $allpic ; then else echo "STATIC_LIBS_ARE_PIC :=" fi +echo "QMAIL_HOME := $qmailhome" +echo "QMAILR_IDS := $qmailrids" exec 1>&3 3>&- echo " ... done." @@ -544,6 +550,8 @@ else fi echo "#define ${package_macro_name}_LIBEXECPREFIX \"$libexecdir/\"" echo "#define ${package_macro_name}_SYSCONFPREFIX \"$sysconfdir/\"" +echo "#undef ${package_macro_name}_QMAIL_HOME" +echo "#define ${package_macro_name}_QMAIL_HOME \"$qmailhome\"" echo echo "#endif" diff --git a/doc/index.html b/doc/index.html index 89ef1d8..fc355d7 100644 --- a/doc/index.html +++ b/doc/index.html @@ -34,16 +34,13 @@ inetd-like mail servers that do not already support it. <li> A POSIX-compliant system with a standard C development environment </li> <li> GNU make, version 3.81 or later </li> <li> <a href="//skarnet.org/software/skalibs/">skalibs</a> version -2.14.5.0 or later. It's a build-time requirement. It's also a run-time +2.14.5.1 or later. It's a build-time requirement. It's also a run-time requirement if you link against the shared version of the skalibs library. </li> <li> <a href="//skarnet.org/software/s6/">s6</a> version -2.13.3.0 or later. It's a build-time and run-time requirement. </li> - <li> Recommended at run-time: <a href="//skarnet.org/software/s6-networking/">s6-networking</a> version -2.7.2.0 or later. It's not a requirement, but you won't be able -to do much with smtpd-starttls-proxy if you don't have a suitable -UCSPI-TLS server tool, and <a href="//skarnet.org/software/s6-networking/">s6-networking</a> -provides a simple one. </li> +2.14.0.1 or later. It's a build-time and run-time requirement. </li> + <li> <a href="//skarnet.org/software/s6-networking/">s6-networking</a> version +2.7.2.1 or later. It's a build-time and run-time requirement. </li> </ul> <h3> Licensing </h3> @@ -57,8 +54,8 @@ provides a simple one. </li> <ul> <li> The current released version of smtpd-starttls-proxy is -<a href="smtpd-starttls-proxy-0.0.2.1.tar.gz">0.0.2.1</a>. -You can access its checksum <a href="smtpd-starttls-proxy-0.0.2.1.tar.gz.sha256">here</a>. </li> +<a href="smtpd-starttls-proxy-0.1.0.0.tar.gz">0.1.0.0</a>. +You can access its checksum <a href="smtpd-starttls-proxy-0.1.0.0.tar.gz.sha256">here</a>. </li> <li> You can checkout a copy of the <a href="//git.skarnet.org/cgi-bin/cgit.cgi/smtpd-starttls-proxy/">smtpd-starttls-proxy git repository</a>: @@ -90,6 +87,7 @@ the previous versions of smtpd-starttls-proxy and the current one. </li> <ul> <li><a href="smtpd-starttls-proxy-io.html">The <tt>smtpd-starttls-proxy-io</tt> program</a></li> +<li><a href="qmail-remote.html">The <tt>qmail-remote</tt> program</a></li> </ul> <h2> Related resources </h2> diff --git a/doc/upgrade.html b/doc/upgrade.html index b07e8d7..7aa6237 100644 --- a/doc/upgrade.html +++ b/doc/upgrade.html @@ -18,6 +18,17 @@ <h1> What has changed in smtpd-starttls-proxy </h1> +<h2> in 0.1.0.0 </h2> + +<ul> + <li> <a href="//skarnet.org/software/skalibs/">skalibs</a> +dependency bumped to 2.14.5.1 </li> + <li> <a href="//skarnet.org/software/s6/">s6</a> +dependency bumped to 2.14.0.1 </li> + <li> <a href="//skarnet.org/software/s6-networking/">s6-networking</a> +dependency bumped to 2.7.2.1 and made mandatory. </li> +</ul> + <h2> in 0.0.2.1 </h2> <ul> diff --git a/package/configure-snippets/configure_case_lines b/package/configure-snippets/configure_case_lines index 8b13789..d0af7a0 100644 --- a/package/configure-snippets/configure_case_lines +++ b/package/configure-snippets/configure_case_lines @@ -1 +1,2 @@ - + --with-qmail-home=*) qmailhome=${arg#*=} ;; + --with-qmailr-ids=*) qmailrids=${arg#*=} ;; diff --git a/package/configure-snippets/configure_expand_dirs b/package/configure-snippets/configure_expand_dirs index 8b13789..f9a8b46 100644 --- a/package/configure-snippets/configure_expand_dirs +++ b/package/configure-snippets/configure_expand_dirs @@ -1 +1 @@ - +qmailhome diff --git a/package/configure-snippets/configure_generate_configh b/package/configure-snippets/configure_generate_configh index 8b13789..d9bcacf 100644 --- a/package/configure-snippets/configure_generate_configh +++ b/package/configure-snippets/configure_generate_configh @@ -1 +1,3 @@ +echo "#undef ${package_macro_name}_QMAIL_HOME" +echo "#define ${package_macro_name}_QMAIL_HOME \"$qmailhome\"" diff --git a/package/configure-snippets/configure_generate_make b/package/configure-snippets/configure_generate_make index 8b13789..54fa00c 100644 --- a/package/configure-snippets/configure_generate_make +++ b/package/configure-snippets/configure_generate_make @@ -1 +1,3 @@ +echo "QMAIL_HOME := $qmailhome" +echo "QMAILR_IDS := $qmailrids" diff --git a/package/configure-snippets/configure_help_options b/package/configure-snippets/configure_help_options index 8b13789..b439bc5 100644 --- a/package/configure-snippets/configure_help_options +++ b/package/configure-snippets/configure_help_options @@ -1 +1,3 @@ + --with-qmail-home=DIR qmail installation directory [/var/qmail] + --with-qmailr-ids=uid:gid qmail-remote user:group [qmailr:qmail] diff --git a/package/configure-snippets/configure_init_vars b/package/configure-snippets/configure_init_vars index 8b13789..a30b387 100644 --- a/package/configure-snippets/configure_init_vars +++ b/package/configure-snippets/configure_init_vars @@ -1 +1,2 @@ - +qmailhome=/var/qmail +qmailrids=qmailr:qmail diff --git a/package/deps.mak b/package/deps.mak index 343c0fc..f17b9c0 100644 --- a/package/deps.mak +++ b/package/deps.mak @@ -2,8 +2,19 @@ # This file has been generated by tools/gen-deps.sh # +src/qmail-smtpc/qmail-smtpc.o src/qmail-smtpc/qmail-smtpc.lo: src/qmail-smtpc/qmail-smtpc.c src/qmail-smtpc/qmailr.h src/include/smtpd-starttls-proxy/config.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/smtproutes.o src/qmail-smtpc/smtproutes.lo: src/qmail-smtpc/smtproutes.c src/qmail-smtpc/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_error.o src/qmail-smtpc/qmailr_tcpto.o +else +libqmailr.a.xyzzy:src/qmail-smtpc/qmailr_error.lo src/qmail-smtpc/qmailr_tcpto.lo +endif +qmail-smtpc: EXTRA_LIBS := +qmail-smtpc: src/qmail-smtpc/qmail-smtpc.o src/qmail-smtpc/smtproutes.o libqmailr.a.xyzzy -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 := +INTERNAL_LIBS := libqmailr.a.xyzzy diff --git a/package/info b/package/info index c93dc0c..8c539c9 100644 --- a/package/info +++ b/package/info @@ -1,4 +1,4 @@ package=smtpd-starttls-proxy -version=0.0.2.1 +version=0.1.0.0 category=mail package_macro_name=SMTPD_STARTTLS_PROXY diff --git a/package/modes b/package/modes index 07461a5..e22534f 100644 --- a/package/modes +++ b/package/modes @@ -1 +1,2 @@ smtpd-starttls-proxy-io 0755 +qmail-smtpc 0755 diff --git a/package/targets.mak b/package/targets.mak index 9faea17..c4707f9 100644 --- a/package/targets.mak +++ b/package/targets.mak @@ -1,5 +1,20 @@ BIN_TARGETS := \ -smtpd-starttls-proxy-io +smtpd-starttls-proxy-io \ +qmail-smtpc LIBEXEC_TARGETS := +EXTRA_INSTALL += install-qmailr + +QMAILR_UID := $(firstword $(subst :, ,$(QMAILR_IDS))) +QMAILR_GID := $(lastword $(subst :, ,$(QMAILR_IDS))) + +install-qmailr: + mkdir -p -- $(QMAIL_HOME)/run/qmail-remote + chgrp -- $(QMAILR_GID) $(QMAIL_HOME)/run + chmod 02750 $(QMAIL_HOME)/run + chown -- $(QMAILR_IDS) $(QMAIL_HOME)/run/qmail-remote + chmod 02700 $(QMAIL_HOME)/run/qmail-remote + touch -- $(QMAIL_HOME)/run/qmail-remote/tcpto6 + chown -- $(QMAILR_IDS) $(QMAIL_HOME)/run/qmail-remote + chmod 0640 $(QMAIL_HOME)/run/qmail-remote 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 <unistd.h> + +#include <smtpd-starttls-proxy/config.h> +#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 <skalibs/cdb.h> + + +/* 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 <stdint.h> + +#include <skalibs/gccattributes.h> +#include <skalibs/tai.h> + +/* 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 <string.h> +#include <unistd.h> +#include <errno.h> + +#include <skalibs/buffer.h> + +#include <smtpd-starttls-proxy/config.h> + +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 <stdint.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> + +#include <skalibs/uint32.h> +#include <skalibs/uint64.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/cdb.h> +#include <skalibs/tai.h> +#include <skalibs/djbunix.h> + +#include <smtpd-starttls-proxy/config.h> +#include "qmailr.h" + +#include <skalibs/posixishard.h> + + +/* + 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 <skalibs/bsdsnowflake.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" + + +/* + 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 ; +} |
