DKIM: tighter checking while parsing signature headers. Bug 2217
[exim.git] / src / src / pdkim / pdkim.c
index 06d455d7dbeaeb116ac7905ac1735651e979c754..b884671da9867888e04ffb6362e24a1543abad68 100644 (file)
 #define PDKIM_MAX_HEADERS           512
 #define PDKIM_MAX_BODY_LINE_LEN     16384
 #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 {
@@ -232,13 +224,13 @@ static void
 pdkim_strtrim(gstring * str)
 {
 uschar * p = str->s;
-uschar * q = p + str->ptr;
+uschar * q;
 
 while (*p == '\t' || *p == ' ')                /* dump the leading whitespace */
   { str->size--; str->ptr--; str->s++; }
 
 while (  str->ptr > 0
-      && (q = str->s + str->ptr - 1),  *q == '\t' || *q == ' '
+      && ((q = str->s + str->ptr - 1),  (*q == '\t' || *q == ' '))
       )
   str->ptr--;                          /* dump trailing whitespace */
 
@@ -258,16 +250,19 @@ pdkim_free_ctx(pdkim_ctx *ctx)
 /* -------------------------------------------------------------------------- */
 /* Matches the name of the passed raw "header" against
    the passed colon-separated "tick", and invalidates
-   the entry in tick. Returns OK or fail-code */
-/*XXX might be safer done using a pdkim_stringlist for "tick" */
+   the entry in tick.  Entries can be prefixed for multi- or over-signing,
+   in which case do not invalidate.
+
+   Returns OK for a match, or fail-code
+*/
 
 static int
 header_name_match(const uschar * header, uschar * tick)
 {
-uschar * hname;
-uschar * lcopy;
-uschar * p;
-uschar * q;
+const uschar * ticklist = tick;
+int sep = ':';
+BOOL multisign;
+uschar * hname, * p, * ele;
 uschar * hcolon = Ustrchr(header, ':');                /* Get header name */
 
 if (!hcolon)
@@ -276,27 +271,22 @@ if (!hcolon)
 /* if we had strncmpic() we wouldn't need this copy */
 hname = string_copyn(header, hcolon-header);
 
-/* Copy tick-off list locally, so we can punch zeroes into it */
-p = lcopy = string_copy(tick);
-
-for (q = Ustrchr(p, ':'); q; q = Ustrchr(p, ':'))
+while (p = US ticklist, ele = string_nextinlist(&ticklist, &sep, NULL, 0))
   {
-  *q = '\0';
-  if (strcmpic(p, hname) == 0)
-    goto found;
-
-  p = q+1;
+  switch (*ele)
+  {
+  case '=': case '+':  multisign = TRUE; ele++; break;
+  default:             multisign = FALSE; break;
   }
 
-if (strcmpic(p, hname) == 0)
-  goto found;
-
+  if (strcmpic(ele, hname) == 0)
+    {
+    if (!multisign)
+      *p = '_';        /* Invalidate this header name instance in tick-off list */
+    return PDKIM_OK;
+    }
+  }
 return PDKIM_FAIL;
-
-found:
-  /* Invalidate header name instance in tick-off list */
-  tick[p-lcopy] = '_';
-  return PDKIM_OK;
 }
 
 
@@ -500,7 +490,12 @@ for (p = raw_hdr; ; p++)
 
     if (c == ';' || c == '\0')
       {
-      if (cur_tag && cur_val)
+      /* We must have both tag and value, and tags must be one char except
+      for the possibility of "bh". */
+
+      if (  cur_tag && cur_val
+        && (cur_tag->ptr == 1 || *cur_tag->s == 'b')
+        )
         {
        (void) string_from_gstring(cur_val);
        pdkim_strtrim(cur_val);
@@ -510,8 +505,14 @@ for (p = raw_hdr; ; p++)
        switch (*cur_tag->s)
          {
          case 'b':
-           pdkim_decode_base64(cur_val->s,
-                           cur_tag->s[1] == 'h' ? &sig->bodyhash : &sig->sighash);
+           switch (cur_tag->s[1])
+             {
+             case '\0': pdkim_decode_base64(cur_val->s, &sig->sighash); break;
+             case 'h':  if (cur_tag->ptr == 2)
+                          pdkim_decode_base64(cur_val->s, &sig->bodyhash);
+                        break;
+             default:   break;
+             }
            break;
          case 'v':
              /* We only support version 1, and that is currently the
@@ -1453,11 +1454,17 @@ for (sig = ctx->sig; sig; sig = sig->next)
        }
       }
 
-    /* Any headers we wanted to sign but were not present must also be listed */
+    /* Any headers we wanted to sign but were not present must also be listed.
+    Ignore elements that have been ticked-off or are marked as never-oversign. */
+
     l = sig->sign_headers;
     while((s = string_nextinlist(&l, &sep, NULL, 0)))
-      if (*s != '_')
+      {
+      if (*s == '+')                   /* skip oversigning marker */
+        s++;
+      if (*s != '_' && *s != '=')
        g = string_append_listele(g, ':', s);
+      }
     sig->headernames = string_from_gstring(g);
 
     /* Create signature header with b= omitted */