#include #include #include #include #include #include "buffer.h" #include "case.h" #include "env.h" #include "error.h" #include "exit.h" #include "fd.h" #include "getln.h" #include "open.h" #include "pathexec.h" #include "sig.h" #include "str.h" #include "stralloc.h" #include "uint_t.h" #include "wait.h" #include "control.h" #include "fmtqfn.h" #ifdef USE_CONFIG #include "fehsqm-config.h" #else #include "auto_qmail.h" #endif #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 bufin[1024]; // RFC 5322: 998 chars - why? buffer bi = BUFFER_INIT(read, 0, bufin, sizeof(bufin)); // read buffer char bufout[1024]; buffer bo = BUFFER_INIT(write, 1, bufout, sizeof(bufout)); // output message static void die(int e) { _exit(e); } static void die_pipe(char *fn) { unlink(fn); die(53); } static void die_write(char *fn) { unlink(fn); die(53); } static void die_read() { die(54); } static void out(char *s) { if (buffer_puts(&bo, s) == -1) _exit(111); } static void zero() { if (buffer_put(&bo, "\0", 1) == -1) _exit(111); } static void zerodie() { zero(); buffer_flush(&bo); _exit(111); } static void temp_nomem() { out("ZOut of memory. (#4.3.0)\n"); zerodie(); } static void temp_chdir() { out("ZUnable to switch to target directory. (#4.3.0)\n"); zerodie(); } static void temp_create() { out("ZUnable to create DKIM stage file. (#4.3.0)\n"); zerodie(); } static void temp_unlink() { out("ZUnable to unlink DKIM stage file. (#4.3.0)\n"); zerodie(); } static void temp_read() { out("ZUnable to read message. (#4.3.0)\n"); zerodie(); } static void temp_socket() { out("ZUnable to crate socket pair. (#4.3.0)\n"); zerodie(); } static 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}; static 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); } static void dkim_stage() { int r; int fd; char ch; struct stat st; 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, bufin, sizeof(bufin)); buffer_init(&bo, write, fd, bufout, sizeof(bufout)); for (int i = 0;;) { r = buffer_get(&bi, &ch, 1); if (r == 0) break; if (r == -1) temp_read(); while (ch != '\n') { if (ch != '\r') buffer_put(&bo, &ch, 1); r = buffer_get(&bi, &ch, 1); if (r == -1) temp_read(); i++; } buffer_put(&bo, "\r\n", 2); } 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); } static 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, bufin, sizeof(bufin)); 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; len -= at - 1; if (len) { if (!stralloc_copyb(&senddomain, line.s + at + 1, len)) temp_nomem(); } else if (!stralloc_copys(&senddomain, "uknown")) 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; } static 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; } } static 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, bufin, sizeof(bufin))) > 0) if (!stralloc_catb(&result, bufin, 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; } static int qmail_queue() { int fd; int r; int child; int wstat; int pi[2]; char *(args[2]); char ch; 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], bufout, sizeof(bufout)); 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 byte-by-byte; we need to remove the CR (inefficient) */ if ((fd = open_read(fndkin.s)) == -1) die_read(); while ((r = read(fd, &ch, 1)) > 0) if (ch != '\r') if (buffer_put(&bo, &ch, 1) == -1) die_write(fndkin.s); 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; } static 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); }