tidying
[exim.git] / src / src / pdkim / pdkim.c
index f68324097083d73f06ffb93ae4b671b42f246eff..565ee068eaec42e5458522c407b464a3e47dafea 100644 (file)
@@ -1,8 +1,10 @@
 /*
  *  PDKIM - a RFC4871 (DKIM) implementation
  *
- *  Copyright (C) 2009 - 2016  Tom Kistner <tom@duncanthrax.net>
+ *  Copyright (c) The Exim Maintainers 2021 - 2023
  *  Copyright (C) 2016 - 2020  Jeremy Harris <jgh@exim.org>
+ *  Copyright (C) 2009 - 2016  Tom Kistner <tom@duncanthrax.net>
+ *  SPDX-License-Identifier: GPL-2.0-or-later
  *
  *  http://duncanthrax.net/pdkim/
  *
@@ -107,7 +109,7 @@ pdkim_combined_canon_entry pdkim_combined_canons[] = {
 };
 
 
-static blob lineending = {.data = US"\r\n", .len = 2};
+static const blob lineending = {.data = US"\r\n", .len = 2};
 
 /* -------------------------------------------------------------------------- */
 uschar *
@@ -247,7 +249,7 @@ debug_printf("\n");
 static pdkim_stringlist *
 pdkim_prepend_stringlist(pdkim_stringlist * base, const uschar * str)
 {
-pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist), FALSE);
+pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist), GET_UNTAINTED);
 
 memset(new_entry, 0, sizeof(pdkim_stringlist));
 new_entry->value = string_copy(str);
@@ -337,7 +339,7 @@ pdkim_relax_header_n(const uschar * header, int len, BOOL append_crlf)
 {
 BOOL past_field_name = FALSE;
 BOOL seen_wsp = FALSE;
-uschar * relaxed = store_get(len+3, TRUE);     /* tainted */
+uschar * relaxed = store_get(len+3, GET_TAINTED);
 uschar * q = relaxed;
 
 for (const uschar * p = header; p - header < len; p++)
@@ -417,7 +419,7 @@ pdkim_decode_qp(const uschar * str)
 int nchar = 0;
 uschar * q;
 const uschar * p = str;
-uschar * n = store_get(Ustrlen(str)+1, TRUE);
+uschar * n = store_get(Ustrlen(str)+1, GET_TAINTED);
 
 *n = '\0';
 q = n;
@@ -446,7 +448,7 @@ return n;
 void
 pdkim_decode_base64(const uschar * str, blob * b)
 {
-int dlen = b64decode(str, &b->data);
+int dlen = b64decode(str, &b->data, str);
 if (dlen < 0) b->data = NULL;
 b->len = dlen;
 }
@@ -474,7 +476,7 @@ BOOL past_hname = FALSE;
 BOOL in_b_val = FALSE;
 int where = PDKIM_HDR_LIMBO;
 
-sig = store_get(sizeof(pdkim_signature), FALSE);
+sig = store_get(sizeof(pdkim_signature), GET_UNTAINTED);
 memset(sig, 0, sizeof(pdkim_signature));
 sig->bodylength = -1;
 
@@ -483,7 +485,7 @@ sig->version = 0;
 sig->keytype = -1;
 sig->hashtype = -1;
 
-q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1, TRUE);        /* tainted */
+q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1, GET_TAINTED);
 
 for (uschar * p = raw_hdr; ; p++)
   {
@@ -510,10 +512,6 @@ for (uschar * p = raw_hdr; ; p++)
     }
 
   if (where == PDKIM_HDR_TAG)
-    {
-    if (c >= 'a' && c <= 'z')
-      cur_tag = string_catn(cur_tag, p, 1);
-
     if (c == '=')
       {
       if (Ustrcmp(string_from_gstring(cur_tag), "b") == 0)
@@ -524,7 +522,8 @@ for (uschar * p = raw_hdr; ; p++)
       where = PDKIM_HDR_VALUE;
       goto NEXT_CHAR;
       }
-    }
+    else if (!isspace(c))
+      cur_tag = string_catn(cur_tag, p, 1);
 
   if (where == PDKIM_HDR_VALUE)
     {
@@ -551,17 +550,17 @@ for (uschar * p = raw_hdr; ; p++)
            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);
+             case 'h':  if (cur_tag->ptr != 2) goto bad_tag;
+                        pdkim_decode_base64(cur_val->s, &sig->bodyhash);
                         break;
-             default:   break;
+             default:   goto bad_tag;
              }
            break;
          case 'v':                                     /* version */
              /* We only support version 1, and that is currently the
                 only version there is. */
            sig->version =
