/* ** Copyright (c) 2007 Sendmail, Inc. and its suppliers. ** All rights reserved. */ #ifndef lint static char dkim_canon_c_id[] = "@(#)$Id: dkim-canon.c,v 1.36 2007/12/18 02:15:47 msk Exp $"; #endif /* !lint */ /* system includes */ #include #include #include #include #include #include #include /* libsm includes */ #include /* libdkim includes */ #include "dkim.h" #include "dkim-types.h" #include "dkim-canon.h" #include "dkim-util.h" #include "util.h" /* definitions */ #define CRLF "\r\n" #define SP " " /* prototypes */ extern void dkim_error __P((DKIM *, const char *, ...)); /* ========================= PRIVATE SECTION ========================= */ /* ** DKIM_CANON_WRITE -- write data to canonicalization stream(s) ** ** Parameters: ** canon -- DKIM_CANON handle ** buf -- buffer containing canonicalized data ** buflen -- number of bytes to consume ** ** Return value: ** None. */ static void dkim_canon_write(DKIM_CANON *canon, u_char *buf, size_t buflen) { assert(canon != NULL); if (canon->canon_remain != (off_t) -1) buflen = MIN(buflen, canon->canon_remain); if (buf == NULL || buflen == 0) return; assert(canon->canon_hash != NULL); switch (canon->canon_hashtype) { case DKIM_HASHTYPE_SHA1: { struct dkim_sha1 *sha1; sha1 = (struct dkim_sha1 *) canon->canon_hash; SHA1_Update(&sha1->sha1_ctx, buf, buflen); if (sha1->sha1_tmpbio != NULL) BIO_write(sha1->sha1_tmpbio, buf, buflen); break; } #ifdef SHA256_DIGEST_LENGTH case DKIM_HASHTYPE_SHA256: { struct dkim_sha256 *sha256; sha256 = (struct dkim_sha256 *) canon->canon_hash; SHA256_Update(&sha256->sha256_ctx, buf, buflen); if (sha256->sha256_tmpbio != NULL) BIO_write(sha256->sha256_tmpbio, buf, buflen); break; } #endif /*SHA256_DIGEST_LENGTH */ } canon->canon_wrote += buflen; if (canon->canon_remain != (off_t) -1) canon->canon_remain -= buflen; } /* ** DKIM_CANON_BUFFER -- buffer for dkim_canon_write() ** ** Parameters: ** canon -- DKIM_CANON handle ** buf -- buffer containing canonicalized data ** buflen -- number of bytes to consume ** ** Return value: ** None. */ static void dkim_canon_buffer(DKIM_CANON *canon, u_char *buf, size_t buflen) { assert(canon != NULL); /* NULL buffer or 0 length means flush */ if (buf == NULL || buflen == 0) { if (canon->canon_hashbuflen > 0) { dkim_canon_write(canon, canon->canon_hashbuf, canon->canon_hashbuflen); canon->canon_hashbuflen = 0; } return; } /* not enough buffer space; write the buffer out */ if (canon->canon_hashbuflen + buflen > canon->canon_hashbufsize) { dkim_canon_write(canon, canon->canon_hashbuf, canon->canon_hashbuflen); canon->canon_hashbuflen = 0; } /* ** Now, if the input is bigger than the buffer, write it too; ** otherwise cache it. */ if (buflen >= canon->canon_hashbufsize) { dkim_canon_write(canon, buf, buflen); } else { memcpy(&canon->canon_hashbuf[canon->canon_hashbuflen], buf, buflen); canon->canon_hashbuflen += buflen; } } /* ** DKIM_CANON_HEADER -- canonicalize a header and write it ** ** Parameters: ** dkim -- DKIM handle ** canon -- DKIM_CANON handle ** hdr -- header handle ** crlf -- write a CRLF at the end? ** ** Return value: ** A DKIM_STAT constant. */ static DKIM_STAT dkim_canon_header(DKIM *dkim, DKIM_CANON *canon, struct dkim_header *hdr, bool crlf) { bool space; int n; u_char *p; u_char *tmp; u_char *end; u_char tmpbuf[BUFRSZ]; assert(canon != NULL); assert(hdr != NULL); tmp = tmpbuf; end = tmpbuf + sizeof tmpbuf - 1; if (dkim->dkim_canonbuf == NULL) { dkim->dkim_canonbuf = dkim_dstring_new(dkim, hdr->hdr_textlen, 0); if (dkim->dkim_canonbuf == NULL) return DKIM_STAT_NORESOURCE; } else { dkim_dstring_blank(dkim->dkim_canonbuf); } n = 0; dkim_canon_buffer(canon, NULL, 0); switch (canon->canon_canon) { case DKIM_CANON_SIMPLE: if (!dkim_dstring_catn(dkim->dkim_canonbuf, hdr->hdr_text, hdr->hdr_textlen) || (crlf && !dkim_dstring_catn(dkim->dkim_canonbuf, CRLF, 2))) return DKIM_STAT_NORESOURCE; break; case DKIM_CANON_RELAXED: /* process header field name (before colon) first */ for (p = hdr->hdr_text; *p != '\0'; p++) { /* ** Discard spaces before the colon or before the end ** of the first word. */ if (isascii(*p)) { /* discard spaces */ if (isspace(*p)) continue; /* convert to lowercase */ if (isupper(*p)) *tmp++ = tolower(*p); else *tmp++ = *p; } else { *tmp++ = *p; } /* reaching the end of the cache buffer, flush it */ if (tmp == end) { *tmp = '\0'; if (!dkim_dstring_catn(dkim->dkim_canonbuf, tmpbuf, tmp - tmpbuf)) return DKIM_STAT_NORESOURCE; tmp = tmpbuf; } if (*p == ':') { p++; break; } } /* skip all spaces before first word */ while (*p != '\0' && isascii(*p) && isspace(*p)) p++; space = FALSE; /* just saw a space */ for ( ; *p != '\0'; p++) { if (isascii(*p) && isspace(*p)) { /* mark that there was a space and continue */ space = TRUE; continue; } /* ** Any non-space marks the beginning of a word. ** If there's a stored space, use it up. */ if (space) { *tmp++ = ' '; /* flush buffer? */ if (tmp == end) { *tmp = '\0'; if (!dkim_dstring_catn(dkim->dkim_canonbuf, tmpbuf, tmp - tmpbuf)) return DKIM_STAT_NORESOURCE; tmp = tmpbuf; } space = FALSE; } /* copy the byte */ *tmp++ = *p; /* flush buffer? */ if (tmp == end) { *tmp = '\0'; if (!dkim_dstring_catn(dkim->dkim_canonbuf, tmpbuf, tmp - tmpbuf)) return DKIM_STAT_NORESOURCE; tmp = tmpbuf; } } /* flush any cached data */ if (tmp != tmpbuf) { *tmp = '\0'; if (!dkim_dstring_catn(dkim->dkim_canonbuf, tmpbuf, tmp - tmpbuf)) return DKIM_STAT_NORESOURCE; } if (crlf && !dkim_dstring_catn(dkim->dkim_canonbuf, CRLF, 2)) return DKIM_STAT_NORESOURCE; break; } dkim_canon_buffer(canon, dkim_dstring_get(dkim->dkim_canonbuf), dkim_dstring_len(dkim->dkim_canonbuf)); return DKIM_STAT_OK; } /* ** DKIM_CANON_FLUSHBLANKS -- use accumulated blank lines in canonicalization ** ** Parameters: ** canon -- DKIM_CANON handle ** ** Return value: ** None. */ static void dkim_canon_flushblanks(DKIM_CANON *canon) { int c; assert(canon != NULL); for (c = 0; c < canon->canon_blanks; c++) dkim_canon_buffer(canon, CRLF, 2); canon->canon_blanks = 0; } /* ** DKIM_CANON_SELECTHDRS -- choose headers to be included in canonicalization ** ** Parameters: ** dkim -- DKIM context in which this is performed ** hdrlist -- string containing headers that should be marked, separated ** by the ":" character ** ptrs -- array of header pointers (modified) ** nptr -- number of pointers available at "ptrs" ** ** Return value: ** Count of headers added to "ptrs", or -1 on error. ** ** Notes: ** Selects headers to be passed to canonicalization and the order in ** which this is done. "ptrs" is populated by pointers to headers ** in the order in which they should be fed to canonicalization. ** ** If any of the returned pointers is NULL, then a header named by ** "hdrlist" was not found. */ static int dkim_canon_selecthdrs(DKIM *dkim, u_char *hdrlist, struct dkim_header **ptrs, int nptrs) { u_char *bar; char *ctx; char *colon; int c; int n; int m; size_t len; struct dkim_header *hdr; struct dkim_header **lhdrs; u_char **hdrs; assert(dkim != NULL); assert(ptrs != NULL); assert(nptrs != 0); /* if there are no headers named, use them all */ if (hdrlist == NULL) { n = 0; for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next) { if (n >= nptrs) break; ptrs[n] = hdr; n++; } return n; } if (dkim->dkim_hdrlist == NULL) { dkim->dkim_hdrlist = dkim_malloc(dkim->dkim_libhandle, dkim->dkim_closure, DKIM_MAXHEADER); if (dkim->dkim_hdrlist == NULL) { dkim_error(dkim, "unable to allocate %d bytes(s)", DKIM_MAXHEADER); return -1; } } sm_strlcpy(dkim->dkim_hdrlist, hdrlist, DKIM_MAXHEADER); /* mark all headers as not used */ for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next) hdr->hdr_flags &= ~DKIM_HDR_SIGNED; n = dkim->dkim_hdrcnt * sizeof(struct dkim_header *); lhdrs = DKIM_MALLOC(dkim, n); if (lhdrs == NULL) return -1; n = 1; for (colon = dkim->dkim_hdrlist; *colon != '\0'; colon++) { if (*colon == ':') n++; } n = dkim->dkim_hdrcnt * n; hdrs = DKIM_MALLOC(dkim, n); if (hdrs == NULL) return -1; n = 0; /* make a split-out copy of hdrlist */ memset(hdrs, '\0', sizeof hdrs); for (bar = strtok_r(dkim->dkim_hdrlist, ":", &ctx); bar != NULL; bar = strtok_r(NULL, ":", &ctx)) { hdrs[n] = (u_char *) bar; n++; } /* bounds check */ if (n > nptrs) { dkim_error(dkim, "too many headers (max %d)", nptrs); DKIM_FREE(dkim, lhdrs); DKIM_FREE(dkim, hdrs); return -1; } /* for each named header, find the last unused one and use it up */ for (c = 0; c < n; c++) { lhdrs[c] = NULL; len = MIN(DKIM_MAXHEADER, strlen(hdrs[c])); while (len > 0 && isascii(hdrs[c][len - 1]) && isspace(hdrs[c][len - 1])) len--; for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next) { if (hdr->hdr_flags & DKIM_HDR_SIGNED) continue; if (len == hdr->hdr_namelen && strncasecmp(hdr->hdr_text, hdrs[c], len) == 0) lhdrs[c] = hdr; } if (lhdrs[c] != NULL) lhdrs[c]->hdr_flags |= DKIM_HDR_SIGNED; } /* copy to the caller's buffers */ m = 0; for (c = 0; c < n; c++) { if (lhdrs[c] != NULL) { ptrs[m] = lhdrs[c]; m++; } } DKIM_FREE(dkim, lhdrs); DKIM_FREE(dkim, hdrs); return m; } /* ========================= PUBLIC SECTION ========================= */ /* ** DKIM_CANON_INIT -- initialize all canonicalizations ** ** Parameters: ** dkim -- DKIM handle ** tmp -- make temp files? ** keep -- keep temp files? ** ** Return value: ** A DKIM_STAT_* constant. */ DKIM_STAT dkim_canon_init(DKIM *dkim, bool tmp, bool keep) { int fd; DKIM_STAT status; DKIM_CANON *cur; assert(dkim != NULL); for (cur = dkim->dkim_canonhead; cur != NULL; cur = cur->canon_next) { cur->canon_hashbuf = DKIM_MALLOC(dkim, DKIM_HASHBUFSIZE); if (cur->canon_hashbuf == NULL) { dkim_error(dkim, "unable to allocate %d byte(s)", sizeof(struct dkim_sha1)); return DKIM_STAT_NORESOURCE; } cur->canon_hashbufsize = DKIM_HASHBUFSIZE; cur->canon_hashbuflen = 0; switch (cur->canon_hashtype) { case DKIM_HASHTYPE_SHA1: { struct dkim_sha1 *sha1; sha1 = (struct dkim_sha1 *) DKIM_MALLOC(dkim, sizeof(struct dkim_sha1)); if (sha1 == NULL) { dkim_error(dkim, "unable to allocate %d byte(s)", sizeof(struct dkim_sha1)); return DKIM_STAT_NORESOURCE; } memset(sha1, '\0', sizeof(struct dkim_sha1)); SHA1_Init(&sha1->sha1_ctx); if (tmp) { status = dkim_tmpfile(dkim, &fd, keep); if (status != DKIM_STAT_OK) { DKIM_FREE(dkim, sha1); return status; } sha1->sha1_tmpfd = fd; sha1->sha1_tmpbio = BIO_new_fd(fd, 1); } cur->canon_hash = sha1; break; } #ifdef DKIM_HASHTYPE_SHA256 case DKIM_HASHTYPE_SHA256: { struct dkim_sha256 *sha256; sha256 = (struct dkim_sha256 *) DKIM_MALLOC(dkim, sizeof(struct dkim_sha256)); if (sha256 == NULL) { dkim_error(dkim, "unable to allocate %d byte(s)", sizeof(struct dkim_sha256)); return DKIM_STAT_NORESOURCE; } memset(sha256, '\0', sizeof(struct dkim_sha256)); SHA256_Init(&sha256->sha256_ctx); if (tmp) { status = dkim_tmpfile(dkim, &fd, keep); if (status != DKIM_STAT_OK) { DKIM_FREE(dkim, sha256); return status; } sha256->sha256_tmpfd = fd; sha256->sha256_tmpbio = BIO_new_fd(fd, 1); } cur->canon_hash = sha256; break; } #endif /* DKIM_HASHTYPE_SHA256 */ default: assert(0); } } return DKIM_STAT_OK; } /* ** DKIM_CANON_CLEANUP -- discard canonicalizations ** ** Parameters: ** dkim -- DKIM handle ** ** Return value: ** None. */ void dkim_canon_cleanup(DKIM *dkim) { DKIM_CANON *cur; DKIM_CANON *next; assert(dkim != NULL); cur = dkim->dkim_canonhead; while (cur != NULL) { next = cur->canon_next; dkim_mfree(dkim->dkim_libhandle, dkim->dkim_closure, cur->canon_hash); cur = next; } } /* ** DKIM_ADD_CANON -- add a new canonicalization handle if needed ** ** Parameters: ** dkim -- verification handle ** hdr -- TRUE iff this is specifying a header canonicalization ** canon -- canonicalization mode ** hashtype -- hash type ** hdrlist -- for header canonicalization, the header list ** hdr -- pointer to header being verified (NULL for signing) ** length -- for body canonicalization, the length limit (-1 == all) ** cout -- DKIM_CANON handle (returned) ** ** Return value: ** A DKIM_STAT_* constant. */ DKIM_STAT dkim_add_canon(DKIM *dkim, bool hdr, dkim_canon_t canon, int hashtype, u_char *hdrlist, struct dkim_header *sighdr, off_t length, DKIM_CANON **cout) { DKIM_CANON *cur; DKIM_CANON *new; assert(dkim != NULL); assert(canon == DKIM_CANON_SIMPLE || canon == DKIM_CANON_RELAXED); #ifdef DKIM_HASHTYPE_SHA256 assert(hashtype == DKIM_HASHTYPE_SHA1 || hashtype == DKIM_HASHTYPE_SHA256); #else /* DKIM_HASHTYPE_SHA256 */ assert(hashtype == DKIM_HASHTYPE_SHA1); #endif /* DKIM_HASHTYPE_SHA256 */ for (cur = dkim->dkim_canonhead; cur != NULL; cur = cur->canon_next) { if (cur->canon_hdr != hdr || cur->canon_hashtype != hashtype || cur->canon_canon != canon) continue; if (hdr && ((hdrlist == NULL && cur->canon_hdrlist != NULL) || (hdrlist != NULL && cur->canon_hdrlist == NULL) || strcasecmp(hdrlist, cur->canon_hdrlist) != 0)) continue; if (!hdr && length != cur->canon_length) continue; if (cout != NULL) *cout = cur; return DKIM_STAT_OK; } new = (DKIM_CANON *) dkim_malloc(dkim->dkim_libhandle, dkim->dkim_closure, sizeof *new); if (new == NULL) { dkim_error(dkim, "unable to allocate %d byte(s)", sizeof *new); return DKIM_STAT_NORESOURCE; } new->canon_done = FALSE; new->canon_hdr = hdr; new->canon_canon = canon; new->canon_hashtype = hashtype; new->canon_hash = NULL; new->canon_wrote = 0; if (hdr) { new->canon_length = (off_t) -1; new->canon_remain = (off_t) -1; } else { new->canon_length = length; new->canon_remain = length; } new->canon_sigheader = sighdr; new->canon_hdrlist = hdrlist; new->canon_next = NULL; new->canon_done = FALSE; new->canon_blankline = FALSE; new->canon_blanks = 0; new->canon_wrote = 0; new->canon_hashbuflen = 0; new->canon_hashbufsize = 0; new->canon_hashbuf = NULL; if (dkim->dkim_canonhead == NULL) { dkim->dkim_canontail = new; dkim->dkim_canonhead = new; } else { dkim->dkim_canontail->canon_next = new; dkim->dkim_canontail = new; } if (cout != NULL) *cout = new; return DKIM_STAT_OK; } /* ** DKIM_CANON_RUNHEADERS -- run the headers through all header ** canonicalizations ** ** Parameters: ** dkim -- DKIM handle ** signing -- TRUE iff we're signing ** ** Return value: ** A DKIM_STAT_* constant. ** ** Note: ** Header canonicalizations are finalized by this function. */ DKIM_STAT dkim_canon_runheaders(DKIM *dkim, bool signing) { u_char savechar; int c; int n; int in; int nhdrs; int last = '\0'; DKIM_STAT status; u_char *tmp; u_char *end; DKIM_CANON *cur; u_char *p; struct dkim_header *hdr; struct dkim_header **hdrset; struct dkim_header tmphdr; u_char tmpbuf[BUFRSZ]; assert(dkim != NULL); tmp = tmpbuf; end = tmpbuf + sizeof tmpbuf - 1; n = dkim->dkim_hdrcnt * sizeof(struct dkim_header *); hdrset = DKIM_MALLOC(dkim, n); if (hdrset == NULL) return DKIM_STAT_NORESOURCE; if (dkim->dkim_hdrbuf == NULL) { dkim->dkim_hdrbuf = dkim_dstring_new(dkim, BUFRSZ, MAXBUFRSZ); if (dkim->dkim_hdrbuf == NULL) { DKIM_FREE(dkim, hdrset); return DKIM_STAT_NORESOURCE; } } else { dkim_dstring_blank(dkim->dkim_hdrbuf); } for (cur = dkim->dkim_canonhead; cur != NULL; cur = cur->canon_next) { /* skip done hashes and those which are of the wrong type */ if (cur->canon_done || !cur->canon_hdr) continue; /* clear header selection flags if verifying */ if (!signing) { if (cur->canon_hdrlist == NULL) { for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next) hdr->hdr_flags |= ~DKIM_HDR_SIGNED; } else { for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next) hdr->hdr_flags &= ~DKIM_HDR_SIGNED; memset(hdrset, '\0', sizeof hdrset); /* do header selection */ nhdrs = dkim_canon_selecthdrs(dkim, cur->canon_hdrlist, hdrset, dkim->dkim_hdrcnt); if (nhdrs == -1) { dkim_error(dkim, "dkim_canon_selecthdrs() failed during canonicalization"); DKIM_FREE(dkim, hdrset); return DKIM_STAT_INTERNAL; } } } else { DKIM_LIB *lib; char *tmp; lib = dkim->dkim_libhandle; memset(hdrset, '\0', sizeof hdrset); nhdrs = 0; /* tag headers to be signed */ for (hdr = dkim->dkim_hhead; hdr != NULL && nhdrs < MAXHEADERS; hdr = hdr->hdr_next) { if (!lib->dkiml_signre) { tmp = dkim_dstring_get(dkim->dkim_hdrbuf); if (tmp[0] != '\0') { dkim_dstring_cat1(dkim->dkim_hdrbuf, ':'); } dkim_dstring_catn(dkim->dkim_hdrbuf, hdr->hdr_text, hdr->hdr_namelen); continue; } /* could be space, could be colon ... */ savechar = hdr->hdr_text[hdr->hdr_namelen]; /* terminate the header field name and test */ hdr->hdr_text[hdr->hdr_namelen] = '\0'; status = regexec(&lib->dkiml_hdrre, hdr->hdr_text, 0, NULL, 0); /* restore the character */ hdr->hdr_text[hdr->hdr_namelen] = savechar; if (status == 0) { tmp = dkim_dstring_get(dkim->dkim_hdrbuf); if (tmp[0] != '\0') { dkim_dstring_cat1(dkim->dkim_hdrbuf, ':'); } dkim_dstring_catn(dkim->dkim_hdrbuf, hdr->hdr_text, hdr->hdr_namelen); } else { assert(status == REG_NOMATCH); } } memset(hdrset, '\0', sizeof hdrset); /* do header selection */ nhdrs = dkim_canon_selecthdrs(dkim, dkim_dstring_get(dkim->dkim_hdrbuf), hdrset, dkim->dkim_hdrcnt); if (nhdrs == -1) { dkim_error(dkim, "dkim_canon_selecthdrs() failed during canonicalization"); DKIM_FREE(dkim, hdrset); return DKIM_STAT_INTERNAL; } } /* canonicalize each marked header */ for (c = 0; c < nhdrs; c++) { if (hdrset[c] != NULL && (hdrset[c]->hdr_flags & DKIM_HDR_SIGNED) != 0) { status = dkim_canon_header(dkim, cur, hdrset[c], TRUE); if (status != DKIM_STAT_OK) { DKIM_FREE(dkim, hdrset); return status; } } } /* if signing, we can't do the rest of this yet */ if (signing) continue; /* ** We need to copy the DKIM-Signature: header being verified, ** minus the contents of the "b=" part, and include it in the ** canonicalization. However, skip this if no hashing was ** done. */ dkim_dstring_blank(dkim->dkim_hdrbuf); tmp = tmpbuf; n = 0; in = '\0'; for (p = cur->canon_sigheader->hdr_text; *p != '\0'; p++) { if (*p == ';') in = '\0'; if (in == 'b') { last = *p; continue; } if (in == '\0' && *p == '=') in = last; *tmp++ = *p; /* flush buffer? */ if (tmp == end) { *tmp = '\0'; if (!dkim_dstring_catn(dkim->dkim_hdrbuf, tmpbuf, tmp - tmpbuf)) return DKIM_STAT_NORESOURCE; tmp = tmpbuf; } last = *p; } /* flush anything cached */ if (tmp != tmpbuf) { *tmp = '\0'; if (!dkim_dstring_catn(dkim->dkim_hdrbuf, tmpbuf, tmp - tmpbuf)) return DKIM_STAT_NORESOURCE; } /* canonicalize */ tmphdr.hdr_text = dkim_dstring_get(dkim->dkim_hdrbuf); tmphdr.hdr_namelen = cur->canon_sigheader->hdr_namelen; tmphdr.hdr_colon = tmphdr.hdr_text + (cur->canon_sigheader->hdr_colon - cur->canon_sigheader->hdr_text); tmphdr.hdr_textlen = dkim_dstring_len(dkim->dkim_hdrbuf); tmphdr.hdr_flags = 0; tmphdr.hdr_next = NULL; if (cur->canon_canon == DKIM_CANON_RELAXED) dkim_lowerhdr(tmphdr.hdr_text); (void) dkim_canon_header(dkim, cur, &tmphdr, FALSE); dkim_canon_buffer(cur, NULL, 0); /* finalize */ switch (cur->canon_hashtype) { case DKIM_HASHTYPE_SHA1: { struct dkim_sha1 *sha1; sha1 = (struct dkim_sha1 *) cur->canon_hash; SHA1_Final(sha1->sha1_out, &sha1->sha1_ctx); if (sha1->sha1_tmpbio != NULL) BIO_flush(sha1->sha1_tmpbio); break; } #ifdef DKIM_HASHTYPE_SHA256 case DKIM_HASHTYPE_SHA256: { struct dkim_sha256 *sha256; sha256 = (struct dkim_sha256 *) cur->canon_hash; SHA256_Final(sha256->sha256_out, &sha256->sha256_ctx); if (sha256->sha256_tmpbio != NULL) BIO_flush(sha256->sha256_tmpbio); break; } #endif /* DKIM_HASHTYPE_SHA256 */ default: assert(0); /* NOTREACHED */ } cur->canon_done = TRUE; } DKIM_FREE(dkim, hdrset); return DKIM_STAT_OK; } /* ** DKIM_CANON_SIGNATURE -- append a signature header when signing ** ** Parameters: ** dkim -- DKIM handle ** hdr -- header ** ** Return value: ** A DKIM_STAT_* constant. */ DKIM_STAT dkim_canon_signature(DKIM *dkim, struct dkim_header *hdr) { DKIM_STAT status; DKIM_CANON *cur; struct dkim_header tmphdr; assert(dkim != NULL); assert(hdr != NULL); if (dkim->dkim_hdrbuf == NULL) { dkim->dkim_hdrbuf = dkim_dstring_new(dkim, DKIM_MAXHEADER, 0); if (dkim->dkim_hdrbuf == NULL) return DKIM_STAT_NORESOURCE; } else { dkim_dstring_blank(dkim->dkim_hdrbuf); } for (cur = dkim->dkim_canonhead; cur != NULL; cur = cur->canon_next) { /* skip done hashes and those which are of the wrong type */ if (cur->canon_done || !cur->canon_hdr) continue; /* prepare the data */ dkim_dstring_copy(dkim->dkim_hdrbuf, hdr->hdr_text); tmphdr.hdr_text = dkim_dstring_get(dkim->dkim_hdrbuf); tmphdr.hdr_namelen = hdr->hdr_namelen; tmphdr.hdr_colon = tmphdr.hdr_text + (hdr->hdr_colon - hdr->hdr_text); tmphdr.hdr_textlen = dkim_dstring_len(dkim->dkim_hdrbuf); tmphdr.hdr_flags = 0; tmphdr.hdr_next = NULL; if (cur->canon_canon == DKIM_CANON_RELAXED) dkim_lowerhdr(tmphdr.hdr_text); /* canonicalize the signature */ status = dkim_canon_header(dkim, cur, &tmphdr, FALSE); if (status != DKIM_STAT_OK) return status; dkim_canon_buffer(cur, NULL, 0); /* now close it */ switch (cur->canon_hashtype) { case DKIM_HASHTYPE_SHA1: { struct dkim_sha1 *sha1; sha1 = (struct dkim_sha1 *) cur->canon_hash; SHA1_Final(sha1->sha1_out, &sha1->sha1_ctx); if (sha1->sha1_tmpbio != NULL) BIO_flush(sha1->sha1_tmpbio); break; } #ifdef DKIM_HASHTYPE_SHA256 case DKIM_HASHTYPE_SHA256: { struct dkim_sha256 *sha256; sha256 = (struct dkim_sha256 *) cur->canon_hash; SHA256_Final(sha256->sha256_out, &sha256->sha256_ctx); if (sha256->sha256_tmpbio != NULL) BIO_flush(sha256->sha256_tmpbio); break; } #endif /* DKIM_HASHTYPE_SHA256 */ default: assert(0); /* NOTREACHED */ } cur->canon_done = TRUE; } return DKIM_STAT_OK; } /* ** DKIM_CANON_MINBODY -- return number of bytes required to satisfy ** canonicalizations ** ** Parameters: ** dkim -- DKIM handle ** ** Return value: ** 0 -- all canonicalizations satisfied ** ULONG_MAX -- at least one canonicalization wants the whole message ** other -- bytes required to satisfy all canonicalizations */ u_long dkim_canon_minbody(DKIM *dkim) { u_long minbody = 0; DKIM_CANON *cur; assert(dkim != NULL); for (cur = dkim->dkim_canonhead; cur != NULL; cur = cur->canon_next) { /* skip done hashes and those which are of the wrong type */ if (cur->canon_done || cur->canon_hdr) continue; /* if this one wants the whole message, short-circuit */ if (cur->canon_remain == (off_t) -1) return ULONG_MAX; /* compare to current minimum */ minbody = MIN(minbody, (u_long) cur->canon_remain); } return minbody; } /* ** DKIM_CANON_BODYCHUNK -- run a body chunk through all body ** canonicalizations ** ** Parameters: ** dkim -- DKIM handle ** buf -- pointer to bytes to canonicalize ** buflen -- number of bytes to canonicalize ** ** Return value: ** A DKIM_STAT_* constant. */ DKIM_STAT dkim_canon_bodychunk(DKIM *dkim, u_char *buf, size_t buflen) { u_int wlen; DKIM_CANON *cur; u_char *p; u_char *wrote; u_char *eob; assert(dkim != NULL); dkim->dkim_bodylen += buflen; for (cur = dkim->dkim_canonhead; cur != NULL; cur = cur->canon_next) { /* skip done hashes and those which are of the wrong type */ if (cur->canon_done || cur->canon_hdr) continue; /* short-circuit completed canonicalizations */ if (cur->canon_remain == 0) continue; eob = buf + buflen - 1; wrote = buf; wlen = 0; switch (cur->canon_canon) { case DKIM_CANON_SIMPLE: for (p = buf; p <= eob; p++) { if (*p == '\n') { if (cur->canon_lastchar == '\r') { if (cur->canon_blankline) { cur->canon_blanks++; } else if (wlen == 1) { dkim_canon_buffer(cur, CRLF, 2); } else { dkim_canon_buffer(cur, wrote, wlen + 1); } wrote = p + 1; wlen = 0; cur->canon_blankline = TRUE; } } else { if (*p != '\r') { if (cur->canon_blanks > 0) dkim_canon_flushblanks(cur); cur->canon_blankline = FALSE; } wlen++; } cur->canon_lastchar = *p; } break; case DKIM_CANON_RELAXED: for (p = buf; p <= eob; p++) { if (*p == '\n') { if (cur->canon_lastchar == '\r') { if (cur->canon_blankline) { cur->canon_blanks++; } else if (wlen == 1) { dkim_canon_buffer(cur, CRLF, 2); } else { dkim_canon_buffer(cur, wrote, wlen + 1); } wrote = p + 1; wlen = 0; cur->canon_blankline = TRUE; } } else { /* non-space after a space */ if (dkim_islwsp(cur->canon_lastchar) && !dkim_islwsp(*p) && *p != '\r') { if (cur->canon_blanks > 0) dkim_canon_flushblanks(cur); dkim_canon_buffer(cur, SP, 1); wrote = p; wlen = 1; cur->canon_blankline = FALSE; } /* space after a non-space */ else if (!dkim_islwsp(cur->canon_lastchar) && dkim_islwsp(*p)) { if (cur->canon_blanks > 0) dkim_canon_flushblanks(cur); dkim_canon_buffer(cur, wrote, wlen); wlen = 0; wrote = p + 1; } /* space after a space */ else if (dkim_islwsp(cur->canon_lastchar) && dkim_islwsp(*p)) { /* XXX -- fix logic */ ; } /* non-space after a non-space */ else { wlen++; if (cur->canon_blankline && (wlen > 1 || *p != '\r')) { if (cur->canon_blanks > 0) dkim_canon_flushblanks(cur); cur->canon_blankline = FALSE; } } } cur->canon_lastchar = *p; } break; default: assert(0); /* NOTREACHED */ } dkim_canon_buffer(cur, wrote, wlen); } return DKIM_STAT_OK; } /* ** DKIM_CANON_CLOSEBODY -- close all body canonicalizations ** ** Parameters: ** dkim -- DKIM handle ** ** Return value: ** A DKIM_STAT_* constant. */ DKIM_STAT dkim_canon_closebody(DKIM *dkim) { DKIM_CANON *cur; assert(dkim != NULL); for (cur = dkim->dkim_canonhead; cur != NULL; cur = cur->canon_next) { /* skip done hashes or header canonicalizations */ if (cur->canon_done || cur->canon_hdr) continue; /* "simple" canonicalization must include at least a CRLF */ if (cur->canon_canon == DKIM_CANON_SIMPLE && cur->canon_wrote == 0) dkim_canon_buffer(cur, CRLF, 2); dkim_canon_buffer(cur, NULL, 0); /* finalize */ switch (cur->canon_hashtype) { case DKIM_HASHTYPE_SHA1: { struct dkim_sha1 *sha1; sha1 = (struct dkim_sha1 *) cur->canon_hash; SHA1_Final(sha1->sha1_out, &sha1->sha1_ctx); if (sha1->sha1_tmpbio != NULL) BIO_flush(sha1->sha1_tmpbio); break; } #ifdef DKIM_HASHTYPE_SHA256 case DKIM_HASHTYPE_SHA256: { struct dkim_sha256 *sha256; sha256 = (struct dkim_sha256 *) cur->canon_hash; SHA256_Final(sha256->sha256_out, &sha256->sha256_ctx); if (sha256->sha256_tmpbio != NULL) BIO_flush(sha256->sha256_tmpbio); break; } #endif /* DKIM_HASHTYPE_SHA256 */ default: assert(0); /* NOTREACHED */ } cur->canon_done = TRUE; } return DKIM_STAT_OK; } /* ** DKIM_CANON_GETFINAL -- retrieve final digest ** ** Parameters: ** canon -- DKIM_CANON handle ** digest -- pointer to the digest (returned) ** dlen -- digest length (returned) ** ** Return value: ** A DKIM_STAT_* constant. */ DKIM_STAT dkim_canon_getfinal(DKIM_CANON *canon, u_char **digest, size_t *dlen) { assert(canon != NULL); assert(digest != NULL); assert(dlen != NULL); if (!canon->canon_done) return DKIM_STAT_INVALID; switch (canon->canon_hashtype) { case DKIM_HASHTYPE_SHA1: { struct dkim_sha1 *sha1; sha1 = (struct dkim_sha1 *) canon->canon_hash; *digest = sha1->sha1_out; *dlen = sizeof sha1->sha1_out; return DKIM_STAT_OK; } #ifdef DKIM_HASHTYPE_SHA256 case DKIM_HASHTYPE_SHA256: { struct dkim_sha256 *sha256; sha256 = (struct dkim_sha256 *) canon->canon_hash; *digest = sha256->sha256_out; *dlen = sizeof sha256->sha256_out; return DKIM_STAT_OK; } #endif /* DKIM_HASHTYPE_SHA256 */ default: assert(0); /* NOTREACHED */ return DKIM_STAT_INTERNAL; } } /* ** DKIM_CANON_FREE -- destroy a canonicalization ** ** Parameters: ** dkim -- DKIM handle ** canon -- canonicalization to destroy ** ** Return value: ** None. */ void dkim_canon_free(DKIM *dkim, DKIM_CANON *canon) { assert(dkim != NULL); assert(canon != NULL); if (canon->canon_hash != NULL) { switch (canon->canon_hashtype) { case DKIM_HASHTYPE_SHA1: { struct dkim_sha1 *sha1; sha1 = (struct dkim_sha1 *) canon->canon_hash; if (sha1->sha1_tmpbio != NULL) { BIO_free(sha1->sha1_tmpbio); sha1->sha1_tmpfd = -1; sha1->sha1_tmpbio = NULL; } break; } #ifdef DKIM_HASHTYPE_SHA256 case DKIM_HASHTYPE_SHA256: { struct dkim_sha256 *sha256; sha256 = (struct dkim_sha256 *) canon->canon_hash; if (sha256->sha256_tmpbio != NULL) { BIO_free(sha256->sha256_tmpbio); sha256->sha256_tmpfd = -1; sha256->sha256_tmpbio = NULL; } break; } #endif /* DKIM_HASHTYPE_SHA256 */ default: assert(0); /* NOTREACHED */ } dkim_mfree(dkim->dkim_libhandle, dkim->dkim_closure, canon->canon_hash); } if (canon->canon_hashbuf != NULL) { dkim_mfree(dkim->dkim_libhandle, dkim->dkim_closure, canon->canon_hashbuf); } dkim_mfree(dkim->dkim_libhandle, dkim->dkim_closure, canon); }