commit bfe32b5c6ea033736a26da8421513206db9fe305 Author: Heiko Schlittermann (HS12-RIPE) Date: Tue Feb 18 19:13:40 2025 +0100 Squashed from fix/cve-2025-26794 (fixes CVE-26794) commits: - 7f6698462878112cdf0492a20ebe2c741add6e13 - cb555353adf0b3a2636cc871c798d6a3dc883ccf - 9c3a6612aa38f959f5066fe4fa3ffeb8d2ac41d9 - ce7afaaa984a6abf7f96aab7c2deacd7313ab4fa - 2e5a01d44a311f83e80428c51c3e176516c8dfee - dd47d83b492c3de1f3fa897932458b000d3b89d6 - d2535688188d6df4cb99ff8d7ef4d6bfed0d8b00 - a893031446b24fb3270a7a7435f4eb599586406c - fef415a3ec1c58d85bb6feec5833c534ca9d600b - 621d158fec79ef0df6d1dcebfbd50515f616424a - 48181b3ab70924ae6b1ad6c4c8c10f10d8994046 - e073dfcfb5ccb807d5d19a130dc40e1343f6f665 - ce244262d5f8bee25231608d75ff3b500d6e3b81 - bf36dce31f105f1798625cd2508b05d56dc501b4 - 2487665952d853725fedef21d3fc10d6130008d2 - 95c259a14ec9c5a888e0a90e9cf16b80bde45ec1 - 1b59cf192ae9a0b3d56a33ae60c6e5c5b96548f3 - 1f11530a3b06fc07963974932ff9a58e18e008f6 - 1c70fe1448e79605bcd59ee86b2b8608d71cda89 - 385b54d67c2100c2614ade31b9c9f2176c99f58a - 0b3c72c5b809efe1098e5df7b4de3f251f785dc3 diff --git a/src/src/hintsdb.h b/src/src/hintsdb.h index a6555ed25..6d71c994c 100644 --- a/src/src/hintsdb.h +++ b/src/src/hintsdb.h @@ -66,6 +66,15 @@ required by Exim's process transisitions)? #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 @@ required by Exim's process transisitions)? # /* 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 @@ if ((ret = sqlite3_open_v2(CCS name, &dbp, sflags, NULL)) == SQLITE_OK) 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 @@ return ret; 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 @@ return exim_s_dbp(dbp, key, data, US"ABORT"); 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 @@ return c; } /* 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 @@ store_free(cursor); # endif } - /* EXIM_DBCLOSE */ static void exim_dbclose__(EXIM_DB * dbp) @@ -335,16 +467,16 @@ exim_dbclose__(EXIM_DB * dbp) 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 @@ static void 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 @@ exim_datum_free(EXIM_DATUM * dp) # define EXIM_DB_RLIMIT 150 - - - - - #elif defined(USE_TDB) # if defined(USE_DB) || defined(USE_GDBM) || defined(USE_SQLITE) @@ -513,11 +638,6 @@ d->dptr = NULL; # define EXIM_DB_RLIMIT 150 - - - - - /********************* Berkeley db native definitions **********************/ #elif defined USE_DB @@ -574,13 +694,11 @@ at DB release 4.3. */ 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 @@ exim_datum_free(EXIM_DATUM * d) # endif - # else /* DB_VERSION_MAJOR >= 3 */ # error Berkeley DB versions earlier than 3 are not supported */ # endif /* DB_VERSION_MAJOR */ @@ -849,17 +966,11 @@ exim_datum_free(EXIM_DATUM * d) # 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 @@ exim_datum_free(EXIM_DATUM * d) #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 @@ -1145,10 +1250,6 @@ exim_datum_free(EXIM_DATUM * d) #endif /* !USE_GDBM */ - - - - #if defined(COMPILE_UTILITY) || defined(MACRO_PREDEF) static inline EXIM_DB * @@ -1166,9 +1267,6 @@ exim_dbclose(EXIM_DB * dbp) /* 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 @@ exim_dbclose__(dbp); /********************* 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 --git a/src/src/smtp_in.c b/src/src/smtp_in.c index c52d3f4d6..b80e075e6 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -5576,14 +5576,27 @@ while (done <= 0) 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 @@ while (done <= 0) 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) {