-             Ustrcmp(cur_val->s, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
+               Ustrcmp(cur_val->s, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
            break;
          case 'a':                                     /* algorithm */
            {
@@ -576,6 +575,7 @@ for (uschar * p = raw_hdr; ; p++)
                if (Ustrcmp(elem, pdkim_hashes[i].dkim_hashname) == 0)
                  { sig->hashtype = i; break; }
            }
+           break;
 
          case 'c':                                     /* canonicalization */
            pdkim_cstring_to_canons(cur_val->s, 0,
@@ -584,15 +584,15 @@ for (uschar * p = raw_hdr; ; p++)
          case 'q':                             /* Query method (for pubkey)*/
            for (int i = 0; pdkim_querymethods[i]; i++)
              if (Ustrcmp(cur_val->s, pdkim_querymethods[i]) == 0)
-               {
+               {
                sig->querymethod = i;   /* we never actually use this */
                break;
                }
            break;
          case 's':                                     /* Selector */
-           sig->selector = string_copyn(cur_val->s, cur_val->ptr); break;
+           sig->selector = string_copy_from_gstring(cur_val); break;
          case 'd':                                     /* SDID */
-           sig->domain = string_copyn(cur_val->s, cur_val->ptr); break;
+           sig->domain = string_copy_from_gstring(cur_val); break;
          case 'i':                                     /* AUID */
            sig->identity = pdkim_decode_qp(cur_val->s); break;
          case 't':                                     /* Timestamp */
@@ -602,16 +602,18 @@ for (uschar * p = raw_hdr; ; p++)
          case 'l':                                     /* Body length count */
            sig->bodylength = strtol(CS cur_val->s, NULL, 10); break;
          case 'h':                                     /* signed header fields */
-           sig->headernames = string_copyn(cur_val->s, cur_val->ptr); break;
+           sig->headernames = string_copy_from_gstring(cur_val); break;
          case 'z':                                     /* Copied headfields */
            sig->copiedheaders = pdkim_decode_qp(cur_val->s); break;
 /*XXX draft-ietf-dcrup-dkim-crypto-05 would need 'p' tag support
 for rsafp signatures.  But later discussion is dropping those. */
          default:
-           DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
-           break;
+           goto bad_tag;
          }
        }
+       else
+bad_tag:  DEBUG(D_acl) debug_printf(" Unknown tag encountered: %Y\n", cur_tag);
+
       cur_tag = cur_val = NULL;
       in_b_val = FALSE;
       where = PDKIM_HDR_LIMBO;
@@ -657,25 +659,36 @@ return sig;
 /* -------------------------------------------------------------------------- */
 
 pdkim_pubkey *
-pdkim_parse_pubkey_record(const uschar *raw_record)
+pdkim_parse_pubkey_record(const uschar * raw_record)
 {
-const uschar * ele;
-int sep = ';';
-pdkim_pubkey * pub;
+pdkim_pubkey * pub = store_get(sizeof(pdkim_pubkey), GET_TAINTED);
 
-pub = store_get(sizeof(pdkim_pubkey), TRUE);   /* tainted */
 memset(pub, 0, sizeof(pdkim_pubkey));
 
-while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0)))
+for (const uschar * ele = raw_record, * tspec, * end, * val; *ele; ele = end)
   {
-  const uschar * val;
+  Uskip_whitespace(&ele);
+  end = US strchrnul(CS ele, ';');
+  tspec = string_copyn(ele, end - ele);
+  if (*end) end++;                     /* skip the ; */
 
-  if ((val = Ustrchr(ele, '=')))
+  if ((val = Ustrchr(tspec, '=')))
     {
-    int taglen = val++ - ele;
+    int taglen = val++ - tspec;
+
+    DEBUG(D_acl) debug_printf(" %.*s=%s\n", taglen, tspec, val);
+    while (taglen > 1 && isspace(tspec[taglen-1]))
+      taglen--;                        /* Ignore whitespace before = */
+    Uskip_whitespace(&val);    /* Ignore whitespace after = */
+    if (isspace(val[ Ustrlen(val)-1 ]))
+      {                                /* Ignore whitespace after value */
+      gstring * g = string_cat(NULL, val);
+      while (isspace(gstring_last_char(g)))
+       gstring_trim(g, 1);
+      val = string_from_gstring(g);
+      }
 
-    DEBUG(D_acl) debug_printf(" %.*s=%s\n", taglen, ele, val);
-    switch (ele[0])
+    if (taglen == 1) switch (tspec[0])
       {
       case 'v': pub->version = val;                    break;
       case 'h': pub->hashes = val;                     break;
@@ -687,8 +700,11 @@ while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0)))
       case 't': if (Ustrchr(val, 'y')) pub->testing = 1;
                if (Ustrchr(val, 's')) pub->no_subdomaining = 1;
                break;
-      default:  DEBUG(D_acl) debug_printf(" Unknown tag encountered\n"); break;
+      default:  goto bad_tag;
       }
