]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.12-20260413 master
authorWietse Z Venema <wietse@porcupine.org>
Mon, 13 Apr 2026 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <ietf-dane@dukhovni.org>
Tue, 14 Apr 2026 06:01:22 +0000 (16:01 +1000)
39 files changed:
postfix/HISTORY
postfix/html/tlsproxy.8.html
postfix/man/man8/tlsproxy.8
postfix/proto/stop.double-cc
postfix/proto/stop.double-history
postfix/proto/stop.spell-cc
postfix/src/dns/dns.h
postfix/src/dns/dns_lookup.c
postfix/src/dns/dns_rr.c
postfix/src/dns/dns_rr_test.c
postfix/src/global/dict_pgsql.c
postfix/src/global/haproxy_srvr.c
postfix/src/global/mail_version.h
postfix/src/global/mime_state.c
postfix/src/global/pipe_command.c
postfix/src/global/rfc2047_code.c
postfix/src/local/dotforward.c
postfix/src/postscreen/postscreen_dnsbl_test.c
postfix/src/proxymap/proxymap.c
postfix/src/smtp/smtp_proto.c
postfix/src/smtpd/smtpd_expand.c
postfix/src/testing/mock_dns_lookup.c
postfix/src/tlsproxy/tlsproxy.c
postfix/src/tlsproxy/tlsproxy.h
postfix/src/tlsproxy/tlsproxy_state.c
postfix/src/util/argv.c
postfix/src/util/dict.h
postfix/src/util/dict_cdb.c
postfix/src/util/dict_cidr.c
postfix/src/util/dict_sockmap.c
postfix/src/util/midna_domain.c
postfix/src/util/myaddrinfo.c
postfix/src/util/netstring.c
postfix/src/util/slmdb.c
postfix/src/util/valid_hostname.c
postfix/src/util/valid_hostname.ref
postfix/src/util/vbuf_print.c
postfix/src/util/vstream.c
postfix/src/util/vstring.c

index 23fac43c2b0b7217269448dafb1ff33e92c67948..a972260f334a3846adc8054a935dbbe98b100774 100644 (file)
@@ -30833,12 +30833,12 @@ Apologies for any names omitted.
 
 20260407
 
-       Tech debt: added 'bool' support to internal protocols.
+       Code health: added 'bool' support to internal protocols.
        util/attr.h, util/attr_print0.c, util/attr_print64.c,
        util/attr_print_plain.c, util/attr_scan0.c, util/attr_scan0.ref,
        util/attr_scan64.c, util/attr_scan64.ref, util/attr_scan_plain.c,
        util/attr_scan_plain.ref. There is no code that uses this now,
-       but there are tests to make sure that it is works when neded.
+       but there are tests to make sure that it is works when needed.
 
 20260408
 
@@ -30848,7 +30848,7 @@ Apologies for any names omitted.
 
 20260410
 
-       Tech debt: refactored tlsproxy PARAM, INIT and START send
+       Code health: refactored tlsproxy PARAM, INIT and START send
        and receive handlers to make the code easier to test, and
        added some unit tests. This removes tls_proxy_client_misc.c,
        tls_proxy_client_print.c, tls_proxy_client_scan.c,
@@ -30869,6 +30869,44 @@ Apologies for any names omitted.
        smtpd(8) manpage, missing tls_trust_server_ccerts parameter
        support in postlink. Files: smtpd/smtpd.c, mantools/postlink.
 
+20260413
+
+       Viktor Dukhovni scanned Postfix source with Claude Opus
+       4.6. It found a minor bug in the proxymap server (requires
+       an invalid map name configured in main.cf) and in the RFC
+       2047 encoder (requires an absurdly long charset name
+       configured main.cf), two instances where Postfix internal
+       documentation was not consistent with Postfix implementation
+       (handling of "*" wildcards in domain names, handling of a
+       'truncated list' flag in the DNS client), two bugs in debug
+       logging (CIDR map, DNS client), one null pointer bug in the
+       PostgreSQL client (with libpq < 8.0), one resource leak
+       after fork() failure, ignored errors that may happen during
+       memory shortage, missing detection of integer overflows
+       that are unlikely because Postfix memory by design does not
+       contain large objects or large numbers of objects. The
+       number of false positives was remarkably low (about three).
+       Claude also suggested clarification in code comments that
+       will help to make Postfix maintainable by other people (and
+       to silence future code scanners). Files: dns/dns.h,
+       dns/dns_lookup.c, dns/dns_rr.c, dns/dns_rr_test.c,
+       global/dict_pgsql.c, global/haproxy_srvr.c, global/mime_state.c,
+       global/pipe_command.c, global/rfc2047_code.c, local/dotforward.c,
+       proxymap/proxymap.c, smtp/smtp_proto.c, smtpd/smtpd_expand.c,
+       testing/mock_dns_lookup.c, util/argv.c, util/dict_cdb.c,
+       util/dict_cidr.c, util/dict.h, util/dict_sockmap.c,
+       util/midna_domain.c, util/myaddrinfo.c, util/netstring.c,
+       util/slmdb.c, util/valid_hostname.c, util/valid_hostname.ref,
+       util/vbuf_print.c, util/vstream.c, util/vstring.c.
+
+       Code health: renamed identifiers to prepare for integration
+       of proxied tls_server_init() and tls_server_start() calls.
+       Files: tlsproxy/tlsproxy.c, tlsproxy/tlsproxy.h,
+       tlsproxy/tlsproxy_state.c.
+
+       Testing: updated postscreen tests for changes in make_attr()
+       API. File: src/postscreen/postscreen_dnsbl_test.c.
+
 TODO
 
        Reorganize PTEST_LIB, PMOCK_LIB, TESTLIB, TESTLIBS, etc.
index 4737654e88da17c36bf54234a78aaa95dec1881b..58d68a542b14968fa739db2309d51f9365a42eda 100644 (file)
@@ -452,5 +452,8 @@ TLSPROXY(8)                                                        TLSPROXY(8)
        111 8th Avenue
        New York, NY 10011, USA
 
+       Wietse Venema
+       porcupine.org
+
                                                                    TLSPROXY(8)
 </pre> </body> </html>
index 53010ac56558058b0edd3271084fb0dc7b0995b0..72714017544a78126eae053083c8f1af111385d5 100644 (file)
@@ -417,3 +417,6 @@ Wietse Venema
 Google, Inc.
 111 8th Avenue
 New York, NY 10011, USA
+
+Wietse Venema
+porcupine.org
index 18329d08d323542065cea977ad0feea09ad0a701..fb7a0e31686ad517b4c8eb4e96faed5bea2424ba 100644 (file)
@@ -364,3 +364,4 @@ application  application specific
  Null name or name name 
 void  void defer_ctx 
  To undo a ptest_defer call call the function with a
