commit - 48c4bdc1d44aac5c5fc5309b6add646c1b03f853
commit + 06f6b49b29480f8a3027791df2dcc85f1c871e6b
blob - f219be34eee7854002c2b4cb1eb0f06d5d1c075a
blob + 6f8831125f075cd3ee773d55426af3321e62bded
--- main.c
+++ main.c
#include <sys/tree.h>
#include <openssl/evp.h>
+#include <openssl/pem.h>
#include <openssl/sha.h>
#include <ctype.h>
#include "log.h"
#include "smtp_proc.h"
+struct dkim_signature {
+ char *signature;
+ size_t size;
+ size_t len;
+};
+
struct dkim_session {
uint64_t reqid;
uint64_t token;
};
/* Use largest hash size her */
char bbh[SHA256_DIGEST_LENGTH];
- char bh[(((SHA256_DIGEST_LENGTH + 2) / 3) * 4) + 1];
+// char bh[(((SHA256_DIGEST_LENGTH + 2) / 3) * 4) + 1];
+ char bh[1000];
size_t body_whitelines;
int has_body;
+ struct dkim_signature signature;
+ EVP_MD_CTX *md_ctx;
RB_ENTRY(dkim_session) entry;
};
static int canonbody = CANON_SIMPLE;
static char *domain = NULL;
+static char *selector = NULL;
+static EVP_PKEY *pkey;
+static const EVP_MD *hash_md;
+
+#define DKIM_SIGNATURE_LINELEN 78
+
void usage(void);
void dkim_err(struct dkim_session *, char *);
void dkim_errx(struct dkim_session *, char *);
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_header(struct dkim_session *, char *, int);
void dkim_parse_body(struct dkim_session *, char *);
-void dkim_built_signature(struct dkim_session *);
+int dkim_signature_printf(struct dkim_session *, char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+int dkim_signature_normalize(struct dkim_session *);
+int dkim_signature_need(struct dkim_session *, size_t);
+int dkim_sign_init(struct dkim_session *);
+int dkim_hash_init(struct dkim_session *);
int dkim_hash_update(struct dkim_session *, char *, size_t);
int dkim_hash_final(struct dkim_session *, char *);
int ch;
int i;
int debug = 0;
+ FILE *keyfile;
- while ((ch = getopt(argc, argv, "a:c:Dd:h:")) != -1) {
+ while ((ch = getopt(argc, argv, "a:c:Dd:h:k:s:")) != -1) {
switch (ch) {
case 'a':
if (strncmp(optarg, "rsa-", 4))
err(1, "invalid algorithm");
- if (strcmp(optarg + 4, "sha256") == 0)
+ if (strcmp(optarg + 4, "sha256") == 0) {
hashalg = HASH_SHA256;
- else if (strcmp(optarg + 4, "sha1") == 0)
+ hash_md = EVP_sha256();
+ } else if (strcmp(optarg + 4, "sha1") == 0) {
hashalg = HASH_SHA1;
- else
+ hash_md = EVP_sha1();
+ } else
err(1, "invalid algorithm");
break;
case 'c':
break;
case 'd':
domain = optarg;
- if (strlen(domain) > 255)
- err(1, "Domain too long");
break;
+ case 'h':
+ dkim_headers_set(optarg);
+ break;
+ case 'k':
+ if ((keyfile = fopen(optarg, "r")) == NULL)
+ err(1, "Can't open key file");
+ pkey = PEM_read_PrivateKey(keyfile, NULL, NULL, NULL);
+ if (pkey == NULL)
+ errx(1, "Can't read key file");
+ if (EVP_PKEY_get0_RSA(pkey) == NULL)
+ err(1, "Key is not of type rsa");
+ fclose(keyfile);
+ break;
+ case 's':
+ selector = optarg;
+ break;
case 'D':
debug = 1;
break;
- case 'h':
- dkim_headers_set(optarg);
- break;
default:
usage();
}
if (pledge("tmppath stdio", NULL) == -1)
fatal("pledge");
- if (domain == NULL)
+ if (domain == NULL || selector == NULL || pkey == NULL)
usage();
smtp_register_filter_dataline(dkim_dataline);
char *phase, uint64_t reqid, uint64_t token, char *line)
{
struct dkim_session *session, search;
- size_t i;
+ struct dkim_signature sig;
+ ssize_t i, j;
size_t linelen;
+ char *tmp, *tmp2;
+ char tmpchar;
search.reqid = reqid;
session = RB_FIND(dkim_sessions, &dkim_sessions, &search);
if (session == NULL) {
- session = dkim_session_new(reqid);
+ if ((session = dkim_session_new(reqid)) == NULL)
+ return;
session->token = token;
} else if (session->token != token)
fatalx("Token incorrect");
linelen = strlen(line);
- if (fwrite(line, 1, linelen, session->origf) < linelen)
+ if (fprintf(session->origf, "%s\r\n", line) < linelen)
dkim_err(session, "Couldn't write to tempfile");
if (linelen != 0 && session->parsing_headers) {
- dkim_parse_header(session, line);
+ dkim_parse_header(session, line, 0);
} else if (linelen == 0 && session->parsing_headers) {
session->parsing_headers = 0;
} else if (line[0] == '.' && line[1] =='\0') {
EVP_EncodeBlock(session->bh, session->bbh,
hashalg == HASH_SHA1 ? SHA_DIGEST_LENGTH :
SHA256_DIGEST_LENGTH);
- dkim_built_signature(session);
+ if (!dkim_signature_printf(session, "bh=%s; h=", session->bh))
+ return;
+ /* Reverse order for ease of use of RFC6367 section 5.4.2 */
+ for (i = 0; session->headers[i] != NULL; i++)
+ continue;
+ if (EVP_DigestSignInit(session->md_ctx, NULL, hash_md, NULL,
+ pkey) <= 0) {
+ dkim_errx(session,
+ "Failed to initialize digest context");
+ return;
+ }
+ for (i--; i >= 0; i--) {
+ if (EVP_DigestSignUpdate(session->md_ctx,
+ session->headers[i],
+ strlen(session->headers[i])) <= 0 ||
+ EVP_DigestSignUpdate(session->md_ctx,
+ "\r\n", 2) <= 0) {
+ dkim_errx(session,
+ "Failed to update digest context");
+ return;
+ }
+ /* We're done with the cashed header after hashing */
+ for (tmp = session->headers[i]; tmp[0] != ':'; tmp++) {
+ if (tmp[0] == ' ' || tmp[0] == '\t')
+ break;
+ tmp[0] = tolower(tmp[0]);
+ }
+ tmp[0] = '\0';
+ if (!dkim_signature_printf(session, "%s%s",
+ session->headers[i + 1] == NULL ? "" : ":",
+ session->headers[i]))
+ return;
+ tmp[0] = tmpchar;
+ }
+ dkim_signature_printf(session, "; b=");
+ if (!dkim_signature_normalize(session))
+ return;
+ if ((tmp = strdup(session->signature.signature)) == NULL) {
+ dkim_err(session, "Can't create DKIM signature");
+ return;
+ }
+ dkim_parse_header(session, tmp, 1);
+ if (EVP_DigestSignUpdate(session->md_ctx, tmp,
+ strlen(tmp)) <= 0) {
+ dkim_err(session, "Failed to update digest context");
+ return;
+ }
+ free(tmp);
+ if (EVP_DigestSignFinal(session->md_ctx, NULL, &linelen) <= 0) {
+ dkim_err(session, "Failed to finalize digest");
+ return;
+ }
+ if ((tmp = malloc(linelen)) == NULL) {
+ dkim_err(session, "Can't allocate space for digest");
+ return;
+ }
+ if (EVP_DigestSignFinal(session->md_ctx, tmp, &linelen) <= 0) {
+ dkim_err(session, "Failed to finalize digest");
+ return;
+ }
+ EVP_EncodeBlock(session->bh, tmp, linelen);
+ free(tmp);
+ dkim_signature_printf(session, "%s\r\n", session->bh);
+ dkim_signature_normalize(session);
+ tmp = session->signature.signature;
+ while ((tmp2 = strchr(tmp, '\r')) != NULL) {
+ tmp2[0] = '\0';
+ smtp_filter_dataline(session->reqid, session->token,
+ "%s", tmp);
+ tmp = tmp2 + 2;
+ }
+ tmp = NULL;
+ linelen = 0;
+ rewind(session->origf);
+ while ((i = getline(&tmp, &linelen, session->origf)) != -1) {
+ tmp[i - 1] = '\0';
+ smtp_filter_dataline(session->reqid, session->token,
+ "%s", tmp);
+ }
} else
dkim_parse_body(session, line);
}
dkim_session_new(uint64_t reqid)
{
struct dkim_session *session;
+ struct dkim_signature *signature;
char origfile[] = "/tmp/filter-dkimXXXXXX";
int fd;
}
session->parsing_headers = 1;
- if (hashalg == HASH_SHA1)
- SHA1_Init(&(session->sha1));
- else
- SHA256_Init(&(session->sha256));
+ if (!dkim_hash_init(session))
+ return NULL;
session->body_whitelines = 0;
session->headers = calloc(1, sizeof(*(session->headers)));
if (session->headers == NULL) {
return NULL;
}
session->lastheader = 0;
+ session->signature.signature = NULL;
+ session->signature.size = 0;
+ session->signature.len = 0;
+ if (!dkim_signature_printf(session,
+ "DKIM-signature: %s; a=%s-%s; c=%s/%s; d=%s; s=%s; ", "v=1",
+ cryptalg == CRYPT_RSA ? "rsa" : "",
+ hashalg == HASH_SHA1 ? "sha1" : "sha256",
+ canonheader == CANON_SIMPLE ? "simple" : "relaxed",
+ canonbody == CANON_SIMPLE ? "simple" : "relaxed",
+ domain, selector))
+ return NULL;
+
+ if ((session->md_ctx = EVP_MD_CTX_new()) == NULL) {
+ dkim_errx(session, "Can't create hash context");
+ return NULL;
+ }
if (RB_INSERT(dkim_sessions, &dkim_sessions, session) != NULL)
fatalx("session already registered");
return session;
fclose(session->origf);
for (i = 0; session->headers[i] != NULL; i++)
free(session->headers[i]);
+ free(session->signature.signature);
free(session->headers);
free(session);
}
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))
if (headers[i - 1] == ':')
errx(1, "-h: header can't be empty");
- sign_headers = reallocarray(NULL, nsign_headers, sizeof(*sign_headers));
+ sign_headers = reallocarray(NULL, nsign_headers + 1, sizeof(*sign_headers));
if (sign_headers == NULL)
errx(1, NULL);
}
void
-dkim_parse_header(struct dkim_session *session, char *line)
+dkim_parse_header(struct dkim_session *session, char *line, int force)
{
size_t i;
size_t r, w;
size_t linelen;
size_t lastheader;
+ size_t hlen;
int fieldname;
char **mtmp;
char *htmp;
if ((line[0] == ' ' || line[0] == '\t') && !session->lastheader)
return;
if ((line[0] != ' ' && line[0] != '\t')) {
+ session->lastheader = 0;
for (i = 0; i < nsign_headers; i++) {
- if (strncasecmp(line, sign_headers[i],
- strlen(sign_headers[i])) == 0) {
+ hlen = strlen(sign_headers[i]);
+ if (strncasecmp(line, sign_headers[i], hlen) == 0) {
+ while (line[hlen] == ' ' || line[hlen] == '\t')
+ hlen++;
+ if (line[hlen] != ':')
+ continue;
break;
}
}
- if (i == nsign_headers) {
- session->lastheader = 0;
+ if (i == nsign_headers && !force)
return;
- }
}
if (canonheader == CANON_RELAXED) {
r++;
continue;
}
- if (line[r] == ' ' || line[r] == '\t') {
+ if (line[r] == ' ' || line[r] == '\t' ||
+ line[r] == '\r' || line[r] == '\n') {
if (r != 0 && line[w - 1] == ' ')
continue;
else
return;
}
-void
-dkim_built_signature(struct dkim_session *session)
+int
+dkim_signature_normalize(struct dkim_session *session)
{
- char *signature;
- size_t signaturesize = 1024;
- size_t signaturelen = 0;
- size_t linelen = 0;
- size_t ncopied;
+ size_t i;
+ size_t linelen;
+ size_t checkpoint;
+ size_t skip;
+ size_t *headerlen = &(session->signature.len);
+ int headername = 1;
+ char tag = '\0';
+ char *sig = session->signature.signature;
- 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);
+ for (linelen = i = 0; sig[i] != '\0'; i++) {
+ if (sig[i] == '\n') {
+ checkpoint = 0;
+ linelen = 0;
+ continue;
+ }
+ if (sig[i] == '\t')
+ linelen = (linelen + 8) & ~7;
else
- (void) strlcat(signature, "c=relaxed", signaturesize);
+ linelen++;
+ if (headername) {
+ if (sig[i] == ':') {
+ headername = 0;
+ checkpoint = i;
+ }
+ continue;
+ }
+ if (linelen > DKIM_SIGNATURE_LINELEN) {
+ if (checkpoint == 0)
+ break;
+ for (skip = checkpoint + 1;
+ sig[skip] == ' ' || sig[skip] == '\t';
+ skip++)
+ continue;
+ skip -= checkpoint + 1;
+ if (!dkim_signature_need(session,
+ skip > 3 ? 0 : 3 - skip))
+ return 0;
+ memmove(sig + checkpoint + 3,
+ sig + checkpoint + skip,
+ *headerlen - skip - checkpoint);
+ sig[checkpoint + 1] = '\r';
+ sig[checkpoint + 2] = '\n';
+ sig[checkpoint + 3] = '\t';
+ linelen = 8;
+ *headerlen = *headerlen + 3 - skip;
+ i = checkpoint + 3;
+ checkpoint = 0;
+ }
+ if (sig[i] == ';') {
+ checkpoint = i;
+ tag = '\0';
+ continue;
+ }
+ switch (tag) {
+ case 'B':
+ case 'b':
+ checkpoint = i;
+ break;
+ case 'h':
+ if (sig[i] == ':')
+ checkpoint = i;
+ }
+ if (tag == '\0' && sig[i] != ' ' && sig[i] != '\t') {
+ if ((tag = sig[i]) == 'b' && sig[i + 1] == 'h' &&
+ sig[i + 2] == '=') {
+ tag = 'B';
+ linelen += 2;
+ i += 2;
+ } else
+ tag = sig[i];
+ }
}
- if (canonbody != CANON_SIMPLE)
- (void) strlcat(signature, "/relaxed", signaturesize);
+ return 1;
+}
- (void) strlcat(signature, "d=", signaturesize);
- (void) strlcat(signature, domain, signaturesize);
- (void) strlcat(signature, "; ", signaturesize);
- printf("%s\n", signature);
+int
+dkim_hash_init(struct dkim_session *session)
+{
+ if (hashalg == HASH_SHA1) {
+ if (SHA1_Init(&(session->sha1)) == 0) {
+ dkim_errx(session, "Unable to init hash");
+ return 0;
+ }
+ } else {
+ if (SHA256_Init(&(session->sha256)) == 0) {
+ dkim_errx(session, "Unable to init hash");
+ return 0;
+ }
+ }
+ return 1;
}
int
return 1;
}
+int
+dkim_signature_printf(struct dkim_session *session, char *fmt, ...)
+{
+ struct dkim_signature *sig = &(session->signature);
+ va_list ap;
+ size_t newlen;
+ char *tmp;
+ size_t len;
+
+ va_start(ap, fmt);
+ if ((len = vsnprintf(sig->signature + sig->len, sig->size - sig->len,
+ fmt, ap)) >= sig->size - sig->len) {
+ va_end(ap);
+ if (!dkim_signature_need(session, len))
+ return 0;
+ va_start(ap, fmt);
+ if ((len = vsnprintf(sig->signature + sig->len, sig->size - sig->len,
+ fmt, ap)) >= sig->size - sig->len)
+ fatalx("Miscalculated header size");
+ }
+ sig->len += len;
+ va_end(ap);
+ return 1;
+}
+
+int
+dkim_signature_need(struct dkim_session *session, size_t len)
+{
+ struct dkim_signature *sig = &(session->signature);
+ char *tmp;
+
+ if (sig->len + len <= sig->size)
+ return 1;
+ sig->size = (((len + sig->len - sig->size) / 512) + 1) * 512;
+ if ((tmp = realloc(sig->signature, sig->size)) == NULL) {
+ dkim_err(session, "No room for signature");
+ return 0;
+ }
+ sig->signature = tmp;
+ return 1;
+}
+
__dead void
usage(void)
{