+    else
+bad_tag:
+       DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
     }
   }
 
@@ -720,9 +736,11 @@ return NULL;
 If we have to relax the data for this sig, return our copy of it. */
 
 static blob *
-pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, blob * orig_data, blob * relaxed_data)
+pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, const blob * orig_data, blob * relaxed_data)
 {
-blob * canon_data = orig_data;
+const blob * canon_data = orig_data;
+size_t left;
+
 /* Defaults to simple canon (no further treatment necessary) */
 
 if (b->canon_method == PDKIM_CANON_RELAXED)
@@ -767,16 +785,17 @@ if (b->canon_method == PDKIM_CANON_RELAXED)
   }
 
 /* Make sure we don't exceed the to-be-signed body length */
+left = canon_data->len;
 if (  b->bodylength >= 0
-   && b->signed_body_bytes + (unsigned long)canon_data->len > b->bodylength
+   && left > (unsigned long)b->bodylength - b->signed_body_bytes
    )
-  canon_data->len = b->bodylength - b->signed_body_bytes;
+  left = (unsigned long)b->bodylength - b->signed_body_bytes;
 
-if (canon_data->len > 0)
+if (left > 0)
   {
-  exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, canon_data->len);
-  b->signed_body_bytes += canon_data->len;
-  DEBUG(D_acl) pdkim_quoteprint(canon_data->data, canon_data->len);
+  exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, left);
+  b->signed_body_bytes += left;
+  DEBUG(D_acl) pdkim_quoteprint(canon_data->data, left);
   }
 
 return relaxed_data;
@@ -790,8 +809,9 @@ pdkim_finish_bodyhash(pdkim_ctx * ctx)
 {
 for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)     /* Finish hashes */
   {
-  DEBUG(D_acl) debug_printf("DKIM: finish bodyhash %d/%d/%ld len %ld\n",
-           b->hashtype, b->canon_method, b->bodylength, b->signed_body_bytes);
+  DEBUG(D_acl) debug_printf("DKIM: finish bodyhash %s/%s/%ld len %ld\n",
+      pdkim_hashes[b->hashtype].dkim_hashname, pdkim_canons[b->canon_method],
+      b->bodylength, b->signed_body_bytes);
   exim_sha_finish(&b->body_hash_ctx, &b->bh);
   }
 
@@ -802,10 +822,10 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
 
   DEBUG(D_acl)
     {
-    debug_printf("DKIM [%s] Body bytes (%s) hashed: %lu\n"
-                "DKIM [%s] Body %s computed: ",
-       sig->domain, pdkim_canons[b->canon_method], b->signed_body_bytes,
-       sig->domain, pdkim_hashes[b->hashtype].dkim_hashname);
+    debug_printf("DKIM [%s]%s Body bytes (%s) hashed: %lu\n"
+                "DKIM [%s]%s Body %s computed: ",
+       sig->domain, sig->selector, pdkim_canons[b->canon_method], b->signed_body_bytes,
+       sig->domain, sig->selector, pdkim_hashes[b->hashtype].dkim_hashname);
     pdkim_hexprint(CUS b->bh.data, b->bh.len);
     }
 
@@ -822,7 +842,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
   /* VERIFICATION --------------------------------------------------------- */
   /* Be careful that the header sig included a bodyash */
 
-    if (  sig->bodyhash.data
+    if (sig->bodyhash.data && sig->bodyhash.len == b->bh.len
        && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0)
       {
       DEBUG(D_acl) debug_printf("DKIM [%s] Body hash compared OK\n", sig->domain);
@@ -951,9 +971,8 @@ return;
 static int
 pdkim_header_complete(pdkim_ctx * ctx)
 {
-if ( (ctx->cur_header->ptr > 1) &&
-     (ctx->cur_header->s[ctx->cur_header->ptr-1] == '\r') )
-  --ctx->cur_header->ptr;
+if (ctx->cur_header->ptr > 1)
+  gstring_trim_trailing(ctx->cur_header, '\r');
 (void) string_from_gstring(ctx->cur_header);
 
 #ifdef EXPERIMENTAL_ARC
@@ -1003,7 +1022,7 @@ else
       last_sig->next = sig;
       }
 
-    if (--dkim_collect_input == 0)
+    if (dkim_collect_input && --dkim_collect_input == 0)
       {
       ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
       ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';
@@ -1261,7 +1280,7 @@ if (sig->identity)
 
 if (sig->created > 0)
   {
-  uschar minibuf[20];
+  uschar minibuf[21];
 
   snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->created);
   hdr = pdkim_headcat(&col, hdr, US";", US"t=", minibuf);
@@ -1269,7 +1288,7 @@ if (sig->created > 0)
 
 if (sig->expires > 0)
   {
-  uschar minibuf[20];
+  uschar minibuf[21];
 
   snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->expires);
   hdr = pdkim_headcat(&col, hdr, US";", US"x=", minibuf);
@@ -1277,7 +1296,7 @@ if (sig->expires > 0)
 
 if (sig->bodylength >= 0)
   {
-  uschar minibuf[20];
+  uschar minibuf[21];
 
   snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->bodylength);
   hdr = pdkim_headcat(&col, hdr, US";", US"l=", minibuf);
@@ -1608,7 +1627,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
          rh = pdkim_relax_header(rh, TRUE);    /* cook header for relaxed canon */
 
        /* Feed header to the hash algorithm */
-       exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh));
+       exim_sha_update_string(&hhash_ctx, CUS rh);
 
        /* Remember headers block for signing (when the library cannot do incremental)  */
        /*XXX we could avoid doing this for all but the GnuTLS/RSA case */
@@ -1669,7 +1688,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
              : string_copy(CUS hdrs->value);
 
            /* Feed header to the hash algorithm */
-           exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh));
+           exim_sha_update_string(&hhash_ctx, CUS rh);
 
            DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
            hdrs->tag = 1;
