diff options
Diffstat (limited to 'src/qmail-popup.c')
-rw-r--r-- | src/qmail-popup.c | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/src/qmail-popup.c b/src/qmail-popup.c new file mode 100644 index 0000000..a2fd39d --- /dev/null +++ b/src/qmail-popup.c @@ -0,0 +1,303 @@ +#include <unistd.h> +#include "commands.h" +#include "fd.h" +#include "sig.h" +#include "stralloc.h" +#include "buffer.h" +#include "alloc.h" +#include "wait.h" +#include "str.h" +#include "byte.h" +#include "now.h" +#include "fmt.h" +#include "case.h" +#include "exit.h" +#include "timeout.h" +#include "env.h" +#include "tls_start.h" +#include "ip.h" +#include "qmail.h" + +#define PORT_POP3S "995" // string +#define FDIN 0 +#define FDOUT 1 +#define FDAUTH 3 +#define FDLOG 5 +#define POP3_TIMEOUT 1200 + +void die() { _exit(1); } + +ssize_t saferead(int fd,char *buf,int len) +{ + int r; + r = timeoutread(POP3_TIMEOUT,fd,buf,len); + if (r <= 0) die(); + return r; +} + +ssize_t safewrite(int fd,char *buf,int len) +{ + int r; + r = timeoutwrite(POP3_TIMEOUT,fd,buf,len); + if (r <= 0) die(); + return r; +} + +char inbuf[BUFSIZE_AUTH]; +buffer bi = BUFFER_INIT(saferead,FDIN,inbuf,sizeof(inbuf)); + +char outbuf[BUFSIZE_AUTH]; +buffer bo = BUFFER_INIT(safewrite,FDOUT,outbuf,sizeof(outbuf)); + +void outs(char *s) +{ + buffer_puts(&bo,s); +} +void flush() +{ + buffer_flush(&bo); +} +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[BUFSIZE_LOG]; +buffer bl = BUFFER_INIT(safewrite,FDLOG,logbuf,sizeof(logbuf)); + +void logs(char *s) { if (buffer_puts(&bl,s) == -1) _exit(1); } +void logp(char *s) { logs(" P:"); logs(s); } +void logh(char *s1, char *s2) { logs(" S:"); logs(s1); logs(":"); logs(s2); } +void logu(char *s) { logs(" ?~ '"); logs(s); logs("'"); } +void logn(char *s) { if (buffer_puts(&bl,s) == -1) _exit(1); if (buffer_flush(&bl) == -1) _exit(1); } +void logpid() { strnum[fmt_ulong(strnum,getpid())] = 0; logs("qmail-popup: pid "); logs(strnum); logs(" "); } +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"); } + +void die_usage() { err("usage: popup hostname subprogram"); die(); } +void die_nomem() { err("out of memory"); die(); } +void die_pipe() { err("unable to open pipe"); die(); } +void die_write() { err("unable to write pipe"); die(); } +void die_fork() { err("unable to fork"); die(); } +void die_childcrashed() { err("aack, child crashed"); } +void die_badauth() { err("authorization failed"); } +void die_tls() { err("TLS startup failed"); die(); } +void die_notls() { + err("TLS required but not negotiated"); + log_pop("Reject::STLS::","Any","POP3",remoteip,remotehost,"unknown"); + die(); +} + +void err_syntax() { err("syntax error"); } +void err_wantuser() { err("USER first"); } +void err_authoriz() { err("authorization first"); } + +void okay() { outs("+OK \r\n"); flush(); } +void pop3_quit() { okay(); die(); } + +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; +char authbuf[BUFSIZE_AUTH]; +buffer ba; +int stls = 0; +int seenstls = 0; +int apop = 0; + +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(); +} + +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(); +} + +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(); +} + +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); +} + +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); +} + +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(); +} + +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(); +} |