[exim-cvs] Expansions: new ${lheader:<name>}. Bug 2272

Top Page
Delete this message
Reply to this message
Author: Exim Git Commits Mailing List
Date:  
To: exim-cvs
Subject: [exim-cvs] Expansions: new ${lheader:<name>}. Bug 2272
Gitweb: https://git.exim.org/exim.git/commitdiff/bce15b62182d356f86e7a0bdbb513cbb22de1a20
Commit:     bce15b62182d356f86e7a0bdbb513cbb22de1a20
Parent:     c8599aad9649a7970e77fdf24f29ade0fcb987a7
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Tue May 1 17:45:21 2018 +0100
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Tue May 1 17:45:21 2018 +0100


    Expansions: new ${lheader:<name>}.  Bug 2272
---
 doc/doc-docbook/spec.xfpt         |  14 +-
 doc/doc-txt/NewStuff              |   5 +
 doc/doc-txt/experimental-spec.txt |   8 +
 src/src/acl.c                     |  31 +---
 src/src/expand.c                  | 298 +++++++++++++++++++-------------------
 src/src/functions.h               |   1 +
 src/src/string.c                  |  20 +++
 test/confs/4560                   |  10 +-
 test/log/4560                     |  80 ++++++++++
 test/log/4561                     |   3 +
 10 files changed, 294 insertions(+), 176 deletions(-)


diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index f865fc9..0d6c239 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -9434,11 +9434,14 @@ letters appear. For example:
         &*$h_*&<&'header&~name'&>&*:*&" &&&
        "&*$bheader_*&<&'header&~name'&>&*:*&&~or&~&&&
         &*$bh_*&<&'header&~name'&>&*:*&" &&&
+       "&*$lheader_*&<&'header&~name'&>&*:*&&~or&~&&&
+        &*$lh_*&<&'header&~name'&>&*:*&"
        "&*$rheader_*&<&'header&~name'&>&*:*&&~or&~&&&
         &*$rh_*&<&'header&~name'&>&*:*&"
 .cindex "expansion" "header insertion"
 .vindex "&$header_$&"
 .vindex "&$bheader_$&"
+.vindex "&$lheader_$&"
 .vindex "&$rheader_$&"
 .cindex "header lines" "in expansion strings"
 .cindex "header lines" "character sets"
@@ -9451,7 +9454,7 @@ The newline that terminates a header line is not included in the expansion, but
 internal newlines (caused by splitting the header line over several physical
 lines) may be present.


-The difference between &%rheader%&, &%bheader%&, and &%header%& is in the way
+The difference between the four pairs of expansions is in the way
the data in the header line is interpreted.

.ilist
@@ -9460,6 +9463,15 @@ the data in the header line is interpreted.
processing at all, and without the removal of leading and trailing white space.

.next
+.cindex "list" "of header lines"
+&%lheader%& gives a colon-separated list, one element per header when there
+are multiple headers with a given name.
+Any embedded colon characters within an element are doubled, so normal Exim
+list-processing facilities can be used.
+The terminating newline of each element is removed; in other respects
+the content is &"raw"&.
+
+.next
.cindex "base64 encoding" "in header lines"
&%bheader%& removes leading and trailing white space, and then decodes base64
or quoted-printable MIME &"words"& within the header text, but does no
diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index 30ec7c9..aaf9734 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -9,6 +9,11 @@ the documentation is updated, this file is reduced to a short list.
Version 4.92
--------------

+ 1. ${l_header:<name>} and ${l_h:<name>} expansion items, giving a colon-sep
+    list when there are multiple headers having a given name.  This matters
+    when individual headers are wrapped onto multiple lines; with previous
+    facilities hard to parse.
+
 Version 4.91
 --------------


diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt
index ac3f1cc..15df152 100644
--- a/doc/doc-txt/experimental-spec.txt
+++ b/doc/doc-txt/experimental-spec.txt
@@ -801,6 +801,14 @@ There are three new variables: $arc_state, $arc_state_reason, $arc_domains:
             problematic elements may have empty list elements
   $arc_oldest_pass    lowest passing instance number of chain


+Example:
+  logwrite = oldest-p-ams: <${reduce {$lh_ARC-Authentication-Results:} \
+                {} \
+                {${if = {$arc_oldest_pass} \
+                    {${extract {i}{${extract {1}{;}{$item}}}}} \
+                    {$item} {$value}}} \
+                }>
+
 Receive log lines for an ARC pass will be tagged "ARC".



diff --git a/src/src/acl.c b/src/src/acl.c
index edf4fb4..d243ff4 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -1042,33 +1042,16 @@ uschar *
fn_hdrs_added(void)
{
gstring * g = NULL;
-header_line * h = acl_added_headers;
-uschar * s;
-uschar * cp;
+header_line * h;

-if (!h) return NULL;
-
-do
+for (h = acl_added_headers; h; h = h->next)
   {
-  s = h->text;
-  while ((cp = Ustrchr(s, '\n')) != NULL)
-    {
-    if (cp[1] == '\0') break;
-
-    /* contains embedded newline; needs doubling */
-    g = string_catn(g, s, cp-s+1);
-    g = string_catn(g, US"\n", 1);
-    s = cp+1;
-    }
-  /* last bit of header */
-
-/*XXX could we use add_listele? */
-  g = string_catn(g, s, cp-s+1);    /* newline-sep list */
+  int i = h->slen;
+  if (h->text[i-1] == '\n') i--;
+  g = string_append_listele_n(g, '\n', h->text, i);
   }
-while((h = h->next));


-g->s[g->ptr - 1] = '\0';    /* overwrite last newline */
-return g->s;
+return g ? g->s : NULL;
 }



@@ -2865,7 +2848,7 @@ int rc = OK;
int sep = -'/';
#endif

