#include #include "alloc.h" #include "buffer.h" #include "byte.h" #include "case.h" #include "env.h" #include "exit.h" #include "fd.h" #include "fmt.h" #include "ip.h" #include "sig.h" #include "str.h" #include "stralloc.h" #include "timeout.h" #include "wait.h" #include "commands.h" #include "now.h" #include "tls_start.h" #define PORT_POP3S "995" #define FDIN 0 #define FDOUT 1 #define FDAUTH 3 #define FDLOG 5 static void die() { _exit(1); } static ssize_t saferead(int fd, char *buf, int len) { int r; r = timeoutread(1200, fd, buf, len); if (r <= 0) die(); return r; } static ssize_t safewrite(int fd, char *buf, int len) { int r; r = timeoutwrite(1200, fd, buf, len); if (r <= 0) die(); return r; } char outbuf[128]; buffer bo = BUFFER_INIT(safewrite, FDOUT, outbuf, sizeof(outbuf)); char inbuf[128]; buffer bi = BUFFER_INIT(saferead, FDIN, inbuf, sizeof(inbuf)); static void outs(char *s) { buffer_puts(&bo, s); } static void flush() { buffer_flush(&bo); } static void err(char *s) { outs("-ERR "); outs(s); outs("\r\n"); flush(); } /* Logging */ stralloc protocol = {0}; stralloc auth = {0}; char *localport; char *remoteip; char *remotehost; char strnum[FMT_ULONG]; char logbuf[512]; buffer bl = BUFFER_INIT(safewrite, FDLOG, logbuf, sizeof(logbuf)); static void logs(char *s) { if (buffer_puts(&bl, s) == -1) _exit(1); } static void logp(char *s) { logs(" P:"); logs(s); } static void logh(char *s1, char *s2) { logs(" S:"); logs(s1); logs(":"); logs(s2); } static void logu(char *s) { logs(" ?~ '"); logs(s); logs("'"); } static void logn(char *s) { if (buffer_puts(&bl, s) == -1) _exit(1); if (buffer_flush(&bl) == -1) _exit(1); } static void logpid() { strnum[fmt_ulong(strnum, getpid())] = 0; logs("qmail-popup: pid "); logs(strnum); logs(" "); } static void log_pop(char *s1, char *s2, char *s3, char *s4, char *s5, char *s6) { logpid(); logs(s1); logs(s2); logp(s3); logh(s4, s5), logu(s6), logn("\n"); } static void die_usage() { err("usage: popup hostname subprogram"); die(); } static void die_nomem() { err("out of memory"); die(); } static void die_pipe() { err("unable to open pipe"); die(); } static void die_write() { err("unable to write pipe"); die(); } static void die_fork() { err("unable to fork"); die(); } static void die_childcrashed() { err("aack, child crashed"); } static void die_badauth() { err("authorization failed"); } static void die_tls() { err("TLS startup failed"); die(); } static void die_notls() { err("TLS required but not negotiated"); log_pop("Reject::STLS::", "Any", "POP3", remoteip, remotehost, "unknown"); die(); } static void err_syntax() { err("syntax error"); } static void err_wantuser() { err("USER first"); } static void err_authoriz() { err("authorization first"); } static void okay() { outs("+OK \r\n"); flush(); } static void pop3_quit() { okay(); die(); } static void poplog_init() { if (!stralloc_copys(&protocol, "POP3")) die_nomem(); localport = env_get("TCP6LOCALPORT"); if (!localport) localport = env_get("TCPLOCALPORT"); if (!localport) localport = "unknown"; if (!case_diffs(localport, PORT_POP3S)) if (!stralloc_cats(&protocol, "S")) die_nomem(); remoteip = env_get("TCP6REMOTEIP"); if (remoteip && byte_equal(remoteip, 7, V4MAPPREFIX)) remoteip = remoteip + 7; if (!remoteip) remoteip = env_get("TCPREMOTEIP"); if (!remoteip) remoteip = "unknown"; remotehost = env_get("TCP6REMOTEHOST"); if (!remotehost) remotehost = env_get("TCPREMOTEHOST"); if (!remotehost) remotehost = "unknown"; } char unique[FMT_ULONG + FMT_ULONG + 3]; char *hostname; stralloc username = {0}; int seenuser = 0; char **childargs; buffer ba; char authbuf[128]; int stls = 0; int seenstls = 0; int apop = 0; static void doanddie(char *user, unsigned int userlen, char *pass) /* userlen: including 0 byte */ { int child; int wstat; int pi[2]; if (fd_copy(2, 1) == -1) die_pipe(); close(FDAUTH); if (pipe(pi) == -1) die_pipe(); if (pi[0] != FDAUTH) die_pipe(); switch (child = fork()) { case -1: die_fork(); case 0: close(pi[1]); sig_pipedefault(); execvp(*childargs, childargs); _exit(1); } close(pi[0]); buffer_init(&ba, write, pi[1], authbuf, sizeof(authbuf)); if (buffer_put(&ba, user, userlen) == -1) die_write(); if (buffer_put(&ba, pass, str_len(pass) + 1) == -1) die_write(); if (buffer_puts(&ba, "<") == -1) die_write(); if (buffer_puts(&ba, unique) == -1) die_write(); if (buffer_puts(&ba, hostname) == -1) die_write(); if (buffer_put(&ba, ">", 2) == -1) die_write(); if (buffer_flush(&ba) == -1) die_write(); close(pi[1]); byte_zero(pass, str_len(pass)); byte_zero(authbuf, sizeof(authbuf)); if (wait_pid(&wstat, child) == -1) die(); if (wait_crashed(wstat)) die_childcrashed(); if (!stralloc_0(&auth)) die_nomem(); if (!stralloc_0(&protocol)) die_nomem(); if (wait_exitcode(wstat)) { die_badauth(); log_pop("Reject::AUTH::", auth.s, protocol.s, remoteip, remotehost, user); } else { log_pop("Accept::AUTH::", auth.s, protocol.s, remoteip, remotehost, user); } die(); } static void pop3_greet() { char *s; s = unique; s += fmt_uint(s, getpid()); *s++ = '.'; s += fmt_ulong(s, (unsigned long)now()); *s++ = '@'; *s++ = 0; if (!apop) { outs("+OK\r\n"); } else { outs("+OK <"); outs(unique); outs(hostname); outs(">\r\n"); } flush(); } static void pop3_user(char *arg) { if (stls == 2 && !seenstls) die_notls(); if (!*arg) { err_syntax(); return; } okay(); seenuser = 1; if (!stralloc_copys(&username, arg)) die_nomem(); if (!stralloc_0(&username)) die_nomem(); } static void pop3_pass(char *arg) { if (!seenuser) { err_wantuser(); return; } if (!*arg) { err_syntax(); return; } if (!stralloc_copys(&auth, "User")) die_nomem(); doanddie(username.s, username.len, arg); } static void pop3_apop(char *arg) { char *space; if (stls == 2 && !seenstls) die_notls(); space = arg + str_chr(arg, ' '); if (!*space) { err_syntax(); return; } *space++ = 0; if (!stralloc_copys(&auth, "Apop")) die_nomem(); doanddie(arg, space - arg, space); } static void pop3_capa(char *arg) { outs("+OK capability list follows\r\n"); outs("TOP\r\n"); outs("USER\r\n"); outs("UIDL\r\n"); if (apop) outs("APOP\r\n"); if (stls > 0) outs("STLS\r\n"); outs(".\r\n"); flush(); } static void pop3_stls(char *arg) { if (stls == 0 || seenstls == 1) return err("STLS not available"); outs("+OK starting TLS negotiation\r\n"); flush(); if (!starttls_init()) die_tls(); buffer_init(&bi, saferead, FDIN, inbuf, sizeof(inbuf)); seenstls = 1; /* reset state */ seenuser = 0; if (!stralloc_cats(&protocol, "S")) die_nomem(); } struct commands pop3commands[] = { {"user", pop3_user, 0}, {"pass", pop3_pass, 0}, {"apop", pop3_apop, 0}, {"quit", pop3_quit, 0}, {"capa", pop3_capa, 0}, {"stls", pop3_stls, 0}, {"noop", okay, 0}, { 0, err_authoriz, 0} }; int main(int argc, char **argv) { char *pop3auth; char *ucspitls; sig_alarmcatch(die); sig_pipeignore(); hostname = argv[1]; if (!hostname) die_usage(); childargs = argv + 2; if (!*childargs) die_usage(); ucspitls = env_get("UCSPITLS"); if (ucspitls) { stls = 1; if (!case_diffs(ucspitls, "-")) stls = 0; if (!case_diffs(ucspitls, "!")) stls = 2; } pop3auth = env_get("POP3AUTH"); if (pop3auth) { if (case_starts(pop3auth, "apop")) apop = 2; if (case_starts(pop3auth, "+apop")) apop = 1; } poplog_init(); pop3_greet(); commands(&bi, pop3commands); die(); }