Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package exim for openSUSE:Factory checked in at 2025-03-27 22:34:18 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/exim (Old) and /work/SRC/openSUSE:Factory/.exim.new.2696 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "exim" Thu Mar 27 22:34:18 2025 rev:85 rq:1262987 version:4.98.2 Changes: -------- --- /work/SRC/openSUSE:Factory/exim/exim.changes 2025-03-19 22:32:50.303889624 +0100 +++ /work/SRC/openSUSE:Factory/.exim.new.2696/exim.changes 2025-03-27 22:34:57.411201480 +0100 @@ -1,0 +2,9 @@ +Thu Mar 27 14:41:49 UTC 2025 - Alexander Bergmann <[email protected]> + +- security release 4.98.2 for CVE-2025-30232 (bsc#1239794) + * Fixed use-after-free with potential for privilege escalation. +- security release 4.98.1 for CVE-2025-26794 (bsc#1237424) + * Fixed remote SQL injection when SQLite hints and ETRN serialization + are used. + +------------------------------------------------------------------- Old: ---- exim-4.98.tar.bz2 exim-4.98.tar.bz2.asc New: ---- exim-4.98.2.tar.bz2 exim-4.98.2.tar.bz2.asc ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ exim.spec ++++++ --- /var/tmp/diff_new_pack.Gm8OxG/_old 2025-03-27 22:34:58.199234099 +0100 +++ /var/tmp/diff_new_pack.Gm8OxG/_new 2025-03-27 22:34:58.203234265 +0100 @@ -31,7 +31,7 @@ %bcond_with dane %endif Name: exim -Version: 4.98 +Version: 4.98.2 Release: 0 Summary: The Exim Mail Transfer Agent, a Replacement for sendmail License: GPL-2.0-or-later ++++++ exim-4.98.tar.bz2 -> exim-4.98.2.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exim-4.98/doc/ChangeLog new/exim-4.98.2/doc/ChangeLog --- old/exim-4.98/doc/ChangeLog 2024-07-10 16:45:44.000000000 +0200 +++ new/exim-4.98.2/doc/ChangeLog 2025-03-21 14:45:58.000000000 +0100 @@ -2,13 +2,32 @@ affect Exim's operation, with an unchanged configuration file. For new options, and new features, see the NewStuff file next to this ChangeLog. +Exim version 4.98.2 +------------------- + +This is a security release, addressing CVE-2025-30232 + +JH/01 Fix use-after-free notified by Trend Micro (ref: ZDI-CAN-26250). + Null out debug_pretrigger_buf pointer before freeing the buffer; + the use of this buffer by the storage management checks the pointer + for non-null before using it. + +Exim version 4.98.1 +------------------- + +This is a security release, addressing CVE-2025-26794 + +JH/01 Serialization of SMTP commands is based on the MD5 sum of + the command's argument. + +HS/01 Prevent SQL injection for the hints database. Exim version 4.98 ----------------- JH/01 Support list of dkim results in the dkim_status ACL condition, making it more usable in the data ACL. -JH/02 Bug 3040: Handle error on close of the spool data file during reception. +JH/02 Bug 3040: Handle error on close of the spool data file during reception. Previously This was only logged, on the assumption that errors would be seen for a previous fflush(). However, a fuse filesystem has been reported as showing this an error for the fclose(). The spool is now in diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exim-4.98/doc/exim.8 new/exim-4.98.2/doc/exim.8 --- old/exim-4.98/doc/exim.8 2024-07-10 16:50:10.000000000 +0200 +++ new/exim-4.98.2/doc/exim.8 2025-03-25 11:13:50.000000000 +0100 @@ -309,7 +309,7 @@ Features such as authentication and encryption, where the client input is not plain text, cannot easily be tested with \fB\-bh\fP. Instead, you should use a specialized SMTP test program such as -\fBswaks\fP. +swaks [\fBhttps://www.jetmore.org/john/code/swaks/\fP]. .TP 10 \fB\-bhc\fP <\fIIP address\fP> This option operates in the same way as \fB\-bh\fP, except that address diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exim-4.98/doc/filter.txt new/exim-4.98.2/doc/filter.txt --- old/exim-4.98/doc/filter.txt 2024-07-10 16:50:10.000000000 +0200 +++ new/exim-4.98.2/doc/filter.txt 2025-03-25 11:13:50.000000000 +0100 @@ -4,7 +4,7 @@ Copyright (c) 2023 The Exim Maintainers -Revision 4.98 10 Jul 2024 PH +Revision 4.98.2 25 Mar 2025 PH ------------------------------------------------------------------------------- @@ -72,7 +72,7 @@ This document describes the user interfaces to Exim's in-built mail filtering facilities, and is copyright (c) The Exim Maintainers 2023. It corresponds to -Exim version 4.98. +Exim version 4.98.2. 1.1 Introduction diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exim-4.98/doc/spec.txt new/exim-4.98.2/doc/spec.txt --- old/exim-4.98/doc/spec.txt 2024-07-10 16:50:09.000000000 +0200 +++ new/exim-4.98.2/doc/spec.txt 2025-03-25 11:13:49.000000000 +0100 @@ -4,7 +4,7 @@ Copyright (c) 2024 The Exim Maintainers -Revision 4.98 10 Jul 2024 EM +Revision 4.98.2 25 Mar 2025 EM ------------------------------------------------------------------------------- @@ -635,7 +635,7 @@ 1.1 Exim documentation ---------------------- -This edition of the Exim specification applies to version 4.98 of Exim. +This edition of the Exim specification applies to version 4.98.2 of Exim. Substantive changes from the 4.97 edition are marked in some renditions of this document; this paragraph is so marked if the rendition is capable of showing a change indicator. @@ -1760,7 +1760,7 @@ Exim is distributed as a gzipped or bzipped tar file which, when unpacked, creates a directory with the name of the current release (for example, -exim-4.98) into which the following files are placed: +exim-4.98.2) into which the following files are placed: ACKNOWLEDGMENTS contains some acknowledgments CHANGES contains a reference to where changes are documented @@ -2381,7 +2381,7 @@ For the utility programs, old versions are renamed by adding the suffix .O to their names. The Exim binary itself, however, is handled differently. It is installed under a name that includes the version number and the compile number, -for example, exim-4.98-1. The script then arranges for a symbolic link called +for example, exim-4.98.2-1. The script then arranges for a symbolic link called exim to point to the binary. If you are updating a previous version of Exim, the script takes care to ensure that the name exim is never absent from the directory (as seen by other processes). @@ -2945,7 +2945,8 @@ Features such as authentication and encryption, where the client input is not plain text, cannot easily be tested with -bh. Instead, you should use a - specialized SMTP test program such as swaks. + specialized SMTP test program such as swaks [https://www.jetmore.org/john/ + code/swaks/]. -bhc <IP address> @@ -16461,6 +16462,19 @@ SMTP, so it is not possible for it to change the uid before running the command. +Serialization of ETRN commands is based on the MD5 hash of the command's +argument. No more than one ETRN command with the same hash of its arguments can +run at a given time. Other ETRN commands get a 458 SMTP rejection. + +To protect against attacks flooding the ETRN serialization table, you should +consider rate limiting the ETRN command. + +deny + ratelimit = 3 / 1m / per_cmd / $sender_host_address + message = rate for ETRN is over the limit ($sender_rate > $sender_rate_limit) + +accept + +---------------------------------------------------------+ |smtp_etrn_serialize|Use: main|Type: boolean|Default: true| +---------------------------------------------------------+ @@ -28161,7 +28175,8 @@ A TLSA record consist of 4 fields, the "Certificate Usage", the "Selector", the "Matching type", and the "Certificate Association Data". For a detailed -description of the TLSA record see RFC 7671. +description of the TLSA record see RFC 7671 [https://tools.ietf.org/html/ +rfc7671#page-5]. The TLSA record for the server may have "Certificate Usage" (1st) field of DANE-TA(2) or DANE-EE(3). These are the "Trust Anchor" and "End Entity" @@ -28199,7 +28214,7 @@ random serial numbers. The list of requirements is subject to change as best practices evolve. If you're not already using a private CA, or it doesn't meet these requirements, then we encourage you to avoid all these issues and use a -public CA such as Let's Encrypt instead. +public CA such as Let's Encrypt [https://letsencrypt.org/] instead. The TLSA record should have a "Selector" (2nd) field of SPKI(1) and a "Matching Type" (3rd) field of SHA2-512(2). @@ -28327,12 +28342,13 @@ which is recognized by clients sending to you. That selection of which CAs are trusted by others is outside your control. -The most interoperable course of action is probably to use Let's Encrypt, with -automated certificate renewal; to publish the anchor information in -DNSSEC-secured DNS via TLSA records for DANE clients (such as Exim and Postfix) -and to publish anchor information for MTA-STS as well. This is what is done for -the exim.org domain itself (with caveats around occasionally broken MTA-STS -because of incompatible specification changes prior to reaching RFC status). +The most interoperable course of action is probably to use Let's Encrypt [ +https://letsencrypt.org/], with automated certificate renewal; to publish the +anchor information in DNSSEC-secured DNS via TLSA records for DANE clients +(such as Exim and Postfix) and to publish anchor information for MTA-STS as +well. This is what is done for the exim.org domain itself (with caveats around +occasionally broken MTA-STS because of incompatible specification changes prior +to reaching RFC status). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exim-4.98/src/debug.c new/exim-4.98.2/src/debug.c --- old/exim-4.98/src/debug.c 2024-07-10 16:45:44.000000000 +0200 +++ new/exim-4.98.2/src/debug.c 2025-03-21 14:45:58.000000000 +0100 @@ -453,6 +453,18 @@ What ends up in the buffer is subject to the usual debug_selector. */ void +debug_pretrigger_discard(void) +{ +dtrigger_selector = 0; +if (debug_pretrigger_buf) + { + uschar * buf = debug_pretrigger_buf; + debug_pretrigger_buf = NULL; + store_free(buf); + } +} + +void debug_pretrigger_setup(const uschar * size_string) { long size = Ustrtol(size_string, NULL, 0); @@ -460,10 +472,10 @@ { unsigned bufsize = MIN(size, 16384); - dtrigger_selector |= BIT(DTi_pretrigger); - if (debug_pretrigger_buf) store_free(debug_pretrigger_buf); - debug_pretrigger_buf = store_malloc((size_t)(debug_pretrigger_bsize = bufsize)); + debug_pretrigger_discard(); pretrigger_readoff = pretrigger_writeoff = 0; + debug_pretrigger_buf = store_malloc((size_t)(debug_pretrigger_bsize = bufsize)); + dtrigger_selector |= BIT(DTi_pretrigger); } } @@ -487,13 +499,5 @@ debug_pretrigger_discard(); } -void -debug_pretrigger_discard(void) -{ -if (debug_pretrigger_buf) store_free(debug_pretrigger_buf); -debug_pretrigger_buf = NULL; -dtrigger_selector = 0; -} - /* End of debug.c */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exim-4.98/src/hintsdb.h new/exim-4.98.2/src/hintsdb.h --- old/exim-4.98/src/hintsdb.h 2024-07-10 16:45:44.000000000 +0200 +++ new/exim-4.98.2/src/hintsdb.h 2025-03-21 14:45:58.000000000 +0100 @@ -66,6 +66,15 @@ #ifndef HINTSDB_H #define HINTSDB_H +# if COMPILE_UTILITY +# undef DEBUG +# define DEBUG(...) if (getenv("DEBUG")) +# define debug_printf_indent(x, y...) fprintf(stderr, "# " x, y) +# define debug_printf(x, y...) fprintf(stderr, "# " x, y) +# else +extern void debug_printf_indent(const char *, ...); +# endif +static inline BOOL is_tainted(const void *); #ifdef USE_SQLITE # if defined(USE_DB) || defined(USE_GDBM) || defined(USE_TDB) @@ -89,6 +98,27 @@ # /* Access functions */ +/* The key must be zero terminated, an empty key has len == 1. */ +static inline BOOL +is_cstring(EXIM_DATUM *key) +{ +if (key->len < 1) + { +# ifdef SQL_DEBUG + fprintf(stderr, "invalid key length %d (must be >= 1)\n", key->len); +# endif + return FALSE; + } +if (key->data[key->len-1] != '\0') + { +# ifdef SQL_DEBUG + fprintf(stderr, "key %.*s is not zero terminated\n", key->len, key->data); +# endif + return FALSE; + } +return TRUE; +} + static inline BOOL exim_lockfile_needed(void) { @@ -119,68 +149,75 @@ return ret == SQLITE_OK ? dbp : NULL; } -/* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */ -/* note we alloc'n'copy - the caller need not do so */ -/* result has a NUL appended, but the length is as per the DB */ - static inline BOOL -exim_dbget__(EXIM_DB * dbp, const uschar * s, EXIM_DATUM * res) +exim_dbget__(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res) { -sqlite3_stmt * statement; -int ret; +int ret = FALSE; +sqlite3_stmt * stmt = NULL; /* don't make it static, as it depends on the dbp */ +const char query[] = "SELECT dat FROM tbl WHERE ky = ?"; -res->len = (size_t) -1; -/* fprintf(stderr, "exim_dbget__(%s)\n", s); */ -if ((ret = sqlite3_prepare_v2(dbp, CCS s, -1, &statement, NULL)) != SQLITE_OK) +if (SQLITE_OK != sqlite3_prepare_v2(dbp, query, sizeof(query)-1, &stmt, NULL)) { -/* fprintf(stderr, "prepare fail: %s\n", sqlite3_errmsg(dbp)); */ - return FALSE; +# ifdef SQL_DEBUG + fprintf(stderr, EXIM_DBTYPE " prepare %s: %s\n", query, sqlite3_errmsg(dbp)); +# endif + goto DONE; } -if (sqlite3_step(statement) != SQLITE_ROW) + +# ifdef SQL_DEBUG +DEBUG(D_hints_lookup) debug_printf_indent("prepared SQL: %s\n", sqlite3_sql(stmt)); +# endif + +if (SQLITE_OK != sqlite3_bind_text(stmt, 1, CCS key->data, key->len-1, SQLITE_STATIC)) { -/* fprintf(stderr, "step fail: %s\n", sqlite3_errmsg(dbp)); */ - sqlite3_finalize(statement); - return FALSE; +# ifdef SQL_DEBUG + fprintf(stderr, EXIM_DBTYPE " bind text (%s): %s\n", sqlite3_sql(stmt), sqlite3_errmsg(dbp)); +# endif + goto DONE; } -res->len = sqlite3_column_bytes(statement, 0); +# ifdef SQL_DEBUG +DEBUG(D_hints_lookup) debug_printf_indent("expanded SQL: %s\n", sqlite3_expanded_sql(stmt)); +# endif + +if (SQLITE_ROW != sqlite3_step(stmt)) + { +# ifdef SQL_DEBUG + DEBUG(D_hints_lookup) debug_printf_indent("step (%s): %s\n", sqlite3_expanded_sql(stmt), sqlite3_errmsg(dbp)); +# endif + goto DONE; + } + +res->len = sqlite3_column_bytes(stmt, 0); + # ifdef COMPILE_UTILITY -if (!(res->data = malloc(res->len +1))) - { sqlite3_finalize(statement); return FALSE; } +if (!(res->data = malloc(res->len +1))) goto DONE; # else res->data = store_get(res->len +1, GET_TAINTED); # endif -memcpy(res->data, sqlite3_column_blob(statement, 0), res->len); + +memcpy(res->data, sqlite3_column_blob(stmt, 0), res->len); res->data[res->len] = '\0'; /* fprintf(stderr, "res %d bytes: '%.*s'\n", (int)res->len, (int)res->len, res->data); */ -sqlite3_finalize(statement); -return TRUE; + +ret = TRUE; + +DONE: +sqlite3_finalize(stmt); + +return ret; } +/* EXIM_DBGET - returns the value associated with the key. The key must +be zero terminated, an empty key has len == 1. */ static inline BOOL exim_dbget(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res) { -# define FMT "SELECT dat FROM tbl WHERE ky = '%.*s';" -uschar * qry; -int i; -BOOL ret; - -# ifdef COMPILE_UTILITY -/* fprintf(stderr, "exim_dbget(k len %d '%.*s')\n", (int)key->len, (int)key->len, key->data); */ -i = snprintf(NULL, 0, FMT, (int) key->len, key->data)+1; -if (!(qry = malloc(i))) - return FALSE; -snprintf(CS qry, i, FMT, (int) key->len, key->data); -ret = exim_dbget__(dbp, qry, res); -free(qry); -# else -/* fprintf(stderr, "exim_dbget(k len %d '%.*s')\n", (int)key->len, (int)key->len, key->data); */ -qry = string_sprintf(FMT, (int) key->len, key->data); -ret = exim_dbget__(dbp, qry, res); +# ifdef SQL_DEBUG +DEBUG(D_hints_lookup) debug_printf_indent(EXIM_DBTYPE " get key: len=%d, strlen=%d, key=%.*s\n", key->len, Ustrlen(key->data), key->len, key->data); # endif - -return ret; -# undef FMT +if (!is_cstring(key)) return FALSE; +return exim_dbget__(dbp, key, res); } /**/ @@ -190,46 +227,83 @@ static inline int exim_s_dbp(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data, const uschar * alt) { -int hlen = data->len * 2, off = 0, res; -# define FMT "INSERT OR %s INTO tbl (ky,dat) VALUES ('%.*s', X'%.*s');" -uschar * qry; +const char sql[] = "INSERT OR %s INTO tbl (ky, dat) VALUES(?, ?)"; +int ret = EXIM_DBPUTB_DUP; +sqlite3_stmt *stmt = NULL; +uschar * query; + # ifdef COMPILE_UTILITY -uschar * hex = malloc(hlen+1); -if (!hex) return EXIM_DBPUTB_DUP; /* best we can do */ +int i = 1 + snprintf(NULL, 0, sql, alt); +if (NULL == (query = US malloc(i))) + { + fprintf(stderr, "can't allocate memory for %s", sql); + return EXIM_DBPUTB_DUP; + } +snprintf(CS query, i, sql, alt); # else -uschar * hex = store_get(hlen+1, data->data); +query = string_sprintf(sql, alt); # endif -for (const uschar * s = data->data, * t = s + data->len; s < t; s++, off += 2) - sprintf(CS hex + off, "%02X", *s); +if (SQLITE_OK != sqlite3_prepare_v2(dbp, CCS query, -1, &stmt, NULL)) + { +# ifdef SQL_DEBUG + fprintf(stderr, EXIM_DBTYPE " prepare %s: %s\n", query, sqlite3_errmsg(dbp)); +# endif + goto DONE; + } -# ifdef COMPILE_UTILITY -res = snprintf(CS hex, 0, FMT, alt, (int) key->len, key->data, hlen, hex) +1; -if (!(qry = malloc(res))) return EXIM_DBPUTB_DUP; -snprintf(CS qry, res, FMT, alt, (int) key->len, key->data, hlen, hex); -/* fprintf(stderr, "exim_s_dbp(%s)\n", qry); */ -res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL); -free(qry); -free(hex); -# else -qry = string_sprintf(FMT, alt, (int) key->len, key->data, hlen, hex); -/* fprintf(stderr, "exim_s_dbp(%s)\n", qry); */ -res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL); -/* fprintf(stderr, "exim_s_dbp res %d\n", res); */ +# ifdef SQL_DEBUG +DEBUG(D_hints_lookup) debug_printf_indent("prepared SQL: %s\n", sqlite3_sql(stmt)); # endif -if (res != SQLITE_OK) - fprintf(stderr, "sqlite3_exec: %s\n", sqlite3_errmsg(dbp)); +if (SQLITE_OK != sqlite3_bind_text(stmt, 1, CCS key->data, key->len-1, NULL)) + { +# ifdef SQL_DEBUG + fprintf(stderr, EXIM_DBTYPE " bind to value 1: %s\n", sqlite3_errmsg(dbp)); +# endif + goto DONE; + } -return res == SQLITE_OK ? EXIM_DBPUTB_OK : EXIM_DBPUTB_DUP; -# undef FMT -} +if (SQLITE_OK != sqlite3_bind_blob(stmt, 2, data->data, data->len, NULL)) + { +# ifdef SQL_DEBUG + fprintf(stderr, EXIM_DBTYPE " bind to value 2: %s\n", sqlite3_errmsg(dbp)); +# endif + goto DONE; + } -/* EXIM_DBPUT - returns nothing useful, assumes replace mode */ +# ifdef SQL_DEBUG +DEBUG(D_hints_lookup) debug_printf_indent("expanded SQL: %s\n", sqlite3_expanded_sql(stmt)); +# endif + +if (SQLITE_DONE != sqlite3_step(stmt)) + { +# ifdef SQL_DEBUG + fprintf(stderr, EXIM_DBTYPE " step (%s): %s\n", sqlite3_expanded_sql(stmt), sqlite3_errmsg(dbp)); +# endif + goto DONE; + } + +ret = EXIM_DBPUTB_OK; +DONE: +sqlite3_finalize(stmt); +# ifdef COMPILE_UTILITY +free(query); +# endif + +return ret; +} + +/* EXIM_DBPUT - returns nothing useful, assumes replace mode +The key must be zero terminated. An empty key has len == 1. */ static inline int exim_dbput(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data) { +# ifdef SQL_DEBUG +DEBUG(D_hints_lookup) debug_printf_indent(EXIM_DBTYPE " put: key: len=%d, strlen=%d, key=%.*s\n", key->len, Ustrlen(key->data), key->len, key->data); +# endif +if (!is_cstring(key)) return -1; /* fprintf(stderr, "exim_dbput()\n"); */ (void) exim_s_dbp(dbp, key, data, US"REPLACE"); return 0; @@ -249,26 +323,52 @@ static inline int exim_dbdel(EXIM_DB * dbp, EXIM_DATUM * key) { -# define FMT "DELETE FROM tbl WHERE ky = '%.*s';" -uschar * qry; -int res; +int res = -1; +sqlite3_stmt *stmt = NULL; /* don't make it static, because it depends on the dbp */ +const char query[] = "DELETE FROM tbl WHERE ky = ?"; -# ifdef COMPILE_UTILITY -res = snprintf(NULL, 0, FMT, (int) key->len, key->data) +1; /* res includes nul */ -if (!(qry = malloc(res))) return SQLITE_NOMEM; -snprintf(CS qry, res, FMT, (int) key->len, key->data); -res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL); -free(qry); -# else -qry = string_sprintf(FMT, (int) key->len, key->data); -res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL); +DEBUG(D_hints_lookup) debug_printf_indent(EXIM_DBTYPE " del key: len=%d, strlen=%d, key=%.*s\n", key->len, Ustrlen(key->data), key->len, key->data); +if (!is_cstring(key)) return -1; + +if (SQLITE_OK != sqlite3_prepare_v2(dbp, query, sizeof(query)-1, &stmt, NULL)) + { +# ifdef SQL_DEBUG + fprintf(stderr, EXIM_DBTYPE " prepare %s: %s\n", query, sqlite3_errmsg(dbp)); # endif + goto DONE; + } + +# ifdef SQL_DEBUG +DEBUG(D_hints_lookup) debug_printf_indent("query: %s\n", sqlite3_sql(stmt)); +# endif + +if (SQLITE_OK != sqlite3_bind_text(stmt, 1, CCS key->data, key->len-1, SQLITE_STATIC)) + { +# ifdef SQL_DEBUG + fprintf(stderr, EXIM_DBTYPE " bind value 1: %s\n", sqlite3_errmsg(dbp)); +# endif + goto DONE; + } + +# ifdef SQL_DEBUG +DEBUG(D_hints_lookup) debug_printf_indent("expanded query: %s\n", sqlite3_expanded_sql(stmt)); +# endif + +if (SQLITE_DONE != sqlite3_step(stmt)) + { +# ifdef SQL_DEBUG + fprintf(stderr, EXIM_DBTYPE " step: %s: %s\n", sqlite3_expanded_sql(stmt), sqlite3_errmsg(dbp)); +# endif + goto DONE; + } +res = 0; + +DONE: +sqlite3_finalize(stmt); return res; -# undef FMT } - /* EXIM_DBCREATE_CURSOR - initialize for scanning operation */ /* Cursors are inefficiently emulated by repeating searches */ @@ -286,33 +386,66 @@ } /* EXIM_DBSCAN */ -/* Note that we return the (next) key, not the record value */ +/* Note that we return the (next) key, not the record value. + * We've to add the zero terminator, as this isn't stored in the database */ static inline BOOL -exim_dbscan(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res, BOOL first, - EXIM_CURSOR * cursor) +exim_dbscan(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res /* unusied */, BOOL first /*unused*/, EXIM_CURSOR * cursor) { -# define FMT "SELECT ky FROM tbl ORDER BY ky LIMIT 1 OFFSET %d;" -uschar * qry; -int i; -BOOL ret; +BOOL more = FALSE; +sqlite3_stmt *stmt = NULL; +const char query[] = "SELECT ky FROM tbl ORDER BY ky LIMIT 1 OFFSET ?"; -# ifdef COMPILE_UTILITY -i = snprintf(NULL, 0, FMT, *cursor)+1; -if (!(qry = malloc(i))) return FALSE; -snprintf(CS qry, i, FMT, *cursor); -/* fprintf(stderr, "exim_dbscan(%s)\n", qry); */ -ret = exim_dbget__(dbp, qry, key); -free(qry); -/* fprintf(stderr, "exim_dbscan ret %c\n", ret ? 'T':'F'); */ -# else -qry = string_sprintf(FMT, *cursor); -/* fprintf(stderr, "exim_dbscan(%s)\n", qry); */ -ret = exim_dbget__(dbp, qry, key); -/* fprintf(stderr, "exim_dbscan ret %c\n", ret ? 'T':'F'); */ +if (SQLITE_OK != sqlite3_prepare_v2(dbp, query, sizeof(query)-1, &stmt, NULL)) + { +# ifdef SQL_DEBUG + fprintf(stderr, EXIM_DBTYPE " prepare %s: %s\n", query, sqlite3_errmsg(dbp)); # endif -if (ret) *cursor = *cursor + 1; -return ret; -# undef FMT + goto DONE; + } + +# ifdef SQL_DEBUG +DEBUG(D_hints_lookup) debug_printf_indent("prepared query: %s\n", sqlite3_sql(stmt)); +# endif + +if (SQLITE_OK != sqlite3_bind_int(stmt, 1, *cursor)) + { +# ifdef SQL_DEBUG + fprintf(stderr, EXIM_DBTYPE " bind value 1: %s\n", query, sqlite3_errmsg(dbp)); +# endif + goto DONE; + } + +# ifdef SQL_DEBUG +DEBUG(D_hints_lookup) debug_printf_indent("expanded query: %s\n", sqlite3_expanded_sql(stmt)); +# endif + +switch (sqlite3_step(stmt)) + { + case SQLITE_DONE: goto DONE; + case SQLITE_ROW: (*cursor)++; + key->len = sqlite3_column_bytes(stmt, 0); +#ifdef COMPILE_UTILITY + if (!(key->data = malloc(key->len+1))) goto DONE; +#else + key->data = store_get(key->len+1, GET_TAINTED); // TAINTED? We're talking about the key! +#endif + memcpy(key->data, sqlite3_column_blob(stmt, 0), key->len); + key->data[key->len] = '\0'; +# ifdef SQL_DEBUG + DEBUG(D_hints_lookup) debug_printf_indent("key length=%d, val=%s\n", key->len, key->data); +# endif + more = TRUE; + goto DONE; + default: +# ifdef SQL_DEBUG + fprintf(stderr, EXIM_DBTYPE " step: %s: %s\n", sqlite3_expanded_sql(stmt), sqlite3_errmsg(dbp)); +# endif + goto DONE; + } + +DONE: +sqlite3_finalize(stmt); +return more; } /* EXIM_DBDELETE_CURSOR - terminate scanning operation. */ @@ -326,7 +459,6 @@ # endif } - /* EXIM_DBCLOSE */ static void exim_dbclose__(EXIM_DB * dbp) @@ -335,16 +467,16 @@ sqlite3_close(dbp); } - /* Datum access */ static uschar * exim_datum_data_get(EXIM_DATUM * dp) { return US dp->data; } + static void exim_datum_data_set(EXIM_DATUM * dp, void * s) { dp->data = s; } - + static unsigned exim_datum_size_get(EXIM_DATUM * dp) { return dp->len; } @@ -352,8 +484,6 @@ exim_datum_size_set(EXIM_DATUM * dp, unsigned n) { dp->len = n; } - - static inline void exim_datum_init(EXIM_DATUM * dp) { dp->data = NULL; } /* compiler quietening */ @@ -368,11 +498,6 @@ # define EXIM_DB_RLIMIT 150 - - - - - #elif defined(USE_TDB) # if defined(USE_DB) || defined(USE_GDBM) || defined(USE_SQLITE) @@ -513,11 +638,6 @@ # define EXIM_DB_RLIMIT 150 - - - - - /********************* Berkeley db native definitions **********************/ #elif defined USE_DB @@ -574,13 +694,11 @@ static inline void dbfn_bdb_error_callback(const DB_ENV * dbenv, const char * pfx, const char * msg) { -#ifndef MACRO_PREDEF +#ifndef MACRO_PREDEF log_write(0, LOG_MAIN, "Berkeley DB error: %s", msg); #endif } - - /* Access functions (BDB 4.1+) */ static inline BOOL @@ -841,7 +959,6 @@ # endif - # else /* DB_VERSION_MAJOR >= 3 */ # error Berkeley DB versions earlier than 3 are not supported */ # endif /* DB_VERSION_MAJOR */ @@ -849,17 +966,11 @@ # error Berkeley DB version 1 is no longer supported # endif /* DB_VERSION_STRING */ - /* all BDB versions */ /* size limit */ # define EXIM_DB_RLIMIT 150 - - - - - /********************* gdbm interface definitions **********************/ #elif defined USE_GDBM @@ -1007,15 +1118,9 @@ #else /* USE_GDBM */ - - - - - /* If none of USE_DB, USG_GDBM, USE_SQLITE or USE_TDB are set, the default is the NDBM interface (which seems to be a wrapper for GDBM) */ - /********************* ndbm interface definitions **********************/ # include <ndbm.h> @@ -1145,10 +1250,6 @@ #endif /* !USE_GDBM */ - - - - #if defined(COMPILE_UTILITY) || defined(MACRO_PREDEF) static inline EXIM_DB * @@ -1166,9 +1267,6 @@ /* Wrappers for open/close with debug tracing */ -extern void debug_printf_indent(const char *, ...); -static inline BOOL is_tainted(const void *); - static inline EXIM_DB * exim_dbopen(const uschar * name, const uschar * dirname, int flags, unsigned mode) @@ -1204,7 +1302,11 @@ /********************* End of dbm library definitions **********************/ - +# if COMPILE_UTILITY +# undef debug_printf_indent +# undef debug_printf +# undef DEBUG +# endif #endif /* whole file */ /* End of hintsdb.h */ /* vi: aw ai sw=2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exim-4.98/src/smtp_in.c new/exim-4.98.2/src/smtp_in.c --- old/exim-4.98/src/smtp_in.c 2024-07-10 16:45:44.000000000 +0200 +++ new/exim-4.98.2/src/smtp_in.c 2025-03-21 14:45:58.000000000 +0100 @@ -5576,14 +5576,27 @@ break; } - /* Compute the serialization key for this command. */ + /* Compute the serialization key for this command. We used (all the way + back to 4.00) to include the given string as part of the key, but this + opens a security hole for hintsdb types that use a command-string for + operations. All ETRN with the same command hash are serialized */ - etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_cmd_data); + md5 hash; + uschar *digest = store_get(16, GET_TAINTED); + + md5_start(&hash); + md5_end(&hash, smtp_cmd_argument, Ustrlen(smtp_cmd_argument), digest); + + etrn_serialize_key = string_sprintf("etrn-" /* don't we have a function doing exactly this? */ + "%02x%02x%02x%02x" "%02x%02x%02x%02x" /* we have, since 2024-09-xx we can use %.16H */ + "%02x%02x%02x%02x" "%02x%02x%02x%02x", + digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7], + digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]); /* If a command has been specified for running as a result of ETRN, we - permit any argument to ETRN. If not, only the # standard form is permitted, - since that is strictly the only kind of ETRN that can be implemented - according to the RFC. */ + permit any argument to ETRN. If not, only the # standard form is + permitted, since that is strictly the only kind of ETRN that can be + implemented according to the RFC. */ GET_OPTION("smtp_etrn_command"); if (smtp_etrn_command) @@ -5592,8 +5605,8 @@ BOOL rc; etrn_command = smtp_etrn_command; deliver_domain = smtp_cmd_data; - rc = transport_set_up_command(&argv, smtp_etrn_command, TSUC_EXPAND_ARGS, 0, NULL, - US"ETRN processing", &error); + rc = transport_set_up_command(&argv, smtp_etrn_command, + TSUC_EXPAND_ARGS, 0, NULL, US"ETRN processing", &error); deliver_domain = NULL; if (!rc) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exim-4.98/src/version.h new/exim-4.98.2/src/version.h --- old/exim-4.98/src/version.h 2024-07-10 16:49:39.000000000 +0200 +++ new/exim-4.98.2/src/version.h 2025-03-25 11:13:22.000000000 +0100 @@ -1,5 +1,5 @@ /* automatically generated file - see ../scripts/reversion */ -#define EXIM_RELEASE_VERSION "4.98" +#define EXIM_RELEASE_VERSION "4.98.2" #ifdef EXIM_VARIANT_VERSION #define EXIM_VERSION_STR EXIM_RELEASE_VERSION "-" EXIM_VARIANT_VERSION #else diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exim-4.98/src/version.sh new/exim-4.98.2/src/version.sh --- old/exim-4.98/src/version.sh 2024-07-10 16:49:39.000000000 +0200 +++ new/exim-4.98.2/src/version.sh 2025-03-25 11:13:22.000000000 +0100 @@ -1,3 +1,3 @@ # automatically generated file - see ../scripts/reversion -EXIM_RELEASE_VERSION="4.98" +EXIM_RELEASE_VERSION="4.98.2" EXIM_COMPILE_NUMBER="1"