@@ -1710,7 +1729,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
     }
 
   /* Finalize header hash */
-  exim_sha_update(&hhash_ctx, CUS sig_hdr, Ustrlen(sig_hdr));
+  exim_sha_update_string(&hhash_ctx, CUS sig_hdr);
   exim_sha_finish(&hhash_ctx, &hhash);
 
   DEBUG(D_acl)
@@ -1863,9 +1882,9 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
     if (*dkim_verify_min_keysizes)
       {
       unsigned minbits;
-      uschar * ss = expand_getkeyed(US pdkim_keytypes[sig->keytype],
+      const uschar * ss = expand_getkeyed(US pdkim_keytypes[sig->keytype],
                                    dkim_verify_min_keysizes);
-      if (ss &&  (minbits = atoi(CS ss)) > sig->keybits)
+      if (ss &&  (minbits = atoi(CCS ss)) > sig->keybits)
        {
        DEBUG(D_acl) debug_printf("Key too short: Actual: %s %u  Minima '%s'\n",
          pdkim_keytypes[sig->keytype], sig->keybits, dkim_verify_min_keysizes);
@@ -1915,13 +1934,14 @@ pdkim_init_verify(uschar * (*dns_txt_callback)(const uschar *), BOOL dot_stuffin
 {
 pdkim_ctx * ctx;
 
-ctx = store_get(sizeof(pdkim_ctx), FALSE);
+ctx = store_get(sizeof(pdkim_ctx), GET_UNTAINTED);
 memset(ctx, 0, sizeof(pdkim_ctx));
 
 if (dot_stuffing) ctx->flags = PDKIM_DOT_TERM;
 /* The line-buffer is for message data, hence tainted */
-ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, TRUE);
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, GET_TAINTED);
 ctx->dns_txt_callback = dns_txt_callback;
+ctx->cur_header = string_get_tainted(36, GET_TAINTED);
 
 return ctx;
 }
@@ -1942,7 +1962,7 @@ if (!domain || !selector || !privkey)
 
 /* Allocate & init one signature struct */
 
-sig = store_get(sizeof(pdkim_signature), FALSE);
+sig = store_get(sizeof(pdkim_signature), GET_UNTAINTED);
 memset(sig, 0, sizeof(pdkim_signature));
 
 sig->bodylength = -1;
@@ -2023,14 +2043,14 @@ for (b = ctx->bodyhash; b; b = b->next)
      && canon_method == b->canon_method
      && bodylength == b->bodylength)
     {
-    DEBUG(D_receive) debug_printf("DKIM: using existing bodyhash %d/%d/%ld\n",
-                                 hashtype, canon_method, bodylength);
+    DEBUG(D_receive) debug_printf("DKIM: using existing bodyhash %s/%s/%ld\n",
+      pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[canon_method], bodylength);
     return b;
     }
 
-DEBUG(D_receive) debug_printf("DKIM: new bodyhash %d/%d/%ld\n",
-                             hashtype, canon_method, bodylength);
-b = store_get(sizeof(pdkim_bodyhash), FALSE);
+DEBUG(D_receive) debug_printf("DKIM: new bodyhash %s/%s/%ld\n",
+    pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[canon_method], bodylength);
+b = store_get(sizeof(pdkim_bodyhash), GET_UNTAINTED);
 b->next = ctx->bodyhash;
 b->hashtype = hashtype;
 b->canon_method = canon_method;
@@ -2075,7 +2095,7 @@ pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed,
 memset(ctx, 0, sizeof(pdkim_ctx));
 ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
 /* The line buffer is for message data, hence tainted */
-ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, TRUE);
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, GET_TAINTED);
 DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
 }