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;