+charset  charset 
index 0655242df94e6f88d59fc4a6f7adaaafe11b828c..60aa8aff8a9992ae48925a8f1ffa31747e4d114b 100644 (file)
@@ -232,3 +232,8 @@ proto  proto stop proto stop double cc
  local local hc local Makefile 
  Dukhovni Files proto postconf proto proto TLS_README html 
  tls tls h tls tls_misc c tls tls_server c 
+ support in postlink Files smtpd smtpd c mantools postlink 
+ to silence future code scanners Files dns dns h 
+ proxymap proxymap c smtp smtp_proto c smtpd smtpd_expand c 
+ Files tlsproxy tlsproxy c tlsproxy tlsproxy h 
+ Files tlsproxy tlsproxy c tlsproxy tlsproxy h 
index 05a0afebbc504b329eedc09cf005d24d4673a2c6..627642a4960485dca44f090fc5047ae8deffff54 100644 (file)
@@ -1976,3 +1976,7 @@ Bool
 HERMETICITY
 VPRINT
 deserializes
+PQerrorMessage
+deref
+openUTS
+xff
index 087e05e9978d438a346e84847a4c2de35a62d83d..72635b9c00c1f584984890306d9ab1ff940fab93 100644 (file)
@@ -211,6 +211,7 @@ extern DNS_RR *dns_rr_create(const char *, const char *,
                                     unsigned, unsigned,
                                     const char *, size_t);
 extern void dns_rr_free(DNS_RR *);
+extern DNS_RR *dns_rr_full_copy(DNS_RR *);
 extern DNS_RR *dns_rr_copy(DNS_RR *);
 extern DNS_RR *dns_rr_append(DNS_RR *, DNS_RR *);
 extern DNS_RR *dns_rr_sort(DNS_RR *, int (*) (DNS_RR *, DNS_RR *));
