]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.2-20160728
authorWietse Venema <wietse@porcupine.org>
Thu, 28 Jul 2016 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <postfix-users@dukhovni.org>
Sun, 31 Jul 2016 20:38:00 +0000 (16:38 -0400)
17 files changed:
postfix/.indent.pro
postfix/HISTORY
postfix/README_FILES/XCLIENT_README
postfix/RELEASE_NOTES
postfix/WISHLIST
postfix/html/XCLIENT_README.html
postfix/html/postconf.5.html
postfix/man/man5/postconf.5
postfix/proto/XCLIENT_README.html
postfix/proto/postconf.proto
postfix/src/global/mail_version.h
postfix/src/postscreen/postscreen.c
postfix/src/postscreen/postscreen.h
postfix/src/postscreen/postscreen_early.c
postfix/src/postscreen/postscreen_misc.c
postfix/src/postscreen/postscreen_state.c
postfix/src/postscreen/postscreen_tests.c

index 5502f64db306cf54471f4173d96e9c7c8b52ae88..06435a2a6459772c0e186c10da724b12f7251553 100644 (file)
 -TPOST_MAIL_STATE
 -TPRIVATE_STR_TABLE
 -TPSC_CALL_BACK_ENTRY
+-TPSC_CLIENT_INFO
 -TPSC_DNSBL_HEAD
 -TPSC_DNSBL_SCORE
 -TPSC_DNSBL_SITE
index 7246e352951b6818c347552cd65850c53596469d..741ec1c245de9ecfbda89b3aca3f6d6974989e94 100644 (file)
@@ -1,8 +1,4 @@
 In addition to the names listed below, the following people provided
-
-20160618
-
-       Bugfix: 
 useful inputs on many occasions: Paul D. Robertson, Simon J. Mudd.
 Apologies for any names omitted.
 
@@ -22384,7 +22380,7 @@ Apologies for any names omitted.
 
 20160618
 
-       Bugfix(introduced: 20091121): with the introduction of
+       Bugfix (introduced: 20091121): with the introduction of
        sender_dependent_default_transport_maps, the SMTP daemon
        was not updated. This resulted in false rejects with
        sender-dependent "error" transports. Based on a fix by
@@ -22411,3 +22407,15 @@ Apologies for any names omitted.
        xsasl/xsasl_server.c.
 
        Cleanup: dnsblog manpage. File: dnsblog/dnsblog.c.
+
+20160728
+
+       Bugfix (introduced: 20090614): with concurrent connections
+       from the same client IP address, and after-220 tests enabled,
+       postscreen could overwrite the cached "all tests completed"
+       result of one connection that completed the after-220 tests,
+       with the "some tests not completed" result of a concurrent
+       connection where the client hung up before completing the
+       after-220 tests.  Files: postscreen_misc.c, postscreen_state.c,
+       postscreen.h, postscreen_tests.c, postscreen.c, postscreen_smtpd.c,
+       postscreen_early.c.
index 439dd4aa7edcc3a2f16210d751c6f88d1bb3d5d1..89b11bff6d83a89da98cc3b2e810663915701ada 100644 (file)
@@ -50,7 +50,8 @@ are in fact case insensitive.
 
     xclient-command = XCLIENT 1*( SP attribute-name"="attribute-value )
 
-    attribute-name = ( NAME | ADDR | PORT | PROTO | HELO | LOGIN )
+    attribute-name = ( NAME | ADDR | PORT | PROTO | HELO | LOGIN | DESTADDR |
+    DESTPORT )
 
     attribute-value = xtext
 
index 94fab09e306a4a16e5f876b9e63671abe1b6d1f8..1fe8d8331dc2f663d123fc347fe65c5ba992bfde 100644 (file)
@@ -16,6 +16,29 @@ specifies the release date of a stable release or snapshot release.
 If you upgrade from Postfix 3.0 or earlier, read RELEASE_NOTES-3.1
 before proceeding.
 
+Major changes with snapshot 20160625
+====================================
+
+Support in the Postfix SMTP server for propagating the local SMTP
+server IP address and port. This affects the following Postfix
+interfaces:
+
+- Policy delegation. The server address and port are available as
+"server_address" and "server_port". See SMTPD_POLICY_README for an
+overview of available attributes.
+
+- Milter applications. The server address and port are available
+as "{daemon_addr}" and "{daemon_port}". See MILTER_README for a
+table of available attributes.
+
+- Cyrus SASL. The server address and port are now passed to the
+sasl_server_new() function as "ipaddress;port".
+
+- XCLIENT protocol. The server address and port can be specified
+as "DESTADDR" and "DESTPORT". See XCLIENT_README for a description
+of the attribute syntax. The new attributes may be of interest for
+nxginx.
+
 Major changes with snapshot 20160527
 ====================================
 