-for (; cb != NULL; cb = cb->next)
+for (; cb; cb = cb->next)
{
const uschar *arg;
int control_type;
diff --git a/src/src/expand.c b/src/src/expand.c
index 862544c..e386bbd 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -817,6 +817,10 @@ static uschar *mtable_setid[] =
static uschar *mtable_sticky[] =
{ US"--T", US"--t", US"-wT", US"-wt", US"r-T", US"r-t", US"rwT", US"rwt" };

+/* flags for find_header() */
+#define FH_EXISTS_ONLY    BIT(0)
+#define FH_WANT_RAW    BIT(1)
+#define FH_WANT_LIST    BIT(2)



 /*************************************************
@@ -1529,14 +1533,20 @@ pretty trivial.
 Arguments:
   name          the name of the header, without the leading $header_ or $h_,
                 or NULL if a concatenation of all headers is required
-  exists_only   TRUE if called from a def: test; don't need to build a string;
-                just return a string that is not "" and not "0" if the header
-                exists
   newsize       return the size of memory block that was obtained; may be NULL
                 if exists_only is TRUE
-  want_raw      TRUE if called for $rh_ or $rheader_ variables; no processing,
-                other than concatenating, will be done on the header. Also used
-                for $message_headers_raw.
+  flags        FH_EXISTS_ONLY
+          set if called from a def: test; don't need to build a string;
+          just return a string that is not "" and not "0" if the header
+          exists
+        FH_WANT_RAW
+          set if called for $rh_ or $rheader_ variables; no processing,
+          other than concatenating, will be done on the header. Also used
+          for $message_headers_raw.
+        FH_WANT_LIST
+          Double colon chars in the content, and replace newline with
+          colon between each element when concatenating; returning a
+          colon-sep list (elements might contain newlines)
   charset       name of charset to translate MIME words to; used only if
                 want_raw is false; if NULL, no translation is done (this is
                 used for $bh_ and $bheader_)
@@ -1546,121 +1556,100 @@ Returns:        NULL if the header does not exist, else a pointer to a new
 */


 static uschar *
-find_header(uschar *name, BOOL exists_only, int *newsize, BOOL want_raw,
-  uschar *charset)
+find_header(uschar *name, int *newsize, unsigned flags, uschar *charset)
 {
-BOOL found = name == NULL;
-int comma = 0;
-int len = found? 0 : Ustrlen(name);
-int i;
-uschar *yield = NULL;
-uschar *ptr = NULL;
-
-/* Loop for two passes - saves code repetition */
-
-for (i = 0; i < 2; i++)
-  {
-  int size = 0;
-  header_line *h;
-
-  for (h = header_list; size < header_insert_maxlen && h; h = h->next)
-    if (h->type != htype_old && h->text)  /* NULL => Received: placeholder */
-      if (!name || (len <= h->slen && strncmpic(name, h->text, len) == 0))
-        {
-        int ilen;
-        uschar *t;
-
-        if (exists_only) return US"1";      /* don't need actual string */
-        found = TRUE;
-        t = h->text + len;                  /* text to insert */
-        if (!want_raw)                      /* unless wanted raw, */
-          while (isspace(*t)) t++;          /* remove leading white space */
-        ilen = h->slen - (t - h->text);     /* length to insert */
-
-        /* Unless wanted raw, remove trailing whitespace, including the
-        newline. */
+BOOL found = !name;
+int len = name ? Ustrlen(name) : 0;
+BOOL comma = FALSE;
+header_line * h;
+gstring * g = NULL;


-        if (!want_raw)
-          while (ilen > 0 && isspace(t[ilen-1])) ilen--;
+for (h = header_list; h; h = h->next)
+  if (h->type != htype_old && h->text)  /* NULL => Received: placeholder */
+    if (!name || (len <= h->slen && strncmpic(name, h->text, len) == 0))
+      {
+      uschar * s, * t;
+      size_t inc;


-        /* Set comma = 1 if handling a single header and it's one of those
-        that contains an address list, except when asked for raw headers. Only
-        need to do this once. */
+      if (flags & FH_EXISTS_ONLY)
+    return US"1";  /* don't need actual string */


-        if (!want_raw && name && comma == 0 &&
-            Ustrchr("BCFRST", h->type) != NULL)
-          comma = 1;
+      found = TRUE;
+      s = h->text + len;        /* text to insert */
+      if (!(flags & FH_WANT_RAW))    /* unless wanted raw, */
+    while (isspace(*s)) s++;    /* remove leading white space */
+      t = h->text + h->slen;        /* end-point */


-        /* First pass - compute total store needed; second pass - compute
-        total store used, including this header. */
+      /* Unless wanted raw, remove trailing whitespace, including the
+      newline. */


-        size += ilen + comma + 1;  /* +1 for the newline */
+      if (flags & FH_WANT_LIST)
+    while (t > s && t[-1] == '\n') t--;
+      else if (!(flags & FH_WANT_RAW))
+    {
+    while (t > s && isspace(t[-1])) t--;


-        /* Second pass - concatenate the data, up to a maximum. Note that
-        the loop stops when size hits the limit. */
+    /* Set comma if handling a single header and it's one of those
+    that contains an address list, except when asked for raw headers. Only
+    need to do this once. */


-        if (i != 0)
-          {
-          if (size > header_insert_maxlen)
-            {
-            ilen -= size - header_insert_maxlen - 1;
-            comma = 0;
-            }
-          Ustrncpy(ptr, t, ilen);
-          ptr += ilen;
-
-          /* For a non-raw header, put in the comma if needed, then add
-          back the newline we removed above, provided there was some text in
-          the header. */
+    if (name && !comma && Ustrchr("BCFRST", h->type)) comma = TRUE;
+    }


-          if (!want_raw && ilen > 0)
-            {
-            if (comma != 0) *ptr++ = ',';
-            *ptr++ = '\n';
-            }
-          }
-        }
+      /* Trim the header roughly if we're approaching limits */
+      inc = t - s;
+      if ((g ? g->ptr : 0) + inc > header_insert_maxlen)
+    inc = header_insert_maxlen - (g ? g->ptr : 0);
+
+      /* For raw just copy the data; for a list, add the data as a colon-sep
+      list-element; for comma-list add as an unchecked comma,newline sep
+      list-elemment; for other nonraw add as an unchecked newline-sep list (we
+      stripped trailing WS above including the newline). We ignore the potential
+      expansion due to colon-doubling, just leaving the loop if the limit is met
+      or exceeded. */
+
+      if (flags & FH_WANT_LIST)
+        g = string_append_listele_n(g, ':', s, (unsigned)inc);
+      else if (flags & FH_WANT_RAW)
+    {
+    g = string_catn(g, s, (unsigned)inc);
+    (void) string_from_gstring(g);
+    }
+      else if (inc > 0)
+    if (comma)
+      g = string_append2_listele_n(g, US",\n", s, (unsigned)inc);
+    else
+      g = string_append2_listele_n(g, US"\n", s, (unsigned)inc);


-  /* At end of first pass, return NULL if no header found. Then truncate size
-  if necessary, and get the buffer to hold the data, returning the buffer size.
-  */
+      if (g && g->ptr >= header_insert_maxlen) break;
+      }


-  if (i == 0)
-    {
-    if (!found) return NULL;
-    if (size > header_insert_maxlen) size = header_insert_maxlen;
-    *newsize = size + 1;
-    ptr = yield = store_get(*newsize);
-    }
-  }
+if (!found) return NULL;    /* No header found */
+if (!g) return US"";


/* That's all we do for raw header expansion. */

-if (want_raw)
- *ptr = 0;
+*newsize = g->size;
+if (flags & FH_WANT_RAW)
+ return g->s;

-/* Otherwise, remove a final newline and a redundant added comma. Then we do
-RFC 2047 decoding, translating the charset if requested. The rfc2047_decode2()
-function can return an error with decoded data if the charset translation
-fails. If decoding fails, it returns NULL. */
+/* Otherwise do RFC 2047 decoding, translating the charset if requested.
+The rfc2047_decode2() function can return an error with decoded data if the
+charset translation fails. If decoding fails, it returns NULL. */

 else
   {
   uschar *decoded, *error;
-  if (ptr > yield && ptr[-1] == '\n') ptr--;
-  if (ptr > yield && comma != 0 && ptr[-1] == ',') ptr--;
-  *ptr = 0;
-  decoded = rfc2047_decode2(yield, check_rfc2047_length, charset, '?', NULL,
+
+  decoded = rfc2047_decode2(g->s, check_rfc2047_length, charset, '?', NULL,
     newsize, &error);
-  if (error != NULL)
+  if (error)
     {
     DEBUG(D_any) debug_printf("*** error in RFC 2047 decoding: %s\n"
-      "    input was: %s\n", error, yield);
+      "    input was: %s\n", error, g->s);
     }
-  if (decoded != NULL) yield = decoded;
+  return decoded ? decoded : g->s;
   }
-
-return yield;
 }



@@ -1711,6 +1700,7 @@ generated from a system filter, but not elsewhere. */
static uschar *
fn_recipients(void)
{
+uschar * s;
gstring * g = NULL;
int i;

@@ -1718,11 +1708,10 @@ if (!enable_dollar_recipients) return NULL;

for (i = 0; i < recipients_count; i++)
{
- /*XXX variant of list_appendele? */
- if (i != 0) g = string_catn(g, US", ", 2);
- g = string_cat(g, recipients_list[i].address);
+ s = recipients_list[i].address;
+ g = string_append2_listele_n(g, US", ", s, Ustrlen(s));
}
-return string_from_gstring(g);
+return g ? g->s : NULL;
}


@@ -1866,10 +1855,11 @@ switch (vp->type)
     return (domain == NULL)? US"" : domain + 1;


   case vtype_msgheaders:
-    return find_header(NULL, exists_only, newsize, FALSE, NULL);
+    return find_header(NULL, newsize, exists_only ? FH_EXISTS_ONLY : 0, NULL);


   case vtype_msgheaders_raw:
-    return find_header(NULL, exists_only, newsize, TRUE, NULL);
+    return find_header(NULL, newsize,
+        exists_only ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW, NULL);


   case vtype_msgbody:                        /* Pointer to msgbody string */
   case vtype_msgbody_end:                    /* Ditto, the end of the msg */
@@ -1934,13 +1924,16 @@ switch (vp->type)
     return tod_stamp(tod_log_datestamp_daily);


   case vtype_reply:                          /* Get reply address */
-    s = find_header(US"reply-to:", exists_only, newsize, TRUE,
-      headers_charset);
+    s = find_header(US"reply-to:", newsize,
+        exists_only ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW,
+        headers_charset);
     if (s) while (isspace(*s)) s++;
     if (!s || !*s)
       {
       *newsize = 0;                            /* For the *s==0 case */
-      s = find_header(US"from:", exists_only, newsize, TRUE, headers_charset);
+      s = find_header(US"from:", newsize,
+        exists_only ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW,
+        headers_charset);
       }
     if (s)
       {
@@ -2229,50 +2222,52 @@ switch(cond_type)
   yield == NULL we are in a skipping state, and don't care about the answer. */


   case ECOND_DEF:
-  if (*s != ':')
     {
-    expand_string_message = US"\":\" expected after \"def\"";
-    return NULL;
-    }
+    uschar * t;


-  s = read_name(name, 256, s+1, US"_");
+    if (*s != ':')
+      {
+      expand_string_message = US"\":\" expected after \"def\"";
+      return NULL;
+      }


-  /* Test for a header's existence. If the name contains a closing brace
-  character, this may be a user error where the terminating colon has been
-  omitted. Set a flag to adjust a subsequent error message in this case. */
+    s = read_name(name, 256, s+1, US"_");


-  if (Ustrncmp(name, "h_", 2) == 0 ||
-      Ustrncmp(name, "rh_", 3) == 0 ||
-      Ustrncmp(name, "bh_", 3) == 0 ||
-      Ustrncmp(name, "header_", 7) == 0 ||
-      Ustrncmp(name, "rheader_", 8) == 0 ||
-      Ustrncmp(name, "bheader_", 8) == 0)
-    {
-    s = read_header_name(name, 256, s);
-    /* {-for-text-editors */
-    if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
-    if (yield != NULL) *yield =
-      (find_header(name, TRUE, NULL, FALSE, NULL) != NULL) == testfor;
-    }
+    /* Test for a header's existence. If the name contains a closing brace
+    character, this may be a user error where the terminating colon has been
+    omitted. Set a flag to adjust a subsequent error message in this case. */


-  /* Test for a variable's having a non-empty value. A non-existent variable
-  causes an expansion failure. */
+    if (  ( *(t = name) == 'h'
+      || (*t == 'r' || *t == 'l' || *t == 'b') && *++t == 'h'
+      )
+       && (*++t == '_' || Ustrncmp(t, "eader_", 6) == 0)
+       )
+      {
+      s = read_header_name(name, 256, s);
+      /* {-for-text-editors */
+      if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
+      if (yield) *yield =
+    (find_header(name, NULL, FH_EXISTS_ONLY, NULL) != NULL) == testfor;
+      }


-  else
-    {
-    uschar *value = find_variable(name, TRUE, yield == NULL, NULL);
-    if (value == NULL)
+    /* Test for a variable's having a non-empty value. A non-existent variable
+    causes an expansion failure. */
+
+    else
       {
-      expand_string_message = (name[0] == 0)?
-        string_sprintf("variable name omitted after \"def:\"") :
-        string_sprintf("unknown variable \"%s\" after \"def:\"", name);
-      check_variable_error_message(name);
-      return NULL;
+      if (!(t = find_variable(name, TRUE, yield == NULL, NULL)))
+    {
+    expand_string_message = (name[0] == 0)?
+      string_sprintf("variable name omitted after \"def:\"") :
+      string_sprintf("unknown variable \"%s\" after \"def:\"", name);
+    check_variable_error_message(name);
+    return NULL;
+    }
+      if (yield) *yield = (t[0] != 0) == testfor;
       }
-    if (yield != NULL) *yield = (value[0] != 0) == testfor;
-    }


-  return s;
+    return s;
+    }



   /* first_delivery tests for first delivery attempt */
@@ -3979,6 +3974,7 @@ while (*s != 0)
     int len;
     int newsize = 0;
     gstring * g = NULL;
+    uschar * t;


     s = read_name(name, sizeof(name), s, US"_");


@@ -3996,17 +3992,19 @@ while (*s != 0)

     /* Header */


-    if (Ustrncmp(name, "h_", 2) == 0 ||
-        Ustrncmp(name, "rh_", 3) == 0 ||
-        Ustrncmp(name, "bh_", 3) == 0 ||
-        Ustrncmp(name, "header_", 7) == 0 ||
-        Ustrncmp(name, "rheader_", 8) == 0 ||
-        Ustrncmp(name, "bheader_", 8) == 0)
+    if (  ( *(t = name) == 'h'
+          || (*t == 'r' || *t == 'l' || *t == 'b') && *++t == 'h'
+      )
+       && (*++t == '_' || Ustrncmp(t, "eader_", 6) == 0)
+       )
       {
-      BOOL want_raw = (name[0] == 'r')? TRUE : FALSE;
-      uschar *charset = (name[0] == 'b')? NULL : headers_charset;
+      unsigned flags = *name == 'r' ? FH_WANT_RAW
+              : *name == 'l' ? FH_WANT_RAW|FH_WANT_LIST
+              : 0;
+      uschar * charset = *name == 'b' ? NULL : headers_charset;
+
       s = read_header_name(name, sizeof(name), s);
-      value = find_header(name, FALSE, &newsize, want_raw, charset);
+      value = find_header(name, &newsize, flags, charset);


       /* If we didn't find the header, and the header contains a closing brace
       character, this may be a user error where the terminating colon
diff --git a/src/src/functions.h b/src/src/functions.h
index 3432d78..e333f4e 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -481,6 +481,7 @@ extern int     stdin_ungetc(int);
 extern gstring *string_append(gstring *, int, ...) WARN_UNUSED_RESULT;
 extern gstring *string_append_listele(gstring *, uschar, const uschar *) WARN_UNUSED_RESULT;
 extern gstring *string_append_listele_n(gstring *, uschar, const uschar *, unsigned) WARN_UNUSED_RESULT;
+extern gstring *string_append2_listele_n(gstring *, const uschar *, const uschar *, unsigned) WARN_UNUSED_RESULT;
 extern uschar *string_base62(unsigned long int);
 extern gstring *string_cat (gstring *, const uschar *     ) WARN_UNUSED_RESULT;
 extern gstring *string_catn(gstring *, const uschar *, int) WARN_UNUSED_RESULT;
diff --git a/src/src/string.c b/src/src/string.c
index 29a87c5..5a8d0e7 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -1051,6 +1051,26 @@ return list;




+/* A slightly-bogus listmaker utility; the separator is a string so
+can be multiple chars - there is no checking for the element content
+containing any of the separator. */
+
+gstring *
+string_append2_listele_n(gstring * list, const uschar * sepstr,
+ const uschar * ele, unsigned len)
+{
+const uschar * sp;
+
+if (list && list->ptr)
+ list = string_cat(list, sepstr);
+
+list = string_catn(list, ele, len);
+(void) string_from_gstring(list);
+return list;
+}
+
+
+
/************************************************/
/* Create a growable-string with some preassigned space */

diff --git a/test/confs/4560 b/test/confs/4560
index 3c31539..e2095af 100644
--- a/test/confs/4560
+++ b/test/confs/4560
@@ -26,8 +26,16 @@ check_data:
   warn    logwrite =    arc_state:      <$arc_state>
     logwrite =    domains:        <$arc_domains>
     logwrite =    arc_oldest_pass <$arc_oldest_pass>
-    condition =    ${if def:arc_state_reason}
     logwrite =    reason:         <$arc_state_reason>
+    logwrite =    lh_A-R:         <$lh_Authentication-Results:>
+    logwrite =    lh-ams:         <$lh_ARC-Authentication-Results:>
+#    logwrite =    oldest-p-ams:   <${listextract {$arc_oldest_pass} {$lh_ARC-Authentication-Results:}}>
+    logwrite =    oldest-p-ams:   <${reduce {$lh_ARC-Authentication-Results:} \
+                        {} \
+                        {${if = {$arc_oldest_pass} \
+                            {${extract {i}{${extract {1}{;}{$item}}}}} \
+                            {$item} {$value}}} \
+                    }>


 .ifdef OPTION
   accept
diff --git a/test/log/4560 b/test/log/4560
index 97677d4..153c3c7 100644
--- a/test/log/4560
+++ b/test/log/4560
@@ -4,6 +4,10 @@
 1999-03-02 09:44:33 10HmaX-0005vi-00 arc_state:      <pass>
 1999-03-02 09:44:33 10HmaX-0005vi-00 domains:        <test.ex>
 1999-03-02 09:44:33 10HmaX-0005vi-00 arc_oldest_pass <1>
+1999-03-02 09:44:33 10HmaX-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmaX-0005vi-00 lh_A-R:         < test.ex; arc=none>
+1999-03-02 09:44:33 10HmaX-0005vi-00 lh-ams:         < i=1; test.ex; arc=none>
+1999-03-02 09:44:33 10HmaX-0005vi-00 oldest-p-ams:   <i=1; test.ex; arc=none>
 1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? H=(xxx) [127.0.0.1] P=smtp S=sss ARC id=qwerty1234@??? for a@???
 1999-03-02 09:44:33 Start queue run: pid=pppp
 1999-03-02 09:44:33 10HmaX-0005vi-00 => a <a@???> R=d1 T=tfile
@@ -12,11 +16,19 @@
 1999-03-02 09:44:33 10HmaY-0005vi-00 arc_state:      <none>
 1999-03-02 09:44:33 10HmaY-0005vi-00 domains:        <>
 1999-03-02 09:44:33 10HmaY-0005vi-00 arc_oldest_pass <0>
+1999-03-02 09:44:33 10HmaY-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmaY-0005vi-00 lh_A-R:         <>
+1999-03-02 09:44:33 10HmaY-0005vi-00 lh-ams:         <>
+1999-03-02 09:44:33 10HmaY-0005vi-00 oldest-p-ams:   <>
 1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@??? H=(xxx) [127.0.0.1] P=smtp S=sss for za@???
 1999-03-02 09:44:33 Start queue run: pid=pppp
 1999-03-02 09:44:33 10HmaZ-0005vi-00 arc_state:      <pass>
 1999-03-02 09:44:33 10HmaZ-0005vi-00 domains:        <test.ex>
 1999-03-02 09:44:33 10HmaZ-0005vi-00 arc_oldest_pass <1>
+1999-03-02 09:44:33 10HmaZ-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmaZ-0005vi-00 lh_A-R:         < test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmaZ-0005vi-00 lh-ams:         < i=1; test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmaZ-0005vi-00 oldest-p-ams:   <i=1; test.ex;\n    arc=none>
 1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@??? H=localhost (test.ex) [127.0.0.1] P=esmtp S=sss ARC for a@???
 1999-03-02 09:44:33 10HmaY-0005vi-00 => a@??? <za@???> R=fwd T=tsmtp H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmaZ-0005vi-00"
 1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
@@ -28,11 +40,19 @@
 1999-03-02 09:44:33 10HmbA-0005vi-00 arc_state:      <none>
 1999-03-02 09:44:33 10HmbA-0005vi-00 domains:        <>
 1999-03-02 09:44:33 10HmbA-0005vi-00 arc_oldest_pass <0>
+1999-03-02 09:44:33 10HmbA-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmbA-0005vi-00 lh_A-R:         <>
+1999-03-02 09:44:33 10HmbA-0005vi-00 lh-ams:         <>
+1999-03-02 09:44:33 10HmbA-0005vi-00 oldest-p-ams:   <>
 1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@??? H=(xxx) [127.0.0.1] P=smtp S=sss for zza@???
 1999-03-02 09:44:33 Start queue run: pid=pppp
 1999-03-02 09:44:33 10HmbB-0005vi-00 arc_state:      <pass>
 1999-03-02 09:44:33 10HmbB-0005vi-00 domains:        <test.ex>
 1999-03-02 09:44:33 10HmbB-0005vi-00 arc_oldest_pass <1>
+1999-03-02 09:44:33 10HmbB-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmbB-0005vi-00 lh_A-R:         < test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbB-0005vi-00 lh-ams:         < i=1; test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbB-0005vi-00 oldest-p-ams:   <i=1; test.ex;\n    arc=none>
 1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@??? H=localhost (test.ex) [127.0.0.1] P=esmtp S=sss ARC for za@???
 1999-03-02 09:44:33 10HmbA-0005vi-00 => za@??? <zza@???> R=fwd T=tsmtp H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmbB-0005vi-00"
 1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
@@ -41,6 +61,10 @@
 1999-03-02 09:44:33 10HmbC-0005vi-00 arc_state:      <pass>
 1999-03-02 09:44:33 10HmbC-0005vi-00 domains:        <test.ex:test.ex>
 1999-03-02 09:44:33 10HmbC-0005vi-00 arc_oldest_pass <1>
+1999-03-02 09:44:33 10HmbC-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmbC-0005vi-00 lh_A-R:         < test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1: test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbC-0005vi-00 lh-ams:         < i=2; test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1: i=1; test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbC-0005vi-00 oldest-p-ams:   <i=1; test.ex;\n    arc=none>
 1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@??? H=localhost (test.ex) [127.0.0.1] P=esmtp S=sss ARC for a@???
 1999-03-02 09:44:33 10HmbB-0005vi-00 => a@??? <za@???> R=fwd T=tsmtp H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmbC-0005vi-00"
 1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
@@ -52,11 +76,19 @@
 1999-03-02 09:44:33 10HmbD-0005vi-00 arc_state:      <none>
 1999-03-02 09:44:33 10HmbD-0005vi-00 domains:        <>
 1999-03-02 09:44:33 10HmbD-0005vi-00 arc_oldest_pass <0>
+1999-03-02 09:44:33 10HmbD-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmbD-0005vi-00 lh_A-R:         <>
+1999-03-02 09:44:33 10HmbD-0005vi-00 lh-ams:         <>
+1999-03-02 09:44:33 10HmbD-0005vi-00 oldest-p-ams:   <>
 1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@??? H=(xxx) [127.0.0.1] P=smtp S=sss for zmza@???
 1999-03-02 09:44:33 Start queue run: pid=pppp
 1999-03-02 09:44:33 10HmbE-0005vi-00 arc_state:      <pass>
 1999-03-02 09:44:33 10HmbE-0005vi-00 domains:        <test.ex>
 1999-03-02 09:44:33 10HmbE-0005vi-00 arc_oldest_pass <1>
+1999-03-02 09:44:33 10HmbE-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmbE-0005vi-00 lh_A-R:         < test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbE-0005vi-00 lh-ams:         < i=1; test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbE-0005vi-00 oldest-p-ams:   <i=1; test.ex;\n    arc=none>
 1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@??? H=localhost (test.ex) [127.0.0.1] P=esmtp S=sss ARC for mza@???
 1999-03-02 09:44:33 10HmbD-0005vi-00 => mza@??? <zmza@???> R=fwd T=tsmtp H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmbE-0005vi-00"
 1999-03-02 09:44:33 10HmbD-0005vi-00 Completed
@@ -65,6 +97,10 @@
 1999-03-02 09:44:33 10HmbF-0005vi-00 arc_state:      <pass>
 1999-03-02 09:44:33 10HmbF-0005vi-00 domains:        <test.ex:test.ex>
 1999-03-02 09:44:33 10HmbF-0005vi-00 arc_oldest_pass <2>
+1999-03-02 09:44:33 10HmbF-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmbF-0005vi-00 lh_A-R:         < test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1: test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbF-0005vi-00 lh-ams:         < i=2; test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1: i=1; test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbF-0005vi-00 oldest-p-ams:   <i=2; test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1>
 1999-03-02 09:44:33 10HmbF-0005vi-00 <= CALLER@??? H=localhost (test.ex) [127.0.0.1] P=esmtp S=sss ARC for za@???
 1999-03-02 09:44:33 10HmbE-0005vi-00 => za@??? <mza@???> R=mlist T=tmlist H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmbF-0005vi-00"
 1999-03-02 09:44:33 10HmbE-0005vi-00 Completed
@@ -73,6 +109,10 @@
 1999-03-02 09:44:33 10HmbG-0005vi-00 arc_state:      <pass>
 1999-03-02 09:44:33 10HmbG-0005vi-00 domains:        <test.ex:test.ex:test.ex>
 1999-03-02 09:44:33 10HmbG-0005vi-00 arc_oldest_pass <2>
+1999-03-02 09:44:33 10HmbG-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmbG-0005vi-00 lh_A-R:         < test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=2) header.s=sel arc.oldest-pass=2 smtp.client-ip=127.0.0.1: test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1: test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbG-0005vi-00 lh-ams:         < i=3; test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=2) header.s=sel arc.oldest-pass=2 smtp.client-ip=127.0.0.1: i=2; test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1: i=1; test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbG-0005vi-00 oldest-p-ams:   <i=2; test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1>
 1999-03-02 09:44:33 10HmbG-0005vi-00 <= CALLER@??? H=localhost (test.ex) [127.0.0.1] P=esmtp S=sss ARC for a@???
 1999-03-02 09:44:33 10HmbF-0005vi-00 => a@??? <za@???> R=fwd T=tsmtp H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmbG-0005vi-00"
 1999-03-02 09:44:33 10HmbF-0005vi-00 Completed
