Fix failed verification when header canon != body canon
[users/jgh/exim.git] / src / src / pdkim / pdkim.c
index c70b5d4f4e77195a7faebabb6b2527184294f882..35c6ed9b5826a082a6b66d4970f8455d8cebc967 100644 (file)
@@ -20,7 +20,7 @@
  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-/* $Cambridge: exim/src/src/pdkim/pdkim.c,v 1.1.2.9 2009/04/09 07:49:11 tom Exp $ */
+/* $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>
@@ -270,6 +270,7 @@ void pdkim_free_sig(pdkim_signature *sig) {
     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);
 
@@ -295,21 +296,24 @@ DLLEXPORT 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 = strdup(list);
+
+  /* Copy tick-off list locally, so we can punch zeroes into it */
+  lcopy = strdup(tick);
   if (lcopy == NULL) {
     free(hname);
     return PDKIM_ERR_OOM;
@@ -318,20 +322,24 @@ int header_name_match(char *header,
   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);
@@ -498,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') )
@@ -539,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
@@ -640,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;
@@ -659,6 +668,9 @@ 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--;
@@ -710,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') )
@@ -744,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
@@ -797,6 +809,7 @@ pdkim_pubkey *pdkim_parse_pubkey_record(pdkim_ctx *ctx, char *raw_record) {
     }
 
     NEXT_CHAR:
+    if (*p == '\0') break;
     p++;
   }
 
@@ -1022,16 +1035,13 @@ int pdkim_header_complete(pdkim_ctx *ctx) {
       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 */
@@ -1303,7 +1313,7 @@ DLLEXPORT int pdkim_feed_finish(pdkim_ctx *ctx, pdkim_signature **return_signatu
       }
       /* ---------------------------------------------------------------------- */
 
-      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 */
@@ -1369,14 +1379,22 @@ DLLEXPORT int pdkim_feed_finish(pdkim_ctx *ctx, pdkim_signature **return_signatu
     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);
-      fprintf(ctx->debug_stream, "PDKIM [%s] hh computed: ", sig->domain);
-      pdkim_hexprint(ctx->debug_stream, headerhash, 20, 1);
+      #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);
-      fprintf(ctx->debug_stream, "PDKIM [%s] hh computed: ", sig->domain);
-      pdkim_hexprint(ctx->debug_stream, headerhash, 32, 1);
+      #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);