diff options
Diffstat (limited to 'src/qmail-dkverify.c')
-rw-r--r-- | src/qmail-dkverify.c | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/src/qmail-dkverify.c b/src/qmail-dkverify.c new file mode 100644 index 0000000..e607e08 --- /dev/null +++ b/src/qmail-dkverify.c @@ -0,0 +1,365 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/socket.h> +#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 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 + +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; + 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); +} + +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 <user@senddomain> + 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; +} + +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,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; +} + +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; +} + +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); +} |