index c9f6558cd43d120a2e4a6ded460c3df74ea1b06d..1df78719e5f6b75c56acef9c4361ab24293b686f 100644 (file)
@@ -6,6 +6,9 @@ Wish list:
 
        Disable -DSNAPSHOT and -DNONPROD in makedefs.
 
+       Why does postqueue show UTC time, even if TZ is set in
+       the import_environment setting?
+
        Propagate SMTPD_PEER_CODE_XXX from smtpd(8) to cleanup(8),
        so that {client_resolve} and {_} produce consistent results.
 
@@ -13,7 +16,8 @@ Wish list:
 
        Modeline support in config files to enable/disable trailing
        #comment, and to give hints about how to handle an LHS or
-       RHS.
+       RHS. This will not preserve trailing comments in lines that
+       are modified with "postconf -e" and the like.
 
        The cleanup daemon searches canonical_maps and virtual_alias_maps
        with quoted address forms. The address local part should
index 60724dd36c55798b01365955567ad630e8ac3025..a58a32140d7d370e310daa13056015a4cc0be4a2 100644 (file)
@@ -80,7 +80,7 @@ names are shown in upper case, they are in fact case insensitive.
     xclient-command = XCLIENT 1*( SP attribute-name"="attribute-value )
 </p>
 <p>
-    attribute-name = ( NAME | ADDR | PORT | PROTO | HELO | LOGIN )
+    attribute-name = ( NAME | ADDR | PORT | PROTO | HELO | LOGIN | DESTADDR | DESTPORT )
 </p>
 <p>
     attribute-value = xtext
index 35ae0188c4498e4bcafb85a0a440457e1c96e48a..1134ae35f861cfa37e4f4361ebbf6767bd961a17 100644 (file)
@@ -17335,8 +17335,8 @@ errors while accessing the Postfix <a href="postconf.5.html">main.cf</a> configu
 (default: see "postconf -d" output)</b></DT><DD>
 
 <p>
-The mail system name that is prepended to the process name in syslog
-records, so that "smtpd" becomes, for example, "postfix/smtpd".
+A prefix that is prepended to the process name in syslog
+records, so that, for example, "smtpd" becomes "prefix/smtpd".
 </p>
 
 <p>
index e0dd9d1dbe2f44994c682b2496af3a1d2f890f33..a22a09927fb9365b3c3b36b9bd625ac83631f6a4 100644 (file)
@@ -11886,8 +11886,8 @@ process initialization will be logged with the default facility.
 Examples are errors while parsing the command line arguments, and
 errors while accessing the Postfix main.cf configuration file.
 .SH syslog_name (default: see "postconf \-d" output)
-The mail system name that is prepended to the process name in syslog
-records, so that "smtpd" becomes, for example, "postfix/smtpd".
+A prefix that is prepended to the process name in syslog
+records, so that, for example, "smtpd" becomes "prefix/smtpd".
 .PP
 Warning: a non\-default syslog_name setting takes effect only after
 a Postfix process has completed initialization. Errors during
index 5873662f7752117fe278769f022d934576039ce6..e4e2f0a1f92ee439d0e4650a0dbbbba4940e280d 100644 (file)
@@ -80,7 +80,7 @@ names are shown in upper case, they are in fact case insensitive.
     xclient-command = XCLIENT 1*( SP attribute-name"="attribute-value )
 </p>
 <p>
-    attribute-name = ( NAME | ADDR | PORT | PROTO | HELO | LOGIN )
+    attribute-name = ( NAME | ADDR | PORT | PROTO | HELO | LOGIN | DESTADDR | DESTPORT )
 </p>
 <p>
     attribute-value = xtext
index 9ead6ea9e9a01ea8d4fd7246194aa58643413daf..eb62ab0ea6b4ebf99f1b6cfb4f4bb49e8e590370 100644 (file)
@@ -6746,8 +6746,8 @@ errors while accessing the Postfix main.cf configuration file.
 %PARAM syslog_name see "postconf -d" output
 
 <p>
-The mail system name that is prepended to the process name in syslog
-records, so that "smtpd" becomes, for example, "postfix/smtpd".
+A prefix that is prepended to the process name in syslog
+records, so that, for example, "smtpd" becomes "prefix/smtpd".
 </p>
 
 <p>