index 11115c3dada00d33836d6b4be27f5ce88ec73382..b18b7b2b4dc62cc589cb0249e95c35c684592470 100644 (file)
@@ -837,14 +837,15 @@ static int dns_get_rr(DNS_RR **list, const char *orig_name, DNS_REPLY *reply,
        for (src = pos, dst = (unsigned char *) ltemp;
             src < pos + fixed->length; /* */ ) {
            frag_len = *src++;
-           if (msg_verbose)
-               msg_info("frag_len=%d text=\"%.*s\"",
-                        (int) frag_len, (int) frag_len, (char *) src);
+           /* 202604 Claude: move debug logging after the frag_len check. */
            if (frag_len > reply->end - src
            || frag_len >= ((unsigned char *) ltemp + sizeof(ltemp)) - dst) {
                msg_warn("extract_answer: bad TXT string length: %d", frag_len);
                return (DNS_RETRY);
            }
+           if (msg_verbose)
+               msg_info("frag_len=%d text=\"%.*s\"",
+                        (int) frag_len, (int) frag_len, (char *) src);
            while (frag_len-- > 0) {
                ch = *src++;
                *dst++ = (ISPRINT(ch) ? ch : ' ');
index e5775a276d9ff337b2b10c78663f9d3185e3fa66..aea19c0405d669ce86ce4da4d534d054f1f655da 100644 (file)
@@ -22,6 +22,9 @@
 /*     void    dns_rr_free(list)
 /*     DNS_RR  *list;
 /*
+/*     DNS_RR  *dns_rr_full_copy(record)
+/*     DNS_RR  *record;
+/*
 /*     DNS_RR  *dns_rr_copy(record)
 /*     DNS_RR  *record;
 /*
 /*     dns_rr_free() releases the resource used by of zero or more
 /*     resource records.
 /*
-/*     dns_rr_copy() makes a copy of a resource record.
+/*     dns_rr_full_copy() makes a copy of a resource record.
+/*
+/*     dns_rr_copy() makes a sanitized copy of a resource record.
+/*     This does not copy the DNS_RR_FLAG_TRUNCATED flag.
 /*
 /*     dns_rr_append() appends an input resource record list to
 /*     an output list. Null arguments are explicitly allowed.
@@ -229,6 +235,25 @@ void    dns_rr_free(DNS_RR *rr)
     }
 }
 
+/* dns_rr_full_copy - copy resource record */
+
+DNS_RR *dns_rr_full_copy(DNS_RR *src)
+{
+    DNS_RR *dst;
+
+    /*
+     * Note: struct copy, because dns_rr_create() would not copy all fields.
+     */
+    dst = (DNS_RR *) mymalloc(sizeof(*dst));
+    *dst = *src;
+    dst->qname = mystrdup(src->qname);
+    dst->rname = mystrdup(src->rname);
+    if (dst->data)
+       dst->data = mymemdup(src->data, src->data_len);
+    dst->next = 0;
+    return (dst);
+}
+
 /* dns_rr_copy - copy resource record */
 
 DNS_RR *dns_rr_copy(DNS_RR *src)
@@ -240,6 +265,8 @@ DNS_RR *dns_rr_copy(DNS_RR *src)
      */
     dst = (DNS_RR *) mymalloc(sizeof(*dst));
     *dst = *src;
+    /* 202604 Claude: avoid surprising behavior. */
+    dst->flags &= ~DNS_RR_FLAG_TRUNCATED;
     dst->qname = mystrdup(src->qname);
     dst->rname = mystrdup(src->rname);
     if (dst->data)
index 12cf01c58fe7a6d0eef79831cd176ca92cc72e5d..d6f4b8651894918e9fda699e1f47a12d8148bacf 100644 (file)
@@ -230,6 +230,33 @@ static int append_to_list_from_list(void)
     return (eq_dns_rr_free(got, want));
 }
 
+static int dns_rr_copy_clears_truncated_flag(void)
+{
+    DNS_RR *orig;
+    DNS_RR *copy;
+
+    orig = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+
+    orig->flags |= DNS_RR_FLAG_TRUNCATED;
+    copy = dns_rr_copy(orig);
+    orig->flags &= ~DNS_RR_FLAG_TRUNCATED;
+
+    return (eq_dns_rr_free(copy, orig));
+}
+
+static int dns_rr_full_copy_copies_truncated_flag(void)
+{
+    DNS_RR *orig;
+    DNS_RR *full_copy;
+
+    orig = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+
+    orig->flags |= DNS_RR_FLAG_TRUNCATED;
+    full_copy = dns_rr_full_copy(orig);
+
+    return (eq_dns_rr_free(full_copy, orig));
+}
+
 static int append_propagates_flags(void)
 {
     DNS_RR *a = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
@@ -471,6 +498,12 @@ static const TEST_CASE test_cases[] = {
     "append to list from element", append_to_list_from_elem,
     "append to list from list", append_to_list_from_list,
 
+    /*
+     * Test dns_rr_copy() flag propagation.
+     */
+    "dns_rr_copy clears truncation", dns_rr_copy_clears_truncated_flag,
+    "dns_rr_full_copy copies truncation", dns_rr_full_copy_copies_truncated_flag,
+
     /*
      * Test dns_rr_append() flag propagation.
      */
index f5e97321d9f14e5cde60223d1d55f4657d26b031..6bf2622bcb566d0a86fd32bc7f152dcf0f6ba81d 100644 (file)
@@ -574,8 +574,10 @@ static void plpgsql_connect_single(DICT_PGSQL *dict_pgsql, HOST *host)
                                dict_pgsql->password);
     }
     if (host->db == NULL || PQstatus(host->db) != CONNECTION_OK) {
+       /* 202604 Claude: don't call PQerrorMessage(NULL). */
        msg_warn("connect to pgsql server %s: %s",
-                host->hostname, PQerrorMessage(host->db));
+                host->hostname, host->db ? PQerrorMessage(host->db) :
+                "PQconnectdb or PQsetdbLogin failed");
        plpgsql_down_host(host, dict_pgsql->retry_interval);
        return;
     }
index 0f09bf03ea9165593e931c6a1001eb4a630d0460..e2c495f8bcfa3c7c70e2ed3230eb24cc8d0b6524 100644 (file)
@@ -61,6 +61,9 @@
 /*     of bytes parsed, and the non_proxy argument is true or false
 /*     if the haproxy message specifies a non-proxied connection.
 /*
+/*     Note: haproxy v2 protocol support requires that the protocol
+/*     message buffer has "struct proxy_hdr_v2" alignment.
+/*
 /*     haproxy_srvr_receive_sa() receives and parses a haproxy protocol
 /*     handshake. This must be called before any I/O is done on
 /*     the specified file descriptor. The result is 0 in case of
@@ -583,7 +586,12 @@ int     haproxy_srvr_receive_sa(int fd, int *non_proxy,
 {
     const char *err;
     VSTRING *escape_buf;
-    char    read_buf[HAPROXY_HEADER_MAX_LEN + 1];
+
+    /* 202604 Claude: read_buf should have "struct proxy_hdr_v2" alignment. */
+    union {
+       char    b[HAPROXY_HEADER_MAX_LEN + 1];
+       struct proxy_hdr_v2 p;
+    }       read_buf;
     ssize_t read_len;
 
     /*
@@ -592,7 +600,7 @@ int     haproxy_srvr_receive_sa(int fd, int *non_proxy,
      * therefore we peek, parse the entire input, then read(2) only the
      * number of bytes parsed.
      */
-    if ((read_len = recv(fd, read_buf, sizeof(read_buf) - 1, MSG_PEEK)) <= 0) {
+    if ((read_len = recv(fd, read_buf.b, sizeof(read_buf.b) - 1, MSG_PEEK)) <= 0) {
        msg_warn("haproxy read: EOF");
        return (-1);
     }
@@ -600,15 +608,15 @@ int     haproxy_srvr_receive_sa(int fd, int *non_proxy,
     /*
      * Parse the haproxy handshake, and determine the handshake length.
      */
-    read_buf[read_len] = 0;
+    read_buf.b[read_len] = 0;
 
-    if ((err = haproxy_srvr_parse_sa(read_buf, &read_len, non_proxy,
+    if ((err = haproxy_srvr_parse_sa(read_buf.b, &read_len, non_proxy,
                                     smtp_client_addr, smtp_client_port,
                                     smtp_server_addr, smtp_server_port,
                                     client_sa, client_sa_len,
                                     server_sa, server_sa_len)) != 0) {
        escape_buf = vstring_alloc(read_len * 2);
-       escape(escape_buf, read_buf, read_len);
+       escape(escape_buf, read_buf.b, read_len);
        msg_warn("haproxy read: %s: %s", err, vstring_str(escape_buf));
        vstring_free(escape_buf);
        return (-1);
@@ -617,7 +625,7 @@ int     haproxy_srvr_receive_sa(int fd, int *non_proxy,
     /*
      * Try to pop the haproxy handshake off the input queue.
      */
-    if (recv(fd, read_buf, read_len, 0) != read_len) {
+    if (recv(fd, read_buf.b, read_len, 0) != read_len) {
        msg_warn("haproxy read: %m");
        return (-1);
     }
index b3f4491bb81d11059030a18cfa747fae23a8b594..4653dea22ad5db3deaa7b0442acd90490e933a89 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      "20260410"
+#define MAIL_RELEASE_DATE      "20260413"
 #define MAIL_VERSION_NUMBER    "3.12"
 
 #ifdef SNAPSHOT
index 798e13de7d89605aef4a55df716a6b3154841177..920e34603db481c55423ea9540d7c082871d9185 100644 (file)
@@ -714,7 +714,8 @@ static void mime_state_downgrade(MIME_STATE *state, int rec_type,
 
 #define QP_ENCODE(buffer, ch) { \
        VSTRING_ADDCH(buffer, '='); \
-       VSTRING_ADDCH(buffer, hexchars[(ch >> 4) & 0xff]); \
+       /* 202406 Claude: 0xff should be 0xf. */ \
+       VSTRING_ADDCH(buffer, hexchars[(ch >> 4) & 0xf]); \
        VSTRING_ADDCH(buffer, hexchars[ch & 0xf]); \
     }
 
index 66aec8a67321d493e90dde1f7d9a695f69fe6261..a7a1d62a5f805b86ba21357399d93563aa23deee 100644 (file)
@@ -460,6 +460,11 @@ int     pipe_command(VSTREAM *src, DSN_BUF *why,...)
        msg_warn("fork: %m");
        dsb_unix(why, "4.3.0", sys_exits_detail(EX_OSERR)->text,
                 "Delivery failed: %m");
+       /* 202604 Claude: close pipes for the child and parent paths. */
+       close(cmd_in_pipe[0]);
+       close(cmd_in_pipe[1]);
+       close(cmd_out_pipe[0]);
+       close(cmd_out_pipe[1]);
        return (PIPE_STAT_DEFER);
 
        /*
index 9a9da5eb856326d83b6c2afd2110df38549d1b85..3c8b7b201292ae281bb442555a5c5356036c5338 100644 (file)
 
  /*
   * What ASCII characters are allowed in the 'charset' or 'encoding' tokens
-  * of an encoded-word.
+  * of an encoded-word. 202604 Claude: in the macro expansion replace
+  * several instances of 'ch' with 'c'.
   */
 #define RFC2047_ESPECIALS_STR  "()<>@,;:\\\"/[]?.="
 #define RFC2047_ALLOWED_TOKEN_CHAR(c) \
-    (isascii(c) && !iscntrl(c) && ch != ' ' \
-       && !strchr(RFC2047_ESPECIALS_STR, ch))
+       (isascii(c) && !iscntrl(c) && c != ' ' \
+       && !strchr(RFC2047_ESPECIALS_STR, c))
 
  /*
   * Common definitions for the base64 and quoted-printable encoders.
@@ -274,6 +275,12 @@ char   *rfc2047_encode(VSTRING *result, int header_context,
        msg_warn("%s: encoder called with empty charset name", myname);
        return (0);
     }
+    /* 202604 Claude: avoid 'space_left' underflow. */
+    if (strlen(charset) > ENC_WORD_MAX_LEN / 2) {
+       msg_warn("%s: unreasonable charset name: '%.100s'",
+                myname, charset);
+       return (0);
+    }
     for (cp = (const unsigned char *) charset; (ch = *cp) != 0; cp++) {
        if (!RFC2047_ALLOWED_TOKEN_CHAR(ch)) {
            msg_warn("%s: invalid character: 0x%x in charset name: '%s'",
@@ -651,6 +658,13 @@ static const TEST_CASE test_cases[] = {
        "testme", 0, " ", NO_OUTPUT, "rfc2047_code: warning: rfc2047_encode: "
        "encoder called with empty charset name\n"
     },
+    {"validates_charset_length", test_rfc2047_encode,
+       RFC2047_HEADER_CONTEXT_COMMENT, /* charset= */ 
+       "1234567890123456789012345678901234567890",
+       "testme", 0, " ", NO_OUTPUT, "rfc2047_code: warning: rfc2047_encode: "
+       "unreasonable charset name: "
+       "'1234567890123456789012345678901234567890'\n"
+    },
     {"validates_charset", validates_charset},
     {"encodes_comment_text", encodes_comment_text},
     {"encodes_long_comment_text", test_rfc2047_encode,
index 3ce2cfca90565c5422d76af8601151de1860c980..3ba7017e4c9a2d6c770f3f7f3b48ee69c04a8ff9 100644 (file)
@@ -256,6 +256,19 @@ int     deliver_dotforward(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp)
                 * associated with that alias.  The NOTIFY parameter is
                 * propagated to the forwarding addresses, except that any
                 * SUCCESS keyword is removed.
+                * 
+                * 202604 Claude: a local recipient can attempt to sabotage
+                * deliveries with malicious targets inside .forward such as
+                * a /path/to/fifo without listener, or a "|command" that
+                * sleeps, or by replacing .forward with a fifo. The impact
+                * of such attempts is limited by design because each local
+                * recipient is limited to only two concurrent deliveries
+                * (the defaults are "local_destination_concurrency_limit =
+                * 2" and "local_destination_recipient_limit = 1"). So they
+                * will only slow down their own deliveries; 98% of local
+                * delivery processes will remain available for other
+                * recipients (the default setting "default_process_limit =
+                * 100").
                 */
                close_on_exec(fd, CLOSE_ON_EXEC);
                addr_count = 0;
index 5cbcd4da8fb069bb30e82b27ad66e10a2812f6f6..96ec561c47de13e0453f5f00a2a0f9d0d63b1a9c 100644 (file)
@@ -258,14 +258,14 @@ static void test_single_dnsbl(PTEST_CTX *t, const PTEST_CASE *tp)
             * event request, so we should send something soon.
             */
            serialized_req =
-               make_attr(ATTR_FLAG_NONE,
+               make_attr(attr_vprint, ATTR_FLAG_NONE,
                          SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, tt->req_dnsbl),
                          SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR,
                                        tt->req_addr),
                          SEND_ATTR_INT(MAIL_ATTR_LABEL, request_id),
                          ATTR_TYPE_END);
            serialized_resp =
-               make_attr(ATTR_FLAG_NONE,
+               make_attr(attr_vprint, ATTR_FLAG_NONE,
                          SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, tt->req_dnsbl),
                          SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR,
                                        tt->req_addr),
@@ -479,7 +479,7 @@ static void test_multi_dnsbl(PTEST_CTX *t, const PTEST_CASE *tp)
                 * send something soon.
                 */
                serialized_req =
-                   make_attr(ATTR_FLAG_NONE,
+                   make_attr(attr_vprint, ATTR_FLAG_NONE,
                              SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN,
                                            dp[n].req_dnsbl),
                              SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR,
@@ -487,7 +487,7 @@ static void test_multi_dnsbl(PTEST_CTX *t, const PTEST_CASE *tp)
                              SEND_ATTR_INT(MAIL_ATTR_LABEL, request_id),
                              ATTR_TYPE_END);
                serialized_resp =
-                   make_attr(ATTR_FLAG_NONE,
+                   make_attr(attr_vprint, ATTR_FLAG_NONE,
                              SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN,
                                            dp[n].req_dnsbl),
                              SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR,
index c3ef77dab47cee81b48e2639b7987f99294422cf..97053b1098145b8901a0dc526bcc070c21b83be0 100644 (file)
@@ -477,7 +477,7 @@ static void proxymap_lookup_service(VSTREAM *client_stream)
 {
     int     inst_flags;
     int     request_flags;
-    DICT   *dict;
+    DICT   *dict = 0;
     const char *reply_value;
     int     reply_status;
 
@@ -508,11 +508,12 @@ static void proxymap_lookup_service(VSTREAM *client_stream)
     }
 
     /*
-     * Respond to the client.
+     * Respond to the client. 202604 Claude: don't dereference uninitialized
+     * dict.
      */
     attr_print(client_stream, ATTR_FLAG_NONE,
               SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
-              SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict->flags),
+              SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict ? dict->flags : 0),
               SEND_ATTR_STR(MAIL_ATTR_VALUE, reply_value),
               ATTR_TYPE_END);
 }
@@ -523,7 +524,7 @@ static void proxymap_update_service(VSTREAM *client_stream)
 {
     int     inst_flags;
     int     request_flags;
-    DICT   *dict;
+    DICT   *dict = 0;
     int     dict_status;
     int     reply_status;
 
@@ -567,11 +568,12 @@ static void proxymap_update_service(VSTREAM *client_stream)
     }
 
     /*
-     * Respond to the client.
+     * Respond to the client. 202604 Claude: don't dereference uninitialized
+     * dict.
      */
     attr_print(client_stream, ATTR_FLAG_NONE,
               SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
-              SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict->flags),
+              SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict ? dict->flags : 0),
               ATTR_TYPE_END);
 }
 
@@ -581,7 +583,7 @@ static void proxymap_delete_service(VSTREAM *client_stream)
 {
     int     inst_flags;
     int     request_flags;
-    DICT   *dict;
+    DICT   *dict = 0;
     int     dict_status;
     int     reply_status;
 
@@ -621,11 +623,12 @@ static void proxymap_delete_service(VSTREAM *client_stream)
     }
 
     /*
-     * Respond to the client.
+     * Respond to the client. 202604 Claude: don't dereference uninitialized
+     * dict.
      */
     attr_print(client_stream, ATTR_FLAG_NONE,
               SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
-              SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict->flags),
+              SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict ? dict->flags : 0),
               ATTR_TYPE_END);
 }
 
index 69a02311ef5a2e8778b5d956464bcf21ac417aec..b5538b4e51cb7ed26ae16a1958e168643c68bc9c 100644 (file)
@@ -595,6 +595,7 @@ int     smtp_helo(SMTP_STATE *state)
                                msg_warn("bad EHLO SIZE limit \"%s\" from %s",
                                         word, session->namaddrport);
                            else
+                               /* We treat size_limit <= 0 as 'unknown'. */
                                session->size_limit = off_cvt_string(word);
                        }
                    }
index 8362bd701d9929acf7a1e9167faa5cd219fd7d3a..eeabd84ddf37439a7d0d41d1bd16cafa9ca4e3ad 100644 (file)
@@ -220,7 +220,8 @@ const char *smtpd_expand_lookup(const char *name, int unused_mode,
     } else if (STREQN(name, MAIL_ATTR_RECIP, CONST_LEN(MAIL_ATTR_RECIP))) {
        return (smtpd_expand_addr(state->expand_buf, state->recipient,
                                  name, CONST_LEN(MAIL_ATTR_RECIP)));
-    } if (STREQ(name, MAIL_ATTR_LOCALTIME)) {
+       /* 202406 Claude: add missing 'else'. No effect on generated code. */
+    } else if (STREQ(name, MAIL_ATTR_LOCALTIME)) {
        if (time(&now) == (time_t) -1)
            msg_fatal("time lookup failed: %m");
        lt = localtime(&now);
index a7500b20fa2c1c3a3bff229573a4d62eae06f77c..286d3cab7ccf4197020b429c56e0c5bd83caedbb 100644 (file)
@@ -171,16 +171,16 @@ const char *dns_status_to_string(int status)
     return (str_name_code(status_string, status));
 }
 
-/* copy_dns_rrlist - deep copy */
+/* full_copy_dns_rrlist - deep copy */
 
-static DNS_RR *copy_dns_rrlist(DNS_RR *list)
+static DNS_RR *full_copy_dns_rrlist(DNS_RR *list)
 {
     DNS_RR *rr;
 
     if (list == 0)
        return (0);
-    rr = dns_rr_copy(list);
-    rr->next = copy_dns_rrlist(list->next);
+    rr = dns_rr_full_copy(list);
+    rr->next = full_copy_dns_rrlist(list->next);
     return (rr);
 }
 
@@ -353,7 +353,7 @@ static void assign_dns_lookup_x(const MOCK_EXPECT *expect,
 
     if (pe->retval == DNS_OK) {
        if (pt->rrlist)
-           *(pt->rrlist) = copy_dns_rrlist(pe->rrlist);
+           *(pt->rrlist) = full_copy_dns_rrlist(pe->rrlist);
        if (pt->fqdn && pe->fqdn)
            vstring_strcpy(pt->fqdn, STR(pe->fqdn));
     } else {
@@ -439,7 +439,7 @@ void    _expect_dns_lookup_x(const char *file, int line, int calls_expected,
     pe->herrval = herrval;
     pe->retval = retval;
     if (pe->retval == DNS_OK) {
-       pe->rrlist = copy_dns_rrlist(rrlist);
+       pe->rrlist = full_copy_dns_rrlist(rrlist);
        pe->fqdn = VSTRDUP_OR_NULL(fqdn);
     } else {
        pe->why = VSTRDUP_OR_NULL(why);
index cef9a4f2394cc2d7aefca4bbd584edbbcc2f34b1..d9c48001af3b54e560403c8020f6dedbe7ec0617 100644 (file)
 /*     Google, Inc.
 /*     111 8th Avenue
 /*     New York, NY 10011, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
 /*--*/
 
  /*
@@ -1465,7 +1468,7 @@ static void tlsp_get_request_event(int event, void *context)
        state->is_server_role = 0;
        if (attr_scan(plaintext_stream, ATTR_FLAG_STRICT,
                      RECV_ATTR_FUNC(tls_proxy_client_param_scan,
-                                    (void *) &state->tls_params),
+                                    (void *) &state->client_params),
                      RECV_ATTR_FUNC(tls_proxy_client_init_scan,
                                     (void *) &state->client_init_props),
                      RECV_ATTR_FUNC(tls_proxy_client_start_scan,
@@ -1475,7 +1478,7 @@ static void tlsp_get_request_event(int event, void *context)
            tlsp_state_free(state);
            return;
        }
-       state->appl_state = tlsp_client_init(state->tls_params,
+       state->appl_state = tlsp_client_init(state->client_params,
                                             state->client_init_props);
        ready = state->appl_state != 0;
        break;
index eacbb1fabaae8513f17e8eadc1f94b20e3da97a8..b3223ab7425201d45b0d49f984ba2da536ef9513 100644 (file)
@@ -39,9 +39,10 @@ typedef struct {
     TLS_APPL_STATE *appl_state;                /* libtls state */
     TLS_SESS_STATE *tls_context;       /* libtls state */
     int     ssl_last_err;              /* TLS I/O state */
-    TLS_CLIENT_PARAMS *tls_params;     /* globals not part of init_props */
+    TLS_SERVER_PARAMS *server_params;  /* globals not part of init_props */
     TLS_SERVER_INIT_PROPS *server_init_props;
     TLS_SERVER_START_PROPS *server_start_props;
+    TLS_CLIENT_PARAMS *client_params;  /* globals not part of init_props */
     TLS_CLIENT_INIT_PROPS *client_init_props;
     TLS_CLIENT_START_PROPS *client_start_props;
 } TLSP_STATE;
@@ -66,4 +67,7 @@ extern void tlsp_state_free(TLSP_STATE *);
 /*     Google, Inc.
 /*     111 8th Avenue
 /*     New York, NY 10011, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
 /*--*/
index 4a08d17fedd2016be9d5d59f3760c44709611e9a..a821066834a0d80fb954a0aa0c5241a1e85d1bd6 100644 (file)
@@ -66,6 +66,9 @@
 /*     Google, Inc.
 /*     111 8th Avenue
 /*     New York, NY 10011, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
 /*--*/
 
  /*
@@ -115,9 +118,10 @@ TLSP_STATE *tlsp_state_create(const char *service,
     state->remote_endpt = 0;
     state->server_id = 0;
     state->tls_context = 0;
-    state->tls_params = 0;
+    state->server_params = 0;
     state->server_init_props = 0;
     state->server_start_props = 0;
+    state->client_params = 0;
     state->client_init_props = 0;
     state->client_start_props = 0;
 
@@ -153,12 +157,14 @@ void    tlsp_state_free(TLSP_STATE *state)
        myfree(state->server_id);
     if (state->tls_context)
        tls_free_context(state->tls_context);
-    if (state->tls_params)
-       tls_proxy_client_param_free(state->tls_params);
+    if (state->server_params)
+       tls_proxy_server_param_free(state->server_params);
     if (state->server_init_props)
        tls_proxy_server_init_free(state->server_init_props);
     if (state->server_start_props)
        tls_proxy_server_start_free(state->server_start_props);
+    if (state->client_params)
+       tls_proxy_client_param_free(state->client_params);
     if (state->client_init_props)
        tls_proxy_client_init_free(state->client_init_props);
     if (state->client_start_props)
index ea76b5152a8deb2226b49bb8a6a857da4a37c6cd..a0dc395f3a0caf8452849f77a75c0f05f70a5f1f 100644 (file)
@@ -262,6 +262,9 @@ static void argv_extend(ARGV *argvp)
 {
     ssize_t new_len;
 
+    /* 202604 Claude: avoid small non-negative expression 'new_len + 1'.  */
+    if (argvp->len > SSIZE_MAX / 2)
+       msg_panic("argv_extend: array length overflow");
     new_len = argvp->len * 2;
     argvp->argv = (char **)
        myrealloc((void *) argvp->argv, (new_len + 1) * sizeof(char *));
@@ -409,9 +412,10 @@ void    argv_delete(ARGV *argvp, ssize_t first, ssize_t how_many)
     ssize_t pos;
 
     /*
-     * Sanity check.
+     * Sanity check. 202604 Claude: avoid expression 'first + how_many'.
      */
-    if (first < 0 || how_many < 0 || first + how_many > argvp->argc)
+    if (first < 0 || how_many < 0 || first > argvp->argc 
+       || how_many > argvp->argc - first)
        msg_panic("argv_delete bad range: (start=%ld count=%ld)",
                  (long) first, (long) how_many);
 
index efefb885dabbf3ba3f5f9327054cbaf94e6fac46..1ca8b7f6294102f906c1070e9809b1ebae3e33ad 100644 (file)
@@ -51,9 +51,10 @@ typedef struct DICT_OWNER {
   * When combining tables with different provenance, we initialize to the
   * highest trust level, and remember the lowest trust level that we find
   * during aggregation. If we combine tables that are owned by different
-  * untrusted users, the resulting provenance is "unknown".
+  * untrusted users, the resulting provenance is "unknown". 202406 Claude:
+  * add missing 'do'.
   */
-#define DICT_OWNER_AGGREGATE_INIT(dst) { \
+#define DICT_OWNER_AGGREGATE_INIT(dst) do { \
        (dst).status = DICT_OWNER_TRUSTED; \
        (dst).uid = 0; \
     } while (0)
@@ -313,7 +314,7 @@ extern const char *dict_file_lookup(DICT *dict, const char *);
   * dict_stream(3)
   */
 extern VSTREAM *dict_stream_open(const char *dict_type, const char *mapname,
-           int open_flags, int dict_flags, struct stat * st, VSTRING **why);
+            int open_flags, int dict_flags, struct stat *st, VSTRING **why);
 
 /* LICENSE
 /* .ad
index 25b52fbfad309e6fada7e792293ecfc1f96714cd..93a1f026ce2d5a24b455f22de9cb23b715999eaa 100644 (file)
@@ -456,7 +456,8 @@ static DICT *dict_cdbm_open(const char *path, int dict_flags)
     }
 
 #ifndef NO_FTRUNCATE
-    if (st0.st_size)
+    /* 202604 Claude: use the post-myflock() fstat result. */
+    if (st1.st_size)
        ftruncate(fd, 0);
 #endif
 
index bf59ba758f12feb29565dc4d928e8b336f1fbca4..ca6435d8c2ca7bbd26dc4051fe8237e73b447325 100644 (file)
@@ -237,11 +237,14 @@ static DICT_CIDR_ENTRY *dict_cidr_parse_rule(DICT *dict, char *p, int lineno,
     rule->lineno = lineno;
 
     if (msg_verbose) {
-       if (inet_ntop(cidr_info.addr_family, cidr_info.net_bytes,
-                     hostaddr.buf, sizeof(hostaddr.buf)) == 0)
-           msg_fatal("inet_ntop: %m");
-       msg_info("dict_cidr_open: add %s/%d %s",
-                hostaddr.buf, cidr_info.mask_shift, rule->value);
+       /* 202604 Claude: ENDIF has no address pattern. */
+       if (cidr_info.op != CIDR_MATCH_OP_ENDIF) {
+           if (inet_ntop(cidr_info.addr_family, cidr_info.net_bytes,
+                         hostaddr.buf, sizeof(hostaddr.buf)) == 0)
+               msg_fatal("inet_ntop: %m");
+           msg_info("dict_cidr_open: add %s/%d %s",
+                    hostaddr.buf, cidr_info.mask_shift, rule->value);
+       }
     }
     return (rule);
 }
index 9bd0a9d27b6f624b5bab18028b1509f551c5792d..b96b26cff65bc9596ef424074afdf1551722238f 100644 (file)
@@ -254,7 +254,8 @@ static const char *dict_sockmap_lookup(DICT *dict, const char *key)
     reply_payload = split_at(STR(dp->rdwr_buf), ' ');
     if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_OK) == 0) {
        dict->error = 0;
-       return (reply_payload);
+       /* 202604 Claude: don't return NULL with dict->error==0. */
+       return (reply_payload ? reply_payload : "");
     } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_NOTFOUND) == 0) {
        dict->error = 0;
        return (0);
index 5e9d6f8cbade55cefdf6392ad366d4bc86d185b8..018b537dbde4a23fe4999f524bce8f4e52eed02c 100644 (file)
@@ -189,11 +189,13 @@ static void *midna_domain_to_ascii_create(const char *name, void *unused_context
      */
     idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT
                           : UIDNA_NONTRANSITIONAL_TO_ASCII, &error);
-    anl = uidna_nameToASCII_UTF8(idna,
-                                name, strlen(name),
-                                buf, sizeof(buf) - 1,
-                                &info,
-                                &error);
+    /* 202604 Claude: avoid null deref after uidna_openUTS46() failure. */
+    if (idna && U_SUCCESS(error))
+       anl = uidna_nameToASCII_UTF8(idna,
+                                    name, strlen(name),
+                                    buf, sizeof(buf) - 1,
+                                    &info,
+                                    &error);
     uidna_close(idna);
 
     /*
@@ -203,7 +205,7 @@ static void *midna_domain_to_ascii_create(const char *name, void *unused_context
      * "fake" A-labels, as required by UTS 46 section 4.1, but we rely on
      * valid_hostname() on the output side just to be sure.
      */
-    if (U_SUCCESS(error) && info.errors == 0 && anl > 0) {
+    if (idna && U_SUCCESS(error) && info.errors == 0 && anl > 0) {
        buf[anl] = 0;                           /* XXX */
        if (!valid_hostname(buf, DONT_GRIPE)) {
            msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s",
@@ -243,11 +245,13 @@ static void *midna_domain_to_utf8_create(const char *name, void *unused_context)
      */
     idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT
                           : UIDNA_NONTRANSITIONAL_TO_UNICODE, &error);
-    anl = uidna_nameToUnicodeUTF8(idna,
-                                 name, strlen(name),
-                                 buf, sizeof(buf) - 1,
-                                 &info,
-                                 &error);
+    /* 202604 Claude: avoid null deref after uidna_openUTS46() failure. */
+    if (idna && U_SUCCESS(error))
+       anl = uidna_nameToUnicodeUTF8(idna,
+                                     name, strlen(name),
+                                     buf, sizeof(buf) - 1,
+                                     &info,
+                                     &error);
     uidna_close(idna);
 
     /*
@@ -256,7 +260,7 @@ static void *midna_domain_to_utf8_create(const char *name, void *unused_context)
      * other invalid forms that are not covered in UTS 46, section 4.1). We
      * rely on midna_domain_to_ascii() to validate the output.
      */
-    if (U_SUCCESS(error) && info.errors == 0 && anl > 0) {
+    if (idna && U_SUCCESS(error) && info.errors == 0 && anl > 0) {
        buf[anl] = 0;                           /* XXX */
        if (midna_domain_to_ascii(buf) == 0)
            return (0);
index ef4ca7a5da4e072eda72964137db6add08178e8e..3ba09c3e36435b116aea7aa18069e9fed14bd93b 100644 (file)
@@ -356,15 +356,16 @@ int     hostname_to_sockaddr_pf(const char *hostname, int pf,
      */
     if ((hp = gethostbyname(hostname)) == 0)
        return (h_errno == TRY_AGAIN ? EAI_AGAIN : EAI_NODATA);
+    /* 202604 Claude: return EAI_NODATA when gethostbyname() returns empty. */
     if (hp->h_addrtype != AF_INET
+       || hp->h_addr_list[0] == 0
        || hp->h_length != sizeof(template.sin.sin_addr))
        return (EAI_NODATA);
 
     /*
-     * Initialize the result template.
+     * Initialize the result template. 202604 Claude: always do that.
      */
-    if (template.info.ai_addrlen == 0)
-       init_ipv4addrinfo(&template, socktype);
+    init_ipv4addrinfo(&template, socktype);
 
     /*
      * Copy the address information into an addrinfo structure.
@@ -474,7 +475,7 @@ int     hostname_to_sockaddr_pf(const char *hostname, int pf,
 /* hostaddr_to_sockaddr - printable address to binary address form */
 
 int     hostaddr_to_sockaddr(const char *hostaddr, const char *service,
-                                    int socktype, struct addrinfo ** res)
+                                    int socktype, struct addrinfo **  res)
 {
 #ifdef EMULATE_IPV4_ADDRINFO
 
index ea68ccd34b42123def43d2dda0d398597a19e322..fe32ddf568f7d2fc12464ff44bb063540b38e183 100644 (file)
@@ -305,14 +305,17 @@ void    netstring_put_multi(VSTREAM *stream,...)
     VA_COPY(ap2, ap);
 
     /*
-     * Figure out the total result size.
+     * Figure out the total result size. 202604 Claude: move the wrap-around
+     * guard inside the loop.
      */
-    for (total = 0; (data = va_arg(ap, char *)) != 0; total += data_len)
+    for (total = 0; (data = va_arg(ap, char *)) != 0; /* see below */ ) {
        if ((data_len = va_arg(ap, ssize_t)) < 0)
            msg_panic("%s: bad data length %ld", myname, (long) data_len);
+       if (data_len > SSIZE_T_MAX - total)
+           msg_panic("%s: total length overflow", myname);
+       total += data_len;
+    }
     va_end(ap);
-    if (total < 0)
-       msg_panic("%s: bad total length %ld", myname, (long) total);
     if (msg_verbose > 1)
        msg_info("%s: write total length %ld", myname, (long) total);
 
index e1b8fd64c8b432710ac2cf671d1040ea06ff34fb..3f795631e4cf60838702a4b2159e287ee278de61 100644 (file)
@@ -481,8 +481,9 @@ static int slmdb_recover(SLMDB *slmdb, int status)
     case MDB_MAP_RESIZED:
        if ((status = mdb_env_set_mapsize(slmdb->env, 0)) == 0) {
            /* Do not panic. Maps may shrink after bulk update. */
-           mdb_env_info(slmdb->env, &info);
-           slmdb->curr_limit = info.me_mapsize;
+           /* 202604 Claude: handle impossible mdb_env_info() error. */
+           if (mdb_env_info(slmdb->env, &info) == 0)
+               slmdb->curr_limit = info.me_mapsize;
            if (slmdb->notify_fn)
                slmdb->notify_fn(slmdb->cb_context, MDB_MAP_RESIZED,
                                 slmdb->curr_limit);
index 457d1f1dacf8af67b77c899e9bf048983cf2293e..580b4482acc5daf1ab5d906effe0a120914daccc 100644 (file)
@@ -122,7 +122,8 @@ int     valid_hostname(const char *name, int flags)
            if (!ISDIGIT(ch))
                non_numeric = 1;
        } else if ((flags & DO_WILDCARD) && ch == '*') {
-           if (label_length || label_count || (cp[1] && cp[1] != '.')) {
+           /* 202604 Claude: leading '*' must be followed by '.'. */
+           if (label_length || label_count || cp[1] != '.') {
                if (gripe)
                    msg_warn("%s: '*' can be the first label only: %.100s", myname, name);
                return (0);
index eccc5585e5e8cac572ebbdfb05467de448f4b6bc..d7b0e27ad6a069004dbcc71546059d1242bc5f0e 100644 (file)
 ./valid_hostname: testing: "foo.bar*"
 ./valid_hostname: warning: valid_hostname: '*' can be the first label only: foo.bar*
 ./valid_hostname: testing: "*"
+./valid_hostname: warning: valid_hostname: '*' can be the first label only: *
index 5472566cf8cc993458f57c9b629e54fecb21d232..6e14cc0e906557f452252a8662ac704afe5384c4 100644 (file)
 
  /*
   * Helper macros... Note that there is no need to check the result from
-  * VSTRING_SPACE() because that always succeeds or never returns.
+  * VSTRING_SPACE() because that always succeeds or never returns. 202406
+  * Claude: avoid integer overflow in field width computations.
   */
 #ifndef NO_SNPRINTF
-#define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \
+#define VBUF_SNPRINTF(bp, width_or_prec, type_space, fmt, arg) do { \
        ssize_t _ret; \
-       if (VBUF_SPACE((bp), (sz)) != 0) \
+       if ((width_or_prec) > INT_MAX - (type_space)) \
+           msg_panic("vbuf_print: field width (%d + %lu) > INT_MAX", \
+               (width_or_prec), (unsigned long) (type_space)); \
+       if (VBUF_SPACE((bp), (width_or_prec) + (type_space)) != 0) \
            return (bp); \
        _ret = snprintf((char *) (bp)->ptr, (bp)->cnt, (fmt), (arg)); \
        if (_ret < 0) \
     } while (0)
 
 #define VSTRING_ADDNUM(vp, n) do { \
-       VBUF_SNPRINTF(&(vp)->vbuf, INT_SPACE, "%d", n); \
+       VBUF_SNPRINTF(&(vp)->vbuf, 0, INT_SPACE, "%d", n); \
     } while (0)
 
 #define VBUF_STRCAT(bp, s) do { \
@@ -274,7 +278,7 @@ VBUF   *vbuf_print(VBUF *bp, const char *format, va_list ap)
                    msg_panic("%s: %%j%c is not supported", myname, *cp);
                s = va_arg(ap, char *);
                if (prec >= 0 || (width > 0 && width > strlen(s))) {
-                   VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
+                   VBUF_SNPRINTF(bp, (width > prec ? width : prec), INT_SPACE,
                                  vstring_str(fmt), s);
                } else {
                    VBUF_STRCAT(bp, s);
@@ -295,16 +299,16 @@ VBUF   *vbuf_print(VBUF *bp, const char *format, va_list ap)
                    msg_panic("%s: '%s%c' has both 'j' and 'l' modifiers",
                              myname, vstring_str(fmt), *cp);
                if (intmax_flag)
-                   VBUF_SNPRINTF(bp, (width > prec ? width : prec) + IMX_SPACE,
+                   VBUF_SNPRINTF(bp, (width > prec ? width : prec), IMX_SPACE,
                                  vstring_str(fmt), va_arg(ap, intmax_t));
                else if (long_flag == 2)
-                   VBUF_SNPRINTF(bp, (width > prec ? width : prec) + LL_SPACE,
+                   VBUF_SNPRINTF(bp, (width > prec ? width : prec), LL_SPACE,
                                  vstring_str(fmt), va_arg(ap, long long));
                else if (long_flag == 1)
-                   VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
+                   VBUF_SNPRINTF(bp, (width > prec ? width : prec), INT_SPACE,
                                  vstring_str(fmt), va_arg(ap, long));
                else if (long_flag == 0)
-                   VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
+                   VBUF_SNPRINTF(bp, (width > prec ? width : prec), INT_SPACE,
                                  vstring_str(fmt), va_arg(ap, int));
                else
                    msg_panic("%s: bad long_flag: %u", myname, long_flag);
@@ -313,7 +317,7 @@ VBUF   *vbuf_print(VBUF *bp, const char *format, va_list ap)
            case 'f':
            case 'g':
                /* C99 *printf ignore the 'l' modifier. */
-               VBUF_SNPRINTF(bp, (width > prec ? width : prec) + DBL_SPACE,
+               VBUF_SNPRINTF(bp, (width > prec ? width : prec), DBL_SPACE,
                              vstring_str(fmt), va_arg(ap, double));
                break;
            case 'm':
@@ -325,7 +329,7 @@ VBUF   *vbuf_print(VBUF *bp, const char *format, va_list ap)
                    msg_panic("%s: %%l%c is not supported", myname, *cp);
                if (intmax_flag)
                    msg_panic("%s: %%j%c is not supported", myname, *cp);
-               VBUF_SNPRINTF(bp, (width > prec ? width : prec) + PTR_SPACE,
+               VBUF_SNPRINTF(bp, (width > prec ? width : prec), PTR_SPACE,
                              vstring_str(fmt), va_arg(ap, char *));
                break;
            default:                            /* anything else is bad */
index 5acfb0f1023648c3d8d1a90e03435828a88ee1d2..136db9de05f2a6d163501b93275535d909c06adc 100644 (file)
@@ -1224,6 +1224,7 @@ off_t   vstream_fseek(VSTREAM *stream, off_t offset, int whence)
            return (-1);
        }
        if (offset > bp->len && (bp->flags & VSTREAM_FLAG_WRITE))
+           /* Note: vstring_space() does not return after error. */
            vstream_buf_space(bp, offset - bp->len);
        VSTREAM_BUF_AT_OFFSET(bp, offset);
        return (offset);
@@ -1766,7 +1767,8 @@ void    vstream_control(VSTREAM *stream, int name,...)
            stream->min_data_rate = min_data_rate;
            break;
        case VSTREAM_CTL_OWN_VSTRING:
-           if ((stream->buf.flags |= VSTREAM_FLAG_MEMORY) == 0)
+           /* 202604 Claude: '|=' should be '&'. */
+           if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) == 0)
                msg_panic("%s: operation on non-VSTRING stream", myname);
            stream->buf.flags |= VSTREAM_FLAG_OWN_VSTRING;
            break;
index 301ae5df5cd8e43a200d6d93ca3d8fec381335da..a1de4f18e2dbdb5c0f8cb90f84ee61692095edd9 100644 (file)
@@ -517,6 +517,7 @@ VSTRING *vstring_memcpy(VSTRING *vp, const char *src, ssize_t len)
 
 VSTRING *vstring_memcat(VSTRING *vp, const char *src, ssize_t len)
 {
+    /* VSTRING_SPACE() does not return if the buffer size would wrap around. */
     VSTRING_SPACE(vp, len);
     memcpy(vstring_end(vp), src, len);
     len += VSTRING_LEN(vp);