Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package postfix for openSUSE:Factory checked in at 2026-09-10 11:46:58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/postfix (Old) and /work/SRC/openSUSE:Factory/.postfix.new.1265 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "postfix" Thu Sep 10 11:46:58 2026 rev:278 rq:1376583 version:3.11.7 Changes: -------- --- /work/SRC/openSUSE:Factory/postfix/postfix-bdb.changes 2026-08-19 17:55:16.101420459 +0200 +++ /work/SRC/openSUSE:Factory/.postfix.new.1265/postfix-bdb.changes 2026-09-10 11:47:29.657766908 +0200 @@ -1,0 +2,74 @@ +Tue Sep 8 17:47:46 UTC 2026 - Arjen de Korte <[email protected]> + +- update to 3.11.7 + * Bug (introduced: Postfix 3.9, date: 20240106) SMTP smuggling + was still possible with smtpd_proxy_filter (disabled by default) + when the after-filter SMTP server used the default policy + settings "smtpd_forbid_bare_newline_exclusions = $mynetworks" + and "smtpd_forbid_bare_newline = normalize". + * Bug (introduced: Postfix 3.11, date: 20250917): SMTP smuggling + was possible with smtpd_proxy_filter (disabled by default) when + the before-filter SMTP server added a "Require-TLS-ESMTP: yes" + message header, due to implementation edge cases. Adding this + header is enabled with the "requiretls_esmtp_header = yes" + default setting. + * Bug (defect introduced: Postfix 3.0, date: 20140707): null + pointer read error after receiving MAIL FROM, RCPT TO, and VRFY + with an UTF8 address but no SMTPUTF8 parameter. This requires + "smtputf8_enable = yes" (the default) and "strict_smtputf8 = + yes" (not default). With this, the SMTP server did an unnecessary + MAIL FROM reset without RCPT TO reset. A crafted remote SMTP + client could then send a DATA command and crash a Postfix SMTP + server process with a null pointer read error. + * Bug (defect introduced: Postfix 3.4, date: 20180303): the MySQL + client setting "tls_verify_cert = yes" had no effect with Oracle + MySQL 8 and later. + * Bug (defect introduced: Postfix-beta, date: 19990119): the + pipe(8) delivery agent deleted a command-line argument if the + argument contained $user AND $user expanded to an empty string, + breaking the positional order of arguments. This was a workaround + for a problem that hopefully no longer exists. + * Bug (defect introduced: Postfix 2.3, date: 20050323): the SMTP + client enhanced status code parser could process stale data + when a remote SMTP server sent a three-digit reply without other + text. + * Isolation: stamp Postfix SMTP server TLS session tickets with + their master.cf service name. With this, an SMTP server defined + in master.cf will no longer accept tickets issued by a different + SMTP server defined in the same master.cf file. + * The postmap and postalias commands now log a warning when + creating a root-owned database file in a directory that is not + owned by root. They log that the database source file, indexed + file(s), and parent directory should have the same owner, to + prevent a privilege-escalation attack. + * Bug (introduced: Postfix 2.3, date: 20060629): a malicious + Milter or attacker-in-the-middle could trigger a null-terminated + heap memory overread in the SMTP daemon while formatting a + malformed multiline response. + * Bug (defect introduced: Postfix 3.0, date: 20141117): in the + postqueue command don't free() text before logging a fatal error + message. + * Code hygiene: in the SMTP client protocol engine, evaluate a + RETURN() macro argument before freeing resources. + * (Postfix 3.11) Hardening: in the non-BerkeleyDB migration + service, delay the decision between running postmap or postalias + until after the database file/directory owner/permission checks. + The benefit from making the decision early (better error messages) + was not worth the risk. + * (Postfix-3.11) Hardened the database parent directory permission + checks for automatic re-indexing with the non-Berkeley-DB + migration service. + * Hardening command-line email submission: the postdrop command + now disallows null and line-break characters in queue file + envelope records (line-break characters in non-envelope queue + file records are already neutralized by default with + "cleanup_replace_stray_cr_lf = yes"). + * Shut up nagging from multiple AIs and harden the virtual delivery + agent against an evil (LDAP or SQL) database. + * Code hygiene: myrealloc(ptr, 0) still resulted in a panic. + Also adopt a mystrndup() fix from Postfix 3.12. + * Portability: OpenBSD does not define NS_INT16SZ. +- refreshed patch: + % postfix-vda-v14-3.0.3.patch + +------------------------------------------------------------------- postfix.changes: same change Old: ---- postfix-3.11.6.tar.gz postfix-3.11.6.tar.gz.asc New: ---- postfix-3.11.7.tar.gz postfix-3.11.7.tar.gz.asc ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ postfix-bdb.spec ++++++ --- /var/tmp/diff_new_pack.RVJ8R7/_old 2026-09-10 11:47:31.946862871 +0200 +++ /var/tmp/diff_new_pack.RVJ8R7/_new 2026-09-10 11:47:31.953863165 +0200 @@ -61,7 +61,7 @@ %endif %bcond_without ldap Name: postfix-bdb -Version: 3.11.6 +Version: 3.11.7 Release: 0 Summary: A fast, secure, and flexible mailer License: EPL-2.0 OR IPL-1.0 ++++++ postfix.spec ++++++ --- /var/tmp/diff_new_pack.RVJ8R7/_old 2026-09-10 11:47:32.040866812 +0200 +++ /var/tmp/diff_new_pack.RVJ8R7/_new 2026-09-10 11:47:32.042866896 +0200 @@ -63,7 +63,7 @@ # %bcond_without ldap Name: postfix -Version: 3.11.6 +Version: 3.11.7 Release: 0 Summary: A fast, secure, and flexible mailer License: EPL-2.0 OR IPL-1.0 ++++++ postfix-3.11.6.tar.gz -> postfix-3.11.7.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/HISTORY new/postfix-3.11.7/HISTORY --- old/postfix-3.11.6/HISTORY 2026-08-09 22:34:23.000000000 +0200 +++ new/postfix-3.11.7/HISTORY 2026-09-06 16:10:56.000000000 +0200 @@ -31118,3 +31118,123 @@ Queue hygiene: the postdrop command accepted the null record type which the rest of Postfix ingnores. Reported by OpenAI Security. File: postdrop.c. + +20260903 + + Bug (defect introduced: Postfix 2.3, date: 20050323): the + SMTP client enhanced status code parser could process stale + data. Reported by Qualys, assisted by Claude Mythos Preview. + File: smtp_chat.c. + + Bug (defect introduced: Postfix 3.0, date: 20141117): don't + free() text before logging a fatal error message. Reported + by Qualys, assisted by Claude Mythos Preview. File: + postqueue.c. + + Hardening: in the non-BerkeleyDB migration service, delay + the decision between running postmap or postalias until + after the database file/directory owner/permission checks. + The benefit from making the decision early (better error + messages) was not worth the risk. Qualys, assisted by Claude + Mythos Preview. Files: nbdb_process.c, nbdb_process_test.c. + + Hardened the database parent directory permission checks + for automatic re-indexing with the non-Berkeley-DB migration + service. Files: nbdb_safe.c, nbdb_safe.h, nbdb_safe_test.c, + nbdb_process_test.c. + + Bug (defect introduced: Postfix-beta, date: 19990119): the + pipe(8) delivery agent deleted a command-line argument if + the argument contained $user AND $user expanded to an empty + string, breaking the positional order of arguments. This + was a workaround for a problem that hopefully no longer + exists. Reported by Qualys, assisted by Claude Mythos + Preview. File: pipe/pipe.c. + + Code hygiene: in the SMTP client protocol engine, evaluate + the RETURN() macro argument before freeing resources. + Reported by Qualys, assisted by Claude Mythos Preview. File: + smtp_proto.c. + + Code hygiene: myrealloc(ptr, 0) still resulted in a panic. + Reported by Qualys, assisted by Claude Mythos Preview. Also + adopt a mystrndup() fix from Postfix 3.12. File: mymalloc.c. + + The postmap and postalias commands now log a warning when + creating a root-owned database file in a directory that is + not owned by root. They log that the database source file, + indexed file(s), and parent directory should have the same + owner, to prevent a privilege-escalation attack. Problem + reported by OpenAI Security, remediation strategy (don't + break PROD) by Wietse. Files: postalias.c, postmap.c. + + Hardening command-line email submission: the postdrop + command now disallows null and line-break characters in + queue file envelope records (line-break characters in other + queue file records are already neutralized by default with + "cleanup_replace_stray_cr_lf = yes"). + + The new constraint not only eliminates line-break injection + into local mailbox files as reported by OpenAI Security, + but also prevents other forms of misuse. Later, this + constraint may be moved into the Postfix core. Fix by Wietse. + File: postdrop.c. + + Shut up nagging from multiple AIs and harden the virtual + delivery agent against an evil SQL database. File: + virtual/mailbox.c. + + Bug (defect introduced: Postfix 3.4, date: 20180303): the + MySQL client setting "tls_verify_cert = yes" had no effect + with Oracle MySQL 8 and later. Report and fix by OpenAI + Security. File: dict_mysql.c. + + Isolation: stamp Postfix SMTP server TLS session tickets + with their master.cf service name. With this, an SMTP server + defined in master.cf will no longer accept tickets issued + by a different SMTP server defined in the same master.cf + file. Fix by OpenAI security. File: tls_server.c. + + Bug (introduced: Postfix 2.3, date: 20060629): a malicious + Milter or attacker-in-the-middle could trigger a null-terminated + heap memory overread in the SMTP daemon while formatting a + malformed multiline response. Fix from OpenAI Security + adopted with minor changes. Files: milter8.c, smtpd_chat.c. + + Bug (introduced: Postfix 3.9, date: 20240106) SMTP smuggling + was still possible with smtpd_proxy_filter (disabled by + default) when the after-filter SMTP server used the default + policy settings "smtpd_forbid_bare_newline_exclusions = + $mynetworks" and "smtpd_forbid_bare_newline = normalize". + Reported by OpenAI Security. Fix by Wietse. Files: + smtpd_proxy.[hc]. + + Bug (introduced: Postfix 3.11, date: 20250917): SMTP smuggling + was possible with smtpd_proxy_filter (disabled by default) + when the before-filter SMTP server added a "Require-TLS-ESMTP: + yes" message header, due to implementation edge cases. + Adding this header is enabled with the "requiretls_esmtp_header + = yes" default setting. Reported by OpenAI Security. Fix + by Wietse. Files: smtpd_proxy.[hc]. + + Cleanup: as suggested by OpenAI Security, eliminate stray + CR characters from the smtpd_proxy_filter input stream. + The before-proxy-filter SMTP server already eliminated stray + LF. File: smtpd/smtpd_proxy.[hc]. + + Portability: OpenBSD does not define NS_INT16SZ. Brad Smith. + File: dns.h. + + Shut up nagging about allowing gid==0 in database responses. + File: virtual/mailbox.c. + + Bug (defect introduced: Postfix 3.0, date: 20140707): null + pointer read error after receiving MAIL FROM, RCPT TO, and + VRFY with an UTF8 address but no SMTPUTF8 parameter. This + requires "smtputf8_enable = yes" (the default) and + "strict_smtputf8 = yes" (not default). With this, the SMTP + server did an unnecessary MAIL FROM reset without RCPT TO + reset. A crafted remote SMTP client could then send a DATA + command and crash the Postfix SMTP server with a null pointer + read error. Reported by Wonyoung Jung (정원영). File: + smtpd/smtpd.c. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/dns/dns.h new/postfix-3.11.7/src/dns/dns.h --- old/postfix-3.11.6/src/dns/dns.h 2026-01-27 23:01:58.000000000 +0100 +++ new/postfix-3.11.7/src/dns/dns.h 2026-09-03 21:31:19.000000000 +0200 @@ -54,6 +54,10 @@ #endif +#ifndef NS_INT16SZ +#define NS_INT16SZ 2 +#endif + /* * Provide API compatibility for systems without res_nxxx() API. Also * require calling dns_get_h_errno() instead of directly accessing the diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/global/dict_mysql.c new/postfix-3.11.7/src/global/dict_mysql.c --- old/postfix-3.11.6/src/global/dict_mysql.c 2025-06-24 23:24:27.000000000 +0200 +++ new/postfix-3.11.7/src/global/dict_mysql.c 2026-09-03 21:09:22.000000000 +0200 @@ -118,7 +118,8 @@ #if defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 50023 #define DICT_MYSQL_SSL_VERIFY_SERVER_CERT MYSQL_OPT_SSL_VERIFY_SERVER_CERT #elif MYSQL_VERSION_ID >= 80000 -#define DICT_MYSQL_SSL_VERIFY_SERVER_CERT MYSQL_OPT_SSL_MODE +/* 202607 OpenAI: MYSQL_OPT_SSL_MODE is an enum, not a bool. */ +#define DICT_MYSQL_SSL_MODE MYSQL_OPT_SSL_MODE #endif /* @@ -171,7 +172,7 @@ char *tls_CAfile; char *tls_CApath; char *tls_ciphers; -#if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT) +#if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT) || defined(DICT_MYSQL_SSL_MODE) int tls_verify_cert; #endif int require_result_set; @@ -601,6 +602,11 @@ */ static void plmysql_connect_single(DICT_MYSQL *dict_mysql, HOST *host) { +#if defined(DICT_MYSQL_SSL_MODE) + enum mysql_ssl_mode ssl_mode; + +#endif + if ((host->db = mysql_init(NULL)) == NULL) msg_fatal("dict_mysql: insufficient memory"); if (dict_mysql->option_file) @@ -628,7 +634,19 @@ dict_mysql->tls_CAfile, dict_mysql->tls_CApath, dict_mysql->tls_ciphers); #endif -#if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT) +#if defined(DICT_MYSQL_SSL_MODE) + if (dict_mysql->tls_verify_cert != -1) { + /* MYSQL_OPT_SSL_MODE enum values are not Boolean values. */ + ssl_mode = dict_mysql->tls_verify_cert ? SSL_MODE_VERIFY_IDENTITY : + SSL_MODE_PREFERRED; + if (mysql_options(host->db, DICT_MYSQL_SSL_MODE, &ssl_mode) != 0) { + msg_warn("dict_mysql: could not set TLS mode for host %s: %s", + host->hostname, mysql_error(host->db)); + plmysql_down_host(host, dict_mysql->retry_interval); + return; + } + } +#elif defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT) if (dict_mysql->tls_verify_cert != -1) mysql_options(host->db, DICT_MYSQL_SSL_VERIFY_SERVER_CERT, &dict_mysql->tls_verify_cert); @@ -704,7 +722,7 @@ dict_mysql->tls_CAfile = cfg_get_str(p, "tls_CAfile", NULL, 0, 0); dict_mysql->tls_CApath = cfg_get_str(p, "tls_CApath", NULL, 0, 0); dict_mysql->tls_ciphers = cfg_get_str(p, "tls_ciphers", NULL, 0, 0); -#if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT) +#if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT) || defined(DICT_MYSQL_SSL_MODE) dict_mysql->tls_verify_cert = cfg_get_bool(p, "tls_verify_cert", -1); #endif dict_mysql->require_result_set = cfg_get_bool(p, "require_result_set", 1); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/global/mail_version.h new/postfix-3.11.7/src/global/mail_version.h --- old/postfix-3.11.6/src/global/mail_version.h 2026-08-09 22:36:19.000000000 +0200 +++ new/postfix-3.11.7/src/global/mail_version.h 2026-09-07 18:24:23.000000000 +0200 @@ -20,8 +20,8 @@ * Patches change both the patchlevel and the release date. Snapshots have no * patchlevel; they change the release date only. */ -#define MAIL_RELEASE_DATE "20260809" -#define MAIL_VERSION_NUMBER "3.11.6" +#define MAIL_RELEASE_DATE "20260907" +#define MAIL_VERSION_NUMBER "3.11.7" #ifdef SNAPSHOT #define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/milter/milter8.c new/postfix-3.11.7/src/milter/milter8.c --- old/postfix-3.11.6/src/milter/milter8.c 2026-07-06 16:33:50.000000000 +0200 +++ new/postfix-3.11.7/src/milter/milter8.c 2026-09-03 21:12:17.000000000 +0200 @@ -955,6 +955,64 @@ return (err); } +/* milter8_valid_smtp_reply - validate SMTP reply (after 202607 OpenAI) */ + +static int milter8_valid_smtp_reply(VSTRING *buf) +{ + const unsigned char *cp = (unsigned char *) STR(buf); + const unsigned char *buf_end = cp + LEN(buf); + + /* Exclude one terminal Milter protocol null from the reply text. */ + if (buf_end > cp && buf_end[-1] == 0) + buf_end -= 1; + + if (memchr(cp, 0, buf_end - cp) != 0) + return (0); + + while (cp < buf_end) { + const unsigned char *line_end; + ssize_t line_len; + int final; + + /* Part of a breaking change, not allowed in stable release. */ +#ifdef SNAPSHOT + unsigned char code[3]; + int first = 1; + +#endif + + /* A final line must not end in a line terminator. */ + line_len = strcspn((const char *) cp, "\r\n"); + line_end = cp + line_len; + final = (line_end == buf_end); + + /* Each line must match /^ddd(-| )d?/. */ + if (line_len < 4 || (cp[0] != '4' && cp[0] != '5') || !ISDIGIT(cp[1]) + || !ISDIGIT(cp[2]) || (cp[3] != (final ? ' ' : '-')) + || (line_len > 4 && ISDIGIT(cp[4]) && cp[4] != cp[0])) + return (0); + + /* Part of a breaking change, not allowed in stable release. */ +#ifdef SNAPSHOT + if (first) { + code[0] = cp[0]; + code[1] = cp[1]; + code[2] = cp[2]; + first = 0; + } else if (cp[0] != code[0] || cp[1] != code[1] || cp[2] != code[2]) { + return (0); + } +#endif + /* A non-final line must end in a line terminator. */ + if (final) + return (1); + if (line_end > buf_end - 2 || *line_end != '\r' || line_end[1] != '\n') + return (0); + cp = line_end + 2; + } + return (0); +} + /* milter8_event - report event and receive reply */ static const char *milter8_event(MILTER8 *milter, int event, @@ -1290,13 +1348,7 @@ MILTER8_DATA_BUFFER, milter->buf, MILTER8_DATA_END) != 0) MILTER8_EVENT_BREAK(milter->def_reply); - /* XXX Enforce this for each line of a multi-line reply. */ - if ((STR(milter->buf)[0] != '4' && STR(milter->buf)[0] != '5') - || !ISDIGIT(STR(milter->buf)[1]) - || !ISDIGIT(STR(milter->buf)[2]) - || (STR(milter->buf)[3] != ' ' && STR(milter->buf)[3] != '-') - || (ISDIGIT(STR(milter->buf)[4]) - && (STR(milter->buf)[4] != STR(milter->buf)[0]))) { + if (!milter8_valid_smtp_reply(milter->buf)) { msg_warn("milter %s: malformed reply: %s", milter->m.name, STR(milter->buf)); milter8_conf_error(milter); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/nbdb_reindexd/Makefile.in new/postfix-3.11.7/src/nbdb_reindexd/Makefile.in --- old/postfix-3.11.6/src/nbdb_reindexd/Makefile.in 2026-02-26 01:25:02.000000000 +0100 +++ new/postfix-3.11.7/src/nbdb_reindexd/Makefile.in 2026-09-03 20:36:33.000000000 +0200 @@ -50,7 +50,7 @@ test_nbdb_sniffer: nbdb_sniffer_test $(SHLIB_ENV) $(VALGRIND) ./nbdb_sniffer_test -SAFE_TEST_OBJ = nbdb_safe.o +SAFE_TEST_OBJ = nbdb_safe.o $(TESTLIB) nbdb_safe_test: nbdb_safe_test.o $(SAFE_TEST_OBJ) $(LIBS) $(CC) $(CFLAGS) -o $@ [email protected] $(SAFE_TEST_OBJ) $(LIBS) $(SYSLIBS) @@ -206,6 +206,8 @@ nbdb_safe.o: ../../include/check_arg.h nbdb_safe.o: ../../include/mail_conf.h nbdb_safe.o: ../../include/mail_params.h +nbdb_safe.o: ../../include/msg.h +nbdb_safe.o: ../../include/mymalloc.h nbdb_safe.o: ../../include/sys_defs.h nbdb_safe.o: ../../include/vbuf.h nbdb_safe.o: ../../include/vstring.h @@ -218,6 +220,7 @@ nbdb_safe_test.o: ../../include/check_arg.h nbdb_safe_test.o: ../../include/mail_conf.h nbdb_safe_test.o: ../../include/mail_params.h +nbdb_safe_test.o: ../../include/mock_stat.h nbdb_safe_test.o: ../../include/msg.h nbdb_safe_test.o: ../../include/msg_vstream.h nbdb_safe_test.o: ../../include/stringops.h diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/nbdb_reindexd/nbdb_process.c new/postfix-3.11.7/src/nbdb_reindexd/nbdb_process.c --- old/postfix-3.11.6/src/nbdb_reindexd/nbdb_process.c 2026-02-19 22:58:18.000000000 +0100 +++ new/postfix-3.11.7/src/nbdb_reindexd/nbdb_process.c 2026-09-03 20:35:08.000000000 +0200 @@ -171,26 +171,24 @@ } /* - * Should we run postmap or postalias? Open the source file with the same - * (uid, gid) as the postmap or postalias commands would, so that we can - * detect permission errors quickly. - * - * Note: we do this before the file allowlist/owner/permission safety - * checks, so that we can log the concrete postmap or postalias command - * if a safety check fails. - */ - if ((index_cmd = nbdb_get_index_cmd_as(source_path, leg_idx_st.st_uid, - leg_idx_st.st_gid, why)) == 0) - return (NBDB_STAT_ERROR); - - /* * Allow indexing as the legacy indexed file owner if it is considered * "safe". */ if (!nbdb_safe_to_index_as_legacy_index_owner(source_path, &source_st, STR(leg_idx_path), &leg_idx_st, parent_dir, &parent_dir_st, why)) { - status = NBDB_STAT_ERROR; + return (NBDB_STAT_ERROR); + } + + /* + * Should we run postmap or postalias? Open the source file with the same + * (uid, gid) as the postmap or postalias commands would, so that we can + * detect permission errors quickly. 202607 Qualys+Mythos: move this + * after the safety checks. + */ + if ((index_cmd = nbdb_get_index_cmd_as(source_path, leg_idx_st.st_uid, + leg_idx_st.st_gid, why)) == 0) { + return (NBDB_STAT_ERROR); } /* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/nbdb_reindexd/nbdb_process_test.c new/postfix-3.11.7/src/nbdb_reindexd/nbdb_process_test.c --- old/postfix-3.11.6/src/nbdb_reindexd/nbdb_process_test.c 2026-02-23 00:30:58.000000000 +0100 +++ new/postfix-3.11.7/src/nbdb_reindexd/nbdb_process_test.c 2026-09-03 20:36:33.000000000 +0200 @@ -102,6 +102,8 @@ MOCK_STAT_REQ mock_stat_source; MOCK_STAT_REQ mock_stat_leg_idx; MOCK_STAT_REQ mock_stat_parent; + MOCK_STAT_REQ mock_stat_slash_path; /* "/path" */ + MOCK_STAT_REQ mock_stat_slash; /* "/" */ MOCK_STAT_REQ mock_stat_new_idx; MOCK_OPEN_AS_REQ mock_open; MOCK_SPAWN_CMD_REQ mock_spawn; @@ -143,6 +145,7 @@ nbdb_util_init(tp->params.migr_level); setup_mock_cdb("{{x = x}}"); setup_mock_lmdb("{{x = x}}"); + teardown_mock_stat(); if (nbdb_level >= NBDB_LEV_CODE_REINDEX) { var_nbdb_allow_root_pfxs = (char *) tp->params.allow_root_pfxs; var_nbdb_allow_user_pfxs = (char *) tp->params.allow_user_pfxs; @@ -154,6 +157,8 @@ setup_mock_stat(&tp->mock_stat_leg_idx); setup_mock_stat(&tp->mock_stat_parent); setup_mock_stat(&tp->mock_stat_new_idx); + setup_mock_stat(&tp->mock_stat_slash_path); + setup_mock_stat(&tp->mock_stat_slash); setup_mock_vstream_fopen_as(&tp->mock_open); setup_mock_spawn_command(&tp->mock_spawn); } @@ -222,6 +227,16 @@ .out_errno = 0, .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, }, + .mock_stat_slash_path = { + .want_path = "/path", + .out_errno = 0, + .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, + }, + .mock_stat_slash = { + .want_path = "/", + .out_errno = 0, + .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, + }, .mock_stat_new_idx = { .want_path = "/path/to/file.cdb", .out_errno = ENOENT, @@ -286,6 +301,16 @@ .out_errno = 0, .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, }, + .mock_stat_slash_path = { + .want_path = "/path", + .out_errno = 0, + .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, + }, + .mock_stat_slash = { + .want_path = "/", + .out_errno = 0, + .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, + }, .mock_stat_new_idx = { .want_path = "/path/to/file.cdb", .out_errno = ENOENT, @@ -338,6 +363,16 @@ .out_errno = 0, .out_st = {.st_mode = S_IWGRP,.st_uid = 0,.st_gid = 0,}, }, + .mock_stat_slash_path = { + .want_path = "/path", + .out_errno = 0, + .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, + }, + .mock_stat_slash = { + .want_path = "/", + .out_errno = 0, + .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, + }, .mock_stat_new_idx = { .want_path = "/path/to/file.cdb", .out_errno = ENOENT, @@ -379,6 +414,16 @@ .out_errno = 0, .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, }, + .mock_stat_slash_path = { + .want_path = "/path", + .out_errno = 0, + .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, + }, + .mock_stat_slash = { + .want_path = "/", + .out_errno = 0, + .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, + }, .mock_stat_new_idx = { .want_path = "/path/to/file.cdb", .out_errno = ENOENT, @@ -430,6 +475,16 @@ .st_mode = S_IRWXU,.st_uid = 1,.st_gid = 0, }, }, + .mock_stat_slash_path = { + .want_path = "/path", + .out_errno = 0, + .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, + }, + .mock_stat_slash = { + .want_path = "/", + .out_errno = 0, + .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, + }, .mock_stat_new_idx = { .want_path = "/path/to/file.cdb", .out_errno = ENOENT, @@ -450,7 +505,7 @@ .in_new_type = "hash", .in_source_path = "/path/to/file", .want_status = NBDB_STAT_ERROR, - .want_why = "could not execute command 'postmap cdb:/path/to/file': table /path/to/file has an unexpected pathname", + .want_why = "table /path/to/file has an unexpected pathname", }, }, @@ -471,6 +526,16 @@ .out_errno = 0, .out_st = {.st_mode = S_IRWXU,.st_uid = 1,.st_gid = 0,}, }, + .mock_stat_slash_path = { + .want_path = "/path", + .out_errno = 0, + .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, + }, + .mock_stat_slash = { + .want_path = "/", + .out_errno = 0, + .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, + }, .mock_stat_new_idx = { .want_path = "/path/to/file.cdb", .out_errno = ENOENT, @@ -524,13 +589,23 @@ .out_errno = 0, .out_st = {.st_mode = S_IRWXU,.st_uid = 1,.st_gid = 0,}, }, + .mock_stat_slash_path = { + .want_path = "/path", + .out_errno = 0, + .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, + }, + .mock_stat_slash = { + .want_path = "/", + .out_errno = 0, + .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,}, + }, .mock_stat_new_idx = { .want_path = "/path/to/file.cdb", .out_errno = ENOENT, }, .mock_open = { .want_path = "/path/to/file", - .want_uid = 0, + .want_uid = 1, .want_gid = 0, .out_errno = 0, .out_data = "foo bar\nfoo :bar", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/nbdb_reindexd/nbdb_safe.c new/postfix-3.11.7/src/nbdb_reindexd/nbdb_safe.c --- old/postfix-3.11.6/src/nbdb_reindexd/nbdb_safe.c 2026-02-19 22:58:18.000000000 +0100 +++ new/postfix-3.11.7/src/nbdb_reindexd/nbdb_safe.c 2026-09-03 20:36:33.000000000 +0200 @@ -10,6 +10,11 @@ /* uid_t uid, /* const struct stat *st) /* +/* bool nbdb_safe_parent_for_uid( +/* uid_t uid, +/* const char *parent_dir, +/* const struct stat *parent_dir_st) +/* /* bool nbdb_safe_to_index_as_legacy_index_owner( /* const char *source_path, /* const struct stat *source_st, @@ -26,6 +31,11 @@ /* the safety of other pathname components. It is a good idea to /* trust only a limited number of pathname prefixes. /* +/* nbdb_safe_parent_for_uid() implements nbdb_safe_for_uid() +/* for the directories in the specified pathname. When a pathname +/* element contains a symlink, this function will examine the +/* symlink target but not the target's parent directories. +/* /* nbdb_safe_to_index_as_legacy_index_owner() determines /* if a Berkeley DB source file and parent directory are 'safe' /* for the uid of the Berkeley DB indexed file. @@ -55,6 +65,8 @@ */ #include <sys_defs.h> #include <sys/stat.h> +#include <string.h> +#include <libgen.h> /* * Utility library. @@ -67,6 +79,9 @@ #include <allowed_prefix.h> #include <mail_conf.h> #include <mail_params.h> +#include <msg.h> +#include <mymalloc.h> +#include <wrap_stat.h> /* * Application-specific. @@ -74,6 +89,36 @@ #include <nbdb_safe.h> #include <nbdb_reindexd.h> +/* nbdb_safe_parent_for_uid - simple pathname safety */ + +bool nbdb_safe_parent_for_uid(uid_t uid, const char *path, + const struct stat * path_st) +{ + bool ret; + + /* + * See also the comment in nbdb_safe_for_uid(). + */ + if (!nbdb_safe_for_uid(uid, path_st)) { + ret = false; + } else if (strcmp(path, "/") == 0) { + ret = true; + } else { + char *saved_path = mystrdup(path); + char *parent = dirname(saved_path); + struct stat parent_st; + + if (stat(parent, &parent_st) < 0) { + ret = false; + msg_warn("look up status for directory '%s': %m", parent); + } else { + ret = nbdb_safe_parent_for_uid(uid, parent, &parent_st); + } + myfree(saved_path); + } + return (ret); +} + /* nbdb_safe_for_uid - owned by the user or root, not group or other writable */ bool nbdb_safe_for_uid(uid_t uid, const struct stat *st) @@ -147,7 +192,7 @@ leg_idx_path, (int) runas_uid, source_path); return (false); } - if (!nbdb_safe_for_uid(runas_uid, parent_dir_st)) { + if (!nbdb_safe_parent_for_uid(runas_uid, parent_dir, parent_dir_st)) { vstring_sprintf(why, "legacy indexed file '%s' is owned by " "uid '%d', but parent directory '%s' is " "owned or writable by other user; to allow " diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/nbdb_reindexd/nbdb_safe.h new/postfix-3.11.7/src/nbdb_reindexd/nbdb_safe.h --- old/postfix-3.11.6/src/nbdb_reindexd/nbdb_safe.h 2026-02-19 22:58:18.000000000 +0100 +++ new/postfix-3.11.7/src/nbdb_reindexd/nbdb_safe.h 2026-09-03 20:36:33.000000000 +0200 @@ -25,6 +25,7 @@ * Internal API. */ extern bool nbdb_safe_for_uid(uid_t, const struct stat *); +extern bool nbdb_safe_parent_for_uid(uid_t, const char *, const struct stat *); extern bool nbdb_safe_to_index_as_legacy_index_owner( const char *, const struct stat *, const char *, const struct stat *, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/nbdb_reindexd/nbdb_safe_test.c new/postfix-3.11.7/src/nbdb_reindexd/nbdb_safe_test.c --- old/postfix-3.11.6/src/nbdb_reindexd/nbdb_safe_test.c 2026-02-19 22:58:18.000000000 +0100 +++ new/postfix-3.11.7/src/nbdb_reindexd/nbdb_safe_test.c 2026-09-03 20:36:33.000000000 +0200 @@ -21,6 +21,7 @@ * System library. */ #include <sys_defs.h> +#include <sys/stat.h> #include <errno.h> #include <stdlib.h> #include <string.h> @@ -41,6 +42,11 @@ #include <mail_params.h> /* + * Test library. + */ +#include <mock_stat.h> + + /* * Application-specific. */ #include <nbdb_safe.h> @@ -72,8 +78,11 @@ struct stat source_st; const char *leg_idx_path; struct stat leg_idx_st; - const char *parent_dir; + const char *parent_dir; /* "/etc/postfix" */ struct stat parent_dir_st; + MOCK_STAT_REQ slash_etc_dir; /* "/etc" */ + MOCK_STAT_REQ slash_dir; /* "/" */ + struct stat slash_dir_st; const char *allow_root_pfxs; const char *allow_user_pfxs; const char *want_why; @@ -118,6 +127,9 @@ var_nbdb_allow_user_pfxs = (char *) tp->allow_user_pfxs; parsed_allow_root_pfxs = allowed_prefix_create(var_nbdb_allow_root_pfxs); parsed_allow_user_pfxs = allowed_prefix_create(var_nbdb_allow_user_pfxs); + teardown_mock_stat(); + setup_mock_stat(&tp->slash_etc_dir); + setup_mock_stat(&tp->slash_dir); got_bool = nbdb_safe_to_index_as_legacy_index_owner( tp->source_path, &tp->source_st, tp->leg_idx_path, &tp->leg_idx_st, @@ -231,6 +243,8 @@ .leg_idx_st = {.st_uid = 0,}, .parent_dir = "/etc/postfix", .parent_dir_st = {.st_uid = 0,.st_mode = S_IRWXU,}, + .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, + .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, .allow_root_pfxs = "/etc/postfix", .allow_user_pfxs = "", .want_bool = true, @@ -244,6 +258,8 @@ .leg_idx_st = {.st_uid = 0,}, .parent_dir = "/etc/postfix", .parent_dir_st = {.st_uid = 0,.st_mode = S_IRWXU,}, + .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, + .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, .allow_root_pfxs = "/etc/posttfix", .allow_user_pfxs = "", .want_bool = false, @@ -257,6 +273,8 @@ .leg_idx_st = {.st_uid = 0,}, .parent_dir = "/etc/postfix", .parent_dir_st = {.st_uid = 0,.st_mode = S_IRWXU,}, + .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, + .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, .allow_root_pfxs = "/etc/postfix", .allow_user_pfxs = "", .want_bool = false, @@ -270,6 +288,8 @@ .leg_idx_st = {.st_uid = 0,}, .parent_dir = "/etc/postfix", .parent_dir_st = {.st_uid = 0,.st_mode = S_IRWXU,}, + .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, + .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, .allow_root_pfxs = "/etc/postfix", .allow_user_pfxs = "", .want_bool = false, @@ -283,11 +303,43 @@ .leg_idx_st = {.st_uid = 0,}, .parent_dir = "/etc/postfix", .parent_dir_st = {.st_uid = 0,.st_mode = S_IRWXU,}, + .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, + .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, .allow_root_pfxs = "/etc/postfix", .allow_user_pfxs = "", .want_bool = false, .want_why = "'/etc/postfix/access' is owned or writable by other user", }, + {.label = "safe_to_index_as_root:bad_root_owner", + .action = test_nbdb_safe_to_index_as_legacy_index_owner, + .source_path = "/etc/postfix/access", + .source_st = {.st_uid = 0,.st_mode = S_IRWXU,}, + .leg_idx_path = "/etc/postfix/access.db", + .leg_idx_st = {.st_uid = 0,}, + .parent_dir = "/etc/postfix", + .parent_dir_st = {.st_uid = 0,.st_mode = S_IRWXU,}, + .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, + .slash_dir = {"/", {.st_uid = 1,.st_mode = S_IRWXU,}, 0,}, + .allow_root_pfxs = "/etc/postfix", + .allow_user_pfxs = "", + .want_bool = false, + .want_why = "", + }, + {.label = "safe_to_index_as_root:bad_root_perms", + .action = test_nbdb_safe_to_index_as_legacy_index_owner, + .source_path = "/etc/postfix/access", + .source_st = {.st_uid = 0,.st_mode = S_IRWXU,}, + .leg_idx_path = "/etc/postfix/access.db", + .leg_idx_st = {.st_uid = 0,}, + .parent_dir = "/etc/postfix", + .parent_dir_st = {.st_uid = 0,.st_mode = S_IRWXU,}, + .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, + .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IWOTH,}, 0,}, + .allow_root_pfxs = "/etc/postfix", + .allow_user_pfxs = "", + .want_bool = false, + .want_why = "", + }, {.label = "safe_to_index_as_root:bad_parent_owner", .action = test_nbdb_safe_to_index_as_legacy_index_owner, .source_path = "/etc/postfix/access", @@ -296,6 +348,8 @@ .leg_idx_st = {.st_uid = 0,}, .parent_dir = "/etc/postfix", .parent_dir_st = {.st_uid = 1,.st_mode = S_IRWXU,}, + .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, + .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, .allow_root_pfxs = "/etc/postfix", .allow_user_pfxs = "", .want_bool = false, @@ -309,6 +363,8 @@ .leg_idx_st = {.st_uid = 0,}, .parent_dir = "/etc/postfix", .parent_dir_st = {.st_uid = 0,.st_mode = S_IWGRP,}, + .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, + .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, .allow_root_pfxs = "/etc/postfix", .allow_user_pfxs = "", .want_bool = false, @@ -322,6 +378,8 @@ .leg_idx_st = {.st_uid = 0,}, .parent_dir = "/etc/postfix", .parent_dir_st = {.st_uid = 0,.st_mode = S_IWOTH,}, + .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, + .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,}, .allow_root_pfxs = "/etc/postfix", .allow_user_pfxs = "", .want_bool = false, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/pipe/pipe.c new/postfix-3.11.7/src/pipe/pipe.c --- old/postfix-3.11.6/src/pipe/pipe.c 2025-09-16 23:05:54.000000000 +0200 +++ new/postfix-3.11.7/src/pipe/pipe.c 2026-09-03 20:39:05.000000000 +0200 @@ -765,12 +765,6 @@ * Either anything to the left of the extension delimiter or, * in absence of the latter, anything to the left of the * rightmost @. - * - * Beware: if the user name is blank (e.g. +user@host), the - * argument is suppressed. This is necessary to allow for - * cyrus bulletin-board (global mailbox) delivery. XXX But, - * skipping empty user parts will also prevent other - * expansions of this specific command-line argument. */ if (state.expand_flag & PIPE_FLAG_USER) { morph_recipient(buf, rcpt_list->info[i].address, @@ -780,8 +774,7 @@ rcpt_list->info[i].address); if (*var_rcpt_delim) split_addr(STR(buf), var_rcpt_delim); - if (*STR(buf) == 0) - continue; + /* 202607 Qualys+Mythos: don't skip arg if $user is "".*/ dict_update(PIPE_DICT_TABLE, PIPE_DICT_USER, STR(buf)); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/postalias/postalias.c new/postfix-3.11.7/src/postalias/postalias.c --- old/postfix-3.11.6/src/postalias/postalias.c 2026-02-20 20:37:30.000000000 +0100 +++ new/postfix-3.11.7/src/postalias/postalias.c 2026-09-03 21:04:44.000000000 +0200 @@ -349,11 +349,30 @@ /* * If running as root, run as the owner of the source file, so that the * result shows proper ownership, and so that a bug in postalias does not - * allow privilege escalation. + * allow privilege escalation. If the source file is owned by root, but + * the parent directory is not, log a warning with suggestions. */ - if ((postalias_flags & POSTALIAS_FLAG_AS_OWNER) && getuid() == 0 - && (st.st_uid != geteuid() || st.st_gid != getegid())) - set_eugid(st.st_uid, st.st_gid); + if ((postalias_flags & POSTALIAS_FLAG_AS_OWNER) && getuid() == 0) { + if (st.st_uid != 0) { + set_eugid(st.st_uid, st.st_gid); + } else { + char *parent_dir; + struct stat parent_st; + + parent_dir = sane_dirname((VSTRING *) 0, path_name); + if (stat(parent_dir, &parent_st) < 0) + msg_fatal("stat %s: %m", parent_dir); + if (parent_st.st_uid != 0) { + msg_warn("Creating root-owned database file(s) %s.* " + "in non-root-owned directory %s. Files " + "%s, %s.*, and directory %s should have " + "the same owner. Please fix ownership " + "to avoid a privilege-escalation attack.", + path_name, parent_dir, VSTREAM_PATH(source_fp), + path_name, parent_dir); + } + } + } /* * Override the default per-table cache size for DB map (re)builds. We diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/postdrop/postdrop.c new/postfix-3.11.7/src/postdrop/postdrop.c --- old/postfix-3.11.6/src/postdrop/postdrop.c 2026-08-09 22:31:40.000000000 +0200 +++ new/postfix-3.11.7/src/postdrop/postdrop.c 2026-09-03 21:06:43.000000000 +0200 @@ -515,6 +515,11 @@ msg_fatal("uid=%ld: malformed input", (long) uid); if (rec_type == 0 || strchr(*expected, rec_type) == 0) msg_fatal("uid=%ld: unexpected record type: %d", (long) uid, rec_type); + /* 202607 OpenAI: reject line breaks and nulls in envelope content. */ + if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT + && strcspn(vstring_str(buf), "\r\n") != VSTRING_LEN(buf)) + msg_fatal("uid=%ld: null or line break in '%s' record type: %.200s", + (long) uid, rec_type_name(rec_type), vstring_str(buf)); if (rec_type == **expected) expected++; /* Override time information from the untrusted caller. */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/postmap/postmap.c new/postfix-3.11.7/src/postmap/postmap.c --- old/postfix-3.11.6/src/postmap/postmap.c 2026-02-20 20:37:30.000000000 +0100 +++ new/postfix-3.11.7/src/postmap/postmap.c 2026-09-03 21:04:44.000000000 +0200 @@ -442,11 +442,30 @@ /* * If running as root, run as the owner of the source file, so that the * result shows proper ownership, and so that a bug in postmap does not - * allow privilege escalation. + * allow privilege escalation. If the source file is owned by root, but + * the parent directory is not, log a warning with suggestions. */ - if ((postmap_flags & POSTMAP_FLAG_AS_OWNER) && getuid() == 0 - && (st.st_uid != geteuid() || st.st_gid != getegid())) - set_eugid(st.st_uid, st.st_gid); + if ((postmap_flags & POSTMAP_FLAG_AS_OWNER) && getuid() == 0) { + if (st.st_uid != 0) { + set_eugid(st.st_uid, st.st_gid); + } else { + char *parent_dir; + struct stat parent_st; + + parent_dir = sane_dirname((VSTRING *) 0, path_name); + if (stat(parent_dir, &parent_st) < 0) + msg_fatal("stat %s: %m", parent_dir); + if (parent_st.st_uid != 0) { + msg_warn("Creating root-owned database file(s) %s.* " + "in non-root-owned directory %s. Files " + "%s, %s.*, and directory %s should have " + "the same owner. Please fix ownership " + "to avoid a privilege-escalation attack.", + path_name, parent_dir, VSTREAM_PATH(source_fp), + path_name, parent_dir); + } + } + } /* * Override the default per-table cache size for DB map (re)builds. We diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/postqueue/postqueue.c new/postfix-3.11.7/src/postqueue/postqueue.c --- old/postfix-3.11.6/src/postqueue/postqueue.c 2025-12-19 18:22:14.000000000 +0100 +++ new/postfix-3.11.7/src/postqueue/postqueue.c 2026-09-03 20:33:08.000000000 +0200 @@ -431,10 +431,11 @@ stat = vstream_pclose(showq); } argv_free(argv); - myfree(showq_path); + /* 202606 Qualys+Mythos: don't free() text before logging. */ if (stat != 0) msg_fatal_status(stat < 0 ? EX_OSERR : EX_SOFTWARE, "Error running %s", showq_path); + myfree(showq_path); } /* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/smtp/smtp_chat.c new/postfix-3.11.7/src/smtp/smtp_chat.c --- old/postfix-3.11.6/src/smtp/smtp_chat.c 2021-10-02 16:46:46.000000000 +0200 +++ new/postfix-3.11.7/src/smtp/smtp_chat.c 2026-09-03 20:31:04.000000000 +0200 @@ -413,7 +413,8 @@ if (three_digs != 0) { rdata.code = atoi(STR(session->buffer)); if (strchr("245", STR(session->buffer)[0]) != 0) { - for (cp = STR(session->buffer) + 4; *cp == ' '; cp++) + /* 202606 Qualys+Mythos: start loop immediately after 'ddd'. */ + for (cp = STR(session->buffer) + 3; *cp == ' '; cp++) /* void */ ; if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) { vstring_strncpy(rdata.dsn_buf, cp, len); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/smtp/smtp_proto.c new/postfix-3.11.7/src/smtp/smtp_proto.c --- old/postfix-3.11.6/src/smtp/smtp_proto.c 2026-06-08 18:06:22.000000000 +0200 +++ new/postfix-3.11.7/src/smtp/smtp_proto.c 2026-09-03 20:43:39.000000000 +0200 @@ -1704,7 +1704,10 @@ /* Caution: changes to RETURN() also affect code outside the main loop. */ + /* 202607 Qualys+Mythos: evaluate argument before freeing resources. */ + #define RETURN(x) do { \ + int _rv = (x); \ if (recv_state != SMTP_STATE_LAST) \ DONT_CACHE_THIS_SESSION; \ vstring_free(next_command); \ @@ -1712,7 +1715,7 @@ myfree((void *) survivors); \ if (session->mime_state) \ session->mime_state = mime_state_free(session->mime_state); \ - return (x); \ + return (_rv); \ } while (0) #define SENDER_IS_AHEAD \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/smtpd/smtpd.c new/postfix-3.11.7/src/smtpd/smtpd.c --- old/postfix-3.11.6/src/smtpd/smtpd.c 2026-08-09 18:55:01.000000000 +0200 +++ new/postfix-3.11.7/src/smtpd/smtpd.c 2026-09-03 21:52:19.000000000 +0200 @@ -4306,8 +4306,8 @@ state->act_size += len + 2; if (*start == '.' && proxy != 0 && state->bdat_prev_rec_type != REC_TYPE_CONT) - if (out_record(out_stream, REC_TYPE_CONT, ".", 1) < 0) - state->err = out_error; + /* 202507 OpenAI: more robust dot-stuffing. */ + vstring_prepend(state->bdat_get_buffer, ".", 1); if (state->err == CLEANUP_STAT_OK && out_record(out_stream, curr_rec_type, vstring_str(state->bdat_get_buffer), @@ -4491,7 +4491,7 @@ /* Fix 20140707: Check the VRFY command. */ if (smtputf8 == 0 && var_strict_smtputf8) { if (*STR(state->addr_buf) && !allascii(STR(state->addr_buf))) { - mail_reset(state); + /* Wonyoung.Jung 20260811: don't reset SMTP state. */ smtpd_chat_reply(state, "553 5.6.7 Must declare SMTPUTF8 to send unicode address"); return (-1); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/smtpd/smtpd_chat.c new/postfix-3.11.7/src/smtpd/smtpd_chat.c --- old/postfix-3.11.6/src/smtpd/smtpd_chat.c 2025-12-22 23:01:57.000000000 +0100 +++ new/postfix-3.11.7/src/smtpd/smtpd_chat.c 2026-09-03 21:12:17.000000000 +0200 @@ -217,6 +217,7 @@ char *end; const char *alt_reply; const char *footer; + ssize_t line_len; /* 202607 OpenAI line length checks */ /* * Slow down clients that make errors. Sleep-on-anything slows down @@ -258,20 +259,26 @@ /* All 5xx replies must have a 5.xx.xx detail code. */ for (cp = STR(state->buffer), end = cp + strlen(STR(state->buffer));;) { + if ((next = strstr(cp, "\r\n")) != 0) + line_len = next - cp; + else + line_len = end - cp; if (var_soft_bounce) { - if (cp[0] == '5') { + if (line_len > 0 && cp[0] == '5') { cp[0] = '4'; - if (cp[4] == '5') + if (line_len > 4 && cp[4] == '5') cp[4] = '4'; } } /* This is why we use strlen() above instead of VSTRING_LEN(). */ - if ((next = strstr(cp, "\r\n")) != 0) { + if (next != 0) { *next = 0; - if (next[2] != 0) - cp[3] = '-'; /* contact footer kludge */ - else + if (next[2] != 0) { + if (line_len > 3) + cp[3] = '-'; /* contact footer kludge */ + } else { next = end; /* strip trailing \r\n */ + } } else { next = end; } @@ -280,7 +287,7 @@ if (msg_verbose) msg_info("> %s: %s", state->namaddr, cp); - smtp_fputs(cp, next - cp, state->client); + smtp_fputs(cp, line_len, state->client); if (next < end) cp = next + 2; else diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/smtpd/smtpd_proxy.c new/postfix-3.11.7/src/smtpd/smtpd_proxy.c --- old/postfix-3.11.6/src/smtpd/smtpd_proxy.c 2025-09-17 22:56:39.000000000 +0200 +++ new/postfix-3.11.7/src/smtpd/smtpd_proxy.c 2026-09-03 21:14:37.000000000 +0200 @@ -883,12 +883,39 @@ } } +/* smtpd_proxy_fix_line_breaks - replace line-break crud with space */ + +static const char *smtpd_proxy_fix_line_breaks(SMTPD_PROXY *proxy, + const char *data, + ssize_t len) +{ + char *cp; + + /* + * We still need to eliminate <CR>. + * + * <LF> is already eliminated by smtp_get_no_except() (used for BDAT), and + * by smtp_get() (used for DATA) which calls smtp_get_no_except(). + */ + if (memchr(data, '\r', len) != 0) { + if (proxy->data_buf == 0) + proxy->data_buf = vstring_alloc(100); + vstring_memcpy(proxy->data_buf, data, len); + for (cp = STR(proxy->data_buf); cp < vstring_end(proxy->data_buf); cp++) + if (*cp == '\r') + *cp = ' '; + data = STR(proxy->data_buf); + } + return (data); +} + /* smtpd_proxy_rec_put - send message content, rec_put() clone */ static int smtpd_proxy_rec_put(VSTREAM *stream, int rec_type, const char *data, ssize_t len) { const char *myname = "smtpd_proxy_rec_put"; + SMTPD_PROXY *proxy = VSTREAM_TO_SMTPD_STATE(stream)->proxy; int err = 0; /* @@ -899,20 +926,31 @@ (void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err); return (REC_TYPE_ERROR); } + if (rec_type != REC_TYPE_CONT && rec_type != REC_TYPE_NORM) + msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname); + + /* 202608 OpenAI: fixed guard against header prepend edge cases. */ + if (proxy->last_text_rec != REC_TYPE_CONT) { + if (proxy->flags & SMTPD_PROXY_FLAG_REQTLS_HDR) + smtpd_proxy_handle_reqtls(proxy, stream, data, len); + /* Wietse: leading '.' may be followed by line-break crud. */ + if (data[0] == '.' && (len == 1 || data[1] != '.')) { + msg_warn("prepending '.' to malformed line: '%.*s'", + len > 10 ? 10 : (int) len, data); + smtp_fwrite(".", 1, stream); + } + } /* * Send one content record. Errors and results must be as with rec_put(). */ + /* 202607 OpenAI: replace line-break crud with space. */ + data = smtpd_proxy_fix_line_breaks(proxy, data, len); if (rec_type == REC_TYPE_NORM) { - SMTPD_PROXY *proxy = VSTREAM_TO_SMTPD_STATE(stream)->proxy; - - if (proxy->flags & SMTPD_PROXY_FLAG_REQTLS_HDR) - smtpd_proxy_handle_reqtls(proxy, stream, data, len); smtp_fputs(data, len, stream); } else if (rec_type == REC_TYPE_CONT) smtp_fwrite(data, len, stream); - else - msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname); + proxy->last_text_rec = rec_type; return (rec_type); } @@ -1053,10 +1091,10 @@ * parameters, and have the compiler enforce the argument count. */ #define SMTPD_PROXY_ALLOC(p, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, \ - a12, a13) \ + a12, a13, a14, a15) \ ((p) = (SMTPD_PROXY *) mymalloc(sizeof(*(p))), (p)->a1, (p)->a2, \ (p)->a3, (p)->a4, (p)->a5, (p)->a6, (p)->a7, (p)->a8, (p)->a9, \ - (p)->a10, (p)->a11, (p)->a12, (p)->a13, (p)) + (p)->a10, (p)->a11, (p)->a12, (p)->a13, (p)->a14, (p)->a15, (p)) /* * Sanity check. @@ -1077,7 +1115,8 @@ flags = flags, service_stream = 0, service_name = service, timeout = timeout, ehlo_name = ehlo_name, mail_from = mail_from, - reqtls_esmtp_hdr_seen = 0); + reqtls_esmtp_hdr_seen = 0, last_text_rec = 0, + data_buf = 0); if (smtpd_proxy_connect(state) < 0) { /* NOT: smtpd_proxy_free(state); we still need proxy->reply. */ return (-1); @@ -1108,7 +1147,8 @@ flags = flags, service_stream = 0, service_name = service, timeout = timeout, ehlo_name = ehlo_name, mail_from = mail_from, - reqtls_esmtp_hdr_seen = 0); + reqtls_esmtp_hdr_seen = 0, last_text_rec = 0, + data_buf = 0); return (0); #endif } @@ -1151,6 +1191,8 @@ vstring_free(proxy->request); if (proxy->reply != 0) vstring_free(proxy->reply); + if (proxy->data_buf != 0) + vstring_free(proxy->data_buf); myfree((void *) proxy); state->proxy = 0; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/smtpd/smtpd_proxy.h new/postfix-3.11.7/src/smtpd/smtpd_proxy.h --- old/postfix-3.11.6/src/smtpd/smtpd_proxy.h 2025-09-17 22:56:39.000000000 +0200 +++ new/postfix-3.11.7/src/smtpd/smtpd_proxy.h 2026-09-03 21:14:37.000000000 +0200 @@ -38,6 +38,8 @@ const char *ehlo_name; const char *mail_from; int reqtls_esmtp_hdr_seen; + int last_text_rec; + VSTRING *data_buf; } SMTPD_PROXY; #define SMTPD_PROXY_FLAG_SPEED_ADJUST (1<<0) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/tls/tls_server.c new/postfix-3.11.7/src/tls/tls_server.c --- old/postfix-3.11.6/src/tls/tls_server.c 2025-07-30 00:14:02.000000000 +0200 +++ new/postfix-3.11.7/src/tls/tls_server.c 2026-09-03 21:11:03.000000000 +0200 @@ -827,6 +827,11 @@ const char *cipher_list; TLS_APPL_STATE *app_ctx = props->ctx; int log_mask = app_ctx->log_mask; + /* 202607 OpenAI: isolate session tickets by master.cf service name. */ + EVP_MD_CTX *sessid_ctx_md = 0; + unsigned char sessid_ctx[EVP_MAX_MD_SIZE]; + unsigned int sessid_ctx_len; + static const char sessid_ctx_label[] = "Postfix/TLS serverid"; /* * Implicitly enable logging of trust chain errors when verified certs @@ -868,6 +873,29 @@ TLScontext->stream = props->stream; TLScontext->mdalg = props->mdalg; + /* + * The serverid already partitions stateful sessions in the tlsmgr + * session cache. Apply the same partition to stateless sessions; all + * services in one Postfix instance share the tlsmgr ticket keys. + */ + if (tls_digest_byname(LN_sha256, &sessid_ctx_md) == 0 + || EVP_DigestUpdate(sessid_ctx_md, sessid_ctx_label, + sizeof(sessid_ctx_label)) != 1 + || EVP_DigestUpdate(sessid_ctx_md, props->serverid, + strlen(props->serverid) + 1) != 1 + || EVP_DigestFinal_ex(sessid_ctx_md, sessid_ctx, &sessid_ctx_len) != 1 + || sessid_ctx_len > SSL_MAX_SID_CTX_LENGTH + || SSL_set_session_id_context(TLScontext->con, sessid_ctx, + sessid_ctx_len) != 1) { + msg_warn("Could not set TLS session ID context"); + tls_print_errors(); + if (sessid_ctx_md) + EVP_MD_CTX_destroy(sessid_ctx_md); + tls_free_context(TLScontext); + return (0); + } + EVP_MD_CTX_destroy(sessid_ctx_md); + if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) { msg_warn("Could not set application data for 'TLScontext->con'"); tls_print_errors(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/util/mymalloc.c new/postfix-3.11.7/src/util/mymalloc.c --- old/postfix-3.11.6/src/util/mymalloc.c 2026-06-28 22:10:45.000000000 +0200 +++ new/postfix-3.11.7/src/util/mymalloc.c 2026-09-03 20:45:36.000000000 +0200 @@ -197,6 +197,10 @@ #ifndef NO_SHARED_EMPTY_STRINGS if (ptr == empty_string) return (mymalloc(len)); + if (len == 0) { + myfree(ptr); + return (mymalloc(0)); + } #endif /* @@ -204,7 +208,7 @@ * allows us to catch integer overflow problems that weren't already * caught up-stream. */ - if (len < 1) + if (len < 0) msg_panic("myrealloc: requested length %ld", (long) len); #ifdef MYMALLOC_FUZZ len += MYMALLOC_FUZZ; @@ -266,7 +270,7 @@ if (len < 0) msg_panic("mystrndup: requested length %ld", (long) len); #ifndef NO_SHARED_EMPTY_STRINGS - if (*str == 0) + if (*str == 0 || /* fix 20220615 */ len == 0) return ((char *) empty_string); #endif if ((cp = memchr(str, 0, len)) != 0) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/virtual/Makefile.in new/postfix-3.11.7/src/virtual/Makefile.in --- old/postfix-3.11.6/src/virtual/Makefile.in 2025-09-30 00:35:01.000000000 +0200 +++ new/postfix-3.11.7/src/virtual/Makefile.in 2026-09-03 22:04:41.000000000 +0200 @@ -96,6 +96,7 @@ mailbox.o: ../../include/pol_stats.h mailbox.o: ../../include/recipient_list.h mailbox.o: ../../include/safe_open.h +mailbox.o: ../../include/sane_strtol.h mailbox.o: ../../include/sent.h mailbox.o: ../../include/set_eugid.h mailbox.o: ../../include/stringops.h diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/postfix-3.11.6/src/virtual/mailbox.c new/postfix-3.11.7/src/virtual/mailbox.c --- old/postfix-3.11.6/src/virtual/mailbox.c 2018-01-06 16:43:44.000000000 +0100 +++ new/postfix-3.11.7/src/virtual/mailbox.c 2026-09-03 21:50:05.000000000 +0200 @@ -57,6 +57,7 @@ #include <mymalloc.h> #include <stringops.h> #include <set_eugid.h> +#include <sane_strtol.h> /* Global library. */ @@ -176,7 +177,8 @@ const char *uid_res; const char *gid_res; DSN_BUF *why = state.msg_attr.why; - long n; + char *end; + unsigned long n; /* * Make verbose logging easier to understand. @@ -215,6 +217,15 @@ #define RETURN(res) { myfree(usr_attr.mailbox); return (res); } + if (strstr(usr_attr.mailbox + strlen(var_virt_mailbox_base), "/../")) { + msg_warn("recipient %s: bad mailbox path %s in %s", + state.msg_attr.user, mailbox_res, virtual_mailbox_maps->title); + dsb_simple(why, "4.3.5", "mail system configuration error"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + RETURN(YES); + } + /* * Look up the mailbox owner rights. Defer in case of trouble. */ @@ -228,7 +239,10 @@ BOUNCE_ATTR(state.msg_attr)); RETURN(YES); } - if ((n = atol(uid_res)) < var_virt_minimum_uid) { + usr_attr.uid = (uid_t) (n = sane_strtoul(uid_res, &end, 10)); + if (*end != 0 || errno != 0 || usr_attr.uid != n + || usr_attr.uid == (uid_t) - 1 /* Special for safe_open()) */ + || usr_attr.uid < var_virt_minimum_uid) { msg_warn("recipient %s: bad uid %s in %s", state.msg_attr.user, uid_res, virtual_uid_maps->title); dsb_simple(why, "4.3.5", "mail system configuration error"); @@ -236,7 +250,6 @@ BOUNCE_ATTR(state.msg_attr)); RETURN(YES); } - usr_attr.uid = (uid_t) n; /* * Look up the mailbox group rights. Defer in case of trouble. @@ -251,7 +264,10 @@ BOUNCE_ATTR(state.msg_attr)); RETURN(YES); } - if ((n = atol(gid_res)) <= 0) { + usr_attr.gid = (gid_t) (n = sane_strtoul(gid_res, &end, 10)); + if (*end != 0 || errno != 0 || usr_attr.gid !=n + || usr_attr.gid == 0 /* Backwards compatibility */ + || usr_attr.gid == (gid_t) - 1) { /* Special for safe_open()) */ msg_warn("recipient %s: bad gid %s in %s", state.msg_attr.user, gid_res, virtual_gid_maps->title); dsb_simple(why, "4.3.5", "mail system configuration error"); @@ -259,12 +275,10 @@ BOUNCE_ATTR(state.msg_attr)); RETURN(YES); } - usr_attr.gid = (gid_t) n; - if (msg_verbose) - msg_info("%s[%d]: set user_attr: %s, uid = %u, gid = %u", + msg_info("%s[%d]: set user_attr: %s, uid = %lu, gid = %lu", myname, state.level, usr_attr.mailbox, - (unsigned) usr_attr.uid, (unsigned) usr_attr.gid); + (unsigned long) usr_attr.uid, (unsigned long) usr_attr.gid); /* * Deliver to mailbox or to maildir. ++++++ postfix-vda-v14-3.0.3.patch ++++++ --- /var/tmp/diff_new_pack.RVJ8R7/_old 2026-09-10 11:47:34.201957410 +0200 +++ /var/tmp/diff_new_pack.RVJ8R7/_new 2026-09-10 11:47:34.207957661 +0200 @@ -19,7 +19,7 @@ =================================================================== --- src/global/mail_params.h.orig +++ src/global/mail_params.h -@@ -2742,6 +2742,54 @@ extern char *var_virt_uid_maps; +@@ -2620,6 +2620,54 @@ extern char *var_virt_uid_maps; #define DEF_VIRT_GID_MAPS "" extern char *var_virt_gid_maps; @@ -95,15 +95,15 @@ =================================================================== --- src/virtual/mailbox.c.orig +++ src/virtual/mailbox.c -@@ -57,6 +57,7 @@ - #include <mymalloc.h> +@@ -58,6 +58,7 @@ #include <stringops.h> #include <set_eugid.h> + #include <sane_strtol.h> +#include <iostuff.h> /* Global library. */ -@@ -75,6 +76,70 @@ +@@ -76,6 +77,70 @@ #define YES 1 #define NO 0 @@ -174,7 +174,7 @@ /* deliver_mailbox_file - deliver to recipient mailbox */ static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) -@@ -219,62 +284,72 @@ int deliver_mailbox(LOCAL_STATE stat +@@ -230,51 +295,52 @@ int deliver_mailbox(LOCAL_STATE stat * Look up the mailbox owner rights. Defer in case of trouble. */ uid_res = mail_addr_find(virtual_uid_maps, state.msg_attr.user, @@ -197,7 +197,10 @@ + } } + - if ((n = atol(uid_res)) < var_virt_minimum_uid) { + usr_attr.uid = (uid_t) (n = sane_strtoul(uid_res, &end, 10)); + if (*end != 0 || errno != 0 || usr_attr.uid != n + || usr_attr.uid == (uid_t) - 1 /* Special for safe_open()) */ + || usr_attr.uid < var_virt_minimum_uid) { - msg_warn("recipient %s: bad uid %s in %s", - state.msg_attr.user, uid_res, virtual_uid_maps->title); - dsb_simple(why, "4.3.5", "mail system configuration error"); @@ -209,8 +212,6 @@ + *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); + RETURN(YES); } -+ - usr_attr.uid = (uid_t) n; /* * Look up the mailbox group rights. Defer in case of trouble. @@ -235,7 +236,10 @@ + } } + - if ((n = atol(gid_res)) <= 0) { + usr_attr.gid = (gid_t) (n = sane_strtoul(gid_res, &end, 10)); + if (*end != 0 || errno != 0 || usr_attr.gid !=n + || usr_attr.gid == 0 /* Backwards compatibility */ + || usr_attr.gid == (gid_t) - 1) { /* Special for safe_open()) */ - msg_warn("recipient %s: bad gid %s in %s", - state.msg_attr.user, gid_res, virtual_gid_maps->title); - dsb_simple(why, "4.3.5", "mail system configuration error"); @@ -248,18 +252,10 @@ + RETURN(YES); } + - usr_attr.gid = (gid_t) n; - if (msg_verbose) -- msg_info("%s[%d]: set user_attr: %s, uid = %u, gid = %u", -- myname, state.level, usr_attr.mailbox, -- (unsigned) usr_attr.uid, (unsigned) usr_attr.gid); -+ msg_info("%s[%d]: set user_attr: %s, uid = %u, gid = %u", -+ myname, state.level, usr_attr.mailbox, -+ (unsigned) usr_attr.uid, (unsigned) usr_attr.gid); - - /* - * Deliver to mailbox or to maildir. + msg_info("%s[%d]: set user_attr: %s, uid = %lu, gid = %lu", + myname, state.level, usr_attr.mailbox, +@@ -285,10 +349,18 @@ int deliver_mailbox(LOCAL_STATE stat */ #define LAST_CHAR(s) (s[strlen(s) - 1])