index f26efffd3bc6f56ee204dc95deb66d17761db9b7..f723684bfb0d28611c2815a7dda5d9f0596ae3a5 100644 (file)
@@ -20,7 +20,7 @@
   * Patches change both the patchlevel and the release date. Snapshots have no
   * patchlevel; they change the release date only.
   */
-#define MAIL_RELEASE_DATE      "20160625"
+#define MAIL_RELEASE_DATE      "20160728"
 #define MAIL_VERSION_NUMBER    "3.2"
 
 #ifdef SNAPSHOT
index 188d2842347872dded11413d0c7bcef39120f748..4b766d4f043d93f115882132cef899bbf8d0efae 100644 (file)
@@ -592,6 +592,9 @@ static void psc_drain(char *unused_service, char **unused_argv)
      * 
      * XXX Some Berkeley DB versions break with close-after-fork. Every new
      * version is an improvement over its predecessor.
+     * 
+     * XXX Don't assume that it is OK to share the same LMDB lockfile descriptor
+     * between different processes.
      */
     if (psc_cache_map != 0                     /* XXX && psc_cache_map
            requires locking */ ) {
@@ -766,6 +769,7 @@ static void psc_endpt_lookup_done(int endpt_status,
      * valid.
      */
     if ((state->flags & PSC_STATE_MASK_ANY_FAIL) == 0
+       && state->client_info->concurrency == 1
        && psc_cache_map != 0
        && (stamp_str = psc_cache_lookup(psc_cache_map, state->smtp_client_addr)) != 0) {
        saved_flags = state->flags;
@@ -779,6 +783,13 @@ static void psc_endpt_lookup_done(int endpt_status,
            psc_conclude(state);
            return;
        }
+    } else if (state->client_info->concurrency > 1) {
+       saved_flags = state->flags;
+       psc_todo_tests(state, event_time());
+       state->flags |= saved_flags;
+       if (msg_verbose)
+           msg_info("%s: new + recent flags: %s",
+                    myname, psc_print_state_flags(state->flags, myname));
     } else {
        saved_flags = state->flags;
        psc_new_tests(state);
@@ -830,6 +841,7 @@ static int psc_cache_validator(const char *client_addr,
                                       void *unused_context)
 {
     PSC_STATE dummy;
+    PSC_CLIENT_INFO dummy_client_info;
 
     /*
      * This function is called by the cache cleanup pseudo thread.
@@ -839,6 +851,7 @@ static int psc_cache_validator(const char *client_addr,
      * silly logging we remove the cache entry only after all tests have
      * expired longer ago than the cache retention time.
      */
+    dummy.client_info = &dummy_client_info;
     psc_parse_tests(&dummy, stamp_str, event_time() - var_psc_cache_ret);
     return ((dummy.flags & PSC_STATE_MASK_ANY_TODO) == 0);
 }
index 2e4585b5c3b439b3594212f826fca01b314d0393..a02fa7fac9990ba18f17cd09eb87c1c7e4fb6900 100644 (file)
 
 #define PSC_TINDX_BYTNAME(tname) (PSC_TINDX_ ## tname)
 
+ /*
+  * Per-client shared state.
+  */
+typedef struct {
+    int     concurrency;               /* per-client */
+    int     pass_new_count;            /* per-client */
+    time_t  expire_time[PSC_TINDX_COUNT];      /* per-test expiration */
+} PSC_CLIENT_INFO;
+
  /*
   * Per-session state.
   */
@@ -66,13 +75,12 @@ typedef struct {
     char   *smtp_client_port;          /* client port */
     char   *smtp_server_addr;          /* server address */
     char   *smtp_server_port;          /* server port */
-    int     client_concurrency;                /* per-client */
     const char *final_reply;           /* cause for hanging up */
     VSTRING *send_buf;                 /* pending output */
     /* Test context. */
     struct timeval start_time;         /* start of current test */
     const char *test_name;             /* name of current test */
-    time_t  expire_time[PSC_TINDX_COUNT];      /* per-test expiration */
+    PSC_CLIENT_INFO *client_info;      /* shared client state */
     VSTRING *dnsbl_reply;              /* dnsbl reject text */
     int     dnsbl_score;               /* saved DNSBL score */
     int     dnsbl_ttl;                 /* saved DNSBL TTL */
@@ -95,11 +103,14 @@ typedef struct {
   * Emulate legacy ad-hoc variables on top of indexable time stamps. This
   * avoids massive scar tissue during initial feature development.
   */
-#define pregr_stamp    expire_time[PSC_TINDX_PREGR]
-#define dnsbl_stamp    expire_time[PSC_TINDX_DNSBL]
-#define pipel_stamp    expire_time[PSC_TINDX_PIPEL]
-#define nsmtp_stamp    expire_time[PSC_TINDX_NSMTP]
-#define barlf_stamp    expire_time[PSC_TINDX_BARLF]
+#define pregr_stamp    client_info->expire_time[PSC_TINDX_PREGR]
+#define dnsbl_stamp    client_info->expire_time[PSC_TINDX_DNSBL]
+#define pipel_stamp    client_info->expire_time[PSC_TINDX_PIPEL]
+#define nsmtp_stamp    client_info->expire_time[PSC_TINDX_NSMTP]
+#define barlf_stamp    client_info->expire_time[PSC_TINDX_BARLF]
+
+ /* Minize the patch size for stable releases. */
+#define client_concurrency client_info->concurrency
 
  /*
   * Special expiration time values.
@@ -489,17 +500,21 @@ extern int psc_dnsbl_request(const char *, void (*) (int, void *), void *);
 #define PSC_INIT_TESTS(dst) do { \
        time_t *_it_stamp_p; \
        (dst)->flags = 0; \
-       for (_it_stamp_p = (dst)->expire_time; \
-           _it_stamp_p < (dst)->expire_time + PSC_TINDX_COUNT; \
+       for (_it_stamp_p = (dst)->client_info->expire_time; \
+           _it_stamp_p < (dst)->client_info->expire_time + PSC_TINDX_COUNT; \
            _it_stamp_p++) \
            *_it_stamp_p = PSC_TIME_STAMP_INVALID; \
     } while (0)
+#define PSC_INIT_TEST_FLAGS_ONLY(dst) do { \
+       (dst)->flags = 0; \
+    } while (0)
 #define PSC_BEGIN_TESTS(state, name) do { \
        (state)->test_name = (name); \
        GETTIMEOFDAY(&(state)->start_time); \
     } while (0)
 extern void psc_new_tests(PSC_STATE *);
 extern void psc_parse_tests(PSC_STATE *, const char *, time_t);
+extern void psc_todo_tests(PSC_STATE *, time_t);
 extern char *psc_print_tests(VSTRING *, PSC_STATE *);
 extern char *psc_print_grey_key(VSTRING *, const char *, const char *,
                                        const char *, const char *);
index 36c3d5cd6ff871ec416e107c4e8af5ec8cef28f4..b5bb50e14208e4de32c3c370285316c829628e76 100644 (file)
@@ -92,8 +92,8 @@ static void psc_whitelist_non_dnsbl(PSC_STATE *state)
                state->flags |= PSC_STATE_FLAG_BYTINDX_PASS(tindx);
            }
            /* Update expiration even if the test was completed or disabled. */
-           if (state->expire_time[tindx] < now + state->dnsbl_ttl)
-               state->expire_time[tindx] = now + state->dnsbl_ttl;
+           if (state->client_info->expire_time[tindx] < now + state->dnsbl_ttl)
+               state->client_info->expire_time[tindx] = now + state->dnsbl_ttl;
        }
     }
 }
index 57a1bb67ddcf82adce9c1971c4bc6bc6c443856f..95cd6e9a81a6dd2e986f89aca34638ffed1fdb5c 100644 (file)
@@ -105,7 +105,8 @@ void    psc_conclude(PSC_STATE *state)
     if ((state->flags & PSC_STATE_MASK_ANY_PASS) != 0
        && (state->flags & PSC_STATE_MASK_ANY_PASS) ==
        PSC_STATE_FLAGS_TODO_TO_PASS(state->flags & PSC_STATE_MASK_ANY_TODO))
-       msg_info("PASS %s [%s]:%s", (state->flags & PSC_STATE_FLAG_NEW) == 0 ?
+       msg_info("PASS %s [%s]:%s", (state->flags & PSC_STATE_FLAG_NEW) == 0
+                || state->client_info->pass_new_count++ > 0 ?
                 "OLD" : "NEW", PSC_CLIENT_ADDR_PORT(state));
 
     /*
@@ -114,7 +115,7 @@ void    psc_conclude(PSC_STATE *state)
      * that client does not "fail" any test.
      */
     if ((state->flags & PSC_STATE_MASK_ANY_UPDATE) != 0
-       && psc_cache_map != 0) {
+       && psc_cache_map != 0 && state->client_info->pass_new_count <= 1) {
        psc_print_tests(psc_temp, state);
        psc_cache_update(psc_cache_map, state->smtp_client_addr, STR(psc_temp));
     }
index 946afd6fa58c5170b5cd4ec16a6584f8d2f98e9a..b84f6656be72c199982adfe441e1964da8a4a2d7 100644 (file)
@@ -61,7 +61,8 @@
 /*     psc_new_session_state() creates a new session state object
 /*     for the specified client stream, and increments the
 /*     psc_check_queue_length counter.  The flags and per-test time
-/*     stamps are initialized with PSC_INIT_TESTS().  The addr and
+/*     stamps are initialized with PSC_INIT_TESTS(), or for concurrent
+/*     sessions, with PSC_INIT_TEST_FLAGS_ONLY().  The addr and
 /*     port arguments are null-terminated strings with the remote
 /*     SMTP client endpoint. The _reply members are set to
 /*     polite "try again" SMTP replies. The protocol member is set
@@ -149,10 +150,8 @@ PSC_STATE *psc_new_session_state(VSTREAM *stream,
                                         const char *server_port)
 {
     PSC_STATE *state;
-    HTABLE_INFO *ht;
 
     state = (PSC_STATE *) mymalloc(sizeof(*state));
-    PSC_INIT_TESTS(state);
     if ((state->smtp_client_stream = stream) != 0)
        psc_check_queue_length++;
     state->smtp_server_fd = (-1);
@@ -188,10 +187,19 @@ PSC_STATE *psc_new_session_state(VSTREAM *stream,
     /*
      * Update the per-client session count.
      */
-    if ((ht = htable_locate(psc_client_concurrency, client_addr)) == 0)
-       ht = htable_enter(psc_client_concurrency, client_addr, (void *) 0);
-    ht->value += 1;
-    state->client_concurrency = CAST_ANY_PTR_TO_INT(ht->value);
+    if ((state->client_info = (PSC_CLIENT_INFO *)
+        htable_find(psc_client_concurrency, client_addr)) == 0) {
+       state->client_info = (PSC_CLIENT_INFO *)
+           mymalloc(sizeof(state->client_info[0]));
+       (void) htable_enter(psc_client_concurrency, client_addr,
+                           (void *) state->client_info);
+       PSC_INIT_TESTS(state);
+       state->client_info->concurrency = 1;
+       state->client_info->pass_new_count = 0;
+    } else {
+       PSC_INIT_TEST_FLAGS_ONLY(state);
+       state->client_info->concurrency += 1;
+    }
 
     return (state);
 }
@@ -210,9 +218,8 @@ void    psc_free_session_state(PSC_STATE *state)
                            state->smtp_client_addr)) == 0)
        msg_panic("%s: unknown client address: %s",
                  myname, state->smtp_client_addr);
