/* This file is part of Mailfromd. Copyright (C) 2020 Sergey Poznyakoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include "mailfromd.h" #include #include "dkim.h" /* * Read public and private rsa keys from file. * Matching code derived from pkcs1-conv.c in Nettle. */ static const uint8_t pem_start_pattern[] = "-----BEGIN "; static int pem_start_pattern_length = sizeof(pem_start_pattern) - 1; static const uint8_t pem_end_pattern[] = "-----END "; static int pem_end_pattern_length = sizeof(pem_end_pattern) - 1; static const uint8_t pem_trailer_pattern[] = "-----"; static int pem_trailer_pattern_length = sizeof(pem_trailer_pattern) - 1; enum { READ_PEM_OK, READ_PEM_ERROR, READ_PEM_EOF }; /* Returns READ_PEM_OK on match. */ static int match_pem_start(size_t length, const uint8_t *line, size_t *marker_start, size_t *marker_length) { while (length > 0 && mu_isspace(line[length - 1])) length--; if (length > (pem_start_pattern_length + pem_trailer_pattern_length) && memcmp(line, pem_start_pattern, pem_start_pattern_length) == 0 && memcmp(line + length - pem_trailer_pattern_length, pem_trailer_pattern, pem_trailer_pattern_length) == 0) { *marker_start = pem_start_pattern_length; *marker_length = length - (pem_start_pattern_length + pem_trailer_pattern_length); return READ_PEM_OK; } return READ_PEM_ERROR; } /* Returns READ_PEM_OK on match, READ_PEM_EOF if the line is of the right form except for the marker, otherwise READ_PEM_ERROR. */ static int match_pem_end(size_t length, const uint8_t *line, size_t marker_length, const uint8_t *marker) { while (length > 0 && mu_isspace(line[length - 1])) length--; if (length > (pem_end_pattern_length + pem_trailer_pattern_length) && memcmp(line, pem_end_pattern, pem_end_pattern_length) == 0 && memcmp(line + length - pem_trailer_pattern_length, pem_trailer_pattern, pem_trailer_pattern_length) == 0) { if (length == marker_length + (pem_end_pattern_length + pem_trailer_pattern_length) && memcmp(line + pem_end_pattern_length, marker, marker_length) == 0) return READ_PEM_OK; else return READ_PEM_EOF; } return READ_PEM_ERROR; } struct pem_info { size_t marker_start; size_t marker_length; size_t data_start; size_t data_length; }; /* Read a single line from file into buffer. */ static int read_line(FILE *fp, struct nettle_buffer *buffer) { int c; while ((c = getc(fp)) != EOF) { if (!NETTLE_BUFFER_PUTC(buffer, c)) return READ_PEM_ERROR; if (c == '\n') return READ_PEM_OK; } if (ferror(fp)) return READ_PEM_ERROR; return READ_PEM_EOF; } /* Read PEM file into buffer and parse it. Arguments: * * fp input file. * buffer buffer to read PEM into. * info fill this structure with information about PEM structure. */ static int read_pem(FILE *fp, struct nettle_buffer *buffer, struct pem_info *info) { int rc; /* Find start line */ for (;;) { nettle_buffer_reset(buffer); rc = read_line(fp, buffer); if (rc != READ_PEM_OK) return rc; if (match_pem_start(buffer->size, buffer->contents, &info->marker_start, &info->marker_length) == READ_PEM_OK) break; } buffer->contents[info->marker_start + info->marker_length] = 0; info->data_start = buffer->size; for (;;) { size_t line_start = buffer->size; if ((rc = read_line(fp, buffer)) != READ_PEM_OK) return rc; switch (match_pem_end(buffer->size - line_start, buffer->contents + line_start, info->marker_length, buffer->contents + info->marker_start)) { case READ_PEM_OK: info->data_length = line_start - info->data_start; return READ_PEM_OK; case READ_PEM_ERROR: break; case READ_PEM_EOF: return READ_PEM_EOF; } } return READ_PEM_ERROR; } static inline int base64_decode_in_place (struct base64_decode_ctx *ctx, size_t *dst_length, size_t length, uint8_t *data) { return base64_decode_update(ctx, dst_length, data, length, (const uint8_t *) data); } static int decode_base64(struct nettle_buffer *buffer, size_t start, size_t *length) { struct base64_decode_ctx ctx; base64_decode_init(&ctx); /* Decode in place */ if (base64_decode_in_place(&ctx, length, *length, buffer->contents + start) && base64_decode_final(&ctx)) return READ_PEM_OK; return READ_PEM_ERROR; } static int convert_rsa_private_key(uint8_t *buffer, size_t size, struct rsa_public_key *pub, struct rsa_private_key *priv) { rsa_public_key_init(pub); rsa_private_key_init(priv); return rsa_keypair_from_der(pub, priv, 0, size, buffer) ? READ_PEM_OK : READ_PEM_ERROR; } /* Read a private key PEM file and return RSA key pair. */ static int read_keys(FILE *fp, struct rsa_public_key *pub, struct rsa_private_key *priv) { struct nettle_buffer buffer; struct pem_info info; static char privkey_marker[] = "RSA PRIVATE KEY"; static size_t privkey_marker_length = sizeof(privkey_marker) - 1; int rc; nettle_buffer_init_realloc(&buffer, NULL, nettle_xrealloc);//FIXME rc = read_pem(fp, &buffer, &info); if (info.marker_length == privkey_marker_length && memcmp(buffer.contents + info.marker_start, privkey_marker, privkey_marker_length) == 0 && decode_base64(&buffer, info.data_start, &info.data_length) == READ_PEM_OK && convert_rsa_private_key(buffer.contents + info.data_start, info.data_length, pub, priv) == READ_PEM_OK) rc = READ_PEM_OK; else rc = READ_PEM_ERROR; nettle_buffer_clear(&buffer); return rc; } static int pubkey_from_base64(struct rsa_public_key *pub, const char *str) { struct nettle_buffer buffer; size_t length = strlen(str); struct asn1_der_iterator i, j; int result = READ_PEM_ERROR; nettle_buffer_init_realloc(&buffer, NULL, nettle_xrealloc); nettle_buffer_write(&buffer, strlen(str), (const uint8_t*) str); if (decode_base64(&buffer, 0, &length) == READ_PEM_OK /* SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING } AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER, parameters OPTIONAL } */ && asn1_der_iterator_first(&i, length, buffer.contents) == ASN1_ITERATOR_CONSTRUCTED && i.type == ASN1_SEQUENCE && asn1_der_decode_constructed_last(&i) == ASN1_ITERATOR_CONSTRUCTED && i.type == ASN1_SEQUENCE /* Use the j iterator to parse the algorithm identifier */ && asn1_der_decode_constructed(&i, &j) == ASN1_ITERATOR_PRIMITIVE && j.type == ASN1_IDENTIFIER && asn1_der_iterator_next(&i) == ASN1_ITERATOR_PRIMITIVE && i.type == ASN1_BITSTRING /* Use i to parse the object wrapped in the bit string.*/ && asn1_der_decode_bitstring_last(&i)) { /* pkcs-1 { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) modules(0) pkcs-1(1) } -- -- When rsaEncryption is used in an AlgorithmIdentifier the -- parameters MUST be present and MUST be NULL. -- rsaEncryption OBJECT IDENTIFIER ::= { pkcs-1 1 } */ static const uint8_t id_rsaEncryption[9] = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }; if (j.length == sizeof(id_rsaEncryption) && memcmp(j.data, id_rsaEncryption, sizeof(id_rsaEncryption)) == 0 && asn1_der_iterator_next(&j) == ASN1_ITERATOR_PRIMITIVE && j.type == ASN1_NULL && j.length == 0 && asn1_der_iterator_next(&j) == ASN1_ITERATOR_END) { rsa_public_key_init(pub); if (rsa_public_key_from_der_iterator(pub, 0, &i)) result = READ_PEM_OK; } } nettle_buffer_clear(&buffer); return result; } /* State of iteratiom over a colon-separated list of headers. */ struct h_list_buf { char const *ptr; /* Current position in the header list. */ char *base; /* Return memory pointer. */ size_t size; /* Size of memory allocated for base. */ }; /* * dkim_header_list_next(SAVE) * --------------------------- * Free the state buffer allocated by dkim_header_list_first. */ void dkim_header_list_end(void *save) { struct h_list_buf *hbuf = save; free(hbuf->base); free(hbuf); } /* * Return next header from the header list, or NULL if the list is exhausted. * SAVE is the iteration state pointer returned by dkim_header_list_first. */ static char * dkim_header_list_next(void *save) { struct h_list_buf *hbuf = save; char const *hp = hbuf->ptr; while (*hp && (*hp == ' ' || *hp == '\t' || *hp == ':')) hp++; if (*hp) { size_t len = strcspn(hp, " \t:"); if (len + 1 > hbuf->size) { hbuf->base = mu_realloc(hbuf->base, len + 1); hbuf->size = len + 1; } memcpy(hbuf->base, hp, len); hbuf->base[len] = 0; hbuf->ptr = hp + len; } else { hbuf->ptr = hp; return NULL; } return hbuf->base; } /* * Start iteration over a list of header names (H_LIST), delimited by * colons with optional whitespace around them. Return first header * name and save the state in the memory location pointed to by SAVE. * No matter the return value, dkim_header_list_end must be called to * reclaim the allocated memory. */ char * dkim_header_list_first(char const *h_list, void *save) { struct h_list_buf *hbuf, **hbuf_ptr = save; hbuf = mu_alloc(sizeof(hbuf[0])); hbuf->ptr = h_list; hbuf->base = NULL; hbuf->size = 0; *hbuf_ptr = hbuf; return dkim_header_list_next(hbuf); } int dkim_header_list_match(char const *h_list, char const *h) { size_t len = strlen (h); while (*h_list) { size_t n; while (*h_list && (*h_list == ' ' || *h_list == '\t')) h_list++; if (*h_list == 0) break; n = strcspn(h_list, " \t:"); if (n == len && mu_c_strncasecmp (h_list, h, len) == 0) return 1; h_list += n; while (*h_list && (*h_list == ' ' || *h_list == '\t')) h_list++; if (*h_list != ':') break; ++h_list; } return 0; } /* * SHA256 hashing functions. */ #define BUF_SIZE 1024 /* Hash the contents read from the mailutils stream STR starting from * the current position and up to the end of file. */ static int hash_stream(mu_stream_t str, struct sha256_ctx *ctx) { uint8_t buffer[BUF_SIZE]; size_t count; int rc; while ((rc = mu_stream_read(str, buffer, sizeof(buffer), &count)) == 0 && count > 0) { // mu_error("READ %*.*s", (int)count, (int)count, buffer); sha256_update(ctx, count, buffer); } if (rc) { mu_diag_funcall(MU_DIAG_ERROR, "mu_stream_read", NULL, rc); return -1; } return 0; } /* Hash LEN bytes from the current position in stream STR. */ static int hash_stream_segment(mu_stream_t str, size_t len, struct sha256_ctx *ctx) { while (len) { uint8_t buffer[BUF_SIZE]; int rc; size_t n = sizeof(buffer), count; if (n > len) n = len; rc = mu_stream_read(str, buffer, n, &count); //printf("HASH %*.*s\n",(int)count,(int)count,buffer); if (rc) { mu_diag_funcall(MU_DIAG_ERROR, "mu_stream_read", NULL, rc); return -1; } if (count == 0) { mu_error(_("unexpected end of file in canonical stream")); return -1; } sha256_update(ctx, count, buffer); len -= count; } return 0; } /* Hash the remaining content of the stream from the current position * and store a nul-terminated base64 encoded result in outbuf. * Outbuf must have at least BASE64_ENCODE_RAW_LENGTH(SHA256_DIGEST_SIZE) + 1 * bytes of capacity, */ static int dkim_body_hash(mu_stream_t str, size_t len, uint8_t *outbuf) { struct sha256_ctx ctx; uint8_t body_digest[SHA256_DIGEST_SIZE]; sha256_init(&ctx); if (len == DKIM_LENGTH_ALL) hash_stream(str, &ctx); else hash_stream_segment(str, len, &ctx); sha256_digest(&ctx, sizeof(body_digest), body_digest); base64_encode_raw(outbuf, sizeof(body_digest), body_digest); return 0; } /* * RSA SHA256 digest. */ /* Given the RSA private key and a pointer to SHA256 context, create the * RSA digest in base64. Return value is stored in RET_B64 (malloced). */ int dkim_rsa_sha256_sign(struct rsa_private_key *priv, struct sha256_ctx *ctx, uint8_t **ret_b64) { int rc; mpz_t sig; size_t i; struct nettle_buffer buffer; uint8_t *outbuf; mpz_init(sig); rc = rsa_sha256_sign(priv, ctx, sig); if (!rc) return -1; nettle_buffer_init_realloc(&buffer, NULL, nettle_xrealloc);//FIXME for (i = mpz_size(sig); i > 0; i--) { mp_limb_t limb = mpz_getlimbn(sig, i - 1); size_t j; uint8_t *p = nettle_buffer_space(&buffer, sizeof(mp_limb_t)) + sizeof(mp_limb_t) - 1; for (j = 0; j < sizeof(mp_limb_t); j++) { *p-- = limb & 0xff; limb >>= 8; } } mpz_clear(sig); outbuf = malloc(BASE64_ENCODE_RAW_LENGTH(buffer.size) + 1); if (!outbuf) { nettle_buffer_clear(&buffer); return -1; } base64_encode_raw(outbuf, buffer.size, buffer.contents); outbuf[BASE64_ENCODE_RAW_LENGTH(buffer.size)] = 0; nettle_buffer_clear(&buffer); *ret_b64 = outbuf; return 0; } /* * Canonicalization names * re. "DKIM-Signature Canonicalization Header" IANA registry. */ static char const *dkim_canon_string[] = { "simple", "relaxed", NULL }; #define DKIM_CANON_STRING_MAX (sizeof(dkim_canon_string[DKIM_CANON_RELAXED])-1) /* * Convert STR to a DKIM_CANON_* constant (return DKIM_CANON_ERR on error). * If ENDP is NULL, STR must be one of the strings from dkim_canon_string. * Otherwise, STR may be followed by '\0' or '/'. The pointer to that * character will be returned in the memory location pointed to by ENDP. */ int dkim_str_to_canon_type(char const *str, char **endp) { int i; size_t len = strcspn(str, "/"); if (endp || str[len] == 0) { for (i = 0; dkim_canon_string[i]; i++) if (len == strlen(dkim_canon_string[i]) && memcmp(str, dkim_canon_string[i], len) == 0) { if (endp) *endp = (char*)(str + len); return i; } } return DKIM_CANON_ERR; } /* Format a struct dkim_signature as a DKIM-Signature header. */ #define MAX_LINE_LEN 78 struct dkim_format_buf { mu_stream_t str; mu_stream_stat_buffer stat; int crlf; }; static inline void dkim_format_nl(struct dkim_format_buf *fb) { if (fb->crlf) mu_stream_write(fb->str, "\r\n ", 3, NULL); else mu_stream_write(fb->str, "\n ", 2, NULL); fb->stat[MU_STREAM_STAT_OUT] = 0; } static inline int dkim_format_wrap(struct dkim_format_buf *fb, size_t len) { if (fb->stat[MU_STREAM_STAT_OUT] && fb->stat[MU_STREAM_STAT_OUT] + len > MAX_LINE_LEN) { dkim_format_nl(fb); return 1; } return 0; } static void dkim_format_tag(struct dkim_format_buf *fb, char const *tag, char const *val) { if (!val) return; if (!dkim_format_wrap(fb, strlen(tag) + strlen(val) + 2)) dkim_format_wrap(fb, strlen(tag) + 1); mu_stream_write(fb->str, tag, strlen(tag), NULL); mu_stream_write(fb->str, "=", 1, NULL); dkim_format_wrap(fb, strlen(val) + 1); mu_stream_write(fb->str, val, strlen(val), NULL); mu_stream_write(fb->str, ";", 1, NULL); } static void dkim_format_tag_base64(struct dkim_format_buf *fb, char const *tag, char const *val) { size_t len = strlen(val); if (!dkim_format_wrap(fb, strlen(tag) + strlen(val) + 2)) dkim_format_wrap(fb, strlen(tag) + 1); mu_stream_write(fb->str, tag, strlen(tag), NULL); mu_stream_write(fb->str, "=", 1, NULL); while (len > 0) { size_t n = MAX_LINE_LEN - fb->stat[MU_STREAM_STAT_OUT]; if (len < n) n = len; mu_stream_write(fb->str, val, n, NULL); dkim_format_wrap(fb, 1); val += n; len -= n; } mu_stream_write(fb->str, ";", 1, NULL); } void dkim_signature_free(struct dkim_signature *sig) { free(sig->a); free(sig->b); free(sig->bh); free(sig->d); free(sig->s); free(sig->h); free(sig->i); free(sig->q); free(sig->v); } /* * Auxiliary functions for parsing and formatting dkim_signature fields. * * Each parser is declared as * int P(char const *value, void *data) * Its arguments are: * value - actual tag value obtained from the header. * data - pointer to the member of struct dkim_signature. * The parser returns 0 on success and non-zero on error. It is not supposed * to emit any diagnostic messages. * * Each formatter is declared as * int F(struct dkim_format_buf *fb, char const *tag, void *data) * Its arguments are: * fb - formatting buffer, * tag - the tag name, * value - pointer to the member of struct dkim_signature. */ /* General purpose parser for char* (or uint8_t*) fields */ static int dkim_tag_char_parser(char const *value, void *data) { char **cptr = data; *cptr = mu_strdup(value); return 0; } /* General purpose formatter for char* (or uint8_t*) fields */ static void dkim_tag_char_formatter(struct dkim_format_buf *fb, char const *tag, void *data) { char **cptr = data; dkim_format_tag(fb, tag, *cptr); } /* Formatter for the a= tag */ static void dkim_tag_a_formatter(struct dkim_format_buf *fb, char const *tag, void *data) { char **cptr = data; dkim_format_tag(fb, tag, *cptr ? *cptr : DKIM_ALGORITHM); } /* Formatter for the q= tag */ static void dkim_tag_q_formatter(struct dkim_format_buf *fb, char const *tag, void *data) { char **cptr = data; dkim_format_tag(fb, tag, *cptr ? *cptr : DKIM_QUERY_METHOD); } /* Special formatter for the h= tag, that ensures proper wrapping. */ static void dkim_tag_h_formatter(struct dkim_format_buf *fb, char const *tag, void *data) { char **cptr = data; void *save; char const *hval; if (!dkim_format_wrap(fb, 2)) dkim_format_wrap(fb, strlen(*cptr) + 3); mu_stream_write(fb->str, "h=", 2, NULL); if ((hval = dkim_header_list_first(*cptr, &save)) != NULL) { dkim_format_wrap(fb, strlen(hval)); mu_stream_write(fb->str, hval, strlen(hval), NULL); while ((hval = dkim_header_list_next(save)) != NULL) { dkim_format_wrap(fb, strlen(hval) + 1); mu_stream_write(fb->str, ":", 1, NULL); mu_stream_write(fb->str, hval, strlen(hval), NULL); } } dkim_header_list_end(save); dkim_format_wrap(fb, 1); mu_stream_write(fb->str, ";", 1, NULL); } /* Parser and formatter for the c= tag. */ static int dkim_tag_c_parser(char const *value, void *data) { int *canon = data; char *s; if ((canon[0] = dkim_str_to_canon_type(value, &s)) == DKIM_CANON_ERR) return -1; if (*s == 0) canon[1] = canon[0]; else if (*s != '/' || (canon[1] = dkim_str_to_canon_type(s + 1, NULL)) == DKIM_CANON_ERR) return -1; return 0; } static void dkim_tag_c_formatter(struct dkim_format_buf *fb, char const *tag, void *data) { int *canon = data; char v[(DKIM_CANON_STRING_MAX+1)*2]; strcpy(v, dkim_canon_string[canon[0]]); strcat(v, "/"); strcat(v, dkim_canon_string[canon[1]]); dkim_format_tag(fb, tag, v); } /* General-purpose parser and formatter for time_t members (t and x) */ static int dkim_tag_time_parser(char const *value, void *data) { time_t *tptr = data; unsigned long n; char *p; errno = 0; n = strtoul(value, &p, 10); if (errno || *p) return -1; *tptr = n; return 0; } static void dkim_tag_time_formatter(struct dkim_format_buf *fb, char const *tag, void *data) { time_t *tptr = data; if (*tptr) { char tbuf[80]; snprintf(tbuf, sizeof(tbuf), "%lu", (long unsigned) *tptr); dkim_format_tag(fb, tag, tbuf); } } /* Parser and formatter for the l= tag */ static int dkim_tag_l_parser(char const *value, void *data) { size_t *sptr = data; unsigned long n; char *endp; errno = 0; n = strtoul(value, &endp, 10); if (errno || *endp) return -1; *sptr = n; return 0; } static void dkim_tag_l_formatter(struct dkim_format_buf *fb, char const *tag, void *data) { size_t *sptr = data; char tbuf[80]; if (*sptr != DKIM_LENGTH_ALL) { snprintf(tbuf, sizeof(tbuf), "%zu", *sptr); dkim_format_tag(fb, tag, tbuf); } } /* Formatter for the bh= tag. */ static void dkim_tag_bh_formatter(struct dkim_format_buf *fb, char const *tag, void *data) { char **sptr = data; dkim_format_tag_base64(fb, tag, *sptr); } /* Formatter for the b= tag. */ static void dkim_tag_b_formatter(struct dkim_format_buf *fb, char const *tag, void *data) { char **sptr = data; dkim_format_nl(fb); dkim_format_tag_base64(fb, tag, *sptr ? *sptr : ""); } /* Tag definition structure */ struct dkim_tag_descr { char *tag; /* Tag name. */ size_t off; /* Field offset in struct dkim_signature. */ int (*parser)(char const *, void *); /* Parser function. */ void (*formatter)(struct dkim_format_buf *, char const *, void *); /* Formatter function. */ }; /* Order of entries in this array defines the order in which tags are formatted. */ static struct dkim_tag_descr tag_descr[] = { { "v", offsetof(struct dkim_signature, v), dkim_tag_char_parser, dkim_tag_char_formatter, }, { "a", offsetof(struct dkim_signature, a), dkim_tag_char_parser, dkim_tag_a_formatter }, { "d", offsetof(struct dkim_signature, d), dkim_tag_char_parser, dkim_tag_char_formatter, }, { "s", offsetof(struct dkim_signature, s), dkim_tag_char_parser, dkim_tag_char_formatter, }, { "c", offsetof(struct dkim_signature, canon), dkim_tag_c_parser, dkim_tag_c_formatter }, { "q", offsetof(struct dkim_signature, q), dkim_tag_char_parser, dkim_tag_q_formatter }, { "h", offsetof(struct dkim_signature, h), dkim_tag_char_parser, dkim_tag_h_formatter, }, { "i", offsetof(struct dkim_signature, i), dkim_tag_char_parser, dkim_tag_char_formatter, }, { "l", offsetof(struct dkim_signature, l), dkim_tag_l_parser, dkim_tag_l_formatter }, { "t", offsetof(struct dkim_signature, t), dkim_tag_time_parser, dkim_tag_time_formatter }, { "x", offsetof(struct dkim_signature, x), dkim_tag_time_parser, dkim_tag_time_formatter }, { "bh", offsetof(struct dkim_signature, bh), dkim_tag_char_parser, dkim_tag_bh_formatter }, { "b", offsetof(struct dkim_signature, b), dkim_tag_char_parser, dkim_tag_b_formatter }, { NULL } }; /* Table-driven DKIM-Signature parser. */ int dkim_signature_parse(char *str, struct dkim_signature *ret_sig) { struct mu_wordsplit ws; int i; int rc = 0; struct dkim_signature sig; /* * tag-list = tag-spec *( ";" tag-spec ) [ ";" ] * tag-spec = [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] * * The mu_wordsplit call splits the value on ';' and removes * whitespace at both sides of each token. Internal FWS (around * the equals sign) is removed later. */ ws.ws_delim = ";"; rc = mu_wordsplit(str, &ws, MU_WRDSF_DELIM | MU_WRDSF_NOVAR | MU_WRDSF_WS | MU_WRDSF_NOCMD); if (rc) { mu_wordsplit_free(&ws); return rc; } memset(&sig, 0, sizeof(sig)); sig.canon[0] = sig.canon[1] = DKIM_CANON_SIMPLE; sig.l = DKIM_LENGTH_ALL; for (i = 0; i < ws.ws_wordc; i++) { struct dkim_tag_descr *tg; char *k = ws.ws_wordv[i]; char *p = strchr(k, '='); if (!p) { rc = -1; goto end; } *p++ = 0; /* Remove internal FWS */ mu_rtrim_class(k, MU_CTYPE_BLANK); mu_ltrim_class(p, MU_CTYPE_BLANK); /* Handle known tags */ for (tg = tag_descr; tg->tag; tg++) { if (strcmp(k, tg->tag) == 0) { rc = tg->parser(p, (char*)&sig + tg->off); if (rc) goto end; break; } } } end: mu_wordsplit_free(&ws); if (rc) dkim_signature_free(&sig); else *ret_sig = sig; return rc; } /* * Table-driven DKIM-Signature formatter. * The CRLF parameter defines what delimiter to use for wrapping. */ int dkim_signature_format(struct dkim_signature *sig, int crlf, char **result) { struct dkim_format_buf fb; int rc; static char header_field[] = DKIM_SIGNATURE_HEADER ": "; mu_off_t off; size_t size; char *text; struct dkim_tag_descr *tg; /* Initialize format buffer */ fb.crlf = crlf; rc = mu_memory_stream_create(&fb.str, MU_STREAM_RDWR); if (rc) { mu_diag_funcall(MU_DIAG_ERROR, "mu_memory_stream_create", NULL, rc); return rc; } mu_stream_set_stat(fb.str, MU_STREAM_STAT_MASK (MU_STREAM_STAT_OUT), fb.stat); mu_stream_write(fb.str, header_field, strlen(header_field), NULL); for (tg = tag_descr; tg->tag; tg++) { tg->formatter(&fb, tg->tag, ((char*)sig + tg->off)); } if (mu_stream_err(fb.str)) { rc = mu_stream_last_error(fb.str); mu_diag_funcall(MU_DIAG_ERROR, "dkim_signature_format", NULL, rc); } else { rc = 0; mu_stream_seek(fb.str, 0, MU_SEEK_CUR, &off); //FIXME: assert(size < (size_t)~0) size = off; text = mu_alloc(size + 1); mu_stream_seek(fb.str, 0, MU_SEEK_SET, NULL); mu_stream_read(fb.str, text, size, NULL); text[size] = 0; *result = text; } mu_stream_destroy(&fb.str); return rc; } /* Canonicalize the message into a stream. * Arguments: * * msg input message. * canon canonicalization algorithms for header and body. * canon_str output stream. */ int canonicalize(mu_message_t msg, int canon[2], mu_stream_t *canon_str) { mu_stream_t mstr, flt, in; int rc; rc = mu_temp_file_stream_create(&mstr, NULL, 0); if (rc) { mu_diag_funcall(MU_DIAG_ERROR, "mu_temp_file_stream_create", NULL, rc); return -1; } rc = mu_message_get_streamref(msg, &in); if (rc) { mu_diag_funcall(MU_DIAG_ERROR, "mu_message_get_streamref", NULL, rc); mu_stream_destroy(&mstr); return -1; } rc = dkim_canonicalizer_create(&flt, in, canon[0], canon[1], MU_STREAM_READ); mu_stream_unref(in); if (rc) { mu_error("dkim_canonicalizer_create: %s", mu_strerror(rc)); mu_stream_destroy(&mstr); return -1; } in = flt; rc = mu_filter_create(&flt, in, "CRLF", MU_FILTER_ENCODE, MU_STREAM_READ); mu_stream_unref(in); if (rc) { mu_error("mu_filter_stream_create: %s", mu_strerror(rc)); mu_stream_destroy(&mstr); return -1; } rc = mu_stream_copy(mstr, flt, 0, NULL); mu_stream_unref(flt); if (rc) { mu_error("mu_stream_copy: %s", mu_strerror(rc)); mu_stream_destroy(&mstr); return -1; } mu_stream_seek(mstr, 0, MU_SEEK_SET, NULL); *canon_str = mstr; return 0; } /* * Header maps. * * Message headers form a doubly-linked list of struct header_map * pointers. The list itself is referenced by its head structure, * which is a regular header_map where only prev and next pointers * are valid. In a header, next points to the first and prev to the * last element in the list. */ struct header_map { struct header_map *prev, *next; /* List of elements */ char *header; /* Header name */ mu_off_t start; /* Header start offset */ mu_off_t end; /* Header end offset (points past the final CRLF) */ }; /* Header_map is initialized using this macro. */ #define HEADER_MAP_INITIALIZER(h) { &(h), &(h) } /* Remove the header_map from the list */ static inline void header_map_remove(struct header_map *hp) { hp->prev->next = hp->next; hp->next->prev = hp->prev; } /* Insert header_map B to the list after A. */ static inline void header_map_insert_after(struct header_map *a, struct header_map *b) { a->next->prev = b; b->next = a->next; a->next = b; b->prev = a; } /* Append header_map to the end of the list. */ static inline void header_map_append(struct header_map *link, struct header_map *hp) { header_map_insert_after(link->prev, hp); } /* Free the list. List head is not freed. */ static inline void header_map_free(struct header_map *link) { struct header_map *hmap = link->next; while (hmap != link) { header_map_remove(hmap); free(hmap); hmap = link->next; } } /* * Two iterators. * * Arguments: * link the head element of the list. * h iteration variable */ /* Iterate over the list in natural direction. */ #define HEADER_MAP_FOREACH(h,link) \ for (h = (link)->next; h != (link); h = (h)->next) /* Iterate over the list in reverse direction. */ #define HEADER_MAP_FOREACH_REV(h,link) \ for (h = (link)->prev; h != (link); h = (h)->prev) /* Return true if a message can have multiple instances of the named header. * * RFC 6376 mandates that such headers be processed in reverse order. * Refer to subsection 5.4.2. "Signatures Involving Multiple Instances of * a Field" on page 41 for details. */ static int is_rev_header(char const *name) { static char *rev_headers[] = { "Received", DKIM_SIGNATURE_HEADER, "Resent-*", NULL }; int i; for (i = 0; rev_headers[i]; i++) { if (mu_imap_wildmatch_ci(rev_headers[i], name, ':') == 0) return 1; } return 0; } static int dkim_tag_find(char const *sigstr, char const *tag, size_t *ret_len) { int i; size_t tag_len = strlen(tag); size_t sig_len = strlen(sigstr); for (i = 0; i + tag_len + 1 < sig_len; i++) { if (!(sigstr[i] == ' ' || sigstr[i] == '\t' || sigstr[i] == '\r' || sigstr[i] == '\n')) { size_t n = strcspn(sigstr + i, ";"); if (memcmp(sigstr + i, tag, tag_len) == 0 && sigstr[i + tag_len] == '=') { *ret_len = n - tag_len - 1; return i; } i += n; } } return -1; } enum { DKIM_HASH_ERR = -1, DKIM_HASH_OK, DKIM_HASH_DIFF }; #define BH_SIZE (BASE64_ENCODE_RAW_LENGTH(SHA256_DIGEST_SIZE)) /* * dkim_hash(MSG, SIG, SIGSTR, CTX) * -------------------------------- * Compute a message hash of MSG as per RFC 6376 section 3.7. * * Parameters: * MSG - (input) message * SIG - (input/output) parsed out DKIM signature * SIGSTR - (input) original value of the DKIM signature header. * CTX - (output) SHA256 context to leave the hash in. * * The function is used both for message signing and verification. * * When signing, SIGSTR is NULL. Before return, the malloced copy * of the computed body hash is left in SIG->bh. The caller is * responsible for freeing it when no longer needed. In this mode, * the function returns DKIM_HASH_OK on success and DKIM_HASH_ERR * on error. * * When verifying, SIGSTR is not NULL and SIG is the validated broken * out DKIM signature obtained from SIGSTR. In this case, the function * does not modify SIG in any way. Instead, it checks whether the computed * body hash matches SIG->bh and returns DKIM_HASH_DIFF if it does not. */ static int dkim_hash(mu_message_t msg, struct dkim_signature *sig, char const *sigstr, struct sha256_ctx *ctx) { mu_stream_t canon_stream = NULL; char *hp; void *hstate; struct header_map h_all = HEADER_MAP_INITIALIZER(h_all); struct header_map h_sel = HEADER_MAP_INITIALIZER(h_sel); struct header_map *hmap; uint8_t bh[BH_SIZE]; mu_opool_t op = NULL; char c; int rc; int result = DKIM_HASH_ERR; size_t count; enum { H_INIT, H_HEADER, H_CR1, H_CR2, H_NL } state = H_INIT; mu_stream_t sigcanon, str; char *sig_str_buf; /* Create a canonical representation of the message */ if (canonicalize(msg, sig->canon, &canon_stream)) goto err; /* * Scan the header part of the canonicalized stream and record * the headers in the h_all list. * * Header names its elements refer to are stored in the object * pool. */ mu_opool_create(&op, MU_OPOOL_DEFAULT); hmap = NULL; while ((rc = mu_stream_read(canon_stream, &c, 1, &count)) == 0 && count == 1) { switch (state) { case H_INIT: if (c == ':') { c = 0; mu_opool_append(op, &c, 1); hmap = calloc(1, sizeof(hmap[0])); hmap->header = mu_opool_finish(op, NULL); mu_strlower(hmap->header); mu_stream_seek(canon_stream, 0, MU_SEEK_CUR, &hmap->start); hmap->start -= strlen(hmap->header) + 1; header_map_append(&h_all, hmap); state = H_HEADER; } else mu_opool_append(op, &c, 1); break; case H_HEADER: if (c == '\r') state = H_CR1; break; case H_CR1: if (c == '\n') state = H_NL; break; case H_NL: if (mu_isblank(c)) state = H_HEADER; else { mu_stream_seek(canon_stream, 0, MU_SEEK_CUR, &hmap->end); hmap->end--; if (c == '\r') state = H_CR2; else { state = H_INIT; mu_opool_append(op, &c, 1); } } break; case H_CR2: if (c != '\n') { goto badstream; } goto end; } } /* Error exit from the above loop */ if (rc) mu_diag_funcall(MU_DIAG_ERROR, "mu_stream_read", NULL, rc); badstream: mu_error(_("malformed canonical stream")); goto err; end: /* * Scanning terminated successfully. Current position in the * canon_stream is left at the start of the body, which will come * handy later. * * Select headers to hash according to the h= tag. Selected headers * are removed from the h_all and added to the h_sel list. */ for (hp = dkim_header_list_first(sig->h, &hstate); hp; hp = dkim_header_list_next(hstate)) { if (is_rev_header(hp)) { HEADER_MAP_FOREACH_REV(hmap, &h_all) { if (mu_c_strcasecmp(hmap->header, hp) == 0) { header_map_remove(hmap); header_map_append(&h_sel, hmap); break; } } } else { HEADER_MAP_FOREACH(hmap, &h_all) { if (mu_c_strcasecmp(hmap->header, hp) == 0) { header_map_remove(hmap); header_map_append(&h_sel, hmap); break; } } } } dkim_header_list_end(hstate); /* Hash the body */ if (dkim_body_hash(canon_stream, sig->l, bh)) goto err; if (sig->bh) { if (memcmp(sig->bh, bh, BH_SIZE)) { result = DKIM_HASH_DIFF; goto err; } } else { sig->bh = malloc(BH_SIZE + 1); if (!sig->bh) goto err; memcpy(sig->bh, bh, BH_SIZE); sig->bh[BH_SIZE] = 0; } /* Hash the selected headers */ HEADER_MAP_FOREACH(hmap, &h_sel) { mu_stream_seek(canon_stream, hmap->start, MU_SEEK_SET, NULL); hash_stream_segment(canon_stream, hmap->end - hmap->start, ctx); } /* Add to the hash the DKIM-Signature header with empty b= tag. */ if (sigstr) { size_t len, blen; char *vp; int n = dkim_tag_find(sigstr, "b", &blen); if (n == -1) goto err; len = strlen(sigstr); sig_str_buf = malloc(sizeof(DKIM_SIGNATURE_HEADER) + 1 + len - blen + 1); if (!sig_str_buf) goto err; strcpy(sig_str_buf, DKIM_SIGNATURE_HEADER ": "); vp = sig_str_buf + sizeof(DKIM_SIGNATURE_HEADER) + 1; memcpy(vp, sigstr, n); memcpy(vp + n, "b=", 2); strcpy(vp + n + 2, sigstr + n + 2 + blen); } else { dkim_signature_format(sig, 0, &sig_str_buf); } mu_fixed_memory_stream_create(&str, sig_str_buf, strlen(sig_str_buf), MU_STREAM_RDWR|MU_STREAM_SEEK); rc = dkim_canonicalizer_create(&sigcanon, str, sig->canon[0], sig->canon[1], MU_STREAM_READ); mu_stream_unref(str); if (rc) { mu_error("dkim_canonicalizer_create: %s", mu_strerror(rc)); goto err; } rc = mu_filter_create(&str, sigcanon, "CRLF", MU_FILTER_ENCODE, MU_STREAM_READ); mu_stream_unref(sigcanon); if (rc) { mu_diag_funcall(MU_DIAG_ERROR, "mu_filter_stream_create", NULL, c); goto err; } sigcanon = str; hash_stream(sigcanon, ctx); mu_stream_unref(sigcanon); free(sig_str_buf); result = DKIM_HASH_OK; err: /* Reclaim the allocated memory. */ mu_opool_destroy(&op); header_map_free(&h_all); header_map_free(&h_sel); mu_stream_destroy(&canon_stream); return result; } /* Sign the message. Arguments: * * msg message to sign. * sig initialized struct dkim_signature. * priv_file name of a disk file with the RSA private key in PEM format. * ret_sighdr return pointer. * * On success, a malloced copy of DKIM-Signature header line will be stored * in ret_sighdr and 0 will be returned. * * Side effects: sig->bh is filled with SHA256 digest of the message body. */ int mfd_dkim_sign(mu_message_t msg, struct dkim_signature *sig, char *priv_file, char **ret_sighdr) { int rc; struct rsa_private_key priv; struct rsa_public_key pub; FILE *fp; struct sha256_ctx ctx; int result = -1; fp = fopen(priv_file, "r"); if (!fp) { mu_error(_("can't open %s: %s"), priv_file, strerror(errno)); return -1; } rc = read_keys(fp, &pub, &priv); fclose(fp); if (rc != READ_PEM_OK) { mu_error(_("can't read private key from %s: %s"), priv_file, strerror(errno)); return -1; } rsa_public_key_clear(&pub); sha256_init(&ctx); if (dkim_hash(msg, sig, NULL, &ctx) == DKIM_HASH_OK) { /* Create the RSA-SHA256 signature in b */ dkim_rsa_sha256_sign(&priv, &ctx, &sig->b); /* Create the header */ dkim_signature_format(sig, 1, ret_sighdr); result = 0; } /* Reclaim the allocated memory. */ free(sig->b); sig->b = NULL; free(sig->bh); sig->bh = NULL; rsa_private_key_clear(&priv); return result; } char const *dkim_explanation_str[] = { [DKIM_EXPL_OK] = "DKIM verification passed", [DKIM_EXPL_NO_SIG] = "No DKIM signature", [DKIM_EXPL_INTERNAL_ERROR] = "internal error", [DKIM_EXPL_SIG_SYNTAX] = "signature syntax error", [DKIM_EXPL_SIG_MISS] = "signature is missing required tag", [DKIM_EXPL_DOMAIN_MISMATCH] = "domain mismatch", [DKIM_EXPL_BAD_VERSION] = "incompatible version", [DKIM_EXPL_BAD_ALGORITHM] = "unsupported algorithm", [DKIM_EXPL_BAD_QUERY] = "unsupported query method", [DKIM_EXPL_FROM] = "From field not signed", [DKIM_EXPL_EXPIRED] = "signature expired", [DKIM_EXPL_DNS_UNAVAIL] = "public key unavailable", [DKIM_EXPL_DNS_NOTFOUND] = "public key not found", [DKIM_EXPL_KEY_SYNTAX] = "key syntax error", [DKIM_EXPL_KEY_REVOKED] = "key revoked", [DKIM_EXPL_BAD_BODY] = "body hash did not verify", [DKIM_EXPL_BAD_BASE64] = "can't decode b= tag", [DKIM_EXPL_BAD_SIG] = "signature did not verify", }; int dkim_result_trans[] = { [DKIM_EXPL_OK] = DKIM_VERIFY_OK, [DKIM_EXPL_BAD_ALGORITHM] = DKIM_VERIFY_PERMFAIL, [DKIM_EXPL_BAD_BASE64] = DKIM_VERIFY_PERMFAIL, [DKIM_EXPL_BAD_BODY] = DKIM_VERIFY_PERMFAIL, [DKIM_EXPL_BAD_SIG] = DKIM_VERIFY_PERMFAIL, [DKIM_EXPL_BAD_QUERY] = DKIM_VERIFY_PERMFAIL, [DKIM_EXPL_BAD_VERSION] = DKIM_VERIFY_PERMFAIL, [DKIM_EXPL_DNS_NOTFOUND] = DKIM_VERIFY_PERMFAIL, [DKIM_EXPL_DOMAIN_MISMATCH] = DKIM_VERIFY_PERMFAIL, [DKIM_EXPL_EXPIRED] = DKIM_VERIFY_PERMFAIL, [DKIM_EXPL_FROM] = DKIM_VERIFY_PERMFAIL, [DKIM_EXPL_KEY_REVOKED] = DKIM_VERIFY_PERMFAIL, [DKIM_EXPL_KEY_SYNTAX] = DKIM_VERIFY_PERMFAIL, [DKIM_EXPL_SIG_MISS] = DKIM_VERIFY_PERMFAIL, [DKIM_EXPL_SIG_SYNTAX] = DKIM_VERIFY_PERMFAIL, [DKIM_EXPL_NO_SIG] = DKIM_VERIFY_TEMPFAIL, [DKIM_EXPL_DNS_UNAVAIL] = DKIM_VERIFY_TEMPFAIL, [DKIM_EXPL_INTERNAL_ERROR] = DKIM_VERIFY_TEMPFAIL, }; static int dkim_sig_validate(struct dkim_signature *sig) { if (!sig->a || !sig->b || !sig->bh || !sig->d || !sig->h || !sig->s || !sig->v) { return DKIM_EXPL_SIG_MISS; } if (strcmp(sig->v, DKIM_VERSION)) return DKIM_EXPL_BAD_VERSION; if (strcmp(sig->a, DKIM_ALGORITHM)) return DKIM_EXPL_BAD_ALGORITHM; if (!sig->q) sig->q = mu_strdup(DKIM_QUERY_METHOD); else if (strcmp(sig->q, DKIM_QUERY_METHOD)) return DKIM_EXPL_BAD_QUERY; if (sig->i) { char *p = strchr(sig->i, '@'); size_t ilen, dlen; if (!p) return DKIM_EXPL_SIG_SYNTAX; p++; ilen = strlen(p); dlen = strlen(sig->d); if (!(dlen <= ilen && mu_c_strcasecmp(sig->d, p + ilen - dlen) == 0 && (p[ilen - dlen - 1] == '.' || p[ilen - dlen - 1] == '@'))) return DKIM_EXPL_DOMAIN_MISMATCH; } if (!dkim_header_list_match(sig->h, MU_HEADER_FROM)) return DKIM_EXPL_FROM; if (sig->x && time(NULL) > sig->x) return DKIM_EXPL_EXPIRED; return DKIM_EXPL_OK; } static int dnsrec_parse(char *rec, mu_assoc_t *pa) { mu_assoc_t a; struct mu_wordsplit ws; int result; if (mu_assoc_create (&a, 0)) mu_alloc_die (); mu_assoc_set_destroy_item (a, mu_list_free_item); ws.ws_delim = ";"; if (mu_wordsplit(rec, &ws, MU_WRDSF_DELIM | MU_WRDSF_NOVAR | MU_WRDSF_WS | MU_WRDSF_NOCMD)) { result = 1; } else { size_t i; for (i = 0; i < ws.ws_wordc; i++) { char *p = strchr(ws.ws_wordv[i], '='); char **slot; int rc; if (!p) { result = 1; break; } *p++ = 0; mu_rtrim_class(ws.ws_wordv[i], MU_CTYPE_BLANK); mu_ltrim_class(p, MU_CTYPE_BLANK); rc = mu_assoc_install_ref(a, ws.ws_wordv[i], &slot); if (rc == ENOMEM) mu_alloc_die (); else if (rc) { result = 1; break; } else result = 0; *slot = mu_strdup(p); } } mu_wordsplit_free(&ws); if (result) mu_assoc_destroy (&a); else *pa = a; return result; } static int pubkey_validate(mu_assoc_t a, struct dkim_signature const *sig) { char *s; size_t n; if ((s = mu_assoc_get(a, "v")) != NULL && strcmp(s, DKIM_KEYRECORD_VERSION)) return DKIM_EXPL_KEY_SYNTAX; if ((s = mu_assoc_get(a, "p")) == NULL) return DKIM_EXPL_KEY_SYNTAX; if (s[0] == 0) return DKIM_EXPL_KEY_REVOKED; n = strcspn(sig->a, "-"); if ((s = mu_assoc_get(a, "k")) != NULL && !(n == strlen(s) && memcmp(s, sig->a, n) == 0)) return DKIM_EXPL_BAD_ALGORITHM; if ((s = mu_assoc_get(a, "h")) != NULL && !dkim_header_list_match(s, sig->a + n + 1)) return DKIM_EXPL_BAD_ALGORITHM; return DKIM_EXPL_OK; } static int dkim_sig_key_verify(mu_message_t msg, struct dkim_signature *sig, struct rsa_public_key *pub) { struct sha256_ctx ctx; mpz_t bs; int rc; struct nettle_buffer buffer; size_t length; mu_header_t hdr; char const *sigstr; rc = mu_message_get_header(msg, &hdr); if (rc) { mu_diag_funcall(MU_DIAG_ERROR, "mu_message_get_header", NULL, rc); return DKIM_EXPL_INTERNAL_ERROR; } rc = mu_header_sget_value(hdr, DKIM_SIGNATURE_HEADER, &sigstr); if (rc) { mu_diag_funcall(MU_DIAG_ERROR, "mu_header_sget_value", DKIM_SIGNATURE_HEADER, rc); return DKIM_EXPL_INTERNAL_ERROR; } sha256_init(&ctx); switch (dkim_hash(msg, sig, sigstr, &ctx)) { case DKIM_HASH_OK: break; case DKIM_HASH_DIFF: return DKIM_EXPL_BAD_BODY; case DKIM_HASH_ERR: return DKIM_EXPL_INTERNAL_ERROR; } length = strlen((char*)sig->b); nettle_buffer_init_realloc(&buffer, NULL, nettle_xrealloc); nettle_buffer_write(&buffer, length, sig->b); if (decode_base64(&buffer, 0, &length) != READ_PEM_OK) return DKIM_EXPL_BAD_BASE64; nettle_mpz_init_set_str_256_u(bs, length, buffer.contents); rc = rsa_sha256_verify (pub, &ctx, bs); nettle_buffer_clear(&buffer); mpz_clear(bs); return rc ? DKIM_EXPL_OK : DKIM_EXPL_BAD_SIG; } static int dkim_sig_verify(mu_message_t msg, struct dkim_signature *sig) { char **dnsrec; int i; int result; /* Get the DKIM DNS record */ switch (dkim_lookup(sig->d, sig->s, &dnsrec)) { case dns_success: break; case dns_not_found: return DKIM_EXPL_DNS_NOTFOUND; default: return DKIM_EXPL_DNS_UNAVAIL; } for (i = 0; dnsrec[i]; i++) { mu_assoc_t a; struct rsa_public_key pub; int rc; if (dnsrec_parse(dnsrec[i], &a)) continue; if ((rc = pubkey_validate(a, sig)) != DKIM_EXPL_OK) { result = rc; } else if (pubkey_from_base64(&pub, mu_assoc_get(a, "p")) != READ_PEM_OK) { result = DKIM_EXPL_KEY_SYNTAX; } else { result = dkim_sig_key_verify(msg, sig, &pub); rsa_public_key_clear(&pub); } mu_assoc_destroy(&a); if (result == DKIM_EXPL_OK) break; } for (i = 0; dnsrec[i]; i++) free(dnsrec[i]); free(dnsrec); return result; } static void wselim(char *s) { char *p = s; /* current destination pointer */ char *q = s; /* q - current source pointer */ while (*q) { size_t len = strcspn(q, " \t"); if (p != q) memmove(p, q, len); p += len; q += len; q += strspn(q, " \t"); } *p = 0; } int mfd_dkim_verify(mu_message_t msg, char **ret_sig) { mu_header_t hdr; int rc; int result = DKIM_EXPL_NO_SIG; size_t i; /* Get the DKIM-Signature header from the message */ rc = mu_message_get_header(msg, &hdr); if (rc) { mu_diag_funcall(MU_DIAG_ERROR, "mu_message_get_header", NULL, rc); return DKIM_EXPL_INTERNAL_ERROR; } for (i = 1; result != DKIM_EXPL_OK; i++) { struct dkim_signature sig; char *sig_str; rc = mu_header_aget_value_unfold_n(hdr, DKIM_SIGNATURE_HEADER, i, &sig_str); if (rc == MU_ERR_NOENT) break; else if (rc) { mu_diag_funcall(MU_DIAG_ERROR, "mu_header_aget_value_unfold", NULL, rc); result = DKIM_EXPL_INTERNAL_ERROR; break; } wselim(sig_str); /* Parse the DKIM signature */ if (dkim_signature_parse(sig_str, &sig)) { result = DKIM_EXPL_SIG_SYNTAX; } else { /* Validate the signature */ result = dkim_sig_validate(&sig); if (result == DKIM_EXPL_OK) result = dkim_sig_verify(msg, &sig); if (result == DKIM_EXPL_OK && ret_sig) { *ret_sig = sig_str; sig_str = NULL; } dkim_signature_free(&sig); } free(sig_str); } return result; }