wip
[users/jgh/exim.git] / src / src / pdkim / pdkim.c
index c701b98543753bcb340034e5de500d698a064017..da82fb7c873b4396e750c9d1c957886602c77eff 100644 (file)
@@ -1,14 +1,67 @@
-/* $Cambridge: exim/src/src/pdkim/pdkim.c,v 1.1.2.4 2009/02/26 16:07:36 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.12 2009/04/09 19:18:11 tom Exp $ */
 
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
-#include <strings.h>
 #include <ctype.h>
-#include <unistd.h>
+
 #include "pdkim.h"
 
+#include "sha1.h"
+#include "sha2.h"
+#include "rsa.h"
+#include "base64.h"
+
+#define PDKIM_SIGNATURE_VERSION     "1"
+#define PDKIM_PUB_RECORD_VERSION    "DKIM1"
+
+#define PDKIM_MAX_HEADER_LEN        65536
+#define PDKIM_MAX_HEADERS           512
+#define PDKIM_MAX_BODY_LINE_LEN     1024
+#define PDKIM_DNS_TXT_MAX_NAMELEN   1024
+#define PDKIM_DEFAULT_SIGN_HEADERS "From:Sender:Reply-To:Subject:Date:"\
+                             "Message-ID:To:Cc:MIME-Version:Content-Type:"\
+                             "Content-Transfer-Encoding:Content-ID:"\
+                             "Content-Description:Resent-Date:Resent-From:"\
+                             "Resent-Sender:Resent-To:Resent-Cc:"\
+                             "Resent-Message-ID:In-Reply-To:References:"\
+                             "List-Id:List-Help:List-Unsubscribe:"\
+                             "List-Subscribe:List-Post:List-Owner:List-Archive"
+
+/* -------------------------------------------------------------------------- */
+struct pdkim_stringlist {
+  char *value;
+  void *next;
+};
+
+#define PDKIM_STR_ALLOC_FRAG 256
+struct pdkim_str {
+  char         *str;
+  unsigned int  len;
+  unsigned int  allocated;
+};
 
 /* -------------------------------------------------------------------------- */
 /* A bunch of list constants */
@@ -26,6 +79,15 @@ char *pdkim_canons[] = {
   "relaxed",
   NULL
 };