@@ -84,11 +124,19 @@
 1999-03-02 09:44:33 10HmbH-0005vi-00 arc_state:      <none>
 1999-03-02 09:44:33 10HmbH-0005vi-00 domains:        <>
 1999-03-02 09:44:33 10HmbH-0005vi-00 arc_oldest_pass <0>
+1999-03-02 09:44:33 10HmbH-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmbH-0005vi-00 lh_A-R:         <>
+1999-03-02 09:44:33 10HmbH-0005vi-00 lh-ams:         <>
+1999-03-02 09:44:33 10HmbH-0005vi-00 oldest-p-ams:   <>
 1999-03-02 09:44:33 10HmbH-0005vi-00 <= CALLER@??? H=(xxx) [127.0.0.1] P=smtp S=sss for zzmza@???
 1999-03-02 09:44:33 Start queue run: pid=pppp
 1999-03-02 09:44:33 10HmbI-0005vi-00 arc_state:      <pass>
 1999-03-02 09:44:33 10HmbI-0005vi-00 domains:        <test.ex>
 1999-03-02 09:44:33 10HmbI-0005vi-00 arc_oldest_pass <1>
+1999-03-02 09:44:33 10HmbI-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmbI-0005vi-00 lh_A-R:         < test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbI-0005vi-00 lh-ams:         < i=1; test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbI-0005vi-00 oldest-p-ams:   <i=1; test.ex;\n    arc=none>
 1999-03-02 09:44:33 10HmbI-0005vi-00 <= CALLER@??? H=localhost (test.ex) [127.0.0.1] P=esmtp S=sss ARC for zmza@???
 1999-03-02 09:44:33 10HmbH-0005vi-00 => zmza@??? <zzmza@???> R=fwd T=tsmtp H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmbI-0005vi-00"
 1999-03-02 09:44:33 10HmbH-0005vi-00 Completed
