Fix failed verification when header canon != body canon
[users/jgh/exim.git] / src / src / pdkim / pdkim.c
index 82bfe1ed4cfdd3048f07fd7461ade11c2a6a38db..35c6ed9b5826a082a6b66d4970f8455d8cebc967 100644 (file)
@@ -1,12 +1,31 @@
-/* $Cambridge: exim/src/src/pdkim/pdkim.c,v 1.1.2.7 2009/03/17 14:56:55 tom Exp $ */
-/* pdkim.c */
+/*
+ *  PDKIM - a RFC4871 (DKIM) implementation
+ *
+ *  Copyright (C) 2009  Tom Kistner <tom@duncanthrax.net>
+ *
+ *  http://duncanthrax.net/pdkim/
+ *
+ *  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 2 of the License, 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, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* $Cambridge: exim/src/src/pdkim/pdkim.c,v 1.1.2.14 2009/04/30 19:15:48 tom Exp $ */
 
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
-#include <strings.h>
 #include <ctype.h>
-#include <unistd.h>
 
 #include "pdkim.h"
 
@@ -22,7 +41,6 @@
 #define PDKIM_MAX_HEADERS           512
 #define PDKIM_MAX_BODY_LINE_LEN     1024
 #define PDKIM_DNS_TXT_MAX_NAMELEN   1024
-#define PDKIM_DNS_TXT_MAX_RECLEN    4096
 #define PDKIM_DEFAULT_SIGN_HEADERS "From:Sender:Reply-To:Subject:Date:"\
                              "Message-ID:To:Cc:MIME-Version:Content-Type:"\
                              "Content-Transfer-Encoding:Content-ID:"\
@@ -32,7 +50,7 @@
                              "List-Id:List-Help:List-Unsubscribe:"\
                              "List-Subscribe:List-Post:List-Owner:List-Archive"
 
