#include #include #include #include #include #include "sig.h" #include "stralloc.h" #include "buffer.h" #include "error.h" #include "auto_qmail.h" #include "str.h" #include "exit.h" #include "uint_t.h" #include "fd.h" #include "open.h" #include "fmt.h" #include "fmtqfn.h" #include "readwrite.h" #include "getln.h" #include "qmail.h" #include "wait.h" #include "byte.h" #include "case.h" #include "control.h" #include "pathexec.h" #include "env.h" #include "logmsg.h" #define WHO "qmail-dkverify" /** @file qmail-dkverify.c @brief stub routine for DKIM signature verification and indication in received message Steps: ------ a) Store message with CRLF b) Get DKIM signature from message - if given: c) Call qmail-dkim for verification d) Include results as appended header e) Queue the message for processing */ char inbuf[BUFSIZE_LINE]; buffer bi = BUFFER_INIT(read,0,inbuf,sizeof(inbuf)); // read buffer char outbuf[BUFSIZE_MESS]; buffer bo = BUFFER_INIT(write,1,outbuf,sizeof(outbuf)); // output message void die(int e) { _exit(e); } void die_pipe(char *fn) { unlink(fn); die(53); }; void die_write(char *fn) { unlink(fn); die(53); }; void die_read() { die(54); }; void out(char *s) { if (buffer_puts(&bo,s) == -1) _exit(111); } void zero() { if (buffer_put(&bo,"\0",1) == -1) _exit(111); } void zerodie() { zero(); buffer_flush(&bo); _exit(111); } void temp_nomem() { out("ZOut of memory. (#4.3.0)\n"); zerodie(); } void temp_chdir() { out("ZUnable to switch to target directory. (#4.3.0)\n"); zerodie(); } void temp_create() { out("ZUnable to create DKIM stage file. (#4.3.0)\n"); zerodie(); } void temp_unlink() { out("ZUnable to unlink DKIM stage file. (#4.3.0)\n"); zerodie(); } void temp_read() { out("ZUnable to read message. (#4.3.0)\n"); zerodie(); } void temp_socket() { out("ZUnable to crate socket pair. (#4.3.0)\n"); zerodie(); } void temp_control() { out("ZUnable to read control files. (#4.3.0)\n"); zerodie(); } static stralloc me = {0}; static stralloc senddomain = {0}; static stralloc dkheader = {0}; static stralloc fndkin = {0}; static stralloc fndkout = {0}; static stralloc result = {0}; void fnmake_dkim(unsigned long id) { fndkin.len = fmtqfn(fndkin.s,"queue/dkim/",id,1); id += id; fndkout.len = fmtqfn(fndkout.s,"queue/dkim/",id,1); } void dkim_stage() { int r; int fd; int in, out; struct stat st; char tmpbuf[BUFSIZE_MESS + 2]; if (!stralloc_ready(&fndkin,FMTQFN)) temp_nomem(); if (!stralloc_ready(&fndkout,FMTQFN)) temp_nomem(); fnmake_dkim(getpid()); // pre-staging fd = open_excl(fndkin.s); if (fd == -1) die_write(fndkin.s); buffer_init(&bi,read,0,inbuf,sizeof(inbuf)); buffer_init(&bo,write,fd,outbuf,sizeof(outbuf)); while((r = buffer_get(&bi,inbuf,sizeof(inbuf)))) { // read into buffer if (r == -1) temp_read(); for (in = out = 0; in < r; in++) { // reconstruct CRLF (ok) if (inbuf[in] != '\n') { tmpbuf[out++] = inbuf[in]; } else { tmpbuf[out++] = '\r'; tmpbuf[out++] = '\n'; } } if (out) buffer_put(&bo,tmpbuf,out); } if (buffer_flush(&bo) == -1) die(51); if (fstat(fd,&st) == -1) die_write(fndkin.s); if (fsync(fd) == -1) die_write(fndkin.s); if (close(fd) == -1) die_write(fndkin.s); } int mess_dkim() { stralloc line = {0}; int match; int fd; int at = 0; int ket = 0; int end = 0; int len = 0; int r = 0; int i; fd = open_read(fndkin.s); if (fd == -1) die_read(); buffer_init(&bi,read,fd,inbuf,sizeof(inbuf)); if (!stralloc_copys(&senddomain,"")) temp_nomem(); for (;;) { if (getln(&bi,&line,&match,'\n') == -1) temp_read(); if (case_starts(line.s,"DKIM-Signature: ")) r = 1; if (r == 1) { if (case_starts(line.s,"From: ")) { // fallback: From at = str_chr(line.s,'@'); if (at < line.len) { end = str_chr(line.s,'\n'); // From: user@senddomain\n ket = str_chr(line.s,'>'); // From: User len = (ket < end) ? ket : end; if (!stralloc_copyb(&senddomain,line.s + at + 1,len - at - 1)) temp_nomem(); r = 2; } } for (i = 0; i < line.len; ++i) { // d=domain.tld if (*(line.s + i) == '=' && *(line.s + i - 1) == 'd') { ++i; // gotcha while (*(line.s + i) != ';') { if (!stralloc_catb(&senddomain,line.s + i,1)) temp_nomem(); i++; r = 3; } } } } if (r >= 2 || !match) break; } if (senddomain.len < 2) if (!stralloc_copys(&senddomain,"unknown")) temp_nomem(); if (!stralloc_0(&senddomain)) temp_nomem(); return r; } int dkim_verify() { int child; int wstat; char *(args[6]); int r = -1; args[0] = "qmail-dkim"; args[1] = "-V"; args[2] = fndkin.s; args[3] = "none"; args[4] = fndkout.s; args[5] = 0; if (!(child = fork())) { pathexec(args); if (errno) _exit(111); _exit(100); } wait_pid(&wstat,child); if (wait_crashed(wstat)) return 1; switch (r = wait_exitcode(wstat)) { case 10: return 1; default: return 0; } } int dkim_result(const char *me) { int max = 64; int fd; int j; char ch; int r = 0; if (!stralloc_copys(&result,"")) temp_nomem(); if ((fd = open_read(fndkout.s)) == -1) return 0; // nothing to read while ((r = read(fd,inbuf,sizeof(inbuf))) > 0) if (!stralloc_catb(&result,inbuf,r)) temp_nomem(); if (!stralloc_0(&result)) temp_nomem(); if (result.len > 2) { if (case_starts(result.s,"pass")) r = 0; if (case_starts(result.s,"fail")) r = 35; } else if (!stralloc_copys(&result,"unknown")) temp_nomem(); if (!stralloc_copys(&dkheader,"X-Authentication-Results: ")) temp_nomem(); if (!stralloc_cats(&dkheader,senddomain.s)) temp_nomem(); if (!stralloc_cats(&dkheader,"; dkim=")) temp_nomem(); for (j = 0; j < result.len; j++) { ch = result.s[j]; if (ch == '\r' || ch == '\n' || ch == '\0') continue; if (j <= max) if (!stralloc_catb(&dkheader,&ch,1)) temp_nomem(); if (ch == ' ' && (j > max)) { if (!stralloc_cats(&dkheader,"\n ")) temp_nomem(); max += max; } } if (!stralloc_cats(&dkheader,"; ")) temp_nomem(); if (!stralloc_cats(&dkheader,me)) temp_nomem(); if (!stralloc_0(&dkheader)) temp_nomem(); return r; } int qmail_queue() { int fd; int r; int child; int wstat; int pi[2]; char *(args[2]); char tmpbuf[BUFSIZE_MESS]; int in, out; if (pipe(pi) == -1) die_pipe(fndkin.s); args[0] = "qmail-queue"; args[1] = 0; switch (child = vfork()) { case -1: close(pi[0]); close(pi[1]); die_write(fndkin.s); case 0: close(pi[1]); if (fd_move(0,pi[0]) == -1) die_pipe(fndkin.s); sig_pipedefault(); pathexec(args); if (errno) _exit(111); _exit(100); } close(pi[0]); buffer_init(&bo,write,pi[1],outbuf,sizeof(outbuf)); if (dkheader.len > 2) { // write DKIM header if (buffer_put(&bo,dkheader.s,dkheader.len - 1) == -1) die_write(fndkout.s); if (buffer_put(&bo,"\n",1) == -1) die_write(fndkout.s); if (buffer_flush(&bo) == -1) die_write(fndkout.s); } /* read/write message; we need to remove the CR (ok) */ if ((fd = open_read(fndkin.s)) == -1) die_read(); while ((r = read(fd,tmpbuf,sizeof(tmpbuf))) > 0) { for (in = 0, out = 0; in < r; ++in) { if (tmpbuf[in] == '\r') { buffer_put(&bo,&tmpbuf[out],in - out); out = in + 1; // \n to follow } } } if (buffer_flush(&bo) == -1) die_write(fndkin.s); close(pi[1]); wait_pid(&wstat,child); if (wait_crashed(wstat)) return 1; switch (r = wait_exitcode(wstat)) { case 10: return 1; default: return 0; } return 0; } void dkim_unlink() { if (unlink(fndkin.s) == -1) if (errno != ENOENT) temp_unlink(); if (unlink(fndkout.s) == -1) if (errno != ENOENT) temp_unlink(); } int main() { int r = 0; char *mode = 0; umask(033); if (chdir(auto_qmail) == -1) temp_chdir(); if (control_init() == -1) temp_control(); if (control_readline(&me,"control/me") == -1) temp_control(); if (!stralloc_0(&me)) temp_nomem(); dkim_stage(); if (mess_dkim()) { dkim_verify(); r = dkim_result(me.s); } /* we are done: call qmail-queue */ mode = env_get("DKIM"); if (!mode || *mode != '+') r = 0; qmail_queue(); dkim_unlink(); _exit(r); }