@@ -97,6 +145,10 @@
 1999-03-02 09:44:33 10HmbJ-0005vi-00 arc_state:      <pass>
 1999-03-02 09:44:33 10HmbJ-0005vi-00 domains:        <test.ex:test.ex>
 1999-03-02 09:44:33 10HmbJ-0005vi-00 arc_oldest_pass <1>
+1999-03-02 09:44:33 10HmbJ-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmbJ-0005vi-00 lh_A-R:         < test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1: test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbJ-0005vi-00 lh-ams:         < i=2; test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1: i=1; test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbJ-0005vi-00 oldest-p-ams:   <i=1; test.ex;\n    arc=none>
 1999-03-02 09:44:33 10HmbJ-0005vi-00 <= CALLER@??? H=localhost (test.ex) [127.0.0.1] P=esmtp S=sss ARC for mza@???
 1999-03-02 09:44:33 10HmbI-0005vi-00 => mza@??? <zmza@???> R=fwd T=tsmtp H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmbJ-0005vi-00"
 1999-03-02 09:44:33 10HmbI-0005vi-00 Completed
@@ -106,6 +158,9 @@
 1999-03-02 09:44:33 10HmbK-0005vi-00 domains:        <test.ex:test.ex>
 1999-03-02 09:44:33 10HmbK-0005vi-00 arc_oldest_pass <0>
 1999-03-02 09:44:33 10HmbK-0005vi-00 reason:         <AMS body hash miscompare>
