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
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,
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.
111 8th Avenue
New York, NY 10011, USA
+ Wietse Venema
+ porcupine.org
+
TLSPROXY(8)
</pre> </body> </html>
Google, Inc.
111 8th Avenue
New York, NY 10011, USA
+
+Wietse Venema
+porcupine.org
Null name or name name
void void defer_ctx
To undo a ptest_defer call call the function with a
+charset charset
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
HERMETICITY
VPRINT
deserializes
+PQerrorMessage
+deref
+openUTS
+xff
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 *));
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 : ' ');
/* 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.
}
}
+/* 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)
*/
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)
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);
"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.
*/
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;
}
/* 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
{
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;
/*
* 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);
}
/*
* 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);
/*
* 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);
}
* 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
#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]); \
}
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);
/*
/*
* 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.
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'",
"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,
* 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;
* 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),
* 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,
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,
{
int inst_flags;
int request_flags;
- DICT *dict;
+ DICT *dict = 0;
const char *reply_value;
int reply_status;
}
/*
- * 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);
}
{
int inst_flags;
int request_flags;
- DICT *dict;
+ DICT *dict = 0;
int dict_status;
int reply_status;
}
/*
- * 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);
}
{
int inst_flags;
int request_flags;
- DICT *dict;
+ DICT *dict = 0;
int dict_status;
int reply_status;
}
/*
- * 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);
}
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);
}
}
} 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);
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);
}
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 {
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);
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
+/*
+/* Wietse Venema
+/* porcupine.org
/*--*/
/*
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,
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;
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;
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
+/*
+/* Wietse Venema
+/* porcupine.org
/*--*/
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
+/*
+/* Wietse Venema
+/* porcupine.org
/*--*/
/*
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;
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)
{
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 *));
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);
* 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)
* 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
}
#ifndef NO_FTRUNCATE
- if (st0.st_size)
+ /* 202604 Claude: use the post-myflock() fstat result. */
+ if (st1.st_size)
ftruncate(fd, 0);
#endif
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);
}
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);
*/
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);
/*
* "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",
*/
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);
/*
* 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);
*/
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.
/* 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
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);
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);
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);
./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: *
/*
* 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 { \
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);
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);
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':
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 */
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);
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;
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);