diff options
Diffstat (limited to 'src/qmail-remote/qmail-remote.c')
| -rw-r--r-- | src/qmail-remote/qmail-remote.c | 262 |
1 files changed, 244 insertions, 18 deletions
diff --git a/src/qmail-remote/qmail-remote.c b/src/qmail-remote/qmail-remote.c index 870c797..2fa98ab 100644 --- a/src/qmail-remote/qmail-remote.c +++ b/src/qmail-remote/qmail-remote.c @@ -1,29 +1,197 @@ /* ISC license. */ #include <string.h> +#include <strings.h> #include <stdint.h> #include <unistd.h> +#include <errno.h> +#include <limits.h> +#include <skalibs/types.h> +#include <skalibs/env.h> +#include <skalibs/exec.h> +#include <skalibs/fmtscan.h> +#include <skalibs/buffer.h> #include <skalibs/cdb.h> #include <skalibs/stralloc.h> #include <skalibs/sig.h> #include <skalibs/tai.h> +#include <skalibs/djbunix.h> +#include <skalibs/socket.h> #include <skalibs/ip46.h> +#include <skalibs/unix-timed.h> +#include <s6-networking/config.h> #include <smtpd-starttls-proxy/config.h> #include "qmailr.h" #include "qmail-remote.h" #define dieusage() qmailr_perm("qmail-remote was invoked improperly") +static inline void exec_tls (int fd, char const *fmtip, unsigned int timeoutconnect, unsigned int timeoutremote, qmailr_tls const *qtls, size_t helopos, size_t const *eaddrpos, unsigned int n, char const *storage) gccattr_noreturn ; +static inline void exec_tls (int fd, char const *fmtip, unsigned int timeoutconnect, unsigned int timeoutremote, qmailr_tls const *qtls, size_t helopos, size_t const *eaddrpos, unsigned int n, char const *storage) +{ + unsigned int m = 0 ; + char fmtfd[UINT_FMT] ; + char fmtt[UINT_FMT] ; + char fmtk[UINT_FMT] ; + char const *argv[20 + n] ; + + if (!env_mexec("TLS_UID", 0) || !env_mexec("TLS_GID", 0) + || !env_mexec(qtls->flagtadir ? "CADIR" : "CAFILE", storage + qtls->tapos)) dienomem() ; + if (qtls->flagclientcert) + { + if (!env_mexec("CERTFILE", storage + qtls->certpos) + || !env_mexec("KEYFILE", storage + qtls->keypos)) dienomem() ; + } + + { + int devnull = open_readb("/dev/null") ; + if (devnull >= 0) + { + if (devnull < 3) qmailr_temp("weird fd configuration") ; + fd_move(2, devnull) ; + } + } + + fmtfd[uint_fmt(fmtfd, (unsigned int)fd)] = 0 ; + fmtt[uint_fmt(fmtt, timeoutremote)] = 0 ; + fmtk[uint_fmt(fmtk, timeoutconnect > UINT_MAX/1000 ? UINT_MAX : timeoutconnect * 1000)] = 0 ; + + argv[m++] = S6_NETWORKING_EXTBINPREFIX "s6-tlsc" ; + argv[m++] = "-Sjzv0" ; + argv[m++] = "-K" ; + argv[m++] = fmtk ; + argv[m++] = "-6" ; + argv[m++] = fmtfd ; + argv[m++] = "-7" ; + argv[m++] = fmtfd ; + argv[m++] = "--" ; + + argv[m++] = SMTPD_STARTTLS_PROXY_LIBEXECPREFIX "qmail-remote-io" ; + argv[m++] = "-t" ; + argv[m++] = fmtt ; + argv[m++] = "-6" ; + argv[m++] = fmtfd ; + argv[m++] = "-7" ; + argv[m++] = fmtfd ; + argv[m++] = "--" ; + argv[m++] = fmtip ; + argv[m++] = storage + helopos ; + for (unsigned int i = 0 ; i < n ; i++) argv[m++] = storage + eaddrpos[i] ; + argv[m++] = 0 ; + mexec(argv) ; + qmailr_tempusys("exec ", argv[0]) ; +} + +static inline void exec_notls (int fd, char const *fmtip, unsigned int timeoutremote, size_t helopos, size_t const *eaddrpos, unsigned int n, char const *storage) gccattr_noreturn ; +static inline void exec_notls (int fd, char const *fmtip, unsigned int timeoutremote, size_t helopos, size_t const *eaddrpos, unsigned int n, char const *storage) +{ + unsigned int m = 0 ; + char fmtfd[UINT_FMT] ; + char fmtt[UINT_FMT] ; + char const *argv[11 + n] ; + + fmtfd[uint_fmt(fmtfd, (unsigned int)fd)] = 0 ; + fmtt[uint_fmt(fmtt, timeoutremote)] = 0 ; + argv[m++] = SMTPD_STARTTLS_PROXY_LIBEXECPREFIX "qmail-remote-io" ; + argv[m++] = "-t" ; + argv[m++] = fmtt ; + argv[m++] = "-6" ; + argv[m++] = fmtfd ; + argv[m++] = "-7" ; + argv[m++] = fmtfd ; + argv[m++] = "--" ; + argv[m++] = fmtip ; + argv[m++] = storage + helopos ; + for (unsigned int i = 0 ; i < n ; i++) argv[m++] = storage + eaddrpos[i] ; + argv[m++] = 0 ; + exec(argv) ; + qmailr_tempusys("exec ", argv[0]) ; +} + +static int smtp_start (buffer *in, buffer *out, char const *helohost, unsigned int timeout, char const *fmtip) +{ + int hastls = 0 ; + tain deadline ; + char line[1024] ; + + int r = qmailr_smtp_read_answer(in, line, 1024, timeout) ; + if (r == -1) qmailr_tempusys("read from ", fmtip) ; + if (!r) qmailr_temp("Connected to ", fmtip, " but connection died") ; + if (r != 220) + { + qmailr_smtp_quit(out, timeout) ; + qmailr_temp("Connected to ", fmtip, " but greeting failed") ; + } + + buffer_putnoflush(out, "EHLO ", 5) ; + buffer_putsnoflush(out, helohost) ; + buffer_putnoflush(out, "\r\n", 2) ; + + tain_addsec_g(&deadline, timeout) ; + if (!buffer_timed_flush_g(out, &deadline)) + qmailr_tempusys("send ", "EHLO", " to ", fmtip) ; + + tain_addsec_g(&deadline, timeout) ; + for (;;) + { + unsigned int code = 250 ; + int r = qmailr_smtp_read_line(in, line, 1024, &code, &deadline) ; + if (r == -1) qmailr_tempusys("read from ", fmtip) ; + if (!r) qmailr_temp("Connected to ", fmtip, " but connection died") ; + if (code != 250) qmailr_temp("Connected to ", fmtip, " but it speaks a weird protocol") ; + if (!strcasecmp(line + 4, "STARTTLS")) hastls = 1 ; + if (r == 1) break ; + } + return hastls ; +} + +static void attempt_smtp (int fd, char const *ip, int is6, unsigned int timeoutconnect, unsigned int timeoutremote, qmailr_tls const *qtls, size_t helopos, size_t const *eaddrpos, unsigned int n, char const *storage) +{ + int hastls ; + char inbuf[2048] ; + char outbuf[2048] ; + char fmtip[IP6_FMT] ; + buffer in = BUFFER_INIT(&buffer_read, fd, inbuf, 2048) ; + buffer out = BUFFER_INIT(&buffer_write, fd, outbuf, 2048) ; + if (is6) fmtip[ip6_fmt(fmtip, ip)] = 0 ; + else fmtip[ip4_fmt(fmtip, ip)] = 0 ; + + hastls = smtp_start(&in, &out, storage + helopos, timeoutremote, fmtip) ; + if (qtls->flagwanttls) + { + if (hastls) + { + int r ; + tain deadline ; + char line[1024] ; + buffer_putsnoflush(&out, "STARTTLS\r\n") ; + tain_addsec_g(&deadline, timeoutremote) ; + if (!buffer_timed_flush_g(&out, &deadline)) qmailr_tempusys("send ", "STARTTLS", " to ", fmtip) ; + r = qmailr_smtp_read_answer(&in, line, 1024, timeoutremote) ; + if (r == -1) qmailr_tempusys("read from ", fmtip) ; + else if (!r) + { + qmailr_smtp_quit(&out, timeoutremote) ; + qmailr_temp("Connected to ", fmtip, " but connection died") ; + } + else if (r == 220) exec_tls(fd, fmtip, timeoutconnect, timeoutremote, qtls, helopos, eaddrpos, n, storage) ; + if (qtls->strictness) return ; + } + else if (qtls->strictness >= 2) return ; + } + exec_notls(fd, fmtip, timeoutremote, helopos, eaddrpos, n, storage) ; +} + int main (int argc, char const *const *argv) { stralloc storage = STRALLOC_ZERO ; stralloc ipme4 = STRALLOC_ZERO ; stralloc ipme6 = STRALLOC_ZERO ; - qmailr_tls qt = QMAILR_TLS_ZERO ; + qmailr_tls qtls = QMAILR_TLS_ZERO ; smtproutes routes = SMTPROUTES_ZERO ; - unsigned int timeoutconnect = 60, timeoutremote = 1200 ; + unsigned int timeoutconnect = 60, timeoutremote = 1200, timeoutdns = 0 ; char const *host ; size_t mepos, helopos, hostpos = 0 ; uint16_t port = 25 ; @@ -31,8 +199,8 @@ int main (int argc, char const *const *argv) if (argc-- < 4) dieusage() ; argv++ ; - if (chdir(SMTPD_STARTTLS_PROXY_QMAIL_HOME) == -1) qmailr_temp("Unable to chdir to " SMTPD_STARTTLS_PROXY_QMAIL_HOME) ; - if (sig_altignore(SIGPIPE) == -1) qmailr_tempsys("Unable to ignore SIGPIPE") ; + if (chdir(SMTPD_STARTTLS_PROXY_QMAIL_HOME) == -1) qmailr_tempusys("chdir to ", SMTPD_STARTTLS_PROXY_QMAIL_HOME) ; + if (sig_altignore(SIGPIPE) == -1) qmailr_tempusys("ignore SIGPIPE") ; host = *argv++ ; argc-- ; tain_now_set_stopwatch_g() ; @@ -40,27 +208,27 @@ int main (int argc, char const *const *argv) /* init control */ r = qmailr_control_read("control/me", &storage, &mepos) ; - if (r == -1) qmailr_tempsys("Unable to read control/me") ; - else if (!r) qmailr_temp("Invalid control/me") ; + if (r == -1) qmailr_tempusys("read ", "control/me") ; + else if (!r) qmailr_temp("Invalid ", "control/me") ; r = qmailr_control_read("control/helohost", &storage, &helopos) ; - if (r == -1) qmailr_tempsys("Unable to read control/helohost") ; + if (r == -1) qmailr_tempusys("read ", "control/helohost") ; else if (!r) helopos = mepos ; r = qmailr_control_readint("control/timeoutconnect", &timeoutconnect, &storage) ; - if (r == -1) qmailr_tempsys("Unable to read control/timeoutconnect") ; + if (r == -1) qmailr_tempusys("read ", "control/timeoutconnect") ; r = qmailr_control_readint("control/timeoutremote", &timeoutremote, &storage) ; - if (r == -1) qmailr_tempsys("Unable to read control/timeoutremote") ; + if (r == -1) qmailr_tempusys("read ", "control/timeoutremote") ; + r = qmailr_control_readint("control/timeoutdns", &timeoutdns, &storage) ; + if (r == -1) qmailr_tempusys("read ", "control/timeoutdns") ; if (!qmailr_control_readiplist("control/ipme", &ipme4, &ipme6)) - qmailr_tempsys("Unable to read control/ipme") ; - stralloc_shrink(&ipme4) ; - stralloc_shrink(&ipme6) ; + qmailr_tempusys("read ", "control/ipme") ; qsort(ipme4.s, ipme4.len >> 2, 4, &qmailr_memcmp4) ; qsort(ipme6.s, ipme6.len >> 4, 16, &qmailr_memcmp16) ; - if (!qmailr_tls_init(&qt, &storage)) - qmailr_tempsys("Unable to read TLS control files") ; + if (!qmailr_tls_init(&qtls, &storage)) + qmailr_tempusys("read ", "TLS control files") ; if (smtproutes_init(&routes)) { @@ -76,11 +244,69 @@ int main (int argc, char const *const *argv) { genalloc mxipind = GENALLOC_ZERO ; + mxip const *mxs ; size_t eaddrpos[argc] ; - dns_stuff(hostpos ? storage.s + hostpos : host, argv, argc, eaddrpos, &mxipind, &storage, timeoutconnect, ipme4.s, ipme4.len >> 2, ipme6.s, ipme6.len >> 4, !hostpos) ; - - } + size_t ntot = 0 ; + unsigned int mxn = dns_stuff(hostpos ? storage.s + hostpos : host, argv, argc, eaddrpos, &mxipind, &storage, timeoutdns, ipme4.s, ipme4.len >> 2, ipme6.s, ipme6.len >> 4, !hostpos) ; + if (!mxn) qmailr_perm("No suitable MX found for remote host") ; + stralloc_free(&ipme4) ; + stralloc_free(&ipme6) ; + mxs = genalloc_s(mxip, &mxipind) ; + for (unsigned int i = 0 ; i < mxn ; i++) ntot += mxs[i].n4 + mxs[i].n6 ; + if (!ntot) qmailr_perm("No suitable IP addresses for the MX") ; - _exit(0) ; + for (; qtls.flagwanttls && qtls.strictness == 1 ; qtls.flagwanttls = 0) + { + for (unsigned int i = 0 ; i < mxn ; i++) + { +#ifdef SKALIBS_IPV6_ENABLED + for (unsigned int j = 0 ; j < mxs[i].n6 ; j++) + { + char const *ip = storage.s + mxs[i].pos6 + (j << 4) ; + tain deadline ; + int fd ; + if (qmailr_tcpto_match(ip, 1)) continue ; + fd = socket_tcp6() ; + if (fd == -1) qmailr_tempusys("create socket") ; + tain_addsec_g(&deadline, timeoutconnect) ; + if (!socket_deadlineconnstamp6_g(fd, ip, port, &deadline)) + { + if (!qmailr_tcpto_update(ip, 1, errno == ETIMEDOUT)) + qmailr_tempusys("update ", "tcpto6") ; + fd_close(fd) ; + continue ; + } + if (!qmailr_tcpto_update(ip, 1, 0)) + qmailr_tempusys("update ", "tcpto6") ; + attempt_smtp(fd, ip, 1, timeoutconnect, timeoutremote, &qtls, helopos, eaddrpos, argc, storage.s) ; + fd_close(fd) ; + } +#endif + for (unsigned int j = 0 ; j < mxs[i].n4 ; j++) + { + char const *ip = storage.s + mxs[i].pos4 + (j << 2) ; + tain deadline ; + int fd ; + if (qmailr_tcpto_match(ip, 0)) continue ; + fd = socket_tcp4() ; + if (fd == -1) qmailr_tempusys("create socket") ; + tain_addsec_g(&deadline, timeoutconnect) ; + if (!socket_deadlineconnstamp4_g(fd, ip, port, &deadline)) + { + if (!qmailr_tcpto_update(ip, 0, errno == ETIMEDOUT)) + qmailr_tempusys("update ", "tcpto") ; + fd_close(fd) ; + continue ; + } + if (!qmailr_tcpto_update(ip, 0, 0)) + qmailr_tempusys("update ", "tcpto") ; + attempt_smtp(fd, ip, 0, timeoutconnect, timeoutremote, &qtls, helopos, eaddrpos, argc, storage.s) ; + fd_close(fd) ; + } + } + } + } + qmailr_tempusys("establish an SMTP connection") ; + _exit(101) ; /* not reached */ } |