+1999-03-02 09:44:33 10HmbK-0005vi-00 lh_A-R:         < test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=2) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1: test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1: test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbK-0005vi-00 lh-ams:         < i=2; test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1: i=1; test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbK-0005vi-00 oldest-p-ams:   <>
 1999-03-02 09:44:33 10HmbK-0005vi-00 <= CALLER@??? H=localhost (test.ex) [127.0.0.1] P=esmtp S=sss for za@???
 1999-03-02 09:44:33 10HmbJ-0005vi-00 => za@??? <mza@???> R=mlist T=tmlist H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmbK-0005vi-00"
 1999-03-02 09:44:33 10HmbJ-0005vi-00 Completed
@@ -115,6 +170,9 @@
 1999-03-02 09:44:33 10HmbL-0005vi-00 domains:        <test.ex:test.ex:test.ex>
 1999-03-02 09:44:33 10HmbL-0005vi-00 arc_oldest_pass <0>
 1999-03-02 09:44:33 10HmbL-0005vi-00 reason:         <i=3 (cv)>
+1999-03-02 09:44:33 10HmbL-0005vi-00 lh_A-R:         < test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=fail (i=2)(AMS body hash miscompare) header.s=sel arc.oldest-pass=0 smtp.client-ip=127.0.0.1: test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=2) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1: test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1: test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbL-0005vi-00 lh-ams:         < i=3; test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=fail (i=2)(AMS body hash miscompare) header.s=sel arc.oldest-pass=0 smtp.client-ip=127.0.0.1: i=2; test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1: i=1; test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbL-0005vi-00 oldest-p-ams:   <>
 1999-03-02 09:44:33 10HmbL-0005vi-00 <= CALLER@??? H=localhost (test.ex) [127.0.0.1] P=esmtp S=sss for a@???
 1999-03-02 09:44:33 10HmbK-0005vi-00 => a@??? <za@???> R=fwd T=tsmtp H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmbL-0005vi-00"
 1999-03-02 09:44:33 10HmbK-0005vi-00 Completed
