+uschar keyhash[MD5_HASHLEN];
+uschar innerhash[MD5_HASHLEN];
+uschar finalhash[MD5_HASHLEN];
+uschar innerkey[MD5_HASHBLOCKLEN];
+uschar outerkey[MD5_HASHBLOCKLEN];
+
+keyptr = key;
+keylen = Ustrlen(keyptr);
+
+/* If the key is longer than the hash block length, then hash the key
+first */
+
+if (keylen > MD5_HASHBLOCKLEN)
+ {
+ chash_start(HMAC_MD5, &md5_base);
+ chash_end(HMAC_MD5, &md5_base, keyptr, keylen, keyhash);
+ keyptr = keyhash;
+ keylen = MD5_HASHLEN;
+ }
+
+/* Now make the inner and outer key values */
+
+memset(innerkey, 0x36, MD5_HASHBLOCKLEN);
+memset(outerkey, 0x5c, MD5_HASHBLOCKLEN);
+
+for (int i = 0; i < keylen; i++)
+ {
+ innerkey[i] ^= keyptr[i];
+ outerkey[i] ^= keyptr[i];
+ }
+
+/* Now do the hashes */
+
+chash_start(HMAC_MD5, &md5_base);
+chash_mid(HMAC_MD5, &md5_base, innerkey);
+chash_end(HMAC_MD5, &md5_base, src, Ustrlen(src), innerhash);
+
+chash_start(HMAC_MD5, &md5_base);
+chash_mid(HMAC_MD5, &md5_base, outerkey);
+chash_end(HMAC_MD5, &md5_base, innerhash, MD5_HASHLEN, finalhash);
+
+/* Encode the final hash as a hex string, limited by output buffer size */
+
+p = buf;
+for (int i = 0, j = len; i < MD5_HASHLEN; i++)
+ {
+ if (j-- <= 0) break;
+ *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
+ if (j-- <= 0) break;
+ *p++ = hex_digits[finalhash[i] & 0x0f];
+ }
+return;
+}
+
+
+/*************************************************
+* Read and evaluate a condition *
+*************************************************/
+
+/*
+Arguments:
+ s points to the start of the condition text
+ resetok points to a BOOL which is written false if it is unsafe to
+ free memory. Certain condition types (acl) may have side-effect
+ allocation which must be preserved.
+ yield points to a BOOL to hold the result of the condition test;
+ if NULL, we are just reading through a condition that is
+ part of an "or" combination to check syntax, or in a state
+ where the answer isn't required
+
+Returns: a pointer to the first character after the condition, or
+ NULL after an error
+*/
+
+static const uschar *
+eval_condition(const uschar *s, BOOL *resetok, BOOL *yield)
+{
+BOOL testfor = TRUE;
+BOOL tempcond, combined_cond;
+BOOL *subcondptr;
+BOOL sub2_honour_dollar = TRUE;
+BOOL is_forany, is_json, is_jsons;
+int rc, cond_type, roffset;
+int_eximarith_t num[2];
+struct stat statbuf;
+uschar * opname;
+uschar name[256];
+const uschar *sub[10];
+
+const pcre *re;
+const uschar *rerror;
+
+for (;;)
+ {
+ while (isspace(*s)) s++;
+ if (*s == '!') { testfor = !testfor; s++; } else break;
+ }
+
+switch(cond_type = identify_operator(&s, &opname))
+ {
+ /* def: tests for a non-empty variable, or for the existence of a header. If
+ yield == NULL we are in a skipping state, and don't care about the answer. */
+
+ case ECOND_DEF:
+ {
+ uschar * t;
+
+ if (*s != ':')
+ {
+ expand_string_message = US"\":\" expected after \"def\"";
+ return NULL;
+ }
+
+ s = read_name(name, sizeof(name), s+1, US"_");
+
+ /* 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. */
+
+ if ( ( *(t = name) == 'h'
+ || (*t == 'r' || *t == 'l' || *t == 'b') && *++t == 'h'
+ )
+ && (*++t == '_' || Ustrncmp(t, "eader_", 6) == 0)
+ )
+ {
+ s = read_header_name(name, sizeof(name), s);
+ /* {-for-text-editors */
+ if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
+ if (yield) *yield =
+ (find_header(name, NULL, FH_EXISTS_ONLY, NULL) != NULL) == testfor;
+ }
+
+ /* Test for a variable's having a non-empty value. A non-existent variable
+ causes an expansion failure. */
+
+ else
+ {
+ if (!(t = find_variable(name, TRUE, yield == NULL, NULL)))
+ {
+ expand_string_message = name[0]
+ ? string_sprintf("unknown variable \"%s\" after \"def:\"", name)
+ : US"variable name omitted after \"def:\"";
+ check_variable_error_message(name);
+ return NULL;
+ }
+ if (yield) *yield = (t[0] != 0) == testfor;
+ }
+
+ return s;
+ }
+
+
+ /* first_delivery tests for first delivery attempt */
+
+ case ECOND_FIRST_DELIVERY:
+ if (yield) *yield = f.deliver_firsttime == testfor;
+ return s;
+
+
+ /* queue_running tests for any process started by a queue runner */
+
+ case ECOND_QUEUE_RUNNING:
+ if (yield) *yield = (queue_run_pid != (pid_t)0) == testfor;
+ return s;
+
+
+ /* exists: tests for file existence
+ isip: tests for any IP address
+ isip4: tests for an IPv4 address