-    if (--(ht->value) == 0)
-       htable_delete(psc_client_concurrency, state->smtp_client_addr,
-                     (void (*) (void *)) 0);
+    if (--(state->client_info->concurrency) == 0)
+       htable_delete(psc_client_concurrency, state->smtp_client_addr, myfree);
 
     if (state->smtp_client_stream != 0) {
        event_server_disconnect(state->smtp_client_stream);
index 17b7a3926149a951c637d6a160d72a03c0c6a10c..f8b92a9888ece7200c9733b3147edf4d158074af 100644 (file)
 /*     const char *stamp_text;
 /*     time_t time_value;
 /*
+/*     void    psc_todo_tests(state, time_value)
+/*     PSC_STATE *state;
+/*     const char *stamp_text;
+/*     time_t time_value;
+/*
 /*     char    *psc_print_tests(buffer, state)
 /*     VSTRING *buffer;
 /*     PSC_STATE *state;
 /*     zeroes all the flags bits. These values are not meant to
 /*     be stored into the postscreen(8) cache.
 /*
+/*     PSC_INIT_TEST_FLAGS_ONLY() zeroes all the flag bits.  It
+/*     should be used when the time stamps are already initialized.
+/*
 /*     psc_new_tests() sets all test expiration time stamps to
-/*     PSC_TIME_STAMP_NEW, and overwrites all flags bits. Only
-/*     enabled tests are flagged with PSC_STATE_FLAG_TODO; the
-/*     object is flagged with PSC_STATE_FLAG_NEW.
+/*     PSC_TIME_STAMP_NEW, and invokes psc_todo_tests().
+/*
+/*     psc_parse_tests() parses a cache file record and invokes
+/*     psc_todo_tests().
 /*
-/*     psc_parse_tests() parses a cache file record and overwrites
-/*     all flags bits. Tests are considered "expired" when they
+/*     psc_todo_tests() overwrites all per-session flag bits, and
+/*     populates the flags based on test expiration time stamp
+/*     information.  Tests are considered "expired" when they
 /*     would be expired at the specified time value. Only enabled
 /*     tests are flagged as "expired"; the object is flagged as
 /*     "new" if some enabled tests have "new" time stamps.
 void    psc_new_tests(PSC_STATE *state)
 {
 
-    /*
-     * We know this client is brand new.
-     */
-    state->flags = PSC_STATE_FLAG_NEW;
-
     /*
      * Give all tests a PSC_TIME_STAMP_NEW time stamp, so that we can later
      * recognize cache entries that haven't passed all enabled tests. When we
@@ -142,19 +147,9 @@ void    psc_new_tests(PSC_STATE *state)
     state->barlf_stamp = PSC_TIME_STAMP_NEW;
 
     /*
-     * Don't flag disabled tests as "todo", because there would be no way to
-     * make those bits go away.
+     * Determine what tests need to be completed.
      */
-    if (PSC_PREGR_TEST_ENABLE())
-       state->flags |= PSC_STATE_FLAG_PREGR_TODO;
-    if (PSC_DNSBL_TEST_ENABLE())
-       state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
-    if (var_psc_pipel_enable)
-       state->flags |= PSC_STATE_FLAG_PIPEL_TODO;
-    if (var_psc_nsmtp_enable)
-       state->flags |= PSC_STATE_FLAG_NSMTP_TODO;
-    if (var_psc_barlf_enable)
-       state->flags |= PSC_STATE_FLAG_BARLF_TODO;
+    psc_todo_tests(state, PSC_TIME_STAMP_NEW + 1);
 }
 
 /* psc_parse_tests - parse test results from cache */