@@ -126,11 +184,19 @@
 1999-03-02 09:44:33 10HmbM-0005vi-00 arc_state:      <none>
 1999-03-02 09:44:33 10HmbM-0005vi-00 domains:        <>
 1999-03-02 09:44:33 10HmbM-0005vi-00 arc_oldest_pass <0>
+1999-03-02 09:44:33 10HmbM-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmbM-0005vi-00 lh_A-R:         <>
+1999-03-02 09:44:33 10HmbM-0005vi-00 lh-ams:         <>
+1999-03-02 09:44:33 10HmbM-0005vi-00 oldest-p-ams:   <>
 1999-03-02 09:44:33 10HmbM-0005vi-00 <= CALLER@??? H=(xxx) [127.0.0.1] P=smtp S=sss for zza@???
 1999-03-02 09:44:33 Start queue run: pid=pppp
 1999-03-02 09:44:33 10HmbN-0005vi-00 arc_state:      <pass>
 1999-03-02 09:44:33 10HmbN-0005vi-00 domains:        <test.ex>
 1999-03-02 09:44:33 10HmbN-0005vi-00 arc_oldest_pass <1>
+1999-03-02 09:44:33 10HmbN-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmbN-0005vi-00 lh_A-R:         < test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbN-0005vi-00 lh-ams:         < i=1; test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbN-0005vi-00 oldest-p-ams:   <i=1; test.ex;\n    arc=none>
 1999-03-02 09:44:33 10HmbN-0005vi-00 <= CALLER@??? H=localhost (test.ex) [127.0.0.1] P=esmtp S=sss ARC for za@???
 1999-03-02 09:44:33 10HmbM-0005vi-00 => za@??? <zza@???> R=fwd T=tsmtp H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmbN-0005vi-00"
 1999-03-02 09:44:33 10HmbM-0005vi-00 Completed
