#include "rsa.h"
#define PDKIM_SIGNATURE_VERSION "1"
-#define PDKIM_PUB_RECORD_VERSION "DKIM1"
+#define PDKIM_PUB_RECORD_VERSION US "DKIM1"
#define PDKIM_MAX_HEADER_LEN 65536
#define PDKIM_MAX_HEADERS 512
/* -------------------------------------------------------------------------- */
struct pdkim_stringlist {
- char *value;
- int tag;
- void *next;
+ uschar * value;
+ int tag;
+ void * next;
};
/* -------------------------------------------------------------------------- */
/* A bunch of list constants */
-const char *pdkim_querymethods[] = {
- "dns/txt",
+const uschar * pdkim_querymethods[] = {
+ US"dns/txt",
NULL
};
-const char *pdkim_algos[] = {
- "rsa-sha256",
- "rsa-sha1",
+const uschar * pdkim_algos[] = {
+ US"rsa-sha256",
+ US"rsa-sha1",
NULL
};
-const char *pdkim_canons[] = {
- "simple",
- "relaxed",
+const uschar * pdkim_canons[] = {
+ US"simple",
+ US"relaxed",
NULL
};
-const char *pdkim_hashes[] = {
- "sha256",
- "sha1",
+const uschar * pdkim_hashes[] = {
+ US"sha256",
+ US"sha1",
NULL
};
-const char *pdkim_keytypes[] = {
- "rsa",
+const uschar * pdkim_keytypes[] = {
+ US"rsa",
NULL
};
typedef struct pdkim_combined_canon_entry {
- const char *str;
+ const uschar * str;
int canon_headers;
int canon_body;
} pdkim_combined_canon_entry;
pdkim_combined_canon_entry pdkim_combined_canons[] = {
- { "simple/simple", PDKIM_CANON_SIMPLE, PDKIM_CANON_SIMPLE },
- { "simple/relaxed", PDKIM_CANON_SIMPLE, PDKIM_CANON_RELAXED },
- { "relaxed/simple", PDKIM_CANON_RELAXED, PDKIM_CANON_SIMPLE },
- { "relaxed/relaxed", PDKIM_CANON_RELAXED, PDKIM_CANON_RELAXED },
- { "simple", PDKIM_CANON_SIMPLE, PDKIM_CANON_SIMPLE },
- { "relaxed", PDKIM_CANON_RELAXED, PDKIM_CANON_SIMPLE },
- { NULL, 0, 0 }
+ { US"simple/simple", PDKIM_CANON_SIMPLE, PDKIM_CANON_SIMPLE },
+ { US"simple/relaxed", PDKIM_CANON_SIMPLE, PDKIM_CANON_RELAXED },
+ { US"relaxed/simple", PDKIM_CANON_RELAXED, PDKIM_CANON_SIMPLE },
+ { US"relaxed/relaxed", PDKIM_CANON_RELAXED, PDKIM_CANON_RELAXED },
+ { US"simple", PDKIM_CANON_SIMPLE, PDKIM_CANON_SIMPLE },
+ { US"relaxed", PDKIM_CANON_RELAXED, PDKIM_CANON_SIMPLE },
+ { NULL, 0, 0 }
};
const char *
pdkim_verify_status_str(int status)
{
- switch(status) {
- case PDKIM_VERIFY_NONE: return "PDKIM_VERIFY_NONE";
- case PDKIM_VERIFY_INVALID: return "PDKIM_VERIFY_INVALID";
- case PDKIM_VERIFY_FAIL: return "PDKIM_VERIFY_FAIL";
- case PDKIM_VERIFY_PASS: return "PDKIM_VERIFY_PASS";
- default: return "PDKIM_VERIFY_UNKNOWN";
+switch(status)
+ {
+ case PDKIM_VERIFY_NONE: return "PDKIM_VERIFY_NONE";
+ case PDKIM_VERIFY_INVALID: return "PDKIM_VERIFY_INVALID";
+ case PDKIM_VERIFY_FAIL: return "PDKIM_VERIFY_FAIL";
+ case PDKIM_VERIFY_PASS: return "PDKIM_VERIFY_PASS";
+ default: return "PDKIM_VERIFY_UNKNOWN";
}
}
const char *
pdkim_verify_ext_status_str(int ext_status)
{
- switch(ext_status) {
- case PDKIM_VERIFY_FAIL_BODY: return "PDKIM_VERIFY_FAIL_BODY";
- case PDKIM_VERIFY_FAIL_MESSAGE: return "PDKIM_VERIFY_FAIL_MESSAGE";
- case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE: return "PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE";
- case PDKIM_VERIFY_INVALID_BUFFER_SIZE: return "PDKIM_VERIFY_INVALID_BUFFER_SIZE";
- case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD: return "PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD";
- case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return "PDKIM_VERIFY_INVALID_PUBKEY_IMPORT";
- default: return "PDKIM_VERIFY_UNKNOWN";
+switch(ext_status)
+ {
+ case PDKIM_VERIFY_FAIL_BODY: return "PDKIM_VERIFY_FAIL_BODY";
+ case PDKIM_VERIFY_FAIL_MESSAGE: return "PDKIM_VERIFY_FAIL_MESSAGE";
+ case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE: return "PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE";
+ case PDKIM_VERIFY_INVALID_BUFFER_SIZE: return "PDKIM_VERIFY_INVALID_BUFFER_SIZE";
+ case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD: return "PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD";
+ case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return "PDKIM_VERIFY_INVALID_PUBKEY_IMPORT";
+ case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR: return "PDKIM_VERIFY_INVALID_SIGNATURE_ERROR";
+ case PDKIM_VERIFY_INVALID_DKIM_VERSION: return "PDKIM_VERIFY_INVALID_DKIM_VERSION";
+ default: return "PDKIM_VERIFY_UNKNOWN";
+ }
+}
+
+const char *
+pdkim_errstr(int status)
+{
+switch(status)
+ {
+ case PDKIM_OK: return "OK";
+ case PDKIM_FAIL: return "FAIL";
+ case PDKIM_ERR_RSA_PRIVKEY: return "RSA_PRIVKEY";
+ case PDKIM_ERR_RSA_SIGNING: return "RSA SIGNING";
+ case PDKIM_ERR_LONG_LINE: return "RSA_LONG_LINE";
+ case PDKIM_ERR_BUFFER_TOO_SMALL: return "BUFFER_TOO_SMALL";
+ case PDKIM_SIGN_PRIVKEY_WRAP: return "PRIVKEY_WRAP";
+ case PDKIM_SIGN_PRIVKEY_B64D: return "PRIVKEY_B64D";
+ default: return "(unknown)";
}
}
/*XXX might be safer done using a pdkim_stringlist for "tick" */
static int
-header_name_match(const uschar * header, char * tick)
+header_name_match(const uschar * header, uschar * tick)
{
uschar * hname;
uschar * lcopy;
/* -------------------------------------------------------------------------- */
#define PDKIM_QP_ERROR_DECODE -1
-static char *
-pdkim_decode_qp_char(char *qp_p, int *c)
+static uschar *
+pdkim_decode_qp_char(uschar *qp_p, int *c)
{
-char *initial_pos = qp_p;
+uschar *initial_pos = qp_p;
/* Advance one char */
qp_p++;
/* -------------------------------------------------------------------------- */
-static char *
+static uschar *
pdkim_decode_qp(uschar * str)
{
int nchar = 0;
memset(sig, 0, sizeof(pdkim_signature));
sig->bodylength = -1;
+/* Set so invalid/missing data error display is accurate */
+sig->algo = -1;
+sig->version = 0;
+
q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1);
for (p = raw_hdr; ; p++)
if (where == PDKIM_HDR_TAG)
{
if (c >= 'a' && c <= 'z')
- cur_tag = string_cat(cur_tag, &ts, &tl, p, 1);
+ cur_tag = string_catn(cur_tag, &ts, &tl, p, 1);
if (c == '=')
{
case 'v':
/* We only support version 1, and that is currently the
only version there is. */
- if (Ustrcmp(cur_val, PDKIM_SIGNATURE_VERSION) == 0)
- sig->version = 1;
+ sig->version =
+ Ustrcmp(cur_val, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
break;
case 'a':
for (i = 0; pdkim_algos[i]; i++)
where = PDKIM_HDR_LIMBO;
}
else
- cur_val = string_cat(cur_val, &vs, &vl, p, 1);
+ cur_val = string_catn(cur_val, &vs, &vl, p, 1);
}
NEXT_CHAR:
*q++ = c;
}
-/* Make sure the most important bits are there. */
-if (!sig->version)
- return NULL;
-
*q = '\0';
/* Chomp raw header. The final newline must not be added to the signature. */
while (--q > sig->rawsig_no_b_val && (*q == '\r' || *q == '\n'))
{
debug_printf(
"PDKIM >> Raw signature w/o b= tag value >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
- pdkim_quoteprint(US sig->rawsig_no_b_val, strlen(sig->rawsig_no_b_val));
+ pdkim_quoteprint(US sig->rawsig_no_b_val, Ustrlen(sig->rawsig_no_b_val));
debug_printf(
"PDKIM >> Sig size: %4u bits\n", (unsigned) sig->sigdata.len*8);
debug_printf(
"PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
}
-exim_sha_init(&sig->body_hash, sig->algo == PDKIM_ALGO_RSA_SHA1);
+exim_sha_init(&sig->body_hash, sig->algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256);
return sig;
}
/* -------------------------------------------------------------------------- */
static pdkim_pubkey *
-pdkim_parse_pubkey_record(pdkim_ctx *ctx, char *raw_record)
+pdkim_parse_pubkey_record(pdkim_ctx *ctx, const uschar *raw_record)
{
pdkim_pubkey *pub;
-char *p;
+const uschar *p;
uschar * cur_tag = NULL; int ts = 0, tl = 0;
uschar * cur_val = NULL; int vs = 0, vl = 0;
int where = PDKIM_HDR_LIMBO;
memset(pub, 0, sizeof(pdkim_pubkey));
for (p = raw_record; ; p++)
- {
- char c = *p;
-
- /* Ignore FWS */
- if (c == '\r' || c == '\n')
- goto NEXT_CHAR;
-
- if (where == PDKIM_HDR_LIMBO)
{
- /* In limbo, just wait for a tag-char to appear */
- if (!(c >= 'a' && c <= 'z'))
- goto NEXT_CHAR;
-
- where = PDKIM_HDR_TAG;
- }
+ uschar c = *p;
- if (where == PDKIM_HDR_TAG)
- {
- if (c >= 'a' && c <= 'z')
- cur_tag = string_cat(cur_tag, &ts, &tl, p, 1);
-
- if (c == '=')
+ /* Ignore FWS */
+ if (c != '\r' && c != '\n') switch (where)
{
- cur_tag[tl] = '\0';
- where = PDKIM_HDR_VALUE;
- goto NEXT_CHAR;
- }
- }
+ case PDKIM_HDR_LIMBO: /* In limbo, just wait for a tag-char to appear */
+ if (!(c >= 'a' && c <= 'z'))
+ break;
+ where = PDKIM_HDR_TAG;
+ /*FALLTHROUGH*/
- if (where == PDKIM_HDR_VALUE)
- {
- if (c == ';' || c == '\0')
- {
- if (tl && vl)
- {
- cur_val[vl] = '\0';
- pdkim_strtrim(cur_val);
- DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag, cur_val);
+ case PDKIM_HDR_TAG:
+ if (c >= 'a' && c <= 'z')
+ cur_tag = string_catn(cur_tag, &ts, &tl, p, 1);
- switch (cur_tag[0])
+ if (c == '=')
{
- case 'v':
- /* This tag isn't evaluated because:
- - We only support version DKIM1.
- - Which is the default for this value (set below)
- - Other versions are currently not specified. */
- break;
- case 'h':
- case 'k':
- pub->hashes = string_copy(cur_val); break;
- case 'g':
- pub->granularity = string_copy(cur_val); break;
- case 'n':
- pub->notes = pdkim_decode_qp(cur_val); break;
- case 'p':
- pdkim_decode_base64(US cur_val, &pub->key);
- break;
- case 's':
- pub->srvtype = string_copy(cur_val); break;
- case 't':
- if (strchr(cur_val, 'y') != NULL) pub->testing = 1;
- if (strchr(cur_val, 's') != NULL) pub->no_subdomaining = 1;
- break;
- default:
- DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
- break;
+ cur_tag[tl] = '\0';
+ where = PDKIM_HDR_VALUE;
}
- }
- tl = 0;
- vl = 0;
- where = PDKIM_HDR_LIMBO;
+ break;
+
+ case PDKIM_HDR_VALUE:
+ if (c == ';' || c == '\0')
+ {
+ if (tl && vl)
+ {
+ cur_val[vl] = '\0';
+ pdkim_strtrim(cur_val);
+ DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag, cur_val);
+
+ switch (cur_tag[0])
+ {
+ case 'v':
+ pub->version = string_copy(cur_val); break;
+ case 'h':
+ case 'k':
+/* This field appears to never be used. Also, unclear why
+a 'k' (key-type_ would go in this field name. There is a field
+"keytype", also never used.
+ pub->hashes = string_copy(cur_val);
+*/
+ break;
+ case 'g':
+ pub->granularity = string_copy(cur_val); break;
+ case 'n':
+ pub->notes = pdkim_decode_qp(cur_val); break;
+ case 'p':
+ pdkim_decode_base64(US cur_val, &pub->key); break;
+ case 's':
+ pub->srvtype = string_copy(cur_val); break;
+ case 't':
+ if (Ustrchr(cur_val, 'y') != NULL) pub->testing = 1;
+ if (Ustrchr(cur_val, 's') != NULL) pub->no_subdomaining = 1;
+ break;
+ default:
+ DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
+ break;
+ }
+ }
+ tl = 0;
+ vl = 0;
+ where = PDKIM_HDR_LIMBO;
+ }
+ else
+ cur_val = string_catn(cur_val, &vs, &vl, p, 1);
+ break;
}
- else
- cur_val = string_cat(cur_val, &vs, &vl, p, 1);
- }
-NEXT_CHAR:
- if (c == '\0') break;
- }
+ if (c == '\0') break;
+ }
/* Set fallback defaults */
if (!pub->version ) pub->version = string_copy(PDKIM_PUB_RECORD_VERSION);
+else if (Ustrcmp(pub->version, PDKIM_PUB_RECORD_VERSION) != 0) return NULL;
+
if (!pub->granularity) pub->granularity = string_copy(US"*");
-if (!pub->keytype ) pub->keytype = string_copy("rsa");
+/*
+if (!pub->keytype ) pub->keytype = string_copy(US"rsa");
+*/
if (!pub->srvtype ) pub->srvtype = string_copy(US"*");
/* p= is required */
const char *p;
int q = 0;
- relaxed_data = store_get(len+1);
+ /* We want to be able to free this else we allocate
+ for the entire message which could be many MB. Since
+ we don't know what allocations the SHA routines might
+ do, not safe to use store_get()/store_reset(). */
+
+ relaxed_data = store_malloc(len+1);
for (p = data; *p; p++)
{
if (canon_len > 0)
{
- exim_sha_update(&sig->body_hash, CCS canon_data, canon_len);
+ exim_sha_update(&sig->body_hash, CUS canon_data, canon_len);
sig->signed_body_bytes += canon_len;
DEBUG(D_acl) pdkim_quoteprint(canon_data, canon_len);
}
sig = sig->next;
}
+if (relaxed_data) store_free(relaxed_data);
return PDKIM_OK;
}
DEBUG(D_acl)
{
debug_printf("PDKIM [%s] Body bytes hashed: %lu\n"
- "PDKIM [%s] bh computed: ",
+ "PDKIM [%s] Body hash computed: ",
sig->domain, sig->signed_body_bytes, sig->domain);
pdkim_hexprint(CUS bh.data, bh.len);
}
/* SIGNING -------------------------------------------------------------- */
- if (ctx->mode == PDKIM_MODE_SIGN)
+ if (ctx->flags & PDKIM_MODE_SIGN)
{
sig->bodyhash = bh;
{
DEBUG(D_acl)
{
- debug_printf("PDKIM [%s] bh signature: ", sig->domain);
+ debug_printf("PDKIM [%s] Body hash signature from headers: ", sig->domain);
pdkim_hexprint(sig->bodyhash.data,
exim_sha_hashlen(&sig->body_hash));
debug_printf("PDKIM [%s] Body hash did NOT verify\n", sig->domain);
+static int
+pdkim_body_complete(pdkim_ctx * ctx)
+{
+pdkim_signature * sig = ctx->sig; /*XXX assumes only one sig */
+
+/* In simple body mode, if any empty lines were buffered,
+replace with one. rfc 4871 3.4.3 */
+/*XXX checking the signed-body-bytes is a gross hack; I think
+it indicates that all linebreaks should be buffered, including
+the one terminating a text line */
+
+if ( sig && sig->canon_body == PDKIM_CANON_SIMPLE
+ && sig->signed_body_bytes == 0
+ && ctx->num_buffered_crlf > 0
+ )
+ pdkim_update_bodyhash(ctx, "\r\n", 2);
+
+ctx->flags |= PDKIM_SEEN_EOD;
+ctx->linebuf_offset = 0;
+return PDKIM_OK;
+}
+
+
+
/* -------------------------------------------------------------------------- */
-/* Callback from pdkim_feed below for processing complete body lines */
+/* Call from pdkim_feed below for processing complete body lines */
static int
pdkim_bodyline_complete(pdkim_ctx *ctx)
pdkim_signature *sig = ctx->sig; /*XXX assumes only one sig */
/* Ignore extra data if we've seen the end-of-data marker */
-if (ctx->seen_eod) goto BAIL;
+if (ctx->flags & PDKIM_SEEN_EOD) goto BAIL;
/* We've always got one extra byte to stuff a zero ... */
ctx->linebuf[ctx->linebuf_offset] = '\0';
/* Terminate on EOD marker */
-if (memcmp(p, ".\r\n", 3) == 0)
+if (ctx->flags & PDKIM_DOT_TERM)
{
- /* In simple body mode, if any empty lines were buffered,
- replace with one. rfc 4871 3.4.3 */
- /*XXX checking the signed-body-bytes is a gross hack; I think
- it indicates that all linebreaks should be buffered, including
- the one terminating a text line */
- if ( sig && sig->canon_body == PDKIM_CANON_SIMPLE
- && sig->signed_body_bytes == 0
- && ctx->num_buffered_crlf > 0
- )
- pdkim_update_bodyhash(ctx, "\r\n", 2);
+ if ( memcmp(p, ".\r\n", 3) == 0)
+ return pdkim_body_complete(ctx);
- ctx->seen_eod = TRUE;
- goto BAIL;
- }
-/* Unstuff dots */
-if (memcmp(p, "..", 2) == 0)
- {
- p++;
- n--;
+ /* Unstuff dots */
+ if (memcmp(p, "..", 2) == 0)
+ {
+ p++;
+ n--;
+ }
}
/* Empty lines need to be buffered until we find a non-empty line */
if (ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
/* SIGNING -------------------------------------------------------------- */
-if (ctx->mode == PDKIM_MODE_SIGN)
+if (ctx->flags & PDKIM_MODE_SIGN)
{
pdkim_signature *sig;
/* VERIFICATION ----------------------------------------------------------- */
/* DKIM-Signature: headers are added to the verification list */
-if (ctx->mode == PDKIM_MODE_VERIFY)
+else
{
- if (strncasecmp(ctx->cur_header,
+ if (strncasecmp(CCS ctx->cur_header,
DKIM_SIGNATURE_HEADERNAME,
- strlen(DKIM_SIGNATURE_HEADERNAME)) == 0)
+ Ustrlen(DKIM_SIGNATURE_HEADERNAME)) == 0)
{
pdkim_signature *new_sig;
{
int p;
-for (p = 0; p<len; p++)
+/* Alternate EOD signal, used in non-dotstuffing mode */
+if (!data)
+ pdkim_body_complete(ctx);
+
+else for (p = 0; p<len; p++)
{
uschar c = data[p];
- if (ctx->past_headers)
+ if (ctx->flags & PDKIM_PAST_HDRS)
{
/* Processing body byte */
ctx->linebuf[ctx->linebuf_offset++] = c;
{
if (c == '\n')
{
- if (ctx->seen_lf)
+ if (ctx->flags & PDKIM_SEEN_LF)
{
int rc = pdkim_header_complete(ctx); /* Seen last header line */
if (rc != PDKIM_OK) return rc;
- ctx->past_headers = TRUE;
- ctx->seen_lf = 0;
+ ctx->flags = ctx->flags & ~PDKIM_SEEN_LF | PDKIM_PAST_HDRS;
DEBUG(D_acl) debug_printf(
"PDKIM >> Body data for hash, canonicalized >>>>>>>>>>>>>>>>>>>>>>\n");
continue;
}
else
- ctx->seen_lf = TRUE;
+ ctx->flags |= PDKIM_SEEN_LF;
}
- else if (ctx->seen_lf)
+ else if (ctx->flags & PDKIM_SEEN_LF)
{
if (!(c == '\t' || c == ' '))
{
int rc = pdkim_header_complete(ctx); /* End of header */
if (rc != PDKIM_OK) return rc;
}
- ctx->seen_lf = FALSE;
+ ctx->flags &= ~PDKIM_SEEN_LF;
}
}
if (ctx->cur_header_len < PDKIM_MAX_HEADER_LEN)
- ctx->cur_header = string_cat(ctx->cur_header, &ctx->cur_header_size,
- &ctx->cur_header_len, &data[p], 1);
+ ctx->cur_header = string_catn(ctx->cur_header, &ctx->cur_header_size,
+ &ctx->cur_header_len, CUS &data[p], 1);
}
}
return PDKIM_OK;
pdkim_hdr_cont(uschar * str, int * size, int * ptr, int * col)
{
*col = 1;
-return string_cat(str, size, ptr, US"\r\n\t", 3);
+return string_catn(str, size, ptr, US"\r\n\t", 3);
}
l = Ustrlen(pad);
if (*col + l > 78)
str = pdkim_hdr_cont(str, size, ptr, col);
- str = string_cat(str, size, ptr, pad, l);
+ str = string_catn(str, size, ptr, pad, l);
*col += l;
}
{ /* this fragment will not fit on a single line */
if (pad)
{
- str = string_cat(str, size, ptr, US" ", 1);
+ str = string_catn(str, size, ptr, US" ", 1);
*col += 1;
pad = NULL; /* only want this once */
l--;
{
size_t sl = Ustrlen(intro);
- str = string_cat(str, size, ptr, intro, sl);
+ str = string_catn(str, size, ptr, intro, sl);
*col += sl;
l -= sl;
intro = NULL; /* only want this once */
size_t sl = Ustrlen(payload);
size_t chomp = *col+sl < 77 ? sl : 78-*col;
- str = string_cat(str, size, ptr, payload, chomp);
+ str = string_catn(str, size, ptr, payload, chomp);
*col += chomp;
payload += chomp;
l -= chomp-1;
if (pad)
{
- str = string_cat(str, size, ptr, US" ", 1);
+ str = string_catn(str, size, ptr, US" ", 1);
*col += 1;
pad = NULL;
}
{
size_t sl = Ustrlen(intro);
- str = string_cat(str, size, ptr, intro, sl);
+ str = string_catn(str, size, ptr, intro, sl);
*col += sl;
l -= sl;
intro = NULL;
{
size_t sl = Ustrlen(payload);
- str = string_cat(str, size, ptr, payload, sl);
+ str = string_catn(str, size, ptr, payload, sl);
*col += sl;
}
uschar * hdr; int hdr_size = 0, hdr_len = 0;
uschar * canon_all; int can_size = 0, can_len = 0;
-canon_all = string_cat(NULL, &can_size, &can_len,
- pdkim_canons[sig->canon_headers], -1);
-canon_all = string_cat(canon_all, &can_size, &can_len, US"/", 1);
-canon_all = string_cat(canon_all, &can_size, &can_len,
- pdkim_canons[sig->canon_body], -1);
+canon_all = string_cat (NULL, &can_size, &can_len,
+ pdkim_canons[sig->canon_headers]);
+canon_all = string_catn(canon_all, &can_size, &can_len, US"/", 1);
+canon_all = string_cat (canon_all, &can_size, &can_len,
+ pdkim_canons[sig->canon_body]);
canon_all[can_len] = '\0';
hdr = string_cat(NULL, &hdr_size, &hdr_len,
- "DKIM-Signature: v="PDKIM_SIGNATURE_VERSION, -1);
+ US"DKIM-Signature: v="PDKIM_SIGNATURE_VERSION);
col = hdr_len;
/* Required and static bits */
/* list of header names can be split between items. */
{
- uschar * n = CS string_copy(sig->headernames);
+ uschar * n = string_copy(sig->headernames);
uschar * i = US"h=";
uschar * s = US";";
if (sig->created > 0)
{
- char minibuf[20];
+ uschar minibuf[20];
- snprintf(minibuf, 20, "%lu", sig->created);
+ snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->created);
hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"t=", minibuf);
}
if (sig->expires > 0)
{
- char minibuf[20];
+ uschar minibuf[20];
- snprintf(minibuf, 20, "%lu", sig->expires);
+ snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->expires);
hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"x=", minibuf);
}
if (sig->bodylength >= 0)
{
- char minibuf[20];
+ uschar minibuf[20];
- snprintf(minibuf, 20, "%lu", sig->bodylength);
+ snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->bodylength);
hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"l=", minibuf);
}
}
+/* -------------------------------------------------------------------------- */
+
+static pdkim_pubkey *
+pdkim_key_from_dns(pdkim_ctx * ctx, pdkim_signature * sig, ev_ctx * vctx)
+{
+uschar * dns_txt_name, * dns_txt_reply;
+pdkim_pubkey * p;
+const uschar * errstr;
+
+/* Fetch public key for signing domain, from DNS */
+
+dns_txt_name = string_sprintf("%s._domainkey.%s.", sig->selector, sig->domain);
+
+dns_txt_reply = store_get(PDKIM_DNS_TXT_MAX_RECLEN);
+memset(dns_txt_reply, 0, PDKIM_DNS_TXT_MAX_RECLEN);
+
+if ( ctx->dns_txt_callback(CS dns_txt_name, CS dns_txt_reply) != PDKIM_OK
+ || dns_txt_reply[0] == '\0'
+ )
+ {
+ sig->verify_status = PDKIM_VERIFY_INVALID;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE;
+ return NULL;
+ }
+
+DEBUG(D_acl)
+ {
+ debug_printf(
+ "PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
+ " Raw record: ");
+ pdkim_quoteprint(CUS dns_txt_reply, Ustrlen(dns_txt_reply));
+ }
+
+if ( !(p = pdkim_parse_pubkey_record(ctx, CUS dns_txt_reply))
+ || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
+ )
+ {
+ sig->verify_status = PDKIM_VERIFY_INVALID;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD;
+
+ DEBUG(D_acl)
+ {
+ if (p)
+ debug_printf(" Invalid public key service type '%s'\n", p->srvtype);
+ else
+ debug_printf(" Error while parsing public key record\n");
+ debug_printf(
+ "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }
+ return NULL;
+ }
+
+DEBUG(D_acl) debug_printf(
+ "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+
+/* Import public key */
+if ((errstr = exim_rsa_verify_init(&p->key, vctx)))
+ {
+ DEBUG(D_acl) debug_printf("verify_init: %s\n", errstr);
+ sig->verify_status = PDKIM_VERIFY_INVALID;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
+ return NULL;
+ }
+
+return p;
+}
+
+
/* -------------------------------------------------------------------------- */
DLLEXPORT int
pdkim_feed_finish(pdkim_ctx *ctx, pdkim_signature **return_signatures)
{
pdkim_signature *sig = ctx->sig;
-uschar * headernames = NULL; /* Collected signed header names */
-int hs = 0, hl = 0;
/* Check if we must still flush a (partial) header. If that is the
case, the message has no body, and we must compute a body hash
{
BOOL is_sha1 = sig->algo == PDKIM_ALGO_RSA_SHA1;
hctx hhash_ctx;
- uschar * sig_hdr;
+ uschar * sig_hdr = US"";
blob hhash;
blob hdata;
int hdata_alloc = 0;
hdata.data = NULL;
hdata.len = 0;
- exim_sha_init(&hhash_ctx, is_sha1);
+ exim_sha_init(&hhash_ctx, is_sha1 ? HASH_SHA1 : HASH_SHA256);
DEBUG(D_acl) debug_printf(
- "PDKIM >> Hashed header data, canonicalized, in sequence >>>>>>>>>>>>>>\n");
+ "PDKIM >> Header data for hash, canonicalized, in sequence >>>>>>>>>>>>>>\n");
/* SIGNING ---------------------------------------------------------------- */
/* When signing, walk through our header list and add them to the hash. As we
Then append to that list any remaining header names for which there was no
header to sign. */
- if (ctx->mode == PDKIM_MODE_SIGN)
+ if (ctx->flags & PDKIM_MODE_SIGN)
{
+ uschar * headernames = NULL; /* Collected signed header names */
+ int hs = 0, hl = 0;
pdkim_stringlist *p;
const uschar * l;
uschar * s;
/* Collect header names (Note: colon presence is guaranteed here) */
uschar * q = Ustrchr(p->value, ':');
- headernames = string_cat(headernames, &hs, &hl,
+ headernames = string_catn(headernames, &hs, &hl,
p->value, (q - US p->value) + (p->next ? 1 : 0));
rh = sig->canon_headers == PDKIM_CANON_RELAXED
: string_copy(CUS p->value); /* just copy it for simple canon */
/* Feed header to the hash algorithm */
- exim_sha_update(&hhash_ctx, CCS rh, Ustrlen(rh));
+ exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh));
/* Remember headers block for signing (when the library cannot do incremental) */
(void) exim_rsa_data_append(&hdata, &hdata_alloc, rh);
if (*s != '_')
{ /*SSS string_append_listele() */
if (hl > 0 && headernames[hl-1] != ':')
- headernames = string_cat(headernames, &hs, &hl, US":", 1);
+ headernames = string_catn(headernames, &hs, &hl, US":", 1);
- headernames = string_cat(headernames, &hs, &hl, s, -1);
+ headernames = string_cat(headernames, &hs, &hl, s);
}
headernames[hl] = '\0';
/* Copy headernames to signature struct */
sig->headernames = headernames;
- headernames = NULL, hs = hl = 0;
/* Create signature header with b= omitted */
sig_hdr = pdkim_create_header(sig, FALSE);
add the headers to the hash in that order. */
else
{
- uschar * b = string_copy(sig->headernames);
- uschar * p = b;
+ uschar * p = sig->headernames;
uschar * q;
pdkim_stringlist * hdrs;
- /* clear tags */
- for (hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
- hdrs->tag = 0;
-
- while(1)
+ if (p)
{
- if ((q = Ustrchr(p, ':')))
- *q = '\0';
-
-/*XXX walk the list of headers in same order as received. */
+ /* clear tags */
for (hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
- if ( hdrs->tag == 0
- && strncasecmp(hdrs->value, CS p, Ustrlen(p)) == 0
- && (hdrs->value)[Ustrlen(p)] == ':'
- )
- {
- uschar * rh = sig->canon_headers == PDKIM_CANON_RELAXED
- ? pdkim_relax_header(hdrs->value, 1) /* cook header for relaxed canon */
- : string_copy(CUS hdrs->value); /* just copy it for simple canon */
+ hdrs->tag = 0;
- /* Feed header to the hash algorithm */
- exim_sha_update(&hhash_ctx, CCS rh, Ustrlen(rh));
+ p = string_copy(p);
+ while(1)
+ {
+ if ((q = Ustrchr(p, ':')))
+ *q = '\0';
+
+ /*XXX walk the list of headers in same order as received. */
+ for (hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
+ if ( hdrs->tag == 0
+ && strncasecmp(CCS hdrs->value, CCS p, Ustrlen(p)) == 0
+ && (hdrs->value)[Ustrlen(p)] == ':'
+ )
+ {
+ /* cook header for relaxed canon, or just copy it for simple */
+
+ uschar * rh = sig->canon_headers == PDKIM_CANON_RELAXED
+ ? pdkim_relax_header(hdrs->value, 1)
+ : string_copy(CUS hdrs->value);
+
+ /* Feed header to the hash algorithm */
+ exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh));
+
+ DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
+ hdrs->tag = 1;
+ break;
+ }
- DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
- hdrs->tag = 1;
- break;
- }
+ if (!q) break;
+ p = q+1;
+ }
- if (!q) break;
- p = q+1;
+ sig_hdr = string_copy(sig->rawsig_no_b_val);
}
-
- sig_hdr = string_copy(sig->rawsig_no_b_val);
}
DEBUG(D_acl) debug_printf(
{
debug_printf(
"PDKIM >> Signed DKIM-Signature header, canonicalized >>>>>>>>>>>>>>>>>\n");
- pdkim_quoteprint(CUS sig_hdr, strlen(sig_hdr));
+ pdkim_quoteprint(CUS sig_hdr, Ustrlen(sig_hdr));
debug_printf(
"PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
}
/* Finalize header hash */
- exim_sha_update(&hhash_ctx, sig_hdr, strlen(sig_hdr));
+ exim_sha_update(&hhash_ctx, CUS sig_hdr, Ustrlen(sig_hdr));
exim_sha_finish(&hhash_ctx, &hhash);
DEBUG(D_acl)
{
- debug_printf("PDKIM [%s] hh computed: ", sig->domain);
+ debug_printf("PDKIM [%s] Header hash computed: ", sig->domain);
pdkim_hexprint(hhash.data, hhash.len);
}
/* Remember headers block for signing (when the library cannot do incremental) */
- if (ctx->mode == PDKIM_MODE_SIGN)
+ if (ctx->flags & PDKIM_MODE_SIGN)
(void) exim_rsa_data_append(&hdata, &hdata_alloc, US sig_hdr);
/* SIGNING ---------------------------------------------------------------- */
- if (ctx->mode == PDKIM_MODE_SIGN)
+ if (ctx->flags & PDKIM_MODE_SIGN)
{
es_ctx sctx;
const uschar * errstr;
{
ev_ctx vctx;
const uschar * errstr;
-
- uschar *dns_txt_name, *dns_txt_reply;
-
- /* Fetch public key for signing domain, from DNS */
-
- dns_txt_name = string_sprintf("%s._domainkey.%s.",
- sig->selector, sig->domain);
-
- dns_txt_reply = store_get(PDKIM_DNS_TXT_MAX_RECLEN);
- memset(dns_txt_reply, 0, PDKIM_DNS_TXT_MAX_RECLEN);
-
- if ( ctx->dns_txt_callback(CS dns_txt_name, CS dns_txt_reply) != PDKIM_OK
- || dns_txt_reply[0] == '\0')
+ pdkim_pubkey * p;
+
+ /* Make sure we have all required signature tags */
+ if (!( sig->domain && *sig->domain
+ && sig->selector && *sig->selector
+ && sig->headernames && *sig->headernames
+ && sig->bodyhash.data
+ && sig->sigdata.data
+ && sig->algo > -1
+ && sig->version
+ ) )
{
- sig->verify_status = PDKIM_VERIFY_INVALID;
- sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE;
- goto NEXT_VERIFY;
- }
+ sig->verify_status = PDKIM_VERIFY_INVALID;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_SIGNATURE_ERROR;
- DEBUG(D_acl)
- {
- debug_printf(
- "PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
- " Raw record: ");
- pdkim_quoteprint(CUS dns_txt_reply, Ustrlen(dns_txt_reply));
+ DEBUG(D_acl) debug_printf(
+ " Error in DKIM-Signature header: tags missing or invalid\n"
+ "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ goto NEXT_VERIFY;
}
- if (!(sig->pubkey = pdkim_parse_pubkey_record(ctx, dns_txt_reply)))
+ /* Make sure sig uses supported DKIM version (only v1) */
+ if (sig->version != 1)
{
- sig->verify_status = PDKIM_VERIFY_INVALID;
- sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD;
+ sig->verify_status = PDKIM_VERIFY_INVALID;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_DKIM_VERSION;
DEBUG(D_acl) debug_printf(
- " Error while parsing public key record\n"
- "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ " Error in DKIM-Signature header: unsupported DKIM version\n"
+ "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
goto NEXT_VERIFY;
}
- DEBUG(D_acl) debug_printf(
- "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-
- /* Import public key */
- if ((errstr = exim_rsa_verify_init(&sig->pubkey->key, &vctx)))
- {
- DEBUG(D_acl) debug_printf("verify_init: %s\n", errstr);
- sig->verify_status = PDKIM_VERIFY_INVALID;
- sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
+ if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx)))
goto NEXT_VERIFY;
- }
/* Check the signature */
if ((errstr = exim_rsa_verify(&vctx, is_sha1, &hhash, &sig->sigdata)))
}
- /* We have a winner! (if bodydhash was correct earlier) */
+ /* We have a winner! (if bodyhash was correct earlier) */
if (sig->verify_status == PDKIM_VERIFY_NONE)
sig->verify_status = PDKIM_VERIFY_PASS;
/* -------------------------------------------------------------------------- */
DLLEXPORT pdkim_ctx *
-pdkim_init_verify(int(*dns_txt_callback)(char *, char *))
+pdkim_init_verify(int(*dns_txt_callback)(char *, char *), BOOL dot_stuffing)
{
pdkim_ctx * ctx;
ctx = store_get(sizeof(pdkim_ctx));
memset(ctx, 0, sizeof(pdkim_ctx));
+if (dot_stuffing) ctx->flags = PDKIM_DOT_TERM;
ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN);
-ctx->mode = PDKIM_MODE_VERIFY;
ctx->dns_txt_callback = dns_txt_callback;
return ctx;
/* -------------------------------------------------------------------------- */
DLLEXPORT pdkim_ctx *
-pdkim_init_sign(char *domain, char *selector, char *rsa_privkey, int algo)
+pdkim_init_sign(char * domain, char * selector, char * rsa_privkey, int algo,
+ BOOL dot_stuffed, int(*dns_txt_callback)(char *, char *))
{
-pdkim_ctx *ctx;
-pdkim_signature *sig;
+pdkim_ctx * ctx;
+pdkim_signature * sig;
if (!domain || !selector || !rsa_privkey)
return NULL;
-ctx = store_get(sizeof(pdkim_ctx));
+ctx = store_get(sizeof(pdkim_ctx) + PDKIM_MAX_BODY_LINE_LEN + sizeof(pdkim_signature));
memset(ctx, 0, sizeof(pdkim_ctx));
-ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN);
+ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
+ctx->linebuf = CS (ctx+1);
-sig = store_get(sizeof(pdkim_signature));
+DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
+
+sig = (pdkim_signature *)(ctx->linebuf + PDKIM_MAX_BODY_LINE_LEN);
memset(sig, 0, sizeof(pdkim_signature));
sig->bodylength = -1;
-
-ctx->mode = PDKIM_MODE_SIGN;
ctx->sig = sig;
-sig->domain = string_copy(domain);
-sig->selector = string_copy(selector);
-sig->rsa_privkey = string_copy(rsa_privkey);
+sig->domain = string_copy(US domain);
+sig->selector = string_copy(US selector);
+sig->rsa_privkey = string_copy(US rsa_privkey);
sig->algo = algo;
-exim_sha_init(&sig->body_hash, algo == PDKIM_ALGO_RSA_SHA1);
+exim_sha_init(&sig->body_hash, algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256);
+
+DEBUG(D_acl)
+ {
+ pdkim_signature s = *sig;
+ ev_ctx vctx;
+
+ debug_printf("PDKIM (checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ if (!pdkim_key_from_dns(ctx, &s, &vctx))
+ debug_printf("WARNING: bad dkim key in dns\n");
+ debug_printf("PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }
+
return ctx;
}
pdkim_signature * sig = ctx->sig;
if (identity)
- sig->identity = string_copy(identity);
+ sig->identity = string_copy(US identity);
sig->sign_headers = string_copy(sign_headers
? US sign_headers : US PDKIM_DEFAULT_SIGN_HEADERS);