commit 48c4bdc1d44aac5c5fc5309b6add646c1b03f853 from: Martijn van Duren date: Thu Apr 4 15:14:18 2019 UTC Basics for dkim calculations available. Nothing near useful. Importing now for backup dolphins commit - /dev/null commit + 48c4bdc1d44aac5c5fc5309b6add646c1b03f853 blob - /dev/null blob + 7aadff031bac46191b5530f5a06610c9f867a240 (mode 644) --- /dev/null +++ Makefile @@ -0,0 +1,11 @@ +# $OpenBSD: Makefile,v 1.1 2018/04/26 13:57:13 eric Exp $ + +PROG= filter-dkim +BINDIR= /usr/libexec/smtpd/ +SRCS+= main.c log.c smtp_proc.c + +CFLAGS+= -g3 -O0 +LDADD+= -levent -lcrypto +DPADD= ${LIBEVENT} ${LIBCRYPTO} + +.include blob - /dev/null blob + e24c1ca92392875378a5a183d7c8b176c831b480 (mode 644) --- /dev/null +++ filter-dkim.1 @@ -0,0 +1,38 @@ +.\" $OpenBSD$ +.\" +.\" Copyright (c) 2019 Martijn van Duren +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate$ +.Dt FILTER-DNSBL 8 +.Os +.Sh NAME +.Nm filter-dnsbl +.Nd block senders based on dnsbl +.Sh SYNOPSIS +.Nm +.Op Fl m +.Ar blacklist +.Op Ar ... +.Sh DESCRIPTION +.Nm +looks up the ip-address of the sender at the +.Ar blacklist +and by default drops the connection if it is found. +If the +.Fl m +flag is specified it will allow the message to continue, but it will be marked +with an X-Spam and X-Spam-DNSBL header. +.Sh SEE ALSO +.Xr smtpd 8 blob - /dev/null blob + 7ec8ca42e18d1c57b84e27a564c23ff352c267cf (mode 644) --- /dev/null +++ log.c @@ -0,0 +1,218 @@ +/* $OpenBSD: log.c,v 1.20 2017/03/21 12:06:56 bluhm Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +static int debug; +static int verbose; +const char *log_procname; + +void log_init(int, int); +void log_procinit(const char *); +void log_setverbose(int); +int log_getverbose(void); +void log_warn(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_warnx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_info(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void logit(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void vlog(int, const char *, va_list) + __attribute__((__format__ (printf, 2, 0))); +__dead void fatal(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +__dead void fatalx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); + +void +log_init(int n_debug, int facility) +{ + extern char *__progname; + + debug = n_debug; + verbose = n_debug; + log_procinit(__progname); + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, facility); + + tzset(); +} + +void +log_procinit(const char *procname) +{ + if (procname != NULL) + log_procname = procname; +} + +void +log_setverbose(int v) +{ + verbose = v; +} + +int +log_getverbose(void) +{ + return (verbose); +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + int saved_errno = errno; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); + + errno = saved_errno; +} + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + int saved_errno = errno; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_ERR, "%s", strerror(saved_errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, + strerror(saved_errno)) == -1) { + /* we tried it... */ + vlog(LOG_ERR, emsg, ap); + logit(LOG_ERR, "%s", strerror(saved_errno)); + } else { + vlog(LOG_ERR, nfmt, ap); + free(nfmt); + } + va_end(ap); + } + + errno = saved_errno; +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_ERR, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (verbose > 1) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +static void +vfatalc(int code, const char *emsg, va_list ap) +{ + static char s[BUFSIZ]; + const char *sep; + + if (emsg != NULL) { + (void)vsnprintf(s, sizeof(s), emsg, ap); + sep = ": "; + } else { + s[0] = '\0'; + sep = ""; + } + if (code) + logit(LOG_CRIT, "%s: %s%s%s", + log_procname, s, sep, strerror(code)); + else + logit(LOG_CRIT, "%s%s%s", log_procname, sep, s); +} + +void +fatal(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(errno, emsg, ap); + va_end(ap); + exit(1); +} + +void +fatalx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(0, emsg, ap); + va_end(ap); + exit(1); +} blob - /dev/null blob + 22bb416439467fde6a3c3e58209877edd7fdf468 (mode 644) --- /dev/null +++ log.h @@ -0,0 +1,46 @@ +/* $OpenBSD: log.h,v 1.8 2018/04/26 20:57:59 eric Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef LOG_H +#define LOG_H + +#include +#include + +void log_init(int, int); +void log_procinit(const char *); +void log_setverbose(int); +int log_getverbose(void); +void log_warn(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_warnx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_info(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void logit(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void vlog(int, const char *, va_list) + __attribute__((__format__ (printf, 2, 0))); +__dead void fatal(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +__dead void fatalx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); + +#endif /* LOG_H */ blob - /dev/null blob + f219be34eee7854002c2b4cb1eb0f06d5d1c075a (mode 644) --- /dev/null +++ main.c @@ -0,0 +1,536 @@ +/* + * Copyright (c) 2019 Martijn van Duren + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "smtp_proc.h" + +struct dkim_session { + uint64_t reqid; + uint64_t token; + FILE *origf; + int parsing_headers; + char **headers; + int lastheader; + union { + SHA_CTX sha1; + SHA256_CTX sha256; + }; + /* Use largest hash size her */ + char bbh[SHA256_DIGEST_LENGTH]; + char bh[(((SHA256_DIGEST_LENGTH + 2) / 3) * 4) + 1]; + size_t body_whitelines; + int has_body; + RB_ENTRY(dkim_session) entry; +}; + +RB_HEAD(dkim_sessions, dkim_session) dkim_sessions = RB_INITIALIZER(NULL); +RB_PROTOTYPE(dkim_sessions, dkim_session, entry, dkim_session_cmp); + +static char **sign_headers = NULL; +static size_t nsign_headers = 0; + +#define HASH_SHA1 0 +#define HASH_SHA256 1 +static int hashalg = HASH_SHA256; + +#define CRYPT_RSA 0 +static int cryptalg = CRYPT_RSA; + +#define CANON_SIMPLE 0 +#define CANON_RELAXED 1 +static int canonheader = CANON_SIMPLE; +static int canonbody = CANON_SIMPLE; + +static char *domain = NULL; + +void usage(void); +void dkim_err(struct dkim_session *, char *); +void dkim_errx(struct dkim_session *, char *); +void dkim_headers_set(char *); +void dkim_dataline(char *, int, struct timespec *, char *, char *, uint64_t, + uint64_t, char *); +void dkim_disconnect(char *, int, struct timespec *, char *, char *, uint64_t); +struct dkim_session *dkim_session_new(uint64_t); +void dkim_session_free(struct dkim_session *); +int dkim_session_cmp(struct dkim_session *, struct dkim_session *); +void dkim_parse_header(struct dkim_session *, char *); +void dkim_parse_body(struct dkim_session *, char *); +void dkim_built_signature(struct dkim_session *); +int dkim_hash_update(struct dkim_session *, char *, size_t); +int dkim_hash_final(struct dkim_session *, char *); + +int +main(int argc, char *argv[]) +{ + int ch; + int i; + int debug = 0; + + while ((ch = getopt(argc, argv, "a:c:Dd:h:")) != -1) { + switch (ch) { + case 'a': + if (strncmp(optarg, "rsa-", 4)) + err(1, "invalid algorithm"); + if (strcmp(optarg + 4, "sha256") == 0) + hashalg = HASH_SHA256; + else if (strcmp(optarg + 4, "sha1") == 0) + hashalg = HASH_SHA1; + else + err(1, "invalid algorithm"); + break; + case 'c': + if (strncmp(optarg, "simple", 6) == 0) { + canonheader = CANON_SIMPLE; + optarg += 6; + } else if (strncmp(optarg, "relaxed", 7) == 0) { + canonheader = CANON_RELAXED; + optarg += 7; + } else + err(1, "Invalid canonicalization"); + if (optarg[0] == '/') { + if (strcmp(optarg + 1, "simple") == 0) + canonbody = CANON_SIMPLE; + else if (strcmp(optarg + 1, "relaxed") == 0) + canonbody = CANON_RELAXED; + else + err(1, "Invalid canonicalization"); + } else if (optarg[0] == '\0') + canonbody = CANON_SIMPLE; + else + err(1, "Invalid canonicalization"); + break; + case 'd': + domain = optarg; + if (strlen(domain) > 255) + err(1, "Domain too long"); + break; + case 'D': + debug = 1; + break; + case 'h': + dkim_headers_set(optarg); + break; + default: + usage(); + } + } + + log_init(debug, LOG_MAIL); + if (pledge("tmppath stdio", NULL) == -1) + fatal("pledge"); + + if (domain == NULL) + usage(); + + smtp_register_filter_dataline(dkim_dataline); + smtp_in_register_report_disconnect(dkim_disconnect); + smtp_run(debug); + + return 0; +} + +void +dkim_disconnect(char *type, int version, struct timespec *tm, char *direction, + char *phase, uint64_t reqid) +{ + struct dkim_session *session, search; + + search.reqid = reqid; + if ((session = RB_FIND(dkim_sessions, &dkim_sessions, &search)) != NULL) + dkim_session_free(session); +} + +void +dkim_dataline(char *type, int version, struct timespec *tm, char *direction, + char *phase, uint64_t reqid, uint64_t token, char *line) +{ + struct dkim_session *session, search; + size_t i; + size_t linelen; + + search.reqid = reqid; + session = RB_FIND(dkim_sessions, &dkim_sessions, &search); + if (session == NULL) { + session = dkim_session_new(reqid); + session->token = token; + } else if (session->token != token) + fatalx("Token incorrect"); + + linelen = strlen(line); + if (fwrite(line, 1, linelen, session->origf) < linelen) + dkim_err(session, "Couldn't write to tempfile"); + + if (linelen != 0 && session->parsing_headers) { + dkim_parse_header(session, line); + } else if (linelen == 0 && session->parsing_headers) { + session->parsing_headers = 0; + } else if (line[0] == '.' && line[1] =='\0') { + if (canonbody == CANON_SIMPLE && !session->has_body) { + if (dkim_hash_update(session, "\r\n", 2) == 0) + return; + } + if (dkim_hash_final(session, session->bbh) == 0) + return; + EVP_EncodeBlock(session->bh, session->bbh, + hashalg == HASH_SHA1 ? SHA_DIGEST_LENGTH : + SHA256_DIGEST_LENGTH); + dkim_built_signature(session); + } else + dkim_parse_body(session, line); +} + +struct dkim_session * +dkim_session_new(uint64_t reqid) +{ + struct dkim_session *session; + char origfile[] = "/tmp/filter-dkimXXXXXX"; + int fd; + + if ((session = calloc(1, sizeof(*session))) == NULL) + fatal(NULL); + + session->reqid = reqid; + if ((fd = mkstemp(origfile)) == -1) { + dkim_err(session, "Can't open tempfile"); + return NULL; + } + if (unlink(origfile) == -1) + log_warn("Failed to unlink tempfile %s", origfile); + if ((session->origf = fdopen(fd, "r+")) == NULL) { + dkim_err(session, "Can't open tempfile"); + return NULL; + } + session->parsing_headers = 1; + + if (hashalg == HASH_SHA1) + SHA1_Init(&(session->sha1)); + else + SHA256_Init(&(session->sha256)); + session->body_whitelines = 0; + session->headers = calloc(1, sizeof(*(session->headers))); + if (session->headers == NULL) { + dkim_err(session, "Can't save headers"); + return NULL; + } + session->lastheader = 0; + + if (RB_INSERT(dkim_sessions, &dkim_sessions, session) != NULL) + fatalx("session already registered"); + return session; +} + +void +dkim_session_free(struct dkim_session *session) +{ + size_t i; + + RB_REMOVE(dkim_sessions, &dkim_sessions, session); + fclose(session->origf); + for (i = 0; session->headers[i] != NULL; i++) + free(session->headers[i]); + free(session->headers); + free(session); +} + +int +dkim_session_cmp(struct dkim_session *s1, struct dkim_session *s2) +{ + return (s1->reqid < s2->reqid ? -1 : s1->reqid > s2->reqid); +} + +void +dkim_headers_set(char *headers) +{ + size_t i; + int has_from = 0; + + nsign_headers = 1; + + /* We don't support FWS for the -h flag */ + for (i = 0; headers[i] != '\0'; i++) { + /* RFC 5322 field-name */ + if (!(headers[i] >= 33 && headers[i] <= 126)) + errx(1, "-h: invalid character"); + if (headers[i] == ':') { + /* Test for empty headers */ + if (i == 0 || headers[i - 1] == ':') + errx(1, "-h: header can't be empty"); + nsign_headers++; + } + headers[i] = tolower(headers[i]); + } + if (headers[i - 1] == ':') + errx(1, "-h: header can't be empty"); + + sign_headers = reallocarray(NULL, nsign_headers, sizeof(*sign_headers)); + if (sign_headers == NULL) + errx(1, NULL); + + for (i = 0; i < nsign_headers; i++) { + sign_headers[i] = headers; + if (i != nsign_headers - 1) { + headers = strchr(headers, ':'); + headers++[0] = '\0'; + } + if (strcasecmp(sign_headers[i], "from") == 0) + has_from = 1; + } + if (!has_from) + errx(1, "From header must be included"); +} + +void +dkim_err(struct dkim_session *session, char *msg) +{ + smtp_filter_disconnect(session->reqid, session->token, + "Internal server error"); + log_warn("%s", msg); + dkim_session_free(session); +} + +void +dkim_errx(struct dkim_session *session, char *msg) +{ + smtp_filter_disconnect(session->reqid, session->token, + "Internal server error"); + log_warnx("%s", msg); + dkim_session_free(session); +} + +void +dkim_parse_header(struct dkim_session *session, char *line) +{ + size_t i; + size_t r, w; + size_t linelen; + size_t lastheader; + int fieldname; + char **mtmp; + char *htmp; + + if ((line[0] == ' ' || line[0] == '\t') && !session->lastheader) + return; + if ((line[0] != ' ' && line[0] != '\t')) { + for (i = 0; i < nsign_headers; i++) { + if (strncasecmp(line, sign_headers[i], + strlen(sign_headers[i])) == 0) { + break; + } + } + if (i == nsign_headers) { + session->lastheader = 0; + return; + } + } + + if (canonheader == CANON_RELAXED) { + fieldname = 1; + for (r = w = 0; line[r] != '\0'; r++) { + if (line[r] == ':') { + if (line[w - 1] == ' ') + line[w - 1] = ':'; + else + line[w++] = ':'; + fieldname = 0; + while (line[r + 1] == ' ' || + line[r + 1] == '\t') + r++; + continue; + } + if (line[r] == ' ' || line[r] == '\t') { + if (r != 0 && line[w - 1] == ' ') + continue; + else + line[w++] = ' '; + } else if (fieldname) { + line[w++] = tolower(line[r]); + continue; + } else + line[w++] = line[r]; + } + linelen = line[w - 1] == ' ' ? w - 1 : w; + line[linelen] = '\0'; + } else + linelen = strlen(line); + + for (lastheader = 0; session->headers[lastheader] != NULL; lastheader++) + continue; + if (!session->lastheader) { + mtmp = reallocarray(session->headers, lastheader + 1, + sizeof(*mtmp)); + if (mtmp == NULL) { + dkim_err(session, "Can't store header"); + return; + } + session->headers = mtmp; + + session->headers[lastheader] = strdup(line); + session->headers[lastheader + 1 ] = NULL; + session->lastheader = 1; + } else { + lastheader--; + linelen += strlen(session->headers[lastheader]); + if (canonheader == CANON_SIMPLE) + linelen += 2; + linelen++; + htmp = reallocarray(session->headers[lastheader], linelen, + sizeof(*htmp)); + if (htmp == NULL) { + dkim_err(session, "Can't store header"); + return; + } + session->headers[lastheader] = htmp; + if (canonheader == CANON_SIMPLE) { + if (strlcat(htmp, "\r\n", linelen) >= linelen) + fatalx("Missized header"); + } + if (strlcat(htmp, line, linelen) >= linelen) + fatalx("Missized header"); + } +} + +void +dkim_parse_body(struct dkim_session *session, char *line) +{ + size_t r, w; + size_t linelen; + if (line[0] == '\0') { + session->body_whitelines++; + return; + } + + while (session->body_whitelines--) { + if (dkim_hash_update(session, "\r\n", 2) == 0) + return; + } + session->body_whitelines = 0; + + session->has_body = 1; + if (canonbody == CANON_RELAXED) { + for (r = w = 0; line[r] != '\0'; r++) { + if (line[r] == ' ' || line[r] == '\t') { + if (r != 0 && line[w - 1] == ' ') + continue; + else + line[w++] = ' '; + } else + line[w++] = line[r]; + } + linelen = line[w - 1] == ' ' ? w - 1 : w; + line[linelen] = '\0'; + } else + linelen = strlen(line); + + if (dkim_hash_update(session, line, linelen) == 0) + return; + if (dkim_hash_update(session, "\r\n", 2) == 0) + return; +} + +void +dkim_built_signature(struct dkim_session *session) +{ + char *signature; + size_t signaturesize = 1024; + size_t signaturelen = 0; + size_t linelen = 0; + size_t ncopied; + + if ((signature = malloc(signaturesize)) == NULL) { + dkim_err(session, "Can't create signature"); + return; + } + + do { + ncopied = strlcpy(signature, "DKIM-Signature: v=1; a=", + signaturesize); + if (ncopied >= signaturesize) + if (cryptalg == CRYPT_RSA) + (void) strlcat(signature, "rsa", signaturesize); + if (hashalg == HASH_SHA1) + (void) strlcat(signature, "-sha1; ", signaturesize); + else + (void) strlcat(signature, "-sha256; ", signaturesize); + if (canonheader != CANON_SIMPLE || canonbody != CANON_SIMPLE) { + if (canonheader == CANON_SIMPLE) + (void) strlcat(signature, "c=simple", signaturesize); + else + (void) strlcat(signature, "c=relaxed", signaturesize); + } + if (canonbody != CANON_SIMPLE) + (void) strlcat(signature, "/relaxed", signaturesize); + + (void) strlcat(signature, "d=", signaturesize); + (void) strlcat(signature, domain, signaturesize); + (void) strlcat(signature, "; ", signaturesize); + printf("%s\n", signature); +} + +int +dkim_hash_update(struct dkim_session *session, char *buf, size_t len) +{ + if (hashalg == HASH_SHA1) { + if (SHA1_Update(&(session->sha1), buf, len) == 0) { + dkim_errx(session, "Unable to update hash"); + return 0; + } + } else { + if (SHA256_Update(&(session->sha256), buf, len) == 0) { + dkim_errx(session, "Unable to update hash"); + return 0; + } + } + return 1; +} + +int +dkim_hash_final(struct dkim_session *session, char *dest) +{ + if (hashalg == HASH_SHA1) { + if (SHA1_Final(dest, &(session->sha1)) == 0) { + dkim_errx(session, "Unable to finalize hash"); + return 0; + } + } else { + if (SHA256_Final(dest, &(session->sha256)) == 0) { + dkim_errx(session, "Unable to finalize hash"); + return 0; + } + } + return 1; +} + +__dead void +usage(void) +{ + fprintf(stderr, "usage: %s [-a signalg] [-c canonicalization] -d domain -h headerfields\n", getprogname()); + exit(1); +} + +RB_GENERATE(dkim_sessions, dkim_session, entry, dkim_session_cmp); blob - /dev/null blob + 0a700a0ff66e3ff542a44f3055ed3e3100ad5e08 (mode 644) --- /dev/null +++ smtp_proc.c @@ -0,0 +1,481 @@ +/* + * Copyright (c) 2019 Martijn van Duren + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "smtp_proc.h" + +#define NITEMS(x) (sizeof(x) / sizeof(*x)) + +struct smtp_callback; +struct smtp_request; + +extern struct event_base *current_base; + +static int smtp_register(char *, char *, char *, void *); +static ssize_t smtp_getline(char ** restrict, size_t * restrict); +static void smtp_newline(int, short, void *); +static void smtp_connect(struct smtp_callback *, int, struct timespec *, + uint64_t, uint64_t, char *); +static void smtp_data(struct smtp_callback *, int, struct timespec *, + uint64_t, uint64_t, char *); +static void smtp_dataline(struct smtp_callback *, int, struct timespec *, + uint64_t, uint64_t, char *); +static void smtp_in_link_disconnect(struct smtp_callback *, int, struct timespec *, + uint64_t, char *); +static void smtp_printf(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +static void smtp_vprintf(const char *, va_list); +static void smtp_write(int, short, void *); + +struct smtp_writebuf { + char *buf; + size_t bufsize; + size_t buflen; +}; + +struct smtp_callback { + char *type; + char *phase; + char *direction; + union { + void (*smtp_filter)(struct smtp_callback *, int, + struct timespec *, uint64_t, uint64_t, char *); + void (*smtp_report)(struct smtp_callback *, int, + struct timespec *, uint64_t, char *); + }; + void *cb; +} smtp_callbacks[] = { + {"filter", "connect", "smtp-in", .smtp_filter = smtp_connect, NULL}, + {"filter", "data", "smtp-in", .smtp_filter = smtp_data, NULL}, + {"filter", "data-line", "smtp-in", .smtp_filter = smtp_dataline, NULL}, + {"report", "link-disconnect", "smtp-in", + .smtp_report = smtp_in_link_disconnect, NULL} +}; + +static int ready = 0; + +int +smtp_register_filter_connect(void (*cb)(char *, int, struct timespec *, char *, + char *, uint64_t, uint64_t, char *, struct inx_addr *)) +{ + return smtp_register("filter", "connect", "smtp-in", (void *)cb); +} + +int +smtp_register_filter_data(void (*cb)(char *, int, struct timespec *, char *, + char *, uint64_t, uint64_t)) +{ + return smtp_register("filter", "data", "smtp-in", (void *)cb); +} + +int +smtp_register_filter_dataline(void (*cb)(char *, int, struct timespec *, char *, + char *, uint64_t, uint64_t, char *)) +{ + return smtp_register("filter", "data-line", "smtp-in", (void *)cb); +} + +int +smtp_in_register_report_disconnect(void (*cb)(char *, int, struct timespec *, + char *, char *, uint64_t)) +{ + return smtp_register("report", "link-disconnect", "smtp-in", (void *)cb); +} + +void +smtp_run(int debug) +{ + struct event stdinev; + + smtp_printf("register|ready\n"); + ready = 1; + + log_init(debug, LOG_MAIL); + event_set(&stdinev, STDIN_FILENO, EV_READ | EV_PERSIST, smtp_newline, + &stdinev); + event_add(&stdinev, NULL); + + if (fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK) == -1) + fatal("fcntl"); + event_dispatch(); +} + +static ssize_t +smtp_getline(char ** restrict buf, size_t * restrict size) +{ + static char *rbuf = NULL; + static size_t rsoff = 0, reoff = 0; + static size_t rbsize = 0; + char *sep; + size_t sepoff; + ssize_t strlen, nread; + + do { + if (rsoff != reoff) { + if ((sep = memchr(rbuf + rsoff, '\n', reoff - rsoff)) + != NULL) { + sepoff = sep - rbuf; + if (*buf == NULL) + *size = 0; + if (*size < (sepoff - rsoff + 1)) { + *size = sepoff - rsoff + 1; + *buf = realloc(*buf, sepoff - rsoff + 1); + if (*buf == NULL) + fatal(NULL); + } + sep[0] = '\0'; + strlen = strlcpy(*buf, rbuf + rsoff, *size); + if (strlen >= *size) + fatalx("copy buffer too small"); + rsoff = sepoff + 1; + return strlen; + } + } + /* If we can't fill at the end, move everything back. */ + if (rbsize - reoff < 1500 && rsoff != 0) { + memmove(rbuf, rbuf + rsoff, reoff - rsoff); + reoff -= rsoff; + rsoff = 0; + } + /* If we still can't fill alloc some new memory. */ + if (rbsize - reoff < 1500) { + if ((rbuf = realloc(rbuf, rbsize + 4096)) == NULL) + fatal(NULL); + rbsize += 4096; + } + nread = read(STDIN_FILENO, rbuf + reoff, rbsize - reoff); + if (nread <= 0) + return nread; + reoff += nread; + } while (1); +} + +static void +smtp_newline(int fd, short event, void *arg) +{ + struct event *stdinev = (struct event *)arg; + static char *line = NULL, *linedup = NULL; + static size_t linesize = 0; + static size_t dupsize = 0; + ssize_t linelen; + char *start, *end, *type, *direction, *phase, *params; + int version; + struct timespec tm; + uint64_t reqid, token; + int i; + + while ((linelen = smtp_getline(&line, &linesize)) > 0) { + if (dupsize < linesize) { + if ((linedup = realloc(linedup, linesize)) == NULL) + fatal(NULL); + dupsize = linesize; + } + strlcpy(linedup, line, dupsize); + type = line; + if ((start = strchr(type, '|')) == NULL) + fatalx("Invalid line received: missing version: %s", linedup); + start++[0] = '\0'; + if ((end = strchr(start, '|')) == NULL) + fatalx("Invalid line received: missing time: %s", linedup); + end++[0] = '\0'; + if (strcmp(start, "1") != 0) + fatalx("Unsupported protocol received: %s: %s", start, linedup); + version = 1; + start = end; + if ((direction = strchr(start, '|')) == NULL) + fatalx("Invalid line received: missing direction: %s", linedup); + direction++[0] = '\0'; + tm.tv_sec = (time_t) strtoull(start, &end, 10); + tm.tv_nsec = 0; + if (start[0] == '\0' || (end[0] != '\0' && end[0] != '.')) + fatalx("Invalid line received: invalid timestamp: %s", linedup); + if (end[0] == '.') { + start = end + 1; + tm.tv_nsec = strtol(start, &end, 10); + if (start[0] == '\0' || end[0] != '\0') + fatalx("Invalid line received: invalid " + "timestamp: %s", linedup); + for (i = 9 - (end - start); i > 0; i--) + tm.tv_nsec *= 10; + } + if ((phase = strchr(direction, '|')) == NULL) + fatalx("Invalid line receieved: missing phase: %s", linedup); + phase++[0] = '\0'; + if ((start = strchr(phase, '|')) == NULL) + fatalx("Invalid line received: missing reqid: %s", linedup); + start++[0] = '\0'; + reqid = strtoull(start, ¶ms, 16); + if (start[0] == '|' || (params[0] != '|' & params[0] != '\0')) + fatalx("Invalid line received: invalid reqid: %s", linedup); + params++; + + for (i = 0; i < NITEMS(smtp_callbacks); i++) { + if (strcmp(type, smtp_callbacks[i].type) == 0 && + strcmp(phase, smtp_callbacks[i].phase) == 0 && + strcmp(direction, smtp_callbacks[i].direction) == 0) + break; + } + if (i == NITEMS(smtp_callbacks)) { + fatalx("Invalid line received: received unregistered " + "%s: %s: %s", type, phase, linedup); + } + if (strcmp(type, "filter") == 0) { + start = params; + token = strtoull(start, ¶ms, 16); + if (start[0] == '|' || params[0] != '|') + fatalx("Invalid line received: invalid token: %s", linedup); + params++; + smtp_callbacks[i].smtp_filter(&(smtp_callbacks[i]), + version, &tm, reqid, token, params); + } else + smtp_callbacks[i].smtp_report(&(smtp_callbacks[i]), + version, &tm, reqid, params); + } + if (linelen == 0 || errno != EAGAIN) + event_del(stdinev); +} + +static void +smtp_connect(struct smtp_callback *cb, int version, struct timespec *tm, + uint64_t reqid, uint64_t token, char *params) +{ + struct inx_addr addrx; + char *hostname; + char *address; + int ret; + void (*f)(char *, int, struct timespec *,char *, char *, uint64_t, + uint64_t, char *, struct inx_addr *); + + hostname = params; + if ((address = strchr(params, '|')) == NULL) + fatalx("Invalid line received: missing address: %s", params); + address++[0] = '\0'; + + addrx.af = AF_INET; + if (strncasecmp(address, "ipv6:", 5) == 0) { + addrx.af = AF_INET6; + address += 5; + } + + ret = inet_pton(addrx.af, address, addrx.af == AF_INET ? + (void *)&(addrx.addr) : (void *)&(addrx.addr6)); + if (ret == 0) + fatalx("Invalid line received: Couldn't parse address: %s", params); + if (ret == -1) + fatal("Couldn't convert address: %s", params); + + f = cb->cb; + f(cb->type, version, tm, cb->direction, cb->phase, reqid, token, + hostname, &addrx); +} + +static void +smtp_data(struct smtp_callback *cb, int version, struct timespec *tm, + uint64_t reqid, uint64_t token, char *params) +{ + void (*f)(char *, int, struct timespec *, char *, char *, uint64_t, + uint64_t); + + f = cb->cb; + f(cb->type, version, tm, cb->direction, cb->phase, reqid, token); +} + +static void +smtp_dataline(struct smtp_callback *cb, int version, struct timespec *tm, + uint64_t reqid, uint64_t token, char *line) +{ + void (*f)(char *, int, struct timespec *, char *, char *, uint64_t, + uint64_t, char *); + + f = cb->cb; + f(cb->type, version, tm, cb->direction, cb->phase, reqid, token, + line); +} + +static void +smtp_in_link_disconnect(struct smtp_callback *cb, int version, + struct timespec *tm, uint64_t reqid, char *params) +{ + void (*f)(char *, int, struct timespec *, char *, char *, uint64_t); + + f = cb->cb; + f(cb->type, version, tm, cb->direction, cb->phase, reqid); +} + +void +smtp_filter_proceed(uint64_t reqid, uint64_t token) +{ + smtp_printf("filter-result|%016"PRIx64"|%016"PRIx64"|proceed\n", token, + reqid); +} + +static void +smtp_printf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + smtp_vprintf(fmt, ap); + va_end(ap); +} + +static void +smtp_vprintf(const char *fmt, va_list ap) +{ + va_list cap; + static struct smtp_writebuf buf = {NULL, 0, 0}; + int fmtlen; + + va_copy(cap, ap); + fmtlen = vsnprintf(buf.buf + buf.buflen, buf.bufsize - buf.buflen, fmt, + ap); + if (fmtlen == -1) + fatal("vsnprintf"); + if (fmtlen >= buf.bufsize - buf.buflen) { + buf.bufsize = buf.buflen + fmtlen + 1; + buf.buf = reallocarray(buf.buf, buf.bufsize, + sizeof(*(buf.buf))); + if (buf.buf == NULL) + fatalx(NULL); + fmtlen = vsnprintf(buf.buf + buf.buflen, + buf.bufsize - buf.buflen, fmt, cap); + if (fmtlen == -1) + fatal("vsnprintf"); + } + va_end(cap); + buf.buflen += fmtlen; + + if (strchr(buf.buf, '\n') != NULL) + smtp_write(STDOUT_FILENO, EV_WRITE, &buf); +} + +static void +smtp_write(int fd, short event, void *arg) +{ + struct smtp_writebuf *buf = arg; + static struct event stdoutev; + static int evset = 0; + ssize_t wlen; + + if (buf->buflen == 0) + return; + if (event_pending(&stdoutev, EV_WRITE, NULL)) + return; + if (!evset) { + event_set(&stdoutev, fd, EV_WRITE, smtp_write, buf); + evset = 1; + } + wlen = write(fd, buf->buf, buf->buflen); + if (wlen == -1) { + if (errno != EAGAIN && errno != EINTR) + fatal("Failed to write to smtpd"); + event_add(&stdoutev, NULL); + return; + } + if (wlen < buf->buflen) { + memmove(buf->buf, buf->buf + wlen, buf->buflen - wlen); + event_add(&stdoutev, NULL); + } + buf->buflen -= wlen; +} + +void +smtp_filter_reject(uint64_t reqid, uint64_t token, int code, + const char *reason, ...) +{ + va_list ap; + + if (code < 200 || code > 599) + fatalx("Invalid reject code"); + + smtp_printf("filter-result|%016"PRIx64"|%016"PRIx64"|reject|%d ", token, + reqid, code); + va_start(ap, reason); + smtp_vprintf(reason, ap); + va_end(ap); + smtp_printf("\n"); +} + +void +smtp_filter_disconnect(uint64_t reqid, uint64_t token, const char *reason, ...) +{ + va_list ap; + + smtp_printf("filter-result|%016"PRIx64"|%016"PRIx64"|disconnect|421 ", + token, reqid); + va_start(ap, reason); + smtp_vprintf(reason, ap); + va_end(ap); + smtp_printf("\n"); +} + +void +smtp_filter_dataline(uint64_t reqid, uint64_t token, const char *line, ...) +{ + va_list ap; + + smtp_printf("filter-dataline|%016"PRIx64"|%016"PRIx64"|", token, reqid); + va_start(ap, line); + smtp_vprintf(line, ap); + va_end(ap); + smtp_printf("\n"); +} + +static int +smtp_register(char *type, char *phase, char *direction, void *cb) +{ + int i; + static int evinit = 0; + + if (ready) + fatalx("Can't register when proc is running"); + + if (!evinit) { + event_init(); + evinit = 1; + } + + for (i = 0; i < NITEMS(smtp_callbacks); i++) { + if (strcmp(type, smtp_callbacks[i].type) == 0 && + strcmp(phase, smtp_callbacks[i].phase) == 0 && + strcmp(direction, smtp_callbacks[i].direction) == 0) { + if (smtp_callbacks[i].cb != NULL) { + errno = EALREADY; + return -1; + } + smtp_callbacks[i].cb = cb; + smtp_printf("register|%s|%s|%s\n", type, direction, + phase); + return 0; + } + } + errno = EINVAL; + return -1; +} blob - /dev/null blob + 39a43d963a2e37a9f520a2229d53d6b73694db1e (mode 644) --- /dev/null +++ smtp_proc.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Martijn van Duren + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include + +struct inx_addr { + int af; + union { + struct in_addr addr; + struct in6_addr addr6; + }; +}; + +int smtp_register_filter_connect(void (*)(char *, int, struct timespec *, + char *, char *, uint64_t, uint64_t, char *, struct inx_addr *)); +int smtp_register_filter_data(void (*)(char *, int, struct timespec *, char *, + char *, uint64_t, uint64_t)); +int smtp_register_filter_dataline(void (*)(char *, int, struct timespec *, char *, + char *, uint64_t, uint64_t, char *)); +int smtp_in_register_report_disconnect(void (*)(char *, int, struct timespec *, + char *, char *, uint64_t)); +void smtp_filter_proceed(uint64_t, uint64_t); +void smtp_filter_reject(uint64_t, uint64_t, int, const char *, ...) + __attribute__((__format__ (printf, 4, 5))); +void smtp_filter_disconnect(uint64_t, uint64_t, const char *, ...) + __attribute__((__format__ (printf, 3, 4))); +void smtp_filter_dataline(uint64_t, uint64_t, const char *, ...) + __attribute__((__format__ (printf, 3, 4))); +void smtp_run(int);