commit eb39a357cb57fdf83b89c9039dd6e7a7983fd5df Merge: d997c8cd3 dddc92898 Author: Jeremy Harris Date: Tue Oct 28 13:26:04 2025 +0000 Merge branch '4.next' diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index 5a15b0a6b..e24ef95f2 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -539,8 +539,7 @@ transport-filter.pl: config ../src/transport-filter.src # are thrown away by the linker. OBJ_WITH_CONTENT_SCAN = malware.o mime.o regex.o spam.o spool_mbox.o -OBJ_EXPERIMENTAL = bmi_spam.o \ - dane.o \ +OBJ_EXPERIMENTAL = dane.o \ dcc.o \ imap_utf7.o \ utf8.o \ @@ -938,7 +937,6 @@ spool_mbox.o: $(HDRS) spool_mbox.c # Dependencies for EXPERIMENTAL_* modules -bmi_spam.o: $(HDRS) bmi_spam.c dane.o: $(HDRS) dane.c dane-openssl.c dcc.o: $(HDRS) dcc.h dcc.c imap_utf7.o: $(HDRS) imap_utf7.c diff --git a/src/exim_monitor/em_globals.c b/src/exim_monitor/em_globals.c index 491a9dda6..5627a2277 100644 --- a/src/exim_monitor/em_globals.c +++ b/src/exim_monitor/em_globals.c @@ -44,11 +44,6 @@ uschar actioned_message[24]; uschar *action_required; uschar *alternate_config = NULL; -#ifdef EXPERIMENTAL_BRIGHTMAIL -int bmi_run = 0; -uschar *bmi_verdicts = NULL; -#endif - int body_max = 20000; uschar *exim_path = US BIN_DIRECTORY "/exim" diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index d35962f98..8f55be0b5 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -32,9 +32,9 @@ d="lookups" mkdir $d cd $d # Makefile is generated -for f in README cdb.c dbmdb.c dnsdb.c dsearch.c ibase.c json.c ldap.c \ +for f in README cdb.c dbmdb.c dnsdb.c dsearch.c json.c ldap.c \ lmdb.c lsearch.c mysql.c nis.c nisplus.c nmh.c oracle.c passwd.c \ - pgsql.c readsock.c redis.c spf.c sqlite.c testdb.c whoson.c \ + pgsql.c psl.c readsock.c redis.c spf.c sqlite.c testdb.c whoson.c \ lf_functions.h lf_check_file.c lf_quote.c lf_sqlperform.c do ln -s ../../src/$d/$f $f @@ -155,7 +155,7 @@ do done # EXPERIMENTAL_* -for f in bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \ +for f in dcc.c dcc.h dane.c dane-openssl.c \ danessl.h imap_utf7.c utf8.c xclient.c do ln -s ../src/$f $f diff --git a/src/scripts/lookups-Makefile b/src/scripts/lookups-Makefile index 0617e31cd..6555e820c 100755 --- a/src/scripts/lookups-Makefile +++ b/src/scripts/lookups-Makefile @@ -101,7 +101,8 @@ want_dynamic() { want_at_all() { local want_name="$1" - local re="(LOOKUP|EXPERIMENTAL)_${want_name}[ $tab]*=[ $tab]*." + # SUPPORT is here purely for the spf lookup + local re="(LOOKUP|EXPERIMENTAL|SUPPORT)_${want_name}[ $tab]*=[ $tab]*." env | ${egrep} -q "^$re" if [ $? -eq 0 ]; then return 0; fi ${egrep} -q "^[ $tab]*$re" "$defs_source" @@ -156,23 +157,18 @@ exec > "$target" sed -n "1,/$tag_marker/p" < "$input" for name_mod in \ - CDB DBM:dbmdb DNSDB DSEARCH IBASE JSON LMDB LDAP LSEARCH MYSQL NIS NISPLUS \ - NMH ORACLE PASSWD PGSQL REDIS SQLITE TESTDB WHOSON + CDB DBM:dbmdb DNSDB DSEARCH JSON LMDB LDAP LSEARCH MYSQL NIS NISPLUS \ + NMH ORACLE PASSWD PGSQL PSL REDIS SQLITE SPF TESTDB WHOSON do emit_module_rule $name_mod done -# Because the variable is SUPPORT_SPF and not LOOKUP_SPF we -# always include spf.o and compile a dummy if SUPPORT_SPF is not -# defined. - -OBJ="${OBJ} spf.o" - # readsock is always wanted as it implements the ${readsock } expansion -OBJ="${OBJ} readsock.o" +OBJ_ALWAYS="${OBJ_ALWAYS} readsock.o" echo "MODS = $MODS" echo "OBJ = $OBJ" +echo "OBJ_ALWAYS = $OBJ_ALWAYS" sed -n "/$tag_marker/,\$p" < "$input" diff --git a/src/src/EDITME b/src/src/EDITME index 3a9b167de..998406359 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -436,7 +436,6 @@ LOOKUP_DNSDB=yes # LOOKUP_CDB=yes # LOOKUP_DSEARCH=yes -# LOOKUP_IBASE=yes # LOOKUP_JSON=yes # LOOKUP_LDAP=yes # LOOKUP_LMDB=yes @@ -448,6 +447,7 @@ LOOKUP_DNSDB=yes # LOOKUP_ORACLE=yes # LOOKUP_PASSWD=yes # LOOKUP_PGSQL=yes +# LOOKUP_PSL=yes # LOOKUP_REDIS=yes # LOOKUP_SQLITE=yes # LOOKUP_SQLITE_PC=sqlite3 @@ -461,9 +461,6 @@ LOOKUP_DNSDB=yes # LOOKUP_NWILDLSEARCH=yes -# For IBASE you may need: -#LIBS += -lfbclient - #------------------------------------------------------------------------------ # If you have set LOOKUP_LDAP, you should set LDAP_LIB_TYPE to indicate # which LDAP library you have. Unfortunately, though most of their functions @@ -512,7 +509,7 @@ SUPPORT_DANE=yes # the command for linking Exim itself, not on any auxiliary programs. You # don't need to set LOOKUP_INCLUDE if the relevant directories are already # specified in INCLUDE. The settings below are just examples; -lpq is for -# PostgreSQL, -lgds is for Interbase, -lsqlite3 is for SQLite, -lhiredis +# PostgreSQL, -lsqlite3 is for SQLite, -lhiredis # is for Redis, -ljansson for JSON. # # You do not need to use this for any lookup information added via pkg-config. @@ -526,15 +523,16 @@ SUPPORT_DANE=yes # LOOKUP_INCLUDE=-I /usr/local/ldap/include -I /usr/local/mysql/include -I /usr/local/pgsql/include # LOOKUP_INCLUDE +=-I /usr/local/include -# LOOKUP_LIBS=-L/usr/local/lib -lldap -llber -lmysqlclient -lpq -lgds -lsqlite3 -llmdb +# LOOKUP_LIBS=-L/usr/local/lib -lldap -llber -lmysqlclient -lpq -lsqlite3 -llmdb # LOOKUP_LIBS=-L/usr/local/lib -lldap -llber # Some platforms may need this for LOOKUP_NIS: #LOOKUP_LIBS += -lnsl + +# These lookup types need appropriate libraries #LOOKUP_LIBS += -ljansson #LOOKUP_LIBS += -lhiredis -#------------------------------------------------------------------------------ # If you included LOOKUP_LMDB above you will need the library. Depending # on where installed you may also need an include directory # @@ -542,6 +540,7 @@ SUPPORT_DANE=yes # LOOKUP_LIBS += -llmdb # For dynamic-modules builds, use instead LOOKUP_LMDB_INCLUDE & LOOKUP_LMDB_LIBS +# LOOKUP_PSL will require that SUPPORT_I18N is defined (but needs no libraries) #------------------------------------------------------------------------------ # Compiling the Exim monitor: If you want to compile the Exim monitor, a @@ -694,15 +693,6 @@ DISABLE_MAL_MKS=yes # support. You must have SPF and DKIM support enabled also. # EXPERIMENTAL_ARC=yes -# Uncomment the following lines to add Brightmail AntiSpam support. You need -# to have the Brightmail client SDK installed. Please check the experimental -# documentation for implementation details. You need to edit the CFLAGS and -# LDFLAGS lines. - -# EXPERIMENTAL_BRIGHTMAIL=yes -# CFLAGS += -I/opt/brightmail/bsdk-6.0/include -# LDFLAGS += -lxml2_single -lbmiclient_single -L/opt/brightmail/bsdk-6.0/lib - # Uncomment the following to include extra information in fail DSN message (bounces) # EXPERIMENTAL_DSN_INFO=yes diff --git a/src/src/acl.c b/src/src/acl.c index 80a17c937..36fb850c8 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -63,9 +63,6 @@ enum { ACLC_ACL, ACLC_ADD_HEADER, ACLC_ATRN_DOMAINS, ACLC_AUTHENTICATED, -#ifdef EXPERIMENTAL_BRIGHTMAIL - ACLC_BMI_OPTIN, -#endif ACLC_CONDITION, ACLC_CONTINUE, ACLC_CONTROL, @@ -168,19 +165,6 @@ static condition_def conditions[] = { ACL_BIT_NOTSMTP_START | ACL_BIT_CONNECT | ACL_BIT_HELO), }, -#ifdef EXPERIMENTAL_BRIGHTMAIL - [ACLC_BMI_OPTIN] = { US"bmi_optin", ACD_EXP | ACD_MOD, - FORBIDDEN(ACL_BIT_AUTH | - ACL_BIT_CONNECT | ACL_BIT_HELO | - ACL_BIT_DATA | ACL_BIT_MIME | - ACL_BIT_PRDR | - ACL_BIT_ETRN | ACL_BIT_EXPN | - ACL_BIT_MAILAUTH | - ACL_BIT_MAIL | ACL_BIT_STARTTLS | - ACL_BIT_VRFY | ACL_BIT_PREDATA | - ACL_BIT_NOTSMTP_START), - }, -#endif [ACLC_CONDITION] = { US"condition", ACD_EXP, FORBIDDEN(0) }, [ACLC_CONTINUE] = { US"continue", ACD_EXP | ACD_MOD, @@ -433,9 +417,6 @@ static condition_module condition_modules[] = { enum { CONTROL_AUTH_UNADVERTISED, -#ifdef EXPERIMENTAL_BRIGHTMAIL - CONTROL_BMI_RUN, -#endif CONTROL_CASEFUL_LOCAL_PART, CONTROL_CASELOWER_LOCAL_PART, CONTROL_CUTTHROUGH_DELIVERY, @@ -493,10 +474,6 @@ static control_def controls_list[] = { (unsigned) ~(ACL_BIT_CONNECT | ACL_BIT_HELO) }, -#ifdef EXPERIMENTAL_BRIGHTMAIL -[CONTROL_BMI_RUN] = - { US"bmi_run", FALSE, 0 }, -#endif [CONTROL_CASEFUL_LOCAL_PART] = { US"caseful_local_part", FALSE, (unsigned) ~ACL_BIT_RCPT }, [CONTROL_CASELOWER_LOCAL_PART] = @@ -3460,17 +3437,6 @@ for (; cb; cb = cb->next) &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL) : FAIL; break; - #ifdef EXPERIMENTAL_BRIGHTMAIL - case ACLC_BMI_OPTIN: - { - int old_pool = store_pool; - store_pool = POOL_PERM; - bmi_current_optin = string_copy(arg); - store_pool = old_pool; - } - break; - #endif - case ACLC_CONDITION: /* The true/false parsing here should be kept in sync with that used in expand.c when dealing with ECOND_BOOL so that we don't have too many @@ -3512,12 +3478,6 @@ for (; cb; cb = cb->next) f.allow_auth_unadvertised = TRUE; break; -#ifdef EXPERIMENTAL_BRIGHTMAIL - case CONTROL_BMI_RUN: - bmi_run = 1; - break; -#endif - #ifndef DISABLE_DKIM case CONTROL_DKIM_VERIFY: f.dkim_disable_verify = TRUE; diff --git a/src/src/auths/cram_md5.c b/src/src/auths/cram_md5.c index df861f6c4..2f6a56626 100644 --- a/src/src/auths/cram_md5.c +++ b/src/src/auths/cram_md5.c @@ -343,7 +343,7 @@ auth_info cram_md5_auth_info = { .options_block = &auth_cram_md5_option_defaults, .options_len = sizeof(auth_cram_md5_options_block), .init = auth_cram_md5_init, -# ifdef DYNLOOKUP +# if AUTH_CRAM_MD5==2 .dyn_magic = AUTH_MAGIC, # endif }, diff --git a/src/src/auths/cyrus_sasl.c b/src/src/auths/cyrus_sasl.c index 8d39945bc..0ccb1f5fe 100644 --- a/src/src/auths/cyrus_sasl.c +++ b/src/src/auths/cyrus_sasl.c @@ -521,7 +521,7 @@ auth_info cyrus_sasl_auth_info = { .options_block = &auth_cyrus_sasl_option_defaults, .options_len = sizeof(auth_cyrus_sasl_options_block), .init = auth_cyrus_sasl_init, -# ifdef DYNLOOKUP +# if AUTH_CYRUS_SASL==2 .dyn_magic = AUTH_MAGIC, # endif }, diff --git a/src/src/auths/dovecot.c b/src/src/auths/dovecot.c index 5319878e2..118e95f29 100644 --- a/src/src/auths/dovecot.c +++ b/src/src/auths/dovecot.c @@ -564,7 +564,7 @@ auth_info dovecot_auth_info = { .options_block = &auth_dovecot_option_defaults, .options_len = sizeof(auth_dovecot_options_block), .init = auth_dovecot_init, -# ifdef DYNLOOKUP +# if AUTH_DOVECOT==2 .dyn_magic = AUTH_MAGIC, # endif }, diff --git a/src/src/auths/external.c b/src/src/auths/external.c index 0cdfcdcfe..883c9cae8 100644 --- a/src/src/auths/external.c +++ b/src/src/auths/external.c @@ -172,7 +172,7 @@ auth_info external_auth_info = { .options_block = &auth_external_option_defaults, .options_len = sizeof(auth_external_options_block), .init = auth_external_init, -# ifdef DYNLOOKUP +# if AUTH_EXTERNAL==2 .dyn_magic = AUTH_MAGIC, # endif }, diff --git a/src/src/auths/gsasl.c b/src/src/auths/gsasl.c index 9c315fc92..ad4c9b479 100644 --- a/src/src/auths/gsasl.c +++ b/src/src/auths/gsasl.c @@ -1073,7 +1073,7 @@ auth_info gsasl_auth_info = { .options_block = &auth_gsasl_option_defaults, .options_len = sizeof(auth_gsasl_options_block), .init = auth_gsasl_init, -# ifdef DYNLOOKUP +# if AUTH_GSASL==2 .dyn_magic = AUTH_MAGIC, # endif }, diff --git a/src/src/auths/heimdal_gssapi.c b/src/src/auths/heimdal_gssapi.c index 8014e61bb..1c79112c3 100644 --- a/src/src/auths/heimdal_gssapi.c +++ b/src/src/auths/heimdal_gssapi.c @@ -625,7 +625,7 @@ auth_info heimdal_gssapi_auth_info = { .options_block = &auth_heimdal_gssapi_option_defaults, .options_len = sizeof(auth_heimdal_gssapi_options_block), .init = auth_heimdal_gssapi_init, -# ifdef DYNLOOKUP +# if AUTH_HEIMDAL_GSSAPI==2 .dyn_magic = AUTH_MAGIC, # endif }, diff --git a/src/src/auths/plaintext.c b/src/src/auths/plaintext.c index 598311e65..772b49ff9 100644 --- a/src/src/auths/plaintext.c +++ b/src/src/auths/plaintext.c @@ -194,7 +194,7 @@ auth_info plaintext_auth_info = { .options_block = &auth_plaintext_option_defaults, .options_len = sizeof(auth_plaintext_options_block), .init = auth_plaintext_init, -# ifdef DYNLOOKUP +# if AUTH_PLAINTEXT==2 .dyn_magic = AUTH_MAGIC, # endif }, @@ -204,5 +204,5 @@ auth_info plaintext_auth_info = { .macros_create = NULL, }; -#endif /*AUTH_PLAINTEST*/ +#endif /*AUTH_PLAINTEXT*/ /* End of plaintext.c */ diff --git a/src/src/auths/spa.c b/src/src/auths/spa.c index 8bd95813e..3e2cccde9 100644 --- a/src/src/auths/spa.c +++ b/src/src/auths/spa.c @@ -389,7 +389,7 @@ auth_info spa_auth_info = { .options_block = &auth_spa_option_defaults, .options_len = sizeof(auth_spa_options_block), .init = auth_spa_init, -# ifdef DYNLOOKUP +# if AUTH_SPA==2 .dyn_magic = AUTH_MAGIC, # endif }, diff --git a/src/src/auths/tls.c b/src/src/auths/tls.c index b427d29aa..d44750098 100644 --- a/src/src/auths/tls.c +++ b/src/src/auths/tls.c @@ -108,7 +108,7 @@ auth_info tls_auth_info = { .options_block = &auth_tls_option_defaults, .options_len = sizeof(auth_tls_options_block), .init = auth_tls_init, -# ifdef DYNLOOKUP +# if AUTH_TLS==2 .dyn_magic = AUTH_MAGIC, # endif }, diff --git a/src/src/bmi_spam.c b/src/src/bmi_spam.c index 03e8defa6..e69de29bb 100644 --- a/src/src/bmi_spam.c +++ b/src/src/bmi_spam.c @@ -1,477 +0,0 @@ -/************************************************* -* Exim - an Internet mail transport agent * -*************************************************/ - -/* Code for calling Brightmail AntiSpam. - Copyright (c) Tom Kistner 2004 - License: GPL */ -/* Copyright (c) The Exim Maintainers 2021 - 2022 */ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "exim.h" -#ifdef EXPERIMENTAL_BRIGHTMAIL - -#include "bmi_spam.h" - -uschar *bmi_current_optin = NULL; - -uschar *bmi_process_message(header_line *header_list, int data_fd) { - BmiSystem *system = NULL; - BmiMessage *message = NULL; - BmiError err; - BmiErrorLocation err_loc; - BmiErrorType err_type; - const BmiVerdict *verdict = NULL; - FILE *data_file; - uschar data_buffer[4096]; - uschar localhost[] = "127.0.0.1"; - uschar *host_address; - uschar *verdicts = NULL; - int i,j; - - err = bmiInitSystem(BMI_VERSION, CS bmi_config_file, &system); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: could not initialize Brightmail system.", (int)err_loc, (int)err_type); - return NULL; - } - - err = bmiInitMessage(system, &message); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: could not initialize Brightmail message.", (int)err_loc, (int)err_type); - bmiFreeSystem(system); - return NULL; - } - - /* Send IP address of sending host */ - if (sender_host_address == NULL) - host_address = localhost; - else - host_address = sender_host_address; - err = bmiProcessConnection(CS host_address, message); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: bmiProcessConnection() failed (IP %s).", (int)err_loc, (int)err_type, CS host_address); - bmiFreeMessage(message); - bmiFreeSystem(system); - return NULL; - }; - - /* Send envelope sender address */ - err = bmiProcessFROM(CS sender_address, message); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: bmiProcessFROM() failed (address %s).", (int)err_loc, (int)err_type, CS sender_address); - bmiFreeMessage(message); - bmiFreeSystem(system); - return NULL; - }; - - /* Send envelope recipients */ - for(i=0;ibmi_optin != NULL) && (Ustrlen(r->bmi_optin) > 1)) { - debug_printf("passing bmiOptin string: %s\n", r->bmi_optin); - bmiOptinInit(&optin); - err = bmiOptinMset(optin, r->bmi_optin, ':'); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - log_write(0, LOG_PANIC|LOG_MAIN, - "bmi warning: [loc %d type %d]: bmiOptinMSet() failed (address '%s', string '%s').", (int)err_loc, (int)err_type, CS r->address, CS r->bmi_optin); - if (optin != NULL) - bmiOptinFree(optin); - optin = NULL; - }; - }; - - err = bmiAccumulateTO(CS r->address, optin, message); - - if (optin != NULL) - bmiOptinFree(optin); - - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: bmiAccumulateTO() failed (address %s).", (int)err_loc, (int)err_type, CS r->address); - bmiFreeMessage(message); - bmiFreeSystem(system); - return NULL; - }; - }; - err = bmiEndTO(message); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: bmiEndTO() failed.", (int)err_loc, (int)err_type); - bmiFreeMessage(message); - bmiFreeSystem(system); - return NULL; - }; - - /* Send message headers */ - while (header_list != NULL) { - /* skip deleted headers */ - if (header_list->type == '*') { - header_list = header_list->next; - continue; - }; - err = bmiAccumulateHeaders(CCS header_list->text, header_list->slen, message); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: bmiAccumulateHeaders() failed.", (int)err_loc, (int)err_type); - bmiFreeMessage(message); - bmiFreeSystem(system); - return NULL; - }; - header_list = header_list->next; - }; - err = bmiEndHeaders(message); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: bmiEndHeaders() failed.", (int)err_loc, (int)err_type); - bmiFreeMessage(message); - bmiFreeSystem(system); - return NULL; - }; - - /* Send body */ - data_file = fdopen(data_fd,"r"); - do { - j = fread(data_buffer, 1, sizeof(data_buffer), data_file); - if (j > 0) { - err = bmiAccumulateBody(CCS data_buffer, j, message); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: bmiAccumulateBody() failed.", (int)err_loc, (int)err_type); - bmiFreeMessage(message); - bmiFreeSystem(system); - return NULL; - }; - }; - } while (j > 0); - err = bmiEndBody(message); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: bmiEndBody() failed.", (int)err_loc, (int)err_type); - bmiFreeMessage(message); - bmiFreeSystem(system); - return NULL; - }; - - - /* End message */ - err = bmiEndMessage(message); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: bmiEndMessage() failed.", (int)err_loc, (int)err_type); - bmiFreeMessage(message); - bmiFreeSystem(system); - return NULL; - }; - - /* Get store for the verdict string. Since we are processing message data, assume that - the verdict is tainted. XXX this should use a growable-string */ - - verdicts = store_get(1, GET_TAINTED); - *verdicts = '\0'; - - for ( err = bmiAccessFirstVerdict(message, &verdict); - verdict; - err = bmiAccessNextVerdict(message, verdict, &verdict) ) { - char *verdict_str; - - err = bmiCreateStrFromVerdict(verdict,&verdict_str); - if (!store_extend(verdicts, - Ustrlen(verdicts)+1, Ustrlen(verdicts)+1+strlen(verdict_str)+1)) { - /* can't allocate more store */ - return NULL; - }; - if (*verdicts != '\0') - Ustrcat(verdicts, US ":"); - Ustrcat(verdicts, US verdict_str); - bmiFreeStr(verdict_str); - }; - - DEBUG(D_receive) debug_printf("bmi verdicts: %s\n", verdicts); - - if (Ustrlen(verdicts) == 0) - return NULL; - else - return verdicts; -} - - -int bmi_get_delivery_status(uschar *base64_verdict) { - BmiError err; - BmiErrorLocation err_loc; - BmiErrorType err_type; - BmiVerdict *verdict = NULL; - int rc = 1; /* deliver by default */ - - /* always deliver when there is no verdict */ - if (base64_verdict == NULL) - return 1; - - /* create verdict from base64 string */ - err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); - return 1; - }; - - err = bmiVerdictError(verdict); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - /* deliver normally due to error */ - rc = 1; - } - else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) { - /* deliver normally */ - rc = 1; - } - else if (bmiVerdictAccessDestination(verdict) == NULL) { - /* do not deliver */ - rc = 0; - } - else { - /* deliver to alternate location */ - rc = 1; - }; - - bmiFreeVerdict(verdict); - return rc; -} - - -uschar *bmi_get_alt_location(uschar *base64_verdict) { - BmiError err; - BmiErrorLocation err_loc; - BmiErrorType err_type; - BmiVerdict *verdict = NULL; - uschar *rc = NULL; - - /* always deliver when there is no verdict */ - if (base64_verdict == NULL) - return NULL; - - /* create verdict from base64 string */ - err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); - return NULL; - }; - - err = bmiVerdictError(verdict); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - /* deliver normally due to error */ - rc = NULL; - } - else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) { - /* deliver normally */ - rc = NULL; - } - else if (bmiVerdictAccessDestination(verdict) == NULL) { - /* do not deliver */ - rc = NULL; - } - else { - /* deliver to alternate location */ - rc = store_get(strlen(bmiVerdictAccessDestination(verdict))+1, GET_TAINTED); - Ustrcpy(rc, bmiVerdictAccessDestination(verdict)); - rc[strlen(bmiVerdictAccessDestination(verdict))] = '\0'; - }; - - bmiFreeVerdict(verdict); - return rc; -} - -uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) { - BmiError err; - BmiErrorLocation err_loc; - BmiErrorType err_type; - BmiVerdict *verdict = NULL; - const BmiRecipient *recipient = NULL; - const char *verdict_str = NULL; - uschar *verdict_ptr; - uschar *verdict_buffer = NULL; - int sep = 0; - - /* return nothing if there are no verdicts available */ - if (bmi_verdicts == NULL) - return NULL; - - /* allocate room for the b64 verdict string */ - verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1, GET_TAINTED); - - /* loop through verdicts */ - verdict_ptr = bmi_verdicts; - while ((verdict_str = CCS string_nextinlist(&verdict_ptr, &sep, - verdict_buffer, - Ustrlen(bmi_verdicts)+1)) != NULL) { - - /* create verdict from base64 string */ - err = bmiCreateVerdictFromStr(verdict_str, &verdict); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, verdict_str); - return NULL; - }; - - /* loop through rcpts for this verdict */ - for ( recipient = bmiVerdictAccessFirstRecipient(verdict); - recipient != NULL; - recipient = bmiVerdictAccessNextRecipient(verdict, recipient)) { - uschar *rcpt_local_part; - uschar *rcpt_domain; - - /* compare address against our subject */ - rcpt_local_part = US bmiRecipientAccessAddress(recipient); - rcpt_domain = Ustrchr(rcpt_local_part,'@'); - if (rcpt_domain == NULL) { - rcpt_domain = US""; - } - else { - *rcpt_domain = '\0'; - rcpt_domain++; - }; - - if ( (strcmpic(rcpt_local_part, bmi_local_part) == 0) && - (strcmpic(rcpt_domain, bmi_domain) == 0) ) { - /* found verdict */ - bmiFreeVerdict(verdict); - return US verdict_str; - }; - }; - - bmiFreeVerdict(verdict); - }; - - return NULL; -} - - -uschar *bmi_get_base64_tracker_verdict(uschar *base64_verdict) { - BmiError err; - BmiErrorLocation err_loc; - BmiErrorType err_type; - BmiVerdict *verdict = NULL; - uschar *rc = NULL; - - /* always deliver when there is no verdict */ - if (base64_verdict == NULL) - return NULL; - - /* create verdict from base64 string */ - err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); - return NULL; - }; - - /* create old tracker string from verdict */ - err = bmiCreateOldStrFromVerdict(verdict, &rc); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: bmiCreateOldStrFromVerdict() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); - return NULL; - }; - - bmiFreeVerdict(verdict); - return rc; -} - - -int bmi_check_rule(uschar *base64_verdict, uschar *option_list) { - BmiError err; - BmiErrorLocation err_loc; - BmiErrorType err_type; - BmiVerdict *verdict = NULL; - int rc = 0; - uschar *rule_num; - uschar *rule_ptr; - uschar rule_buffer[32]; - int sep = 0; - - - /* no verdict -> no rule fired */ - if (base64_verdict == NULL) - return 0; - - /* create verdict from base64 string */ - err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - err_loc = bmiErrorGetLocation(err); - err_type = bmiErrorGetType(err); - log_write(0, LOG_PANIC, - "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); - return 0; - }; - - err = bmiVerdictError(verdict); - if (bmiErrorIsFatal(err) == BMI_TRUE) { - /* error -> no rule fired */ - bmiFreeVerdict(verdict); - return 0; - } - - /* loop through numbers */ - /* option_list doesn't seem to be expanded so cannot be tainted. If it ever is we - will trap here */ - rule_ptr = option_list; - while ((rule_num = string_nextinlist(&rule_ptr, &sep, - rule_buffer, sizeof(rule_buffer)))) { - int rule_int = -1; - - /* try to translate to int */ - (void)sscanf(rule_num, "%d", &rule_int); - if (rule_int > 0) { - debug_printf("checking rule #%d\n", rule_int); - /* check if rule fired on the message */ - if (bmiVerdictRuleFired(verdict, rule_int) == BMI_TRUE) { - debug_printf("rule #%d fired\n", rule_int); - rc = 1; - break; - }; - }; - }; - - bmiFreeVerdict(verdict); - return rc; -}; - -#endif diff --git a/src/src/bmi_spam.h b/src/src/bmi_spam.h index bb1c859a9..e69de29bb 100644 --- a/src/src/bmi_spam.h +++ b/src/src/bmi_spam.h @@ -1,23 +0,0 @@ -/************************************************* -* Exim - an Internet mail transport agent * -*************************************************/ - -/* Code for calling Brightmail AntiSpam. - Copyright (c) Tom Kistner 2004 - License: GPL */ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -#ifdef EXPERIMENTAL_BRIGHTMAIL - -#include - -extern uschar *bmi_process_message(header_line *, int); -extern uschar *bmi_get_base64_verdict(uschar *, uschar *); -extern uschar *bmi_get_base64_tracker_verdict(uschar *); -extern int bmi_get_delivery_status(uschar *); -extern uschar *bmi_get_alt_location(uschar *); -extern int bmi_check_rule(uschar *,uschar *); - -extern uschar *bmi_current_optin; - -#endif diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults index ebf67ad57..05bdcd6d3 100644 --- a/src/src/config.h.defaults +++ b/src/src/config.h.defaults @@ -102,7 +102,6 @@ Do not put spaces between # and the 'define'. #define LOOKUP_DBM #define LOOKUP_DNSDB #define LOOKUP_DSEARCH -#define LOOKUP_IBASE #define LOOKUP_JSON #define LOOKUP_LDAP #define LOOKUP_LMDB @@ -113,6 +112,7 @@ Do not put spaces between # and the 'define'. #define LOOKUP_ORACLE #define LOOKUP_PASSWD #define LOOKUP_PGSQL +#define LOOKUP_PSL #define LOOKUP_REDIS #define LOOKUP_SQLITE #define LOOKUP_TESTDB @@ -217,7 +217,6 @@ Do not put spaces between # and the 'define'. /* EXPERIMENTAL features */ #define EXPERIMENTAL_ARC -#define EXPERIMENTAL_BRIGHTMAIL #define EXPERIMENTAL_DCC #define EXPERIMENTAL_DSN_INFO #define EXPERIMENTAL_NMH diff --git a/src/src/configure.default b/src/src/configure.default index 633c6539e..c05ed182e 100644 --- a/src/src/configure.default +++ b/src/src/configure.default @@ -260,21 +260,6 @@ host_lookup = * dns_dnssec_ok = 1 -# The settings below cause Exim to make RFC 1413 (ident) callbacks -# for all incoming SMTP calls. You can limit the hosts to which these -# calls are made, and/or change the timeout that is used. If you set -# the timeout to zero, all RFC 1413 calls are disabled. RFC 1413 calls -# are cheap and can provide useful information for tracing problem -# messages, but some hosts and firewalls have problems with them. -# This can result in a timeout instead of an immediate refused -# connection, leading to delays on starting up SMTP sessions. -# (The default was reduced from 30s to 5s for release 4.61. and to -# disabled for release 4.86) -# -#rfc1413_hosts = * -#rfc1413_query_timeout = 5s - - # Enable an efficiency feature. We advertise the feature; clients # may request to use it. For multi-recipient mails we then can # reject or accept per-user after the message is received. diff --git a/src/src/daemon.c b/src/src/daemon.c index a31ef1eb5..824fe0187 100644 --- a/src/src/daemon.c +++ b/src/src/daemon.c @@ -479,20 +479,9 @@ if (pid == 0) signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); - /* Attempt to get an id from the sending machine via the RFC 1413 - protocol. We do this in the sub-process in order not to hold up the - main process if there is any delay. Then set up the fullhost information - in case there is no HELO/EHLO. + /* Set up the fullhost information in case there is no HELO/EHLO. */ - If debugging is enabled only for the daemon, we must turn if off while - finding the id, but turn it on again afterwards so that information about the - incoming connection is output. */ - - if (f.debug_daemon) debug_selector = 0; - verify_get_ident(IDENT_PORT); host_build_sender_fullhost(); - debug_selector = save_debug_selector; - DEBUG(D_any) debug_printf("Process %d is handling incoming connection from %s\n", (int)getpid(), sender_fullhost); diff --git a/src/src/deliver.c b/src/src/deliver.c index 21a610859..497c62e81 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -206,13 +206,6 @@ router_var = addr->prop.variables; deliver_domain = addr->domain; self_hostname = addr->self_hostname; -#ifdef EXPERIMENTAL_BRIGHTMAIL -bmi_deliver = 1; /* deliver by default */ -bmi_alt_location = NULL; -bmi_base64_verdict = NULL; -bmi_base64_tracker_verdict = NULL; -#endif - /* If there's only one address we can set everything. */ if (!addr->next) @@ -269,17 +262,6 @@ if (!addr->next) } } -#ifdef EXPERIMENTAL_BRIGHTMAIL - /* Set expansion variables related to Brightmail AntiSpam */ - bmi_base64_verdict = bmi_get_base64_verdict(deliver_localpart_orig, deliver_domain_orig); - bmi_base64_tracker_verdict = bmi_get_base64_tracker_verdict(bmi_base64_verdict); - /* get message delivery status (0 - don't deliver | 1 - deliver) */ - bmi_deliver = bmi_get_delivery_status(bmi_base64_verdict); - /* if message is to be delivered, get eventual alternate location */ - if (bmi_deliver == 1) - bmi_alt_location = bmi_get_alt_location(bmi_base64_verdict); -#endif - } /* For multiple addresses, don't set local part, and leave the domain and @@ -1059,7 +1041,7 @@ else s = addr->domain; #ifdef SUPPORT_I18N if (testflag(addr, af_utf8_downcvt)) - s = string_localpart_utf8_to_alabel(s, NULL); + s = string_domain_utf8_to_alabel(US s, NULL); #endif g = string_cat(g, s); } @@ -2656,10 +2638,12 @@ if (addr->special_action == SPECIAL_WARN) /* Check transport for the given concurrency limit. Return TRUE if over the limit (or an expansion failure), else FALSE and if there was a limit, -the key for the hints database used for the concurrency count. */ +the key for the hints database used for the concurrency count +and a string for observability. */ static BOOL -tpt_parallel_check(transport_instance * tp, address_item * addr, uschar ** key) +tpt_parallel_check(const transport_instance * tp, address_item * addr, + uschar ** key, uschar ** state) { const uschar * trname = tp->drinst.name; unsigned max_parallel; @@ -2679,22 +2663,22 @@ if (expand_string_message) if (max_parallel > 0) { uschar * serialize_key = string_sprintf("tpt-serialize-%s", trname); - if (!enq_start(serialize_key, max_parallel)) + unsigned running; + if (!(running = enq_start(serialize_key, max_parallel))) { - address_item * next; DEBUG(D_transport) debug_printf("skipping tpt %s because concurrency limit %u reached\n", trname, max_parallel); do { - next = addr->next; addr->message = US"concurrency limit reached for transport"; addr->basic_errno = ERRNO_TRETRY; post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_TRANSPORT, 0); - } while ((addr = next)); + } while ((addr = addr->next)); return TRUE; } *key = serialize_key; + *state = string_sprintf(" (%u/max_parallel:%u)", running, max_parallel); } return FALSE; } @@ -2731,7 +2715,7 @@ while (addr_local) int logflags = LOG_MAIN; int logchar = f.dont_deliver? '*' : '='; transport_instance * tp; - uschar * serialize_key = NULL; + uschar * serialize_key = NULL, * tpt_dummy; const uschar * trname; /* Pick the first undelivered address off the chain */ @@ -3018,7 +3002,7 @@ while (addr_local) We use a hints DB entry, incremented here and decremented after the transport (and any shadow transport) completes. */ - if (tpt_parallel_check(tp, addr, &serialize_key)) + if (tpt_parallel_check(tp, addr, &serialize_key, &tpt_dummy)) { if (expand_string_message) { @@ -3996,20 +3980,22 @@ can be created, or when waiting for the last ones to complete. It must wait for the completion of one subprocess, empty the control block slot, and return a pointer to the address chain. -Arguments: none -Returns: pointer to the chain of addresses handled by the process; - NULL if no subprocess found - this is an unexpected error +Arguments: + reason: observability: reason for call + +Returns: pointer to the chain of addresses handled by the process; + NULL if no subprocess found - this is an unexpected error */ static address_item * -par_wait(void) +par_wait(const uschar * reason) { int poffset, status; address_item * addrlist; pid_t pid; set_process_info("delivering %s: waiting for a remote delivery subprocess " - "to finish", message_id); + "to finish (%s)", message_id, reason); /* Loop until either a subprocess completes, or there are no subprocesses in existence - in which case give an error return. We cannot proceed just by @@ -4256,16 +4242,17 @@ log and proceed as if all done. Arguments: max maximum number of subprocesses to leave running fallback TRUE if processing fallback hosts + reason observability: reason for call Returns: nothing */ static void -par_reduce(int max, BOOL fallback) +par_reduce(int max, BOOL fallback, const uschar * reason) { while (parcount > max) { - address_item * doneaddr = par_wait(); + address_item * doneaddr = par_wait(reason); if (!doneaddr) { log_write(0, LOG_MAIN|LOG_PANIC, @@ -4357,6 +4344,7 @@ static BOOL do_remote_deliveries(BOOL fallback) { int parmax, poffset; +const uschar * plimit_reason = US"remote_max_parallel"; parcount = 0; /* Number of executing subprocesses */ @@ -4364,7 +4352,8 @@ parcount = 0; /* Number of executing subprocesses */ We use a local variable (parmax) to hold the maximum number of processes; this gets reduced from remote_max_parallel if we can't create enough pipes. */ -if (continue_transport) remote_max_parallel = 1; +if (continue_transport) + { remote_max_parallel = 1; plimit_reason = US"continue_transport"; } parmax = remote_max_parallel; /* If the data for keeping a list of processes hasn't yet been @@ -4388,12 +4377,12 @@ for (int delivery_count = 0; addr_remote; delivery_count++) uid_t uid; gid_t gid; int pfd[2]; - int address_count = 1, address_count_max; + unsigned address_count = 1, address_count_max; BOOL pipe_done = FALSE, multi_domain, use_initgroups; transport_instance * tp; address_item ** anchor = &addr_remote; address_item * addr = addr_remote, * last = addr, * next; - uschar * serialize_key = NULL, * panicmsg; + uschar * serialize_key = NULL, * tpt_parallel_level, * panicmsg; /* Pull the first address right off the list. */ @@ -4457,7 +4446,7 @@ So look out for the place it gets used. unlimited, which is forced for the MUA wrapper case and if the value could vary depending on the messages. For those, we only split (below) by (tpt,dest,erraddr,hdrs) and rely on the - transport splitting further by max_rcp. So we potentially lose some + transport splitting further by max_rcpt. So we potentially lose some parallellism. */ GET_OPTION("max_rcpt"); @@ -4573,7 +4562,8 @@ Does that also apply to address_data? last = next; address_count++; } - else anchor = &(next->next); + else + anchor = &next->next; deliver_set_expansions(NULL); } @@ -4591,7 +4581,8 @@ Does that also apply to address_data? The hints DB entry is decremented in par_reduce(), when we reap the transport process. */ - if (tpt_parallel_check(tp, addr, &serialize_key)) + tpt_parallel_level = US""; + if (tpt_parallel_check(tp, addr, &serialize_key, &tpt_parallel_level)) if ((panicmsg = expand_string_message)) goto panic_continue; else @@ -4788,9 +4779,12 @@ parmax * tpt-max is exceeded? */ while (!pipe_done) { - if (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0) pipe_done = TRUE; - else if (parcount > 0) parmax = parcount; - else break; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0) + pipe_done = TRUE; + else if (parcount > 0) + { parmax = parcount; plimit_reason = US"fildesc limit"; } + else + break; /* We need to make the reading end of the pipe non-blocking. There are two different options for this. Exim is cunningly (I hope!) coded so @@ -4811,7 +4805,7 @@ all pipes, so I do not see a reason to use non-blocking IO here to finish. If we ran out of file descriptors, parmax will have been reduced from its initial value of remote_max_parallel. */ - par_reduce(parmax - 1, fallback); + par_reduce(parmax - 1, fallback, plimit_reason); } /* If we failed to create a pipe and there were no processes to wait @@ -4956,7 +4950,9 @@ do_remote_deliveries par_reduce par_wait par_read_pipe of bytes written. */ (void)close(pfd[pipe_read]); - set_process_info("delivering %s using %s", message_id, tp->drinst.name); + set_process_info("delivering %s (%u addr%s) using %s%s", + message_id, address_count, address_count>1?"s":"", + tp->drinst.name, tpt_parallel_level); debug_print_string(tp->debug_string); { @@ -5364,7 +5360,7 @@ do_remote_deliveries par_reduce par_wait par_read_pipe if (continue_transport) { - par_reduce(0, fallback); + par_reduce(0, fallback, US"continue_transport wait-complete"); if (!*continue_next_id && continue_wait_db) { dbfn_close_multi(continue_wait_db); continue_wait_db = NULL; } @@ -5393,7 +5389,7 @@ panic_continue: /* Reached the end of the list of addresses. Wait for all the subprocesses that are still running and post-process their addresses. */ -par_reduce(0, fallback); +par_reduce(0, fallback, US"delivery wait-all-complete"); return TRUE; } diff --git a/src/src/dns.c b/src/src/dns.c index 4c8f1dd14..dd754f6c9 100644 --- a/src/src/dns.c +++ b/src/src/dns.c @@ -841,15 +841,14 @@ if ((rc = dns_fail_cache_hit(name, type)) > 0) #ifdef SUPPORT_I18N /* Convert all names to a-label form before doing lookup */ { - uschar * alabel; + const uschar * alabel; uschar * errstr = NULL; DEBUG(D_dns) if (string_is_utf8(name)) debug_printf_indent("convert utf8 '%s' to alabel for for lookup\n", name); if ((alabel = string_domain_utf8_to_alabel(name, &errstr)), errstr) { - DEBUG(D_dns) - debug_printf_indent("DNS name '%s' utf8 conversion to alabel failed: %s\n", name, - errstr); + DEBUG(D_dns) debug_printf_indent( + "DNS name '%s' utf8 conversion to alabel failed: %s\n", name, errstr); f.host_find_failed_syntax = TRUE; return DNS_NOMATCH; } diff --git a/src/src/drtables.c b/src/src/drtables.c index adbf7d539..5993b5c29 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -31,200 +31,52 @@ transport_info * transports_available = NULL; #ifndef MACRO_PREDEF -gstring * -auth_show_supported(gstring * g) +static gstring * +dr_show_list(gstring * g, const uschar ** list, const uschar * label, + const uschar * class) { -uschar * b = US"" /* static-build authenticatornames */ -#if defined(AUTH_CRAM_MD5) && AUTH_CRAM_MD5!=2 - " cram_md5" -#endif -#if defined(AUTH_CYRUS_SASL) && AUTH_CYRUS_SASL!=2 - " cyrus_sasl" -#endif -#if defined(AUTH_DOVECOT) && AUTH_DOVECOT!=2 - " dovecot" -#endif -#if defined(AUTH_EXTERNAL) && AUTH_EXTERNAL!=2 - " external" -#endif -#if defined(AUTH_GSASL) && AUTH_GSASL!=2 - " gsasl" -#endif -#if defined(AUTH_HEIMDAL_GSSAPI) && AUTH_HEIMDAL_GSSAPI!=2 - " heimdal_gssapi" -#endif -#if defined(AUTH_PLAINTEXT) && AUTH_PLAINTEXT!=2 - " plaintext" -#endif -#if defined(AUTH_SPA) && AUTH_SPA!=2 - " spa" -#endif -#if defined(AUTH_TLS) && AUTH_TLS!=2 - " tls" -#endif - ; - -uschar * d = US"" /* dynamic-module authenticator names */ -#if defined(AUTH_CRAM_MD5) && AUTH_CRAM_MD5==2 - " cram_md5" -#endif -#if defined(AUTH_CYRUS_SASL) && AUTH_CYRUS_SASL==2 - " cyrus_sasl" -#endif -#if defined(AUTH_DOVECOT) && AUTH_DOVECOT==2 - " dovecot" -#endif -#if defined(AUTH_EXTERNAL) && AUTH_EXTERNAL==2 - " external" -#endif -#if defined(AUTH_GSASL) && AUTH_GSASL==2 - " gsasl" -#endif -#if defined(AUTH_HEIMDAL_GSSAPI) && AUTH_HEIMDAL_GSSAPI==2 - " heimdal_gssapi" -#endif -#if defined(AUTH_PLAINTEXT) && AUTH_PLAINTEXT==2 - " plaintext" -#endif -#if defined(AUTH_SPA) && AUTH_SPA==2 - " spa" -#endif -#if defined(AUTH_TLS) && AUTH_TLS==2 - " tls" -#endif - ; +if (*list) + { + const uschar ** ele = list; + g = string_fmt_append(g, "%s (%s): ", class, label); + while (*ele) ele++; + while (--ele >= list) g = string_fmt_append(g, " %s", *ele); + g = string_catn(g, US"\n", 1); + } +return g; +} -if (*b) g = string_fmt_append(g, "Authenticators (built-in):%s\n", b); -if (*d) g = string_fmt_append(g, "Authenticators (dynamic): %s\n", d); +static gstring * +dr_show_supported(gstring * g, + const uschar ** statics, const uschar ** dynamics, const uschar * class) +{ +g = dr_show_list(g, statics, US"built-in", class); +g = dr_show_list(g, dynamics, US"dynamic", class); return g; } gstring * -route_show_supported(gstring * g) +auth_show_supported(gstring * g) { -uschar * b = US"" /* static-build router names */ -#if defined(ROUTER_ACCEPT) && ROUTER_ACCEPT!=2 - " accept" -#endif -#if defined(ROUTER_DNSLOOKUP) && ROUTER_DNSLOOKUP!=2 - " dnslookup" -#endif -# if defined(ROUTER_IPLITERAL) && ROUTER_IPLITERAL!=2 - " ipliteral" -#endif -#if defined(ROUTER_IPLOOKUP) && ROUTER_IPLOOKUP!=2 - " iplookup" -#endif -#if defined(ROUTER_MANUALROUTE) && ROUTER_MANUALROUTE!=2 - " manualroute" -#endif -#if defined(ROUTER_REDIRECT) && ROUTER_REDIRECT!=2 - " redirect" -#endif -#if defined(ROUTER_QUERYPROGRAM) && ROUTER_QUERYPROGRAM!=2 - " queryprogram" -#endif - ; - -uschar * d = US"" /* dynamic-module router names */ -#if defined(ROUTER_ACCEPT) && ROUTER_ACCEPT==2 - " accept" -#endif -#if defined(ROUTER_DNSLOOKUP) && ROUTER_DNSLOOKUP==2 - " dnslookup" -#endif -# if defined(ROUTER_IPLITERAL) && ROUTER_IPLITERAL==2 - " ipliteral" -#endif -#if defined(ROUTER_IPLOOKUP) && ROUTER_IPLOOKUP==2 - " iplookup" -#endif -#if defined(ROUTER_MANUALROUTE) && ROUTER_MANUALROUTE==2 - " manualroute" -#endif -#if defined(ROUTER_REDIRECT) && ROUTER_REDIRECT==2 - " redirect" -#endif -#if defined(ROUTER_QUERYPROGRAM) && ROUTER_QUERYPROGRAM==2 - " queryprogram" -#endif - ; +return dr_show_supported(g, avail_static_auths, avail_dynamic_auths, + US"Authenticators"); +} -if (*b) g = string_fmt_append(g, "Routers (built-in):%s\n", b); -if (*d) g = string_fmt_append(g, "Routers (dynamic): %s\n", d); -return g; +gstring * +route_show_supported(gstring * g) +{ +return dr_show_supported(g, avail_static_routers, avail_dynamic_routers, + US"Routers"); } gstring * transport_show_supported(gstring * g) { -uschar * b = US"" /* static-build transportnames */ -#if defined(TRANSPORT_APPENDFILE) && TRANSPORT_APPENDFILE!=2 - " appendfile" -# ifdef SUPPORT_MAILDIR - "/maildir" -# endif -# ifdef SUPPORT_MAILSTORE - "/mailstore" -# endif -# ifdef SUPPORT_MBX - "/mbx" -# endif -#endif -#if defined(TRANSPORT_AUTOREPLY) && TRANSPORT_AUTOREPLY!=2 - " autoreply" -#endif -#if defined(TRANSPORT_LMTP) && TRANSPORT_LMTP!=2 - " lmtp" -#endif -#if defined(TRANSPORT_PIPE) && TRANSPORT_PIPE!=2 - " pipe" -#endif -#if defined(EXPERIMENTAL_QUEUEFILE) && EXPERIMENTAL_QUEUEFILE!=2 - " queuefile" -#endif -#if defined(TRANSPORT_SMTP) && TRANSPORT_SMTP!=2 - " smtp" -#endif - ; - -uschar * d = US"" /* dynamic-module transportnames */ -#if defined(TRANSPORT_APPENDFILE) && TRANSPORT_APPENDFILE==2 - " appendfile" -# ifdef SUPPORT_MAILDIR - "/maildir" -# endif -# ifdef SUPPORT_MAILSTORE - "/mailstore" -# endif -# ifdef SUPPORT_MBX - "/mbx" -# endif -#endif -#if defined(TRANSPORT_AUTOREPLY) && TRANSPORT_AUTOREPLY==2 - " autoreply" -#endif -#if defined(TRANSPORT_LMTP) && TRANSPORT_LMTP==2 - " lmtp" -#endif -#if defined(TRANSPORT_PIPE) && TRANSPORT_PIPE==2 - " pipe" -#endif -#if defined(EXPERIMENTAL_QUEUEFILE) && EXPERIMENTAL_QUEUEFILE==2 - " queuefile" -#endif -#if defined(TRANSPORT_SMTP) && TRANSPORT_SMTP==2 - " smtp" -#endif - ; - -if (*b) g = string_fmt_append(g, "Transports (built-in):%s\n", b); -if (*d) g = string_fmt_append(g, "Transports (dynamic): %s\n", d); -return g; +return dr_show_supported(g, avail_static_transports, avail_dynamic_transports, + US"Transports"); } - static void add_lookup_to_tree(lookup_info * li) { @@ -271,77 +123,6 @@ return li; -/* These need to be at file level for old versions of gcc (2.95.2 reported), -which give parse errors on an extern in function scope. Each entry needs -to also be invoked in init_lookup_list() below */ - -#if defined(LOOKUP_CDB) && LOOKUP_CDB!=2 -extern lookup_module_info cdb_lookup_module_info; -#endif -#if defined(LOOKUP_DBM) && LOOKUP_DBM!=2 -extern lookup_module_info dbmdb_lookup_module_info; -#endif -#if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2 -extern lookup_module_info dnsdb_lookup_module_info; -#endif -#if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2 -extern lookup_module_info dsearch_lookup_module_info; -#endif -#if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2 -extern lookup_module_info ibase_lookup_module_info; -#endif -#if defined(LOOKUP_JSON) && LOOKUP_JSON!=2 -extern lookup_module_info json_lookup_module_info; -#endif -#if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2 -extern lookup_module_info ldap_lookup_module_info; -#endif -#if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2 -extern lookup_module_info lsearch_lookup_module_info; -#endif -#if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2 -extern lookup_module_info mysql_lookup_module_info; -#endif -#if defined(LOOKUP_NIS) && LOOKUP_NIS!=2 -extern lookup_module_info nis_lookup_module_info; -#endif -#if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2 -extern lookup_module_info nisplus_lookup_module_info; -#endif -#if defined(EXPERIMENTAL_NMH) && EXPERIMENTAL_NMH!=2 -extern lookup_module_info nmh_lookup_module_info; -#endif -#if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2 -extern lookup_module_info oracle_lookup_module_info; -#endif -#if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2 -extern lookup_module_info passwd_lookup_module_info; -#endif -#if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2 -extern lookup_module_info pgsql_lookup_module_info; -#endif -#if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2 -extern lookup_module_info redis_lookup_module_info; -#endif -#if defined(LOOKUP_LMDB) && LOOKUP_LMDB!=2 -extern lookup_module_info lmdb_lookup_module_info; -#endif -#if defined(EXIM_HAVE_SPF) -extern lookup_module_info spf_lookup_module_info; /* see below */ -#endif -#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2 -extern lookup_module_info sqlite_lookup_module_info; -#endif -#if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2 -extern lookup_module_info testdb_lookup_module_info; -#endif -#if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2 -extern lookup_module_info whoson_lookup_module_info; -#endif - -extern lookup_module_info readsock_lookup_module_info; - - #ifdef LOOKUP_MODULE_DIR static void * mod_open(const uschar * name, const uschar * class, uschar ** errstr) @@ -381,13 +162,13 @@ static BOOL lookup_mod_load(const uschar * name, uschar ** errstr) { void * dl; -struct lookup_module_info * info; +lookup_module_info * info; const char * errormsg; if (!(dl = mod_open(name, US"lookup", errstr))) return FALSE; -info = (struct lookup_module_info *) dlsym(dl, "_lookup_module_info"); +info = (lookup_module_info *) dlsym(dl, "_lookup_module_info"); if ((errormsg = dlerror())) { EARLY_DEBUG(D_any, "%s does not appear to be a lookup module (%s)\n", name, errormsg); @@ -426,6 +207,45 @@ return TRUE; #endif /*LOOKUP_MODULE_DIR*/ +/* Look at all the lookup module files and add a name from each lookup type */ + +gstring * +lookup_dynamic_supported(gstring * g) +{ +#ifdef LOOKUP_MODULE_DIR +DIR * dd; +const pcre2_code * regex_islookupmod = regex_must_compile( + US"^([a-z0-9]+)_lookup\\." DYNLIB_FN_EXT "$", MCS_NOFLAGS, TRUE); + +if (!(dd = exim_opendir(CUS LOOKUP_MODULE_DIR))) + g = string_cat(g, US"FAIL exim_opendir"); +else + for (struct dirent * ent; ent = readdir(dd); ) + { + void * dl; + uschar * errstr; + + if ( regex_match_and_setup(regex_islookupmod, US ent->d_name, 0, 0) + && (dl = mod_open(expand_nstring[1], US"lookup", &errstr)) + ) + { + lookup_module_info * lmi= + (lookup_module_info *) dlsym(dl, "_lookup_module_info"); + + if ( ! dlerror() + && lmi->magic == LOOKUP_MODULE_INFO_MAGIC + ) + for (lookup_info ** lip = lmi->lookups; + lip < lmi->lookups + lmi->lookupcount; lip++) + g = string_fmt_append(g, " %s", (*lip)->name); + + dlclose(dl); + } + } +#endif /*!LOOKUP_MODULE_DIR*/ +return g; +} + misc_module_info * misc_module_list = NULL; @@ -612,99 +432,8 @@ if (lookup_list_init_done) return; lookup_list_init_done = TRUE; -#if defined(LOOKUP_CDB) && LOOKUP_CDB!=2 -addlookupmodule(&cdb_lookup_module_info); -#endif - -#if defined(LOOKUP_DBM) && LOOKUP_DBM!=2 -addlookupmodule(&dbmdb_lookup_module_info); -#endif - -#if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2 -addlookupmodule(&dnsdb_lookup_module_info); -#endif - -#if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2 -addlookupmodule(&dsearch_lookup_module_info); -#endif - -#if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2 -addlookupmodule(&ibase_lookup_module_info); -#endif - -#if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2 -addlookupmodule(&ldap_lookup_module_info); -#endif - -#if defined(LOOKUP_JSON) && LOOKUP_JSON!=2 -addlookupmodule(&json_lookup_module_info); -#endif - -#if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2 -addlookupmodule(&lsearch_lookup_module_info); -#endif - -#if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2 -addlookupmodule(&mysql_lookup_module_info); -#endif - -#if defined(LOOKUP_NIS) && LOOKUP_NIS!=2 -addlookupmodule(&nis_lookup_module_info); -#endif - -#if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2 -addlookupmodule(&nisplus_lookup_module_info); -#endif - -#if defined(EXPERIMENTAL_NMH) && EXPERIMENTAL_NMH!=2 -addlookupmodule(&nmh_lookup_module_info); -#endif - -#if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2 -addlookupmodule(&oracle_lookup_module_info); -#endif - -#if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2 -addlookupmodule(&passwd_lookup_module_info); -#endif - -#if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2 -addlookupmodule(&pgsql_lookup_module_info); -#endif - -#if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2 -addlookupmodule(&redis_lookup_module_info); -#endif - -#if defined(LOOKUP_LMDB) && LOOKUP_LMDB!=2 -addlookupmodule(&lmdb_lookup_module_info); -#endif - -#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2 -addlookupmodule(&sqlite_lookup_module_info); -#endif - -#if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2 -addlookupmodule(&testdb_lookup_module_info); -#endif - -#if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2 -addlookupmodule(&whoson_lookup_module_info); -#endif - -/* This is provided by the spf "misc" module, and the lookup aspect is always -linked statically whether or not the "misc" module (and hence libspf2) is -dynamic-load. */ - -#ifdef EXIM_HAVE_SPF -addlookupmodule(&spf_lookup_module_info); -#endif - -/* This is a custom expansion, and not available as either -a list-syntax lookup or a lookup expansion. However, it is -implemented by a lookup module. */ - -addlookupmodule(&readsock_lookup_module_info); +for (lookup_module_info ** avi = avail_static_lookups; *avi; avi++) + addlookupmodule(*avi); DEBUG(D_lookup) debug_printf_indent("Total %d built-in lookups\n", lookup_list_count); @@ -727,10 +456,7 @@ else EARLY_DEBUG(D_lookup, "Loading lookup modules from %s\n", LOOKUP_MODULE_DIR); while ((ent = readdir(dd))) - { - char * name = ent->d_name; - int len = (int)strlen(name); - if (regex_match_and_setup(regex_islookupmod, US name, 0, 0)) + if (regex_match_and_setup(regex_islookupmod, US ent->d_name, 0, 0)) { uschar * errstr; if (lookup_mod_load(expand_nstring[1], &errstr)) @@ -741,7 +467,6 @@ else log_write(0, LOG_MAIN|LOG_PANIC, "%s", errstr); } } - } closedir(dd); } diff --git a/src/src/enq.c b/src/src/enq.c index 842f2d6ba..8129b3b2b 100644 --- a/src/src/enq.c +++ b/src/src/enq.c @@ -23,24 +23,24 @@ connections. It is also called when ETRN is listed for serialization. We open the misc database and look for a record, which implies an existing connection or ETRN run. If increasing the count would take us past the given limit -value return FALSE. If not, bump it and return TRUE. If not found, create +value return FALSE. If not, bump it and return the new count. If not found, create one with value 1 and return TRUE. Arguments: key string on which to serialize lim parallelism limit -Returns: TRUE if OK to proceed; FALSE otherwise +Returns: >0 if OK to proceed; 0 otherwise */ -BOOL -enq_start(uschar *key, unsigned lim) +unsigned +enq_start(uschar * key, unsigned lim) { const dbdata_serialize * serial_record; dbdata_serialize new_record; open_db dbblock; -open_db *dbm_file; +open_db * dbm_file; DEBUG(D_transport) debug_printf("check serialized: %s\n", key); @@ -60,7 +60,7 @@ if (serial_record && time(NULL) - serial_record->time_stamp < 6*60*60) dbfn_close(dbm_file); DEBUG(D_transport) debug_printf("outstanding serialization record for %s\n", key); - return FALSE; + return 0; } new_record.count = serial_record->count + 1; } @@ -73,7 +73,7 @@ DEBUG(D_transport) debug_printf("write serialization record for %s val %d\n", key, new_record.count); dbfn_write(dbm_file, key, &new_record, (int)sizeof(dbdata_serialize)); dbfn_close(dbm_file); -return TRUE; +return new_record.count; } diff --git a/src/src/exim.c b/src/src/exim.c index b1d77576f..88ed33a8e 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -1074,153 +1074,12 @@ lookup_show_supported(gstring * g) { gstring * b = NULL, * d = NULL; -#ifdef LOOKUP_LSEARCH -# if LOOKUP_LSEARCH!=2 - b = string_cat(b, US" lsearch wildlsearch nwildlsearch iplsearch"); -# else - d = string_cat(d, US" lsearch wildlsearch nwildlsearch iplsearch"); -# endif -#endif -#ifdef LOOKUP_CDB -# if LOOKUP_CDB!=2 - b = string_cat(b, US" cdb"); -# else - d = string_cat(d, US" cdb"); -# endif -#endif -#ifdef LOOKUP_DBM -# if LOOKUP_DBM!=2 - b = string_cat(b, US" dbm dbmjz dbmnz"); -# else - d = string_cat(d, US" dbm dbmjz dbmnz"); -# endif -#endif -#ifdef LOOKUP_DNSDB -# if LOOKUP_DNSDB!=2 - b = string_cat(b, US" dnsdb"); -# else - d = string_cat(d, US" dnsdb"); -# endif -#endif -#ifdef LOOKUP_DSEARCH -# if LOOKUP_DSEARCH!=2 - b = string_cat(b, US" dsearch"); -# else - d = string_cat(d, US" dsearch"); -# endif -#endif -#ifdef LOOKUP_IBASE -# if LOOKUP_IBASE!=2 - b = string_cat(b, US" ibase"); -# else - d = string_cat(d, US" ibase"); -# endif -#endif -#ifdef LOOKUP_JSON -# if LOOKUP_JSON!=2 - b = string_cat(b, US" json"); -# else - d = string_cat(d, US" json"); -# endif -#endif -#ifdef LOOKUP_LDAP -# if LOOKUP_LDAP!=2 - b = string_cat(b, US" ldap ldapdn ldapm"); -# else - d = string_cat(d, US" ldap ldapdn ldapm"); -# endif -#endif -#ifdef LOOKUP_LMDB -# if LOOKUP_LMDB!=2 - b = string_cat(b, US" lmdb"); -# else - d = string_cat(d, US" lmdb"); -# endif -#endif -#ifdef LOOKUP_MYSQL -# if LOOKUP_MYSQL!=2 - b = string_cat(b, US" mysql"); -# else - d = string_cat(d, US" mysql"); -# endif -#endif -#ifdef LOOKUP_NIS -# if LOOKUP_NIS!=2 - b = string_cat(b, US" nis nis0"); -# else - d = string_cat(d, US" nis nis0"); -# endif -#endif -#ifdef LOOKUP_NISPLUS -# if LOOKUP_NISPLUS!=2 - b = string_cat(b, US" nisplus"); -# else - d = string_cat(d, US" nisplus"); -# endif -#endif -#ifdef EXPERIMENTAL_NMH -# if EXPERIMENTAL_NMH!=2 - b = string_cat(b, US" nmh"); -# else - d = string_cat(d, US" nmh"); -# endif -#endif -#ifdef LOOKUP_ORACLE -# if LOOKUP_ORACLE!=2 - b = string_cat(b, US" oracle"); -# else - d = string_cat(d, US" oracle"); -# endif -#endif -#ifdef LOOKUP_PASSWD -# if LOOKUP_PASSWD!=2 - b = string_cat(b, US" passwd"); -# else - d = string_cat(d, US" passwd"); -# endif -#endif -#ifdef LOOKUP_PGSQL -# if LOOKUP_PGSQL!=2 - b = string_cat(b, US" pgsql"); -# else - d = string_cat(d, US" pgsql"); -# endif -#endif -#ifdef LOOKUP_REDIS -# if LOOKUP_REDIS!=2 - b = string_cat(b, US" redis"); -# else - d = string_cat(d, US" redis"); -# endif -#endif -#ifdef EXIM_HAVE_SPF -# if EXIM_HAVE_SPF !=2 - b = string_cat(b, US" spf"); -# else - d = string_cat(d, US" spf"); -# endif -#endif -#ifdef LOOKUP_SQLITE -# if LOOKUP_SQLITE!=2 - b = string_cat(b, US" sqlite"); -# else - d = string_cat(d, US" sqlite"); -# endif -#endif -#ifdef LOOKUP_TESTDB -# if LOOKUP_TESTDB!=2 - b = string_cat(b, US" testdb"); -# else - d = string_cat(d, US" testdb"); -# endif -#endif -#ifdef LOOKUP_WHOSON -# if LOOKUP_WHOSON!=2 - b = string_cat(b, US" whoson"); -# else - d = string_cat(d, US" whoson"); -# endif -#endif +for (lookup_module_info ** lmip = avail_static_lookups; *lmip; lmip++) + for (lookup_info ** lip = (*lmip)->lookups; + lip < (*lmip)->lookups + (*lmip)->lookupcount; lip++) + b = string_fmt_append(b, " %s", (*lip)->name); + +d = lookup_dynamic_supported(d); if (b) g = string_fmt_append(g, "Lookups (built-in):%Y\n", b); if (d) g = string_fmt_append(g, "Lookups (dynamic): %Y\n", d); @@ -1369,9 +1228,6 @@ g = string_cat(g, US"Support for:"); #ifdef EXPERIMENTAL_ARC g = string_cat(g, US" Experimental_ARC"); #endif -#ifdef EXPERIMENTAL_BRIGHTMAIL - g = string_cat(g, US" Experimental_Brightmail"); -#endif #ifdef EXPERIMENTAL_DCC g = string_cat(g, US" Experimental_DCC"); #endif @@ -1965,7 +1821,6 @@ BOOL arg_queue_only = FALSE; BOOL bi_option = FALSE; BOOL checking = FALSE; BOOL count_queue = FALSE; -BOOL expansion_test = FALSE; BOOL extract_recipients = FALSE; BOOL flag_G = FALSE; BOOL flag_n = FALSE; @@ -2483,7 +2338,7 @@ on the second character (the one after '-'), to save some effort. */ -bem: Ditto, but read a message from a file first */ case 'e': - expansion_test = checking = TRUE; + f.expansion_test = checking = TRUE; if (*argrest == 'm') { if (++i >= argc) { badarg = TRUE; break; } @@ -2914,14 +2769,9 @@ on the second character (the one after '-'), to save some effort. */ case 'd': - /* -dropcr: Set this option. Now a no-op, retained for compatibility only. */ - - if (Ustrcmp(argrest, "ropcr") == 0) - /* drop_cr = TRUE */ ; - /* -dp: Set up a debug pretrigger buffer with given size. */ - else if (Ustrcmp(argrest, "p") == 0) + if (Ustrcmp(argrest, "p") == 0) if (++i >= argc) badarg = TRUE; else @@ -4070,9 +3920,9 @@ if ( (smtp_input || extract_recipients || recipients_arg < argc) || smtp_input && (sender_address || filter_test != FTEST_NONE || extract_recipients) || deliver_selectstring && !qrunners - || msg_action == MSG_LOAD && (!expansion_test || expansion_test_message) + || msg_action == MSG_LOAD && (!f.expansion_test || expansion_test_message) || atrn_mode - && ( f.daemon_listen || expansion_test || filter_test != FTEST_NONE + && ( f.daemon_listen || f.expansion_test || filter_test != FTEST_NONE || checking /* || bi_option || info_stdout || receiving_message || malware_test_file || list_queue || list_config || list_options || version_printed || msg_action_arg > 0 || qrunners @@ -4230,7 +4080,7 @@ if (( /* EITHER */ real_uid != root_uid && /* Not root, and */ !f.running_in_test_harness /* Not fudged */ ) || /* OR */ - expansion_test /* expansion testing */ + f.expansion_test /* expansion testing */ || /* OR */ filter_test != FTEST_NONE) /* Filter testing */ { @@ -4960,7 +4810,7 @@ needed in transports so we lost the optimisation. */ /* -be can add macro definitions, needing to link to the macro structure chain. Otherwise, make the memory used for config data readonly. */ - if (!expansion_test) + if (!f.expansion_test) store_writeprotect(POOL_CONFIG); #ifdef MEASURE_TIMING @@ -5498,7 +5348,7 @@ from stdin if there aren't any. If -Mset was specified, load the message so that its variables can be used, but restrict this facility to admin users. Otherwise, if -bem was used, read a message from stdin. */ -if (expansion_test) +if (f.expansion_test) { set_process_info("expansion-test"); dns_init(FALSE, FALSE, FALSE); @@ -5615,9 +5465,7 @@ if (raw_active_hostname) /* Handle host checking: this facility mocks up an incoming SMTP call from a given IP address so that the blocking and relay configuration can be tested. Unless a sender_ident was set by -oMt, we discard it (the default is the -caller's login name). An RFC 1413 call is made only if we are running in the -test harness and an incoming interface and both ports are specified, because -there is no TCP/IP call to find the ident for. */ +caller's login name). */ if (host_checking) { @@ -5625,12 +5473,7 @@ if (host_checking) int size; if (!sender_ident_set) - { sender_ident = NULL; - if (f.running_in_test_harness && sender_host_port - && interface_address && interface_port) - verify_get_ident(test_harness_identd_port); - } /* In case the given address is a non-canonical IPv6 address, canonicalize it. Use the compressed form for IPv6. */ @@ -5653,7 +5496,6 @@ if (host_checking) debug_file = stderr; debug_fd = fileno(debug_file); dprintf(smtp_out_fd, "\n**** SMTP testing session as if from host %s\n" - "**** but without any ident (RFC 1413) callback.\n" "**** This is not for real!\n\n", sender_host_address); @@ -5765,14 +5607,12 @@ sendmail error modes other than -oem ever actually used? Later: yes.) */ if (!smtp_input) error_handling = arg_error_handling; /* If this is an inetd call, ensure that stderr is closed to prevent panic -logging being sent down the socket and make an identd call to get the -sender_ident. */ +logging being sent down the socket. */ else if (f.is_inetd && !atrn_mode) { (void)fclose(stderr); exim_nullstd(); /* Re-open to /dev/null */ - verify_get_ident(IDENT_PORT); host_build_sender_fullhost(); set_process_info("handling incoming connection from %s via inetd", sender_fullhost); diff --git a/src/src/exim.h b/src/src/exim.h index 8109c2a7c..fa56f2c2c 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -156,8 +156,6 @@ configuration file. We also use this for some other short strings, such as queue names. Also TLS ciphersuite name (no real known limit since the protocols use integers, but max seen in reality is 45 octets). - -RFC 1413 gives us the 512 limit on IDENT protocol userids. */ #define EXIM_EMAILADDR_MAX 256 @@ -541,9 +539,6 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly. #endif #include "osfunctions.h" -#ifdef EXPERIMENTAL_BRIGHTMAIL -# include "bmi_spam.h" -#endif #if defined(SUPPORT_SPF) || defined(EXPERIMENTAL_SPF_PERL) # include "miscmods/spf.h" # include "miscmods/spf_api.h" diff --git a/src/src/expand.c b/src/src/expand.c index e852c27c7..554d68ba6 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -474,12 +474,6 @@ static var_entry var_table[] = { { "authentication_failed",vtype_int, &authentication_failed }, #ifdef WITH_CONTENT_SCAN { "av_failed", vtype_int, &av_failed }, -#endif -#ifdef EXPERIMENTAL_BRIGHTMAIL - { "bmi_alt_location", vtype_stringptr, &bmi_alt_location }, - { "bmi_base64_tracker_verdict", vtype_stringptr, &bmi_base64_tracker_verdict }, - { "bmi_base64_verdict", vtype_stringptr, &bmi_base64_verdict }, - { "bmi_deliver", vtype_int, &bmi_deliver }, #endif { "body_linecount", vtype_int, &body_linecount }, { "body_zerocount", vtype_int, &body_zerocount }, diff --git a/src/src/functions.h b/src/src/functions.h index 0825b1b57..a29dddbed 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -218,7 +218,7 @@ extern void dscp_list_to_stream(FILE *); extern BOOL dscp_lookup(const uschar *, int, int *, int *, int *); extern void enq_end(uschar *); -extern BOOL enq_start(uschar *, unsigned); +extern unsigned enq_start(uschar *, unsigned); #ifndef DISABLE_EVENT extern uschar *event_raise(const uschar *, const uschar *, const uschar *, int *); extern void msg_event_raise(const uschar *, const address_item *); @@ -319,6 +319,7 @@ extern void log_write_die(unsigned, int, const char * format, ...) PRINTF_FUNCTION(3,4) NORETURN; extern const lookup_info * lookup_with_acq_num(unsigned); +extern gstring *lookup_dynamic_supported(gstring *); #ifdef LOOKUP_MODULE_DIR extern BOOL lookup_one_mod_load(const uschar *, uschar **); #endif @@ -601,11 +602,11 @@ extern const uschar *string_printing2(const uschar *, int); extern uschar *string_split_message(uschar *); extern uschar *string_unprinting(uschar *); #ifdef SUPPORT_I18N -extern uschar *string_address_utf8_to_alabel(const uschar *, uschar **); +extern const uschar *string_address_utf8_to_alabel(const uschar *, uschar **); extern uschar *string_domain_alabel_to_utf8(const uschar *, uschar **); -extern uschar *string_domain_utf8_to_alabel(const uschar *, uschar **); +extern const uschar *string_domain_utf8_to_alabel(const uschar *, uschar **); extern uschar *string_localpart_alabel_to_utf8(const uschar *, uschar **); -extern uschar *string_localpart_utf8_to_alabel(const uschar *, uschar **); +extern const uschar *string_localpart_utf8_to_alabel(const uschar *, uschar **); #endif #define string_format(buf, siz, fmt, ...) \ @@ -695,7 +696,6 @@ extern int verify_check_given_host(const uschar **, const host_item *); extern int verify_check_this_host(const uschar **, unsigned int *, const uschar*, const uschar *, const uschar **); extern address_item *verify_checked_sender(const uschar *); -extern void verify_get_ident(int); extern void verify_quota(uschar *); extern int verify_quota_call(const uschar *, int, int, uschar **); extern BOOL verify_sender(int *, uschar **); diff --git a/src/src/globals.c b/src/src/globals.c index 3eda97628..be2b7ec13 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -58,10 +58,6 @@ BOOL opt_perl_taintmode = FALSE; tree_node *dlobj_anchor = NULL; #endif -#ifdef LOOKUP_IBASE -uschar *ibase_servers = NULL; -#endif - #ifdef LOOKUP_LDAP uschar *eldap_ca_cert_dir = NULL; uschar *eldap_ca_cert_file = NULL; @@ -265,6 +261,7 @@ struct global_flags f = .enable_dollar_recipients = FALSE, .expand_string_forcedfail = FALSE, + .expansion_test = FALSE, .filter_running = FALSE, @@ -366,7 +363,6 @@ BOOL disable_fsync = FALSE; #endif BOOL disable_ipv6 = FALSE; BOOL dns_csa_use_reverse = TRUE; -BOOL drop_cr = FALSE; /* No longer used */ BOOL envelope_to_remove = TRUE; BOOL exim_gid_set = TRUE; /* This gid is always set */ @@ -585,15 +581,6 @@ uschar *base62_chars= US"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; uschar *bi_command = NULL; uschar *big_buffer = NULL; int big_buffer_size = BIG_BUFFER_SIZE; -#ifdef EXPERIMENTAL_BRIGHTMAIL -uschar *bmi_alt_location = NULL; -uschar *bmi_base64_tracker_verdict = NULL; -uschar *bmi_base64_verdict = NULL; -uschar *bmi_config_file = US"/opt/brightmail/etc/brightmail.cfg"; -int bmi_deliver = 1; -int bmi_run = 0; -uschar *bmi_verdicts = NULL; -#endif int bsmtp_transaction_linecount = 0; int body_8bitmime = 0; int body_linecount = 0; @@ -720,7 +707,6 @@ bit_table debug_options[] = { /* must be in alphabetical order and use BIT_TABLE(D, filter), BIT_TABLE(D, hints_lookup), BIT_TABLE(D, host_lookup), - BIT_TABLE(D, ident), BIT_TABLE(D, interface), BIT_TABLE(D, lists), BIT_TABLE(D, load), @@ -1003,7 +989,6 @@ bit_table log_options[] = { /* must be in alphabetical order, BIT_TABLE(L, dnssec), BIT_TABLE(L, etrn), BIT_TABLE(L, host_lookup_failed), - BIT_TABLE(L, ident_timeout), BIT_TABLE(L, incoming_interface), BIT_TABLE(L, incoming_port), BIT_TABLE(L, lost_incoming_connection), @@ -1245,7 +1230,7 @@ const pcre2_code *regex_whitelisted_macro = NULL; uschar *regex_match_string = NULL; #endif int remote_delivery_count = 0; -int remote_max_parallel = 4; +int remote_max_parallel = 6; uschar *remote_sort_domains = NULL; int retry_data_expire = 7*24*60*60; int retry_interval_max = 24*60*60; @@ -1253,8 +1238,6 @@ int retry_maximum_timeout = 0; /* set from retry config */ retry_config *retries = NULL; const uschar *return_path = NULL; int rewrite_existflags = 0; -uschar *rfc1413_hosts = US"@[]"; -int rfc1413_query_timeout = 0; uid_t root_gid = ROOT_GID; uid_t root_uid = ROOT_UID; @@ -1416,7 +1399,6 @@ uid_t system_filter_uid = (uid_t)-1; blob tcp_fastopen_nodata = { .data = NULL, .len = 0 }; tfo_state_t tcp_out_fastopen = TFO_NOT_USED; -int test_harness_identd_port = IDENT_PORT; int test_harness_load_avg = 0; int thismessage_size_limit = 0; int timeout_frozen_after = 0; diff --git a/src/src/globals.h b/src/src/globals.h index cb6b91c5a..ccd790cc2 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -40,10 +40,6 @@ extern BOOL opt_perl_taintmode; /* Enable taint mode in Perl */ extern tree_node *dlobj_anchor; /* Tree of dynamically-loaded objects */ #endif -#ifdef LOOKUP_IBASE -extern uschar *ibase_servers; -#endif - #ifdef LOOKUP_LDAP extern uschar *eldap_ca_cert_dir; /* Directory with CA certificates */ extern uschar *eldap_ca_cert_file; /* CA certificate file */ @@ -123,6 +119,7 @@ typedef struct { BOOL channelbind_exporter:1; /* channelbinding is EXPORTER not UNIQUE */ BOOL on_connect:1; /* For older MTAs that don't STARTTLS */ BOOL verify_override:1; /* certificate_verified only due to tls_try_verify_hosts */ + BOOL smtp_quit:1; /* QUIT has been sent or received */ } tls_support; extern tls_support tls_in; extern tls_support tls_out; @@ -234,6 +231,7 @@ extern struct global_flags { BOOL enable_dollar_recipients :1; /* Make $recipients available */ BOOL expand_string_forcedfail :1; /* TRUE if failure was "expected" */ + BOOL expansion_test :1; /* TRUE for -be test mode */ BOOL filter_running :1; /* TRUE while running a filter */ @@ -392,19 +390,19 @@ extern int av_failed; /* TRUE if the AV process failed */ extern uschar *av_scanner; /* AntiVirus scanner to use for the malware condition */ #endif +extern lookup_module_info * avail_static_lookups[];/* List of built-in lookup drivers */ + +extern const uschar * avail_static_auths[]; /* List of built-in driver names */ +extern const uschar * avail_dynamic_auths[]; /* List of dynamic-load driver names */ +extern const uschar * avail_static_routers[]; +extern const uschar * avail_dynamic_routers[]; +extern const uschar * avail_static_transports[]; +extern const uschar * avail_dynamic_transports[]; + extern uschar *base62_chars; /* Table of base-62 characters */ extern uschar *bi_command; /* Command for -bi option */ extern uschar *big_buffer; /* Used for various temp things */ extern int big_buffer_size; /* Current size (can expand) */ -#ifdef EXPERIMENTAL_BRIGHTMAIL -extern uschar *bmi_alt_location; /* expansion variable that contains the alternate location for the rcpt (available during routing) */ -extern uschar *bmi_base64_tracker_verdict; /* expansion variable with base-64 encoded OLD verdict string (available during routing) */ -extern uschar *bmi_base64_verdict; /* expansion variable with base-64 encoded verdict string (available during routing) */ -extern uschar *bmi_config_file; /* Brightmail config file */ -extern int bmi_deliver; /* Flag that determines if the message should be delivered to the rcpt (available during routing) */ -extern int bmi_run; /* Flag that determines if message should be run through Brightmail server */ -extern uschar *bmi_verdicts; /* BASE64-encoded verdicts with recipient lists */ -#endif extern int bsmtp_transaction_linecount; /* Start of last transaction */ extern int body_8bitmime; /* sender declared BODY= ; 7=7BIT, 8=8BITMIME */ extern uschar *bounce_message_file; /* Template file */ @@ -575,8 +573,6 @@ extern uschar *dnslist_value; /* DNS (black) list IP address */ extern tree_node *domainlist_anchor; /* Tree of defined domain lists */ extern int domainlist_count; /* Number defined */ -/* This option is now a no-opt, retained for compatibility */ -extern BOOL drop_cr; /* For broken local MUAs */ extern const uschar *driver_srcfile; /* For debug & errors */ extern int driver_srcline; /* For debug & errors */ @@ -923,8 +919,6 @@ extern int retry_maximum_timeout; /* The maximum timeout */ extern const uschar *return_path; /* Return path for a message */ extern BOOL return_path_remove; /* Remove return-path headers */ extern int rewrite_existflags; /* Indicate which headers have rewrites */ -extern uschar *rfc1413_hosts; /* RFC hosts */ -extern int rfc1413_query_timeout; /* Timeout on RFC 1413 calls */ /* extern BOOL rfc821_domains; */ /* If set, syntax is 821, not 822 => being abolished */ extern uid_t root_gid; /* The gid for root */ extern uid_t root_uid; /* The uid for root */ @@ -1065,7 +1059,6 @@ extern BOOL system_filter_uid_set; /* TRUE if uid set */ extern blob tcp_fastopen_nodata; /* for zero-data TFO connect requests */ extern BOOL tcp_nodelay; /* Controls TCP_NODELAY on daemon */ extern tfo_state_t tcp_out_fastopen; /* TCP fast open */ -extern int test_harness_identd_port; /* For use when testing */ extern int test_harness_load_avg; /* For use when testing */ extern int thismessage_size_limit; /* Limit for this message */ extern int timeout_frozen_after; /* Max time to keep frozen messages */ diff --git a/src/src/local_scan.h b/src/src/local_scan.h index a97fb4612..355487b0d 100644 --- a/src/src/local_scan.h +++ b/src/src/local_scan.h @@ -160,9 +160,6 @@ typedef struct recipient_item { const uschar *errors_to; /* the errors_to address or NULL */ uschar *orcpt; /* DSN orcpt */ int dsn_flags; /* DSN flags */ -#ifdef EXPERIMENTAL_BRIGHTMAIL - uschar *bmi_optin; -#endif } recipient_item; diff --git a/src/src/lookupapi.h b/src/src/lookupapi.h index 2e857c5d3..b8ec17880 100644 --- a/src/src/lookupapi.h +++ b/src/src/lookupapi.h @@ -18,9 +18,10 @@ */ typedef struct lookup_info { - uschar *name; /* e.g. "lsearch" */ - int type; /* query/singlekey/abs-file */ - unsigned acq_num; /* acquisition number */ + uschar * name; /* e.g. "lsearch" */ + int type; /* query/singlekey/abs-file */ + unsigned acq_num; /* acquisition number */ + void *(*open)( /* open function */ const uschar *, /* file name for those that have one */ uschar **); /* for error message */ @@ -60,9 +61,9 @@ typedef struct lookup_info { /* Version 5 change: version report now adds to a gstring */ typedef struct lookup_module_info { - uint magic; + uint magic; lookup_info **lookups; - uint lookupcount; + uint lookupcount; } lookup_module_info; /* End of lookupapi.h */ diff --git a/src/src/lookups/Makefile b/src/src/lookups/Makefile index 74e705e37..d794fe895 100644 --- a/src/src/lookups/Makefile +++ b/src/src/lookups/Makefile @@ -8,17 +8,38 @@ # extra variable definitions and prepended to it and module build rules # interpolated below. This is done by scripts/lookups-Makefile. -# When adding a new driver here, attend also to scripts/lookups-Makefile -# and scripts/MakeLinks +# When adding a new driver here, attend also to scripts/lookups-Makefile, +# and scripts/MakeLinks. # MAGIC-TAG-MODS-OBJ-RULES-GO-HERE +OBJS = $(OBJ) $(OBJ_ALWAYS) + +# This assumes that the driver and object-file names match +AVAIL= $(OBJS:.o=) + all: Makefile lookups.a $(MODS) -lookups.a: $(OBJ) +# We assume here that the driver name is used as the prefix for +# its module_info struct. +avail_static_lookups.c: Makefile + @echo "make $@" + $(FE)echo '/* File built by lookups/Makefile */' >$@ + $(FE)echo '#include "../exim.h"' >>$@ + $(FE)for f in $(AVAIL); do \ + echo "extern lookup_module_info $${f}_lookup_module_info;" >>$@; \ + done + $(FE)echo 'lookup_module_info * avail_static_lookups[] = {' >>$@ + $(FE)for f in $(AVAIL); do \ + echo "& $${f}_lookup_module_info," >>$@; \ + done + $(FE)echo 'NULL };' >>$@ +avail_static_lookups.o: $(HDRS) avail_static_lookups.c + +lookups.a: avail_static_lookups.o $(OBJS) @$(RM_COMMAND) -f lookups.a @echo "$(AR) lookups.a" - @$(AR) lookups.a $(OBJ) + @$(AR) lookups.a avail_static_lookups.o $(OBJS) $(RANLIB) $@ .SUFFIXES: .o .c .so @@ -32,7 +53,6 @@ cdb.o cdb.so: $(HDRS) cdb.c dbmdb.o dbmdb.so: $(HDRS) dbmdb.c dnsdb.o dnsdb.so: $(HDRS) dnsdb.c dsearch.o dsearch.so: $(HDRS) dsearch.c -ibase.o ibase.so: $(HDRS) ibase.c ldap.o ldap.so: $(HDRS) ldap.c lmdb.o lmdb.so: $(HDRS) lmdb.c json.o json.so: $(HDRS) json.c @@ -44,6 +64,7 @@ nmh.o nmh.so: $(HDRS) nmh.c oracle.o oracle.so: $(HDRS) oracle.c passwd.o passwd.so: $(HDRS) passwd.c pgsql.o pgsql.so: $(HDRS) pgsql.c +psl.o psl.so: $(HDRS) psl.c readsock.o readsock.so: $(HDRS) readsock.c redis.o redis.so: $(HDRS) redis.c spf.o spf.so: $(HDRS) spf.c diff --git a/src/src/lookups/ibase.c b/src/src/lookups/ibase.c deleted file mode 100644 index 141681aab..000000000 --- a/src/src/lookups/ibase.c +++ /dev/null @@ -1,550 +0,0 @@ -/************************************************* -* Exim - an Internet mail transport agent * -*************************************************/ - -/* Copyright (c) The Exim Maintainers 2020 - 2025 */ -/* Copyright (c) University of Cambridge 1995 - 2018 */ -/* See the file NOTICE for conditions of use and distribution. */ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/* The code in this module was contributed by Ard Biesheuvel. */ - -#include "../exim.h" -#include "lf_functions.h" - -#include /* The system header */ - -/* Structure and anchor for caching connections. */ - -typedef struct ibase_connection { - struct ibase_connection *next; - uschar *server; - isc_db_handle dbh; - isc_tr_handle transh; -} ibase_connection; - -static ibase_connection *ibase_connections = NULL; - - -#if defined(_LP64) || defined(__LP64__) || defined(__arch64__) || defined(_WIN64) -# define ISC_NULL 0 -#else -# define ISC_NULL NULL -#endif - -/************************************************* -* Open entry point * -*************************************************/ - -/* See local README for interface description. */ - -static void *ibase_open(const uschar * filename, uschar ** errmsg) -{ -return (void *) (1); /* Just return something non-null */ -} - - - -/************************************************* -* Tidy entry point * -*************************************************/ - -/* See local README for interface description. */ - -static void -ibase_tidy(void) -{ -ibase_connection *cn; -ISC_STATUS status[20]; - -while ((cn = ibase_connections)) - { - ibase_connections = cn->next; - DEBUG(D_lookup) debug_printf_indent("close Interbase connection: %s\n", - cn->server); - isc_commit_transaction(status, &cn->transh); - isc_detach_database(status, &cn->dbh); - } -} - -static int -fetch_field(uschar * buffer, int buffer_size, XSQLVAR * var) -{ -if (buffer_size < var->sqllen) - return 0; - -switch (var->sqltype & ~1) - { - case SQL_VARYING: - strncpy(CS buffer, &var->sqldata[2], *(short *) var->sqldata); - return *(short *) var->sqldata; - case SQL_TEXT: - strncpy(CS buffer, var->sqldata, var->sqllen); - return var->sqllen; - case SQL_SHORT: - return sprintf(CS buffer, "%d", *(short *) var->sqldata); - case SQL_LONG: - return sprintf(CS buffer, "%ld", *(ISC_LONG *) var->sqldata); - #ifdef SQL_INT64 - case SQL_INT64: - return sprintf(CS buffer, "%lld", *(ISC_INT64 *) var->sqldata); - #endif - default: - /* not implemented */ - return 0; - } -} - -/************************************************* -* Internal search function * -*************************************************/ - -/* This function is called from the find entry point to do the search for a -single server. - -Arguments: - query the query string - server the server string - resultptr where to store the result - errmsg where to point an error message - defer_break TRUE if no more servers are to be tried after DEFER - -The server string is of the form "host:dbname|user|password". The host can be -host:port. This string is in a nextinlist temporary buffer, so can be -overwritten. - -Returns: OK, FAIL, or DEFER -*/ - -static int -perform_ibase_search(const uschar * query, uschar * server, uschar ** resultptr, - uschar ** errmsg, BOOL * defer_break) -{ -isc_stmt_handle stmth; -XSQLDA *out_sqlda; -XSQLVAR *var; -int i; -rmark reset_point; - -uschar buffer[256]; -ISC_STATUS status[20], *statusp = status; - -gstring * result = NULL; -int yield = DEFER; -ibase_connection *cn; -uschar *server_copy = NULL; -uschar *sdata[3]; - -/* Disaggregate the parameters from the server argument. The order is host, -database, user, password. We can write to the string, since it is in a -nextinlist temporary buffer. The copy of the string that is used for caching -has the password removed. This copy is also used for debugging output. */ - -for (int i = 2; i > 0; i--) - { - uschar * pp = Ustrrchr(server, '|'); - - if (!pp) - { - *errmsg = string_sprintf("incomplete Interbase server data: %s", - i == 3 ? server : server_copy); - *defer_break = TRUE; - return DEFER; - } - *pp++ = 0; - sdata[i] = pp; - if (i == 2) - server_copy = string_copy(server); /* sans password */ - } -sdata[0] = server; /* What's left at the start */ - -/* See if we have a cached connection to the server */ - -for (cn = ibase_connections; cn; cn = cn->next) - if (Ustrcmp(cn->server, server_copy) == 0) - break; - -/* Use a previously cached connection ? */ - -if (cn) - { - static char db_info_options[] = { isc_info_base_level }; - - /* test if the connection is alive */ - if (isc_database_info(status, &cn->dbh, sizeof(db_info_options), - db_info_options, sizeof(buffer), CS buffer)) - { - /* error occurred: assume connection is down */ - DEBUG(D_lookup) - debug_printf("Interbase cleaning up cached connection: %s\n", cn->server); - isc_detach_database(status, &cn->dbh); - } - else - DEBUG(D_lookup) - debug_printf_indent("Interbase using cached connection for %s\n", - server_copy); - } -else - { - cn = store_get(sizeof(ibase_connection), GET_UNTAINTED); - cn->server = server_copy; - cn->dbh = ISC_NULL; - cn->transh = ISC_NULL; - cn->next = ibase_connections; - ibase_connections = cn; - } - -/* If no cached connection, we must set one up. */ - -if (!cn->dbh || !cn->transh) - { - uschar * dpb; - short dpb_length; - static char trans_options[] = - { isc_tpb_version3, isc_tpb_read, isc_tpb_read_committed, - isc_tpb_rec_version }; - - /* Construct the database parameter buffer. */ - dpb = buffer; - *dpb++ = isc_dpb_version1; - *dpb++ = isc_dpb_user_name; - *dpb++ = Ustrlen(sdata[1]); - for (uschar * p = sdata[1]; *p;) *dpb++ = *p++; - *dpb++ = isc_dpb_password; - *dpb++ = Ustrlen(sdata[2]); - for (uschar * p = sdata[2]; *p;) *dpb++ = *p++; - dpb_length = dpb - buffer; - - DEBUG(D_lookup) - debug_printf_indent("new Interbase connection: database=%s user=%s\n", - sdata[0], sdata[1]); - - /* Connect to the database */ - if (isc_attach_database(status, 0, CS sdata[0], &cn->dbh, - dpb_length, CS buffer)) - { - isc_interprete(CS buffer, &statusp); - *errmsg = string_sprintf("Interbase attach() failed: %s", buffer); - *defer_break = FALSE; - goto IBASE_EXIT; - } - - /* Now start a read-only read-committed transaction */ - if (isc_start_transaction(status, &cn->transh, 1, &cn->dbh, - sizeof(trans_options), trans_options)) - { - isc_interprete(CS buffer, &statusp); - isc_detach_database(status, &cn->dbh); - *errmsg = string_sprintf("Interbase start_transaction() failed: %s", - buffer); - *defer_break = FALSE; - goto IBASE_EXIT; - } - } - -/* Run the query */ -if (isc_dsql_allocate_statement(status, &cn->dbh, &stmth)) - { - isc_interprete(CS buffer, &statusp); - *errmsg = string_sprintf("Interbase alloc_statement() failed: %s", buffer); - *defer_break = FALSE; - goto IBASE_EXIT; - } - -/* Lacking any information, assume that the data is untainted */ -reset_point = store_mark(); -out_sqlda = store_get(XSQLDA_LENGTH(1), GET_UNTAINTED); -out_sqlda->version = SQLDA_VERSION1; -out_sqlda->sqln = 1; - -if (isc_dsql_prepare(status, &cn->transh, &stmth, 0, CCS query, 1, out_sqlda)) - { - isc_interprete(CS buffer, &statusp); - reset_point = store_reset(reset_point); - out_sqlda = NULL; - *errmsg = string_sprintf("Interbase prepare_statement() failed: %s", buffer); - *defer_break = FALSE; - goto IBASE_EXIT; - } - -/* re-allocate the output structure if there's more than one field */ -if (out_sqlda->sqln < out_sqlda->sqld) - { - XSQLDA *new_sqlda = store_get(XSQLDA_LENGTH(out_sqlda->sqld), GET_UNTAINTED); - if (isc_dsql_describe - (status, &stmth, out_sqlda->version, new_sqlda)) - { - isc_interprete(CS buffer, &statusp); - isc_dsql_free_statement(status, &stmth, DSQL_drop); - reset_point = store_reset(reset_point); - out_sqlda = NULL; - *errmsg = string_sprintf("Interbase describe_statement() failed: %s", - buffer); - *defer_break = FALSE; - goto IBASE_EXIT; - } - out_sqlda = new_sqlda; - } - -/* allocate storage for every returned field */ -for (i = 0, var = out_sqlda->sqlvar; i < out_sqlda->sqld; i++, var++) - { - switch (var->sqltype & ~1) - { - case SQL_VARYING: - var->sqldata = CS store_get(sizeof(char) * var->sqllen + 2, GET_UNTAINTED); - break; - case SQL_TEXT: - var->sqldata = CS store_get(sizeof(char) * var->sqllen, GET_UNTAINTED); - break; - case SQL_SHORT: - var->sqldata = CS store_get(sizeof(short), GET_UNTAINTED); - break; - case SQL_LONG: - var->sqldata = CS store_get(sizeof(ISC_LONG), GET_UNTAINTED); - break; -#ifdef SQL_INT64 - case SQL_INT64: - var->sqldata = CS store_get(sizeof(ISC_INT64), GET_UNTAINTED); - break; -#endif - case SQL_FLOAT: - var->sqldata = CS store_get(sizeof(float), GET_UNTAINTED); - break; - case SQL_DOUBLE: - var->sqldata = CS store_get(sizeof(double), GET_UNTAINTED); - break; -#ifdef SQL_TIMESTAMP - case SQL_DATE: - var->sqldata = CS store_get(sizeof(ISC_QUAD), GET_UNTAINTED); - break; -#else - case SQL_TIMESTAMP: - var->sqldata = CS store_get(sizeof(ISC_TIMESTAMP), GET_UNTAINTED); - break; - case SQL_TYPE_DATE: - var->sqldata = CS store_get(sizeof(ISC_DATE), GET_UNTAINTED); - break; - case SQL_TYPE_TIME: - var->sqldata = CS store_get(sizeof(ISC_TIME), GET_UNTAINTED); - break; - #endif - } - if (var->sqltype & 1) - var->sqlind = (short *) store_get(sizeof(short), GET_UNTAINTED); - } - -/* finally, we're ready to execute the statement */ -if (isc_dsql_execute(status, &cn->transh, &stmth, out_sqlda->version, NULL)) - { - isc_interprete(CS buffer, &statusp); - *errmsg = string_sprintf("Interbase describe_statement() failed: %s", buffer); - isc_dsql_free_statement(status, &stmth, DSQL_drop); - *defer_break = FALSE; - goto IBASE_EXIT; - } - -while (isc_dsql_fetch(status, &stmth, out_sqlda->version, out_sqlda) != 100L) - { - /* check if an error occurred */ - if (status[0] & status[1]) - { - isc_interprete(CS buffer, &statusp); - *errmsg = string_sprintf("Interbase fetch() failed: %s", buffer); - isc_dsql_free_statement(status, &stmth, DSQL_drop); - *defer_break = FALSE; - goto IBASE_EXIT; - } - - if (result) - result = string_catn(result, US "\n", 1); - - /* Find the number of fields returned. If this is one, we don't add field - names to the data. Otherwise we do. */ - if (out_sqlda->sqld == 1) - { - if (out_sqlda->sqlvar[0].sqlind == NULL || *out_sqlda->sqlvar[0].sqlind != -1) /* NULL value yields nothing */ - result = string_catn(result, US buffer, - fetch_field(buffer, sizeof(buffer), &out_sqlda->sqlvar[0])); - } - - else - for (int i = 0; i < out_sqlda->sqld; i++) - { - int len = fetch_field(buffer, sizeof(buffer), &out_sqlda->sqlvar[i]); - - result = string_catn(result, US out_sqlda->sqlvar[i].aliasname, - out_sqlda->sqlvar[i].aliasname_length); - result = string_catn(result, US "=", 1); - - /* Quote the value if it contains spaces or is empty */ - - if (*out_sqlda->sqlvar[i].sqlind == -1) /* NULL value */ - result = string_catn(result, US "\"\"", 2); - - else if (buffer[0] == 0 || Ustrchr(buffer, ' ') != NULL) - { - result = string_catn(result, US "\"", 1); - for (int j = 0; j < len; j++) - { - if (buffer[j] == '\"' || buffer[j] == '\\') - result = string_catn(result, US "\\", 1); - result = string_catn(result, US buffer + j, 1); - } - result = string_catn(result, US "\"", 1); - } - else - result = string_catn(result, US buffer, len); - result = string_catn(result, US " ", 1); - } - } - -/* If result is NULL then no data has been found and so we return FAIL. -Otherwise, we must terminate the string which has been built; string_cat() -always leaves enough room for a terminating zero. */ - -if (!result) - { - yield = FAIL; - *errmsg = US "Interbase: no data found"; - } -else - gstring_release_unused(result); - - -/* Get here by goto from various error checks. */ - -IBASE_EXIT: - -if (stmth) - isc_dsql_free_statement(status, &stmth, DSQL_drop); - -/* Non-NULL result indicates a successful result */ - -if (result) - { - *resultptr = string_from_gstring(result); - return OK; - } -else - { - DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg); - return yield; /* FAIL or DEFER */ - } -} - - - - -/************************************************* -* Find entry point * -*************************************************/ - -/* See local README for interface description. The handle and filename -arguments are not used. Loop through a list of servers while the query is -deferred with a retryable error. */ - -static int -ibase_find(void * handle, const uschar * filename, const uschar * query, - int length, uschar ** result, uschar ** errmsg, uint * do_cache, - const uschar * opts) -{ -uschar * server; -const uschar * list = ibase_servers; - -DEBUG(D_lookup) debug_printf_indent("Interbase query: %s\n", query); - -for (int sep = 0; server = string_nextinlist(&list, &sep, NULL, 0); ) - { - BOOL defer_break = FALSE; - int rc = perform_ibase_search(query, server, result, errmsg, &defer_break); - if (rc != DEFER || defer_break) - return rc; - } - -if (!ibase_servers) - *errmsg = US "no Interbase servers defined (ibase_servers option)"; - -return DEFER; -} - - - -/************************************************* -* Quote entry point * -*************************************************/ - -/* The only characters that need to be quoted (with backslash) are newline, -tab, carriage return, backspace, backslash itself, and the quote characters. -Percent, and underscore and not escaped. They are only special in contexts -where they can be wild cards, and this isn't usually the case for data inserted -from messages, since that isn't likely to be treated as a pattern of any kind. -Sadly, MySQL doesn't seem to behave like other programs. If you use something -like "where id="ab\%cd" it does not treat the string as "ab%cd". So you really -can't quote "on spec". - -Arguments: - s the string to be quoted - opt additional option text or NULL if none - idx lookup type index - -Returns: the processed string or NULL for a bad option -*/ - -static uschar * -ibase_quote(uschar * s, const uschar * opt, unsigned idx) -{ -gstring * quoted = store_get_quoted(1, s, idx, US"ibase"); - -if (opt) - return NULL; /* No options recognized */ - -for (uschar c; c = *s; s++) - { - if (c == '\'') quoted = string_catn(quoted, US"\\", 1); - quoted = string_catn(quoted, s, 1); - } -gstring_release_unused(quoted); -return(string_from_gstring(quoted)); -} - - - -/************************************************* -* Version reporting entry point * -*************************************************/ - -/* See local README for interface description. */ - -#include "../version.h" - -gstring * -ibase_version_report(gstring * g) -{ -#ifdef DYNLOOKUP -g = string_fmt_append(g, "Library version: ibase: Exim version %s\n", EXIM_VERSION_STR)); -#endif -return g; -} - - -static lookup_info _lookup_info = { - .name = US"ibase", /* lookup name */ - .type = lookup_querystyle, /* query-style lookup */ - .open = ibase_open, /* open function */ - .check = NULL, /* no check function */ - .find = ibase_find, /* find function */ - .close = NULL, /* no close function */ - .tidy = ibase_tidy, /* tidy function */ - .quote = ibase_quote, /* quoting function */ - .version_report = ibase_version_report /* version reporting */ -}; - -#ifdef DYNLOOKUP -#define ibase_lookup_module_info _lookup_module_info -#endif - -static lookup_info *_lookup_list[] = { &_lookup_info }; -lookup_module_info ibase_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 }; - -/* End of lookups/ibase.c */ diff --git a/src/src/lookups/ldap.c b/src/src/lookups/ldap.c index 624b947d6..a6e99f567 100644 --- a/src/src/lookups/ldap.c +++ b/src/src/lookups/ldap.c @@ -1580,7 +1580,7 @@ static lookup_info ldap_lookup_info = { .close = NULL, /* no close function */ .tidy = eldap_tidy, /* tidy function */ .quote = eldap_quote, /* quoting function */ - .version_report = ldap_version_report /* version reporting */ + .version_report = ldap_version_report /* version reporting */ }; static lookup_info ldapdn_lookup_info = { @@ -1592,7 +1592,7 @@ static lookup_info ldapdn_lookup_info = { .close = NULL, /* no close function */ .tidy = eldap_tidy, /* sic */ /* tidy function */ .quote = eldap_quote, /* sic */ /* quoting function */ - .version_report = NULL /* no version reporting (redundant) */ + .version_report = NULL /* no version reporting (redundant) */ }; static lookup_info ldapm_lookup_info = { @@ -1604,7 +1604,7 @@ static lookup_info ldapm_lookup_info = { .close = NULL, /* no close function */ .tidy = eldap_tidy, /* sic */ /* tidy function */ .quote = eldap_quote, /* sic */ /* quoting function */ - .version_report = NULL /* no version reporting (redundant) */ + .version_report = NULL /* no version reporting (redundant) */ }; static lookup_info ldapauth_lookup_info = { @@ -1616,7 +1616,7 @@ static lookup_info ldapauth_lookup_info = { .close = NULL, /* no close function */ .tidy = eldap_tidy, /* sic */ /* tidy function */ .quote = NULL, /* NO quoting function */ - .version_report = NULL /* no version reporting (redundant) */ + .version_report = NULL /* no version reporting (redundant) */ }; #ifdef DYNLOOKUP diff --git a/src/src/lookups/lsearch.c b/src/src/lookups/lsearch.c index c85cda5d5..89a9e6c80 100644 --- a/src/src/lookups/lsearch.c +++ b/src/src/lookups/lsearch.c @@ -474,7 +474,7 @@ static lookup_info wildlsearch_lookup_info = { }; #ifdef DYNLOOKUP -#define lsearch_lookup_module_info _lookup_module_info +# define lsearch_lookup_module_info _lookup_module_info #endif static lookup_info *_lookup_list[] = { &iplsearch_lookup_info, diff --git a/src/src/lookups/psl.c b/src/src/lookups/psl.c new file mode 100644 index 000000000..1592fe32d --- /dev/null +++ b/src/src/lookups/psl.c @@ -0,0 +1,252 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2025 */ +/* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "../exim.h" + +#ifndef SUPPORT_I18N +# error PSL lookup requires internationalisation support +#endif + + +/************************************************* +* Open entry point * +*************************************************/ + +/* See local README for interface description */ + +static void * +psl_open(const uschar * filename, uschar ** errmsg) +{ +FILE * f = fopen(CCS filename, "r"); +if (f) return (void *) f; +*errmsg = US strerror(errno); +return NULL; +} + +static void +psl_close(void * handle) +{ +(void) fclose(handle); +} + + + + +/************************************************* +* Generic "find" implementation * +*************************************************/ + +static int +psl_gen_find(void * handle, const uschar * keystring, + int length, uschar ** result, uschar ** errmsg, BOOL is_regdom) +{ +uschar rulebuf[128], * res = NULL; +const uschar * s, * k, * kmatch; +unsigned res_label_cnt = 0, nlabels; +BOOL key_utf8; + +/* Ensure key is punycode and lowercase */ + +if ((key_utf8 = string_is_utf8(keystring))) + { + DEBUG(D_lookup) debug_printf_indent("converting utf8 key %q\n", keystring); + if (!(keystring = string_domain_utf8_to_alabel(keystring, errmsg))) + return FAIL; + length = Ustrlen(keystring); + DEBUG(D_lookup) debug_printf_indent(" result %q\n", keystring); + } +else + for (k = keystring; *k; k++) + if (isupper(*k)) { keystring = string_copylc(keystring); break; } + +while ((s = US fgets(CS rulebuf, sizeof(rulebuf), handle))) + { + const uschar * r; + + if (!*s || *s == '\n') continue; /* empty line */ + if (s[0] == '/' && s[1] == '/') continue; /* comment line */ + + nlabels = 1; + if ((r = US strsep(CSS &s, " \n\t"))) + { + BOOL exception = *r == '!'; + const uschar * t; + + /* We convert any utf8 to punycode before starting comparison. It might + be more efficient to wait until hitting a top-bit-set byte? */ + if (!(t = string_domain_utf8_to_alabel(r, errmsg))) + goto fail; + if (t != r) + { + DEBUG(D_lookup) debug_printf_indent("converting utf8 psl entry %q\n" + " result %q\n", r, t); + r = t; + } + + for (s = r + Ustrlen(r), k = keystring + length; ; ) + { + uschar rch = s[-1]; + + if (rch == '.') /* label separator */ + nlabels++; + if (rch == '*') /* wildcard in rule (assume leading) */ + { + /* take the current label from the key */ + while (k > keystring && k[-1] != '.') + k--; + s = k; + /* s is the match */ + /* k is the key trail after the regdom. label */ + /* nlabels describes k */ + break; /* - match */ + } + if (rch == '!') /* exception rule */ + { + if (!is_regdom) + /* Remove the LH label then treat as a match */ + while (TRUE) + if (!*s || *s++ == '.') break; + + /* s is the match, or the regdom */ + /* nlabels don't care */ + res = string_copy_taint(s, GET_UNTAINTED); + goto found; /* ER's have priority; stop reading file */ + } + + s--; k--; + + if (rch != *k) /* character difference */ + goto nonmatch; + if (k <= keystring && !exception) /* ran out of key */ + goto nonmatch; + if (s == r) /* run out of rule */ + if (k[-1] != '.') /* key has prefix on rule */ + goto nonmatch; + else + break; /* out of rule: s is the match */ + } + + if (nlabels > res_label_cnt) + { /* new longest match (by cnt of labels) */ + res = string_copy_taint(s, GET_UNTAINTED); + res_label_cnt = nlabels; + kmatch = k; + } + + nonmatch: + } + } + /* kmatch is the key trail after the regdom. label */ + /* res_label_cnt describes kmatch */ + +if (is_regdom && res) /* prepend label from key to pub-suffix */ + { + s = kmatch; + /* back up one label in key */ + if (s-- <= keystring) goto fail; /* there must ba a dot */ + if (s-- <= keystring) goto fail; /* there must ba at least one ch */ + while (s > keystring && s[-1] != '.') s--; + res = string_sprintf("%.*s%s", (int)(kmatch - s), s, res); + } + +found: + +if (key_utf8 && res) + { + if (!(*result = string_domain_alabel_to_utf8(res, errmsg))) + goto fail; + DEBUG(D_lookup) + debug_printf_indent("utf8 converting result %q\n to %q\n", res, *result); + } +else + *result = res; + +rewind(handle); +return OK; + +fail: +rewind(handle); +return FAIL; +} + + + +/************************************************* +* Find entry points for pub-suffix and regdom * +*************************************************/ + +/* See local README for interface description */ + +static int +psl_find(void * handle, const uschar * filename, const uschar * keystring, + int length, uschar ** result, uschar ** errmsg, uint * do_cache, + const uschar * opts) +{ +return psl_gen_find(handle, keystring, length, result, errmsg, FALSE); +} + +static int +regdom_find(void * handle, const uschar * filename, const uschar * keystring, + int length, uschar ** result, uschar ** errmsg, uint * do_cache, + const uschar * opts) +{ +return psl_gen_find(handle, keystring, length, result, errmsg, TRUE); +} + + + + +/************************************************* +* Version reporting entry point * +*************************************************/ + +/* See local README for interface description. */ + +#include "../version.h" + +gstring * +psl_version_report(gstring * g) +{ +#ifdef DYNLOOKUP +g = string_fmt_append(g, "Library version: psl: Exim version %s\n", EXIM_VERSION_STR); +#endif +return g; +} + +static lookup_info psl_lookup_info = { + .name = US"psl", /* lookup name */ + .type = lookup_absfile, /* lookup from file */ + .open = psl_open, /* open function */ + .check = NULL, /* no check function */ + .find = psl_find, /* find function */ + .close = psl_close, /* close function */ + .tidy = NULL, /* no tidy function */ + .quote = NULL, /* no quoting function */ + .version_report = psl_version_report /* version reporting */ +}; + +static lookup_info regdom_lookup_info = { + .name = US"regdom", /* lookup name */ + .type = lookup_absfile, /* lookup from file */ + .open = psl_open, /* open function */ + .check = NULL, /* no check function */ + .find = regdom_find, /* find function */ + .close = psl_close, /* close function */ + .tidy = NULL, /* no tidy function */ + .quote = NULL, /* no quoting function */ + .version_report = NULL /* no version reporting (redundant) */ +}; + +#ifdef DYNLOOKUP +#define psl_lookup_module_info _lookup_module_info +#endif + +static lookup_info *_lookup_list[] = { &psl_lookup_info, ®dom_lookup_info }; +lookup_module_info psl_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 2 }; + +/* End of lookups/psl.c */ diff --git a/src/src/lookups/spf.c b/src/src/lookups/spf.c index ac03983c2..e1931ca0f 100644 --- a/src/src/lookups/spf.c +++ b/src/src/lookups/spf.c @@ -15,13 +15,6 @@ of the License, or (at your option) any later version. */ #include "../exim.h" - -#ifndef EXIM_HAVE_SPF -static void dummy(int x); -static void dummy2(int x) { dummy(x-1); } -static void dummy(int x) { dummy2(x-1); } -#else - #include "lf_functions.h" #ifndef EXPERIMENTAL_SPF_PERL @@ -119,4 +112,3 @@ static lookup_info spf_lookup_info = { static lookup_info *_lookup_list[] = { &spf_lookup_info }; lookup_module_info spf_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 }; -#endif /* EXIM_HAVE_SPF */ diff --git a/src/src/macro_predef.c b/src/src/macro_predef.c index 48287131a..caa6bf7c3 100644 --- a/src/src/macro_predef.c +++ b/src/src/macro_predef.c @@ -8,7 +8,7 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* Create a static data structure with the predefined macros, to be -included in the main Exim build */ +included in the main Exim build. Also, table for various drivers. */ #include "exim.h" #include "macro_predef.h" @@ -203,9 +203,6 @@ due to conflicts with other common macros. */ #ifdef EXPERIMENTAL_ARC builtin_macro_create(US"_HAVE_ARC"); #endif -#ifdef EXPERIMENTAL_BRIGHTMAIL - builtin_macro_create(US"_HAVE_BRIGHTMAIL"); -#endif #ifdef SUPPORT_DANE builtin_macro_create(US"_HAVE_DANE"); #endif @@ -252,9 +249,6 @@ due to conflicts with other common macros. */ #ifdef LOOKUP_DSEARCH builtin_macro_create(US"_HAVE_LOOKUP_DSEARCH"); #endif -#ifdef LOOKUP_IBASE - builtin_macro_create(US"_HAVE_LOOKUP_IBASE"); -#endif #ifdef LOOKUP_LMDB builtin_macro_create(US"_HAVE_LMDB"); builtin_macro_create(US"_HAVE_LOOKUP_LMDB"); @@ -283,6 +277,10 @@ due to conflicts with other common macros. */ #ifdef LOOKUP_PGSQL builtin_macro_create(US"_HAVE_LOOKUP_PGSQL"); #endif +#ifdef LOOKUP_PSL + builtin_macro_create(US"_HAVE_LOOKUP_PSL"); + builtin_macro_create(US"_HAVE_LOOKUP_REGDOM"); +#endif #ifdef LOOKUP_REDIS builtin_macro_create(US"_HAVE_LOOKUP_REDIS"); #endif @@ -322,9 +320,6 @@ exp_features(void) #ifdef EXPERIMENTAL_ARC builtin_macro_create(US"_EXP_ARC"); #endif -#ifdef EXPERIMENTAL_BRIGHTMAIL - builtin_macro_create(US"_EXP_BMI"); -#endif #ifdef EXPERIMENTAL_DCC builtin_macro_create(US"_EXP_DCC"); #endif @@ -358,6 +353,41 @@ params_dkim(); #endif } +/******************************************************************************/ +static void +avail_append(const driver_info * di) +{ +const uschar * avail_string = di->avail_string; +if (!avail_string) avail_string = di->driver_name; + printf(" US\"%s\",", avail_string); +} + +static void +avail_list(const driver_info * drtable, const uschar * tag, + const uschar * group, BOOL magic) +{ +printf("const uschar * avail_%s_%s[] = {", group, tag); +for (const driver_info * di = drtable; di; di = di->next) + if (!!di->dyn_magic == magic) avail_append(di); +printf(" NULL};\n"); +} + +static void +avail_drs(const driver_info * drtable, const uschar * tag) +{ +avail_list(drtable, tag, US"static", FALSE); +avail_list(drtable, tag, US"dynamic", TRUE); +} + +static void +avail(void) +{ +avail_drs((driver_info *)auths_available, US"auths"); +avail_drs((driver_info *)routers_available, US"routers"); +avail_drs((driver_info *)transports_available, US"transports"); +} + +/******************************************************************************/ int main(void) @@ -368,8 +398,10 @@ exp_features(); options(); expansions(); params(); - printf("macro_item * macros = &p%u;\n", mp_index-1); printf("macro_item * mlast = &p0;\n"); + +avail(); + exit(0); } diff --git a/src/src/macros.h b/src/src/macros.h index 75563d86b..2e0efc9b8 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -90,10 +90,6 @@ don't make the file descriptors two-way. */ #define pipe_read 0 #define pipe_write 1 -/* The RFC 1413 ident port */ - -#define IDENT_PORT 113 - /* A macro to simplify testing bits in lookup types */ #define mac_islookup(li,b) ((li)->type & (b)) @@ -385,26 +381,25 @@ enum { DEBUG_BIT(filter), DEBUG_BIT(hints_lookup), DEBUG_BIT(host_lookup), - DEBUG_BIT(ident), DEBUG_BIT(interface), DEBUG_BIT(lists), - DEBUG_BIT(load), /* 15 */ - DEBUG_BIT(lookup), + DEBUG_BIT(load), + DEBUG_BIT(lookup), /* 15 */ DEBUG_BIT(memory), DEBUG_BIT(noutf8), DEBUG_BIT(pid), DEBUG_BIT(process_info), DEBUG_BIT(queue_run), DEBUG_BIT(receive), - DEBUG_BIT(resolver), /* 23 */ - DEBUG_BIT(retry), + DEBUG_BIT(resolver), + DEBUG_BIT(retry), /* 23 */ DEBUG_BIT(rewrite), DEBUG_BIT(route), DEBUG_BIT(timestamp), DEBUG_BIT(tls), DEBUG_BIT(transport), DEBUG_BIT(uid), - DEBUG_BIT(verify), /* 31 */ + DEBUG_BIT(verify), /* 30 - one spare! */ }; /* Multi-bit debug masks */ @@ -474,7 +469,6 @@ enum logbit { Li_dkim, Li_dkim_verbose, Li_dnssec, - Li_ident_timeout, Li_incoming_interface, Li_incoming_port, Li_millisec, diff --git a/src/src/queue.c b/src/src/queue.c index b3d972f41..97238ba1a 100644 --- a/src/src/queue.c +++ b/src/src/queue.c @@ -351,7 +351,8 @@ Returns: nothing */ void -queue_run(qrunner * q, const uschar * start_id, const uschar * stop_id, BOOL recurse) +queue_run(qrunner * q, const uschar * start_id, const uschar * stop_id, + BOOL recurse) { BOOL force_delivery = q->queue_run_force || deliver_selectstring || deliver_selectstring_sender; @@ -519,6 +520,10 @@ for (int i = queue_run_in_order ? -1 : 0; #endif nelem(qpid) - 1]) { /* The child table is maxed out; wait for the oldest */ + /*XXX It might be nicer to wait for the first one + that happens to complete. */ + set_process_info("running queue (ph 1): parallel limit %u", + nelem(qpid)); DEBUG(D_queue_run) debug_printf("q2stage waiting for child %d\n", (int)qpid[0]); waitpid(qpid[0], NULL, 0); @@ -799,15 +804,20 @@ turned off. */ if (q->queue_2stage) { + unsigned active; + for (active = 0; active < nelem(qpid); active++) + if (!qpid[active]) { --active; break; } - /* wait for last children */ - for (int i = 0; i < nelem(qpid); i++) + /* wait for all first-stage children */ + for (unsigned i = 0; i < nelem(qpid); i++) if (qpid[i]) { - DEBUG(D_queue_run) debug_printf("q2stage reaped child %d\n", (int)qpid[i]); + set_process_info("running queue (ph 1): wait-all, child %u/%u", + i+1, active); waitpid(qpid[i], NULL, 0); + DEBUG(D_queue_run) debug_printf("q2stage reaped child %d\n", (int)qpid[i]); } - else break; + else break; /* should be no holes in table, so we're done */ #ifdef MEASURE_TIMING report_time_since(×tamp_startup, US"queue_run phase 1 done"); diff --git a/src/src/readconf.c b/src/src/readconf.c index cec89f9bb..f4da20b6a 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -79,9 +79,6 @@ static optionlist optionlist_config[] = { { "av_scanner", opt_stringptr, {&av_scanner} }, #endif { "bi_command", opt_stringptr, {&bi_command} }, -#ifdef EXPERIMENTAL_BRIGHTMAIL - { "bmi_config_file", opt_stringptr, {&bmi_config_file} }, -#endif { "bounce_message_file", opt_stringptr, {&bounce_message_file} }, { "bounce_message_text", opt_stringptr, {&bounce_message_text} }, { "bounce_return_body", opt_bool, {&bounce_return_body} }, @@ -143,9 +140,6 @@ static optionlist optionlist_config[] = { { "dns_retry", opt_int, {&dns_retry} }, { "dns_trust_aa", opt_stringptr, {&dns_trust_aa} }, { "dns_use_edns0", opt_int, {&dns_use_edns0} }, - /* This option is now a no-op, retained for compatibility */ - { "drop_cr", opt_bool, {&drop_cr} }, -/*********************************************************/ { "dsn_advertise_hosts", opt_stringptr, {&dsn_advertise_hosts} }, { "dsn_from", opt_stringptr, {&dsn_from} }, { "envelope_to_remove", opt_bool, {&envelope_to_remove} }, @@ -191,9 +185,6 @@ static optionlist optionlist_config[] = { { "hosts_treat_as_local", opt_stringptr, {&hosts_treat_as_local} }, #ifdef EXPERIMENTAL_XCLIENT { "hosts_xclient", opt_stringptr, {&hosts_xclient} }, -#endif -#ifdef LOOKUP_IBASE - { "ibase_servers", opt_stringptr, {&ibase_servers} }, #endif { "ignore_bounce_errors_after", opt_time, {&ignore_bounce_errors_after} }, { "ignore_fromline_hosts", opt_stringptr, {&ignore_fromline_hosts} }, @@ -307,9 +298,6 @@ static optionlist optionlist_config[] = { { "retry_interval_max", opt_time, {&retry_interval_max} }, { "return_path_remove", opt_bool, {&return_path_remove} }, { "return_size_limit", opt_mkint|opt_hidden, {&bounce_return_size_limit} }, - { "rfc1413_hosts", opt_stringptr, {&rfc1413_hosts} }, - { "rfc1413_port", opt_int|opt_hidden, {&test_harness_identd_port} }, - { "rfc1413_query_timeout", opt_time, {&rfc1413_query_timeout} }, { "sender_unqualified_hosts", opt_stringptr, {&sender_unqualified_hosts} }, { "slow_lookup_log", opt_int, {&slow_lookup_log} }, { "smtp_accept_keepalive", opt_bool, {&smtp_accept_keepalive} }, @@ -987,8 +975,13 @@ if (*s) for (macro_item * m = *s == '_' ? macros : macros_user; m; m = m->next) { int moveby; - EARLY_DEBUG(D_any, "%s: matched '%s' in '%.*s'\n", __FUNCTION__, - m->name, (int) Ustrlen(ss)-1, ss); + DEBUG(D_any) + if (f.expansion_test) + printf("macro '%s' -> '%s'\n", m->name, m->replacement); + else + EARLY_DEBUG(D_any, "%s: matched '%s' in '%.*s'\n", __FUNCTION__, + m->name, (int) Ustrlen(ss)-1, ss); + /* Expand the buffer if necessary */ while (*newlen - m->namelen + m->replen + 1 > big_buffer_size) @@ -3824,13 +3817,12 @@ for (di = *info_anchor; di; di = di->next) /* Potentially a loadable module. Look for a file with the right name. */ if (!(dd = exim_opendir(CUS LOOKUP_MODULE_DIR))) - { log_write(0, LOG_MAIN|LOG_PANIC, - "Couldn't open %s: not loading driver modules\n", LOOKUP_MODULE_DIR); - } + "Couldn't open %s: not loading driver modules\n", LOOKUP_MODULE_DIR); else { - uschar * fname = string_sprintf("%s_%s." DYNLIB_FN_EXT, d->driver_name, class), * sname; + uschar * fname = string_sprintf("%s_%s." DYNLIB_FN_EXT, + d->driver_name, class); const char * errormsg; DEBUG(D_any) debug_printf("Loading %s %s driver from %s\n", diff --git a/src/src/receive.c b/src/src/receive.c index 9d8bfa89c..cde09c40a 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -533,11 +533,6 @@ if (recipients_count >= recipients_list_max) recipients_list[recipients_count].address = recipient; recipients_list[recipients_count].pno = pno; -#ifdef EXPERIMENTAL_BRIGHTMAIL -recipients_list[recipients_count].bmi_optin = bmi_current_optin; -/* reset optin string pointer for next recipient */ -bmi_current_optin = NULL; -#endif recipients_list[recipients_count].orcpt = NULL; recipients_list[recipients_count].dsn_flags = 0; recipients_list[recipients_count++].errors_to = NULL; @@ -3982,14 +3977,6 @@ if (fake_response != OK) f.deliver_firsttime = TRUE; -#ifdef EXPERIMENTAL_BRIGHTMAIL -if (bmi_run == 1) - { /* rewind data file */ - lseek(data_fd, (long int)spool_data_start_offset(message_id), SEEK_SET); - bmi_verdicts = bmi_process_message(header_list, data_fd); - } -#endif - /* Update the timestamp in our Received: header to account for any time taken by an ACL or by local_scan(). The new time is the time that all reception processing is complete. */ diff --git a/src/src/route.c b/src/src/route.c index 534198a85..778e524c1 100644 --- a/src/src/route.c +++ b/src/src/route.c @@ -35,16 +35,6 @@ optionlist optionlist_routers[] = { LOFF(address_data) }, { "address_test", opt_bool|opt_public, LOFF(address_test) }, -#ifdef EXPERIMENTAL_BRIGHTMAIL - { "bmi_deliver_alternate", opt_bool | opt_public, - LOFF(bmi_deliver_alternate) }, - { "bmi_deliver_default", opt_bool | opt_public, - LOFF(bmi_deliver_default) }, - { "bmi_dont_deliver", opt_bool | opt_public, - LOFF(bmi_dont_deliver) }, - { "bmi_rule", opt_stringptr|opt_public, - LOFF(bmi_rule) }, -#endif { "cannot_route_message", opt_stringptr | opt_public, LOFF(cannot_route_message) }, { "caseful_local_part", opt_bool | opt_public, @@ -1095,48 +1085,6 @@ if (r->condition) } } -#ifdef EXPERIMENTAL_BRIGHTMAIL -/* check if a specific Brightmail AntiSpam rule fired on the message */ -if (r->bmi_rule) - { - DEBUG(D_route) debug_printf_indent("checking bmi_rule\n"); - if (bmi_check_rule(bmi_base64_verdict, r->bmi_rule) == 0) - { /* none of the rules fired */ - DEBUG(D_route) - debug_printf_indent("%s router skipped: none of bmi_rule rules fired\n", rname); - return SKIP; - } - } - -/* check if message should not be delivered */ -if (r->bmi_dont_deliver && bmi_deliver == 1) - { - DEBUG(D_route) - debug_printf_indent("%s router skipped: bmi_dont_deliver is FALSE\n", rname); - return SKIP; - } - -/* check if message should go to an alternate location */ -if ( r->bmi_deliver_alternate - && (bmi_deliver == 0 || !bmi_alt_location) - ) - { - DEBUG(D_route) - debug_printf_indent("%s router skipped: bmi_deliver_alternate is FALSE\n", rname); - return SKIP; - } - -/* check if message should go to default location */ -if ( r->bmi_deliver_default - && (bmi_deliver == 0 || bmi_alt_location) - ) - { - DEBUG(D_route) - debug_printf_indent("%s router skipped: bmi_deliver_default is FALSE\n", rname); - return SKIP; - } -#endif - /* All the checks passed. */ return OK; diff --git a/src/src/routers/accept.c b/src/src/routers/accept.c index 09992b85f..9986c39aa 100644 --- a/src/src/routers/accept.c +++ b/src/src/routers/accept.c @@ -156,7 +156,7 @@ router_info accept_router_info = .options_block = &accept_router_option_defaults, .options_len = sizeof(accept_router_options_block), .init = accept_router_init, -# ifdef DYNLOOKUP +# if ROUTER_ACCEPT==2 .dyn_magic = ROUTER_MAGIC, # endif }, diff --git a/src/src/routers/dnslookup.c b/src/src/routers/dnslookup.c index 97e273dca..5df6ea8f9 100644 --- a/src/src/routers/dnslookup.c +++ b/src/src/routers/dnslookup.c @@ -484,7 +484,7 @@ router_info dnslookup_router_info = .options_block = &dnslookup_router_option_defaults, .options_len = sizeof(dnslookup_router_options_block), .init = dnslookup_router_init, -# ifdef DYNLOOKUP +# if ROUTER_DNSLOOKUP==2 .dyn_magic = ROUTER_MAGIC, # endif }, diff --git a/src/src/routers/ipliteral.c b/src/src/routers/ipliteral.c index 9052f2d48..022592c9e 100644 --- a/src/src/routers/ipliteral.c +++ b/src/src/routers/ipliteral.c @@ -218,7 +218,7 @@ router_info ipliteral_router_info = .options_block = &ipliteral_router_option_defaults, .options_len = sizeof(ipliteral_router_options_block), .init = ipliteral_router_init, -# ifdef DYNLOOKUP +# if ROUTER_IPLITERAL==2 .dyn_magic = ROUTER_MAGIC, # endif }, diff --git a/src/src/routers/iplookup.c b/src/src/routers/iplookup.c index 1f3945bc8..5d9c04d71 100644 --- a/src/src/routers/iplookup.c +++ b/src/src/routers/iplookup.c @@ -433,7 +433,7 @@ router_info iplookup_router_info = .options_block = &iplookup_router_option_defaults, .options_len = sizeof(iplookup_router_options_block), .init = iplookup_router_init, -# ifdef DYNLOOKUP +# if ROUTER_IPLOOKUP==2 .dyn_magic = ROUTER_MAGIC, # endif }, diff --git a/src/src/routers/manualroute.c b/src/src/routers/manualroute.c index 5a4c72bb0..589db7ead 100644 --- a/src/src/routers/manualroute.c +++ b/src/src/routers/manualroute.c @@ -517,7 +517,7 @@ router_info manualroute_router_info = .options_block = &manualroute_router_option_defaults, .options_len = sizeof(manualroute_router_options_block), .init = manualroute_router_init, -# ifdef DYNLOOKUP +# if ROUTER_MANUALROUTE==2 .dyn_magic = ROUTER_MAGIC, # endif }, diff --git a/src/src/routers/queryprogram.c b/src/src/routers/queryprogram.c index 92a2524ab..3e12ec0d7 100644 --- a/src/src/routers/queryprogram.c +++ b/src/src/routers/queryprogram.c @@ -549,7 +549,7 @@ router_info queryprogram_router_info = .options_block = &queryprogram_router_option_defaults, .options_len = sizeof(queryprogram_router_options_block), .init = queryprogram_router_init, -# ifdef DYNLOOKUP +# if ROUTER_QUERYPROGRAM==2 .dyn_magic = ROUTER_MAGIC, # endif }, diff --git a/src/src/routers/redirect.c b/src/src/routers/redirect.c index 6d0574c14..e90ad2147 100644 --- a/src/src/routers/redirect.c +++ b/src/src/routers/redirect.c @@ -804,7 +804,7 @@ router_info redirect_router_info = .options_block = &redirect_router_option_defaults, .options_len = sizeof(redirect_router_options_block), .init = redirect_router_init, -# ifdef DYNLOOKUP +# if ROUTER_REDIRECT==2 .dyn_magic = ROUTER_MAGIC, # endif }, diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index b5a054a16..e6c9dbacc 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -1780,10 +1780,7 @@ memset(sender_address_cache, 0, sizeof(sender_address_cache)); memset(sender_domain_cache, 0, sizeof(sender_domain_cache)); authenticated_sender = NULL; -#ifdef EXPERIMENTAL_BRIGHTMAIL - bmi_run = 0; - bmi_verdicts = NULL; -#endif +dnslist_domain = dnslist_matched = NULL; dsn_ret = 0; dsn_envid = NULL; diff --git a/src/src/spool_in.c b/src/src/spool_in.c index ff9edfc2e..b475c3955 100644 --- a/src/src/spool_in.c +++ b/src/src/spool_in.c @@ -263,11 +263,6 @@ f.spool_file_wireformat = FALSE; #endif tree_nonrecipients = NULL; -#ifdef EXPERIMENTAL_BRIGHTMAIL -bmi_run = 0; -bmi_verdicts = NULL; -#endif - #ifndef DISABLE_DKIM f.dkim_disable_verify = FALSE; # ifdef COMPILE_UTILITY @@ -615,10 +610,6 @@ for (;;) body_linecount = Uatoi(var + 14); else if (Ustrncmp(p, "ody_zerocount", 13) == 0) body_zerocount = Uatoi(var + 14); -#ifdef EXPERIMENTAL_BRIGHTMAIL - else if (Ustrncmp(p, "mi_verdicts ", 12) == 0) - bmi_verdicts = string_copy_taint(var + 13, proto_mem); -#endif break; case 'd': diff --git a/src/src/spool_out.c b/src/src/spool_out.c index e5cded966..91b776df2 100644 --- a/src/src/spool_out.c +++ b/src/src/spool_out.c @@ -269,15 +269,12 @@ if (spam_score_int) spool_var_write(fp, US"spam_score_int", spam_score_int); if (f.deliver_manual_thaw) fprintf(fp, "-manual_thaw\n"); if (f.sender_set_untrusted) fprintf(fp, "-sender_set_untrusted\n"); -#ifdef EXPERIMENTAL_BRIGHTMAIL -if (bmi_verdicts) spool_var_write(fp, US"bmi_verdicts", bmi_verdicts); -#endif - #ifndef DISABLE_TLS if (tls_in.certificate_verified) fprintf(fp, "-tls_certificate_verified\n"); if (tls_in.cipher) spool_var_write(fp, US"tls_cipher", tls_in.cipher); if (tls_in.peercert) { + /* -- marks as tainted */ if (tls_export_cert(big_buffer, big_buffer_size, tls_in.peercert)) fprintf(fp, "--tls_peercert %s\n", CS big_buffer); } diff --git a/src/src/structs.h b/src/src/structs.h index 8f3eaacbf..147819dd5 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -153,6 +153,7 @@ typedef struct driver_instance { typedef struct driver_info { struct driver_info * next; uschar *driver_name; /* Name of driver */ + uschar *avail_string; /* if set, display rather than name */ optionlist *options; /* Table of private options names */ int *options_count; /* -> Number of entries in table */ @@ -299,9 +300,6 @@ typedef struct router_instance { driver_instance drinst; uschar *address_data; /* Arbitrary data */ -#ifdef EXPERIMENTAL_BRIGHTMAIL - uschar *bmi_rule; /* Brightmail AntiSpam rule checking */ -#endif uschar *cannot_route_message; /* Used when routing fails */ uschar *condition; /* General condition */ uschar *current_directory; /* For use during delivery */ @@ -330,11 +328,6 @@ typedef struct router_instance { uschar *transport_name; /* Transport name */ BOOL address_test; /* Use this router when testing addresses */ -#ifdef EXPERIMENTAL_BRIGHTMAIL - BOOL bmi_deliver_alternate; /* TRUE => BMI said that message should be delivered to alternate location */ - BOOL bmi_deliver_default; /* TRUE => BMI said that message should be delivered to default location */ - BOOL bmi_dont_deliver; /* TRUE => BMI said that message should not be delivered at all */ -#endif BOOL expn; /* Use this router when processing EXPN */ BOOL caseful_local_part; /* TRUE => don't lowercase */ BOOL check_local_user; /* TRUE => check local user */ diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 66359bb3e..80fb419d0 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -578,16 +578,23 @@ msg = rc == GNUTLS_E_FATAL_ALERT_RECEIVED (void) tls_error(when, msg, state->host, &errstr); -if (state->host) - log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection %s", - state->host->name, state->host->address, errstr); -else +if (!state->host) { uschar * conn_info = smtp_get_connection_info(); if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) conn_info += 5; /* I'd like to get separated H= here, but too hard for now */ log_write(0, LOG_MAIN, "TLS error on %s %s", conn_info, errstr); } +else if ( !tls_out.smtp_quit +#ifdef GNUTLS_E_PREMATURE_TERMINATION + || rc != GNUTLS_E_PREMATURE_TERMINATION +#endif + ) + log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection %s", + state->host->name, state->host->address, errstr); +else DEBUG(D_tls) + debug_printf("H=%s [%s] TLS error on connection %s\n", + state->host->name, state->host->address, errstr); } @@ -3523,7 +3530,7 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET) debug_printf(" extract session data: %s\n", US gnutls_strerror(rc)); } else DEBUG(D_tls) - debug_printf(" host not resmable; not saving ticket\n"); + debug_printf(" host not resumable; not saving ticket\n"); } } diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 66661e94e..d5a6d368d 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -509,6 +509,22 @@ return host ? FAIL : DEFER; } +static void +tls_debug_err(SSL * ssl, uschar * where, int rc) +{ +BIO * bio = BIO_new(BIO_s_mem()); +char * buf = NULL; +size_t len; +int error = SSL_get_error(ssl, rc); + +ERR_error_string_n(ERR_peek_error(), ssl_errstring, sizeof(ssl_errstring)); +ERR_print_errors(bio); +len = BIO_get_mem_data(bio, &buf); +debug_printf("%s: error %d\n%.*s\n", where, error, (int)len, buf); +BIO_free (bio); +} + + /************************************************** * General library initalisation * @@ -983,24 +999,24 @@ DEBUG(D_tls) if (where & SSL_CB_HANDSHAKE_DONE) g = string_append_listele(g, ',', US"hshake_done"); if (where & SSL_CB_LOOP) - debug_printf("SSL %s: %s\n", g->s, SSL_state_string_long(s)); + debug_printf("SSL %Y: %s\n", g, SSL_state_string_long(s)); else if (where & SSL_CB_ALERT) - debug_printf("SSL %s %s:%s\n", g->s, + debug_printf("SSL %Y %s:%s\n", g, SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); else if (where & SSL_CB_EXIT) { if (ret <= 0) - debug_printf("SSL %s: %s in %s\n", g->s, + debug_printf("SSL %Y: %s in %s\n", g, ret == 0 ? "failed" : "error", SSL_state_string_long(s)); } else if (where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE)) - debug_printf("SSL %s: %s\n", g->s, SSL_state_string_long(s)); + debug_printf("SSL %Y: %s\n", g, SSL_state_string_long(s)); } } #ifdef OPENSSL_HAVE_KEYLOG_CB static void -keylog_callback(const SSL *ssl, const char *line) +keylog_callback(const SSL * ssl, const char * line) { char * filename; FILE * fp; @@ -4533,11 +4549,10 @@ static BOOL tls_refill(unsigned lim) { SSL * ssl = state_server.lib_state.lib_ssl; -int error; -int inbytes; +int error, inbytes; -DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl, - ssl_xfer_buffer, ssl_xfer_buffer_size); +DEBUG(D_tls) debug_printf("Calling SSL_read(tls_refill %p, %p, %u)\n", + ssl, ssl_xfer_buffer, ssl_xfer_buffer_size); ERR_clear_error(); if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout); @@ -4579,8 +4594,9 @@ switch(error) uschar * conn_info = smtp_get_connection_info(); if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) conn_info += 5; /* I'd like to get separated H= here, but too hard for now */ - ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); + ERR_error_string_n(ERR_peek_error(), ssl_errstring, sizeof(ssl_errstring)); log_write(0, LOG_MAIN, "TLS error (SSL_read): on %s %s", conn_info, ssl_errstring); + DEBUG(D_tls) tls_debug_err(ssl, US"SSL_read", inbytes); ssl_xfer_error = TRUE; return FALSE; } @@ -4701,29 +4717,30 @@ Only used by the client-side TLS. */ int -tls_read(void * ct_ctx, uschar *buff, size_t len) +tls_read(void * ct_ctx, uschar * buff, size_t len) { SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : state_server.lib_state.lib_ssl; int inbytes; int error; -DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl, - buff, (unsigned int)len); +DEBUG(D_tls) debug_printf("Calling SSL_read(tls_read %p, %p, %u)\n", + ssl, buff, (unsigned int)len); ERR_clear_error(); inbytes = SSL_read(ssl, CS buff, len); error = SSL_get_error(ssl, inbytes); -if (error == SSL_ERROR_ZERO_RETURN) - { - DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n"); - return -1; - } -else if (error != SSL_ERROR_NONE) - return -1; +if (error == SSL_ERROR_NONE) + return inbytes; -return inbytes; +else DEBUG(D_tls) + if (error == SSL_ERROR_ZERO_RETURN) + debug_printf("Got SSL_ERROR_ZERO_RETURN\n"); + else + tls_debug_err(ssl, US"SSL_read", inbytes); +ERR_clear_error(); +return -1; } @@ -4861,12 +4878,9 @@ if ((o_ctx ? tls_out.active.sock : tls_in.active.sock) < 0) tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */ HDEBUG(D_transport|D_tls|D_acl|D_v) debug_printf_indent(" SMTP(TLS shutdown)>>\n"); -rc = SSL_shutdown(ssl); -if (rc < 0) DEBUG(D_tls) - { - ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); - debug_printf("SSL_shutdown: %s\n", ssl_errstring); - } +ERR_clear_error(); +if ((rc = SSL_shutdown(ssl)) < 0) + DEBUG(D_tls) tls_debug_err(ssl, US"SSL_shutdown", rc); } /************************************************* @@ -4905,6 +4919,7 @@ if (do_shutdown > TLS_NO_SHUTDOWN) tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */ + ERR_clear_error(); if ( ( do_shutdown >= TLS_SHUTDOWN_WONLY || (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */ ) @@ -4919,11 +4934,7 @@ if (do_shutdown > TLS_NO_SHUTDOWN) ALARM_CLR(0); } - if (rc < 0) DEBUG(D_tls) - { - ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); - debug_printf("SSL_shutdown: %s\n", ssl_errstring); - } + if (rc < 0) DEBUG(D_tls) tls_debug_err(*sslp, US"SSL_shutdown", rc); } if (!o_ctx) /* server side */ diff --git a/src/src/tlscert-gnu.c b/src/src/tlscert-gnu.c index 6bbea7516..43d362904 100644 --- a/src/src/tlscert-gnu.c +++ b/src/src/tlscert-gnu.c @@ -150,6 +150,7 @@ if ((ret = gnutls_x509_crt_get_issuer_dn(cert, CS cp, &siz)) != GNUTLS_E_SHORT_MEMORY_BUFFER) return g_err("gi0", __FUNCTION__, ret); +/*XXX we might want to distinguish ourcert from peercert (but this is safe) */ cp = store_get(siz, GET_TAINTED); if ((ret = gnutls_x509_crt_get_issuer_dn(cert, CS cp, &siz)) < 0) return g_err("gi1", __FUNCTION__, ret); @@ -226,14 +227,15 @@ return algo < 0 ? NULL : string_copy(US gnutls_sign_get_name(algo)); uschar * tls_cert_subject(void * cert, const uschar * mod) { -uschar * cp = NULL; +uschar * cp; int ret; size_t siz = 0; -if ((ret = gnutls_x509_crt_get_dn(cert, CS cp, &siz)) +if ((ret = gnutls_x509_crt_get_dn(cert, NULL, &siz)) != GNUTLS_E_SHORT_MEMORY_BUFFER) return g_err("gs0", __FUNCTION__, ret); +/*XXX we might want to distinguish ourcert from peercert (but this is safe) */ cp = store_get(siz, GET_TAINTED); if ((ret = gnutls_x509_crt_get_dn(cert, CS cp, &siz)) < 0) return g_err("gs1", __FUNCTION__, ret); @@ -262,6 +264,7 @@ ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert, if (ret != GNUTLS_E_SHORT_MEMORY_BUFFER) return g_err("ge0", __FUNCTION__, ret); +/*XXX we might want to distinguish ourcert from peercert (but this is safe) */ cp1 = store_get(siz*4 + 1, GET_TAINTED); ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert, @@ -317,6 +320,7 @@ for (int index = 0;; index++) return g_err("gs0", __FUNCTION__, ret); } + /*XXX we might want to distinguish ourcert from peercert (but this is safe) */ ele = store_get(siz+1, GET_TAINTED); if ((ret = gnutls_x509_crt_get_subject_alt_name( (gnutls_x509_crt_t)cert, index, ele, &siz, NULL)) < 0) @@ -347,7 +351,8 @@ tls_cert_ocsp_uri(void * cert, const uschar * mod) gnutls_datum_t uri; int ret; uschar sep = '\n'; -gstring * list = NULL; +/*XXX we might want to distinguish ourcert from peercert (but this is safe) */ +gstring * list = string_get_tainted(0, GET_TAINTED); if (mod) if (*mod == '>' && *++mod) sep = *mod++; @@ -381,7 +386,8 @@ tls_cert_crl_uri(void * cert, const uschar * mod) { int ret; uschar sep = '\n'; -gstring * list = NULL; +/*XXX we might want to distinguish ourcert from peercert (but this is safe) */ +gstring * list = string_get_tainted(0, GET_TAINTED); if (mod) if (*mod == '>' && *++mod) sep = *mod++; @@ -394,7 +400,7 @@ for (int index = 0;; index++) (gnutls_x509_crt_t)cert, index, NULL, &siz, NULL, NULL)) { case GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE: - return string_from_gstring(list); + return gstring_length(list) > 0 ? string_from_gstring(list) : NULL; case GNUTLS_E_SHORT_MEMORY_BUFFER: break; default: @@ -449,6 +455,7 @@ if ((ret = gnutls_x509_crt_get_fingerprint(cert, algo, NULL, &siz)) != GNUTLS_E_SHORT_MEMORY_BUFFER) return g_err("gf0", __FUNCTION__, ret); +/*XXX we might want to distinguish ourcert from peercert (but this is safe) */ cp = store_get(siz*3+1, GET_TAINTED); if ((ret = gnutls_x509_crt_get_fingerprint(cert, algo, cp, &siz)) < 0) return g_err("gf1", __FUNCTION__, ret); diff --git a/src/src/tlscert-openssl.c b/src/src/tlscert-openssl.c index cdac8f6b2..5e3ce22a4 100644 --- a/src/src/tlscert-openssl.c +++ b/src/src/tlscert-openssl.c @@ -213,7 +213,9 @@ uschar * tls_cert_issuer(void * cert, const uschar * mod) { uschar * cp = x509_name_copy(X509_get_issuer_name((X509 *)cert)); -return mod ? tls_field_from_dn(cp, mod) : cp; +if (mod) cp = tls_field_from_dn(cp, mod); +/*XXX we might want to distinguish ourcert from peercert (but this is safe) */ +return cp ? string_copy_taint(cp, GET_TAINTED) : cp; } uschar * @@ -312,7 +314,9 @@ uschar * tls_cert_subject(void * cert, const uschar * mod) { uschar * cp = x509_name_copy(X509_get_subject_name((X509 *)cert)); -return mod ? tls_field_from_dn(cp, mod) : cp; +if (mod) cp = tls_field_from_dn(cp, mod); +/*XXX we might want to distinguish ourcert from peercert (but this is safe) */ +return cp ? string_copy_taint(cp, GET_TAINTED) : cp; } uschar * @@ -345,6 +349,7 @@ M_ASN1_OCTET_STRING_print(bp, adata); /* binary data, DER encoded */ /* just dump for now */ len = BIO_get_mem_data(bp, &cp1); +/*XXX we might want to distinguish ourcert from peercert (but this is safe) */ cp3 = cp2 = store_get(len*3+1, GET_TAINTED); while(len) @@ -360,14 +365,13 @@ return cp3; uschar * tls_cert_subject_altname(void * cert, const uschar * mod) { -gstring * list = NULL; STACK_OF(GENERAL_NAME) * san = (STACK_OF(GENERAL_NAME) *) X509_get_ext_d2i((X509 *)cert, NID_subject_alt_name, NULL, NULL); uschar osep = '\n'; -uschar * tag = US""; -uschar * ele; -int match = -1; -int len; +uschar * tag = US"", * ele; +int match = -1, len; +/*XXX we might want to distinguish ourcert from peercert (but this is safe) */ +gstring * list = string_get_tainted(0, GET_TAINTED); if (!san) return NULL; @@ -417,7 +421,7 @@ while (sk_GENERAL_NAME_num(san) > 0) } sk_GENERAL_NAME_free(san); -return string_from_gstring(list); +return gstring_length(list) > 0 ? string_from_gstring(list) : NULL; } uschar * @@ -427,7 +431,8 @@ STACK_OF(ACCESS_DESCRIPTION) * ads = (STACK_OF(ACCESS_DESCRIPTION) *) X509_get_ext_d2i((X509 *)cert, NID_info_access, NULL, NULL); int adsnum = sk_ACCESS_DESCRIPTION_num(ads); uschar sep = '\n'; -gstring * list = NULL; +/*XXX we might want to distinguish ourcert from peercert (but this is safe) */ +gstring * list = string_get_tainted(0, GET_TAINTED); if (mod) if (*mod == '>' && *++mod) sep = *mod++; @@ -442,7 +447,7 @@ for (int i = 0; i < adsnum; i++) ASN1_STRING_length(ad->location->d.ia5)); } sk_ACCESS_DESCRIPTION_free(ads); -return string_from_gstring(list); +return gstring_length(list) > 0 ? string_from_gstring(list) : NULL; } uschar * @@ -453,7 +458,8 @@ STACK_OF(DIST_POINT) * dps = (STACK_OF(DIST_POINT) *) NULL, NULL); DIST_POINT * dp; uschar sep = '\n'; -gstring * list = NULL; +/*XXX we might want to distinguish ourcert from peercert (but this is safe) */ +gstring * list = string_get_tainted(0, GET_TAINTED); if (mod) if (*mod == '>' && *++mod) sep = *mod++; @@ -473,7 +479,7 @@ if (dps) for (int i = 0, dpsnum = sk_DIST_POINT_num(dps); i < dpsnum; i++) ASN1_STRING_length(np->d.uniformResourceIdentifier)); } sk_DIST_POINT_free(dps); -return string_from_gstring(list); +return gstring_length(list) > 0 ? string_from_gstring(list) : NULL; } diff --git a/src/src/transports/appendfile.c b/src/src/transports/appendfile.c index ebf11b7f7..cad14b889 100644 --- a/src/src/transports/appendfile.c +++ b/src/src/transports/appendfile.c @@ -3343,12 +3343,23 @@ ret_panic: transport_info appendfile_transport_info = { .drinfo = { .driver_name = US"appendfile", + .avail_string = US" appendfile" +# ifdef SUPPORT_MAILDIR + "/maildir" +# endif +# ifdef SUPPORT_MAILSTORE + "/mailstore" +# endif +# ifdef SUPPORT_MBX + "/mbx" +# endif + , .options = appendfile_transport_options, .options_count = &appendfile_transport_options_count, .options_block = &appendfile_transport_option_defaults, /* private options defaults */ .options_len = sizeof(appendfile_transport_options_block), .init = appendfile_transport_init, -# ifdef DYNLOOKUP +# if TRANSPORT_APPENDFILE==2 .dyn_magic = TRANSPORT_MAGIC, # endif }, diff --git a/src/src/transports/autoreply.c b/src/src/transports/autoreply.c index 62eb888d0..d9d9d7a9d 100644 --- a/src/src/transports/autoreply.c +++ b/src/src/transports/autoreply.c @@ -817,7 +817,7 @@ transport_info autoreply_transport_info = { .options_block = &autoreply_transport_option_defaults, .options_len = sizeof(autoreply_transport_options_block), .init = autoreply_transport_init, -# ifdef DYNLOOKUP +# if TRANSPORT_AUTOREPLY==2 .dyn_magic = TRANSPORT_MAGIC, # endif }, diff --git a/src/src/transports/lmtp.c b/src/src/transports/lmtp.c index 3403dce7a..322dfbb71 100644 --- a/src/src/transports/lmtp.c +++ b/src/src/transports/lmtp.c @@ -825,7 +825,7 @@ transport_info lmtp_transport_info = { .options_block = &lmtp_transport_option_defaults, .options_len = sizeof(lmtp_transport_options_block), .init = lmtp_transport_init, -# ifdef DYNLOOKUP +# if TRANSPORT_LMTP==2 .dyn_magic = TRANSPORT_MAGIC, # endif }, diff --git a/src/src/transports/pipe.c b/src/src/transports/pipe.c index 68036ce03..fd248113a 100644 --- a/src/src/transports/pipe.c +++ b/src/src/transports/pipe.c @@ -1142,7 +1142,7 @@ transport_info pipe_transport_info = { .options_block = &pipe_transport_option_defaults, .options_len = sizeof(pipe_transport_options_block), .init = pipe_transport_init, -# ifdef DYNLOOKUP +# if TRANSPORT_PIPE==2 .dyn_magic = TRANSPORT_MAGIC, # endif }, diff --git a/src/src/transports/queuefile.c b/src/src/transports/queuefile.c index 7ae71a8da..5bd02682e 100644 --- a/src/src/transports/queuefile.c +++ b/src/src/transports/queuefile.c @@ -300,7 +300,7 @@ transport_info queuefile_transport_info = { .options_block = &queuefile_transport_option_defaults, .options_len = sizeof(queuefile_transport_options_block), .init = queuefile_transport_init, -# ifdef DYNLOOKUP +# if EXPERIMENTAL_QUEUEFILE==2 .dyn_magic = TRANSPORT_MAGIC, # endif }, diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index e9dc0957c..36b6370fd 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -2381,6 +2381,7 @@ if (continue_hostname && continue_proxy_cipher) if (write(0, "QUIT\r\n", 6) < 0) DEBUG(D_any) debug_printf("stupid compiler\n"); close(0); + tls_out.active.sock = -1; continue_hostname = continue_proxy_cipher = NULL; f.continue_more = FALSE; continue_sequence = 1; /* Ensure proper logging of non-cont-conn */ @@ -2882,6 +2883,7 @@ else if (write(0, "QUIT\r\n", 6) < 0) DEBUG(D_any) debug_printf("stupid compiler\n"); close(0); + tls_out.active.sock = -1; continue_hostname = continue_proxy_cipher = NULL; f.continue_more = FALSE; continue_sequence = 1; /* Ensure proper logging of non-cont-conn */ @@ -3027,6 +3029,7 @@ if ( smtp_peer_options & OPTION_TLS sx->send_quit = FALSE; goto TLS_FAILED; } + tls_out.smtp_quit = FALSE; sx->send_tlsclose = TRUE; # ifdef TCP_FASTOPEN @@ -3484,7 +3487,10 @@ FAILED: SEND_QUIT: if (sx->send_quit) + { (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n"); + tls_out.smtp_quit = TRUE; + } #ifndef DISABLE_TLS if (sx->cctx.tls_ctx) @@ -4476,6 +4482,7 @@ else HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(shutdown)>>\n"); shutdown(sx->cctx.sock, SHUT_WR); /* flush output buffer, with TCP FIN */ } + tls_out.smtp_quit = TRUE; } if (smtp_peer_options & OPTION_CHUNKING && sx->cmd_count > 1) @@ -5170,6 +5177,10 @@ if (sx->send_quit) { /* Use _MORE to get QUIT in FIN segment */ (void)smtp_write_command(sx, SCMD_MORE, "QUIT\r\n"); #ifndef DISABLE_TLS + /* This flag is a custom hack to avoid logging, under GnuTLS, Google's + habit of just dropping the TCP conn (which violates TLS spec) */ + tls_out.smtp_quit = TRUE; + if (sx->cctx.tls_ctx && sx->send_tlsclose) { # ifdef EXIM_TCP_CORK /* Use _CORK to get TLS Close Notify in FIN segment */ @@ -5357,6 +5368,7 @@ sx.outblock.cmd_count = 0; sx.outblock.authenticating = FALSE; (void)smtp_write_command(&sx, SCMD_FLUSH, "QUIT\r\n"); +tls_out.smtp_quit = TRUE; (void)smtp_read_response(&sx, buffer, sizeof(buffer), '2', ob->command_timeout); (void)close(cctx.sock); } @@ -6545,7 +6557,7 @@ transport_info smtp_transport_info = { .options_block = &smtp_transport_option_defaults, .options_len = sizeof(smtp_transport_options_block), .init = smtp_transport_init, -# ifdef DYNLOOKUP +# if TRANSPORT_SMTP==2 .dyn_magic = TRANSPORT_MAGIC, # endif }, diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h index df6c9e963..49e3c8b96 100644 --- a/src/src/transports/smtp.h +++ b/src/src/transports/smtp.h @@ -202,7 +202,7 @@ typedef struct { unsigned avoid_option; uschar * igquotstr; - uschar * helo_data; + const uschar * helo_data; #ifdef EXPERIMENTAL_DSN_INFO uschar * smtp_greeting; uschar * helo_response; diff --git a/src/src/utf8.c b/src/src/utf8.c index c05853838..6cb104ddd 100644 --- a/src/src/utf8.c +++ b/src/src/utf8.c @@ -47,37 +47,40 @@ The *err string pointer should be null before the call Return NULL for error, with optional errstr pointer filled in */ -uschar * +const uschar * string_domain_utf8_to_alabel(const uschar * utf8, uschar ** err) { -uschar * s1, * s; +const uschar * cs; +uschar * t, * s; int rc; #ifdef SUPPORT_I18N_2008 /* Avoid lowercasing plain-ascii domains */ if (!string_is_utf8(utf8)) - return string_copy(utf8); + return utf8; /* Only lowercase is accepted by the library call. A pity since we lose any mixed-case annotation. This does not really matter for a domain. */ { + const uschar * cs1; uschar c; - for (s1 = s = US utf8; (c = *s1); s1++) if (!(c & 0x80) && isupper(c)) + for (cs1 = cs = utf8; (c = *cs1); cs1++) if (!(c & 0x80) && isupper(c)) { s = string_copy(utf8); - for (s1 = s + (s1 - utf8); (c = *s1); s1++) if (!(c & 0x80) && isupper(c)) - *s1 = tolower(c); + for (t = s + (cs1 - utf8); (c = *t); t++) if (!(c & 0x80) && isupper(c)) + *t = tolower(c); + cs = s; break; } } -if ((rc = idn2_lookup_u8((const uint8_t *) s, &s1, IDN2_NFC_INPUT)) != IDN2_OK) +if ((rc = idn2_lookup_u8((const uint8_t *) cs, &t, IDN2_NFC_INPUT)) != IDN2_OK) { if (err) *err = US idn2_strerror(rc); return NULL; } #else s = US stringprep_utf8_nfkc_normalize(CCS utf8, -1); -if ( (rc = idna_to_ascii_8z(CCS s, CSS &s1, IDNA_ALLOW_UNASSIGNED)) +if ( (rc = idna_to_ascii_8z(CCS s, CSS &t, IDNA_ALLOW_UNASSIGNED)) != IDNA_SUCCESS) { free(s); @@ -86,9 +89,9 @@ if ( (rc = idna_to_ascii_8z(CCS s, CSS &s1, IDNA_ALLOW_UNASSIGNED)) } free(s); #endif -s = string_copy(s1); -free(s1); -return s; +cs = string_copy(t); +free(t); +return cs; } @@ -132,7 +135,7 @@ return s; /* the *err string pointer should be null before the call */ -uschar * +const uschar * string_localpart_utf8_to_alabel(const uschar * utf8, uschar ** err) { size_t ucs4_len = 0; @@ -141,7 +144,7 @@ size_t p_len; uschar * res; int rc; -if (!string_is_utf8(utf8)) return string_copy(utf8); +if (!string_is_utf8(utf8)) return utf8; p = (punycode_uint *) stringprep_utf8_to_ucs4(CCS utf8, -1, &ucs4_len); if (!p || !ucs4_len) @@ -212,10 +215,10 @@ The *err string pointer should be null before the call. Return NULL on error, with (optional) errstring pointer filled in */ -uschar * +const uschar * string_address_utf8_to_alabel(const uschar * utf8, uschar ** err) { -uschar * l, * d; +const uschar * l, * d; if (!*utf8) return string_copy(utf8); diff --git a/src/src/utils/eximstats.src b/src/src/utils/eximstats.src index 84b60ceaa..41912c1fb 100644 --- a/src/src/utils/eximstats.src +++ b/src/src/utils/eximstats.src @@ -1852,7 +1852,7 @@ EoText sub generate_parser { my $parser = ' my($ip,$host,$email,$edomain,$domain,$thissize,$size,$old,$new); - my($tod,$m_hour,$m_min,$id,$flag,$extra,$length); + my($tod,$m_hour,$m_min,$id,$flag,$offset,$length); my($seconds,$queued,$rcpt_time,$local_domain); my $rej_id = 0; while (<$fh>) { @@ -1865,72 +1865,68 @@ sub generate_parser { $length = length($_); next if ($length < 38); next unless /^ - (\\d{4}\\-\\d\\d-\\d\\d\\s # 1: YYYYMMDD HHMMSS - (\\d\\d) # 2: HH - : - (\\d\\d) # 3: MM - :\\d\\d - ) - (\\.\\d+)? # 4: subseconds - (\s[-+]\\d\\d\\d\\d)? # 5: tz-offset - (\s\\[\\d+\\])? # 6: pid + (\\d{4}\\-\\d\\d-\\d\\d\\s # 1: YYYYMMDD HHMMSS 11 + (\\d\\d) # 2: HH 2 + : # 1 + (\\d\\d) # 3: MM 2 + :\\d\\d # 3 + ) # = 19 + (\\.\\d+)? # 4: subseconds (var) + (\s[-+]\\d{4})? # 5: tz-offset 6 + (\s\\[\\d+\\])? # 6: pid (var) /ox; + $offset = 19; $tod = defined($5) ? $1 . $5 : $1; ($m_hour,$m_min) = ($2,$3); + # watch for subsecond precision + if (defined($4)) { + $offset += length($4); + next if ($length < 19 + $offset); + } + # PH - watch for GMT offsets in the timestamp. if (defined($5)) { - $extra = 6; + $offset += 6; next if ($length < 44); - } - else { - $extra = 0; - } - - # watch for subsecond precision - if (defined($4)) { - $extra += length($4); - next if ($length < 38 + $extra); + next if ($length < 19 + $offset); } # PH - watch for PID added after the timestamp. if (defined($6)) { - $extra += length($6); - next if ($length < 38 + $extra); + $offset += length($6); + next if ($length < 19 + $offset); } - # jgh 2025/09/30 - I really dislike this magic "20" offset... - # It would be better to develop it from the substrings captured so far. + # skip space after the initial groups + $offset++; - # $id = substr($_, 20 + $extra, 16); # old ID was 16 chars - $id = substr($_, 20 + $extra, 23); # new ID is 23 chars + $id = substr($_, $offset, 23); # new ID is 23 chars, old 16 $id =~ s/(\S+).*/$1/; - # - # jgh 2025/09/30 - very fragile when this word was not an ID; - # could be shorter than 16. - $extra += length($id) - 16; - # 37 = 20+16+1 so next field after ID. Assuming there was really an ID. - $flag = substr($_, 37 + $extra, 2); + # skip ID and space + $offset += length($id) + 1; + + $flag = substr($_, $offset, 2); # jgh 2025/09/30 - - # 2020-05-12 17:31:41.630 [23229] auth_login authenticator failed for (User) [185.143.75.81]:28556 I=[192.168.56.110]:25: 535 Incorrect authentication data (set_id=dropped@kiev.ua) - # OTOH the "refused" and "dropped" probably are referring to connectios - # rather than messages. + # 2020-05-12 17:31:41.630 [23229] auth_login authenticator failed for (User) [185.143.75.81]:28556 I=[192.168.56.110]:25: 535 Incorrect authentication data (set_id=dropped@foo.us) + # We now count these as cause for connection-drop, since the client often + # does on getting an auth-fail (distributed password-guessing attacks...). # # The "[-<>=*(]+" will be for ignoring accept & delivery lines. # What is the "SA"? SpamAssissin ? if ($flag !~ /^([-<>=*(]+|SA)$/ && / (rejected|refused|dropped|authenticator failed)/) { $flag = "Re"; - $extra -= 3; + } else { + # skip flag and space + $offset += 3; } # Rejects might have no MSGID... - # Note that $extra can go negative here. Really, a $offset would be - # much more clear. if ($flag eq "Re" && $id !~ /^[-0-9a-zA-Z]+$/) { - $extra -= length($id) + 1; + $offset -= length($id) + 1; $id = "reject:" . ++$rej_id; } '; @@ -1947,11 +1943,13 @@ sub generate_parser { } $parser .= ' - next unless ($flag =~ /<=|=>|->|==|\\*\\*|Co|SA|Re/); + next unless ($flag =~ /<=|\(=|=>|->|==|\\*\\*|Co|SA|Re/); + + # TODO: handling for fakereject [ flag (= ] - #Strip away the timestamp, ID and flag to speed up later pattern matches. - #The flags include Co (Completed), Re (Rejected), and SA (SpamAssassin). - $_ = substr($_, 40 + $extra); # PH + # Strip away the timestamp, ID and flag to speed up later pattern matches. + # The flags include Co (Completed), Re (Rejected), and SA (SpamAssassin). + $_ = substr($_, $offset); # PH # Alias @message to the array of information about the message. # This minimises the number of calls to hash functions. diff --git a/src/src/utils/exipick.src b/src/src/utils/exipick.src index c441936a2..788f7efae 100644 --- a/src/src/utils/exipick.src +++ b/src/src/utils/exipick.src @@ -941,8 +941,6 @@ sub _parse_header { } elsif ($tag eq '-spam_score_int') { $self->{_vars}{spam_score_int} = $arg; $self->{_vars}{spam_score} = $arg / 10; - } elsif ($tag eq '-bmi_verdicts') { - $self->{_vars}{bmi_verdicts} = $arg; } elsif ($tag eq '-host_lookup_deferred') { $self->{_vars}{host_lookup_deferred} = 1; } elsif ($tag eq '-host_lookup_failed') { @@ -1569,10 +1567,6 @@ The value of AUTH= param for smtp messages, or a generated value from the callin Value of the header(s) with the same name with any RFC2047 words decoded if present. See section 11.5 of Exim's spec.txt for full details. -=item S + B<$bmi_verdicts> - -The verdict string provided by a Brightmail content scan - =item N . B<$body_linecount> The number of lines in the message's body. diff --git a/src/src/verify.c b/src/src/verify.c index a12e74ce6..a3ade640b 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -2779,162 +2779,6 @@ return yield; -/************************************************* -* Get RFC 1413 identification * -*************************************************/ - -/* Attempt to get an id from the sending machine via the RFC 1413 protocol. If -the timeout is set to zero, then the query is not done. There may also be lists -of hosts and nets which are exempt. To guard against malefactors sending -non-printing characters which could, for example, disrupt a message's headers, -make sure the string consists of printing characters only. - -Argument: - port the port to connect to; usually this is IDENT_PORT (113), but when - running in the test harness with -bh a different value is used. - -Returns: nothing - -Side effect: any received ident value is put in sender_ident (NULL otherwise) -*/ - -void -verify_get_ident(int port) -{ -client_conn_ctx ident_conn_ctx = {0}; -int host_af, qlen; -int received_sender_port, received_interface_port, n; -uschar *p; -blob early_data; -uschar buffer[2048]; - -/* Default is no ident. Check whether we want to do an ident check for this -host. */ - -sender_ident = NULL; -if (rfc1413_query_timeout <= 0 || verify_check_host(&rfc1413_hosts) != OK) - return; - -DEBUG(D_ident) debug_printf("doing ident callback\n"); - -/* Set up a connection to the ident port of the remote host. Bind the local end -to the incoming interface address. If the sender host address is an IPv6 -address, the incoming interface address will also be IPv6. */ - -host_af = Ustrchr(sender_host_address, ':') == NULL ? AF_INET : AF_INET6; -if ((ident_conn_ctx.sock = ip_socket(SOCK_STREAM, host_af)) < 0) return; - -if (ip_bind(ident_conn_ctx.sock, host_af, interface_address, 0) < 0) - { - DEBUG(D_ident) debug_printf("bind socket for ident failed: %s\n", - strerror(errno)); - goto END_OFF; - } - -/* Construct and send the query. */ - -qlen = snprintf(CS buffer, sizeof(buffer), "%d , %d\r\n", - sender_host_port, interface_port); -early_data.data = buffer; -early_data.len = qlen; - -/*XXX we trust that the query is idempotent */ -if (ip_connect(ident_conn_ctx.sock, host_af, sender_host_address, port, - rfc1413_query_timeout, &early_data) < 0) - { - if (errno == ETIMEDOUT && LOGGING(ident_timeout)) - log_write(0, LOG_MAIN, "ident connection to %s timed out", - sender_host_address); - else - DEBUG(D_ident) debug_printf("ident connection to %s failed: %s\n", - sender_host_address, strerror(errno)); - goto END_OFF; - } - -/* Read a response line. We put it into the rest of the buffer, using several -recv() calls if necessary. */ - -p = buffer + qlen; - -for (;;) - { - uschar *pp; - int count; - int size = sizeof(buffer) - (p - buffer); - - if (size <= 0) goto END_OFF; /* Buffer filled without seeing \n. */ - count = ip_recv(&ident_conn_ctx, p, size, time(NULL) + rfc1413_query_timeout); - if (count <= 0) goto END_OFF; /* Read error or EOF */ - - /* Scan what we just read, to see if we have reached the terminating \r\n. Be - generous, and accept a plain \n terminator as well. The only illegal - character is 0. */ - - for (pp = p; pp < p + count; pp++) - { - if (*pp == 0) goto END_OFF; /* Zero octet not allowed */ - if (*pp == '\n') - { - if (pp[-1] == '\r') pp--; - *pp = 0; - goto GOT_DATA; /* Break out of both loops */ - } - } - - /* Reached the end of the data without finding \n. Let the loop continue to - read some more, if there is room. */ - - p = pp; - } - -GOT_DATA: - -/* We have received a line of data. Check it carefully. It must start with the -same two port numbers that we sent, followed by data as defined by the RFC. For -example, - - 12345 , 25 : USERID : UNIX :root - -However, the amount of white space may be different to what we sent. In the -"osname" field there may be several sub-fields, comma separated. The data we -actually want to save follows the third colon. Some systems put leading spaces -in it - we discard those. */ - -if (sscanf(CS buffer + qlen, "%d , %d%n", &received_sender_port, - &received_interface_port, &n) != 2 || - received_sender_port != sender_host_port || - received_interface_port != interface_port) - goto END_OFF; - -p = buffer + qlen + n; -Uskip_whitespace(&p); -if (*p++ != ':') goto END_OFF; -Uskip_whitespace(&p); -if (Ustrncmp(p, "USERID", 6) != 0) goto END_OFF; -p += 6; -Uskip_whitespace(&p); -if (*p++ != ':') goto END_OFF; -while (*p && *p != ':') p++; -if (!*p++) goto END_OFF; -Uskip_whitespace(&p); -if (!*p) goto END_OFF; - -/* The rest of the line is the data we want. We turn it into printing -characters when we save it, so that it cannot mess up the format of any logging -or Received: lines into which it gets inserted. We keep a maximum of 127 -characters. The deconst cast is ok as we fed a nonconst to string_printing() */ - -sender_ident = US string_printing(string_copyn(p, 127)); -DEBUG(D_ident) debug_printf("sender_ident = %s\n", sender_ident); - -END_OFF: -(void)close(ident_conn_ctx.sock); -return; -} - - - - /************************************************* * Match host to a single host-list item * *************************************************/ commit 3858878623272c18ad8b4d3f856c3a3dbe22577c Author: Jeremy Harris Date: Thu Oct 30 09:42:01 2025 +0000 Fix taint status for dbm lookups. Bug 3169 Broken-by: c66a6edf7ba8 diff --git a/src/src/dbfn.c b/src/src/dbfn.c index 7f6951ee3..30015fe73 100644 --- a/src/src/dbfn.c +++ b/src/src/dbfn.c @@ -386,13 +386,15 @@ Arguments: key the key of the record to be read klen length of key including a terminating NUL (if present) length a pointer to an int into which to return the length, if not NULL + hintsdb TRUE for hints DB use, FALSE for lookup dbm use Returns: a pointer to the retrieved record, or NULL if the record is not found */ void * -dbfn_read_klen(open_db * dbblock, const uschar * key, int klen, int * length) +dbfn_read_klen(open_db * dbblock, const uschar * key, int klen, int * length, + BOOL hintsdb) { void * yield; EXIM_DATUM key_datum, result_datum; @@ -423,7 +425,7 @@ store the taint status with the data. */ dlen = exim_datum_size_get(&result_datum); DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: size %u return\n", dlen); -yield = store_get(dlen+1, GET_TAINTED); +yield = store_get(dlen+1, hintsdb ? GET_TAINTED : GET_UNTAINTED); memcpy(yield, exim_datum_data_get(&result_datum), dlen); ((uschar *)yield)[dlen] = '\0'; if (length) *length = dlen; @@ -451,7 +453,7 @@ Returns: a pointer to the retrieved record, or void * dbfn_read_with_length(open_db * dbblock, const uschar * key, int * lenp) { -return dbfn_read_klen(dbblock, key, Ustrlen(key)+1, lenp); +return dbfn_read_klen(dbblock, key, Ustrlen(key)+1, lenp, TRUE); } diff --git a/src/src/dbfunctions.h b/src/src/dbfunctions.h index 1b0e446f7..a6fb2b51e 100644 --- a/src/src/dbfunctions.h +++ b/src/src/dbfunctions.h @@ -18,7 +18,7 @@ int dbfn_delete(open_db *, const uschar *); open_db *dbfn_open(const uschar *, int, open_db *, BOOL, BOOL); open_db * dbfn_open_path(const uschar *, open_db *); open_db *dbfn_open_multi(const uschar *, int, open_db *); -void *dbfn_read_klen(open_db *, const uschar *, int, int *); +void *dbfn_read_klen(open_db *, const uschar *, int, int *, BOOL); void *dbfn_read_with_length(open_db *, const uschar *, int *); void *dbfn_read_enforce_length(open_db *, const uschar *, size_t); uschar *dbfn_scan(open_db *, BOOL, EXIM_CURSOR **); diff --git a/src/src/lookups/dbmdb.c b/src/src/lookups/dbmdb.c index d3b26cf0a..58a839e84 100644 --- a/src/src/lookups/dbmdb.c +++ b/src/src/lookups/dbmdb.c @@ -91,7 +91,8 @@ dbmdb_find(void * handle, const uschar * filename, const uschar * keystring, const uschar * opts) { open_db * d = (open_db *)handle; -return (*result = dbfn_read_klen(d, keystring, length+1, NULL)) ? OK : FAIL; +return (*result = dbfn_read_klen(d, keystring, length+1, NULL, FALSE)) + ? OK : FAIL; } commit 50a6abf200c5116e9b86f12afbcc973ccd021261 Author: Jeremy Harris Date: Fri Oct 31 12:55:40 2025 +0000 Testsuite: expand store_free() checking diff --git a/src/src/deliver.c b/src/src/deliver.c index 497c62e81..94dc092a3 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -9088,6 +9088,38 @@ fail: #endif } + +/* When running with debug_store this is called on every store_reset(). Walk all +the address lists we maintain checking that none of the pointers are in the region +being freed. */ +/*XXX what about all the pointers contained in the addr? +If implemented, beware unbounded recursion. */ + +static void +check_addr_list(const uschar * name, const address_item * a, + void (*f)(const uschar*, const uschar*, void*), void * ctx) +{ +while (a) + { + f(name, CUS a, ctx); /* We lie about the data type */ + a = a->next; + } +} + +void +check_deliver_addrs_not_freed(void (*f)(const uschar*, const uschar*, void*), void * ctx) +{ +check_addr_list(US"(addr_defer)", addr_defer, f, ctx); +check_addr_list(US"(addr_failed)", addr_failed, f, ctx); +check_addr_list(US"(addr_fallback)", addr_fallback, f, ctx); +check_addr_list(US"(addr_local)", addr_local, f, ctx); +check_addr_list(US"(addr_new)", addr_new, f, ctx); +check_addr_list(US"(addr_remote)", addr_remote, f, ctx); +check_addr_list(US"(addr_route)", addr_route, f, ctx); +check_addr_list(US"(addr_succeed)", addr_succeed, f, ctx); +check_addr_list(US"(addr_duplicate)", addr_duplicate, f, ctx); +} + /* vi: aw ai sw=2 */ /* End of deliver.c */ diff --git a/src/src/expand.c b/src/src/expand.c index 554d68ba6..9bfa39fdc 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -9017,15 +9017,18 @@ typedef struct { const uschar *var_data; } err_ctx; -/* Called via tree_walk, which allows nonconst name/data. Our usage is const. */ +/* Called via tree_walk, which allows nonconst name/data. Our usage is const. */ +typedef void (*twalk_compat)(uschar *, uschar *, void *); + static void -assert_variable_notin(uschar * var_name, uschar * var_data, void * ctx) +assert_variable_notin(const uschar * var_name, const uschar * var_data, + void * ctx) { err_ctx * e = ctx; if (var_data >= e->region_start && var_data < e->region_end) { - e->var_name = CUS var_name; - e->var_data = CUS var_data; + e->var_name = var_name; + e->var_data = var_data; } } @@ -9036,8 +9039,8 @@ err_ctx e = { .region_start = ptr, .region_end = US ptr + len, .var_name = NULL, .var_data = NULL }; /* check acl_ variables */ -tree_walk(acl_var_c, assert_variable_notin, &e); -tree_walk(acl_var_m, assert_variable_notin, &e); +tree_walk(acl_var_c, (twalk_compat) assert_variable_notin, &e); +tree_walk(acl_var_m, (twalk_compat) assert_variable_notin, &e); /* check auth variables. assert_variable_notin() treats as const, so deconst is safe. */ @@ -9056,10 +9059,13 @@ for (var_entry * v = var_table; v < var_table + nelem(var_table); v++) assert_variable_notin(US v->name, *(USS v->value), &e); /* check dns and address trees */ -tree_walk(tree_dns_fails, assert_variable_notin, &e); -tree_walk(tree_duplicates, assert_variable_notin, &e); -tree_walk(tree_nonrecipients, assert_variable_notin, &e); -tree_walk(tree_unusable, assert_variable_notin, &e); +tree_walk(tree_dns_fails, (twalk_compat) assert_variable_notin, &e); +tree_walk(tree_duplicates, (twalk_compat) assert_variable_notin, &e); +tree_walk(tree_nonrecipients, (twalk_compat) assert_variable_notin, &e); +tree_walk(tree_unusable, (twalk_compat) assert_variable_notin, &e); + +/* check address-lists */ +check_deliver_addrs_not_freed(assert_variable_notin, &e); if (e.var_name) log_write_die(0, LOG_MAIN, diff --git a/src/src/functions.h b/src/src/functions.h index a29dddbed..223723b81 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -144,6 +144,7 @@ extern void bits_set(unsigned int *, size_t, int *); extern void cancel_cutthrough_connection(BOOL, const uschar *); extern gstring *cat_file(FILE *, gstring *, const uschar *); extern gstring *cat_file_tls(void *, gstring *, const uschar *); +extern void check_deliver_addrs_not_freed(void (*)(const uschar*, const uschar*, void*), void *); extern int check_host(void *, const uschar *, const uschar **, uschar **); extern uschar **child_exec_exim(int, BOOL, int *, BOOL, int, ...); extern pid_t child_open_exim_function(int *, const uschar *); diff --git a/src/src/transport.c b/src/src/transport.c index d88653964..a59a5ae3d 100644 --- a/src/src/transport.c +++ b/src/src/transport.c @@ -705,10 +705,10 @@ Returns: FALSE if writing failed */ static BOOL -write_env_to(address_item *p, struct aci **pplist, struct aci **pdlist, - BOOL *first, transport_ctx * tctx) +write_env_to(address_item * p, struct aci ** pplist, struct aci ** pdlist, + BOOL * first, transport_ctx * tctx) { -address_item *pp; +address_item * pp; struct aci *ppp; /* Do nothing if we have already handled this address. If not, remember it @@ -725,8 +725,7 @@ ppp->ptr = p; for (pp = p;; pp = pp->parent) { - address_item *dup; - for (dup = addr_duplicate; dup; dup = dup->next) + for (address_item * dup = addr_duplicate; dup; dup = dup->next) if (dup->dupof == pp) /* a dup of our address */ if (!write_env_to(dup, pplist, pdlist, first, tctx)) return FALSE; commit baad2e56cdcffa83e2e5d138537dcef858bdf5b6 Author: Jeremy Harris Date: Fri Oct 31 12:59:39 2025 +0000 Fix duplicate address processing vs. continued-transport Broken-by: 79344067b96a diff --git a/src/src/deliver.c b/src/src/deliver.c index 94dc092a3..29c5dbfc6 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -8980,7 +8980,9 @@ report_time_since(×tamp_startup, US"delivery end"); /* testcase 0005 */ if (final_yield == DELIVER_ATTEMPTED_NORMAL && *continue_next_id) { addr_defer = addr_failed = addr_succeed = NULL; + tree_duplicates = NULL; /* discard dups info from old message */ + addr_duplicate = NULL; spool_clear_header_globals(); deliver_set_expansions(NULL); commit d021d9bddfbe66cfec7027999aa3ee501198e20a Author: Jeremy Harris Date: Sun Nov 2 10:12:55 2025 +0000 tidying diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 80fb419d0..154d3be38 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -2163,10 +2163,7 @@ if (host) /* For client-side sessions we allocate a context. This lets us run several in parallel. */ - int old_pool = store_pool; - store_pool = POOL_PERM; - state = store_get(sizeof(exim_gnutls_state_st), GET_UNTAINTED); - store_pool = old_pool; + state = store_get_perm(sizeof(exim_gnutls_state_st), GET_UNTAINTED); memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); state->lib_state = ob->tls_preload; @@ -2806,7 +2803,7 @@ char sni_name[MAX_HOST_LEN]; size_t data_len = MAX_HOST_LEN; exim_gnutls_state_st *state = &state_server; unsigned int sni_type; -int rc, old_pool; +int rc; uschar * dummy_errstr; rc = gnutls_server_name_get(session, sni_name, &data_len, &sni_type, 0); @@ -2828,10 +2825,7 @@ if (sni_type != GNUTLS_NAME_DNS) } /* We now have a UTF-8 string in sni_name */ -old_pool = store_pool; -store_pool = POOL_PERM; -state->received_sni = string_copy_taint(US sni_name, GET_TAINTED); -store_pool = old_pool; +state->received_sni = string_copy_perm(US sni_name, TRUE); /* We set this one now so that variable expansions below will work */ state->tlsp->sni = state->received_sni; @@ -4386,8 +4380,9 @@ host_item * h; int old_pool = store_pool; store_pool = POOL_PERM; -state = store_get(sizeof(exim_gnutls_state_st), GET_UNTAINTED); -h = store_get(sizeof(host_item), GET_UNTAINTED); +state = store_get(sizeof(exim_gnutls_state_st) + sizeof(host_item), + GET_UNTAINTED); +h = ((host_item *)state) + 1; memset(h, 0, sizeof(host_item)); h->name = h->address = string_copy(ipaddr); @@ -4419,12 +4414,8 @@ store_pool = old_pool; void tls_state_out_to_in(int newfd, const uschar * ipaddr, int port) { -host_item * h; -int old_pool = store_pool; +host_item * h = store_get_perm(sizeof(host_item), GET_UNTAINTED); -store_pool = POOL_PERM; -h = store_get(sizeof(host_item), GET_UNTAINTED); -store_pool = old_pool; memset(h, 0, sizeof(host_item)); h->name = h->address = string_copy(ipaddr); h->port = port; diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index d5a6d368d..ddc72bfa2 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -2231,7 +2231,6 @@ tls_servername_cb(SSL * s, int * ad ARG_UNUSED, void * arg) const char * servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); exim_openssl_state_st * state = (exim_openssl_state_st *) arg; int rc; -int old_pool = store_pool; uschar * errstr; if (!servername) @@ -2241,9 +2240,7 @@ DEBUG(D_tls) debug_printf("Received TLS SNI %q%s\n", servername, reexpand_tls_files_for_sni ? "" : " (unused for certificate selection)"); /* Make the extension value available for expansion */ -store_pool = POOL_PERM; -tls_in.sni = string_copy_taint(US servername, GET_TAINTED); -store_pool = old_pool; +tls_in.sni = string_copy_perm(US servername, TRUE); if (!reexpand_tls_files_for_sni) return SSL_TLSEXT_ERR_OK; @@ -3119,13 +3116,8 @@ return cipher_stdname(id >> 8, id & 0xff); static const uschar * tlsver_name(const SSL * ssl) { -const uschar * s; -uschar * p; -int pool = store_pool; +uschar * s = string_copy_perm(US SSL_get_version(ssl), FALSE), * p; -store_pool = POOL_PERM; -s = string_copy(US SSL_get_version(ssl)); -store_pool = pool; if ((p = Ustrchr(s, 'v'))) /* TLSv1.2 -> TLS1.2 */ for (;; p++) if (!(*p = p[1])) break; return CUS s; @@ -3151,12 +3143,8 @@ if (tlsp->peercert) { DEBUG(D_tls) debug_printf("X509_NAME_oneline() error\n"); } else { - int oldpool = store_pool; - peerdn[siz-1] = '\0'; /* paranoia */ - store_pool = POOL_PERM; - tlsp->peerdn = string_copy(peerdn); - store_pool = oldpool; + tlsp->peerdn = string_copy_perm(peerdn, TRUE); /* We used to set CV in the cert-verify callbacks (either plain or dane) but they don't get called on session-resumption. So use the official @@ -4237,11 +4225,9 @@ BOOL request_ocsp = FALSE; BOOL require_ocsp = FALSE; #endif -rc = store_pool; -store_pool = POOL_PERM; -exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx), GET_UNTAINTED); +exim_client_ctx = store_get_perm(sizeof(exim_openssl_client_tls_ctx), + GET_UNTAINTED); exim_client_ctx->corked = NULL; -store_pool = rc; #ifdef SUPPORT_DANE tlsp->tlsa_usage = 0; @@ -5268,18 +5254,16 @@ void tls_state_in_to_out(int newfd, const uschar * ipaddr, int port) { exim_openssl_client_tls_ctx * exim_client_ctx; -int old_pool = store_pool; state_server.is_server = FALSE; state_server.tlsp = &tls_out; client_static_state = &state_server; -store_pool = POOL_PERM; -exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx), GET_UNTAINTED); +exim_client_ctx = store_get_perm(sizeof(exim_openssl_client_tls_ctx), + GET_UNTAINTED); exim_client_ctx->ctx = client_static_state->lib_state.lib_ctx; exim_client_ctx->ssl = client_static_state->lib_state.lib_ssl; exim_client_ctx->corked = NULL; -store_pool = old_pool; SSL_set_fd(exim_client_ctx->ssl, newfd); commit 195bf3719bb6d673f6730b221cfcd0dfec0281b4 Author: Jeremy Harris Date: Sun Nov 2 14:31:22 2025 +0000 Revert "Retire identd support" This reverts commit 106c3eb8ee31297588d5ec0555195be966dfd7b2. diff --git a/src/src/configure.default b/src/src/configure.default index c05ed182e..633c6539e 100644 --- a/src/src/configure.default +++ b/src/src/configure.default @@ -260,6 +260,21 @@ host_lookup = * dns_dnssec_ok = 1 +# The settings below cause Exim to make RFC 1413 (ident) callbacks +# for all incoming SMTP calls. You can limit the hosts to which these +# calls are made, and/or change the timeout that is used. If you set +# the timeout to zero, all RFC 1413 calls are disabled. RFC 1413 calls +# are cheap and can provide useful information for tracing problem +# messages, but some hosts and firewalls have problems with them. +# This can result in a timeout instead of an immediate refused +# connection, leading to delays on starting up SMTP sessions. +# (The default was reduced from 30s to 5s for release 4.61. and to +# disabled for release 4.86) +# +#rfc1413_hosts = * +#rfc1413_query_timeout = 5s + + # Enable an efficiency feature. We advertise the feature; clients # may request to use it. For multi-recipient mails we then can # reject or accept per-user after the message is received. diff --git a/src/src/daemon.c b/src/src/daemon.c index 824fe0187..a31ef1eb5 100644 --- a/src/src/daemon.c +++ b/src/src/daemon.c @@ -479,9 +479,20 @@ if (pid == 0) signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); - /* Set up the fullhost information in case there is no HELO/EHLO. */ + /* Attempt to get an id from the sending machine via the RFC 1413 + protocol. We do this in the sub-process in order not to hold up the + main process if there is any delay. Then set up the fullhost information + in case there is no HELO/EHLO. + If debugging is enabled only for the daemon, we must turn if off while + finding the id, but turn it on again afterwards so that information about the + incoming connection is output. */ + + if (f.debug_daemon) debug_selector = 0; + verify_get_ident(IDENT_PORT); host_build_sender_fullhost(); + debug_selector = save_debug_selector; + DEBUG(D_any) debug_printf("Process %d is handling incoming connection from %s\n", (int)getpid(), sender_fullhost); diff --git a/src/src/exim.c b/src/src/exim.c index 88ed33a8e..df343b5a1 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -5465,7 +5465,9 @@ if (raw_active_hostname) /* Handle host checking: this facility mocks up an incoming SMTP call from a given IP address so that the blocking and relay configuration can be tested. Unless a sender_ident was set by -oMt, we discard it (the default is the -caller's login name). */ +caller's login name). An RFC 1413 call is made only if we are running in the +test harness and an incoming interface and both ports are specified, because +there is no TCP/IP call to find the ident for. */ if (host_checking) { @@ -5473,7 +5475,12 @@ if (host_checking) int size; if (!sender_ident_set) + { sender_ident = NULL; + if (f.running_in_test_harness && sender_host_port + && interface_address && interface_port) + verify_get_ident(test_harness_identd_port); + } /* In case the given address is a non-canonical IPv6 address, canonicalize it. Use the compressed form for IPv6. */ @@ -5496,6 +5503,7 @@ if (host_checking) debug_file = stderr; debug_fd = fileno(debug_file); dprintf(smtp_out_fd, "\n**** SMTP testing session as if from host %s\n" + "**** but without any ident (RFC 1413) callback.\n" "**** This is not for real!\n\n", sender_host_address); @@ -5607,12 +5615,14 @@ sendmail error modes other than -oem ever actually used? Later: yes.) */ if (!smtp_input) error_handling = arg_error_handling; /* If this is an inetd call, ensure that stderr is closed to prevent panic -logging being sent down the socket. */ +logging being sent down the socket and make an identd call to get the +sender_ident. */ else if (f.is_inetd && !atrn_mode) { (void)fclose(stderr); exim_nullstd(); /* Re-open to /dev/null */ + verify_get_ident(IDENT_PORT); host_build_sender_fullhost(); set_process_info("handling incoming connection from %s via inetd", sender_fullhost); diff --git a/src/src/exim.h b/src/src/exim.h index fa56f2c2c..5b931e9f4 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -156,6 +156,8 @@ configuration file. We also use this for some other short strings, such as queue names. Also TLS ciphersuite name (no real known limit since the protocols use integers, but max seen in reality is 45 octets). + +RFC 1413 gives us the 512 limit on IDENT protocol userids. */ #define EXIM_EMAILADDR_MAX 256 diff --git a/src/src/functions.h b/src/src/functions.h index 223723b81..98dc1a461 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -697,6 +697,7 @@ extern int verify_check_given_host(const uschar **, const host_item *); extern int verify_check_this_host(const uschar **, unsigned int *, const uschar*, const uschar *, const uschar **); extern address_item *verify_checked_sender(const uschar *); +extern void verify_get_ident(int); extern void verify_quota(uschar *); extern int verify_quota_call(const uschar *, int, int, uschar **); extern BOOL verify_sender(int *, uschar **); diff --git a/src/src/globals.c b/src/src/globals.c index be2b7ec13..c8636e191 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -707,6 +707,7 @@ bit_table debug_options[] = { /* must be in alphabetical order and use BIT_TABLE(D, filter), BIT_TABLE(D, hints_lookup), BIT_TABLE(D, host_lookup), + BIT_TABLE(D, ident), BIT_TABLE(D, interface), BIT_TABLE(D, lists), BIT_TABLE(D, load), @@ -989,6 +990,7 @@ bit_table log_options[] = { /* must be in alphabetical order, BIT_TABLE(L, dnssec), BIT_TABLE(L, etrn), BIT_TABLE(L, host_lookup_failed), + BIT_TABLE(L, ident_timeout), BIT_TABLE(L, incoming_interface), BIT_TABLE(L, incoming_port), BIT_TABLE(L, lost_incoming_connection), @@ -1238,6 +1240,8 @@ int retry_maximum_timeout = 0; /* set from retry config */ retry_config *retries = NULL; const uschar *return_path = NULL; int rewrite_existflags = 0; +uschar *rfc1413_hosts = US"@[]"; +int rfc1413_query_timeout = 0; uid_t root_gid = ROOT_GID; uid_t root_uid = ROOT_UID; @@ -1399,6 +1403,7 @@ uid_t system_filter_uid = (uid_t)-1; blob tcp_fastopen_nodata = { .data = NULL, .len = 0 }; tfo_state_t tcp_out_fastopen = TFO_NOT_USED; +int test_harness_identd_port = IDENT_PORT; int test_harness_load_avg = 0; int thismessage_size_limit = 0; int timeout_frozen_after = 0; diff --git a/src/src/globals.h b/src/src/globals.h index ccd790cc2..ddf78b4d8 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -919,6 +919,8 @@ extern int retry_maximum_timeout; /* The maximum timeout */ extern const uschar *return_path; /* Return path for a message */ extern BOOL return_path_remove; /* Remove return-path headers */ extern int rewrite_existflags; /* Indicate which headers have rewrites */ +extern uschar *rfc1413_hosts; /* RFC hosts */ +extern int rfc1413_query_timeout; /* Timeout on RFC 1413 calls */ /* extern BOOL rfc821_domains; */ /* If set, syntax is 821, not 822 => being abolished */ extern uid_t root_gid; /* The gid for root */ extern uid_t root_uid; /* The uid for root */ @@ -1059,6 +1061,7 @@ extern BOOL system_filter_uid_set; /* TRUE if uid set */ extern blob tcp_fastopen_nodata; /* for zero-data TFO connect requests */ extern BOOL tcp_nodelay; /* Controls TCP_NODELAY on daemon */ extern tfo_state_t tcp_out_fastopen; /* TCP fast open */ +extern int test_harness_identd_port; /* For use when testing */ extern int test_harness_load_avg; /* For use when testing */ extern int thismessage_size_limit; /* Limit for this message */ extern int timeout_frozen_after; /* Max time to keep frozen messages */ diff --git a/src/src/macros.h b/src/src/macros.h index 2e0efc9b8..75563d86b 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -90,6 +90,10 @@ don't make the file descriptors two-way. */ #define pipe_read 0 #define pipe_write 1 +/* The RFC 1413 ident port */ + +#define IDENT_PORT 113 + /* A macro to simplify testing bits in lookup types */ #define mac_islookup(li,b) ((li)->type & (b)) @@ -381,25 +385,26 @@ enum { DEBUG_BIT(filter), DEBUG_BIT(hints_lookup), DEBUG_BIT(host_lookup), + DEBUG_BIT(ident), DEBUG_BIT(interface), DEBUG_BIT(lists), - DEBUG_BIT(load), - DEBUG_BIT(lookup), /* 15 */ + DEBUG_BIT(load), /* 15 */ + DEBUG_BIT(lookup), DEBUG_BIT(memory), DEBUG_BIT(noutf8), DEBUG_BIT(pid), DEBUG_BIT(process_info), DEBUG_BIT(queue_run), DEBUG_BIT(receive), - DEBUG_BIT(resolver), - DEBUG_BIT(retry), /* 23 */ + DEBUG_BIT(resolver), /* 23 */ + DEBUG_BIT(retry), DEBUG_BIT(rewrite), DEBUG_BIT(route), DEBUG_BIT(timestamp), DEBUG_BIT(tls), DEBUG_BIT(transport), DEBUG_BIT(uid), - DEBUG_BIT(verify), /* 30 - one spare! */ + DEBUG_BIT(verify), /* 31 */ }; /* Multi-bit debug masks */ @@ -469,6 +474,7 @@ enum logbit { Li_dkim, Li_dkim_verbose, Li_dnssec, + Li_ident_timeout, Li_incoming_interface, Li_incoming_port, Li_millisec, diff --git a/src/src/readconf.c b/src/src/readconf.c index f4da20b6a..1fe2b2bfc 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -298,6 +298,9 @@ static optionlist optionlist_config[] = { { "retry_interval_max", opt_time, {&retry_interval_max} }, { "return_path_remove", opt_bool, {&return_path_remove} }, { "return_size_limit", opt_mkint|opt_hidden, {&bounce_return_size_limit} }, + { "rfc1413_hosts", opt_stringptr, {&rfc1413_hosts} }, + { "rfc1413_port", opt_int|opt_hidden, {&test_harness_identd_port} }, + { "rfc1413_query_timeout", opt_time, {&rfc1413_query_timeout} }, { "sender_unqualified_hosts", opt_stringptr, {&sender_unqualified_hosts} }, { "slow_lookup_log", opt_int, {&slow_lookup_log} }, { "smtp_accept_keepalive", opt_bool, {&smtp_accept_keepalive} }, diff --git a/src/src/verify.c b/src/src/verify.c index a3ade640b..a12e74ce6 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -2779,6 +2779,162 @@ return yield; +/************************************************* +* Get RFC 1413 identification * +*************************************************/ + +/* Attempt to get an id from the sending machine via the RFC 1413 protocol. If +the timeout is set to zero, then the query is not done. There may also be lists +of hosts and nets which are exempt. To guard against malefactors sending +non-printing characters which could, for example, disrupt a message's headers, +make sure the string consists of printing characters only. + +Argument: + port the port to connect to; usually this is IDENT_PORT (113), but when + running in the test harness with -bh a different value is used. + +Returns: nothing + +Side effect: any received ident value is put in sender_ident (NULL otherwise) +*/ + +void +verify_get_ident(int port) +{ +client_conn_ctx ident_conn_ctx = {0}; +int host_af, qlen; +int received_sender_port, received_interface_port, n; +uschar *p; +blob early_data; +uschar buffer[2048]; + +/* Default is no ident. Check whether we want to do an ident check for this +host. */ + +sender_ident = NULL; +if (rfc1413_query_timeout <= 0 || verify_check_host(&rfc1413_hosts) != OK) + return; + +DEBUG(D_ident) debug_printf("doing ident callback\n"); + +/* Set up a connection to the ident port of the remote host. Bind the local end +to the incoming interface address. If the sender host address is an IPv6 +address, the incoming interface address will also be IPv6. */ + +host_af = Ustrchr(sender_host_address, ':') == NULL ? AF_INET : AF_INET6; +if ((ident_conn_ctx.sock = ip_socket(SOCK_STREAM, host_af)) < 0) return; + +if (ip_bind(ident_conn_ctx.sock, host_af, interface_address, 0) < 0) + { + DEBUG(D_ident) debug_printf("bind socket for ident failed: %s\n", + strerror(errno)); + goto END_OFF; + } + +/* Construct and send the query. */ + +qlen = snprintf(CS buffer, sizeof(buffer), "%d , %d\r\n", + sender_host_port, interface_port); +early_data.data = buffer; +early_data.len = qlen; + +/*XXX we trust that the query is idempotent */ +if (ip_connect(ident_conn_ctx.sock, host_af, sender_host_address, port, + rfc1413_query_timeout, &early_data) < 0) + { + if (errno == ETIMEDOUT && LOGGING(ident_timeout)) + log_write(0, LOG_MAIN, "ident connection to %s timed out", + sender_host_address); + else + DEBUG(D_ident) debug_printf("ident connection to %s failed: %s\n", + sender_host_address, strerror(errno)); + goto END_OFF; + } + +/* Read a response line. We put it into the rest of the buffer, using several +recv() calls if necessary. */ + +p = buffer + qlen; + +for (;;) + { + uschar *pp; + int count; + int size = sizeof(buffer) - (p - buffer); + + if (size <= 0) goto END_OFF; /* Buffer filled without seeing \n. */ + count = ip_recv(&ident_conn_ctx, p, size, time(NULL) + rfc1413_query_timeout); + if (count <= 0) goto END_OFF; /* Read error or EOF */ + + /* Scan what we just read, to see if we have reached the terminating \r\n. Be + generous, and accept a plain \n terminator as well. The only illegal + character is 0. */ + + for (pp = p; pp < p + count; pp++) + { + if (*pp == 0) goto END_OFF; /* Zero octet not allowed */ + if (*pp == '\n') + { + if (pp[-1] == '\r') pp--; + *pp = 0; + goto GOT_DATA; /* Break out of both loops */ + } + } + + /* Reached the end of the data without finding \n. Let the loop continue to + read some more, if there is room. */ + + p = pp; + } + +GOT_DATA: + +/* We have received a line of data. Check it carefully. It must start with the +same two port numbers that we sent, followed by data as defined by the RFC. For +example, + + 12345 , 25 : USERID : UNIX :root + +However, the amount of white space may be different to what we sent. In the +"osname" field there may be several sub-fields, comma separated. The data we +actually want to save follows the third colon. Some systems put leading spaces +in it - we discard those. */ + +if (sscanf(CS buffer + qlen, "%d , %d%n", &received_sender_port, + &received_interface_port, &n) != 2 || + received_sender_port != sender_host_port || + received_interface_port != interface_port) + goto END_OFF; + +p = buffer + qlen + n; +Uskip_whitespace(&p); +if (*p++ != ':') goto END_OFF; +Uskip_whitespace(&p); +if (Ustrncmp(p, "USERID", 6) != 0) goto END_OFF; +p += 6; +Uskip_whitespace(&p); +if (*p++ != ':') goto END_OFF; +while (*p && *p != ':') p++; +if (!*p++) goto END_OFF; +Uskip_whitespace(&p); +if (!*p) goto END_OFF; + +/* The rest of the line is the data we want. We turn it into printing +characters when we save it, so that it cannot mess up the format of any logging +or Received: lines into which it gets inserted. We keep a maximum of 127 +characters. The deconst cast is ok as we fed a nonconst to string_printing() */ + +sender_ident = US string_printing(string_copyn(p, 127)); +DEBUG(D_ident) debug_printf("sender_ident = %s\n", sender_ident); + +END_OFF: +(void)close(ident_conn_ctx.sock); +return; +} + + + + /************************************************* * Match host to a single host-list item * *************************************************/ commit e35ada6dc21d111ac30c30e8b9d792fbca55c9b4 Author: Jeremy Harris Date: Sun Nov 2 14:34:25 2025 +0000 TLS: log key-exchange group diff --git a/src/src/exim.h b/src/src/exim.h index 5b931e9f4..18d1b1f40 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -155,7 +155,8 @@ The driver name is a name of a router/transport/authenticator etc in the configuration file. We also use this for some other short strings, such as queue names. Also TLS ciphersuite name (no real known limit since the protocols use -integers, but max seen in reality is 45 octets). +integers, but max seen so far is 70 octets. Likely to increase further with +postquantum methods). RFC 1413 gives us the 512 limit on IDENT protocol userids. */ @@ -168,7 +169,7 @@ RFC 1413 gives us the 512 limit on IDENT protocol userids. #define EXIM_HUMANNAME_MAX 256 #define EXIM_DISPLAYMAIL_MAX 1024 #define EXIM_DRIVERNAME_MAX 64 -#define EXIM_CIPHERNAME_MAX 64 +#define EXIM_CIPHERNAME_MAX 128 #define EXIM_IDENTUSER_MAX 512 diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 154d3be38..f63d2ee12 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -57,11 +57,6 @@ require current GnuTLS, then we'll drop support for the ancient libraries). #if GNUTLS_VERSION_NUMBER >= 0x030000 # define SUPPORT_SELFSIGN /* Uncertain what version is first usable but 2.12.23 is not */ #endif -#if GNUTLS_VERSION_NUMBER >= 0x030306 -# define SUPPORT_CA_DIR -#else -# undef SUPPORT_CA_DIR -#endif #if GNUTLS_VERSION_NUMBER >= 0x030014 # define SUPPORT_SYSDEFAULT_CABUNDLE #endif @@ -78,6 +73,11 @@ require current GnuTLS, then we'll drop support for the ancient libraries). # define GNUTLS_AUTO_GLOBAL_INIT # define GNUTLS_AUTO_PKCS11_MANUAL #endif +#if GNUTLS_VERSION_NUMBER >= 0x030306 +# define SUPPORT_CA_DIR +#else +# undef SUPPORT_CA_DIR +#endif #if (GNUTLS_VERSION_NUMBER >= 0x030404) \ || (GNUTLS_VERSION_NUMBER >= 0x030311) && (GNUTLS_VERSION_NUMBER & 0xffff00 == 0x030300) # ifndef DISABLE_OCSP @@ -90,6 +90,9 @@ require current GnuTLS, then we'll drop support for the ancient libraries). #if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP) # define SUPPORT_SRV_OCSP_STACK #endif +#if GNUTLS_VERSION_NUMBER >= 0x030600 +# define EXIM_TLS_KEX_GROUP +#endif #if GNUTLS_VERSION_NUMBER >= 0x030603 # define EXIM_HAVE_TLS1_3 # define SUPPORT_GNUTLS_EXT_RAW_PARSE @@ -2385,11 +2388,15 @@ kx = old_pool = store_pool; { tls_support * tlsp = state->tlsp; + gstring * g = NULL; +#ifdef EXIM_TLS_KEX_GROUP + const char * kex_gp = gnutls_group_get_name(gnutls_group_get(session)); +#endif + store_pool = POOL_PERM; #ifdef SUPPORT_GNUTLS_SESS_DESC { - gstring * g = NULL; uschar * s = US gnutls_session_get_desc(session), c; if (!s) @@ -2424,15 +2431,25 @@ old_pool = store_pool; if ((c = *s) && *++s == '-') g = string_catn(g, US"__", 2); /* now on _ between groups */ } - g = string_catn(g, US":", 1); - g = string_cat(g, string_sprintf("%d", (int) gnutls_cipher_get_key_size(cipher) * 8)); + g = string_fmt_append(g, ":%d", + (int) gnutls_cipher_get_key_size(cipher) * 8); + +# ifdef EXIM_TLS_KEX_GROUP + if (kex_gp) + g = string_fmt_append(g, ":%s", kex_gp); +# endif state->ciphersuite = string_from_gstring(g); } #else - state->ciphersuite = string_sprintf("%s:%s:%d", - gnutls_protocol_get_name(protocol), - gnutls_cipher_suite_get_name(kx, cipher, mac), - (int) gnutls_cipher_get_key_size(cipher) * 8); + g = string_fmt_append(NULL, "%s:%s:%d", + gnutls_protocol_get_name(protocol), + gnutls_cipher_suite_get_name(kx, cipher, mac), + (int) gnutls_cipher_get_key_size(cipher) * 8); +# ifdef EXIM_TLS_KEX_GROUP + if (kex_gp) + g = string_fmt_append(g, ":%s", kex_gp); +# endif + state->ciphersuite = string_from_gstring(g); /* I don't see a way that spaces could occur, in the current GnuTLS code base, but it was a concern in the old code and perhaps older GnuTLS @@ -2441,7 +2458,7 @@ old_pool = store_pool; for (uschar * p = state->ciphersuite; *p; p++) if (isspace(*p)) *p = '-'; tlsp->ver = string_copyn(state->ciphersuite, Ustrchr(state->ciphersuite, ':') - state->ciphersuite); -#endif +#endif /*SUPPORT_GNUTLS_SESS_DESC*/ /* debug_printf("peer_status: ciphersuite %s\n", state->ciphersuite); */ diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index ddc72bfa2..eea1f15d3 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -86,6 +86,9 @@ change this guard and punt the issue for a while longer. */ # if OPENSSL_VERSION_NUMBER >= 0x010101000L # define EXIM_TLS_EARLY_BANNER # endif +# if OPENSSL_VERSION_NUMBER >= 0x030200000L +# define EXIM_TLS_KEX_GROUP +# endif # if OPENSSL_VERSION_NUMBER < 0x030200020L # define EXIM_OPENSSL_BOGUS_SERVER_ALPN /*XXX when was this fixed? */ # endif @@ -3070,8 +3073,11 @@ return OK; *************************************************/ /* -Argument: pointer to an SSL structure for the connection - pointer to number of bits for cipher +Arguments: + ssl pointer to an SSL structure for the connection + ver TLS version string + bits pointer for return of number of bits for cipher + Returns: pointer to allocated string in perm-pool */ @@ -3089,7 +3095,15 @@ uschar * s; SSL_CIPHER_get_bits(c, bits); store_pool = POOL_PERM; -s = string_sprintf("%s:%s:%u", ver, SSL_CIPHER_get_name(c), *bits); + { +#ifdef EXIM_TLS_KEX_GROUP + const char * cs = SSL_get0_group_name(ssl); + if (cs) + s = string_sprintf("%s:%s:%u:%s", ver, SSL_CIPHER_get_name(c), *bits, cs); + else +#endif + s = string_sprintf("%s:%s:%u", ver, SSL_CIPHER_get_name(c), *bits); + } store_pool = pool; DEBUG(D_tls) debug_printf("Cipher: %s\n", s); return s; commit b93ee3883ef8a11c440c5519812f3cb6c074a02f Author: Jeremy Harris Date: Sun Nov 2 19:58:29 2025 +0000 Build: quieten sqlite-hints -Wunused-function build diff --git a/src/src/hintsdb.h b/src/src/hintsdb.h index 92cb91909..d2d30969b 100644 --- a/src/src/hintsdb.h +++ b/src/src/hintsdb.h @@ -74,8 +74,8 @@ extern void debug_printf_indent(const char *, ...) PRINTF_FUNCTION(1,2); # error USE_SQLITE conflict with alternate definition # endif # include "hintsdb/hints_sqlite.h" -#elif defined(USE_TDB) +#elif defined(USE_TDB) # if defined(USE_DB) || defined(USE_GDBM) || defined(USE_SQLITE) # error USE_TDB conflict with alternate definition # endif diff --git a/src/src/hintsdb/hints_sqlite.h b/src/src/hintsdb/hints_sqlite.h index 9e1e90322..9e4f65a51 100644 --- a/src/src/hintsdb/hints_sqlite.h +++ b/src/src/hintsdb/hints_sqlite.h @@ -354,17 +354,17 @@ exim_dbclose_multi__(dbp); /* Datum access */ -static uschar * +static inline uschar * exim_datum_data_get(EXIM_DATUM * dp) { return US dp->data; } -static void +static inline void exim_datum_data_set(EXIM_DATUM * dp, void * s) { dp->data = s; } -static unsigned +static inline unsigned exim_datum_size_get(EXIM_DATUM * dp) { return dp->len; } -static void +static inline void exim_datum_size_set(EXIM_DATUM * dp, unsigned n) { dp->len = n; } commit c7b6065cb9b945155491477297662bae458919d4 Author: Jeremy Harris Date: Sun Nov 2 21:06:43 2025 +0000 RFC 2047: fix encode operation. Bug 3168 diff --git a/src/src/parse.c b/src/src/parse.c index d840beb6d..cdb1e0c1e 100644 --- a/src/src/parse.c +++ b/src/src/parse.c @@ -882,13 +882,10 @@ const uschar * parse_quote_2047(const uschar * string, int len, const uschar * charset, BOOL fold) { -int hlen, line_off; -BOOL coded = FALSE; -BOOL first_byte = FALSE; -gstring * g = - string_fmt_append(NULL, "=?%s?Q?%n", charset ? charset : US"iso-8859-1", &hlen); - -line_off = hlen; +int line_off = 0, hlen; +BOOL coded = FALSE, first_byte = FALSE; +gstring * g = string_fmt_append(NULL, "=?%s?Q?%n", + charset ? charset : US"iso-8859-1", &hlen); for (const uschar * s = string; len > 0; s++, len--) { @@ -898,7 +895,7 @@ for (const uschar * s = string; len > 0; s++, len--) { g = fold ? string_catn(g, US"?=\n ", 4) : string_catn(g, US"?= ", 3); line_off = g->ptr; - g = string_catn(g, g->s, hlen); + g = string_catn(g, g->s, hlen); /* dup the leader */ } if ( ch < 33 || ch > 126 commit dd081f9869df90ab7f7d7911c1bdd3ef976f1439 Author: Jeremy Harris Date: Wed Oct 29 12:13:09 2025 +0000 constify diff --git a/src/src/acl.c b/src/src/acl.c index 36fb850c8..7d07ab698 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -3939,7 +3939,7 @@ for (; cb; cb = cb->next) /* See comment on ACLC_SPF wrt. coding issues */ { misc_module_info * mi = misc_mod_find(US"dmarc", &log_message); - typedef uschar * (*efn_t)(int); + typedef const uschar * (*efn_t)(void); const uschar * expanded_query; if (!mi) @@ -3956,8 +3956,7 @@ for (; cb; cb = cb->next) view into the process in the future. */ /*XXX is this call used with any other arg? */ - expanded_query = (((efn_t *) mi->functions)[DMARC_EXPAND_QUERY]) - (DMARC_VERIFY_STATUS); + expanded_query = (((efn_t *) mi->functions)[DMARC_EXPAND_QUERY]) (); rc = match_isinlist(expanded_query, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); } diff --git a/src/src/functions.h b/src/src/functions.h index 98dc1a461..4801eb8b5 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -374,15 +374,17 @@ extern int misc_mod_msg_init(void); extern void misc_mod_smtp_reset(void); extern uschar *moan_check_errorcopy(const uschar *); -extern BOOL moan_skipped_syntax_errors(uschar *, error_block *, uschar *, - BOOL, uschar *); +extern BOOL moan_skipped_syntax_errors(const uschar *, const error_block *, + const uschar *, BOOL, const uschar *); extern void moan_smtp_batch(const uschar *, const char *, ...) PRINTF_FUNCTION(2,3); -extern BOOL moan_send_message(const uschar *, int, error_block *eblock, - header_line *, FILE *, const uschar *); -extern void moan_tell_someone(const uschar *, address_item *, - const uschar *, const char *, ...) PRINTF_FUNCTION(4,5); -extern BOOL moan_to_sender(int, error_block *, header_line *, FILE *, BOOL); +extern BOOL moan_send_message(const uschar *, int, + const error_block * eblock, const header_line *, + FILE *, const uschar *); +extern void moan_tell_someone(const uschar *, const address_item *, + const uschar *, const char *, ...) PRINTF_FUNCTION(4,5); +extern BOOL moan_to_sender(int, const error_block *, const header_line *, + FILE *, BOOL); extern void moan_write_from(FILE *); extern void moan_write_references(FILE *, uschar *); #ifdef LOOKUP_MODULE_DIR diff --git a/src/src/miscmods/dmarc.c b/src/src/miscmods/dmarc.c index bc96607e0..185ef9f4f 100644 --- a/src/src/miscmods/dmarc.c +++ b/src/src/miscmods/dmarc.c @@ -60,10 +60,10 @@ static dmarc_exim_p dmarc_policy_description[] = { /* $variables */ BOOL dmarc_alignment_dkim = FALSE; /* Subtest result */ BOOL dmarc_alignment_spf = FALSE; /* Subtest result */ -uschar * dmarc_domain_policy = NULL; /* Declared policy of used domain */ -uschar * dmarc_status = NULL; /* One word value */ -uschar * dmarc_status_text = NULL; /* Human readable value */ -uschar * dmarc_used_domain = NULL; /* Domain libopendmarc chose for DMARC policy lookup */ +const uschar * dmarc_domain_policy = NULL; /* Declared policy of used domain */ +const uschar * dmarc_status; /* One word value */ +const uschar * dmarc_status_text = NULL; /* Human readable value */ +uschar * dmarc_used_domain; /* Domain libopendmarc chose for DMARC policy lookup */ /* options */ uschar * dmarc_forensic_sender = NULL; /* Set sender address for forensic reports */ @@ -108,9 +108,9 @@ end, and append the two strings passed to it. Used for adding variable amounts of value:pair data to the forensic emails. */ static error_block * -add_to_eblock(error_block *eblock, uschar *t1, uschar *t2) +add_to_eblock(error_block * eblock, const uschar * t1, const uschar * t2) { -error_block *eb = store_malloc(sizeof(error_block)); +error_block * eb = store_malloc(sizeof(error_block)); if (!eblock) eblock = eb; else @@ -645,10 +645,7 @@ The EDITME provides a DMARC_API variable */ libdm_status = opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans); for (c = 0; dmarc_policy_description[c].name; c++) if (tmp_ans == dmarc_policy_description[c].value) - { - dmarc_domain_policy = string_sprintf("%s",dmarc_policy_description[c].name); - break; - } + { dmarc_domain_policy = dmarc_policy_description[c].name; break; } /* Can't use exim's string manipulation functions so allocate memory for libopendmarc using its max hostname length definition. */ @@ -743,23 +740,19 @@ if (!f.dmarc_disable_verify) return OK; } -static uschar * -dmarc_exim_expand_defaults(int what) +static const uschar * +dmarc_exim_expand_defaults(void) { -if (what == DMARC_VERIFY_STATUS) - return f.dmarc_disable_verify ? US"off" : US"none"; -return US""; +return f.dmarc_disable_verify ? US"off" : US"none"; } -static uschar * -dmarc_exim_expand_query(int what) +static const uschar * +dmarc_exim_expand_query(void) { if (f.dmarc_disable_verify || !dmarc_pctx) - return dmarc_exim_expand_defaults(what); + return dmarc_exim_expand_defaults(); -if (what == DMARC_VERIFY_STATUS) - return dmarc_status; -return US""; +return dmarc_status; } diff --git a/src/src/moan.c b/src/src/moan.c index efac33681..5635f93e2 100644 --- a/src/src/moan.c +++ b/src/src/moan.c @@ -159,8 +159,9 @@ Returns: TRUE if message successfully sent */ BOOL -moan_send_message(const uschar * recipient, int ident, error_block * eblock, - header_line * headers, FILE * message_file, const uschar * firstline) +moan_send_message(const uschar * recipient, int ident, + const error_block * eblock, const header_line * headers, + FILE * message_file, const uschar * firstline) { int written = 0, fd, status, count = 0, size_limit = bounce_return_size_limit; FILE * fp; @@ -389,7 +390,7 @@ if (bounce_return_message) while (headers) { - if (headers->text != NULL) fprintf(fp, "%s", CS headers->text); + if (headers->text) fprintf(fp, "%s", CS headers->text); headers = headers->next; } @@ -494,8 +495,8 @@ Returns: FALSE if there is no sender_address to send to; */ BOOL -moan_to_sender(int ident, error_block *eblock, header_line *headers, - FILE *message_file, BOOL check_sender) +moan_to_sender(int ident, const error_block * eblock, + const header_line * headers, FILE * message_file, BOOL check_sender) { uschar *firstline = NULL; uschar *msg = US"Error while reading message with no usable sender address"; @@ -601,7 +602,7 @@ Returns: nothing */ void -moan_tell_someone(const uschar * who, address_item * addr, +moan_tell_someone(const uschar * who, const address_item * addr, const uschar * subject, const char * format, ...) { FILE * f; @@ -814,14 +815,14 @@ Returns: FALSE if string expansion failed; TRUE otherwise */ BOOL -moan_skipped_syntax_errors(uschar *rname, error_block *eblock, - uschar *syntax_errors_to, BOOL some, uschar *custom) +moan_skipped_syntax_errors(const uschar * rname, const error_block * eblock, + const uschar * syntax_errors_to, BOOL some, const uschar * custom) { int pid, fd; const uschar * s; FILE * f; -for (error_block * e = eblock; e; e = e->next) +for (const error_block * e = eblock; e; e = e->next) if (e->text2) log_write(0, LOG_MAIN, "%s router: skipped error: %s in %q", rname, e->text1, e->text2); @@ -870,11 +871,10 @@ if (custom) fprintf(f, "The %s router encountered the following error(s):\n\n", rname); -for (error_block * e = eblock; e; e = e->next) +for (const error_block * e = eblock; e; e = e->next) { fprintf(f, " %s", e->text1); - if (e->text2 != NULL) - fprintf(f, " in the address\n \"%s\"", e->text2); + if (e->text2) fprintf(f, " in the address\n \"%s\"", e->text2); fprintf(f, "\n\n"); } diff --git a/src/src/structs.h b/src/src/structs.h index 147819dd5..a6261afcb 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -680,8 +680,8 @@ typedef struct { typedef struct error_block { struct error_block *next; - const uschar *text1; - uschar *text2; + const uschar * text1; + const uschar * text2; } error_block; /* Chain of file names when processing the queue */ commit dea05068da8cbd8cb9c9707aa8c3e432bd967e55 Author: Jeremy Harris Date: Mon Nov 3 16:40:26 2025 +0000 fix constification Broken-by: 96f8f12e212b diff --git a/src/src/exim.c b/src/src/exim.c index df343b5a1..c6fa25fc7 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -5320,7 +5320,7 @@ if (verify_address_mode || f.address_test_mode) while (*s) { BOOL finished = FALSE; - uschar *ss = parse_find_address_end(s, FALSE); + uschar * ss = parse_find_address_end(s, FALSE); if (*ss == ',') *ss = 0; else finished = TRUE; test_address(s, flags, &exit_value); s = ss; @@ -5875,7 +5875,7 @@ for (BOOL more = TRUE; more; ) uschar * errmess; /* There can be multiple addresses, so EXIM_DISPLAYMAIL_MAX (tuned for 1) is too short. We'll still want to cap it to something, just in case. */ - const uschar * s = string_copy_taint( + uschar * s = string_copy_taint( exim_str_fail_toolong(list[i], BIG_BUFFER_SIZE, "address argument"), GET_TAINTED); diff --git a/src/src/functions.h b/src/src/functions.h index 4801eb8b5..ef1c59dc3 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -398,7 +398,17 @@ extern uschar *parse_extract_address(const uschar *, uschar **, int *, int *, in BOOL); extern int parse_forward_list(const uschar *, int, address_item **, uschar **, const uschar *, const uschar *, error_block **); -extern uschar *parse_find_address_end(const uschar *, BOOL); + +extern const uschar * parse_find_address_end_gen(const uschar *, BOOL); +static inline uschar * parse_find_address_end_nc(uschar * s, BOOL b) +{ return US parse_find_address_end_gen(s, b); } +static inline const uschar * parse_find_address_end_c(const uschar * s, BOOL b) +{ return parse_find_address_end_gen(s, b); } +#define parse_find_address_end(X, B) _Generic((X), \ + uschar *: parse_find_address_end_nc, \ + const uschar *: parse_find_address_end_c \ + )(X, B) + extern const uschar *parse_find_at(const uschar *); extern const uschar *parse_fix_phrase(const uschar *, int); extern const uschar *parse_message_id(const uschar *, uschar **, uschar **); diff --git a/src/src/miscmods/exim_filter.c b/src/src/miscmods/exim_filter.c index 08124b3f7..21ede4ad5 100644 --- a/src/src/miscmods/exim_filter.c +++ b/src/src/miscmods/exim_filter.c @@ -2419,15 +2419,14 @@ while (commands) tt = to; while (*tt) { - uschar * ss = parse_find_address_end(tt, FALSE), * errmess; - const uschar * recipient; + const uschar * ss = parse_find_address_end(tt, FALSE); + const uschar * ttt, * recipient; + uschar * errmess; int start, end, domain; - int temp = *ss; - *ss = 0; - recipient = parse_extract_address(tt, &errmess, + ttt = *ss ? string_copyn(tt, ss - tt) : tt; + recipient = parse_extract_address(ttt, &errmess, &start, &end, &domain, FALSE); - *ss = temp; /* Ignore empty addresses and errors; an error will occur later if there's something really bad. */ diff --git a/src/src/miscmods/sieve_filter.c b/src/src/miscmods/sieve_filter.c index 38b408f1b..cb57fa11f 100644 --- a/src/src/miscmods/sieve_filter.c +++ b/src/src/miscmods/sieve_filter.c @@ -2036,7 +2036,7 @@ if (parse_identifier(filter, CUS "address")) for (gstring * h = hdr; h->ptr != -1 && !*cond; ++h) { const uschar * header_value = NULL; - uschar * extracted_addr, * end_addr; + uschar * extracted_addr; if ( !eq_asciicase(h, &str_from, FALSE) && !eq_asciicase(h, &str_to, FALSE) @@ -2061,15 +2061,15 @@ if (parse_identifier(filter, CUS "address")) f.parse_allow_group = TRUE; while (*header_value && !*cond) { - uschar *error; + uschar * part = NULL, * error; + const uschar * end_addr, * ss; int start, end, domain; - int saveend; - uschar *part = NULL; end_addr = parse_find_address_end(header_value, FALSE); - saveend = *end_addr; - *end_addr = 0; - extracted_addr = parse_extract_address(header_value, &error, &start, &end, &domain, FALSE); + ss = *end_addr + ? header_value : string_copyn(header_value, end_addr - header_value); + extracted_addr = parse_extract_address(ss, &error, + &start, &end, &domain, FALSE); if (extracted_addr) switch (addressPart) { @@ -2084,7 +2084,6 @@ if (parse_identifier(filter, CUS "address")) #endif } - *end_addr = saveend; if (part && extracted_addr) { gstring partStr = {.s = part, .ptr = Ustrlen(part), .size = Ustrlen(part)+1}; @@ -2096,7 +2095,7 @@ if (parse_identifier(filter, CUS "address")) } } - if (saveend == 0) break; + if (*end_addr) break; header_value = end_addr + 1; } f.parse_allow_group = FALSE; diff --git a/src/src/parse.c b/src/src/parse.c index cdb1e0c1e..1fe4f70f8 100644 --- a/src/src/parse.c +++ b/src/src/parse.c @@ -68,8 +68,8 @@ Returns: pointer past the end of the address (i.e. points to null or comma) */ -uschar * -parse_find_address_end(const uschar * s, BOOL nl_ends) +const uschar * +parse_find_address_end_gen(const uschar * s, BOOL nl_ends) { BOOL source_routing = *s == '@'; int no_term = source_routing ? 1 : 0; @@ -129,7 +129,7 @@ while (*s && (*s != ',' || no_term > 0) && (*s != '\n' || !nl_ends)) } } -return US s; +return s; } diff --git a/src/src/verify.c b/src/src/verify.c index a12e74ce6..1fee97150 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -2338,9 +2338,9 @@ Returns: OK */ int -verify_check_headers(uschar **msgptr) +verify_check_headers(uschar ** msgptr) { -uschar *colon, *s; +uschar * colon, * s; int yield = OK; for (header_line * h = header_list; h && yield == OK; h = h->next) @@ -2639,8 +2639,7 @@ int yield = FAIL; for (int i = 0; i < 3 && !done; i++) for (const header_line * h = header_list; h && !done; h = h->next) { - const uschar * endname, * s; - uschar * ss; + const uschar * endname, * s, * ss, * es; if (h->type != header_types[i]) continue; s = endname = Ustrchr(h->text, ':') + 1; @@ -2652,38 +2651,36 @@ for (int i = 0; i < 3 && !done; i++) while (*s) { - int terminator, new_ok; + int new_ok; address_item * vaddr; while (isspace(*s) || *s == ',') s++; if (!*s) break; /* End of header */ - ss = parse_find_address_end(s, FALSE); + es = parse_find_address_end(s, FALSE); /* The terminator is a comma or end of header, but there may be white space preceding it (including newline for the last address). Move back past any white space so we can check against any cached envelope sender address verifications. */ - while (isspace(ss[-1])) ss--; - terminator = *ss; - *ss = '\0'; + while (isspace(es[-1])) es--; + ss = *es ? string_copyn(s, es - s) : s; HDEBUG(D_verify) debug_printf("verifying %.*s header address %s\n", - (int)(endname - h->text), h->text, s); + (int)(endname - h->text), h->text, ss); /* See if we have already verified this address as an envelope sender, and if so, use the previous answer. */ - vaddr = verify_checked_sender(s); + vaddr = verify_checked_sender(ss); - if (vaddr != NULL && /* Previously checked */ - (callout <= 0 || /* No callout needed; OR */ - vaddr->special_action > 256)) /* Callout was done */ + if ( vaddr /* Previously checked */ + && ( callout <= 0 /* No callout needed; OR */ + || vaddr->special_action > 256)) /* Callout was done */ { new_ok = vaddr->special_action & 255; HDEBUG(D_verify) debug_printf("previously checked as envelope sender\n"); - *ss = terminator; /* Restore shortened string */ } /* Otherwise we run the verification now. We must restore the shortened @@ -2693,9 +2690,8 @@ for (int i = 0; i < 3 && !done; i++) else { int start, end, domain; - const uschar * address = parse_extract_address(s, log_msgptr, + const uschar * address = parse_extract_address(ss, log_msgptr, &start, &end, &domain, FALSE); - *ss = terminator; /* If we found an empty address, just carry on with the next one, but kill the message. */ @@ -2703,7 +2699,7 @@ for (int i = 0; i < 3 && !done; i++) if (!address && Ustrcmp(*log_msgptr, "empty address") == 0) { *log_msgptr = NULL; - s = ss; + s = es; continue; } @@ -2713,10 +2709,10 @@ for (int i = 0; i < 3 && !done; i++) if (!address) { - while (ss > s && isspace(ss[-1])) ss--; + while (es > s && isspace(es[-1])) es--; *log_msgptr = string_sprintf("syntax error in '%.*s' header when " "scanning for sender: %s in \"%.*s\"", - (int)(endname - h->text), h->text, *log_msgptr, (int)(ss - s), s); + (int)(endname - h->text), h->text, *log_msgptr, (int)(es - s), s); yield = FAIL; done = TRUE; break; @@ -2759,7 +2755,7 @@ for (int i = 0; i < 3 && !done; i++) /* Move on to any more addresses in the header */ - s = ss; + s = es; } /* Next address */ f.parse_allow_group = FALSE; commit e5dc9209cde969a66ac528c2d5fc5a244c5f5999 Author: Jeremy Harris Date: Tue Nov 4 09:58:43 2025 +0000 compiler quietening diff --git a/src/src/auths/cram_md5.c b/src/src/auths/cram_md5.c index 2f6a56626..44b05465c 100644 --- a/src/src/auths/cram_md5.c +++ b/src/src/auths/cram_md5.c @@ -272,7 +272,6 @@ auth_cram_md5_options_block * ob = ablock->drinst.options_block; uschar *secret = expand_string(ob->client_secret); uschar *name = expand_string(ob->client_name); uschar *challenge, *p; -int i; uschar digest[16]; /* If expansion of either the secret or the user name failed, return CANCELLED diff --git a/src/src/hintsdb/hints_sqlite.h b/src/src/hintsdb/hints_sqlite.h index 9e4f65a51..01906f839 100644 --- a/src/src/hintsdb/hints_sqlite.h +++ b/src/src/hintsdb/hints_sqlite.h @@ -127,7 +127,6 @@ exim_dbget(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res) { # define FMT "SELECT dat FROM tbl WHERE ky = '%s';" uschar * encoded_key, * qry; -int i; BOOL ret; # ifdef COMPILE_UTILITY @@ -140,12 +139,14 @@ encoded_key = xtextencode(key->data, key->len); (int)key->len, encoded_key); */ # ifdef COMPILE_UTILITY -i = snprintf(NULL, 0, FMT, encoded_key) + 1; -if (!(qry = malloc(i))) - return FALSE; -snprintf(CS qry, i, FMT, encoded_key); -ret = exim_dbget__(dbp, qry, res); -free(qry); + { + int i = snprintf(NULL, 0, FMT, encoded_key) + 1; + if (!(qry = malloc(i))) + return FALSE; + snprintf(CS qry, i, FMT, encoded_key); + ret = exim_dbget__(dbp, qry, res); + free(qry); + } free(encoded_key); # else qry = string_sprintf(FMT, encoded_key); @@ -172,7 +173,7 @@ int hlen = data->len * 2, off = 0, res; # define FMT "INSERT OR %s INTO tbl (ky,dat) VALUES ('%s', X'%.*s');" uschar * encoded_key, * qry; # ifdef COMPILE_UTILITY -uschar * hex = malloc(hlen+1); +uschar * hex = malloc(hlen+1), dummy[1]; if (!hex) return EXIM_DBPUTB_DUP; /* best we can do */ # else uschar * hex = store_get(hlen+1, data->data); @@ -186,7 +187,7 @@ for (const uschar * s = data->data, * t = s + data->len; s < t; s++, off += 2) # ifdef COMPILE_UTILITY if (!(encoded_key = xtextencode(key->data, key->len))) return EXIM_DBPUTB_DUP; -res = snprintf(CS hex, 0, FMT, alt, encoded_key, hlen, hex) +1; +res = snprintf(CS dummy, 0, FMT, alt, encoded_key, hlen, hex) +1; if (!(qry = malloc(res))) return EXIM_DBPUTB_DUP; snprintf(CS qry, res, FMT, alt, encoded_key, hlen, hex); DEBUG(D_hints_lookup) debug_printf_indent("exim_s_dbp(%s)\n", qry); diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index f63d2ee12..b78c33fcb 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -393,7 +393,7 @@ Returns: DEFER/FAIL */ static int -tls_error(const uschar *prefix, const uschar *msg, const host_item *host, +tls_error(const uschar * prefix, const uschar * msg, const host_item * host, uschar ** errstr) { if (errstr) @@ -404,7 +404,7 @@ return host ? FAIL : DEFER; /* Returns: DEFER/FAIL */ static int -tls_error_gnu(exim_gnutls_state_st * state, const uschar *prefix, int err, +tls_error_gnu(exim_gnutls_state_st * state, const uschar * prefix, int err, uschar ** errstr) { return tls_error(prefix, @@ -461,7 +461,6 @@ return FALSE; static int tls_g_init(uschar ** errstr) { -int rc; DEBUG(D_tls) debug_printf("GnuTLS global init required\n"); #if defined(HAVE_GNUTLS_PKCS11) && !defined(GNUTLS_AUTO_PKCS11_MANUAL) @@ -472,13 +471,19 @@ environment variables are used and so breaks for users calling mailq. To prevent this, we init PKCS11 first, which is the documented approach. */ if (!gnutls_allow_auto_pkcs11) + { + int rc; if ((rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL))) return tls_error_gnu(NULL, US"gnutls_pkcs11_init", rc, errstr); + } #endif #ifndef GNUTLS_AUTO_GLOBAL_INIT -if ((rc = gnutls_global_init())) - return tls_error_gnu(NULL, US"gnutls_global_init", rc, errstr); + { + int rc; + if ((rc = gnutls_global_init())) + return tls_error_gnu(NULL, US"gnutls_global_init", rc, errstr); + } #endif #if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0 @@ -833,7 +838,6 @@ const uschar * filename = NULL; size_t sz; uschar * exp_tls_dhparam; BOOL use_file_in_spool = FALSE; -const host_item * host = NULL; /* dummy for macros */ DEBUG(D_tls) debug_printf("Initialising GnuTLS server params\n"); @@ -1380,12 +1384,14 @@ creds_load_server_certs(exim_gnutls_state_st * state, const uschar * cert, const uschar * pkey, const uschar * ocsp, uschar ** errstr) { const uschar * clist = cert, * klist = pkey, * kfile, * cfile; -int csep = 0, ksep = 0, cnt = 0, rc; +int csep = 0, ksep = 0, rc; #ifndef DISABLE_OCSP uschar * ofile; const uschar * olist; # ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE gnutls_x509_crt_fmt_t ocsp_fmt = GNUTLS_X509_FMT_DER; +# else +int ocsp_file_cnt = 0; # endif if (!expand_check(ocsp, US"tls_ocsp_file", &ofile, errstr)) @@ -1460,7 +1466,7 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0)) else # endif { - if (cnt++ > 0) + if (ocsp_file_cnt++ > 0) { DEBUG(D_tls) debug_printf("oops; multiple OCSP files not supported\n"); commit 2486cac31dc0ce68d3705695c6fe61d68db51d31 Author: Jeremy Harris Date: Tue Nov 4 11:12:11 2025 +0000 Unbreak Solaris build Broken-by: dea05068da8c diff --git a/src/src/macro_predef.c b/src/src/macro_predef.c index caa6bf7c3..e7070f554 100644 --- a/src/src/macro_predef.c +++ b/src/src/macro_predef.c @@ -20,9 +20,12 @@ unsigned mp_index = 0; void fn_smtp_receive_timeout(const uschar * name, const uschar * str) {} uschar * syslog_facility_str; -/* Solaris needs this one for the macro expand_string() */ +/* Solaris needs these for the macros expand_string, parse_find_address_end */ const uschar * expand_string_2(const uschar * string, BOOL * textonly_p) {return NULL; } +const uschar * parse_find_address_end_gen(const uschar *, BOOL) +{return NULL; } + /******************************************************************************/ commit acfcde05f2800db3bf24ba521c62599d95713a08 Author: Jeremy Harris Date: Tue Nov 4 11:48:08 2025 +0000 Unbreak Solaris build Broken-by: dea05068da8c diff --git a/src/src/macro_predef.c b/src/src/macro_predef.c index e7070f554..79cbd5290 100644 --- a/src/src/macro_predef.c +++ b/src/src/macro_predef.c @@ -23,7 +23,7 @@ uschar * syslog_facility_str; /* Solaris needs these for the macros expand_string, parse_find_address_end */ const uschar * expand_string_2(const uschar * string, BOOL * textonly_p) {return NULL; } -const uschar * parse_find_address_end_gen(const uschar *, BOOL) +const uschar * parse_find_address_end_gen(const uschar * s, BOOL b) {return NULL; } commit 826a1788778a6655a25b7d157ff4000531b9a215 Author: Jeremy Harris Date: Tue Nov 4 12:46:33 2025 +0000 Unbreak Solaris build Broken-by: dea05068da8c diff --git a/src/src/exim_dbmbuild.c b/src/src/exim_dbmbuild.c index 02754d0ee..99ea5c826 100644 --- a/src/src/exim_dbmbuild.c +++ b/src/src/exim_dbmbuild.c @@ -71,6 +71,9 @@ string_format_trc(uschar * buf, int len, const uschar * func, unsigned line, void log_write(unsigned int selector, int flags, const char *format, ...) { } +const uschar * parse_find_address_end_gen(const uschar * s, BOOL b) +{return NULL; } + struct global_flags f; diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c index 339d022bb..cd98ff053 100644 --- a/src/src/exim_dbutil.c +++ b/src/src/exim_dbutil.c @@ -77,6 +77,8 @@ BOOL string_format_trc(uschar * buf, int len, const uschar * func, unsigned line, const char * fmt, ...) { return FALSE; } +const uschar * parse_find_address_end_gen(const uschar * s, BOOL b) +{return NULL; } struct global_flags f; unsigned int log_selector[1]; commit e5c1a2ba01a0bd00615ec1bfd05b47c6127bec38 Author: Jeremy Harris Date: Tue Nov 4 14:52:05 2025 +0000 Fix local delivery defers Broken-by: 3cee6033bae8 diff --git a/src/src/deliver.c b/src/src/deliver.c index 29c5dbfc6..445b9dacb 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -2903,65 +2903,57 @@ while (addr_local) { BOOL ok = TRUE; /* to deliver this address */ - if (f.queue_2stage) - { - DEBUG(D_deliver) - debug_printf_indent("no router retry check (ph1 qrun)\n"); - } - else + /* Set up the retry key to include the domain or not, and change its + leading character from "R" to "T". Must make a copy before doing this, + because the old key may be pointed to from a "delete" retry item after + a routing delay. */ + uschar * retry_key = string_copy(tp->retry_use_local_part + ? addr2->address_retry_key : addr2->domain_retry_key); + *retry_key = 'T'; + + /* Inspect the retry data. If there is no hints file, delivery happens. */ + + if (dbm_file) { - /* Set up the retry key to include the domain or not, and change its - leading character from "R" to "T". Must make a copy before doing this, - because the old key may be pointed to from a "delete" retry item after - a routing delay. */ - uschar * retry_key = string_copy(tp->retry_use_local_part - ? addr2->address_retry_key : addr2->domain_retry_key); - *retry_key = 'T'; + dbdata_retry * retry_record = dbfn_read(dbm_file, retry_key); - /* Inspect the retry data. If there is no hints file, delivery happens. */ + /* If there is no retry record, delivery happens. If there is, + remember it exists so it can be deleted after a successful delivery. */ - if (dbm_file) + if (retry_record) { - dbdata_retry * retry_record = dbfn_read(dbm_file, retry_key); + setflag(addr2, af_lt_retry_exists); - /* If there is no retry record, delivery happens. If there is, - remember it exists so it can be deleted after a successful delivery. */ + /* A retry record exists for this address. If queue running and not + forcing, inspect its contents. If the record is too old, or if its + retry time has come, or if it has passed its cutoff time, delivery + will go ahead. */ - if (retry_record) + DEBUG(D_retry) { - setflag(addr2, af_lt_retry_exists); - - /* A retry record exists for this address. If queue running and not - forcing, inspect its contents. If the record is too old, or if its - retry time has come, or if it has passed its cutoff time, delivery - will go ahead. */ - - DEBUG(D_retry) - { - debug_printf("retry record exists: age=%s ", - readconf_printtime(now - retry_record->time_stamp)); - debug_printf("(max %s)\n", readconf_printtime(retry_data_expire)); - debug_printf(" time to retry = %s expired = %d\n", - readconf_printtime(retry_record->next_try - now), - retry_record->expired); - } + debug_printf("retry record exists: age=%s ", + readconf_printtime(now - retry_record->time_stamp)); + debug_printf("(max %s)\n", readconf_printtime(retry_data_expire)); + debug_printf(" time to retry = %s expired = %d\n", + readconf_printtime(retry_record->next_try - now), + retry_record->expired); + } - if (f.queue_running && !f.deliver_force) - { - ok = (now - retry_record->time_stamp > retry_data_expire) - || (now >= retry_record->next_try) - || retry_record->expired; + if (f.queue_running && !f.deliver_force) + { + ok = (now - retry_record->time_stamp > retry_data_expire) + || (now >= retry_record->next_try) + || retry_record->expired; - /* If we haven't reached the retry time, there is one more check - to do, which is for the ultimate address timeout. */ + /* If we haven't reached the retry time, there is one more check + to do, which is for the ultimate address timeout. */ - if (!ok) - ok = retry_ultimate_address_timeout(retry_key, addr2->domain, - retry_record, now); - } + if (!ok) + ok = retry_ultimate_address_timeout(retry_key, addr2->domain, + retry_record, now); } - else DEBUG(D_retry) debug_printf("no retry record exists\n"); } + else DEBUG(D_retry) debug_printf("no retry record exists\n"); } /* This address is to be delivered. Leave it on the chain. */ @@ -7821,82 +7813,74 @@ while (addr_new) /* Loop until all addresses dealt with */ continue; } - if (f.queue_2stage) + /* Get the routing retry status, saving the two retry keys (with and + without the local part) for subsequent use. If there is no retry record + for the standard address routing retry key, we look for the same key with + the sender attached, because this form is used by the smtp transport after + a 4xx response to RCPT when address_retry_include_sender is true. */ + + DEBUG(D_deliver|D_retry) { - DEBUG(D_deliver) - debug_printf_indent("no router retry check (ph1 qrun)\n"); + debug_printf_indent("checking router retry status\n"); + acl_level++; } - else - { - /* Get the routing retry status, saving the two retry keys (with and - without the local part) for subsequent use. If there is no retry record - for the standard address routing retry key, we look for the same key with - the sender attached, because this form is used by the smtp transport after - a 4xx response to RCPT when address_retry_include_sender is true. */ + addr->domain_retry_key = string_sprintf("R:%s", addr->domain); + addr->address_retry_key = string_sprintf("R:%s@%s", addr->local_part, + addr->domain); - DEBUG(D_deliver|D_retry) + if (dbm_file) + { + domain_retry_record = dbfn_read(dbm_file, addr->domain_retry_key); + if ( domain_retry_record + && now - domain_retry_record->time_stamp > retry_data_expire + ) { - debug_printf_indent("checking router retry status\n"); - acl_level++; + DEBUG(D_deliver|D_retry) + debug_printf_indent("domain retry record present but expired\n"); + domain_retry_record = NULL; /* Ignore if too old */ } - addr->domain_retry_key = string_sprintf("R:%s", addr->domain); - addr->address_retry_key = string_sprintf("R:%s@%s", addr->local_part, - addr->domain); - if (dbm_file) + address_retry_record = dbfn_read(dbm_file, addr->address_retry_key); + if ( address_retry_record + && now - address_retry_record->time_stamp > retry_data_expire + ) { - domain_retry_record = dbfn_read(dbm_file, addr->domain_retry_key); - if ( domain_retry_record - && now - domain_retry_record->time_stamp > retry_data_expire - ) - { - DEBUG(D_deliver|D_retry) - debug_printf_indent("domain retry record present but expired\n"); - domain_retry_record = NULL; /* Ignore if too old */ - } + DEBUG(D_deliver|D_retry) + debug_printf_indent("address retry record present but expired\n"); + address_retry_record = NULL; /* Ignore if too old */ + } - address_retry_record = dbfn_read(dbm_file, addr->address_retry_key); + if (!address_retry_record) + { + const uschar * altkey = string_sprintf("%s:<%s>", + addr->address_retry_key, sender_address); + address_retry_record = dbfn_read(dbm_file, altkey); if ( address_retry_record - && now - address_retry_record->time_stamp > retry_data_expire - ) + && now - address_retry_record->time_stamp > retry_data_expire) { DEBUG(D_deliver|D_retry) - debug_printf_indent("address retry record present but expired\n"); + debug_printf_indent("address retry record present but expired\n"); address_retry_record = NULL; /* Ignore if too old */ } - - if (!address_retry_record) - { - const uschar * altkey = string_sprintf("%s:<%s>", - addr->address_retry_key, sender_address); - address_retry_record = dbfn_read(dbm_file, altkey); - if ( address_retry_record - && now - address_retry_record->time_stamp > retry_data_expire) - { - DEBUG(D_deliver|D_retry) - debug_printf_indent("address retry record present but expired\n"); - address_retry_record = NULL; /* Ignore if too old */ - } - } } + } - DEBUG(D_deliver|D_retry) - { - if (!domain_retry_record) - debug_printf_indent("no domain retry record\n"); - else - debug_printf_indent("have domain retry record; next_try = now%+d\n", - f.running_in_test_harness ? 0 : - (int)(domain_retry_record->next_try - now)); + DEBUG(D_deliver|D_retry) + { + if (!domain_retry_record) + debug_printf_indent("no domain retry record\n"); + else + debug_printf_indent("have domain retry record; next_try = now%+d\n", + f.running_in_test_harness ? 0 : + (int)(domain_retry_record->next_try - now)); - if (!address_retry_record) - debug_printf_indent("no address retry record\n"); - else - debug_printf_indent("have address retry record; next_try = now%+d\n", - f.running_in_test_harness ? 0 : - (int)(address_retry_record->next_try - now)); - acl_level--; - } + if (!address_retry_record) + debug_printf_indent("no address retry record\n"); + else + debug_printf_indent("have address retry record; next_try = now%+d\n", + f.running_in_test_harness ? 0 : + (int)(address_retry_record->next_try - now)); + acl_level--; } /* If we are sending a message down an existing SMTP connection, we must commit 140c289d1170334e29ee3fd4e2c385cdb7bd837c Author: Jeremy Harris Date: Tue Nov 4 18:44:53 2025 +0000 Fix remote-delivery DNS defers. Bug 3172 Broken-by: 6748707c6446 diff --git a/src/src/deliver.c b/src/src/deliver.c index 445b9dacb..3dc1aef73 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -7540,42 +7540,37 @@ while (addr_new) /* Loop until all addresses dealt with */ address_item * addr, * parent; /* Failure to open the retry database is treated the same as if it does - not exist. In both cases, dbm_file is NULL. For the first stage of a 2-phase - queue run don't bother checking domain- or address-retry info; they will take - effect on the second stage. */ - - if (!f.queue_2stage) - { - /* If we have transaction-capable hintsdbs, open the retry db without - locking, and leave open for the transport process and for subsequent - deliveries. Use a writeable open as we can keep it open all the way through - to writing retry records if needed due to message fails. - If the open fails, tag that explicitly for the transport but retry the open - next time around, in case it was created in the interim. - If non-transaction, we are only reading records at this stage and - we close the db before running the transport. - Either way we do a non-creating open. */ - - if (continue_retry_db == (open_db *)-1) - continue_retry_db = NULL; - - if (continue_retry_db) - { - DEBUG(D_hints_lookup) debug_printf("using cached retry hintsdb handle\n"); - dbm_file = continue_retry_db; - } - else if (!exim_lockfile_needed()) - { - dbm_file = dbfn_open_multi(US"retry", O_RDWR, &dbblock); - continue_retry_db = dbm_file ? dbm_file : (open_db *)-1; - } - else - dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE); + not exist. In both cases, dbm_file is NULL. */ + + /* If we have transaction-capable hintsdbs, open the retry db without + locking, and leave open for the transport process and for subsequent + deliveries. Use a writeable open as we can keep it open all the way through + to writing retry records if needed due to message fails. + If the open fails, tag that explicitly for the transport but retry the open + next time around, in case it was created in the interim. + If non-transaction, we are only reading records at this stage and + we close the db before running the transport. + Either way we do a non-creating open. */ - if (!dbm_file) - DEBUG(D_deliver|D_retry|D_route|D_hints_lookup) - debug_printf("no retry data available\n"); + if (continue_retry_db == (open_db *)-1) + continue_retry_db = NULL; + + if (continue_retry_db) + { + DEBUG(D_hints_lookup) debug_printf("using cached retry hintsdb handle\n"); + dbm_file = continue_retry_db; + } + else if (!exim_lockfile_needed()) + { + dbm_file = dbfn_open_multi(US"retry", O_RDWR, &dbblock); + continue_retry_db = dbm_file ? dbm_file : (open_db *)-1; } + else + dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE); + + if (!dbm_file) + DEBUG(D_deliver|D_retry|D_route|D_hints_lookup) + debug_printf("no retry data available\n"); /* Scan the current batch of new addresses, to handle pipes, files and autoreplies, and determine which others are ready for routing. */ commit ea41f8373bd699697585193862afab334a62ec1a Author: Jeremy Harris Date: Wed Nov 5 10:17:04 2025 +0000 Build: more files for cscope diff --git a/src/Makefile b/src/Makefile index 9c08915ef..45711a05b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -121,10 +121,15 @@ cscope.files: FRC echo "-p3" >> $@ -bd=build-$(buildname); [ -d $$bd ] && echo -e "$$bd/config.h\n$$bd/Makefile" >> $@ find src Local OS exim_monitor -name "*.[cshyl]" -print \ + -o -name "*.src" -print \ -o -name "os.[ch]*" -print \ -o -name "*akefile*" -print \ -o -name config.h.defaults -print \ -o -name EDITME -print >> $@ + echo scripts/* >> $@ + +cscope: cscope.files + cscope -b -p 3 FRC: commit d3dd48e449dcd329126c4365c8f92335c8afa350 Author: Jeremy Harris Date: Thu Nov 6 10:55:05 2025 +0000 fix radius expansion condition diff --git a/src/src/miscmods/pam.c b/src/src/miscmods/pam.c index 842282ba5..f39e09208 100644 --- a/src/src/miscmods/pam.c +++ b/src/src/miscmods/pam.c @@ -68,7 +68,7 @@ static int pam_converse (int num_msg, PAM_CONVERSE_ARG2_TYPE **msg, struct pam_response **resp, void *appdata_ptr) { -int sep = 0; +int sep = ':'; struct pam_response *reply; /* It seems that PAM frees reply[] */ @@ -131,7 +131,7 @@ Returns: OK if authentication succeeded static int auth_call_pam(const uschar * s, uschar ** errptr) { -pam_handle_t *pamh = NULL; +pam_handle_t * pamh = NULL; struct pam_conv pamc; int pam_error; int sep = ':'; /* Do not permit change-of-separator */ diff --git a/src/src/miscmods/radius.c b/src/src/miscmods/radius.c index 3f8232756..fd75b3ae4 100644 --- a/src/src/miscmods/radius.c +++ b/src/src/miscmods/radius.c @@ -59,8 +59,8 @@ using its original API. At release 0.4.0 the API changed. */ more data strings. Arguments: - s a colon-separated list of strings - errptr where to point an error message + radius_args a colon-separated list of strings + errptr where to point an error message Returns: OK if authentication succeeded FAIL if authentication failed @@ -68,12 +68,10 @@ Returns: OK if authentication succeeded */ static int -auth_call_radius(const uschar *s, uschar **errptr) +auth_call_radius(const uschar * radius_args, uschar ** errptr) { -uschar *user; -const uschar *radius_args = s; -int result; -int sep = ':'; +uschar * user, * pwd; +int sep = ':', result; #ifdef RADIUS_LIB_RADLIB struct rad_handle *h; @@ -89,9 +87,10 @@ int sep = ':'; if (!(user = string_nextinlist(&radius_args, &sep, NULL, 0))) user = US""; +pwd = string_nextinlist(&radius_args, &sep, NULL, 0); DEBUG(D_auth) debug_printf("Running RADIUS authentication for user %q " - "and %q\n", user, radius_args); + "and %q\n", user, pwd); *errptr = NULL; @@ -112,7 +111,7 @@ else if (rc_read_dictionary(rc_conf_str("dictionary")) != 0) else if (!rc_avpair_add(&send, PW_USER_NAME, user, 0)) *errptr = US"RADIUS: add user name failed"; -else if (!rc_avpair_add(&send, PW_USER_PASSWORD, CS radius_args, 0)) +else if (!rc_avpair_add(&send, PW_USER_PASSWORD, pwd, 0)) *errptr = US"RADIUS: add password failed"); else if (!rc_avpair_add(&send, PW_SERVICE_TYPE, &service, 0)) commit eaf7eae8474ded19ec64022f71d1b9e610013d5c Author: Samuel Thibault Date: Fri Nov 7 09:37:04 2025 +0000 Fix file open modes for Gnu/Hurd. Bug 3175 diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c index cd98ff053..5065b6e9b 100644 --- a/src/src/exim_dbutil.c +++ b/src/src/exim_dbutil.c @@ -335,7 +335,7 @@ if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0) return NULL; -dbblock->readonly = (flags & (O_WRONLY|O_RDWR)) == O_RDONLY; +dbblock->readonly = (flags & O_ACCMODE) == O_RDONLY; dbblock->lockfd = -1; if (exim_lockfile_needed()) { diff --git a/src/src/hintsdb/hints_bdb.h b/src/src/hintsdb/hints_bdb.h index 7285e85f9..85e8984ec 100644 --- a/src/src/hintsdb/hints_bdb.h +++ b/src/src/hintsdb/hints_bdb.h @@ -115,7 +115,7 @@ if (db_create(&b, dbp, 0) == 0) if (b->open(b, NULL, CS name, NULL, flags & O_CREAT ? DB_HASH : DB_UNKNOWN, flags & O_CREAT ? DB_CREATE - : flags & (O_WRONLY|O_RDWR) ? 0 : DB_RDONLY, + : (flags & O_ACCMODE) == O_RDONLY ? DB_RDONLY : 0, mode) == 0 ) return dbp; @@ -266,7 +266,7 @@ return db_create(&dbp, NULL, 0) == 0 dbp->open(dbp, CS name, NULL, flags & O_CREAT ? DB_HASH : DB_UNKNOWN, flags & O_CREAT ? DB_CREATE - : flags & (O_WRONLY|O_RDWR) ? 0 : DB_RDONLY, + : (flags & O_ACCMODE) == O_RDONLY ? DB_RDONLY : 0, mode) ) == 0 ? dbp : NULL; diff --git a/src/src/hintsdb/hints_gdbm.h b/src/src/hintsdb/hints_gdbm.h index 00c94a777..34df8c84f 100644 --- a/src/src/hintsdb/hints_gdbm.h +++ b/src/src/hintsdb/hints_gdbm.h @@ -58,7 +58,7 @@ if (dbp) dbp->lkey.dptr = NULL; dbp->gdbm = gdbm_open(CS name, 0, flags & O_CREAT ? GDBM_WRCREAT - : flags & (O_RDWR|O_WRONLY) ? GDBM_WRITER : GDBM_READER, + : (flags & O_ACCMODE) == O_RDONLY ? GDBM_READER : GDBM_WRITER, mode, 0); if (dbp->gdbm) return dbp; diff --git a/src/src/hintsdb/hints_sqlite.h b/src/src/hintsdb/hints_sqlite.h index 01906f839..40d50b8c0 100644 --- a/src/src/hintsdb/hints_sqlite.h +++ b/src/src/hintsdb/hints_sqlite.h @@ -45,7 +45,8 @@ exim_dbopen_multi__(const uschar * name, const uschar * dirname, int flags, unsigned mode) { EXIM_DB * dbp; -int ret, sflags = flags & O_RDWR ? SQLITE_OPEN_READWRITE : SQLITE_OPEN_READONLY; +int ret, sflags = (flags & O_ACCMODE) == O_RDONLY + ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE; if (flags & O_CREAT) sflags |= SQLITE_OPEN_CREATE; if ((ret = sqlite3_open_v2(CCS name, &dbp, sflags, NULL)) == SQLITE_OK) commit 0cf5f1656eff31bcc1131ca047030f4866b4224b Author: Jeremy Harris Date: Fri Nov 7 17:50:28 2025 +0000 Retire "pwcheck" expansion condition diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index e24ef95f2..25a8e82a7 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -552,8 +552,7 @@ OBJ_LOOKUPS = lf_quote.o lf_check_file.o lf_sqlperform.o OBJ_ROUTERS = rf_change_domain.o rf_expand_data.o rf_get_errors_address.o \ rf_get_munge_headers.o rf_get_transport.o rf_get_ugid.o \ rf_lookup_hostlist.o rf_queue_add.o rf_self_action.o rf_set_ugid.o -OBJ_AUTHS = call_pwcheck.o check_serv_cond.o \ - get_data.o get_no64_data.o pwcheck.o +OBJ_AUTHS = check_serv_cond.o get_data.o get_no64_data.o pwcheck.o OBJ_EXIM = acl.o atrn.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o \ deliver.o directory.o dns.o drtables.o enq.o exim.o expand.o \ @@ -1003,9 +1002,6 @@ rf_set_ugid.o: routers/rf_set_ugid.c auth-spa.o: auths/auth-spa.c @echo "$(CC) $<" $(FE)$(CC) -c $(CFLAGS) -I. $(INCLUDE) $< -call_pwcheck.o: auths/call_pwcheck.c auths/pwcheck.h - @echo "$(CC) $<" - $(FE)$(CC) -c $(CFLAGS) -I. $(INCLUDE) $< check_serv_cond.o: auths/check_serv_cond.c @echo "$(CC) $<" $(FE)$(CC) -c $(CFLAGS) -I. $(INCLUDE) $< diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index 8f55be0b5..765472195 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -78,7 +78,7 @@ d="auths" mkdir $d cd $d # Makefile is generated -for f in README call_pwcheck.c \ +for f in README \ check_serv_cond.c cyrus_sasl.c cyrus_sasl.h gsasl.c \ gsasl.h get_data.c get_no64_data.c heimdal_gssapi.c heimdal_gssapi.h \ cram_md5.c cram_md5.h plaintext.c plaintext.h \ diff --git a/src/src/EDITME b/src/src/EDITME index 998406359..d38ac98e5 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -1205,23 +1205,6 @@ ZCAT_COMMAND=/usr/bin/zcat # using the original API. -#------------------------------------------------------------------------------ -# Support for authentication via the Cyrus SASL pwcheck daemon is available. -# Note, however, that pwcheck is now deprecated in favour of saslauthd (see -# next item). The Exim support for pwcheck, which is intented for use in -# conjunction with the SMTP AUTH facilities, is included only when requested by -# setting the following parameter to the location of the pwcheck daemon's -# socket. -# -# There is no need to install all of SASL on your system. You just need to run -# ./configure --with-pwcheck, cd to the pwcheck directory within the sources, -# make and make install. You must create the socket directory (default -# /var/pwcheck) and chown it to Exim's user and group. Once you have installed -# pwcheck, you should arrange for it to be started by root at boot time. - -# CYRUS_PWCHECK_SOCKET=/var/pwcheck/pwcheck - - #------------------------------------------------------------------------------ # Support for authentication via the Cyrus SASL saslauthd daemon is available. # The Exim support, which is intended for use in conjunction with the SMTP AUTH diff --git a/src/src/auths/call_pwcheck.c b/src/src/auths/call_pwcheck.c deleted file mode 100644 index 9e0d5d41f..000000000 --- a/src/src/auths/call_pwcheck.c +++ /dev/null @@ -1,120 +0,0 @@ -/************************************************* -* Exim - an Internet mail transport agent * -*************************************************/ - -/* Copyright (c) The Exim Maintainers 2020 - 2025 */ -/* Copyright (c) University of Cambridge 1995 - 2015 */ -/* See the file NOTICE for conditions of use and distribution. */ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/* This module contains interface functions to the two Cyrus authentication -daemons. The original one was "pwcheck", which gives its name to the source -file. This is now deprecated in favour of "saslauthd". */ - - -#include "../exim.h" -#include "pwcheck.h" - - -/************************************************* -* External entry point for pwcheck * -*************************************************/ - -/* This function calls the now-deprecated "pwcheck" Cyrus-SASL authentication -daemon, passing over a colon-separated user name and password. As this is -called from the string expander, the string will always be in dynamic store and -can be overwritten. - -Arguments: - s a colon-separated username:password string - errptr where to point an error message - -Returns: OK if authentication succeeded - FAIL if authentication failed - ERROR some other error condition -*/ - -int -auth_call_pwcheck(uschar *s, uschar **errptr) -{ -uschar * reply = NULL, * pw = Ustrrchr(s, ':'); - -if (!pw) - { - *errptr = US"pwcheck: malformed input - missing colon"; - return ERROR; - } - -*pw++ = 0; /* Separate user and password */ - -DEBUG(D_auth) debug_printf("Running pwcheck authentication for user %q\n", s); - -switch (pwcheck_verify_password(CS s, CS pw, CCSS &reply)) - { - case PWCHECK_OK: - DEBUG(D_auth) debug_printf("pwcheck: success (%s)\n", reply); - return OK; - - case PWCHECK_NO: - DEBUG(D_auth) debug_printf("pwcheck: access denied (%s)\n", reply); - return FAIL; - - default: - DEBUG(D_auth) debug_printf("pwcheck: query failed (%s)\n", reply); - *errptr = reply; - return ERROR; - } -} - - -/************************************************* -* External entry point for pwauthd * -*************************************************/ - -/* This function calls the "saslauthd" Cyrus-SASL authentication daemon, -saslauthd, As this is called from the string expander, all the strings will -always be in dynamic store and can be overwritten. - -Arguments: - username username - password password - service optional service - realm optional realm - errptr where to point an error message - -Returns: OK if authentication succeeded - FAIL if authentication failed - ERROR some other error condition -*/ - -int -auth_call_saslauthd(const uschar *username, const uschar *password, - const uschar *service, const uschar *realm, uschar **errptr) -{ -uschar *reply = NULL; - -if (service == NULL) service = US""; -if (realm == NULL) realm = US""; - -DEBUG(D_auth) - debug_printf("Running saslauthd authentication for user %q \n", username); - -switch (saslauthd_verify_password(username, password, service, - realm, (const uschar **)(&reply))) - { - case PWCHECK_OK: - DEBUG(D_auth) debug_printf("saslauthd: success (%s)\n", reply); - return OK; - - case PWCHECK_NO: - DEBUG(D_auth) debug_printf("saslauthd: access denied (%s)\n", reply); - return FAIL; - - default: - DEBUG(D_auth) debug_printf("saslauthd: query failed (%s)\n", reply); - *errptr = reply; - return ERROR; - } -} - -/* End of call_pwcheck.c */ diff --git a/src/src/auths/pwcheck.c b/src/src/auths/pwcheck.c index bf305832f..cd8ed1e10 100644 --- a/src/src/auths/pwcheck.c +++ b/src/src/auths/pwcheck.c @@ -59,16 +59,16 @@ */ /* Originally this module supported only the pwcheck daemon, which is where its -name comes from. Nowadays it supports saslauthd as well; pwcheck is in fact -deprecated. The definitions of CYRUS_PWCHECK_SOCKET and CYRUS_SASLAUTHD_SOCKET -determine whether the facilities are actually supported or not. */ +name comes from. Nowadays it supports saslauthd instead; pwcheck is in fact +deprecated. The definition of CYRUS_SASLAUTHD_SOCKET +determines whether the facilities are actually supported or not. */ #include "../exim.h" #include "pwcheck.h" -#if defined(CYRUS_PWCHECK_SOCKET) || defined(CYRUS_SASLAUTHD_SOCKET) +#if defined(CYRUS_SASLAUTHD_SOCKET) #include @@ -80,79 +80,6 @@ static int write_string(int, const uschar *, int); #endif -/* A dummy function that always fails if pwcheck support is not -wanted. */ - -#ifndef CYRUS_PWCHECK_SOCKET -int pwcheck_verify_password(const char *userid, - const char *passwd, - const char **reply) -{ -*reply = "pwcheck support is not included in this Exim binary"; -return PWCHECK_FAIL; -} - - -/* This is the real function */ - -#else - - /* taken from cyrus-sasl file checkpw.c */ - /* pwcheck daemon-authenticated login */ - int pwcheck_verify_password(const char *userid, - const char *passwd, - const char **reply) - { - int s, start, r, n; - struct sockaddr_un srvaddr; - struct iovec iov[2]; - static char response[1024]; - - *reply = NULL; - - s = socket(AF_UNIX, SOCK_STREAM, 0); - if (s == -1) { return PWCHECK_FAIL; } - - memset(CS &srvaddr, 0, sizeof(srvaddr)); - srvaddr.sun_family = AF_UNIX; - strncpy(srvaddr.sun_path, CYRUS_PWCHECK_SOCKET, sizeof(srvaddr.sun_path)); - r = connect(s, (struct sockaddr *)&srvaddr, sizeof(srvaddr)); - if (r == -1) { - DEBUG(D_auth) - debug_printf("Cannot connect to pwcheck daemon (at '%s')\n",CYRUS_PWCHECK_SOCKET); - *reply = "cannot connect to pwcheck daemon"; - return PWCHECK_FAIL; - } - - iov[0].iov_base = CS userid; - iov[0].iov_len = strlen(userid)+1; - iov[1].iov_base = CS passwd; - iov[1].iov_len = strlen(passwd)+1; - - retry_writev(s, iov, 2); - - start = 0; - while (start < sizeof(response) - 1) { - n = read(s, response+start, sizeof(response) - 1 - start); - if (n < 1) break; - start += n; - } - - (void)close(s); - - if (start > 1 && !strncmp(response, "OK", 2)) { - return PWCHECK_OK; - } - - response[start] = '\0'; - *reply = response; - return PWCHECK_NO; - } - -#endif - - - /* A dummy function that always fails if saslauthd support is not wanted. */ @@ -268,7 +195,7 @@ int saslauthd_verify_password(const uschar *userid, /* helper functions */ -#if defined(CYRUS_PWCHECK_SOCKET) || defined(CYRUS_SASLAUTHD_SOCKET) +#if defined(CYRUS_SASLAUTHD_SOCKET) #define MAX_REQ_LEN 1024 diff --git a/src/src/auths/pwcheck.h b/src/src/auths/pwcheck.h index 4c1d71d92..0c20292ce 100644 --- a/src/src/auths/pwcheck.h +++ b/src/src/auths/pwcheck.h @@ -6,8 +6,8 @@ /* See the file NOTICE for conditions of use and distribution. */ /* SPDX-License-Identifier: GPL-2.0-or-later */ -/* This file provides support for authentication via the Cyrus SASL pwcheck -daemon (whence its name) and the newer saslauthd daemon. */ +/* This file originally provided support for authentication via the Cyrus +SASL pwcheck daemon (whence its name), but now the newer saslauthd daemon. */ /* Error codes used internally within the authentication functions */ @@ -21,7 +21,6 @@ daemon (whence its name) and the newer saslauthd daemon. */ /* Cyrus functions for doing the business. */ -extern int pwcheck_verify_password(const char *, const char *, const char **); extern int saslauthd_verify_password(const uschar *, const uschar *, const uschar *, const uschar *, const uschar **); diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults index 05bdcd6d3..f331ad0d5 100644 --- a/src/src/config.h.defaults +++ b/src/src/config.h.defaults @@ -40,7 +40,6 @@ Do not put spaces between # and the 'define'. #define CONFIGURE_FILE_USE_NODE #define CONFIGURE_GROUP #define CONFIGURE_OWNER -#define CYRUS_PWCHECK_SOCKET #define CYRUS_SASLAUTHD_SOCKET #define DEFAULT_CRYPT crypt diff --git a/src/src/exim.c b/src/src/exim.c index c6fa25fc7..9d6f858bd 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -1158,9 +1158,6 @@ g = string_cat(g, US"Support for:"); #ifdef USE_OPENSSL g = string_cat(g, US" OpenSSL"); #endif -#if defined(CYRUS_PWCHECK_SOCKET) - g = string_cat(g, US" pwcheck"); -#endif #if defined(RADIUS_CONFIG_FILE) g = string_cat(g, US" radius"); #endif diff --git a/src/src/expand.c b/src/src/expand.c index 9bfa39fdc..fb32ce985 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -362,7 +362,6 @@ static uschar *cond_table[] = { US"match_local_part", US"or", US"pam", - US"pwcheck", US"queue_running", US"radius", US"saslauthd" @@ -415,7 +414,6 @@ enum { ECOND_MATCH_LOCAL_PART, ECOND_OR, ECOND_PAM, - ECOND_PWCHECK, ECOND_QUEUE_RUNNING, ECOND_RADIUS, ECOND_SASLAUTHD @@ -2740,7 +2738,6 @@ switch(cond_type = identify_operator(&s, &opname)) case ECOND_PAM: case ECOND_RADIUS: case ECOND_LDAPAUTH: - case ECOND_PWCHECK: if (Uskip_whitespace(&s) != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ @@ -2835,16 +2832,8 @@ switch(cond_type = identify_operator(&s, &opname)) goto COND_FAILED_NOT_COMPILED; #endif /* LOOKUP_LDAP */ - case ECOND_PWCHECK: - #ifdef CYRUS_PWCHECK_SOCKET - rc = auth_call_pwcheck(sub[0], &expand_string_message); - goto END_AUTH; - #else - goto COND_FAILED_NOT_COMPILED; - #endif /* CYRUS_PWCHECK_SOCKET */ - #if defined(SUPPORT_PAM) || defined(RADIUS_CONFIG_FILE) || \ - defined(LOOKUP_LDAP) || defined(CYRUS_PWCHECK_SOCKET) + defined(LOOKUP_LDAP) END_AUTH: if (rc == ERROR || rc == DEFER) goto failout; *yield = (rc == OK) == testfor; @@ -3724,7 +3713,7 @@ goto failout; /* A condition requires code that is not compiled */ #if !defined(SUPPORT_PAM) || !defined(RADIUS_CONFIG_FILE) || \ - !defined(LOOKUP_LDAP) || !defined(CYRUS_PWCHECK_SOCKET) || \ + !defined(LOOKUP_LDAP) || \ !defined(SUPPORT_CRYPTEQ) || !defined(CYRUS_SASLAUTHD_SOCKET) COND_FAILED_NOT_COMPILED: expand_string_message = string_sprintf("support for %q not compiled", diff --git a/src/src/functions.h b/src/src/functions.h index ef1c59dc3..bd390cce6 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -112,7 +112,6 @@ extern void assert_no_variables(void *, int, const char *, int); extern void atrn_handle_customer(void); extern int atrn_handle_provider(uschar **, uschar **); -extern int auth_call_pwcheck(uschar *, uschar **); extern int auth_call_saslauthd(const uschar *, const uschar *, const uschar *, const uschar *, uschar **); extern int auth_check_serv_cond(auth_instance *); commit 3bac7a1bac4fbccea548b3f8ed9a788b8b41d943 Author: Jeremy Harris Date: Mon Nov 10 10:09:49 2025 +0000 Fix CYRUS_SASLAUTHD_SOCKET build. Bug 3176 Broken-by: 0cf5f1656eff diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index 25a8e82a7..40befe325 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -552,7 +552,8 @@ OBJ_LOOKUPS = lf_quote.o lf_check_file.o lf_sqlperform.o OBJ_ROUTERS = rf_change_domain.o rf_expand_data.o rf_get_errors_address.o \ rf_get_munge_headers.o rf_get_transport.o rf_get_ugid.o \ rf_lookup_hostlist.o rf_queue_add.o rf_self_action.o rf_set_ugid.o -OBJ_AUTHS = check_serv_cond.o get_data.o get_no64_data.o pwcheck.o +OBJ_AUTHS = call_saslauthd.o check_serv_cond.o \ + get_data.o get_no64_data.o pwcheck.o OBJ_EXIM = acl.o atrn.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o \ deliver.o directory.o dns.o drtables.o enq.o exim.o expand.o \ @@ -1002,6 +1003,9 @@ rf_set_ugid.o: routers/rf_set_ugid.c auth-spa.o: auths/auth-spa.c @echo "$(CC) $<" $(FE)$(CC) -c $(CFLAGS) -I. $(INCLUDE) $< +call_saslauthd.o: auths/call_saslauthd.c auths/pwcheck.h + @echo "$(CC) $<" + $(FE)$(CC) -c $(CFLAGS) -I. $(INCLUDE) $< check_serv_cond.o: auths/check_serv_cond.c @echo "$(CC) $<" $(FE)$(CC) -c $(CFLAGS) -I. $(INCLUDE) $< diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index 765472195..4e4a638c7 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -78,7 +78,7 @@ d="auths" mkdir $d cd $d # Makefile is generated -for f in README \ +for f in README call_saslauthd.c \ check_serv_cond.c cyrus_sasl.c cyrus_sasl.h gsasl.c \ gsasl.h get_data.c get_no64_data.c heimdal_gssapi.c heimdal_gssapi.h \ cram_md5.c cram_md5.h plaintext.c plaintext.h \ diff --git a/src/src/auths/call_saslauthd.c b/src/src/auths/call_saslauthd.c new file mode 100644 index 000000000..b5735fc6c --- /dev/null +++ b/src/src/auths/call_saslauthd.c @@ -0,0 +1,69 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2020 - 2025 */ +/* Copyright (c) University of Cambridge 1995 - 2015 */ +/* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* This module contains interface functions to the two Cyrus authentication +daemons. The original one was "pwcheck", which gives its name to the source +file. This is now deprecated in favour of "saslauthd". */ + + +#include "../exim.h" +#include "pwcheck.h" + + +/************************************************* +* External entry point for saslauthd * +*************************************************/ + +/* This function calls the "saslauthd" Cyrus-SASL authentication daemon, +saslauthd, As this is called from the string expander, all the strings will +always be in dynamic store and can be overwritten. + +Arguments: + username username + password password + service optional service + realm optional realm + errptr where to point an error message + +Returns: OK if authentication succeeded + FAIL if authentication failed + ERROR some other error condition +*/ + +int +auth_call_saslauthd(const uschar *username, const uschar *password, + const uschar *service, const uschar *realm, uschar **errptr) +{ +uschar *reply = NULL; + +if (service == NULL) service = US""; +if (realm == NULL) realm = US""; + +DEBUG(D_auth) + debug_printf("Running saslauthd authentication for user %q \n", username); + +switch (saslauthd_verify_password(username, password, service, + realm, (const uschar **)(&reply))) + { + case PWCHECK_OK: + DEBUG(D_auth) debug_printf("saslauthd: success (%s)\n", reply); + return OK; + + case PWCHECK_NO: + DEBUG(D_auth) debug_printf("saslauthd: access denied (%s)\n", reply); + return FAIL; + + default: + DEBUG(D_auth) debug_printf("saslauthd: query failed (%s)\n", reply); + *errptr = reply; + return ERROR; + } +} + +/* End of call_pwcheck.c */ diff --git a/src/src/expand.c b/src/src/expand.c index fb32ce985..fcf699d2e 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -3712,14 +3712,10 @@ goto failout; /* A condition requires code that is not compiled */ -#if !defined(SUPPORT_PAM) || !defined(RADIUS_CONFIG_FILE) || \ - !defined(LOOKUP_LDAP) || \ - !defined(SUPPORT_CRYPTEQ) || !defined(CYRUS_SASLAUTHD_SOCKET) COND_FAILED_NOT_COMPILED: expand_string_message = string_sprintf("support for %q not compiled", opname); goto failout; -#endif failout: next = NULL; commit 1cb819c5032dc7797999c60c023f65c842dcf5d4 Author: Jeremy Harris Date: Sun Nov 9 12:15:07 2025 +0000 Build: quietening diff --git a/src/Makefile b/src/Makefile index 45711a05b..a19c6928a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -29,6 +29,9 @@ RM_COMMAND=/bin/rm buildname=$${build:-`$(SHELL) scripts/os-type`-`$(SHELL) scripts/arch-type`}$${EXIM_BUILD_SUFFIX:+.$$EXIM_BUILD_SUFFIX} +# Quieten gnumake wrt. sub-make directories +GNUMAKEFLAGS=--no-print-directory + # The default target checks for the existence of Local/Makefile, that the main # makefile is built and up-to-date, and then it runs it. # If Local/Makefile- exists, it is read too. @@ -63,12 +66,14 @@ build-directory: (mkdir $$builddir; cd $$builddir; $(SHELL) ../scripts/MakeLinks)"; checks: - $(SHELL) scripts/source_checks + @$(SHELL) scripts/source_checks # The "configure" target ensures that the build directory exists, then arranges # to build the main makefile from inside the build directory, by calling the # Configure-Makefile script. This does its own dependency checking because of # the optional files. +# We always run the script (as there is no actual"configure" file) +# but it quietly dooes nothing if nothing is needed. configure: checks build-directory \ scripts/lookups-Makefile scripts/drivers-Makefile diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index 40befe325..80f1f1f97 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -36,8 +36,9 @@ FE = $(FULLECHO) # up-to-date. Then the os-specific source files and the C configuration file # are set up, and finally it goes to the main Exim target. -all: utils exim dynmodules -config: $(EDITME) checklocalmake Makefile os.c config.h version.h version.sh macro.c +CONFIG_DEPS = Makefile os.c config.h version.h version.sh macro.c + +all: $(EDITME) checklocalmake $(CONFIG_DEPS) utils exim dynmodules exim_openssl exim_gnutls: clean exim cp exim $@ @@ -243,9 +244,7 @@ macro.c: macro_predef # therefore always be run, even if the files exist. This shouldn't in fact be a # problem, but it does no harm. Other make programs will just ignore this. -.PHONY: all config utils \ - buildauths buildlookups buildrouters \ - buildtransports buildmisc dynmodules checklocalmake clean +.PHONY: all config utils dynmodules clean utils: $(EXIM_MONITOR) exicyclog exinext exiwhat \ @@ -261,12 +260,12 @@ buildconfig: buildconfig.c $(FE)$(CC) $(CFLAGS) $(INCLUDE) -o buildconfig buildconfig.c $(LIBS) -util_deps: config ../src/utils/msgid.frag +UTIL_DEPS = $(CONFIG_DEPS) ../src/utils/msgid.frag # Target for the exicyclog utility script -exicyclog: util_deps ../src/utils/exicyclog.src - @rm -f exicyclog - @. ./version.sh && sed \ +exicyclog: $(UTIL_DEPS) ../src/utils/exicyclog.src + $(FE)rm -f exicyclog + $(FE). ./version.sh && sed \ -e "s?PROCESSED_FLAG?This file has been so processed.?"\ -e "/^# /p" \ -e "/^# /d" \ @@ -292,15 +291,15 @@ exicyclog: util_deps ../src/utils/exicyclog.src -e "s?^?# Insert ?" \ -e "r ../src/utils/msgid.frag" \ ../src/utils/exicyclog.src > exicyclog-t - @mv exicyclog-t exicyclog - @chmod a+x exicyclog - @./exicyclog -v 2>&1 >/dev/null - @echo ">>> exicyclog script built" + $(FE)mv exicyclog-t exicyclog + $(FE)chmod a+x exicyclog + $(FE)./exicyclog -v 2>&1 >/dev/null +# $(FE)echo ">>> exicyclog script built" # Target for the exinext utility script -exinext: util_deps ../src/utils/exinext.src - @rm -f exinext - @. ./version.sh && sed \ +exinext: $(UTIL_DEPS) ../src/utils/exinext.src + $(FE)rm -f exinext + $(FE). ./version.sh && sed \ -e "s?PROCESSED_FLAG?This file has been so processed.?"\ -e "/^# /p" \ -e "/^# /d" \ @@ -316,15 +315,15 @@ exinext: util_deps ../src/utils/exinext.src -e "s?^?# Insert ?" \ -e "r ../src/utils/msgid.frag" \ ../src/utils/exinext.src > exinext-t - @mv exinext-t exinext - @chmod a+x exinext - @./exinext -v 2>&1 >/dev/null - @echo ">>> exinext script built" + $(FE)mv exinext-t exinext + $(FE)chmod a+x exinext + $(FE)./exinext -v 2>&1 >/dev/null +# $(FE)echo ">>> exinext script built" # Target for the exiwhat utility script -exiwhat: config ../src/utils/exiwhat.src - @rm -f exiwhat - @. ./version.sh && sed \ +exiwhat: $(CONFIG_DEPS) ../src/utils/exiwhat.src + $(FE)rm -f exiwhat + $(FE). ./version.sh && sed \ -e "s?PROCESSED_FLAG?This file has been so processed.?"\ -e "/^# /p" \ -e "/^# /d" \ @@ -341,15 +340,15 @@ exiwhat: config ../src/utils/exiwhat.src -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \ -e "s?RM_COMMAND?$(RM_COMMAND)?" \ ../src/utils/exiwhat.src > exiwhat-t - @mv exiwhat-t exiwhat - @chmod a+x exiwhat - @./exiwhat -v 2>&1 >/dev/null - @echo ">>> exiwhat script built" + $(FE)mv exiwhat-t exiwhat + $(FE)chmod a+x exiwhat + $(FE)./exiwhat -v 2>&1 >/dev/null +# $(FE)echo ">>> exiwhat script built" # Target for the exim_checkaccess utility script -exim_checkaccess: config ../src/utils/exim_checkaccess.src - @rm -f exim_checkaccess - @. ./version.sh && sed \ +exim_checkaccess: $(CONFIG_DEPS) ../src/utils/exim_checkaccess.src + $(FE)rm -f exim_checkaccess + $(FE). ./version.sh && sed \ -e "s?PROCESSED_FLAG?This file has been so processed.?"\ -e "/^# /p" \ -e "/^# /d" \ @@ -360,17 +359,17 @@ exim_checkaccess: config ../src/utils/exim_checkaccess.src -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \ ../src/utils/exim_checkaccess.src > exim_checkaccess-t - @mv exim_checkaccess-t exim_checkaccess - @chmod a+x exim_checkaccess - # @./exim_checkaccess -v 2>&1 >/dev/null - @echo ">>> exim_checkaccess script built"; echo "" + $(FE)mv exim_checkaccess-t exim_checkaccess + $(FE)chmod a+x exim_checkaccess +# @./exim_checkaccess -v 2>&1 >/dev/null +# $(FE)echo ">>> exim_checkaccess script built"; echo "" # Target for the Exim monitor start-up script -eximon: config ../src/utils/eximon.src ../OS/eximon.conf-Default \ +eximon: $(CONFIG_DEPS) ../src/utils/eximon.src ../OS/eximon.conf-Default \ ../Local/eximon.conf - @rm -f eximon + $(FE)rm -f eximon $(SHELL) $(SCRIPTS)/Configure-eximon - @. ./version.sh && sed \ + $(FE). ./version.sh && sed \ -e "s?PROCESSED_FLAG?This file has been so processed.?"\ -e "/^# /p" \ -e "/^# /d" \ @@ -383,15 +382,15 @@ eximon: config ../src/utils/eximon.src ../OS/eximon.conf-Default \ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \ ../src/utils/eximon.src >> eximon - @./eximon -v 2>&1 >/dev/null - @echo ">>> eximon script built"; echo "" + $(FE)./eximon -v 2>&1 >/dev/null +# $(FE)echo ">>> eximon script built"; echo "" # Targets for utilities; these are all Perl scripts that have to get the # location of Perl put in them. A few need other things as well. -exigrep: util_deps ../src/utils/exigrep.src - @rm -f exigrep - @. ./version.sh && sed \ +exigrep: $(UTIL_DEPS) ../src/utils/exigrep.src + $(FE)rm -f exigrep + $(FE). ./version.sh && sed \ -e "s?PROCESSED_FLAG?This file has been so processed.?"\ -e "/^# /p" \ -e "/^# /d" \ @@ -407,14 +406,14 @@ exigrep: util_deps ../src/utils/exigrep.src -e "s?^?# Insert ?" \ -e "r ../src/utils/msgid.frag" \ ../src/utils/exigrep.src > exigrep-t - @mv exigrep-t exigrep - @chmod a+x exigrep - @./exigrep --version 2>&1 >/dev/null - @echo ">>> exigrep script built" - -exim_msgdate: util_deps ../src/utils/exim_msgdate.src - @rm -f exim_msgdate - @. ./version.sh && sed \ + $(FE)mv exigrep-t exigrep + $(FE)chmod a+x exigrep + $(FE)./exigrep --version 2>&1 >/dev/null +# $(FE)echo ">>> exigrep script built" + +exim_msgdate: $(UTIL_DEPS) ../src/utils/exim_msgdate.src + $(FE)rm -f exim_msgdate + $(FE). ./version.sh && sed \ -e "s?PROCESSED_FLAG?This file has been so processed.?"\ -e "/^[ \t]*# /p" \ -e "/^[ \t]*# /d" \ @@ -431,26 +430,26 @@ exim_msgdate: util_deps ../src/utils/exim_msgdate.src -e "s?^?# Insert ?" \ -e "r ../src/utils/msgid.frag" \ ../src/utils/exim_msgdate.src > exim_msgdate-t - @mv exim_msgdate-t exim_msgdate - @chmod a+x exim_msgdate - @./exim_msgdate -v 2>&1 >/dev/null - @echo ">>> exim_msgdate script built" - -eximstats: config ../src/utils/eximstats.src - @rm -f eximstats - @. ./version.sh && sed \ + $(FE)mv exim_msgdate-t exim_msgdate + $(FE)chmod a+x exim_msgdate + $(FE)./exim_msgdate -v 2>&1 >/dev/null +# $(FE)echo ">>> exim_msgdate script built" + +eximstats: $(CONFIG_DEPS) ../src/utils/eximstats.src + $(FE)rm -f eximstats + $(FE). ./version.sh && sed \ -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \ ../src/utils/eximstats.src > eximstats-t - @mv eximstats-t eximstats - @chmod a+x eximstats - @./eximstats -v 2>&1 >/dev/null - @echo ">>> eximstats script built" - -exiqgrep: util_deps ../src/utils/exiqgrep.src - @rm -f exiqgrep - @. ./version.sh && sed \ + $(FE)mv eximstats-t eximstats + $(FE)chmod a+x eximstats + $(FE)./eximstats -v 2>&1 >/dev/null +# $(FE)echo ">>> eximstats script built" + +exiqgrep: $(UTIL_DEPS) ../src/utils/exiqgrep.src + $(FE)rm -f exiqgrep + $(FE). ./version.sh && sed \ -e "s?PROCESSED_FLAG?This file has been so processed.?"\ -e "/^# /p" \ -e "/^# /d" \ @@ -465,14 +464,14 @@ exiqgrep: util_deps ../src/utils/exiqgrep.src -e "s?^?# Insert ?" \ -e "r ../src/utils/msgid.frag" \ ../src/utils/exiqgrep.src > exiqgrep-t - @mv exiqgrep-t exiqgrep - @chmod a+x exiqgrep - @./exiqgrep -v 2>&1 >/dev/null - @echo ">>> exiqgrep script built" - -exiqsumm: util_deps ../src/utils/exiqsumm.src - @rm -f exiqsumm - @. ./version.sh && sed \ + $(FE)mv exiqgrep-t exiqgrep + $(FE)chmod a+x exiqgrep + $(FE)./exiqgrep -v 2>&1 >/dev/null +# $(FE)echo ">>> exiqgrep script built" + +exiqsumm: $(UTIL_DEPS) ../src/utils/exiqsumm.src + $(FE)rm -f exiqsumm + $(FE). ./version.sh && sed \ -e "/^MSGID_RE/b mi" \ -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \ @@ -483,28 +482,28 @@ exiqsumm: util_deps ../src/utils/exiqsumm.src -e "s?^?# Insert ?" \ -e "r ../src/utils/msgid.frag" \ ../src/utils/exiqsumm.src > exiqsumm-t - @mv exiqsumm-t exiqsumm - @chmod a+x exiqsumm - @./exiqsumm -v 2>&1 >/dev/null - @echo ">>> exiqsumm script built" - -exipick: config ../src/utils/exipick.src - @rm -f exipick - @. ./version.sh && sed \ + $(FE)mv exiqsumm-t exiqsumm + $(FE)chmod a+x exiqsumm + $(FE)./exiqsumm -v 2>&1 >/dev/null +# $(FE)echo ">>> exiqsumm script built" + +exipick: $(CONFIG_DEPS) ../src/utils/exipick.src + $(FE)rm -f exipick + $(FE). ./version.sh && sed \ -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \ -e "s?SPOOL_DIRECTORY?$(SPOOL_DIRECTORY)?" \ -e "s?BIN_DIRECTORY?$(BIN_DIRECTORY)?" \ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \ ../src/utils/exipick.src > exipick-t - @mv exipick-t exipick - @chmod a+x exipick - @./exipick -v 2>&1 >/dev/null - @echo ">>> exipick script built" - -exim_id_update: util_deps ../src/utils/exim_id_update.src - @rm -f exim_id_update - @. ./version.sh && sed \ + $(FE)mv exipick-t exipick + $(FE)chmod a+x exipick + $(FE)./exipick -v 2>&1 >/dev/null +# $(FE)echo ">>> exipick script built" + +exim_id_update: $(UTIL_DEPS) ../src/utils/exim_id_update.src + $(FE)rm -f exim_id_update + $(FE). ./version.sh && sed \ -e "/^MSGID_RE/b mi" \ -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \ -e "s?SPOOL_DIRECTORY?$(SPOOL_DIRECTORY)?" \ @@ -517,21 +516,21 @@ exim_id_update: util_deps ../src/utils/exim_id_update.src -e "s?^?# Insert ?" \ -e "r ../src/utils/msgid.frag" \ ../src/utils/exim_id_update.src > exim_id_update-t - @mv exim_id_update-t exim_id_update - @chmod a+x exim_id_update - @./exim_id_update -v 2>&1 >/dev/null - @echo ">>> exim_id_update script built" - -transport-filter.pl: config ../src/transport-filter.src - @rm -f transport-filter.pl - @. ./version.sh && sed \ + $(FE)mv exim_id_update-t exim_id_update + $(FE)chmod a+x exim_id_update + $(FE)./exim_id_update -v 2>&1 >/dev/null +# $(FE)echo ">>> exim_id_update script built" + +transport-filter.pl: $(CONFIG_DEPS) ../src/transport-filter.src + $(FE)rm -f transport-filter.pl + $(FE). ./version.sh && sed \ -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \ ../src/transport-filter.src > transport-filter.pl-t - @mv transport-filter.pl-t transport-filter.pl - @chmod a+x transport-filter.pl - @echo ">>> transport-filter.pl script built" + $(FE)mv transport-filter.pl-t transport-filter.pl + $(FE)chmod a+x transport-filter.pl +# $(FE)echo ">>> transport-filter.pl script built" # These are objects of optional features. They are always compiled, but @@ -568,9 +567,8 @@ OBJ_EXIM = acl.o atrn.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o \ local_scan.o $(OBJ_WITH_CONTENT_SCAN) \ $(OBJ_EXPERIMENTAL) -exim: buildlookups buildauths \ - buildrouters buildtransports buildmisc \ - $(OBJ_EXIM) version.o +exim: buildlookups buildauths buildrouters buildtransports buildmisc \ + $(OBJ_EXIM) version.o @echo "$(LNCC) -o exim" $(FE)$(PURIFY) $(LNCC) -o exim $(LFLAGS) $(OBJ_EXIM) version.o \ routers/routers.a transports/transports.a lookups/lookups.a \ @@ -583,9 +581,9 @@ exim: buildlookups buildauths \ $(STRIP_COMMAND) exim; \ fi $(EXIM_CHMOD) - @echo " " - @echo ">>> exim binary built" - @echo " " +# @echo " " +# @echo ">>> exim binary built" +# @echo " " # The utility for dumping the contents of an exim database @@ -599,8 +597,8 @@ exim_dumpdb: $(OBJ_DUMPDB) echo $(STRIP_COMMAND) exim_dumpdb; \ $(STRIP_COMMAND) exim_dumpdb; \ fi - @echo ">>> exim_dumpdb utility built" - @echo " " +# @echo ">>> exim_dumpdb utility built" +# @echo " " # The utility for interrogating/fixing the contents of an exim database @@ -614,8 +612,8 @@ exim_fixdb: $(OBJ_FIXDB) echo $(STRIP_COMMAND) exim_fixdb; \ $(STRIP_COMMAND) exim_fixdb; \ fi - @echo ">>> exim_fixdb utility built" - @echo " " +# @echo ">>> exim_fixdb utility built" +# @echo " " # The utility for tidying the contents of an exim database @@ -630,8 +628,8 @@ exim_tidydb: $(OBJ_TIDYDB) echo $(STRIP_COMMAND) exim_tidydb; \ $(STRIP_COMMAND) exim_tidydb; \ fi - @echo ">>> exim_tidydb utility built" - @echo " " +# @echo ">>> exim_tidydb utility built" +# @echo " " # The utility for building dbm files @@ -646,8 +644,8 @@ exim_dbmbuild: $(OBJ_DBMBUILD) echo $(STRIP_COMMAND) exim_dbmbuild; \ $(STRIP_COMMAND) exim_dbmbuild; \ fi - @echo ">>> exim_dbmbuild utility built" - @echo " " +# @echo ">>> exim_dbmbuild utility built" +# @echo " " # The utility for locking a mailbox while messing around with it @@ -661,8 +659,8 @@ exim_lock: exim_lock.c os.h echo $(STRIP_COMMAND) exim_lock; \ $(STRIP_COMMAND) exim_lock; \ fi - @echo ">>> exim_lock utility built" - @echo " " +# @echo ">>> exim_lock utility built" +# @echo " " # The X-based Exim monitor program's binary part. There's a macro for cutting # out the modified TextPop module, because some antique link editors cannot @@ -701,8 +699,8 @@ eximon.bin: $(EXIMON_EDITME) eximon $(OBJ_MONBIN) ../exim_monitor/em_version.c \ echo $(STRIP_COMMAND) eximon.bin; \ $(STRIP_COMMAND) eximon.bin; \ fi - @echo ">>> exim monitor binary built" - @echo " " +# @echo ">>> exim monitor binary built" +# @echo " " # Compile step for most of the exim modules. HDRS is a list of headers @@ -861,7 +859,7 @@ util-os.o: $(HDRS) os.c # The local scan module depends only on its own special header, and is compiled # from a source whose location is set by configuration. -local_scan.o: config local_scan.h ../$(LOCAL_SCAN_SOURCE) +local_scan.o: $(CONFIG_DEPS) local_scan.h ../$(LOCAL_SCAN_SOURCE) @echo "$(CC) local_scan.c" $(FE)$(CC) -DLOCAL_SCAN -c $(CFLAGS) -I. $(INCLUDE) -o local_scan.o ../$(LOCAL_SCAN_SOURCE) @@ -1047,11 +1045,10 @@ $(MONBIN): $(HDRS) # Copies of modules built as dynamic-load libraries -dynmodules: buildlookups buildrouters buildtransports buildauths \ - buildmisc - rm -fr dynmodules - mkdir dynmodules - for d in lookup router transport auth miscmod; do \ +dynmodules: buildlookups buildrouters buildtransports buildauths buildmisc + $(FE)rm -fr dynmodules + $(FE)mkdir dynmodules + $(FE)for d in lookup router transport auth miscmod; do \ for f in $${d}s/*.so; do \ [ -e $$f ] && ln $$f dynmodules/`basename $$f .so`_$$d.so; \ done; \ @@ -1060,42 +1057,46 @@ dynmodules: buildlookups buildrouters buildtransports buildauths \ # The lookups library. -buildlookups: config - @cd lookups && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \ +buildlookups: $(CONFIG_DEPS) + @cd lookups && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \ CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" HDRS="../version.h $(PHDRS)" \ FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" \ INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE) $(LOOKUP_INCLUDE)" - @echo " " + $(FE)touch $@ +# @echo " " # The routers library. -buildrouters: config - @cd routers && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \ +buildrouters: $(CONFIG_DEPS) + @cd routers && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \ CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" \ FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" HDRS="$(PHDRS)" \ INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)" - @echo " " + $(FE)touch $@ +# @echo " " # The transports library. -buildtransports: config - @cd transports && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \ +buildtransports: $(CONFIG_DEPS) + @cd transports && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \ CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" \ FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" HDRS="$(PHDRS)" \ INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)" - @echo " " + $(FE)touch $@ +# @echo " " # The library of authorization modules -buildauths: config - @cd auths && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \ +buildauths: $(CONFIG_DEPS) + @cd auths && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \ CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" \ FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" HDRS="$(PHDRS)" \ INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)" - @echo " " + $(FE)touch $@ +# @echo " " -buildmisc: config - @cd miscmods && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) \ +buildmisc: $(CONFIG_DEPS) + @cd miscmods && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) \ CC="$(CC)" CFLAGS="$(CFLAGS)" \ CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" \ LDFLAGS_PARTIAL="$(LDFLAGS_PARTIAL)" HDRS="../version.h $(PHDRS)" \ @@ -1103,7 +1104,8 @@ buildmisc: config PERL_CC="$(PERL_CC)" PERL_CCOPTS="$(PERL_CCOPTS)" \ PERL_CFLAGS="$(PERL_CFLAGS)" PERL_LFLAGS="$(PERL_LFLAGS)" \ INCLUDE="$(INCLUDE) $(IPV6_INCLUDE)" TLS_INCLUDE="$(TLS_INCLUDE)" - @echo " " + $(FE)touch $@ +# @echo " " # The "clean", "install", and "makefile" targets just pass themselves back to # the main Exim makefile. These targets will be obeyed only if "make" is obeyed diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile index 73737274e..cf85d6306 100755 --- a/src/scripts/Configure-Makefile +++ b/src/scripts/Configure-Makefile @@ -38,15 +38,20 @@ rebuild=yes if [ -f Makefile ] ; then rebuild=no - if ../scripts/newer $editme Makefile || \ - ../scripts/newer $editme-$ostype Makefile || \ - ../scripts/newer $editme-$archtype Makefile || \ - ../scripts/newer $editme-$ostype-$archtype Makefile || \ - ../scripts/newer ../scripts/Configure-Makefile Makefile || \ - ../scripts/newer ../scripts/lookups-Makefile Makefile || \ - ../scripts/newer ../scripts/drivers-Makefile Makefile || \ - ../scripts/newer ../OS/Makefile-Base Makefile || \ - ../scripts/newer ../OS/Makefile-Default Makefile + if ../scripts/newer $editme Makefile \ + || ../scripts/newer $editme-$ostype Makefile \ + || ../scripts/newer $editme-$archtype Makefile \ + || ../scripts/newer $editme-$ostype-$archtype Makefile \ + || ../scripts/newer ../scripts/Configure-Makefile Makefile \ + || ../scripts/newer ../scripts/lookups-Makefile Makefile \ + || ../scripts/newer ../scripts/drivers-Makefile Makefile \ + || ../scripts/newer ../OS/Makefile-Base Makefile \ + || ../scripts/newer ../OS/Makefile-Default Makefile \ + || ../scripts/newer ../src/auths/Makefile Makefile \ + || ../scripts/newer ../src/lookups/Makefile Makefile \ + || ../scripts/newer ../src/miscmods/Makefile Makefile \ + || ../scripts/newer ../src/routers/Makefile Makefile \ + || ../scripts/newer ../src/transports/Makefile Makefile then rebuild=yes fi @@ -78,8 +83,8 @@ fi # If Makefile is up-to-date, no need to rebuild it. if [ $rebuild = no ] ; then - echo "\`Makefile' is up to date." - echo " " +# echo "\`Makefile' is up to date." +# echo " " exit fi diff --git a/src/src/auths/Makefile b/src/src/auths/Makefile index b929f6742..6eab96a66 100644 --- a/src/src/auths/Makefile +++ b/src/src/auths/Makefile @@ -17,6 +17,7 @@ OBJ += auth-spa.o all: auths.a $(MODS) + $(FE): auths.a: $(OBJ) @$(RM_COMMAND) -f auths.a diff --git a/src/src/lookups/Makefile b/src/src/lookups/Makefile index d794fe895..23347e83b 100644 --- a/src/src/lookups/Makefile +++ b/src/src/lookups/Makefile @@ -18,7 +18,8 @@ OBJS = $(OBJ) $(OBJ_ALWAYS) # This assumes that the driver and object-file names match AVAIL= $(OBJS:.o=) -all: Makefile lookups.a $(MODS) +all: Makefile lookups.a $(MODS) + $(FE): # We assume here that the driver name is used as the prefix for # its module_info struct. diff --git a/src/src/miscmods/Makefile b/src/src/miscmods/Makefile index 1e46d4456..cac4bd316 100644 --- a/src/src/miscmods/Makefile +++ b/src/src/miscmods/Makefile @@ -14,6 +14,7 @@ OBJ += dummy.o all: miscmods.a $(MODS) + $(FE): miscmods.a: $(OBJ) @$(RM_COMMAND) -f miscmods.a diff --git a/src/src/routers/Makefile b/src/src/routers/Makefile index 6c1dc6e9a..1b2953d40 100644 --- a/src/src/routers/Makefile +++ b/src/src/routers/Makefile @@ -12,7 +12,8 @@ # MAGIC-TAG-MODS-OBJ-RULES-GO-HERE -all: routers.a $(MODS) +all: routers.a $(MODS) + $(FE): routers.a: $(OBJ) @$(RM_COMMAND) -f routers.a diff --git a/src/src/transports/Makefile b/src/src/transports/Makefile index 454d36347..89cadc568 100644 --- a/src/src/transports/Makefile +++ b/src/src/transports/Makefile @@ -13,7 +13,8 @@ OBJ += smtp_socks.o tf_maildir.o -all: transports.a $(MODS) +all: transports.a $(MODS) + $(FE): transports.a: $(OBJ) smtp_socks.o tf_maildir.o @$(RM_COMMAND) -f transports.a commit 998636a4f8493855a351bb1080fc7687269cb9f9 Author: Jeremy Harris Date: Wed Nov 12 11:20:42 2025 +0000 library versions diff --git a/src/scripts/reversion b/src/scripts/reversion index 6fd0782a4..00b4058d3 100755 --- a/src/scripts/reversion +++ b/src/scripts/reversion @@ -122,9 +122,9 @@ then test -n "$EXIM_VARIANT_VERSION" && \ echo '#define EXIM_VARIANT_VERSION "'"$EXIM_VARIANT_VERSION"'"' echo '#ifdef EXIM_VARIANT_VERSION' - echo '#define EXIM_VERSION_STR EXIM_RELEASE_VERSION "-" EXIM_VARIANT_VERSION' + echo '# define EXIM_VERSION_STR EXIM_RELEASE_VERSION "-" EXIM_VARIANT_VERSION' echo '#else' - echo '#define EXIM_VERSION_STR EXIM_RELEASE_VERSION' + echo '# define EXIM_VERSION_STR EXIM_RELEASE_VERSION' echo '#endif' if [ ".${exim_build_date_override:-}" != "." ]; then echo '#define EXIM_BUILD_DATE_OVERRIDE "'"${exim_build_date_override}"'"' diff --git a/src/src/exim.c b/src/src/exim.c index 9d6f858bd..13cc4ff2c 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -1316,19 +1316,15 @@ Currently they are output in misc_mod_add() */ #ifndef PCRE_PRERELEASE # define PCRE_PRERELEASE #endif -#define QUOTE(X) #X -#define EXPAND_AND_QUOTE(X) QUOTE(X) { uschar buf[24]; pcre2_config(PCRE2_CONFIG_VERSION, buf); g = string_fmt_append(g, "Library version: PCRE2: Compile: %d.%d%s\n" " Runtime: %s\n", PCRE2_MAJOR, PCRE2_MINOR, - EXPAND_AND_QUOTE(PCRE2_PRERELEASE) "", + mac_expanded_string(PCRE2_PRERELEASE) "", buf); } -#undef QUOTE -#undef EXPAND_AND_QUOTE show_string(is_stdout, g); g = NULL; diff --git a/src/src/lookups/cdb.c b/src/src/lookups/cdb.c index b56306a7f..98ff462a3 100644 --- a/src/src/lookups/cdb.c +++ b/src/src/lookups/cdb.c @@ -462,10 +462,8 @@ if (cdbp->cdb_map) gstring * cdb_version_report(gstring * g) { -#ifdef DYNLOOKUP -g = string_fmt_append(g, "Library version: CDB: Exim version %s\n", EXIM_VERSION_STR); -#endif -return g; +return string_fmt_append(g, "Library version: CDB: Exim %s builtin\n", + EXIM_VERSION_STR); } diff --git a/src/src/lookups/dnsdb.c b/src/src/lookups/dnsdb.c index d08f6122e..6e2be8ae1 100644 --- a/src/src/lookups/dnsdb.c +++ b/src/src/lookups/dnsdb.c @@ -591,10 +591,8 @@ return rc; gstring * dnsdb_version_report(gstring * g) { -#ifdef DYNLOOKUP -g = string_fmt_append(g, "Library version: DNSDB: Exim version %s\n", EXIM_VERSION_STR); -#endif -return g; +return string_fmt_append(g, "Library version: DNSDB: Exim %s builtin\n", + EXIM_VERSION_STR); } diff --git a/src/src/lookups/dsearch.c b/src/src/lookups/dsearch.c index 99e1c8648..4fb8f0c27 100644 --- a/src/src/lookups/dsearch.c +++ b/src/src/lookups/dsearch.c @@ -172,10 +172,8 @@ handle = handle; /* Avoid compiler warning */ gstring * dsearch_version_report(gstring * g) { -#ifdef DYNLOOKUP -g = string_fmt_append(g, "Library version: dsearch: Exim version %s\n", EXIM_VERSION_STR); -#endif -return g; +return string_fmt_append(g, "Library version: dsearch: Exim %s builtin\n", + EXIM_VERSION_STR); } diff --git a/src/src/lookups/json.c b/src/src/lookups/json.c index 43575cacf..3e5f9609e 100644 --- a/src/src/lookups/json.c +++ b/src/src/lookups/json.c @@ -162,7 +162,8 @@ json_close(void *handle) gstring * json_version_report(gstring * g) { -return string_fmt_append(g, "Library version: json: Jansonn version %s\n", JANSSON_VERSION); +return string_fmt_append(g, "Library version: json: Jansonn version %s\n", + JANSSON_VERSION); } diff --git a/src/src/lookups/ldap.c b/src/src/lookups/ldap.c index a6e99f567..c657797d9 100644 --- a/src/src/lookups/ldap.c +++ b/src/src/lookups/ldap.c @@ -1563,11 +1563,29 @@ return quoted; gstring * ldap_version_report(gstring * g) { -#ifdef DYNLOOKUP -/*XXX it would be nice to haul a version string for the underlying ldap library */ -g = string_fmt_append(g, "Library version: LDAP: Exim version %s\n", EXIM_VERSION_STR); -#endif +#ifdef LDAP_LIB_OPENLDAP2 +LDAP * ld = ldap_init("", 0); +LDAPAPIInfo info = {.ldapai_info_version = LDAP_API_INFO_VERSION}; + +g = string_fmt_append(g, "Library version: LDAP: Compile %s %u.%u.%u\n", + LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION_MAJOR, + LDAP_VENDOR_VERSION_MINOR, LDAP_VENDOR_VERSION_PATCH); + +if (ldap_get_option(ld, LDAP_OPT_API_INFO, &info) == LDAP_OPT_SUCCESS) + { + g = string_fmt_append(g, " Runtime %s %d\n", + info.ldapai_vendor_name, info.ldapai_vendor_version); + + for (char ** sp = info.ldapai_extensions; *sp; *sp++) + ldap_memfree(*sp); + ldap_memfree(info.ldapai_extensions); + ldap_memfree(info.ldapai_vendor_name); + } return g; + +#else +return string_fmt_append(g, "Library version: LDAP: (unknown)\n"); +#endif } diff --git a/src/src/lookups/lmdb.c b/src/src/lookups/lmdb.c index 3a3eebcba..c90632cee 100644 --- a/src/src/lookups/lmdb.c +++ b/src/src/lookups/lmdb.c @@ -134,10 +134,9 @@ gstring * lmdb_version_report(gstring * g) { g = string_fmt_append(g, "Library version: LMDB: Compile: %d.%d.%d\n", - MDB_VERSION_MAJOR, MDB_VERSION_MINOR, MDB_VERSION_PATCH); -#ifdef DYNLOOKUP -g = string_fmt_append(g, " Exim version %s\n", EXIM_VERSION_STR); -#endif + MDB_VERSION_MAJOR, MDB_VERSION_MINOR, MDB_VERSION_PATCH); +g = string_fmt_append(g, " Runtime: %s\n", + mdb_version(NULL, NULL, NULL)); return g; } diff --git a/src/src/lookups/lsearch.c b/src/src/lookups/lsearch.c index 89a9e6c80..fc7ebaf62 100644 --- a/src/src/lookups/lsearch.c +++ b/src/src/lookups/lsearch.c @@ -418,10 +418,8 @@ lsearch_close(void *handle) gstring * lsearch_version_report(gstring * g) { -#ifdef DYNLOOKUP -g = string_fmt_append(g, "Library version: lsearch: Exim version %s\n", EXIM_VERSION_STR); -#endif -return g; +return string_fmt_append(g, "Library version: lsearch: Exim %s builtin\n", + EXIM_VERSION_STR); } diff --git a/src/src/lookups/mysql.c b/src/src/lookups/mysql.c index bbf98cb0c..76954ca43 100644 --- a/src/src/lookups/mysql.c +++ b/src/src/lookups/mysql.c @@ -475,12 +475,8 @@ mysql_version_report(gstring * g) g = string_fmt_append(g, "Library version: MySQL: Compile: %lu %s [%s]\n" " Runtime: %lu %s\n", - (long)EXIM_MxSQL_VERSION_ID, EXIM_MxSQL_VERSION_STR, EXIM_MxSQL_BASE_STR, - mysql_get_client_version(), mysql_get_client_info()); -#ifdef DYNLOOKUP -g = string_fmt_append(g, - " Exim version %s\n", EXIM_VERSION_STR); -#endif + (long)EXIM_MxSQL_VERSION_ID, EXIM_MxSQL_VERSION_STR, EXIM_MxSQL_BASE_STR, + mysql_get_client_version(), mysql_get_client_info()); return g; } diff --git a/src/src/lookups/nis.c b/src/src/lookups/nis.c index aa12f4742..77f6b0f68 100644 --- a/src/src/lookups/nis.c +++ b/src/src/lookups/nis.c @@ -101,6 +101,7 @@ return (rc == YPERR_KEY || rc == YPERR_MAP)? FAIL : DEFER; gstring * nis_version_report(gstring * g) { +/* maybe part of glibc? */ #ifdef DYNLOOKUP g = string_fmt_append(g, "Library version: NIS: Exim version %s\n", EXIM_VERSION_STR); #endif diff --git a/src/src/lookups/nmh.c b/src/src/lookups/nmh.c index c2fd0e61c..0e93186c1 100644 --- a/src/src/lookups/nmh.c +++ b/src/src/lookups/nmh.c @@ -347,6 +347,7 @@ while ((cn = nmh_connections)) gstring * nmh_version_report(gstring * g) { +/* NMH has no version api! */ #ifdef DYNLOOKUP g = string_fmt_append(g, "Library version: NMH: Exim version %s\n", EXIM_VERSION_STR); #endif diff --git a/src/src/lookups/passwd.c b/src/src/lookups/passwd.c index 7bf499920..c22c2d4cc 100644 --- a/src/src/lookups/passwd.c +++ b/src/src/lookups/passwd.c @@ -58,10 +58,8 @@ return OK; gstring * passwd_version_report(gstring * g) { -#ifdef DYNLOOKUP -g = string_fmt_append(g, "Library version: passwd: Exim version %s\n", EXIM_VERSION_STR); -#endif -return g; +return string_fmt_append(g, "Library version: passwd: Exim %s builtin\n", + EXIM_VERSION_STR); } static lookup_info _lookup_info = { diff --git a/src/src/lookups/pgsql.c b/src/src/lookups/pgsql.c index a578645ff..8ddf0e058 100644 --- a/src/src/lookups/pgsql.c +++ b/src/src/lookups/pgsql.c @@ -477,9 +477,7 @@ return quoted; gstring * pgsql_version_report(gstring * g) { -#ifdef DYNLOOKUP -g = string_fmt_append(g, "Library version: PostgreSQL: Exim version %s\n", EXIM_VERSION_STR); -#endif +int ver = PQlibVersion(); /* Version reporting: there appears to be no available information about the client library in libpq-fe.h; once you have a connection object, you @@ -487,6 +485,9 @@ can access the server version and the chosen protocol version, but those aren't really what we want. It might make sense to debug_printf those when the connection is established though? */ +/* version 9.1 onwards */ +g = string_fmt_append(g, "Library version: PostgreSQL: Runtime: %d.%d\n", + ver/10000, ver%10000); return g; } diff --git a/src/src/lookups/psl.c b/src/src/lookups/psl.c index 1592fe32d..9171192a3 100644 --- a/src/src/lookups/psl.c +++ b/src/src/lookups/psl.c @@ -212,10 +212,8 @@ return psl_gen_find(handle, keystring, length, result, errmsg, TRUE); gstring * psl_version_report(gstring * g) { -#ifdef DYNLOOKUP -g = string_fmt_append(g, "Library version: psl: Exim version %s\n", EXIM_VERSION_STR); -#endif -return g; +return string_fmt_append(g, "Library version: psl: Exim %s builtin\n", + EXIM_VERSION_STR); } static lookup_info psl_lookup_info = { diff --git a/src/src/lookups/redis.c b/src/src/lookups/redis.c index 4b11475d0..a721d0605 100644 --- a/src/src/lookups/redis.c +++ b/src/src/lookups/redis.c @@ -433,13 +433,8 @@ return quoted; gstring * redis_version_report(gstring * g) { -g = string_fmt_append(g, - "Library version: REDIS: Compile: %d [%d]\n", HIREDIS_MAJOR, HIREDIS_MINOR); -#ifdef DYNLOOKUP -g = string_fmt_append(g, - " Exim version %s\n", EXIM_VERSION_STR); -#endif -return g; +return string_fmt_append(g, "Library version: REDIS: Compile: %d.%d.%d\n", + HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH); } diff --git a/src/src/lookups/spf.c b/src/src/lookups/spf.c index e1931ca0f..ce314f7e5 100644 --- a/src/src/lookups/spf.c +++ b/src/src/lookups/spf.c @@ -86,10 +86,11 @@ return FAIL; gstring * spf_version_report(gstring * g) { -#ifdef DYNLOOKUP -g = string_fmt_append(g, "Library version: SPF: Exim version %s\n", EXIM_VERSION_STR)); -#endif -return g; +int maj, min, patch; + +SPF_get_lib_version(&maj, &min, &patch); +return string_fmt_append(g, "Library version: SPF: Runtime: %d.%d.%d\n", + maj, min, patch); } diff --git a/src/src/lookups/sqlite.c b/src/src/lookups/sqlite.c index 722916cf3..e358c9f84 100644 --- a/src/src/lookups/sqlite.c +++ b/src/src/lookups/sqlite.c @@ -172,10 +172,6 @@ g = string_fmt_append(g, "Library version: SQLite: Compile: %s\n" " Runtime: %s\n", SQLITE_VERSION, sqlite3_libversion()); -#ifdef DYNLOOKUP -g = string_fmt_append(g, - " Exim version %s\n", EXIM_VERSION_STR); -#endif return g; } diff --git a/src/src/lookups/testdb.c b/src/src/lookups/testdb.c index 44d77b8c8..2e3ed36c2 100644 --- a/src/src/lookups/testdb.c +++ b/src/src/lookups/testdb.c @@ -83,10 +83,8 @@ return quoted; gstring * testdb_version_report(gstring * g) { -#ifdef DYNLOOKUP -g = string_fmt_append(g, "Library version: TestDB: Exim version %s\n", EXIM_VERSION_STR); -#endif -return g; +return string_fmt_append(g, "Library version: TestDB: Exim version %s\n", + EXIM_VERSION_STR); } diff --git a/src/src/lookups/whoson.c b/src/src/lookups/whoson.c index cd6c7e85c..f783a905a 100644 --- a/src/src/lookups/whoson.c +++ b/src/src/lookups/whoson.c @@ -68,13 +68,8 @@ switch (wso_query(CS query, CS buffer, sizeof(buffer))) gstring * whoson_version_report(gstring * g) { -g = string_fmt_append(g, +return string_fmt_append(g, "Library version: Whoson: Runtime: %s\n", wso_version()); -#ifdef DYNLOOKUP -g = string_fmt_append(g, - " Exim version %s\n", EXIM_VERSION_STR); -#endif -return g; } static lookup_info _lookup_info = { diff --git a/src/src/miscmods/spf_perl.c b/src/src/miscmods/spf_perl.c index d491ae64e..e7bc1e331 100644 --- a/src/src/miscmods/spf_perl.c +++ b/src/src/miscmods/spf_perl.c @@ -121,16 +121,14 @@ return g; /******************************************************************************/ -#ifdef notdef /*API*/ static gstring * spf_lib_version_report(gstring * g) { /*XXX Does Mail::SPF have a version? MetaCPAN says yes, but does not document a method that returns it. */ -return g; +return string_fmt_append(g, "Library_version: SPF: perl Mail::SPF\n"); } -#endif @@ -389,7 +387,7 @@ misc_module_info spf_module_info = # ifdef DYNLOOKUP .dyn_magic = MISC_MODULE_MAGIC, # endif - /* .lib_vers_report = spf_lib_version_report, */ + .lib_vers_report = spf_lib_version_report, .conn_init = spf_conn_init, .smtp_reset = spf_smtp_reset, .authres = authres_spf, commit ed97fea3e920289ac87f1736146c68f4ce3050a8 Author: Jeremy Harris Date: Wed Nov 12 14:37:42 2025 +0000 Fix non-openldap2 build Broken-by: 998636a4f849 diff --git a/src/src/lookups/ldap.c b/src/src/lookups/ldap.c index c657797d9..7d240309c 100644 --- a/src/src/lookups/ldap.c +++ b/src/src/lookups/ldap.c @@ -1584,7 +1584,7 @@ if (ldap_get_option(ld, LDAP_OPT_API_INFO, &info) == LDAP_OPT_SUCCESS) return g; #else -return string_fmt_append(g, "Library version: LDAP: (unknown)\n"); +return string_cat(g, "Library version: LDAP: (unknown)\n"); #endif } commit 86161af9d94e2beeb7ad630e80247ef791c22313 Author: Jeremy Harris Date: Wed Nov 12 21:40:04 2025 +0000 Experimental native DMARC Bug 3140 diff --git a/src/Makefile b/src/Makefile index a19c6928a..eac96323e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -124,7 +124,7 @@ distclean: clean_doc cscope.files: FRC echo "-q" > $@ echo "-p3" >> $@ - -bd=build-$(buildname); [ -d $$bd ] && echo -e "$$bd/config.h\n$$bd/Makefile" >> $@ + -bd=build-$(buildname); [ -d $$bd ] && echo -e "$$bd/config.h\n$$bd/version.h\n$$bd/Makefile" >> $@ find src Local OS exim_monitor -name "*.[cshyl]" -print \ -o -name "*.src" -print \ -o -name "os.[ch]*" -print \ diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index 80f1f1f97..f713ad3ca 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -1062,7 +1062,6 @@ buildlookups: $(CONFIG_DEPS) CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" HDRS="../version.h $(PHDRS)" \ FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" \ INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE) $(LOOKUP_INCLUDE)" - $(FE)touch $@ # @echo " " # The routers library. @@ -1072,7 +1071,6 @@ buildrouters: $(CONFIG_DEPS) CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" \ FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" HDRS="$(PHDRS)" \ INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)" - $(FE)touch $@ # @echo " " # The transports library. @@ -1082,17 +1080,15 @@ buildtransports: $(CONFIG_DEPS) CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" \ FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" HDRS="$(PHDRS)" \ INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)" - $(FE)touch $@ # @echo " " -# The library of authorization modules +# The library of authentication modules buildauths: $(CONFIG_DEPS) @cd auths && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \ CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" \ FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" HDRS="$(PHDRS)" \ INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)" - $(FE)touch $@ # @echo " " buildmisc: $(CONFIG_DEPS) @@ -1104,7 +1100,6 @@ buildmisc: $(CONFIG_DEPS) PERL_CC="$(PERL_CC)" PERL_CCOPTS="$(PERL_CCOPTS)" \ PERL_CFLAGS="$(PERL_CFLAGS)" PERL_LFLAGS="$(PERL_LFLAGS)" \ INCLUDE="$(INCLUDE) $(IPV6_INCLUDE)" TLS_INCLUDE="$(TLS_INCLUDE)" - $(FE)touch $@ # @echo " " # The "clean", "install", and "makefile" targets just pass themselves back to diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile index cf85d6306..70dc886ab 100755 --- a/src/scripts/Configure-Makefile +++ b/src/scripts/Configure-Makefile @@ -326,7 +326,7 @@ done <<-END routers ROUTER ACCEPT DNSLOOKUP IPLITERAL IPLOOKUP MANUALROUTE QUERYPROGRAM REDIRECT transports TRANSPORT APPENDFILE AUTOREPLY LMTP PIPE QUEUEFILE SMTP auths AUTH CRAM_MD5 CYRUS_SASL DOVECOT EXTERNAL GSASL HEIMDAL_GSSAPI PLAINTEXT SPA TLS - miscmods SUPPORT ARC _DKIM DMARC _EXIM_FILTER PAM PERL RADIUS _SIEVE_FILTER SPF SPF_PERL + miscmods SUPPORT ARC _DKIM DMARC DMARC_NATIVE _EXIM_FILTER PAM PERL RADIUS _SIEVE_FILTER SPF SPF_PERL END # See if there is a definition of EXIM_PERL in what we have built so far. diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index 4e4a638c7..517310c8a 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -101,7 +101,7 @@ for f in dummy.c \ dkim.c dkim_transport.c dkim.h dkim_api.h \ pdkim/crypt_ver.h pdkim/pdkim.c pdkim/pdkim.h \ pdkim/pdkim_hash.h pdkim/signing.c pdkim/signing.h \ - dmarc.c dmarc.h dmarc_api.h \ + dmarc.c dmarc_common.c dmarc.h dmarc_api.h dmarc_native.c \ exim_filter.c exim_filter_api.h \ pam.c pam_api.h \ perl.c perl_api.h \ diff --git a/src/src/acl.c b/src/src/acl.c index 7d07ab698..be61fc953 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -77,7 +77,7 @@ enum { ACLC_ACL, ACLC_DKIM_SIGNER, ACLC_DKIM_STATUS, #endif -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC ACLC_DMARC_STATUS, #endif ACLC_DNSLISTS, @@ -206,9 +206,9 @@ static condition_def conditions[] = { ), }, #endif -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC [ACLC_DMARC_STATUS] = { US"dmarc_status", -# if SUPPORT_DMARC==2 +# if EXIM_HAVE_DMARC==2 ACD_LOAD | # endif ACD_EXP, @@ -390,7 +390,7 @@ static int spf_condx[] = { ACLC_SPF, ACLC_SPF_GUESS, -1 }; # if SUPPORT_DKIM==2 static int dkim_condx[] = { ACLC_DKIM_SIGNER, ACLC_DKIM_STATUS, -1 }; # endif -# if SUPPORT_DMARC==2 +# if EXIM_HAVE_DMARC==2 static int dmarc_condx[] = { ACLC_DMARC_STATUS, -1 }; # endif @@ -404,7 +404,7 @@ static condition_module condition_modules[] = { # if SUPPORT_DKIM==2 {.mod_name = US"dkim", .conditions = dkim_condx}, # endif -# if SUPPORT_DMARC==2 +# if EXIM_HAVE_DMARC==2 {.mod_name = US"dmarc", .conditions = dmarc_condx}, # endif }; @@ -424,7 +424,7 @@ enum { #ifndef DISABLE_DKIM CONTROL_DKIM_VERIFY, #endif -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC CONTROL_DMARC_VERIFY, CONTROL_DMARC_FORENSIC, #endif @@ -492,7 +492,7 @@ static control_def controls_list[] = { }, #endif -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC [CONTROL_DMARC_VERIFY] = { US"dmarc_disable_verify", FALSE, ACL_BIT_DATA | ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START @@ -3481,7 +3481,7 @@ for (; cb; cb = cb->next) #ifndef DISABLE_DKIM case CONTROL_DKIM_VERIFY: f.dkim_disable_verify = TRUE; -# ifdef SUPPORT_DMARC +# ifdef EXIM_HAVE_DMARC /* Since DKIM was blocked, skip DMARC too */ f.dmarc_disable_verify = TRUE; f.dmarc_enable_forensic = FALSE; @@ -3489,7 +3489,7 @@ for (; cb; cb = cb->next) break; #endif -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC case CONTROL_DMARC_VERIFY: f.dmarc_disable_verify = TRUE; break; @@ -3934,7 +3934,7 @@ for (; cb; cb = cb->next) } #endif -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC case ACLC_DMARC_STATUS: /* See comment on ACLC_SPF wrt. coding issues */ { @@ -3945,7 +3945,7 @@ for (; cb; cb = cb->next) if (!mi) { rc = DEFER; break; } /* shouldn't happen */ - if (!f.dmarc_has_been_checked) + if (!f.dmarc_has_been_checked) /* only once per message */ { typedef int (*pfn_t)(void); (void) (((pfn_t *) mi->functions)[DMARC_PROCESS]) (); @@ -3955,7 +3955,6 @@ for (; cb; cb = cb->next) /* used long way of dmarc_exim_expand_query() in case we need more view into the process in the future. */ - /*XXX is this call used with any other arg? */ expanded_query = (((efn_t *) mi->functions)[DMARC_EXPAND_QUERY]) (); rc = match_isinlist(expanded_query, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults index f331ad0d5..71146ae3b 100644 --- a/src/src/config.h.defaults +++ b/src/src/config.h.defaults @@ -217,6 +217,7 @@ Do not put spaces between # and the 'define'. /* EXPERIMENTAL features */ #define EXPERIMENTAL_ARC #define EXPERIMENTAL_DCC +#define EXPERIMENTAL_DMARC_NATIVE #define EXPERIMENTAL_DSN_INFO #define EXPERIMENTAL_NMH #define EXPERIMENTAL_QUEUEFILE diff --git a/src/src/drtables.c b/src/src/drtables.c index 5993b5c29..9e25472d3 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -481,7 +481,7 @@ built as static */ #if !defined(DISABLE_DKIM) && (!defined(SUPPORT_DKIM) || SUPPORT_DKIM!=2) extern misc_module_info dkim_module_info; #endif -#if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2 +#if defined(EXIM_HAVE_DMARC) && EXIM_HAVE_DMARC!=2 extern misc_module_info dmarc_module_info; #endif #if defined(EXIM_HAVE_SPF) && EXIM_HAVE_SPF!=2 @@ -519,13 +519,13 @@ onetime = TRUE; #if defined(EXIM_HAVE_SPF) && EXIM_HAVE_SPF!=2 misc_mod_add(&spf_module_info); #endif -#if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2 -/* dmarc depends on spf so this add must go after, for the both-static case */ - misc_mod_add(&dmarc_module_info); -#endif #if defined(EXPERIMENTAL_ARC) && (!defined(SUPPORT_ARC) || SUPPORT_ARC!=2) misc_mod_add(&arc_module_info); #endif +#if defined(EXIM_HAVE_DMARC) && EXIM_HAVE_DMARC!=2 +/* dmarc depends on spf/dkim/arc so this add must go after, for the both-static case */ + misc_mod_add(&dmarc_module_info); +#endif #if defined(RADIUS_CONFIG_FILE) && (!defined(SUPPORT_RADIUS) || SUPPORT_RADIUS!=2) misc_mod_add(&radius_module_info); #endif diff --git a/src/src/exim.c b/src/src/exim.c index 13cc4ff2c..c23eb868c 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -1176,7 +1176,7 @@ g = string_cat(g, US"Support for:"); #ifndef DISABLE_DKIM g = string_cat(g, US" DKIM"); #endif -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC g = string_cat(g, US" DMARC"); #endif #ifndef DISABLE_DNSSEC diff --git a/src/src/exim.h b/src/src/exim.h index 18d1b1f40..538f354e2 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -535,8 +535,6 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly. #if !defined(MACRO_PREDEF) && !defined(COMPILE_UTILITY) # include "hash.h" #endif -#include "globals.h" -#include "functions.h" #if !defined(MACRO_PREDEF) && !defined(COMPILE_UTILITY) # include "dbfunctions.h" #endif @@ -550,10 +548,12 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly. # include "miscmods/dkim.h" # include "miscmods/dkim_api.h" #endif -#ifdef SUPPORT_DMARC +#if defined(SUPPORT_DMARC) || defined(EXPERIMENTAL_DMARC_NATIVE) # include "miscmods/dmarc.h" # include "miscmods/dmarc_api.h" -# include +# ifdef SUPPORT_DMARC +# include +# endif #endif #ifdef EXPERIMENTAL_ARC # include "miscmods/arc_api.h" @@ -570,6 +570,9 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly. #include "miscmods/exim_filter_api.h" #include "miscmods/sieve_filter_api.h" +#include "globals.h" +#include "functions.h" + /* The following stuff must follow the inclusion of config.h because it requires various things that are set therein. */ diff --git a/src/src/expand.c b/src/src/expand.c index fcf699d2e..1b5980f5b 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -514,7 +514,7 @@ static var_entry var_table[] = { { "dkim_verify_signers", vtype_module, US"dkim" }, { "dkim_verify_status", vtype_module, US"dkim" }, #endif -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC { "dmarc_alignment_dkim",vtype_module, US"dmarc" }, { "dmarc_alignment_spf", vtype_module, US"dmarc" }, { "dmarc_domain_policy", vtype_module, US"dmarc" }, diff --git a/src/src/globals.c b/src/src/globals.c index c8636e191..36f364996 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -251,7 +251,7 @@ struct global_flags f = .dkim_disable_verify = FALSE, .dkim_init_done = FALSE, #endif -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC .dmarc_has_been_checked = FALSE, .dmarc_disable_verify = FALSE, .dmarc_enable_forensic = FALSE, diff --git a/src/src/globals.h b/src/src/globals.h index ddf78b4d8..9f4d04a2d 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -221,7 +221,7 @@ extern struct global_flags { BOOL dkim_disable_verify :1; /* Set via ACL control statement. When set, DKIM verification is disabled for the current message */ BOOL dkim_init_done :1; /* lazy-init status */ #endif -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC BOOL dmarc_has_been_checked :1; /* Global variable to check if test has been called yet */ BOOL dmarc_disable_verify :1; /* Set via ACL control statement. When set, DMARC verification is disabled for the current message */ BOOL dmarc_enable_forensic :1; /* Set via ACL control statement. When set, DMARC forensic reports are enabled for the current message */ diff --git a/src/src/log.c b/src/src/log.c index c485fc285..41f91a1ad 100644 --- a/src/src/log.c +++ b/src/src/log.c @@ -260,9 +260,6 @@ subprocess when the original process is root. Arguments: name the file name -The file name has been build in a working buffer, so it is permissible to -overwrite it temporarily if it is necessary to create the directory. - Returns: a file descriptor, or < 0 on failure (errno set) */ @@ -286,15 +283,14 @@ problem. */ if (fd < 0 && errno == ENOENT) { BOOL created; - uschar *lastslash = Ustrrchr(name, '/'); - *lastslash = 0; - created = directory_make(NULL, name, LOG_DIRECTORY_MODE, FALSE); + const uschar * lastslash = Ustrrchr(name, '/'); + uschar * dirname = string_copyn(name, lastslash - name); + created = directory_make(NULL, dirname, LOG_DIRECTORY_MODE, FALSE); DEBUG(D_any) if (created) - debug_printf("created log directory %s\n", name); + debug_printf("created log directory %s\n", dirname); else - debug_printf("failed to create log directory %s: %s\n", name, strerror(errno)); - *lastslash = '/'; + debug_printf("failed to create log directory %s: %s\n", dirname, strerror(errno)); if (created) fd = Uopen(name, flags, LOG_MODE); } diff --git a/src/src/lookups/testdb.c b/src/src/lookups/testdb.c index 2e3ed36c2..7745bd193 100644 --- a/src/src/lookups/testdb.c +++ b/src/src/lookups/testdb.c @@ -109,7 +109,7 @@ static lookup_info testdb2_lookup_info = { .close = NULL, /* no close function */ .tidy = NULL, /* no tidy function */ .quote = testdb_quote, /* same quoting function */ - .version_report = testdb_version_report /* version reporting */ + .version_report = NULL /* version reporting */ }; static lookup_info testdb3_lookup_info = { @@ -121,7 +121,7 @@ static lookup_info testdb3_lookup_info = { .close = NULL, /* no close function */ .tidy = NULL, /* no tidy function */ .quote = NULL, /* NO quoting function */ - .version_report = testdb_version_report /* version reporting */ + .version_report = NULL /* version reporting */ }; #ifdef DYNLOOKUP diff --git a/src/src/macro_predef.c b/src/src/macro_predef.c index 79cbd5290..a8ebc40d4 100644 --- a/src/src/macro_predef.c +++ b/src/src/macro_predef.c @@ -158,7 +158,7 @@ due to conflicts with other common macros. */ #ifndef DISABLE_DKIM builtin_macro_create(US"_HAVE_DKIM"); #endif -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC builtin_macro_create(US"_HAVE_DMARC"); #endif #ifndef DISABLE_DNSSEC diff --git a/src/src/macros.h b/src/src/macros.h index 75563d86b..c0900eee9 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -240,10 +240,8 @@ enum { ERRMESS_TOOBIG, /* Message too big */ ERRMESS_TOOMANYRECIP, /* Too many recipients */ ERRMESS_LOCAL_SCAN, /* Rejected by local scan */ - ERRMESS_LOCAL_ACL /* Rejected by non-SMTP ACL */ -#ifdef SUPPORT_DMARC - ,ERRMESS_DMARC_FORENSIC /* DMARC Forensic Report */ -#endif + ERRMESS_LOCAL_ACL, /* Rejected by non-SMTP ACL */ + ERRMESS_DMARC_FORENSIC /* DMARC Forensic Report */ }; /* Error handling styles - set by option, and apply only when receiving diff --git a/src/src/miscmods/Makefile b/src/src/miscmods/Makefile index cac4bd316..e92c34941 100644 --- a/src/src/miscmods/Makefile +++ b/src/src/miscmods/Makefile @@ -37,7 +37,10 @@ arc.o arc.so: $(HDRS) pdkim.h arc.c dkim.o dkim.so: $(HDRS) dkim.h dkim.c dkim_transport.c \ crypt_ver.h pdkim.h pdkim_hash.h pdkim.c \ signing.h signing.c -dmarc.o dmarc.so: $(HDRS) spf.h pdkim.h dmarc.h dmarc.c +dmarc.o dmarc.so: $(HDRS) spf.h pdkim.h dmarc.h \ + dmarc.c dmarc_common.c +dmarc_native.o dmarc_native.so: $(HDRS) spf.h pdkim.h dmarc.h \ + dmarc_native.c dmarc_common.c dummy.o: dummy.c exim_filter.o exim_filter.so: $(HDRS) exim_filter.c pam.o pam.so: $(HDRS) pam.c @@ -52,10 +55,6 @@ spf_perl.o spf_perl.so: $(HDRS) spf.h spf_perl.c # We need a single .o because that's what scripts/Configure-Makefile # understands and fills in to $(OBJ). # Try desparately to get the Solaris cc/ld to build one. -#dkim.o: -# @echo "$(CC) dkim.c dkim_transport.c pdkim.c signing.c" -# $(FE)$(CC) -r $(LDFLAGS_PARTIAL) -o $@ $(CFLAGS) $(INCLUDE) \ -# dkim.c dkim_transport.c pdkim.c signing.c dkim.o: @echo "$(CC) dkim.c dkim_transport.c pdkim.c signing.c" $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) dkim.c @@ -86,6 +85,22 @@ spf_perl.so: -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) \ $(DLFLAGS) spf_perl.c -o spf.so +# DMARC +dmarc.o dmarc_native.o: + @echo "$(CC) $*.c dmarc_common.c" + $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c + $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) dmarc_common.c + $(FE)mv $@ dmarc_tmp.o + $(FE)ld -r -o $@ $(LDFLAGS_PARTIAL) dmarc_tmp.o dmarc_common.o + +# dmarc_native is special in the same way as spf_perl +dmarc.so dmarc_native.so: + @echo "$(CC) -shared $*.c dmarc_common.c" + $(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) -o dmarc.so \ + $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) \ + $(CFLAGS) $(INCLUDE) $(TLS_INCLUDE) $(DLFLAGS) \ + $*.c dmarc_common.c + # Compile instructions for static perl.o for when EXIM_PERL is set # Dynamic is managed all via scripts/Configure-Makefile diff --git a/src/src/miscmods/arc.c b/src/src/miscmods/arc.c index 4767d45bf..cdd746547 100644 --- a/src/src/miscmods/arc.c +++ b/src/src/miscmods/arc.c @@ -2090,7 +2090,7 @@ return g; } -# ifdef SUPPORT_DMARC +# ifdef EXIM_HAVE_DMARC /* Module API: obtain ARC info for DMARC history. Arguments: @@ -2144,7 +2144,7 @@ static void * arc_functions[] = { [ARC_STATE_IS_PASS] = (void *) arc_is_pass, [ARC_SIGN_INIT] = (void *) arc_sign_init, [ARC_SIGN] = (void *) arc_sign, -# ifdef SUPPORT_DMARC +# ifdef EXIM_HAVE_DMARC [ARC_ARCSET_INFO] = (void *) arc_arcset_string, # endif }; diff --git a/src/src/miscmods/dkim.c b/src/src/miscmods/dkim.c index 4a2c50516..ccb649ca1 100644 --- a/src/src/miscmods/dkim.c +++ b/src/src/miscmods/dkim.c @@ -1137,7 +1137,7 @@ expand_bad: -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC /* Module API */ @@ -1331,7 +1331,7 @@ static void * dkim_functions[] = { [DKIM_TRANSPORT_INIT] = (void *) dkim_exim_sign_init, [DKIM_TRANSPORT_WRITE] = (void *) dkim_transport_write_message, -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC [DKIM_SIGS_LIST] = (void *) dkim_sigs_list, #endif #ifdef EXPERIMENTAL_ARC diff --git a/src/src/miscmods/dmarc.c b/src/src/miscmods/dmarc.c index 185ef9f4f..be5d68c3a 100644 --- a/src/src/miscmods/dmarc.c +++ b/src/src/miscmods/dmarc.c @@ -26,21 +26,13 @@ # include "dmarc.h" # include "pdkim.h" +extern void dmarc_send_forensic_report(const uschar **); +extern uschar * dmarc_dns_lookup(const uschar *); +extern void dmarc_write_history_file(const gstring *); + OPENDMARC_LIB_T dmarc_ctx; -DMARC_POLICY_T *dmarc_pctx = NULL; -OPENDMARC_STATUS_T libdm_status, action, dmarc_policy; -OPENDMARC_STATUS_T da, sa, action; -BOOL dmarc_abort = FALSE; -uschar *dmarc_pass_fail = US"skipped"; -header_line *from_header = NULL; - -static misc_module_info * dmarc_dkim_mod_info; -static misc_module_info * dmarc_spf_mod_info; -int dmarc_spf_ares_result = ARES_RESULT_UNDEFINED; -uschar *spf_sender_domain = NULL; -uschar *spf_human_readable = NULL; -u_char *header_from_sender = NULL; -int history_file_status = DMARC_HIST_OK; +DMARC_POLICY_T *dmarc_pctx; +OPENDMARC_STATUS_T libdm_status; typedef struct dmarc_exim_p { uschar *name; @@ -58,41 +50,24 @@ static dmarc_exim_p dmarc_policy_description[] = { /* $variables */ -BOOL dmarc_alignment_dkim = FALSE; /* Subtest result */ -BOOL dmarc_alignment_spf = FALSE; /* Subtest result */ -const uschar * dmarc_domain_policy = NULL; /* Declared policy of used domain */ -const uschar * dmarc_status; /* One word value */ -const uschar * dmarc_status_text = NULL; /* Human readable value */ -uschar * dmarc_used_domain; /* Domain libopendmarc chose for DMARC policy lookup */ +extern BOOL dmarc_alignment_dkim; /* Subtest result */ +extern BOOL dmarc_alignment_spf; /* Subtest result */ +extern const uschar * dmarc_domain_policy; /* Declared policy of used domain */ +extern const uschar * dmarc_status; /* One word value */ +extern const uschar * dmarc_status_text; /* Human readable value */ +extern uschar * dmarc_used_domain; /* options */ -uschar * dmarc_forensic_sender = NULL; /* Set sender address for forensic reports */ -uschar * dmarc_history_file = NULL; /* File to store dmarc results */ -uschar * dmarc_tld_file = NULL; /* Mozilla TLDs text file */ - +extern uschar * dmarc_forensic_sender; /* Set sender address for forensic reports */ +extern uschar * dmarc_history_file; /* File to store dmarc results */ +extern uschar * dmarc_tld_file; /* Mozilla TLDs text file */ -/* One-time initialisation for dmarc. Ensure the spf module is available. */ - -static BOOL -dmarc_init(void * dummy) -{ -uschar * errstr; -if (!(dmarc_spf_mod_info = misc_mod_find(US"spf", &errstr))) - { - log_write(0, LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr); - return FALSE; - } -if (!(dmarc_dkim_mod_info = misc_mod_find(US"dkim", &errstr))) - { - log_write(0, LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr); - return FALSE; - } +void +dmarc_local_init(void) +{} -return TRUE; -} - -static gstring * +gstring * dmarc_version_report(gstring * g) { return string_fmt_append(g, "Library version: dmarc: Compile: %d.%d.%d.%d\n", @@ -103,58 +78,10 @@ return string_fmt_append(g, "Library version: dmarc: Compile: %d.%d.%d.%d\n", } -/* Accept an error_block struct, initialize if empty, parse to the -end, and append the two strings passed to it. Used for adding -variable amounts of value:pair data to the forensic emails. */ - -static error_block * -add_to_eblock(error_block * eblock, const uschar * t1, const uschar * t2) +void +dmarc_local_msg_init(void) { -error_block * eb = store_malloc(sizeof(error_block)); -if (!eblock) - eblock = eb; -else - { - /* Find the end of the eblock struct and point it at eb */ - error_block *tmp = eblock; - while(tmp->next) - tmp = tmp->next; - tmp->next = eb; - } -eb->text1 = t1; -eb->text2 = t2; -eb->next = NULL; -return eblock; -} - -/* dmarc_msg_init sets up a context that can be re-used for several -messages on the same SMTP connection (that come from the -same host with the same HELO string). -However, we seem to only use it for one; we destroy some sort of context -at the tail end of dmarc_process(). */ - -static int -dmarc_msg_init() -{ -int *netmask = NULL; /* Ignored */ -uschar * s; - -/* Set some sane defaults. Also clears previous results when -multiple messages in one connection. */ - dmarc_pctx = NULL; -dmarc_status = US"none"; -dmarc_abort = FALSE; -dmarc_pass_fail = US"skipped"; -dmarc_used_domain = US""; -f.dmarc_has_been_checked = FALSE; -header_from_sender = NULL; -spf_sender_domain = NULL; -spf_human_readable = NULL; - -/* ACLs have "control=dmarc_disable_verify" */ -if (f.dmarc_disable_verify) - return OK; (void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx); dmarc_ctx.nscount = 0; @@ -165,27 +92,17 @@ if (libdm_status != DMARC_PARSE_OKAY) opendmarc_policy_status_to_str(libdm_status)); dmarc_abort = TRUE; } -GET_OPTION("dmarc_tld_file"); -if (!(s = dmarc_tld_file) || !(s = expand_string(s)) || !*s) - { - DEBUG(D_receive) debug_printf_indent("DMARC: no dmarc_tld_file\n"); - dmarc_abort = TRUE; - } else if (opendmarc_tld_read_file(CS dmarc_tld_file, NULL, NULL, NULL)) { log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list '%s': %s", dmarc_tld_file, strerror(errno)); dmarc_abort = TRUE; } -if (!sender_host_address) - { - DEBUG(D_receive) debug_printf_indent("DMARC: no sender_host_address\n"); - dmarc_abort = TRUE; - } + /* This catches locally originated email and startup errors above. */ if (!dmarc_abort) { - int is_ipv6 = string_is_ip_address(sender_host_address, netmask) == 6; + int is_ipv6 = string_is_ip_address(sender_host_address, NULL) == 6; if (!(dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6))) { log_write(0, LOG_MAIN|LOG_PANIC, @@ -193,244 +110,39 @@ if (!dmarc_abort) dmarc_abort = TRUE; } } - -return OK; -} - - -static void -dmarc_smtp_reset(void) -{ -f.dmarc_has_been_checked = f.dmarc_disable_verify = -f.dmarc_enable_forensic = FALSE; -dmarc_domain_policy = dmarc_status = dmarc_status_text = -dmarc_used_domain = NULL; -} - - -/* dmarc_store_data stores the header data so that subsequent dmarc_process can -access the data. -Called after the entire message has been received, with the From: header. */ - -static int -dmarc_store_data(header_line * hdr) -{ -/* No debug output because would change every test debug output */ -if (!f.dmarc_disable_verify) - from_header = hdr; -return OK; } static void -dmarc_send_forensic_report(u_char ** ruf) +dmarc_local_send_forensic_report(u_char ** ruf) { -uschar * recipient; -error_block * eblock = NULL; -FILE *message_file = NULL; - /* Earlier ACL does not have *required* control=dmarc_enable_forensic */ if (!f.dmarc_enable_forensic) return; -if ( dmarc_policy == DMARC_POLICY_REJECT && action == DMARC_RESULT_REJECT - || dmarc_policy == DMARC_POLICY_QUARANTINE && action == DMARC_RESULT_QUARANTINE - || dmarc_policy == DMARC_POLICY_NONE && action == DMARC_RESULT_REJECT - || dmarc_policy == DMARC_POLICY_NONE && action == DMARC_RESULT_QUARANTINE +if ( dmarc_policy == DMARC_POLICY_REJECT + && dmarc_action == DMARC_RESULT_REJECT + || dmarc_policy == DMARC_POLICY_QUARANTINE + && dmarc_action == DMARC_RESULT_QUARANTINE + || dmarc_policy == DMARC_POLICY_NONE + && dmarc_action == DMARC_RESULT_REJECT + || dmarc_policy == DMARC_POLICY_NONE + && dmarc_action == DMARC_RESULT_QUARANTINE ) if (ruf) - { - eblock = add_to_eblock(eblock, US"Sender Domain", dmarc_used_domain); - eblock = add_to_eblock(eblock, US"Sender IP Address", sender_host_address); - eblock = add_to_eblock(eblock, US"Received Date", tod_stamp(tod_full)); - eblock = add_to_eblock(eblock, US"SPF Alignment", - sa == DMARC_POLICY_SPF_ALIGNMENT_PASS ? US"yes" : US"no"); - eblock = add_to_eblock(eblock, US"DKIM Alignment", - da == DMARC_POLICY_DKIM_ALIGNMENT_PASS ? US"yes" : US"no"); - eblock = add_to_eblock(eblock, US"DMARC Results", dmarc_status_text); - - for (int c = 0; ruf[c]; c++) - { - recipient = string_copylc(ruf[c]); - if (Ustrncmp(recipient, "mailto:",7)) - continue; - /* Move to first character past the colon */ - recipient += 7; - DEBUG(D_receive) - debug_printf_indent("DMARC forensic report to %s%s\n", recipient, - (host_checking || f.running_in_test_harness) ? " (not really)" : ""); - if (host_checking || f.running_in_test_harness) - continue; - - if (!moan_send_message(recipient, ERRMESS_DMARC_FORENSIC, eblock, - header_list, message_file, NULL)) - log_write(0, LOG_MAIN|LOG_PANIC, - "failure to send DMARC forensic report to %s", recipient); - } - } -} - - -/* Look up a DNS dmarc record for the given domain. Return it or NULL */ - -static uschar * -dmarc_dns_lookup(uschar * dom) -{ -dns_answer * dnsa = store_get_dns_answer(); -dns_scan dnss; -uschar * res = NULL; - -expand_level++; - -if (dns_lookup(dnsa, string_sprintf("_dmarc.%s", dom), T_TXT, NULL) - == DNS_SUCCEED) - for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; - rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) - if (rr->type == T_TXT && rr->size > 3) - res = string_copyn_taint(US rr->data, rr->size, GET_TAINTED); - -expand_level--; -store_free_dns_answer(dnsa); -return res; + dmarc_send_forensic_report(CUSS ruf); } -static int -dmarc_write_history_file(const gstring * dkim_history_buffer) -{ -int history_file_fd = 0, tmp_ans; -u_char ** rua; /* aggregate report addressees */ -uschar * s; -gstring * g; -GET_OPTION("dmarc_history_file"); -if (!(s = dmarc_history_file) || !(s = expand_string(s)) || !*s) - { - DEBUG(D_receive) debug_printf_indent("DMARC history file not set\n"); - return DMARC_HIST_DISABLED; - } -if (!host_checking) - /* Ensure we use a modifiiable copy for the filename */ - if ((history_file_fd = - log_open_as_exim(s == dmarc_history_file ? string_copy(s) : s)) < 0) - { - log_write(0, LOG_MAIN|LOG_PANIC, - "failure to create DMARC history file: %s: %s", - s, strerror(errno)); - return DMARC_HIST_FILE_ERR; - } - -/* Generate the contents of the history file entry */ - -g = string_fmt_append(NULL, - "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n", - message_id, primary_hostname, time(NULL), sender_host_address, - header_from_sender, expand_string(US"$sender_address_domain")); - -if (dmarc_spf_ares_result != ARES_RESULT_UNDEFINED) - g = string_fmt_append(g, "spf %d\n", dmarc_spf_ares_result); - -if (dkim_history_buffer) - g = string_fmt_append(g, "%Y", dkim_history_buffer); - -g = string_fmt_append(g, "pdomain %s\npolicy %d\n", - dmarc_used_domain, dmarc_policy); - -if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1))) - for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++) - g = string_fmt_append(g, "rua %s\n", rua[tmp_ans]); -else - g = string_catn(g, US"rua -\n", 6); - -opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans); -g = string_fmt_append(g, "pct %d\n", tmp_ans); - -opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans); -g = string_fmt_append(g, "adkim %d\n", tmp_ans); - -opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans); -g = string_fmt_append(g, "aspf %d\n", tmp_ans); - -opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans); -g = string_fmt_append(g, "p %d\n", tmp_ans); - -opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans); -g = string_fmt_append(g, "sp %d\n", tmp_ans); - -g = string_fmt_append(g, "align_dkim %d\nalign_spf %d\naction %d\n", - da, sa, action); - -#if DMARC_API >= 100400 -# ifdef EXPERIMENTAL_ARC - { - const misc_module_info * mi = misc_mod_findonly(US"arc"); - const uschar * t; - gstring * g2 = NULL; - typedef const uschar * (*fn_t)(gstring **); - - if (mi && (t = (((fn_t *) mi->functions)[ARC_ARCSET_INFO]) (&g2))) - { - int i = Ustrcmp(t, "pass") == 0 ? ARES_RESULT_PASS - : Ustrcmp(t, "fail") == 0 ? ARES_RESULT_FAIL - : ARES_RESULT_UNKNOWN; - - g = string_fmt_append(g, "arc %d\n" - "arc_policy %d json[%#Y ]\n", - i, - i == ARES_RESULT_PASS ? DMARC_ARC_POLICY_RESULT_PASS - : i == ARES_RESULT_FAIL ? DMARC_ARC_POLICY_RESULT_FAIL - : DMARC_ARC_POLICY_RESULT_UNUSED, - g2 - ); - } - else - string_fmt_append(g, "arc %d\narc_policy %d json:[]\n", - ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED); - } - -# else -g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n", - ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED); -# endif -#endif - -/* Write the contents to the history file */ -DEBUG(D_receive) - { - debug_printf_indent("DMARC logging history data for opendmarc reporting%s\n", - host_checking ? " (not really)" : ""); - debug_printf_indent("DMARC history data for debugging:\n"); - expand_level++; - debug_printf_indent("%Y", g); - expand_level--; - } - -if (!host_checking) - { - ssize_t written_len = write_to_fd_buf(history_file_fd, - g->s, - gstring_length(g)); - if (written_len == 0) - { - log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s", - dmarc_history_file); - return DMARC_HIST_WRITE_ERR; - } - (void)close(history_file_fd); - } -return DMARC_HIST_OK; -} - - -/* dmarc_process adds the envelope sender address to the existing +/*API: dmarc_process adds the envelope sender address to the existing context (if any), retrieves the result, sets up expansion strings and evaluates the condition outcome. Called for the first ACL dmarc= condition. */ -static int +int dmarc_process(void) { -int sr = SPF_RESULT_INVALID, origin; /* used in SPF section */ int dmarc_spf_result; /* stores spf into dmarc conn ctx */ int tmp_ans, c; uschar * rr; @@ -444,44 +156,44 @@ if (f.dmarc_disable_verify) return OK; /* Store the header From: sender domain for this part of DMARC. -If there is no from_header struct, then it's likely this message +If there is no from_header string, then it's likely this message is locally generated and relying on fixups to add it. Just skip the entire DMARC system if we can't find a From: header....or if there was a previous error. */ -if (!from_header) +if (!dmarc_from_header) { DEBUG(D_receive) debug_printf_indent("DMARC: no From: header\n"); dmarc_abort = TRUE; } else if (!dmarc_abort) { + const uschar * end_addr, * s; uschar * errormsg; int dummy, domain; - uschar * p; - uschar saveend; f.parse_allow_group = TRUE; - p = parse_find_address_end(from_header->text, FALSE); - saveend = *p; *p = '\0'; - if ((header_from_sender = parse_extract_address(from_header->text, &errormsg, + end_addr = parse_find_address_end(dmarc_from_header, FALSE); + s = *end_addr + ? string_copyn(dmarc_from_header, end_addr - dmarc_from_header) + : dmarc_from_header; + if ((dmarc_header_from_sender = parse_extract_address(s, &errormsg, &dummy, &dummy, &domain, FALSE))) - header_from_sender += domain; - *p = saveend; + dmarc_header_from_sender += domain; /* The opendmarc library extracts the domain from the email address, but only try to store it if it's not empty. Otherwise, skip out of DMARC. */ - if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0)) + if (!dmarc_header_from_sender || !*dmarc_header_from_sender) dmarc_abort = TRUE; libdm_status = dmarc_abort ? DMARC_PARSE_OKAY - : opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender); + : opendmarc_policy_store_from_domain(dmarc_pctx, dmarc_header_from_sender); if (libdm_status != DMARC_PARSE_OKAY) { log_write(0, LOG_MAIN|LOG_PANIC, "failure to store header From: in DMARC: %s, header was '%s'", - opendmarc_policy_status_to_str(libdm_status), from_header->text); + opendmarc_policy_status_to_str(libdm_status), dmarc_from_header); dmarc_abort = TRUE; } } @@ -491,7 +203,8 @@ instead do this in the ACLs. */ if (!dmarc_abort && !sender_host_authenticated) { - uschar * dmarc_domain; + int sr = SPF_RESULT_INVALID, origin; + uschar * spf_human_readable = NULL, * spf_sender_domain; gstring * dkim_history_buffer = NULL; typedef const pdkim_signature * (*sigs_fn_t)(void); @@ -511,14 +224,11 @@ if (!dmarc_abort && !sender_host_authenticated) /* No spf data means null envelope sender so generate a domain name from the sender_helo_name */ - if (!spf_sender_domain) + if (!spf_sender_domain || !*spf_sender_domain) { spf_sender_domain = sender_helo_name; log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n", spf_sender_domain); - DEBUG(D_receive) - debug_printf_indent("DMARC using synthesized SPF sender domain = %s\n", - spf_sender_domain); } dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE; dmarc_spf_ares_result = ARES_RESULT_UNKNOWN; @@ -544,7 +254,7 @@ if (!dmarc_abort && !sender_host_authenticated) DEBUG(D_receive) debug_printf_indent("DMARC using SPF sender domain = %s\n", spf_sender_domain); } - if (strcmp( CCS spf_sender_domain, "") == 0) + if (!*spf_sender_domain) dmarc_abort = TRUE; if (!dmarc_abort) { @@ -610,24 +320,24 @@ The EDITME provides a DMARC_API variable */ our dns access path is used for debug tracing and for the testsuite diversion. */ - libdm_status = (rr = dmarc_dns_lookup(header_from_sender)) - ? opendmarc_policy_store_dmarc(dmarc_pctx, rr, header_from_sender, NULL) + libdm_status = (rr = dmarc_dns_lookup(dmarc_header_from_sender)) + ? opendmarc_policy_store_dmarc(dmarc_pctx, rr, dmarc_header_from_sender, NULL) : DMARC_DNS_ERROR_NO_RECORD; switch (libdm_status) { case DMARC_DNS_ERROR_NXDOMAIN: case DMARC_DNS_ERROR_NO_RECORD: DEBUG(D_receive) - debug_printf_indent("DMARC no record found for %s\n", header_from_sender); + debug_printf_indent("DMARC no record found for %s\n", dmarc_header_from_sender); has_dmarc_record = FALSE; break; case DMARC_PARSE_OKAY: DEBUG(D_receive) - debug_printf_indent("DMARC record found for %s\n", header_from_sender); + debug_printf_indent("DMARC record found for %s\n", dmarc_header_from_sender); break; case DMARC_PARSE_ERROR_BAD_VALUE: DEBUG(D_receive) - debug_printf_indent("DMARC record parse error for %s\n", header_from_sender); + debug_printf_indent("DMARC record parse error for %s\n", dmarc_header_from_sender); has_dmarc_record = FALSE; break; default: @@ -635,7 +345,7 @@ The EDITME provides a DMARC_API variable */ DEBUG(D_receive) debug_printf_indent("DMARC skipping (%s), unsure what to do with %s", opendmarc_policy_status_to_str(libdm_status), - from_header->text); + dmarc_from_header); has_dmarc_record = FALSE; break; } @@ -650,11 +360,10 @@ The EDITME provides a DMARC_API variable */ /* Can't use exim's string manipulation functions so allocate memory for libopendmarc using its max hostname length definition. */ - dmarc_domain = store_get(DMARC_MAXHOSTNAMELEN, GET_TAINTED); + dmarc_used_domain = store_get(DMARC_MAXHOSTNAMELEN, GET_TAINTED); libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx, - dmarc_domain, DMARC_MAXHOSTNAMELEN-1); - store_release_above(dmarc_domain + Ustrlen(dmarc_domain)+1); - dmarc_used_domain = dmarc_domain; + dmarc_used_domain, DMARC_MAXHOSTNAMELEN-1); + store_release_above(dmarc_used_domain + Ustrlen(dmarc_used_domain)+1); if (libdm_status != DMARC_PARSE_OKAY) log_write(0, LOG_MAIN|LOG_PANIC, @@ -662,61 +371,65 @@ The EDITME provides a DMARC_API variable */ opendmarc_policy_status_to_str(libdm_status)); dmarc_policy = libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx); + switch(libdm_status) { - case DMARC_POLICY_ABSENT: /* No DMARC record found */ + case DMARC_POLICY_ABSENT: /* No DMARC record found */ dmarc_status = US"norecord"; dmarc_pass_fail = US"none"; dmarc_status_text = US"No DMARC record"; - action = DMARC_RESULT_ACCEPT; + dmarc_action = DMARC_RESULT_ACCEPT; break; - case DMARC_FROM_DOMAIN_ABSENT: /* No From: domain */ + case DMARC_FROM_DOMAIN_ABSENT: /* No From: domain */ dmarc_status = US"nofrom"; dmarc_pass_fail = US"temperror"; dmarc_status_text = US"No From: domain found"; - action = DMARC_RESULT_ACCEPT; + dmarc_action = DMARC_RESULT_ACCEPT; break; - case DMARC_POLICY_NONE: /* Accept and report */ + case DMARC_POLICY_NONE: /* Accept and report */ dmarc_status = US"none"; dmarc_pass_fail = US"none"; dmarc_status_text = US"None, Accept"; - action = DMARC_RESULT_ACCEPT; + dmarc_action = DMARC_RESULT_ACCEPT; break; - case DMARC_POLICY_PASS: /* Explicit accept */ + case DMARC_POLICY_PASS: /* Explicit accept */ dmarc_status = US"accept"; dmarc_pass_fail = US"pass"; dmarc_status_text = US"Accept"; - action = DMARC_RESULT_ACCEPT; + dmarc_action = DMARC_RESULT_ACCEPT; break; - case DMARC_POLICY_REJECT: /* Explicit reject */ + case DMARC_POLICY_REJECT: /* Explicit reject */ dmarc_status = US"reject"; dmarc_pass_fail = US"fail"; dmarc_status_text = US"Reject"; - action = DMARC_RESULT_REJECT; + dmarc_action = DMARC_RESULT_REJECT; break; - case DMARC_POLICY_QUARANTINE: /* Explicit quarantine */ + case DMARC_POLICY_QUARANTINE: /* Explicit quarantine */ dmarc_status = US"quarantine"; dmarc_pass_fail = US"fail"; dmarc_status_text = US"Quarantine"; - action = DMARC_RESULT_QUARANTINE; + dmarc_action = DMARC_RESULT_QUARANTINE; break; default: dmarc_status = US"temperror"; dmarc_pass_fail = US"temperror"; dmarc_status_text = US"Internal Policy Error"; - action = DMARC_RESULT_TEMPFAIL; + dmarc_action = DMARC_RESULT_TEMPFAIL; break; } - libdm_status = opendmarc_policy_fetch_alignment(dmarc_pctx, &da, &sa); + libdm_status = opendmarc_policy_fetch_alignment(dmarc_pctx, + &dmarc_dkim_alignment, &dmarc_spf_alignment); if (libdm_status != DMARC_PARSE_OKAY) log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s", opendmarc_policy_status_to_str(libdm_status)); if (has_dmarc_record) { - dmarc_alignment_spf = sa == DMARC_POLICY_SPF_ALIGNMENT_PASS; - dmarc_alignment_dkim = da == DMARC_POLICY_DKIM_ALIGNMENT_PASS; + dmarc_alignment_spf = + dmarc_spf_alignment == DMARC_POLICY_SPF_ALIGNMENT_PASS; + dmarc_alignment_dkim = + dmarc_dkim_alignment == DMARC_POLICY_DKIM_ALIGNMENT_PASS; log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s " "spf_align=%s dkim_align=%s enforcement='%s'", @@ -724,10 +437,18 @@ The EDITME provides a DMARC_API variable */ dmarc_alignment_spf ? "yes" : "no", dmarc_alignment_dkim ? "yes" : "no", dmarc_status_text); - history_file_status = dmarc_write_history_file(dkim_history_buffer); + + dmarc_rua = USS opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1); + opendmarc_policy_fetch_pct(dmarc_pctx, &dmarc_pct); + opendmarc_policy_fetch_adkim(dmarc_pctx, &dmarc_adkim); + opendmarc_policy_fetch_aspf(dmarc_pctx, &dmarc_aspf); + opendmarc_policy_fetch_p(dmarc_pctx, &dmarc_dom_policy); + opendmarc_policy_fetch_sp(dmarc_pctx, &dmarc_subdom_policy); + dmarc_write_history_file(dkim_history_buffer); + /* Now get the forensic reporting addresses, if any */ ruf = opendmarc_policy_fetch_ruf(dmarc_pctx, NULL, 0, 1); - dmarc_send_forensic_report(ruf); + dmarc_local_send_forensic_report(ruf); } } @@ -746,7 +467,8 @@ dmarc_exim_expand_defaults(void) return f.dmarc_disable_verify ? US"off" : US"none"; } -static const uschar * +/*API*/ +const uschar * dmarc_exim_expand_query(void) { if (f.dmarc_disable_verify || !dmarc_pctx) @@ -756,75 +478,6 @@ return dmarc_status; } -static gstring * -authres_dmarc(gstring * g) -{ -if (f.dmarc_has_been_checked) - { - int start = 0; /* Compiler quietening */ - DEBUG(D_acl) start = gstring_length(g); - g = string_append(g, 2, US";\n\tdmarc=", dmarc_pass_fail); - if (header_from_sender) - g = string_append(g, 2, US" header.from=", header_from_sender); - DEBUG(D_acl) debug_printf_indent("DMARC:\tauthres '%.*s'\n", - gstring_length(g) - start - 3, g->s + start + 3); - } -else - DEBUG(D_acl) debug_printf_indent("DMARC:\tno authres\n"); -return g; -} - -/******************************************************************************/ -/* Module API */ - -static optionlist dmarc_options[] = { - { "dmarc_forensic_sender", opt_stringptr, {&dmarc_forensic_sender} }, - { "dmarc_history_file", opt_stringptr, {&dmarc_history_file} }, - { "dmarc_tld_file", opt_stringptr, {&dmarc_tld_file} }, -}; - -static void * dmarc_functions[] = { - [DMARC_PROCESS] = (void *) dmarc_process, - [DMARC_EXPAND_QUERY] = (void *) dmarc_exim_expand_query, - [DMARC_STORE_DATA] = (void *) dmarc_store_data, -}; - -/* dmarc_forensic_sender is provided for visibility of the the option setting -by moan_send_message. We do not document it as a config-visible $variable. -We could provide it via a function but there's little advantage. */ - -static var_entry dmarc_variables[] = { - { "dmarc_alignment_dkim", vtype_bool, &dmarc_alignment_dkim }, - { "dmarc_alignment_spf", vtype_bool, &dmarc_alignment_spf }, - { "dmarc_domain_policy", vtype_stringptr, &dmarc_domain_policy }, - { "dmarc_forensic_sender", vtype_stringptr, &dmarc_forensic_sender}, - { "dmarc_status", vtype_stringptr, &dmarc_status }, - { "dmarc_status_text", vtype_stringptr, &dmarc_status_text }, - { "dmarc_used_domain", vtype_stringptr, &dmarc_used_domain }, -}; - -misc_module_info dmarc_module_info = -{ - .name = US"dmarc", -# ifdef DYNLOOKUP - .dyn_magic = MISC_MODULE_MAGIC, -# endif - .init = dmarc_init, - .lib_vers_report = dmarc_version_report, - .smtp_reset = dmarc_smtp_reset, - .msg_init = dmarc_msg_init, - .authres = authres_dmarc, - - .options = dmarc_options, - .options_count = nelem(dmarc_options), - - .functions = dmarc_functions, - .functions_count = nelem(dmarc_functions), - - .variables = dmarc_variables, - .variables_count = nelem(dmarc_variables), -}; - # endif /* SUPPORT_SPF */ #endif /* SUPPORT_DMARC */ /* vi: aw ai sw=2 diff --git a/src/src/miscmods/dmarc.h b/src/src/miscmods/dmarc.h index c1cafd0d1..6f8c456dd 100644 --- a/src/src/miscmods/dmarc.h +++ b/src/src/miscmods/dmarc.h @@ -2,8 +2,8 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Experimental DMARC support. - Copyright (c) The Exim Maintainers 2021 - 2023 +/* DMARC support. + Copyright (c) The Exim Maintainers 2021 - 2025 Copyright (c) Todd Lyons 2012 - 2014 License: GPL */ /* SPDX-License-Identifier: GPL-2.0-or-later */ @@ -11,14 +11,54 @@ /* Portions Copyright (c) 2012, 2013, The Trusted Domain Project; All rights reserved, licensed for use per LICENSE.opendmarc. */ -#ifdef SUPPORT_DMARC +#ifdef EXPERIMENTAL_DMARC_NATIVE +# define EXIM_HAVE_DMARC EXPERIMENTAL_DMARC_NATIVE +# define DMARC_SUPPORTS_ARC + +/* from opendmarc/dmarc.h */ +# define DMARC_MAXHOSTNAMELEN 256 + +# define DMARC_POLICY_ABSENT 14 +# define DMARC_POLICY_PASS 15 +# define DMARC_POLICY_REJECT 16 +# define DMARC_POLICY_QUARANTINE 17 +# define DMARC_POLICY_NONE 18 +# define DMARC_USED_POLICY_IS_P 19 +# define DMARC_USED_POLICY_IS_SP 20 + +# define DMARC_POLICY_SPF_ORIGIN_MAILFROM 1 +# define DMARC_POLICY_SPF_ORIGIN_HELO 2 + +# define DMARC_POLICY_SPF_OUTCOME_NONE 0 +# define DMARC_POLICY_SPF_OUTCOME_PASS 1 +# define DMARC_POLICY_SPF_OUTCOME_FAIL 2 +# define DMARC_POLICY_SPF_OUTCOME_TMPFAIL 3 +# define DMARC_POLICY_SPF_ALIGNMENT_PASS 4 +# define DMARC_POLICY_SPF_ALIGNMENT_FAIL 5 + +# define DMARC_POLICY_DKIM_OUTCOME_NONE 0 +# define DMARC_POLICY_DKIM_OUTCOME_PASS 1 +# define DMARC_POLICY_DKIM_OUTCOME_FAIL 2 +# define DMARC_POLICY_DKIM_OUTCOME_TMPFAIL 3 +# define DMARC_POLICY_DKIM_ALIGNMENT_PASS 4 +# define DMARC_POLICY_DKIM_ALIGNMENT_FAIL 5 + + + +#elif defined(SUPPORT_DMARC) +# define EXIM_HAVE_DMARC SUPPORT_DMARC +# if DMARC_API >= 100400 +# define DMARC_SUPPORTS_ARC +# endif # include # ifdef SUPPORT_SPF # include # endif /* SUPPORT_SPF */ -#define DMARC_VERIFY_STATUS 1 +#endif /* SUPPORT_DMARC */ + + #define DMARC_HIST_OK 1 #define DMARC_HIST_DISABLED 2 @@ -50,8 +90,55 @@ #define ARES_RESULT_UNKNOWN 11 #define ARES_RESULT_DISCARD 12 +# define DMARC_RECORD_A_UNSPECIFIED ('\0') /* adkim and aspf */ +# define DMARC_RECORD_A_STRICT ('s') /* adkim and aspf */ +# define DMARC_RECORD_A_RELAXED ('r') /* adkim and aspf */ +# define DMARC_RECORD_P_UNSPECIFIED ('\0') /* p and sp */ +# define DMARC_RECORD_P_NONE ('n') /* p and sp */ +# define DMARC_RECORD_P_QUARANTINE ('q') /* p and sp */ +# define DMARC_RECORD_P_REJECT ('r') /* p and sp */ + +#ifndef DMARC_POLICY_SPF_ALIGNMENT_PASS +/* From opendmarc/dmarc.h */ +# define DMARC_POLICY_SPF_ALIGNMENT_PASS 4 +# define DMARC_POLICY_SPF_ALIGNMENT_FAIL 5 +#endif + +#ifndef DMARC_POLICY_DKIM_ALIGNMENT_PASS +/* From opendmarc/dmarc.h */ +# define DMARC_POLICY_DKIM_ALIGNMENT_PASS 4 +# define DMARC_POLICY_DKIM_ALIGNMENT_FAIL 5 +#endif + #define DMARC_ARC_POLICY_RESULT_PASS 0 #define DMARC_ARC_POLICY_RESULT_UNUSED 1 #define DMARC_ARC_POLICY_RESULT_FAIL 2 -#endif /* SUPPORT_DMARC */ + + +/* These live in dmarc_common.c */ +extern BOOL dmarc_abort; +extern uschar * dmarc_header_from_sender; +extern uschar * dmarc_pass_fail; +extern const uschar * dmarc_from_header; +extern const misc_module_info * dmarc_dkim_mod_info; +extern const misc_module_info * dmarc_spf_mod_info; +extern int dmarc_spf_ares_result; +extern uschar ** dmarc_rua; /* aggregate report addressees */ +extern int dmarc_pct; +extern int dmarc_adkim; +extern int dmarc_aspf; +extern int dmarc_policy; +extern int dmarc_dom_policy; +extern int dmarc_subdom_policy; +extern int dmarc_spf_alignment; +extern int dmarc_dkim_alignment; +extern int dmarc_action; +extern uschar * dmarc_used_domain; +extern const uschar * dmarc_domain_policy; +extern BOOL dmarc_alignment_spf; +extern BOOL dmarc_alignment_dkim; + +extern const uschar * dmarc_status; +extern const uschar * dmarc_status_text; + diff --git a/src/src/miscmods/dmarc_api.h b/src/src/miscmods/dmarc_api.h index 2f10463fc..54b1630a6 100644 --- a/src/src/miscmods/dmarc_api.h +++ b/src/src/miscmods/dmarc_api.h @@ -13,4 +13,4 @@ #define DMARC_PROCESS 0 #define DMARC_EXPAND_QUERY 1 -#define DMARC_STORE_DATA 2 +#define DMARC_STORE_FROMHDR 2 diff --git a/src/src/miscmods/dmarc_common.c b/src/src/miscmods/dmarc_common.c new file mode 100644 index 000000000..168e7da8e --- /dev/null +++ b/src/src/miscmods/dmarc_common.c @@ -0,0 +1,546 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* DMARC support. + Copyright (c) The Exim Maintainers 2025 + License: GPL */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "../exim.h" + +#ifdef EXIM_HAVE_DMARC + +// # include "dmarc.h" +// # include "pdkim.h" + +extern BOOL dmarc_local_init(void); +extern void dmarc_local_msg_init(void); +extern gstring * dmarc_version_report(gstring *); +extern int dmarc_process(void); +extern const uschar * dmarc_exim_expand_query(void); + + +/* Other modules needed for services */ +const misc_module_info * dmarc_spf_mod_info; +const misc_module_info * dmarc_dkim_mod_info; +const misc_module_info * dmarc_arc_mod_info; + +/* Working data */ +BOOL dmarc_abort; +uschar * dmarc_pass_fail; /* for authres */ +const uschar * dmarc_from_header; +uschar * dmarc_header_from_sender; + +/* results */ +int dmarc_spf_ares_result; +uschar ** dmarc_rua; /* aggregate report addressees */ +int dmarc_pct; /* percentage */ +int dmarc_adkim; /* dkim policy */ +int dmarc_aspf; /* spf policy */ +int dmarc_policy; /* policy to enforce */ +int dmarc_dom_policy; /* (the p tag, as numeric) */ +int dmarc_subdom_policy; /* (the sp tag, as numeric) */ +int dmarc_spf_alignment; +int dmarc_dkim_alignment; +int dmarc_action; + + +/* $variables */ +BOOL dmarc_alignment_dkim = FALSE; /* Subtest result */ +BOOL dmarc_alignment_spf = FALSE; /* Subtest result */ +const uschar * dmarc_domain_policy = NULL; /* Declared policy of used domain */ +const uschar * dmarc_status; /* One word value */ +const uschar * dmarc_status_text = NULL; /* Human readable value */ +uschar * dmarc_used_domain; /* Domain libopendmarc chose for DMARC policy lookup */ + +/* options */ +uschar * dmarc_forensic_sender = NULL; /* Set sender address for forensic reports */ +uschar * dmarc_history_file = NULL; /* File to store dmarc results */ +uschar * dmarc_tld_file = NULL; /* Mozilla TLDs text file */ + + +/*API: +One-time initialisation for dmarc. Ensure the spf module is available. +*/ + +static BOOL +dmarc_init(void * dummy) +{ +uschar * errstr; +if (!(dmarc_spf_mod_info = misc_mod_find(US"spf", &errstr))) + { + log_write(0, LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr); + return FALSE; + } + +if (!(dmarc_dkim_mod_info = misc_mod_find(US"dkim", &errstr))) + { + log_write(0, LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr); + return FALSE; + } + +dmarc_arc_mod_info = misc_mod_findonly(US"arc"); +return dmarc_local_init(); +} + + + +/*API: dmarc_msg_init could set up a context that can be re-used for several +messages on the same SMTP connection +(that come from the same host with the same HELO string). +However, we seem to only use it for one; we destroy some sort of context +at the tail end of dmarc_process(). */ + +static int +dmarc_msg_init(void) +{ +/* Set some sane defaults. Also clears previous results when +multiple messages in one connection. */ + +f.dmarc_has_been_checked = FALSE; +dmarc_from_header = NULL; +dmarc_header_from_sender = NULL; +dmarc_spf_ares_result = ARES_RESULT_UNDEFINED; +dmarc_status = US"none"; +dmarc_abort = FALSE; +dmarc_pass_fail = US"skipped"; +dmarc_used_domain = US""; + +/* ACLs have "control=dmarc_disable_verify" */ +if (f.dmarc_disable_verify) + return OK; + +GET_OPTION("dmarc_tld_file"); +if ( !dmarc_tld_file + || !(dmarc_tld_file = expand_string(dmarc_tld_file)) + || !*dmarc_tld_file) + { + DEBUG(D_receive) debug_printf_indent("DMARC: no dmarc_tld_file\n"); + dmarc_abort = TRUE; + } +else if (!sender_host_address) + { + DEBUG(D_receive) debug_printf_indent("DMARC: no sender_host_address\n"); + dmarc_abort = TRUE; + } +else + dmarc_local_msg_init(); + +return OK; +} + + +/*API*/ + +static void +dmarc_smtp_reset(void) +{ +f.dmarc_has_been_checked = f.dmarc_disable_verify = + f.dmarc_enable_forensic = FALSE; +dmarc_domain_policy = dmarc_status = dmarc_status_text = + dmarc_used_domain = NULL; +} + + +/* API: dmarc_store_data stores the header data so that subsequent dmarc_process +can access the data. Cleared by msg_init. +Called after the entire message has been received, with the From: header. */ + +static void +dmarc_store_fromhdr(const uschar * hdr) +{ +/* No debug output because would change every test debug output */ +dmarc_from_header = hdr; +} + + +/* Accept an error_block struct, initialize if empty, parse to the +end, and append the two strings passed to it. Used for adding +variable amounts of value:pair data to the forensic emails. */ + +static error_block * +add_to_eblock(error_block * eblock, const uschar * t1, const uschar * t2) +{ +error_block * eb = store_malloc(sizeof(error_block)); +if (!eblock) + eblock = eb; +else + { + /* Find the end of the eblock struct and point it at eb */ + error_block * tmp = eblock; + while(tmp->next) + tmp = tmp->next; + tmp->next = eb; + } +eb->text1 = t1; +eb->text2 = t2; +eb->next = NULL; +return eblock; +} + +void +dmarc_send_forensic_report(const uschar ** ruf) +{ +error_block * eblock; + +/* Earlier ACL does not have *required* control=dmarc_enable_forensic */ +if (!f.dmarc_enable_forensic || !ruf) + return; + +eblock = add_to_eblock(NULL, + string_sprintf("Subject: DMARC Forensic Report for %s from IP %s\n\n", + dmarc_used_domain, sender_host_address), NULL); +eblock = add_to_eblock(eblock, + US"A message claiming to be from you has failed the published DMARC\n" + "policy for your domain.\n\n", NULL); + +eblock = add_to_eblock(eblock, US"Sender Domain", dmarc_header_from_sender); +eblock = add_to_eblock(eblock, US"Sender IP Address", sender_host_address); +eblock = add_to_eblock(eblock, US"Received Date", tod_stamp(tod_full)); +eblock = add_to_eblock(eblock, US"SPF Alignment", + dmarc_alignment_spf ? US"yes" : US"no"); +eblock = add_to_eblock(eblock, US"DKIM Alignment", + dmarc_alignment_dkim ? US"yes" : US"no"); +eblock = add_to_eblock(eblock, US"DMARC Results", dmarc_status_text); + +for (int c = 0; ruf[c]; c++) + { + uschar * recipient = string_copylc(ruf[c]); + if (Ustrncmp(recipient, "mailto:",7)) + continue; + /* Move to first character past the colon */ + recipient += 7; + DEBUG(D_receive) + debug_printf_indent("DMARC forensic report to %s%s\n", recipient, + host_checking || f.running_in_test_harness ? " (not really)" : ""); + if (host_checking || f.running_in_test_harness) + continue; + + if (!moan_send_message(recipient, ERRMESS_DMARC_FORENSIC, eblock, + header_list, NULL, NULL)) + log_write(0, LOG_MAIN|LOG_PANIC, + "failure to send DMARC forensic report to %s", recipient); + } +} + + +/* Look up a DNS dmarc record for the given domain. Return it or NULL */ + +const uschar * +dmarc_dns_lookup(const uschar * dom) +{ +dns_answer * dnsa = store_get_dns_answer(); +dns_scan dnss; +const uschar * res = NULL; + +expand_level++; + +/* RFC 7489 6.6.1 :- policy record is at a "_dmarc" sub of the domain */ + +if (dns_lookup(dnsa, string_sprintf("_dmarc.%s", dom), T_TXT, NULL) + == DNS_SUCCEED) + { +/*XXX we lose track of temporary DNS failures */ + + for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) + { + const uschar * rdata = rr->data; + int len = rdata[0]; + + if (len > 511) len = 127; + rdata++; + +/* RFC 7489 6.6.1 :- policy record is a TXT record */ +/* RFC 7489 6.6.3 step 2: ignore records not starting "v=DMARC1;" + (also noted in 6.3 for the v tag) */ + + if ( rr->type == T_TXT && len > 9 + && Ustrncmp(rdata, "v=DMARC1;", 9) == 0) + if (!res) + res = string_copyn_taint(rdata, len, GET_TAINTED); + else +/* RFC 7489 6.6.3 step 5: multiple records are treated as no record */ + { + DEBUG(D_receive) debug_printf_indent("DMARC: multiple rr\n"); + res = NULL; + break; + } + } + } +else + DEBUG(D_receive) debug_printf_indent("DMARC: no ret\n"); + +expand_level--; +store_free_dns_answer(dnsa); +DEBUG(D_receive) debug_printf_indent("DMARC: rr %q\n", res); +return res; +} + + + +const uschar * +dmarc_lookup_regdom(const uschar * dom) +{ +int expand_setup = -1, partial, affixlen, starflags; +const uschar * affix, * opts, * res; +const lookup_info * li; +void * handle; +static const uschar * cached_key = NULL, * cached_res = NULL; + +DEBUG(D_receive) debug_printf_indent("DMARC: lookup regdom for %q\n", dom); + +if (cached_key && Ustrcmp(dom, cached_key) == 0) + { + res = cached_res; + DEBUG(D_receive) debug_printf_indent(" DMARC: cached value %q\n", res); + return res; + } + +expand_level++; +res = NULL; +if (!(li = search_findtype_partial(US"regdom", &partial, &affix, &affixlen, + &starflags, &opts))) + { + DEBUG(D_receive) debug_printf_indent("DMARC: missing regdom lookup\n"); + goto out; + } + +if (!(handle = search_open(dmarc_tld_file, li, 0, NULL, NULL))) + goto out; + +/*XXX should we handle a defer return? cf. f.search_find_defer */ + +res = search_find(handle, dmarc_tld_file, dom, partial, affix, + affixlen, starflags, &expand_setup, opts); + +out: + cached_key = dom; cached_res = res; + expand_level--; + return res; +} + +const uschar * +dmarc_get_dns_policy_record(const uschar ** used_dom_p) +{ +const uschar * s; + +DEBUG(D_receive) debug_printf_indent("DMARC: lookup policy record for %s\n", + dmarc_header_from_sender); + +/* RFC 7489 6.6.3 step 1: DNS domain matching the 5322.From */ + +if ((s= dmarc_dns_lookup(*used_dom_p = dmarc_header_from_sender))) + return s; + +/* RFC 7489 6.6.3 step 3: if no record, use the Organizational Domain */ + +if (!(s = dmarc_lookup_regdom(dmarc_header_from_sender))) + return NULL; + +/* RFC 7489 6.6.3 step 3: if the Organizational Domain differs */ + +if (Ustrcmp(s, dmarc_header_from_sender) == 0) + return NULL; + +return dmarc_dns_lookup(*used_dom_p = s); +} + + +void +dmarc_write_history_file(const gstring * dkim_history_buffer) +{ +int history_file_fd = -1; +uschar * s; +gstring * g; + +GET_OPTION("dmarc_history_file"); +if (!(s = dmarc_history_file) || !(s = expand_string(s)) || !*s) + { + DEBUG(D_receive) debug_printf_indent("DMARC history file not set\n"); + return; + } +if (!host_checking) /* -bh mode: nothing written except debug */ + if ((history_file_fd = log_open_as_exim(s)) < 0) + { + log_write(0, LOG_MAIN|LOG_PANIC, + "failure to create DMARC history file: %s: %s", + s, strerror(errno)); + return; + } + +/* Generate the contents of the history file entry */ + +g = string_fmt_append(NULL, + "job %s\n" + "reporter %s\n" + "received %ld\n" + "ipaddr %s\n" + "from %s\n" + "mfrom %s\n", + message_id, primary_hostname, time(NULL), sender_host_address, + dmarc_header_from_sender, expand_string(US"$sender_address_domain")); + +if (dmarc_spf_ares_result != ARES_RESULT_UNDEFINED) + g = string_fmt_append(g, "spf %d\n", dmarc_spf_ares_result); + +if (dkim_history_buffer) + g = string_fmt_append(g, "%Y", dkim_history_buffer); + +g = string_fmt_append(g, "pdomain %s\n" + "policy %d\n", + dmarc_used_domain, dmarc_policy); + +if (dmarc_rua) + for (uschar ** ss = dmarc_rua; *ss; ss++) + g = string_fmt_append(g, "rua %s\n", *ss); +else + g = string_catn(g, US"rua -\n", 6); + +/* policy tag values */ +g = string_fmt_append(g, "pct %d\n" + "adkim %d\n" + "aspf %d\n" + "p %d\n" + "sp %d\n", + dmarc_pct, dmarc_adkim, dmarc_aspf, dmarc_dom_policy, dmarc_subdom_policy); + +g = string_fmt_append(g, "align_dkim %d\n" + "align_spf %d\n" + "action %d\n", + dmarc_dkim_alignment, dmarc_spf_alignment, dmarc_action); + +#ifdef DMARC_SUPPORTS_ARC + { +# ifdef EXPERIMENTAL_ARC + const uschar * s; + gstring * g2 = NULL; + typedef const uschar * (*fn_t)(gstring **); + + if ( dmarc_arc_mod_info + && (s = (((fn_t *) dmarc_arc_mod_info->functions)[ARC_ARCSET_INFO]) (&g2))) + { + int i = Ustrcmp(s, "pass") == 0 ? ARES_RESULT_PASS + : Ustrcmp(s, "fail") == 0 ? ARES_RESULT_FAIL + : ARES_RESULT_UNKNOWN; + + g = string_fmt_append(g, "arc %d\n" + "arc_policy %d json[%#Y ]\n", + i, + i == ARES_RESULT_PASS ? DMARC_ARC_POLICY_RESULT_PASS + : i == ARES_RESULT_FAIL ? DMARC_ARC_POLICY_RESULT_FAIL + : DMARC_ARC_POLICY_RESULT_UNUSED, + g2 + ); + } + else + +# endif + g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n", + ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED); + } +#endif + +/* Write the contents to the history file */ +DEBUG(D_receive) + { + debug_printf_indent("DMARC history data for debugging:\n"); + expand_level++; + debug_printf_indent("%Y", g); + expand_level--; + debug_printf_indent("DMARC logging history data for opendmarc reporting%s\n", + host_checking ? " (not really)" : ""); + } + +if (!host_checking) + { + ssize_t written_len = write_to_fd_buf(history_file_fd, + string_from_gstring(g), gstring_length(g)); + if (written_len == 0) + { + log_write(0, LOG_MAIN|LOG_PANIC, + "failure to write to DMARC history file: %s", dmarc_history_file); + (void)close(history_file_fd); + return; + } + (void)close(history_file_fd); + } +return; +} + + + +/*API*/ +static gstring * +authres_dmarc(gstring * g) +{ +if (f.dmarc_has_been_checked) + { + int start = 0; /* Compiler quietening */ + DEBUG(D_acl) start = gstring_length(g); + g = string_append(g, 2, US";\n\tdmarc=", dmarc_pass_fail); + if (dmarc_header_from_sender) + g = string_append(g, 2, US" header.from=", dmarc_header_from_sender); + DEBUG(D_acl) debug_printf_indent("DMARC:\tauthres '%.*s'\n", + gstring_length(g) - start - 3, g->s + start + 3); + } +else + DEBUG(D_acl) debug_printf_indent("DMARC:\tno authres\n"); +return g; +} + +/******************************************************************************/ +/* Module API */ + +static optionlist dmarc_options[] = { + { "dmarc_forensic_sender", opt_stringptr, {&dmarc_forensic_sender} }, + { "dmarc_history_file", opt_stringptr, {&dmarc_history_file} }, + { "dmarc_tld_file", opt_stringptr, {&dmarc_tld_file} }, +}; + +static void * dmarc_functions[] = { + [DMARC_PROCESS] = (void *) dmarc_process, + [DMARC_EXPAND_QUERY] = (void *) dmarc_exim_expand_query, + [DMARC_STORE_FROMHDR] = (void *) dmarc_store_fromhdr, +}; + +/* dmarc_forensic_sender is provided for visibility of the the option setting +by moan_send_message. We do not document it as a config-visible $variable. +We could provide it via a function but there's little advantage. */ + +static var_entry dmarc_variables[] = { + { "dmarc_alignment_dkim", vtype_bool, &dmarc_alignment_dkim }, + { "dmarc_alignment_spf", vtype_bool, &dmarc_alignment_spf }, + { "dmarc_domain_policy", vtype_stringptr, &dmarc_domain_policy }, + { "dmarc_forensic_sender", vtype_stringptr, &dmarc_forensic_sender}, + { "dmarc_status", vtype_stringptr, &dmarc_status }, + { "dmarc_status_text", vtype_stringptr, &dmarc_status_text }, + { "dmarc_used_domain", vtype_stringptr, &dmarc_used_domain }, +}; + +misc_module_info dmarc_module_info = +{ + .name = US"dmarc", +# ifdef DYNLOOKUP + .dyn_magic = MISC_MODULE_MAGIC, +# endif + .init = dmarc_init, + .lib_vers_report = dmarc_version_report, + .smtp_reset = dmarc_smtp_reset, + .msg_init = dmarc_msg_init, + .authres = authres_dmarc, + + .options = dmarc_options, + .options_count = nelem(dmarc_options), + + .functions = dmarc_functions, + .functions_count = nelem(dmarc_functions), + + .variables = dmarc_variables, + .variables_count = nelem(dmarc_variables), +}; + +#endif /*EXIM_HAVE_DMARC*/ +/* vi: aw ai sw=2 + */ diff --git a/src/src/miscmods/dmarc_native.c b/src/src/miscmods/dmarc_native.c new file mode 100644 index 000000000..ce4aaee4f --- /dev/null +++ b/src/src/miscmods/dmarc_native.c @@ -0,0 +1,707 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* DMARC support. + Copyright (c) The Exim Maintainers 2025 + License: GPL */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "../exim.h" + +#ifdef SUPPORT_DMARC +# error Build cannot support both libopendmarc and native DMARC modules +#endif + +#ifdef EXPERIMENTAL_DMARC_NATIVE +# ifndef EXIM_HAVE_SPF +# error SPF must also be enabled for DMARC +# elif defined DISABLE_DKIM +# error DKIM must also be enabled for DMARC +# elif !defined LOOKUP_PSL +# error PSL lookups must be enabled for DMARC +# else + +# include "../functions.h" +# include "pdkim.h" + +extern void dmarc_send_forensic_report(const uschar **); +extern const uschar * dmarc_get_dns_policy_record(uschar **); +extern void dmarc_write_history_file(const gstring *); +extern const uschar * dmarc_lookup_regdom(const uschar *); + + +static const pcre2_code * dmarc_regex_uri = NULL; +static const pcre2_code * dmarc_regex_pct = NULL; +static const pcre2_code * dmarc_regex_ri = NULL; +static const pcre2_code * dmarc_regex_fo = NULL; + +BOOL +dmarc_local_init(void) +{ +if (!dmarc_regex_uri) + dmarc_regex_uri = regex_must_compile(US "^mailto:[^@]+@[^ !]+(?:[ !]|$)", + MCS_CACHEABLE, FALSE); +if (!dmarc_regex_pct) + dmarc_regex_uri = regex_must_compile(US "^\\d{1,3}$", MCS_CACHEABLE, FALSE); +if (!dmarc_regex_ri) + dmarc_regex_ri = regex_must_compile(US "^\\d{1,10}$", MCS_CACHEABLE, FALSE); +if (!dmarc_regex_fo) + dmarc_regex_fo = regex_must_compile(US "^[01ds]$", MCS_CACHEABLE, FALSE); +} + + +#include "../version.h" + +gstring * +dmarc_version_report(gstring * g) +{ +/* +return string_fmt_append(g, "Library version: dmarc: Compile: %d.%d.%d.%d\n", + (OPENDMARC_LIB_VERSION & 0xff000000) >> 24, + (OPENDMARC_LIB_VERSION & 0x00ff0000) >> 16, + (OPENDMARC_LIB_VERSION & 0x0000ff00) >> 8, + (OPENDMARC_LIB_VERSION & 0x000000ff)); +*/ +return string_fmt_append(g, "Library version: dmarc: Exim %s builtin\n", + EXIM_VERSION_STR); +} + + +int +dmarc_local_msg_init() +{ +return OK; +} + + +/* Convert to comma-sep list to NULL-terminated array of pointers */ +static uschar ** +dmarc_clist_to_array(const uschar * list) +{ +int cnt = 0, sep = ','; +const uschar * s = list; +uschar * buf = store_get(2, list), ** rarray; + +while (string_nextinlist(&s, &sep, buf, 1)) cnt++; /* count the elements */ +rarray = store_get((cnt+1) * sizeof(*rarray), list); +for (cnt = 0; rarray[cnt] = string_nextinlist(&list, &sep, NULL, 0); ) cnt++; +return rarray; +} + + +static void +dmarc_maybe_send_forensic(const uschar * ruf) +{ +/* Earlier ACL does not have *required* control=dmarc_enable_forensic */ +if (!f.dmarc_enable_forensic) + return; + +/* RFC 7489 6.3 - ruf is optional */ +if (!ruf) + return; + +if ( dmarc_policy == DMARC_POLICY_REJECT + && dmarc_action == DMARC_RESULT_REJECT + || dmarc_policy == DMARC_POLICY_QUARANTINE + && dmarc_action == DMARC_RESULT_QUARANTINE + || dmarc_policy == DMARC_POLICY_NONE + && dmarc_action == DMARC_RESULT_REJECT + || dmarc_policy == DMARC_POLICY_NONE + && dmarc_action == DMARC_RESULT_QUARANTINE + ) + { +/* RFC 7489 6.3 - ruf is a comma-sep list */ + /* Convert to NULL-terminated array of pointers */ + const uschar ** rarray = CUSS dmarc_clist_to_array(ruf); + dmarc_send_forensic_report(rarray); + } +} + + +/******************************************************************************/ +/* Policy record parsing */ + +/* Value verification routines: return boolean "good" */ + +static BOOL dmarc_vfy_vmode(const uschar * val) +{ return (*val == 's' || *val == 'r') && val[1] == '\0'; } +static BOOL dmarc_vfy_policy(const uschar * val) +{ return Ustrcmp(val, "none") == 0 + || Ustrcmp(val, "quarantine") == 0 + || Ustrcmp(val, "reject") == 0; } +static BOOL dmarc_vfy_fbl(const uschar * val) +{ +/* For now, permit a list starting with (a plausible) mailto URI */ +return regex_match(dmarc_regex_uri, val, -1, NULL); +} + +static BOOL dmarc_tag_vfy_adkim(const uschar * val) +{ return dmarc_vfy_vmode(val); } +static BOOL dmarc_tag_vfy_aspf(const uschar * val) +{ return dmarc_vfy_vmode(val); } +static BOOL dmarc_tag_vfy_fo(const uschar * val) +{ return regex_match(dmarc_regex_fo, val, -1, NULL); } +static BOOL dmarc_tag_vfy_p(const uschar * val) +{ return dmarc_vfy_policy(val); } +static BOOL dmarc_tag_vfy_pct(const uschar * val) +{ return regex_match(dmarc_regex_pct, val, -1, NULL); } +static BOOL dmarc_tag_vfy_rf(const uschar * val) +{ return Ustrcmp(val, "afrf") == 0; } +static BOOL dmarc_tag_vfy_ri(const uschar * val) +{ return regex_match(dmarc_regex_ri, val, -1, NULL); } +static BOOL dmarc_tag_vfy_rua(const uschar * val) +{ return dmarc_vfy_fbl(val); } +static BOOL dmarc_tag_vfy_ruf(const uschar * val) +{ return dmarc_vfy_fbl(val); } +static BOOL dmarc_tag_vfy_sp(const uschar * val) +{ return dmarc_vfy_policy(val); } +static BOOL dmarc_tag_vfy_v(const uschar * val) +{ return Ustrcmp(val, "DMARC1") == 0; } + +typedef struct dmarc_policy_record { + const uschar * adkim; + const uschar * aspf; + const uschar * fo; + const uschar * p; + const uschar * pct; + const uschar * rf; + const uschar * ri; + const uschar * rua; + const uschar * ruf; + const uschar * sp; + const uschar * v; +} dmarc_policy_record; + +typedef struct tag { + const uschar * name; + unsigned offset; + BOOL (*verify)(const uschar *); +} tag; +#define TAG(field) {.name = US mac_expanded_string(field), \ + .offset = offsetof(dmarc_policy_record, field), \ + .verify = dmarc_tag_vfy_ ## field } +tag policy_tags[] = { + TAG(adkim), + TAG(aspf), + TAG(fo), + TAG(p), + TAG(pct), + TAG(rf), + TAG(ri), + TAG(rua), + TAG(ruf), + TAG(sp), + TAG(v), +}; +#undef TAG + +/* Handle one potential tag +Return: boolean success; else parsing error + +RFC 7489 6.3 :- unknown tags are ignored +*/ +static int +parse_tag(const uschar * tagrecord, dmarc_policy_record * prp) +{ +const uschar * e = Ustrchr(tagrecord, '='), * s; + +/* RFC 6736 3.2 tagspec must have = */ +if (!*e) + return FALSE; + +/* RFC 6736 3.2 ignore whitespace between tag name and = */ +for (s = e; s > tagrecord && isspace(s[-1]); ) s--; + +/* RFC 6736 3.2 tag name at least 1 char */ +if (s == tagrecord) + return FALSE; + +/* search for tag name in our table of known ones */ +for (tag * ptp = policy_tags; ptp < policy_tags + nelem(policy_tags); ptp++) + { + if ( Ustrncmp(ptp->name, tagrecord, s - tagrecord) == 0 + && Ustrlen(ptp->name) == s - tagrecord) + + { /* match; copy tag value to policy record struct */ + const uschar ** vp = CUSS (US prp + ptp->offset); + +// debug_printf_indent("matched %q, off %u\n", tagrecord, ptp->offset); + +/* RFC 6736 3.2 ignore whitespace between = and value */ + s = e + 1; + Uskip_whitespace(&s); + + if (!ptp->verify(s)) DEBUG(D_receive) + debug_printf_indent("DMARC: bad value for tag %q: %q\n", ptp->name, s); + *vp = string_copy(s); + break; + } + } +return TRUE; +} + +static BOOL +dmarc_local_parse_policy(const uschar * rr, dmarc_policy_record * prp) +{ +/* RFC 6376 3.2 :- a taglist is a ;-sep list of tagspec */ +int sep = ';'; + +/* RFC 6736 3.2 :- ignore whitespace preceding tag-name and after value */ + +for (uschar * tagspec; tagspec = string_nextinlist(&rr, &sep, NULL, 0); ) + if (!parse_tag(tagspec, prp)) + return FALSE; + +return TRUE; +} + +/******************************************************************************/ + +static BOOL +identifier_aligned(const uschar * a, const uschar * b, const uschar * mode) +{ +BOOL res; + +/* RFC 7489 3.3.1 - In strict mode, only an exact match */ + +if (*mode == 's') + res = Ustrcmp(a, b) == 0; + +/* RFC 7489 3.3.1 - In relaxed mode, the Organizational Domains of both */ +else + { + /* - if there is an exact match, the ODs will also match - + so check that first to save on regdom lookups. */ + + if (Ustrcmp(a, b) == 0) + res = TRUE; + else + { + a = dmarc_lookup_regdom(a); + b = dmarc_lookup_regdom(b); + res = a && b && Ustrcmp(a, b) == 0; + } + } +DEBUG(D_receive) + if (res) debug_printf_indent("DMARC aligned(%s) %s %s\n", mode, a, b); +return res; +} + + +/* API: dmarc_process adds the envelope sender address to the existing +context (if any), retrieves the result, sets up expansion +strings and evaluates the condition outcome. +Called for the first ACL dmarc= condition. */ + +int +dmarc_process(void) +{ +const uschar * rr; +BOOL has_dmarc_record = TRUE; +u_char ** ruf; /* forensic report addressees, if called for */ + +dmarc_alignment_spf = dmarc_alignment_dkim = FALSE; +dmarc_dkim_alignment = DMARC_POLICY_DKIM_ALIGNMENT_FAIL; +dmarc_spf_alignment = DMARC_POLICY_SPF_ALIGNMENT_FAIL; + +/* ACLs have "control=dmarc_disable_verify" */ +if (f.dmarc_disable_verify || dmarc_abort) + return OK; + +DEBUG(D_receive) { debug_printf_indent("DMARC: process\n"); expand_level++; } + +/* Store the header From: sender domain for this part of DMARC. +If there is no from_header string, then it's likely this message +is locally generated and relying on fixups to add it. Just skip +the entire DMARC system if we can't find a From: header....or if +there was a previous error. */ + +if (!dmarc_from_header) + { + DEBUG(D_receive) debug_printf_indent("DMARC: no From: header\n"); + dmarc_abort = TRUE; + } +else + { +/* RFC 7489 6.6.1 :- extract the domain from the 5322.From */ + const uschar * end_addr, * s; + uschar * errormsg; + int dummy, domain; + + f.parse_allow_group = TRUE; + end_addr = parse_find_address_end(dmarc_from_header, FALSE); + s = *end_addr + ? string_copyn(dmarc_from_header, end_addr - dmarc_from_header) + : dmarc_from_header; + if ((dmarc_header_from_sender = parse_extract_address(s, &errormsg, + &dummy, &dummy, &domain, FALSE))) + dmarc_header_from_sender += domain; + + /* Only use the domain if not empty. Otherwise, skip out of DMARC. */ + + if (!dmarc_header_from_sender || !*dmarc_header_from_sender) + { + dmarc_status = US"nofrom"; + dmarc_pass_fail = US"temperror"; + dmarc_status_text = US"No From: domain found"; + dmarc_action = DMARC_RESULT_ACCEPT; + + dmarc_abort = TRUE; + } + } + +/* Skip DMARC if connection is SMTP Auth. Temporarily, admin should +instead do this in the ACLs. */ + +if (!dmarc_abort && !sender_host_authenticated) + { +/* RFC 7489 6.3 :- defaults for policy record tags */ + dmarc_policy_record dmarc_parsed = { + .adkim = US"r", + .aspf = US"r", + .fo = US"0", + .pct = US"100", + .rf = US"afrf", + .ri = US"86400", + }; + + int sr = SPF_RESULT_INVALID, spf_origin; + uschar * spf_human_readable = NULL, * spf_sender_domain = NULL; + unsigned dkim_sig_count = 0; + gstring * dkim_history_buffer = NULL; + typedef const pdkim_signature * (*sigs_fn_t)(void); + +/* RFC 7489 6.6.2 step 2: DMARC policy record from DNS */ + DEBUG(D_receive) + { + debug_printf_indent("DMARC: get policy record\n"); + expand_level++; + } + + /* uses $dmarc_header_from_sender */ + if (!(rr = dmarc_get_dns_policy_record(&dmarc_used_domain))) + /*XXX want to handle nxdomain,temprror etc. here */ + { + DEBUG(D_receive) debug_printf_indent("DMARC: no record found for %s\n", + dmarc_header_from_sender); + dmarc_policy = DMARC_POLICY_ABSENT; + dmarc_status = US"norecord"; + dmarc_pass_fail = US"none"; + dmarc_status_text = US"No DMARC record"; + dmarc_action = DMARC_RESULT_ACCEPT; + + has_dmarc_record = FALSE; + } + + else if (!dmarc_local_parse_policy(rr, &dmarc_parsed)) + { + DEBUG(D_receive) debug_printf_indent("DMARC: invalid record found for %s\n", + dmarc_header_from_sender); + dmarc_policy = DMARC_POLICY_ABSENT; + dmarc_status = US"norecord"; + dmarc_pass_fail = US"none"; + dmarc_status_text = US"No DMARC record"; + dmarc_action = DMARC_RESULT_ACCEPT; + + has_dmarc_record = FALSE; + goto out; + } + +/* RFC 7489 6.6.3 step 6: p/sp checks */ + if ( !dmarc_parsed.p + || !dmarc_tag_vfy_p(dmarc_parsed.p) + || dmarc_parsed.sp && !dmarc_tag_vfy_sp(dmarc_parsed.sp) + ) + +/* RFC 7489 6.6.3 step 6: if a valid rua, continue with p=none */ +/*XXX "at least one syntactically valid reporting URI" */ + if (dmarc_parsed.rua && dmarc_tag_vfy_rua(dmarc_parsed.rua)) + { + DEBUG(D_receive) + debug_printf_indent("DMARC: invalid p or sp; continue for rua\n"); + dmarc_parsed.p = US"none"; + } + else + { + DEBUG(D_receive) + debug_printf_indent("DMARC: invalid p or sp, and no rua. Abort.\n"); + dmarc_abort = TRUE; + goto out; + } + +/* RFC 7489 6.6.2 step 3: Perform DKIM signature verification checks */ + DEBUG(D_receive) + { + expand_level--; + debug_printf_indent("DMARC: process dkim results\n"); + expand_level++; + } + + /* Now we cycle through the dkim signature results and put into + the opendmarc context, further building the DMARC reply. */ + + if (has_dmarc_record) + for(const pdkim_signature * sig = + (((sigs_fn_t *)dmarc_dkim_mod_info->functions)[DKIM_SIGS_LIST])(); + sig; sig = sig->next) + { + int dkim_result, dkim_ares_result, vs, ves; + + dkim_sig_count++; + vs = sig->verify_status & ~PDKIM_VERIFY_POLICY; + ves = sig->verify_ext_status; + dkim_result = vs == PDKIM_VERIFY_PASS ? DMARC_POLICY_DKIM_OUTCOME_PASS : + vs == PDKIM_VERIFY_FAIL ? DMARC_POLICY_DKIM_OUTCOME_FAIL : + vs == PDKIM_VERIFY_INVALID ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL : + DMARC_POLICY_DKIM_OUTCOME_NONE; + + DEBUG(D_receive) + debug_printf_indent("DMARC: adding DKIM sender domain = %s\n", + sig->domain); + + /* Update the history buffer */ + + dkim_ares_result = + vs == PDKIM_VERIFY_PASS ? ARES_RESULT_PASS : + vs == PDKIM_VERIFY_FAIL ? ARES_RESULT_FAIL : + vs == PDKIM_VERIFY_NONE ? ARES_RESULT_NONE : + vs == PDKIM_VERIFY_INVALID ? + ves == PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE ? ARES_RESULT_PERMERROR : + ves == PDKIM_VERIFY_INVALID_BUFFER_SIZE ? ARES_RESULT_PERMERROR : + ves == PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD ? ARES_RESULT_PERMERROR : + ves == PDKIM_VERIFY_INVALID_PUBKEY_IMPORT ? ARES_RESULT_PERMERROR : + ARES_RESULT_UNKNOWN : + ARES_RESULT_UNKNOWN; + + dkim_history_buffer = string_fmt_append(dkim_history_buffer, + "dkim %s %s %d\n", sig->domain, sig->selector, dkim_ares_result); + + /* Evaluate the sig vs. dmarc requirements */ + +/* RFC 7489 3.1.1 if any DKIM signature ... verifies. */ + if ( !dmarc_alignment_dkim + && dkim_result == DMARC_POLICY_DKIM_OUTCOME_PASS + +/* RFC 7489 3.1.1 dkim alignment: d= tag in dkim sig */ +/* RFC 7489 3.1.1 dkim alignment: 5322.From domain */ +/* RFC 7489 6.3 adkim: DKIM Identifier Alignment mode */ + + && (dmarc_alignment_dkim = identifier_aligned(sig->domain, + dmarc_header_from_sender, dmarc_parsed.adkim)) + ) + dmarc_dkim_alignment = DMARC_POLICY_DKIM_ALIGNMENT_PASS; + } + DEBUG(D_receive) debug_printf_indent("DMARC: %u dkim sig%s\n", + dkim_sig_count, dkim_sig_count == 1 ? "" : "s"); + + DEBUG(D_receive) + { + expand_level--; + debug_printf_indent("DMARC: process spf results\n"); + expand_level++; + } + +/* RFC 7489 6.6.2 step 4: Perform SPF validation checks */ + + if (has_dmarc_record) + { + int spf_result; + typedef int (*fn_t)(uschar **); + + /* Use the envelope sender domain for this part of DMARC */ + + spf_sender_domain = expand_string(US"$sender_address_domain"); + + if (dmarc_spf_mod_info) + sr = ((fn_t *) dmarc_spf_mod_info->functions)[SPF_GET_RESULTS] + (&spf_human_readable); + + if (sr == SPF_RESULT_INVALID) + { + /* No spf data means null envelope sender so generate a domain name + from the sender_helo_name */ + + DEBUG(D_receive) debug_printf_indent("DMARC: spf result 'invalid'\n"); + + if (!spf_sender_domain || !*spf_sender_domain) + { + spf_sender_domain = sender_helo_name; + log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n", + spf_sender_domain); + } + spf_result = DMARC_POLICY_SPF_OUTCOME_NONE; + dmarc_spf_ares_result = ARES_RESULT_UNKNOWN; + spf_origin = DMARC_POLICY_SPF_ORIGIN_HELO; + spf_human_readable = US""; + } + else + { + spf_result = sr == SPF_RESULT_NEUTRAL ? DMARC_POLICY_SPF_OUTCOME_NONE : + sr == SPF_RESULT_PASS ? DMARC_POLICY_SPF_OUTCOME_PASS : + sr == SPF_RESULT_FAIL ? DMARC_POLICY_SPF_OUTCOME_FAIL : + sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL : + DMARC_POLICY_SPF_OUTCOME_NONE; + dmarc_spf_ares_result = sr == SPF_RESULT_NEUTRAL ? ARES_RESULT_NEUTRAL : + sr == SPF_RESULT_PASS ? ARES_RESULT_PASS : + sr == SPF_RESULT_FAIL ? ARES_RESULT_FAIL : + sr == SPF_RESULT_SOFTFAIL ? ARES_RESULT_SOFTFAIL : + sr == SPF_RESULT_NONE ? ARES_RESULT_NONE : + sr == SPF_RESULT_TEMPERROR ? ARES_RESULT_TEMPERROR : + sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR : + ARES_RESULT_UNKNOWN; + /*XXX hmm, spf_origin never used? */ + spf_origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM; + DEBUG(D_receive) + debug_printf_indent("DMARC: using SPF sender domain = %s\n", + spf_sender_domain); + } + if (!spf_sender_domain || !*spf_sender_domain) + dmarc_abort = TRUE; + if (!dmarc_abort) + { + /* RFC 7489 3.1.1 spf alignment: the SPF-authenticated domain */ + /* RFC 7489 3.1.1 spf alignment: 5322.From domain */ + /* RFC 7489 6.3 aspf: SPF Identifier Alignment mode */ + + if ( spf_result == DMARC_POLICY_SPF_OUTCOME_PASS + && (dmarc_alignment_spf = identifier_aligned(spf_sender_domain, + dmarc_header_from_sender, dmarc_parsed.aspf)) + ) + dmarc_spf_alignment = DMARC_POLICY_SPF_ALIGNMENT_PASS; + } + } + + DEBUG(D_receive) + { + expand_level--; + debug_printf_indent("DMARC: finished spf\n"); + } + + /* Store the policy string in an expandable variable. */ + +/* RFC 7489 is unclear how to obtain the policy-string that is to be used. +The decription of tags p & sp in 6.3 uses the term "domain queried". I assume +that is the portion of the DNS lookup key *after* the prepended "_dmarc." +which returned the DMARC RR being used (so it could be the Organizational +Domain, per 6.6.3 bullet 3, rather than the 5322.From domain). + +Given that assumption: if dom-used != 5322.From.dom and there is an sp, +use the sp. Otherwise use the p. */ + + dmarc_domain_policy = dmarc_parsed.sp + && dmarc_used_domain != dmarc_header_from_sender + ? dmarc_parsed.sp : dmarc_parsed.p; + +/* RFC 7489 6.6.2 step 5 - if either the spf or dkim shows alignment, pass */ + if (dmarc_alignment_spf || dmarc_alignment_dkim) + { /* Explicit accept */ + dmarc_policy = DMARC_POLICY_PASS; + dmarc_status = US"accept"; + dmarc_pass_fail = US"pass"; + dmarc_status_text = US"Accept"; + dmarc_action = DMARC_RESULT_ACCEPT; + } + +/* RFC 7489 6.6.2 step 6 - dispose of no-alignment per discovered policy */ + else + { + dmarc_status = dmarc_domain_policy; + if (Ustrcmp(dmarc_domain_policy, "none") == 0) + { /* Accept and report */ + dmarc_policy = DMARC_POLICY_NONE; + dmarc_pass_fail = US"none"; + dmarc_status_text = US"None, Accept"; + dmarc_action = DMARC_RESULT_ACCEPT; + } + else if (Ustrcmp(dmarc_domain_policy, "quarantine") == 0) + { /* Explicit quarantine*/ + dmarc_policy = DMARC_POLICY_QUARANTINE; + dmarc_pass_fail = US"fail"; + dmarc_status_text = US"Quarantine"; + dmarc_action = DMARC_RESULT_QUARANTINE; + } + else if (Ustrcmp(dmarc_domain_policy, "reject") == 0) + { /* Explicit reject */ + dmarc_policy = DMARC_POLICY_REJECT; + dmarc_pass_fail = US"fail"; + dmarc_status_text = US"Reject"; + dmarc_action = DMARC_RESULT_REJECT; + } + else /* should never happen; tag values were validated */ + { /* could use similar for dns tmpfail */ + dmarc_status = dmarc_pass_fail = US"temperror"; + dmarc_status_text = US"Internal Policy Error"; + dmarc_action = DMARC_RESULT_TEMPFAIL; + } + } + + // the alignments would be results of the DMARC evaluation + // - we have them already, from the dkim & spf processing + + if (has_dmarc_record && !dmarc_abort) + { + /* Log results. Robably should have a log_selector to reduce noise. */ + + log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s " + "spf_align=%s dkim_align=%s enforcement='%s'", + spf_sender_domain, dmarc_used_domain, + dmarc_alignment_spf ? "yes" : "no", + dmarc_alignment_dkim ? "yes" : "no", + dmarc_status_text); + + + /* History file, for later aggregate reporting. */ + + dmarc_pct = atoi(CCS dmarc_parsed.pct); + + dmarc_adkim = dmarc_parsed.adkim + ? *dmarc_parsed.adkim : DMARC_RECORD_A_UNSPECIFIED; + dmarc_aspf = dmarc_parsed.aspf + ? *dmarc_parsed.aspf : DMARC_RECORD_A_UNSPECIFIED; + dmarc_dom_policy = dmarc_parsed.p + ? *dmarc_parsed.p : DMARC_RECORD_P_UNSPECIFIED; + dmarc_subdom_policy = dmarc_parsed.sp + ? *dmarc_parsed.sp : DMARC_RECORD_P_UNSPECIFIED; + +/* RFC 7489 6.3 - rua is a comma-sep list */ + dmarc_rua = dmarc_clist_to_array(dmarc_parsed.rua); + + dmarc_write_history_file(dkim_history_buffer); + + /* Forensic reporting */ + + dmarc_maybe_send_forensic(dmarc_parsed.ruf); + } + } + +out: + DEBUG(D_receive) + { + expand_level--; + debug_printf_indent("DMARC: finished process, status %q\n", dmarc_status); + } + return OK; +} + +static const uschar * +dmarc_exim_expand_defaults(void) +{ +return f.dmarc_disable_verify ? US"off" : US"none"; +} + +/*API*/ +const uschar * +dmarc_exim_expand_query(void) +{ +if (f.dmarc_disable_verify ) // || !dmarc_pctx) + return dmarc_exim_expand_defaults(); + +return dmarc_status; +} + + +# endif /* have SPF & DKIM */ +#endif /* EXPERIMENTAL_DMARC_NATIVE */ +/* vi: aw ai sw=2 + */ diff --git a/src/src/miscmods/spf.c b/src/src/miscmods/spf.c index 9d215f516..f73a8776b 100644 --- a/src/src/miscmods/spf.c +++ b/src/src/miscmods/spf.c @@ -84,18 +84,18 @@ SPF_dns_rr_t srr = { .source = spf_dns_server }; -DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain); +DEBUG(D_receive) + { debug_printf_indent("SPF_dns_exim_lookup '%s'\n", domain); expand_level++; } /* Shortcircuit SPF RR lookups by returning NO_DATA. They were obsoleted by RFC 6686/7208 years ago. see bug #1294 */ if (rr_type == T_SPF) { - HDEBUG(D_host_lookup) debug_printf("faking NO_DATA for SPF RR(99) lookup\n"); + HDEBUG(D_host_lookup) + debug_printf_indent("faking NO_DATA for SPF RR(99) lookup\n"); srr.herrno = NO_DATA; - SPF_dns_rr_dup(&spfrr, &srr); - store_free_dns_answer(dnsa); - return spfrr; + goto out; } switch (dns_lookup(dnsa, US domain, rr_type, NULL)) @@ -115,11 +115,7 @@ switch (dns_lookup(dnsa, US domain, rr_type, NULL)) } if (found == 0) - { - SPF_dns_rr_dup(&spfrr, &srr); - store_free_dns_answer(dnsa); - return spfrr; - } + goto out; srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found); @@ -153,8 +149,8 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; if (rr->size < 1+6) continue; /* min for version str */ if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0) { - HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n", - (int) s[0], s+1); + HDEBUG(D_host_lookup) debug_printf_indent("not an spf record: %.*s\n", + (int) s[0], s+1); continue; } @@ -170,7 +166,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; continue; gstring_release_unused(g); s = string_copy_malloc(string_from_gstring(g)); - DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s); + DEBUG(D_receive) debug_printf_indent("SPF_dns_exim_lookup '%s'\n", s); break; } @@ -192,10 +188,13 @@ empty ANSWER section. */ if (!(srr.num_rr = found)) srr.herrno = NO_DATA; -/* spfrr->rr must have been malloc()d for this */ -SPF_dns_rr_dup(&spfrr, &srr); -store_free_dns_answer(dnsa); -return spfrr; +out: + /* spfrr->rr must have been malloc()d for this */ + SPF_dns_rr_dup(&spfrr, &srr); + + DEBUG(D_receive) expand_level--; + store_free_dns_answer(dnsa); + return spfrr; } @@ -205,7 +204,7 @@ SPF_dns_exim_new(int debug) { SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t)); -/* DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n"); */ +/* DEBUG(D_receive) debug_printf_indent("SPF_dns_exim_new\n"); */ memset(spf_dns_server, 0, sizeof(SPF_dns_server_t)); spf_dns_server->destroy = NULL; @@ -252,17 +251,17 @@ testsuite. */ if (!(dc = SPF_dns_exim_new(debug))) { - DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n"); + DEBUG(D_receive) debug_printf_indent("SPF_dns_exim_new() failed\n"); return FALSE; } if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8))) { - DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n"); + DEBUG(D_receive) debug_printf_indent("SPF_dns_cache_new() failed\n"); return FALSE; } if (!(spf_server = SPF_server_new_dns(dc, debug))) { - DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n"); + DEBUG(D_receive) debug_printf_indent("SPF_server_new() failed.\n"); return FALSE; } @@ -294,8 +293,8 @@ static int spf_conn_init(const uschar * spf_helo_domain, const uschar * spf_remote_addr, const uschar ** errstr) { -DEBUG(D_receive) - debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr); +DEBUG(D_receive) debug_printf_indent("spf_conn_init: %s %s\n", + spf_helo_domain, spf_remote_addr); if (!spf_server && !spf_init(NULL)) { @@ -305,8 +304,8 @@ if (!spf_server && !spf_init(NULL)) if (SPF_server_set_rec_dom(spf_server, CS primary_hostname)) { - DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(%q) failed.\n", - primary_hostname); + DEBUG(D_receive) debug_printf_indent("SPF_server_set_rec_dom(%q) failed.\n", + primary_hostname); spf_server = NULL; *errstr = US"spf: setting host name"; return FAIL; @@ -319,7 +318,7 @@ if ( SPF_request_set_ipv4_str(spf_request, CCS spf_remote_addr) ) { DEBUG(D_receive) - debug_printf("spf: SPF_request_set_ipv4_str() and " + debug_printf_indent("SPF_request_set_ipv4_str() and " "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr); spf_server = NULL; spf_request = NULL; @@ -329,8 +328,8 @@ if ( SPF_request_set_ipv4_str(spf_request, CCS spf_remote_addr) if (SPF_request_set_helo_dom(spf_request, CCS spf_helo_domain)) { - DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(%q) failed.\n", - spf_helo_domain); + DEBUG(D_receive) debug_printf_indent("SPF_set_helo_dom(%q) failed.\n", + spf_helo_domain); spf_server = NULL; spf_request = NULL; *errstr = US"spf: setting helo string"; @@ -352,14 +351,14 @@ static void spf_response_debug(SPF_response_t * spf_response) { if (SPF_response_messages(spf_response) == 0) - debug_printf(" (no errors)\n"); + debug_printf_indent(" (no errors)\n"); else for (int i = 0; i < SPF_response_messages(spf_response); i++) { SPF_error_t * err = SPF_response_message(spf_response, i); - debug_printf( "%s_msg = (%d) %s\n", - (SPF_error_errorp(err) ? "warn" : "err"), - SPF_error_code(err), - SPF_error_message(err)); + debug_printf_indent("%s_msg = (%d) %s\n", + SPF_error_errorp(err) ? "warn" : "err", + SPF_error_code(err), + SPF_error_message(err)); } } @@ -375,11 +374,11 @@ spf_process(const uschar ** listptr, const uschar * spf_envelope_sender, int action) { int sep = 0; -const uschar *list = *listptr; -uschar *spf_result_id; -int rc = SPF_RESULT_PERMERROR; +const uschar * list = * listptr; +uschar * spf_result_id; +int rc = SPF_RESULT_PERMERROR, ret = OK; -DEBUG(D_receive) debug_printf("spf_process\n"); +DEBUG(D_receive) { debug_printf_indent("SPF: process\n"); expand_level++; } if (!(spf_server && spf_request)) /* no global context, assume temp error and skip to evaluation */ @@ -412,24 +411,32 @@ else } /* We got a result. Now see if we should return OK or FAIL for it */ -DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc); +DEBUG(D_acl) + debug_printf_indent("SPF: result is %s (%d)\n", SPF_strresult(rc), rc); if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none"))) - return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK); + ret = spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK); -while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0))) +else { - BOOL negate, result; + while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0))) + { + BOOL negate, result; - if ((negate = spf_result_id[0] == '!')) - spf_result_id++; + if ((negate = spf_result_id[0] == '!')) + spf_result_id++; + + result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0; + if (negate != result) goto out; + } - result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0; - if (negate != result) return OK; + /* no match */ + ret = FAIL; } -/* no match */ -return FAIL; +out: + DEBUG(D_receive) expand_level--; + return ret; } @@ -457,11 +464,11 @@ if (spf_result) ? string_append(g, 2, US" smtp.helo=", s) : string_cat(g, US" smtp.mailfrom=<>"); } - DEBUG(D_acl) debug_printf("SPF:\tauthres '%.*s'\n", + DEBUG(D_acl) debug_printf_indent("SPF:\tauthres '%.*s'\n", gstring_length(g) - start - 3, g->s + start + 3); } else - DEBUG(D_acl) debug_printf("SPF:\tno authres\n"); + DEBUG(D_acl) debug_printf_indent("SPF:\tno authres\n"); return g; } @@ -478,7 +485,7 @@ if (spf_response) s = US spf_response->header_comment; } *human_readable_p = s ? string_copy(s) : US""; -DEBUG(D_acl) debug_printf("SPF: %d '%s'\n", res, s); +DEBUG(D_acl) debug_printf_indent(" SPF: %d '%s'\n", res, s); return res; } diff --git a/src/src/miscmods/spf_perl.c b/src/src/miscmods/spf_perl.c index e7bc1e331..33871c9aa 100644 --- a/src/src/miscmods/spf_perl.c +++ b/src/src/miscmods/spf_perl.c @@ -298,7 +298,7 @@ if (spf_result) } *human_readable_p = s ? string_copy(s) : US""; -DEBUG(D_acl) debug_printf_indent("SPF: %d '%s'\n", res, s); +DEBUG(D_acl) debug_printf_indent(" SPF: %d '%s'\n", res, s); return res; } diff --git a/src/src/moan.c b/src/src/moan.c index 5635f93e2..01bf4f578 100644 --- a/src/src/moan.c +++ b/src/src/moan.c @@ -167,7 +167,7 @@ int written = 0, fd, status, count = 0, size_limit = bounce_return_size_limit; FILE * fp; int pid; -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC uschar * s, * s2; /* For DMARC if there is a specific sender set, expand the variable for the @@ -206,7 +206,7 @@ fp = fdopen(fd, "wb"); if (errors_reply_to) fprintf(fp, "Reply-To: %s\n", errors_reply_to); fprintf(fp, "Auto-Submitted: auto-replied\n"); -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC if (s) fprintf(fp, "From: %s\n", s); else @@ -324,23 +324,16 @@ switch(ident) fprintf(fp, "\n"); break; -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC case ERRMESS_DMARC_FORENSIC: bounce_return_message = TRUE; bounce_return_body = FALSE; - fprintf(fp, "Subject: DMARC Forensic Report for %s from IP %s\n\n", - eblock ? eblock->text2 : US"Unknown", - sender_host_address); - fprintf(fp, - "A message claiming to be from you has failed the published DMARC\n" - "policy for your domain.\n\n"); - while (eblock) - { - fprintf(fp, " %s: %s\n", eblock->text1, eblock->text2); - count++; - eblock = eblock->next; - } - break; + for (; eblock; eblock = eblock->next) + if (eblock->text2) + fprintf(fp, " %s: %s\n", eblock->text1, eblock->text2); + else + fprintf(fp, "%s", eblock->text1); + break; #endif default: @@ -435,13 +428,13 @@ if (bounce_return_message) fputs(CS buf, fp); } } -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC /* Overkill, but use exact test in case future code gets inserted */ - else if (bounce_return_body && message_file == NULL) + else if (bounce_return_body && !message_file) { /*XXX limit line length here? */ /* This doesn't print newlines, disable until can parse and fix - * output to be legible. */ + output to be legible. */ fprintf(fp, "%s", expand_string(US"$message_body")); } #endif diff --git a/src/src/readconf.c b/src/src/readconf.c index 1fe2b2bfc..04cd5e68a 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -124,7 +124,7 @@ static optionlist optionlist_config[] = { { "dkim_verify_minimal", opt_module, {US"dkim"} }, { "dkim_verify_signers", opt_module, {US"dkim"} }, #endif -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC { "dmarc_forensic_sender", opt_module, {US"dmarc"} }, { "dmarc_history_file", opt_module, {US"dmarc"} }, { "dmarc_tld_file", opt_module, {US"dmarc"} }, diff --git a/src/src/receive.c b/src/src/receive.c index cde09c40a..0f407ce93 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -16,7 +16,7 @@ extern int dcc_ok; #endif -#ifdef SUPPORT_DMARC +#ifdef EXIM_HAVE_DMARC # include "miscmods/dmarc.h" #endif @@ -1754,9 +1754,6 @@ BOOL date_header_exists = FALSE; /* Pointers to receive the addresses of headers whose contents we need. */ header_line * from_header = NULL; -#ifdef SUPPORT_DMARC -header_line * dmarc_from_header = NULL; -#endif header_line * subject_header = NULL, * msgid_header = NULL, * received_header; BOOL msgid_header_newly_created = FALSE; @@ -2454,8 +2451,16 @@ for (header_line * h = header_list->next; h; h = h->next) case htype_from: h->type = htype_from; -#ifdef SUPPORT_DMARC - if (!is_resent) dmarc_from_header = h; +#ifdef EXIM_HAVE_DMARC + if (!is_resent && !f.dmarc_disable_verify) + { + misc_module_info * mi = misc_mod_findonly(US"dmarc"); + if (mi) + { + typedef void (*fn_t)(const uschar *); + (((fn_t *) mi->functions)[DMARC_STORE_FROMHDR]) (h->text); + } + } #endif if (!resents_exist || is_resent) { @@ -3584,17 +3589,6 @@ else } #endif /* WITH_CONTENT_SCAN */ -#ifdef SUPPORT_DMARC - { - misc_module_info * mi = misc_mod_findonly(US"dmarc"); - if (mi) - { - typedef int (*fn_t)(header_line *); - (((fn_t *) mi->functions)[DMARC_STORE_DATA]) (dmarc_from_header); - } - } -#endif - #ifndef DISABLE_PRDR if (prdr_requested && recipients_count > 1) { diff --git a/src/src/regex_cache.c b/src/src/regex_cache.c index b92556d92..713da90d5 100644 --- a/src/src/regex_cache.c +++ b/src/src/regex_cache.c @@ -142,7 +142,7 @@ size_t offset; const pcre2_code * yield; int old_pool = store_pool, err; -/* Optionall, check the cache and return if found */ +/* Optionally, check the cache and return if found */ if ( flags & MCS_CACHEABLE && (yield = regex_from_cache(pattern, caseless))) commit 726e6f798bc2a18b019a23ba48ee6ddbf68ebf7f Author: Jeremy Harris Date: Fri Nov 14 11:58:10 2025 +0000 Build: fix spf dynamic-module build Broken-by: 998636a4f849 diff --git a/src/src/EDITME b/src/src/EDITME index d38ac98e5..a2dddd780 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -406,7 +406,8 @@ TRANSPORT_SMTP=yes # for the specialist case of using the DNS as a general database facility (not # common). # If set to "2" instead of "yes" then the corresponding lookup will be -# built as a module and must be installed into LOOKUP_MODULE_DIR. You need to +# built as a module and left in the "dynlibs" directory under the build +# directory. They must be installed into LOOKUP_MODULE_DIR. You need to # add -export-dynamic -rdynamic to EXTRALIBS. You may also need to add -ldl to # EXTRALIBS so that dlopen() is available to Exim. You need to define # LOOKUP_MODULE_DIR above so the exim binary actually loads dynamic lookup diff --git a/src/src/lookups/spf.c b/src/src/lookups/spf.c index ce314f7e5..bfce04fa7 100644 --- a/src/src/lookups/spf.c +++ b/src/src/lookups/spf.c @@ -86,11 +86,15 @@ return FAIL; gstring * spf_version_report(gstring * g) { +#if EXIM_HAVE_SPF == 2 +return g; +#else int maj, min, patch; SPF_get_lib_version(&maj, &min, &patch); return string_fmt_append(g, "Library version: SPF: Runtime: %d.%d.%d\n", maj, min, patch); +#endif } commit 03c4dd24276bfb43f5f803fa7c8cc83076f1c485 Author: Jeremy Harris Date: Fri Nov 14 12:11:10 2025 +0000 LDAP: fix version report under dynamic build diff --git a/src/src/lookups/ldap.c b/src/src/lookups/ldap.c index 7d240309c..b73413a39 100644 --- a/src/src/lookups/ldap.c +++ b/src/src/lookups/ldap.c @@ -1576,7 +1576,7 @@ if (ldap_get_option(ld, LDAP_OPT_API_INFO, &info) == LDAP_OPT_SUCCESS) g = string_fmt_append(g, " Runtime %s %d\n", info.ldapai_vendor_name, info.ldapai_vendor_version); - for (char ** sp = info.ldapai_extensions; *sp; *sp++) + for (char ** sp = info.ldapai_extensions; *sp; sp++) ldap_memfree(*sp); ldap_memfree(info.ldapai_extensions); ldap_memfree(info.ldapai_vendor_name); commit 11f6b4c7d34582a3b046345dd67d576b3fef920f Author: Jeremy Harris Date: Fri Nov 14 14:17:14 2025 +0000 DMARC: fix history-file ARC reporting, under dynamic build diff --git a/src/src/miscmods/dmarc_common.c b/src/src/miscmods/dmarc_common.c index 168e7da8e..059b31167 100644 --- a/src/src/miscmods/dmarc_common.c +++ b/src/src/miscmods/dmarc_common.c @@ -418,6 +418,9 @@ g = string_fmt_append(g, "align_dkim %d\n" gstring * g2 = NULL; typedef const uschar * (*fn_t)(gstring **); + if (!dmarc_arc_mod_info) + dmarc_arc_mod_info = misc_mod_findonly(US"arc"); + if ( dmarc_arc_mod_info && (s = (((fn_t *) dmarc_arc_mod_info->functions)[ARC_ARCSET_INFO]) (&g2))) { @@ -437,7 +440,7 @@ g = string_fmt_append(g, "align_dkim %d\n" else # endif - g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n", + g = string_fmt_append(g, "arc %d\narc_policy %d json[ ]\n", ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED); } #endif commit 68841125d8ba6168ae38de296273bdcd4df592c8 Author: Jeremy Harris Date: Sun Nov 16 15:16:53 2025 +0000 tidying diff --git a/src/src/functions.h b/src/src/functions.h index bd390cce6..73371cc7b 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -1080,7 +1080,7 @@ if (gstring_last_char(g) == c) gstring_trim(g, 1); static inline void gstring_reset(gstring * g) { -g->ptr = 0; +g->s[g->ptr = 0] = '\0'; } diff --git a/src/src/host.c b/src/src/host.c index cfa2620df..28e08ee23 100644 --- a/src/src/host.c +++ b/src/src/host.c @@ -836,6 +836,7 @@ Returns: pointer to character string uschar * host_ntoa(int type, const void * arg, uschar * buffer, int * portptr) { +#define NTOA_BSIZE 46 uschar * yield; /* The new world. It is annoying that we have to fish out the address from @@ -845,7 +846,7 @@ function inet_ntoa() returns just uschar *, and some picky compilers insist on warning if one assigns a const uschar * to a uschar *. Hence the casts. */ #if HAVE_IPV6 -uschar addr_buffer[46]; +uschar addr_buffer[NTOA_BSIZE]; if (type < 0) { int family = ((struct sockaddr *)arg)->sa_family; @@ -888,7 +889,7 @@ else /* If there is no buffer, put the string into some new store. */ -if (!buffer) buffer = store_get(46, GET_UNTAINTED); +if (!buffer) buffer = store_get(NTOA_BSIZE, GET_UNTAINTED); /* Callers of this function with a non-NULL buffer must ensure that it is large enough to hold an IPv6 address, namely, at least 46 bytes. That's what @@ -896,9 +897,10 @@ makes this use of strcpy() OK. If the library returned apparently an apparently tainted string, clean it; we trust IP addresses. */ -string_format_nt(buffer, 46, "%s", yield); +string_format_nt(buffer, NTOA_BSIZE, "%s", yield); return buffer; } +#undef NTOA_BSIZE diff --git a/src/src/miscmods/dmarc_native.c b/src/src/miscmods/dmarc_native.c index ce4aaee4f..617517d84 100644 --- a/src/src/miscmods/dmarc_native.c +++ b/src/src/miscmods/dmarc_native.c @@ -299,7 +299,6 @@ dmarc_process(void) { const uschar * rr; BOOL has_dmarc_record = TRUE; -u_char ** ruf; /* forensic report addressees, if called for */ dmarc_alignment_spf = dmarc_alignment_dkim = FALSE; dmarc_dkim_alignment = DMARC_POLICY_DKIM_ALIGNMENT_FAIL; diff --git a/src/src/miscmods/pdkim/pdkim.c b/src/src/miscmods/pdkim/pdkim.c index a43ea9bfb..a7ec5f511 100644 --- a/src/src/miscmods/pdkim/pdkim.c +++ b/src/src/miscmods/pdkim/pdkim.c @@ -1000,10 +1000,12 @@ else last_sig->next = sig; } + /* Check for too many signatures */ + if (dkim_collect_input && --dkim_collect_input == 0) { ctx->headers = pdkim_prepend_stringlist(ctx->headers, g->s); - g->s[g->ptr = 0] = '\0'; + gstring_reset(g); return PDKIM_ERR_EXCESS_SIGS; } } @@ -1013,7 +1015,7 @@ else } BAIL: -g->s[g->ptr = 0] = '\0'; /* leave buffer for reuse */ +gstring_reset(g); /* leave buffer for reuse */ return PDKIM_OK; } commit 3c24de41ada52816d2890033b99eda0543a06b06 Author: Jeremy Harris Date: Fri Nov 21 11:54:08 2025 +0000 debug: indents diff --git a/src/OS/os.c-Linux b/src/OS/os.c-Linux index 34dd7c190..b7cd4e53a 100644 --- a/src/OS/os.c-Linux +++ b/src/OS/os.c-Linux @@ -140,7 +140,7 @@ while (fscanf(f, "%4s%4s%4s%4s%4s%4s%4s%4s %02x %02x %02x %02x %20s\n", } DEBUG(D_interface) - debug_printf("Actual local interface address is %s (%s)\n", last->address, + debug_printf_indent("Actual local interface address is %s (%s)\n", last->address, devname); } fclose(f); diff --git a/src/OS/unsupported/os.c-IRIX b/src/OS/unsupported/os.c-IRIX index 6f043d044..67725322b 100644 --- a/src/OS/unsupported/os.c-IRIX +++ b/src/OS/unsupported/os.c-IRIX @@ -106,7 +106,7 @@ for (nextaddr = buf; nextaddr < lim; nextaddr += ifm->ifm_msglen) last = next; } - DEBUG(D_interface) debug_printf("Actual local interface address is %s\n", + DEBUG(D_interface) debug_printf_indent("Actual local interface address is %s\n", last->address); } } diff --git a/src/OS/unsupported/os.c-IRIX6 b/src/OS/unsupported/os.c-IRIX6 index 6f043d044..67725322b 100644 --- a/src/OS/unsupported/os.c-IRIX6 +++ b/src/OS/unsupported/os.c-IRIX6 @@ -106,7 +106,7 @@ for (nextaddr = buf; nextaddr < lim; nextaddr += ifm->ifm_msglen) last = next; } - DEBUG(D_interface) debug_printf("Actual local interface address is %s\n", + DEBUG(D_interface) debug_printf_indent("Actual local interface address is %s\n", last->address); } } diff --git a/src/OS/unsupported/os.c-IRIX632 b/src/OS/unsupported/os.c-IRIX632 index 6f043d044..67725322b 100644 --- a/src/OS/unsupported/os.c-IRIX632 +++ b/src/OS/unsupported/os.c-IRIX632 @@ -106,7 +106,7 @@ for (nextaddr = buf; nextaddr < lim; nextaddr += ifm->ifm_msglen) last = next; } - DEBUG(D_interface) debug_printf("Actual local interface address is %s\n", + DEBUG(D_interface) debug_printf_indent("Actual local interface address is %s\n", last->address); } } diff --git a/src/OS/unsupported/os.c-IRIX65 b/src/OS/unsupported/os.c-IRIX65 index 6f043d044..67725322b 100644 --- a/src/OS/unsupported/os.c-IRIX65 +++ b/src/OS/unsupported/os.c-IRIX65 @@ -106,7 +106,7 @@ for (nextaddr = buf; nextaddr < lim; nextaddr += ifm->ifm_msglen) last = next; } - DEBUG(D_interface) debug_printf("Actual local interface address is %s\n", + DEBUG(D_interface) debug_printf_indent("Actual local interface address is %s\n", last->address); } } diff --git a/src/src/host.c b/src/src/host.c index 28e08ee23..4c513e28c 100644 --- a/src/src/host.c +++ b/src/src/host.c @@ -413,7 +413,7 @@ else if (Ustrchr(h->name, ':') == p) h->name = string_copyn(h->name, p - h->name); else return PORT_NONE; -DEBUG(D_route|D_host_lookup) debug_printf("host=%s port=%d\n", h->name, port); +DEBUG(D_route|D_host_lookup) debug_printf_indent("host=%s port=%d\n", h->name, port); return port; } @@ -792,7 +792,7 @@ if (!local_interface_data) local_interface_data = add_unique_interface(local_interface_data, ipa); DEBUG(D_interface) { - debug_printf("Configured local interface: address=%s", ipa->address); + debug_printf_indent("Configured local interface: address=%s", ipa->address); if (ipa->port != 0) debug_printf(" port=%d", ipa->port); debug_printf("\n"); } @@ -1408,7 +1408,7 @@ while (host != *lastptr) if (h->next->address != NULL && Ustrcmp(h->next->address, host->address) == 0) { - DEBUG(D_host_lookup) debug_printf("duplicate IP address %s (MX=%d) " + DEBUG(D_host_lookup) debug_printf_indent("duplicate IP address %s (MX=%d) " "removed\n", host->address, h->next->mx); if (h->next == *lastptr) *lastptr = h; h->next = h->next->next; @@ -1490,7 +1490,7 @@ if ( slow_lookup_log if (!hosts) { - HDEBUG(D_host_lookup) debug_printf("IP address lookup failed: h_errno=%d\n", + HDEBUG(D_host_lookup) debug_printf_indent("IP address lookup failed: h_errno=%d\n", h_errno); return (h_errno == TRY_AGAIN || h_errno == NO_RECOVERY) ? DEFER : FAIL; } @@ -1602,7 +1602,7 @@ if (f.running_in_test_harness && Ustrcmp(sender_host_address, "99.99.99.99") == 0) { HDEBUG(D_host_lookup) - debug_printf("Test harness: host name lookup returns DEFER\n"); + debug_printf_indent("Test harness: host name lookup returns DEFER\n"); host_lookup_deferred = TRUE; yield = DEFER; goto out; @@ -1701,7 +1701,7 @@ while ((ordername = string_nextinlist(&list, &sep, NULL, 0))) if (rc == DNS_AGAIN) { HDEBUG(D_host_lookup) - debug_printf("IP address PTR lookup gave temporary error\n"); + debug_printf_indent("IP address PTR lookup gave temporary error\n"); host_lookup_deferred = TRUE; yield = DEFER; goto out; @@ -1713,7 +1713,7 @@ while ((ordername = string_nextinlist(&list, &sep, NULL, 0))) else if (strcmpic(ordername, US"byaddr") == 0) { HDEBUG(D_host_lookup) - debug_printf("IP address lookup using gethostbyaddr()\n"); + debug_printf_indent("IP address lookup using gethostbyaddr()\n"); rc = host_name_lookup_byaddr(); if (rc == DEFER) { @@ -1786,27 +1786,27 @@ for (uschar * hname = sender_host_name; hname; hname = *aliases++) for (host_item * hh = &h; hh; hh = hh->next) if (host_is_in_net(hh->address, sender_host_address, 0)) { - HDEBUG(D_host_lookup) debug_printf(" %s OK\n", hh->address); + HDEBUG(D_host_lookup) debug_printf_indent(" %s OK\n", hh->address); ok = TRUE; break; } else - HDEBUG(D_host_lookup) debug_printf(" %s\n", hh->address); + HDEBUG(D_host_lookup) debug_printf_indent(" %s\n", hh->address); if (!ok) HDEBUG(D_host_lookup) - debug_printf("no IP address for %s matched %s\n", hname, + debug_printf_indent("no IP address for %s matched %s\n", hname, sender_host_address); } else if (rc == HOST_FIND_AGAIN) { - HDEBUG(D_host_lookup) debug_printf("temporary error for host name lookup\n"); + HDEBUG(D_host_lookup) debug_printf_indent("temporary error for host name lookup\n"); host_lookup_deferred = TRUE; sender_host_name = NULL; yield = DEFER; goto out; } else - HDEBUG(D_host_lookup) debug_printf("no IP addresses found for %s\n", hname); + HDEBUG(D_host_lookup) debug_printf_indent("no IP addresses found for %s\n", hname); /* If this name is no good, and it's the sender name, set it null pro tem; if it's an alias, just remove it from the list. */ @@ -1835,7 +1835,7 @@ if (sender_host_name) { yield = OK; goto out; } /* We have failed to find an address that matches. */ HDEBUG(D_host_lookup) - debug_printf("%s does not match any IP address for %s\n", + debug_printf_indent("%s does not match any IP address for %s\n", sender_host_address, save_hostname); /* This message must be in permanent store */ @@ -2051,7 +2051,7 @@ for (int i = 1; i <= times; text_address, NULL) == OK) { DEBUG(D_host_lookup) - debug_printf("ignored host %s [%s]\n", host->name, text_address); + debug_printf_indent("ignored host %s [%s]\n", host->name, text_address); continue; } #endif @@ -2130,8 +2130,8 @@ yield = local_host_check? HDEBUG(D_host_lookup) { if (fully_qualified_name) - debug_printf("fully qualified name = %s\n", *fully_qualified_name); - debug_printf("%s looked up these IP addresses:\n", + debug_printf_indent("fully qualified name = %s\n", *fully_qualified_name); + debug_printf_indent("%s looked up these IP addresses:\n", #if HAVE_IPV6 #if HAVE_GETIPNODEBYNAME "getipnodebyname" @@ -2143,7 +2143,7 @@ HDEBUG(D_host_lookup) #endif ); for (const host_item * h = host; h != last->next; h = h->next) - debug_printf(" name=%s address=%s\n", h->name, + debug_printf_indent(" name=%s address=%s\n", h->name, h->address ? h->address : US""); } @@ -2165,7 +2165,7 @@ RETURN_AGAIN: deliver_domain = save; if (rc == OK) { - DEBUG(D_host_lookup) debug_printf("%s is in dns_again_means_nonexist: " + DEBUG(D_host_lookup) debug_printf_indent("%s is in dns_again_means_nonexist: " "returning HOST_FIND_FAILED\n", host->name); return HOST_FIND_FAILED; } @@ -2297,7 +2297,7 @@ for (; i >= 0; i--) && !dns_is_secure(dnsa) && dns_is_aa(dnsa) ) - debug_printf("DNS lookup of %.256s (A/AAAA) requested AD, but got AA\n", host->name); + debug_printf_indent("DNS lookup of %.256s (A/AAAA) requested AD, but got AA\n", host->name); /* We want to return HOST_FIND_AGAIN if one of the A or AAAA lookups fails or times out, but not if another one succeeds. (In the early @@ -2328,7 +2328,7 @@ for (; i >= 0; i--) { if (dns_is_secure(dnsa)) { - DEBUG(D_host_lookup) debug_printf("%s A DNSSEC\n", host->name); + DEBUG(D_host_lookup) debug_printf_indent("%s A DNSSEC\n", host->name); if (host->dnssec_used == DS_UNK) /* set in host_find_bydns() */ host->dnssec_used = DS_YES; } @@ -2337,13 +2337,13 @@ for (; i >= 0; i--) if (dnssec_require) { dnssec_fail = TRUE; - DEBUG(D_host_lookup) debug_printf("dnssec fail on %s for %.256s", + DEBUG(D_host_lookup) debug_printf_indent("dnssec fail on %s for %.256s", i>0 ? "AAAA" : "A", host->name); continue; } if (host->dnssec_used == DS_YES) /* set in host_find_bydns() */ { - DEBUG(D_host_lookup) debug_printf("%s A cancel DNSSEC\n", host->name); + DEBUG(D_host_lookup) debug_printf_indent("%s A cancel DNSSEC\n", host->name); host->dnssec_used = DS_NO; lookup_dnssec_authenticated = US"no"; } @@ -2364,7 +2364,7 @@ for (; i >= 0; i--) dns_address * da = dns_address_from_rr(dnsa, rr); DEBUG(D_host_lookup) - if (!da) debug_printf("no addresses extracted from A6 RR for %s\n", + if (!da) debug_printf_indent("no addresses extracted from A6 RR for %s\n", host->name); /* This loop runs only once for A and AAAA records, but may run @@ -2378,7 +2378,7 @@ for (; i >= 0; i--) host->name, da->address, NULL) == OK) { DEBUG(D_host_lookup) - debug_printf("ignored host %s [%s]\n", host->name, da->address); + debug_printf_indent("ignored host %s [%s]\n", host->name, da->address); continue; } #endif @@ -3198,7 +3198,7 @@ BOOL sec; rc = dns_lookup_timerwrap(dnsa, buffer, T_TLSA, &fullname); sec = dns_is_secure(dnsa); DEBUG(D_transport) - debug_printf("TLSA lookup ret %s %sDNSSEC\n", dns_rc_names[rc], sec ? "" : "not "); + debug_printf_indent("TLSA lookup ret %s %sDNSSEC\n", dns_rc_names[rc], sec ? "" : "not "); switch (rc) { @@ -3221,7 +3221,7 @@ switch (rc) if (payload_length > MAX_TLSA_EXPANDED_SIZE) payload_length = MAX_TLSA_EXPANDED_SIZE; - debug_printf(" %d %d %d %.*H\n", + debug_printf_indent(" %d %d %d %.*H\n", usage, selector, matching_type, payload_length, p); } } diff --git a/src/src/os.c b/src/src/os.c index 4a054ac50..4b9d2d1d5 100644 --- a/src/src/os.c +++ b/src/src/os.c @@ -528,7 +528,7 @@ for (struct ifaddrs * ifa = ifalist; ifa; ifa = ifa->ifa_next) last = next; } - DEBUG(D_interface) debug_printf("Actual local interface address is %s (%s)\n", + DEBUG(D_interface) debug_printf_indent("Actual local interface address is %s (%s)\n", last->address, ifa->ifa_name); } @@ -761,7 +761,7 @@ for (char * cp = buf; cp < buf + ifc.V_ifc_len; cp += len) last = next; } - DEBUG(D_interface) debug_printf("Actual local interface address is %s (%s)\n", + DEBUG(D_interface) debug_printf_indent("Actual local interface address is %s (%s)\n", last->address, ifreq.V_ifr_name); } diff --git a/src/src/routers/rf_lookup_hostlist.c b/src/src/routers/rf_lookup_hostlist.c index 0a3a71ba9..59e0761ea 100644 --- a/src/src/routers/rf_lookup_hostlist.c +++ b/src/src/routers/rf_lookup_hostlist.c @@ -73,6 +73,7 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h) DEBUG(D_route|D_host_lookup) debug_printf_indent("finding IP address for %s\n", h->name); + expand_level++; /* Handle any port setting that may be on the name; it will be removed from the end of the name. */ @@ -164,12 +165,14 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h) if (rc == HOST_FIND_SECURITY) { + expand_level--; addr->message = string_sprintf("host lookup for %s done insecurely" , h->name); addr->basic_errno = ERRNO_DNSDEFER; return DEFER; } if (rc == HOST_FIND_AGAIN) { + expand_level--; if (rblock->pass_on_timeout) { DEBUG(D_route) @@ -187,6 +190,7 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h) if (rc == HOST_FIND_FAILED) { + expand_level--; if (hff_code == hff_ignore) { if (prev == NULL) addr->host_list = next_h; else prev->next = next_h; @@ -238,6 +242,7 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h) } prev->next = NULL; setflag(addr, af_local_host_removed); + expand_level--; break; } rc = rf_self_action(addr, h, rblock->self_code, rblock->self_rewrite, @@ -245,6 +250,7 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h) if (rc != OK) { addr->host_list = NULL; /* Kill the host list for */ + expand_level--; return rc; /* anything other than "send" */ } self_send = TRUE; @@ -255,6 +261,7 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h) prev = h; while (prev->next != next_h) prev = prev->next; + expand_level--; } return OK; diff --git a/src/src/routers/rf_self_action.c b/src/src/routers/rf_self_action.c index 75fbaa400..dba0774f8 100644 --- a/src/src/routers/rf_self_action.c +++ b/src/src/routers/rf_self_action.c @@ -95,25 +95,25 @@ switch (code) case self_reroute: DEBUG(D_route) - debug_printf("%s: %s: domain changed to %s\n", msg, addr->domain, new); + debug_printf_indent("%s: %s: domain changed to %s\n", msg, addr->domain, new); rf_change_domain(addr, new, rewrite, addr_new); return REROUTED; case self_send: DEBUG(D_route) - debug_printf("%s: %s: configured to try delivery anyway\n", msg, addr->domain); + debug_printf_indent("%s: %s: configured to try delivery anyway\n", msg, addr->domain); return OK; case self_pass: /* This is soft failure; pass to next router */ DEBUG(D_route) - debug_printf("%s: %s: passed to next router (self = pass)\n", msg, addr->domain); + debug_printf_indent("%s: %s: passed to next router (self = pass)\n", msg, addr->domain); addr->message = msg; addr->self_hostname = string_copy(host->name); return PASS; case self_fail: DEBUG(D_route) - debug_printf("%s: %s: address failed (self = fail)\n", msg, addr->domain); + debug_printf_indent("%s: %s: address failed (self = fail)\n", msg, addr->domain); addr->message = msg; setflag(addr, af_pass_message); return FAIL; commit d2353fcd8f602a1f4fdeb31ae006e6b7bc46b349 Author: Jeremy Harris Date: Fri Nov 21 14:17:39 2025 +0000 fix TLS held-open verify to delivery diff --git a/src/src/daemon.c b/src/src/daemon.c index a31ef1eb5..056bf6311 100644 --- a/src/src/daemon.c +++ b/src/src/daemon.c @@ -709,11 +709,8 @@ if (pid == 0) signal(SIGINT, SIG_DFL); if (geteuid() != root_uid && !deliver_drop_privilege) - { - signal(SIGALRM, SIG_DFL); delivery_re_exec(CEE_EXEC_PANIC); /* Control does not return here. */ - } /* No need to re-exec; SIGALRM remains set to the default handler */ diff --git a/src/src/deliver.c b/src/src/deliver.c index 3dc1aef73..c978e67b5 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -9011,7 +9011,6 @@ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only) { int channel_fd = cutthrough.cctx.sock; - smtp_peer_options = cutthrough.peer_options; continue_sequence = 0; #ifndef DISABLE_TLS @@ -9019,9 +9018,7 @@ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only) { int pfd[2], pid; - smtp_peer_options |= OPTION_TLS; - sending_ip_address = cutthrough.snd_ip; - sending_port = cutthrough.snd_port; + cutthrough.peer_options |= OPTION_TLS; where = US"socketpair"; if (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) != 0) @@ -9034,7 +9031,7 @@ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only) if (pid == 0) /* child: will fork again to totally disconnect */ { - smtp_proxy_tls(cutthrough.cctx.tls_ctx, big_buffer, big_buffer_size, + smtp_proxy_tls(&cutthrough.cctx, big_buffer, big_buffer_size, pfd, 5*60, cutthrough.host.name); /* does not return */ } @@ -9046,8 +9043,7 @@ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only) } #endif - transport_do_pass_socket(cutthrough.transport, cutthrough.host.name, - cutthrough.host.address, cutthrough.host.port, message_id, channel_fd); + transport_do_pass_socket(message_id, channel_fd); } else { diff --git a/src/src/exim.c b/src/src/exim.c index c23eb868c..a50303049 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -405,13 +405,13 @@ Returns: nothing */ static void -milliwait(struct itimerval *itval) +milliwait(struct itimerval * itval) { sigset_t sigmask; sigset_t old_sigmask; int save_errno = errno; -if (itval->it_value.tv_usec < 50 && itval->it_value.tv_sec == 0) +if (itval->it_value.tv_usec < 50 && itval->it_value.tv_sec <= 0) return; (void)sigemptyset(&sigmask); /* Empty mask */ (void)sigaddset(&sigmask, SIGALRM); /* Add SIGALRM */ @@ -3000,7 +3000,16 @@ on the second character (the one after '-'), to save some effort. */ if (!continue_proxy_cipher) if (getsockname(0, (struct sockaddr *)(&tmp_sock), &size) == 0) - sending_ip_address = host_ntoa(-1, &tmp_sock, NULL, &sending_port); + switch (tmp_sock.v0.sa_family) + { + case AF_INET: + case AF_INET6: + sending_ip_address = + host_ntoa(-1, &tmp_sock, NULL, &sending_port); + break; + default: + exim_fail("non-INET socket on stdin for -MC option"); + } else exim_fail("getsockname() failed after -MC option: %s", strerror(errno)); diff --git a/src/src/functions.h b/src/src/functions.h index 73371cc7b..937259334 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -551,7 +551,7 @@ extern void smtp_log_no_mail(void); extern void smtp_message_code(uschar **, int *, uschar **, uschar **, BOOL); extern void smtp_notquit_exit(const uschar *, uschar *, const uschar *, ...); extern void smtp_port_for_connect(host_item *, int); -extern void smtp_proxy_tls(void *, uschar *, size_t, int *, int, const uschar *) NORETURN; +extern void smtp_proxy_tls(client_conn_ctx *, uschar *, size_t, int *, int, const uschar *) NORETURN; extern BOOL smtp_read_response(void *, uschar *, int, int, int); rmark smtp_reset(rmark); extern void smtp_respond(uschar *, int, BOOL, uschar *); @@ -662,8 +662,7 @@ extern uschar *tod_stamp(int); extern BOOL transport_check_waiting(const uschar *, const uschar *, int, uschar *, oicf, void*); extern uschar *transport_current_name(void); -extern void transport_do_pass_socket(const uschar *, const uschar *, - const uschar *, int, uschar *, int); +extern void transport_do_pass_socket(uschar *, int); extern void transport_init(void); extern const uschar *transport_rcpt_address(address_item *, BOOL); extern BOOL transport_set_up_command(const uschar ***, const uschar *, diff --git a/src/src/globals.c b/src/src/globals.c index 36f364996..38fdbc7a3 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -665,14 +665,8 @@ open_db *continue_wait_db = NULL; #endif uschar *csa_status = NULL; -cut_t cutthrough = { - .callout_hold_only = FALSE, /* verify-only: normal delivery */ - .delivery = FALSE, /* when to attempt */ - .tpt_sender = FALSE, /* use tpt's sender */ - .defer_pass = FALSE, /* on defer: spool locally */ - .is_tls = FALSE, /* not a TLS conn yet */ +cut_t cutthrough = { /* All remaining items 0/FALSE/NULL */ .cctx = {.sock = -1}, /* open connection */ - .nrcpt = 0, /* number of addresses */ }; int daemon_notifier_fd = -1; diff --git a/src/src/globals.h b/src/src/globals.h index 9f4d04a2d..1c133f5ea 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -474,11 +474,14 @@ extern open_db *continue_wait_db; /* Hintsdb for wait-transport */ extern uschar *csa_status; /* Client SMTP Authorization result */ typedef struct { - unsigned callout_hold_only:1; /* Conn is only for verify callout */ - unsigned delivery:1; /* When to attempt */ - unsigned tpt_sender:1; /* Use tpt-defined sender */ - unsigned defer_pass:1; /* Pass 4xx to caller rather than spooling */ - unsigned is_tls:1; /* Conn has TLS active */ + BOOL callout_hold_only:1; /* Conn is only for verify callout */ + BOOL delivery:1; /* When to attempt */ + BOOL tpt_sender:1; /* Use tpt-defined sender */ + BOOL defer_pass:1; /* Pass 4xx to caller rather than spooling */ + BOOL is_tls:1; /* Conn has TLS active */ + BOOL is_dane:1; + uschar * sni; + uschar * cipher; client_conn_ctx cctx; /* Open connection */ int nrcpt; /* Count of addresses */ uschar * transport; /* Name of transport */ diff --git a/src/src/transport.c b/src/src/transport.c index a59a5ae3d..5d3b8a732 100644 --- a/src/src/transport.c +++ b/src/src/transport.c @@ -2025,16 +2025,17 @@ retfalse: * Deliver waiting message down same socket * *************************************************/ -/* Just the regain-root-privilege exec portion */ +/* Just the regain-root-privilege exec portion. +The sole caller is delivery_re_exec(). */ + void -transport_do_pass_socket(const uschar * transport_name, const uschar * hostname, - const uschar * hostaddress, int hostport, uschar * id, int socket_fd) +transport_do_pass_socket(uschar * id, int socket_fd) { int i = 14; const uschar **argv; #ifndef DISABLE_TLS -if (smtp_peer_options & OPTION_TLS) i += 6; +if (cutthrough.peer_options & OPTION_TLS) i += 6; #endif #ifndef DISABLE_ESMTP_LIMITS if (continue_limit_mail || continue_limit_rcpt || continue_limit_rcptdom) @@ -2051,27 +2052,27 @@ but we have a number of extras that may be added. */ argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0); if (f.smtp_authenticated) argv[i++] = US"-MCA"; -if (smtp_peer_options & OPTION_CHUNKING) argv[i++] = US"-MCK"; -if (smtp_peer_options & OPTION_DSN) argv[i++] = US"-MCD"; -if (smtp_peer_options & OPTION_PIPE) argv[i++] = US"-MCP"; -if (smtp_peer_options & OPTION_SIZE) argv[i++] = US"-MCS"; +if (cutthrough.peer_options & OPTION_CHUNKING) argv[i++] = US"-MCK"; +if (cutthrough.peer_options & OPTION_DSN) argv[i++] = US"-MCD"; +if (cutthrough.peer_options & OPTION_PIPE) argv[i++] = US"-MCP"; +if (cutthrough.peer_options & OPTION_SIZE) argv[i++] = US"-MCS"; #ifndef DISABLE_TLS -if (smtp_peer_options & OPTION_TLS) - if (tls_out.active.sock >= 0 || continue_proxy_cipher) +if (cutthrough.peer_options & OPTION_TLS) + if (cutthrough.is_tls) { argv[i++] = US"-MCt"; - argv[i++] = sending_ip_address; - argv[i++] = string_sprintf("%d", sending_port); - argv[i++] = tls_out.active.sock >= 0 ? tls_out.cipher : continue_proxy_cipher; + argv[i++] = cutthrough.snd_ip; + argv[i++] = string_sprintf("%d", cutthrough.snd_port); + argv[i++] = cutthrough.cipher; - if (tls_out.sni) + if (cutthrough.sni) { argv[i++] = #ifdef SUPPORT_DANE - tls_out.dane_verified ? US"-MCr" : + cutthrough.is_dane ? US"-MCr" : #endif US"-MCs"; - argv[i++] = tls_out.sni; + argv[i++] = cutthrough.sni; } } else @@ -2107,11 +2108,11 @@ if (proxy_session) #endif argv[i++] = US"-MC"; -argv[i++] = US transport_name; -argv[i++] = US hostname; -argv[i++] = US hostaddress; -argv[i++] = string_sprintf("%d", hostport); -argv[i++] = string_sprintf("%d", continue_sequence + 1); +argv[i++] = US cutthrough.transport; +argv[i++] = US cutthrough.host.name; +argv[i++] = US cutthrough.host.address; +argv[i++] = string_sprintf("%d", cutthrough.host.port); +argv[i++] = string_sprintf("%d", continue_sequence + 1); /*XXX always 0+1 */ argv[i++] = id; argv[i++] = NULL; diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 36b6370fd..7160f0616 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -3881,7 +3881,7 @@ Do blocking full-size writes, and reads under a timeout. Once both input channels are closed, exit the process. Arguments: - ct_ctx tls context + ctx client context buf space to use for buffering bufsiz size of buffer pfd pipe filedescriptor array; [0] is comms to proxied process @@ -3892,11 +3892,12 @@ Does not return. */ void -smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd, +smtp_proxy_tls(client_conn_ctx * ctx, uschar * buf, size_t bsize, int * pfd, int timeout, const uschar * host) { -struct pollfd p[2] = {{.fd = tls_out.active.sock, .events = POLLIN}, +struct pollfd p[2] = {{.fd = ctx->sock, .events = POLLIN}, {.fd = pfd[0], .events = POLLIN}}; +void * tls_ctx = ctx->tls_ctx; int rc, i; BOOL send_tls_shutdown = TRUE; @@ -3930,7 +3931,7 @@ do if (p[0].revents & POLLERR || p[1].revents & POLLERR) { - DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n", + DEBUG(D_transport) debug_printf("poll: err cond on %s fd\n", p[0].revents & POLLERR ? "tls" : "proxy"); if (!(p[0].revents & POLLIN || p[1].events & POLLIN)) goto done; @@ -3941,7 +3942,7 @@ do /* handle inbound data */ if (p[0].revents & POLLIN) - if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0) /* Expect -1 for EOF; */ + if ((rc = tls_read(tls_ctx, buf, bsize)) <= 0) /* Expect -1 for EOF; */ { /* that reaps the TLS Close Notify record */ p[0].fd = -1; shutdown(pfd[0], SHUT_WR); @@ -3965,19 +3966,19 @@ do # ifdef EXIM_TCP_CORK /* Use _CORK to get TLS Close Notify in FIN segment */ (void) setsockopt(tls_out.active.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on)); # endif - tls_shutdown_wr(ct_ctx); + tls_shutdown_wr(tls_ctx); send_tls_shutdown = FALSE; shutdown(tls_out.active.sock, SHUT_WR); } else for (int nbytes = 0; rc - nbytes > 0; nbytes += i) - if ((i = tls_write(ct_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0) + if ((i = tls_write(tls_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0) goto done; } while (p[0].fd >= 0 || p[1].fd >= 0); done: - if (send_tls_shutdown) tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT); + if (send_tls_shutdown) tls_close(tls_ctx, TLS_SHUTDOWN_NOWAIT); testharness_pause_ms(100); /* let logging complete */ exim_exit(EXIT_SUCCESS); } @@ -5108,7 +5109,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit) if (pid == 0) /* child; fork again to disconnect totally */ { /* does not return */ - smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), + smtp_proxy_tls(&sx->cctx, sx->buffer, sizeof(sx->buffer), pfd, ob->command_timeout, host->name); } diff --git a/src/src/verify.c b/src/src/verify.c index 1fee97150..84134e49c 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -1157,7 +1157,12 @@ no_conn: ? "cutthrough delivery" : "potential further verifies and delivery"); cutthrough.callout_hold_only = !cutthrough.delivery; - cutthrough.is_tls = tls_out.active.sock >= 0; + if ((cutthrough.is_tls = tls_out.active.sock >= 0)) + { + cutthrough.is_dane = tls_out.sni && tls_out.dane_verified; + cutthrough.sni = tls_out.sni; + cutthrough.cipher = tls_out.cipher; + } /* We assume no buffer in use in the outblock */ cutthrough.cctx = sx->cctx; cutthrough.nrcpt = 1; commit 1fd121860e713f4d2783be54939b92a297a82dd4 Author: Jeremy Harris Date: Fri Nov 21 14:37:12 2025 +0000 fix non-DANE build Broken-by: d2353fcd8f60 diff --git a/src/src/verify.c b/src/src/verify.c index 84134e49c..18c53c1e8 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -1159,7 +1159,9 @@ no_conn: cutthrough.callout_hold_only = !cutthrough.delivery; if ((cutthrough.is_tls = tls_out.active.sock >= 0)) { +#ifdef SUPPORT_DANE cutthrough.is_dane = tls_out.sni && tls_out.dane_verified; +#endif cutthrough.sni = tls_out.sni; cutthrough.cipher = tls_out.cipher; } commit 9ecbb33982299a8d83a72d4dcd951949e5cfb81e Author: Jeremy Harris Date: Sun Nov 23 10:35:12 2025 +0000 Local-scan: bump API minor version Things like constification and ifdeffed-feature removal have, strictly, affected the API definition diff --git a/src/src/exim.c b/src/src/exim.c index a50303049..32ca1e1d2 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -1125,6 +1125,9 @@ g = string_cat(g, US"Support for:"); #ifdef WITH_CONTENT_SCAN g = string_cat(g, US" Content_Scanning"); #endif +#ifdef HAVE_LOCAL_SCAN + g = string_cat(g, US" Local_Scan"); +#endif #ifndef DISABLE_EXIM_FILTER g = string_cat(g, US" Exim_filter"); #endif @@ -1282,6 +1285,7 @@ DEBUG(D_any) #else g = string_cat(g, US"Compiler: \n"); #endif +/*XXX Sun Studio compiler? */ #if defined(__GLIBC__) && !defined(__UCLIBC__) g = string_fmt_append(g, "Library version: Glibc: Compile: %d.%d\n", @@ -1344,6 +1348,10 @@ Currently they are output in misc_mod_add() */ g = string_fmt_append(g, "TRUSTED_CONFIG_LIST: %q\n", TRUSTED_CONFIG_LIST); #else g = string_cat(g, US"TRUSTED_CONFIG_LIST unset\n"); +#endif +#ifdef HAVE_LOCAL_SCAN + g = string_cat(g, US"Local-Scan API: " + mac_expanded_string(LOCAL_SCAN_ABI_VERSION) "\n"); #endif } diff --git a/src/src/local_scan.h b/src/src/local_scan.h index 355487b0d..915b12007 100644 --- a/src/src/local_scan.h +++ b/src/src/local_scan.h @@ -44,7 +44,7 @@ each time a new feature is added (in a way that doesn't break backward compatibility). */ #define LOCAL_SCAN_ABI_VERSION_MAJOR 6 -#define LOCAL_SCAN_ABI_VERSION_MINOR 0 +#define LOCAL_SCAN_ABI_VERSION_MINOR 1 #define LOCAL_SCAN_ABI_VERSION \ LOCAL_SCAN_ABI_VERSION_MAJOR.LOCAL_SCAN_ABI_VERSION_MINOR commit 66a452cb6128ddc0805eac37230afd77d6fb168c Author: Jeremy Harris Date: Sun Nov 23 14:18:54 2025 +0000 Local-scan: fix API Broken-by: 7b4e2a15a529 diff --git a/src/src/functions.h b/src/src/functions.h index 937259334..76bc24c1b 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -238,19 +238,6 @@ extern int exp_bool(address_item *, extern BOOL expand_check_condition(const uschar *, const uschar *, const uschar *); extern uschar *expand_file_big_buffer(const uschar *); -extern const uschar *expand_string_2(const uschar *, BOOL *); - -static inline uschar * expand_nc_string(uschar * s) -{ return US expand_string_2(s, NULL); } -static inline const uschar * expand_c_string(const uschar * s) -{ return expand_string_2(s, NULL); } - -/* A macro that picks which function to use depending on the type of the arg */ -#define expand_string(X) _Generic((X), \ - uschar *: expand_nc_string, \ - const uschar *: expand_c_string \ - )(X) - extern BOOL expand_string_nonempty(const uschar *); extern uschar *expand_getkeyed(const uschar *, const uschar *); diff --git a/src/src/local_scan.h b/src/src/local_scan.h index 915b12007..5aff615e7 100644 --- a/src/src/local_scan.h +++ b/src/src/local_scan.h @@ -193,7 +193,19 @@ extern BOOL smtp_input; /* TRUE if input is via SMTP */ extern int child_close(pid_t, int); extern void debug_printf(const char *, ...) PRINTF_FUNCTION(1,2); -extern uschar *expand_string(uschar *); + +extern const uschar * expand_string_2(const uschar *, BOOL *); +static inline uschar * expand_nc_string(uschar * s) +{ return US expand_string_2(s, NULL); } +static inline const uschar * expand_c_string(const uschar * s) +{ return expand_string_2(s, NULL); } + +/* A macro that picks which function to use depending on the type of the arg */ +#define expand_string(X) _Generic((X), \ + uschar *: expand_nc_string, \ + const uschar *: expand_c_string \ + )(X) + extern void header_add(int, const char *, ...); extern void header_add_at_position(BOOL, uschar *, BOOL, int, const char *, ...); extern void header_remove(int, const uschar *); diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index e6c9dbacc..674bdd785 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -1798,6 +1798,9 @@ deliver_host = deliver_host_address = NULL; /* Can be set by ACL */ #ifdef SUPPORT_SRS srs_recipient = NULL; #endif +#ifdef HAVE_LOCAL_SCAN +local_scan_data = NULL; +#endif #ifdef WITH_CONTENT_SCAN regex_vars_clear(); malware_name = NULL; commit 5921ece54a48e3d773293b280722fb0e34a66539 Author: Jeremy Harris Date: Mon Nov 24 12:27:09 2025 +0000 Testsuite: add testcases for dbmjz/dbmnz under sqlite diff --git a/src/src/dbfn.c b/src/src/dbfn.c index 30015fe73..7c2dbcce3 100644 --- a/src/src/dbfn.c +++ b/src/src/dbfn.c @@ -403,10 +403,7 @@ unsigned dlen; memcpy(key_copy, key, klen); -/*XXX the %.*s will terminate early on a key with embedded NUL (legit for -lookup dbmjz). We do not have a convenient function; maybe extend -string_printing2() ? */ -DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: key=%.*s\n", klen, key); +DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: key=%.*W\n", klen, key); exim_datum_init(&key_datum); /* Some DBM libraries require the datum */ exim_datum_init(&result_datum); /* to be cleared before use. */ commit 89c878362b94d35fa662e02a8834238b699762d7 Author: Jeremy Harris Date: Sat Nov 29 12:51:38 2025 +0000 extend string-formatting facility diff --git a/src/src/exim_dbmbuild.c b/src/src/exim_dbmbuild.c index 99ea5c826..e3ef848d3 100644 --- a/src/src/exim_dbmbuild.c +++ b/src/src/exim_dbmbuild.c @@ -44,7 +44,7 @@ uschar * readconf_printtime(int t) { return NULL; } const uschar * expand_string_2(const uschar * string, BOOL * textonly_p) -{return NULL; } +{ return NULL; } void * store_get_3(int size, const void * proto_mem, const char *filename, int linenumber) { return NULL; } @@ -55,9 +55,6 @@ void store_release_above_3(void *ptr, const char *func, int linenumber) { } gstring * -string_catn(gstring * g, const uschar * s, int count) -{ return NULL; } -gstring * string_vformat_trc(gstring * g, const uschar * func, unsigned line, unsigned size_limit, unsigned flags, const char *format, va_list ap) { return NULL; } diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c index 5065b6e9b..30d446bad 100644 --- a/src/src/exim_dbutil.c +++ b/src/src/exim_dbutil.c @@ -62,9 +62,6 @@ uschar * readconf_printtime(int t) { return NULL; } const uschar * expand_string_2(const uschar * string, BOOL * textonly_p) -{return NULL; } -gstring * -string_catn(gstring * g, const uschar * s, int count) { return NULL; } gstring * string_vformat_trc(gstring * g, const uschar * func, unsigned line, diff --git a/src/src/string.c b/src/src/string.c index 474f4a1bb..472008382 100644 --- a/src/src/string.c +++ b/src/src/string.c @@ -15,6 +15,11 @@ utilities and tests, and are cut out by the COMPILE_UTILITY macro. */ #include +#ifdef COMPILE_UTILITY +BOOL print_topbitchars = FALSE; /* referenced by string_printing3() */ +#endif + + #ifndef COMPILE_UTILITY /************************************************* * Test for IP address * @@ -310,7 +315,6 @@ return ch; -#ifndef COMPILE_UTILITY /************************************************* * Ensure string is printable * *************************************************/ @@ -325,19 +329,20 @@ Arguments: flags Bit 0: convert tabs. Bit 1: convert spaces. Bit 2: convert doublequotes. + len if >= 0, max size of string + otherwise, NUL-terminated Returns: string with non-printers encoded as printing sequences */ const uschar * -string_printing2(const uschar * s, int flags) +string_printing3(const uschar * s, int flags, int len) { -int nonprintcount = 0; -int length = 0; -const uschar *t = s; -uschar *ss, *tt; +int nonprintcount = 0, olen = 0; +const uschar * t = s; +uschar * ss, * tt; -while (*t) +for (int n = len; n != 0 && *t; n--) { int c = *t++; if ( !mac_isprint(c) @@ -345,7 +350,7 @@ while (*t) || flags & SP_SPACE && c == ' ' || flags & SP_DQUOTES && c == '"' ) nonprintcount++; - length++; + olen++; } if (nonprintcount == 0) return s; @@ -353,13 +358,15 @@ if (nonprintcount == 0) return s; /* Get a new block of store guaranteed big enough to hold the expanded string. */ -tt = ss = store_get(length + nonprintcount * 3 + 1, s); +tt = ss = store_get(olen + nonprintcount * 3 + 1, s); /* Copy everything, escaping non printers. */ for (t = s; *t; ) { int c = *t; + /*XXX does \ go through unchanged here? Since we use it for escaping, + surely it should be doubled? */ if ( mac_isprint(c) && (!(flags & SP_TAB) || c != '\t') && (!(flags & SP_SPACE) || c != ' ') @@ -386,7 +393,12 @@ for (t = s; *t; ) *tt = 0; return ss; } -#endif /* COMPILE_UTILITY */ + +const uschar * +string_printing2(const uschar * s, int flags) +{ +return string_printing3(s, flags, -1); +} /************************************************* * Undo printing escapes in string * @@ -1215,6 +1227,7 @@ if (!store_extend(g->s, oldsize, g->size)) g->s = store_newblock(g->s, g->size, p); } +#endif /*!COMPILE_UTILITY*/ /************************************************* @@ -1290,6 +1303,7 @@ return g; +#ifndef COMPILE_UTILITY /************************************************* * Append strings to another string * *************************************************/ @@ -1410,15 +1424,15 @@ enum ltypes { L_NORMAL=1, L_SHORT=2, L_LONG=3, L_LONGLONG=4, L_LONGDOUBLE=5, L_S int width, precision, initial_off, lim, need; const char * fp = format; /* Deliberately not unsigned */ -string_datestamp_offset = -1; /* Datestamp not inserted */ -string_datestamp_length = 0; /* Datestamp not inserted */ -string_datestamp_type = 0; /* Datestamp not inserted */ - #ifdef COMPILE_UTILITY assert(!(flags & SVFMT_EXTEND)); assert(g); #else +string_datestamp_offset = -1; /* Datestamp not inserted */ +string_datestamp_length = 0; /* Datestamp not inserted */ +string_datestamp_type = 0; /* Datestamp not inserted */ + /* Ensure we have a string, to save on checking later */ if (!g) g = string_get(16); @@ -1532,7 +1546,7 @@ while (*fp) gp = CS g->s + g->ptr; } strncpy(newformat, item_start, fp - item_start); - newformat[fp - item_start] = 0; + newformat[fp - item_start] = '\0'; /* Short int is promoted to int when passing through ..., so we must use int for va_arg(). */ @@ -1566,7 +1580,7 @@ while (*fp) if ((ptr = va_arg(ap, void *))) { strncpy(newformat, item_start, fp - item_start); - newformat[fp - item_start] = 0; + newformat[fp - item_start] = '\0'; g->ptr += sprintf(gp, newformat, ptr); } else @@ -1595,7 +1609,7 @@ while (*fp) gp = CS g->s + g->ptr; } strncpy(newformat, item_start, fp - item_start); - newformat[fp-item_start] = 0; + newformat[fp-item_start] = '\0'; if (length == L_LONGDOUBLE) g->ptr += sprintf(gp, newformat, va_arg(ap, long double)); else @@ -1624,6 +1638,7 @@ while (*fp) g->s[g->ptr++] = (uschar) va_arg(ap, int); break; +#ifndef COMPILE_UTILITY case 'D': /* Insert daily datestamp for log file names */ s = CS tod_stamp(tod_log_datestamp_daily); string_datestamp_offset = g->ptr; /* Passed back via global */ @@ -1639,6 +1654,7 @@ while (*fp) string_datestamp_type = tod_log_datestamp_monthly; slen = string_datestamp_length; goto INSERT_STRING; +#endif case 'Y': /* gstring pointer */ { @@ -1782,12 +1798,14 @@ while (*fp) { s = ""; precision = slen = 6; } } goto INSERT_GSTRING; +#endif case 'q': /* string, to be wrapped in "" and with tab & " escaped */ if ((s = va_arg(ap, char *))) { gstring * zg = string_catn(NULL, US"\"", 1); - zg = string_cat(zg, string_printing2(US s, SP_TAB | SP_DQUOTES)); + zg = string_cat(zg, + string_printing3(US s, SP_TAB | SP_DQUOTES, precision)); zg = string_catn(zg, US"\"", 1); s = CS zg->s; precision = slen = gstring_length(zg); } @@ -1795,7 +1813,6 @@ while (*fp) { s = ""; precision = slen = 6; } goto INSERT_GSTRING; -#endif case 's': case 'S': /* Forces *lower* case */ case 'T': /* Forces *upper* case */ @@ -1876,7 +1893,7 @@ while (*fp) default: strncpy(newformat, item_start, fp - item_start); - newformat[fp-item_start] = 0; + newformat[fp-item_start] = '\0'; log_write_die(0, LOG_MAIN, "string_format: unsupported type " "in %q in %q", newformat, format); break; commit e4e66eda1683cfd3a6658b39c21be91a7ca99df0 Author: Jeremy Harris Date: Sat Nov 29 14:01:04 2025 +0000 local_scan: bump API version, for smtp_fflush() Broken-by: 9682a4923d1d diff --git a/src/src/local_scan.h b/src/src/local_scan.h index 5aff615e7..c9d179375 100644 --- a/src/src/local_scan.h +++ b/src/src/local_scan.h @@ -43,8 +43,8 @@ ABI is changed in a non backward compatible way. The minor number is increased each time a new feature is added (in a way that doesn't break backward compatibility). */ -#define LOCAL_SCAN_ABI_VERSION_MAJOR 6 -#define LOCAL_SCAN_ABI_VERSION_MINOR 1 +#define LOCAL_SCAN_ABI_VERSION_MAJOR 7 +#define LOCAL_SCAN_ABI_VERSION_MINOR 0 #define LOCAL_SCAN_ABI_VERSION \ LOCAL_SCAN_ABI_VERSION_MAJOR.LOCAL_SCAN_ABI_VERSION_MINOR commit 34b791b30d336dd78db0ded8c9b9790fdfa8f3c9 Author: Jeremy Harris Date: Sun Nov 16 15:23:56 2025 +0000 fix build with perl-spf diff --git a/src/scripts/lookups-Makefile b/src/scripts/lookups-Makefile index 6555e820c..7aa186e2e 100755 --- a/src/scripts/lookups-Makefile +++ b/src/scripts/lookups-Makefile @@ -158,7 +158,7 @@ sed -n "1,/$tag_marker/p" < "$input" for name_mod in \ CDB DBM:dbmdb DNSDB DSEARCH JSON LMDB LDAP LSEARCH MYSQL NIS NISPLUS \ - NMH ORACLE PASSWD PGSQL PSL REDIS SQLITE SPF TESTDB WHOSON + NMH ORACLE PASSWD PGSQL PSL REDIS SQLITE SPF SPF_PERL:spf TESTDB WHOSON do emit_module_rule $name_mod done diff --git a/src/src/miscmods/spf_perl.c b/src/src/miscmods/spf_perl.c index 33871c9aa..3008facc8 100644 --- a/src/src/miscmods/spf_perl.c +++ b/src/src/miscmods/spf_perl.c @@ -127,7 +127,7 @@ spf_lib_version_report(gstring * g) { /*XXX Does Mail::SPF have a version? MetaCPAN says yes, but does not document a method that returns it. */ -return string_fmt_append(g, "Library_version: SPF: perl Mail::SPF\n"); +return string_cat(g, US"Library_version: SPF: perl Mail::SPF\n"); } commit 21811ff3fd95a5325c665cb28b1d65e612a179dc Author: Jeremy Harris Date: Tue Nov 18 18:03:39 2025 +0000 Testsuite: fix for perl-spf dynamic build diff --git a/src/src/EDITME b/src/src/EDITME index a2dddd780..1c9d834dd 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -412,6 +412,7 @@ TRANSPORT_SMTP=yes # EXTRALIBS so that dlopen() is available to Exim. You need to define # LOOKUP_MODULE_DIR above so the exim binary actually loads dynamic lookup # modules. +# # Also, instead of adding all the libraries/includes to LOOKUP_INCLUDE and # LOOKUP_LIBS, add them to the respective LOOKUP_*_INCLUDE and LOOKUP_*_LIBS # (where * is the name as given here in this list). That ensures that only @@ -1170,6 +1171,7 @@ ZCAT_COMMAND=/usr/bin/zcat # SUPPORT_SPF=yes # CFLAGS += -I/usr/local/include # LDFLAGS += -lspf2 +# SUPPORT_SPF_LIBS= -lspf2 #------------------------------------------------------------------------------ diff --git a/src/src/drtables.c b/src/src/drtables.c index 9e25472d3..87ff8da61 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -185,9 +185,13 @@ if (info->magic != LOOKUP_MODULE_INFO_MAGIC) } addlookupmodule(info); -EARLY_DEBUG(D_lookup, "Loaded %q (%d lookup type%s)\n", +if (debug_startup) + { EARLY_DEBUG(D_lookup, "Loaded %q (%d lookup type%s)\n", name, info->lookupcount, - info->lookupcount > 1 ? "s" : ""); + info->lookupcount > 1 ? "s" : ""); } +else + DEBUG(D_lookup) debug_printf_indent("Loaded module %q\n", name); + return TRUE; } @@ -341,7 +345,8 @@ if ((mi = misc_mod_findonly(name))) return mi; #ifdef LOOKUP_MODULE_DIR return misc_mod_load(name, errstr); #else -*errstr = string_sprintf("module '%s' not found", name); +*errstr = string_sprintf("module %q not built-in, and" + "no setting for LOOKUP_MODULE_DIR", name); return NULL; #endif /*LOOKUP_MODULE_DIR*/ } @@ -470,7 +475,7 @@ else closedir(dd); } -EARLY_DEBUG(D_lookup, "Loaded %d lookup modules\n", countmodules); +EARLY_DEBUG(D_lookup, "Loaded %d dynamic lookup modules\n", countmodules); #endif } diff --git a/src/src/exim_dbmbuild.c b/src/src/exim_dbmbuild.c index e3ef848d3..8cb06fefe 100644 --- a/src/src/exim_dbmbuild.c +++ b/src/src/exim_dbmbuild.c @@ -55,6 +55,9 @@ void store_release_above_3(void *ptr, const char *func, int linenumber) { } gstring * +string_catn(gstring * g, const uschar * s, int count) +{ return NULL; } +gstring * string_vformat_trc(gstring * g, const uschar * func, unsigned line, unsigned size_limit, unsigned flags, const char *format, va_list ap) { return NULL; } diff --git a/src/src/lookups/json.c b/src/src/lookups/json.c index 3e5f9609e..92da9b9cb 100644 --- a/src/src/lookups/json.c +++ b/src/src/lookups/json.c @@ -157,8 +157,6 @@ json_close(void *handle) /* See local README for interface description. */ -#include "../version.h" - gstring * json_version_report(gstring * g) { diff --git a/src/src/lookups/ldap.c b/src/src/lookups/ldap.c index b73413a39..0a00633ab 100644 --- a/src/src/lookups/ldap.c +++ b/src/src/lookups/ldap.c @@ -1558,8 +1558,6 @@ return quoted; /* See local README for interface description. */ -#include "../version.h" - gstring * ldap_version_report(gstring * g) { diff --git a/src/src/lookups/lmdb.c b/src/src/lookups/lmdb.c index c90632cee..dc8bec3ba 100644 --- a/src/src/lookups/lmdb.c +++ b/src/src/lookups/lmdb.c @@ -128,8 +128,6 @@ mdb_env_close(db_env); * Version reporting entry point * *************************************************/ -#include "../version.h" - gstring * lmdb_version_report(gstring * g) { diff --git a/src/src/lookups/mysql.c b/src/src/lookups/mysql.c index 76954ca43..f28e9154f 100644 --- a/src/src/lookups/mysql.c +++ b/src/src/lookups/mysql.c @@ -467,8 +467,6 @@ return quoted; /* See local README for interface description. */ -#include "../version.h" - gstring * mysql_version_report(gstring * g) { diff --git a/src/src/lookups/pgsql.c b/src/src/lookups/pgsql.c index 8ddf0e058..7071cb4ab 100644 --- a/src/src/lookups/pgsql.c +++ b/src/src/lookups/pgsql.c @@ -472,8 +472,6 @@ return quoted; /* See local README for interface description. */ -#include "../version.h" - gstring * pgsql_version_report(gstring * g) { diff --git a/src/src/lookups/redis.c b/src/src/lookups/redis.c index a721d0605..61c264a73 100644 --- a/src/src/lookups/redis.c +++ b/src/src/lookups/redis.c @@ -428,7 +428,6 @@ return quoted; /************************************************* * Version reporting entry point * *************************************************/ -#include "../version.h" gstring * redis_version_report(gstring * g) diff --git a/src/src/lookups/spf.c b/src/src/lookups/spf.c index bfce04fa7..a8404d9bf 100644 --- a/src/src/lookups/spf.c +++ b/src/src/lookups/spf.c @@ -81,39 +81,33 @@ return FAIL; /* See local README for interface description. */ -#include "../version.h" - gstring * spf_version_report(gstring * g) { -#if EXIM_HAVE_SPF == 2 -return g; -#else -int maj, min, patch; - -SPF_get_lib_version(&maj, &min, &patch); -return string_fmt_append(g, "Library version: SPF: Runtime: %d.%d.%d\n", - maj, min, patch); -#endif +uschar * dummy_errmsg; +misc_module_info * mi = misc_mod_find(US"spf", &dummy_errmsg); +return mi && mi->lib_vers_report ? mi->lib_vers_report(g) : g; } + static lookup_info spf_lookup_info = { - .name = US"spf", /* lookup name */ - .type = 0, /* not absfile, not query style */ - .open = spf_open, /* open function */ - .check = NULL, /* no check function */ - .find = spf_find, /* find function */ - .close = spf_close, /* close function */ - .tidy = NULL, /* no tidy function */ - .quote = NULL, /* no quoting function */ - .version_report = spf_version_report /* version reporting */ + .name = US"spf", /* lookup name */ + .type = 0, /* not absfile, not query style */ + .open = spf_open, /* open function */ + .check = NULL, /* no check function */ + .find = spf_find, /* find function */ + .close = spf_close, /* close function */ + .tidy = NULL, /* no tidy function */ + .quote = NULL, /* no quoting function */ + .version_report = spf_version_report /* version reporting */ }; -#ifdef notdef_DYNLOOKUP -#define spf_lookup_module_info _lookup_module_info +#ifdef DYNLOOKUP +# define spf_lookup_module_info _lookup_module_info #endif static lookup_info *_lookup_list[] = { &spf_lookup_info }; -lookup_module_info spf_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 }; +lookup_module_info spf_lookup_module_info = + { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 }; diff --git a/src/src/lookups/sqlite.c b/src/src/lookups/sqlite.c index e358c9f84..eb23b8631 100644 --- a/src/src/lookups/sqlite.c +++ b/src/src/lookups/sqlite.c @@ -163,8 +163,6 @@ return quoted; /* See local README for interface description. */ -#include "../version.h" - gstring * sqlite_version_report(gstring * g) { diff --git a/src/src/lookups/whoson.c b/src/src/lookups/whoson.c index f783a905a..dcd48db2d 100644 --- a/src/src/lookups/whoson.c +++ b/src/src/lookups/whoson.c @@ -63,8 +63,6 @@ switch (wso_query(CS query, CS buffer, sizeof(buffer))) /* See local README for interface description. */ -#include "../version.h" - gstring * whoson_version_report(gstring * g) { diff --git a/src/src/miscmods/perl.c b/src/src/miscmods/perl.c index 2220c7dc9..41d946735 100644 --- a/src/src/miscmods/perl.c +++ b/src/src/miscmods/perl.c @@ -131,7 +131,8 @@ s = US SvPV(ST(0), len); log_write(0, LOG_MAIN, "%.*s", (int)len, s); } -/* Do a DNS lookup using Exim's facilities. Returns a scalar with the response packet. */ +/* Do a DNS lookup using Exim's facilities. +Returns a scalar with the response packet; undef for DNS_FAIL or DNS_AGAIN. */ XS(xs_dns_lookup) { @@ -154,8 +155,10 @@ debug_printf_indent(" dnsa answer %p len %d\n", dnsa->answer, dnsa->answerlen); */ ST(0) = sv_newmortal(); -sv_setpvn(ST(0), CCS dnsa->answer, - (STRLEN) (dns_res == DNS_NODATA ? 0 : dnsa->answerlen)); +if (dns_res == DNS_AGAIN || dns_res == DNS_FAIL) + sv_setsv(ST(0), &PL_sv_undef); +else + sv_setpvn(ST(0), CCS dnsa->answer, (STRLEN) dnsa->answerlen); XSRETURN(1); /* ? needed because there are 2 arg, but 1 res? */ store_free_dns_answer(dnsa); @@ -222,9 +225,11 @@ errstr = exim_perl_add_codeblock(US "\"TLSA\" => 52," "\"SPF\" => 99," "};" - "my $rrtype = $rr->{$rrtype_str};" /*XXX only one rrtype per query...*/ + /*XXX only one rrtype per query...*/ + "my $rrtype = $rr->{$rrtype_str};" "my $dnsa = Exim::dns_lookup($dom, $rrtype);" - "my $res = Net::DNS::Packet->decode( \\$dnsa );" + "my $res;" + "my $res = Net::DNS::Packet->decode(\\$dnsa) unless (!defined($dnsa));" /* "Exim::debug_write( $res->string . '\n' );" */ "return $res;" "}" diff --git a/src/src/miscmods/spf_perl.c b/src/src/miscmods/spf_perl.c index 3008facc8..17eb3ce35 100644 --- a/src/src/miscmods/spf_perl.c +++ b/src/src/miscmods/spf_perl.c @@ -212,7 +212,7 @@ else sep = '\n'; spf_result = string_nextinlist(CUSS &res_list, &sep, NULL, 0); - DEBUG(D_acl) debug_printf_indent("SPF result is %s\n", spf_received); + DEBUG(D_acl) debug_printf_indent("MAIL::SPF result is %q\n", spf_received); spf_received = res_list; /* remainder of the returned string */ @@ -329,9 +329,9 @@ spf_lookup_find(void * handle, const uschar * filename, { int res = FAIL; -expand_level++; DEBUG(D_acl) debug_printf_indent("%s: mfrom:<%s> ip %q\n", __FUNCTION__, keystring, filename); +expand_level++; if (setup_spf_perl_mi()) if (!filename) @@ -346,11 +346,12 @@ if (setup_spf_perl_mi()) uschar * res_list = US string_from_gstring(g); int sep = '\n'; *result = string_nextinlist(CUSS &res_list, &sep, NULL, 0); - DEBUG(D_acl) debug_printf_indent("SPF result is %s\n", *result); + DEBUG(D_acl) debug_printf_indent("MAIL::SPF result is %q\n", *result); res = OK; } } +expand_level--; return res; } commit 6096b50a869de851b710262dcc479f61e68c1bc7 Author: Jeremy Harris Date: Tue Nov 18 18:11:10 2025 +0000 dmarc-native: fix rua/ruf verify diff --git a/src/src/drtables.c b/src/src/drtables.c index 87ff8da61..45b1ade16 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -346,7 +346,7 @@ if ((mi = misc_mod_findonly(name))) return mi; return misc_mod_load(name, errstr); #else *errstr = string_sprintf("module %q not built-in, and" - "no setting for LOOKUP_MODULE_DIR", name); + " no setting for LOOKUP_MODULE_DIR", name); return NULL; #endif /*LOOKUP_MODULE_DIR*/ } diff --git a/src/src/miscmods/dmarc_common.c b/src/src/miscmods/dmarc_common.c index 059b31167..d6e27c892 100644 --- a/src/src/miscmods/dmarc_common.c +++ b/src/src/miscmods/dmarc_common.c @@ -11,9 +11,6 @@ #ifdef EXIM_HAVE_DMARC -// # include "dmarc.h" -// # include "pdkim.h" - extern BOOL dmarc_local_init(void); extern void dmarc_local_msg_init(void); extern gstring * dmarc_version_report(gstring *); diff --git a/src/src/miscmods/dmarc_native.c b/src/src/miscmods/dmarc_native.c index 617517d84..d073e4274 100644 --- a/src/src/miscmods/dmarc_native.c +++ b/src/src/miscmods/dmarc_native.c @@ -43,7 +43,7 @@ if (!dmarc_regex_uri) dmarc_regex_uri = regex_must_compile(US "^mailto:[^@]+@[^ !]+(?:[ !]|$)", MCS_CACHEABLE, FALSE); if (!dmarc_regex_pct) - dmarc_regex_uri = regex_must_compile(US "^\\d{1,3}$", MCS_CACHEABLE, FALSE); + dmarc_regex_pct = regex_must_compile(US "^\\d{1,3}$", MCS_CACHEABLE, FALSE); if (!dmarc_regex_ri) dmarc_regex_ri = regex_must_compile(US "^\\d{1,10}$", MCS_CACHEABLE, FALSE); if (!dmarc_regex_fo) @@ -56,13 +56,6 @@ if (!dmarc_regex_fo) gstring * dmarc_version_report(gstring * g) { -/* -return string_fmt_append(g, "Library version: dmarc: Compile: %d.%d.%d.%d\n", - (OPENDMARC_LIB_VERSION & 0xff000000) >> 24, - (OPENDMARC_LIB_VERSION & 0x00ff0000) >> 16, - (OPENDMARC_LIB_VERSION & 0x0000ff00) >> 8, - (OPENDMARC_LIB_VERSION & 0x000000ff)); -*/ return string_fmt_append(g, "Library version: dmarc: Exim %s builtin\n", EXIM_VERSION_STR); } commit 2a87c1fb9d76cd00cc1815c9b56b8bea47e409e1 Author: Jeremy Harris Date: Sun Nov 30 19:41:43 2025 +0000 perl-spf: fix undef whine diff --git a/src/src/miscmods/perl.c b/src/src/miscmods/perl.c index 41d946735..1b111e711 100644 --- a/src/src/miscmods/perl.c +++ b/src/src/miscmods/perl.c @@ -77,6 +77,7 @@ if (SvTRUE(ERRSV)) STRLEN len; s = US SvPV(ERRSV, len); s = string_copyn(s, (unsigned)len); + debug_printf_indent("adding perl codeblock: %s\n", s); } setlocale(LC_ALL, "C"); /* In case it got changed */ @@ -228,9 +229,13 @@ errstr = exim_perl_add_codeblock(US /*XXX only one rrtype per query...*/ "my $rrtype = $rr->{$rrtype_str};" "my $dnsa = Exim::dns_lookup($dom, $rrtype);" + "my $res;" - "my $res = Net::DNS::Packet->decode(\\$dnsa) unless (!defined($dnsa));" - /* "Exim::debug_write( $res->string . '\n' );" */ + "$res = new Net::DNS::Packet(\\$dnsa) if (defined($dnsa));" + + /*XXX dumb, but at least clears the undef on errorstring */ + "$self->errorstring(defined($dnsa) ? 'ok' : 'timeout');" + "return $res;" "}" "package MAIN;" diff --git a/src/src/miscmods/spf_perl.c b/src/src/miscmods/spf_perl.c index 17eb3ce35..4e854d7af 100644 --- a/src/src/miscmods/spf_perl.c +++ b/src/src/miscmods/spf_perl.c @@ -61,9 +61,10 @@ static const uschar spf_pl[] = "use Mail::SPF;" "sub my_spf_req {" "my ($mfrom, $conn_addr, $conn_helo) = @_;" + "my ($id, $sc) = ($mfrom ne '') ? ($mfrom, 'mfrom') : ($conn_helo, 'helo');" "my $request = Mail::SPF::Request->new(" - "scope => 'mfrom'," - "identity => $mfrom," + "scope => $sc," + "identity => $id," "ip_address => $conn_addr," "helo_identity => $conn_helo" ");" @@ -80,21 +81,19 @@ spf lookup routine to it. Safely does nothing if called again. */ static const misc_module_info * -setup_spf_perl_mi(void) +setup_spf_perl_mi(uschar ** errstr) { typedef uschar * (*fn_t)(const uschar *); if (!spf_perl_mi) { if (!(spf_perl_mi = perl_startup(opt_perl_startup ? opt_perl_startup : US""))) - /* errstr = string_sprintf("spf: %s", expand_string_message); */ + { + *errstr = string_sprintf("spf: %s", expand_string_message); return NULL; + } - /*XXX could return an error string here: - if ((errstr = (((fn_t *) spf_perl_mi->functions)[PERL_ADDBLOCK]) (spf_pl))) - */ - - if ((((fn_t *) spf_perl_mi->functions)[PERL_ADDBLOCK]) (spf_pl)) + if ((*errstr = (((fn_t *) spf_perl_mi->functions)[PERL_ADDBLOCK]) (spf_pl))) return spf_perl_mi = NULL; } return spf_perl_mi; @@ -187,14 +186,18 @@ spf_process(const uschar ** listptr, const uschar * spf_envelope_sender, int action) { int res = FAIL, sep; +uschar * errstr; const uschar * arglist = *listptr; expand_level++; DEBUG(D_acl) debug_printf_indent("%s: mfrom:<%s>\n", __FUNCTION__, spf_envelope_sender); -if (!setup_spf_perl_mi()) +if (!setup_spf_perl_mi(&errstr)) + { + expand_level--; return FAIL; + } if (!(conn_helo && conn_addr)) spf_result = US"permerror"; @@ -327,13 +330,14 @@ spf_lookup_find(void * handle, const uschar * filename, const uschar * keystring, int key_len, uschar ** result, uschar ** errmsg, uint * do_cache, const uschar * opts) { +uschar * errstr; int res = FAIL; DEBUG(D_acl) debug_printf_indent("%s: mfrom:<%s> ip %q\n", __FUNCTION__, keystring, filename); expand_level++; -if (setup_spf_perl_mi()) +if (setup_spf_perl_mi(&errstr)) if (!filename) *result = US"permerror"; else commit 1b88acb34288b0858d0f26c1ab2ebc752d046595 Author: Jeremy Harris Date: Mon Dec 1 18:17:46 2025 +0000 Fix nonstandard DNS server port number use from perl. Bug 3177 Broken-by: 040f2adb9003 diff --git a/src/src/miscmods/perl.c b/src/src/miscmods/perl.c index 1b111e711..cee920c77 100644 --- a/src/src/miscmods/perl.c +++ b/src/src/miscmods/perl.c @@ -200,19 +200,26 @@ perl_parse(interp_perl, xs_init, argc, argv, 0); perl_run(interp_perl); /*********************************************************************/ +errstr = exim_perl_add_codeblock(US + /* These lines by PH added to make "warn" output go to the Exim log; I hope this doesn't break anything. */ -errstr = exim_perl_add_codeblock(US "$SIG{__WARN__} = sub { my($s) = $_[0];" "$s =~ s/\\n$//;" "Exim::log_write($s) };" -/* These lines added by JGH to route DNS queries via Exim's facilities */ +/* These lines added by JGH to route DNS queries via Exim's facilities. +If a port was specified, we punt. +*/ "package Net::DNS::Resolver;" "sub send {" - "my ( $self, $dom, $rrtype_str ) = @_;" + "my $self = shift;" + + "return $self->SUPER::send(@_) if ($self->{'port'} != 53);" + + "my ( $dom, $rrtype_str ) = @_;" "my $rr = {" "\"A\" => 1," "\"NS\" => 2," commit 5f52bd307cccd1a840ab4e0b761add7b3d1946cd Author: Jeremy Harris Date: Mon Dec 1 22:43:53 2025 +0000 Build: Solaris compilers diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c index 30d446bad..42396fe8b 100644 --- a/src/src/exim_dbutil.c +++ b/src/src/exim_dbutil.c @@ -64,6 +64,9 @@ readconf_printtime(int t) const uschar * expand_string_2(const uschar * string, BOOL * textonly_p) { return NULL; } gstring * +string_catn(gstring * g, const uschar * s, int count) +{ return NULL; } +gstring * string_vformat_trc(gstring * g, const uschar * func, unsigned line, unsigned size_limit, unsigned flags, const char *format, va_list ap) { return NULL; } commit 29b229ef956c08381bb5514a4da3fecf89148441 Author: Jeremy Harris Date: Tue Dec 2 11:19:49 2025 +0000 Build: GCC on Solaris diff --git a/src/OS/os.h-SunOS5 b/src/OS/os.h-SunOS5 index f7ad50421..389b3403b 100644 --- a/src/OS/os.h-SunOS5 +++ b/src/OS/os.h-SunOS5 @@ -2,6 +2,13 @@ /* Copyright (c) The Exim Maintainers 2021 - 2025 */ /* SPDX-License-Identifier: GPL-2.0-or-later */ +/* The Sun Studio compilers define these, but GCC does not. +Adjust if needed. */ +#if !defined(__SunOS_5_10) && !defined(__SunOS_5_11) +# define __SunOS_5_11 +#endif + + #define CRYPT_H #define HAVE_MMAP #define HAVE_SYS_STATVFS_H @@ -35,6 +42,11 @@ it seems. */ /* default is non-const */ #define ICONV_ARG2_TYPE const char ** +/* A GCC user reported that the following was needed. Possibly using +Glibc iconv? + #define ICONV_ARG2_TYPE char ** restrict +A way of spotting the difference would be useful. +*/ #if _POSIX_C_SOURCE + 0 < 200112L # define MISSING_UNSETENV_3 commit 9536defc5cb25beeb00491f6b00c454aadfdb7a5 Author: Jeremy Harris Date: Tue Dec 2 22:59:04 2025 +0000 SPF: logging diff --git a/src/src/functions.h b/src/src/functions.h index 76bc24c1b..873b8c8be 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -107,6 +107,7 @@ extern uschar *acl_standalone_setvar(const uschar *, BOOL); extern tree_node *acl_var_create(const uschar *); extern void acl_var_write(uschar *, uschar *, void *); extern void add_driver_info(driver_info **, const driver_info *, size_t); +extern gstring * add_spf_info_for_log(gstring *); extern void assert_no_variables(void *, int, const char *, int); extern void atrn_handle_customer(void); diff --git a/src/src/globals.c b/src/src/globals.c index 38fdbc7a3..be5afe736 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -1021,6 +1021,10 @@ bit_table log_options[] = { /* must be in alphabetical order, BIT_TABLE(L, smtp_no_mail), BIT_TABLE(L, smtp_protocol_error), BIT_TABLE(L, smtp_syntax_error), +#ifdef EXIM_HAVE_SPF + BIT_TABLE(L, spf), + BIT_TABLE(L, spf_verbose), +#endif BIT_TABLE(L, subject), BIT_TABLE(L, tls_certificate_verified), BIT_TABLE(L, tls_cipher), diff --git a/src/src/macros.h b/src/src/macros.h index c0900eee9..ad5d73267 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -453,7 +453,7 @@ enum logbit { LOG_BIT(dnslist_defer), LOG_BIT(etrn), LOG_BIT(host_lookup_failed), - LOG_BIT(lost_incoming_connection), + LOG_BIT(lost_incoming_connection), /* 7 */ LOG_BIT(queue_run), LOG_BIT(retry_defer), LOG_BIT(size_reject), @@ -461,7 +461,7 @@ enum logbit { LOG_BIT(smtp_connection), LOG_BIT(smtp_incomplete_transaction), LOG_BIT(smtp_protocol_error), - LOG_BIT(smtp_syntax_error), + LOG_BIT(smtp_syntax_error), /* 15 */ Li_8bitmime = BITWORDSIZE, Li_acl_warn_skipped, @@ -497,6 +497,8 @@ enum logbit { Li_smtp_confirmation, Li_smtp_mailauth, Li_smtp_no_mail, + Li_spf, + Li_spf_verbose, Li_subject, Li_tls_certificate_verified, Li_tls_cipher, diff --git a/src/src/receive.c b/src/src/receive.c index 0f407ce93..a0fcffd06 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -1361,6 +1361,21 @@ DEBUG(D_receive|D_acl) debug_printf_indent(">>\n"); * Add host information for log line * *************************************************/ +gstring * +add_spf_info_for_log(gstring * g) +{ +#ifdef EXIM_HAVE_SPF +if (LOGGING(spf_verbose)) + { + const uschar * s = expand_string(CUS"$spf_result"); + if (*s) g = string_append(g, 2, US" SPF=", s); + } +else if (LOGGING(spf) && Ustrcmp(expand_string(CUS"$spf_result"), "pass") == 0) + g = string_catn(g, US" SPF", 4); +#endif +return g; +} + /* Called for acceptance and rejecting log lines. This adds information about the calling host to a string that is being built dynamically. @@ -1407,6 +1422,8 @@ if (LOGGING(pipelining) && f.smtp_in_pipelining_advertised) if (!f.smtp_in_pipelining_used) g = string_catn(g, US"-", 1); } + +g = add_spf_info_for_log(g); return g; } diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 674bdd785..6cfc36f7a 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -3166,7 +3166,7 @@ switch (where) #ifdef WITH_CONTENT_SCAN case ACL_WHERE_MIME: #endif - sender_info = string_sprintf("F=<%s>%s%s%s%s ", + sender_info = string_sprintf(" F=<%s>%s%s%s%s", sender_address_unrewritten ? sender_address_unrewritten : sender_address, sender_host_authenticated ? US" A=" : US"", sender_host_authenticated ? sender_host_authenticated : US"", @@ -3266,18 +3266,23 @@ is closing if required and return 2. */ if (log_reject_target) { #ifndef DISABLE_TLS - gstring * g = add_tls_info_for_log(NULL); - uschar * tls = string_from_gstring(g); - if (!tls) tls = US""; + gstring * tls = add_tls_info_for_log(NULL); #else - uschar * tls = US""; + gstring * tls = NULL; #endif +#ifdef EXIM_HAVE_SPF + gstring * spf= add_spf_info_for_log(NULL); +#else + gstring * spf = NULL; +#endif + log_write(where == ACL_WHERE_CONNECT ? L_connection_reject : 0, - log_reject_target, "%s%s%s %s%srejected %s%s", + log_reject_target, "%s%s%#Y%s%#Y %srejected %s%s", LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"", host_and_ident(TRUE), tls, sender_info, + spf, rc == FAIL ? US"" : US"temporarily ", what, log_msg); } commit b8d16e2a4e8e2016e8cd03402705a6f47f1e2b5b Author: Jeremy Harris Date: Wed Dec 3 12:16:59 2025 +0000 SPF: support * in ACL condition diff --git a/src/src/miscmods/spf.c b/src/src/miscmods/spf.c index f73a8776b..4febe7f4e 100644 --- a/src/src/miscmods/spf.c +++ b/src/src/miscmods/spf.c @@ -373,10 +373,7 @@ static int spf_process(const uschar ** listptr, const uschar * spf_envelope_sender, int action) { -int sep = 0; -const uschar * list = * listptr; -uschar * spf_result_id; -int rc = SPF_RESULT_PERMERROR, ret = OK; +int rc = SPF_RESULT_PERMERROR, ret; DEBUG(D_receive) { debug_printf_indent("SPF: process\n"); expand_level++; } @@ -419,24 +416,13 @@ if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none"))) else { - while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0))) - { - BOOL negate, result; - - if ((negate = spf_result_id[0] == '!')) - spf_result_id++; - - result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0; - if (negate != result) goto out; - } - - /* no match */ - ret = FAIL; + const uschar * list = *listptr; + ret = match_isinlist(spf_result_id_list[rc].name, &list, + 0, NULL, NULL, MCL_STRING, TRUE, NULL); } -out: - DEBUG(D_receive) expand_level--; - return ret; +DEBUG(D_receive) expand_level--; +return ret; } diff --git a/src/src/miscmods/spf_perl.c b/src/src/miscmods/spf_perl.c index 4e854d7af..55741eef0 100644 --- a/src/src/miscmods/spf_perl.c +++ b/src/src/miscmods/spf_perl.c @@ -35,9 +35,6 @@ static spf_result_id spf_result_id_list[] = { { US"permerror", 7 } /* RFC 4408 defined */ }; -const uschar * conn_helo = NULL; -const uschar * conn_addr = NULL; - uschar * spf_guess = US"v=spf1 a/24 mx/24 ptr ?all"; uschar * spf_header_comment = NULL; uschar * spf_received = NULL; @@ -131,34 +128,6 @@ return string_cat(g, US"Library_version: SPF: perl Mail::SPF\n"); -/*API*/ -/* Set up a context that can be re-used for several - messages on the same SMTP connection (that come from the - same host with the same HELO string). - -We delay doing perl startup until spf processing time, as ACL might -never need us on any given connection. - -Return: OK/FAIL -*/ - -static int -spf_conn_init(const uschar * spf_helo_domain, const uschar * spf_remote_addr, - const uschar ** errstr) -{ -DEBUG(D_receive) debug_printf_indent("spf_conn_init: helo:%s addr:%s\n", - spf_helo_domain, spf_remote_addr); - -/* Copy the args to globals */ - -conn_helo = spf_helo_domain; -conn_addr = spf_remote_addr; - -return OK; -} - - - /*API*/ static void spf_smtp_reset(void) @@ -185,7 +154,7 @@ static int spf_process(const uschar ** listptr, const uschar * spf_envelope_sender, int action) { -int res = FAIL, sep; +int res, sep; uschar * errstr; const uschar * arglist = *listptr; @@ -199,17 +168,18 @@ if (!setup_spf_perl_mi(&errstr)) return FAIL; } -if (!(conn_helo && conn_addr)) +if (!(sender_helo_name && sender_host_address)) spf_result = US"permerror"; else { - const uschar * argv[4] = {spf_envelope_sender, conn_addr, conn_helo, NULL}; + const uschar * argv[4] = {spf_envelope_sender, sender_host_address, + sender_helo_name, NULL}; gstring * g; uschar * res_list, * s; if (!(g = call_my_spf_req(argv))) - goto out; + { res = FAIL; goto out; } res_list = US string_from_gstring(g); sep = '\n'; @@ -228,18 +198,8 @@ else } } -sep = 0; -for (uschar * ele; ele = string_nextinlist(&arglist, &sep, NULL, 0); ) - { - BOOL negate, result; - - if ((negate = *ele == '!')) - ele++; - - result = Ustrcmp(ele, spf_result) == 0; - if (negate != result) { res = OK; break; } - } -/* if the loop ran out of list, no match */ +res = match_isinlist(spf_result, &arglist, + 0, NULL, NULL, MCL_STRING, TRUE, NULL); out: expand_level--; @@ -342,7 +302,7 @@ if (setup_spf_perl_mi(&errstr)) *result = US"permerror"; else { - const uschar * argv[4] = {keystring, filename, conn_helo, NULL}; + const uschar * argv[4] = {keystring, filename, sender_helo_name, NULL}; gstring * g; if ((g = call_my_spf_req(argv))) @@ -393,7 +353,7 @@ misc_module_info spf_module_info = .dyn_magic = MISC_MODULE_MAGIC, # endif .lib_vers_report = spf_lib_version_report, - .conn_init = spf_conn_init, + .conn_init = NULL, .smtp_reset = spf_smtp_reset, .authres = authres_spf, commit 266b6e714cf4a95d412a81197516e6dcb1454214 Author: Jeremy Harris Date: Wed Dec 3 14:10:50 2025 +0000 Debug: redefine the early-debug macro to be also active after main-debug init diff --git a/src/src/macros.h b/src/src/macros.h index ad5d73267..0b69bb83f 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -106,11 +106,10 @@ don't make the file descriptors two-way. */ #define HDEBUG(x) if (host_checking || IS_DEBUG(x)) #define EARLY_DEBUG(x, fmt, ...) \ - if (debug_startup) \ - if (debug_fd < 0) \ - fprintf(stderr, "%s", string_sprintf(fmt, __VA_ARGS__)); \ - else DEBUG(x) \ - debug_printf_indent(fmt, __VA_ARGS__); + if (debug_fd >= 0) \ + { DEBUG(x) debug_printf_indent(fmt, __VA_ARGS__); } \ + else if (debug_startup) \ + fprintf(stderr, "%s", string_sprintf(fmt, __VA_ARGS__)); /* The default From: text for DSNs */ diff --git a/src/src/readconf.c b/src/src/readconf.c index 04cd5e68a..37d9c6b85 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -4592,7 +4592,7 @@ readconf_rest(void) { int had = 0; -while(next_section[0] != 0) +while(*next_section) { int bit; int first = 0; @@ -4601,6 +4601,7 @@ while(next_section[0] != 0) int n = Ustrlen(next_section); EARLY_DEBUG(D_any, "%s: %s\n", __FUNCTION__, next_section); + expand_level++; if (tolower(next_section[n-1]) != 's') Ustrcpy(next_section+n, US"s"); for (;;) @@ -4630,6 +4631,7 @@ while(next_section[0] != 0) case 5: route_init(); break; case 6: transport_init(); break; } + expand_level--; } (void)fclose(config_file); commit c5d27945075095ee56c92eee36a81aa239a53494 Author: Jeremy Harris Date: Wed Dec 3 15:06:10 2025 +0000 DMARC: logging diff --git a/src/src/functions.h b/src/src/functions.h index 873b8c8be..a15369af9 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -20,7 +20,6 @@ are in in fact in separate headers. */ #ifndef DISABLE_TLS -extern gstring * add_tls_info_for_log(gstring *); extern const char * std_dh_prime_default(void); extern const char * std_dh_prime_named(const uschar *); @@ -106,8 +105,10 @@ extern uschar *acl_standalone_setvar(const uschar *, BOOL); extern tree_node *acl_var_create(const uschar *); extern void acl_var_write(uschar *, uschar *, void *); +extern gstring * add_dmarc_info_for_log(gstring *); extern void add_driver_info(driver_info **, const driver_info *, size_t); extern gstring * add_spf_info_for_log(gstring *); +extern gstring * add_tls_info_for_log(gstring *); extern void assert_no_variables(void *, int, const char *, int); extern void atrn_handle_customer(void); diff --git a/src/src/globals.c b/src/src/globals.c index be5afe736..610c72909 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -979,6 +979,10 @@ bit_table log_options[] = { /* must be in alphabetical order, #ifndef DISABLE_DKIM BIT_TABLE(L, dkim), BIT_TABLE(L, dkim_verbose), +#endif +#ifdef EXIM_HAVE_DMARC + BIT_TABLE(L, dmarc), + BIT_TABLE(L, dmarc_verbose), #endif BIT_TABLE(L, dnslist_defer), BIT_TABLE(L, dnssec), diff --git a/src/src/macros.h b/src/src/macros.h index 0b69bb83f..6c7d8b085 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -470,6 +470,8 @@ enum logbit { Li_delivery_size, Li_dkim, Li_dkim_verbose, + Li_dmarc, + Li_dmarc_verbose, Li_dnssec, Li_ident_timeout, Li_incoming_interface, diff --git a/src/src/miscmods/dmarc.c b/src/src/miscmods/dmarc.c index be5d68c3a..8d840ae84 100644 --- a/src/src/miscmods/dmarc.c +++ b/src/src/miscmods/dmarc.c @@ -431,7 +431,8 @@ The EDITME provides a DMARC_API variable */ dmarc_alignment_dkim = dmarc_dkim_alignment == DMARC_POLICY_DKIM_ALIGNMENT_PASS; - log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s " + if (LOGGING(dmarc_verbose)) + log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s " "spf_align=%s dkim_align=%s enforcement='%s'", spf_sender_domain, dmarc_used_domain, dmarc_alignment_spf ? "yes" : "no", diff --git a/src/src/miscmods/dmarc_native.c b/src/src/miscmods/dmarc_native.c index d073e4274..02eb5dbd3 100644 --- a/src/src/miscmods/dmarc_native.c +++ b/src/src/miscmods/dmarc_native.c @@ -628,14 +628,12 @@ use the sp. Otherwise use the p. */ } } - // the alignments would be results of the DMARC evaluation - // - we have them already, from the dkim & spf processing - if (has_dmarc_record && !dmarc_abort) { - /* Log results. Robably should have a log_selector to reduce noise. */ + /* Log results. */ - log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s " + if (LOGGING(dmarc_verbose)) + log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s " "spf_align=%s dkim_align=%s enforcement='%s'", spf_sender_domain, dmarc_used_domain, dmarc_alignment_spf ? "yes" : "no", diff --git a/src/src/receive.c b/src/src/receive.c index a0fcffd06..7827d0288 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -1376,6 +1376,21 @@ else if (LOGGING(spf) && Ustrcmp(expand_string(CUS"$spf_result"), "pass") == 0) return g; } +gstring * +add_dmarc_info_for_log(gstring * g) +{ +#ifdef EXIM_HAVE_DMARC +if (LOGGING(dmarc_verbose)) + { + const uschar * s = expand_string(CUS"$dmarc_status"); + if (*s) g = string_append(g, 2, US" DMARC=", s); + } +else if (LOGGING(dmarc) && Ustrcmp(expand_string(CUS"$dmarc_status"), "accept") == 0) + g = string_catn(g, US" DMARC", 6); +#endif +return g; +} + /* Called for acceptance and rejecting log lines. This adds information about the calling host to a string that is being built dynamically. @@ -4099,10 +4114,7 @@ if (message_reference) g = string_append(g, 2, US" R=", message_reference); g = add_host_info_for_log(g); - -#ifndef DISABLE_TLS g = add_tls_info_for_log(g); -#endif if (sender_host_authenticated) { @@ -4156,6 +4168,8 @@ if (LOGGING(dkim)) } #endif +g = add_dmarc_info_for_log(g); + if (LOGGING(receive_time)) { struct timeval diff = received_time_complete; diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 6cfc36f7a..d694c8d7a 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -1483,7 +1483,6 @@ return string_from_gstring(g); -#ifndef DISABLE_TLS /* Append TLS-related information to a log line Arguments: @@ -1494,13 +1493,14 @@ Returns: Allocated string or NULL gstring * add_tls_info_for_log(gstring * g) { +#ifndef DISABLE_TLS if (LOGGING(tls_cipher) && tls_in.cipher) { g = string_append(g, 2, US" X=", tls_in.cipher); -#ifndef DISABLE_TLS_RESUME +# ifndef DISABLE_TLS_RESUME if (LOGGING(tls_resumption) && tls_in.resumption & RESUME_USED) g = string_catn(g, US"*", 1); -#endif +# endif } if (LOGGING(tls_certificate_verified) && tls_in.peercert) g = string_append(g, 2, US" CV=", tls_in.certificate_verified? "yes":"no"); @@ -1509,8 +1509,8 @@ if (LOGGING(tls_peerdn) && tls_in.peerdn) if (LOGGING(tls_sni) && tls_in.sni) g = string_append(g, 2, US" SNI=", string_printing2(tls_in.sni, SP_TAB|SP_SPACE)); return g; -} #endif +} @@ -1547,7 +1547,6 @@ Returns: nothing void smtp_log_no_mail(void) { -uschar * s; gstring * g = NULL; if (smtp_mailcmd_count > 0 || !LOGGING(smtp_no_mail)) @@ -1559,17 +1558,12 @@ if (sender_host_authenticated) if (authenticated_id) g = string_append(g, 2, US":", authenticated_id); } -#ifndef DISABLE_TLS g = add_tls_info_for_log(g); -#endif - g = s_connhad_log(g); -if (!(s = string_from_gstring(g))) s = US""; - -log_write(0, LOG_MAIN, "no MAIL in %sSMTP connection from %s D=%s%s", +log_write(0, LOG_MAIN, "no MAIL in %sSMTP connection from %s D=%s%#Y", f.tcp_in_fastopen ? f.tcp_in_fastopen_data ? US"TFO* " : US"TFO " : US"", - host_and_ident(FALSE), string_timesince(&smtp_connection_start), s); + host_and_ident(FALSE), string_timesince(&smtp_connection_start), g); } @@ -2178,21 +2172,13 @@ static void log_connect_tls_drop(const uschar * what, const uschar * log_msg) { if (log_reject_target) - { -#ifdef DISABLE_TLS - uschar * tls = NULL; -#else - gstring * g = add_tls_info_for_log(NULL); - uschar * tls = string_from_gstring(g); -#endif log_write(L_connection_reject, - log_reject_target, "%s%s%s dropped by %s%s%s", + log_reject_target, "%s%s%#Y dropped by %s%s%s", LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"", host_and_ident(TRUE), - tls ? tls : US"", + add_tls_info_for_log(NULL), what, log_msg ? US": " : US"", log_msg); - } } @@ -3264,28 +3250,16 @@ the connection is not forcibly to be dropped, return 0. Otherwise, log why it is closing if required and return 2. */ if (log_reject_target) - { -#ifndef DISABLE_TLS - gstring * tls = add_tls_info_for_log(NULL); -#else - gstring * tls = NULL; -#endif -#ifdef EXIM_HAVE_SPF - gstring * spf= add_spf_info_for_log(NULL); -#else - gstring * spf = NULL; -#endif - log_write(where == ACL_WHERE_CONNECT ? L_connection_reject : 0, - log_reject_target, "%s%s%#Y%s%#Y %srejected %s%s", + log_reject_target, "%s%s%#Y%s%#Y%#Y %srejected %s%s", LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"", host_and_ident(TRUE), - tls, + add_tls_info_for_log(NULL), sender_info, - spf, + add_spf_info_for_log(NULL), + add_dmarc_info_for_log(NULL), rc == FAIL ? US"" : US"temporarily ", what, log_msg); - } if (!drop) return 0; commit c5007722d5f5e86953912ea75b13575577e4f98a Author: Jeremy Harris Date: Thu Dec 4 22:22:17 2025 +0000 Perl: tweak dns lookup fail cases diff --git a/src/src/dns.c b/src/src/dns.c index dd754f6c9..5ed896b38 100644 --- a/src/src/dns.c +++ b/src/src/dns.c @@ -717,7 +717,7 @@ the packet length if the packet header looks plausible. Return TRUE iff it seemed ok */ -static BOOL +BOOL fake_dnsa_len_for_fail(dns_answer * dnsa, int type) { const HEADER * h = (const HEADER *)dnsa->answer; diff --git a/src/src/functions.h b/src/src/functions.h index a15369af9..49ed1064d 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -248,6 +248,7 @@ extern uschar *expand_string_copy(const uschar *); extern int_eximarith_t expand_string_integer(uschar *, BOOL); extern void modify_variable(uschar *, void *); +extern BOOL fake_dnsa_len_for_fail(dns_answer *, int); extern BOOL fd_ready(int, time_t); extern BOOL filter_runtest(int, const uschar *, BOOL, BOOL); diff --git a/src/src/lookups/Makefile b/src/src/lookups/Makefile index 23347e83b..27ae3fe1e 100644 --- a/src/src/lookups/Makefile +++ b/src/src/lookups/Makefile @@ -40,7 +40,7 @@ avail_static_lookups.o: $(HDRS) avail_static_lookups.c lookups.a: avail_static_lookups.o $(OBJS) @$(RM_COMMAND) -f lookups.a @echo "$(AR) lookups.a" - @$(AR) lookups.a avail_static_lookups.o $(OBJS) + $(FE)$(AR) lookups.a avail_static_lookups.o $(OBJS) $(RANLIB) $@ .SUFFIXES: .o .c .so diff --git a/src/src/miscmods/perl.c b/src/src/miscmods/perl.c index cee920c77..5877d3dea 100644 --- a/src/src/miscmods/perl.c +++ b/src/src/miscmods/perl.c @@ -158,6 +158,8 @@ debug_printf_indent(" dnsa answer %p len %d\n", dnsa->answer, dnsa->answerlen); ST(0) = sv_newmortal(); if (dns_res == DNS_AGAIN || dns_res == DNS_FAIL) sv_setsv(ST(0), &PL_sv_undef); +else if (dnsa->answerlen == -1 && !fake_dnsa_len_for_fail(dnsa, rrtype_int)) + sv_setsv(ST(0), &PL_sv_undef); else sv_setpvn(ST(0), CCS dnsa->answer, (STRLEN) dnsa->answerlen); XSRETURN(1); /* ? needed because there are 2 arg, but 1 res? */ commit 46846fc1f6254cb6a1ef14babc40fcae9f5baea1 Author: Jeremy Harris Date: Fri Dec 5 12:18:39 2025 +0000 taint: better tracking for dns lookup results diff --git a/src/src/acl.c b/src/src/acl.c index be61fc953..b3c45668a 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -1435,7 +1435,7 @@ tree_node *t; const uschar *found; int priority, weight, port; dns_answer * dnsa; -dns_scan dnss; +dns_scan dnss = {0}; dns_record *rr; int rc, type, yield; #define TARGET_SIZE 256 @@ -1584,7 +1584,7 @@ be authorized. (This is an odd configuration because weight=2 target=. is equivalent to weight=1, but we check for it in order to keep load off the root name servers.) Note that dn_expand() turns "." into "". */ -if (Ustrcmp(target, "") == 0) +if (!*target) { yield = CSA_FAIL_NOADDR; goto out; diff --git a/src/src/auths/dovecot.c b/src/src/auths/dovecot.c index 118e95f29..c9fdc0ea9 100644 --- a/src/src/auths/dovecot.c +++ b/src/src/auths/dovecot.c @@ -298,7 +298,10 @@ if (ob->server_tls) { union sockaddr_46 interface_sock; EXIM_SOCKLEN_T size = sizeof(interface_sock); + + /* Large (64k if DANE supported) */ smtp_connect_args conn_args = { .host = &host }; + tls_support tls_dummy = { .sni = NULL }; uschar * errstr; diff --git a/src/src/dns.c b/src/src/dns.c index 5ed896b38..6b527ef00 100644 --- a/src/src/dns.c +++ b/src/src/dns.c @@ -318,6 +318,20 @@ dnss_inc_aptr(const dns_answer * dnsa, dns_scan * dnss, unsigned delta) return dnsa_bad_ptr(dnsa, dnss->aptr += delta); } +static int +dns_rr_expand_taint(const uschar * msg, const uschar * eom, + const uschar * comp_dn, dns_scan * dnss) +{ +uschar buf[DNS_MAXNAME]; +int ret; + +if ((ret = dn_expand(msg, eom, comp_dn, + (DN_EXPAND_ARG4_TYPE) buf, sizeof(buf))) < 0) + return ret; +dnss->srr.name = string_copy_taint(buf, GET_TAINTED); +return ret; +} + /************************************************* * Get next DNS record from answer block * *************************************************/ @@ -339,6 +353,7 @@ dns_record * dns_next_rr(const dns_answer * dnsa, dns_scan * dnss, int reset) { const HEADER * h = (const HEADER *)dnsa->answer; +const uschar * eom = dnsa->answer + dnsa->answerlen; int namelen; char * trace = NULL; @@ -361,9 +376,9 @@ if (reset != RESET_NEXT) while (dnss->rrcount-- > 0) { TRACE trace = "Q-namelen"; - namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, - dnss->aptr, (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME); - if (namelen < 0) goto null_return; + /*TTT*/ + if ((namelen = dns_rr_expand_taint(dnsa->answer, eom, dnss->aptr, dnss)) <0) + goto null_return; /* skip name & type & class */ TRACE trace = "Q-skip"; if (dnss_inc_aptr(dnsa, dnss, namelen+4)) goto null_return; @@ -392,9 +407,10 @@ if (reset != RESET_NEXT) while (dnss->rrcount-- > 0) { TRACE trace = "A-namelen"; - namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, - dnss->aptr, (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME); - if (namelen < 0) goto null_return; + /*TTT*/ + if ( (namelen = dns_rr_expand_taint(dnsa->answer, eom, dnss->aptr, dnss)) + < 0) + goto null_return; /* skip name, type, class & TTL */ TRACE trace = "A-hdr"; @@ -426,9 +442,9 @@ if (dnss->rrcount-- <= 0) return NULL; (something safe). */ TRACE trace = "R-namelen"; -namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, dnss->aptr, - (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME); -if (namelen < 0) goto null_return; +/*TTT*/ +if ((namelen = dns_rr_expand_taint(dnsa->answer, eom, dnss->aptr, dnss)) < 0) + goto null_return; /* Move the pointer past the name and fill in the rest of the data structure from the following bytes. We seem to be assuming here that the RR blob passed @@ -482,16 +498,16 @@ Scan the whole AUTHORITY section, since it may contain other records Return: name for the authority, in an allocated string, or NULL if none found */ static const uschar * -dns_extract_auth_name(const dns_answer * dnsa) /* FIXME: const dns_answer */ +dns_extract_auth_name(const dns_answer * dnsa) { -dns_scan dnss; +dns_scan dnss = {0}; const HEADER * h = (const HEADER *) dnsa->answer; if (h->nscount && h->aa) for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY); rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == (h->ancount ? T_NS : T_SOA)) - return string_copy(rr->name); + return string_copy(rr->name); /*TTT*/ return NULL; } @@ -750,7 +766,7 @@ bother doing a separate lookup; if not found return a forever TTL. time_t dns_expire_from_soa(dns_answer * dnsa, int type) { -dns_scan dnss; +dns_scan dnss = {0}; if (fake_dnsa_len_for_fail(dnsa, type)) for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY); @@ -1046,7 +1062,7 @@ for (int i = 0; i <= dns_cname_loops; i++) { uschar * data; dns_record cname_rr, type_rr; - dns_scan dnss; + dns_scan dnss = {0}; /* DNS lookup failures get passed straight back. */ @@ -1087,7 +1103,7 @@ for (int i = 0; i <= dns_cname_loops; i++) ) #endif ) - *fully_qualified_name = string_copy_dnsdomain(rr_name); + *fully_qualified_name = string_copy_dnsdomain(rr_name); /*TTT*/ } /* If any data records of the correct type were found, we are done. */ @@ -1111,7 +1127,7 @@ for (int i = 0; i <= dns_cname_loops; i++) } /* DNS data comes from the outside, hence tainted */ - data = store_get(256, GET_TAINTED); + data = store_get(256, GET_TAINTED); /*TTT alloc*/ if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, 256) < 0) { @@ -1120,6 +1136,13 @@ for (int i = 0; i <= dns_cname_loops; i++) } name = data; + for (int i = 256-50; i > 0; i--) /* Usually much smaller, so find end */ + if (!*data++) + { + store_release_above(data); /* and release excess allocation */ + break; + } + if (!dns_is_secure(dnsa)) secure_so_far = FALSE; @@ -1239,8 +1262,8 @@ switch (type) uschar * namesuff, * tld; int priority, dummy_weight, port, limit, rc, i; BOOL ipv6; - dns_record *rr; - dns_scan dnss; + dns_record * rr; + dns_scan dnss = {0}; DEBUG(D_dns) debug_printf_indent("CSA lookup of %s\n", name); @@ -1336,7 +1359,7 @@ switch (type) /* If it's making an interesting assertion, return this response. */ if (port & 1) { - *fully_qualified_name = namesuff + 1; + *fully_qualified_name = namesuff + 1; /*TTT*/ return DNS_SUCCEED; } } diff --git a/src/src/dnsbl.c b/src/src/dnsbl.c index dc4a0bea0..ac3e017e9 100644 --- a/src/src/dnsbl.c +++ b/src/src/dnsbl.c @@ -70,7 +70,7 @@ one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain, int defer_return) { dns_answer * dnsa = store_get_dns_answer(); -dns_scan dnss; +dns_scan dnss = {0}; tree_node *t; dnsbl_cache_block *cb; int old_pool = store_pool; @@ -363,6 +363,7 @@ if (cb->rc == DNS_SUCCEED) int len = (rr->data)[0]; if (len > 511) len = 127; store_pool = POOL_PERM; + /*TTT*/ cb->text = string_copyn_taint(CUS (rr->data+1), len, GET_TAINTED); store_pool = old_pool; break; diff --git a/src/src/functions.h b/src/src/functions.h index 49ed1064d..ec13c544e 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -590,7 +590,7 @@ extern uschar *string_base62_32(unsigned long int); extern uschar *string_base62_64(unsigned long int); extern gstring *string_catn(gstring *, const uschar *, int) WARN_UNUSED_RESULT; extern int string_compare_by_pointer(const void *, const void *); -extern uschar *string_copy_dnsdomain(uschar *); +extern uschar *string_copy_dnsdomain(const uschar *); extern uschar *string_copy_malloc(const uschar *); extern uschar *string_dequote(const uschar **); extern uschar *string_format_size(int, uschar *); @@ -1145,8 +1145,9 @@ return item /* Use store_malloc for DNSA structs, and explicit frees. Using the same pool for them as the strings we proceed to copy from them meant they could not be released, hence blowing 64k for every DNS lookup. That mounted up. With malloc -we do have to take care over marking tainted all copied strings. A separate pool -could be used and would handle that implicitly. */ +we do have to take care over marking tainted all copied strings. +A separate pool could be used and could handle taint implicitly - but we would +want to support independent free ops, not limited to stacked alloc/release */ #define store_get_dns_answer() store_get_dns_answer_trc(CUS __FUNCTION__, __LINE__) diff --git a/src/src/host.c b/src/src/host.c index 4c513e28c..95694635a 100644 --- a/src/src/host.c +++ b/src/src/host.c @@ -180,7 +180,7 @@ uschar *adds; uschar **alist; struct hostent *yield; dns_answer * dnsa = store_get_dns_answer(); -dns_scan dnss; +dns_scan dnss = {0}; DEBUG(D_host_lookup) debug_printf_indent("using host_fake_gethostbyname for %s (%s)\n", name, @@ -1587,7 +1587,7 @@ uschar **aliases; uschar *ordername; const uschar *list = host_lookup_order; dns_answer * dnsa = store_get_dns_answer(); -dns_scan dnss; +dns_scan dnss = {0}; sender_host_dnssec = host_lookup_deferred = host_lookup_failed = FALSE; @@ -1662,6 +1662,7 @@ while ((ordername = string_nextinlist(&list, &sep, NULL, 0))) /* If an overlong response was received, the data will have been truncated and dn_expand may fail. */ + /*TTT*/ if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, US rr->data, (DN_EXPAND_ARG4_TYPE)(s), ssize) < 0) { @@ -2286,7 +2287,7 @@ for (; i >= 0; i--) int type = types[i]; int randoffset = i == (whichrrs & HOST_FIND_IPV4_FIRST ? 1 : 0) ? 500 : 0; /* Ensures v6/4 sort order */ - dns_scan dnss; + dns_scan dnss = {0}; int rc = dns_lookup_timerwrap(dnsa, host->name, type, fully_qualified_name); lookup_dnssec_authenticated = !dnssec_request ? NULL @@ -2389,7 +2390,7 @@ for (; i >= 0; i--) if (thishostlast == NULL) { if (strcmpic(host->name, rr->name) != 0) - host->name = string_copy_dnsdomain(rr->name); + host->name = string_copy_dnsdomain(rr->name); /*TTT*/ host->address = da->address; host->sort_key = host->mx * 1000 + random_number(500) + randoffset; host->status = hstatus_unknown; @@ -2530,7 +2531,7 @@ BOOL srv_smtps = FALSE; #endif int rc = DNS_FAIL, ind_type = 0, yield; dns_answer * dnsa = store_get_dns_answer(); -dns_scan dnss; +dns_scan dnss = {0}; BOOL dnssec_require, dnssec_request; dnssec_status_t dnssec; @@ -2784,7 +2785,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); int port = PORT_NONE; /* MX lookups get PORT_NONE */ const uschar * s = rr->data; /* MUST be unsigned for GETSHORT */ host_item * next; - uschar data[256]; + uschar data[256]; /*TTT*/ if (rr_bad_size(rr, sizeof(uint16_t))) continue; GETSHORT(precedence, s); /* Pointer s is advanced */ @@ -2811,8 +2812,10 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); /* Get the name of the host pointed to. */ - (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s, - (DN_EXPAND_ARG4_TYPE)data, sizeof(data)); + /*TTT*/ + if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s, + (DN_EXPAND_ARG4_TYPE)data, sizeof(data)) < 0) + continue; /* Check that we haven't already got this host on the chain; if we have, keep only the lower precedence. This situation shouldn't occur, but you @@ -2851,7 +2854,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); before the first block, copy the first block's data to a new second block. */ next = last ? store_get(sizeof(host_item), GET_UNTAINTED) : host; - next->name = string_copy_dnsdomain(data); + next->name = string_copy_dnsdomain(data); /*TTT*/ next->address = NULL; next->port = port; next->mx = precedence; @@ -3210,7 +3213,7 @@ switch (rc) { DEBUG(D_transport) { - dns_scan dnss; + dns_scan dnss = {0}; for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == T_TLSA && rr->size > 3) diff --git a/src/src/lookups/dnsdb.c b/src/src/lookups/dnsdb.c index 6e2be8ae1..80cf2e62a 100644 --- a/src/src/lookups/dnsdb.c +++ b/src/src/lookups/dnsdb.c @@ -134,22 +134,19 @@ dnsdb_find(void * handle, const uschar * filename, const uschar * keystring, int length, uschar ** result, uschar ** errmsg, uint * do_cache, const uschar * opts) { -int rc; -int sep = 0; int defer_mode = PASS, dnssec_mode = PASS; -int save_retrans = dns_retrans, save_retry = dns_retry; -int type; -int failrc = FAIL; +int save_retrans = dns_retrans, save_retry = dns_retry; +int sep = 0, rc, type, failrc = FAIL; const uschar * outsep = CUS"\n", * outsep2 = NULL; uschar * equals, * domain, * found; -dns_answer * dnsa = store_get_dns_answer(); -dns_scan dnss; +dns_answer * dnsa = store_get_dns_answer(); /*TTT alloc */ +dns_scan dnss = {0}; /* Because we're working in the search pool, we try to reclaim as much store as possible later, so we preallocate the result here */ -gstring * yield = string_get(256); +gstring * yield = string_get_tainted(256, GET_TAINTED); /*TTT alloc*/ /* If the string starts with '>' we change the output separator. If it's followed by ';' or ',' we set the TXT output separator. */ @@ -406,6 +403,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) remain = rr->size - ++data_offset; if (chunk_len > remain) chunk_len = remain; + /*TTT*/ yield = string_catn(yield, US ((rr->data) + data_offset), chunk_len); data_offset += chunk_len; @@ -430,6 +428,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) if (payload_length > MAX_TLSA_EXPANDED_SIZE) payload_length = MAX_TLSA_EXPANDED_SIZE; + /*TTT*/ yield = string_fmt_append(yield, "%d%c%d%c%d%c%.*H", usage, *outsep2, selector, *outsep2, @@ -439,9 +438,15 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) else /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SOA, T_SRV */ { int priority, weight, port; - uschar s[264]; uschar * p = US rr->data; + /* NB: this memory is released implicitly by the call + gstring_release_unused(yield) below. We used to use a stack-auto, but + I want to track taint wherever possible. The dnsa is not (yet) + allocated using taint-marked memory. */ +#define LCL_BUF_SIZE 264 + uschar * buf = store_get(LCL_BUF_SIZE, GET_TAINTED); + switch (type) { case T_MXH: @@ -453,8 +458,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) case T_MX: if (rr_bad_size(rr, sizeof(uint16_t))) continue; GETSHORT(priority, p); - sprintf(CS s, "%d%c", priority, *outsep2); - yield = string_cat(yield, s); + yield = string_fmt_append(yield, "%d%c", priority, *outsep2); break; case T_SRV: @@ -462,9 +466,8 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) GETSHORT(priority, p); GETSHORT(weight, p); GETSHORT(port, p); - sprintf(CS s, "%d%c%d%c%d%c", priority, *outsep2, + yield = string_fmt_append(yield, "%d%c%d%c%d%c", priority, *outsep2, weight, *outsep2, port, *outsep2); - yield = string_cat(yield, s); break; case T_CSA: @@ -481,20 +484,18 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) authorization status in the weight field. */ if (Ustrcmp(found, domain) != 0) - { - if (port & 1) *s = 'X'; /* explicit authorization required */ - else *s = '?'; /* no subdomain assertions here */ - } + yield = string_catn(yield, port & 1 + ? US"X " /* explicit authorization required */ + : US"? ", /* no subdomain assertions here */ + 2); + else if (weight > 3) + continue; else - { - if (weight < 2) *s = 'N'; /* not authorized */ - else if (weight == 2) *s = 'Y'; /* authorized */ - else if (weight == 3) *s = '?'; /* unauthorizable */ - else continue; /* invalid */ - } - - s[1] = ' '; - yield = string_catn(yield, s, 2); + yield = string_catn(yield, + weight < 2 ? US"N " /* not authorized */ + : weight == 2 ? US"Y " /* authorized */ + : US"? ", /* unauthorizable */ + 2); break; default: @@ -503,8 +504,9 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) /* GETSHORT() has advanced the pointer to the target domain. */ + /*TTT*/ rc = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, p, - (DN_EXPAND_ARG4_TYPE)s, sizeof(s)); + (DN_EXPAND_ARG4_TYPE)buf, LCL_BUF_SIZE); /* If an overlong response was received, the data will have been truncated and dn_expand may fail. */ @@ -515,7 +517,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) "domain=%s", dns_text_type(type), domain); break; } - else yield = string_cat(yield, s); + else yield = string_cat(yield, buf); if (type == T_SOA && outsep2 != NULL) { @@ -524,15 +526,16 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) p += rc; yield = string_catn(yield, outsep2, 1); + /*TTT*/ rc = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, p, - (DN_EXPAND_ARG4_TYPE)s, sizeof(s)); + (DN_EXPAND_ARG4_TYPE)buf, LCL_BUF_SIZE); if (rc < 0) { log_write(0, LOG_MAIN, "responsible-mailbox truncated: type=%s " "domain=%s", dns_text_type(type), domain); break; } - else yield = string_cat(yield, s); + else yield = string_cat(yield, buf); p += rc; if (!rr_bad_increment(rr, p, 5 * sizeof(uint32_t))) @@ -540,12 +543,12 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) GETLONG(serial, p); GETLONG(refresh, p); GETLONG(retry, p); GETLONG(expire, p); GETLONG(minimum, p); } - sprintf(CS s, "%c%lu%c%lu%c%lu%c%lu%c%lu", + yield = string_fmt_append(yield, "%c%lu%c%lu%c%lu%c%lu%c%lu", *outsep2, serial, *outsep2, refresh, *outsep2, retry, *outsep2, expire, *outsep2, minimum); - yield = string_cat(yield, s); } } +#undef LCL_BUF_SIZE } /* Loop for list of returned records */ /* Loop for set of A-lookup types */ @@ -564,7 +567,7 @@ dns_retrans = save_retrans; dns_retry = save_retry; dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */ -if (!yield || !yield->ptr) +if (gstring_length(yield) == 0) rc = failrc; else { diff --git a/src/src/miscmods/dkim.c b/src/src/miscmods/dkim.c index ccb649ca1..4bd87bbf9 100644 --- a/src/src/miscmods/dkim.c +++ b/src/src/miscmods/dkim.c @@ -81,9 +81,9 @@ static uschar * dkim_exim_query_dns_txt(const uschar * name) { dns_answer * dnsa = store_get_dns_answer(); -dns_scan dnss; +dns_scan dnss = {0}; rmark reset_point = store_mark(); -gstring * g = string_get_tainted(256, GET_TAINTED); +gstring * g = string_get_tainted(256, GET_TAINTED); /*TTT alloc*/ lookup_dnssec_authenticated = NULL; if (dns_lookup(dnsa, name, T_TXT, NULL) != DNS_SUCCEED) @@ -100,6 +100,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); { uschar len = rr->data[rr_offset++]; + /*TTT*/ g = string_catn(g, US(rr->data + rr_offset), len); if (g->ptr >= PDKIM_DNS_TXT_MAX_RECLEN) goto bad; diff --git a/src/src/miscmods/dmarc_common.c b/src/src/miscmods/dmarc_common.c index d6e27c892..1669d8400 100644 --- a/src/src/miscmods/dmarc_common.c +++ b/src/src/miscmods/dmarc_common.c @@ -228,7 +228,7 @@ const uschar * dmarc_dns_lookup(const uschar * dom) { dns_answer * dnsa = store_get_dns_answer(); -dns_scan dnss; +dns_scan dnss = {0}; const uschar * res = NULL; expand_level++; @@ -256,7 +256,7 @@ if (dns_lookup(dnsa, string_sprintf("_dmarc.%s", dom), T_TXT, NULL) if ( rr->type == T_TXT && len > 9 && Ustrncmp(rdata, "v=DMARC1;", 9) == 0) if (!res) - res = string_copyn_taint(rdata, len, GET_TAINTED); + res = string_copyn_taint(rdata, len, GET_TAINTED); /*XXX*/ else /* RFC 7489 6.6.3 step 5: multiple records are treated as no record */ { diff --git a/src/src/miscmods/spf.c b/src/src/miscmods/spf.c index 4febe7f4e..d97995d56 100644 --- a/src/src/miscmods/spf.c +++ b/src/src/miscmods/spf.c @@ -67,7 +67,7 @@ SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server, const char *domain, ns_type rr_type, int should_cache) { dns_answer * dnsa = store_get_dns_answer(); -dns_scan dnss; +dns_scan dnss = {0}; SPF_dns_rr_t * spfrr; unsigned found = 0; @@ -134,9 +134,16 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; s += 2; /* skip the MX precedence field */ case T_PTR: { - uschar * buf = store_malloc(256); - (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s, - (DN_EXPAND_ARG4_TYPE)buf, 256); + /* We lose taint-tracking here, really just assuming the data + given to the spf library will never leak back out. Not sure if + the lib assumes it can free this (it does for srr.rr) - meaning we + cannot use pool store. The use of malloc also for T_TXT implies so. */ + + uschar * buf = store_malloc(256); /*TTT alloc*/ + /*TTT*/ + if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s, + (DN_EXPAND_ARG4_TYPE)buf, 256) < 0) + continue; s = buf; break; } @@ -160,12 +167,13 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; if ( !(chunk_len = s[offset++]) || rr->size < offset + chunk_len /* ignore bogus size chunks */ ) break; - g = string_catn(g, s+offset , chunk_len); + g = string_catn(g, s+offset , chunk_len); /*TTT*/ } if (!g) continue; + s = string_copy_malloc(string_from_gstring(g)); /*TTT*/ + gstring_reset(g); gstring_release_unused(g); - s = string_copy_malloc(string_from_gstring(g)); DEBUG(D_receive) debug_printf_indent("SPF_dns_exim_lookup '%s'\n", s); break; } @@ -174,7 +182,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; case T_AAAA: default: { - uschar * buf = store_malloc(dnsa->answerlen + 1); + uschar * buf = store_malloc(dnsa->answerlen + 1); /*TTT alloc*/ s = memcpy(buf, s, dnsa->answerlen + 1); break; } diff --git a/src/src/store.c b/src/src/store.c index 4daa92512..e265acd4c 100644 --- a/src/src/store.c +++ b/src/src/store.c @@ -1217,7 +1217,7 @@ return yield; } void * -store_malloc_3(size_t size, const char *func, int linenumber) +store_malloc_3(size_t size, const char * func, int linenumber) { if (n_nonpool_blocks++ > max_nonpool_blocks) max_nonpool_blocks = n_nonpool_blocks; diff --git a/src/src/string.c b/src/src/string.c index 472008382..4e122a97b 100644 --- a/src/src/string.c +++ b/src/src/string.c @@ -618,9 +618,10 @@ Returns: copy of string in new store, de-escaped */ uschar * -string_copy_dnsdomain(uschar * s) +string_copy_dnsdomain(const uschar * s) { uschar * yield; +/*TTT*/ uschar * ss = yield = store_get(Ustrlen(s) + 1, GET_TAINTED); /* always treat as tainted */ while (*s) diff --git a/src/src/structs.h b/src/src/structs.h index a6261afcb..22a4bba90 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -760,11 +760,11 @@ typedef struct search_cache { uncompressed, but the data pointer is into the raw data. */ typedef struct { - uschar name[DNS_MAXNAME]; /* domain name */ - int type; /* record type */ - unsigned short ttl; /* time-to-live, seconds */ - int size; /* size of data */ - const uschar *data; /* pointer to data */ + uschar *name; /* domain name */ + int type; /* record type */ + unsigned short ttl; /* time-to-live, seconds */ + int size; /* size of data */ + const uschar *data; /* pointer to data */ } dns_record; /* Structure for holding the result of a DNS query. A touch over @@ -826,6 +826,9 @@ typedef struct { #ifdef SUPPORT_DANE BOOL dane:1; /* TLSA says connection must do dane */ + /*TTT alloc*/ + /* Strictly, this should use tainted mem. Also, is rather large - 64k - so + perhaps should be allocated only when needed (for DANE)? */ dns_answer tlsa_dnsa; /* strictly, this should use tainted mem */ #endif } smtp_connect_args; diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index b78c33fcb..1610387d9 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -3397,7 +3397,7 @@ after verification is done.*/ static BOOL dane_tlsa_load(exim_gnutls_state_st * state, const dns_answer * dnsa) { -dns_scan dnss; +dns_scan dnss = {0}; int i; const char ** dane_data; int * dane_data_len; @@ -3416,7 +3416,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; ) if (rr->type == T_TLSA && rr->size > 3) { const uschar * p = rr->data; -/*XXX need somehow to mark rr and its data as tainted. Doues this mean copying it? */ +/*XXX need somehow to mark rr and its data as tainted. Doues this mean copying it? *//*TTT*/ uint8_t usage = p[0], sel = p[1], type = p[2]; DEBUG(D_tls) @@ -3438,7 +3438,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; } tls_out.tlsa_usage |= 1<size; } diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index eea1f15d3..40acee7a3 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -3940,7 +3940,7 @@ static int dane_tlsa_load(SSL * ssl, const host_item * host, const dns_answer * dnsa, uschar ** errstr) { -dns_scan dnss; +dns_scan dnss = {0}; const char * hostnames[2] = { CS host->name, NULL }; int found = 0; @@ -3972,6 +3972,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; } found++; + /*TTT*/ switch (DANESSL_add_tlsa(ssl, usage, selector, mdname, p, rr->size - 3)) { default: diff --git a/src/src/tls.c b/src/src/tls.c index dd82c7156..041cca5e9 100644 --- a/src/src/tls.c +++ b/src/src/tls.c @@ -879,7 +879,10 @@ tls_client_adjunct_start(host_item * host, client_conn_ctx * cctx, { union sockaddr_46 interface_sock; EXIM_SOCKLEN_T size = sizeof(interface_sock); + +/* Large (64k if DANE supported) */ smtp_connect_args conn_args = {.host = host }; + tls_support tls_dummy = { .sni = NULL }; uschar * errstr; diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 7160f0616..1f2161ae2 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -4045,8 +4045,11 @@ int yield = OK; int save_errno; int rc; -uschar *message = NULL; -smtp_context * sx = store_get(sizeof(*sx), GET_TAINTED); /* tainted, for the data buffers */ +uschar * message = NULL; + +/* Large (12k, 66k if DANE supported). Tainted, for the data buffers */ +smtp_context * sx = store_get(sizeof(*sx), GET_TAINTED); + BOOL pass_message = FALSE; #ifndef DISABLE_ESMTP_LIMITS BOOL mail_limit = FALSE; @@ -5346,7 +5349,7 @@ smtp_transport_closedown(transport_instance * tblock) { const smtp_transport_options_block * ob = tblock->drinst.options_block; client_conn_ctx cctx; -smtp_context sx = {0}; +smtp_context sx = {0}; /* Large (12k, 66k if DANE supported) */ uschar buffer[256]; uschar inbuffer[4096]; uschar outbuffer[16]; @@ -5381,7 +5384,7 @@ int smtp_write_atrn(address_item * addr, cut_t * cutp) { const smtp_transport_options_block * ob = addr->transport->drinst.options_block; -smtp_context sx = {0}; +smtp_context sx = {0}; /* Large (12k, 66k if DANE supported */ uschar buffer[256]; uschar inbuffer[4096]; uschar outbuffer[256]; diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h index 49e3c8b96..506d6094a 100644 --- a/src/src/transports/smtp.h +++ b/src/src/transports/smtp.h @@ -144,7 +144,7 @@ typedef struct { const uschar * from_addr; address_item * addrlist; - smtp_connect_args conn_args; + smtp_connect_args conn_args; /* Large (if DANE supported) */ int port; BOOL verify:1; diff --git a/src/src/transports/smtp_socks.c b/src/src/transports/smtp_socks.c index 436674e9b..c17b808e9 100644 --- a/src/src/transports/smtp_socks.c +++ b/src/src/transports/smtp_socks.c @@ -258,6 +258,8 @@ for(;;) { int idx; host_item proxy; + + /* Large (64k if DANE supported) */ smtp_connect_args proxy_sc = {.sock = -1}; if ((idx = socks_get_proxy(proxies, nproxies)) < 0) diff --git a/src/src/verify.c b/src/src/verify.c index 18c53c1e8..6696ec4d9 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -16,7 +16,7 @@ caching was contributed by Kevin Fleming (but I hacked it around a bit). */ #define CUTTHROUGH_CMD_TIMEOUT 30 /* timeout for cutthrough-routing calls */ #define CUTTHROUGH_DATA_TIMEOUT 60 /* timeout for cutthrough-routing calls */ -static smtp_context ctctx; +static smtp_context ctctx; /* Large (12k, 66k if DANE supported */ uschar ctbuffer[8192]; @@ -703,7 +703,8 @@ coding means skipping this whole loop and doing the append separately. */ log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address, addr->message); - if (!sx) sx = store_get(sizeof(*sx), GET_TAINTED); /* tainted buffers */ + /* Large (12k, 66k if DANE supported. Tainted, for the receive buffers */ + if (!sx) sx = store_get(sizeof(*sx), GET_TAINTED); memset(sx, 0, sizeof(*sx)); sx->addrlist = sx->first_addr = addr; @@ -1417,7 +1418,7 @@ Used for static uschar cutthrough_response(client_conn_ctx * cctx, char expect, uschar ** copy, int timeout) { -smtp_context sx = {0}; +smtp_context sx = {0}; /* Large (12k, 66k if DANE supported) */ uschar inbuffer[4096]; uschar responsebuffer[4096]; commit 6b77712fcc9e4daf673751f0302c1ae003ed7dce Author: Jeremy Harris Date: Sun Dec 7 13:17:41 2025 +0000 DMARC: fix retval for module init diff --git a/src/src/miscmods/dmarc.c b/src/src/miscmods/dmarc.c index 8d840ae84..49ad795c2 100644 --- a/src/src/miscmods/dmarc.c +++ b/src/src/miscmods/dmarc.c @@ -63,9 +63,9 @@ extern uschar * dmarc_history_file; /* File to store dmarc results */ extern uschar * dmarc_tld_file; /* Mozilla TLDs text file */ -void +BOOL dmarc_local_init(void) -{} +{ return TRUE; } gstring * dmarc_version_report(gstring * g) diff --git a/src/src/miscmods/dmarc_native.c b/src/src/miscmods/dmarc_native.c index 02eb5dbd3..084adab28 100644 --- a/src/src/miscmods/dmarc_native.c +++ b/src/src/miscmods/dmarc_native.c @@ -48,6 +48,7 @@ if (!dmarc_regex_ri) dmarc_regex_ri = regex_must_compile(US "^\\d{1,10}$", MCS_CACHEABLE, FALSE); if (!dmarc_regex_fo) dmarc_regex_fo = regex_must_compile(US "^[01ds]$", MCS_CACHEABLE, FALSE); +return TRUE; } commit 01c6fb5f07c0d7555e45ab0409dd8e551359dc2c Author: Jeremy Harris Date: Sun Dec 7 14:16:30 2025 +0000 tidying diff --git a/src/src/exim.c b/src/src/exim.c index 32ca1e1d2..d35168d60 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -988,13 +988,13 @@ has the effect of collapsing source routes. Arguments: s the address string flags flag bits for verify_address() - exit_value to be set for failures + ret exit value so far -Returns: nothing +Returns: updated exit value */ -static void -test_address(const uschar * s, int flags, int * exit_value) +static int +test_address(const uschar * s, int flags, int ret) { int start, end, domain; uschar * parse_error = NULL; @@ -1003,16 +1003,17 @@ const uschar * address = parse_extract_address(s, &parse_error, if (!address) { fprintf(stdout, "syntax error: %s\n", parse_error); - *exit_value = 2; + ret = 2; } else { /* NB we lose stdio buffering here */ int rc = verify_address(deliver_make_addr(address,TRUE), fileno(stdout), flags, -1, -1, -1, NULL, NULL, NULL); - if (rc == FAIL) *exit_value = 2; - else if (rc == DEFER && *exit_value == 0) *exit_value = 1; + if (rc == FAIL) ret = 2; + else if (rc == DEFER && ret == EXIT_SUCCESS) ret = EXIT_FAILURE; } +return ret; } @@ -5302,8 +5303,7 @@ stdin. Set debug_level to at least D_v to get full output for address testing. if (verify_address_mode || f.address_test_mode) { - int exit_value = 0; - int flags = vopt_qualify; + int exit_value = EXIT_SUCCESS, flags = vopt_qualify; if (verify_address_mode) { @@ -5320,33 +5320,35 @@ if (verify_address_mode || f.address_test_mode) DEBUG(D_verify) debug_print_ids(US"Address testing:"); } - if (recipients_arg < argc) + if (recipients_arg < argc) /* addresses on cmdline */ while (recipients_arg < argc) { /* Supplied addresses are tainted since they come from a user */ uschar * s = string_copy_taint( - exim_str_fail_toolong(argv[recipients_arg++], EXIM_DISPLAYMAIL_MAX, "address verification"), + exim_str_fail_toolong(argv[recipients_arg++], EXIM_DISPLAYMAIL_MAX, + "address verification"), GET_TAINTED); while (*s) { BOOL finished = FALSE; uschar * ss = parse_find_address_end(s, FALSE); if (*ss == ',') *ss = 0; else finished = TRUE; - test_address(s, flags, &exit_value); + exit_value = test_address(s, flags, exit_value); s = ss; if (!finished) while (*++s == ',' || isspace(*s)) ; } } - else for (;;) + else for (;;) /* addresses from stdin */ { const uschar * s = get_stdinput(NULL, NULL); if (!s) break; - test_address(string_copy_taint( - exim_str_fail_toolong(s, EXIM_DISPLAYMAIL_MAX, "address verification (stdin)"), + exit_value = test_address(string_copy_taint( + exim_str_fail_toolong(s, EXIM_DISPLAYMAIL_MAX, + "address verification (stdin)"), GET_TAINTED), - flags, &exit_value); + flags, exit_value); } route_tidyup(); diff --git a/src/src/lookups/psl.c b/src/src/lookups/psl.c index 9171192a3..3b2af489e 100644 --- a/src/src/lookups/psl.c +++ b/src/src/lookups/psl.c @@ -46,7 +46,7 @@ psl_gen_find(void * handle, const uschar * keystring, int length, uschar ** result, uschar ** errmsg, BOOL is_regdom) { uschar rulebuf[128], * res = NULL; -const uschar * s, * k, * kmatch; +const uschar * s, * k, * kmatch = NULL; unsigned res_label_cnt = 0, nlabels; BOOL key_utf8; @@ -138,7 +138,7 @@ while ((s = US fgets(CS rulebuf, sizeof(rulebuf), handle))) kmatch = k; } - nonmatch: + nonmatch: ; } } /* kmatch is the key trail after the regdom. label */ diff --git a/src/src/miscmods/dmarc_native.c b/src/src/miscmods/dmarc_native.c index 084adab28..a3434a146 100644 --- a/src/src/miscmods/dmarc_native.c +++ b/src/src/miscmods/dmarc_native.c @@ -359,7 +359,7 @@ if (!dmarc_abort && !sender_host_authenticated) .ri = US"86400", }; - int sr = SPF_RESULT_INVALID, spf_origin; + int sr = SPF_RESULT_INVALID /*, spf_origin*/; uschar * spf_human_readable = NULL, * spf_sender_domain = NULL; unsigned dkim_sig_count = 0; gstring * dkim_history_buffer = NULL; @@ -525,7 +525,7 @@ if (!dmarc_abort && !sender_host_authenticated) } spf_result = DMARC_POLICY_SPF_OUTCOME_NONE; dmarc_spf_ares_result = ARES_RESULT_UNKNOWN; - spf_origin = DMARC_POLICY_SPF_ORIGIN_HELO; + /* spf_origin = DMARC_POLICY_SPF_ORIGIN_HELO; */ spf_human_readable = US""; } else @@ -544,7 +544,7 @@ if (!dmarc_abort && !sender_host_authenticated) sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR : ARES_RESULT_UNKNOWN; /*XXX hmm, spf_origin never used? */ - spf_origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM; + /* spf_origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM; */ DEBUG(D_receive) debug_printf_indent("DMARC: using SPF sender domain = %s\n", spf_sender_domain); diff --git a/src/src/moan.c b/src/src/moan.c index 01bf4f578..e5af5e511 100644 --- a/src/src/moan.c +++ b/src/src/moan.c @@ -666,7 +666,7 @@ void moan_smtp_batch(const uschar * cmd_buffer, const char * format, ...) { va_list ap; -int yield = (receive_messagecount > 0)? 1 : 2; +int yield = receive_messagecount > 0 ? EXIT_FAILURE : 2; DEBUG(D_any) debug_printf("Handling error in batched SMTP input\n"); diff --git a/src/src/string.c b/src/src/string.c index 4e122a97b..6be0b7678 100644 --- a/src/src/string.c +++ b/src/src/string.c @@ -1835,9 +1835,9 @@ while (*fp) else die_tainted(US"string_vformat", func, line); #endif - - INSERT_STRING: /* Come to from %D or %M above */ - +#ifndef COMPILE_UTILITY + INSERT_STRING: /* Come from %D or %M above */ +#endif { BOOL truncated = FALSE; commit c038d63e56be080c1bf8043b2fc5c3aac4b57544 Author: Jeremy Harris Date: Mon Dec 8 11:37:18 2025 +0000 DMARC: downgrade detailed result logging to debug diff --git a/src/src/miscmods/dmarc.c b/src/src/miscmods/dmarc.c index 49ad795c2..ce000ef8e 100644 --- a/src/src/miscmods/dmarc.c +++ b/src/src/miscmods/dmarc.c @@ -431,8 +431,8 @@ The EDITME provides a DMARC_API variable */ dmarc_alignment_dkim = dmarc_dkim_alignment == DMARC_POLICY_DKIM_ALIGNMENT_PASS; - if (LOGGING(dmarc_verbose)) - log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s " + DEBUG(D_receive) + debug_printf_indent("DMARC results: spf_domain=%s dmarc_domain=%s " "spf_align=%s dkim_align=%s enforcement='%s'", spf_sender_domain, dmarc_used_domain, dmarc_alignment_spf ? "yes" : "no", diff --git a/src/src/miscmods/dmarc_native.c b/src/src/miscmods/dmarc_native.c index a3434a146..aa28df583 100644 --- a/src/src/miscmods/dmarc_native.c +++ b/src/src/miscmods/dmarc_native.c @@ -631,17 +631,14 @@ use the sp. Otherwise use the p. */ if (has_dmarc_record && !dmarc_abort) { - /* Log results. */ - - if (LOGGING(dmarc_verbose)) - log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s " + DEBUG(D_receive) + debug_printf_indent("DMARC results: spf_domain=%s dmarc_domain=%s " "spf_align=%s dkim_align=%s enforcement='%s'", spf_sender_domain, dmarc_used_domain, dmarc_alignment_spf ? "yes" : "no", dmarc_alignment_dkim ? "yes" : "no", dmarc_status_text); - /* History file, for later aggregate reporting. */ dmarc_pct = atoi(CCS dmarc_parsed.pct); commit abef0531c0676680cab9b1fbd91da4482e35d45d Author: Jeremy Harris Date: Mon Dec 8 16:02:57 2025 +0000 DMARC: fix for empty env-from. Bug 1994 diff --git a/src/src/expand.c b/src/src/expand.c index 1b5980f5b..e222e8e75 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -719,6 +719,7 @@ static var_entry var_table[] = { { "spf_result", vtype_module, US"spf" }, { "spf_result_guessed", vtype_module, US"spf" }, { "spf_smtp_comment", vtype_module, US"spf" }, + { "spf_used_domain", vtype_module, US"spf" }, #endif { "spool_directory", vtype_stringptr, &spool_directory }, { "spool_inodes", vtype_pinodes, (void *)TRUE }, diff --git a/src/src/miscmods/dmarc.c b/src/src/miscmods/dmarc.c index ce000ef8e..a18100c81 100644 --- a/src/src/miscmods/dmarc.c +++ b/src/src/miscmods/dmarc.c @@ -206,30 +206,19 @@ if (!dmarc_abort && !sender_host_authenticated) int sr = SPF_RESULT_INVALID, origin; uschar * spf_human_readable = NULL, * spf_sender_domain; gstring * dkim_history_buffer = NULL; - typedef const pdkim_signature * (*sigs_fn_t)(void); + typedef const pdkim_signature * (*dkim_sigs_fn_t)(void); + typedef int (*spf_fn_t)(uschar **); - /* Use the envelope sender domain for this part of DMARC */ + if (dmarc_spf_mod_info) + sr = ((spf_fn_t *) dmarc_spf_mod_info->functions)[SPF_GET_RESULTS] + (&spf_human_readable); - spf_sender_domain = expand_string(US"$sender_address_domain"); - - { - typedef int (*fn_t)(uschar **); - if (dmarc_spf_mod_info) - sr = ((fn_t *) dmarc_spf_mod_info->functions)[SPF_GET_RESULTS] - (&spf_human_readable); - } + spf_sender_domain = expand_string(US"$spf_used_domain"); if (sr == SPF_RESULT_INVALID) { - /* No spf data means null envelope sender so generate a domain name - from the sender_helo_name */ - - if (!spf_sender_domain || !*spf_sender_domain) - { - spf_sender_domain = sender_helo_name; - log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n", - spf_sender_domain); - } + DEBUG(D_receive) debug_printf_indent("DMARC: spf result 'invalid'\n"); + dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE; dmarc_spf_ares_result = ARES_RESULT_UNKNOWN; origin = DMARC_POLICY_SPF_ORIGIN_HELO; @@ -237,11 +226,12 @@ if (!dmarc_abort && !sender_host_authenticated) } else { - dmarc_spf_result = sr == SPF_RESULT_NEUTRAL ? DMARC_POLICY_SPF_OUTCOME_NONE : - sr == SPF_RESULT_PASS ? DMARC_POLICY_SPF_OUTCOME_PASS : - sr == SPF_RESULT_FAIL ? DMARC_POLICY_SPF_OUTCOME_FAIL : - sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL : - DMARC_POLICY_SPF_OUTCOME_NONE; + dmarc_spf_result = + sr == SPF_RESULT_NEUTRAL ? DMARC_POLICY_SPF_OUTCOME_NONE : + sr == SPF_RESULT_PASS ? DMARC_POLICY_SPF_OUTCOME_PASS : + sr == SPF_RESULT_FAIL ? DMARC_POLICY_SPF_OUTCOME_FAIL : + sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL : + DMARC_POLICY_SPF_OUTCOME_NONE; dmarc_spf_ares_result = sr == SPF_RESULT_NEUTRAL ? ARES_RESULT_NEUTRAL : sr == SPF_RESULT_PASS ? ARES_RESULT_PASS : sr == SPF_RESULT_FAIL ? ARES_RESULT_FAIL : @@ -269,7 +259,7 @@ if (!dmarc_abort && !sender_host_authenticated) the opendmarc context, further building the DMARC reply. */ for(const pdkim_signature * sig = - (((sigs_fn_t *)dmarc_dkim_mod_info->functions)[DKIM_SIGS_LIST])(); + (((dkim_sigs_fn_t *)dmarc_dkim_mod_info->functions)[DKIM_SIGS_LIST])(); sig; sig = sig->next) { int dkim_result, dkim_ares_result, vs, ves; diff --git a/src/src/miscmods/dmarc_native.c b/src/src/miscmods/dmarc_native.c index aa28df583..f0c91868b 100644 --- a/src/src/miscmods/dmarc_native.c +++ b/src/src/miscmods/dmarc_native.c @@ -360,7 +360,8 @@ if (!dmarc_abort && !sender_host_authenticated) }; int sr = SPF_RESULT_INVALID /*, spf_origin*/; - uschar * spf_human_readable = NULL, * spf_sender_domain = NULL; + uschar * spf_human_readable = NULL; + const uschar * spf_sender_domain = NULL; unsigned dkim_sig_count = 0; gstring * dkim_history_buffer = NULL; typedef const pdkim_signature * (*sigs_fn_t)(void); @@ -502,30 +503,19 @@ if (!dmarc_abort && !sender_host_authenticated) int spf_result; typedef int (*fn_t)(uschar **); - /* Use the envelope sender domain for this part of DMARC */ - - spf_sender_domain = expand_string(US"$sender_address_domain"); - if (dmarc_spf_mod_info) sr = ((fn_t *) dmarc_spf_mod_info->functions)[SPF_GET_RESULTS] (&spf_human_readable); + spf_sender_domain = expand_string(CUS"$spf_used_domain"); + if (sr == SPF_RESULT_INVALID) { - /* No spf data means null envelope sender so generate a domain name - from the sender_helo_name */ - DEBUG(D_receive) debug_printf_indent("DMARC: spf result 'invalid'\n"); - if (!spf_sender_domain || !*spf_sender_domain) - { - spf_sender_domain = sender_helo_name; - log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n", - spf_sender_domain); - } spf_result = DMARC_POLICY_SPF_OUTCOME_NONE; dmarc_spf_ares_result = ARES_RESULT_UNKNOWN; - /* spf_origin = DMARC_POLICY_SPF_ORIGIN_HELO; */ + /* spf_origin = DMARC_POLICY_SPF_ORIGIN_HELO; not used, and we'd want to ask the spf impl really */ spf_human_readable = US""; } else diff --git a/src/src/miscmods/spf.c b/src/src/miscmods/spf.c index d97995d56..a0cdb8dc6 100644 --- a/src/src/miscmods/spf.c +++ b/src/src/miscmods/spf.c @@ -42,7 +42,8 @@ uschar * spf_smtp_comment = NULL; uschar * spf_smtp_comment_template /* Used to be: "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}" */ = US"Please%_see%_http://www.open-spf.org/Why"; -BOOL spf_result_guessed = FALSE; +BOOL spf_result_guessed = FALSE; +const uschar * spf_used_domain = NULL; @@ -409,6 +410,9 @@ else spf_received = US SPF_response_get_received_spf(spf_response); spf_result = US SPF_strresult(SPF_response_result(spf_response)); spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response); + spf_used_domain = sender_address && *sender_address + ? expand_string(US"$sender_address_domain") + : sender_helo_name; rc = SPF_response_result(spf_response); @@ -595,6 +599,7 @@ static var_entry spf_variables[] = { { "spf_result", vtype_stringptr, &spf_result }, { "spf_result_guessed", vtype_bool, &spf_result_guessed }, { "spf_smtp_comment", vtype_stringptr, &spf_smtp_comment }, + { "spf_used_domain", vtype_stringptr, &spf_used_domain }, }; misc_module_info spf_module_info = diff --git a/src/src/miscmods/spf_perl.c b/src/src/miscmods/spf_perl.c index 55741eef0..c30e0efe4 100644 --- a/src/src/miscmods/spf_perl.c +++ b/src/src/miscmods/spf_perl.c @@ -43,7 +43,8 @@ uschar * spf_smtp_comment = NULL; uschar * spf_smtp_comment_template /* Used to be: "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}" */ = US"Please%_see%_http://www.open-spf.org/Why"; -BOOL spf_result_guessed = FALSE; +BOOL spf_result_guessed = FALSE; +const uschar * spf_used_domain = NULL; static const misc_module_info * spf_perl_mi = NULL; @@ -196,6 +197,10 @@ else uschar * t = Ustrchr(s, ')'); /* grab a (comment) if there is one */ if (t) spf_header_comment = string_copyn(s+1, t-s-1); } + + spf_used_domain = sender_address && *sender_address + ? expand_string(US"$sender_address_domain") + : sender_helo_name; } res = match_isinlist(spf_result, &arglist, @@ -344,6 +349,7 @@ static var_entry spf_variables[] = { { "spf_result", vtype_stringptr, &spf_result }, { "spf_result_guessed", vtype_bool, &spf_result_guessed }, { "spf_smtp_comment", vtype_stringptr, &spf_smtp_comment }, + { "spf_used_domain", vtype_stringptr, &spf_used_domain }, }; misc_module_info spf_module_info = commit b064cc0ef055fdcb91c971234b756e494f64e347 Author: Jeremy Harris Date: Tue Dec 9 10:59:13 2025 +0000 DMARC: rework module API diff --git a/src/src/acl.c b/src/src/acl.c index b3c45668a..63478ca7d 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -3939,25 +3939,20 @@ for (; cb; cb = cb->next) /* See comment on ACLC_SPF wrt. coding issues */ { misc_module_info * mi = misc_mod_find(US"dmarc", &log_message); - typedef const uschar * (*efn_t)(void); - const uschar * expanded_query; + typedef int (*pfn_t)(void); + typedef int (*mfn_t)(const uschar * const *); if (!mi) - { rc = DEFER; break; } /* shouldn't happen */ - - if (!f.dmarc_has_been_checked) /* only once per message */ + rc = DEFER; /* shouldn't happen */ + else { - typedef int (*pfn_t)(void); - (void) (((pfn_t *) mi->functions)[DMARC_PROCESS]) (); - f.dmarc_has_been_checked = TRUE; + if (!f.dmarc_has_been_checked) /* only once per message */ + { + (void) (((pfn_t *) mi->functions)[DMARC_PROCESS]) (); + f.dmarc_has_been_checked = TRUE; + } + rc = (((mfn_t *) mi->functions)[DMARC_RESULT_INLIST]) (&arg); } - - /* used long way of dmarc_exim_expand_query() in case we need more - view into the process in the future. */ - - expanded_query = (((efn_t *) mi->functions)[DMARC_EXPAND_QUERY]) (); - rc = match_isinlist(expanded_query, - &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); } break; #endif diff --git a/src/src/globals.c b/src/src/globals.c index 610c72909..6c8e8ec05 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -838,6 +838,7 @@ volatile sig_atomic_t had_command_sigterm = 0; volatile sig_atomic_t had_data_timeout = 0; volatile sig_atomic_t had_data_sigint = 0; const uschar *headers_charset = US HEADERS_CHARSET; +const uschar *header_from = NULL; /* mainly for dmarc */ int header_insert_maxlen = 64 * 1024; header_line *header_last = NULL; header_line *header_list = NULL; diff --git a/src/src/globals.h b/src/src/globals.h index 1c133f5ea..977107f58 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -633,6 +633,7 @@ extern volatile sig_atomic_t had_command_timeout; /* Alarm sighandler called * extern volatile sig_atomic_t had_command_sigterm; /* TERM sighandler called */ extern volatile sig_atomic_t had_data_timeout; /* Alarm sighandler called */ extern volatile sig_atomic_t had_data_sigint; /* TERM/INT sighandler called */ +extern const uschar *header_from; /* Text of the From: header */ extern int header_insert_maxlen; /* Max for inserting headers */ extern int header_maxsize; /* Max total length for header */ extern int header_line_maxsize; /* Max for an individual line */ diff --git a/src/src/miscmods/dmarc.c b/src/src/miscmods/dmarc.c index a18100c81..5a5ea7398 100644 --- a/src/src/miscmods/dmarc.c +++ b/src/src/miscmods/dmarc.c @@ -161,7 +161,7 @@ is locally generated and relying on fixups to add it. Just skip the entire DMARC system if we can't find a From: header....or if there was a previous error. */ -if (!dmarc_from_header) +if (!header_from) { DEBUG(D_receive) debug_printf_indent("DMARC: no From: header\n"); dmarc_abort = TRUE; @@ -173,10 +173,10 @@ else if (!dmarc_abort) int dummy, domain; f.parse_allow_group = TRUE; - end_addr = parse_find_address_end(dmarc_from_header, FALSE); + end_addr = parse_find_address_end(header_from, FALSE); s = *end_addr - ? string_copyn(dmarc_from_header, end_addr - dmarc_from_header) - : dmarc_from_header; + ? string_copyn(header_from, end_addr - header_from) + : header_from; if ((dmarc_header_from_sender = parse_extract_address(s, &errormsg, &dummy, &dummy, &domain, FALSE))) dmarc_header_from_sender += domain; @@ -193,7 +193,7 @@ else if (!dmarc_abort) { log_write(0, LOG_MAIN|LOG_PANIC, "failure to store header From: in DMARC: %s, header was '%s'", - opendmarc_policy_status_to_str(libdm_status), dmarc_from_header); + opendmarc_policy_status_to_str(libdm_status), header_from); dmarc_abort = TRUE; } } @@ -335,7 +335,7 @@ The EDITME provides a DMARC_API variable */ DEBUG(D_receive) debug_printf_indent("DMARC skipping (%s), unsure what to do with %s", opendmarc_policy_status_to_str(libdm_status), - dmarc_from_header); + header_from); has_dmarc_record = FALSE; break; } @@ -458,14 +458,17 @@ dmarc_exim_expand_defaults(void) return f.dmarc_disable_verify ? US"off" : US"none"; } -/*API*/ -const uschar * -dmarc_exim_expand_query(void) +/*API +Check result against list. Return OK/FAIL/DEFER. +*/ +int +dmarc_result_inlist(const uschar * const * listp) { -if (f.dmarc_disable_verify || !dmarc_pctx) - return dmarc_exim_expand_defaults(); - -return dmarc_status; +const uschar * res = + f.dmarc_disable_verify || !dmarc_pctx + ? dmarc_exim_expand_defaults() + : dmarc_status; +return match_isinlist(res, listp, 0, NULL, NULL, MCL_STRING, TRUE, NULL); } diff --git a/src/src/miscmods/dmarc.h b/src/src/miscmods/dmarc.h index 6f8c456dd..fe3138cf2 100644 --- a/src/src/miscmods/dmarc.h +++ b/src/src/miscmods/dmarc.h @@ -120,7 +120,6 @@ extern BOOL dmarc_abort; extern uschar * dmarc_header_from_sender; extern uschar * dmarc_pass_fail; -extern const uschar * dmarc_from_header; extern const misc_module_info * dmarc_dkim_mod_info; extern const misc_module_info * dmarc_spf_mod_info; extern int dmarc_spf_ares_result; diff --git a/src/src/miscmods/dmarc_api.h b/src/src/miscmods/dmarc_api.h index 54b1630a6..e99b45804 100644 --- a/src/src/miscmods/dmarc_api.h +++ b/src/src/miscmods/dmarc_api.h @@ -12,5 +12,4 @@ /* Function table entry numbers */ #define DMARC_PROCESS 0 -#define DMARC_EXPAND_QUERY 1 -#define DMARC_STORE_FROMHDR 2 +#define DMARC_RESULT_INLIST 1 diff --git a/src/src/miscmods/dmarc_common.c b/src/src/miscmods/dmarc_common.c index 1669d8400..35b832c59 100644 --- a/src/src/miscmods/dmarc_common.c +++ b/src/src/miscmods/dmarc_common.c @@ -15,7 +15,7 @@ extern BOOL dmarc_local_init(void); extern void dmarc_local_msg_init(void); extern gstring * dmarc_version_report(gstring *); extern int dmarc_process(void); -extern const uschar * dmarc_exim_expand_query(void); +extern const uschar * dmarc_result_inlist(const uschar * const *); /* Other modules needed for services */ @@ -26,7 +26,6 @@ const misc_module_info * dmarc_arc_mod_info; /* Working data */ BOOL dmarc_abort; uschar * dmarc_pass_fail; /* for authres */ -const uschar * dmarc_from_header; uschar * dmarc_header_from_sender; /* results */ @@ -96,7 +95,6 @@ dmarc_msg_init(void) multiple messages in one connection. */ f.dmarc_has_been_checked = FALSE; -dmarc_from_header = NULL; dmarc_header_from_sender = NULL; dmarc_spf_ares_result = ARES_RESULT_UNDEFINED; dmarc_status = US"none"; @@ -140,18 +138,6 @@ dmarc_domain_policy = dmarc_status = dmarc_status_text = } -/* API: dmarc_store_data stores the header data so that subsequent dmarc_process -can access the data. Cleared by msg_init. -Called after the entire message has been received, with the From: header. */ - -static void -dmarc_store_fromhdr(const uschar * hdr) -{ -/* No debug output because would change every test debug output */ -dmarc_from_header = hdr; -} - - /* Accept an error_block struct, initialize if empty, parse to the end, and append the two strings passed to it. Used for adding variable amounts of value:pair data to the forensic emails. */ @@ -501,8 +487,7 @@ static optionlist dmarc_options[] = { static void * dmarc_functions[] = { [DMARC_PROCESS] = (void *) dmarc_process, - [DMARC_EXPAND_QUERY] = (void *) dmarc_exim_expand_query, - [DMARC_STORE_FROMHDR] = (void *) dmarc_store_fromhdr, + [DMARC_RESULT_INLIST] = (void *) dmarc_result_inlist, }; /* dmarc_forensic_sender is provided for visibility of the the option setting diff --git a/src/src/miscmods/dmarc_native.c b/src/src/miscmods/dmarc_native.c index f0c91868b..96628dee1 100644 --- a/src/src/miscmods/dmarc_native.c +++ b/src/src/miscmods/dmarc_native.c @@ -310,7 +310,7 @@ is locally generated and relying on fixups to add it. Just skip the entire DMARC system if we can't find a From: header....or if there was a previous error. */ -if (!dmarc_from_header) +if (!header_from) { DEBUG(D_receive) debug_printf_indent("DMARC: no From: header\n"); dmarc_abort = TRUE; @@ -323,10 +323,10 @@ else int dummy, domain; f.parse_allow_group = TRUE; - end_addr = parse_find_address_end(dmarc_from_header, FALSE); + end_addr = parse_find_address_end(header_from, FALSE); s = *end_addr - ? string_copyn(dmarc_from_header, end_addr - dmarc_from_header) - : dmarc_from_header; + ? string_copyn(header_from, end_addr - header_from) + : header_from; if ((dmarc_header_from_sender = parse_extract_address(s, &errormsg, &dummy, &dummy, &domain, FALSE))) dmarc_header_from_sender += domain; @@ -668,14 +668,15 @@ dmarc_exim_expand_defaults(void) return f.dmarc_disable_verify ? US"off" : US"none"; } -/*API*/ -const uschar * -dmarc_exim_expand_query(void) +/*API +Check result against list. Return OK/FAIL/DEFER. +*/ +int +dmarc_result_inlist(const uschar * const * listp) { -if (f.dmarc_disable_verify ) // || !dmarc_pctx) - return dmarc_exim_expand_defaults(); - -return dmarc_status; +const uschar * res = + f.dmarc_disable_verify ? dmarc_exim_expand_defaults() : dmarc_status; +return match_isinlist(res, listp, 0, NULL, NULL, MCL_STRING, TRUE, NULL); } diff --git a/src/src/receive.c b/src/src/receive.c index 7827d0288..f1833c5a0 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -1732,47 +1732,36 @@ not. */ BOOL receive_msg(BOOL extract_recip) { -int rc = FAIL; -int msg_size = 0; -int process_info_len = Ustrlen(process_info); +int rc = FAIL, msg_size = 0, process_info_len = Ustrlen(process_info); +int header_size = 256, had_zero = 0, prevlines_length = 0, ptr = 0; + int error_rc = error_handling == ERRORS_SENDER ? errors_sender_rc : EXIT_FAILURE; -int header_size = 256; -int had_zero = 0; -int prevlines_length = 0; const int id_resolution = BASE_62 == 62 && !host_number_string ? 1 : BASE_62 != 62 && host_number_string ? 4 : 2; -int ptr = 0; - -BOOL contains_resent_headers = FALSE; -BOOL extracted_ignored = FALSE; -BOOL first_line_ended_crlf = TRUE_UNSET; -BOOL smtp_yield = TRUE; -BOOL yield = FALSE; +BOOL contains_resent_headers = FALSE, extracted_ignored = FALSE; +BOOL first_line_ended_crlf = TRUE_UNSET, smtp_yield = TRUE, yield = FALSE; BOOL resents_exist = FALSE; -uschar *resent_prefix = US""; -uschar *blackholed_by = NULL; -uschar *blackhole_log_msg = US""; +uschar * resent_prefix = US"", * blackholed_by = NULL; +uschar * blackhole_log_msg = US""; enum {NOT_TRIED, TMP_REJ, PERM_REJ, ACCEPTED} cutthrough_done = NOT_TRIED; flock_t lock_data; -error_block *bad_addresses = NULL; +error_block * bad_addresses = NULL; -uschar *frozen_by = NULL; -uschar *queued_by = NULL; +uschar * frozen_by = NULL, * queued_by = NULL; -uschar *errmsg; +uschar * errmsg; rmark rcvd_log_reset_point; gstring * g; struct stat statbuf; /* Final message to give to SMTP caller, and messages from ACLs */ -uschar *smtp_reply = NULL; -uschar *user_msg, *log_msg; +uschar * smtp_reply = NULL, * user_msg, * log_msg; /* Working header pointers */ @@ -1808,6 +1797,7 @@ search_tidyup(); /* Extracting the recipient list from an input file is incompatible with cutthrough delivery with the no-spool option. It shouldn't be possible to set up the combination, but just in case kill any ongoing connection. */ + if (extract_recip || !smtp_input) cancel_cutthrough_connection(TRUE, US"not smtp input"); @@ -1815,7 +1805,8 @@ if (extract_recip || !smtp_input) header. Temporarily mark it as "old", i.e. not to be used. We keep header_last pointing to the end of the chain to make adding headers simple. */ -received_header = header_list = header_last = store_get(sizeof(header_line), GET_UNTAINTED); +received_header = header_list = header_last = + store_get(sizeof(header_line), GET_UNTAINTED); header_list->next = NULL; header_list->type = htype_old; header_list->text = NULL; @@ -1842,11 +1833,7 @@ received_count = 1; /* For the one we will add */ if (thismessage_size_limit <= 0) thismessage_size_limit = INT_MAX; -/* While reading the message, the following counts are computed. */ - -message_linecount = body_linecount = body_zerocount = - max_received_linelength = 0; - +header_from = NULL; #ifdef WITH_CONTENT_SCAN /* reset non-per-part mime variables */ mime_is_coverletter = 0; @@ -1857,6 +1844,11 @@ mime_part_count = -1; if (misc_mod_msg_init() != OK) goto CONN_GONE; +/* While reading the message, the following counts are computed. */ + +message_linecount = body_linecount = body_zerocount = + max_received_linelength = 0; + /* In SMTP sessions we may receive several messages in one connection. Before each subsequent one, we wait for the clock to tick at the level of message-id granularity. @@ -2170,7 +2162,7 @@ OVERSIZE: beyond it. If it turns out to be a real header, internal binary zeros will be squashed later. */ - next->text[ptr] = 0; + next->text[ptr] = '\0'; next->slen = ptr; store_release_above(next->text + ptr + 1); @@ -2483,17 +2475,7 @@ for (header_line * h = header_list->next; h; h = h->next) case htype_from: h->type = htype_from; -#ifdef EXIM_HAVE_DMARC - if (!is_resent && !f.dmarc_disable_verify) - { - misc_module_info * mi = misc_mod_findonly(US"dmarc"); - if (mi) - { - typedef void (*fn_t)(const uschar *); - (((fn_t *) mi->functions)[DMARC_STORE_FROMHDR]) (h->text); - } - } -#endif + if (!is_resent) header_from = h->text; if (!resents_exist || is_resent) { from_header = h; commit 6b109f69cfc8804105f373dd22936d7b9edd66f9 Author: Jeremy Harris Date: Sat Dec 13 11:58:46 2025 +0000 Debug: tweaks diff --git a/src/src/drtables.c b/src/src/drtables.c index 45b1ade16..bf1c3e530 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -287,7 +287,7 @@ void * dl; struct misc_module_info * mi; const char * errormsg; -EARLY_DEBUG(D_any, "loading module %q\n", name); +EARLY_DEBUG(D_any, "Loading module %q\n", name); if (!(dl = mod_open(name, US"miscmod", errstr))) { if (errstr) EARLY_DEBUG(D_any, " mod_open: %s\n", *errstr); @@ -313,7 +313,7 @@ if (mi->dyn_magic != MISC_MODULE_MAGIC) return FALSE; } -EARLY_DEBUG(D_lookup, "loaded %q\n", name); +EARLY_DEBUG(D_lookup, "Loaded module %q\n", name); misc_mod_add(mi); return mi; } diff --git a/src/src/readconf.c b/src/src/readconf.c index 37d9c6b85..7840fd0ac 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -3853,7 +3853,7 @@ else if ((errormsg = dlerror())) { log_write(0, LOG_MAIN|LOG_PANIC, - "%s does not appear to be a %s module (%s)\n", fname, class, errormsg); + "%s does not appear to be a %s module (%s)\n", fname, class, errormsg); dlclose(dl); break; } @@ -3864,7 +3864,8 @@ else store_pool = POOL_PERM; add_driver_info(info_anchor, di, size_of_info); store_pool = old_pool; - DEBUG(D_any) debug_printf("Loaded %s %s\n", d->driver_name, class); + DEBUG(D_any) + debug_printf("Loaded module %q (%s)\n", d->driver_name, class); closedir(dd); goto found; } commit acdc767008460bf5e86a1bf39b43e8da7bd6e90d Author: Andreas Metzler Date: Sat Dec 13 14:52:14 2025 +0000 Build: linker invocation ordering Apparently some linkers are sensitive to positioning of library args in the commandline diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile index 70dc886ab..b38fcd360 100755 --- a/src/scripts/Configure-Makefile +++ b/src/scripts/Configure-Makefile @@ -313,7 +313,13 @@ fi cp ../src/lookups/Makefile $look_mf_pre EGREP="$egrep" ../scripts/lookups-Makefile -# make the Makefiles for routers, transports, auths and miscmods +# Make the Makefiles for routers, transports, auths and miscmods +# +# Columns in the here-file: +# 1 "class" the subdir name to work in +# 2 "classdef" is a prefix on the driver name, used for pulling +# construction data from Local/Makefile +# (remainder) "names" a list of driver names, in caps # An _ prefix on a name means the control in Local/Makefile is DISABLED_ # while read class classdef names diff --git a/src/src/auths/Makefile b/src/src/auths/Makefile index 6eab96a66..44ca8d8fb 100644 --- a/src/src/auths/Makefile +++ b/src/src/auths/Makefile @@ -31,8 +31,8 @@ auths.a: $(OBJ) SO_FLAGS = -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS) .c.so:; @echo "$(CC) -shared $*.c" - $(FE)$(CC) $(SO_FLAGS) $(AUTH_$*_INCLUDE) $(AUTH_$*_LIBS) \ - $*.c -o $@ + $(FE)$(CC) $(SO_FLAGS) $(AUTH_$*_INCLUDE) $*.c $(AUTH_$*_LIBS) \ + -o $@ $(OBJ) $(MOD): $(HDRS) diff --git a/src/src/lookups/Makefile b/src/src/lookups/Makefile index 27ae3fe1e..560a5cba5 100644 --- a/src/src/lookups/Makefile +++ b/src/src/lookups/Makefile @@ -48,7 +48,9 @@ lookups.a: avail_static_lookups.o $(OBJS) $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c .c.so:; @echo "$(CC) -shared $*.c" - $(FE)$(CC) $(LOOKUP_$*_INCLUDE) $(LOOKUP_$*_LIBS) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS) $*.c -o $@ + $(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) \ + $(LOOKUP_$*_INCLUDE) $(INCLUDE) $(DLFLAGS) $*.c \ + $(LOOKUP_$*_LIBS) -o $@ cdb.o cdb.so: $(HDRS) cdb.c dbmdb.o dbmdb.so: $(HDRS) dbmdb.c diff --git a/src/src/miscmods/Makefile b/src/src/miscmods/Makefile index e92c34941..2d44059ba 100644 --- a/src/src/miscmods/Makefile +++ b/src/src/miscmods/Makefile @@ -27,9 +27,9 @@ miscmods.a: $(OBJ) $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c .c.so:; @echo "$(CC) -shared $*.c" - $(FE)$(CC) $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) \ - -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) \ - $(DLFLAGS) $*.c -o $@ + $(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) \ + $(SUPPORT_$*_INCLUDE) $(INCLUDE) $(DLFLAGS) $*.c \ + $(SUPPORT_$*_LIBS) -o $@ # Note that the sources from pdkim/ are linked into the build.../miscmods/ dir # by scripts/Makelinks. @@ -68,10 +68,10 @@ dkim.o: # Similarly, we want a single .so for the dynamic-load module dkim.so: @echo "$(CC) -shared dkim.c dkim_transport.c pdkim.c signing.c" - $(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) -o $@ \ - $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) \ - $(CFLAGS) $(INCLUDE) $(TLS_INCLUDE) $(DLFLAGS) \ - dkim.c dkim_transport.c pdkim.c signing.c + $(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) \ + $(SUPPORT_$*_INCLUDE) $(INCLUDE) $(TLS_INCLUDE) $(DLFLAGS) \ + dkim.c dkim_transport.c pdkim.c signing.c $(SUPPORT_$*_LIBS) \ + -o $@ # spf_perl is special; Configure-Makefile asks for it but we actually # build resulting in a file named spf.so because the module-loading @@ -81,9 +81,9 @@ dkim.so: spf_perl.so: @echo "$(CC) -shared $*.c" - $(FE)$(CC) $(SUPPORT_SPF_PERL_INCLUDE) $(SUPPORT_SPF_PERL_LIBS) \ - -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) \ - $(DLFLAGS) spf_perl.c -o spf.so + $(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) \ + $(SUPPORT_SPF_PERL_INCLUDE) $(INCLUDE) $(DLFLAGS) \ + spf_perl.c $(SUPPORT_SPF_PERL_LIBS) -o spf.so # DMARC dmarc.o dmarc_native.o: @@ -96,10 +96,9 @@ dmarc.o dmarc_native.o: # dmarc_native is special in the same way as spf_perl dmarc.so dmarc_native.so: @echo "$(CC) -shared $*.c dmarc_common.c" - $(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) -o dmarc.so \ - $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) \ - $(CFLAGS) $(INCLUDE) $(TLS_INCLUDE) $(DLFLAGS) \ - $*.c dmarc_common.c + $(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) \ + $(SUPPORT_$*_INCLUDE) $(INCLUDE) $(TLS_INCLUDE) $(DLFLAGS) \ + $*.c dmarc_common.c $(SUPPORT_$*_LIBS) -o dmarc.so # Compile instructions for static perl.o for when EXIM_PERL is set # Dynamic is managed all via scripts/Configure-Makefile diff --git a/src/src/routers/Makefile b/src/src/routers/Makefile index 1b2953d40..32515fcf8 100644 --- a/src/src/routers/Makefile +++ b/src/src/routers/Makefile @@ -26,7 +26,8 @@ routers.a: $(OBJ) $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c .c.so:; @echo "$(CC) -shared $*.c" - $(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS) $*.c -o $@ + $(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) \ + $(DLFLAGS) $*.c -o $@ commit d9081efaca19d0bef80c0dbc3ffa045207f3171e Author: Jeremy Harris Date: Sat Dec 13 23:52:08 2025 +0000 Re-order module init in "-bV" support diff --git a/src/src/exim.c b/src/src/exim.c index d35168d60..693301899 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -1334,11 +1334,13 @@ Currently they are output in misc_mod_add() */ show_string(is_stdout, g); g = NULL; + /* Has to be before the lookups as the spf lookup calls into the spf module */ + init_misc_mod_list(); + init_lookup_list(); tree_walk(lookups_tree, lookup_version_report_cb, &g); show_string(is_stdout, g); g = NULL; - init_misc_mod_list(); #ifdef WHITELIST_D_MACROS g = string_fmt_append(g, "WHITELIST_D_MACROS: %q\n", WHITELIST_D_MACROS); commit 4a11617127599c2a40552f5f7e9bf0e863f10e8d Merge: d9081efac d46a67277 Author: Heiko Schlittermann (HS12-RIPE) Date: Tue Dec 16 12:55:32 2025 +0100 Merge branch 'exim-4.99+fixes' (EXIM-Security-2025-12-09.1, CVE-2025-67896) * exim-4.99+fixes: doc: update about CVE-2025-67896 fix: release process docbook2texi EXIM-Security-2025-12-09.1 doc: update Changelog (relates #7) Testsuite: add testing for sqlite db external usage return scan result into key parameter move to blobs for keys as well as data. refactor sqlite3_ calls to reduce duplication new: xtextencode CVE-2025-30232 Squashed from fix/cve-2025-26794 (fixes CVE-26794) diff --git a/src/src/hintsdb.h b/src/src/hintsdb.h index d2d30969b..9a65d8b19 100644 --- a/src/src/hintsdb.h +++ b/src/src/hintsdb.h @@ -67,6 +67,7 @@ required by Exim's process transitions)? /* Include file ordering problem */ extern void debug_printf_indent(const char *, ...) PRINTF_FUNCTION(1,2); +static inline BOOL is_tainted(const void *); #ifdef USE_SQLITE @@ -102,15 +103,8 @@ the default is the NDBM interface (which seems to be a wrapper for GDBM) */ #endif /* !USE_GDBM */ - - - - /* 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) diff --git a/src/src/hintsdb/hints_sqlite.h b/src/src/hintsdb/hints_sqlite.h index 40d50b8c0..dd88dd98b 100644 --- a/src/src/hintsdb/hints_sqlite.h +++ b/src/src/hintsdb/hints_sqlite.h @@ -17,7 +17,7 @@ backend provider. */ /* Basic DB type */ # define EXIM_DB sqlite3 -# define EXIM_CURSOR int +# define EXIM_CURSOR sqlite3_stmt # /* The datum type used for queries */ # define EXIM_DATUM blob @@ -25,12 +25,6 @@ backend provider. */ /* Some text for messages */ # define EXIM_DBTYPE "sqlite3" -/* Utility functionss */ - -extern uschar *xtextencode(const uschar *, int); -extern int xtextdecode(const uschar *, uschar**); - - /* Access functions */ static inline BOOL @@ -54,10 +48,12 @@ if ((ret = sqlite3_open_v2(CCS name, &dbp, sflags, NULL)) == SQLITE_OK) sqlite3_busy_timeout(dbp, 5000); if (flags & O_CREAT) ret = sqlite3_exec(dbp, - "CREATE TABLE IF NOT EXISTS tbl (ky TEXT PRIMARY KEY, dat BLOB);", + "CREATE TABLE IF NOT EXISTS tblblob (ky BLOB PRIMARY KEY, dat BLOB);", NULL, NULL, NULL); if (ret != SQLITE_OK) sqlite3_close(dbp); + /* in case we are migrating, drop the old table, return code not needed */ + (void) sqlite3_exec(dbp, "DROP TABLE IF EXISTS tbl;", NULL, NULL, NULL); } else DEBUG(D_hints_lookup) debug_printf_indent("sqlite_open(flags 0x%x mode %04o) %s\n", @@ -82,148 +78,143 @@ sqlite3_close(dbp); return 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) +static inline sqlite3_stmt * +exim_sqlbind_blob(EXIM_DB * dbp, sqlite3_stmt *stmt, int *bindcolp, EXIM_DATUM *data ) { -sqlite3_stmt * statement; -int ret; - -res->len = (size_t) -1; -/* DEBUG(D_hints_lookup) debug_printf_indent("exim_dbget__(%s)\n", s); */ -if ((ret = sqlite3_prepare_v2(dbp, CCS s, -1, &statement, NULL)) != SQLITE_OK) +if (data && stmt) { - DEBUG(D_hints_lookup) - debug_printf_indent("prepare fail: %s\n", sqlite3_errmsg(dbp)); - return FALSE; - } -if (sqlite3_step(statement) != SQLITE_ROW) - { - /* DEBUG(D_hints_lookup) - debug_printf_indent("step fail: %s\n", sqlite3_errmsg(dbp)); */ - sqlite3_finalize(statement); - return FALSE; - } - -res->len = sqlite3_column_bytes(statement, 0); -# ifdef COMPILE_UTILITY -if (!(res->data = malloc(res->len +1))) - { sqlite3_finalize(statement); return FALSE; } -# else -res->data = store_get(res->len +1, GET_TAINTED); + if (SQLITE_OK != sqlite3_bind_blob(stmt, *bindcolp, data->data, data->len, SQLITE_STATIC)) + { +# ifdef SQL_DEBUG + fprintf(stderr, EXIM_DBTYPE " bind to value %d: %s\n", *bindcolp, sqlite3_errmsg(dbp)); # endif -memcpy(res->data, sqlite3_column_blob(statement, 0), res->len); -res->data[res->len] = '\0'; -/* DEBUG(D_hints_lookup) debug_printf_indent("res %d bytes: '%.*s'\n", - (int)res->len, (int)res->len, res->data); */ -sqlite3_finalize(statement); -return TRUE; + sqlite3_finalize(stmt); + stmt = NULL; + } + else + { + (*bindcolp)++; + } + } +return stmt; } -static inline BOOL -exim_dbget(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res) +/* We use a common prepare/bind mechanism with optional value binding */ +static inline sqlite3_stmt * +exim_sqlprep(EXIM_DB * dbp, const char *query, EXIM_DATUM *key, EXIM_DATUM *data ) { -# define FMT "SELECT dat FROM tbl WHERE ky = '%s';" -uschar * encoded_key, * qry; -BOOL ret; +sqlite3_stmt * stmt = NULL; /* don't make it static, as it depends on the dbp */ +int bindcol = 1; -# ifdef COMPILE_UTILITY -if (!(encoded_key = xtextencode(key->data, key->len))) - return FALSE; -# else -encoded_key = xtextencode(key->data, key->len); +if (SQLITE_OK != sqlite3_prepare_v2(dbp, query, strlen(query), &stmt, NULL)) + { +# ifdef SQL_DEBUG + fprintf(stderr, EXIM_DBTYPE " prepare %s: %s\n", query, sqlite3_errmsg(dbp)); # endif -/* DEBUG(D_hints_lookup) debug_printf_indent("exim_dbget(k len %d '%s')\n", - (int)key->len, encoded_key); */ + goto DONE; + } -# ifdef COMPILE_UTILITY +# ifdef SQL_DEBUG +DEBUG(D_hints_lookup) debug_printf_indent("prepared SQL: %s\n", sqlite3_sql(stmt)); +# endif + +stmt = exim_sqlbind_blob(dbp, stmt, &bindcol, key); +stmt = exim_sqlbind_blob(dbp, stmt, &bindcol, data); + +# ifdef SQL_DEBUG +if (stmt) { - int i = snprintf(NULL, 0, FMT, encoded_key) + 1; - if (!(qry = malloc(i))) - return FALSE; - snprintf(CS qry, i, FMT, encoded_key); - ret = exim_dbget__(dbp, qry, res); - free(qry); + DEBUG(D_hints_lookup) debug_printf_indent("expanded SQL: %s\n", sqlite3_expanded_sql(stmt)); } -free(encoded_key); -# else -qry = string_sprintf(FMT, encoded_key); -ret = exim_dbget__(dbp, qry, res); # endif -return ret; -# undef FMT -} +DONE: -/* Note that we return claiming a duplicate record for any error. -It seem not uncommon to get a "database is locked" error. +return stmt; +} -Keys are stored xtext-encoded (which is mostly readable, for plaintext). -Values are stored in a BLOB type in the DB, for which the SQL interface -is hex-encoded. */ -# define EXIM_DBPUTB_OK 0 -# define EXIM_DBPUTB_DUP (-1) static inline int -exim_s_dbp(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data, const uschar * alt) +exim_sqlstep(EXIM_DB * dbp, sqlite3_stmt * stmt, EXIM_DATUM *res ) { -int hlen = data->len * 2, off = 0, res; -# define FMT "INSERT OR %s INTO tbl (ky,dat) VALUES ('%s', X'%.*s');" -uschar * encoded_key, * qry; +int rv = SQLITE_MISUSE; + +switch ((rv = sqlite3_step(stmt))) + { + case SQLITE_DONE: /* might want to call sqlite3_reset(stmt); here */ + goto DONE; + case SQLITE_ROW: if (!res) + { + /* allow for fetch but didn't want data (existence check ?) */ + goto DONE; + } + res->len = sqlite3_column_bytes(stmt, 0); # ifdef COMPILE_UTILITY -uschar * hex = malloc(hlen+1), dummy[1]; -if (!hex) return EXIM_DBPUTB_DUP; /* best we can do */ + res->data = malloc(res->len +1); + if (! res->data ) goto DONE; # else -uschar * hex = store_get(hlen+1, data->data); + res->data = store_get(res->len +1, GET_TAINTED); +# endif + memcpy(res->data, sqlite3_column_blob(stmt, 0), res->len); + res->data[res->len] = '\0'; + goto DONE; + default: +# ifdef SQL_DEBUG + fprintf(stderr, EXIM_DBTYPE " step: %s: %s\n", sqlite3_expanded_sql(stmt), sqlite3_errmsg(dbp)); # endif + goto DONE; + } -/* Encode the value for the SQL API */ +DONE: +return rv; +} -for (const uschar * s = data->data, * t = s + data->len; s < t; s++, off += 2) - sprintf(CS hex + off, "%02X", *s); -# ifdef COMPILE_UTILITY -if (!(encoded_key = xtextencode(key->data, key->len))) - return EXIM_DBPUTB_DUP; -res = snprintf(CS dummy, 0, FMT, alt, encoded_key, hlen, hex) +1; -if (!(qry = malloc(res))) return EXIM_DBPUTB_DUP; -snprintf(CS qry, res, FMT, alt, encoded_key, hlen, hex); -DEBUG(D_hints_lookup) debug_printf_indent("exim_s_dbp(%s)\n", qry); - -res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL); -free(qry); -free(encoded_key); -free(hex); +/* simplest case when updating a single row or fetching a single row */ +static inline int +exim_sqlprep_step(EXIM_DB * dbp, const char *query, EXIM_DATUM *key, EXIM_DATUM *data, EXIM_DATUM *res ) +{ +int more = SQLITE_ERROR; +sqlite3_stmt * stmt = NULL; /* don't make it static, as it depends on the dbp */ -# else -encoded_key = xtextencode(key->data, key->len); -qry = string_sprintf(FMT, alt, encoded_key, hlen, hex); -/* DEBUG(D_hints_lookup) debug_printf_indent("exim_s_dbp(%s)\n", qry); */ +if ((stmt = exim_sqlprep(dbp, query, key, data ))) + { + more = exim_sqlstep(dbp, stmt, res ); + sqlite3_finalize(stmt); + stmt = NULL; + } +return more; +} -res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL); -/* DEBUG(D_hints_lookup) debug_printf_indent("exim_s_dbp res %d\n", res); */ -# endif +/* EXIM_DBGET - returns the value associated with the key. */ +static inline BOOL +exim_dbget(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res) +{ +const char query[] = "SELECT dat FROM tblblob WHERE ky = ?"; +return (exim_sqlprep_step(dbp, query, key, NULL, res ) == SQLITE_ROW); +} -# ifdef COMPILE_UTILITY -if (res != SQLITE_OK) - DEBUG(D_hints_lookup) - debug_printf_indent("sqlite3_exec: %s\n", sqlite3_errmsg(dbp)); -# endif -return res == SQLITE_OK ? EXIM_DBPUTB_OK : EXIM_DBPUTB_DUP; -# undef FMT +/* Note that we return claiming a duplicate record for any error. + * It seem not uncommon to get a "database is locked" error. +*/ +# define EXIM_DBPUTB_OK 0 +# define EXIM_DBPUTB_DUP (-1) + +static inline int +exim_s_dbp(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data, const char * sql) +{ +return (SQLITE_DONE == exim_sqlprep_step(dbp, sql, key, data, NULL )) ? EXIM_DBPUTB_OK : EXIM_DBPUTB_DUP; } /* EXIM_DBPUT - returns nothing useful, assumes replace mode */ - static inline int exim_dbput(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data) { -/* DEBUG(D_hints_lookup) debug_printf_indent("exim_dbput()\n"); */ -(void) exim_s_dbp(dbp, key, data, US"REPLACE"); +# ifdef SQL_DEBUG +DEBUG(D_hints_lookup) debug_printf_indent(EXIM_DBTYPE " put: key:%.*W data:%.*W\n", key->len, key->data, data->len, data->data ); +# endif +(void) exim_s_dbp(dbp, key, data, "INSERT OR REPLACE INTO tblblob (ky, dat) VALUES(?, ?)"); return 0; } @@ -234,110 +225,52 @@ return 0; static inline int exim_dbputb(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data) { -return exim_s_dbp(dbp, key, data, US"ABORT"); +return exim_s_dbp(dbp, key, data, "INSERT OR ABORT INTO tblblob (ky, dat) VALUES(?, ?)"); } /* EXIM_DBDEL */ static inline int exim_dbdel(EXIM_DB * dbp, EXIM_DATUM * key) { -# define FMT "DELETE FROM tbl WHERE ky = '%s';" -uschar * encoded_key, * qry; -int res; - -# ifdef COMPILE_UTILITY -if (!(encoded_key = xtextencode(key->data, key->len))) - return EXIM_DBPUTB_DUP; -res = snprintf(NULL, 0, FMT, encoded_key) +1; /* res includes nul */ -if (!(qry = malloc(res))) return SQLITE_NOMEM; -snprintf(CS qry, res, FMT, encoded_key); -res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL); -free(qry); - -# else -encoded_key = xtextencode(key->data, key->len); -qry = string_sprintf(FMT, encoded_key); -res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL); - -# endif - -return res == SQLITE_OK ? EXIM_DBPUTB_OK : EXIM_DBPUTB_DUP; -# undef FMT +const char query[] = "DELETE FROM tblblob WHERE ky = ?"; +return (SQLITE_DONE == exim_sqlprep_step(dbp, query, key, NULL, NULL )) ? 0 : -1; } - /* EXIM_DBCREATE_CURSOR - initialize for scanning operation */ -/* Cursors are inefficiently emulated by repeating searches */ static inline EXIM_CURSOR * exim_dbcreate_cursor(EXIM_DB * dbp) { -# ifdef COMPILE_UTILITY -EXIM_CURSOR * c = malloc(sizeof(int)); -if (!c) return NULL; -# else -EXIM_CURSOR * c = store_malloc(sizeof(int)); +EXIM_CURSOR * cursor; + +cursor = exim_sqlprep(dbp, "SELECT ky FROM tblblob ORDER BY ky", NULL, NULL ); +if (!cursor) return NULL; +# ifdef SQL_DEBUG +DEBUG(D_hints_lookup) debug_printf_indent("prepared query: %s\n", sqlite3_sql(cursor)); # endif -*c = 0; -return c; + +return cursor; } /* EXIM_DBSCAN */ -/* Note that we return the (next) key, not the record value. -We allocate memory for the return. */ - +/* Note that we return the (next) key into the key parameter, not the res parameter. */ 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 /* unused */, BOOL first /*unused*/, EXIM_CURSOR * cursor) { -# define FMT "SELECT ky FROM tbl ORDER BY ky LIMIT 1 OFFSET %d;" -uschar * qry; -EXIM_DATUM encoded_key; -BOOL ret; - -# ifdef COMPILE_UTILITY -int i = snprintf(NULL, 0, FMT, *cursor)+1; - -if (!(qry = malloc(i))) return FALSE; -snprintf(CS qry, i, FMT, *cursor); -DEBUG(D_hints_lookup) debug_printf_indent("exim_dbscan(%s)\n", qry); -ret = exim_dbget__(dbp, qry, &encoded_key); -free(qry); - -# else /*!COMPILE_UTILITY*/ -qry = string_sprintf(FMT, *cursor); -DEBUG(D_hints_lookup) debug_printf_indent("exim_dbscan(%s)\n", qry); -ret = exim_dbget__(dbp, qry, &encoded_key); - -# endif /*COMPILE_UTILITY*/ - -DEBUG(D_hints_lookup) - debug_printf_indent("exim_dbscan ret %c\n", ret ? 'T':'F'); - -if (ret) - { - key->len = xtextdecode(encoded_key.data, &key->data); - *cursor = *cursor + 1; - } -return ret; -# undef FMT +return (exim_sqlstep(dbp, cursor, key )==SQLITE_ROW); } /* EXIM_DBDELETE_CURSOR - terminate scanning operation. */ static inline void exim_dbdelete_cursor(EXIM_CURSOR * cursor) { -# ifdef COMPILE_UTILITY -free(cursor); -# else -store_free(cursor); -# endif +if(cursor) + sqlite3_finalize(cursor); } - /* EXIM_DBCLOSE */ static inline void -exim_dbclose_multi__(EXIM_DB * dbp) +exim_dbclose_multi__(EXIM_DB *dbp) { sqlite3_close(dbp); } @@ -359,19 +292,19 @@ exim_dbclose_multi__(dbp); static inline uschar * exim_datum_data_get(EXIM_DATUM * dp) { return US dp->data; } -static inline void + +static void exim_datum_data_set(EXIM_DATUM * dp, void * s) { dp->data = s; } - -static inline unsigned + +static unsigned exim_datum_size_get(EXIM_DATUM * dp) { return dp->len; } + static inline 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 */ @@ -386,7 +319,7 @@ exim_datum_free(EXIM_DATUM * dp) # define EXIM_DB_RLIMIT 150 - /* End of hints_sqlite.h */ /* vi: aw ai sw=2 + * */ commit 227e467d611d9c5bcb942f2b2635e4466a8c7fd6 Author: Jeremy Harris Date: Mon Dec 15 13:43:19 2025 +0000 Fast-ramp: do not notify daemon during high load-avg diff --git a/src/src/queue.c b/src/src/queue.c index 97238ba1a..46f7f3d57 100644 --- a/src/src/queue.c +++ b/src/src/queue.c @@ -1695,6 +1695,13 @@ int fd; DEBUG(D_queue_run) debug_printf("%s: %s\n", __FUNCTION__, msgid); +if ( deliver_queue_load_max >= 0 + && os_getloadavg() > deliver_queue_load_max) + { + DEBUG(D_queue_run) debug_printf(" - avoided due to load-avg\n"); + return; + } + buf[0] = NOTIFY_MSG_QRUN; memcpy(buf+1, msgid, MESSAGE_ID_LENGTH+1); Ustrcpy(buf+1+MESSAGE_ID_LENGTH+1, queue_name); commit d09946f296565ebdd640b07d9bee0b30283d186d Author: Jeremy Harris Date: Mon Dec 15 15:32:12 2025 +0000 Fix DISABLE_ESMTP_LIMITS build diff --git a/src/src/globals.c b/src/src/globals.c index 6c8e8ec05..6d2b27227 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -648,6 +648,7 @@ unsigned continue_flags = 0; unsigned continue_limit_mail = 0; unsigned continue_limit_rcpt = 0; unsigned continue_limit_rcptdom= 0; +#endif int continue_fd = -1; uschar *continue_proxy_cipher = NULL; BOOL continue_proxy_dane = FALSE; @@ -662,7 +663,6 @@ const uschar *continue_transport = NULL; open_db *continue_retry_db = NULL; open_db *continue_wait_db = NULL; #endif -#endif uschar *csa_status = NULL; cut_t cutthrough = { /* All remaining items 0/FALSE/NULL */ diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 1f2161ae2..8ea3d882a 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -4051,9 +4051,7 @@ uschar * message = NULL; smtp_context * sx = store_get(sizeof(*sx), GET_TAINTED); BOOL pass_message = FALSE; -#ifndef DISABLE_ESMTP_LIMITS BOOL mail_limit = FALSE; -#endif #ifdef SUPPORT_DANE BOOL dane_held; #endif @@ -4971,7 +4969,6 @@ DEBUG(D_transport) sx->send_rset, f.continue_more, yield, sx->first_addr ? "not " : ""); if (sx->completed_addr && sx->ok && sx->send_quit) -#ifndef DISABLE_ESMTP_LIMITS if (mail_limit = continue_sequence >= sx->max_mail) { DEBUG(D_transport) @@ -4984,7 +4981,6 @@ if (sx->completed_addr && sx->ok && sx->send_quit) process. */ } else -#endif { BOOL send_rst; smtp_compare_t t_compare = @@ -5299,7 +5295,6 @@ if (dane_held) } #endif -#ifndef DISABLE_ESMTP_LIMITS if (mail_limit && sx->first_addr) { /* Reset the sequence count since we closed the connection. This is flagged @@ -5309,7 +5304,6 @@ if (mail_limit && sx->first_addr) continue_sequence = 1; /* for consistency */ goto REPEAT_CONN; /* open a fresh connection */ } -#endif OUT: smtp_debug_cmd_report(); commit 5103de3423da39e0edcbd556ab2633c8d5c09c5f Author: Jeremy Harris Date: Wed Dec 17 15:56:32 2025 +0000 dscp module diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile index b38fcd360..9b766d6ee 100755 --- a/src/scripts/Configure-Makefile +++ b/src/scripts/Configure-Makefile @@ -332,7 +332,7 @@ done <<-END routers ROUTER ACCEPT DNSLOOKUP IPLITERAL IPLOOKUP MANUALROUTE QUERYPROGRAM REDIRECT transports TRANSPORT APPENDFILE AUTOREPLY LMTP PIPE QUEUEFILE SMTP auths AUTH CRAM_MD5 CYRUS_SASL DOVECOT EXTERNAL GSASL HEIMDAL_GSSAPI PLAINTEXT SPA TLS - miscmods SUPPORT ARC _DKIM DMARC DMARC_NATIVE _EXIM_FILTER PAM PERL RADIUS _SIEVE_FILTER SPF SPF_PERL + miscmods SUPPORT ARC _DKIM DMARC DMARC_NATIVE DSCP _EXIM_FILTER PAM PERL RADIUS _SIEVE_FILTER SPF SPF_PERL END # See if there is a definition of EXIM_PERL in what we have built so far. diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index 517310c8a..35b58ec04 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -90,7 +90,7 @@ done cd .. # miscellaneous modules -# Note that the file in the miscmods/pdkim/ source subdir get linked to the +# Note that the files in the miscmods/pdkim/ source subdir get linked to the # destination miscmods/ dir d="miscmods" mkdir $d @@ -102,6 +102,7 @@ for f in dummy.c \ pdkim/crypt_ver.h pdkim/pdkim.c pdkim/pdkim.h \ pdkim/pdkim_hash.h pdkim/signing.c pdkim/signing.h \ dmarc.c dmarc_common.c dmarc.h dmarc_api.h dmarc_native.c \ + dscp.c dscp_api.h \ exim_filter.c exim_filter_api.h \ pam.c pam_api.h \ perl.c perl_api.h \ diff --git a/src/src/EDITME b/src/src/EDITME index 1c9d834dd..98baae7d5 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -577,6 +577,9 @@ SUPPORT_DANE=yes # SUPPORT_SIEVE_FILTER=2 +# Uncomment the line to include DSCP support. Set to 2 for a module build. +# SUPPORT_DSCP + #------------------------------------------------------------------------------ # Compiling Exim with content scanning support: If you want to compile Exim # with support for message body content scanning, set WITH_CONTENT_SCAN to diff --git a/src/src/acl.c b/src/src/acl.c index 63478ca7d..bc4f6af6d 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -3498,44 +3498,18 @@ for (; cb; cb = cb->next) f.dmarc_enable_forensic = TRUE; break; #endif - +#ifdef SUPPORT_DSCP case CONTROL_DSCP: - if (*p == '/') - { - int af, socklevel, optname, value; - /* If we are acting on stdin, the setsockopt may fail if stdin is not - a socket; we can accept that, we'll just debug-log failures anyway. */ - if (smtp_in_fd < 0) return ERROR; - if ((af = ip_get_address_family(smtp_in_fd)) < 0) - { - HDEBUG(D_acl) - debug_printf_indent("smtp input is probably not a socket [%s], not setting DSCP\n", - strerror(errno)); - break; - } - if (dscp_lookup(p+1, af, &socklevel, &optname, &value)) - if (setsockopt(smtp_in_fd, socklevel, optname, - &value, sizeof(value)) < 0) - { - HDEBUG(D_acl) debug_printf_indent("failed to set input DSCP[%s]: %s\n", - p+1, strerror(errno)); - } - else - { - HDEBUG(D_acl) debug_printf_indent("set input DSCP to %q\n", p+1); - } - else - { - *log_msgptr = string_sprintf("unrecognised DSCP value in \"control=%s\"", arg); - return ERROR; - } - } - else - { - *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); - return ERROR; - } + { + misc_module_info * mi = misc_mod_find(US"dscp", &log_message); + typedef uschar * (*fn_t)(const uschar *, const uschar *); + if (!mi) + rc = DEFER; + else if ((*log_msgptr = ((fn_t *) mi->functions)[DSCP_ACL] (arg, p))) + rc = ERROR; break; + } +#endif case CONTROL_ERROR: return ERROR; diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults index 71146ae3b..ba9f50b01 100644 --- a/src/src/config.h.defaults +++ b/src/src/config.h.defaults @@ -155,6 +155,7 @@ Do not put spaces between # and the 'define'. #define SUPPORT_DMARC #define DMARC_API 100400 #define DMARC_TLD_FILE "/etc/exim/opendmarc.tlds" +#define SUPPORT_DSCP #define SUPPORT_I18N #define SUPPORT_I18N_2008 #define SUPPORT_MAILDIR diff --git a/src/src/drtables.c b/src/src/drtables.c index bf1c3e530..e21d47047 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -356,7 +356,7 @@ return NULL; int misc_mod_conn_init(const uschar * sender_helo_name, - const uschar * sender_host_address, const uschar * * errstr) + const uschar * sender_host_address, const uschar ** errstr) { for (const misc_module_info * mi = misc_module_list; mi; mi = mi->next) if (mi->conn_init) @@ -495,6 +495,9 @@ extern misc_module_info spf_module_info; #if defined(EXPERIMENTAL_ARC) && (!defined(SUPPORT_ARC) || SUPPORT_ARC!=2) extern misc_module_info arc_module_info; #endif +#if defined(SUPPORT_DSCP) && SUPPORT_DSCP!=2 +extern misc_module_info dscp_module_info; +#endif #if defined(RADIUS_CONFIG_FILE) && (!defined(SUPPORT_RADIUS) || SUPPORT_RADIUS!=2) extern misc_module_info radius_module_info; #endif @@ -531,6 +534,9 @@ onetime = TRUE; /* dmarc depends on spf/dkim/arc so this add must go after, for the both-static case */ misc_mod_add(&dmarc_module_info); #endif +#if defined(SUPPORT_DSCP) && SUPPORT_DSCP!=2 + misc_mod_add(&dscp_module_info); +#endif #if defined(RADIUS_CONFIG_FILE) && (!defined(SUPPORT_RADIUS) || SUPPORT_RADIUS!=2) misc_mod_add(&radius_module_info); #endif diff --git a/src/src/exim.c b/src/src/exim.c index 693301899..383fe8247 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -1186,6 +1186,9 @@ g = string_cat(g, US"Support for:"); #ifndef DISABLE_DNSSEC g = string_cat(g, US" DNSSEC"); #endif +#ifdef SUPPORT_DSCP + g = string_cat(g, US" DSCP"); +#endif #ifndef DISABLE_ESMTP_LIMITS g = string_cat(g, US" ESMTP_Limits"); #endif @@ -1382,7 +1385,9 @@ switch(request) "If the string is not recognised, you'll get this help (on stderr).\n" "\n" " exim -bI:help this information\n" +#ifdef SUPPORT_DSCP " exim -bI:dscp list of known dscp value keywords\n" +#endif " exim -bI:sieve list of supported sieve extensions\n" ); return; @@ -1396,9 +1401,16 @@ switch(request) fprintf(stream, "Sieve filtering not available\n"); } return; +#ifdef SUPPORT_DSCP case CMDINFO_DSCP: - dscp_list_to_stream(stream); + { + uschar * dummy_errstr; + misc_module_info * mi = misc_mod_find(US"dscp", &dummy_errstr); + typedef void (*fn_t)(FILE *); + if (mi) ((fn_t *) mi->functions)[DSCP_KEYWORDS] (stream); + } return; +#endif } } @@ -2419,15 +2431,11 @@ on the second character (the one after '-'), to save some effort. */ info_flag = CMDINFO_HELP; if (Ustrlen(p)) if (strcmpic(p, CUS"sieve") == 0) - { - info_flag = CMDINFO_SIEVE; - info_stdout = TRUE; - } + { info_flag = CMDINFO_SIEVE; info_stdout = TRUE; } +#ifdef SUPPORT_DSCP else if (strcmpic(p, CUS"dscp") == 0) - { - info_flag = CMDINFO_DSCP; - info_stdout = TRUE; - } + { info_flag = CMDINFO_DSCP; info_stdout = TRUE; } +#endif else if (strcmpic(p, CUS"help") == 0) info_stdout = TRUE; } diff --git a/src/src/exim.h b/src/src/exim.h index 538f354e2..07b017a01 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -558,6 +558,9 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly. #ifdef EXPERIMENTAL_ARC # include "miscmods/arc_api.h" #endif +#ifdef SUPPORT_DSCP +# include "miscmods/dscp_api.h" +#endif #ifdef RADIUS_CONFIG_FILE # include "miscmods/radius_api.h" #endif diff --git a/src/src/expand.c b/src/src/expand.c index e222e8e75..b0500037d 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -8753,8 +8753,7 @@ uschar * expand_string_copy(const uschar * string) { const uschar * yield = expand_string(string); -if (yield == string) yield = string_copy(string); -return US yield; +return yield == string ? string_copy(string) : US yield; } diff --git a/src/src/functions.h b/src/src/functions.h index ec13c544e..4e56c14a4 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -216,8 +216,6 @@ extern void dns_pattern_init(void); extern int dns_special_lookup(dns_answer *, const uschar *, int, const uschar **); extern dns_record *dns_next_rr(const dns_answer *, dns_scan *, int); extern uschar *dns_text_type(int); -extern void dscp_list_to_stream(FILE *); -extern BOOL dscp_lookup(const uschar *, int, int *, int *, int *); extern void enq_end(uschar *); extern unsigned enq_start(uschar *, unsigned); @@ -290,7 +288,6 @@ extern int ip_bind(int, int, const uschar *, int); extern int ip_connect(int, int, const uschar *, int, int, const blob *); extern int ip_connectedsocket(int, const uschar *, int, int, int, host_item *, uschar **, const blob *); -extern int ip_get_address_family(int); extern void ip_keepalive(int, const uschar *, BOOL); extern int ip_recv(client_conn_ctx *, uschar *, int, time_t); extern int ip_socket(int, int); diff --git a/src/src/ip.c b/src/src/ip.c index f48fa82a9..80038e3ec 100644 --- a/src/src/ip.c +++ b/src/src/ip.c @@ -681,179 +681,6 @@ return -1; - -/************************************************* -* Lookup address family of potential socket * -*************************************************/ - -/* Given a file-descriptor, check to see if it's a socket and, if so, -return the address family; detects IPv4 vs IPv6. If not a socket then -return -1. - -The value 0 is typically AF_UNSPEC, which should not be seen on a connected -fd. If the return is -1, the errno will be from getsockname(); probably -ENOTSOCK or ECONNRESET. - -Arguments: socket-or-not fd -Returns: address family or -1 -*/ - -int -ip_get_address_family(int fd) -{ -struct sockaddr_storage ss; -socklen_t sslen = sizeof(ss); - -if (getsockname(fd, (struct sockaddr *) &ss, &sslen) < 0) - return -1; - -return (int) ss.ss_family; -} - - - - -/************************************************* -* Lookup DSCP settings for a socket * -*************************************************/ - -struct dscp_name_tableentry { - const uschar *name; - int value; -}; -/* Keep both of these tables sorted! */ -static struct dscp_name_tableentry dscp_table[] = { -#ifdef IPTOS_DSCP_AF11 - { CUS"af11", IPTOS_DSCP_AF11 }, - { CUS"af12", IPTOS_DSCP_AF12 }, - { CUS"af13", IPTOS_DSCP_AF13 }, - { CUS"af21", IPTOS_DSCP_AF21 }, - { CUS"af22", IPTOS_DSCP_AF22 }, - { CUS"af23", IPTOS_DSCP_AF23 }, - { CUS"af31", IPTOS_DSCP_AF31 }, - { CUS"af32", IPTOS_DSCP_AF32 }, - { CUS"af33", IPTOS_DSCP_AF33 }, - { CUS"af41", IPTOS_DSCP_AF41 }, - { CUS"af42", IPTOS_DSCP_AF42 }, - { CUS"af43", IPTOS_DSCP_AF43 }, - { CUS"ef", IPTOS_DSCP_EF }, -#endif -#ifdef IPTOS_LOWCOST - { CUS"lowcost", IPTOS_LOWCOST }, -#endif - { CUS"lowdelay", IPTOS_LOWDELAY }, -#ifdef IPTOS_MINCOST - { CUS"mincost", IPTOS_MINCOST }, -#endif - { CUS"reliability", IPTOS_RELIABILITY }, - { CUS"throughput", IPTOS_THROUGHPUT } -}; -static int dscp_table_size = - sizeof(dscp_table) / sizeof(struct dscp_name_tableentry); - -/* DSCP values change by protocol family, and so do the options used for -setsockopt(); this utility does all the lookups. It takes an unexpanded -option string, expands it, strips off affix whitespace, then checks if it's -a number. If all of what's left is a number, then that's how the option will -be parsed and success/failure is a range check. If it's not all a number, -then it must be a supported keyword. - -Arguments: - dscp_name a string, so far unvalidated - af address_family in use - level setsockopt level to use - optname setsockopt name to use - dscp_value value for dscp_name - -Returns: TRUE if okay to setsockopt(), else FALSE - -*level and *optname may be set even if FALSE is returned -*/ - -BOOL -dscp_lookup(const uschar *dscp_name, int af, - int *level, int *optname, int *dscp_value) -{ -uschar *dscp_lookup, *p; -int first, last; -long rawlong; - -if (af == AF_INET) - { - *level = IPPROTO_IP; - *optname = IP_TOS; - } -#if HAVE_IPV6 && defined(IPV6_TCLASS) -else if (af == AF_INET6) - { - *level = IPPROTO_IPV6; - *optname = IPV6_TCLASS; - } -#endif -else - { - DEBUG(D_transport) - debug_printf("Unhandled address family %d in dscp_lookup()\n", af); - return FALSE; - } -if (!dscp_name) - { - DEBUG(D_transport) - debug_printf("[empty DSCP]\n"); - return FALSE; - } -dscp_lookup = expand_string(US dscp_name); -if (dscp_lookup == NULL || *dscp_lookup == '\0') - return FALSE; - -p = dscp_lookup + Ustrlen(dscp_lookup) - 1; -while (isspace(*p)) *p-- = '\0'; -while (isspace(*dscp_lookup) && dscp_lookup < p) dscp_lookup++; -if (*dscp_lookup == '\0') - return FALSE; - -rawlong = Ustrtol(dscp_lookup, &p, 0); -if (p != dscp_lookup && *p == '\0') - { - /* We have six bits available, which will end up shifted to fit in 0xFC mask. - RFC 2597 defines the values unshifted. */ - if (rawlong < 0 || rawlong > 0x3F) - { - DEBUG(D_transport) - debug_printf("DSCP value %ld out of range, ignored.\n", rawlong); - return FALSE; - } - *dscp_value = rawlong << 2; - return TRUE; - } - -first = 0; -last = dscp_table_size; -while (last > first) - { - int middle = (first + last)/2; - int c = Ustrcmp(dscp_lookup, dscp_table[middle].name); - if (c == 0) - { - *dscp_value = dscp_table[middle].value; - return TRUE; - } - else if (c > 0) - first = middle + 1; - else - last = middle; - } -return FALSE; -} - -void -dscp_list_to_stream(FILE *stream) -{ -for (int i = 0; i < dscp_table_size; ++i) - fprintf(stream, "%s\n", dscp_table[i].name); -} - - /* End of ip.c */ /* vi: aw ai sw=2 */ diff --git a/src/src/macro_predef.c b/src/src/macro_predef.c index a8ebc40d4..4778ace64 100644 --- a/src/src/macro_predef.c +++ b/src/src/macro_predef.c @@ -212,6 +212,9 @@ due to conflicts with other common macros. */ #ifdef EXPERIMENTAL_DCC builtin_macro_create(US"_HAVE_DCC"); #endif +#ifdef SUPPORT_DSCP + builtin_macro_create(US"_HAVE_DSCP"); +#endif #ifdef EXPERIMENTAL_DSN_INFO builtin_macro_create(US"_HAVE_DSN_INFO"); #endif diff --git a/src/src/miscmods/Makefile b/src/src/miscmods/Makefile index 2d44059ba..e129fe8bc 100644 --- a/src/src/miscmods/Makefile +++ b/src/src/miscmods/Makefile @@ -41,6 +41,7 @@ dmarc.o dmarc.so: $(HDRS) spf.h pdkim.h dmarc.h \ dmarc.c dmarc_common.c dmarc_native.o dmarc_native.so: $(HDRS) spf.h pdkim.h dmarc.h \ dmarc_native.c dmarc_common.c +dscp.o dscp.so: $(HDRS) dscp.c dummy.o: dummy.c exim_filter.o exim_filter.so: $(HDRS) exim_filter.c pam.o pam.so: $(HDRS) pam.c diff --git a/src/src/miscmods/dscp.c b/src/src/miscmods/dscp.c new file mode 100644 index 000000000..8c25833d9 --- /dev/null +++ b/src/src/miscmods/dscp.c @@ -0,0 +1,278 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* DSCP support for Exim + Copyright (c) The Exim Maintainers - 2025 + License: GPL + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "../exim.h" +#if defined SUPPORT_DSCP + +# include "../functions.h" + +/******************************************************************************/ +/* Utility functions */ + +/************************************************* +* Lookup address family of potential socket * +*************************************************/ + +/* Given a file-descriptor, check to see if it's a socket and, if so, +return the address family; detects IPv4 vs IPv6. If not a socket then +return -1. + +The value 0 is typically AF_UNSPEC, which should not be seen on a connected +fd. If the return is -1, the errno will be from getsockname(); probably +ENOTSOCK or ECONNRESET. + +Arguments: socket-or-not fd +Returns: address family or -1 +*/ + +static int +ip_get_address_family(int fd) +{ +struct sockaddr_storage ss; +socklen_t sslen = sizeof(ss); + +if (getsockname(fd, (struct sockaddr *) &ss, &sslen) < 0) + return -1; + +return (int) ss.ss_family; +} + + + +/************************************************* +* Lookup DSCP settings for a socket * +*************************************************/ + +struct dscp_name_tableentry { + const uschar *name; + int value; +}; +/* Keep both of these tables sorted! */ +static struct dscp_name_tableentry dscp_table[] = { +#ifdef IPTOS_DSCP_AF11 + { CUS"af11", IPTOS_DSCP_AF11 }, + { CUS"af12", IPTOS_DSCP_AF12 }, + { CUS"af13", IPTOS_DSCP_AF13 }, + { CUS"af21", IPTOS_DSCP_AF21 }, + { CUS"af22", IPTOS_DSCP_AF22 }, + { CUS"af23", IPTOS_DSCP_AF23 }, + { CUS"af31", IPTOS_DSCP_AF31 }, + { CUS"af32", IPTOS_DSCP_AF32 }, + { CUS"af33", IPTOS_DSCP_AF33 }, + { CUS"af41", IPTOS_DSCP_AF41 }, + { CUS"af42", IPTOS_DSCP_AF42 }, + { CUS"af43", IPTOS_DSCP_AF43 }, + { CUS"ef", IPTOS_DSCP_EF }, +#endif +#ifdef IPTOS_LOWCOST + { CUS"lowcost", IPTOS_LOWCOST }, +#endif + { CUS"lowdelay", IPTOS_LOWDELAY }, +#ifdef IPTOS_MINCOST + { CUS"mincost", IPTOS_MINCOST }, +#endif + { CUS"reliability", IPTOS_RELIABILITY }, + { CUS"throughput", IPTOS_THROUGHPUT } +}; +static int dscp_table_size = + sizeof(dscp_table) / sizeof(struct dscp_name_tableentry); + +/* DSCP values change by protocol family, and so do the options used for +setsockopt(); this utility does all the lookups. It takes an unexpanded +option string, expands it, strips off affix whitespace, then checks if it's +a number. If all of what's left is a number, then that's how the option will +be parsed and success/failure is a range check. If it's not all a number, +then it must be a supported keyword. + +Arguments: + dscp_name a string, so far unvalidated + af address_family in use + level setsockopt level to use + optname setsockopt name to use + dscp_value value for dscp_name + +Returns: TRUE if okay to setsockopt(), else FALSE + +*level and *optname may be set even if FALSE is returned +*/ + +static BOOL +dscp_lookup(const uschar * dscp_name, int af, + int * level, int * optname, int * dscp_value) +{ +uschar * dscp_lookup, * p; +int first, last; +long rawlong; + +if (af == AF_INET) + { *level = IPPROTO_IP; *optname = IP_TOS; } +#if HAVE_IPV6 && defined(IPV6_TCLASS) +else if (af == AF_INET6) + { *level = IPPROTO_IPV6; *optname = IPV6_TCLASS; } +#endif +else + { + DEBUG(D_transport) + debug_printf("Unhandled address family %d in dscp_lookup()\n", af); + return FALSE; + } +if (!dscp_name) + { + DEBUG(D_transport) + debug_printf("[empty DSCP]\n"); + return FALSE; + } +dscp_lookup = expand_string_copy(dscp_name); +if (!dscp_lookup || !*dscp_lookup) + return FALSE; + +p = dscp_lookup + Ustrlen(dscp_lookup) - 1; +while (isspace(*p)) *p-- = '\0'; +while (isspace(*dscp_lookup) && dscp_lookup < p) dscp_lookup++; +if (*dscp_lookup == '\0') + return FALSE; + +rawlong = Ustrtol(dscp_lookup, &p, 0); +if (p != dscp_lookup && *p == '\0') + { + /* We have six bits available, which will end up shifted to fit in 0xFC mask. + RFC 2597 defines the values unshifted. */ + if (rawlong < 0 || rawlong > 0x3F) + { + DEBUG(D_transport) + debug_printf("DSCP value %ld out of range, ignored.\n", rawlong); + return FALSE; + } + *dscp_value = rawlong << 2; + return TRUE; + } + +first = 0; +last = dscp_table_size; +while (last > first) + { + int middle = (first + last)/2; + int c = Ustrcmp(dscp_lookup, dscp_table[middle].name); + if (c == 0) + { + *dscp_value = dscp_table[middle].value; + return TRUE; + } + else if (c > 0) + first = middle + 1; + else + last = middle; + } +return FALSE; +} + +/******************************************************************************/ + +/*API +Set DSCP on stdin. Called from ACL control. +Return error message on fail, NULL on ok. +*/ + +static uschar * +dscp_acl(const uschar * control, const uschar * opt) +{ +int af, socklevel, optname, value; + +if (*opt != '/') + return string_sprintf("syntax error in \"control=%s\"", control); + +/* If we are acting on stdin, the setsockopt may fail if stdin is +not a socket; we can accept that, we'll just debug-log failures +anyway. */ +if (smtp_in_fd < 0) return US"no stdin"; +if ((af = ip_get_address_family(smtp_in_fd)) < 0) + { + HDEBUG(D_acl) debug_printf_indent( + "smtp input is probably not a socket [%s], not setting DSCP\n", + strerror(errno)); + return NULL; + } +if (!dscp_lookup(++opt, af, &socklevel, &optname, &value)) + return string_sprintf("unrecognised DSCP value in \"control=%s\"", control); + +value = setsockopt(smtp_in_fd, socklevel, optname, + &value, sizeof(value)); +HDEBUG(D_acl) + if (value < 0) + debug_printf_indent("failed to set input DSCP[%s]: %s\n", + opt, strerror(errno)); + else + debug_printf_indent("set input DSCP to %q\n", opt); +return NULL; +} + + +/*API +Set DSCP on given socket; called from smtp transport. +Can silently fail. +*/ + +static void +dscp_transport(int sock, const uschar * dscp_str, int host_af) +{ +int dscp_value, dscp_level, dscp_option; + +if ( dscp_str + && dscp_lookup(dscp_str, host_af, &dscp_level, &dscp_option, &dscp_value) + ) + { + HDEBUG(D_transport|D_acl|D_v) + debug_printf_indent("DSCP %q=%x ", dscp_str, dscp_value); + + if (setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value)) < 0) + HDEBUG(D_transport|D_acl|D_v) + debug_printf_indent("failed to set DSCP: %s ", strerror(errno)); + + /* If the kernel supports IPv4 and IPv6 on an IPv6 socket, we need to set the + option for both; ignore failures here */ + + if ( host_af == AF_INET6 + && dscp_lookup(dscp_str, AF_INET, &dscp_level, &dscp_option, &dscp_value) + ) + (void) setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value)); + } +} + + +/*API: output known DSCP names */ +static void +dscp_keywords(FILE * stream) +{ +for (int i = 0; i < dscp_table_size; ++i) + fprintf(stream, "%s\n", dscp_table[i].name); +} + +/******************************************************************************/ +/* Module API */ + +static void * dscp_functions[] = { + [DSCP_ACL] = (void *) dscp_acl, + [DSCP_TRANSPORT] = (void *) dscp_transport, + [DSCP_KEYWORDS] = (void *) dscp_keywords, +}; + +misc_module_info dscp_module_info = +{ + .name = US"dscp", +# ifdef DYNLOOKUP + .dyn_magic = MISC_MODULE_MAGIC, +# endif + .functions = dscp_functions, + .functions_count = nelem(dscp_functions), +}; + +#endif /* SUPPORT_DSCP */ +/* vi: aw ai sw=2 + */ diff --git a/src/src/miscmods/dscp_api.h b/src/src/miscmods/dscp_api.h new file mode 100644 index 000000000..ce355319d --- /dev/null +++ b/src/src/miscmods/dscp_api.h @@ -0,0 +1,16 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2025 */ +/* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* API definitions for the arcmodule */ + + +/* Function table entry numbers */ + +#define DSCP_ACL 0 +#define DSCP_TRANSPORT 1 +#define DSCP_KEYWORDS 2 diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c index 465acb5ed..790a0c005 100644 --- a/src/src/smtp_out.c +++ b/src/src/smtp_out.c @@ -276,10 +276,7 @@ Update those with the state. Return the fd, or -1 with errno set. int smtp_boundsock(smtp_connect_args * sc) { -transport_instance * tb = sc->tblock; -smtp_transport_options_block * ob = tb->drinst.options_block; -const uschar * dscp = ob->dscp; -int sock, dscp_value, dscp_level, dscp_option; +int sock; if ((sock = ip_socket(SOCK_STREAM, sc->host_af)) < 0) return -1; @@ -290,23 +287,23 @@ if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, US &on, sizeof(on))) HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("failed to set NODELAY: %s ", strerror(errno)); +#ifdef SUPPORT_DSCP /* Set DSCP value, if we can. For now, if we fail to set the value, we don't bomb out, just log it and continue in default traffic class. */ - -GET_OPTION("dscp"); -if (dscp && dscp_lookup(dscp, sc->host_af, &dscp_level, &dscp_option, &dscp_value)) { - HDEBUG(D_transport|D_acl|D_v) - debug_printf_indent("DSCP %q=%x ", dscp, dscp_value); - if (setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value)) < 0) - HDEBUG(D_transport|D_acl|D_v) - debug_printf_indent("failed to set DSCP: %s ", strerror(errno)); - /* If the kernel supports IPv4 and IPv6 on an IPv6 socket, we need to set the - option for both; ignore failures here */ - if (sc->host_af == AF_INET6 && - dscp_lookup(dscp, AF_INET, &dscp_level, &dscp_option, &dscp_value)) - (void) setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value)); + transport_instance * tb = sc->tblock; + smtp_transport_options_block * ob = tb->drinst.options_block; + GET_OPTION("dscp"); + if (ob->dscp) + { + uschar * dummy_errstr; + misc_module_info * mi = misc_mod_find(US"dscp", &dummy_errstr); + typedef void (*fn_t)(int, const uschar *, int); + if (mi) + ((fn_t *) mi->functions)[DSCP_TRANSPORT] (sock, ob->dscp, sc->host_af); + } } +#endif /* Bind to a specific interface if requested. Caller must ensure the interface is the same type (IPv4 or IPv6) as the outgoing address. */ commit 732c68f17cb249e6d40727ab0a043ded0fed2247 Author: Jeremy Harris Date: Sun Dec 14 12:52:18 2025 +0000 socks module diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile index 9b766d6ee..82e93d2ad 100755 --- a/src/scripts/Configure-Makefile +++ b/src/scripts/Configure-Makefile @@ -332,7 +332,7 @@ done <<-END routers ROUTER ACCEPT DNSLOOKUP IPLITERAL IPLOOKUP MANUALROUTE QUERYPROGRAM REDIRECT transports TRANSPORT APPENDFILE AUTOREPLY LMTP PIPE QUEUEFILE SMTP auths AUTH CRAM_MD5 CYRUS_SASL DOVECOT EXTERNAL GSASL HEIMDAL_GSSAPI PLAINTEXT SPA TLS - miscmods SUPPORT ARC _DKIM DMARC DMARC_NATIVE DSCP _EXIM_FILTER PAM PERL RADIUS _SIEVE_FILTER SPF SPF_PERL + miscmods SUPPORT ARC _DKIM DMARC DMARC_NATIVE DSCP _EXIM_FILTER PAM PERL RADIUS _SIEVE_FILTER SOCKS SPF SPF_PERL END # See if there is a definition of EXIM_PERL in what we have built so far. diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index 35b58ec04..1875c6963 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -66,7 +66,7 @@ cd $d # Makefile is generated for f in README appendfile.h appendfile.c autoreply.h \ autoreply.c lmtp.h lmtp.c pipe.h pipe.c queuefile.c queuefile.h \ - smtp.h smtp.c smtp_socks.c tf_maildir.c tf_maildir.h + smtp.h smtp.c tf_maildir.c tf_maildir.h do ln -s ../../src/$d/$f $f done @@ -108,6 +108,7 @@ for f in dummy.c \ perl.c perl_api.h \ radius.c radius_api.h \ sieve_filter.c sieve_filter_api.h \ + socks.c socks_api.h \ spf.c spf_perl.c spf.h spf_api.h do ln -s ../../src/$d/$f `basename $f` diff --git a/src/src/EDITME b/src/src/EDITME index 98baae7d5..695cceafd 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -1131,7 +1131,7 @@ ZCAT_COMMAND=/usr/bin/zcat # Proxying. # # If you may want to use outbound (client-side) proxying, using Socks5, -# uncomment the line below. +# uncomment the line below. To build as a dynamic module set it to 2. # SUPPORT_SOCKS=yes diff --git a/src/src/drtables.c b/src/src/drtables.c index e21d47047..3c714ba24 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -513,6 +513,9 @@ extern misc_module_info exim_filter_module_info; #if !defined(DISABLE_SIEVE_FILTER) && (!defined(SUPPORT_SIEVE_FILTER) || SUPPORT_SIEVE_FILTER!=2) extern misc_module_info sieve_filter_module_info; #endif +#if defined(SUPPORT_SOCKS) && SUPPORT_SOCKS!=2 +extern misc_module_info socks_module_info; +#endif void init_misc_mod_list(void) @@ -537,9 +540,6 @@ onetime = TRUE; #if defined(SUPPORT_DSCP) && SUPPORT_DSCP!=2 misc_mod_add(&dscp_module_info); #endif -#if defined(RADIUS_CONFIG_FILE) && (!defined(SUPPORT_RADIUS) || SUPPORT_RADIUS!=2) - misc_mod_add(&radius_module_info); -#endif #if defined(SUPPORT_PAM) && SUPPORT_PAM!=2 misc_mod_add(&pam_module_info); #endif @@ -552,6 +552,12 @@ onetime = TRUE; #if !defined(DISABLE_SIEVE_FILTER) && (!defined(SUPPORT_SIEVE_FILTER) || SUPPORT_SIEVE_FILTER!=2) misc_mod_add(&sieve_filter_module_info); #endif +#if defined(SUPPORT_SOCKS) && SUPPORT_SOCKS!=2 + misc_mod_add(&socks_module_info); +#endif +#if defined(RADIUS_CONFIG_FILE) && (!defined(SUPPORT_RADIUS) || SUPPORT_RADIUS!=2) + misc_mod_add(&radius_module_info); +#endif } diff --git a/src/src/exim.c b/src/src/exim.c index 383fe8247..e955d2488 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -3093,6 +3093,8 @@ on the second character (the one after '-'), to save some effort. */ #ifdef SUPPORT_SOCKS /* -MCp: Socks proxy in use; nearside IP, port, external IP, port */ + /*XXX Neatness might have this code in the socks module, but having to + load a dynamic version puely for this? However, of logging moved there...*/ case 'p': proxy_session = TRUE; if (++i < argc) { diff --git a/src/src/exim.h b/src/src/exim.h index 07b017a01..4bd0a87e7 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -570,6 +570,9 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly. #ifdef EXIM_PERL # include "miscmods/perl_api.h" #endif +#ifdef SUPPORT_SOCKS +# include "miscmods/socks_api.h" +#endif #include "miscmods/exim_filter_api.h" #include "miscmods/sieve_filter_api.h" diff --git a/src/src/miscmods/Makefile b/src/src/miscmods/Makefile index e129fe8bc..a5fbba441 100644 --- a/src/src/miscmods/Makefile +++ b/src/src/miscmods/Makefile @@ -48,6 +48,7 @@ pam.o pam.so: $(HDRS) pam.c perl.o perl.so: $(HDRS) perl.c radius.o radius.so: $(HDRS) radius.c sieve_filter.o sieve_filter.so: $(HDRS) sieve_filter.c +socks.o socks.so: $(HDRS) socks_api.h ../transports/smtp.h socks.c spf.o spf.so: $(HDRS) spf.h spf.c spf_perl.o spf_perl.so: $(HDRS) spf.h spf_perl.c diff --git a/src/src/transports/smtp_socks.c b/src/src/miscmods/socks.c similarity index 94% rename from src/src/transports/smtp_socks.c rename to src/src/miscmods/socks.c index c17b808e9..6694044da 100644 --- a/src/src/transports/smtp_socks.c +++ b/src/src/miscmods/socks.c @@ -10,15 +10,10 @@ /* SOCKS version 5 proxy, client-mode */ #include "../exim.h" -#include "smtp.h" +#include "../transports/smtp.h" #ifdef SUPPORT_SOCKS /* entire file */ -#ifndef nelem -# define nelem(arr) (sizeof(arr)/sizeof(*arr)) -#endif - - /* Defaults */ #define SOCKS_PORT 1080 #define SOCKS_TIMEOUT 5 @@ -187,18 +182,18 @@ return -1; -/* Make a connection via a socks proxy +/*API: Make a connection via a socks proxy Arguments: sc details for making connection: host, af, interface, transport early_data data to send down the smtp channel (once proxied) Return value: - 0 on success; -1 on failure, with errno set + connected file descriptor on success; -1 on failure, with errno set */ -int -socks_sock_connect(smtp_connect_args * sc, const blob * early_data) +static int +socks_sock_connect(const smtp_connect_args * sc, const blob * early_data) { transport_instance * tb = sc->tblock; smtp_transport_options_block * ob = tb->drinst.options_block; @@ -407,6 +402,24 @@ rcv_err: return -1; } +/******************************************************************************/ +/* Module API */ + +static void * socks_functions[] = { + [SOCKS_CONNECT] = (void *) socks_sock_connect, +}; + +misc_module_info socks_module_info = +{ + .name = US"socks", +# ifdef DYNLOOKUP + .dyn_magic = MISC_MODULE_MAGIC, +# endif + + .functions = socks_functions, + .functions_count = nelem(socks_functions), +}; + #endif /* entire file */ /* vi: aw ai sw=2 */ diff --git a/src/src/miscmods/socks_api.h b/src/src/miscmods/socks_api.h new file mode 100644 index 000000000..6a083d3cb --- /dev/null +++ b/src/src/miscmods/socks_api.h @@ -0,0 +1,14 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2025 */ +/* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* API definitions for the socks module */ + + +/* Function table entry numbers */ + +#define SOCKS_CONNECT 0 diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c index 790a0c005..a5557d09e 100644 --- a/src/src/smtp_out.c +++ b/src/src/smtp_out.c @@ -520,7 +520,12 @@ if (ob->socks_proxy) return -1; } if (*ob->socks_proxy) - return socks_sock_connect(sc, early_data); + { + uschar * dummy_errmsg; + misc_module_info * mi = misc_mod_find(US"socks", &dummy_errmsg); + typedef int (*fn_t) (const smtp_connect_args *, const blob *); + return mi ? ((fn_t *) mi->functions)[SOCKS_CONNECT] (sc, early_data) : -1; + } } #endif diff --git a/src/src/transports/Makefile b/src/src/transports/Makefile index 89cadc568..761cf01c8 100644 --- a/src/src/transports/Makefile +++ b/src/src/transports/Makefile @@ -11,12 +11,12 @@ # MAGIC-TAG-MODS-OBJ-RULES-GO-HERE -OBJ += smtp_socks.o tf_maildir.o +OBJ += tf_maildir.o all: transports.a $(MODS) $(FE): -transports.a: $(OBJ) smtp_socks.o tf_maildir.o +transports.a: $(OBJ) tf_maildir.o @$(RM_COMMAND) -f transports.a @echo "$(AR) transports.a" @$(AR) transports.a $(OBJ) @@ -39,7 +39,6 @@ lmtp.o lmtp.so: lmtp.c lmtp.h pipe.o pipe.so: pipe.c pipe.h queuefile.o queuefile.so: queuefile.c queuefile.h smtp.o: smtp.c smtp.h -smtp_socks.o: smtp_socks.c smtp.h tf_maildir.o: tf_maildir.c tf_maildir.h appendfile.h diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h index 506d6094a..72b3b00ba 100644 --- a/src/src/transports/smtp.h +++ b/src/src/transports/smtp.h @@ -250,9 +250,4 @@ extern BOOL smtp_transport_entry(transport_instance *, address_item *); extern void smtp_transport_closedown(transport_instance *); - -#ifdef SUPPORT_SOCKS -extern int socks_sock_connect(smtp_connect_args *, const blob *); -#endif - /* End of transports/smtp.h */ commit ee143587852f7a6937a6997da730b4e02e427fc4 Author: Jeremy Harris Date: Sun Dec 14 18:23:37 2025 +0000 xclient module diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index f713ad3ca..9f26eff0f 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -541,8 +541,7 @@ OBJ_WITH_CONTENT_SCAN = malware.o mime.o regex.o spam.o spool_mbox.o OBJ_EXPERIMENTAL = dane.o \ dcc.o \ imap_utf7.o \ - utf8.o \ - xclient.o + utf8.o # Targets for final binaries; the main one has a build number which is # updated each time. We don't bother with that for the auxiliaries. @@ -939,7 +938,6 @@ dane.o: $(HDRS) dane.c dane-openssl.c dcc.o: $(HDRS) dcc.h dcc.c imap_utf7.o: $(HDRS) imap_utf7.c utf8.o: $(HDRS) utf8.c -xclient.o: $(HDRS) xclient.c # The module containing tables of available lookups, routers, auths, and # transports must be rebuilt if any of them are. However, because the makefiles diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile index 82e93d2ad..9a7351482 100755 --- a/src/scripts/Configure-Makefile +++ b/src/scripts/Configure-Makefile @@ -332,7 +332,7 @@ done <<-END routers ROUTER ACCEPT DNSLOOKUP IPLITERAL IPLOOKUP MANUALROUTE QUERYPROGRAM REDIRECT transports TRANSPORT APPENDFILE AUTOREPLY LMTP PIPE QUEUEFILE SMTP auths AUTH CRAM_MD5 CYRUS_SASL DOVECOT EXTERNAL GSASL HEIMDAL_GSSAPI PLAINTEXT SPA TLS - miscmods SUPPORT ARC _DKIM DMARC DMARC_NATIVE DSCP _EXIM_FILTER PAM PERL RADIUS _SIEVE_FILTER SOCKS SPF SPF_PERL + miscmods SUPPORT ARC _DKIM DMARC DMARC_NATIVE DSCP _EXIM_FILTER PAM PERL RADIUS _SIEVE_FILTER SOCKS SPF SPF_PERL XCLIENT END # See if there is a definition of EXIM_PERL in what we have built so far. diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index 1875c6963..642cd7302 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -109,7 +109,8 @@ for f in dummy.c \ radius.c radius_api.h \ sieve_filter.c sieve_filter_api.h \ socks.c socks_api.h \ - spf.c spf_perl.c spf.h spf_api.h + spf.c spf_perl.c spf.h spf_api.h \ + xclient.c xclient_api.h do ln -s ../../src/$d/$f `basename $f` done @@ -158,7 +159,7 @@ done # EXPERIMENTAL_* for f in dcc.c dcc.h dane.c dane-openssl.c \ - danessl.h imap_utf7.c utf8.c xclient.c + danessl.h imap_utf7.c utf8.c do ln -s ../src/$f $f done diff --git a/src/src/EDITME b/src/src/EDITME index 695cceafd..9e5b25029 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -707,7 +707,8 @@ DISABLE_MAL_MKS=yes # Uncomment the following line to add SRV smtps support # EXPERIMENTAL_SRV_SMTPS=yes # -# Uncomment the following line to add XCLIENT support +# Uncomment the following line to add XCLIENT support. +# Set to 2 for a dynamic-load module. # EXPERIMENTAL_XCLIENT=yes ############################################################################### diff --git a/src/src/drtables.c b/src/src/drtables.c index 3c714ba24..4df612c65 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -498,24 +498,28 @@ extern misc_module_info arc_module_info; #if defined(SUPPORT_DSCP) && SUPPORT_DSCP!=2 extern misc_module_info dscp_module_info; #endif -#if defined(RADIUS_CONFIG_FILE) && (!defined(SUPPORT_RADIUS) || SUPPORT_RADIUS!=2) -extern misc_module_info radius_module_info; -#endif #if defined(SUPPORT_PAM) && SUPPORT_PAM!=2 extern misc_module_info pam_module_info; #endif #if defined(EXIM_PERL) && (!defined(SUPPORT_PERL) || SUPPORT_PERL!=2) extern misc_module_info perl_module_info; #endif +#if defined(RADIUS_CONFIG_FILE) && (!defined(SUPPORT_RADIUS) || SUPPORT_RADIUS!=2) +extern misc_module_info radius_module_info; +#endif +#if defined(SUPPORT_SOCKS) && SUPPORT_SOCKS!=2 +extern misc_module_info socks_module_info; +#endif +#if defined(EXPERIMENTAL_XCLIENT) && EXPERIMENTAL_XCLIENT!=2 +extern misc_module_info xclient_module_info; +#endif + #if !defined(DISABLE_EXIM_FILTER) && (!defined(SUPPORT_EXIM_FILTER) || SUPPORT_EXIM_FILTER!=2) extern misc_module_info exim_filter_module_info; #endif #if !defined(DISABLE_SIEVE_FILTER) && (!defined(SUPPORT_SIEVE_FILTER) || SUPPORT_SIEVE_FILTER!=2) extern misc_module_info sieve_filter_module_info; #endif -#if defined(SUPPORT_SOCKS) && SUPPORT_SOCKS!=2 -extern misc_module_info socks_module_info; -#endif void init_misc_mod_list(void) @@ -543,6 +547,9 @@ onetime = TRUE; #if defined(SUPPORT_PAM) && SUPPORT_PAM!=2 misc_mod_add(&pam_module_info); #endif +#if defined(EXPERIMENTAL_XCLIENT) && EXPERIMENTAL_XCLIENT!=2 + misc_mod_add(&xclient_module_info); +#endif #if defined(EXIM_PERL) && (!defined(SUPPORT_PERL) || SUPPORT_PERL!=2) misc_mod_add(&perl_module_info); #endif diff --git a/src/src/exim.h b/src/src/exim.h index 4bd0a87e7..7b4270cad 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -561,9 +561,6 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly. #ifdef SUPPORT_DSCP # include "miscmods/dscp_api.h" #endif -#ifdef RADIUS_CONFIG_FILE -# include "miscmods/radius_api.h" -#endif #ifdef SUPPORT_PAM # include "miscmods/pam_api.h" #endif @@ -573,6 +570,12 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly. #ifdef SUPPORT_SOCKS # include "miscmods/socks_api.h" #endif +#ifdef RADIUS_CONFIG_FILE +# include "miscmods/radius_api.h" +#endif +#ifdef EXPERIMENTAL_XCLIENT +# include "miscmods/xclient_api.h" +#endif #include "miscmods/exim_filter_api.h" #include "miscmods/sieve_filter_api.h" diff --git a/src/src/functions.h b/src/src/functions.h index 4e56c14a4..f07799321 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -706,10 +706,6 @@ extern BOOL write_chunk(transport_ctx *, const uschar *, int); extern ssize_t write_to_fd_buf(int, const uschar *, size_t); extern uschar *wrap_header(const uschar *, unsigned, unsigned, const uschar *, unsigned); -#ifdef EXPERIMENTAL_XCLIENT -extern uschar * xclient_smtp_command(uschar *, int *, BOOL *); -extern gstring * xclient_smtp_advertise_str(gstring *); -#endif extern uschar *xtextencode(const uschar *, int); extern int xtextdecode(const uschar *, uschar **); diff --git a/src/src/miscmods/Makefile b/src/src/miscmods/Makefile index a5fbba441..f731088ba 100644 --- a/src/src/miscmods/Makefile +++ b/src/src/miscmods/Makefile @@ -43,14 +43,15 @@ dmarc_native.o dmarc_native.so: $(HDRS) spf.h pdkim.h dmarc.h \ dmarc_native.c dmarc_common.c dscp.o dscp.so: $(HDRS) dscp.c dummy.o: dummy.c -exim_filter.o exim_filter.so: $(HDRS) exim_filter.c -pam.o pam.so: $(HDRS) pam.c -perl.o perl.so: $(HDRS) perl.c -radius.o radius.so: $(HDRS) radius.c -sieve_filter.o sieve_filter.so: $(HDRS) sieve_filter.c -socks.o socks.so: $(HDRS) socks_api.h ../transports/smtp.h socks.c -spf.o spf.so: $(HDRS) spf.h spf.c -spf_perl.o spf_perl.so: $(HDRS) spf.h spf_perl.c +exim_filter.o exim_filter.so: $(HDRS) exim_filter.c +pam.o pam.so: $(HDRS) pam.c +perl.o perl.so: $(HDRS) perl.c +radius.o radius.so: $(HDRS) radius.c +sieve_filter.o sieve_filter.so: $(HDRS) sieve_filter.c +socks.o socks.so: $(HDRS) socks_api.h ../transports/smtp.h socks.c +spf.o spf.so: $(HDRS) spf.h spf.c +spf_perl.o spf_perl.so: $(HDRS) spf.h spf_perl.c +xclient.o xclient.so: $(HDRS) xclient_api.h xclient.c diff --git a/src/src/miscmods/xclient.c b/src/src/miscmods/xclient.c new file mode 100644 index 000000000..77bf743a4 --- /dev/null +++ b/src/src/miscmods/xclient.c @@ -0,0 +1,356 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2023 - 2025 */ +/* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "../exim.h" + +#ifdef EXPERIMENTAL_XCLIENT + +/* This is a proxy protocol. + +From https://www.postfix.org/XCLIENT_README.html I infer two generations of +protocol. The more recent one obviates the utility of the HELO attribute, since +it mandates the proxy always sending a HELO/EHLO smtp command following (a +successful) XCLIENT command, and that will carry a NELO name (which we assume, +though it isn't specified, will be the actual one presented to the proxy by the +possibly-new client). The same applies to the PROTO attribute. */ + +# define XCLIENT_V2 + +enum xclient_cmd_e { + XCLIENT_CMD_UNKNOWN, + XCLIENT_CMD_ADDR, + XCLIENT_CMD_NAME, + XCLIENT_CMD_PORT, + XCLIENT_CMD_LOGIN, + XCLIENT_CMD_DESTADDR, + XCLIENT_CMD_DESTPORT, +# ifdef XCLIENT_V1 + XCLIENT_CMD_HELO, + XCLIENT_CMD_PROTO, +# endif +}; + +struct xclient_cmd { + const uschar * str; + unsigned len; +} xclient_cmds[] = { + [XCLIENT_CMD_UNKNOWN] = { NULL }, + [XCLIENT_CMD_ADDR] = { US"ADDR", 4 }, + [XCLIENT_CMD_NAME] = { US"NAME", 4 }, + [XCLIENT_CMD_PORT] = { US"PORT", 4 }, + [XCLIENT_CMD_LOGIN] = { US"LOGIN", 5 }, + [XCLIENT_CMD_DESTADDR] = { US"DESTADDR", 8 }, + [XCLIENT_CMD_DESTPORT] = { US"DESTPORT", 8 }, +# ifdef XCLIENT_V1 + [XCLIENT_CMD_HELO] = { US"HELO", 4 }, + [XCLIENT_CMD_PROTO] = { US"PROTO", 5 }, +# endif +}; + +/************************************************* +* XCLIENT proxy implementation * +*************************************************/ + +/* Arguments: + code points to the coded string + end points to the end of coded string + ptr where to put the pointer to the result, which is in + dynamic store +Returns: the number of bytes in the result, excluding the final zero; + -1 if the input is malformed +*/ + +static int +xclient_xtextdecode(const uschar * code, const uschar * end, uschar ** ptr) +{ +return xtextdecode(string_copyn(code, end-code), ptr); +} + +/************************************************* +* Check XCLIENT line and set sender_address * +*************************************************/ + + +/* Check the format of a XCLIENT line. +Arguments: + s the data portion of the line (already past any white space) + resp result: smtp respose code + flagp input: helo seen output: fail is fatal + +Return: NULL on success, or error message +*/ + +# define XCLIENT_UNAVAIL US"[UNAVAILABLE]" +# define XCLIENT_TEMPUNAVAIL US"[TEMPUNAVAIL]" + +static uschar * +xclient_smtp_command(const uschar * s, int * resp, BOOL * flagp) +{ +const uschar * word = s; +uschar * errmsg = NULL; +enum { + XCLIENT_READ_COMMAND = 0, + XCLIENT_READ_VALUE, + XCLIENT_SKIP_SPACES +} state = XCLIENT_SKIP_SPACES; +enum xclient_cmd_e cmd; +static BOOL xc_proxy_session = FALSE; + +if ( !*flagp + && verify_check_host(&hosts_require_helo) == OK) + { + *resp = 503; + *flagp= FALSE; + return US"no HELO/EHLO given"; + } + +/* If already in a proxy session, do not re-check permission. */ + +if (!xc_proxy_session && verify_check_host(&hosts_xclient) == FAIL) + { + *resp = 501; + *flagp= TRUE; + return US"XCLIENT command used when not advertised"; + } + +if (sender_address) + { + *resp = 503; + *flagp= FALSE; + return US"mail transaction in progress"; + } + +if (!*word) + { + errmsg = US"XCLIENT must have at least one operand"; + goto fatal_501; + } + +for (state = XCLIENT_SKIP_SPACES; *s; ) + switch (state) + { + case XCLIENT_READ_COMMAND: + { + int len; + + word = s; + while (*s && *s != '=') s++; + len = s - word; + if (!*s) + { + errmsg = string_sprintf("XCLIENT: missing value for parameter '%.*s'", + len, word); + goto fatal_501; + } + + DEBUG(D_transport) debug_printf(" XCLIENT: cmd %.*s\n", len, word); + cmd = XCLIENT_CMD_UNKNOWN; + for (struct xclient_cmd * x = xclient_cmds + 1; + x < xclient_cmds + nelem(xclient_cmds); x++) + if (len == x->len && strncmpic(word, x->str, len) == 0) + { + cmd = x - xclient_cmds; + break; + } + if (cmd == XCLIENT_CMD_UNKNOWN) + { + errmsg = string_sprintf("XCLIENT: unrecognised parameter '%.*s'", + len, word); + goto fatal_501; + } + state = XCLIENT_READ_VALUE; + } + break; + + case XCLIENT_READ_VALUE: + { + int old_pool = store_pool; + int len; + uschar * val; + + word = ++s; /* skip the = */ + Uskip_nonwhite(&s); + len = s - word; + + DEBUG(D_transport) debug_printf(" XCLIENT: \tvalue %.*s\n", len, word); + if (len == 0) + { errmsg = US"XCLIENT: zero-length value for param"; goto fatal_501; } + + if ( len == 13 + && ( strncmpic(word, XCLIENT_UNAVAIL, 13) == 0 + || strncmpic(word, XCLIENT_TEMPUNAVAIL, 13) == 0 + ) ) + val = NULL; + + else if ((len = xclient_xtextdecode(word, s, &val)) == -1) + { + errmsg = string_sprintf("failed xtext decode for XCLIENT: '%.*s'", len, word); + goto fatal_501; + } + + store_pool = POOL_PERM; + switch (cmd) + { + case XCLIENT_CMD_ADDR: + proxy_local_address = sender_host_address; + sender_host_address = val ? string_copyn(val, len) : NULL; + break; + case XCLIENT_CMD_NAME: + sender_host_name = val ? string_copyn(val, len) : NULL; + break; + case XCLIENT_CMD_PORT: + proxy_local_port = sender_host_port; + sender_host_port = val ? Uatoi(val) : 0; + break; + case XCLIENT_CMD_DESTADDR: + proxy_external_address = val ? string_copyn(val, len) : NULL; + break; + case XCLIENT_CMD_DESTPORT: + proxy_external_port = val ? Uatoi(val) : 0; + break; + + case XCLIENT_CMD_LOGIN: + if (val) + { + authenticated_id = string_copyn(val, len); + sender_host_authenticated = US"xclient"; + authentication_failed = FALSE; + } + else + { + authenticated_id = NULL; + sender_host_authenticated = NULL; + } + break; + +# ifdef XCLIENT_V1 + case XCLIENT_CMD_HELO: + sender_helo_name = val ? string_copyn(val, len) : NULL; + break; + case XCLIENT_CMD_PROTO: + if (!val) + { store_pool = old_pool; errmsg = US"missing proto for XCLIENT"; goto fatal_501; } + else if (len == 4 && strncmpic(val, US"SMTP", 4) == 0) + *esmtpflag = FALSE; /* function arg */ + else if (len == 5 && strncmpic(val, US"ESMTP", 5) == 0) + *esmtpflag = TRUE; + else + { store_pool = old_pool; errmsg = US"bad proto for XCLIENT"; goto fatal_501; } + break; +# endif + default: break; /* stupid compiler silencing */ + } + store_pool = old_pool; + state = XCLIENT_SKIP_SPACES; + break; + } + + case XCLIENT_SKIP_SPACES: + Uskip_whitespace(&s); + state = XCLIENT_READ_COMMAND; + break; + + default: + errmsg = US"unhandled XCLIENT parameter type"; + goto fatal_501; + } + +if (!proxy_local_address) + { errmsg = US"missing ADDR for XCLIENT"; goto fatal_501; } +if (!proxy_local_port) + { errmsg = US"missing PORT for XCLIENT"; goto fatal_501; } +if (state != XCLIENT_SKIP_SPACES) + { errmsg = US"bad state parsing XCLIENT parameters"; goto fatal_501; } + +host_build_sender_fullhost(); +proxy_session = TRUE; /* global */ +xc_proxy_session = TRUE; /* local */ +*resp = 220; +return NULL; + +fatal_501: + *flagp= TRUE; + *resp = 501; + return errmsg; +} + +# undef XCLIENT_UNAVAIL +# undef XCLIENT_TEMPUNAVAIL + + +/*API*/ +static gstring * +xclient_smtp_advertise_str(gstring * g) +{ +g = string_catn(g, US"-XCLIENT ", 8); +for (int i = 1; i < nelem(xclient_cmds); i++) + { + g = string_catn(g, US" ", 1); + g = string_cat(g, xclient_cmds[i].str); + } +return string_catn(g, US"\r\n", 2); +} + + +/*API +Return: true if the cmd was good and the protocol started +*/ + +static int +xclient_protocol_start(const uschar * smtp_cmd_data, int * donep, + BOOL helo_seen) +{ +BOOL fatal = helo_seen; +uschar * errmsg; +int resp; + +if (!(errmsg = xclient_smtp_command(smtp_cmd_data, &resp, &fatal))) + { + /*XXX unclear in spec. if this needs to be an ESMTP banner, + nor whether we get the original client's HELO after (or a proxy fake). + We require that we do; the following HELO/EHLO handling will set + sender_helo_name as normal. */ + + smtp_printf("%d XCLIENT success\r\n", SP_NO_MORE, resp); + return TRUE; + } +else if (fatal) + *donep = synprot_error(L_smtp_syntax_error, resp, NULL, errmsg); +else + { + smtp_printf("%d %s\r\n", SP_NO_MORE, resp, errmsg); + log_write(0, LOG_MAIN|LOG_REJECT, "rejected XCLIENT from %s: %s", + host_and_ident(FALSE), errmsg); + } + +return FALSE; +} + +/******************************************************************************/ +/* Module API */ + +static void * xclient_functions[] = { + [XCLIENT_PROTO_ADVERTISE] = (void *) xclient_smtp_advertise_str, + [XCLIENT_PROTO_START] = (void *) xclient_protocol_start, +}; + +misc_module_info xclient_module_info = +{ + .name = US"xclient", +# ifdef DYNLOOKUP + .dyn_magic = MISC_MODULE_MAGIC, +# endif + .functions = xclient_functions, + .functions_count = nelem(xclient_functions), +}; +#endif /*EXPERIMENTAL_XCLIENT*/ + +/* vi: aw ai sw=2 +*/ +/* End of xclient.c */ + diff --git a/src/src/miscmods/xclient_api.h b/src/src/miscmods/xclient_api.h new file mode 100644 index 000000000..e59296395 --- /dev/null +++ b/src/src/miscmods/xclient_api.h @@ -0,0 +1,15 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2025 */ +/* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* API definitions for the proxy-protocol module */ + + +/* Function table entry numbers */ + +#define XCLIENT_PROTO_ADVERTISE 0 +#define XCLIENT_PROTO_START 1 diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index d694c8d7a..222fc5b93 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -3811,6 +3811,9 @@ smtp_setup_msg(void) int done = 0; BOOL toomany = FALSE, discarded = FALSE, last_was_rej_mail = FALSE, last_was_rcpt = FALSE; +#ifdef EXPERIMENTAL_XCLIENT +static misc_module_info * xclient_mi = NULL; +#endif rmark reset_point = store_mark(); DEBUG(D_receive) debug_printf("smtp_setup_msg entered\n"); @@ -4433,8 +4436,17 @@ while (done <= 0) #ifdef EXPERIMENTAL_XCLIENT if (proxy_session || verify_check_host(&hosts_xclient) != FAIL) { + uschar * dummy_errmsg; + typedef gstring * (*fn_t) (gstring *); + + if ( !xclient_mi + && !(xclient_mi = misc_mod_find(US"xclient", &dummy_errmsg))) + { + smtp_closedown(US"Temporary local problem - please try later"); + return FALSE; + } g = string_catn(g, smtp_code, 3); - g = xclient_smtp_advertise_str(g); + g = ((fn_t *) xclient_mi->functions)[XCLIENT_PROTO_ADVERTISE] (g); } #endif #ifndef DISABLE_PRDR @@ -4516,37 +4528,21 @@ while (done <= 0) #ifdef EXPERIMENTAL_XCLIENT case XCLIENT_CMD: - { - BOOL fatal = fl.helo_seen; - uschar * errmsg; - int resp; - HAD(SCH_XCLIENT); smtp_mailcmd_count++; - if ((errmsg = xclient_smtp_command(smtp_cmd_data, &resp, &fatal))) - if (fatal) - done = synprot_error(L_smtp_syntax_error, resp, NULL, errmsg); - else - { - smtp_printf("%d %s\r\n", SP_NO_MORE, resp, errmsg); - log_write(0, LOG_MAIN|LOG_REJECT, "rejected XCLIENT from %s: %s", - host_and_ident(FALSE), errmsg); - } + if (!xclient_mi) + done = synprot_error(L_smtp_syntax_error, 501, NULL, + US"XCLIENT command used when not advertised"); else { - fl.helo_seen = FALSE; /* Require another EHLO */ - smtp_code = string_sprintf("%d", resp); - - /*XXX unclear in spec. if this needs to be an ESMTP banner, - nor whether we get the original client's HELO after (or a proxy fake). - We require that we do; the following HELO/EHLO handling will set - sender_helo_name as normal. */ - - smtp_printf("%s XCLIENT success\r\n", SP_NO_MORE, smtp_code); + typedef BOOL (*fn_t) (const uschar *, int *, BOOL); + BOOL started = ((fn_t *) xclient_mi->functions)[XCLIENT_PROTO_START] + (smtp_cmd_data, &done, fl.helo_seen); + if (started) + fl.helo_seen = FALSE; /* Require another EHLO */ } break; /* XCLIENT */ - } #endif commit fda16457fbba6a11abdb09370ce21ea09f9e64f7 Author: Jeremy Harris Date: Sun Dec 14 15:32:48 2025 +0000 proxy-protocol module diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index 9f26eff0f..ff733b490 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -557,7 +557,7 @@ OBJ_EXIM = acl.o atrn.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o \ deliver.o directory.o dns.o drtables.o enq.o exim.o expand.o \ filtertest.o globals.o dnsbl.o hash.o \ header.o host.o host_address.o ip.o log.o lss.o match.o md5.o moan.o \ - os.o parse.o priv.o proxy.o queue.o \ + os.o parse.o priv.o queue.o \ rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o regex_cache.o \ route.o search.o smtp_in.o smtp_out.o spool_in.o spool_out.o \ std-crypto.o store.o string.o tls.o tod.o transport.o tree.o verify.o \ @@ -895,7 +895,6 @@ moan.o: $(HDRS) moan.c os.o: $(HDRS) $(OS_C_INCLUDES) os.c parse.o: $(HDRS) parse.c priv.o: $(HDRS) priv.c -proxy.o: $(HDRS) proxy.c queue.o: $(HDRS) queue.c rda.o: $(HDRS) rda.c readconf.o: $(HDRS) readconf.c diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile index 9a7351482..84a8abbb2 100755 --- a/src/scripts/Configure-Makefile +++ b/src/scripts/Configure-Makefile @@ -332,7 +332,7 @@ done <<-END routers ROUTER ACCEPT DNSLOOKUP IPLITERAL IPLOOKUP MANUALROUTE QUERYPROGRAM REDIRECT transports TRANSPORT APPENDFILE AUTOREPLY LMTP PIPE QUEUEFILE SMTP auths AUTH CRAM_MD5 CYRUS_SASL DOVECOT EXTERNAL GSASL HEIMDAL_GSSAPI PLAINTEXT SPA TLS - miscmods SUPPORT ARC _DKIM DMARC DMARC_NATIVE DSCP _EXIM_FILTER PAM PERL RADIUS _SIEVE_FILTER SOCKS SPF SPF_PERL XCLIENT + miscmods SUPPORT ARC _DKIM DMARC DMARC_NATIVE DSCP _EXIM_FILTER PAM PERL PROXY RADIUS _SIEVE_FILTER SOCKS SPF SPF_PERL XCLIENT END # See if there is a definition of EXIM_PERL in what we have built so far. diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index 642cd7302..5219d3dbc 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -106,6 +106,7 @@ for f in dummy.c \ exim_filter.c exim_filter_api.h \ pam.c pam_api.h \ perl.c perl_api.h \ + proxy.c proxy_api.h \ radius.c radius_api.h \ sieve_filter.c sieve_filter_api.h \ socks.c socks_api.h \ @@ -139,7 +140,7 @@ for f in blob.h dbfunctions.h exim.h functions.h globals.h \ debug.c deliver.c directory.c dns.c dnsbl.c drtables.c dummies.c enq.c \ exim.c exim_dbmbuild.c exim_dbutil.c exim_lock.c expand.c filtertest.c \ globals.c hash.c header.c host.c host_address.c ip.c log.c lss.c match.c \ - md5.c moan.c parse.c priv.c proxy.c queue.c rda.c readconf.c receive.c \ + md5.c moan.c parse.c priv.c queue.c rda.c readconf.c receive.c \ retry.c rewrite.c regex_cache.c rfc2047.c route.c search.c setenv.c \ environment.c smtp_in.c smtp_out.c spool_in.c spool_out.c std-crypto.c \ store.c string.c tls.c tlscert-gnu.c tlscert-openssl.c tls-cipher-stdname.c \ diff --git a/src/src/EDITME b/src/src/EDITME index 9e5b25029..8977f624b 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -1137,7 +1137,7 @@ ZCAT_COMMAND=/usr/bin/zcat # SUPPORT_SOCKS=yes # If you may want to use inbound (server-side) proxying, using Proxy Protocol, -# uncomment the line below. +# uncomment the line below. To build as a dynamic module set it to 2. # SUPPORT_PROXY=yes diff --git a/src/src/drtables.c b/src/src/drtables.c index 4df612c65..833c352c0 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -504,6 +504,9 @@ extern misc_module_info pam_module_info; #if defined(EXIM_PERL) && (!defined(SUPPORT_PERL) || SUPPORT_PERL!=2) extern misc_module_info perl_module_info; #endif +#if defined(SUPPORT_PROXY) && SUPPORT_PROXY!=2 +extern misc_module_info proxy_module_info; +#endif #if defined(RADIUS_CONFIG_FILE) && (!defined(SUPPORT_RADIUS) || SUPPORT_RADIUS!=2) extern misc_module_info radius_module_info; #endif @@ -553,6 +556,9 @@ onetime = TRUE; #if defined(EXIM_PERL) && (!defined(SUPPORT_PERL) || SUPPORT_PERL!=2) misc_mod_add(&perl_module_info); #endif +#if defined(SUPPORT_PROXY) && SUPPORT_PROXY!=2 + misc_mod_add(&proxy_module_info); +#endif #if !defined(DISABLE_EXIM_FILTER) && (!defined(SUPPORT_EXIM_FILTER) || SUPPORT_EXIM_FILTER!=2) misc_mod_add(&exim_filter_module_info); #endif diff --git a/src/src/exim.h b/src/src/exim.h index 7b4270cad..70911b0f8 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -567,6 +567,9 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly. #ifdef EXIM_PERL # include "miscmods/perl_api.h" #endif +#ifdef SUPPORT_PROXY +# include "miscmods/proxy_api.h" +#endif #ifdef SUPPORT_SOCKS # include "miscmods/socks_api.h" #endif diff --git a/src/src/functions.h b/src/src/functions.h index f07799321..b48103922 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -407,10 +407,6 @@ const misc_module_info * perl_startup(const uschar *); extern void priv_drop_temp(const uid_t, const gid_t); extern void priv_restore(void); -#ifdef SUPPORT_PROXY -extern BOOL proxy_protocol_host(void); -extern void proxy_protocol_setup(void); -#endif extern BOOL queue_action(const uschar *, int, const uschar **, int, int); extern void queue_check_only(void); diff --git a/src/src/globals.c b/src/src/globals.c index 6d2b27227..2d1354236 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -286,15 +286,15 @@ struct global_flags f = .parse_allow_group = FALSE, .parse_found_group = FALSE, .pipelining_enable = TRUE, -#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) - .proxy_session_failed = FALSE, -#endif .queue_2stage = FALSE, .queue_only_policy = FALSE, .queue_run_local = FALSE, .queue_running = FALSE, .queue_smtp = FALSE, +#if defined(SUPPORT_PROXY) + .quit_cmd_only = FALSE, +#endif .really_exim = TRUE, .receive_call_bombout = FALSE, diff --git a/src/src/globals.h b/src/src/globals.h index 977107f58..52c46f169 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -256,15 +256,15 @@ extern struct global_flags { BOOL parse_allow_group :1; /* Allow group syntax */ BOOL parse_found_group :1; /* In the middle of a group */ BOOL pipelining_enable :1; /* As it says */ -#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) - BOOL proxy_session_failed :1; /* TRUE if required proxy negotiation failed */ -#endif BOOL queue_2stage :1; /* Run queue in 2-stage manner */ BOOL queue_only_policy :1; /* ACL or local_scan wants queue_only */ BOOL queue_run_local :1; /* Local deliveries only in queue run */ BOOL queue_running :1; /* TRUE for queue running process and */ BOOL queue_smtp :1; /* Disable all immediate SMTP (-odqs)*/ +#ifdef SUPPORT_PROXY + BOOL quit_cmd_only :1; /* TRUE if required proxy negotiation failed */ +#endif BOOL really_exim :1; /* FALSE in utilities */ BOOL receive_call_bombout :1; /* Flag for crashing log */ diff --git a/src/src/miscmods/Makefile b/src/src/miscmods/Makefile index f731088ba..1076c6daf 100644 --- a/src/src/miscmods/Makefile +++ b/src/src/miscmods/Makefile @@ -46,6 +46,7 @@ dummy.o: dummy.c exim_filter.o exim_filter.so: $(HDRS) exim_filter.c pam.o pam.so: $(HDRS) pam.c perl.o perl.so: $(HDRS) perl.c +proxy.o proxy.so: $(HDRS) proxy_api.h proxy.c radius.o radius.so: $(HDRS) radius.c sieve_filter.o sieve_filter.so: $(HDRS) sieve_filter.c socks.o socks.so: $(HDRS) socks_api.h ../transports/smtp.h socks.c diff --git a/src/src/proxy.c b/src/src/miscmods/proxy.c similarity index 95% rename from src/src/proxy.c rename to src/src/miscmods/proxy.c index c8230b009..320b57ea8 100644 --- a/src/src/proxy.c +++ b/src/src/miscmods/proxy.c @@ -11,9 +11,17 @@ * Proxy-Protocol support * ************************************************/ -#include "exim.h" +#include "../exim.h" #ifdef SUPPORT_PROXY + + +static void +command_timeout_handler(int sig) +{ +had_command_timeout = sig; +} + /************************************************* * Check if host is required proxy host * *************************************************/ @@ -25,7 +33,7 @@ Arguments: none Returns: boolean for Proxy Protocol needed */ -BOOL +static BOOL proxy_protocol_host(void) { if ( sender_host_address @@ -34,6 +42,7 @@ if ( sender_host_address { DEBUG(D_receive) debug_printf("Detected proxy protocol configured host\n"); + /* having this set when we could still fail is ugly */ proxy_session = TRUE; } return proxy_session; @@ -114,6 +123,7 @@ debug_printf("PROXY<<%3.*H\n", (int)(end - start), buf + start); } +/*API*/ /************************************************* * Setup host for proxy protocol * *************************************************/ @@ -123,11 +133,11 @@ so exit with an error if do not find the exact required pieces. This includes an incorrect number of spaces separating args. Arguments: none -Returns: Boolean success +Returns: TRUE iff success */ -void -proxy_protocol_setup(void) +static BOOL +proxy_protocol(void) { union { struct { @@ -201,6 +211,10 @@ const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; uschar * iptype; /* To display debug info */ BOOL yield = FALSE; +if (!proxy_protocol_host()) + return TRUE; + +os_non_restarting_signal(SIGALRM, command_timeout_handler); ALARM(proxy_protocol_timeout); do @@ -496,6 +510,8 @@ done: should cause a synchronization failure */ proxyfail: + + ALARM(0); DEBUG(D_receive) if (had_command_timeout) debug_printf("Timeout while reading proxy header\n"); @@ -506,15 +522,29 @@ proxyfail: host_build_sender_fullhost(); } else - { - f.proxy_session_failed = TRUE; DEBUG(D_receive) debug_printf("Failure to extract proxied host, only QUIT allowed\n"); - } -ALARM(0); -return; +return yield; } + + +/******************************************************************************/ +/* Module API */ + +static void * proxy_functions[] = { + [PROXY_PROTO_START] = (void *) proxy_protocol, +}; + +misc_module_info proxy_module_info = +{ + .name = US"proxy", +# ifdef DYNLOOKUP + .dyn_magic = MISC_MODULE_MAGIC, +# endif + .functions = proxy_functions, + .functions_count = nelem(proxy_functions), +}; #endif /*SUPPORT_PROXY*/ /* vi: aw ai sw=2 diff --git a/src/src/miscmods/proxy_api.h b/src/src/miscmods/proxy_api.h new file mode 100644 index 000000000..4cd57908c --- /dev/null +++ b/src/src/miscmods/proxy_api.h @@ -0,0 +1,14 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2025 */ +/* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* API definitions for the proxy-protocol module */ + + +/* Function table entry numbers */ + +#define PROXY_PROTO_START 0 diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 222fc5b93..3ffb6fe8b 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -1308,8 +1308,8 @@ if required. */ for (smtp_cmd_list * p = cmd_list; p < cmd_list + nelem(cmd_list); p++) { #ifdef SUPPORT_PROXY - /* Only allow QUIT command if Proxy Protocol parsing failed */ - if (proxy_session && f.proxy_session_failed && p->cmd != QUIT_CMD) + /* If Proxy Protocol parsing failed, only allow QUIT command */ + if (f.quit_cmd_only && p->cmd != QUIT_CMD) continue; #endif if ( p->len @@ -1363,8 +1363,8 @@ for (smtp_cmd_list * p = cmd_list; p < cmd_list + nelem(cmd_list); p++) } #ifdef SUPPORT_PROXY -/* Only allow QUIT command if Proxy Protocol parsing failed */ -if (proxy_session && f.proxy_session_failed) +/* If Proxy Protocol parsing failed, only allow QUIT command */ +if (f.quit_cmd_only) return PROXY_FAIL_IGNORE_CMD; #endif @@ -2668,11 +2668,20 @@ proxy_session = FALSE; /* If valid Proxy Protocol source is connecting, set up session. Failure will not allow any SMTP function other than QUIT. */ -f.proxy_session_failed = FALSE; -if (proxy_protocol_host()) +f.quit_cmd_only = FALSE; +if (hosts_proxy) { - os_non_restarting_signal(SIGALRM, command_timeout_handler); - proxy_protocol_setup(); + uschar * dummy_errmsg; + misc_module_info * mi; + typedef BOOL (*fn_t) (void); + + if (!(mi = misc_mod_find(US"proxy", &dummy_errmsg))) + { + smtp_closedown(US"Temporary local problem - please try later"); + return FALSE; + } + if (!((fn_t *) mi->functions)[PROXY_PROTO_START] ()) + f.quit_cmd_only = TRUE; } #endif commit 53e771b0ddaa89477793c8955ed474acf42c32b5 Author: Jeremy Harris Date: Thu Dec 18 10:50:33 2025 +0000 Docs: more notes on DBM lookups diff --git a/src/src/dbfn.c b/src/src/dbfn.c index 7c2dbcce3..3ed78901b 100644 --- a/src/src/dbfn.c +++ b/src/src/dbfn.c @@ -381,6 +381,16 @@ no guarantee of alignment. Since all the records used by Exim need to be properly aligned to pick out the timestamps, etc., we might as well do the copying centrally here. +Callers: + dbfn_read_with_length (below) + dbfn_read dbfunctions.h + (assorted hintsdb uses) + (assorted hintsdb uses) + dbfn_read_enforce_length (below) + (assorted hintsdb uses) + exim_dbutil.c utils + lookups/dbmdb.c lookup dbm + Arguments: dbblock a pointer to an open database block key the key of the record to be read diff --git a/src/src/hintsdb/hints_sqlite.h b/src/src/hintsdb/hints_sqlite.h index dd88dd98b..1772a85c7 100644 --- a/src/src/hintsdb/hints_sqlite.h +++ b/src/src/hintsdb/hints_sqlite.h @@ -187,6 +187,11 @@ return more; } /* EXIM_DBGET - returns the value associated with the key. */ +/* Callers: + dbfn_read_klen lookup-dbm, hints general + autoreply_transport_entry autoreply tpt + dbfn_read_with_length utils +*/ static inline BOOL exim_dbget(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res) { @@ -208,6 +213,12 @@ return (SQLITE_DONE == exim_sqlprep_step(dbp, sql, key, data, NULL )) ? EXIM_DBP } /* EXIM_DBPUT - returns nothing useful, assumes replace mode */ +/* Callers: + dbfn_write hints general + autoreply_transport_entry autoreply tpt + main(dbmbuild) utils + dbfn_write utils +*/ static inline int exim_dbput(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data) { @@ -229,6 +240,10 @@ return exim_s_dbp(dbp, key, data, "INSERT OR ABORT INTO tblblob (ky, dat) VALUES } /* EXIM_DBDEL */ +/* Callers + dbfn_delete hints general + dnfn_delete utils +*/ static inline int exim_dbdel(EXIM_DB * dbp, EXIM_DATUM * key) { commit a51a01811d1b0984603662609a6b7f0845b34387 Author: Jeremy Harris Date: Fri Nov 28 12:45:10 2025 +0000 exim_dumpdb for lookup-dbm files diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c index 42396fe8b..be1cf710e 100644 --- a/src/src/exim_dbutil.c +++ b/src/src/exim_dbutil.c @@ -30,25 +30,23 @@ There are a number of common subroutines, followed by three main programs, whose inclusion is controlled by -D on the compilation command. */ +#include #include "exim.h" #include "hintsdb.h" /* Identifiers for the different database types. */ -#define type_retry 1 -#define type_wait 2 -#define type_misc 3 -#define type_callout 4 -#define type_ratelimit 5 -#define type_tls 6 -#define type_seen 7 - +enum dbtype { type_retry = 1, type_wait, type_misc, type_callout, + type_ratelimit, type_tls, type_seen, type_dbm +}; /* This is used by our cut-down dbfn_open(). */ -uschar *spool_directory; +uschar * spool_directory = NULL; +const uschar * lockfile_name = NULL; +BOOL dbmdb = FALSE; BOOL keyonly = FALSE; BOOL utc = FALSE; @@ -110,7 +108,15 @@ sigalrm_seen = 1; static void usage(const uschar * name, const uschar * options) { -printf("Usage: exim_%s%s \n", name, options); +uschar * s = store_get(Ustrlen(options), options), * t = s; + +if (Ustrchr(options, 'L')) + printf("Usage: exim_%s -L \n\n", name); + +/* drop the L from options */ +do { if (*options != 'L') *t++ = *options; } while (*options++); + +printf("Usage: exim_%s%s \n", name, s); printf(" = retry | misc | wait- | callout | ratelimit | tls | seen\n"); exit(EXIT_FAILURE); } @@ -146,6 +152,8 @@ second of them to be sure it is a known database name. */ static int check_args(int argc, uschar **argv, const uschar * name, const uschar * options) { +if (dbmdb && argc - optind == 1) return type_dbm; + if (argc - optind == 2) { const uschar * aname = argv[optind + 1]; @@ -172,9 +180,10 @@ opterr = 0; while ((opt = getopt(argc, (char * const *)argv, CCS opts)) != -1) switch (opt) { + case 'L': dbmdb = TRUE; break; case 'k': keyonly = TRUE; break; case 'z': utc = TRUE; break; - default: usage(name, US" [-z] [-k]"); + default: usage(name, US" [-kLz]"); } } @@ -322,29 +331,40 @@ dbfn_open(const uschar * name, int flags, open_db * dbblock, { int rc; struct flock lock_data; -uschar * dirname, * filename; +uschar * dname, * filename; /* The first thing to do is to open a separate file on which to lock. This ensures that Exim has exclusive use of the database before it even tries to open it. If there is a database, there should be a lock file in existence; -if no lockfile we infer there is no database and error out. We open the +if no lockfile and a create fails, we assume it was the spooldir perms (and +we have no write perms) and there is no database - and error out. We open the lockfile using the r/w mode requested for the DB, users lacking permission for the DB access mode will error out here. */ -if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0 - || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0) +if (dbmdb) + { + if (asprintf(CSS &filename, "%s.lockfile", name) < 0) + return NULL; + dname = Ustrchr(name, '/') /* path has dirs */ + ? US dirname(CS string_copy(name)) + : US "."; + } +else if ( asprintf(CSS &dname, "%s/db", spool_directory) < 0 + || asprintf(CSS &filename, "%s/%s.lockfile", dname, name) < 0) return NULL; dbblock->readonly = (flags & O_ACCMODE) == O_RDONLY; dbblock->lockfd = -1; if (exim_lockfile_needed()) { - if ((dbblock->lockfd = Uopen(filename, flags, 0)) < 0) + if ((dbblock->lockfd = Uopen(filename, dbmdb ? flags | O_CREAT : flags, + 0600)) < 0) { printf("** Failed to open database lock file %s: %s\n", filename, strerror(errno)); return NULL; } + lockfile_name = string_copy(filename); /* Now we must get a lock on the opened lock file; do this with a blocking lock that times out. */ @@ -366,6 +386,7 @@ if (exim_lockfile_needed()) filename, errno == ETIMEDOUT ? "timed out" : strerror(errno)); (void)close(dbblock->lockfd); + unlink(CS filename); return NULL; } @@ -373,11 +394,14 @@ if (exim_lockfile_needed()) exclusive access to the database, so we can go ahead and open it. */ } -if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL; +if ((dbmdb + ? asprintf(CSS &filename, "%s", name) + : asprintf(CSS &filename, "%s/%s", dname, name) + ) < 0) return NULL; if (!(dbblock->dbptr = dbblock->readonly && !exim_lockfile_needed() - ? exim_dbopen_multi(filename, dirname, flags, 0) - : exim_dbopen(filename, dirname, flags, 0))) + ? exim_dbopen_multi(filename, dname, flags, 0) + : exim_dbopen(filename, dname, flags, 0))) { printf("** Failed to open hintsdb file %s for %s: %s%s\n", filename, dbblock->readonly ? "reading" : "writing", strerror(errno), @@ -388,6 +412,7 @@ if (!(dbblock->dbptr = dbblock->readonly && !exim_lockfile_needed() #endif ); if (dbblock->lockfd >= 0) (void)close(dbblock->lockfd); + unlink(CCS lockfile_name); return NULL; } @@ -590,26 +615,33 @@ return yield; *************************************************/ int -main(int argc, char **cargv) +main(int argc, char ** cargv) { -int dbdata_type = 0; -int yield = 0; +int dbdata_type = 0, yield = 0; open_db dbblock; -open_db *dbm; -EXIM_CURSOR *cursor; -uschar **argv = USS cargv; +open_db * dbm; +EXIM_CURSOR * cursor; +uschar ** argv = USS cargv; +const uschar * file; uschar keybuffer[1024]; store_init(); -options(argc, argv, US"dumpdb", US"kz"); +options(argc, argv, US"dumpdb", US"kLz"); /* Check the arguments, and open the database */ -dbdata_type = check_args(argc, argv, US"dumpdb", US" [-z] [-k]"); +dbdata_type = check_args(argc, argv, US"dumpdb", US" [-kLz]"); argc -= optind; argv += optind; -spool_directory = argv[0]; -if (!(dbm = dbfn_open(argv[1], O_RDONLY, &dbblock, FALSE, TRUE))) +if (dbmdb) + file = argv[0]; +else + { + spool_directory = argv[0]; + file = argv[1]; + } + +if (!(dbm = dbfn_open(file, O_RDONLY, &dbblock, FALSE, TRUE))) exit(EXIT_FAILURE); /* Scan the file, formatting the information for each entry. Note @@ -772,11 +804,15 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor); seen = (dbdata_seen *)value; printf("%s\t%s\n", keybuffer, print_time(seen->time_stamp)); break; + + case type_dbm: + printf("%s\t%s\n", keybuffer, value); } store_reset(reset_point); } dbfn_close(dbm); +unlink(CCS lockfile_name); return yield; } commit e76fd60f5560db45225a4e2a35c5c4a27217464b Author: Jeremy Harris Date: Sat Nov 29 12:56:09 2025 +0000 exim_fixdb for lookup-dbm files diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index ff733b490..9524dcef9 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -586,7 +586,7 @@ exim: buildlookups buildauths buildrouters buildtransports buildmisc \ # The utility for dumping the contents of an exim database -OBJ_DUMPDB = exim_dumpdb.o util-os.o util-store.o util-xtextencode.o +OBJ_DUMPDB = exim_dumpdb.o util-os.o util-store.o util-string.o exim_dumpdb: $(OBJ_DUMPDB) @echo "$(LNCC) -o exim_dumpdb" @@ -601,7 +601,7 @@ exim_dumpdb: $(OBJ_DUMPDB) # The utility for interrogating/fixing the contents of an exim database -OBJ_FIXDB = exim_fixdb.o util-os.o util-store.o util-md5.o util-xtextencode.o +OBJ_FIXDB = exim_fixdb.o util-os.o util-store.o util-md5.o exim_fixdb: $(OBJ_FIXDB) @echo "$(LNCC) -o exim_fixdb" @@ -616,7 +616,7 @@ exim_fixdb: $(OBJ_FIXDB) # The utility for tidying the contents of an exim database -OBJ_TIDYDB = exim_tidydb.o util-os.o util-store.o util-xtextencode.o +OBJ_TIDYDB = exim_tidydb.o util-os.o util-store.o exim_tidydb: $(OBJ_TIDYDB) @echo "$(LNCC) -o exim_tidydb" @@ -632,7 +632,7 @@ exim_tidydb: $(OBJ_TIDYDB) # The utility for building dbm files -OBJ_DBMBUILD = exim_dbmbuild.o util-xtextencode.o +OBJ_DBMBUILD = exim_dbmbuild.o exim_dbmbuild: $(OBJ_DBMBUILD) @echo "$(LNCC) -o exim_dbmbuild" @@ -682,7 +682,6 @@ OBJ_MONBIN = util-host_address.o \ util-string.o \ util-tod.o \ util-tree.o \ - util-xtextencode.o \ $(MONBIN) eximon.bin: $(EXIMON_EDITME) eximon $(OBJ_MONBIN) ../exim_monitor/em_version.c \ @@ -843,10 +842,6 @@ util-tree.o: $(HDRS) tree.c @echo "$(CC) -DCOMPILE_UTILITY tree.c" $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -DCOMPILE_UTILITY -o util-tree.o tree.c -util-xtextencode.o: $(HDRS) xtextencode.c - @echo "$(CC) -DCOMPILE_UTILITY xtextencode.c" - $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -DCOMPILE_UTILITY -o util-xtextencode.o xtextencode.c - util-os.o: $(HDRS) os.c @echo "$(CC) -DCOMPILE_UTILITY os.c" $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) \ diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c index be1cf710e..26dca7dbd 100644 --- a/src/src/exim_dbutil.c +++ b/src/src/exim_dbutil.c @@ -52,7 +52,7 @@ BOOL utc = FALSE; /******************************************************************************/ - /* dummies needed by Solaris build */ + /* dummies needed by Solaris build */ void millisleep(int msec) {} @@ -61,20 +61,6 @@ readconf_printtime(int t) { return NULL; } const uschar * expand_string_2(const uschar * string, BOOL * textonly_p) { return NULL; } -gstring * -string_catn(gstring * g, const uschar * s, int count) -{ return NULL; } -gstring * -string_vformat_trc(gstring * g, const uschar * func, unsigned line, - unsigned size_limit, unsigned flags, const char *format, va_list ap) -{ return NULL; } -uschar * -string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...) -{ return NULL; } -BOOL -string_format_trc(uschar * buf, int len, const uschar * func, unsigned line, - const char * fmt, ...) -{ return FALSE; } const uschar * parse_find_address_end_gen(const uschar * s, BOOL b) {return NULL; } @@ -442,7 +428,9 @@ else exim_dbclose(dbp->dbptr); if (dbp->lockfd >= 0) - (void) close(dbp->lockfd); + { (void) close(dbp->lockfd); dbp->lockfd = -1; } +unlink(CCS lockfile_name); +lockfile_name = NULL; } @@ -516,15 +504,13 @@ Returns: the yield of the underlying dbm or db "write" function. If this */ int -dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length) +dbfn_write(open_db * dbblock, const uschar * key, void * ptr, int length) { EXIM_DATUM key_datum, value_datum; -dbdata_generic *gptr = (dbdata_generic *)ptr; int klen = Ustrlen(key) + 1; uschar * key_copy = store_get(klen, key); memcpy(key_copy, key, klen); -gptr->time_stamp = time(NULL); exim_datum_init(&key_datum); /* Some DBM libraries require the datum */ exim_datum_init(&value_datum); /* to be cleared before use. */ @@ -738,9 +724,18 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor); break; case type_misc: - printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp), - keybuffer); + /* It might be nice to recognize the subtypes of "misc" DBs (by the + key prefix?) for better decode. For now, dump the raw data. */ + + { + dbdata_generic * recp = (dbdata_generic *)value; + uschar * dp = US (recp + 1); + int dlen = length = sizeof(dbdata_generic); + + printf("%s %s\n", print_time(recp->time_stamp), keybuffer); + printf("%s\n", string_sprintf("%.*q\n", dlen, dp)); break; + } case type_callout: callout = (dbdata_callout_cache *)value; @@ -806,13 +801,12 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor); break; case type_dbm: - printf("%s\t%s\n", keybuffer, value); + printf("%s\t%.*s\n", keybuffer, length, value); } store_reset(reset_point); } dbfn_close(dbm); -unlink(CCS lockfile_name); return yield; } @@ -860,22 +854,30 @@ int dbdata_type; uschar **argv = USS cargv; uschar buffer[256]; uschar name[256]; +const uschar * file; rmark reset_point; -uschar * aname; store_init(); -options(argc, argv, US"fixdb", US"z"); -name[0] = 0; /* No name set */ +options(argc, argv, US"fixdb", US"Lz"); +*name = '\0'; /* No name set */ /* Sort out the database type, verify what we are working on and then process user requests */ -dbdata_type = check_args(argc, argv, US"fixdb", US" [-z]"); +dbdata_type = check_args(argc, argv, US"fixdb", US" [-Lz]"); argc -= optind; argv += optind; -spool_directory = argv[0]; -aname = argv[1]; -printf("Modifying Exim hints database %s/db/%s\n", spool_directory, aname); +if (dbmdb) + { + file = argv[0]; + printf("Modifying Exim dbm database %s\n", file); + } +else + { + spool_directory = argv[0]; + file = argv[1]; + printf("Modifying Exim hints database %s/db/%s\n", spool_directory, file); + } for(; (reset_point = store_mark()); store_reset(reset_point)) { @@ -888,9 +890,8 @@ for(; (reset_point = store_mark()); store_reset(reset_point)) dbdata_ratelimit *ratelimit; dbdata_ratelimit_unique *rate_unique; dbdata_tls_session *session; - int oldlength; - uschar *t; - uschar field[256], value[256]; + int oldlength, newlength; + uschar * t, field[256], value[256]; printf("> "); if (Ufgets(buffer, 256, stdin) == NULL) break; @@ -904,7 +905,7 @@ for(; (reset_point = store_mark()); store_reset(reset_point)) if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0')) || Ustrcmp(buffer, "d") == 0) { - if (name[0] == 0) + if (!*name) { printf("No previous record name is set\n"); continue; @@ -913,167 +914,175 @@ for(; (reset_point = store_mark()); store_reset(reset_point)) } else { - name[0] = 0; + *name = '\0'; (void)sscanf(CS buffer, "%s %s %s", name, field, value); } /* Handle an update request */ - if (field[0] != 0) + if (*field) { int verify = 1; - if (!(dbm = dbfn_open(aname, O_RDWR|O_CREAT, &dbblock, FALSE, TRUE))) + if (!(dbm = dbfn_open(file, O_RDWR|O_CREAT, &dbblock, FALSE, TRUE))) continue; if (Ustrcmp(field, "d") == 0) { - if (value[0] != 0) printf("unexpected value after \"d\"\n"); - else printf("%s\n", (dbfn_delete(dbm, name) < 0)? - "not found" : "deleted"); + if (*value) + printf("unexpected value after \"d\"\n"); + else + printf("%s\n", dbfn_delete(dbm, name) < 0 ? "not found" : "deleted"); dbfn_close(dbm); continue; } - else if (isdigit((uschar)field[0])) + else if (dbdata_type == type_dbm || isdigit((uschar)field[0])) { - int fieldno = Uatoi(field); - if (value[0] == 0) + if (!*value) { printf("value missing\n"); dbfn_close(dbm); continue; } + else if (!(record = dbfn_read_with_length(dbm, name, &oldlength))) + printf("not found\n"); else - { - record = dbfn_read_with_length(dbm, name, &oldlength); - if (record == NULL) printf("not found\n"); else - { - time_t tt; - /*int length = 0; Stops compiler warning */ + { + int fieldno = Uatoi(field); + time_t tt; + /*int length = 0; Stops compiler warning */ - switch(dbdata_type) - { - case type_retry: - retry = (dbdata_retry *)record; - /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */ + newlength = oldlength; + switch(dbdata_type) + { + case type_retry: + retry = (dbdata_retry *)record; + /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */ + + switch(fieldno) + { + case 0: retry->basic_errno = Uatoi(value); + break; + case 1: retry->more_errno = Uatoi(value); + break; + case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt; + else printf("bad time value\n"); + break; + case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt; + else printf("bad time value\n"); + break; + case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt; + else printf("bad time value\n"); + break; + case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE; + else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE; + else printf("\"yes\" or \"no\" expected=n"); + break; + default: printf("unknown field number\n"); + verify = 0; + break; + } + break; - switch(fieldno) - { - case 0: retry->basic_errno = Uatoi(value); - break; - case 1: retry->more_errno = Uatoi(value); - break; - case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt; - else printf("bad time value\n"); - break; - case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt; - else printf("bad time value\n"); - break; - case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt; - else printf("bad time value\n"); - break; - case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE; - else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE; - else printf("\"yes\" or \"no\" expected=n"); - break; - default: printf("unknown field number\n"); - verify = 0; - break; - } - break; + case type_wait: + printf("Can't change contents of wait database record\n"); + break; - case type_wait: - printf("Can't change contents of wait database record\n"); - break; + case type_misc: + printf("Can't change contents of misc database record\n"); + break; - case type_misc: - printf("Can't change contents of misc database record\n"); + case type_callout: + callout = (dbdata_callout_cache *)record; + /* length = sizeof(dbdata_callout_cache); */ + switch(fieldno) + { + case 0: callout->result = Uatoi(value); + break; + case 1: callout->postmaster_result = Uatoi(value); + break; + case 2: callout->random_result = Uatoi(value); + break; + default: printf("unknown field number\n"); + verify = 0; + break; + } break; - case type_callout: - callout = (dbdata_callout_cache *)record; - /* length = sizeof(dbdata_callout_cache); */ - switch(fieldno) - { - case 0: callout->result = Uatoi(value); - break; - case 1: callout->postmaster_result = Uatoi(value); - break; - case 2: callout->random_result = Uatoi(value); + case type_ratelimit: + ratelimit = (dbdata_ratelimit *)record; + switch(fieldno) + { + case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt; + else printf("bad time value\n"); + break; + case 1: ratelimit->time_usec = Uatoi(value); + break; + case 2: ratelimit->rate = Ustrtod(value, NULL); + break; + case 3: if (Ustrstr(name, "/unique/") != NULL + && oldlength >= sizeof(dbdata_ratelimit_unique)) + { + rate_unique = (dbdata_ratelimit_unique *)record; + if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt; + else printf("bad time value\n"); break; - default: printf("unknown field number\n"); - verify = 0; - break; - } - break; - - case type_ratelimit: - ratelimit = (dbdata_ratelimit *)record; - switch(fieldno) - { - case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt; - else printf("bad time value\n"); - break; - case 1: ratelimit->time_usec = Uatoi(value); - break; - case 2: ratelimit->rate = Ustrtod(value, NULL); - break; - case 3: if (Ustrstr(name, "/unique/") != NULL - && oldlength >= sizeof(dbdata_ratelimit_unique)) + } + /* else fall through */ + case 4: + case 5: if (Ustrstr(name, "/unique/") != NULL + && oldlength >= sizeof(dbdata_ratelimit_unique)) + { + /* see acl.c */ + BOOL seen; + unsigned hash, hinc; + uschar md5sum[16]; + md5 md5info; + md5_start(&md5info); + md5_end(&md5info, value, Ustrlen(value), md5sum); + hash = md5sum[0] << 0 | md5sum[1] << 8 + | md5sum[2] << 16 | md5sum[3] << 24; + hinc = md5sum[4] << 0 | md5sum[5] << 8 + | md5sum[6] << 16 | md5sum[7] << 24; + rate_unique = (dbdata_ratelimit_unique *)record; + seen = TRUE; + for (unsigned n = 0; n < 8; n++, hash += hinc) { - rate_unique = (dbdata_ratelimit_unique *)record; - if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt; - else printf("bad time value\n"); - break; - } - /* else fall through */ - case 4: - case 5: if (Ustrstr(name, "/unique/") != NULL - && oldlength >= sizeof(dbdata_ratelimit_unique)) - { - /* see acl.c */ - BOOL seen; - unsigned hash, hinc; - uschar md5sum[16]; - md5 md5info; - md5_start(&md5info); - md5_end(&md5info, value, Ustrlen(value), md5sum); - hash = md5sum[0] << 0 | md5sum[1] << 8 - | md5sum[2] << 16 | md5sum[3] << 24; - hinc = md5sum[4] << 0 | md5sum[5] << 8 - | md5sum[6] << 16 | md5sum[7] << 24; - rate_unique = (dbdata_ratelimit_unique *)record; - seen = TRUE; - for (unsigned n = 0; n < 8; n++, hash += hinc) + int bit = 1 << (hash % 8); + int byte = (hash / 8) % rate_unique->bloom_size; + if ((rate_unique->bloom[byte] & bit) == 0) { - int bit = 1 << (hash % 8); - int byte = (hash / 8) % rate_unique->bloom_size; - if ((rate_unique->bloom[byte] & bit) == 0) - { - seen = FALSE; - if (fieldno == 5) rate_unique->bloom[byte] |= bit; - } + seen = FALSE; + if (fieldno == 5) rate_unique->bloom[byte] |= bit; } - printf("%s %s\n", - seen ? "seen" : fieldno == 5 ? "added" : "unseen", value); - break; } - /* else fall through */ - default: printf("unknown field number\n"); - verify = 0; - break; - } - break; + printf("%s %s\n", + seen ? "seen" : fieldno == 5 ? "added" : "unseen", value); + break; + } + /* else fall through */ + default: printf("unknown field number\n"); + verify = 0; + break; + } + break; - case type_tls: - printf("Can't change contents of tls database record\n"); - break; - } + case type_tls: + printf("Can't change contents of tls database record\n"); + break; - dbfn_write(dbm, name, record, oldlength); - } - } + case type_dbm: + record = value; + newlength = Ustrlen(value); + break; + } + + if (dbdata_type != type_dbm) + ((dbdata_generic *)record)->time_stamp = time(NULL); + + dbfn_write(dbm, name, record, newlength); + } } else @@ -1092,18 +1101,21 @@ for(; (reset_point = store_mark()); store_reset(reset_point)) /* Handle a read request, or verify after an update. */ - if (!(dbm = dbfn_open(aname, O_RDONLY, &dbblock, FALSE, TRUE))) + if (!(dbm = dbfn_open(file, O_RDONLY, &dbblock, FALSE, TRUE))) continue; if (!(record = dbfn_read_with_length(dbm, name, &oldlength))) { printf("record %s not found\n", name); - name[0] = 0; + *name = '\0'; } else { int count_bad = 0; - printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp)); + + if (dbdata_type != type_dbm) + printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp)); + switch(dbdata_type) { case type_retry: @@ -1113,7 +1125,7 @@ for(; (reset_point = store_mark()); store_reset(reset_point)) printf("2 first failed: %s\n", print_time(retry->first_failed)); printf("3 last try: %s\n", print_time(retry->last_try)); printf("4 next try: %s\n", print_time(retry->next_try)); - printf("5 expired: %s\n", (retry->expired)? "yes" : "no"); + printf("5 expired: %s\n", retry->expired ? "yes" : "no"); break; case type_wait: @@ -1185,6 +1197,9 @@ for(; (reset_point = store_mark()); store_reset(reset_point)) printf("0 time stamp: %s\n", print_time(session->time_stamp)); printf("1 session: .%s\n", session->session); break; + + case type_dbm: + printf("0 value: %.*s\n", oldlength, record); } } @@ -1451,6 +1466,7 @@ for (; keychain && (reset_point = store_mark()); store_reset(reset_point)) if (update) { printf("updated %s\n", key); + wait->time_stamp = time(NULL); dbfn_write(dbm, key, wait, sizeof(dbdata_wait) + wait->count * MESSAGE_ID_LENGTH); } diff --git a/src/src/functions.h b/src/src/functions.h index b48103922..bfe34f391 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -594,6 +594,7 @@ extern int string_is_ip_addressX(const uschar *, int *, const uschar **); extern BOOL string_is_utf8(const uschar *); #endif extern const uschar *string_printing2(const uschar *, int); +extern const uschar *string_printing3(const uschar *, int, int); extern uschar *string_split_message(uschar *); extern uschar *string_unprinting(uschar *); #ifdef SUPPORT_I18N diff --git a/src/src/hintsdb_structs.h b/src/src/hintsdb_structs.h index 3eb8b3386..c77190f38 100644 --- a/src/src/hintsdb_structs.h +++ b/src/src/hintsdb_structs.h @@ -25,8 +25,10 @@ typedef struct { } open_db; -/* Structures for records stored in exim database dbm files. They all -start with the same fields, described in the generic type. */ +/* Structures for records stored in exim database hinsts files. They all +start with the same fields, described in the generic type. +DBM databases are used for hints files. +*/ typedef struct { commit 73d3b73c090557b9f11cef78623cf11b81e9c7a9 Author: Jeremy Harris Date: Sat Nov 29 21:02:12 2025 +0000 track tainted values through hintsdb storage diff --git a/src/src/acl.c b/src/src/acl.c index bc4f6af6d..7ec3a898c 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -2826,7 +2826,7 @@ if (!dbd) { HDEBUG(D_acl) debug_printf_indent("ratelimit initializing new key's rate data\n"); dbd = &dbdb->dbd; - dbd->time_stamp = tv.tv_sec; + dbd->gen.time_stamp = tv.tv_sec; dbd->time_usec = tv.tv_usec; dbd->rate = count; } @@ -2876,7 +2876,7 @@ else double this_time = (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0; - double prev_time = (double)dbd->time_stamp + double prev_time = (double)dbd->gen.time_stamp + (double)dbd->time_usec / 1000000.0; /* We must avoid division by zero, and deal gracefully with the clock going @@ -2893,7 +2893,7 @@ else using the smoothing factor a. In order to measure sized events, multiply the instantaneous rate by the count of bytes or recipients etc. */ - dbd->time_stamp = tv.tv_sec; + dbd->gen.time_stamp = tv.tv_sec; dbd->time_usec = tv.tv_usec; dbd->rate = (1 - a) * count / i_over_p + a * dbd->rate; @@ -3048,7 +3048,7 @@ dbd = dbfn_read_with_length(dbm, key, NULL); now = time(NULL); if (dbd) /* an existing record */ { - time_t diff = now - dbd->time_stamp; /* time since the record was written */ + time_t diff = now - dbd->gen.time_stamp; /* time since record was written */ if (before ? diff >= interval : diff < interval) yield = OK; @@ -3057,13 +3057,13 @@ if (dbd) /* an existing record */ { HDEBUG(D_acl) debug_printf_indent("seen db not written (readonly)\n"); } else if (mode == SEEN_WRITE || !before) { - dbd->time_stamp = now; + dbd->gen.time_stamp = now; dbfn_write(dbm, key, dbd, sizeof(*dbd)); HDEBUG(D_acl) debug_printf_indent("seen db written (update)\n"); } else if (diff >= refresh) { - dbd->time_stamp = now - interval; + dbd->gen.time_stamp = now - interval; dbfn_write(dbm, key, dbd, sizeof(*dbd)); HDEBUG(D_acl) debug_printf_indent("seen db written (refresh)\n"); } @@ -3072,7 +3072,7 @@ else { /* No record found, yield always FAIL */ if (mode != SEEN_READONLY) { - dbdata_seen d = {.time_stamp = now}; + dbdata_seen d = {.gen = {.time_stamp = now}}; dbfn_write(dbm, key, &d, sizeof(*dbd)); HDEBUG(D_acl) debug_printf_indent("seen db written (create)\n"); } diff --git a/src/src/dbfn.c b/src/src/dbfn.c index 3ed78901b..0a05524e1 100644 --- a/src/src/dbfn.c +++ b/src/src/dbfn.c @@ -410,6 +410,7 @@ void * yield; EXIM_DATUM key_datum, result_datum; uschar * key_copy = store_get(klen, key); unsigned dlen; +BOOL tainted; memcpy(key_copy, key, klen); @@ -426,13 +427,17 @@ if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) return NULL; } -/* Assume the data store could have been tainted. Properly, we should -store the taint status with the data. */ - dlen = exim_datum_size_get(&result_datum); DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: size %u return\n", dlen); -yield = store_get(dlen+1, hintsdb ? GET_TAINTED : GET_UNTAINTED); +/* Hintsdb uses store the taint of the payload of the value in the value. +For non-hints uses we have no provenance, so assume untainted */ + +tainted = hintsdb + ? ((dbdata_generic *) exim_datum_data_get(&result_datum))->tainted + : FALSE; + +yield = store_get(dlen+1, tainted ? GET_TAINTED : GET_UNTAINTED); memcpy(yield, exim_datum_data_get(&result_datum), dlen); ((uschar *)yield)[dlen] = '\0'; if (length) *length = dlen; @@ -498,6 +503,8 @@ return NULL; *************************************************/ /* +All callers are writing a hintsdb record. + Arguments: dbblock a pointer to an open database block key the key of the record to be written @@ -509,7 +516,7 @@ Returns: the yield of the underlying dbm or db "write" function. If this */ int -dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length) +dbfn_write(open_db * dbblock, const uschar * key, void * ptr, int length) { EXIM_DATUM key_datum, value_datum; dbdata_generic *gptr = (dbdata_generic *)ptr; @@ -518,6 +525,7 @@ uschar * key_copy = store_get(klen, key); memcpy(key_copy, key, klen); gptr->time_stamp = time(NULL); +gptr->tainted = is_tainted(ptr); DEBUG(D_hints_lookup) debug_printf_indent("dbfn_write: key=%s datalen %d\n", key, length); diff --git a/src/src/deliver.c b/src/src/deliver.c index c978e67b5..cf3a41156 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -2932,7 +2932,7 @@ while (addr_local) DEBUG(D_retry) { debug_printf("retry record exists: age=%s ", - readconf_printtime(now - retry_record->time_stamp)); + readconf_printtime(now - retry_record->gen.time_stamp)); debug_printf("(max %s)\n", readconf_printtime(retry_data_expire)); debug_printf(" time to retry = %s expired = %d\n", readconf_printtime(retry_record->next_try - now), @@ -2941,7 +2941,7 @@ while (addr_local) if (f.queue_running && !f.deliver_force) { - ok = (now - retry_record->time_stamp > retry_data_expire) + ok = (now - retry_record->gen.time_stamp > retry_data_expire) || (now >= retry_record->next_try) || retry_record->expired; @@ -7827,7 +7827,7 @@ while (addr_new) /* Loop until all addresses dealt with */ { domain_retry_record = dbfn_read(dbm_file, addr->domain_retry_key); if ( domain_retry_record - && now - domain_retry_record->time_stamp > retry_data_expire + && now - domain_retry_record->gen.time_stamp > retry_data_expire ) { DEBUG(D_deliver|D_retry) @@ -7837,7 +7837,7 @@ while (addr_new) /* Loop until all addresses dealt with */ address_retry_record = dbfn_read(dbm_file, addr->address_retry_key); if ( address_retry_record - && now - address_retry_record->time_stamp > retry_data_expire + && now - address_retry_record->gen.time_stamp > retry_data_expire ) { DEBUG(D_deliver|D_retry) @@ -7851,7 +7851,7 @@ while (addr_new) /* Loop until all addresses dealt with */ addr->address_retry_key, sender_address); address_retry_record = dbfn_read(dbm_file, altkey); if ( address_retry_record - && now - address_retry_record->time_stamp > retry_data_expire) + && now - address_retry_record->gen.time_stamp > retry_data_expire) { DEBUG(D_deliver|D_retry) debug_printf_indent("address retry record present but expired\n"); diff --git a/src/src/enq.c b/src/src/enq.c index 8129b3b2b..372607daf 100644 --- a/src/src/enq.c +++ b/src/src/enq.c @@ -53,7 +53,7 @@ if (!(dbm_file = dbfn_open(US"misc", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE))) proceed with the connection unless the record is very old. */ serial_record = dbfn_read_enforce_length(dbm_file, key, sizeof(dbdata_serialize)); -if (serial_record && time(NULL) - serial_record->time_stamp < 6*60*60) +if (serial_record && time(NULL) - serial_record->gen.time_stamp < 6*60*60) { if (serial_record->count >= lim) { diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c index 26dca7dbd..2c03be1c5 100644 --- a/src/src/exim_dbutil.c +++ b/src/src/exim_dbutil.c @@ -380,10 +380,7 @@ if (exim_lockfile_needed()) exclusive access to the database, so we can go ahead and open it. */ } -if ((dbmdb - ? asprintf(CSS &filename, "%s", name) - : asprintf(CSS &filename, "%s/%s", dname, name) - ) < 0) return NULL; +if (asprintf(CSS &filename, "%s", name) < 0) return NULL; if (!(dbblock->dbptr = dbblock->readonly && !exim_lockfile_needed() ? exim_dbopen_multi(filename, dname, flags, 0) @@ -461,6 +458,7 @@ EXIM_DATUM key_datum, result_datum; int klen = Ustrlen(key) + 1; uschar * key_copy = store_get(klen, key); unsigned dlen; +BOOL tainted; memcpy(key_copy, key, klen); @@ -475,7 +473,15 @@ if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) return NULL; we should store the taint status along with the data. */ dlen = exim_datum_size_get(&result_datum); -yield = store_get(dlen, GET_TAINTED); + +/* Hintsdb uses store the taint of the payload of the value in the value. +For non-hints uses we have no provenance, so assume untainted */ + +tainted = dbmdb + ? FALSE + : ((dbdata_generic *) exim_datum_data_get(&result_datum))->tainted; + +yield = store_get(dlen, tainted ? GET_TAINTED : GET_UNTAINTED); memcpy(yield, exim_datum_data_get(&result_datum), dlen); DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: size %u return\n", dlen); if (length) *length = dlen; @@ -733,7 +739,33 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor); int dlen = length = sizeof(dbdata_generic); printf("%s %s\n", print_time(recp->time_stamp), keybuffer); - printf("%s\n", string_sprintf("%.*q\n", dlen, dp)); + if ( Ustrncmp(keybuffer, "etrn-", 5) == 0 + || Ustrncmp(keybuffer, "tpt-serialize-", 14) == 0 + || Ustrncmp(keybuffer, "host-serialize-", 15) == 0 + ) + printf("serialize %.*s: %d running\n", + (int)(Ustrchr(keybuffer, '-') - keybuffer), keybuffer, + ((dbdata_serialize *)recp)->count); + else + { + uschar * s = keybuffer + Ustrlen(keybuffer) - 5; + if (s > keybuffer && Ustrcmp(s, ".EHLO") == 0) + { + ehlo_resp_precis * erp = &((dbdata_ehlo_resp *)recp)->data; + + printf("helo/ehlo %.*s clr %04x/%04x cry %04x/%04x", + (int)(s - keybuffer), keybuffer, + erp->cleartext_features, erp->cleartext_auths, + erp->crypted_features, erp->crypted_auths); + + if (sizeof(*erp) > 4 * sizeof(unsigned short)) + printf(" lim %05d/%05d/%05d", + erp->limit_mail, erp->limit_rcpt, erp->limit_rcptdom); + putchar('\n'); + } + else + printf("%s\n", string_sprintf("%.*q\n", dlen, dp)); + } break; } @@ -745,7 +777,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor); if (length == sizeof(dbdata_callout_cache_address)) { printf("%s %s callout=%s\n", - print_time(((dbdata_generic *)value)->time_stamp), + print_time(callout->gen.time_stamp), keybuffer, print_cache(callout->result)); } @@ -755,7 +787,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor); else if (length == sizeof(dbdata_callout_cache)) { printf("%s %s callout=%s postmaster=%s", - print_time(((dbdata_generic *)value)->time_stamp), + print_time(callout->gen.time_stamp), keybuffer, print_cache(callout->result), print_cache(callout->postmaster_result)); @@ -775,7 +807,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor); ratelimit = (dbdata_ratelimit *)value; rate_unique = (dbdata_ratelimit_unique *)value; printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n", - print_time(ratelimit->time_stamp), + print_time(ratelimit->gen.time_stamp), ratelimit->time_usec, ratelimit->rate, print_time(rate_unique->bloom_epoch), rate_unique->bloom_size, keybuffer); @@ -784,7 +816,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor); { ratelimit = (dbdata_ratelimit *)value; printf("%s.%06d rate: %10.3f key: %s\n", - print_time(ratelimit->time_stamp), + print_time(ratelimit->gen.time_stamp), ratelimit->time_usec, ratelimit->rate, keybuffer); } @@ -797,7 +829,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor); case type_seen: seen = (dbdata_seen *)value; - printf("%s\t%s\n", keybuffer, print_time(seen->time_stamp)); + printf("%s\t%s\n", keybuffer, print_time(seen->gen.time_stamp)); break; case type_dbm: @@ -1014,8 +1046,10 @@ for(; (reset_point = store_mark()); store_reset(reset_point)) ratelimit = (dbdata_ratelimit *)record; switch(fieldno) { - case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt; - else printf("bad time value\n"); + case 0: if ((tt = read_time(value)) > 0) + ratelimit->gen.time_stamp = tt; + else + printf("bad time value\n"); break; case 1: ratelimit->time_usec = Uatoi(value); break; @@ -1079,7 +1113,11 @@ for(; (reset_point = store_mark()); store_reset(reset_point)) } if (dbdata_type != type_dbm) - ((dbdata_generic *)record)->time_stamp = time(NULL); + { + dbdata_generic * p = (dbdata_generic *)record; + p->time_stamp = time(NULL); + p->tainted = FALSE; + } dbfn_write(dbm, name, record, newlength); } @@ -1179,7 +1217,7 @@ for(; (reset_point = store_mark()); store_reset(reset_point)) case type_ratelimit: ratelimit = (dbdata_ratelimit *)record; - printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp)); + printf("0 time stamp: %s\n", print_time(ratelimit->gen.time_stamp)); printf("1 fract. time: .%06d\n", ratelimit->time_usec); printf("2 sender rate: % .3f\n", ratelimit->rate); if (Ustrstr(name, "/unique/") != NULL @@ -1193,8 +1231,8 @@ for(; (reset_point = store_mark()); store_reset(reset_point)) break; case type_tls: - session = (dbdata_tls_session *)value; - printf("0 time stamp: %s\n", print_time(session->time_stamp)); + session = (dbdata_tls_session *)record; + printf("0 time stamp: %s\n", print_time(session->gen.time_stamp)); printf("1 session: .%s\n", session->session); break; @@ -1360,10 +1398,10 @@ for (; keychain && (reset_point = store_mark()); store_reset(reset_point)) /* Leave corrupt records alone */ - if (wait->time_stamp > time(NULL)) + if (wait->gen.time_stamp > time(NULL)) { printf("**** Data for '%s' corrupted\n time in future: %s\n", - key, print_time(((dbdata_generic *)value)->time_stamp)); + key, print_time(wait->gen.time_stamp)); continue; } if (wait->count > WAIT_NAME_MAX) @@ -1381,7 +1419,7 @@ for (; keychain && (reset_point = store_mark()); store_reset(reset_point)) /* Record over 1 year old; just remove it */ - if (wait->time_stamp < time(NULL) - 365*24*60*60) + if (wait->gen.time_stamp < time(NULL) - 365*24*60*60) { dbfn_delete(dbm, key); printf("deleted %s (too old)\n", key); @@ -1466,7 +1504,8 @@ for (; keychain && (reset_point = store_mark()); store_reset(reset_point)) if (update) { printf("updated %s\n", key); - wait->time_stamp = time(NULL); + wait->gen.time_stamp = time(NULL); + /* leave the taint marker unchanged */ dbfn_write(dbm, key, wait, sizeof(dbdata_wait) + wait->count * MESSAGE_ID_LENGTH); } diff --git a/src/src/hintsdb_structs.h b/src/src/hintsdb_structs.h index c77190f38..f4d72fdc9 100644 --- a/src/src/hintsdb_structs.h +++ b/src/src/hintsdb_structs.h @@ -32,7 +32,8 @@ DBM databases are used for hints files. typedef struct { - time_t time_stamp; /* Timestamp of writing */ + time_t time_stamp; /* Timestamp of writing */ + BOOL tainted; /* metadata for the "value" part of the record */ } dbdata_generic; @@ -40,7 +41,7 @@ typedef struct { address. */ typedef struct { - time_t time_stamp; + dbdata_generic gen; /*************/ time_t first_failed; /* Time of first failure */ time_t last_try; /* Time of last try */ @@ -72,7 +73,7 @@ handle the old type of record, we retain the old definition. The different kinds of record can be distinguished by their different lengths. */ typedef struct { - time_t time_stamp; + dbdata_generic gen; /*************/ int result; int postmaster_result; /* Postmaster is accepted */ @@ -80,7 +81,7 @@ typedef struct { } dbdata_callout_cache_obs; typedef struct { - time_t time_stamp; /* Timestamp of last address check */ + dbdata_generic gen; /* Timestamp of last address check */ /*************/ int result; /* accept or reject */ } dbdata_callout_cache_address; @@ -90,7 +91,7 @@ last so that if somebody reverts to an older Exim, the new records will still make sense because they match the old layout. */ typedef struct { - time_t time_stamp; /* Time stamp of last connection */ + dbdata_generic gen; /* Time stamp of last connection */ /*************/ int result; /* Domain reject or accept */ int postmaster_result; /* Postmaster result */ @@ -103,7 +104,7 @@ typedef struct { host for a particular transport. */ typedef struct { - time_t time_stamp; + dbdata_generic gen; /*************/ int count; /* Count of message ids */ int sequence; /* Sequence for continued records */ @@ -123,7 +124,7 @@ at present in use. The same structure is used for recording a running ETRN process. */ typedef struct { - time_t time_stamp; + dbdata_generic gen; /*************/ int count; /* Reserved for possible connection count */ } dbdata_serialize; @@ -133,7 +134,7 @@ typedef struct { ACL condition. */ typedef struct { - time_t time_stamp; + dbdata_generic gen; /*************/ int time_usec; /* Fractional part of time, from gettimeofday() */ double rate; /* Smoothed sending rate at that time */ @@ -151,7 +152,7 @@ typedef struct { /* For "seen" ACL condition */ typedef struct { - time_t time_stamp; + dbdata_generic gen; } dbdata_seen; #ifndef DISABLE_PIPE_CONNECT @@ -174,14 +175,14 @@ typedef struct { } ehlo_resp_precis; typedef struct { - time_t time_stamp; + dbdata_generic gen; /*************/ ehlo_resp_precis data; } dbdata_ehlo_resp; #endif typedef struct { - time_t time_stamp; + dbdata_generic gen; /*************/ uschar verify_override:1; uschar ocsp:3; diff --git a/src/src/retry.c b/src/src/retry.c index 24b2fa66a..515c94288 100644 --- a/src/src/retry.c +++ b/src/src/retry.c @@ -227,7 +227,7 @@ if (!host_retry_record) { DEBUG(D_transport|D_retry) debug_printf_indent("no host retry record\n"); } -else if (now - host_retry_record->time_stamp > retry_data_expire) +else if (now - host_retry_record->gen.time_stamp > retry_data_expire) { host_retry_record = NULL; DEBUG(D_transport|D_retry) debug_printf_indent("host retry record too old\n"); @@ -237,7 +237,7 @@ if (!message_retry_record) { DEBUG(D_transport|D_retry) debug_printf_indent("no message retry record\n"); } -else if (now - message_retry_record->time_stamp > retry_data_expire) +else if (now - message_retry_record->gen.time_stamp > retry_data_expire) { message_retry_record = NULL; DEBUG(D_transport|D_retry) @@ -744,7 +744,7 @@ for (int i = 0; i < 3; i++) retry_record = dbfn_read_with_length(dbm_file, rti->key, &message_space); if ( retry_record - && now - retry_record->time_stamp > retry_data_expire) + && now - retry_record->gen.time_stamp > retry_data_expire) retry_record = NULL; if (!retry_record) diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 3ffb6fe8b..abc893ddc 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -5748,7 +5748,8 @@ while (done <= 0) /* 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 */ + operations. So, use a hash of the string. All ETRN with the same command + hash are serialized */ md5 hash; uschar *digest = store_get(16, GET_TAINTED); @@ -5756,11 +5757,7 @@ while (done <= 0) 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]); + etrn_serialize_key = string_sprintf("etrn-%.16H", digest); /* 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 diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 40acee7a3..9d3f5af73 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -4035,7 +4035,7 @@ if (tlsp->host_resumable) #else /* Use, fairly arbitrilarily, what we as server would */ f.running_in_test_harness ? TESTSUITE_TICKET_LIFE : ssl_session_timeout; #endif - time_t now = time(NULL), expires = lifetime + dt->time_stamp; + time_t now = time(NULL), expires = lifetime + dt->gen.time_stamp; if (expires < now) { DEBUG(D_tls) debug_printf("session expired (by " TIME_T_FMT "s from %lus)\n", now - expires, lifetime); diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 8ea3d882a..80a70b490 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -992,7 +992,7 @@ if ( sx->early_pipe_active debug_printf("no ehlo-resp record!\n"); else debug_printf("ehlo-resp record is %.0f seconds old\n", - difftime(time(NULL), er->time_stamp)); + difftime(time(NULL), er->gen.time_stamp)); } dbfn_delete(dbm_file, ehlo_resp_key); @@ -1018,7 +1018,7 @@ else dbfn_close(dbm_file); DEBUG(D_transport) debug_printf("no ehlo-resp record\n"); } - else if (time(NULL) - er->time_stamp > retry_data_expire) + else if (time(NULL) - er->gen.time_stamp > retry_data_expire) { DEBUG(D_transport) debug_printf("ehlo-resp record too old\n"); dbfn_close(dbm_file); diff --git a/src/src/verify.c b/src/src/verify.c index 6696ec4d9..be9ea7fe4 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -63,7 +63,7 @@ negative = cache_record->result != ccache_accept || expire = negative? negative_expire : positive_expire; now = time(NULL); -if (now - cache_record->time_stamp > expire) +if (now - cache_record->gen.time_stamp > expire) { HDEBUG(D_verify) debug_printf_indent("callout cache: %s record expired for %s\n", type, key); return NULL; @@ -81,7 +81,7 @@ if (type[0] == 'd' && cache_record->result != ccache_reject) { dbdata_callout_cache * new = store_get(sizeof(dbdata_callout_cache), GET_UNTAINTED); memcpy(new, cache_record, length); - new->postmaster_stamp = new->random_stamp = new->time_stamp; + new->postmaster_stamp = new->random_stamp = new->gen.time_stamp; cache_record = new; } commit de32296674ed4e57cb5a8efa3bb3f2c838d03d1a Author: Jeremy Harris Date: Sun Nov 30 10:19:10 2025 +0000 add version checking and grooming for hintsdb records diff --git a/src/src/dbfn.c b/src/src/dbfn.c index 0a05524e1..64807a7b8 100644 --- a/src/src/dbfn.c +++ b/src/src/dbfn.c @@ -17,25 +17,29 @@ with database files like $spooldirectory/db/ */ /* Functions for accessing Exim's hints database, which consists of a number of -different DBM files. This module does not contain code for reading DBM files -for (e.g.) alias expansion. That is all contained within the general search -functions. As Exim now has support for several DBM interfaces, all the relevant +different DBM files. Also used for the readonly access for dbm lookup +expansions via the general search functions, for non-hints DBM file. + +As Exim now has support for several DBM interfaces, all the relevant functions are called as inlinable functions from an included file. -All the data in Exim's database is in the nature of *hints*. Therefore it +All the data in Exim's hists database is in the nature of *hints*. Therefore it doesn't matter if it gets destroyed by accident. These functions are not supposed to implement a "safe" database. -Keys are passed in as C strings, and the terminating zero *is* used when -building the dbm files. This just makes life easier when scanning the files +For hints, keys are passed in as C strings - and the terminating zero *is* used +when building the dbm files. This just makes life easier when scanning the files sequentially. -Synchronization is required on the database files, and this is achieved by -means of locking on independent lock files. (Earlier attempts to lock on the -DBM files themselves were never completely successful.) Since callers may in -general want to do more than one read or write while holding the lock, there -are separate open and close functions. However, the calling modules should -arrange to hold the locks for the bare minimum of time. +For many of the DBM interfaces, synchronization is required on the database +files; and this is achieved by means of locking on independent lock files. +(Earlier attempts to lock on the DBM files themselves were never completely +successful.) Since callers may in general want to do more than one read or write +while holding the lock, there are separate open and close functions. +However, the calling modules should arrange to hold the locks for the bare +minimum of time. +A predicate call is provided to tell if this locking is required, as opposed +to being built-in to the DBM. API: exim_lockfile_needed facilities predicate @@ -62,6 +66,13 @@ Users: peer capability cache callout & quota cache DBM lookup type + + +NOTE: the autoreply transport accesses a DBM database using +exim_db{open,close,get,put} directly, not using this layer. + +The DBM interface is selected at build time from one of the +files src/hintsdb/hints_*.h */ @@ -430,12 +441,30 @@ if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) dlen = exim_datum_size_get(&result_datum); DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: size %u return\n", dlen); -/* Hintsdb uses store the taint of the payload of the value in the value. -For non-hints uses we have no provenance, so assume untainted */ +if (hintsdb) + { + dbdata_generic * gp = (dbdata_generic *) exim_datum_data_get(&result_datum); -tainted = hintsdb - ? ((dbdata_generic *) exim_datum_data_get(&result_datum))->tainted - : FALSE; + if (dlen < sizeof(dbdata_generic)) + { + DEBUG(D_hints_lookup) + debug_printf_indent("dbfn_read: bad record size %u\n", dlen); + return NULL; + } + if (gp->version != HINTS_VERSION) + { + DEBUG(D_hints_lookup) + debug_printf_indent("dbfn_read: bad record version %u\n", gp->version); + return NULL; + } + + /* Hintsdb uses store the taint of the payload of the value in the value. + For non-hints uses we have no provenance, so assume untainted */ + + tainted = gp->tainted; + } +else + tainted = FALSE; yield = store_get(dlen+1, tainted ? GET_TAINTED : GET_UNTAINTED); memcpy(yield, exim_datum_data_get(&result_datum), dlen); @@ -519,13 +548,14 @@ int dbfn_write(open_db * dbblock, const uschar * key, void * ptr, int length) { EXIM_DATUM key_datum, value_datum; -dbdata_generic *gptr = (dbdata_generic *)ptr; +dbdata_generic * gptr = (dbdata_generic *)ptr; int klen = Ustrlen(key) + 1; uschar * key_copy = store_get(klen, key); memcpy(key_copy, key, klen); -gptr->time_stamp = time(NULL); +gptr->version = HINTS_VERSION; gptr->tainted = is_tainted(ptr); +gptr->time_stamp = time(NULL); DEBUG(D_hints_lookup) debug_printf_indent("dbfn_write: key=%s datalen %d\n", key, length); diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c index 2c03be1c5..f3842874f 100644 --- a/src/src/exim_dbutil.c +++ b/src/src/exim_dbutil.c @@ -380,7 +380,8 @@ if (exim_lockfile_needed()) exclusive access to the database, so we can go ahead and open it. */ } -if (asprintf(CSS &filename, "%s", name) < 0) return NULL; +/* Drop the ".lockfile" off the name */ +*Ustrrchr(filename, '.') = '\0'; if (!(dbblock->dbptr = dbblock->readonly && !exim_lockfile_needed() ? exim_dbopen_multi(filename, dname, flags, 0) @@ -447,7 +448,7 @@ Arguments: length where to put the length (or NULL if length not wanted). Includes overhead. Returns: a pointer to the retrieved record, or - NULL if the record is not found + NULL if the record is not found or bad */ void * @@ -467,26 +468,42 @@ exim_datum_init(&result_datum); /* to be cleared before use. */ exim_datum_data_set(&key_datum, key_copy); exim_datum_size_set(&key_datum, klen); -if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) return NULL; - -/* Assume for now that anything stored could have been tainted. Properly -we should store the taint status along with the data. */ +if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) + return NULL; dlen = exim_datum_size_get(&result_datum); +DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: size %u return\n", dlen); +if (length) *length = dlen; /* Hintsdb uses store the taint of the payload of the value in the value. For non-hints uses we have no provenance, so assume untainted */ -tainted = dbmdb - ? FALSE - : ((dbdata_generic *) exim_datum_data_get(&result_datum))->tainted; +if (dbmdb) + tainted = FALSE; +else + { + dbdata_generic * gp = (dbdata_generic *) exim_datum_data_get(&result_datum); + if (dlen < sizeof(dbdata_generic)) + { + DEBUG(D_hints_lookup) + debug_printf_indent("dbfn_read: bad record size %u\n", dlen); + return NULL; + } + if (gp->version != HINTS_VERSION) + { + DEBUG(D_hints_lookup) + debug_printf_indent("dbfn_read: bad record version %u; deleting\n", + gp->version); + return NULL; + } + + tainted = gp->tainted; + } yield = store_get(dlen, tainted ? GET_TAINTED : GET_UNTAINTED); memcpy(yield, exim_datum_data_get(&result_datum), dlen); -DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: size %u return\n", dlen); -if (length) *length = dlen; +exim_datum_free(&result_datum); /* Some DBM libs require freeing */ -exim_datum_free(&result_datum); /* Some DBM libs require freeing */ return yield; } @@ -726,13 +743,10 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor); printf("%s ", name); t += MESSAGE_ID_LENGTH; } - printf("\n"); + putchar('\n'); break; case type_misc: - /* It might be nice to recognize the subtypes of "misc" DBs (by the - key prefix?) for better decode. For now, dump the raw data. */ - { dbdata_generic * recp = (dbdata_generic *)value; uschar * dp = US (recp + 1); @@ -1368,16 +1382,26 @@ the store each time round. */ for (; keychain && (reset_point = store_mark()); store_reset(reset_point)) { - dbdata_generic *value; + dbdata_generic * value; + int size = 0; key = keychain->key; keychain = keychain->next; - value = dbfn_read_with_length(dbm, key, NULL); + value = dbfn_read_with_length(dbm, key, &size); /* A continuation record may have been deleted or renamed already, so - non-existence is not serious. */ + non-existence is not serious. Also, for a back-version record we get + a null value - so attempt a delete. */ - if (!value) continue; + if (!value) + { + if (size) + { + printf(" deleted %s (bad record version)\n", key); + dbfn_delete(dbm, key); + } + continue; + } /* Delete if too old */ diff --git a/src/src/hintsdb_structs.h b/src/src/hintsdb_structs.h index f4d72fdc9..fdca1f468 100644 --- a/src/src/hintsdb_structs.h +++ b/src/src/hintsdb_structs.h @@ -25,17 +25,20 @@ typedef struct { } open_db; -/* Structures for records stored in exim database hinsts files. They all +/* Structures for records stored in exim database hints files. They all start with the same fields, described in the generic type. DBM databases are used for hints files. */ typedef struct { - time_t time_stamp; /* Timestamp of writing */ + unsigned version; BOOL tainted; /* metadata for the "value" part of the record */ + time_t time_stamp; /* Timestamp of writing */ } dbdata_generic; +#define HINTS_VERSION 2 + /* This structure keeps track of retry information for a host or a local address. */ commit 7873db47ac111972cc46b42e741fd437ec7a9869 Author: Jeremy Harris Date: Thu Dec 18 11:03:22 2025 +0000 Add notes on alternates to prototype Makefile diff --git a/src/src/EDITME b/src/src/EDITME index 8977f624b..0edeedaa2 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -654,8 +654,8 @@ DISABLE_MAL_MKS=yes # Uncomment the following to remove the fast-ramp two-phase-queue-run support # DISABLE_QUEUE_RAMP=yes -# Uncomment the following lines to add SRS (Sender Rewriting Scheme) support -# using only native facilities. +# Uncomment the following lines to add SRS (Sender Rewriting Scheme) support. +# This needs no library. # SUPPORT_SRS=yes # Uncomment the following to remove support for the ESMTP extension "WELLKNOWN" @@ -693,6 +693,9 @@ DISABLE_MAL_MKS=yes # 1.3.2-3 works. It seems that the OpenDMARC project broke their API. # Use this option if you need to build with an old library (1.3.x) # DMARC_API=100300 +# +# As an alternative, see EXPERIMENTAL_DMARC_NATIVE +# in doc-txt/experimental-spec.txt # Uncomment the following line to add ARC (Authenticated Received Chain) # support. You must have SPF and DKIM support enabled also. @@ -1177,6 +1180,8 @@ ZCAT_COMMAND=/usr/bin/zcat # LDFLAGS += -lspf2 # SUPPORT_SPF_LIBS= -lspf2 +# As an alternative, see EXPERIMENTAL_SPF_PERL +# in doc-txt/experimental-spec.txt #------------------------------------------------------------------------------ # Support for authentication via Radius is also available. The Exim support, commit dbb8fa7d6275bb2a21d9a7803b3ab902a2e15301 Author: Jeremy Harris Date: Thu Dec 18 13:30:02 2025 +0000 Utils; fix exim_dumpdb build for non- pipe-connect diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c index f3842874f..3ef241a4e 100644 --- a/src/src/exim_dbutil.c +++ b/src/src/exim_dbutil.c @@ -763,6 +763,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor); else { uschar * s = keybuffer + Ustrlen(keybuffer) - 5; +#ifndef DISABLE_PIPE_CONNECT if (s > keybuffer && Ustrcmp(s, ".EHLO") == 0) { ehlo_resp_precis * erp = &((dbdata_ehlo_resp *)recp)->data; @@ -778,6 +779,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor); putchar('\n'); } else +#endif printf("%s\n", string_sprintf("%.*q\n", dlen, dp)); } break; @@ -847,7 +849,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor); break; case type_dbm: - printf("%s\t%.*s\n", keybuffer, length, value); + printf("%s\t%.*s\n", keybuffer, length, CS value); } store_reset(reset_point); } commit 0b98b3d655993f08104b9caf71be382cee57bd79 Author: Jeremy Harris Date: Thu Dec 18 13:35:42 2025 +0000 Avoid function name conflict with Solaris libgen diff --git a/src/src/acl.c b/src/src/acl.c index 7ec3a898c..b236eb2de 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -4095,7 +4095,7 @@ for (; cb; cb = cb->next) #ifdef WITH_CONTENT_SCAN case ACLC_REGEX: - rc = regex(&arg, textonly); + rc = exim_regex(&arg, textonly); break; #endif diff --git a/src/src/functions.h b/src/src/functions.h index bfe34f391..1923ca5b6 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -446,7 +446,7 @@ extern int_eximarith_t receive_statvfs(BOOL, int *); extern void receive_swallow_smtp(void); extern int recv_fd_from_sock(int); #ifdef WITH_CONTENT_SCAN -extern int regex(const uschar **, BOOL); +extern int exim_regex(const uschar **, BOOL); extern void regex_vars_clear(void); #endif extern void regex_at_daemon(const uschar *); diff --git a/src/src/regex.c b/src/src/regex.c index ff52d349c..43f7a9403 100644 --- a/src/src/regex.c +++ b/src/src/regex.c @@ -113,7 +113,7 @@ for (int i = 0; i < REGEX_VARS; i++) regex_vars[i] = NULL; int -regex(const uschar ** listptr, BOOL cacheable) +exim_regex(const uschar ** listptr, BOOL cacheable) { unsigned long mbox_size; FILE * mbox_file; commit 7b81731f51c53643c30d330596779a1c2f7eeb73 Author: Jeremy Harris Date: Thu Dec 18 16:04:24 2025 +0000 Build: Solaris diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index 9524dcef9..d0c00e431 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -601,7 +601,7 @@ exim_dumpdb: $(OBJ_DUMPDB) # The utility for interrogating/fixing the contents of an exim database -OBJ_FIXDB = exim_fixdb.o util-os.o util-store.o util-md5.o +OBJ_FIXDB = exim_fixdb.o util-os.o util-store.o util-string.o util-md5.o exim_fixdb: $(OBJ_FIXDB) @echo "$(LNCC) -o exim_fixdb" commit fc6c642e1bfecab156b65c992697dbf1b3002b8b Author: Jeremy Harris Date: Thu Dec 18 19:13:06 2025 +0000 Build: Solaris diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index d0c00e431..699f485c1 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -616,7 +616,7 @@ exim_fixdb: $(OBJ_FIXDB) # The utility for tidying the contents of an exim database -OBJ_TIDYDB = exim_tidydb.o util-os.o util-store.o +OBJ_TIDYDB = exim_tidydb.o util-os.o util-store.o util-string.o exim_tidydb: $(OBJ_TIDYDB) @echo "$(LNCC) -o exim_tidydb" commit e46d2bb2bd33044b45cd9cc508a50f63d91aa0f8 Author: Jeremy Harris Date: Fri Dec 19 10:29:58 2025 +0000 Utils: eliminate use of asprintf() diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c index 3ef241a4e..6ffcf764c 100644 --- a/src/src/exim_dbutil.c +++ b/src/src/exim_dbutil.c @@ -329,15 +329,16 @@ for the DB access mode will error out here. */ if (dbmdb) { - if (asprintf(CSS &filename, "%s.lockfile", name) < 0) - return NULL; + filename = string_sprintf("%s.lockfile", name); dname = Ustrchr(name, '/') /* path has dirs */ ? US dirname(CS string_copy(name)) : US "."; } -else if ( asprintf(CSS &dname, "%s/db", spool_directory) < 0 - || asprintf(CSS &filename, "%s/%s.lockfile", dname, name) < 0) - return NULL; +else + { + dname = string_sprintf("%s/db", spool_directory); + filename = string_sprintf("%s/%s.lockfile", dname, name); + } dbblock->readonly = (flags & O_ACCMODE) == O_RDONLY; dbblock->lockfd = -1; commit cb28da1d541b29ce86ea862f00f6c278117cbcb9 Author: Jeremy Harris Date: Fri Dec 19 20:27:36 2025 +0000 Tidying diff --git a/src/src/exim.c b/src/src/exim.c index e955d2488..d8851d782 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -85,7 +85,11 @@ function_store_nullfree(void * block, void * tag) *************************************************/ enum commandline_info { CMDINFO_NONE=0, - CMDINFO_HELP, CMDINFO_SIEVE, CMDINFO_DSCP }; + CMDINFO_HELP, CMDINFO_SIEVE, +#ifdef SUPPORT_DSCP + CMDINFO_DSCP +#endif +}; diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c index 6ffcf764c..b6977513b 100644 --- a/src/src/exim_dbutil.c +++ b/src/src/exim_dbutil.c @@ -1254,7 +1254,7 @@ for(; (reset_point = store_mark()); store_reset(reset_point)) break; case type_dbm: - printf("0 value: %.*s\n", oldlength, record); + printf("0 value: %.*s\n", oldlength, CS record); } } diff --git a/src/src/miscmods/dmarc_common.c b/src/src/miscmods/dmarc_common.c index 35b832c59..ab0027f17 100644 --- a/src/src/miscmods/dmarc_common.c +++ b/src/src/miscmods/dmarc_common.c @@ -15,7 +15,7 @@ extern BOOL dmarc_local_init(void); extern void dmarc_local_msg_init(void); extern gstring * dmarc_version_report(gstring *); extern int dmarc_process(void); -extern const uschar * dmarc_result_inlist(const uschar * const *); +extern int dmarc_result_inlist(const uschar * const *); /* Other modules needed for services */ diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 80a70b490..7274050f1 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -1126,8 +1126,6 @@ sx->pending_EHLO = FALSE; if (pending_BANNER) { - const uschar * s; - DEBUG(D_transport) debug_printf("%s expect banner\n", __FUNCTION__); (*countp)--; if (!smtp_reap_banner(sx)) @@ -1140,9 +1138,12 @@ if (pending_BANNER) # if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME) GET_OPTION("host_name_extract"); - s = ((smtp_transport_options_block *)sx->conn_args.ob)->host_name_extract; - if (!s) s = HNE_DEFAULT; - ehlo_response_lbserver(sx, s); + { + const uschar * s; + s = ((smtp_transport_options_block *)sx->conn_args.ob)->host_name_extract; + if (!s) s = HNE_DEFAULT; + ehlo_response_lbserver(sx, s); + } # endif } @@ -2677,8 +2678,6 @@ goto SEND_QUIT; #ifndef DISABLE_TLS if (sx->smtps) { - const uschar * s; - smtp_peer_options |= OPTION_TLS; suppress_tls = FALSE; ob->tls_tempfail_tryclear = FALSE; @@ -2690,8 +2689,11 @@ goto SEND_QUIT; force resumption attempts. */ GET_OPTION("host_name_extract"); - if (!(s = ob->host_name_extract)) s = US"never-LB"; - ehlo_response_lbserver(sx, s); + { + const uschar * s; + if (!(s = ob->host_name_extract)) s = US"never-LB"; + ehlo_response_lbserver(sx, s); + } # endif smtp_record_protocol_sequence(sx, US"s"); goto TLS_NEGOTIATE; @@ -2784,8 +2786,6 @@ goto SEND_QUIT; if (!sx->early_pipe_active) #endif { - const uschar * s; - sx->peer_offered = ehlo_response(sx->buffer, OPTION_TLS /* others checked later */ #ifndef DISABLE_PIPE_CONNECT @@ -2824,8 +2824,11 @@ goto SEND_QUIT; #endif #if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME) GET_OPTION("host_name_extract"); - if (!(s = ob->host_name_extract)) s = HNE_DEFAULT; - ehlo_response_lbserver(sx, s); + { + const uschar * s; + if (!(s = ob->host_name_extract)) s = HNE_DEFAULT; + ehlo_response_lbserver(sx, s); + } #endif } commit 8783c47fcae5a69aea91cb37da694070185569e5 Author: Andreas Metzler Date: Sun Dec 21 12:42:13 2025 +0000 typo, c'n'p errors diff --git a/src/src/EDITME b/src/src/EDITME index 0edeedaa2..21d81f43c 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -320,8 +320,8 @@ SPOOL_DIRECTORY=/var/spool/exim # is historic). # You need to add -export-dynamic -rdynamic to EXTRALIBS. You may also need to # add -ldl to EXTRALIBS so that dlopen() is available to Exim. You need to -# define CFLAGS_DYNAIC and LOOKUP_MODULE_DIR below so the builds are done right, -# and so the exim binary actually loads dynamic lookup modules. +# define CFLAGS_DYNAMIC and LOOKUP_MODULE_DIR below so the builds are done +# right, and so the exim binary actually loads dynamic lookup modules. ROUTER_ACCEPT=yes ROUTER_DNSLOOKUP=yes @@ -350,8 +350,8 @@ ROUTER_REDIRECT=yes # is historic). # You need to add -export-dynamic -rdynamic to EXTRALIBS. You may also need to # add -ldl to EXTRALIBS so that dlopen() is available to Exim. You need to -# define CFLAGS_DYNAIC and LOOKUP_MODULE_DIR below so the builds are done right, -# and so the exim binary actually loads dynamic lookup modules. +# define CFLAGS_DYNAMIC and LOOKUP_MODULE_DIR below so the builds are done +# right, and so the exim binary actually loads dynamic lookup modules. # The smtp transport cannot be built as a module. TRANSPORT_APPENDFILE=yes @@ -874,7 +874,7 @@ FIXED_NEVER_USERS=root # right and so the exim binary actually loads dynamic lookup modules. # # Libraries being built as modules should be added to respective -# LOOKUP_*_INCLUDE and LOOKUP_*_LIBS rather than the the ones for the +# AUTH_*_INCLUDE and AUTH_*_LIBS rather than the the ones for the # core exim build. This gets them linked with the module instead. # The heimdal does build but we have no test coverage so it is not know to work. commit 91586f969ddddc4ff4339167c05141141a4b6272 Author: Jeremy Harris Date: Sun Dec 21 15:54:59 2025 +0000 Cmdline option for list of dynamic-load modules diff --git a/src/src/drtables.c b/src/src/drtables.c index 833c352c0..1b9dc0c98 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -224,6 +224,7 @@ const pcre2_code * regex_islookupmod = regex_must_compile( if (!(dd = exim_opendir(CUS LOOKUP_MODULE_DIR))) g = string_cat(g, US"FAIL exim_opendir"); else + { for (struct dirent * ent; ent = readdir(dd); ) { void * dl; @@ -246,6 +247,8 @@ else dlclose(dl); } } + closedir(dd); + } #endif /*!LOOKUP_MODULE_DIR*/ return g; } @@ -574,5 +577,37 @@ onetime = TRUE; } +void +mod_names(FILE * stream) +{ +#ifdef LOOKUP_MODULE_DIR +DIR * dd; +gstring * list = NULL; +const pcre2_code * regex_ismodule = regex_must_compile( + US"^([a-z0-9]+)_(?:lookup|auth|router|transport|miscmod)\\." + DYNLIB_FN_EXT "$", + MCS_NOFLAGS, TRUE); + +if ((dd = exim_opendir(CUS LOOKUP_MODULE_DIR))) + { + for (struct dirent * ent; ent = readdir(dd); ) + if (regex_match_and_setup(regex_ismodule, US ent->d_name, 0, 0)) + list = string_append_listele(list, ' ', expand_nstring[1]); + closedir(dd); + + if (list) + fprintf(stream, "Installed modules: %s\n", string_from_gstring(list)); + else + fprintf(stream, "No modules are installed\n"); + } +else + fprintf(stream, "Modules directory not readable\n"); + +#else +fprintf(stream, "Loadable-module support is not available\n"); +#endif +} + + #endif /*!MACRO_PREDEF*/ /* End of drtables.c */ diff --git a/src/src/exim.c b/src/src/exim.c index d8851d782..055882ad0 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -85,7 +85,7 @@ function_store_nullfree(void * block, void * tag) *************************************************/ enum commandline_info { CMDINFO_NONE=0, - CMDINFO_HELP, CMDINFO_SIEVE, + CMDINFO_HELP, CMDINFO_SIEVE, CMDINFO_MODULES, #ifdef SUPPORT_DSCP CMDINFO_DSCP #endif @@ -1392,9 +1392,13 @@ switch(request) #ifdef SUPPORT_DSCP " exim -bI:dscp list of known dscp value keywords\n" #endif +" exim -bI:modules list of loadable modules\n" " exim -bI:sieve list of supported sieve extensions\n" ); return; + case CMDINFO_MODULES: + mod_names(stream); + return; case CMDINFO_SIEVE: { const misc_module_info * mi; @@ -2436,6 +2440,8 @@ on the second character (the one after '-'), to save some effort. */ if (Ustrlen(p)) if (strcmpic(p, CUS"sieve") == 0) { info_flag = CMDINFO_SIEVE; info_stdout = TRUE; } + else if (strcmpic(p, CUS"modules") == 0) + { info_flag = CMDINFO_MODULES; info_stdout = TRUE; } #ifdef SUPPORT_DSCP else if (strcmpic(p, CUS"dscp") == 0) { info_flag = CMDINFO_DSCP; info_stdout = TRUE; } diff --git a/src/src/functions.h b/src/src/functions.h index 1923ca5b6..fe9d718ea 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -376,6 +376,7 @@ extern void moan_write_references(FILE *, uschar *); #ifdef LOOKUP_MODULE_DIR //extern void mod_load_check(const uschar *); #endif +extern void mod_names(FILE *); extern FILE *modefopen(const uschar *, const char *, mode_t); extern int open_cutthrough_connection(address_item *, BOOL); commit 6d75a89d2f506f88cfddf092440af5b2d54f07df Author: Jeremy Harris Date: Sun Dec 21 18:01:26 2025 +0000 Invalidate more variables at smtp-reset diff --git a/src/src/deliver.c b/src/src/deliver.c index cf3a41156..a984ee21e 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -174,8 +174,10 @@ deliver_set_expansions(address_item * addr) { if (!addr) { - const uschar ***p = address_expansions; - while (*p) **p++ = NULL; + for (const uschar ***p = address_expansions; *p; ) **p++ = NULL; + deliver_host_port = 0; + deliver_recipients = NULL; + router_var = NULL; return; } diff --git a/src/src/globals.c b/src/src/globals.c index 2d1354236..d04feb24f 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -188,6 +188,8 @@ when verifying one address while routing/verifying another. We have to have the size explicit, because it is referenced from more than one module. */ const uschar **address_expansions[ADDRESS_EXPANSIONS_COUNT] = { + CUSS &address_file, + CUSS &address_pipe, CUSS &deliver_address_data, CUSS &deliver_domain, CUSS &deliver_domain_data, @@ -198,12 +200,13 @@ const uschar **address_expansions[ADDRESS_EXPANSIONS_COUNT] = { CUSS &deliver_localpart_orig, CUSS &deliver_localpart_parent, CUSS &deliver_localpart_prefix, + CUSS &deliver_localpart_prefix_v, CUSS &deliver_localpart_suffix, + CUSS &deliver_localpart_suffix_v, CUSS (uschar **)(&deliver_recipients), CUSS &deliver_host, + CUSS &deliver_host_address, CUSS &deliver_home, - CUSS &address_file, - CUSS &address_pipe, CUSS &self_hostname, NULL }; diff --git a/src/src/log.c b/src/src/log.c index 41f91a1ad..64587a9f2 100644 --- a/src/src/log.c +++ b/src/src/log.c @@ -1323,7 +1323,7 @@ va_end(ap); } /* As the above, but adds in LOG_PANIC_DIE. -We have this as a wripper so that we can mark it as never returning, +We have this as a wrapper so that we can mark it as never returning, for the benefit of static analysers. */ void diff --git a/src/src/macros.h b/src/src/macros.h index 6c7d8b085..0070ef7ed 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -119,7 +119,7 @@ don't make the file descriptors two-way. */ verifying. This has to be explicit because it is referenced in more than one source module. */ -#define ADDRESS_EXPANSIONS_COUNT 19 +#define ADDRESS_EXPANSIONS_COUNT 21 /* The maximum permitted number of command-line (-D) macro definitions. We need a limit only to make it easier to generate argument vectors for re-exec diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index abc893ddc..da919048c 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -1736,6 +1736,8 @@ Returns: a replacement reset point rmark smtp_reset(rmark reset_point) { +deliver_set_expansions(NULL); + recipients_list = NULL; rcpt_count = rcpt_defer_count = rcpt_fail_count = raw_recipients_count = recipients_count = recipients_list_max = 0; @@ -1760,11 +1762,8 @@ f.active_local_from_check = local_from_check; /* Can be set by ACL */ f.active_local_sender_retain = local_sender_retain; /* Can be set by ACL */ sending_ip_address = NULL; return_path = sender_address = NULL; -deliver_localpart_data = deliver_domain_data = recipient_data = sender_data = NULL; /* Can be set by ACL */ recipient_verify_failure = NULL; -deliver_localpart_parent = deliver_localpart_orig = NULL; -deliver_domain_parent = deliver_domain_orig = NULL; callout_address = NULL; submission_name = NULL; /* Can be set by ACL */ raw_sender = NULL; /* After SMTP rewrite, before qualifying */ @@ -1778,7 +1777,6 @@ dnslist_domain = dnslist_matched = NULL; dsn_ret = 0; dsn_envid = NULL; -deliver_host = deliver_host_address = NULL; /* Can be set by ACL */ #ifndef DISABLE_TLS memset(&tls_out, 0, sizeof(tls_out)); /* Can be set by callout */ tls_out.active.sock = -1; commit bfd2759002fd2b5b376a1544e8fc59f313dc4e9c Author: Jeremy Harris Date: Mon Dec 22 10:10:01 2025 +0000 Disable -bI:sieve for sieve-less builds. Bug 3184 diff --git a/src/src/EDITME b/src/src/EDITME index 21d81f43c..f641a6699 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -566,7 +566,6 @@ SUPPORT_DANE=yes # SUPPORT_EXIM_FILTER=2 -#------------------------------------------------------------------------------ # Compiling with support for Sieve filters is the default. To disable this # uncomment the line below. @@ -577,6 +576,7 @@ SUPPORT_DANE=yes # SUPPORT_SIEVE_FILTER=2 +#------------------------------------------------------------------------------ # Uncomment the line to include DSCP support. Set to 2 for a module build. # SUPPORT_DSCP diff --git a/src/src/drtables.c b/src/src/drtables.c index 1b9dc0c98..29ae84bd6 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -348,8 +348,9 @@ if ((mi = misc_mod_findonly(name))) return mi; #ifdef LOOKUP_MODULE_DIR return misc_mod_load(name, errstr); #else -*errstr = string_sprintf("module %q not built-in, and" - " no setting for LOOKUP_MODULE_DIR", name); +if (errstr) + *errstr = string_sprintf("module %q not built-in, and" + " no setting for LOOKUP_MODULE_DIR", name); return NULL; #endif /*LOOKUP_MODULE_DIR*/ } diff --git a/src/src/exim.c b/src/src/exim.c index 055882ad0..8c5089f7b 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -85,7 +85,10 @@ function_store_nullfree(void * block, void * tag) *************************************************/ enum commandline_info { CMDINFO_NONE=0, - CMDINFO_HELP, CMDINFO_SIEVE, CMDINFO_MODULES, + CMDINFO_HELP, CMDINFO_MODULES, +#ifndef DISABLE_SIEVE_FILTER + CMDINFO_SIEVE, +#endif #ifdef SUPPORT_DSCP CMDINFO_DSCP #endif @@ -1393,27 +1396,30 @@ switch(request) " exim -bI:dscp list of known dscp value keywords\n" #endif " exim -bI:modules list of loadable modules\n" +#ifndef DISABLE_SIEVE_FILTER " exim -bI:sieve list of supported sieve extensions\n" +#endif ); return; case CMDINFO_MODULES: mod_names(stream); return; +#ifndef DISABLE_SIEVE_FILTER case CMDINFO_SIEVE: { - const misc_module_info * mi; + misc_module_info * mi = misc_mod_find(US"sieve_filter", NULL); typedef void (*fn_t)(FILE *); - if ((mi = misc_mod_find(US"sieve_filter", NULL))) + if (mi) (((fn_t *) mi->functions)[SIEVE_EXTENSIONS]) (stream); else fprintf(stream, "Sieve filtering not available\n"); } return; +#endif #ifdef SUPPORT_DSCP case CMDINFO_DSCP: { - uschar * dummy_errstr; - misc_module_info * mi = misc_mod_find(US"dscp", &dummy_errstr); + misc_module_info * mi = misc_mod_find(US"dscp", NULL); typedef void (*fn_t)(FILE *); if (mi) ((fn_t *) mi->functions)[DSCP_KEYWORDS] (stream); } @@ -2438,10 +2444,12 @@ on the second character (the one after '-'), to save some effort. */ const uschar * p = argrest+1; info_flag = CMDINFO_HELP; if (Ustrlen(p)) - if (strcmpic(p, CUS"sieve") == 0) - { info_flag = CMDINFO_SIEVE; info_stdout = TRUE; } - else if (strcmpic(p, CUS"modules") == 0) + if (strcmpic(p, CUS"modules") == 0) { info_flag = CMDINFO_MODULES; info_stdout = TRUE; } +#ifndef DISABLE_SIEVE_FILTER + else if (strcmpic(p, CUS"sieve") == 0) + { info_flag = CMDINFO_SIEVE; info_stdout = TRUE; } +#endif #ifdef SUPPORT_DSCP else if (strcmpic(p, CUS"dscp") == 0) { info_flag = CMDINFO_DSCP; info_stdout = TRUE; } diff --git a/src/src/expand.c b/src/src/expand.c index b0500037d..85d27b534 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -2802,7 +2802,8 @@ switch(cond_type = identify_operator(&s, &opname)) case ECOND_RADIUS: #ifdef RADIUS_CONFIG_FILE { - const misc_module_info * mi = misc_mod_find(US"radius", NULL); + uschar * dummy_errstr; + const misc_module_info * mi = misc_mod_find(US"radius", &dummy_errstr); typedef int (*fn_t)(const uschar *, uschar **); if (!mi) goto COND_FAILED_NOT_COMPILED; diff --git a/src/src/lookups/spf.c b/src/src/lookups/spf.c index a8404d9bf..e41e1dcb0 100644 --- a/src/src/lookups/spf.c +++ b/src/src/lookups/spf.c @@ -84,8 +84,7 @@ return FAIL; gstring * spf_version_report(gstring * g) { -uschar * dummy_errmsg; -misc_module_info * mi = misc_mod_find(US"spf", &dummy_errmsg); +misc_module_info * mi = misc_mod_find(US"spf", NULL); return mi && mi->lib_vers_report ? mi->lib_vers_report(g) : g; } diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index da919048c..643d88ca2 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -2669,11 +2669,10 @@ Failure will not allow any SMTP function other than QUIT. */ f.quit_cmd_only = FALSE; if (hosts_proxy) { - uschar * dummy_errmsg; misc_module_info * mi; typedef BOOL (*fn_t) (void); - if (!(mi = misc_mod_find(US"proxy", &dummy_errmsg))) + if (!(mi = misc_mod_find(US"proxy", NULL))) { smtp_closedown(US"Temporary local problem - please try later"); return FALSE; @@ -4443,11 +4442,10 @@ while (done <= 0) #ifdef EXPERIMENTAL_XCLIENT if (proxy_session || verify_check_host(&hosts_xclient) != FAIL) { - uschar * dummy_errmsg; typedef gstring * (*fn_t) (gstring *); if ( !xclient_mi - && !(xclient_mi = misc_mod_find(US"xclient", &dummy_errmsg))) + && !(xclient_mi = misc_mod_find(US"xclient", NULL))) { smtp_closedown(US"Temporary local problem - please try later"); return FALSE; diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c index a5557d09e..2712ee0ce 100644 --- a/src/src/smtp_out.c +++ b/src/src/smtp_out.c @@ -296,8 +296,7 @@ bomb out, just log it and continue in default traffic class. */ GET_OPTION("dscp"); if (ob->dscp) { - uschar * dummy_errstr; - misc_module_info * mi = misc_mod_find(US"dscp", &dummy_errstr); + misc_module_info * mi = misc_mod_find(US"dscp", NULL); typedef void (*fn_t)(int, const uschar *, int); if (mi) ((fn_t *) mi->functions)[DSCP_TRANSPORT] (sock, ob->dscp, sc->host_af); @@ -521,8 +520,7 @@ if (ob->socks_proxy) } if (*ob->socks_proxy) { - uschar * dummy_errmsg; - misc_module_info * mi = misc_mod_find(US"socks", &dummy_errmsg); + misc_module_info * mi = misc_mod_find(US"socks", NULL); typedef int (*fn_t) (const smtp_connect_args *, const blob *); return mi ? ((fn_t *) mi->functions)[SOCKS_CONNECT] (sc, early_data) : -1; } commit 711a7387e0200c5dda3f9d0a744dae18afb80351 Author: Jeremy Harris Date: Tue Dec 23 13:07:35 2025 +0000 Keep handle open for module dir diff --git a/src/src/drtables.c b/src/src/drtables.c index 29ae84bd6..ec9ca3c67 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -221,7 +221,7 @@ DIR * dd; const pcre2_code * regex_islookupmod = regex_must_compile( US"^([a-z0-9]+)_lookup\\." DYNLIB_FN_EXT "$", MCS_NOFLAGS, TRUE); -if (!(dd = exim_opendir(CUS LOOKUP_MODULE_DIR))) +if (!(dd = open_module_dir())) g = string_cat(g, US"FAIL exim_opendir"); else { @@ -247,7 +247,6 @@ else dlclose(dl); } } - closedir(dd); } #endif /*!LOOKUP_MODULE_DIR*/ return g; @@ -448,7 +447,7 @@ DEBUG(D_lookup) debug_printf_indent("Total %d built-in lookups\n", lookup_list_c #ifdef LOOKUP_MODULE_DIR -if (!(dd = exim_opendir(CUS LOOKUP_MODULE_DIR))) +if (!(dd = open_module_dir())) { EARLY_DEBUG(D_lookup, "Couldn't open %s: not loading lookup modules\n", LOOKUP_MODULE_DIR); log_write(0, LOG_MAIN|LOG_PANIC, @@ -476,7 +475,6 @@ else log_write(0, LOG_MAIN|LOG_PANIC, "%s", errstr); } } - closedir(dd); } EARLY_DEBUG(D_lookup, "Loaded %d dynamic lookup modules\n", countmodules); @@ -589,12 +587,11 @@ const pcre2_code * regex_ismodule = regex_must_compile( DYNLIB_FN_EXT "$", MCS_NOFLAGS, TRUE); -if ((dd = exim_opendir(CUS LOOKUP_MODULE_DIR))) +if ((dd = open_module_dir())) { for (struct dirent * ent; ent = readdir(dd); ) if (regex_match_and_setup(regex_ismodule, US ent->d_name, 0, 0)) list = string_append_listele(list, ' ', expand_nstring[1]); - closedir(dd); if (list) fprintf(stream, "Installed modules: %s\n", string_from_gstring(list)); diff --git a/src/src/functions.h b/src/src/functions.h index fe9d718ea..b2c2626ab 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -1327,7 +1327,7 @@ if (f.running_in_test_harness && f.testsuite_delays) millisleep(millisec); /* Taint-checked file opens. Return values/errno per open(2). */ static inline int -exim_open2(const char *pathname, int flags) +exim_open2(const char * pathname, int flags) { if (!is_tainted(pathname)) return open(pathname, flags); log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname); @@ -1344,7 +1344,7 @@ return -1; } #ifdef EXIM_HAVE_OPENAT static inline int -exim_openat(int dirfd, const char *pathname, int flags) +exim_openat(int dirfd, const char * pathname, int flags) { if (!is_tainted(pathname)) return openat(dirfd, pathname, flags); log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname); @@ -1362,7 +1362,7 @@ return -1; #endif static inline FILE * -exim_fopen(const char *pathname, const char *mode) +exim_fopen(const char * pathname, const char * mode) { if (!is_tainted(pathname)) return fopen(pathname, mode); log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname); @@ -1379,6 +1379,18 @@ errno = EACCES; return NULL; } +#ifdef LOOKUP_MODULE_DIR +static inline DIR * +open_module_dir(void) +{ +if (module_dir) + rewinddir(module_dir); +else + module_dir = exim_opendir(CUS LOOKUP_MODULE_DIR); +return module_dir; +} +#endif + /******************************************************************************/ # if !defined(COMPILE_UTILITY) diff --git a/src/src/globals.c b/src/src/globals.c index d04feb24f..e5de80ea4 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -1101,6 +1101,9 @@ int mime_is_coverletter = 0; int mime_is_rfc822 = 0; int mime_part_count = -1; #endif +#ifdef LOOKUP_MODULE_DIR +DIR * module_dir = NULL; +#endif uid_t *never_users = NULL; uschar *notifier_socket = US"$spool_directory/" NOTIFIER_SOCKET_NAME ; diff --git a/src/src/globals.h b/src/src/globals.h index 52c46f169..313062890 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -769,6 +769,9 @@ extern int mime_is_coverletter; extern int mime_is_rfc822; extern int mime_part_count; #endif +#ifdef LOOKUP_MODULE_DIR +extern DIR * module_dir; +#endif extern BOOL mua_wrapper; /* TRUE when Exim is wrapping an MUA */ diff --git a/src/src/readconf.c b/src/src/readconf.c index 7840fd0ac..417a786e3 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -3819,7 +3819,7 @@ for (di = *info_anchor; di; di = di->next) #ifdef LOOKUP_MODULE_DIR /* Potentially a loadable module. Look for a file with the right name. */ -if (!(dd = exim_opendir(CUS LOOKUP_MODULE_DIR))) +if (!(dd = open_module_dir())) log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't open %s: not loading driver modules\n", LOOKUP_MODULE_DIR); else @@ -3828,7 +3828,7 @@ else d->driver_name, class); const char * errormsg; - DEBUG(D_any) debug_printf("Loading %s %s driver from %s\n", + DEBUG(D_any) debug_printf("Loading %q %s driver from %s\n", d->driver_name, class, LOOKUP_MODULE_DIR); for(struct dirent * ent; ent = readdir(dd); ) if (Ustrcmp(ent->d_name, fname) == 0) @@ -3866,7 +3866,6 @@ else store_pool = old_pool; DEBUG(D_any) debug_printf("Loaded module %q (%s)\n", d->driver_name, class); - closedir(dd); goto found; } @@ -3876,7 +3875,6 @@ else dlclose(dl); break; } - closedir(dd); } #endif /* LOOKUP_MODULE_DIR */ commit b3881e820e87ccaabfb2c4a08d3cd07974c59b75 Author: Jeremy Harris Date: Wed Dec 24 13:48:55 2025 +0000 Move lookup options & variables from global to module diff --git a/src/src/drtables.c b/src/src/drtables.c index ec9ca3c67..20034e7ce 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -152,7 +152,7 @@ return dl; /* Try to load a lookup module with the given name. Arguments: - name name of the lookup + name name of the module errstr if not NULL, place "open fail" error message here Return: boolean success @@ -211,6 +211,15 @@ return TRUE; #endif /*LOOKUP_MODULE_DIR*/ +const lookup_info * +lookup_find(const uschar * name, uschar ** errstr) +{ +const lookup_info * li; +if ((li = lookup_findonly(name))) return li; +lookup_mod_load(name, errstr); +return lookup_findonly(name); +} + /* Look at all the lookup module files and add a name from each lookup type */ gstring * diff --git a/src/src/expand.c b/src/src/expand.c index 85d27b534..ab0c0b37a 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -459,10 +459,10 @@ static var_entry var_table[] = { { "address_file", vtype_stringptr, &address_file }, { "address_pipe", vtype_stringptr, &address_pipe }, #ifdef EXPERIMENTAL_ARC - { "arc_domains", vtype_module, US"arc" }, - { "arc_oldest_pass", vtype_module, US"arc" }, - { "arc_state", vtype_module, US"arc" }, - { "arc_state_reason", vtype_module, US"arc" }, + { "arc_domains", vtype_misc_module, US"arc" }, + { "arc_oldest_pass", vtype_misc_module, US"arc" }, + { "arc_state", vtype_misc_module, US"arc" }, + { "arc_state_reason", vtype_misc_module, US"arc" }, #endif { "atrn_host", vtype_stringptr, &atrn_host }, { "atrn_mode", vtype_stringptr, &atrn_mode }, @@ -491,36 +491,36 @@ static var_entry var_table[] = { { "dcc_result", vtype_stringptr, &dcc_result }, #endif #ifndef DISABLE_DKIM - { "dkim_algo", vtype_module, US"dkim" }, - { "dkim_bodylength", vtype_module, US"dkim" }, - { "dkim_canon_body", vtype_module, US"dkim" }, - { "dkim_canon_headers", vtype_module, US"dkim" }, - { "dkim_copiedheaders", vtype_module, US"dkim" }, - { "dkim_created", vtype_module, US"dkim" }, - { "dkim_cur_signer", vtype_module, US"dkim" }, - { "dkim_domain", vtype_module, US"dkim" }, - { "dkim_expires", vtype_module, US"dkim" }, - { "dkim_headernames", vtype_module, US"dkim" }, - { "dkim_identity", vtype_module, US"dkim" }, - { "dkim_key_granularity",vtype_module, US"dkim" }, - { "dkim_key_length", vtype_module, US"dkim" }, - { "dkim_key_nosubdomains",vtype_module, US"dkim" }, - { "dkim_key_notes", vtype_module, US"dkim" }, - { "dkim_key_srvtype", vtype_module, US"dkim" }, - { "dkim_key_testing", vtype_module, US"dkim" }, - { "dkim_selector", vtype_module, US"dkim" }, - { "dkim_signers", vtype_module, US"dkim" }, - { "dkim_verify_reason", vtype_module, US"dkim" }, - { "dkim_verify_signers", vtype_module, US"dkim" }, - { "dkim_verify_status", vtype_module, US"dkim" }, + { "dkim_algo", vtype_misc_module, US"dkim" }, + { "dkim_bodylength", vtype_misc_module, US"dkim" }, + { "dkim_canon_body", vtype_misc_module, US"dkim" }, + { "dkim_canon_headers", vtype_misc_module, US"dkim" }, + { "dkim_copiedheaders", vtype_misc_module, US"dkim" }, + { "dkim_created", vtype_misc_module, US"dkim" }, + { "dkim_cur_signer", vtype_misc_module, US"dkim" }, + { "dkim_domain", vtype_misc_module, US"dkim" }, + { "dkim_expires", vtype_misc_module, US"dkim" }, + { "dkim_headernames", vtype_misc_module, US"dkim" }, + { "dkim_identity", vtype_misc_module, US"dkim" }, + { "dkim_key_granularity",vtype_misc_module, US"dkim" }, + { "dkim_key_length", vtype_misc_module, US"dkim" }, + { "dkim_key_nosubdomains",vtype_misc_module, US"dkim" }, + { "dkim_key_notes", vtype_misc_module, US"dkim" }, + { "dkim_key_srvtype", vtype_misc_module, US"dkim" }, + { "dkim_key_testing", vtype_misc_module, US"dkim" }, + { "dkim_selector", vtype_misc_module, US"dkim" }, + { "dkim_signers", vtype_misc_module, US"dkim" }, + { "dkim_verify_reason", vtype_misc_module, US"dkim" }, + { "dkim_verify_signers", vtype_misc_module, US"dkim" }, + { "dkim_verify_status", vtype_misc_module, US"dkim" }, #endif #ifdef EXIM_HAVE_DMARC - { "dmarc_alignment_dkim",vtype_module, US"dmarc" }, - { "dmarc_alignment_spf", vtype_module, US"dmarc" }, - { "dmarc_domain_policy", vtype_module, US"dmarc" }, - { "dmarc_status", vtype_module, US"dmarc" }, - { "dmarc_status_text", vtype_module, US"dmarc" }, - { "dmarc_used_domain", vtype_module, US"dmarc" }, + { "dmarc_alignment_dkim",vtype_misc_module, US"dmarc" }, + { "dmarc_alignment_spf", vtype_misc_module, US"dmarc" }, + { "dmarc_domain_policy", vtype_misc_module, US"dmarc" }, + { "dmarc_status", vtype_misc_module, US"dmarc" }, + { "dmarc_status_text", vtype_misc_module, US"dmarc" }, + { "dmarc_used_domain", vtype_misc_module, US"dmarc" }, #endif { "dnslist_domain", vtype_stringptr, &dnslist_domain }, { "dnslist_matched", vtype_stringptr, &dnslist_matched }, @@ -554,7 +554,7 @@ static var_entry var_table[] = { { "interface_port", vtype_int, &interface_port }, { "item", vtype_stringptr, &iterate_item }, #ifdef LOOKUP_LDAP - { "ldap_dn", vtype_stringptr, &eldap_dn }, + { "ldap_dn", vtype_lookup_module, US"ldap" }, #endif { "load_average", vtype_load_avg, NULL }, { "local_part", vtype_stringptr, &deliver_localpart }, @@ -713,13 +713,13 @@ static var_entry var_table[] = { { "spam_score_int", vtype_stringptr, &spam_score_int }, #endif #ifdef EXIM_HAVE_SPF - { "spf_guess", vtype_module, US"spf" }, - { "spf_header_comment", vtype_module, US"spf" }, - { "spf_received", vtype_module, US"spf" }, - { "spf_result", vtype_module, US"spf" }, - { "spf_result_guessed", vtype_module, US"spf" }, - { "spf_smtp_comment", vtype_module, US"spf" }, - { "spf_used_domain", vtype_module, US"spf" }, + { "spf_guess", vtype_misc_module, US"spf" }, + { "spf_header_comment", vtype_misc_module, US"spf" }, + { "spf_received", vtype_misc_module, US"spf" }, + { "spf_result", vtype_misc_module, US"spf" }, + { "spf_result_guessed", vtype_misc_module, US"spf" }, + { "spf_smtp_comment", vtype_misc_module, US"spf" }, + { "spf_used_domain", vtype_misc_module, US"spf" }, #endif { "spool_directory", vtype_stringptr, &spool_directory }, { "spool_inodes", vtype_pinodes, (void *)TRUE }, @@ -2161,7 +2161,7 @@ switch (vp->type) } #endif - case vtype_module: + case vtype_misc_module: { uschar * errstr; misc_module_info * mi = misc_mod_find(val, &errstr); diff --git a/src/src/functions.h b/src/src/functions.h index b2c2626ab..57ae06c58 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -307,6 +307,8 @@ extern void log_write_die(unsigned, int, const char * format, ...) extern const lookup_info * lookup_with_acq_num(unsigned); extern gstring *lookup_dynamic_supported(gstring *); +extern const lookup_info * lookup_find(const uschar *, uschar **); +extern const lookup_info * lookup_findonly(const uschar *); #ifdef LOOKUP_MODULE_DIR extern BOOL lookup_one_mod_load(const uschar *, uschar **); #endif diff --git a/src/src/globals.c b/src/src/globals.c index e5de80ea4..4fe89f727 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -58,39 +58,6 @@ BOOL opt_perl_taintmode = FALSE; tree_node *dlobj_anchor = NULL; #endif -#ifdef LOOKUP_LDAP -uschar *eldap_ca_cert_dir = NULL; -uschar *eldap_ca_cert_file = NULL; -uschar *eldap_cert_file = NULL; -uschar *eldap_cert_key = NULL; -uschar *eldap_cipher_suite = NULL; -uschar *eldap_default_servers = NULL; -uschar *eldap_require_cert = NULL; -int eldap_version = -1; -BOOL eldap_start_tls = FALSE; -#endif - -#ifdef LOOKUP_MYSQL -uschar *mysql_servers = NULL; -#endif - -#ifdef LOOKUP_ORACLE -uschar *oracle_servers = NULL; -#endif - -#ifdef LOOKUP_PGSQL -uschar *pgsql_servers = NULL; -#endif - -#ifdef LOOKUP_REDIS -uschar *redis_servers = NULL; -#endif - -#ifdef LOOKUP_SQLITE -uschar *sqlite_dbfile = NULL; -int sqlite_lock_timeout = 5; -#endif - #ifdef SUPPORT_MOVE_FROZEN_MESSAGES BOOL move_frozen_messages = FALSE; #endif @@ -908,7 +875,6 @@ uschar *keep_environment = NULL; int keep_malformed = 4*24*60*60; /* 4 days */ -uschar *eldap_dn = NULL; const uschar *letter_digit_hyphen_dot = US"abcdefghijklmnopqrstuvwxyz" ".-0123456789" diff --git a/src/src/globals.h b/src/src/globals.h index 313062890..f8cd0548b 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -40,39 +40,6 @@ extern BOOL opt_perl_taintmode; /* Enable taint mode in Perl */ extern tree_node *dlobj_anchor; /* Tree of dynamically-loaded objects */ #endif -#ifdef LOOKUP_LDAP -extern uschar *eldap_ca_cert_dir; /* Directory with CA certificates */ -extern uschar *eldap_ca_cert_file; /* CA certificate file */ -extern uschar *eldap_cert_file; /* Certificate file */ -extern uschar *eldap_cert_key; /* Certificate key file */ -extern uschar *eldap_cipher_suite; /* Allowed cipher suite */ -extern uschar *eldap_default_servers; /* List of default servers */ -extern uschar *eldap_require_cert; /* Peer certificate checking strategy */ -extern BOOL eldap_start_tls; /* Use STARTTLS */ -extern int eldap_version; /* LDAP version */ -#endif - -#ifdef LOOKUP_MYSQL -extern uschar *mysql_servers; /* List of servers and connect info */ -#endif - -#ifdef LOOKUP_ORACLE -extern uschar *oracle_servers; /* List of servers and connect info */ -#endif - -#ifdef LOOKUP_PGSQL -extern uschar *pgsql_servers; /* List of servers and connect info */ -#endif - -#ifdef LOOKUP_REDIS -extern uschar *redis_servers; /* List of servers and connect info */ -#endif - -#ifdef LOOKUP_SQLITE -extern uschar *sqlite_dbfile; /* Filname for database */ -extern int sqlite_lock_timeout; /* Internal lock waiting timeout */ -#endif - #ifdef SUPPORT_MOVE_FROZEN_MESSAGES extern BOOL move_frozen_messages; /* Get them out of the normal directory */ #endif @@ -677,7 +644,6 @@ extern int journal_fd; /* Fd for journal file */ extern uschar *keep_environment; /* Whitelist for environment variables */ extern int keep_malformed; /* Time to keep malformed messages */ -extern uschar *eldap_dn; /* Where LDAP DNs are left */ extern const uschar *letter_digit_hyphen_dot; /* Legitimate DNS host name chars */ #ifndef DISABLE_ESMTP_LIMITS extern uschar *limits_advertise_hosts; /* for banner/EHLO pipelining */ diff --git a/src/src/lookupapi.h b/src/src/lookupapi.h index b8ec17880..84f150e03 100644 --- a/src/src/lookupapi.h +++ b/src/src/lookupapi.h @@ -50,15 +50,21 @@ typedef struct lookup_info { unsigned); /* lookup type index */ gstring * (*version_report)( /* diagnostic function */ gstring *); /* string to appand to */ + + void * options; + unsigned options_count; + void * variables; + unsigned variables_count; } lookup_info; /* This magic number is used by the following lookup_module_info structure for checking API compatibility. It used to be equivalent to the string"LMM3" */ -#define LOOKUP_MODULE_INFO_MAGIC 0x4c4d4935 +#define LOOKUP_MODULE_INFO_MAGIC 0x4c4d4936 /* Version 2 adds: version_report */ /* Version 3 change: non/cache becomes TTL in seconds */ /* Version 4 add: index on quoting function */ /* Version 5 change: version report now adds to a gstring */ +/* Version 6 add: options */ typedef struct lookup_module_info { uint magic; diff --git a/src/src/lookups/ldap.c b/src/src/lookups/ldap.c index 0a00633ab..06b82aa4c 100644 --- a/src/src/lookups/ldap.c +++ b/src/src/lookups/ldap.c @@ -74,6 +74,19 @@ LDAP_LIB_OPENLDAP1 SEARCH_LDAP_DN lookup). */ +static uschar * eldap_ca_cert_dir = NULL; /* Directory with CA certificates */ +static uschar * eldap_ca_cert_file = NULL; /* CA certificate file */ +static uschar * eldap_cert_file = NULL; /* Certificate file */ +static uschar * eldap_cert_key = NULL; /* Certificate key file */ +static uschar * eldap_cipher_suite = NULL; /* Allowed cipher suite */ +static uschar * eldap_default_servers = NULL; /* List of default servers */ +static uschar * eldap_require_cert = NULL; /* Peer certificate checking strategy */ +static int eldap_version = -1; /* Use STARTTLS */ +static BOOL eldap_start_tls = FALSE; /* LDAP version */ + +static uschar * eldap_dn; /* Where LDAP DNs are left */ + + /* Structure and anchor for caching connections. */ typedef struct ldap_connection { @@ -87,7 +100,7 @@ typedef struct ldap_connection { LDAP *ld; } LDAP_CONNECTION; -static LDAP_CONNECTION *ldap_connections = NULL; +static LDAP_CONNECTION * ldap_connections = NULL; @@ -1587,16 +1600,42 @@ return string_cat(g, "Library version: LDAP: (unknown)\n"); } +/******************************************************************************/ +/* Module API */ + +static optionlist eldap_options[] = { + { "ldap_ca_cert_dir", opt_stringptr, {&eldap_ca_cert_dir} }, + { "ldap_ca_cert_file", opt_stringptr, {&eldap_ca_cert_file} }, + { "ldap_cert_file", opt_stringptr, {&eldap_cert_file} }, + { "ldap_cert_key", opt_stringptr, {&eldap_cert_key} }, + { "ldap_cipher_suite", opt_stringptr, {&eldap_cipher_suite} }, + { "ldap_default_servers", opt_stringptr, {&eldap_default_servers} }, + { "ldap_require_cert", opt_stringptr, {&eldap_require_cert} }, + { "ldap_start_tls", opt_bool, {&eldap_start_tls} }, + { "ldap_version", opt_int, {&eldap_version} }, +}; + +static var_entry eldap_variables[] = { + { "ldap_dn", vtype_stringptr, &eldap_dn }, +}; + static lookup_info ldap_lookup_info = { - .name = US"ldap", /* lookup name */ - .type = lookup_querystyle, /* query-style lookup */ - .open = eldap_open, /* open function */ - .check = NULL, /* check function */ - .find = eldap_find, /* find function */ - .close = NULL, /* no close function */ - .tidy = eldap_tidy, /* tidy function */ - .quote = eldap_quote, /* quoting function */ - .version_report = ldap_version_report /* version reporting */ + .name = US"ldap", /* lookup name */ + .type = lookup_querystyle, /* query-style lookup */ + .open = eldap_open, /* open function */ + .check = NULL, /* check function */ + .find = eldap_find, /* find function */ + .close = NULL, /* no close function */ + .tidy = eldap_tidy, /* tidy function */ + .quote = eldap_quote, /* quoting function */ + .version_report = ldap_version_report, /* version reporting */ + + /* This lookup name matches the modules name, so access options here */ + .options = eldap_options, + .options_count = nelem(eldap_options), + + .variables = eldap_variables, + .variables_count = nelem(eldap_variables), }; static lookup_info ldapdn_lookup_info = { diff --git a/src/src/lookups/mysql.c b/src/src/lookups/mysql.c index f28e9154f..dca125188 100644 --- a/src/src/lookups/mysql.c +++ b/src/src/lookups/mysql.c @@ -64,6 +64,8 @@ with versions before 10.2, as they do not define there there specific symbols. #endif +static uschar * mysql_servers = NULL; /* List of servers and connect info */ + /* Structure and anchor for caching connections. */ typedef struct mysql_connection { @@ -478,18 +480,28 @@ g = string_fmt_append(g, return g; } +/******************************************************************************/ +/* Module API */ + +static optionlist mysql_glbl_options[] = { + { "mysql_servers", opt_stringptr, {&mysql_servers} } +}; + /* These are the lookup_info blocks for this driver */ static lookup_info mysql_lookup_info = { - .name = US"mysql", /* lookup name */ - .type = lookup_querystyle, /* query-style lookup */ - .open = mysql_open, /* open function */ - .check = NULL, /* no check function */ - .find = mysql_find, /* find function */ - .close = NULL, /* no close function */ - .tidy = mysql_tidy, /* tidy function */ - .quote = mysql_quote, /* quoting function */ - .version_report = mysql_version_report /* version reporting */ + .name = US"mysql", /* lookup name */ + .type = lookup_querystyle, /* query-style lookup */ + .open = mysql_open, /* open function */ + .check = NULL, /* no check function */ + .find = mysql_find, /* find function */ + .close = NULL, /* no close function */ + .tidy = mysql_tidy, /* tidy function */ + .quote = mysql_quote, /* quoting function */ + .version_report = mysql_version_report, /* version reporting */ + + .options = mysql_glbl_options, + .options_count = nelem(mysql_glbl_options), }; #ifdef DYNLOOKUP diff --git a/src/src/lookups/oracle.c b/src/src/lookups/oracle.c index e4eedbe28..9472fc2ff 100644 --- a/src/src/lookups/oracle.c +++ b/src/src/lookups/oracle.c @@ -69,6 +69,11 @@ typedef struct Ora_Define { ub2 col_retlen, col_retcode; } Ora_Define; + + +static uschar * oracle_servers = NULL; /* List of servers and connect info */ + + /* Structure and anchor for caching connections. */ typedef struct oracle_connection { @@ -604,16 +609,26 @@ return g; } +/******************************************************************************/ +/* Module API */ + +static optionlist oracle_options[] = { + { "oracle_servers", opt_stringptr, {&oracle_servers} } +}; + static lookup_info _lookup_info = { - .name = US"oracle", /* lookup name */ - .type = lookup_querystyle, /* query-style lookup */ - .open = oracle_open, /* open function */ - .check = NULL, /* check function */ - .find = oracle_find, /* find function */ - .close = NULL, /* no close function */ - .tidy = oracle_tidy, /* tidy function */ - .quote = oracle_quote, /* quoting function */ - .version_report = oracle_version_report /* version reporting */ + .name = US"oracle", /* lookup name */ + .type = lookup_querystyle, /* query-style lookup */ + .open = oracle_open, /* open function */ + .check = NULL, /* check function */ + .find = oracle_find, /* find function */ + .close = NULL, /* no close function */ + .tidy = oracle_tidy, /* tidy function */ + .quote = oracle_quote, /* quoting function */ + .version_report = oracle_version_report, /* version reporting */ + + .options = oracle_options, + .options_count = nelem(oracle_options), }; #ifdef DYNLOOKUP diff --git a/src/src/lookups/pgsql.c b/src/src/lookups/pgsql.c index 7071cb4ab..bef6c59e2 100644 --- a/src/src/lookups/pgsql.c +++ b/src/src/lookups/pgsql.c @@ -16,6 +16,9 @@ socket extension. */ #include /* The system header */ + +static uschar * pgsql_servers = NULL; /* List of servers and connect info */ + /* Structure and anchor for caching connections. */ typedef struct pgsql_connection { @@ -490,16 +493,26 @@ return g; } +/******************************************************************************/ +/* Module API */ + +static optionlist pgsql_options[] = { + { "pgsql_servers", opt_stringptr, {&pgsql_servers} } +}; + static lookup_info _lookup_info = { - .name = US"pgsql", /* lookup name */ - .type = lookup_querystyle, /* query-style lookup */ - .open = pgsql_open, /* open function */ - .check = NULL, /* no check function */ - .find = pgsql_find, /* find function */ - .close = NULL, /* no close function */ - .tidy = pgsql_tidy, /* tidy function */ - .quote = pgsql_quote, /* quoting function */ - .version_report = pgsql_version_report /* version reporting */ + .name = US"pgsql", /* lookup name */ + .type = lookup_querystyle, /* query-style lookup */ + .open = pgsql_open, /* open function */ + .check = NULL, /* no check function */ + .find = pgsql_find, /* find function */ + .close = NULL, /* no close function */ + .tidy = pgsql_tidy, /* tidy function */ + .quote = pgsql_quote, /* quoting function */ + .version_report = pgsql_version_report, /* version reporting */ + + .options = pgsql_options, + .options_count = nelem(pgsql_options), }; #ifdef DYNLOOKUP diff --git a/src/src/lookups/redis.c b/src/src/lookups/redis.c index 61c264a73..e88ded154 100644 --- a/src/src/lookups/redis.c +++ b/src/src/lookups/redis.c @@ -15,9 +15,7 @@ #include -#ifndef nele -# define nele(arr) (sizeof(arr) / sizeof(*arr)) -#endif +static uschar * redis_servers = NULL; /* List of servers and connect info */ /* Structure and anchor for caching connections. */ typedef struct redis_connection { @@ -26,7 +24,7 @@ typedef struct redis_connection { redisContext *handle; } redis_connection; -static redis_connection *redis_connections = NULL; +static redis_connection * redis_connections = NULL; static void * @@ -210,7 +208,7 @@ if(sdata[1]) Uskip_whitespace(&s); - for (i = 0; *s && i < nele(argv); i++) + for (i = 0; *s && i < nelem(argv); i++) { gstring * g; @@ -438,17 +436,27 @@ return string_fmt_append(g, "Library version: REDIS: Compile: %d.%d.%d\n", +/******************************************************************************/ +/* Module API */ + +static optionlist redis_options[] = { + { "redis_servers", opt_stringptr, {&redis_servers} } +}; + /* These are the lookup_info blocks for this driver */ static lookup_info redis_lookup_info = { - .name = US"redis", /* lookup name */ - .type = lookup_querystyle, /* query-style lookup */ - .open = redis_open, /* open function */ - .check = NULL, /* no check function */ - .find = redis_find, /* find function */ - .close = NULL, /* no close function */ - .tidy = redis_tidy, /* tidy function */ - .quote = redis_quote, /* quoting function */ - .version_report = redis_version_report /* version reporting */ + .name = US"redis", /* lookup name */ + .type = lookup_querystyle, /* query-style lookup */ + .open = redis_open, /* open function */ + .check = NULL, /* no check function */ + .find = redis_find, /* find function */ + .close = NULL, /* no close function */ + .tidy = redis_tidy, /* tidy function */ + .quote = redis_quote, /* quoting function */ + .version_report = redis_version_report, /* version reporting */ + + .options = redis_options, + .options_count = nelem(redis_options), }; #ifdef DYNLOOKUP diff --git a/src/src/lookups/sqlite.c b/src/src/lookups/sqlite.c index eb23b8631..c91440e1e 100644 --- a/src/src/lookups/sqlite.c +++ b/src/src/lookups/sqlite.c @@ -12,6 +12,9 @@ #include +static uschar * sqlite_dbfile = NULL; /* Filename for database */ +static int sqlite_lock_timeout = 5;/* Internal lock waiting timeout */ + /************************************************* * Open entry point * @@ -173,20 +176,31 @@ g = string_fmt_append(g, return g; } +/******************************************************************************/ +/* Module API */ + +static optionlist sqlite_options[] = { + { "sqlite_dbfile", opt_stringptr, {&sqlite_dbfile} }, + { "sqlite_lock_timeout", opt_int, {&sqlite_lock_timeout} }, +}; + static lookup_info _lookup_info = { - .name = US"sqlite", /* lookup name */ - .type = lookup_absfilequery, /* query-style lookup, starts with file name */ - .open = sqlite_open, /* open function */ - .check = NULL, /* no check function */ - .find = sqlite_find, /* find function */ - .close = sqlite_close, /* close function */ - .tidy = NULL, /* no tidy function */ - .quote = sqlite_quote, /* quoting function */ - .version_report = sqlite_version_report /* version reporting */ + .name = US"sqlite", /* lookup name */ + .type = lookup_absfilequery, /* query-style lookup, starts with file name */ + .open = sqlite_open, /* open function */ + .check = NULL, /* no check function */ + .find = sqlite_find, /* find function */ + .close = sqlite_close, /* close function */ + .tidy = NULL, /* no tidy function */ + .quote = sqlite_quote, /* quoting function */ + .version_report = sqlite_version_report, /* version reporting */ + + .options = sqlite_options, + .options_count = nelem(sqlite_options), }; #ifdef DYNLOOKUP -#define sqlite_lookup_module_info _lookup_module_info +# define sqlite_lookup_module_info _lookup_module_info #endif static lookup_info *_lookup_list[] = { &_lookup_info }; diff --git a/src/src/macros.h b/src/src/macros.h index 0070ef7ed..f6b240322 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -707,7 +707,8 @@ can be easily tested as a group. That is the only use of opt_bool_last. */ enum { opt_bit = 32, opt_bool_verify, opt_bool_set, opt_expand_bool, opt_bool_last, opt_rewrite, opt_timelist, opt_uid, opt_gid, opt_uidlist, opt_gidlist, - opt_expand_uid, opt_expand_gid, opt_func, opt_void, opt_module }; + opt_expand_uid, opt_expand_gid, opt_func, opt_void, + opt_lookup_module, opt_misc_module }; /* There's a high-ish bit which is used to flag duplicate options, kept for compatibility, which shouldn't be output. Also used for hidden options diff --git a/src/src/readconf.c b/src/src/readconf.c index 417a786e3..46adac621 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -47,7 +47,7 @@ static optionlist optionlist_config[] = { { "acl_smtp_data_prdr", opt_stringptr, {&acl_smtp_data_prdr} }, #endif #ifndef DISABLE_DKIM - { "acl_smtp_dkim", opt_module, {US"dkim"} }, + { "acl_smtp_dkim", opt_misc_module, {US"dkim"} }, #endif { "acl_smtp_etrn", opt_stringptr, {&acl_smtp_etrn} }, { "acl_smtp_expn", opt_stringptr, {&acl_smtp_expn} }, @@ -118,16 +118,16 @@ static optionlist optionlist_config[] = { #endif { "disable_ipv6", opt_bool, {&disable_ipv6} }, #ifndef DISABLE_DKIM - { "dkim_verify_hashes", opt_module, {US"dkim"} }, - { "dkim_verify_keytypes", opt_module, {US"dkim"} }, - { "dkim_verify_min_keysizes", opt_module, {US"dkim"} }, - { "dkim_verify_minimal", opt_module, {US"dkim"} }, - { "dkim_verify_signers", opt_module, {US"dkim"} }, + { "dkim_verify_hashes", opt_misc_module, {US"dkim"} }, + { "dkim_verify_keytypes", opt_misc_module, {US"dkim"} }, + { "dkim_verify_min_keysizes", opt_misc_module, {US"dkim"} }, + { "dkim_verify_minimal", opt_misc_module, {US"dkim"} }, + { "dkim_verify_signers", opt_misc_module, {US"dkim"} }, #endif #ifdef EXIM_HAVE_DMARC - { "dmarc_forensic_sender", opt_module, {US"dmarc"} }, - { "dmarc_history_file", opt_module, {US"dmarc"} }, - { "dmarc_tld_file", opt_module, {US"dmarc"} }, + { "dmarc_forensic_sender", opt_misc_module, {US"dmarc"} }, + { "dmarc_history_file", opt_misc_module, {US"dmarc"} }, + { "dmarc_tld_file", opt_misc_module, {US"dmarc"} }, #endif { "dns_again_means_nonexist", opt_stringptr, {&dns_again_means_nonexist} }, { "dns_check_names_pattern", opt_stringptr, {&check_dns_names_pattern} }, @@ -192,15 +192,15 @@ static optionlist optionlist_config[] = { { "keep_environment", opt_stringptr, {&keep_environment} }, { "keep_malformed", opt_time, {&keep_malformed} }, #ifdef LOOKUP_LDAP - { "ldap_ca_cert_dir", opt_stringptr, {&eldap_ca_cert_dir} }, - { "ldap_ca_cert_file", opt_stringptr, {&eldap_ca_cert_file} }, - { "ldap_cert_file", opt_stringptr, {&eldap_cert_file} }, - { "ldap_cert_key", opt_stringptr, {&eldap_cert_key} }, - { "ldap_cipher_suite", opt_stringptr, {&eldap_cipher_suite} }, - { "ldap_default_servers", opt_stringptr, {&eldap_default_servers} }, - { "ldap_require_cert", opt_stringptr, {&eldap_require_cert} }, - { "ldap_start_tls", opt_bool, {&eldap_start_tls} }, - { "ldap_version", opt_int, {&eldap_version} }, + { "ldap_ca_cert_dir", opt_lookup_module, {US"ldap"} }, + { "ldap_ca_cert_file", opt_lookup_module, {US"ldap"} }, + { "ldap_cert_file", opt_lookup_module, {US"ldap"} }, + { "ldap_cert_key", opt_lookup_module, {US"ldap"} }, + { "ldap_cipher_suite", opt_lookup_module, {US"ldap"} }, + { "ldap_default_servers", opt_lookup_module, {US"ldap"} }, + { "ldap_require_cert", opt_lookup_module, {US"ldap"} }, + { "ldap_start_tls", opt_lookup_module, {US"ldap"} }, + { "ldap_version", opt_lookup_module, {US"ldap"} }, #endif #ifndef DISABLE_ESMTP_LIMITS { "limits_advertise_hosts", opt_stringptr, {&limits_advertise_hosts} }, @@ -231,7 +231,7 @@ static optionlist optionlist_config[] = { #endif { "mua_wrapper", opt_bool, {&mua_wrapper} }, #ifdef LOOKUP_MYSQL - { "mysql_servers", opt_stringptr, {&mysql_servers} }, + { "mysql_servers", opt_lookup_module, {US"mysql"} }, #endif { "never_users", opt_uidlist, {&never_users} }, { "notifier_socket", opt_stringptr, {¬ifier_socket} }, @@ -239,7 +239,7 @@ static optionlist optionlist_config[] = { { "openssl_options", opt_stringptr, {&openssl_options} }, #endif #ifdef LOOKUP_ORACLE - { "oracle_servers", opt_stringptr, {&oracle_servers} }, + { "oracle_servers", opt_lookup_module, {US"oracle"} }, #endif { "panic_coredump", opt_bool, {&panic_coredump} }, { "percent_hack_domains", opt_stringptr, {&percent_hack_domains} }, @@ -249,7 +249,7 @@ static optionlist optionlist_config[] = { { "perl_taintmode", opt_bool, {&opt_perl_taintmode} }, #endif #ifdef LOOKUP_PGSQL - { "pgsql_servers", opt_stringptr, {&pgsql_servers} }, + { "pgsql_servers", opt_lookup_module, {US"pgsql"} }, #endif { "pid_file_path", opt_stringptr, {&pid_file_path} }, { "pipelining_advertise_hosts", opt_stringptr, {&pipelining_advertise_hosts} }, @@ -290,7 +290,7 @@ static optionlist optionlist_config[] = { { "recipients_max", opt_stringptr, {&recipients_max} }, { "recipients_max_reject", opt_bool, {&recipients_max_reject} }, #ifdef LOOKUP_REDIS - { "redis_servers", opt_stringptr, {&redis_servers} }, + { "redis_servers", opt_lookup_module, {US"redis"} }, #endif { "remote_max_parallel", opt_int, {&remote_max_parallel} }, { "remote_sort_domains", opt_stringptr, {&remote_sort_domains} }, @@ -336,15 +336,15 @@ static optionlist optionlist_config[] = { { "spamd_address", opt_stringptr, {&spamd_address} }, #endif #ifdef EXIM_HAVE_SPF - { "spf_guess", opt_module, {US"spf"} }, - { "spf_smtp_comment_template",opt_module, {US"spf"} }, + { "spf_guess", opt_misc_module, {US"spf"} }, + { "spf_smtp_comment_template",opt_misc_module, {US"spf"} }, #endif { "split_spool_directory", opt_bool, {&split_spool_directory} }, { "spool_directory", opt_stringptr, {&spool_directory} }, { "spool_wireformat", opt_bool, {&spool_wireformat} }, #ifdef LOOKUP_SQLITE - { "sqlite_dbfile", opt_stringptr, {&sqlite_dbfile} }, - { "sqlite_lock_timeout", opt_int, {&sqlite_lock_timeout} }, + { "sqlite_dbfile", opt_lookup_module, {US"sqlite"} }, + { "sqlite_lock_timeout", opt_lookup_module, {US"sqlite"} }, #endif { "strict_acl_vars", opt_bool, {&strict_acl_vars} }, { "strip_excess_angle_brackets", opt_bool, {&strip_excess_angle_brackets} }, @@ -1762,13 +1762,10 @@ Returns: TRUE if an option was read successfully, */ static BOOL -readconf_handle_option(uschar *buffer, optionlist *oltop, int last, - void *data_block, uschar *unknown_txt) +readconf_handle_option(uschar * buffer, optionlist * oltop, int last, + void * data_block, uschar * unknown_txt) { -int ptr; -int offset = 0; -int count, type, value; -int issecure = 0; +int ptr, offset = 0, count, type, value, issecure = 0; uid_t uid; gid_t gid; BOOL boolvalue = TRUE; @@ -2507,10 +2504,23 @@ switch (type) ol->v.fn(name, s, 0); break; - case opt_module: + case opt_lookup_module: { uschar * errstr; - misc_module_info * mi = misc_mod_find(US ol->v.value, &errstr); + const lookup_info * li = lookup_find(US ol->v.value, &errstr); + if (!li) + log_write_die(0, LOG_CONFIG_IN, + "failed to find %s module for %s: %s", US ol->v.value, name, errstr); + + oltop = li->options; + last = li->options_count; + goto sublist; + } + + case opt_misc_module: + { + uschar * errstr; + const misc_module_info * mi = misc_mod_find(US ol->v.value, &errstr); if (!mi) log_write_die(0, LOG_CONFIG_IN, "failed to find %s module for %s: %s", US ol->v.value, name, errstr); diff --git a/src/src/search.c b/src/src/search.c index e09f3ab4e..e81806379 100644 --- a/src/src/search.c +++ b/src/src/search.c @@ -54,8 +54,8 @@ static rmark search_reset_point = NULL; * Validate a plain lookup type name * *************************************************/ -static const lookup_info * -internal_search_findtype(const uschar * name) +const lookup_info * +lookup_findonly(const uschar * name) { tree_node * t = tree_search(lookups_tree, name); return t ? t->data.ptr : NULL; @@ -81,7 +81,7 @@ const lookup_info * li; if (name[len]) name = string_copyn(name, len); -if ((li = internal_search_findtype(name))) +if ((li = lookup_findonly(name))) return li; #ifdef LOOKUP_MODULE_DIR @@ -89,7 +89,7 @@ if ((li = internal_search_findtype(name))) debug_printf_indent("searchtype %s not initially found\n", name); if (lookup_one_mod_load(name, NULL)) - if ((li = internal_search_findtype(name))) + if ((li = lookup_findonly(name))) return li; else { DEBUG(D_lookup) debug_printf_indent("find retry failed\n"); } diff --git a/src/src/structs.h b/src/src/structs.h index 22a4bba90..52b540356 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -1012,7 +1012,8 @@ enum vtypes { #ifndef DISABLE_DKIM vtype_dkim, /* Lookup of value in DKIM signature */ #endif - vtype_module, /* variable lives in a module; value is module name */ + vtype_lookup_module, /* variable lives in a module; value is module name */ + vtype_misc_module, /* variable lives in a module; value is module name */ }; /* Type for main variable table */ commit d6c8c56fd08000ea9da90d4cf506870a6a7e205b Author: Jeremy Harris Date: Wed Dec 24 14:18:00 2025 +0000 Docs: build note diff --git a/src/src/EDITME b/src/src/EDITME index f641a6699..ec187e919 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -751,6 +751,8 @@ DISABLE_MAL_MKS=yes # sqlite # USE_SQLITE = yes # DBMLIB = -lsqlite3 +# If using Gnu Make this is usable: +# DBMLIB = $(shell pkg-config --libs sqlite3) #------------------------------------------------------------------------------ commit 928e43f00d3b88e2684fdd530545932895ef684b Author: Jeremy Harris Date: Wed Dec 24 14:51:36 2025 +0000 Fix non-LOOKUP_MODULE_DIR build Broken-by: b3881e820e87 diff --git a/src/src/drtables.c b/src/src/drtables.c index 20034e7ce..21c9feb60 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -214,9 +214,11 @@ return TRUE; const lookup_info * lookup_find(const uschar * name, uschar ** errstr) { +#ifdef LOOKUP_MODULE_DIR const lookup_info * li; if ((li = lookup_findonly(name))) return li; lookup_mod_load(name, errstr); +#endif return lookup_findonly(name); }