+char *pdkim_hashes[] = {
+  "sha256",
+  "sha1",
+  NULL
+};
+char *pdkim_keytypes[] = {
+  "rsa",
+  NULL
+};
 
 typedef struct pdkim_combined_canon_entry {
   char *str;
@@ -44,7 +106,7 @@ pdkim_combined_canon_entry pdkim_combined_canons[] = {
 
 
 /* -------------------------------------------------------------------------- */
-/* Various debugging functions */
+/* Print debugging functions */
 #ifdef PDKIM_DEBUG
 void pdkim_quoteprint(FILE *stream, char *data, int len, int lf) {
   int i;
@@ -70,6 +132,17 @@ void pdkim_quoteprint(FILE *stream, char *data, int len, int lf) {
   if (lf)
     fputc('\n',stream);
 }
+void pdkim_hexprint(FILE *stream, char *data, int len, int lf) {
+  int i;
+  unsigned char *p = (unsigned char *)data;
+
+  for (i=0;i<len;i++) {
+    int c = p[i];
+    fprintf(stream,"%02x",c);
+  }
+  if (lf)
+    fputc('\n',stream);
+}
 #endif
 
 
@@ -79,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; };
@@ -109,9 +181,6 @@ pdkim_str *pdkim_strnew (char *cstr) {
   if (cstr) strcpy(p->str,cstr);
   return p;
 };
-char *pdkim_strcat(pdkim_str *str, char *cstr) {
-  return pdkim_strncat(str, cstr, strlen(cstr));
-};
 char *pdkim_strncat(pdkim_str *str, char *data, int len) {
   if ((str->allocated - str->len) < (len+1)) {
     /* Extend the buffer */
@@ -127,6 +196,9 @@ char *pdkim_strncat(pdkim_str *str, char *data, int len) {
   str->str[str->len] = '\0';
   return str->str;
 };
+char *pdkim_strcat(pdkim_str *str, char *cstr) {
+  return pdkim_strncat(str, cstr, strlen(cstr));
+};
 char *pdkim_numcat(pdkim_str *str, unsigned long num) {
   char minibuf[20];
   snprintf(minibuf,20,"%lu",num);
@@ -157,16 +229,79 @@ void pdkim_strfree(pdkim_str *str) {
 };
 
 
+
+/* -------------------------------------------------------------------------- */
+void pdkim_free_pubkey(pdkim_pubkey *pub) {
+  if (pub) {
+    if (pub->version        != NULL) free(pub->version);
+    if (pub->granularity    != NULL) free(pub->granularity);
+    if (pub->hashes         != NULL) free(pub->hashes);
+    if (pub->keytype        != NULL) free(pub->keytype);
+    if (pub->srvtype        != NULL) free(pub->srvtype);
+    if (pub->notes          != NULL) free(pub->notes);
+    if (pub->key            != NULL) free(pub->key);
+    free(pub);
+  }
+}
+
+
+/* -------------------------------------------------------------------------- */
+void pdkim_free_sig(pdkim_signature *sig) {
+  if (sig) {
+    pdkim_signature *next = (pdkim_signature *)sig->next;
+
+    pdkim_stringlist *e = sig->headers;
+    while(e != NULL) {
+      pdkim_stringlist *c = e;
+      if (e->value != NULL) free(e->value);
+      e = e->next;
+      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->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->pubkey != NULL) pdkim_free_pubkey(sig->pubkey);
+
+    free(sig);
+    if (next != NULL) pdkim_free_sig(next);
+  }
+};
+
+
+/* -------------------------------------------------------------------------- */
+DLLEXPORT void pdkim_free_ctx(pdkim_ctx *ctx) {
+  if (ctx) {
+    pdkim_free_sig(ctx->sig);
+    pdkim_strfree(ctx->cur_header);
+    free(ctx);
+  }
+};
+
+
 /* -------------------------------------------------------------------------- */
 /* Matches the name of the passed raw "header" against
-   the passed colon-separated "list". Case-insensitive.
-   Returns '0' for a match. */
+   the passed colon-separated "list", starting at entry
+   "start". Returns the position of the header name in
+   the list. */
 int header_name_match(char *header,
-                      char *list) {
+                      char *list,
+                      int   start) {
   char *hname;
   char *lcopy;
   char *p;
   char *q;
+  int pos = 0;
   int rc = PDKIM_FAIL;
   char *hcolon = strchr(header,':');
   if (hcolon == NULL) return rc; /* This isn't a header */
@@ -174,24 +309,29 @@ int header_name_match(char *header,
   if (hname == NULL) return PDKIM_ERR_OOM;
   memset(hname,0,(hcolon-header)+1);
   strncpy(hname,header,(hcolon-header));
-  lcopy = malloc(strlen(list)+1);
+  lcopy = strdup(list);
   if (lcopy == NULL) {
     free(hname);
     return PDKIM_ERR_OOM;
   }
-  strcpy(lcopy,list);
   p = lcopy;
   q = strchr(p,':');
   while (q != NULL) {
     *q = '\0';
-    if (strcasecmp(p,hname) == 0) {
-      rc = PDKIM_OK;
-      goto BAIL;
+    if (pos >= start) {
+      if (strcasecmp(p,hname) == 0) {
+        rc = pos;
+        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;
   BAIL:
   free(hname);
   free(lcopy);
@@ -299,14 +439,30 @@ char *pdkim_decode_qp(char *str) {
 
 
 /* -------------------------------------------------------------------------- */
-char *pdkim_decode_base64(char *str) {
+char *pdkim_decode_base64(char *str, int *num_decoded) {
+  int dlen = 0;
+  char *res;
+
+  base64_decode(NULL, &dlen, (unsigned char *)str, strlen(str));
+  res = malloc(dlen+1);
+  if (res == NULL) return NULL;
+  if (base64_decode((unsigned char *)res,&dlen,(unsigned char *)str,strlen(str)) != 0) {
+    free(res);
+    return NULL;
+  }
+  if (num_decoded != NULL) *num_decoded = dlen;
+  return res;
+}
+
+/* -------------------------------------------------------------------------- */
+char *pdkim_encode_base64(char *str, int num) {
   int dlen = 0;
   char *res;
 
-  base64_decode(NULL, &dlen, str, strlen(str));
+  base64_encode(NULL, &dlen, (unsigned char *)str, num);
   res = malloc(dlen+1);
   if (res == NULL) return NULL;
-  if (base64_decode(res,&dlen,str,strlen(str)) != 0) {
+  if (base64_encode((unsigned char *)res,&dlen,(unsigned char *)str,num) != 0) {
     free(res);
     return NULL;
   }
@@ -320,7 +476,6 @@ char *pdkim_decode_base64(char *str) {
 #define PDKIM_HDR_VALUE 2
 pdkim_signature *pdkim_parse_sig_header(pdkim_ctx *ctx, char *raw_hdr) {
   pdkim_signature *sig ;
-  char *rawsig_no_b_val;
   char *p,*q;
   pdkim_str *cur_tag = NULL;
   pdkim_str *cur_val = NULL;
@@ -332,6 +487,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) {
@@ -342,7 +498,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') )
@@ -383,10 +539,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
@@ -397,10 +553,10 @@ pdkim_signature *pdkim_parse_sig_header(pdkim_ctx *ctx, char *raw_hdr) {
             case 'b':
               switch (cur_tag->str[1]) {
                 case 'h':
-                  sig->bodyhash = pdkim_decode_base64(cur_val->str);
+                  sig->bodyhash = pdkim_decode_base64(cur_val->str,&(sig->bodyhash_len));
                 break;
                 default:
-                  sig->sigdata = pdkim_decode_base64(cur_val->str);
+                  sig->sigdata = pdkim_decode_base64(cur_val->str,&(sig->sigdata_len));
                 break;
               }
             break;
@@ -443,14 +599,10 @@ pdkim_signature *pdkim_parse_sig_header(pdkim_ctx *ctx, char *raw_hdr) {
               }
             break;
             case 's':
-              sig->selector = malloc(strlen(cur_val->str)+1);
-              if (sig->selector == NULL) break;
-              strcpy(sig->selector, cur_val->str);
+              sig->selector = strdup(cur_val->str);
             break;
             case 'd':
-              sig->domain = malloc(strlen(cur_val->str)+1);
-              if (sig->domain == NULL) break;
-              strcpy(sig->domain, cur_val->str);
+              sig->domain = strdup(cur_val->str);
             break;
             case 'i':
               sig->identity = pdkim_decode_qp(cur_val->str);
@@ -462,12 +614,10 @@ 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 = malloc(strlen(cur_val->str)+1);
-              if (sig->headernames == NULL) break;
-              strcpy(sig->headernames, cur_val->str);
+              sig->headernames = strdup(cur_val->str);
             break;
             case 'z':
               sig->copiedheaders = pdkim_decode_qp(cur_val->str);
@@ -490,6 +640,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;
@@ -502,12 +653,20 @@ pdkim_signature *pdkim_parse_sig_header(pdkim_ctx *ctx, char *raw_hdr) {
   if (!(sig->domain      && (*(sig->domain)      != '\0') &&
         sig->selector    && (*(sig->selector)    != '\0') &&
         sig->headernames && (*(sig->headernames) != '\0') &&
+        sig->bodyhash    &&
+        sig->sigdata     &&
         sig->version)) {
-    pdkim_free_signature(sig);
+    pdkim_free_sig(sig);
     return NULL;
   }
 
   *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,
@@ -519,10 +678,145 @@ pdkim_signature *pdkim_parse_sig_header(pdkim_ctx *ctx, char *raw_hdr) {
             "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
   }
   #endif
+
+  sig->sha1_body = malloc(sizeof(sha1_context));
+  if (sig->sha1_body == NULL) {
+    pdkim_free_sig(sig);
+    return NULL;
+  }
+  sig->sha2_body = malloc(sizeof(sha2_context));
+  if (sig->sha2_body == NULL) {
+    pdkim_free_sig(sig);
+    return NULL;
+  }
+
+  sha1_starts(sig->sha1_body);
+  sha2_starts(sig->sha2_body,0);
+
   return sig;
 }
 
 
+/* -------------------------------------------------------------------------- */
+pdkim_pubkey *pdkim_parse_pubkey_record(pdkim_ctx *ctx, char *raw_record) {
+  pdkim_pubkey *pub ;
+  char *p;
+  pdkim_str *cur_tag = NULL;
+  pdkim_str *cur_val = NULL;
+  int where = PDKIM_HDR_LIMBO;
+
+  pub = malloc(sizeof(pdkim_pubkey));
+  if (pub == NULL) return NULL;
+  memset(pub,0,sizeof(pdkim_pubkey));
+
+  p = raw_record;
+
+  while (1) {
+
+    /* Ignore FWS */
+    if ( (*p == '\r') || (*p == '\n') )
+      goto NEXT_CHAR;
+
+    if (where == PDKIM_HDR_LIMBO) {
+      /* In limbo, just wait for a tag-char to appear */
+      if (!((*p >= 'a') && (*p <= 'z')))
+        goto NEXT_CHAR;
+
+      where = PDKIM_HDR_TAG;
+    }
+
+    if (where == PDKIM_HDR_TAG) {
+      if (cur_tag == NULL)
+        cur_tag = pdkim_strnew(NULL);
+
+      if ((*p >= 'a') && (*p <= 'z'))
+        pdkim_strncat(cur_tag,p,1);
+
+      if (*p == '=') {
+        where = PDKIM_HDR_VALUE;
+        goto NEXT_CHAR;
+      }
+    }
+
+    if (where == PDKIM_HDR_VALUE) {
+      if (cur_val == NULL)
+        cur_val = pdkim_strnew(NULL);
+
+      if ( (*p == '\r') || (*p == '\n') )
+        goto NEXT_CHAR;
+
+      if ( (*p == ';') || (*p == '\0') ) {
+        if (cur_tag->len > 0) {
+          pdkim_strtrim(cur_val);
+          #ifdef PDKIM_DEBUG
+          if (ctx->debug_stream)
+            fprintf(ctx->debug_stream, "%s=%s\n", cur_tag->str, cur_val->str);
+          #endif
+          switch (cur_tag->str[0]) {
+            case 'v':
+              /* This tag isn't evaluated because:
+                 - We only support version DKIM1.
+                 - Which is the default for this value (set below)
+                 - Other versions are currently not specified.      */
+            break;
+            case 'h':
+              pub->hashes = strdup(cur_val->str);
+            break;
+            case 'g':
+              pub->granularity = strdup(cur_val->str);
+            break;
+            case 'n':
+              pub->notes = pdkim_decode_qp(cur_val->str);
+            break;
+            case 'p':
+              pub->key = pdkim_decode_base64(cur_val->str,&(pub->key_len));
+            break;
+            case 'k':
+              pub->hashes = strdup(cur_val->str);
+            break;
+            case 's':
+              pub->srvtype = strdup(cur_val->str);
+            break;
+            case 't':
+              if (strchr(cur_val->str,'t') != NULL) pub->testing = 1;
+              if (strchr(cur_val->str,'s') != NULL) pub->no_subdomaining = 1;
+            break;
+            default:
+              #ifdef PDKIM_DEBUG
+              if (ctx->debug_stream)
+                fprintf(ctx->debug_stream, "Unknown tag encountered\n");
+              #endif
+            break;
+          }
+        }
+        pdkim_strclear(cur_tag);
+        pdkim_strclear(cur_val);
+        where = PDKIM_HDR_LIMBO;
+        goto NEXT_CHAR;
+      }
+      else pdkim_strncat(cur_val,p,1);
+    }
+
+    NEXT_CHAR:
+    if (*p == '\0') break;
+    p++;
+  }
+
+  /* Set fallback defaults */
+  if (pub->version     == NULL) pub->version     = strdup(PDKIM_PUB_RECORD_VERSION);
+  if (pub->granularity == NULL) pub->granularity = strdup("*");
+  if (pub->keytype     == NULL) pub->keytype     = strdup("rsa");
+  if (pub->srvtype     == NULL) pub->srvtype     = strdup("*");
+
+  /* p= is required */
+  if (pub->key == NULL) {
+    pdkim_free_pubkey(pub);
+    return NULL;
+  }
+
+  return pub;
+}
+
 
 /* -------------------------------------------------------------------------- */
 int pdkim_update_bodyhash(pdkim_ctx *ctx, char *data, int len) {
@@ -567,15 +861,15 @@ 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);
 
     if (canon_len > 0) {
       if (sig->algo == PDKIM_ALGO_RSA_SHA1)
-        sha1_update(&(sig->sha1_body),(unsigned char *)canon_data,canon_len);
+        sha1_update(sig->sha1_body,(unsigned char *)canon_data,canon_len);
       else
-        sha2_update(&(sig->sha2_body),(unsigned char *)canon_data,canon_len);
+        sha2_update(sig->sha2_body,(unsigned char *)canon_data,canon_len);
       sig->signed_body_bytes += canon_len;
 #ifdef PDKIM_DEBUG
       if (ctx->debug_stream!=NULL)
@@ -598,48 +892,60 @@ int pdkim_finish_bodyhash(pdkim_ctx *ctx) {
   /* Traverse all signatures */
   while (sig != NULL) {
 
-#ifdef PDKIM_DEBUG
-    if (ctx->debug_stream)
-      fprintf(ctx->debug_stream, "PDKIM [%s] Body bytes hashed: %lu\n",
-              sig->domain, sig->signed_body_bytes);
-#endif
-
     /* Finish hashes */
     unsigned char bh[32]; /* SHA-256 = 32 Bytes,  SHA-1 = 20 Bytes */
     if (sig->algo == PDKIM_ALGO_RSA_SHA1)
-      sha1_finish(&(sig->sha1_body),bh);
+      sha1_finish(sig->sha1_body,bh);
     else
-      sha2_finish(&(sig->sha2_body),bh);
+      sha2_finish(sig->sha2_body,bh);
+
+    #ifdef PDKIM_DEBUG
+    if (ctx->debug_stream) {
+      fprintf(ctx->debug_stream, "PDKIM [%s] Body bytes hashed: %lu\n",
+        sig->domain, sig->signed_body_bytes);
+      fprintf(ctx->debug_stream, "PDKIM [%s] bh  computed: ", sig->domain);
+      pdkim_hexprint(ctx->debug_stream, (char *)bh,
+                     (sig->algo == PDKIM_ALGO_RSA_SHA1)?20:32,1);
+    }
+    #endif
 
     /* SIGNING -------------------------------------------------------------- */
     if (ctx->mode == PDKIM_MODE_SIGN) {
-
-      /* Build base64 version of body hash and place it in the sig struct */
-      int slen = (sig->algo == PDKIM_ALGO_RSA_SHA1)?20:32;
-      int dlen = 0;
-      base64_encode(NULL,&dlen,bh,slen); /* Puts needed length in dlen */
-      sig->bodyhash = malloc(dlen+1);
+      sig->bodyhash_len = (sig->algo == PDKIM_ALGO_RSA_SHA1)?20:32;
+      sig->bodyhash = malloc(sig->bodyhash_len);
       if (sig->bodyhash == NULL) return PDKIM_ERR_OOM;
-      if (base64_encode((unsigned char *)sig->bodyhash,&dlen,bh,slen) == 0) {
-        sig->bodyhash[dlen] = '\0';
-#ifdef PDKIM_DEBUG
-        if (ctx->debug_stream)
-          fprintf(ctx->debug_stream, "PDKIM [%s] body hash: %s\n",
-                  sig->domain, sig->bodyhash);
-#endif
-        return PDKIM_OK;
-      }
+      memcpy(sig->bodyhash,bh,sig->bodyhash_len);
 
       /* 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 {
-
+      /* Compare bodyhash */
+      if (memcmp(bh,sig->bodyhash,
+                 (sig->algo == PDKIM_ALGO_RSA_SHA1)?20:32) == 0) {
+        #ifdef PDKIM_DEBUG
+        if (ctx->debug_stream)
+          fprintf(ctx->debug_stream, "PDKIM [%s] Body hash verified OK\n",
+                  sig->domain);
+        #endif
+      }
+      else {
+        #ifdef PDKIM_DEBUG
+        if (ctx->debug_stream) {
+          fprintf(ctx->debug_stream, "PDKIM [%s] Body hash did NOT verify\n",
+                  sig->domain);
+          fprintf(ctx->debug_stream, "PDKIM [%s] bh signature: ", sig->domain);
+          pdkim_hexprint(ctx->debug_stream, sig->bodyhash,
+                           (sig->algo == PDKIM_ALGO_RSA_SHA1)?20:32,1);
+        }
+        #endif
+        sig->verify_status     = PDKIM_VERIFY_FAIL;
+        sig->verify_ext_status = PDKIM_VERIFY_FAIL_BODY;
+      }
     }
 
-
     sig = sig->next;
   }
 
@@ -706,25 +1012,37 @@ int pdkim_header_complete(pdkim_ctx *ctx) {
     ctx->cur_header->len--;
   }
 
+  ctx->num_headers++;
+  if (ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
+
   /* 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) {
-        pdkim_stringlist *list = pdkim_append_stringlist(sig->headers,
-                                                         ctx->cur_header->str);
-        if (list == NULL) return PDKIM_ERR_OOM;
-        sig->headers = list;
-      }
+                            sig->sign_headers?
+                              sig->sign_headers:
+                              PDKIM_DEFAULT_SIGN_HEADERS, 0) < 0) 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;
     }
 
+    /* Add header to the signed headers list */
+    list = pdkim_append_stringlist(sig->headers,
+                                   ctx->cur_header->str);
+    if (list == NULL) return PDKIM_ERR_OOM;
+    sig->headers = list;
+
+    NEXT_SIG:
     sig = sig->next;
   }
 
@@ -733,13 +1051,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) {
@@ -757,10 +1076,11 @@ int pdkim_header_complete(pdkim_ctx *ctx) {
         fprintf(ctx->debug_stream,
           "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
       }
-    #endif
+      #endif
     }
   }
 
+  BAIL:
   pdkim_strclear(ctx->cur_header); /* Re-use existing pdkim_str */
   return PDKIM_OK;
 };
@@ -769,7 +1089,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;
@@ -815,8 +1135,9 @@ int pdkim_feed (pdkim_ctx *ctx,
         ctx->cur_header = pdkim_strnew(NULL);
         if (ctx->cur_header == NULL) return PDKIM_ERR_OOM;
       }
-      if (pdkim_strncat(ctx->cur_header,&data[p],1) == NULL)
-        return PDKIM_ERR_OOM;
+      if (ctx->cur_header->len < PDKIM_MAX_HEADER_LEN)
+        if (pdkim_strncat(ctx->cur_header,&data[p],1) == NULL)
+          return PDKIM_ERR_OOM;
     }
   }
   return PDKIM_OK;
@@ -824,10 +1145,16 @@ int pdkim_feed (pdkim_ctx *ctx,
 
 
 /* -------------------------------------------------------------------------- */
-pdkim_str *pdkim_create_header(pdkim_signature *sig, int final) {
-
+char *pdkim_create_header(pdkim_signature *sig, int final) {
+  char *rc = NULL;
+  char *base64_bh = NULL;
+  char *base64_b  = NULL;
   pdkim_str *hdr = pdkim_strnew("DKIM-Signature: v="PDKIM_SIGNATURE_VERSION);
   if (hdr == NULL) return NULL;
+
+  base64_bh = pdkim_encode_base64(sig->bodyhash, sig->bodyhash_len);
+  if (base64_bh == NULL) goto BAIL;
+
   /* Required and static bits */
   if (
         pdkim_strcat(hdr,"; a=")                                &&
@@ -845,7 +1172,7 @@ pdkim_str *pdkim_create_header(pdkim_signature *sig, int final) {
         pdkim_strcat(hdr,";\r\n\th=")                           &&
         pdkim_strcat(hdr,sig->headernames)                      &&
         pdkim_strcat(hdr,"; bh=")                               &&
-        pdkim_strcat(hdr,sig->bodyhash)                         &&
+        pdkim_strcat(hdr,base64_bh)                             &&
         pdkim_strcat(hdr,";\r\n\t")
      ) {
     /* Optional bits */
@@ -853,52 +1180,66 @@ pdkim_str *pdkim_create_header(pdkim_signature *sig, int final) {
       if (!( pdkim_strcat(hdr,"i=")                             &&
              pdkim_strcat(hdr,sig->identity)                    &&
              pdkim_strcat(hdr,";") ) ) {
-        return NULL;
+        goto BAIL;
       }
     }
     if (sig->created > 0) {
       if (!( pdkim_strcat(hdr,"t=")                             &&
              pdkim_numcat(hdr,sig->created)                     &&
              pdkim_strcat(hdr,";") ) ) {
-        return NULL;
+        goto BAIL;
       }
     }
     if (sig->expires > 0) {
       if (!( pdkim_strcat(hdr,"x=")                             &&
              pdkim_numcat(hdr,sig->expires)                     &&
              pdkim_strcat(hdr,";") ) ) {
-        return NULL;
+        goto BAIL;
       }
     }
-    if (sig->bodylength > 0) {
+    if (sig->bodylength >= 0) {
       if (!( pdkim_strcat(hdr,"l=")                             &&
              pdkim_numcat(hdr,sig->bodylength)                  &&
              pdkim_strcat(hdr,";") ) ) {
-        return NULL;
+        goto BAIL;
       }
     }
     /* Extra linebreak */
     if (hdr->str[(hdr->len)-1] == ';') {
-      if (!pdkim_strcat(hdr," \r\n\t")) return NULL;
+      if (!pdkim_strcat(hdr," \r\n\t")) goto BAIL;
     }
     /* Preliminary or final version? */
     if (final) {
+      base64_b = pdkim_encode_base64(sig->sigdata, sig->sigdata_len);
+      if (base64_b == NULL) goto BAIL;
       if (
             pdkim_strcat(hdr,"b=")                              &&
-            pdkim_strcat(hdr,sig->sigdata)                      &&
+            pdkim_strcat(hdr,base64_b)                          &&
             pdkim_strcat(hdr,";")
-         ) return hdr;
+         ) goto DONE;
     }
     else {
-      if (pdkim_strcat(hdr,"b=;")) return hdr;
+      if (pdkim_strcat(hdr,"b=;")) goto DONE;
     }
+
+    goto BAIL;
   }
-  return NULL;
+
+  DONE:
+  rc = strdup(hdr->str);
+
+  BAIL:
+  pdkim_strfree(hdr);
+  if (base64_bh != NULL) free(base64_bh);
+  if (base64_b  != NULL) free(base64_b);
+  return rc;
 }
 
 
 /* -------------------------------------------------------------------------- */
-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 */
 
   /* Check if we must still flush a (partial) header. If that is the
      case, the message has no body, and we must compute a body hash
@@ -916,273 +1257,405 @@ int pdkim_feed_finish(pdkim_ctx *ctx, char **signature) {
       pdkim_update_bodyhash(ctx, ctx->linebuf, ctx->linebuf_offset);
       pdkim_update_bodyhash(ctx,"\r\n",2);
     }
-#ifdef PDKIM_DEBUG
+    #ifdef PDKIM_DEBUG
     if (ctx->debug_stream)
       fprintf(ctx->debug_stream,
         "\nPDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-#endif
+    #endif
   }
 
+  /* Build (and/or evaluate) body hash */
   if (pdkim_finish_bodyhash(ctx) != PDKIM_OK) return PDKIM_ERR_OOM;
 
-  /* SIGNING ---------------------------------------------------------------- */
+  /* SIGNING -------------------------------------------------------------- */
   if (ctx->mode == PDKIM_MODE_SIGN) {
-    pdkim_stringlist *p;
-    pdkim_str *headernames;
-    pdkim_str *hdr;
-    char *canon_signature;
-    unsigned char headerhash[32];
-    char *headerhash_base64;
-    char *rsa_sig;
-    int sigdata_len = 0;
+    headernames = pdkim_strnew(NULL);
+    if (headernames == NULL) return PDKIM_ERR_OOM;
+  }
+  /* ---------------------------------------------------------------------- */
+
+  while (sig != NULL) {
     sha1_context sha1_headers;
     sha2_context sha2_headers;
-    rsa_context rsa;
-    if (ctx->sig->algo == PDKIM_ALGO_RSA_SHA1) sha1_starts(&sha1_headers);
-    else sha2_starts(&sha2_headers,0);
-    /* Run through the accumulated list of to-be-signed headers */
-#ifdef PDKIM_DEBUG
+    pdkim_stringlist *p = sig->headers;
+    char *sig_hdr;
+    char headerhash[32];
+
+    if (sig->algo == PDKIM_ALGO_RSA_SHA1)
+      sha1_starts(&sha1_headers);
+    else
+      sha2_starts(&sha2_headers,0);
+
+    #ifdef PDKIM_DEBUG
     if (ctx->debug_stream)
       fprintf(ctx->debug_stream,
               "PDKIM >> Hashed header data, canonicalized, in sequence >>>>>>>>>>>>>>\n");
-#endif
-    headernames = pdkim_strnew(NULL);
-    p = ctx->sig->headers;
+    #endif
+
     while (p != NULL) {
-      char *rh = p->value;
-      /* Collect header names (Note: colon presence is guaranteed here) */
-      char *q = strchr(p->value,':');
-      if (pdkim_strncat(headernames, p->value,
-                        (q-(p->value))+((p->next==NULL)?0:1)) == NULL)
-        return PDKIM_ERR_OOM;
-      /* Cook the header if using relaxed canon */
-      if (ctx->sig->canon_body == PDKIM_CANON_RELAXED) {
-        rh = pdkim_relax_header(p->value,1);
-        if (rh == NULL) return PDKIM_ERR_OOM;
+      char *rh;
+
+      /* SIGNING -------------------------------------------------------------- */
+      if (ctx->mode == PDKIM_MODE_SIGN) {
+        /* Collect header names (Note: colon presence is guaranteed here) */
+        char *q = strchr(p->value,':');
+        if (pdkim_strncat(headernames, p->value,
+                          (q-(p->value))+((p->next==NULL)?0:1)) == NULL)
+          return PDKIM_ERR_OOM;
       }
+      /* ---------------------------------------------------------------------- */
+
+      if (sig->canon_body == 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 */
+
+      if (rh == NULL) return PDKIM_ERR_OOM;
+
       /* Feed header to the hash algorithm */
-      if (ctx->sig->algo == PDKIM_ALGO_RSA_SHA1)
+      if (sig->algo == PDKIM_ALGO_RSA_SHA1)
         sha1_update(&(sha1_headers),(unsigned char *)rh,strlen(rh));
       else
         sha2_update(&(sha2_headers),(unsigned char *)rh,strlen(rh));
-#ifdef PDKIM_DEBUG
+      #ifdef PDKIM_DEBUG
       if (ctx->debug_stream)
         pdkim_quoteprint(ctx->debug_stream, rh, strlen(rh), 1);
-#endif
+      #endif
+      free(rh);
       p = p->next;
     }
 
-#ifdef PDKIM_DEBUG
+    #ifdef PDKIM_DEBUG
     if (ctx->debug_stream)
       fprintf(ctx->debug_stream,
               "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-#endif
+    #endif
 
-    /* Copy headernames to signature struct */
-    ctx->sig->headernames = malloc((headernames->len)+1);
-    if (ctx->sig->headernames == NULL) return PDKIM_ERR_OOM;
-    strcpy(ctx->sig->headernames, headernames->str);
-    pdkim_strfree(headernames);
-
-    /* Create signature header with b= omitted */
-    hdr = pdkim_create_header(ctx->sig,0);
-    if (hdr == NULL) return PDKIM_ERR_OOM;
-
-    /* If necessary, perform relaxed canon */
-    canon_signature = hdr->str;
-    if (ctx->sig->canon_headers == PDKIM_CANON_RELAXED) {
-      canon_signature = pdkim_relax_header(canon_signature,0);
-      if (canon_signature == NULL) return PDKIM_ERR_OOM;
-    }
 
-#ifdef PDKIM_DEBUG
-  if (ctx->debug_stream) {
-    fprintf(ctx->debug_stream,
-            "PDKIM >> Signed DKIM-Signature header, canonicalized >>>>>>>>>>>>>>>>>\n");
-    pdkim_quoteprint(ctx->debug_stream, canon_signature, strlen(canon_signature), 1);
-    fprintf(ctx->debug_stream,
-            "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-  }
-#endif
+    /* SIGNING ---------------------------------------------------------------- */
+    if (ctx->mode == PDKIM_MODE_SIGN) {
+      /* Copy headernames to signature struct */
+      sig->headernames = strdup(headernames->str);
+      pdkim_strfree(headernames);
 
-    /* Feed preliminary signature header to the hash algorithm */
-    if (ctx->sig->algo == PDKIM_ALGO_RSA_SHA1) {
-      int dlen = 0;
-      sha1_update(&(sha1_headers),(unsigned char *)canon_signature,strlen(canon_signature));
-      sha1_finish(&(sha1_headers),headerhash);
-      base64_encode(NULL,&dlen,headerhash,20);
-      headerhash_base64 = malloc(dlen+1);
-      if (headerhash == NULL) return PDKIM_ERR_OOM;
-      base64_encode((unsigned char *)headerhash_base64,&dlen,headerhash,20);
-      headerhash_base64[dlen] = '\0';
-#ifdef PDKIM_DEBUG
-      if (ctx->debug_stream)
-        fprintf(ctx->debug_stream,
-          "PDKIM SHA1 header hash: %s\n",headerhash_base64);
-#endif
+      /* Create signature header with b= omitted */
+      sig_hdr = pdkim_create_header(ctx->sig,0);
     }
+    /* VERIFICATION ----------------------------------------------------------- */
     else {
-      int dlen = 0;
-      sha2_update(&(sha2_headers),(unsigned char *)canon_signature,strlen(canon_signature));
-      sha2_finish(&(sha2_headers),headerhash);
-      base64_encode(NULL,&dlen,headerhash,32);
-      headerhash_base64 = malloc(dlen+1);
-      if (headerhash == NULL) return PDKIM_ERR_OOM;
-      base64_encode((unsigned char *)headerhash_base64,&dlen,headerhash,32);
-      headerhash_base64[dlen] = '\0';
-#ifdef PDKIM_DEBUG
-      if (ctx->debug_stream)
-        fprintf(ctx->debug_stream,
-          "PDKIM SHA256 header hash: %s\n",headerhash_base64);
-#endif
+      sig_hdr = strdup(sig->rawsig_no_b_val);
     }
+    /* ------------------------------------------------------------------------ */
 
-    if (rsa_parse_key(&rsa, (unsigned char *)ctx->sig->rsa_privkey,
-                      strlen(ctx->sig->rsa_privkey), NULL, 0) != 0) {
-      return PDKIM_ERR_RSA_PRIVKEY;
-    }
+    if (sig_hdr == NULL) return PDKIM_ERR_OOM;
 
-    rsa_sig = malloc(mpi_size(&(rsa.N)));
-    if (rsa_sig == NULL) return PDKIM_ERR_OOM;
-
-    if (rsa_pkcs1_sign( &rsa, RSA_PRIVATE,
-                        ((ctx->sig->algo == PDKIM_ALGO_RSA_SHA1)?
-                           RSA_SHA1
-                           :
-                           RSA_SHA256
-                        ),
-                        0, headerhash, (unsigned char *)rsa_sig ) != 0) {
-      return PDKIM_ERR_RSA_SIGNING;
+    /* Relax header if necessary */
+    if (sig->canon_headers == PDKIM_CANON_RELAXED) {
+      char *relaxed_hdr = pdkim_relax_header(sig_hdr,0);
+      free(sig_hdr);
+      if (relaxed_hdr == NULL) return PDKIM_ERR_OOM;
+      sig_hdr = relaxed_hdr;
     }
 
-    base64_encode(NULL,&sigdata_len,(unsigned char *)rsa_sig,mpi_size(&(rsa.N)));
-    ctx->sig->sigdata = malloc(sigdata_len+1);
-    if (ctx->sig->sigdata == NULL) return PDKIM_ERR_OOM;
-    base64_encode((unsigned char *)ctx->sig->sigdata,
-                  &sigdata_len,
-                  (unsigned char *)rsa_sig,
-                  mpi_size(&(rsa.N)));
-    ctx->sig->sigdata[sigdata_len] = '\0';
-
-#ifdef PDKIM_DEBUG
-    if (ctx->debug_stream)
-      fprintf(ctx->debug_stream,
-        "PDKIM RSA-signed hash: %s\n",ctx->sig->sigdata);
-#endif
-
-    /* Recreate signature header with b= included */
-    pdkim_strfree(hdr);
-    hdr = pdkim_create_header(ctx->sig,1);
-    if (hdr == NULL) return PDKIM_ERR_OOM;
-
-#ifdef PDKIM_DEBUG
+    #ifdef PDKIM_DEBUG
     if (ctx->debug_stream) {
       fprintf(ctx->debug_stream,
-              "PDKIM >> Final DKIM-Signature header >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
-      pdkim_quoteprint(ctx->debug_stream, hdr->str, hdr->len, 1);
+              "PDKIM >> Signed DKIM-Signature header, canonicalized >>>>>>>>>>>>>>>>>\n");
+      pdkim_quoteprint(ctx->debug_stream, sig_hdr, strlen(sig_hdr), 1);
       fprintf(ctx->debug_stream,
               "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
     }
-#endif
+    #endif
 
-    if (signature != NULL) {
-      *signature = hdr->str;
+    /* Finalize header hash */
+    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);
 
+    /* SIGNING ---------------------------------------------------------------- */
+    if (ctx->mode == PDKIM_MODE_SIGN) {
+      rsa_context rsa;
+
+      rsa_init(&rsa,RSA_PKCS_V15,0,NULL,NULL);
+
+      /* Perform private key operation */
+      if (rsa_parse_key(&rsa, (unsigned char *)sig->rsa_privkey,
+                        strlen(sig->rsa_privkey), NULL, 0) != 0) {
+        return PDKIM_ERR_RSA_PRIVKEY;
+      }
+
+      sig->sigdata_len = mpi_size(&(rsa.N));
+      sig->sigdata = malloc(sig->sigdata_len);
+      if (sig->sigdata == NULL) return PDKIM_ERR_OOM;
+
+      if (rsa_pkcs1_sign( &rsa, RSA_PRIVATE,
+                          ((sig->algo == PDKIM_ALGO_RSA_SHA1)?
+                             RSA_SHA1:RSA_SHA256),
+                          0,
+                          (unsigned char *)headerhash,
+                          (unsigned char *)sig->sigdata ) != 0) {
+        return PDKIM_ERR_RSA_SIGNING;
+      }
+
+      rsa_free(&rsa);
+
+      #ifdef PDKIM_DEBUG
+      if (ctx->debug_stream) {
+        fprintf(ctx->debug_stream, "PDKIM [%s] b computed: ",
+                sig->domain);
+        pdkim_hexprint(ctx->debug_stream, sig->sigdata, sig->sigdata_len, 1);
+      }
+      #endif
+
+      sig->signature_header = pdkim_create_header(ctx->sig,1);
+      if (sig->signature_header == NULL) return PDKIM_ERR_OOM;
+    }
+    /* VERIFICATION ----------------------------------------------------------- */
+    else {
+      rsa_context rsa;
+      char *dns_txt_name, *dns_txt_reply;
+
+      rsa_init(&rsa,RSA_PKCS_V15,0,NULL,NULL);
+
+      dns_txt_name  = malloc(PDKIM_DNS_TXT_MAX_NAMELEN);
+      if (dns_txt_name == NULL) return PDKIM_ERR_OOM;
+      dns_txt_reply = malloc(PDKIM_DNS_TXT_MAX_RECLEN);
+      if (dns_txt_reply == NULL) {
+        free(dns_txt_name);
+        return PDKIM_ERR_OOM;
+      }
+      memset(dns_txt_reply,0,PDKIM_DNS_TXT_MAX_RECLEN);
+      memset(dns_txt_name ,0,PDKIM_DNS_TXT_MAX_NAMELEN);
+
+      if (snprintf(dns_txt_name,PDKIM_DNS_TXT_MAX_NAMELEN,
+                   "%s._domainkey.%s.",
+                   sig->selector,sig->domain) >= PDKIM_DNS_TXT_MAX_NAMELEN) {
+        sig->verify_status =      PDKIM_VERIFY_INVALID;
+        sig->verify_ext_status =  PDKIM_VERIFY_INVALID_BUFFER_SIZE;
+        goto NEXT_VERIFY;
+      };
+
+      if ((ctx->dns_txt_callback(dns_txt_name, dns_txt_reply) != PDKIM_OK) ||
+          (dns_txt_reply[0] == '\0')) {
+        sig->verify_status =      PDKIM_VERIFY_INVALID;
+        sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE;
+        goto NEXT_VERIFY;
+      }
+
+      #ifdef PDKIM_DEBUG
+      if (ctx->debug_stream) {
+        fprintf(ctx->debug_stream,
+                "PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+        fprintf(ctx->debug_stream,"Raw record: ");
+        pdkim_quoteprint(ctx->debug_stream, dns_txt_reply, strlen(dns_txt_reply), 1);
+      }
+      #endif
+
+      sig->pubkey = pdkim_parse_pubkey_record(ctx,dns_txt_reply);
+      if (sig->pubkey == NULL) {
+        sig->verify_status =      PDKIM_VERIFY_INVALID;
+        sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_PARSING;
+        #ifdef PDKIM_DEBUG
+        if (ctx->debug_stream) {
+          fprintf(ctx->debug_stream,"Error while parsing public key record\n");
+          fprintf(ctx->debug_stream,
+            "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+        }
+        #endif
+        goto NEXT_VERIFY;
+      }
+
+      #ifdef PDKIM_DEBUG
+      if (ctx->debug_stream) {
+        fprintf(ctx->debug_stream,
+          "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+      }
+      #endif
+
+      if (rsa_parse_public_key(&rsa,
+                              (unsigned char *)sig->pubkey->key,
+                               sig->pubkey->key_len) != 0) {
+        sig->verify_status =      PDKIM_VERIFY_INVALID;
+        sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_PARSING;
+        goto NEXT_VERIFY;
+      }
+
+      /* Check the signature */
+      if (rsa_pkcs1_verify(&rsa,
+                        RSA_PUBLIC,
+                        ((sig->algo == PDKIM_ALGO_RSA_SHA1)?
+                             RSA_SHA1:RSA_SHA256),
+                        0,
+                        (unsigned char *)headerhash,
+                        (unsigned char *)sig->sigdata) != 0) {
+        sig->verify_status =      PDKIM_VERIFY_FAIL;
+        sig->verify_ext_status =  PDKIM_VERIFY_FAIL_MESSAGE;
+        #ifdef PDKIM_DEBUG
+        if (ctx->debug_stream) {
+          fprintf(ctx->debug_stream, "PDKIM [%s] signature did NOT verify OK\n",
+                  sig->domain);
+        }
+        #endif
+        goto NEXT_VERIFY;
+      }
+
+      /* We have a winner! */
+      sig->verify_status = PDKIM_VERIFY_PASS;
+
+      #ifdef PDKIM_DEBUG
+      if (ctx->debug_stream) {
+        fprintf(ctx->debug_stream, "PDKIM [%s] signature verified OK\n",
+                sig->domain);
+      }
+      #endif
+
+      NEXT_VERIFY:
+      rsa_free(&rsa);
+      free(dns_txt_name);
+      free(dns_txt_reply);
+    }
+
+    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(void) {
+DLLEXPORT pdkim_ctx *pdkim_init_verify(int input_mode,
+                             int(*dns_txt_callback)(char *, char *)
+                             ) {
   pdkim_ctx *ctx = malloc(sizeof(pdkim_ctx));
   if (ctx == NULL) return NULL;
   memset(ctx,0,sizeof(pdkim_ctx));
+
+  ctx->linebuf = malloc(PDKIM_MAX_BODY_LINE_LEN);
+  if (ctx->linebuf == NULL) {
+    free(ctx);
+    return NULL;
+  }
+
   ctx->mode = PDKIM_MODE_VERIFY;
+  ctx->input_mode = input_mode;
+  ctx->dns_txt_callback = dns_txt_callback;
+
   return ctx;
 }
 
 
 /* -------------------------------------------------------------------------- */
-pdkim_ctx *pdkim_init_sign(char *domain,
+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;
 
   ctx = malloc(sizeof(pdkim_ctx));
   if (ctx == NULL) return NULL;
   memset(ctx,0,sizeof(pdkim_ctx));
-  pdkim_signature *sig = malloc(sizeof(pdkim_signature));
+
+  ctx->linebuf = malloc(PDKIM_MAX_BODY_LINE_LEN);
+  if (ctx->linebuf == NULL) {
+    free(ctx);
+    return NULL;
+  }
+
+  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);
 
-  sha1_starts(&(ctx->sig->sha1_body));
-  sha2_starts(&(ctx->sig->sha2_body),0);
+  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;
 };
 
 #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,
-                       int input_mode,
+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->input_mode = input_mode;
   ctx->sig->canon_headers = canon_headers;
   ctx->sig->canon_body = canon_body;
   ctx->sig->bodylength = bodylength;
@@ -1192,44 +1665,3 @@ int pdkim_set_optional(pdkim_ctx *ctx,
 
   return PDKIM_OK;
 };
-
-
-
-
-/* -------------------------------------------------------------------------- */
-void pdkim_free_sig(pdkim_signature *sig) {
-  if (sig) {
-    pdkim_signature *next = (pdkim_signature *)sig->next;
-
-    pdkim_stringlist *e = sig->headers;
-    while(e != NULL) {
-      pdkim_stringlist *c = e;
-      if (e->value != NULL) free(e->value);
-      e = e->next;
-      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);
-
-    free(sig);
-    if (next != NULL) pdkim_free_sig(next);
-  }
-};
-
-
-/* -------------------------------------------------------------------------- */
-void pdkim_free_ctx(pdkim_ctx *ctx) {
-  if (ctx) {
-    pdkim_free_sig(ctx->sig);
-    pdkim_strfree(ctx->cur_header);
-    free(ctx);
-  }
-};