-
+/* -------------------------------------------------------------------------- */
 struct pdkim_stringlist {
   char *value;
   void *next;
@@ -45,8 +63,6 @@ struct pdkim_str {
   unsigned int  allocated;
 };
 
-
-
 /* -------------------------------------------------------------------------- */
 /* A bunch of list constants */
 char *pdkim_querymethods[] = {
@@ -122,7 +138,7 @@ void pdkim_hexprint(FILE *stream, char *data, int len, int lf) {
 
   for (i=0;i<len;i++) {
     int c = p[i];
-    fprintf(stream,"%02x ",c);
+    fprintf(stream,"%02x",c);
   }
   if (lf)
     fputc('\n',stream);
@@ -136,9 +152,8 @@ pdkim_stringlist *pdkim_append_stringlist(pdkim_stringlist *base, char *str) {
   pdkim_stringlist *new_entry = malloc(sizeof(pdkim_stringlist));
   if (new_entry == NULL) return NULL;
   memset(new_entry,0,sizeof(pdkim_stringlist));
-  new_entry->value = malloc(strlen(str)+1);
+  new_entry->value = strdup(str);
   if (new_entry->value == NULL) return NULL;
-  strcpy(new_entry->value,str);
   if (base != NULL) {
     pdkim_stringlist *last = base;
     while (last->next != NULL) { last = last->next; };
@@ -243,15 +258,19 @@ void pdkim_free_sig(pdkim_signature *sig) {
       free(c);
     }
 
-    if (sig->sigdata        != NULL) free(sig->sigdata);
-    if (sig->bodyhash       != NULL) free(sig->bodyhash);
-    if (sig->selector       != NULL) free(sig->selector);
-    if (sig->domain         != NULL) free(sig->domain);
-    if (sig->identity       != NULL) free(sig->identity);
-    if (sig->headernames    != NULL) free(sig->headernames);
-    if (sig->copiedheaders  != NULL) free(sig->copiedheaders);
-    if (sig->rsa_privkey    != NULL) free(sig->rsa_privkey);
-    if (sig->sign_headers   != NULL) free(sig->sign_headers);
+    if (sig->sigdata          != NULL) free(sig->sigdata);
+    if (sig->bodyhash         != NULL) free(sig->bodyhash);
+    if (sig->selector         != NULL) free(sig->selector);
+    if (sig->domain           != NULL) free(sig->domain);
+    if (sig->identity         != NULL) free(sig->identity);
+    if (sig->headernames      != NULL) free(sig->headernames);
+    if (sig->copiedheaders    != NULL) free(sig->copiedheaders);
+    if (sig->rsa_privkey      != NULL) free(sig->rsa_privkey);
+    if (sig->sign_headers     != NULL) free(sig->sign_headers);
+    if (sig->signature_header != NULL) free(sig->signature_header);
+    if (sig->sha1_body        != NULL) free(sig->sha1_body);
+    if (sig->sha2_body        != NULL) free(sig->sha2_body);
+    if (sig->hnames_check     != NULL) free(sig->hnames_check);
 
     if (sig->pubkey != NULL) pdkim_free_pubkey(sig->pubkey);
 
@@ -262,7 +281,7 @@ void pdkim_free_sig(pdkim_signature *sig) {
 
 
 /* -------------------------------------------------------------------------- */
-void pdkim_free_ctx(pdkim_ctx *ctx) {
+DLLEXPORT void pdkim_free_ctx(pdkim_ctx *ctx) {
   if (ctx) {
     pdkim_free_sig(ctx->sig);
     pdkim_strfree(ctx->cur_header);
@@ -277,44 +296,50 @@ void pdkim_free_ctx(pdkim_ctx *ctx) {
    "start". Returns the position of the header name in
    the list. */
 int header_name_match(char *header,
-                      char *list,
-                      int   start) {
+                      char *tick,
+                      int   do_tick) {
   char *hname;
   char *lcopy;
   char *p;
   char *q;
-  int pos = 0;
   int rc = PDKIM_FAIL;
+
+  /* Get header name */
   char *hcolon = strchr(header,':');
   if (hcolon == NULL) return rc; /* This isn't a header */
   hname = malloc((hcolon-header)+1);
   if (hname == NULL) return PDKIM_ERR_OOM;
   memset(hname,0,(hcolon-header)+1);
   strncpy(hname,header,(hcolon-header));
-  lcopy = malloc(strlen(list)+1);
+
+  /* Copy tick-off list locally, so we can punch zeroes into it */
+  lcopy = strdup(tick);
   if (lcopy == NULL) {
     free(hname);
     return PDKIM_ERR_OOM;
   }
-  strcpy(lcopy,list);
   p = lcopy;
   q = strchr(p,':');
   while (q != NULL) {
     *q = '\0';
-    if (pos >= start) {
-      if (strcasecmp(p,hname) == 0) {
-        rc = pos;
-        goto BAIL;
-      }
+
+    if (strcasecmp(p,hname) == 0) {
+      rc = PDKIM_OK;
+      /* Invalidate header name instance in tick-off list */
+      if (do_tick) tick[p-lcopy] = '_';
+      goto BAIL;
     }
+
     p = q+1;
     q = strchr(p,':');
-    pos++;
   }
-  if (pos >= start) {
-    if (strcasecmp(p,hname) == 0)
-      rc = pos;
+
+  if (strcasecmp(p,hname) == 0) {
+    rc = PDKIM_OK;
+    /* Invalidate header name instance in tick-off list */
+    if (do_tick) tick[p-lcopy] = '_';
   }
+
   BAIL:
   free(hname);
   free(lcopy);
@@ -470,6 +495,7 @@ pdkim_signature *pdkim_parse_sig_header(pdkim_ctx *ctx, char *raw_hdr) {
   sig = malloc(sizeof(pdkim_signature));
   if (sig == NULL) return NULL;
   memset(sig,0,sizeof(pdkim_signature));
+  sig->bodylength = -1;
 
   sig->rawsig_no_b_val = malloc(strlen(raw_hdr)+1);
   if (sig->rawsig_no_b_val == NULL) {
@@ -480,7 +506,7 @@ pdkim_signature *pdkim_parse_sig_header(pdkim_ctx *ctx, char *raw_hdr) {
   p = raw_hdr;
   q = sig->rawsig_no_b_val;
 
-  while (*p != '\0') {
+  while (1) {
 
     /* Ignore FWS */
     if ( (*p == '\r') || (*p == '\n') )
@@ -521,10 +547,10 @@ pdkim_signature *pdkim_parse_sig_header(pdkim_ctx *ctx, char *raw_hdr) {
       if (cur_val == NULL)
         cur_val = pdkim_strnew(NULL);
 
-      if ( (*p == '\r') || (*p == '\n') )
+      if ( (*p == '\r') || (*p == '\n') || (*p == ' ') || (*p == '\t') )
         goto NEXT_CHAR;
 
-      if (*p == ';') {
+      if ( (*p == ';') || (*p == '\0') ) {
         if (cur_tag->len > 0) {
           pdkim_strtrim(cur_val);
           #ifdef PDKIM_DEBUG
@@ -596,7 +622,7 @@ pdkim_signature *pdkim_parse_sig_header(pdkim_ctx *ctx, char *raw_hdr) {
               sig->expires = strtoul(cur_val->str,NULL,10);
             break;
             case 'l':
-              sig->bodylength = strtoul(cur_val->str,NULL,10);
+              sig->bodylength = strtol(cur_val->str,NULL,10);
             break;
             case 'h':
               sig->headernames = strdup(cur_val->str);
@@ -622,6 +648,7 @@ pdkim_signature *pdkim_parse_sig_header(pdkim_ctx *ctx, char *raw_hdr) {
     }
 
     NEXT_CHAR:
+    if (*p == '\0') break;
 
     if (!in_b_val) {
       *q = *p;
@@ -641,7 +668,16 @@ pdkim_signature *pdkim_parse_sig_header(pdkim_ctx *ctx, char *raw_hdr) {
     return NULL;
   }
 
+  /* Copy header list to 'tick-off' header list */
+  sig->hnames_check = strdup(sig->headernames);
+
   *q = '\0';
+  /* Chomp raw header. The final newline must not be added to the signature. */
+  q--;
+  while( (q > sig->rawsig_no_b_val) && ((*q == '\r') || (*q == '\n')) ) {
+    *q = '\0'; q--;
+  }
+
   #ifdef PDKIM_DEBUG
   if (ctx->debug_stream) {
     fprintf(ctx->debug_stream,
@@ -686,7 +722,7 @@ pdkim_pubkey *pdkim_parse_pubkey_record(pdkim_ctx *ctx, char *raw_record) {
 
   p = raw_record;
 
-  while (*p != '\0') {
+  while (1) {
 
     /* Ignore FWS */
     if ( (*p == '\r') || (*p == '\n') )
@@ -720,7 +756,7 @@ pdkim_pubkey *pdkim_parse_pubkey_record(pdkim_ctx *ctx, char *raw_record) {
       if ( (*p == '\r') || (*p == '\n') )
         goto NEXT_CHAR;
 
-      if (*p == ';') {
+      if ( (*p == ';') || (*p == '\0') ) {
         if (cur_tag->len > 0) {
           pdkim_strtrim(cur_val);
           #ifdef PDKIM_DEBUG
@@ -773,6 +809,7 @@ pdkim_pubkey *pdkim_parse_pubkey_record(pdkim_ctx *ctx, char *raw_record) {
     }
 
     NEXT_CHAR:
+    if (*p == '\0') break;
     p++;
   }
 
@@ -835,7 +872,7 @@ int pdkim_update_bodyhash(pdkim_ctx *ctx, char *data, int len) {
     }
 
     /* Make sure we don't exceed the to-be-signed body length */
-    if (sig->bodylength &&
+    if ((sig->bodylength >= 0) &&
         ((sig->signed_body_bytes+(unsigned long)canon_len) > sig->bodylength))
       canon_len = (sig->bodylength - sig->signed_body_bytes);
 
@@ -892,7 +929,7 @@ int pdkim_finish_bodyhash(pdkim_ctx *ctx) {
 
       /* If bodylength limit is set, and we have received less bytes
          than the requested amount, effectively remove the limit tag. */
-      if (sig->signed_body_bytes < sig->bodylength) sig->bodylength = 0;
+      if (sig->signed_body_bytes < sig->bodylength) sig->bodylength = -1;
     }
     /* VERIFICATION --------------------------------------------------------- */
     else {
@@ -991,27 +1028,25 @@ int pdkim_header_complete(pdkim_ctx *ctx) {
 
   /* Traverse all signatures */
   while (sig != NULL) {
+    pdkim_stringlist *list;
 
     /* SIGNING -------------------------------------------------------------- */
     if (ctx->mode == PDKIM_MODE_SIGN) {
       if (header_name_match(ctx->cur_header->str,
                             sig->sign_headers?
                               sig->sign_headers:
-                              PDKIM_DEFAULT_SIGN_HEADERS, 0) < 0) goto NEXT_SIG;
+                              PDKIM_DEFAULT_SIGN_HEADERS, 0) != PDKIM_OK) goto NEXT_SIG;
     }
     /* VERIFICATION --------------------------------------------------------- */
     else {
-      int rc = header_name_match(ctx->cur_header->str,
-                                 sig->headernames,
-                                 sig->headernames_pos);
-      /* Header is not included or out-of-sequence */
-      if (rc < 0) goto NEXT_SIG;
-      sig->headernames_pos = rc;
+      /* Header is not included or all instances were already 'ticked off' */
+      if (header_name_match(ctx->cur_header->str,
+                            sig->hnames_check, 1) != PDKIM_OK) goto NEXT_SIG;
     }
 
     /* Add header to the signed headers list */
-    pdkim_stringlist *list = pdkim_append_stringlist(sig->headers,
-                                                     ctx->cur_header->str);
+    list = pdkim_append_stringlist(sig->headers,
+                                   ctx->cur_header->str);
     if (list == NULL) return PDKIM_ERR_OOM;
     sig->headers = list;
 
@@ -1024,13 +1059,14 @@ int pdkim_header_complete(pdkim_ctx *ctx) {
        (strncasecmp(ctx->cur_header->str,
                     DKIM_SIGNATURE_HEADERNAME,
                     strlen(DKIM_SIGNATURE_HEADERNAME)) == 0) ) {
+     pdkim_signature *new_sig;
     /* Create and chain new signature block */
     #ifdef PDKIM_DEBUG
     if (ctx->debug_stream)
       fprintf(ctx->debug_stream,
         "PDKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
     #endif
-    pdkim_signature *new_sig = pdkim_parse_sig_header(ctx, ctx->cur_header->str);
+    new_sig = pdkim_parse_sig_header(ctx, ctx->cur_header->str);
     if (new_sig != NULL) {
       pdkim_signature *last_sig = ctx->sig;
       if (last_sig == NULL) {
@@ -1061,7 +1097,7 @@ int pdkim_header_complete(pdkim_ctx *ctx) {
 
 /* -------------------------------------------------------------------------- */
 #define HEADER_BUFFER_FRAG_SIZE 256
-int pdkim_feed (pdkim_ctx *ctx,
+DLLEXPORT int pdkim_feed (pdkim_ctx *ctx,
                 char *data,
                 int   len) {
   int p;
@@ -1169,7 +1205,7 @@ char *pdkim_create_header(pdkim_signature *sig, int final) {
         goto BAIL;
       }
     }
-    if (sig->bodylength > 0) {
+    if (sig->bodylength >= 0) {
       if (!( pdkim_strcat(hdr,"l=")                             &&
              pdkim_numcat(hdr,sig->bodylength)                  &&
              pdkim_strcat(hdr,";") ) ) {
@@ -1209,7 +1245,7 @@ char *pdkim_create_header(pdkim_signature *sig, int final) {
 
 
 /* -------------------------------------------------------------------------- */
-int pdkim_feed_finish(pdkim_ctx *ctx, char **signature) {
+DLLEXPORT int pdkim_feed_finish(pdkim_ctx *ctx, pdkim_signature **return_signatures) {
   pdkim_signature *sig = ctx->sig;
   pdkim_str *headernames = NULL;             /* Collected signed header names */
 
@@ -1277,7 +1313,7 @@ int pdkim_feed_finish(pdkim_ctx *ctx, char **signature) {
       }
       /* ---------------------------------------------------------------------- */
 
-      if (sig->canon_body == PDKIM_CANON_RELAXED)
+      if (sig->canon_headers == PDKIM_CANON_RELAXED)
         rh = pdkim_relax_header(p->value,1); /* cook header for relaxed canon */
       else
         rh = strdup(p->value);               /* just copy it for simple canon */
@@ -1343,10 +1379,22 @@ int pdkim_feed_finish(pdkim_ctx *ctx, char **signature) {
     if (sig->algo == PDKIM_ALGO_RSA_SHA1) {
       sha1_update(&(sha1_headers),(unsigned char *)sig_hdr,strlen(sig_hdr));
       sha1_finish(&(sha1_headers),(unsigned char *)headerhash);
+      #ifdef PDKIM_DEBUG
+      if (ctx->debug_stream) {
+        fprintf(ctx->debug_stream, "PDKIM [%s] hh computed: ", sig->domain);
+        pdkim_hexprint(ctx->debug_stream, headerhash, 20, 1);
+      }
+      #endif
     }
     else {
       sha2_update(&(sha2_headers),(unsigned char *)sig_hdr,strlen(sig_hdr));
       sha2_finish(&(sha2_headers),(unsigned char *)headerhash);
+      #ifdef PDKIM_DEBUG
+      if (ctx->debug_stream) {
+        fprintf(ctx->debug_stream, "PDKIM [%s] hh computed: ", sig->domain);
+        pdkim_hexprint(ctx->debug_stream, headerhash, 32, 1);
+      }
+      #endif
     }
 
     free(sig_hdr);
@@ -1386,11 +1434,8 @@ int pdkim_feed_finish(pdkim_ctx *ctx, char **signature) {
       }
       #endif
 
-      /* Recreate signature header with b= included, return it to the caller */
-      if (signature != NULL) {
-        *signature = pdkim_create_header(ctx->sig,1);
-        if (*signature == NULL) return PDKIM_ERR_OOM;
-      }
+      sig->signature_header = pdkim_create_header(ctx->sig,1);
+      if (sig->signature_header == NULL) return PDKIM_ERR_OOM;
     }
     /* VERIFICATION ----------------------------------------------------------- */
     else {
@@ -1500,12 +1545,17 @@ int pdkim_feed_finish(pdkim_ctx *ctx, char **signature) {
     sig = sig->next;
   }
 
+  /* If requested, set return pointer to signature(s) */
+  if (return_signatures != NULL) {
+    *return_signatures = ctx->sig;
+  }
+
   return PDKIM_OK;
 }
 
 
 /* -------------------------------------------------------------------------- */
-pdkim_ctx *pdkim_init_verify(int input_mode,
+DLLEXPORT pdkim_ctx *pdkim_init_verify(int input_mode,
                              int(*dns_txt_callback)(char *, char *)
                              ) {
   pdkim_ctx *ctx = malloc(sizeof(pdkim_ctx));
@@ -1527,11 +1577,12 @@ pdkim_ctx *pdkim_init_verify(int input_mode,
 
 
 /* -------------------------------------------------------------------------- */
-pdkim_ctx *pdkim_init_sign(int input_mode,
+DLLEXPORT pdkim_ctx *pdkim_init_sign(int input_mode,
                            char *domain,
                            char *selector,
                            char *rsa_privkey) {
   pdkim_ctx *ctx;
+  pdkim_signature *sig;
 
   if (!domain || !selector || !rsa_privkey) return NULL;
 
@@ -1545,32 +1596,40 @@ pdkim_ctx *pdkim_init_sign(int input_mode,
     return NULL;
   }
 
-  pdkim_signature *sig = malloc(sizeof(pdkim_signature));
+  sig = malloc(sizeof(pdkim_signature));
   if (sig == NULL) {
     free(ctx->linebuf);
     free(ctx);
     return NULL;
   }
   memset(sig,0,sizeof(pdkim_signature));
+  sig->bodylength = -1;
 
   ctx->mode = PDKIM_MODE_SIGN;
   ctx->input_mode = input_mode;
   ctx->sig = sig;
 
-  ctx->sig->domain = malloc(strlen(domain)+1);
-  ctx->sig->selector = malloc(strlen(selector)+1);
-  ctx->sig->rsa_privkey = malloc(strlen(rsa_privkey)+1);
+  ctx->sig->domain = strdup(domain);
+  ctx->sig->selector = strdup(selector);
+  ctx->sig->rsa_privkey = strdup(rsa_privkey);
 
   if (!ctx->sig->domain || !ctx->sig->selector || !ctx->sig->rsa_privkey) {
     pdkim_free_ctx(ctx);
     return NULL;
   }
 
-  strcpy(ctx->sig->domain, domain);
-  strcpy(ctx->sig->selector, selector);
-  strcpy(ctx->sig->rsa_privkey, rsa_privkey);
-
+  ctx->sig->sha1_body = malloc(sizeof(sha1_context));
+  if (ctx->sig->sha1_body == NULL) {
+    pdkim_free_ctx(ctx);
+    return NULL;
+  }
   sha1_starts(ctx->sig->sha1_body);
+
+  ctx->sig->sha2_body = malloc(sizeof(sha2_context));
+  if (ctx->sig->sha2_body == NULL) {
+    pdkim_free_ctx(ctx);
+    return NULL;
+  }
   sha2_starts(ctx->sig->sha2_body,0);
 
   return ctx;
@@ -1578,37 +1637,31 @@ pdkim_ctx *pdkim_init_sign(int input_mode,
 
 #ifdef PDKIM_DEBUG
 /* -------------------------------------------------------------------------- */
-void pdkim_set_debug_stream(pdkim_ctx *ctx,
+DLLEXPORT void pdkim_set_debug_stream(pdkim_ctx *ctx,
                             FILE *debug_stream) {
   ctx->debug_stream = debug_stream;
 };
 #endif
 
 /* -------------------------------------------------------------------------- */
-int pdkim_set_optional(pdkim_ctx *ctx,
+DLLEXPORT int pdkim_set_optional(pdkim_ctx *ctx,
                        char *sign_headers,
                        char *identity,
                        int canon_headers,
                        int canon_body,
-                       unsigned long bodylength,
+                       long bodylength,
                        int algo,
                        unsigned long created,
                        unsigned long expires) {
 
   if (identity != NULL) {
-    ctx->sig->identity = malloc(strlen(identity)+1);
-    if (!ctx->sig->identity) {
-      return PDKIM_ERR_OOM;
-    }
-    strcpy(ctx->sig->identity, identity);
+    ctx->sig->identity = strdup(identity);
+    if (ctx->sig->identity == NULL) return PDKIM_ERR_OOM;
   }
 
   if (sign_headers != NULL) {
-    ctx->sig->sign_headers = malloc(strlen(sign_headers)+1);
-    if (!ctx->sig->sign_headers) {
-      return PDKIM_ERR_OOM;
-    }
-    strcpy(ctx->sig->sign_headers, sign_headers);
+    ctx->sig->sign_headers = strdup(sign_headers);
+    if (ctx->sig->sign_headers == NULL) return PDKIM_ERR_OOM;
   }
 
   ctx->sig->canon_headers = canon_headers;