@@ -139,6 +205,10 @@
 1999-03-02 09:44:33 10HmbO-0005vi-00 arc_state:      <pass>
 1999-03-02 09:44:33 10HmbO-0005vi-00 domains:        <test.ex>
 1999-03-02 09:44:33 10HmbO-0005vi-00 arc_oldest_pass <1>
+1999-03-02 09:44:33 10HmbO-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmbO-0005vi-00 lh_A-R:         < test.ex;\n    iprev=pass (localhost) smtp.client-ip=127.0.0.1;\n    arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=127.0.0.1: test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbO-0005vi-00 lh-ams:         < i=1; test.ex;\n    arc=none>
+1999-03-02 09:44:33 10HmbO-0005vi-00 oldest-p-ams:   <i=1; test.ex;\n    arc=none>
 1999-03-02 09:44:33 10HmbO-0005vi-00 <= CALLER@??? H=localhost (test.ex) [127.0.0.1] P=esmtp S=sss ARC for a@???
 1999-03-02 09:44:33 10HmbN-0005vi-00 => a@??? <za@???> R=fwd T=tsmtp H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmbO-0005vi-00"
 1999-03-02 09:44:33 10HmbN-0005vi-00 Completed
@@ -153,6 +223,9 @@
 1999-03-02 09:44:33 10HmbP-0005vi-00 domains:        <convivian.com>
 1999-03-02 09:44:33 10HmbP-0005vi-00 arc_oldest_pass <0>
 1999-03-02 09:44:33 10HmbP-0005vi-00 reason:         <AMS body hash miscompare>
