ARC: harden against malformed headers
[exim.git] / src / src / arc.c
index 7ad00fc9fa60df4a339df67717814cba70c0081f..e7ebb97196db5110f6114ff969df6d152ff58abc 100644 (file)
@@ -260,6 +260,7 @@ while ((c = *s))
          while ((c = *++s) && c != ';')
            if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
              g = string_catn(g, s, 1);
+         if (!g) return US"no b= value";
          al->b.data = string_from_gstring(g);
          al->b.len = g->ptr;
          gstring_reset_unused(g);
@@ -276,6 +277,7 @@ while ((c = *s))
          while ((c = *++s) && c != ';')
            if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
              g = string_catn(g, s, 1);
+         if (!g) return US"no bh= value";
          al->bh.data = string_from_gstring(g);
          al->bh.len = g->ptr;
          gstring_reset_unused(g);
@@ -770,22 +772,25 @@ arc_set * as;
 int inst;
 BOOL ams_fail_found = FALSE;
 
-if (!(as = ctx->arcset_chain))
+if (!(as = ctx->arcset_chain_last))
   return US"none";
 
-for(inst = 0; as; as = as->next)
+for(inst = as->instance; as; as = as->prev, inst--)
   {
-  if (  as->instance != ++inst
-     || !as->hdr_aar || !as->hdr_ams || !as->hdr_as
-     || arc_cv_match(as->hdr_as, US"fail")
-     )
-    {
-    arc_state_reason = string_sprintf("i=%d"
-      " (cv, sequence or missing header)", as->instance);
-    DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason);
-    return US"fail";
-    }
+  if (as->instance != inst)
+    arc_state_reason = string_sprintf("i=%d (sequence; expected %d)",
+      as->instance, inst);
+  else if (!as->hdr_aar || !as->hdr_ams || !as->hdr_as)
+    arc_state_reason = string_sprintf("i=%d (missing header)", as->instance);
+  else if (arc_cv_match(as->hdr_as, US"fail"))
+    arc_state_reason = string_sprintf("i=%d (cv)", as->instance);
+  else
+    goto good;
+
+  DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason);
+  return US"fail";
 
+  good:
   /* Evaluate the oldest-pass AMS validation while we're here.
   It does not affect the AS chain validation but is reported as
   auxilary info. */
@@ -797,9 +802,15 @@ for(inst = 0; as; as = as->next)
       arc_oldest_pass = inst;
   arc_state_reason = NULL;
   }
+if (inst != 0)
+  {
+  arc_state_reason = string_sprintf("(sequence; expected i=%d)", inst);
+  DEBUG(D_acl) debug_printf("ARC chain fail %s\n", arc_state_reason);
+  return US"fail";
+  }
 
 arc_received = ctx->arcset_chain_last;
-arc_received_instance = inst;
+arc_received_instance = arc_received->instance;
 
 /* We can skip the latest-AMS validation, if we already did it. */
 
@@ -975,16 +986,13 @@ return NULL;
 static const uschar *
 arc_verify_seals(arc_ctx * ctx)
 {
-arc_set * as = ctx->arcset_chain;
+arc_set * as = ctx->arcset_chain_last;
 
 if (!as)
   return US"none";
 
-while (as)
-  {
-  if (arc_seal_verify(ctx, as)) return US"fail";
-  as = as->next;
-  }
+for ( ; as; as = as->prev) if (arc_seal_verify(ctx, as)) return US"fail";
+
 DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
 return NULL;
 }
@@ -1188,7 +1196,7 @@ arc_line * al = (arc_line *)(as+1);
 header_line * h = (header_line *)(al+1);
 
 g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR);
-g = string_cat(g, string_sprintf(" i=%d; %s;\r\n\t", instance, identity));
+g = string_fmt_append(g, " i=%d; %s;\r\n\t", instance, identity);
 g = string_catn(g, US ar->data, ar->len);
 
 h->slen = g->ptr - aar_off;
@@ -1249,6 +1257,9 @@ if (  (errstr = exim_dkim_signing_init(privkey, &sctx))
    || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig)))
   {
   log_write(0, LOG_MAIN, "ARC: %s signing: %s\n", why, errstr);
+  DEBUG(D_transport)
+    debug_printf("private key, or private-key file content, was: '%s'\n",
+      privkey);
   return FALSE;
   }
 return TRUE;
@@ -1298,20 +1309,14 @@ header_line * h = (header_line *)(al+1);
 /* Construct the to-be-signed AMS pseudo-header: everything but the sig. */
 
 ams_off = g->ptr;