@@ -165,29 +160,18 @@ void    psc_parse_tests(PSC_STATE *state,
 {
     const char *start = stamp_str;
     char   *cp;
-    time_t *time_stamps = state->expire_time;
+    time_t *time_stamps = state->client_info->expire_time;
     time_t *sp;
 
-    /*
-     * We don't know what tests have expired or have never passed.
-     */
-    state->flags = 0;
-
     /*
      * Parse the cache entry, and allow for older postscreen versions that
      * implemented fewer tests. We pretend that the newer tests were disabled
      * at the time that the cache entry was written.
-     * 
-     * Flag the cache entry as "new" when the cache entry has fields for all
-     * enabled tests, but the remote SMTP client has not yet passed all those
-     * tests.
      */
     for (sp = time_stamps; sp < time_stamps + PSC_TINDX_COUNT; sp++) {
        *sp = strtoul(start, &cp, 10);
        if (*start == 0 || (*cp != '\0' && *cp != ';') || errno == ERANGE)
            *sp = PSC_TIME_STAMP_DISABLED;
-       if (*sp == PSC_TIME_STAMP_NEW)
-           state->flags |= PSC_STATE_FLAG_NEW;
        if (msg_verbose)
            msg_info("%s -> %lu", start, (unsigned long) *sp);
        if (*cp == ';')
@@ -196,6 +180,34 @@ void    psc_parse_tests(PSC_STATE *state,
            start = cp;
     }
 
+    /*
+     * Determine what tests need to be completed.
+     */
+    psc_todo_tests(state, time_value);
+}
+
+/* psc_todo_tests - determine what tests to perform */
+
+void    psc_todo_tests(PSC_STATE *state, time_t time_value)
+{
+    time_t *time_stamps = state->client_info->expire_time;
+    time_t *sp;
+
+    /*
+     * Reset all per-session flags.
+     */
+    state->flags = 0;
+
+    /*
+     * Flag the tests as "new" when the cache entry has fields for all
+     * enabled tests, but the remote SMTP client has not yet passed all those
+     * tests.
+     */
+    for (sp = time_stamps; sp < time_stamps + PSC_TINDX_COUNT; sp++) {
+       if (*sp == PSC_TIME_STAMP_NEW)
+           state->flags |= PSC_STATE_FLAG_NEW;
+    }
+
     /*
      * Don't flag disabled tests as "todo", because there would be no way to
      * make those bits go away.