+1999-03-02 09:44:33 10HmbP-0005vi-00 lh_A-R:         < dragon.trusteddomain.org; sender-id=fail (NotPermitted) header.sender=arc-discuss-bounces@???; spf=fail (NotPermitted) smtp.mfrom=arc-discuss-bounces@???: dragon.trusteddomain.org; dkim=pass\n reason="1024-bit key"\n header.d=convivian.com header.i=@convivian.com header.b=LHXEAl5e;\n dkim-adsp=pass: dragon.trusteddomain.org;\n sender-id=pass header.from=jered@???;\n spf=pass smtp.mfrom=jered@???>
+1999-03-02 09:44:33 10HmbP-0005vi-00 lh-ams:         < i=1; mailhub.convivian.com; none>
+1999-03-02 09:44:33 10HmbP-0005vi-00 oldest-p-ams:   <>
 1999-03-02 09:44:33 10HmbP-0005vi-00 <= CALLER@??? H=(xxx) [127.0.0.1] P=smtp S=sss DKIM=dmarc.org id=1426665656.110316.1517535248039.JavaMail.zimbra@??? for za@???
 1999-03-02 09:44:33 Start queue run: pid=pppp
 1999-03-02 09:44:33 10HmbQ-0005vi-00 DKIM: d=dmarc.org s=clochette c=simple/simple a=rsa-sha256 b=1024 t=1517535263 [verification succeeded]
@@ -161,6 +234,9 @@
 1999-03-02 09:44:33 10HmbQ-0005vi-00 domains:        <convivian.com:test.ex>
 1999-03-02 09:44:33 10HmbQ-0005vi-00 arc_oldest_pass <0>
 1999-03-02 09:44:33 10HmbQ-0005vi-00 reason:         <i=2 (cv)>
+1999-03-02 09:44:33 10HmbQ-0005vi-00 lh_A-R:         < test.ex;\n    dkim=pass header.d=dmarc.org header.s=clochette header.a=rsa-sha256;\n    dkim=fail (body hash mismatch; body probably modified in transit)\n         header.d=convivian.com header.s=default header.a=rsa-sha256;\n    arc=fail (i=1)(AMS body hash miscompare) header.s=default arc.oldest-pass=0 smtp.client-ip=127.0.0.1: dragon.trusteddomain.org; sender-id=fail (NotPermitted) header.sender=arc-discuss-bounces@???; spf=fail (NotPermitted) smtp.mfrom=arc-discuss-bounces@???: dragon.trusteddomain.org; dkim=pass\n reason="1024-bit key"\n header.d=convivian.com header.i=@convivian.com header.b=LHXEAl5e;\n dkim-adsp=pass: dragon.trusteddomain.org;\n sender-id=pass header.from=jered@???;\n spf=pass smtp.mfrom=jered@???>
+1999-03-02 09:44:33 10HmbQ-0005vi-00 lh-ams:         < i=2; test.ex;\n    dkim=pass header.d=dmarc.org header.s=clochette header.a=rsa-sha256;\n    dkim=fail (body hash mismatch; body probably modified in transit)\n         header.d=convivian.com header.s=default header.a=rsa-sha256;\n    arc=fail (i=1)(AMS body hash miscompare) header.s=default arc.oldest-pass=0 smtp.client-ip=127.0.0.1: i=1; mailhub.convivian.com; none>
+1999-03-02 09:44:33 10HmbQ-0005vi-00 oldest-p-ams:   <>
 1999-03-02 09:44:33 10HmbQ-0005vi-00 <= CALLER@??? H=localhost (test.ex) [127.0.0.1] P=esmtp S=sss DKIM=dmarc.org id=1426665656.110316.1517535248039.JavaMail.zimbra@??? for a@???
 1999-03-02 09:44:33 10HmbP-0005vi-00 => a@??? <za@???> R=fwd T=tsmtp H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmbQ-0005vi-00"
 1999-03-02 09:44:33 10HmbP-0005vi-00 Completed
@@ -174,4 +250,8 @@
 1999-03-02 09:44:33 10HmbR-0005vi-00 arc_state:      <none>
 1999-03-02 09:44:33 10HmbR-0005vi-00 domains:        <>
 1999-03-02 09:44:33 10HmbR-0005vi-00 arc_oldest_pass <0>
+1999-03-02 09:44:33 10HmbR-0005vi-00 reason:         <>
+1999-03-02 09:44:33 10HmbR-0005vi-00 lh_A-R:         <>
+1999-03-02 09:44:33 10HmbR-0005vi-00 lh-ams:         <>
+1999-03-02 09:44:33 10HmbR-0005vi-00 oldest-p-ams:   <>
 1999-03-02 09:44:33 10HmbR-0005vi-00 <= CALLER@??? H=(xxx) [127.0.0.1] P=smtp S=sss for a@???
diff --git a/test/log/4561 b/test/log/4561
index 3770b2d..cdf581e 100644
--- a/test/log/4561
+++ b/test/log/4561
@@ -5,6 +5,9 @@
 1999-03-02 09:44:33 10HmaX-0005vi-00 domains:        <::test.ex>
 1999-03-02 09:44:33 10HmaX-0005vi-00 arc_oldest_pass <0>
 1999-03-02 09:44:33 10HmaX-0005vi-00 reason:         <(sequence; expected i=1)>
+1999-03-02 09:44:33 10HmaX-0005vi-00 lh_A-R:         < test.ex;\n     iprev=fail;\n     auth=pass (PLAIN) smtp.auth=fred@???>
+1999-03-02 09:44:33 10HmaX-0005vi-00 lh-ams:         < i=2; test.ex;\n     iprev=fail;\n     auth=pass (PLAIN) smtp.auth=fred@???>
+1999-03-02 09:44:33 10HmaX-0005vi-00 oldest-p-ams:   <>
 1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? H=(xxx) [127.0.0.1] P=smtp S=sss id=3885245d-3bae-66a2-7a1e-0dbceae2fb50@??? for a@???
 1999-03-02 09:44:33 Start queue run: pid=pppp
 1999-03-02 09:44:33 10HmaX-0005vi-00 => a <a@???> R=d1 T=tfile