-g = string_append(g, 7,
-      ARC_HDR_AMS,
-      US" i=", string_sprintf("%d", instance),
-      US"; a=rsa-sha256; c=relaxed; d=", identity,             /*XXX hardwired */
-      US"; s=", selector);
+g = string_fmt_append(g, "%s i=%d; a=rsa-sha256; c=relaxed; d=%s; s=%s",
+      ARC_HDR_AMS, instance, identity, selector);      /*XXX hardwired a= */
 if (options & ARC_SIGN_OPT_TSTAMP)
-  g = string_append(g, 2,
-      US"; t=", string_sprintf("%lu", (u_long)now));
+  g = string_fmt_append(g, "; t=%lu", (u_long)now);
 if (options & ARC_SIGN_OPT_EXPIRE)
-  g = string_append(g, 2,
-      US"; x=", string_sprintf("%lu", (u_long)expire));
-g = string_append(g, 3,
-      US";\r\n\tbh=", pdkim_encode_base64(bodyhash),
-      US";\r\n\th=");
+  g = string_fmt_append(g, "; x=%lu", (u_long)expire);
+g = string_fmt_append(g, ";\r\n\tbh=%s;\r\n\th=",
+      pdkim_encode_base64(bodyhash));
 
 for(col = 3; rheaders; rheaders = rheaders->prev)
   {
@@ -1596,13 +1601,13 @@ if ((opts = string_nextinlist(&signspec, &sep, NULL, 0)))
       if (*(s += 6) == '=')
        if (*++s == '+')
          {
-         if (!(expire = (time_t)atoi(++s)))
+         if (!(expire = (time_t)atoi(CS ++s)))
            expire = ARC_SIGN_DEFAULT_EXPIRE_DELTA;
          if (!now) now = time(NULL);
          expire += now;
          }
        else
-         expire = (time_t)atol(s);
+         expire = (time_t)atol(CS s);
       else
        {
        if (!now) now = time(NULL);
@@ -1687,12 +1692,13 @@ g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
         including self (but with an empty b= in self)
 */
 
-g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar,
+if (g)
+  g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar,
       privkey, options);
 
 /* Finally, append the dkim headers and return the lot. */
 
-g = string_catn(g, sigheaders->s, sigheaders->ptr);
+if (sigheaders) g = string_catn(g, sigheaders->s, sigheaders->ptr);
 (void) string_from_gstring(g);
 gstring_reset_unused(g);
 return g;
@@ -1780,21 +1786,31 @@ uschar *
 fn_arc_domains(void)
 {
 arc_set * as;
+unsigned inst;
 gstring * g = NULL;
 
-if (!arc_state || Ustrcmp(arc_state, "pass") != 0)
-  return US"";
-
-for(as = arc_verify_ctx.arcset_chain; as; as = as->next)
+for (as = arc_verify_ctx.arcset_chain, inst = 1; as; as = as->next, inst++)
   {
-  blob * d = &as->hdr_as->d;
-  g = string_append_listele_n(g, ':', d->data, d->len);
+  arc_line * hdr_as = as->hdr_as;
+  if (hdr_as)
+    {
+    blob * d = &hdr_as->d;
+
+    for (; inst < as->instance; inst++)
+      g = string_catn(g, US":", 1);
+
+    g = d->data && d->len
+      ? string_append_listele_n(g, ':', d->data, d->len)
+      : string_catn(g, US":", 1);
+    }
+  else
+    g = string_catn(g, US":", 1);
   }
 return g ? g->s : US"";
 }
 
 
-/* Construct an Authenticate-Results header portion, for the ARC module */
+/* Construct an Authentication-Results header portion, for the ARC module */
 
 gstring *
 authres_arc(gstring * g)
@@ -1808,19 +1824,17 @@ if (arc_state)
   g = string_append(g, 2, US";\n\tarc=", arc_state);
   if (arc_received_instance > 0)
     {
-    g = string_append(g, 3, US" (i=",
-      string_sprintf("%d", arc_received_instance), US")");
+    g = string_fmt_append(g, " (i=%d)", arc_received_instance);
     if (arc_state_reason)
       g = string_append(g, 3, US"(", arc_state_reason, US")");
     g = string_catn(g, US" header.s=", 10);
     highest_ams = arc_received->hdr_ams;
     g = string_catn(g, highest_ams->s.data, highest_ams->s.len);
 
-    g = string_append(g, 2,
-      US" arc.oldest-pass=", string_sprintf("%d", arc_oldest_pass));
+    g = string_fmt_append(g, " arc.oldest-pass=%d", arc_oldest_pass);
 
     if (sender_host_address)
-      g = string_append(g, 2, US" smtp.client-ip=", sender_host_address);
+      g = string_append(g, 2, US" smtp.remote-ip=", sender_host_address);
     }
   else if (arc_state_reason)
     g = string_append(g, 3, US" (", arc_state_reason, US")");