ALT Linux Bugzilla
– Attachment 13372 Details for
Bug 46381
Обновление исходников до версии 4.96
New bug
|
Search
|
[?]
|
Help
Register
|
Log In
[x]
|
Forgot Password
Login:
[x]
|
EN
|
RU
[patch]
4.94.2 -> 4.96
exim-update.patch (text/plain), 1.45 MB, created by
Danil Nazarov
on 2023-06-04 16:46:45 MSK
(
hide
)
Description:
4.94.2 -> 4.96
Filename:
MIME Type:
Creator:
Danil Nazarov
Created:
2023-06-04 16:46:45 MSK
Size:
1.45 MB
patch
obsolete
>ТолÑко в exim: doc >diff -ur exim.orig/exim_monitor/em_globals.c exim/exim_monitor/em_globals.c >--- exim.orig/exim_monitor/em_globals.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/exim_monitor/em_globals.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2018 */ >+/* Copyright (c) The Exim Maintainers 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -81,7 +82,7 @@ > int queue_update = 60; > int queue_width = 600; > >-pcre *yyyymmdd_regex; >+pcre2_code *yyyymmdd_regex; > > uschar *size_stripchart = NULL; > uschar *size_stripchart_name = NULL; >@@ -89,7 +90,7 @@ > int start_small = FALSE; > int stripchart_height = 90; > int stripchart_number = 1; >-pcre **stripchart_regex; >+pcre2_code **stripchart_regex; > uschar **stripchart_title; > int *stripchart_total; > int stripchart_update = 60; >@@ -196,6 +197,7 @@ > int received_count = 0; > uschar *received_protocol = NULL; > struct timeval received_time = { 0, 0 }; >+struct timeval received_time_complete = { 0, 0 }; > int recipients_count = 0; > recipient_item *recipients_list = NULL; > int recipients_list_max = 0; >diff -ur exim.orig/exim_monitor/em_hdr.h exim/exim_monitor/em_hdr.h >--- exim.orig/exim_monitor/em_hdr.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/exim_monitor/em_hdr.h 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2009 */ >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -85,7 +86,8 @@ > > /* Regular expression include */ > >-#include <pcre.h> >+#define PCRE2_CODE_UNIT_WIDTH 8 >+#include <pcre2.h> > > /* Includes from the main source of Exim. One of these days I should tidy up > this interface so that this kind of kludge isn't needed. */ >@@ -93,14 +95,15 @@ > #ifndef NS_MAXMSG > # define NS_MAXMSG 65535 > #endif >-typedef void hctx; >+typedef void * hctx; > > #include "local_scan.h" > #include "macros.h" > #include "structs.h" > #include "blob.h" > #include "globals.h" >-#include "dbstuff.h" >+#include "hintsdb.h" >+#include "hintsdb_structs.h" > #include "functions.h" > #include "osfunctions.h" > >@@ -273,7 +276,7 @@ > extern int queue_update; /* update interval */ > extern int queue_width; /* width of queue window */ > >-extern pcre *yyyymmdd_regex; /* for matching yyyy-mm-dd */ >+extern pcre2_code *yyyymmdd_regex; /* for matching yyyy-mm-dd */ > > extern uschar *size_stripchart; /* path for size monitoring */ > extern uschar *size_stripchart_name; /* name for size stripchart */ >@@ -282,7 +285,7 @@ > extern int start_small; /* True to start with small window */ > extern int stripchart_height; /* height of stripcharts */ > extern int stripchart_number; /* number of stripcharts */ >-extern pcre **stripchart_regex; /* vector of regexps */ >+extern pcre2_code **stripchart_regex; /* vector of regexps */ > extern uschar **stripchart_title; /* vector of titles */ > extern int *stripchart_total; /* vector of accumulating values */ > extern int stripchart_update; /* update interval */ >diff -ur exim.orig/exim_monitor/em_init.c exim/exim_monitor/em_init.c >--- exim.orig/exim_monitor/em_init.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/exim_monitor/em_init.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,7 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2009 */ >-/* Copyright (c) The Exim Maintainers 2020 */ >+/* Copyright (c) The Exim Maintainers 2020 - 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* This module contains code to initialize things from the >@@ -32,7 +32,6 @@ > work. */ > > for (i = 0; i <= 1; i++) >- > { > int first = 1; > int count = 0; >@@ -69,13 +68,18 @@ > buffer[p-pp] = 0; > if (first) > { >- int offset; >- const uschar *error; >- if (!(stripchart_regex[indx] = pcre_compile(CS buffer, PCRE_COPT, >- CCSS &error, &offset, NULL))) >+ size_t offset; >+ int err; >+ >+ if (!(stripchart_regex[indx] = >+ pcre2_compile((PCRE2_SPTR)buffer, >+ PCRE2_ZERO_TERMINATED, PCRE_COPT, >+ &err, &offset, NULL))) > { >- printf("regular expression error: %s at offset %d " >- "while compiling %s\n", error, offset, buffer); >+ uschar errbuf[128]; >+ pcre2_get_error_message(err, errbuf, sizeof(errbuf)); >+ printf("regular expression error: %s at offset %ld " >+ "while compiling %s\n", errbuf, (long)offset, buffer); > exit(99); > } > } >@@ -95,7 +99,7 @@ > if (i == 0) > { > stripchart_number += count; >- stripchart_regex = (pcre **)store_malloc(stripchart_number * sizeof(pcre *)); >+ stripchart_regex = (pcre2_code **)store_malloc(stripchart_number * sizeof(pcre2_code *)); > stripchart_title = (uschar **)store_malloc(stripchart_number * sizeof(uschar *)); > } > } >@@ -109,40 +113,36 @@ > void init(int argc, uschar **argv) > { > int x; >-int erroroffset; >+size_t erroroffset; > uschar *s; >-const uschar *error; > > argc = argc; /* These are currently unused. */ > argv = argv; > > /* Deal with simple values in the environment. */ > >-s = US getenv("ACTION_OUTPUT"); >-if (s != NULL) >+if ((s = US getenv("ACTION_OUTPUT"))) > { > if (Ustrcmp(s, "no") == 0) action_output = FALSE; > if (Ustrcmp(s, "yes") == 0) action_output = TRUE; > } > >-s = US getenv("ACTION_QUEUE_UPDATE"); >-if (s != NULL) >+if ((s = US getenv("ACTION_QUEUE_UPDATE"))) > { > if (Ustrcmp(s, "no") == 0) action_queue_update = FALSE; > if (Ustrcmp(s, "yes") == 0) action_queue_update = TRUE; > } > > s = US getenv("BODY_MAX"); >-if (s != NULL && (x = Uatoi(s)) != 0) body_max = x; >+if (s && (x = Uatoi(s)) != 0) body_max = x; > >-s = US getenv("EXIM_PATH"); >-if (s != NULL) exim_path = string_copy(s); >+if ((s = US getenv("EXIM_PATH"))) >+ exim_path = string_copy(s); > >-s = US getenv("EXIMON_EXIM_CONFIG"); >-if (s != NULL) alternate_config = string_copy(s); >+if ((s = US getenv("EXIMON_EXIM_CONFIG"))) >+ alternate_config = string_copy(s); > >-s = US getenv("LOG_BUFFER"); >-if (s != NULL) >+if ((s = US getenv("LOG_BUFFER"))) > { > uschar c[1]; > if (sscanf(CS s, "%d%c", &x, c) > 0) >@@ -154,63 +154,64 @@ > } > > s = US getenv("LOG_DEPTH"); >-if (s != NULL && (x = Uatoi(s)) != 0) log_depth = x; >+if (s && (x = Uatoi(s)) != 0) log_depth = x; > >-s = US getenv("LOG_FILE_NAME"); >-if (s != NULL) log_file = string_copy(s); >+if ((s = US getenv("LOG_FILE_NAME"))) >+ log_file = string_copy(s); > >-s = US getenv("LOG_FONT"); >-if (s != NULL) log_font = string_copy(s); >+if ((s = US getenv("LOG_FONT"))) >+ log_font = string_copy(s); > > s = US getenv("LOG_WIDTH"); >-if (s != NULL && (x = Uatoi(s)) != 0) log_width = x; >+if (s && (x = Uatoi(s)) != 0) log_width = x; > >-s = US getenv("MENU_EVENT"); >-if (s != NULL) menu_event = string_copy(s); >+if ((s = US getenv("MENU_EVENT"))) >+ menu_event = string_copy(s); > > s = US getenv("MIN_HEIGHT"); >-if (s != NULL && (x = Uatoi(s)) > 0) min_height = x; >+if (s && (x = Uatoi(s)) > 0) min_height = x; > > s = US getenv("MIN_WIDTH"); >-if (s != NULL && (x = Uatoi(s)) > 0) min_width = x; >+if (s && (x = Uatoi(s)) > 0) min_width = x; > >-s = US getenv("QUALIFY_DOMAIN"); >-if (s != NULL) qualify_domain = string_copy(s); >- else qualify_domain = US""; /* Don't want NULL */ >+if ((s = US getenv("QUALIFY_DOMAIN"))) >+ qualify_domain = string_copy(s); >+else >+ qualify_domain = US""; /* Don't want NULL */ > > s = US getenv("QUEUE_DEPTH"); >-if (s != NULL && (x = Uatoi(s)) != 0) queue_depth = x; >+if (s && (x = Uatoi(s)) != 0) queue_depth = x; > >-s = US getenv("QUEUE_FONT"); >-if (s != NULL) queue_font = string_copy(s); >+if ((s = US getenv("QUEUE_FONT"))) >+ queue_font = string_copy(s); > > s = US getenv("QUEUE_INTERVAL"); >-if (s != NULL && (x = Uatoi(s)) != 0) queue_update = x; >+if (s && (x = Uatoi(s)) != 0) queue_update = x; > > s = US getenv("QUEUE_MAX_ADDRESSES"); >-if (s != NULL && (x = Uatoi(s)) != 0) queue_max_addresses = x; >+if (s && (x = Uatoi(s)) != 0) queue_max_addresses = x; > > s = US getenv("QUEUE_WIDTH"); >-if (s != NULL && (x = Uatoi(s)) != 0) queue_width = x; >+if (s && (x = Uatoi(s)) != 0) queue_width = x; > >-s = US getenv("SPOOL_DIRECTORY"); >-if (s != NULL) spool_directory = string_copy(s); >+if ((s = US getenv("SPOOL_DIRECTORY"))) >+ spool_directory = string_copy(s); > > s = US getenv("START_SMALL"); >-if (s != NULL && Ustrcmp(s, "yes") == 0) start_small = 1; >+if (s && Ustrcmp(s, "yes") == 0) start_small = 1; > > s = US getenv("TEXT_DEPTH"); >-if (s != NULL && (x = Uatoi(s)) != 0) text_depth = x; >+if (s && (x = Uatoi(s)) != 0) text_depth = x; > >-s = US getenv("WINDOW_TITLE"); >-if (s != NULL) window_title = string_copy(s); >+if ((s = US getenv("WINDOW_TITLE"))) >+ window_title = string_copy(s); > > /* Deal with stripchart configuration. First see if we are monitoring > the size of a partition, then deal with log stripcharts in a separate > function */ > > s = US getenv("SIZE_STRIPCHART"); >-if (s != NULL && *s != 0) >+if (s && *s) > { > stripchart_number++; > stripchart_varstart++; >@@ -219,19 +220,19 @@ > if (s != NULL && *s != 0) size_stripchart_name = string_copy(s); > } > >-s = US getenv("LOG_STRIPCHARTS"); >-if (s != NULL) decode_stripchart_config(s); >+if ((s = US getenv("LOG_STRIPCHARTS"))) >+ decode_stripchart_config(s); > > s = US getenv("STRIPCHART_INTERVAL"); >-if (s != NULL && (x = Uatoi(s)) != 0) stripchart_update = x; >+if (s && (x = Uatoi(s)) != 0) stripchart_update = x; > > s = US getenv("QUEUE_STRIPCHART_NAME"); >-queue_stripchart_name = (s != NULL)? string_copy(s) : US"queue"; >+queue_stripchart_name = s ? string_copy(s) : US"queue"; > > /* Compile the regex for matching yyyy-mm-dd at the start of a string. */ > >-yyyymmdd_regex = pcre_compile("^\\d{4}-\\d\\d-\\d\\d\\s", PCRE_COPT, >- CCSS &error, &erroroffset, NULL); >+yyyymmdd_regex = pcre2_compile((PCRE2_SPTR)"^\\d{4}-\\d\\d-\\d\\d\\s", >+ PCRE2_ZERO_TERMINATED, PCRE_COPT, &x, &erroroffset, NULL); > } > > /* End of em_init.c */ >diff -ur exim.orig/exim_monitor/em_log.c exim/exim_monitor/em_log.c >--- exim.orig/exim_monitor/em_log.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/exim_monitor/em_log.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2018 */ >+/* Copyright (c) The Exim Maintainters 2021 - 2022 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* This module contains code for scanning the main log, >@@ -229,7 +230,7 @@ > uschar *p = buffer; > rmark reset_point; > int length = Ustrlen(buffer); >- int i; >+ pcre2_match_data * md = pcre2_match_data_create(1, NULL); > > /* Skip totally blank lines (paranoia: there shouldn't be any) */ > >@@ -246,27 +247,25 @@ > stripchart is the queue length, which is handled elsewhere, and the > 1st may the a size monitor. */ > >- for (i = stripchart_varstart; i < stripchart_number; i++) >- { >- if (pcre_exec(stripchart_regex[i], NULL, CS buffer, length, 0, PCRE_EOPT, >- NULL, 0) >= 0) >+ for (int i = stripchart_varstart; i < stripchart_number; i++) >+ if (pcre2_match(stripchart_regex[i], (PCRE2_SPTR)buffer, length, >+ 0, PCRE_EOPT, md, NULL) >= 0) > stripchart_total[i]++; >- } > > /* Munge the log entry and display shortened form on one line. > We omit the date and show only the time. Remove any time zone offset. > Take note of the presence of [pid]. */ > >- if (pcre_exec(yyyymmdd_regex,NULL,CS buffer,length,0,PCRE_EOPT,NULL,0) >= 0) >+ if (pcre2_match(yyyymmdd_regex, (PCRE2_SPTR) buffer, length, 0, PCRE_EOPT, >+ md, NULL) >= 0) > { > int pidlength = 0; >- if ((buffer[20] == '+' || buffer[20] == '-') && >- isdigit(buffer[21]) && buffer[25] == ' ') >+ if ( (buffer[20] == '+' || buffer[20] == '-') >+ && isdigit(buffer[21]) && buffer[25] == ' ') > memmove(buffer + 20, buffer + 26, Ustrlen(buffer + 26) + 1); > if (buffer[20] == '[') >- { >- while (Ustrchr("[]0123456789", buffer[20+pidlength++]) != NULL); >- } >+ while (Ustrchr("[]0123456789", buffer[20+pidlength++]) != NULL) >+ ; > id = string_copyn(buffer + 20 + pidlength, MESSAGE_ID_LENGTH); > show_log("%s", buffer+11); > } >@@ -275,6 +274,7 @@ > id = US""; > show_log("%s", buffer); > } >+ pcre2_match_data_free(md); > > /* Deal with frozen and unfrozen messages */ > >@@ -292,7 +292,7 @@ > if ((p = Ustrstr(buffer, "==")) != NULL) > { > queue_item *qq = find_queue(id, queue_noop, 0); >- if (qq != NULL) >+ if (qq) > { > dest_item *d; > uschar *q, *r; >diff -ur exim.orig/exim_monitor/em_main.c exim/exim_monitor/em_main.c >--- exim.orig/exim_monitor/em_main.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/exim_monitor/em_main.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2018 */ >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -173,8 +174,6 @@ > vfprintf(stderr, format, ap); > fprintf(stderr, "\n"); > va_end(ap); >-selector = selector; /* Keep picky compilers happy */ >-flags = flags; > } > > >@@ -198,19 +197,47 @@ > */ > > int >-host_address_extract_port(uschar *address) >+host_address_extract_port(uschar * address) > { >-int skip = -3; /* Skip 3 dots in IPv4 addresses */ >-address--; >-while (*(++address) != 0) >- { >- int ch = *address; >- if (ch == ':') skip = 0; /* Skip 0 dots in IPv6 addresses */ >- else if (ch == '.' && skip++ >= 0) break; >- } >-if (*address == 0) return 0; >-*address++ = 0; >-return Uatoi(address); >+int port = 0; >+uschar *endptr; >+ >+/* Handle the "bracketed with colon on the end" format */ >+ >+if (*address == '[') >+ { >+ uschar *rb = address + 1; >+ while (*rb != 0 && *rb != ']') rb++; >+ if (*rb++ == 0) return 0; /* Missing ]; leave invalid address */ >+ if (*rb == ':') >+ { >+ port = Ustrtol(rb + 1, &endptr, 10); >+ if (*endptr != 0) return 0; /* Invalid port; leave invalid address */ >+ } >+ else if (*rb != 0) return 0; /* Bad syntax; leave invalid address */ >+ memmove(address, address + 1, rb - address - 2); >+ rb[-2] = 0; >+ } >+ >+/* Handle the "dot on the end" format */ >+ >+else >+ { >+ int skip = -3; /* Skip 3 dots in IPv4 addresses */ >+ address--; >+ while (*(++address) != 0) >+ { >+ int ch = *address; >+ if (ch == ':') skip = 0; /* Skip 0 dots in IPv6 addresses */ >+ else if (ch == '.' && skip++ >= 0) break; >+ } >+ if (*address == 0) return 0; >+ port = Ustrtol(address + 1, &endptr, 10); >+ if (*endptr != 0) return 0; /* Invalid port; leave invalid address */ >+ *address = 0; >+ } >+ >+return port; > } > > >@@ -240,9 +267,6 @@ > > void updateAction(Widget w, XtPointer client_data, XtPointer call_data) > { >-w = w; /* Keep picky compilers happy */ >-client_data = client_data; >-call_data = call_data; > scan_spool_input(TRUE); > queue_display(); > tick_queue_accumulator = 0; >@@ -250,9 +274,6 @@ > > void hideAction(Widget w, XtPointer client_data, XtPointer call_data) > { >-w = w; /* Keep picky compilers happy */ >-client_data = client_data; >-call_data = call_data; > actioned_message[0] = 0; > dialog_ref_widget = w; > dialog_action = da_hide; >@@ -263,11 +284,7 @@ > { > skip_item *sk = queue_skip; > >-w = w; /* Keep picky compilers happy */ >-client_data = client_data; >-call_data = call_data; >- >-while (sk != NULL) >+while (sk) > { > skip_item *next = sk->next; > store_free(sk); >@@ -285,9 +302,6 @@ > > void quitAction(Widget w, XtPointer client_data, XtPointer call_data) > { >-w = w; /* Keep picky compilers happy */ >-client_data = client_data; >-call_data = call_data; > exit(0); > } > >@@ -318,10 +332,6 @@ > XWindowAttributes a; > Window w = XtWindow(toplevel_widget); > >-button = button; /* Keep picky compilers happy */ >-client_data = client_data; >-call_data = call_data; >- > /* Get the position and size of the top level widget. */ > > sizepos_args[0].value = (XtArgVal)(&width); >@@ -473,9 +483,6 @@ > tick_stripchart_accumulator += tick_interval; > read_log(); > >-pt = pt; /* Keep picky compilers happy */ >-i = i; >- > /* If we have passed the queue update time, we must do a full > scan of the queue, checking for new arrivals, etc. This will > as a by-product set the count of items for use by the stripchart >@@ -592,7 +599,8 @@ > * Initialize * > *************************************************/ > >-int main(int argc, char **argv) >+int >+main(int argc, char **argv) > { > int i; > struct stat statdata; >@@ -613,7 +621,8 @@ > constructing file names and things. This call will initialize > the store_get() function. */ > >-big_buffer = store_get(big_buffer_size, FALSE); >+store_init(); >+big_buffer = store_get(big_buffer_size, GET_UNTAINTED); > > /* Set up the version string and date and output them */ > >diff -ur exim.orig/exim_monitor/em_menu.c exim/exim_monitor/em_menu.c >--- exim.orig/exim_monitor/em_menu.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/exim_monitor/em_menu.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2018 */ >+/* Copyright (c) The Exim Maintainers 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -115,10 +116,9 @@ > * Destroy the menu when popped down * > *************************************************/ > >-static void popdownAction(Widget w, XtPointer client_data, XtPointer call_data) >+static void >+popdownAction(Widget w, XtPointer client_data, XtPointer call_data) > { >-client_data = client_data; /* Keep picky compilers happy */ >-call_data = call_data; > if (highlighted_x >= 0) > XawTextSinkDisplayText(queue_text_sink, > highlighted_x, highlighted_y, >@@ -136,17 +136,13 @@ > static void > msglogAction(Widget w, XtPointer client_data, XtPointer call_data) > { >-int i; > Widget text = text_create(US client_data, text_depth); > uschar * fname = NULL; > FILE * f = NULL; > >-w = w; /* Keep picky compilers happy */ >-call_data = call_data; >- > /* End up with the split version, so message looks right when non-exist */ > >-for (i = 0; i < (spool_is_split ? 2:1); i++) >+for (int i = 0; i < (spool_is_split ? 2:1); i++) > { > message_subdir[0] = i != 0 ? (US client_data)[5] : 0; > fname = spool_fname(US"msglog", message_subdir, US client_data, US""); >@@ -173,14 +169,10 @@ > static void > bodyAction(Widget w, XtPointer client_data, XtPointer call_data) > { >-int i; > Widget text = text_create(US client_data, text_depth); > FILE *f = NULL; > >-w = w; /* Keep picky compilers happy */ >-call_data = call_data; >- >-for (i = 0; i < (spool_is_split? 2:1); i++) >+for (int i = 0; i < (spool_is_split? 2:1); i++) > { > uschar * fname; > message_subdir[0] = i != 0 ? (US client_data)[5] : 0; >@@ -189,7 +181,7 @@ > break; > } > >-if (f == NULL) >+if (!f) > text_showf(text, "Failed to open file: %s\n", strerror(errno)); > else > { >@@ -221,7 +213,8 @@ > the command whether we want the output or not, so the pipe has to be set up in > all cases. */ > >-static void ActOnMessage(uschar *id, uschar *action, uschar *address_arg) >+static void >+ActOnMessage(uschar *id, uschar *action, uschar *address_arg) > { > int pid; > int pipe_fd[2]; >@@ -407,41 +400,32 @@ > * Cause a message to be delivered * > *************************************************/ > >-static void deliverAction(Widget w, XtPointer client_data, XtPointer call_data) >+static void >+deliverAction(Widget w, XtPointer client_data, XtPointer call_data) > { >-w = w; /* Keep picky compilers happy */ >-call_data = call_data; > ActOnMessage(US client_data, US"-v -M", US""); > } > >- >- > /************************************************* > * Cause a message to be Frozen * > *************************************************/ > >-static void freezeAction(Widget w, XtPointer client_data, XtPointer call_data) >+static void >+freezeAction(Widget w, XtPointer client_data, XtPointer call_data) > { >-w = w; /* Keep picky compilers happy */ >-call_data = call_data; > ActOnMessage(US client_data, US"-Mf", US""); > } > >- >- > /************************************************* > * Cause a message to be thawed * > *************************************************/ > >-static void thawAction(Widget w, XtPointer client_data, XtPointer call_data) >+static void >+thawAction(Widget w, XtPointer client_data, XtPointer call_data) > { >-w = w; /* Keep picky compilers happy */ >-call_data = call_data; > ActOnMessage(US client_data, US"-Mt", US""); > } > >- >- > /************************************************* > * Take action using dialog data * > *************************************************/ >@@ -450,25 +434,19 @@ > in. It is global because it is set up in the action table at > start-up time. If the string is empty, do nothing. */ > >-XtActionProc dialogAction(Widget w, XEvent *event, String *ss, Cardinal *c) >+XtActionProc >+dialogAction(Widget w, XEvent *event, String *ss, Cardinal *c) > { > uschar *s = US XawDialogGetValueString(dialog_widget); > >-w = w; /* Keep picky compilers happy */ >-event = event; >-ss = ss; >-c = c; >- > XtPopdown((Widget)dialog_shell); > XtDestroyWidget((Widget)dialog_shell); > while (isspace(*s)) s++; > if (s[0] != 0) >- { > if (actioned_message[0] != 0) > ActOnMessage(actioned_message, action_required, s); > else > NonMessageDialogue(s); /* When called from somewhere else */ >- } > return NULL; > } > >@@ -482,7 +460,8 @@ > be done to the application until the box is filled in. This > function is also used by the Hide button handler. */ > >-void create_dialog(uschar *label, uschar *value) >+void >+create_dialog(uschar *label, uschar *value) > { > Arg warg[4]; > Dimension x, y, xx, yy; >@@ -544,9 +523,6 @@ > } > > >- >- >- > /************************************************* > * Cause a recipient to be added * > *************************************************/ >@@ -554,10 +530,9 @@ > /* This just sets up the dialog box; the action happens when it has been filled > in. */ > >-static void addrecipAction(Widget w, XtPointer client_data, XtPointer call_data) >+static void >+addrecipAction(Widget w, XtPointer client_data, XtPointer call_data) > { >-w = w; /* Keep picky compilers happy */ >-call_data = call_data; > Ustrncpy(actioned_message, client_data, 24); > actioned_message[23] = '\0'; > action_required = US"-Mar"; >@@ -565,16 +540,13 @@ > create_dialog(US"Recipient address to add?", US""); > } > >- >- > /************************************************* > * Cause an address to be marked delivered * > *************************************************/ > >-static void markdelAction(Widget w, XtPointer client_data, XtPointer call_data) >+static void >+markdelAction(Widget w, XtPointer client_data, XtPointer call_data) > { >-w = w; /* Keep picky compilers happy */ >-call_data = call_data; > Ustrncpy(actioned_message, client_data, 24); > actioned_message[23] = '\0'; > action_required = US"-Mmd"; >@@ -582,30 +554,26 @@ > create_dialog(US"Recipient address to mark delivered?", US""); > } > >- > /************************************************* > * Cause all addresses to be marked delivered * > *************************************************/ > >-static void markalldelAction(Widget w, XtPointer client_data, XtPointer call_data) >+static void >+markalldelAction(Widget w, XtPointer client_data, XtPointer call_data) > { >-w = w; /* Keep picky compilers happy */ >-call_data = call_data; > ActOnMessage(US client_data, US"-Mmad", US""); > } > >- > /************************************************* > * Edit the message's sender * > *************************************************/ > >-static void editsenderAction(Widget w, XtPointer client_data, >- XtPointer call_data) >+static void >+editsenderAction(Widget w, XtPointer client_data, XtPointer call_data) > { > queue_item *q; > uschar *sender; >-w = w; /* Keep picky compilers happy */ >-call_data = call_data; >+ > Ustrncpy(actioned_message, client_data, 24); > actioned_message[23] = '\0'; > q = find_queue(actioned_message, queue_noop, 0); >@@ -615,47 +583,37 @@ > create_dialog(US"New sender address?", sender); > } > >- > /************************************************* > * Cause a message to be returned to sender * > *************************************************/ > >-static void giveupAction(Widget w, XtPointer client_data, XtPointer call_data) >+static void >+giveupAction(Widget w, XtPointer client_data, XtPointer call_data) > { >-w = w; /* Keep picky compilers happy */ >-call_data = call_data; > ActOnMessage(US client_data, US"-v -Mg", US""); > } > >- >- > /************************************************* > * Cause a message to be cancelled * > *************************************************/ > >-static void removeAction(Widget w, XtPointer client_data, XtPointer call_data) >+static void >+removeAction(Widget w, XtPointer client_data, XtPointer call_data) > { >-w = w; /* Keep picky compilers happy */ >-call_data = call_data; > ActOnMessage(US client_data, US"-Mrm", US""); > } > >- >- > /************************************************* > * Display a message's headers * > *************************************************/ > >-static void headersAction(Widget w, XtPointer client_data, XtPointer call_data) >+static void >+headersAction(Widget w, XtPointer client_data, XtPointer call_data) > { > uschar buffer[256]; >-header_line *h, *next; > Widget text = text_create(US client_data, text_depth); > rmark reset_point; > >-w = w; /* Keep picky compilers happy */ >-call_data = call_data; >- > /* Remember the point in the dynamic store so we can recover to it afterwards. > Then use Exim's function to read the header. */ > >@@ -678,26 +636,22 @@ > return; > } > >-if (sender_address != NULL) >- { >+if (sender_address) > text_showf(text, "%s sender: <%s>\n", f.sender_local ? "Local" : "Remote", > sender_address); >- } > >-if (recipients_list != NULL) >+if (recipients_list) > { >- int i; > text_show(text, US"Recipients:\n"); >- for (i = 0; i < recipients_count; i++) >- { >+ for (int i = 0; i < recipients_count; i++) > text_showf(text, " %s %s\n", >- (tree_search(tree_nonrecipients, recipients_list[i].address) == NULL)? >- " ":"*", recipients_list[i].address); >- } >+ tree_search(tree_nonrecipients, recipients_list[i].address) >+ ? "*" : " ", >+ recipients_list[i].address); > text_show(text, US"\n"); > } > >-for (h = header_list; h != NULL; h = next) >+for (header_line * next, * h = header_list; h; h = next) > { > next = h->next; > text_showf(text, "%c ", h->type); /* Don't push h->text through a %s */ >@@ -707,20 +661,13 @@ > store_reset(reset_point); > } > >- >- >- > /************************************************* > * Dismiss a text window * > *************************************************/ > >-static void dismissAction(Widget w, XtPointer client_data, XtPointer call_data) >+static void >+dismissAction(Widget w, XtPointer client_data, XtPointer call_data) > { >-pipe_item *p = pipe_chain; >- >-w = w; /* Keep picky compilers happy */ >-call_data = call_data; >- > XtPopdown((Widget)client_data); > XtDestroyWidget((Widget)client_data); > >@@ -729,16 +676,9 @@ > to search the parents of the saved widget to see if one of them > is what we have just destroyed. */ > >-while (p != NULL) >- { >- Widget pp = p->widget; >- while (pp != NULL) >- { >+for (pipe_item * p = pipe_chain; p; p = p->next) >+ for (Widget pp = p->widget; pp; pp = XtParent(pp)) > if (pp == (Widget)client_data) { p->widget = NULL; return; } >- pp = XtParent(pp); >- } >- p = p->next; >- } > } > > >@@ -747,7 +687,8 @@ > * Set up popup text window * > *************************************************/ > >-static Widget text_create(uschar *name, int height) >+static Widget >+text_create(uschar *name, int height) > { > Widget textshell, form, text, button; > >@@ -798,9 +739,6 @@ > return text; > } > >- >- >- > /************************************************* > * Set up menu in queue window * > *************************************************/ >@@ -808,7 +746,8 @@ > /* We have added an action table that causes this function to > be called, and set up button 2 in the text widgets to call it. */ > >-void menu_create(Widget w, XEvent *event, String *actargs, Cardinal *count) >+void >+menu_create(Widget w, XEvent *event, String *actargs, Cardinal *count) > { > int line; > int i; >@@ -824,9 +763,6 @@ > <BtnUp>: MenuPopdown()notify()unhighlight()\n\ > "); > >-actargs = actargs; /* Keep picky compilers happy */ >-count = count; >- > /* Get the sink and source and the current text pointer */ > > queue_get_arg[0].value = (XtArgVal)(&queue_text_sink); >diff -ur exim.orig/exim_monitor/em_queue.c exim/exim_monitor/em_queue.c >--- exim.orig/exim_monitor/em_queue.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/exim_monitor/em_queue.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,7 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -138,8 +138,8 @@ > acl_var_create(uschar *name) > { > tree_node *node, **root; >-root = (name[0] == 'c')? &acl_var_c : &acl_var_m; >-node = store_get(sizeof(tree_node) + Ustrlen(name), FALSE); >+root = name[0] == 'c' ? &acl_var_c : &acl_var_m; >+node = store_get(sizeof(tree_node) + Ustrlen(name), GET_UNTAINTED); > Ustrcpy(node->name, name); > node->data.ptr = NULL; > (void)tree_insertnode(root, node); >diff -ur exim.orig/exim_monitor/em_TextPop.c exim/exim_monitor/em_TextPop.c >--- exim.orig/exim_monitor/em_TextPop.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/exim_monitor/em_TextPop.c 2022-06-23 16:41:10.000000000 +0300 >@@ -1,4 +1,5 @@ > /*********************************************************** >+Copyright (c) The Exim Maintainers 2022 > Copyright 1989 by the Massachusetts Institute of Technology, > Cambridge, Massachusetts. > >@@ -246,7 +247,7 @@ > Arg args[1]; > > #ifdef notdef >- if (ctx->text.source->Search == NULL) { >+ if (!ctx->text.source->Search) { > XBell(XtDisplay(w), 0); > return; > } >@@ -279,16 +280,15 @@ > return; > } > >- if (ctx->text.search== NULL) { >+ if (!ctx->text.search) { > ctx->text.search = XtNew(struct SearchAndReplace); > ctx->text.search->search_popup = CreateDialog(w, ptr, "search", > AddSearchChildren); > XtRealizeWidget(ctx->text.search->search_popup); > SetWMProtocolTranslations(ctx->text.search->search_popup); > } >- else if (*num_params > 1) { >+ else if (*num_params > 1) > XtVaSetValues(ctx->text.search->search_text, XtNstring, ptr, NULL); >- } > > XtSetArg(args[0], XtNeditType,&edit_mode); > XtGetValues(ctx->text.source, args, ONE); >diff -ur exim.orig/exim_monitor/em_version.c exim/exim_monitor/em_version.c >--- exim.orig/exim_monitor/em_version.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/exim_monitor/em_version.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,7 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ >+/* Copyright (c) The Exim Maintainers 2020 - 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #define EM_VERSION_C >ТолÑко в exim: Local >diff -ur exim.orig/Makefile exim/Makefile >--- exim.orig/Makefile 2023-06-04 16:22:05.000000000 +0300 >+++ exim/Makefile 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > # appropriate links, and then creating and running the main makefile in that > # directory. > >+# Copyright (c) The Exim Maintainers 2022 > # Copyright (c) University of Cambridge, 1995 - 2018 > # See the file NOTICE for conditions of use and distribution. > >@@ -102,6 +103,7 @@ > cscope.files: FRC > echo "-q" > $@ > 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 "os.[ch]*" -print \ > -o -name "*akefile*" -print \ >diff -ur exim.orig/OS/Makefile-Base exim/OS/Makefile-Base >--- exim.orig/OS/Makefile-Base 2023-06-04 16:22:05.000000000 +0300 >+++ exim/OS/Makefile-Base 2022-06-23 16:41:10.000000000 +0300 >@@ -5,8 +5,7 @@ > # optional, Local/* files at the front of this file, to create Makefile in the > # build directory. > # >-# Copyright (c) The Exim Maintainers 1995 - 2018 >-# Copyright (c) The Exim Maintainers 2020 >+# Copyright (c) The Exim Maintainers 1995 - 2022 > > SHELL = $(MAKE_SHELL) > SCRIPTS = ../scripts >@@ -81,7 +80,6 @@ > # Build (link) the os.h file > > os.h: $(SCRIPTS)/Configure-os.h \ >- $(O)/os.h-Darwin \ > $(O)/os.h-FreeBSD \ > $(O)/os.h-GNU \ > $(O)/os.h-Linux \ >@@ -113,7 +111,7 @@ > > OBJ_MACRO = macro_predef.o \ > macro-globals.o macro-readconf.o macro-route.o macro-transport.o macro-drtables.o \ >- macro-tls.o \ >+ macro-acl.o macro-tls.o \ > macro-appendfile.o macro-autoreply.o macro-lmtp.o macro-pipe.o macro-queuefile.o \ > macro-smtp.o macro-accept.o macro-dnslookup.o macro-ipliteral.o macro-iplookup.o \ > macro-manualroute.o macro-queryprogram.o macro-redirect.o \ >@@ -141,6 +139,9 @@ > macro-drtables.o : drtables.c > @echo "$(CC) -DMACRO_PREDEF drtables.c" > $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ drtables.c >+macro-acl.o: acl.c >+ @echo "$(CC) -DMACRO_PREDEF acl.c" >+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ acl.c > macro-tls.o: tls.c tls-gnu.c tls-openssl.c > @echo "$(CC) -DMACRO_PREDEF tls.c" > $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ tls.c >@@ -474,7 +475,6 @@ > dmarc.o \ > imap_utf7.o \ > spf.o \ >- srs.o \ > utf8.o > > # Targets for final binaries; the main one has a build number which is >@@ -484,7 +484,7 @@ > > OBJ_EXIM = acl.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 filter.o \ >- filtertest.o globals.o dkim.o dkim_transport.o hash.o \ >+ filtertest.o globals.o dkim.o dkim_transport.o dnsbl.o hash.o \ > header.o host.o ip.o log.o lss.o match.o md5.o moan.o \ > os.o parse.o priv.o queue.o \ > rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o \ >@@ -635,11 +635,12 @@ > HDRS = blob.h \ > config.h \ > dbfunctions.h \ >- dbstuff.h \ > exim.h \ > functions.h \ > globals.h \ > hash.h \ >+ hintsdb.h \ >+ hintsdb_structs.h \ > local_scan.h \ > macros.h \ > mytypes.h \ >@@ -648,10 +649,11 @@ > os.h > PHDRS = ../config.h \ > ../dbfunctions.h \ >- ../dbstuff.h \ > ../exim.h \ > ../functions.h \ > ../globals.h \ >+ ../hintsdb.h \ >+ ../hintsdb_structs.h \ > ../local_scan.h \ > ../macros.h \ > ../mytypes.h \ >@@ -774,6 +776,7 @@ > deliver.o: $(HDRS) transports/smtp.h deliver.c > directory.o: $(HDRS) directory.c > dns.o: $(HDRS) dns.c >+dnsbl.o: $(HDRS) dnsbl.c > enq.o: $(HDRS) enq.c > exim.o: $(HDRS) exim.c > expand.o: $(HDRS) expand.c >@@ -839,7 +842,6 @@ > dmarc.o: $(HDRS) pdkim/pdkim.h dmarc.h dmarc.c > imap_utf7.o: $(HDRS) imap_utf7.c > spf.o: $(HDRS) spf.h spf.c >-srs.o: $(HDRS) srs.h srs.c > utf8.o: $(HDRS) utf8.c > > # The module containing tables of available lookups, routers, auths, and >ТолÑко в exim.orig/OS: Makefile-Darwin >diff -ur exim.orig/OS/Makefile-Default exim/OS/Makefile-Default >--- exim.orig/OS/Makefile-Default 2023-06-04 16:22:05.000000000 +0300 >+++ exim/OS/Makefile-Default 2022-06-23 16:41:10.000000000 +0300 >@@ -89,7 +89,7 @@ > > # PCRE_LIBS contains the library to be linked for PCRE > >-PCRE_LIBS=-lpcre >+PCRE_LIBS=-lpcre2-8 > > > # LIBS and EXTRALIBS contain library settings that are used on linking >diff -ur exim.orig/OS/Makefile-FreeBSD exim/OS/Makefile-FreeBSD >--- exim.orig/OS/Makefile-FreeBSD 2023-06-04 16:22:05.000000000 +0300 >+++ exim/OS/Makefile-FreeBSD 2022-06-23 16:41:10.000000000 +0300 >@@ -2,7 +2,7 @@ > # Copyright (c) The Exim Maintainers 2020 > > CHOWN_COMMAND=/usr/sbin/chown >-STRIP_COMMAND=/usr/bin/strip >+#STRIP_COMMAND=/usr/bin/strip > CHMOD_COMMAND=/bin/chmod > > >diff -ur exim.orig/OS/Makefile-OpenBSD exim/OS/Makefile-OpenBSD >--- exim.orig/OS/Makefile-OpenBSD 2023-06-04 16:22:05.000000000 +0300 >+++ exim/OS/Makefile-OpenBSD 2022-06-23 16:41:10.000000000 +0300 >@@ -1,5 +1,5 @@ > # Exim: OS-specific make file for OpenBSD >-# Copyright (c) The Exim Maintainers 2020 >+# Copyright (c) The Exim Maintainers 2022 > > CHOWN_COMMAND=/usr/sbin/chown > CHGRP_COMMAND=/usr/sbin/chgrp >@@ -24,7 +24,7 @@ > > HAVE_IPV6=YES > >-# OpenBSD always ships with Berkeley DB >-USE_DB=yes >+# OpenBSD ships with a too-old Berkeley DB. NDBM is the default if we don't specify one. >+#USE_DB=yes > > # End >diff -ur exim.orig/OS/os.c-FreeBSD exim/OS/os.c-FreeBSD >--- exim.orig/OS/os.c-FreeBSD 2023-06-04 16:22:05.000000000 +0300 >+++ exim/OS/os.c-FreeBSD 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > > /* Copyright (c) Jeremy Harris 1995 - 2020 */ >+/* Copyright (c) The Exim Maintainers 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* FreeBSD-specific code. This is concatenated onto the generic >@@ -16,10 +17,11 @@ > ssize_t > os_sendfile(int out, int in, off_t * offp, size_t cnt) > { >-off_t loff = *offp, written; >+off_t loff = offp ? *offp : 0; >+off_t written; > > if (sendfile(in, out, loff, cnt, NULL, &written, 0) < 0) return (ssize_t)-1; >-*offp = loff + written; >+if (offp) *offp = loff + written; > return (ssize_t)written; > } > >ТолÑко в exim.orig/OS: os.h-Darwin >diff -ur exim.orig/OS/os.h-FreeBSD exim/OS/os.h-FreeBSD >--- exim.orig/OS/os.h-FreeBSD 2023-06-04 16:22:05.000000000 +0300 >+++ exim/OS/os.h-FreeBSD 2022-06-23 16:41:10.000000000 +0300 >@@ -1,6 +1,6 @@ > /* Exim: OS-specific C header file for FreeBSD */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ >+/* Copyright (c) The Exim Maintainers 2020 - 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -11,9 +11,12 @@ > #define HAVE_SETCLASSRESOURCES > #define HAVE_MMAP > #define HAVE_SYS_MOUNT_H >+#define HAVE_GETIFADDRS > #define SIOCGIFCONF_GIVES_ADDR > #define HAVE_SRANDOMDEV > #define HAVE_ARC4RANDOM >+#define EXIM_HAVE_OPENAT >+#define EXIM_HAVE_FUTIMENS > > /* Applications should not call arc4random_stir() explicitly after > * FreeBSD r227520 (approximately 1000002). >@@ -55,6 +58,11 @@ > #define OS_SENDFILE > extern ssize_t os_sendfile(int, int, off_t *, size_t); > >+#ifdef PID_T_FMT >+# undef PID_T_FMT >+#endif >+#define PID_T_FMT "%d" >+ > > /*******************/ > >@@ -68,4 +76,6 @@ > > /*******************/ > >+#define EXIM_HAVE_KEVENT >+ > /* End */ >diff -ur exim.orig/OS/os.h-GNU exim/OS/os.h-GNU >--- exim.orig/OS/os.h-GNU 2023-06-04 16:22:05.000000000 +0300 >+++ exim/OS/os.h-GNU 2022-06-23 16:41:10.000000000 +0300 >@@ -1,5 +1,5 @@ > /* Exim: OS-specific C header file for GNU/Hurd */ >-/* Copyright (c) The Exim Maintainers 2020 */ >+/* Copyright (c) The Exim Maintainers 2020 - 2021 */ > > #include <features.h> > >diff -ur exim.orig/OS/os.h-Linux exim/OS/os.h-Linux >--- exim.orig/OS/os.h-Linux 2023-06-04 16:22:05.000000000 +0300 >+++ exim/OS/os.h-Linux 2022-06-23 16:41:10.000000000 +0300 >@@ -1,5 +1,6 @@ > /* Exim: OS-specific C header file for Linux */ > /* Copyright (c) University of Cambridge 1995 - 2020 */ >+/* Copyright (c) The Exim Maintainers 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -16,6 +17,7 @@ > #define HAVE_MMAP > #define HAVE_BSD_GETLOADAVG > #define HAVE_SYS_STATVFS_H >+#define HAVE_GETIFADDRS > #define NO_IP_VAR_H > #define SIG_IGN_WORKS > >@@ -71,8 +73,9 @@ > # define LLONG_MAX LONG_LONG_MAX > #endif > >-#if _POSIX_C_SOURCE >= 200809L || _ATFILE_SOUCE >+#if _POSIX_C_SOURCE >= 200809L || _ATFILE_SOURCE > # define EXIM_HAVE_OPENAT >+# define EXIM_HAVE_FUTIMENS > #endif > > /* TCP Fast Open support */ >@@ -90,5 +93,12 @@ > /* "Abstract" Unix-socket names */ > #define EXIM_HAVE_ABSTRACT_UNIX_SOCKETS > >+/* inotify(7) etc syscalls */ >+#define EXIM_HAVE_INOTIFY >+ >+/* Needed for uClibc */ >+#ifndef NS_MAXMSG >+# define NS_MAXMSG 65535 >+#endif > > /* End */ >diff -ur exim.orig/OS/os.h-OpenBSD exim/OS/os.h-OpenBSD >--- exim.orig/OS/os.h-OpenBSD 2023-06-04 16:22:05.000000000 +0300 >+++ exim/OS/os.h-OpenBSD 2022-06-23 16:41:10.000000000 +0300 >@@ -1,12 +1,15 @@ > /* Exim: OS-specific C header file for OpenBSD */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >+/* Copyright (c) The Exim Maintainers 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > > #define HAVE_BSD_GETLOADAVG > #define HAVE_MMAP > #define HAVE_SYS_MOUNT_H >-#define SIOCGIFCONF_GIVES_ADDR >+#define HAVE_GETIFADDRS >+#define EXIM_HAVE_OPENAT >+#define EXIM_HAVE_FUTIMENS > #define HAVE_ARC4RANDOM > /* In May 2014, OpenBSD 5.5 was released which cleaned up the arc4random_* API > which removed the arc4random_stir() function. Set NOT_HAVE_ARC4RANDOM_STIR >@@ -57,4 +60,6 @@ > Space-constrained devices could use much smaller; a few k. */ > #define NS_MAXMSG 65535 > >+#define EXIM_HAVE_KEVENT >+ > /* End */ >diff -ur exim.orig/OS/os.h-SunOS5 exim/OS/os.h-SunOS5 >--- exim.orig/OS/os.h-SunOS5 2023-06-04 16:22:05.000000000 +0300 >+++ exim/OS/os.h-SunOS5 2022-06-23 16:41:10.000000000 +0300 >@@ -1,4 +1,5 @@ > /* Exim: OS-specific C header file for SunOS5 aka Solaris */ >+/* Copyright (c) The Exim Maintainers 2021 */ > > #define CRYPT_H > #define HAVE_MMAP >@@ -8,6 +9,8 @@ > > #define HAVE_GETIPNODEBYNAME 1 > #define HAVE_GETIPNODEBYADDR 1 >+#define EXIM_HAVE_OPENAT >+#define EXIM_HAVE_FUTIMENS > > #define HAVE_KSTAT > #define LOAD_AVG_KSTAT "system_misc" >@@ -36,6 +39,10 @@ > # define MISSING_UNSETENV_3 > #endif > >+#if _POSIX_C_SOURCE < 200809L >+# define MISSING_POSIX_MEMALIGN >+#endif >+ > > /* SunOS5 doesn't accept getcwd(NULL, 0) to auto-allocate > a buffer */ >ТолÑко в exim/OS/unsupported: Makefile-Darwin >diff -ur exim.orig/OS/unsupported/os.c-IRIX exim/OS/unsupported/os.c-IRIX >--- exim.orig/OS/unsupported/os.c-IRIX 2023-06-04 16:22:05.000000000 +0300 >+++ exim/OS/unsupported/os.c-IRIX 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2022 */ > /* Copyright (c) University of Cambridge 2001 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -59,7 +60,7 @@ > log_write(0, LOG_PANIC_DIE, "iflist-sysctl-estimate failed: %s", > strerror(errno)); > >-buf = store_get(needed, FALSE); >+buf = store_get(needed, GET_UNTAINTED); > > if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) > log_write(0, LOG_PANIC_DIE, "sysctl of ifnet list failed: %s", >diff -ur exim.orig/OS/unsupported/os.c-IRIX6 exim/OS/unsupported/os.c-IRIX6 >--- exim.orig/OS/unsupported/os.c-IRIX6 2023-06-04 16:22:05.000000000 +0300 >+++ exim/OS/unsupported/os.c-IRIX6 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2022 */ > /* Copyright (c) University of Cambridge 2001 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -59,7 +60,7 @@ > log_write(0, LOG_PANIC_DIE, "iflist-sysctl-estimate failed: %s", > strerror(errno)); > >-buf = store_get(needed, FALSE); >+buf = store_get(needed, GET_UNTAINTED); > > if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) > log_write(0, LOG_PANIC_DIE, "sysctl of ifnet list failed: %s", >diff -ur exim.orig/OS/unsupported/os.c-IRIX632 exim/OS/unsupported/os.c-IRIX632 >--- exim.orig/OS/unsupported/os.c-IRIX632 2023-06-04 16:22:05.000000000 +0300 >+++ exim/OS/unsupported/os.c-IRIX632 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2022 */ > /* Copyright (c) University of Cambridge 2001 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -59,7 +60,7 @@ > log_write(0, LOG_PANIC_DIE, "iflist-sysctl-estimate failed: %s", > strerror(errno)); > >-buf = store_get(needed, FALSE); >+buf = store_get(needed, GET_UNTAINTED); > > if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) > log_write(0, LOG_PANIC_DIE, "sysctl of ifnet list failed: %s", >diff -ur exim.orig/OS/unsupported/os.c-IRIX65 exim/OS/unsupported/os.c-IRIX65 >--- exim.orig/OS/unsupported/os.c-IRIX65 2023-06-04 16:22:05.000000000 +0300 >+++ exim/OS/unsupported/os.c-IRIX65 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2022 */ > /* Copyright (c) University of Cambridge 2001 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -59,7 +60,7 @@ > log_write(0, LOG_PANIC_DIE, "iflist-sysctl-estimate failed: %s", > strerror(errno)); > >-buf = store_get(needed, FALSE); >+buf = store_get(needed, GET_UNTAINTED); > > if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) > log_write(0, LOG_PANIC_DIE, "sysctl of ifnet list failed: %s", >ТолÑко в exim/OS/unsupported: os.h-Darwin >diff -ur exim.orig/OS/unsupported/os.h-NetBSD exim/OS/unsupported/os.h-NetBSD >--- exim.orig/OS/unsupported/os.h-NetBSD 2023-06-04 16:22:05.000000000 +0300 >+++ exim/OS/unsupported/os.h-NetBSD 2022-06-23 16:41:10.000000000 +0300 >@@ -1,4 +1,5 @@ > /* Exim: OS-specific C header file for NetBSD */ >+/* Copyright (c) The Exim Maintainers 2021 */ > > #define HAVE_BSD_GETLOADAVG > #define HAVE_GETIFADDRS >@@ -25,4 +26,6 @@ > /* default is non-const */ > #define ICONV_ARG2_TYPE const char ** > >+#define EXIM_HAVE_KEVENT >+ > /* End */ >diff -ur exim.orig/scripts/Configure-Makefile exim/scripts/Configure-Makefile >--- exim.orig/scripts/Configure-Makefile 2023-06-04 16:22:05.000000000 +0300 >+++ exim/scripts/Configure-Makefile 2022-06-23 16:41:10.000000000 +0300 >@@ -7,7 +7,7 @@ > # just got too horrendous to get it right in "make", because of the optionally > # existing configuration files. > # >-# Copyright (c) The Exim Maintainers 1995 - 2020 >+# Copyright (c) The Exim Maintainers 1995 - 2021 > > > # First off, get the OS type, and check that there is a make file for it. >@@ -150,7 +150,7 @@ > egrep "^[$st]*(AUTH|LOOKUP)_[A-Z0-9_]*[$st]*=[$st]*" $mft | \ > sed "s/[$st]*=/='/" | \ > sed "s/\$/'/" > $mftt >-egrep "^[$st]*((USE_(OPENSSL|GNUTLS)_PC)|SUPPORT_TLS|USE_GNUTLS|PCRE_CONFIG|AVOID_GNUTLS_PKCS11)[$st]*=[$st]*" $mft | \ >+egrep "^[$st]*((USE_(OPENSSL|GNUTLS)_PC)|SUPPORT_TLS|USE_GNUTLS|PCRE2?_CONFIG|AVOID_GNUTLS_PKCS11)[$st]*=[$st]*" $mft | \ > sed "s/[$st]*=/='/" | \ > sed "s/\$/'/" >> $mftt > if test -s $mftt >@@ -233,12 +233,32 @@ > PCRE_CONFIG) > case $PCRE_CONFIG in > yes|YES|y|Y) >- cflags=`pcre-config --cflags` >+ echo >&2 "pcre is no longer supported; migrate to pcre2" >+ exit 1 >+ >+# cflags=`pcre-config --cflags` >+# if [ $? -ne 0 ]; then >+# echo >&2 "*** Missing pcre-config for regular expression support" >+# exit 1 >+# fi >+# libs=`pcre-config --libs` >+# if [ ".$cflags" != "." ]; then >+# echo "INCLUDE += $cflags" >+# fi >+# echo "PCRE_LIBS=$libs" >+ ;; >+ esac >+ ;; >+ >+ PCRE2_CONFIG) >+ case $PCRE2_CONFIG in >+ yes|YES|y|Y) >+ cflags=`pcre2-config --cflags` > if [ $? -ne 0 ]; then >- echo >&2 "*** Missing pcre-config for regular expression support" >+ echo >&2 "*** Missing pcre2-config for regular expression support" > exit 1 > fi >- libs=`pcre-config --libs` >+ libs=`pcre2-config --libs8` > if [ ".$cflags" != "." ]; then > echo "INCLUDE += $cflags" > fi >diff -ur exim.orig/scripts/lookups-Makefile exim/scripts/lookups-Makefile >--- exim.orig/scripts/lookups-Makefile 2023-06-04 16:22:05.000000000 +0300 >+++ exim/scripts/lookups-Makefile 2022-06-23 16:41:10.000000000 +0300 >@@ -1,5 +1,7 @@ > #! /bin/sh > >+# Copyright (c) The Exim Maintainers 1995 - 2021 >+ > # We turn the configure-built build-$foo/lookups/Makefile.predynamic into Makefile > > # We always re-exec ourselves at least once, because it's the cleanest and >@@ -160,7 +162,7 @@ > sed -n "1,/$tag_marker/p" < "$input" > > for name_mod in \ >- CDB DBM:dbmdb DNSDB DSEARCH IBASE JSON LSEARCH MYSQL NIS NISPLUS ORACLE \ >+ CDB DBM:dbmdb DNSDB DSEARCH IBASE JSON LMDB LSEARCH MYSQL NIS NISPLUS ORACLE \ > PASSWD PGSQL REDIS SQLITE TESTDB WHOSON > do > emit_module_rule $name_mod >@@ -177,11 +179,6 @@ > > OBJ="${OBJ} spf.o" > >-if want_experimental LMDB >-then >- OBJ="${OBJ} lmdb.o" >-fi >- > # readsock is always wanted as it implements the ${readsock } expansion > OBJ="${OBJ} readsock.o" > >diff -ur exim.orig/scripts/MakeLinks exim/scripts/MakeLinks >--- exim.orig/scripts/MakeLinks 2023-06-04 16:22:05.000000000 +0300 >+++ exim/scripts/MakeLinks 2022-06-23 16:41:10.000000000 +0300 >@@ -3,7 +3,7 @@ > # Script to build links for all the exim source files from the system- > # specific build directory. It should be run from within that directory. > # >-# Copyright (c) The Exim Maintainers 1995 - 2020 >+# Copyright (c) The Exim Maintainers 1995 - 2022 > > test ! -d ../src && \ > echo "*** $0 should be run in a system-specific subdirectory." && \ >@@ -95,12 +95,12 @@ > # but local_scan.c does not, because its location is taken from the build-time > # configuration. Likewise for the os.c file, which gets build dynamically. > >-for f in blob.h dbfunctions.h dbstuff.h exim.h functions.h globals.h \ >- hash.h local_scan.h \ >+for f in blob.h dbfunctions.h exim.h functions.h globals.h \ >+ hash.h hintsdb.h hintsdb_structs.h local_scan.h \ > macros.h mytypes.h osfunctions.h store.h structs.h lookupapi.h sha_ver.h \ > \ > acl.c buildconfig.c base64.c child.c crypt16.c daemon.c dbfn.c debug.c \ >- deliver.c directory.c dns.c drtables.c dummies.c enq.c exim.c \ >+ deliver.c directory.c dns.c dnsbl.c drtables.c dummies.c enq.c exim.c \ > exim_dbmbuild.c exim_dbutil.c exim_lock.c expand.c filter.c filtertest.c \ > globals.c hash.c header.c host.c ip.c log.c lss.c match.c md5.c moan.c \ > parse.c perl.c priv.c queue.c rda.c readconf.c receive.c retry.c rewrite.c \ >diff -ur exim.orig/scripts/reversion exim/scripts/reversion >--- exim.orig/scripts/reversion 2023-06-04 16:22:05.000000000 +0300 >+++ exim/scripts/reversion 2022-06-23 16:41:10.000000000 +0300 >@@ -1,5 +1,5 @@ > #!/bin/sh >-# Copyright (c) The Exim Maintainers 1995 - 2018 >+# Copyright (c) The Exim Maintainers 1995 - 2021 > > set -e > LC_ALL=C >@@ -29,7 +29,11 @@ > # Read version information that was generated by a previous run of > # this script, or during the release process. > >-if [ -f ./version.sh ]; then >+# Override, used for automated testing w/o access to the >+# .git directory (w.g. inside a git worktree) >+if [ -n "$EXIM_RELEASE_VERSION" ]; then >+ : >+elif [ -f ./version.sh ]; then > . ./version.sh > elif [ -f ../src/version.sh ]; then > . ../src/version.sh >@@ -51,9 +55,12 @@ > EXIM_VARIANT_VERSION="$3" > rm -f version.h > fi >-else >+fi >+ >+if [ -z "$EXIM_RELEASE_VERSION" ]; then > echo "Cannot determine the release number" >&2 >- exit >+ echo "You may want to override it with EXIM_RELEASE_VERSION" >&2 >+ exit 1 > fi > > # If you are maintaining a patched version of Exim, you can either >diff -ur exim.orig/src/acl.c exim/src/acl.c >--- exim.orig/src/acl.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/acl.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,19 +2,26 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Code for handling Access Control Lists (ACLs) */ > > #include "exim.h" > >+#ifndef MACRO_PREDEF > > /* Default callout timeout */ > > #define CALLOUT_TIMEOUT_DEFAULT 30 > >+/* Default quota cache TTLs */ >+ >+#define QUOTA_POS_DEFAULT (5*60) >+#define QUOTA_NEG_DEFAULT (60*60) >+ >+ > /* ACL verb codes - keep in step with the table of verbs that follows */ > > enum { ACL_ACCEPT, ACL_DEFER, ACL_DENY, ACL_DISCARD, ACL_DROP, ACL_REQUIRE, >@@ -47,6 +54,8 @@ > [ACL_WARN] = BIT(OK) > }; > >+#endif >+ > /* ACL condition and modifier codes - keep in step with the table that > follows. > down. */ >@@ -97,6 +106,7 @@ > ACLC_REGEX, > #endif > ACLC_REMOVE_HEADER, >+ ACLC_SEEN, > ACLC_SENDER_DOMAINS, > ACLC_SENDERS, > ACLC_SET, >@@ -282,6 +292,7 @@ > ACL_BIT_MIME | ACL_BIT_NOTSMTP | > ACL_BIT_NOTSMTP_START), > }, >+ [ACLC_SEEN] = { US"seen", TRUE, FALSE, 0 }, > [ACLC_SENDER_DOMAINS] = { US"sender_domains", FALSE, FALSE, > ACL_BIT_AUTH | ACL_BIT_CONNECT | > ACL_BIT_HELO | >@@ -332,6 +343,24 @@ > }; > > >+#ifdef MACRO_PREDEF >+# include "macro_predef.h" >+void >+features_acl(void) >+{ >+for (condition_def * c = conditions; c < conditions + nelem(conditions); c++) >+ { >+ uschar buf[64], * p, * s; >+ int n = sprintf(CS buf, "_ACL_%s_", c->is_modifier ? "MOD" : "COND"); >+ for (p = buf + n, s = c->name; *s; s++) *p++ = toupper(*s); >+ *p = '\0'; >+ builtin_macro_create(buf); >+ } >+} >+#endif >+ >+ >+#ifndef MACRO_PREDEF > > /* Return values from decode_control(); used as index so keep in step > with the controls_list table that follows! */ >@@ -613,6 +642,8 @@ > static int acl_check_wargs(int, address_item *, const uschar *, uschar **, > uschar **); > >+static acl_block * acl_current = NULL; >+ > > /************************************************* > * Find control in list * >@@ -777,7 +808,7 @@ > *error = string_sprintf("malformed ACL line \"%s\"", saveline); > return NULL; > } >- this = store_get(sizeof(acl_block), FALSE); >+ this = store_get(sizeof(acl_block), GET_UNTAINTED); > *lastp = this; > lastp = &(this->next); > this->next = NULL; >@@ -824,7 +855,7 @@ > return NULL; > } > >- cond = store_get(sizeof(acl_condition_block), FALSE); >+ cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED); > cond->next = NULL; > cond->type = c; > cond->u.negated = negated; >@@ -1023,7 +1054,7 @@ > { > /* The header_line struct itself is not tainted, though it points to > possibly tainted data. */ >- header_line * h = store_get(sizeof(header_line), FALSE); >+ header_line * h = store_get(sizeof(header_line), GET_UNTAINTED); > h->text = hdr; > h->next = NULL; > h->type = newtype; >@@ -1180,11 +1211,9 @@ > { > int rc; > >-user_msgptr = user_msgptr; /* stop compiler warning */ >- > /* Previous success */ > >-if (sender_host_name != NULL) return OK; >+if (sender_host_name) return OK; > > /* Previous failure */ > >@@ -1309,11 +1338,12 @@ > tree_node *t; > const uschar *found; > int priority, weight, port; >-dns_answer * dnsa = store_get_dns_answer(); >+dns_answer * dnsa; > dns_scan dnss; > dns_record *rr; >-int rc, type; >-uschar target[256]; >+int rc, type, yield; >+#define TARGET_SIZE 256 >+uschar * target = store_get(TARGET_SIZE, GET_TAINTED); > > /* Work out the domain we are using for the CSA lookup. The default is the > client's HELO domain. If the client has not said HELO, use its IP address >@@ -1321,8 +1351,8 @@ > > while (isspace(*domain) && *domain != '\0') ++domain; > if (*domain == '\0') domain = sender_helo_name; >-if (domain == NULL) domain = sender_host_address; >-if (sender_host_address == NULL) return CSA_UNKNOWN; >+if (!domain) domain = sender_host_address; >+if (!sender_host_address) return CSA_UNKNOWN; > > /* If we have an address literal, strip off the framing ready for turning it > into a domain. The framing consists of matched square brackets possibly >@@ -1352,33 +1382,36 @@ > for this domain. The name is filled in now, and the value is filled in when > we return from this function. */ > >-t = tree_search(csa_cache, domain); >-if (t != NULL) return t->data.val; >+if ((t = tree_search(csa_cache, domain))) >+ return t->data.val; > >-t = store_get_perm(sizeof(tree_node) + Ustrlen(domain), is_tainted(domain)); >+t = store_get_perm(sizeof(tree_node) + Ustrlen(domain), domain); > Ustrcpy(t->name, domain); > (void)tree_insertnode(&csa_cache, t); > > /* Now we are ready to do the actual DNS lookup(s). */ > > found = domain; >+dnsa = store_get_dns_answer(); > switch (dns_special_lookup(dnsa, domain, T_CSA, &found)) > { > /* If something bad happened (most commonly DNS_AGAIN), defer. */ > > default: >- return t->data.val = CSA_DEFER_SRV; >+ yield = CSA_DEFER_SRV; >+ goto out; > > /* If we found nothing, the client's authorization is unknown. */ > > case DNS_NOMATCH: > case DNS_NODATA: >- return t->data.val = CSA_UNKNOWN; >+ yield = CSA_UNKNOWN; >+ goto out; > > /* We got something! Go on to look at the reply in more detail. */ > > case DNS_SUCCEED: >- break; >+ break; > } > > /* Scan the reply for well-formed CSA SRV records. */ >@@ -1409,7 +1442,10 @@ > SRV records of their own. */ > > if (Ustrcmp(found, domain) != 0) >- return t->data.val = port & 1 ? CSA_FAIL_EXPLICIT : CSA_UNKNOWN; >+ { >+ yield = port & 1 ? CSA_FAIL_EXPLICIT : CSA_UNKNOWN; >+ goto out; >+ } > > /* This CSA SRV record refers directly to our domain, so we check the value > in the weight field to work out the domain's authorization. 0 and 1 are >@@ -1417,7 +1453,11 @@ > address in order to authenticate it, so we treat it as unknown; values > greater than 3 are undefined. */ > >- if (weight < 2) return t->data.val = CSA_FAIL_DOMAIN; >+ if (weight < 2) >+ { >+ yield = CSA_FAIL_DOMAIN; >+ goto out; >+ } > > if (weight > 2) continue; > >@@ -1426,7 +1466,7 @@ > target hostname then break to scan the additional data for its addresses. */ > > (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, p, >- (DN_EXPAND_ARG4_TYPE)target, sizeof(target)); >+ (DN_EXPAND_ARG4_TYPE)target, TARGET_SIZE); > > DEBUG(D_acl) debug_printf_indent("CSA target is %s\n", target); > >@@ -1435,7 +1475,11 @@ > > /* If we didn't break the loop then no appropriate records were found. */ > >-if (!rr) return t->data.val = CSA_UNKNOWN; >+if (!rr) >+ { >+ yield = CSA_UNKNOWN; >+ goto out; >+ } > > /* Do not check addresses if the target is ".", in accordance with RFC 2782. > A target of "." indicates there are no valid addresses, so the client cannot >@@ -1443,7 +1487,11 @@ > equivalent to weight=1, but we check for it in order to keep load off the > root name servers.) Note that dn_expand() turns "." into "". */ > >-if (Ustrcmp(target, "") == 0) return t->data.val = CSA_FAIL_NOADDR; >+if (Ustrcmp(target, "") == 0) >+ { >+ yield = CSA_FAIL_NOADDR; >+ goto out; >+ } > > /* Scan the additional section of the CSA SRV reply for addresses belonging > to the target. If the name server didn't return any additional data (e.g. >@@ -1451,7 +1499,11 @@ > to obtain the target addresses; otherwise we have a definitive result. */ > > rc = acl_verify_csa_address(dnsa, &dnss, RESET_ADDITIONAL, target); >-if (rc != CSA_FAIL_NOADDR) return t->data.val = rc; >+if (rc != CSA_FAIL_NOADDR) >+ { >+ yield = rc; >+ goto out; >+ } > > /* The DNS lookup type corresponds to the IP version used by the client. */ > >@@ -1469,13 +1521,18 @@ > /* If something bad happened (most commonly DNS_AGAIN), defer. */ > > default: >- return t->data.val = CSA_DEFER_ADDR; >+ yield = CSA_DEFER_ADDR; >+ break; > > /* If the query succeeded, scan the addresses and return the result. */ > > case DNS_SUCCEED: > rc = acl_verify_csa_address(dnsa, &dnss, RESET_ANSWERS, target); >- if (rc != CSA_FAIL_NOADDR) return t->data.val = rc; >+ if (rc != CSA_FAIL_NOADDR) >+ { >+ yield = rc; >+ break; >+ } > /* else fall through */ > > /* If the target has no IP addresses, the client cannot have an authorized >@@ -1484,8 +1541,14 @@ > > case DNS_NOMATCH: > case DNS_NODATA: >- return t->data.val = CSA_FAIL_NOADDR; >+ yield = CSA_FAIL_NOADDR; >+ break; > } >+ >+out: >+ >+store_free_dns_answer(dnsa); >+return t->data.val = yield; > } > > >@@ -1511,14 +1574,14 @@ > { US"certificate", VERIFY_CERT, (unsigned)~0, TRUE, 0 }, > { US"helo", VERIFY_HELO, (unsigned)~0, TRUE, 0 }, > { US"csa", VERIFY_CSA, (unsigned)~0, FALSE, 0 }, >- { US"header_syntax", VERIFY_HDR_SYNTAX, ACL_BIT_DATA | ACL_BIT_NOTSMTP, TRUE, 0 }, >- { US"not_blind", VERIFY_NOT_BLIND, ACL_BIT_DATA | ACL_BIT_NOTSMTP, FALSE, 0 }, >- { US"header_sender", VERIFY_HDR_SNDR, ACL_BIT_DATA | ACL_BIT_NOTSMTP, FALSE, 0 }, >+ { US"header_syntax", VERIFY_HDR_SYNTAX, ACL_BITS_HAVEDATA, TRUE, 0 }, >+ { US"not_blind", VERIFY_NOT_BLIND, ACL_BITS_HAVEDATA, FALSE, 0 }, >+ { US"header_sender", VERIFY_HDR_SNDR, ACL_BITS_HAVEDATA, FALSE, 0 }, > { US"sender", VERIFY_SNDR, ACL_BIT_MAIL | ACL_BIT_RCPT >- |ACL_BIT_PREDATA | ACL_BIT_DATA | ACL_BIT_NOTSMTP, >+ | ACL_BIT_PREDATA | ACL_BIT_DATA | ACL_BIT_NOTSMTP, > FALSE, 6 }, > { US"recipient", VERIFY_RCPT, ACL_BIT_RCPT, FALSE, 0 }, >- { US"header_names_ascii", VERIFY_HDR_NAMES_ASCII, ACL_BIT_DATA | ACL_BIT_NOTSMTP, TRUE, 0 }, >+ { US"header_names_ascii", VERIFY_HDR_NAMES_ASCII, ACL_BITS_HAVEDATA, TRUE, 0 }, > #ifdef EXPERIMENTAL_ARC > { US"arc", VERIFY_ARC, ACL_BIT_DATA, FALSE , 0 }, > #endif >@@ -1556,6 +1619,20 @@ > > > >+static int >+v_period(const uschar * s, const uschar * arg, uschar ** log_msgptr) >+{ >+int period; >+if ((period = readconf_readtime(s, 0, FALSE)) < 0) >+ { >+ *log_msgptr = string_sprintf("bad time value in ACL condition " >+ "\"verify %s\"", arg); >+ } >+return period; >+} >+ >+ >+ > /* This function implements the "verify" condition. It is called when > encountered in any ACL, because some tests are almost always permitted. Some > just don't make sense, and always fail (for example, an attempt to test a host >@@ -1590,6 +1667,8 @@ > BOOL callout_defer_ok = FALSE; > BOOL no_details = FALSE; > BOOL success_on_redirect = FALSE; >+BOOL quota = FALSE; >+int quota_pos_cache = QUOTA_POS_DEFAULT, quota_neg_cache = QUOTA_NEG_DEFAULT; > address_item *sender_vaddr = NULL; > uschar *verify_sender_address = NULL; > uschar *pm_mailfrom = NULL; >@@ -1746,7 +1825,7 @@ > in place of the actual sender (rare special-case requirement). */ > { > uschar *s = ss + 6; >- if (*s == 0) >+ if (!*s) > verify_sender_address = sender_address; > else > { >@@ -1792,19 +1871,16 @@ > else if (strncmpic(ss, US"callout", 7) == 0) > { > callout = CALLOUT_TIMEOUT_DEFAULT; >- ss += 7; >- if (*ss != 0) >+ if (*(ss += 7)) > { > while (isspace(*ss)) ss++; > if (*ss++ == '=') > { > const uschar * sublist = ss; > int optsep = ','; >- uschar buffer[256]; >- uschar * opt; > > while (isspace(*sublist)) sublist++; >- while ((opt = string_nextinlist(&sublist, &optsep, NULL, 0))) >+ for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); ) > { > callout_opt_t * op; > double period = 1.0F; >@@ -1826,12 +1902,8 @@ > } > while (isspace(*opt)) opt++; > } >- if (op->timeval && (period = readconf_readtime(opt, 0, FALSE)) < 0) >- { >- *log_msgptr = string_sprintf("bad time value in ACL condition " >- "\"verify %s\"", arg); >+ if (op->timeval && (period = v_period(opt, arg, log_msgptr)) < 0) > return ERROR; >- } > > switch(op->value) > { >@@ -1864,6 +1936,38 @@ > } > } > >+ /* The quota option has sub-options, comma-separated */ >+ >+ else if (strncmpic(ss, US"quota", 5) == 0) >+ { >+ quota = TRUE; >+ if (*(ss += 5)) >+ { >+ while (isspace(*ss)) ss++; >+ if (*ss++ == '=') >+ { >+ const uschar * sublist = ss; >+ int optsep = ','; >+ int period; >+ >+ while (isspace(*sublist)) sublist++; >+ for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); ) >+ if (Ustrncmp(opt, "cachepos=", 9) == 0) >+ if ((period = v_period(opt += 9, arg, log_msgptr)) < 0) >+ return ERROR; >+ else >+ quota_pos_cache = period; >+ else if (Ustrncmp(opt, "cacheneg=", 9) == 0) >+ if ((period = v_period(opt += 9, arg, log_msgptr)) < 0) >+ return ERROR; >+ else >+ quota_neg_cache = period; >+ else if (Ustrcmp(opt, "no_cache") == 0) >+ quota_pos_cache = quota_neg_cache = 0; >+ } >+ } >+ } >+ > /* Option not recognized */ > > else >@@ -1882,6 +1986,32 @@ > return ERROR; > } > >+/* Handle quota verification */ >+if (quota) >+ { >+ if (vp->value != VERIFY_RCPT) >+ { >+ *log_msgptr = US"can only verify quota of recipient"; >+ return ERROR; >+ } >+ >+ if ((rc = verify_quota_call(addr->address, >+ quota_pos_cache, quota_neg_cache, log_msgptr)) != OK) >+ { >+ *basic_errno = errno; >+ if (smtp_return_error_details) >+ { >+ if (!*user_msgptr && *log_msgptr) >+ *user_msgptr = string_sprintf("Rejected after %s: %s", >+ smtp_names[smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)]], >+ *log_msgptr); >+ if (rc == DEFER) f.acl_temp_details = TRUE; >+ } >+ } >+ >+ return rc; >+ } >+ > /* Handle sender-in-header verification. Default the user message to the log > message if giving out verification details. */ > >@@ -1928,8 +2058,8 @@ > } > > sender_vaddr = verify_checked_sender(verify_sender_address); >- if (sender_vaddr != NULL && /* Previously checked */ >- callout <= 0) /* No callout needed this time */ >+ if ( sender_vaddr /* Previously checked */ >+ && callout <= 0) /* No callout needed this time */ > { > /* If the "routed" flag is set, it means that routing worked before, so > this check can give OK (the saved return code value, if set, belongs to a >@@ -1996,14 +2126,12 @@ > *basic_errno = sender_vaddr->basic_errno; > else > DEBUG(D_acl) >- { > if (Ustrcmp(sender_vaddr->address, verify_sender_address) != 0) > debug_printf_indent("sender %s verified ok as %s\n", > verify_sender_address, sender_vaddr->address); > else > debug_printf_indent("sender %s verified ok\n", > verify_sender_address); >- } > } > else > rc = OK; /* Null sender */ >@@ -2047,8 +2175,7 @@ > > *basic_errno = addr2.basic_errno; > *log_msgptr = addr2.message; >- *user_msgptr = (addr2.user_message != NULL)? >- addr2.user_message : addr2.message; >+ *user_msgptr = addr2.user_message ? addr2.user_message : addr2.message; > > /* Allow details for temporary error if the address is so flagged. */ > if (testflag((&addr2), af_pass_message)) f.acl_temp_details = TRUE; >@@ -2059,8 +2186,10 @@ > > /* We have a result from the relevant test. Handle defer overrides first. */ > >-if (rc == DEFER && (defer_ok || >- (callout_defer_ok && *basic_errno == ERRNO_CALLOUTDEFER))) >+if ( rc == DEFER >+ && ( defer_ok >+ || callout_defer_ok && *basic_errno == ERRNO_CALLOUTDEFER >+ ) ) > { > HDEBUG(D_acl) debug_printf_indent("verify defer overridden by %s\n", > defer_ok? "defer_ok" : "callout_defer_ok"); >@@ -2070,7 +2199,7 @@ > /* If we've failed a sender, set up a recipient message, and point > sender_verified_failed to the address item that actually failed. */ > >-if (rc != OK && verify_sender_address != NULL) >+if (rc != OK && verify_sender_address) > { > if (rc != DEFER) > *log_msgptr = *user_msgptr = US"Sender verify failed"; >@@ -2089,7 +2218,7 @@ > /* Verifying an address messes up the values of $domain and $local_part, > so reset them before returning if this is a RCPT ACL. */ > >-if (addr != NULL) >+if (addr) > { > deliver_domain = addr->domain; > deliver_localpart = addr->local_part; >@@ -2458,7 +2587,7 @@ > /* No Bloom filter. This basic ratelimit block is initialized below. */ > HDEBUG(D_acl) debug_printf_indent("ratelimit creating new rate data block\n"); > dbdb_size = sizeof(*dbd); >- dbdb = store_get(dbdb_size, FALSE); /* not tainted */ >+ dbdb = store_get(dbdb_size, GET_UNTAINTED); > } > else > { >@@ -2472,7 +2601,7 @@ > extra = (int)limit * 2 - sizeof(dbdb->bloom); > if (extra < 0) extra = 0; > dbdb_size = sizeof(*dbdb) + extra; >- dbdb = store_get(dbdb_size, FALSE); /* not tainted */ >+ dbdb = store_get(dbdb_size, GET_UNTAINTED); > dbdb->bloom_epoch = tv.tv_sec; > dbdb->bloom_size = sizeof(dbdb->bloom) + extra; > memset(dbdb->bloom, 0, dbdb->bloom_size); >@@ -2692,7 +2821,7 @@ > /* Store the result in the tree for future reference. Take the taint status > from the key for consistency even though it's unlikely we'll ever expand this. */ > >-t = store_get(sizeof(tree_node) + Ustrlen(key), is_tainted(key)); >+t = store_get(sizeof(tree_node) + Ustrlen(key), key); > t->data.ptr = dbd; > Ustrcpy(t->name, key); > (void)tree_insertnode(anchor, t); >@@ -2712,6 +2841,143 @@ > > > /************************************************* >+* Handle a check for previously-seen * >+*************************************************/ >+ >+/* >+ACL clauses like: seen = -5m / key=$foo / readonly >+ >+Return is true for condition-true - but the semantics >+depend heavily on the actual use-case. >+ >+Negative times test for seen-before, positive for seen-more-recently-than >+(the given interval before current time). >+ >+All are subject to history not having been cleaned from the DB. >+ >+Default for seen-before is to create if not present, and to >+update if older than 10d (with the seen-test time). >+Default for seen-since is to always create or update. >+ >+Options: >+ key=value. Default key is $sender_host_address >+ readonly >+ write >+ refresh=<interval>: update an existing DB entry older than given >+ amount. Default refresh lacking this option is 10d. >+ The update sets the record timestamp to the seen-test time. >+ >+XXX do we need separate nocreate, noupdate controls? >+ >+Arguments: >+ arg the option string for seen= >+ where ACL_WHERE_xxxx indicating which ACL this is >+ log_msgptr for error messages >+ >+Returns: OK - Condition is true >+ FAIL - Condition is false >+ DEFER - Problem opening history database >+ ERROR - Syntax error in options >+*/ >+ >+static int >+acl_seen(const uschar * arg, int where, uschar ** log_msgptr) >+{ >+enum { SEEN_DEFAULT, SEEN_READONLY, SEEN_WRITE }; >+ >+const uschar * list = arg; >+int slash = '/', interval, mode = SEEN_DEFAULT, yield = FAIL; >+BOOL before; >+int refresh = 10 * 24 * 60 * 60; /* 10 days */ >+const uschar * ele, * key = sender_host_address; >+open_db dbblock, * dbm; >+dbdata_seen * dbd; >+time_t now; >+ >+/* Parse the first element, the time-relation. */ >+ >+if (!(ele = string_nextinlist(&list, &slash, NULL, 0))) >+ goto badparse; >+if ((before = *ele == '-')) >+ ele++; >+if ((interval = readconf_readtime(ele, 0, FALSE)) < 0) >+ goto badparse; >+ >+/* Remaining elements are options */ >+ >+while ((ele = string_nextinlist(&list, &slash, NULL, 0))) >+ if (Ustrncmp(ele, "key=", 4) == 0) >+ key = ele + 4; >+ else if (Ustrcmp(ele, "readonly") == 0) >+ mode = SEEN_READONLY; >+ else if (Ustrcmp(ele, "write") == 0) >+ mode = SEEN_WRITE; >+ else if (Ustrncmp(ele, "refresh=", 8) == 0) >+ { >+ if ((refresh = readconf_readtime(ele + 8, 0, FALSE)) < 0) >+ goto badparse; >+ } >+ else >+ goto badopt; >+ >+if (!(dbm = dbfn_open(US"seen", O_RDWR, &dbblock, TRUE, TRUE))) >+ { >+ HDEBUG(D_acl) debug_printf_indent("database for 'seen' not available\n"); >+ *log_msgptr = US"database for 'seen' not available"; >+ return DEFER; >+ } >+ >+dbd = dbfn_read_with_length(dbm, key, NULL); >+now = time(NULL); >+if (dbd) /* an existing record */ >+ { >+ time_t diff = now - dbd->time_stamp; /* time since the record was written */ >+ >+ if (before ? diff >= interval : diff < interval) >+ yield = OK; >+ >+ if (mode == SEEN_READONLY) >+ { HDEBUG(D_acl) debug_printf_indent("seen db not written (readonly)\n"); } >+ else if (mode == SEEN_WRITE || !before) >+ { >+ dbd->time_stamp = now; >+ dbfn_write(dbm, key, dbd, sizeof(*dbd)); >+ HDEBUG(D_acl) debug_printf_indent("seen db written (update)\n"); >+ } >+ else if (diff >= refresh) >+ { >+ dbd->time_stamp = now - interval; >+ dbfn_write(dbm, key, dbd, sizeof(*dbd)); >+ HDEBUG(D_acl) debug_printf_indent("seen db written (refresh)\n"); >+ } >+ } >+else >+ { /* No record found, yield always FAIL */ >+ if (mode != SEEN_READONLY) >+ { >+ dbdata_seen d = {.time_stamp = now}; >+ dbfn_write(dbm, key, &d, sizeof(*dbd)); >+ HDEBUG(D_acl) debug_printf_indent("seen db written (create)\n"); >+ } >+ else >+ HDEBUG(D_acl) debug_printf_indent("seen db not written (readonly)\n"); >+ } >+ >+dbfn_close(dbm); >+return yield; >+ >+ >+badparse: >+ *log_msgptr = string_sprintf("failed to parse '%s'", arg); >+ return ERROR; >+badopt: >+ *log_msgptr = string_sprintf("unrecognised option '%s' in '%s'", ele, arg); >+ return ERROR; >+} >+ >+ >+ >+/************************************************* > * The udpsend ACL modifier * > *************************************************/ > >@@ -2765,7 +3031,7 @@ > } > > /* Make a single-item host list. */ >-h = store_get(sizeof(host_item), FALSE); >+h = store_get(sizeof(host_item), GET_UNTAINTED); > memset(h, 0, sizeof(host_item)); > h->name = hostname; > h->port = portnum; >@@ -3131,13 +3397,15 @@ > > case CONTROL_FAKEREJECT: > cancel_cutthrough_connection(TRUE, US"fakereject"); >- case CONTROL_FAKEDEFER: >+ case CONTROL_FAKEDEFER: > fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL; > if (*p == '/') > { > const uschar *pp = p + 1; > while (*pp) pp++; >- fake_response_text = expand_string(string_copyn(p+1, pp-p-1)); >+ /* The entire control= line was expanded at top so no need to expand >+ the part after the / */ >+ fake_response_text = string_copyn(p+1, pp-p-1); > p = pp; > } > else /* Explicitly reset to default string */ >@@ -3213,9 +3481,8 @@ > > case CONTROL_DEBUG: > { >- uschar * debug_tag = NULL; >- uschar * debug_opts = NULL; >- BOOL kill = FALSE; >+ uschar * debug_tag = NULL, * debug_opts = NULL; >+ BOOL kill = FALSE, stop = FALSE; > > while (*p == '/') > { >@@ -3232,18 +3499,39 @@ > } > else if (Ustrncmp(pp, "kill", 4) == 0) > { >- for (pp += 4; *pp && *pp != '/';) pp++; >+ pp += 4; > kill = TRUE; > } >- else >- while (*pp && *pp != '/') pp++; >+ else if (Ustrncmp(pp, "stop", 4) == 0) >+ { >+ pp += 4; >+ stop = TRUE; >+ } >+ else if (Ustrncmp(pp, "pretrigger=", 11) == 0) >+ debug_pretrigger_setup(pp+11); >+ else if (Ustrncmp(pp, "trigger=", 8) == 0) >+ { >+ if (Ustrncmp(pp += 8, "now", 3) == 0) >+ { >+ pp += 3; >+ debug_trigger_fire(); >+ } >+ else if (Ustrncmp(pp, "paniclog", 8) == 0) >+ { >+ pp += 8; >+ dtrigger_selector |= BIT(DTi_panictrigger); >+ } >+ } >+ while (*pp && *pp != '/') pp++; > p = pp; > } > >- if (kill) >- debug_logging_stop(); >- else >- debug_logging_activate(debug_tag, debug_opts); >+ if (kill) >+ debug_logging_stop(TRUE); >+ else if (stop) >+ debug_logging_stop(FALSE); >+ else if (debug_tag || debug_opts) >+ debug_logging_activate(debug_tag, debug_opts); > break; > } > >@@ -3434,7 +3722,7 @@ > } > break; > >- #ifndef DISABLE_DKIM >+#ifndef DISABLE_DKIM > case ACLC_DKIM_SIGNER: > if (dkim_cur_signer) > rc = match_isinlist(dkim_cur_signer, >@@ -3447,7 +3735,7 @@ > rc = match_isinlist(dkim_verify_status, > &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); > break; >- #endif >+#endif > > #ifdef SUPPORT_DMARC > case ACLC_DMARC_STATUS: >@@ -3632,6 +3920,10 @@ > setup_remove_header(arg); > break; > >+ case ACLC_SEEN: >+ rc = acl_seen(arg, where, log_msgptr); >+ break; >+ > case ACLC_SENDER_DOMAINS: > { > uschar *sdomain; >@@ -3910,6 +4202,18 @@ > > > >+/************************************************/ >+/* For error messages, a string describing the config location >+associated with current processing. NULL if not in an ACL. */ >+ >+uschar * >+acl_current_verb(void) >+{ >+if (acl_current) return string_sprintf(" (ACL %s, %s %d)", >+ verbs[acl_current->verb], acl_current->srcfile, acl_current->srcline); >+return NULL; >+} >+ > /************************************************* > * Check access using an ACL * > *************************************************/ >@@ -3984,6 +4288,15 @@ > > acl_text = ss; > >+if (is_tainted(acl_text) && !f.running_in_test_harness) >+ { >+ log_write(0, LOG_MAIN|LOG_PANIC, >+ "attempt to use tainted ACL text \"%s\"", acl_text); >+ /* Avoid leaking info to an attacker */ >+ *log_msgptr = US"internal configuration error"; >+ return ERROR; >+ } >+ > /* Handle the case of a string that does not contain any spaces. Look for a > named ACL among those read from the configuration, or a previously read file. > It is possible that the pointer to the ACL is NULL if the configuration >@@ -4007,14 +4320,6 @@ > else if (*ss == '/') > { > struct stat statbuf; >- if (is_tainted(ss)) >- { >- log_write(0, LOG_MAIN|LOG_PANIC, >- "attempt to open tainted ACL file name \"%s\"", ss); >- /* Avoid leaking info to an attacker */ >- *log_msgptr = US"internal configuration error"; >- return ERROR; >- } > if ((fd = Uopen(ss, O_RDONLY, 0)) < 0) > { > *log_msgptr = string_sprintf("failed to open ACL file \"%s\": %s", ss, >@@ -4029,7 +4334,7 @@ > } > > /* If the string being used as a filename is tainted, so is the file content */ >- acl_text = store_get(statbuf.st_size + 1, is_tainted(ss)); >+ acl_text = store_get(statbuf.st_size + 1, ss); > acl_text_end = acl_text + statbuf.st_size + 1; > > if (read(fd, acl_text, statbuf.st_size) != statbuf.st_size) >@@ -4059,7 +4364,7 @@ > if (!acl && *log_msgptr) return ERROR; > if (fd >= 0) > { >- tree_node *t = store_get_perm(sizeof(tree_node) + Ustrlen(ss), is_tainted(ss)); >+ tree_node * t = store_get_perm(sizeof(tree_node) + Ustrlen(ss), ss); > Ustrcpy(t->name, ss); > t->data.ptr = acl; > (void)tree_insertnode(&acl_anchor, t); >@@ -4068,7 +4373,7 @@ > > /* Now we have an ACL to use. It's possible it may be NULL. */ > >-while (acl) >+while ((acl_current = acl)) > { > int cond; > int basic_errno = 0; >@@ -4215,8 +4520,8 @@ > else if (cond == DEFER && LOGGING(acl_warn_skipped)) > log_write(0, LOG_MAIN, "%s Warning: ACL \"warn\" statement skipped: " > "condition test deferred%s%s", host_and_ident(TRUE), >- (*log_msgptr == NULL)? US"" : US": ", >- (*log_msgptr == NULL)? US"" : *log_msgptr); >+ *log_msgptr ? US": " : US"", >+ *log_msgptr ? *log_msgptr : US""); > *log_msgptr = *user_msgptr = NULL; /* In case implicit DENY follows */ > break; > >@@ -4541,7 +4846,7 @@ > tree_node * node, ** root = name[0] == 'c' ? &acl_var_c : &acl_var_m; > if (!(node = tree_search(*root, name))) > { >- node = store_get(sizeof(tree_node) + Ustrlen(name), is_tainted(name)); >+ node = store_get(sizeof(tree_node) + Ustrlen(name), name); > Ustrcpy(node->name, name); > (void)tree_insertnode(root, node); > } >@@ -4572,13 +4877,20 @@ > */ > > void >-acl_var_write(uschar *name, uschar *value, void *ctx) >+acl_var_write(uschar * name, uschar * value, void * ctx) > { >-FILE *f = (FILE *)ctx; >-if (is_tainted(value)) putc('-', f); >-fprintf(f, "-acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value); >+FILE * f = (FILE *)ctx; >+putc('-', f); >+if (is_tainted(value)) >+ { >+ int q = quoter_for_address(value); >+ putc('-', f); >+ if (is_real_quoter(q)) fprintf(f, "(%s)", lookup_list[q]->name); >+ } >+fprintf(f, "acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value); > } > >+#endif /* !MACRO_PREDEF */ > /* vi: aw ai sw=2 > */ > /* End of acl.c */ >diff -ur exim.orig/src/arc.c exim/src/arc.c >--- exim.orig/src/arc.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/arc.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > /* Experimental ARC support for Exim > Copyright (c) Jeremy Harris 2018 - 2020 >+ Copyright (c) The Exim Maintainers 2021 - 2022 > License: GPL > */ > >@@ -141,7 +142,7 @@ > } > > DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i); >-*pas = as = store_get(sizeof(arc_set), FALSE); >+*pas = as = store_get(sizeof(arc_set), GET_UNTAINTED); > memset(as, 0, sizeof(arc_set)); > as->next = next; > as->prev = prev; >@@ -199,7 +200,7 @@ > > if (!instance_only) > { >- al->rawsig_no_b_val.data = store_get(h->slen + 1, TRUE); /* tainted */ >+ al->rawsig_no_b_val.data = store_get(h->slen + 1, GET_TAINTED); > memcpy(al->rawsig_no_b_val.data, h->text, off); /* copy the header name blind */ > r = al->rawsig_no_b_val.data + off; > al->rawsig_no_b_val.len = off; >@@ -385,7 +386,7 @@ > { > unsigned i; > arc_set * as; >-arc_line * al = store_get(sizeof(arc_line), FALSE), ** alp; >+arc_line * al = store_get(sizeof(arc_line), GET_UNTAINTED), ** alp; > uschar * e; > > memset(al, 0, sizeof(arc_line)); >@@ -496,7 +497,7 @@ > DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n"); > for (h = header_list; h; h = h->next) > { >- r = store_get(sizeof(hdr_rlist), FALSE); >+ r = store_get(sizeof(hdr_rlist), GET_UNTAINTED); > r->prev = rprev; > r->used = FALSE; > r->h = h; >@@ -568,7 +569,7 @@ > > len = Ustrlen(s); > DEBUG(D_acl) pdkim_quoteprint(s, len); >- exim_sha_update(&hhash_ctx, s, Ustrlen(s)); >+ exim_sha_update_string(&hhash_ctx, s); > r->used = TRUE; > break; > } >@@ -1102,7 +1103,7 @@ > static hdr_rlist * > arc_rlist_entry(hdr_rlist * list, const uschar * s, int len) > { >-hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line), FALSE); >+hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line), GET_UNTAINTED); > header_line * h = r->h = (header_line *)(r+1); > > r->prev = list; >@@ -1112,11 +1113,6 @@ > h->slen = len; > h->text = US s; > >-/* This works for either NL or CRLF lines; also nul-termination */ >-while (*++s) >- if (*s == '\n' && s[1] != '\t' && s[1] != ' ') break; >-s++; /* move past end of line */ >- > return r; > } > >@@ -1194,7 +1190,7 @@ > { > int aar_off = gstring_length(g); > arc_set * as = >- store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line), FALSE); >+ store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line), GET_UNTAINTED); > arc_line * al = (arc_line *)(as+1); > header_line * h = (header_line *)(al+1); > >@@ -1304,7 +1300,7 @@ > int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */ > blob sig; > int ams_off; >-arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), FALSE); >+arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), GET_UNTAINTED); > header_line * h = (header_line *)(al+1); > > /* debug_printf("%s\n", __FUNCTION__); */ >@@ -1419,7 +1415,7 @@ > { > gstring * arcset; > uschar * status = arc_ar_cv_status(ar); >-arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), FALSE); >+arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), GET_UNTAINTED); > header_line * h = (header_line *)(al+1); > uschar * badline_str; > >@@ -1531,6 +1527,7 @@ > arc_sign_init(void) > { > memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx)); >+headers_rlist = NULL; > } > > >@@ -1557,6 +1554,23 @@ > > > >+/* Per RFCs 6376, 7489 the only allowed chars in either an ADMD id >+or a selector are ALPHA/DIGGIT/'-'/'.' >+ >+Check, to help catch misconfigurations such as a missing selector >+element in the arc_sign list. >+*/ >+ >+static BOOL >+arc_valid_id(const uschar * s) >+{ >+for (uschar c; c = *s++; ) >+ if (!isalnum(c) && c != '-' && c != '.') return FALSE; >+return TRUE; >+} >+ >+ >+ > /* ARC signing. Called from the smtp transport, if the arc_sign option is set. > The dkim_exim_sign() function has already been called, so will have hashed the > message body for us so long as we requested a hash previously. >@@ -1590,17 +1604,18 @@ > > /* Parse the signing specification */ > >-identity = string_nextinlist(&signspec, &sep, NULL, 0); >-selector = string_nextinlist(&signspec, &sep, NULL, 0); >-if ( !*identity || !*selector >- || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey) >- { >- log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)", >- !*identity ? "identity" : !*selector ? "selector" : "private-key"); >- return sigheaders ? sigheaders : string_get(0); >- } >+if (!(identity = string_nextinlist(&signspec, &sep, NULL, 0)) || !*identity) >+ { s = US"identity"; goto bad_arg_ret; } >+if (!(selector = string_nextinlist(&signspec, &sep, NULL, 0)) || !*selector) >+ { s = US"selector"; goto bad_arg_ret; } >+if (!(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey) >+ { s = US"privkey"; goto bad_arg_ret; } >+if (!arc_valid_id(identity)) >+ { s = US"identity"; goto bad_arg_ret; } >+if (!arc_valid_id(selector)) >+ { s = US"selector"; goto bad_arg_ret; } > if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey))) >- return sigheaders ? sigheaders : string_get(0); >+ goto ret_sigheaders; > > if ((opts = string_nextinlist(&signspec, &sep, NULL, 0))) > { >@@ -1659,7 +1674,7 @@ > if (!(arc_sign_find_ar(headers, identity, &ar))) > { > log_write(0, LOG_MAIN, "ARC: no Authentication-Results header for signing"); >- return sigheaders ? sigheaders : string_get(0); >+ goto ret_sigheaders; > } > > /* We previously built the data-struct for the existing ARC chain, if any, using a headers >@@ -1715,9 +1730,19 @@ > /* Finally, append the dkim headers and return the lot. */ > > if (sigheaders) g = string_catn(g, sigheaders->s, sigheaders->ptr); >-(void) string_from_gstring(g); >-gstring_release_unused(g); >-return g; >+ >+out: >+ if (!g) return string_get(1); >+ (void) string_from_gstring(g); >+ gstring_release_unused(g); >+ return g; >+ >+ >+bad_arg_ret: >+ log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)", s); >+ret_sigheaders: >+ g = sigheaders; >+ goto out; > } > > >diff -ur exim.orig/src/auths/auth-spa.c exim/src/auths/auth-spa.c >--- exim.orig/src/auths/auth-spa.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/auths/auth-spa.c 2022-06-23 16:41:10.000000000 +0300 >@@ -8,6 +8,8 @@ > > * All the original code used here was torn by Marc Prud'hommeaux out of the > * Samba project (by Andrew Tridgell, Jeremy Allison, and others). >+ * >+ * Copyright (c) The Exim Maintainers 2021 > > * Tom Kistner provided additional code, adding spa_build_auth_challenge() to > * support server authentication mode. >@@ -1395,8 +1397,6 @@ > int p = (int)getpid(); > int random_seed = (int)time(NULL) ^ ((p << 16) | p); > >-request = request; /* Added by PH to stop compilers whinging */ >- > /* Ensure challenge data is cleared, in case it isn't all used. This > patch added by PH on suggestion of Russell King */ > >diff -ur exim.orig/src/auths/call_pam.c exim/src/auths/call_pam.c >--- exim.orig/src/auths/call_pam.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/auths/call_pam.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,7 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ >+/* Copyright (c) The Exim Maintainers 2020 - 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >@@ -88,7 +88,7 @@ > arg = US""; > pam_arg_ended = TRUE; > } >- reply[i].resp = CS string_copy_malloc(arg); /* PAM frees resp */ >+ reply[i].resp = strdup(CCS arg); /* Use libc malloc, PAM frees resp directly*/ > reply[i].resp_retcode = PAM_SUCCESS; > break; > >diff -ur exim.orig/src/auths/call_radius.c exim/src/auths/call_radius.c >--- exim.orig/src/auths/call_radius.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/auths/call_radius.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,23 +2,13 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >-/* Copyright (c) The Exim Maintainers 2020 */ >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2016 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* This file was originally supplied by Ian Kirk. The libradius support came > from Alex Kiernan. */ > >-/* ugly hack to work around redefinition of ENV by radiusclient.h and >- * db.h: define _DB_H_ so the db.h include thinks it's already included, >- * we can get away with it like this, since this file doesn't use any db >- * functions. */ >-#ifndef _DB_H_ >-# define _DB_H_ 1 >-# define _DB_EXT_PROT_IN_ 1 >-# define DB void >-#endif >- > #include "../exim.h" > > /* This module contains functions that call the Radius authentication >@@ -44,17 +34,18 @@ > using its original API. At release 0.4.0 the API changed. */ > > #ifdef RADIUS_LIB_RADLIB >- #include <radlib.h> >+# include <radlib.h> > #else >- #if !defined(RADIUS_LIB_RADIUSCLIENT) && !defined(RADIUS_LIB_RADIUSCLIENTNEW) >- # define RADIUS_LIB_RADIUSCLIENT >- #endif >- >- #ifdef RADIUS_LIB_RADIUSCLIENTNEW >- # include <freeradius-client.h> >- #else >- # include <radiusclient.h> >- #endif >+# if !defined(RADIUS_LIB_RADIUSCLIENT) && !defined(RADIUS_LIB_RADIUSCLIENTNEW) >+# define RADIUS_LIB_RADIUSCLIENT >+# endif >+ >+# ifdef RADIUS_LIB_RADIUSCLIENTNEW >+# define ENV FREERADIUSCLIENT_ENV /* Avoid clash with Berkeley DB */ >+# include <freeradius-client.h> >+# else >+# include <radiusclient.h> >+# endif > #endif > > >diff -ur exim.orig/src/auths/cyrus_sasl.c exim/src/auths/cyrus_sasl.c >--- exim.orig/src/auths/cyrus_sasl.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/auths/cyrus_sasl.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* This code was originally contributed by Matthew Byng-Maddick */ >@@ -71,7 +71,7 @@ > int auth_cyrus_sasl_server(auth_instance *ablock, uschar *data) {return 0;} > int auth_cyrus_sasl_client(auth_instance *ablock, void * sx, > int timeout, uschar *buffer, int buffsize) {return 0;} >-void auth_cyrus_sasl_version_report(FILE *f) {} >+gstring * auth_cyrus_sasl_version_report(gstring * g) {return NULL;} > > #else /*!MACRO_PREDEF*/ > >@@ -378,7 +378,7 @@ > HDEBUG(D_auth) > debug_printf("Cyrus SASL library will not tell us the username: %s\n", > sasl_errstring(rc, NULL, NULL)); >- log_write(0, LOG_REJECT, "%s authenticator (%s):\n " >+ log_write(0, LOG_REJECT, "%s authenticator (%s): " > "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech, > sasl_errstring(rc, NULL, NULL)); > sasl_dispose(&conn); >@@ -397,7 +397,7 @@ > /* these are considered permanent failure codes */ > HDEBUG(D_auth) > debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL)); >- log_write(0, LOG_REJECT, "%s authenticator (%s):\n " >+ log_write(0, LOG_REJECT, "%s authenticator (%s): " > "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech, > sasl_errstring(rc, NULL, NULL)); > sasl_dispose(&conn); >@@ -427,7 +427,7 @@ > HDEBUG(D_auth) > debug_printf("Cyrus SASL library will not tell us the SSF: %s\n", > sasl_errstring(rc, NULL, NULL)); >- log_write(0, LOG_REJECT, "%s authenticator (%s):\n " >+ log_write(0, LOG_REJECT, "%s authenticator (%s): " > "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech, > sasl_errstring(rc, NULL, NULL)); > sasl_dispose(&conn); >@@ -441,7 +441,7 @@ > { > HDEBUG(D_auth) > debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf); >- log_write(0, LOG_REJECT, "%s authenticator (%s):\n " >+ log_write(0, LOG_REJECT, "%s authenticator (%s): " > "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf); > sasl_dispose(&conn); > sasl_done(); >@@ -476,15 +476,17 @@ > * Diagnostic API * > *************************************************/ > >-void >-auth_cyrus_sasl_version_report(FILE *f) >+gstring * >+auth_cyrus_sasl_version_report(gstring * g) > { >-const char *implementation, *version; >+const char * implementation, * version; > sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL); >-fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n" >- " Runtime: %s [%s]\n", >+g = string_fmt_append(g, >+ "Library version: Cyrus SASL: Compile: %d.%d.%d\n" >+ " Runtime: %s [%s]\n", > SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP, > version, implementation); >+return g; > } > > /************************************************* >diff -ur exim.orig/src/auths/cyrus_sasl.h exim/src/auths/cyrus_sasl.h >--- exim.orig/src/auths/cyrus_sasl.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/auths/cyrus_sasl.h 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2012 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -30,6 +31,6 @@ > extern void auth_cyrus_sasl_init(auth_instance *); > extern int auth_cyrus_sasl_server(auth_instance *, uschar *); > extern int auth_cyrus_sasl_client(auth_instance *, void *, int, uschar *, int); >-extern void auth_cyrus_sasl_version_report(FILE *f); >+extern gstring * auth_cyrus_sasl_version_report(gstring *); > > /* End of cyrus_sasl.h */ >diff -ur exim.orig/src/auths/dovecot.c exim/src/auths/dovecot.c >--- exim.orig/src/auths/dovecot.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/auths/dovecot.c 2022-06-23 16:41:10.000000000 +0300 >@@ -1,6 +1,6 @@ > /* >+ * Copyright (c) The Exim Maintainers 2006 - 2022 > * Copyright (c) 2004 Andrey Panin <pazke@donpac.ru> >- * Copyright (c) 2006-2020 The Exim Maintainers > * > * This program is free software; you can redistribute it and/or modify > * it under the terms of the GNU General Public License as published >@@ -275,11 +275,20 @@ > # ifndef DISABLE_TLS > if (ob->server_tls) > { >- uschar * s; >+ union sockaddr_46 interface_sock; >+ EXIM_SOCKLEN_T size = sizeof(interface_sock); > smtp_connect_args conn_args = { .host = &host }; >- tls_support tls_dummy = {.sni=NULL}; >+ tls_support tls_dummy = { .sni = NULL }; > uschar * errstr; > >+ if (getsockname(cctx->sock, (struct sockaddr *) &interface_sock, &size) == 0) >+ conn_args.sending_ip_address = host_ntoa(-1, &interface_sock, NULL, NULL); >+ else >+ { >+ *errmsg = string_sprintf("getsockname failed: %s", strerror(errno)); >+ goto bad; >+ } >+ > if (!tls_client_start(&cctx, &conn_args, NULL, &tls_dummy, &errstr)) > { > auth_defer_msg = string_sprintf("TLS connect failed: %s", errstr); >diff -ur exim.orig/src/auths/get_data.c exim/src/auths/get_data.c >--- exim.orig/src/auths/get_data.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/auths/get_data.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,7 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ >+/* Copyright (c) The Exim Maintainers 2020 - 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >diff -ur exim.orig/src/auths/gsasl_exim.c exim/src/auths/gsasl_exim.c >--- exim.orig/src/auths/gsasl_exim.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/auths/gsasl_exim.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,7 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >-/* Copyright (c) The Exim Maintainers 2019-2020 */ >+/* Copyright (c) The Exim Maintainers 2019 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -27,7 +27,6 @@ > */ > > #include "../exim.h" >-#define CHANNELBIND_HACK > > #ifndef AUTH_GSASL > /* dummy function to satisfy compilers when we link in an "empty" file. */ >@@ -40,14 +39,28 @@ > #include "gsasl_exim.h" > > >-#if GSASL_VERSION_MINOR >= 9 >+#if GSASL_VERSION_MINOR >= 10 >+# define EXIM_GSASL_HAVE_SCRAM_SHA_256 >+# define EXIM_GSASL_SCRAM_S_KEY >+ >+#elif GSASL_VERSION_MINOR == 9 > # define EXIM_GSASL_HAVE_SCRAM_SHA_256 > > # if GSASL_VERSION_PATCH >= 1 > # define EXIM_GSASL_SCRAM_S_KEY > # endif >+# if GSASL_VERSION_PATCH < 2 >+# define CHANNELBIND_HACK >+# endif >+ >+#else >+# define CHANNELBIND_HACK > #endif > >+/* Convenience for testing strings */ >+ >+#define STREQIC(Foo, Bar) (strcmpic((Foo), (Bar)) == 0) >+ > > /* Authenticator-specific options. */ > /* I did have server_*_condition options for various mechanisms, but since >@@ -99,7 +112,7 @@ > int auth_gsasl_server(auth_instance *ablock, uschar *data) {return 0;} > int auth_gsasl_client(auth_instance *ablock, void * sx, > int timeout, uschar *buffer, int buffsize) {return 0;} >-void auth_gsasl_version_report(FILE *f) {} >+gstring * auth_gsasl_version_report(gstring * g) {return NULL;} > > void > auth_gsasl_macros(void) >@@ -191,15 +204,21 @@ > "GNU SASL does not support mechanism \"%s\"", > ablock->name, ob->server_mech); > >-ablock->server = TRUE; >+if (ablock->server_condition) >+ ablock->server = TRUE; >+else if( ob->server_mech >+ && !STREQIC(ob->server_mech, US"EXTERNAL") >+ && !STREQIC(ob->server_mech, US"ANONYMOUS") >+ && !STREQIC(ob->server_mech, US"PLAIN") >+ && !STREQIC(ob->server_mech, US"LOGIN") >+ ) >+ { >+ /* At present, for mechanisms we don't panic on absence of server_condition; >+ need to figure out the most generically correct approach to deciding when >+ it's critical and when it isn't. Eg, for simple validation (PLAIN mechanism, >+ etc) it clearly is critical. >+ */ > >-if ( !ablock->server_condition >- && ( streqic(ob->server_mech, US"EXTERNAL") >- || streqic(ob->server_mech, US"ANONYMOUS") >- || streqic(ob->server_mech, US"PLAIN") >- || streqic(ob->server_mech, US"LOGIN") >- ) ) >- { > ablock->server = FALSE; > HDEBUG(D_auth) debug_printf("%s authenticator: " > "Need server_condition for %s mechanism\n", >@@ -210,7 +229,7 @@ > which properties will be needed. */ > > if ( !ob->server_realm >- && streqic(ob->server_mech, US"DIGEST-MD5")) >+ && STREQIC(ob->server_mech, US"DIGEST-MD5")) > { > ablock->server = FALSE; > HDEBUG(D_auth) debug_printf("%s authenticator: " >@@ -218,12 +237,6 @@ > ablock->name, ob->server_mech); > } > >-/* At present, for mechanisms we don't panic on absence of server_condition; >-need to figure out the most generically correct approach to deciding when >-it's critical and when it isn't. Eg, for simple validation (PLAIN mechanism, >-etc) it clearly is critical. >-*/ >- > ablock->client = ob->client_username && ob->client_password; > } > >@@ -299,44 +312,41 @@ > { > switch (prop) > { >- case GSASL_AUTHID: return US"AUTHID"; >- case GSASL_AUTHZID: return US"AUTHZID"; >- case GSASL_PASSWORD: return US"PASSWORD"; >- case GSASL_ANONYMOUS_TOKEN: return US"ANONYMOUS_TOKEN"; >- case GSASL_SERVICE: return US"SERVICE"; >- case GSASL_HOSTNAME: return US"HOSTNAME"; >- case GSASL_GSSAPI_DISPLAY_NAME: return US"GSSAPI_DISPLAY_NAME"; >- case GSASL_PASSCODE: return US"PASSCODE"; >- case GSASL_SUGGESTED_PIN: return US"SUGGESTED_PIN"; >- case GSASL_PIN: return US"PIN"; >- case GSASL_REALM: return US"REALM"; >- case GSASL_DIGEST_MD5_HASHED_PASSWORD: return US"DIGEST_MD5_HASHED_PASSWORD"; >- case GSASL_QOPS: return US"QOPS"; >- case GSASL_QOP: return US"QOP"; >- case GSASL_SCRAM_ITER: return US"SCRAM_ITER"; >- case GSASL_SCRAM_SALT: return US"SCRAM_SALT"; >- case GSASL_SCRAM_SALTED_PASSWORD: return US"SCRAM_SALTED_PASSWORD"; >-#ifdef EXIM_GSASL_SCRAM_S_KEY >- case GSASL_SCRAM_STOREDKEY: return US"SCRAM_STOREDKEY"; >- case GSASL_SCRAM_SERVERKEY: return US"SCRAM_SERVERKEY"; >-#endif >- case GSASL_CB_TLS_UNIQUE: return US"CB_TLS_UNIQUE"; >- case GSASL_SAML20_IDP_IDENTIFIER: return US"SAML20_IDP_IDENTIFIER"; >- case GSASL_SAML20_REDIRECT_URL: return US"SAML20_REDIRECT_URL"; >- case GSASL_OPENID20_REDIRECT_URL: return US"OPENID20_REDIRECT_URL"; >- case GSASL_OPENID20_OUTCOME_DATA: return US"OPENID20_OUTCOME_DATA"; >- case GSASL_SAML20_AUTHENTICATE_IN_BROWSER: return US"SAML20_AUTHENTICATE_IN_BROWSER"; >- case GSASL_OPENID20_AUTHENTICATE_IN_BROWSER: return US"OPENID20_AUTHENTICATE_IN_BROWSER"; >+ case GSASL_AUTHID: return US"AUTHID"; >+ case GSASL_AUTHZID: return US"AUTHZID"; >+ case GSASL_PASSWORD: return US"PASSWORD"; >+ case GSASL_ANONYMOUS_TOKEN: return US"ANONYMOUS_TOKEN"; >+ case GSASL_SERVICE: return US"SERVICE"; >+ case GSASL_HOSTNAME: return US"HOSTNAME"; >+ case GSASL_GSSAPI_DISPLAY_NAME: return US"GSSAPI_DISPLAY_NAME"; >+ case GSASL_PASSCODE: return US"PASSCODE"; >+ case GSASL_SUGGESTED_PIN: return US"SUGGESTED_PIN"; >+ case GSASL_PIN: return US"PIN"; >+ case GSASL_REALM: return US"REALM"; >+ case GSASL_DIGEST_MD5_HASHED_PASSWORD: return US"DIGEST_MD5_HASHED_PASSWORD"; >+ case GSASL_QOPS: return US"QOPS"; >+ case GSASL_QOP: return US"QOP"; >+ case GSASL_SCRAM_ITER: return US"SCRAM_ITER"; >+ case GSASL_SCRAM_SALT: return US"SCRAM_SALT"; >+ case GSASL_SCRAM_SALTED_PASSWORD: return US"SCRAM_SALTED_PASSWORD"; > #ifdef EXIM_GSASL_SCRAM_S_KEY >- case GSASL_SCRAM_CLIENTKEY: return US"SCRAM_CLIENTKEY"; >+ case GSASL_SCRAM_STOREDKEY: return US"SCRAM_STOREDKEY"; >+ case GSASL_SCRAM_SERVERKEY: return US"SCRAM_SERVERKEY"; > #endif >- case GSASL_VALIDATE_SIMPLE: return US"VALIDATE_SIMPLE"; >- case GSASL_VALIDATE_EXTERNAL: return US"VALIDATE_EXTERNAL"; >- case GSASL_VALIDATE_ANONYMOUS: return US"VALIDATE_ANONYMOUS"; >- case GSASL_VALIDATE_GSSAPI: return US"VALIDATE_GSSAPI"; >- case GSASL_VALIDATE_SECURID: return US"VALIDATE_SECURID"; >- case GSASL_VALIDATE_SAML20: return US"VALIDATE_SAML20"; >- case GSASL_VALIDATE_OPENID20: return US"VALIDATE_OPENID20"; >+ case GSASL_CB_TLS_UNIQUE: return US"CB_TLS_UNIQUE"; >+ case GSASL_SAML20_IDP_IDENTIFIER: return US"SAML20_IDP_IDENTIFIER"; >+ case GSASL_SAML20_REDIRECT_URL: return US"SAML20_REDIRECT_URL"; >+ case GSASL_OPENID20_REDIRECT_URL: return US"OPENID20_REDIRECT_URL"; >+ case GSASL_OPENID20_OUTCOME_DATA: return US"OPENID20_OUTCOME_DATA"; >+ case GSASL_SAML20_AUTHENTICATE_IN_BROWSER: return US"SAML20_AUTHENTICATE_IN_BROWSER"; >+ case GSASL_OPENID20_AUTHENTICATE_IN_BROWSER: return US"OPENID20_AUTHENTICATE_IN_BROWSER"; >+ case GSASL_VALIDATE_SIMPLE: return US"VALIDATE_SIMPLE"; >+ case GSASL_VALIDATE_EXTERNAL: return US"VALIDATE_EXTERNAL"; >+ case GSASL_VALIDATE_ANONYMOUS: return US"VALIDATE_ANONYMOUS"; >+ case GSASL_VALIDATE_GSSAPI: return US"VALIDATE_GSSAPI"; >+ case GSASL_VALIDATE_SECURID: return US"VALIDATE_SECURID"; >+ case GSASL_VALIDATE_SAML20: return US"VALIDATE_SAML20"; >+ case GSASL_VALIDATE_OPENID20: return US"VALIDATE_OPENID20"; > } > return CUS string_sprintf("(unknown prop: %d)", (int)prop); > } >@@ -365,7 +375,7 @@ > #ifndef DISABLE_TLS > if (tls_in.channelbinding && ob->server_channelbinding) > { >-# ifdef EXPERIMENTAL_TLS_RESUME >+# ifndef DISABLE_TLS_RESUME > if (!tls_in.ext_master_secret && tls_in.resumption == RESUME_USED) > { /* per RFC 7677 section 4 */ > HDEBUG(D_auth) debug_printf( >@@ -374,9 +384,9 @@ > } > # endif > # ifdef CHANNELBIND_HACK >-/* This is a gross hack to get around the library a) requiring that >-c-b was already set, at the _start() call, and b) caching a b64'd >-version of the binding then which it never updates. */ >+/* This is a gross hack to get around the library before 1.9.2 >+a) requiring that c-b was already set, at the _start() call, and >+b) caching a b64'd version of the binding then which it never updates. */ > > gsasl_callback_hook_set(gsasl_ctx, tls_in.channelbinding); > # endif >@@ -429,6 +439,12 @@ > would then result in mechanism name changes on a library update, we > have little choice but to default it off and let the admin choose to > enable it. *sigh* >+ >+ Earlier library versions need this set early, during the _start() call, >+ so we had to misuse gsasl_callback_hook_set/get() as a data transfer >+ mech for the callback done at that time to get the bind-data. More recently >+ the callback is done (if needed) during the first gsasl_stop(). We know >+ the bind-data here so can set it (and should not get a callback). > */ > if (ob->server_channelbinding) > { >@@ -570,14 +586,19 @@ > } > > >+/* Set the "next" $auth[n] and increment expand_nmax */ >+ > static void > set_exim_authvar_from_prop(Gsasl_session * sctx, Gsasl_property prop) > { > uschar * propval = US gsasl_property_fast(sctx, prop); > int i = expand_nmax, j = i + 1; > propval = propval ? string_copy(propval) : US""; >-auth_vars[i] = expand_nstring[j] = propval; >+HDEBUG(D_auth) debug_printf("auth[%d] <= %s'%s'\n", >+ j, gsasl_prop_code_to_name(prop), propval); >+expand_nstring[j] = propval; > expand_nlength[j] = Ustrlen(propval); >+if (i < AUTH_VARS) auth_vars[i] = propval; > expand_nmax = j; > } > >@@ -621,10 +642,10 @@ > server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, > auth_instance *ablock) > { >-char *tmps; >-uschar *s, *propval; >+char * tmps; >+uschar * s; > int cbrc = GSASL_NO_CALLBACK; >-auth_gsasl_options_block *ob = >+auth_gsasl_options_block * ob = > (auth_gsasl_options_block *)(ablock->options_block); > > HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as server\n", >@@ -740,7 +761,7 @@ > for memory wiping, so expanding strings will leave stuff laying around. > But no need to compound the problem, so get rid of the one we can. */ > >- memset(tmps, '\0', strlen(tmps)); >+ if (US tmps != s) memset(tmps, '\0', strlen(tmps)); > cbrc = GSASL_OK; > break; > >@@ -765,7 +786,6 @@ > unsigned flags, uschar * buffer, int buffsize) > { > uschar * s; >-int rc; > > if (!val) return !!(flags & PROP_OPTIONAL); > if (!(s = expand_string(val)) || !(flags & PROP_OPTIONAL) && !*s) >@@ -814,18 +834,19 @@ > #ifndef DISABLE_TLS > if (tls_out.channelbinding && ob->client_channelbinding) > { >-# ifdef EXPERIMENTAL_TLS_RESUME >+# ifndef DISABLE_TLS_RESUME > if (!tls_out.ext_master_secret && tls_out.resumption == RESUME_USED) >- { /* per RFC 7677 section 4 */ >+ { /* Per RFC 7677 section 4. See also RFC 7627, "Triple Handshake" >+ vulnerability, and https://www.mitls.org/pages/attacks/3SHAKE */ > string_format(buffer, buffsize, "%s", > "channel binding not usable on resumed TLS without extended-master-secret"); > return FAIL; > } > # endif > # ifdef CHANNELBIND_HACK >- /* This is a gross hack to get around the library a) requiring that >- c-b was already set, at the _start() call, and b) caching a b64'd >- version of the binding then which it never updates. */ >+ /* This is a gross hack to get around the library before 1.9.2 >+ a) requiring that c-b was already set, at the _start() call, and >+ b) caching a b64'd version of the binding then which it never updates. */ > > gsasl_callback_hook_set(gsasl_ctx, tls_out.channelbinding); > # endif >@@ -846,10 +867,7 @@ > > /* Set properties */ > >-if ( !set_client_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD, ob->client_spassword, >- 0, buffer, buffsize) >- && >- !set_client_prop(sctx, GSASL_PASSWORD, ob->client_password, >+if ( !set_client_prop(sctx, GSASL_PASSWORD, ob->client_password, > 0, buffer, buffsize) > || !set_client_prop(sctx, GSASL_AUTHID, ob->client_username, > 0, buffer, buffsize) >@@ -879,24 +897,27 @@ > for(s = NULL; ;) > { > uschar * outstr; >- BOOL fail; >+ BOOL fail = TRUE; > > rc = gsasl_step64(sctx, CS s, CSS &outstr); > >- fail = initial >- ? smtp_write_command(sx, SCMD_FLUSH, >- outstr ? "AUTH %s %s\r\n" : "AUTH %s\r\n", >- ablock->public_name, outstr) <= 0 >- : outstr >- ? smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", outstr) <= 0 >- : FALSE; >- if (outstr && *outstr) free(outstr); >- if (fail) >+ if (rc == GSASL_NEEDS_MORE || rc == GSASL_OK) > { >- yield = FAIL_SEND; >- goto done; >+ fail = initial >+ ? smtp_write_command(sx, SCMD_FLUSH, >+ outstr ? "AUTH %s %s\r\n" : "AUTH %s\r\n", >+ ablock->public_name, outstr) <= 0 >+ : outstr >+ ? smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", outstr) <= 0 >+ : FALSE; >+ free(outstr); >+ if (fail) >+ { >+ yield = FAIL_SEND; >+ goto done; >+ } >+ initial = FALSE; > } >- initial = FALSE; > > if (rc != GSASL_NEEDS_MORE) > { >@@ -927,10 +948,13 @@ > } > > done: >-HDEBUG(D_auth) >+if (yield == OK) > { >- const uschar * s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SALTED_PASSWORD); >- if (s) debug_printf(" - SaltedPassword: '%s'\n", s); >+ expand_nmax = 0; >+ set_exim_authvar_from_prop(sctx, GSASL_AUTHID); >+ set_exim_authvar_from_prop(sctx, GSASL_SCRAM_ITER); >+ set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALT); >+ set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD); > } > > gsasl_finish(sctx); >@@ -944,11 +968,31 @@ > gsasl_prop_code_to_name(prop), ablock->name, ablock->public_name); > switch (prop) > { >- case GSASL_CB_TLS_UNIQUE: >+ case GSASL_CB_TLS_UNIQUE: /*XXX should never get called for this */ > HDEBUG(D_auth) > debug_printf(" filling in\n"); > gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding); >+ return GSASL_OK; >+ case GSASL_SCRAM_SALTED_PASSWORD: >+ { >+ uschar * client_spassword = >+ ((auth_gsasl_options_block *) ablock->options_block)->client_spassword; >+ uschar dummy[4]; >+ HDEBUG(D_auth) if (!client_spassword) >+ debug_printf(" client_spassword option unset\n"); >+ if (client_spassword) >+ { >+ expand_nmax = 0; >+ set_exim_authvar_from_prop(sctx, GSASL_AUTHID); >+ set_exim_authvar_from_prop(sctx, GSASL_SCRAM_ITER); >+ set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALT); >+ set_client_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD, client_spassword, >+ 0, dummy, sizeof(dummy)); >+ for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; >+ expand_nmax = 0; >+ } > break; >+ } > default: > HDEBUG(D_auth) > debug_printf(" not providing one\n"); >@@ -961,14 +1005,12 @@ > * Diagnostic API * > *************************************************/ > >-void >-auth_gsasl_version_report(FILE *f) >+gstring * >+auth_gsasl_version_report(gstring * g) > { >-const char *runtime; >-runtime = gsasl_check_version(NULL); >-fprintf(f, "Library version: GNU SASL: Compile: %s\n" >- " Runtime: %s\n", >- GSASL_VERSION, runtime); >+return string_fmt_append(g, "Library version: GNU SASL: Compile: %s\n" >+ " Runtime: %s\n", >+ GSASL_VERSION, gsasl_check_version(NULL)); > } > > >diff -ur exim.orig/src/auths/gsasl_exim.h exim/src/auths/gsasl_exim.h >--- exim.orig/src/auths/gsasl_exim.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/auths/gsasl_exim.h 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2019 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2012 */ >-/* Copyright (c) The Exim Maintainers 2019-2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Copyright (c) Twitter Inc 2012 */ >@@ -47,7 +47,7 @@ > extern int auth_gsasl_server(auth_instance *, uschar *); > extern int auth_gsasl_client(auth_instance *, void *, > int, uschar *, int); >-extern void auth_gsasl_version_report(FILE *f); >+extern gstring * auth_gsasl_version_report(gstring *); > extern void auth_gsasl_macros(void); > > /* End of gsasl_exim.h */ >diff -ur exim.orig/src/auths/heimdal_gssapi.c exim/src/auths/heimdal_gssapi.c >--- exim.orig/src/auths/heimdal_gssapi.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/auths/heimdal_gssapi.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Copyright (c) Twitter Inc 2012 >@@ -85,7 +85,7 @@ > int auth_heimdal_gssapi_server(auth_instance *ablock, uschar *data) {return 0;} > int auth_heimdal_gssapi_client(auth_instance *ablock, void * sx, > int timeout, uschar *buffer, int buffsize) {return 0;} >-void auth_heimdal_gssapi_version_report(FILE *f) {} >+gstring * auth_heimdal_gssapi_version_report(gstring * g) {} > > #else /*!MACRO_PREDEF*/ > >@@ -601,14 +601,15 @@ > * Diagnostic API * > *************************************************/ > >-void >-auth_heimdal_gssapi_version_report(FILE *f) >+gstring * >+auth_heimdal_gssapi_version_report(gstring * g) > { > /* No build-time constants available unless we link against libraries at > build-time and export the result as a string into a header ourselves. */ >-fprintf(f, "Library version: Heimdal: Runtime: %s\n" >- " Build Info: %s\n", >- heimdal_version, heimdal_long_version); >+ >+return string_fmt_append(g, "Library version: Heimdal: Runtime: %s\n" >+ " Build Info: %s\n", >+ heimdal_version, heimdal_long_version)); > } > > #endif /*!MACRO_PREDEF*/ >diff -ur exim.orig/src/auths/heimdal_gssapi.h exim/src/auths/heimdal_gssapi.h >--- exim.orig/src/auths/heimdal_gssapi.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/auths/heimdal_gssapi.h 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2012 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -33,6 +34,6 @@ > extern void auth_heimdal_gssapi_init(auth_instance *); > extern int auth_heimdal_gssapi_server(auth_instance *, uschar *); > extern int auth_heimdal_gssapi_client(auth_instance *, void *, int, uschar *, int); >-extern void auth_heimdal_gssapi_version_report(FILE *f); >+extern void auth_heimdal_gssapi_version_report(BOOL); > > /* End of heimdal_gssapi.h */ >diff -ur exim.orig/src/auths/plaintext.c exim/src/auths/plaintext.c >--- exim.orig/src/auths/plaintext.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/auths/plaintext.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,7 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ >+/* Copyright (c) The Exim Maintainers 2020 - 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >@@ -109,7 +109,7 @@ > out as prompts, and get a data item back. If the data item is "*", abandon the > authentication attempt. Otherwise, split it into items as above. */ > >-while ( (s = string_nextinlist(&prompts, &sep, big_buffer, big_buffer_size)) >+while ( (s = string_nextinlist(&prompts, &sep, NULL, 0)) > && expand_nmax < EXPAND_MAXN) > if (number++ > expand_nmax) > if ((rc = auth_prompt(CUS s)) != OK) >diff -ur exim.orig/src/auths/pwcheck.c exim/src/auths/pwcheck.c >--- exim.orig/src/auths/pwcheck.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/auths/pwcheck.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > * Tim Martin > * $Id: checkpw.c,v 1.49 2002/03/07 19:14:04 ken3 Exp $ > */ >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ > /* > * Copyright (c) 2001 Carnegie Mellon University. All rights reserved. > * >@@ -86,8 +87,6 @@ > const char *passwd, > const char **reply) > { >-userid = userid; /* Keep picky compilers happy */ >-passwd = passwd; > *reply = "pwcheck support is not included in this Exim binary"; > return PWCHECK_FAIL; > } >@@ -163,10 +162,6 @@ > const uschar *realm, > const uschar **reply) > { >-userid = userid; /* Keep picky compilers happy */ >-passwd = passwd; >-service = service; >-realm = realm; > *reply = US"saslauthd support is not included in this Exim binary"; > return PWCHECK_FAIL; > } >@@ -297,7 +292,7 @@ > return -1; > } else { > /* Assume the file is trusted, so no tainting */ >- *retval = store_get(count + 1, FALSE); >+ *retval = store_get(count + 1, GET_UNTAINTED); > rc = (retry_read(fd, *retval, count) < (int) count); > (*retval)[count] = '\0'; > return count; >diff -ur exim.orig/src/auths/xtextdecode.c exim/src/auths/xtextdecode.c >--- exim.orig/src/auths/xtextdecode.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/auths/xtextdecode.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2009 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -33,7 +34,7 @@ > auth_xtextdecode(uschar *code, uschar **ptr) > { > register int x; >-uschar *result = store_get(Ustrlen(code) + 1, is_tainted(code)); >+uschar * result = store_get(Ustrlen(code) + 1, code); > *ptr = result; > > while ((x = (*code++)) != 0) >diff -ur exim.orig/src/auths/xtextencode.c exim/src/auths/xtextencode.c >--- exim.orig/src/auths/xtextencode.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/auths/xtextencode.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -40,7 +41,7 @@ > while (c -- > 0) > count += ((x = *p++) < 33 || x > 127 || x == '+' || x == '=')? 3 : 1; > >-pp = code = store_get(count, is_tainted(clear)); >+pp = code = store_get(count, clear); > > p = US clear; > c = len; >diff -ur exim.orig/src/base64.c exim/src/base64.c >--- exim.orig/src/base64.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/base64.c 2022-06-23 16:41:10.000000000 +0300 >@@ -6,7 +6,7 @@ > /* License: GPL */ > > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -157,10 +157,10 @@ > int x, y; > uschar *result; > >-{ >+ { > int l = Ustrlen(code); >- *ptr = result = store_get(1 + l/4 * 3 + l%4, is_tainted(code)); >-} >+ *ptr = result = store_get(1 + l/4 * 3 + l%4, code); >+ } > > /* Each cycle of the loop handles a quantum of 4 input bytes. For the last > quantum this may decode to 1, 2, or 3 output bytes. */ >@@ -234,6 +234,7 @@ > Arguments: > clear points to the clear text bytes > len the number of bytes to encode >+ proto_mem taint indicator > > Returns: a pointer to the zero-terminated base 64 string, which > is in working store >@@ -243,10 +244,10 @@ > US"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; > > uschar * >-b64encode_taint(const uschar * clear, int len, BOOL tainted) >+b64encode_taint(const uschar * clear, int len, const void * proto_mem) > { >-uschar *code = store_get(4*((len+2)/3) + 1, tainted); >-uschar *p = code; >+uschar * code = store_get(4*((len+2)/3) + 1, proto_mem); >+uschar * p = code; > > while (len-- >0) > { >@@ -287,7 +288,7 @@ > uschar * > b64encode(const uschar * clear, int len) > { >-return b64encode_taint(clear, len, is_tainted(clear)); >+return b64encode_taint(clear, len, clear); > } > > >diff -ur exim.orig/src/bmi_spam.c exim/src/bmi_spam.c >--- exim.orig/src/bmi_spam.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/bmi_spam.c 2022-06-23 16:41:10.000000000 +0300 >@@ -5,6 +5,7 @@ > /* Code for calling Brightmail AntiSpam. > Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 > License: GPL */ >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ > > #include "exim.h" > #ifdef EXPERIMENTAL_BRIGHTMAIL >@@ -193,16 +194,16 @@ > /* 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, TRUE); >+ verdicts = store_get(1, GET_TAINTED); > *verdicts = '\0'; > > for ( err = bmiAccessFirstVerdict(message, &verdict); >- verdict != NULL; >+ verdict; > err = bmiAccessNextVerdict(message, verdict, &verdict) ) { > char *verdict_str; > > err = bmiCreateStrFromVerdict(verdict,&verdict_str); >- if (!store_extend(verdicts, TRUE, >+ if (!store_extend(verdicts, > Ustrlen(verdicts)+1, Ustrlen(verdicts)+1+strlen(verdict_str)+1)) { > /* can't allocate more store */ > return NULL; >@@ -302,7 +303,7 @@ > } > else { > /* deliver to alternate location */ >- rc = store_get(strlen(bmiVerdictAccessDestination(verdict))+1, TRUE); >+ rc = store_get(strlen(bmiVerdictAccessDestination(verdict))+1, GET_TAINTED); > Ustrcpy(rc, bmiVerdictAccessDestination(verdict)); > rc[strlen(bmiVerdictAccessDestination(verdict))] = '\0'; > }; >@@ -327,7 +328,7 @@ > return NULL; > > /* allocate room for the b64 verdict string */ >- verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1, TRUE); >+ verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1, GET_TAINTED); > > /* loop through verdicts */ > verdict_ptr = bmi_verdicts; >@@ -448,9 +449,11 @@ > } > > /* 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, 32)) != NULL) { >+ rule_buffer, sizeof(rule_buffer)))) { > int rule_int = -1; > > /* try to translate to int */ >diff -ur exim.orig/src/buildconfig.c exim/src/buildconfig.c >--- exim.orig/src/buildconfig.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/buildconfig.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -51,7 +52,7 @@ > char *data; > } save_item; > >-static const char *db_opts[] = { "", "USE_DB", "USE_GDBM", "USE_TDB" }; >+static const char *db_opts[] = { "", "USE_DB", "USE_GDBM", "USE_TDB", "USE_NDBM" }; > > static int have_ipv6 = 0; > static int have_iconv = 0; >@@ -221,8 +222,7 @@ > > /* Now search the makefile for certain settings */ > >-base = fopen("Makefile", "rb"); >-if (base == NULL) >+if (!(base = fopen("Makefile", "rb"))) > { > printf("*** Buildconfig: failed to open Makefile\n"); > (void)fclose(new); >@@ -387,7 +387,6 @@ > encountered. */ > > for (i = 1; i < sizeof(db_opts)/sizeof(char *); i++) >- { > if (strcmp(name, db_opts[i]) == 0) > { > if (use_which_db == i) >@@ -397,7 +396,6 @@ > fprintf(new, "/* %s not set */\n", name); > break; > } >- } > if (i < sizeof(db_opts)/sizeof(char *)) continue; > > /* EXIM_USER is a special case. We look in the environment for EXIM_USER or >diff -ur exim.orig/src/child.c exim/src/child.c >--- exim.orig/src/child.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/child.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2015 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -27,7 +27,7 @@ > Returns: nothing > */ > >-static void >+void > force_fd(int oldfd, int newfd) > { > if (oldfd == newfd) return; >@@ -76,22 +76,19 @@ > int extra = pcount ? *pcount : 0; > uschar **argv; > >-argv = store_get((extra + acount + MAX_CLMACROS + 21) * sizeof(char *), FALSE); >+argv = store_get((extra + acount + MAX_CLMACROS + 24) * sizeof(char *), GET_UNTAINTED); > > /* In all case, the list starts out with the path, any macros, and a changed > config file. */ > >-argv[n++] = exim_path; >+argv[n++] = exim_path; /* assume untainted */ > if (clmacro_count > 0) > { > memcpy(argv + n, clmacros, clmacro_count * sizeof(uschar *)); > n += clmacro_count; > } > if (f.config_changed) >- { >- argv[n++] = US"-C"; >- argv[n++] = config_main_filename; >- } >+ { argv[n++] = US"-C"; argv[n++] = config_main_filename; } > > /* These values are added only for non-minimal cases. If debug_selector is > precisely D_v, we have to assume this was started by a non-admin user, and >@@ -108,24 +105,35 @@ > else > { > if (debug_selector != 0) >+ { > argv[n++] = string_sprintf("-d=0x%x", debug_selector); >+ if (debug_fd > 2) >+ { >+ int flags = fcntl(debug_fd, F_GETFD); >+ if (flags != -1) (void)fcntl(debug_fd, F_SETFD, flags & ~FD_CLOEXEC); >+ close(2); >+ dup2(debug_fd, 2); >+ close(debug_fd); >+ } >+ } > } >+ if (debug_pretrigger_buf) >+ { argv[n++] = US"-dp"; argv[n++] = string_sprintf("0x%x", debug_pretrigger_bsize); } >+ if (dtrigger_selector != 0) >+ argv[n++] = string_sprintf("-dt=0x%x", dtrigger_selector); > DEBUG(D_any) > { > argv[n++] = US"-MCd"; > argv[n++] = US process_purpose; > } >- if (!f.testsuite_delays) argv[n++] = US"-odd"; >- if (f.dont_deliver) argv[n++] = US"-N"; >- if (f.queue_smtp) argv[n++] = US"-odqs"; >- if (f.synchronous_delivery) argv[n++] = US"-odi"; >+ if (!f.testsuite_delays) argv[n++] = US"-odd"; >+ if (f.dont_deliver) argv[n++] = US"-N"; >+ if (f.queue_smtp) argv[n++] = US"-odqs"; >+ if (f.synchronous_delivery) argv[n++] = US"-odi"; > if (connection_max_messages >= 0) > argv[n++] = string_sprintf("-oB%d", connection_max_messages); > if (*queue_name) >- { >- argv[n++] = US"-MCG"; >- argv[n++] = queue_name; >- } >+ { argv[n++] = US"-MCG"; argv[n++] = queue_name; } > } > > /* Now add in any others that are in the call. Remember which they were, >@@ -159,7 +167,7 @@ > execv(CS argv[0], (char *const *)argv); > > log_write(0, >- LOG_MAIN | ((exec_type == CEE_EXEC_EXIT)? LOG_PANIC : LOG_PANIC_DIE), >+ LOG_MAIN | (exec_type == CEE_EXEC_EXIT ? LOG_PANIC : LOG_PANIC_DIE), > "re-exec of exim (%s) with %s failed: %s", exim_path, argv[first_special], > strerror(errno)); > >@@ -269,6 +277,8 @@ > } > } > >+testharness_pause_ms(100); /* let child work even longer, for exec */ >+ > /* Parent process. Save fork() errno and close the reading end of the stdin > pipe. */ > >@@ -333,6 +343,13 @@ > int inpfd[2], outpfd[2]; > pid_t pid; > >+if (is_tainted(argv[0])) >+ { >+ log_write(0, LOG_MAIN | LOG_PANIC, "Attempt to exec tainted path: '%s'", argv[0]); >+ errno = EPERM; >+ return (pid_t)(-1); >+ } >+ > /* Create the pipes. */ > > if (pipe(inpfd) != 0) return (pid_t)(-1); >ТолÑко в exim/src: cnumber.h >diff -ur exim.orig/src/config.h.defaults exim/src/config.h.defaults >--- exim.orig/src/config.h.defaults 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/config.h.defaults 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2018 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2018-2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* The default settings for Exim configuration variables. A #define without >@@ -31,7 +31,7 @@ > #define AUTH_SPA > #define AUTH_TLS > >-#define AUTH_VARS 3 >+#define AUTH_VARS 4 > > #define BIN_DIRECTORY > >@@ -46,14 +46,18 @@ > #define DEFAULT_CRYPT crypt > #define DELIVER_IN_BUFFER_SIZE 8192 > #define DELIVER_OUT_BUFFER_SIZE 8192 >+ >+#define DISABLE_CLIENT_CMD_LOG >+#define DISABLE_D_OPTION > #define DISABLE_DNSSEC > #define DISABLE_DKIM > #define DISABLE_EVENT > #define DISABLE_OCSP > #define DISABLE_PIPE_CONNECT > #define DISABLE_PRDR >+#define DISABLE_QUEUE_RAMP > #define DISABLE_TLS >-#define DISABLE_D_OPTION >+#define DISABLE_TLS_RESUME > > #define ENABLE_DISABLE_FSYNC > >@@ -97,6 +101,7 @@ > #define LOOKUP_IBASE > #define LOOKUP_JSON > #define LOOKUP_LDAP >+#define LOOKUP_LMDB > #define LOOKUP_LSEARCH > #define LOOKUP_MYSQL > #define LOOKUP_NIS >@@ -156,6 +161,7 @@ > #define SUPPORT_PROXY > #define SUPPORT_SOCKS > #define SUPPORT_SPF >+#define SUPPORT_SRS > #define SUPPORT_TRANSLATE_IP_ADDRESS > > #define SYSLOG_LOG_PID >@@ -175,6 +181,7 @@ > #define USE_GDBM > #define USE_GNUTLS > #define AVOID_GNUTLS_PKCS11 >+#define USE_NDBM > #define USE_OPENSSL > #define USE_READLINE > #define USE_TCP_WRAPPERS >@@ -201,12 +208,8 @@ > #define EXPERIMENTAL_BRIGHTMAIL > #define EXPERIMENTAL_DCC > #define EXPERIMENTAL_DSN_INFO >-#define EXPERIMENTAL_LMDB >-#define EXPERIMENTAL_QUEUE_RAMP >+#define EXPERIMENTAL_ESMTP_LIMITS > #define EXPERIMENTAL_QUEUEFILE >-#define EXPERIMENTAL_SRS >-#define EXPERIMENTAL_SRS_NATIVE >-#define EXPERIMENTAL_TLS_RESUME > > > /* For developers */ >diff -ur exim.orig/src/configure.default exim/src/configure.default >--- exim.orig/src/configure.default 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/configure.default 2022-06-23 16:41:10.000000000 +0300 >@@ -147,15 +147,15 @@ > # spamd_address = 127.0.0.1 783 > > >-# If Exim is compiled with support for TLS, you may want to enable the >-# following options so that Exim allows clients to make encrypted >-# connections. In the authenticators section below, there are template >-# configurations for plaintext username/password authentication. This kind >-# of authentication is only safe when used within a TLS connection, so the >-# authenticators will only work if the following TLS settings are turned on >-# as well. >+# If Exim is compiled with support for TLS, you may want to change the >+# following option so that Exim disallows certain clients from makeing encrypted >+# connections. The default is to allow all. >+# In the authenticators section below, there are template configurations for >+# plaintext username/password authentication. This kind of authentication is >+# only safe when used within a TLS connection, so the authenticators will only >+# work if TLS is allowed here. > >-# Allow any client to use TLS. >+# This is equivalent to the default. > > # tls_advertise_hosts = * > >@@ -169,7 +169,16 @@ > # tls_privatekey = /etc/ssl/exim.pem > > # For OpenSSL, prefer EC- over RSA-authenticated ciphers >-# tls_require_ciphers = ECDSA:RSA:!COMPLEMENTOFDEFAULT >+.ifdef _HAVE_OPENSSL >+tls_require_ciphers = ECDSA:RSA:!COMPLEMENTOFDEFAULT >+.endif >+ >+# Don't offer resumption to (most) MUAs, who we don't want to reuse >+# tickets. Once the TLS extension for vended ticket numbers comes >+# though, re-examine since resumption on a single-use ticket is still a benefit. >+.ifdef _HAVE_TLS_RESUME >+tls_resumption_hosts = ${if inlist {$received_port}{587:465} {:}{*}} >+.endif > > # In order to support roaming users who wish to send email from anywhere, > # you may want to make Exim listen on other ports as well as port 25, in >@@ -495,11 +504,6 @@ > control = submission > control = dkim_disable_verify > >- # Insist that a HELO/EHLO was accepted. >- >- require message = nice hosts say HELO first >- condition = ${if def:sender_helo_name} >- > # Insist that any other recipient address that we accept is either in one of > # our local domains, or is in a domain for which we explicitly allow > # relaying. Any other domain is rejected as being unacceptable for relaying. >@@ -545,11 +549,11 @@ > # to the first recipient must be deferred unless the sender talks PRDR. > # > # defer !condition = $prdr_requested >- # condition = ${if > {0}{$receipients_count}} >+ # condition = ${if > {0}{$recipients_count}} > # condition = ${if !eq {$acl_m_content_filter} \ > # {${lookup PER_RCPT_CONTENT_FILTER}}} > # warn !condition = $prdr_requested >- # condition = ${if > {0}{$receipients_count}} >+ # condition = ${if > {0}{$recipients_count}} > # set acl_m_content_filter = ${lookup PER_RCPT_CONTENT_FILTER} > ############################################################################# > >@@ -815,13 +819,12 @@ > > > # This transport is used for delivering messages over SMTP connections. >-# Refuse to send any message with over-long lines, which could have >-# been received other than via SMTP. The use of message_size_limit to >-# enforce this is a red herring. > > remote_smtp: > driver = smtp >- message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}} >+.ifdef _HAVE_TLS_RESUME >+ tls_resumption_hosts = * >+.endif > > > # This transport is used for delivering messages to a smarthost, if the >@@ -833,7 +836,6 @@ > > smarthost_smtp: > driver = smtp >- message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}} > multi_domain > # > .ifdef _HAVE_TLS >@@ -841,9 +843,9 @@ > # request with your smarthost provider to get things fixed: > hosts_require_tls = * > tls_verify_hosts = * >- # As long as tls_verify_hosts is enabled, this won't matter, but if you >- # have to comment it out then this will at least log whether you succeed >- # or not: >+ # As long as tls_verify_hosts is enabled, this this will have no effect, >+ # but if you have to comment it out then this will at least log whether >+ # you succeed or not: > tls_try_verify_hosts = * > # > # The SNI name should match the name which we'll expect to verify; >@@ -859,6 +861,9 @@ > .ifdef _HAVE_GNUTLS > tls_require_ciphers = SECURE192:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1 > .endif >+.ifdef _HAVE_TLS_RESUME >+ tls_resumption_hosts = * >+.endif > .endif > > >diff -ur exim.orig/src/daemon.c exim/src/daemon.c >--- exim.orig/src/daemon.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/daemon.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions concerned with running Exim as a daemon */ >@@ -59,7 +59,6 @@ > static void > sighup_handler(int sig) > { >-sig = sig; /* Keep picky compilers happy */ > sighup_seen = TRUE; > signal(SIGHUP, sighup_handler); > } >@@ -83,13 +82,12 @@ > static void > main_sigchld_handler(int sig) > { >-sig = sig; /* Keep picky compilers happy */ > os_non_restarting_signal(SIGCHLD, SIG_DFL); > sigchld_seen = TRUE; > } > > >-/* SIGTERM handler. Try to get the damon pif file removed >+/* SIGTERM handler. Try to get the daemon pid file removed > before exiting. */ > > static void >@@ -143,7 +141,7 @@ > > static void > close_daemon_sockets(int daemon_notifier_fd, >- int * listen_sockets, int listen_socket_count) >+ struct pollfd * fd_polls, int listen_socket_count) > { > if (daemon_notifier_fd >= 0) > { >@@ -154,7 +152,7 @@ > #endif > } > >-for (int i = 0; i < listen_socket_count; i++) (void) close(listen_sockets[i]); >+for (int i = 0; i < listen_socket_count; i++) (void) close(fd_polls[i].fd); > } > > >@@ -169,7 +167,7 @@ > leak store in this process - reset the stacking pool at the end. > > Arguments: >- listen_sockets sockets which are listening for incoming calls >+ fd_polls sockets which are listening for incoming calls > listen_socket_count count of listening sockets > accept_socket socket of the current accepted call > accepted socket information about the current call >@@ -178,7 +176,7 @@ > */ > > static void >-handle_smtp_call(int *listen_sockets, int listen_socket_count, >+handle_smtp_call(struct pollfd *fd_polls, int listen_socket_count, > int accept_socket, struct sockaddr *accepted) > { > pid_t pid; >@@ -277,7 +275,7 @@ > if (smtp_load_reserve >= 0) > { > load_average = OS_GETLOADAVG(); >- if (smtp_reserve_hosts == NULL && load_average > smtp_load_reserve) >+ if (!smtp_reserve_hosts && load_average > smtp_load_reserve) > { > DEBUG(D_any) debug_printf("rejecting SMTP connection: load average = %.2f\n", > (double)load_average/1000.0); >@@ -297,10 +295,10 @@ > this is in the daemon mainline, only fast expansions (such as inline address > checks) should be used. The documentation is full of warnings. */ > >-if (smtp_accept_max_per_host != NULL) >+if (smtp_accept_max_per_host) > { > uschar *expanded = expand_string(smtp_accept_max_per_host); >- if (expanded == NULL) >+ if (!expanded) > { > if (!f.expand_string_forcedfail) > log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host " >@@ -312,7 +310,7 @@ > uschar *s = expanded; > while (isdigit(*s)) > max_for_this_host = max_for_this_host * 10 + *s++ - '0'; >- if (*s != 0) >+ if (*s) > log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host " > "for %s contains non-digit: %s", whofrom->s, expanded); > } >@@ -322,8 +320,7 @@ > per host_address checks. Note that at this stage smtp_accept_count contains the > count of *other* connections, not including this one. */ > >-if ((max_for_this_host > 0) && >- (smtp_accept_count >= max_for_this_host)) >+if (max_for_this_host > 0 && smtp_accept_count >= max_for_this_host) > { > int host_accept_count = 0; > int other_host_count = 0; /* keep a count of non matches to optimise */ >@@ -340,8 +337,8 @@ > early, either by hitting the target, or finding there are not enough > connections left to make the target. */ > >- if ((host_accept_count >= max_for_this_host) || >- ((smtp_accept_count - other_host_count) < max_for_this_host)) >+ if ( host_accept_count >= max_for_this_host >+ || smtp_accept_count - other_host_count < max_for_this_host) > break; > } > >@@ -377,7 +374,7 @@ > { > uschar *list = hosts_connection_nolog; > memset(sender_host_cache, 0, sizeof(sender_host_cache)); >- if (list != NULL && verify_check_host(&list) == OK) >+ if (list && verify_check_host(&list) == OK) > save_log_selector &= ~L_smtp_connection; > else > log_write(L_smtp_connection, LOG_MAIN, "SMTP connection from %s " >@@ -399,12 +396,18 @@ > int save_debug_selector = debug_selector; > BOOL local_queue_only; > BOOL session_local_queue_only; >- #ifdef SA_NOCLDWAIT >+#ifdef SA_NOCLDWAIT > struct sigaction act; >- #endif >+#endif > > smtp_accept_count++; /* So that it includes this process */ > >+ /* If the listen backlog was over the monitoring level, log it. */ >+ >+ if (smtp_listen_backlog > smtp_backlog_monitor) >+ log_write(0, LOG_MAIN, "listen backlog %d I=[%s]:%d", >+ smtp_listen_backlog, interface_address, interface_port); >+ > /* May have been modified for the subprocess */ > > *log_selector = save_log_selector; >@@ -456,7 +459,7 @@ > extensive comment before the reception loop in exim.c for a fuller > explanation of this logic. */ > >- close_daemon_sockets(daemon_notifier_fd, listen_sockets, listen_socket_count); >+ close_daemon_sockets(daemon_notifier_fd, fd_polls, listen_socket_count); > > /* Set FD_CLOEXEC on the SMTP socket. We don't want any rogue child processes > to be able to communicate with them, under any circumstances. */ >@@ -465,14 +468,14 @@ > (void)fcntl(dup_accept_socket, F_SETFD, > fcntl(dup_accept_socket, F_GETFD) | FD_CLOEXEC); > >- #ifdef SA_NOCLDWAIT >+#ifdef SA_NOCLDWAIT > act.sa_handler = SIG_IGN; > sigemptyset(&(act.sa_mask)); > act.sa_flags = SA_NOCLDWAIT; > sigaction(SIGCHLD, &act, NULL); >- #else >+#else > signal(SIGCHLD, SIG_IGN); >- #endif >+#endif > signal(SIGTERM, SIG_DFL); > signal(SIGINT, SIG_DFL); > >@@ -558,7 +561,7 @@ > } > if (message_id[0] == 0) continue; /* No message was accepted */ > } >- else >+ else /* bad smtp_setup_msg() */ > { > if (smtp_out) > { >@@ -678,16 +681,17 @@ > { > pid_t dpid; > >- /* Before forking, ensure that the C output buffer is flushed. Otherwise >- anything that it in it will get duplicated, leading to duplicate copies >- of the pending output. */ >- >- mac_smtp_fflush(); >+ /* We used to flush smtp_out before forking so that buffered data was not >+ duplicated, but now we want to pipeline the responses for data and quit. >+ Instead, hard-close the fd underlying smtp_out right after fork to discard >+ the data buffer. */ > > if ((dpid = exim_fork(US"daemon-accept-delivery")) == 0) > { > (void)fclose(smtp_in); >+ (void)close(fileno(smtp_out)); > (void)fclose(smtp_out); >+ smtp_in = smtp_out = NULL; > > /* Don't ever molest the parent's SSL connection, but do clean up > the data structures if necessary. */ >@@ -789,10 +793,8 @@ > the incoming host address and an expanded active_hostname. */ > > log_close_all(); >-interface_address = >-sender_host_address = NULL; >+interface_address = sender_host_name = sender_host_address = NULL; > store_reset(reset_point); >-sender_host_address = NULL; > } > > >@@ -956,26 +958,20 @@ > the file. FIXME. > Returns: true on success, false + errno==EACCES otherwise > */ >+ > static BOOL > operate_on_pid_file(const enum pid_op operation, const pid_t pid) > { > char pid_line[sizeof(int) * 3 + 2]; > const int pid_len = snprintf(pid_line, sizeof(pid_line), "%d\n", (int)pid); > BOOL lines_match = FALSE; >- >-char * path = NULL; >-char * base = NULL; >-char * dir = NULL; >+uschar * path, * base, * dir; > > const int dir_flags = O_RDONLY | O_NONBLOCK; > const int base_flags = O_NOFOLLOW | O_NONBLOCK; > const mode_t base_mode = 0644; > struct stat sb; >- >-int cwd_fd = -1; >-int dir_fd = -1; >-int base_fd = -1; >- >+int cwd_fd = -1, dir_fd = -1, base_fd = -1; > BOOL success = FALSE; > errno = EACCES; > >@@ -983,24 +979,24 @@ > if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid) goto cleanup; > if (pid_len < 2 || pid_len >= (int)sizeof(pid_line)) goto cleanup; > >-path = CS string_copy(pid_file_path); >-if ((base = Ustrrchr(path, '/')) == NULL) /* should not happen, but who knows */ >+path = string_copy(pid_file_path); >+if ((base = Ustrrchr(path, '/')) == NULL) /* should not happen, but who knows */ > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "pid file path \"%s\" does not contain a '/'", pid_file_path); > >-dir = (base != path) ? path : "/"; >+dir = base != path ? path : US"/"; > *base++ = '\0'; > > if (!dir || !*dir || *dir != '/') goto cleanup; >-if (!base || !*base || strchr(base, '/') != NULL) goto cleanup; >+if (!base || !*base || Ustrchr(base, '/') != NULL) goto cleanup; > > cwd_fd = open(".", dir_flags); > if (cwd_fd < 0 || fstat(cwd_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup; >-dir_fd = open(dir, dir_flags); >+dir_fd = open(CS dir, dir_flags); > if (dir_fd < 0 || fstat(dir_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup; > > /* emulate openat */ > if (fchdir(dir_fd) != 0) goto cleanup; >-base_fd = open(base, O_RDONLY | base_flags); >+base_fd = open(CS base, O_RDONLY | base_flags); > if (fchdir(cwd_fd) != 0) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); > >@@ -1019,7 +1015,7 @@ > > if (strspn(line, "0123456789") != (size_t)len-1) goto cleanup; > if (line[len-1] != '\n') goto cleanup; >- lines_match = (len == pid_len && strcmp(line, pid_line) == 0); >+ lines_match = len == pid_len && strcmp(line, pid_line) == 0; > } > > if (operation == PID_WRITE) >@@ -1031,7 +1027,7 @@ > int error = -1; > /* emulate unlinkat */ > if (fchdir(dir_fd) != 0) goto cleanup; >- error = unlink(base); >+ error = unlink(CS base); > if (fchdir(cwd_fd) != 0) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); > if (error) goto cleanup; >@@ -1040,7 +1036,7 @@ > } > /* emulate openat */ > if (fchdir(dir_fd) != 0) goto cleanup; >- base_fd = open(base, O_WRONLY | O_CREAT | O_EXCL | base_flags, base_mode); >+ base_fd = open(CS base, O_WRONLY | O_CREAT | O_EXCL | base_flags, base_mode); > if (fchdir(cwd_fd) != 0) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); > if (base_fd < 0) goto cleanup; >@@ -1057,7 +1053,7 @@ > int error = -1; > /* emulate unlinkat */ > if (fchdir(dir_fd) != 0) goto cleanup; >- error = unlink(base); >+ error = unlink(CS base); > if (fchdir(cwd_fd) != 0) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); > if (error) goto cleanup; >@@ -1144,6 +1140,11 @@ > struct sockaddr_un sa_un = {.sun_family = AF_UNIX}; > int len; > >+if (!notifier_socket || !*notifier_socket) >+ { >+ DEBUG(D_any) debug_printf("-oY used so not creating notifier socket\n"); >+ return; >+ } > if (override_local_interfaces && !override_pid_file_path) > { > DEBUG(D_any) >@@ -1272,14 +1273,14 @@ > buf[sz] = 0; > switch (buf[0]) > { >-#ifdef EXPERIMENTAL_QUEUE_RAMP >+#ifndef DISABLE_QUEUE_RAMP > case NOTIFY_MSG_QRUN: > /* this should be a message_id */ > DEBUG(D_queue_run) > debug_printf("%s: qrunner trigger: %s\n", __FUNCTION__, buf+1); > memcpy(queuerun_msgid, buf+1, MESSAGE_ID_LENGTH+1); > return TRUE; >-#endif /*EXPERIMENTAL_QUEUE_RAMP*/ >+#endif > > case NOTIFY_QUEUE_SIZE_REQ: > { >@@ -1300,6 +1301,8 @@ > } > > >+ >+ > /************************************************* > * Exim Daemon Mainline * > *************************************************/ >@@ -1326,10 +1329,10 @@ > void > daemon_go(void) > { >-struct passwd *pw; >-int *listen_sockets = NULL; >-int listen_socket_count = 0; >-ip_address_item *addresses = NULL; >+struct passwd * pw; >+struct pollfd * fd_polls, * tls_watch_poll = NULL, * dnotify_poll = NULL; >+int listen_socket_count = 0, poll_fd_count; >+ip_address_item * addresses = NULL; > time_t last_connection_time = (time_t)0; > int local_queue_run_max = atoi(CS expand_string(queue_run_max)); > >@@ -1340,16 +1343,21 @@ > > DEBUG(D_any|D_v) debug_selector |= D_pid; > >+/* Allocate enough pollstructs for inetd mode plus the ancillary sockets; >+also used when there are no listen sockets. */ >+ >+fd_polls = store_get(sizeof(struct pollfd) * 3, GET_UNTAINTED); >+ > if (f.inetd_wait_mode) > { > listen_socket_count = 1; >- listen_sockets = store_get(sizeof(int), FALSE); > (void) close(3); > if (dup2(0, 3) == -1) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, > "failed to dup inetd socket safely away: %s", strerror(errno)); > >- listen_sockets[0] = 3; >+ fd_polls[0].fd = 3; >+ fd_polls[0].events = POLLIN; > (void) close(0); > (void) close(1); > (void) close(2); >@@ -1386,11 +1394,11 @@ > for those OS for which this is necessary the first time it is called (in > order to perform an "open" on the kernel memory file). */ > >- #ifdef LOAD_AVG_NEEDS_ROOT >+#ifdef LOAD_AVG_NEEDS_ROOT > if (queue_only_load >= 0 || smtp_load_reserve >= 0 || > (deliver_queue_load_max >= 0 && deliver_drop_privilege)) > (void)os_getloadavg(); >- #endif >+#endif > } > > >@@ -1526,7 +1534,7 @@ > sep = 0; > while ((s = string_nextinlist(&list, &sep, NULL, 0))) > pct++; >- default_smtp_port = store_get((pct+1) * sizeof(int), FALSE); >+ default_smtp_port = store_get((pct+1) * sizeof(int), GET_UNTAINTED); > list = daemon_smtp_port; > sep = 0; > for (pct = 0; >@@ -1554,6 +1562,7 @@ > > list = tls_in.on_connect_ports; > sep = 0; >+ /* the list isn't expanded so cannot be tainted. If it ever is we will trap here */ > while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) > if (!isdigit(*s)) > { >@@ -1614,7 +1623,7 @@ > ipa->port = default_smtp_port[0]; > for (int i = 1; default_smtp_port[i] > 0; i++) > { >- ip_address_item *new = store_get(sizeof(ip_address_item), FALSE); >+ ip_address_item * new = store_get(sizeof(ip_address_item), GET_UNTAINTED); > > memcpy(new->address, ipa->address, Ustrlen(ipa->address) + 1); > new->port = default_smtp_port[i]; >@@ -1668,11 +1677,16 @@ > } > } > >- /* Get a vector to remember all the sockets in */ >+ /* Get a vector to remember all the sockets in. >+ Two extra elements for the ancillary sockets */ > > for (ipa = addresses; ipa; ipa = ipa->next) > listen_socket_count++; >- listen_sockets = store_get(sizeof(int) * listen_socket_count, FALSE); >+ fd_polls = store_get(sizeof(struct pollfd) * (listen_socket_count + 2), >+ GET_UNTAINTED); >+ for (struct pollfd * p = fd_polls; p < fd_polls + listen_socket_count + 2; >+ p++) >+ { p->fd = -1; p->events = POLLIN; } > > } /* daemon_listen but not inetd_wait_mode */ > >@@ -1695,7 +1709,7 @@ > > if (smtp_accept_max > 0) > { >- smtp_slots = store_get(smtp_accept_max * sizeof(smtp_slot), FALSE); >+ smtp_slots = store_get(smtp_accept_max * sizeof(smtp_slot), GET_UNTAINTED); > for (int i = 0; i < smtp_accept_max; i++) smtp_slots[i] = empty_smtp_slot; > } > } >@@ -1763,8 +1777,8 @@ > for (ipa = addresses, sk = 0; sk < listen_socket_count; ipa = ipa->next, sk++) > { > BOOL wildcard; >- ip_address_item *ipa2; >- int af; >+ ip_address_item * ipa2; >+ int fd, af; > > if (Ustrchr(ipa->address, ':') != NULL) > { >@@ -1777,7 +1791,7 @@ > wildcard = ipa->address[0] == 0; > } > >- if ((listen_sockets[sk] = ip_socket(SOCK_STREAM, af)) < 0) >+ if ((fd_polls[sk].fd = fd = ip_socket(SOCK_STREAM, af)) < 0) > { > if (check_special_case(0, addresses, ipa, FALSE)) > { >@@ -1786,7 +1800,7 @@ > goto SKIP_SOCKET; > } > log_write(0, LOG_PANIC_DIE, "IPv%c socket creation failed: %s", >- (af == AF_INET6)? '6' : '4', strerror(errno)); >+ af == AF_INET6 ? '6' : '4', strerror(errno)); > } > > /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is >@@ -1795,8 +1809,7 @@ > > #ifdef IPV6_V6ONLY > if (af == AF_INET6 && wildcard && >- setsockopt(listen_sockets[sk], IPPROTO_IPV6, IPV6_V6ONLY, CS (&on), >- sizeof(on)) < 0) >+ setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0) > log_write(0, LOG_MAIN, "Setting IPV6_V6ONLY on daemon's IPv6 wildcard " > "socket failed (%s): carrying on without it", strerror(errno)); > #endif /* IPV6_V6ONLY */ >@@ -1805,16 +1818,14 @@ > is being handled. Without this, a connection will prevent reuse of the > smtp port for listening. */ > >- if (setsockopt(listen_sockets[sk], SOL_SOCKET, SO_REUSEADDR, >- US (&on), sizeof(on)) < 0) >+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "setting SO_REUSEADDR on socket " > "failed when starting daemon: %s", strerror(errno)); > > /* Set TCP_NODELAY; Exim does its own buffering. There is a switch to > disable this because it breaks some broken clients. */ > >- if (tcp_nodelay) setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_NODELAY, >- US (&on), sizeof(on)); >+ if (tcp_nodelay) setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); > > /* Now bind the socket to the required port; if Exim is being restarted > it may not always be possible to bind immediately, even with SO_REUSEADDR >@@ -1832,12 +1843,12 @@ > for(;;) > { > uschar *msg, *addr; >- if (ip_bind(listen_sockets[sk], af, ipa->address, ipa->port) >= 0) break; >+ if (ip_bind(fd, af, ipa->address, ipa->port) >= 0) break; > if (check_special_case(errno, addresses, ipa, TRUE)) > { > DEBUG(D_any) debug_printf("wildcard IPv4 bind() failed after IPv6 " > "listen() success; EADDRINUSE ignored\n"); >- (void)close(listen_sockets[sk]); >+ (void)close(fd); > goto SKIP_SOCKET; > } > msg = US strerror(errno); >@@ -1865,30 +1876,30 @@ > else > debug_printf("listening on %s port %d\n", ipa->address, ipa->port); > >+ /* Start listening on the bound socket, establishing the maximum backlog of >+ connections that is allowed. On success, add to the set of sockets for select >+ and continue to the next address. */ >+ > #if defined(TCP_FASTOPEN) && !defined(__APPLE__) > if ( f.tcp_fastopen_ok >- && setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_FASTOPEN, >+ && setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, > &smtp_connect_backlog, sizeof(smtp_connect_backlog))) > { > DEBUG(D_any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno)); > f.tcp_fastopen_ok = FALSE; > } > #endif >- >- /* Start listening on the bound socket, establishing the maximum backlog of >- connections that is allowed. On success, continue to the next address. */ >- >- if (listen(listen_sockets[sk], smtp_connect_backlog) >= 0) >+ if (listen(fd, smtp_connect_backlog) >= 0) > { > #if defined(TCP_FASTOPEN) && defined(__APPLE__) > if ( f.tcp_fastopen_ok >- && setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_FASTOPEN, >- &on, sizeof(on))) >+ && setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &on, sizeof(on))) > { > DEBUG(D_any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno)); > f.tcp_fastopen_ok = FALSE; > } > #endif >+ fd_polls[sk].fd = fd; > continue; > } > >@@ -1906,7 +1917,7 @@ > > DEBUG(D_any) debug_printf("wildcard IPv4 listen() failed after IPv6 " > "listen() success; EADDRINUSE ignored\n"); >- (void)close(listen_sockets[sk]); >+ (void)close(fd); > > /* Come here if there has been a problem with the socket which we > are going to ignore. We remove the address from the chain, and back up the >@@ -1955,6 +1966,7 @@ > } > > /* Set up the handler for SIGHUP, which causes a restart of the daemon. */ >+ > sighup_seen = FALSE; > signal(SIGHUP, sighup_handler); > >@@ -1979,7 +1991,7 @@ > > if (queue_interval > 0 && local_queue_run_max > 0) > { >- queue_pid_slots = store_get(local_queue_run_max * sizeof(pid_t), FALSE); >+ queue_pid_slots = store_get(local_queue_run_max * sizeof(pid_t), GET_UNTAINTED); > for (int i = 0; i < local_queue_run_max; i++) queue_pid_slots[i] = 0; > } > >@@ -2164,6 +2176,27 @@ > #ifdef SUPPORT_SPF > spf_init(); > #endif >+#ifndef DISABLE_TLS >+tls_daemon_init(); >+#endif >+ >+/* Add ancillary sockets to the set for select */ >+ >+poll_fd_count = listen_socket_count; >+#ifndef DISABLE_TLS >+if (tls_watch_fd >= 0) >+ { >+ tls_watch_poll = &fd_polls[poll_fd_count++]; >+ tls_watch_poll->fd = tls_watch_fd; >+ tls_watch_poll->events = POLLIN; >+ } >+#endif >+if (daemon_notifier_fd >= 0) >+ { >+ dnotify_poll = &fd_polls[poll_fd_count++]; >+ dnotify_poll->fd = daemon_notifier_fd; >+ dnotify_poll->events = POLLIN; >+ } > > /* Close the log so it can be renamed and moved. In the few cases below where > this long-running process writes to the log (always exceptional conditions), it >@@ -2185,13 +2218,6 @@ > > for (;;) > { >- #if HAVE_IPV6 >- struct sockaddr_in6 accepted; >- #else >- struct sockaddr_in accepted; >- #endif >- >- EXIM_SOCKLEN_T len; > pid_t pid; > > if (sigterm_seen) >@@ -2246,7 +2272,7 @@ > else > { > DEBUG(D_any) debug_printf("%s received\n", >-#ifdef EXPERIMENTAL_QUEUE_RAMP >+#ifndef DISABLE_QUEUE_RAMP > *queuerun_msgid ? "qrun notification" : > #endif > "SIGALRM"); >@@ -2269,7 +2295,7 @@ > /* Close any open listening sockets in the child */ > > close_daemon_sockets(daemon_notifier_fd, >- listen_sockets, listen_socket_count); >+ fd_polls, listen_socket_count); > > /* Reset SIGHUP and SIGCHLD in the child in both cases. */ > >@@ -2292,7 +2318,7 @@ > *p++ = '-'; > *p++ = 'q'; > if ( f.queue_2stage >-#ifdef EXPERIMENTAL_QUEUE_RAMP >+#ifndef DISABLE_QUEUE_RAMP > && !*queuerun_msgid > #endif > ) *p++ = 'q'; >@@ -2304,7 +2330,7 @@ > extra[0] = *queue_name > ? string_sprintf("%sG%s", opt, queue_name) : opt; > >-#ifdef EXPERIMENTAL_QUEUE_RAMP >+#ifndef DISABLE_QUEUE_RAMP > if (*queuerun_msgid) > { > log_write(0, LOG_MAIN, "notify triggered queue run"); >@@ -2339,7 +2365,7 @@ > > /* No need to re-exec; SIGALRM remains set to the default handler */ > >-#ifdef EXPERIMENTAL_QUEUE_RAMP >+#ifndef DISABLE_QUEUE_RAMP > if (*queuerun_msgid) > { > log_write(0, LOG_MAIN, "notify triggered queue run"); >@@ -2375,7 +2401,7 @@ > /* Reset the alarm clock */ > > sigalrm_seen = FALSE; >-#ifdef EXPERIMENTAL_QUEUE_RAMP >+#ifndef DISABLE_QUEUE_RAMP > if (*queuerun_msgid) > *queuerun_msgid = 0; > else >@@ -2397,19 +2423,8 @@ > > if (f.daemon_listen) > { >- int lcount, select_errno; >- int max_socket = 0; >+ int lcount; > BOOL select_failed = FALSE; >- fd_set select_listen; >- >- FD_ZERO(&select_listen); >- if (daemon_notifier_fd >= 0) >- FD_SET(daemon_notifier_fd, &select_listen); >- for (int sk = 0; sk < listen_socket_count; sk++) >- { >- FD_SET(listen_sockets[sk], &select_listen); >- if (listen_sockets[sk] > max_socket) max_socket = listen_sockets[sk]; >- } > > DEBUG(D_any) debug_printf("Listening...\n"); > >@@ -2426,8 +2441,7 @@ > errno = EINTR; > } > else >- lcount = select(max_socket + 1, (SELECT_ARG2_TYPE *)&select_listen, >- NULL, NULL, NULL); >+ lcount = poll(fd_polls, poll_fd_count, -1); > > if (lcount < 0) > { >@@ -2442,14 +2456,23 @@ > old one had just finished. Preserve the errno from any select() failure for > the use of the common select/accept error processing below. */ > >- select_errno = errno; >- handle_ending_processes(); >- errno = select_errno; >+ { >+ int select_errno = errno; >+ handle_ending_processes(); > > #ifndef DISABLE_TLS >- /* Create or rotate any required keys */ >- tls_daemon_init(); >+ { >+ int old_tfd; >+ /* Create or rotate any required keys; handle (delayed) filewatch event */ >+ >+ if ((old_tfd = tls_daemon_tick()) >= 0) >+ for (struct pollfd * p = &fd_polls[listen_socket_count]; >+ p < fd_polls + poll_fd_count; p++) >+ if (p->fd == old_tfd) { p->fd = tls_watch_fd ; break; } >+ } > #endif >+ errno = select_errno; >+ } > > /* Loop for all the sockets that are currently ready to go. If select > actually failed, we have set the count to 1 and select_failed=TRUE, so as >@@ -2458,23 +2481,57 @@ > while (lcount-- > 0) > { > int accept_socket = -1; >+#if HAVE_IPV6 >+ struct sockaddr_in6 accepted; >+#else >+ struct sockaddr_in accepted; >+#endif > > if (!select_failed) > { >- if ( daemon_notifier_fd >= 0 >- && FD_ISSET(daemon_notifier_fd, &select_listen)) >+#if !defined(DISABLE_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)) >+ if (tls_watch_poll && tls_watch_poll->revents & POLLIN) >+ { >+ tls_watch_poll->revents = 0; >+ tls_watch_trigger_time = time(NULL); /* Set up delayed event */ >+ tls_watch_discard_event(tls_watch_fd); >+ break; /* to top of daemon loop */ >+ } >+#endif >+ if (dnotify_poll && dnotify_poll->revents & POLLIN) > { >- FD_CLR(daemon_notifier_fd, &select_listen); >+ dnotify_poll->revents = 0; > sigalrm_seen = daemon_notification(); > break; /* to top of daemon loop */ > } >- for (int sk = 0; sk < listen_socket_count; sk++) >- if (FD_ISSET(listen_sockets[sk], &select_listen)) >+ for (struct pollfd * p = fd_polls; p < fd_polls + listen_socket_count; >+ p++) >+ if (p->revents & POLLIN) > { >- len = sizeof(accepted); >- accept_socket = accept(listen_sockets[sk], >- (struct sockaddr *)&accepted, &len); >- FD_CLR(listen_sockets[sk], &select_listen); >+ EXIM_SOCKLEN_T alen = sizeof(accepted); >+#ifdef TCP_INFO >+ struct tcp_info ti; >+ socklen_t tlen = sizeof(ti); >+ >+ /* If monitoring the backlog is wanted, grab for later logging */ >+ >+ smtp_listen_backlog = 0; >+ if ( smtp_backlog_monitor > 0 >+ && getsockopt(p->fd, IPPROTO_TCP, TCP_INFO, &ti, &tlen) == 0) >+ { >+# ifdef EXIM_HAVE_TCPI_UNACKED >+ DEBUG(D_interface) debug_printf("listen fd %d queue max %u curr %u\n", >+ p->fd, ti.tcpi_sacked, ti.tcpi_unacked); >+ smtp_listen_backlog = ti.tcpi_unacked; >+# elif defined(__FreeBSD__) /* This does not work. Investigate kernel sourcecode. */ >+ DEBUG(D_interface) debug_printf("listen fd %d queue max %u curr %u\n", >+ p->fd, ti.__tcpi_sacked, ti.__tcpi_unacked); >+ smtp_listen_backlog = ti.__tcpi_unacked; >+# endif >+ } >+#endif >+ p->revents = 0; >+ accept_socket = accept(p->fd, (struct sockaddr *)&accepted, &alen); > break; > } > } >@@ -2499,41 +2556,43 @@ > else if ( errno != accept_retry_errno > || select_failed != accept_retry_select_failed > || accept_retry_count >= 50) >- { >- log_write(0, LOG_MAIN | (accept_retry_count >= 50? LOG_PANIC : 0), >- "%d %s() failure%s: %s", >- accept_retry_count, >- accept_retry_select_failed? "select" : "accept", >- accept_retry_count == 1 ? "" : "s", >- strerror(accept_retry_errno)); >- log_close_all(); >- accept_retry_count = 0; >- accept_retry_errno = errno; >- accept_retry_select_failed = select_failed; >- } >+ { >+ log_write(0, LOG_MAIN | (accept_retry_count >= 50 ? LOG_PANIC : 0), >+ "%d %s() failure%s: %s", >+ accept_retry_count, >+ accept_retry_select_failed ? "select" : "accept", >+ accept_retry_count == 1 ? "" : "s", >+ strerror(accept_retry_errno)); >+ log_close_all(); >+ accept_retry_count = 0; >+ accept_retry_errno = errno; >+ accept_retry_select_failed = select_failed; >+ } > accept_retry_count++; > } >- else >- { >- if (accept_retry_count > 0) >- { >- log_write(0, LOG_MAIN, "%d %s() failure%s: %s", >- accept_retry_count, >- accept_retry_select_failed? "select" : "accept", >- (accept_retry_count == 1)? "" : "s", >- strerror(accept_retry_errno)); >- log_close_all(); >- accept_retry_count = 0; >- } >- } >+ else if (accept_retry_count > 0) >+ { >+ log_write(0, LOG_MAIN, "%d %s() failure%s: %s", >+ accept_retry_count, >+ accept_retry_select_failed ? "select" : "accept", >+ accept_retry_count == 1 ? "" : "s", >+ strerror(accept_retry_errno)); >+ log_close_all(); >+ accept_retry_count = 0; >+ } > > /* If select/accept succeeded, deal with the connection. */ > > if (accept_socket >= 0) > { >+#ifdef TCP_QUICKACK /* Avoid pure-ACKs while in tls protocol pingpong phase */ >+ /* Unfortunately we cannot be certain to do this before a TLS-on-connect >+ Client Hello arrives and is acked. We do it as early as possible. */ >+ (void) setsockopt(accept_socket, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off)); >+#endif > if (inetd_wait_timeout) > last_connection_time = time(NULL); >- handle_smtp_call(listen_sockets, listen_socket_count, accept_socket, >+ handle_smtp_call(fd_polls, listen_socket_count, accept_socket, > (struct sockaddr *)&accepted); > } > } >@@ -2548,10 +2607,8 @@ > > else > { >- struct timeval tv; >- tv.tv_sec = queue_interval; >- tv.tv_usec = 0; >- select(0, NULL, NULL, NULL, &tv); >+ struct pollfd p; >+ poll(&p, 0, queue_interval * 1000); > handle_ending_processes(); > } > >@@ -2576,8 +2633,7 @@ > { > log_write(0, LOG_MAIN, "pid %d: SIGHUP received: re-exec daemon", > getpid()); >- close_daemon_sockets(daemon_notifier_fd, >- listen_sockets, listen_socket_count); >+ close_daemon_sockets(daemon_notifier_fd, fd_polls, listen_socket_count); > ALARM_CLR(0); > signal(SIGHUP, SIG_IGN); > sighup_argv[0] = exim_path; >diff -ur exim.orig/src/dbfn.c exim/src/dbfn.c >--- exim.orig/src/dbfn.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/dbfn.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,13 +2,18 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > > #include "exim.h" > >+/* We have buffers holding path names for database files. >+PATH_MAX could be used here, but would be wasting memory, as we deal >+with database files like $spooldirectory/db/<name> */ >+#define PATHLEN 256 >+ > > /* Functions for accessing Exim's hints database, which consists of a number of > different DBM files. This module does not contain code for reading DBM files >@@ -34,31 +39,6 @@ > > > /************************************************* >-* Berkeley DB error callback * >-*************************************************/ >- >-/* For Berkeley DB >= 2, we can define a function to be called in case of DB >-errors. This should help with debugging strange DB problems, e.g. getting "File >-exists" when you try to open a db file. The API for this function was changed >-at DB release 4.3. */ >- >-#if defined(USE_DB) && defined(DB_VERSION_STRING) >-void >-#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3) >-dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg) >-{ >-dbenv = dbenv; >-#else >-dbfn_bdb_error_callback(const char *pfx, char *msg) >-{ >-#endif >-pfx = pfx; >-log_write(0, LOG_MAIN, "Berkeley DB error: %s", msg); >-} >-#endif >- >- >-/************************************************* > * Open and lock a database file * > *************************************************/ > >@@ -90,7 +70,7 @@ > int rc, save_errno; > BOOL read_only = flags == O_RDONLY; > flock_t lock_data; >-uschar dirname[256], filename[256]; >+uschar dirname[PATHLEN], filename[PATHLEN]; > > DEBUG(D_hints_lookup) acl_level++; > >@@ -121,7 +101,7 @@ > if (dbblock->lockfd < 0) > { > log_write(0, LOG_MAIN, "%s", >- string_open_failed(errno, "database lock file %s", filename)); >+ string_open_failed("database lock file %s", filename)); > errno = 0; /* Indicates locking failure */ > DEBUG(D_hints_lookup) acl_level--; > return NULL; >@@ -167,12 +147,12 @@ > snprintf(CS filename, sizeof(filename), "%s/%s", dirname, name); > > priv_drop_temp(exim_uid, exim_gid); >-EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr)); >+dbblock->dbptr = exim_dbopen(filename, dirname, flags, EXIMDB_MODE); > if (!dbblock->dbptr && errno == ENOENT && flags == O_RDWR) > { > DEBUG(D_hints_lookup) > debug_printf_indent("%s appears not to exist: trying to create\n", filename); >- EXIM_DBOPEN(filename, dirname, flags|O_CREAT, EXIMDB_MODE, &(dbblock->dbptr)); >+ dbblock->dbptr = exim_dbopen(filename, dirname, flags|O_CREAT, EXIMDB_MODE); > } > save_errno = errno; > priv_restore(); >@@ -183,12 +163,13 @@ > > if (!dbblock->dbptr) > { >+ errno = save_errno; > if (lof && save_errno != ENOENT) >- log_write(0, LOG_MAIN, "%s", string_open_failed(save_errno, "DB file %s", >+ log_write(0, LOG_MAIN, "%s", string_open_failed("DB file %s", > filename)); > else > DEBUG(D_hints_lookup) >- debug_printf_indent("%s\n", CS string_open_failed(save_errno, "DB file %s", >+ debug_printf_indent("%s\n", CS string_open_failed("DB file %s", > filename)); > (void)close(dbblock->lockfd); > errno = save_errno; >@@ -226,7 +207,7 @@ > void > dbfn_close(open_db *dbblock) > { >-EXIM_DBCLOSE(dbblock->dbptr); >+exim_dbclose(dbblock->dbptr); > (void)close(dbblock->lockfd); > DEBUG(D_hints_lookup) > { debug_printf_indent("closed hints database and lockfile\n"); acl_level--; } >@@ -262,31 +243,59 @@ > void *yield; > EXIM_DATUM key_datum, result_datum; > int klen = Ustrlen(key) + 1; >-uschar * key_copy = store_get(klen, is_tainted(key)); >+uschar * key_copy = store_get(klen, key); > > memcpy(key_copy, key, klen); > > DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: key=%s\n", key); > >-EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */ >-EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */ >-EXIM_DATUM_DATA(key_datum) = CS key_copy; >-EXIM_DATUM_SIZE(key_datum) = klen; >+exim_datum_init(&key_datum); /* Some DBM libraries require the datum */ >+exim_datum_init(&result_datum); /* to be cleared before use. */ >+exim_datum_data_set(&key_datum, key_copy); >+exim_datum_size_set(&key_datum, klen); > >-if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL; >+if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) return NULL; > > /* Assume the data store could have been tainted. Properly, we should > store the taint status with the data. */ > >-yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE); >-memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum)); >-if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum); >+yield = store_get(exim_datum_size_get(&result_datum), GET_TAINTED); >+memcpy(yield, exim_datum_data_get(&result_datum), exim_datum_size_get(&result_datum)); >+if (length) *length = exim_datum_size_get(&result_datum); > >-EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */ >+exim_datum_free(&result_datum); /* Some DBM libs require freeing */ > return yield; > } > > >+/* Read a record. If the length is not as expected then delete it, write >+an error log line, delete the record and return NULL. >+Use this for fixed-size records (so not retry or wait records). >+ >+Arguments: >+ dbblock a pointer to an open database block >+ key the key of the record to be read >+ length the expected record length >+ >+Returns: a pointer to the retrieved record, or >+ NULL if the record is not found/bad >+*/ >+ >+void * >+dbfn_read_enforce_length(open_db * dbblock, const uschar * key, size_t length) >+{ >+int rlen; >+void * yield = dbfn_read_with_length(dbblock, key, &rlen); >+ >+if (yield) >+ { >+ if (rlen == length) return yield; >+ log_write(0, LOG_MAIN|LOG_PANIC, "Bad db record size for '%s'", key); >+ dbfn_delete(dbblock, key); >+ } >+return NULL; >+} >+ > > /************************************************* > * Write to database file * >@@ -309,20 +318,20 @@ > EXIM_DATUM key_datum, value_datum; > dbdata_generic *gptr = (dbdata_generic *)ptr; > int klen = Ustrlen(key) + 1; >-uschar * key_copy = store_get(klen, is_tainted(key)); >+uschar * key_copy = store_get(klen, key); > > memcpy(key_copy, key, klen); > gptr->time_stamp = time(NULL); > > DEBUG(D_hints_lookup) debug_printf_indent("dbfn_write: key=%s\n", key); > >-EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */ >-EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */ >-EXIM_DATUM_DATA(key_datum) = CS key_copy; >-EXIM_DATUM_SIZE(key_datum) = klen; >-EXIM_DATUM_DATA(value_datum) = CS ptr; >-EXIM_DATUM_SIZE(value_datum) = length; >-return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum); >+exim_datum_init(&key_datum); /* Some DBM libraries require the datum */ >+exim_datum_init(&value_datum); /* to be cleared before use. */ >+exim_datum_data_set(&key_datum, key_copy); >+exim_datum_size_set(&key_datum, klen); >+exim_datum_data_set(&value_datum, ptr); >+exim_datum_size_set(&value_datum, length); >+return exim_dbput(dbblock->dbptr, &key_datum, &value_datum); > } > > >@@ -343,16 +352,16 @@ > dbfn_delete(open_db *dbblock, const uschar *key) > { > int klen = Ustrlen(key) + 1; >-uschar * key_copy = store_get(klen, is_tainted(key)); >+uschar * key_copy = store_get(klen, key); >+EXIM_DATUM key_datum; > > DEBUG(D_hints_lookup) debug_printf_indent("dbfn_delete: key=%s\n", key); > > memcpy(key_copy, key, klen); >-EXIM_DATUM key_datum; >-EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */ >-EXIM_DATUM_DATA(key_datum) = CS key_copy; >-EXIM_DATUM_SIZE(key_datum) = klen; >-return EXIM_DBDEL(dbblock->dbptr, key_datum); >+exim_datum_init(&key_datum); /* Some DBM libraries require clearing */ >+exim_datum_data_set(&key_datum, key_copy); >+exim_datum_size_set(&key_datum, klen); >+return exim_dbdel(dbblock->dbptr, &key_datum); > } > > >@@ -378,23 +387,22 @@ > { > EXIM_DATUM key_datum, value_datum; > uschar *yield; >-value_datum = value_datum; /* dummy; not all db libraries use this */ > > DEBUG(D_hints_lookup) debug_printf_indent("dbfn_scan\n"); > > /* Some dbm require an initialization */ > >-if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor); >+if (start) *cursor = exim_dbcreate_cursor(dbblock->dbptr); > >-EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */ >-EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */ >+exim_datum_init(&key_datum); /* Some DBM libraries require the datum */ >+exim_datum_init(&value_datum); /* to be cleared before use. */ > >-yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))? >- US EXIM_DATUM_DATA(key_datum) : NULL; >+yield = exim_dbscan(dbblock->dbptr, &key_datum, &value_datum, start, *cursor) >+ ? US exim_datum_data_get(&key_datum) : NULL; > > /* Some dbm require a termination */ > >-if (!yield) EXIM_DBDELETE_CURSOR(*cursor); >+if (!yield) exim_dbdelete_cursor(*cursor); > return yield; > } > >diff -ur exim.orig/src/dbfunctions.h exim/src/dbfunctions.h >--- exim.orig/src/dbfunctions.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/dbfunctions.h 2022-06-23 16:41:10.000000000 +0300 >@@ -2,9 +2,12 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >-/* Copyright (c) University of Cambridge 1995 - 2015 */ >+/* Copyright (c) The Exim Maintainers 2022 */ >+/* Copyright (c) University of Cambridge 1995 - 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > >+#ifndef DBFUNCTIONS_H >+#define DBFUNCTIONS_H > > /* Functions for reading/writing exim database files */ > >@@ -12,6 +15,7 @@ > int dbfn_delete(open_db *, const uschar *); > open_db *dbfn_open(uschar *, int, open_db *, BOOL, 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 **); > int dbfn_write(open_db *, const uschar *, void *, int); > >@@ -23,11 +27,12 @@ > changed at release 4.3. */ > > #if defined(USE_DB) && defined(DB_VERSION_STRING) >-#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3) >+# if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3) > void dbfn_bdb_error_callback(const DB_ENV *, const char *, const char *); >-#else >+# else > void dbfn_bdb_error_callback(const char *, char *); >-#endif >+# endif > #endif > >+#endif > /* End of dbfunctions.h */ >ТолÑко в exim.orig/src: dbstuff.h >diff -ur exim.orig/src/dcc.c exim/src/dcc.c >--- exim.orig/src/dcc.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/dcc.c 2022-06-23 16:41:10.000000000 +0300 >@@ -7,7 +7,7 @@ > * wbreyha@gmx.net > * See the file NOTICE for conditions of use and distribution. > * >- * Copyright (c) The Exim Maintainers 2015 - 2019 >+ * Copyright (c) The Exim Maintainers 2015 - 2021 > */ > > /* Code for calling dccifd. Called from acl.c. */ >@@ -23,442 +23,468 @@ > int dcc_rc = 0; > > /* This function takes a file descriptor and a buffer as input and >- * returns either 0 for success or errno in case of error. */ >+returns either 0 for success or errno in case of error. */ > >-int flushbuffer (int socket, gstring *buffer) >+static int flushbuffer >+(int socket, gstring *buffer) > { >- int retval, rsp; >- rsp = write(socket, buffer->s, buffer->ptr); >+int rsp; >+ >+rsp = write(socket, buffer->s, buffer->ptr); >+DEBUG(D_acl) >+ debug_printf("DCC: flushbuffer(): Result of the write() = %d\n", rsp); >+if(rsp < 0) >+ { > DEBUG(D_acl) >- debug_printf("DCC: flushbuffer(): Result of the write() = %d\n", rsp); >- if(rsp < 0) { >- DEBUG(D_acl) >- debug_printf("DCC: flushbuffer(): Error writing buffer to socket: %s\n", strerror(errno)); >- retval = errno; >+ debug_printf("DCC: flushbuffer(): Error writing buffer to socket: %s\n", strerror(errno)); >+ return errno; > } >- else { >- DEBUG(D_acl) >- debug_printf("DCC: flushbuffer(): Wrote buffer to socket:\n%.*s\n", buffer->ptr, buffer->s); >- retval = 0; >- } >- return retval; >+DEBUG(D_acl) >+ debug_printf("DCC: flushbuffer(): Wrote buffer to socket:\n%.*s\n", buffer->ptr, buffer->s); >+return 0; > } > > int > dcc_process(uschar **listptr) > { >- int sep = 0; >- const uschar *list = *listptr; >- FILE *data_file; >- uschar *dcc_default_ip_option = US"127.0.0.1"; >- uschar *dcc_helo_option = US"localhost"; >- uschar *xtra_hdrs = NULL; >- uschar *override_client_ip = NULL; >- >- /* from local_scan */ >- int dcc_resplen, retval, sockfd, resp; >- unsigned int portnr; >- struct sockaddr_un serv_addr; >- struct sockaddr_in serv_addr_in; >- struct hostent *ipaddress; >- uschar sockpath[128]; >- uschar sockip[40], client_ip[40]; >- gstring *dcc_headers; >- gstring *sendbuf; >- uschar *dcc_return_text; >- struct header_line *mail_headers; >- uschar *dcc_acl_options; >- gstring *dcc_xtra_hdrs; >- gstring *dcc_header_str; >- >- /* grep 1st option */ >- if ((dcc_acl_options = string_nextinlist(&list, &sep, NULL, 0))) { >- /* parse 1st option */ >- if ( strcmpic(dcc_acl_options, US"false") == 0 >- || Ustrcmp(dcc_acl_options, "0") == 0) >- return FAIL; /* explicitly no matching */ >+int sep = 0; >+const uschar *list = *listptr; >+FILE *data_file; >+uschar *dcc_default_ip_option = US"127.0.0.1"; >+uschar *dcc_helo_option = US"localhost"; >+uschar *xtra_hdrs = NULL; >+uschar *override_client_ip = NULL; >+ >+/* from local_scan */ >+int dcc_resplen, retval, sockfd, resp; >+unsigned int portnr; >+struct sockaddr_un serv_addr; >+struct sockaddr_in serv_addr_in; >+struct hostent *ipaddress; >+uschar sockpath[128]; >+uschar sockip[40], client_ip[40]; >+gstring *dcc_headers; >+gstring *sendbuf; >+uschar *dcc_return_text = US"''"; >+struct header_line *mail_headers; >+uschar *dcc_acl_options; >+gstring *dcc_xtra_hdrs; >+gstring *dcc_header_str; >+ >+/* grep 1st option */ >+if ((dcc_acl_options = string_nextinlist(&list, &sep, NULL, 0))) >+ { >+ /* parse 1st option */ >+ if ( strcmpic(dcc_acl_options, US"false") == 0 >+ || Ustrcmp(dcc_acl_options, "0") == 0) >+ return FAIL; /* explicitly no matching */ > } >- else >- return FAIL; /* empty means "don't match anything" */ >- >- sep = 0; >+else >+ return FAIL; /* empty means "don't match anything" */ > >- /* if we scanned this message last time, just return */ >- if (dcc_ok) >- return dcc_rc; >+sep = 0; > >- /* open the spooled body */ >- for (int i = 0; i < 2; i++) { >- uschar message_subdir[2]; >- set_subdir_str(message_subdir, message_id, i); >- if ((data_file = Ufopen( >- spool_fname(US"input", message_subdir, message_id, US"-D"), "rb"))) >- break; >- } >- >- if (!data_file) { >- /* error while spooling */ >- log_write(0, LOG_MAIN|LOG_PANIC, >- "DCC: error while opening spool file"); >- return DEFER; >- } >- >- /* Initialize the variables */ >+/* if we scanned this message last time, just return */ >+if (dcc_ok) >+ return dcc_rc; > >- bzero(sockip,sizeof(sockip)); >- if (dccifd_address) { >- if (dccifd_address[0] == '/') >- Ustrncpy(sockpath, dccifd_address, sizeof(sockpath)); >- else >- if( sscanf(CS dccifd_address, "%s %u", sockip, &portnr) != 2) { >- log_write(0, LOG_MAIN, >- "DCC: warning - invalid dccifd address: '%s'", dccifd_address); >- (void)fclose(data_file); >- return DEFER; >+/* open the spooled body */ >+for (int i = 0; i < 2; i++) >+ { >+ uschar message_subdir[2]; >+ set_subdir_str(message_subdir, message_id, i); >+ if ((data_file = Ufopen( >+ spool_fname(US"input", message_subdir, message_id, US"-D"), "rb"))) >+ break; >+ } >+ >+if (!data_file) >+ { >+ /* error while spooling */ >+ log_write(0, LOG_MAIN|LOG_PANIC, >+ "DCC: error while opening spool file"); >+ return DEFER; >+ } >+ >+/* Initialize the variables */ >+ >+bzero(sockip,sizeof(sockip)); >+if (dccifd_address) >+ { >+ if (dccifd_address[0] == '/') >+ Ustrncpy(sockpath, dccifd_address, sizeof(sockpath)); >+ else >+ if( sscanf(CS dccifd_address, "%s %u", sockip, &portnr) != 2) >+ { >+ log_write(0, LOG_MAIN, >+ "DCC: warning - invalid dccifd address: '%s'", dccifd_address); >+ (void)fclose(data_file); >+ return DEFER; > } > } > >- /* dcc_headers is what we send as dccifd options - see man dccifd */ >- /* We don't support any other option than 'header' so just copy that */ >- dcc_headers = string_cat(NULL, dccifd_options); >- /* if $acl_m_dcc_override_client_ip is set use it */ >- if (((override_client_ip = expand_string(US"$acl_m_dcc_override_client_ip")) != NULL) && >- (override_client_ip[0] != '\0')) { >- Ustrncpy(client_ip, override_client_ip, sizeof(client_ip)-1); >- DEBUG(D_acl) >- debug_printf("DCC: Client IP (overridden): %s\n", client_ip); >+/* dcc_headers is what we send as dccifd options - see man dccifd */ >+/* We don't support any other option than 'header' so just copy that */ >+dcc_headers = string_cat(NULL, dccifd_options); >+/* if $acl_m_dcc_override_client_ip is set use it */ >+if (((override_client_ip = expand_string(US"$acl_m_dcc_override_client_ip")) != NULL) && >+ (override_client_ip[0] != '\0')) >+ { >+ Ustrncpy(client_ip, override_client_ip, sizeof(client_ip)-1); >+ DEBUG(D_acl) >+ debug_printf("DCC: Client IP (overridden): %s\n", client_ip); > } >- else if(sender_host_address) { >+else if(sender_host_address) >+ { > /* else if $sender_host_address is available use that? */ >- Ustrncpy(client_ip, sender_host_address, sizeof(client_ip)-1); >- DEBUG(D_acl) >- debug_printf("DCC: Client IP (sender_host_address): %s\n", client_ip); >+ Ustrncpy(client_ip, sender_host_address, sizeof(client_ip)-1); >+ DEBUG(D_acl) >+ debug_printf("DCC: Client IP (sender_host_address): %s\n", client_ip); > } >- else { >- /* sender_host_address is NULL which means it comes from localhost */ >- Ustrncpy(client_ip, dcc_default_ip_option, sizeof(client_ip)-1); >- DEBUG(D_acl) >- debug_printf("DCC: Client IP (default): %s\n", client_ip); >+else >+ { >+ /* sender_host_address is NULL which means it comes from localhost */ >+ Ustrncpy(client_ip, dcc_default_ip_option, sizeof(client_ip)-1); >+ DEBUG(D_acl) >+ debug_printf("DCC: Client IP (default): %s\n", client_ip); > } >- /* build options block */ >- dcc_headers = string_append(dcc_headers, 5, US"\n", client_ip, US"\nHELO ", dcc_helo_option, US"\n"); >+/* build options block */ >+dcc_headers = string_append(dcc_headers, 5, US"\n", client_ip, US"\nHELO ", dcc_helo_option, US"\n"); > >- /* initialize the other variables */ >- mail_headers = header_list; >- /* we set the default return value to DEFER */ >- retval = DEFER; >- >- /* send a null return path as "<>". */ >- dcc_headers = string_cat (dcc_headers, *sender_address ? sender_address : US"<>"); >- dcc_headers = string_catn(dcc_headers, US"\n", 1); >- >- /************************************** >- * Now creating the socket connection * >- **************************************/ >- >- /* If sockip contains an ip, we use a tcp socket, otherwise a UNIX socket */ >- if(Ustrcmp(sockip, "")) { >- ipaddress = gethostbyname(CS sockip); >- bzero(CS &serv_addr_in, sizeof(serv_addr_in)); >- serv_addr_in.sin_family = AF_INET; >- bcopy(CS ipaddress->h_addr, CS &serv_addr_in.sin_addr.s_addr, ipaddress->h_length); >- serv_addr_in.sin_port = htons(portnr); >- if ((sockfd = socket(AF_INET, SOCK_STREAM,0)) < 0) { >- DEBUG(D_acl) >- debug_printf("DCC: Creating TCP socket connection failed: %s\n", strerror(errno)); >- log_write(0,LOG_PANIC,"DCC: Creating TCP socket connection failed: %s\n", strerror(errno)); >- /* if we cannot create the socket, defer the mail */ >- (void)fclose(data_file); >- return retval; >+/* initialize the other variables */ >+mail_headers = header_list; >+/* we set the default return value to DEFER */ >+retval = DEFER; >+ >+/* send a null return path as "<>". */ >+dcc_headers = string_cat (dcc_headers, *sender_address ? sender_address : US"<>"); >+dcc_headers = string_catn(dcc_headers, US"\n", 1); >+ >+/************************************** >+ * Now creating the socket connection * >+ **************************************/ >+ >+/* If sockip contains an ip, we use a tcp socket, otherwise a UNIX socket */ >+if(Ustrcmp(sockip, "")) >+ { >+ ipaddress = gethostbyname(CS sockip); >+ bzero(CS &serv_addr_in, sizeof(serv_addr_in)); >+ serv_addr_in.sin_family = AF_INET; >+ bcopy(CS ipaddress->h_addr, CS &serv_addr_in.sin_addr.s_addr, ipaddress->h_length); >+ serv_addr_in.sin_port = htons(portnr); >+ if ((sockfd = socket(AF_INET, SOCK_STREAM,0)) < 0) >+ { >+ DEBUG(D_acl) >+ debug_printf("DCC: Creating TCP socket connection failed: %s\n", strerror(errno)); >+ log_write(0,LOG_PANIC,"DCC: Creating TCP socket connection failed: %s\n", strerror(errno)); >+ /* if we cannot create the socket, defer the mail */ >+ (void)fclose(data_file); >+ return retval; > } >- /* Now connecting the socket (INET) */ >- if (connect(sockfd, (struct sockaddr *)&serv_addr_in, sizeof(serv_addr_in)) < 0) { >- DEBUG(D_acl) >- debug_printf("DCC: Connecting to TCP socket failed: %s\n", strerror(errno)); >- log_write(0,LOG_PANIC,"DCC: Connecting to TCP socket failed: %s\n", strerror(errno)); >- /* if we cannot contact the socket, defer the mail */ >- (void)fclose(data_file); >- return retval; >+ /* Now connecting the socket (INET) */ >+ if (connect(sockfd, (struct sockaddr *)&serv_addr_in, sizeof(serv_addr_in)) < 0) >+ { >+ DEBUG(D_acl) >+ debug_printf("DCC: Connecting to TCP socket failed: %s\n", strerror(errno)); >+ log_write(0,LOG_PANIC,"DCC: Connecting to TCP socket failed: %s\n", strerror(errno)); >+ /* if we cannot contact the socket, defer the mail */ >+ (void)fclose(data_file); >+ return retval; > } > } >- else { >- /* connecting to the dccifd UNIX socket */ >- bzero(&serv_addr, sizeof(serv_addr)); >- serv_addr.sun_family = AF_UNIX; >- Ustrncpy(US serv_addr.sun_path, sockpath, sizeof(serv_addr.sun_path)); >- if ((sockfd = socket(AF_UNIX, SOCK_STREAM,0)) < 0) { >- DEBUG(D_acl) >- debug_printf("DCC: Creating UNIX socket connection failed: %s\n", strerror(errno)); >- log_write(0,LOG_PANIC,"DCC: Creating UNIX socket connection failed: %s\n", strerror(errno)); >- /* if we cannot create the socket, defer the mail */ >- (void)fclose(data_file); >- return retval; >+else >+ { >+ /* connecting to the dccifd UNIX socket */ >+ bzero(&serv_addr, sizeof(serv_addr)); >+ serv_addr.sun_family = AF_UNIX; >+ Ustrncpy(US serv_addr.sun_path, sockpath, sizeof(serv_addr.sun_path)); >+ if ((sockfd = socket(AF_UNIX, SOCK_STREAM,0)) < 0) >+ { >+ DEBUG(D_acl) >+ debug_printf("DCC: Creating UNIX socket connection failed: %s\n", strerror(errno)); >+ log_write(0,LOG_PANIC,"DCC: Creating UNIX socket connection failed: %s\n", strerror(errno)); >+ /* if we cannot create the socket, defer the mail */ >+ (void)fclose(data_file); >+ return retval; > } >- /* Now connecting the socket (UNIX) */ >- if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { >- DEBUG(D_acl) >- debug_printf("DCC: Connecting to UNIX socket failed: %s\n", strerror(errno)); >- log_write(0,LOG_PANIC,"DCC: Connecting to UNIX socket failed: %s\n", strerror(errno)); >- /* if we cannot contact the socket, defer the mail */ >- (void)fclose(data_file); >- return retval; >+ /* Now connecting the socket (UNIX) */ >+ if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) >+ { >+ DEBUG(D_acl) >+ debug_printf("DCC: Connecting to UNIX socket failed: %s\n", strerror(errno)); >+ log_write(0,LOG_PANIC,"DCC: Connecting to UNIX socket failed: %s\n", strerror(errno)); >+ /* if we cannot contact the socket, defer the mail */ >+ (void)fclose(data_file); >+ return retval; > } > } >- /* the socket is open, now send the options to dccifd*/ >+/* the socket is open, now send the options to dccifd*/ >+DEBUG(D_acl) >+ debug_printf("DCC: -----------------------------------\nDCC: Socket opened; now sending input\n" >+ "DCC: -----------------------------------\n"); >+ >+/* let's send each of the recipients to dccifd */ >+for (int i = 0; i < recipients_count; i++) >+ { > DEBUG(D_acl) >- debug_printf("DCC: -----------------------------------\nDCC: Socket opened; now sending input\n" >- "DCC: -----------------------------------\n"); >- >- /* let's send each of the recipients to dccifd */ >- for (int i = 0; i < recipients_count; i++) { >- DEBUG(D_acl) >- debug_printf("DCC: recipient = %s\n",recipients_list[i].address); >- dcc_headers = string_append(dcc_headers, 2, recipients_list[i].address, "\n"); >+ debug_printf("DCC: recipient = %s\n",recipients_list[i].address); >+ dcc_headers = string_append(dcc_headers, 2, recipients_list[i].address, "\n"); > } >- /* send a blank line between options and message */ >- dcc_headers = string_catn(dcc_headers, US"\n", 1); >- /* Now we send the input buffer */ >- (void) string_from_gstring(dcc_headers); >- DEBUG(D_acl) >- debug_printf("DCC: ***********************************\nDCC: Sending options:\n%s" >- "DCC: ***********************************\n", dcc_headers->s); >- if (flushbuffer(sockfd, dcc_headers) != 0) { >- (void)fclose(data_file); >- return retval; >+/* send a blank line between options and message */ >+dcc_headers = string_catn(dcc_headers, US"\n", 1); >+/* Now we send the input buffer */ >+(void) string_from_gstring(dcc_headers); >+DEBUG(D_acl) >+ debug_printf("DCC: ***********************************\nDCC: Sending options:\n%s" >+ "DCC: ***********************************\n", dcc_headers->s); >+if (flushbuffer(sockfd, dcc_headers) != 0) >+ { >+ (void)fclose(data_file); >+ return retval; > } > >- /* now send the message */ >- /* First send the headers */ >- DEBUG(D_acl) >- debug_printf("DCC: ***********************************\nDCC: Sending headers:\n"); >- sendbuf = string_get(8192); >+/* now send the message */ >+/* First send the headers */ >+DEBUG(D_acl) >+ debug_printf("DCC: ***********************************\nDCC: Sending headers:\n"); >+sendbuf = string_get(8192); >+sendbuf = string_catn(sendbuf, mail_headers->text, mail_headers->slen); >+while((mail_headers=mail_headers->next)) > sendbuf = string_catn(sendbuf, mail_headers->text, mail_headers->slen); >- while((mail_headers=mail_headers->next)) { >- sendbuf = string_catn(sendbuf, mail_headers->text, mail_headers->slen); >- } > >- /* a blank line separates header from body */ >- sendbuf = string_catn(sendbuf, US"\r\n", 2); >- (void) string_from_gstring(sendbuf); >- gstring_release_unused(sendbuf); >- DEBUG(D_acl) >- debug_printf("%sDCC: ***********************************\n", sendbuf->s); >- if (flushbuffer(sockfd, sendbuf) != 0) { >- (void)fclose(data_file); >- return retval; >+/* a blank line separates header from body */ >+sendbuf = string_catn(sendbuf, US"\r\n", 2); >+(void) string_from_gstring(sendbuf); >+gstring_release_unused(sendbuf); >+DEBUG(D_acl) >+ debug_printf("%sDCC: ***********************************\n", sendbuf->s); >+if (flushbuffer(sockfd, sendbuf) != 0) >+ { >+ (void)fclose(data_file); >+ return retval; > } > >- /* now send the body */ >- DEBUG(D_acl) >- debug_printf("DCC: ***********************************\nDCC: Writing body:\n"); >- (void)fseek(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET); >- >- gstring filebuf = { .size = big_buffer_size, .ptr = 0, .s = big_buffer }; >- >- while((filebuf.ptr = fread(filebuf.s, 1, filebuf.size, data_file)) > 0) { >- if (flushbuffer(sockfd, &filebuf) != 0) { >- (void)fclose(data_file); >- return retval; >+/* now send the body */ >+DEBUG(D_acl) >+ debug_printf("DCC: ***********************************\nDCC: Writing body:\n"); >+(void)fseek(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET); >+ >+gstring filebuf = { .size = big_buffer_size, .ptr = 0, .s = big_buffer }; >+ >+while((filebuf.ptr = fread(filebuf.s, 1, filebuf.size, data_file)) > 0) >+ if (flushbuffer(sockfd, &filebuf) != 0) >+ { >+ (void)fclose(data_file); >+ return retval; > } >- } >- DEBUG(D_acl) >- debug_printf("DCC: ***********************************\n"); >+DEBUG(D_acl) >+ debug_printf("DCC: ***********************************\n"); > >- /* shutdown() the socket */ >- if(shutdown(sockfd, SHUT_WR) < 0) { >+/* shutdown() the socket */ >+if(shutdown(sockfd, SHUT_WR) < 0) >+ { >+ DEBUG(D_acl) >+ debug_printf("DCC: Couldn't shutdown socket: %s\n", strerror(errno)); >+ log_write(0,LOG_MAIN,"DCC: Couldn't shutdown socket: %s\n", strerror(errno)); >+ /* If there is a problem with the shutdown() >+ * defer the mail. */ >+ (void)fclose(data_file); >+ return retval; >+ } >+DEBUG(D_acl) >+ debug_printf("DCC: Input sent.\n" >+ "DCC: +++++++++++++++++++++++++++++++++++\n" >+ "DCC: Now receiving output from server\n" >+ "DCC: -----------------------------------\n"); >+ >+/******************************** >+ * receiving output from dccifd * >+ ********************************/ >+ >+/****************************************************************** >+ * We should get 3 lines: * >+ * 1/ First line is overall result: either 'A' for Accept, * >+ * 'R' for Reject, 'S' for accept Some recipients or * >+ * 'T' for a Temporary error. * >+ * 2/ Second line contains the list of Accepted/Rejected * >+ * recipients in the form AARRA (A = accepted, R = rejected). * >+ * 3/ Third line contains the X-DCC header. * >+ ******************************************************************/ >+ >+int line = 1; /* we start at the first line of the output */ >+int bufoffset; >+ >+dcc_header_str = string_get(DCC_HEADER_LIMIT + 2); >+/* Let's read from the socket until there's nothing left to read */ >+while((dcc_resplen = read(sockfd, big_buffer, big_buffer_size-1)) > 0) >+ { >+ /* fail on read error */ >+ if(dcc_resplen < 0) >+ { > DEBUG(D_acl) >- debug_printf("DCC: Couldn't shutdown socket: %s\n", strerror(errno)); >- log_write(0,LOG_MAIN,"DCC: Couldn't shutdown socket: %s\n", strerror(errno)); >- /* If there is a problem with the shutdown() >- * defer the mail. */ >+ debug_printf("DCC: Error reading from socket: %s\n", strerror(errno)); > (void)fclose(data_file); > return retval; >- } >- DEBUG(D_acl) >- debug_printf("DCC: Input sent.\n" >- "DCC: +++++++++++++++++++++++++++++++++++\n" >- "DCC: Now receiving output from server\n" >- "DCC: -----------------------------------\n"); >- >- /******************************** >- * receiving output from dccifd * >- ********************************/ >- >- /****************************************************************** >- * We should get 3 lines: * >- * 1/ First line is overall result: either 'A' for Accept, * >- * 'R' for Reject, 'S' for accept Some recipients or * >- * 'T' for a Temporary error. * >- * 2/ Second line contains the list of Accepted/Rejected * >- * recipients in the form AARRA (A = accepted, R = rejected). * >- * 3/ Third line contains the X-DCC header. * >- ******************************************************************/ >- >- int line = 1; /* we start at the first line of the output */ >- int bufoffset; >- >- dcc_header_str = string_get(DCC_HEADER_LIMIT + 2); >- /* Let's read from the socket until there's nothing left to read */ >- while((dcc_resplen = read(sockfd, big_buffer, big_buffer_size-1)) > 0) { >- /* fail on read error */ >- if(dcc_resplen < 0) { >- DEBUG(D_acl) >- debug_printf("DCC: Error reading from socket: %s\n", strerror(errno)); >- (void)fclose(data_file); >- return retval; > } >- /* make the answer 0-terminated. only needed for debug_printf */ >- DEBUG(D_acl) >- debug_printf("DCC: Length of the output buffer is: %d\nDCC: Output buffer is:\n" >- "DCC: -----------------------------------\n%.*s\n" >- "DCC: -----------------------------------\n", dcc_resplen, dcc_resplen, big_buffer); >- >- /* Now let's read each character and see what we've got */ >- for(bufoffset = 0; bufoffset < dcc_resplen, line <= 2; bufoffset++) { >- /* First check if we reached the end of the line and >- * then increment the line counter */ >- if(big_buffer[bufoffset] == '\n') >- line++; >- else { >- /* The first character of the first line is the >- * overall response. If there's another character >- * on that line it is not correct. */ >- if(line == 1) { >- if(bufoffset == 0) { >- /* Now get the value and set the >- * return value accordingly */ >- switch(big_buffer[bufoffset]) { >- case 'A': >- DEBUG(D_acl) >- debug_printf("DCC: Overall result = A\treturning OK\n"); >- dcc_return_text = US"Mail accepted by DCC"; >- dcc_result = US"A"; >- retval = OK; >- break; >- case 'R': >- DEBUG(D_acl) >- debug_printf("DCC: Overall result = R\treturning FAIL\n"); >- dcc_return_text = US"Rejected by DCC"; >- dcc_result = US"R"; >- retval = FAIL; >- if(sender_host_name) >- log_write(0, LOG_MAIN, "H=%s [%s] F=<%s>: rejected by DCC", >- sender_host_name, sender_host_address, sender_address); >- else >- log_write(0, LOG_MAIN, "H=[%s] F=<%s>: rejected by DCC", >- sender_host_address, sender_address); >- break; >- case 'S': >- DEBUG(D_acl) >- debug_printf("DCC: Overall result = S\treturning OK\n"); >- dcc_return_text = US"Not all recipients accepted by DCC"; >- /* Since we're in an ACL we want a global result >- * so we accept for all */ >- dcc_result = US"A"; >- retval = OK; >- break; >- case 'G': >- DEBUG(D_acl) >- debug_printf("DCC: Overall result = G\treturning FAIL\n"); >- dcc_return_text = US"Greylisted by DCC"; >- dcc_result = US"G"; >- retval = FAIL; >- break; >- case 'T': >- DEBUG(D_acl) >- debug_printf("DCC: Overall result = T\treturning DEFER\n"); >- dcc_return_text = US"Temporary error with DCC"; >- dcc_result = US"T"; >- retval = DEFER; >- log_write(0,LOG_MAIN,"Temporary error with DCC: %s\n", big_buffer); >- break; >- default: >- DEBUG(D_acl) >- debug_printf("DCC: Overall result = something else\treturning DEFER\n"); >- dcc_return_text = US"Unknown DCC response"; >- dcc_result = US"T"; >- retval = DEFER; >- log_write(0,LOG_MAIN,"Unknown DCC response: %s\n", big_buffer); >- break; >- } >- } >- else { >- /* We're on the first line but not on the first character, >- * there must be something wrong. */ >- DEBUG(D_acl) debug_printf("DCC: Line = %d but bufoffset = %d != 0" >- " character is %c - This is wrong!\n", line, bufoffset, big_buffer[bufoffset]); >- log_write(0,LOG_MAIN,"Wrong header from DCC, output is %s\n", big_buffer); >- } >- } >- else if(line == 2) { >- /* On the second line we get a list of >- * answers for each recipient. We don't care about >- * it because we're in an acl and take the >- * global result. */ >- } >+ /* make the answer 0-terminated. only needed for debug_printf */ >+ DEBUG(D_acl) >+ debug_printf("DCC: Length of the output buffer is: %d\nDCC: Output buffer is:\n" >+ "DCC: -----------------------------------\n%.*s\n" >+ "DCC: -----------------------------------\n", dcc_resplen, dcc_resplen, big_buffer); >+ >+ /* Now let's read each character and see what we've got */ >+ for(bufoffset = 0; bufoffset < dcc_resplen && line <= 2; bufoffset++) >+ { >+ /* First check if we reached the end of the line and >+ then increment the line counter */ >+ if(big_buffer[bufoffset] == '\n') >+ line++; >+ else >+ { >+ /* The first character of the first line is the overall response. If >+ there's another character on that line it is not correct. */ >+ if(line == 1) >+ { >+ if(bufoffset == 0) >+ { >+ /* Now get the value and set the return value accordingly */ >+ switch (big_buffer[bufoffset]) >+ { >+ case 'A': >+ DEBUG(D_acl) >+ debug_printf("DCC: Overall result = A\treturning OK\n"); >+ dcc_return_text = US"Mail accepted by DCC"; >+ dcc_result = US"A"; >+ retval = OK; >+ break; >+ case 'R': >+ DEBUG(D_acl) >+ debug_printf("DCC: Overall result = R\treturning FAIL\n"); >+ dcc_return_text = US"Rejected by DCC"; >+ dcc_result = US"R"; >+ retval = FAIL; >+ if(sender_host_name) >+ log_write(0, LOG_MAIN, "H=%s [%s] F=<%s>: rejected by DCC", >+ sender_host_name, sender_host_address, sender_address); >+ else >+ log_write(0, LOG_MAIN, "H=[%s] F=<%s>: rejected by DCC", >+ sender_host_address, sender_address); >+ break; >+ case 'S': >+ DEBUG(D_acl) >+ debug_printf("DCC: Overall result = S\treturning OK\n"); >+ dcc_return_text = US"Not all recipients accepted by DCC"; >+ /* Since we're in an ACL we want a global result so we accept for all */ >+ dcc_result = US"A"; >+ retval = OK; >+ break; >+ case 'G': >+ DEBUG(D_acl) >+ debug_printf("DCC: Overall result = G\treturning FAIL\n"); >+ dcc_return_text = US"Greylisted by DCC"; >+ dcc_result = US"G"; >+ retval = FAIL; >+ break; >+ case 'T': >+ DEBUG(D_acl) >+ debug_printf("DCC: Overall result = T\treturning DEFER\n"); >+ dcc_return_text = US"Temporary error with DCC"; >+ dcc_result = US"T"; >+ retval = DEFER; >+ log_write(0,LOG_MAIN,"Temporary error with DCC: %s\n", big_buffer); >+ break; >+ default: >+ DEBUG(D_acl) >+ debug_printf("DCC: Overall result = something else\treturning DEFER\n"); >+ dcc_return_text = US"Unknown DCC response"; >+ dcc_result = US"T"; >+ retval = DEFER; >+ log_write(0,LOG_MAIN,"Unknown DCC response: %s\n", big_buffer); >+ break; >+ } >+ } >+ else >+ { >+ /* We're on the first line but not on the first character, >+ * there must be something wrong. */ >+ DEBUG(D_acl) debug_printf("DCC: Line = %d but bufoffset = %d != 0" >+ " character is %c - This is wrong!\n", line, bufoffset, big_buffer[bufoffset]); >+ log_write(0,LOG_MAIN,"Wrong header from DCC, output is %s\n", big_buffer); >+ } >+ } >+ else if(line == 2) >+ { >+ /* On the second line we get a list of answers for each recipient. We >+ don't care about it because we're in an acl and take the global result. */ >+ } > } > } >- if(line > 2) { >- /* The third and following lines are the X-DCC header, >- * so we store it in dcc_header_str up to our limit. */ >- /* check if buffer contains the end of the header .."\n\n" and truncate it */ >- if ((big_buffer[dcc_resplen-1] == '\n') && >- (big_buffer[dcc_resplen-2] == '\n')) >- dcc_resplen -= 2; >- dcc_resplen -= bufoffset; >- if (dcc_header_str->ptr + dcc_resplen > DCC_HEADER_LIMIT) { >- dcc_resplen = DCC_HEADER_LIMIT - dcc_header_str->ptr; >- DEBUG(D_acl) debug_printf("DCC: We got more output than we can store" >- "in the X-DCC header. Truncating at 120 characters.\n"); >+ if(line > 2) >+ { >+ /* The third and following lines are the X-DCC header, so we store it in >+ dcc_header_str up to our limit. */ >+ /* check if buffer contains the end of the header .."\n\n" and truncate it */ >+ if ((big_buffer[dcc_resplen-1] == '\n') && >+ (big_buffer[dcc_resplen-2] == '\n')) >+ dcc_resplen -= 2; >+ dcc_resplen -= bufoffset; >+ if (dcc_header_str->ptr + dcc_resplen > DCC_HEADER_LIMIT) >+ { >+ dcc_resplen = DCC_HEADER_LIMIT - dcc_header_str->ptr; >+ DEBUG(D_acl) debug_printf("DCC: We got more output than we can store" >+ "in the X-DCC header. Truncating at 120 characters.\n"); > } >- dcc_header_str = string_catn(dcc_header_str, &big_buffer[bufoffset], dcc_resplen); >+ dcc_header_str = string_catn(dcc_header_str, &big_buffer[bufoffset], dcc_resplen); > } > } >- /* We have read everything from the socket. make sure the header ends with "\n" */ >- dcc_header_str = string_catn(dcc_header_str, US"\n", 1); >+/* We have read everything from the socket. make sure the header ends with "\n" */ >+dcc_header_str = string_catn(dcc_header_str, US"\n", 1); > >- (void) string_from_gstring(dcc_header_str); >- /* Now let's sum up what we've got. */ >- DEBUG(D_acl) >- debug_printf("\nDCC: --------------------------\nDCC: Overall result = %d\n" >- "DCC: X-DCC header: %sReturn message: %s\nDCC: dcc_result: %s\n", >- retval, dcc_header_str->s, dcc_return_text, dcc_result); >- >- /* We only add the X-DCC header if it starts with X-DCC */ >- if(!(Ustrncmp(dcc_header_str->s, "X-DCC", 5))) { >- dcc_header = dcc_header_str->s; >- if(dcc_direct_add_header) { >- header_add(' ' , "%s", dcc_header_str->s); >- /* since the MIME ACL already writes the .eml file to disk without DCC Header we've to erase it */ >- unspool_mbox(); >+(void) string_from_gstring(dcc_header_str); >+/* Now let's sum up what we've got. */ >+DEBUG(D_acl) >+ debug_printf("\nDCC: --------------------------\nDCC: Overall result = %d\n" >+ "DCC: X-DCC header: %sReturn message: %s\nDCC: dcc_result: %s\n", >+ retval, dcc_header_str->s, dcc_return_text, dcc_result); >+ >+/* We only add the X-DCC header if it starts with X-DCC */ >+if(!(Ustrncmp(dcc_header_str->s, "X-DCC", 5))) >+ { >+ dcc_header = dcc_header_str->s; >+ if(dcc_direct_add_header) >+ { >+ header_add(' ' , "%s", dcc_header_str->s); >+/* since the MIME ACL already writes the .eml file to disk without DCC Header we've to erase it */ >+ unspool_mbox(); > } > } >- else { >- DEBUG(D_acl) >- debug_printf("DCC: Wrong format of the X-DCC header: %.*s\n", dcc_header_str->ptr, dcc_header_str->s); >- } >+else >+ DEBUG(D_acl) >+ debug_printf("DCC: Wrong format of the X-DCC header: %.*s\n", dcc_header_str->ptr, dcc_header_str->s); > >- /* check if we should add additional headers passed in acl_m_dcc_add_header */ >- if(dcc_direct_add_header) { >- if (((xtra_hdrs = expand_string(US"$acl_m_dcc_add_header")) != NULL) && (xtra_hdrs[0] != '\0')) { >- dcc_xtra_hdrs = string_cat(NULL, xtra_hdrs); >- if (dcc_xtra_hdrs->s[dcc_xtra_hdrs->ptr - 1] != '\n') >- dcc_xtra_hdrs = string_catn(dcc_xtra_hdrs, US"\n", 1); >- header_add(' ', "%s", string_from_gstring(dcc_xtra_hdrs)); >- DEBUG(D_acl) >- debug_printf("DCC: adding additional headers in $acl_m_dcc_add_header: %.*s", dcc_xtra_hdrs->ptr, dcc_xtra_hdrs->s); >+/* check if we should add additional headers passed in acl_m_dcc_add_header */ >+if (dcc_direct_add_header) >+ { >+ if (((xtra_hdrs = expand_string(US"$acl_m_dcc_add_header")) != NULL) && (xtra_hdrs[0] != '\0')) >+ { >+ dcc_xtra_hdrs = string_cat(NULL, xtra_hdrs); >+ if (dcc_xtra_hdrs->s[dcc_xtra_hdrs->ptr - 1] != '\n') >+ dcc_xtra_hdrs = string_catn(dcc_xtra_hdrs, US"\n", 1); >+ header_add(' ', "%s", string_from_gstring(dcc_xtra_hdrs)); >+ DEBUG(D_acl) >+ debug_printf("DCC: adding additional headers in $acl_m_dcc_add_header: %.*s", dcc_xtra_hdrs->ptr, dcc_xtra_hdrs->s); > } > } > >- dcc_ok = 1; >- /* Now return to exim main process */ >- DEBUG(D_acl) >- debug_printf("DCC: Before returning to exim main process:\nDCC: return_text = %s - retval = %d\n" >- "DCC: dcc_result = %s\n", dcc_return_text, retval, dcc_result); >- >- (void)fclose(data_file); >- dcc_rc = retval; >- return dcc_rc; >+dcc_ok = 1; >+/* Now return to exim main process */ >+DEBUG(D_acl) >+ debug_printf("DCC: Before returning to exim main process:\nDCC: return_text = %s - retval = %d\n" >+ "DCC: dcc_result = %s\n", dcc_return_text, retval, dcc_result); >+ >+(void)fclose(data_file); >+dcc_rc = retval; >+return dcc_rc; > } > > #endif >diff -ur exim.orig/src/debug.c exim/src/debug.c >--- exim.orig/src/debug.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/debug.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2015 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -12,6 +13,8 @@ > static uschar *debug_ptr = debug_buffer; > static int debug_prefix_length = 0; > >+static unsigned pretrigger_writeoff; >+static unsigned pretrigger_readoff; > > > const uschar * rc_names[] = { /* Mostly for debug output */ >@@ -52,8 +55,8 @@ > moves down the page. This function is used only in debugging circumstances. The > output is done via debug_printf(). */ > >-#define tree_printlinesize 132 /* line size for printing */ >-static uschar tree_printline[tree_printlinesize]; >+#define TREE_PRINTLINESIZE 132 /* line size for printing */ >+static uschar tree_printline[TREE_PRINTLINESIZE]; > > /* Internal recursive subroutine. > >@@ -66,12 +69,12 @@ > */ > > static void >-tree_printsub(tree_node *p, int pos, int barswitch) >+tree_printsub(tree_node * p, int pos, int barswitch) > { > if (p->right) tree_printsub(p->right, pos+2, 1); >-for (int i = 0; i <= pos-1; i++) debug_printf("%c", tree_printline[i]); >-debug_printf("-->%s [%d]\n", p->name, p->balance); >-tree_printline[pos] = barswitch? '|' : ' '; >+for (int i = 0; i <= pos-1; i++) debug_printf_indent(" %c", tree_printline[i]); >+debug_printf_indent(" -->%s [%d]\n", p->name, p->balance); >+tree_printline[pos] = barswitch ? '|' : ' '; > if (p->left) > { > tree_printline[pos+2] = '|'; >@@ -82,11 +85,12 @@ > /* The external function, with just a tree node argument. */ > > void >-debug_print_tree(tree_node *p) >+debug_print_tree(const char * title, tree_node * p) > { >-for (int i = 0; i < tree_printlinesize; i++) tree_printline[i] = ' '; >-if (!p) debug_printf("Empty Tree\n"); else tree_printsub(p, 0, 0); >-debug_printf("---- End of tree ----\n"); >+debug_printf_indent("%s:\n", title); >+for (int i = 0; i < TREE_PRINTLINESIZE; i++) tree_printline[i] = ' '; >+if (!p) debug_printf_indent(" Empty Tree\n"); else tree_printsub(p, 0, 0); >+debug_printf_indent("---- End of tree ----\n"); > } > > >@@ -316,8 +320,41 @@ > } > } > >- fprintf(debug_file, "%s", CS debug_buffer); >- fflush(debug_file); >+ if (debug_pretrigger_buf) >+ { >+ int needed = Ustrlen(debug_buffer)+1, avail; >+ char c; >+ >+ if (needed > debug_pretrigger_bsize) >+ needed = debug_pretrigger_bsize; >+ if ((avail = pretrigger_readoff - pretrigger_writeoff) <= 0) >+ avail += debug_pretrigger_bsize; >+ >+ /* We have a pretrigger set up, trigger not yet hit. Copy the line(s) to the >+ pretrig buffer, dropping earlier lines if needed but truncating this line if >+ the pbuf is maxed out. In the PTB the lines are NOT nul-terminated. */ >+ >+ while (avail < needed) >+ do >+ { >+ avail++; >+ c = debug_pretrigger_buf[pretrigger_readoff]; >+ if (++pretrigger_readoff >= debug_pretrigger_bsize) pretrigger_readoff = 0; >+ } >+ while (c && c != '\n' && pretrigger_readoff != pretrigger_writeoff); >+ >+ needed--; >+ for (int i = 0; needed; i++, needed--) >+ { >+ debug_pretrigger_buf[pretrigger_writeoff] = debug_buffer[i]; >+ if (++pretrigger_writeoff >= debug_pretrigger_bsize) pretrigger_writeoff = 0; >+ } >+ } >+ else >+ { >+ fprintf(debug_file, "%s", CS debug_buffer); >+ fflush(debug_file); >+ } > debug_ptr = debug_buffer; > debug_prefix_length = 0; > } >@@ -351,7 +388,7 @@ > g = string_fmt_append(g, " lcl [%s]:%u", > inet_ntoa(sinp->sin_addr), ntohs(sinp->sin_port)); > alen = sizeof(*sinp); >- if (getpeername(fd, sinp, &alen) == 0) >+ if (getpeername(fd, (struct sockaddr *)sinp, &alen) == 0) > g = string_fmt_append(g, " rmt [%s]:%u", > inet_ntoa(sinp->sin_addr), ntohs(sinp->sin_port)); > break; >@@ -363,7 +400,7 @@ > inet_ntop(AF_INET6, &sin6p->sin6_addr, CS buf, sizeof(buf)), > ntohs(sin6p->sin6_port)); > alen = sizeof(*sin6p); >- if (getpeername(fd, sin6p, &alen) == 0) >+ if (getpeername(fd, (struct sockaddr *)sin6p, &alen) == 0) > g = string_fmt_append(g, " rmt [%s]:%u", > inet_ntop(AF_INET6, &sin6p->sin6_addr, CS buf, sizeof(buf)), > ntohs(sin6p->sin6_port)); >@@ -376,7 +413,7 @@ > sunp->sun_path[0] ? US"" : US"@", > sunp->sun_path[0] ? sunp->sun_path : sunp->sun_path+1); > alen = sizeof(*sunp); >- if (getpeername(fd, sunp, &alen) == 0) >+ if (getpeername(fd, (struct sockaddr *)sunp, &alen) == 0) > g = string_fmt_append(g, " rmt %s%s", > sunp->sun_path[0] ? US"" : US"@", > sunp->sun_path[0] ? sunp->sun_path : sunp->sun_path+1); >@@ -408,4 +445,54 @@ > } > > >+/**************************************************************/ >+/* Pretrigger handling for debug. The debug_printf implementation >+diverts output to a circular buffer if the buffer is set up. >+The routines here set up the buffer, and unload it to file (and release it). >+What ends up in the buffer is subject to the usual debug_selector. */ >+ >+void >+debug_pretrigger_setup(const uschar * size_string) >+{ >+long size = Ustrtol(size_string, NULL, 0); >+if (size > 0) >+ { >+ unsigned bufsize = MIN(size, 16384); >+ >+ dtrigger_selector |= BIT(DTi_pretrigger); >+ if (debug_pretrigger_buf) store_free(debug_pretrigger_buf); >+ debug_pretrigger_buf = store_malloc((size_t)(debug_pretrigger_bsize = bufsize)); >+ pretrigger_readoff = pretrigger_writeoff = 0; >+ } >+} >+ >+void >+debug_trigger_fire(void) >+{ >+int nbytes; >+ >+if (!debug_pretrigger_buf) return; >+ >+if (debug_file && (nbytes = pretrigger_writeoff - pretrigger_readoff) != 0) >+ if (nbytes > 0) >+ fwrite(debug_pretrigger_buf + pretrigger_readoff, 1, nbytes, debug_file); >+ else >+ { >+ fwrite(debug_pretrigger_buf + pretrigger_readoff, 1, >+ debug_pretrigger_bsize - pretrigger_readoff, debug_file); >+ fwrite(debug_pretrigger_buf, 1, pretrigger_writeoff, debug_file); >+ } >+ >+debug_pretrigger_discard(); >+} >+ >+void >+debug_pretrigger_discard(void) >+{ >+if (debug_pretrigger_buf) store_free(debug_pretrigger_buf); >+debug_pretrigger_buf = NULL; >+dtrigger_selector = 0; >+} >+ >+ > /* End of debug.c */ >diff -ur exim.orig/src/deliver.c exim/src/deliver.c >--- exim.orig/src/deliver.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/deliver.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* The main code for delivering a message. */ >@@ -74,6 +74,7 @@ > static BOOL remove_journal; > static int parcount = 0; > static pardata *parlist = NULL; >+static struct pollfd *parpoll; > static int return_count; > static uschar *frozen_info = US""; > static uschar *used_return_path = NULL; >@@ -145,7 +146,7 @@ > address_item * > deliver_make_addr(uschar *address, BOOL copy) > { >-address_item *addr = store_get(sizeof(address_item), FALSE); >+address_item * addr = store_get(sizeof(address_item), GET_UNTAINTED); > *addr = address_defaults; > if (copy) address = string_copy(address); > addr->address = address; >@@ -521,8 +522,12 @@ > else if (one->port != two->port) > return FALSE; > >- /* Hosts matched */ >+#ifdef SUPPORT_DANE >+ /* DNSSEC equality */ >+ if (one->dnssec != two->dnssec) return FALSE; >+#endif > >+ /* Hosts matched */ > one = one->next; > two = two->next; > } >@@ -792,6 +797,9 @@ > if (LOGGING(outgoing_port)) > g = string_fmt_append(g, ":%d", h->port); > >+if (continue_sequence > 1) /*XXX this is wrong for a dropped proxyconn. Would have to pass back from transport */ >+ g = string_catn(g, US"*", 1); >+ > #ifdef SUPPORT_SOCKS > if (LOGGING(proxy) && proxy_local_address) > { >@@ -820,7 +828,7 @@ > if (LOGGING(tls_cipher) && addr->cipher) > { > g = string_append(g, 2, US" X=", addr->cipher); >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifndef DISABLE_TLS_RESUME > if (LOGGING(tls_resumption) && testflag(addr, af_tls_resume)) > g = string_catn(g, US"*", 1); > #endif >@@ -846,8 +854,18 @@ > > > #ifndef DISABLE_EVENT >+/* Distribute a named event to any listeners. >+ >+Args: action config option specifying listener >+ event name of the event >+ ev_data associated data for the event >+ errnop pointer to errno for modification, or null >+ >+Return: string expansion from listener, or NULL >+*/ >+ > uschar * >-event_raise(uschar * action, const uschar * event, uschar * ev_data) >+event_raise(uschar * action, const uschar * event, uschar * ev_data, int * errnop) > { > uschar * s; > if (action) >@@ -874,7 +892,8 @@ > { > DEBUG(D_deliver) > debug_printf("Event(%s): event_action returned \"%s\"\n", event, s); >- errno = ERRNO_EVENT; >+ if (errnop) >+ *errnop = ERRNO_EVENT; > return s; > } > } >@@ -903,7 +922,7 @@ > a filter was used which triggered a fail command (in such a case > a transport isn't needed). Convert it to an internal fail event. */ > >- (void) event_raise(event_action, US"msg:fail:internal", addr->message); >+ (void) event_raise(event_action, US"msg:fail:internal", addr->message, NULL); > } > } > else >@@ -915,7 +934,8 @@ > || Ustrcmp(addr->transport->driver_name, "smtp") == 0 > || Ustrcmp(addr->transport->driver_name, "lmtp") == 0 > || Ustrcmp(addr->transport->driver_name, "autoreply") == 0 >- ? addr->message : NULL); >+ ? addr->message : NULL, >+ NULL); > } > > deliver_host_port = save_port; >@@ -933,9 +953,22 @@ > > > /************************************************* >-* Generate local prt for logging * >+* Generate local part for logging * > *************************************************/ > >+static uschar * >+string_get_lpart_sub(const address_item * addr, uschar * s) >+{ >+#ifdef SUPPORT_I18N >+if (testflag(addr, af_utf8_downcvt)) >+ { >+ uschar * t = string_localpart_utf8_to_alabel(s, NULL); >+ return t ? t : s; /* t is NULL on a failed conversion */ >+ } >+#endif >+return s; >+} >+ > /* This function is a subroutine for use in string_log_address() below. > > Arguments: >@@ -950,32 +983,13 @@ > { > uschar * s; > >-s = addr->prefix; >-if (testflag(addr, af_include_affixes) && s) >- { >-#ifdef SUPPORT_I18N >- if (testflag(addr, af_utf8_downcvt)) >- s = string_localpart_utf8_to_alabel(s, NULL); >-#endif >- yield = string_cat(yield, s); >- } >+if (testflag(addr, af_include_affixes) && (s = addr->prefix)) >+ yield = string_cat(yield, string_get_lpart_sub(addr, s)); > >-s = addr->local_part; >-#ifdef SUPPORT_I18N >-if (testflag(addr, af_utf8_downcvt)) >- s = string_localpart_utf8_to_alabel(s, NULL); >-#endif >-yield = string_cat(yield, s); >+yield = string_cat(yield, string_get_lpart_sub(addr, addr->local_part)); > >-s = addr->suffix; >-if (testflag(addr, af_include_affixes) && s) >- { >-#ifdef SUPPORT_I18N >- if (testflag(addr, af_utf8_downcvt)) >- s = string_localpart_utf8_to_alabel(s, NULL); >-#endif >- yield = string_cat(yield, s); >- } >+if (testflag(addr, af_include_affixes) && (s = addr->suffix)) >+ yield = string_cat(yield, string_get_lpart_sub(addr, s)); > > return yield; > } >@@ -1128,7 +1142,7 @@ > #endif > > reset_point = store_mark(); >-g = string_get_tainted(256, TRUE); /* addrs will be tainted, so avoid copy */ >+g = string_get_tainted(256, GET_TAINTED); /* addrs will be tainted, so avoid copy */ > > if (msg) > g = string_append(g, 2, host_and_ident(TRUE), US" "); >@@ -1152,11 +1166,6 @@ > if (*queue_name) > g = string_append(g, 2, US" Q=", queue_name); > >-#ifdef EXPERIMENTAL_SRS >-if(addr->prop.srs_sender) >- g = string_append(g, 3, US" SRS=<", addr->prop.srs_sender, US">"); >-#endif >- > /* You might think that the return path must always be set for a successful > delivery; indeed, I did for some time, until this statement crashed. The case > when it is not set is for a delivery to /dev/null which is optimised by not >@@ -1195,8 +1204,6 @@ > if (addr->host_used) > { > g = d_hostlog(g, addr); >- if (continue_sequence > 1) /*XXX this is wrong for a dropped proxyconn. Would have to pass back from transport */ >- g = string_catn(g, US"*", 1); > > #ifndef DISABLE_EVENT > deliver_host_address = addr->host_used->address; >@@ -1268,8 +1275,8 @@ > /* Time on queue and actual time taken to deliver */ > > if (LOGGING(queue_time)) >- g = string_append(g, 2, US" QT=", >- string_timesince(&received_time)); >+ g = string_append(g, 2, US" QT=", string_timesince( >+ LOGGING(queue_time_exclusive) ? &received_time_complete : &received_time)); > > if (LOGGING(deliver_time)) > g = string_append(g, 2, US" DT=", string_timediff(&addr->delivery_time)); >@@ -1325,20 +1332,10 @@ > g = string_fmt_append(g, " defer (%d)", addr->basic_errno); > > if (addr->basic_errno > 0) >- g = string_append(g, 2, US": ", >- US strerror(addr->basic_errno)); >+ g = string_append(g, 2, US": ", US strerror(addr->basic_errno)); > > if (addr->host_used) >- { >- g = string_append(g, 5, >- US" H=", addr->host_used->name, >- US" [", addr->host_used->address, US"]"); >- if (LOGGING(outgoing_port)) >- { >- int port = addr->host_used->port; >- g = string_fmt_append(g, ":%d", port == PORT_NONE ? 25 : port); >- } >- } >+ g = d_hostlog(g, addr); > > if (LOGGING(deliver_time)) > g = string_append(g, 2, US" DT=", string_timediff(&addr->delivery_time)); >@@ -1589,6 +1586,12 @@ > (void)close(addr->return_file); > } > >+/* Check if the transport notifed continue-conn status explicitly, and >+update our knowlege. */ >+ >+if (testflag(addr, af_new_conn)) continue_sequence = 1; >+else if (testflag(addr, af_cont_conn)) continue_sequence++; >+ > /* The success case happens only after delivery by a transport. */ > > if (result == OK) >@@ -2128,7 +2131,7 @@ > Returns: nothing > */ > >-static void >+void > deliver_local(address_item *addr, BOOL shadowing) > { > BOOL use_initgroups; >@@ -2146,27 +2149,21 @@ > > if(addr->prop.errors_address) > return_path = addr->prop.errors_address; >-#ifdef EXPERIMENTAL_SRS >-else if (addr->prop.srs_sender) >- return_path = addr->prop.srs_sender; >-#endif > else > return_path = sender_address; > > if (tp->return_path) > { >- uschar *new_return_path = expand_string(tp->return_path); >- if (!new_return_path) >- { >- if (!f.expand_string_forcedfail) >- { >- common_error(TRUE, addr, ERRNO_EXPANDFAIL, >- US"Failed to expand return path \"%s\" in %s transport: %s", >- tp->return_path, tp->name, expand_string_message); >- return; >- } >+ uschar * new_return_path = expand_string(tp->return_path); >+ if (new_return_path) >+ return_path = new_return_path; >+ else if (!f.expand_string_forcedfail) >+ { >+ common_error(TRUE, addr, ERRNO_EXPANDFAIL, >+ US"Failed to expand return path \"%s\" in %s transport: %s", >+ tp->return_path, tp->name, expand_string_message); >+ return; > } >- else return_path = new_return_path; > } > > /* For local deliveries, one at a time, the value used for logging can just be >@@ -2373,27 +2370,29 @@ > { > BOOL ok = TRUE; > set_process_info("delivering %s to %s using %s", message_id, >- addr->local_part, addr->transport->name); >+ addr->local_part, tp->name); > >- /* Setting this global in the subprocess means we need never clear it */ >+ /* Setting these globals in the subprocess means we need never clear them */ > transport_name = addr->transport->name; >+ driver_srcfile = tp->srcfile; >+ driver_srcline = tp->srcline; > > /* If a transport filter has been specified, set up its argument list. > Any errors will get put into the address, and FALSE yielded. */ > >- if (addr->transport->filter_command) >+ if (tp->filter_command) > { > ok = transport_set_up_command(&transport_filter_argv, >- addr->transport->filter_command, >- TRUE, PANIC, addr, US"transport filter", NULL); >- transport_filter_timeout = addr->transport->filter_timeout; >+ tp->filter_command, >+ TRUE, PANIC, addr, FALSE, US"transport filter", NULL); >+ transport_filter_timeout = tp->filter_timeout; > } > else transport_filter_argv = NULL; > > if (ok) > { >- debug_print_string(addr->transport->debug_string); >- replicate = !(addr->transport->info->code)(addr->transport, addr); >+ debug_print_string(tp->debug_string); >+ replicate = !(tp->info->code)(addr->transport, addr); > } > } > >@@ -2413,13 +2412,13 @@ > uschar *s; > int ret; > >- if( (ret = write(pfd[pipe_write], &addr2->transport_return, sizeof(int))) != sizeof(int) >+ if( (i = addr2->transport_return, (ret = write(pfd[pipe_write], &i, sizeof(int))) != sizeof(int)) > || (ret = write(pfd[pipe_write], &transport_count, sizeof(transport_count))) != sizeof(transport_count) > || (ret = write(pfd[pipe_write], &addr2->flags, sizeof(addr2->flags))) != sizeof(addr2->flags) > || (ret = write(pfd[pipe_write], &addr2->basic_errno, sizeof(int))) != sizeof(int) > || (ret = write(pfd[pipe_write], &addr2->more_errno, sizeof(int))) != sizeof(int) > || (ret = write(pfd[pipe_write], &addr2->delivery_time, sizeof(struct timeval))) != sizeof(struct timeval) >- || (ret = write(pfd[pipe_write], &addr2->special_action, sizeof(int))) != sizeof(int) >+ || (i = addr2->special_action, (ret = write(pfd[pipe_write], &i, sizeof(int))) != sizeof(int)) > || (ret = write(pfd[pipe_write], &addr2->transport, > sizeof(transport_instance *))) != sizeof(transport_instance *) > >@@ -2487,7 +2486,7 @@ > len = read(pfd[pipe_read], &addr2->basic_errno, sizeof(int)); > len = read(pfd[pipe_read], &addr2->more_errno, sizeof(int)); > len = read(pfd[pipe_read], &addr2->delivery_time, sizeof(struct timeval)); >- len = read(pfd[pipe_read], &addr2->special_action, sizeof(int)); >+ len = read(pfd[pipe_read], &i, sizeof(int)); addr2->special_action = i; > len = read(pfd[pipe_read], &addr2->transport, > sizeof(transport_instance *)); > >@@ -2889,10 +2888,8 @@ > deliveries (e.g. to pipes) can take a substantial time. */ > > if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE))) >- { > DEBUG(D_deliver|D_retry|D_hints_lookup) > debug_printf("no retry data available\n"); >- } > > addr2 = addr; > addr3 = NULL; >@@ -3048,7 +3045,7 @@ > else for (addr2 = addr; addr2; addr2 = addr2->next) > if (addr2->transport_return == OK) > { >- addr3 = store_get(sizeof(address_item), FALSE); >+ addr3 = store_get(sizeof(address_item), GET_UNTAINTED); > *addr3 = *addr2; > addr3->next = NULL; > addr3->shadow_message = US &addr2->shadow_message; >@@ -3196,6 +3193,7 @@ > uschar *pattern; > uschar patbuf[256]; > >+/*XXX The list is used before expansion. Not sure how that ties up with the docs */ > while ( *aptr > && (pattern = string_nextinlist(&listptr, &sep, patbuf, sizeof(patbuf))) > ) >@@ -3308,7 +3306,7 @@ > > /* Loop through all items, reading from the pipe when necessary. The pipe > used to be non-blocking. But I do not see a reason for using non-blocking I/O >-here, as the preceding select() tells us, if data is available for reading. >+here, as the preceding poll() tells us, if data is available for reading. > > A read() on a "selected" handle should never block, but(!) it may return > less data then we expected. (The buffer size we pass to read() shouldn't be >@@ -3442,7 +3440,7 @@ > > if (!r || !(*ptr & rf_delete)) > { >- r = store_get(sizeof(retry_item), FALSE); >+ r = store_get(sizeof(retry_item), GET_UNTAINTED); > r->next = addr->retries; > addr->retries = r; > r->flags = *ptr++; >@@ -3581,7 +3579,13 @@ > > switch (*subid) > { >- #ifdef SUPPORT_SOCKS >+ case 3: /* explicit notification of continued-connection (non)use; >+ overrides caller's knowlege. */ >+ if (*ptr & BIT(1)) setflag(addr, af_new_conn); >+ else if (*ptr & BIT(2)) setflag(addr, af_cont_conn); >+ break; >+ >+#ifdef SUPPORT_SOCKS > case '2': /* proxy information; must arrive before A0 and applies to that addr XXX oops*/ > proxy_session = TRUE; /*XXX should this be cleared somewhere? */ > if (*ptr == 0) >@@ -3594,9 +3598,9 @@ > ptr += sizeof(proxy_local_port); > } > break; >- #endif >+#endif > >- #ifdef EXPERIMENTAL_DSN_INFO >+#ifdef EXPERIMENTAL_DSN_INFO > case '1': /* must arrive before A0, and applies to that addr */ > /* Two strings: smtp_greeting and helo_response */ > addr->smtp_greeting = string_copy(ptr); >@@ -3604,7 +3608,7 @@ > addr->helo_response = string_copy(ptr); > while(*ptr++); > break; >- #endif >+#endif > > case '0': > DEBUG(D_deliver) debug_printf("A0 %s tret %d\n", addr->address, *ptr); >@@ -3627,7 +3631,7 @@ > > if (*ptr) > { >- h = store_get(sizeof(host_item), FALSE); >+ h = store_get(sizeof(host_item), GET_UNTAINTED); > h->name = string_copy(ptr); > while (*ptr++); > h->address = string_copy(ptr); >@@ -3836,7 +3840,7 @@ > par_wait(void) > { > int poffset, status; >-address_item *addr, *addrlist; >+address_item * addr, * addrlist; > pid_t pid; > > set_process_info("delivering %s: waiting for a remote delivery subprocess " >@@ -3846,18 +3850,18 @@ > existence - in which case give an error return. We cannot proceed just by > waiting for a completion, because a subprocess may have filled up its pipe, and > be waiting for it to be emptied. Therefore, if no processes have finished, we >-wait for one of the pipes to acquire some data by calling select(), with a >+wait for one of the pipes to acquire some data by calling poll(), with a > timeout just in case. > > The simple approach is just to iterate after reading data from a ready pipe. > This leads to non-ideal behaviour when the subprocess has written its final Z > item, closed the pipe, and is in the process of exiting (the common case). A >-call to waitpid() yields nothing completed, but select() shows the pipe ready - >+call to waitpid() yields nothing completed, but poll() shows the pipe ready - > reading it yields EOF, so you end up with busy-waiting until the subprocess has > actually finished. > > To avoid this, if all the data that is needed has been read from a subprocess >-after select(), an explicit wait() for it is done. We know that all it is doing >+after poll(), an explicit wait() for it is done. We know that all it is doing > is writing to the pipe and then exiting, so the wait should not be long. > > The non-blocking waitpid() is to some extent just insurance; if we could >@@ -3877,9 +3881,7 @@ > { > while ((pid = waitpid(-1, &status, WNOHANG)) <= 0) > { >- struct timeval tv; >- fd_set select_pipes; >- int maxpipe, readycount; >+ int readycount; > > /* A return value of -1 can mean several things. If errno != ECHILD, it > either means invalid options (which we discount), or that this process was >@@ -3903,7 +3905,7 @@ > subprocesses are still in existence. If kill() gives an OK return, we know > it must be for one of our processes - it can't be for a re-use of the pid, > because if our process had finished, waitpid() would have found it. If any >- of our subprocesses are in existence, we proceed to use select() as if >+ of our subprocesses are in existence, we proceed to use poll() as if > waitpid() had returned zero. I think this is safe. */ > > if (pid < 0) >@@ -3927,7 +3929,7 @@ > if (poffset >= remote_max_parallel) > { > DEBUG(D_deliver) debug_printf("*** no delivery children found\n"); >- return NULL; /* This is the error return */ >+ return NULL; /* This is the error return */ > } > } > >@@ -3936,28 +3938,23 @@ > subprocess, but there are no completed subprocesses. See if any pipes are > ready with any data for reading. */ > >- DEBUG(D_deliver) debug_printf("selecting on subprocess pipes\n"); >+ DEBUG(D_deliver) debug_printf("polling subprocess pipes\n"); > >- maxpipe = 0; >- FD_ZERO(&select_pipes); > for (poffset = 0; poffset < remote_max_parallel; poffset++) > if (parlist[poffset].pid != 0) >- { >- int fd = parlist[poffset].fd; >- FD_SET(fd, &select_pipes); >- if (fd > maxpipe) maxpipe = fd; >- } >+ { >+ parpoll[poffset].fd = parlist[poffset].fd; >+ parpoll[poffset].events = POLLIN; >+ } >+ else >+ parpoll[poffset].fd = -1; > > /* Stick in a 60-second timeout, just in case. */ > >- tv.tv_sec = 60; >- tv.tv_usec = 0; >- >- readycount = select(maxpipe + 1, (SELECT_ARG2_TYPE *)&select_pipes, >- NULL, NULL, &tv); >+ readycount = poll(parpoll, remote_max_parallel, 60 * 1000); > > /* Scan through the pipes and read any that are ready; use the count >- returned by select() to stop when there are no more. Select() can return >+ returned by poll() to stop when there are no more. Select() can return > with no processes (e.g. if interrupted). This shouldn't matter. > > If par_read_pipe() returns TRUE, it means that either the terminating Z was >@@ -3974,7 +3971,7 @@ > poffset++) > { > if ( (pid = parlist[poffset].pid) != 0 >- && FD_ISSET(parlist[poffset].fd, &select_pipes) >+ && parpoll[poffset].revents > ) > { > readycount--; >@@ -4012,7 +4009,7 @@ > "transport process list", pid); > } /* End of the "for" loop */ > >-/* Come here when all the data was completely read after a select(), and >+/* Come here when all the data was completely read after a poll(), and > the process in pid has been wait()ed for. */ > > PROCESS_DONE: >@@ -4047,7 +4044,7 @@ > "%s %d", > addrlist->transport->driver_name, > status, >- (msb == 0)? "terminated by signal" : "exit code", >+ msb == 0 ? "terminated by signal" : "exit code", > code); > > if (msb != 0 || (code != SIGTERM && code != SIGKILL && code != SIGQUIT)) >@@ -4065,7 +4062,8 @@ > /* Else complete reading the pipe to get the result of the delivery, if all > the data has not yet been obtained. */ > >-else if (!parlist[poffset].done) (void)par_read_pipe(poffset, TRUE); >+else if (!parlist[poffset].done) >+ (void) par_read_pipe(poffset, TRUE); > > /* Put the data count and return path into globals, mark the data slot unused, > decrement the count of subprocesses, and return the address chain. */ >@@ -4211,9 +4209,10 @@ > > if (!parlist) > { >- parlist = store_get(remote_max_parallel * sizeof(pardata), FALSE); >+ parlist = store_get(remote_max_parallel * sizeof(pardata), GET_UNTAINTED); > for (poffset = 0; poffset < remote_max_parallel; poffset++) > parlist[poffset].pid = 0; >+ parpoll = store_get(remote_max_parallel * sizeof(struct pollfd), GET_UNTAINTED); > } > > /* Now loop for each remote delivery */ >@@ -4441,10 +4440,6 @@ > > if(addr->prop.errors_address) > return_path = addr->prop.errors_address; >-#ifdef EXPERIMENTAL_SRS >- else if(addr->prop.srs_sender) >- return_path = addr->prop.srs_sender; >-#endif > else > return_path = sender_address; > >@@ -4609,7 +4604,7 @@ > that it can use either of them, though it prefers O_NONBLOCK, which > distinguishes between EOF and no-more-data. */ > >-/* The data appears in a timely manner and we already did a select on >+/* The data appears in a timely manner and we already did a poll on > all pipes, so I do not see a reason to use non-blocking IO here > > #ifdef O_NONBLOCK >@@ -4665,8 +4660,10 @@ > int fd = pfd[pipe_write]; > host_item *h; > >- /* Setting this global in the subprocess means we need never clear it */ >- transport_name = tp->name; >+ /* Setting these globals in the subprocess means we need never clear them */ >+ transport_name = addr->transport->name; >+ driver_srcfile = tp->srcfile; >+ driver_srcline = tp->srcline; > > /* There are weird circumstances in which logging is disabled */ > f.disable_logging = tp->disable_logging; >@@ -4786,7 +4783,7 @@ > #ifdef SUPPORT_DANE > if (tls_out.dane_verified) setflag(addr, af_dane_verified); > #endif >-# ifdef EXPERIMENTAL_TLS_RESUME >+# ifndef DISABLE_TLS_RESUME > if (tls_out.resumption & RESUME_USED) setflag(addr, af_tls_resume); > # endif > >@@ -4895,6 +4892,14 @@ > rmt_dlv_checked_write(fd, 'R', '0', big_buffer, ptr - big_buffer); > } > >+ if (testflag(addr, af_new_conn) || testflag(addr, af_cont_conn)) >+ { >+ DEBUG(D_deliver) debug_printf("%scontinued-connection\n", >+ testflag(addr, af_new_conn) ? "non-" : ""); >+ big_buffer[0] = testflag(addr, af_new_conn) ? BIT(1) : BIT(2); >+ rmt_dlv_checked_write(fd, 'A', '3', big_buffer, 1); >+ } >+ > #ifdef SUPPORT_SOCKS > if (LOGGING(proxy) && proxy_session) > { >@@ -5104,7 +5109,7 @@ > this, Jan 1999.] We know the syntax is valid, so this can be done by simply > removing quoting backslashes and any unquoted doublequotes. */ > >-t = addr->cc_local_part = store_get(len+1, is_tainted(address)); >+t = addr->cc_local_part = store_get(len+1, address); > while(len-- > 0) > { > int c = *address++; >@@ -5147,7 +5152,7 @@ > > if (new_address) > { >- address_item *new_parent = store_get(sizeof(address_item), FALSE); >+ address_item * new_parent = store_get(sizeof(address_item), GET_UNTAINTED); > *new_parent = *addr; > addr->parent = new_parent; > new_parent->child_count = 1; >@@ -5341,10 +5346,10 @@ > */ > > static void >-print_address_error(address_item *addr, FILE *f, uschar *t) >+print_address_error(address_item * addr, FILE * f, const uschar * t) > { > int count = Ustrlen(t); >-uschar *s = testflag(addr, af_pass_message) ? addr->message : NULL; >+uschar * s = testflag(addr, af_pass_message) ? addr->message : NULL; > > if (!s && !(s = addr->user_message)) > return; >@@ -5884,7 +5889,7 @@ > return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */ > } > >- /* Make a C stream out of it. */ >+ /* Make a stdio stream out of it. */ > > if (!(message_log = fdopen(fd, "a"))) > { >@@ -6331,7 +6336,7 @@ > string_copyn(addr+start, dom ? (dom-1) - start : end - start); > deliver_domain = dom ? CUS string_copyn(addr+dom, end - dom) : CUS""; > >- event_raise(event_action, US"msg:fail:internal", new->message); >+ (void) event_raise(event_action, US"msg:fail:internal", new->message, NULL); > > deliver_localpart = save_local; > deliver_domain = save_domain; >@@ -6411,10 +6416,8 @@ > while (addr_new) > { > int rc; >- uschar *p; >- tree_node *tnode; >- dbdata_retry *domain_retry_record; >- dbdata_retry *address_retry_record; >+ tree_node * tnode; >+ dbdata_retry * domain_retry_record, * address_retry_record; > > addr = addr_new; > addr_new = addr->next; >@@ -6540,14 +6543,19 @@ > > /* Treat /dev/null as a special case and abandon the delivery. This > avoids having to specify a uid on the transport just for this case. >- Arrange for the transport name to be logged as "**bypassed**". */ >+ Arrange for the transport name to be logged as "**bypassed**". >+ Copy the transport for this fairly unusual case rather than having >+ to make all transports mutable. */ > > if (Ustrcmp(addr->address, "/dev/null") == 0) > { >- uschar *save = addr->transport->name; >- addr->transport->name = US"**bypassed**"; >+ transport_instance * save_t = addr->transport; >+ transport_instance * t = store_get(sizeof(*t), save_t); >+ *t = *save_t; >+ t->name = US"**bypassed**"; >+ addr->transport = t; > (void)post_process_one(addr, OK, LOG_MAIN, EXIM_DTYPE_TRANSPORT, '='); >- addr->transport->name = save; >+ addr->transport= save_t; > continue; /* with the next new address */ > } > >@@ -6626,8 +6634,7 @@ > /* Ensure that the domain in the unique field is lower cased, because > domains are always handled caselessly. */ > >- p = Ustrrchr(addr->unique, '@'); >- while (*p != 0) { *p = tolower(*p); p++; } >+ for (uschar * p = Ustrrchr(addr->unique, '@'); *p; p++) *p = tolower(*p); > > DEBUG(D_deliver|D_route) debug_printf("unique = %s\n", addr->unique); > >@@ -6824,7 +6831,7 @@ > addr_route = addr->next; > > deliver_domain = addr->domain; /* set $domain */ >- if ((rc = match_isinlist(addr->domain, (const uschar **)&queue_domains, 0, >+ if ((rc = match_isinlist(addr->domain, CUSS &queue_domains, 0, > &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL)) > != OK) > if (rc == DEFER) >@@ -7095,7 +7102,7 @@ > debug_printf("deferring local deliveries due to continued-transport\n"); > if (addr_defer) > { >- address_item *addr = addr_defer; >+ address_item * addr = addr_defer; > while (addr->next) addr = addr->next; > addr->next = addr_local; > } >@@ -7377,7 +7384,7 @@ > { > /* copy and relink address_item and send report with all of them at once later */ > address_item * addr_next = addr_senddsn; >- addr_senddsn = store_get(sizeof(address_item), FALSE); >+ addr_senddsn = store_get(sizeof(address_item), GET_UNTAINTED); > *addr_senddsn = *a; > addr_senddsn->next = addr_next; > } >@@ -8077,7 +8084,7 @@ > f.deliver_freeze = FALSE; > > #ifndef DISABLE_EVENT >- (void) event_raise(event_action, US"msg:complete", NULL); >+ (void) event_raise(event_action, US"msg:complete", NULL, NULL); > #endif > } > >@@ -8426,7 +8433,7 @@ > /* If this was a first delivery attempt, unset the first time flag, and > ensure that the spool gets updated. */ > >- if (f.deliver_firsttime) >+ if (f.deliver_firsttime && !f.queue_2stage) > { > f.deliver_firsttime = FALSE; > update_spool = TRUE; >@@ -8559,52 +8566,9 @@ > } > > >-uschar * >-deliver_get_sender_address (uschar * id) >-{ >-int rc; >-uschar * new_sender_address, >- * save_sender_address; >-BOOL save_qr = f.queue_running; >-uschar * spoolname; >- >-/* make spool_open_datafile non-noisy on fail */ >- >-f.queue_running = TRUE; >- >-/* Side effect: message_subdir is set for the (possibly split) spool directory */ >- >-deliver_datafile = spool_open_datafile(id); >-f.queue_running = save_qr; >-if (deliver_datafile < 0) >- return NULL; >- >-/* Save and restore the global sender_address. I'm not sure if we should >-not save/restore all the other global variables too, because >-spool_read_header() may change all of them. But OTOH, when this >-deliver_get_sender_address() gets called, the current message is done >-already and nobody needs the globals anymore. (HS12, 2015-08-21) */ >- >-spoolname = string_sprintf("%s-H", id); >-save_sender_address = sender_address; >- >-rc = spool_read_header(spoolname, TRUE, TRUE); >- >-new_sender_address = sender_address; >-sender_address = save_sender_address; >- >-if (rc != spool_read_OK) >- return NULL; >- >-assert(new_sender_address); >- >-(void)close(deliver_datafile); >-deliver_datafile = -1; >- >-return new_sender_address; >-} >- > >+/* Called from a commandline, or from the daemon, to do a delivery. >+We need to regain privs; do this by exec of the exim binary. */ > > void > delivery_re_exec(int exec_type) >@@ -8639,7 +8603,7 @@ > if (pid == 0) /* child: will fork again to totally disconnect */ > { > smtp_proxy_tls(cutthrough.cctx.tls_ctx, big_buffer, big_buffer_size, >- pfd, 5*60); >+ pfd, 5*60, cutthrough.host.name); > /* does not return */ > } > >diff -ur exim.orig/src/directory.c exim/src/directory.c >--- exim.orig/src/directory.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/directory.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2010 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2009 */ >-/* Copyright (c) The Exim Maintainers 2010 - 2018 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "exim.h" >@@ -44,6 +44,9 @@ > struct stat statbuf; > uschar * path; > >+if (is_tainted(name)) >+ { p = US"create"; path = US name; errno = ERRNO_TAINT; goto bad; } >+ > if (parent) > { > path = string_sprintf("%s%s%s", parent, US"/", name); >@@ -85,7 +88,7 @@ > > bad: > if (panic) log_write(0, LOG_MAIN|LOG_PANIC_DIE, >- "Failed to %s directory \"%s\": %s\n", p, path, strerror(errno)); >+ "Failed to %s directory \"%s\": %s\n", p, path, exim_errstr(errno)); > return FALSE; > } > >diff -ur exim.orig/src/dkim.c exim/src/dkim.c >--- exim.orig/src/dkim.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/dkim.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge, 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Code for DKIM support. Other DKIM relevant code is in >@@ -50,11 +50,11 @@ > dns_answer * dnsa = store_get_dns_answer(); > dns_scan dnss; > rmark reset_point = store_mark(); >-gstring * g = NULL; >+gstring * g = string_get_tainted(256, GET_TAINTED); > > lookup_dnssec_authenticated = NULL; > if (dns_lookup(dnsa, name, T_TXT, NULL) != DNS_SUCCEED) >- return NULL; /*XXX better error detail? logging? */ >+ goto bad; > > /* Search for TXT record */ > >@@ -62,12 +62,8 @@ > rr; > rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) > if (rr->type == T_TXT) >- { >- int rr_offset = 0; >- >- /* Copy record content to the answer buffer */ >- >- while (rr_offset < rr->size) >+ { /* Copy record content to the answer buffer */ >+ for (int rr_offset = 0; rr_offset < rr->size; ) > { > uschar len = rr->data[rr_offset++]; > >@@ -78,18 +74,20 @@ > rr_offset += len; > } > >- /* check if this looks like a DKIM record */ >+ /* Check if this looks like a DKIM record */ > if (Ustrncmp(g->s, "v=", 2) != 0 || strncasecmp(CS g->s, "v=dkim", 6) == 0) > { >+ store_free_dns_answer(dnsa); > gstring_release_unused(g); > return string_from_gstring(g); > } > >- if (g) g->ptr = 0; /* overwrite previous record */ >+ g->ptr = 0; /* overwrite previous record */ > } > > bad: > store_reset(reset_point); >+store_free_dns_answer(dnsa); > return NULL; /*XXX better error detail? logging? */ > } > >@@ -109,12 +107,15 @@ > { > dkim_exim_init(); > >-/* There is a store-reset between header & body reception >-so cannot use the main pool. Any allocs done by Exim >-memory-handling must use the perm pool. */ >+/* There is a store-reset between header & body reception for the main pool >+(actually, after every header line) so cannot use that as we need the data we >+store per-header, during header processing, at the end of body reception >+for evaluating the signature. Any allocs done for dkim verify >+memory-handling must use a different pool. We use a separate one that we >+can reset per message. */ > > dkim_verify_oldpool = store_pool; >-store_pool = POOL_PERM; >+store_pool = POOL_MESSAGE; > > /* Free previous context if there is one */ > >@@ -127,19 +128,22 @@ > dkim_collect_input = dkim_verify_ctx ? DKIM_MAX_SIGNATURES : 0; > dkim_collect_error = NULL; > >-/* Start feed up with any cached data */ >-receive_get_cache(); >+/* Start feed up with any cached data, but limited to message data */ >+receive_get_cache(chunking_state == CHUNKING_LAST >+ ? chunking_data_left : GETC_BUFFER_UNLIMITED); > > store_pool = dkim_verify_oldpool; > } > > >+/* Submit a chunk of data for verification input. >+Only use the data when the feed is activated. */ > void > dkim_exim_verify_feed(uschar * data, int len) > { > int rc; > >-store_pool = POOL_PERM; >+store_pool = POOL_MESSAGE; > if ( dkim_collect_input > && (rc = pdkim_feed(dkim_verify_ctx, data, len)) != PDKIM_OK) > { >@@ -305,7 +309,7 @@ > gstring * g = NULL; > const uschar * errstr = NULL; > >-store_pool = POOL_PERM; >+store_pool = POOL_MESSAGE; > > /* Delete eventual previous signature chain */ > >diff -ur exim.orig/src/dkim_transport.c exim/src/dkim_transport.c >--- exim.orig/src/dkim_transport.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/dkim_transport.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -169,7 +170,7 @@ > #ifdef EXPERIMENTAL_ARC > if (dkim->arc_signspec) /* Prepend ARC headers */ > { >- uschar * e; >+ uschar * e = NULL; > if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, &e))) > { > *err = e; >diff -ur exim.orig/src/dmarc.c exim/src/dmarc.c >--- exim.orig/src/dmarc.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/dmarc.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > /* DMARC support. >+ Copyright (c) The Exim Maintainers 2019 - 2022 > Copyright (c) Todd Lyons <tlyons@exim.org> 2012 - 2014 >- Copyright (c) The Exim Maintainers 2019 > License: GPL */ > > /* Portions Copyright (c) 2012, 2013, The Trusted Domain Project; >@@ -51,21 +51,32 @@ > { US"reject", DMARC_RECORD_P_REJECT }, > { NULL, 0 } > }; >+ >+ >+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); >+} >+ >+ > /* 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. */ >+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) > { > error_block *eb = store_malloc(sizeof(error_block)); >-if (eblock == NULL) >+if (!eblock) > eblock = eb; > else > { > /* Find the end of the eblock struct and point it at eb */ > error_block *tmp = eblock; >- while(tmp->next != NULL) >+ while(tmp->next) > tmp = tmp->next; > tmp->next = eb; > } >@@ -76,8 +87,8 @@ > } > > /* dmarc_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) */ >+messages on the same SMTP connection (that come from the >+same host with the same HELO string) */ > > int > dmarc_init() >@@ -218,7 +229,11 @@ > 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) >- return string_copyn(US rr->data, rr->size); >+ { >+ store_free_dns_answer(dnsa); >+ return string_copyn_taint(US rr->data, rr->size, GET_TAINTED); >+ } >+store_free_dns_answer(dnsa); > return NULL; > } > >@@ -226,6 +241,8 @@ > static int > dmarc_write_history_file() > { >+int history_file_fd; >+ssize_t written_len; > int tmp_ans; > u_char **rua; /* aggregate report addressees */ > uschar *history_buffer = NULL; >@@ -235,6 +252,14 @@ > DEBUG(D_receive) debug_printf("DMARC history file not set\n"); > return DMARC_HIST_DISABLED; > } >+history_file_fd = log_open_as_exim(dmarc_history_file); >+ >+if (history_file_fd < 0) >+ { >+ log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s", >+ dmarc_history_file); >+ return DMARC_HIST_FILE_ERR; >+ } > > /* Generate the contents of the history file */ > history_buffer = string_sprintf( >@@ -286,28 +311,16 @@ > } > else > { >- ssize_t written_len; >- const int history_file_fd = log_open_as_exim(dmarc_history_file); >- >- if (history_file_fd < 0) >- { >- log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s", >- dmarc_history_file); >- return DMARC_HIST_FILE_ERR; >- } >- > written_len = write_to_fd_buf(history_file_fd, > history_buffer, > Ustrlen(history_buffer)); >- >- (void)close(history_file_fd); >- >- if (written_len <= 0) >+ 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; > } >@@ -517,7 +530,7 @@ > /* 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, TRUE); >+ dmarc_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); >diff -ur exim.orig/src/dmarc.h exim/src/dmarc.h >--- exim.orig/src/dmarc.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/dmarc.h 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > > /* Experimental DMARC support. >+ Copyright (c) The Exim Maintainers 2021 - 2022 > Copyright (c) Todd Lyons <tlyons@exim.org> 2012 - 2014 > License: GPL */ > >@@ -17,15 +18,13 @@ > # endif /* SUPPORT_SPF */ > > /* prototypes */ >+gstring * dmarc_version_report(gstring *); > int dmarc_init(); > int dmarc_store_data(header_line *); > int dmarc_process(); > uschar *dmarc_exim_expand_query(int); > uschar *dmarc_exim_expand_defaults(int); >-uschar *dmarc_auth_results_header(header_line *,uschar *); >-static int dmarc_write_history_file(); > >-#define DMARC_AR_HEADER US"Authentication-Results:" > #define DMARC_VERIFY_STATUS 1 > > #define DMARC_HIST_OK 1 >ТолÑко в exim/src: dnsbl.c >diff -ur exim.orig/src/dns.c exim/src/dns.c >--- exim.orig/src/dns.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/dns.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions for interfacing with the DNS. */ >@@ -258,7 +258,7 @@ > { > int v6[4]; > >- g = string_get_tainted(32, is_tainted(string)); >+ g = string_get_tainted(32, string); > (void)host_aton(string, v6); > > /* The original specification for IPv6 reverse lookup was to invert each >@@ -334,7 +334,6 @@ > #ifdef rr_trace > # define TRACE DEBUG(D_dns) > #else >-trace = trace; > # define TRACE if (FALSE) > #endif > >@@ -638,7 +637,7 @@ > e = previous->data.ptr; > else > { >- e = store_get_perm(DNS_FAILNODE_SIZE, is_tainted(name)); >+ e = store_get_perm(DNS_FAILNODE_SIZE, name); > new = (void *)(e+1); > dns_fail_tag(new->name, name, type); > new->data.ptr = e; >@@ -672,13 +671,10 @@ > val = e->data.val; > rc = e->expiry && e->expiry <= time(NULL) ? -1 : val; > >-DEBUG(D_dns) debug_printf("DNS lookup of %.255s-%s: %scached value %s%s\n", >+DEBUG(D_dns) debug_printf("DNS lookup of %.255s (%s): %scached value %s%s\n", > name, dns_text_type(type), > rc == -1 ? "" : "using ", >- val == DNS_NOMATCH ? "DNS_NOMATCH" : >- val == DNS_NODATA ? "DNS_NODATA" : >- val == DNS_AGAIN ? "DNS_AGAIN" : >- val == DNS_FAIL ? "DNS_FAIL" : "??", >+ dns_rc_names[val], > rc == -1 ? " past valid time" : ""); > > return rc; >@@ -695,9 +691,11 @@ > replacement value. (The only way to fix this properly would be to > re-implement res_search() and res_query() so that they don't muddle their > success and packet length return values.) For added safety we only reset >-the packet length if the packet header looks plausible. */ >+the packet length if the packet header looks plausible. > >-static void >+Return TRUE iff it seemed ok */ >+ >+static BOOL > fake_dnsa_len_for_fail(dns_answer * dnsa, int type) > { > const HEADER * h = (const HEADER *)dnsa->answer; >@@ -714,7 +712,11 @@ > DEBUG(D_dns) debug_printf("faking res_search(%s) response length as %d\n", > dns_text_type(type), (int)sizeof(dnsa->answer)); > dnsa->answerlen = sizeof(dnsa->answer); >+ return TRUE; > } >+DEBUG(D_dns) debug_printf("DNS: couldn't fake dnsa len\n"); >+/* Maybe we should just do a second lookup for an SOA? */ >+return FALSE; > } > > >@@ -728,37 +730,37 @@ > { > dns_scan dnss; > >-fake_dnsa_len_for_fail(dnsa, type); >+if (fake_dnsa_len_for_fail(dnsa, type)) >+ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY); >+ rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT) >+ ) if (rr->type == T_SOA) >+ { >+ const uschar * p = rr->data; >+ uschar discard_buf[256]; >+ int len; >+ unsigned long ttl; >+ >+ /* Skip the mname & rname strings */ >+ >+ if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, >+ p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0) >+ break; >+ p += len; >+ if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, >+ p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0) >+ break; >+ p += len; >+ >+ /* Skip the SOA serial, refresh, retry & expire. Grab the TTL */ >+ >+ if (p > dnsa->answer + dnsa->answerlen - 5 * INT32SZ) >+ break; >+ p += 4 * INT32SZ; >+ GETLONG(ttl, p); > >-for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY); >- rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT) >- ) if (rr->type == T_SOA) >- { >- const uschar * p = rr->data; >- uschar discard_buf[256]; >- int len; >- unsigned long ttl; >- >- /* Skip the mname & rname strings */ >- >- if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, >- p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0) >- break; >- p += len; >- if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, >- p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0) >- break; >- p += len; >- >- /* Skip the SOA serial, refresh, retry & expire. Grab the TTL */ >- >- if (p > dnsa->answer + dnsa->answerlen - 5 * INT32SZ) >- break; >- p += 4 * INT32SZ; >- GETLONG(ttl, p); >+ return time(NULL) + ttl; >+ } > >- return time(NULL) + ttl; >- } > DEBUG(D_dns) debug_printf("DNS: no SOA record found for neg-TTL\n"); > return 0; > } >@@ -848,11 +850,8 @@ > > if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT) > { >- int ovector[3*(EXPAND_MAXN+1)]; >- > dns_pattern_init(); >- if (pcre_exec(regex_check_dns_names, NULL, CCS name, Ustrlen(name), >- 0, PCRE_EOPT, ovector, nelem(ovector)) < 0) >+ if (!regex_match(regex_check_dns_names, name, -1, NULL)) > { > DEBUG(D_dns) > debug_printf("DNS name syntax check failed: %s (%s)\n", name, >@@ -1066,7 +1065,7 @@ > return DNS_FAIL; > > /* DNS data comes from the outside, hence tainted */ >- data = store_get(256, TRUE); >+ data = store_get(256, GET_TAINTED); > if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, > cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, 256) < 0) > return DNS_FAIL; >@@ -1149,7 +1148,7 @@ > case T_CSA: > { > uschar *srvname, *namesuff, *tld; >- int priority, weight, port; >+ int priority, dummy_weight, port; > int limit, rc, i; > BOOL ipv6; > dns_record *rr; >@@ -1203,18 +1202,7 @@ > If the TLD and the 2LD exist but the explicit CSA record lookup failed, then > the AUTHORITY SOA will be the 2LD's or a subdomain thereof. */ > >- if (rc == DNS_NOMATCH) >- { >- fake_dnsa_len_for_fail(dnsa, T_CSA); >- >- for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY); >- rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT) >- ) >- if (rr->type != T_SOA) continue; >- else if (strcmpic(rr->name, US"") == 0 || >- strcmpic(rr->name, tld) == 0) return DNS_NOMATCH; >- else break; >- } >+ if (rc == DNS_NOMATCH) return DNS_NOMATCH; > > for (i = 0; i < limit; i++) > { >@@ -1249,7 +1237,7 @@ > > /* Extract the numerical SRV fields (p is incremented) */ > GETSHORT(priority, p); >- GETSHORT(weight, p); weight = weight; /* compiler quietening */ >+ GETSHORT(dummy_weight, p); > GETSHORT(port, p); > > /* Check the CSA version number */ >@@ -1305,7 +1293,7 @@ > if (p + 4 <= dnsa_lim) > { > /* the IP is not regarded as tainted */ >- yield = store_get(sizeof(dns_address) + 20, FALSE); >+ yield = store_get(sizeof(dns_address) + 20, GET_UNTAINTED); > (void)sprintf(CS yield->address, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); > yield->next = NULL; > } >@@ -1319,7 +1307,7 @@ > { > struct in6_addr in6; > for (int i = 0; i < 16; i++) in6.s6_addr[i] = rr->data[i]; >- yield = store_get(sizeof(dns_address) + 50, FALSE); >+ yield = store_get(sizeof(dns_address) + 50, GET_UNTAINTED); > inet_ntop(AF_INET6, &in6, CS yield->address, 50); > yield->next = NULL; > } >diff -ur exim.orig/src/drtables.c exim/src/drtables.c >--- exim.orig/src/drtables.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/drtables.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -518,7 +518,7 @@ > static void > addlookupmodule(void *dl, struct lookup_module_info *info) > { >-struct lookupmodulestr *p = store_get(sizeof(struct lookupmodulestr), FALSE); >+struct lookupmodulestr *p = store_get(sizeof(struct lookupmodulestr), GET_UNTAINTED); > > p->dl = dl; > p->info = info; >@@ -602,7 +602,7 @@ > #if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2 > extern lookup_module_info redis_lookup_module_info; > #endif >-#if defined(EXPERIMENTAL_LMDB) >+#if defined(LOOKUP_LMDB) > extern lookup_module_info lmdb_lookup_module_info; > #endif > #if defined(SUPPORT_SPF) >@@ -698,7 +698,7 @@ > addlookupmodule(NULL, &redis_lookup_module_info); > #endif > >-#ifdef EXPERIMENTAL_LMDB >+#ifdef LOOKUP_LMDB > addlookupmodule(NULL, &lmdb_lookup_module_info); > #endif > >@@ -728,15 +728,15 @@ > } > else > { >- const pcre *regex_islookupmod = regex_must_compile( >+ const pcre2_code *regex_islookupmod = regex_must_compile( > US"\\." DYNLIB_FN_EXT "$", FALSE, TRUE); > > DEBUG(D_lookup) debug_printf("Loading lookup modules from %s\n", LOOKUP_MODULE_DIR); > while ((ent = readdir(dd))) > { >- char *name = ent->d_name; >+ char * name = ent->d_name; > int len = (int)strlen(name); >- if (pcre_exec(regex_islookupmod, NULL, name, len, 0, PCRE_EOPT, NULL, 0) >= 0) >+ if (regex_match(regex_islookupmod, US name, len, NUL)) > { > int pathnamelen = len + (int)strlen(LOOKUP_MODULE_DIR) + 2; > void *dl; >@@ -764,10 +764,10 @@ > } > > /* FreeBSD nsdispatch() can trigger dlerror() errors about >- * _nss_cache_cycle_prevention_function; we need to clear the dlerror() >- * state before calling dlsym(), so that any error afterwards only >- * comes from dlsym(). >- */ >+ _nss_cache_cycle_prevention_function; we need to clear the dlerror() >+ state before calling dlsym(), so that any error afterwards only comes >+ from dlsym(). */ >+ > errormsg = dlerror(); > > info = (struct lookup_module_info*) dlsym(dl, "_lookup_module_info"); >diff -ur exim.orig/src/dummies.c exim/src/dummies.c >--- exim.orig/src/dummies.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/dummies.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2009 */ >+/* Copyright (c) The Exim Maintainers 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* This file is not part of the main Exim code. There are little bits of test >@@ -47,8 +48,6 @@ > vfprintf(stderr, format, ap); > fprintf(stderr, "\n"); > va_end(ap); >-selector = selector; /* Keep picky compilers happy */ >-flags = flags; > } > > >@@ -103,7 +102,6 @@ > void > sigalrm_handler(int sig) > { >-sig = sig; /* Keep picky compilers happy */ > sigalrm_seen = TRUE; > } > >@@ -116,19 +114,12 @@ > int > header_checkname(void *h, char *name, int len) > { >-h = h; /* Keep picky compilers happy */ >-name = name; >-len = len; > return 0; > } > > void > directory_make(char *parent, char *name, int mode, int panic) > { >-parent = parent; /* Keep picky compilers happy */ >-name = name; >-mode = mode; >-panic = panic; > } > > void >@@ -140,10 +131,6 @@ > char * > host_ntoa(int type, const void *arg, char *buffer, int *portptr) > { >-type = type; /* Keep picky compilers happy */ >-arg = arg; >-buffer = buffer; >-portptr = portptr; > return NULL; > } > #endif >diff -ur exim.orig/src/EDITME exim/src/EDITME >--- exim.orig/src/EDITME 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/EDITME 2022-06-23 16:41:10.000000000 +0300 >@@ -198,7 +198,7 @@ > # the libraries and headers are installed, as the pkg-config .pc > # specification should include all -L/-I information necessary. > # Enabling the USE_*_PC options should be sufficient. If not using >-# pkg-config, then you have to specify the libraries, and you mmight >+# pkg-config, then you have to specify the libraries, and you might > # need to specify the locations too. > > # Uncomment the following lines if you want >@@ -207,7 +207,7 @@ > # Unless you do this, you must define one of USE_OPENSSL or USE_GNUTLS > # below. > >-# If you are buliding with TLS, the library configuration must be done: >+# If you are building with TLS, the library configuration must be done: > > # Uncomment this if you are using OpenSSL > # USE_OPENSSL=yes >@@ -276,6 +276,9 @@ > # specified in INCLUDE. > > >+# Uncomment the following line to remove support for TLS Resumption >+# DISABLE_TLS_RESUME=yes >+ > > ############################################################################### > # THESE ARE THINGS YOU PROBABLY WANT TO SPECIFY # >@@ -411,6 +414,8 @@ > # LOOKUP_IBASE=yes > # LOOKUP_JSON=yes > # LOOKUP_LDAP=yes >+# LOOKUP_LMDB=yes >+ > # LOOKUP_MYSQL=yes > # LOOKUP_MYSQL_PC=mariadb > # LOOKUP_NIS=yes >@@ -452,19 +457,19 @@ > > > #------------------------------------------------------------------------------ >-# The PCRE library is required for Exim. There is no longer an embedded >+# The PCRE2 library is required for Exim. There is no longer an embedded > # version of the PCRE library included with the source code, instead you >-# must use a system library or build your own copy of PCRE. >+# must use a system library or build your own copy of PCRE2. > # In either case you must specify the library link info here. If the >-# PCRE header files are not in the standard search path you must also >+# PCRE2 header files are not in the standard search path you must also > # modify the INCLUDE path (above) > # > # Use PCRE_CONFIG to query the pcre-config command (first found in $PATH) > # to find the include files and libraries, else use PCRE_LIBS and set INCLUDE > # too if needed. > >-PCRE_CONFIG=yes >-# PCRE_LIBS=-lpcre >+PCRE2_CONFIG=yes >+# PCRE_LIBS=-lpcre2 > > > #------------------------------------------------------------------------------ >@@ -487,7 +492,15 @@ > # You do not need to use this for any lookup information added via pkg-config. > > # LOOKUP_INCLUDE=-I /usr/local/ldap/include -I /usr/local/mysql/include -I /usr/local/pgsql/include >-# LOOKUP_LIBS=-L/usr/local/lib -lldap -llber -lmysqlclient -lpq -lgds -lsqlite3 >+# LOOKUP_INCLUDE +=-I /usr/local/include >+# LOOKUP_LIBS=-L/usr/local/lib -lldap -llber -lmysqlclient -lpq -lgds -lsqlite3 -llmdb >+ >+#------------------------------------------------------------------------------ >+# If you included LOOKUP_LMDB above you will need the library. Depending >+# on where installed you may also need an include directory >+# >+# LOOKUP_INCLUDE += -I/usr/local/include >+# LOOKUP_LIBS += -llmdb > > > #------------------------------------------------------------------------------ >@@ -560,7 +573,6 @@ > # DISABLE_DNSSEC=yes > > # To disable support for Events set DISABLE_EVENT to "yes" >- > # DISABLE_EVENT=yes > > >@@ -569,6 +581,14 @@ > # DISABLE_PIPE_CONNECT=yes > > >+# Uncomment the following to remove the fast-ramp two-phase-queue-run support >+# DISABLE_QUEUE_RAMP=yes >+ >+# Uncomment the following lines to add SRS (Sender Rewriting Scheme) support >+# using only native facilities. >+# SUPPORT_SRS=yes >+ >+ > #------------------------------------------------------------------------------ > # Compiling Exim with experimental features. These are documented in > # experimental-spec.txt. "Experimental" means that the way these features are >@@ -580,21 +600,10 @@ > > # EXPERIMENTAL_DCC=yes > >-# Uncomment the following lines to add SRS (Sender rewriting scheme) support. >-# You need to have libsrs_alt installed on your system (srs.mirtol.com). >-# Depending on where it is installed you may have to edit the CFLAGS and >-# LDFLAGS lines. >- >-# EXPERIMENTAL_SRS=yes >-# CFLAGS += -I/usr/local/include >-# LDFLAGS += -lsrs_alt >- >-# Uncomment the following lines to add SRS (Sender rewriting scheme) support >-# using only native facilities. >-# EXPERIMENTAL_SRS_NATIVE=yes >- > # Uncomment the following line to add DMARC checking capability, implemented > # using libopendmarc libraries. You must have SPF and DKIM support enabled also. >+# Library version libopendmarc-1.4.1-1.fc33.x86_64 (on Fedora 33) is known broken; >+# 1.3.2-3 works. I seems that the OpenDMARC project broke their API. > # SUPPORT_DMARC=yes > # CFLAGS += -I/usr/local/include > # LDFLAGS += -lopendmarc >@@ -618,22 +627,9 @@ > # Uncomment the following to include extra information in fail DSN message (bounces) > # EXPERIMENTAL_DSN_INFO=yes > >-# Uncomment the following to add LMDB lookup support >-# You need to have LMDB installed on your system (https://github.com/LMDB/lmdb) >-# Depending on where it is installed you may have to edit the CFLAGS and LDFLAGS lines. >-# EXPERIMENTAL_LMDB=yes >-# CFLAGS += -I/usr/local/include >-# LDFLAGS += -llmdb >- > # Uncomment the following line to add queuefile transport support > # EXPERIMENTAL_QUEUEFILE=yes > >-# Uncomment the following line to include support for TLS Resumption >-# EXPERIMENTAL_TLS_RESUME=yes >- >-# Uncomment the following to include the fast-ramp two-phase-queue-run support >-# EXPERIMENTAL_QUEUE_RAMP=yes >- > ############################################################################### > # THESE ARE THINGS YOU MIGHT WANT TO SPECIFY # > ############################################################################### >@@ -1493,4 +1489,9 @@ > # For development, add this to include code to time various stages and report. > # CFLAGS += -DMEASURE_TIMING > >+# For a very slightly smaller build, for constrained systems, uncomment this. >+# The feature involved is purely for debugging. >+ >+# DISABLE_CLIENT_CMD_LOG=yes >+ > # End of EDITME for Exim 4. >diff -ur exim.orig/src/enq.c exim/src/enq.c >--- exim.orig/src/enq.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/enq.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2015 */ >+/* Copyright (c) The Exim Maintainers 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions concerned with serialization. */ >@@ -53,7 +54,7 @@ > /* See if there is a record for this host or queue run; if there is, we cannot > proceed with the connection unless the record is very old. */ > >-serial_record = dbfn_read(dbm_file, key); >+serial_record = dbfn_read_enforce_length(dbm_file, key, sizeof(dbdata_serialize)); > if (serial_record && time(NULL) - serial_record->time_stamp < 6*60*60) > { > if (serial_record->count >= lim) >@@ -102,7 +103,7 @@ > DEBUG(D_transport) debug_printf("end serialized: %s\n", key); > > if ( !(dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)) >- || !(serial_record = dbfn_read(dbm_file, key)) >+ || !(serial_record = dbfn_read_enforce_length(dbm_file, key, sizeof(dbdata_serialize))) > ) > return; > if (--serial_record->count > 0) >diff -ur exim.orig/src/exigrep.src exim/src/exigrep.src >--- exim.orig/src/exigrep.src 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/exigrep.src 2022-06-23 16:41:10.000000000 +0300 >@@ -9,7 +9,7 @@ > use File::Basename; > > # Copyright (c) 2007-2017 University of Cambridge. >-# Copyright (c) The Exim Maintainers 2020 >+# Copyright (c) The Exim Maintainers 2020 - 2021 > # See the file NOTICE for conditions of use and distribution. > > # Except when they appear in comments, the following placeholders in this >@@ -45,20 +45,21 @@ > # the number of seconds since the epoch. It handles optional timezone > # information. > >-sub seconds { >-my($year,$month,$day,$hour,$min,$sec,$tzs,$tzh,$tzm) = >- $_[0] =~ /^(\d{4})-(\d\d)-(\d\d)\s(\d\d):(\d\d):(\d\d)(?:.\d+)?(?>\s([+-])(\d\d)(\d\d))?/o; >+sub seconds >+ { >+ my($year,$month,$day,$hour,$min,$sec,$tzs,$tzh,$tzm) = >+ $_[0] =~ /^(\d{4})-(\d\d)-(\d\d)\s(\d\d):(\d\d):(\d\d)(?:.\d+)?(?>\s([+-])(\d\d)(\d\d))?/o; > >-my $seconds = mktime $sec, $min, $hour, $day, $month - 1, $year - 1900; >+ my $seconds = mktime $sec, $min, $hour, $day, $month - 1, $year - 1900; > >-if (defined $tzs) >- { >- $seconds -= $tzh * 3600 + $tzm * 60 if $tzs eq "+"; >- $seconds += $tzh * 3600 + $tzm * 60 if $tzs eq "-"; >- } >+ if (defined $tzs) >+ { >+ $seconds -= $tzh * 3600 + $tzm * 60 if $tzs eq "+"; >+ $seconds += $tzh * 3600 + $tzm * 60 if $tzs eq "-"; >+ } > >-return $seconds; >-} >+ return $seconds; >+ } > > > # This subroutine processes a single line (in $_) from a log file. Program >@@ -78,77 +79,78 @@ > my $related_re=''; > my @Mids = (); > >-sub do_line { >+sub do_line >+ { > >-# Convert syslog lines to mainlog format, as in eximstats. >+ # Convert syslog lines to mainlog format, as in eximstats. > >-if (!/^\d{4}-/o) { $_ =~ s/^.*? exim\b.*?: //o; } >+ if (!/^\d{4}-/o) { $_ =~ s/^.*? exim\b.*?: //o; } > >-return unless >- my($date,$id) = /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d(?:\.\d+)? (?:[+-]\d{4} )?)(?:\[\d+\] )?(\w{6}\-\w{6}\-\w{2})?/o; >+ return unless >+ my($date,$id) = /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d(?:\.\d+)? (?:[+-]\d{4} )?)(?:\[\d+\] )?(\w{6}\-\w{6}\-\w{2})?/o; > >-# Handle the case when the log line belongs to a specific message. We save >-# lines for specific messages until the message is complete. Then either print >-# or discard. >+ # Handle the case when the log line belongs to a specific message. We save >+ # lines for specific messages until the message is complete. Then either print >+ # or discard. > >-if (defined $id) >- { >- $saved{$id} = '' unless defined($saved{$id}); >+ if (defined $id) >+ { >+ $saved{$id} = '' unless defined($saved{$id}); > >- # Save up the data for this message in case it becomes interesting later. >+ # Save up the data for this message in case it becomes interesting later. > >- $saved{$id} .= $_; >+ $saved{$id} .= $_; > >- # Are we interested in this id ? Short circuit if we already were interested. >+ # Are we interested in this id ? Short circuit if we already were interested. > >- if ($invert) >- { >- $id_list{$id} = 1 if (!defined($id_list{$id})); >- $id_list{$id} = 0 if (($insensitive && /$pattern/io) || /$pattern/o); >- } >- else >- { >- if (defined $id_list{$id} || >- ($insensitive && /$pattern/io) || /$pattern/o) >+ if ($invert) > { >- $id_list{$id} = 1; >- get_related_ids($id) if $related; >+ $id_list{$id} = 1 if (!defined($id_list{$id})); >+ $id_list{$id} = 0 if (($insensitive && /$pattern/io) || /$pattern/o); > } >- elsif ($related && $related_re) >+ else > { >- grep_for_related($_, $id); >+ if (defined $id_list{$id} || >+ ($insensitive && /$pattern/io) || /$pattern/o) >+ { >+ $id_list{$id} = 1; >+ get_related_ids($id) if $related; >+ } >+ elsif ($related && $related_re) >+ { >+ grep_for_related($_, $id); >+ } > } >- } > >- # See if this is a completion for some message. If it is interesting, >- # print it, but in any event, throw away what was saved. >+ # See if this is a completion for some message. If it is interesting, >+ # print it, but in any event, throw away what was saved. > >- if (index($_, 'Completed') != -1 || >- index($_, 'SMTP data timeout') != -1 || >- (index($_, 'rejected') != -1 && >- /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d(?:\.\d+)? (?:[+-]\d{4} )?)(?:\[\d+\] )?\w{6}\-\w{6}\-\w{2} rejected/o)) >- { >- if ($queue_time != -1 && >- $saved{$id} =~ /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d ([+-]\d{4} )?)/o) >+ if (index($_, 'Completed') != -1 || >+ index($_, 'SMTP data timeout') != -1 || >+ (index($_, 'rejected') != -1 && >+ /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d(?:\.\d+)? (?:[+-]\d{4} )?)(?:\[\d+\] )?\w{6}\-\w{6}\-\w{2} rejected/o)) > { >- my $old_sec = &seconds($1); >- my $sec = &seconds($date); >- $id_list{$id} = 0 if $id_list{$id} && $sec - $old_sec <= $queue_time; >+ if ($queue_time != -1 && >+ $saved{$id} =~ /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d ([+-]\d{4} )?)/o) >+ { >+ my $old_sec = &seconds($1); >+ my $sec = &seconds($date); >+ $id_list{$id} = 0 if $id_list{$id} && $sec - $old_sec <= $queue_time; >+ } >+ >+ print "$saved{$id}\n" if ($id_list{$id}); >+ delete $id_list{$id}; >+ delete $saved{$id}; > } >- >- print "$saved{$id}\n" if ($id_list{$id}); >- delete $id_list{$id}; >- delete $saved{$id}; > } >- } > >-# Handle the case where the log line does not belong to a specific message. >-# Print it if it is interesting. >+ # Handle the case where the log line does not belong to a specific message. >+ # Print it if it is interesting. > >-elsif ( ($invert && (($insensitive && !/$pattern/io) || !/$pattern/o)) || >- (!$invert && (($insensitive && /$pattern/io) || /$pattern/o)) ) >- { print "$_\n"; } >-} >+ elsif ( ($invert && (($insensitive && !/$pattern/io) || !/$pattern/o)) || >+ (!$invert && (($insensitive && /$pattern/io) || /$pattern/o)) ) >+ { print "$_\n"; } >+ } > > # Rotated log files are frequently compressed and there are a variety of > # formats it could be compressed with. Rather than use just one that is >@@ -200,17 +202,19 @@ > return $cmdline; > } > >-sub grep_for_related { >+sub grep_for_related >+ { > my ($line,$id) = @_; > $id_list{$id} = 1 if $line =~ m/$related_re/; >-} >+ } > >-sub get_related_ids { >+sub get_related_ids >+ { > my ($id) = @_; > push @Mids, $id unless grep /\b$id\b/, @Mids; > my $re = join '|', @Mids; > $related_re = qr/$re/; >-} >+ } > > # The main program. Extract the pattern and make sure any relevant characters > # are quoted if the -l flag is given. The -t flag gives a time-on-queue value >diff -ur exim.orig/src/exim.c exim/src/exim.c >--- exim.orig/src/exim.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/exim.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -40,31 +40,19 @@ > for store allocation via Exim's store manager. The normal calls are actually > macros that pass over location information to make tracing easier. These > functions just interface to the standard macro calls. A good compiler will >-optimize out the tail recursion and so not make them too expensive. There >-are two sets of functions; one for use when we want to retain the compiled >-regular expression for a long time; the other for short-term use. */ >+optimize out the tail recursion and so not make them too expensive. */ > > static void * >-function_store_get(size_t size) >-{ >-/* For now, regard all RE results as potentially tainted. We might need >-more intelligence on this point. */ >-return store_get((int)size, TRUE); >-} >- >-static void >-function_dummy_free(void *block) { block = block; } >- >-static void * >-function_store_malloc(size_t size) >+function_store_malloc(PCRE2_SIZE size, void * tag) > { > return store_malloc((int)size); > } > > static void >-function_store_free(void *block) >+function_store_free(void * block, void * tag) > { >-store_free(block); >+/* At least some version of pcre2 pass a null pointer */ >+if (block) store_free(block); > } > > >@@ -98,29 +86,51 @@ > Returns: pointer to the compiled pattern > */ > >-const pcre * >-regex_must_compile(const uschar *pattern, BOOL caseless, BOOL use_malloc) >+const pcre2_code * >+regex_must_compile(const uschar * pattern, BOOL caseless, BOOL use_malloc) > { >-int offset; >-int options = PCRE_COPT; >-const pcre *yield; >-const uschar *error; >+size_t offset; >+int options = caseless ? PCRE_COPT|PCRE2_CASELESS : PCRE_COPT; >+const pcre2_code * yield; >+int err; >+pcre2_general_context * gctx; >+pcre2_compile_context * cctx; >+ > if (use_malloc) > { >- pcre_malloc = function_store_malloc; >- pcre_free = function_store_free; >+ gctx = pcre2_general_context_create(function_store_malloc, function_store_free, NULL); >+ cctx = pcre2_compile_context_create(gctx); > } >-if (caseless) options |= PCRE_CASELESS; >-yield = pcre_compile(CCS pattern, options, CCSS &error, &offset, NULL); >-pcre_malloc = function_store_get; >-pcre_free = function_dummy_free; >-if (yield == NULL) >+else >+ cctx = pcre_cmp_ctx; >+ >+if (!(yield = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, options, >+ &err, &offset, cctx))) >+ { >+ uschar errbuf[128]; >+ pcre2_get_error_message(err, errbuf, sizeof(errbuf)); > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "regular expression error: " >- "%s at offset %d while compiling %s", error, offset, pattern); >+ "%s at offset %ld while compiling %s", errbuf, (long)offset, pattern); >+ } >+ >+if (use_malloc) >+ { >+ pcre2_compile_context_free(cctx); >+ pcre2_general_context_free(gctx); >+ } > return yield; > } > > >+static void >+pcre_init(void) >+{ >+pcre_gen_ctx = pcre2_general_context_create(function_store_malloc, function_store_free, NULL); >+pcre_cmp_ctx = pcre2_compile_context_create(pcre_gen_ctx); >+pcre_mtc_ctx = pcre2_match_context_create(pcre_gen_ctx); >+} >+ >+ > > > /************************************************* >@@ -128,7 +138,8 @@ > *************************************************/ > > /* This function runs a regular expression match, and sets up the pointers to >-the matched substrings. >+the matched substrings. The matched strings are copied so the lifetime of >+the subject is not a problem. > > Arguments: > re the compiled expression >@@ -138,32 +149,67 @@ > if >= 0 setup from setup+1 onwards, > excluding the full matched string > >-Returns: TRUE or FALSE >+Returns: TRUE if matched, or FALSE > */ > > BOOL >-regex_match_and_setup(const pcre *re, const uschar *subject, int options, int setup) >+regex_match_and_setup(const pcre2_code * re, const uschar * subject, int options, int setup) > { >-int ovector[3*(EXPAND_MAXN+1)]; >-uschar * s = string_copy(subject); /* de-constifying */ >-int n = pcre_exec(re, NULL, CS s, Ustrlen(s), 0, >- PCRE_EOPT | options, ovector, nelem(ovector)); >-BOOL yield = n >= 0; >-if (n == 0) n = EXPAND_MAXN + 1; >-if (yield) >+pcre2_match_data * md = pcre2_match_data_create_from_pattern(re, pcre_gen_ctx); >+int res = pcre2_match(re, (PCRE2_SPTR)subject, PCRE2_ZERO_TERMINATED, 0, >+ PCRE_EOPT | options, md, pcre_mtc_ctx); >+BOOL yield; >+ >+if ((yield = (res >= 0))) > { >+ res = pcre2_get_ovector_count(md); > expand_nmax = setup < 0 ? 0 : setup + 1; >- for (int nn = setup < 0 ? 0 : 2; nn < n*2; nn += 2) >+ for (int matchnum = setup < 0 ? 0 : 1; matchnum < res; matchnum++) > { >- expand_nstring[expand_nmax] = s + ovector[nn]; >- expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn]; >+ PCRE2_SIZE len; >+ pcre2_substring_get_bynumber(md, matchnum, >+ (PCRE2_UCHAR **)&expand_nstring[expand_nmax], &len); >+ expand_nlength[expand_nmax++] = (int)len; > } > expand_nmax--; > } >+else if (res != PCRE2_ERROR_NOMATCH) DEBUG(D_any) >+ { >+ uschar errbuf[128]; >+ pcre2_get_error_message(res, errbuf, sizeof(errbuf)); >+ debug_printf_indent("pcre2: %s\n", errbuf); >+ } >+pcre2_match_data_free(md); > return yield; > } > > >+/* Check just for match with regex. Uses the common memory-handling. >+ >+Arguments: >+ re compiled regex >+ subject string to be checked >+ slen length of subject; -1 for nul-terminated >+ rptr pointer for matched string, copied, or NULL >+ >+Return: TRUE for a match. >+*/ >+ >+BOOL >+regex_match(const pcre2_code * re, const uschar * subject, int slen, uschar ** rptr) >+{ >+pcre2_match_data * md = pcre2_match_data_create(1, pcre_gen_ctx); >+int rc = pcre2_match(re, (PCRE2_SPTR)subject, >+ slen >= 0 ? slen : PCRE2_ZERO_TERMINATED, >+ 0, PCRE_EOPT, md, pcre_mtc_ctx); >+PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md); >+if (rc < 0) >+ return FALSE; >+if (rptr) >+ *rptr = string_copyn(subject + ovec[0], ovec[1] - ovec[0]); >+return TRUE; >+} >+ > > > /************************************************* >@@ -211,6 +257,46 @@ > } > > >+/*********************************************** >+* Handler for SIGSEGV * >+***********************************************/ >+ >+static void >+#ifdef SA_SIGINFO >+segv_handler(int sig, siginfo_t * info, void * uctx) >+{ >+log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (fault address: %p)", info->si_addr); >+# if defined(SEGV_MAPERR) && defined(SEGV_ACCERR) && defined(SEGV_BNDERR) && defined(SEGV_PKUERR) >+switch (info->si_code) >+ { >+ case SEGV_MAPERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_MAPERR"); break; >+ case SEGV_ACCERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_ACCERR"); break; >+ case SEGV_BNDERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_BNDERR"); break; >+ case SEGV_PKUERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_PKUERR"); break; >+ } >+# endif >+if (US info->si_addr < US 4096) >+ log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (null pointer indirection)"); >+else >+ log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (maybe attempt to write to immutable memory)"); >+if (process_info_len > 0) >+ log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (%.*s)", process_info_len, process_info); >+signal(SIGSEGV, SIG_DFL); >+kill(getpid(), sig); >+} >+ >+#else >+segv_handler(int sig) >+{ >+log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (maybe attempt to write to immutable memory)"); >+if (process_info_len > 0) >+ log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (%.*s)", process_info_len, process_info); >+signal(SIGSEGV, SIG_DFL); >+kill(getpid(), sig); >+} >+#endif >+ >+ > /************************************************* > * Handler for SIGUSR1 * > *************************************************/ >@@ -269,7 +355,6 @@ > void > sigalrm_handler(int sig) > { >-sig = sig; /* Keep picky compilers happy */ > sigalrm_seen = TRUE; > os_non_restarting_signal(SIGALRM, sigalrm_handler); > } >@@ -432,9 +517,10 @@ > clocks that go backwards. > > Arguments: >- tgt_tv A timeval which was used to create uniqueness; its usec field >+ prev_tv A timeval which was used to create uniqueness; its usec field > has been rounded down to the value of the resolution. > We want to be sure the current time is greater than this. >+ On return, updated to current (rounded down). > resolution The resolution that was used to divide the microseconds > (1 for maildir, larger for message ids) > >@@ -442,7 +528,7 @@ > */ > > void >-exim_wait_tick(struct timeval * tgt_tv, int resolution) >+exim_wait_tick(struct timeval * prev_tv, int resolution) > { > struct timeval now_tv; > long int now_true_usec; >@@ -451,13 +537,13 @@ > now_true_usec = now_tv.tv_usec; > now_tv.tv_usec = (now_true_usec/resolution) * resolution; > >-while (exim_tvcmp(&now_tv, tgt_tv) <= 0) >+while (exim_tvcmp(&now_tv, prev_tv) <= 0) > { > struct itimerval itval; > itval.it_interval.tv_sec = 0; > itval.it_interval.tv_usec = 0; >- itval.it_value.tv_sec = tgt_tv->tv_sec - now_tv.tv_sec; >- itval.it_value.tv_usec = tgt_tv->tv_usec + resolution - now_true_usec; >+ itval.it_value.tv_sec = prev_tv->tv_sec - now_tv.tv_sec; >+ itval.it_value.tv_usec = prev_tv->tv_usec + resolution - now_true_usec; > > /* We know that, overall, "now" is less than or equal to "then". Therefore, a > negative value for the microseconds is possible only in the case when "now" >@@ -475,7 +561,7 @@ > if (!f.running_in_test_harness) > { > debug_printf("tick check: " TIME_T_FMT ".%06lu " TIME_T_FMT ".%06lu\n", >- tgt_tv->tv_sec, (long) tgt_tv->tv_usec, >+ prev_tv->tv_sec, (long) prev_tv->tv_usec, > now_tv.tv_sec, (long) now_tv.tv_usec); > debug_printf("waiting " TIME_T_FMT ".%06lu sec\n", > itval.it_value.tv_sec, (long) itval.it_value.tv_usec); >@@ -487,10 +573,11 @@ > /* Be prapared to go around if the kernel does not implement subtick > granularity (GNU Hurd) */ > >- (void)gettimeofday(&now_tv, NULL); >+ exim_gettime(&now_tv); > now_true_usec = now_tv.tv_usec; > now_tv.tv_usec = (now_true_usec/resolution) * resolution; > } >+*prev_tv = now_tv; > } > > >@@ -555,7 +642,7 @@ > { > if (devnull < 0) devnull = open("/dev/null", O_RDWR); > if (devnull < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", >- string_open_failed(errno, "/dev/null", NULL)); >+ string_open_failed("/dev/null", NULL)); > if (devnull != i) (void)dup2(devnull, i); > } > } >@@ -652,7 +739,7 @@ > */ > > void >-exim_setugid(uid_t uid, gid_t gid, BOOL igflag, uschar *msg) >+exim_setugid(uid_t uid, gid_t gid, BOOL igflag, const uschar * msg) > { > uid_t euid = geteuid(); > gid_t egid = getegid(); >@@ -861,7 +948,7 @@ > int rc = verify_address(deliver_make_addr(address,TRUE), stdout, flags, -1, > -1, -1, NULL, NULL, NULL); > if (rc == FAIL) *exit_value = 2; >- else if (rc == DEFER && *exit_value == 0) *exit_value = 1; >+ else if (rc == DEFER && *exit_value == 0) *exit_value = 1; > } > } > >@@ -872,54 +959,66 @@ > *************************************************/ > > static void >-show_db_version(FILE * f) >+show_string(BOOL is_stdout, gstring * g) >+{ >+const uschar * s = string_from_gstring(g); >+if (s) >+ if (is_stdout) fputs(CCS s, stdout); >+ else debug_printf("%s", s); >+} >+ >+ >+static gstring * >+show_db_version(gstring * g) > { > #ifdef DB_VERSION_STRING > DEBUG(D_any) > { >- fprintf(f, "Library version: BDB: Compile: %s\n", DB_VERSION_STRING); >- fprintf(f, " Runtime: %s\n", >+ g = string_fmt_append(g, "Library version: BDB: Compile: %s\n", DB_VERSION_STRING); >+ g = string_fmt_append(g, " Runtime: %s\n", > db_version(NULL, NULL, NULL)); > } > else >- fprintf(f, "Berkeley DB: %s\n", DB_VERSION_STRING); >+ g = string_fmt_append(g, "Berkeley DB: %s\n", DB_VERSION_STRING); > > #elif defined(BTREEVERSION) && defined(HASHVERSION) >- #ifdef USE_DB >- fprintf(f, "Probably Berkeley DB version 1.8x (native mode)\n"); >- #else >- fprintf(f, "Probably Berkeley DB version 1.8x (compatibility mode)\n"); >- #endif >+# ifdef USE_DB >+ g = string_cat(g, US"Probably Berkeley DB version 1.8x (native mode)\n"); >+# else >+ g = string_cat(g, US"Probably Berkeley DB version 1.8x (compatibility mode)\n"); >+# endif > > #elif defined(_DBM_RDONLY) || defined(dbm_dirfno) >-fprintf(f, "Probably ndbm\n"); >+g = string_cat(g, US"Probably ndbm\n"); > #elif defined(USE_TDB) >-fprintf(f, "Using tdb\n"); >+g = string_cat(g, US"Using tdb\n"); > #else >- #ifdef USE_GDBM >- fprintf(f, "Probably GDBM (native mode)\n"); >- #else >- fprintf(f, "Probably GDBM (compatibility mode)\n"); >- #endif >+# ifdef USE_GDBM >+ g = string_cat(g, US"Probably GDBM (native mode)\n"); >+# else >+ g = string_cat(g, US"Probably GDBM (compatibility mode)\n"); >+# endif > #endif >+return g; > } > > > /* This function is called for -bV/--version and for -d to output the optional > features of the current Exim binary. > >-Arguments: a FILE for printing >+Arguments: BOOL, true for stdout else debug channel > Returns: nothing > */ > > static void >-show_whats_supported(FILE * fp) >+show_whats_supported(BOOL is_stdout) > { > rmark reset_point = store_mark(); >-gstring * g; >-DEBUG(D_any) {} else show_db_version(fp); >+gstring * g = NULL; > >-g = string_cat(NULL, US"Support for:"); >+DEBUG(D_any) {} else g = show_db_version(g); >+ >+g = string_cat(g, US"Support for:"); > #ifdef SUPPORT_CRYPTEQ > g = string_cat(g, US" crypteq"); > #endif >@@ -950,6 +1049,9 @@ > #ifdef USE_OPENSSL > g = string_cat(g, US" OpenSSL"); > #endif >+#ifndef DISABLE_TLS_RESUME >+ g = string_cat(g, US" TLS_resume"); >+#endif > #ifdef SUPPORT_TRANSLATE_IP_ADDRESS > g = string_cat(g, US" translate_ip_address"); > #endif >@@ -965,6 +1067,9 @@ > #ifndef DISABLE_DKIM > g = string_cat(g, US" DKIM"); > #endif >+#ifdef SUPPORT_DMARC >+ g = string_cat(g, US" DMARC"); >+#endif > #ifndef DISABLE_DNSSEC > g = string_cat(g, US" DNSSEC"); > #endif >@@ -978,7 +1083,7 @@ > g = string_cat(g, US" OCSP"); > #endif > #ifndef DISABLE_PIPE_CONNECT >- g = string_cat(g, US" PIPE_CONNECT"); >+ g = string_cat(g, US" PIPECONNECT"); > #endif > #ifndef DISABLE_PRDR > g = string_cat(g, US" PRDR"); >@@ -986,14 +1091,17 @@ > #ifdef SUPPORT_PROXY > g = string_cat(g, US" PROXY"); > #endif >+#ifndef DISABLE_QUEUE_RAMP >+ g = string_cat(g, US" Queue_Ramp"); >+#endif > #ifdef SUPPORT_SOCKS > g = string_cat(g, US" SOCKS"); > #endif > #ifdef SUPPORT_SPF > g = string_cat(g, US" SPF"); > #endif >-#ifdef SUPPORT_DMARC >- g = string_cat(g, US" DMARC"); >+#if defined(SUPPORT_SRS) >+ g = string_cat(g, US" SRS"); > #endif > #ifdef TCP_FASTOPEN > tcp_init(); >@@ -1011,21 +1119,12 @@ > #ifdef EXPERIMENTAL_DSN_INFO > g = string_cat(g, US" Experimental_DSN_info"); > #endif >-#ifdef EXPERIMENTAL_LMDB >- g = string_cat(g, US" Experimental_LMDB"); >-#endif >-#ifdef EXPERIMENTAL_QUEUE_RAMP >- g = string_cat(g, US" Experimental_Queue_Ramp"); >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ g = string_cat(g, US" Experimental_ESMTP_Limits"); > #endif > #ifdef EXPERIMENTAL_QUEUEFILE > g = string_cat(g, US" Experimental_QUEUEFILE"); > #endif >-#if defined(EXPERIMENTAL_SRS) || defined(EXPERIMENTAL_SRS_NATIVE) >- g = string_cat(g, US" Experimental_SRS"); >-#endif >-#ifdef EXPERIMENTAL_TLS_RESUME >- g = string_cat(g, US" Experimental_TLS_resume"); >-#endif > g = string_cat(g, US"\n"); > > g = string_cat(g, US"Lookups (built-in):"); >@@ -1053,7 +1152,7 @@ > #if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2 > g = string_cat(g, US" ldap ldapdn ldapm"); > #endif >-#ifdef EXPERIMENTAL_LMDB >+#ifdef LOOKUP_LMDB > g = string_cat(g, US" lmdb"); > #endif > #if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2 >@@ -1095,6 +1194,7 @@ > #ifdef WITH_CONTENT_SCAN > g = malware_show_supported(g); > #endif >+show_string(is_stdout, g); g = NULL; > > if (fixed_never_users[0] > 0) > { >@@ -1106,19 +1206,19 @@ > } > > g = string_fmt_append(g, "Configure owner: %d:%d\n", config_uid, config_gid); >-fputs(CS string_from_gstring(g), fp); > >-fprintf(fp, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t)); >+g = string_fmt_append(g, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t)); > > /* Everything else is details which are only worth reporting when debugging. > Perhaps the tls_version_report should move into this too. */ >-DEBUG(D_any) do { >+DEBUG(D_any) >+ { > > /* clang defines __GNUC__ (at least, for me) so test for it first */ > #if defined(__clang__) >- fprintf(fp, "Compiler: CLang [%s]\n", __clang_version__); >+ g = string_fmt_append(g, "Compiler: CLang [%s]\n", __clang_version__); > #elif defined(__GNUC__) >- fprintf(fp, "Compiler: GCC [%s]\n", >+ g = string_fmt_append(g, "Compiler: GCC [%s]\n", > # ifdef __VERSION__ > __VERSION__ > # else >@@ -1126,32 +1226,38 @@ > # endif > ); > #else >- fprintf(fp, "Compiler: <unknown>\n"); >+ g = string_cat(g, US"Compiler: <unknown>\n"); > #endif > > #if defined(__GLIBC__) && !defined(__UCLIBC__) >- fprintf(fp, "Library version: Glibc: Compile: %d.%d\n", >+ g = string_fmt_append(g, "Library version: Glibc: Compile: %d.%d\n", > __GLIBC__, __GLIBC_MINOR__); > if (__GLIBC_PREREQ(2, 1)) >- fprintf(fp, " Runtime: %s\n", >+ g = string_fmt_append(g, " Runtime: %s\n", > gnu_get_libc_version()); > #endif > >-show_db_version(fp); >+g = show_db_version(g); > > #ifndef DISABLE_TLS >- tls_version_report(fp); >+ g = tls_version_report(g); > #endif > #ifdef SUPPORT_I18N >- utf8_version_report(fp); >+ g = utf8_version_report(g); >+#endif >+#ifdef SUPPORT_DMARC >+ g = dmarc_version_report(g); > #endif > #ifdef SUPPORT_SPF >- spf_lib_version_report(fp); >+ g = spf_lib_version_report(g); > #endif > >- for (auth_info * authi = auths_available; *authi->driver_name != '\0'; ++authi) >- if (authi->version_report) >- (*authi->version_report)(fp); >+show_string(is_stdout, g); >+g = NULL; >+ >+for (auth_info * authi = auths_available; *authi->driver_name != '\0'; ++authi) >+ if (authi->version_report) >+ g = (*authi->version_report)(g); > > /* PCRE_PRERELEASE is either defined and empty or a bare sequence of > characters; unless it's an ancient version of PCRE in which case it >@@ -1161,31 +1267,41 @@ > #endif > #define QUOTE(X) #X > #define EXPAND_AND_QUOTE(X) QUOTE(X) >- fprintf(fp, "Library version: PCRE: Compile: %d.%d%s\n" >- " Runtime: %s\n", >- PCRE_MAJOR, PCRE_MINOR, >- EXPAND_AND_QUOTE(PCRE_PRERELEASE) "", >- pcre_version()); >+ { >+ 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) "", >+ buf); >+ } > #undef QUOTE > #undef EXPAND_AND_QUOTE > >- init_lookup_list(); >- for (int i = 0; i < lookup_list_count; i++) >- if (lookup_list[i]->version_report) >- lookup_list[i]->version_report(fp); >+show_string(is_stdout, g); >+g = NULL; >+ >+init_lookup_list(); >+for (int i = 0; i < lookup_list_count; i++) >+ if (lookup_list[i]->version_report) >+ g = lookup_list[i]->version_report(g); >+show_string(is_stdout, g); >+g = NULL; > > #ifdef WHITELIST_D_MACROS >- fprintf(fp, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS); >+ g = string_fmt_append(g, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS); > #else >- fprintf(fp, "WHITELIST_D_MACROS unset\n"); >+ g = string_cat(g, US"WHITELIST_D_MACROS unset\n"); > #endif > #ifdef TRUSTED_CONFIG_LIST >- fprintf(fp, "TRUSTED_CONFIG_LIST: \"%s\"\n", TRUSTED_CONFIG_LIST); >+ g = string_fmt_append(g, "TRUSTED_CONFIG_LIST: \"%s\"\n", TRUSTED_CONFIG_LIST); > #else >- fprintf(fp, "TRUSTED_CONFIG_LIST unset\n"); >+ g = string_cat(g, US"TRUSTED_CONFIG_LIST unset\n"); > #endif >+ } > >-} while (0); >+show_string(is_stdout, g); > store_reset(reset_point); > } > >@@ -1337,56 +1453,58 @@ > get_stdinput(char *(*fn_readline)(const char *), void(*fn_addhist)(const char *)) > { > gstring * g = NULL; >+BOOL had_input = FALSE; > > if (!fn_readline) { printf("> "); fflush(stdout); } > > for (int i = 0;; i++) > { > uschar buffer[1024]; >- uschar *p, *ss; >+ uschar * p, * ss; > >- #ifdef USE_READLINE >+#ifdef USE_READLINE > char *readline_line = NULL; > if (fn_readline) > { > if (!(readline_line = fn_readline((i > 0)? "":"> "))) break; >- if (*readline_line != 0 && fn_addhist) fn_addhist(readline_line); >+ if (*readline_line && fn_addhist) fn_addhist(readline_line); > p = US readline_line; > } > else >- #endif >+#endif > > /* readline() not in use */ > > { >- if (Ufgets(buffer, sizeof(buffer), stdin) == NULL) break; >+ if (Ufgets(buffer, sizeof(buffer), stdin) == NULL) break; /*EOF*/ > p = buffer; > } > > /* Handle the line */ > >- ss = p + (int)Ustrlen(p); >- while (ss > p && isspace(ss[-1])) ss--; >+ had_input = TRUE; >+ ss = p + Ustrlen(p); >+ while (ss > p && isspace(ss[-1])) ss--; /* strip trailing newline (and spaces) */ > > if (i > 0) >- while (p < ss && isspace(*p)) p++; /* leading space after cont */ >+ while (p < ss && isspace(*p)) p++; /* strip leading space after cont */ > > g = string_catn(g, p, ss - p); > >- #ifdef USE_READLINE >+#ifdef USE_READLINE > if (fn_readline) free(readline_line); >- #endif >+#endif > > /* g can only be NULL if ss==p */ >- if (ss == p || g->s[g->ptr-1] != '\\') >+ if (ss == p || g->s[g->ptr-1] != '\\') /* not continuation; done */ > break; > >- --g->ptr; >- (void) string_from_gstring(g); >+ --g->ptr; /* drop the \ */ > } > >-if (!g) printf("\n"); >-return string_from_gstring(g); >+if (had_input) return g ? string_from_gstring(g) : US""; >+printf("\n"); >+return NULL; > } > > >@@ -1518,14 +1636,8 @@ > continue; > if ((len = m->replen) == 0) > continue; >- n = pcre_exec(regex_whitelisted_macro, NULL, CS m->replacement, len, >- 0, PCRE_EOPT, NULL, 0); >- if (n < 0) >- { >- if (n != PCRE_ERROR_NOMATCH) >- debug_printf("macros_trusted checking %s returned %d\n", m->name, n); >+ if (!regex_match(regex_whitelisted_macro, m->replacement, len, NULL)) > return FALSE; >- } > } > DEBUG(D_any) debug_printf("macros_trusted overridden to true by whitelisting\n"); > return TRUE; >@@ -1601,7 +1713,7 @@ > int list_queue_option = 0; > int msg_action = 0; > int msg_action_arg = -1; >-int namelen = (argv[0] == NULL)? 0 : Ustrlen(argv[0]); >+int namelen = argv[0] ? Ustrlen(argv[0]) : 0; > int queue_only_reason = 0; > #ifdef EXIM_PERL > int perl_start_option = 0; >@@ -1626,7 +1738,6 @@ > BOOL list_options = FALSE; > BOOL list_config = FALSE; > BOOL local_queue_only; >-BOOL more = TRUE; > BOOL one_msg_action = FALSE; > BOOL opt_D_used = FALSE; > BOOL queue_only_set = FALSE; >@@ -1638,6 +1749,7 @@ > BOOL usage_wanted = FALSE; > BOOL verify_address_mode = FALSE; > BOOL verify_as_sender = FALSE; >+BOOL rcpt_verify_quota = FALSE; > BOOL version_printed = FALSE; > uschar *alias_arg = NULL; > uschar *called_as = US""; >@@ -1679,6 +1791,9 @@ > (void)gettimeofday(×tamp_startup, NULL); > #endif > >+store_init(); /* Initialise the memory allocation susbsystem */ >+pcre_init(); /* Set up memory handling for pcre */ >+ > /* If the Exim user and/or group and/or the configuration file owner/group were > defined by ref:name at build time, we must now find the actual uid/gid values. > This is a feature to make the lives of binary distributors easier. */ >@@ -1742,6 +1857,7 @@ > debug_store = TRUE; > > /* Protect against abusive argv[0] */ >+if (!argv[0] || !argc) exim_fail("exim: executable name required\n"); > exim_str_fail_toolong(argv[0], PATH_MAX, "argv[0]"); > > /* The C standard says that the equivalent of setlocale(LC_ALL, "C") is obeyed >@@ -1778,15 +1894,6 @@ > > if (fstat(fileno(stderr), &statbuf) >= 0) log_stderr = stderr; > >-/* Arrange for the PCRE regex library to use our store functions. Note that >-the normal calls are actually macros that add additional arguments for >-debugging purposes so we have to assign specially constructed functions here. >-The default is to use store in the stacking pool, but this is overridden in the >-regex_must_compile() function. */ >- >-pcre_malloc = function_store_get; >-pcre_free = function_dummy_free; >- > /* Ensure there is a big buffer for temporary use in several places. It is put > in malloc store so that it can be freed for enlargement if necessary. */ > >@@ -1795,9 +1902,17 @@ > /* Set up the handler for the data request signal, and set the initial > descriptive text. */ > >-process_info = store_get(PROCESS_INFO_SIZE, TRUE); /* tainted */ >+process_info = store_get(PROCESS_INFO_SIZE, GET_TAINTED); > set_process_info("initializing"); >-os_restarting_signal(SIGUSR1, usr1_handler); >+os_restarting_signal(SIGUSR1, usr1_handler); /* exiwhat */ >+#ifdef SA_SIGINFO >+ { >+ struct sigaction act = { .sa_sigaction = segv_handler, .sa_flags = SA_RESETHAND | SA_SIGINFO }; >+ sigaction(SIGSEGV, &act, NULL); >+ } >+#else >+signal(SIGSEGV, segv_handler); /* log faults */ >+#endif > > /* If running in a dockerized environment, the TERM signal is only > delegated to the PID 1 if we request it by setting an signal handler */ >@@ -2158,7 +2273,9 @@ > if (!*argrest || Ustrcmp(argrest, "c") == 0) > { > if (++i >= argc) { badarg = TRUE; break; } >- sender_host_address = string_copy_taint(exim_str_fail_toolong(argv[i], EXIM_IPADDR_MAX, "-bh"), TRUE); >+ sender_host_address = string_copy_taint( >+ exim_str_fail_toolong(argv[i], EXIM_IPADDR_MAX, "-bh"), >+ GET_TAINTED); > host_checking = checking = f.log_testing_mode = TRUE; > f.host_checking_callout = *argrest == 'c'; > message_logs = FALSE; >@@ -2268,7 +2385,7 @@ > case 'P': > > /* -bP config: we need to setup here, because later, >- * when list_options is checked, the config is read already */ >+ when list_options is checked, the config is read already */ > if (*argrest) > badarg = TRUE; > else if (argv[i+1] && Ustrcmp(argv[i+1], "config") == 0) >@@ -2349,7 +2466,7 @@ > version_cnumber, version_date); > printf("%s\n", CS version_copyright); > version_printed = TRUE; >- show_whats_supported(stdout); >+ show_whats_supported(TRUE); > f.log_testing_mode = TRUE; > } > else badarg = TRUE; >@@ -2386,6 +2503,7 @@ > int len = Ustrlen(ALT_CONFIG_PREFIX); > const uschar *list = argrest; > uschar *filename; >+ /* The argv is untainted, so big_buffer (also untainted) is ok to use */ > while((filename = string_nextinlist(&list, &sep, big_buffer, > big_buffer_size))) > if ( ( Ustrlen(filename) < len >@@ -2543,21 +2661,36 @@ > #endif > break; > >- /* -d: Set debug level (see also -v below) or set the drop_cr option. >- The latter is now a no-op, retained for compatibility only. If -dd is used, >- debugging subprocesses of the daemon is disabled. */ >- > case 'd': >+ >+ /* -dropcr: Set this option. Now a no-op, retained for compatibility only. */ >+ > if (Ustrcmp(argrest, "ropcr") == 0) > { > /* drop_cr = TRUE; */ > } > >- /* Use an intermediate variable so that we don't set debugging while >- decoding the debugging bits. */ >+ /* -dp: Set up a debug pretrigger buffer with given size. */ >+ >+ else if (Ustrcmp(argrest, "p") == 0) >+ if (++i >= argc) >+ badarg = TRUE; >+ else >+ debug_pretrigger_setup(argv[i]); >+ >+ /* -dt: Set a debug trigger selector */ >+ >+ else if (Ustrncmp(argrest, "t=", 2) == 0) >+ dtrigger_selector = (unsigned int) Ustrtol(argrest + 2, NULL, 0); >+ >+ /* -d: Set debug level (see also -v below). >+ If -dd is used, debugging subprocesses of the daemon is disabled. */ > > else > { >+ /* Use an intermediate variable so that we don't set debugging while >+ decoding the debugging bits. */ >+ > unsigned int selector = D_default; > debug_selector = 0; > debug_file = NULL; >@@ -2615,7 +2748,9 @@ > case 'F': > if (!*argrest) > if (++i < argc) argrest = argv[i]; else { badarg = TRUE; break; } >- originator_name = string_copy_taint(exim_str_fail_toolong(argrest, EXIM_HUMANNAME_MAX, "-F"), TRUE); >+ originator_name = string_copy_taint( >+ exim_str_fail_toolong(argrest, EXIM_HUMANNAME_MAX, "-F"), >+ GET_TAINTED); > f.sender_name_forced = TRUE; > break; > >@@ -2643,7 +2778,7 @@ > if (i+1 < argc) argrest = argv[++i]; else { badarg = TRUE; break; } > (void) exim_str_fail_toolong(argrest, EXIM_DISPLAYMAIL_MAX, "-f"); > if (!*argrest) >- *(sender_address = store_get(1, FALSE)) = '\0'; /* Ensure writeable memory */ >+ *(sender_address = store_get(1, GET_UNTAINTED)) = '\0'; /* Ensure writeable memory */ > else > { > uschar * temp = argrest + Ustrlen(argrest) - 1; >@@ -2658,7 +2793,7 @@ > &dummy_start, &dummy_end, &sender_address_domain, TRUE))) > exim_fail("exim: bad -f address \"%s\": %s\n", argrest, errmess); > >- sender_address = string_copy_taint(sender_address, TRUE); >+ sender_address = string_copy_taint(sender_address, GET_TAINTED); > #ifdef SUPPORT_I18N > message_smtputf8 = string_is_utf8(sender_address); > allow_utf8_domains = FALSE; >@@ -2708,7 +2843,7 @@ > exim_fail("exim: the -L syslog name is too long: \"%s\"\n", argrest); > if (sz < 1) > exim_fail("exim: the -L syslog name is too short\n"); >- cmdline_syslog_name = string_copy_taint(argrest, TRUE); >+ cmdline_syslog_name = string_copy_taint(argrest, GET_TAINTED); > break; > > case 'M': >@@ -2738,9 +2873,15 @@ > if (msg_action_arg >= 0) > exim_fail("exim: incompatible arguments\n"); > >- continue_transport = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-C internal transport"), TRUE); >- continue_hostname = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_HOSTNAME_MAX, "-C internal hostname"), TRUE); >- continue_host_address = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_IPADDR_MAX, "-C internal hostaddr"), TRUE); >+ continue_transport = string_copy_taint( >+ exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-C internal transport"), >+ GET_TAINTED); >+ continue_hostname = string_copy_taint( >+ exim_str_fail_toolong(argv[++i], EXIM_HOSTNAME_MAX, "-C internal hostname"), >+ GET_TAINTED); >+ continue_host_address = string_copy_taint( >+ exim_str_fail_toolong(argv[++i], EXIM_IPADDR_MAX, "-C internal hostaddr"), >+ GET_TAINTED); > continue_sequence = Uatoi(argv[++i]); > msg_action = MSG_DELIVER; > msg_action_arg = ++i; >@@ -2785,13 +2926,19 @@ > /* -MCd: for debug, set a process-purpose string */ > > case 'd': if (++i < argc) >- process_purpose = string_copy_taint(exim_str_fail_toolong(argv[i], EXIM_DRIVERNAME_MAX, "-MCd"), TRUE); >+ process_purpose = string_copy_taint( >+ exim_str_fail_toolong(argv[i], EXIM_DRIVERNAME_MAX, "-MCd"), >+ GET_TAINTED); > else badarg = TRUE; > break; > >- /* -MCG: set the queue name, to a non-default value */ >- >- case 'G': if (++i < argc) queue_name = string_copy_taint(exim_str_fail_toolong(argv[i], EXIM_DRIVERNAME_MAX, "-MCG"), TRUE); >+ /* -MCG: set the queue name, to a non-default value. Arguably, anything >+ from the commandline should be tainted - but we will need an untainted >+ value for the spoolfile when doing a -odi delivery process. */ >+ >+ case 'G': if (++i < argc) queue_name = string_copy_taint( >+ exim_str_fail_toolong(argv[i], EXIM_DRIVERNAME_MAX, "-MCG"), >+ GET_UNTAINTED); > else badarg = TRUE; > break; > >@@ -2799,11 +2946,42 @@ > > case 'K': smtp_peer_options |= OPTION_CHUNKING; break; > >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ /* -MCL: peer used LIMITS RCPTMAX and/or RCPTDOMAINMAX */ >+ case 'L': if (++i < argc) continue_limit_mail = Uatoi(argv[i]); >+ else badarg = TRUE; >+ if (++i < argc) continue_limit_rcpt = Uatoi(argv[i]); >+ else badarg = TRUE; >+ if (++i < argc) continue_limit_rcptdom = Uatoi(argv[i]); >+ else badarg = TRUE; >+ break; >+#endif >+ > /* -MCP: set the smtp_use_pipelining flag; this is useful only when > it preceded -MC (see above) */ > > case 'P': smtp_peer_options |= OPTION_PIPE; break; > >+#ifdef SUPPORT_SOCKS >+ /* -MCp: Socks proxy in use; nearside IP, port, external IP, port */ >+ case 'p': proxy_session = TRUE; >+ if (++i < argc) >+ { >+ proxy_local_address = string_copy_taint(argv[i], GET_TAINTED); >+ if (++i < argc) >+ { >+ proxy_local_port = Uatoi(argv[i]); >+ if (++i < argc) >+ { >+ proxy_external_address = string_copy_taint(argv[i], GET_TAINTED); >+ if (++i < argc) >+ { >+ proxy_external_port = Uatoi(argv[i]); >+ break; >+ } } } } >+ badarg = TRUE; >+ break; >+#endif > /* -MCQ: pass on the pid of the queue-running process that started > this chain of deliveries and the fd of its synchronizing pipe; this > is useful only when it precedes -MC (see above) */ >@@ -2814,6 +2992,13 @@ > else badarg = TRUE; > break; > >+ /* -MCq: do a quota check on the given recipient for the given size >+ of message. Separate from -MC. */ >+ case 'q': rcpt_verify_quota = TRUE; >+ if (++i < argc) message_size = Uatoi(argv[i]); >+ else badarg = TRUE; >+ break; >+ > /* -MCS: set the smtp_use_size flag; this is useful only when it > precedes -MC (see above) */ > >@@ -2826,7 +3011,9 @@ > case 'r': > case 's': if (++i < argc) > { >- continue_proxy_sni = string_copy_taint(exim_str_fail_toolong(argv[i], EXIM_HOSTNAME_MAX, "-MCr/-MCs"), TRUE); >+ continue_proxy_sni = string_copy_taint( >+ exim_str_fail_toolong(argv[i], EXIM_HOSTNAME_MAX, "-MCr/-MCs"), >+ GET_TAINTED); > if (argrest[1] == 'r') continue_proxy_dane = TRUE; > } > else badarg = TRUE; >@@ -2838,13 +3025,17 @@ > and the TLS cipher. */ > > case 't': if (++i < argc) >- sending_ip_address = string_copy_taint(exim_str_fail_toolong(argv[i], EXIM_IPADDR_MAX, "-MCt IP"), TRUE); >+ sending_ip_address = string_copy_taint( >+ exim_str_fail_toolong(argv[i], EXIM_IPADDR_MAX, "-MCt IP"), >+ GET_TAINTED); > else badarg = TRUE; > if (++i < argc) > sending_port = (int)(Uatol(argv[i])); > else badarg = TRUE; > if (++i < argc) >- continue_proxy_cipher = string_copy_taint(exim_str_fail_toolong(argv[i], EXIM_CIPHERNAME_MAX, "-MCt cipher"), TRUE); >+ continue_proxy_cipher = string_copy_taint( >+ exim_str_fail_toolong(argv[i], EXIM_CIPHERNAME_MAX, "-MCt cipher"), >+ GET_TAINTED); > else badarg = TRUE; > /*FALLTHROUGH*/ > >@@ -2907,12 +3098,11 @@ > else if (Ustrcmp(argrest, "G") == 0) > { > msg_action = MSG_SETQUEUE; >- queue_name_dest = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-MG"), TRUE); >- } >- else if (Ustrcmp(argrest, "mad") == 0) >- { >- msg_action = MSG_MARK_ALL_DELIVERED; >+ queue_name_dest = string_copy_taint( >+ exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-MG"), >+ GET_TAINTED); > } >+ else if (Ustrcmp(argrest, "mad") == 0) msg_action = MSG_MARK_ALL_DELIVERED; > else if (Ustrcmp(argrest, "md") == 0) > { > msg_action = MSG_MARK_DELIVERED; >@@ -3120,27 +3310,37 @@ > /* -oMa: Set sender host address */ > > if (Ustrcmp(argrest, "a") == 0) >- sender_host_address = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_IPADDR_MAX, "-oMa"), TRUE); >+ sender_host_address = string_copy_taint( >+ exim_str_fail_toolong(argv[++i], EXIM_IPADDR_MAX, "-oMa"), >+ GET_TAINTED); > > /* -oMaa: Set authenticator name */ > > else if (Ustrcmp(argrest, "aa") == 0) >- sender_host_authenticated = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-oMaa"), TRUE); >+ sender_host_authenticated = string_copy_taint( >+ exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-oMaa"), >+ GET_TAINTED); > > /* -oMas: setting authenticated sender */ > > else if (Ustrcmp(argrest, "as") == 0) >- authenticated_sender = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_EMAILADDR_MAX, "-oMas"), TRUE); >+ authenticated_sender = string_copy_taint( >+ exim_str_fail_toolong(argv[++i], EXIM_EMAILADDR_MAX, "-oMas"), >+ GET_TAINTED); > > /* -oMai: setting authenticated id */ > > else if (Ustrcmp(argrest, "ai") == 0) >- authenticated_id = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_EMAILADDR_MAX, "-oMas"), TRUE); >+ authenticated_id = string_copy_taint( >+ exim_str_fail_toolong(argv[++i], EXIM_EMAILADDR_MAX, "-oMas"), >+ GET_TAINTED); > > /* -oMi: Set incoming interface address */ > > else if (Ustrcmp(argrest, "i") == 0) >- interface_address = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_IPADDR_MAX, "-oMi"), TRUE); >+ interface_address = string_copy_taint( >+ exim_str_fail_toolong(argv[++i], EXIM_IPADDR_MAX, "-oMi"), >+ GET_TAINTED); > > /* -oMm: Message reference */ > >@@ -3160,19 +3360,25 @@ > if (received_protocol) > exim_fail("received_protocol is set already\n"); > else >- received_protocol = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-oMr"), TRUE); >+ received_protocol = string_copy_taint( >+ exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-oMr"), >+ GET_TAINTED); > > /* -oMs: Set sender host name */ > > else if (Ustrcmp(argrest, "s") == 0) >- sender_host_name = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_HOSTNAME_MAX, "-oMs"), TRUE); >+ sender_host_name = string_copy_taint( >+ exim_str_fail_toolong(argv[++i], EXIM_HOSTNAME_MAX, "-oMs"), >+ GET_TAINTED); > > /* -oMt: Set sender ident */ > > else if (Ustrcmp(argrest, "t") == 0) > { > sender_ident_set = TRUE; >- sender_ident = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_IDENTUSER_MAX, "-oMt"), TRUE); >+ sender_ident = string_copy_taint( >+ exim_str_fail_toolong(argv[++i], EXIM_IDENTUSER_MAX, "-oMt"), >+ GET_TAINTED); > } > > /* Else a bad argument */ >@@ -3230,7 +3436,16 @@ > > case 'X': > if (*argrest) badarg = TRUE; >- else override_local_interfaces = string_copy_taint(exim_str_fail_toolong(argv[++i], 1024, "-oX"), TRUE); >+ else override_local_interfaces = string_copy_taint( >+ exim_str_fail_toolong(argv[++i], 1024, "-oX"), >+ GET_TAINTED); >+ break; >+ >+ /* -oY: Override creation of daemon notifier socket */ >+ >+ case 'Y': >+ if (*argrest) badarg = TRUE; >+ else notifier_socket = NULL; > break; > > /* Unknown -o argument */ >@@ -3271,12 +3486,14 @@ > exim_fail("received_protocol is set already\n"); > > if (!hn) >- received_protocol = string_copy_taint(exim_str_fail_toolong(argrest, EXIM_DRIVERNAME_MAX, "-p<protocol>"), TRUE); >+ received_protocol = string_copy_taint( >+ exim_str_fail_toolong(argrest, EXIM_DRIVERNAME_MAX, "-p<protocol>"), >+ GET_TAINTED); > else > { > (void) exim_str_fail_toolong(argrest, (EXIM_DRIVERNAME_MAX+1+EXIM_HOSTNAME_MAX), "-p<protocol>:<host>"); >- received_protocol = string_copyn_taint(argrest, hn - argrest, TRUE); >- sender_host_name = string_copy_taint(hn + 1, TRUE); >+ received_protocol = string_copyn_taint(argrest, hn - argrest, GET_TAINTED); >+ sender_host_name = string_copy_taint(hn + 1, GET_TAINTED); > } > } > break; >@@ -3345,9 +3562,9 @@ > { > queue_interval = 0; > if (i+1 < argc && mac_ismsgid(argv[i+1])) >- start_queue_run_id = string_copy_taint(argv[++i], TRUE); >+ start_queue_run_id = string_copy_taint(argv[++i], GET_TAINTED); > if (i+1 < argc && mac_ismsgid(argv[i+1])) >- stop_queue_run_id = string_copy_taint(argv[++i], TRUE); >+ stop_queue_run_id = string_copy_taint(argv[++i], GET_TAINTED); > } > > /* -q[f][f][l][G<name>/]<n>: Run the queue at regular intervals, optionally >@@ -3395,7 +3612,9 @@ > tainted_selectstr = argv[++i]; > else > exim_fail("exim: string expected after -R\n"); >- deliver_selectstring = string_copy_taint(exim_str_fail_toolong(tainted_selectstr, EXIM_EMAILADDR_MAX, "-R"), TRUE); >+ deliver_selectstring = string_copy_taint( >+ exim_str_fail_toolong(tainted_selectstr, EXIM_EMAILADDR_MAX, "-R"), >+ GET_TAINTED); > } > break; > >@@ -3438,7 +3657,9 @@ > tainted_selectstr = argv[++i]; > else > exim_fail("exim: string expected after -S\n"); >- deliver_selectstring_sender = string_copy_taint(exim_str_fail_toolong(tainted_selectstr, EXIM_EMAILADDR_MAX, "-S"), TRUE); >+ deliver_selectstring_sender = string_copy_taint( >+ exim_str_fail_toolong(tainted_selectstr, EXIM_EMAILADDR_MAX, "-S"), >+ GET_TAINTED); > } > break; > >@@ -3449,7 +3670,7 @@ > > case 'T': > if (f.running_in_test_harness && Ustrcmp(argrest, "qt") == 0) >- fudged_queue_times = string_copy_taint(argv[++i], TRUE); >+ fudged_queue_times = string_copy_taint(argv[++i], GET_TAINTED); > else badarg = TRUE; > break; > >@@ -3526,7 +3747,9 @@ > case 'z': > if (!*argrest) > if (++i < argc) >- log_oneline = string_copy_taint(exim_str_fail_toolong(argv[i], 2048, "-z logtext"), TRUE); >+ log_oneline = string_copy_taint( >+ exim_str_fail_toolong(argv[i], 2048, "-z logtext"), >+ GET_TAINTED); > else > exim_fail("exim: file name expected after %s\n", argv[i-1]); > break; >@@ -3561,54 +3784,40 @@ > if (usage_wanted) exim_usage(called_as); > > /* Arguments have been processed. Check for incompatibilities. */ >-if (( >- (smtp_input || extract_recipients || recipients_arg < argc) && >- (f.daemon_listen || queue_interval >= 0 || bi_option || >- test_retry_arg >= 0 || test_rewrite_arg >= 0 || >- filter_test != FTEST_NONE || (msg_action_arg > 0 && !one_msg_action)) >- ) || >- ( >- msg_action_arg > 0 && >- (f.daemon_listen || queue_interval > 0 || list_options || >- (checking && msg_action != MSG_LOAD) || >- bi_option || test_retry_arg >= 0 || test_rewrite_arg >= 0) >- ) || >- ( >- (f.daemon_listen || queue_interval > 0) && >- (sender_address != NULL || list_options || list_queue || checking || >- bi_option) >- ) || >- ( >- f.daemon_listen && queue_interval == 0 >- ) || >- ( >- f.inetd_wait_mode && queue_interval >= 0 >- ) || >- ( >- list_options && >- (checking || smtp_input || extract_recipients || >- filter_test != FTEST_NONE || bi_option) >- ) || >- ( >- verify_address_mode && >- (f.address_test_mode || smtp_input || extract_recipients || >- filter_test != FTEST_NONE || bi_option) >- ) || >- ( >- f.address_test_mode && (smtp_input || extract_recipients || >- filter_test != FTEST_NONE || bi_option) >- ) || >- ( >- smtp_input && (sender_address != NULL || filter_test != FTEST_NONE || >- extract_recipients) >- ) || >- ( >- deliver_selectstring != NULL && queue_interval < 0 >- ) || >- ( >- msg_action == MSG_LOAD && >- (!expansion_test || expansion_test_message != NULL) >- ) >+if ( ( (smtp_input || extract_recipients || recipients_arg < argc) >+ && ( f.daemon_listen || queue_interval >= 0 || bi_option >+ || test_retry_arg >= 0 || test_rewrite_arg >= 0 >+ || filter_test != FTEST_NONE >+ || msg_action_arg > 0 && !one_msg_action >+ ) ) >+ || ( msg_action_arg > 0 >+ && ( f.daemon_listen || queue_interval > 0 || list_options >+ || checking && msg_action != MSG_LOAD >+ || bi_option || test_retry_arg >= 0 || test_rewrite_arg >= 0 >+ ) ) >+ || ( (f.daemon_listen || queue_interval > 0) >+ && ( sender_address || list_options || list_queue || checking >+ || bi_option >+ ) ) >+ || f.daemon_listen && queue_interval == 0 >+ || f.inetd_wait_mode && queue_interval >= 0 >+ || ( list_options >+ && ( checking || smtp_input || extract_recipients >+ || filter_test != FTEST_NONE || bi_option >+ ) ) >+ || ( verify_address_mode >+ && ( f.address_test_mode || smtp_input || extract_recipients >+ || filter_test != FTEST_NONE || bi_option >+ ) ) >+ || ( f.address_test_mode >+ && ( smtp_input || extract_recipients || filter_test != FTEST_NONE >+ || bi_option >+ ) ) >+ || ( smtp_input >+ && (sender_address || filter_test != FTEST_NONE || extract_recipients) >+ ) >+ || deliver_selectstring && queue_interval < 0 >+ || msg_action == MSG_LOAD && (!expansion_test || expansion_test_message) > ) > exim_fail("exim: incompatible command-line options or arguments\n"); > >@@ -3628,7 +3837,7 @@ > version_string, (long int)real_uid, (long int)real_gid, (int)getpid(), > debug_selector); > if (!version_printed) >- show_whats_supported(stderr); >+ show_whats_supported(FALSE); > } > } > >@@ -3646,7 +3855,7 @@ > { > struct rlimit rlp; > >- #ifdef RLIMIT_NOFILE >+#ifdef RLIMIT_NOFILE > if (getrlimit(RLIMIT_NOFILE, &rlp) < 0) > { > log_write(0, LOG_MAIN|LOG_PANIC, "getrlimit(RLIMIT_NOFILE) failed: %s", >@@ -3669,9 +3878,9 @@ > strerror(errno)); > } > } >- #endif >+#endif > >- #ifdef RLIMIT_NPROC >+#ifdef RLIMIT_NPROC > if (getrlimit(RLIMIT_NPROC, &rlp) < 0) > { > log_write(0, LOG_MAIN|LOG_PANIC, "getrlimit(RLIMIT_NPROC) failed: %s", >@@ -3679,20 +3888,20 @@ > rlp.rlim_cur = rlp.rlim_max = 0; > } > >- #ifdef RLIM_INFINITY >+# ifdef RLIM_INFINITY > if (rlp.rlim_cur != RLIM_INFINITY && rlp.rlim_cur < 1000) > { > rlp.rlim_cur = rlp.rlim_max = RLIM_INFINITY; >- #else >+# else > if (rlp.rlim_cur < 1000) > { > rlp.rlim_cur = rlp.rlim_max = 1000; >- #endif >+# endif > if (setrlimit(RLIMIT_NPROC, &rlp) < 0) > log_write(0, LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_NPROC) failed: %s", > strerror(errno)); > } >- #endif >+#endif > } > > /* Exim is normally entered as root (but some special configurations are >@@ -3815,6 +4024,7 @@ > This needs to happen before we read the main configuration. */ > init_lookup_list(); > >+/*XXX this excrescence could move to the testsuite standard config setup file */ > #ifdef SUPPORT_I18N > if (f.running_in_test_harness) smtputf8_advertise_hosts = NULL; > #endif >@@ -3853,19 +4063,21 @@ > defined) */ > > { >+ int old_pool = store_pool; > #ifdef MEASURE_TIMING >- struct timeval t0, diff; >+ struct timeval t0; > (void)gettimeofday(&t0, NULL); > #endif > >+ store_pool = POOL_CONFIG; > readconf_main(checking || list_options); >+ store_pool = old_pool; > > #ifdef MEASURE_TIMING > report_time_since(&t0, US"readconf_main (delta)"); > #endif > } > >- > /* Now in directory "/" */ > > if (cleanup_environment() == FALSE) >@@ -4168,10 +4380,8 @@ > > if (Uchdir(spool_directory) != 0) > { >- int dummy; >- (void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, FALSE); >- dummy = /* quieten compiler */ Uchdir(spool_directory); >- dummy = dummy; /* yet more compiler quietening, sigh */ >+ (void) directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, FALSE); >+ (void) Uchdir(spool_directory); > } > > /* Handle calls with the -bi option. This is a sendmail option to rebuild *the* >@@ -4182,23 +4392,23 @@ > > if (bi_option) > { >- (void)fclose(config_file); >- if (bi_command) >+ (void) fclose(config_file); >+ if (bi_command && *bi_command) > { > int i = 0; > uschar *argv[3]; >- argv[i++] = bi_command; >+ argv[i++] = bi_command; /* nonexpanded option so assume untainted */ > if (alias_arg) argv[i++] = alias_arg; > argv[i++] = NULL; > > setgroups(group_count, group_list); > exim_setugid(real_uid, real_gid, FALSE, US"running bi_command"); > >- DEBUG(D_exec) debug_printf("exec %.256s %.256s\n", argv[0], >- argv[1] ? argv[1] : US""); >+ DEBUG(D_exec) debug_printf("exec '%.256s' %s%.256s%s\n", argv[0], >+ argv[1] ? "'" : "", argv[1] ? argv[1] : US"", argv[1] ? "'" : ""); > > execv(CS argv[0], (char *const *)argv); >- exim_fail("exim: exec failed: %s\n", strerror(errno)); >+ exim_fail("exim: exec '%s' failed: %s\n", argv[0], strerror(errno)); > } > else > { >@@ -4232,7 +4442,7 @@ > || queue_name_dest && prod_requires_admin > || debugset && !f.running_in_test_harness > ) >- exim_fail("exim:%s permission denied\n", debugset? " debugging" : ""); >+ exim_fail("exim:%s permission denied\n", debugset ? " debugging" : ""); > } > > /* If the real user is not root or the exim uid, the argument for passing >@@ -4241,11 +4451,13 @@ > one that supplied an input message, or we are using a patched exim for > regression testing. */ > >-if (real_uid != root_uid && real_uid != exim_uid && >- (continue_hostname != NULL || >- (f.dont_deliver && >- (queue_interval >= 0 || f.daemon_listen || msg_action_arg > 0) >- )) && !f.running_in_test_harness) >+if ( real_uid != root_uid && real_uid != exim_uid >+ && ( continue_hostname >+ || ( f.dont_deliver >+ && (queue_interval >= 0 || f.daemon_listen || msg_action_arg > 0) >+ ) ) >+ && !f.running_in_test_harness >+ ) > exim_fail("exim: Permission denied\n"); > > /* If the caller is not trusted, certain arguments are ignored when running for >@@ -4267,9 +4479,9 @@ > > else > { >- if (sender_host_address != NULL) >+ if (sender_host_address) > sender_host_port = check_port(sender_host_address); >- if (interface_address != NULL) >+ if (interface_address) > interface_port = check_port(interface_address); > } > >@@ -4356,18 +4568,20 @@ > situation (controlled by the TRUE below), in order to be as close as possible > to the state Exim usually runs in. */ > >-if (!unprivileged && /* originally had root AND */ >- !removed_privilege && /* still got root AND */ >- !f.daemon_listen && /* not starting the daemon */ >- queue_interval <= 0 && /* (either kind of daemon) */ >- ( /* AND EITHER */ >- deliver_drop_privilege || /* requested unprivileged */ >- ( /* OR */ >- queue_interval < 0 && /* not running the queue */ >- (msg_action_arg < 0 || /* and */ >- msg_action != MSG_DELIVER) && /* not delivering and */ >- (!checking || !f.address_test_mode) /* not address checking */ >- ) ) ) >+if ( !unprivileged /* originally had root AND */ >+ && !removed_privilege /* still got root AND */ >+ && !f.daemon_listen /* not starting the daemon */ >+ && queue_interval <= 0 /* (either kind of daemon) */ >+ && ( /* AND EITHER */ >+ deliver_drop_privilege /* requested unprivileged */ >+ || ( /* OR */ >+ queue_interval < 0 /* not running the queue */ >+ && ( msg_action_arg < 0 /* and */ >+ || msg_action != MSG_DELIVER /* not delivering */ >+ ) /* and */ >+ && (!checking || !f.address_test_mode) /* not address checking */ >+ && !rcpt_verify_quota /* and not quota checking */ >+ ) ) ) > exim_setugid(exim_uid, exim_gid, TRUE, US"privilege not needed"); > > /* When we are retaining a privileged uid, we still change to the exim gid. */ >@@ -4398,8 +4612,7 @@ > #ifdef WITH_CONTENT_SCAN > int result; > set_process_info("scanning file for malware"); >- result = malware_in_file(malware_test_file); >- if (result == FAIL) >+ if ((result = malware_in_file(malware_test_file)) == FAIL) > { > printf("No malware found.\n"); > exit(EXIT_SUCCESS); >@@ -4451,7 +4664,13 @@ > event_action gets expanded */ > > if (msg_action == MSG_REMOVE) >+ { >+ int old_pool = store_pool; >+ store_pool = POOL_CONFIG; > readconf_rest(); >+ store_pool = old_pool; >+ store_writeprotect(POOL_CONFIG); >+ } > > if (!one_msg_action) > { >@@ -4476,18 +4695,39 @@ > needed in transports so we lost the optimisation. */ > > { >+ int old_pool = store_pool; > #ifdef MEASURE_TIMING >- struct timeval t0, diff; >+ struct timeval t0; > (void)gettimeofday(&t0, NULL); > #endif > >+ store_pool = POOL_CONFIG; > readconf_rest(); >+ store_pool = old_pool; >+ >+ /* -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) >+ store_writeprotect(POOL_CONFIG); > > #ifdef MEASURE_TIMING > report_time_since(&t0, US"readconf_rest (delta)"); > #endif > } > >+/* Handle a request to check quota */ >+if (rcpt_verify_quota) >+ if (real_uid != root_uid && real_uid != exim_uid) >+ exim_fail("exim: Permission denied\n"); >+ else if (recipients_arg >= argc) >+ exim_fail("exim: missing recipient for quota check\n"); >+ else >+ { >+ verify_quota(argv[recipients_arg]); >+ exim_exit(EXIT_SUCCESS); >+ } >+ > /* Handle the -brt option. This is for checking out retry configurations. > The next three arguments are a domain name or a complete address, and > optionally two error numbers. All it does is to call the function that >@@ -4762,7 +5002,7 @@ > > if (gecos_pattern && gecos_name) > { >- const pcre *re; >+ const pcre2_code *re; > re = regex_must_compile(gecos_pattern, FALSE, TRUE); /* Use malloc */ > > if (regex_match_and_setup(re, name, 0, -1)) >@@ -4803,16 +5043,16 @@ > configuration specifies something to use. When running in the test harness, > any setting of unknown_login overrides the actual name. */ > >-if (originator_login == NULL || f.running_in_test_harness) >+if (!originator_login || f.running_in_test_harness) > { >- if (unknown_login != NULL) >+ if (unknown_login) > { > originator_login = expand_string(unknown_login); >- if (originator_name == NULL && unknown_username != NULL) >+ if (!originator_name && unknown_username) > originator_name = expand_string(unknown_username); >- if (originator_name == NULL) originator_name = US""; >+ if (!originator_name) originator_name = US""; > } >- if (originator_login == NULL) >+ if (!originator_login) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed to get user name for uid %d", > (int)real_uid); > } >@@ -4820,7 +5060,7 @@ > /* Ensure that the user name is in a suitable form for use as a "phrase" in an > RFC822 address.*/ > >-originator_name = parse_fix_phrase(originator_name, Ustrlen(originator_name)); >+originator_name = US parse_fix_phrase(originator_name, Ustrlen(originator_name)); > > /* If a message is created by this call of Exim, the uid/gid of its originator > are those of the caller. These values are overridden if an existing message is >@@ -4851,7 +5091,7 @@ > routines in it, so call even if tls_require_ciphers is unset */ > { > # ifdef MEASURE_TIMING >- struct timeval t0, diff; >+ struct timeval t0; > (void)gettimeofday(&t0, NULL); > # endif > if (!tls_dropprivs_validate_require_cipher(FALSE)) >@@ -4922,8 +5162,8 @@ > sender, or if a sender other than <> is set, override with the originator's > login (which will get qualified below), except when checking things. */ > >- if (sender_address == NULL /* No sender_address set */ >- || /* OR */ >+ if ( !sender_address /* No sender_address set */ >+ || /* OR */ > (sender_address[0] != 0 && /* Non-empty sender address, AND */ > !checking)) /* Not running tests, including filter tests */ > { >@@ -4974,11 +5214,12 @@ > } > > if (recipients_arg < argc) >- { > while (recipients_arg < argc) > { > /* Supplied addresses are tainted since they come from a user */ >- uschar * s = string_copy_taint(exim_str_fail_toolong(argv[recipients_arg++], EXIM_DISPLAYMAIL_MAX, "address verification"), TRUE); >+ uschar * s = string_copy_taint( >+ exim_str_fail_toolong(argv[recipients_arg++], EXIM_DISPLAYMAIL_MAX, "address verification"), >+ GET_TAINTED); > while (*s) > { > BOOL finished = FALSE; >@@ -4990,13 +5231,15 @@ > while (*++s == ',' || isspace(*s)) ; > } > } >- } > > else for (;;) > { > uschar * s = get_stdinput(NULL, NULL); > if (!s) break; >- test_address(string_copy_taint(exim_str_fail_toolong(s, EXIM_DISPLAYMAIL_MAX, "address verification (stdin)"), TRUE), flags, &exit_value); >+ test_address(string_copy_taint( >+ exim_str_fail_toolong(s, EXIM_DISPLAYMAIL_MAX, "address verification (stdin)"), >+ GET_TAINTED), >+ flags, &exit_value); > } > > route_tidyup(); >@@ -5019,7 +5262,7 @@ > message_id = US exim_str_fail_toolong(argv[msg_action_arg], MESSAGE_ID_LENGTH, "message-id"); > /* Checking the length of the ID is sufficient to validate it. > Get an untainted version so file opens can be done. */ >- message_id = string_copy_taint(message_id, FALSE); >+ message_id = string_copy_taint(message_id, GET_UNTAINTED); > > spoolname = string_sprintf("%s-H", message_id); > if ((deliver_datafile = spool_open_datafile(message_id)) < 0) >@@ -5136,7 +5379,7 @@ > it. The code works for both IPv4 and IPv6, as it happens. */ > > size = host_aton(sender_host_address, x); >- sender_host_address = store_get(48, FALSE); /* large enough for full IPv6 */ >+ sender_host_address = store_get(48, GET_UNTAINTED); /* large enough for full IPv6 */ > (void)host_nmtoa(size, x, -1, sender_host_address, ':'); > > /* Now set up for testing */ >@@ -5304,7 +5547,7 @@ > { > if (!f.is_inetd) set_process_info("accepting a local %sSMTP message from <%s>", > smtp_batched_input? "batched " : "", >- (sender_address!= NULL)? sender_address : originator_login); >+ sender_address ? sender_address : originator_login); > } > else > { >@@ -5354,7 +5597,8 @@ > } > } > >-/* Otherwise, set up the input size limit here. */ >+/* Otherwise, set up the input size limit here and set no stdin stdio buffer >+(we handle buferring so as to have visibility of fill level). */ > > else > { >@@ -5366,6 +5610,8 @@ > else > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "invalid value for " > "message_size_limit: %s", expand_string_message); >+ >+ setvbuf(stdin, NULL, _IONBF, 0); > } > > /* Loop for several messages when reading SMTP input. If we fork any child >@@ -5398,15 +5644,15 @@ > > if (!f.synchronous_delivery) > { >- #ifdef SA_NOCLDWAIT >+#ifdef SA_NOCLDWAIT > struct sigaction act; > act.sa_handler = SIG_IGN; > sigemptyset(&(act.sa_mask)); > act.sa_flags = SA_NOCLDWAIT; > sigaction(SIGCHLD, &act, NULL); >- #else >+#else > signal(SIGCHLD, SIG_IGN); >- #endif >+#endif > } > > /* Save the current store pool point, for resetting at the start of >@@ -5418,7 +5664,7 @@ > messages to be read (SMTP input), or FALSE otherwise (not SMTP, or SMTP channel > collapsed). */ > >-while (more) >+for (BOOL more = TRUE; more; ) > { > rmark reset_point = store_mark(); > message_id[0] = 0; >@@ -5460,10 +5706,10 @@ > /* Now get the data for the message */ > > more = receive_msg(extract_recipients); >- if (message_id[0] == 0) >+ if (!message_id[0]) > { > cancel_cutthrough_connection(TRUE, US"receive dropped"); >- if (more) goto moreloop; >+ if (more) goto MORELOOP; > smtp_log_no_mail(); /* Log no mail if configured */ > exim_exit(EXIT_FAILURE); > } >@@ -5505,11 +5751,13 @@ > 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. */ >- uschar * s = string_copy_taint(exim_str_fail_toolong(list[i], BIG_BUFFER_SIZE, "address argument"), TRUE); >+ uschar * s = string_copy_taint( >+ exim_str_fail_toolong(list[i], BIG_BUFFER_SIZE, "address argument"), >+ GET_TAINTED); > > /* Loop for each comma-separated address */ > >- while (*s != 0) >+ while (*s) > { > BOOL finished = FALSE; > uschar *recipient; >@@ -5571,7 +5819,7 @@ > errors_sender_rc : EXIT_FAILURE; > } > >- receive_add_recipient(string_copy_taint(recipient, TRUE), -1); >+ receive_add_recipient(string_copy_taint(recipient, GET_TAINTED), -1); > s = ss; > if (!finished) > while (*(++s) != 0 && (*s == ',' || isspace(*s))); >@@ -5610,13 +5858,8 @@ > the file copy. */ > > if (!receive_timeout) >- { >- struct timeval t = { .tv_sec = 30*60, .tv_usec = 0 }; /* 30 minutes */ >- fd_set r; >- >- FD_ZERO(&r); FD_SET(0, &r); >- if (select(1, &r, NULL, NULL, &t) == 0) mainlog_close(); >- } >+ if (poll_one_fd(0, POLLIN, 30*60*1000) == 0) /* 30 minutes */ >+ mainlog_close(); > > /* Read the data for the message. If filter_test is not FTEST_NONE, this > will just read the headers for the message, and not write anything onto the >@@ -5629,7 +5872,7 @@ > for real; when reading the headers of a message for filter testing, > it is TRUE if the headers were terminated by '.' and FALSE otherwise. */ > >- if (message_id[0] == 0) exim_exit(EXIT_FAILURE); >+ if (!message_id[0]) exim_exit(EXIT_FAILURE); > } /* Non-SMTP message reception */ > > /* If this is a filter testing run, there are headers in store, but >@@ -5822,11 +6065,11 @@ > finished subprocesses here, in case there are lots of messages coming in > from the same source. */ > >- #ifndef SIG_IGN_WORKS >+#ifndef SIG_IGN_WORKS > while (waitpid(-1, NULL, WNOHANG) > 0); >- #endif >+#endif > >-moreloop: >+MORELOOP: > return_path = sender_address = NULL; > authenticated_sender = NULL; > deliver_localpart_orig = NULL; >diff -ur exim.orig/src/exim_dbmbuild.c exim/src/exim_dbmbuild.c >--- exim.orig/src/exim_dbmbuild.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/exim_dbmbuild.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -31,7 +31,7 @@ > > #include "exim.h" > >-uschar * spool_directory = NULL; /* dummy for dbstuff.h */ >+uschar * spool_directory = NULL; /* dummy for hintsdb.h */ > > /******************************************************************************/ > /* dummies needed by Solaris build */ >@@ -42,10 +42,10 @@ > readconf_printtime(int t) > { return NULL; } > void * >-store_get_3(int size, BOOL tainted, const char *filename, int linenumber) >+store_get_3(int size, const void * proto_mem, const char *filename, int linenumber) > { return NULL; } > void ** >-store_reset_3(void **ptr, int pool, const char *filename, int linenumber) >+store_reset_3(void **ptr, const char *filename, int linenumber) > { return NULL; } > void > store_release_above_3(void *ptr, const char *func, int linenumber) >@@ -70,6 +70,16 @@ > unsigned int log_selector[1]; > uschar * queue_name; > BOOL split_spool_directory; >+ >+ >+/* These introduced by the taintwarn handling */ >+rmark >+store_mark_3(const char *func, int linenumber) >+{ return NULL; } >+#ifdef ALLOW_INSECURE_TAINTED_DATA >+BOOL allow_insecure_tainted_data; >+#endif >+ > /******************************************************************************/ > > >@@ -96,26 +106,6 @@ > #endif /* STRERROR_FROM_ERRLIST */ > > >-/* For Berkeley DB >= 2, we can define a function to be called in case of DB >-errors. This should help with debugging strange DB problems, e.g. getting "File >-exists" when you try to open a db file. The API changed at release 4.3. */ >- >-#if defined(USE_DB) && defined(DB_VERSION_STRING) >-void >-# if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3) >-dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg) >-{ >-dbenv = dbenv; >-# else >-dbfn_bdb_error_callback(const char *pfx, char *msg) >-{ >-# endif >-pfx = pfx; >-printf("Berkeley DB error: %s\n", msg); >-} >-#endif >- >- > > /************************************************* > * Interpret escape sequence * >@@ -256,9 +246,7 @@ > /* It is apparently necessary to open with O_RDWR for this to work > with gdbm-1.7.3, though no reading is actually going to be done. */ > >-EXIM_DBOPEN(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644, &d); >- >-if (d == NULL) >+if (!(d = exim_dbopen(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644))) > { > printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname, > strerror(errno)); >@@ -337,11 +325,11 @@ > > if (started) > { >- EXIM_DATUM_INIT(content); >- EXIM_DATUM_DATA(content) = CS buffer; >- EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero; >+ exim_datum_init(&content); >+ exim_datum_data_set(&content, buffer); >+ exim_datum_size_set(&content, bptr - buffer + add_zero); > >- switch(rc = EXIM_DBPUTB(d, key, content)) >+ switch(rc = exim_dbputb(d, &key, &content)) > { > case EXIM_DBPUTB_OK: > count++; >@@ -351,7 +339,7 @@ > if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer); > dupcount++; > if(duperr) yield = 1; >- if (lastdup) EXIM_DBPUT(d, key, content); >+ if (lastdup) exim_dbput(d, &key, &content); > break; > > default: >@@ -364,8 +352,8 @@ > bptr = buffer; > } > >- EXIM_DATUM_INIT(key); >- EXIM_DATUM_DATA(key) = CS keybuffer; >+ exim_datum_init(&key); >+ exim_datum_data_set(&key, keybuffer); > > /* Deal with quoted keys. Escape sequences always make one character > out of several, so we can re-build in place. */ >@@ -382,16 +370,16 @@ > s++; > } > if (*s != 0) s++; /* Past terminating " */ >- EXIM_DATUM_SIZE(key) = t - keystart + add_zero; >+ exim_datum_size_set(&key, t - keystart + add_zero); > } > else > { > keystart = s; > while (*s != 0 && *s != ':' && !isspace(*s)) s++; >- EXIM_DATUM_SIZE(key) = s - keystart + add_zero; >+ exim_datum_size_set(&key, s - keystart + add_zero); > } > >- if (EXIM_DATUM_SIZE(key) > 256) >+ if (exim_datum_size_get(&key) > 256) > { > printf("Keys longer than 255 characters cannot be handled\n"); > started = 0; >@@ -400,10 +388,10 @@ > } > > if (lowercase) >- for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++) >+ for (i = 0; i < exim_datum_size_get(&key) - add_zero; i++) > keybuffer[i] = tolower(keystart[i]); > else >- for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++) >+ for (i = 0; i < exim_datum_size_get(&key) - add_zero; i++) > keybuffer[i] = keystart[i]; > > keybuffer[i] = 0; >@@ -427,11 +415,11 @@ > if (started) > { > int rc; >- EXIM_DATUM_INIT(content); >- EXIM_DATUM_DATA(content) = CS buffer; >- EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero; >+ exim_datum_init(&content); >+ exim_datum_data_set(&content, buffer); >+ exim_datum_size_set(&content, bptr - buffer + add_zero); > >- switch(rc = EXIM_DBPUTB(d, key, content)) >+ switch(rc = exim_dbputb(d, &key, &content)) > { > case EXIM_DBPUTB_OK: > count++; >@@ -441,7 +429,7 @@ > if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer); > dupcount++; > if (duperr) yield = 1; >- if (lastdup) EXIM_DBPUT(d, key, content); >+ if (lastdup) exim_dbput(d, &key, &content); > break; > > default: >@@ -456,7 +444,7 @@ > > TIDYUP: > >-EXIM_DBCLOSE(d); >+exim_dbclose(d); > (void)fclose(f); > > /* If successful, output the number of entries and rename the temporary >diff -ur exim.orig/src/exim_dbutil.c exim/src/exim_dbutil.c >--- exim.orig/src/exim_dbutil.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/exim_dbutil.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -17,11 +17,13 @@ > In all cases, the first argument is the name of the spool directory. The second > argument is the name of the database file. The available names are: > >- retry: retry delivery information >- misc: miscellaneous hints data >- wait-<t>: message waiting information; <t> is a transport name >- callout: callout verification cache >- tls: TLS session resumption cache >+ callout: callout verification cache >+ misc: miscellaneous hints data >+ ratelimit: record for ACL "ratelimit" condition >+ retry: etry delivery information >+ seen: imestamp records for ACL "seen" condition >+ tls: TLS session resumption cache >+ wait-<t>: message waiting information; <t> is a transport name > > There are a number of common subroutines, followed by three main programs, > whose inclusion is controlled by -D on the compilation command. */ >@@ -38,12 +40,16 @@ > #define type_callout 4 > #define type_ratelimit 5 > #define type_tls 6 >+#define type_seen 7 > > > /* This is used by our cut-down dbfn_open(). */ > > uschar *spool_directory; > >+BOOL keyonly = FALSE; >+BOOL utc = FALSE; >+ > > /******************************************************************************/ > /* dummies needed by Solaris build */ >@@ -69,32 +75,14 @@ > unsigned int log_selector[1]; > uschar * queue_name; > BOOL split_spool_directory; >-/******************************************************************************/ >- >- >-/************************************************* >-* Berkeley DB error callback * >-*************************************************/ > >-/* For Berkeley DB >= 2, we can define a function to be called in case of DB >-errors. This should help with debugging strange DB problems, e.g. getting "File >-exists" when you try to open a db file. The API changed at release 4.3. */ > >-#if defined(USE_DB) && defined(DB_VERSION_STRING) >-void >-#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3) >-dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg) >-{ >-dbenv = dbenv; >-#else >-dbfn_bdb_error_callback(const char *pfx, char *msg) >-{ >-#endif >-pfx = pfx; >-printf("Berkeley DB error: %s\n", msg); >-} >+/* These introduced by the taintwarn handling */ >+#ifdef ALLOW_INSECURE_TAINTED_DATA >+BOOL allow_insecure_tainted_data; > #endif > >+/******************************************************************************/ > > > /************************************************* >@@ -106,7 +94,6 @@ > void > sigalrm_handler(int sig) > { >-sig = sig; /* Keep picky compilers happy */ > sigalrm_seen = 1; > } > >@@ -120,8 +107,8 @@ > usage(uschar *name, uschar *options) > { > printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options); >-printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls\n"); >-exit(1); >+printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls | seen\n"); >+exit(EXIT_FAILURE); > } > > >@@ -136,20 +123,40 @@ > static int > check_args(int argc, uschar **argv, uschar *name, uschar *options) > { >-if (argc == 3) >+uschar * aname = argv[optind + 1]; >+if (argc - optind == 2) > { >- if (Ustrcmp(argv[2], "retry") == 0) return type_retry; >- if (Ustrcmp(argv[2], "misc") == 0) return type_misc; >- if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait; >- if (Ustrcmp(argv[2], "callout") == 0) return type_callout; >- if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit; >- if (Ustrcmp(argv[2], "tls") == 0) return type_tls; >+ if (Ustrcmp(aname, "retry") == 0) return type_retry; >+ if (Ustrcmp(aname, "misc") == 0) return type_misc; >+ if (Ustrncmp(aname, "wait-", 5) == 0) return type_wait; >+ if (Ustrcmp(aname, "callout") == 0) return type_callout; >+ if (Ustrcmp(aname, "ratelimit") == 0) return type_ratelimit; >+ if (Ustrcmp(aname, "tls") == 0) return type_tls; >+ if (Ustrcmp(aname, "seen") == 0) return type_seen; > } > usage(name, options); > return -1; /* Never obeyed */ > } > > >+FUNC_MAYBE_UNUSED >+static void >+options(int argc, uschar * argv[], uschar * name, const uschar * opts) >+{ >+int opt; >+ >+opterr = 0; >+while ((opt = getopt(argc, (char * const *)argv, CCS opts)) != -1) >+ switch (opt) >+ { >+ case 'k': keyonly = TRUE; break; >+ case 'z': utc = TRUE; break; >+ default: usage(name, US" [-z] [-k]"); >+ } >+} >+ >+ >+ > > /************************************************* > * Handle attempts to write the log * >@@ -177,8 +184,6 @@ > vfprintf(stderr, format, ap); > fprintf(stderr, "\n"); > va_end(ap); >-selector = selector; /* Keep picky compilers happy */ >-flags = flags; > } > > >@@ -192,7 +197,7 @@ > uschar * > print_time(time_t t) > { >-struct tm *tmstr = localtime(&t); >+struct tm *tmstr = utc ? gmtime(&t) : localtime(&t); > Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr); > return time_buffer; > } >@@ -206,8 +211,8 @@ > uschar * > print_cache(int value) > { >-return (value == ccache_accept)? US"accept" : >- (value == ccache_reject)? US"reject" : >+return value == ccache_accept ? US"accept" : >+ value == ccache_reject ? US"reject" : > US"unknown"; > } > >@@ -337,7 +342,7 @@ > #else > filename = string_sprintf("%s/%s", dirname, name); > #endif >-EXIM_DBOPEN(filename, dirname, flags, 0, &dbblock->dbptr); >+dbblock->dbptr = exim_dbopen(filename, dirname, flags, 0); > > if (!dbblock->dbptr) > { >@@ -373,7 +378,7 @@ > void > dbfn_close(open_db *dbblock) > { >-EXIM_DBCLOSE(dbblock->dbptr); >+exim_dbclose(dbblock->dbptr); > (void)close(dbblock->lockfd); > } > >@@ -403,25 +408,25 @@ > void *yield; > EXIM_DATUM key_datum, result_datum; > int klen = Ustrlen(key) + 1; >-uschar * key_copy = store_get(klen, is_tainted(key)); >+uschar * key_copy = store_get(klen, key); > > memcpy(key_copy, key, klen); > >-EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */ >-EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */ >-EXIM_DATUM_DATA(key_datum) = CS key_copy; >-EXIM_DATUM_SIZE(key_datum) = klen; >+exim_datum_init(&key_datum); /* Some DBM libraries require the datum */ >+exim_datum_init(&result_datum); /* to be cleared before use. */ >+exim_datum_data_set(&key_datum, key_copy); >+exim_datum_size_set(&key_datum, klen); > >-if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL; >+if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) return NULL; > > /* Assume for now that anything stored could have been tainted. Properly > we should store the taint status along with the data. */ > >-yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE); >-memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum)); >-if (length) *length = EXIM_DATUM_SIZE(result_datum); >+yield = store_get(exim_datum_size_get(&result_datum), GET_TAINTED); >+memcpy(yield, exim_datum_data_get(&result_datum), exim_datum_size_get(&result_datum)); >+if (length) *length = exim_datum_size_get(&result_datum); > >-EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */ >+exim_datum_free(&result_datum); /* Some DBM libs require freeing */ > return yield; > } > >@@ -450,18 +455,18 @@ > EXIM_DATUM key_datum, value_datum; > dbdata_generic *gptr = (dbdata_generic *)ptr; > int klen = Ustrlen(key) + 1; >-uschar * key_copy = store_get(klen, is_tainted(key)); >+uschar * key_copy = store_get(klen, key); > > memcpy(key_copy, key, klen); > gptr->time_stamp = time(NULL); > >-EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */ >-EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */ >-EXIM_DATUM_DATA(key_datum) = CS key_copy; >-EXIM_DATUM_SIZE(key_datum) = klen; >-EXIM_DATUM_DATA(value_datum) = CS ptr; >-EXIM_DATUM_SIZE(value_datum) = length; >-return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum); >+exim_datum_init(&key_datum); /* Some DBM libraries require the datum */ >+exim_datum_init(&value_datum); /* to be cleared before use. */ >+exim_datum_data_set(&key_datum, key_copy); >+exim_datum_size_set(&key_datum, klen); >+exim_datum_data_set(&value_datum, ptr); >+exim_datum_size_set(&value_datum, length); >+return exim_dbput(dbblock->dbptr, &key_datum, &value_datum); > } > > >@@ -482,14 +487,14 @@ > dbfn_delete(open_db *dbblock, const uschar *key) > { > int klen = Ustrlen(key) + 1; >-uschar * key_copy = store_get(klen, is_tainted(key)); >+uschar * key_copy = store_get(klen, key); >+EXIM_DATUM key_datum; > > memcpy(key_copy, key, klen); >-EXIM_DATUM key_datum; >-EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */ >-EXIM_DATUM_DATA(key_datum) = CS key_copy; >-EXIM_DATUM_SIZE(key_datum) = klen; >-return EXIM_DBDEL(dbblock->dbptr, key_datum); >+exim_datum_init(&key_datum); /* Some DBM libraries require clearing */ >+exim_datum_data_set(&key_datum, key_copy); >+exim_datum_size_set(&key_datum, klen); >+return exim_dbdel(dbblock->dbptr, &key_datum); > } > > #endif /* EXIM_TIDYDB || EXIM_FIXDB */ >@@ -518,21 +523,20 @@ > { > EXIM_DATUM key_datum, value_datum; > uschar *yield; >-value_datum = value_datum; /* dummy; not all db libraries use this */ > > /* Some dbm require an initialization */ > >-if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor); >+if (start) *cursor = exim_dbcreate_cursor(dbblock->dbptr); > >-EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */ >-EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */ >+exim_datum_init(&key_datum); /* Some DBM libraries require the datum */ >+exim_datum_init(&value_datum); /* to be cleared before use. */ > >-yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))? >- US EXIM_DATUM_DATA(key_datum) : NULL; >+yield = exim_dbscan(dbblock->dbptr, &key_datum, &value_datum, start, *cursor) >+ ? US exim_datum_data_get(&key_datum) : NULL; > > /* Some dbm require a termination */ > >-if (!yield) EXIM_DBDELETE_CURSOR(*cursor); >+if (!yield) exim_dbdelete_cursor(*cursor); > return yield; > } > #endif /* EXIM_DUMPDB || EXIM_TIDYDB */ >@@ -555,11 +559,16 @@ > uschar **argv = USS cargv; > uschar keybuffer[1024]; > >+store_init(); >+options(argc, argv, US"dumpdb", US"kz"); >+ > /* Check the arguments, and open the database */ > >-dbdata_type = check_args(argc, argv, US"dumpdb", US""); >-spool_directory = argv[1]; >-if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE))) >+dbdata_type = check_args(argc, argv, US"dumpdb", US" [-z] [-k]"); >+argc -= optind; argv += optind; >+spool_directory = argv[0]; >+ >+if (!(dbm = dbfn_open(argv[1], O_RDONLY, &dbblock, FALSE, TRUE))) > exit(1); > > /* Scan the file, formatting the information for each entry. Note >@@ -576,6 +585,7 @@ > dbdata_ratelimit *ratelimit; > dbdata_ratelimit_unique *rate_unique; > dbdata_tls_session *session; >+ dbdata_seen *seen; > int count_bad = 0; > int length; > uschar *t; >@@ -593,12 +603,13 @@ > } > Ustrcpy(keybuffer, key); > >- if (!(value = dbfn_read_with_length(dbm, keybuffer, &length))) >+ if (keyonly) >+ printf(" %s\n", keybuffer); >+ else if (!(value = dbfn_read_with_length(dbm, keybuffer, &length))) > fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record " > "was not found in the file - something is wrong!\n", > CS keybuffer); > else >- { > /* Note: don't use print_time more than once in one statement, since > it uses a single buffer. */ > >@@ -715,8 +726,12 @@ > session = (dbdata_tls_session *)value; > printf(" %s %.*s\n", keybuffer, length, session->session); > break; >+ >+ case type_seen: >+ seen = (dbdata_seen *)value; >+ printf("%s\t%s\n", keybuffer, print_time(seen->time_stamp)); >+ break; > } >- } > store_reset(reset_point); > } > >@@ -761,21 +776,29 @@ > is re-used. */ > > >-int main(int argc, char **cargv) >+int >+main(int argc, char **cargv) > { > int dbdata_type; > uschar **argv = USS cargv; > uschar buffer[256]; > uschar name[256]; > rmark reset_point; >+uschar * aname; > >+store_init(); >+options(argc, argv, US"fixdb", US"z"); > name[0] = 0; /* No name set */ > > /* Sort out the database type, verify what we are working on and then process > user requests */ > >-dbdata_type = check_args(argc, argv, US"fixdb", US""); >-printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]); >+dbdata_type = check_args(argc, argv, US"fixdb", US" [-z]"); >+argc -= optind; argv += optind; >+spool_directory = argv[0]; >+aname = argv[1]; >+ >+printf("Modifying Exim hints database %s/db/%s\n", spool_directory, aname); > > for(; (reset_point = store_mark()); store_reset(reset_point)) > { >@@ -822,9 +845,8 @@ > if (field[0] != 0) > { > int verify = 1; >- spool_directory = argv[1]; > >- if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE))) >+ if (!(dbm = dbfn_open(aname, O_RDWR, &dbblock, FALSE, TRUE))) > continue; > > if (Ustrcmp(field, "d") == 0) >@@ -993,8 +1015,7 @@ > > /* Handle a read request, or verify after an update. */ > >- spool_directory = argv[1]; >- if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE))) >+ if (!(dbm = dbfn_open(aname, O_RDONLY, &dbblock, FALSE, TRUE))) > continue; > > if (!(record = dbfn_read_with_length(dbm, name, &oldlength))) >@@ -1124,7 +1145,8 @@ > } key_item; > > >-int main(int argc, char **cargv) >+int >+main(int argc, char **cargv) > { > struct stat statbuf; > int maxkeep = 30 * 24 * 60 * 60; >@@ -1138,6 +1160,8 @@ > uschar buffer[256]; > uschar *key; > >+store_init(); >+ > /* Scan the options */ > > for (i = 1; i < argc; i++) >@@ -1203,7 +1227,7 @@ > key; > key = dbfn_scan(dbm, FALSE, &cursor)) > { >- key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key)); >+ key_item * k = store_get(sizeof(key_item) + Ustrlen(key), key); > k->next = keychain; > keychain = k; > Ustrcpy(k->key, key); >diff -ur exim.orig/src/exim.h exim/src/exim.h >--- exim.orig/src/exim.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/exim.h 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -87,6 +88,13 @@ > # include <limits.h> > #endif > >+#ifdef EXIM_HAVE_INOTIFY >+# include <sys/inotify.h> >+#endif >+#ifdef EXIM_HAVE_KEVENT >+# include <sys/event.h> >+#endif >+ > /* C99 integer types, figure out how to undo this if needed for older systems */ > > #include <inttypes.h> >@@ -515,7 +523,8 @@ > > /* The header from the PCRE regex package */ > >-#include <pcre.h> >+#define PCRE2_CODE_UNIT_WIDTH 8 >+#include <pcre2.h> > > /* Exim includes are in several files. Note that local_scan.h #includes > config.h, mytypes.h, and store.h, so we don't need to mention them explicitly. >@@ -523,11 +532,12 @@ > > #include "local_scan.h" > #include "macros.h" >-#include "dbstuff.h" >+#include "hintsdb.h" >+#include "hintsdb_structs.h" > #include "structs.h" > #include "blob.h" >-#include "globals.h" > #include "hash.h" >+#include "globals.h" > #include "functions.h" > #include "dbfunctions.h" > #include "osfunctions.h" >@@ -538,9 +548,6 @@ > #ifdef SUPPORT_SPF > # include "spf.h" > #endif >-#ifdef EXPERIMENTAL_SRS >-# include "srs.h" >-#endif > #ifndef DISABLE_DKIM > # include "dkim.h" > #endif >@@ -644,5 +651,21 @@ > # define EXIM_GROUPLIST_SIZE NGROUPS_MAX > #endif > >+/* Linux has TCP_CORK, FreeBSD has TCP_NOPUSH; they do pretty much the same */ >+ >+#ifdef TCP_CORK >+# define EXIM_TCP_CORK TCP_CORK >+#elif defined(TCP_NOPUSH) >+# define EXIM_TCP_CORK TCP_NOPUSH >+#endif >+ >+/* LibreSSL seems to not push out the SMTP response to QUIT with our usual >+handling which is trying to get the client to FIN first so that the server does >+not get the TIME_WAIT */ >+ >+#if !defined(DISABLE_TLS) && defined(USE_OPENSSL) && defined(LIBRESSL_VERSION_NUMBER) >+# define SERVERSIDE_CLOSE_NOWAIT >+#endif >+ > #endif > /* End of exim.h */ >diff -ur exim.orig/src/exim_lock.c exim/src/exim_lock.c >--- exim.orig/src/exim_lock.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/exim_lock.c 2022-06-23 16:41:10.000000000 +0300 >@@ -10,7 +10,7 @@ > > Argument: the name of the lock file > >-Copyright (c) The Exim Maintainers 2016 >+Copyright (c) The Exim Maintainers 2016 - 2021 > */ > > #include "os.h" >@@ -87,7 +87,6 @@ > static void > sigalrm_handler(int sig) > { >-sig = sig; /* Keep picky compilers happy */ > sigalrm_seen = TRUE; > } > >@@ -584,7 +583,7 @@ > if (restore_times) > { > struct stat strestore; >-#ifdef EXIM_HAVE_OPENAT >+#ifdef EXIM_HAVE_FUTIMENS > int fd = open(filename, O_RDWR); /* use fd for both get & restore */ > struct timespec tt[2]; > >diff -ur exim.orig/src/exiqgrep.src exim/src/exiqgrep.src >--- exim.orig/src/exiqgrep.src 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/exiqgrep.src 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,8 @@ > > # Utility for searching and displaying queue information. > # Written by Matt Hubbard 15 August 2002 >+# >+# Copyright (c) The Exim Maintainers 2021 - 2022 > > # Except when they appear in comments, the following placeholders in this > # source are replaced when it is turned into a runnable script: >@@ -53,12 +55,14 @@ > exit 0; > } > >-getopts('hf:r:y:o:s:C:zxlibRcaG:',\%opt); >-if ($ARGV[0]) { &help; exit;} >-if ($opt{h}) { &help; exit;} >+if (!getopts('hf:r:y:o:s:C:zxlibRcaG:E:',\%opt)) { &help; exit; } >+if ($opt{h}) { &help; exit; } >+if ($ARGV[0] || !($opt{f} || $opt{r} || $opt{s} || $opt{y} || $opt{o} || $opt{z} || $opt{x} || $opt{c})) >+ { &help; exit(1); } > if ($opt{a}) { $eargs = '-bp'; } > if ($opt{C} && -e $opt{C} && -f $opt{C} && -R $opt{C}) { $eargs .= ' -C '.$opt{C}; } > if ($opt{G}) { $eargs .= ' -qG'.$opt{G}; } >+if ($opt{E}) { $exim = $opt{E}; } > > # Read message queue output into hash > &collect(); >@@ -75,6 +79,7 @@ > > -h This help message. > -C Specify which exim.conf to use. >+ -E Specify exim binary to use. > > Selection criteria: > -f <regexp> Match sender address sender (field is "< >" wrapped) >diff -ur exim.orig/src/expand.c exim/src/expand.c >--- exim.orig/src/expand.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/expand.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -131,7 +131,7 @@ > US"run", > US"sg", > US"sort", >-#ifdef EXPERIMENTAL_SRS_NATIVE >+#ifdef SUPPORT_SRS > US"srs_encode", > #endif > US"substr", >@@ -166,7 +166,7 @@ > EITEM_RUN, > EITEM_SG, > EITEM_SORT, >-#ifdef EXPERIMENTAL_SRS_NATIVE >+#ifdef SUPPORT_SRS > EITEM_SRS_ENCODE, > #endif > EITEM_SUBSTR, >@@ -177,7 +177,7 @@ > cases to introduce arguments, whereas for other it is part of the name. This is > an historical mis-design. */ > >-static uschar *op_table_underscore[] = { >+static uschar * op_table_underscore[] = { > US"from_utf8", > US"local_part", > US"quote_local_part", >@@ -216,7 +216,6 @@ > US"base62d", > US"base64", > US"base64d", >- US"bless", > US"domain", > US"escape", > US"escape8bit", >@@ -264,7 +263,6 @@ > EOP_BASE62D, > EOP_BASE64, > EOP_BASE64D, >- EOP_BLESS, > EOP_DOMAIN, > EOP_ESCAPE, > EOP_ESCAPE8BIT, >@@ -334,7 +332,7 @@ > US"gei", > US"gt", > US"gti", >-#ifdef EXPERIMENTAL_SRS_NATIVE >+#ifdef SUPPORT_SRS > US"inbound_srs", > #endif > US"inlist", >@@ -387,7 +385,7 @@ > ECOND_STR_GEI, > ECOND_STR_GT, > ECOND_STR_GTI, >-#ifdef EXPERIMENTAL_SRS_NATIVE >+#ifdef SUPPORT_SRS > ECOND_INBOUND_SRS, > #endif > ECOND_INLIST, >@@ -446,9 +444,9 @@ > vtype_pspace, /* partition space; value is T/F for spool/log */ > vtype_pinodes, /* partition inodes; value is T/F for spool/log */ > vtype_cert /* SSL certificate */ >- #ifndef DISABLE_DKIM >+#ifndef DISABLE_DKIM > ,vtype_dkim /* Lookup of value in DKIM signature */ >- #endif >+#endif > }; > > /* Type for main variable table */ >@@ -585,9 +583,9 @@ > { "interface_address", vtype_stringptr, &interface_address }, > { "interface_port", vtype_int, &interface_port }, > { "item", vtype_stringptr, &iterate_item }, >- #ifdef LOOKUP_LDAP >+#ifdef LOOKUP_LDAP > { "ldap_dn", vtype_stringptr, &eldap_dn }, >- #endif >+#endif > { "load_average", vtype_load_avg, NULL }, > { "local_part", vtype_stringptr, &deliver_localpart }, > { "local_part_data", vtype_stringptr, &deliver_localpart_data }, >@@ -752,18 +750,9 @@ > { "spool_directory", vtype_stringptr, &spool_directory }, > { "spool_inodes", vtype_pinodes, (void *)TRUE }, > { "spool_space", vtype_pspace, (void *)TRUE }, >-#ifdef EXPERIMENTAL_SRS >- { "srs_db_address", vtype_stringptr, &srs_db_address }, >- { "srs_db_key", vtype_stringptr, &srs_db_key }, >- { "srs_orig_recipient", vtype_stringptr, &srs_orig_recipient }, >- { "srs_orig_sender", vtype_stringptr, &srs_orig_sender }, >-#endif >-#if defined(EXPERIMENTAL_SRS) || defined(EXPERIMENTAL_SRS_NATIVE) >+#ifdef SUPPORT_SRS > { "srs_recipient", vtype_stringptr, &srs_recipient }, > #endif >-#ifdef EXPERIMENTAL_SRS >- { "srs_status", vtype_stringptr, &srs_status }, >-#endif > { "thisaddress", vtype_stringptr, &filter_thisaddress }, > > /* The non-(in,out) variables are now deprecated */ >@@ -779,7 +768,7 @@ > { "tls_in_ourcert", vtype_cert, &tls_in.ourcert }, > { "tls_in_peercert", vtype_cert, &tls_in.peercert }, > { "tls_in_peerdn", vtype_stringptr, &tls_in.peerdn }, >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifndef DISABLE_TLS_RESUME > { "tls_in_resumption", vtype_int, &tls_in.resumption }, > #endif > #ifndef DISABLE_TLS >@@ -797,7 +786,7 @@ > { "tls_out_ourcert", vtype_cert, &tls_out.ourcert }, > { "tls_out_peercert", vtype_cert, &tls_out.peercert }, > { "tls_out_peerdn", vtype_stringptr, &tls_out.peerdn }, >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifndef DISABLE_TLS_RESUME > { "tls_out_resumption", vtype_int, &tls_out.resumption }, > #endif > #ifndef DISABLE_TLS >@@ -1299,7 +1288,7 @@ > const uschar * tlist = list; > int sep = 0; > /* Tainted mem for the throwaway element copies */ >-uschar * dummy = store_get(2, TRUE); >+uschar * dummy = store_get(2, GET_TAINTED); > > if (field < 0) > { >@@ -1595,7 +1584,7 @@ > */ > > static uschar * >-find_header(uschar *name, int *newsize, unsigned flags, uschar *charset) >+find_header(uschar *name, int *newsize, unsigned flags, const uschar *charset) > { > BOOL found = !name; > int len = name ? Ustrlen(name) : 0; >@@ -1709,9 +1698,9 @@ > if (sender_host_name) > g = string_append(g, 3, US";\n\tiprev=pass (", sender_host_name, US")"); > else if (host_lookup_deferred) >- g = string_catn(g, US";\n\tiprev=temperror", 19); >+ g = string_cat(g, US";\n\tiprev=temperror"); > else if (host_lookup_failed) >- g = string_catn(g, US";\n\tiprev=fail", 13); >+ g = string_cat(g, US";\n\tiprev=fail"); > else > return g; > >@@ -1762,8 +1751,6 @@ > #ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS > uschar * sname; > #endif >-fd_set fds; >-struct timeval tv; > > if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) > { >@@ -1807,9 +1794,7 @@ > buf[0] = NOTIFY_QUEUE_SIZE_REQ; > if (send(fd, buf, 1, 0) < 0) { where = US"send"; goto bad; } > >-FD_ZERO(&fds); FD_SET(fd, &fds); >-tv.tv_sec = 2; tv.tv_usec = 0; >-if (select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tv) != 1) >+if (poll_one_fd(fd, POLLIN, 2 * 1000) != 1) > { > DEBUG(D_expand) debug_printf("no daemon response; using local evaluation\n"); > len = snprintf(CS buf, sizeof(buf), "%u", queue_count_cached()); >@@ -1856,7 +1841,7 @@ > something non-NULL if exists_only is TRUE > */ > >-static uschar * >+static const uschar * > find_variable(uschar *name, BOOL exists_only, BOOL skipping, int *newsize) > { > var_entry * vp; >@@ -1894,15 +1879,15 @@ > { > uschar *endptr; > int n = Ustrtoul(name + 4, &endptr, 10); >- if (*endptr == 0 && n != 0 && n <= AUTH_VARS) >- return !auth_vars[n-1] ? US"" : auth_vars[n-1]; >+ if (!*endptr && n != 0 && n <= AUTH_VARS) >+ return auth_vars[n-1] ? auth_vars[n-1] : US""; > } > else if (Ustrncmp(name, "regex", 5) == 0) > { > uschar *endptr; > int n = Ustrtoul(name + 5, &endptr, 10); >- if (*endptr == 0 && n != 0 && n <= REGEX_VARS) >- return !regex_vars[n-1] ? US"" : regex_vars[n-1]; >+ if (!*endptr && n != 0 && n <= REGEX_VARS) >+ return regex_vars[n-1] ? regex_vars[n-1] : US""; > } > > /* For all other variables, search the table */ >@@ -1985,11 +1970,12 @@ > ss = (uschar **)(val); > if (!*ss && deliver_datafile >= 0) /* Read body when needed */ > { >- uschar *body; >+ uschar * body; > off_t start_offset = SPOOL_DATA_START_OFFSET; > int len = message_body_visible; >+ > if (len > message_size) len = message_size; >- *ss = body = store_malloc(len+1); >+ *ss = body = store_get(len+1, GET_TAINTED); > body[0] = 0; > if (vp->type == vtype_msgbody_end) > { >@@ -2004,8 +1990,7 @@ > if (lseek(deliver_datafile, start_offset, SEEK_SET) < 0) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "deliver_datafile lseek: %s", > strerror(errno)); >- len = read(deliver_datafile, body, len); >- if (len > 0) >+ if ((len = read(deliver_datafile, body, len)) > 0) > { > body[len] = 0; > if (message_body_newlines) /* Separate loops for efficiency */ >@@ -2440,7 +2425,7 @@ > > > >-#ifdef EXPERIMENTAL_SRS_NATIVE >+#ifdef SUPPORT_SRS > /* Do an hmac_md5. The result is _not_ nul-terminated, and is sized as > the smaller of a full hmac_md5 result (16 bytes) or the supplied output buffer. > >@@ -2515,7 +2500,7 @@ > } > return; > } >-#endif /*EXPERIMENTAL_SRS_NATIVE*/ >+#endif /*SUPPORT_SRS*/ > > > /************************************************* >@@ -2545,16 +2530,13 @@ > BOOL *subcondptr; > BOOL sub2_honour_dollar = TRUE; > BOOL is_forany, is_json, is_jsons; >-int rc, cond_type, roffset; >+int rc, cond_type; > int_eximarith_t num[2]; > struct stat statbuf; > uschar * opname; > uschar name[256]; > const uschar *sub[10]; > >-const pcre *re; >-const uschar *rerror; >- > for (;;) > if (Uskip_whitespace(&s) == '!') { testfor = !testfor; s++; } else break; > >@@ -2565,7 +2547,7 @@ > > case ECOND_DEF: > { >- uschar * t; >+ const uschar * t; > > if (*s != ':') > { >@@ -2916,143 +2898,133 @@ > { > case ECOND_NUM_E: > case ECOND_NUM_EE: >- tempcond = (num[0] == num[1]); >- break; >+ tempcond = (num[0] == num[1]); break; > > case ECOND_NUM_G: >- tempcond = (num[0] > num[1]); >- break; >+ tempcond = (num[0] > num[1]); break; > > case ECOND_NUM_GE: >- tempcond = (num[0] >= num[1]); >- break; >+ tempcond = (num[0] >= num[1]); break; > > case ECOND_NUM_L: >- tempcond = (num[0] < num[1]); >- break; >+ tempcond = (num[0] < num[1]); break; > > case ECOND_NUM_LE: >- tempcond = (num[0] <= num[1]); >- break; >+ tempcond = (num[0] <= num[1]); break; > > case ECOND_STR_LT: >- tempcond = (Ustrcmp(sub[0], sub[1]) < 0); >- break; >+ tempcond = (Ustrcmp(sub[0], sub[1]) < 0); break; > > case ECOND_STR_LTI: >- tempcond = (strcmpic(sub[0], sub[1]) < 0); >- break; >+ tempcond = (strcmpic(sub[0], sub[1]) < 0); break; > > case ECOND_STR_LE: >- tempcond = (Ustrcmp(sub[0], sub[1]) <= 0); >- break; >+ tempcond = (Ustrcmp(sub[0], sub[1]) <= 0); break; > > case ECOND_STR_LEI: >- tempcond = (strcmpic(sub[0], sub[1]) <= 0); >- break; >+ tempcond = (strcmpic(sub[0], sub[1]) <= 0); break; > > case ECOND_STR_EQ: >- tempcond = (Ustrcmp(sub[0], sub[1]) == 0); >- break; >+ tempcond = (Ustrcmp(sub[0], sub[1]) == 0); break; > > case ECOND_STR_EQI: >- tempcond = (strcmpic(sub[0], sub[1]) == 0); >- break; >+ tempcond = (strcmpic(sub[0], sub[1]) == 0); break; > > case ECOND_STR_GT: >- tempcond = (Ustrcmp(sub[0], sub[1]) > 0); >- break; >+ tempcond = (Ustrcmp(sub[0], sub[1]) > 0); break; > > case ECOND_STR_GTI: >- tempcond = (strcmpic(sub[0], sub[1]) > 0); >- break; >+ tempcond = (strcmpic(sub[0], sub[1]) > 0); break; > > case ECOND_STR_GE: >- tempcond = (Ustrcmp(sub[0], sub[1]) >= 0); >- break; >+ tempcond = (Ustrcmp(sub[0], sub[1]) >= 0); break; > > case ECOND_STR_GEI: >- tempcond = (strcmpic(sub[0], sub[1]) >= 0); >- break; >+ tempcond = (strcmpic(sub[0], sub[1]) >= 0); break; > > case ECOND_MATCH: /* Regular expression match */ >- if (!(re = pcre_compile(CS sub[1], PCRE_COPT, CCSS &rerror, >- &roffset, NULL))) > { >- expand_string_message = string_sprintf("regular expression error in " >- "\"%s\": %s at offset %d", sub[1], rerror, roffset); >- return NULL; >+ const pcre2_code * re; >+ PCRE2_SIZE offset; >+ int err; >+ >+ if (!(re = pcre2_compile((PCRE2_SPTR)sub[1], PCRE2_ZERO_TERMINATED, >+ PCRE_COPT, &err, &offset, pcre_cmp_ctx))) >+ { >+ uschar errbuf[128]; >+ pcre2_get_error_message(err, errbuf, sizeof(errbuf)); >+ expand_string_message = string_sprintf("regular expression error in " >+ "\"%s\": %s at offset %ld", sub[1], errbuf, (long)offset); >+ return NULL; >+ } >+ >+ tempcond = regex_match_and_setup(re, sub[0], 0, -1); >+ break; > } >- tempcond = regex_match_and_setup(re, sub[0], 0, -1); >- break; > > case ECOND_MATCH_ADDRESS: /* Match in an address list */ >- rc = match_address_list(sub[0], TRUE, FALSE, &(sub[1]), NULL, -1, 0, NULL); >- goto MATCHED_SOMETHING; >+ rc = match_address_list(sub[0], TRUE, FALSE, &(sub[1]), NULL, -1, 0, >+ CUSS &lookup_value); >+ goto MATCHED_SOMETHING; > > case ECOND_MATCH_DOMAIN: /* Match in a domain list */ >- rc = match_isinlist(sub[0], &(sub[1]), 0, &domainlist_anchor, NULL, >- MCL_DOMAIN + MCL_NOEXPAND, TRUE, NULL); >- goto MATCHED_SOMETHING; >+ rc = match_isinlist(sub[0], &(sub[1]), 0, &domainlist_anchor, NULL, >+ MCL_DOMAIN + MCL_NOEXPAND, TRUE, CUSS &lookup_value); >+ goto MATCHED_SOMETHING; > > case ECOND_MATCH_IP: /* Match IP address in a host list */ >- if (sub[0][0] != 0 && string_is_ip_address(sub[0], NULL) == 0) >- { >- expand_string_message = string_sprintf("\"%s\" is not an IP address", >- sub[0]); >- return NULL; >- } >- else >- { >- unsigned int *nullcache = NULL; >- check_host_block cb; >+ if (sub[0][0] != 0 && string_is_ip_address(sub[0], NULL) == 0) >+ { >+ expand_string_message = string_sprintf("\"%s\" is not an IP address", >+ sub[0]); >+ return NULL; >+ } >+ else >+ { >+ unsigned int *nullcache = NULL; >+ check_host_block cb; > >- cb.host_name = US""; >- cb.host_address = sub[0]; >+ cb.host_name = US""; >+ cb.host_address = sub[0]; > >- /* If the host address starts off ::ffff: it is an IPv6 address in >- IPv4-compatible mode. Find the IPv4 part for checking against IPv4 >- addresses. */ >- >- cb.host_ipv4 = (Ustrncmp(cb.host_address, "::ffff:", 7) == 0)? >- cb.host_address + 7 : cb.host_address; >- >- rc = match_check_list( >- &sub[1], /* the list */ >- 0, /* separator character */ >- &hostlist_anchor, /* anchor pointer */ >- &nullcache, /* cache pointer */ >- check_host, /* function for testing */ >- &cb, /* argument for function */ >- MCL_HOST, /* type of check */ >- sub[0], /* text for debugging */ >- NULL); /* where to pass back data */ >- } >- goto MATCHED_SOMETHING; >+ /* If the host address starts off ::ffff: it is an IPv6 address in >+ IPv4-compatible mode. Find the IPv4 part for checking against IPv4 >+ addresses. */ >+ >+ cb.host_ipv4 = (Ustrncmp(cb.host_address, "::ffff:", 7) == 0)? >+ cb.host_address + 7 : cb.host_address; >+ >+ rc = match_check_list( >+ &sub[1], /* the list */ >+ 0, /* separator character */ >+ &hostlist_anchor, /* anchor pointer */ >+ &nullcache, /* cache pointer */ >+ check_host, /* function for testing */ >+ &cb, /* argument for function */ >+ MCL_HOST, /* type of check */ >+ sub[0], /* text for debugging */ >+ CUSS &lookup_value); /* where to pass back data */ >+ } >+ goto MATCHED_SOMETHING; > > case ECOND_MATCH_LOCAL_PART: >- rc = match_isinlist(sub[0], &(sub[1]), 0, &localpartlist_anchor, NULL, >- MCL_LOCALPART + MCL_NOEXPAND, TRUE, NULL); >- /* Fall through */ >- /* VVVVVVVVVVVV */ >- MATCHED_SOMETHING: >- switch(rc) >- { >- case OK: >- tempcond = TRUE; >- break; >- >- case FAIL: >- tempcond = FALSE; >- break; >+ rc = match_isinlist(sub[0], &(sub[1]), 0, &localpartlist_anchor, NULL, >+ MCL_LOCALPART + MCL_NOEXPAND, TRUE, CUSS &lookup_value); >+ /* Fall through */ >+ /* VVVVVVVVVVVV */ >+ MATCHED_SOMETHING: >+ switch(rc) >+ { >+ case OK: tempcond = TRUE; break; >+ case FAIL: tempcond = FALSE; break; > >- case DEFER: >- expand_string_message = string_sprintf("unable to complete match " >- "against \"%s\": %s", sub[1], search_error_message); >- return NULL; >- } >+ case DEFER: >+ expand_string_message = string_sprintf("unable to complete match " >+ "against \"%s\": %s", sub[1], search_error_message); >+ return NULL; >+ } > >- break; >+ break; > > /* Various "encrypted" comparisons. If the second string starts with > "{" then an encryption type is given. Default to crypt() or crypt16() >@@ -3061,138 +3033,138 @@ > > case ECOND_CRYPTEQ: > #ifndef SUPPORT_CRYPTEQ >- goto COND_FAILED_NOT_COMPILED; >+ goto COND_FAILED_NOT_COMPILED; > #else >- if (strncmpic(sub[1], US"{md5}", 5) == 0) >- { >- int sublen = Ustrlen(sub[1]+5); >- md5 base; >- uschar digest[16]; >- >- md5_start(&base); >- md5_end(&base, sub[0], Ustrlen(sub[0]), digest); >- >- /* If the length that we are comparing against is 24, the MD5 digest >- is expressed as a base64 string. This is the way LDAP does it. However, >- some other software uses a straightforward hex representation. We assume >- this if the length is 32. Other lengths fail. */ >- >- if (sublen == 24) >- { >- uschar *coded = b64encode(CUS digest, 16); >- DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n" >- " subject=%s\n crypted=%s\n", coded, sub[1]+5); >- tempcond = (Ustrcmp(coded, sub[1]+5) == 0); >- } >- else if (sublen == 32) >- { >- uschar coded[36]; >- for (int i = 0; i < 16; i++) sprintf(CS (coded+2*i), "%02X", digest[i]); >- coded[32] = 0; >- DEBUG(D_auth) debug_printf("crypteq: using MD5+hex hashing\n" >- " subject=%s\n crypted=%s\n", coded, sub[1]+5); >- tempcond = (strcmpic(coded, sub[1]+5) == 0); >- } >- else >- { >- DEBUG(D_auth) debug_printf("crypteq: length for MD5 not 24 or 32: " >- "fail\n crypted=%s\n", sub[1]+5); >- tempcond = FALSE; >- } >- } >- >- else if (strncmpic(sub[1], US"{sha1}", 6) == 0) >- { >- int sublen = Ustrlen(sub[1]+6); >- hctx h; >- uschar digest[20]; >+ if (strncmpic(sub[1], US"{md5}", 5) == 0) >+ { >+ int sublen = Ustrlen(sub[1]+5); >+ md5 base; >+ uschar digest[16]; >+ >+ md5_start(&base); >+ md5_end(&base, sub[0], Ustrlen(sub[0]), digest); >+ >+ /* If the length that we are comparing against is 24, the MD5 digest >+ is expressed as a base64 string. This is the way LDAP does it. However, >+ some other software uses a straightforward hex representation. We assume >+ this if the length is 32. Other lengths fail. */ >+ >+ if (sublen == 24) >+ { >+ uschar *coded = b64encode(CUS digest, 16); >+ DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n" >+ " subject=%s\n crypted=%s\n", coded, sub[1]+5); >+ tempcond = (Ustrcmp(coded, sub[1]+5) == 0); >+ } >+ else if (sublen == 32) >+ { >+ uschar coded[36]; >+ for (int i = 0; i < 16; i++) sprintf(CS (coded+2*i), "%02X", digest[i]); >+ coded[32] = 0; >+ DEBUG(D_auth) debug_printf("crypteq: using MD5+hex hashing\n" >+ " subject=%s\n crypted=%s\n", coded, sub[1]+5); >+ tempcond = (strcmpic(coded, sub[1]+5) == 0); >+ } >+ else >+ { >+ DEBUG(D_auth) debug_printf("crypteq: length for MD5 not 24 or 32: " >+ "fail\n crypted=%s\n", sub[1]+5); >+ tempcond = FALSE; >+ } >+ } > >- sha1_start(&h); >- sha1_end(&h, sub[0], Ustrlen(sub[0]), digest); >+ else if (strncmpic(sub[1], US"{sha1}", 6) == 0) >+ { >+ int sublen = Ustrlen(sub[1]+6); >+ hctx h; >+ uschar digest[20]; > >- /* If the length that we are comparing against is 28, assume the SHA1 >- digest is expressed as a base64 string. If the length is 40, assume a >- straightforward hex representation. Other lengths fail. */ >+ sha1_start(&h); >+ sha1_end(&h, sub[0], Ustrlen(sub[0]), digest); > >- if (sublen == 28) >- { >- uschar *coded = b64encode(CUS digest, 20); >- DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n" >- " subject=%s\n crypted=%s\n", coded, sub[1]+6); >- tempcond = (Ustrcmp(coded, sub[1]+6) == 0); >- } >- else if (sublen == 40) >- { >- uschar coded[44]; >- for (int i = 0; i < 20; i++) sprintf(CS (coded+2*i), "%02X", digest[i]); >- coded[40] = 0; >- DEBUG(D_auth) debug_printf("crypteq: using SHA1+hex hashing\n" >- " subject=%s\n crypted=%s\n", coded, sub[1]+6); >- tempcond = (strcmpic(coded, sub[1]+6) == 0); >- } >- else >- { >- DEBUG(D_auth) debug_printf("crypteq: length for SHA-1 not 28 or 40: " >- "fail\n crypted=%s\n", sub[1]+6); >- tempcond = FALSE; >- } >- } >+ /* If the length that we are comparing against is 28, assume the SHA1 >+ digest is expressed as a base64 string. If the length is 40, assume a >+ straightforward hex representation. Other lengths fail. */ >+ >+ if (sublen == 28) >+ { >+ uschar *coded = b64encode(CUS digest, 20); >+ DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n" >+ " subject=%s\n crypted=%s\n", coded, sub[1]+6); >+ tempcond = (Ustrcmp(coded, sub[1]+6) == 0); >+ } >+ else if (sublen == 40) >+ { >+ uschar coded[44]; >+ for (int i = 0; i < 20; i++) sprintf(CS (coded+2*i), "%02X", digest[i]); >+ coded[40] = 0; >+ DEBUG(D_auth) debug_printf("crypteq: using SHA1+hex hashing\n" >+ " subject=%s\n crypted=%s\n", coded, sub[1]+6); >+ tempcond = (strcmpic(coded, sub[1]+6) == 0); >+ } >+ else >+ { >+ DEBUG(D_auth) debug_printf("crypteq: length for SHA-1 not 28 or 40: " >+ "fail\n crypted=%s\n", sub[1]+6); >+ tempcond = FALSE; >+ } >+ } > >- else /* {crypt} or {crypt16} and non-{ at start */ >- /* }-for-text-editors */ >- { >- int which = 0; >- uschar *coded; >+ else /* {crypt} or {crypt16} and non-{ at start */ >+ /* }-for-text-editors */ >+ { >+ int which = 0; >+ uschar *coded; > >- if (strncmpic(sub[1], US"{crypt}", 7) == 0) >- { >- sub[1] += 7; >- which = 1; >- } >- else if (strncmpic(sub[1], US"{crypt16}", 9) == 0) >- { >- sub[1] += 9; >- which = 2; >- } >- else if (sub[1][0] == '{') /* }-for-text-editors */ >- { >- expand_string_message = string_sprintf("unknown encryption mechanism " >- "in \"%s\"", sub[1]); >- return NULL; >- } >+ if (strncmpic(sub[1], US"{crypt}", 7) == 0) >+ { >+ sub[1] += 7; >+ which = 1; >+ } >+ else if (strncmpic(sub[1], US"{crypt16}", 9) == 0) >+ { >+ sub[1] += 9; >+ which = 2; >+ } >+ else if (sub[1][0] == '{') /* }-for-text-editors */ >+ { >+ expand_string_message = string_sprintf("unknown encryption mechanism " >+ "in \"%s\"", sub[1]); >+ return NULL; >+ } > >- switch(which) >- { >- case 0: coded = US DEFAULT_CRYPT(CS sub[0], CS sub[1]); break; >- case 1: coded = US crypt(CS sub[0], CS sub[1]); break; >- default: coded = US crypt16(CS sub[0], CS sub[1]); break; >- } >- >- #define STR(s) # s >- #define XSTR(s) STR(s) >- DEBUG(D_auth) debug_printf("crypteq: using %s()\n" >- " subject=%s\n crypted=%s\n", >- which == 0 ? XSTR(DEFAULT_CRYPT) : which == 1 ? "crypt" : "crypt16", >- coded, sub[1]); >- #undef STR >- #undef XSTR >- >- /* If the encrypted string contains fewer than two characters (for the >- salt), force failure. Otherwise we get false positives: with an empty >- string the yield of crypt() is an empty string! */ >- >- if (coded) >- tempcond = Ustrlen(sub[1]) < 2 ? FALSE : Ustrcmp(coded, sub[1]) == 0; >- else if (errno == EINVAL) >- tempcond = FALSE; >- else >- { >- expand_string_message = string_sprintf("crypt error: %s\n", >- US strerror(errno)); >- return NULL; >+ switch(which) >+ { >+ case 0: coded = US DEFAULT_CRYPT(CS sub[0], CS sub[1]); break; >+ case 1: coded = US crypt(CS sub[0], CS sub[1]); break; >+ default: coded = US crypt16(CS sub[0], CS sub[1]); break; >+ } >+ >+ #define STR(s) # s >+ #define XSTR(s) STR(s) >+ DEBUG(D_auth) debug_printf("crypteq: using %s()\n" >+ " subject=%s\n crypted=%s\n", >+ which == 0 ? XSTR(DEFAULT_CRYPT) : which == 1 ? "crypt" : "crypt16", >+ coded, sub[1]); >+ #undef STR >+ #undef XSTR >+ >+ /* If the encrypted string contains fewer than two characters (for the >+ salt), force failure. Otherwise we get false positives: with an empty >+ string the yield of crypt() is an empty string! */ >+ >+ if (coded) >+ tempcond = Ustrlen(sub[1]) < 2 ? FALSE : Ustrcmp(coded, sub[1]) == 0; >+ else if (errno == EINVAL) >+ tempcond = FALSE; >+ else >+ { >+ expand_string_message = string_sprintf("crypt error: %s\n", >+ US strerror(errno)); >+ return NULL; >+ } > } >- } >- break; >+ break; > #endif /* SUPPORT_CRYPTEQ */ > > case ECOND_INLIST: >@@ -3215,6 +3187,7 @@ > if (compare(sub[0], iterate_item) == 0) > { > tempcond = TRUE; >+ lookup_value = iterate_item; > break; > } > } >@@ -3445,14 +3418,15 @@ > return s; > } > >-#ifdef EXPERIMENTAL_SRS_NATIVE >+#ifdef SUPPORT_SRS > case ECOND_INBOUND_SRS: > /* ${if inbound_srs {local_part}{secret} {yes}{no}} */ > { > uschar * sub[2]; >- const pcre * re; >- int ovec[3*(4+1)]; >- int n; >+ const pcre2_code * re; >+ pcre2_match_data * md; >+ PCRE2_SIZE * ovec; >+ int quoting = 0; > uschar cksum[4]; > BOOL boolvalue = FALSE; > >@@ -3468,18 +3442,30 @@ > > re = regex_must_compile(US"^(?i)SRS0=([^=]+)=([A-Z2-7]+)=([^=]*)=(.*)$", > TRUE, FALSE); >- if (pcre_exec(re, NULL, CS sub[0], Ustrlen(sub[0]), 0, PCRE_EOPT, >- ovec, nelem(ovec)) < 0) >+ md = pcre2_match_data_create(4+1, pcre_gen_ctx); >+ if (pcre2_match(re, sub[0], PCRE2_ZERO_TERMINATED, 0, PCRE_EOPT, >+ md, pcre_mtc_ctx) < 0) > { > DEBUG(D_expand) debug_printf("no match for SRS'd local-part pattern\n"); > goto srs_result; > } >+ ovec = pcre2_get_ovector_pointer(md); > >- /* Side-effect: record the decoded recipient */ >- >- srs_recipient = string_sprintf("%.*S@%.*S", /* lowercased */ >- ovec[9]-ovec[8], sub[0] + ovec[8], /* substring 4 */ >- ovec[7]-ovec[6], sub[0] + ovec[6]); /* substring 3 */ >+ if (sub[0][0] == '"') >+ quoting = 1; >+ else for (uschar * s = sub[0]; *s; s++) >+ if (!isalnum(*s) && Ustrchr(".!#$%&'*+-/=?^_`{|}~", *s) == NULL) >+ { quoting = 1; break; } >+ if (quoting) >+ DEBUG(D_expand) debug_printf_indent("auto-quoting local part\n"); >+ >+ /* Record the (quoted, if needed) decoded recipient as $srs_recipient */ >+ >+ srs_recipient = string_sprintf("%.*s%.*S%.*s@%.*S", /* lowercased */ >+ quoting, "\"", >+ (int) (ovec[9]-ovec[8]), sub[0] + ovec[8], /* substr 4 */ >+ quoting, "\"", >+ (int) (ovec[7]-ovec[6]), sub[0] + ovec[6]); /* substr 3 */ > > /* If a zero-length secret was given, we're done. Otherwise carry on > and validate the given SRS local_part againt our secret. */ >@@ -3495,6 +3481,7 @@ > struct timeval now; > uschar * ss = sub[0] + ovec[4]; /* substring 2, the timestamp */ > long d; >+ int n; > > gettimeofday(&now, NULL); > now.tv_sec /= 86400; /* days since epoch */ >@@ -3537,7 +3524,7 @@ > if (yield) *yield = (boolvalue == testfor); > return s; > } >-#endif /*EXPERIMENTAL_SRS_NATIVE*/ >+#endif /*SUPPORT_SRS*/ > > /* Unknown condition */ > >@@ -3588,7 +3575,7 @@ > */ > > static int >-save_expand_strings(uschar **save_expand_nstring, int *save_expand_nlength) >+save_expand_strings(const uschar **save_expand_nstring, int *save_expand_nlength) > { > for (int i = 0; i <= expand_nmax; i++) > { >@@ -3615,7 +3602,7 @@ > */ > > static void >-restore_expand_strings(int save_expand_nmax, uschar **save_expand_nstring, >+restore_expand_strings(int save_expand_nmax, const uschar **save_expand_nstring, > int *save_expand_nlength) > { > expand_nmax = save_expand_nmax; >@@ -3829,8 +3816,8 @@ > static uschar * > prvs_daystamp(int day_offset) > { >-uschar *days = store_get(32, FALSE); /* Need at least 24 for cases */ >-(void)string_format(days, 32, TIME_T_FMT, /* where TIME_T_FMT is %lld */ >+uschar * days = store_get(32, GET_UNTAINTED); /* Need at least 24 for cases */ >+(void)string_format(days, 32, TIME_T_FMT, /* where TIME_T_FMT is %lld */ > (time(NULL) + day_offset*86400)/86400); > return (Ustrlen(days) >= 3) ? &days[Ustrlen(days)-3] : US"100"; > } >@@ -3901,7 +3888,7 @@ > > /* Hashing is deemed sufficient to de-taint any input data */ > >-p = finalhash_hex = store_get(40, FALSE); >+p = finalhash_hex = store_get(40, GET_UNTAINTED); > for (int i = 0; i < 3; i++) > { > *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4]; >@@ -3932,7 +3919,7 @@ > */ > > gstring * >-cat_file(FILE *f, gstring *yield, uschar *eol) >+cat_file(FILE * f, gstring * yield, uschar * eol) > { > uschar buffer[1024]; > >@@ -3944,8 +3931,6 @@ > if (eol && buffer[len]) > yield = string_cat(yield, eol); > } >- >-(void) string_from_gstring(yield); > return yield; > } > >@@ -3967,7 +3952,6 @@ > /* We assume that all errors, and any returns of zero bytes, > are actually EOF. */ > >-(void) string_from_gstring(yield); > return yield; > } > #endif >@@ -4293,6 +4277,127 @@ > } > > >+/* Expand a named list. Return false on failure. */ >+static gstring * >+expand_listnamed(gstring * yield, const uschar * name, const uschar * listtype) >+{ >+tree_node *t = NULL; >+const uschar * list; >+int sep = 0; >+uschar * item; >+BOOL needsep = FALSE; >+#define LISTNAMED_BUF_SIZE 256 >+uschar b[LISTNAMED_BUF_SIZE]; >+uschar * buffer = b; >+ >+if (*name == '+') name++; >+if (!listtype) /* no-argument version */ >+ { >+ if ( !(t = tree_search(addresslist_anchor, name)) >+ && !(t = tree_search(domainlist_anchor, name)) >+ && !(t = tree_search(hostlist_anchor, name))) >+ t = tree_search(localpartlist_anchor, name); >+ } >+else switch(*listtype) /* specific list-type version */ >+ { >+ case 'a': t = tree_search(addresslist_anchor, name); break; >+ case 'd': t = tree_search(domainlist_anchor, name); break; >+ case 'h': t = tree_search(hostlist_anchor, name); break; >+ case 'l': t = tree_search(localpartlist_anchor, name); break; >+ default: >+ expand_string_message = US"bad suffix on \"list\" operator"; >+ return yield; >+ } >+ >+if(!t) >+ { >+ expand_string_message = string_sprintf("\"%s\" is not a %snamed list", >+ name, !listtype?"" >+ : *listtype=='a'?"address " >+ : *listtype=='d'?"domain " >+ : *listtype=='h'?"host " >+ : *listtype=='l'?"localpart " >+ : 0); >+ return yield; >+ } >+ >+list = ((namedlist_block *)(t->data.ptr))->string; >+ >+/* The list could be quite long so we (re)use a buffer for each element >+rather than getting each in new memory */ >+ >+if (is_tainted(list)) buffer = store_get(LISTNAMED_BUF_SIZE, GET_TAINTED); >+while ((item = string_nextinlist(&list, &sep, buffer, LISTNAMED_BUF_SIZE))) >+ { >+ uschar * buf = US" : "; >+ if (needsep) >+ yield = string_catn(yield, buf, 3); >+ else >+ needsep = TRUE; >+ >+ if (*item == '+') /* list item is itself a named list */ >+ { >+ yield = expand_listnamed(yield, item, listtype); >+ if (expand_string_message) >+ return yield; >+ } >+ >+ else if (sep != ':') /* item from non-colon-sep list, re-quote for colon list-separator */ >+ { >+ char tok[3]; >+ tok[0] = sep; tok[1] = ':'; tok[2] = 0; >+ >+ for(char * cp; cp = strpbrk(CCS item, tok); item = US cp) >+ { >+ yield = string_catn(yield, item, cp - CS item); >+ if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */ >+ yield = string_catn(yield, US"::", 2); >+ else /* sep in item; should already be doubled; emit once */ >+ { >+ yield = string_catn(yield, US tok, 1); >+ if (*cp == sep) cp++; >+ } >+ } >+ yield = string_cat(yield, item); >+ } >+ else >+ yield = string_cat(yield, item); >+ } >+return yield; >+} >+ >+ >+ >+/************************************************/ >+static void >+debug_expansion_interim(const uschar * what, const uschar * value, int nchar, >+ BOOL skipping) >+{ >+DEBUG(D_noutf8) >+ debug_printf_indent("|"); >+else >+ debug_printf_indent(UTF8_VERT_RIGHT); >+ >+for (int fill = 11 - Ustrlen(what); fill > 0; fill--) >+ DEBUG(D_noutf8) >+ debug_printf("-"); >+ else >+ debug_printf(UTF8_HORIZ); >+ >+debug_printf("%s: %.*s\n", what, nchar, value); >+if (is_tainted(value)) >+ { >+ DEBUG(D_noutf8) >+ debug_printf_indent("%s \\__", skipping ? "| " : " "); >+ else >+ debug_printf_indent("%s", >+ skipping >+ ? UTF8_VERT " " : " " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ); >+ debug_printf("(tainted)\n"); >+ } >+} >+ >+ > /************************************************* > * Expand string * > *************************************************/ >@@ -4363,23 +4468,12 @@ > rmark reset_point = store_mark(); > gstring * yield = string_get(Ustrlen(string) + 64); > int item_type; >-const uschar *s = string; >-uschar *save_expand_nstring[EXPAND_MAXN+1]; >+const uschar * s = string; >+const uschar * save_expand_nstring[EXPAND_MAXN+1]; > int save_expand_nlength[EXPAND_MAXN+1]; >-BOOL resetok = TRUE; >+BOOL resetok = TRUE, first = TRUE; > > expand_level++; >-DEBUG(D_expand) >- DEBUG(D_noutf8) >- debug_printf_indent("/%s: %s\n", >- skipping ? "---scanning" : "considering", string); >- else >- debug_printf_indent(UTF8_DOWN_RIGHT "%s: %s\n", >- skipping >- ? UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ "scanning" >- : "considering", >- string); >- > f.expand_string_forcedfail = FALSE; > expand_string_message = US""; > >@@ -4391,11 +4485,26 @@ > goto EXPAND_FAILED; > } > >-while (*s != 0) >+while (*s) > { >- uschar *value; > uschar name[256]; > >+ DEBUG(D_expand) >+ { >+ DEBUG(D_noutf8) >+ debug_printf_indent("%c%s: %s\n", >+ first ? '/' : '|', >+ skipping ? "---scanning" : "considering", s); >+ else >+ debug_printf_indent("%s%s: %s\n", >+ first ? UTF8_DOWN_RIGHT : UTF8_VERT_RIGHT, >+ skipping >+ ? UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ "scanning" >+ : "considering", >+ s); >+ first = FALSE; >+ } >+ > /* \ escapes the next character, which must exist, or else > the expansion fails. There's a special escape, \N, which causes > copying of the subject verbatim up to the next \N. Otherwise, >@@ -4412,32 +4521,44 @@ > if (s[1] == 'N') > { > const uschar * t = s + 2; >- for (s = t; *s != 0; s++) if (*s == '\\' && s[1] == 'N') break; >+ for (s = t; *s ; s++) if (*s == '\\' && s[1] == 'N') break; >+ >+ DEBUG(D_expand) >+ debug_expansion_interim(US"protected", t, (int)(s - t), skipping); > yield = string_catn(yield, t, s - t); >- if (*s != 0) s += 2; >+ if (*s) s += 2; > } >- > else > { > uschar ch[1]; >+ DEBUG(D_expand) >+ DEBUG(D_noutf8) >+ debug_printf_indent("|backslashed: '\\%c'\n", s[1]); >+ else >+ debug_printf_indent(UTF8_VERT_RIGHT "backslashed: '\\%c'\n", s[1]); > ch[0] = string_interpret_escape(&s); > s++; > yield = string_catn(yield, ch, 1); > } >- > continue; > } > >- /*{*/ >+ /*{{*/ > /* Anything other than $ is just copied verbatim, unless we are > looking for a terminating } character. */ > >- /*{*/ > if (ket_ends && *s == '}') break; > > if (*s != '$' || !honour_dollar) > { >- yield = string_catn(yield, s++, 1); >+ int i = 1; /*{*/ >+ for (const uschar * t = s+1; >+ *t && *t != '$' && *t != '}' && *t != '\\'; t++) i++; >+ >+ DEBUG(D_expand) debug_expansion_interim(US"text", s, i, skipping); >+ >+ yield = string_catn(yield, s, i); >+ s += i; > continue; > } > >@@ -4449,10 +4570,10 @@ > "$header_". A non-existent header yields a NULL value; nothing is > inserted. */ /*}*/ > >- if (isalpha((*(++s)))) >+ if (isalpha(*++s)) > { >- int len; >- int newsize = 0; >+ const uschar * value; >+ int newsize = 0, len; > gstring * g = NULL; > uschar * t; > >@@ -4462,13 +4583,13 @@ > buffer. */ > > if (!yield) >- g = store_get(sizeof(gstring), FALSE); >+ g = store_get(sizeof(gstring), GET_UNTAINTED); > else if (yield->ptr == 0) > { > if (resetok) reset_point = store_reset(reset_point); > yield = NULL; > reset_point = store_mark(); >- g = store_get(sizeof(gstring), FALSE); /* alloc _before_ calling find_variable() */ >+ g = store_get(sizeof(gstring), GET_UNTAINTED); /* alloc _before_ calling find_variable() */ > } > > /* Header */ >@@ -4482,7 +4603,7 @@ > unsigned flags = *name == 'r' ? FH_WANT_RAW > : *name == 'l' ? FH_WANT_RAW|FH_WANT_LIST > : 0; >- uschar * charset = *name == 'b' ? NULL : headers_charset; >+ const uschar * charset = *name == 'b' ? NULL : headers_charset; > > s = read_header_name(name, sizeof(name), s); > value = find_header(name, &newsize, flags, charset); >@@ -4493,7 +4614,7 @@ > But there is no error here - nothing gets inserted. */ > > if (!value) >- { >+ { /*{*/ > if (Ustrchr(name, '}')) malformed_header = TRUE; > continue; > } >@@ -4523,7 +4644,7 @@ > yield = g; > yield->size = newsize; > yield->ptr = len; >- yield->s = value; >+ yield->s = US value; /* known to be in new store i.e. a copy, so deconst safe */ > } > else > yield = string_catn(yield, value, len); >@@ -4549,14 +4670,14 @@ > } > > /* After { there can be various things, but they all start with >- an initial word, except for a number for a string match variable. */ >+ an initial word, except for a number for a string match variable. */ /*}*/ > >- if (isdigit((*(++s)))) >+ if (isdigit(*++s)) > { > int n; >- s = read_cnumber(&n, s); /*{*/ >+ s = read_cnumber(&n, s); /*{{*/ > if (*s++ != '}') >- { /*{*/ >+ { > expand_string_message = US"} expected after number"; > goto EXPAND_FAILED; > } >@@ -4573,11 +4694,16 @@ > > /* Allow "-" in names to cater for substrings with negative > arguments. Since we are checking for known names after { this is >- OK. */ >+ OK. */ /*}*/ > > s = read_name(name, sizeof(name), s, US"_-"); > item_type = chop_match(name, item_table, nelem(item_table)); > >+ /* Switch on item type. All nondefault choices should "continue* when >+ skipping, but "break" otherwise so we get debug output for the item >+ expansion. */ >+ { >+ int start = gstring_length(yield); > switch(item_type) > { > /* Call an ACL from an expansion. We feed data in via $acl_arg1 - $acl_arg9. >@@ -4592,8 +4718,8 @@ > case EITEM_ACL: > /* ${acl {name} {arg1}{arg2}...} */ > { >- uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */ >- uschar *user_msg; >+ uschar * sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */ >+ uschar * user_msg; > int rc; > > switch(read_subs(sub, nelem(sub), 1, &s, skipping, TRUE, name, >@@ -4614,7 +4740,7 @@ > debug_printf_indent("acl expansion yield: %s\n", user_msg); > if (user_msg) > yield = string_cat(yield, user_msg); >- continue; >+ break; > > case DEFER: > f.expand_string_forcedfail = TRUE; >@@ -4624,12 +4750,13 @@ > rc_names[rc], sub[0]); > goto EXPAND_FAILED; > } >+ break; > } > > case EITEM_AUTHRESULTS: > /* ${authresults {mysystemname}} */ > { >- uschar *sub_arg[1]; >+ uschar * sub_arg[1]; > > switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, skipping, TRUE, name, > &resetok)) >@@ -4658,7 +4785,7 @@ > #ifdef EXPERIMENTAL_ARC > yield = authres_arc(yield); > #endif >- continue; >+ break; > } > > /* Handle conditionals - preserve the values of the numerical expansion >@@ -4672,27 +4799,18 @@ > const uschar *next_s; > int save_expand_nmax = > save_expand_strings(save_expand_nstring, save_expand_nlength); >+ uschar * save_lookup_value = lookup_value; > > Uskip_whitespace(&s); > if (!(next_s = eval_condition(s, &resetok, skipping ? NULL : &cond))) > goto EXPAND_FAILED; /* message already set */ > > DEBUG(D_expand) >- DEBUG(D_noutf8) >- { >- debug_printf_indent("|--condition: %.*s\n", (int)(next_s - s), s); >- debug_printf_indent("|-----result: %s\n", cond ? "true" : "false"); >- } >- else >- { >- debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ >- "condition: %.*s\n", >- (int)(next_s - s), s); >- debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ >- UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ >- "result: %s\n", >- cond ? "true" : "false"); >- } >+ { >+ debug_expansion_interim(US"condition", s, (int)(next_s - s), skipping); >+ debug_expansion_interim(US"result", >+ cond ? US"true" : US"false", cond ? 4 : 5, skipping); >+ } > > s = next_s; > >@@ -4715,14 +4833,15 @@ > /* Restore external setting of expansion variables for continuation > at this level. */ > >+ lookup_value = save_lookup_value; > restore_expand_strings(save_expand_nmax, save_expand_nstring, > save_expand_nlength); >- continue; >+ break; > } > > #ifdef SUPPORT_I18N > case EITEM_IMAPFOLDER: >- { /* ${imapfolder {name}{sep]{specials}} */ >+ { /* ${imapfolder {name}{sep}{specials}} */ > uschar *sub_arg[3]; > uschar *encoded; > >@@ -4748,14 +4867,13 @@ > goto EXPAND_FAILED; > } > >- if (!skipping) >- { >- if (!(encoded = imap_utf7_encode(sub_arg[0], headers_charset, >- sub_arg[1][0], sub_arg[2], &expand_string_message))) >- goto EXPAND_FAILED; >- yield = string_cat(yield, encoded); >- } >- continue; >+ if (skipping) continue; >+ >+ if (!(encoded = imap_utf7_encode(sub_arg[0], headers_charset, >+ sub_arg[1][0], sub_arg[2], &expand_string_message))) >+ goto EXPAND_FAILED; >+ yield = string_cat(yield, encoded); >+ break; > } > #endif > >@@ -4771,13 +4889,13 @@ > int stype, partial, affixlen, starflags; > int expand_setup = 0; > int nameptr = 0; >- uschar *key, *filename; >+ uschar * key, * filename; > const uschar * affix, * opts; >- uschar *save_lookup_value = lookup_value; >+ uschar * save_lookup_value = lookup_value; > int save_expand_nmax = > save_expand_strings(save_expand_nstring, save_expand_nlength); > >- if ((expand_forbid & RDO_LOOKUP) != 0) >+ if (expand_forbid & RDO_LOOKUP) > { > expand_string_message = US"lookup expansions are not permitted"; > goto EXPAND_FAILED; >@@ -4841,15 +4959,12 @@ > goto EXPAND_FAILED; > } > } >- else >- { >- if (key) >- { >- expand_string_message = string_sprintf("a single key was given for " >- "lookup type \"%s\", which is not a single-key lookup type", name); >- goto EXPAND_FAILED; >- } >- } >+ else if (key) >+ { >+ expand_string_message = string_sprintf("a single key was given for " >+ "lookup type \"%s\", which is not a single-key lookup type", name); >+ goto EXPAND_FAILED; >+ } > > /* Get the next string in brackets and expand it. It is the file name for > single-key+file lookups, and the whole query otherwise. In the case of >@@ -4859,10 +4974,11 @@ > if (*s != '{') > { > expand_string_message = US"missing '{' for lookup file-or-query arg"; >- goto EXPAND_FAILED_CURLY; >+ goto EXPAND_FAILED_CURLY; /*}}*/ > } > if (!(filename = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok))) > goto EXPAND_FAILED; >+ /*{{*/ > if (*s++ != '}') > { > expand_string_message = US"missing '}' closing lookup file-or-query arg"; >@@ -4876,21 +4992,7 @@ > file types, the query (i.e. "key") starts with a file name. */ > > if (!key) >- { >- Uskip_whitespace(&filename); >- key = filename; >- >- if (mac_islookup(stype, lookup_querystyle)) >- filename = NULL; >- else >- if (*filename == '/') >- { >- while (*key && !isspace(*key)) key++; >- if (*key) *key++ = '\0'; >- } >- else >- filename = NULL; >- } >+ key = search_args(stype, name, filename, &filename, opts); > > /* If skipping, don't do the next bit - just lookup_value == NULL, as if > the entry was not found. Note that there is no search_close() function. >@@ -4909,7 +5011,7 @@ > lookup_value = NULL; > else > { >- void *handle = search_open(filename, stype, 0, NULL, NULL); >+ void * handle = search_open(filename, stype, 0, NULL, NULL); > if (!handle) > { > expand_string_message = search_error_message; >@@ -4948,7 +5050,9 @@ > > restore_expand_strings(save_expand_nmax, save_expand_nstring, > save_expand_nlength); >- continue; >+ >+ if (skipping) continue; >+ break; > } > > /* If Perl support is configured, handle calling embedded perl subroutines, >@@ -4956,20 +5060,20 @@ > or ${perl{sub}{arg1}{arg2}} or up to a maximum of EXIM_PERL_MAX_ARGS > arguments (defined below). */ > >- #define EXIM_PERL_MAX_ARGS 8 >+#define EXIM_PERL_MAX_ARGS 8 > > case EITEM_PERL: >- #ifndef EXIM_PERL >- expand_string_message = US"\"${perl\" encountered, but this facility " /*}*/ >- "is not included in this binary"; >- goto EXPAND_FAILED; >+#ifndef EXIM_PERL >+ expand_string_message = US"\"${perl\" encountered, but this facility " /*}*/ >+ "is not included in this binary"; >+ goto EXPAND_FAILED; > >- #else /* EXIM_PERL */ >+#else /* EXIM_PERL */ > { >- uschar *sub_arg[EXIM_PERL_MAX_ARGS + 2]; >- gstring *new_yield; >+ uschar * sub_arg[EXIM_PERL_MAX_ARGS + 2]; >+ gstring * new_yield; > >- if ((expand_forbid & RDO_PERL) != 0) >+ if (expand_forbid & RDO_PERL) > { > expand_string_message = US"Perl calls are not permitted"; > goto EXPAND_FAILED; >@@ -4991,7 +5095,7 @@ > > if (!opt_perl_started) > { >- uschar *initerror; >+ uschar * initerror; > if (!opt_perl_startup) > { > expand_string_message = US"A setting of perl_startup is needed when " >@@ -5035,17 +5139,16 @@ > > f.expand_string_forcedfail = FALSE; > yield = new_yield; >- continue; >+ break; > } >- #endif /* EXIM_PERL */ >+#endif /* EXIM_PERL */ > > /* Transform email address to "prvs" scheme to use > as BATV-signed return path */ > > case EITEM_PRVS: > { >- uschar *sub_arg[3]; >- uschar *p,*domain; >+ uschar * sub_arg[3], * p, * domain; > > switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, name, &resetok)) > { >@@ -5094,17 +5197,16 @@ > yield = string_catn(yield, US"@", 1); > yield = string_cat (yield, domain); > >- continue; >+ break; > } > > /* Check a prvs-encoded address for validity */ > > case EITEM_PRVSCHECK: > { >- uschar *sub_arg[3]; >+ uschar * sub_arg[3], * p; > gstring * g; >- const pcre *re; >- uschar *p; >+ const pcre2_code * re; > > /* TF: Ugliness: We want to expand parameter 1 first, then set > up expansion variables that are used in the expansion of >@@ -5114,7 +5216,7 @@ > PH: Actually, that isn't necessary. The read_subs() function is > designed to work this way for the ${if and ${lookup expansions. I've > tidied the code. >- */ >+ */ /*}}*/ > > /* Reset expansion variables */ > prvscheck_result = NULL; >@@ -5133,11 +5235,11 @@ > > if (regex_match_and_setup(re,sub_arg[0],0,-1)) > { >- uschar *local_part = string_copyn(expand_nstring[4],expand_nlength[4]); >- uschar *key_num = string_copyn(expand_nstring[1],expand_nlength[1]); >- uschar *daystamp = string_copyn(expand_nstring[2],expand_nlength[2]); >- uschar *hash = string_copyn(expand_nstring[3],expand_nlength[3]); >- uschar *domain = string_copyn(expand_nstring[5],expand_nlength[5]); >+ uschar * local_part = string_copyn(expand_nstring[4],expand_nlength[4]); >+ uschar * key_num = string_copyn(expand_nstring[1],expand_nlength[1]); >+ uschar * daystamp = string_copyn(expand_nstring[2],expand_nlength[2]); >+ uschar * hash = string_copyn(expand_nstring[3],expand_nlength[3]); >+ uschar * domain = string_copyn(expand_nstring[5],expand_nlength[5]); > > DEBUG(D_expand) debug_printf_indent("prvscheck localpart: %s\n", local_part); > DEBUG(D_expand) debug_printf_indent("prvscheck key number: %s\n", key_num); >@@ -5235,15 +5337,16 @@ > case 3: goto EXPAND_FAILED; > } > >- continue; >+ if (skipping) continue; >+ break; > } > > /* Handle "readfile" to insert an entire file */ > > case EITEM_READFILE: > { >- FILE *f; >- uschar *sub_arg[2]; >+ FILE * f; >+ uschar * sub_arg[2]; > > if ((expand_forbid & RDO_READFILE) != 0) > { >@@ -5266,13 +5369,13 @@ > > if (!(f = Ufopen(sub_arg[0], "rb"))) > { >- expand_string_message = string_open_failed(errno, "%s", sub_arg[0]); >+ expand_string_message = string_open_failed("%s", sub_arg[0]); > goto EXPAND_FAILED; > } > > yield = cat_file(f, yield, sub_arg[1]); > (void)fclose(f); >- continue; >+ break; > } > > /* Handle "readsocket" to insert data from a socket, either >@@ -5378,38 +5481,39 @@ > /* The whole thing has worked (or we were skipping). If there is a > failure string following, we need to skip it. */ > >- if (*s == '{') >+ if (*s == '{') /*}*/ > { > if (!expand_string_internal(s+1, TRUE, &s, TRUE, TRUE, &resetok)) >- goto EXPAND_FAILED; >+ goto EXPAND_FAILED; /*{*/ > if (*s++ != '}') >- { >+ { /*{*/ > expand_string_message = US"missing '}' closing failstring for readsocket"; > goto EXPAND_FAILED_CURLY; > } > Uskip_whitespace(&s); > } > >- READSOCK_DONE: >+ READSOCK_DONE: /*{*/ > if (*s++ != '}') >- { >+ { /*{*/ > expand_string_message = US"missing '}' closing readsocket"; > goto EXPAND_FAILED_CURLY; > } >- continue; >+ if (skipping) continue; >+ break; > > /* Come here on failure to create socket, connect socket, write to the > socket, or timeout on reading. If another substring follows, expand and > use it. Otherwise, those conditions give expand errors. */ > > SOCK_FAIL: >- if (*s != '{') goto EXPAND_FAILED; >+ if (*s != '{') goto EXPAND_FAILED; /*}*/ > DEBUG(D_any) debug_printf("%s\n", expand_string_message); > if (!(arg = expand_string_internal(s+1, TRUE, &s, FALSE, TRUE, &resetok))) > goto EXPAND_FAILED; >- yield = string_cat(yield, arg); >+ yield = string_cat(yield, arg); /*{*/ > if (*s++ != '}') >- { >+ { /*{*/ > expand_string_message = US"missing '}' closing failstring for readsocket"; > goto EXPAND_FAILED_CURLY; > } >@@ -5421,11 +5525,9 @@ > > case EITEM_RUN: > { >- FILE *f; >- uschar *arg; >- const uschar **argv; >- pid_t pid; >- int fd_in, fd_out; >+ FILE * f; >+ const uschar * arg, ** argv; >+ BOOL late_expand = TRUE; > > if ((expand_forbid & RDO_RUN) != 0) > { >@@ -5433,17 +5535,45 @@ > goto EXPAND_FAILED; > } > >+ /* Handle options to the "run" */ >+ >+ while (*s == ',') >+ { >+ if (Ustrncmp(++s, "preexpand", 9) == 0) >+ { late_expand = FALSE; s += 9; } >+ else >+ { >+ const uschar * t = s; >+ while (isalpha(*++t)) ; >+ expand_string_message = string_sprintf("bad option '%.*s' for run", >+ (int)(t-s), s); >+ goto EXPAND_FAILED; >+ } >+ } > Uskip_whitespace(&s); >- if (*s != '{') >+ >+ if (*s != '{') /*}*/ > { > expand_string_message = US"missing '{' for command arg of run"; >- goto EXPAND_FAILED_CURLY; >+ goto EXPAND_FAILED_CURLY; /*"}*/ > } >- if (!(arg = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok))) >- goto EXPAND_FAILED; >- Uskip_whitespace(&s); >+ s++; >+ >+ if (late_expand) /* this is the default case */ >+ { >+ int n = Ustrcspn(s, "}"); >+ arg = skipping ? NULL : string_copyn(s, n); >+ s += n; >+ } >+ else >+ { >+ if (!(arg = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok))) >+ goto EXPAND_FAILED; >+ Uskip_whitespace(&s); >+ } >+ /*{*/ > if (*s++ != '}') >- { >+ { /*{*/ > expand_string_message = US"missing '}' closing command arg of run"; > goto EXPAND_FAILED_CURLY; > } >@@ -5455,13 +5585,17 @@ > } > else > { >+ int fd_in, fd_out; >+ pid_t pid; >+ > if (!transport_set_up_command(&argv, /* anchor for arg list */ > arg, /* raw command */ >- FALSE, /* don't expand the arguments */ >- 0, /* not relevant when... */ >- NULL, /* no transporting address */ >- US"${run} expansion", /* for error messages */ >- &expand_string_message)) /* where to put error message */ >+ late_expand, /* expand args if not already done */ >+ 0, /* not relevant when... */ >+ NULL, /* no transporting address */ >+ late_expand, /* allow tainted args, when expand-after-split */ >+ US"${run} expansion", /* for error messages */ >+ &expand_string_message)) /* where to put error message */ > goto EXPAND_FAILED; > > /* Create the child process, making it a group leader. */ >@@ -5472,7 +5606,7 @@ > expand_string_message = > string_sprintf("couldn't create child process: %s", strerror(errno)); > goto EXPAND_FAILED; >- } >+ } > > /* Nothing is written to the standard input. */ > >@@ -5530,7 +5664,8 @@ > case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ > } > >- continue; >+ if (skipping) continue; >+ break; > } > > /* Handle character translation for "tr" */ >@@ -5539,7 +5674,7 @@ > { > int oldptr = gstring_length(yield); > int o2m; >- uschar *sub[3]; >+ uschar * sub[3]; > > switch(read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok)) > { >@@ -5561,7 +5696,8 @@ > } > } > >- continue; >+ if (skipping) continue; >+ break; > } > > /* Handle "hash", "length", "nhash", and "substr" when they are given with >@@ -5575,7 +5711,7 @@ > int len; > uschar *ret; > int val[2] = { 0, -1 }; >- uschar *sub[3]; >+ uschar * sub[3]; > > /* "length" takes only 2 arguments whereas the others take 2 or 3. > Ensure that sub[2] is set in the ${length } case. */ >@@ -5624,7 +5760,8 @@ > if (!ret) > goto EXPAND_FAILED; > yield = string_catn(yield, ret, len); >- continue; >+ if (skipping) continue; >+ break; > } > > /* Handle HMAC computation: ${hmac{<algorithm>}{<secret>}{<text>}} >@@ -5639,14 +5776,14 @@ > > case EITEM_HMAC: > { >- uschar *sub[3]; >+ uschar * sub[3]; > md5 md5_base; > hctx sha1_ctx; >- void *use_base; >+ void * use_base; > int type; > int hashlen; /* Number of octets for the hash algorithm's output */ > int hashblocklen; /* Number of octets the hash algorithm processes */ >- uschar *keyptr, *p; >+ uschar * keyptr, * p; > unsigned int keylen; > > uschar keyhash[MAX_HASHLEN]; >@@ -5663,79 +5800,78 @@ > case 3: goto EXPAND_FAILED; > } > >- if (!skipping) >+ if (skipping) continue; >+ >+ if (Ustrcmp(sub[0], "md5") == 0) > { >- if (Ustrcmp(sub[0], "md5") == 0) >- { >- type = HMAC_MD5; >- use_base = &md5_base; >- hashlen = 16; >- hashblocklen = 64; >- } >- else if (Ustrcmp(sub[0], "sha1") == 0) >- { >- type = HMAC_SHA1; >- use_base = &sha1_ctx; >- hashlen = 20; >- hashblocklen = 64; >- } >- else >- { >- expand_string_message = >- string_sprintf("hmac algorithm \"%s\" is not recognised", sub[0]); >- goto EXPAND_FAILED; >- } >+ type = HMAC_MD5; >+ use_base = &md5_base; >+ hashlen = 16; >+ hashblocklen = 64; >+ } >+ else if (Ustrcmp(sub[0], "sha1") == 0) >+ { >+ type = HMAC_SHA1; >+ use_base = &sha1_ctx; >+ hashlen = 20; >+ hashblocklen = 64; >+ } >+ else >+ { >+ expand_string_message = >+ string_sprintf("hmac algorithm \"%s\" is not recognised", sub[0]); >+ goto EXPAND_FAILED; >+ } > >- keyptr = sub[1]; >- keylen = Ustrlen(keyptr); >+ keyptr = sub[1]; >+ keylen = Ustrlen(keyptr); > >- /* If the key is longer than the hash block length, then hash the key >- first */ >+ /* If the key is longer than the hash block length, then hash the key >+ first */ > >- if (keylen > hashblocklen) >- { >- chash_start(type, use_base); >- chash_end(type, use_base, keyptr, keylen, keyhash); >- keyptr = keyhash; >- keylen = hashlen; >- } >+ if (keylen > hashblocklen) >+ { >+ chash_start(type, use_base); >+ chash_end(type, use_base, keyptr, keylen, keyhash); >+ keyptr = keyhash; >+ keylen = hashlen; >+ } > >- /* Now make the inner and outer key values */ >+ /* Now make the inner and outer key values */ > >- memset(innerkey, 0x36, hashblocklen); >- memset(outerkey, 0x5c, hashblocklen); >+ memset(innerkey, 0x36, hashblocklen); >+ memset(outerkey, 0x5c, hashblocklen); > >- for (int i = 0; i < keylen; i++) >- { >- innerkey[i] ^= keyptr[i]; >- outerkey[i] ^= keyptr[i]; >- } >+ for (int i = 0; i < keylen; i++) >+ { >+ innerkey[i] ^= keyptr[i]; >+ outerkey[i] ^= keyptr[i]; >+ } > >- /* Now do the hashes */ >+ /* Now do the hashes */ > >- chash_start(type, use_base); >- chash_mid(type, use_base, innerkey); >- chash_end(type, use_base, sub[2], Ustrlen(sub[2]), innerhash); >+ chash_start(type, use_base); >+ chash_mid(type, use_base, innerkey); >+ chash_end(type, use_base, sub[2], Ustrlen(sub[2]), innerhash); > >- chash_start(type, use_base); >- chash_mid(type, use_base, outerkey); >- chash_end(type, use_base, innerhash, hashlen, finalhash); >+ chash_start(type, use_base); >+ chash_mid(type, use_base, outerkey); >+ chash_end(type, use_base, innerhash, hashlen, finalhash); > >- /* Encode the final hash as a hex string */ >+ /* Encode the final hash as a hex string */ > >- p = finalhash_hex; >- for (int i = 0; i < hashlen; i++) >- { >- *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4]; >- *p++ = hex_digits[finalhash[i] & 0x0f]; >- } >+ p = finalhash_hex; >+ for (int i = 0; i < hashlen; i++) >+ { >+ *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4]; >+ *p++ = hex_digits[finalhash[i] & 0x0f]; >+ } > >- DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%s)=%.*s\n", >- sub[0], (int)keylen, keyptr, sub[2], hashlen*2, finalhash_hex); >+ DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%s)=%.*s\n", >+ sub[0], (int)keylen, keyptr, sub[2], hashlen*2, finalhash_hex); > >- yield = string_catn(yield, finalhash_hex, hashlen*2); >- } >- continue; >+ yield = string_catn(yield, finalhash_hex, hashlen*2); >+ break; > } > > /* Handle global substitution for "sg" - like Perl's s/xxx/yyy/g operator. >@@ -5743,13 +5879,12 @@ > > case EITEM_SG: > { >- const pcre *re; >+ const pcre2_code * re; > int moffset, moffsetextra, slen; >- int roffset; >- int emptyopt; >- const uschar *rerror; >- uschar *subject; >- uschar *sub[3]; >+ PCRE2_SIZE roffset; >+ pcre2_match_data * md; >+ int err, emptyopt; >+ uschar * subject, * sub[3]; > int save_expand_nmax = > save_expand_strings(save_expand_nstring, save_expand_nlength); > >@@ -5760,15 +5895,19 @@ > case 3: goto EXPAND_FAILED; > } > >+ /*XXX no handling of skipping? */ > /* Compile the regular expression */ > >- if (!(re = pcre_compile(CS sub[1], PCRE_COPT, CCSS &rerror, >- &roffset, NULL))) >+ if (!(re = pcre2_compile((PCRE2_SPTR)sub[1], PCRE2_ZERO_TERMINATED, >+ PCRE_COPT, &err, &roffset, pcre_cmp_ctx))) > { >+ uschar errbuf[128]; >+ pcre2_get_error_message(err, errbuf, sizeof(errbuf)); > expand_string_message = string_sprintf("regular expression error in " >- "\"%s\": %s at offset %d", sub[1], rerror, roffset); >+ "\"%s\": %s at offset %ld", sub[1], errbuf, (long)roffset); > goto EXPAND_FAILED; > } >+ md = pcre2_match_data_create(EXPAND_MAXN + 1, pcre_gen_ctx); > > /* Now run a loop to do the substitutions as often as necessary. It ends > when there are no more matches. Take care over matches of the null string; >@@ -5781,10 +5920,10 @@ > > for (;;) > { >- int ovector[3*(EXPAND_MAXN+1)]; >- int n = pcre_exec(re, NULL, CS subject, slen, moffset + moffsetextra, >- PCRE_EOPT | emptyopt, ovector, nelem(ovector)); >- uschar *insert; >+ PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md); >+ int n = pcre2_match(re, (PCRE2_SPTR)subject, slen, moffset + moffsetextra, >+ PCRE_EOPT | emptyopt, md, pcre_mtc_ctx); >+ uschar * insert; > > /* No match - if we previously set PCRE_NOTEMPTY after a null match, this > is not necessarily the end. We want to repeat the match from one >@@ -5806,24 +5945,26 @@ > } > > /* Match - set up for expanding the replacement. */ >+ DEBUG(D_expand) debug_printf_indent("%s: match\n", name); > > if (n == 0) n = EXPAND_MAXN + 1; > expand_nmax = 0; > for (int nn = 0; nn < n*2; nn += 2) > { >- expand_nstring[expand_nmax] = subject + ovector[nn]; >- expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn]; >+ expand_nstring[expand_nmax] = subject + ovec[nn]; >+ expand_nlength[expand_nmax++] = ovec[nn+1] - ovec[nn]; > } > expand_nmax--; > > /* Copy the characters before the match, plus the expanded insertion. */ > >- yield = string_catn(yield, subject + moffset, ovector[0] - moffset); >+ yield = string_catn(yield, subject + moffset, ovec[0] - moffset); >+ > if (!(insert = expand_string(sub[2]))) > goto EXPAND_FAILED; > yield = string_cat(yield, insert); > >- moffset = ovector[1]; >+ moffset = ovec[1]; > moffsetextra = 0; > emptyopt = 0; > >@@ -5834,10 +5975,10 @@ > string at the same point. If this fails (picked up above) we advance to > the next character. */ > >- if (ovector[0] == ovector[1]) >+ if (ovec[0] == ovec[1]) > { >- if (ovector[0] == slen) break; >- emptyopt = PCRE_NOTEMPTY | PCRE_ANCHORED; >+ if (ovec[0] == slen) break; >+ emptyopt = PCRE2_NOTEMPTY | PCRE2_ANCHORED; > } > } > >@@ -5845,7 +5986,8 @@ > > restore_expand_strings(save_expand_nmax, save_expand_nstring, > save_expand_nlength); >- continue; >+ if (skipping) continue; >+ break; > } > > /* Handle keyed and numbered substring extraction. If the first argument >@@ -5855,8 +5997,7 @@ > { > int field_number = 1; > BOOL field_number_set = FALSE; >- uschar *save_lookup_value = lookup_value; >- uschar *sub[3]; >+ uschar * save_lookup_value = lookup_value, * sub[3]; > int save_expand_nmax = > save_expand_strings(save_expand_nstring, save_expand_nlength); > >@@ -5882,7 +6023,7 @@ > > if (skipping) > { >- for (int j = 5; j > 0 && *s == '{'; j--) /*'}'*/ >+ for (int j = 5; j > 0 && *s == '{'; j--) /*'}'*/ > { > if (!expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok)) > goto EXPAND_FAILED; /*'{'*/ >@@ -5893,13 +6034,13 @@ > } > Uskip_whitespace(&s); > } >- if ( Ustrncmp(s, "fail", 4) == 0 /*'{'*/ >+ if ( Ustrncmp(s, "fail", 4) == 0 /*'{'*/ > && (s[4] == '}' || s[4] == ' ' || s[4] == '\t' || !s[4]) > ) > { > s += 4; > Uskip_whitespace(&s); >- } /*'{'*/ >+ } /*'{'*/ > if (*s != '}') > { > expand_string_message = US"missing '}' closing extract"; >@@ -5909,10 +6050,10 @@ > > else for (int i = 0, j = 2; i < j; i++) /* Read the proper number of arguments */ > { >- if (Uskip_whitespace(&s) == '{') /*'}'*/ >+ if (Uskip_whitespace(&s) == '{') /*'}'*/ > { > if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok))) >- goto EXPAND_FAILED; /*'{'*/ >+ goto EXPAND_FAILED; /*'{'*/ > if (*s++ != '}') > { > expand_string_message = string_sprintf( >@@ -5929,7 +6070,7 @@ > { > int len; > int x = 0; >- uschar *p = sub[0]; >+ uschar * p = sub[0]; > > Uskip_whitespace(&p); > sub[0] = p; >@@ -5938,7 +6079,7 @@ > while (len > 0 && isspace(p[len-1])) len--; > p[len] = 0; > >- if (*p == 0) >+ if (!*p) > { > expand_string_message = US"first argument of \"extract\" must " > "not be empty"; >@@ -5950,8 +6091,8 @@ > field_number = -1; > p++; > } >- while (*p != 0 && isdigit(*p)) x = x * 10 + *p++ - '0'; >- if (*p == 0) >+ while (*p && isdigit(*p)) x = x * 10 + *p++ - '0'; >+ if (!*p) > { > field_number *= x; > if (fmt == extract_basic) j = 3; /* Need 3 args */ >@@ -6081,7 +6222,8 @@ > restore_expand_strings(save_expand_nmax, save_expand_nstring, > save_expand_nlength); > >- continue; >+ if (skipping) continue; >+ break; > } > > /* return the Nth item from a list */ >@@ -6089,8 +6231,7 @@ > case EITEM_LISTEXTRACT: > { > int field_number = 1; >- uschar *save_lookup_value = lookup_value; >- uschar *sub[2]; >+ uschar * save_lookup_value = lookup_value, * sub[2]; > int save_expand_nmax = > save_expand_strings(save_expand_nstring, save_expand_nlength); > >@@ -6098,15 +6239,15 @@ > > for (int i = 0; i < 2; i++) > { >- if (Uskip_whitespace(&s) != '{') /*'}'*/ >+ if (Uskip_whitespace(&s) != '{') /*}*/ > { > expand_string_message = string_sprintf( >- "missing '{' for arg %d of listextract", i+1); >+ "missing '{' for arg %d of listextract", i+1); /*}*/ > goto EXPAND_FAILED_CURLY; > } > > sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); >- if (!sub[i]) goto EXPAND_FAILED; /*{*/ >+ if (!sub[i]) goto EXPAND_FAILED; /*{{*/ > if (*s++ != '}') > { > expand_string_message = string_sprintf( >@@ -6179,7 +6320,8 @@ > restore_expand_strings(save_expand_nmax, save_expand_nstring, > save_expand_nlength); > >- continue; >+ if (skipping) continue; >+ break; > } > > case EITEM_LISTQUOTE: >@@ -6197,14 +6339,14 @@ > yield = string_catn(yield, sub[1], 1); > } > else yield = string_catn(yield, US" ", 1); >- continue; >+ if (skipping) continue; >+ break; > } > > #ifndef DISABLE_TLS > case EITEM_CERTEXTRACT: > { >- uschar *save_lookup_value = lookup_value; >- uschar *sub[2]; >+ uschar * save_lookup_value = lookup_value, * sub[2]; > int save_expand_nmax = > save_expand_strings(save_expand_nstring, save_expand_nlength); > >@@ -6212,10 +6354,10 @@ > if (Uskip_whitespace(&s) != '{') /*}*/ > { > expand_string_message = US"missing '{' for field arg of certextract"; >- goto EXPAND_FAILED_CURLY; >+ goto EXPAND_FAILED_CURLY; /*}*/ > } > sub[0] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); >- if (!sub[0]) goto EXPAND_FAILED; /*{*/ >+ if (!sub[0]) goto EXPAND_FAILED; /*{{*/ > if (*s++ != '}') > { > expand_string_message = US"missing '}' closing field arg of certextract"; >@@ -6238,7 +6380,7 @@ > if (Uskip_whitespace(&s) != '{') /*}*/ > { > expand_string_message = US"missing '{' for cert variable arg of certextract"; >- goto EXPAND_FAILED_CURLY; >+ goto EXPAND_FAILED_CURLY; /*}*/ > } > if (*++s != '$') > { >@@ -6247,7 +6389,7 @@ > goto EXPAND_FAILED; > } > sub[1] = expand_string_internal(s+1, TRUE, &s, skipping, FALSE, &resetok); >- if (!sub[1]) goto EXPAND_FAILED; /*{*/ >+ if (!sub[1]) goto EXPAND_FAILED; /*{{*/ > if (*s++ != '}') > { > expand_string_message = US"missing '}' closing cert variable arg of certextract"; >@@ -6276,7 +6418,8 @@ > > restore_expand_strings(save_expand_nmax, save_expand_nstring, > save_expand_nlength); >- continue; >+ if (skipping) continue; >+ break; > } > #endif /*DISABLE_TLS*/ > >@@ -6286,23 +6429,22 @@ > case EITEM_MAP: > case EITEM_REDUCE: > { >- int sep = 0; >- int save_ptr = gstring_length(yield); >+ int sep = 0, save_ptr = gstring_length(yield); > uschar outsep[2] = { '\0', '\0' }; > const uschar *list, *expr, *temp; >- uschar *save_iterate_item = iterate_item; >- uschar *save_lookup_value = lookup_value; >+ uschar * save_iterate_item = iterate_item; >+ uschar * save_lookup_value = lookup_value; > > Uskip_whitespace(&s); >- if (*s++ != '{') >+ if (*s++ != '{') /*}*/ > { > expand_string_message = > string_sprintf("missing '{' for first arg of %s", name); >- goto EXPAND_FAILED_CURLY; >+ goto EXPAND_FAILED_CURLY; /*}*/ > } > > if (!(list = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok))) >- goto EXPAND_FAILED; >+ goto EXPAND_FAILED; /*{{*/ > if (*s++ != '}') > { > expand_string_message = >@@ -6314,14 +6456,14 @@ > { > uschar * t; > Uskip_whitespace(&s); >- if (*s++ != '{') >+ if (*s++ != '{') /*}*/ > { > expand_string_message = US"missing '{' for second arg of reduce"; >- goto EXPAND_FAILED_CURLY; >+ goto EXPAND_FAILED_CURLY; /*}*/ > } > t = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok); > if (!t) goto EXPAND_FAILED; >- lookup_value = t; >+ lookup_value = t; /*{{*/ > if (*s++ != '}') > { > expand_string_message = US"missing '}' closing second arg of reduce"; >@@ -6330,10 +6472,10 @@ > } > > Uskip_whitespace(&s); >- if (*s++ != '{') >+ if (*s++ != '{') /*}*/ > { > expand_string_message = >- string_sprintf("missing '{' for last arg of %s", name); >+ string_sprintf("missing '{' for last arg of %s", name); /*}*/ > goto EXPAND_FAILED_CURLY; > } > >@@ -6345,13 +6487,10 @@ > condition for real. For EITEM_MAP and EITEM_REDUCE, do the same, using > the normal internal expansion function. */ > >- if (item_type == EITEM_FILTER) >- { >- if ((temp = eval_condition(expr, &resetok, NULL))) >- s = temp; >- } >- else >+ if (item_type != EITEM_FILTER) > temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok); >+ else >+ if ((temp = eval_condition(expr, &resetok, NULL))) s = temp; > > if (!temp) > { >@@ -6360,18 +6499,18 @@ > goto EXPAND_FAILED; > } > >- Uskip_whitespace(&s); >+ Uskip_whitespace(&s); /*{{{*/ > if (*s++ != '}') >- { /*{*/ >+ { > expand_string_message = string_sprintf("missing } at end of condition " > "or expression inside \"%s\"; could be an unquoted } in the content", > name); > goto EXPAND_FAILED; > } > >- Uskip_whitespace(&s); /*{*/ >+ Uskip_whitespace(&s); /*{{*/ > if (*s++ != '}') >- { /*{*/ >+ { > expand_string_message = string_sprintf("missing } at end of \"%s\"", > name); > goto EXPAND_FAILED; >@@ -6434,6 +6573,9 @@ > item of the output list, add in a space if the new item begins with the > separator character, or is an empty string. */ > >+/*XXX is there not a standard support function for this, appending to a list? */ >+/* yes, string_append_listele(), but it depends on lack of text before the list */ >+ > if ( yield && yield->ptr != save_ptr > && (temp[0] == *outsep || temp[0] == 0)) > yield = string_catn(yield, US" ", 1); >@@ -6451,7 +6593,7 @@ > too many; backup and end the loop. Otherwise arrange to double the > separator. */ > >- if (temp[seglen] == '\0') { yield->ptr--; break; } >+ if (!temp[seglen]) { yield->ptr--; break; } > yield = string_catn(yield, outsep, 1); > temp += seglen + 1; > } >@@ -6480,28 +6622,27 @@ > /* Restore preserved $item */ > > iterate_item = save_iterate_item; >- continue; >+ if (skipping) continue; >+ break; > } > > case EITEM_SORT: > { >- int cond_type; >- int sep = 0; >- const uschar *srclist, *cmp, *xtract; >+ int sep = 0, cond_type; >+ const uschar * srclist, * cmp, * xtract; > uschar * opname, * srcitem; >- const uschar *dstlist = NULL, *dstkeylist = NULL; >- uschar * tmp; >- uschar *save_iterate_item = iterate_item; >+ const uschar * dstlist = NULL, * dstkeylist = NULL; >+ uschar * tmp, * save_iterate_item = iterate_item; > > Uskip_whitespace(&s); >- if (*s++ != '{') >+ if (*s++ != '{') /*}*/ > { > expand_string_message = US"missing '{' for list arg of sort"; >- goto EXPAND_FAILED_CURLY; >+ goto EXPAND_FAILED_CURLY; /*}*/ > } > > srclist = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok); >- if (!srclist) goto EXPAND_FAILED; >+ if (!srclist) goto EXPAND_FAILED; /*{{*/ > if (*s++ != '}') > { > expand_string_message = US"missing '}' closing list arg of sort"; >@@ -6509,14 +6650,14 @@ > } > > Uskip_whitespace(&s); >- if (*s++ != '{') >+ if (*s++ != '{') /*}*/ > { > expand_string_message = US"missing '{' for comparator arg of sort"; >- goto EXPAND_FAILED_CURLY; >+ goto EXPAND_FAILED_CURLY; /*}*/ > } > > cmp = expand_string_internal(s, TRUE, &s, skipping, FALSE, &resetok); >- if (!cmp) goto EXPAND_FAILED; >+ if (!cmp) goto EXPAND_FAILED; /*{{*/ > if (*s++ != '}') > { > expand_string_message = US"missing '}' closing comparator arg of sort"; >@@ -6543,25 +6684,25 @@ > } > > Uskip_whitespace(&s); >- if (*s++ != '{') >+ if (*s++ != '{') /*}*/ > { > expand_string_message = US"missing '{' for extractor arg of sort"; >- goto EXPAND_FAILED_CURLY; >+ goto EXPAND_FAILED_CURLY; /*}*/ > } > > xtract = s; > if (!(tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok))) > goto EXPAND_FAILED; > xtract = string_copyn(xtract, s - xtract); >- >+ /*{{*/ > if (*s++ != '}') > { > expand_string_message = US"missing '}' closing extractor arg of sort"; > goto EXPAND_FAILED_CURLY; > } >- /*{*/ >+ /*{{*/ > if (*s++ != '}') >- { /*{*/ >+ { > expand_string_message = US"missing } at end of \"sort\""; > goto EXPAND_FAILED; > } >@@ -6571,8 +6712,7 @@ > while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0))) > { > uschar * srcfield, * dstitem; >- gstring * newlist = NULL; >- gstring * newkeylist = NULL; >+ gstring * newlist = NULL, * newkeylist = NULL; > > DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, srcitem); > >@@ -6596,7 +6736,7 @@ > > /* field for comparison */ > if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0))) >- goto sort_mismatch; >+ goto SORT_MISMATCH; > > /* String-comparator names start with a letter; numeric names do not */ > >@@ -6617,7 +6757,7 @@ > while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0))) > { > if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0))) >- goto sort_mismatch; >+ goto SORT_MISMATCH; > newlist = string_append_listele(newlist, sep, dstitem); > newkeylist = string_append_listele(newkeylist, sep, dstfield); > } >@@ -6648,9 +6788,9 @@ > > /* Restore preserved $item */ > iterate_item = save_iterate_item; >- continue; >+ break; > >- sort_mismatch: >+ SORT_MISMATCH: > expand_string_message = US"Internal error in sort (list mismatch)"; > goto EXPAND_FAILED; > } >@@ -6671,13 +6811,13 @@ > > #else /* EXPAND_DLFUNC */ > { >- tree_node *t; >- exim_dlfunc_t *func; >- uschar *result; >+ tree_node * t; >+ exim_dlfunc_t * func; >+ uschar * result; > int status, argc; >- uschar *argv[EXPAND_DLFUNC_MAX_ARGS + 3]; >+ uschar * argv[EXPAND_DLFUNC_MAX_ARGS + 3]; > >- if ((expand_forbid & RDO_DLFUNC) != 0) >+ if (expand_forbid & RDO_DLFUNC) > { > expand_string_message = > US"dynamically-loaded functions are not permitted"; >@@ -6701,7 +6841,7 @@ > > if (!(t = tree_search(dlobj_anchor, argv[0]))) > { >- void *handle = dlopen(CS argv[0], RTLD_LAZY); >+ void * handle = dlopen(CS argv[0], RTLD_LAZY); > if (!handle) > { > expand_string_message = string_sprintf("dlopen \"%s\" failed: %s", >@@ -6709,7 +6849,7 @@ > log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message); > goto EXPAND_FAILED; > } >- t = store_get_perm(sizeof(tree_node) + Ustrlen(argv[0]), is_tainted(argv[0])); >+ t = store_get_perm(sizeof(tree_node) + Ustrlen(argv[0]), argv[0]); > Ustrcpy(t->name, argv[0]); > t->data.ptr = handle; > (void)tree_insertnode(&dlobj_anchor, t); >@@ -6735,15 +6875,9 @@ > > resetok = FALSE; > result = NULL; >- for (argc = 0; argv[argc]; argc++); >- status = func(&result, argc - 2, &argv[2]); >- if(status == OK) >- { >- if (!result) result = US""; >- yield = string_cat(yield, result); >- continue; >- } >- else >+ for (argc = 0; argv[argc]; argc++) ; >+ >+ if ((status = func(&result, argc - 2, &argv[2])) != OK) > { > expand_string_message = result ? result : US"(no message)"; > if (status == FAIL_FORCED) >@@ -6753,6 +6887,9 @@ > argv[0], argv[1], status, expand_string_message); > goto EXPAND_FAILED; > } >+ >+ if (result) yield = string_cat(yield, result); >+ break; > } > #endif /* EXPAND_DLFUNC */ > >@@ -6765,10 +6902,10 @@ > goto EXPAND_FAILED; > > key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); >- if (!key) goto EXPAND_FAILED; /*{*/ >+ if (!key) goto EXPAND_FAILED; /*{{*/ > if (*s++ != '}') > { >- expand_string_message = US"missing '{' for name arg of env"; >+ expand_string_message = US"missing '}' for name arg of env"; > goto EXPAND_FAILED_CURLY; > } > >@@ -6786,15 +6923,18 @@ > case 1: goto EXPAND_FAILED; /* when all is well, the */ > case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ > } >- continue; >+ if (skipping) continue; >+ break; > } > >-#ifdef EXPERIMENTAL_SRS_NATIVE >+#ifdef SUPPORT_SRS > case EITEM_SRS_ENCODE: > /* ${srs_encode {secret} {return_path} {orig_domain}} */ > { > uschar * sub[3]; > uschar cksum[4]; >+ gstring * g = NULL; >+ BOOL quoted = FALSE; > > switch (read_subs(sub, 3, 3, CUSS &s, skipping, TRUE, name, &resetok)) > { >@@ -6802,49 +6942,93 @@ > case 2: > case 3: goto EXPAND_FAILED; > } >+ if (skipping) continue; > >- yield = string_catn(yield, US"SRS0=", 5); >+ if (sub[1] && *(sub[1])) >+ { >+ g = string_catn(g, US"SRS0=", 5); > >- /* ${l_4:${hmac{md5}{SRS_SECRET}{${lc:$return_path}}}}= */ >- hmac_md5(sub[0], string_copylc(sub[1]), cksum, sizeof(cksum)); >- yield = string_catn(yield, cksum, sizeof(cksum)); >- yield = string_catn(yield, US"=", 1); >+ /* ${l_4:${hmac{md5}{SRS_SECRET}{${lc:$return_path}}}}= */ >+ hmac_md5(sub[0], string_copylc(sub[1]), cksum, sizeof(cksum)); >+ g = string_catn(g, cksum, sizeof(cksum)); >+ g = string_catn(g, US"=", 1); > >- /* ${base32:${eval:$tod_epoch/86400&0x3ff}}= */ >- { >- struct timeval now; >- unsigned long i; >- gstring * g = NULL; >+ /* ${base32:${eval:$tod_epoch/86400&0x3ff}}= */ >+ { >+ struct timeval now; >+ unsigned long i; >+ gstring * h = NULL; > >- gettimeofday(&now, NULL); >- for (unsigned long i = (now.tv_sec / 86400) & 0x3ff; i; i >>= 5) >- g = string_catn(g, &base32_chars[i & 0x1f], 1); >- if (g) while (g->ptr > 0) >- yield = string_catn(yield, &g->s[--g->ptr], 1); >- } >- yield = string_catn(yield, US"=", 1); >+ gettimeofday(&now, NULL); >+ for (unsigned long i = (now.tv_sec / 86400) & 0x3ff; i; i >>= 5) >+ h = string_catn(h, &base32_chars[i & 0x1f], 1); >+ if (h) while (h->ptr > 0) >+ g = string_catn(g, &h->s[--h->ptr], 1); >+ } >+ g = string_catn(g, US"=", 1); > >- /* ${domain:$return_path}=${local_part:$return_path} */ >- { >- int start, end, domain; >- uschar * t = parse_extract_address(sub[1], &expand_string_message, >- &start, &end, &domain, FALSE); >- if (!t) >- goto EXPAND_FAILED; >+ /* ${domain:$return_path}=${local_part:$return_path} */ >+ { >+ int start, end, domain; >+ uschar * t = parse_extract_address(sub[1], &expand_string_message, >+ &start, &end, &domain, FALSE); >+ uschar * s; > >- if (domain > 0) yield = string_cat(yield, t + domain); >- yield = string_catn(yield, US"=", 1); >- yield = domain > 0 >- ? string_catn(yield, t, domain - 1) : string_cat(yield, t); >- } >+ if (!t) >+ goto EXPAND_FAILED; > >- /* @$original_domain */ >- yield = string_catn(yield, US"@", 1); >- yield = string_cat(yield, sub[2]); >- continue; >+ if (domain > 0) g = string_cat(g, t + domain); >+ g = string_catn(g, US"=", 1); >+ >+ s = domain > 0 ? string_copyn(t, domain - 1) : t; >+ if ((quoted = Ustrchr(s, '"') != NULL)) >+ { >+ gstring * h = NULL; >+ DEBUG(D_expand) debug_printf_indent("auto-quoting local part\n"); >+ while (*s) /* de-quote */ >+ { >+ while (*s && *s != '"') h = string_catn(h, s++, 1); >+ if (*s) s++; >+ while (*s && *s != '"') h = string_catn(h, s++, 1); >+ if (*s) s++; >+ } >+ gstring_release_unused(h); >+ s = string_from_gstring(h); >+ } >+ g = string_cat(g, s); >+ } >+ >+ /* Assume that if the original local_part had quotes >+ it was for good reason */ >+ >+ if (quoted) yield = string_catn(yield, US"\"", 1); >+ yield = string_catn(yield, g->s, g->ptr); >+ if (quoted) yield = string_catn(yield, US"\"", 1); >+ >+ /* @$original_domain */ >+ yield = string_catn(yield, US"@", 1); >+ yield = string_cat(yield, sub[2]); >+ } >+ else >+ DEBUG(D_expand) debug_printf_indent("null return_path for srs-encode\n"); >+ >+ break; > } >-#endif /*EXPERIMENTAL_SRS_NATIVE*/ >+#endif /*SUPPORT_SRS*/ >+ >+ default: >+ goto NOT_ITEM; > } /* EITEM_* switch */ >+ /*NOTREACHED*/ >+ >+ DEBUG(D_expand) >+ if (yield && (start > 0 || *s)) /* only if not the sole expansion of the line */ >+ debug_expansion_interim(US"item-res", >+ yield->s + start, yield->ptr - start, skipping); >+ continue; >+ >+NOT_ITEM: ; >+ } > > /* Control reaches here if the name is not recognized as one of the more > complicated expansion items. Check for the "operator" syntax (name terminated >@@ -6854,10 +7038,9 @@ > if (*s == ':') > { > int c; >- uschar *arg = NULL; >- uschar *sub; >+ uschar * arg = NULL, * sub; > #ifndef DISABLE_TLS >- var_entry *vp = NULL; >+ var_entry * vp = NULL; > #endif > > /* Owing to an historical mis-design, an underscore may be part of the >@@ -6891,7 +7074,7 @@ > FALSE, &resetok); > if (!sub) goto EXPAND_FAILED; /*{*/ > if (*s1 != '}') >- { >+ { /*{*/ > expand_string_message = > string_sprintf("missing '}' closing cert arg of %s", name); > goto EXPAND_FAILED_CURLY; >@@ -6920,129 +7103,118 @@ > > if (skipping && c >= 0) continue; > >- /* Otherwise, switch on the operator type */ >+ /* Otherwise, switch on the operator type. After handling go back >+ to the main loop top. */ > >- switch(c) >+ { >+ int start = yield->ptr; >+ switch(c) > { > case EOP_BASE32: > { >- uschar *t; >- unsigned long int n = Ustrtoul(sub, &t, 10); >+ uschar *t; >+ unsigned long int n = Ustrtoul(sub, &t, 10); > gstring * g = NULL; > >- if (*t != 0) >- { >- expand_string_message = string_sprintf("argument for base32 " >- "operator is \"%s\", which is not a decimal number", sub); >- goto EXPAND_FAILED; >- } >+ if (*t != 0) >+ { >+ expand_string_message = string_sprintf("argument for base32 " >+ "operator is \"%s\", which is not a decimal number", sub); >+ goto EXPAND_FAILED; >+ } > for ( ; n; n >>= 5) > g = string_catn(g, &base32_chars[n & 0x1f], 1); > > if (g) while (g->ptr > 0) yield = string_catn(yield, &g->s[--g->ptr], 1); >- continue; >+ break; > } > > case EOP_BASE32D: >- { >- uschar *tt = sub; >- unsigned long int n = 0; >- while (*tt) >- { >- uschar * t = Ustrchr(base32_chars, *tt++); >- if (!t) >- { >- expand_string_message = string_sprintf("argument for base32d " >- "operator is \"%s\", which is not a base 32 number", sub); >- goto EXPAND_FAILED; >- } >- n = n * 32 + (t - base32_chars); >- } >- yield = string_fmt_append(yield, "%ld", n); >- continue; >- } >+ { >+ uschar *tt = sub; >+ unsigned long int n = 0; >+ while (*tt) >+ { >+ uschar * t = Ustrchr(base32_chars, *tt++); >+ if (!t) >+ { >+ expand_string_message = string_sprintf("argument for base32d " >+ "operator is \"%s\", which is not a base 32 number", sub); >+ goto EXPAND_FAILED; >+ } >+ n = n * 32 + (t - base32_chars); >+ } >+ yield = string_fmt_append(yield, "%ld", n); >+ break; >+ } > > case EOP_BASE62: >- { >- uschar *t; >- unsigned long int n = Ustrtoul(sub, &t, 10); >- if (*t != 0) >- { >- expand_string_message = string_sprintf("argument for base62 " >- "operator is \"%s\", which is not a decimal number", sub); >- goto EXPAND_FAILED; >- } >- yield = string_cat(yield, string_base62(n)); >- continue; >- } >+ { >+ uschar *t; >+ unsigned long int n = Ustrtoul(sub, &t, 10); >+ if (*t != 0) >+ { >+ expand_string_message = string_sprintf("argument for base62 " >+ "operator is \"%s\", which is not a decimal number", sub); >+ goto EXPAND_FAILED; >+ } >+ yield = string_cat(yield, string_base62(n)); >+ break; >+ } > > /* Note that for Darwin and Cygwin, BASE_62 actually has the value 36 */ > > case EOP_BASE62D: >- { >- uschar *tt = sub; >- unsigned long int n = 0; >- while (*tt != 0) >- { >- uschar *t = Ustrchr(base62_chars, *tt++); >- if (!t) >- { >- expand_string_message = string_sprintf("argument for base62d " >- "operator is \"%s\", which is not a base %d number", sub, >- BASE_62); >- goto EXPAND_FAILED; >- } >- n = n * BASE_62 + (t - base62_chars); >- } >- yield = string_fmt_append(yield, "%ld", n); >- continue; >- } >- >- case EOP_BLESS: >- /* This is purely for the convenience of the test harness. Do not enable >- it otherwise as it defeats the taint-checking security. */ >- >- if (f.running_in_test_harness) >- yield = string_cat(yield, is_tainted(sub) >- ? string_copy_taint(sub, FALSE) : sub); >- else >+ { >+ uschar *tt = sub; >+ unsigned long int n = 0; >+ while (*tt != 0) > { >- DEBUG(D_expand) debug_printf_indent("bless operator not supported\n"); >- yield = string_cat(yield, sub); >+ uschar *t = Ustrchr(base62_chars, *tt++); >+ if (!t) >+ { >+ expand_string_message = string_sprintf("argument for base62d " >+ "operator is \"%s\", which is not a base %d number", sub, >+ BASE_62); >+ goto EXPAND_FAILED; >+ } >+ n = n * BASE_62 + (t - base62_chars); > } >- continue; >+ yield = string_fmt_append(yield, "%ld", n); >+ break; >+ } > > case EOP_EXPAND: >- { >- uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE, &resetok); >- if (!expanded) >- { >- expand_string_message = >- string_sprintf("internal expansion of \"%s\" failed: %s", sub, >- expand_string_message); >- goto EXPAND_FAILED; >- } >- yield = string_cat(yield, expanded); >- continue; >- } >+ { >+ uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE, &resetok); >+ if (!expanded) >+ { >+ expand_string_message = >+ string_sprintf("internal expansion of \"%s\" failed: %s", sub, >+ expand_string_message); >+ goto EXPAND_FAILED; >+ } >+ yield = string_cat(yield, expanded); >+ break; >+ } > > case EOP_LC: >- { >- int count = 0; >- uschar *t = sub - 1; >- while (*(++t) != 0) { *t = tolower(*t); count++; } >- yield = string_catn(yield, sub, count); >- continue; >- } >+ { >+ int count = 0; >+ uschar *t = sub - 1; >+ while (*(++t) != 0) { *t = tolower(*t); count++; } >+ yield = string_catn(yield, sub, count); >+ break; >+ } > > case EOP_UC: >- { >- int count = 0; >- uschar *t = sub - 1; >- while (*(++t) != 0) { *t = toupper(*t); count++; } >- yield = string_catn(yield, sub, count); >- continue; >- } >+ { >+ int count = 0; >+ uschar *t = sub - 1; >+ while (*(++t) != 0) { *t = toupper(*t); count++; } >+ yield = string_catn(yield, sub, count); >+ break; >+ } > > case EOP_MD5: > #ifndef DISABLE_TLS >@@ -7061,7 +7233,7 @@ > for (int j = 0; j < 16; j++) > yield = string_fmt_append(yield, "%02x", digest[j]); > } >- continue; >+ break; > > case EOP_SHA1: > #ifndef DISABLE_TLS >@@ -7080,7 +7252,7 @@ > for (int j = 0; j < 20; j++) > yield = string_fmt_append(yield, "%02X", digest[j]); > } >- continue; >+ break; > > case EOP_SHA2: > case EOP_SHA256: >@@ -7106,7 +7278,7 @@ > goto EXPAND_FAILED; > } > >- exim_sha_update(&h, sub, Ustrlen(sub)); >+ exim_sha_update_string(&h, sub); > exim_sha_finish(&h, &b); > while (b.len-- > 0) > yield = string_fmt_append(yield, "%02X", *b.data++); >@@ -7114,7 +7286,7 @@ > #else > expand_string_message = US"sha256 only supported with TLS"; > #endif >- continue; >+ break; > > case EOP_SHA3: > #ifdef EXIM_HAVE_SHA3 >@@ -7134,12 +7306,12 @@ > goto EXPAND_FAILED; > } > >- exim_sha_update(&h, sub, Ustrlen(sub)); >+ exim_sha_update_string(&h, sub); > exim_sha_finish(&h, &b); > while (b.len-- > 0) > yield = string_fmt_append(yield, "%02X", *b.data++); > } >- continue; >+ break; > #else > expand_string_message = US"sha3 only supported with GnuTLS 3.5.0 + or OpenSSL 1.1.1 +"; > goto EXPAND_FAILED; >@@ -7148,160 +7320,85 @@ > /* Convert hex encoding to base64 encoding */ > > case EOP_HEX2B64: >- { >- int c = 0; >- int b = -1; >- uschar *in = sub; >- uschar *out = sub; >- uschar *enc; >+ { >+ int c = 0; >+ int b = -1; >+ uschar *in = sub; >+ uschar *out = sub; >+ uschar *enc; > >- for (enc = sub; *enc; enc++) >- { >- if (!isxdigit(*enc)) >- { >- expand_string_message = string_sprintf("\"%s\" is not a hex " >- "string", sub); >- goto EXPAND_FAILED; >- } >- c++; >- } >+ for (enc = sub; *enc; enc++) >+ { >+ if (!isxdigit(*enc)) >+ { >+ expand_string_message = string_sprintf("\"%s\" is not a hex " >+ "string", sub); >+ goto EXPAND_FAILED; >+ } >+ c++; >+ } > >- if ((c & 1) != 0) >- { >- expand_string_message = string_sprintf("\"%s\" contains an odd " >- "number of characters", sub); >- goto EXPAND_FAILED; >- } >+ if ((c & 1) != 0) >+ { >+ expand_string_message = string_sprintf("\"%s\" contains an odd " >+ "number of characters", sub); >+ goto EXPAND_FAILED; >+ } > >- while ((c = *in++) != 0) >- { >- if (isdigit(c)) c -= '0'; >- else c = toupper(c) - 'A' + 10; >- if (b == -1) >- b = c << 4; >- else >- { >- *out++ = b | c; >- b = -1; >- } >- } >+ while ((c = *in++) != 0) >+ { >+ if (isdigit(c)) c -= '0'; >+ else c = toupper(c) - 'A' + 10; >+ if (b == -1) >+ b = c << 4; >+ else >+ { >+ *out++ = b | c; >+ b = -1; >+ } >+ } > >- enc = b64encode(CUS sub, out - sub); >- yield = string_cat(yield, enc); >- continue; >- } >+ enc = b64encode(CUS sub, out - sub); >+ yield = string_cat(yield, enc); >+ break; >+ } > > /* Convert octets outside 0x21..0x7E to \xXX form */ > > case EOP_HEXQUOTE: > { >- uschar *t = sub - 1; >- while (*(++t) != 0) >- { >- if (*t < 0x21 || 0x7E < *t) >- yield = string_fmt_append(yield, "\\x%02x", *t); >+ uschar *t = sub - 1; >+ while (*(++t) != 0) >+ { >+ if (*t < 0x21 || 0x7E < *t) >+ yield = string_fmt_append(yield, "\\x%02x", *t); > else > yield = string_catn(yield, t, 1); >- } >- continue; >+ } >+ break; > } > > /* count the number of list elements */ > > case EOP_LISTCOUNT: >- { >- int cnt = 0; >- int sep = 0; >+ { >+ int cnt = 0, sep = 0; >+ uschar * buf = store_get(2, sub); > >- while (string_nextinlist(CUSS &sub, &sep, NULL, 0)) cnt++; >+ while (string_nextinlist(CUSS &sub, &sep, buf, 1)) cnt++; > yield = string_fmt_append(yield, "%d", cnt); >- continue; >- } >+ break; >+ } > > /* expand a named list given the name */ > /* handles nested named lists; requotes as colon-sep list */ > > case EOP_LISTNAMED: >- { >- tree_node *t = NULL; >- const uschar * list; >- int sep = 0; >- uschar * item; >- uschar * suffix = US""; >- BOOL needsep = FALSE; >- uschar buffer[256]; >- >- if (*sub == '+') sub++; >- if (!arg) /* no-argument version */ >- { >- if (!(t = tree_search(addresslist_anchor, sub)) && >- !(t = tree_search(domainlist_anchor, sub)) && >- !(t = tree_search(hostlist_anchor, sub))) >- t = tree_search(localpartlist_anchor, sub); >- } >- else switch(*arg) /* specific list-type version */ >- { >- case 'a': t = tree_search(addresslist_anchor, sub); suffix = US"_a"; break; >- case 'd': t = tree_search(domainlist_anchor, sub); suffix = US"_d"; break; >- case 'h': t = tree_search(hostlist_anchor, sub); suffix = US"_h"; break; >- case 'l': t = tree_search(localpartlist_anchor, sub); suffix = US"_l"; break; >- default: >- expand_string_message = US"bad suffix on \"list\" operator"; >- goto EXPAND_FAILED; >- } >- >- if(!t) >- { >- expand_string_message = string_sprintf("\"%s\" is not a %snamed list", >- sub, !arg?"" >- : *arg=='a'?"address " >- : *arg=='d'?"domain " >- : *arg=='h'?"host " >- : *arg=='l'?"localpart " >- : 0); >+ expand_string_message = NULL; >+ yield = expand_listnamed(yield, sub, arg); >+ if (expand_string_message) > goto EXPAND_FAILED; >- } >- >- list = ((namedlist_block *)(t->data.ptr))->string; >- >- while ((item = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))) >- { >- uschar * buf = US" : "; >- if (needsep) >- yield = string_catn(yield, buf, 3); >- else >- needsep = TRUE; >- >- if (*item == '+') /* list item is itself a named list */ >- { >- uschar * sub = string_sprintf("${listnamed%s:%s}", suffix, item); >- item = expand_string_internal(sub, FALSE, NULL, FALSE, TRUE, &resetok); >- } >- else if (sep != ':') /* item from non-colon-sep list, re-quote for colon list-separator */ >- { >- char * cp; >- char tok[3]; >- tok[0] = sep; tok[1] = ':'; tok[2] = 0; >- while ((cp= strpbrk(CCS item, tok))) >- { >- yield = string_catn(yield, item, cp - CS item); >- if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */ >- { >- yield = string_catn(yield, US"::", 2); >- item = US cp; >- } >- else /* sep in item; should already be doubled; emit once */ >- { >- yield = string_catn(yield, US tok, 1); >- if (*cp == sep) cp++; >- item = US cp; >- } >- } >- } >- yield = string_cat(yield, item); >- } >- continue; >- } >+ break; > > /* quote a list-item for the given list-separator */ > >@@ -7309,54 +7406,65 @@ > ${mask:131.111.10.206/28} is 131.111.10.192/28. */ > > case EOP_MASK: >- { >- int count; >- uschar *endptr; >- int binary[4]; >- int mask, maskoffset; >- int type = string_is_ip_address(sub, &maskoffset); >- uschar buffer[64]; >+ { >+ int count; >+ uschar *endptr; >+ int binary[4]; >+ int type, mask, maskoffset; >+ BOOL normalised; >+ uschar buffer[64]; > >- if (type == 0) >- { >- expand_string_message = string_sprintf("\"%s\" is not an IP address", >- sub); >- goto EXPAND_FAILED; >- } >+ if ((type = string_is_ip_address(sub, &maskoffset)) == 0) >+ { >+ expand_string_message = string_sprintf("\"%s\" is not an IP address", >+ sub); >+ goto EXPAND_FAILED; >+ } > >- if (maskoffset == 0) >- { >- expand_string_message = string_sprintf("missing mask value in \"%s\"", >- sub); >- goto EXPAND_FAILED; >- } >+ if (maskoffset == 0) >+ { >+ expand_string_message = string_sprintf("missing mask value in \"%s\"", >+ sub); >+ goto EXPAND_FAILED; >+ } > >- mask = Ustrtol(sub + maskoffset + 1, &endptr, 10); >+ mask = Ustrtol(sub + maskoffset + 1, &endptr, 10); > >- if (*endptr != 0 || mask < 0 || mask > ((type == 4)? 32 : 128)) >- { >- expand_string_message = string_sprintf("mask value too big in \"%s\"", >- sub); >- goto EXPAND_FAILED; >- } >+ if (*endptr || mask < 0 || mask > (type == 4 ? 32 : 128)) >+ { >+ expand_string_message = string_sprintf("mask value too big in \"%s\"", >+ sub); >+ goto EXPAND_FAILED; >+ } > >- /* Convert the address to binary integer(s) and apply the mask */ >+ /* If an optional 'n' was given, ipv6 gets normalised output: >+ colons rather than dots, and zero-compressed. */ > >- sub[maskoffset] = 0; >- count = host_aton(sub, binary); >- host_mask(count, binary, mask); >+ normalised = arg && *arg == 'n'; > >- /* Convert to masked textual format and add to output. */ >+ /* Convert the address to binary integer(s) and apply the mask */ > >- yield = string_catn(yield, buffer, >- host_nmtoa(count, binary, mask, buffer, '.')); >- continue; >- } >+ sub[maskoffset] = 0; >+ count = host_aton(sub, binary); >+ host_mask(count, binary, mask); >+ >+ /* Convert to masked textual format and add to output. */ >+ >+ if (type == 4 || !normalised) >+ yield = string_catn(yield, buffer, >+ host_nmtoa(count, binary, mask, buffer, '.')); >+ else >+ { >+ ipv6_nmtoa(binary, buffer); >+ yield = string_fmt_append(yield, "%s/%d", buffer, mask); >+ } >+ break; >+ } > > case EOP_IPV6NORM: > case EOP_IPV6DENORM: > { >- int type = string_is_ip_address(sub, NULL); >+ int type = string_is_ip_address(sub, NULL); > int binary[4]; > uschar buffer[44]; > >@@ -7382,94 +7490,94 @@ > ? ipv6_nmtoa(binary, buffer) > : host_nmtoa(4, binary, -1, buffer, ':') > ); >- continue; >+ break; > } > > case EOP_ADDRESS: > case EOP_LOCAL_PART: > case EOP_DOMAIN: >- { >- uschar * error; >- int start, end, domain; >- uschar * t = parse_extract_address(sub, &error, &start, &end, &domain, >- FALSE); >- if (t) >+ { >+ uschar * error; >+ int start, end, domain; >+ uschar * t = parse_extract_address(sub, &error, &start, &end, &domain, >+ FALSE); >+ if (t) > if (c != EOP_DOMAIN) > yield = c == EOP_LOCAL_PART && domain > 0 > ? string_catn(yield, t, domain - 1) > : string_cat(yield, t); > else if (domain > 0) > yield = string_cat(yield, t + domain); >- continue; >- } >+ break; >+ } > > case EOP_ADDRESSES: >- { >- uschar outsep[2] = { ':', '\0' }; >- uschar *address, *error; >- int save_ptr = gstring_length(yield); >- int start, end, domain; /* Not really used */ >+ { >+ uschar outsep[2] = { ':', '\0' }; >+ uschar *address, *error; >+ int save_ptr = gstring_length(yield); >+ int start, end, domain; /* Not really used */ > > if (Uskip_whitespace(&sub) == '>') >- if (*outsep = *++sub) ++sub; >- else >+ if (*outsep = *++sub) ++sub; >+ else > { >- expand_string_message = string_sprintf("output separator " >- "missing in expanding ${addresses:%s}", --sub); >- goto EXPAND_FAILED; >- } >- f.parse_allow_group = TRUE; >+ expand_string_message = string_sprintf("output separator " >+ "missing in expanding ${addresses:%s}", --sub); >+ goto EXPAND_FAILED; >+ } >+ f.parse_allow_group = TRUE; > >- for (;;) >- { >- uschar * p = parse_find_address_end(sub, FALSE); >- uschar saveend = *p; >- *p = '\0'; >- address = parse_extract_address(sub, &error, &start, &end, &domain, >- FALSE); >- *p = saveend; >- >- /* Add the address to the output list that we are building. This is >- done in chunks by searching for the separator character. At the >- start, unless we are dealing with the first address of the output >- list, add in a space if the new address begins with the separator >- character, or is an empty string. */ >+ for (;;) >+ { >+ uschar * p = parse_find_address_end(sub, FALSE); >+ uschar saveend = *p; >+ *p = '\0'; >+ address = parse_extract_address(sub, &error, &start, &end, &domain, >+ FALSE); >+ *p = saveend; >+ >+ /* Add the address to the output list that we are building. This is >+ done in chunks by searching for the separator character. At the >+ start, unless we are dealing with the first address of the output >+ list, add in a space if the new address begins with the separator >+ character, or is an empty string. */ > >- if (address) >- { >- if (yield && yield->ptr != save_ptr && address[0] == *outsep) >- yield = string_catn(yield, US" ", 1); >+ if (address) >+ { >+ if (yield && yield->ptr != save_ptr && address[0] == *outsep) >+ yield = string_catn(yield, US" ", 1); > >- for (;;) >- { >- size_t seglen = Ustrcspn(address, outsep); >- yield = string_catn(yield, address, seglen + 1); >- >- /* If we got to the end of the string we output one character >- too many. */ >- >- if (address[seglen] == '\0') { yield->ptr--; break; } >- yield = string_catn(yield, outsep, 1); >- address += seglen + 1; >- } >+ for (;;) >+ { >+ size_t seglen = Ustrcspn(address, outsep); >+ yield = string_catn(yield, address, seglen + 1); > >- /* Output a separator after the string: we will remove the >- redundant final one at the end. */ >+ /* If we got to the end of the string we output one character >+ too many. */ > >- yield = string_catn(yield, outsep, 1); >- } >+ if (address[seglen] == '\0') { yield->ptr--; break; } >+ yield = string_catn(yield, outsep, 1); >+ address += seglen + 1; >+ } > >- if (saveend == '\0') break; >- sub = p + 1; >- } >+ /* Output a separator after the string: we will remove the >+ redundant final one at the end. */ > >- /* If we have generated anything, remove the redundant final >- separator. */ >+ yield = string_catn(yield, outsep, 1); >+ } > >- if (yield && yield->ptr != save_ptr) yield->ptr--; >- f.parse_allow_group = FALSE; >- continue; >- } >+ if (saveend == '\0') break; >+ sub = p + 1; >+ } >+ >+ /* If we have generated anything, remove the redundant final >+ separator. */ >+ >+ if (yield && yield->ptr != save_ptr) yield->ptr--; >+ f.parse_allow_group = FALSE; >+ break; >+ } > > > /* quote puts a string in quotes if it is empty or contains anything >@@ -7483,586 +7591,615 @@ > > case EOP_QUOTE: > case EOP_QUOTE_LOCAL_PART: >- if (!arg) >- { >- BOOL needs_quote = (!*sub); /* TRUE for empty string */ >- uschar *t = sub - 1; >- >- if (c == EOP_QUOTE) >- { >- while (!needs_quote && *(++t) != 0) >- needs_quote = !isalnum(*t) && !strchr("_-.", *t); >- } >- else /* EOP_QUOTE_LOCAL_PART */ >- { >- while (!needs_quote && *(++t) != 0) >- needs_quote = !isalnum(*t) && >- strchr("!#$%&'*+-/=?^_`{|}~", *t) == NULL && >- (*t != '.' || t == sub || t[1] == 0); >- } >+ if (!arg) >+ { >+ BOOL needs_quote = (!*sub); /* TRUE for empty string */ >+ uschar *t = sub - 1; > >- if (needs_quote) >- { >- yield = string_catn(yield, US"\"", 1); >- t = sub - 1; >- while (*(++t) != 0) >- { >- if (*t == '\n') >- yield = string_catn(yield, US"\\n", 2); >- else if (*t == '\r') >- yield = string_catn(yield, US"\\r", 2); >- else >- { >- if (*t == '\\' || *t == '"') >- yield = string_catn(yield, US"\\", 1); >- yield = string_catn(yield, t, 1); >- } >- } >- yield = string_catn(yield, US"\"", 1); >- } >- else yield = string_cat(yield, sub); >- continue; >- } >+ if (c == EOP_QUOTE) >+ while (!needs_quote && *++t) >+ needs_quote = !isalnum(*t) && !strchr("_-.", *t); >+ >+ else /* EOP_QUOTE_LOCAL_PART */ >+ while (!needs_quote && *++t) >+ needs_quote = !isalnum(*t) >+ && strchr("!#$%&'*+-/=?^_`{|}~", *t) == NULL >+ && (*t != '.' || t == sub || !t[1]); > >- /* quote_lookuptype does lookup-specific quoting */ >+ if (needs_quote) >+ { >+ yield = string_catn(yield, US"\"", 1); >+ t = sub - 1; >+ while (*++t) >+ if (*t == '\n') >+ yield = string_catn(yield, US"\\n", 2); >+ else if (*t == '\r') >+ yield = string_catn(yield, US"\\r", 2); >+ else >+ { >+ if (*t == '\\' || *t == '"') >+ yield = string_catn(yield, US"\\", 1); >+ yield = string_catn(yield, t, 1); >+ } >+ yield = string_catn(yield, US"\"", 1); >+ } >+ else >+ yield = string_cat(yield, sub); >+ break; >+ } > >- else >- { >- int n; >- uschar *opt = Ustrchr(arg, '_'); >+ /* quote_lookuptype does lookup-specific quoting */ > >- if (opt) *opt++ = 0; >+ else >+ { >+ int n; >+ uschar * opt = Ustrchr(arg, '_'); > >- if ((n = search_findtype(arg, Ustrlen(arg))) < 0) >- { >- expand_string_message = search_error_message; >- goto EXPAND_FAILED; >- } >+ if (opt) *opt++ = 0; > >- if (lookup_list[n]->quote) >- sub = (lookup_list[n]->quote)(sub, opt); >- else if (opt) >- sub = NULL; >+ if ((n = search_findtype(arg, Ustrlen(arg))) < 0) >+ { >+ expand_string_message = search_error_message; >+ goto EXPAND_FAILED; >+ } > >- if (!sub) >- { >- expand_string_message = string_sprintf( >- "\"%s\" unrecognized after \"${quote_%s\"", >- opt, arg); >- goto EXPAND_FAILED; >- } >+ if (lookup_list[n]->quote) >+ sub = (lookup_list[n]->quote)(sub, opt, (unsigned)n); >+ else if (opt) >+ sub = NULL; > >- yield = string_cat(yield, sub); >- continue; >- } >+ if (!sub) >+ { >+ expand_string_message = string_sprintf( >+ "\"%s\" unrecognized after \"${quote_%s\"", /*}*/ >+ opt, arg); >+ goto EXPAND_FAILED; >+ } > >- /* rx quote sticks in \ before any non-alphameric character so that >- the insertion works in a regular expression. */ >+ yield = string_cat(yield, sub); >+ break; >+ } > >- case EOP_RXQUOTE: >- { >- uschar *t = sub - 1; >- while (*(++t) != 0) >- { >- if (!isalnum(*t)) >- yield = string_catn(yield, US"\\", 1); >- yield = string_catn(yield, t, 1); >- } >- continue; >- } >+ /* rx quote sticks in \ before any non-alphameric character so that >+ the insertion works in a regular expression. */ > >- /* RFC 2047 encodes, assuming headers_charset (default ISO 8859-1) as >- prescribed by the RFC, if there are characters that need to be encoded */ >+ case EOP_RXQUOTE: >+ { >+ uschar *t = sub - 1; >+ while (*(++t) != 0) >+ { >+ if (!isalnum(*t)) >+ yield = string_catn(yield, US"\\", 1); >+ yield = string_catn(yield, t, 1); >+ } >+ break; >+ } > >- case EOP_RFC2047: >- yield = string_cat(yield, >- parse_quote_2047(sub, Ustrlen(sub), headers_charset, >- FALSE)); >- continue; >+ /* RFC 2047 encodes, assuming headers_charset (default ISO 8859-1) as >+ prescribed by the RFC, if there are characters that need to be encoded */ > >- /* RFC 2047 decode */ >+ case EOP_RFC2047: >+ yield = string_cat(yield, >+ parse_quote_2047(sub, Ustrlen(sub), headers_charset, >+ FALSE)); >+ break; > >- case EOP_RFC2047D: >- { >- int len; >- uschar *error; >- uschar *decoded = rfc2047_decode(sub, check_rfc2047_length, >- headers_charset, '?', &len, &error); >- if (error) >- { >- expand_string_message = error; >- goto EXPAND_FAILED; >- } >- yield = string_catn(yield, decoded, len); >- continue; >- } >+ /* RFC 2047 decode */ > >- /* from_utf8 converts UTF-8 to 8859-1, turning non-existent chars into >- underscores */ >+ case EOP_RFC2047D: >+ { >+ int len; >+ uschar *error; >+ uschar *decoded = rfc2047_decode(sub, check_rfc2047_length, >+ headers_charset, '?', &len, &error); >+ if (error) >+ { >+ expand_string_message = error; >+ goto EXPAND_FAILED; >+ } >+ yield = string_catn(yield, decoded, len); >+ break; >+ } > >- case EOP_FROM_UTF8: >- { >- uschar * buff = store_get(4, is_tainted(sub)); >- while (*sub) >- { >- int c; >- GETUTF8INC(c, sub); >- if (c > 255) c = '_'; >- buff[0] = c; >- yield = string_catn(yield, buff, 1); >- } >- continue; >- } >+ /* from_utf8 converts UTF-8 to 8859-1, turning non-existent chars into >+ underscores */ > >- /* replace illegal UTF-8 sequences by replacement character */ >+ case EOP_FROM_UTF8: >+ { >+ uschar * buff = store_get(4, sub); >+ while (*sub) >+ { >+ int c; >+ GETUTF8INC(c, sub); >+ if (c > 255) c = '_'; >+ buff[0] = c; >+ yield = string_catn(yield, buff, 1); >+ } >+ break; >+ } > >- #define UTF8_REPLACEMENT_CHAR US"?" >+ /* replace illegal UTF-8 sequences by replacement character */ > >- case EOP_UTF8CLEAN: >- { >- int seq_len = 0, index = 0; >- int bytes_left = 0; >- long codepoint = -1; >- int complete; >- uschar seq_buff[4]; /* accumulate utf-8 here */ >+ #define UTF8_REPLACEMENT_CHAR US"?" > >- /* Manually track tainting, as we deal in individual chars below */ >+ case EOP_UTF8CLEAN: >+ { >+ int seq_len = 0, index = 0; >+ int bytes_left = 0; >+ long codepoint = -1; >+ int complete; >+ uschar seq_buff[4]; /* accumulate utf-8 here */ > >- if (is_tainted(sub)) >- if (yield->s && yield->ptr) >- gstring_rebuffer(yield); >- else >- yield->s = store_get(yield->size = Ustrlen(sub), TRUE); >+ /* Manually track tainting, as we deal in individual chars below */ > >- /* Check the UTF-8, byte-by-byte */ >+ if (!yield->s || !yield->ptr) >+ yield->s = store_get(yield->size = Ustrlen(sub), sub); >+ else if (is_incompatible(yield->s, sub)) >+ gstring_rebuffer(yield, sub); > >- while (*sub) >- { >- complete = 0; >- uschar c = *sub++; >+ /* Check the UTF-8, byte-by-byte */ > >- if (bytes_left) >+ while (*sub) > { >- if ((c & 0xc0) != 0x80) >- /* wrong continuation byte; invalidate all bytes */ >- complete = 1; /* error */ >- else >- { >- codepoint = (codepoint << 6) | (c & 0x3f); >- seq_buff[index++] = c; >- if (--bytes_left == 0) /* codepoint complete */ >- if(codepoint > 0x10FFFF) /* is it too large? */ >- complete = -1; /* error (RFC3629 limit) */ >- else >- { /* finished; output utf-8 sequence */ >- yield = string_catn(yield, seq_buff, seq_len); >- index = 0; >- } >- } >- } >- else /* no bytes left: new sequence */ >- { >- if(!(c & 0x80)) /* 1-byte sequence, US-ASCII, keep it */ >- { >- yield = string_catn(yield, &c, 1); >- continue; >- } >- if((c & 0xe0) == 0xc0) /* 2-byte sequence */ >+ complete = 0; >+ uschar c = *sub++; >+ >+ if (bytes_left) > { >- if(c == 0xc0 || c == 0xc1) /* 0xc0 and 0xc1 are illegal */ >- complete = -1; >+ if ((c & 0xc0) != 0x80) >+ /* wrong continuation byte; invalidate all bytes */ >+ complete = 1; /* error */ > else > { >- bytes_left = 1; >- codepoint = c & 0x1f; >+ codepoint = (codepoint << 6) | (c & 0x3f); >+ seq_buff[index++] = c; >+ if (--bytes_left == 0) /* codepoint complete */ >+ if(codepoint > 0x10FFFF) /* is it too large? */ >+ complete = -1; /* error (RFC3629 limit) */ >+ else >+ { /* finished; output utf-8 sequence */ >+ yield = string_catn(yield, seq_buff, seq_len); >+ index = 0; >+ } > } > } >- else if((c & 0xf0) == 0xe0) /* 3-byte sequence */ >+ else /* no bytes left: new sequence */ > { >- bytes_left = 2; >- codepoint = c & 0x0f; >- } >- else if((c & 0xf8) == 0xf0) /* 4-byte sequence */ >+ if(!(c & 0x80)) /* 1-byte sequence, US-ASCII, keep it */ >+ { >+ yield = string_catn(yield, &c, 1); >+ continue; >+ } >+ if((c & 0xe0) == 0xc0) /* 2-byte sequence */ >+ { >+ if(c == 0xc0 || c == 0xc1) /* 0xc0 and 0xc1 are illegal */ >+ complete = -1; >+ else >+ { >+ bytes_left = 1; >+ codepoint = c & 0x1f; >+ } >+ } >+ else if((c & 0xf0) == 0xe0) /* 3-byte sequence */ >+ { >+ bytes_left = 2; >+ codepoint = c & 0x0f; >+ } >+ else if((c & 0xf8) == 0xf0) /* 4-byte sequence */ >+ { >+ bytes_left = 3; >+ codepoint = c & 0x07; >+ } >+ else /* invalid or too long (RFC3629 allows only 4 bytes) */ >+ complete = -1; >+ >+ seq_buff[index++] = c; >+ seq_len = bytes_left + 1; >+ } /* if(bytes_left) */ >+ >+ if (complete != 0) > { >- bytes_left = 3; >- codepoint = c & 0x07; >+ bytes_left = index = 0; >+ yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1); > } >- else /* invalid or too long (RFC3629 allows only 4 bytes) */ >- complete = -1; >- >- seq_buff[index++] = c; >- seq_len = bytes_left + 1; >- } /* if(bytes_left) */ >+ if ((complete == 1) && ((c & 0x80) == 0)) >+ /* ASCII character follows incomplete sequence */ >+ yield = string_catn(yield, &c, 1); >+ } >+ /* If given a sequence truncated mid-character, we also want to report ? >+ Eg, ${length_1:ãã£ã«} is one byte, not one character, so we expect >+ ${utf8clean:${length_1:ãã£ã«}} to yield '?' */ > >- if (complete != 0) >- { >- bytes_left = index = 0; >+ if (bytes_left != 0) > yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1); >- } >- if ((complete == 1) && ((c & 0x80) == 0)) >- /* ASCII character follows incomplete sequence */ >- yield = string_catn(yield, &c, 1); >- } >- /* If given a sequence truncated mid-character, we also want to report ? >- * Eg, ${length_1:ãã£ã«} is one byte, not one character, so we expect >- * ${utf8clean:${length_1:ãã£ã«}} to yield '?' */ >- if (bytes_left != 0) >- yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1); > >- continue; >- } >+ break; >+ } > > #ifdef SUPPORT_I18N >- case EOP_UTF8_DOMAIN_TO_ALABEL: >- { >- uschar * error = NULL; >- uschar * s = string_domain_utf8_to_alabel(sub, &error); >- if (error) >+ case EOP_UTF8_DOMAIN_TO_ALABEL: > { >- expand_string_message = string_sprintf( >- "error converting utf8 (%s) to alabel: %s", >- string_printing(sub), error); >- goto EXPAND_FAILED; >+ uschar * error = NULL; >+ uschar * s = string_domain_utf8_to_alabel(sub, &error); >+ if (error) >+ { >+ expand_string_message = string_sprintf( >+ "error converting utf8 (%s) to alabel: %s", >+ string_printing(sub), error); >+ goto EXPAND_FAILED; >+ } >+ yield = string_cat(yield, s); >+ break; > } >- yield = string_cat(yield, s); >- continue; >- } > >- case EOP_UTF8_DOMAIN_FROM_ALABEL: >- { >- uschar * error = NULL; >- uschar * s = string_domain_alabel_to_utf8(sub, &error); >- if (error) >+ case EOP_UTF8_DOMAIN_FROM_ALABEL: > { >- expand_string_message = string_sprintf( >- "error converting alabel (%s) to utf8: %s", >- string_printing(sub), error); >- goto EXPAND_FAILED; >+ uschar * error = NULL; >+ uschar * s = string_domain_alabel_to_utf8(sub, &error); >+ if (error) >+ { >+ expand_string_message = string_sprintf( >+ "error converting alabel (%s) to utf8: %s", >+ string_printing(sub), error); >+ goto EXPAND_FAILED; >+ } >+ yield = string_cat(yield, s); >+ break; > } >- yield = string_cat(yield, s); >- continue; >- } > >- case EOP_UTF8_LOCALPART_TO_ALABEL: >- { >- uschar * error = NULL; >- uschar * s = string_localpart_utf8_to_alabel(sub, &error); >- if (error) >+ case EOP_UTF8_LOCALPART_TO_ALABEL: > { >- expand_string_message = string_sprintf( >- "error converting utf8 (%s) to alabel: %s", >- string_printing(sub), error); >- goto EXPAND_FAILED; >+ uschar * error = NULL; >+ uschar * s = string_localpart_utf8_to_alabel(sub, &error); >+ if (error) >+ { >+ expand_string_message = string_sprintf( >+ "error converting utf8 (%s) to alabel: %s", >+ string_printing(sub), error); >+ goto EXPAND_FAILED; >+ } >+ yield = string_cat(yield, s); >+ DEBUG(D_expand) debug_printf_indent("yield: '%s'\n", yield->s); >+ break; > } >- yield = string_cat(yield, s); >- DEBUG(D_expand) debug_printf_indent("yield: '%s'\n", yield->s); >- continue; >- } > >- case EOP_UTF8_LOCALPART_FROM_ALABEL: >- { >- uschar * error = NULL; >- uschar * s = string_localpart_alabel_to_utf8(sub, &error); >- if (error) >+ case EOP_UTF8_LOCALPART_FROM_ALABEL: > { >- expand_string_message = string_sprintf( >- "error converting alabel (%s) to utf8: %s", >- string_printing(sub), error); >- goto EXPAND_FAILED; >+ uschar * error = NULL; >+ uschar * s = string_localpart_alabel_to_utf8(sub, &error); >+ if (error) >+ { >+ expand_string_message = string_sprintf( >+ "error converting alabel (%s) to utf8: %s", >+ string_printing(sub), error); >+ goto EXPAND_FAILED; >+ } >+ yield = string_cat(yield, s); >+ break; > } >- yield = string_cat(yield, s); >- continue; >- } > #endif /* EXPERIMENTAL_INTERNATIONAL */ > >- /* escape turns all non-printing characters into escape sequences. */ >+ /* escape turns all non-printing characters into escape sequences. */ > >- case EOP_ESCAPE: >- { >- const uschar * t = string_printing(sub); >- yield = string_cat(yield, t); >- continue; >- } >+ case EOP_ESCAPE: >+ { >+ const uschar * t = string_printing(sub); >+ yield = string_cat(yield, t); >+ break; >+ } > >- case EOP_ESCAPE8BIT: >- { >- uschar c; >+ case EOP_ESCAPE8BIT: >+ { >+ uschar c; > >- for (const uschar * s = sub; (c = *s); s++) >- yield = c < 127 && c != '\\' >- ? string_catn(yield, s, 1) >- : string_fmt_append(yield, "\\%03o", c); >- continue; >- } >+ for (const uschar * s = sub; (c = *s); s++) >+ yield = c < 127 && c != '\\' >+ ? string_catn(yield, s, 1) >+ : string_fmt_append(yield, "\\%03o", c); >+ break; >+ } > >- /* Handle numeric expression evaluation */ >+ /* Handle numeric expression evaluation */ > >- case EOP_EVAL: >- case EOP_EVAL10: >- { >- uschar *save_sub = sub; >- uschar *error = NULL; >- int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE); >- if (error) >- { >- expand_string_message = string_sprintf("error in expression " >- "evaluation: %s (after processing \"%.*s\")", error, >- (int)(sub-save_sub), save_sub); >- goto EXPAND_FAILED; >- } >- yield = string_fmt_append(yield, PR_EXIM_ARITH, n); >- continue; >- } >+ case EOP_EVAL: >+ case EOP_EVAL10: >+ { >+ uschar *save_sub = sub; >+ uschar *error = NULL; >+ int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE); >+ if (error) >+ { >+ expand_string_message = string_sprintf("error in expression " >+ "evaluation: %s (after processing \"%.*s\")", error, >+ (int)(sub-save_sub), save_sub); >+ goto EXPAND_FAILED; >+ } >+ yield = string_fmt_append(yield, PR_EXIM_ARITH, n); >+ break; >+ } > >- /* Handle time period formatting */ >+ /* Handle time period formatting */ > >- case EOP_TIME_EVAL: >- { >- int n = readconf_readtime(sub, 0, FALSE); >- if (n < 0) >- { >- expand_string_message = string_sprintf("string \"%s\" is not an " >- "Exim time interval in \"%s\" operator", sub, name); >- goto EXPAND_FAILED; >- } >- yield = string_fmt_append(yield, "%d", n); >- continue; >- } >+ case EOP_TIME_EVAL: >+ { >+ int n = readconf_readtime(sub, 0, FALSE); >+ if (n < 0) >+ { >+ expand_string_message = string_sprintf("string \"%s\" is not an " >+ "Exim time interval in \"%s\" operator", sub, name); >+ goto EXPAND_FAILED; >+ } >+ yield = string_fmt_append(yield, "%d", n); >+ break; >+ } > >- case EOP_TIME_INTERVAL: >- { >- int n; >- uschar *t = read_number(&n, sub); >- if (*t != 0) /* Not A Number*/ >- { >- expand_string_message = string_sprintf("string \"%s\" is not a " >- "positive number in \"%s\" operator", sub, name); >- goto EXPAND_FAILED; >- } >- t = readconf_printtime(n); >- yield = string_cat(yield, t); >- continue; >- } >+ case EOP_TIME_INTERVAL: >+ { >+ int n; >+ uschar *t = read_number(&n, sub); >+ if (*t != 0) /* Not A Number*/ >+ { >+ expand_string_message = string_sprintf("string \"%s\" is not a " >+ "positive number in \"%s\" operator", sub, name); >+ goto EXPAND_FAILED; >+ } >+ t = readconf_printtime(n); >+ yield = string_cat(yield, t); >+ break; >+ } > >- /* Convert string to base64 encoding */ >+ /* Convert string to base64 encoding */ > >- case EOP_STR2B64: >- case EOP_BASE64: >- { >+ case EOP_STR2B64: >+ case EOP_BASE64: >+ { > #ifndef DISABLE_TLS >- uschar * s = vp && *(void **)vp->value >- ? tls_cert_der_b64(*(void **)vp->value) >- : b64encode(CUS sub, Ustrlen(sub)); >+ uschar * s = vp && *(void **)vp->value >+ ? tls_cert_der_b64(*(void **)vp->value) >+ : b64encode(CUS sub, Ustrlen(sub)); > #else >- uschar * s = b64encode(CUS sub, Ustrlen(sub)); >+ uschar * s = b64encode(CUS sub, Ustrlen(sub)); > #endif >- yield = string_cat(yield, s); >- continue; >- } >+ yield = string_cat(yield, s); >+ break; >+ } > >- case EOP_BASE64D: >- { >- uschar * s; >- int len = b64decode(sub, &s); >- if (len < 0) >- { >- expand_string_message = string_sprintf("string \"%s\" is not " >- "well-formed for \"%s\" operator", sub, name); >- goto EXPAND_FAILED; >- } >- yield = string_cat(yield, s); >- continue; >- } >+ case EOP_BASE64D: >+ { >+ uschar * s; >+ int len = b64decode(sub, &s); >+ if (len < 0) >+ { >+ expand_string_message = string_sprintf("string \"%s\" is not " >+ "well-formed for \"%s\" operator", sub, name); >+ goto EXPAND_FAILED; >+ } >+ yield = string_cat(yield, s); >+ break; >+ } > >- /* strlen returns the length of the string */ >+ /* strlen returns the length of the string */ > >- case EOP_STRLEN: >- yield = string_fmt_append(yield, "%d", Ustrlen(sub)); >- continue; >+ case EOP_STRLEN: >+ yield = string_fmt_append(yield, "%d", Ustrlen(sub)); >+ break; > >- /* length_n or l_n takes just the first n characters or the whole string, >- whichever is the shorter; >+ /* length_n or l_n takes just the first n characters or the whole string, >+ whichever is the shorter; > >- substr_m_n, and s_m_n take n characters from offset m; negative m take >- from the end; l_n is synonymous with s_0_n. If n is omitted in substr it >- takes the rest, either to the right or to the left. >- >- hash_n or h_n makes a hash of length n from the string, yielding n >- characters from the set a-z; hash_n_m makes a hash of length n, but >- uses m characters from the set a-zA-Z0-9. >- >- nhash_n returns a single number between 0 and n-1 (in text form), while >- nhash_n_m returns a div/mod hash as two numbers "a/b". The first lies >- between 0 and n-1 and the second between 0 and m-1. */ >- >- case EOP_LENGTH: >- case EOP_L: >- case EOP_SUBSTR: >- case EOP_S: >- case EOP_HASH: >- case EOP_H: >- case EOP_NHASH: >- case EOP_NH: >- { >- int sign = 1; >- int value1 = 0; >- int value2 = -1; >- int *pn; >- int len; >- uschar *ret; >+ substr_m_n, and s_m_n take n characters from offset m; negative m take >+ from the end; l_n is synonymous with s_0_n. If n is omitted in substr it >+ takes the rest, either to the right or to the left. >+ >+ hash_n or h_n makes a hash of length n from the string, yielding n >+ characters from the set a-z; hash_n_m makes a hash of length n, but >+ uses m characters from the set a-zA-Z0-9. >+ >+ nhash_n returns a single number between 0 and n-1 (in text form), while >+ nhash_n_m returns a div/mod hash as two numbers "a/b". The first lies >+ between 0 and n-1 and the second between 0 and m-1. */ >+ >+ case EOP_LENGTH: >+ case EOP_L: >+ case EOP_SUBSTR: >+ case EOP_S: >+ case EOP_HASH: >+ case EOP_H: >+ case EOP_NHASH: >+ case EOP_NH: >+ { >+ int sign = 1; >+ int value1 = 0; >+ int value2 = -1; >+ int *pn; >+ int len; >+ uschar *ret; > >- if (!arg) >- { >- expand_string_message = string_sprintf("missing values after %s", >- name); >- goto EXPAND_FAILED; >- } >+ if (!arg) >+ { >+ expand_string_message = string_sprintf("missing values after %s", >+ name); >+ goto EXPAND_FAILED; >+ } > >- /* "length" has only one argument, effectively being synonymous with >- substr_0_n. */ >+ /* "length" has only one argument, effectively being synonymous with >+ substr_0_n. */ > >- if (c == EOP_LENGTH || c == EOP_L) >- { >- pn = &value2; >- value2 = 0; >- } >+ if (c == EOP_LENGTH || c == EOP_L) >+ { >+ pn = &value2; >+ value2 = 0; >+ } > >- /* The others have one or two arguments; for "substr" the first may be >- negative. The second being negative means "not supplied". */ >+ /* The others have one or two arguments; for "substr" the first may be >+ negative. The second being negative means "not supplied". */ > >- else >- { >- pn = &value1; >- if (name[0] == 's' && *arg == '-') { sign = -1; arg++; } >- } >+ else >+ { >+ pn = &value1; >+ if (name[0] == 's' && *arg == '-') { sign = -1; arg++; } >+ } > >- /* Read up to two numbers, separated by underscores */ >+ /* Read up to two numbers, separated by underscores */ > >- ret = arg; >- while (*arg != 0) >- { >- if (arg != ret && *arg == '_' && pn == &value1) >- { >- pn = &value2; >- value2 = 0; >- if (arg[1] != 0) arg++; >- } >- else if (!isdigit(*arg)) >- { >- expand_string_message = >- string_sprintf("non-digit after underscore in \"%s\"", name); >- goto EXPAND_FAILED; >- } >- else *pn = (*pn)*10 + *arg++ - '0'; >- } >- value1 *= sign; >+ ret = arg; >+ while (*arg != 0) >+ { >+ if (arg != ret && *arg == '_' && pn == &value1) >+ { >+ pn = &value2; >+ value2 = 0; >+ if (arg[1] != 0) arg++; >+ } >+ else if (!isdigit(*arg)) >+ { >+ expand_string_message = >+ string_sprintf("non-digit after underscore in \"%s\"", name); >+ goto EXPAND_FAILED; >+ } >+ else *pn = (*pn)*10 + *arg++ - '0'; >+ } >+ value1 *= sign; > >- /* Perform the required operation */ >+ /* Perform the required operation */ > >- ret = c == EOP_HASH || c == EOP_H >- ? compute_hash(sub, value1, value2, &len) >- : c == EOP_NHASH || c == EOP_NH >- ? compute_nhash(sub, value1, value2, &len) >- : extract_substr(sub, value1, value2, &len); >- if (!ret) goto EXPAND_FAILED; >+ ret = c == EOP_HASH || c == EOP_H >+ ? compute_hash(sub, value1, value2, &len) >+ : c == EOP_NHASH || c == EOP_NH >+ ? compute_nhash(sub, value1, value2, &len) >+ : extract_substr(sub, value1, value2, &len); >+ if (!ret) goto EXPAND_FAILED; > >- yield = string_catn(yield, ret, len); >- continue; >- } >+ yield = string_catn(yield, ret, len); >+ break; >+ } > >- /* Stat a path */ >+ /* Stat a path */ > >- case EOP_STAT: >- { >- uschar smode[12]; >- uschar **modetable[3]; >- mode_t mode; >- struct stat st; >+ case EOP_STAT: >+ { >+ uschar smode[12]; >+ uschar **modetable[3]; >+ mode_t mode; >+ struct stat st; > >- if (expand_forbid & RDO_EXISTS) >- { >- expand_string_message = US"Use of the stat() expansion is not permitted"; >- goto EXPAND_FAILED; >- } >+ if (expand_forbid & RDO_EXISTS) >+ { >+ expand_string_message = US"Use of the stat() expansion is not permitted"; >+ goto EXPAND_FAILED; >+ } > >- if (stat(CS sub, &st) < 0) >- { >- expand_string_message = string_sprintf("stat(%s) failed: %s", >- sub, strerror(errno)); >- goto EXPAND_FAILED; >- } >- mode = st.st_mode; >- switch (mode & S_IFMT) >- { >- case S_IFIFO: smode[0] = 'p'; break; >- case S_IFCHR: smode[0] = 'c'; break; >- case S_IFDIR: smode[0] = 'd'; break; >- case S_IFBLK: smode[0] = 'b'; break; >- case S_IFREG: smode[0] = '-'; break; >- default: smode[0] = '?'; break; >- } >+ if (stat(CS sub, &st) < 0) >+ { >+ expand_string_message = string_sprintf("stat(%s) failed: %s", >+ sub, strerror(errno)); >+ goto EXPAND_FAILED; >+ } >+ mode = st.st_mode; >+ switch (mode & S_IFMT) >+ { >+ case S_IFIFO: smode[0] = 'p'; break; >+ case S_IFCHR: smode[0] = 'c'; break; >+ case S_IFDIR: smode[0] = 'd'; break; >+ case S_IFBLK: smode[0] = 'b'; break; >+ case S_IFREG: smode[0] = '-'; break; >+ default: smode[0] = '?'; break; >+ } > >- modetable[0] = ((mode & 01000) == 0)? mtable_normal : mtable_sticky; >- modetable[1] = ((mode & 02000) == 0)? mtable_normal : mtable_setid; >- modetable[2] = ((mode & 04000) == 0)? mtable_normal : mtable_setid; >+ modetable[0] = ((mode & 01000) == 0)? mtable_normal : mtable_sticky; >+ modetable[1] = ((mode & 02000) == 0)? mtable_normal : mtable_setid; >+ modetable[2] = ((mode & 04000) == 0)? mtable_normal : mtable_setid; > >- for (int i = 0; i < 3; i++) >- { >- memcpy(CS(smode + 7 - i*3), CS(modetable[i][mode & 7]), 3); >- mode >>= 3; >- } >+ for (int i = 0; i < 3; i++) >+ { >+ memcpy(CS(smode + 7 - i*3), CS(modetable[i][mode & 7]), 3); >+ mode >>= 3; >+ } > >- smode[10] = 0; >- yield = string_fmt_append(yield, >- "mode=%04lo smode=%s inode=%ld device=%ld links=%ld " >- "uid=%ld gid=%ld size=" OFF_T_FMT " atime=%ld mtime=%ld ctime=%ld", >- (long)(st.st_mode & 077777), smode, (long)st.st_ino, >- (long)st.st_dev, (long)st.st_nlink, (long)st.st_uid, >- (long)st.st_gid, st.st_size, (long)st.st_atime, >- (long)st.st_mtime, (long)st.st_ctime); >- continue; >- } >+ smode[10] = 0; >+ yield = string_fmt_append(yield, >+ "mode=%04lo smode=%s inode=%ld device=%ld links=%ld " >+ "uid=%ld gid=%ld size=" OFF_T_FMT " atime=%ld mtime=%ld ctime=%ld", >+ (long)(st.st_mode & 077777), smode, (long)st.st_ino, >+ (long)st.st_dev, (long)st.st_nlink, (long)st.st_uid, >+ (long)st.st_gid, st.st_size, (long)st.st_atime, >+ (long)st.st_mtime, (long)st.st_ctime); >+ break; >+ } > >- /* vaguely random number less than N */ >+ /* vaguely random number less than N */ > >- case EOP_RANDINT: >- { >- int_eximarith_t max = expanded_string_integer(sub, TRUE); >+ case EOP_RANDINT: >+ { >+ int_eximarith_t max = expanded_string_integer(sub, TRUE); > >- if (expand_string_message) >- goto EXPAND_FAILED; >- yield = string_fmt_append(yield, "%d", vaguely_random_number((int)max)); >- continue; >- } >+ if (expand_string_message) >+ goto EXPAND_FAILED; >+ yield = string_fmt_append(yield, "%d", vaguely_random_number((int)max)); >+ break; >+ } > >- /* Reverse IP, including IPv6 to dotted-nibble */ >+ /* Reverse IP, including IPv6 to dotted-nibble */ > >- case EOP_REVERSE_IP: >- { >- int family, maskptr; >- uschar reversed[128]; >+ case EOP_REVERSE_IP: >+ { >+ int family, maskptr; >+ uschar reversed[128]; > >- family = string_is_ip_address(sub, &maskptr); >- if (family == 0) >- { >- expand_string_message = string_sprintf( >- "reverse_ip() not given an IP address [%s]", sub); >- goto EXPAND_FAILED; >- } >- invert_address(reversed, sub); >- yield = string_cat(yield, reversed); >- continue; >- } >+ family = string_is_ip_address(sub, &maskptr); >+ if (family == 0) >+ { >+ expand_string_message = string_sprintf( >+ "reverse_ip() not given an IP address [%s]", sub); >+ goto EXPAND_FAILED; >+ } >+ invert_address(reversed, sub); >+ yield = string_cat(yield, reversed); >+ break; >+ } > >- /* Unknown operator */ >+ /* Unknown operator */ > >- default: >- expand_string_message = >- string_sprintf("unknown expansion operator \"%s\"", name); >- goto EXPAND_FAILED; >- } >+ default: >+ expand_string_message = >+ string_sprintf("unknown expansion operator \"%s\"", name); >+ goto EXPAND_FAILED; >+ } /* EOP_* switch */ >+ >+ DEBUG(D_expand) >+ { >+ const uschar * s = yield->s + start; >+ int i = yield->ptr - start; >+ BOOL tainted = is_tainted(s); >+ >+ DEBUG(D_noutf8) >+ { >+ debug_printf_indent("|-----op-res: %.*s\n", i, s); >+ if (tainted) >+ { >+ debug_printf_indent("%s \\__", skipping ? "| " : " "); >+ debug_print_taint(yield->s); >+ } >+ } >+ else >+ { >+ debug_printf_indent(UTF8_VERT_RIGHT >+ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ >+ "op-res: %.*s\n", i, s); >+ if (tainted) >+ { >+ debug_printf_indent("%s", >+ skipping >+ ? UTF8_VERT " " : " " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ); >+ debug_print_taint(yield->s); >+ } >+ } >+ } >+ continue; >+ } > } > >+ /* Not an item or an operator */ > /* Handle a plain name. If this is the first thing in the expansion, release > the pre-allocated buffer. If the result data is known to be in a new buffer, > newsize will be set to the size of that buffer, and we can just point at that >@@ -8072,18 +8209,19 @@ > /*{*/ > if (*s++ == '}') > { >+ const uschar * value; > int len; > int newsize = 0; > gstring * g = NULL; > > if (!yield) >- g = store_get(sizeof(gstring), FALSE); >+ g = store_get(sizeof(gstring), GET_UNTAINTED); > else if (yield->ptr == 0) > { > if (resetok) reset_point = store_reset(reset_point); > yield = NULL; > reset_point = store_mark(); >- g = store_get(sizeof(gstring), FALSE); /* alloc _before_ calling find_variable() */ >+ g = store_get(sizeof(gstring), GET_UNTAINTED); /* alloc _before_ calling find_variable() */ > } > if (!(value = find_variable(name, FALSE, skipping, &newsize))) > { >@@ -8098,7 +8236,7 @@ > yield = g; > yield->size = newsize; > yield->ptr = len; >- yield->s = value; >+ yield->s = US value; /* known to be in new store i.e. a copy, so deconst safe */ > } > else > yield = string_catn(yield, value, len); >@@ -8116,7 +8254,7 @@ > /* If we hit the end of the string when ket_ends is set, there is a missing > terminating brace. */ > >-if (ket_ends && *s == 0) >+if (ket_ends && !*s) > { > expand_string_message = malformed_header > ? US"missing } at end of string - could be header name not terminated by colon" >@@ -8149,8 +8287,10 @@ > debug_printf_indent("%sresult: %s\n", > skipping ? "|-----" : "\\_____", yield->s); > if (tainted) >- debug_printf_indent("%s \\__(tainted)\n", >- skipping ? "| " : " "); >+ { >+ debug_printf_indent("%s \\__", skipping ? "| " : " "); >+ debug_print_taint(yield->s); >+ } > if (skipping) > debug_printf_indent("\\___skipping: result is not used\n"); > } >@@ -8164,9 +8304,12 @@ > skipping ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT, > yield->s); > if (tainted) >- debug_printf_indent("%s(tainted)\n", >+ { >+ debug_printf_indent("%s", > skipping > ? UTF8_VERT " " : " " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ); >+ debug_print_taint(yield->s); >+ } > if (skipping) > debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ > "skipping: result is not used\n"); >@@ -8193,6 +8336,7 @@ > EXPAND_FAILED: > if (left) *left = s; > DEBUG(D_expand) >+ { > DEBUG(D_noutf8) > { > debug_printf_indent("|failed to expand: %s\n", string); >@@ -8212,6 +8356,7 @@ > if (f.expand_string_forcedfail) > debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n"); > } >+ } > if (resetok_p && !resetok) *resetok_p = FALSE; > expand_level--; > return NULL; >@@ -8519,6 +8664,7 @@ > const uschar *var_data; > } err_ctx; > >+/* Called via tree_walk, which allows nonconst name/data. Our usage is const. */ > static void > assert_variable_notin(uschar * var_name, uschar * var_data, void * ctx) > { >@@ -8540,13 +8686,14 @@ > tree_walk(acl_var_c, assert_variable_notin, &e); > tree_walk(acl_var_m, assert_variable_notin, &e); > >-/* check auth<n> variables */ >+/* check auth<n> variables. >+assert_variable_notin() treats as const, so deconst is safe. */ > for (int i = 0; i < AUTH_VARS; i++) if (auth_vars[i]) >- assert_variable_notin(US"auth<n>", auth_vars[i], &e); >+ assert_variable_notin(US"auth<n>", US auth_vars[i], &e); > >-/* check regex<n> variables */ >+/* check regex<n> variables. assert_variable_notin() treats as const. */ > for (int i = 0; i < REGEX_VARS; i++) if (regex_vars[i]) >- assert_variable_notin(US"regex<n>", regex_vars[i], &e); >+ assert_variable_notin(US"regex<n>", US regex_vars[i], &e); > > /* check known-name variables */ > for (var_entry * v = var_table; v < var_table + var_table_size; v++) >@@ -8577,11 +8724,11 @@ > > > BOOL >-regex_match_and_setup(const pcre *re, uschar *subject, int options, int setup) >+regex_match_and_setup(const pcre2_code *re, uschar *subject, int options, int setup) > { >-int ovector[3*(EXPAND_MAXN+1)]; >+int ovec[3*(EXPAND_MAXN+1)]; > int n = pcre_exec(re, NULL, subject, Ustrlen(subject), 0, PCRE_EOPT|options, >- ovector, nelem(ovector)); >+ ovec, nelem(ovec)); > BOOL yield = n >= 0; > if (n == 0) n = EXPAND_MAXN + 1; > if (yield) >@@ -8589,8 +8736,8 @@ > expand_nmax = setup < 0 ? 0 : setup + 1; > for (int nn = setup < 0 ? 0 : 2; nn < n*2; nn += 2) > { >- expand_nstring[expand_nmax] = subject + ovector[nn]; >- expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn]; >+ expand_nstring[expand_nmax] = subject + ovec[nn]; >+ expand_nlength[expand_nmax++] = ovec[nn+1] - ovec[nn]; > } > expand_nmax--; > } >@@ -8606,6 +8753,7 @@ > debug_file = stderr; > debug_fd = fileno(debug_file); > big_buffer = malloc(big_buffer_size); >+store_init(); > > for (int i = 1; i < argc; i++) > { >diff -ur exim.orig/src/filter.c exim/src/filter.c >--- exim.orig/src/filter.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/filter.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -27,7 +27,7 @@ > struct condition_block *c; > struct filter_cmd *f; > int i; >- uschar *u; >+ const uschar *u; > }; > > /* Local structures used in this module */ >@@ -67,7 +67,7 @@ > > enum { had_neither, had_else, had_elif, had_endif }; > >-static BOOL read_command_list(uschar **, filter_cmd ***, BOOL); >+static BOOL read_command_list(const uschar **, filter_cmd ***, BOOL); > > > /* The string arguments for the mail command. The header line ones (that are >@@ -252,8 +252,8 @@ > Returns: pointer to next non-whitespace character > */ > >-static uschar * >-nextsigchar(uschar *ptr, BOOL comment_allowed) >+static const uschar * >+nextsigchar(const uschar *ptr, BOOL comment_allowed) > { > for (;;) > { >@@ -290,8 +290,8 @@ > Returns: pointer to the next significant character after the word > */ > >-static uschar * >-nextword(uschar *ptr, uschar *buffer, int size, BOOL bracket) >+static const uschar * >+nextword(const uschar *ptr, uschar *buffer, int size, BOOL bracket) > { > uschar *bp = buffer; > while (*ptr != 0 && !isspace(*ptr) && >@@ -326,13 +326,13 @@ > Returns: the next significant character after the item > */ > >-static uschar * >-nextitem(uschar *ptr, uschar *buffer, int size, BOOL bracket) >+static const uschar * >+nextitem(const uschar *ptr, uschar *buffer, int size, BOOL bracket) > { > uschar *bp = buffer; > if (*ptr != '\"') return nextword(ptr, buffer, size, bracket); > >-while (*(++ptr) != 0 && *ptr != '\"' && *ptr != '\n') >+while (*++ptr && *ptr != '\"' && *ptr != '\n') > { > if (bp - buffer >= size - 1) > { >@@ -345,7 +345,7 @@ > { > if (isspace(ptr[1])) /* \<whitespace>NL<whitespace> ignored */ > { >- uschar *p = ptr + 1; >+ const uschar *p = ptr + 1; > while (*p != '\n' && isspace(*p)) p++; > if (*p == '\n') > { >@@ -385,7 +385,7 @@ > */ > > static int >-get_number(uschar *s, BOOL *ok) >+get_number(const uschar *s, BOOL *ok) > { > int value, count; > *ok = FALSE; >@@ -416,8 +416,8 @@ > Returns: points to next character after "then" > */ > >-static uschar * >-read_condition(uschar *ptr, condition_block **cond, BOOL toplevel) >+static const uschar * >+read_condition(const uschar *ptr, condition_block **cond, BOOL toplevel) > { > uschar buffer[1024]; > BOOL testfor = TRUE; >@@ -434,7 +434,7 @@ > > /* reaching the end of the input is an error. */ > >- if (*ptr == 0) >+ if (!*ptr) > { > *error_pointer = US"\"then\" missing at end of filter file"; > break; >@@ -477,7 +477,7 @@ > else > { > ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE); >- if (*error_pointer != NULL) break; >+ if (*error_pointer) break; > > /* "Then" at the start of a condition is an error */ > >@@ -498,7 +498,7 @@ > > /* Build a condition block from the specific word. */ > >- c = store_get(sizeof(condition_block), FALSE); >+ c = store_get(sizeof(condition_block), GET_UNTAINTED); > c->left.u = c->right.u = NULL; > c->testfor = testfor; > testfor = TRUE; >@@ -518,7 +518,7 @@ > for (;;) > { > string_item *aa; >- uschar *saveptr = ptr; >+ const uschar * saveptr = ptr; > ptr = nextword(ptr, buffer, sizeof(buffer), TRUE); > if (*error_pointer) break; > if (Ustrcmp(buffer, "alias") != 0) >@@ -528,7 +528,7 @@ > } > ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE); > if (*error_pointer) break; >- aa = store_get(sizeof(string_item), FALSE); >+ aa = store_get(sizeof(string_item), GET_UNTAINTED); > aa->text = string_copy(buffer); > aa->next = c->left.a; > c->left.a = aa; >@@ -569,7 +569,7 @@ > else > { > int i; >- uschar *isptr = NULL; >+ const uschar *isptr = NULL; > > c->left.u = string_copy(buffer); > ptr = nextword(ptr, buffer, sizeof(buffer), TRUE); >@@ -655,18 +655,23 @@ > > else > { >- uschar *saveptr = ptr; >+// const uschar *saveptr = ptr; > ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); > if (*error_pointer) break; > > /* "Then" terminates a toplevel condition; otherwise a closing bracket > has been omitted. Put a string terminator at the start of "then" so > that reflecting the condition can be done when testing. */ >+ /*XXX This stops us doing a constification job in this file, unfortunately. >+ Comment it out and see if anything breaks. >+ With one addition down at DEFERFREEZEFAIL it passes the testsuite. */ > > if (Ustrcmp(buffer, "then") == 0) > { >- if (toplevel) *saveptr = 0; >- else *error_pointer = string_sprintf("missing \")\" at end of " >+// if (toplevel) *saveptr = 0; >+// else >+ if (!toplevel) >+ *error_pointer = string_sprintf("missing \")\" at end of " > "condition near line %d of filter file", line_number); > break; > } >@@ -679,7 +684,7 @@ > > else if (Ustrcmp(buffer, "and") == 0) > { >- condition_block *andc = store_get(sizeof(condition_block), FALSE); >+ condition_block * andc = store_get(sizeof(condition_block), GET_UNTAINTED); > andc->parent = current_parent; > andc->type = cond_and; > andc->testfor = TRUE; >@@ -697,8 +702,8 @@ > > else if (Ustrcmp(buffer, "or") == 0) > { >- condition_block *orc = store_get(sizeof(condition_block), FALSE); >- condition_block *or_parent = NULL; >+ condition_block * orc = store_get(sizeof(condition_block), GET_UNTAINTED); >+ condition_block * or_parent = NULL; > > if (current_parent) > { >@@ -837,7 +842,7 @@ > */ > > static BOOL >-read_command(uschar **pptr, filter_cmd ***lastcmdptr) >+read_command(const uschar **pptr, filter_cmd ***lastcmdptr) > { > int command, i, cmd_bit; > filter_cmd *new, **newlastcmdptr; >@@ -845,8 +850,8 @@ > BOOL was_seen_or_unseen = FALSE; > BOOL was_noerror = FALSE; > uschar buffer[1024]; >-uschar *ptr = *pptr; >-uschar *saveptr; >+const uschar *ptr = *pptr; >+const uschar *saveptr; > uschar *fmsg = NULL; > > /* Read the next word and find which command it is. Command words are normally >@@ -855,6 +860,8 @@ > as brackets are allowed in conditions and users will expect not to require > white space here. */ > >+*buffer = '\0'; /* compiler quietening */ >+ > if (Ustrncmp(ptr, "if(", 3) == 0) > { > Ustrcpy(buffer, US"if"); >@@ -868,7 +875,7 @@ > else > { > ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); >- if (*error_pointer != NULL) return FALSE; >+ if (*error_pointer) return FALSE; > } > > for (command = 0; command < command_list_count; command++) >@@ -907,11 +914,11 @@ > case testprint_command: > > ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); >- if (*buffer == 0) >+ if (!*buffer) > *error_pointer = string_sprintf("\"%s\" requires an argument " > "near line %d of filter file", command_list[command], line_number); > >- if (*error_pointer != NULL) yield = FALSE; else >+ if (*error_pointer) yield = FALSE; else > { > union argtypes argument, second_argument; > >@@ -921,13 +928,13 @@ > { > argument.u = string_copy(buffer); > ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); >- if (*buffer == 0 || Ustrcmp(buffer, "to") != 0) >+ if (!*buffer || Ustrcmp(buffer, "to") != 0) > *error_pointer = string_sprintf("\"to\" expected in \"add\" command " > "near line %d of filter file", line_number); > else > { > ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); >- if (*buffer == 0) >+ if (!*buffer) > *error_pointer = string_sprintf("value missing after \"to\" " > "near line %d of filter file", line_number); > else second_argument.u = string_copy(buffer); >@@ -963,7 +970,7 @@ > if (yield) > { > ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); >- if (*buffer == 0) >+ if (!*buffer) > *error_pointer = string_sprintf("value missing after \"add\", " > "\"remove\", or \"charset\" near line %d of filter file", > line_number); >@@ -999,7 +1006,7 @@ > > else if (command == deliver_command) > { >- uschar *save_ptr = ptr; >+ const uschar *save_ptr = ptr; > ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); > if (Ustrcmp(buffer, "errors_to") == 0) > { >@@ -1014,9 +1021,10 @@ > FALSE for logging commands, and it doesn't matter for testprint, as > that doesn't change the "delivered" status. */ > >- if (*error_pointer != NULL) yield = FALSE; else >+ if (*error_pointer) yield = FALSE; >+ else > { >- new = store_get(sizeof(filter_cmd) + sizeof(union argtypes), FALSE); >+ new = store_get(sizeof(filter_cmd) + sizeof(union argtypes), GET_UNTAINTED); > new->next = NULL; > **lastcmdptr = new; > *lastcmdptr = &(new->next); >@@ -1081,7 +1089,7 @@ > > saveptr = ptr; > ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); >- if (*saveptr != '\"' && (*buffer == 0 || Ustrcmp(buffer, "text") != 0)) >+ if (*saveptr != '\"' && (!*buffer || Ustrcmp(buffer, "text") != 0)) > { > ptr = saveptr; > fmsg = US""; >@@ -1100,12 +1108,12 @@ > /* Finish has no arguments; fmsg defaults to NULL */ > > case finish_command: >- new = store_get(sizeof(filter_cmd), FALSE); >+ new = store_get(sizeof(filter_cmd), GET_UNTAINTED); > new->next = NULL; > **lastcmdptr = new; > *lastcmdptr = &(new->next); > new->command = command; >- new->seen = seen_force? seen_value : FALSE; >+ new->seen = seen_force ? seen_value : FALSE; > new->args[0].u = fmsg; > break; > >@@ -1124,10 +1132,10 @@ > > /* Set up the command block for if */ > >- new = store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), FALSE); >+ new = store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), GET_UNTAINTED); > new->next = NULL; > **lastcmdptr = new; >- *lastcmdptr = &(new->next); >+ *lastcmdptr = &new->next; > new->command = command; > new->seen = FALSE; > new->args[0].u = NULL; >@@ -1136,8 +1144,8 @@ > > /* Read the condition */ > >- ptr = read_condition(ptr, &(new->args[0].c), TRUE); >- if (*error_pointer != NULL) { yield = FALSE; break; } >+ ptr = read_condition(ptr, &new->args[0].c, TRUE); >+ if (*error_pointer) { yield = FALSE; break; } > > /* Read the commands to be obeyed if the condition is true */ > >@@ -1152,7 +1160,7 @@ > while (had_else_endif == had_elif) > { > filter_cmd *newnew = >- store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), FALSE); >+ store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), GET_UNTAINTED); > new->args[2].f = newnew; > new = newnew; > new->next = NULL; >@@ -1162,8 +1170,8 @@ > new->args[1].u = new->args[2].u = NULL; > new->args[3].u = ptr; > >- ptr = read_condition(ptr, &(new->args[0].c), TRUE); >- if (*error_pointer != NULL) { yield = FALSE; break; } >+ ptr = read_condition(ptr, &new->args[0].c, TRUE); >+ if (*error_pointer) { yield = FALSE; break; } > newlastcmdptr = &(new->args[1].f); > if (!read_command_list(&ptr, &newlastcmdptr, TRUE)) > yield = FALSE; >@@ -1205,10 +1213,10 @@ > > case mail_command: > case vacation_command: >- new = store_get(sizeof(filter_cmd) + mailargs_total * sizeof(union argtypes), FALSE); >+ new = store_get(sizeof(filter_cmd) + mailargs_total * sizeof(union argtypes), GET_UNTAINTED); > new->next = NULL; > new->command = command; >- new->seen = seen_force? seen_value : FALSE; >+ new->seen = seen_force ? seen_value : FALSE; > new->noerror = noerror_force; > for (i = 0; i < mailargs_total; i++) new->args[i].u = NULL; > >@@ -1219,13 +1227,10 @@ > > for (;;) > { >- uschar *saveptr = ptr; >+ const uschar *saveptr = ptr; > ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); >- if (*error_pointer != NULL) >- { >- yield = FALSE; >- break; >- } >+ if (*error_pointer) >+ { yield = FALSE; break; } > > /* Ensure "return" is followed by "message"; that's a complete option */ > >@@ -1275,11 +1280,8 @@ > /* Found keyword, read the data item */ > > ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); >- if (*error_pointer != NULL) >- { >- yield = FALSE; >- break; >- } >+ if (*error_pointer) >+ { yield = FALSE; break; } > else new->args[i].u = string_copy(buffer); > } > >@@ -1288,18 +1290,18 @@ > > if (command == vacation_command) > { >- if (new->args[mailarg_index_file].u == NULL) >+ if (!new->args[mailarg_index_file].u) > { > new->args[mailarg_index_file].u = string_copy(US".vacation.msg"); > new->args[mailarg_index_expand].u = US""; /* not NULL => TRUE */ > } >- if (new->args[mailarg_index_log].u == NULL) >+ if (!new->args[mailarg_index_log].u) > new->args[mailarg_index_log].u = string_copy(US".vacation.log"); >- if (new->args[mailarg_index_once].u == NULL) >+ if (!new->args[mailarg_index_once].u) > new->args[mailarg_index_once].u = string_copy(US".vacation"); >- if (new->args[mailarg_index_once_repeat].u == NULL) >+ if (!new->args[mailarg_index_once_repeat].u) > new->args[mailarg_index_once_repeat].u = string_copy(US"7d"); >- if (new->args[mailarg_index_subject].u == NULL) >+ if (!new->args[mailarg_index_subject].u) > new->args[mailarg_index_subject].u = string_copy(US"On vacation"); > } > >@@ -1314,7 +1316,7 @@ > > case seen_command: > case unseen_command: >- if (*ptr == 0) >+ if (!*ptr) > { > *error_pointer = string_sprintf("\"seen\" or \"unseen\" " > "near line %d is not followed by a command", line_number); >@@ -1335,7 +1337,7 @@ > /* So does noerror */ > > case noerror_command: >- if (*ptr == 0) >+ if (!*ptr) > { > *error_pointer = string_sprintf("\"noerror\" " > "near line %d is not followed by a command", line_number); >@@ -1384,11 +1386,11 @@ > */ > > static BOOL >-read_command_list(uschar **pptr, filter_cmd ***lastcmdptr, BOOL conditional) >+read_command_list(const uschar **pptr, filter_cmd ***lastcmdptr, BOOL conditional) > { > if (conditional) expect_endif++; > had_else_endif = had_neither; >-while (**pptr != 0 && had_else_endif == had_neither) >+while (**pptr && had_else_endif == had_neither) > { > if (!read_command(pptr, lastcmdptr)) return FALSE; > *pptr = nextsigchar(*pptr, TRUE); >@@ -1425,10 +1427,7 @@ > test_condition(condition_block *c, BOOL toplevel) > { > BOOL yield = FALSE; >-const pcre *re; >-uschar *exp[2], *p, *pp; >-const uschar *regcomp_error = NULL; >-int regcomp_error_offset; >+const uschar *exp[2], * p, * pp; > int val[2]; > int i; > >@@ -1486,8 +1485,7 @@ > > case cond_foranyaddress: > p = c->left.u; >- pp = expand_string(p); >- if (pp == NULL) >+ if (!(pp = expand_cstring(p))) > { > *error_pointer = string_sprintf("failed to expand \"%s\" in " > "filter file: %s", p, expand_string_message); >@@ -1497,19 +1495,17 @@ > yield = FALSE; > f.parse_allow_group = TRUE; /* Allow group syntax */ > >- while (*pp != 0) >+ while (*pp) > { > uschar *error; > int start, end, domain; >- int saveend; >+ uschar * s; > > p = parse_find_address_end(pp, FALSE); >- saveend = *p; >+ s = string_copyn(pp, p - pp); > >- *p = 0; > filter_thisaddress = >- parse_extract_address(pp, &error, &start, &end, &domain, FALSE); >- *p = saveend; >+ parse_extract_address(s, &error, &start, &end, &domain, FALSE); > > if (filter_thisaddress) > { >@@ -1523,7 +1519,7 @@ > } > > if (yield) break; >- if (saveend == 0) break; >+ if (!*p) break; > pp = p + 1; > } > >@@ -1538,8 +1534,7 @@ > p = c->left.u; > for (i = 0; i < 2; i++) > { >- exp[i] = expand_string(p); >- if (exp[i] == NULL) >+ if (!(exp[i] = expand_cstring(p))) > { > *error_pointer = string_sprintf("failed to expand \"%s\" in " > "filter file: %s", p, expand_string_message); >@@ -1561,7 +1556,7 @@ > break; > > case cond_contains: >- yield = strstric(exp[0], exp[1], FALSE) != NULL; >+ yield = strstric_c(exp[0], exp[1], FALSE) != NULL; > break; > > case cond_CONTAINS: >@@ -1580,34 +1575,43 @@ > case cond_ENDS: > { > int len = Ustrlen(exp[1]); >- uschar *s = exp[0] + Ustrlen(exp[0]) - len; >- yield = (s < exp[0])? FALSE : >- ((c->type == cond_ends)? strcmpic(s, exp[1]) : Ustrcmp(s, exp[1])) == 0; >+ const uschar *s = exp[0] + Ustrlen(exp[0]) - len; >+ yield = s < exp[0] >+ ? FALSE >+ : (c->type == cond_ends ? strcmpic(s, exp[1]) : Ustrcmp(s, exp[1])) == 0; > } > break; > > case cond_matches: > case cond_MATCHES: >- if ((filter_test != FTEST_NONE && debug_selector != 0) || >- (debug_selector & D_filter) != 0) > { >- debug_printf_indent("Match expanded arguments:\n"); >- debug_printf_indent(" Subject = %s\n", exp[0]); >- debug_printf_indent(" Pattern = %s\n", exp[1]); >- } >+ const pcre2_code *re; >+ int err; >+ PCRE2_SIZE offset; > >- if (!(re = pcre_compile(CS exp[1], >- PCRE_COPT | ((c->type == cond_matches)? PCRE_CASELESS : 0), >- CCSS ®comp_error, ®comp_error_offset, NULL))) >- { >- *error_pointer = string_sprintf("error while compiling " >- "regular expression \"%s\": %s at offset %d", >- exp[1], regcomp_error, regcomp_error_offset); >- return FALSE; >- } >+ if ((filter_test != FTEST_NONE && debug_selector != 0) || >+ (debug_selector & D_filter) != 0) >+ { >+ debug_printf_indent("Match expanded arguments:\n"); >+ debug_printf_indent(" Subject = %s\n", exp[0]); >+ debug_printf_indent(" Pattern = %s\n", exp[1]); >+ } > >- yield = regex_match_and_setup(re, exp[0], PCRE_EOPT, -1); >- break; >+ if (!(re = pcre2_compile((PCRE2_SPTR)exp[1], PCRE2_ZERO_TERMINATED, >+ PCRE_COPT | (c->type == cond_matches ? PCRE2_CASELESS : 0), >+ &err, &offset, pcre_cmp_ctx))) >+ { >+ uschar errbuf[128]; >+ pcre2_get_error_message(err, errbuf, sizeof(errbuf)); >+ *error_pointer = string_sprintf("error while compiling " >+ "regular expression \"%s\": %s at offset %ld", >+ exp[1], errbuf, (long)offset); >+ return FALSE; >+ } >+ >+ yield = regex_match_and_setup(re, exp[0], PCRE_EOPT, -1); >+ break; >+ } > > /* For above and below, convert the strings to numbers */ > >@@ -1686,17 +1690,16 @@ > > for (i = 0; i < (command_exparg_count[commands->command] & 15); i++) > { >- uschar *ss = commands->args[i].u; >+ const uschar *ss = commands->args[i].u; > if (!ss) > expargs[i] = NULL; >- else >- if (!(expargs[i] = expand_string(ss))) >- { >- *error_pointer = string_sprintf("failed to expand \"%s\" in " >- "%s command: %s", ss, command_list[commands->command], >- expand_string_message); >- return FF_ERROR; >- } >+ else if (!(expargs[i] = expand_cstring(ss))) >+ { >+ *error_pointer = string_sprintf("failed to expand \"%s\" in " >+ "%s command: %s", ss, command_list[commands->command], >+ expand_string_message); >+ return FF_ERROR; >+ } > } > > /* Now switch for each command, setting the "delivered" flag if any of them >@@ -1892,7 +1895,7 @@ > if (expand_nmax >= 0 || filter_thisaddress != NULL) > { > int ecount = expand_nmax >= 0 ? expand_nmax : -1; >- uschar **ss = store_get(sizeof(uschar *) * (ecount + 3), FALSE); >+ uschar ** ss = store_get(sizeof(uschar *) * (ecount + 3), GET_UNTAINTED); > > addr->pipe_expandn = ss; > if (!filter_thisaddress) filter_thisaddress = US""; >@@ -1951,7 +1954,7 @@ > (long int)geteuid()); > if (log_fd < 0) > { >- if (log_filename == NULL) >+ if (!log_filename) > { > *error_pointer = US"attempt to obey \"logwrite\" command " > "without a previous \"logfile\""; >@@ -1960,7 +1963,7 @@ > log_fd = Uopen(log_filename, O_CREAT|O_APPEND|O_WRONLY, log_mode); > if (log_fd < 0) > { >- *error_pointer = string_open_failed(errno, "filter log file \"%s\"", >+ *error_pointer = string_open_failed("filter log file \"%s\"", > log_filename); > return FF_ERROR; > } >@@ -1974,9 +1977,8 @@ > } > } > else >- { >- DEBUG(D_filter) debug_printf_indent("skipping logwrite (verifying or testing)\n"); >- } >+ DEBUG(D_filter) >+ debug_printf_indent("skipping logwrite (verifying or testing)\n"); > break; > > /* Header addition and removal is available only in the system filter. The >@@ -1989,16 +1991,19 @@ > s = expargs[0]; > > if (filter_test != FTEST_NONE) >- printf("Headers %s \"%s\"\n", (subtype == TRUE)? "add" : >- (subtype == FALSE)? "remove" : "charset", string_printing(s)); >+ printf("Headers %s \"%s\"\n", >+ subtype == TRUE ? "add" >+ : subtype == FALSE ? "remove" >+ : "charset", >+ string_printing(s)); > > if (subtype == TRUE) > { > while (isspace(*s)) s++; >- if (s[0] != 0) >+ if (*s) > { >- header_add(htype_other, "%s%s", s, (s[Ustrlen(s)-1] == '\n')? >- "" : "\n"); >+ header_add(htype_other, "%s%s", s, >+ s[Ustrlen(s)-1] == '\n' ? "" : "\n"); > header_last->type = header_checkname(header_last, FALSE); > if (header_last->type >= 'a') header_last->type = htype_other; > } >@@ -2007,18 +2012,16 @@ > else if (subtype == FALSE) > { > int sep = 0; >- uschar *ss; >- const uschar *list = s; >- uschar buffer[128]; >- while ((ss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) >- != NULL) >+ const uschar * list = s; >+ >+ for (uschar * ss; ss = string_nextinlist(&list, &sep, NULL, 0); ) > header_remove(0, ss); > } > > /* This setting lasts only while the filter is running; on exit, the > variable is reset to the previous value. */ > >- else headers_charset = s; /*XXX loses track of const */ >+ else headers_charset = s; > } > break; > >@@ -2041,18 +2044,20 @@ > ff_name = US"freeze"; > ff_ret = FF_FREEZE; > >- DEFERFREEZEFAIL: >- fmsg = expargs[0]; /*XXX loses track of const */ >- if (Ustrlen(fmsg) > 1024) Ustrcpy(fmsg + 1000, US" ... (truncated)"); >- fmsg = US string_printing(fmsg); >- *error_pointer = fmsg; >+ DEFERFREEZEFAIL: >+ *error_pointer = fmsg = US string_printing(Ustrlen(expargs[0]) > 1024 >+ ? string_sprintf("%.1000s ... (truncated)", expargs[0]) >+ : string_copy(expargs[0])); >+ for(uschar * s = fmsg; *s; s++) >+ if (!s[1] && *s == '\n') { *s = '\0'; break; } /* drop trailing newline */ > > if (filter_test != FTEST_NONE) > { > indent(); > printf("%c%s text \"%s\"\n", toupper(ff_name[0]), ff_name+1, fmsg); > } >- else DEBUG(D_filter) debug_printf_indent("Filter: %s \"%s\"\n", ff_name, fmsg); >+ else >+ DEBUG(D_filter) debug_printf_indent("Filter: %s \"%s\"\n", ff_name, fmsg); > return ff_ret; > > case finish_command: >@@ -2062,19 +2067,19 @@ > printf("%sinish\n", (commands->seen)? "Seen f" : "F"); > } > else >- { > DEBUG(D_filter) debug_printf_indent("Filter: %sfinish\n", >- (commands->seen)? " Seen " : ""); >- } >+ commands->seen ? " Seen " : ""); > finish_obeyed = TRUE; >- return filter_delivered? FF_DELIVERED : FF_NOTDELIVERED; >+ return filter_delivered ? FF_DELIVERED : FF_NOTDELIVERED; > > case if_command: > { > uschar *save_address = filter_thisaddress; > int ok = FF_DELIVERED; > condition_value = test_condition(commands->args[0].c, TRUE); >- if (*error_pointer != NULL) ok = FF_ERROR; else >+ if (*error_pointer) >+ ok = FF_ERROR; >+ else > { > output_indent += 2; > ok = interpret_commands(commands->args[condition_value? 1:2].f, >@@ -2082,7 +2087,7 @@ > output_indent -= 2; > } > filter_thisaddress = save_address; >- if (finish_obeyed || (ok != FF_DELIVERED && ok != FF_NOTDELIVERED)) >+ if (finish_obeyed || ok != FF_DELIVERED && ok != FF_NOTDELIVERED) > return ok; > } > break; >@@ -2094,7 +2099,7 @@ > > case mail_command: > case vacation_command: >- if (return_path == NULL || return_path[0] == 0) >+ if (!return_path || !*return_path) > { > if (filter_test != FTEST_NONE) > printf("%s command ignored because return_path is empty\n", >@@ -2124,12 +2129,11 @@ > > for (i = 0; i < MAILARGS_STRING_COUNT; i++) > { >- uschar *p; > const uschar *s = expargs[i]; > >- if (s == NULL) continue; >+ if (!s) continue; > >- if (i != mailarg_index_text) for (p = s; *p != 0; p++) >+ if (i != mailarg_index_text) for (const uschar * p = s; *p; p++) > { > int c = *p; > if (i > mailarg_index_text) >@@ -2159,12 +2163,12 @@ > > else > { >- uschar *pp; >+ const uschar *pp; > for (pp = p + 1;; pp++) > { > c = *pp; > if (c == ':' && pp != p + 1) break; >- if (c == 0 || c == ':' || isspace(*pp)) >+ if (!c || c == ':' || isspace(c)) > { > *error_pointer = string_sprintf("\\n not followed by space or " > "valid header name in \"%.1024s\" in %s command", >@@ -2179,14 +2183,14 @@ > > /* The string is OK */ > >- commands->args[i].u = s; /*XXX loses track of const */ >+ commands->args[i].u = s; > } > > /* Proceed with mail or vacation command */ > > if (filter_test != FTEST_NONE) > { >- uschar *to = commands->args[mailarg_index_to].u; >+ const uschar *to = commands->args[mailarg_index_to].u; > indent(); > printf("%sail to: %s%s%s\n", (commands->seen)? "Seen m" : "M", > to ? to : US"<default>", >@@ -2194,7 +2198,7 @@ > commands->noerror ? " (noerror)" : ""); > for (i = 1; i < MAILARGS_STRING_COUNT; i++) > { >- uschar *arg = commands->args[i].u; >+ const uschar *arg = commands->args[i].u; > if (arg) > { > int len = Ustrlen(mailargs[i]); >@@ -2210,15 +2214,22 @@ > } > else > { >- uschar *tt; >- uschar *to = commands->args[mailarg_index_to].u; >+ const uschar *tt; >+ const uschar *to = commands->args[mailarg_index_to].u; > gstring * log_addr = NULL; > > if (!to) to = expand_string(US"$reply_address"); > while (isspace(*to)) to++; > >- for (tt = to; *tt != 0; tt++) /* Get rid of newlines */ >- if (*tt == '\n') *tt = ' '; >+ for (tt = to; *tt; tt++) /* Get rid of newlines */ >+ if (*tt == '\n') >+ { >+ uschar * s = string_copy(to); >+ for (uschar * ss = s; *ss; ss++) >+ if (*ss == '\n') *ss = ' '; >+ to = s; >+ break; >+ } > > DEBUG(D_filter) > { >@@ -2229,8 +2240,8 @@ > commands->noerror ? " (noerror)" : ""); > for (i = 1; i < MAILARGS_STRING_COUNT; i++) > { >- uschar *arg = commands->args[i].u; >- if (arg != NULL) >+ const uschar *arg = commands->args[i].u; >+ if (arg) > { > int len = Ustrlen(mailargs[i]); > while (len++ < 15) debug_printf_indent(" "); >@@ -2248,7 +2259,7 @@ > string gets too long. */ > > tt = to; >- while (*tt != 0) >+ while (*tt) > { > uschar *ss = parse_find_address_end(tt, FALSE); > uschar *recipient, *errmess; >@@ -2296,7 +2307,7 @@ > addr->next = *generated; > *generated = addr; > >- addr->reply = store_get(sizeof(reply_item), FALSE); >+ addr->reply = store_get(sizeof(reply_item), GET_UNTAINTED); > addr->reply->from = NULL; > addr->reply->to = string_copy(to); > addr->reply->file_expand = >@@ -2324,7 +2335,7 @@ > > for (i = 1; i < mailargs_string_passed; i++) > { >- uschar *ss = commands->args[i].u; >+ const uschar *ss = commands->args[i].u; > *(USS((US addr->reply) + reply_offsets[i])) = > ss ? string_copy(ss) : NULL; > } >@@ -2497,13 +2508,13 @@ > */ > > int >-filter_interpret(uschar *filter, int options, address_item **generated, >+filter_interpret(const uschar *filter, int options, address_item **generated, > uschar **error) > { > int i; > int yield = FF_ERROR; >-uschar *ptr = filter; >-uschar *save_headers_charset = headers_charset; >+const uschar *ptr = filter; >+const uschar *save_headers_charset = headers_charset; > filter_cmd *commands = NULL; > filter_cmd **lastcmdptr = &commands; > >diff -ur exim.orig/src/filtertest.c exim/src/filtertest.c >--- exim.orig/src/filtertest.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/filtertest.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2009 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -45,11 +46,11 @@ > body_linecount = 0; > header_size = message_size; > >-if (!dot_ended && !feof(stdin)) >+if (!dot_ended && !stdin_feof()) > { > if (!f.dot_ends) > { >- while ((ch = getc(stdin)) != EOF) >+ while ((ch = stdin_getc(GETC_BUFFER_UNLIMITED)) != EOF) > { > if (ch == 0) body_zerocount++; > if (ch == '\n') body_linecount++; >@@ -62,7 +63,7 @@ > else > { > int ch_state = 1; >- while ((ch = getc(stdin)) != EOF) >+ while ((ch = stdin_getc(GETC_BUFFER_UNLIMITED)) != EOF) > { > if (ch == 0) body_zerocount++; > switch (ch_state) >@@ -95,10 +96,11 @@ > if (s > message_body_end + message_body_visible) s = message_body_end; > message_size++; > } >- READ_END: ch = ch; /* Some compilers don't like null statements */ >+ READ_END: ; > } > if (s == message_body_end || s[-1] != '\n') body_linecount++; > } >+debug_printf("%s %d\n", __FUNCTION__, __LINE__); > > message_body[body_len] = 0; > message_body_size = message_size - header_size; >@@ -112,7 +114,7 @@ > int above = message_body_visible - below; > if (above > 0) > { >- uschar *temp = store_get(below, TRUE); >+ uschar * temp = store_get(below, GET_UNTAINTED); > memcpy(temp, message_body_end, below); > memmove(message_body_end, s+1, above); > memcpy(message_body_end + above, temp, below); >@@ -178,7 +180,7 @@ > return FALSE; > } > >-filebuf = store_get(statbuf.st_size + 1, is_tainted(filename)); >+filebuf = store_get(statbuf.st_size + 1, filename); > rc = read(fd, filebuf, statbuf.st_size); > (void)close(fd); > >@@ -250,7 +252,7 @@ > /* For a filter, set up the message_body variables and the message size if this > is the first time this function has been called. */ > >-if (message_body == NULL) read_message_body(dot_ended); >+if (!message_body) read_message_body(dot_ended); > > /* Now pass the filter file to the function that interprets it. Because > filter_test is not FILTER_NONE, the interpreter will output comments about what >@@ -269,10 +271,9 @@ > } > else > { >- yield = (filter_type == FILTER_SIEVE)? >- sieve_interpret(filebuf, RDO_REWRITE, NULL, NULL, NULL, NULL, &generated, &error) >- : >- filter_interpret(filebuf, RDO_REWRITE, &generated, &error); >+ yield = filter_type == FILTER_SIEVE >+ ? sieve_interpret(filebuf, RDO_REWRITE, NULL, NULL, NULL, NULL, &generated, &error) >+ : filter_interpret(filebuf, RDO_REWRITE, &generated, &error); > } > > return yield != FF_ERROR; >diff -ur exim.orig/src/functions.h exim/src/functions.h >--- exim.orig/src/functions.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/functions.h 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -53,31 +53,39 @@ > extern void tls_clean_env(void); > extern BOOL tls_client_start(client_conn_ctx *, smtp_connect_args *, > void *, tls_support *, uschar **); >+extern void tls_client_creds_reload(BOOL); > > extern void tls_close(void *, int); >-extern BOOL tls_could_read(void); >+extern BOOL tls_could_getc(void); > extern void tls_daemon_init(void); >+extern int tls_daemon_tick(void); > extern BOOL tls_dropprivs_validate_require_cipher(BOOL); > extern BOOL tls_export_cert(uschar *, size_t, void *); > extern int tls_feof(void); > extern int tls_ferror(void); >+extern uschar *tls_field_from_dn(uschar *, const uschar *); > extern void tls_free_cert(void **); > extern int tls_getc(unsigned); > extern uschar *tls_getbuf(unsigned *); >-extern void tls_get_cache(void); >+extern void tls_get_cache(unsigned); >+extern BOOL tls_hasc(void); > extern BOOL tls_import_cert(const uschar *, void **); >+extern BOOL tls_is_name_for_cert(const uschar *, void *); >+# ifdef USE_OPENSSL >+extern BOOL tls_openssl_options_parse(uschar *, long *); >+# endif > extern int tls_read(void *, uschar *, size_t); >-extern int tls_server_start(const uschar *, uschar **); >+extern int tls_server_start(uschar **); >+extern void tls_shutdown_wr(void *); > extern BOOL tls_smtp_buffered(void); > extern int tls_ungetc(int); >+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) >+extern void tls_watch_discard_event(int); >+extern void tls_watch_invalidate(void); >+#endif > extern int tls_write(void *, const uschar *, size_t, BOOL); > extern uschar *tls_validate_require_cipher(void); >-extern void tls_version_report(FILE *); >-# ifdef USE_OPENSSL >-extern BOOL tls_openssl_options_parse(uschar *, long *); >-# endif >-extern uschar * tls_field_from_dn(uschar *, const uschar *); >-extern BOOL tls_is_name_for_cert(const uschar *, void *); >+extern gstring *tls_version_report(gstring *); > > # ifdef SUPPORT_DANE > extern int tlsa_lookup(const host_item *, dns_answer *, BOOL); >@@ -90,6 +98,7 @@ > > extern acl_block *acl_read(uschar *(*)(void), uschar **); > extern int acl_check(int, uschar *, uschar *, uschar **, uschar **); >+extern uschar *acl_current_verb(void); > extern int acl_eval(int, uschar *, uschar **, uschar **); > > extern tree_node *acl_var_create(uschar *); >@@ -123,6 +132,7 @@ > extern gstring * auth_show_supported(gstring *); > extern uschar *auth_xtextencode(uschar *, int); > extern int auth_xtextdecode(uschar *, uschar **); >+extern uschar *authenticator_current_name(void); > > #ifdef EXPERIMENTAL_ARC > extern gstring *authres_arc(gstring *); >@@ -139,10 +149,11 @@ > #endif > > extern uschar *b64encode(const uschar *, int); >-extern uschar *b64encode_taint(const uschar *, int, BOOL); >+extern uschar *b64encode_taint(const uschar *, int, const void *); > extern int b64decode(const uschar *, uschar **); > extern int bdat_getc(unsigned); > extern uschar *bdat_getbuf(unsigned *); >+extern BOOL bdat_hasc(void); > extern int bdat_ungetc(int); > extern void bdat_flush_data(void); > >@@ -176,19 +187,24 @@ > extern int dcc_process(uschar **); > #endif > >-extern void debug_logging_activate(uschar *, uschar *); >-extern void debug_logging_stop(void); >+extern void debug_logging_activate(const uschar *, const uschar *); >+extern void debug_logging_from_spool(const uschar *); >+extern void debug_logging_stop(BOOL); > extern void debug_print_argv(const uschar **); > extern void debug_print_ids(uschar *); > extern void debug_printf_indent(const char *, ...) PRINTF_FUNCTION(1,2); > extern void debug_print_string(uschar *); >-extern void debug_print_tree(tree_node *); >+extern void debug_print_tree(const char *, tree_node *); > extern void debug_vprintf(int, const char *, va_list); >+extern void debug_pretrigger_setup(const uschar *); >+extern void debug_pretrigger_discard(void); > extern void debug_print_socket(int); >+extern void debug_trigger_fire(void); > > extern void decode_bits(unsigned int *, size_t, int *, >- uschar *, bit_table *, int, uschar *, int); >+ const uschar *, bit_table *, int, uschar *, int); > extern void delete_pid_file(void); >+extern void deliver_local(address_item *, BOOL); > extern address_item *deliver_make_addr(uschar *, BOOL); > extern void delivery_log(int, address_item *, int, uschar *); > extern int deliver_message(uschar *, BOOL, BOOL); >@@ -197,7 +213,6 @@ > extern int deliver_split_address(address_item *); > extern void deliver_succeeded(address_item *); > >-extern uschar *deliver_get_sender_address (uschar *id); > extern void delivery_re_exec(int); > > extern void die_tainted(const uschar *, const uschar *, int); >@@ -227,7 +242,7 @@ > extern void enq_end(uschar *); > extern BOOL enq_start(uschar *, unsigned); > #ifndef DISABLE_EVENT >-extern uschar *event_raise(uschar *, const uschar *, uschar *); >+extern uschar *event_raise(uschar *, const uschar *, uschar *, int *); > extern void msg_event_raise(const uschar *, const address_item *); > #endif > >@@ -236,7 +251,7 @@ > extern void exim_exit(int) NORETURN; > extern void exim_gettime(struct timeval *); > extern void exim_nullstd(void); >-extern void exim_setugid(uid_t, gid_t, BOOL, uschar *); >+extern void exim_setugid(uid_t, gid_t, BOOL, const uschar *); > extern void exim_underbar_exit(int) NORETURN; > extern void exim_wait_tick(struct timeval *, int); > extern int exp_bool(address_item *addr, >@@ -255,12 +270,13 @@ > > extern BOOL fd_ready(int, time_t); > >-extern int filter_interpret(uschar *, int, address_item **, uschar **); >+extern int filter_interpret(const uschar *, int, address_item **, uschar **); > extern BOOL filter_personal(string_item *, BOOL); > extern BOOL filter_runtest(int, uschar *, BOOL, BOOL); > extern BOOL filter_system_interpret(address_item **, uschar **); > > extern uschar * fn_hdrs_added(void); >+extern void force_fd(int, int); > > extern void header_add(int, const char *, ...); > extern header_line *header_add_at_position_internal(BOOL, uschar *, BOOL, int, const char *, ...); >@@ -308,7 +324,7 @@ > extern int ipv6_nmtoa(int *, uschar *); > > extern uschar *local_part_quote(uschar *); >-extern int log_open_as_exim(uschar *); >+extern int log_open_as_exim(const uschar * const); > extern void log_close_all(void); > > extern macro_item * macro_create(const uschar *, const uschar *, BOOL); >@@ -331,6 +347,9 @@ > unsigned int *, int, BOOL, const uschar **); > extern int match_check_string(const uschar *, const uschar *, int, BOOL, BOOL, BOOL, > const uschar **); >+ >+extern void message_start(void); >+extern void message_tidyup(void); > extern void md5_end(md5 *, const uschar *, int, uschar *); > extern void md5_mid(md5 *, const uschar *); > extern void md5_start(md5 *); >@@ -361,13 +380,14 @@ > > extern uschar *parse_extract_address(const uschar *, uschar **, int *, int *, int *, > BOOL); >-extern int parse_forward_list(uschar *, int, address_item **, uschar **, >- const uschar *, uschar *, error_block **); >-extern uschar *parse_find_address_end(uschar *, 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_at(const uschar *); > extern const uschar *parse_fix_phrase(const uschar *, int); > extern const uschar *parse_message_id(const uschar *, uschar **, uschar **); >-extern const uschar *parse_quote_2047(const uschar *, int, uschar *, BOOL); >+extern const uschar *parse_quote_2047(const uschar *, int, const uschar *, >+ BOOL); > extern const uschar *parse_date_time(const uschar *str, time_t *t); > extern void priv_drop_temp(const uid_t, const gid_t); > extern void priv_restore(void); >@@ -381,16 +401,16 @@ > extern unsigned queue_count(void); > extern unsigned queue_count_cached(void); > extern void queue_list(int, uschar **, int); >-#ifdef EXPERIMENTAL_QUEUE_RAMP >+#ifndef DISABLE_QUEUE_RAMP > extern void queue_notify_daemon(const uschar * hostname); > #endif > extern void queue_run(uschar *, uschar *, BOOL); > > extern int random_number(int); > extern const uschar *rc_to_string(int); >-extern int rda_interpret(redirect_block *, int, uschar *, uschar *, >- uschar *, uschar *, uschar *, ugid_block *, address_item **, >- uschar **, error_block **, int *, uschar *); >+extern int rda_interpret(redirect_block *, int, const uschar *, const uschar *, >+ const uschar *, const uschar *, const uschar *, const ugid_block *, address_item **, >+ uschar **, error_block **, int *, const uschar *); > extern int rda_is_filter(const uschar *); > extern BOOL readconf_depends(driver_instance *, uschar *); > extern void readconf_driver_init(uschar *, driver_instance **, >@@ -415,8 +435,9 @@ > #ifdef WITH_CONTENT_SCAN > extern int regex(const uschar **); > #endif >-extern BOOL regex_match_and_setup(const pcre *, const uschar *, int, int); >-extern const pcre *regex_must_compile(const uschar *, BOOL, BOOL); >+extern BOOL regex_match(const pcre2_code *, const uschar *, int, uschar **); >+extern BOOL regex_match_and_setup(const pcre2_code *, const uschar *, int, int); >+extern const pcre2_code *regex_must_compile(const uschar *, BOOL, BOOL); > extern void retry_add_item(address_item *, uschar *, int); > extern BOOL retry_check_address(const uschar *, host_item *, uschar *, BOOL, > uschar **, uschar **); >@@ -432,8 +453,8 @@ > extern const uschar *rewrite_one(const uschar *, int, BOOL *, BOOL, uschar *, > rewrite_rule *); > extern void rewrite_test(const uschar *); >-extern uschar *rfc2047_decode2(uschar *, BOOL, uschar *, int, int *, int *, >- uschar **); >+extern uschar *rfc2047_decode2(uschar *, BOOL, const uschar *, int, int *, >+ int *, uschar **); > extern int route_address(address_item *, address_item **, address_item **, > address_item **, address_item **, int); > extern int route_check_prefix(const uschar *, const uschar *, unsigned *); >@@ -447,7 +468,9 @@ > extern void route_init(void); > extern gstring * route_show_supported(gstring *); > extern void route_tidyup(void); >+extern uschar *router_current_name(void); > >+extern uschar *search_args(int, uschar *, uschar *, uschar **, const uschar *); > extern uschar *search_find(void *, const uschar *, uschar *, int, > const uschar *, int, int, int *, const uschar *); > extern int search_findtype(const uschar *, int); >@@ -459,10 +482,11 @@ > extern void sha1_end(hctx *, const uschar *, int, uschar *); > extern void sha1_mid(hctx *, const uschar *); > extern void sha1_start(hctx *); >-extern int sieve_interpret(uschar *, int, uschar *, uschar *, uschar *, >- uschar *, address_item **, uschar **); >+extern int sieve_interpret(const uschar *, int, const uschar *, >+ const uschar *, const uschar *, const uschar *, >+ address_item **, uschar **); > extern void sigalrm_handler(int); >-extern BOOL smtp_buffered(void); >+extern int smtp_boundsock(smtp_connect_args *); > extern void smtp_closedown(uschar *); > extern void smtp_command_timeout_exit(void) NORETURN; > extern void smtp_command_sigterm_exit(void) NORETURN; >@@ -471,8 +495,6 @@ > extern void smtp_deliver_init(void); > extern uschar *smtp_cmd_hist(void); > extern int smtp_connect(smtp_connect_args *, const blob *); >-extern int smtp_sock_connect(host_item *, int, int, uschar *, >- transport_instance * tb, int, const blob *); > extern int smtp_feof(void); > extern int smtp_ferror(void); > extern uschar *smtp_get_connection_info(void); >@@ -481,11 +503,12 @@ > extern BOOL smtp_get_port(uschar *, address_item *, int *, uschar *); > extern int smtp_getc(unsigned); > extern uschar *smtp_getbuf(unsigned *); >-extern void smtp_get_cache(void); >+extern void smtp_get_cache(unsigned); >+extern BOOL smtp_hasc(void); > extern int smtp_handle_acl_fail(int, int, uschar *, uschar *); > extern void smtp_log_no_mail(void); > extern void smtp_message_code(uschar **, int *, uschar **, uschar **, BOOL); >-extern void smtp_proxy_tls(void *, uschar *, size_t, int *, int) NORETURN; >+extern void smtp_proxy_tls(void *, uschar *, size_t, int *, int, const uschar *) NORETURN; > extern BOOL smtp_read_response(void *, uschar *, int, int, int); > extern void *smtp_reset(void *); > extern void smtp_respond(uschar *, int, BOOL, uschar *); >@@ -493,6 +516,7 @@ > extern void smtp_port_for_connect(host_item *, int); > extern void smtp_send_prohibition_message(int, uschar *); > extern int smtp_setup_msg(void); >+extern int smtp_sock_connect(smtp_connect_args *, int, const blob *); > extern BOOL smtp_start_session(void); > extern int smtp_ungetc(int); > extern BOOL smtp_verify_helo(void); >@@ -506,13 +530,18 @@ > extern int spool_open_datafile(uschar *); > extern int spool_open_temp(uschar *); > extern int spool_read_header(uschar *, BOOL, BOOL); >+extern uschar *spool_sender_from_msgid(const uschar *); > extern int spool_write_header(uschar *, int, uschar **); > extern int stdin_getc(unsigned); > extern int stdin_feof(void); > extern int stdin_ferror(void); >+extern BOOL stdin_hasc(void); > extern int stdin_ungetc(int); > > extern void store_exit(void); >+extern void store_init(void); >+extern void store_writeprotect(int); >+ > extern gstring *string_append(gstring *, int, ...) WARN_UNUSED_RESULT; > extern gstring *string_append_listele(gstring *, uschar, const uschar *) WARN_UNUSED_RESULT; > extern gstring *string_append_listele_n(gstring *, uschar, const uschar *, unsigned) WARN_UNUSED_RESULT; >@@ -552,10 +581,10 @@ > extern gstring *string_vformat_trc(gstring *, const uschar *, unsigned, > unsigned, unsigned, const char *, va_list); > >-#define string_open_failed(eno, fmt, ...) \ >- string_open_failed_trc(eno, US __FUNCTION__, __LINE__, fmt, __VA_ARGS__) >-extern uschar *string_open_failed_trc(int, const uschar *, unsigned, >- const char *, ...) PRINTF_FUNCTION(4,5); >+#define string_open_failed(fmt, ...) \ >+ string_open_failed_trc(US __FUNCTION__, __LINE__, fmt, __VA_ARGS__) >+extern uschar *string_open_failed_trc(const uschar *, unsigned, >+ const char *, ...) PRINTF_FUNCTION(3,4); > > #define string_nextinlist(lp, sp, b, l) \ > string_nextinlist_trc((lp), (sp), (b), (l), US __FUNCTION__, __LINE__) >@@ -565,6 +594,7 @@ > extern int strcmpic(const uschar *, const uschar *); > extern int strncmpic(const uschar *, const uschar *, int); > extern uschar *strstric(uschar *, uschar *, BOOL); >+extern const uschar *strstric_c(const uschar *, const uschar *, BOOL); > > extern int test_harness_fudged_queue_time(int); > extern void tcp_init(void); >@@ -575,15 +605,19 @@ > extern uschar *tod_stamp(int); > > extern BOOL transport_check_waiting(const uschar *, const uschar *, int, uschar *, >- BOOL *, oicf, void*); >-extern void transport_init(void); >+ oicf, void*); >+extern uschar *transport_current_name(void); > extern void transport_do_pass_socket(const uschar *, const uschar *, > const uschar *, uschar *, int); >-extern BOOL transport_pass_socket(const uschar *, const uschar *, const uschar *, uschar *, >- int); >+extern void transport_init(void); >+extern BOOL transport_pass_socket(const uschar *, const uschar *, const uschar *, uschar *, int >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ , unsigned, unsigned, unsigned >+#endif >+ ); > extern uschar *transport_rcpt_address(address_item *, BOOL); >-extern BOOL transport_set_up_command(const uschar ***, uschar *, >- BOOL, int, address_item *, uschar *, uschar **); >+extern BOOL transport_set_up_command(const uschar ***, const uschar *, >+ BOOL, int, address_item *, BOOL, const uschar *, uschar **); > extern void transport_update_waiting(host_item *, uschar *); > extern BOOL transport_write_block(transport_ctx *, uschar *, int, BOOL); > extern void transport_write_reset(int); >@@ -605,7 +639,7 @@ > extern void unspool_mbox(void); > #endif > #ifdef SUPPORT_I18N >-extern void utf8_version_report(FILE *); >+extern gstring *utf8_version_report(gstring *); > #endif > > extern int verify_address(address_item *, FILE *, int, int, int, int, >@@ -622,6 +656,8 @@ > const uschar*, const uschar *, const uschar **); > extern address_item *verify_checked_sender(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 **); > extern BOOL verify_sender_preliminary(int *, uschar **); > extern void version_init(void); >@@ -647,6 +683,18 @@ > #endif > } > >+static inline BOOL >+is_incompatible(const void * old, const void * new) >+{ >+#if defined(COMPILE_UTILITY) || defined(MACRO_PREDEF) || defined(EM_VERSION_C) >+return FALSE; >+ >+#else >+extern BOOL is_incompatible_fn(const void *, const void *); >+return is_incompatible_fn(old, new); >+#endif >+} >+ > /******************************************************************************/ > /* String functions */ > static inline uschar * __Ustrcat(uschar * dst, const uschar * src, const char * func, int line) >@@ -739,32 +787,35 @@ > > static inline uschar * > string_copyn_taint_trc(const uschar * s, unsigned len, >- BOOL tainted, const char * func, int line) >+ const void * proto_mem, const char * func, int line) > { >-uschar * ss = store_get_3(len + 1, tainted, func, line); >+uschar * ss; >+unsigned slen = Ustrlen(s); >+if (len > slen) len = slen; >+ss = store_get_3(len + 1, proto_mem, func, line); > memcpy(ss, s, len); > ss[len] = '\0'; > return ss; > } > > static inline uschar * >-string_copy_taint_trc(const uschar * s, BOOL tainted, const char * func, int line) >-{ return string_copyn_taint_trc(s, Ustrlen(s), tainted, func, line); } >+string_copy_taint_trc(const uschar * s, const void * proto_mem, const char * func, int line) >+{ return string_copyn_taint_trc(s, Ustrlen(s), proto_mem, func, line); } > > static inline uschar * > string_copyn_trc(const uschar * s, unsigned len, const char * func, int line) >-{ return string_copyn_taint_trc(s, len, is_tainted(s), func, line); } >+{ return string_copyn_taint_trc(s, len, s, func, line); } > static inline uschar * > string_copy_trc(const uschar * s, const char * func, int line) >-{ return string_copy_taint_trc(s, is_tainted(s), func, line); } >+{ return string_copy_taint_trc(s, s, func, line); } > > > /* String-copy functions explicitly setting the taint status */ > >-#define string_copyn_taint(s, len, tainted) \ >- string_copyn_taint_trc((s), (len), (tainted), __FUNCTION__, __LINE__) >-#define string_copy_taint(s, tainted) \ >- string_copy_taint_trc((s), (tainted), __FUNCTION__, __LINE__) >+#define string_copyn_taint(s, len, proto_mem) \ >+ string_copyn_taint_trc((s), (len), (proto_mem), __FUNCTION__, __LINE__) >+#define string_copy_taint(s, proto_mem) \ >+ string_copy_taint_trc((s), (proto_mem), __FUNCTION__, __LINE__) > > /* Simple string-copy functions maintaining the taint */ > >@@ -784,11 +835,11 @@ > */ > > static inline uschar * >-string_copylc(const uschar *s) >+string_copylc(const uschar * s) > { >-uschar *ss = store_get(Ustrlen(s) + 1, is_tainted(s)); >-uschar *p = ss; >-while (*s != 0) *p++ = tolower(*s++); >+uschar * ss = store_get(Ustrlen(s) + 1, s); >+uschar * p = ss; >+while (*s) *p++ = tolower(*s++); > *p = 0; > return ss; > } >@@ -810,10 +861,10 @@ > */ > > static inline uschar * >-string_copynlc(uschar *s, int n) >+string_copynlc(uschar * s, int n) > { >-uschar *ss = store_get(n + 1, is_tainted(s)); >-uschar *p = ss; >+uschar * ss = store_get(n + 1, s); >+uschar * p = ss; > while (n-- > 0) *p++ = tolower(*s++); > *p = 0; > return ss; >@@ -839,7 +890,7 @@ > uschar *ss; > > store_pool = POOL_PERM; >-ss = store_get(len, force_taint || is_tainted(s)); >+ss = store_get(len, force_taint ? GET_TAINTED : s); > memcpy(ss, s, len); > store_pool = old_pool; > return ss; >@@ -867,14 +918,14 @@ > > /* Create a growable-string with some preassigned space */ > >-#define string_get_tainted(size, tainted) \ >- string_get_tainted_trc((size), (tainted), __FUNCTION__, __LINE__) >+#define string_get_tainted(size, proto_mem) \ >+ string_get_tainted_trc((size), (proto_mem), __FUNCTION__, __LINE__) > > static inline gstring * >-string_get_tainted_trc(unsigned size, BOOL tainted, const char * func, unsigned line) >+string_get_tainted_trc(unsigned size, const void * proto_mem, const char * func, unsigned line) > { >-gstring * g = store_get_3(sizeof(gstring) + size, tainted, func, line); >-g->size = size; >+gstring * g = store_get_3(sizeof(gstring) + size, proto_mem, func, line); >+g->size = size; /*XXX would be good if we could see the actual alloc size */ > g->ptr = 0; > g->s = US(g + 1); > return g; >@@ -886,7 +937,7 @@ > static inline gstring * > string_get_trc(unsigned size, const char * func, unsigned line) > { >-return string_get_tainted_trc(size, FALSE, func, line); >+return string_get_tainted_trc(size, GET_UNTAINTED, func, line); > } > > /* NUL-terminate the C string in the growable-string, and return it. */ >@@ -939,31 +990,45 @@ > } > > >-/* Copy the content of a string to tainted memory */ >+/* Copy the content of a string to tainted memory. The proto_mem arg >+will always be tainted, and suitable as a prototype. */ > > static inline void >-gstring_rebuffer(gstring * g) >+gstring_rebuffer(gstring * g, const void * proto_mem) > { >-uschar * s = store_get(g->size, TRUE); >+uschar * s = store_get_3(g->size, proto_mem, __FUNCTION__, __LINE__); > memcpy(s, g->s, g->ptr); > g->s = s; > } > > >+# ifndef COMPILE_UTILITY > /******************************************************************************/ >+/* Use store_malloc for DNSA structs, and explicit frees. Using the same pool >+for them as the strings we proceed to copy from them meant they could not be >+released, hence blowing 64k for every DNS lookup. That mounted up. With malloc >+we do have to take care over marking tainted all copied strings. A separate pool >+could be used and would handle that implicitly. */ > > #define store_get_dns_answer() store_get_dns_answer_trc(CUS __FUNCTION__, __LINE__) > > static inline dns_answer * > store_get_dns_answer_trc(const uschar * func, unsigned line) > { >-return store_get_3(sizeof(dns_answer), TRUE, CCS func, line); /* use tainted mem */ >+return store_malloc_3(sizeof(dns_answer), CCS func, line); >+} >+ >+#define store_free_dns_answer(dnsa) store_free_dns_answer_trc(dnsa, CUS __FUNCTION__, __LINE__) >+ >+static inline void >+store_free_dns_answer_trc(dns_answer * dnsa, const uschar * func, unsigned line) >+{ >+store_free_3(dnsa, CCS func, line); > } > > /******************************************************************************/ > /* Routines with knowledge of spool layout */ > >-# ifndef COMPILE_UTILITY > static inline void > spool_pname_buf(uschar * buf, int len) > { >@@ -1008,7 +1073,7 @@ > #ifdef COMPILE_UTILITY /* version avoiding string-extension */ > int len = Ustrlen(spool_directory) + 1 + Ustrlen(queue_name) + 1 + Ustrlen(purpose) + 1 > + Ustrlen(subdir) + 1 + Ustrlen(fname) + Ustrlen(suffix) + 1; >-uschar * buf = store_get(len, FALSE); >+uschar * buf = store_get(len, GET_UNTAINTED); > string_format(buf, len, "%s/%s/%s/%s/%s%s", > spool_directory, queue_name, purpose, subdir, fname, suffix); > return buf; >@@ -1029,18 +1094,25 @@ > /******************************************************************************/ > /* Time calculations */ > >+/* Diff two times (later, earlier) returning diff in 1st arg */ > static inline void >-timesince(struct timeval * diff, const struct timeval * then) >+timediff(struct timeval * later, const struct timeval * earlier) > { >-gettimeofday(diff, NULL); >-diff->tv_sec -= then->tv_sec; >-if ((diff->tv_usec -= then->tv_usec) < 0) >+later->tv_sec -= earlier->tv_sec; >+if ((later->tv_usec -= earlier->tv_usec) < 0) > { >- diff->tv_sec--; >- diff->tv_usec += 1000*1000; >+ later->tv_sec--; >+ later->tv_usec += 1000*1000; > } > } > >+static inline void >+timesince(struct timeval * diff, const struct timeval * then) >+{ >+gettimeofday(diff, NULL); >+timediff(diff, then); >+} >+ > static inline uschar * > string_timediff(const struct timeval * diff) > { >@@ -1101,6 +1173,7 @@ > errno = EACCES; > return -1; > } >+#ifdef EXIM_HAVE_OPENAT > static inline int > exim_openat(int dirfd, const char *pathname, int flags) > { >@@ -1117,6 +1190,7 @@ > errno = EACCES; > return -1; > } >+#endif > > static inline FILE * > exim_fopen(const char *pathname, const char *mode) >@@ -1175,6 +1249,50 @@ > outfdptr, make_leader, purpose); > } > >+/* Return 1 if fd is usable per pollbits, else 0 */ >+static inline int >+poll_one_fd(int fd, short pollbits, int tmo_millisec) >+{ >+struct pollfd p = {.fd = fd, .events = pollbits}; >+return poll(&p, 1, tmo_millisec); >+} >+ >+/******************************************************************************/ >+/* Client-side smtp log string, for debug */ >+ >+static inline void >+smtp_debug_cmd(const uschar * buf, int mode) >+{ >+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP%c> %s\n", >+ mode == SCMD_BUFFER ? '|' : mode == SCMD_MORE ? '+' : '>', buf); >+ >+# ifndef DISABLE_CLIENT_CMD_LOG >+ { >+ int len = Ustrcspn(buf, " \n"); >+ int old_pool = store_pool; >+ store_pool = POOL_PERM; /* Main pool ACL allocations eg. callouts get released */ >+ client_cmd_log = string_append_listele_n(client_cmd_log, ':', buf, MIN(len, 8)); >+ if (mode == SCMD_BUFFER) >+ { >+ client_cmd_log = string_catn(client_cmd_log, US"|", 1); >+ (void) string_from_gstring(client_cmd_log); >+ } >+ store_pool = old_pool; >+ } >+# endif >+} >+ >+ >+static inline void >+smtp_debug_cmd_report(void) >+{ >+# ifndef DISABLE_CLIENT_CMD_LOG >+debug_printf("cmdlog: '%s'\n", client_cmd_log ? client_cmd_log->s : US"(unset)"); >+# endif >+} >+ >+ >+ > # endif /* !COMPILE_UTILITY */ > > /******************************************************************************/ >diff -ur exim.orig/src/globals.c exim/src/globals.c >--- exim.orig/src/globals.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/globals.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* All the global variables are defined together in this one module, so >@@ -114,15 +114,17 @@ > > uschar *dsn_envid = NULL; > int dsn_ret = 0; >-const pcre *regex_DSN = NULL; >+const pcre2_code *regex_DSN = NULL; > uschar *dsn_advertise_hosts = NULL; > > #ifndef DISABLE_TLS > BOOL gnutls_compat_mode = FALSE; > BOOL gnutls_allow_auto_pkcs11 = FALSE; >+uschar *hosts_require_alpn = NULL; > uschar *openssl_options = NULL; >-const pcre *regex_STARTTLS = NULL; >+const pcre2_code *regex_STARTTLS = NULL; > uschar *tls_advertise_hosts = US"*"; >+uschar *tls_alpn = US"smtp:esmtp"; > uschar *tls_certificate = NULL; > uschar *tls_crl = NULL; > /* This default matches NSS DH_MAX_P_BITS value at current time (2012), because >@@ -137,12 +139,14 @@ > uschar *tls_privatekey = NULL; > BOOL tls_remember_esmtp = FALSE; > uschar *tls_require_ciphers = NULL; >-# ifdef EXPERIMENTAL_TLS_RESUME >+# ifndef DISABLE_TLS_RESUME > uschar *tls_resumption_hosts = NULL; > # endif > uschar *tls_try_verify_hosts = NULL; > uschar *tls_verify_certificates= US"system"; > uschar *tls_verify_hosts = NULL; >+int tls_watch_fd = -1; >+time_t tls_watch_trigger_time = (time_t)0; > #else /*DISABLE_TLS*/ > uschar *tls_advertise_hosts = NULL; > #endif >@@ -151,11 +155,11 @@ > /* Per Recipient Data Response variables */ > BOOL prdr_enable = FALSE; > BOOL prdr_requested = FALSE; >-const pcre *regex_PRDR = NULL; >+const pcre2_code *regex_PRDR = NULL; > #endif > > #ifdef SUPPORT_I18N >-const pcre *regex_UTF8 = NULL; >+const pcre2_code *regex_UTF8 = NULL; > #endif > > /* Input-reading functions for messages, so we can use special ones for >@@ -163,16 +167,18 @@ > stand-alone tests. */ > > #if !defined(STAND_ALONE) && !defined(MACRO_PREDEF) >-int (*lwr_receive_getc)(unsigned) = stdin_getc; >+int (*lwr_receive_getc)(unsigned) = stdin_getc; > uschar * (*lwr_receive_getbuf)(unsigned *) = NULL; >-int (*lwr_receive_ungetc)(int) = stdin_ungetc; >-int (*receive_getc)(unsigned) = stdin_getc; >-uschar * (*receive_getbuf)(unsigned *) = NULL; >-void (*receive_get_cache)(void)= NULL; >-int (*receive_ungetc)(int) = stdin_ungetc; >-int (*receive_feof)(void) = stdin_feof; >-int (*receive_ferror)(void) = stdin_ferror; >-BOOL (*receive_smtp_buffered)(void) = NULL; /* Only used for SMTP */ >+int (*lwr_receive_ungetc)(int) = stdin_ungetc; >+BOOL (*lwr_receive_hasc)(void) = stdin_hasc; >+ >+int (*receive_getc)(unsigned) = stdin_getc; >+uschar * (*receive_getbuf)(unsigned *) = NULL; >+void (*receive_get_cache)(unsigned) = NULL; >+BOOL (*receive_hasc)(void) = stdin_hasc; >+int (*receive_ungetc)(int) = stdin_ungetc; >+int (*receive_feof)(void) = stdin_feof; >+int (*receive_ferror)(void) = stdin_ferror; > #endif > > >@@ -308,6 +314,7 @@ > #endif > .smtp_in_pipelining_advertised = FALSE, > .smtp_in_pipelining_used = FALSE, >+ .smtp_in_quit = FALSE, > .spool_file_wireformat = FALSE, > .submission_mode = FALSE, > .suppress_local_fixups = FALSE, >@@ -383,7 +390,7 @@ > BOOL proxy_session = FALSE; > #endif > >-#ifdef EXPERIMENTAL_QUEUE_RAMP >+#ifndef DISABLE_QUEUE_RAMP > BOOL queue_fast_ramp = FALSE; > #endif > BOOL queue_list_requires_admin = TRUE; >@@ -408,10 +415,6 @@ > #endif > BOOL split_spool_directory = FALSE; > BOOL spool_wireformat = FALSE; >-#ifdef EXPERIMENTAL_SRS >-BOOL srs_usehash = TRUE; >-BOOL srs_usetimestamp = TRUE; >-#endif > BOOL strict_acl_vars = FALSE; > BOOL strip_excess_angle_brackets = FALSE; > BOOL strip_trailing_dot = FALSE; >@@ -597,9 +600,6 @@ > .extra_headers = NULL, > .remove_headers = NULL, > .variables = NULL, >-#ifdef EXPERIMENTAL_SRS >- .srs_sender = NULL, >-#endif > .ignore_error = FALSE, > #ifdef SUPPORT_I18N > .utf8_msg = FALSE, >@@ -649,7 +649,8 @@ > > uschar *auth_defer_msg = US"reason not recorded"; > uschar *auth_defer_user_msg = US""; >-uschar *auth_vars[AUTH_VARS]; >+const uschar *auth_vars[AUTH_VARS]; >+uschar *authenticator_name = NULL; > int auto_thaw = 0; > #ifdef WITH_CONTENT_SCAN > int av_failed = FALSE; /* boolean but accessed as vtype_int*/ >@@ -702,11 +703,18 @@ > unsigned chunking_datasize = 0; > unsigned chunking_data_left = 0; > chunking_state_t chunking_state= CHUNKING_NOT_OFFERED; >-const pcre *regex_CHUNKING = NULL; >+const pcre2_code *regex_CHUNKING = NULL; >+ >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+const pcre2_code *regex_LIMITS = NULL; >+#endif > > uschar *client_authenticator = NULL; > uschar *client_authenticated_id = NULL; > uschar *client_authenticated_sender = NULL; >+#ifndef DISABLE_CLIENT_CMD_LOG >+gstring *client_cmd_log = NULL; >+#endif > int clmacro_count = 0; > uschar *clmacros[MAX_CLMACROS]; > FILE *config_file = NULL; >@@ -736,6 +744,11 @@ > uschar *continue_host_address = NULL; > int continue_sequence = 1; > uschar *continue_transport = NULL; >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+unsigned continue_limit_mail = 0; >+unsigned continue_limit_rcpt = 0; >+unsigned continue_limit_rcptdom= 0; >+#endif > > uschar *csa_status = NULL; > cut_t cutthrough = { >@@ -801,9 +814,12 @@ > BIT_TABLE(D, uid), > BIT_TABLE(D, verify), > }; >-int debug_options_count = nelem(debug_options); >+int debug_options_count = nelem(debug_options); >+uschar debuglog_name[LOG_NAME_SIZE] = {0}; >+unsigned debug_pretrigger_bsize = 0; >+uschar * debug_pretrigger_buf = NULL; >+unsigned int debug_selector = 0; > >-unsigned int debug_selector = 0; > int delay_warning[DELAY_WARNING_SIZE] = { DELAY_WARNING_SIZE, 1, 24*60*60 }; > uschar *delay_warning_condition= > US"${if or {" >@@ -883,7 +899,10 @@ > uschar *dnslist_value = NULL; > tree_node *domainlist_anchor = NULL; > int domainlist_count = 0; >+const uschar *driver_srcfile = NULL; >+int driver_srcline = 0; > uschar *dsn_from = US DEFAULT_DSN_FROM; >+unsigned int dtrigger_selector = 0; > > int errno_quota = ERRNO_QUOTA; > uschar *errors_copy = NULL; >@@ -906,7 +925,7 @@ > int expand_forbid = 0; > int expand_nlength[EXPAND_MAXN+1]; > int expand_nmax = -1; >-uschar *expand_nstring[EXPAND_MAXN+1]; >+const uschar *expand_nstring[EXPAND_MAXN+1]; > uschar *expand_string_message; > uschar *extra_local_interfaces = NULL; > >@@ -935,7 +954,7 @@ > volatile sig_atomic_t had_command_sigterm = 0; > volatile sig_atomic_t had_data_timeout = 0; > volatile sig_atomic_t had_data_sigint = 0; >-uschar *headers_charset = US HEADERS_CHARSET; >+const uschar *headers_charset = US HEADERS_CHARSET; > int header_insert_maxlen = 64 * 1024; > header_line *header_last = NULL; > header_line *header_list = NULL; >@@ -978,6 +997,7 @@ > tree_node *hostlist_anchor = NULL; > int hostlist_count = 0; > uschar *hosts_treat_as_local = NULL; >+uschar *hosts_require_helo = US"*"; > uschar *hosts_connection_nolog = NULL; > > int ignore_bounce_errors_after = 10*7*24*60*60; /* 10 weeks */ >@@ -995,6 +1015,9 @@ > int keep_malformed = 4*24*60*60; /* 4 days */ > > uschar *eldap_dn = NULL; >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+uschar *limits_advertise_hosts = US"*"; >+#endif > int load_average = -2; > uschar *local_from_prefix = NULL; > uschar *local_from_suffix = NULL; >@@ -1028,6 +1051,7 @@ > Li_outgoing_interface, /* see d_log_interface in deliver.c */ > Li_msg_id, > Li_queue_run, >+ Li_queue_time_exclusive, > Li_rejected_header, > Li_retry_defer, > Li_sender_verify_fail, >@@ -1076,11 +1100,13 @@ > BIT_TABLE(L, outgoing_port), > BIT_TABLE(L, pid), > BIT_TABLE(L, pipelining), >+ BIT_TABLE(L, protocol_detail), > #if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) > BIT_TABLE(L, proxy), > #endif > BIT_TABLE(L, queue_run), > BIT_TABLE(L, queue_time), >+ BIT_TABLE(L, queue_time_exclusive), > BIT_TABLE(L, queue_time_overall), > BIT_TABLE(L, receive_time), > BIT_TABLE(L, received_recipients), >@@ -1136,7 +1162,6 @@ > uschar *message_id; > uschar *message_id_domain = NULL; > uschar *message_id_text = NULL; >-struct timeval message_id_tv = { 0, 0 }; > uschar message_id_option[MESSAGE_ID_LENGTH + 3]; > uschar *message_id_external; > int message_linecount = 0; >@@ -1182,6 +1207,10 @@ > uschar *override_local_interfaces = NULL; > uschar *override_pid_file_path = NULL; > >+pcre2_general_context * pcre_gen_ctx = NULL; >+pcre2_compile_context * pcre_cmp_ctx = NULL; >+pcre2_match_context * pcre_mtc_ctx = NULL; >+ > uschar *percent_hack_domains = NULL; > uschar *pid_file_path = US PID_FILE_PATH > "\0<--------------Space to patch pid_file_path->"; >@@ -1201,6 +1230,7 @@ > int proxy_external_port = 0; > uschar *proxy_local_address = NULL; > int proxy_local_port = 0; >+int proxy_protocol_timeout = 3; > #endif > > uschar *prvscheck_address = NULL; >@@ -1266,7 +1296,7 @@ > int received_headers_max = 30; > uschar *received_protocol = NULL; > struct timeval received_time = { 0, 0 }; >-struct timeval received_time_taken = { 0, 0 }; >+struct timeval received_time_complete = { 0, 0 }; > uschar *recipient_data = NULL; > uschar *recipient_unqualified_hosts = NULL; > uschar *recipient_verify_failure = NULL; >@@ -1274,20 +1304,20 @@ > recipient_item *recipients_list = NULL; > int recipients_list_max = 0; > int recipients_max = 50000; >-const pcre *regex_AUTH = NULL; >-const pcre *regex_check_dns_names = NULL; >-const pcre *regex_From = NULL; >-const pcre *regex_IGNOREQUOTA = NULL; >-const pcre *regex_PIPELINING = NULL; >-const pcre *regex_SIZE = NULL; >+const pcre2_code *regex_AUTH = NULL; >+const pcre2_code *regex_check_dns_names = NULL; >+const pcre2_code *regex_From = NULL; >+const pcre2_code *regex_IGNOREQUOTA = NULL; >+const pcre2_code *regex_PIPELINING = NULL; >+const pcre2_code *regex_SIZE = NULL; > #ifndef DISABLE_PIPE_CONNECT >-const pcre *regex_EARLY_PIPE = NULL; >+const pcre2_code *regex_EARLY_PIPE = NULL; > #endif >-const pcre *regex_ismsgid = NULL; >-const pcre *regex_smtp_code = NULL; >-uschar *regex_vars[REGEX_VARS]; >+const pcre2_code *regex_ismsgid = NULL; >+const pcre2_code *regex_smtp_code = NULL; >+const uschar *regex_vars[REGEX_VARS]; > #ifdef WHITELIST_D_MACROS >-const pcre *regex_whitelisted_macro = NULL; >+const pcre2_code *regex_whitelisted_macro = NULL; > #endif > #ifdef WITH_CONTENT_SCAN > uschar *regex_match_string = NULL; >@@ -1442,12 +1472,13 @@ > int smtp_accept_max = 20; > int smtp_accept_max_nonmail= 10; > uschar *smtp_accept_max_nonmail_hosts = US"*"; >-int smtp_accept_max_per_connection = 1000; >+uschar *smtp_accept_max_per_connection = US"1000"; > uschar *smtp_accept_max_per_host = NULL; > int smtp_accept_queue = 0; > int smtp_accept_queue_per_connection = 10; > int smtp_accept_reserve = 0; > uschar *smtp_active_hostname = NULL; >+int smtp_backlog_monitor = 0; > uschar *smtp_banner = US"$smtp_active_hostname ESMTP " > "Exim $version_number $tod_full" > "\0<---------------Space to patch smtp_banner->"; >@@ -1460,13 +1491,17 @@ > double smtp_delay_mail = 0.0; > double smtp_delay_rcpt = 0.0; > FILE *smtp_in = NULL; >+int smtp_listen_backlog = 0; > int smtp_load_reserve = -1; > int smtp_mailcmd_count = 0; >+int smtp_mailcmd_max = -1; > FILE *smtp_out = NULL; > uschar *smtp_etrn_command = NULL; > int smtp_max_synprot_errors= 3; > int smtp_max_unknown_commands = 3; > uschar *smtp_notquit_reason = NULL; >+unsigned smtp_peer_options = 0; >+unsigned smtp_peer_options_wrap= 0; > uschar *smtp_ratelimit_hosts = NULL; > uschar *smtp_ratelimit_mail = NULL; > uschar *smtp_ratelimit_rcpt = NULL; >@@ -1482,8 +1517,6 @@ > double smtp_rlr_factor = 0.0; > int smtp_rlr_limit = 0; > int smtp_rlr_threshold = INT_MAX; >-unsigned smtp_peer_options = 0; >-unsigned smtp_peer_options_wrap= 0; > #ifdef SUPPORT_I18N > uschar *smtputf8_advertise_hosts = US"*"; /* overridden under test-harness */ > #endif >@@ -1511,27 +1544,14 @@ > FILE *spool_data_file = NULL; > uschar *spool_directory = US SPOOL_DIRECTORY > "\0<--------------Space to patch spool_directory->"; >-#ifdef EXPERIMENTAL_SRS >-uschar *srs_config = NULL; >-uschar *srs_db_address = NULL; >-uschar *srs_db_key = NULL; >-int srs_hashlength = 6; >-int srs_hashmin = -1; >-int srs_maxage = 31; >-uschar *srs_orig_recipient = NULL; >-uschar *srs_orig_sender = NULL; >-uschar *srs_recipient = NULL; >-uschar *srs_secrets = NULL; >-uschar *srs_status = NULL; >-#endif >-#ifdef EXPERIMENTAL_SRS_NATIVE >+#ifdef SUPPORT_SRS > uschar *srs_recipient = NULL; > #endif > int string_datestamp_offset= -1; > int string_datestamp_length= 0; > int string_datestamp_type = -1; >-uschar *submission_domain = NULL; >-uschar *submission_name = NULL; >+const uschar *submission_domain = NULL; >+const uschar *submission_name = NULL; > int syslog_facility = LOG_MAIL; > uschar *syslog_processname = US"exim"; > uschar *system_filter = NULL; >@@ -1559,60 +1579,16 @@ > transport_instance *transports = NULL; > > transport_instance transport_defaults = { >- .next = NULL, >- .name = NULL, >- .info = NULL, >- .options_block = NULL, >- .driver_name = NULL, >- .setup = NULL, >+ /* All non-mentioned elements zero/NULL/FALSE */ > .batch_max = 1, >- .batch_id = NULL, >- .home_dir = NULL, >- .current_dir = NULL, >- .expand_multi_domain = NULL, > .multi_domain = TRUE, >- .overrides_hosts = FALSE, > .max_addresses = 100, > .connection_max_messages = 500, >- .deliver_as_creator = FALSE, >- .disable_logging = FALSE, >- .initgroups = FALSE, >- .uid_set = FALSE, >- .gid_set = FALSE, > .uid = (uid_t)(-1), > .gid = (gid_t)(-1), >- .expand_uid = NULL, >- .expand_gid = NULL, >- .warn_message = NULL, >- .shadow = NULL, >- .shadow_condition = NULL, >- .filter_command = NULL, >- .add_headers = NULL, >- .remove_headers = NULL, >- .return_path = NULL, >- .debug_string = NULL, >- .max_parallel = NULL, >- .message_size_limit = NULL, >- .headers_rewrite = NULL, >- .rewrite_rules = NULL, >- .rewrite_existflags = 0, > .filter_timeout = 300, >- .body_only = FALSE, >- .delivery_date_add = FALSE, >- .envelope_to_add = FALSE, >- .headers_only = FALSE, >- .rcpt_include_affixes = FALSE, >- .return_path_add = FALSE, >- .return_output = FALSE, >- .return_fail_output = FALSE, >- .log_output = FALSE, >- .log_fail_output = FALSE, >- .log_defer_output = FALSE, > .retry_use_local_part = TRUE_UNSET, /* retry_use_local_part: BOOL, but set neither > 1 nor 0 so can detect unset */ >-#ifndef DISABLE_EVENT >- .event_action = NULL >-#endif > }; > > int transport_count; >@@ -1663,7 +1639,7 @@ > uschar *verify_mode = NULL; > uschar *version_copyright = > US"Copyright (c) University of Cambridge, 1995 - 2018\n" >- "(c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2018"; >+ "(c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2022"; > uschar *version_date = US"?"; > uschar *version_cnumber = US"????"; > uschar *version_string = US"?"; >diff -ur exim.orig/src/globals.h exim/src/globals.h >--- exim.orig/src/globals.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/globals.h 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Almost all the global variables are defined together in this one header, so >@@ -107,7 +107,10 @@ > OCSP_FAILED, /* verify failed */ > OCSP_VFIED /* verified */ > } ocsp; /* Stapled OCSP status */ >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifndef DISABLE_TLS_RESUME >+ hctx resume_hctx; /* session lookup key accumulation */ >+ const uschar * resume_index; /* session lookup key */ >+ > unsigned resumption; /* Session resumption */ > BOOL host_resumable:1; > BOOL ticket_received:1; >@@ -121,8 +124,10 @@ > #ifndef DISABLE_TLS > extern BOOL gnutls_compat_mode; /* Less security, more compatibility */ > extern BOOL gnutls_allow_auto_pkcs11; /* Let GnuTLS autoload PKCS11 modules */ >+extern uschar *hosts_require_alpn; /* Mandatory ALPN successful nogitiation */ > extern uschar *openssl_options; /* OpenSSL compatibility options */ >-extern const pcre *regex_STARTTLS; /* For recognizing STARTTLS settings */ >+extern const pcre2_code *regex_STARTTLS; /* For recognizing STARTTLS settings */ >+extern uschar *tls_alpn; /* ALPN names acceptable */ > extern uschar *tls_certificate; /* Certificate file */ > extern uschar *tls_crl; /* CRL File */ > extern int tls_dh_max_bits; /* don't accept higher lib suggestions */ >@@ -134,18 +139,20 @@ > extern uschar *tls_privatekey; /* Private key file */ > extern BOOL tls_remember_esmtp; /* For YAEB */ > extern uschar *tls_require_ciphers; /* So some can be avoided */ >-# ifdef EXPERIMENTAL_TLS_RESUME >+# ifndef DISABLE_TLS_RESUME > extern uschar *tls_resumption_hosts; /* TLS session resumption */ > # endif > extern uschar *tls_try_verify_hosts; /* Optional client verification */ > extern uschar *tls_verify_certificates;/* Path for certificates to check */ > extern uschar *tls_verify_hosts; /* Mandatory client verification */ >+extern int tls_watch_fd; /* for inotify of creds files */ >+extern time_t tls_watch_trigger_time; /* non-0: triggered */ > #endif > extern uschar *tls_advertise_hosts; /* host for which TLS is advertised */ > > extern uschar *dsn_envid; /* DSN envid string */ > extern int dsn_ret; /* DSN ret type*/ >-extern const pcre *regex_DSN; /* For recognizing DSN settings */ >+extern const pcre2_code *regex_DSN; /* For recognizing DSN settings */ > extern uschar *dsn_advertise_hosts; /* host for which TLS is advertised */ > > /* Input-reading functions for messages, so we can use special ones for >@@ -153,14 +160,16 @@ > > extern int (*lwr_receive_getc)(unsigned); > extern uschar * (*lwr_receive_getbuf)(unsigned *); >+extern BOOL (*lwr_receive_hasc)(void); > extern int (*lwr_receive_ungetc)(int); >+ > extern int (*receive_getc)(unsigned); > extern uschar * (*receive_getbuf)(unsigned *); >-extern void (*receive_get_cache)(void); >+extern BOOL (*receive_hasc)(void); >+extern void (*receive_get_cache)(unsigned); > extern int (*receive_ungetc)(int); > extern int (*receive_feof)(void); > extern int (*receive_ferror)(void); >-extern BOOL (*receive_smtp_buffered)(void); > > > /* For clearing, saving, restoring address expansion variables. We have to have >@@ -264,12 +273,13 @@ > BOOL sender_set_untrusted :1; /* Sender set by untrusted caller */ > BOOL smtp_authenticated :1; /* Sending client has authenticated */ > #ifndef DISABLE_PIPE_CONNECT >- BOOL smtp_in_early_pipe_advertised :1; /* server advertised PIPE_CONNECT */ >+ BOOL smtp_in_early_pipe_advertised :1; /* server advertised PIPECONNECT */ > BOOL smtp_in_early_pipe_no_auth :1; /* too many authenticator names */ > BOOL smtp_in_early_pipe_used :1; /* client did send early data */ > #endif > BOOL smtp_in_pipelining_advertised :1; /* server advertised PIPELINING */ > BOOL smtp_in_pipelining_used :1; /* server noted client using PIPELINING */ >+ BOOL smtp_in_quit :1; /* server noted QUIT command */ > BOOL spool_file_wireformat :1; /* current -D file has CRLF rather than NL */ > BOOL submission_mode :1; /* Can be forced from ACL */ > BOOL suppress_local_fixups :1; /* Can be forced from ACL */ >@@ -311,7 +321,7 @@ > extern uschar *acl_smtp_data; /* ACL run after DATA received */ > #ifndef DISABLE_PRDR > extern uschar *acl_smtp_data_prdr; /* ACL run after DATA received if in PRDR mode*/ >-const extern pcre *regex_PRDR; /* For recognizing PRDR settings */ >+const extern pcre2_code *regex_PRDR; /* For recognizing PRDR settings */ > #endif > #ifndef DISABLE_DKIM > extern uschar *acl_smtp_dkim; /* ACL run for DKIM signatures / domains */ >@@ -357,13 +367,14 @@ > extern uschar *authenticated_id; /* ID that was authenticated */ > extern uschar *authenticated_sender; /* From AUTH on MAIL */ > extern BOOL authentication_failed; /* TRUE if AUTH was tried and failed */ >+extern uschar *authenticator_name; /* for debug and error messages */ > extern uschar *auth_advertise_hosts; /* Only advertise to these */ > extern auth_info auths_available[]; /* Vector of available auth mechanisms */ > extern auth_instance *auths; /* Chain of instantiated auths */ > extern auth_instance auth_defaults; /* Default values */ > extern uschar *auth_defer_msg; /* Error message for log */ > extern uschar *auth_defer_user_msg; /* Error message for user */ >-extern uschar *auth_vars[]; /* $authn variables */ >+extern const uschar *auth_vars[]; /* $authn variables */ > extern int auto_thaw; /* Auto-thaw interval */ > #ifdef WITH_CONTENT_SCAN > extern int av_failed; /* TRUE if the AV process failed */ >@@ -413,6 +424,9 @@ > extern uschar *client_authenticator; /* Authenticator name used for smtp delivery */ > extern uschar *client_authenticated_id; /* "login" name used for SMTP AUTH */ > extern uschar *client_authenticated_sender; /* AUTH option to SMTP MAIL FROM (not yet used) */ >+#ifndef DISABLE_CLIENT_CMD_LOG >+extern gstring *client_cmd_log; /* debug log of client cmds & responses */ >+#endif > extern int clmacro_count; /* Number of command line macros */ > extern uschar *clmacros[]; /* Copy of them, for re-exec */ > extern BOOL commandline_checks_require_admin; /* belt and braces for insecure setups */ >@@ -432,6 +446,12 @@ > extern uschar *continue_host_address; /* IP address for ditto */ > extern int continue_sequence; /* Sequence num for continued delivery */ > extern uschar *continue_transport; /* Transport for continued delivery */ >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+extern unsigned continue_limit_mail; /* Peer advertised limit */ >+extern unsigned continue_limit_rcpt; >+extern unsigned continue_limit_rcptdom; >+#endif >+ > > extern uschar *csa_status; /* Client SMTP Authorization result */ > >@@ -470,7 +490,11 @@ > extern int debug_notall[]; /* Debug options excluded from +all */ > extern bit_table debug_options[]; /* Table of debug options */ > extern int debug_options_count; /* Size of table */ >+extern unsigned debug_pretrigger_bsize; >+extern uschar *debug_pretrigger_buf; /* circular buffer for precapture */ > extern BOOL debug_store; /* Do extra checks on store_reset */ >+extern uschar debuglog_name[LOG_NAME_SIZE]; /* ACL-init debug */ >+ > extern int delay_warning[]; /* Times between warnings */ > extern uschar *delay_warning_condition; /* Condition string for warnings */ > extern BOOL delivery_date_remove; /* Remove delivery-date headers */ >@@ -546,6 +570,7 @@ > extern int dns_retrans; /* Retransmission time setting */ > extern int dns_retry; /* Number of retries */ > extern int dns_dnssec_ok; /* When constructing DNS query, set DO flag */ >+extern const uschar * dns_rc_names[]; /* Mostly for debug output */ > extern uschar *dns_trust_aa; /* DNSSEC trust AA as AD */ > extern int dns_use_edns0; /* Coerce EDNS0 support on/off in resolver. */ > extern uschar *dnslist_domain; /* DNS (black) list domain */ >@@ -557,6 +582,10 @@ > > /* 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 */ >+ >+extern unsigned int dtrigger_selector; /* when to start debug */ > > extern uschar *dsn_from; /* From: string for DSNs */ > >@@ -584,7 +613,7 @@ > extern int expand_forbid; /* RDO flags for forbidding things */ > extern int expand_nlength[]; /* Lengths of numbered strings */ > extern int expand_nmax; /* Max numerical value */ >-extern uschar *expand_nstring[]; /* Numbered strings */ >+extern const uschar *expand_nstring[]; /* Numbered strings */ > extern BOOL extract_addresses_remove_arguments; /* Controls -t behaviour */ > extern uschar *extra_local_interfaces; /* Local, non-listen interfaces */ > >@@ -630,6 +659,7 @@ > extern uschar *host_lookup_msg; /* Text for why it failed */ > extern int host_number; /* For sharing spools */ > extern uschar *host_number_string; /* For expanding */ >+extern uschar *hosts_require_helo; /* check for HELO/EHLO before MAIL */ > extern uschar *host_reject_connection; /* Reject these hosts */ > extern tree_node *hostlist_anchor; /* Tree of defined host lists */ > extern int hostlist_count; /* Number defined */ >@@ -649,6 +679,9 @@ > extern int keep_malformed; /* Time to keep malformed messages */ > > extern uschar *eldap_dn; /* Where LDAP DNs are left */ >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+extern uschar *limits_advertise_hosts; /* for banner/EHLO pipelining */ >+#endif > extern int load_average; /* Most recently read load average */ > extern BOOL local_from_check; /* For adding Sender: (global value) */ > extern uschar *local_from_prefix; /* Permitted prefixes */ >@@ -704,7 +737,6 @@ > extern uschar *message_id_external; /* External form of following */ > extern uschar *message_id_domain; /* Expanded to form domain-part of message_id */ > extern uschar *message_id_text; /* Expanded to form message_id */ >-extern struct timeval message_id_tv; /* Time used to create last message_id */ > extern int message_linecount; /* As it says */ > extern BOOL message_logs; /* TRUE to write message logs */ > extern int message_size; /* Size of message */ >@@ -712,7 +744,7 @@ > #ifdef SUPPORT_I18N > extern BOOL message_smtputf8; /* Internationalized mail handling */ > extern int message_utf8_downconvert; /* convert from utf8 */ >-const extern pcre *regex_UTF8; /* For recognizing SMTPUTF8 settings */ >+const extern pcre2_code *regex_UTF8; /* For recognizing SMTPUTF8 settings */ > #endif > extern uschar message_subdir[]; /* Subdirectory for messages */ > extern uschar *message_reference; /* Reference for error messages */ >@@ -760,6 +792,10 @@ > extern uschar *override_local_interfaces; /* Value of -oX argument */ > extern uschar *override_pid_file_path; /* Value of -oP argument */ > >+extern pcre2_general_context * pcre_gen_ctx; /* pcre memory management */ >+extern pcre2_compile_context * pcre_cmp_ctx; >+extern pcre2_match_context * pcre_mtc_ctx; >+ > extern uschar *percent_hack_domains; /* Local domains for which '% operates */ > extern uschar *pid_file_path; /* For writing daemon pids */ > #ifndef DISABLE_PIPE_CONNECT >@@ -785,6 +821,7 @@ > extern int proxy_external_port; /* Port on remote interface of proxy */ > extern uschar *proxy_local_address; /* IP of local interface of proxy */ > extern int proxy_local_port; /* Port on local interface of proxy */ >+extern int proxy_protocol_timeout; /* Timeout for proxy negotiation */ > extern BOOL proxy_session; /* TRUE if receiving mail from valid proxy */ > #endif > >@@ -795,7 +832,7 @@ > extern const uschar *qualify_domain_recipient; /* Domain to qualify recipients with */ > extern uschar *qualify_domain_sender; /* Domain to qualify senders with */ > extern uschar *queue_domains; /* Queue these domains */ >-#ifdef EXPERIMENTAL_QUEUE_RAMP >+#ifndef DISABLE_QUEUE_RAMP > extern BOOL queue_fast_ramp; /* 2-phase queue-run overlap */ > #endif > extern BOOL queue_list_requires_admin; /* TRUE if -bp requires admin */ >@@ -837,29 +874,32 @@ > extern uschar *received_for; /* For "for" field */ > extern uschar *received_header_text; /* Definition of Received: header */ > extern int received_headers_max; /* Max count of Received: headers */ >-extern struct timeval received_time; /* Time the message was received */ >-extern struct timeval received_time_taken; /* Interval the message took to be received */ >+extern struct timeval received_time; /* Time the message started to be received */ >+extern struct timeval received_time_complete; /* Time the message completed reception */ > extern uschar *recipient_data; /* lookup data for recipients */ > extern uschar *recipient_unqualified_hosts; /* Permitted unqualified recipients */ > extern uschar *recipient_verify_failure; /* What went wrong */ > extern int recipients_list_max; /* Maximum number fitting in list */ > extern int recipients_max; /* Max permitted */ > extern BOOL recipients_max_reject; /* If TRUE, reject whole message */ >-extern const pcre *regex_AUTH; /* For recognizing AUTH settings */ >-extern const pcre *regex_check_dns_names; /* For DNS name checking */ >-extern const pcre *regex_From; /* For recognizing "From_" lines */ >-extern const pcre *regex_CHUNKING; /* For recognizing CHUNKING (RFC 3030) */ >-extern const pcre *regex_IGNOREQUOTA; /* For recognizing IGNOREQUOTA (LMTP) */ >-extern const pcre *regex_PIPELINING; /* For recognizing PIPELINING */ >-extern const pcre *regex_SIZE; /* For recognizing SIZE settings */ >+extern const pcre2_code *regex_AUTH; /* For recognizing AUTH settings */ >+extern const pcre2_code *regex_check_dns_names; /* For DNS name checking */ >+extern const pcre2_code *regex_From; /* For recognizing "From_" lines */ >+extern const pcre2_code *regex_CHUNKING; /* For recognizing CHUNKING (RFC 3030) */ >+extern const pcre2_code *regex_IGNOREQUOTA; /* For recognizing IGNOREQUOTA (LMTP) */ >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+extern const pcre2_code *regex_LIMITS; /* For recognizing LIMITS */ >+#endif >+extern const pcre2_code *regex_PIPELINING; /* For recognizing PIPELINING */ >+extern const pcre2_code *regex_SIZE; /* For recognizing SIZE settings */ > #ifndef DISABLE_PIPE_CONNECT >-extern const pcre *regex_EARLY_PIPE; /* For recognizing PIPE_CONNCT */ >+extern const pcre2_code *regex_EARLY_PIPE; /* For recognizing PIPE_CONNCT */ > #endif >-extern const pcre *regex_ismsgid; /* Compiled r.e. for message it */ >-extern const pcre *regex_smtp_code; /* For recognizing SMTP codes */ >-extern uschar *regex_vars[]; /* $regexN variables */ >+extern const pcre2_code *regex_ismsgid; /* Compiled r.e. for message ID */ >+extern const pcre2_code *regex_smtp_code; /* For recognizing SMTP codes */ >+extern const uschar *regex_vars[]; /* $regexN variables */ > #ifdef WHITELIST_D_MACROS >-extern const pcre *regex_whitelisted_macro; /* For -D macro values */ >+extern const pcre2_code *regex_whitelisted_macro; /* For -D macro values */ > #endif > #ifdef WITH_CONTENT_SCAN > extern uschar *regex_match_string; /* regex that matched a line (regex ACL condition) */ >@@ -922,12 +962,13 @@ > extern int smtp_accept_max; /* Max SMTP connections */ > extern int smtp_accept_max_nonmail;/* Max non-mail commands in one con */ > extern uschar *smtp_accept_max_nonmail_hosts; /* Limit non-mail cmds from these hosts */ >-extern int smtp_accept_max_per_connection; /* Max msgs per connection */ >+extern uschar *smtp_accept_max_per_connection; /* Max msgs per connection */ > extern uschar *smtp_accept_max_per_host; /* Max SMTP cons from one IP addr */ > extern int smtp_accept_queue; /* Queue after so many connections */ > extern int smtp_accept_queue_per_connection; /* Queue after so many msgs */ > extern int smtp_accept_reserve; /* Reserve these SMTP connections */ > extern uschar *smtp_active_hostname; /* Hostname for this message */ >+extern int smtp_backlog_monitor; /* listen backlog level to log */ > extern uschar *smtp_banner; /* Banner string (to be expanded) */ > extern BOOL smtp_check_spool_space; /* TRUE to check SMTP SIZE value */ > extern int smtp_ch_index; /* Index in smtp_connection_had */ >@@ -942,10 +983,13 @@ > extern uschar *smtp_etrn_command; /* Command to run */ > extern BOOL smtp_etrn_serialize; /* Only one at once */ > extern FILE *smtp_in; /* Incoming SMTP input file */ >+extern int smtp_listen_backlog; /* Current listener socket backlog, if monitored */ > extern int smtp_load_reserve; /* Only from reserved if load > this */ > extern int smtp_mailcmd_count; /* Count of MAIL commands */ >+extern int smtp_mailcmd_max; /* Limit for MAIL commands */ > extern int smtp_max_synprot_errors;/* Max syntax/protocol errors */ > extern int smtp_max_unknown_commands; /* As it says */ >+extern uschar *smtp_names[]; /* decode for command codes */ > extern uschar *smtp_notquit_reason; /* Global for disconnect reason */ > extern FILE *smtp_out; /* Incoming SMTP output file */ > extern uschar *smtp_ratelimit_hosts; /* Rate limit these hosts */ >@@ -992,22 +1036,7 @@ > extern FILE *spool_data_file; /* handle for -D file */ > extern uschar *spool_directory; /* Name of spool directory */ > extern BOOL spool_wireformat; /* can write wireformat -D files */ >-#ifdef EXPERIMENTAL_SRS >-extern uschar *srs_config; /* SRS config secret:max age:hash length:use timestamp:use hash */ >-extern uschar *srs_db_address; /* SRS db address */ >-extern uschar *srs_db_key; /* SRS db key */ >-extern int srs_hashlength; /* SRS hash length */ >-extern int srs_hashmin; /* SRS minimum hash length */ >-extern int srs_maxage; /* SRS max age */ >-extern uschar *srs_orig_sender; /* SRS original sender */ >-extern uschar *srs_orig_recipient; /* SRS original recipient */ >-extern uschar *srs_recipient; /* SRS recipient */ >-extern uschar *srs_secrets; /* SRS secrets list */ >-extern uschar *srs_status; /* SRS staus */ >-extern BOOL srs_usehash; /* SRS use hash flag */ >-extern BOOL srs_usetimestamp; /* SRS use timestamp flag */ >-#endif >-#ifdef EXPERIMENTAL_SRS_NATIVE >+#ifdef SUPPORT_SRS > extern uschar *srs_recipient; /* SRS recipient */ > #endif > extern BOOL strict_acl_vars; /* ACL variables have to be set before being used */ >@@ -1016,8 +1045,8 @@ > extern int string_datestamp_type; /* After insertion by string_format */ > extern BOOL strip_excess_angle_brackets; /* Surrounding route-addrs */ > extern BOOL strip_trailing_dot; /* Remove dots at ends of domains */ >-extern uschar *submission_domain; /* Domain for submission mode */ >-extern uschar *submission_name; /* User name set from ACL */ >+extern const uschar *submission_domain;/* Domain for submission mode */ >+extern const uschar *submission_name; /* User name set from ACL */ > extern BOOL syslog_duplication; /* FALSE => no duplicate logging */ > extern int syslog_facility; /* As defined by Syslog.h */ > extern BOOL syslog_pid; /* TRUE if PID on syslogs */ >diff -ur exim.orig/src/hash.c exim/src/hash.c >--- exim.orig/src/hash.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/hash.c 2022-06-23 16:41:10.000000000 +0300 >@@ -1,7 +1,7 @@ > /* > * Exim - an Internet mail transport agent > * >- * Copyright (C) 2010 - 2018 Exim maintainers >+ * Copyright (c) The Exim Maintainers 2010 - 2022 > * Copyright (c) University of Cambridge 1995 - 2009 > * > * Hash interface functions >@@ -29,17 +29,19 @@ > > /******************************************************************************/ > #ifdef SHA_OPENSSL >+# define HAVE_PARTIAL_SHA > > BOOL > exim_sha_init(hctx * h, hashmethod m) > { >+# if OPENSSL_VERSION_NUMBER < 0x30000000L > switch (h->method = m) > { > case HASH_SHA1: h->hashlen = 20; SHA1_Init (&h->u.sha1); break; > case HASH_SHA2_256: h->hashlen = 32; SHA256_Init(&h->u.sha2_256); break; > case HASH_SHA2_384: h->hashlen = 48; SHA384_Init(&h->u.sha2_512); break; > case HASH_SHA2_512: h->hashlen = 64; SHA512_Init(&h->u.sha2_512); break; >-#ifdef EXIM_HAVE_SHA3 >+# ifdef EXIM_HAVE_SHA3 > case HASH_SHA3_224: h->hashlen = 28; > EVP_DigestInit(h->u.mctx = EVP_MD_CTX_new(), EVP_sha3_224()); > break; >@@ -52,32 +54,62 @@ > case HASH_SHA3_512: h->hashlen = 64; > EVP_DigestInit(h->u.mctx = EVP_MD_CTX_new(), EVP_sha3_512()); > break; >-#endif >+# endif > default: h->hashlen = 0; return FALSE; > } > return TRUE; >+ >+# else >+EVP_MD * md; >+ >+h->hashlen = 0; >+if (!(h->u.mctx = EVP_MD_CTX_new())) return FALSE; >+switch (h->method = m) >+ { >+ case HASH_SHA1: h->hashlen = 20; md = EVP_MD_fetch(NULL, "SHA1", NULL); break; >+ case HASH_SHA2_256: h->hashlen = 32; md = EVP_MD_fetch(NULL, "SHA2-256", NULL); break; >+ case HASH_SHA2_384: h->hashlen = 48; md = EVP_MD_fetch(NULL, "SHA2-384", NULL); break; >+ case HASH_SHA2_512: h->hashlen = 64; md = EVP_MD_fetch(NULL, "SHA2-512", NULL); break; >+ case HASH_SHA3_224: h->hashlen = 28; md = EVP_MD_fetch(NULL, "SHA3-224", NULL); break; >+ case HASH_SHA3_256: h->hashlen = 32; md = EVP_MD_fetch(NULL, "SHA3-256", NULL); break; >+ case HASH_SHA3_384: h->hashlen = 48; md = EVP_MD_fetch(NULL, "SHA3-384", NULL); break; >+ case HASH_SHA3_512: h->hashlen = 64; md = EVP_MD_fetch(NULL, "SHA3-512", NULL); break; >+ default: return FALSE; >+ } >+if (md && EVP_DigestInit_ex(h->u.mctx, md, NULL)) >+ return TRUE; >+ >+h->hashlen = 0; >+return FALSE; >+# endif > } > > > void > exim_sha_update(hctx * h, const uschar * data, int len) > { >+# if OPENSSL_VERSION_NUMBER < 0x30000000L > switch (h->method) > { > case HASH_SHA1: SHA1_Update (&h->u.sha1, data, len); break; > case HASH_SHA2_256: SHA256_Update(&h->u.sha2_256, data, len); break; > case HASH_SHA2_384: SHA384_Update(&h->u.sha2_512, data, len); break; > case HASH_SHA2_512: SHA512_Update(&h->u.sha2_512, data, len); break; >-#ifdef EXIM_HAVE_SHA3 >+# ifdef EXIM_HAVE_SHA3 > case HASH_SHA3_224: > case HASH_SHA3_256: > case HASH_SHA3_384: > case HASH_SHA3_512: EVP_DigestUpdate(h->u.mctx, data, len); break; >-#endif >+# endif > /* should be blocked by init not handling these, but be explicit to > guard against accidents later (and hush up clang -Wswitch) */ > default: assert(0); > } >+ >+# else >+ >+EVP_DigestUpdate(h->u.mctx, data, len); >+# endif > } > > >@@ -85,26 +117,37 @@ > exim_sha_finish(hctx * h, blob * b) > { > /* Hashing is sufficient to purify any tainted input */ >-b->data = store_get(b->len = h->hashlen, FALSE); >+b->data = store_get(b->len = h->hashlen, GET_UNTAINTED); >+ >+# if OPENSSL_VERSION_NUMBER < 0x30000000L > switch (h->method) > { > case HASH_SHA1: SHA1_Final (b->data, &h->u.sha1); break; > case HASH_SHA2_256: SHA256_Final(b->data, &h->u.sha2_256); break; > case HASH_SHA2_384: SHA384_Final(b->data, &h->u.sha2_512); break; > case HASH_SHA2_512: SHA512_Final(b->data, &h->u.sha2_512); break; >-#ifdef EXIM_HAVE_SHA3 >+# ifdef EXIM_HAVE_SHA3 > case HASH_SHA3_224: > case HASH_SHA3_256: > case HASH_SHA3_384: > case HASH_SHA3_512: EVP_DigestFinal(h->u.mctx, b->data, NULL); break; >-#endif >+# endif > default: assert(0); > } >+ >+# else >+ >+EVP_DigestFinal_ex(h->u.mctx, b->data, NULL); >+EVP_MD_free((EVP_MD *) EVP_MD_CTX_get0_md(h->u.mctx)); >+EVP_MD_CTX_free(h->u.mctx); >+ >+# endif > } > > > > #elif defined(SHA_GNUTLS) >+# define HAVE_PARTIAL_SHA > /******************************************************************************/ > > BOOL >@@ -138,13 +181,14 @@ > void > exim_sha_finish(hctx * h, blob * b) > { >-b->data = store_get(b->len = h->hashlen, FALSE); >+b->data = store_get(b->len = h->hashlen, GET_UNTAINTED); > gnutls_hash_output(h->sha, b->data); > } > > > > #elif defined(SHA_GCRYPT) >+# define HAVE_PARTIAL_SHA > /******************************************************************************/ > > BOOL >@@ -175,7 +219,7 @@ > void > exim_sha_finish(hctx * h, blob * b) > { >-b->data = store_get(b->len = h->hashlen, FALSE); >+b->data = store_get(b->len = h->hashlen, GET_UNTAINTED); > memcpy(b->data, gcry_md_read(h->sha, 0), h->hashlen); > } > >@@ -183,6 +227,7 @@ > > > #elif defined(SHA_POLARSSL) >+# define HAVE_PARTIAL_SHA > /******************************************************************************/ > > BOOL >@@ -213,7 +258,7 @@ > void > exim_sha_finish(hctx * h, blob * b) > { >-b->data = store_get(b->len = h->hashlen, FALSE); >+b->data = store_get(b->len = h->hashlen, GET_INTAINTED); > switch (h->method) > { > case HASH_SHA1: sha1_finish(h->u.sha1, b->data); break; >@@ -391,9 +436,7 @@ > memset(work, 0, 56); > } > else >- { > memset(work+length+1, 0, 55-length); >- } > > /* The final 8 bytes of the final chunk are a 64-bit representation of the > length of the input string *bits*, before padding, high order word first, and >@@ -451,7 +494,7 @@ > void > exim_sha_finish(hctx * h, blob * b) > { >-b->data = store_get(b->len = h->hashlen, FALSE); >+b->data = store_get(b->len = h->hashlen, GET_UNTAINTED); > > native_sha1_end(&h->sha1, NULL, 0, b->data); > } >@@ -515,6 +558,14 @@ > > > >+#ifdef HAVE_PARTIAL_SHA >+# undef HAVE_PARTIAL_SHA >+void >+exim_sha_update_string(hctx * h, const uschar * s) >+{ >+if (s) exim_sha_update(h, s, Ustrlen(s)); >+} >+#endif > > > >@@ -524,7 +575,7 @@ > ************************************************** > *************************************************/ > >-# ifdef STAND_ALONE >+#ifdef STAND_ALONE > > /* Test values. The first 128 may contain binary zeros and have increasing > length. */ >@@ -839,6 +890,6 @@ > if (strcmp(s, atest) != 0) printf("*** No match ***\n"); > > } >-# endif /*STAND_ALONE*/ >+#endif /*STAND_ALONE*/ > > /* End of File */ >diff -ur exim.orig/src/hash.h exim/src/hash.h >--- exim.orig/src/hash.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/hash.h 2022-06-23 16:41:10.000000000 +0300 >@@ -1,7 +1,6 @@ > /* > * Exim - an Internet mail transport agent >- * >- * Copyright (C) 1995 - 2018 Exim maintainers >+ * Copyright (c) The Exim Maintainers 1995 - 2022 > * > * Hash interface functions > */ >@@ -77,6 +76,7 @@ > > extern BOOL exim_sha_init(hctx *, hashmethod); > extern void exim_sha_update(hctx *, const uschar *a, int); >+extern void exim_sha_update_string(hctx *, const uschar *a); > extern void exim_sha_finish(hctx *, blob *); > > #endif >diff -ur exim.orig/src/header.c exim/src/header.c >--- exim.orig/src/header.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/header.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2016 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -97,12 +97,15 @@ > header_line *h, *new = NULL; > header_line **hptr; > >-uschar *p, *q; >-uschar * buf = store_get(HEADER_ADD_BUFFER_SIZE, FALSE); >-gstring gs = { .size = HEADER_ADD_BUFFER_SIZE, .ptr = 0, .s = buf }; >+uschar * p, * q, * buf; >+gstring gs; > > if (!header_last) return NULL; > >+gs.s = buf = store_get(HEADER_ADD_BUFFER_SIZE, GET_UNTAINTED); >+gs.size = HEADER_ADD_BUFFER_SIZE; >+gs.ptr = 0; >+ > if (!string_vformat(&gs, SVFMT_REBUFFER, format, ap)) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "string too long in header_add: " > "%.100s ...", string_from_gstring(&gs)); >@@ -179,7 +182,7 @@ > if (*(++q) != ' ' && *q != '\t') break; > } > >- new = store_get(sizeof(header_line), FALSE); >+ new = store_get(sizeof(header_line), GET_UNTAINTED); > new->text = string_copyn(p, q - p); > new->slen = q - p; > new->type = type; >@@ -368,7 +371,7 @@ > one_pattern_match(uschar *name, int slen, BOOL has_addresses, uschar *pattern) > { > BOOL yield = FALSE; >-const pcre *re = NULL; >+const pcre2_code *re = NULL; > > /* If the pattern is a regex, compile it. Bomb out if compiling fails; these > patterns are all constructed internally and should be valid. */ >@@ -416,10 +419,9 @@ > > /* Otherwise, test for the pattern; a non-regex must be an exact match */ > >- yield = !re >- ? (strcmpic(next, pattern) == 0) >- : (pcre_exec(re, NULL, CS next, Ustrlen(next), 0, PCRE_EOPT, NULL, 0) >- >= 0); >+ yield = re >+ ? regex_match(re, next, -1, NULL) >+ : (strcmpic(next, pattern) == 0); > } > } > >@@ -428,10 +430,9 @@ > > else > { >- yield = (re == NULL)? >- (strstric(h->text, pattern, FALSE) != NULL) >- : >- (pcre_exec(re, NULL, CS h->text, h->slen, 0, PCRE_EOPT, NULL, 0) >= 0); >+ yield = re >+ ? regex_match(re, h->text, h->slen, NULL) >+ : (strstric(h->text, pattern, FALSE) != NULL); > } > } > >ТолÑко в exim/src: hintsdb.h >ТолÑко в exim/src: hintsdb_structs.h >diff -ur exim.orig/src/host.c exim/src/host.c >--- exim.orig/src/host.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/host.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions for finding hosts, either by gethostbyname(), gethostbyaddr(), or >@@ -197,9 +197,9 @@ > || ipa == 6 && af == AF_INET6) > { > int x[4]; >- yield = store_get(sizeof(struct hostent), FALSE); >- alist = store_get(2 * sizeof(char *), FALSE); >- adds = store_get(alen, FALSE); >+ yield = store_get(sizeof(struct hostent), GET_UNTAINTED); >+ alist = store_get(2 * sizeof(char *), GET_UNTAINTED); >+ adds = store_get(alen, GET_UNTAINTED); > yield->h_name = CS name; > yield->h_aliases = NULL; > yield->h_addrtype = af; >@@ -222,7 +222,8 @@ > else > { > *error_num = HOST_NOT_FOUND; >- return NULL; >+ yield = NULL; >+ goto out; > } > > /* Handle a host name */ >@@ -238,11 +239,11 @@ > switch(rc) > { > case DNS_SUCCEED: break; >- case DNS_NOMATCH: *error_num = HOST_NOT_FOUND; return NULL; >- case DNS_NODATA: *error_num = NO_DATA; return NULL; >- case DNS_AGAIN: *error_num = TRY_AGAIN; return NULL; >+ case DNS_NOMATCH: *error_num = HOST_NOT_FOUND; yield = NULL; goto out; >+ case DNS_NODATA: *error_num = NO_DATA; yield = NULL; goto out; >+ case DNS_AGAIN: *error_num = TRY_AGAIN; yield = NULL; goto out; > default: >- case DNS_FAIL: *error_num = NO_RECOVERY; return NULL; >+ case DNS_FAIL: *error_num = NO_RECOVERY; yield = NULL; goto out; > } > > for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); >@@ -250,9 +251,9 @@ > rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == type) > count++; > >- yield = store_get(sizeof(struct hostent), FALSE); >- alist = store_get((count + 1) * sizeof(char *), FALSE); >- adds = store_get(count *alen, FALSE); >+ yield = store_get(sizeof(struct hostent), GET_UNTAINTED); >+ alist = store_get((count + 1) * sizeof(char *), GET_UNTAINTED); >+ adds = store_get(count *alen, GET_UNTAINTED); > > yield->h_name = CS name; > yield->h_aliases = NULL; >@@ -280,6 +281,9 @@ > *alist = NULL; > } > >+out: >+ >+store_free_dns_answer(dnsa); > return yield; > } > >@@ -324,12 +328,12 @@ > continue; > } > >- h = store_get(sizeof(host_item), FALSE); >+ h = store_get(sizeof(host_item), GET_UNTAINTED); > h->name = name; > h->address = NULL; > h->port = PORT_NONE; > h->mx = fake_mx; >- h->sort_key = randomize? (-fake_mx)*1000 + random_number(1000) : 0; >+ h->sort_key = randomize ? (-fake_mx)*1000 + random_number(1000) : 0; > h->status = hstatus_unknown; > h->why = hwhy_unknown; > h->last_try = 0; >@@ -366,15 +370,18 @@ > * Extract port from address string * > *************************************************/ > >-/* In the spool file, and in the -oMa and -oMi options, a host plus port is >-given as an IP address followed by a dot and a port number. This function >-decodes this. >+/* In the -oMa and -oMi options, a host plus port is given as an IP address >+followed by a dot and a port number. This function decodes this. > > An alternative format for the -oMa and -oMi options is [ip address]:port which >-is what Exim 4 uses for output, because it seems to becoming commonly used, >+is what Exim uses for output, because it seems to becoming commonly used, > whereas the dot form confuses some programs/people. So we recognize that form > too. > >+The spool file used to use the first form, but this breaks with a v4mapped ipv6 >+hybrid, because the parsing here is not clever. So for spool we now use the >+second form. >+ > Argument: > address points to the string; if there is a port, the '.' in the string > is overwritten with zero to terminate the address; if the string >@@ -728,7 +735,6 @@ > int sep = 0; > uschar *s; > ip_address_item * yield = NULL, * last = NULL, * next; >-BOOL taint = is_tainted(list); > > while ((s = string_nextinlist(&list, &sep, NULL, 0))) > { >@@ -747,7 +753,7 @@ > address above. The field in the ip_address_item is large enough to hold an > IPv6 address. */ > >- next = store_get(sizeof(ip_address_item), taint); >+ next = store_get(sizeof(ip_address_item), list); > next->next = NULL; > Ustrcpy(next->address, s); > next->port = port; >@@ -815,7 +821,7 @@ > { > ip_address_item *running_interfaces = NULL; > >-if (local_interface_data == NULL) >+if (!local_interface_data) > { > void *reset_item = store_mark(); > ip_address_item *dlist = host_build_ifacelist(CUS local_interfaces, >@@ -911,14 +917,14 @@ > struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg; > yield = US inet_ntop(family, &(sk->sin6_addr), CS addr_buffer, > sizeof(addr_buffer)); >- if (portptr != NULL) *portptr = ntohs(sk->sin6_port); >+ if (portptr) *portptr = ntohs(sk->sin6_port); > } > else > { > struct sockaddr_in *sk = (struct sockaddr_in *)arg; > yield = US inet_ntop(family, &(sk->sin_addr), CS addr_buffer, > sizeof(addr_buffer)); >- if (portptr != NULL) *portptr = ntohs(sk->sin_port); >+ if (portptr) *portptr = ntohs(sk->sin_port); > } > } > else >@@ -937,7 +943,7 @@ > if (type < 0) > { > yield = US inet_ntoa(((struct sockaddr_in *)arg)->sin_addr); >- if (portptr != NULL) *portptr = ntohs(((struct sockaddr_in *)arg)->sin_port); >+ if (portptr) *portptr = ntohs(((struct sockaddr_in *)arg)->sin_port); > } > else > yield = US inet_ntoa(*((struct in_addr *)arg)); >@@ -945,7 +951,7 @@ > > /* If there is no buffer, put the string into some new store. */ > >-if (!buffer) buffer = store_get(46, FALSE); >+if (!buffer) buffer = store_get(46, 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 >@@ -1197,9 +1203,9 @@ > c++; > } > >-c[-1] = '\0'; /* drop trailing colon */ >+*--c = '\0'; /* drop trailing colon */ > >-/* debug_printf("%s: D k %d <%s> <%s>\n", __FUNCTION__, k, d, d + 2*(k+1)); */ >+/* debug_printf("%s: D k %d <%s> <%s>\n", __FUNCTION__, k, buffer, buffer + 2*(k+1)); */ > if (k >= 0) > { /* collapse */ > c = d + 2*(k+1); >@@ -1232,14 +1238,11 @@ > host_is_tls_on_connect_port(int port) > { > int sep = 0; >-uschar buffer[32]; >-const uschar *list = tls_in.on_connect_ports; >-uschar *s; >-uschar *end; >+const uschar * list = tls_in.on_connect_ports; > > if (tls_in.on_connect) return TRUE; > >-while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))) >+for (uschar * s, * end; s = string_nextinlist(&list, &sep, NULL, 0); ) > if (Ustrtol(s, &end, 10) == port) > return TRUE; > >@@ -1581,12 +1584,12 @@ > > if (hosts->h_aliases) > { >- int count = 1; >+ int count = 1; /* need 1 more for terminating NULL */ > uschar **ptr; > > for (uschar ** aliases = USS hosts->h_aliases; *aliases; aliases++) count++; > store_pool = POOL_PERM; >- ptr = sender_host_aliases = store_get(count * sizeof(uschar *), FALSE); >+ ptr = sender_host_aliases = store_get(count * sizeof(uschar *), GET_UNTAINTED); > store_pool = POOL_TAINT_PERM; > > for (uschar ** aliases = USS hosts->h_aliases; *aliases; aliases++) >@@ -1690,7 +1693,7 @@ > { > uschar **aptr = NULL; > int ssize = 264; >- int count = 0; >+ int count = 1; /* need 1 more for terminating NULL */ > int old_pool = store_pool; > > sender_host_dnssec = dns_is_secure(dnsa); >@@ -1708,7 +1711,7 @@ > /* Get store for the list of aliases. For compatibility with > gethostbyaddr, we make an empty list if there are none. */ > >- aptr = sender_host_aliases = store_get(count * sizeof(uschar *), FALSE); >+ aptr = sender_host_aliases = store_get(count * sizeof(uschar *), GET_UNTAINTED); > > /* Re-scan and extract the names */ > >@@ -1716,7 +1719,7 @@ > rr; > rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == T_PTR) > { >- uschar * s = store_get(ssize, TRUE); /* names are tainted */ >+ uschar * s = store_get(ssize, GET_TAINTED); /* names are tainted */ > > /* If an overlong response was received, the data will have been > truncated and dn_expand may fail. */ >@@ -1793,7 +1796,7 @@ > { > uschar **aliases = sender_host_aliases; > debug_printf("IP address lookup yielded \"%s\"\n", sender_host_name); >- while (*aliases != NULL) debug_printf(" alias \"%s\"\n", *aliases++); >+ while (*aliases) debug_printf(" alias \"%s\"\n", *aliases++); > } > > /* We need to verify that a forward lookup on the name we found does indeed >@@ -2041,44 +2044,42 @@ > > if (!hostdata) > { >- uschar *error; >+ uschar * error; > switch (error_num) > { >- case HOST_NOT_FOUND: error = US"HOST_NOT_FOUND"; break; >- case TRY_AGAIN: error = US"TRY_AGAIN"; break; >- case NO_RECOVERY: error = US"NO_RECOVERY"; break; >- case NO_DATA: error = US"NO_DATA"; break; >+ case HOST_NOT_FOUND: error = US"HOST_NOT_FOUND"; break; >+ case TRY_AGAIN: error = US"TRY_AGAIN"; temp_error = TRUE; break; >+ case NO_RECOVERY: error = US"NO_RECOVERY"; temp_error = TRUE; break; >+ case NO_DATA: error = US"NO_DATA"; break; > #if NO_DATA != NO_ADDRESS >- case NO_ADDRESS: error = US"NO_ADDRESS"; break; >+ case NO_ADDRESS: error = US"NO_ADDRESS"; break; > #endif > default: error = US"?"; break; > } > >- DEBUG(D_host_lookup) debug_printf("%s returned %d (%s)\n", >+ DEBUG(D_host_lookup) debug_printf("%s(af=%s) returned %d (%s)\n", > f.running_in_test_harness ? "host_fake_gethostbyname" : >- #if HAVE_IPV6 >- #if HAVE_GETIPNODEBYNAME >- af == AF_INET6 ? "getipnodebyname(af=inet6)" : "getipnodebyname(af=inet)", >- #else >- af == AF_INET6 ? "gethostbyname2(af=inet6)" : "gethostbyname2(af=inet)", >- #endif >- #else >- "gethostbyname", >- #endif >- error_num, error); >+#if HAVE_IPV6 >+# if HAVE_GETIPNODEBYNAME >+ "getipnodebyname", >+# else >+ "gethostbyname2", >+# endif >+#else >+ "gethostbyname", >+#endif >+ af == AF_INET ? "inet" : "inet6", error_num, error); > >- if (error_num == TRY_AGAIN || error_num == NO_RECOVERY) temp_error = TRUE; > continue; > } >- if ((hostdata->h_addr_list)[0] == NULL) continue; >+ if (!(hostdata->h_addr_list)[0]) continue; > > /* Replace the name with the fully qualified one if necessary, and fill in > the fully_qualified_name pointer. */ > >- if (hostdata->h_name[0] != 0 && >- Ustrcmp(host->name, hostdata->h_name) != 0) >+ if (hostdata->h_name[0] && Ustrcmp(host->name, hostdata->h_name) != 0) > host->name = string_copy_dnsdomain(US hostdata->h_name); >- if (fully_qualified_name != NULL) *fully_qualified_name = host->name; >+ if (fully_qualified_name) *fully_qualified_name = host->name; > > /* Get the list of addresses. IPv4 and IPv6 addresses can be distinguished > by their different lengths. Scan the list, ignoring any that are to be >@@ -2092,9 +2093,9 @@ > host_ntoa(ipv4_addr? AF_INET:AF_INET6, *addrlist, NULL, NULL); > > #ifndef STAND_ALONE >- if (ignore_target_hosts != NULL && >- verify_check_this_host(&ignore_target_hosts, NULL, host->name, >- text_address, NULL) == OK) >+ if ( ignore_target_hosts >+ && verify_check_this_host(&ignore_target_hosts, NULL, host->name, >+ text_address, NULL) == OK) > { > DEBUG(D_host_lookup) > debug_printf("ignored host %s [%s]\n", host->name, text_address); >@@ -2102,10 +2103,10 @@ > } > #endif > >- /* If this is the first address, last == NULL and we put the data in the >+ /* If this is the first address, last is NULL and we put the data in the > original block. */ > >- if (last == NULL) >+ if (!last) > { > host->address = text_address; > host->port = PORT_NONE; >@@ -2120,7 +2121,7 @@ > > else > { >- host_item *next = store_get(sizeof(host_item), FALSE); >+ host_item *next = store_get(sizeof(host_item), GET_UNTAINTED); > next->name = host->name; > #ifndef DISABLE_TLS > next->certname = host->certname; >@@ -2143,12 +2144,12 @@ > NULL. If temp_error is set, at least one of the lookups gave a temporary error, > so we pass that back. */ > >-if (host->address == NULL) >+if (!host->address) > { > uschar *msg = > #ifndef STAND_ALONE >- (message_id[0] == 0 && smtp_in != NULL)? >- string_sprintf("no IP address found for host %s (during %s)", host->name, >+ !message_id[0] && smtp_in >+ ? string_sprintf("no IP address found for host %s (during %s)", host->name, > smtp_get_connection_info()) : > #endif > string_sprintf("no IP address found for host %s", host->name); >@@ -2196,7 +2197,7 @@ > > RETURN_AGAIN: > { >- #ifndef STAND_ALONE >+#ifndef STAND_ALONE > int rc; > const uschar *save = deliver_domain; > deliver_domain = host->name; /* set $domain */ >@@ -2209,7 +2210,7 @@ > "returning HOST_FIND_FAILED\n", host->name); > return HOST_FIND_FAILED; > } >- #endif >+#endif > return HOST_FIND_AGAIN; > } > } >@@ -2267,6 +2268,7 @@ > BOOL v6_find_again = FALSE; > BOOL dnssec_fail = FALSE; > int i; >+dns_answer * dnsa; > > #ifndef DISABLE_TLS > /* Copy the host name at this point to the value which is used for >@@ -2292,6 +2294,8 @@ > return HOST_FOUND; > } > >+dnsa = store_get_dns_answer(); >+ > /* On an IPv6 system, unless IPv6 is disabled, go round the loop up to twice, > looking for AAAA records the first time. However, unless doing standalone > testing, we force an IPv4 lookup if the domain matches dns_ipv4_lookup global. >@@ -2323,7 +2327,6 @@ > int type = types[i]; > int randoffset = i == (whichrrs & HOST_FIND_IPV4_FIRST ? 1 : 0) > ? 500 : 0; /* Ensures v6/4 sort order */ >- dns_answer * dnsa = store_get_dns_answer(); > dns_scan dnss; > > int rc = dns_lookup_timerwrap(dnsa, host->name, type, fully_qualified_name); >@@ -2346,10 +2349,13 @@ > { > if (i == 0) /* Just tried for an A record, i.e. end of loop */ > { >- if (host->address != NULL) return HOST_FOUND; /* AAAA was found */ >- if (rc == DNS_AGAIN || rc == DNS_FAIL || v6_find_again) >- return HOST_FIND_AGAIN; >- return HOST_FIND_FAILED; /* DNS_NOMATCH or DNS_NODATA */ >+ if (host->address != NULL) >+ i = HOST_FOUND; /* AAAA was found */ >+ else if (rc == DNS_AGAIN || rc == DNS_FAIL || v6_find_again) >+ i = HOST_FIND_AGAIN; >+ else >+ i = HOST_FIND_FAILED; /* DNS_NOMATCH or DNS_NODATA */ >+ goto out; > } > > /* Tried for an AAAA record: remember if this was a temporary >@@ -2452,7 +2458,7 @@ > /* Not a duplicate */ > > new_sort_key = host->mx * 1000 + random_number(500) + randoffset; >- next = store_get(sizeof(host_item), FALSE); >+ next = store_get(sizeof(host_item), GET_UNTAINTED); > > /* New address goes first: insert the new block after the first one > (so as not to disturb the original pointer) but put the new address >@@ -2494,11 +2500,15 @@ > /* Control gets here only if the second lookup (the A record) succeeded. > However, the address may not be filled in if it was ignored. */ > >-return host->address >+i = host->address > ? HOST_FOUND > : dnssec_fail > ? HOST_FIND_SECURITY > : HOST_IGNORED; >+ >+out: >+ store_free_dns_answer(dnsa); >+ return i; > } > > >@@ -2637,7 +2647,7 @@ > #endif > { yield = HOST_FIND_AGAIN; goto out; } > DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA " >- "(domain in srv_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN"); >+ "(domain in srv_fail_domains)\n", rc == DNS_FAIL ? "FAIL":"AGAIN"); > } > } > >@@ -2855,7 +2865,7 @@ > /* Make a new host item and seek the correct insertion place */ > { > int sort_key = precedence * 1000 + weight; >- host_item *next = store_get(sizeof(host_item), FALSE); >+ host_item * next = store_get(sizeof(host_item), GET_UNTAINTED); > next->name = string_copy_dnsdomain(data); > next->address = NULL; > next->port = port; >@@ -3167,6 +3177,7 @@ > out: > > dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */ >+store_free_dns_answer(dnsa); > return yield; > } > >@@ -3195,7 +3206,7 @@ > rc = dns_lookup_timerwrap(dnsa, buffer, T_TLSA, &fullname); > sec = dns_is_secure(dnsa); > DEBUG(D_transport) >- debug_printf("TLSA lookup ret %d %sDNSSEC\n", rc, sec ? "" : "not "); >+ debug_printf("TLSA lookup ret %s %sDNSSEC\n", dns_rc_names[rc], sec ? "" : "not "); > > switch (rc) > { >@@ -3265,6 +3276,7 @@ > > disable_ipv6 = FALSE; > primary_hostname = US""; >+store_init(); > store_pool = POOL_MAIN; > debug_selector = D_host_lookup|D_interface; > debug_file = stdout; >diff -ur exim.orig/src/ip.c exim/src/ip.c >--- exim.orig/src/ip.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/ip.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,7 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ >+/* Copyright (c) The Exim Maintainers 2020 - 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions for doing things with sockets. With the advent of IPv6 this has >@@ -127,8 +127,6 @@ > return sizeof(sin->v6); > } > else >-#else /* HAVE_IPv6 */ >-af = af; /* Avoid compiler warning */ > #endif /* HAVE_IPV6 */ > > /* Setup code when using IPv4 socket. The wildcard address is "". */ >@@ -209,8 +207,6 @@ > s_len = sizeof(s_in6); > } > else >-#else /* HAVE_IPV6 */ >-af = af; /* Avoid compiler warning */ > #endif /* HAVE_IPV6 */ > > /* For an IPv4 address, use an IPv4 sockaddr structure, even on a system with >@@ -467,8 +463,8 @@ > for (int port = portlo; port <= porthi; port++) > if (ip_connect(fd, af, h->address, port, timeout, fastopen_blob) == 0) > { >- if (fd != fd6) close(fd6); >- if (fd != fd4) close(fd4); >+ if (fd6 >= 0 && fd != fd6) close(fd6); >+ if (fd4 >= 0 && fd != fd4) close(fd4); > if (connhost) > { > h->port = port; >@@ -593,9 +589,7 @@ > BOOL > fd_ready(int fd, time_t timelimit) > { >-fd_set select_inset; >-int time_left = timelimit - time(NULL); >-int rc; >+int rc, time_left = timelimit - time(NULL); > > if (time_left <= 0) > { >@@ -606,12 +600,8 @@ > > do > { >- struct timeval tv = { .tv_sec = time_left, .tv_usec = 0 }; >- FD_ZERO (&select_inset); >- FD_SET (fd, &select_inset); >- > /*DEBUG(D_transport) debug_printf("waiting for data on fd\n");*/ >- rc = select(fd + 1, (SELECT_ARG2_TYPE *)&select_inset, NULL, NULL, &tv); >+ rc = poll_one_fd(fd, POLLIN, time_left * 1000); > > /* If some interrupt arrived, just retry. We presume this to be rare, > but it can happen (e.g. the SIGUSR1 signal sent by exiwhat causes >@@ -640,7 +630,7 @@ > /* Checking the FD_ISSET is not enough, if we're interrupted, the > select_inset may still contain the 'input'. */ > } >-while (rc < 0 || !FD_ISSET(fd, &select_inset)); >+while (rc < 0); > return TRUE; > } > >diff -ur exim.orig/src/local_scan.c exim/src/local_scan.c >--- exim.orig/src/local_scan.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/local_scan.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2009 */ >+/* Copyright (c) The Exim Maintainers 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -57,8 +58,6 @@ > int > local_scan(int fd, uschar **return_text) > { >-fd = fd; /* Keep picky compilers happy */ >-return_text = return_text; > return LOCAL_SCAN_ACCEPT; > } > >diff -ur exim.orig/src/local_scan.h exim/src/local_scan.h >--- exim.orig/src/local_scan.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/local_scan.h 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2020 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* This file is the header that is the only Exim header to be included in the >@@ -40,8 +40,8 @@ > each time a new feature is added (in a way that doesn't break backward > compatibility). */ > >-#define LOCAL_SCAN_ABI_VERSION_MAJOR 4 >-#define LOCAL_SCAN_ABI_VERSION_MINOR 1 >+#define LOCAL_SCAN_ABI_VERSION_MAJOR 6 >+#define LOCAL_SCAN_ABI_VERSION_MINOR 0 > #define LOCAL_SCAN_ABI_VERSION \ > LOCAL_SCAN_ABI_VERSION_MAJOR.LOCAL_SCAN_ABI_VERSION_MINOR > >@@ -160,7 +160,7 @@ > extern int body_linecount; /* Line count in body */ > extern int body_zerocount; /* Binary zero count in body */ > extern uschar *expand_string_message; /* Error info for failing expansion */ >-extern uschar *headers_charset; /* Charset for RFC 2047 decoding */ >+extern const uschar *headers_charset; /* Charset for RFC 2047 decoding */ > extern header_line *header_last; /* Final header */ > extern header_line *header_list; /* First header */ > extern BOOL host_checking; /* Set when checking a host */ >@@ -198,7 +198,8 @@ > extern int lss_match_host(uschar *, uschar *, uschar *); > extern void receive_add_recipient(uschar *, int); > extern BOOL receive_remove_recipient(uschar *); >-extern uschar *rfc2047_decode(uschar *, BOOL, uschar *, int, int *, uschar **); >+extern uschar *rfc2047_decode(uschar *, BOOL, const uschar *, int, int *, >+ uschar **); > extern int smtp_fflush(void); > extern void smtp_printf(const char *, BOOL, ...) PRINTF_FUNCTION(1,3); > extern void smtp_vprintf(const char *, BOOL, va_list); >@@ -207,12 +208,12 @@ > string_sprintf_trc(fmt, US __FUNCTION__, __LINE__, __VA_ARGS__) > extern uschar *string_sprintf_trc(const char *, const uschar *, unsigned, ...) ALMOST_PRINTF(1,4); > >-#define store_get(size, tainted) \ >- store_get_3(size, tainted, __FUNCTION__, __LINE__) >-extern void *store_get_3(int, BOOL, const char *, int) ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT; >-#define store_get_perm(size, tainted) \ >- store_get_perm_3(size, tainted, __FUNCTION__, __LINE__) >-extern void *store_get_perm_3(int, BOOL, const char *, int) ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT; >+#define store_get(size, proto_mem) \ >+ store_get_3((size), (proto_mem), __FUNCTION__, __LINE__) >+extern void *store_get_3(int, const void *, const char *, int) ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT; >+#define store_get_perm(size, proto_mem) \ >+ store_get_perm_3((size), (proto_mem), __FUNCTION__, __LINE__) >+extern void *store_get_perm_3(int, const void *, const char *, int) ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT; > > > #if defined(LOCAL_SCAN) || defined(DLFUNC_IMPL) >@@ -229,7 +230,7 @@ > > extern uschar * string_copy_function(const uschar *); > extern uschar * string_copyn_function(const uschar *, int n); >-extern uschar * string_copy_taint_function(const uschar *, BOOL tainted); >+extern uschar * string_copy_taint_function(const uschar *, const void * proto_mem); > extern pid_t child_open_exim_function(int *, const uschar *); > extern pid_t child_open_exim2_function(int *, uschar *, uschar *, const uschar *); > extern pid_t child_open_function(uschar **, uschar **, int, int *, int *, BOOL, const uschar *); >diff -ur exim.orig/src/log.c exim/src/log.c >--- exim.orig/src/log.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/log.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions for writing log files. The code for maintaining datestamped >@@ -12,7 +12,6 @@ > > #include "exim.h" > >-#define LOG_NAME_SIZE 256 > #define MAX_SYSLOG_LEN 870 > > #define LOG_MODE_FILE 1 >@@ -30,7 +29,6 @@ > > static uschar mainlog_name[LOG_NAME_SIZE]; > static uschar rejectlog_name[LOG_NAME_SIZE]; >-static uschar debuglog_name[LOG_NAME_SIZE]; > > static uschar *mainlog_datestamp = NULL; > static uschar *rejectlog_datestamp = NULL; >@@ -55,65 +53,68 @@ > number definitions in macros.h */ > > static const uschar * exim_errstrings[] = { >- US"", >- US"unknown error", >- US"user slash", >- US"exist race", >- US"not regular", >- US"not directory", >- US"bad ugid", >- US"bad mode", >- US"inode changed", >- US"lock failed", >- US"bad address2", >- US"forbid pipe", >- US"forbid file", >- US"forbid reply", >- US"missing pipe", >- US"missing file", >- US"missing reply", >- US"bad redirect", >- US"smtp closed", >- US"smtp format", >- US"spool format", >- US"not absolute", >- US"Exim-imposed quota", >- US"held", >- US"Delivery filter process failure", >- US"Delivery add/remove header failure", >- US"Delivery write incomplete error", >- US"Some expansion failed", >- US"Failed to get gid", >- US"Failed to get uid", >- US"Unset or non-existent transport", >- US"MBX length mismatch", >- US"Lookup failed routing or in smtp tpt", >- US"Can't match format in appendfile", >- US"Creation outside home in appendfile", >- US"Can't check a list; lookup defer", >- US"DNS lookup defer", >- US"Failed to start TLS session", >- US"Mandatory TLS session not started", >- US"Failed to chown a file", >- US"Failed to create a pipe", >- US"When verifying", >- US"When required by client", >- US"Used internally in smtp transport", >- US"RCPT gave 4xx error", >- US"MAIL gave 4xx error", >- US"DATA gave 4xx error", >- US"Negotiation failed for proxy configured host", >- US"Authenticator 'other' failure", >- US"target not supporting SMTPUTF8", >- US"", >- >- US"Not time for routing", >- US"Not time for local delivery", >- US"Not time for any remote host", >- US"Local-only delivery", >- US"Domain in queue_domains", >- US"Transport concurrency limit", >- US"Event requests alternate response", >+ [0] = US"", >+ [- ERRNO_UNKNOWNERROR] = US"unknown error", >+ [- ERRNO_USERSLASH] = US"user slash", >+ [- ERRNO_EXISTRACE] = US"exist race", >+ [- ERRNO_NOTREGULAR] = US"not regular", >+ [- ERRNO_NOTDIRECTORY] = US"not directory", >+ [- ERRNO_BADUGID] = US"bad ugid", >+ [- ERRNO_BADMODE] = US"bad mode", >+ [- ERRNO_INODECHANGED] = US"inode changed", >+ [- ERRNO_LOCKFAILED] = US"lock failed", >+ [- ERRNO_BADADDRESS2] = US"bad address2", >+ [- ERRNO_FORBIDPIPE] = US"forbid pipe", >+ [- ERRNO_FORBIDFILE] = US"forbid file", >+ [- ERRNO_FORBIDREPLY] = US"forbid reply", >+ [- ERRNO_MISSINGPIPE] = US"missing pipe", >+ [- ERRNO_MISSINGFILE] = US"missing file", >+ [- ERRNO_MISSINGREPLY] = US"missing reply", >+ [- ERRNO_BADREDIRECT] = US"bad redirect", >+ [- ERRNO_SMTPCLOSED] = US"smtp closed", >+ [- ERRNO_SMTPFORMAT] = US"smtp format", >+ [- ERRNO_SPOOLFORMAT] = US"spool format", >+ [- ERRNO_NOTABSOLUTE] = US"not absolute", >+ [- ERRNO_EXIMQUOTA] = US"Exim-imposed quota", >+ [- ERRNO_HELD] = US"held", >+ [- ERRNO_FILTER_FAIL] = US"Delivery filter process failure", >+ [- ERRNO_CHHEADER_FAIL] = US"Delivery add/remove header failure", >+ [- ERRNO_WRITEINCOMPLETE] = US"Delivery write incomplete error", >+ [- ERRNO_EXPANDFAIL] = US"Some expansion failed", >+ [- ERRNO_GIDFAIL] = US"Failed to get gid", >+ [- ERRNO_UIDFAIL] = US"Failed to get uid", >+ [- ERRNO_BADTRANSPORT] = US"Unset or non-existent transport", >+ [- ERRNO_MBXLENGTH] = US"MBX length mismatch", >+ [- ERRNO_UNKNOWNHOST] = US"Lookup failed routing or in smtp tpt", >+ [- ERRNO_FORMATUNKNOWN] = US"Can't match format in appendfile", >+ [- ERRNO_BADCREATE] = US"Creation outside home in appendfile", >+ [- ERRNO_LISTDEFER] = US"Can't check a list; lookup defer", >+ [- ERRNO_DNSDEFER] = US"DNS lookup defer", >+ [- ERRNO_TLSFAILURE] = US"Failed to start TLS session", >+ [- ERRNO_TLSREQUIRED] = US"Mandatory TLS session not started", >+ [- ERRNO_CHOWNFAIL] = US"Failed to chown a file", >+ [- ERRNO_PIPEFAIL] = US"Failed to create a pipe", >+ [- ERRNO_CALLOUTDEFER] = US"When verifying", >+ [- ERRNO_AUTHFAIL] = US"When required by client", >+ [- ERRNO_CONNECTTIMEOUT] = US"Used internally in smtp transport", >+ [- ERRNO_RCPT4XX] = US"RCPT gave 4xx error", >+ [- ERRNO_MAIL4XX] = US"MAIL gave 4xx error", >+ [- ERRNO_DATA4XX] = US"DATA gave 4xx error", >+ [- ERRNO_PROXYFAIL] = US"Negotiation failed for proxy configured host", >+ [- ERRNO_AUTHPROB] = US"Authenticator 'other' failure", >+ [- ERRNO_UTF8_FWD] = US"target not supporting SMTPUTF8", >+ [- ERRNO_HOST_IS_LOCAL] = US"host is local", >+ [- ERRNO_TAINT] = US"tainted filename", >+ >+ [- ERRNO_RRETRY] = US"Not time for routing", >+ >+ [- ERRNO_LRETRY] = US"Not time for local delivery", >+ [- ERRNO_HRETRY] = US"Not time for any remote host", >+ [- ERRNO_LOCAL_ONLY] = US"Local-only delivery", >+ [- ERRNO_QUEUE_DOMAIN] = US"Domain in queue_domains", >+ [- ERRNO_TRETRY] = US"Transport concurrency limit", >+ >+ [- ERRNO_EVENT] = US"Event requests alternate response", > }; > > >@@ -265,7 +266,7 @@ > */ > > static int >-log_open_already_exim(uschar * const name) >+log_open_already_exim(const uschar * const name) > { > int fd = -1; > const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK; >@@ -287,8 +288,11 @@ > uschar *lastslash = Ustrrchr(name, '/'); > *lastslash = 0; > created = directory_make(NULL, name, LOG_DIRECTORY_MODE, FALSE); >- DEBUG(D_any) debug_printf("%s log directory %s\n", >- created ? "created" : "failed to create", name); >+ DEBUG(D_any) >+ if (created) >+ debug_printf("created log directory %s\n", name); >+ else >+ debug_printf("failed to create log directory %s: %s\n", name, strerror(errno)); > *lastslash = '/'; > if (created) fd = Uopen(name, flags, LOG_MODE); > } >@@ -298,9 +302,12 @@ > > > >-/* Inspired by OpenSSH's mm_send_fd(). Thanks! */ >+/* Inspired by OpenSSH's mm_send_fd(). Thanks! >+Send fd over socketpair. >+Return: true iff good. >+*/ > >-static int >+static BOOL > log_send_fd(const int sock, const int fd) > { > struct msghdr msg; >@@ -309,8 +316,8 @@ > char buf[CMSG_SPACE(sizeof(int))]; > } cmsgbuf; > struct cmsghdr *cmsg; >-struct iovec vec; > char ch = 'A'; >+struct iovec vec = {.iov_base = &ch, .iov_len = 1}; > ssize_t n; > > memset(&msg, 0, sizeof(msg)); >@@ -324,17 +331,16 @@ > cmsg->cmsg_type = SCM_RIGHTS; > *(int *)CMSG_DATA(cmsg) = fd; > >-vec.iov_base = &ch; >-vec.iov_len = 1; > msg.msg_iov = &vec; > msg.msg_iovlen = 1; > > while ((n = sendmsg(sock, &msg, 0)) == -1 && errno == EINTR); >-if (n != 1) return -1; >-return 0; >+return n == 1; > } > >-/* Inspired by OpenSSH's mm_receive_fd(). Thanks! */ >+/* Inspired by OpenSSH's mm_receive_fd(). Thanks! >+Return fd passed over socketpair, or -1 on error. >+*/ > > static int > log_recv_fd(const int sock) >@@ -345,14 +351,12 @@ > char buf[CMSG_SPACE(sizeof(int))]; > } cmsgbuf; > struct cmsghdr *cmsg; >-struct iovec vec; >-ssize_t n; > char ch = '\0'; >-int fd = -1; >+struct iovec vec = {.iov_base = &ch, .iov_len = 1}; >+ssize_t n; >+int fd; > > memset(&msg, 0, sizeof(msg)); >-vec.iov_base = &ch; >-vec.iov_len = 1; > msg.msg_iov = &vec; > msg.msg_iovlen = 1; > >@@ -360,14 +364,12 @@ > msg.msg_control = &cmsgbuf.buf; > msg.msg_controllen = sizeof(cmsgbuf.buf); > >-while ((n = recvmsg(sock, &msg, 0)) == -1 && errno == EINTR); >+while ((n = recvmsg(sock, &msg, 0)) == -1 && errno == EINTR) ; > if (n != 1 || ch != 'A') return -1; > >-cmsg = CMSG_FIRSTHDR(&msg); >-if (cmsg == NULL) return -1; >+if (!(cmsg = CMSG_FIRSTHDR(&msg))) return -1; > if (cmsg->cmsg_type != SCM_RIGHTS) return -1; >-fd = *(const int *)CMSG_DATA(cmsg); >-if (fd < 0) return -1; >+if ((fd = *(const int *)CMSG_DATA(cmsg)) < 0) return -1; > return fd; > } > >@@ -388,34 +390,32 @@ > */ > > int >-log_open_as_exim(uschar * const name) >+log_open_as_exim(const uschar * const name) > { > int fd = -1; > const uid_t euid = geteuid(); > > if (euid == exim_uid) >- { > fd = log_open_already_exim(name); >- } > else if (euid == root_uid) > { > int sock[2]; > if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock) == 0) > { >- const pid_t pid = exim_fork(US"logfile-open"); >+ const pid_t pid = fork(); > if (pid == 0) > { > (void)close(sock[0]); >- if (setgroups(1, &exim_gid) != 0) _exit(EXIT_FAILURE); >- if (setgid(exim_gid) != 0) _exit(EXIT_FAILURE); >- if (setuid(exim_uid) != 0) _exit(EXIT_FAILURE); >- >- if (getuid() != exim_uid || geteuid() != exim_uid) _exit(EXIT_FAILURE); >- if (getgid() != exim_gid || getegid() != exim_gid) _exit(EXIT_FAILURE); >- >- fd = log_open_already_exim(name); >- if (fd < 0) _exit(EXIT_FAILURE); >- if (log_send_fd(sock[1], fd) != 0) _exit(EXIT_FAILURE); >+ if ( setgroups(1, &exim_gid) != 0 >+ || setgid(exim_gid) != 0 >+ || setuid(exim_uid) != 0 >+ >+ || getuid() != exim_uid || geteuid() != exim_uid >+ || getgid() != exim_gid || getegid() != exim_gid >+ >+ || (fd = log_open_already_exim(name)) < 0 >+ || !log_send_fd(sock[1], fd) >+ ) _exit(EXIT_FAILURE); > (void)close(sock[1]); > _exit(EXIT_SUCCESS); > } >@@ -439,9 +439,7 @@ > if (flags != -1) (void)fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); > } > else >- { > errno = EACCES; >- } > > return fd; > } >@@ -457,7 +455,7 @@ > it does not exist. This may be called recursively on failure, in order to open > the panic log. > >-The directory is in the static variable file_path. This is static so that it >+The directory is in the static variable file_path. This is static so that > the work of sorting out the path is done just once per Exim process. > > Exim is normally configured to avoid running as root wherever possible, the log >@@ -475,7 +473,7 @@ > */ > > static void >-open_log(int *fd, int type, uschar *tag) >+open_log(int * fd, int type, const uschar * tag) > { > uid_t euid; > BOOL ok, ok2; >@@ -492,60 +490,64 @@ > > ok = string_format(buffer, sizeof(buffer), CS file_path, log_names[type]); > >-/* Save the name of the mainlog for rollover processing. Without a datestamp, >-it gets statted to see if it has been cycled. With a datestamp, the datestamp >-will be compared. The static slot for saving it is the same size as buffer, >-and the text has been checked above to fit, so this use of strcpy() is OK. */ >- >-if (type == lt_main && string_datestamp_offset >= 0) >+switch (type) > { >- Ustrcpy(mainlog_name, buffer); >- mainlog_datestamp = mainlog_name + string_datestamp_offset; >- } >- >-/* Ditto for the reject log */ >- >-else if (type == lt_reject && string_datestamp_offset >= 0) >- { >- Ustrcpy(rejectlog_name, buffer); >- rejectlog_datestamp = rejectlog_name + string_datestamp_offset; >- } >- >-/* and deal with the debug log (which keeps the datestamp, but does not >-update it) */ >+ case lt_main: >+ /* Save the name of the mainlog for rollover processing. Without a datestamp, >+ it gets statted to see if it has been cycled. With a datestamp, the datestamp >+ will be compared. The static slot for saving it is the same size as buffer, >+ and the text has been checked above to fit, so this use of strcpy() is OK. */ >+ Ustrcpy(mainlog_name, buffer); >+ if (string_datestamp_offset > 0) >+ mainlog_datestamp = mainlog_name + string_datestamp_offset; >+ break; > >-else if (type == lt_debug) >- { >- Ustrcpy(debuglog_name, buffer); >- if (tag) >- { >- /* this won't change the offset of the datestamp */ >- ok2 = string_format(buffer, sizeof(buffer), "%s%s", >- debuglog_name, tag); >- if (ok2) >- Ustrcpy(debuglog_name, buffer); >- } >- } >+ case lt_reject: >+ /* Ditto for the reject log */ >+ Ustrcpy(rejectlog_name, buffer); >+ if (string_datestamp_offset > 0) >+ rejectlog_datestamp = rejectlog_name + string_datestamp_offset; >+ break; > >-/* Remove any datestamp if this is the panic log. This is rare, so there's no >-need to optimize getting the datestamp length. We remove one non-alphanumeric >-char afterwards if at the start, otherwise one before. */ >+ case lt_debug: >+ /* and deal with the debug log (which keeps the datestamp, but does not >+ update it) */ >+ Ustrcpy(debuglog_name, buffer); >+ if (tag) >+ { >+ if (is_tainted(tag)) >+ die(US"exim: tainted tag for debug log filename", >+ US"Logging failure; please try later"); >+ >+ /* this won't change the offset of the datestamp */ >+ ok2 = string_format(buffer, sizeof(buffer), "%s%s", >+ debuglog_name, tag); >+ if (ok2) >+ Ustrcpy(debuglog_name, buffer); >+ } >+ break; > >-else if (string_datestamp_offset >= 0) >- { >- uschar * from = buffer + string_datestamp_offset; >- uschar * to = from + string_datestamp_length; >+ default: >+ /* Remove any datestamp if this is the panic log. This is rare, so there's no >+ need to optimize getting the datestamp length. We remove one non-alphanumeric >+ char afterwards if at the start, otherwise one before. */ >+ if (string_datestamp_offset >= 0) >+ { >+ uschar * from = buffer + string_datestamp_offset; >+ uschar * to = from + string_datestamp_length; > >- if (from == buffer || from[-1] == '/') >- { >- if (!isalnum(*to)) to++; >- } >- else >- if (!isalnum(from[-1])) from--; >+ if (from == buffer || from[-1] == '/') >+ { >+ if (!isalnum(*to)) to++; >+ } >+ else >+ if (!isalnum(from[-1])) from--; > >- /* This copy is ok, because we know that to is a substring of from. But >- due to overlap we must use memmove() not Ustrcpy(). */ >- memmove(from, to, Ustrlen(to)+1); >+ /* This copy is ok, because we know that to is a substring of from. But >+ due to overlap we must use memmove() not Ustrcpy(). */ >+ memmove(from, to, Ustrlen(to)+1); >+ } >+ break; > } > > /* If the file name is too long, it is an unrecoverable disaster */ >@@ -556,12 +558,8 @@ > > /* We now have the file name. After a successful open, return. */ > >-*fd = log_open_as_exim(buffer); >- >-if (*fd >= 0) >- { >+if ((*fd = log_open_as_exim(buffer)) >= 0) > return; >- } > > euid = geteuid(); > >@@ -729,10 +727,17 @@ > } > > >+/* Close mainlog, unless we do not see a chance to open the file mainlog later >+again. This will happen if we log from a transport process (which has dropped >+privs); something we traditionally avoid, but the introduction of taint-tracking >+and resulting detection of errors is makinng harder. */ >+ > void > mainlog_close(void) > { >-if (mainlogfd < 0) return; >+if (mainlogfd < 0 >+ || !(geteuid() == 0 || geteuid() == exim_uid)) >+ return; > (void)close(mainlogfd); > mainlogfd = -1; > mainlog_inode = 0; >@@ -902,6 +907,11 @@ > "More than one path given in log_file_path: using %s", file_path); > } > >+/* Optionally trigger debug */ >+ >+if (flags & LOG_PANIC && dtrigger_selector & BIT(DTi_panictrigger)) >+ debug_trigger_fire(); >+ > /* If debugging, show all log entries, but don't show headers. Do it all > in one go so that it doesn't get split when multi-processing. */ > >@@ -1249,10 +1259,7 @@ > panic_recurseflag = FALSE; > > if (panic_save_buffer) >- { >- int i = write(paniclogfd, panic_save_buffer, Ustrlen(panic_save_buffer)); >- i = i; /* compiler quietening */ >- } >+ (void) write(paniclogfd, panic_save_buffer, Ustrlen(panic_save_buffer)); > > written_len = write_to_fd_buf(paniclogfd, g->s, g->ptr); > if (written_len != g->ptr) >@@ -1356,8 +1363,9 @@ > */ > > void >-decode_bits(unsigned int *selector, size_t selsize, int *notall, >- uschar *string, bit_table *options, int count, uschar *which, int flags) >+decode_bits(unsigned int * selector, size_t selsize, int * notall, >+ const uschar * string, bit_table * options, int count, uschar * which, >+ int flags) > { > uschar *errmsg; > if (!string) return; >@@ -1366,7 +1374,7 @@ > { > char *end; /* Not uschar */ > memset(selector, 0, sizeof(*selector)*selsize); >- *selector = strtoul(CS string+1, &end, 0); >+ *selector = strtoul(CCS string+1, &end, 0); > if (!*end) return; > errmsg = string_sprintf("malformed numeric %s_selector setting: %s", which, > string); >@@ -1378,9 +1386,9 @@ > else for(;;) > { > BOOL adding; >- uschar *s; >+ const uschar * s; > int len; >- bit_table *start, *end; >+ bit_table * start, * end; > > Uskip_whitespace(&string); > if (!*string) return; >@@ -1469,13 +1477,15 @@ > > The first use of this is in ACL logic, "control = debug/tag=foo/opts=+expand" > which can be combined with conditions, etc, to activate extra logging only >-for certain sources. The second use is inetd wait mode debug preservation. */ >+for certain sources. The second use is inetd wait mode debug preservation. >+ >+It might be nice, in ACL-initiated pretrigger mode, to not create the file >+immediately but only upon a trigger - but we'd need another cmdline option >+to pass the name through child_exxec_exim(). */ > > void >-debug_logging_activate(uschar *tag_name, uschar *opts) >+debug_logging_activate(const uschar * tag_name, const uschar * opts) > { >-int fd = -1; >- > if (debug_file) > { > debug_printf("DEBUGGING ACTIVATED FROM WITHIN CONFIG.\n" >@@ -1483,7 +1493,7 @@ > return; > } > >-if (tag_name != NULL && (Ustrchr(tag_name, '/') != NULL)) >+if (tag_name && (Ustrchr(tag_name, '/') != NULL)) > { > log_write(0, LOG_MAIN|LOG_PANIC, "debug tag may not contain a '/' in: %s", > tag_name); >@@ -1501,24 +1511,43 @@ > > if (!*file_path) set_file_path(); > >-open_log(&fd, lt_debug, tag_name); >+open_log(&debug_fd, lt_debug, tag_name); > >-if (fd != -1) >- debug_file = fdopen(fd, "w"); >+if (debug_fd != -1) >+ debug_file = fdopen(debug_fd, "w"); > else > log_write(0, LOG_MAIN|LOG_PANIC, "unable to open debug log"); > } > > > void >-debug_logging_stop(void) >+debug_logging_from_spool(const uschar * filename) >+{ >+if (debug_fd < 0) >+ { >+ Ustrncpy(debuglog_name, filename, sizeof(debuglog_name)); >+ if ((debug_fd = log_open_as_exim(filename)) >= 0) >+ debug_file = fdopen(debug_fd, "w"); >+ DEBUG(D_deliver) debug_printf("debug enabled by spoolfile\n"); >+ } >+/* >+else DEBUG(D_deliver) >+ debug_printf("debug already active; ignoring spoolfile '%s'\n", filename); >+*/ >+} >+ >+ >+void >+debug_logging_stop(BOOL kill) > { >+debug_pretrigger_discard(); > if (!debug_file || !debuglog_name[0]) return; > > debug_selector = 0; > fclose(debug_file); > debug_file = NULL; >-unlink_log(lt_debug); >+debug_fd = -1; >+if (kill) unlink_log(lt_debug); > } > > >diff -ur exim.orig/src/lookupapi.h exim/src/lookupapi.h >--- exim.orig/src/lookupapi.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookupapi.h 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2015 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -42,16 +42,19 @@ > void (*tidy)(void); /* tidy function */ > uschar *(*quote)( /* quoting function */ > uschar *, /* string to quote */ >- uschar *); /* additional data from quote name */ >- void (*version_report)( /* diagnostic function */ >- FILE *); /* fh to write to */ >+ uschar *, /* additional data from quote name */ >+ unsigned); /* lookup type index */ >+ gstring * (*version_report)( /* diagnostic function */ >+ gstring *); /* string to appand to */ > } lookup_info; > > /* This magic number is used by the following lookup_module_info structure > for checking API compatibility. It used to be equivalent to the string"LMM3" */ >-#define LOOKUP_MODULE_INFO_MAGIC 0x4c4d4933 >+#define LOOKUP_MODULE_INFO_MAGIC 0x4c4d4935 > /* Version 2 adds: version_report */ > /* Version 3 change: non/cache becomes TTL in seconds */ >+/* Version 4 add: index on quoting function */ >+/* Version 5 change: version report now adds to a gstring */ > > typedef struct lookup_module_info { > uint magic; >diff -ur exim.orig/src/lookups/cdb.c exim/src/lookups/cdb.c >--- exim.orig/src/lookups/cdb.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/cdb.c 2022-06-23 16:41:10.000000000 +0300 >@@ -6,8 +6,8 @@ > * Exim - CDB database lookup module > * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > * >+ * Copyright (c) The Exim Maintainers 2020 - 2022 > * Copyright (c) 1998 Nigel Metheringham, Planet Online Ltd >- * Copyright (c) The Exim Maintainers 2020 > * > * This program is free software; you can redistribute it and/or > * modify it under the terms of the GNU General Public License >@@ -157,19 +157,14 @@ > > if ((fileno = Uopen(filename, O_RDONLY, 0)) < 0) > { >- int save_errno = errno; >- *errmsg = string_open_failed(errno, "%s for cdb lookup", filename); >- errno = save_errno; >+ *errmsg = string_open_failed("%s for cdb lookup", filename); > return NULL; > } > > if (fstat(fileno, &statbuf) != 0) > { >- int save_errno = errno; >- *errmsg = string_open_failed(errno, >- "fstat(%s) failed - cannot do cdb lookup", >+ *errmsg = string_open_failed("fstat(%s) failed - cannot do cdb lookup", > filename); >- errno = save_errno; > return NULL; > } > >@@ -178,16 +173,12 @@ > > if (statbuf.st_size < CDB_HASH_TABLE) > { >- int save_errno = errno; >- *errmsg = string_open_failed(errno, >- "%s too short for cdb lookup", >- filename); >- errno = save_errno; >+ *errmsg = string_open_failed("%s too short for cdb lookup", filename); > return NULL; > } > > /* Having got a file open we need the structure to put things in */ >-cdbp = store_get(sizeof(struct cdb_state), FALSE); >+cdbp = store_get(sizeof(struct cdb_state), GET_UNTAINTED); > /* store_get() does not return if memory was not available... */ > /* preload the structure.... */ > cdbp->fileno = fileno; >@@ -222,7 +213,7 @@ > /* get a buffer to stash the basic offsets in - this should speed > things up a lot - especially on multiple lookups */ > >-cdbp->cdb_offsets = store_get(CDB_HASH_TABLE, FALSE); >+cdbp->cdb_offsets = store_get(CDB_HASH_TABLE, GET_UNTAINTED); > > /* now fill the buffer up... */ > >@@ -231,8 +222,7 @@ > /* read of hash table failed, oh dear, oh..... time to give up I think.... > call the close routine (deallocs the memory), and return NULL */ > >- *errmsg = string_open_failed(errno, >- "cannot read header from %s for cdb lookup", >+ *errmsg = string_open_failed("cannot read header from %s for cdb lookup", > filename); > cdb_close(cdbp); > return NULL; >@@ -281,9 +271,6 @@ > hash_offlen, > hash_slotnm; > >-/* Keep picky compilers happy */ >-do_cache = do_cache; >- > key_hash = cdb_hash(keystring, key_len); > > hash_offset_entry = CDB_HASH_ENTRY * (key_hash & CDB_HASH_MASK); >@@ -356,7 +343,7 @@ > /* ... and the returned result. Assume it is not > tainted, lacking any way of telling. */ > >- *result = store_get(item_dat_len + 1, FALSE); >+ *result = store_get(item_dat_len + 1, GET_UNTAINTED); > memcpy(*result, item_ptr, item_dat_len); > (*result)[item_dat_len] = 0; > return OK; >@@ -400,7 +387,7 @@ > if (item_key_len == key_len) > { /* finally check if key matches */ > rmark reset_point = store_mark(); >- uschar * item_key = store_get(key_len, TRUE); /* keys liable to be tainted */ >+ uschar * item_key = store_get(key_len, GET_TAINTED); /* keys liable to be tainted */ > > if (cdb_bread(cdbp->fileno, item_key, key_len) == -1) return DEFER; > if (Ustrncmp(keystring, item_key, key_len) == 0) >@@ -414,7 +401,7 @@ > /* then we build a new result string. We know we have enough > memory so disable Coverity errors about the tainted item_dat_ken */ > >- *result = store_get(item_dat_len + 1, FALSE); >+ *result = store_get(item_dat_len + 1, GET_UNTAINTED); > /* coverity[tainted_data] */ > if (cdb_bread(cdbp->fileno, *result, item_dat_len) == -1) > return DEFER; >@@ -471,12 +458,13 @@ > > #include "../version.h" > >-void >-cdb_version_report(FILE *f) >+gstring * >+cdb_version_report(gstring * g) > { > #ifdef DYNLOOKUP >-fprintf(f, "Library version: CDB: Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, "Library version: CDB: Exim version %s\n", EXIM_VERSION_STR); > #endif >+return g; > } > > >diff -ur exim.orig/src/lookups/dbmdb.c exim/src/lookups/dbmdb.c >--- exim.orig/src/lookups/dbmdb.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/dbmdb.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >@@ -21,16 +21,11 @@ > { > uschar * dirname = string_copy(filename); > uschar * s; >-EXIM_DB *yield = NULL; >+EXIM_DB * yield = NULL; > > if ((s = Ustrrchr(dirname, '/'))) *s = '\0'; >-EXIM_DBOPEN(filename, dirname, O_RDONLY, 0, &yield); >-if (!yield) >- { >- int save_errno = errno; >- *errmsg = string_open_failed(errno, "%s as a %s file", filename, EXIM_DBTYPE); >- errno = save_errno; >- } >+if (!(yield = exim_dbopen(filename, dirname, O_RDONLY, 0))) >+ *errmsg = string_open_failed("%s as a %s file", filename, EXIM_DBTYPE); > return yield; > } > >@@ -52,7 +47,6 @@ > gid_t *owngroups, uschar **errmsg) > { > int rc; >-handle = handle; /* Keep picky compilers happy */ > > #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM) > rc = lf_check_file(-1, filename, S_IFREG, modemask, owners, owngroups, >@@ -98,19 +92,17 @@ > EXIM_DB *d = (EXIM_DB *)handle; > EXIM_DATUM key, data; > >-filename = filename; /* Keep picky compilers happy */ >-errmsg = errmsg; >-do_cache = do_cache; >- >-EXIM_DATUM_INIT(key); /* Some DBM libraries require datums to */ >-EXIM_DATUM_INIT(data); /* be cleared before use. */ >-EXIM_DATUM_DATA(key) = CS keystring; >-EXIM_DATUM_SIZE(key) = length + 1; >+exim_datum_init(&key); /* Some DBM libraries require datums to */ >+exim_datum_init(&data); /* be cleared before use. */ >+length++; >+exim_datum_data_set(&key, >+ memcpy(store_get(length, keystring), keystring, length)); /* key can have embedded NUL */ >+exim_datum_size_set(&key, length); > >-if (EXIM_DBGET(d, key, data)) >+if (exim_dbget(d, &key, &data)) > { >- *result = string_copyn(US EXIM_DATUM_DATA(data), EXIM_DATUM_SIZE(data)); >- EXIM_DATUM_FREE(data); /* Some DBM libraries need a free() call */ >+ *result = string_copyn(exim_datum_data_get(&data), exim_datum_size_get(&data)); >+ exim_datum_free(&data); /* Some DBM libraries need a free() call */ > return OK; > } > return FAIL; >@@ -158,7 +150,7 @@ > or less than, the length of the delimited list passed in + 1. */ > > buflen = length + 3; >-key_buffer = store_get(buflen, is_tainted(keystring)); >+key_buffer = store_get(buflen, keystring); > > key_buffer[0] = '\0'; > >@@ -223,7 +215,7 @@ > void > static dbmdb_close(void *handle) > { >-EXIM_DBCLOSE((EXIM_DB *)handle); >+exim_dbclose((EXIM_DB *)handle); > } > > >@@ -236,12 +228,13 @@ > > #include "../version.h" > >-void >-dbm_version_report(FILE *f) >+gstring * >+dbm_version_report(gstring * g) > { > #ifdef DYNLOOKUP >-fprintf(f, "Library version: DBM: Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, "Library version: DBM: Exim version %s\n", EXIM_VERSION_STR); > #endif >+return g; > } > > >diff -ur exim.orig/src/lookups/dnsdb.c exim/src/lookups/dnsdb.c >--- exim.orig/src/lookups/dnsdb.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/dnsdb.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >@@ -80,8 +80,6 @@ > static void * > dnsdb_open(const uschar * filename, uschar **errmsg) > { >-filename = filename; /* Keep picky compilers happy */ >-errmsg = errmsg; /* Ditto */ > return (void *)(-1); /* Any non-0 value */ > } > >@@ -155,11 +153,6 @@ > > gstring * yield = string_get(256); > >-handle = handle; /* Keep picky compilers happy */ >-filename = filename; >-length = length; >-do_cache = do_cache; >- > /* If the string starts with '>' we change the output separator. > If it's followed by ';' or ',' we set the TXT output separator. */ > >@@ -197,7 +190,8 @@ > else > { > *errmsg = US"unsupported dnsdb defer behaviour"; >- return DEFER; >+ rc = DEFER; >+ goto out; > } > } > else if (strncmpic(keystring, US"dnssec_", 7) == 0) >@@ -212,7 +206,8 @@ > else > { > *errmsg = US"unsupported dnsdb dnssec behaviour"; >- return DEFER; >+ rc = DEFER; >+ goto out; > } > } > else if (strncmpic(keystring, US"retrans_", 8) == 0) >@@ -221,7 +216,8 @@ > if ((timeout_sec = readconf_readtime(keystring += 8, ',', FALSE)) <= 0) > { > *errmsg = US"unsupported dnsdb timeout value"; >- return DEFER; >+ rc = DEFER; >+ goto out; > } > dns_retrans = timeout_sec; > while (*keystring != ',') keystring++; >@@ -232,7 +228,8 @@ > if ((retries = (int)strtol(CCS keystring + 6, CSS &keystring, 0)) < 0) > { > *errmsg = US"unsupported dnsdb retry count"; >- return DEFER; >+ rc = DEFER; >+ goto out; > } > dns_retry = retries; > } >@@ -243,7 +240,8 @@ > if (*keystring++ != ',') > { > *errmsg = US"dnsdb modifier syntax error"; >- return DEFER; >+ rc = DEFER; >+ goto out; > } > while (isspace(*keystring)) keystring++; > } >@@ -271,7 +269,8 @@ > if (i >= nelem(type_names)) > { > *errmsg = US"unsupported DNS record type"; >- return DEFER; >+ rc = DEFER; >+ goto out; > } > > keystring = equals + 1; >@@ -366,7 +365,8 @@ > dns_retrans = save_retrans; > dns_retry = save_retry; > dns_init(FALSE, FALSE, FALSE); /* clr dnssec bit */ >- return DEFER; /* always defer */ >+ rc = DEFER; /* always defer */ >+ goto out; > } > if (defer_mode == PASS) failrc = DEFER; /* defer only if all do */ > continue; /* treat defer as fail */ >@@ -556,10 +556,18 @@ > dns_retry = save_retry; > dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */ > >-if (!yield || !yield->ptr) return failrc; >+if (!yield || !yield->ptr) >+ rc = failrc; >+else >+ { >+ *result = string_from_gstring(yield); >+ rc = OK; >+ } >+ >+out: > >-*result = string_from_gstring(yield); >-return OK; >+store_free_dns_answer(dnsa); >+return rc; > } > > >@@ -572,12 +580,13 @@ > > #include "../version.h" > >-void >-dnsdb_version_report(FILE *f) >+gstring * >+dnsdb_version_report(gstring * g) > { > #ifdef DYNLOOKUP >-fprintf(f, "Library version: DNSDB: Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, "Library version: DNSDB: Exim version %s\n", EXIM_VERSION_STR); > #endif >+return g; > } > > >diff -ur exim.orig/src/lookups/dsearch.c exim/src/lookups/dsearch.c >--- exim.orig/src/lookups/dsearch.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/dsearch.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2015 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* The idea for this code came from Matthew Byng-Maddick, but his original has >@@ -31,9 +31,7 @@ > DIR * dp = exim_opendir(dirname); > if (!dp) > { >- int save_errno = errno; >- *errmsg = string_open_failed(errno, "%s for directory search", dirname); >- errno = save_errno; >+ *errmsg = string_open_failed("%s for directory search", dirname); > return NULL; > } > closedir(dp); >@@ -86,10 +84,6 @@ > uschar * filename; > unsigned flags = 0; > >-handle = handle; /* Keep picky compilers happy */ >-length = length; >-do_cache = do_cache; >- > if (Ustrchr(keystring, '/') != 0) > { > *errmsg = string_sprintf("key for dsearch lookup contains a slash: %s", >@@ -130,7 +124,7 @@ > { > /* Since the filename exists in the filesystem, we can return a > non-tainted result. */ >- *result = string_copy_taint(flags & RET_FULL ? filename : keystring, FALSE); >+ *result = string_copy_taint(flags & RET_FULL ? filename : keystring, GET_UNTAINTED); > return OK; > } > >@@ -164,12 +158,13 @@ > > #include "../version.h" > >-void >-dsearch_version_report(FILE *f) >+gstring * >+dsearch_version_report(gstring * g) > { > #ifdef DYNLOOKUP >-fprintf(f, "Library version: dsearch: Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, "Library version: dsearch: Exim version %s\n", EXIM_VERSION_STR); > #endif >+return g; > } > > >diff -ur exim.orig/src/lookups/ibase.c exim/src/lookups/ibase.c >--- exim.orig/src/lookups/ibase.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/ibase.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* The code in this module was contributed by Ard Biesheuvel. */ >@@ -177,7 +177,7 @@ > } > else > { >- cn = store_get(sizeof(ibase_connection), FALSE); >+ cn = store_get(sizeof(ibase_connection), GET_UNTAINTED); > cn->server = server_copy; > cn->dbh = NULL; > cn->transh = NULL; >@@ -252,7 +252,7 @@ > > /* Lacking any information, assume that the data is untainted */ > reset_point = store_mark(); >-out_sqlda = store_get(XSQLDA_LENGTH(1), FALSE); >+out_sqlda = store_get(XSQLDA_LENGTH(1), GET_UNTAINTED); > out_sqlda->version = SQLDA_VERSION1; > out_sqlda->sqln = 1; > >@@ -272,7 +272,7 @@ > /* 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), FALSE); >+ XSQLDA *new_sqlda = store_get(XSQLDA_LENGTH(out_sqlda->sqld), GET_UNTAINTED); > if (isc_dsql_describe > (status, &stmth, out_sqlda->version, new_sqlda)) > { >@@ -294,46 +294,46 @@ > switch (var->sqltype & ~1) > { > case SQL_VARYING: >- var->sqldata = CS store_get(sizeof(char) * var->sqllen + 2, FALSE); >+ 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, FALSE); >+ var->sqldata = CS store_get(sizeof(char) * var->sqllen, GET_UNTAINTED); > break; > case SQL_SHORT: >- var->sqldata = CS store_get(sizeof(short), FALSE); >+ var->sqldata = CS store_get(sizeof(short), GET_UNTAINTED); > break; > case SQL_LONG: >- var->sqldata = CS store_get(sizeof(ISC_LONG), FALSE); >+ 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), FALSE); >+ var->sqldata = CS store_get(sizeof(ISC_INT64), GET_UNTAINTED); > break; > #endif > case SQL_FLOAT: >- var->sqldata = CS store_get(sizeof(float), FALSE); >+ var->sqldata = CS store_get(sizeof(float), GET_UNTAINTED); > break; > case SQL_DOUBLE: >- var->sqldata = CS store_get(sizeof(double), FALSE); >+ var->sqldata = CS store_get(sizeof(double), GET_UNTAINTED); > break; > #ifdef SQL_TIMESTAMP > case SQL_DATE: >- var->sqldata = CS store_get(sizeof(ISC_QUAD), FALSE); >+ var->sqldata = CS store_get(sizeof(ISC_QUAD), GET_UNTAINTED); > break; > #else > case SQL_TIMESTAMP: >- var->sqldata = CS store_get(sizeof(ISC_TIMESTAMP), FALSE); >+ var->sqldata = CS store_get(sizeof(ISC_TIMESTAMP), GET_UNTAINTED); > break; > case SQL_TYPE_DATE: >- var->sqldata = CS store_get(sizeof(ISC_DATE), FALSE); >+ var->sqldata = CS store_get(sizeof(ISC_DATE), GET_UNTAINTED); > break; > case SQL_TYPE_TIME: >- var->sqldata = CS store_get(sizeof(ISC_TIME), FALSE); >+ var->sqldata = CS store_get(sizeof(ISC_TIME), GET_UNTAINTED); > break; > #endif > } > if (var->sqltype & 1) >- var->sqlind = (short *) store_get(sizeof(short), FALSE); >+ var->sqlind = (short *) store_get(sizeof(short), GET_UNTAINTED); > } > > /* finally, we're ready to execute the statement */ >@@ -457,14 +457,10 @@ > int sep = 0; > uschar *server; > uschar *list = ibase_servers; >-uschar buffer[512]; >- >-/* Keep picky compilers happy */ >-do_cache = do_cache; > > DEBUG(D_lookup) debug_printf_indent("Interbase query: %s\n", query); > >-while ((server = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))) >+while ((server = string_nextinlist(&list, &sep, NULL, 0))) > { > BOOL defer_break = FALSE; > int rc = perform_ibase_search(query, server, result, errmsg, &defer_break); >@@ -496,51 +492,32 @@ > 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, uschar * opt) >+static uschar * >+ibase_quote(uschar * s, uschar * opt, unsigned idx) > { >- register int c; >- int count = 0; >- uschar *t = s; >- uschar *quoted; >- >- if (opt != NULL) >- return NULL; /* No options recognized */ >- >- while ((c = *t++) != 0) >- if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL) >- count++; >- >- if (count == 0) >- return s; >- t = quoted = store_get(Ustrlen(s) + count + 1, FALSE); >- >- while ((c = *s++) != 0) { >- if (Ustrchr("'", c) != NULL) { >- *t++ = '\''; >- *t++ = '\''; >-/* switch(c) >- { >- case '\n': *t++ = 'n'; >- break; >- case '\t': *t++ = 't'; >- break; >- case '\r': *t++ = 'r'; >- break; >- case '\b': *t++ = 'b'; >- break; >- default: *t++ = c; >- break; >- }*/ >- } else >- *t++ = c; >- } >+int c; >+int count = 0; >+uschar * t = s, * quoted; >+ >+if (opt) >+ return NULL; /* No options recognized */ >+ >+while ((c = *t++)) >+ if (c == '\'') count++; >+ >+t = quoted = store_get_quoted(Ustrlen(s) + count + 1, s, idx); >+ >+while ((c = *s++)) >+ if (c == '\'') { *t++ = '\''; *t++ = '\''; } >+ else *t++ = c; > >- *t = 0; >- return quoted; >+*t = 0; >+return quoted; > } > > >@@ -552,12 +529,13 @@ > > #include "../version.h" > >-void >-ibase_version_report(FILE *f) >+gstring * >+ibase_version_report(gstring * g) > { > #ifdef DYNLOOKUP >-fprintf(f, "Library version: ibase: Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, "Library version: ibase: Exim version %s\n", EXIM_VERSION_STR)); > #endif >+return g; > } > > >diff -ur exim.orig/src/lookups/json.c exim/src/lookups/json.c >--- exim.orig/src/lookups/json.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/json.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,7 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >-/* Copyright (c) Jeremy Harris 2019-2020 */ >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ >+/* Copyright (c) Jeremy Harris 2019 - 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >@@ -23,7 +24,7 @@ > static void * > json_malloc(size_t nbytes) > { >-void * p = store_get((int)nbytes, FALSE); >+void * p = store_get((int)nbytes, GET_UNTAINTED); > /* debug_printf("%s %d: %p\n", __FUNCTION__, (int)nbytes, p); */ > return p; > } >@@ -47,12 +48,7 @@ > json_set_alloc_funcs(json_malloc, json_free); > > if (!(f = Ufopen(filename, "rb"))) >- { >- int save_errno = errno; >- *errmsg = string_open_failed(errno, "%s for json search", filename); >- errno = save_errno; >- return NULL; >- } >+ *errmsg = string_open_failed("%s for json search", filename); > return f; > } > >@@ -89,9 +85,6 @@ > uschar * key; > int sep = 0; > >-length = length; /* Keep picky compilers happy */ >-do_cache = do_cache; /* Keep picky compilers happy */ >- > rewind(f); > if (!(j = json_loadf(f, 0, &jerr))) > { >@@ -165,10 +158,10 @@ > > #include "../version.h" > >-void >-json_version_report(FILE *f) >+gstring * >+json_version_report(gstring * g) > { >-fprintf(f, "Library version: json: Jansonn version %s\n", JANSSON_VERSION); >+return string_fmt_append(g, "Library version: json: Jansonn version %s\n", JANSSON_VERSION); > } > > >diff -ur exim.orig/src/lookups/ldap.c exim/src/lookups/ldap.c >--- exim.orig/src/lookups/ldap.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/ldap.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Many thanks to Stuart Lynne for contributing the original code for this >@@ -496,7 +496,7 @@ > > /* Now add this connection to the chain of cached connections */ > >- lcp = store_get(sizeof(LDAP_CONNECTION), FALSE); >+ lcp = store_get(sizeof(LDAP_CONNECTION), GET_UNTAINTED); > lcp->host = host ? string_copy(host) : NULL; > lcp->bound = FALSE; > lcp->user = NULL; >@@ -1091,7 +1091,6 @@ > uschar *user = NULL; > uschar *password = NULL; > uschar *local_servers = NULL; >-uschar *server; > const uschar *list; > > while (isspace(*url)) url++; >@@ -1250,13 +1249,13 @@ > &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference, > referrals); > >-/* Loop through the default servers until OK or FAIL. Use local_servers list >- * if defined in the lookup, otherwise use the global default list */ >-list = !local_servers ? eldap_default_servers : local_servers; >-while ((server = string_nextinlist(&list, &sep, NULL, 0))) >+/* Loop through the servers until OK or FAIL. Use local_servers list >+if defined in the lookup, otherwise use the global default list */ >+ >+list = local_servers ? local_servers : eldap_default_servers; >+for (uschar * server; server = string_nextinlist(&list, &sep, NULL, 0); ) > { >- int rc; >- int port = 0; >+ int rc, port = 0; > uschar *colon = Ustrchr(server, ':'); > if (colon) > { >@@ -1287,8 +1286,6 @@ > int length, uschar ** result, uschar ** errmsg, uint * do_cache, > const uschar * opts) > { >-/* Keep picky compilers happy */ >-do_cache = do_cache; > return(control_ldap_search(ldap_url, SEARCH_LDAP_SINGLE, result, errmsg)); > } > >@@ -1297,8 +1294,6 @@ > int length, uschar ** result, uschar ** errmsg, uint * do_cache, > const uschar * opts) > { >-/* Keep picky compilers happy */ >-do_cache = do_cache; > return(control_ldap_search(ldap_url, SEARCH_LDAP_MULTIPLE, result, errmsg)); > } > >@@ -1307,8 +1302,6 @@ > int length, uschar ** result, uschar ** errmsg, uint * do_cache, > const uschar * opts) > { >-/* Keep picky compilers happy */ >-do_cache = do_cache; > return(control_ldap_search(ldap_url, SEARCH_LDAP_DN, result, errmsg)); > } > >@@ -1316,8 +1309,6 @@ > eldapauth_find(void * handle, const uschar * filename, const uschar * ldap_url, > int length, uschar ** result, uschar ** errmsg, uint * do_cache) > { >-/* Keep picky compilers happy */ >-do_cache = do_cache; > return(control_ldap_search(ldap_url, SEARCH_LDAP_AUTH, result, errmsg)); > } > >@@ -1347,16 +1338,13 @@ > static void > eldap_tidy(void) > { >-LDAP_CONNECTION *lcp = NULL; > eldap_dn = NULL; > >-while ((lcp = ldap_connections) != NULL) >+for (LDAP_CONNECTION *lcp; lcp = ldap_connections; ldap_connections = lcp->next) > { >- DEBUG(D_lookup) debug_printf_indent("unbind LDAP connection to %s:%d\n", lcp->host, >- lcp->port); >- if(lcp->bound == TRUE) >- ldap_unbind(lcp->ld); >- ldap_connections = lcp->next; >+ DEBUG(D_lookup) debug_printf_indent("unbind LDAP connection to %s:%d\n", >+ lcp->host, lcp->port); >+ if(lcp->bound) ldap_unbind(lcp->ld); > } > } > >@@ -1417,6 +1405,7 @@ > s the string to be quoted > opt additional option text or NULL if none > only "dn" is recognized >+ idx lookup type index > > Returns: the processed string or NULL for a bad option > */ >@@ -1442,18 +1431,15 @@ > > > static uschar * >-eldap_quote(uschar *s, uschar *opt) >+eldap_quote(uschar * s, uschar * opt, unsigned idx) > { >-register int c; >-int count = 0; >-int len = 0; >+int c, count = 0, len = 0; > BOOL dn = FALSE; >-uschar *t = s; >-uschar *quoted; >+uschar * t = s, * quoted; > > /* Test for a DN quotation. */ > >-if (opt != NULL) >+if (opt) > { > if (Ustrcmp(opt, "dn") != 0) return NULL; /* No others recognized */ > dn = TRUE; >@@ -1466,24 +1452,25 @@ > possibly escaped character. The really fast way would be just to test for > non-alphanumerics, but it is probably better to spot a few others that are > never escaped, because if there are no specials at all, we can avoid copying >-the string. */ >+the string. >+XXX No longer true; we always copy, to support quoted-enforcement */ > >-while ((c = *t++) != 0) >+while ((c = *t++)) > { > len++; > if (!isalnum(c) && Ustrchr(ALWAYS_LITERAL, c) == NULL) count += 5; > } >-if (count == 0) return s; >+/*if (count == 0) return s;*/ > > /* Get sufficient store to hold the quoted string */ > >-t = quoted = store_get(len + count + 1, is_tainted(s)); >+t = quoted = store_get_quoted(len + count + 1, s, idx); > > /* Handle plain quote_ldap */ > > if (!dn) > { >- while ((c = *s++) != 0) >+ while ((c = *s++)) > { > if (!isalnum(c)) > { >@@ -1508,7 +1495,7 @@ > > else > { >- uschar *ss = s + len; >+ uschar * ss = s + len; > > /* Find the last char before any trailing spaces */ > >@@ -1570,12 +1557,13 @@ > > #include "../version.h" > >-void >-ldap_version_report(FILE *f) >+gstring * >+ldap_version_report(gstring * g) > { > #ifdef DYNLOOKUP >-fprintf(f, "Library version: LDAP: Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, "Library version: LDAP: Exim version %s\n", EXIM_VERSION_STR); > #endif >+return g; > } > > >diff -ur exim.orig/src/lookups/lf_sqlperform.c exim/src/lookups/lf_sqlperform.c >--- exim.orig/src/lookups/lf_sqlperform.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/lf_sqlperform.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >diff -ur exim.orig/src/lookups/lmdb.c exim/src/lookups/lmdb.c >--- exim.orig/src/lookups/lmdb.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/lmdb.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,13 +2,13 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 2016 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" > >-#ifdef EXPERIMENTAL_LMDB >+#ifdef LOOKUP_LMDB > > #include <lmdb.h> > >@@ -31,7 +31,7 @@ > int ret, save_errno; > const uschar * errstr; > >-lmdb_p = store_get(sizeof(Lmdbstrct), FALSE); >+lmdb_p = store_get(sizeof(Lmdbstrct), GET_UNTAINTED); > lmdb_p->txn = NULL; > > if ((ret = mdb_env_create(&db_env))) >@@ -129,14 +129,15 @@ > > #include "../version.h" > >-void >-lmdb_version_report(FILE * f) >+gstring * >+lmdb_version_report(gstring * g) > { >-fprintf(f, "Library version: LMDB: Compile: %d.%d.%d\n", >- MDB_VERSION_MAJOR, MDB_VERSION_MINOR, MDB_VERSION_PATCH); >+g = string_fmt_append(g, "Library version: LMDB: Compile: %d.%d.%d\n", >+ MDB_VERSION_MAJOR, MDB_VERSION_MINOR, MDB_VERSION_PATCH); > #ifdef DYNLOOKUP >-fprintf(f, " Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, " Exim version %s\n", EXIM_VERSION_STR); > #endif >+return g; > } > > static lookup_info lmdb_lookup_info = { >@@ -158,4 +159,4 @@ > static lookup_info *_lookup_list[] = { &lmdb_lookup_info }; > lookup_module_info lmdb_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 }; > >-#endif /* EXPERIMENTAL_LMDB */ >+#endif /* LOOKUP_LMDB */ >diff -ur exim.orig/src/lookups/lsearch.c exim/src/lookups/lsearch.c >--- exim.orig/src/lookups/lsearch.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/lsearch.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >@@ -29,14 +29,9 @@ > static void * > lsearch_open(const uschar * filename, uschar ** errmsg) > { >-FILE *f = Ufopen(filename, "rb"); >-if (f == NULL) >- { >- int save_errno = errno; >- *errmsg = string_open_failed(errno, "%s for linear search", filename); >- errno = save_errno; >- return NULL; >- } >+FILE * f = Ufopen(filename, "rb"); >+if (!f) >+ *errmsg = string_open_failed("%s for linear search", filename); > return f; > } > >@@ -74,32 +69,38 @@ > static int > internal_lsearch_find(void * handle, const uschar * filename, > const uschar * keystring, int length, uschar ** result, uschar ** errmsg, >- int type) >+ int type, const uschar * opts) > { >-FILE *f = (FILE *)handle; >-BOOL last_was_eol = TRUE; >-BOOL this_is_eol = TRUE; >+FILE *f = handle; >+BOOL ret_full = FALSE; > int old_pool = store_pool; > rmark reset_point = NULL; > uschar buffer[4096]; > >+if (opts) >+ { >+ int sep = ','; >+ uschar * ele; >+ >+ while ((ele = string_nextinlist(&opts, &sep, NULL, 0))) >+ if (Ustrcmp(ele, "ret=full") == 0) >+ { ret_full = TRUE; break; } >+ } >+ > /* Wildcard searches may use up some store, because of expansions. We don't > want them to fill up our search store. What we do is set the pool to the main > pool and get a point to reset to later. Wildcard searches could also issue > lookups, but internal_search_find will take care of that, and the cache will be > safely stored in the search pool again. */ > >-if(type == LSEARCH_WILD || type == LSEARCH_NWILD) >+if (type == LSEARCH_WILD || type == LSEARCH_NWILD) > { > store_pool = POOL_MAIN; > reset_point = store_mark(); > } > >-filename = filename; /* Keep picky compilers happy */ >-errmsg = errmsg; >- > rewind(f); >-for (last_was_eol = TRUE; >+for (BOOL this_is_eol, last_was_eol = TRUE; > Ufgets(buffer, sizeof(buffer), f) != NULL; > last_was_eol = this_is_eol) > { >@@ -145,21 +146,22 @@ > if (*s == '\"') > { > uschar *t = s++; >- while (*s != 0 && *s != '\"') >+ while (*s && *s != '\"') > { >- if (*s == '\\') *t++ = string_interpret_escape(CUSS &s); >- else *t++ = *s; >+ *t++ = *s == '\\' ? string_interpret_escape(CUSS &s) : *s; > s++; > } >- if (*s != 0) s++; /* Past terminating " */ > linekeylength = t - buffer; >+ if (*s) s++; /* Past terminating " */ >+ if (ret_full) >+ memmove(t, s, Ustrlen(s)+1); /* copy the rest of line also */ > } > > /* Otherwise it is terminated by a colon or white space */ > > else > { >- while (*s != 0 && *s != ':' && !isspace(*s)) s++; >+ while (*s && *s != ':' && !isspace(*s)) s++; > linekeylength = s - buffer; > } > >@@ -170,9 +172,9 @@ > /* A plain lsearch treats each key as a literal */ > > case LSEARCH_PLAIN: >- if (linekeylength != length || strncmpic(buffer, keystring, length) != 0) >- continue; >- break; /* Key matched */ >+ if (linekeylength != length || strncmpic(buffer, keystring, length) != 0) >+ continue; >+ break; /* Key matched */ > > /* A wild lsearch treats each key as a possible wildcarded string; no > expansion is done for nwildlsearch. */ >@@ -189,7 +191,7 @@ > UCHAR_MAX+1, /* Single-item list */ > NULL, /* No anchor */ > NULL, /* No caching */ >- MCL_STRING + ((type == LSEARCH_WILD)? 0:MCL_NOEXPAND), >+ MCL_STRING + (type == LSEARCH_WILD ? 0 : MCL_NOEXPAND), > TRUE, /* Caseless */ > NULL); > buffer[linekeylength] = save; >@@ -197,47 +199,47 @@ > if (rc == DEFER) return DEFER; > } > >- /* The key has matched. If the search involved a regular expression, it >- might have caused numerical variables to be set. However, their values will >- be in the wrong storage pool for external use. Copying them to the standard >- pool is not feasible because of the caching of lookup results - a repeated >- lookup will not match the regular expression again. Therefore, we flatten >- all numeric variables at this point. */ >+ /* The key has matched. If the search involved a regular expression, it >+ might have caused numerical variables to be set. However, their values will >+ be in the wrong storage pool for external use. Copying them to the standard >+ pool is not feasible because of the caching of lookup results - a repeated >+ lookup will not match the regular expression again. Therefore, we drop >+ all numeric variables at this point. */ > >- expand_nmax = -1; >- break; >+ expand_nmax = -1; >+ break; > > /* Compare an ip address against a list of network/ip addresses. We have to > allow for the "*" case specially. */ > > case LSEARCH_IP: >- if (linekeylength == 1 && buffer[0] == '*') >- { >- if (length != 1 || keystring[0] != '*') continue; >- } >- else if (length == 1 && keystring[0] == '*') continue; >- else >- { >- int maskoffset; >- int save = buffer[linekeylength]; >- buffer[linekeylength] = 0; >- if (string_is_ip_address(buffer, &maskoffset) == 0 || >- !host_is_in_net(keystring, buffer, maskoffset)) continue; >- buffer[linekeylength] = save; >- } >- break; /* Key matched */ >+ if (linekeylength == 1 && buffer[0] == '*') >+ { >+ if (length != 1 || keystring[0] != '*') continue; >+ } >+ else if (length == 1 && keystring[0] == '*') continue; >+ else >+ { >+ int maskoffset; >+ int save = buffer[linekeylength]; >+ buffer[linekeylength] = 0; >+ if (string_is_ip_address(buffer, &maskoffset) == 0 || >+ !host_is_in_net(keystring, buffer, maskoffset)) continue; >+ buffer[linekeylength] = save; >+ } >+ break; /* Key matched */ > } > > /* The key has matched. Skip spaces after the key, and allow an optional > colon after the spaces. This is an odd specification, but it's for > compatibility. */ > >- while (isspace((uschar)*s)) s++; >- if (*s == ':') >- { >- s++; >- while (isspace((uschar)*s)) s++; >- } >+ if (!ret_full) >+ if (Uskip_whitespace(&s) == ':') >+ { >+ s++; >+ Uskip_whitespace(&s); >+ } > > /* Reset dynamic store, if we need to, and revert to the search pool */ > >@@ -256,7 +258,9 @@ > > this_is_comment = FALSE; > yield = string_get(100); >- if (*s != 0) >+ if (ret_full) >+ yield = string_cat(yield, buffer); >+ else if (*s) > yield = string_cat(yield, s); > > /* Now handle continuations */ >@@ -324,9 +328,8 @@ > int length, uschar ** result, uschar ** errmsg, uint * do_cache, > const uschar * opts) > { >-do_cache = do_cache; /* Keep picky compilers happy */ > return internal_lsearch_find(handle, filename, keystring, length, result, >- errmsg, LSEARCH_PLAIN); >+ errmsg, LSEARCH_PLAIN, opts); > } > > >@@ -342,9 +345,8 @@ > int length, uschar ** result, uschar ** errmsg, uint * do_cache, > const uschar * opts) > { >-do_cache = do_cache; /* Keep picky compilers happy */ > return internal_lsearch_find(handle, filename, keystring, length, result, >- errmsg, LSEARCH_WILD); >+ errmsg, LSEARCH_WILD, opts); > } > > >@@ -360,9 +362,8 @@ > int length, uschar ** result, uschar ** errmsg, uint * do_cache, > const uschar * opts) > { >-do_cache = do_cache; /* Keep picky compilers happy */ > return internal_lsearch_find(handle, filename, keystring, length, result, >- errmsg, LSEARCH_NWILD); >+ errmsg, LSEARCH_NWILD, opts); > } > > >@@ -379,12 +380,10 @@ > int length, uschar ** result, uschar ** errmsg, uint * do_cache, > const uschar * opts) > { >-do_cache = do_cache; /* Keep picky compilers happy */ >- > if ((length == 1 && keystring[0] == '*') || > string_is_ip_address(keystring, NULL) != 0) > return internal_lsearch_find(handle, filename, keystring, length, result, >- errmsg, LSEARCH_IP); >+ errmsg, LSEARCH_IP, opts); > > *errmsg = string_sprintf("\"%s\" is not a valid iplsearch key (an IP " > "address, with optional CIDR mask, is wanted): " >@@ -417,12 +416,13 @@ > > #include "../version.h" > >-void >-lsearch_version_report(FILE *f) >+gstring * >+lsearch_version_report(gstring * g) > { > #ifdef DYNLOOKUP >-fprintf(f, "Library version: lsearch: Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, "Library version: lsearch: Exim version %s\n", EXIM_VERSION_STR)); > #endif >+return g; > } > > >diff -ur exim.orig/src/lookups/Makefile exim/src/lookups/Makefile >--- exim.orig/src/lookups/Makefile 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/Makefile 2022-06-23 16:41:10.000000000 +0300 >@@ -1,6 +1,8 @@ > # Make file for building Exim's lookup modules. > # This is called from the main make file, after cd'ing > # to the lookups subdirectory. >+# >+# Copyright (c) The Exim Maintainers 2021 > > # nb: at build time, the version of this file used will have had some > # extra variable definitions and prepended to it and module build rules >@@ -24,51 +26,51 @@ > .c.so:; @echo "$(CC) -shared $*.c" > $(FE)$(CC) $(LOOKUP_$*_INCLUDE) $(LOOKUP_$*_LIBS) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS) $*.c -o $@ > >-lf_check_file.o: $(PHDRS) lf_check_file.c lf_functions.h >-lf_quote.o: $(PHDRS) lf_quote.c lf_functions.h >-lf_sqlperform.o: $(PHDRS) lf_sqlperform.c lf_functions.h >- >-cdb.o: $(PHDRS) cdb.c >-dbmdb.o: $(PHDRS) dbmdb.c >-dnsdb.o: $(PHDRS) dnsdb.c >-dsearch.o: $(PHDRS) dsearch.c >-ibase.o: $(PHDRS) ibase.c >-ldap.o: $(PHDRS) ldap.c >-lmdb.o: $(PHDRS) lmdb.c >-json.o: $(PHDRS) json.c >-lsearch.o: $(PHDRS) lsearch.c >-mysql.o: $(PHDRS) mysql.c >-nis.o: $(PHDRS) nis.c >-nisplus.o: $(PHDRS) nisplus.c >-oracle.o: $(PHDRS) oracle.c >-passwd.o: $(PHDRS) passwd.c >-pgsql.o: $(PHDRS) pgsql.c >-readsock.o: $(PHDRS) readsock.c >-redis.o: $(PHDRS) redis.c >-spf.o: $(PHDRS) spf.c >-sqlite.o: $(PHDRS) sqlite.c >-testdb.o: $(PHDRS) testdb.c >-whoson.o: $(PHDRS) whoson.c >- >-cdb.so: $(PHDRS) cdb.c >-dbmdb.so: $(PHDRS) dbmdb.c >-dnsdb.so: $(PHDRS) dnsdb.c >-dsearch.so: $(PHDRS) dsearch.c >-ibase.so: $(PHDRS) ibase.c >-json.so: $(PHDRS) json.c >-ldap.so: $(PHDRS) ldap.c >-lmdb.so: $(PHDRS) lmdb.c >-lsearch.so: $(PHDRS) lsearch.c >-mysql.so: $(PHDRS) mysql.c >-nis.so: $(PHDRS) nis.c >-nisplus.so: $(PHDRS) nisplus.c >-oracle.so: $(PHDRS) oracle.c >-passwd.so: $(PHDRS) passwd.c >-pgsql.so: $(PHDRS) pgsql.c >-redis.so: $(PHDRS) redis.c >-spf.so: $(PHDRS) spf.c >-sqlite.so: $(PHDRS) sqlite.c >-testdb.so: $(PHDRS) testdb.c >-whoson.so: $(PHDRS) whoson.c >+lf_check_file.o: $(HDRS) lf_check_file.c lf_functions.h >+lf_quote.o: $(HDRS) lf_quote.c lf_functions.h >+lf_sqlperform.o: $(HDRS) lf_sqlperform.c lf_functions.h >+ >+cdb.o: $(HDRS) cdb.c >+dbmdb.o: $(HDRS) dbmdb.c >+dnsdb.o: $(HDRS) dnsdb.c >+dsearch.o: $(HDRS) dsearch.c >+ibase.o: $(HDRS) ibase.c >+ldap.o: $(HDRS) ldap.c >+lmdb.o: $(HDRS) lmdb.c >+json.o: $(HDRS) json.c >+lsearch.o: $(HDRS) lsearch.c >+mysql.o: $(HDRS) mysql.c >+nis.o: $(HDRS) nis.c >+nisplus.o: $(HDRS) nisplus.c >+oracle.o: $(HDRS) oracle.c >+passwd.o: $(HDRS) passwd.c >+pgsql.o: $(HDRS) pgsql.c >+readsock.o: $(HDRS) readsock.c >+redis.o: $(HDRS) redis.c >+spf.o: $(HDRS) spf.c >+sqlite.o: $(HDRS) sqlite.c >+testdb.o: $(HDRS) testdb.c >+whoson.o: $(HDRS) whoson.c >+ >+cdb.so: $(HDRS) cdb.c >+dbmdb.so: $(HDRS) dbmdb.c >+dnsdb.so: $(HDRS) dnsdb.c >+dsearch.so: $(HDRS) dsearch.c >+ibase.so: $(HDRS) ibase.c >+json.so: $(HDRS) json.c >+ldap.so: $(HDRS) ldap.c >+lmdb.so: $(HDRS) lmdb.c >+lsearch.so: $(HDRS) lsearch.c >+mysql.so: $(HDRS) mysql.c >+nis.so: $(HDRS) nis.c >+nisplus.so: $(HDRS) nisplus.c >+oracle.so: $(HDRS) oracle.c >+passwd.so: $(HDRS) passwd.c >+pgsql.so: $(HDRS) pgsql.c >+redis.so: $(HDRS) redis.c >+spf.so: $(HDRS) spf.c >+sqlite.so: $(HDRS) sqlite.c >+testdb.so: $(HDRS) testdb.c >+whoson.so: $(HDRS) whoson.c > > # End >diff -ur exim.orig/src/lookups/mysql.c exim/src/lookups/mysql.c >--- exim.orig/src/lookups/mysql.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/mysql.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Thanks to Paul Kelly for contributing the original code for these >@@ -231,7 +231,7 @@ > > /* Get store for a new handle, initialize it, and connect to the server */ > >- mysql_handle = store_get(sizeof(MYSQL), FALSE); >+ mysql_handle = store_get(sizeof(MYSQL), GET_UNTAINTED); > mysql_init(mysql_handle); > mysql_options(mysql_handle, MYSQL_READ_DEFAULT_GROUP, CS group); > if (mysql_real_connect(mysql_handle, >@@ -247,7 +247,7 @@ > > /* Add the connection to the cache */ > >- cn = store_get(sizeof(mysql_connection), FALSE); >+ cn = store_get(sizeof(mysql_connection), GET_UNTAINTED); > cn->server = server_copy; > cn->handle = mysql_handle; > cn->next = mysql_connections; >@@ -286,7 +286,7 @@ > { > DEBUG(D_lookup) debug_printf_indent("MYSQL: query was not one that returns data\n"); > result = string_cat(result, >- string_sprintf("%d", mysql_affected_rows(mysql_handle))); >+ string_sprintf("%lld", mysql_affected_rows(mysql_handle))); > *do_cache = 0; > goto MYSQL_EXIT; > } >@@ -308,7 +308,7 @@ > > while ((mysql_row_data = mysql_fetch_row(mysql_result))) > { >- unsigned long *lengths = mysql_fetch_lengths(mysql_result); >+ unsigned long * lengths = mysql_fetch_lengths(mysql_result); > > if (result) > result = string_catn(result, US"\n", 1); >@@ -319,7 +319,9 @@ > result); > > else if (mysql_row_data[0] != NULL) /* NULL value yields nothing */ >- result = string_catn(result, US mysql_row_data[0], lengths[0]); >+ result = lengths[0] == 0 && !result >+ ? string_get(1) /* for 0-len string result ensure non-null gstring */ >+ : string_catn(result, US mysql_row_data[0], lengths[0]); > } > > /* more results? -1 = no, >0 = error, 0 = yes (keep looping) >@@ -411,43 +413,39 @@ > 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 * >-mysql_quote(uschar *s, uschar *opt) >+mysql_quote(uschar * s, uschar * opt, unsigned idx) > { >-register int c; >-int count = 0; >-uschar *t = s; >-uschar *quoted; >+int c, count = 0; >+uschar * t = s, * quoted; > >-if (opt != NULL) return NULL; /* No options recognized */ >+if (opt) return NULL; /* No options recognized */ > >-while ((c = *t++) != 0) >+while ((c = *t++)) > if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL) count++; > >-if (count == 0) return s; >-t = quoted = store_get(Ustrlen(s) + count + 1, is_tainted(s)); >+/* Old code: if (count == 0) return s; >+Now always allocate and copy, to track the quoted status. */ > >-while ((c = *s++) != 0) >+t = quoted = store_get_quoted(Ustrlen(s) + count + 1, s, idx); >+ >+while ((c = *s++)) > { > if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL) > { > *t++ = '\\'; > switch(c) > { >- case '\n': *t++ = 'n'; >- break; >- case '\t': *t++ = 't'; >- break; >- case '\r': *t++ = 'r'; >- break; >- case '\b': *t++ = 'b'; >- break; >- default: *t++ = c; >- break; >+ case '\n': *t++ = 'n'; break; >+ case '\t': *t++ = 't'; break; >+ case '\r': *t++ = 'r'; break; >+ case '\b': *t++ = 'b'; break; >+ default: *t++ = c; break; > } > } > else *t++ = c; >@@ -466,16 +464,19 @@ > > #include "../version.h" > >-void >-mysql_version_report(FILE *f) >+gstring * >+mysql_version_report(gstring * g) > { >-fprintf(f, "Library version: MySQL: Compile: %lu %s [%s]\n" >- " Runtime: %lu %s\n", >+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 >-fprintf(f, " Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, >+ " Exim version %s\n", EXIM_VERSION_STR); > #endif >+return g; > } > > /* These are the lookup_info blocks for this driver */ >diff -ur exim.orig/src/lookups/nis.c exim/src/lookups/nis.c >--- exim.orig/src/lookups/nis.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/nis.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2015 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >@@ -97,12 +97,13 @@ > > #include "../version.h" > >-void >-nis_version_report(FILE *f) >+gstring * >+nis_version_report(gstring * g) > { > #ifdef DYNLOOKUP >-fprintf(f, "Library version: NIS: Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, "Library version: NIS: Exim version %s\n", EXIM_VERSION_STR); > #endif >+return g; > } > > >diff -ur exim.orig/src/lookups/nisplus.c exim/src/lookups/nisplus.c >--- exim.orig/src/lookups/nisplus.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/nisplus.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >@@ -226,25 +226,24 @@ > 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 * >-nisplus_quote(uschar *s, uschar *opt) >+nisplus_quote(uschar * s, uschar * opt, unsigned idx) > { > int count = 0; >-uschar *quoted; >-uschar *t = s; >+uschar * quoted, * t = s; > >-if (opt != NULL) return NULL; /* No options recognized */ >+if (opt) return NULL; /* No options recognized */ > >-while (*t != 0) if (*t++ == '\"') count++; >-if (count == 0) return s; >+while (*t) if (*t++ == '\"') count++; > >-t = quoted = store_get(Ustrlen(s) + count + 1, is_tainted(s)); >+t = quoted = store_get_quoted(Ustrlen(s) + count + 1, s, idx); > >-while (*s != 0) >+while (*s) > { > *t++ = *s; > if (*s++ == '\"') *t++ = '\"'; >@@ -263,12 +262,13 @@ > > #include "../version.h" > >-void >-nisplus_version_report(FILE *f) >+gstring * >+nisplus_version_report(gstring * g) > { > #ifdef DYNLOOKUP >-fprintf(f, "Library version: NIS+: Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, "Library version: NIS+: Exim version %s\n", EXIM_VERSION_STR); > #endif >+return g; > } > > >diff -ur exim.orig/src/lookups/oracle.c exim/src/lookups/oracle.c >--- exim.orig/src/lookups/oracle.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/oracle.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2015 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Interface to an Oracle database. This code was originally supplied by >@@ -306,8 +306,8 @@ > > /* Get store for a new connection, initialize it, and connect to the server */ > >- oracle_handle = store_get(sizeof(struct cda_def), FALSE); >- hda = store_get(HDA_SIZE, FALSE); >+ oracle_handle = store_get(sizeof(struct cda_def), GET_UNTAINTED); >+ hda = store_get(HDA_SIZE, GET_UNTAINTED); > memset(hda,'\0',HDA_SIZE); > > /* >@@ -330,7 +330,7 @@ > > /* Add the connection to the cache */ > >- cn = store_get(sizeof(oracle_connection), FALSE); >+ cn = store_get(sizeof(oracle_connection), GET_UNTAINTED); > cn->server = server_copy; > cn->handle = oracle_handle; > cn->next = oracle_connections; >@@ -349,7 +349,7 @@ > > /* We have a connection. Open a cursor and run the query */ > >-cda = store_get(sizeof(Cda_Def), FALSE); >+cda = store_get(sizeof(Cda_Def), GET_UNTAINTED); > > if (oopen(cda, oracle_handle, (text *)0, -1, -1, (text *)0, -1) != 0) > { >@@ -370,8 +370,8 @@ > /* Find the number of fields returned and sort out their types. If the number > is one, we don't add field names to the data. Otherwise we do. */ > >-def = store_get(sizeof(Ora_Define)*MAX_SELECT_LIST_SIZE, FALSE); >-desc = store_get(sizeof(Ora_Describe)*MAX_SELECT_LIST_SIZE, FALSE); >+def = store_get(sizeof(Ora_Define)*MAX_SELECT_LIST_SIZE, GET_UNTAINTED); >+desc = store_get(sizeof(Ora_Describe)*MAX_SELECT_LIST_SIZE, GET_UNTAINTED); > > if ((num_fields = describe_define(cda,def,desc)) == -1) > { >@@ -510,13 +510,12 @@ > int sep = 0; > uschar *server; > uschar *list = oracle_servers; >-uschar buffer[512]; > > do_cache = do_cache; /* Placate picky compilers */ > > DEBUG(D_lookup) debug_printf_indent("ORACLE query: %s\n", query); > >-while ((server = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))) >+while ((server = string_nextinlist(&list, &sep, NULL, 0))) > { > BOOL defer_break; > int rc = perform_oracle_search(query, server, result, errmsg, &defer_break); >@@ -544,27 +543,25 @@ > 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 * >-oracle_quote(uschar *s, uschar *opt) >+oracle_quote(uschar * s, uschar * opt, unsigned idx) > { >-register int c; >-int count = 0; >-uschar *t = s; >-uschar *quoted; >+int c, count = 0; >+uschar * t = s, * quoted; > >-if (opt != NULL) return NULL; /* No options are recognized */ >+if (opt) return NULL; /* No options are recognized */ > >-while ((c = *t++) != 0) >+while ((c = *t++)) > if (strchr("\n\t\r\b\'\"\\", c) != NULL) count++; > >-if (count == 0) return s; >-t = quoted = store_get((int)strlen(s) + count + 1, is_tainted(s)); >+t = quoted = store_get_quoted((int)Ustrlen(s) + count + 1, s, idx); > >-while ((c = *s++) != 0) >+while ((c = *s++)) > { > if (strchr("\n\t\r\b\'\"\\", c) != NULL) > { >@@ -599,12 +596,13 @@ > > #include "../version.h" > >-void >-oracle_version_report(FILE *f) >+gstring * >+oracle_version_report(gstring * g) > { > #ifdef DYNLOOKUP >-fprintf(f, "Library version: Oracle: Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, "Library version: Oracle: Exim version %s\n", EXIM_VERSION_STR); > #endif >+return g; > } > > >diff -ur exim.orig/src/lookups/passwd.c exim/src/lookups/passwd.c >--- exim.orig/src/lookups/passwd.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/passwd.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2015 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >@@ -19,8 +19,6 @@ > static void * > passwd_open(const uschar * filename, uschar ** errmsg) > { >-filename = filename; /* Keep picky compilers happy */ >-errmsg = errmsg; > return (void *)(-1); /* Just return something non-null */ > } > >@@ -40,12 +38,6 @@ > { > struct passwd *pw; > >-handle = handle; /* Keep picky compilers happy */ >-filename = filename; >-length = length; >-errmsg = errmsg; >-do_cache = do_cache; >- > if (!route_finduser(keystring, &pw, NULL)) return FAIL; > *result = string_sprintf("*:%d:%d:%s:%s:%s", (int)pw->pw_uid, (int)pw->pw_gid, > pw->pw_gecos, pw->pw_dir, pw->pw_shell); >@@ -62,12 +54,13 @@ > > #include "../version.h" > >-void >-passwd_version_report(FILE *f) >+gstring * >+passwd_version_report(gstring * g) > { > #ifdef DYNLOOKUP >-fprintf(f, "Library version: passwd: Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, "Library version: passwd: Exim version %s\n", EXIM_VERSION_STR); > #endif >+return g; > } > > static lookup_info _lookup_info = { >diff -ur exim.orig/src/lookups/pgsql.c exim/src/lookups/pgsql.c >--- exim.orig/src/lookups/pgsql.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/pgsql.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Thanks to Petr Cech for contributing the original code for these >@@ -262,7 +262,7 @@ > > /* Add the connection to the cache */ > >- cn = store_get(sizeof(pgsql_connection), FALSE); >+ cn = store_get(sizeof(pgsql_connection), GET_UNTAINTED); > cn->server = server_copy; > cn->handle = pg_conn; > cn->next = pgsql_connections; >@@ -336,6 +336,7 @@ > uschar *tmp = US PQgetvalue(pg_result, i, j); > result = lf_quote(US PQfname(pg_result, j), tmp, Ustrlen(tmp), result); > } >+ if (!result) result = string_get(1); > } > > /* If result is NULL then no data has been found and so we return FAIL. */ >@@ -413,27 +414,25 @@ > 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 * >-pgsql_quote(uschar *s, uschar *opt) >+pgsql_quote(uschar * s, uschar * opt, unsigned idx) > { >-register int c; >-int count = 0; >-uschar *t = s; >-uschar *quoted; >+int count = 0, c; >+uschar * t = s, * quoted; > >-if (opt != NULL) return NULL; /* No options recognized */ >+if (opt) return NULL; /* No options recognized */ > >-while ((c = *t++) != 0) >+while ((c = *t++)) > if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL) count++; > >-if (count == 0) return s; >-t = quoted = store_get(Ustrlen(s) + count + 1, is_tainted(s)); >+t = quoted = store_get_quoted(Ustrlen(s) + count + 1, s, idx); > >-while ((c = *s++) != 0) >+while ((c = *s++)) > { > if (c == '\'') > { >@@ -445,16 +444,11 @@ > *t++ = '\\'; > switch(c) > { >- case '\n': *t++ = 'n'; >- break; >- case '\t': *t++ = 't'; >- break; >- case '\r': *t++ = 'r'; >- break; >- case '\b': *t++ = 'b'; >- break; >- default: *t++ = c; >- break; >+ case '\n': *t++ = 'n'; break; >+ case '\t': *t++ = 't'; break; >+ case '\r': *t++ = 'r'; break; >+ case '\b': *t++ = 'b'; break; >+ default: *t++ = c; break; > } > } > else *t++ = c; >@@ -473,11 +467,11 @@ > > #include "../version.h" > >-void >-pgsql_version_report(FILE *f) >+gstring * >+pgsql_version_report(gstring * g) > { > #ifdef DYNLOOKUP >-fprintf(f, "Library version: PostgreSQL: Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, "Library version: PostgreSQL: Exim version %s\n", EXIM_VERSION_STR); > #endif > > /* Version reporting: there appears to be no available information about >@@ -485,6 +479,8 @@ > 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? */ >+ >+return g; > } > > >diff -ur exim.orig/src/lookups/readsock.c exim/src/lookups/readsock.c >--- exim.orig/src/lookups/readsock.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/readsock.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ > /* Copyright (c) Jeremy Harris 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -13,8 +14,6 @@ > internal_readsock_open(client_conn_ctx * cctx, const uschar * sspec, > int timeout, BOOL do_tls, uschar ** errmsg) > { >-int sep = ','; >-uschar * ele; > const uschar * server_name; > host_item host; > >@@ -97,7 +96,7 @@ > > sigalrm_seen = FALSE; > ALARM(timeout); >- rc = connect(cctx->sock, (struct sockaddr *)(&sockun), sizeof(sockun)); >+ rc = connect(cctx->sock, (struct sockaddr *) &sockun, sizeof(sockun)); > ALARM_CLR(0); > if (sigalrm_seen) > { >@@ -117,10 +116,20 @@ > #ifndef DISABLE_TLS > if (do_tls) > { >+ union sockaddr_46 interface_sock; >+ EXIM_SOCKLEN_T size = sizeof(interface_sock); > smtp_connect_args conn_args = {.host = &host }; >- tls_support tls_dummy = {.sni=NULL}; >+ tls_support tls_dummy = { .sni = NULL }; > uschar * errstr; > >+ if (getsockname(cctx->sock, (struct sockaddr *) &interface_sock, &size) == 0) >+ conn_args.sending_ip_address = host_ntoa(-1, &interface_sock, NULL, NULL); >+ else >+ { >+ *errmsg = string_sprintf("getsockname failed: %s", strerror(errno)); >+ goto bad; >+ } >+ > if (!tls_client_start(cctx, &conn_args, NULL, &tls_dummy, &errstr)) > { > *errmsg = string_sprintf("TLS connect failed: %s", errstr); >@@ -151,7 +160,7 @@ > static void * > readsock_open(const uschar * filename, uschar ** errmsg) > { >-client_conn_ctx * cctx = store_get(sizeof(*cctx), FALSE); >+client_conn_ctx * cctx = store_get(sizeof(*cctx), GET_UNTAINTED); > cctx->sock = -1; > cctx->tls_ctx = NULL; > DEBUG(D_lookup) debug_printf_indent("readsock: allocated context\n"); >@@ -182,7 +191,6 @@ > } lf = {.do_shutdown = TRUE}; > uschar * eol = NULL; > int timeout = 5; >-FILE * fp; > gstring * yield; > int ret = DEFER; > >@@ -250,16 +258,28 @@ > and might need later write ops on the socket, the stdio must be in > writable mode or the underlying socket goes non-writable. */ > >-if (!cctx->tls_ctx) >- fp = fdopen(cctx->sock, lf.do_shutdown ? "rb" : "wb"); >- > sigalrm_seen = FALSE; >-ALARM(timeout); >-yield = >-#ifndef DISABLE_TLS >- cctx->tls_ctx ? cat_file_tls(cctx->tls_ctx, NULL, eol) : >+#ifdef DISABLE_TLS >+if (TRUE) >+#else >+if (!cctx->tls_ctx) > #endif >- cat_file(fp, NULL, eol); >+ { >+ FILE * fp = fdopen(cctx->sock, "rb"); >+ if (!fp) >+ { >+ log_write(0, LOG_MAIN|LOG_PANIC, "readsock fdopen: %s\n", strerror(errno)); >+ goto out; >+ } >+ ALARM(timeout); >+ yield = cat_file(fp, NULL, eol); >+ } >+else >+ { >+ ALARM(timeout); >+ yield = cat_file_tls(cctx->tls_ctx, NULL, eol); >+ } >+ > ALARM_CLR(0); > > if (sigalrm_seen) >diff -ur exim.orig/src/lookups/redis.c exim/src/lookups/redis.c >--- exim.orig/src/lookups/redis.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/redis.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >@@ -171,7 +171,7 @@ > } > > /* Add the connection to the cache */ >- cn = store_get(sizeof(redis_connection), FALSE); >+ cn = store_get(sizeof(redis_connection), GET_UNTAINTED); > cn->server = server_copy; > cn->handle = redis_handle; > cn->next = redis_connections; >@@ -399,27 +399,25 @@ > 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 * >-redis_quote(uschar *s, uschar *opt) >+redis_quote(uschar * s, uschar * opt, unsigned idx) > { >-register int c; >-int count = 0; >-uschar *t = s; >-uschar *quoted; >+int c, count = 0; >+uschar * t = s, * quoted; > > if (opt) return NULL; /* No options recognized */ > >-while ((c = *t++) != 0) >+while ((c = *t++)) > if (isspace(c) || c == '\\') count++; > >-if (count == 0) return s; >-t = quoted = store_get(Ustrlen(s) + count + 1, is_tainted(s)); >+t = quoted = store_get_quoted(Ustrlen(s) + count + 1, s, idx); > >-while ((c = *s++) != 0) >+while ((c = *s++)) > { > if (isspace(c) || c == '\\') *t++ = '\\'; > *t++ = c; >@@ -435,14 +433,16 @@ > *************************************************/ > #include "../version.h" > >-void >-redis_version_report(FILE *f) >+gstring * >+redis_version_report(gstring * g) > { >-fprintf(f, "Library version: REDIS: Compile: %d [%d]\n", >- HIREDIS_MAJOR, HIREDIS_MINOR); >+g = string_fmt_append(g, >+ "Library version: REDIS: Compile: %d [%d]\n", HIREDIS_MAJOR, HIREDIS_MINOR); > #ifdef DYNLOOKUP >-fprintf(f, " Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, >+ " Exim version %s\n", EXIM_VERSION_STR); > #endif >+return g; > } > > >diff -ur exim.orig/src/lookups/spf.c exim/src/lookups/spf.c >--- exim.orig/src/lookups/spf.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/spf.c 2022-06-23 16:41:10.000000000 +0300 >@@ -5,14 +5,13 @@ > /* Exim - SPF lookup module using libspf2 > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > >+Copyright (c) The Exim Maintainers 2020 - 2022 > Copyright (c) 2005 Chris Webb, Arachsys Internet Services Ltd > > This program is free software; you can redistribute it and/or > modify it under the terms of the GNU General Public License > as published by the Free Software Foundation; either version 2 > of the License, or (at your option) any later version. >- >-Copyright (c) The Exim Maintainers 2020 > */ > > #include "../exim.h" >@@ -128,12 +127,13 @@ > > #include "../version.h" > >-void >-spf_version_report(FILE *f) >+gstring * >+spf_version_report(gstring * g) > { > #ifdef DYNLOOKUP >-fprintf(f, "Library version: SPF: Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, "Library version: SPF: Exim version %s\n", EXIM_VERSION_STR)); > #endif >+return g; > } > > >diff -ur exim.orig/src/lookups/sqlite.c exim/src/lookups/sqlite.c >--- exim.orig/src/lookups/sqlite.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/sqlite.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >@@ -66,7 +66,7 @@ > /* For multiple fields, include the field name too */ > for (int i = 0; i < argc; i++) > { >- uschar *value = US((argv[i] != NULL)? argv[i]:"<NULL>"); >+ uschar * value = US(argv[i] ? argv[i] : "<NULL>"); > res = lf_quote(US azColName[i], value, Ustrlen(value), res); > } > } >@@ -74,7 +74,8 @@ > else > res = string_cat(res, argv[0] ? US argv[0] : US "<NULL>"); > >-*(gstring **)arg = res; >+/* always return a non-null gstring, even for a zero-length string result */ >+*(gstring **)arg = res ? res : string_get(1); > return 0; > } > >@@ -94,7 +95,7 @@ > return FAIL; > } > >-if (!res) *do_cache = 0; >+if (!res) *do_cache = 0; /* on fail, wipe cache */ > > *result = string_from_gstring(res); > return OK; >@@ -125,26 +126,25 @@ > 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 * >-sqlite_quote(uschar *s, uschar *opt) >+sqlite_quote(uschar * s, uschar * opt, unsigned idx) > { >-register int c; >-int count = 0; >-uschar *t = s; >-uschar *quoted; >+int c, count = 0; >+uschar * t = s, * quoted; > >-if (opt != NULL) return NULL; /* No options recognized */ >+if (opt) return NULL; /* No options recognized */ > >-while ((c = *t++) != 0) if (c == '\'') count++; >+while ((c = *t++)) if (c == '\'') count++; >+count += t - s; > >-if (count == 0) return s; >-t = quoted = store_get(Ustrlen(s) + count + 1, is_tainted(s)); >+t = quoted = store_get_quoted(count + 1, s, idx); > >-while ((c = *s++) != 0) >+while ((c = *s++)) > { > if (c == '\'') *t++ = '\''; > *t++ = c; >@@ -164,15 +164,18 @@ > > #include "../version.h" > >-void >-sqlite_version_report(FILE *f) >+gstring * >+sqlite_version_report(gstring * g) > { >-fprintf(f, "Library version: SQLite: Compile: %s\n" >- " Runtime: %s\n", >+g = string_fmt_append(g, >+ "Library version: SQLite: Compile: %s\n" >+ " Runtime: %s\n", > SQLITE_VERSION, sqlite3_libversion()); > #ifdef DYNLOOKUP >-fprintf(f, " Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, >+ " Exim version %s\n", EXIM_VERSION_STR); > #endif >+return g; > } > > static lookup_info _lookup_info = { >diff -ur exim.orig/src/lookups/testdb.c exim/src/lookups/testdb.c >--- exim.orig/src/lookups/testdb.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/testdb.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2015 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >@@ -24,8 +24,6 @@ > static void * > testdb_open(const uschar * filename, uschar ** errmsg) > { >-filename = filename; /* Keep picky compilers happy */ >-errmsg = errmsg; > return (void *)(1); /* Just return something non-null */ > } > >@@ -42,10 +40,6 @@ > int length, uschar ** result, uschar ** errmsg, uint * do_cache, > const uschar * opts) > { >-handle = handle; /* Keep picky compilers happy */ >-filename = filename; >-length = length; >- > if (Ustrcmp(query, "fail") == 0) > { > *errmsg = US"testdb lookup forced FAIL"; >@@ -74,12 +68,13 @@ > > #include "../version.h" > >-void >-testdb_version_report(FILE *f) >+gstring * >+testdb_version_report(gstring * g) > { > #ifdef DYNLOOKUP >-fprintf(f, "Library version: TestDB: Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, "Library version: TestDB: Exim version %s\n", EXIM_VERSION_STR); > #endif >+return g; > } > > >diff -ur exim.orig/src/lookups/whoson.c exim/src/lookups/whoson.c >--- exim.orig/src/lookups/whoson.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/lookups/whoson.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2015 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* This code originally came from Robert Wal. */ >@@ -23,8 +23,6 @@ > static void * > whoson_open(const uschar * filename, uschar ** errmsg) > { >-filename = filename; /* Keep picky compilers happy */ >-errmsg = errmsg; > return (void *)(1); /* Just return something non-null */ > } > >@@ -40,11 +38,6 @@ > uschar ** result, uschar ** errmsg, uint * do_cache, const uschar * opts) > { > uschar buffer[80]; >-handle = handle; /* Keep picky compilers happy */ >-filename = filename; >-length = length; >-errmsg = errmsg; >-do_cache = do_cache; > > switch (wso_query(CS query, CS buffer, sizeof(buffer))) > { >@@ -71,13 +64,16 @@ > > #include "../version.h" > >-void >-whoson_version_report(FILE *f) >+gstring * >+whoson_version_report(gstring * g) > { >-fprintf(f, "Library version: Whoson: Runtime: %s\n", wso_version()); >+g = string_fmt_append(g, >+ "Library version: Whoson: Runtime: %s\n", wso_version()); > #ifdef DYNLOOKUP >-fprintf(f, " Exim version %s\n", EXIM_VERSION_STR); >+g = string_fmt_append(g, >+ " Exim version %s\n", EXIM_VERSION_STR); > #endif >+return g; > } > > static lookup_info _lookup_info = { >diff -ur exim.orig/src/macro_predef.c exim/src/macro_predef.c >--- exim.orig/src/macro_predef.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/macro_predef.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) Jeremy Harris 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Create a static data structure with the predefined macros, to be >@@ -174,21 +174,18 @@ > #ifdef SUPPORT_SOCKS > builtin_macro_create(US"_HAVE_SOCKS"); > #endif >+#if defined(SUPPORT_SRS) >+ builtin_macro_create(US"_HAVE_NATIVE_SRS"); /* beware clash with _HAVE_SRS */ >+#endif > #ifdef TCP_FASTOPEN > builtin_macro_create(US"_HAVE_TCP_FASTOPEN"); > #endif >-#ifdef EXPERIMENTAL_LMDB >- builtin_macro_create(US"_HAVE_LMDB"); >-#endif > #ifdef SUPPORT_SPF > builtin_macro_create(US"_HAVE_SPF"); > #endif >-#if defined(EXPERIMENTAL_SRS) || defined(EXPERIMENTAL_SRS_NATIVE) >+#ifdef SUPPORT_SRS > builtin_macro_create(US"_HAVE_SRS"); > #endif >-#if defined(EXPERIMENTAL_SRS_NATIVE) >- builtin_macro_create(US"_HAVE_NATIVE_SRS"); /* beware clash with _HAVE_SRS */ >-#endif > #ifdef EXPERIMENTAL_ARC > builtin_macro_create(US"_HAVE_ARC"); > #endif >@@ -204,7 +201,7 @@ > #ifdef EXPERIMENTAL_DSN_INFO > builtin_macro_create(US"_HAVE_DSN_INFO"); > #endif >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifndef DISABLE_TLS_RESUME > builtin_macro_create(US"_HAVE_TLS_RESUME"); > #endif > >@@ -226,15 +223,16 @@ > #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"); >+#endif > #ifdef LOOKUP_LDAP > builtin_macro_create(US"_HAVE_LOOKUP_JSON"); > #endif > #ifdef LOOKUP_LDAP > builtin_macro_create(US"_HAVE_LOOKUP_LDAP"); > #endif >-#ifdef EXPERIMENTAL_LMDB >- builtin_macro_create(US"_HAVE_LOOKUP_LMDB"); >-#endif > #ifdef LOOKUP_MYSQL > builtin_macro_create(US"_HAVE_LOOKUP_MYSQL"); > #endif >@@ -278,11 +276,35 @@ > # endif > #endif > >+features_acl(); >+features_crypto(); >+ > #ifdef WITH_CONTENT_SCAN > features_malware(); > #endif >+} > >-features_crypto(); >+static void >+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 >+#ifdef EXPERIMENTAL_DSN_INFO >+ builtin_macro_create(US"_EXP_DSNI"); >+#endif >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ builtin_macro_create(US"_EXP_LIMITS"); >+#endif >+#ifdef EXPERIMENTAL_QUEUEFILE >+ builtin_macro_create(US"_EXP_QUEUEFILE"); >+#endif > } > > >@@ -313,6 +335,7 @@ > { > printf("#include \"exim.h\"\n"); > features(); >+exp_features(); > options(); > params(); > >diff -ur exim.orig/src/macro_predef.h exim/src/macro_predef.h >--- exim.orig/src/macro_predef.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/macro_predef.h 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > > /* Copyright (c) Jeremy Harris 2017 - 2018 */ >+/* Copyright (c) The Exim Maintainers 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Global functions */ >@@ -12,6 +13,7 @@ > extern void builtin_macro_create_var(const uschar *, const uschar *); > extern void options_from_list(optionlist *, unsigned, const uschar *, uschar *); > >+extern void features_acl(void); > extern void features_malware(void); > extern void features_crypto(void); > extern void options_main(void); >diff -ur exim.orig/src/macros.h exim/src/macros.h >--- exim.orig/src/macros.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/macros.h 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -79,11 +79,6 @@ > ((uschar)(c) > 127 && print_topbitchars)) > > >-/* Convenience for testing strings */ >- >-#define streqic(Foo, Bar) (strcmpic(Foo, Bar) == 0) >- >- > /* When built with TLS support, the act of flushing SMTP output becomes > a no-op once an SSL session is in progress. */ > >@@ -110,8 +105,9 @@ > > /* Debugging control */ > >+#define LOG_NAME_SIZE 256 > #define DEBUG(x) if (debug_selector & (x)) >-#define HDEBUG(x) if (host_checking || (debug_selector & (x))) >+#define HDEBUG(x) if (host_checking || debug_selector & (x)) > > /* The default From: text for DSNs */ > >@@ -195,14 +191,6 @@ > #define WAIT_NAME_MAX 50 > #define WAIT_CONT_MAX 1000 > >-/* Wait this long before determining that a Proxy Protocol configured >-host isn't speaking the protocol, and so is disallowed. Can be moved to >-runtime configuration if per site settings become needed. */ >-#ifdef SUPPORT_PROXY >-#define PROXY_NEGOTIATION_TIMEOUT_SEC 3 >-#define PROXY_NEGOTIATION_TIMEOUT_USEC 0 >-#endif >- > /* Fixed option values for all PCRE functions */ > > #define PCRE_COPT 0 /* compile */ >@@ -210,8 +198,7 @@ > > /* Macros for trivial functions */ > >-#define mac_ismsgid(s) \ >- (pcre_exec(regex_ismsgid,NULL,CS s,Ustrlen(s),0,PCRE_EOPT,NULL,0) >= 0) >+#define mac_ismsgid(s) (regex_match(regex_ismsgid, (s), -1, NULL)) > > > /* Options for dns_next_rr */ >@@ -313,7 +300,7 @@ > #define DELIVER_MUA_FAILED 2 /* Failure when mua_wrapper is set */ > #define DELIVER_NOT_ATTEMPTED 3 /* Not tried (no msg or is locked */ > >-/* Returns from DNS lookup functions. */ >+/* Returns from DNS lookup functions. Use dns_rc_names[] for debug strings */ > > enum { DNS_SUCCEED, DNS_NOMATCH, DNS_NODATA, DNS_AGAIN, DNS_FAIL }; > >@@ -436,6 +423,13 @@ > D_timestamp | \ > D_resolver)) > >+/* Bits for debug triggers */ >+ >+enum { >+ DTi_panictrigger, >+ DTi_pretrigger, >+}; >+ > /* Options bits for logging. Those that have values < BITWORDSIZE can be used > in calls to log_write(). The others are put into later words in log_selector > and are only ever tested independently, so they do not need bit mask >@@ -483,8 +477,10 @@ > Li_outgoing_port, > Li_pid, > Li_pipelining, >+ Li_protocol_detail, > Li_proxy, > Li_queue_time, >+ Li_queue_time_exclusive, > Li_queue_time_overall, > Li_receive_time, > Li_received_sender, >@@ -563,19 +559,20 @@ > #define ERRNO_AUTHPROB (-48) /* Authenticator "other" failure */ > #define ERRNO_UTF8_FWD (-49) /* target not supporting SMTPUTF8 */ > #define ERRNO_HOST_IS_LOCAL (-50) /* Transport refuses to talk to localhost */ >+#define ERRNO_TAINT (-51) /* Transport refuses to talk use tainted filename */ > > /* These must be last, so all retry deferments can easily be identified */ > >-#define ERRNO_RETRY_BASE (-51) /* Base to test against */ >-#define ERRNO_RRETRY (-51) /* Not time for routing */ >+#define ERRNO_RETRY_BASE (-52) /* Base to test against */ >+#define ERRNO_RRETRY (-52) /* Not time for routing */ > >-#define ERRNO_WARN_BASE (-52) /* Base to test against */ >-#define ERRNO_LRETRY (-52) /* Not time for local delivery */ >-#define ERRNO_HRETRY (-53) /* Not time for any remote host */ >-#define ERRNO_LOCAL_ONLY (-54) /* Local-only delivery */ >-#define ERRNO_QUEUE_DOMAIN (-55) /* Domain in queue_domains */ >-#define ERRNO_TRETRY (-56) /* Transport concurrency limit */ >-#define ERRNO_EVENT (-57) /* Event processing request alternate response */ >+#define ERRNO_WARN_BASE (-53) /* Base to test against */ >+#define ERRNO_LRETRY (-53) /* Not time for local delivery */ >+#define ERRNO_HRETRY (-54) /* Not time for any remote host */ >+#define ERRNO_LOCAL_ONLY (-55) /* Local-only delivery */ >+#define ERRNO_QUEUE_DOMAIN (-56) /* Domain in queue_domains */ >+#define ERRNO_TRETRY (-57) /* Transport concurrency limit */ >+#define ERRNO_EVENT (-58) /* Event processing request alternate response */ > > > >@@ -739,6 +736,7 @@ > #define vopt_callout_recippmaster 0x0100 /* use postmaster to verify recip */ > #define vopt_callout_hold 0x0200 /* lazy close connection */ > #define vopt_success_on_redirect 0x0400 >+#define vopt_quota 0x0800 /* quota check, to local/appendfile */ > > /* Values for fields in callout cache records */ > >@@ -869,22 +867,23 @@ > > /* Options for transport_write_message */ > >-#define topt_add_return_path 0x001 >-#define topt_add_delivery_date 0x002 >-#define topt_add_envelope_to 0x004 >-#define topt_use_crlf 0x008 /* Terminate lines with CRLF */ >-#define topt_end_dot 0x010 /* Send terminating dot line */ >-#define topt_no_headers 0x020 /* Omit headers */ >-#define topt_no_body 0x040 /* Omit body */ >-#define topt_escape_headers 0x080 /* Apply escape check to headers */ >-#define topt_use_bdat 0x100 /* prepend chunks with RFC3030 BDAT header */ >-#define topt_output_string 0x200 /* create string rather than write to fd */ >-#define topt_continuation 0x400 /* do not reset buffer */ >-#define topt_not_socket 0x800 /* cannot do socket-only syscalls */ >+#define topt_add_return_path 0x0001 >+#define topt_add_delivery_date 0x0002 >+#define topt_add_envelope_to 0x0004 >+#define topt_escape_headers 0x0008 /* Apply escape check to headers */ >+#define topt_use_crlf 0x0010 /* Terminate lines with CRLF */ >+#define topt_no_headers 0x0020 /* Omit headers */ >+#define topt_no_body 0x0040 /* Omit body */ >+#define topt_end_dot 0x0080 /* Send terminating dot line */ >+#define topt_no_flush 0x0100 /* more data expected after message (eg QUIT) */ >+#define topt_use_bdat 0x0200 /* prepend chunks with RFC3030 BDAT header */ >+#define topt_output_string 0x0400 /* create string rather than write to fd */ >+#define topt_continuation 0x0800 /* do not reset buffer */ >+#define topt_not_socket 0x1000 /* cannot do socket-only syscalls */ > > /* Options for smtp_write_command */ > >-enum { >+enum { > SCMD_FLUSH = 0, /* write to kernel */ > SCMD_MORE, /* write to kernel, but likely more soon */ > SCMD_BUFFER /* stash in application cmd output buffer */ >@@ -976,7 +975,9 @@ > #define ACL_BIT_MIME BIT(ACL_WHERE_MIME) > #define ACL_BIT_DKIM BIT(ACL_WHERE_DKIM) > #define ACL_BIT_DATA BIT(ACL_WHERE_DATA) >-#ifndef DISABLE_PRDR >+#ifdef DISABLE_PRDR >+# define ACL_BIT_PRDR 0 >+#else > # define ACL_BIT_PRDR BIT(ACL_WHERE_PRDR) > #endif > #define ACL_BIT_NOTSMTP BIT(ACL_WHERE_NOTSMTP) >@@ -994,6 +995,10 @@ > #define ACL_BIT_DELIVERY BIT(ACL_WHERE_DELIVERY) > #define ACL_BIT_UNKNOWN BIT(ACL_WHERE_UNKNOWN) > >+#define ACL_BITS_HAVEDATA (ACL_BIT_MIME | ACL_BIT_DKIM | ACL_BIT_DATA \ >+ | ACL_BIT_PRDR \ >+ | ACL_BIT_NOTSMTP | ACL_BIT_QUIT | ACL_BIT_NOTQUIT) >+ > > /* Situations for spool_write_header() */ > >@@ -1047,9 +1052,10 @@ > > > /* Options on tls_close */ >-#define TLS_NO_SHUTDOWN 0 >-#define TLS_SHUTDOWN_NOWAIT 1 >-#define TLS_SHUTDOWN_WAIT 2 >+#define TLS_NO_SHUTDOWN 0 /* Just forget the context */ >+#define TLS_SHUTDOWN_NOWAIT 1 /* Send alert; do not wait */ >+#define TLS_SHUTDOWN_WAIT 2 /* Send alert & wait for peer's alert */ >+#define TLS_SHUTDOWN_WONLY 3 /* only wait for peer's alert */ > > > #ifdef COMPILE_UTILITY >@@ -1070,8 +1076,8 @@ > > #define AUTHS_REGEX US"\\n250[\\s\\-]AUTH\\s+([\\-\\w \\t]+)(?:\\n|$)" > >-#define EARLY_PIPE_FEATURE_NAME "PIPE_CONNECT" >-#define EARLY_PIPE_FEATURE_LEN 12 >+#define EARLY_PIPE_FEATURE_NAME "PIPECONNECT" >+#define EARLY_PIPE_FEATURE_LEN 11 > > > /* Flags for auth_client_item() */ >diff -ur exim.orig/src/malware.c exim/src/malware.c >--- exim.orig/src/malware.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/malware.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,9 +2,10 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015 >+/* >+ * Copyright (c) The Exim Maintainers 2015 - 2022 >+ * Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015 > * License: GPL >- * Copyright (c) The Exim Maintainers 2015 - 2020 > */ > > /* Code for calling virus (malware) scanners. Called from acl.c. */ >@@ -129,8 +130,7 @@ > #define MALWARE_TIMEOUT 120 /* default timeout, seconds */ > > static const uschar * malware_regex_default = US ".+"; >-static const pcre * malware_default_re = NULL; >- >+static const pcre2_code * malware_default_re = NULL; > > > #ifndef DISABLE_MAL_CLAM >@@ -157,35 +157,35 @@ > # define DERR_BAD_CALL (1<<15) /* wrong command */ > > static const uschar * drweb_re_str = US "infected\\swith\\s*(.+?)$"; >-static const pcre * drweb_re = NULL; >+static const pcre2_code * drweb_re = NULL; > #endif > > #ifndef DISABLE_MAL_FSECURE > static const uschar * fsec_re_str = US "\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$"; >-static const pcre * fsec_re = NULL; >+static const pcre2_code * fsec_re = NULL; > #endif > > #ifndef DISABLE_MAL_KAV > static const uschar * kav_re_sus_str = US "suspicion:\\s*(.+?)\\s*$"; > static const uschar * kav_re_inf_str = US "infected:\\s*(.+?)\\s*$"; >-static const pcre * kav_re_sus = NULL; >-static const pcre * kav_re_inf = NULL; >+static const pcre2_code * kav_re_sus = NULL; >+static const pcre2_code * kav_re_inf = NULL; > #endif > > #ifndef DISABLE_MAL_AVAST > static const uschar * ava_re_clean_str = US "(?!\\\\)\\t\\[\\+\\]"; > static const uschar * ava_re_virus_str = US "(?!\\\\)\\t\\[L\\]\\d+\\.0\\t0\\s(.*)"; > static const uschar * ava_re_error_str = US "(?!\\\\)\\t\\[E\\]\\d+\\.0\\tError\\s\\d+\\s(.*)"; >-static const pcre * ava_re_clean = NULL; >-static const pcre * ava_re_virus = NULL; >-static const pcre * ava_re_error = NULL; >+static const pcre2_code * ava_re_clean = NULL; >+static const pcre2_code * ava_re_virus = NULL; >+static const pcre2_code * ava_re_error = NULL; > #endif > > #ifndef DISABLE_MAL_FFROT6D > static const uschar * fprot6d_re_error_str = US "^\\d+\\s<(.+?)>$"; > static const uschar * fprot6d_re_virus_str = US "^\\d+\\s<infected:\\s+(.+?)>\\s+.+$"; >-static const pcre * fprot6d_re_error = NULL; >-static const pcre * fprot6d_re_virus = NULL; >+static const pcre2_code * fprot6d_re_error = NULL; >+static const pcre2_code * fprot6d_re_virus = NULL; > #endif > > >@@ -220,6 +220,7 @@ > /* Some (currently avast only) use backslash escaped whitespace, > this function undoes these escapes */ > >+#ifndef DISABLE_MAL_AVAST > static inline void > unescape(uschar *p) > { >@@ -228,6 +229,7 @@ > if (*p == '\\' && (isspace(p[1]) || p[1] == '\\')) > for (p0 = p; *p0; ++p0) *p0 = p0[1]; > } >+#endif > > /* --- malware_*_defer --- */ > static inline int >@@ -250,13 +252,6 @@ > return malware_panic_defer(string_sprintf("%s %s : %s", > scanent->name, hostport ? hostport : CUS"", str)); > } >-static inline int >-m_log_defer(struct scan * scanent, const uschar * hostport, >- const uschar * str) >-{ >-return malware_log_defer(string_sprintf("%s %s : %s", >- scanent->name, hostport ? hostport : CUS"", str)); >-} > /* --- m_*_defer_3 */ > static inline int > m_panic_defer_3(struct scan * scanent, const uschar * hostport, >@@ -277,8 +272,15 @@ > m_tcpsocket(const uschar * hostname, unsigned int port, > host_item * host, uschar ** errstr, const blob * fastopen_blob) > { >-return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, >+int fd = ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, > host, errstr, fastopen_blob); >+#ifdef EXIM_TFO_FREEBSD >+/* Under some fault conditions, FreeBSD 12.2 seen to send a (non-TFO) SYN >+and, getting no response, wait for a long time. Impose a 5s max. */ >+if (fd >= 0) >+ (void) poll_one_fd(fd, POLLOUT, 5 * 1000); >+#endif >+return fd; > } > #endif > >@@ -296,37 +298,43 @@ > return sock; > } > >-static const pcre * >+static const pcre2_code * > m_pcre_compile(const uschar * re, uschar ** errstr) > { >-const uschar * rerror; >-int roffset; >-const pcre * cre; >- >-if (!(cre = pcre_compile(CS re, PCRE_COPT, CCSS &rerror, &roffset, NULL))) >- *errstr= string_sprintf("regular expression error in '%s': %s at offset %d", >- re, rerror, roffset); >+int err; >+PCRE2_SIZE roffset; >+const pcre2_code * cre; >+ >+if (!(cre = pcre2_compile((PCRE2_SPTR)re, PCRE2_ZERO_TERMINATED, >+ PCRE_COPT, &err, &roffset, pcre_cmp_ctx))) >+ { >+ uschar errbuf[128]; >+ pcre2_get_error_message(err, errbuf, sizeof(errbuf)); >+ *errstr= string_sprintf("regular expression error in '%s': %s at offset %ld", >+ re, errbuf, (long)roffset); >+ } > return cre; > } > > uschar * >-m_pcre_exec(const pcre * cre, uschar * text) >+m_pcre_exec(const pcre2_code * cre, uschar * text) > { >-int ovector[10*3]; >-int i = pcre_exec(cre, NULL, CS text, Ustrlen(text), 0, 0, >- ovector, nelem(ovector)); >-uschar * substr = NULL; >+pcre2_match_data * md = pcre2_match_data_create(2, pcre_gen_ctx); >+int i = pcre2_match(cre, text, PCRE2_ZERO_TERMINATED, 0, 0, md, pcre_mtc_ctx); >+PCRE2_UCHAR * substr = NULL; >+PCRE2_SIZE slen; >+ > if (i >= 2) /* Got it */ >- pcre_get_substring(CS text, ovector, i, 1, CCSS &substr); >-return substr; >+ pcre2_substring_get_bynumber(md, 1, &substr, &slen); >+return US substr; > } > >-static const pcre * >+static const pcre2_code * > m_pcre_nextinlist(const uschar ** list, int * sep, > char * listerr, uschar ** errstr) > { > const uschar * list_ele; >-const pcre * cre = NULL; >+const pcre2_code * cre = NULL; > > if (!(list_ele = string_nextinlist(list, sep, NULL, 0))) > *errstr = US listerr; >@@ -386,6 +394,7 @@ > } > > /* return TRUE iff size as requested */ >+#ifndef DISABLE_MAL_DRWEB > static BOOL > recv_len(int sock, void * buf, int size, time_t tmo) > { >@@ -393,6 +402,7 @@ > ? recv(sock, buf, size, 0) == size > : FALSE; > } >+#endif > > > >@@ -574,7 +584,7 @@ > uschar *scanner_name; > unsigned long mbox_size; > FILE *mbox_file; >-const pcre *re; >+const pcre2_code *re; > uschar * errstr; > struct scan * scanent; > const uschar * scanner_options; >@@ -916,7 +926,7 @@ > /* read and concatenate virus names into one string */ > for (int i = 0; i < drweb_vnum; i++) > { >- int ovector[10*3]; >+ pcre2_match_data * md = pcre2_match_data_create(2, pcre_gen_ctx); > > /* read the size of report */ > if (!recv_len(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), tmo)) >@@ -925,7 +935,7 @@ > drweb_slen = ntohl(drweb_slen); > > /* assume tainted, since it is external input */ >- tmpbuf = store_get(drweb_slen, TRUE); >+ tmpbuf = store_get(drweb_slen, GET_TAINTED); > > /* read report body */ > if (!recv_len(malware_daemon_ctx.sock, tmpbuf, drweb_slen, tmo)) >@@ -934,22 +944,20 @@ > tmpbuf[drweb_slen] = '\0'; > > /* try matcher on the line, grab substring */ >- result = pcre_exec(drweb_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0, >- ovector, nelem(ovector)); >+ result = pcre2_match(drweb_re, (PCRE2_SPTR)tmpbuf, PCRE2_ZERO_TERMINATED, >+ 0, 0, md, pcre_mtc_ctx); > if (result >= 2) > { >- const char * pre_malware_nb; >- >- pcre_get_substring(CS tmpbuf, ovector, result, 1, &pre_malware_nb); >+ PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md); > > if (i==0) /* the first name we just copy to malware_name */ >- g = string_cat(NULL, US pre_malware_nb); >+ g = string_catn(NULL, US ovec[2], ovec[3] - ovec[2]); > >- /*XXX could be string_append_listele? */ > else /* concatenate each new virus name to previous */ >- g = string_append(g, 2, "/", pre_malware_nb); >- >- pcre_free_substring(pre_malware_nb); >+ { >+ g = string_catn(g, US"/", 1); >+ g = string_catn(g, US ovec[2], ovec[3] - ovec[2]); >+ } > } > } > malware_name = string_from_gstring(g); >@@ -1142,7 +1150,7 @@ > int kav_rc; > unsigned long kav_reportlen; > int bread; >- const pcre *kav_re; >+ const pcre2_code *kav_re; > uschar *p; > > /* get current date and time, build scan request */ >@@ -1251,8 +1259,8 @@ > case M_CMDL: /* "cmdline" scanner type ---------------------------------- */ > { > const uschar *cmdline_scanner = scanner_options; >- const pcre *cmdline_trigger_re; >- const pcre *cmdline_regex_re; >+ const pcre2_code *cmdline_trigger_re; >+ const pcre2_code *cmdline_regex_re; > uschar * file_name; > uschar * commandline; > void (*eximsigchld)(int); >@@ -1446,9 +1454,7 @@ > uschar av_buffer[1024]; > uschar *hostname = US""; > host_item connhost; >- uschar *clamav_fbuf; >- int clam_fd, result; >- off_t fsize; >+ int clam_fd; > unsigned int fsize_uint; > BOOL use_scan_command = FALSE; > clamd_address * cv[MAX_CLAMD_SERVERS]; >@@ -1466,9 +1472,9 @@ > int subsep = ' '; > > /* Local file; so we def want to use_scan_command and don't want to try >- * passing IP/port combinations */ >+ passing IP/port combinations */ > use_scan_command = TRUE; >- cd = (clamd_address *) store_get(sizeof(clamd_address), FALSE); >+ cd = (clamd_address *) store_get(sizeof(clamd_address), GET_UNTAINTED); > > /* extract socket-path part */ > sublist = scanner_options; >@@ -1502,7 +1508,7 @@ > continue; > } > >- cd = (clamd_address *) store_get(sizeof(clamd_address), FALSE); >+ cd = (clamd_address *) store_get(sizeof(clamd_address), GET_UNTAINTED); > > /* extract host and port part */ > sublist = scanner_options; >@@ -1555,16 +1561,17 @@ > { cmd_str.data = US"zINSTREAM"; cmd_str.len = 10; } > else > { >- cmd_str.data = string_sprintf("SCAN %s\n", eml_filename); >- cmd_str.len = Ustrlen(cmd_str.data); >+ int n; >+ cmd_str.data = string_sprintf("SCAN %s\n%n", eml_filename, &n); >+ cmd_str.len = n; /* .len is a size_t */ > } > > /* We have some network servers specified */ > if (num_servers) > { > /* Confirmed in ClamAV source (0.95.3) that the TCPAddr option of clamd >- * only supports AF_INET, but we should probably be looking to the >- * future and rewriting this to be protocol-independent anyway. */ >+ only supports AF_INET, but we should probably be looking to the >+ future and rewriting this to be protocol-independent anyway. */ > > while (num_servers > 0) > { >@@ -1575,16 +1582,17 @@ > cd->hostspec, cd->tcp_port); > > /* Lookup the host. This is to ensure that we connect to the same IP >- * on both connections (as one host could resolve to multiple ips) */ >+ on both connections (as one host could resolve to multiple ips) */ > for (;;) > { >- /*XXX we trust that the cmd_str is ideempotent */ >+ /*XXX we trust that the cmd_str is idempotent */ > if ((malware_daemon_ctx.sock = m_tcpsocket(cd->hostspec, cd->tcp_port, >- &connhost, &errstr, &cmd_str)) >= 0) >+ &connhost, &errstr, >+ use_scan_command ? &cmd_str : NULL)) >= 0) > { > /* Connection successfully established with a server */ > hostname = cd->hostspec; >- cmd_str.len = 0; >+ if (use_scan_command) cmd_str.len = 0; > break; > } > if (cd->retry <= 0) break; >@@ -1618,20 +1626,28 @@ > } > > /* have socket in variable "sock"; command to use is semi-independent of >- * the socket protocol. We use SCAN if is local (either Unix/local >- * domain socket, or explicitly told local) else we stream the data. >- * How we stream the data depends upon how we were built. */ >+ the socket protocol. We use SCAN if is local (either Unix/local >+ domain socket, or explicitly told local) else we stream the data. >+ How we stream the data depends upon how we were built. */ > > if (!use_scan_command) > { >+ struct stat st; >+#if defined(EXIM_TCP_CORK) && !defined(OS_SENDFILE) >+ BOOL corked = TRUE; >+#endif > /* New protocol: "zINSTREAM\n" followed by a sequence of <length><data> > chunks, <n> a 4-byte number (network order), terminated by a zero-length >- chunk. */ >+ chunk. We only send one chunk. */ > > DEBUG(D_acl) debug_printf_indent( > "Malware scan: issuing %s new-style remote scan (zINSTREAM)\n", > scanner_name); > >+#if defined(EXIM_TCP_CORK) >+ (void) setsockopt(malware_daemon_ctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, >+ US &on, sizeof(on)); >+#endif > /* Pass the string to ClamAV (10 = "zINSTREAM\0"), if not already sent */ > if (cmd_str.len) > if (send(malware_daemon_ctx.sock, cmd_str.data, cmd_str.len, 0) < 0) >@@ -1640,7 +1656,6 @@ > strerror(errno)), > malware_daemon_ctx.sock); > >- /* calc file size */ > if ((clam_fd = exim_open2(CS eml_filename, O_RDONLY)) < 0) > { > int err = errno; >@@ -1649,66 +1664,79 @@ > eml_filename, strerror(err)), > malware_daemon_ctx.sock); > } >- if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0) >+ if (fstat(clam_fd, &st) < 0) > { >- int err; >-b_seek: err = errno; >+ int err = errno; > (void)close(clam_fd); > return m_panic_defer_3(scanent, NULL, >- string_sprintf("can't seek spool file %s: %s", >+ string_sprintf("can't stat spool file %s: %s", > eml_filename, strerror(err)), > malware_daemon_ctx.sock); > } >- fsize_uint = (unsigned int) fsize; >- if ((off_t)fsize_uint != fsize) >+ fsize_uint = (unsigned int) st.st_size; >+ if ((off_t)fsize_uint != st.st_size) > { > (void)close(clam_fd); > return m_panic_defer_3(scanent, NULL, >- string_sprintf("seeking spool file %s, size overflow", >- eml_filename), >+ string_sprintf("stat spool file %s, size overflow", eml_filename), > malware_daemon_ctx.sock); > } >- if (lseek(clam_fd, 0, SEEK_SET) < 0) >- goto b_seek; > >- if (!(clamav_fbuf = store_malloc(fsize_uint))) >- { >- (void)close(clam_fd); >+ /* send file size */ >+ send_size = htonl(fsize_uint); >+ if (send(malware_daemon_ctx.sock, &send_size, sizeof(send_size), 0) < 0) > return m_panic_defer_3(scanent, NULL, >- string_sprintf("unable to allocate memory %u for file (%s)", >- fsize_uint, eml_filename), >+ string_sprintf("unable to send file size to socket (%s)", hostname), > malware_daemon_ctx.sock); >- } > >- if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0) >+ /* send file body */ >+ while (fsize_uint) > { >- int err = errno; >- store_free(clamav_fbuf); (void)close(clam_fd); >- return m_panic_defer_3(scanent, NULL, >- string_sprintf("can't read spool file %s: %s", >- eml_filename, strerror(err)), >- malware_daemon_ctx.sock); >+#ifdef OS_SENDFILE >+ int n = os_sendfile(malware_daemon_ctx.sock, clam_fd, NULL, (size_t)fsize_uint); >+ if (n < 0) >+ return m_panic_defer_3(scanent, NULL, >+ string_sprintf("unable to send file body to socket (%s): %s", hostname, strerror(errno)), >+ malware_daemon_ctx.sock); >+ fsize_uint -= n; >+#else >+ int n = MIN(fsize_uint, big_buffer_size); >+ if ((n = read(clam_fd, big_buffer, n)) < 0) >+ return m_panic_defer_3(scanent, NULL, >+ string_sprintf("can't read spool file %s: %s", >+ eml_filename, strerror(errno)), >+ malware_daemon_ctx.sock); >+ if (send(malware_daemon_ctx.sock, big_buffer, (size_t)n, 0) < 0) >+ return m_panic_defer_3(scanent, NULL, >+ string_sprintf("unable to send file body to socket (%s): %s", hostname, strerror(errno)), >+ malware_daemon_ctx.sock); >+ fsize_uint -= n; >+# ifdef EXIM_TCP_CORK >+ if (corked) >+ { >+ corked = FALSE; >+ (void) setsockopt(malware_daemon_ctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, >+ US &off, sizeof(off)); >+ } >+# endif >+#endif /*!OS_SENDFILE*/ >+ > } >- (void)close(clam_fd); > >- /* send file body to socket */ >- send_size = htonl(fsize_uint); > send_final_zeroblock = 0; >- if ((send(malware_daemon_ctx.sock, &send_size, sizeof(send_size), 0) < 0) || >- (send(malware_daemon_ctx.sock, clamav_fbuf, fsize_uint, 0) < 0) || >- (send(malware_daemon_ctx.sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0)) >- { >- store_free(clamav_fbuf); >+ if (send(malware_daemon_ctx.sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0) > return m_panic_defer_3(scanent, NULL, >- string_sprintf("unable to send file body to socket (%s)", hostname), >+ string_sprintf("unable to send file terminator to socket (%s)", hostname), > malware_daemon_ctx.sock); >- } >- store_free(clamav_fbuf); >+#ifdef OS_SENDFILE >+ (void) setsockopt(malware_daemon_ctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, >+ US &off, sizeof(off)); >+#endif > } > else > { /* use scan command */ > /* Send a SCAN command pointing to a filename; then in the then in the >- * scan-method-neutral part, read the response back */ >+ scan-method-neutral part, read the response back */ > > /* ================================================================= */ > >@@ -1733,10 +1761,10 @@ > malware_daemon_ctx.sock); > > /* Do not shut down the socket for writing; a user report noted that >- * clamd 0.70 does not react well to this. */ >+ clamd 0.70 does not react well to this. */ > } > /* Commands have been sent, no matter which scan method or connection >- * type we're using; now just read the result, independent of method. */ >+ type we're using; now just read the result, independent of method. */ > > /* Read the result */ > memset(av_buffer, 0, sizeof(av_buffer)); >@@ -1782,6 +1810,7 @@ > /* strip newline at the end (won't be present for zINSTREAM) > (also any trailing whitespace, which shouldn't exist, but we depend upon > this below, so double-check) */ >+ > p = av_buffer + Ustrlen(av_buffer) - 1; > if (*p == '\n') *p = '\0'; > >@@ -1792,7 +1821,7 @@ > if (*p) ++p; > > /* colon in returned output? */ >- if(!(p = Ustrchr(av_buffer,':'))) >+ if (!(p = Ustrchr(av_buffer,':'))) > return m_panic_defer(scanent, CUS callout_address, string_sprintf( > "ClamAV returned malformed result (missing colon): %s", > av_buffer)); >@@ -1856,8 +1885,8 @@ > uschar * linebuffer; > uschar * sockline_scanner; > uschar sockline_scanner_default[] = "%s\n"; >- const pcre *sockline_trig_re; >- const pcre *sockline_name_re; >+ const pcre2_code *sockline_trig_re; >+ const pcre2_code *sockline_name_re; > > /* find scanner command line */ > if ( (sockline_scanner = string_nextinlist(&av_scanner_work, &sep, >@@ -2096,7 +2125,7 @@ > if (malware_name) /* Nothing else matters, just read on */ > break; > >- if (pcre_exec(ava_re_clean, NULL, CS buf, slen, 0, 0, NULL, 0) == 0) >+ if (regex_match(ava_re_clean, buf, slen, NULL)) > break; > > if ((malware_name = m_pcre_exec(ava_re_virus, buf))) >@@ -2117,7 +2146,7 @@ > break; > } > } >- else if (pcre_exec(ava_re_error, NULL, CS buf, slen, 0, 0, NULL, 0) == 0) >+ else if (regex_match(ava_re_error, buf, slen, NULL)) > { > log_write(0, LOG_MAIN, "internal scanner error (ignored): %s", buf); > break; >diff -ur exim.orig/src/match.c exim/src/match.c >--- exim.orig/src/match.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/match.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions for matching strings */ >@@ -104,8 +104,6 @@ > uschar *keyquery, *result, *semicolon; > void *handle; > >-error = error; /* Keep clever compilers from complaining */ >- > if (valueptr) *valueptr = NULL; > > /* For regular expressions, use cb->origsubject rather than cb->subject so that >@@ -130,9 +128,9 @@ > > if (pattern[0] == '^') > { >- const pcre * re = regex_must_compile(pattern, cb->caseless, FALSE); >+ const pcre2_code * re = regex_must_compile(pattern, cb->caseless, FALSE); > if (expand_setup < 0 >- ? pcre_exec(re, NULL, CCS s, Ustrlen(s), 0, PCRE_EOPT, NULL, 0) < 0 >+ ? !regex_match(re, s, -1, NULL) > : !regex_match_and_setup(re, s, 0, expand_setup) > ) > return FAIL; >@@ -286,22 +284,7 @@ > > /* Set the parameters for the three different kinds of lookup. */ > >-keyquery = semicolon + 1; >-Uskip_whitespace(&keyquery); >- >-if (mac_islookup(search_type, lookup_absfilequery)) >- { >- filename = keyquery; >- while (*keyquery && !isspace(*keyquery)) keyquery++; >- filename = string_copyn(filename, keyquery - filename); >- Uskip_whitespace(&keyquery); >- } >- >-else if (!mac_islookup(search_type, lookup_querystyle)) >- { >- filename = keyquery; >- keyquery = s; >- } >+keyquery = search_args(search_type, s, semicolon+1, &filename, opts); > > /* Now do the actual lookup; throw away the data returned unless it was asked > for; partial matching is all handled inside search_find(). Note that there is >@@ -449,11 +432,9 @@ > void *arg, int type, const uschar *name, const uschar **valueptr) > { > int yield = OK; >-unsigned int *original_cache_bits = *cache_ptr; >-BOOL include_unknown = FALSE; >-BOOL ignore_unknown = FALSE; >-BOOL include_defer = FALSE; >-BOOL ignore_defer = FALSE; >+unsigned int * original_cache_bits = *cache_ptr; >+BOOL include_unknown = FALSE, ignore_unknown = FALSE, >+ include_defer = FALSE, ignore_defer = FALSE; > const uschar *list; > uschar *sss; > uschar *ot = NULL; >@@ -462,8 +443,8 @@ > > HDEBUG(D_any) > { >- uschar *listname = readconf_find_option(listptr); >- if (listname[0] != 0) ot = string_sprintf("%s in %s?", name, listname); >+ uschar * listname = readconf_find_option(listptr); >+ if (*listname) ot = string_sprintf("%s in %s?", name, listname); > } > > /* If the list is empty, the answer is no. Skip the debugging output for >@@ -471,7 +452,7 @@ > > if (!*listptr) > { >- HDEBUG(D_lists) if (ot) debug_printf("%s no (option unset)\n", ot); >+ HDEBUG(D_lists) if (ot) debug_printf_indent("%s no (option unset)\n", ot); > return FAIL; > } > >@@ -504,7 +485,7 @@ > { > if (f.expand_string_forcedfail) > { >- HDEBUG(D_lists) debug_printf("expansion of \"%s\" forced failure: " >+ HDEBUG(D_lists) debug_printf_indent("expansion of \"%s\" forced failure: " > "assume not in this list\n", *listptr); > return FAIL; > } >@@ -515,8 +496,18 @@ > } > > /* For an unnamed list, use the expanded version in comments */ >+#define LIST_LIMIT_PR 2048 > >-HDEBUG(D_any) if (!ot) ot = string_sprintf("%s in \"%s\"?", name, list); >+HDEBUG(D_any) if (!ot) >+ { >+ int n, m; >+ gstring * g = string_fmt_append(NULL, "%s in \"%n%.*s%n\"", >+ name, &n, LIST_LIMIT_PR, list, &m); >+ if (m - n >= LIST_LIMIT_PR) g = string_catn(g, US"...", 3); >+ g = string_catn(g, US"?", 1); >+ gstring_release_unused(g); >+ ot = string_from_gstring(g); >+ } > > /* Now scan the list and process each item in turn, until one of them matches, > or we hit an error. */ >@@ -676,7 +667,7 @@ > so we use the permanent store pool */ > > store_pool = POOL_PERM; >- p = store_get(sizeof(namedlist_cacheblock), FALSE); >+ p = store_get(sizeof(namedlist_cacheblock), GET_UNTAINTED); > p->key = string_copy(get_check_key(arg, type)); > > >@@ -686,7 +677,7 @@ > p->next = nb->cache_data; > nb->cache_data = p; > if (*valueptr) >- DEBUG(D_lists) debug_printf("data from lookup saved for " >+ DEBUG(D_lists) debug_printf_indent("data from lookup saved for " > "cache for %s: key '%s' value '%s'\n", ss, p->key, *valueptr); > } > } >@@ -698,7 +689,7 @@ > > else > { >- DEBUG(D_lists) debug_printf("cached %s match for %s\n", >+ DEBUG(D_lists) debug_printf_indent("cached %s match for %s\n", > (bits & (-bits)) == bits ? "yes" : "no", ss); > > cached = US" - cached"; >@@ -712,7 +703,7 @@ > *valueptr = p->data; > break; > } >- DEBUG(D_lists) debug_printf("cached lookup data = %s\n", *valueptr); >+ DEBUG(D_lists) debug_printf_indent("cached lookup data = %s\n", *valueptr); > } > } > >@@ -721,8 +712,8 @@ > > if ((bits & (-bits)) == bits) /* Only one of the two bits is set */ > { >- HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\"%s)\n", ot, >- (yield == OK)? "yes" : "no", sss, cached); >+ HDEBUG(D_lists) debug_printf_indent("%s %s (matched \"%s\"%s)\n", ot, >+ yield == OK ? "yes" : "no", sss, cached); > return yield; > } > } >@@ -735,7 +726,7 @@ > switch ((func)(arg, ss, valueptr, &error)) > { > case OK: >- HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\")\n", ot, >+ HDEBUG(D_lists) debug_printf_indent("%s %s (matched \"%s\")\n", ot, > (yield == OK)? "yes" : "no", sss); > return yield; > >@@ -744,7 +735,7 @@ > error = string_sprintf("DNS lookup of \"%s\" deferred", ss); > if (ignore_defer) > { >- HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_defer\n", >+ HDEBUG(D_lists) debug_printf_indent("%s: item ignored by +ignore_defer\n", > error); > break; > } >@@ -764,12 +755,12 @@ > case ERROR: > if (ignore_unknown) > { >- HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown\n", >+ HDEBUG(D_lists) debug_printf_indent("%s: item ignored by +ignore_unknown\n", > error); > } > else > { >- HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot, >+ HDEBUG(D_lists) debug_printf_indent("%s %s (%s)\n", ot, > include_unknown? "yes":"no", error); > if (!include_unknown) > { >@@ -803,7 +794,7 @@ > if (listname[0] == 0) > listname = string_sprintf("\"%s\"", *listptr); > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", >- string_open_failed(errno, "%s when checking %s", sss, listname)); >+ string_open_failed("%s when checking %s", sss, listname)); > } > > /* Trailing comments are introduced by #, but in an address list or local >@@ -826,19 +817,19 @@ > sss = ss + 1; > } > >- ss = filebuffer + Ustrlen(filebuffer); /* trailing space */ >+ ss = filebuffer + Ustrlen(filebuffer); /* trailing space */ > while (ss > filebuffer && isspace(ss[-1])) ss--; > *ss = 0; > > ss = filebuffer; >- while (isspace(*ss)) ss++; /* leading space */ >+ while (isspace(*ss)) ss++; /* leading space */ > >- if (*ss == 0) continue; /* ignore empty */ >+ if (!*ss) continue; /* ignore empty */ > >- file_yield = yield; /* positive yield */ >- sss = ss; /* for debugging */ >+ file_yield = yield; /* positive yield */ >+ sss = ss; /* for debugging */ > >- if (*ss == '!') /* negation */ >+ if (*ss == '!') /* negation */ > { > file_yield = (file_yield == OK)? FAIL : OK; > while (isspace((*(++ss)))); >@@ -848,7 +839,7 @@ > { > case OK: > (void)fclose(f); >- HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\" in %s)\n", ot, >+ HDEBUG(D_lists) debug_printf_indent("%s %s (matched \"%s\" in %s)\n", ot, > yield == OK ? "yes" : "no", sss, filename); > > /* The "pattern" being matched came from the file; we use a stack-local. >@@ -862,7 +853,7 @@ > error = string_sprintf("DNS lookup of %s deferred", ss); > if (ignore_defer) > { >- HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_defer\n", >+ HDEBUG(D_lists) debug_printf_indent("%s: item ignored by +ignore_defer\n", > error); > break; > } >@@ -877,12 +868,12 @@ > case ERROR: /* host name lookup failed - this can only */ > if (ignore_unknown) /* be for an incoming host (not outgoing) */ > { >- HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown\n", >+ HDEBUG(D_lists) debug_printf_indent("%s: item ignored by +ignore_unknown\n", > error); > } > else > { >- HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot, >+ HDEBUG(D_lists) debug_printf_indent("%s %s (%s)\n", ot, > include_unknown? "yes":"no", error); > (void)fclose(f); > if (!include_unknown) >@@ -908,7 +899,7 @@ > /* End of list reached: if the last item was negated yield OK, else FAIL. */ > > HDEBUG(D_lists) >- debug_printf("%s %s (end of list)\n", ot, yield == OK ? "no":"yes"); >+ debug_printf_indent("%s %s (end of list)\n", ot, yield == OK ? "no":"yes"); > return yield == OK ? FAIL : OK; > > /* Something deferred */ >@@ -1014,19 +1005,18 @@ > static int > check_address(void *arg, const uschar *pattern, const uschar **valueptr, uschar **error) > { >-check_address_block *cb = (check_address_block *)arg; >+check_address_block * cb = (check_address_block *)arg; > check_string_block csb; > int rc; > int expand_inc = 0; >-unsigned int *null = NULL; >-const uschar *listptr; >-uschar *subject = cb->address; >-const uschar *s; >-uschar *pdomain, *sdomain; >+unsigned int * null = NULL; >+const uschar * listptr; >+uschar * subject = cb->address; >+const uschar * s; >+uschar * pdomain, * sdomain; >+uschar * value = NULL; > >-error = error; /* Keep clever compilers from complaining */ >- >-DEBUG(D_lists) debug_printf("address match test: subject=%s pattern=%s\n", >+DEBUG(D_lists) debug_printf_indent("address match test: subject=%s pattern=%s\n", > subject, pattern); > > /* Find the subject's domain */ >@@ -1075,7 +1065,7 @@ > because other patterns expect to have a local part and a domain to match > against. */ > >-if (*subject == 0) return (*pattern == 0)? OK : FAIL; >+if (!*subject) return *pattern ? FAIL : OK; > > /* If the pattern starts with "@@" we have a split lookup, where the domain is > looked up to obtain a list of local parts. If the subject's local part is just >@@ -1085,7 +1075,6 @@ > { > int watchdog = 50; > uschar *list, *ss; >- uschar buffer[1024]; > > if (sdomain == subject + 1 && *subject == '*') return FAIL; > >@@ -1116,7 +1105,7 @@ > /* Look up the local parts provided by the list; negation is permitted. > If a local part has to begin with !, a regex can be used. */ > >- while ((ss = string_nextinlist(CUSS &list, &sep, buffer, sizeof(buffer)))) >+ while ((ss = string_nextinlist(CUSS &list, &sep, NULL, 0))) > { > int local_yield; > >@@ -1198,6 +1187,7 @@ > expand_nlength[cb->expand_setup] = sllen - cllen; > expand_inc = 1; > } >+ value = string_copyn(pattern + 1, cllen); > } > else > { >@@ -1206,6 +1196,7 @@ > ? strncmpic(subject, pattern, sllen) != 0 > : Ustrncmp(subject, pattern, sllen) != 0) return FAIL; > } >+ value = string_copyn(pattern, sllen); > } > > /* If the local part matched, or was not being checked, check the domain using >@@ -1230,16 +1221,23 @@ > listptr = pdomain ? pdomain + 1 : pattern; > if (valueptr) *valueptr = NULL; > >-return match_check_list( >- &listptr, /* list of one item */ >- UCHAR_MAX+1, /* impossible separator; single item */ >- &domainlist_anchor, /* it's a domain list */ >- &null, /* ptr to NULL means no caching */ >- check_string, /* the function to do one test */ >- &csb, /* its data */ >- MCL_DOMAIN + MCL_NOEXPAND, /* domain list; don't expand */ >- csb.subject, /* string for messages */ >- valueptr); /* where to pass back lookup data */ >+ { >+ const uschar * dvalue = NULL; >+ rc = match_check_list( >+ &listptr, /* list of one item */ >+ UCHAR_MAX+1, /* impossible separator; single item */ >+ &domainlist_anchor, /* it's a domain list */ >+ &null, /* ptr to NULL means no caching */ >+ check_string, /* the function to do one test */ >+ &csb, /* its data */ >+ MCL_DOMAIN + MCL_NOEXPAND, /* domain list; don't expand */ >+ csb.subject, /* string for messages */ >+ &dvalue); /* where to pass back lookup data */ >+ if (valueptr && (value || dvalue)) >+ *valueptr = string_sprintf("%s@%s", >+ value ? value : US"", dvalue ? dvalue : US""); >+ } >+return rc; > } > > >@@ -1294,8 +1292,8 @@ > patterns.) Otherwise just the domain is lower cases. A magic item "+caseful" in > the list can be used to restore a caseful copy of the local part from the > original address. >-Limit the subject address size to avoid mem-exhastion attacks. The size chosen >-is historical (we used to use big_buffer her). */ >+Limit the subject address size to avoid mem-exhaustion attacks. The size chosen >+is historical (we used to use big_buffer here). */ > > if ((len = Ustrlen(address)) > BIG_BUFFER_SIZE) len = BIG_BUFFER_SIZE; > ab.address = string_copyn(address, len); >diff -ur exim.orig/src/mime.c exim/src/mime.c >--- exim.orig/src/mime.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/mime.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,9 +2,10 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2015 >+/* >+ * Copyright (c) The Exim Maintainers 2015 - 2022 >+ * Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2015 > * License: GPL >- * Copyright (c) The Exim Maintainers 2015 - 2020 > */ > > #include "exim.h" >@@ -501,7 +502,7 @@ > struct mime_boundary_context nested_context; > > /* reserve a line buffer to work in. Assume tainted data. */ >-header = store_get(MIME_MAX_HEADER_SIZE+1, TRUE); >+header = store_get(MIME_MAX_HEADER_SIZE+1, GET_TAINTED); > > /* Not actually used at the moment, but will be vital to fixing > * some RFC 2046 nonconformance later... */ >diff -ur exim.orig/src/moan.c exim/src/moan.c >--- exim.orig/src/moan.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/moan.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions for sending messages to sender or to mailmaster. */ >@@ -386,7 +386,7 @@ > if (bounce_return_body && message_file) > { > BOOL enddot = f.dot_ends && message_file == stdin; >- uschar * buf = store_get(bounce_return_linesize_limit+2, TRUE); >+ uschar * buf = store_get(bounce_return_linesize_limit+2, GET_TAINTED); > > if (firstline) fprintf(fp, "%s", CS firstline); > >@@ -719,7 +719,6 @@ > uschar *item, *localpart, *domain; > const uschar *listptr = errors_copy; > uschar *yield = NULL; >-uschar buffer[256]; > int sep = 0; > int llen; > >@@ -735,7 +734,7 @@ > > /* Scan through the configured items */ > >-while ((item = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer)))) >+while ((item = string_nextinlist(&listptr, &sep, NULL, 0))) > { > const uschar *newaddress = item; > const uschar *pattern = string_dequote(&newaddress); >diff -ur exim.orig/src/mytypes.h exim/src/mytypes.h >--- exim.orig/src/mytypes.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/mytypes.h 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -30,28 +30,37 @@ > > > /* If gcc is being used to compile Exim, we can use its facility for checking >-the arguments of printf-like functions. This is done by a macro. */ >+the arguments of printf-like functions. This is done by a macro. >+OpenBSD has unfortunately taken to objecting to use of %n in printf >+so we have to give up on all of the available parameter checking. */ > > #if defined(__GNUC__) || defined(__clang__) >-# define PRINTF_FUNCTION(A,B) __attribute__((format(printf,A,B))) >+# ifndef __OpenBSD__ >+# define PRINTF_FUNCTION(A,B) __attribute__((format(printf,A,B))) >+# endif > # define ARG_UNUSED __attribute__((__unused__)) >+# define FUNC_MAYBE_UNUSED __attribute__((__unused__)) > # define WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) > # define ALLOC __attribute__((malloc)) > # define ALLOC_SIZE(A) __attribute__((alloc_size(A))) > # define NORETURN __attribute__((noreturn)) > #else >-# define PRINTF_FUNCTION(A,B) > # define ARG_UNUSED /**/ >+# define FUNC_MAYBE_UNUSED /**/ > # define WARN_UNUSED_RESULT /**/ > # define ALLOC /**/ > # define ALLOC_SIZE(A) /**/ > # define NORETURN /**/ > #endif > >+#ifndef PRINTF_FUNCTION >+# define PRINTF_FUNCTION(A,B) /**/ >+#endif >+ > #ifdef WANT_DEEPER_PRINTF_CHECKS > # define ALMOST_PRINTF(A, B) PRINTF_FUNCTION(A, B) > #else >-# define ALMOST_PRINTF(A, B) >+# define ALMOST_PRINTF(A, B) /**/ > #endif > > >diff -ur exim.orig/src/os.c exim/src/os.c >--- exim.orig/src/os.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/os.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -495,9 +496,11 @@ > > for (struct ifaddrs * ifa = ifalist; ifa; ifa = ifa->ifa_next) > { >- if (ifa->ifa_addr->sa_family != AF_INET >+ struct sockaddr * ifa_addr = ifa->ifa_addr; >+ if (!ifa_addr) continue; >+ if (ifa_addr->sa_family != AF_INET > #if HAVE_IPV6 >- && ifa->ifa_addr->sa_family != AF_INET6 >+ && ifa_addr->sa_family != AF_INET6 > #endif /* HAVE_IPV6 */ > ) > continue; >@@ -508,12 +511,12 @@ > /* Create a data block for the address, fill in the data, and put it on the > chain. */ > >- next = store_get(sizeof(ip_address_item), FALSE); >+ next = store_get(sizeof(ip_address_item), GET_UNTAINTED); > next->next = NULL; > next->port = 0; >- (void)host_ntoa(-1, ifa->ifa_addr, next->address, NULL); >+ (void)host_ntoa(-1, ifa_addr, next->address, NULL); > >- if (yield == NULL) >+ if (!yield) > yield = last = next; > else > { >@@ -743,7 +746,7 @@ > /* Create a data block for the address, fill in the data, and put it on the > chain. */ > >- next = store_get(sizeof(ip_address_item), FALSE); >+ next = store_get(sizeof(ip_address_item), GET_UNTAINTED); > next->next = NULL; > next->port = 0; > (void)host_ntoa(-1, addrp, next->address, NULL); >@@ -775,13 +778,13 @@ > ip_address_item * > os_common_find_running_interfaces(void) > { >-ip_address_item *yield = store_get(sizeof(address_item), FALSE); >+ip_address_item *yield = store_get(sizeof(address_item), GET_UNTAINTED); > yield->address = US"127.0.0.1"; > yield->port = 0; > yield->next = NULL; > > #if HAVE_IPV6 >-yield->next = store_get(sizeof(address_item), FALSE); >+yield->next = store_get(sizeof(address_item), GET_UNTAINTED); > yield->next->address = US"::1"; > yield->next->port = 0; > yield->next->next = NULL; >diff -ur exim.orig/src/parse.c exim/src/parse.c >--- exim.orig/src/parse.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/parse.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions for parsing addresses */ >@@ -22,22 +22,25 @@ > > #ifdef STAND_ALONE > >-address_item *deliver_make_addr(uschar *address, BOOL copy) >+address_item * >+deliver_make_addr(uschar *address, BOOL copy) > { >-address_item *addr = store_get(sizeof(address_item), FALSE); >+address_item *addr = store_get(sizeof(address_item), GET_UNTAINTED); > addr->next = NULL; > addr->parent = NULL; > addr->address = address; > return addr; > } > >-uschar *rewrite_address(uschar *recipient, BOOL dummy1, BOOL dummy2, rewrite_rule >+uschar * >+rewrite_address(uschar *recipient, BOOL dummy1, BOOL dummy2, rewrite_rule > *dummy3, int dummy4) > { > return recipient; > } > >-uschar *rewrite_address_qualify(uschar *recipient, BOOL dummy1) >+uschar * >+rewrite_address_qualify(uschar *recipient, BOOL dummy1) > { > return recipient; > } >@@ -65,7 +68,7 @@ > */ > > uschar * >-parse_find_address_end(uschar *s, BOOL nl_ends) >+parse_find_address_end(const uschar *s, BOOL nl_ends) > { > BOOL source_routing = *s == '@'; > int no_term = source_routing? 1 : 0; >@@ -121,7 +124,7 @@ > } > } > >-return s; >+return US s; > } > > >@@ -224,16 +227,20 @@ > in []. Make sure the output is set to the null string if there is a syntax > error as well as if there is no domain at all. > >+Optionally, msg_id domain literals ( printable-ascii enclosed in [] ) >+are permitted. >+ > Arguments: > s current character pointer > t where to put the domain >+ msg_id_literals flag for relaxed domain-literal processing > errorptr put error message here on failure (*t will be 0 on exit) > > Returns: new character pointer > */ > > static const uschar * >-read_domain(const uschar *s, uschar *t, uschar **errorptr) >+read_domain(const uschar *s, uschar *t, BOOL msg_id_literals, uschar **errorptr) > { > uschar *tt = t; > s = skip_comment(s); >@@ -259,7 +266,11 @@ > t += 5; > s += 5; > } >- while (*s == '.' || *s == ':' || isxdigit(*s)) *t++ = *s++; >+ >+ if (msg_id_literals) >+ while (*s >= 33 && *s <= 90 || *s >= 94 && *s <= 126) *t++ = *s++; >+ else >+ while (*s == '.' || *s == ':' || isxdigit(*s)) *t++ = *s++; > > if (*s == ']') *t++ = *s++; else > { >@@ -267,7 +278,7 @@ > *tt = 0; > } > >- if (!allow_domain_literals) >+ if (!allow_domain_literals && !msg_id_literals) > { > *errorptr = US"domain literals not allowed"; > *tt = 0; >@@ -500,7 +511,7 @@ > while (*s == '@') > { > *t++ = '@'; >- s = read_domain(s+1, t, errorptr); >+ s = read_domain(s+1, t, FALSE, errorptr); > if (*t == 0) return s; > t += Ustrlen((const uschar *)t); > if (*s != ',') break; >@@ -559,7 +570,7 @@ > t += Ustrlen((const uschar *)t); > *t++ = *s++; > *domainptr = t; >- s = read_domain(s, t, errorptr); >+ s = read_domain(s, t, FALSE, errorptr); > } > return s; > } >@@ -619,7 +630,7 @@ > parse_extract_address(const uschar *mailbox, uschar **errorptr, int *start, int *end, > int *domain, BOOL allow_null) > { >-uschar *yield = store_get(Ustrlen(mailbox) + 1, is_tainted(mailbox)); >+uschar * yield = store_get(Ustrlen(mailbox) + 1, mailbox); > const uschar *startptr, *endptr; > const uschar *s = US mailbox; > uschar *t = US yield; >@@ -647,9 +658,9 @@ > > if (*s != '@' && *s != '<') > { >- if (*s == 0 || *s == ';') >+ if (!*s || *s == ';') > { >- if (*t == 0) FAILED(US"empty address"); >+ if (!*t) FAILED(US"empty address"); > endptr = last_comment_position; > goto PARSE_SUCCEEDED; /* Bare local part */ > } >@@ -740,7 +751,7 @@ > } > > endptr = s; >- if (*errorptr != NULL) goto PARSE_FAILED; >+ if (*errorptr) goto PARSE_FAILED; > while (bracket_count-- > 0) if (*s++ != '>') > { > *errorptr = s[-1] == 0 >@@ -759,14 +770,14 @@ > not enclosed in <> as well, which is indicated by an empty first local > part preceding '@'. The source routing is, however, ignored. */ > >-else if (*t == 0) >+else if (!*t) > { > uschar *domainptr = yield; > s = read_route(s, t, errorptr); >- if (*errorptr != NULL) goto PARSE_FAILED; >+ if (*errorptr) goto PARSE_FAILED; > *t = 0; /* Ensure route is ignored - probably overkill */ > s = read_addr_spec(s, t, 0, errorptr, &domainptr); >- if (*errorptr != NULL) goto PARSE_FAILED; >+ if (*errorptr) goto PARSE_FAILED; > *domain = domainptr - yield; > endptr = last_comment_position; > if (*domain == 0) FAILED(US"domain missing in source-routed address"); >@@ -779,8 +790,8 @@ > t += Ustrlen((const uschar *)t); > *t++ = *s++; > *domain = t - yield; >- s = read_domain(s, t, errorptr); >- if (*t == 0) goto PARSE_FAILED; >+ s = read_domain(s, t, TRUE, errorptr); >+ if (!*t) goto PARSE_FAILED; > endptr = last_comment_position; > } > >@@ -789,7 +800,7 @@ > move it back past white space if necessary. */ > > PARSE_SUCCEEDED: >-if (*s != 0) >+if (*s) > { > if (f.parse_found_group && *s == ';') > { >@@ -863,7 +874,8 @@ > */ > > const uschar * >-parse_quote_2047(const uschar *string, int len, uschar *charset, BOOL fold) >+parse_quote_2047(const uschar *string, int len, const uschar *charset, >+ BOOL fold) > { > const uschar * s = string; > int hlen, l; >@@ -904,8 +916,13 @@ > { g = string_catn(g, s, 1); first_byte = FALSE; } > } > >-g = string_catn(g, US"?=", 2); >-return coded ? string_from_gstring(g) : string; >+if (coded) >+ string = string_from_gstring(g = string_catn(g, US"?=", 2)); >+else >+ g->ptr = -1; >+ >+gstring_release_unused(g); >+return string; > } > > >@@ -980,11 +997,9 @@ > /* No non-printers; use the RFC 822 quoting rules */ > > if (len <= 0 || len >= INT_MAX/4) >- { >- return string_copy_taint(CUS"", is_tainted(phrase)); >- } >+ return string_copy_taint(CUS"", phrase); > >-buffer = store_get((len+1)*4, is_tainted(phrase)); >+buffer = store_get((len+1)*4, phrase); > > s = phrase; > end = s + len; >@@ -1228,8 +1243,8 @@ > */ > > int >-parse_forward_list(uschar *s, int options, address_item **anchor, >- uschar **error, const uschar *incoming_domain, uschar *directory, >+parse_forward_list(const uschar *s, int options, address_item **anchor, >+ uschar **error, const uschar *incoming_domain, const uschar *directory, > error_block **syntax_errors) > { > int count = 0; >@@ -1238,18 +1253,15 @@ > > for (;;) > { >- int len; >- int special = 0; >- int specopt = 0; >- int specbit = 0; >- uschar *ss, *nexts; >- address_item *addr; >+ int len, special = 0, specopt = 0, specbit = 0; >+ const uschar * ss, * nexts; >+ address_item * addr; > BOOL inquote = FALSE; > > for (;;) > { > while (isspace(*s) || *s == ',') s++; >- if (*s == '#') { while (*s != 0 && *s != '\n') s++; } else break; >+ if (*s == '#') { while (*s && *s != '\n') s++; } else break; > } > > /* When we reach the end of the list, we return FF_DELIVERED if any child >@@ -1270,18 +1282,17 @@ > syntax error has been skipped. I now think it is the wrong approach, but > have left this here just in case, and for the record. */ > >- #ifdef NEVER >+#ifdef NEVER > if (count > 0) return FF_DELIVERED; /* Something was generated */ > >- if (syntax_errors == NULL || /* Not skipping syntax errors, or */ >- *syntax_errors == NULL) /* we didn't actually skip any */ >+ if (!syntax_errors || /* Not skipping syntax errors, or */ >+ !*syntax_errors) /* we didn't actually skip any */ > return FF_NOTDELIVERED; > > *error = string_sprintf("no addresses generated: syntax error in %s: %s", > (*syntax_errors)->text2, (*syntax_errors)->text1); > return FF_ERROR; >- #endif >- >+#endif > } > > /* Find the end of the next address. Quoted strings in addresses may contain >@@ -1298,7 +1309,7 @@ > > /* Remove any trailing spaces; we know there's at least one non-space. */ > >- while (isspace((ss[-1]))) ss--; >+ while (isspace(ss[-1])) ss--; > > /* We now have s->start and ss->end of the next address. Remove quotes > if they completely enclose, remembering the address started with a quote >@@ -1311,20 +1322,14 @@ > ss--; > inquote = TRUE; > while (s < ss && isspace(*s)) s++; >- while (ss > s && isspace((ss[-1]))) ss--; >+ while (ss > s && isspace(ss[-1])) ss--; > } > > /* Set up the length of the address. */ > > len = ss - s; > >- DEBUG(D_route) >- { >- int save = s[len]; >- s[len] = 0; >- debug_printf("extract item: %s\n", s); >- s[len] = save; >- } >+ DEBUG(D_route) debug_printf("extract item: %.*s\n", len, s); > > /* Handle special addresses if permitted. If the address is :unknown: > ignore it - this is for backward compatibility with old alias files. You >@@ -1345,18 +1350,18 @@ > else if (Ustrncmp(s, ":fail:", 6) == 0) > { special = FF_FAIL; specopt = RDO_FAIL; } /* specbit is 0 */ > >- if (special != 0) >+ if (special) > { >- uschar *ss = Ustrchr(s+1, ':') + 1; >+ uschar * ss = Ustrchr(s+1, ':') + 1; /* line after the special... */ > if ((options & specopt) == specbit) > { > *error = string_sprintf("\"%.*s\" is not permitted", len, s); > return FF_ERROR; > } >- while (*ss != 0 && isspace(*ss)) ss++; >- while (s[len] != 0 && s[len] != '\n') len++; >- s[len] = 0; >- *error = string_copy(ss); >+ while (*ss && isspace(*ss)) ss++; /* skip leading whitespace */ >+ if ((len = Ustrlen(ss)) > 0) /* ignore trailing newlines */ >+ for (const uschar * t = ss + len - 1; t >= ss && *t == '\n'; t--) len--; >+ *error = string_copyn(ss, len); /* becomes the error */ > return special; > } > >@@ -1367,14 +1372,14 @@ > > if (Ustrncmp(s, ":include:", 9) == 0) > { >- uschar *filebuf; >+ uschar * filebuf; > uschar filename[256]; >- uschar *t = s+9; >+ const uschar * t = s+9; > int flen = len - 9; > int frc; > struct stat statbuf; >- address_item *last; >- FILE *f; >+ address_item * last; >+ FILE * f; > > while (flen > 0 && isspace(*t)) { t++; flen--; } > >@@ -1384,7 +1389,7 @@ > return FF_ERROR; > } > >- if (flen > 255) >+ if (flen > sizeof(filename)-1) > { > *error = string_sprintf("included file name \"%s\" is too long", t); > return FF_ERROR; >@@ -1422,8 +1427,10 @@ > if (directory) > { > int len = Ustrlen(directory); >- uschar *p = filename + len; >+ uschar * p; > >+ while (len > 0 && directory[len-1] == '/') len--; /* ignore trailing '/' */ >+ p = filename + len; > if (Ustrncmp(filename, directory, len) != 0 || *p != '/') > { > *error = string_sprintf("included file %s is not in directory %s", >@@ -1438,7 +1445,7 @@ > with a flag that fails symlinks. */ > > { >- int fd = exim_open2(CS directory, O_RDONLY); >+ int fd = exim_open2(CCS directory, O_RDONLY); > if (fd < 0) > { > *error = string_sprintf("failed to open directory %s", directory); >@@ -1448,9 +1455,10 @@ > { > uschar temp; > int fd2; >- uschar * q = p; >+ uschar * q = p + 1; /* skip dividing '/' */ > >- while (*++p && *p != '/') ; >+ while (*q == '/') q++; /* skip extra '/' */ >+ while (*++p && *p != '/') ; /* end of component */ > temp = *p; > *p = '\0'; > >@@ -1508,7 +1516,7 @@ > > if (!f) > { >- *error = string_open_failed(errno, "included file %s", filename); >+ *error = string_open_failed("included file %s", filename); > return FF_INCLUDEFAIL; > } > >@@ -1538,7 +1546,7 @@ > return FF_ERROR; > } > >- filebuf = store_get(statbuf.st_size + 1, is_tainted(filename)); >+ filebuf = store_get(statbuf.st_size + 1, filename); > if (fread(filebuf, 1, statbuf.st_size, f) != statbuf.st_size) > { > *error = string_sprintf("error while reading included file %s: %s", >@@ -1586,18 +1594,17 @@ > { > int start, end, domain; > const uschar *recipient = NULL; >- int save = s[len]; >- s[len] = 0; >+ uschar * s_ltd = string_copyn(s, len); > > /* If it starts with \ and the rest of it parses as a valid mail address > without a domain, carry on with that address, but qualify it with the > incoming domain. Otherwise arrange for the address to fall through, > causing an error message on the re-parse. */ > >- if (*s == '\\') >+ if (*s_ltd == '\\') > { > recipient = >- parse_extract_address(s+1, error, &start, &end, &domain, FALSE); >+ parse_extract_address(s_ltd+1, error, &start, &end, &domain, FALSE); > if (recipient) > recipient = domain != 0 ? NULL : > string_sprintf("%s@%s", recipient, incoming_domain); >@@ -1606,22 +1613,22 @@ > /* Try parsing the item as an address. */ > > if (!recipient) recipient = >- parse_extract_address(s, error, &start, &end, &domain, FALSE); >+ parse_extract_address(s_ltd, error, &start, &end, &domain, FALSE); > > /* If item starts with / or | and is not a valid address, or there > is no domain, treat it as a file or pipe. If it was a quoted item, > remove the quoting occurrences of \ within it. */ > >- if ((*s == '|' || *s == '/') && (recipient == NULL || domain == 0)) >+ if ((*s_ltd == '|' || *s_ltd == '/') && (!recipient || domain == 0)) > { >- uschar *t = store_get(Ustrlen(s) + 1, is_tainted(s)); >- uschar *p = t; >- uschar *q = s; >- while (*q != 0) >+ uschar * t = store_get(Ustrlen(s_ltd) + 1, s_ltd); >+ uschar * p = t, * q = s_ltd; >+ >+ while (*q) > { > if (inquote) > { >- *p++ = (*q == '\\')? *(++q) : *q; >+ *p++ = *q == '\\' ? *++q : *q; > q++; > } > else *p++ = *q++; >@@ -1629,7 +1636,7 @@ > *p = 0; > addr = deliver_make_addr(t, TRUE); > setflag(addr, af_pfr); /* indicates pipe/file/reply */ >- if (*s != '|') setflag(addr, af_file); /* indicates file */ >+ if (*s_ltd != '|') setflag(addr, af_file); /* indicates file */ > } > > /* Item must be an address. Complain if not, else qualify, rewrite and set >@@ -1641,36 +1648,35 @@ > > else > { >- if (recipient == NULL) >+ if (!recipient) > { > if (Ustrcmp(*error, "empty address") == 0) > { > *error = NULL; >- s[len] = save; > s = nexts; > continue; > } > >- if (syntax_errors != NULL) >+ if (syntax_errors) > { >- error_block *e = store_get(sizeof(error_block), FALSE); >- error_block *last = *syntax_errors; >- if (last == NULL) *syntax_errors = e; else >+ error_block * e = store_get(sizeof(error_block), GET_UNTAINTED); >+ error_block * last = *syntax_errors; >+ if (last) > { >- while (last->next != NULL) last = last->next; >+ while (last->next) last = last->next; > last->next = e; > } >+ else >+ *syntax_errors = e; > e->next = NULL; > e->text1 = *error; >- e->text2 = string_copy(s); >- s[len] = save; >+ e->text2 = s_ltd; > s = nexts; > continue; > } > else > { >- *error = string_sprintf("%s in \"%s\"", *error, s); >- s[len] = save; /* _after_ using it for *error */ >+ *error = string_sprintf("%s in \"%s\"", *error, s_ltd); > return FF_ERROR; > } > } >@@ -1678,17 +1684,15 @@ > /* Address was successfully parsed. Rewrite, and then make an address > block. */ > >- recipient = ((options & RDO_REWRITE) != 0)? >- rewrite_address(recipient, TRUE, FALSE, global_rewrite_rules, >- rewrite_existflags) : >- rewrite_address_qualify(recipient, TRUE); /*XXX loses track of const */ >+ recipient = options & RDO_REWRITE >+ ? rewrite_address(recipient, TRUE, FALSE, global_rewrite_rules, >+ rewrite_existflags) >+ : rewrite_address_qualify(recipient, TRUE); /*XXX loses track of const */ > addr = deliver_make_addr(US recipient, TRUE); /* TRUE => copy recipient, so deconst ok */ > } > >- /* Restore the final character in the original data, and add to the >- output chain. */ >+ /* Add the original data to the output chain. */ > >- s[len] = save; > addr->next = *anchor; > *anchor = addr; > count++; >@@ -1735,7 +1739,7 @@ > line. Therefore, take care to release unwanted store afterwards. */ > > reset_point = store_mark(); >-id = *yield = store_get(Ustrlen(str) + 1, is_tainted(str)); >+id = *yield = store_get(Ustrlen(str) + 1, str); > *id++ = *str++; > > str = read_addr_spec(str, id, '>', error, &domain); >@@ -2086,6 +2090,7 @@ > int start, end, domain; > uschar buffer[1024]; > >+store_init(); > big_buffer = store_malloc(big_buffer_size); > > /* strip_trailing_dot = TRUE; */ >diff -ur exim.orig/src/pdkim/pdkim.c exim/src/pdkim/pdkim.c >--- exim.orig/src/pdkim/pdkim.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/pdkim/pdkim.c 2022-06-23 16:41:10.000000000 +0300 >@@ -1,6 +1,7 @@ > /* > * PDKIM - a RFC4871 (DKIM) implementation > * >+ * Copyright (c) The Exim Maintainers 2021 - 2022 > * Copyright (C) 2009 - 2016 Tom Kistner <tom@duncanthrax.net> > * Copyright (C) 2016 - 2020 Jeremy Harris <jgh@exim.org> > * >@@ -247,7 +248,7 @@ > static pdkim_stringlist * > pdkim_prepend_stringlist(pdkim_stringlist * base, const uschar * str) > { >-pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist), FALSE); >+pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist), GET_UNTAINTED); > > memset(new_entry, 0, sizeof(pdkim_stringlist)); > new_entry->value = string_copy(str); >@@ -337,7 +338,7 @@ > { > BOOL past_field_name = FALSE; > BOOL seen_wsp = FALSE; >-uschar * relaxed = store_get(len+3, TRUE); /* tainted */ >+uschar * relaxed = store_get(len+3, GET_TAINTED); > uschar * q = relaxed; > > for (const uschar * p = header; p - header < len; p++) >@@ -417,7 +418,7 @@ > int nchar = 0; > uschar * q; > const uschar * p = str; >-uschar * n = store_get(Ustrlen(str)+1, TRUE); >+uschar * n = store_get(Ustrlen(str)+1, GET_TAINTED); > > *n = '\0'; > q = n; >@@ -474,7 +475,7 @@ > BOOL in_b_val = FALSE; > int where = PDKIM_HDR_LIMBO; > >-sig = store_get(sizeof(pdkim_signature), FALSE); >+sig = store_get(sizeof(pdkim_signature), GET_UNTAINTED); > memset(sig, 0, sizeof(pdkim_signature)); > sig->bodylength = -1; > >@@ -483,7 +484,7 @@ > sig->keytype = -1; > sig->hashtype = -1; > >-q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1, TRUE); /* tainted */ >+q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1, GET_TAINTED); > > for (uschar * p = raw_hdr; ; p++) > { >@@ -663,7 +664,7 @@ > int sep = ';'; > pdkim_pubkey * pub; > >-pub = store_get(sizeof(pdkim_pubkey), TRUE); /* tainted */ >+pub = store_get(sizeof(pdkim_pubkey), GET_TAINTED); > memset(pub, 0, sizeof(pdkim_pubkey)); > > while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0))) >@@ -793,8 +794,9 @@ > { > for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next) /* Finish hashes */ > { >- DEBUG(D_acl) debug_printf("DKIM: finish bodyhash %d/%d/%ld len %ld\n", >- b->hashtype, b->canon_method, b->bodylength, b->signed_body_bytes); >+ DEBUG(D_acl) debug_printf("DKIM: finish bodyhash %s/%s/%ld len %ld\n", >+ pdkim_hashes[b->hashtype].dkim_hashname, pdkim_canons[b->canon_method], >+ b->bodylength, b->signed_body_bytes); > exim_sha_finish(&b->body_hash_ctx, &b->bh); > } > >@@ -805,10 +807,10 @@ > > DEBUG(D_acl) > { >- debug_printf("DKIM [%s] Body bytes (%s) hashed: %lu\n" >- "DKIM [%s] Body %s computed: ", >- sig->domain, pdkim_canons[b->canon_method], b->signed_body_bytes, >- sig->domain, pdkim_hashes[b->hashtype].dkim_hashname); >+ debug_printf("DKIM [%s]%s Body bytes (%s) hashed: %lu\n" >+ "DKIM [%s]%s Body %s computed: ", >+ sig->domain, sig->selector, pdkim_canons[b->canon_method], b->signed_body_bytes, >+ sig->domain, sig->selector, pdkim_hashes[b->hashtype].dkim_hashname); > pdkim_hexprint(CUS b->bh.data, b->bh.len); > } > >@@ -1264,7 +1266,7 @@ > > if (sig->created > 0) > { >- uschar minibuf[20]; >+ uschar minibuf[21]; > > snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->created); > hdr = pdkim_headcat(&col, hdr, US";", US"t=", minibuf); >@@ -1272,7 +1274,7 @@ > > if (sig->expires > 0) > { >- uschar minibuf[20]; >+ uschar minibuf[21]; > > snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->expires); > hdr = pdkim_headcat(&col, hdr, US";", US"x=", minibuf); >@@ -1280,7 +1282,7 @@ > > if (sig->bodylength >= 0) > { >- uschar minibuf[20]; >+ uschar minibuf[21]; > > snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->bodylength); > hdr = pdkim_headcat(&col, hdr, US";", US"l=", minibuf); >@@ -1611,7 +1613,7 @@ > rh = pdkim_relax_header(rh, TRUE); /* cook header for relaxed canon */ > > /* Feed header to the hash algorithm */ >- exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh)); >+ exim_sha_update_string(&hhash_ctx, CUS rh); > > /* Remember headers block for signing (when the library cannot do incremental) */ > /*XXX we could avoid doing this for all but the GnuTLS/RSA case */ >@@ -1672,7 +1674,7 @@ > : string_copy(CUS hdrs->value); > > /* Feed header to the hash algorithm */ >- exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh)); >+ exim_sha_update_string(&hhash_ctx, CUS rh); > > DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh)); > hdrs->tag = 1; >@@ -1713,7 +1715,7 @@ > } > > /* Finalize header hash */ >- exim_sha_update(&hhash_ctx, CUS sig_hdr, Ustrlen(sig_hdr)); >+ exim_sha_update_string(&hhash_ctx, CUS sig_hdr); > exim_sha_finish(&hhash_ctx, &hhash); > > DEBUG(D_acl) >@@ -1918,13 +1920,14 @@ > { > pdkim_ctx * ctx; > >-ctx = store_get(sizeof(pdkim_ctx), FALSE); >+ctx = store_get(sizeof(pdkim_ctx), GET_UNTAINTED); > memset(ctx, 0, sizeof(pdkim_ctx)); > > if (dot_stuffing) ctx->flags = PDKIM_DOT_TERM; > /* The line-buffer is for message data, hence tainted */ >-ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, TRUE); >+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, GET_TAINTED); > ctx->dns_txt_callback = dns_txt_callback; >+ctx->cur_header = string_get_tainted(36, GET_TAINTED); > > return ctx; > } >@@ -1945,7 +1948,7 @@ > > /* Allocate & init one signature struct */ > >-sig = store_get(sizeof(pdkim_signature), FALSE); >+sig = store_get(sizeof(pdkim_signature), GET_UNTAINTED); > memset(sig, 0, sizeof(pdkim_signature)); > > sig->bodylength = -1; >@@ -2026,14 +2029,14 @@ > && canon_method == b->canon_method > && bodylength == b->bodylength) > { >- DEBUG(D_receive) debug_printf("DKIM: using existing bodyhash %d/%d/%ld\n", >- hashtype, canon_method, bodylength); >+ DEBUG(D_receive) debug_printf("DKIM: using existing bodyhash %s/%s/%ld\n", >+ pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[canon_method], bodylength); > return b; > } > >-DEBUG(D_receive) debug_printf("DKIM: new bodyhash %d/%d/%ld\n", >- hashtype, canon_method, bodylength); >-b = store_get(sizeof(pdkim_bodyhash), FALSE); >+DEBUG(D_receive) debug_printf("DKIM: new bodyhash %s/%s/%ld\n", >+ pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[canon_method], bodylength); >+b = store_get(sizeof(pdkim_bodyhash), GET_UNTAINTED); > b->next = ctx->bodyhash; > b->hashtype = hashtype; > b->canon_method = canon_method; >@@ -2078,7 +2081,7 @@ > memset(ctx, 0, sizeof(pdkim_ctx)); > ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN; > /* The line buffer is for message data, hence tainted */ >-ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, TRUE); >+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, GET_TAINTED); > DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback; > } > >diff -ur exim.orig/src/pdkim/signing.c exim/src/pdkim/signing.c >--- exim.orig/src/pdkim/signing.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/pdkim/signing.c 2022-06-23 16:41:10.000000000 +0300 >@@ -1,7 +1,6 @@ > /* > * PDKIM - a RFC4871 (DKIM) implementation >- * >- * Copyright (C) 1995 - 2020 Exim maintainers >+ * Copyright (c) The Exim Maintainers 1995 - 2022 > * > * signing/verification interface > */ >@@ -37,6 +36,10 @@ > #ifdef SIGN_GNUTLS > # define EXIM_GNUTLS_LIBRARY_LOG_LEVEL 3 > >+# ifndef GNUTLS_VERIFY_ALLOW_BROKEN >+# define GNUTLS_VERIFY_ALLOW_BROKEN 0 >+# endif >+ > > /* Logging function which can be registered with > * gnutls_global_set_log_function() >@@ -219,7 +222,8 @@ > default: return US"nonhandled hash type"; > } > >- if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->key, algo, 0, &k, &s)) < 0) >+ if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->key, algo, >+ GNUTLS_VERIFY_ALLOW_BROKEN, &k, &s)) < 0) > ret = US gnutls_strerror(rc); > } > >@@ -502,7 +506,7 @@ > } > > #define SIGSPACE 128 >-sig->data = store_get(SIGSPACE, FALSE); >+sig->data = store_get(SIGSPACE, GET_UNTAINTED); > > if (gcry_mpi_cmp (sign_ctx->p, sign_ctx->q) > 0) > { >@@ -761,7 +765,7 @@ > if ( (ctx = EVP_MD_CTX_new()) > && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0 > && EVP_DigestSign(ctx, NULL, &siglen, NULL, 0) > 0 >- && (sig->data = store_get(siglen, FALSE)) >+ && (sig->data = store_get(siglen, GET_UNTAINTED)) > > /* Obtain the signature (slen could change here!) */ > && EVP_DigestSign(ctx, sig->data, &siglen, data->data, data->len) > 0 >@@ -777,7 +781,7 @@ > && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0 > && EVP_DigestSignUpdate(ctx, data->data, data->len) > 0 > && EVP_DigestSignFinal(ctx, NULL, &siglen) > 0 >- && (sig->data = store_get(siglen, FALSE)) >+ && (sig->data = store_get(siglen, GET_UNTAINTED)) > > /* Obtain the signature (slen could change here!) */ > && EVP_DigestSignFinal(ctx, sig->data, &siglen) > 0 >diff -ur exim.orig/src/perl.c exim/src/perl.c >--- exim.orig/src/perl.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/perl.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 1999 - 2022 */ > /* Copyright (c) 1998 Malcolm Beattie */ >-/* Copyright (C) 1999 - 2018 Exim maintainers */ > > /* Modified by PH to get rid of the "na" usage, March 1999. > Modified further by PH for general tidying for Exim 4. >@@ -15,6 +15,10 @@ > /* See the file NOTICE for conditions of use and distribution. */ > > #include <assert.h> >+ >+#define HINTSDB_H >+#define DBFUNCTIONS_H >+ > #include "exim.h" > > #define EXIM_TRUE TRUE >diff -ur exim.orig/src/queue.c exim/src/queue.c >--- exim.orig/src/queue.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/queue.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions that operate on the input queue. */ >@@ -26,6 +26,9 @@ > #define LOG2_MAXNODES 32 > > >+#ifndef DISABLE_TLS >+static BOOL queue_tls_init = FALSE; >+#endif > > /************************************************* > * Helper sort function for queue_get_spool_list * >@@ -211,8 +214,8 @@ > (*pcount)++; > else > { >- queue_filename *next = >- store_get(sizeof(queue_filename) + Ustrlen(name), is_tainted(name)); >+ queue_filename * next = >+ store_get(sizeof(queue_filename) + Ustrlen(name), name); > Ustrcpy(next->text, name); > next->dir_uschar = subdirchar; > >@@ -347,8 +350,8 @@ > { > BOOL force_delivery = f.queue_run_force || deliver_selectstring != NULL || > deliver_selectstring_sender != NULL; >-const pcre *selectstring_regex = NULL; >-const pcre *selectstring_regex_sender = NULL; >+const pcre2_code *selectstring_regex = NULL; >+const pcre2_code *selectstring_regex_sender = NULL; > uschar *log_detail = NULL; > int subcount = 0; > uschar subdirs[64]; >@@ -566,9 +569,7 @@ > > else if ( deliver_selectstring_sender > && !(f.deliver_selectstring_sender_regex >- ? (pcre_exec(selectstring_regex_sender, NULL, >- CS sender_address, Ustrlen(sender_address), 0, PCRE_EOPT, >- NULL, 0) >= 0) >+ ? regex_match(selectstring_regex_sender, sender_address, -1, NULL) > : (strstric(sender_address, deliver_selectstring_sender, FALSE) > != NULL) > ) ) >@@ -587,8 +588,7 @@ > { > uschar *address = recipients_list[i].address; > if ( (f.deliver_selectstring_regex >- ? (pcre_exec(selectstring_regex, NULL, CS address, >- Ustrlen(address), 0, PCRE_EOPT, NULL, 0) >= 0) >+ ? regex_match(selectstring_regex, address, -1, NULL) > : (strstric(address, deliver_selectstring, FALSE) != NULL) > ) > && tree_search(tree_nonrecipients, address) == NULL >@@ -654,6 +654,16 @@ > report_time_since(×tamp_startup, US"queue msg selected"); > #endif > >+#ifndef DISABLE_TLS >+ if (!queue_tls_init) >+ { >+ queue_tls_init = TRUE; >+ /* Preload TLS library info for smtp transports. Once, and only if we >+ have a delivery to do. */ >+ tls_client_creds_reload(FALSE); >+ } >+#endif >+ > single_item_retry: > if ((pid = exim_fork(US"qrun-delivery")) == 0) > { >@@ -891,8 +901,8 @@ > queue_filename *last = NULL; > for (int i = 0; i < count; i++) > { >- queue_filename *next = >- store_get(sizeof(queue_filename) + Ustrlen(list[i]) + 2, is_tainted(list[i])); >+ queue_filename * next = >+ store_get(sizeof(queue_filename) + Ustrlen(list[i]) + 2, list[i]); > sprintf(CS next->text, "%s-H", list[i]); > next->dir_uschar = '*'; > next->next = NULL; >@@ -1338,15 +1348,15 @@ > deliver_domain = dom > ? CUS string_copyn(addr+dom, end - dom) : CUS""; > >- event_raise(event_action, US"msg:fail:internal", >- string_sprintf("message removed by %s", username)); >+ (void) event_raise(event_action, US"msg:fail:internal", >+ string_sprintf("message removed by %s", username), NULL); > > deliver_localpart = save_local; > deliver_domain = save_domain; > } > } > } >- (void) event_raise(event_action, US"msg:complete", NULL); >+ (void) event_raise(event_action, US"msg:complete", NULL, NULL); > #endif > log_write(0, LOG_MAIN, "removed by %s", username); > log_write(0, LOG_MAIN, "Completed"); >@@ -1537,7 +1547,7 @@ > /******************************************************************************/ > /******************************************************************************/ > >-#ifdef EXPERIMENTAL_QUEUE_RAMP >+#ifndef DISABLE_QUEUE_RAMP > void > queue_notify_daemon(const uschar * msgid) > { >diff -ur exim.orig/src/rda.c exim/src/rda.c >--- exim.orig/src/rda.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/rda.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* This module contains code for extracting addresses from a forwarding list >@@ -166,7 +166,7 @@ > */ > > static uschar * >-rda_get_file_contents(redirect_block *rdata, int options, uschar **error, >+rda_get_file_contents(const redirect_block *rdata, int options, uschar **error, > int *yield) > { > FILE *fwd; >@@ -222,7 +222,7 @@ > > DEFAULT_ERROR: > default: >- *error = string_open_failed(errno, "%s", filename); >+ *error = string_open_failed("%s", filename); > *yield = FF_ERROR; > return NULL; > } >@@ -284,7 +284,7 @@ > > /* Read the file in one go in order to minimize the time we have it open. */ > >-filebuf = store_get(statbuf.st_size + 1, is_tainted(filename)); >+filebuf = store_get(statbuf.st_size + 1, filename); > > if (fread(filebuf, 1, statbuf.st_size, fwd) != statbuf.st_size) > { >@@ -339,13 +339,13 @@ > */ > > static int >-rda_extract(redirect_block *rdata, int options, uschar *include_directory, >- uschar *sieve_vacation_directory, uschar *sieve_enotify_mailto_owner, >- uschar *sieve_useraddress, uschar *sieve_subaddress, >- address_item **generated, uschar **error, error_block **eblockp, >- int *filtertype) >+rda_extract(const redirect_block * rdata, int options, >+ const uschar * include_directory, const uschar * sieve_vacation_directory, >+ const uschar * sieve_enotify_mailto_owner, const uschar * sieve_useraddress, >+ const uschar * sieve_subaddress, address_item ** generated, uschar ** error, >+ error_block ** eblockp, int * filtertype) > { >-uschar *data; >+const uschar * data; > > if (rdata->isfile) > { >@@ -442,9 +442,9 @@ > static int > rda_write_string(int fd, const uschar *s) > { >-int len = (s == NULL)? 0 : Ustrlen(s) + 1; >+int len = s ? Ustrlen(s) + 1 : 0; > return ( write(fd, &len, sizeof(int)) != sizeof(int) >- || (s != NULL && write(fd, s, len) != len) >+ || (s && write(fd, s, len) != len) > ) > ? -1 : 0; > } >@@ -476,7 +476,7 @@ > /* We know we have enough memory so disable the error on "len" */ > /* coverity[tainted_data] */ > /* We trust the data source, so untainted */ >- if (read(fd, *sp = store_get(len, FALSE), len) != len) return FALSE; >+ if (read(fd, *sp = store_get(len, GET_UNTAINTED), len) != len) return FALSE; > return TRUE; > } > >@@ -541,11 +541,11 @@ > */ > > int >-rda_interpret(redirect_block *rdata, int options, uschar *include_directory, >- uschar *sieve_vacation_directory, uschar *sieve_enotify_mailto_owner, >- uschar *sieve_useraddress, uschar *sieve_subaddress, ugid_block *ugid, >- address_item **generated, uschar **error, error_block **eblockp, >- int *filtertype, uschar *rname) >+rda_interpret(redirect_block * rdata, int options, >+ const uschar * include_directory, const uschar * sieve_vacation_directory, >+ const uschar * sieve_enotify_mailto_owner, const uschar * sieve_useraddress, >+ const uschar * sieve_subaddress, const ugid_block * ugid, address_item ** generated, >+ uschar ** error, error_block ** eblockp, int * filtertype, const uschar * rname) > { > int fd, rc, pfd[2]; > int yield, status; >@@ -808,7 +808,7 @@ > uschar *s; > if (!rda_read_string(fd, &s)) goto DISASTER; > if (!s) break; >- e = store_get(sizeof(error_block), FALSE); >+ e = store_get(sizeof(error_block), GET_UNTAINTED); > e->next = NULL; > e->text1 = s; > if (!rda_read_string(fd, &s)) goto DISASTER; >@@ -907,7 +907,7 @@ > > if (i > 0) > { >- addr->pipe_expandn = store_get((i+1) * sizeof(uschar *), FALSE); >+ addr->pipe_expandn = store_get((i+1) * sizeof(uschar *), GET_UNTAINTED); > addr->pipe_expandn[i] = NULL; > while (--i >= 0) addr->pipe_expandn[i] = expandn[i]; > } >@@ -917,7 +917,7 @@ > if (read(fd, &reply_options, sizeof(int)) != sizeof(int)) goto DISASTER; > if ((reply_options & REPLY_EXISTS) != 0) > { >- addr->reply = store_get(sizeof(reply_item), FALSE); >+ addr->reply = store_get(sizeof(reply_item), GET_UNTAINTED); > > addr->reply->file_expand = (reply_options & REPLY_EXPAND) != 0; > addr->reply->return_message = (reply_options & REPLY_RETURN) != 0; >@@ -961,16 +961,14 @@ > *error = string_sprintf("internal problem in %s: failure to transfer " > "data from subprocess: status=%04x%s%s%s", rname, > status, readerror, >- (*error == NULL)? US"" : US": error=", >- (*error == NULL)? US"" : *error); >+ *error ? US": error=" : US"", >+ *error ? *error : US""); > log_write(0, LOG_MAIN|LOG_PANIC, "%s", *error); > } > else if (status != 0) >- { > log_write(0, LOG_MAIN|LOG_PANIC, "internal problem in %s: unexpected status " > "%04x from redirect subprocess (but data correctly received)", rname, > status); >- } > > FINAL_EXIT: > (void)close(fd); >diff -ur exim.orig/src/readconf.c exim/src/readconf.c >--- exim.orig/src/readconf.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/readconf.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions for reading the configuration file, and for displaying >@@ -181,6 +181,10 @@ > #ifdef SUPPORT_PROXY > { "hosts_proxy", opt_stringptr, {&hosts_proxy} }, > #endif >+#ifndef DISABLE_TLS >+ { "hosts_require_alpn", opt_stringptr, {&hosts_require_alpn} }, >+#endif >+ { "hosts_require_helo", opt_stringptr, {&hosts_require_helo} }, > { "hosts_treat_as_local", opt_stringptr, {&hosts_treat_as_local} }, > #ifdef LOOKUP_IBASE > { "ibase_servers", opt_stringptr, {&ibase_servers} }, >@@ -201,6 +205,9 @@ > { "ldap_start_tls", opt_bool, {&eldap_start_tls} }, > { "ldap_version", opt_int, {&eldap_version} }, > #endif >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ { "limits_advertise_hosts", opt_stringptr, {&limits_advertise_hosts} }, >+#endif > { "local_from_check", opt_bool, {&local_from_check} }, > { "local_from_prefix", opt_stringptr, {&local_from_prefix} }, > { "local_from_suffix", opt_stringptr, {&local_from_suffix} }, >@@ -259,10 +266,13 @@ > { "print_topbitchars", opt_bool, {&print_topbitchars} }, > { "process_log_path", opt_stringptr, {&process_log_path} }, > { "prod_requires_admin", opt_bool, {&prod_requires_admin} }, >+#ifdef SUPPORT_PROXY >+ { "proxy_protocol_timeout", opt_time, {&proxy_protocol_timeout} }, >+#endif > { "qualify_domain", opt_stringptr, {&qualify_domain_sender} }, > { "qualify_recipient", opt_stringptr, {&qualify_domain_recipient} }, > { "queue_domains", opt_stringptr, {&queue_domains} }, >-#ifdef EXPERIMENTAL_QUEUE_RAMP >+#ifndef DISABLE_QUEUE_RAMP > { "queue_fast_ramp", opt_bool, {&queue_fast_ramp} }, > #endif > { "queue_list_requires_admin",opt_bool, {&queue_list_requires_admin} }, >@@ -297,12 +307,13 @@ > { "smtp_accept_max", opt_int, {&smtp_accept_max} }, > { "smtp_accept_max_nonmail", opt_int, {&smtp_accept_max_nonmail} }, > { "smtp_accept_max_nonmail_hosts", opt_stringptr, {&smtp_accept_max_nonmail_hosts} }, >- { "smtp_accept_max_per_connection", opt_int, {&smtp_accept_max_per_connection} }, >+ { "smtp_accept_max_per_connection", opt_stringptr, {&smtp_accept_max_per_connection} }, > { "smtp_accept_max_per_host", opt_stringptr, {&smtp_accept_max_per_host} }, > { "smtp_accept_queue", opt_int, {&smtp_accept_queue} }, > { "smtp_accept_queue_per_connection", opt_int, {&smtp_accept_queue_per_connection} }, > { "smtp_accept_reserve", opt_int, {&smtp_accept_reserve} }, > { "smtp_active_hostname", opt_stringptr, {&raw_active_hostname} }, >+ { "smtp_backlog_monitor", opt_int, {&smtp_backlog_monitor} }, > { "smtp_banner", opt_stringptr, {&smtp_banner} }, > { "smtp_check_spool_space", opt_bool, {&smtp_check_spool_space} }, > { "smtp_connect_backlog", opt_int, {&smtp_connect_backlog} }, >@@ -335,15 +346,6 @@ > { "sqlite_dbfile", opt_stringptr, {&sqlite_dbfile} }, > { "sqlite_lock_timeout", opt_int, {&sqlite_lock_timeout} }, > #endif >-#ifdef EXPERIMENTAL_SRS >- { "srs_config", opt_stringptr, {&srs_config} }, >- { "srs_hashlength", opt_int, {&srs_hashlength} }, >- { "srs_hashmin", opt_int, {&srs_hashmin} }, >- { "srs_maxage", opt_int, {&srs_maxage} }, >- { "srs_secrets", opt_stringptr, {&srs_secrets} }, >- { "srs_usehash", opt_bool, {&srs_usehash} }, >- { "srs_usetimestamp", opt_bool, {&srs_usetimestamp} }, >-#endif > { "strict_acl_vars", opt_bool, {&strict_acl_vars} }, > { "strip_excess_angle_brackets", opt_bool, {&strip_excess_angle_brackets} }, > { "strip_trailing_dot", opt_bool, {&strip_trailing_dot} }, >@@ -367,6 +369,7 @@ > { "timezone", opt_stringptr, {&timezone_string} }, > { "tls_advertise_hosts", opt_stringptr, {&tls_advertise_hosts} }, > #ifndef DISABLE_TLS >+ { "tls_alpn", opt_stringptr, {&tls_alpn} }, > { "tls_certificate", opt_stringptr, {&tls_certificate} }, > { "tls_crl", opt_stringptr, {&tls_crl} }, > { "tls_dh_max_bits", opt_int, {&tls_dh_max_bits} }, >@@ -379,7 +382,7 @@ > { "tls_privatekey", opt_stringptr, {&tls_privatekey} }, > { "tls_remember_esmtp", opt_bool, {&tls_remember_esmtp} }, > { "tls_require_ciphers", opt_stringptr, {&tls_require_ciphers} }, >-# ifdef EXPERIMENTAL_TLS_RESUME >+# ifndef DISABLE_TLS_RESUME > { "tls_resumption_hosts", opt_stringptr, {&tls_resumption_hosts} }, > # endif > { "tls_try_verify_hosts", opt_stringptr, {&tls_try_verify_hosts} }, >@@ -643,7 +646,7 @@ > macro_item * > macro_create(const uschar * name, const uschar * val, BOOL command_line) > { >-macro_item * m = store_get(sizeof(macro_item), FALSE); >+macro_item * m = store_get(sizeof(macro_item), GET_UNTAINTED); > > READCONF_DEBUG fprintf(stderr, "%s: '%s' '%s'\n", __FUNCTION__, name, val); > m->next = NULL; >@@ -1073,7 +1076,7 @@ > > if (config_lines) > save_config_position(config_filename, config_lineno); >- save = store_get(sizeof(config_file_item), FALSE); >+ save = store_get(sizeof(config_file_item), GET_UNTAINTED); > save->next = config_file_stack; > config_file_stack = save; > save->file = config_file; >@@ -1422,7 +1425,7 @@ > static rewrite_rule * > readconf_one_rewrite(const uschar *p, int *existflags, BOOL isglobal) > { >-rewrite_rule *next = store_get(sizeof(rewrite_rule), FALSE); >+rewrite_rule * next = store_get(sizeof(rewrite_rule), GET_UNTAINTED); > > next->next = NULL; > next->key = string_dequote(&p); >@@ -1855,6 +1858,7 @@ > flagptr = (int *)ol3->v.value; > } > >+ /* This will trap if sptr is tainted. Not sure if that can happen */ > while ((p = string_nextinlist(CUSS &sptr, &sep, big_buffer, BIG_BUFFER_SIZE))) > { > rewrite_rule *next = readconf_one_rewrite(p, flagptr, FALSE); >@@ -1999,6 +2003,7 @@ > while (count-- > 1) > { > int sep = 0; >+ /* If p is tainted we trap. Not sure that can happen */ > (void)string_nextinlist(&p, &sep, big_buffer, BIG_BUFFER_SIZE); > if (!route_finduser(big_buffer, NULL, &uid)) > log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "user %s was not found", >@@ -2040,6 +2045,7 @@ > while (count-- > 1) > { > int sep = 0; >+ /* If p is tainted we trap. Not sure that can happen */ > (void)string_nextinlist(&p, &sep, big_buffer, BIG_BUFFER_SIZE); > if (!route_findgroup(big_buffer, &gid)) > log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "group %s was not found", >@@ -2997,7 +3003,7 @@ > BOOL forcecache = FALSE; > uschar *ss; > tree_node *t; >-namedlist_block * nb = store_get(sizeof(namedlist_block), FALSE); >+namedlist_block * nb = store_get_perm(sizeof(namedlist_block), FALSE); > > if (Ustrncmp(s, "_cache", 6) == 0) > { >@@ -3015,7 +3021,7 @@ > Uskip_whitespace(&s); > ss = s; > while (isalnum(*s) || *s == '_') s++; >-t = store_get(sizeof(tree_node) + s-ss, is_tainted(ss)); >+t = store_get(sizeof(tree_node) + s-ss, ss); > Ustrncpy(t->name, ss, s-ss); > t->name[s-ss] = 0; > Uskip_whitespace(&s); >@@ -3122,60 +3128,60 @@ > > /* Loop through the possible file names */ > >+/* Should never be a tainted list */ > while((filename = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) > { > > /* Cut out all the fancy processing unless specifically wanted */ > >- #if defined(CONFIGURE_FILE_USE_NODE) || defined(CONFIGURE_FILE_USE_EUID) >+#if defined(CONFIGURE_FILE_USE_NODE) || defined(CONFIGURE_FILE_USE_EUID) > uschar *suffix = filename + Ustrlen(filename); > > /* Try for the node-specific file if a node name exists */ > >- #ifdef CONFIGURE_FILE_USE_NODE >+# ifdef CONFIGURE_FILE_USE_NODE > struct utsname uts; > if (uname(&uts) >= 0) > { >- #ifdef CONFIGURE_FILE_USE_EUID >+# ifdef CONFIGURE_FILE_USE_EUID > sprintf(CS suffix, ".%ld.%.256s", (long int)original_euid, uts.nodename); >- config_file = Ufopen(filename, "rb"); >- if (config_file == NULL) >- #endif /* CONFIGURE_FILE_USE_EUID */ >+ if (!(config_file = Ufopen(filename, "rb"))) >+# endif /* CONFIGURE_FILE_USE_EUID */ > { > sprintf(CS suffix, ".%.256s", uts.nodename); > config_file = Ufopen(filename, "rb"); > } > } >- #endif /* CONFIGURE_FILE_USE_NODE */ >+# endif /* CONFIGURE_FILE_USE_NODE */ > > /* Otherwise, try the generic name, possibly with the euid added */ > >- #ifdef CONFIGURE_FILE_USE_EUID >- if (config_file == NULL) >+# ifdef CONFIGURE_FILE_USE_EUID >+ if (!config_file) > { > sprintf(CS suffix, ".%ld", (long int)original_euid); > config_file = Ufopen(filename, "rb"); > } >- #endif /* CONFIGURE_FILE_USE_EUID */ >+# endif /* CONFIGURE_FILE_USE_EUID */ > > /* Finally, try the unadorned name */ > >- if (config_file == NULL) >+ if (!config_file) > { > *suffix = 0; > config_file = Ufopen(filename, "rb"); > } >- #else /* if neither defined */ >+#else /* if neither defined */ > > /* This is the common case when the fancy processing is not included. */ > > config_file = Ufopen(filename, "rb"); >- #endif >+#endif > > /* If the file does not exist, continue to try any others. For any other > error, break out (and die). */ > >- if (config_file != NULL || errno != ENOENT) break; >+ if (config_file || errno != ENOENT) break; > } > > /* On success, save the name for verification; config_filename is used when >@@ -3198,39 +3204,37 @@ > config_main_directory = last_slash == filename ? US"/" : string_copyn(filename, last_slash - filename); > else > { >- /* relative configuration file name: working dir + / + basename(filename) */ >+ /* relative configuration file name: working dir + / + basename(filename) */ > >- uschar buf[PATH_MAX]; >- gstring * g; >+ uschar buf[PATH_MAX]; >+ gstring * g; > >- if (os_getcwd(buf, PATH_MAX) == NULL) >- { >- perror("exim: getcwd"); >- exit(EXIT_FAILURE); >- } >- g = string_cat(NULL, buf); >+ if (os_getcwd(buf, PATH_MAX) == NULL) >+ { >+ perror("exim: getcwd"); >+ exit(EXIT_FAILURE); >+ } >+ g = string_cat(NULL, buf); > >- /* If the dir does not end with a "/", append one */ >- if (g->s[g->ptr-1] != '/') >- g = string_catn(g, US"/", 1); >+ /* If the dir does not end with a "/", append one */ >+ if (g->s[g->ptr-1] != '/') >+ g = string_catn(g, US"/", 1); > >- /* If the config file contains a "/", extract the directory part */ >- if (last_slash) >- g = string_catn(g, filename, last_slash - filename); >+ /* If the config file contains a "/", extract the directory part */ >+ if (last_slash) >+ g = string_catn(g, filename, last_slash - filename); > >- config_main_directory = string_from_gstring(g); >+ config_main_directory = string_from_gstring(g); > } > config_directory = config_main_directory; > } > else >- { >- if (filename == NULL) >+ if (!filename) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "non-existent configuration file(s): " > "%s", config_main_filelist); > else >- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", string_open_failed(errno, >- "configuration file %s", filename)); >- } >+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", >+ string_open_failed("configuration file %s", filename)); > > /* Now, once we found and opened our configuration file, we change the directory > to a safe place. Later we change to $spool_directory. */ >@@ -3250,19 +3254,19 @@ > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to stat configuration file %s", > big_buffer); > >- if ((statbuf.st_uid != root_uid /* owner not root */ >- #ifdef CONFIGURE_OWNER >- && statbuf.st_uid != config_uid /* owner not the special one */ >- #endif >- ) || /* or */ >- (statbuf.st_gid != root_gid /* group not root & */ >- #ifdef CONFIGURE_GROUP >- && statbuf.st_gid != config_gid /* group not the special one */ >- #endif >- && (statbuf.st_mode & 020) != 0) || /* group writeable */ >- /* or */ >- ((statbuf.st_mode & 2) != 0)) /* world writeable */ >- >+ if ( statbuf.st_uid != root_uid /* owner not root */ >+#ifdef CONFIGURE_OWNER >+ && statbuf.st_uid != config_uid /* owner not the special one */ >+#endif >+ || /* or */ >+ statbuf.st_gid != root_gid /* group not root & */ >+#ifdef CONFIGURE_GROUP >+ && statbuf.st_gid != config_gid /* group not the special one */ >+#endif >+ && (statbuf.st_mode & 020) != 0 /* group writeable */ >+ || /* or */ >+ (statbuf.st_mode & 2) != 0 /* world writeable */ >+ ) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Exim configuration file %s has the " > "wrong owner, group, or mode", big_buffer); > >@@ -3275,7 +3279,7 @@ > if (statbuf.st_size > 8192) > { > rmark r = store_mark(); >- void * dummy = store_get((int)statbuf.st_size, FALSE); >+ void * dummy = store_get((int)statbuf.st_size, GET_UNTAINTED); > store_reset(r); > } > } >@@ -3309,11 +3313,11 @@ > read_named_list(&hostlist_anchor, &hostlist_count, > MAX_NAMED_LIST, t+8, US"host list", hide); > >- else if (Ustrncmp(t, US"addresslist", 11) == 0) >+ else if (Ustrncmp(t, "addresslist", 11) == 0) > read_named_list(&addresslist_anchor, &addresslist_count, > MAX_NAMED_LIST, t+11, US"address list", hide); > >- else if (Ustrncmp(t, US"localpartlist", 13) == 0) >+ else if (Ustrncmp(t, "localpartlist", 13) == 0) > read_named_list(&localpartlist_anchor, &localpartlist_count, > MAX_NAMED_LIST, t+13, US"local part list", hide); > >@@ -3332,7 +3336,7 @@ > /* If the timezone string is empty, set it to NULL, implying no TZ variable > wanted. */ > >-if (timezone_string != NULL && *timezone_string == 0) timezone_string = NULL; >+if (timezone_string && !*timezone_string) timezone_string = NULL; > > /* The max retry interval must not be greater than 24 hours. */ > >@@ -3353,10 +3357,11 @@ > canonize it. Some people like upper case letters in their host names, so we > don't force the case. */ > >-if (primary_hostname == NULL) >+if (!primary_hostname) > { >- const uschar *hostname; >+ const uschar * hostname; > struct utsname uts; >+ > if (uname(&uts) < 0) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "uname() failed to yield host name"); > hostname = US uts.nodename; >@@ -3366,33 +3371,29 @@ > int af = AF_INET; > struct hostent *hostdata; > >- #if HAVE_IPV6 >- if (!disable_ipv6 && (dns_ipv4_lookup == NULL || >- match_isinlist(hostname, CUSS &dns_ipv4_lookup, 0, NULL, NULL, >+#if HAVE_IPV6 >+ if ( !disable_ipv6 >+ && ( !dns_ipv4_lookup >+ || match_isinlist(hostname, CUSS &dns_ipv4_lookup, 0, NULL, NULL, > MCL_DOMAIN, TRUE, NULL) != OK)) > af = AF_INET6; >- #else >- af = AF_INET; >- #endif >+#endif > > for (;;) > { >- #if HAVE_IPV6 >- #if HAVE_GETIPNODEBYNAME >+#if HAVE_IPV6 >+# if HAVE_GETIPNODEBYNAME > int error_num; > hostdata = getipnodebyname(CS hostname, af, 0, &error_num); > #else > hostdata = gethostbyname2(CS hostname, af); >- #endif >- #else >+# endif >+#else > hostdata = gethostbyname(CS hostname); >- #endif >+#endif > >- if (hostdata != NULL) >- { >- hostname = US hostdata->h_name; >- break; >- } >+ if (hostdata) >+ { hostname = US hostdata->h_name; break; } > > if (af == AF_INET) break; > af = AF_INET; >@@ -3435,6 +3436,7 @@ > "\"%s\": %s", log_file_path, expand_string_message); > > ss = s; >+ /* should never be a tainted list */ > while ((sss = string_nextinlist(&ss, &sep, big_buffer, big_buffer_size))) > { > uschar *t; >@@ -3479,7 +3481,7 @@ > > /* Expand pid_file_path */ > >-if (*pid_file_path != 0) >+if (*pid_file_path) > { > if (!(s = expand_string(pid_file_path))) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand pid_file_path " >@@ -3489,7 +3491,7 @@ > > /* Set default value of process_log_path */ > >-if (!process_log_path || *process_log_path =='\0') >+if (!process_log_path || !*process_log_path) > process_log_path = string_sprintf("%s/exim-process.info", spool_directory); > > /* Compile the regex for matching a UUCP-style "From_" line in an incoming >@@ -3541,7 +3543,7 @@ > log_write(0, LOG_PANIC_DIE|LOG_CONFIG, > "error in errors_reply_to (%s): %s", errors_reply_to, errmess); > >- if (domain == 0) >+ if (!domain) > log_write(0, LOG_PANIC_DIE|LOG_CONFIG, > "errors_reply_to (%s) does not contain a domain", errors_reply_to); > } >@@ -3549,8 +3551,7 @@ > /* If smtp_accept_queue or smtp_accept_max_per_host is set, then > smtp_accept_max must also be set. */ > >-if (smtp_accept_max == 0 && >- (smtp_accept_queue > 0 || smtp_accept_max_per_host != NULL)) >+if (smtp_accept_max == 0 && (smtp_accept_queue > 0 || smtp_accept_max_per_host)) > log_write(0, LOG_PANIC_DIE|LOG_CONFIG, > "smtp_accept_max must be set if smtp_accept_queue or " > "smtp_accept_max_per_host is set"); >@@ -3571,7 +3572,7 @@ > host_number_string, expand_string_message); > n = Ustrtol(s, &end, 0); > while (isspace(*end)) end++; >- if (*end != 0) >+ if (*end) > log_write(0, LOG_PANIC_DIE|LOG_CONFIG, > "localhost_number value is not a number: %s", s); > if (n > LOCALHOST_MAX) >@@ -3648,7 +3649,7 @@ > { > int len = dd->options_len; > d->info = dd; >- d->options_block = store_get(len, FALSE); >+ d->options_block = store_get_perm(len, FALSE); > memcpy(d->options_block, dd->options_block, len); > for (int i = 0; i < *(dd->options_count); i++) > dd->options[i].type &= ~opt_set; >@@ -3664,6 +3665,16 @@ > > > >+static void >+driver_init_fini(driver_instance * d, const uschar * class) >+{ >+if (!d->driver_name) >+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG, >+ "no driver defined for %s \"%s\"", class, d->name); >+(d->info->init)(d); >+} >+ >+ > /************************************************* > * Initialize driver list * > *************************************************/ >@@ -3724,11 +3735,8 @@ > { > if (d) > { >- if (!d->driver_name) >- log_write(0, LOG_PANIC_DIE|LOG_CONFIG, >- "no driver defined for %s \"%s\"", class, d->name); > /* s is using big_buffer, so this call had better not */ >- (d->info->init)(d); >+ driver_init_fini(d, class); > d = NULL; > } > if (!macro_read_assignment(buffer)) exim_exit(EXIT_FAILURE); >@@ -3744,12 +3752,7 @@ > /* Finish off initializing the previous driver. */ > > if (d) >- { >- if (!d->driver_name) >- log_write(0, LOG_PANIC_DIE|LOG_CONFIG, >- "no driver defined for %s \"%s\"", class, d->name); >- (d->info->init)(d); >- } >+ driver_init_fini(d, class); > > /* Check that we haven't already got a driver of this name */ > >@@ -3761,11 +3764,13 @@ > /* Set up a new driver instance data block on the chain, with > its default values installed. */ > >- d = store_get(instance_size, FALSE); >+ d = store_get_perm(instance_size, FALSE); > memcpy(d, instance_default, instance_size); > *p = d; > p = &d->next; > d->name = string_copy(name); >+ d->srcfile = config_filename; >+ d->srcline = config_lineno; > > /* Clear out the "set" bits in the generic options */ > >@@ -3813,12 +3818,7 @@ > /* Run the initialization function for the final driver. */ > > if (d) >- { >- if (!d->driver_name) >- log_write(0, LOG_PANIC_DIE|LOG_CONFIG, >- "no driver defined for %s \"%s\"", class, d->name); >- (d->info->init)(d); >- } >+ driver_init_fini(d, class); > } > > >@@ -4058,7 +4058,7 @@ > const uschar *pp; > uschar *error; > >- next = store_get(sizeof(retry_config), FALSE); >+ next = store_get(sizeof(retry_config), GET_UNTAINTED); > next->next = NULL; > *chain = next; > chain = &(next->next); >@@ -4102,7 +4102,7 @@ > > while (*p) > { >- retry_rule *rule = store_get(sizeof(retry_rule), FALSE); >+ retry_rule * rule = store_get(sizeof(retry_rule), GET_UNTAINTED); > *rchain = rule; > rchain = &(rule->next); > rule->next = NULL; >@@ -4180,11 +4180,12 @@ > > for (auth_instance * bu = au->next; bu; bu = bu->next) > if (strcmpic(au->public_name, bu->public_name) == 0) >- if ((au->client && bu->client) || (au->server && bu->server)) >+ if ( au->client && bu->client >+ || au->server && bu->server) > log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "two %s authenticators " > "(%s and %s) have the same public name (%s)", >- au->client ? US"client" : US"server", au->name, bu->name, >- au->public_name); >+ au->client && bu->client ? US"client" : US"server", >+ au->name, bu->name, au->public_name); > #ifndef DISABLE_PIPE_CONNECT > nauths++; > #endif >@@ -4195,6 +4196,18 @@ > } > > >+/* For error messages, a string describing the config location associated >+with current processing. NULL if we are not in an authenticator. */ >+ >+uschar * >+authenticator_current_name(void) >+{ >+if (!authenticator_name) return NULL; >+return string_sprintf(" (authenticator %s, %s %d)", authenticator_name, driver_srcfile, driver_srcline); >+} >+ >+ >+ > > > /************************************************* >@@ -4251,7 +4264,7 @@ > if (*p != ':' || name[0] == 0) > log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "missing or malformed ACL name"); > >- node = store_get(sizeof(tree_node) + Ustrlen(name), is_tainted(name)); >+ node = store_get_perm(sizeof(tree_node) + Ustrlen(name), name); > Ustrcpy(node->name, name); > if (!tree_insertnode(&acl_anchor, node)) > log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, >@@ -4396,7 +4409,7 @@ > static config_line_item *current; > config_line_item *next; > >-next = (config_line_item*) store_get(sizeof(config_line_item), FALSE); >+next = (config_line_item*) store_get(sizeof(config_line_item), GET_UNTAINTED); > next->line = string_copy(line); > next->next = NULL; > >@@ -4413,25 +4426,28 @@ > { > const int TS = terse ? 0 : 2; > int indent = 0; >+rmark r = NULL; > >-for (config_line_item * i = config_lines; i; i = i->next) >+for (const config_line_item * i = config_lines; i; i = i->next) > { >- uschar *current; >- uschar *p; >+ uschar * current, * p; >+ >+ if (r) store_reset(r); >+ r = store_mark(); > > /* skip over to the first non-space */ >- for (current = i->line; *current && isspace(*current); ++current) >+ for (current = string_copy(i->line); *current && isspace(*current); ++current) > ; > >- if (*current == '\0') >+ if (!*current) > continue; > > /* Collapse runs of spaces. We stop this if we encounter one of the >- * following characters: "'$, as this may indicate careful formatting */ >- for (p = current; *p; ++p) >+ following characters: "'$, as this may indicate careful formatting */ >+ >+ for (p = current; *p; p++) if (isspace(*p)) > { > uschar *next; >- if (!isspace(*p)) continue; > if (*p != ' ') *p = ' '; > > for (next = p; isspace(*next); ++next) >@@ -4485,6 +4501,7 @@ > /* rest is public */ > printf("%*s%s\n", indent, "", current); > } >+if (r) store_reset(r); > } > > #endif /*!MACRO_PREDEF*/ >diff -ur exim.orig/src/receive.c exim/src/receive.c >--- exim.orig/src/receive.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/receive.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Code for receiving a message and setting up spool files. */ >@@ -44,42 +44,71 @@ > the file. (When SMTP input is occurring, different functions are used by > changing the pointer variables.) */ > >-int >-stdin_getc(unsigned lim) >-{ >-int c = getc(stdin); >+uschar stdin_buf[4096]; >+uschar * stdin_inptr = stdin_buf; >+uschar * stdin_inend = stdin_buf; > >-if (had_data_timeout) >- { >- fprintf(stderr, "exim: timed out while reading - message abandoned\n"); >- log_write(L_lost_incoming_connection, >- LOG_MAIN, "timed out while reading local message"); >- receive_bomb_out(US"data-timeout", NULL); /* Does not return */ >- } >-if (had_data_sigint) >+static BOOL >+stdin_refill(void) >+{ >+size_t rc = fread(stdin_buf, 1, sizeof(stdin_buf), stdin); >+if (rc <= 0) > { >- if (filter_test == FTEST_NONE) >+ if (had_data_timeout) >+ { >+ fprintf(stderr, "exim: timed out while reading - message abandoned\n"); >+ log_write(L_lost_incoming_connection, >+ LOG_MAIN, "timed out while reading local message"); >+ receive_bomb_out(US"data-timeout", NULL); /* Does not return */ >+ } >+ if (had_data_sigint) > { >- fprintf(stderr, "\nexim: %s received - message abandoned\n", >- had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT"); >- log_write(0, LOG_MAIN, "%s received while reading local message", >- had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT"); >+ if (filter_test == FTEST_NONE) >+ { >+ fprintf(stderr, "\nexim: %s received - message abandoned\n", >+ had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT"); >+ log_write(0, LOG_MAIN, "%s received while reading local message", >+ had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT"); >+ } >+ receive_bomb_out(US"signal-exit", NULL); /* Does not return */ > } >- receive_bomb_out(US"signal-exit", NULL); /* Does not return */ >+ return FALSE; > } >-return c; >+stdin_inend = stdin_buf + rc; >+stdin_inptr = stdin_buf; >+return TRUE; >+} >+ >+int >+stdin_getc(unsigned lim) >+{ >+if (stdin_inptr >= stdin_inend) >+ if (!stdin_refill()) >+ return EOF; >+return *stdin_inptr++; >+} >+ >+ >+BOOL >+stdin_hasc(void) >+{ >+return stdin_inptr < stdin_inend; > } > > int > stdin_ungetc(int c) > { >-return ungetc(c, stdin); >+if (stdin_inptr <= stdin_buf) >+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in stdin_ungetc"); >+ >+*--stdin_inptr = c; >+return c; > } > > int > stdin_feof(void) > { >-return feof(stdin); >+return stdin_hasc() ? FALSE : feof(stdin); > } > > int >@@ -176,6 +205,7 @@ > empty item in a list. */ > > if (*p == 0) p = US":"; >+ /* should never be a tainted list */ > while ((path = string_nextinlist(&p, &sep, buffer, sizeof(buffer)))) > if (Ustrcmp(path, "syslog") != 0) > break; >@@ -497,8 +527,8 @@ > } > > recipients_list_max = recipients_list_max ? 2*recipients_list_max : 50; >- recipients_list = store_get(recipients_list_max * sizeof(recipient_item), FALSE); >- if (oldlist != NULL) >+ recipients_list = store_get(recipients_list_max * sizeof(recipient_item), GET_UNTAINTED); >+ if (oldlist) > memcpy(recipients_list, oldlist, oldmax * sizeof(recipient_item)); > } > >@@ -587,19 +617,15 @@ > static void > log_close_chk(void) > { >-if (!receive_timeout) >+if (!receive_timeout && !receive_hasc()) > { > struct timeval t; > timesince(&t, &received_time); > if (t.tv_sec > 30*60) > mainlog_close(); > else >- { >- fd_set r; >- FD_ZERO(&r); FD_SET(0, &r); >- t.tv_sec = 30*60 - t.tv_sec; t.tv_usec = 0; >- if (select(1, &r, NULL, NULL, &t) == 0) mainlog_close(); >- } >+ if (poll_one_fd(0, POLLIN, (30*60 - t.tv_sec) * 1000) == 0) >+ mainlog_close(); > } > } > >@@ -653,11 +679,6 @@ > { > int last_ch = '\n'; > >-/*XXX we do a gettimeofday before checking for every received char, >-which is hardly clever. The function-indirection doesn't help, but >-an additional function to check for nonempty read buffer would help. >-See stdin_getc() / smtp_getc() / tls_getc() / bdat_getc(). */ >- > for ( ; > log_close_chk(), (ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF; > last_ch = ch) >@@ -946,7 +967,7 @@ > */ > > static int >-read_message_bdat_smtp(FILE *fout) >+read_message_bdat_smtp(FILE * fout) > { > int linelength = 0, ch; > enum CH_STATE ch_state = LF_SEEN; >@@ -1052,7 +1073,7 @@ > } > > static int >-read_message_bdat_smtp_wire(FILE *fout) >+read_message_bdat_smtp_wire(FILE * fout) > { > int ch; > >@@ -1235,9 +1256,8 @@ > const uschar * list = acl_removed_headers; > int sep = ':'; /* This is specified as a colon-separated list */ > uschar *s; >- uschar buffer[128]; > >- while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))) >+ while ((s = string_nextinlist(&list, &sep, NULL, 0))) > if (header_testname(h, s, Ustrlen(s), FALSE)) > { > h->type = htype_old; >@@ -1523,11 +1543,10 @@ > void > received_header_gen(void) > { >-uschar *received; >-uschar *timestamp; >-header_line *received_header= header_list; >+uschar * received; >+uschar * timestamp = expand_string(US"${tod_full}"); >+header_line * received_header= header_list; > >-timestamp = expand_string(US"${tod_full}"); > if (recipients_count == 1) received_for = recipients_list[0].address; > received = expand_string(received_header_text); > received_for = NULL; >@@ -1546,14 +1565,14 @@ > the result of the expansion is an empty string, we leave the header marked as > "old" so as to refrain from adding a Received header. */ > >-if (received[0] == 0) >+if (!received[0]) > { > received_header->text = string_sprintf("Received: ; %s\n", timestamp); > received_header->type = htype_old; > } > else > { >- received_header->text = string_sprintf("%s; %s\n", received, timestamp); >+ received_header->text = string_sprintf("%s;\n\t%s\n", received, timestamp); > received_header->type = htype_received; > } > >@@ -1664,10 +1683,9 @@ > int error_rc = error_handling == ERRORS_SENDER > ? errors_sender_rc : EXIT_FAILURE; > int header_size = 256; >-int start, end, domain; >-int id_resolution = 0; > int had_zero = 0; > int prevlines_length = 0; >+const int id_resolution = BASE_62 == 62 ? 5000 : 10000; > > int ptr = 0; > >@@ -1721,6 +1739,10 @@ > uschar *timestamp; > int tslen; > >+/* Time of creation of message_id */ >+ >+static struct timeval message_id_tv = { 0, 0 }; >+ > > /* Release any open files that might have been cached while preparing to > accept the message - e.g. by verifying addresses - because reading a message >@@ -1738,17 +1760,18 @@ > header. Temporarily mark it as "old", i.e. not to be used. We keep header_last > pointing to the end of the chain to make adding headers simple. */ > >-received_header = header_list = header_last = store_get(sizeof(header_line), FALSE); >+received_header = header_list = header_last = store_get(sizeof(header_line), GET_UNTAINTED); > header_list->next = NULL; > header_list->type = htype_old; > header_list->text = NULL; > header_list->slen = 0; > >-/* Control block for the next header to be read. */ >+/* Control block for the next header to be read. >+The data comes from the message, so is tainted. */ > > reset_point = store_mark(); >-next = store_get(sizeof(header_line), FALSE); /* not tainted */ >-next->text = store_get(header_size, TRUE); /* tainted */ >+next = store_get(sizeof(header_line), GET_UNTAINTED); >+next->text = store_get(header_size, GET_TAINTED); > > /* Initialize message id to be null (indicating no message read), and the > header names list to be the normal list. Indicate there is no data file open >@@ -1787,17 +1810,40 @@ > if (sender_host_address) dmarc_init(); /* initialize libopendmarc */ > #endif > >+/* In SMTP sessions we may receive several messages in one connection. Before >+each subsequent one, we wait for the clock to tick at the level of message-id >+granularity. >+This is so that the combination of time+pid is unique, even on systems where the >+pid can be re-used within our time interval. We can't shorten the interval >+without re-designing the message-id. See comments above where the message id is >+created. This is Something For The Future. >+Do this wait any time we have previously created a message-id, even if we >+rejected the message. This gives unique IDs for logging done by ACLs. >+The initial timestamp must have been obtained via exim_gettime() to avoid >+issues on Linux with suspend/resume. */ >+ >+if (message_id_tv.tv_sec) >+ { >+ message_id_tv.tv_usec = (message_id_tv.tv_usec/id_resolution) * id_resolution; >+ exim_wait_tick(&message_id_tv, id_resolution); >+ } >+ > /* Remember the time of reception. Exim uses time+pid for uniqueness of message > ids, and fractions of a second are required. See the comments that precede the >-message id creation below. */ >+message id creation below. >+We use a routine that if possible uses a monotonic clock, and can be used again >+after reception for the tick-wait even under the Linux non-Posix behaviour. */ > >-exim_gettime(&message_id_tv); >+else >+ exim_gettime(&message_id_tv); > > /* For other uses of the received time we can operate with granularity of one > second, and for that we use the global variable received_time. This is for >-things like ultimate message timeouts. */ >+things like ultimate message timeouts. >+For this we do not care about the Linux suspend/resume problem, so rather than >+use exim_gettime() everywhere we use a plain gettimeofday() here. */ > >-received_time = message_id_tv; >+gettimeofday(&received_time, NULL); > > /* If SMTP input, set the special handler for timeouts. The alarm() calls > happen in the smtp_getc() function when it refills its buffer. */ >@@ -1843,12 +1889,15 @@ > /* If we hit EOF on a SMTP connection, it's an error, since incoming > SMTP must have a correct "." terminator. */ > >- if (ch == EOF && smtp_input /* && !smtp_batched_input */) >- { >- smtp_reply = handle_lost_connection(US" (header)"); >- smtp_yield = FALSE; >- goto TIDYUP; /* Skip to end of function */ >- } >+ if (smtp_input /* && !smtp_batched_input */) >+ if (ch == EOF) >+ { >+ smtp_reply = handle_lost_connection(US" (header)"); >+ smtp_yield = FALSE; >+ goto TIDYUP; /* Skip to end of function */ >+ } >+ else if (ch == ERR) >+ goto TIDYUP; > > /* See if we are at the current header's size limit - there must be at least > four bytes left. This allows for the new character plus a zero, plus two for >@@ -1872,10 +1921,8 @@ > goto OVERSIZE; > header_size *= 2; > >- /* The data came from the message, so is tainted. */ >- >- if (!store_extend(next->text, TRUE, oldsize, header_size)) >- next->text = store_newblock(next->text, TRUE, header_size, ptr); >+ if (!store_extend(next->text, oldsize, header_size)) >+ next->text = store_newblock(next->text, header_size, ptr); > } > > /* Cope with receiving a binary zero. There is dispute about whether >@@ -1890,7 +1937,7 @@ > those from data files use just LF. Treat LF in local SMTP input as a > terminator too. Treat EOF as a line terminator always. */ > >- if (ch == EOF) goto EOL; >+ if (ch < 0) goto EOL; > > /* FUDGE: There are sites out there that don't send CRs before their LFs, and > other MTAs accept this. We are therefore forced into this "liberalisation" >@@ -1915,7 +1962,7 @@ > prevent further reading), and break out of the loop, having freed the > empty header, and set next = NULL to indicate no data line. */ > >- if (ptr == 0 && ch == '.' && f.dot_ends) >+ if (f.dot_ends && ptr == 0 && ch == '.') > { > ch = (receive_getc)(GETC_BUFFER_UNLIMITED); > if (ch == '\r') >@@ -1923,7 +1970,7 @@ > ch = (receive_getc)(GETC_BUFFER_UNLIMITED); > if (ch != '\n') > { >- receive_ungetc(ch); >+ if (ch >= 0) receive_ungetc(ch); > ch = '\r'; /* Revert to CR */ > } > } >@@ -1961,7 +2008,7 @@ > /* Otherwise, put back the character after CR, and turn the bare CR > into LF SP. */ > >- ch = (receive_ungetc)(ch); >+ if (ch >= 0) (receive_ungetc)(ch); > next->text[ptr++] = '\n'; > message_size++; > ch = ' '; >@@ -2045,7 +2092,7 @@ > whitespace character. If it is, we have a continuation of this header line. > There is always space for at least one character at this point. */ > >- if (ch != EOF) >+ if (ch >= 0) > { > int nextch = (receive_getc)(GETC_BUFFER_UNLIMITED); > if (nextch == ' ' || nextch == '\t') >@@ -2055,8 +2102,9 @@ > goto OVERSIZE; > continue; /* Iterate the loop */ > } >- else if (nextch != EOF) (receive_ungetc)(nextch); /* For next time */ >- else ch = EOF; /* Cause main loop to exit at end */ >+ else if (nextch >= 0) /* not EOF, ERR etc */ >+ (receive_ungetc)(nextch); /* For next time */ >+ else ch = nextch; /* Cause main loop to exit at end */ > } > > /* We have got to the real line end. Terminate the string and release store >@@ -2158,7 +2206,7 @@ > > else > { >- uschar *p = next->text; >+ uschar * p = next->text; > > /* If not a valid header line, break from the header reading loop, leaving > next != NULL, indicating that it holds the first line of the body. */ >@@ -2256,16 +2304,22 @@ > } > > /* The line has been handled. If we have hit EOF, break out of the loop, >- indicating no pending data line. */ >+ indicating no pending data line and no more data for the message */ > >- if (ch == EOF) { next = NULL; break; } >+ if (ch < 0) >+ { >+ next = NULL; >+ if (ch == EOF) message_ended = END_DOT; >+ else if (ch == ERR) message_ended = END_PROTOCOL; >+ break; >+ } > > /* Set up for the next header */ > > reset_point = store_mark(); > header_size = 256; >- next = store_get(sizeof(header_line), FALSE); >- next->text = store_get(header_size, TRUE); >+ next = store_get(sizeof(header_line), GET_UNTAINTED); >+ next->text = store_get(header_size, GET_TAINTED); > ptr = 0; > had_zero = 0; > prevlines_length = 0; >@@ -2288,14 +2342,21 @@ > /* End of file on any SMTP connection is an error. If an incoming SMTP call > is dropped immediately after valid headers, the next thing we will see is EOF. > We must test for this specially, as further down the reading of the data is >-skipped if already at EOF. */ >+skipped if already at EOF. >+In CHUNKING mode, a protocol error makes us give up on the message. */ > >-if (smtp_input && (receive_feof)()) >- { >- smtp_reply = handle_lost_connection(US" (after header)"); >- smtp_yield = FALSE; >- goto TIDYUP; /* Skip to end of function */ >- } >+if (smtp_input) >+ if ((receive_feof)()) >+ { >+ smtp_reply = handle_lost_connection(US" (after header)"); >+ smtp_yield = FALSE; >+ goto TIDYUP; /* Skip to end of function */ >+ } >+ else if (message_ended == END_PROTOCOL) >+ { >+ smtp_reply = US""; /* no reply needed */ >+ goto TIDYUP; >+ } > > /* If this is a filter test run and no headers were read, output a warning > in case there is a mistake in the test message. */ >@@ -2549,7 +2610,7 @@ > white space that follows the newline must not be removed - it is part > of the header. */ > >- pp = recipient = store_get(ss - s + 1, is_tainted(s)); >+ pp = recipient = store_get(ss - s + 1, s); > for (uschar * p = s; p < ss; p++) if (*p != '\n') *pp++ = *p; > *pp = 0; > >@@ -2581,7 +2642,7 @@ > if (!recipient && Ustrcmp(errmess, "empty address") != 0) > { > int len = Ustrlen(s); >- error_block *b = store_get(sizeof(error_block), FALSE); >+ error_block * b = store_get(sizeof(error_block), GET_UNTAINTED); > while (len > 0 && isspace(s[len-1])) len--; > b->next = NULL; > b->text1 = string_printing(string_copyn(s, len)); >@@ -2678,28 +2739,20 @@ > Ustrncpy(message_id + 7, string_base62((long int)getpid()), 6); > > /* Deal with the case where the host number is set. The value of the number was >-checked when it was read, to ensure it isn't too big. The timing granularity is >-left in id_resolution so that an appropriate wait can be done after receiving >-the message, if necessary (we hope it won't be). */ >+checked when it was read, to ensure it isn't too big. */ > > if (host_number_string) >- { >- id_resolution = BASE_62 == 62 ? 5000 : 10000; > sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s", > string_base62((long int)( > host_number * (1000000/id_resolution) + > message_id_tv.tv_usec/id_resolution)) + 4); >- } > > /* Host number not set: final field is just the fractional time at an > appropriate resolution. */ > > else >- { >- id_resolution = BASE_62 == 62 ? 500 : 1000; > sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s", > string_base62((long int)(message_id_tv.tv_usec/id_resolution)) + 4); >- } > > /* Add the current message id onto the current process info string if > it will fit. */ >@@ -2788,7 +2841,7 @@ > > if (LOGGING(received_recipients)) > { >- raw_recipients = store_get(recipients_count * sizeof(uschar *), FALSE); >+ raw_recipients = store_get(recipients_count * sizeof(uschar *), GET_UNTAINTED); > for (int i = 0; i < recipients_count; i++) > raw_recipients[i] = string_copy(recipients_list[i].address); > raw_recipients_count = recipients_count; >@@ -2798,10 +2851,13 @@ > recipients will get here only if the conditions were right (allow_unqualified_ > recipient is TRUE). */ > >+DEBUG(D_rewrite) >+ { debug_printf_indent("qualify & rewrite recipients list\n"); acl_level++; } > for (int i = 0; i < recipients_count; i++) > recipients_list[i].address = /* deconst ok as src was not cont */ > US rewrite_address(recipients_list[i].address, TRUE, TRUE, > global_rewrite_rules, rewrite_existflags); >+DEBUG(D_rewrite) acl_level--; > > /* If there is no From: header, generate one for local (without > suppress_local_fixups) or submission_mode messages. If there is no sender >@@ -2815,7 +2871,7 @@ > if ( !from_header > && ((!sender_host_address && !f.suppress_local_fixups) || f.submission_mode)) > { >- uschar *oname = US""; >+ const uschar * oname = US""; > > /* Use the originator_name if this is a locally submitted message and the > caller is not trusted. For trusted callers, use it only if -F was used to >@@ -2973,6 +3029,8 @@ > /* If there are any rewriting rules, apply them to the sender address, unless > it has already been rewritten as part of verification for SMTP input. */ > >+DEBUG(D_rewrite) >+ { debug_printf("global rewrite rules\n"); acl_level++; } > if (global_rewrite_rules && !sender_address_unrewritten && *sender_address) > { > /* deconst ok as src was not const */ >@@ -2981,6 +3039,7 @@ > DEBUG(D_receive|D_rewrite) > debug_printf("rewritten sender = %s\n", sender_address); > } >+DEBUG(D_rewrite) acl_level--; > > > /* The headers must be run through rewrite_header(), because it ensures that >@@ -2997,12 +3056,13 @@ > documented as happening *after* recipient addresses are taken from the headers > by the -t command line option. An added Sender: gets rewritten here. */ > >-for (header_line * h = header_list->next; h; h = h->next) >- { >- header_line *newh = rewrite_header(h, NULL, NULL, global_rewrite_rules, >- rewrite_existflags, TRUE); >- if (newh) h = newh; >- } >+DEBUG(D_rewrite) >+ { debug_printf("rewrite headers\n"); acl_level++; } >+for (header_line * h = header_list->next, * newh; h; h = h->next) >+ if ((newh = rewrite_header(h, NULL, NULL, global_rewrite_rules, >+ rewrite_existflags, TRUE))) >+ h = newh; >+DEBUG(D_rewrite) acl_level--; > > > /* An RFC 822 (sic) message is not legal unless it has at least one of "to", >@@ -3282,7 +3342,7 @@ > /* No I/O errors were encountered while writing the data file. */ > > DEBUG(D_receive) debug_printf("Data file written for message %s\n", message_id); >-if (LOGGING(receive_time)) timesince(&received_time_taken, &received_time); >+gettimeofday(&received_time_complete, NULL); > > > /* If there were any bad addresses extracted by -t, or there were no recipients >@@ -4004,7 +4064,7 @@ > if (LOGGING(tls_cipher) && tls_in.cipher) > { > g = string_append(g, 2, US" X=", tls_in.cipher); >-# ifdef EXPERIMENTAL_TLS_RESUME >+# ifndef DISABLE_TLS_RESUME > if (LOGGING(tls_resumption) && tls_in.resumption & RESUME_USED) > g = string_catn(g, US"*", 1); > # endif >@@ -4060,7 +4120,11 @@ > #endif > > if (LOGGING(receive_time)) >- g = string_append(g, 2, US" RT=", string_timediff(&received_time_taken)); >+ { >+ struct timeval diff = received_time_complete; >+ timediff(&diff, &received_time); >+ g = string_append(g, 2, US" RT=", string_timediff(&diff)); >+ } > > if (*queue_name) > g = string_append(g, 2, US" Q=", queue_name); >@@ -4077,6 +4141,8 @@ > uschar * old_id; > BOOL save_allow_domain_literals = allow_domain_literals; > allow_domain_literals = TRUE; >+ int start, end, domain; >+ > old_id = parse_extract_address(Ustrchr(msgid_header->text, ':') + 1, > &errmsg, &start, &end, &domain, FALSE); > allow_domain_literals = save_allow_domain_literals; >@@ -4169,7 +4235,7 @@ > /* Before sending an SMTP response in a TCP/IP session, we check to see if the > connection has gone away. This can only be done if there is no unconsumed input > waiting in the local input buffer. We can test for this by calling >-receive_smtp_buffered(). RFC 2920 (pipelining) explicitly allows for additional >+receive_hasc(). RFC 2920 (pipelining) explicitly allows for additional > input to be sent following the final dot, so the presence of following input is > not an error. > >@@ -4184,20 +4250,14 @@ > connection will vanish between the time of this test and the sending of the > response, but the chance of this happening should be small. */ > >-if (smtp_input && sender_host_address && !f.sender_host_notsocket && >- !receive_smtp_buffered()) >+if ( smtp_input && sender_host_address && !f.sender_host_notsocket >+ && !receive_hasc()) > { >- struct timeval tv; >- fd_set select_check; >- FD_ZERO(&select_check); >- FD_SET(fileno(smtp_in), &select_check); >- tv.tv_sec = 0; >- tv.tv_usec = 0; >- >- if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0) >+ if (poll_one_fd(fileno(smtp_in), POLLIN, 0) != 0) > { > int c = (receive_getc)(GETC_BUFFER_UNLIMITED); >- if (c != EOF) (receive_ungetc)(c); else >+ if (c != EOF) (receive_ungetc)(c); >+ else > { > smtp_notquit_exit(US"connection-lost", NULL, NULL); > smtp_reply = US""; /* No attempt to send a response */ >@@ -4318,23 +4378,6 @@ > > > TIDYUP: >-/* In SMTP sessions we may receive several messages in one connection. After >-each one, we wait for the clock to tick at the level of message-id granularity. >-This is so that the combination of time+pid is unique, even on systems where the >-pid can be re-used within our time interval. We can't shorten the interval >-without re-designing the message-id. See comments above where the message id is >-created. This is Something For The Future. >-Do this wait any time we have created a message-id, even if we rejected the >-message. This gives unique IDs for logging done by ACLs. */ >- >-if (id_resolution != 0) >- { >- message_id_tv.tv_usec = (message_id_tv.tv_usec/id_resolution) * id_resolution; >- exim_wait_tick(&message_id_tv, id_resolution); >- id_resolution = 0; >- } >- >- > process_info[process_info_len] = 0; /* Remove message id */ > if (spool_data_file && cutthrough_done == NOT_TRIED) > { >@@ -4385,12 +4428,17 @@ > > else if (chunking_state > CHUNKING_OFFERED) > { >- smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n", FALSE, >+ /* If there is more input waiting, no need to flush (probably the client >+ pipelined QUIT after data). We check only the in-process buffer, not >+ the socket. */ >+ >+ smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n", >+ receive_hasc(), > chunking_datasize, message_size+message_linecount, message_id); > chunking_state = CHUNKING_OFFERED; > } > else >- smtp_printf("250 OK id=%s\r\n", FALSE, message_id); >+ smtp_printf("250 OK id=%s\r\n", receive_hasc(), message_id); > > if (host_checking) > fprintf(stdout, >diff -ur exim.orig/src/regex.c exim/src/regex.c >--- exim.orig/src/regex.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/regex.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,9 +2,10 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-2015 >+/* >+ * Copyright (c) The Exim Maintainers 2016 - 2022 >+ * Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-2015 > * License: GPL >- * Copyright (c) The Exim Maintainers 2016 - 2018 > */ > > /* Code for matching regular expressions against headers and body. >@@ -17,7 +18,7 @@ > > /* Structure to hold a list of Regular expressions */ > typedef struct pcre_list { >- pcre *re; >+ pcre2_code *re; > uschar *pcre_text; > struct pcre_list *next; > } pcre_list; >@@ -32,8 +33,6 @@ > { > int sep = 0; > uschar *regex_string; >-const char *pcre_error; >-int pcre_erroffset; > pcre_list *re_list_head = NULL; > pcre_list *ri; > >@@ -41,19 +40,23 @@ > while ((regex_string = string_nextinlist(&list, &sep, NULL, 0))) > if (strcmpic(regex_string, US"false") != 0 && Ustrcmp(regex_string, "0") != 0) > { >- pcre *re; >+ pcre2_code * re; >+ int err; >+ PCRE2_SIZE pcre_erroffset; > > /* compile our regular expression */ >- if (!(re = pcre_compile( CS regex_string, >- 0, &pcre_error, &pcre_erroffset, NULL ))) >+ if (!(re = pcre2_compile( (PCRE2_SPTR) regex_string, PCRE2_ZERO_TERMINATED, >+ 0, &err, &pcre_erroffset, pcre_cmp_ctx))) > { >+ uschar errbuf[128]; >+ pcre2_get_error_message(err, errbuf, sizeof(errbuf)); > log_write(0, LOG_MAIN, >- "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.", >- regex_string, pcre_error, pcre_erroffset); >+ "regex acl condition warning - error in regex '%s': %s at offset %ld, skipped.", >+ regex_string, errbuf, (long)pcre_erroffset); > continue; > } > >- ri = store_get(sizeof(pcre_list), FALSE); >+ ri = store_get(sizeof(pcre_list), GET_UNTAINTED); > ri->re = re; > ri->pcre_text = regex_string; > ri->next = re_list_head; >@@ -65,25 +68,31 @@ > static int > matcher(pcre_list * re_list_head, uschar * linebuffer, int len) > { >-for(pcre_list * ri = re_list_head; ri; ri = ri->next) >+pcre2_match_data * md = pcre2_match_data_create(REGEX_VARS + 1, pcre_gen_ctx); >+ >+for (pcre_list * ri = re_list_head; ri; ri = ri->next) > { >- int ovec[3*(REGEX_VARS+1)]; > int n; > > /* try matcher on the line */ >- if ((n = pcre_exec(ri->re, NULL, CS linebuffer, len, 0, 0, ovec, nelem(ovec))) > 0) >+ if ((n = pcre2_match(ri->re, (PCRE2_SPTR)linebuffer, len, 0, 0, md, pcre_mtc_ctx)) > 0) > { > Ustrncpy(regex_match_string_buffer, ri->pcre_text, > sizeof(regex_match_string_buffer)-1); > regex_match_string = regex_match_string_buffer; > > for (int nn = 1; nn < n; nn++) >- regex_vars[nn-1] = >- string_copyn(linebuffer + ovec[nn*2], ovec[nn*2+1] - ovec[nn*2]); >+ { >+ PCRE2_UCHAR * cstr; >+ PCRE2_SIZE cslen; >+ pcre2_substring_get_bynumber(md, nn, &cstr, &cslen); >+ regex_vars[nn-1] = CUS cstr; >+ } > > return OK; > } > } >+pcre2_match_data_free(md); > return FAIL; > } > >@@ -125,7 +134,7 @@ > return FAIL; /* no regexes -> nothing to do */ > > /* match each line against all regexes */ >-linebuffer = store_get(32767, TRUE); /* tainted */ >+linebuffer = store_get(32767, GET_TAINTED); > while (fgets(CS linebuffer, 32767, mbox_file)) > { > if ( mime_stream && mime_current_boundary /* check boundary */ >@@ -196,7 +205,7 @@ > } > > /* get 32k memory, tainted */ >-mime_subject = store_get(32767, TRUE); >+mime_subject = store_get(32767, GET_TAINTED); > > mime_subject_len = fread(mime_subject, 1, 32766, f); > >diff -ur exim.orig/src/retry.c exim/src/retry.c >--- exim.orig/src/retry.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/retry.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions concerned with retrying unsuccessful deliveries. */ >@@ -127,11 +127,10 @@ > { > BOOL yield = FALSE; > time_t now = time(NULL); >-uschar *host_key, *message_key; >-open_db dbblock; >-open_db *dbm_file; >-tree_node *node; >-dbdata_retry *host_retry_record, *message_retry_record; >+uschar * host_key, * message_key; >+open_db dbblock, * dbm_file; >+tree_node * node; >+dbdata_retry * host_retry_record, * message_retry_record; > > *retry_host_key = *retry_message_key = NULL; > >@@ -145,9 +144,9 @@ > /* Generate the host key for the unusable tree and the retry database. Ensure > host names are lower cased (that's what %S does). */ > >-host_key = include_ip_address? >- string_sprintf("T:%S:%s%s", host->name, host->address, portstring) : >- string_sprintf("T:%S%s", host->name, portstring); >+host_key = include_ip_address >+ ? string_sprintf("T:%S:%s%s", host->name, host->address, portstring) >+ : string_sprintf("T:%S%s", host->name, portstring); > > /* Generate the message-specific key */ > >@@ -292,7 +291,7 @@ > void > retry_add_item(address_item *addr, uschar *key, int flags) > { >-retry_item *rti = store_get(sizeof(retry_item), FALSE); >+retry_item * rti = store_get(sizeof(retry_item), GET_UNTAINTED); > host_item * host = addr->host_used; > > rti->next = addr->retries; >@@ -655,7 +654,7 @@ > ? US string_printing(rti->message) > : US"unknown error"; > message_length = Ustrlen(message); >- if (message_length > 150) message_length = 150; >+ if (message_length > EXIM_DB_RLIMIT) message_length = EXIM_DB_RLIMIT; > > /* Read a retry record from the database or construct a new one. > Ignore an old one if it is too old since it was last updated. */ >@@ -669,7 +668,7 @@ > if (!retry_record) > { > retry_record = store_get(sizeof(dbdata_retry) + message_length, >- is_tainted(message)); >+ message); > message_space = message_length; > retry_record->first_failed = now; > retry_record->last_try = now; >@@ -815,7 +814,7 @@ > if (message_length > message_space) > { > dbdata_retry * newr = >- store_get(sizeof(dbdata_retry) + message_length, is_tainted(message)); >+ store_get(sizeof(dbdata_retry) + message_length, message); > memcpy(newr, retry_record, sizeof(dbdata_retry)); > retry_record = newr; > } >diff -ur exim.orig/src/rewrite.c exim/src/rewrite.c >--- exim.orig/src/rewrite.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/rewrite.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -109,11 +110,11 @@ > > if (whole) *whole = FALSE; > >-/* Scan the rewriting rules */ >+/* Scan the rewriting rules, ignoring any without matching flag */ > > for (rewrite_rule * rule = rewrite_rules; > rule && !done; >- rule_number++, rule = rule->next) >+ rule_number++, rule = rule->next) if (rule->flags & flag) > { > int start, end, pdomain; > int count = 0; >@@ -122,10 +123,6 @@ > uschar *error, *new; > const uschar * newparsed; > >- /* Ensure that the flag matches the flags in the rule. */ >- >- if (!(rule->flags & flag)) continue; >- > /* Come back here for a repeat after a successful rewrite. We do this > only so many times. */ > >@@ -453,10 +450,11 @@ > header_line *newh = NULL; > rmark function_reset_point = store_mark(); > uschar *s = Ustrchr(h->text, ':') + 1; >+ > while (isspace(*s)) s++; > > DEBUG(D_rewrite) >- debug_printf("rewrite_one_header: type=%c:\n %s", h->type, h->text); >+ debug_printf_indent("rewrite_one_header: type=%c:\n %s", h->type, h->text); > > f.parse_allow_group = TRUE; /* Allow group syntax */ > >@@ -483,11 +481,10 @@ > the next address, saving the start of the old one. */ > > *ss = 0; >- recipient = parse_extract_address(s,&errmess,&start,&end,&domain,FALSE); >- >+ recipient = parse_extract_address(s, &errmess, &start, &end, &domain, FALSE); > *ss = terminator; > sprev = s; >- s = ss + (terminator? 1:0); >+ s = ss + (terminator ? 1 : 0); > while (isspace(*s)) s++; > > /* There isn't much we can do for syntactic disasters at this stage. >@@ -496,30 +493,17 @@ > empty address, overlong addres. Sometimes the result matters, sometimes not. > It seems this function is called for *any* header we see. */ > >- > if (!recipient) > { >-#if 0 >- /* FIXME: >- This was(!) an attempt tho handle empty rewrits, but seemingly it >- needs more effort to decide if the returned empty address matters. >- Now this will now break test 471 again. >- >- 471 fails now because it uses an overlong address, for wich parse_extract_address() >- returns an empty address (which was not expected). >- >- Checking the output and exit if rewrite_rules or routed_old are present >- isn't a good idea either: It's enough to have *any* rewrite rule >- in the configuration plus "To: undisclosed recpients:;" to exit(), which >- is not what we want. >- */ >+ /* Handle unparesable addresses in the header. Slightly ugly because a >+ null output from the extract can also result from a header without an >+ address, "To: undisclosed recpients:;" being the classic case. */ > >- if (rewrite_rules || routed_old) >+ if ((rewrite_rules || routed_old) && Ustrcmp(errmess, "empty address") != 0) > { > log_write(0, LOG_MAIN, "rewrite: %s", errmess); > exim_exit(EXIT_FAILURE); > } >-#endif > loop_reset_point = store_reset(loop_reset_point); > continue; > } >@@ -534,7 +518,7 @@ > as abc@xyz, which the DNS lookup turns into abc@xyz.foo.com). However, if no > change is made here, don't bother carrying on. */ > >- if (routed_old != NULL) >+ if (routed_old) > { > if (domain <= 0 || strcmpic(recipient+domain, routed_old) != 0) continue; > recipient[domain-1] = 0; >@@ -579,7 +563,7 @@ > "whole" flag set, adjust the pointers so that the whole address gets > replaced, except possibly a final \n. */ > >- if ((existflags & flag) != 0) >+ if (existflags & flag) > { > BOOL whole; > /* deconst ok as recipient was notconst */ >@@ -619,7 +603,7 @@ > int oldlen = end - start; > > header_line * prev = newh ? newh : h; >- uschar * newt = store_get_perm(prev->slen - oldlen + newlen + 4, TRUE); >+ uschar * newt = store_get_perm(prev->slen - oldlen + newlen + 4, GET_TAINTED); > uschar * newtstart = newt; > > int type = prev->type; >@@ -683,7 +667,7 @@ > > store_reset(function_reset_point); > function_reset_point = store_mark(); >- newh = store_get(sizeof(header_line), FALSE); >+ newh = store_get(sizeof(header_line), GET_UNTAINTED); > newh->type = type; > newh->slen = slen; > newh->text = string_copyn(newtstart, slen); >@@ -691,7 +675,7 @@ > /* Set up for scanning the rest of the header */ > > s = newh->text + remlen; >- DEBUG(D_rewrite) debug_printf("remainder: %s", (*s == 0)? US"\n" : s); >+ DEBUG(D_rewrite) debug_printf("remainder: %s", *s ? s : US"\n"); > } > } > >@@ -701,10 +685,10 @@ > /* If a rewrite happened and "replace" is true, put the new header into the > chain following the old one, and mark the old one as replaced. */ > >-if (newh != NULL && replace) >+if (newh && replace) > { > newh->next = h->next; >- if (newh->next == NULL) header_last = newh; >+ if (!newh->next) header_last = newh; > h->type = htype_old; > h->next = newh; > } >diff -ur exim.orig/src/rfc2047.c exim/src/rfc2047.c >--- exim.orig/src/rfc2047.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/rfc2047.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* This file contains a function for decoding message header lines that may >@@ -47,7 +47,7 @@ > int len = 0; > uschar *ptr; > >-ptr = *ptrptr = store_get(Ustrlen(string) + 1, is_tainted(string)); /* No longer than this */ >+ptr = *ptrptr = store_get(Ustrlen(string) + 1, string); /* No longer than this */ > > while (*string != 0) > { >@@ -186,8 +186,8 @@ > */ > > uschar * >-rfc2047_decode2(uschar *string, BOOL lencheck, uschar *target, int zeroval, >- int *lenptr, int *sizeptr, uschar **error) >+rfc2047_decode2(uschar *string, BOOL lencheck, const uschar *target, >+ int zeroval, int *lenptr, int *sizeptr, uschar **error) > { > int size = Ustrlen(string); > size_t dlen; >@@ -209,7 +209,7 @@ > translated into a multibyte code such as UTF-8. That's why we use the dynamic > string building code. */ > >-yield = store_get(sizeof(gstring) + ++size, is_tainted(string)); >+yield = store_get(sizeof(gstring) + ++size, string); > yield->size = size; > yield->ptr = 0; > yield->s = US(yield + 1); >@@ -336,7 +336,7 @@ > argument. */ > > uschar * >-rfc2047_decode(uschar *string, BOOL lencheck, uschar *target, int zeroval, >+rfc2047_decode(uschar *string, BOOL lencheck, const uschar *target, int zeroval, > int *lenptr, uschar **error) > { > return rfc2047_decode2(string, lencheck, target, zeroval, lenptr, NULL, error); >diff -ur exim.orig/src/route.c exim/src/route.c >--- exim.orig/src/route.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/route.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions concerned with routing, and the list of generic router options. */ >@@ -288,7 +288,12 @@ > > /* Build a host list if fallback hosts is set. */ > >- host_build_hostlist(&(r->fallback_hostlist), r->fallback_hosts, FALSE); >+ { >+ int old_pool = store_pool; >+ store_pool = POOL_PERM; >+ host_build_hostlist(&r->fallback_hostlist, r->fallback_hosts, FALSE); >+ store_pool = old_pool; >+ } > > /* Check redirect_router and pass_router are valid */ > >@@ -607,14 +612,13 @@ > BOOL ugid_set = FALSE; > const uschar *listptr; > uschar *check; >-uschar buffer[1024]; > > if (!s) return OK; > > DEBUG(D_route) debug_printf("checking require_files\n"); > > listptr = s; >-while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer)))) >+while ((check = string_nextinlist(&listptr, &sep, NULL, 0))) > { > int rc; > int eacces_code = 0; >@@ -1495,7 +1499,7 @@ > > if (!(node = tree_search(*root, name))) > { /* name should never be tainted */ >- node = store_get(sizeof(tree_node) + Ustrlen(name), FALSE); >+ node = store_get(sizeof(tree_node) + Ustrlen(name), GET_UNTAINTED); > Ustrcpy(node->name, name); > (void)tree_insertnode(root, node); > } >@@ -1666,7 +1670,7 @@ > addr->prefix_v = string_copyn(addr->local_part, vlen); > } > else >- addr->prefix = string_copyn_taint(addr->local_part, plen, FALSE); >+ addr->prefix = string_copyn_taint(addr->local_part, plen, GET_UNTAINTED); > addr->local_part += plen; > DEBUG(D_route) debug_printf("stripped prefix %s\n", addr->prefix); > } >@@ -1689,7 +1693,7 @@ > int lplen = Ustrlen(addr->local_part) - slen; > addr->suffix = vlen > ? addr->local_part + lplen >- : string_copy_taint(addr->local_part + lplen, slen); >+ : string_copy_taint(addr->local_part + lplen, GET_UNTAINTED); > addr->suffix_v = addr->suffix + Ustrlen(addr->suffix) - vlen; > addr->local_part = string_copyn(addr->local_part, lplen); > DEBUG(D_route) debug_printf("stripped suffix %s\n", addr->suffix); >@@ -1706,6 +1710,8 @@ > the local part sorted. */ > > router_name = r->name; >+ driver_srcfile = r->srcfile; >+ driver_srcline = r->srcline; > deliver_set_expansions(addr); > > /* For convenience, the pre-router checks are in a separate function, which >@@ -1713,7 +1719,7 @@ > > if ((rc = check_router_conditions(r, addr, verify, &pw, &error)) != OK) > { >- router_name = NULL; >+ driver_srcfile = router_name = NULL; driver_srcline = 0; > if (rc == SKIP) continue; > addr->message = error; > yield = rc; >@@ -1764,7 +1770,7 @@ > { > DEBUG(D_route) > debug_printf("\"more\"=false: skipping remaining routers\n"); >- router_name = NULL; >+ driver_srcfile = router_name = NULL; driver_srcline = 0; > r = NULL; > break; > } >@@ -1816,7 +1822,7 @@ > yield = (r->info->code)(r, addr, pw, verify, paddr_local, paddr_remote, > addr_new, addr_succeed); > >- router_name = NULL; >+ driver_srcfile = router_name = NULL; driver_srcline = 0; > > if (yield == FAIL) > { >@@ -2044,10 +2050,23 @@ > addr->message = expand_hide_passwords(addr->message); > > deliver_set_expansions(NULL); >-router_name = NULL; >+driver_srcfile = router_name = NULL; driver_srcline = 0; > f.disable_logging = FALSE; > return yield; > } > >+ >+ >+/* For error messages, a string describing the config location associated >+with current processing. NULL if we are not in a router. */ >+/* Name only, for now */ >+ >+uschar * >+router_current_name(void) >+{ >+if (!router_name) return NULL; >+return string_sprintf(" (router %s, %s %d)", router_name, driver_srcfile, driver_srcline); >+} >+ > #endif /*!MACRO_PREDEF*/ > /* End of route.c */ >diff -ur exim.orig/src/routers/accept.c exim/src/routers/accept.c >--- exim.orig/src/routers/accept.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/routers/accept.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,7 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ >+/* Copyright (c) The Exim Maintainers 2020 - 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -107,9 +107,6 @@ > uschar *remove_headers; > header_line *extra_headers; > >-addr_new = addr_new; /* Keep picky compilers happy */ >-addr_succeed = addr_succeed; >- > DEBUG(D_route) debug_printf("%s router called for %s\n domain = %s\n", > rblock->name, addr->address, addr->domain); > >diff -ur exim.orig/src/routers/dnslookup.c exim/src/routers/dnslookup.c >--- exim.orig/src/routers/dnslookup.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/routers/dnslookup.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >@@ -160,9 +160,6 @@ > const uschar *listptr; > uschar widen_buffer[256]; > >-addr_new = addr_new; /* Keep picky compilers happy */ >-addr_succeed = addr_succeed; >- > DEBUG(D_route) > debug_printf("%s router called for %s\n domain = %s\n", > rblock->name, addr->address, addr->domain); >@@ -207,6 +204,7 @@ > && (verify != v_sender || !ob->rewrite_headers || addr->parent)) > { > listptr = ob->widen_domains; >+ /* not expanded so should never be tainted */ > widen = string_nextinlist(&listptr, &widen_sep, widen_buffer, > sizeof(widen_buffer)); > >@@ -236,6 +234,7 @@ > else if (widen) > { > h.name = string_sprintf("%s.%s", addr->domain, widen); >+ /* not expanded so should never be tainted */ > widen = string_nextinlist(&listptr, &widen_sep, widen_buffer, > sizeof(widen_buffer)); > DEBUG(D_route) debug_printf("%s router widened %s to %s\n", rblock->name, >@@ -457,7 +456,7 @@ > /* Get store in which to preserve the original host item, chained on > to the address. */ > >-addr->host_list = store_get(sizeof(host_item), FALSE); >+addr->host_list = store_get(sizeof(host_item), GET_UNTAINTED); > addr->host_list[0] = h; > > /* Fill in the transport and queue the address for delivery. */ >diff -ur exim.orig/src/routers/ipliteral.c exim/src/routers/ipliteral.c >--- exim.orig/src/routers/ipliteral.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/routers/ipliteral.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -57,7 +57,6 @@ > ipliteral_router_options_block *ob = > (ipliteral_router_options_block *)(rblock->options_block); > */ >-rblock = rblock; > } > > >@@ -116,9 +115,6 @@ > int len = Ustrlen(domain); > int rc, ipv; > >-addr_new = addr_new; /* Keep picky compilers happy */ >-addr_succeed = addr_succeed; >- > DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n", > rblock->name, addr->address, addr->domain); > >@@ -150,7 +146,7 @@ > > /* Set up a host item */ > >-h = store_get(sizeof(host_item), FALSE); >+h = store_get(sizeof(host_item), GET_UNTAINTED); > > h->next = NULL; > h->address = string_copy(ip); >diff -ur exim.orig/src/routers/iplookup.c exim/src/routers/iplookup.c >--- exim.orig/src/routers/iplookup.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/routers/iplookup.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -161,23 +161,18 @@ > uschar *hostname, *reroute, *domain; > const uschar *listptr; > uschar host_buffer[256]; >-host_item *host = store_get(sizeof(host_item), FALSE); >+host_item *host = store_get(sizeof(host_item), GET_UNTAINTED); > address_item *new_addr; > iplookup_router_options_block *ob = > (iplookup_router_options_block *)(rblock->options_block); >-const pcre *re = ob->re_response_pattern; >+const pcre2_code *re = ob->re_response_pattern; > int count, query_len, rc; > int sep = 0; > >-addr_local = addr_local; /* Keep picky compilers happy */ >-addr_remote = addr_remote; >-addr_succeed = addr_succeed; >-pw = pw; >- > DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n", > rblock->name, addr->address, addr->domain); > >-reply = store_get(256, TRUE); /* tainted data */ >+reply = store_get(256, GET_TAINTED); > > /* Build the query string to send. If not explicitly given, a default of > "user@domain user@domain" is used. */ >@@ -205,6 +200,7 @@ > being a host list. */ > > listptr = ob->hosts; >+/* not expanded so should never be tainted */ > while ((hostname = string_nextinlist(&listptr, &sep, host_buffer, > sizeof(host_buffer)))) > { >diff -ur exim.orig/src/routers/iplookup.h exim/src/routers/iplookup.h >--- exim.orig/src/routers/iplookup.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/routers/iplookup.h 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2009 */ >+/* Copyright (c) The Exim Maintainers 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -17,7 +18,7 @@ > uschar *query; > uschar *response_pattern; > uschar *reroute; >- const pcre *re_response_pattern; >+ const pcre2_code *re_response_pattern; > BOOL optional; > } iplookup_router_options_block; > >diff -ur exim.orig/src/routers/manualroute.c exim/src/routers/manualroute.c >--- exim.orig/src/routers/manualroute.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/routers/manualroute.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -248,9 +248,6 @@ > BOOL individual_transport_set = FALSE; > BOOL randomize = ob->hosts_randomize; > >-addr_new = addr_new; /* Keep picky compilers happy */ >-addr_succeed = addr_succeed; >- > DEBUG(D_route) debug_printf("%s router called for %s\n domain = %s\n", > rblock->name, addr->address, addr->domain); > >@@ -262,7 +259,7 @@ > int sep = -(';'); /* Default is semicolon */ > listptr = ob->route_list; > >- while ((route_item = string_nextinlist(&listptr, &sep, NULL, 0)) != NULL) >+ while ((route_item = string_nextinlist(&listptr, &sep, NULL, 0))) > { > int rc; > >@@ -402,7 +399,7 @@ > if (hostlist[0]) > { > host_item *h; >- addr->host_list = h = store_get(sizeof(host_item), FALSE); >+ addr->host_list = h = store_get(sizeof(host_item), GET_UNTAINTED); > h->name = string_copy(hostlist); > h->address = NULL; > h->port = PORT_NONE; >@@ -471,7 +468,7 @@ > defined for these hosts. It will be a remote one, as a local transport is > dealt with above. However, we don't need one if verifying only. */ > >-if (transport == NULL && verify == v_none) >+if (!transport && verify == v_none) > { > log_write(0, LOG_MAIN, "Error in %s router: no transport defined", > rblock->name); >diff -ur exim.orig/src/routers/queryprogram.c exim/src/routers/queryprogram.c >--- exim.orig/src/routers/queryprogram.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/routers/queryprogram.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >@@ -86,13 +86,13 @@ > > /* A command must be given */ > >-if (ob->command == NULL) >+if (!ob->command) > log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n " > "a command specification is required", rblock->name); > > /* A uid/gid must be supplied */ > >-if (!ob->cmd_uid_set && ob->expand_cmd_uid == NULL) >+if (!ob->cmd_uid_set && !ob->expand_cmd_uid) > log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n " > "command_user must be specified", rblock->name); > } >@@ -242,41 +242,31 @@ > &addr_prop.remove_headers); > if (rc != OK) return rc; > >-#ifdef EXPERIMENTAL_SRS >-addr_prop.srs_sender = NULL; >-#endif >- > /* Get the fixed or expanded uid under which the command is to run > (initialization ensures that one or the other is set). */ > >-if (!ob->cmd_uid_set) >- { >- if (!route_find_expanded_user(ob->expand_cmd_uid, rblock->name, US"router", >- &upw, &uid, &(addr->message))) >+if ( !ob->cmd_uid_set >+ && !route_find_expanded_user(ob->expand_cmd_uid, rblock->name, US"router", >+ &upw, &uid, &(addr->message))) > return DEFER; >- } > > /* Get the fixed or expanded gid, or take the gid from the passwd entry. */ > > if (!ob->cmd_gid_set) >- { >- if (ob->expand_cmd_gid != NULL) >+ if (ob->expand_cmd_gid) > { > if (route_find_expanded_group(ob->expand_cmd_gid, rblock->name, > US"router", &gid, &(addr->message))) > return DEFER; > } >- else if (upw != NULL) >- { >+ else if (upw) > gid = upw->pw_gid; >- } > else > { > addr->message = string_sprintf("command_user set without command_group " > "for %s router", rblock->name); > return DEFER; > } >- } > > DEBUG(D_route) debug_printf("requires uid=%ld gid=%ld current_directory=%s\n", > (long int)uid, (long int)gid, current_directory); >@@ -301,11 +291,10 @@ > TRUE, /* expand the arguments */ > 0, /* not relevant when... */ > NULL, /* no transporting address */ >+ FALSE, /* args must be untainted */ > US"queryprogram router", /* for error messages */ >- &(addr->message))) /* where to put error message */ >- { >+ &addr->message)) /* where to put error message */ > return DEFER; >- } > > /* Create the child process, making it a group leader. */ > >@@ -373,8 +362,8 @@ > rword = buffer; > while (isspace(*rword)) rword++; > rdata = rword; >-while (*rdata != 0 && !isspace(*rdata)) rdata++; >-if (*rdata != 0) *rdata++ = 0; >+while (*rdata && !isspace(*rdata)) rdata++; >+if (*rdata) *rdata++ = 0; > > /* The word must be a known yield name. If it is "REDIRECT", the rest of the > line is redirection data, as for a .forward file. It may not contain filter >@@ -402,7 +391,7 @@ > NULL, /* sieve subaddress not relevant */ > &ugid, /* uid/gid (but not set) */ > &generated, /* where to hang the results */ >- &(addr->message), /* where to put messages */ >+ &addr->message, /* where to put messages */ > NULL, /* don't skip syntax errors */ > &filtertype, /* not used; will always be FILTER_FORWARD */ > string_sprintf("%s router", rblock->name)); >@@ -414,28 +403,28 @@ > response after verifying. */ > > case FF_DEFER: >- if (addr->message == NULL) addr->message = US"forced defer"; >+ if (!addr->message) addr->message = US"forced defer"; > else addr->user_message = addr->message; >- return DEFER; >+ return DEFER; > > case FF_FAIL: >- add_generated(rblock, addr_new, addr, generated, &addr_prop); >- if (addr->message == NULL) addr->message = US"forced rejection"; >+ add_generated(rblock, addr_new, addr, generated, &addr_prop); >+ if (!addr->message) addr->message = US"forced rejection"; > else addr->user_message = addr->message; >- return FAIL; >+ return FAIL; > > case FF_DELIVERED: >- break; >+ break; > > case FF_NOTDELIVERED: /* an empty redirection list is bad */ >- addr->message = US"no addresses supplied"; >+ addr->message = US"no addresses supplied"; > /* Fall through */ > > case FF_ERROR: > default: >- addr->basic_errno = ERRNO_BADREDIRECT; >- addr->message = string_sprintf("error in redirect data: %s", addr->message); >- return DEFER; >+ addr->basic_errno = ERRNO_BADREDIRECT; >+ addr->message = string_sprintf("error in redirect data: %s", addr->message); >+ return DEFER; > } > > /* Handle the generated addresses, if any. */ >@@ -473,20 +462,15 @@ > } > > /* The command yielded "ACCEPT". The rest of the string is a number of keyed >-fields from which we can fish out values using the "extract" expansion >-function. To use this feature, we must put the string into the $value variable, >-i.e. set lookup_value. */ >- >-lookup_value = rdata; >-s = expand_string(US"${extract{data}{$value}}"); >-if (*s != 0) addr_prop.address_data = string_copy(s); >+fields from which we can fish out values using the equivalent of the "extract" >+expansion function. */ > >-s = expand_string(US"${extract{transport}{$value}}"); >-lookup_value = NULL; >+if ((s = expand_getkeyed(US"data", rdata)) && *s) >+ addr_prop.address_data = string_copy(s); > > /* If we found a transport name, find the actual transport */ > >-if (*s != 0) >+if ((s = expand_getkeyed(US"transport", rdata)) && *s) > { > transport_instance *transport; > for (transport = transports; transport; transport = transport->next) >@@ -507,7 +491,7 @@ > > else > { >- if (!rf_get_transport(rblock->transport_name, &(rblock->transport), addr, >+ if (!rf_get_transport(rblock->transport_name, &rblock->transport, addr, > rblock->name, US"transport")) > return DEFER; > addr->transport = rblock->transport; >@@ -515,16 +499,12 @@ > > /* See if a host list is given, and if so, look up the addresses. */ > >-lookup_value = rdata; >-s = expand_string(US"${extract{hosts}{$value}}"); >- >-if (*s != 0) >+if ((s = expand_getkeyed(US"hosts", rdata)) && *s) > { > int lookup_type = LK_DEFAULT; >- uschar *ss = expand_string(US"${extract{lookup}{$value}}"); >- lookup_value = NULL; >+ uschar * ss = expand_getkeyed(US"lookup", rdata); > >- if (*ss != 0) >+ if (ss && *ss) > { > if (Ustrcmp(ss, "byname") == 0) lookup_type = LK_BYNAME; > else if (Ustrcmp(ss, "bydns") == 0) lookup_type = LK_BYDNS; >@@ -551,8 +531,7 @@ > > /* Queue the address for local or remote delivery. */ > >-return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)? >- OK : DEFER; >+return rf_queue_add(addr, addr_local, addr_remote, rblock, pw) ? OK : DEFER; > } > > #endif /*!MACRO_PREDEF*/ >diff -ur exim.orig/src/routers/redirect.c exim/src/routers/redirect.c >--- exim.orig/src/routers/redirect.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/routers/redirect.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -93,13 +93,6 @@ > { "sieve_useraddress", opt_stringptr, LOFF(sieve_useraddress) }, > { "sieve_vacation_directory", opt_stringptr, LOFF(sieve_vacation_directory) }, > { "skip_syntax_errors", opt_bool, LOFF(skip_syntax_errors) }, >-#ifdef EXPERIMENTAL_SRS >- { "srs", opt_stringptr, LOFF(srs) }, >- { "srs_alias", opt_stringptr, LOFF(srs_alias) }, >- { "srs_condition", opt_stringptr, LOFF(srs_condition) }, >- { "srs_dbinsert", opt_stringptr, LOFF(srs_dbinsert) }, >- { "srs_dbselect", opt_stringptr, LOFF(srs_dbselect) }, >-#endif > { "syntax_errors_text", opt_stringptr, LOFF(syntax_errors_text) }, > { "syntax_errors_to", opt_stringptr, LOFF(syntax_errors_to) } > }; >@@ -149,13 +142,6 @@ > NULL, /* qualify_domain */ > NULL, /* owners */ > NULL, /* owngroups */ >-#ifdef EXPERIMENTAL_SRS >- NULL, /* srs */ >- NULL, /* srs_alias */ >- NULL, /* srs_condition */ >- NULL, /* srs_dbinsert */ >- NULL, /* srs_dbselect */ >-#endif > 022, /* modemask */ > RDO_REWRITE | RDO_PREPEND_HOME, /* bit_options */ > FALSE, /* check_ancestor */ >@@ -409,14 +395,14 @@ > if (next->address[0] == '|') > { > address_pipe = next->address; >- if (rf_get_transport(ob->pipe_transport_name, &(ob->pipe_transport), >+ if (rf_get_transport(ob->pipe_transport_name, &ob->pipe_transport, > next, rblock->name, US"pipe_transport")) > next->transport = ob->pipe_transport; > address_pipe = NULL; > } > else if (next->address[0] == '>') > { >- if (rf_get_transport(ob->reply_transport_name, &(ob->reply_transport), >+ if (rf_get_transport(ob->reply_transport_name, &ob->reply_transport, > next, rblock->name, US"reply_transport")) > next->transport = ob->reply_transport; > } >@@ -432,11 +418,10 @@ > next->transport = ob->directory_transport; > } > else >- { >- if (rf_get_transport(ob->file_transport_name, &(ob->file_transport), >+ if (rf_get_transport(ob->file_transport_name, &ob->file_transport, > next, rblock->name, US"file_transport")) > next->transport = ob->file_transport; >- } >+ > address_file = NULL; > } > } >@@ -529,9 +514,6 @@ > int frc = 0; > int xrc = 0; > >-addr_local = addr_local; /* Keep picky compilers happy */ >-addr_remote = addr_remote; >- > /* Initialize the data to be propagated to the children */ > > addr_prop.address_data = deliver_address_data; >@@ -543,9 +525,6 @@ > addr_prop.variables = NULL; > tree_dup((tree_node **)&addr_prop.variables, addr->prop.variables); > >-#ifdef EXPERIMENTAL_SRS >-addr_prop.srs_sender = NULL; >-#endif > #ifdef SUPPORT_I18N > addr_prop.utf8_msg = addr->prop.utf8_msg; > addr_prop.utf8_downcvt = addr->prop.utf8_downcvt; >@@ -578,95 +557,6 @@ > ugid.gid_set = TRUE; > } > >-#ifdef EXPERIMENTAL_SRS >- /* Perform SRS on recipient/return-path as required */ >- >- if(ob->srs != NULL) >- { >- BOOL usesrs = TRUE; >- >- if(ob->srs_condition != NULL) >- usesrs = expand_check_condition(ob->srs_condition, "srs_condition expansion failed", NULL); >- >- if(usesrs) >- { >- int srs_action = 0, n_srs; >- uschar *res; >- uschar *usedomain; >- >- /* What are we doing? */ >- if(Ustrcmp(ob->srs, "forward") == 0) >- srs_action = 1; >- else if(Ustrcmp(ob->srs, "reverseandforward") == 0) >- { >- srs_action = 3; >- >- if((ob->srs_dbinsert == NULL) ^ (ob->srs_dbselect == NULL)) >- return DEFER; >- } >- else if(Ustrcmp(ob->srs, "reverse") == 0) >- srs_action = 2; >- >- /* Reverse SRS */ >- if(srs_action & 2) >- { >- srs_orig_recipient = addr->address; >- >- eximsrs_init(); >- if(ob->srs_dbselect) >- eximsrs_db_set(TRUE, ob->srs_dbselect); >-/* Comment this out for now... >-// else >-// eximsrs_db_set(TRUE, NULL); >-*/ >- >- if((n_srs = eximsrs_reverse(&res, addr->address)) == OK) >- { >- srs_recipient = res; >- DEBUG(D_any) >- debug_printf("SRS (reverse): Recipient '%s' rewritten to '%s'\n", srs_orig_recipient, srs_recipient); >- } >- >- eximsrs_done(); >- >- if(n_srs != OK) >- return n_srs; >- } >- >- /* Forward SRS */ >- /* No point in actually performing SRS if we are just verifying a recipient */ >- if((srs_action & 1) && verify == v_none && >- (sender_address ? sender_address[0] != 0 : FALSE)) >- { >- >- srs_orig_sender = sender_address; >- eximsrs_init(); >- if(ob->srs_dbinsert) >- eximsrs_db_set(FALSE, ob->srs_dbinsert); >-/* Comment this out for now... >-// else >-// eximsrs_db_set(FALSE, NULL); >-*/ >- >- if (!(usedomain = ob->srs_alias ? expand_string(ob->srs_alias) : NULL)) >- usedomain = string_copy(deliver_domain); >- >- if((n_srs = eximsrs_forward(&res, sender_address, usedomain)) == OK) >- { >- addr_prop.srs_sender = res; >- DEBUG(D_any) >- debug_printf("SRS (forward): Sender '%s' rewritten to '%s'\n", srs_orig_sender, res); >- } >- >- eximsrs_done(); >- >- if(n_srs != OK) >- return n_srs; >- } >- } >- } >-#endif >- > /* Call the function that interprets redirection data, either inline or from a > file. This is a separate function so that the system filter can use it. It will > run the function in a subprocess if necessary. If qualify_preserve_domain is >@@ -696,7 +586,7 @@ > frc = rda_interpret(&redirect, options, ob->include_directory, > ob->sieve_vacation_directory, ob->sieve_enotify_mailto_owner, > ob->sieve_useraddress, ob->sieve_subaddress, &ugid, &generated, >- &(addr->message), ob->skip_syntax_errors? &eblock : NULL, &filtertype, >+ &addr->message, ob->skip_syntax_errors? &eblock : NULL, &filtertype, > string_sprintf("%s router (recipient is %s)", rblock->name, addr->address)); > > qualify_domain_recipient = save_qualify_domain_recipient; >@@ -737,15 +627,13 @@ > addr->message = yield == FAIL ? US"forced rejection" : US"forced defer"; > else > { >- int ovector[3]; >- if (ob->forbid_smtp_code && >- pcre_exec(regex_smtp_code, NULL, CS addr->message, >- Ustrlen(addr->message), 0, PCRE_EOPT, >- ovector, sizeof(ovector)/sizeof(int)) >= 0) >+ uschar * matched; >+ if ( ob->forbid_smtp_code >+ && regex_match(regex_smtp_code, addr->message, -1, &matched)) > { > DEBUG(D_route) debug_printf("SMTP code at start of error message " > "is ignored because forbid_smtp_code is set\n"); >- addr->message += ovector[1]; >+ addr->message += Ustrlen(matched); > } > addr->user_message = addr->message; > setflag(addr, af_pass_message); >@@ -787,7 +675,7 @@ > > if (filtertype != FILTER_FORWARD && ob->skip_syntax_errors) > { >- eblock = store_get(sizeof(error_block), FALSE); >+ eblock = store_get(sizeof(error_block), GET_UNTAINTED); > eblock->next = NULL; > eblock->text1 = addr->message; > eblock->text2 = NULL; >diff -ur exim.orig/src/routers/redirect.h exim/src/routers/redirect.h >--- exim.orig/src/routers/redirect.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/routers/redirect.h 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2009 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -34,14 +35,6 @@ > uid_t *owners; > gid_t *owngroups; > >-#ifdef EXPERIMENTAL_SRS >- uschar *srs; >- uschar *srs_alias; >- uschar *srs_condition; >- uschar *srs_dbinsert; >- uschar *srs_dbselect; >-#endif >- > int modemask; > int bit_options; > BOOL check_ancestor; >diff -ur exim.orig/src/routers/rf_change_domain.c exim/src/routers/rf_change_domain.c >--- exim.orig/src/routers/rf_change_domain.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/routers/rf_change_domain.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -35,9 +36,9 @@ > rf_change_domain(address_item *addr, const uschar *domain, BOOL rewrite, > address_item **addr_new) > { >-address_item *parent = store_get(sizeof(address_item), FALSE); >-uschar *at = Ustrrchr(addr->address, '@'); >-uschar *address = string_sprintf("%.*s@%s", >+address_item * parent = store_get(sizeof(address_item), GET_UNTAINTED); >+uschar * at = Ustrrchr(addr->address, '@'); >+uschar * address = string_sprintf("%.*s@%s", > (int)(at - addr->address), addr->address, domain); > > DEBUG(D_route) debug_printf("domain changed to %s\n", domain); >diff -ur exim.orig/src/routers/rf_get_munge_headers.c exim/src/routers/rf_get_munge_headers.c >--- exim.orig/src/routers/rf_get_munge_headers.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/routers/rf_get_munge_headers.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -38,17 +39,17 @@ > { > const uschar * list = rblock->extra_headers; > int sep = '\n'; >- uschar * s; >+ uschar * s, * t; > int slen; > > while ((s = string_nextinlist(&list, &sep, NULL, 0))) >- if (!(s = expand_string(s))) >+ if (!(s = expand_string(t = s))) > { > if (!f.expand_string_forcedfail) > { > addr->message = string_sprintf( > "%s router failed to expand add_headers item \"%s\": %s", >- rblock->name, s, expand_string_message); >+ rblock->name, t, expand_string_message); > return DEFER; > } > } >@@ -59,7 +60,7 @@ > shared with other addresses. The output function outputs them in reverse > order. */ > >- header_line * h = store_get(sizeof(header_line), FALSE); >+ header_line * h = store_get(sizeof(header_line), GET_UNTAINTED); > > /* We used to use string_sprintf() to add the newline if needed, but that > causes problems if the header line is exceedingly long (e.g. adding >@@ -69,7 +70,7 @@ > h->text = s; > else > { >- h->text = store_get(slen+2, is_tainted(s)); >+ h->text = store_get(slen+2, s); > memcpy(h->text, s, slen); > h->text[slen++] = '\n'; > h->text[slen] = 0; >@@ -90,20 +91,20 @@ > { > const uschar * list = rblock->remove_headers; > int sep = ':'; >- uschar * s; >+ uschar * s, * t; > gstring * g = NULL; > > if (*remove_headers) > g = string_cat(NULL, *remove_headers); > > while ((s = string_nextinlist(&list, &sep, NULL, 0))) >- if (!(s = expand_string(s))) >+ if (!(s = expand_string(t = s))) > { > if (!f.expand_string_forcedfail) > { > addr->message = string_sprintf( > "%s router failed to expand remove_headers item \"%s\": %s", >- rblock->name, s, expand_string_message); >+ rblock->name, t, expand_string_message); > return DEFER; > } > } >diff -ur exim.orig/src/routers/rf_get_transport.c exim/src/routers/rf_get_transport.c >--- exim.orig/src/routers/rf_get_transport.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/routers/rf_get_transport.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2009 */ > /* See the file NOTICE for conditions of use and distribution. */ > >diff -ur exim.orig/src/routers/rf_queue_add.c exim/src/routers/rf_queue_add.c >--- exim.orig/src/routers/rf_queue_add.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/routers/rf_queue_add.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2018 */ >+/* Copyright (c) The Exim Maintainers 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >diff -ur exim.orig/src/search.c exim/src/search.c >--- exim.orig/src/search.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/search.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2015 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* A set of functions to search databases in various formats. An open >@@ -63,11 +63,9 @@ > */ > > int >-search_findtype(const uschar *name, int len) >+search_findtype(const uschar * name, int len) > { >-int bot = 0; >-int top = lookup_list_count; >-while (top > bot) >+for (int bot = 0, top = lookup_list_count; top > bot; ) > { > int mid = (top + bot)/2; > int c = Ustrncmp(name, lookup_list[mid]->name, len); >@@ -92,7 +90,7 @@ > if (c > 0) bot = mid + 1; else top = mid; > } > >-search_error_message = string_sprintf("unknown lookup type \"%.*s\"",len,name); >+search_error_message = string_sprintf("unknown lookup type \"%.*s\"", len, name); > return -1; > } > >@@ -217,6 +215,55 @@ > } > > >+/* Set the parameters for the three different kinds of lookup. >+Arguments: >+ search_type the search-type code >+ search the search-type string >+ query argument for the search; filename or query >+ fnamep pointer to return filename >+ opts options >+ >+Return: keyquery the search-type (for single-key) or query (for query-type) >+ */ >+uschar * >+search_args(int search_type, uschar * search, uschar * query, uschar ** fnamep, >+ const uschar * opts) >+{ >+Uskip_whitespace(&query); >+if (mac_islookup(search_type, lookup_absfilequery)) >+ { /* query-style but with file (sqlite) */ >+ int sep = ','; >+ >+ /* Check options first for new-style file spec */ >+ if (opts) for (uschar * s; s = string_nextinlist(&opts, &sep, NULL, 0); ) >+ if (Ustrncmp(s, "file=", 5) == 0) >+ { >+ *fnamep = s+5; >+ return query; >+ } >+ >+ /* If no filename from options, use old-tyle space-sep prefix on query */ >+ if (*query == '/') >+ { >+ uschar * s = query; >+ while (*query && !isspace(*query)) query++; >+ *fnamep = string_copyn(s, query - s); >+ Uskip_whitespace(&query); >+ } >+ else >+ *fnamep = NULL; >+ return query; /* remainder after file skipped */ >+ } >+if (!mac_islookup(search_type, lookup_querystyle)) >+ { /* single-key */ >+ *fnamep = query; >+ return search; /* modifiers important so use "keyquery" for them */ >+ } >+*fnamep = NULL; /* else query-style */ >+return query; >+} >+ >+ > > /************************************************* > * Release cached resources * >@@ -429,8 +476,8 @@ > > if (!t) > { >- t = store_get(sizeof(tree_node) + Ustrlen(keybuffer), FALSE); >- t->data.ptr = c = store_get(sizeof(search_cache), FALSE); >+ t = store_get(sizeof(tree_node) + Ustrlen(keybuffer), GET_UNTAINTED); >+ t->data.ptr = c = store_get(sizeof(search_cache), GET_UNTAINTED); > c->item_cache = NULL; > Ustrcpy(t->name, keybuffer); > tree_insertnode(&search_tree, t); >@@ -462,6 +509,7 @@ > NULL for query-style searches > keystring the keystring for single-key+file lookups, or > the querystring for query-style lookups >+ cache_rd FALSE to avoid lookup in cache layer > opts type-specific options > > Returns: a pointer to a dynamic string containing the answer, >@@ -472,7 +520,7 @@ > > static uschar * > internal_search_find(void * handle, const uschar * filename, uschar * keystring, >- const uschar * opts) >+ BOOL cache_rd, const uschar * opts) > { > tree_node * t = (tree_node *)handle; > search_cache * c = (search_cache *)(t->data.ptr); >@@ -501,11 +549,13 @@ > store_pool = POOL_SEARCH; > > /* Look up the data for the key, unless it is already in the cache for this >-file. No need to check c->item_cache for NULL, tree_search will do so. */ >+file. No need to check c->item_cache for NULL, tree_search will do so. Check >+whether we want to use the cache entry last so that we can always replace it. */ > > if ( (t = tree_search(c->item_cache, keystring)) > && (!(e = t->data.ptr)->expiry || e->expiry > time(NULL)) > && (!opts && !e->opts || opts && e->opts && Ustrcmp(opts, e->opts) == 0) >+ && cache_rd > ) > { /* Data was in the cache already; set the pointer from the tree node */ > data = e->data.ptr; >@@ -522,11 +572,53 @@ > { > if (t) > debug_printf_indent("cached data found but %s; ", >- e->expiry && e->expiry <= time(NULL) ? "out-of-date" : "wrong opts"); >+ e->expiry && e->expiry <= time(NULL) ? "out-of-date" >+ : cache_rd ? "wrong opts" : "no_rd option set"); > debug_printf_indent("%s lookup required for %s%s%s\n", > filename ? US"file" : US"database", > keystring, > filename ? US"\n in " : US"", filename ? filename : US""); >+ if (!filename && is_tainted(keystring)) >+ { >+ debug_printf_indent(" "); >+ debug_print_taint(keystring); >+ } >+ } >+ >+ /* Check that the query, for query-style lookups, >+ is either untainted or properly quoted for the lookup type. >+ >+ XXX Should we this move into lf_sqlperform() ? The server-taint check is there. >+ */ >+ >+ if ( !filename && lookup_list[search_type]->quote >+ && is_tainted(keystring) && !is_quoted_like(keystring, search_type)) >+ { >+ uschar * s = acl_current_verb(); >+ if (!s) s = authenticator_current_name(); /* must be before transport */ >+ if (!s) s = transport_current_name(); /* must be before router */ >+ if (!s) s = router_current_name(); /* GCC ?: would be good, but not in clang */ >+ if (!s) s = US""; >+#ifdef enforce_quote_protection_notyet >+ search_error_message = string_sprintf( >+ "tainted search query is not properly quoted%s: %s%s", >+ s, keystring); >+ f.search_find_defer = TRUE; >+#else >+ { >+ int q = quoter_for_address(keystring); >+ /* If we're called from a transport, no privs to open the paniclog; >+ the logging punts to using stderr - and that seems to stop the debug >+ stream. */ >+ log_write(0, >+ transport_name ? LOG_MAIN : LOG_MAIN|LOG_PANIC, >+ "tainted search query is not properly quoted%s: %s", s, keystring); >+ >+ DEBUG(D_lookup) debug_printf_indent("search_type %d (%s) quoting %d (%s)\n", >+ search_type, lookup_list[search_type]->name, >+ q, is_real_quoter(q) ? lookup_list[q]->name : US"none"); >+ } >+#endif > } > > /* Call the code for the different kinds of search. DEFER is handled >@@ -534,21 +626,26 @@ > distinguish if necessary. */ > > if (lookup_list[search_type]->find(c->handle, filename, keystring, keylength, >- &data, &search_error_message, &do_cache, opts) == DEFER) >+ &data, &search_error_message, &do_cache, opts) == DEFER) > f.search_find_defer = TRUE; > > /* A record that has been found is now in data, which is either NULL > or points to a bit of dynamic store. Cache the result of the lookup if > caching is permitted. Lookups can disable caching, when they did something > that changes their data. The mysql and pgsql lookups do this when an >- UPDATE/INSERT query was executed. */ >+ UPDATE/INSERT query was executed. Lookups can also set a TTL for the >+ cache entry; the dnsdb lookup does. >+ Finally, the caller can request no caching by setting an option. */ > > else if (do_cache) > { >+ DEBUG(D_lookup) debug_printf_indent("%s cache entry\n", >+ t ? "replacing old" : "creating new"); > if (!t) /* No existing entry. Create new one. */ > { > int len = keylength + 1; >- e = store_get(sizeof(expiring_data) + sizeof(tree_node) + len, is_tainted(keystring)); >+ /* The cache node value should never be expanded so use tainted mem */ >+ e = store_get(sizeof(expiring_data) + sizeof(tree_node) + len, GET_TAINTED); > t = (tree_node *)(e+1); > memcpy(t->name, keystring, len); > t->data.ptr = e; >@@ -561,8 +658,8 @@ > e->data.ptr = data; > } > >- /* If caching was disabled, empty the cache tree. We just set the cache >- pointer to NULL here, because we cannot release the store at this stage. */ >+/* If caching was disabled, empty the cache tree. We just set the cache >+pointer to NULL here, because we cannot release the store at this stage. */ > > else > { >@@ -621,9 +718,9 @@ > int partial, const uschar * affix, int affixlen, int starflags, > int * expand_setup, const uschar * opts) > { >-tree_node *t = (tree_node *)handle; >-BOOL set_null_wild = FALSE; >-uschar *yield; >+tree_node * t = (tree_node *)handle; >+BOOL set_null_wild = FALSE, cache_rd = TRUE, ret_key = FALSE; >+uschar * yield; > > DEBUG(D_lookup) > { >@@ -636,6 +733,23 @@ > > } > >+/* Parse global lookup options. Also, create a new options list with >+the global options dropped so that the cache-modifiers are not >+used in the cache key. */ >+ >+if (opts) >+ { >+ int sep = ','; >+ gstring * g = NULL; >+ >+ for (uschar * ele; ele = string_nextinlist(&opts, &sep, NULL, 0); ) >+ if (Ustrcmp(ele, "ret=key") == 0) ret_key = TRUE; >+ else if (Ustrcmp(ele, "cache=no_rd") == 0) cache_rd = FALSE; >+ else g = string_append_listele(g, ',', ele); >+ >+ opts = string_from_gstring(g); >+ } >+ > /* Arrange to put this database at the top of the LRU chain if it is a type > that opens real files. */ > >@@ -683,7 +797,7 @@ > /* First of all, try to match the key string verbatim. If matched a complete > entry but could have been partial, flag to set up variables. */ > >-yield = internal_search_find(handle, filename, keystring, opts); >+yield = internal_search_find(handle, filename, keystring, cache_rd, opts); > if (f.search_find_defer) return NULL; > > if (yield) { if (partial >= 0) set_null_wild = TRUE; } >@@ -704,11 +818,11 @@ > if (affixlen == 0) keystring2 = keystring; else > { > keystring2 = store_get(len + affixlen + 1, >- is_tainted(keystring) || is_tainted(affix)); >+ is_tainted(keystring) || is_tainted(affix) ? GET_TAINTED : GET_UNTAINTED); > Ustrncpy(keystring2, affix, affixlen); > Ustrcpy(keystring2 + affixlen, keystring); > DEBUG(D_lookup) debug_printf_indent("trying partial match %s\n", keystring2); >- yield = internal_search_find(handle, filename, keystring2, opts); >+ yield = internal_search_find(handle, filename, keystring2, cache_rd, opts); > if (f.search_find_defer) return NULL; > } > >@@ -746,7 +860,8 @@ > } > > DEBUG(D_lookup) debug_printf_indent("trying partial match %s\n", keystring3); >- yield = internal_search_find(handle, filename, keystring3, opts); >+ yield = internal_search_find(handle, filename, keystring3, >+ cache_rd, opts); > if (f.search_find_defer) return NULL; > if (yield) > { >@@ -787,7 +902,7 @@ > *atat = '*'; > > DEBUG(D_lookup) debug_printf_indent("trying default match %s\n", atat); >- yield = internal_search_find(handle, filename, atat, opts); >+ yield = internal_search_find(handle, filename, atat, cache_rd, opts); > *atat = savechar; > if (f.search_find_defer) return NULL; > >@@ -810,7 +925,7 @@ > if (!yield && starflags & (SEARCH_STAR|SEARCH_STARAT)) > { > DEBUG(D_lookup) debug_printf_indent("trying to match *\n"); >- yield = internal_search_find(handle, filename, US"*", opts); >+ yield = internal_search_find(handle, filename, US"*", cache_rd, opts); > if (yield && expand_setup && *expand_setup >= 0) > { > *expand_setup += 1; >@@ -843,17 +958,8 @@ > than the result. Return a de-tainted version of the key on the grounds that > it have been validated by the lookup. */ > >-if (yield && opts) >- { >- int sep = ','; >- for (uschar * ele; ele = string_nextinlist(&opts, &sep, NULL, 0); ) >- if (Ustrcmp(ele, "ret=key") == 0) >- { >- DEBUG(D_lookup) debug_printf_indent("lookup ret=key: %s\n", keystring); >- yield = string_copy_taint(keystring, FALSE); >- break; >- } >- } >+if (yield && ret_key) >+ yield = string_copy_taint(keystring, GET_UNTAINTED); > > return yield; > } >diff -ur exim.orig/src/sieve.c exim/src/sieve.c >--- exim.orig/src/sieve.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/sieve.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,9 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >-/* Copyright (c) Michael Haardt 2003 - 2015 >- * Copyright (c) The Exim Maintainers 2016 - 2020 >+/* >+ * Copyright (c) The Exim Maintainers 2016 - 2022 >+ * Copyright (c) Michael Haardt 2003 - 2015 > * See the file NOTICE for conditions of use and distribution. > */ > >@@ -54,7 +55,7 @@ > > struct Sieve > { >- uschar *filter; >+ const uschar *filter; > const uschar *pc; > int line; > const uschar *errmsg; >@@ -71,7 +72,7 @@ > int require_enotify; > struct Notification *notified; > #endif >- uschar *enotify_mailto_owner; >+ const uschar *enotify_mailto_owner; > #ifdef SUBADDRESS > int require_subaddress; > #endif >@@ -79,7 +80,7 @@ > int require_vacation; > int vacation_ran; > #endif >- uschar *vacation_directory; >+ const uschar *vacation_directory; > const uschar *subaddress; > const uschar *useraddress; > int require_copy; >@@ -245,7 +246,7 @@ > dst->length=0; > else > { >- dst->character = store_get(dst->length+1, is_tainted(src->character)); /* plus one for \0 */ >+ dst->character = store_get(dst->length+1, src->character); /* plus one for \0 */ > new=dst->character; > } > for (const uschar * start = src->character, * end = start + src->length; >@@ -444,16 +445,16 @@ > filter->errmsg=US"Invalid URI encoding"; > return -1; > } >- new=store_get(sizeof(string_item), FALSE); >- new->text = store_get(to.length+1, is_tainted(to.character)); >+ new = store_get(sizeof(string_item), GET_UNTAINTED); >+ new->text = store_get(to.length+1, to.character); > if (to.length) memcpy(new->text, to.character, to.length); >- new->text[to.length]='\0'; >- new->next=*recipient; >- *recipient=new; >+ new->text[to.length] = '\0'; >+ new->next = *recipient; >+ *recipient = new; > } > else > { >- filter->errmsg=US"Missing addr-spec in URI"; >+ filter->errmsg = US"Missing addr-spec in URI"; > return -1; > } > if (*uri=='%') uri+=3; >@@ -502,8 +503,8 @@ > } > if (hname.length==2 && strcmpic(hname.character, US"to")==0) > { >- new=store_get(sizeof(string_item), FALSE); >- new->text = store_get(hvalue.length+1, is_tainted(hvalue.character)); >+ new=store_get(sizeof(string_item), GET_UNTAINTED); >+ new->text = store_get(hvalue.length+1, hvalue.character); > if (hvalue.length) memcpy(new->text, hvalue.character, hvalue.length); > new->text[hvalue.length]='\0'; > new->next=*recipient; >@@ -1729,7 +1730,7 @@ > struct String *new; > > dataCapacity = dataCapacity ? dataCapacity * 2 : 4; >- new = store_get(sizeof(struct String) * dataCapacity, FALSE); >+ new = store_get(sizeof(struct String) * dataCapacity, GET_UNTAINTED); > > if (d) memcpy(new,d,sizeof(struct String)*dataLength); > d = new; >@@ -1767,7 +1768,7 @@ > } > else /* single string */ > { >- if (!(d=store_get(sizeof(struct String)*2, FALSE))) >+ if (!(d=store_get(sizeof(struct String)*2, GET_UNTAINTED))) > return -1; > > m=parse_string(filter,&d[0]); >@@ -3073,7 +3074,7 @@ > if (!already) > /* New notification, process it */ > { >- struct Notification * sent = store_get(sizeof(struct Notification), FALSE); >+ struct Notification * sent = store_get(sizeof(struct Notification), GET_UNTAINTED); > sent->method=method; > sent->importance=importance; > sent->message=message; >@@ -3093,9 +3094,9 @@ > ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain") > : from.character); > for (string_item * p = recipient; p; p=p->next) >- fprintf(f,"To: %s\n",p->text); >- fprintf(f,"Auto-Submitted: auto-notified; %s\n",filter->enotify_mailto_owner); >- if (header.length>0) fprintf(f,"%s",header.character); >+ fprintf(f, "To: %s\n",p->text); >+ fprintf(f, "Auto-Submitted: auto-notified; %s\n", filter->enotify_mailto_owner); >+ if (header.length > 0) fprintf(f, "%s", header.character); > if (message.length==-1) > { > message.character=US"Notification"; >@@ -3105,7 +3106,7 @@ > fprintf(f, "Subject: %s\n", parse_quote_2047(message.character, > message.length, US"utf-8", TRUE)); > fprintf(f,"\n"); >- if (body.length>0) fprintf(f,"%s\n",body.character); >+ if (body.length > 0) fprintf(f, "%s\n", body.character); > fflush(f); > (void)fclose(f); > (void)child_close(pid, 0); >@@ -3212,9 +3213,9 @@ > } > for (struct String * a = addresses; a->length != -1; ++a) > { >- string_item * new = store_get(sizeof(string_item), FALSE); >+ string_item * new = store_get(sizeof(string_item), GET_UNTAINTED); > >- new->text = store_get(a->length+1, is_tainted(a->character)); >+ new->text = store_get(a->length+1, a->character); > if (a->length) memcpy(new->text,a->character,a->length); > new->text[a->length]='\0'; > new->next=aliases; >@@ -3327,7 +3328,7 @@ > addr->prop.ignore_error = TRUE; > addr->next = *generated; > *generated = addr; >- addr->reply = store_get(sizeof(reply_item), FALSE); >+ addr->reply = store_get(sizeof(reply_item), GET_UNTAINTED); > memset(addr->reply,0,sizeof(reply_item)); /* XXX */ > addr->reply->to = string_copy(sender_address); > if (from.length==-1) >@@ -3425,7 +3426,7 @@ > > if (parse_white(filter)==-1) return -1; > >-if (exec && filter->vacation_directory != NULL && filter_test == FTEST_NONE) >+if (exec && filter->vacation_directory && filter_test == FTEST_NONE) > { > DIR *oncelogdir; > struct dirent *oncelog; >@@ -3448,8 +3449,8 @@ > while ((oncelog = readdir(oncelogdir))) > if (strlen(oncelog->d_name)==32) > { >- uschar *s = string_sprintf("%s/%s",filter->vacation_directory,oncelog->d_name); >- if (Ustat(s,&properties)==0 && (properties.st_mtime+VACATION_MAX_DAYS*86400)<now) >+ uschar *s = string_sprintf("%s/%s", filter->vacation_directory, oncelog->d_name); >+ if (Ustat(s,&properties) == 0 && properties.st_mtime+VACATION_MAX_DAYS*86400 < now) > Uunlink(s); > } > closedir(oncelogdir); >@@ -3484,7 +3485,7 @@ > #ifdef ENOTIFY > else if (eq_octet(check,&str_enotify,0)) > { >- if (filter->enotify_mailto_owner == NULL) >+ if (!filter->enotify_mailto_owner) > { > filter->errmsg=CUS "enotify disabled"; > return -1; >@@ -3498,7 +3499,7 @@ > #ifdef VACATION > else if (eq_octet(check,&str_vacation,0)) > { >- if (filter_test == FTEST_NONE && filter->vacation_directory == NULL) >+ if (filter_test == FTEST_NONE && !filter->vacation_directory) > { > filter->errmsg=CUS "vacation disabled"; > return -1; >@@ -3554,44 +3555,36 @@ > */ > > int >-sieve_interpret(uschar *filter, int options, uschar *vacation_directory, >- uschar *enotify_mailto_owner, uschar *useraddress, uschar *subaddress, >- address_item **generated, uschar **error) >+sieve_interpret(const uschar * filter, int options, >+ const uschar * vacation_directory, const uschar * enotify_mailto_owner, >+ const uschar * useraddress, const uschar * subaddress, >+ address_item ** generated, uschar ** error) > { > struct Sieve sieve; > int r; >-uschar *msg; >- >-options = options; /* Keep picky compilers happy */ >-error = error; >+uschar * msg; > > DEBUG(D_route) debug_printf("Sieve: start of processing\n"); > sieve.filter = filter; > > if (!vacation_directory) > sieve.vacation_directory = NULL; >-else >+else if (!(sieve.vacation_directory = expand_cstring(vacation_directory))) > { >- if (!(sieve.vacation_directory = expand_string(vacation_directory))) >- { >- *error = string_sprintf("failed to expand \"%s\" " >- "(sieve_vacation_directory): %s", vacation_directory, >- expand_string_message); >- return FF_ERROR; >- } >+ *error = string_sprintf("failed to expand \"%s\" " >+ "(sieve_vacation_directory): %s", vacation_directory, >+ expand_string_message); >+ return FF_ERROR; > } > > if (!enotify_mailto_owner) > sieve.enotify_mailto_owner = NULL; >-else >+else if (!(sieve.enotify_mailto_owner = expand_cstring(enotify_mailto_owner))) > { >- if (!(sieve.enotify_mailto_owner = expand_string(enotify_mailto_owner))) >- { >- *error = string_sprintf("failed to expand \"%s\" " >- "(sieve_enotify_mailto_owner): %s", enotify_mailto_owner, >- expand_string_message); >- return FF_ERROR; >- } >+ *error = string_sprintf("failed to expand \"%s\" " >+ "(sieve_enotify_mailto_owner): %s", enotify_mailto_owner, >+ expand_string_message); >+ return FF_ERROR; > } > > sieve.useraddress = useraddress >@@ -3599,14 +3592,13 @@ > sieve.subaddress = subaddress; > > #ifdef COMPILE_SYNTAX_CHECKER >-if (parse_start(&sieve,0,generated)==1) >+if (parse_start(&sieve, 0, generated) == 1) > #else >-if (parse_start(&sieve,1,generated)==1) >+if (parse_start(&sieve, 1, generated) == 1) > #endif >- { > if (sieve.keep) > { >- add_addr(generated,US"inbox",1,0,0,0); >+ add_addr(generated, US"inbox", 1, 0, 0, 0); > msg = US"Implicit keep"; > r = FF_DELIVERED; > } >@@ -3615,7 +3607,6 @@ > msg = US"No implicit keep"; > r = FF_DELIVERED; > } >- } > else > { > msg = string_sprintf("Sieve error: %s in line %d",sieve.errmsg,sieve.line); >diff -ur exim.orig/src/smtp_in.c exim/src/smtp_in.c >--- exim.orig/src/smtp_in.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/smtp_in.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions for handling an incoming SMTP call. */ >@@ -139,7 +139,7 @@ > #endif > BOOL dsn_advertised :1; > BOOL esmtp :1; >- BOOL helo_required :1; >+ BOOL helo_verify_required :1; > BOOL helo_verify :1; > BOOL helo_seen :1; > BOOL helo_accept_junk :1; >@@ -153,7 +153,7 @@ > BOOL smtputf8_advertised :1; > #endif > } fl = { >- .helo_required = FALSE, >+ .helo_verify_required = FALSE, > .helo_verify = FALSE, > .smtp_exit_function_called = FALSE, > }; >@@ -227,7 +227,7 @@ > /* This list of names is used for performing the smtp_no_mail logging action. > It must be kept in step with the SCH_xxx enumerations. */ > >-static uschar *smtp_names[] = >+uschar * smtp_names[] = > { > US"NONE", US"AUTH", US"DATA", US"BDAT", US"EHLO", US"ETRN", US"EXPN", > US"HELO", US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET", >@@ -320,98 +320,6 @@ > static void smtp_rset_handler(void); > > /************************************************* >-* Recheck synchronization * >-*************************************************/ >- >-/* Synchronization checks can never be perfect because a packet may be on its >-way but not arrived when the check is done. Normally, the checks happen when >-commands are read: Exim ensures that there is no more input in the input buffer. >-In normal cases, the response to the command will be fast, and there is no >-further check. >- >-However, for some commands an ACL is run, and that can include delays. In those >-cases, it is useful to do another check on the input just before sending the >-response. This also applies at the start of a connection. This function does >-that check by means of the select() function, as long as the facility is not >-disabled or inappropriate. A failure of select() is ignored. >- >-When there is unwanted input, we read it so that it appears in the log of the >-error. >- >-Arguments: none >-Returns: TRUE if all is well; FALSE if there is input pending >-*/ >- >-static BOOL >-wouldblock_reading(void) >-{ >-int fd, rc; >-fd_set fds; >-struct timeval tzero; >- >-#ifndef DISABLE_TLS >-if (tls_in.active.sock >= 0) >- return !tls_could_read(); >-#endif >- >-if (smtp_inptr < smtp_inend) >- return FALSE; >- >-fd = fileno(smtp_in); >-FD_ZERO(&fds); >-FD_SET(fd, &fds); >-tzero.tv_sec = 0; >-tzero.tv_usec = 0; >-rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero); >- >-if (rc <= 0) return TRUE; /* Not ready to read */ >-rc = smtp_getc(GETC_BUFFER_UNLIMITED); >-if (rc < 0) return TRUE; /* End of file or error */ >- >-smtp_ungetc(rc); >-return FALSE; >-} >- >-static BOOL >-check_sync(void) >-{ >-if (!smtp_enforce_sync || !sender_host_address || f.sender_host_notsocket) >- return TRUE; >- >-return wouldblock_reading(); >-} >- >- >-/* If there's input waiting (and we're doing pipelineing) then we can pipeline >-a reponse with the one following. */ >- >-static BOOL >-pipeline_response(void) >-{ >-if ( !smtp_enforce_sync || !sender_host_address >- || f.sender_host_notsocket || !f.smtp_in_pipelining_advertised) >- return FALSE; >- >-if (wouldblock_reading()) return FALSE; >-f.smtp_in_pipelining_used = TRUE; >-return TRUE; >-} >- >- >-#ifndef DISABLE_PIPE_CONNECT >-static BOOL >-pipeline_connect_sends(void) >-{ >-if (!sender_host_address || f.sender_host_notsocket || !fl.pipe_connect_acceptable) >- return FALSE; >- >-if (wouldblock_reading()) return FALSE; >-f.smtp_in_early_pipe_used = TRUE; >-return TRUE; >-} >-#endif >- >-/************************************************* > * Log incomplete transactions * > *************************************************/ > >@@ -434,7 +342,7 @@ > > if (recipients_count > 0) > { >- raw_recipients = store_get(recipients_count * sizeof(uschar *), FALSE); >+ raw_recipients = store_get(recipients_count * sizeof(uschar *), GET_UNTAINTED); > for (int i = 0; i < recipients_count; i++) > raw_recipients[i] = recipients_list[i].address; > raw_recipients_count = recipients_count; >@@ -493,6 +401,25 @@ > } > > >+/******************************************************************************/ >+/* SMTP input buffer handling. Most of these are similar to stdio routines. */ >+ >+static void >+smtp_buf_init(void) >+{ >+/* Set up the buffer for inputting using direct read() calls, and arrange to >+call the local functions instead of the standard C ones. Place a NUL at the >+end of the buffer to safety-stop C-string reads from it. */ >+ >+if (!(smtp_inbuffer = US malloc(IN_BUFFER_SIZE))) >+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP input buffer"); >+smtp_inbuffer[IN_BUFFER_SIZE-1] = '\0'; >+ >+smtp_inptr = smtp_inend = smtp_inbuffer; >+smtp_had_eof = smtp_had_error = 0; >+} >+ >+ > > /* Refill the buffer, and notify DKIM verification code. > Return false for error or EOF. >@@ -502,6 +429,7 @@ > smtp_refill(unsigned lim) > { > int rc, save_errno; >+ > if (!smtp_out) return FALSE; > fflush(smtp_out); > if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout); >@@ -543,11 +471,18 @@ > return TRUE; > } > >-/************************************************* >-* SMTP version of getc() * >-*************************************************/ > >-/* This gets the next byte from the SMTP input buffer. If the buffer is empty, >+/* Check if there is buffered data */ >+ >+BOOL >+smtp_hasc(void) >+{ >+return smtp_inptr < smtp_inend; >+} >+ >+/* SMTP version of getc() >+ >+This gets the next byte from the SMTP input buffer. If the buffer is empty, > it flushes the output, and refills the buffer, with a timeout. The signal > handler is set appropriately by the calling function. This function is not used > after a connection has negotiated itself into an TLS/SSL state. >@@ -559,21 +494,20 @@ > int > smtp_getc(unsigned lim) > { >-if (smtp_inptr >= smtp_inend) >- if (!smtp_refill(lim)) >- return EOF; >+if (!smtp_hasc() && !smtp_refill(lim)) return EOF; > return *smtp_inptr++; > } > >+/* Get many bytes, refilling buffer if needed */ >+ > uschar * > smtp_getbuf(unsigned * len) > { > unsigned size; > uschar * buf; > >-if (smtp_inptr >= smtp_inend) >- if (!smtp_refill(*len)) >- { *len = 0; return NULL; } >+if (!smtp_hasc() && !smtp_refill(*len)) >+ { *len = 0; return NULL; } > > if ((size = smtp_inend - smtp_inptr) > *len) size = *len; > buf = smtp_inptr; >@@ -582,17 +516,146 @@ > return buf; > } > >+/* Copy buffered data to the dkim feed. >+Called, unless TLS, just before starting to read message headers. */ >+ > void >-smtp_get_cache(void) >+smtp_get_cache(unsigned lim) > { > #ifndef DISABLE_DKIM > int n = smtp_inend - smtp_inptr; >+if (n > lim) >+ n = lim; > if (n > 0) > dkim_exim_verify_feed(smtp_inptr, n); > #endif > } > > >+/* SMTP version of ungetc() >+Puts a character back in the input buffer. Only ever called once. >+ >+Arguments: >+ ch the character >+ >+Returns: the character >+*/ >+ >+int >+smtp_ungetc(int ch) >+{ >+if (smtp_inptr <= smtp_inbuffer) /* NB: NOT smtp_hasc() ! */ >+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in smtp_ungetc"); >+ >+*--smtp_inptr = ch; >+return ch; >+} >+ >+ >+/* SMTP version of feof() >+Tests for a previous EOF >+ >+Arguments: none >+Returns: non-zero if the eof flag is set >+*/ >+ >+int >+smtp_feof(void) >+{ >+return smtp_had_eof; >+} >+ >+ >+/* SMTP version of ferror() >+Tests for a previous read error, and returns with errno >+restored to what it was when the error was detected. >+ >+Arguments: none >+Returns: non-zero if the error flag is set >+*/ >+ >+int >+smtp_ferror(void) >+{ >+errno = smtp_had_error; >+return smtp_had_error; >+} >+ >+ >+/* Check if a getc will block or not */ >+ >+static BOOL >+smtp_could_getc(void) >+{ >+int fd, rc; >+fd_set fds; >+struct timeval tzero = {.tv_sec = 0, .tv_usec = 0}; >+ >+if (smtp_inptr < smtp_inend) >+ return TRUE; >+ >+fd = fileno(smtp_in); >+FD_ZERO(&fds); >+FD_SET(fd, &fds); >+rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero); >+ >+if (rc <= 0) return FALSE; /* Not ready to read */ >+rc = smtp_getc(GETC_BUFFER_UNLIMITED); >+if (rc < 0) return FALSE; /* End of file or error */ >+ >+smtp_ungetc(rc); >+return TRUE; >+} >+ >+ >+/******************************************************************************/ >+/************************************************* >+* Recheck synchronization * >+*************************************************/ >+ >+/* Synchronization checks can never be perfect because a packet may be on its >+way but not arrived when the check is done. Normally, the checks happen when >+commands are read: Exim ensures that there is no more input in the input buffer. >+In normal cases, the response to the command will be fast, and there is no >+further check. >+ >+However, for some commands an ACL is run, and that can include delays. In those >+cases, it is useful to do another check on the input just before sending the >+response. This also applies at the start of a connection. This function does >+that check by means of the select() function, as long as the facility is not >+disabled or inappropriate. A failure of select() is ignored. >+ >+When there is unwanted input, we read it so that it appears in the log of the >+error. >+ >+Arguments: none >+Returns: TRUE if all is well; FALSE if there is input pending >+*/ >+ >+static BOOL >+wouldblock_reading(void) >+{ >+#ifndef DISABLE_TLS >+if (tls_in.active.sock >= 0) >+ return !tls_could_getc(); >+#endif >+ >+return !smtp_could_getc(); >+} >+ >+static BOOL >+check_sync(void) >+{ >+if (!smtp_enforce_sync || !sender_host_address || f.sender_host_notsocket) >+ return TRUE; >+ >+return wouldblock_reading(); >+} >+ >+ >+/******************************************************************************/ >+/* Variants of the smtp_* input handling functions for use in CHUNKING mode */ >+ > /* Forward declarations */ > static inline void bdat_push_receive_functions(void); > static inline void bdat_pop_receive_functions(void); >@@ -661,7 +724,9 @@ > if (chunking_state == CHUNKING_LAST) > { > #ifndef DISABLE_DKIM >+ dkim_collect_input = dkim_save; > dkim_exim_verify_feed(NULL, 0); /* notify EOD */ >+ dkim_collect_input = 0; > #endif > return EOD; > } >@@ -743,6 +808,14 @@ > } > } > >+BOOL >+bdat_hasc(void) >+{ >+if (chunking_data_left > 0) >+ return lwr_receive_hasc(); >+return TRUE; >+} >+ > uschar * > bdat_getbuf(unsigned * len) > { >@@ -767,12 +840,8 @@ > } > > bdat_pop_receive_functions(); >- >-if (chunking_state != CHUNKING_LAST) >- { >- chunking_state = CHUNKING_OFFERED; >- DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state); >- } >+chunking_state = CHUNKING_OFFERED; >+DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state); > } > > >@@ -782,10 +851,11 @@ > /* push the current receive_* function on the "stack", and > replace them by bdat_getc(), which in turn will use the lwr_receive_* > functions to do the dirty work. */ >-if (lwr_receive_getc == NULL) >+if (!lwr_receive_getc) > { > lwr_receive_getc = receive_getc; > lwr_receive_getbuf = receive_getbuf; >+ lwr_receive_hasc = receive_hasc; > lwr_receive_ungetc = receive_ungetc; > } > else >@@ -795,50 +865,29 @@ > > receive_getc = bdat_getc; > receive_getbuf = bdat_getbuf; >+receive_hasc = bdat_hasc; > receive_ungetc = bdat_ungetc; > } > > static inline void > bdat_pop_receive_functions(void) > { >-if (lwr_receive_getc == NULL) >+if (!lwr_receive_getc) > { > DEBUG(D_receive) debug_printf("chunking double-pop receive functions\n"); > return; > } > receive_getc = lwr_receive_getc; > receive_getbuf = lwr_receive_getbuf; >+receive_hasc = lwr_receive_hasc; > receive_ungetc = lwr_receive_ungetc; > > lwr_receive_getc = NULL; > lwr_receive_getbuf = NULL; >+lwr_receive_hasc = NULL; > lwr_receive_ungetc = NULL; > } > >-/************************************************* >-* SMTP version of ungetc() * >-*************************************************/ >- >-/* Puts a character back in the input buffer. Only ever >-called once. >- >-Arguments: >- ch the character >- >-Returns: the character >-*/ >- >-int >-smtp_ungetc(int ch) >-{ >-if (smtp_inptr <= smtp_inbuffer) >- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in smtp_ungetc"); >- >-*--smtp_inptr = ch; >-return ch; >-} >- >- > int > bdat_ungetc(int ch) > { >@@ -849,62 +898,7 @@ > > > >-/************************************************* >-* SMTP version of feof() * >-*************************************************/ >- >-/* Tests for a previous EOF >- >-Arguments: none >-Returns: non-zero if the eof flag is set >-*/ >- >-int >-smtp_feof(void) >-{ >-return smtp_had_eof; >-} >- >- >- >- >-/************************************************* >-* SMTP version of ferror() * >-*************************************************/ >- >-/* Tests for a previous read error, and returns with errno >-restored to what it was when the error was detected. >- >-Arguments: none >-Returns: non-zero if the error flag is set >-*/ >- >-int >-smtp_ferror(void) >-{ >-errno = smtp_had_error; >-return smtp_had_error; >-} >- >- >- >-/************************************************* >-* Test for characters in the SMTP buffer * >-*************************************************/ >- >-/* Used at the end of a message >- >-Arguments: none >-Returns: TRUE/FALSE >-*/ >- >-BOOL >-smtp_buffered(void) >-{ >-return smtp_inptr < smtp_inend; >-} >- >- >+/******************************************************************************/ > > /************************************************* > * Write formatted string to SMTP channel * >@@ -958,15 +952,12 @@ > yield = !! string_vformat(&gs, SVFMT_TAINT_NOCHK, format, ap); > string_from_gstring(&gs); > >-DEBUG(D_receive) >- { >- uschar *msg_copy, *cr, *end; >- msg_copy = string_copy(gs.s); >- end = msg_copy + gs.ptr; >- while ((cr = Ustrchr(msg_copy, '\r')) != NULL) /* lose CRs */ >- memmove(cr, cr + 1, (end--) - cr); >- debug_printf("SMTP>> %s", msg_copy); >- } >+DEBUG(D_receive) for (const uschar * t, * s = gs.s; >+ s && (t = Ustrchr(s, '\r')); >+ s = t + 2) /* \r\n */ >+ debug_printf("%s %.*s\n", >+ s == gs.s ? "SMTP>>" : " ", >+ (int)(t - s), s); > > if (!yield) > { >@@ -983,7 +974,7 @@ > > if (fl.rcpt_in_progress) > { >- if (rcpt_smtp_response == NULL) >+ if (!rcpt_smtp_response) > rcpt_smtp_response = string_copy(big_buffer); > else if (fl.rcpt_smtp_response_same && > Ustrcmp(rcpt_smtp_response, big_buffer) != 0) >@@ -1034,6 +1025,35 @@ > > > >+/* If there's input waiting (and we're doing pipelineing) then we can pipeline >+a reponse with the one following. */ >+ >+static BOOL >+pipeline_response(void) >+{ >+if ( !smtp_enforce_sync || !sender_host_address >+ || f.sender_host_notsocket || !f.smtp_in_pipelining_advertised) >+ return FALSE; >+ >+if (wouldblock_reading()) return FALSE; >+f.smtp_in_pipelining_used = TRUE; >+return TRUE; >+} >+ >+ >+#ifndef DISABLE_PIPE_CONNECT >+static BOOL >+pipeline_connect_sends(void) >+{ >+if (!sender_host_address || f.sender_host_notsocket || !fl.pipe_connect_acceptable) >+ return FALSE; >+ >+if (wouldblock_reading()) return FALSE; >+f.smtp_in_early_pipe_used = TRUE; >+return TRUE; >+} >+#endif >+ > /************************************************* > * SMTP command read timeout * > *************************************************/ >@@ -1074,25 +1094,6 @@ > > #ifdef SUPPORT_PROXY > /************************************************* >-* Restore socket timeout to previous value * >-*************************************************/ >-/* If the previous value was successfully retrieved, restore >-it before returning control to the non-proxy routines >- >-Arguments: fd - File descriptor for input >- get_ok - Successfully retrieved previous values >- tvtmp - Time struct with previous values >- vslen - Length of time struct >-Returns: none >-*/ >-static void >-restore_socket_timeout(int fd, int get_ok, struct timeval * tvtmp, socklen_t vslen) >-{ >-if (get_ok == 0) >- (void) setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS tvtmp, vslen); >-} >- >-/************************************************* > * Check if host is required proxy host * > *************************************************/ > /* The function determines if inbound host will be a regular smtp host >@@ -1168,7 +1169,7 @@ > > while (capacity > 0) > { >- do { ret = recv(fd, to, 1, 0); } while (ret == -1 && errno == EINTR); >+ do { ret = read(fd, to, 1); } while (ret == -1 && errno == EINTR && !had_command_timeout); > if (ret == -1) > return -1; > have++; >@@ -1272,20 +1273,11 @@ > int fd = fileno(smtp_in); > const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; > uschar * iptype; /* To display debug info */ >-struct timeval tv; >-struct timeval tvtmp; > socklen_t vslen = sizeof(struct timeval); > BOOL yield = FALSE; > >-/* Save current socket timeout values */ >-get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tvtmp, &vslen); >- >-/* Proxy Protocol host must send header within a short time >-(default 3 seconds) or it's considered invalid */ >-tv.tv_sec = PROXY_NEGOTIATION_TIMEOUT_SEC; >-tv.tv_usec = PROXY_NEGOTIATION_TIMEOUT_USEC; >-if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tv, sizeof(tv)) < 0) >- goto bad; >+os_non_restarting_signal(SIGALRM, command_timeout_handler); >+ALARM(proxy_protocol_timeout); > > do > { >@@ -1293,9 +1285,9 @@ > don't do a PEEK into the data, actually slurp up enough to be > "safe". Can't take it all because TLS-on-connect clients follow > immediately with TLS handshake. */ >- ret = recv(fd, &hdr, PROXY_INITIAL_READ, 0); >+ ret = read(fd, &hdr, PROXY_INITIAL_READ); > } >- while (ret == -1 && errno == EINTR); >+ while (ret == -1 && errno == EINTR && !had_command_timeout); > > if (ret == -1) > goto proxyfail; >@@ -1309,8 +1301,8 @@ > /* First get the length fields. */ > do > { >- retmore = recv(fd, (uschar*)&hdr + ret, PROXY_V2_HEADER_SIZE - PROXY_INITIAL_READ, 0); >- } while (retmore == -1 && errno == EINTR); >+ retmore = read(fd, (uschar*)&hdr + ret, PROXY_V2_HEADER_SIZE - PROXY_INITIAL_READ); >+ } while (retmore == -1 && errno == EINTR && !had_command_timeout); > if (retmore == -1) > goto proxyfail; > ret += retmore; >@@ -1346,8 +1338,8 @@ > { > do > { >- retmore = recv(fd, (uschar*)&hdr + ret, size-ret, 0); >- } while (retmore == -1 && errno == EINTR); >+ retmore = read(fd, (uschar*)&hdr + ret, size-ret); >+ } while (retmore == -1 && errno == EINTR && !had_command_timeout); > if (retmore == -1) > goto proxyfail; > ret += retmore; >@@ -1575,7 +1567,8 @@ > should cause a synchronization failure */ > > proxyfail: >- restore_socket_timeout(fd, get_ok, &tvtmp, vslen); >+ DEBUG(D_receive) if (had_command_timeout) >+ debug_printf("Timeout while reading proxy header\n"); > > bad: > if (yield) >@@ -1591,6 +1584,7 @@ > debug_printf("Failure to extract proxied host, only QUIT allowed\n"); > } > >+ALARM(0); > return; > } > #endif >@@ -1681,12 +1675,13 @@ > || smtp_cmd_buffer[p->len] == ' ' > ) ) > { >- if (smtp_inptr < smtp_inend && /* Outstanding input */ >- p->cmd < sync_cmd_limit && /* Command should sync */ >- check_sync && /* Local flag set */ >- smtp_enforce_sync && /* Global flag set */ >- sender_host_address != NULL && /* Not local input */ >- !f.sender_host_notsocket) /* Really is a socket */ >+ if ( smtp_inptr < smtp_inend /* Outstanding input */ >+ && p->cmd < sync_cmd_limit /* Command should sync */ >+ && check_sync /* Local flag set */ >+ && smtp_enforce_sync /* Global flag set */ >+ && sender_host_address != NULL /* Not local input */ >+ && !f.sender_host_notsocket /* Really is a socket */ >+ ) > return BADSYN_CMD; > > /* The variables $smtp_command and $smtp_command_argument point into the >@@ -1735,7 +1730,8 @@ > && check_sync /* Local flag set */ > && smtp_enforce_sync /* Global flag set */ > && sender_host_address /* Not local input */ >- && !f.sender_host_notsocket) /* Really is a socket */ >+ && !f.sender_host_notsocket /* Really is a socket */ >+ ) > return BADSYN_CMD; > > return OTHER_CMD; >@@ -1761,7 +1757,7 @@ > */ > > void >-smtp_closedown(uschar *message) >+smtp_closedown(uschar * message) > { > if (!smtp_in || smtp_batched_input) return; > receive_swallow_smtp(); >@@ -1773,6 +1769,7 @@ > return; > > case QUIT_CMD: >+ f.smtp_in_quit = TRUE; > smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname); > mac_smtp_fflush(); > return; >@@ -1841,7 +1838,7 @@ > if (LOGGING(tls_cipher) && tls_in.cipher) > { > g = string_append(g, 2, US" X=", tls_in.cipher); >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifndef DISABLE_TLS_RESUME > if (LOGGING(tls_resumption) && tls_in.resumption & RESUME_USED) > g = string_catn(g, US"*", 1); > #endif >@@ -1861,7 +1858,7 @@ > static gstring * > s_connhad_log(gstring * g) > { >-uschar * sep = smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE >+const uschar * sep = smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE > ? US" C=..." : US" C="; > > for (int i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++) >@@ -1870,11 +1867,8 @@ > g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]); > sep = US","; > } >-for (int i = 0; i < smtp_ch_index; i++) >- { >+for (int i = 0; i < smtp_ch_index; i++, sep = US",") > g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]); >- sep = US","; >- } > return g; > } > >@@ -2094,30 +2088,32 @@ > raw_recipients_count = recipients_count = recipients_list_max = 0; > message_linecount = 0; > message_size = -1; >+message_body = message_body_end = NULL; > acl_added_headers = NULL; > acl_removed_headers = NULL; > f.queue_only_policy = FALSE; > rcpt_smtp_response = NULL; > fl.rcpt_smtp_response_same = TRUE; > fl.rcpt_in_progress = FALSE; >-f.deliver_freeze = FALSE; /* Can be set by ACL */ >-freeze_tell = freeze_tell_config; /* Can be set by ACL */ >-fake_response = OK; /* Can be set by ACL */ >+f.deliver_freeze = FALSE; /* Can be set by ACL */ >+freeze_tell = freeze_tell_config; /* Can be set by ACL */ >+fake_response = OK; /* Can be set by ACL */ > #ifdef WITH_CONTENT_SCAN >-f.no_mbox_unspool = FALSE; /* Can be set by ACL */ >+f.no_mbox_unspool = FALSE; /* Can be set by ACL */ > #endif >-f.submission_mode = FALSE; /* Can be set by ACL */ >+f.submission_mode = FALSE; /* Can be set by ACL */ > f.suppress_local_fixups = f.suppress_local_fixups_default; /* Can be set by ACL */ >-f.active_local_from_check = local_from_check; /* Can be set by ACL */ >-f.active_local_sender_retain = local_sender_retain; /* Can be set by ACL */ >+f.active_local_from_check = local_from_check; /* Can be set by ACL */ >+f.active_local_sender_retain = local_sender_retain; /* Can be set by ACL */ > sending_ip_address = NULL; > return_path = sender_address = NULL; > deliver_localpart_data = deliver_domain_data = > recipient_data = sender_data = NULL; /* Can be set by ACL */ >+recipient_verify_failure = NULL; > deliver_localpart_parent = deliver_localpart_orig = NULL; > deliver_domain_parent = deliver_domain_orig = NULL; > callout_address = NULL; >-submission_name = NULL; /* Can be set by ACL */ >+submission_name = NULL; /* Can be set by ACL */ > raw_sender = NULL; /* After SMTP rewrite, before qualifying */ > sender_address_unrewritten = NULL; /* Set only after verify rewrite */ > sender_verified_list = NULL; /* No senders verified */ >@@ -2171,23 +2167,7 @@ > > acl_var_m = NULL; > >-/* The message body variables use malloc store. They may be set if this is >-not the first message in an SMTP session and the previous message caused them >-to be referenced in an ACL. */ >- >-if (message_body) >- { >- store_free(message_body); >- message_body = NULL; >- } >- >-if (message_body_end) >- { >- store_free(message_body_end); >- message_body_end = NULL; >- } >- >-/* Warning log messages are also saved in malloc store. They are saved to avoid >+/* Warning log messages are saved in malloc store. They are saved to avoid > repetition in the same message, but it seems right to repeat them for different > messages. */ > >@@ -2197,7 +2177,11 @@ > acl_warn_logged = acl_warn_logged->next; > store_free(this); > } >+ >+message_tidyup(); > store_reset(reset_point); >+ >+message_start(); > return store_mark(); > } > >@@ -2274,7 +2258,7 @@ > > case MAIL_CMD: > smtp_mailcmd_count++; /* Count for no-mail log */ >- if (sender_address != NULL) >+ if (sender_address) > /* The function moan_smtp_batch() does not return. */ > moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given"); > >@@ -2417,8 +2401,9 @@ > break; > > >- case EOF_CMD: > case QUIT_CMD: >+ f.smtp_in_quit = TRUE; >+ case EOF_CMD: > done = 2; > break; > >@@ -2450,9 +2435,9 @@ > > #ifndef DISABLE_TLS > static BOOL >-smtp_log_tls_fail(uschar * errstr) >+smtp_log_tls_fail(const uschar * errstr) > { >-uschar * conn_info = smtp_get_connection_info(); >+const 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 */ >@@ -2580,7 +2565,7 @@ > > /* Allow for trailing 0 in the command and data buffers. Tainted. */ > >-smtp_cmd_buffer = store_get_perm(2*SMTP_CMD_BUFFER_SIZE + 2, TRUE); >+smtp_cmd_buffer = store_get_perm(2*SMTP_CMD_BUFFER_SIZE + 2, GET_TAINTED); > > smtp_cmd_buffer[0] = 0; > smtp_data_buffer = smtp_cmd_buffer + SMTP_CMD_BUFFER_SIZE + 1; >@@ -2601,25 +2586,21 @@ > (sender_host_address ? protocols : protocols_local) [pnormal]; > > /* Set up the buffer for inputting using direct read() calls, and arrange to >-call the local functions instead of the standard C ones. Place a NUL at the >-end of the buffer to safety-stop C-string reads from it. */ >+call the local functions instead of the standard C ones. */ > >-if (!(smtp_inbuffer = US malloc(IN_BUFFER_SIZE))) >- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP input buffer"); >-smtp_inbuffer[IN_BUFFER_SIZE-1] = '\0'; >+smtp_buf_init(); > > receive_getc = smtp_getc; > receive_getbuf = smtp_getbuf; > receive_get_cache = smtp_get_cache; >+receive_hasc = smtp_hasc; > receive_ungetc = smtp_ungetc; > receive_feof = smtp_feof; > receive_ferror = smtp_ferror; >-receive_smtp_buffered = smtp_buffered; > lwr_receive_getc = NULL; > lwr_receive_getbuf = NULL; >+lwr_receive_hasc = NULL; > lwr_receive_ungetc = NULL; >-smtp_inptr = smtp_inend = smtp_inbuffer; >-smtp_had_eof = smtp_had_error = 0; > > /* Set up the message size limit; this may be host-specific */ > >@@ -2694,7 +2675,7 @@ > { > #if OPTSTYLE == 1 > EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN; >- struct ip_options *ipopt = store_get(optlen, FALSE); >+ struct ip_options *ipopt = store_get(optlen, GET_UNTAINTED); > #elif OPTSTYLE == 2 > struct ip_opts ipoptblock; > struct ip_opts *ipopt = &ipoptblock; >@@ -2973,8 +2954,8 @@ > /* Determine whether HELO/EHLO is required for this host. The requirement > can be hard or soft. */ > >- fl.helo_required = verify_check_host(&helo_verify_hosts) == OK; >- if (!fl.helo_required) >+ fl.helo_verify_required = verify_check_host(&helo_verify_hosts) == OK; >+ if (!fl.helo_verify_required) > fl.helo_verify = verify_check_host(&helo_try_verify_hosts) == OK; > > /* Determine whether this hosts is permitted to send syntactic junk >@@ -2988,7 +2969,7 @@ > if (smtp_batched_input) return TRUE; > > /* If valid Proxy Protocol source is connecting, set up session. >- * Failure will not allow any SMTP function other than QUIT. */ >+Failure will not allow any SMTP function other than QUIT. */ > > #ifdef SUPPORT_PROXY > proxy_session = FALSE; >@@ -2997,16 +2978,16 @@ > setup_proxy_protocol_host(); > #endif > >- /* Start up TLS if tls_on_connect is set. This is for supporting the legacy >- smtps port for use with older style SSL MTAs. */ >+/* Start up TLS if tls_on_connect is set. This is for supporting the legacy >+smtps port for use with older style SSL MTAs. */ > > #ifndef DISABLE_TLS >- if (tls_in.on_connect) >- { >- if (tls_server_start(tls_require_ciphers, &user_msg) != OK) >- return smtp_log_tls_fail(user_msg); >- cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE; >- } >+if (tls_in.on_connect) >+ { >+ if (tls_server_start(&user_msg) != OK) >+ return smtp_log_tls_fail(user_msg); >+ cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE; >+ } > #endif > > /* Run the connect ACL if it exists */ >@@ -3052,7 +3033,7 @@ > > p = s + Ustrlen(s); > while (p > s && isspace(p[-1])) p--; >-*p = 0; >+s = string_copyn(s, p-s); > > /* It seems that CC:Mail is braindead, and assumes that the greeting message > is all contained in a single IP packet. The original code wrote out the >@@ -3232,7 +3213,7 @@ > > if (fl.rcpt_in_progress) > { >- if (rcpt_smtp_response == NULL) >+ if (!rcpt_smtp_response) > rcpt_smtp_response = string_copy(msg); > else if (fl.rcpt_smtp_response_same && > Ustrcmp(rcpt_smtp_response, msg) != 0) >@@ -3247,7 +3228,7 @@ > for (;;) > { > uschar *nl = Ustrchr(msg, '\n'); >- if (nl == NULL) >+ if (!nl) > { > smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg); > return; >@@ -3303,27 +3284,26 @@ > smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg, > BOOL check_valid) > { >-int n; >-int ovector[3]; >+uschar * match; >+int len; > >-if (!msg || !*msg) return; >- >-if ((n = pcre_exec(regex_smtp_code, NULL, CS *msg, Ustrlen(*msg), 0, >- PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int))) < 0) return; >+if (!msg || !*msg || !regex_match(regex_smtp_code, *msg, -1, &match)) >+ return; > >+len = Ustrlen(match); > if (check_valid && (*msg)[0] != (*code)[0]) > { > log_write(0, LOG_MAIN|LOG_PANIC, "configured error code starts with " > "incorrect digit (expected %c) in \"%s\"", (*code)[0], *msg); >- if (log_msg != NULL && *log_msg == *msg) >- *log_msg = string_sprintf("%s %s", *code, *log_msg + ovector[1]); >+ if (log_msg && *log_msg == *msg) >+ *log_msg = string_sprintf("%s %s", *code, *log_msg + len); > } > else > { > *code = *msg; >- *codelen = ovector[1]; /* Includes final space */ >+ *codelen = len; /* Includes final space */ > } >-*msg += ovector[1]; /* Chop the code off the message */ >+*msg += len; /* Chop the code off the message */ > return; > } > >@@ -3374,18 +3354,7 @@ > uschar *smtp_code; > uschar *lognl; > uschar *sender_info = US""; >-uschar *what = >-#ifdef WITH_CONTENT_SCAN >- where == ACL_WHERE_MIME ? US"during MIME ACL checks" : >-#endif >- where == ACL_WHERE_PREDATA ? US"DATA" : >- where == ACL_WHERE_DATA ? US"after DATA" : >-#ifndef DISABLE_PRDR >- where == ACL_WHERE_PRDR ? US"after DATA PRDR" : >-#endif >- smtp_cmd_data ? >- string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data) : >- string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]); >+uschar *what; > > if (drop) rc = FAIL; > >@@ -3401,19 +3370,45 @@ > this is what should be logged, so I've changed to logging the unrewritten > address to retain backward compatibility. */ > >-#ifndef WITH_CONTENT_SCAN >-if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA) >-#else >-if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA || where == ACL_WHERE_MIME) >+switch (where) >+ { >+#ifdef WITH_CONTENT_SCAN >+ case ACL_WHERE_MIME: what = US"during MIME ACL checks"; break; >+#endif >+ case ACL_WHERE_PREDATA: what = US"DATA"; break; >+ case ACL_WHERE_DATA: what = US"after DATA"; break; >+#ifndef DISABLE_PRDR >+ case ACL_WHERE_PRDR: what = US"after DATA PRDR"; break; > #endif >+ default: >+ { >+ uschar * place = smtp_cmd_data ? smtp_cmd_data : US"in \"connect\" ACL"; >+ int lim = 100; >+ >+ if (where == ACL_WHERE_AUTH) /* avoid logging auth creds */ >+ { >+ uschar * s; >+ for (s = smtp_cmd_data; *s && !isspace(*s); ) s++; >+ lim = s - smtp_cmd_data; /* atop after method */ >+ } >+ what = string_sprintf("%s %.*s", acl_wherenames[where], lim, place); >+ } >+ } >+switch (where) > { >- 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"", >- sender_host_authenticated && authenticated_id ? US":" : US"", >- sender_host_authenticated && authenticated_id ? authenticated_id : US"" >- ); >+ case ACL_WHERE_RCPT: >+ case ACL_WHERE_DATA: >+#ifdef WITH_CONTENT_SCAN >+ case ACL_WHERE_MIME: >+#endif >+ 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"", >+ sender_host_authenticated && authenticated_id ? US":" : US"", >+ sender_host_authenticated && authenticated_id ? authenticated_id : US"" >+ ); >+ break; > } > > /* If there's been a sender verification failure with a specific message, and >@@ -3775,6 +3770,12 @@ > const uschar *set_id = NULL; > int rc; > >+/* Set up globals for error messages */ >+ >+authenticator_name = au->name; >+driver_srcfile = au->srcfile; >+driver_srcline = au->srcline; >+ > /* Run the checking code, passing the remainder of the command line as > data. Initials the $auth<n> variables as empty. Initialize $0 empty and set > it as the only set numerical variable. The authenticator may set $auth<n> >@@ -3795,6 +3796,7 @@ > if (au->set_id) set_id = expand_string(au->set_id); > expand_nmax = -1; /* Reset numeric variables */ > for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; /* Reset $auth<n> */ >+driver_srcfile = authenticator_name = NULL; driver_srcline = 0; > > /* The value of authenticated_id is stored in the spool file and printed in > log lines. It must not contain binary zeros or newline characters. In >@@ -3907,17 +3909,16 @@ > smtp_quit_handler(uschar ** user_msgp, uschar ** log_msgp) > { > HAD(SCH_QUIT); >+f.smtp_in_quit = TRUE; > incomplete_transaction_log(US"QUIT"); >-if (acl_smtp_quit) >- { >- int rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp); >- if (rc == ERROR) >+if ( acl_smtp_quit >+ && acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp) >+ == ERROR) > log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s", > *log_msgp); >- } > >-#ifdef TCP_CORK >-(void) setsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_CORK, US &on, sizeof(on)); >+#ifdef EXIM_TCP_CORK >+(void) setsockopt(fileno(smtp_out), IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on)); > #endif > > if (*user_msgp) >@@ -3925,12 +3926,27 @@ > else > smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname); > >-#ifndef DISABLE_TLS >+#ifdef SERVERSIDE_CLOSE_NOWAIT >+# ifndef DISABLE_TLS > tls_close(NULL, TLS_SHUTDOWN_NOWAIT); >-#endif >+# endif > > log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", > smtp_get_connection_info()); >+#else >+ >+# ifndef DISABLE_TLS >+tls_close(NULL, TLS_SHUTDOWN_WAIT); >+# endif >+ >+log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", >+ smtp_get_connection_info()); >+ >+/* Pause, hoping client will FIN first so that they get the TIME_WAIT. >+The socket should become readble (though with no data) */ >+ >+(void) poll_one_fd(fileno(smtp_in), POLLIN, 200); >+#endif /*!SERVERSIDE_CLOSE_NOWAIT*/ > } > > >@@ -3941,9 +3957,18 @@ > incomplete_transaction_log(US"RSET"); > smtp_printf("250 Reset OK\r\n", FALSE); > cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE; >+if (chunking_state > CHUNKING_OFFERED) >+ chunking_state = CHUNKING_OFFERED; > } > > >+static int >+expand_mailmax(const uschar * s) >+{ >+if (!(s = expand_cstring(s))) >+ log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand smtp_accept_max_per_connection"); >+return *s ? Uatoi(s) : 0; >+} > > /************************************************* > * Initialize for SMTP incoming message * >@@ -4017,6 +4042,12 @@ > > if (smtp_batched_input) return smtp_setup_batch_msg(); > >+#ifdef TCP_QUICKACK >+if (smtp_in) /* Avoid pure-ACKs while in cmd pingpong phase */ >+ (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK, >+ US &off, sizeof(off)); >+#endif >+ > /* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE > value. The values are 2 larger than the required yield of the function. */ > >@@ -4074,12 +4105,6 @@ > } > #endif > >-#ifdef TCP_QUICKACK >- if (smtp_in) /* Avoid pure-ACKs while in cmd pingpong phase */ >- (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK, >- US &off, sizeof(off)); >-#endif >- > switch(smtp_read_command( > #ifndef DISABLE_PIPE_CONNECT > !fl.pipe_connect_acceptable, >@@ -4140,21 +4165,18 @@ > /* Find the name of the requested authentication mechanism. */ > > s = smtp_cmd_data; >- while ((c = *smtp_cmd_data) != 0 && !isspace(c)) >- { >+ for (; (c = *smtp_cmd_data) && !isspace(c); smtp_cmd_data++) > if (!isalnum(c) && c != '-' && c != '_') > { > done = synprot_error(L_smtp_syntax_error, 501, NULL, > US"invalid character in authentication mechanism name"); > goto COMMAND_LOOP; > } >- smtp_cmd_data++; >- } > > /* If not at the end of the line, we must be at white space. Terminate the > name and move the pointer on to any data that may be present. */ > >- if (*smtp_cmd_data != 0) >+ if (*smtp_cmd_data) > { > *smtp_cmd_data++ = 0; > while (isspace(*smtp_cmd_data)) smtp_cmd_data++; >@@ -4246,7 +4268,7 @@ > /* If sender_host_unknown is true, we have got here via the -bs interface, > not called from inetd. Otherwise, we are running an IP connection and the > host address will be set. If the helo name is the primary name of this >- host and we haven't done a reverse lookup, force one now. If helo_required >+ host and we haven't done a reverse lookup, force one now. If helo_verify_required > is set, ensure that the HELO name matches the actual host. If helo_verify > is set, do the same check, but softly. */ > >@@ -4274,19 +4296,19 @@ > tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE)); > > /* Verify if configured. This doesn't give much security, but it does >- make some people happy to be able to do it. If helo_required is set, >+ make some people happy to be able to do it. If helo_verify_required is set, > (host matches helo_verify_hosts) failure forces rejection. If helo_verify > is set (host matches helo_try_verify_hosts), it does not. This is perhaps > now obsolescent, since the verification can now be requested selectively > at ACL time. */ > > f.helo_verified = f.helo_verify_failed = sender_helo_dnssec = FALSE; >- if (fl.helo_required || fl.helo_verify) >+ if (fl.helo_verify_required || fl.helo_verify) > { > BOOL tempfail = !smtp_verify_helo(); > if (!f.helo_verified) > { >- if (fl.helo_required) >+ if (fl.helo_verify_required) > { > smtp_printf("%d %s argument does not match calling host\r\n", FALSE, > tempfail? 451 : 550, hello); >@@ -4342,11 +4364,14 @@ > fl.smtputf8_advertised = FALSE; > #endif > >+ /* Expand the per-connection message count limit option */ >+ smtp_mailcmd_max = expand_mailmax(smtp_accept_max_per_connection); >+ > smtp_code = US"250 "; /* Default response code plus space*/ > if (!user_msg) > { > /* sender_host_name below will be tainted, so save on copy when we hit it */ >- g = string_get_tainted(24, TRUE); >+ g = string_get_tainted(24, GET_TAINTED); > g = string_fmt_append(g, "%.3s %s Hello %s%s%s", > smtp_code, > smtp_active_hostname, >@@ -4401,6 +4426,19 @@ > g = string_catn(g, US"-SIZE\r\n", 7); > } > >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ if ( (smtp_mailcmd_max > 0 || recipients_max) >+ && verify_check_host(&limits_advertise_hosts) == OK) >+ { >+ g = string_fmt_append(g, "%.3s-LIMITS", smtp_code); >+ if (smtp_mailcmd_max > 0) >+ g = string_fmt_append(g, " MAILMAX=%d", smtp_mailcmd_max); >+ if (recipients_max) >+ g = string_fmt_append(g, " RCPTMAX=%d", recipients_max); >+ g = string_catn(g, US"\r\n", 2); >+ } >+#endif >+ > /* Exim does not do protocol conversion or data conversion. It is 8-bit > clean; if it has an 8-bit character in its hand, it just sends it. It > cannot therefore specify 8BITMIME and remain consistent with the RFCs. >@@ -4577,19 +4615,14 @@ > # endif > else > #endif >+ (void) fwrite(g->s, 1, g->ptr, smtp_out); > >- { >- int i = fwrite(g->s, 1, g->ptr, smtp_out); i = i; /* compiler quietening */ >- } >- DEBUG(D_receive) >- { >- uschar *cr; >- >- (void) string_from_gstring(g); >- while ((cr = Ustrchr(g->s, '\r')) != NULL) /* lose CRs */ >- memmove(cr, cr + 1, (g->ptr--) - (cr - g->s)); >- debug_printf("SMTP>> %s", g->s); >- } >+ DEBUG(D_receive) for (const uschar * t, * s = string_from_gstring(g); >+ s && (t = Ustrchr(s, '\r')); >+ s = t + 2) /* \r\n */ >+ debug_printf("%s %.*s\n", >+ s == g->s ? "SMTP>>" : " ", >+ (int)(t - s), s); > fl.helo_seen = TRUE; > > /* Reset the protocol and the state, abandoning any previous message. */ >@@ -4615,16 +4648,21 @@ > case MAIL_CMD: > HAD(SCH_MAIL); > smtp_mailcmd_count++; /* Count for limit and ratelimit */ >+ message_start(); > was_rej_mail = TRUE; /* Reset if accepted */ > env_mail_type_t * mail_args; /* Sanity check & validate args */ > >- if (fl.helo_required && !fl.helo_seen) >- { >- smtp_printf("503 HELO or EHLO required\r\n", FALSE); >- log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no " >- "HELO/EHLO given", host_and_ident(FALSE)); >- break; >- } >+ if (!fl.helo_seen) >+ if ( fl.helo_verify_required >+ || verify_check_host(&hosts_require_helo) == OK) >+ { >+ smtp_printf("503 HELO or EHLO required\r\n", FALSE); >+ log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no " >+ "HELO/EHLO given", host_and_ident(FALSE)); >+ break; >+ } >+ else if (smtp_mailcmd_max < 0) >+ smtp_mailcmd_max = expand_mailmax(smtp_accept_max_per_connection); > > if (sender_address) > { >@@ -4643,8 +4681,7 @@ > /* Check to see if the limit for messages per connection would be > exceeded by accepting further messages. */ > >- if (smtp_accept_max_per_connection > 0 && >- smtp_mailcmd_count > smtp_accept_max_per_connection) >+ if (smtp_mailcmd_max > 0 && smtp_mailcmd_count > smtp_mailcmd_max) > { > smtp_printf("421 too many messages in this connection\r\n", FALSE); > log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many " >@@ -5042,7 +5079,7 @@ > count this as a protocol error. Reset was_rej_mail so that further RCPTs > get the same treatment. */ > >- if (sender_address == NULL) >+ if (!sender_address) > { > if (f.smtp_in_pipelining_advertised && last_was_rej_mail) > { >@@ -5061,7 +5098,7 @@ > > /* Check for an operand */ > >- if (smtp_cmd_data[0] == 0) >+ if (!smtp_cmd_data[0]) > { > done = synprot_error(L_smtp_syntax_error, 501, NULL, > US"RCPT must have an address operand"); >@@ -5352,7 +5389,7 @@ > #endif > if (!discarded && recipients_count <= 0) > { >- if (fl.rcpt_smtp_response_same && rcpt_smtp_response != NULL) >+ if (fl.rcpt_smtp_response_same && rcpt_smtp_response) > { > uschar *code = US"503"; > int len = Ustrlen(rcpt_smtp_response); >@@ -5402,7 +5439,7 @@ > ACL may have delayed. To handle cutthrough delivery enforce a dummy call > to get the DATA command sent. */ > >- if (acl_smtp_predata == NULL && cutthrough.cctx.sock < 0) >+ if (!acl_smtp_predata && cutthrough.cctx.sock < 0) > rc = OK; > else > { >@@ -5555,7 +5592,7 @@ > Pipelining sync checks will normally have protected us too, unless disabled > by configuration. */ > >- if (receive_smtp_buffered()) >+ if (receive_hasc()) > { > DEBUG(D_any) > debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n"); >@@ -5582,7 +5619,7 @@ > STARTTLS that don't add to the nonmail command count. */ > > s = NULL; >- if ((rc = tls_server_start(tls_require_ciphers, &s)) == OK) >+ if ((rc = tls_server_start(&s)) == OK) > { > if (!tls_remember_esmtp) > fl.helo_seen = fl.esmtp = fl.auth_advertised = f.smtp_in_pipelining_advertised = FALSE; >@@ -5644,6 +5681,7 @@ > some sense is perhaps "right". */ > > case QUIT_CMD: >+ f.smtp_in_quit = TRUE; > user_msg = NULL; > if ( acl_smtp_quit > && ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg, >@@ -5788,7 +5826,7 @@ > etrn_command = smtp_etrn_command; > deliver_domain = smtp_cmd_data; > rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL, >- US"ETRN processing", &error); >+ FALSE, US"ETRN processing", &error); > deliver_domain = NULL; > if (!rc) > { >@@ -5865,6 +5903,7 @@ > { > DEBUG(D_exec) debug_print_argv(argv); > exim_nullstd(); /* Ensure std{in,out,err} exist */ >+ /* argv[0] should be untainted, from child_exec_exim() */ > execv(CS argv[0], (char *const *)argv); > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "exec of \"%s\" (ETRN) failed: %s", > etrn_command, strerror(errno)); >@@ -5993,7 +6032,6 @@ > COMMAND_LOOP: > last_was_rej_mail = was_rej_mail; /* Remember some last commands for */ > last_was_rcpt = was_rcpt; /* protocol error handling */ >- continue; > } > > return done - 2; /* Convert yield values */ >diff -ur exim.orig/src/smtp_out.c exim/src/smtp_out.c >--- exim.orig/src/smtp_out.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/smtp_out.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* A number of functions for driving outgoing SMTP calls. */ >@@ -27,7 +27,7 @@ > which case the function does nothing > host_af AF_INET or AF_INET6 for the outgoing IP address > addr the mail address being handled (for setting errors) >- interface point this to the interface >+ interface point this to the interface if there is one defined > msg to add to any error message > > Returns: TRUE on success, FALSE on failure, with error message >@@ -67,10 +67,10 @@ > Uskip_whitespace(&expint); > if (!*expint) return TRUE; > >-while ((iface = string_nextinlist(&expint, &sep, big_buffer, >- big_buffer_size))) >+while ((iface = string_nextinlist(&expint, &sep, NULL, 0))) > { >- if (string_is_ip_address(iface, NULL) == 0) >+ int if_af = string_is_ip_address(iface, NULL); >+ if (if_af == 0) > { > addr->transport_return = PANIC; > addr->message = string_sprintf("\"%s\" is not a valid IP " >@@ -79,11 +79,11 @@ > return FALSE; > } > >- if (((Ustrchr(iface, ':') == NULL)? AF_INET:AF_INET6) == host_af) >+ if ((if_af == 4 ? AF_INET : AF_INET6) == host_af) > break; > } > >-if (iface) *interface = string_copy(iface); >+*interface = iface; > return TRUE; > } > >@@ -153,25 +153,44 @@ > > > #ifdef TCP_FASTOPEN >+/* Try to record if TFO was attmepted and if it was successfully used. */ >+ > static void > tfo_out_check(int sock) > { >+static BOOL done_once = FALSE; >+ >+if (done_once) return; >+done_once = TRUE; >+ > # ifdef __FreeBSD__ > struct tcp_info tinfo; >-int val; >-socklen_t len = sizeof(val); >+socklen_t len = sizeof(tinfo); >+ >+/* A getsockopt TCP_FASTOPEN unfortunately returns "was-used" for a TFO/R as >+well as a TFO/C. Use what we can of the Linux hack below; reliability issues ditto. */ >+switch (tcp_out_fastopen) >+ { >+ case TFO_ATTEMPTED_NODATA: >+ if ( getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0 >+ && tinfo.tcpi_state == TCPS_SYN_SENT >+ && tinfo.__tcpi_unacked > 0 >+ ) >+ { >+ DEBUG(D_transport|D_v) >+ debug_printf("TCP_FASTOPEN tcpi_unacked %d\n", tinfo.__tcpi_unacked); >+ tcp_out_fastopen = TFO_USED_NODATA; >+ } >+ break; >+ /* >+ case TFO_ATTEMPTED_DATA: >+ case TFO_ATTEMPTED_DATA: >+ if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA) XXX no equvalent as of 12.2 >+ */ >+ } > >-/* The observability as of 12.1 is not useful as a client, only telling us that >-a TFO option was used on SYN. It could have been a TFO-R, or ignored by the >-server. */ >- >-/* >-if (tcp_out_fastopen == TFO_ATTEMPTED_NODATA || tcp_out_fastopen == TFO_ATTEMPTED_DATA) >- if (getsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN, &val, &len) == 0 && val != 0) {} >-*/ > switch (tcp_out_fastopen) > { >- case TFO_ATTEMPTED_NODATA: tcp_out_fastopen = TFO_USED_NODATA; break; > case TFO_ATTEMPTED_DATA: tcp_out_fastopen = TFO_USED_DATA; break; > default: break; /* compiler quietening */ > } >@@ -233,35 +252,21 @@ > #endif > > >-/* Arguments as for smtp_connect(), plus >- early_data if non-NULL, idenmpotent data to be sent - >- preferably in the TCP SYN segment >- >-Returns: connected socket number, or -1 with errno set >+/* Create and bind a socket, given the connect-args. >+Update those with the state. Return the fd, or -1 with errno set. > */ > > int >-smtp_sock_connect(host_item * host, int host_af, int port, uschar * interface, >- transport_instance * tb, int timeout, const blob * early_data) >+smtp_boundsock(smtp_connect_args * sc) > { >+transport_instance * tb = sc->tblock; > smtp_transport_options_block * ob = > (smtp_transport_options_block *)tb->options_block; > const uschar * dscp = ob->dscp; >-int dscp_value; >-int dscp_level; >-int dscp_option; >-int sock; >-int save_errno = 0; >-const blob * fastopen_blob = NULL; >+int sock, dscp_value, dscp_level, dscp_option; > >- >-#ifndef DISABLE_EVENT >-deliver_host_address = host->address; >-deliver_host_port = port; >-if (event_raise(tb->event_action, US"tcp:connect", NULL)) return -1; >-#endif >- >-if ((sock = ip_socket(SOCK_STREAM, host_af)) < 0) return -1; >+if ((sock = ip_socket(SOCK_STREAM, sc->host_af)) < 0) >+ return -1; > > /* Set TCP_NODELAY; Exim does its own buffering. */ > >@@ -272,7 +277,7 @@ > /* Set DSCP value, if we can. For now, if we fail to set the value, we don't > bomb out, just log it and continue in default traffic class. */ > >-if (dscp && dscp_lookup(dscp, host_af, &dscp_level, &dscp_option, &dscp_value)) >+if (dscp && dscp_lookup(dscp, sc->host_af, &dscp_level, &dscp_option, &dscp_value)) > { > HDEBUG(D_transport|D_acl|D_v) > debug_printf_indent("DSCP \"%s\"=%x ", dscp, dscp_value); >@@ -281,7 +286,7 @@ > debug_printf_indent("failed to set DSCP: %s ", strerror(errno)); > /* If the kernel supports IPv4 and IPv6 on an IPv6 socket, we need to set the > option for both; ignore failures here */ >- if (host_af == AF_INET6 && >+ if (sc->host_af == AF_INET6 && > dscp_lookup(dscp, AF_INET, &dscp_level, &dscp_option, &dscp_value)) > (void) setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value)); > } >@@ -289,64 +294,122 @@ > /* Bind to a specific interface if requested. Caller must ensure the interface > is the same type (IPv4 or IPv6) as the outgoing address. */ > >-if (interface && ip_bind(sock, host_af, interface, 0) < 0) >+if (sc->interface) > { >- save_errno = errno; >- HDEBUG(D_transport|D_acl|D_v) >- debug_printf_indent("unable to bind outgoing SMTP call to %s: %s", interface, >- strerror(errno)); >+ union sockaddr_46 interface_sock; >+ EXIM_SOCKLEN_T size = sizeof(interface_sock); >+ >+ if ( ip_bind(sock, sc->host_af, sc->interface, 0) < 0 >+ || getsockname(sock, (struct sockaddr *) &interface_sock, &size) < 0 >+ ) >+ { >+ HDEBUG(D_transport|D_acl|D_v) >+ debug_printf_indent("unable to bind outgoing SMTP call to %s: %s", sc->interface, >+ strerror(errno)); >+ close(sock); >+ return -1; >+ } >+ sending_ip_address = host_ntoa(-1, &interface_sock, NULL, &sending_port); > } > >+sc->sock = sock; >+return sock; >+} >+ >+ >+/* Arguments: >+ host host item containing name and address and port >+ host_af AF_INET or AF_INET6 >+ port TCP port number >+ interface outgoing interface address or NULL >+ tb transport >+ timeout timeout value or 0 >+ early_data if non-NULL, idempotent data to be sent - >+ preferably in the TCP SYN segment >+ Special case: non-NULL but with NULL blob.data - caller is >+ client-data-first (eg. TLS-on-connect) and a lazy-TCP-connect is >+ acceptable. >+ >+Returns: connected socket number, or -1 with errno set >+*/ >+ >+int >+smtp_sock_connect(smtp_connect_args * sc, int timeout, const blob * early_data) >+{ >+smtp_transport_options_block * ob = >+ (smtp_transport_options_block *)sc->tblock->options_block; >+int sock; >+int save_errno = 0; >+const blob * fastopen_blob = NULL; >+ >+ >+#ifndef DISABLE_EVENT >+deliver_host_address = sc->host->address; >+deliver_host_port = sc->host->port; >+if (event_raise(sc->tblock->event_action, US"tcp:connect", NULL, &errno)) return -1; >+#endif >+ >+if ( (sock = sc->sock) < 0 >+ && (sock = smtp_boundsock(sc)) < 0) >+ save_errno = errno; >+sc->sock = -1; >+ > /* Connect to the remote host, and add keepalive to the socket before returning > it, if requested. If the build supports TFO, request it - and if the caller > requested some early-data then include that in the TFO request. If there is > early-data but no TFO support, send it after connecting. */ > >-else >+if (!save_errno) > { > #ifdef TCP_FASTOPEN >- if (verify_check_given_host(CUSS &ob->hosts_try_fastopen, host) == OK) >- fastopen_blob = early_data ? early_data : &tcp_fastopen_nodata; >+ /* See if TCP Fast Open usable. Default is a traditional 3WHS connect */ >+ if (verify_check_given_host(CUSS &ob->hosts_try_fastopen, sc->host) == OK) >+ { >+ if (!early_data) >+ fastopen_blob = &tcp_fastopen_nodata; /* TFO, with no data */ >+ else if (early_data->data) >+ fastopen_blob = early_data; /* TFO, with data */ >+# ifdef TCP_FASTOPEN_CONNECT >+ else >+ { /* expecting client data */ >+ debug_printf(" set up lazy-connect\n"); >+ setsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, US &on, sizeof(on)); >+ /* fastopen_blob = NULL; lazy TFO, triggered by data write */ >+ } >+# endif >+ } > #endif > >- if (ip_connect(sock, host_af, host->address, port, timeout, fastopen_blob) < 0) >+ if (ip_connect(sock, sc->host_af, sc->host->address, sc->host->port, timeout, fastopen_blob) < 0) > save_errno = errno; > else if (early_data && !fastopen_blob && early_data->data && early_data->len) > { >+ /* We had some early-data to send, but couldn't do TFO */ > HDEBUG(D_transport|D_acl|D_v) > debug_printf("sending %ld nonTFO early-data\n", (long)early_data->len); > >-#ifdef TCP_QUICKACK >+#ifdef TCP_QUICKACK_notdef > (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off)); > #endif > if (send(sock, early_data->data, early_data->len, 0) < 0) > save_errno = errno; > } >+#ifdef TCP_QUICKACK_notdef >+ /* Under TFO (with openssl & pipe-conn; testcase 4069, as of >+ 5.10.8-100.fc32.x86_64) this seems to be inop. >+ Perhaps overwritten when we (client) go -> ESTABLISHED on seeing the 3rd-ACK? >+ For that case, added at smtp_reap_banner(). */ >+ (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off)); >+#endif > } > >-/* Either bind() or connect() failed */ >- >-if (save_errno != 0) >- { >- HDEBUG(D_transport|D_acl|D_v) >- { >- debug_printf_indent(" failed: %s", CUstrerror(save_errno)); >- if (save_errno == ETIMEDOUT) >- debug_printf(" (timeout=%s)", readconf_printtime(timeout)); >- debug_printf("\n"); >- } >- (void)close(sock); >- errno = save_errno; >- return -1; >- } >- >-/* Both bind() and connect() succeeded, and any early-data */ >- >-else >+if (!save_errno) > { > union sockaddr_46 interface_sock; > EXIM_SOCKLEN_T size = sizeof(interface_sock); > >+ /* Both bind() and connect() succeeded, and any early-data */ >+ > HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" connected\n"); > if (getsockname(sock, (struct sockaddr *)(&interface_sock), &size) == 0) > sending_ip_address = host_ntoa(-1, &interface_sock, NULL, &sending_port); >@@ -358,12 +421,25 @@ > return -1; > } > >- if (ob->keepalive) ip_keepalive(sock, host->address, TRUE); >+ if (ob->keepalive) ip_keepalive(sock, sc->host->address, TRUE); > #ifdef TCP_FASTOPEN > tfo_out_check(sock); > #endif > return sock; > } >+ >+/* Either bind() or connect() failed */ >+ >+HDEBUG(D_transport|D_acl|D_v) >+ { >+ debug_printf_indent(" failed: %s", CUstrerror(save_errno)); >+ if (save_errno == ETIMEDOUT) >+ debug_printf(" (timeout=%s)", readconf_printtime(timeout)); >+ debug_printf("\n"); >+ } >+(void)close(sock); >+errno = save_errno; >+return -1; > } > > >@@ -396,6 +472,9 @@ > Arguments: > sc details for making connection: host, af, interface, transport > early_data if non-NULL, data to be sent - preferably in the TCP SYN segment >+ Special case: non-NULL but with NULL blob.data - caller is >+ client-data-first (eg. TLS-on-connect) and a lazy-TCP-connect is >+ acceptable. > > Returns: connected socket number, or -1 with errno set > */ >@@ -448,8 +527,7 @@ > } > #endif > >-return smtp_sock_connect(sc->host, sc->host_af, port, sc->interface, >- sc->tblock, ob->connect_timeout, early_data); >+return smtp_sock_connect(sc, ob->connect_timeout, early_data); > } > > >@@ -474,13 +552,21 @@ > int rc; > int n = outblock->ptr - outblock->buffer; > BOOL more = mode == SCMD_MORE; >+client_conn_ctx * cctx; > > HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes%s\n", n, > more ? " (more expected)" : ""); > >+if (!(cctx = outblock->cctx)) >+ { >+ log_write(0, LOG_MAIN|LOG_PANIC, "null conn-context pointer"); >+ errno = 0; >+ return FALSE; >+ } >+ > #ifndef DISABLE_TLS >-if (outblock->cctx->tls_ctx) >- rc = tls_write(outblock->cctx->tls_ctx, outblock->buffer, n, more); >+if (cctx->tls_ctx) /*XXX have seen a null cctx here, rvfy sending QUIT, hence check above */ >+ rc = tls_write(cctx->tls_ctx, outblock->buffer, n, more); > else > #endif > >@@ -494,7 +580,7 @@ > requirement: TFO with data can, in rare cases, replay the data to the > receiver. */ > >- if ( (outblock->cctx->sock = smtp_connect(outblock->conn_args, &early_data)) >+ if ( (cctx->sock = smtp_connect(outblock->conn_args, &early_data)) > < 0) > return FALSE; > outblock->conn_args = NULL; >@@ -502,7 +588,7 @@ > } > else > { >- rc = send(outblock->cctx->sock, outblock->buffer, n, >+ rc = send(cctx->sock, outblock->buffer, n, > #ifdef MSG_MORE > more ? MSG_MORE : 0 > #else >@@ -517,7 +603,7 @@ > https://bugzilla.redhat.com/show_bug.cgi?id=1803806 */ > > if (!more) >- setsockopt(outblock->cctx->sock, IPPROTO_TCP, TCP_CORK, &off, sizeof(off)); >+ setsockopt(cctx->sock, IPPROTO_TCP, TCP_CORK, &off, sizeof(off)); > #endif > } > } >@@ -535,6 +621,22 @@ > > > >+/* This might be called both due to callout and then from delivery. >+Use memory that will not be released between those phases. >+*/ >+static void >+smtp_debug_resp(const uschar * buf) >+{ >+#ifndef DISABLE_CLIENT_CMD_LOG >+int old_pool = store_pool; >+store_pool = POOL_PERM; >+client_cmd_log = string_append_listele_n(client_cmd_log, ':', buf, >+ buf[3] == ' ' ? 3 : 4); >+store_pool = old_pool; >+#endif >+} >+ >+ > /************************************************* > * Write SMTP command * > *************************************************/ >@@ -556,7 +658,7 @@ > */ > > int >-smtp_write_command(void * sx, int mode, const char *format, ...) >+smtp_write_command(void * sx, int mode, const char * format, ...) > { > smtp_outblock * outblock = &((smtp_context *)sx)->outblock; > int rc = 0; >@@ -609,10 +711,10 @@ > while (!isspace(*p)) p++; > while (isspace(*p)) p++; > } >- while (*p != 0) *p++ = '*'; >+ while (*p) *p++ = '*'; > } > >- HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> %s\n", big_buffer); >+ smtp_debug_cmd(big_buffer, mode); > } > > if (mode != SCMD_BUFFER) >@@ -750,8 +852,10 @@ > uschar * ptr = buffer; > int count = 0; > time_t timelimit = time(NULL) + timeout; >+BOOL yield = FALSE; > > errno = 0; /* Ensure errno starts out zero */ >+buffer[0] = '\0'; > > #ifndef DISABLE_PIPE_CONNECT > if (sx->pending_BANNER || sx->pending_EHLO) >@@ -760,9 +864,8 @@ > if ((rc = smtp_reap_early_pipe(sx, &count)) != OK) > { > DEBUG(D_transport) debug_printf("failed reaping pipelined cmd responsess\n"); >- buffer[0] = '\0'; > if (rc == DEFER) errno = ERRNO_TLSFAILURE; >- return FALSE; >+ goto out; > } > } > #endif >@@ -793,7 +896,7 @@ > (ptr[3] != '-' && ptr[3] != ' ' && ptr[3] != 0)) > { > errno = ERRNO_SMTPFORMAT; /* format error */ >- return FALSE; >+ goto out; > } > > /* If the line we have just read is a terminal line, line, we are done. >@@ -811,7 +914,7 @@ > } > > #ifdef TCP_FASTOPEN >- tfo_out_check(sx->cctx.sock); >+tfo_out_check(sx->cctx.sock); > #endif > > /* Return a value that depends on the SMTP return code. On some systems a >@@ -821,7 +924,11 @@ > timeouts, lost connections, etc. */ > > errno = 0; >-return buffer[0] == okdigit; >+yield = buffer[0] == okdigit; >+ >+out: >+ smtp_debug_resp(buffer); >+ return yield; > } > > /* End of smtp_out.c */ >diff -ur exim.orig/src/spam.c exim/src/spam.c >--- exim.orig/src/spam.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/spam.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,9 +2,10 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015 >+/* >+ * Copyright (c) The Exim Maintainers 2016 - 2022 >+ * Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015 > * License: GPL >- * Copyright (c) The Exim Maintainers 2016 - 2020 > */ > > /* Code for calling spamassassin's spamd. Called from acl.c. */ >@@ -35,7 +36,7 @@ > spamd->weight = SPAMD_WEIGHT; > spamd->timeout = SPAMD_TIMEOUT; > spamd->retry = 0; >-spamd->priority = 1; >+spamd->priority = SPAMD_PRIORITY; > return 0; > } > >@@ -139,21 +140,11 @@ > spamd_address_container * sd; > long weights; > unsigned pri; >-static BOOL srandomed = FALSE; > > /* speedup, if we have only 1 server */ > if (num_servers == 1) > return (spamds[0]->is_failed ? -1 : 0); > >-/* init ranmod */ >-if (!srandomed) >- { >- struct timeval tv; >- gettimeofday(&tv, NULL); >- srandom((unsigned int)(tv.tv_usec/1000)); >- srandomed = TRUE; >- } >- > /* scan for highest pri */ > for (pri = 0, i = 0; i < num_servers; i++) > { >@@ -170,7 +161,7 @@ > if (weights == 0) /* all servers failed */ > return -1; > >-for (long rnd = random() % weights, i = 0; i < num_servers; i++) >+for (long rnd = random_number(weights), i = 0; i < num_servers; i++) > { > sd = spamds[i]; > if (!sd->is_failed && sd->priority == pri) >@@ -204,12 +195,6 @@ > int override = 0; > time_t start; > size_t read, wrote; >-#ifndef NO_POLL_H >-struct pollfd pollfd; >-#else /* Patch posted by Erik ? for OS X */ >-struct timeval select_tv; /* and applied by PH */ >-fd_set select_fd; >-#endif > uschar *spamd_address_work; > spamd_address_container * sd; > >@@ -224,8 +209,7 @@ > } > > /* if username is "0" or "false", do not scan */ >-if ( (Ustrcmp(user_name,"0") == 0) || >- (strcmpic(user_name,US"false") == 0) ) >+if (Ustrcmp(user_name, "0") == 0 || strcmpic(user_name, US"false") == 0) > return FAIL; > > /* if there is an additional option, check if it is "true" */ >@@ -234,19 +218,15 @@ > override = 1; > > /* expand spamd_address if needed */ >-if (*spamd_address == '$') >+if (*spamd_address != '$') >+ spamd_address_work = spamd_address; >+else if (!(spamd_address_work = expand_string(spamd_address))) > { >- spamd_address_work = expand_string(spamd_address); >- if (spamd_address_work == NULL) >- { >- log_write(0, LOG_MAIN|LOG_PANIC, >- "%s spamd_address starts with $, but expansion failed: %s", >- loglabel, expand_string_message); >- return DEFER; >- } >+ log_write(0, LOG_MAIN|LOG_PANIC, >+ "%s spamd_address starts with $, but expansion failed: %s", >+ loglabel, expand_string_message); >+ return DEFER; > } >-else >- spamd_address_work = spamd_address; > > DEBUG(D_acl) debug_printf_indent("spamd: addrlist '%s'\n", spamd_address_work); > >@@ -290,7 +270,7 @@ > uschar * s; > > DEBUG(D_acl) debug_printf_indent("spamd: addr entry '%s'\n", address); >- sd = store_get(sizeof(spamd_address_container), FALSE); >+ sd = store_get(sizeof(spamd_address_container), GET_UNTAINTED); > > for (sublist = address, args = 0, spamd_param_init(sd); > (s = string_nextinlist(&sublist, &sublist_sep, NULL, 0)); >@@ -410,19 +390,19 @@ > } > > /* now send the file */ >-/* spamd sometimes accepts connections but doesn't read data off >- * the connection. We make the file descriptor non-blocking so >- * that the write will only write sufficient data without blocking >- * and we poll the descriptor to make sure that we can write without >- * blocking. Short writes are gracefully handled and if the whole >- * transaction takes too long it is aborted. >- * Note: poll() is not supported in OSX 10.2 and is reported to be >- * broken in more recent versions (up to 10.4). >+/* spamd sometimes accepts connections but doesn't read data off the connection. >+We make the file descriptor non-blocking so that the write will only write >+sufficient data without blocking and we poll the descriptor to make sure that we >+can write without blocking. Short writes are gracefully handled and if the >+whole transaction takes too long it is aborted. >+ >+Note: poll() is not supported in OSX 10.2 and is reported to be broken in more >+ recent versions (up to 10.4). Workaround using select() removed 2021/11 (jgh). > */ >-#ifndef NO_POLL_H >-pollfd.fd = spamd_cctx.sock; >-pollfd.events = POLLOUT; >+#ifdef NO_POLL_H >+# error Need poll(2) support > #endif >+ > (void)fcntl(spamd_cctx.sock, F_SETFL, O_NONBLOCK); > do > { >@@ -431,19 +411,7 @@ > { > offset = 0; > again: >-#ifndef NO_POLL_H >- result = poll(&pollfd, 1, 1000); >- >-/* Patch posted by Erik ? for OS X and applied by PH */ >-#else >- select_tv.tv_sec = 1; >- select_tv.tv_usec = 0; >- FD_ZERO(&select_fd); >- FD_SET(spamd_cctx.sock, &select_fd); >- result = select(spamd_cctx.sock+1, NULL, &select_fd, NULL, &select_tv); >-#endif >-/* End Erik's patch */ >- >+ result = poll_one_fd(spamd_cctx.sock, POLLOUT, 1000); > if (result == -1 && errno == EINTR) > goto again; > else if (result < 1) >diff -ur exim.orig/src/spam.h exim/src/spam.h >--- exim.orig/src/spam.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/spam.h 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > > /* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015 */ >+/* Copyright (c) The Exim Maintainers 2021 */ > /* License: GPL */ > > /* spam defines */ >@@ -21,8 +22,9 @@ > # define SHUT_WR 1 > #endif > >-/* default weight */ >+/* Defaults */ > #define SPAMD_WEIGHT 1 >+#define SPAMD_PRIORITY 1 > > typedef struct spamd_address_container > { >diff -ur exim.orig/src/spf.c exim/src/spf.c >--- exim.orig/src/spf.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/spf.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,9 +3,9 @@ > *************************************************/ > > /* SPF support. >+ Copyright (c) The Exim Maintainers 2015 - 2022 > Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014 > License: GPL >- Copyright (c) The Exim Maintainers 2015 - 2020 > */ > > /* Code for calling spf checks via libspf-alt. Called from acl.c. */ >@@ -34,15 +34,17 @@ > SPF_dns_rr_t * spf_nxdomain = NULL; > > >-void >-spf_lib_version_report(FILE * fp) >+gstring * >+spf_lib_version_report(gstring * g) > { > int maj, min, patch; >+ > SPF_get_lib_version(&maj, &min, &patch); >-fprintf(fp, "Library version: spf2: Compile: %d.%d.%d\n", >+g = string_fmt_append(g, "Library version: spf2: Compile: %d.%d.%d\n", > SPF_LIB_VERSION_MAJOR, SPF_LIB_VERSION_MINOR, SPF_LIB_VERSION_PATCH); >-fprintf(fp, " Runtime: %d.%d.%d\n", >+g = string_fmt_append(g, " Runtime: %d.%d.%d\n", > maj, min, patch); >+return g; > } > > >@@ -80,6 +82,7 @@ > HDEBUG(D_host_lookup) debug_printf("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; > } > >@@ -100,6 +103,7 @@ > if (found == 0) > { > SPF_dns_rr_dup(&spfrr, &srr); >+ store_free_dns_answer(dnsa); > return spfrr; > } > >@@ -171,6 +175,7 @@ > > /* spfrr->rr must have been malloc()d for this */ > SPF_dns_rr_dup(&spfrr, &srr); >+store_free_dns_answer(dnsa); > return spfrr; > } > >@@ -248,7 +253,7 @@ > if (!(s = expand_string(spf_smtp_comment_template))) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "expansion of spf_smtp_comment_template failed"); > >-SPF_server_set_explanation(spf_server, s, &spf_response); >+SPF_server_set_explanation(spf_server, CCS s, &spf_response); > if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response))); > >@@ -402,8 +407,12 @@ > g = string_cat(g, US" (best guess record for domain)"); > > s = expand_string(US"$sender_address_domain"); >+if (s && *s) >+ return string_append(g, 2, US" smtp.mailfrom=", s); >+ >+s = sender_helo_name; > return s && *s >- ? string_append(g, 2, US" smtp.mailfrom=", s) >+ ? string_append(g, 2, US" smtp.helo=", s) > : string_cat(g, US" smtp.mailfrom=<>"); > } > >diff -ur exim.orig/src/spf.h exim/src/spf.h >--- exim.orig/src/spf.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/spf.h 2022-06-23 16:41:10.000000000 +0300 >@@ -3,9 +3,9 @@ > *************************************************/ > > /* Experimental SPF support. >+ Copyright (c) The Exim Maintainers 2016 - 2022 > Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 > License: GPL >- Copyright (c) The Exim Maintainers 2016 - 2020 > */ > > #ifdef SUPPORT_SPF >@@ -25,7 +25,7 @@ > } spf_result_id; > > /* prototypes */ >-void spf_lib_version_report(FILE *); >+gstring * spf_lib_version_report(gstring *); > BOOL spf_init(void); > BOOL spf_conn_init(uschar *, uschar *); > int spf_process(const uschar **, uschar *, int); >diff -ur exim.orig/src/spool_in.c exim/src/spool_in.c >--- exim.orig/src/spool_in.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/spool_in.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions for reading spool files. When compiling for a utility (eximon), >@@ -186,7 +186,7 @@ > > if (n < 5) return FALSE; /* malformed line */ > buffer[n-1] = 0; /* Remove \n */ >-node = store_get(sizeof(tree_node) + n - 3, TRUE); /* rcpt names tainted */ >+node = store_get(sizeof(tree_node) + n - 3, GET_TAINTED); /* rcpt names tainted */ > *connect = node; > Ustrcpy(node->name, buffer + 3); > node->data.ptr = NULL; >@@ -300,6 +300,9 @@ > message_utf8_downconvert = 0; > #endif > >+#ifndef COMPILE_UTILITY >+debuglog_name[0] = '\0'; >+#endif > dsn_ret = 0; > dsn_envid = NULL; > } >@@ -442,7 +445,7 @@ > if (n < 3 || big_buffer[0] != '<' || big_buffer[n-2] != '>') > goto SPOOL_FORMAT_ERROR; > >-sender_address = store_get(n-2, TRUE); /* tainted */ >+sender_address = store_get(n-2, GET_TAINTED); > Ustrncpy(sender_address, big_buffer+1, n-3); > sender_address[n-3] = 0; > >@@ -451,6 +454,8 @@ > if (sscanf(CS big_buffer, TIME_T_FMT " %d", &received_time.tv_sec, &warning_count) != 2) > goto SPOOL_FORMAT_ERROR; > received_time.tv_usec = 0; >+received_time_complete = received_time; >+ > > message_age = time(NULL) - received_time.tv_sec; > #ifndef COMPILE_UTILITY >@@ -477,11 +482,13 @@ > that we don't recognize. Otherwise it wouldn't be possible to back off a new > version that left new-style flags written on the spool. > >-If the line starts with "--" the content of the variable is tainted. */ >+If the line starts with "--" the content of the variable is tainted. >+If the line start "--(<lookuptype>)" it is also quoted for the given <lookuptype>. >+*/ > > for (;;) > { >- BOOL tainted; >+ const void * proto_mem; > uschar * var; > const uschar * p; > >@@ -489,8 +496,23 @@ > if (big_buffer[0] != '-') break; > big_buffer[Ustrlen(big_buffer)-1] = 0; > >- tainted = big_buffer[1] == '-'; >- var = big_buffer + (tainted ? 2 : 1); >+ proto_mem = big_buffer[1] == '-' ? GET_TAINTED : GET_UNTAINTED; >+ var = big_buffer + (proto_mem == GET_UNTAINTED ? 1 : 2); >+ if (*var == '(') /* marker for quoted value */ >+ { >+ uschar * s; >+ int idx; >+ for (s = ++var; *s != ')'; ) s++; >+#ifndef COMPILE_UTILITY >+ if ((idx = search_findtype(var, s - var)) < 0) >+ { >+ DEBUG(D_any) debug_printf("Unrecognised quoter %.*s\n", (int)(s - var), var+1); >+ goto SPOOL_FORMAT_ERROR; >+ } >+ proto_mem = store_get_quoted(1, GET_TAINTED, idx); >+#endif /* COMPILE_UTILITY */ >+ var = s + 1; >+ } > p = var + 1; > > switch(*var) >@@ -514,7 +536,7 @@ > (int)(endptr - var - 5), var + 5); > if (sscanf(CS endptr, " %d", &count) != 1) goto SPOOL_FORMAT_ERROR; > node = acl_var_create(name); >- node->data.ptr = store_get(count + 1, tainted); >+ node->data.ptr = store_get(count + 1, proto_mem); > if (fread(node->data.ptr, 1, count+1, fp) < count) goto SPOOL_READ_ERROR; > ((uschar*)node->data.ptr)[count] = 0; > } >@@ -525,11 +547,11 @@ > f.allow_unqualified_sender = TRUE; > > else if (Ustrncmp(p, "uth_id", 6) == 0) >- authenticated_id = string_copy_taint(var + 8, tainted); >+ authenticated_id = string_copy_taint(var + 8, proto_mem); > else if (Ustrncmp(p, "uth_sender", 10) == 0) >- authenticated_sender = string_copy_taint(var + 12, tainted); >+ authenticated_sender = string_copy_taint(var + 12, proto_mem); > else if (Ustrncmp(p, "ctive_hostname", 14) == 0) >- smtp_active_hostname = string_copy_taint(var + 16, tainted); >+ smtp_active_hostname = string_copy_taint(var + 16, proto_mem); > > /* For long-term backward compatibility, we recognize "-acl", which was > used before the number of ACL variables changed from 10 to 20. This was >@@ -553,7 +575,7 @@ > else > (void) string_format(name, sizeof(name), "%c%u", 'm', index - 10); > node = acl_var_create(name); >- node->data.ptr = store_get(count + 1, tainted); >+ node->data.ptr = store_get(count + 1, proto_mem); > /* We sanity-checked the count, so disable the Coverity error */ > /* coverity[tainted_data] */ > if (fread(node->data.ptr, 1, count+1, fp) < count) goto SPOOL_READ_ERROR; >@@ -568,18 +590,23 @@ > body_zerocount = Uatoi(var + 14); > #ifdef EXPERIMENTAL_BRIGHTMAIL > else if (Ustrncmp(p, "mi_verdicts ", 12) == 0) >- bmi_verdicts = string_copy_taint(var + 13, tainted); >+ bmi_verdicts = string_copy_taint(var + 13, proto_mem); > #endif > break; > > case 'd': > if (Ustrcmp(p, "eliver_firsttime") == 0) > f.deliver_firsttime = TRUE; >- /* Check if the dsn flags have been set in the header file */ > else if (Ustrncmp(p, "sn_ret", 6) == 0) > dsn_ret= atoi(CS var + 7); > else if (Ustrncmp(p, "sn_envid", 8) == 0) >- dsn_envid = string_copy_taint(var + 10, tainted); >+ dsn_envid = string_copy_taint(var + 10, proto_mem); >+#ifndef COMPILE_UTILITY >+ else if (Ustrncmp(p, "ebug_selector ", 14) == 0) >+ debug_selector = strtol(CS var + 15, NULL, 0); >+ else if (Ustrncmp(p, "ebuglog_name ", 13) == 0) >+ debug_logging_from_spool(var + 14); >+#endif > break; > > case 'f': >@@ -597,13 +624,13 @@ > else if (Ustrcmp(p, "ost_lookup_failed") == 0) > host_lookup_failed = TRUE; > else if (Ustrncmp(p, "ost_auth_pubname", 16) == 0) >- sender_host_auth_pubname = string_copy_taint(var + 18, tainted); >+ sender_host_auth_pubname = string_copy_taint(var + 18, proto_mem); > else if (Ustrncmp(p, "ost_auth", 8) == 0) >- sender_host_authenticated = string_copy_taint(var + 10, tainted); >+ sender_host_authenticated = string_copy_taint(var + 10, proto_mem); > else if (Ustrncmp(p, "ost_name", 8) == 0) >- sender_host_name = string_copy_taint(var + 10, tainted); >+ sender_host_name = string_copy_taint(var + 10, proto_mem); > else if (Ustrncmp(p, "elo_name", 8) == 0) >- sender_helo_name = string_copy_taint(var + 10, tainted); >+ sender_helo_name = string_copy_taint(var + 10, proto_mem); > > /* We now record the port number after the address, separated by a > dot. For compatibility during upgrading, do nothing if there >@@ -612,7 +639,7 @@ > else if (Ustrncmp(p, "ost_address", 11) == 0) > { > sender_host_port = host_address_extract_port(var + 13); >- sender_host_address = string_copy_taint(var + 13, tainted); >+ sender_host_address = string_copy_taint(var + 13, proto_mem); > } > break; > >@@ -620,10 +647,10 @@ > if (Ustrncmp(p, "nterface_address", 16) == 0) > { > interface_port = host_address_extract_port(var + 18); >- interface_address = string_copy_taint(var + 18, tainted); >+ interface_address = string_copy_taint(var + 18, proto_mem); > } > else if (Ustrncmp(p, "dent", 4) == 0) >- sender_ident = string_copy_taint(var + 6, tainted); >+ sender_ident = string_copy_taint(var + 6, proto_mem); > break; > > case 'l': >@@ -633,7 +660,7 @@ > f.local_error_message = TRUE; > #ifdef HAVE_LOCAL_SCAN > else if (Ustrncmp(p, "ocal_scan ", 10) == 0) >- local_scan_data = string_copy_taint(var + 11, tainted); >+ local_scan_data = string_copy_taint(var + 11, proto_mem); > #endif > break; > >@@ -650,12 +677,24 @@ > > case 'r': > if (Ustrncmp(p, "eceived_protocol", 16) == 0) >- received_protocol = string_copy_taint(var + 18, tainted); >+ received_protocol = string_copy_taint(var + 18, proto_mem); > else if (Ustrncmp(p, "eceived_time_usec", 17) == 0) > { > unsigned usec; > if (sscanf(CS var + 20, "%u", &usec) == 1) >+ { > received_time.tv_usec = usec; >+ if (!received_time_complete.tv_sec) received_time_complete.tv_usec = usec; >+ } >+ } >+ else if (Ustrncmp(p, "eceived_time_complete", 21) == 0) >+ { >+ unsigned sec, usec; >+ if (sscanf(CS var + 23, "%u.%u", &sec, &usec) == 2) >+ { >+ received_time_complete.tv_sec = sec; >+ received_time_complete.tv_usec = usec; >+ } > } > break; > >@@ -664,11 +703,11 @@ > f.sender_set_untrusted = TRUE; > #ifdef WITH_CONTENT_SCAN > else if (Ustrncmp(p, "pam_bar ", 8) == 0) >- spam_bar = string_copy_taint(var + 9, tainted); >+ spam_bar = string_copy_taint(var + 9, proto_mem); > else if (Ustrncmp(p, "pam_score ", 10) == 0) >- spam_score = string_copy_taint(var + 11, tainted); >+ spam_score = string_copy_taint(var + 11, proto_mem); > else if (Ustrncmp(p, "pam_score_int ", 14) == 0) >- spam_score_int = string_copy_taint(var + 15, tainted); >+ spam_score_int = string_copy_taint(var + 15, proto_mem); > #endif > #ifndef COMPILE_UTILITY > else if (Ustrncmp(p, "pool_file_wireformat", 20) == 0) >@@ -688,7 +727,7 @@ > if (Ustrncmp(q, "certificate_verified", 20) == 0) > tls_in.certificate_verified = TRUE; > else if (Ustrncmp(q, "cipher", 6) == 0) >- tls_in.cipher = string_copy_taint(q+7, tainted); >+ tls_in.cipher = string_copy_taint(q+7, proto_mem); > # ifndef COMPILE_UTILITY /* tls support fns not built in */ > else if (Ustrncmp(q, "ourcert", 7) == 0) > (void) tls_import_cert(q+8, &tls_in.ourcert); >@@ -696,17 +735,17 @@ > (void) tls_import_cert(q+9, &tls_in.peercert); > # endif > else if (Ustrncmp(q, "peerdn", 6) == 0) >- tls_in.peerdn = string_unprinting(string_copy_taint(q+7, tainted)); >+ tls_in.peerdn = string_unprinting(string_copy_taint(q+7, proto_mem)); > else if (Ustrncmp(q, "sni", 3) == 0) >- tls_in.sni = string_unprinting(string_copy_taint(q+4, tainted)); >+ tls_in.sni = string_unprinting(string_copy_taint(q+4, proto_mem)); > else if (Ustrncmp(q, "ocsp", 4) == 0) > tls_in.ocsp = q[5] - '0'; >-# ifdef EXPERIMENTAL_TLS_RESUME >+# ifndef DISABLE_TLS_RESUME > else if (Ustrncmp(q, "resumption", 10) == 0) > tls_in.resumption = q[11] - 'A'; > # endif > else if (Ustrncmp(q, "ver", 3) == 0) >- tls_in.ver = string_copy_taint(q+4, tainted); >+ tls_in.ver = string_copy_taint(q+4, proto_mem); > } > break; > #endif >@@ -745,11 +784,7 @@ > goto SPOOL_FORMAT_ERROR; > > #ifndef COMPILE_UTILITY >-DEBUG(D_deliver) >- { >- debug_printf("Non-recipients:\n"); >- debug_print_tree(tree_nonrecipients); >- } >+DEBUG(D_deliver) debug_print_tree("Non-recipients", tree_nonrecipients); > #endif /* COMPILE_UTILITY */ > > /* After reading the tree, the next line has not yet been read into the >@@ -765,7 +800,7 @@ > #endif /* COMPILE_UTILITY */ > > recipients_list_max = rcount; >-recipients_list = store_get(rcount * sizeof(recipient_item), FALSE); >+recipients_list = store_get(rcount * sizeof(recipient_item), GET_UNTAINTED); > > /* We sanitised the count and know we have enough memory, so disable > the Coverity error on recipients_count */ >@@ -865,7 +900,7 @@ > > (void)sscanf(CS p+1, "%d", &flags); > >- if ((flags & 0x01) != 0) /* one_time data exists */ >+ if (flags & 0x01) /* one_time data exists */ > { > int len; > while (isdigit(*(--p)) || *p == ',' || *p == '-'); >@@ -874,12 +909,12 @@ > if (len > 0) > { > p -= len; >- errors_to = string_copy_taint(p, TRUE); >+ errors_to = string_copy_taint(p, GET_TAINTED); > } > } > >- *(--p) = 0; /* Terminate address */ >- if ((flags & 0x02) != 0) /* one_time data exists */ >+ *--p = 0; /* Terminate address */ >+ if (flags & 0x02) /* one_time data exists */ > { > int len; > while (isdigit(*(--p)) || *p == ',' || *p == '-'); >@@ -888,11 +923,11 @@ > if (len > 0) > { > p -= len; >- orcpt = string_copy_taint(p, TRUE); >+ orcpt = string_copy_taint(p, GET_TAINTED); > } > } > >- *(--p) = 0; /* Terminate address */ >+ *--p = 0; /* Terminate address */ > } > #if !defined(COMPILE_UTILITY) > else >@@ -906,7 +941,7 @@ > big_buffer, errors_to); > #endif > >- recipients_list[recipients_count].address = string_copy_taint(big_buffer, TRUE); >+ recipients_list[recipients_count].address = string_copy_taint(big_buffer, GET_TAINTED); > recipients_list[recipients_count].pno = pno; > recipients_list[recipients_count].errors_to = errors_to; > recipients_list[recipients_count].orcpt = orcpt; >@@ -938,11 +973,11 @@ > > if (read_headers) > { >- h = store_get(sizeof(header_line), FALSE); >+ h = store_get(sizeof(header_line), GET_UNTAINTED); > h->next = NULL; > h->type = flag[0]; > h->slen = n; >- h->text = store_get(n+1, TRUE); /* tainted */ >+ h->text = store_get(n+1, GET_TAINTED); > > if (h->type == htype_received) received_count++; > >@@ -1013,6 +1048,47 @@ > return inheader? spool_read_hdrerror : spool_read_enverror; > } > >+ >+#ifndef COMPILE_UTILITY >+/* Read out just the (envelope) sender string from the spool -H file. >+Remove the <> wrap and return it in allocated store. Return NULL on error. >+ >+We assume that message_subdir is already set. >+*/ >+ >+uschar * >+spool_sender_from_msgid(const uschar * id) >+{ >+uschar * name = string_sprintf("%s-H", id); >+FILE * fp; >+int n; >+uschar * yield = NULL; >+ >+if (!(fp = Ufopen(spool_fname(US"input", message_subdir, name, US""), "rb"))) >+ return NULL; >+ >+DEBUG(D_deliver) debug_printf_indent("reading spool file %s\n", name); >+ >+/* Skip the line with the copy of the filename, then the line with login/uid/gid. >+Read the next line, which should be the envelope sender. >+Do basic validation on that. */ >+ >+if ( Ufgets(big_buffer, big_buffer_size, fp) != NULL >+ && Ufgets(big_buffer, big_buffer_size, fp) != NULL >+ && Ufgets(big_buffer, big_buffer_size, fp) != NULL >+ && (n = Ustrlen(big_buffer)) >= 3 >+ && big_buffer[0] == '<' && big_buffer[n-2] == '>' >+ ) >+ { >+ yield = store_get(n-2, GET_TAINTED); >+ Ustrncpy(yield, big_buffer+1, n-3); >+ yield[n-3] = 0; >+ } >+fclose(fp); >+return yield; >+} >+#endif /* COMPILE_UTILITY */ >+ > /* vi: aw ai sw=2 > */ > /* End of spool_in.c */ >diff -ur exim.orig/src/spool_mbox.c exim/src/spool_mbox.c >--- exim.orig/src/spool_mbox.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/spool_mbox.c 2022-06-23 16:41:10.000000000 +0300 >@@ -4,7 +4,7 @@ > > /* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015 > * License: GPL >- * Copyright (c) The Exim Maintainers 2016 - 2020 >+ * Copyright (c) The Exim Maintainers 2016 - 2021 > */ > > /* Code for setting up a MBOX style spool file inside a /scan/<msgid> >@@ -53,8 +53,8 @@ > temp_string = string_sprintf("scan/%s", message_id); > if (!directory_make(spool_directory, temp_string, 0750, FALSE)) > { >- log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno, >- "scan directory %s/scan/%s", spool_directory, temp_string)); >+ log_write(0, LOG_MAIN|LOG_PANIC, "%s", >+ string_open_failed("scan directory %s/scan/%s", spool_directory, temp_string)); > goto OUT; > } > >@@ -62,8 +62,8 @@ > > if (!(mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE))) > { >- log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno, >- "scan file %s", mbox_path)); >+ log_write(0, LOG_MAIN|LOG_PANIC, "%s", >+ string_open_failed("scan file %s", mbox_path)); > goto OUT; > } > >@@ -185,8 +185,8 @@ > if ( !(yield = Ufopen(mbox_path,"rb")) > || fstat(fileno(yield), &statbuf) != 0 > ) >- log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno, >- "scan file %s", mbox_path)); >+ log_write(0, LOG_MAIN|LOG_PANIC, "%s", >+ string_open_failed( "scan file %s", mbox_path)); > else > *mbox_file_size = statbuf.st_size; > >@@ -219,25 +219,26 @@ > { > debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno)); > /* Just in case we still can: */ >- rmdir(CS mbox_path); >+ (void) rmdir(CS mbox_path); > return; > } > /* loop thru dir & delete entries */ > for (struct dirent *entry; entry = readdir(tempdir); ) > { > uschar *name = US entry->d_name; >- int dummy; > if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue; > > file_path = string_sprintf("%s/%s", mbox_path, name); > debug_printf("unspool_mbox(): unlinking '%s'\n", file_path); >- dummy = unlink(CS file_path); dummy = dummy; /* compiler quietening */ >+ if (unlink(CS file_path) != 0) >+ log_write(0, LOG_MAIN|LOG_PANIC, "unlink(%s): %s", file_path, strerror(errno)); > } > > closedir(tempdir); > > /* remove directory */ >- rmdir(CS mbox_path); >+ if (rmdir(CS mbox_path) != 0) >+ log_write(0, LOG_MAIN|LOG_PANIC, "rmdir(%s): %s", mbox_path, strerror(errno)); > store_reset(reset_point); > } > spool_mbox_ok = 0; >diff -ur exim.orig/src/spool_out.c exim/src/spool_out.c >--- exim.orig/src/spool_out.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/spool_out.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions for writing spool files, and moving them about. */ >@@ -120,8 +120,14 @@ > static void > spool_var_write(FILE * fp, const uschar * name, const uschar * val) > { >-if (is_tainted(val)) putc('-', fp); >-fprintf(fp, "-%s %s\n", name, val); >+putc('-', fp); >+if (is_tainted(val)) >+ { >+ int q = quoter_for_address(val); >+ putc('-', fp); >+ if (is_real_quoter(q)) fprintf(fp, "(%s)", lookup_list[q]->name); >+ } >+fprintf(fp, "%s %s\n", name, val); > } > > /************************************************* >@@ -174,6 +180,8 @@ > fprintf(fp, "%d %d\n", (int)received_time.tv_sec, warning_count); > > fprintf(fp, "-received_time_usec .%06d\n", (int)received_time.tv_usec); >+fprintf(fp, "-received_time_complete %d.%06d\n", >+ (int)received_time_complete.tv_sec, (int)received_time_complete.tv_usec); > > /* If there is information about a sending host, remember it. The HELO > data can be set for local SMTP as well as remote. */ >@@ -183,7 +191,7 @@ > if (sender_host_address) > { > if (is_tainted(sender_host_address)) putc('-', fp); >- fprintf(fp, "-host_address %s.%d\n", sender_host_address, sender_host_port); >+ fprintf(fp, "-host_address [%s]:%d\n", sender_host_address, sender_host_port); > if (sender_host_name) > spool_var_write(fp, US"host_name", sender_host_name); > } >@@ -197,7 +205,7 @@ > if (interface_address) > { > if (is_tainted(interface_address)) putc('-', fp); >- fprintf(fp, "-interface_address %s.%d\n", interface_address, interface_port); >+ fprintf(fp, "-interface_address [%s]:%d\n", interface_address, interface_port); > } > > if (smtp_active_hostname != primary_hostname) >@@ -222,6 +230,12 @@ > > /* Now any other data that needs to be remembered. */ > >+if (*debuglog_name) >+ { >+ fprintf(fp, "-debug_selector 0x%x\n", debug_selector); >+ fprintf(fp, "-debuglog_name %s\n", debuglog_name); >+ } >+ > if (f.spool_file_wireformat) > fprintf(fp, "-spool_file_wireformat\n"); > else >@@ -275,7 +289,7 @@ > fprintf(fp, "-tls_ourcert %s\n", CS big_buffer); > } > if (tls_in.ocsp) fprintf(fp, "-tls_ocsp %d\n", tls_in.ocsp); >-# ifdef EXPERIMENTAL_TLS_RESUME >+# ifndef DISABLE_TLS_RESUME > fprintf(fp, "-tls_resumption %c\n", 'A' + tls_in.resumption); > # endif > if (tls_in.ver) spool_var_write(fp, US"tls_ver", tls_in.ver); >@@ -520,6 +534,9 @@ > { > uschar * dest_qname = queue_name_dest ? queue_name_dest : queue_name; > >+/* Since we are working within the spool, de-taint the dest queue name */ >+dest_qname = string_copy_taint(dest_qname, GET_UNTAINTED); >+ > /* Create any output directories that do not exist. */ > > (void) directory_make(spool_directory, >ТолÑко в exim.orig/src: srs.c >ТолÑко в exim.orig/src: srs.h >diff -ur exim.orig/src/std-crypto.c exim/src/std-crypto.c >--- exim.orig/src/std-crypto.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/std-crypto.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,7 +3,7 @@ > *************************************************/ > > /* Copyright (c) Phil Pennock 2012, 2016 >- * Copyright (c) The Exim Maintainers 2017 - 2018 >+ * Copyright (c) The Exim Maintainers 2017 - 2021 > * But almost everything here is fixed published constants from RFCs, so also: > * Copyright (C) The Internet Society (2003) > * Copyright (C) The IETF Trust (2008) >@@ -914,12 +914,11 @@ > > /* ========================================================================= */ > >-/* >- * Generated by Phil as a non-standard option. >- * openssl dhparam -2 2048 >- * No provenance to prove non-tampering available, beyond trusting that this >- * developer generated this as stated above. >- */ >+/* Generated by Phil as a non-standard option. >+openssl dhparam -2 2048 >+No provenance to prove non-tampering available, beyond trusting that this >+developer generated this as stated above. */ >+ > > /* MacOSX 10.10.5 invoking system OpenSSL 0.9.8zg */ > static const char dh_exim_20160529_1[] = >@@ -957,67 +956,75 @@ > /* ========================================================================= */ > > struct dh_constant { >- const char *label; >- const char *pem; >+ const char * label; >+ const char * pem; >+ int logging; > }; > >+#define EXIM_DH_PRIME_DEFAULT dh_exim_20160529_3 >+ > /* KEEP SORTED ALPHABETICALLY; >- * duplicate PEM are okay, if we want aliases, but names must be alphabetical */ >+duplicate PEM are okay, if we want aliases, but names must be alphabetical */ >+ > static struct dh_constant dh_constants[] = { > /* label pem */ >- { "default", dh_exim_20160529_3 }, >- { "exim.dev.20160529.1", dh_exim_20160529_1 }, >- { "exim.dev.20160529.2", dh_exim_20160529_2 }, >- { "exim.dev.20160529.3", dh_exim_20160529_3 }, >- { "ffdhe2048", dh_ffdhe2048_pem }, >- { "ffdhe3072", dh_ffdhe3072_pem }, >- { "ffdhe4096", dh_ffdhe4096_pem }, >- { "ffdhe6144", dh_ffdhe6144_pem }, >- { "ffdhe8192", dh_ffdhe8192_pem }, >- { "ike1", dh_ike_1_pem }, >- { "ike14", dh_ike_14_pem }, >- { "ike15", dh_ike_15_pem }, >- { "ike16", dh_ike_16_pem }, >- { "ike17", dh_ike_17_pem }, >- { "ike18", dh_ike_18_pem }, >- { "ike2", dh_ike_2_pem }, >- { "ike22", dh_ike_22_pem }, >- { "ike23", dh_ike_23_pem }, >- { "ike24", dh_ike_24_pem }, >- { "ike5", dh_ike_5_pem }, >+ { "default", EXIM_DH_PRIME_DEFAULT, 0 }, >+ { "exim.dev.20160529.1", dh_exim_20160529_1, 0 }, >+ { "exim.dev.20160529.2", dh_exim_20160529_2, 0 }, >+ { "exim.dev.20160529.3", dh_exim_20160529_3, 0 }, >+ { "ffdhe2048", dh_ffdhe2048_pem, 0 }, >+ { "ffdhe3072", dh_ffdhe3072_pem, 0 }, >+ { "ffdhe4096", dh_ffdhe4096_pem, 0 }, >+ { "ffdhe6144", dh_ffdhe6144_pem, 0 }, >+ { "ffdhe8192", dh_ffdhe8192_pem, 0 }, >+ { "ike1", dh_ike_1_pem, LOG_MAIN | LOG_PANIC }, >+ { "ike14", dh_ike_14_pem, 0 }, >+ { "ike15", dh_ike_15_pem, 0 }, >+ { "ike16", dh_ike_16_pem, 0 }, >+ { "ike17", dh_ike_17_pem, 0 }, >+ { "ike18", dh_ike_18_pem, 0 }, >+ { "ike2", dh_ike_2_pem, LOG_MAIN }, >+ { "ike22", dh_ike_22_pem, LOG_MAIN | LOG_PANIC }, >+ { "ike23", dh_ike_23_pem, LOG_MAIN }, >+ { "ike24", dh_ike_24_pem, LOG_MAIN }, >+ { "ike5", dh_ike_5_pem, 0 }, > }; >-static const int dh_constants_count = >- sizeof(dh_constants) / sizeof(struct dh_constant); >+static const int dh_constants_count = nelem(dh_constants); > > > /* A policy decision; in absence of any other data, use a 2048 bit prime, >- * pick the first one from the latest RFC providing such. */ >+pick the first one from the latest RFC providing such. */ >+ > const char * > std_dh_prime_default(void) > { >- return dh_ike_23_pem; >+return EXIM_DH_PRIME_DEFAULT; > } > > >+/* Return PEM string for given name */ >+ > const char * >-std_dh_prime_named(const uschar *name) >+std_dh_prime_named(const uschar * name) > { >- int first, last; >- char *search_name = CS string_copylc(US name); >- >- first = 0; >- last = dh_constants_count; >- while (last > first) { >- int middle = (first + last)/2; >- int c = strcmp(search_name, dh_constants[middle].label); >- if (c == 0) >- return dh_constants[middle].pem; >- else if (c > 0) >- first = middle + 1; >- else >- last = middle; >+for (int first = 0, last = dh_constants_count; last > first; ) >+ { >+ int middle = (first + last)/2; >+ struct dh_constant * dp = &dh_constants[middle]; >+ int c = Ustrcmp(name, dp->label); >+ if (c == 0) >+ { >+ if (dp->logging) >+ log_write(0, dp->logging, >+ "WARNING: deprecated Diffie-Hellman parameter '%s' used", dp->label); >+ return dp->pem; >+ } >+ else if (c > 0) >+ first = middle + 1; >+ else >+ last = middle; > } >- return NULL; >+return NULL; > } > > #endif /*DISABLE_TLS*/ >diff -ur exim.orig/src/store.c exim/src/store.c >--- exim.orig/src/store.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/store.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim maintainers 2019 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim maintainers 2019 - 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Exim gets and frees all its store through these functions. In the original >@@ -37,11 +37,20 @@ > This means it can be freed when search_tidyup() is called to close down all > the lookup caching. > >-. Orthogonal to the three pool types, there are two classes of memory: untainted >+- There is another pool (POOL_MESSAGE) used for medium-lifetime objects; within >+ a single message transaction but needed for longer than the use of the main >+ pool permits. Currently this means only receive-time DKIM information. >+ >+- There is a dedicated pool for configuration data read from the config file(s). >+ Once complete, it is made readonly. >+ >+- There are pools for each active combination of lookup-quoting, dynamically created. >+ >+. Orthogonal to the four main pool types, there are two classes of memory: untainted > and tainted. The latter is used for values derived from untrusted input, and > the string-expansion mechanism refuses to operate on such values (obviously, > it can expand an untainted value to return a tainted result). The classes >- are implemented by duplicating the three pool types. Pool resets are requested >+ are implemented by duplicating the four pool types. Pool resets are requested > against the nontainted sibling and apply to both siblings. > > Only memory blocks requested for tainted use are regarded as tainted; anything >@@ -93,6 +102,38 @@ > size_t length; > } storeblock; > >+/* Pool descriptor struct */ >+ >+typedef struct pooldesc { >+ storeblock * chainbase; /* list of blocks in pool */ >+ storeblock * current_block; /* top block, still with free space */ >+ void * next_yield; /* next allocation point */ >+ int yield_length; /* remaining space in current block */ >+ unsigned store_block_order; /* log2(size) block allocation size */ >+ >+ /* This variable is set by store_get() to its yield, and by store_reset() to >+ NULL. This enables string_cat() to optimize its store handling for very long >+ strings. That's why the variable is global. */ >+ >+ void * store_last_get; >+ >+ /* These are purely for stats-gathering */ >+ >+ int nbytes; >+ int maxbytes; >+ int nblocks; >+ int maxblocks; >+ unsigned maxorder; >+} pooldesc; >+ >+/* Enhanced pool descriptor for quoted pools */ >+ >+typedef struct quoted_pooldesc { >+ pooldesc pool; >+ unsigned quoter; >+ struct quoted_pooldesc * next; >+} quoted_pooldesc; >+ > /* Just in case we find ourselves on a system where the structure above has a > length that is not a multiple of the alignment, set up a macro for the padded > length. */ >@@ -101,10 +142,21 @@ > (((sizeof(storeblock) + alignment - 1) / alignment) * alignment) > > /* Size of block to get from malloc to carve up into smaller ones. This >-must be a multiple of the alignment. We assume that 8192 is going to be >-suitably aligned. */ >+must be a multiple of the alignment. We assume that 4096 is going to be >+suitably aligned. Double the size per-pool for every malloc, to mitigate >+certain denial-of-service attacks. Don't bother to decrease on block frees. >+We waste average half the current alloc size per pool. This could be several >+hundred kB now, vs. 4kB with a constant-size block size. But the search time >+for is_tainted(), linear in the number of blocks for the pool, is O(n log n) >+rather than O(n^2). >+A test of 2000 RCPTs and just accept ACL had 370kB in 21 blocks before, >+504kB in 6 blocks now, for the untainted-main (largest) pool. >+Builds for restricted-memory system can disable the expansion by >+defining RESTRICTED_MEMORY */ >+/*XXX should we allow any for malloc's own overhead? But how much? */ > >-#define STORE_BLOCK_SIZE (8192 - ALIGNED_SIZEOF_STOREBLOCK) >+/* #define RESTRICTED_MEMORY */ >+#define STORE_BLOCK_SIZE(order) ((1U << (order)) - ALIGNED_SIZEOF_STOREBLOCK) > > /* Variables holding data for the local pools of store. The current pool number > is held in store_pool, which is global so that it can be changed from outside. >@@ -113,11 +165,13 @@ > > int store_pool = POOL_MAIN; > >-#define NPOOLS 6 >-static storeblock *chainbase[NPOOLS]; >-static storeblock *current_block[NPOOLS]; >-static void *next_yield[NPOOLS]; >-static int yield_length[NPOOLS] = { -1, -1, -1, -1, -1, -1 }; >+pooldesc paired_pools[N_PAIRED_POOLS]; >+quoted_pooldesc * quoted_pools = NULL; >+ >+static int n_nonpool_blocks; /* current number of direct store_malloc() blocks */ >+static int max_nonpool_blocks; >+static int max_pool_malloc; /* max value for pool_malloc */ >+static int max_nonpool_malloc; /* max value for nonpool_malloc */ > > /* pool_malloc holds the amount of memory used by the store pools; this goes up > and down as store is reset or released. nonpool_malloc is the total got by >@@ -127,49 +181,105 @@ > static int pool_malloc; > static int nonpool_malloc; > >-/* This variable is set by store_get() to its yield, and by store_reset() to >-NULL. This enables string_cat() to optimize its store handling for very long >-strings. That's why the variable is global. */ >- >-void *store_last_get[NPOOLS]; >- >-/* These are purely for stats-gathering */ >- >-static int nbytes[NPOOLS]; /* current bytes allocated */ >-static int maxbytes[NPOOLS]; /* max number reached */ >-static int nblocks[NPOOLS]; /* current number of blocks allocated */ >-static int maxblocks[NPOOLS]; >-static int n_nonpool_blocks; /* current number of direct store_malloc() blocks */ >-static int max_nonpool_blocks; >-static int max_pool_malloc; /* max value for pool_malloc */ >-static int max_nonpool_malloc; /* max value for nonpool_malloc */ >- > > #ifndef COMPILE_UTILITY >-static const uschar * pooluse[NPOOLS] = { >+static const uschar * pooluse[N_PAIRED_POOLS] = { > [POOL_MAIN] = US"main", > [POOL_PERM] = US"perm", >+[POOL_CONFIG] = US"config", > [POOL_SEARCH] = US"search", >+[POOL_MESSAGE] = US"message", > [POOL_TAINT_MAIN] = US"main", > [POOL_TAINT_PERM] = US"perm", >+[POOL_TAINT_CONFIG] = US"config", > [POOL_TAINT_SEARCH] = US"search", >+[POOL_TAINT_MESSAGE] = US"message", > }; >-static const uschar * poolclass[NPOOLS] = { >+static const uschar * poolclass[N_PAIRED_POOLS] = { > [POOL_MAIN] = US"untainted", > [POOL_PERM] = US"untainted", >+[POOL_CONFIG] = US"untainted", > [POOL_SEARCH] = US"untainted", >+[POOL_MESSAGE] = US"untainted", > [POOL_TAINT_MAIN] = US"tainted", > [POOL_TAINT_PERM] = US"tainted", >+[POOL_TAINT_CONFIG] = US"tainted", > [POOL_TAINT_SEARCH] = US"tainted", >+[POOL_TAINT_MESSAGE] = US"tainted", > }; > #endif > > >-static void * internal_store_malloc(int, const char *, int); >+static void * internal_store_malloc(size_t, const char *, int); > static void internal_store_free(void *, const char *, int linenumber); > > /******************************************************************************/ > >+static void >+pool_init(pooldesc * pp) >+{ >+memset(pp, 0, sizeof(*pp)); >+pp->yield_length = -1; >+pp->store_block_order = 12; /* log2(allocation_size) ie. 4kB */ >+} >+ >+/* Initialisation, for things fragile with parameter channges when using >+static initialisers. */ >+ >+void >+store_init(void) >+{ >+for (pooldesc * pp = paired_pools; pp < paired_pools + N_PAIRED_POOLS; pp++) >+ pool_init(pp); >+} >+ >+/******************************************************************************/ >+/* Locating elements given memory pointer */ >+ >+static BOOL >+is_pointer_in_block(const storeblock * b, const void * p) >+{ >+uschar * bc = US b + ALIGNED_SIZEOF_STOREBLOCK; >+return US p >= bc && US p < bc + b->length; >+} >+ >+static pooldesc * >+pool_current_for_pointer(const void * p) >+{ >+storeblock * b; >+ >+for (quoted_pooldesc * qp = quoted_pools; qp; qp = qp->next) >+ if ((b = qp->pool.current_block) && is_pointer_in_block(b, p)) >+ return &qp->pool; >+ >+for (pooldesc * pp = paired_pools; pp < paired_pools + N_PAIRED_POOLS; pp++) >+ if ((b = pp->current_block) && is_pointer_in_block(b, p)) >+ return pp; >+return NULL; >+} >+ >+static pooldesc * >+pool_for_pointer(const void * p, const char * func, int linenumber) >+{ >+pooldesc * pp; >+storeblock * b; >+ >+if ((pp = pool_current_for_pointer(p))) return pp; >+ >+for (quoted_pooldesc * qp = quoted_pools; qp; qp = qp->next) >+ for (b = qp->pool.chainbase; b; b = b->next) >+ if (is_pointer_in_block(b, p)) return &qp->pool; >+ >+for (pp = paired_pools; pp < paired_pools + N_PAIRED_POOLS; pp++) >+ for (b = pp->chainbase; b; b = b->next) >+ if (is_pointer_in_block(b, p)) return pp; >+ >+log_write(0, LOG_MAIN|LOG_PANIC_DIE, >+ "bad memory reference; pool not found, at %s %d", func, linenumber); >+return NULL; >+} >+ >+/******************************************************************************/ > /* Test if a pointer refers to tainted memory. > > Slower version check, for use when platform intermixes malloc and mmap area >@@ -184,19 +294,27 @@ > { > storeblock * b; > >-for (int pool = POOL_TAINT_BASE; pool < nelem(chainbase); pool++) >- if ((b = current_block[pool])) >- { >- uschar * bc = US b + ALIGNED_SIZEOF_STOREBLOCK; >- if (US p >= bc && US p < bc + b->length) return TRUE; >- } >+if (p == GET_UNTAINTED) return FALSE; >+if (p == GET_TAINTED) return TRUE; >+ >+for (pooldesc * pp = paired_pools + POOL_TAINT_BASE; >+ pp < paired_pools + N_PAIRED_POOLS; pp++) >+ if ((b = pp->current_block)) >+ if (is_pointer_in_block(b, p)) return TRUE; >+ >+for (quoted_pooldesc * qp = quoted_pools; qp; qp = qp->next) >+ if (b = qp->pool.current_block) >+ if (is_pointer_in_block(b, p)) return TRUE; >+ >+for (pooldesc * pp = paired_pools + POOL_TAINT_BASE; >+ pp < paired_pools + N_PAIRED_POOLS; pp++) >+ for (b = pp->chainbase; b; b = b->next) >+ if (is_pointer_in_block(b, p)) return TRUE; >+ >+for (quoted_pooldesc * qp = quoted_pools; qp; qp = qp->next) >+ for (b = qp->pool.chainbase; b; b = b->next) >+ if (is_pointer_in_block(b, p)) return TRUE; > >-for (int pool = POOL_TAINT_BASE; pool < nelem(chainbase); pool++) >- for (b = chainbase[pool]; b; b = b->next) >- { >- uschar * bc = US b + ALIGNED_SIZEOF_STOREBLOCK; >- if (US p >= bc && US p < bc + b->length) return TRUE; >- } > return FALSE; > } > >@@ -209,30 +327,51 @@ > } > > >+#ifndef COMPILE_UTILITY >+/* Return the pool for the given quoter, or null */ > >-/************************************************* >-* Get a block from the current pool * >-*************************************************/ >+static pooldesc * >+pool_for_quoter(unsigned quoter) >+{ >+for (quoted_pooldesc * qp = quoted_pools; qp; qp = qp->next) >+ if (qp->quoter == quoter) >+ return &qp->pool; >+return NULL; >+} > >-/* Running out of store is a total disaster. This function is called via the >-macro store_get(). It passes back a block of store within the current big >-block, getting a new one if necessary. The address is saved in >-store_last_was_get. >+/* Allocate/init a new quoted-pool and return the pool */ > >-Arguments: >- size amount wanted, bytes >- tainted class: set to true for untrusted data (eg. from smtp input) >- func function from which called >- linenumber line number in source file >+static pooldesc * >+quoted_pool_new(unsigned quoter) >+{ >+// debug_printf("allocating quoted-pool\n"); >+quoted_pooldesc * qp = store_get_perm(sizeof(quoted_pooldesc), GET_UNTAINTED); > >-Returns: pointer to store (panic on malloc failure) >-*/ >+pool_init(&qp->pool); >+qp->quoter = quoter; >+qp->next = quoted_pools; >+quoted_pools = qp; >+return &qp->pool; >+} >+#endif > >-void * >-store_get_3(int size, BOOL tainted, const char *func, int linenumber) >+ >+/******************************************************************************/ >+void >+store_writeprotect(int pool) > { >-int pool = tainted ? store_pool + POOL_TAINT_BASE : store_pool; >+#if !defined(COMPILE_UTILITY) && !defined(MISSING_POSIX_MEMALIGN) >+for (storeblock * b = paired_pools[pool].chainbase; b; b = b->next) >+ if (mprotect(b, ALIGNED_SIZEOF_STOREBLOCK + b->length, PROT_READ) != 0) >+ DEBUG(D_any) debug_printf("config block mprotect: (%d) %s\n", errno, strerror(errno)); >+#endif >+} > >+/******************************************************************************/ >+ >+static void * >+pool_get(pooldesc * pp, int size, BOOL align_mem, const char * func, int linenumber) >+{ > /* Ensure we've been asked to allocate memory. > A negative size is a sign of a security problem. > A zero size might be also suspect, but our internal usage deliberately >@@ -241,7 +380,7 @@ > > if (size < 0 || size >= INT_MAX/2) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, >- "bad memory allocation requested (%d bytes) at %s %d", >+ "bad memory allocation requested (%d bytes) from %s %d", > size, func, linenumber); > > /* Round up the size to a multiple of the alignment. Although this looks a >@@ -256,21 +395,23 @@ > size is STORE_BLOCK_SIZE, and we would expect this to be the norm, since > these functions are mostly called for small amounts of store. */ > >-if (size > yield_length[pool]) >+if (size > pp->yield_length) > { >- int length = size <= STORE_BLOCK_SIZE ? STORE_BLOCK_SIZE : size; >+ int length = MAX( >+ STORE_BLOCK_SIZE(pp->store_block_order) - ALIGNED_SIZEOF_STOREBLOCK, >+ size); > int mlength = length + ALIGNED_SIZEOF_STOREBLOCK; > storeblock * newblock; > > /* Sometimes store_reset() may leave a block for us; check if we can use it */ > >- if ( (newblock = current_block[pool]) >+ if ( (newblock = pp->current_block) > && (newblock = newblock->next) > && newblock->length < length > ) > { > /* Give up on this block, because it's too small */ >- nblocks[pool]--; >+ pp->nblocks--; > internal_store_free(newblock, func, linenumber); > newblock = NULL; > } >@@ -279,54 +420,123 @@ > > if (!newblock) > { >- if ((nbytes[pool] += mlength) > maxbytes[pool]) >- maxbytes[pool] = nbytes[pool]; >+ if ((pp->nbytes += mlength) > pp->maxbytes) >+ pp->maxbytes = pp->nbytes; > if ((pool_malloc += mlength) > max_pool_malloc) /* Used in pools */ > max_pool_malloc = pool_malloc; > nonpool_malloc -= mlength; /* Exclude from overall total */ >- if (++nblocks[pool] > maxblocks[pool]) >- maxblocks[pool] = nblocks[pool]; >+ if (++pp->nblocks > pp->maxblocks) >+ pp->maxblocks = pp->nblocks; > >- newblock = internal_store_malloc(mlength, func, linenumber); >+#ifndef MISSING_POSIX_MEMALIGN >+ if (align_mem) >+ { >+ long pgsize = sysconf(_SC_PAGESIZE); >+ int err = posix_memalign((void **)&newblock, >+ pgsize, (mlength + pgsize - 1) & ~(pgsize - 1)); >+ if (err) >+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, >+ "failed to alloc (using posix_memalign) %d bytes of memory: '%s'" >+ "called from line %d in %s", >+ size, strerror(err), linenumber, func); >+ } >+ else >+#endif >+ newblock = internal_store_malloc(mlength, func, linenumber); > newblock->next = NULL; > newblock->length = length; >+#ifndef RESTRICTED_MEMORY >+ if (pp->store_block_order++ > pp->maxorder) >+ pp->maxorder = pp->store_block_order; >+#endif > >- if (!chainbase[pool]) >- chainbase[pool] = newblock; >+ if (! pp->chainbase) >+ pp->chainbase = newblock; > else >- current_block[pool]->next = newblock; >+ pp->current_block->next = newblock; > } > >- current_block[pool] = newblock; >- yield_length[pool] = newblock->length; >- next_yield[pool] = >- (void *)(CS current_block[pool] + ALIGNED_SIZEOF_STOREBLOCK); >- (void) VALGRIND_MAKE_MEM_NOACCESS(next_yield[pool], yield_length[pool]); >+ pp->current_block = newblock; >+ pp->yield_length = newblock->length; >+ pp->next_yield = >+ (void *)(CS pp->current_block + ALIGNED_SIZEOF_STOREBLOCK); >+ (void) VALGRIND_MAKE_MEM_NOACCESS(pp->next_yield, pp->yield_length); > } > > /* There's (now) enough room in the current block; the yield is the next > pointer. */ > >-store_last_get[pool] = next_yield[pool]; >+pp->store_last_get = pp->next_yield; > >-/* Cut out the debugging stuff for utilities, but stop picky compilers from >-giving warnings. */ >+(void) VALGRIND_MAKE_MEM_UNDEFINED(pp->store_last_get, size); >+/* Update next pointer and number of bytes left in the current block. */ > >-#ifdef COMPILE_UTILITY >-func = func; >-linenumber = linenumber; >-#else >-DEBUG(D_memory) >- debug_printf("---%d Get %6p %5d %-14s %4d\n", pool, >- store_last_get[pool], size, func, linenumber); >-#endif /* COMPILE_UTILITY */ >+pp->next_yield = (void *)(CS pp->next_yield + size); >+pp->yield_length -= size; >+return pp->store_last_get; >+} > >-(void) VALGRIND_MAKE_MEM_UNDEFINED(store_last_get[pool], size); >-/* Update next pointer and number of bytes left in the current block. */ >+/************************************************* >+* Get a block from the current pool * >+*************************************************/ > >-next_yield[pool] = (void *)(CS next_yield[pool] + size); >-yield_length[pool] -= size; >-return store_last_get[pool]; >+/* Running out of store is a total disaster. This function is called via the >+macro store_get(). The current store_pool is used, adjusting for taint. >+If the protoype is quoted, use a quoted-pool. >+Return a block of store within the current big block of the pool, getting a new >+one if necessary. The address is saved in store_last_get for the pool. >+ >+Arguments: >+ size amount wanted, bytes >+ proto_mem class: get store conformant to this >+ Special values: 0 forces untainted, 1 forces tainted >+ func function from which called >+ linenumber line number in source file >+ >+Returns: pointer to store (panic on malloc failure) >+*/ >+ >+void * >+store_get_3(int size, const void * proto_mem, const char * func, int linenumber) >+{ >+#ifndef COMPILE_UTILITY >+int quoter = quoter_for_address(proto_mem); >+#endif >+pooldesc * pp; >+void * yield; >+ >+#ifndef COMPILE_UTILITY >+if (!is_real_quoter(quoter)) >+#endif >+ { >+ BOOL tainted = is_tainted(proto_mem); >+ int pool = tainted ? store_pool + POOL_TAINT_BASE : store_pool; >+ pp = paired_pools + pool; >+ yield = pool_get(pp, size, (pool == POOL_CONFIG), func, linenumber); >+ >+ /* Cut out the debugging stuff for utilities, but stop picky compilers from >+ giving warnings. */ >+ >+#ifndef COMPILE_UTILITY >+ DEBUG(D_memory) >+ debug_printf("---%d Get %6p %5d %-14s %4d\n", pool, >+ pp->store_last_get, size, func, linenumber); >+#endif >+ } >+#ifndef COMPILE_UTILITY >+else >+ { >+ DEBUG(D_memory) >+ debug_printf("allocating quoted-block for quoter %u (from %s %d)\n", >+ quoter, func, linenumber); >+ if (!(pp = pool_for_quoter(quoter))) pp = quoted_pool_new(quoter); >+ yield = pool_get(pp, size, FALSE, func, linenumber); >+ DEBUG(D_memory) >+ debug_printf("---QQ Get %6p %5d %-14s %4d\n", >+ pp->store_last_get, size, func, linenumber); >+ } >+#endif >+return yield; > } > > >@@ -340,6 +550,7 @@ > > Arguments: > size amount wanted >+ proto_mem class: get store conformant to this > func function from which called > linenumber line number in source file > >@@ -347,17 +558,131 @@ > */ > > void * >-store_get_perm_3(int size, BOOL tainted, const char *func, int linenumber) >+store_get_perm_3(int size, const void * proto_mem, const char * func, int linenumber) > { >-void *yield; >+void * yield; > int old_pool = store_pool; > store_pool = POOL_PERM; >-yield = store_get_3(size, tainted, func, linenumber); >+yield = store_get_3(size, proto_mem, func, linenumber); > store_pool = old_pool; > return yield; > } > > >+#ifndef COMPILE_UTILITY >+/************************************************* >+* Get a block annotated as being lookup-quoted * >+*************************************************/ >+ >+/* Allocate from pool a pool consistent with the proto_mem augmented by the >+requested quoter type. >+ >+XXX currently not handling mark/release >+ >+Args: size number of bytes to allocate >+ quoter id for the quoting type >+ func caller, for debug >+ linenumber caller, for debug >+ >+Return: allocated memory block >+*/ >+ >+static void * >+store_force_get_quoted(int size, unsigned quoter, >+ const char * func, int linenumber) >+{ >+pooldesc * pp = pool_for_quoter(quoter); >+void * yield; >+ >+DEBUG(D_memory) >+ debug_printf("allocating quoted-block for quoter %u (from %s %d)\n", quoter, func, linenumber); >+ >+if (!pp) pp = quoted_pool_new(quoter); >+yield = pool_get(pp, size, FALSE, func, linenumber); >+ >+DEBUG(D_memory) >+ debug_printf("---QQ Get %6p %5d %-14s %4d\n", >+ pp->store_last_get, size, func, linenumber); >+ >+return yield; >+} >+ >+/* Maybe get memory for the specified quoter, but only if the >+prototype memory is tainted. Otherwise, get plain memory. >+*/ >+void * >+store_get_quoted_3(int size, const void * proto_mem, unsigned quoter, >+ const char * func, int linenumber) >+{ >+// debug_printf("store_get_quoted_3: quoter %u\n", quoter); >+return is_tainted(proto_mem) >+ ? store_force_get_quoted(size, quoter, func, linenumber) >+ : store_get_3(size, proto_mem, func, linenumber); >+} >+ >+/* Return quoter for given address, or -1 if not in a quoted-pool. */ >+int >+quoter_for_address(const void * p) >+{ >+for (quoted_pooldesc * qp = quoted_pools; qp; qp = qp->next) >+ { >+ pooldesc * pp = &qp->pool; >+ storeblock * b; >+ >+ if (b = pp->current_block) >+ if (is_pointer_in_block(b, p)) >+ return qp->quoter; >+ >+ for (b = pp->chainbase; b; b = b->next) >+ if (is_pointer_in_block(b, p)) >+ return qp->quoter; >+ } >+return -1; >+} >+ >+/* Return TRUE iff the given address is quoted for the given type. >+There is extra complexity to handle lookup providers with multiple >+find variants but shared quote functions. */ >+BOOL >+is_quoted_like(const void * p, unsigned quoter) >+{ >+int pq = quoter_for_address(p); >+BOOL y = >+ is_real_quoter(pq) && lookup_list[pq]->quote == lookup_list[quoter]->quote; >+/* debug_printf("is_quoted(%p, %u): %c\n", p, quoter, y?'T':'F'); */ >+return y; >+} >+ >+/* Return TRUE if the quoter value indicates an actual quoter */ >+BOOL >+is_real_quoter(int quoter) >+{ >+return quoter >= 0; >+} >+ >+/* Return TRUE if the "new" data requires that the "old" data >+be recopied to new-class memory. We order the classes as >+ >+ 2: tainted, not quoted >+ 1: quoted (which is also tainted) >+ 0: untainted >+ >+If the "new" is higher-order than the "old", they are not compatible >+and a copy is needed. If both are quoted, but the quoters differ, >+not compatible. Otherwise they are compatible. >+*/ >+BOOL >+is_incompatible_fn(const void * old, const void * new) >+{ >+int oq, nq; >+unsigned oi, ni; >+ >+ni = is_real_quoter(nq = quoter_for_address(new)) ? 1 : is_tainted(new) ? 2 : 0; >+oi = is_real_quoter(oq = quoter_for_address(old)) ? 1 : is_tainted(old) ? 2 : 0; >+return ni > oi || ni == oi && nq != oq; >+} >+ >+#endif /*!COMPILE_UTILITY*/ > > /************************************************* > * Extend a block if it is at the top * >@@ -380,13 +705,16 @@ > Returns: TRUE if the block is at the top of the stack and has been > extended; FALSE if it isn't at the top of the stack, or cannot > be extended >+ >+XXX needs extension for quoted-tracking. This assumes that the global store_pool >+is the one to alloc from, which breaks with separated pools. > */ > > BOOL >-store_extend_3(void *ptr, BOOL tainted, int oldsize, int newsize, >- const char *func, int linenumber) >+store_extend_3(void * ptr, int oldsize, int newsize, >+ const char * func, int linenumber) > { >-int pool = tainted ? store_pool + POOL_TAINT_BASE : store_pool; >+pooldesc * pp = pool_for_pointer(ptr, func, linenumber); > int inc = newsize - oldsize; > int rounded_oldsize = oldsize; > >@@ -395,34 +723,38 @@ > "bad memory extension requested (%d -> %d bytes) at %s %d", > oldsize, newsize, func, linenumber); > >-/* Check that the block being extended was already of the required taint status; >-refuse to extend if not. */ >- >-if (is_tainted(ptr) != tainted) >- return FALSE; >- > if (rounded_oldsize % alignment != 0) > rounded_oldsize += alignment - (rounded_oldsize % alignment); > >-if (CS ptr + rounded_oldsize != CS (next_yield[pool]) || >- inc > yield_length[pool] + rounded_oldsize - oldsize) >+if (CS ptr + rounded_oldsize != CS (pp->next_yield) || >+ inc > pp->yield_length + rounded_oldsize - oldsize) > return FALSE; > > /* Cut out the debugging stuff for utilities, but stop picky compilers from > giving warnings. */ > >-#ifdef COMPILE_UTILITY >-func = func; >-linenumber = linenumber; >-#else >+#ifndef COMPILE_UTILITY > DEBUG(D_memory) >- debug_printf("---%d Ext %6p %5d %-14s %4d\n", pool, ptr, newsize, >- func, linenumber); >+ { >+ quoted_pooldesc * qp; >+ for (qp = quoted_pools; qp; qp = qp->next) >+ if (pp == &qp->pool) >+ { >+ debug_printf("---Q%d Ext %6p %5d %-14s %4d\n", >+ (int)(qp - quoted_pools), >+ ptr, newsize, func, linenumber); >+ break; >+ } >+ if (!qp) >+ debug_printf("---%d Ext %6p %5d %-14s %4d\n", >+ (int)(pp - paired_pools), >+ ptr, newsize, func, linenumber); >+ } > #endif /* COMPILE_UTILITY */ > > if (newsize % alignment != 0) newsize += alignment - (newsize % alignment); >-next_yield[pool] = CS ptr + newsize; >-yield_length[pool] -= newsize - rounded_oldsize; >+pp->next_yield = CS ptr + newsize; >+pp->yield_length -= newsize - rounded_oldsize; > (void) VALGRIND_MAKE_MEM_UNDEFINED(ptr + oldsize, inc); > return TRUE; > } >@@ -430,6 +762,14 @@ > > > >+static BOOL >+is_pwr2_size(int len) >+{ >+unsigned x = len; >+return (x & (x - 1)) == 0; >+} >+ >+ > /************************************************* > * Back up to a previous point on the stack * > *************************************************/ >@@ -439,8 +779,11 @@ > not call with a pointer returned by store_get(). Both the untainted and tainted > pools corresposding to store_pool are reset. > >+Quoted pools are not handled. >+ > Arguments: >- r place to back up to >+ ptr place to back up to >+ pool pool holding the pointer > func function from which called > linenumber line number in source file > >@@ -451,23 +794,26 @@ > internal_store_reset(void * ptr, int pool, const char *func, int linenumber) > { > storeblock * bb; >-storeblock * b = current_block[pool]; >+pooldesc * pp = paired_pools + pool; >+storeblock * b = pp->current_block; > char * bc = CS b + ALIGNED_SIZEOF_STOREBLOCK; > int newlength, count; > #ifndef COMPILE_UTILITY > int oldmalloc = pool_malloc; > #endif > >+if (!b) return; /* exim_dumpdb gets this, becuse it has never used tainted mem */ >+ > /* Last store operation was not a get */ > >-store_last_get[pool] = NULL; >+pp->store_last_get = NULL; > > /* See if the place is in the current block - as it often will be. Otherwise, > search for the block in which it lies. */ > > if (CS ptr < bc || CS ptr > bc + b->length) > { >- for (b = chainbase[pool]; b; b = b->next) >+ for (b = pp->chainbase; b; b = b->next) > { > bc = CS b + ALIGNED_SIZEOF_STOREBLOCK; > if (CS ptr >= bc && CS ptr <= bc + b->length) break; >@@ -493,19 +839,19 @@ > } > #endif > (void) VALGRIND_MAKE_MEM_NOACCESS(ptr, newlength); >-next_yield[pool] = CS ptr + (newlength % alignment); >-count = yield_length[pool]; >-count = (yield_length[pool] = newlength - (newlength % alignment)) - count; >-current_block[pool] = b; >+pp->next_yield = CS ptr + (newlength % alignment); >+count = pp->yield_length; >+count = (pp->yield_length = newlength - (newlength % alignment)) - count; >+pp->current_block = b; > > /* Free any subsequent block. Do NOT free the first > successor, if our current block has less than 256 bytes left. This should > prevent us from flapping memory. However, keep this block only when it has >-the default size. */ >+a power-of-two size so probably is not a custom inflated one. */ > >-if ( yield_length[pool] < STOREPOOL_MIN_SIZE >+if ( pp->yield_length < STOREPOOL_MIN_SIZE > && b->next >- && b->next->length == STORE_BLOCK_SIZE) >+ && is_pwr2_size(b->next->length + ALIGNED_SIZEOF_STOREBLOCK)) > { > b = b->next; > #ifndef COMPILE_UTILITY >@@ -518,56 +864,65 @@ > } > > bb = b->next; >-b->next = NULL; >+if (pool != POOL_CONFIG) >+ b->next = NULL; > > while ((b = bb)) > { > int siz = b->length + ALIGNED_SIZEOF_STOREBLOCK; >+ > #ifndef COMPILE_UTILITY > if (debug_store) > assert_no_variables(b, b->length + ALIGNED_SIZEOF_STOREBLOCK, > func, linenumber); > #endif > bb = bb->next; >- nbytes[pool] -= siz; >+ pp->nbytes -= siz; > pool_malloc -= siz; >- nblocks[pool]--; >- internal_store_free(b, func, linenumber); >+ pp->nblocks--; >+ if (pool != POOL_CONFIG) >+ internal_store_free(b, func, linenumber); >+ >+#ifndef RESTRICTED_MEMORY >+ if (pp->store_block_order > 13) pp->store_block_order--; >+#endif > } > > /* Cut out the debugging stuff for utilities, but stop picky compilers from > giving warnings. */ > >-#ifdef COMPILE_UTILITY >-func = func; >-linenumber = linenumber; >-#else >+#ifndef COMPILE_UTILITY > DEBUG(D_memory) >- debug_printf("---%d Rst %6p %5d %-14s %4d %d\n", pool, ptr, >+ debug_printf("---%d Rst %6p %5d %-14s %4d\tpool %d\n", pool, ptr, > count + oldmalloc - pool_malloc, > func, linenumber, pool_malloc); > #endif /* COMPILE_UTILITY */ > } > > >+/* Back up the pool pair, untainted and tainted, of the store_pool setting. >+Quoted pools are not handled. >+*/ >+ > rmark >-store_reset_3(rmark r, int pool, const char *func, int linenumber) >+store_reset_3(rmark r, const char * func, int linenumber) > { > void ** ptr = r; > >-if (pool >= POOL_TAINT_BASE) >+if (store_pool >= POOL_TAINT_BASE) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, >- "store_reset called for pool %d: %s %d\n", pool, func, linenumber); >+ "store_reset called for pool %d: %s %d\n", store_pool, func, linenumber); > if (!r) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, > "store_reset called with bad mark: %s %d\n", func, linenumber); > >-internal_store_reset(*ptr, pool + POOL_TAINT_BASE, func, linenumber); >-internal_store_reset(ptr, pool, func, linenumber); >+internal_store_reset(*ptr, store_pool + POOL_TAINT_BASE, func, linenumber); >+internal_store_reset(ptr, store_pool, func, linenumber); > return NULL; > } > > >+/**************/ > > /* Free tail-end unused allocation. This lets us allocate a big chunk > early, for cases when we only discover later how much was really needed. >@@ -580,32 +935,26 @@ > */ > > void >-store_release_above_3(void *ptr, const char *func, int linenumber) >+store_release_above_3(void * ptr, const char * func, int linenumber) > { >+pooldesc * pp; >+ > /* Search all pools' "current" blocks. If it isn't one of those, > ignore it (it usually will be). */ > >-for (int pool = 0; pool < nelem(current_block); pool++) >+if ((pp = pool_current_for_pointer(ptr))) > { >- storeblock * b = current_block[pool]; >- char * bc; >+ storeblock * b = pp->current_block; > int count, newlength; > >- if (!b) >- continue; >- >- bc = CS b + ALIGNED_SIZEOF_STOREBLOCK; >- if (CS ptr < bc || CS ptr > bc + b->length) >- continue; >- > /* Last store operation was not a get */ > >- store_last_get[pool] = NULL; >+ pp->store_last_get = NULL; > > /* Back up, rounding to the alignment if necessary. When testing, flatten > the released memory. */ > >- newlength = bc + b->length - CS ptr; >+ newlength = (CS b + ALIGNED_SIZEOF_STOREBLOCK) + b->length - CS ptr; > #ifndef COMPILE_UTILITY > if (debug_store) > { >@@ -618,20 +967,27 @@ > } > #endif > (void) VALGRIND_MAKE_MEM_NOACCESS(ptr, newlength); >- next_yield[pool] = CS ptr + (newlength % alignment); >- count = yield_length[pool]; >- count = (yield_length[pool] = newlength - (newlength % alignment)) - count; >+ pp->next_yield = CS ptr + (newlength % alignment); >+ count = pp->yield_length; >+ count = (pp->yield_length = newlength - (newlength % alignment)) - count; > > /* Cut out the debugging stuff for utilities, but stop picky compilers from > giving warnings. */ > >-#ifdef COMPILE_UTILITY >- func = func; >- linenumber = linenumber; >-#else >+#ifndef COMPILE_UTILITY > DEBUG(D_memory) >- debug_printf("---%d Rel %6p %5d %-14s %4d %d\n", pool, ptr, count, >- func, linenumber, pool_malloc); >+ { >+ quoted_pooldesc * qp; >+ for (qp = quoted_pools; qp; qp = qp->next) >+ if (pp == &qp->pool) >+ debug_printf("---Q%d Rel %6p %5d %-14s %4d\tpool %d\n", >+ (int)(qp - quoted_pools), >+ ptr, count, func, linenumber, pool_malloc); >+ if (!qp) >+ debug_printf("---%d Rel %6p %5d %-14s %4d\tpool %d\n", >+ (int)(pp - paired_pools), ptr, count, >+ func, linenumber, pool_malloc); >+ } > #endif > return; > } >@@ -644,10 +1000,16 @@ > > > rmark >-store_mark_3(const char *func, int linenumber) >+store_mark_3(const char * func, int linenumber) > { > void ** p; > >+#ifndef COMPILE_UTILITY >+DEBUG(D_memory) >+ debug_printf("---%d Mrk %-14s %4d\tpool %d\n", >+ store_pool, func, linenumber, pool_malloc); >+#endif /* COMPILE_UTILITY */ >+ > if (store_pool >= POOL_TAINT_BASE) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, > "store_mark called for pool %d: %s %d\n", store_pool, func, linenumber); >@@ -657,8 +1019,8 @@ > Reset uses the cookie to recover the t-mark, winds back the tainted pool with it > and winds back the untainted pool with the cookie. */ > >-p = store_get_3(sizeof(void *), FALSE, func, linenumber); >-*p = store_get_3(0, TRUE, func, linenumber); >+p = store_get_3(sizeof(void *), GET_UNTAINTED, func, linenumber); >+*p = store_get_3(0, GET_TAINTED, func, linenumber); > return p; > } > >@@ -674,6 +1036,7 @@ > > Arguments: > block block of store to consider >+ pp pool containing the block > func function from which called > linenumber line number in source file > >@@ -681,28 +1044,25 @@ > */ > > static void >-store_release_3(void * block, int pool, const char * func, int linenumber) >+store_release_3(void * block, pooldesc * pp, const char * func, int linenumber) > { > /* It will never be the first block, so no need to check that. */ > >-for (storeblock * b = chainbase[pool]; b; b = b->next) >+for (storeblock * b = pp->chainbase; b; b = b->next) > { > storeblock * bb = b->next; > if (bb && CS block == CS bb + ALIGNED_SIZEOF_STOREBLOCK) > { > int siz = bb->length + ALIGNED_SIZEOF_STOREBLOCK; > b->next = bb->next; >- nbytes[pool] -= siz; >+ pp->nbytes -= siz; > pool_malloc -= siz; >- nblocks[pool]--; >+ pp->nblocks--; > > /* Cut out the debugging stuff for utilities, but stop picky compilers > from giving warnings. */ > >-#ifdef COMPILE_UTILITY >- func = func; >- linenumber = linenumber; >-#else >+#ifndef COMPILE_UTILITY > DEBUG(D_memory) > debug_printf("-Release %6p %-20s %4d %d\n", (void *)bb, func, > linenumber, pool_malloc); >@@ -711,7 +1071,7 @@ > memset(bb, 0xF0, bb->length+ALIGNED_SIZEOF_STOREBLOCK); > #endif /* COMPILE_UTILITY */ > >- free(bb); >+ internal_store_free(bb, func, linenumber); > return; > } > } >@@ -735,35 +1095,30 @@ > has been allocated since. If so, releases that block. > > Arguments: >- block >- newsize >- len >+ oldblock >+ newsize requested size >+ len current size > > Returns: new location of data > */ > > void * >-store_newblock_3(void * block, BOOL tainted, int newsize, int len, >+store_newblock_3(void * oldblock, int newsize, int len, > const char * func, int linenumber) > { >-int pool = tainted ? store_pool + POOL_TAINT_BASE : store_pool; >-BOOL release_ok = !tainted && store_last_get[pool] == block; >-uschar * newtext; >- >-#if !defined(MACRO_PREDEF) && !defined(COMPILE_UTILITY) >-if (is_tainted(block) != tainted) >- die_tainted(US"store_newblock", CUS func, linenumber); >-#endif >+pooldesc * pp = pool_for_pointer(oldblock, func, linenumber); >+BOOL release_ok = !is_tainted(oldblock) && pp->store_last_get == oldblock; /*XXX why tainted not handled? */ >+uschar * newblock; > > if (len < 0 || len > newsize) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, > "bad memory extension requested (%d -> %d bytes) at %s %d", > len, newsize, func, linenumber); > >-newtext = store_get(newsize, tainted); >-memcpy(newtext, block, len); >-if (release_ok) store_release_3(block, pool, func, linenumber); >-return (void *)newtext; >+newblock = store_get(newsize, oldblock); >+memcpy(newblock, oldblock, len); >+if (release_ok) store_release_3(oldblock, pp, func, linenumber); >+return (void *)newblock; > } > > >@@ -786,37 +1141,43 @@ > */ > > static void * >-internal_store_malloc(int size, const char *func, int line) >+internal_store_malloc(size_t size, const char *func, int line) > { > void * yield; > >-if (size < 0 || size >= INT_MAX/2) >+/* Check specifically for a possibly result of conversion from >+a negative int, to the (unsigned, wider) size_t */ >+ >+if (size >= INT_MAX/2) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, >- "bad memory allocation requested (%d bytes) at %s %d", >- size, func, line); >+ "bad internal_store_malloc request (" SIZE_T_FMT " bytes) from %s %d", >+ size, func, line); > >+size += sizeof(size_t); /* space to store the size, used under debug */ > if (size < 16) size = 16; > >-if (!(yield = malloc((size_t)size))) >- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to malloc %d bytes of memory: " >+if (!(yield = malloc(size))) >+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to malloc " SIZE_T_FMT " bytes of memory: " > "called from line %d in %s", size, line, func); > >+#ifndef COMPILE_UTILITY >+DEBUG(D_any) *(size_t *)yield = size; >+#endif >+yield = US yield + sizeof(size_t); >+ > if ((nonpool_malloc += size) > max_nonpool_malloc) > max_nonpool_malloc = nonpool_malloc; > > /* Cut out the debugging stuff for utilities, but stop picky compilers from > giving warnings. */ > >-#ifdef COMPILE_UTILITY >-func = func; line = line; >-#else >- >+#ifndef COMPILE_UTILITY > /* If running in test harness, spend time making sure all the new store > is not filled with zeros so as to catch problems. */ > > if (f.running_in_test_harness) >- memset(yield, 0xF0, (size_t)size); >-DEBUG(D_memory) debug_printf("--Malloc %6p %5d bytes\t%-14s %4d\tpool %5d nonpool %5d\n", >+ memset(yield, 0xF0, size - sizeof(size_t)); >+DEBUG(D_memory) debug_printf("--Malloc %6p %5lu bytes\t%-20s %4d\tpool %5d nonpool %5d\n", > yield, size, func, line, pool_malloc, nonpool_malloc); > #endif /* COMPILE_UTILITY */ > >@@ -824,7 +1185,7 @@ > } > > void * >-store_malloc_3(int size, const char *func, int linenumber) >+store_malloc_3(size_t size, const char *func, int linenumber) > { > if (n_nonpool_blocks++ > max_nonpool_blocks) > max_nonpool_blocks = n_nonpool_blocks; >@@ -849,14 +1210,13 @@ > static void > internal_store_free(void * block, const char * func, int linenumber) > { >-#ifdef COMPILE_UTILITY >-func = func; >-linenumber = linenumber; >-#else >-DEBUG(D_memory) >- debug_printf("----Free %6p %-20s %4d\n", block, func, linenumber); >-#endif /* COMPILE_UTILITY */ >-free(block); >+uschar * p = US block - sizeof(size_t); >+#ifndef COMPILE_UTILITY >+DEBUG(D_any) nonpool_malloc -= *(size_t *)p; >+DEBUG(D_memory) debug_printf("----Free %6p %5ld bytes\t%-20s %4d\n", >+ block, *(size_t *)p, func, linenumber); >+#endif >+free(p); > } > > void >@@ -874,14 +1234,68 @@ > #ifndef COMPILE_UTILITY > DEBUG(D_memory) > { >+ int i; > debug_printf("----Exit nonpool max: %3d kB in %d blocks\n", > (max_nonpool_malloc+1023)/1024, max_nonpool_blocks); > debug_printf("----Exit npools max: %3d kB\n", max_pool_malloc/1024); >- for (int i = 0; i < NPOOLS; i++) >- debug_printf("----Exit pool %d max: %3d kB in %d blocks\t%s %s\n", >- i, maxbytes[i]/1024, maxblocks[i], poolclass[i], pooluse[i]); >+ >+ for (i = 0; i < N_PAIRED_POOLS; i++) >+ { >+ pooldesc * pp = paired_pools + i; >+ debug_printf("----Exit pool %2d max: %3d kB in %d blocks at order %u\t%s %s\n", >+ i, (pp->maxbytes+1023)/1024, pp->maxblocks, pp->maxorder, >+ poolclass[i], pooluse[i]); >+ } >+ i = 0; >+ for (quoted_pooldesc * qp = quoted_pools; qp; i++, qp = qp->next) >+ { >+ pooldesc * pp = &qp->pool; >+ debug_printf("----Exit pool Q%d max: %3d kB in %d blocks at order %u\ttainted quoted:%s\n", >+ i, (pp->maxbytes+1023)/1024, pp->maxblocks, pp->maxorder, lookup_list[qp->quoter]->name); >+ } > } > #endif > } > >+ >+/******************************************************************************/ >+/* Per-message pool management */ >+ >+static rmark message_reset_point = NULL; >+ >+void >+message_start(void) >+{ >+int oldpool = store_pool; >+store_pool = POOL_MESSAGE; >+if (!message_reset_point) message_reset_point = store_mark(); >+store_pool = oldpool; >+} >+ >+void >+message_tidyup(void) >+{ >+int oldpool; >+if (!message_reset_point) return; >+oldpool = store_pool; >+store_pool = POOL_MESSAGE; >+message_reset_point = store_reset(message_reset_point); >+store_pool = oldpool; >+} >+ >+/******************************************************************************/ >+/* Debug analysis of address */ >+ >+#ifndef COMPILE_UTILITY >+void >+debug_print_taint(const void * p) >+{ >+int q = quoter_for_address(p); >+if (!is_tainted(p)) return; >+debug_printf("(tainted"); >+if (is_real_quoter(q)) debug_printf(", quoted:%s", lookup_list[q]->name); >+debug_printf(")\n"); >+} >+#endif >+ > /* End of store.c */ >diff -ur exim.orig/src/store.h exim/src/store.h >--- exim.orig/src/store.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/store.h 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2009 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Header for Exim's memory allocation functions */ >@@ -13,15 +13,28 @@ > > /* Define symbols for identifying the store pools. */ > >-enum { POOL_MAIN, POOL_PERM, POOL_SEARCH, >+enum { POOL_MAIN, >+ POOL_PERM, >+ POOL_CONFIG, >+ POOL_SEARCH, >+ POOL_MESSAGE, >+ > POOL_TAINT_BASE, >- POOL_TAINT_MAIN = POOL_TAINT_BASE, POOL_TAINT_PERM, POOL_TAINT_SEARCH }; >+ >+ POOL_TAINT_MAIN = POOL_TAINT_BASE, >+ POOL_TAINT_PERM, >+ POOL_TAINT_CONFIG, >+ POOL_TAINT_SEARCH, >+ POOL_TAINT_MESSAGE, >+ >+ N_PAIRED_POOLS >+}; > > /* This variable (the one for the current pool) is set by store_get() to its > yield, and by store_reset() to NULL. This allows string_cat() to optimize its > store handling. */ > >-extern void *store_last_get[6]; >+extern void * store_last_get[]; > > /* This variable contains the current store pool number. */ > >@@ -30,35 +43,46 @@ > /* Macros for calling the memory allocation routines with > tracing information for debugging. */ > >-#define store_extend(addr, tainted, old, new) \ >- store_extend_3(addr, tainted, old, new, __FUNCTION__, __LINE__) >+#define store_extend(addr, old, new) \ >+ store_extend_3(addr, old, new, __FUNCTION__, __LINE__) > > #define store_free(addr) \ > store_free_3(addr, __FUNCTION__, __LINE__) > /* store_get & store_get_perm are in local_scan.h */ >+#define store_get_quoted(size, proto_mem, quoter) \ >+ store_get_quoted_3((size), (proto_mem), (quoter), __FUNCTION__, __LINE__) > #define store_malloc(size) \ > store_malloc_3(size, __FUNCTION__, __LINE__) > #define store_mark(void) \ > store_mark_3(__FUNCTION__, __LINE__) >-#define store_newblock(addr, tainted, newsize, datalen) \ >- store_newblock_3(addr, tainted, newsize, datalen, __FUNCTION__, __LINE__) >+#define store_newblock(oldblock, newsize, datalen) \ >+ store_newblock_3(oldblock, newsize, datalen, __FUNCTION__, __LINE__) > #define store_release_above(addr) \ > store_release_above_3(addr, __FUNCTION__, __LINE__) > #define store_reset(mark) \ >- store_reset_3(mark, store_pool, __FUNCTION__, __LINE__) >+ store_reset_3(mark, __FUNCTION__, __LINE__) > > > /* The real functions */ > typedef void ** rmark; > >-extern BOOL store_extend_3(void *, BOOL, int, int, const char *, int); >+extern BOOL store_extend_3(void *, int, int, const char *, int); > extern void store_free_3(void *, const char *, int); > /* store_get_3 & store_get_perm_3 are in local_scan.h */ >-extern void *store_malloc_3(int, const char *, int) ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT; >+extern void * store_get_quoted_3(int, const void *, unsigned, const char *, int); >+extern void * store_malloc_3(size_t, const char *, int) ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT; > extern rmark store_mark_3(const char *, int); >-extern void *store_newblock_3(void *, BOOL, int, int, const char *, int); >+extern void * store_newblock_3(void *, int, int, const char *, int); > extern void store_release_above_3(void *, const char *, int); >-extern rmark store_reset_3(rmark, int, const char *, int); >+extern rmark store_reset_3(rmark, const char *, int); >+ >+#define GET_UNTAINTED (const void *)0 >+#define GET_TAINTED (const void *)1 >+ >+extern int quoter_for_address(const void *); >+extern BOOL is_quoted_like(const void *, unsigned); >+extern BOOL is_real_quoter(int); >+extern void debug_print_taint(const void * p); > > #endif /* STORE_H */ > >diff -ur exim.orig/src/string.c exim/src/string.c >--- exim.orig/src/string.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/string.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Miscellaneous string-handling functions. Some are not required for >@@ -298,7 +298,7 @@ > const uschar *t = s; > uschar *ss, *tt; > >-while (*t != 0) >+while (*t) > { > int c = *t++; > if ( !mac_isprint(c) >@@ -313,7 +313,7 @@ > /* Get a new block of store guaranteed big enough to hold the > expanded string. */ > >-tt = ss = store_get(length + nonprintcount * 3 + 1, is_tainted(s)); >+tt = ss = store_get(length + nonprintcount * 3 + 1, s); > > /* Copy everything, escaping non printers. */ > >@@ -371,7 +371,7 @@ > if (!p) return s; > > len = Ustrlen(s) + 1; >-ss = store_get(len, is_tainted(s)); >+ss = store_get(len, s); > > q = ss; > off = p - s; >@@ -428,22 +428,18 @@ > */ > > uschar * >-string_copy_function(const uschar *s) >+string_copy_function(const uschar * s) > { >-return string_copy_taint(s, is_tainted(s)); >+return string_copy_taint(s, s); > } > >-/* This function assumes that memcpy() is faster than strcpy(). >-As above, but explicitly specifying the result taint status >+/* As above, but explicitly specifying the result taint status > */ > > uschar * >-string_copy_taint_function(const uschar * s, BOOL tainted) >+string_copy_taint_function(const uschar * s, const void * proto_mem) > { >-int len = Ustrlen(s) + 1; >-uschar *ss = store_get(len, tainted); >-memcpy(ss, s, len); >-return ss; >+return string_copy_taint(s, proto_mem); > } > > >@@ -463,12 +459,9 @@ > */ > > uschar * >-string_copyn_function(const uschar *s, int n) >+string_copyn_function(const uschar * s, int n) > { >-uschar *ss = store_get(n + 1, is_tainted(s)); >-Ustrncpy(ss, s, n); >-ss[n] = 0; >-return ss; >+return string_copyn(s, n); > } > #endif > >@@ -484,10 +477,10 @@ > */ > > uschar * >-string_copy_malloc(const uschar *s) >+string_copy_malloc(const uschar * s) > { > int len = Ustrlen(s) + 1; >-uschar *ss = store_malloc(len); >+uschar * ss = store_malloc(len); > memcpy(ss, s, len); > return ss; > } >@@ -506,37 +499,37 @@ > */ > > uschar * >-string_split_message(uschar *msg) >+string_split_message(uschar * msg) > { > uschar *s, *ss; > >-if (msg == NULL || Ustrlen(msg) <= 75) return msg; >+if (!msg || Ustrlen(msg) <= 75) return msg; > s = ss = msg = string_copy(msg); > > for (;;) > { > int i = 0; >- while (i < 75 && *ss != 0 && *ss != '\n') ss++, i++; >- if (*ss == 0) break; >+ while (i < 75 && *ss && *ss != '\n') ss++, i++; >+ if (!*ss) break; > if (*ss == '\n') > s = ++ss; > else > { >- uschar *t = ss + 1; >- uschar *tt = NULL; >+ uschar * t = ss + 1; >+ uschar * tt = NULL; > while (--t > s + 35) > { > if (*t == ' ') > { > if (t[-1] == ':') { tt = t; break; } >- if (tt == NULL) tt = t; >+ if (!tt) tt = t; > } > } > >- if (tt == NULL) /* Can't split behind - try ahead */ >+ if (!tt) /* Can't split behind - try ahead */ > { > t = ss + 1; >- while (*t != 0) >+ while (*t) > { > if (*t == ' ' || *t == '\n') > { tt = t; break; } >@@ -544,7 +537,7 @@ > } > } > >- if (tt == NULL) break; /* Can't find anywhere to split */ >+ if (!tt) break; /* Can't find anywhere to split */ > *tt = '\n'; > s = ss = tt+1; > } >@@ -572,12 +565,12 @@ > */ > > uschar * >-string_copy_dnsdomain(uschar *s) >+string_copy_dnsdomain(uschar * s) > { >-uschar *yield; >-uschar *ss = yield = store_get(Ustrlen(s) + 1, is_tainted(s)); >+uschar * yield; >+uschar * ss = yield = store_get(Ustrlen(s) + 1, GET_TAINTED); /* always treat as tainted */ > >-while (*s != 0) >+while (*s) > { > if (*s != '\\') > *ss++ = *s++; >@@ -586,7 +579,7 @@ > *ss++ = (s[1] - '0')*100 + (s[2] - '0')*10 + s[3] - '0'; > s += 4; > } >- else if (*(++s) != 0) >+ else if (*++s) > *ss++ = *s++; > } > >@@ -611,15 +604,15 @@ > */ > > uschar * >-string_dequote(const uschar **sptr) >+string_dequote(const uschar ** sptr) > { >-const uschar *s = *sptr; >-uschar *t, *yield; >+const uschar * s = * sptr; >+uschar * t, * yield; > > /* First find the end of the string */ > > if (*s != '\"') >- while (*s != 0 && !isspace(*s)) s++; >+ while (*s && !isspace(*s)) s++; > else > { > s++; >@@ -633,17 +626,17 @@ > > /* Get enough store to copy into */ > >-t = yield = store_get(s - *sptr + 1, is_tainted(*sptr)); >+t = yield = store_get(s - *sptr + 1, *sptr); > s = *sptr; > > /* Do the copy */ > > if (*s != '\"') >- while (*s != 0 && !isspace(*s)) *t++ = *s++; >+ while (*s && !isspace(*s)) *t++ = *s++; > else > { > s++; >- while (*s != 0 && *s != '\"') >+ while (*s && *s != '\"') > { > *t++ = *s == '\\' ? string_interpret_escape(&s) : *s; > s++; >@@ -671,13 +664,15 @@ > Arguments: > format a printf() format - deliberately char * rather than uschar * > because it will most usually be a literal string >+ func caller, for debug >+ line caller, for debug > ... arguments for format > > Returns: pointer to fresh piece of store containing sprintf'ed string > */ > > uschar * >-string_sprintf_trc(const char *format, const uschar * func, unsigned line, ...) >+string_sprintf_trc(const char * format, const uschar * func, unsigned line, ...) > { > #ifdef COMPILE_UTILITY > uschar buffer[STRING_SPRINTF_BUFFER_SIZE]; >@@ -725,7 +720,7 @@ > */ > > int >-strncmpic(const uschar *s, const uschar *t, int n) >+strncmpic(const uschar * s, const uschar * t, int n) > { > while (n--) > { >@@ -749,9 +744,9 @@ > */ > > int >-strcmpic(const uschar *s, const uschar *t) >+strcmpic(const uschar * s, const uschar * t) > { >-while (*s != 0) >+while (*s) > { > int c = tolower(*s++) - tolower(*t++); > if (c != 0) return c; >@@ -775,11 +770,11 @@ > Returns: pointer to substring in string, or NULL if not found > */ > >-uschar * >-strstric(uschar *s, uschar *t, BOOL space_follows) >+const uschar * >+strstric_c(const uschar * s, const uschar * t, BOOL space_follows) > { >-uschar *p = t; >-uschar *yield = NULL; >+const uschar * p = t; >+const uschar * yield = NULL; > int cl = tolower(*p); > int cu = toupper(*p); > >@@ -787,8 +782,8 @@ > { > if (*s == cl || *s == cu) > { >- if (yield == NULL) yield = s; >- if (*(++p) == 0) >+ if (!yield) yield = s; >+ if (!*++p) > { > if (!space_follows || s[1] == ' ' || s[1] == '\n' ) return yield; > yield = NULL; >@@ -798,7 +793,7 @@ > cu = toupper(*p); > s++; > } >- else if (yield != NULL) >+ else if (yield) > { > yield = NULL; > p = t; >@@ -810,6 +805,11 @@ > return NULL; > } > >+uschar * >+strstric(uschar * s, uschar * t, BOOL space_follows) >+{ >+return US strstric_c(s, t, space_follows); >+} > > > #ifdef COMPILE_UTILITY >@@ -858,18 +858,24 @@ > separator a pointer to the separator character in an int (see above) > buffer where to put a copy of the next string in the list; or > NULL if the next string is returned in new memory >+ Note that if the list is tainted then a provided buffer must be >+ also (else we trap, with a message referencing the callsite). >+ If we do the allocation, taint is handled there. > buflen when buffer is not NULL, the size of buffer; otherwise ignored > >+ func caller, for debug >+ line caller, for debug >+ > Returns: pointer to buffer, containing the next substring, > or NULL if no more substrings > */ > > uschar * >-string_nextinlist_trc(const uschar **listptr, int *separator, uschar *buffer, int buflen, >- const uschar * func, int line) >+string_nextinlist_trc(const uschar ** listptr, int * separator, uschar * buffer, >+ int buflen, const uschar * func, int line) > { > int sep = *separator; >-const uschar *s = *listptr; >+const uschar * s = *listptr; > BOOL sep_is_special; > > if (!s) return NULL; >@@ -905,6 +911,7 @@ > sep_is_special = iscntrl(sep); > > /* Handle the case when a buffer is provided. */ >+/*XXX need to also deal with qouted-requirements mismatch */ > > if (buffer) > { >@@ -932,14 +939,11 @@ > start of a string. Avoid getting working memory for an empty item. */ > > if (*s == sep) >- { >- s++; >- if (*s != sep || sep_is_special) >+ if (*++s != sep || sep_is_special) > { > *listptr = s; > return string_copy(US""); > } >- } > > /* Not an empty string; the first character is guaranteed to be a data > character. */ >@@ -952,12 +956,15 @@ > s = ss; > if (!*s || *++s != sep || sep_is_special) break; > } >+ >+ /* Trim trailing spaces from the returned string */ >+ > /* while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--; */ > while ( g->ptr > 0 && isspace(g->s[g->ptr-1]) > && (g->ptr == 1 || g->s[g->ptr-2] != '\\') ) > g->ptr--; > buffer = string_from_gstring(g); >- gstring_release_unused(g); >+ gstring_release_unused_trc(g, CCS func, line); > } > > /* Update the current pointer and return the new string */ >@@ -1081,7 +1088,6 @@ > { > int p = g->ptr; > int oldsize = g->size; >-BOOL tainted = is_tainted(g->s); > > /* Mostly, string_cat() is used to build small strings of a few hundred > characters at most. There are times, however, when the strings are very much >@@ -1113,8 +1119,8 @@ > was the last item on the dynamic memory stack. This is the case if it matches > store_last_get. */ > >-if (!store_extend(g->s, tainted, oldsize, g->size)) >- g->s = store_newblock(g->s, tainted, g->size, p); >+if (!store_extend(g->s, oldsize, g->size)) >+ g->s = store_newblock(g->s, g->size, p); > } > > >@@ -1129,38 +1135,49 @@ > sometimes called to extract parts of other strings. > > Arguments: >- string points to the start of the string that is being built, or NULL >- if this is a new string that has no contents yet >+ g growable-string that is being built, or NULL if not assigned yet > s points to characters to add > count count of characters to add; must not exceed the length of s, if s > is a C string. > >-Returns: pointer to the start of the string, changed if copied for expansion. >+Returns: growable string, changed if copied for expansion. > Note that a NUL is not added, though space is left for one. This is > because string_cat() is often called multiple times to build up a > string - there's no point adding the NUL till the end. >+ NULL is a possible return. > > */ > /* coverity[+alloc] */ > > gstring * >-string_catn(gstring * g, const uschar *s, int count) >+string_catn(gstring * g, const uschar * s, int count) > { > int p; >-BOOL srctaint = is_tainted(s); > > if (count < 0) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, > "internal error in string_catn (count %d)", count); >+if (count == 0) return g; > >+/*debug_printf("string_catn '%.*s'\n", count, s);*/ > if (!g) > { > unsigned inc = count < 4096 ? 127 : 1023; >- unsigned size = ((count + inc) & ~inc) + 1; >- g = string_get_tainted(size, srctaint); >+ unsigned size = ((count + inc) & ~inc) + 1; /* round up requested count */ >+ g = string_get_tainted(size, s); >+ } >+else if (!g->s) /* should not happen */ >+ { >+ g->s = string_copyn(s, count); >+ g->ptr = count; >+ g->size = count; /*XXX suboptimal*/ >+ return g; >+ } >+else if (is_incompatible(g->s, s)) >+ { >+/* debug_printf("rebuf A\n"); */ >+ gstring_rebuffer(g, s); > } >-else if (srctaint && !is_tainted(g->s)) >- gstring_rebuffer(g); > > if (g->ptr < 0 || g->ptr > g->size) > log_write(0, LOG_MAIN|LOG_PANIC_DIE, >@@ -1181,9 +1198,9 @@ > > > gstring * >-string_cat(gstring *string, const uschar *s) >+string_cat(gstring * g, const uschar * s) > { >-return string_catn(string, s, Ustrlen(s)); >+return string_catn(g, s, Ustrlen(s)); > } > > >@@ -1196,30 +1213,29 @@ > It calls string_cat() to do the dirty work. > > Arguments: >- string expanding-string that is being built, or NULL >- if this is a new string that has no contents yet >+ g growable-string that is being built, or NULL if not yet assigned > count the number of strings to append > ... "count" uschar* arguments, which must be valid zero-terminated > C strings > >-Returns: pointer to the start of the string, changed if copied for expansion. >+Returns: growable string, changed if copied for expansion. > The string is not zero-terminated - see string_cat() above. > */ > > __inline__ gstring * >-string_append(gstring *string, int count, ...) >+string_append(gstring * g, int count, ...) > { > va_list ap; > > va_start(ap, count); > while (count-- > 0) > { >- uschar *t = va_arg(ap, uschar *); >- string = string_cat(string, t); >+ uschar * t = va_arg(ap, uschar *); >+ g = string_cat(g, t); > } > va_end(ap); > >-return string; >+return g; > } > #endif > >@@ -1255,7 +1271,7 @@ > string_format_trc(uschar * buffer, int buflen, > const uschar * func, unsigned line, const char * format, ...) > { >-gstring g = { .size = buflen, .ptr = 0, .s = buffer }, *gp; >+gstring g = { .size = buflen, .ptr = 0, .s = buffer }, * gp; > va_list ap; > va_start(ap, format); > gp = string_vformat_trc(&g, func, line, STRING_SPRINTF_BUFFER_SIZE, >@@ -1298,13 +1314,12 @@ > > gstring * > string_vformat_trc(gstring * g, const uschar * func, unsigned line, >- unsigned size_limit, unsigned flags, const char *format, va_list ap) >+ unsigned size_limit, unsigned flags, const char * format, va_list ap) > { > enum ltypes { L_NORMAL=1, L_SHORT=2, L_LONG=3, L_LONGLONG=4, L_LONGDOUBLE=5, L_SIZE=6 }; > > int width, precision, off, lim, need; > const char * fp = format; /* Deliberately not unsigned */ >-BOOL dest_tainted = FALSE; > > string_datestamp_offset = -1; /* Datestamp not inserted */ > string_datestamp_length = 0; /* Datestamp not inserted */ >@@ -1317,16 +1332,15 @@ > > /* Ensure we have a string, to save on checking later */ > if (!g) g = string_get(16); >-else if (!(flags & SVFMT_TAINT_NOCHK)) dest_tainted = is_tainted(g->s); > >-if (!(flags & SVFMT_TAINT_NOCHK) && !dest_tainted && is_tainted(format)) >+if (!(flags & SVFMT_TAINT_NOCHK) && is_incompatible(g->s, format)) > { > #ifndef MACRO_PREDEF > if (!(flags & SVFMT_REBUFFER)) > die_tainted(US"string_vformat", func, line); > #endif >- gstring_rebuffer(g); >- dest_tainted = TRUE; >+/* debug_printf("rebuf B\n"); */ >+ gstring_rebuffer(g, format); > } > #endif /*!COMPILE_UTILITY*/ > >@@ -1338,7 +1352,7 @@ > while (*fp) > { > int length = L_NORMAL; >- int *nptr; >+ int * nptr; > int slen; > const char *null = "NULL"; /* ) These variables */ > const char *item_start, *s; /* ) are deliberately */ >@@ -1546,12 +1560,12 @@ > if (!s) s = null; > slen = Ustrlen(s); > >- if (!(flags & SVFMT_TAINT_NOCHK) && !dest_tainted && is_tainted(s)) >+ if (!(flags & SVFMT_TAINT_NOCHK) && is_incompatible(g->s, s)) > if (flags & SVFMT_REBUFFER) > { >- gstring_rebuffer(g); >+/* debug_printf("%s %d: untainted workarea, tainted %%s :- rebuffer\n", __FUNCTION__, __LINE__); */ >+ gstring_rebuffer(g, s); > gp = CS g->s + g->ptr; >- dest_tainted = TRUE; > } > #ifndef MACRO_PREDEF > else >@@ -1640,16 +1654,17 @@ > "Permission denied", reads and includes the euid and egid. > > Arguments: >- eno the value of errno after the failure > format a text format string - deliberately not uschar * >+ func caller, for debug >+ line caller, for debug > ... arguments for the format string > > Returns: a message, in dynamic store > */ > > uschar * >-string_open_failed_trc(int eno, const uschar * func, unsigned line, >- const char *format, ...) >+string_open_failed_trc(const uschar * func, unsigned line, >+ const char * format, ...) > { > va_list ap; > gstring * g = string_get(1024); >@@ -1664,22 +1679,26 @@ > va_start(ap, format); > (void) string_vformat_trc(g, func, line, STRING_SPRINTF_BUFFER_SIZE, > SVFMT_REBUFFER, format, ap); >-string_from_gstring(g); >-gstring_release_unused(g); > va_end(ap); > >-return eno == EACCES >- ? string_sprintf("%s: %s (euid=%ld egid=%ld)", g->s, strerror(eno), >- (long int)geteuid(), (long int)getegid()) >- : string_sprintf("%s: %s", g->s, strerror(eno)); >+g = string_catn(g, US": ", 2); >+g = string_cat(g, US strerror(errno)); >+ >+if (errno == EACCES) >+ { >+ int save_errno = errno; >+ g = string_fmt_append(g, " (euid=%ld egid=%ld)", >+ (long int)geteuid(), (long int)getegid()); >+ errno = save_errno; >+ } >+gstring_release_unused(g); >+return string_from_gstring(g); > } >-#endif /* COMPILE_UTILITY */ > > > > > >-#ifndef COMPILE_UTILITY > /* qsort(3), currently used to sort the environment variables > for -bP environment output, needs a function to compare two pointers to string > pointers. Here it is. */ >@@ -1706,6 +1725,7 @@ > uschar buffer[256]; > > printf("Testing is_ip_address\n"); >+store_init(); > > while (fgets(CS buffer, sizeof(buffer), stdin) != NULL) > { >diff -ur exim.orig/src/structs.h exim/src/structs.h >--- exim.orig/src/structs.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/structs.h 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -133,11 +133,15 @@ > uschar *name; /* Instance name */ > struct driver_info *info; /* Points to info for this driver */ > void *options_block; /* Pointer to private options */ >+ > uschar *driver_name; /* All start with this generic option */ >+ const uschar *srcfile; /* and config source info for errors */ >+ int srcline; > } driver_instance; > > typedef struct driver_info { > uschar *driver_name; /* Name of driver */ >+ > optionlist *options; /* Table of private options names */ > int *options_count; /* -> Number of entries in table */ > void *options_block; /* Points to default private block */ >@@ -159,6 +163,9 @@ > struct transport_info *info; /* Info for this driver */ > void *options_block; /* Pointer to private options */ > uschar *driver_name; /* Must be first */ >+ const uschar *srcfile; >+ int srcline; >+ > int (*setup)( /* Setup entry point */ > struct transport_instance *, > struct address_item *, >@@ -283,6 +290,8 @@ > struct router_info *info; > void *options_block; /* Pointer to private options */ > uschar *driver_name; /* Must be first */ >+ const uschar *srcfile; >+ int srcline; > > uschar *address_data; /* Arbitrary data */ > #ifdef EXPERIMENTAL_BRIGHTMAIL >@@ -400,6 +409,9 @@ > struct auth_info *info; /* Pointer to driver info block */ > void *options_block; /* Pointer to private options */ > uschar *driver_name; /* Must be first */ >+ const uschar *srcfile; >+ int srcline; >+ > uschar *advertise_condition; /* Are we going to advertise this?*/ > uschar *client_condition; /* Should the client try this? */ > uschar *public_name; /* Advertised name */ >@@ -435,8 +447,8 @@ > int, /* command timeout */ > uschar *, /* buffer for reading response */ > int); /* sizeof buffer */ >- void (*version_report)( /* diagnostic version reporting */ >- FILE *); /* I/O stream to print to */ >+ gstring * (*version_report)( /* diagnostic version reporting */ >+ gstring *); /* string to append to */ > void (*macros_create)(void); /* feature-macro creation */ > } auth_info; > >@@ -518,9 +530,6 @@ > uschar *remove_headers; /* list of those to remove */ > void *variables; /* router-vasriables */ > >-#ifdef EXPERIMENTAL_SRS >- uschar *srs_sender; /* Change return path when delivering */ >-#endif > BOOL ignore_error:1; /* ignore delivery error */ > #ifdef SUPPORT_I18N > BOOL utf8_msg:1; /* requires SMTPUTF8 processing */ >@@ -626,6 +635,8 @@ > BOOL af_verify_routed:1; /* for cached sender verify: routed OK */ > BOOL af_verify_callout:1; /* for cached sender verify: callout was specified */ > BOOL af_include_affixes:1; /* delivered with affixes in RCPT */ >+ BOOL af_new_conn:1; /* delivered on an fresh TCP conn */ >+ BOOL af_cont_conn:1; /* delivered (with new MAIL cmd) on an existing TCP conn */ > BOOL af_cert_verified:1; /* delivered with verified TLS cert */ > BOOL af_pass_message:1; /* pass message in bounces */ > BOOL af_bad_reply:1; /* filter could not generate autoreply */ >@@ -647,7 +658,7 @@ > #ifdef SUPPORT_I18N > BOOL af_utf8_downcvt:1; /* downconvert was done for delivery */ > #endif >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifndef DISABLE_TLS_RESUME > BOOL af_tls_resume:1; /* TLS used a resumed session */ > #endif > } flags; >@@ -820,6 +831,11 @@ > int host_af; > uschar * interface; > >+ int sock; /* used for a bound but not connected socket */ >+ uschar * sending_ip_address; /* used for TLS resumption */ >+ const uschar * host_lbserver; /* ditto, for server-behind LB */ >+ BOOL have_lbserver:1; /* host_lbserver is valid */ >+ > #ifdef SUPPORT_DANE > BOOL dane:1; /* connection must do dane */ > dns_answer tlsa_dnsa; /* strictly, this should use tainted mem */ >diff -ur exim.orig/src/tls.c exim/src/tls.c >--- exim.orig/src/tls.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/tls.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* This module provides TLS (aka SSL) support for Exim. The code for OpenSSL is >@@ -25,6 +25,11 @@ > #endif > > >+/* Forward decl. */ >+static void tls_client_resmption_key(tls_support *, smtp_connect_args *, >+ smtp_transport_options_block *); >+ >+ > #if defined(MACRO_PREDEF) && !defined(DISABLE_TLS) > # include "macro_predef.h" > # ifdef USE_GNUTLS >@@ -36,6 +41,18 @@ > > #ifndef MACRO_PREDEF > >+static void tls_per_lib_daemon_init(void); >+static void tls_per_lib_daemon_tick(void); >+static unsigned tls_server_creds_init(void); >+static void tls_server_creds_invalidate(void); >+static void tls_client_creds_init(transport_instance *, BOOL); >+static void tls_client_creds_invalidate(transport_instance *); >+static void tls_daemon_creds_reload(void); >+static BOOL opt_set_and_noexpand(const uschar *); >+static BOOL opt_unset_or_noexpand(const uschar *); >+ >+ >+ > /* This module is compiled only when it is specifically requested in the > build-time configuration. However, some compilers don't like compiling empty > modules, so keep them happy with a dummy when skipping the rest. Make it >@@ -45,7 +62,9 @@ > > #ifdef DISABLE_TLS > static void dummy(int x) { dummy(x-1); } >-#else >+#else /* most of the rest of the file */ >+ >+const exim_tlslib_state null_tls_preload = {0}; > > /* Static variables that are used for buffering data by both sets of > functions and the common functions below. >@@ -62,6 +81,13 @@ > static BOOL ssl_xfer_error = FALSE; > #endif > >+#ifdef EXIM_HAVE_KEVENT >+# define KEV_SIZE 16 /* Eight file,dir pairs */ >+static struct kevent kev[KEV_SIZE]; >+static int kev_used = 0; >+#endif >+ >+static unsigned tls_creds_expire = 0; > > /************************************************* > * Expand string; give error on failure * >@@ -96,6 +122,299 @@ > } > > >+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) >+/* Add the directory for a filename to the inotify handle, creating that if >+needed. This is enough to see changes to files in that dir. >+Return boolean success. >+ >+The word "system" fails, which is on the safe side as we don't know what >+directory it implies nor if the TLS library handles a watch for us. >+ >+The string "system,cache" is recognised and explicitly accepted without >+setting a watch. This permits the system CA bundle to be cached even though >+we have no way to tell when it gets modified by an update. >+The call chain for OpenSSL uses a (undocumented) call into the library >+to discover the actual file. We don't know what GnuTLS uses. >+ >+A full set of caching including the CAs takes 35ms output off of the >+server tls_init() (GnuTLS, Fedora 32, 2018-class x86_64 laptop hardware). >+*/ >+static BOOL >+tls_set_one_watch(const uschar * filename) >+# ifdef EXIM_HAVE_INOTIFY >+{ >+uschar * s; >+ >+if (Ustrcmp(filename, "system,cache") == 0) return TRUE; >+ >+if (!(s = Ustrrchr(filename, '/'))) return FALSE; >+s = string_copyn(filename, s - filename); /* mem released by tls_set_watch */ >+DEBUG(D_tls) debug_printf("watch dir '%s'\n", s); >+ >+/*XXX unclear what effect symlinked files will have for inotify */ >+ >+if (inotify_add_watch(tls_watch_fd, CCS s, >+ IN_ONESHOT | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF >+ | IN_MOVED_FROM | IN_MOVED_TO | IN_MOVE_SELF) >= 0) >+ return TRUE; >+DEBUG(D_tls) debug_printf("notify_add_watch: %s\n", strerror(errno)); >+return FALSE; >+} >+# endif >+# ifdef EXIM_HAVE_KEVENT >+{ >+uschar * s, * t; >+int fd1, fd2, i, j, cnt = 0; >+struct stat sb; >+#ifdef OpenBSD >+struct kevent k_dummy; >+struct timespec ts = {0}; >+#endif >+ >+errno = 0; >+if (Ustrcmp(filename, "system,cache") == 0) return TRUE; >+ >+for (;;) >+ { >+ if (kev_used > KEV_SIZE-2) { s = US"out of kev space"; goto bad; } >+ if (!(s = Ustrrchr(filename, '/'))) return FALSE; >+ s = string_copyn(filename, s - filename); /* mem released by tls_set_watch */ >+ >+ /* The dir open will fail if there is a symlink on the path. Fine; it's too >+ much effort to handle all possible cases; just refuse the preload. */ >+ >+ if ((fd2 = open(CCS s, O_RDONLY | O_NOFOLLOW)) < 0) { s = US"open dir"; goto bad; } >+ >+ if ((lstat(CCS filename, &sb)) < 0) { s = US"lstat"; goto bad; } >+ if (!S_ISLNK(sb.st_mode)) >+ { >+ if ((fd1 = open(CCS filename, O_RDONLY | O_NOFOLLOW)) < 0) >+ { s = US"open file"; goto bad; } >+ DEBUG(D_tls) debug_printf("watch file '%s':\t%d\n", filename, fd1); >+ EV_SET(&kev[kev_used++], >+ (uintptr_t)fd1, >+ EVFILT_VNODE, >+ EV_ADD | EV_ENABLE | EV_ONESHOT, >+ NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND >+ | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE, >+ 0, >+ NULL); >+ cnt++; >+ } >+ DEBUG(D_tls) debug_printf("watch dir '%s':\t%d\n", s, fd2); >+ EV_SET(&kev[kev_used++], >+ (uintptr_t)fd2, >+ EVFILT_VNODE, >+ EV_ADD | EV_ENABLE | EV_ONESHOT, >+ NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND >+ | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE, >+ 0, >+ NULL); >+ cnt++; >+ >+ if (!(S_ISLNK(sb.st_mode))) break; >+ >+ t = store_get(1024, GET_UNTAINTED); >+ Ustrncpy(t, s, 1022); >+ j = Ustrlen(s); >+ t[j++] = '/'; >+ if ((i = readlink(CCS filename, (void *)(t+j), 1023-j)) < 0) { s = US"readlink"; goto bad; } >+ filename = t; >+ *(t += i+j) = '\0'; >+ store_release_above(t+1); >+ } >+ >+#ifdef OpenBSD >+if (kevent(tls_watch_fd, &kev[kev_used-cnt], cnt, &k_dummy, 1, &ts) >= 0) >+ return TRUE; >+#else >+if (kevent(tls_watch_fd, &kev[kev_used-cnt], cnt, NULL, 0, NULL) >= 0) >+ return TRUE; >+#endif >+s = US"kevent"; >+ >+bad: >+DEBUG(D_tls) >+ if (errno) >+ debug_printf("%s: %s: %s\n", __FUNCTION__, s, strerror(errno)); >+ else >+ debug_printf("%s: %s\n", __FUNCTION__, s); >+return FALSE; >+} >+# endif /*EXIM_HAVE_KEVENT*/ >+ >+ >+/* Create an inotify facility if needed. >+Then set watches on the dir containing the given file or (optionally) >+list of files. Return boolean success. */ >+ >+static BOOL >+tls_set_watch(const uschar * filename, BOOL list) >+{ >+rmark r; >+BOOL rc = FALSE; >+ >+if (!filename || !*filename) return TRUE; >+if (Ustrncmp(filename, "system", 6) == 0) return TRUE; >+ >+DEBUG(D_tls) debug_printf("tls_set_watch: '%s'\n", filename); >+ >+if ( tls_watch_fd < 0 >+# ifdef EXIM_HAVE_INOTIFY >+ && (tls_watch_fd = inotify_init1(O_CLOEXEC)) < 0 >+# endif >+# ifdef EXIM_HAVE_KEVENT >+ && (tls_watch_fd = kqueue()) < 0 >+# endif >+ ) >+ { >+ DEBUG(D_tls) debug_printf("inotify_init: %s\n", strerror(errno)); >+ return FALSE; >+ } >+ >+r = store_mark(); >+ >+if (list) >+ { >+ int sep = 0; >+ for (uschar * s; s = string_nextinlist(&filename, &sep, NULL, 0); ) >+ if (!(rc = tls_set_one_watch(s))) break; >+ } >+else >+ rc = tls_set_one_watch(filename); >+ >+store_reset(r); >+if (!rc) DEBUG(D_tls) debug_printf("tls_set_watch() fail on '%s': %s\n", filename, strerror(errno)); >+return rc; >+} >+ >+ >+void >+tls_watch_discard_event(int fd) >+{ >+#ifdef EXIM_HAVE_INOTIFY >+(void) read(fd, big_buffer, big_buffer_size); >+#endif >+#ifdef EXIM_HAVE_KEVENT >+struct kevent kev; >+struct timespec t = {0}; >+(void) kevent(fd, NULL, 0, &kev, 1, &t); >+#endif >+} >+#endif /*EXIM_HAVE_INOTIFY*/ >+ >+ >+void >+tls_client_creds_reload(BOOL watch) >+{ >+for(transport_instance * t = transports; t; t = t->next) >+ if (Ustrcmp(t->driver_name, "smtp") == 0) >+ { >+ tls_client_creds_invalidate(t); >+ tls_client_creds_init(t, watch); >+ } >+} >+ >+ >+void >+tls_watch_invalidate(void) >+{ >+if (tls_watch_fd < 0) return; >+ >+#ifdef EXIM_HAVE_KEVENT >+/* Close the files we had open for kevent */ >+for (int i = 0; i < kev_used; i++) >+ { >+ DEBUG(D_tls) debug_printf("closing watch fd: %d\n", (int) kev[i].ident); >+ (void) close((int) kev[i].ident); >+ kev[i].ident = (uintptr_t)-1; >+ } >+kev_used = 0; >+#endif >+ >+close(tls_watch_fd); >+tls_watch_fd = -1; >+} >+ >+ >+static void >+tls_daemon_creds_reload(void) >+{ >+unsigned lifetime; >+ >+#ifdef EXIM_HAVE_KEVENT >+tls_watch_invalidate(); >+#endif >+ >+tls_server_creds_invalidate(); >+tls_creds_expire = (lifetime = tls_server_creds_init()) >+ ? time(NULL) + lifetime : 0; >+ >+tls_client_creds_reload(TRUE); >+} >+ >+ >+/* Utility predicates for use by the per-library code */ >+static BOOL >+opt_set_and_noexpand(const uschar * opt) >+{ return opt && *opt && Ustrchr(opt, '$') == NULL; } >+ >+static BOOL >+opt_unset_or_noexpand(const uschar * opt) >+{ return !opt || Ustrchr(opt, '$') == NULL; } >+ >+ >+ >+/* Called every time round the daemon loop. >+ >+If we reloaded fd-watcher, return the old watch fd >+having modified the global for the new one. Otherwise >+return -1. >+*/ >+ >+int >+tls_daemon_tick(void) >+{ >+int old_watch_fd = tls_watch_fd; >+ >+tls_per_lib_daemon_tick(); >+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) >+if (tls_creds_expire && time(NULL) >= tls_creds_expire) >+ { >+ /* The server cert is a selfsign, with limited lifetime. Dump it and >+ generate a new one. Reload the rest of the creds also as the machinery >+ is all there. */ >+ >+ DEBUG(D_tls) debug_printf("selfsign cert rotate\n"); >+ tls_creds_expire = 0; >+ tls_daemon_creds_reload(); >+ return old_watch_fd; >+ } >+else if (tls_watch_trigger_time && time(NULL) >= tls_watch_trigger_time + 5) >+ { >+ /* Called, after a delay for multiple file ops to get done, from >+ the daemon when any of the watches added (above) fire. >+ Dump the set of watches and arrange to reload cached creds (which >+ will set up new watches). */ >+ >+ DEBUG(D_tls) debug_printf("watch triggered\n"); >+ tls_watch_trigger_time = tls_creds_expire = 0; >+ tls_daemon_creds_reload(); >+ return old_watch_fd; >+ } >+#endif >+return -1; >+} >+ >+/* Called once at daemon startup */ >+ >+void >+tls_daemon_init(void) >+{ >+tls_per_lib_daemon_init(); >+} >+ >+ > /************************************************* > * Timezone environment flipping * > *************************************************/ >@@ -372,7 +691,6 @@ > return FALSE; > } > >- > /* Environment cleanup: The GnuTLS library uses SSLKEYLOGFILE in the environment > and writes a file by that name. Our OpenSSL code does the same, using keying > info from the library API. >@@ -478,6 +796,45 @@ > > > >+static void >+tls_client_resmption_key(tls_support * tlsp, smtp_connect_args * conn_args, >+ smtp_transport_options_block * ob) >+{ >+#ifndef DISABLE_TLS_RESUME >+hctx * h = &tlsp->resume_hctx; >+blob b; >+gstring * g; >+ >+DEBUG(D_tls) if (conn_args->host_lbserver) >+ debug_printf("TLS: lbserver '%s'\n", conn_args->host_lbserver); >+ >+# ifdef EXIM_HAVE_SHA2 >+exim_sha_init(h, HASH_SHA2_256); >+# else >+exim_sha_init(h, HASH_SHA1); >+# endif >+exim_sha_update_string(h, conn_args->host_lbserver); >+# ifdef SUPPORT_DANE >+if (conn_args->dane) >+ exim_sha_update(h, CUS &conn_args->tlsa_dnsa, sizeof(dns_answer)); >+# endif >+exim_sha_update_string(h, conn_args->host->address); >+exim_sha_update(h, CUS &conn_args->host->port, sizeof(conn_args->host->port)); >+exim_sha_update_string(h, conn_args->sending_ip_address); >+exim_sha_update_string(h, openssl_options); >+exim_sha_update_string(h, ob->tls_require_ciphers); >+exim_sha_update_string(h, tlsp->sni); >+# ifdef EXIM_HAVE_ALPN >+exim_sha_update_string(h, ob->tls_alpn); >+# endif >+exim_sha_finish(h, &b); >+for (g = string_get(b.len*2+1); b.len-- > 0; ) >+ g = string_fmt_append(g, "%02x", *b.data++); >+tlsp->resume_index = string_from_gstring(g); >+DEBUG(D_tls) debug_printf("TLS: resume session index %s\n", tlsp->resume_index); >+#endif >+} >+ > #endif /*!DISABLE_TLS*/ > #endif /*!MACRO_PREDEF*/ > >diff -ur exim.orig/src/tlscert-gnu.c exim/src/tlscert-gnu.c >--- exim.orig/src/tlscert-gnu.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/tlscert-gnu.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ > /* Copyright (c) Jeremy Harris 2014 - 2018 */ > > /* This file provides TLS/SSL support for Exim using the GnuTLS library, >@@ -114,7 +115,7 @@ > if (mod && Ustrcmp(mod, "int") == 0) > return string_sprintf("%u", (unsigned)t); > >-cp = store_get(len, FALSE); >+cp = store_get(len, GET_UNTAINTED); > if (f.timestamps_utc) > { > uschar * tz = to_tz(US"GMT0"); >@@ -148,7 +149,7 @@ > != GNUTLS_E_SHORT_MEMORY_BUFFER) > return g_err("gi0", __FUNCTION__, ret); > >-cp = store_get(siz, TRUE); >+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); > >@@ -202,7 +203,7 @@ > != GNUTLS_E_SHORT_MEMORY_BUFFER) > return g_err("gs0", __FUNCTION__, ret); > >-cp1 = store_get(len*4+1, TRUE); >+cp1 = store_get(len*4+1, GET_TAINTED); > if (gnutls_x509_crt_get_signature((gnutls_x509_crt_t)cert, CS cp1, &len) != 0) > return g_err("gs1", __FUNCTION__, ret); > >@@ -232,7 +233,7 @@ > != GNUTLS_E_SHORT_MEMORY_BUFFER) > return g_err("gs0", __FUNCTION__, ret); > >-cp = store_get(siz, TRUE); >+cp = store_get(siz, GET_TAINTED); > if ((ret = gnutls_x509_crt_get_dn(cert, CS cp, &siz)) < 0) > return g_err("gs1", __FUNCTION__, ret); > >@@ -260,7 +261,7 @@ > if (ret != GNUTLS_E_SHORT_MEMORY_BUFFER) > return g_err("ge0", __FUNCTION__, ret); > >-cp1 = store_get(siz*4 + 1, TRUE); >+cp1 = store_get(siz*4 + 1, GET_TAINTED); > > ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert, > CS oid, idx, CS cp1, &siz, &crit); >@@ -288,13 +289,13 @@ > uschar * ele; > int match = -1; > >-while (mod) >+if (mod) while (*mod) > { > if (*mod == '>' && *++mod) sep = *mod++; >- else if (Ustrcmp(mod, "dns")==0) { match = GNUTLS_SAN_DNSNAME; mod += 3; } >- else if (Ustrcmp(mod, "uri")==0) { match = GNUTLS_SAN_URI; mod += 3; } >- else if (Ustrcmp(mod, "mail")==0) { match = GNUTLS_SAN_RFC822NAME; mod += 4; } >- else continue; >+ else if (Ustrncmp(mod, "dns", 3)==0) { match = GNUTLS_SAN_DNSNAME; mod += 3; } >+ else if (Ustrncmp(mod, "uri", 3)==0) { match = GNUTLS_SAN_URI; mod += 3; } >+ else if (Ustrncmp(mod, "mail", 4)==0) { match = GNUTLS_SAN_RFC822NAME; mod += 4; } >+ else break; > > if (*mod++ != ',') > break; >@@ -316,7 +317,7 @@ > return g_err("gs0", __FUNCTION__, ret); > } > >- ele = store_get(siz+1, TRUE); >+ 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) > return g_err("gs1", __FUNCTION__, ret); >@@ -399,7 +400,7 @@ > return g_err("gc0", __FUNCTION__, ret); > } > >- ele = store_get(siz, TRUE); >+ ele = store_get(siz, GET_TAINTED); > if ((ret = gnutls_x509_crt_get_crl_dist_points( > (gnutls_x509_crt_t)cert, index, ele, &siz, NULL, NULL)) < 0) > return g_err("gc1", __FUNCTION__, ret); >@@ -422,7 +423,7 @@ > > if ( (fail = gnutls_x509_crt_export((gnutls_x509_crt_t)cert, > GNUTLS_X509_FMT_DER, cp, &len)) != GNUTLS_E_SHORT_MEMORY_BUFFER >- || !(cp = store_get((int)len, TRUE), TRUE) /* tainted */ >+ || !(cp = store_get((int)len, GET_TAINTED), TRUE) /* tainted */ > || (fail = gnutls_x509_crt_export((gnutls_x509_crt_t)cert, > GNUTLS_X509_FMT_DER, cp, &len)) > ) >@@ -447,7 +448,7 @@ > != GNUTLS_E_SHORT_MEMORY_BUFFER) > return g_err("gf0", __FUNCTION__, ret); > >-cp = store_get(siz*3+1, TRUE); >+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 -ur exim.orig/src/tlscert-openssl.c exim/src/tlscert-openssl.c >--- exim.orig/src/tlscert-openssl.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/tlscert-openssl.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2022 */ > /* Copyright (c) Jeremy Harris 2014 - 2019 */ > > /* This module provides TLS (aka SSL) support for Exim using the OpenSSL >@@ -171,7 +172,7 @@ > > /* convert to string in our format */ > len = 32; >- s = store_get(len, FALSE); >+ s = store_get(len, GET_UNTAINTED); > strftime(CS s, (size_t)len, "%b %e %T %Y %z", tm_p); > } > } >@@ -335,7 +336,7 @@ > /* binary data, DER encoded */ > /* just dump for now */ > len = BIO_get_mem_data(bp, &cp1); >-cp3 = cp2 = store_get(len*3+1, TRUE); >+cp3 = cp2 = store_get(len*3+1, GET_TAINTED); > > while(len) > { >@@ -502,7 +503,7 @@ > expand_string_message = US"tls_cert_fprt: out of mem\n"; > return NULL; > } >-cp = store_get(n*2+1, TRUE); >+cp = store_get(n*2+1, GET_TAINTED); > for (int j = 0; j < (int)n; j++) sprintf(CS cp+2*j, "%02X", md[j]); > return(cp); > } >diff -ur exim.orig/src/tls-gnu.c exim/src/tls-gnu.c >--- exim.orig/src/tls-gnu.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/tls-gnu.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,11 +2,10 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ >-/* See the file NOTICE for conditions of use and distribution. */ >- > /* Copyright (c) Phil Pennock 2012 */ >+/* See the file NOTICE for conditions of use and distribution. */ > > /* This file provides TLS/SSL support for Exim using the GnuTLS library, > one of the available supported implementations. This file is #included into >@@ -90,9 +89,6 @@ > #if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP) > # define SUPPORT_SRV_OCSP_STACK > #endif >-#if GNUTLS_VERSION_NUMBER >= 0x030600 >-# define GNUTLS_AUTO_DHPARAMS >-#endif > #if GNUTLS_VERSION_NUMBER >= 0x030603 > # define EXIM_HAVE_TLS1_3 > # define SUPPORT_GNUTLS_EXT_RAW_PARSE >@@ -111,9 +107,17 @@ > # endif > #endif > >-#ifdef EXPERIMENTAL_TLS_RESUME >-# if GNUTLS_VERSION_NUMBER < 0x030603 >-# error GNUTLS version too early for session-resumption >+#ifndef DISABLE_TLS_RESUME >+# if GNUTLS_VERSION_NUMBER >= 0x030603 >+# define EXIM_HAVE_TLS_RESUME >+# else >+# warning "GnuTLS library version too old; resumption unsupported" >+# endif >+#endif >+ >+#if GNUTLS_VERSION_NUMBER >= 0x030200 >+# ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE >+# define EXIM_HAVE_ALPN > # endif > #endif > >@@ -131,7 +135,7 @@ > void > options_tls(void) > { >-# ifdef EXPERIMENTAL_TLS_RESUME >+# ifndef DISABLE_TLS_RESUME > builtin_macro_create_var(US"_RESUME_DECODE", RESUME_DECODE_STRING ); > # endif > # ifdef EXIM_HAVE_TLS1_3 >@@ -143,6 +147,12 @@ > # ifdef SUPPORT_SRV_OCSP_STACK > builtin_macro_create(US"_HAVE_TLS_OCSP_LIST"); > # endif >+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) >+builtin_macro_create(US"_HAVE_TLS_CA_CACHE"); >+# endif >+# ifdef EXIM_HAVE_ALPN >+builtin_macro_create(US"_HAVE_TLS_ALPN"); >+# endif > } > #else > >@@ -176,8 +186,11 @@ > > typedef struct exim_gnutls_state { > gnutls_session_t session; >- gnutls_certificate_credentials_t x509_cred; >- gnutls_priority_t priority_cache; >+ >+ exim_tlslib_state lib_state; >+#define x509_cred libdata0 >+#define pri_cache libdata1 >+ > enum peer_verify_requirement verify_requirement; > int fd_in; > int fd_out; >@@ -243,15 +256,17 @@ > XXX But see gnutls_session_get_ptr() > */ > >-static exim_gnutls_state_st state_server; >+static exim_gnutls_state_st state_server = { >+ /* all elements not explicitly intialised here get 0/NULL/FALSE */ >+ .fd_in = -1, >+ .fd_out = -1, >+}; > >-#ifndef GNUTLS_AUTO_DHPARAMS > /* dh_params are initialised once within the lifetime of a process using TLS; > if we used TLS in a long-lived daemon, we'd have to reconsider this. But we > don't want to repeat this. */ > > static gnutls_dh_params_t dh_server_params = NULL; >-#endif > > static int ssl_session_timeout = 7200; /* Two hours */ > >@@ -266,10 +281,14 @@ > static BOOL exim_testharness_disable_ocsp_validity_check = FALSE; > #endif > >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifdef EXIM_HAVE_ALPN >+static int server_seen_alpn = -1; /* count of names */ >+#endif >+#ifdef EXIM_HAVE_TLS_RESUME > static gnutls_datum_t server_sessticket_key; > #endif > >+ > /* ------------------------------------------------------------------------ */ > /* macros */ > >@@ -296,7 +315,7 @@ > # define EXIM_SERVER_DH_BITS_PRE2_12 1024 > #endif > >-#define expand_check_tlsvar(Varname, errstr) \ >+#define Expand_check_tlsvar(Varname, errstr) \ > expand_check(state->Varname, US #Varname, &state->exp_##Varname, errstr) > > #if GNUTLS_VERSION_NUMBER >= 0x020c00 >@@ -314,6 +333,9 @@ > # endif /* AVOID_GNUTLS_PKCS11 */ > #endif > >+#if GNUTLS_VERSION_NUMBER >= 0x030404 >+# define HAVE_GNUTLS_PRF_RFC5705 >+#endif > > > >@@ -326,34 +348,13 @@ > > static int exim_sni_handling_cb(gnutls_session_t session); > >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifdef EXIM_HAVE_TLS_RESUME > static int > tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when, > unsigned incoming, const gnutls_datum_t * msg); > #endif > > >-/* Daemon one-time initialisation */ >-void >-tls_daemon_init(void) >-{ >-#ifdef EXPERIMENTAL_TLS_RESUME >-/* We are dependent on the GnuTLS implementation of the Session Ticket >-encryption; both the strength and the key rotation period. We hope that >-the strength at least matches that of the ciphersuite (but GnuTLS does not >-document this). */ >- >-static BOOL once = FALSE; >-if (once) return; >-once = TRUE; >-gnutls_session_ticket_key_generate(&server_sessticket_key); /* >= 2.10.0 */ >-if (f.running_in_test_harness) ssl_session_timeout = 6; >-#endif >-} >- >-/* ------------------------------------------------------------------------ */ >-/* Static functions */ >- > /************************************************* > * Handle TLS error * > *************************************************/ >@@ -387,10 +388,15 @@ > > > static int >-tls_error_gnu(const uschar *prefix, int err, const host_item *host, >+tls_error_gnu(exim_gnutls_state_st * state, const uschar *prefix, int err, > uschar ** errstr) > { >-return tls_error(prefix, US gnutls_strerror(err), host, errstr); >+return tls_error(prefix, >+ state && err == GNUTLS_E_FATAL_ALERT_RECEIVED >+ ? US gnutls_alert_get_name(gnutls_alert_get(state->session)) >+ : US gnutls_strerror(err), >+ state ? state->host : NULL, >+ errstr); > } > > static int >@@ -401,6 +407,121 @@ > } > > >+/* ------------------------------------------------------------------------ */ >+/* Initialisation */ >+ >+#ifndef DISABLE_OCSP >+ >+static BOOL >+tls_is_buggy_ocsp(void) >+{ >+const uschar * s; >+uschar maj, mid, mic; >+ >+s = CUS gnutls_check_version(NULL); >+maj = atoi(CCS s); >+if (maj == 3) >+ { >+ while (*s && *s != '.') s++; >+ mid = atoi(CCS ++s); >+ if (mid <= 2) >+ return TRUE; >+ else if (mid >= 5) >+ return FALSE; >+ else >+ { >+ while (*s && *s != '.') s++; >+ mic = atoi(CCS ++s); >+ return mic <= (mid == 3 ? 16 : 3); >+ } >+ } >+return FALSE; >+} >+ >+#endif >+ >+ >+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) >+/* By default, gnutls_global_init will init PKCS11 support in auto mode, >+which loads modules from a config file, which sounds good and may be wanted >+by some sysadmin, but also means in common configurations that GNOME keyring >+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) >+ 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); >+#endif >+ >+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0 >+DEBUG(D_tls) >+ { >+ gnutls_global_set_log_function(exim_gnutls_logger_cb); >+ /* arbitrarily chosen level; bump up to 9 for more */ >+ gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL); >+ } >+#endif >+ >+#ifndef DISABLE_OCSP >+if (tls_ocsp_file && (gnutls_buggy_ocsp = tls_is_buggy_ocsp())) >+ log_write(0, LOG_MAIN, "OCSP unusable with this GnuTLS library version"); >+#endif >+ >+exim_gnutls_base_init_done = TRUE; >+return OK; >+} >+ >+ >+ >+/* Daemon-call before each connection. Nothing to do for GnuTLS. */ >+ >+static void >+tls_per_lib_daemon_tick(void) >+{ >+} >+ >+/* Daemon one-time initialisation */ >+ >+static void >+tls_per_lib_daemon_init(void) >+{ >+uschar * dummy_errstr; >+static BOOL once = FALSE; >+ >+if (!exim_gnutls_base_init_done) >+ tls_g_init(&dummy_errstr); >+ >+if (!once) >+ { >+ once = TRUE; >+ >+#ifdef EXIM_HAVE_TLS_RESUME >+ /* We are dependent on the GnuTLS implementation of the Session Ticket >+ encryption; both the strength and the key rotation period. We hope that >+ the strength at least matches that of the ciphersuite (but GnuTLS does not >+ document this). */ >+ >+ gnutls_session_ticket_key_generate(&server_sessticket_key); /* >= 2.10.0 */ >+ if (f.running_in_test_harness) ssl_session_timeout = 6; >+#endif >+ >+ tls_daemon_creds_reload(); >+ } >+} >+ >+/* ------------------------------------------------------------------------ */ >+ > /************************************************* > * Deal with logging errors during I/O * > *************************************************/ >@@ -422,11 +543,14 @@ > const uschar * msg; > uschar * errstr; > >-if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED) >- msg = string_sprintf("A TLS fatal alert has been received: %s", >- US gnutls_alert_get_name(gnutls_alert_get(state->session))); >-else >- msg = US gnutls_strerror(rc); >+msg = rc == GNUTLS_E_FATAL_ALERT_RECEIVED >+ ? string_sprintf("A TLS fatal alert has been received: %s", >+ US gnutls_alert_get_name(gnutls_alert_get(state->session))) >+#ifdef GNUTLS_E_PREMATURE_TERMINATION >+ : rc == GNUTLS_E_PREMATURE_TERMINATION && errno >+ ? string_sprintf("%s: syscall: %s", US gnutls_strerror(rc), strerror(errno)) >+#endif >+ : US gnutls_strerror(rc); > > (void) tls_error(when, msg, state->host, &errstr); > >@@ -503,11 +627,6 @@ > static void > extract_exim_vars_from_tls_state(exim_gnutls_state_st * state) > { >-#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING >-int old_pool; >-int rc; >-gnutls_datum_t channel; >-#endif > tls_support * tlsp = state->tlsp; > > tlsp->active.sock = state->fd_out; >@@ -525,21 +644,40 @@ > > tlsp->channelbinding = NULL; > #ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING >-channel.data = NULL; >-channel.size = 0; >-if ((rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel))) >- { DEBUG(D_tls) debug_printf("Channel binding error: %s\n", gnutls_strerror(rc)); } >-else > { >- /* Declare the taintedness of the binding info. On server, untainted; on >- client, tainted - being the Finish msg from the server. */ >+ gnutls_datum_t channel = {.data = NULL, .size = 0}; >+ uschar * buf; >+ int rc; > >- old_pool = store_pool; >- store_pool = POOL_PERM; >- tlsp->channelbinding = b64encode_taint(CUS channel.data, (int)channel.size, >- !!state->host); >- store_pool = old_pool; >- DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage\n"); >+# ifdef HAVE_GNUTLS_PRF_RFC5705 >+ /* Older libraries may not have GNUTLS_TLS1_3 defined! */ >+ if (gnutls_protocol_get_version(state->session) > GNUTLS_TLS1_2) >+ { >+ buf = store_get(32, state->host ? GET_TAINTED : GET_UNTAINTED); >+ rc = gnutls_prf_rfc5705(state->session, >+ (size_t)24, "EXPORTER-Channel-Binding", (size_t)0, "", >+ 32, CS buf); >+ channel.data = buf; >+ channel.size = 32; >+ } >+ else >+# endif >+ rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel); >+ >+ if (rc) >+ { DEBUG(D_tls) debug_printf("extracting channel binding: %s\n", gnutls_strerror(rc)); } >+ else >+ { >+ int old_pool = store_pool; >+ /* Declare the taintedness of the binding info. On server, untainted; on >+ client, tainted - being the Finish msg from the server. */ >+ >+ store_pool = POOL_PERM; >+ tlsp->channelbinding = b64encode_taint(CUS channel.data, (int)channel.size, >+ state->host ? GET_TAINTED : GET_UNTAINTED); >+ store_pool = old_pool; >+ DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage\n"); >+ } > } > #endif > >@@ -562,7 +700,6 @@ > > > >-#ifndef GNUTLS_AUTO_DHPARAMS > /************************************************* > * Setup up DH parameters * > *************************************************/ >@@ -585,7 +722,7 @@ > { > int fd, rc; > unsigned int dh_bits; >-gnutls_datum_t m = {.data = NULL, .size = 0}; >+gnutls_datum_t m; > uschar filename_buf[PATH_MAX]; > uschar *filename = NULL; > size_t sz; >@@ -593,10 +730,13 @@ > BOOL use_file_in_spool = FALSE; > host_item *host = NULL; /* dummy for macros */ > >-DEBUG(D_tls) debug_printf("Initialising GnuTLS server params.\n"); >+DEBUG(D_tls) debug_printf("Initialising GnuTLS server params\n"); > > if ((rc = gnutls_dh_params_init(&dh_server_params))) >- return tls_error_gnu(US"gnutls_dh_params_init", rc, host, errstr); >+ return tls_error_gnu(NULL, US"gnutls_dh_params_init", rc, errstr); >+ >+m.data = NULL; >+m.size = 0; > > if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam, errstr)) > return DEFER; >@@ -611,7 +751,7 @@ > use_file_in_spool = TRUE; > else if (Ustrcmp(exp_tls_dhparam, "none") == 0) > { >- DEBUG(D_tls) debug_printf("Requested no DH parameters.\n"); >+ DEBUG(D_tls) debug_printf("Requested no DH parameters\n"); > return OK; > } > else if (exp_tls_dhparam[0] != '/') >@@ -626,7 +766,7 @@ > if (m.data) > { > if ((rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM))) >- return tls_error_gnu(US"gnutls_dh_params_import_pkcs3", rc, host, errstr); >+ return tls_error_gnu(NULL, US"gnutls_dh_params_import_pkcs3", rc, errstr); > DEBUG(D_tls) debug_printf("Loaded fixed standard D-H parameters\n"); > return OK; > } >@@ -638,12 +778,12 @@ > if (!(dh_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_NORMAL))) > return tls_error(US"gnutls_sec_param_to_pk_bits() failed", NULL, NULL, errstr); > DEBUG(D_tls) >- debug_printf("GnuTLS tells us that for D-H PK, NORMAL is %d bits.\n", >+ debug_printf("GnuTLS tells us that for D-H PK, NORMAL is %d bits\n", > dh_bits); > #else > dh_bits = EXIM_SERVER_DH_BITS_PRE2_12; > DEBUG(D_tls) >- debug_printf("GnuTLS lacks gnutls_sec_param_to_pk_bits(), using %d bits.\n", >+ debug_printf("GnuTLS lacks gnutls_sec_param_to_pk_bits(), using %d bits\n", > dh_bits); > #endif > >@@ -651,7 +791,7 @@ > if (dh_bits > tls_dh_max_bits) > { > DEBUG(D_tls) >- debug_printf("tls_dh_max_bits clamping override, using %d bits instead.\n", >+ debug_printf("tls_dh_max_bits clamping override, using %d bits instead\n", > tls_dh_max_bits); > dh_bits = tls_dh_max_bits; > } >@@ -710,7 +850,7 @@ > rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM); > store_free(m.data); > if (rc) >- return tls_error_gnu(US"gnutls_dh_params_import_pkcs3", rc, host, errstr); >+ return tls_error_gnu(NULL, US"gnutls_dh_params_import_pkcs3", rc, errstr); > DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename); > } > >@@ -724,7 +864,7 @@ > debug_printf("D-H parameter cache file \"%s\" does not exist\n", filename); > } > else >- return tls_error(string_open_failed(errno, "\"%s\" for reading", filename), >+ return tls_error(string_open_failed("\"%s\" for reading", filename), > NULL, NULL, errstr); > > /* If ret < 0, either the cache file does not exist, or the data it contains >@@ -742,17 +882,19 @@ > return tls_error(US"Filename too long to generate replacement", > filename, NULL, errstr); > >- temp_fn = string_copy(US"%s.XXXXXXX"); >+ temp_fn = string_copy(US"exim-dh.XXXXXXX"); > if ((fd = mkstemp(CS temp_fn)) < 0) /* modifies temp_fn */ > return tls_error_sys(US"Unable to open temp file", errno, NULL, errstr); > (void)exim_chown(temp_fn, exim_uid, exim_gid); /* Probably not necessary */ > >- /* GnuTLS overshoots! If we ask for 2236, we might get 2237 or more. But >- there's no way to ask GnuTLS how many bits there really are. We can ask >- how many bits were used in a TLS session, but that's it! The prime itself >- is hidden behind too much abstraction. So we ask for less, and proceed on >- a wing and a prayer. First attempt, subtracted 3 for 2233 and got 2240. */ >- >+ /* GnuTLS overshoots! >+ * If we ask for 2236, we might get 2237 or more. >+ * But there's no way to ask GnuTLS how many bits there really are. >+ * We can ask how many bits were used in a TLS session, but that's it! >+ * The prime itself is hidden behind too much abstraction. >+ * So we ask for less, and proceed on a wing and a prayer. >+ * First attempt, subtracted 3 for 2233 and got 2240. >+ */ > if (dh_bits >= EXIM_CLIENT_DH_MIN_BITS + 10) > { > dh_bits_gen = dh_bits - 10; >@@ -765,7 +907,7 @@ > debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n", > dh_bits_gen); > if ((rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen))) >- return tls_error_gnu(US"gnutls_dh_params_generate2", rc, host, errstr); >+ return tls_error_gnu(NULL, US"gnutls_dh_params_generate2", rc, errstr); > > /* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time, > and I confirmed that a NULL call to get the size first is how the GnuTLS >@@ -776,8 +918,8 @@ > if ( (rc = gnutls_dh_params_export_pkcs3(dh_server_params, > GNUTLS_X509_FMT_PEM, m.data, &sz)) > && rc != GNUTLS_E_SHORT_MEMORY_BUFFER) >- return tls_error_gnu(US"gnutls_dh_params_export_pkcs3(NULL) sizing", >- rc, host, errstr); >+ return tls_error_gnu(NULL, US"gnutls_dh_params_export_pkcs3(NULL) sizing", >+ rc, errstr); > m.size = sz; > if (!(m.data = store_malloc(m.size))) > return tls_error_sys(US"memory allocation failed", errno, NULL, errstr); >@@ -787,7 +929,7 @@ > m.data, &sz))) > { > store_free(m.data); >- return tls_error_gnu(US"gnutls_dh_params_export_pkcs3() real", rc, host, errstr); >+ return tls_error_gnu(NULL, US"gnutls_dh_params_export_pkcs3() real", rc, errstr); > } > m.size = sz; /* shrink by 1, probably */ > >@@ -815,12 +957,11 @@ > DEBUG(D_tls) debug_printf("initialized server D-H parameters\n"); > return OK; > } >-#endif > > > > >-/* Create and install a selfsigned certificate, for use in server mode */ >+/* Create and install a selfsigned certificate, for use in server mode. */ > > static int > tls_install_selfsign(exim_gnutls_state_st * state, uschar ** errstr) >@@ -837,6 +978,7 @@ > if (TRUE) goto err; > #endif > >+DEBUG(D_tls) debug_printf("TLS: generating selfsigned server cert\n"); > where = US"initialising pkey"; > if ((rc = gnutls_x509_privkey_init(&pkey))) goto err; > >@@ -861,7 +1003,7 @@ > if ( (rc = gnutls_x509_crt_set_version(cert, 3)) > || (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now))) > || (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL))) >- || (rc = gnutls_x509_crt_set_expiration_time(cert, now + 60 * 60)) /* 1 hr */ >+ || (rc = gnutls_x509_crt_set_expiration_time(cert, (long)2 * 60 * 60)) /* 2 hour */ > || (rc = gnutls_x509_crt_set_key(cert, pkey)) > > || (rc = gnutls_x509_crt_set_dn_by_oid(cert, >@@ -879,7 +1021,8 @@ > > where = US"installing selfsign cert"; > /* Since: 2.4.0 */ >-if ((rc = gnutls_certificate_set_x509_key(state->x509_cred, &cert, 1, pkey))) >+if ((rc = gnutls_certificate_set_x509_key(state->lib_state.x509_cred, >+ &cert, 1, pkey))) > goto err; > > rc = OK; >@@ -890,7 +1033,7 @@ > return rc; > > err: >- rc = tls_error_gnu(where, rc, NULL, errstr); >+ rc = tls_error_gnu(state, where, rc, errstr); > goto out; > } > >@@ -906,14 +1049,14 @@ > > static int > tls_add_certfile(exim_gnutls_state_st * state, const host_item * host, >- uschar * certfile, uschar * keyfile, uschar ** errstr) >+ const uschar * certfile, const uschar * keyfile, uschar ** errstr) > { >-int rc = gnutls_certificate_set_x509_key_file(state->x509_cred, >- CS certfile, CS keyfile, GNUTLS_X509_FMT_PEM); >+int rc = gnutls_certificate_set_x509_key_file(state->lib_state.x509_cred, >+ CCS certfile, CCS keyfile, GNUTLS_X509_FMT_PEM); > if (rc < 0) >- return tls_error_gnu( >+ return tls_error_gnu(state, > string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile), >- rc, host, errstr); >+ rc, errstr); > return -rc; > } > >@@ -948,13 +1091,37 @@ > /* Make a note that we saw a status-request */ > static int > tls_server_clienthello_ext(void * ctx, unsigned tls_id, >- const unsigned char *data, unsigned size) >+ const uschar * data, unsigned size) > { >-/* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */ >-if (tls_id == 5) /* status_request */ >- { >- DEBUG(D_tls) debug_printf("Seen status_request extension from client\n"); >- tls_in.ocsp = OCSP_NOT_RESP; >+/* The values for tls_id are documented here: >+https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */ >+switch (tls_id) >+ { >+ case 5: /* Status Request */ >+ DEBUG(D_tls) debug_printf("Seen status_request extension from client\n"); >+ tls_in.ocsp = OCSP_NOT_RESP; >+ break; >+#ifdef EXIM_HAVE_ALPN >+ case 16: /* Application Layer Protocol Notification */ >+ /* The format of "data" here doesn't seem to be documented, but appears >+ to be a 2-byte field with a (redundant, given the "size" arg) total length >+ then a sequence of one-byte size then string (not nul-term) names. The >+ latter is as described in OpenSSL documentation. */ >+ >+ DEBUG(D_tls) debug_printf("Seen ALPN extension from client (s=%u):", size); >+ for (const uschar * s = data+2; s-data < size-1; s += *s + 1) >+ { >+ server_seen_alpn++; >+ DEBUG(D_tls) debug_printf(" '%.*s'", (int)*s, s+1); >+ } >+ DEBUG(D_tls) debug_printf("\n"); >+ if (server_seen_alpn > 1) >+ { >+ DEBUG(D_tls) debug_printf("TLS: too many ALPNs presented in handshake\n"); >+ return GNUTLS_E_NO_APPLICATION_PROTOCOL; >+ } >+ break; >+#endif > } > return 0; > } >@@ -970,6 +1137,7 @@ > } > > >+# ifdef notdef_crashes > /* Make a note that we saw a status-response */ > static int > tls_server_servercerts_ext(void * ctx, unsigned tls_id, >@@ -985,6 +1153,7 @@ > } > return 0; > } >+# endif > > /* Callback for certificates packet, on server, if we think we might serve stapled-OCSP */ > static int >@@ -992,33 +1161,33 @@ > unsigned when, unsigned int incoming, const gnutls_datum_t * msg) > { > /* Call fn for each extension seen. 3.6.3 onwards */ >-#ifdef notdef >-/*XXX crashes */ >+# ifdef notdef_crashes >+ /*XXX crashes */ > return gnutls_ext_raw_parse(NULL, tls_server_servercerts_ext, msg, 0); >-#endif >+# endif > } >-#endif >+#endif /*SUPPORT_GNUTLS_EXT_RAW_PARSE*/ > > /*XXX in tls1.3 the cert-status travel as an extension next to the cert, in the > "Handshake Protocol: Certificate" record. > So we need to spot the Certificate handshake message, parse it and spot any status_request extension(s) > >-This is different to tls1.2 - where it is a separate record (wireshake term) / handshake message (gnutls term). >+This is different to tls1.2 - where it is a separate record (wireshark term) / handshake message (gnutls term). > */ > >-#if defined(EXPERIMENTAL_TLS_RESUME) || defined(SUPPORT_GNUTLS_EXT_RAW_PARSE) >+#if defined(EXIM_HAVE_TLS_RESUME) || defined(SUPPORT_GNUTLS_EXT_RAW_PARSE) > /* Callback for certificate-status, on server. We sent stapled OCSP. */ > static int > tls_server_certstatus_cb(gnutls_session_t session, unsigned int htype, > unsigned when, unsigned int incoming, const gnutls_datum_t * msg) > { > DEBUG(D_tls) debug_printf("Sending certificate-status\n"); /*XXX we get this for tls1.2 but not for 1.3 */ >-#ifdef SUPPORT_SRV_OCSP_STACK >+# ifdef SUPPORT_SRV_OCSP_STACK > tls_in.ocsp = exim_testharness_disable_ocsp_validity_check > ? OCSP_VFY_NOT_TRIED : OCSP_VFIED; /* We know that GnuTLS verifies responses */ >-#else >+# else > tls_in.ocsp = OCSP_VFY_NOT_TRIED; >-#endif >+# endif > return 0; > } > >@@ -1038,7 +1207,7 @@ > # endif > case GNUTLS_HANDSHAKE_CERTIFICATE_STATUS: > return tls_server_certstatus_cb(sess, htype, when, incoming, msg); >-# ifdef EXPERIMENTAL_TLS_RESUME >+# ifdef EXIM_HAVE_TLS_RESUME > case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: > return tls_server_ticket_cb(sess, htype, when, incoming, msg); > # endif >@@ -1063,6 +1232,500 @@ > } > #endif > >+/************************************************** >+* One-time init credentials for server and client * >+**************************************************/ >+ >+static void >+creds_basic_init(gnutls_certificate_credentials_t x509_cred, BOOL server) >+{ >+#ifdef SUPPORT_SRV_OCSP_STACK >+gnutls_certificate_set_flags(x509_cred, GNUTLS_CERTIFICATE_API_V2); >+ >+# if !defined(DISABLE_OCSP) && defined(SUPPORT_GNUTLS_EXT_RAW_PARSE) >+if (server && tls_ocsp_file) >+ { >+ if (f.running_in_test_harness) >+ tls_server_testharness_ocsp_fiddle(); >+ >+ if (exim_testharness_disable_ocsp_validity_check) >+ gnutls_certificate_set_flags(x509_cred, >+ GNUTLS_CERTIFICATE_API_V2 | GNUTLS_CERTIFICATE_SKIP_OCSP_RESPONSE_CHECK); >+ } >+# endif >+#endif >+DEBUG(D_tls) >+ debug_printf("TLS: basic cred init, %s\n", server ? "server" : "client"); >+} >+ >+static int >+creds_load_server_certs(exim_gnutls_state_st * state, const uschar * cert, >+ const uschar * pkey, const uschar * ocsp, uschar ** errstr) >+{ >+const uschar * clist = cert; >+const uschar * klist = pkey; >+const uschar * olist; >+int csep = 0, ksep = 0, osep = 0, cnt = 0, rc; >+uschar * cfile, * kfile, * ofile; >+#ifndef DISABLE_OCSP >+# ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE >+gnutls_x509_crt_fmt_t ocsp_fmt = GNUTLS_X509_FMT_DER; >+# endif >+ >+if (!expand_check(ocsp, US"tls_ocsp_file", &ofile, errstr)) >+ return DEFER; >+olist = ofile; >+#endif >+ >+while (cfile = string_nextinlist(&clist, &csep, NULL, 0)) >+ >+ if (!(kfile = string_nextinlist(&klist, &ksep, NULL, 0))) >+ return tls_error(US"cert/key setup: out of keys", NULL, NULL, errstr); >+ else if ((rc = tls_add_certfile(state, NULL, cfile, kfile, errstr)) > 0) >+ return rc; >+ else >+ { >+ int gnutls_cert_index = -rc; >+ DEBUG(D_tls) debug_printf("TLS: cert/key %d %s registered\n", >+ gnutls_cert_index, cfile); >+ >+#ifndef DISABLE_OCSP >+ if (ocsp) >+ { >+ /* Set the OCSP stapling server info */ >+ if (gnutls_buggy_ocsp) >+ { >+ DEBUG(D_tls) >+ debug_printf("GnuTLS library is buggy for OCSP; avoiding\n"); >+ } >+ else if ((ofile = string_nextinlist(&olist, &osep, NULL, 0))) >+ { >+ DEBUG(D_tls) debug_printf("OCSP response file %d = %s\n", >+ gnutls_cert_index, ofile); >+# ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE >+ if (Ustrncmp(ofile, US"PEM ", 4) == 0) >+ { >+ ocsp_fmt = GNUTLS_X509_FMT_PEM; >+ ofile += 4; >+ } >+ else if (Ustrncmp(ofile, US"DER ", 4) == 0) >+ { >+ ocsp_fmt = GNUTLS_X509_FMT_DER; >+ ofile += 4; >+ } >+ >+ if ((rc = gnutls_certificate_set_ocsp_status_request_file2( >+ state->lib_state.x509_cred, CCS ofile, gnutls_cert_index, >+ ocsp_fmt)) < 0) >+ return tls_error_gnu(state, >+ US"gnutls_certificate_set_ocsp_status_request_file2", >+ rc, errstr); >+ DEBUG(D_tls) >+ debug_printf(" %d response%s loaded\n", rc, rc>1 ? "s":""); >+ >+ /* Arrange callbacks for OCSP request observability */ >+ >+ if (state->session) >+ gnutls_handshake_set_hook_function(state->session, >+ GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb); >+ else >+ state->lib_state.ocsp_hook = TRUE; >+ >+ >+# else >+# if defined(SUPPORT_SRV_OCSP_STACK) >+ if ((rc = gnutls_certificate_set_ocsp_status_request_function2( >+ state->lib_state.x509_cred, gnutls_cert_index, >+ server_ocsp_stapling_cb, ofile))) >+ return tls_error_gnu(state, >+ US"gnutls_certificate_set_ocsp_status_request_function2", >+ rc, errstr); >+ else >+# endif >+ { >+ if (cnt++ > 0) >+ { >+ DEBUG(D_tls) >+ debug_printf("oops; multiple OCSP files not supported\n"); >+ break; >+ } >+ gnutls_certificate_set_ocsp_status_request_function( >+ state->lib_state.x509_cred, server_ocsp_stapling_cb, ofile); >+ } >+# endif /* SUPPORT_GNUTLS_EXT_RAW_PARSE */ >+ } >+ else >+ DEBUG(D_tls) debug_printf("ran out of OCSP response files in list\n"); >+ } >+#endif /* DISABLE_OCSP */ >+ } >+return 0; >+} >+ >+static int >+creds_load_client_certs(exim_gnutls_state_st * state, const host_item * host, >+ const uschar * cert, const uschar * pkey, uschar ** errstr) >+{ >+int rc = tls_add_certfile(state, host, cert, pkey, errstr); >+if (rc > 0) return rc; >+DEBUG(D_tls) debug_printf("TLS: cert/key registered\n"); >+return 0; >+} >+ >+static int >+creds_load_cabundle(exim_gnutls_state_st * state, const uschar * bundle, >+ const host_item * host, uschar ** errstr) >+{ >+int cert_count; >+struct stat statbuf; >+ >+#ifdef SUPPORT_SYSDEFAULT_CABUNDLE >+if (Ustrcmp(bundle, "system") == 0 || Ustrncmp(bundle, "system,", 7) == 0) >+ cert_count = gnutls_certificate_set_x509_system_trust(state->lib_state.x509_cred); >+else >+#endif >+ { >+ if (Ustat(bundle, &statbuf) < 0) >+ { >+ log_write(0, LOG_MAIN|LOG_PANIC, "could not stat '%s' " >+ "(tls_verify_certificates): %s", bundle, strerror(errno)); >+ return DEFER; >+ } >+ >+#ifndef SUPPORT_CA_DIR >+ /* The test suite passes in /dev/null; we could check for that path explicitly, >+ but who knows if someone has some weird FIFO which always dumps some certs, or >+ other weirdness. The thing we really want to check is that it's not a >+ directory, since while OpenSSL supports that, GnuTLS does not. >+ So s/!S_ISREG/S_ISDIR/ and change some messaging ... */ >+ if (S_ISDIR(statbuf.st_mode)) >+ { >+ log_write(0, LOG_MAIN|LOG_PANIC, >+ "tls_verify_certificates \"%s\" is a directory", bundle); >+ return DEFER; >+ } >+#endif >+ >+ DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n", >+ bundle, statbuf.st_size); >+ >+ if (statbuf.st_size == 0) >+ { >+ DEBUG(D_tls) >+ debug_printf("cert file empty, no certs, no verification, ignoring any CRL\n"); >+ return OK; >+ } >+ >+ cert_count = >+ >+#ifdef SUPPORT_CA_DIR >+ (statbuf.st_mode & S_IFMT) == S_IFDIR >+ ? >+ gnutls_certificate_set_x509_trust_dir(state->lib_state.x509_cred, >+ CS bundle, GNUTLS_X509_FMT_PEM) >+ : >+#endif >+ gnutls_certificate_set_x509_trust_file(state->lib_state.x509_cred, >+ CS bundle, GNUTLS_X509_FMT_PEM); >+ >+#ifdef SUPPORT_CA_DIR >+ /* Mimic the behaviour with OpenSSL of not advertising a usable-cert list >+ when using the directory-of-certs config model. */ >+ >+ if ((statbuf.st_mode & S_IFMT) == S_IFDIR) >+ if (state->session) >+ gnutls_certificate_send_x509_rdn_sequence(state->session, 1); >+ else >+ state->lib_state.ca_rdn_emulate = TRUE; >+#endif >+ } >+ >+if (cert_count < 0) >+ return tls_error_gnu(state, US"setting certificate trust", cert_count, errstr); >+DEBUG(D_tls) >+ debug_printf("Added %d certificate authorities\n", cert_count); >+ >+return OK; >+} >+ >+ >+static int >+creds_load_crl(exim_gnutls_state_st * state, const uschar * crl, uschar ** errstr) >+{ >+int cert_count; >+DEBUG(D_tls) debug_printf("loading CRL file = %s\n", crl); >+if ((cert_count = gnutls_certificate_set_x509_crl_file(state->lib_state.x509_cred, >+ CS crl, GNUTLS_X509_FMT_PEM)) < 0) >+ return tls_error_gnu(state, US"gnutls_certificate_set_x509_crl_file", >+ cert_count, errstr); >+ >+DEBUG(D_tls) debug_printf("Processed %d CRLs\n", cert_count); >+return OK; >+} >+ >+ >+static int >+creds_load_pristring(exim_gnutls_state_st * state, const uschar * p, >+ const char ** errpos) >+{ >+if (!p) >+ { >+ p = exim_default_gnutls_priority; >+ DEBUG(D_tls) >+ debug_printf("GnuTLS using default session cipher/priority \"%s\"\n", p); >+ } >+return gnutls_priority_init( (gnutls_priority_t *) &state->lib_state.pri_cache, >+ CCS p, errpos); >+} >+ >+static unsigned >+tls_server_creds_init(void) >+{ >+uschar * dummy_errstr; >+unsigned lifetime = 0; >+ >+state_server.lib_state = null_tls_preload; >+if (gnutls_certificate_allocate_credentials( >+ (gnutls_certificate_credentials_t *) &state_server.lib_state.x509_cred)) >+ { >+ state_server.lib_state.x509_cred = NULL; >+ return lifetime; >+ } >+creds_basic_init(state_server.lib_state.x509_cred, TRUE); >+ >+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) >+/* If tls_certificate has any $ indicating expansions, it is not good. >+If tls_privatekey is set but has $, not good. Likewise for tls_ocsp_file. >+If all good (and tls_certificate set), load the cert(s). */ >+ >+if ( opt_set_and_noexpand(tls_certificate) >+# ifndef DISABLE_OCSP >+ && opt_unset_or_noexpand(tls_ocsp_file) >+# endif >+ && opt_unset_or_noexpand(tls_privatekey)) >+ { >+ /* Set watches on the filenames. The implementation does de-duplication >+ so we can just blindly do them all. >+ */ >+ >+ if ( tls_set_watch(tls_certificate, TRUE) >+# ifndef DISABLE_OCSP >+ && tls_set_watch(tls_ocsp_file, TRUE) >+# endif >+ && tls_set_watch(tls_privatekey, TRUE)) >+ { >+ DEBUG(D_tls) debug_printf("TLS: preloading server certs\n"); >+ if (creds_load_server_certs(&state_server, tls_certificate, >+ tls_privatekey && *tls_privatekey ? tls_privatekey : tls_certificate, >+# ifdef DISABLE_OCSP >+ NULL, >+# else >+ tls_ocsp_file, >+# endif >+ &dummy_errstr) == 0) >+ state_server.lib_state.conn_certs = TRUE; >+ } >+ } >+else if ( !tls_certificate && !tls_privatekey >+# ifndef DISABLE_OCSP >+ && !tls_ocsp_file >+# endif >+ ) >+ { /* Generate & preload a selfsigned cert. No files to watch. */ >+ if ((tls_install_selfsign(&state_server, &dummy_errstr)) == OK) >+ { >+ state_server.lib_state.conn_certs = TRUE; >+ lifetime = f.running_in_test_harness ? 2 : 60 * 60; /* 1 hour */ >+ } >+ } >+else >+ DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n"); >+ >+/* If tls_verify_certificates is non-empty and has no $, load CAs. >+If none was configured and we can't handle "system", treat as empty. */ >+ >+if ( opt_set_and_noexpand(tls_verify_certificates) >+#ifndef SUPPORT_SYSDEFAULT_CABUNDLE >+ && Ustrcmp(tls_verify_certificates, "system") != 0 >+#endif >+ ) >+ { >+ if (tls_set_watch(tls_verify_certificates, FALSE)) >+ { >+ DEBUG(D_tls) debug_printf("TLS: preloading CA bundle for server\n"); >+ if (creds_load_cabundle(&state_server, tls_verify_certificates, >+ NULL, &dummy_errstr) != OK) >+ return lifetime; >+ state_server.lib_state.cabundle = TRUE; >+ >+ /* If CAs loaded and tls_crl is non-empty and has no $, load it */ >+ >+ if (opt_set_and_noexpand(tls_crl)) >+ { >+ if (tls_set_watch(tls_crl, FALSE)) >+ { >+ DEBUG(D_tls) debug_printf("TLS: preloading CRL for server\n"); >+ if (creds_load_crl(&state_server, tls_crl, &dummy_errstr) != OK) >+ return lifetime; >+ state_server.lib_state.crl = TRUE; >+ } >+ } >+ else >+ DEBUG(D_tls) debug_printf("TLS: not preloading CRL for server\n"); >+ } >+ } >+else >+ DEBUG(D_tls) debug_printf("TLS: not preloading CA bundle for server\n"); >+#endif /* EXIM_HAVE_INOTIFY */ >+ >+/* If tls_require_ciphers is non-empty and has no $, load the >+ciphers priority cache. If unset, load with the default. >+(server-only as the client one depends on non/DANE) */ >+ >+if (!tls_require_ciphers || opt_set_and_noexpand(tls_require_ciphers)) >+ { >+ const char * dummy_errpos; >+ DEBUG(D_tls) debug_printf("TLS: preloading cipher list for server: %s\n", >+ tls_require_ciphers); >+ if ( creds_load_pristring(&state_server, tls_require_ciphers, &dummy_errpos) >+ == OK) >+ state_server.lib_state.pri_string = TRUE; >+ } >+else >+ DEBUG(D_tls) debug_printf("TLS: not preloading cipher list for server\n"); >+return lifetime; >+} >+ >+ >+/* Preload whatever creds are static, onto a transport. The client can then >+just copy the pointer as it starts up. */ >+ >+/*XXX this is not called for a cmdline send. But one needing to use >1 conn would benefit, >+and there seems little downside. */ >+ >+static void >+tls_client_creds_init(transport_instance * t, BOOL watch) >+{ >+smtp_transport_options_block * ob = t->options_block; >+exim_gnutls_state_st tpt_dummy_state; >+host_item * dummy_host = (host_item *)1; >+uschar * dummy_errstr; >+ >+if ( !exim_gnutls_base_init_done >+ && tls_g_init(&dummy_errstr) != OK) >+ return; >+ >+ob->tls_preload = null_tls_preload; >+if (gnutls_certificate_allocate_credentials( >+ (struct gnutls_certificate_credentials_st **)&ob->tls_preload.x509_cred)) >+ { >+ ob->tls_preload.x509_cred = NULL; >+ return; >+ } >+creds_basic_init(ob->tls_preload.x509_cred, FALSE); >+ >+tpt_dummy_state.session = NULL; >+tpt_dummy_state.lib_state = ob->tls_preload; >+ >+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) >+if ( opt_set_and_noexpand(ob->tls_certificate) >+ && opt_unset_or_noexpand(ob->tls_privatekey)) >+ { >+ if ( !watch >+ || ( tls_set_watch(ob->tls_certificate, FALSE) >+ && tls_set_watch(ob->tls_privatekey, FALSE) >+ ) ) >+ { >+ const uschar * pkey = ob->tls_privatekey; >+ >+ DEBUG(D_tls) >+ debug_printf("TLS: preloading client certs for transport '%s'\n", t->name); >+ >+ /* The state->lib_state.x509_cred is used for the certs load, and is the sole >+ structure element used. So we can set up a dummy. The hoat arg only >+ selects a retcode in case of fail, so any value */ >+ >+ if (creds_load_client_certs(&tpt_dummy_state, dummy_host, >+ ob->tls_certificate, pkey ? pkey : ob->tls_certificate, >+ &dummy_errstr) == OK) >+ ob->tls_preload.conn_certs = TRUE; >+ } >+ } >+else >+ DEBUG(D_tls) >+ debug_printf("TLS: not preloading client certs, for transport '%s'\n", t->name); >+ >+/* If tls_verify_certificates is non-empty and has no $, load CAs. >+If none was configured and we can't handle "system", treat as empty. */ >+ >+if ( opt_set_and_noexpand(ob->tls_verify_certificates) >+#ifndef SUPPORT_SYSDEFAULT_CABUNDLE >+ && Ustrcmp(ob->tls_verify_certificates, "system") != 0 >+#endif >+ ) >+ { >+ if (!watch || tls_set_watch(ob->tls_verify_certificates, FALSE)) >+ { >+ DEBUG(D_tls) >+ debug_printf("TLS: preloading CA bundle for transport '%s'\n", t->name); >+ if (creds_load_cabundle(&tpt_dummy_state, ob->tls_verify_certificates, >+ dummy_host, &dummy_errstr) != OK) >+ return; >+ ob->tls_preload.cabundle = TRUE; >+ >+ if (opt_set_and_noexpand(ob->tls_crl)) >+ { >+ if (!watch || tls_set_watch(ob->tls_crl, FALSE)) >+ { >+ DEBUG(D_tls) debug_printf("TLS: preloading CRL for transport '%s'\n", t->name); >+ if (creds_load_crl(&tpt_dummy_state, ob->tls_crl, &dummy_errstr) != OK) >+ return; >+ ob->tls_preload.crl = TRUE; >+ } >+ } >+ else >+ DEBUG(D_tls) debug_printf("TLS: not preloading CRL, for transport '%s'\n", t->name); >+ } >+ } >+else >+ DEBUG(D_tls) >+ debug_printf("TLS: not preloading CA bundle, for transport '%s'\n", t->name); >+ >+/* We do not preload tls_require_ciphers to to the transport as it implicitly >+depends on DANE or plain usage. */ >+ >+#endif >+} >+ >+ >+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) >+/* Invalidate the creds cached, by dropping the current ones. >+Call when we notice one of the source files has changed. */ >+ >+static void >+tls_server_creds_invalidate(void) >+{ >+if (state_server.lib_state.pri_cache) >+ gnutls_priority_deinit(state_server.lib_state.pri_cache); >+state_server.lib_state.pri_cache = NULL; >+ >+if (state_server.lib_state.x509_cred) >+ gnutls_certificate_free_credentials(state_server.lib_state.x509_cred); >+state_server.lib_state = null_tls_preload; >+} >+ >+ >+static void >+tls_client_creds_invalidate(transport_instance * t) >+{ >+smtp_transport_options_block * ob = t->options_block; >+if (ob->tls_preload.x509_cred) >+ gnutls_certificate_free_credentials(ob->tls_preload.x509_cred); >+ob->tls_preload = null_tls_preload; >+} >+#endif >+ >+ > /************************************************* > * Variables re-expanded post-SNI * > *************************************************/ >@@ -1085,13 +1748,12 @@ > static int > tls_expand_session_files(exim_gnutls_state_st * state, uschar ** errstr) > { >-struct stat statbuf; > int rc; > const host_item *host = state->host; /* macro should be reconsidered? */ >-uschar *saved_tls_certificate = NULL; >-uschar *saved_tls_privatekey = NULL; >-uschar *saved_tls_verify_certificates = NULL; >-uschar *saved_tls_crl = NULL; >+const uschar *saved_tls_certificate = NULL; >+const uschar *saved_tls_privatekey = NULL; >+const uschar *saved_tls_verify_certificates = NULL; >+const uschar *saved_tls_crl = NULL; > int cert_count; > > /* We check for tls_sni *before* expansion. */ >@@ -1104,11 +1766,11 @@ > || Ustrstr(state->tls_certificate, US"tls_out_sni") > ) ) > { >- DEBUG(D_tls) debug_printf("We will re-expand TLS session files if we receive SNI.\n"); >+ DEBUG(D_tls) debug_printf("We will re-expand TLS session files if we receive SNI\n"); > state->trigger_sni_changes = TRUE; > } > } >- else >+ else /* SNI callback case */ > { > /* useful for debugging */ > saved_tls_certificate = state->exp_tls_certificate; >@@ -1117,294 +1779,166 @@ > saved_tls_crl = state->exp_tls_crl; > } > >-if ((rc = gnutls_certificate_allocate_credentials(&state->x509_cred))) >- return tls_error_gnu(US"gnutls_certificate_allocate_credentials", >- rc, host, errstr); >- >-#ifdef SUPPORT_SRV_OCSP_STACK >-gnutls_certificate_set_flags(state->x509_cred, GNUTLS_CERTIFICATE_API_V2); >- >-# if !defined(DISABLE_OCSP) && defined(SUPPORT_GNUTLS_EXT_RAW_PARSE) >-if (!host && tls_ocsp_file) >+if (!state->lib_state.x509_cred) > { >- if (f.running_in_test_harness) >- tls_server_testharness_ocsp_fiddle(); >- >- if (exim_testharness_disable_ocsp_validity_check) >- gnutls_certificate_set_flags(state->x509_cred, >- GNUTLS_CERTIFICATE_API_V2 | GNUTLS_CERTIFICATE_SKIP_OCSP_RESPONSE_CHECK); >+ if ((rc = gnutls_certificate_allocate_credentials( >+ (gnutls_certificate_credentials_t *) &state->lib_state.x509_cred))) >+ return tls_error_gnu(state, US"gnutls_certificate_allocate_credentials", >+ rc, errstr); >+ creds_basic_init(state->lib_state.x509_cred, !host); > } >-# endif >-#endif > >-/* remember: expand_check_tlsvar() is expand_check() but fiddling with >+ >+/* remember: Expand_check_tlsvar() is expand_check() but fiddling with > state members, assuming consistent naming; and expand_check() returns > false if expansion failed, unless expansion was forced to fail. */ > > /* check if we at least have a certificate, before doing expensive > D-H generation. */ > >-if (!expand_check_tlsvar(tls_certificate, errstr)) >- return DEFER; >- >-/* certificate is mandatory in server, optional in client */ >- >-if ( !state->exp_tls_certificate >- || !*state->exp_tls_certificate >- ) >- if (!host) >- return tls_install_selfsign(state, errstr); >- else >- DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n"); >- >-if (state->tls_privatekey && !expand_check_tlsvar(tls_privatekey, errstr)) >- return DEFER; >- >-/* tls_privatekey is optional, defaulting to same file as certificate */ >- >-if (!state->tls_privatekey || !*state->tls_privatekey) >+if (!state->lib_state.conn_certs) > { >- state->tls_privatekey = state->tls_certificate; >- state->exp_tls_privatekey = state->exp_tls_certificate; >- } >- >+ if (!Expand_check_tlsvar(tls_certificate, errstr)) >+ return DEFER; > >-if (state->exp_tls_certificate && *state->exp_tls_certificate) >- { >- DEBUG(D_tls) debug_printf("certificate file = %s\nkey file = %s\n", >- state->exp_tls_certificate, state->exp_tls_privatekey); >+ /* certificate is mandatory in server, optional in client */ > >- if (state->received_sni) >- if ( Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0 >- && Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0 >- ) >- { >- DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n"); >- } >+ if ( !state->exp_tls_certificate >+ || !*state->exp_tls_certificate >+ ) >+ if (!host) >+ return tls_install_selfsign(state, errstr); > else >- { >- DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n"); >- } >- >- if (!host) /* server */ >- { >- const uschar * clist = state->exp_tls_certificate; >- const uschar * klist = state->exp_tls_privatekey; >- const uschar * olist; >- int csep = 0, ksep = 0, osep = 0, cnt = 0; >- uschar * cfile, * kfile, * ofile; >-#ifndef DISABLE_OCSP >-# ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE >- gnutls_x509_crt_fmt_t ocsp_fmt = GNUTLS_X509_FMT_DER; >-# endif >+ DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n"); > >- if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", &ofile, errstr)) >- return DEFER; >- olist = ofile; >-#endif >- >- while (cfile = string_nextinlist(&clist, &csep, NULL, 0)) >+ if (state->tls_privatekey && !Expand_check_tlsvar(tls_privatekey, errstr)) >+ return DEFER; > >- if (!(kfile = string_nextinlist(&klist, &ksep, NULL, 0))) >- return tls_error(US"cert/key setup: out of keys", NULL, host, errstr); >- else if (0 < (rc = tls_add_certfile(state, host, cfile, kfile, errstr))) >- return rc; >- else >- { >- int gnutls_cert_index = -rc; >- DEBUG(D_tls) debug_printf("TLS: cert/key %d %s registered\n", >- gnutls_cert_index, cfile); >+ /* tls_privatekey is optional, defaulting to same file as certificate */ > >-#ifndef DISABLE_OCSP >- if (tls_ocsp_file) >- { >- /* Set the OCSP stapling server info */ >- if (gnutls_buggy_ocsp) >- { >- DEBUG(D_tls) >- debug_printf("GnuTLS library is buggy for OCSP; avoiding\n"); >- } >- else if ((ofile = string_nextinlist(&olist, &osep, NULL, 0))) >- { >- DEBUG(D_tls) debug_printf("OCSP response file %d = %s\n", >- gnutls_cert_index, ofile); >-# ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE >- if (Ustrncmp(ofile, US"PEM ", 4) == 0) >- { >- ocsp_fmt = GNUTLS_X509_FMT_PEM; >- ofile += 4; >- } >- else if (Ustrncmp(ofile, US"DER ", 4) == 0) >- { >- ocsp_fmt = GNUTLS_X509_FMT_DER; >- ofile += 4; >- } >- >- if ((rc = gnutls_certificate_set_ocsp_status_request_file2( >- state->x509_cred, CCS ofile, gnutls_cert_index, >- ocsp_fmt)) < 0) >- return tls_error_gnu( >- US"gnutls_certificate_set_ocsp_status_request_file2", >- rc, host, errstr); >- DEBUG(D_tls) >- debug_printf(" %d response%s loaded\n", rc, rc>1 ? "s":""); >- >- /* Arrange callbacks for OCSP request observability */ >+ if (!state->tls_privatekey || !*state->tls_privatekey) >+ { >+ state->tls_privatekey = state->tls_certificate; >+ state->exp_tls_privatekey = state->exp_tls_certificate; >+ } > >- gnutls_handshake_set_hook_function(state->session, >- GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb); >+ if (state->exp_tls_certificate && *state->exp_tls_certificate) >+ { >+ BOOL load = TRUE; >+ DEBUG(D_tls) debug_printf("certificate file = %s\nkey file = %s\n", >+ state->exp_tls_certificate, state->exp_tls_privatekey); > >-# else >-# if defined(SUPPORT_SRV_OCSP_STACK) >- if ((rc = gnutls_certificate_set_ocsp_status_request_function2( >- state->x509_cred, gnutls_cert_index, >- server_ocsp_stapling_cb, ofile))) >- return tls_error_gnu( >- US"gnutls_certificate_set_ocsp_status_request_function2", >- rc, host, errstr); >- else >-# endif >- { >- if (cnt++ > 0) >- { >- DEBUG(D_tls) >- debug_printf("oops; multiple OCSP files not supported\n"); >- break; >- } >- gnutls_certificate_set_ocsp_status_request_function( >- state->x509_cred, server_ocsp_stapling_cb, ofile); >- } >-# endif /* SUPPORT_GNUTLS_EXT_RAW_PARSE */ >- } >- else >- DEBUG(D_tls) debug_printf("ran out of OCSP response files in list\n"); >- } >-#endif /* DISABLE_OCSP */ >+ if (state->received_sni) >+ if ( Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0 >+ && Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0 >+ ) >+ { >+ DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n"); >+ load = FALSE; /* avoid re-loading the same certs */ > } >+ else /* unload the pre-SNI certs before loading new ones */ >+ { >+ DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair\n"); >+ gnutls_certificate_free_keys(state->lib_state.x509_cred); >+ } >+ >+ if ( load >+ && (rc = host >+ ? creds_load_client_certs(state, host, state->exp_tls_certificate, >+ state->exp_tls_privatekey, errstr) >+ : creds_load_server_certs(state, state->exp_tls_certificate, >+ state->exp_tls_privatekey, >+#ifdef DISABLE_OCSP >+ NULL, >+#else >+ tls_ocsp_file, >+#endif >+ errstr) >+ ) ) return rc; > } >- else /* client */ >- { >- if (0 < (rc = tls_add_certfile(state, host, >- state->exp_tls_certificate, state->exp_tls_privatekey, errstr))) >- return rc; >- DEBUG(D_tls) debug_printf("TLS: cert/key registered\n"); >- } >+ } >+else >+ { >+ DEBUG(D_tls) >+ debug_printf("%s certs were preloaded\n", host ? "client" : "server"); >+ >+ if (!state->tls_privatekey) state->tls_privatekey = state->tls_certificate; >+ state->exp_tls_certificate = US state->tls_certificate; >+ state->exp_tls_privatekey = US state->tls_privatekey; > >- } /* tls_certificate */ >+#ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE >+ if (state->lib_state.ocsp_hook) >+ gnutls_handshake_set_hook_function(state->session, >+ GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb); >+#endif >+ } > > > /* Set the trusted CAs file if one is provided, and then add the CRL if one is > provided. Experiment shows that, if the certificate file is empty, an unhelpful > error message is provided. However, if we just refrain from setting anything up > in that case, certificate verification fails, which seems to be the correct >-behaviour. */ >+behaviour. >+If none was configured and we can't handle "system", treat as empty. */ > >-if (state->tls_verify_certificates && *state->tls_verify_certificates) >+if (!state->lib_state.cabundle) > { >- if (!expand_check_tlsvar(tls_verify_certificates, errstr)) >- return DEFER; >+ if (state->tls_verify_certificates && *state->tls_verify_certificates) >+ { >+ if (!Expand_check_tlsvar(tls_verify_certificates, errstr)) >+ return DEFER; > #ifndef SUPPORT_SYSDEFAULT_CABUNDLE >- if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0) >- state->exp_tls_verify_certificates = NULL; >+ if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0) >+ state->exp_tls_verify_certificates = NULL; > #endif >- if (state->tls_crl && *state->tls_crl) >- if (!expand_check_tlsvar(tls_crl, errstr)) >- return DEFER; >+ if (state->tls_crl && *state->tls_crl) >+ if (!Expand_check_tlsvar(tls_crl, errstr)) >+ return DEFER; > >- if (!(state->exp_tls_verify_certificates && >- *state->exp_tls_verify_certificates)) >+ if (!(state->exp_tls_verify_certificates && >+ *state->exp_tls_verify_certificates)) >+ { >+ DEBUG(D_tls) >+ debug_printf("TLS: tls_verify_certificates expanded empty, ignoring\n"); >+ /* With no tls_verify_certificates, we ignore tls_crl too */ >+ return OK; >+ } >+ } >+ else > { > DEBUG(D_tls) >- debug_printf("TLS: tls_verify_certificates expanded empty, ignoring\n"); >- /* With no tls_verify_certificates, we ignore tls_crl too */ >+ debug_printf("TLS: tls_verify_certificates not set or empty, ignoring\n"); > return OK; > } >+ rc = creds_load_cabundle(state, state->exp_tls_verify_certificates, host, errstr); >+ if (rc != OK) return rc; > } > else > { > DEBUG(D_tls) >- debug_printf("TLS: tls_verify_certificates not set or empty, ignoring\n"); >- return OK; >- } >- >-#ifdef SUPPORT_SYSDEFAULT_CABUNDLE >-if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0) >- cert_count = gnutls_certificate_set_x509_system_trust(state->x509_cred); >-else >-#endif >- { >- if (Ustat(state->exp_tls_verify_certificates, &statbuf) < 0) >- { >- log_write(0, LOG_MAIN|LOG_PANIC, "could not stat '%s' " >- "(tls_verify_certificates): %s", state->exp_tls_verify_certificates, >- strerror(errno)); >- return DEFER; >- } >- >-#ifndef SUPPORT_CA_DIR >- /* The test suite passes in /dev/null; we could check for that path explicitly, >- but who knows if someone has some weird FIFO which always dumps some certs, or >- other weirdness. The thing we really want to check is that it's not a >- directory, since while OpenSSL supports that, GnuTLS does not. >- So s/!S_ISREG/S_ISDIR/ and change some messaging ... */ >- if (S_ISDIR(statbuf.st_mode)) >- { >- DEBUG(D_tls) >- debug_printf("verify certificates path is a dir: \"%s\"\n", >- state->exp_tls_verify_certificates); >- log_write(0, LOG_MAIN|LOG_PANIC, >- "tls_verify_certificates \"%s\" is a directory", >- state->exp_tls_verify_certificates); >- return DEFER; >- } >-#endif >- >- DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n", >- state->exp_tls_verify_certificates, statbuf.st_size); >- >- if (statbuf.st_size == 0) >- { >- DEBUG(D_tls) >- debug_printf("cert file empty, no certs, no verification, ignoring any CRL\n"); >- return OK; >- } >- >- cert_count = >- >-#ifdef SUPPORT_CA_DIR >- (statbuf.st_mode & S_IFMT) == S_IFDIR >- ? >- gnutls_certificate_set_x509_trust_dir(state->x509_cred, >- CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM) >- : >-#endif >- gnutls_certificate_set_x509_trust_file(state->x509_cred, >- CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM); >+ debug_printf("%s CA bundle was preloaded\n", host ? "client" : "server"); >+ state->exp_tls_verify_certificates = US state->tls_verify_certificates; > > #ifdef SUPPORT_CA_DIR >- /* Mimic the behaviour with OpenSSL of not advertising a usable-cert list >- when using the directory-of-certs config model. */ >- >- if ((statbuf.st_mode & S_IFMT) == S_IFDIR) >- gnutls_certificate_send_x509_rdn_sequence(state->session, 1); >+/* Mimic the behaviour with OpenSSL of not advertising a usable-cert list >+when using the directory-of-certs config model. */ >+ if (state->lib_state.ca_rdn_emulate) >+ gnutls_certificate_send_x509_rdn_sequence(state->session, 1); > #endif > } > >-if (cert_count < 0) >- return tls_error_gnu(US"setting certificate trust", cert_count, host, errstr); >-DEBUG(D_tls) >- debug_printf("Added %d certificate authorities.\n", cert_count); > >-if (state->tls_crl && *state->tls_crl && >- state->exp_tls_crl && *state->exp_tls_crl) >+if (!state->lib_state.crl) > { >- DEBUG(D_tls) debug_printf("loading CRL file = %s\n", state->exp_tls_crl); >- if ((cert_count = gnutls_certificate_set_x509_crl_file(state->x509_cred, >- CS state->exp_tls_crl, GNUTLS_X509_FMT_PEM)) < 0) >- return tls_error_gnu(US"gnutls_certificate_set_x509_crl_file", >- cert_count, host, errstr); >- >- DEBUG(D_tls) debug_printf("Processed %d CRLs.\n", cert_count); >+ if ( state->tls_crl && *state->tls_crl >+ && state->exp_tls_crl && *state->exp_tls_crl) >+ return creds_load_crl(state, state->exp_tls_crl, errstr); >+ } >+else >+ { >+ DEBUG(D_tls) >+ debug_printf("%s CRL was preloaded\n", host ? "client" : "server"); >+ state->exp_tls_crl = US state->tls_crl; > } > > return OK; >@@ -1436,7 +1970,6 @@ > int rc; > const host_item *host = state->host; /* macro should be reconsidered? */ > >-#ifndef GNUTLS_AUTO_DHPARAMS > /* Create D-H parameters, or read them from the cache file. This function does > its own SMTP error messaging. This only happens for the server, TLS D-H ignores > client-side params. */ >@@ -1446,16 +1979,16 @@ > if (!dh_server_params) > if ((rc = init_server_dh(errstr)) != OK) return rc; > >- /* Unnecessary & discouraged with 3.6.0 or later */ >- gnutls_certificate_set_dh_params(state->x509_cred, dh_server_params); >+ /* Unnecessary & discouraged with 3.6.0 or later, according to docs. But without it, >+ no DHE- ciphers are advertised. */ >+ gnutls_certificate_set_dh_params(state->lib_state.x509_cred, dh_server_params); > } >-#endif > > /* Link the credentials to the session. */ > > if ((rc = gnutls_credentials_set(state->session, >- GNUTLS_CRD_CERTIFICATE, state->x509_cred))) >- return tls_error_gnu(US"gnutls_credentials_set", rc, host, errstr); >+ GNUTLS_CRD_CERTIFICATE, state->lib_state.x509_cred))) >+ return tls_error_gnu(state, US"gnutls_credentials_set", rc, errstr); > > return OK; > } >@@ -1465,47 +1998,12 @@ > *************************************************/ > > >-#ifndef DISABLE_OCSP >- >-static BOOL >-tls_is_buggy_ocsp(void) >-{ >-const uschar * s; >-uschar maj, mid, mic; >- >-s = CUS gnutls_check_version(NULL); >-maj = atoi(CCS s); >-if (maj == 3) >- { >- while (*s && *s != '.') s++; >- mid = atoi(CCS ++s); >- if (mid <= 2) >- return TRUE; >- else if (mid >= 5) >- return FALSE; >- else >- { >- while (*s && *s != '.') s++; >- mic = atoi(CCS ++s); >- return mic <= (mid == 3 ? 16 : 3); >- } >- } >-return FALSE; >-} >- >-#endif >- >- > /* Called from both server and client code. In the case of a server, errors > before actual TLS negotiation return DEFER. > > Arguments: > host connected host, if client; NULL if server >- certificate certificate file >- privatekey private key file >- sni TLS SNI to send, sometimes when client; else NULL >- cas CA certs file >- crl CRL file >+ ob tranport options block, if client; NULL if server > require_ciphers tls_require_ciphers setting > caller_state returned state-info structure > errstr error string pointer >@@ -1516,12 +2014,8 @@ > static int > tls_init( > const host_item *host, >- const uschar *certificate, >- const uschar *privatekey, >- const uschar *sni, >- const uschar *cas, >- const uschar *crl, >- const uschar *require_ciphers, >+ smtp_transport_options_block * ob, >+ const uschar * require_ciphers, > exim_gnutls_state_st **caller_state, > tls_support * tlsp, > uschar ** errstr) >@@ -1529,85 +2023,61 @@ > exim_gnutls_state_st * state; > int rc; > size_t sz; >-const char * errpos; >-const uschar * p; > >-if (!exim_gnutls_base_init_done) >- { >- DEBUG(D_tls) debug_printf("GnuTLS global init required.\n"); >- >-#if defined(HAVE_GNUTLS_PKCS11) && !defined(GNUTLS_AUTO_PKCS11_MANUAL) >- /* By default, gnutls_global_init will init PKCS11 support in auto mode, >- which loads modules from a config file, which sounds good and may be wanted >- by some sysadmin, but also means in common configurations that GNOME keyring >- 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) >- if ((rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL))) >- return tls_error_gnu(US"gnutls_pkcs11_init", rc, host, errstr); >-#endif >- >-#ifndef GNUTLS_AUTO_GLOBAL_INIT >- if ((rc = gnutls_global_init())) >- return tls_error_gnu(US"gnutls_global_init", rc, host, errstr); >-#endif >- >-#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0 >- DEBUG(D_tls) >- { >- gnutls_global_set_log_function(exim_gnutls_logger_cb); >- /* arbitrarily chosen level; bump up to 9 for more */ >- gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL); >- } >-#endif >- >-#ifndef DISABLE_OCSP >- if (tls_ocsp_file && (gnutls_buggy_ocsp = tls_is_buggy_ocsp())) >- log_write(0, LOG_MAIN, "OCSP unusable with this GnuTLS library version"); >-#endif >- >- exim_gnutls_base_init_done = TRUE; >- } >+if ( !exim_gnutls_base_init_done >+ && (rc = tls_g_init(errstr)) != OK) >+ return rc; > > 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), FALSE); >+ state = store_get(sizeof(exim_gnutls_state_st), GET_UNTAINTED); > store_pool = old_pool; > > memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); >+ state->lib_state = ob->tls_preload; > state->tlsp = tlsp; > DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n"); > rc = gnutls_init(&state->session, GNUTLS_CLIENT); >+ >+ state->tls_certificate = ob->tls_certificate; >+ state->tls_privatekey = ob->tls_privatekey; >+ state->tls_sni = ob->tls_sni; >+ state->tls_verify_certificates = ob->tls_verify_certificates; >+ state->tls_crl = ob->tls_crl; > } > else > { >+ /* Server operations always use the one state_server context. It is not >+ shared because we have forked a fresh process for every receive. However it >+ can get re-used for successive TLS sessions on a single TCP connection. */ >+ > state = &state_server; >- memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); > state->tlsp = tlsp; > DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n"); > rc = gnutls_init(&state->session, GNUTLS_SERVER); >+ >+ state->tls_certificate = tls_certificate; >+ state->tls_privatekey = tls_privatekey; >+ state->tls_sni = NULL; >+ state->tls_verify_certificates = tls_verify_certificates; >+ state->tls_crl = tls_crl; > } > if (rc) >- return tls_error_gnu(US"gnutls_init", rc, host, errstr); >+ return tls_error_gnu(state, US"gnutls_init", rc, errstr); > >+state->tls_require_ciphers = require_ciphers; > state->host = host; > >-state->tls_certificate = certificate; >-state->tls_privatekey = privatekey; >-state->tls_require_ciphers = require_ciphers; >-state->tls_sni = sni; >-state->tls_verify_certificates = cas; >-state->tls_crl = crl; >- > /* This handles the variables that might get re-expanded after TLS SNI; >-that's tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */ >+tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */ > > DEBUG(D_tls) >- debug_printf("Expanding various TLS configuration options for session credentials.\n"); >+ debug_printf("Expanding various TLS configuration options for session credentials\n"); > if ((rc = tls_expand_session_files(state, errstr)) != OK) return rc; > > /* These are all other parts of the x509_cred handling, since SNI in GnuTLS >@@ -1618,7 +2088,7 @@ > /* set SNI in client, only */ > if (host) > { >- if (!expand_check(sni, US"tls_out_sni", &state->tlsp->sni, errstr)) >+ if (!expand_check(state->tls_sni, US"tls_out_sni", &state->tlsp->sni, errstr)) > return DEFER; > if (state->tlsp->sni && *state->tlsp->sni) > { >@@ -1627,45 +2097,50 @@ > sz = Ustrlen(state->tlsp->sni); > if ((rc = gnutls_server_name_set(state->session, > GNUTLS_NAME_DNS, state->tlsp->sni, sz))) >- return tls_error_gnu(US"gnutls_server_name_set", rc, host, errstr); >+ return tls_error_gnu(state, US"gnutls_server_name_set", rc, errstr); > } > } > else if (state->tls_sni) > DEBUG(D_tls) debug_printf("*** PROBABLY A BUG *** " \ > "have an SNI set for a server [%s]\n", state->tls_sni); > >-/* This is the priority string support, >-http://www.gnutls.org/manual/html_node/Priority-Strings.html >-and replaces gnutls_require_kx, gnutls_require_mac & gnutls_require_protocols. >-This was backwards incompatible, but means Exim no longer needs to track >-all algorithms and provide string forms for them. */ >- >-p = NULL; >-if (state->tls_require_ciphers && *state->tls_require_ciphers) >+if (!state->lib_state.pri_string) > { >- if (!expand_check_tlsvar(tls_require_ciphers, errstr)) >- return DEFER; >- if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers) >+ const uschar * p = NULL; >+ const char * errpos; >+ >+ /* This is the priority string support, >+ http://www.gnutls.org/manual/html_node/Priority-Strings.html >+ and replaces gnutls_require_kx, gnutls_require_mac & gnutls_require_protocols. >+ This was backwards incompatible, but means Exim no longer needs to track >+ all algorithms and provide string forms for them. */ >+ >+ if (state->tls_require_ciphers && *state->tls_require_ciphers) > { >- p = state->exp_tls_require_ciphers; >- DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n", p); >+ if (!Expand_check_tlsvar(tls_require_ciphers, errstr)) >+ return DEFER; >+ if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers) >+ { >+ p = state->exp_tls_require_ciphers; >+ DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n", p); >+ } > } >+ >+ if ((rc = creds_load_pristring(state, p, &errpos))) >+ return tls_error_gnu(state, string_sprintf( >+ "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"", >+ p, (long)(errpos - CS p), errpos), >+ rc, errstr); > } >-if (!p) >+else > { >- p = exim_default_gnutls_priority; >- DEBUG(D_tls) >- debug_printf("GnuTLS using default session cipher/priority \"%s\"\n", p); >+ DEBUG(D_tls) debug_printf("cipher list preloaded\n"); >+ state->exp_tls_require_ciphers = US state->tls_require_ciphers; > } > >-if ((rc = gnutls_priority_init(&state->priority_cache, CCS p, &errpos))) >- return tls_error_gnu(string_sprintf( >- "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"", >- p, errpos - CS p, errpos), >- rc, host, errstr); > >-if ((rc = gnutls_priority_set(state->session, state->priority_cache))) >- return tls_error_gnu(US"gnutls_priority_set", rc, host, errstr); >+if ((rc = gnutls_priority_set(state->session, state->lib_state.pri_cache))) >+ return tls_error_gnu(state, US"gnutls_priority_set", rc, errstr); > > /* This also sets the server ticket expiration time to the same, and > the STEK rotation time to 3x. */ >@@ -1863,7 +2338,7 @@ > DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \ > (Label), gnutls_strerror(rc)); \ > if (state->verify_requirement >= VERIFY_REQUIRED) \ >- return tls_error_gnu((Label), rc, state->host, errstr); \ >+ return tls_error_gnu(state, (Label), rc, errstr); \ > return OK; \ > } \ > } while (0) >@@ -1880,7 +2355,7 @@ > exim_gnutls_peer_err(US"getting size for cert DN failed"); > return FAIL; /* should not happen */ > } >-dn_buf = store_get_perm(sz, TRUE); /* tainted */ >+dn_buf = store_get_perm(sz, GET_TAINTED); > rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz); > exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]"); > >@@ -1960,8 +2435,8 @@ > for (nrec = 0; state->dane_data_len[nrec]; ) nrec++; > nrec++; > >- dd = store_get(nrec * sizeof(uschar *), FALSE); >- ddl = store_get(nrec * sizeof(int), FALSE); >+ dd = store_get(nrec * sizeof(uschar *), GET_UNTAINTED); >+ ddl = store_get(nrec * sizeof(int), GET_UNTAINTED); > nrec--; > > if ((rc = dane_state_init(&s, 0))) >@@ -2191,7 +2666,7 @@ > { > DEBUG(D_tls) > if (rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) >- debug_printf("TLS: no SNI presented in handshake.\n"); >+ debug_printf("TLS: no SNI presented in handshake\n"); > else > debug_printf("TLS failure: gnutls_server_name_get(): %s [%d]\n", > gnutls_strerror(rc), rc); >@@ -2207,7 +2682,7 @@ > /* 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, TRUE); >+state->received_sni = string_copy_taint(US sni_name, GET_TAINTED); > store_pool = old_pool; > > /* We set this one now so that variable expansions below will work */ >@@ -2266,7 +2741,7 @@ > > state->tlsp->peercert = crt; > if ((yield = event_raise(state->event_action, >- US"tls:cert", string_sprintf("%d", cert_list_size)))) >+ US"tls:cert", string_sprintf("%d", cert_list_size), &errno))) > { > log_write(0, LOG_MAIN, > "SSL verify denied by event-action: depth=%d: %s", >@@ -2331,7 +2806,7 @@ > } > > >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifdef EXIM_HAVE_TLS_RESUME > static int > tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when, > unsigned incoming, const gnutls_datum_t * msg) >@@ -2386,7 +2861,72 @@ > DEBUG(D_tls) debug_printf("Session resumed\n"); > } > } >-#endif >+#endif /* EXIM_HAVE_TLS_RESUME */ >+ >+ >+#ifdef EXIM_HAVE_ALPN >+/* Expand and convert an Exim list to a gnutls_datum list. False return for fail. >+NULL plist return for silent no-ALPN. >+*/ >+ >+static BOOL >+tls_alpn_plist(uschar ** tls_alpn, const gnutls_datum_t ** plist, unsigned * plen, >+ uschar ** errstr) >+{ >+uschar * exp_alpn; >+ >+if (!expand_check(*tls_alpn, US"tls_alpn", &exp_alpn, errstr)) >+ return FALSE; >+ >+if (!exp_alpn) >+ { >+ DEBUG(D_tls) debug_printf("Setting TLS ALPN forced to fail, not sending\n"); >+ *plist = NULL; >+ } >+else >+ { >+ const uschar * list = exp_alpn; >+ int sep = 0; >+ unsigned cnt = 0; >+ gnutls_datum_t * p; >+ uschar * s; >+ >+ while (string_nextinlist(&list, &sep, NULL, 0)) cnt++; >+ >+ p = store_get(sizeof(gnutls_datum_t) * cnt, exp_alpn); >+ list = exp_alpn; >+ for (int i = 0; s = string_nextinlist(&list, &sep, NULL, 0); i++) >+ { p[i].data = s; p[i].size = Ustrlen(s); } >+ *plist = (*plen = cnt) ? p : NULL; >+ } >+return TRUE; >+} >+ >+static void >+tls_server_set_acceptable_alpns(exim_gnutls_state_st * state, uschar ** errstr) >+{ >+uschar * local_alpn = string_copy(tls_alpn); >+int rc; >+const gnutls_datum_t * plist; >+unsigned plen; >+ >+if (tls_alpn_plist(&local_alpn, &plist, &plen, errstr) && plist) >+ { >+ /* This seems to be only mandatory if the client sends an ALPN extension; >+ not trying ALPN is ok. Need to decide how to support server-side must-alpn. */ >+ >+ server_seen_alpn = 0; >+ if (!(rc = gnutls_alpn_set_protocols(state->session, plist, plen, >+ GNUTLS_ALPN_MANDATORY))) >+ gnutls_handshake_set_hook_function(state->session, >+ GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb); >+ else >+ DEBUG(D_tls) >+ debug_printf("setting alpn protocols: %s\n", US gnutls_strerror(rc)); >+ } >+} >+#endif /* EXIM_HAVE_ALPN */ >+ > /* ------------------------------------------------------------------------ */ > /* Exported functions */ > >@@ -2402,7 +2942,6 @@ > a TLS session. > > Arguments: >- require_ciphers list of allowed ciphers or NULL > errstr pointer to error string > > Returns: OK on success >@@ -2412,7 +2951,7 @@ > */ > > int >-tls_server_start(const uschar * require_ciphers, uschar ** errstr) >+tls_server_start(uschar ** errstr) > { > int rc; > exim_gnutls_state_st * state = NULL; >@@ -2436,16 +2975,19 @@ > gettimeofday(&t0, NULL); > #endif > >- if ((rc = tls_init(NULL, tls_certificate, tls_privatekey, >- NULL, tls_verify_certificates, tls_crl, >- require_ciphers, &state, &tls_in, errstr)) != OK) return rc; >+ if ((rc = tls_init(NULL, NULL, >+ tls_require_ciphers, &state, &tls_in, errstr)) != OK) return rc; > > #ifdef MEASURE_TIMING > report_time_since(&t0, US"server tls_init (delta)"); > #endif > } > >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifdef EXIM_HAVE_ALPN >+tls_server_set_acceptable_alpns(state, errstr); >+#endif >+ >+#ifdef EXIM_HAVE_TLS_RESUME > tls_server_resume_prehandshake(state); > #endif > >@@ -2455,21 +2997,21 @@ > if (verify_check_host(&tls_verify_hosts) == OK) > { > DEBUG(D_tls) >- debug_printf("TLS: a client certificate will be required.\n"); >+ debug_printf("TLS: a client certificate will be required\n"); > state->verify_requirement = VERIFY_REQUIRED; > gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); > } > else if (verify_check_host(&tls_try_verify_hosts) == OK) > { > DEBUG(D_tls) >- debug_printf("TLS: a client certificate will be requested but not required.\n"); >+ debug_printf("TLS: a client certificate will be requested but not required\n"); > state->verify_requirement = VERIFY_OPTIONAL; > gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST); > } > else > { > DEBUG(D_tls) >- debug_printf("TLS: a client certificate will not be requested.\n"); >+ debug_printf("TLS: a client certificate will not be requested\n"); > state->verify_requirement = VERIFY_NONE; > gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE); > } >@@ -2479,7 +3021,7 @@ > { > state->event_action = event_action; > gnutls_session_set_ptr(state->session, state); >- gnutls_certificate_set_verify_function(state->x509_cred, verify_cb); >+ gnutls_certificate_set_verify_function(state->lib_state.x509_cred, verify_cb); > } > #endif > >@@ -2522,6 +3064,9 @@ > > if (rc != GNUTLS_E_SUCCESS) > { >+ DEBUG(D_tls) debug_printf(" error %d from gnutls_handshake: %s\n", >+ rc, gnutls_strerror(rc)); >+ > /* It seems that, except in the case of a timeout, we have to close the > connection right here; otherwise if the other end is running OpenSSL it hangs > until the server times out. */ >@@ -2529,14 +3074,19 @@ > if (sigalrm_seen) > { > tls_error(US"gnutls_handshake", US"timed out", NULL, errstr); >+#ifndef DISABLE_EVENT >+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); >+#endif > gnutls_db_remove_session(state->session); > } > else > { >- tls_error_gnu(US"gnutls_handshake", rc, NULL, errstr); >+ tls_error_gnu(state, US"gnutls_handshake", rc, errstr); >+#ifndef DISABLE_EVENT >+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); >+#endif > (void) gnutls_alert_send_appropriate(state->session, rc); > gnutls_deinit(state->session); >- gnutls_certificate_free_credentials(state->x509_cred); > millisleep(500); > shutdown(state->fd_out, SHUT_WR); > for (int i = 1024; fgetc(smtp_in) != EOF && i > 0; ) i--; /* drain skt */ >@@ -2553,12 +3103,39 @@ > tls_in.ext_master_secret = TRUE; > #endif > >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifdef EXIM_HAVE_TLS_RESUME > tls_server_resume_posthandshake(state); > #endif > > DEBUG(D_tls) post_handshake_debug(state); > >+#ifdef EXIM_HAVE_ALPN >+if (server_seen_alpn > 0) >+ { >+ DEBUG(D_tls) >+ { /* The client offered ALPN. See what was negotiated. */ >+ gnutls_datum_t p = {.size = 0}; >+ int rc = gnutls_alpn_get_selected_protocol(state->session, &p); >+ if (!rc) >+ debug_printf("ALPN negotiated: %.*s\n", (int)p.size, p.data); >+ else >+ debug_printf("getting alpn protocol: %s\n", US gnutls_strerror(rc)); >+ >+ } >+ } >+else if (server_seen_alpn == 0) >+ if (verify_check_host(&hosts_require_alpn) == OK) >+ { >+ gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_NO_APPLICATION_PROTOCOL); >+ tls_error(US"handshake", US"ALPN required but not negotiated", NULL, errstr); >+ return FAIL; >+ } >+ else >+ DEBUG(D_tls) debug_printf("TLS: no ALPN presented in handshake\n"); >+else >+ DEBUG(D_tls) debug_printf("TLS: was not watching for ALPN\n"); >+#endif >+ > /* Verify after the fact */ > > if (!verify_certificate(state, errstr)) >@@ -2585,10 +3162,10 @@ > receive_getc = tls_getc; > receive_getbuf = tls_getbuf; > receive_get_cache = tls_get_cache; >+receive_hasc = tls_hasc; > receive_ungetc = tls_ungetc; > receive_feof = tls_feof; > receive_ferror = tls_ferror; >-receive_smtp_buffered = tls_smtp_buffered; > > return OK; > } >@@ -2609,7 +3186,7 @@ > host->certname; > #endif > DEBUG(D_tls) >- debug_printf("TLS: server cert verification includes hostname: \"%s\".\n", >+ debug_printf("TLS: server cert verification includes hostname: \"%s\"\n", > state->exp_tls_verify_cert_hostnames); > } > } >@@ -2638,8 +3215,8 @@ > rr = dns_next_rr(dnsa, &dnss, RESET_NEXT) > ) if (rr->type == T_TLSA) i++; > >-dane_data = store_get(i * sizeof(uschar *), FALSE); >-dane_data_len = store_get(i * sizeof(int), FALSE); >+dane_data = store_get(i * sizeof(uschar *), GET_UNTAINTED); >+dane_data_len = store_get(i * sizeof(int), GET_UNTAINTED); > > i = 0; > for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; >@@ -2686,7 +3263,7 @@ > > > >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifdef EXIM_HAVE_TLS_RESUME > /* On the client, get any stashed session for the given IP from hints db > and apply it to the ssl-connection for attempted resumption. Although > there is a gnutls_session_ticket_enable_client() interface it is >@@ -2697,25 +3274,28 @@ > > static void > tls_retrieve_session(tls_support * tlsp, gnutls_session_t session, >- host_item * host, smtp_transport_options_block * ob) >+ smtp_connect_args * conn_args, smtp_transport_options_block * ob) > { > tlsp->resumption = RESUME_SUPPORTED; >-if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK) >+ >+if (!conn_args->have_lbserver) >+ { DEBUG(D_tls) debug_printf("resumption not supported on continued-connection\n"); } >+else if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, conn_args->host) == OK) > { > dbdata_tls_session * dt; > int len, rc; > open_db dbblock, * dbm_file; > >- DEBUG(D_tls) >- debug_printf("check for resumable session for %s\n", host->address); > tlsp->host_resumable = TRUE; >+ tls_client_resmption_key(tlsp, conn_args, ob); >+ > tlsp->resumption |= RESUME_CLIENT_REQUESTED; > if ((dbm_file = dbfn_open(US"tls", O_RDONLY, &dbblock, FALSE, FALSE))) > { >- /* Key for the db is the IP. We'd like to filter the retrieved session >- for ticket advisory expiry, but 3.6.1 seems to give no access to that */ >+ /* We'd like to filter the retrieved session for ticket advisory expiry, >+ but 3.6.1 seems to give no access to that */ > >- if ((dt = dbfn_read_with_length(dbm_file, host->address, &len))) >+ if ((dt = dbfn_read_with_length(dbm_file, tlsp->resume_index, &len))) > if (!(rc = gnutls_session_set_data(session, > CUS dt->session, (size_t)len - sizeof(dbdata_tls_session)))) > { >@@ -2752,7 +3332,7 @@ > { > open_db dbblock, * dbm_file; > int dlen = sizeof(dbdata_tls_session) + tkt.size; >- dbdata_tls_session * dt = store_get(dlen, TRUE); >+ dbdata_tls_session * dt = store_get(dlen, GET_TAINTED); > > DEBUG(D_tls) debug_printf("session data size %u\n", (unsigned)tkt.size); > memcpy(dt->session, tkt.data, tkt.size); >@@ -2761,8 +3341,7 @@ > if ((dbm_file = dbfn_open(US"tls", O_RDWR, &dbblock, FALSE, FALSE))) > { > /* key for the db is the IP */ >- dbfn_delete(dbm_file, host->address); >- dbfn_write(dbm_file, host->address, dt, dlen); >+ dbfn_write(dbm_file, tlsp->resume_index, dt, dlen); > dbfn_close(dbm_file); > > DEBUG(D_tls) >@@ -2797,14 +3376,14 @@ > > static void > tls_client_resume_prehandshake(exim_gnutls_state_st * state, >- tls_support * tlsp, host_item * host, >+ tls_support * tlsp, smtp_connect_args * conn_args, > smtp_transport_options_block * ob) > { > gnutls_session_set_ptr(state->session, state); > gnutls_handshake_set_hook_function(state->session, > GNUTLS_HANDSHAKE_NEW_SESSION_TICKET, GNUTLS_HOOK_POST, tls_client_ticket_cb); > >-tls_retrieve_session(tlsp, state->session, host, ob); >+tls_retrieve_session(tlsp, state->session, conn_args, ob); > } > > static void >@@ -2819,7 +3398,7 @@ > > tls_save_session(tlsp, state->session, host); > } >-#endif /* EXPERIMENTAL_TLS_RESUME */ >+#endif /* !DISABLE_TLS_RESUME */ > > > /************************************************* >@@ -2870,7 +3449,7 @@ > > if (conn_args->dane && ob->dane_require_tls_ciphers) > { >- /* not using expand_check_tlsvar because not yet in state */ >+ /* not using Expand_check_tlsvar because not yet in state */ > if (!expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers", > &cipher_list, errstr)) > return FALSE; >@@ -2888,17 +3467,36 @@ > gettimeofday(&t0, NULL); > #endif > >- if (tls_init(host, ob->tls_certificate, ob->tls_privatekey, >- ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl, >- cipher_list, &state, tlsp, errstr) != OK) >+ if (tls_init(host, ob, cipher_list, &state, tlsp, errstr) != OK) > return FALSE; > >- > #ifdef MEASURE_TIMING > report_time_since(&t0, US"client tls_init (delta)"); > #endif > } > >+if (ob->tls_alpn) >+#ifdef EXIM_HAVE_ALPN >+ { >+ const gnutls_datum_t * plist; >+ unsigned plen; >+ >+ if (!tls_alpn_plist(&ob->tls_alpn, &plist, &plen, errstr)) >+ return FALSE; >+ if (plist) >+ if (gnutls_alpn_set_protocols(state->session, plist, plen, 0) != 0) >+ { >+ tls_error(US"alpn init", NULL, state->host, errstr); >+ return FALSE; >+ } >+ else >+ DEBUG(D_tls) debug_printf("Setting TLS ALPN '%s'\n", ob->tls_alpn); >+ } >+#else >+ log_write(0, LOG_MAIN, "ALPN unusable with this GnuTLS library version; ignoring \"%s\"\n", >+ ob->tls_alpn); >+#endif >+ > { > int dh_min_bits = ob->tls_dh_min_bits; > if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS) >@@ -2924,7 +3522,7 @@ > if (conn_args->dane && dane_tlsa_load(state, &conn_args->tlsa_dnsa)) > { > DEBUG(D_tls) >- debug_printf("TLS: server certificate DANE required.\n"); >+ debug_printf("TLS: server certificate DANE required\n"); > state->verify_requirement = VERIFY_DANE; > gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); > } >@@ -2939,7 +3537,7 @@ > { > tls_client_setup_hostname_checks(host, state, ob); > DEBUG(D_tls) >- debug_printf("TLS: server certificate verification required.\n"); >+ debug_printf("TLS: server certificate verification required\n"); > state->verify_requirement = VERIFY_REQUIRED; > gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); > } >@@ -2947,14 +3545,14 @@ > { > tls_client_setup_hostname_checks(host, state, ob); > DEBUG(D_tls) >- debug_printf("TLS: server certificate verification optional.\n"); >+ debug_printf("TLS: server certificate verification optional\n"); > state->verify_requirement = VERIFY_OPTIONAL; > gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST); > } > else > { > DEBUG(D_tls) >- debug_printf("TLS: server certificate verification not required.\n"); >+ debug_printf("TLS: server certificate verification not required\n"); > state->verify_requirement = VERIFY_NONE; > gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE); > } >@@ -2967,15 +3565,15 @@ > if ((rc = gnutls_ocsp_status_request_enable_client(state->session, > NULL, 0, NULL)) != OK) > { >- tls_error_gnu(US"cert-status-req", rc, state->host, errstr); >+ tls_error_gnu(state, US"cert-status-req", rc, errstr); > return FALSE; > } > tlsp->ocsp = OCSP_NOT_RESP; > } > #endif > >-#ifdef EXPERIMENTAL_TLS_RESUME >-tls_client_resume_prehandshake(state, tlsp, host, ob); >+#ifdef EXIM_HAVE_TLS_RESUME >+tls_client_resume_prehandshake(state, tlsp, conn_args, ob); > #endif > > #ifndef DISABLE_EVENT >@@ -2983,7 +3581,7 @@ > { > state->event_action = tb->event_action; > gnutls_session_set_ptr(state->session, state); >- gnutls_certificate_set_verify_function(state->x509_cred, verify_cb); >+ gnutls_certificate_set_verify_function(state->lib_state.x509_cred, verify_cb); > } > #endif > >@@ -3009,7 +3607,7 @@ > tls_error(US"gnutls_handshake", US"timed out", state->host, errstr); > } > else >- tls_error_gnu(US"gnutls_handshake", rc, state->host, errstr); >+ tls_error_gnu(state, US"gnutls_handshake", rc, errstr); > return FALSE; > } > >@@ -3054,9 +3652,9 @@ > gnutls_free(printed.data); > } > else >- (void) tls_error_gnu(US"ocsp decode", rc, state->host, errstr); >+ (void) tls_error_gnu(state, US"ocsp decode", rc, errstr); > if (idx == 0 && rc) >- (void) tls_error_gnu(US"ocsp decode", rc, state->host, errstr); >+ (void) tls_error_gnu(state, US"ocsp decode", rc, errstr); > } > > if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0) >@@ -3074,10 +3672,28 @@ > } > #endif > >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifdef EXIM_HAVE_TLS_RESUME > tls_client_resume_posthandshake(state, tlsp, host); > #endif > >+#ifdef EXIM_HAVE_ALPN >+if (ob->tls_alpn) /* We requested. See what was negotiated. */ >+ { >+ gnutls_datum_t p = {.size = 0}; >+ >+ if (gnutls_alpn_get_selected_protocol(state->session, &p) == 0) >+ { DEBUG(D_tls) debug_printf("ALPN negotiated: '%.*s'\n", (int)p.size, p.data); } >+ else if (verify_check_given_host(CUSS &ob->hosts_require_alpn, host) == OK) >+ { >+ gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_NO_APPLICATION_PROTOCOL); >+ tls_error(US"handshake", US"ALPN required but not negotiated", state->host, errstr); >+ return FALSE; >+ } >+ else >+ DEBUG(D_tls) debug_printf("No ALPN negotiated"); >+ } >+#endif >+ > /* Sets various Exim expansion variables; may need to adjust for ACL callouts */ > > extract_exim_vars_from_tls_state(state); >@@ -3089,6 +3705,25 @@ > > > >+/* >+Arguments: >+ ct_ctx client TLS context pointer, or NULL for the one global server context >+*/ >+ >+void >+tls_shutdown_wr(void * ct_ctx) >+{ >+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server; >+tls_support * tlsp = state->tlsp; >+ >+if (!tlsp || tlsp->active.sock < 0) return; /* TLS was not active */ >+ >+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"); >+gnutls_bye(state->session, GNUTLS_SHUT_WR); >+} >+ > /************************************************* > * Close down a TLS session * > *************************************************/ >@@ -3099,27 +3734,39 @@ > > Arguments: > ct_ctx client context pointer, or NULL for the one global server context >- shutdown 1 if TLS close-alert is to be sent, >- 2 if also response to be waited for >+ do_shutdown 0 no data-flush or TLS close-alert >+ 1 if TLS close-alert is to be sent, >+ 2 if also response to be waited for (2s timeout) > > Returns: nothing > */ > > void >-tls_close(void * ct_ctx, int shutdown) >+tls_close(void * ct_ctx, int do_shutdown) > { > exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server; > tls_support * tlsp = state->tlsp; > > if (!tlsp || tlsp->active.sock < 0) return; /* TLS was not active */ > >-if (shutdown) >+if (do_shutdown) > { > DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n", >- shutdown > 1 ? " (with response-wait)" : ""); >+ do_shutdown > TLS_SHUTDOWN_NOWAIT ? " (with response-wait)" : ""); >+ >+ tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */ >+ >+#ifdef EXIM_TCP_CORK >+ if (do_shutdown == TLS_SHUTDOWN_WAIT) >+ (void) setsockopt(tlsp->active.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &off, sizeof(off)); >+#endif >+ >+ /* The library seems to have no way to only wait for a peer's >+ shutdown, so handle the same as TLS_SHUTDOWN_WAIT */ > > ALARM(2); >- gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR); >+ gnutls_bye(state->session, >+ do_shutdown > TLS_SHUTDOWN_NOWAIT ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR); > ALARM_CLR(0); > } > >@@ -3128,15 +3775,13 @@ > receive_getc = smtp_getc; > receive_getbuf = smtp_getbuf; > receive_get_cache = smtp_get_cache; >+ receive_hasc = smtp_hasc; > receive_ungetc = smtp_ungetc; > receive_feof = smtp_feof; > receive_ferror = smtp_ferror; >- receive_smtp_buffered = smtp_buffered; > } > > gnutls_deinit(state->session); >-gnutls_certificate_free_credentials(state->x509_cred); >- > tlsp->active.sock = -1; > tlsp->active.tls_ctx = NULL; > /* Leave bits, peercert, cipher, peerdn, certificate_verified set, for logging */ >@@ -3144,7 +3789,6 @@ > > > if (state->xfer_buffer) store_free(state->xfer_buffer); >-memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); > } > > >@@ -3242,6 +3886,13 @@ > return state->xfer_buffer[state->xfer_buffer_lwm++]; > } > >+BOOL >+tls_hasc(void) >+{ >+exim_gnutls_state_st * state = &state_server; >+return state->xfer_buffer_lwm < state->xfer_buffer_hwm; >+} >+ > uschar * > tls_getbuf(unsigned * len) > { >@@ -3266,12 +3917,15 @@ > } > > >+/* Get up to the given number of bytes from any cached data, and feed to dkim. */ > void >-tls_get_cache() >+tls_get_cache(unsigned lim) > { > #ifndef DISABLE_DKIM > exim_gnutls_state_st * state = &state_server; > int n = state->xfer_buffer_hwm - state->xfer_buffer_lwm; >+if (n > lim) >+ n = lim; > if (n > 0) > dkim_exim_verify_feed(state->xfer_buffer+state->xfer_buffer_lwm, n); > #endif >@@ -3279,15 +3933,13 @@ > > > BOOL >-tls_could_read(void) >+tls_could_getc(void) > { > return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm > || gnutls_record_check_pending(state_server.session) > 0; > } > > >- >- > /************************************************* > * Read bytes from TLS channel * > *************************************************/ >@@ -3396,8 +4048,24 @@ > > if (outbytes < 0) > { >- DEBUG(D_tls) debug_printf("%s: gnutls_record_send err\n", __FUNCTION__); >- record_io_error(state, outbytes, US"send", NULL); >+#ifdef GNUTLS_E_PREMATURE_TERMINATION >+ if ( outbytes == GNUTLS_E_PREMATURE_TERMINATION && errno == ECONNRESET >+ && !ct_ctx && f.smtp_in_quit >+ ) >+ { /* Outlook, dammit */ >+ if (LOGGING(protocol_detail)) >+ log_write(0, LOG_MAIN, "[%s] after QUIT, client reset TCP before" >+ " SMTP response and TLS close\n", sender_host_address); >+ else >+ DEBUG(D_tls) debug_printf("[%s] SSL_write: after QUIT," >+ " client reset TCP before TLS close\n", sender_host_address); >+ } >+ else >+#endif >+ { >+ DEBUG(D_tls) debug_printf("%s: gnutls_record_send err\n", __FUNCTION__); >+ record_io_error(state, outbytes, US"send", NULL); >+ } > return -1; > } > if (outbytes == 0) >@@ -3486,7 +4154,7 @@ > i = gnutls_rnd(GNUTLS_RND_NONCE, smallbuf, needed_len); > if (i < 0) > { >- DEBUG(D_all) debug_printf("gnutls_rnd() failed, using fallback.\n"); >+ DEBUG(D_all) debug_printf("gnutls_rnd() failed, using fallback\n"); > return vaguely_random_number_fallback(max); > } > r = 0; >@@ -3572,7 +4240,7 @@ > rc = gnutls_priority_init(&priority_cache, CS expciphers, &errpos); > validate_check_rc(string_sprintf( > "gnutls_priority_init(%s) failed at offset %ld, \"%.8s..\"", >- expciphers, errpos - CS expciphers, errpos)); >+ expciphers, (long)(errpos - CS expciphers), errpos)); > > #undef return_deinit > #undef validate_check_rc >@@ -3592,17 +4260,18 @@ > > /* See a description in tls-openssl.c for an explanation of why this exists. > >-Arguments: a FILE* to print the results to >-Returns: nothing >+Arguments: string to append to >+Returns: string > */ > >-void >-tls_version_report(FILE *f) >+gstring * >+tls_version_report(gstring * g) > { >-fprintf(f, "Library version: GnuTLS: Compile: %s\n" >- " Runtime: %s\n", >- LIBGNUTLS_VERSION, >- gnutls_check_version(NULL)); >+return string_fmt_append(g, >+ "Library version: GnuTLS: Compile: %s\n" >+ " Runtime: %s\n", >+ LIBGNUTLS_VERSION, >+ gnutls_check_version(NULL)); > } > > #endif /*!MACRO_PREDEF*/ >diff -ur exim.orig/src/tls-openssl.c exim/src/tls-openssl.c >--- exim.orig/src/tls-openssl.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/tls-openssl.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2019 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Portions Copyright (c) The OpenSSL Project 1999 */ >@@ -80,6 +80,7 @@ > # ifndef DISABLE_OCSP > # define EXIM_HAVE_OCSP > # endif >+# define EXIM_HAVE_ALPN /* fail ret from hshake-cb is ignored by LibreSSL */ > # else > # define EXIM_NEED_OPENSSL_INIT > # endif >@@ -89,6 +90,10 @@ > # endif > #endif > >+#if LIBRESSL_VERSION_NUMBER >= 0x3040000fL >+# define EXIM_HAVE_OPENSSL_CIPHER_GET_ID >+#endif >+ > #if !defined(LIBRESSL_VERSION_NUMBER) \ > || LIBRESSL_VERSION_NUMBER >= 0x20010000L > # if !defined(OPENSSL_NO_ECDH) >@@ -116,7 +121,7 @@ > # define DISABLE_OCSP > #endif > >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifndef DISABLE_TLS_RESUME > # if OPENSSL_VERSION_NUMBER < 0x0101010L > # error OpenSSL version too old for session-resumption > # endif >@@ -227,12 +232,16 @@ > { US"no_tlsv1", SSL_OP_NO_TLSv1 }, > #endif > #ifdef SSL_OP_NO_TLSv1_1 >-#if SSL_OP_NO_TLSv1_1 == 0x00000400L >+# if OPENSSL_VERSION_NUMBER < 0x30000000L >+# if SSL_OP_NO_TLSv1_1 == 0x00000400L > /* Error in chosen value in 1.0.1a; see first item in CHANGES for 1.0.1b */ >-#warning OpenSSL 1.0.1a uses a bad value for SSL_OP_NO_TLSv1_1, ignoring >-#else >+# warning OpenSSL 1.0.1a uses a bad value for SSL_OP_NO_TLSv1_1, ignoring >+# define NO_SSL_OP_NO_TLSv1_1 >+# endif >+# endif >+# ifndef NO_SSL_OP_NO_TLSv1_1 > { US"no_tlsv1_1", SSL_OP_NO_TLSv1_1 }, >-#endif >+# endif > #endif > #ifdef SSL_OP_NO_TLSv1_2 > { US"no_tlsv1_2", SSL_OP_NO_TLSv1_2 }, >@@ -274,6 +283,7 @@ > > #ifndef MACRO_PREDEF > static int exim_openssl_options_size = nelem(exim_openssl_options); >+static long init_options = 0; > #endif > > #ifdef MACRO_PREDEF >@@ -292,7 +302,7 @@ > builtin_macro_create(buf); > } > >-# ifdef EXPERIMENTAL_TLS_RESUME >+# ifndef DISABLE_TLS_RESUME > builtin_macro_create_var(US"_RESUME_DECODE", RESUME_DECODE_STRING ); > # endif > # ifdef SSL_OP_NO_TLSv1_3 >@@ -305,6 +315,9 @@ > builtin_macro_create(US"_HAVE_TLS_OCSP"); > builtin_macro_create(US"_HAVE_TLS_OCSP_LIST"); > # endif >+# ifdef EXIM_HAVE_ALPN >+builtin_macro_create(US"_HAVE_TLS_ALPN"); >+# endif > } > #else > >@@ -350,12 +363,16 @@ > gstring * corked; > } exim_openssl_client_tls_ctx; > >-static SSL_CTX *server_ctx = NULL; >-static SSL *server_ssl = NULL; >+ >+/* static SSL_CTX *server_ctx = NULL; */ >+/* static SSL *server_ssl = NULL; */ > > #ifdef EXIM_HAVE_OPENSSL_TLSEXT > static SSL_CTX *server_sni = NULL; > #endif >+#ifdef EXIM_HAVE_ALPN >+static BOOL server_seen_alpn = FALSE; >+#endif > > static char ssl_errstring[256]; > >@@ -371,7 +388,11 @@ > OCSP_RESPONSE * resp; > } ocsp_resplist; > >-typedef struct tls_ext_ctx_cb { >+typedef struct exim_openssl_state { >+ exim_tlslib_state lib_state; >+#define lib_ctx libdata0 >+#define lib_ssl libdata1 >+ > tls_support * tlsp; > uschar * certificate; > uschar * privatekey; >@@ -399,22 +420,19 @@ > #ifndef DISABLE_EVENT > uschar * event_action; > #endif >-} tls_ext_ctx_cb; >+} exim_openssl_state_st; > > /* should figure out a cleanup of API to handle state preserved per > implementation, for various reasons, which can be void * in the APIs. > For now, we hack around it. */ >-tls_ext_ctx_cb *client_static_cbinfo = NULL; /*XXX should not use static; multiple concurrent clients! */ >-tls_ext_ctx_cb *server_static_cbinfo = NULL; >+exim_openssl_state_st *client_static_state = NULL; /*XXX should not use static; multiple concurrent clients! */ >+exim_openssl_state_st state_server = {.is_server = TRUE}; > > static int >-setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional, >- int (*cert_vfy_cb)(int, X509_STORE_CTX *), uschar ** errstr ); >+setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, >+ uschar ** errstr ); > > /* Callbacks */ >-#ifdef EXIM_HAVE_OPENSSL_TLSEXT >-static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg); >-#endif > #ifndef DISABLE_OCSP > static int tls_server_stapling_cb(SSL *s, void *arg); > #endif >@@ -422,18 +440,25 @@ > > > /* Daemon-called, before every connection, key create/rotate */ >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifndef DISABLE_TLS_RESUME > static void tk_init(void); > static int tls_exdata_idx = -1; > #endif > >-void >-tls_daemon_init(void) >+static void >+tls_per_lib_daemon_tick(void) > { >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifndef DISABLE_TLS_RESUME > tk_init(); > #endif >-return; >+} >+ >+/* Called once at daemon startup */ >+ >+static void >+tls_per_lib_daemon_init(void) >+{ >+tls_daemon_creds_reload(); > } > > >@@ -475,10 +500,312 @@ > > > >+/************************************************** >+* General library initalisation * >+**************************************************/ >+ >+static BOOL >+lib_rand_init(void * addr) >+{ >+randstuff r; >+if (!RAND_status()) return TRUE; >+ >+gettimeofday(&r.tv, NULL); >+r.p = getpid(); >+RAND_seed(US (&r), sizeof(r)); >+RAND_seed(US big_buffer, big_buffer_size); >+if (addr) RAND_seed(US addr, sizeof(addr)); >+ >+return RAND_status(); >+} >+ >+ >+static void >+tls_openssl_init(void) >+{ >+static BOOL once = FALSE; >+if (once) return; >+once = TRUE; >+ >+#ifdef EXIM_NEED_OPENSSL_INIT >+SSL_load_error_strings(); /* basic set up */ >+OpenSSL_add_ssl_algorithms(); >+#endif >+ >+#if defined(EXIM_HAVE_SHA256) && !defined(OPENSSL_AUTO_SHA256) >+/* SHA256 is becoming ever more popular. This makes sure it gets added to the >+list of available digests. */ >+EVP_add_digest(EVP_sha256()); >+#endif >+ >+(void) lib_rand_init(NULL); >+(void) tls_openssl_options_parse(openssl_options, &init_options); >+} >+ >+ >+ >+/************************************************* >+* Initialize for DH * >+*************************************************/ >+ >+/* If dhparam is set, expand it, and load up the parameters for DH encryption. >+Server only. >+ >+Arguments: >+ sctx The current SSL CTX (inbound or outbound) >+ dhparam DH parameter file or fixed parameter identity string >+ errstr error string pointer >+ >+Returns: TRUE if OK (nothing to set up, or setup worked) >+*/ >+ >+static BOOL >+init_dh(SSL_CTX * sctx, uschar * dhparam, uschar ** errstr) >+{ >+BIO * bio; >+#if OPENSSL_VERSION_NUMBER < 0x30000000L >+DH * dh; >+#else >+EVP_PKEY * pkey; >+#endif >+uschar * dhexpanded; >+const char * pem; >+int dh_bitsize; >+ >+if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded, errstr)) >+ return FALSE; >+ >+if (!dhexpanded || !*dhexpanded) >+ bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1); >+else if (dhexpanded[0] == '/') >+ { >+ if (!(bio = BIO_new_file(CS dhexpanded, "r"))) >+ { >+ tls_error(string_sprintf("could not read dhparams file %s", dhexpanded), >+ NULL, US strerror(errno), errstr); >+ return FALSE; >+ } >+ } >+else >+ { >+ if (Ustrcmp(dhexpanded, "none") == 0) >+ { >+ DEBUG(D_tls) debug_printf("Requested no DH parameters.\n"); >+ return TRUE; >+ } >+ >+ if (!(pem = std_dh_prime_named(dhexpanded))) >+ { >+ tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded), >+ NULL, US strerror(errno), errstr); >+ return FALSE; >+ } >+ bio = BIO_new_mem_buf(CS pem, -1); >+ } >+ >+if (!( >+#if OPENSSL_VERSION_NUMBER < 0x30000000L >+ dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL) >+#else >+ pkey = PEM_read_bio_Parameters_ex(bio, NULL, NULL, NULL) >+#endif >+ ) ) >+ { >+ BIO_free(bio); >+ tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded), >+ NULL, NULL, errstr); >+ return FALSE; >+ } >+ >+/* note: our default limit of 2236 is not a multiple of 8; the limit comes from >+an NSS limit, and the GnuTLS APIs handle bit-sizes fine, so we went with 2236. >+But older OpenSSL can only report in bytes (octets), not bits. If someone wants >+to dance at the edge, then they can raise the limit or use current libraries. */ >+ >+#if OPENSSL_VERSION_NUMBER < 0x30000000L >+# ifdef EXIM_HAVE_OPENSSL_DH_BITS >+/* Added in commit 26c79d5641d; `git describe --contains` says OpenSSL_1_1_0-pre1~1022 >+This predates OpenSSL_1_1_0 (before a, b, ...) so is in all 1.1.0 */ >+dh_bitsize = DH_bits(dh); >+# else >+dh_bitsize = 8 * DH_size(dh); >+# endif >+#else /* 3.0.0 + */ >+dh_bitsize = EVP_PKEY_get_bits(pkey); >+#endif >+ >+/* Even if it is larger, we silently return success rather than cause things to >+fail out, so that a too-large DH will not knock out all TLS; it's a debatable >+choice. Likewise for a failing attempt to set one. */ >+ >+if (dh_bitsize <= tls_dh_max_bits) >+ { >+ if ( >+#if OPENSSL_VERSION_NUMBER < 0x30000000L >+ SSL_CTX_set_tmp_dh(sctx, dh) >+#else >+ SSL_CTX_set0_tmp_dh_pkey(sctx, pkey) >+#endif >+ == 0) >+ { >+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); >+ log_write(0, LOG_MAIN|LOG_PANIC, "TLS error (D-H param setting '%s'): %s", >+ dhexpanded ? dhexpanded : US"default", ssl_errstring); >+#if OPENSSL_VERSION_NUMBER >= 0x30000000L >+ /* EVP_PKEY_free(pkey); crashes */ >+#endif >+ } >+ else >+ DEBUG(D_tls) >+ debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n", >+ dhexpanded ? dhexpanded : US"default", dh_bitsize); >+ } >+else >+ DEBUG(D_tls) >+ debug_printf("dhparams '%s' %d bits, is > tls_dh_max_bits limit of %d\n", >+ dhexpanded ? dhexpanded : US"default", dh_bitsize, tls_dh_max_bits); >+ >+#if OPENSSL_VERSION_NUMBER < 0x30000000L >+DH_free(dh); >+#endif >+/* The EVP_PKEY ownership stays with the ctx; do not free it */ >+ >+BIO_free(bio); >+return TRUE; >+} >+ >+ >+ >+ >+/************************************************* >+* Initialize for ECDH * >+*************************************************/ >+ >+/* Load parameters for ECDH encryption. Server only. >+ >+For now, we stick to NIST P-256 because: it's simple and easy to configure; >+it avoids any patent issues that might bite redistributors; despite events in >+the news and concerns over curve choices, we're not cryptographers, we're not >+pretending to be, and this is "good enough" to be better than no support, >+protecting against most adversaries. Given another year or two, there might >+be sufficient clarity about a "right" way forward to let us make an informed >+decision, instead of a knee-jerk reaction. >+ >+Longer-term, we should look at supporting both various named curves and >+external files generated with "openssl ecparam", much as we do for init_dh(). >+We should also support "none" as a value, to explicitly avoid initialisation. >+ >+Patches welcome. >+ >+Arguments: >+ sctx The current SSL CTX (inbound or outbound) >+ errstr error string pointer >+ >+Returns: TRUE if OK (nothing to set up, or setup worked) >+*/ >+ >+static BOOL >+init_ecdh(SSL_CTX * sctx, uschar ** errstr) >+{ >+#ifdef OPENSSL_NO_ECDH >+return TRUE; >+#else >+ >+uschar * exp_curve; >+int nid; >+BOOL rv; >+ >+# ifndef EXIM_HAVE_ECDH >+DEBUG(D_tls) >+ debug_printf("No OpenSSL API to define ECDH parameters, skipping\n"); >+return TRUE; >+# else >+ >+if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve, errstr)) >+ return FALSE; >+if (!exp_curve || !*exp_curve) >+ return TRUE; >+ >+/* "auto" needs to be handled carefully. >+ * OpenSSL < 1.0.2: we do not select anything, but fallback to prime256v1 >+ * OpenSSL < 1.1.0: we have to call SSL_CTX_set_ecdh_auto >+ * (openssl/ssl.h defines SSL_CTRL_SET_ECDH_AUTO) >+ * OpenSSL >= 1.1.0: we do not set anything, the libray does autoselection >+ * https://github.com/openssl/openssl/commit/fe6ef2472db933f01b59cad82aa925736935984b >+ */ >+if (Ustrcmp(exp_curve, "auto") == 0) >+ { >+#if OPENSSL_VERSION_NUMBER < 0x10002000L >+ DEBUG(D_tls) debug_printf( >+ "ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n"); >+ exp_curve = US"prime256v1"; >+#else >+# if defined SSL_CTRL_SET_ECDH_AUTO >+ DEBUG(D_tls) debug_printf( >+ "ECDH OpenSSL 1.0.2+: temp key parameter settings: autoselection\n"); >+ SSL_CTX_set_ecdh_auto(sctx, 1); >+ return TRUE; >+# else >+ DEBUG(D_tls) debug_printf( >+ "ECDH OpenSSL 1.1.0+: temp key parameter settings: default selection\n"); >+ return TRUE; >+# endif >+#endif >+ } >+ >+DEBUG(D_tls) debug_printf("ECDH: curve '%s'\n", exp_curve); >+if ( (nid = OBJ_sn2nid (CCS exp_curve)) == NID_undef >+# ifdef EXIM_HAVE_OPENSSL_EC_NIST2NID >+ && (nid = EC_curve_nist2nid(CCS exp_curve)) == NID_undef >+# endif >+ ) >+ { >+ tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'", exp_curve), >+ NULL, NULL, errstr); >+ return FALSE; >+ } >+ >+# if OPENSSL_VERSION_NUMBER < 0x30000000L >+ { >+ EC_KEY * ecdh; >+ if (!(ecdh = EC_KEY_new_by_curve_name(nid))) >+ { >+ tls_error(US"Unable to create ec curve", NULL, NULL, errstr); >+ return FALSE; >+ } >+ >+ /* The "tmp" in the name here refers to setting a temporary key >+ not to the stability of the interface. */ >+ >+ if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0)) >+ tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), NULL, NULL, errstr); >+ else >+ DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve); >+ EC_KEY_free(ecdh); >+ } >+ >+#else /* v 3.0.0 + */ >+ >+if ((rv = SSL_CTX_set1_groups(sctx, &nid, 1)) == 0) >+ tls_error(string_sprintf("Error enabling '%s' group", exp_curve), NULL, NULL, errstr); >+else >+ DEBUG(D_tls) debug_printf("ECDH: enabled '%s' group\n", exp_curve); >+ >+#endif >+ >+return !rv; >+ >+# endif /*EXIM_HAVE_ECDH*/ >+#endif /*OPENSSL_NO_ECDH*/ >+} >+ >+ >+ > /************************************************* >-* Callback to generate RSA key * >+* Expand key and cert file specs * > *************************************************/ > >+#if OPENSSL_VERSION_NUMBER < 0x30000000L > /* > Arguments: > s SSL connection (not used) >@@ -496,17 +823,16 @@ > BIGNUM *bn = BN_new(); > #endif > >-export = export; /* Shut picky compilers up */ > DEBUG(D_tls) debug_printf("Generating %d bit RSA key...\n", keylength); > >-#ifdef EXIM_HAVE_RSA_GENKEY_EX >+# ifdef EXIM_HAVE_RSA_GENKEY_EX > if ( !BN_set_word(bn, (unsigned long)RSA_F4) > || !(rsa_key = RSA_new()) > || !RSA_generate_key_ex(rsa_key, keylength, bn, NULL) > ) >-#else >+# else > if (!(rsa_key = RSA_generate_key(keylength, RSA_F4, NULL, NULL))) >-#endif >+# endif > > { > ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); >@@ -516,33 +842,155 @@ > } > return rsa_key; > } >+#endif /* pre-3.0.0 */ > > > >-/* Extreme debug >-#ifndef DISABLE_OCSP >-void >-x509_store_dump_cert_s_names(X509_STORE * store) >+/* Create and install a selfsigned certificate, for use in server mode */ >+/*XXX we could arrange to call this during prelo for a null tls_certificate option. >+The normal cache inval + relo will suffice. >+Just need a timer for inval. */ >+ >+static int >+tls_install_selfsign(SSL_CTX * sctx, uschar ** errstr) > { >-STACK_OF(X509_OBJECT) * roots= store->objs; >-static uschar name[256]; >+X509 * x509 = NULL; >+EVP_PKEY * pkey; >+X509_NAME * name; >+uschar * where; > >-for (int i= 0; i < sk_X509_OBJECT_num(roots); i++) >+DEBUG(D_tls) debug_printf("TLS: generating selfsigned server cert\n"); >+where = US"allocating pkey"; >+if (!(pkey = EVP_PKEY_new())) >+ goto err; >+ >+where = US"allocating cert"; >+if (!(x509 = X509_new())) >+ goto err; >+ >+where = US"generating pkey"; >+#if OPENSSL_VERSION_NUMBER < 0x30000000L >+ { >+ RSA * rsa; >+ if (!(rsa = rsa_callback(NULL, 0, 2048))) >+ goto err; >+ >+ where = US"assigning pkey"; >+ if (!EVP_PKEY_assign_RSA(pkey, rsa)) >+ goto err; >+ } >+#else >+pkey = EVP_RSA_gen(2048); >+#endif >+ >+X509_set_version(x509, 2); /* N+1 - version 3 */ >+ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); >+X509_gmtime_adj(X509_get_notBefore(x509), 0); >+X509_gmtime_adj(X509_get_notAfter(x509), (long)2 * 60 * 60); /* 2 hour */ >+X509_set_pubkey(x509, pkey); >+ >+name = X509_get_subject_name(x509); >+X509_NAME_add_entry_by_txt(name, "C", >+ MBSTRING_ASC, CUS "UK", -1, -1, 0); >+X509_NAME_add_entry_by_txt(name, "O", >+ MBSTRING_ASC, CUS "Exim Developers", -1, -1, 0); >+X509_NAME_add_entry_by_txt(name, "CN", >+ MBSTRING_ASC, CUS smtp_active_hostname, -1, -1, 0); >+X509_set_issuer_name(x509, name); >+ >+where = US"signing cert"; >+if (!X509_sign(x509, pkey, EVP_md5())) >+ goto err; >+ >+where = US"installing selfsign cert"; >+if (!SSL_CTX_use_certificate(sctx, x509)) >+ goto err; >+ >+where = US"installing selfsign key"; >+if (!SSL_CTX_use_PrivateKey(sctx, pkey)) >+ goto err; >+ >+return OK; >+ >+err: >+ (void) tls_error(where, NULL, NULL, errstr); >+ if (x509) X509_free(x509); >+ if (pkey) EVP_PKEY_free(pkey); >+ return DEFER; >+} >+ >+ >+ >+ >+ >+ >+ >+/************************************************* >+* Information callback * >+*************************************************/ >+ >+/* The SSL library functions call this from time to time to indicate what they >+are doing. We copy the string to the debugging output when TLS debugging has >+been requested. >+ >+Arguments: >+ s the SSL connection >+ where >+ ret >+ >+Returns: nothing >+*/ >+ >+static void >+info_callback(SSL *s, int where, int ret) >+{ >+DEBUG(D_tls) > { >- X509_OBJECT * tmp_obj= sk_X509_OBJECT_value(roots, i); >- if(tmp_obj->type == X509_LU_X509) >+ const uschar * str; >+ >+ if (where & SSL_ST_CONNECT) >+ str = US"SSL_connect"; >+ else if (where & SSL_ST_ACCEPT) >+ str = US"SSL_accept"; >+ else >+ str = US"SSL info (undefined)"; >+ >+ if (where & SSL_CB_LOOP) >+ debug_printf("%s: %s\n", str, SSL_state_string_long(s)); >+ else if (where & SSL_CB_ALERT) >+ debug_printf("SSL3 alert %s:%s:%s\n", >+ str = where & SSL_CB_READ ? US"read" : US"write", >+ SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); >+ else if (where & SSL_CB_EXIT) > { >- X509_NAME * sn = X509_get_subject_name(tmp_obj->data.x509); >- if (X509_NAME_oneline(sn, CS name, sizeof(name))) >- { >- name[sizeof(name)-1] = '\0'; >- debug_printf(" %s\n", name); >- } >+ if (ret == 0) >+ debug_printf("%s: failed in %s\n", str, SSL_state_string_long(s)); >+ else if (ret < 0) >+ debug_printf("%s: error in %s\n", str, SSL_state_string_long(s)); > } >+ else if (where & SSL_CB_HANDSHAKE_START) >+ debug_printf("%s: hshake start: %s\n", str, SSL_state_string_long(s)); >+ else if (where & SSL_CB_HANDSHAKE_DONE) >+ debug_printf("%s: hshake done: %s\n", str, SSL_state_string_long(s)); > } > } >+ >+#ifdef OPENSSL_HAVE_KEYLOG_CB >+static void >+keylog_callback(const SSL *ssl, const char *line) >+{ >+char * filename; >+FILE * fp; >+DEBUG(D_tls) debug_printf("%.200s\n", line); >+if (!(filename = getenv("SSLKEYLOGFILE"))) return; >+if (!(fp = fopen(filename, "a"))) return; >+fprintf(fp, "%s\n", line); >+fclose(fp); >+} > #endif >-*/ >+ >+ >+ > > > #ifndef DISABLE_EVENT >@@ -554,14 +1002,14 @@ > uschar * yield; > X509 * old_cert; > >-ev = tlsp == &tls_out ? client_static_cbinfo->event_action : event_action; >+ev = tlsp == &tls_out ? client_static_state->event_action : event_action; > if (ev) > { > DEBUG(D_tls) debug_printf("verify_event: %s %d\n", what, depth); > old_cert = tlsp->peercert; > tlsp->peercert = X509_dup(cert); > /* NB we do not bother setting peerdn */ >- if ((yield = event_raise(ev, US"tls:cert", string_sprintf("%d", depth)))) >+ if ((yield = event_raise(ev, US"tls:cert", string_sprintf("%d", depth), &errno))) > { > log_write(0, LOG_MAIN, "[%s] %s verify denied by event-action: " > "depth=%d cert=%s: %s", >@@ -661,15 +1109,15 @@ > { > DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n", depth, dn); > #ifndef DISABLE_OCSP >- if (tlsp == &tls_out && client_static_cbinfo->u_ocsp.client.verify_store) >+ if (tlsp == &tls_out && client_static_state->u_ocsp.client.verify_store) > { /* client, wanting stapling */ > /* Add the server cert's signing chain as the one > for the verification of the OCSP stapled information. */ > >- if (!X509_STORE_add_cert(client_static_cbinfo->u_ocsp.client.verify_store, >+ if (!X509_STORE_add_cert(client_static_state->u_ocsp.client.verify_store, > cert)) > ERR_clear_error(); >- sk_X509_push(client_static_cbinfo->verify_stack, cert); >+ sk_X509_push(client_static_state->verify_stack, cert); > } > #endif > #ifndef DISABLE_EVENT >@@ -682,7 +1130,7 @@ > const uschar * verify_cert_hostnames; > > if ( tlsp == &tls_out >- && ((verify_cert_hostnames = client_static_cbinfo->verify_cert_hostnames))) >+ && ((verify_cert_hostnames = client_static_state->verify_cert_hostnames))) > /* client, wanting hostname check */ > { > >@@ -802,15 +1250,15 @@ > { > tls_out.dane_verified = TRUE; > #ifndef DISABLE_OCSP >- if (client_static_cbinfo->u_ocsp.client.verify_store) >+ if (client_static_state->u_ocsp.client.verify_store) > { /* client, wanting stapling */ > /* Add the server cert's signing chain as the one > for the verification of the OCSP stapled information. */ > >- if (!X509_STORE_add_cert(client_static_cbinfo->u_ocsp.client.verify_store, >+ if (!X509_STORE_add_cert(client_static_state->u_ocsp.client.verify_store, > cert)) > ERR_clear_error(); >- sk_X509_push(client_static_cbinfo->verify_stack, cert); >+ sk_X509_push(client_static_state->verify_stack, cert); > } > #endif > } >@@ -828,409 +1276,6 @@ > #endif /*SUPPORT_DANE*/ > > >-/************************************************* >-* Information callback * >-*************************************************/ >- >-/* The SSL library functions call this from time to time to indicate what they >-are doing. We copy the string to the debugging output when TLS debugging has >-been requested. >- >-Arguments: >- s the SSL connection >- where >- ret >- >-Returns: nothing >-*/ >- >-static void >-info_callback(SSL *s, int where, int ret) >-{ >-DEBUG(D_tls) >- { >- const uschar * str; >- >- if (where & SSL_ST_CONNECT) >- str = US"SSL_connect"; >- else if (where & SSL_ST_ACCEPT) >- str = US"SSL_accept"; >- else >- str = US"SSL info (undefined)"; >- >- if (where & SSL_CB_LOOP) >- debug_printf("%s: %s\n", str, SSL_state_string_long(s)); >- else if (where & SSL_CB_ALERT) >- debug_printf("SSL3 alert %s:%s:%s\n", >- str = where & SSL_CB_READ ? US"read" : US"write", >- SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); >- else if (where & SSL_CB_EXIT) >- if (ret == 0) >- debug_printf("%s: failed in %s\n", str, SSL_state_string_long(s)); >- else if (ret < 0) >- debug_printf("%s: error in %s\n", str, SSL_state_string_long(s)); >- else if (where & SSL_CB_HANDSHAKE_START) >- debug_printf("%s: hshake start: %s\n", str, SSL_state_string_long(s)); >- else if (where & SSL_CB_HANDSHAKE_DONE) >- debug_printf("%s: hshake done: %s\n", str, SSL_state_string_long(s)); >- } >-} >- >-#ifdef OPENSSL_HAVE_KEYLOG_CB >-static void >-keylog_callback(const SSL *ssl, const char *line) >-{ >-char * filename; >-FILE * fp; >-DEBUG(D_tls) debug_printf("%.200s\n", line); >-if (!(filename = getenv("SSLKEYLOGFILE"))) return; >-if (!(fp = fopen(filename, "a"))) return; >-fprintf(fp, "%s\n", line); >-fclose(fp); >-} >-#endif >- >- >-#ifdef EXPERIMENTAL_TLS_RESUME >-/* Manage the keysets used for encrypting the session tickets, on the server. */ >- >-typedef struct { /* Session ticket encryption key */ >- uschar name[16]; >- >- const EVP_CIPHER * aes_cipher; >- uschar aes_key[32]; /* size needed depends on cipher. aes_128 implies 128/8 = 16? */ >- const EVP_MD * hmac_hash; >- uschar hmac_key[16]; >- time_t renew; >- time_t expire; >-} exim_stek; >- >-static exim_stek exim_tk; /* current key */ >-static exim_stek exim_tk_old; /* previous key */ >- >-static void >-tk_init(void) >-{ >-time_t t = time(NULL); >- >-if (exim_tk.name[0]) >- { >- if (exim_tk.renew >= t) return; >- exim_tk_old = exim_tk; >- } >- >-if (f.running_in_test_harness) ssl_session_timeout = 6; >- >-DEBUG(D_tls) debug_printf("OpenSSL: %s STEK\n", exim_tk.name[0] ? "rotating" : "creating"); >-if (RAND_bytes(exim_tk.aes_key, sizeof(exim_tk.aes_key)) <= 0) return; >-if (RAND_bytes(exim_tk.hmac_key, sizeof(exim_tk.hmac_key)) <= 0) return; >-if (RAND_bytes(exim_tk.name+1, sizeof(exim_tk.name)-1) <= 0) return; >- >-exim_tk.name[0] = 'E'; >-exim_tk.aes_cipher = EVP_aes_256_cbc(); >-exim_tk.hmac_hash = EVP_sha256(); >-exim_tk.expire = t + ssl_session_timeout; >-exim_tk.renew = t + ssl_session_timeout/2; >-} >- >-static exim_stek * >-tk_current(void) >-{ >-if (!exim_tk.name[0]) return NULL; >-return &exim_tk; >-} >- >-static exim_stek * >-tk_find(const uschar * name) >-{ >-return memcmp(name, exim_tk.name, sizeof(exim_tk.name)) == 0 ? &exim_tk >- : memcmp(name, exim_tk_old.name, sizeof(exim_tk_old.name)) == 0 ? &exim_tk_old >- : NULL; >-} >- >-/* Callback for session tickets, on server */ >-static int >-ticket_key_callback(SSL * ssl, uschar key_name[16], >- uschar * iv, EVP_CIPHER_CTX * ctx, HMAC_CTX * hctx, int enc) >-{ >-tls_support * tlsp = server_static_cbinfo->tlsp; >-exim_stek * key; >- >-if (enc) >- { >- DEBUG(D_tls) debug_printf("ticket_key_callback: create new session\n"); >- tlsp->resumption |= RESUME_CLIENT_REQUESTED; >- >- if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) <= 0) >- return -1; /* insufficient random */ >- >- if (!(key = tk_current())) /* current key doesn't exist or isn't valid */ >- return 0; /* key couldn't be created */ >- memcpy(key_name, key->name, 16); >- DEBUG(D_tls) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - time(NULL)); >- >- /*XXX will want these dependent on the ssl session strength */ >- HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), >- key->hmac_hash, NULL); >- EVP_EncryptInit_ex(ctx, key->aes_cipher, NULL, key->aes_key, iv); >- >- DEBUG(D_tls) debug_printf("ticket created\n"); >- return 1; >- } >-else >- { >- time_t now = time(NULL); >- >- DEBUG(D_tls) debug_printf("ticket_key_callback: retrieve session\n"); >- tlsp->resumption |= RESUME_CLIENT_SUGGESTED; >- >- if (!(key = tk_find(key_name)) || key->expire < now) >- { >- DEBUG(D_tls) >- { >- debug_printf("ticket not usable (%s)\n", key ? "expired" : "not found"); >- if (key) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - now); >- } >- return 0; >- } >- >- HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), >- key->hmac_hash, NULL); >- EVP_DecryptInit_ex(ctx, key->aes_cipher, NULL, key->aes_key, iv); >- >- DEBUG(D_tls) debug_printf("ticket usable, STEK expire " TIME_T_FMT "\n", key->expire - now); >- >- /* The ticket lifetime and renewal are the same as the STEK lifetime and >- renewal, which is overenthusiastic. A factor of, say, 3x longer STEK would >- be better. To do that we'd have to encode ticket lifetime in the name as >- we don't yet see the restored session. Could check posthandshake for TLS1.3 >- and trigger a new ticket then, but cannot do that for TLS1.2 */ >- return key->renew < now ? 2 : 1; >- } >-} >-#endif >- >- >- >-/************************************************* >-* Initialize for DH * >-*************************************************/ >- >-/* If dhparam is set, expand it, and load up the parameters for DH encryption. >- >-Arguments: >- sctx The current SSL CTX (inbound or outbound) >- dhparam DH parameter file or fixed parameter identity string >- host connected host, if client; NULL if server >- errstr error string pointer >- >-Returns: TRUE if OK (nothing to set up, or setup worked) >-*/ >- >-static BOOL >-init_dh(SSL_CTX *sctx, uschar *dhparam, const host_item *host, uschar ** errstr) >-{ >-BIO *bio; >-DH *dh; >-uschar *dhexpanded; >-const char *pem; >-int dh_bitsize; >- >-if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded, errstr)) >- return FALSE; >- >-if (!dhexpanded || !*dhexpanded) >- bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1); >-else if (dhexpanded[0] == '/') >- { >- if (!(bio = BIO_new_file(CS dhexpanded, "r"))) >- { >- tls_error(string_sprintf("could not read dhparams file %s", dhexpanded), >- host, US strerror(errno), errstr); >- return FALSE; >- } >- } >-else >- { >- if (Ustrcmp(dhexpanded, "none") == 0) >- { >- DEBUG(D_tls) debug_printf("Requested no DH parameters.\n"); >- return TRUE; >- } >- >- if (!(pem = std_dh_prime_named(dhexpanded))) >- { >- tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded), >- host, US strerror(errno), errstr); >- return FALSE; >- } >- bio = BIO_new_mem_buf(CS pem, -1); >- } >- >-if (!(dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL))) >- { >- BIO_free(bio); >- tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded), >- host, NULL, errstr); >- return FALSE; >- } >- >-/* note: our default limit of 2236 is not a multiple of 8; the limit comes from >- * an NSS limit, and the GnuTLS APIs handle bit-sizes fine, so we went with >- * 2236. But older OpenSSL can only report in bytes (octets), not bits. >- * If someone wants to dance at the edge, then they can raise the limit or use >- * current libraries. */ >-#ifdef EXIM_HAVE_OPENSSL_DH_BITS >-/* Added in commit 26c79d5641d; `git describe --contains` says OpenSSL_1_1_0-pre1~1022 >- * This predates OpenSSL_1_1_0 (before a, b, ...) so is in all 1.1.0 */ >-dh_bitsize = DH_bits(dh); >-#else >-dh_bitsize = 8 * DH_size(dh); >-#endif >- >-/* Even if it is larger, we silently return success rather than cause things >- * to fail out, so that a too-large DH will not knock out all TLS; it's a >- * debatable choice. */ >-if (dh_bitsize > tls_dh_max_bits) >- { >- DEBUG(D_tls) >- debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d\n", >- dh_bitsize, tls_dh_max_bits); >- } >-else >- { >- SSL_CTX_set_tmp_dh(sctx, dh); >- DEBUG(D_tls) >- debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n", >- dhexpanded ? dhexpanded : US"default", dh_bitsize); >- } >- >-DH_free(dh); >-BIO_free(bio); >- >-return TRUE; >-} >- >- >- >- >-/************************************************* >-* Initialize for ECDH * >-*************************************************/ >- >-/* Load parameters for ECDH encryption. >- >-For now, we stick to NIST P-256 because: it's simple and easy to configure; >-it avoids any patent issues that might bite redistributors; despite events in >-the news and concerns over curve choices, we're not cryptographers, we're not >-pretending to be, and this is "good enough" to be better than no support, >-protecting against most adversaries. Given another year or two, there might >-be sufficient clarity about a "right" way forward to let us make an informed >-decision, instead of a knee-jerk reaction. >- >-Longer-term, we should look at supporting both various named curves and >-external files generated with "openssl ecparam", much as we do for init_dh(). >-We should also support "none" as a value, to explicitly avoid initialisation. >- >-Patches welcome. >- >-Arguments: >- sctx The current SSL CTX (inbound or outbound) >- host connected host, if client; NULL if server >- errstr error string pointer >- >-Returns: TRUE if OK (nothing to set up, or setup worked) >-*/ >- >-static BOOL >-init_ecdh(SSL_CTX * sctx, host_item * host, uschar ** errstr) >-{ >-#ifdef OPENSSL_NO_ECDH >-return TRUE; >-#else >- >-EC_KEY * ecdh; >-uschar * exp_curve; >-int nid; >-BOOL rv; >- >-if (host) /* No ECDH setup for clients, only for servers */ >- return TRUE; >- >-# ifndef EXIM_HAVE_ECDH >-DEBUG(D_tls) >- debug_printf("No OpenSSL API to define ECDH parameters, skipping\n"); >-return TRUE; >-# else >- >-if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve, errstr)) >- return FALSE; >-if (!exp_curve || !*exp_curve) >- return TRUE; >- >-/* "auto" needs to be handled carefully. >- * OpenSSL < 1.0.2: we do not select anything, but fallback to prime256v1 >- * OpenSSL < 1.1.0: we have to call SSL_CTX_set_ecdh_auto >- * (openssl/ssl.h defines SSL_CTRL_SET_ECDH_AUTO) >- * OpenSSL >= 1.1.0: we do not set anything, the libray does autoselection >- * https://github.com/openssl/openssl/commit/fe6ef2472db933f01b59cad82aa925736935984b >- */ >-if (Ustrcmp(exp_curve, "auto") == 0) >- { >-#if OPENSSL_VERSION_NUMBER < 0x10002000L >- DEBUG(D_tls) debug_printf( >- "ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n"); >- exp_curve = US"prime256v1"; >-#else >-# if defined SSL_CTRL_SET_ECDH_AUTO >- DEBUG(D_tls) debug_printf( >- "ECDH OpenSSL 1.0.2+ temp key parameter settings: autoselection\n"); >- SSL_CTX_set_ecdh_auto(sctx, 1); >- return TRUE; >-# else >- DEBUG(D_tls) debug_printf( >- "ECDH OpenSSL 1.1.0+ temp key parameter settings: default selection\n"); >- return TRUE; >-# endif >-#endif >- } >- >-DEBUG(D_tls) debug_printf("ECDH: curve '%s'\n", exp_curve); >-if ( (nid = OBJ_sn2nid (CCS exp_curve)) == NID_undef >-# ifdef EXIM_HAVE_OPENSSL_EC_NIST2NID >- && (nid = EC_curve_nist2nid(CCS exp_curve)) == NID_undef >-# endif >- ) >- { >- tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'", exp_curve), >- host, NULL, errstr); >- return FALSE; >- } >- >-if (!(ecdh = EC_KEY_new_by_curve_name(nid))) >- { >- tls_error(US"Unable to create ec curve", host, NULL, errstr); >- return FALSE; >- } >- >-/* The "tmp" in the name here refers to setting a temporary key >-not to the stability of the interface. */ >- >-if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0)) >- tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL, errstr); >-else >- DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve); >- >-EC_KEY_free(ecdh); >-return !rv; >- >-# endif /*EXIM_HAVE_ECDH*/ >-#endif /*OPENSSL_NO_ECDH*/ >-} >- >- >- >- > #ifndef DISABLE_OCSP > /************************************************* > * Load OCSP information into state * >@@ -1242,16 +1287,14 @@ > ASSUMES: single response, for single cert. > > Arguments: >- sctx the SSL_CTX* to update >- cbinfo various parts of session state >+ state various parts of session state > filename the filename putatively holding an OCSP response > is_pem file is PEM format; otherwise is DER >- > */ > > static void >-ocsp_load_response(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, >- const uschar * filename, BOOL is_pem) >+ocsp_load_response(exim_openssl_state_st * state, const uschar * filename, >+ BOOL is_pem) > { > BIO * bio; > OCSP_RESPONSE * resp; >@@ -1265,10 +1308,14 @@ > DEBUG(D_tls) > debug_printf("tls_ocsp_file (%s) '%s'\n", is_pem ? "PEM" : "DER", filename); > >+if (!filename || !*filename) return; >+ >+ERR_clear_error(); > if (!(bio = BIO_new_file(CS filename, "rb"))) > { >- DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n", >- filename); >+ log_write(0, LOG_MAIN|LOG_PANIC, >+ "Failed to open OCSP response file \"%s\": %.100s", >+ filename, ERR_reason_error_string(ERR_get_error())); > return; > } > >@@ -1279,11 +1326,10 @@ > long len; > if (!PEM_read_bio(bio, &dummy, &dummy, &data, &len)) > { >- DEBUG(D_tls) debug_printf("Failed to read PEM file \"%s\"\n", >- filename); >+ log_write(0, LOG_MAIN|LOG_PANIC, "Failed to read PEM file \"%s\": %.100s", >+ filename, ERR_reason_error_string(ERR_get_error())); > return; > } >-debug_printf("read pem file\n"); > freep = data; > resp = d2i_OCSP_RESPONSE(NULL, CUSS &data, len); > OPENSSL_free(freep); >@@ -1294,7 +1340,8 @@ > > if (!resp) > { >- DEBUG(D_tls) debug_printf("Error reading OCSP response.\n"); >+ log_write(0, LOG_MAIN|LOG_PANIC, "Error reading OCSP response from \"%s\": %s", >+ filename, ERR_reason_error_string(ERR_get_error())); > return; > } > >@@ -1320,7 +1367,7 @@ > goto bad; > } > >-sk = cbinfo->verify_stack; >+sk = state->verify_stack; > verify_flags = OCSP_NOVERIFY; /* check sigs, but not purpose */ > > /* May need to expose ability to adjust those flags? >@@ -1396,10 +1443,10 @@ > supply_response: > /* Add the resp to the list used by tls_server_stapling_cb() */ > { >- ocsp_resplist ** op = &cbinfo->u_ocsp.server.olist, * oentry; >+ ocsp_resplist ** op = &state->u_ocsp.server.olist, * oentry; > while (oentry = *op) > op = &oentry->next; >- *op = oentry = store_get(sizeof(ocsp_resplist), FALSE); >+ *op = oentry = store_get(sizeof(ocsp_resplist), GET_UNTAINTED); > oentry->next = NULL; > oentry->resp = resp; > } >@@ -1421,7 +1468,7 @@ > > > static void >-ocsp_free_response_list(tls_ext_ctx_cb * cbinfo) >+ocsp_free_response_list(exim_openssl_state_st * cbinfo) > { > for (ocsp_resplist * olist = cbinfo->u_ocsp.server.olist; olist; > olist = olist->next) >@@ -1433,74 +1480,9 @@ > > > >-/* Create and install a selfsigned certificate, for use in server mode */ >- >-static int >-tls_install_selfsign(SSL_CTX * sctx, uschar ** errstr) >-{ >-X509 * x509 = NULL; >-EVP_PKEY * pkey; >-RSA * rsa; >-X509_NAME * name; >-uschar * where; >- >-where = US"allocating pkey"; >-if (!(pkey = EVP_PKEY_new())) >- goto err; >- >-where = US"allocating cert"; >-if (!(x509 = X509_new())) >- goto err; >- >-where = US"generating pkey"; >-if (!(rsa = rsa_callback(NULL, 0, 2048))) >- goto err; >- >-where = US"assigning pkey"; >-if (!EVP_PKEY_assign_RSA(pkey, rsa)) >- goto err; >- >-X509_set_version(x509, 2); /* N+1 - version 3 */ >-ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); >-X509_gmtime_adj(X509_get_notBefore(x509), 0); >-X509_gmtime_adj(X509_get_notAfter(x509), (long)60 * 60); /* 1 hour */ >-X509_set_pubkey(x509, pkey); >- >-name = X509_get_subject_name(x509); >-X509_NAME_add_entry_by_txt(name, "C", >- MBSTRING_ASC, CUS "UK", -1, -1, 0); >-X509_NAME_add_entry_by_txt(name, "O", >- MBSTRING_ASC, CUS "Exim Developers", -1, -1, 0); >-X509_NAME_add_entry_by_txt(name, "CN", >- MBSTRING_ASC, CUS smtp_active_hostname, -1, -1, 0); >-X509_set_issuer_name(x509, name); >- >-where = US"signing cert"; >-if (!X509_sign(x509, pkey, EVP_md5())) >- goto err; >- >-where = US"installing selfsign cert"; >-if (!SSL_CTX_use_certificate(sctx, x509)) >- goto err; >- >-where = US"installing selfsign key"; >-if (!SSL_CTX_use_PrivateKey(sctx, pkey)) >- goto err; >- >-return OK; >- >-err: >- (void) tls_error(where, NULL, NULL, errstr); >- if (x509) X509_free(x509); >- if (pkey) EVP_PKEY_free(pkey); >- return DEFER; >-} >- >- >- > > static int >-tls_add_certfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file, >+tls_add_certfile(SSL_CTX * sctx, exim_openssl_state_st * cbinfo, uschar * file, > uschar ** errstr) > { > DEBUG(D_tls) debug_printf("tls_certificate file '%s'\n", file); >@@ -1512,7 +1494,7 @@ > } > > static int >-tls_add_pkeyfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file, >+tls_add_pkeyfile(SSL_CTX * sctx, exim_openssl_state_st * cbinfo, uschar * file, > uschar ** errstr) > { > DEBUG(D_tls) debug_printf("tls_privatekey file '%s'\n", file); >@@ -1523,9 +1505,7 @@ > } > > >-/************************************************* >-* Expand key and cert file specs * >-*************************************************/ >+ > > /* Called once during tls_init and possibly again during TLS setup, for a > new context, if Server Name Indication was used and tls_sni was seen in >@@ -1533,21 +1513,21 @@ > > Arguments: > sctx the SSL_CTX* to update >- cbinfo various parts of session state >+ state various parts of session state > errstr error string pointer > > Returns: OK/DEFER/FAIL > */ > > static int >-tls_expand_session_files(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, >+tls_expand_session_files(SSL_CTX * sctx, exim_openssl_state_st * state, > uschar ** errstr) > { > uschar * expanded; > >-if (!cbinfo->certificate) >+if (!state->certificate) > { >- if (!cbinfo->is_server) /* client */ >+ if (!state->is_server) /* client */ > return OK; > /* server */ > if (tls_install_selfsign(sctx, errstr) != OK) >@@ -1558,23 +1538,23 @@ > int err; > > if ( !reexpand_tls_files_for_sni >- && ( Ustrstr(cbinfo->certificate, US"tls_sni") >- || Ustrstr(cbinfo->certificate, US"tls_in_sni") >- || Ustrstr(cbinfo->certificate, US"tls_out_sni") >+ && ( Ustrstr(state->certificate, US"tls_sni") >+ || Ustrstr(state->certificate, US"tls_in_sni") >+ || Ustrstr(state->certificate, US"tls_out_sni") > ) ) > reexpand_tls_files_for_sni = TRUE; > >- if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded, errstr)) >+ if (!expand_check(state->certificate, US"tls_certificate", &expanded, errstr)) > return DEFER; > > if (expanded) >- if (cbinfo->is_server) >+ if (state->is_server) > { > const uschar * file_list = expanded; > int sep = 0; > uschar * file; > #ifndef DISABLE_OCSP >- const uschar * olist = cbinfo->u_ocsp.server.file; >+ const uschar * olist = state->u_ocsp.server.file; > int osep = 0; > uschar * ofile; > BOOL fmt_pem = FALSE; >@@ -1585,22 +1565,22 @@ > if (olist && !*olist) > olist = NULL; > >- if ( cbinfo->u_ocsp.server.file_expanded && olist >- && (Ustrcmp(olist, cbinfo->u_ocsp.server.file_expanded) == 0)) >+ if ( state->u_ocsp.server.file_expanded && olist >+ && (Ustrcmp(olist, state->u_ocsp.server.file_expanded) == 0)) > { > DEBUG(D_tls) debug_printf(" - value unchanged, using existing values\n"); > olist = NULL; > } > else > { >- ocsp_free_response_list(cbinfo); >- cbinfo->u_ocsp.server.file_expanded = olist; >+ ocsp_free_response_list(state); >+ state->u_ocsp.server.file_expanded = olist; > } > #endif > > while (file = string_nextinlist(&file_list, &sep, NULL, 0)) > { >- if ((err = tls_add_certfile(sctx, cbinfo, file, errstr))) >+ if ((err = tls_add_certfile(sctx, state, file, errstr))) > return err; > > #ifndef DISABLE_OCSP >@@ -1617,7 +1597,7 @@ > fmt_pem = FALSE; > ofile += 4; > } >- ocsp_load_response(sctx, cbinfo, ofile, fmt_pem); >+ ocsp_load_response(state, ofile, fmt_pem); > } > else > DEBUG(D_tls) debug_printf("ran out of ocsp file list\n"); >@@ -1625,11 +1605,11 @@ > } > } > else /* would there ever be a need for multiple client certs? */ >- if ((err = tls_add_certfile(sctx, cbinfo, expanded, errstr))) >+ if ((err = tls_add_certfile(sctx, state, expanded, errstr))) > return err; > >- if ( cbinfo->privatekey >- && !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded, errstr)) >+ if ( state->privatekey >+ && !expand_check(state->privatekey, US"tls_privatekey", &expanded, errstr)) > return DEFER; > > /* If expansion was forced to fail, key_expanded will be NULL. If the result >@@ -1637,18 +1617,18 @@ > key is in the same file as the certificate. */ > > if (expanded && *expanded) >- if (cbinfo->is_server) >+ if (state->is_server) > { > const uschar * file_list = expanded; > int sep = 0; > uschar * file; > > while (file = string_nextinlist(&file_list, &sep, NULL, 0)) >- if ((err = tls_add_pkeyfile(sctx, cbinfo, file, errstr))) >+ if ((err = tls_add_pkeyfile(sctx, state, file, errstr))) > return err; > } > else /* would there ever be a need for multiple client certs? */ >- if ((err = tls_add_pkeyfile(sctx, cbinfo, expanded, errstr))) >+ if ((err = tls_add_pkeyfile(sctx, state, expanded, errstr))) > return err; > } > >@@ -1658,6 +1638,504 @@ > > > >+/************************************************** >+* One-time init credentials for server and client * >+**************************************************/ >+ >+static void >+normalise_ciphers(uschar ** ciphers, const uschar * pre_expansion_ciphers) >+{ >+uschar * s = *ciphers; >+ >+if (!s || !Ustrchr(s, '_')) return; /* no change needed */ >+ >+if (s == pre_expansion_ciphers) >+ s = string_copy(s); /* get writable copy */ >+ >+for (uschar * t = s; *t; t++) if (*t == '_') *t = '-'; >+*ciphers = s; >+} >+ >+static int >+server_load_ciphers(SSL_CTX * ctx, exim_openssl_state_st * state, >+ uschar * ciphers, uschar ** errstr) >+{ >+DEBUG(D_tls) debug_printf("required ciphers: %s\n", ciphers); >+if (!SSL_CTX_set_cipher_list(ctx, CS ciphers)) >+ return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL, errstr); >+state->server_cipher_list = ciphers; >+return OK; >+} >+ >+ >+ >+static int >+lib_ctx_new(SSL_CTX ** ctxp, host_item * host, uschar ** errstr) >+{ >+SSL_CTX * ctx; >+#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD >+if (!(ctx = SSL_CTX_new(host ? TLS_client_method() : TLS_server_method()))) >+#else >+if (!(ctx = SSL_CTX_new(host ? SSLv23_client_method() : SSLv23_server_method()))) >+#endif >+ return tls_error(US"SSL_CTX_new", host, NULL, errstr); >+ >+/* Set up the information callback, which outputs if debugging is at a suitable >+level. */ >+ >+DEBUG(D_tls) >+ { >+ SSL_CTX_set_info_callback(ctx, (void (*)())info_callback); >+#if defined(EXIM_HAVE_OPESSL_TRACE) && !defined(OPENSSL_NO_SSL_TRACE) >+ /* this needs a debug build of OpenSSL */ >+ SSL_CTX_set_msg_callback(ctx, (void (*)())SSL_trace); >+#endif >+#ifdef OPENSSL_HAVE_KEYLOG_CB >+ SSL_CTX_set_keylog_callback(ctx, (void (*)())keylog_callback); >+#endif >+ } >+ >+/* Automatically re-try reads/writes after renegotiation. */ >+(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); >+*ctxp = ctx; >+return OK; >+} >+ >+ >+static unsigned >+tls_server_creds_init(void) >+{ >+SSL_CTX * ctx; >+uschar * dummy_errstr; >+unsigned lifetime = 0; >+ >+tls_openssl_init(); >+ >+state_server.lib_state = null_tls_preload; >+ >+if (lib_ctx_new(&ctx, NULL, &dummy_errstr) != OK) >+ return 0; >+state_server.lib_state.lib_ctx = ctx; >+ >+/* Preload DH params and EC curve */ >+ >+if (opt_unset_or_noexpand(tls_dhparam)) >+ { >+ DEBUG(D_tls) debug_printf("TLS: preloading DH params for server\n"); >+ if (init_dh(ctx, tls_dhparam, &dummy_errstr)) >+ state_server.lib_state.dh = TRUE; >+ } >+else >+ DEBUG(D_tls) debug_printf("TLS: not preloading DH params for server\n"); >+if (opt_unset_or_noexpand(tls_eccurve)) >+ { >+ DEBUG(D_tls) debug_printf("TLS: preloading ECDH curve for server\n"); >+ if (init_ecdh(ctx, &dummy_errstr)) >+ state_server.lib_state.ecdh = TRUE; >+ } >+else >+ DEBUG(D_tls) debug_printf("TLS: not preloading ECDH curve for server\n"); >+ >+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) >+/* If we can, preload the server-side cert, key and ocsp */ >+ >+if ( opt_set_and_noexpand(tls_certificate) >+# ifndef DISABLE_OCSP >+ && opt_unset_or_noexpand(tls_ocsp_file) >+#endif >+ && opt_unset_or_noexpand(tls_privatekey)) >+ { >+ /* Set watches on the filenames. The implementation does de-duplication >+ so we can just blindly do them all. */ >+ >+ if ( tls_set_watch(tls_certificate, TRUE) >+# ifndef DISABLE_OCSP >+ && tls_set_watch(tls_ocsp_file, TRUE) >+#endif >+ && tls_set_watch(tls_privatekey, TRUE)) >+ { >+ state_server.certificate = tls_certificate; >+ state_server.privatekey = tls_privatekey; >+#ifndef DISABLE_OCSP >+ state_server.u_ocsp.server.file = tls_ocsp_file; >+#endif >+ >+ DEBUG(D_tls) debug_printf("TLS: preloading server certs\n"); >+ if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK) >+ state_server.lib_state.conn_certs = TRUE; >+ } >+ } >+else if ( !tls_certificate && !tls_privatekey >+# ifndef DISABLE_OCSP >+ && !tls_ocsp_file >+#endif >+ ) >+ { /* Generate & preload a selfsigned cert. No files to watch. */ >+ if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK) >+ { >+ state_server.lib_state.conn_certs = TRUE; >+ lifetime = f.running_in_test_harness ? 2 : 60 * 60; /* 1 hour */ >+ } >+ } >+else >+ DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n"); >+ >+ >+/* If we can, preload the Authorities for checking client certs against. >+Actual choice to do verify is made (tls_{,try_}verify_hosts) >+at TLS conn startup */ >+ >+if ( opt_set_and_noexpand(tls_verify_certificates) >+ && opt_unset_or_noexpand(tls_crl)) >+ { >+ /* Watch the default dir also as they are always included */ >+ >+ if ( tls_set_watch(CUS X509_get_default_cert_file(), FALSE) >+ && tls_set_watch(tls_verify_certificates, FALSE) >+ && tls_set_watch(tls_crl, FALSE)) >+ { >+ DEBUG(D_tls) debug_printf("TLS: preloading CA bundle for server\n"); >+ >+ if (setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, &dummy_errstr) >+ == OK) >+ state_server.lib_state.cabundle = TRUE; >+ } >+ } >+else >+ DEBUG(D_tls) debug_printf("TLS: not preloading CA bundle for server\n"); >+#endif /* EXIM_HAVE_INOTIFY */ >+ >+ >+/* If we can, preload the ciphers control string */ >+ >+if (opt_set_and_noexpand(tls_require_ciphers)) >+ { >+ DEBUG(D_tls) debug_printf("TLS: preloading cipher list for server\n"); >+ normalise_ciphers(&tls_require_ciphers, tls_require_ciphers); >+ if (server_load_ciphers(ctx, &state_server, tls_require_ciphers, >+ &dummy_errstr) == OK) >+ state_server.lib_state.pri_string = TRUE; >+ } >+else >+ DEBUG(D_tls) debug_printf("TLS: not preloading cipher list for server\n"); >+return lifetime; >+} >+ >+ >+ >+ >+/* Preload whatever creds are static, onto a transport. The client can then >+just copy the pointer as it starts up. >+Called from the daemon after a cache-invalidate with watch set; called from >+a queue-run startup with watch clear. */ >+ >+static void >+tls_client_creds_init(transport_instance * t, BOOL watch) >+{ >+smtp_transport_options_block * ob = t->options_block; >+exim_openssl_state_st tpt_dummy_state; >+host_item * dummy_host = (host_item *)1; >+uschar * dummy_errstr; >+SSL_CTX * ctx; >+ >+tls_openssl_init(); >+ >+ob->tls_preload = null_tls_preload; >+if (lib_ctx_new(&ctx, dummy_host, &dummy_errstr) != OK) >+ return; >+ob->tls_preload.lib_ctx = ctx; >+ >+tpt_dummy_state.lib_state = ob->tls_preload; >+ >+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) >+if ( opt_set_and_noexpand(ob->tls_certificate) >+ && opt_unset_or_noexpand(ob->tls_privatekey)) >+ { >+ if ( !watch >+ || ( tls_set_watch(ob->tls_certificate, FALSE) >+ && tls_set_watch(ob->tls_privatekey, FALSE) >+ ) ) >+ { >+ uschar * pkey = ob->tls_privatekey; >+ >+ DEBUG(D_tls) >+ debug_printf("TLS: preloading client certs for transport '%s'\n",t->name); >+ >+ if ( tls_add_certfile(ctx, &tpt_dummy_state, ob->tls_certificate, >+ &dummy_errstr) == 0 >+ && tls_add_pkeyfile(ctx, &tpt_dummy_state, >+ pkey ? pkey : ob->tls_certificate, >+ &dummy_errstr) == 0 >+ ) >+ ob->tls_preload.conn_certs = TRUE; >+ } >+ } >+else >+ DEBUG(D_tls) >+ debug_printf("TLS: not preloading client certs, for transport '%s'\n", t->name); >+ >+ >+if ( opt_set_and_noexpand(ob->tls_verify_certificates) >+ && opt_unset_or_noexpand(ob->tls_crl)) >+ { >+ if ( !watch >+ || tls_set_watch(CUS X509_get_default_cert_file(), FALSE) >+ && tls_set_watch(ob->tls_verify_certificates, FALSE) >+ && tls_set_watch(ob->tls_crl, FALSE) >+ ) >+ { >+ DEBUG(D_tls) >+ debug_printf("TLS: preloading CA bundle for transport '%s'\n", t->name); >+ >+ if (setup_certs(ctx, ob->tls_verify_certificates, >+ ob->tls_crl, dummy_host, &dummy_errstr) == OK) >+ ob->tls_preload.cabundle = TRUE; >+ } >+ } >+else >+ DEBUG(D_tls) >+ debug_printf("TLS: not preloading CA bundle, for transport '%s'\n", t->name); >+ >+#endif /*EXIM_HAVE_INOTIFY*/ >+} >+ >+ >+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) >+/* Invalidate the creds cached, by dropping the current ones. >+Call when we notice one of the source files has changed. */ >+ >+static void >+tls_server_creds_invalidate(void) >+{ >+SSL_CTX_free(state_server.lib_state.lib_ctx); >+state_server.lib_state = null_tls_preload; >+} >+ >+ >+static void >+tls_client_creds_invalidate(transport_instance * t) >+{ >+smtp_transport_options_block * ob = t->options_block; >+SSL_CTX_free(ob->tls_preload.lib_ctx); >+ob->tls_preload = null_tls_preload; >+} >+ >+#else >+ >+static void >+tls_server_creds_invalidate(void) >+{ return; } >+ >+static void >+tls_client_creds_invalidate(transport_instance * t) >+{ return; } >+ >+#endif /*EXIM_HAVE_INOTIFY*/ >+ >+ >+/* Extreme debug >+#ifndef DISABLE_OCSP >+void >+x509_store_dump_cert_s_names(X509_STORE * store) >+{ >+STACK_OF(X509_OBJECT) * roots= store->objs; >+static uschar name[256]; >+ >+for (int i= 0; i < sk_X509_OBJECT_num(roots); i++) >+ { >+ X509_OBJECT * tmp_obj= sk_X509_OBJECT_value(roots, i); >+ if(tmp_obj->type == X509_LU_X509) >+ { >+ X509_NAME * sn = X509_get_subject_name(tmp_obj->data.x509); >+ if (X509_NAME_oneline(sn, CS name, sizeof(name))) >+ { >+ name[sizeof(name)-1] = '\0'; >+ debug_printf(" %s\n", name); >+ } >+ } >+ } >+} >+#endif >+*/ >+ >+ >+#ifndef DISABLE_TLS_RESUME >+/* Manage the keysets used for encrypting the session tickets, on the server. */ >+ >+typedef struct { /* Session ticket encryption key */ >+ uschar name[16]; >+ >+ const EVP_CIPHER * aes_cipher; >+ uschar aes_key[32]; /* size needed depends on cipher. aes_128 implies 128/8 = 16? */ >+# if OPENSSL_VERSION_NUMBER < 0x30000000L >+ const EVP_MD * hmac_hash; >+# else >+ const uschar * hmac_hashname; >+# endif >+ uschar hmac_key[16]; >+ time_t renew; >+ time_t expire; >+} exim_stek; >+ >+static exim_stek exim_tk; /* current key */ >+static exim_stek exim_tk_old; /* previous key */ >+ >+static void >+tk_init(void) >+{ >+time_t t = time(NULL); >+ >+if (exim_tk.name[0]) >+ { >+ if (exim_tk.renew >= t) return; >+ exim_tk_old = exim_tk; >+ } >+ >+if (f.running_in_test_harness) ssl_session_timeout = 6; >+ >+DEBUG(D_tls) debug_printf("OpenSSL: %s STEK\n", exim_tk.name[0] ? "rotating" : "creating"); >+if (RAND_bytes(exim_tk.aes_key, sizeof(exim_tk.aes_key)) <= 0) return; >+if (RAND_bytes(exim_tk.hmac_key, sizeof(exim_tk.hmac_key)) <= 0) return; >+if (RAND_bytes(exim_tk.name+1, sizeof(exim_tk.name)-1) <= 0) return; >+ >+exim_tk.name[0] = 'E'; >+exim_tk.aes_cipher = EVP_aes_256_cbc(); >+# if OPENSSL_VERSION_NUMBER < 0x30000000L >+exim_tk.hmac_hash = EVP_sha256(); >+# else >+exim_tk.hmac_hashname = US "sha256"; >+# endif >+exim_tk.expire = t + ssl_session_timeout; >+exim_tk.renew = t + ssl_session_timeout/2; >+} >+ >+static exim_stek * >+tk_current(void) >+{ >+if (!exim_tk.name[0]) return NULL; >+return &exim_tk; >+} >+ >+static exim_stek * >+tk_find(const uschar * name) >+{ >+return memcmp(name, exim_tk.name, sizeof(exim_tk.name)) == 0 ? &exim_tk >+ : memcmp(name, exim_tk_old.name, sizeof(exim_tk_old.name)) == 0 ? &exim_tk_old >+ : NULL; >+} >+ >+ >+static int >+tk_hmac_init( >+# if OPENSSL_VERSION_NUMBER < 0x30000000L >+ HMAC_CTX * hctx, >+#else >+ EVP_MAC_CTX * hctx, >+#endif >+ exim_stek * key >+ ) >+{ >+/*XXX will want these dependent on the ssl session strength */ >+# if OPENSSL_VERSION_NUMBER < 0x30000000L >+ HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), >+ key->hmac_hash, NULL); >+#else >+ { >+ OSSL_PARAM params[3]; >+ uschar * hk = string_copy(key->hmac_hashname); /* need nonconst */ >+ params[0] = OSSL_PARAM_construct_octet_string("key", key->hmac_key, sizeof(key->hmac_key)); >+ params[1] = OSSL_PARAM_construct_utf8_string("digest", CS hk, 0); >+ params[2] = OSSL_PARAM_construct_end(); >+ if (EVP_MAC_CTX_set_params(hctx, params) == 0) >+ { >+ DEBUG(D_tls) debug_printf("EVP_MAC_CTX_set_params: %s\n", >+ ERR_reason_error_string(ERR_get_error())); >+ return 0; /* error in mac initialisation */ >+ } >+} >+#endif >+return 1; >+} >+ >+/* Callback for session tickets, on server */ >+static int >+ticket_key_callback(SSL * ssl, uschar key_name[16], >+ uschar * iv, EVP_CIPHER_CTX * c_ctx, >+# if OPENSSL_VERSION_NUMBER < 0x30000000L >+ HMAC_CTX * hctx, >+#else >+ EVP_MAC_CTX * hctx, >+#endif >+ int enc) >+{ >+tls_support * tlsp = state_server.tlsp; >+exim_stek * key; >+ >+if (enc) >+ { >+ DEBUG(D_tls) debug_printf("ticket_key_callback: create new session\n"); >+ tlsp->resumption |= RESUME_CLIENT_REQUESTED; >+ >+ if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) <= 0) >+ return -1; /* insufficient random */ >+ >+ if (!(key = tk_current())) /* current key doesn't exist or isn't valid */ >+ return 0; /* key couldn't be created */ >+ memcpy(key_name, key->name, 16); >+ DEBUG(D_tls) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - time(NULL)); >+ >+ if (tk_hmac_init(hctx, key) == 0) return 0; >+ EVP_EncryptInit_ex(c_ctx, key->aes_cipher, NULL, key->aes_key, iv); >+ >+ DEBUG(D_tls) debug_printf("ticket created\n"); >+ return 1; >+ } >+else >+ { >+ time_t now = time(NULL); >+ >+ DEBUG(D_tls) debug_printf("ticket_key_callback: retrieve session\n"); >+ tlsp->resumption |= RESUME_CLIENT_SUGGESTED; >+ >+ if (!(key = tk_find(key_name)) || key->expire < now) >+ { >+ DEBUG(D_tls) >+ { >+ debug_printf("ticket not usable (%s)\n", key ? "expired" : "not found"); >+ if (key) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - now); >+ } >+ return 0; >+ } >+ >+ if (tk_hmac_init(hctx, key) == 0) return 0; >+ EVP_DecryptInit_ex(c_ctx, key->aes_cipher, NULL, key->aes_key, iv); >+ >+ DEBUG(D_tls) debug_printf("ticket usable, STEK expire " TIME_T_FMT "\n", key->expire - now); >+ >+ /* The ticket lifetime and renewal are the same as the STEK lifetime and >+ renewal, which is overenthusiastic. A factor of, say, 3x longer STEK would >+ be better. To do that we'd have to encode ticket lifetime in the name as >+ we don't yet see the restored session. Could check posthandshake for TLS1.3 >+ and trigger a new ticket then, but cannot do that for TLS1.2 */ >+ return key->renew < now ? 2 : 1; >+ } >+} >+#endif /* !DISABLE_TLS_RESUME */ >+ >+ >+ >+static void >+setup_cert_verify(SSL_CTX * ctx, BOOL optional, >+ int (*cert_vfy_cb)(int, X509_STORE_CTX *)) >+{ >+/* If verification is optional, don't fail if no certificate */ >+ >+SSL_CTX_set_verify(ctx, >+ SSL_VERIFY_PEER | (optional ? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT), >+ cert_vfy_cb); >+} >+ >+ > /************************************************* > * Callback to handle SNI * > *************************************************/ >@@ -1683,7 +2161,7 @@ > tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg) > { > const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); >-tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg; >+exim_openssl_state_st *state = (exim_openssl_state_st *) arg; > int rc; > int old_pool = store_pool; > uschar * dummy_errstr; >@@ -1696,7 +2174,7 @@ > > /* Make the extension value available for expansion */ > store_pool = POOL_PERM; >-tls_in.sni = string_copy_taint(US servername, TRUE); >+tls_in.sni = string_copy_taint(US servername, GET_TAINTED); > store_pool = old_pool; > > if (!reexpand_tls_files_for_sni) >@@ -1706,51 +2184,54 @@ > not confident that memcpy wouldn't break some internal reference counting. > Especially since there's a references struct member, which would be off. */ > >-#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD >-if (!(server_sni = SSL_CTX_new(TLS_server_method()))) >-#else >-if (!(server_sni = SSL_CTX_new(SSLv23_server_method()))) >-#endif >- { >- ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); >- DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring); >+if (lib_ctx_new(&server_sni, NULL, &dummy_errstr) != OK) > goto bad; >- } > > /* Not sure how many of these are actually needed, since SSL object > already exists. Might even need this selfsame callback, for reneg? */ > >-SSL_CTX_set_info_callback(server_sni, SSL_CTX_get_info_callback(server_ctx)); >-SSL_CTX_set_mode(server_sni, SSL_CTX_get_mode(server_ctx)); >-SSL_CTX_set_options(server_sni, SSL_CTX_get_options(server_ctx)); >-SSL_CTX_set_timeout(server_sni, SSL_CTX_get_timeout(server_ctx)); >-SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb); >-SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo); >+ { >+ SSL_CTX * ctx = state_server.lib_state.lib_ctx; >+ SSL_CTX_set_info_callback(server_sni, SSL_CTX_get_info_callback(ctx)); >+ SSL_CTX_set_mode(server_sni, SSL_CTX_get_mode(ctx)); >+ SSL_CTX_set_options(server_sni, SSL_CTX_get_options(ctx)); >+ SSL_CTX_set_timeout(server_sni, SSL_CTX_get_timeout(ctx)); >+ SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb); >+ SSL_CTX_set_tlsext_servername_arg(server_sni, state); >+ } > >-if ( !init_dh(server_sni, cbinfo->dhparam, NULL, &dummy_errstr) >- || !init_ecdh(server_sni, NULL, &dummy_errstr) >+if ( !init_dh(server_sni, state->dhparam, &dummy_errstr) >+ || !init_ecdh(server_sni, &dummy_errstr) > ) > goto bad; > >-if ( cbinfo->server_cipher_list >- && !SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list)) >+if ( state->server_cipher_list >+ && !SSL_CTX_set_cipher_list(server_sni, CS state->server_cipher_list)) > goto bad; > > #ifndef DISABLE_OCSP >-if (cbinfo->u_ocsp.server.file) >+if (state->u_ocsp.server.file) > { > SSL_CTX_set_tlsext_status_cb(server_sni, tls_server_stapling_cb); >- SSL_CTX_set_tlsext_status_arg(server_sni, cbinfo); >+ SSL_CTX_set_tlsext_status_arg(server_sni, state); > } > #endif > >-if ((rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE, >- verify_callback_server, &dummy_errstr)) != OK) >- goto bad; >+ { >+ uschar * expcerts; >+ if ( !expand_check(tls_verify_certificates, US"tls_verify_certificates", >+ &expcerts, &dummy_errstr) >+ || (rc = setup_certs(server_sni, expcerts, tls_crl, NULL, >+ &dummy_errstr)) != OK) >+ goto bad; >+ >+ if (expcerts && *expcerts) >+ setup_cert_verify(server_sni, FALSE, verify_callback_server); >+ } > > /* do this after setup_certs, because this can require the certs for verifying > OCSP information. */ >-if ((rc = tls_expand_session_files(server_sni, cbinfo, &dummy_errstr)) != OK) >+if ((rc = tls_expand_session_files(server_sni, state, &dummy_errstr)) != OK) > goto bad; > > DEBUG(D_tls) debug_printf("Switching SSL context.\n"); >@@ -1764,6 +2245,61 @@ > > > >+#ifdef EXIM_HAVE_ALPN >+/************************************************* >+* Callback to handle ALPN * >+*************************************************/ >+ >+/* Called on server if tls_alpn nonblank after expansion, >+when client offers ALPN, after the SNI callback. >+If set and not matching the list then we dump the connection */ >+ >+static int >+tls_server_alpn_cb(SSL *ssl, const uschar ** out, uschar * outlen, >+ const uschar * in, unsigned int inlen, void * arg) >+{ >+server_seen_alpn = TRUE; >+DEBUG(D_tls) >+ { >+ debug_printf("Received TLS ALPN offer:"); >+ for (int pos = 0, siz; pos < inlen; pos += siz+1) >+ { >+ siz = in[pos]; >+ if (pos + 1 + siz > inlen) siz = inlen - pos - 1; >+ debug_printf(" '%.*s'", siz, in + pos + 1); >+ } >+ debug_printf(". Our list: '%s'\n", tls_alpn); >+ } >+ >+/* Look for an acceptable ALPN */ >+ >+if ( inlen > 1 /* at least one name */ >+ && in[0]+1 == inlen /* filling the vector, so exactly one name */ >+ ) >+ { >+ const uschar * list = tls_alpn; >+ int sep = 0; >+ for (uschar * name; name = string_nextinlist(&list, &sep, NULL, 0); ) >+ if (Ustrncmp(in+1, name, in[0]) == 0) >+ { >+ *out = in+1; /* we checked for exactly one, so can just point to it */ >+ *outlen = inlen; >+ return SSL_TLSEXT_ERR_OK; /* use ALPN */ >+ } >+ } >+ >+/* More than one name from clilent, or name did not match our list. */ >+ >+/* This will be fatal to the TLS conn; would be nice to kill TCP also. >+Maybe as an option in future; for now leave control to the config (must-tls). */ >+ >+DEBUG(D_tls) debug_printf("TLS ALPN rejected\n"); >+return SSL_TLSEXT_ERR_ALERT_FATAL; >+} >+#endif /* EXIM_HAVE_ALPN */ >+ >+ >+ > #ifndef DISABLE_OCSP > > /************************************************* >@@ -1781,8 +2317,8 @@ > static int > tls_server_stapling_cb(SSL *s, void *arg) > { >-const tls_ext_ctx_cb * cbinfo = (tls_ext_ctx_cb *) arg; >-ocsp_resplist * olist = cbinfo->u_ocsp.server.olist; >+const exim_openssl_state_st * state = arg; >+ocsp_resplist * olist = state->u_ocsp.server.olist; > uschar * response_der; /*XXX blob */ > int response_der_len; > >@@ -1799,9 +2335,6 @@ > const X509 * cert_sent = SSL_get_certificate(s); > const ASN1_INTEGER * cert_serial = X509_get0_serialNumber(cert_sent); > const BIGNUM * cert_bn = ASN1_INTEGER_to_BN(cert_serial, NULL); >- const X509_NAME * cert_issuer = X509_get_issuer_name(cert_sent); >- uschar * chash; >- uint chash_len; > > for (; olist; olist = olist->next) > { >@@ -1856,7 +2389,8 @@ > if (response_der_len <= 0) > return SSL_TLSEXT_ERR_NOACK; > >-SSL_set_tlsext_status_ocsp_resp(server_ssl, response_der, response_der_len); >+SSL_set_tlsext_status_ocsp_resp(state_server.lib_state.lib_ssl, >+ response_der, response_der_len); > tls_in.ocsp = OCSP_VFIED; > return SSL_TLSEXT_ERR_OK; > } >@@ -1873,7 +2407,7 @@ > static int > tls_client_stapling_cb(SSL *s, void *arg) > { >-tls_ext_ctx_cb * cbinfo = arg; >+exim_openssl_state_st * cbinfo = arg; > const unsigned char * p; > int len; > OCSP_RESPONSE * rsp; >@@ -1883,8 +2417,12 @@ > DEBUG(D_tls) debug_printf("Received TLS status callback (OCSP stapling):\n"); > len = SSL_get_tlsext_status_ocsp_resp(s, &p); > if(!p) >- { >- /* Expect this when we requested ocsp but got none */ >+ { /* Expect this when we requested ocsp but got none */ >+ if (SSL_session_reused(s) && tls_out.ocsp == OCSP_VFIED) >+ { >+ DEBUG(D_tls) debug_printf(" null, but resumed; ocsp vfy stored with session is good\n"); >+ return 1; >+ } > if (cbinfo->u_ocsp.client.verify_required && LOGGING(tls_cipher)) > log_write(0, LOG_MAIN, "Required TLS certificate status not received"); > else >@@ -1926,7 +2464,7 @@ > STACK_OF(OCSP_SINGLERESP) * sresp = bs->tbsResponseData->responses; > #endif > >- DEBUG(D_tls) bp = BIO_new_fp(debug_file, BIO_NOCLOSE); >+ DEBUG(D_tls) bp = BIO_new(BIO_s_mem()); > > /*OCSP_RESPONSE_print(bp, rsp, 0); extreme debug: stapling content */ > >@@ -1941,9 +2479,12 @@ > if (LOGGING(tls_cipher)) log_write(0, LOG_MAIN, > "Received TLS cert status response, itself unverifiable: %s", > ERR_reason_error_string(ERR_peek_error())); >- BIO_printf(bp, "OCSP response verify failure\n"); >- ERR_print_errors(bp); >- OCSP_RESPONSE_print(bp, rsp, 0); >+ DEBUG(D_tls) >+ { >+ BIO_printf(bp, "OCSP response verify failure\n"); >+ ERR_print_errors(bp); >+ OCSP_RESPONSE_print(bp, rsp, 0); >+ } > goto failed; > } > else >@@ -1981,14 +2522,17 @@ > status = OCSP_single_get0_status(single, &reason, &rev, > &thisupd, &nextupd); > >- DEBUG(D_tls) time_print(bp, "This OCSP Update", thisupd); >- DEBUG(D_tls) if(nextupd) time_print(bp, "Next OCSP Update", nextupd); >+ DEBUG(D_tls) >+ { >+ time_print(bp, "This OCSP Update", thisupd); >+ if (nextupd) time_print(bp, "Next OCSP Update", nextupd); >+ } > if (!OCSP_check_validity(thisupd, nextupd, > EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE)) > { > tls_out.ocsp = OCSP_FAILED; > DEBUG(D_tls) ERR_print_errors(bp); >- log_write(0, LOG_MAIN, "Server OSCP dates invalid"); >+ log_write(0, LOG_MAIN, "OCSP dates invalid"); > goto failed; > } > >@@ -2021,6 +2565,11 @@ > tls_out.ocsp = OCSP_FAILED; > i = cbinfo->u_ocsp.client.verify_required ? 0 : 1; > good: >+ { >+ uschar * s = NULL; >+ int len = (int) BIO_get_mem_data(bp, CSS &s); >+ if (len > 0) debug_printf("%.*s", len, s); >+ } > BIO_free(bp); > } > >@@ -2033,95 +2582,64 @@ > /************************************************* > * Initialize for TLS * > *************************************************/ >- >-static void >-tls_openssl_init(void) >-{ >-#ifdef EXIM_NEED_OPENSSL_INIT >-SSL_load_error_strings(); /* basic set up */ >-OpenSSL_add_ssl_algorithms(); >-#endif >- >-#if defined(EXIM_HAVE_SHA256) && !defined(OPENSSL_AUTO_SHA256) >-/* SHA256 is becoming ever more popular. This makes sure it gets added to the >-list of available digests. */ >-EVP_add_digest(EVP_sha256()); >-#endif >-} >- >- >- > /* Called from both server and client code, to do preliminary initialization > of the library. We allocate and return a context structure. > > Arguments: >- ctxp returned SSL context > host connected host, if client; NULL if server >- dhparam DH parameter file >- certificate certificate file >- privatekey private key >+ ob transport options block, if client; NULL if server > ocsp_file file of stapling info (server); flag for require ocsp (client) > addr address if client; NULL if server (for some randomness) >- cbp place to put allocated callback context >+ caller_state place to put pointer to allocated state-struct > errstr error string pointer > > Returns: OK/DEFER/FAIL > */ > > static int >-tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate, >- uschar *privatekey, >+tls_init(host_item * host, smtp_transport_options_block * ob, > #ifndef DISABLE_OCSP > uschar *ocsp_file, > #endif >- address_item *addr, tls_ext_ctx_cb ** cbp, >+ address_item *addr, exim_openssl_state_st ** caller_state, > tls_support * tlsp, > uschar ** errstr) > { > SSL_CTX * ctx; >-long init_options; >+exim_openssl_state_st * state; > int rc; >-tls_ext_ctx_cb * cbinfo; > >-cbinfo = store_malloc(sizeof(tls_ext_ctx_cb)); >-cbinfo->tlsp = tlsp; >-cbinfo->certificate = certificate; >-cbinfo->privatekey = privatekey; >-cbinfo->is_server = host==NULL; >-#ifndef DISABLE_OCSP >-cbinfo->verify_stack = NULL; >-if (!host) >+if (host) /* client */ > { >- cbinfo->u_ocsp.server.file = ocsp_file; >- cbinfo->u_ocsp.server.file_expanded = NULL; >- cbinfo->u_ocsp.server.olist = NULL; >+ state = store_malloc(sizeof(exim_openssl_state_st)); >+ memset(state, 0, sizeof(*state)); >+ state->certificate = ob->tls_certificate; >+ state->privatekey = ob->tls_privatekey; >+ state->is_server = FALSE; >+ state->dhparam = NULL; >+ state->lib_state = ob->tls_preload; >+ } >+else /* server */ >+ { >+ state = &state_server; >+ state->certificate = tls_certificate; >+ state->privatekey = tls_privatekey; >+ state->is_server = TRUE; >+ state->dhparam = tls_dhparam; >+ state->lib_state = state_server.lib_state; > } >-else >- cbinfo->u_ocsp.client.verify_store = NULL; >-#endif >-cbinfo->dhparam = dhparam; >-cbinfo->server_cipher_list = NULL; >-cbinfo->host = host; >-#ifndef DISABLE_EVENT >-cbinfo->event_action = NULL; >-#endif > >-tls_openssl_init(); >+state->tlsp = tlsp; >+state->host = host; > >-/* Create a context. >-The OpenSSL docs in 1.0.1b have not been updated to clarify TLS variant >-negotiation in the different methods; as far as I can tell, the only >-*_{server,client}_method which allows negotiation is SSLv23, which exists even >-when OpenSSL is built without SSLv2 support. >-By disabling with openssl_options, we can let admins re-enable with the >-existing knob. */ >+if (!state->lib_state.pri_string) >+ state->server_cipher_list = NULL; > >-#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD >-if (!(ctx = SSL_CTX_new(host ? TLS_client_method() : TLS_server_method()))) >-#else >-if (!(ctx = SSL_CTX_new(host ? SSLv23_client_method() : SSLv23_server_method()))) >+#ifndef DISABLE_EVENT >+state->event_action = NULL; > #endif >- return tls_error(US"SSL_CTX_new", host, NULL, errstr); >+ >+tls_openssl_init(); > > /* It turns out that we need to seed the random number generator this early in > order to get the full complement of ciphers to work. It took me roughly a day >@@ -2129,40 +2647,14 @@ > > On systems that have /dev/urandom, SSL may automatically seed itself from > there. Otherwise, we have to make something up as best we can. Double check >-afterwards. */ >+afterwards. > >-if (!RAND_status()) >- { >- randstuff r; >- gettimeofday(&r.tv, NULL); >- r.p = getpid(); >+Although we likely called this before, at daemon startup, this is a chance >+to mix in further variable info (time, pid) if needed. */ > >- RAND_seed(US (&r), sizeof(r)); >- RAND_seed(US big_buffer, big_buffer_size); >- if (addr != NULL) RAND_seed(US addr, sizeof(addr)); >- >- if (!RAND_status()) >- return tls_error(US"RAND_status", host, >- US"unable to seed random number generator", errstr); >- } >- >-/* Set up the information callback, which outputs if debugging is at a suitable >-level. */ >- >-DEBUG(D_tls) >- { >- SSL_CTX_set_info_callback(ctx, (void (*)())info_callback); >-#if defined(EXIM_HAVE_OPESSL_TRACE) && !defined(OPENSSL_NO_SSL_TRACE) >- /* this needs a debug build of OpenSSL */ >- SSL_CTX_set_msg_callback(ctx, (void (*)())SSL_trace); >-#endif >-#ifdef OPENSSL_HAVE_KEYLOG_CB >- SSL_CTX_set_keylog_callback(ctx, (void (*)())keylog_callback); >-#endif >- } >- >-/* Automatically re-try reads/writes after renegotiation. */ >-(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); >+if (!lib_rand_init(addr)) >+ return tls_error(US"RAND_status", host, >+ US"unable to seed random number generator", errstr); > > /* Apply administrator-supplied work-arounds. > Historically we applied just one requested option, >@@ -2173,15 +2665,31 @@ > No OpenSSL version number checks: the options we accept depend upon the > availability of the option value macros from OpenSSL. */ > >-if (!tls_openssl_options_parse(openssl_options, &init_options)) >- return tls_error(US"openssl_options parsing failed", host, NULL, errstr); >+if (!init_options) >+ if (!tls_openssl_options_parse(openssl_options, &init_options)) >+ return tls_error(US"openssl_options parsing failed", host, NULL, errstr); >+ >+/* Create a context. >+The OpenSSL docs in 1.0.1b have not been updated to clarify TLS variant >+negotiation in the different methods; as far as I can tell, the only >+*_{server,client}_method which allows negotiation is SSLv23, which exists even >+when OpenSSL is built without SSLv2 support. >+By disabling with openssl_options, we can let admins re-enable with the >+existing knob. */ > >-#ifdef EXPERIMENTAL_TLS_RESUME >+if (!(ctx = state->lib_state.lib_ctx)) >+ { >+ if ((rc = lib_ctx_new(&ctx, host, errstr)) != OK) >+ return rc; >+ state->lib_state.lib_ctx = ctx; >+ } >+ >+#ifndef DISABLE_TLS_RESUME > tlsp->resumption = RESUME_SUPPORTED; > #endif > if (init_options) > { >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifndef DISABLE_TLS_RESUME > /* Should the server offer session resumption? */ > if (!host && verify_check_host(&tls_resumption_hosts) == OK) > { >@@ -2213,21 +2721,44 @@ > /* Initialize with DH parameters if supplied */ > /* Initialize ECDH temp key parameter selection */ > >-if ( !init_dh(ctx, dhparam, host, errstr) >- || !init_ecdh(ctx, host, errstr) >- ) >- return DEFER; >+if (!host) >+ { >+ if (state->lib_state.dh) >+ { DEBUG(D_tls) debug_printf("TLS: DH params were preloaded\n"); } >+ else >+ if (!init_dh(ctx, state->dhparam, errstr)) return DEFER; >+ >+ if (state->lib_state.ecdh) >+ { DEBUG(D_tls) debug_printf("TLS: ECDH curve was preloaded\n"); } >+ else >+ if (!init_ecdh(ctx, errstr)) return DEFER; >+ } > > /* Set up certificate and key (and perhaps OCSP info) */ > >-if ((rc = tls_expand_session_files(ctx, cbinfo, errstr)) != OK) >- return rc; >+if (state->lib_state.conn_certs) >+ { >+ DEBUG(D_tls) >+ debug_printf("TLS: %s certs were preloaded\n", host ? "client":"server"); >+ } >+else >+ { >+#ifndef DISABLE_OCSP >+ if (!host) >+ { >+ state->u_ocsp.server.file = ocsp_file; >+ state->u_ocsp.server.file_expanded = NULL; >+ state->u_ocsp.server.olist = NULL; >+ } >+#endif >+ if ((rc = tls_expand_session_files(ctx, state, errstr)) != OK) return rc; >+ } > > /* If we need to handle SNI or OCSP, do so */ > > #ifdef EXIM_HAVE_OPENSSL_TLSEXT > # ifndef DISABLE_OCSP >- if (!(cbinfo->verify_stack = sk_X509_new_null())) >+ if (!(state->verify_stack = sk_X509_new_null())) > { > DEBUG(D_tls) debug_printf("failed to create stack for stapling verify\n"); > return FAIL; >@@ -2241,33 +2772,48 @@ > the option exists, not what the current expansion might be, as SNI might > change the certificate and OCSP file in use between now and the time the > callback is invoked. */ >- if (cbinfo->u_ocsp.server.file) >+ if (state->u_ocsp.server.file) > { > SSL_CTX_set_tlsext_status_cb(ctx, tls_server_stapling_cb); >- SSL_CTX_set_tlsext_status_arg(ctx, cbinfo); >+ SSL_CTX_set_tlsext_status_arg(ctx, state); > } > # endif > /* We always do this, so that $tls_sni is available even if not used in > tls_certificate */ > SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb); >- SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo); >+ SSL_CTX_set_tlsext_servername_arg(ctx, state); >+ >+# ifdef EXIM_HAVE_ALPN >+ if (tls_alpn && *tls_alpn) >+ { >+ uschar * exp_alpn; >+ if ( expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr) >+ && *exp_alpn && !isblank(*exp_alpn)) >+ { >+ tls_alpn = exp_alpn; /* subprocess so ok to overwrite */ >+ SSL_CTX_set_alpn_select_cb(ctx, tls_server_alpn_cb, state); >+ } >+ else >+ tls_alpn = NULL; >+ } >+# endif > } > # ifndef DISABLE_OCSP > else /* client */ > if(ocsp_file) /* wanting stapling */ > { >- if (!(cbinfo->u_ocsp.client.verify_store = X509_STORE_new())) >+ if (!(state->u_ocsp.client.verify_store = X509_STORE_new())) > { > DEBUG(D_tls) debug_printf("failed to create store for stapling verify\n"); > return FAIL; > } > SSL_CTX_set_tlsext_status_cb(ctx, tls_client_stapling_cb); >- SSL_CTX_set_tlsext_status_arg(ctx, cbinfo); >+ SSL_CTX_set_tlsext_status_arg(ctx, state); > } > # endif > #endif > >-cbinfo->verify_cert_hostnames = NULL; >+state->verify_cert_hostnames = NULL; > > #ifdef EXIM_HAVE_EPHEM_RSA_KEX > /* Set up the RSA callback */ >@@ -2280,8 +2826,7 @@ > SSL_CTX_set_timeout(ctx, ssl_session_timeout); > DEBUG(D_tls) debug_printf("Initialized TLS\n"); > >-*cbp = cbinfo; >-*ctxp = ctx; >+*caller_state = state; > > return OK; > } >@@ -2408,18 +2953,22 @@ > /* Load certs from file, return TRUE on success */ > > static BOOL >-chain_from_pem_file(const uschar * file, STACK_OF(X509) * verify_stack) >+chain_from_pem_file(const uschar * file, STACK_OF(X509) ** vp) > { > BIO * bp; >-X509 * x; >+STACK_OF(X509) * verify_stack = *vp; > >-while (sk_X509_num(verify_stack) > 0) >- X509_free(sk_X509_pop(verify_stack)); >+if (verify_stack) >+ while (sk_X509_num(verify_stack) > 0) >+ X509_free(sk_X509_pop(verify_stack)); >+else >+ verify_stack = sk_X509_new_null(); > > if (!(bp = BIO_new_file(CS file, "r"))) return FALSE; >-while ((x = PEM_read_bio_X509(bp, NULL, 0, NULL))) >+for (X509 * x; x = PEM_read_bio_X509(bp, NULL, 0, NULL); ) > sk_X509_push(verify_stack, x); > BIO_free(bp); >+*vp = verify_stack; > return TRUE; > } > #endif >@@ -2431,20 +2980,17 @@ > > Arguments: > sctx SSL_CTX* to initialise >- certs certs file or NULL >+ certs certs file, expanded > crl CRL file or NULL > host NULL in a server; the remote host in a client >- optional TRUE if called from a server for a host in tls_try_verify_hosts; >- otherwise passed as FALSE >- cert_vfy_cb Callback function for certificate verification > errstr error string pointer > > Returns: OK/DEFER/FAIL > */ > > static int >-setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional, >- int (*cert_vfy_cb)(int, X509_STORE_CTX *), uschar ** errstr) >+setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, >+ uschar ** errstr) > { > uschar *expcerts, *expcrl; > >@@ -2460,7 +3006,7 @@ > if (!SSL_CTX_set_default_verify_paths(sctx)) > return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL, errstr); > >- if (Ustrcmp(expcerts, "system") != 0) >+ if (Ustrcmp(expcerts, "system") != 0 && Ustrncmp(expcerts, "system,", 7) != 0) > { > struct stat statbuf; > >@@ -2477,6 +3023,13 @@ > { file = NULL; dir = expcerts; } > else > { >+ STACK_OF(X509) * verify_stack = >+#ifndef DISABLE_OCSP >+ !host ? state_server.verify_stack : >+#endif >+ NULL; >+ STACK_OF(X509) ** vp = &verify_stack; >+ > file = expcerts; dir = NULL; > #ifndef DISABLE_OCSP > /* In the server if we will be offering an OCSP proof, load chain from >@@ -2485,11 +3038,10 @@ > /*XXX Glitch! The file here is tls_verify_certs: the chain for verifying the client cert. > This is inconsistent with the need to verify the OCSP proof of the server cert. > */ >- > if ( !host > && statbuf.st_size > 0 >- && server_static_cbinfo->u_ocsp.server.file >- && !chain_from_pem_file(file, server_static_cbinfo->verify_stack) >+ && state_server.u_ocsp.server.file >+ && !chain_from_pem_file(file, vp) > ) > { > log_write(0, LOG_MAIN|LOG_PANIC, >@@ -2506,7 +3058,8 @@ > > if ( (!file || statbuf.st_size > 0) > && !SSL_CTX_load_verify_locations(sctx, CS file, CS dir)) >- return tls_error(US"SSL_CTX_load_verify_locations", host, NULL, errstr); >+ return tls_error(US"SSL_CTX_load_verify_locations", >+ host, NULL, errstr); > > /* On the server load the list of CAs for which we will accept certs, for > sending to the client. This is only for the one-file >@@ -2521,11 +3074,15 @@ > if (file) > { > STACK_OF(X509_NAME) * names = SSL_load_client_CA_file(CS file); >+ int i = sk_X509_NAME_num(names); > > if (!host) SSL_CTX_set_client_CA_list(sctx, names); >- DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", >- sk_X509_NAME_num(names)); >+ DEBUG(D_tls) debug_printf("Added %d additional certificate authorit%s\n", >+ i, i>1 ? "ies":"y"); > } >+ else >+ DEBUG(D_tls) >+ debug_printf("Added dir for additional certificate authorities\n"); > } > } > >@@ -2581,12 +3138,6 @@ > } > > #endif /* OPENSSL_VERSION_NUMBER > 0x00907000L */ >- >- /* If verification is optional, don't fail if no certificate */ >- >- SSL_CTX_set_verify(sctx, >- SSL_VERIFY_PEER | (optional ? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT), >- cert_vfy_cb); > } > > return OK; >@@ -2594,16 +3145,29 @@ > > > >+static void >+tls_dump_keylog(SSL * ssl) >+{ >+#ifdef EXIM_HAVE_OPENSSL_KEYLOG >+ BIO * bp = BIO_new(BIO_s_mem()); >+ uschar * s = NULL; >+ int len; >+ SSL_SESSION_print_keylog(bp, SSL_get_session(ssl)); >+ len = (int) BIO_get_mem_data(bp, CSS &s); >+ if (len > 0) debug_printf("%.*s", len, s); >+ BIO_free(bp); >+#endif >+} >+ >+ > /************************************************* > * Start a TLS session in a server * > *************************************************/ >- > /* This is called when Exim is running as a server, after having received > the STARTTLS command. It must respond to that command, and then negotiate > a TLS session. > > Arguments: >- require_ciphers allowed ciphers > errstr pointer to error message > > Returns: OK on success >@@ -2613,11 +3177,13 @@ > */ > > int >-tls_server_start(const uschar * require_ciphers, uschar ** errstr) >+tls_server_start(uschar ** errstr) > { > int rc; > uschar * expciphers; >-tls_ext_ctx_cb * cbinfo; >+exim_openssl_state_st * dummy_statep; >+SSL_CTX * ctx; >+SSL * ssl; > static uschar peerdn[256]; > > /* Check for previous activation */ >@@ -2632,16 +3198,13 @@ > /* Initialize the SSL library. If it fails, it will already have logged > the error. */ > >-rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey, >+rc = tls_init(NULL, NULL, > #ifndef DISABLE_OCSP > tls_ocsp_file, > #endif >- NULL, &server_static_cbinfo, &tls_in, errstr); >+ NULL, &dummy_statep, &tls_in, errstr); > if (rc != OK) return rc; >-cbinfo = server_static_cbinfo; >- >-if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers, errstr)) >- return FAIL; >+ctx = state_server.lib_state.lib_ctx; > > /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they > were historically separated by underscores. So that I can use either form in my >@@ -2652,13 +3215,19 @@ > TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256 > */ > >-if (expciphers) >+if (state_server.lib_state.pri_string) >+ { DEBUG(D_tls) debug_printf("TLS: cipher list was preloaded\n"); } >+else > { >- for (uschar * s = expciphers; *s; s++ ) if (*s == '_') *s = '-'; >- DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers); >- if (!SSL_CTX_set_cipher_list(server_ctx, CS expciphers)) >- return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL, errstr); >- cbinfo->server_cipher_list = expciphers; >+ if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers, errstr)) >+ return FAIL; >+ >+ if (expciphers) >+ { >+ normalise_ciphers(&expciphers, tls_require_ciphers); >+ if ((rc = server_load_ciphers(ctx, &state_server, expciphers, errstr)) != OK) >+ return rc; >+ } > } > > /* If this is a host for which certificate verification is mandatory or >@@ -2671,37 +3240,54 @@ > server_verify_callback_called = FALSE; > > if (verify_check_host(&tls_verify_hosts) == OK) >- { >- rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, >- FALSE, verify_callback_server, errstr); >- if (rc != OK) return rc; > server_verify_optional = FALSE; >- } > else if (verify_check_host(&tls_try_verify_hosts) == OK) >- { >- rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, >- TRUE, verify_callback_server, errstr); >- if (rc != OK) return rc; > server_verify_optional = TRUE; >- } >+else >+ goto skip_certs; >+ >+ { >+ uschar * expcerts; >+ if (!expand_check(tls_verify_certificates, US"tls_verify_certificates", >+ &expcerts, errstr)) >+ return DEFER; >+ DEBUG(D_tls) debug_printf("tls_verify_certificates: %s\n", expcerts); >+ >+ if (state_server.lib_state.cabundle) >+ { DEBUG(D_tls) debug_printf("TLS: CA bundle for server was preloaded\n"); } >+ else >+ if ((rc = setup_certs(ctx, expcerts, tls_crl, NULL, errstr)) != OK) >+ return rc; >+ >+ if (expcerts && *expcerts) >+ setup_cert_verify(ctx, server_verify_optional, verify_callback_server); >+ } >+skip_certs: ; > >-#ifdef EXPERIMENTAL_TLS_RESUME >-SSL_CTX_set_tlsext_ticket_key_cb(server_ctx, ticket_key_callback); >+#ifndef DISABLE_TLS_RESUME >+# if OPENSSL_VERSION_NUMBER < 0x30000000L >+SSL_CTX_set_tlsext_ticket_key_cb(ctx, ticket_key_callback); > /* despite working, appears to always return failure, so ignoring */ >+# else >+SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ticket_key_callback); >+/* despite working, appears to always return failure, so ignoring */ >+# endif > #endif >+ > #ifdef OPENSSL_HAVE_NUM_TICKETS >-# ifdef EXPERIMENTAL_TLS_RESUME >-SSL_CTX_set_num_tickets(server_ctx, tls_in.host_resumable ? 1 : 0); >+# ifndef DISABLE_TLS_RESUME >+SSL_CTX_set_num_tickets(ctx, tls_in.host_resumable ? 1 : 0); > # else >-SSL_CTX_set_num_tickets(server_ctx, 0); /* send no TLS1.3 stateful-tickets */ >+SSL_CTX_set_num_tickets(ctx, 0); /* send no TLS1.3 stateful-tickets */ > # endif > #endif > > > /* Prepare for new connection */ > >-if (!(server_ssl = SSL_new(server_ctx))) >+if (!(ssl = SSL_new(ctx))) > return tls_error(US"SSL_new", NULL, NULL, errstr); >+state_server.lib_state.lib_ssl = ssl; > > /* Warning: we used to SSL_clear(ssl) here, it was removed. > * >@@ -2722,7 +3308,7 @@ > the response. Other smtp_printf() calls do not need it, because in non-TLS > mode, the fflush() happens when smtp_getc() is called. */ > >-SSL_set_session_id_context(server_ssl, sid_ctx, Ustrlen(sid_ctx)); >+SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx)); > if (!tls_in.on_connect) > { > smtp_printf("220 TLS go ahead\r\n", FALSE); >@@ -2732,20 +3318,21 @@ > /* Now negotiate the TLS session. We put our own timer on it, since it seems > that the OpenSSL library doesn't. */ > >-SSL_set_wfd(server_ssl, fileno(smtp_out)); >-SSL_set_rfd(server_ssl, fileno(smtp_in)); >-SSL_set_accept_state(server_ssl); >+SSL_set_wfd(ssl, fileno(smtp_out)); >+SSL_set_rfd(ssl, fileno(smtp_in)); >+SSL_set_accept_state(ssl); > > DEBUG(D_tls) debug_printf("Calling SSL_accept\n"); > >+ERR_clear_error(); > sigalrm_seen = FALSE; > if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout); >-rc = SSL_accept(server_ssl); >+rc = SSL_accept(ssl); > ALARM_CLR(0); > > if (rc <= 0) > { >- int error = SSL_get_error(server_ssl, rc); >+ int error = SSL_get_error(ssl, rc); > switch(error) > { > case SSL_ERROR_NONE: >@@ -2754,9 +3341,11 @@ > case SSL_ERROR_ZERO_RETURN: > DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n"); > (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr); >- >- if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN) >- SSL_shutdown(server_ssl); >+#ifndef DISABLE_EVENT >+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); >+#endif >+ if (SSL_get_shutdown(ssl) == SSL_RECEIVED_SHUTDOWN) >+ SSL_shutdown(ssl); > > tls_close(NULL, TLS_NO_SHUTDOWN); > return FAIL; >@@ -2764,15 +3353,18 @@ > /* Handle genuine errors */ > case SSL_ERROR_SSL: > { >- uschar * s = US"SSL_accept"; >+ uschar * s = NULL; > int r = ERR_GET_REASON(ERR_peek_error()); > if ( r == SSL_R_WRONG_VERSION_NUMBER > #ifdef SSL_R_VERSION_TOO_LOW > || r == SSL_R_VERSION_TOO_LOW > #endif > || r == SSL_R_UNKNOWN_PROTOCOL || r == SSL_R_UNSUPPORTED_PROTOCOL) >- s = string_sprintf("%s (%s)", s, SSL_get_version(server_ssl)); >- (void) tls_error(s, NULL, sigalrm_seen ? US"timed out" : NULL, errstr); >+ s = string_sprintf("(%s)", SSL_get_version(ssl)); >+ (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : s, errstr); >+#ifndef DISABLE_EVENT >+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); >+#endif > return FAIL; > } > >@@ -2783,11 +3375,20 @@ > if (!errno) > { > *errstr = US"SSL_accept: TCP connection closed by peer"; >+#ifndef DISABLE_EVENT >+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); >+#endif > return FAIL; > } > DEBUG(D_tls) debug_printf(" - syscall %s\n", strerror(errno)); > } >- (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr); >+ (void) tls_error(US"SSL_accept", NULL, >+ sigalrm_seen ? US"timed out" >+ : ERR_peek_error() ? NULL : string_sprintf("ret %d", error), >+ errstr); >+#ifndef DISABLE_EVENT >+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); >+#endif > return FAIL; > } > } >@@ -2796,43 +3397,64 @@ > ERR_clear_error(); /* Even success can leave errors in the stack. Seen with > anon-authentication ciphersuite negotiated. */ > >-#ifdef EXPERIMENTAL_TLS_RESUME >-if (SSL_session_reused(server_ssl)) >+#ifndef DISABLE_TLS_RESUME >+if (SSL_session_reused(ssl)) > { > tls_in.resumption |= RESUME_USED; > DEBUG(D_tls) debug_printf("Session reused\n"); > } > #endif > >+#ifdef EXIM_HAVE_ALPN >+/* If require-alpn, check server_seen_alpn here. Else abort TLS */ >+if (!tls_alpn || !*tls_alpn) >+ { DEBUG(D_tls) debug_printf("TLS: was not watching for ALPN\n"); } >+else if (!server_seen_alpn) >+ if (verify_check_host(&hosts_require_alpn) == OK) >+ { >+ /* We'd like to send a definitive Alert but OpenSSL provides no facility */ >+ SSL_shutdown(ssl); >+ tls_error(US"handshake", NULL, US"ALPN required but not negotiated", errstr); >+ return FAIL; >+ } >+ else >+ { DEBUG(D_tls) debug_printf("TLS: no ALPN presented in handshake\n"); } >+else DEBUG(D_tls) >+ { >+ const uschar * name; >+ unsigned len; >+ SSL_get0_alpn_selected(ssl, &name, &len); >+ if (len && name) >+ debug_printf("ALPN negotiated: '%.*s'\n", (int)*name, name+1); >+ else >+ debug_printf("ALPN: no protocol negotiated\n"); >+ } >+#endif >+ >+ > /* TLS has been set up. Record data for the connection, > adjust the input functions to read via TLS, and initialize things. */ > > #ifdef SSL_get_extms_support >-tls_in.ext_master_secret = SSL_get_extms_support(server_ssl) == 1; >+tls_in.ext_master_secret = SSL_get_extms_support(ssl) == 1; > #endif >-peer_cert(server_ssl, &tls_in, peerdn, sizeof(peerdn)); >+peer_cert(ssl, &tls_in, peerdn, sizeof(peerdn)); > >-tls_in.ver = tlsver_name(server_ssl); >-tls_in.cipher = construct_cipher_name(server_ssl, tls_in.ver, &tls_in.bits); >-tls_in.cipher_stdname = cipher_stdname_ssl(server_ssl); >+tls_in.ver = tlsver_name(ssl); >+tls_in.cipher = construct_cipher_name(ssl, tls_in.ver, &tls_in.bits); >+tls_in.cipher_stdname = cipher_stdname_ssl(ssl); > > DEBUG(D_tls) > { > uschar buf[2048]; >- if (SSL_get_shared_ciphers(server_ssl, CS buf, sizeof(buf))) >+ if (SSL_get_shared_ciphers(ssl, CS buf, sizeof(buf))) > debug_printf("Shared ciphers: %s\n", buf); > >-#ifdef EXIM_HAVE_OPENSSL_KEYLOG >- { >- BIO * bp = BIO_new_fp(debug_file, BIO_NOCLOSE); >- SSL_SESSION_print_keylog(bp, SSL_get_session(server_ssl)); >- BIO_free(bp); >- } >-#endif >+ tls_dump_keylog(ssl); > > #ifdef EXIM_HAVE_SESSION_TICKET > { >- SSL_SESSION * ss = SSL_get_session(server_ssl); >+ SSL_SESSION * ss = SSL_get_session(ssl); > if (SSL_SESSION_has_ticket(ss)) /* 1.1.0 */ > debug_printf("The session has a ticket, life %lu seconds\n", > SSL_SESSION_get_ticket_lifetime_hint(ss)); >@@ -2842,7 +3464,7 @@ > > /* Record the certificate we presented */ > { >- X509 * crt = SSL_get_certificate(server_ssl); >+ X509 * crt = SSL_get_certificate(ssl); > tls_in.ourcert = crt ? X509_dup(crt) : NULL; > } > >@@ -2850,12 +3472,12 @@ > See description in https://paquier.xyz/postgresql-2/channel-binding-openssl/ */ > { > uschar c, * s; >- size_t len = SSL_get_peer_finished(server_ssl, &c, 0); >+ size_t len = SSL_get_peer_finished(ssl, &c, 0); > int old_pool = store_pool; > >- SSL_get_peer_finished(server_ssl, s = store_get((int)len, FALSE), len); >+ SSL_get_peer_finished(ssl, s = store_get((int)len, GET_UNTAINTED), len); > store_pool = POOL_PERM; >- tls_in.channelbinding = b64encode_taint(CUS s, (int)len, FALSE); >+ tls_in.channelbinding = b64encode_taint(CUS s, (int)len, GET_UNTAINTED); > store_pool = old_pool; > DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p\n", tls_in.channelbinding); > } >@@ -2872,10 +3494,10 @@ > receive_getc = tls_getc; > receive_getbuf = tls_getbuf; > receive_get_cache = tls_get_cache; >+receive_hasc = tls_hasc; > receive_ungetc = tls_ungetc; > receive_feof = tls_feof; > receive_ferror = tls_ferror; >-receive_smtp_buffered = tls_smtp_buffered; > > tls_in.active.sock = fileno(smtp_out); > tls_in.active.tls_ctx = NULL; /* not using explicit ctx for server-side */ >@@ -2887,16 +3509,21 @@ > > static int > tls_client_basic_ctx_init(SSL_CTX * ctx, >- host_item * host, smtp_transport_options_block * ob, tls_ext_ctx_cb * cbinfo, >+ host_item * host, smtp_transport_options_block * ob, exim_openssl_state_st * state, > uschar ** errstr) > { > int rc; >-/* stick to the old behaviour for compatibility if tls_verify_certificates is >- set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only >- the specified host patterns if one of them is defined */ > >-if ( ( !ob->tls_verify_hosts >- && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts) >+/* Back-compatible old behaviour if tls_verify_certificates is set but both >+tls_verify_hosts and tls_try_verify_hosts are not set. Check only the specified >+host patterns if one of them is set with content. */ >+ >+if ( ( ( !ob->tls_verify_hosts || !ob->tls_verify_hosts >+ || Ustrcmp(ob->tls_try_verify_hosts, ":") == 0 >+ ) >+ && ( !ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts >+ || Ustrcmp(ob->tls_try_verify_hosts, ":") == 0 >+ ) > ) > || verify_check_given_host(CUSS &ob->tls_verify_hosts, host) == OK > ) >@@ -2906,21 +3533,33 @@ > else > return OK; > >-if ((rc = setup_certs(ctx, ob->tls_verify_certificates, >- ob->tls_crl, host, client_verify_optional, verify_callback_client, >- errstr)) != OK) >- return rc; >+ { >+ uschar * expcerts; >+ if (!expand_check(ob->tls_verify_certificates, US"tls_verify_certificates", >+ &expcerts, errstr)) >+ return DEFER; >+ DEBUG(D_tls) debug_printf("tls_verify_certificates: %s\n", expcerts); >+ >+ if (state->lib_state.cabundle) >+ { DEBUG(D_tls) debug_printf("TLS: CA bundle was preloaded\n"); } >+ else >+ if ((rc = setup_certs(ctx, expcerts, ob->tls_crl, host, errstr)) != OK) >+ return rc; >+ >+ if (expcerts && *expcerts) >+ setup_cert_verify(ctx, client_verify_optional, verify_callback_client); >+ } > > if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK) > { >- cbinfo->verify_cert_hostnames = >+ state->verify_cert_hostnames = > #ifdef SUPPORT_I18N > string_domain_utf8_to_alabel(host->certname, NULL); > #else > host->certname; > #endif > DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n", >- cbinfo->verify_cert_hostnames); >+ state->verify_cert_hostnames); > } > return OK; > } >@@ -2983,26 +3622,26 @@ > > > >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifndef DISABLE_TLS_RESUME > /* On the client, get any stashed session for the given IP from hints db > and apply it to the ssl-connection for attempted resumption. */ > > static void >-tls_retrieve_session(tls_support * tlsp, SSL * ssl, const uschar * key) >+tls_retrieve_session(tls_support * tlsp, SSL * ssl) > { >-tlsp->resumption |= RESUME_SUPPORTED; > if (tlsp->host_resumable) > { >+ const uschar * key = tlsp->resume_index; > dbdata_tls_session * dt; > int len; > open_db dbblock, * dbm_file; > > tlsp->resumption |= RESUME_CLIENT_REQUESTED; >- DEBUG(D_tls) debug_printf("checking for resumable session for %s\n", key); >+ DEBUG(D_tls) >+ debug_printf("checking for resumable session for %s\n", tlsp->resume_index); > if ((dbm_file = dbfn_open(US"tls", O_RDWR, &dbblock, FALSE, FALSE))) > { >- /* key for the db is the IP */ >- if ((dt = dbfn_read_with_length(dbm_file, key, &len))) >+ if ((dt = dbfn_read_with_length(dbm_file, tlsp->resume_index, &len))) > { > SSL_SESSION * ss = NULL; > const uschar * sess_asn1 = dt->session; >@@ -3017,30 +3656,33 @@ > debug_printf("decoding session: %s\n", ssl_errstring); > } > } >-#ifdef EXIM_HAVE_SESSION_TICKET >- else if ( SSL_SESSION_get_ticket_lifetime_hint(ss) + dt->time_stamp >- < time(NULL)) >+ else > { >- DEBUG(D_tls) debug_printf("session expired\n"); >- dbfn_delete(dbm_file, key); >- } >+ unsigned long lifetime = >+#ifdef EXIM_HAVE_SESSION_TICKET >+ SSL_SESSION_get_ticket_lifetime_hint(ss); >+#else /* Use, fairly arbitrilarily, what we as server would */ >+ f.running_in_test_harness ? 6 : ssl_session_timeout; > #endif >- else if (!SSL_set_session(ssl, ss)) >- { >- DEBUG(D_tls) >+ if (lifetime + dt->time_stamp < time(NULL)) >+ { >+ DEBUG(D_tls) debug_printf("session expired\n"); >+ dbfn_delete(dbm_file, tlsp->resume_index); >+ } >+ else if (SSL_set_session(ssl, ss)) >+ { >+ DEBUG(D_tls) debug_printf("good session\n"); >+ tlsp->resumption |= RESUME_CLIENT_SUGGESTED; >+ tlsp->verify_override = dt->verify_override; >+ tlsp->ocsp = dt->ocsp; >+ } >+ else DEBUG(D_tls) > { > ERR_error_string_n(ERR_get_error(), > ssl_errstring, sizeof(ssl_errstring)); > debug_printf("applying session to ssl: %s\n", ssl_errstring); > } > } >- else >- { >- DEBUG(D_tls) debug_printf("good session\n"); >- tlsp->resumption |= RESUME_CLIENT_SUGGESTED; >- tlsp->verify_override = dt->verify_override; >- tlsp->ocsp = dt->ocsp; >- } > } > else > DEBUG(D_tls) debug_printf("no session record\n"); >@@ -3055,7 +3697,7 @@ > static int > tls_save_session_cb(SSL * ssl, SSL_SESSION * ss) > { >-tls_ext_ctx_cb * cbinfo = SSL_get_ex_data(ssl, tls_exdata_idx); >+exim_openssl_state_st * cbinfo = SSL_get_ex_data(ssl, tls_exdata_idx); > tls_support * tlsp; > > DEBUG(D_tls) debug_printf("tls_save_session_cb\n"); >@@ -3068,7 +3710,7 @@ > { > int len = i2d_SSL_SESSION(ss, NULL); > int dlen = sizeof(dbdata_tls_session) + len; >- dbdata_tls_session * dt = store_get(dlen, TRUE); >+ dbdata_tls_session * dt = store_get(dlen, GET_TAINTED); > uschar * s = dt->session; > open_db dbblock, * dbm_file; > >@@ -3081,9 +3723,7 @@ > > if ((dbm_file = dbfn_open(US"tls", O_RDWR, &dbblock, FALSE, FALSE))) > { >- const uschar * key = cbinfo->host->address; >- dbfn_delete(dbm_file, key); >- dbfn_write(dbm_file, key, dt, dlen); >+ dbfn_write(dbm_file, tlsp->resume_index, dt, dlen); > dbfn_close(dbm_file); > DEBUG(D_tls) debug_printf("wrote session (len %u) to db\n", > (unsigned)dlen); >@@ -3093,21 +3733,20 @@ > } > > >+/* Construct a key for session DB lookup, and setup the SSL_CTX for resumption */ >+ > static void > tls_client_ctx_resume_prehandshake( >- exim_openssl_client_tls_ctx * exim_client_ctx, tls_support * tlsp, >- smtp_transport_options_block * ob, host_item * host) >+ exim_openssl_client_tls_ctx * exim_client_ctx, smtp_connect_args * conn_args, >+ tls_support * tlsp, smtp_transport_options_block * ob) > { >-/* Should the client request a session resumption ticket? */ >-if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK) >- { >- tlsp->host_resumable = TRUE; >+tlsp->host_resumable = TRUE; >+tls_client_resmption_key(tlsp, conn_args, ob); > >- SSL_CTX_set_session_cache_mode(exim_client_ctx->ctx, >- SSL_SESS_CACHE_CLIENT >- | SSL_SESS_CACHE_NO_INTERNAL | SSL_SESS_CACHE_NO_AUTO_CLEAR); >- SSL_CTX_sess_set_new_cb(exim_client_ctx->ctx, tls_save_session_cb); >- } >+SSL_CTX_set_session_cache_mode(exim_client_ctx->ctx, >+ SSL_SESS_CACHE_CLIENT >+ | SSL_SESS_CACHE_NO_INTERNAL | SSL_SESS_CACHE_NO_AUTO_CLEAR); >+SSL_CTX_sess_set_new_cb(exim_client_ctx->ctx, tls_save_session_cb); > } > > static BOOL >@@ -3121,17 +3760,17 @@ > SSL_clear_options(ssl, SSL_OP_NO_TICKET); > > tls_exdata_idx = SSL_get_ex_new_index(0, 0, 0, 0, 0); >- if (!SSL_set_ex_data(ssl, tls_exdata_idx, client_static_cbinfo)) >+ if (!SSL_set_ex_data(ssl, tls_exdata_idx, client_static_state)) > { > tls_error(US"set ex_data", host, NULL, errstr); > return FALSE; > } >- debug_printf("tls_exdata_idx %d cbinfo %p\n", tls_exdata_idx, client_static_cbinfo); >+ debug_printf("tls_exdata_idx %d cbinfo %p\n", tls_exdata_idx, client_static_state); > } > > tlsp->resumption = RESUME_SUPPORTED; > /* Pick up a previous session, saved on an old ticket */ >-tls_retrieve_session(tlsp, ssl, host->address); >+tls_retrieve_session(tlsp, ssl); > return TRUE; > } > >@@ -3145,7 +3784,51 @@ > tlsp->resumption |= RESUME_USED; > } > } >-#endif /* EXPERIMENTAL_TLS_RESUME */ >+#endif /* !DISABLE_TLS_RESUME */ >+ >+ >+#ifdef EXIM_HAVE_ALPN >+/* Expand and convert an Exim list to an ALPN list. False return for fail. >+NULL plist return for silent no-ALPN. >+ >+Overwite the passed-in list with the expanded version. >+*/ >+ >+static BOOL >+tls_alpn_plist(uschar ** tls_alpn, const uschar ** plist, unsigned * plen, >+ uschar ** errstr) >+{ >+uschar * exp_alpn; >+ >+if (!expand_check(*tls_alpn, US"tls_alpn", &exp_alpn, errstr)) >+ return FALSE; >+*tls_alpn = exp_alpn; >+ >+if (!exp_alpn) >+ { >+ DEBUG(D_tls) debug_printf("Setting TLS ALPN forced to fail, not sending\n"); >+ *plist = NULL; >+ } >+else >+ { >+ /* The server implementation only accepts exactly one protocol name >+ but it's little extra code complexity in the client. */ >+ >+ const uschar * list = exp_alpn; >+ uschar * p = store_get(Ustrlen(exp_alpn), exp_alpn), * s, * t; >+ int sep = 0; >+ uschar len; >+ >+ for (t = p; s = string_nextinlist(&list, &sep, NULL, 0); t += len) >+ { >+ *t++ = len = (uschar) Ustrlen(s); >+ memcpy(t, s, len); >+ } >+ *plist = (*plen = t - p) ? p : NULL; >+ } >+return TRUE; >+} >+#endif /* EXIM_HAVE_ALPN */ > > > /************************************************* >@@ -3186,7 +3869,7 @@ > > rc = store_pool; > store_pool = POOL_PERM; >-exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx), FALSE); >+exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx), GET_UNTAINTED); > exim_client_ctx->corked = NULL; > store_pool = rc; > >@@ -3223,14 +3906,15 @@ > } > #endif > >-rc = tls_init(&exim_client_ctx->ctx, host, NULL, >- ob->tls_certificate, ob->tls_privatekey, >+rc = tls_init(host, ob, > #ifndef DISABLE_OCSP > (void *)(long)request_ocsp, > #endif >- cookie, &client_static_cbinfo, tlsp, errstr); >+ cookie, &client_static_state, tlsp, errstr); > if (rc != OK) return FALSE; > >+exim_client_ctx->ctx = client_static_state->lib_state.lib_ctx; >+ > tlsp->certificate_verified = FALSE; > client_verify_callback_called = FALSE; > >@@ -3246,21 +3930,25 @@ > return FALSE; > if (expciphers && *expciphers == '\0') > expciphers = NULL; >+ >+ normalise_ciphers(&expciphers, ob->dane_require_tls_ciphers); > } > #endif >-if (!expciphers && >- !expand_check(ob->tls_require_ciphers, US"tls_require_ciphers", >+if (!expciphers) >+ { >+ if (!expand_check(ob->tls_require_ciphers, US"tls_require_ciphers", > &expciphers, errstr)) >- return FALSE; >+ return FALSE; > >-/* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they >-are separated by underscores. So that I can use either form in my tests, and >-also for general convenience, we turn underscores into hyphens here. */ >+ /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they >+ are separated by underscores. So that I can use either form in my tests, and >+ also for general convenience, we turn underscores into hyphens here. */ >+ >+ normalise_ciphers(&expciphers, ob->tls_require_ciphers); >+ } > > if (expciphers) > { >- uschar *s = expciphers; >- while (*s) { if (*s == '_') *s = '-'; s++; } > DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers); > if (!SSL_CTX_set_cipher_list(exim_client_ctx->ctx, CS expciphers)) > { >@@ -3291,47 +3979,78 @@ > > #endif > >- if (tls_client_basic_ctx_init(exim_client_ctx->ctx, host, ob, >- client_static_cbinfo, errstr) != OK) >- return FALSE; >- >-#ifdef EXPERIMENTAL_TLS_RESUME >-tls_client_ctx_resume_prehandshake(exim_client_ctx, tlsp, ob, host); >-#endif >- >- >-if (!(exim_client_ctx->ssl = SSL_new(exim_client_ctx->ctx))) >- { >- tls_error(US"SSL_new", host, NULL, errstr); >+if (tls_client_basic_ctx_init(exim_client_ctx->ctx, host, ob, >+ client_static_state, errstr) != OK) > return FALSE; >- } >-SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx)); >- >-SSL_set_fd(exim_client_ctx->ssl, cctx->sock); >-SSL_set_connect_state(exim_client_ctx->ssl); > > if (ob->tls_sni) > { > if (!expand_check(ob->tls_sni, US"tls_sni", &tlsp->sni, errstr)) > return FALSE; > if (!tlsp->sni) >- { >- DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n"); >- } >+ { DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n"); } > else if (!Ustrlen(tlsp->sni)) > tlsp->sni = NULL; > else > { >-#ifdef EXIM_HAVE_OPENSSL_TLSEXT >- DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tlsp->sni); >- SSL_set_tlsext_host_name(exim_client_ctx->ssl, tlsp->sni); >-#else >+#ifndef EXIM_HAVE_OPENSSL_TLSEXT > log_write(0, LOG_MAIN, "SNI unusable with this OpenSSL library version; ignoring \"%s\"\n", > tlsp->sni); >+ tlsp->sni = NULL; > #endif > } > } > >+if (ob->tls_alpn) >+#ifdef EXIM_HAVE_ALPN >+ { >+ const uschar * plist; >+ unsigned plen; >+ >+ if (!tls_alpn_plist(&ob->tls_alpn, &plist, &plen, errstr)) >+ return FALSE; >+ if (plist) >+ if (SSL_CTX_set_alpn_protos(exim_client_ctx->ctx, plist, plen) != 0) >+ { >+ tls_error(US"alpn init", host, NULL, errstr); >+ return FALSE; >+ } >+ else >+ DEBUG(D_tls) debug_printf("Setting TLS ALPN '%s'\n", ob->tls_alpn); >+ } >+#else >+ log_write(0, LOG_MAIN, "ALPN unusable with this OpenSSL library version; ignoring \"%s\"\n", >+ ob->tls_alpn); >+#endif >+ >+#ifndef DISABLE_TLS_RESUME >+/*XXX have_lbserver: another cmdline arg possibly, for continued-conn, but use >+will be very low. */ >+ >+if (!conn_args->have_lbserver) /* wanted for tls_client_resmption_key() */ >+ { DEBUG(D_tls) debug_printf("resumption not supported on continued-connection\n"); } >+else if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK) >+ tls_client_ctx_resume_prehandshake(exim_client_ctx, conn_args, tlsp, ob); >+#endif >+ >+ >+if (!(exim_client_ctx->ssl = SSL_new(exim_client_ctx->ctx))) >+ { >+ tls_error(US"SSL_new", host, NULL, errstr); >+ return FALSE; >+ } >+SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx)); >+SSL_set_fd(exim_client_ctx->ssl, cctx->sock); >+SSL_set_connect_state(exim_client_ctx->ssl); >+ >+#ifdef EXIM_HAVE_OPENSSL_TLSEXT >+if (tlsp->sni) >+ { >+ DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tlsp->sni); >+ SSL_set_tlsext_host_name(exim_client_ctx->ssl, tlsp->sni); >+ } >+#endif >+ > #ifdef SUPPORT_DANE > if (conn_args->dane) > if (dane_tlsa_load(exim_client_ctx->ssl, host, &conn_args->tlsa_dnsa, errstr) != OK) >@@ -3361,19 +4080,19 @@ > if (request_ocsp) > { > SSL_set_tlsext_status_type(exim_client_ctx->ssl, TLSEXT_STATUSTYPE_ocsp); >- client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp; >+ client_static_state->u_ocsp.client.verify_required = require_ocsp; > tlsp->ocsp = OCSP_NOT_RESP; > } > #endif > >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifndef DISABLE_TLS_RESUME > if (!tls_client_ssl_resume_prehandshake(exim_client_ctx->ssl, tlsp, host, > errstr)) > return FALSE; > #endif > > #ifndef DISABLE_EVENT >-client_static_cbinfo->event_action = tb ? tb->event_action : NULL; >+client_static_state->event_action = tb ? tb->event_action : NULL; > #endif > > /* There doesn't seem to be a built-in timeout on connection. */ >@@ -3398,19 +4117,31 @@ > DEBUG(D_tls) > { > debug_printf("SSL_connect succeeded\n"); >-#ifdef EXIM_HAVE_OPENSSL_KEYLOG >- { >- BIO * bp = BIO_new_fp(debug_file, BIO_NOCLOSE); >- SSL_SESSION_print_keylog(bp, SSL_get_session(exim_client_ctx->ssl)); >- BIO_free(bp); >- } >-#endif >+ tls_dump_keylog(exim_client_ctx->ssl); > } > >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifndef DISABLE_TLS_RESUME > tls_client_resume_posthandshake(exim_client_ctx, tlsp); > #endif > >+#ifdef EXIM_HAVE_ALPN >+if (ob->tls_alpn) /* We requested. See what was negotiated. */ >+ { >+ const uschar * name; >+ unsigned len; >+ >+ SSL_get0_alpn_selected(exim_client_ctx->ssl, &name, &len); >+ if (len > 0) >+ { DEBUG(D_tls) debug_printf("ALPN negotiated %u: '%.*s'\n", len, (int)*name, name+1); } >+ else if (verify_check_given_host(CUSS &ob->hosts_require_alpn, host) == OK) >+ { >+ /* Would like to send a relevant fatal Alert, but OpenSSL has no API */ >+ tls_error(US"handshake", host, US"ALPN required but not negotiated", errstr); >+ return FALSE; >+ } >+ } >+#endif >+ > #ifdef SSL_get_extms_support > tlsp->ext_master_secret = SSL_get_extms_support(exim_client_ctx->ssl) == 1; > #endif >@@ -3433,9 +4164,9 @@ > size_t len = SSL_get_finished(exim_client_ctx->ssl, &c, 0); > int old_pool = store_pool; > >- SSL_get_finished(exim_client_ctx->ssl, s = store_get((int)len, TRUE), len); >+ SSL_get_finished(exim_client_ctx->ssl, s = store_get((int)len, GET_TAINTED), len); > store_pool = POOL_PERM; >- tlsp->channelbinding = b64encode_taint(CUS s, (int)len, TRUE); >+ tlsp->channelbinding = b64encode_taint(CUS s, (int)len, GET_TAINTED); > store_pool = old_pool; > DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p %p\n", tlsp->channelbinding, tlsp); > } >@@ -3453,17 +4184,18 @@ > static BOOL > tls_refill(unsigned lim) > { >+SSL * ssl = state_server.lib_state.lib_ssl; > int error; > int inbytes; > >-DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_ssl, >+DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl, > ssl_xfer_buffer, ssl_xfer_buffer_size); > > ERR_clear_error(); > if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout); >-inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer, >+inbytes = SSL_read(ssl, CS ssl_xfer_buffer, > MIN(ssl_xfer_buffer_size, lim)); >-error = SSL_get_error(server_ssl, inbytes); >+error = SSL_get_error(ssl, inbytes); > if (smtp_receive_timeout > 0) ALARM_CLR(0); > > if (had_command_timeout) /* set by signal handler */ >@@ -3487,8 +4219,8 @@ > case SSL_ERROR_ZERO_RETURN: > DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n"); > >- if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN) >- SSL_shutdown(server_ssl); >+ if (SSL_get_shutdown(ssl) == SSL_RECEIVED_SHUTDOWN) >+ SSL_shutdown(ssl); > > tls_close(NULL, TLS_NO_SHUTDOWN); > return FALSE; >@@ -3542,6 +4274,12 @@ > return ssl_xfer_buffer[ssl_xfer_buffer_lwm++]; > } > >+BOOL >+tls_hasc(void) >+{ >+return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm; >+} >+ > uschar * > tls_getbuf(unsigned * len) > { >@@ -3566,10 +4304,13 @@ > > > void >-tls_get_cache() >+tls_get_cache(unsigned lim) > { > #ifndef DISABLE_DKIM > int n = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm; >+debug_printf("tls_get_cache\n"); >+if (n > lim) >+ n = lim; > if (n > 0) > dkim_exim_verify_feed(ssl_xfer_buffer+ssl_xfer_buffer_lwm, n); > #endif >@@ -3577,9 +4318,10 @@ > > > BOOL >-tls_could_read(void) >+tls_could_getc(void) > { >-return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm || SSL_pending(server_ssl) > 0; >+return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm >+ || SSL_pending(state_server.lib_state.lib_ssl) > 0; > } > > >@@ -3602,7 +4344,8 @@ > int > tls_read(void * ct_ctx, uschar *buff, size_t len) > { >-SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl; >+SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl >+ : state_server.lib_state.lib_ssl; > int inbytes; > int error; > >@@ -3652,7 +4395,8 @@ > size_t olen = len; > int outbytes, error; > SSL * ssl = ct_ctx >- ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl; >+ ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl >+ : state_server.lib_state.lib_ssl; > static gstring * server_corked = NULL; > gstring ** corkedp = ct_ctx > ? &((exim_openssl_client_tls_ctx *)ct_ctx)->corked : &server_corked; >@@ -3671,7 +4415,7 @@ > a store reset there, so use POOL_PERM. */ > /* + if CHUNKING, cmds EHLO,MAIL,RCPT(s),BDAT */ > >-if ((more || corked)) >+if (more || corked) > { > if (!len) buff = US &error; /* dummy just so that string_catn is ok */ > >@@ -3716,9 +4460,16 @@ > return -1; > > case SSL_ERROR_SYSCALL: >- log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s", >- sender_fullhost ? sender_fullhost : US"<unknown>", >- strerror(errno)); >+ if (ct_ctx || errno != ECONNRESET || !f.smtp_in_quit) >+ log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s", >+ sender_fullhost ? sender_fullhost : US"<unknown>", >+ strerror(errno)); >+ else if (LOGGING(protocol_detail)) >+ log_write(0, LOG_MAIN, "[%s] after QUIT, client reset TCP before" >+ " SMTP response and TLS close\n", sender_host_address); >+ else >+ DEBUG(D_tls) debug_printf("[%s] SSL_write: after QUIT," >+ " client reset TCP before TLS close\n", sender_host_address); > return -1; > > default: >@@ -3731,6 +4482,32 @@ > > > >+/* >+Arguments: >+ ct_ctx client TLS context pointer, or NULL for the one global server context >+*/ >+ >+void >+tls_shutdown_wr(void * ct_ctx) >+{ >+exim_openssl_client_tls_ctx * o_ctx = ct_ctx; >+SSL ** sslp = o_ctx ? &o_ctx->ssl : (SSL **) &state_server.lib_state.lib_ssl; >+int * fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock; >+int rc; >+ >+if (*fdp < 0) return; /* TLS was not active */ >+ >+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(*sslp); >+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); >+ } >+} >+ > /************************************************* > * Close down a TLS session * > *************************************************/ >@@ -3741,7 +4518,8 @@ > > Arguments: > ct_ctx client TLS context pointer, or NULL for the one global server context >- shutdown 1 if TLS close-alert is to be sent, >+ do_shutdown 0 no data-flush or TLS close-alert >+ 1 if TLS close-alert is to be sent, > 2 if also response to be waited for > > Returns: nothing >@@ -3750,26 +4528,33 @@ > */ > > void >-tls_close(void * ct_ctx, int shutdown) >+tls_close(void * ct_ctx, int do_shutdown) > { > exim_openssl_client_tls_ctx * o_ctx = ct_ctx; >-SSL_CTX **ctxp = o_ctx ? &o_ctx->ctx : &server_ctx; >-SSL **sslp = o_ctx ? &o_ctx->ssl : &server_ssl; >-int *fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock; >+SSL ** sslp = o_ctx ? &o_ctx->ssl : (SSL **) &state_server.lib_state.lib_ssl; >+int * fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock; > > if (*fdp < 0) return; /* TLS was not active */ > >-if (shutdown) >+if (do_shutdown > TLS_NO_SHUTDOWN) > { > int rc; > DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n", >- shutdown > 1 ? " (with response-wait)" : ""); >+ do_shutdown > TLS_SHUTDOWN_NOWAIT ? " (with response-wait)" : ""); >+ >+ tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */ > >- if ( (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */ >- && shutdown > 1) >+ if ( ( do_shutdown >= TLS_SHUTDOWN_WONLY >+ || (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */ >+ ) >+ && do_shutdown > TLS_SHUTDOWN_NOWAIT >+ ) > { >+#ifdef EXIM_TCP_CORK >+ (void) setsockopt(*fdp, IPPROTO_TCP, EXIM_TCP_CORK, US &off, sizeof(off)); >+#endif > ALARM(2); >- rc = SSL_shutdown(*sslp); /* wait for response */ >+ rc = SSL_shutdown(*sslp); /* wait for response */ > ALARM_CLR(0); > } > >@@ -3783,25 +4568,23 @@ > if (!o_ctx) /* server side */ > { > #ifndef DISABLE_OCSP >- sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free); >- server_static_cbinfo->verify_stack = NULL; >+ sk_X509_pop_free(state_server.verify_stack, X509_free); >+ state_server.verify_stack = NULL; > #endif > > receive_getc = smtp_getc; > receive_getbuf = smtp_getbuf; > receive_get_cache = smtp_get_cache; >+ receive_hasc = smtp_hasc; > receive_ungetc = smtp_ungetc; > receive_feof = smtp_feof; > receive_ferror = smtp_ferror; >- receive_smtp_buffered = smtp_buffered; > tls_in.active.tls_ctx = NULL; > tls_in.sni = NULL; > /* Leave bits, peercert, cipher, peerdn, certificate_verified set, for logging */ > } > >-SSL_CTX_free(*ctxp); > SSL_free(*sslp); >-*ctxp = NULL; > *sslp = NULL; > *fdp = -1; > } >@@ -3822,8 +4605,8 @@ > uschar * > tls_validate_require_cipher(void) > { >-SSL_CTX *ctx; >-uschar *s, *expciphers, *err; >+SSL_CTX * ctx; >+uschar * expciphers, * err; > > tls_openssl_init(); > >@@ -3837,34 +4620,23 @@ > if (!(expciphers && *expciphers)) > return NULL; > >-/* normalisation ripped from above */ >-s = expciphers; >-while (*s != 0) { if (*s == '_') *s = '-'; s++; } >+normalise_ciphers(&expciphers, tls_require_ciphers); > > err = NULL; >- >-#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD >-if (!(ctx = SSL_CTX_new(TLS_server_method()))) >-#else >-if (!(ctx = SSL_CTX_new(SSLv23_server_method()))) >-#endif >+if (lib_ctx_new(&ctx, NULL, &err) == OK) > { >- ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); >- return string_sprintf("SSL_CTX_new() failed: %s", ssl_errstring); >- } >+ DEBUG(D_tls) >+ debug_printf("tls_require_ciphers expands to \"%s\"\n", expciphers); > >-DEBUG(D_tls) >- debug_printf("tls_require_ciphers expands to \"%s\"\n", expciphers); >+ if (!SSL_CTX_set_cipher_list(ctx, CS expciphers)) >+ { >+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); >+ err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed: %s", >+ expciphers, ssl_errstring); >+ } > >-if (!SSL_CTX_set_cipher_list(ctx, CS expciphers)) >- { >- ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); >- err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed: %s", >- expciphers, ssl_errstring); >+ SSL_CTX_free(ctx); > } >- >-SSL_CTX_free(ctx); >- > return err; > } > >@@ -3886,21 +4658,22 @@ > will change, so we can more usefully assist with version diagnosis by also > reporting the build date. > >-Arguments: a FILE* to print the results to >-Returns: nothing >+Arguments: string to append to >+Returns: string > */ > >-void >-tls_version_report(FILE *f) >+gstring * >+tls_version_report(gstring * g) > { >-fprintf(f, "Library version: OpenSSL: Compile: %s\n" >- " Runtime: %s\n" >- " : %s\n", >- OPENSSL_VERSION_TEXT, >- SSLeay_version(SSLEAY_VERSION), >- SSLeay_version(SSLEAY_BUILT_ON)); >-/* third line is 38 characters for the %s and the line is 73 chars long; >-the OpenSSL output includes a "built on: " prefix already. */ >+return string_fmt_append(g, >+ "Library version: OpenSSL: Compile: %s\n" >+ " Runtime: %s\n" >+ " : %s\n", >+ OPENSSL_VERSION_TEXT, >+ SSLeay_version(SSLEAY_VERSION), >+ SSLeay_version(SSLEAY_BUILT_ON)); >+ /* third line is 38 characters for the %s and the line is 73 chars long; >+ the OpenSSL output includes a "built on: " prefix already. */ > } > > >@@ -4054,7 +4827,6 @@ > { > long result, item; > uschar * exp, * end; >-uschar keep_c; > BOOL adding, item_parsed; > > /* Server: send no (<= TLS1.2) session tickets */ >@@ -4096,11 +4868,8 @@ > return FALSE; > } > adding = *s++ == '+'; >- for (end = s; (*end != '\0') && !isspace(*end); ++end) /**/ ; >- keep_c = *end; >- *end = '\0'; >- item_parsed = tls_openssl_one_option_parse(s, &item); >- *end = keep_c; >+ for (end = s; *end && !isspace(*end); ) end++; >+ item_parsed = tls_openssl_one_option_parse(string_copyn(s, end-s), &item); > if (!item_parsed) > { > DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"\n", s); >diff -ur exim.orig/src/transport.c exim/src/transport.c >--- exim.orig/src/transport.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/transport.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* General functions concerned with transportation, and generic options for all >@@ -253,7 +253,6 @@ > > for(;;) > { >- fd_set fds; > /* This code makes use of alarm() in order to implement the timeout. This > isn't a very tidy way of doing things. Using non-blocking I/O with select() > provides a neater approach. However, I don't know how to do this when TLS is >@@ -281,8 +280,7 @@ > if (rc >= 0 || errno != ENOTCONN || connretry <= 0) > break; > >- FD_ZERO(&fds); FD_SET(fd, &fds); >- select(fd+1, NULL, &fds, NULL, NULL); /* could set timout? */ >+ poll_one_fd(fd, POLLOUT, -1); /* could set timeout? retval check? */ > connretry--; > } > >@@ -653,7 +651,7 @@ > > for (ppp = *pdlist; ppp; ppp = ppp->next) if (p == ppp->ptr) return TRUE; > >-ppp = store_get(sizeof(struct aci), FALSE); >+ppp = store_get(sizeof(struct aci), GET_UNTAINTED); > ppp->next = *pdlist; > *pdlist = ppp; > ppp->ptr = p; >@@ -677,7 +675,7 @@ > > /* Remember what we have output, and output it. */ > >-ppp = store_get(sizeof(struct aci), FALSE); >+ppp = store_get(sizeof(struct aci), GET_UNTAINTED); > ppp->next = *pplist; > *pplist = ppp; > ppp->ptr = pp; >@@ -784,7 +782,7 @@ > /* Header removed */ > > else >- DEBUG(D_transport) debug_printf("removed header line:\n%s---\n", h->text); >+ DEBUG(D_transport) debug_printf("removed header line:\n %s---\n", h->text); > } > > /* Add on any address-specific headers. If there are multiple addresses, >@@ -800,8 +798,8 @@ > > if (addr) > { >- header_line *hprev = addr->prop.extra_headers; >- header_line *hnext, * h; >+ header_line * hprev = addr->prop.extra_headers, * hnext, * h; >+ > for (int i = 0; i < 2; i++) > for (h = hprev, hprev = NULL; h; h = hnext) > { >@@ -812,7 +810,7 @@ > { > if (!sendfn(tctx, h->text, h->slen)) return FALSE; > DEBUG(D_transport) >- debug_printf("added header line(s):\n%s---\n", h->text); >+ debug_printf("added header line(s):\n %s---\n", h->text); > } > } > } >@@ -840,7 +838,7 @@ > return FALSE; > DEBUG(D_transport) > { >- debug_printf("added header line:\n%s", s); >+ debug_printf("added header line:\n %s", s); > if (s[len-1] != '\n') debug_printf("\n"); > debug_printf("---\n"); > } >@@ -886,7 +884,7 @@ > > Arguments: > tctx >- (fd, msg) Either and fd, to write the message to, >+ (fd, msg) Either an fd, to write the message to, > or a string: if null write message to allocated space > otherwire take content as headers. > addr (chain of) addresses (for extra headers), or NULL; >@@ -905,6 +903,7 @@ > add_delivery_date if TRUE, add a "delivery-date" header > use_crlf if TRUE, turn NL into CR LF > end_dot if TRUE, send a terminating "." line at the end >+ no_flush if TRUE, do not flush at end > no_headers if TRUE, omit the headers > no_body if TRUE, omit the body > check_string a string to check for at the start of lines, or NULL >@@ -1156,13 +1155,18 @@ > > /* If requested, add a terminating "." line (SMTP output). */ > >-if (tctx->options & topt_end_dot && !write_chunk(tctx, US".\n", 2)) >- return FALSE; >+if (tctx->options & topt_end_dot) >+ { >+ smtp_debug_cmd(US".", 0); >+ if (!write_chunk(tctx, US".\n", 2)) >+ return FALSE; >+ } > > /* Write out any remaining data in the buffer before returning. */ > >-return (len = chunk_ptr - deliver_out_buffer) <= 0 || >- transport_write_block(tctx, deliver_out_buffer, len, FALSE); >+return (len = chunk_ptr - deliver_out_buffer) <= 0 >+ || transport_write_block(tctx, deliver_out_buffer, len, >+ !!(tctx->options & topt_no_flush)); > } > > >@@ -1260,7 +1264,7 @@ > > tctx->u.fd = fd_write; > tctx->check_string = tctx->escape_string = NULL; >- tctx->options &= ~(topt_use_crlf | topt_end_dot | topt_use_bdat); >+ tctx->options &= ~(topt_use_crlf | topt_end_dot | topt_use_bdat | topt_no_flush); > > rc = internal_transport_write_message(tctx, size_limit); > >@@ -1396,11 +1400,10 @@ > yield = FALSE; > } > else if (!ok) >- { >+ { /* Try to drain the pipe; read fails are don't care */ > int dummy = read(pfd[pipe_read], (void *)&save_errno, sizeof(int)); > dummy = read(pfd[pipe_read], (void *)&tctx->addr->more_errno, sizeof(int)); > dummy = read(pfd[pipe_read], (void *)&tctx->addr->delivery_time, sizeof(struct timeval)); >- dummy = dummy; /* compiler quietening */ > yield = FALSE; > } > } >@@ -1427,7 +1430,7 @@ > ? !write_chunk(tctx, US".\n", 2) > : !write_chunk(tctx, US"\n.\n", 3) > ) ) >- yield = FALSE; >+ { smtp_debug_cmd(US".", 0); yield = FALSE; } > > /* Write out any remaining data in the buffer. */ > >@@ -1522,7 +1525,7 @@ > > if (!(host_record = dbfn_read(dbm_file, host->name))) > { >- host_record = store_get(sizeof(dbdata_wait) + MESSAGE_ID_LENGTH, FALSE); >+ host_record = store_get(sizeof(dbdata_wait) + MESSAGE_ID_LENGTH, GET_UNTAINTED); > host_record->count = host_record->sequence = 0; > } > >@@ -1571,7 +1574,7 @@ > { > sprintf(CS buffer, "%.200s:%d", host->name, host_record->sequence); > dbfn_write(dbm_file, buffer, host_record, sizeof(dbdata_wait) + host_length); >-#ifdef EXPERIMENTAL_QUEUE_RAMP >+#ifndef DISABLE_QUEUE_RAMP > if (f.queue_2stage && queue_fast_ramp && !queue_run_in_order) > queue_notify_daemon(message_id); > #endif >@@ -1586,7 +1589,7 @@ > else > { > dbdata_wait *newr = >- store_get(sizeof(dbdata_wait) + host_length + MESSAGE_ID_LENGTH, FALSE); >+ store_get(sizeof(dbdata_wait) + host_length + MESSAGE_ID_LENGTH, GET_UNTAINTED); > memcpy(newr, host_record, sizeof(dbdata_wait) + host_length); > host_record = newr; > } >@@ -1600,7 +1603,8 @@ > /* Update the database */ > > dbfn_write(dbm_file, host->name, host_record, sizeof(dbdata_wait) + host_length); >- DEBUG(D_transport) debug_printf("added to list for %s\n", host->name); >+ DEBUG(D_transport) debug_printf("added %.*s to queue for %s\n", >+ MESSAGE_ID_LENGTH, message_id, host->name); > } > > /* All now done */ >@@ -1628,7 +1632,6 @@ > local_message_max maximum number of messages down one connection > as set by the caller transport > new_message_id set to the message id of a waiting message >- more set TRUE if there are yet more messages waiting > oicf_func function to call to validate if it is ok to send > to this message_id from the current instance. > oicf_data opaque data for oicf_func >@@ -1644,7 +1647,7 @@ > > BOOL > transport_check_waiting(const uschar *transport_name, const uschar *hostname, >- int local_message_max, uschar *new_message_id, BOOL *more, oicf oicf_func, void *oicf_data) >+ int local_message_max, uschar *new_message_id, oicf oicf_func, void *oicf_data) > { > dbdata_wait *host_record; > int host_length; >@@ -1654,8 +1657,6 @@ > int i; > struct stat statbuf; > >-*more = FALSE; >- > DEBUG(D_transport) > { > debug_printf("transport_check_waiting entered\n"); >@@ -1722,7 +1723,7 @@ > > /* create an array to read entire message queue into memory for processing */ > >- msgq = store_get(sizeof(msgq_t) * host_record->count, FALSE); >+ msgq = store_get(sizeof(msgq_t) * host_record->count, GET_UNTAINTED); > msgq_count = host_record->count; > msgq_actual = msgq_count; > >@@ -1860,9 +1861,7 @@ > if (host_length > 0) > { > host_record->count = host_length/MESSAGE_ID_LENGTH; >- > dbfn_write(dbm_file, hostname, host_record, (int)sizeof(dbdata_wait) + host_length); >- *more = TRUE; > } > > dbfn_close(dbm_file); >@@ -1883,9 +1882,21 @@ > transport_do_pass_socket(const uschar *transport_name, const uschar *hostname, > const uschar *hostaddress, uschar *id, int socket_fd) > { >-int i = 22; >+int i = 13; > const uschar **argv; > >+#ifndef DISABLE_TLS >+if (smtp_peer_options & OPTION_TLS) i += 6; >+#endif >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+if (continue_limit_mail || continue_limit_rcpt || continue_limit_rcptdom) >+ i += 4; >+#endif >+if (queue_run_pid != (pid_t)0) i += 3; >+#ifdef SUPPORT_SOCKS >+if (proxy_session) i += 5; >+#endif >+ > /* Set up the calling arguments; use the standard function for the basics, > but we have a number of extras that may be added. */ > >@@ -1919,6 +1930,16 @@ > argv[i++] = US"-MCT"; > #endif > >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+if (continue_limit_rcpt || continue_limit_rcptdom) >+ { >+ argv[i++] = US"-MCL"; >+ argv[i++] = string_sprintf("%u", continue_limit_mail); >+ argv[i++] = string_sprintf("%u", continue_limit_rcpt); >+ argv[i++] = string_sprintf("%u", continue_limit_rcptdom); >+ } >+#endif >+ > if (queue_run_pid != (pid_t)0) > { > argv[i++] = US"-MCQ"; >@@ -1926,6 +1947,17 @@ > argv[i++] = string_sprintf("%d", queue_run_pipe); > } > >+#ifdef SUPPORT_SOCKS >+if (proxy_session) >+ { >+ argv[i++] = US"-MCp"; >+ argv[i++] = proxy_local_address; >+ argv[i++] = string_sprintf("%d", proxy_local_port); >+ argv[i++] = proxy_external_address; >+ argv[i++] = string_sprintf("%d", proxy_external_port); >+ } >+#endif >+ > argv[i++] = US"-MC"; > argv[i++] = US transport_name; > argv[i++] = US hostname; >@@ -1944,6 +1976,7 @@ > > DEBUG(D_exec) debug_print_argv(argv); > exim_nullstd(); /* Ensure std{out,err} exist */ >+/* argv[0] should be untainted, from child_exec_exim() */ > execv(CS argv[0], (char *const *)argv); > > DEBUG(D_any) debug_printf("execv failed: %s\n", strerror(errno)); >@@ -1968,13 +2001,23 @@ > > BOOL > transport_pass_socket(const uschar *transport_name, const uschar *hostname, >- const uschar *hostaddress, uschar *id, int socket_fd) >+ const uschar *hostaddress, uschar *id, int socket_fd >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ , unsigned peer_limit_mail, unsigned peer_limit_rcpt, unsigned peer_limit_rcptdom >+#endif >+ ) > { > pid_t pid; > int status; > > DEBUG(D_transport) debug_printf("transport_pass_socket entered\n"); > >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+continue_limit_mail = peer_limit_mail; >+continue_limit_rcpt = peer_limit_rcpt; >+continue_limit_rcptdom = peer_limit_rcptdom; >+#endif >+ > if ((pid = exim_fork(US"continued-transport-interproc")) == 0) > { > /* Disconnect entirely from the parent process. If we are running in the >@@ -2010,6 +2053,31 @@ > > > >+/* Enforce all args untainted, for consistency with a router-sourced pipe >+command, where (because the whole line is passed as one to the tpt) a >+tainted arg taints the executable name. It's unclear also that letting an >+attacker supply command arguments is wise. */ >+ >+static BOOL >+arg_is_tainted(const uschar * s, int argn, address_item * addr, >+ const uschar * etext, uschar ** errptr) >+{ >+if (is_tainted(s)) >+ { >+ uschar * msg = string_sprintf("Tainted arg %d for %s command: '%s'", >+ argn, etext, s); >+ if (addr) >+ { >+ addr->transport_return = FAIL; >+ addr->message = msg; >+ } >+ else *errptr = msg; >+ return TRUE; >+ } >+return FALSE; >+} >+ >+ > /************************************************* > * Set up direct (non-shell) command * > *************************************************/ >@@ -2027,6 +2095,7 @@ > expand_failed error value to set if expansion fails; not relevant if > addr == NULL > addr chain of addresses, or NULL >+ allow_tainted_args as it says; used for ${run} > etext text for use in error messages > errptr where to put error message if addr is NULL; > otherwise it is put in the first address >@@ -2036,15 +2105,12 @@ > */ > > BOOL >-transport_set_up_command(const uschar ***argvptr, uschar *cmd, >- BOOL expand_arguments, int expand_failed, address_item *addr, >- uschar *etext, uschar **errptr) >+transport_set_up_command(const uschar *** argvptr, const uschar * cmd, >+ BOOL expand_arguments, int expand_failed, address_item * addr, >+ BOOL allow_tainted_args, const uschar * etext, uschar ** errptr) > { >-const uschar **argv; >-uschar *s, *ss; >-int address_count = 0; >-int argcount = 0; >-int max_args; >+const uschar ** argv, * s; >+int address_count = 0, argcount = 0, max_args; > > /* Get store in which to build an argument list. Count the number of addresses > supplied, and allow for that many arguments, plus an additional 60, which >@@ -2053,7 +2119,7 @@ > > for (address_item * ad = addr; ad; ad = ad->next) address_count++; > max_args = address_count + 60; >-*argvptr = argv = store_get((max_args+1)*sizeof(uschar *), FALSE); >+*argvptr = argv = store_get((max_args+1)*sizeof(uschar *), GET_UNTAINTED); > > /* Split the command up into arguments terminated by white space. Lose > trailing space at the start and end. Double-quoted arguments can contain \\ and >@@ -2061,33 +2127,30 @@ > arguments are verbatim. Copy each argument into a new string. */ > > s = cmd; >-while (isspace(*s)) s++; >+Uskip_whitespace(&s); > >-for (; *s != 0 && argcount < max_args; argcount++) >+for (; *s && argcount < max_args; argcount++) > { > if (*s == '\'') > { >- ss = s + 1; >- while (*ss != 0 && *ss != '\'') ss++; >- argv[argcount] = ss = store_get(ss - s++, is_tainted(cmd)); >- while (*s != 0 && *s != '\'') *ss++ = *s++; >- if (*s != 0) s++; >- *ss++ = 0; >+ int n = Ustrcspn(++s, "'"); >+ argv[argcount] = string_copyn(s, n); >+ if (*(s += n) == '\'') s++; > } > else > argv[argcount] = string_dequote(CUSS &s); >- while (isspace(*s)) s++; >+ Uskip_whitespace(&s); > } > >-argv[argcount] = US 0; >+argv[argcount] = NULL; > > /* If *s != 0 we have run out of argument slots. */ > >-if (*s != 0) >+if (*s) > { > uschar *msg = string_sprintf("Too many arguments in command \"%s\" in " > "%s", cmd, etext); >- if (addr != NULL) >+ if (addr) > { > addr->transport_return = FAIL; > addr->message = msg; >@@ -2121,16 +2184,14 @@ > > if (expand_arguments) > { >- BOOL allow_dollar_recipients = addr != NULL && >- addr->parent != NULL && >- Ustrcmp(addr->parent->address, "system-filter") == 0; >+ BOOL allow_dollar_recipients = addr && addr->parent >+ && Ustrcmp(addr->parent->address, "system-filter") == 0; > >- for (int i = 0; argv[i] != US 0; i++) >+ for (int i = 0; argv[i]; i++) > { >- > /* Handle special fudge for passing an address list */ > >- if (addr != NULL && >+ if (addr && > (Ustrcmp(argv[i], "$pipe_addresses") == 0 || > Ustrcmp(argv[i], "${pipe_addresses}") == 0)) > { >@@ -2151,6 +2212,16 @@ > > for (address_item * ad = addr; ad; ad = ad->next) > { >+ /* $pipe_addresses is spefically not checked for taint, because there is >+ a testcase (321) depending on it. It's unclear if the exact thing being >+ done really needs to be legitimate, though I suspect it reflects an >+ actual use-case that showed up a bug. >+ This is a hole in the taint-pretection, mitigated only in that >+ shell-syntax metachars cannot be injected via this route. */ >+ >+ DEBUG(D_transport) if (is_tainted(ad->address)) >+ debug_printf("tainted element '%s' from $pipe_addresses\n", ad->address); >+ > argv[i++] = ad->address; > argcount++; > } >@@ -2162,14 +2233,13 @@ > > /* Handle special case of $address_pipe when af_force_command is set */ > >- else if (addr != NULL && testflag(addr,af_force_command) && >+ else if (addr && testflag(addr,af_force_command) && > (Ustrcmp(argv[i], "$address_pipe") == 0 || > Ustrcmp(argv[i], "${address_pipe}") == 0)) > { > int address_pipe_argcount = 0; > int address_pipe_max_args; > uschar **address_pipe_argv; >- BOOL tainted; > > /* We can never have more then the argv we will be loading into */ > address_pipe_max_args = max_args - argcount + 1; >@@ -2178,13 +2248,12 @@ > debug_printf("address_pipe_max_args=%d\n", address_pipe_max_args); > > /* We allocate an additional for (uschar *)0 */ >- address_pipe_argv = store_get((address_pipe_max_args+1)*sizeof(uschar *), FALSE); >+ address_pipe_argv = store_get((address_pipe_max_args+1)*sizeof(uschar *), GET_UNTAINTED); > > /* +1 because addr->local_part[0] == '|' since af_force_command is set */ > s = expand_string(addr->local_part + 1); >- tainted = is_tainted(s); > >- if (s == NULL || *s == '\0') >+ if (!s || !*s) > { > addr->transport_return = FAIL; > addr->message = string_sprintf("Expansion of \"%s\" " >@@ -2193,32 +2262,29 @@ > return FALSE; > } > >- while (isspace(*s)) s++; /* strip leading space */ >+ Uskip_whitespace(&s); /* strip leading space */ > >- while (*s != 0 && address_pipe_argcount < address_pipe_max_args) >+ while (*s && address_pipe_argcount < address_pipe_max_args) > { > if (*s == '\'') >- { >- ss = s + 1; >- while (*ss != 0 && *ss != '\'') ss++; >- address_pipe_argv[address_pipe_argcount++] = ss = store_get(ss - s++, tainted); >- while (*s != 0 && *s != '\'') *ss++ = *s++; >- if (*s != 0) s++; >- *ss++ = 0; >- } >- else address_pipe_argv[address_pipe_argcount++] = >- string_copy(string_dequote(CUSS &s)); >- while (isspace(*s)) s++; /* strip space after arg */ >+ { >+ int n = Ustrcspn(++s, "'"); >+ argv[argcount] = string_copyn(s, n); >+ if (*(s += n) == '\'') s++; >+ } >+ else >+ address_pipe_argv[address_pipe_argcount++] = string_dequote(CUSS &s); >+ Uskip_whitespace(&s); /* strip space after arg */ > } > >- address_pipe_argv[address_pipe_argcount] = US 0; >+ address_pipe_argv[address_pipe_argcount] = NULL; > > /* If *s != 0 we have run out of argument slots. */ >- if (*s != 0) >+ if (*s) > { > uschar *msg = string_sprintf("Too many arguments in $address_pipe " > "\"%s\" in %s", addr->local_part + 1, etext); >- if (addr != NULL) >+ if (addr) > { > addr->transport_return = FAIL; > addr->message = msg; >@@ -2228,8 +2294,9 @@ > } > > /* address_pipe_argcount - 1 >- * because we are replacing $address_pipe in the argument list >- * with the first thing it expands to */ >+ because we are replacing $address_pipe in the argument list >+ with the first thing it expands to */ >+ > if (argcount + address_pipe_argcount - 1 > max_args) > { > addr->transport_return = FAIL; >@@ -2239,12 +2306,12 @@ > } > > /* If we are not just able to replace the slot that contained >- * $address_pipe (address_pipe_argcount == 1) >- * We have to move the existing argv by address_pipe_argcount - 1 >- * Visually if address_pipe_argcount == 2: >- * [argv 0][argv 1][argv 2($address_pipe)][argv 3][0] >- * [argv 0][argv 1][ap_arg0][ap_arg1][old argv 3][0] >- */ >+ $address_pipe (address_pipe_argcount == 1) >+ We have to move the existing argv by address_pipe_argcount - 1 >+ Visually if address_pipe_argcount == 2: >+ [argv 0][argv 1][argv 2($address_pipe)][argv 3][0] >+ [argv 0][argv 1][ap_arg0][ap_arg1][old argv 3][0] */ >+ > if (address_pipe_argcount > 1) > memmove( > /* current position + additional args */ >@@ -2256,15 +2323,16 @@ > ); > > /* Now we fill in the slots we just moved argv out of >- * [argv 0][argv 1][argv 2=pipeargv[0]][argv 3=pipeargv[1]][old argv 3][0] >- */ >+ [argv 0][argv 1][argv 2=pipeargv[0]][argv 3=pipeargv[1]][old argv 3][0] */ >+ > for (int address_pipe_i = 0; >- address_pipe_argv[address_pipe_i] != US 0; >- address_pipe_i++) >- { >- argv[i++] = address_pipe_argv[address_pipe_i]; >- argcount++; >- } >+ address_pipe_argv[address_pipe_i]; >+ address_pipe_i++, argcount++) >+ { >+ uschar * s = address_pipe_argv[address_pipe_i]; >+ if (arg_is_tainted(s, i, addr, etext, errptr)) return FALSE; >+ argv[i++] = s; >+ } > > /* Subtract one since we replace $address_pipe */ > argcount--; >@@ -2293,6 +2361,17 @@ > else *errptr = msg; > return FALSE; > } >+ >+ if ( f.running_in_test_harness && is_tainted(expanded_arg) >+ && Ustrcmp(etext, "queryprogram router") == 0) >+ { /* hack, would be good to not need it */ >+ DEBUG(D_transport) >+ debug_printf("SPECIFIC TESTSUITE EXEMPTION: tainted arg '%s'\n", >+ expanded_arg); >+ } >+ else if ( !allow_tainted_args >+ && arg_is_tainted(expanded_arg, i, addr, etext, errptr)) >+ return FALSE; > argv[i] = expanded_arg; > } > } >@@ -2300,14 +2379,30 @@ > DEBUG(D_transport) > { > debug_printf("direct command after expansion:\n"); >- for (int i = 0; argv[i] != US 0; i++) >- debug_printf(" argv[%d] = %s\n", i, string_printing(argv[i])); >+ for (int i = 0; argv[i]; i++) >+ { >+ debug_printf(" argv[%d] = '%s'\n", i, string_printing(argv[i])); >+ debug_print_taint(argv[i]); >+ } > } > } > > return TRUE; > } > >+ >+ >+/* For error messages, a string describing the config location associated >+with current processing. NULL if we are not in a transport. */ >+/* Name only, for now */ >+ >+uschar * >+transport_current_name(void) >+{ >+if (!transport_name) return NULL; >+return string_sprintf(" (transport %s, %s %d)", transport_name, driver_srcfile, driver_srcline); >+} >+ > #endif /*!MACRO_PREDEF*/ > /* vi: aw ai sw=2 > */ >diff -ur exim.orig/src/transports/appendfile.c exim/src/transports/appendfile.c >--- exim.orig/src/transports/appendfile.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/transports/appendfile.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >-/* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim maintainers 2020 */ >+/* Copyright (c) The Exim maintainers 2020 - 2022 */ >+/* Copyright (c) University of Cambridge 1995 - 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -112,70 +112,27 @@ > /* Default private options block for the appendfile transport. */ > > appendfile_transport_options_block appendfile_transport_option_defaults = { >- NULL, /* filename */ >- NULL, /* dirname */ >- US"q${base62:$tod_epoch}-$inode", /* dirfilename */ >- NULL, /* message_prefix (default reset in init if not bsmtp) */ >- NULL, /* message_suffix (ditto) */ >- US"anywhere", /* create_file_string (string value for create_file) */ >- NULL, /* quota */ >- NULL, /* quota_directory */ >- NULL, /* quota_filecount */ >- NULL, /* quota_size_regex */ >- NULL, /* quota_warn_threshold */ >- NULL, /* mailbox_size_string */ >- NULL, /* mailbox_filecount_string */ >- NULL, /* expand_maildir_use_size_file */ >- US"^(?:cur|new|\\..*)$", /* maildir_dir_regex */ >- NULL, /* maildir_tag */ >- NULL, /* maildirfolder_create_regex */ >- NULL, /* mailstore_prefix */ >- NULL, /* mailstore_suffix */ >- NULL, /* check_string (default changed for non-bsmtp file)*/ >- NULL, /* escape_string (ditto) */ >- NULL, /* file_format */ >- 0, /* quota_value */ >- 0, /* quota_warn_threshold_value */ >- -1, /* mailbox_size_value */ >- -1, /* mailbox_filecount_value */ >- 0, /* quota_filecount_value */ >- APPENDFILE_MODE, /* mode */ >- APPENDFILE_DIRECTORY_MODE, /* dirmode */ >- APPENDFILE_LOCKFILE_MODE, /* lockfile_mode */ >- 30*60, /* lockfile_timeout */ >- 0, /* lock_fcntl_timeout */ >- 0, /* lock_flock_timeout */ >- 10, /* lock_retries */ >- 3, /* lock_interval */ >- 10, /* maildir_retries */ >- create_anywhere,/* create_file */ >- 0, /* options */ >- FALSE, /* allow_fifo */ >- FALSE, /* allow_symlink */ >- FALSE, /* check_group */ >- TRUE, /* check_owner */ >- TRUE, /* create_directory */ >- FALSE, /* notify_comsat */ >- TRUE, /* use_lockfile */ >- FALSE, /* set_use_lockfile */ >- TRUE, /* use_fcntl */ >- FALSE, /* set_use_fcntl */ >- FALSE, /* use_flock */ >- FALSE, /* set_use_flock */ >- FALSE, /* use_mbx_lock */ >- FALSE, /* set_use_mbx_lock */ >- FALSE, /* use_bsmtp */ >- FALSE, /* use_crlf */ >- FALSE, /* file_must_exist */ >- TRUE, /* mode_fail_narrower */ >- FALSE, /* maildir_format */ >- FALSE, /* maildir_use_size_file */ >- FALSE, /* mailstore_format */ >- FALSE, /* mbx_format */ >- FALSE, /* quota_warn_threshold_is_percent */ >- TRUE, /* quota_is_inclusive */ >- FALSE, /* quota_no_check */ >- FALSE /* quota_filecount_no_check */ >+ /* all non-mentioned members zero/null/false */ >+ .dirfilename = US"q${base62:$tod_epoch}-$inode", >+ .create_file_string = US"anywhere", >+ .maildir_dir_regex = US"^(?:cur|new|\\..*)$", >+ .mailbox_size_value = -1, >+ .mailbox_filecount_value = -1, >+ .mode = APPENDFILE_MODE, >+ .dirmode = APPENDFILE_DIRECTORY_MODE, >+ .lockfile_mode = APPENDFILE_LOCKFILE_MODE, >+ .lockfile_timeout = 30*60, >+ .lock_retries = 10, >+ .lock_interval = 3, >+ .maildir_retries = 10, >+ .create_file = create_anywhere, >+ .check_owner = TRUE, >+ .create_directory = TRUE, >+ .notify_comsat = FALSE, >+ .use_lockfile = TRUE, >+ .use_fcntl = TRUE, >+ .mode_fail_narrower = TRUE, >+ .quota_is_inclusive = TRUE, > }; > > >@@ -226,11 +183,6 @@ > uschar *q = ob->quota; > double default_value = 0.0; > >-addrlist = addrlist; /* Keep picky compilers happy */ >-dummy = dummy; >-uid = uid; >-gid = gid; >- > if (ob->expand_maildir_use_size_file) > ob->maildir_use_size_file = expand_check_condition(ob->expand_maildir_use_size_file, > US"`maildir_use_size_file` in transport", tblock->name); >@@ -240,17 +192,15 @@ > > for (int i = 0; i < 5; i++) > { >- double d; >+ double d = default_value; > int no_check = 0; > uschar *which = NULL; > >- if (q == NULL) d = default_value; >- else >+ if (q) > { >- uschar *rest; >- uschar *s = expand_string(q); >+ uschar * rest, * s; > >- if (!s) >+ if (!(s = expand_string(q))) > { > *errmsg = string_sprintf("Expansion of \"%s\" in %s transport failed: " > "%s", q, tblock->name, expand_string_message); >@@ -320,8 +270,8 @@ > break; > > case 2: >- if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4) >- which = US"quota_warn_threshold"; >+ if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4) >+ which = US"quota_warn_threshold"; > ob->quota_warn_threshold_value = (off_t)d; > q = ob->mailbox_size_string; > default_value = -1.0; >@@ -367,6 +317,7 @@ > { > appendfile_transport_options_block *ob = > (appendfile_transport_options_block *)(tblock->options_block); >+uschar * s; > > /* Set up the setup entry point, to be called in the privileged state */ > >@@ -465,20 +416,17 @@ > /* If "create_file" is set, check that a valid option is given, and set the > integer variable. */ > >-if (ob->create_file_string) >+if ((s = ob->create_file_string ) && *s) > { >- int value = 0; >- if (Ustrcmp(ob->create_file_string, "anywhere") == 0) >- value = create_anywhere; >- else if (Ustrcmp(ob->create_file_string, "belowhome") == 0) >- value = create_belowhome; >- else if (Ustrcmp(ob->create_file_string, "inhome") == 0) >- value = create_inhome; >+ int val = 0; >+ if (Ustrcmp(s, "anywhere") == 0) val = create_anywhere; >+ else if (*s == '/' || Ustrcmp(s, "belowhome") == 0) val = create_belowhome; >+ else if (Ustrcmp(s, "inhome") == 0) val = create_inhome; > else > log_write(0, LOG_PANIC_DIE|LOG_CONFIG, >- "invalid value given for \"file_create\" for the %s transport: %s", >- tblock->name, ob->create_file_string); >- ob->create_file = value; >+ "invalid value given for \"create_file\" for the %s transport: '%s'", >+ tblock->name, s); >+ ob->create_file = val; > } > > /* If quota_warn_threshold is set, set up default for warn_message. It may >@@ -649,7 +597,8 @@ > > /* Search the formats for a match */ > >-while ((s = string_nextinlist(&format,&sep,big_buffer,big_buffer_size))) >+/* not expanded so cannot be tainted */ >+while ((s = string_nextinlist(&format, &sep, big_buffer, big_buffer_size))) > { > int slen = Ustrlen(s); > BOOL match = len >= slen && Ustrncmp(data, s, slen) == 0; >@@ -702,14 +651,14 @@ > Arguments: > dirname the name of the directory > countptr where to add the file count (because this function recurses) >- regex a compiled regex to get the size from a name >+ re a compiled regex to get the size from a name > > Returns: the sum of the sizes of the stattable files > zero if the directory cannot be opened > */ > > off_t >-check_dir_size(const uschar * dirname, int *countptr, const pcre *regex) >+check_dir_size(const uschar * dirname, int * countptr, const pcre2_code * re) > { > DIR *dir; > off_t sum = 0; >@@ -728,14 +677,18 @@ > > /* If there's a regex, try to find the size using it */ > >- if (regex) >+ if (re) > { >- int ovector[6]; >- if (pcre_exec(regex, NULL, CS name, Ustrlen(name), 0, 0, ovector,6) >= 2) >+ pcre2_match_data * md = pcre2_match_data_create(2, pcre_gen_ctx); >+ int rc = pcre2_match(re, (PCRE2_SPTR)name, PCRE2_ZERO_TERMINATED, >+ 0, 0, md, pcre_mtc_ctx); >+ PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md); >+ if ( rc >= 0 >+ && (rc = pcre2_get_ovector_count(md)) >= 2) > { > uschar *endptr; >- off_t size = (off_t)Ustrtod(name + ovector[2], &endptr); >- if (endptr == name + ovector[3]) >+ off_t size = (off_t)Ustrtod(name + ovec[2], &endptr); >+ if (endptr == name + ovec[3]) > { > sum += size; > DEBUG(D_transport) >@@ -762,7 +715,7 @@ > if ((statbuf.st_mode & S_IFMT) == S_IFREG) > sum += statbuf.st_size / statbuf.st_nlink; > else if ((statbuf.st_mode & S_IFMT) == S_IFDIR) >- sum += check_dir_size(path, &count, regex); >+ sum += check_dir_size(path, &count, re); > } > > closedir(dir); >@@ -940,28 +893,28 @@ > Arguments: > filename the file name > create_file the ob->create_file option >+ deliver_dir the delivery directory > > Returns: TRUE if creation is permitted > */ > > static BOOL >-check_creation(uschar *filename, int create_file) >+check_creation(uschar *filename, int create_file, const uschar * deliver_dir) > { > BOOL yield = TRUE; > >-if (deliver_home && create_file != create_anywhere) >+if (deliver_dir && create_file != create_anywhere) > { >- int len = Ustrlen(deliver_home); >+ int len = Ustrlen(deliver_dir); > uschar *file = filename; > > while (file[0] == '/' && file[1] == '/') file++; >- if (Ustrncmp(file, deliver_home, len) != 0 || file[len] != '/' || >- ( Ustrchr(file+len+2, '/') != NULL && >- ( >- create_file != create_belowhome || >- Ustrstr(file+len, "/../") != NULL >- ) >- ) >+ if ( Ustrncmp(file, deliver_dir, len) != 0 >+ || file[len] != '/' >+ || Ustrchr(file+len+2, '/') != NULL >+ && ( create_file != create_belowhome >+ || Ustrstr(file+len, "/../") != NULL >+ ) > ) yield = FALSE; > > /* If yield is TRUE, the file name starts with the home directory, and does >@@ -999,10 +952,10 @@ > if (rp) > { > uschar hdbuffer[PATH_MAX+1]; >- uschar *rph = deliver_home; >+ const uschar * rph = deliver_dir; > int rlen = Ustrlen(big_buffer); > >- if ((rp = US realpath(CS deliver_home, CS hdbuffer))) >+ if ((rp = US realpath(CS deliver_dir, CS hdbuffer))) > { > rph = hdbuffer; > len = Ustrlen(rph); >@@ -1013,7 +966,7 @@ > { > yield = FALSE; > DEBUG(D_transport) debug_printf("Real path \"%s\" does not match \"%s\"\n", >- big_buffer, deliver_home); >+ big_buffer, deliver_dir); > } > } > } >@@ -1183,6 +1136,7 @@ > appendfile_transport_options_block *ob = > (appendfile_transport_options_block *)(tblock->options_block); > struct stat statbuf; >+const uschar * deliver_dir; > uschar *fdname = NULL; > uschar *filename = NULL; > uschar *hitchname = NULL; >@@ -1286,12 +1240,6 @@ > expand_string_message); > goto ret_panic; > } >-if (is_tainted(path)) >- { >- addr->message = string_sprintf("Tainted '%s' (file or directory " >- "name for %s transport) not permitted", path, tblock->name); >- goto ret_panic; >- } > > if (path[0] != '/') > { >@@ -1368,6 +1316,12 @@ > return FALSE; > } > >+/* If an absolute path was given for create_file the it overrides deliver_home >+(here) and de-taints the filename (below, after check_creation() */ >+ >+deliver_dir = *ob->create_file_string == '/' >+ ? ob->create_file_string : deliver_home; >+ > /* Handle the case of a file name. If the file name is /dev/null, we can save > ourselves some effort and just give a success return right away. */ > >@@ -1384,10 +1338,20 @@ > } > > /* Set the name of the file to be opened, and the file to which the data >- is written, and find out if we are permitted to create a non-existent file. */ >+ is written, and find out if we are permitted to create a non-existent file. >+ If the create_file option is an absolute path and the file was within it, >+ de-taint. Chaeck for a tainted path. */ >+ >+ if ( (allow_creation_here = check_creation(path, ob->create_file, deliver_dir)) >+ && ob->create_file == create_belowhome) >+ if (is_tainted(path)) >+ { >+ DEBUG(D_transport) debug_printf("de-tainting path '%s'\n", path); >+ path = string_copy_taint(path, GET_UNTAINTED); >+ } > >+ if (is_tainted(path)) goto tainted_ret_panic; > dataname = filename = path; >- allow_creation_here = check_creation(filename, ob->create_file); > > /* If ob->create_directory is set, attempt to create the directories in > which this mailbox lives, but only if we are permitted to create the file >@@ -1397,17 +1361,16 @@ > if (ob->create_directory && allow_creation_here) > { > uschar *p = Ustrrchr(path, '/'); >- *p = '\0'; >- if (!directory_make(NULL, path, ob->dirmode, FALSE)) >+ p = string_copyn(path, p - path); >+ if (!directory_make(NULL, p, ob->dirmode, FALSE)) > { > addr->basic_errno = errno; > addr->message = > string_sprintf("failed to create directories for %s: %s", path, >- strerror(errno)); >+ exim_errstr(errno)); > DEBUG(D_transport) debug_printf("%s transport: %s\n", tblock->name, path); > return FALSE; > } >- *p = '/'; > } > > /* If file_format is set we must check that any existing file matches one of >@@ -2204,10 +2167,11 @@ > > else > { >- uschar *check_path = path; /* Default quota check path */ >- const pcre *regex = NULL; /* Regex for file size from file name */ >+ uschar *check_path; /* Default quota check path */ >+ const pcre2_code * re = NULL; /* Regex for file size from file name */ > >- if (!check_creation(string_sprintf("%s/any", path), ob->create_file)) >+ if (!check_creation(string_sprintf("%s/any", path), >+ ob->create_file, deliver_dir)) > { > addr->basic_errno = ERRNO_BADCREATE; > addr->message = string_sprintf("tried to create file in %s, but " >@@ -2215,6 +2179,20 @@ > goto RETURN; > } > >+ /* If the create_file option is an absolute path and the file was within >+ it, de-taint. Otherwise check for taint. */ >+ >+ if (is_tainted(path)) >+ if (ob->create_file == create_belowhome) >+ { >+ DEBUG(D_transport) debug_printf("de-tainting path '%s'\n", path); >+ path = string_copy_taint(path, GET_UNTAINTED); >+ } >+ else >+ goto tainted_ret_panic; >+ >+ check_path = path; >+ > #ifdef SUPPORT_MAILDIR > /* For a maildir delivery, ensure that all the relevant directories exist, > and a maildirfolder file if necessary. */ >@@ -2233,18 +2211,20 @@ > > if (ob->quota_value > 0 || THRESHOLD_CHECK || ob->maildir_use_size_file) > { >- const uschar *error; >- int offset; >+ PCRE2_SIZE offset; >+ int err; > > /* Compile the regex if there is one. */ > > if (ob->quota_size_regex) > { >- if (!(regex = pcre_compile(CS ob->quota_size_regex, PCRE_COPT, >- CCSS &error, &offset, NULL))) >+ if (!(re = pcre2_compile((PCRE2_SPTR)ob->quota_size_regex, >+ PCRE2_ZERO_TERMINATED, PCRE_COPT, &err, &offset, pcre_cmp_ctx))) > { >+ uschar errbuf[128]; >+ pcre2_get_error_message(err, errbuf, sizeof(errbuf)); > addr->message = string_sprintf("appendfile: regular expression " >- "error: %s at offset %d while compiling %s", error, offset, >+ "error: %s at offset %ld while compiling %s", errbuf, (long)offset, > ob->quota_size_regex); > return FALSE; > } >@@ -2287,14 +2267,14 @@ > { > uschar *new_check_path = string_copy(check_path); > uschar *slash = Ustrrchr(new_check_path, '/'); >- if (slash != NULL) >+ if (slash) > { >- if (slash[1] == 0) >+ if (!slash[1]) > { > *slash = 0; > slash = Ustrrchr(new_check_path, '/'); > } >- if (slash != NULL) >+ if (slash) > { > *slash = 0; > check_path = new_check_path; >@@ -2319,19 +2299,21 @@ > #ifdef SUPPORT_MAILDIR > if (ob->maildir_use_size_file) > { >- const pcre *dir_regex = NULL; >- const uschar *error; >- int offset; >+ const pcre2_code * dir_regex = NULL; >+ PCRE2_SIZE offset; >+ int err; > > if (ob->maildir_dir_regex) > { > int check_path_len = Ustrlen(check_path); > >- if (!(dir_regex = pcre_compile(CS ob->maildir_dir_regex, PCRE_COPT, >- CCSS &error, &offset, NULL))) >+ if (!(dir_regex = pcre2_compile((PCRE2_SPTR)ob->maildir_dir_regex, >+ PCRE2_ZERO_TERMINATED, PCRE_COPT, &err, &offset, pcre_cmp_ctx))) > { >+ uschar errbuf[128]; >+ pcre2_get_error_message(err, errbuf, sizeof(errbuf)); > addr->message = string_sprintf("appendfile: regular expression " >- "error: %s at offset %d while compiling %s", error, offset, >+ "error: %s at offset %ld while compiling %s", errbuf, (long)offset, > ob->maildir_dir_regex); > return FALSE; > } >@@ -2349,8 +2331,8 @@ > { > uschar *s = path + check_path_len; > while (*s == '/') s++; >- s = (*s == 0) ? US "new" : string_sprintf("%s/new", s); >- if (pcre_exec(dir_regex, NULL, CS s, Ustrlen(s), 0, 0, NULL, 0) < 0) >+ s = *s ? string_sprintf("%s/new", s) : US"new"; >+ if (!regex_match(dir_regex, s, -1, NULL)) > { > disable_quota = TRUE; > DEBUG(D_transport) debug_printf("delivery directory does not match " >@@ -2371,7 +2353,7 @@ > off_t size; > int filecount; > >- if ((maildirsize_fd = maildir_ensure_sizefile(check_path, ob, regex, dir_regex, >+ if ((maildirsize_fd = maildir_ensure_sizefile(check_path, ob, re, dir_regex, > &size, &filecount)) == -1) > { > addr->basic_errno = errno; >@@ -2396,7 +2378,7 @@ > * (void)unlink(CS string_sprintf("%s/maildirsize", check_path)); > * if (THRESHOLD_CHECK) > * mailbox_size = maildir_compute_size(check_path, &mailbox_filecount, &old_latest, >- * regex, dir_regex, FALSE); >+ * re, dir_regex, FALSE); > * } > */ > >@@ -2408,16 +2390,17 @@ > count. Note that ob->quota_filecount_value cannot be set without > ob->quota_value being set. */ > >- if (!disable_quota && >- (ob->quota_value > 0 || THRESHOLD_CHECK) && >- (mailbox_size < 0 || >- (mailbox_filecount < 0 && ob->quota_filecount_value > 0))) >+ if ( !disable_quota >+ && (ob->quota_value > 0 || THRESHOLD_CHECK) >+ && ( mailbox_size < 0 >+ || mailbox_filecount < 0 && ob->quota_filecount_value > 0 >+ ) ) > { > off_t size; > int filecount = 0; > DEBUG(D_transport) > debug_printf("quota checks on directory %s\n", check_path); >- size = check_dir_size(check_path, &filecount, regex); >+ size = check_dir_size(check_path, &filecount, re); > if (mailbox_size < 0) mailbox_size = size; > if (mailbox_filecount < 0) mailbox_filecount = filecount; > } >@@ -2484,7 +2467,7 @@ > uschar *basename; > > (void)gettimeofday(&msg_tv, NULL); >- basename = string_sprintf(TIME_T_FMT ".H%luP" PID_T_FMT ".%s", >+ basename = string_sprintf(TIME_T_FMT ".M%luP" PID_T_FMT ".%s", > msg_tv.tv_sec, msg_tv.tv_usec, getpid(), primary_hostname); > > filename = dataname = string_sprintf("tmp/%s", basename); >@@ -2556,11 +2539,12 @@ > dataname = string_sprintf("%s.msg", mailstore_basename); > > fd = Uopen(filename, O_WRONLY|O_CREAT|O_EXCL, mode); >- if (fd < 0 && /* failed to open, and */ >- (errno != ENOENT || /* either not non-exist */ >- !ob->create_directory || /* or not allowed to make */ >- !directory_make(NULL, path, ob->dirmode, FALSE) || /* or failed to create dir */ >- (fd = Uopen(filename, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0)) /* or then failed to open */ >+ if ( fd < 0 /* failed to open, and */ >+ && ( errno != ENOENT /* either not non-exist */ >+ || !ob->create_directory /* or not allowed to make */ >+ || !directory_make(NULL, path, ob->dirmode, FALSE) /* or failed to create dir */ >+ || (fd = Uopen(filename, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0 /* or then failed to open */ >+ ) ) > { > addr->basic_errno = errno; > addr->message = string_sprintf("while creating file %s", filename); >@@ -2739,6 +2723,18 @@ > > } > >+if (verify_mode) >+ { >+ addr->basic_errno = errno; >+ addr->message = US"Over quota"; >+ addr->transport_return = yield; >+ DEBUG(D_transport) >+ debug_printf("appendfile (verify) yields %d with errno=%d more_errno=%d\n", >+ yield, addr->basic_errno, addr->more_errno); >+ >+ goto RETURN; >+ } >+ > /* If we are writing in MBX format, what we actually do is to write the message > to a temporary file, and then copy it to the real file once we know its size. > This is the most straightforward way of getting the correct length in the >@@ -3195,7 +3191,7 @@ > uschar *iptr = expand_string(nametag); > if (iptr) > { >- uschar *etag = store_get(Ustrlen(iptr) + 2, is_tainted(iptr)); >+ uschar *etag = store_get(Ustrlen(iptr) + 2, iptr); > uschar *optr = etag; > for ( ; *iptr; iptr++) > if (mac_isgraph(*iptr) && *iptr != '/') >@@ -3309,6 +3305,9 @@ > > return FALSE; > >+tainted_ret_panic: >+ addr->message = string_sprintf("Tainted '%s' (file or directory " >+ "name for %s transport) not permitted", path, tblock->name); > ret_panic: > addr->transport_return = PANIC; > return FALSE; >diff -ur exim.orig/src/transports/appendfile.h exim/src/transports/appendfile.h >--- exim.orig/src/transports/appendfile.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/transports/appendfile.h 2022-06-23 16:41:10.000000000 +0300 >@@ -3,6 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2018 */ >+/* Copyright (c) The Exim Maintainers 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Private structure for the private options. */ >@@ -94,6 +95,6 @@ > > /* Function that is shared with tf_maildir.c */ > >-extern off_t check_dir_size(const uschar *, int *, const pcre *); >+extern off_t check_dir_size(const uschar *, int *, const pcre2_code *); > > /* End of transports/appendfile.h */ >diff -ur exim.orig/src/transports/autoreply.c exim/src/transports/autoreply.c >--- exim.orig/src/transports/autoreply.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/transports/autoreply.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -57,27 +57,11 @@ > #else /*!MACRO_PREDEF*/ > > >-/* Default private options block for the autoreply transport. */ >+/* Default private options block for the autoreply transport. >+All non-mentioned lements zero/null/false. */ > > autoreply_transport_options_block autoreply_transport_option_defaults = { >- NULL, /* from */ >- NULL, /* reply_to */ >- NULL, /* to */ >- NULL, /* cc */ >- NULL, /* bcc */ >- NULL, /* subject */ >- NULL, /* headers */ >- NULL, /* text */ >- NULL, /* file */ >- NULL, /* logfile */ >- NULL, /* oncelog */ >- NULL, /* once_repeat */ >- NULL, /* never_mail */ >- 0600, /* mode */ >- 0, /* once_file_size */ >- FALSE, /* file_expand */ >- FALSE, /* file_optional */ >- FALSE /* return message */ >+ .mode = 0600, > }; > > >@@ -175,18 +159,21 @@ > list. Any that are found are removed. > > Arguments: >- listptr points to the list of addresses >+ list list of addresses to be checked > never_mail an address list, already expanded > >-Returns: nothing >+Returns: edited replacement address list, or NULL, or original > */ > >-static void >-check_never_mail(uschar **listptr, const uschar *never_mail) >+static uschar * >+check_never_mail(uschar * list, const uschar * never_mail) > { >-uschar *s = *listptr; >+rmark reset_point = store_mark(); >+uschar * newlist = string_copy(list); >+uschar * s = newlist; >+BOOL hit = FALSE; > >-while (*s != 0) >+while (*s) > { > uschar *error, *next; > uschar *e = parse_find_address_end(s, FALSE); >@@ -220,6 +207,7 @@ > { > DEBUG(D_transport) > debug_printf("discarding recipient %s (matched never_mail)\n", next); >+ hit = TRUE; > if (terminator == ',') e++; > memmove(s, e, Ustrlen(e) + 1); > } >@@ -230,18 +218,31 @@ > } > } > >+/* If no addresses were removed, retrieve the memory used and return >+the original. */ >+ >+if (!hit) >+ { >+ store_reset(reset_point); >+ return list; >+ } >+ > /* Check to see if we removed the last address, leaving a terminating comma > that needs to be removed */ > >-s = *listptr + Ustrlen(*listptr); >-while (s > *listptr && (isspace(s[-1]) || s[-1] == ',')) s--; >+s = newlist + Ustrlen(newlist); >+while (s > newlist && (isspace(s[-1]) || s[-1] == ',')) s--; > *s = 0; > >-/* Check to see if there any addresses left; if not, set NULL */ >+/* Check to see if there any addresses left; if not, return NULL */ >+ >+s = newlist; >+while (s && isspace(*s)) s++; >+if (*s) >+ return newlist; > >-s = *listptr; >-while (s != 0 && isspace(*s)) s++; >-if (*s == 0) *listptr = NULL; >+store_reset(reset_point); >+return NULL; > } > > >@@ -263,7 +264,7 @@ > int cache_fd = -1; > int cache_size = 0; > int add_size = 0; >-EXIM_DB *dbm_file = NULL; >+EXIM_DB * dbm_file = NULL; > BOOL file_expand, return_message; > uschar *from, *reply_to, *to, *cc, *bcc, *subject, *headers, *text, *file; > uschar *logfile, *oncelog; >@@ -345,16 +346,13 @@ > return FALSE; > > if (oncerepeat) >- { >- once_repeat_sec = readconf_readtime(oncerepeat, 0, FALSE); >- if (once_repeat_sec < 0) >+ if ((once_repeat_sec = readconf_readtime(oncerepeat, 0, FALSE)) < 0) > { > addr->transport_return = FAIL; > addr->message = string_sprintf("Invalid time value \"%s\" for " > "\"once_repeat\" in %s transport", oncerepeat, tblock->name); > return FALSE; > } >- } > } > > /* If the never_mail option is set, we have to scan all the recipients and >@@ -372,9 +370,9 @@ > return FALSE; > } > >- if (to) check_never_mail(&to, never_mail); >- if (cc) check_never_mail(&cc, never_mail); >- if (bcc) check_never_mail(&bcc, never_mail); >+ if (to) to = check_never_mail(to, never_mail); >+ if (cc) cc = check_never_mail(cc, never_mail); >+ if (bcc) bcc = check_never_mail(bcc, never_mail); > > if (!to && !cc && !bcc) > { >@@ -439,7 +437,7 @@ > > cache_size = statbuf.st_size; > add_size = sizeof(time_t) + Ustrlen(to) + 1; >- cache_buff = store_get(cache_size + add_size, is_tainted(oncelog)); >+ cache_buff = store_get(cache_size + add_size, oncelog); > > if (read(cache_fd, cache_buff, cache_size) != cache_size) > { >@@ -478,8 +476,7 @@ > > dirname = (s = Ustrrchr(oncelog, '/')) > ? string_copyn(oncelog, s - oncelog) : NULL; >- EXIM_DBOPEN(oncelog, dirname, O_RDWR|O_CREAT, ob->mode, &dbm_file); >- if (!dbm_file) >+ if (!(dbm_file = exim_dbopen(oncelog, dirname, O_RDWR|O_CREAT, ob->mode))) > { > addr->transport_return = DEFER; > addr->basic_errno = errno; >@@ -489,12 +486,12 @@ > goto END_OFF; > } > >- EXIM_DATUM_INIT(key_datum); /* Some DBM libraries need datums */ >- EXIM_DATUM_INIT(result_datum); /* to be cleared */ >- EXIM_DATUM_DATA(key_datum) = CS to; >- EXIM_DATUM_SIZE(key_datum) = Ustrlen(to) + 1; >+ exim_datum_init(&key_datum); /* Some DBM libraries need datums */ >+ exim_datum_init(&result_datum); /* to be cleared */ >+ exim_datum_data_set(&key_datum, (void *) to); >+ exim_datum_size_set(&key_datum, Ustrlen(to) + 1); > >- if (EXIM_DBGET(dbm_file, key_datum, result_datum)) >+ if (exim_dbget(dbm_file, &key_datum, &result_datum)) > { > /* If the datum size is that of a binary time, we are in the new world > where messages are sent periodically. Otherwise the file is an old one, >@@ -503,8 +500,8 @@ > introduced at Exim 3.00. In a couple of years' time the test on the size > can be abolished. */ > >- if (EXIM_DATUM_SIZE(result_datum) == sizeof(time_t)) >- memcpy(&then, EXIM_DATUM_DATA(result_datum), sizeof(time_t)); >+ if (exim_datum_size_get(&result_datum) == sizeof(time_t)) >+ memcpy(&then, exim_datum_data_get(&result_datum), sizeof(time_t)); > else > then = now; > } >@@ -577,7 +574,7 @@ > addr->message = string_sprintf("Failed to create child process to send " > "message from %s transport: %s", tblock->name, strerror(errno)); > DEBUG(D_transport) debug_printf("%s\n", addr->message); >- if (dbm_file) EXIM_DBCLOSE(dbm_file); >+ if (dbm_file) exim_dbclose(dbm_file); > return FALSE; > } > >@@ -649,12 +646,11 @@ > > if (return_message) > { >- uschar *rubric = (tblock->headers_only)? >- US"------ This is a copy of the message's header lines.\n" >- : (tblock->body_only)? >- US"------ This is a copy of the body of the message, without the headers.\n" >- : >- US"------ This is a copy of the message, including all the headers.\n"; >+ uschar *rubric = tblock->headers_only >+ ? US"------ This is a copy of the message's header lines.\n" >+ : tblock->body_only >+ ? US"------ This is a copy of the body of the message, without the headers.\n" >+ : US"------ This is a copy of the message, including all the headers.\n"; > transport_ctx tctx = { > .u = {.fd = fileno(fp)}, > .tblock = tblock, >@@ -739,18 +735,18 @@ > else if (dbm_file) > { > EXIM_DATUM key_datum, value_datum; >- EXIM_DATUM_INIT(key_datum); /* Some DBM libraries need to have */ >- EXIM_DATUM_INIT(value_datum); /* cleared datums. */ >- EXIM_DATUM_DATA(key_datum) = CS to; >- EXIM_DATUM_SIZE(key_datum) = Ustrlen(to) + 1; >+ exim_datum_init(&key_datum); /* Some DBM libraries need to have */ >+ exim_datum_init(&value_datum); /* cleared datums. */ >+ exim_datum_data_set(&key_datum, to); >+ exim_datum_size_set(&key_datum, Ustrlen(to) + 1); > > /* Many OS define the datum value, sensibly, as a void *. However, there > are some which still have char *. By casting this address to a char * we > can avoid warning messages from the char * systems. */ > >- EXIM_DATUM_DATA(value_datum) = CS (&now); >- EXIM_DATUM_SIZE(value_datum) = (int)sizeof(time_t); >- EXIM_DBPUT(dbm_file, key_datum, value_datum); >+ exim_datum_data_set(&value_datum, &now); >+ exim_datum_size_set(&value_datum, sizeof(time_t)); >+ exim_dbput(dbm_file, &key_datum, &value_datum); > } > > /* If sending failed, defer to try again - but if once is set the next >@@ -813,7 +809,7 @@ > } > > END_OFF: >-if (dbm_file) EXIM_DBCLOSE(dbm_file); >+if (dbm_file) exim_dbclose(dbm_file); > if (cache_fd > 0) (void)close(cache_fd); > > DEBUG(D_transport) debug_printf("%s transport succeeded\n", tblock->name); >diff -ur exim.orig/src/transports/lmtp.c exim/src/transports/lmtp.c >--- exim.orig/src/transports/lmtp.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/transports/lmtp.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -489,8 +489,8 @@ > { > DEBUG(D_transport) debug_printf("using command %s\n", ob->cmd); > sprintf(CS buffer, "%.50s transport", tblock->name); >- if (!transport_set_up_command(&argv, ob->cmd, TRUE, PANIC, addrlist, buffer, >- NULL)) >+ if (!transport_set_up_command(&argv, ob->cmd, TRUE, PANIC, addrlist, FALSE, >+ buffer, NULL)) > return FALSE; > > /* If the -N option is set, can't do any more. Presume all has gone well. */ >@@ -558,23 +558,23 @@ > /* First thing is to wait for an initial greeting. */ > > Ustrcpy(big_buffer, US"initial connection"); >-if (!lmtp_read_response(out, buffer, sizeof(buffer), '2', >- timeout)) goto RESPONSE_FAILED; >+if (!lmtp_read_response(out, buffer, sizeof(buffer), '2', timeout)) >+ goto RESPONSE_FAILED; > > /* Next, we send a LHLO command, and expect a positive response */ > >-if (!lmtp_write_command(fd_in, "%s %s\r\n", "LHLO", >- primary_hostname)) goto WRITE_FAILED; >+if (!lmtp_write_command(fd_in, "%s %s\r\n", "LHLO", primary_hostname)) >+ goto WRITE_FAILED; > >-if (!lmtp_read_response(out, buffer, sizeof(buffer), '2', >- timeout)) goto RESPONSE_FAILED; >+if (!lmtp_read_response(out, buffer, sizeof(buffer), '2', timeout)) >+ goto RESPONSE_FAILED; > > /* If the ignore_quota option is set, note whether the server supports the > IGNOREQUOTA option, and if so, set an appropriate addition for RCPT. */ > > if (ob->ignore_quota) >- igquotstr = (pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer, >- Ustrlen(CS buffer), 0, PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US""; >+ igquotstr = regex_match(regex_IGNOREQUOTA, buffer, -1, NULL) >+ ? US" IGNOREQUOTA" : US""; > > /* Now the envelope sender */ > >diff -ur exim.orig/src/transports/pipe.c exim/src/transports/pipe.c >--- exim.orig/src/transports/pipe.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/transports/pipe.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -88,31 +88,13 @@ > /* Default private options block for the pipe transport. */ > > pipe_transport_options_block pipe_transport_option_defaults = { >- NULL, /* cmd */ >- NULL, /* allow_commands */ >- NULL, /* environment */ >- US"/bin:/usr/bin", /* path */ >- NULL, /* message_prefix (reset in init if not bsmtp) */ >- NULL, /* message_suffix (ditto) */ >- US mac_expanded_string(EX_TEMPFAIL) ":" /* temp_errors */ >- mac_expanded_string(EX_CANTCREAT), >- NULL, /* check_string */ >- NULL, /* escape_string */ >- 022, /* umask */ >- 20480, /* max_output */ >- 60*60, /* timeout */ >- 0, /* options */ >- FALSE, /* force_command */ >- FALSE, /* freeze_exec_fail */ >- FALSE, /* freeze_signal */ >- FALSE, /* ignore_status */ >- FALSE, /* permit_coredump */ >- FALSE, /* restrict_to_path */ >- FALSE, /* timeout_defer */ >- FALSE, /* use_shell */ >- FALSE, /* use_bsmtp */ >- FALSE, /* use_classresources */ >- FALSE /* use_crlf */ >+ .path = US"/bin:/usr/bin", >+ .temp_errors = US mac_expanded_string(EX_TEMPFAIL) ":" >+ mac_expanded_string(EX_CANTCREAT), >+ .umask = 022, >+ .max_output = 20480, >+ .timeout = 60*60, >+ /* all others null/zero/false */ > }; > > >@@ -144,13 +126,6 @@ > pipe_transport_options_block *ob = > (pipe_transport_options_block *)(tblock->options_block); > >-addrlist = addrlist; /* Keep compiler happy */ >-dummy = dummy; >-uid = uid; >-gid = gid; >-errmsg = errmsg; >-ob = ob; >- > #ifdef HAVE_SETCLASSRESOURCES > if (ob->use_classresources) > { >@@ -285,12 +260,12 @@ > driver options. Only one of body_only and headers_only can be set. */ > > ob->options |= >- (tblock->body_only? topt_no_headers : 0) | >- (tblock->headers_only? topt_no_body : 0) | >- (tblock->return_path_add? topt_add_return_path : 0) | >- (tblock->delivery_date_add? topt_add_delivery_date : 0) | >- (tblock->envelope_to_add? topt_add_envelope_to : 0) | >- (ob->use_crlf? topt_use_crlf : 0); >+ (tblock->body_only ? topt_no_headers : 0) >+ | (tblock->headers_only ? topt_no_body : 0) >+ | (tblock->return_path_add ? topt_add_return_path : 0) >+ | (tblock->delivery_date_add ? topt_add_delivery_date : 0) >+ | (tblock->envelope_to_add ? topt_add_envelope_to : 0) >+ | (ob->use_crlf ? topt_use_crlf : 0); > } > > >@@ -329,7 +304,7 @@ > is in the addresses). */ > > if (!transport_set_up_command(argvptr, cmd, expand_arguments, expand_fail, >- addr, string_sprintf("%.50s transport", tname), NULL)) >+ addr, FALSE, string_sprintf("%.50s transport", tname), NULL)) > return FALSE; > > /* Point to the set-up arguments. */ >@@ -442,7 +417,7 @@ > { > const uschar **argv; > >-*argvptr = argv = store_get((4)*sizeof(uschar *), FALSE); >+*argvptr = argv = store_get((4)*sizeof(uschar *), GET_UNTAINTED); > > argv[0] = US"/bin/sh"; > argv[1] = US"-c"; >@@ -476,6 +451,9 @@ > > for (address_item * ad = addr; ad; ad = ad->next) > { >+ DEBUG(D_transport) if (is_tainted(ad->address)) >+ debug_printf("tainted element '%s' from $pipe_addresses\n", ad->address); >+ > /*XXX string_append_listele() ? */ > if (ad != addr) g = string_catn(g, US" ", 1); > g = string_cat(g, ad->address); >@@ -588,9 +566,8 @@ > } > > /* If no command has been supplied, we are in trouble. >- * We also check for an empty string since it may be >- * coming from addr->local_part[0] == '|' >- */ >+We also check for an empty string since it may be >+coming from addr->local_part[0] == '|' */ > > if (!cmd || !*cmd) > { >@@ -601,6 +578,7 @@ > } > if (is_tainted(cmd)) > { >+ DEBUG(D_transport) debug_printf("cmd '%s' is tainted\n", cmd); > addr->message = string_sprintf("Tainted '%s' (command " > "for %s transport) not permitted", cmd, tblock->name); > addr->transport_return = PANIC; >@@ -659,11 +637,12 @@ > envp[envcount++] = string_sprintf("SENDER=%s", sender_address); > envp[envcount++] = US"SHELL=/bin/sh"; > >-if (addr->host_list != NULL) >+if (addr->host_list) > envp[envcount++] = string_sprintf("HOST=%s", addr->host_list->name); > >-if (f.timestamps_utc) envp[envcount++] = US"TZ=UTC"; >-else if (timezone_string != NULL && timezone_string[0] != 0) >+if (f.timestamps_utc) >+ envp[envcount++] = US"TZ=UTC"; >+else if (timezone_string && timezone_string[0]) > envp[envcount++] = string_sprintf("TZ=%s", timezone_string); > > /* Add any requested items */ >diff -ur exim.orig/src/transports/queuefile.c exim/src/transports/queuefile.c >--- exim.orig/src/transports/queuefile.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/transports/queuefile.c 2022-06-23 16:41:10.000000000 +0300 >@@ -4,7 +4,7 @@ > > /* Copyright (c) Andrew Colin Kissa <andrew@topdog.za.net> 2016 */ > /* Copyright (c) University of Cambridge 2016 */ >-/* Copyright (c) The Exim Maintainers 1995 - 2020 */ >+/* Copyright (c) The Exim Maintainers 1995 - 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > >@@ -14,6 +14,10 @@ > #ifdef EXPERIMENTAL_QUEUEFILE /* whole file */ > #include "queuefile.h" > >+#ifndef EXIM_HAVE_OPENAT >+# error queuefile transport reqires openat() support >+#endif >+ > /* Options specific to the appendfile transport. They must be in alphabetic > order (note that "_" comes before the lower case letters). Some of them are > stored in the publicly visible instance block - these are flagged with the >diff -ur exim.orig/src/transports/smtp.c exim/src/transports/smtp.c >--- exim.orig/src/transports/smtp.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/transports/smtp.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #include "../exim.h" >@@ -43,7 +43,7 @@ > { "dane_require_tls_ciphers", opt_stringptr, LOFF(dane_require_tls_ciphers) }, > # endif > { "data_timeout", opt_time, LOFF(data_timeout) }, >- { "delay_after_cutoff", opt_bool, LOFF(delay_after_cutoff) }, >+ { "delay_after_cutoff", opt_bool, LOFF(delay_after_cutoff) }, > #ifndef DISABLE_DKIM > { "dkim_canon", opt_stringptr, LOFF(dkim.dkim_canon) }, > { "dkim_domain", opt_stringptr, LOFF(dkim.dkim_domain) }, >@@ -64,6 +64,9 @@ > { "final_timeout", opt_time, LOFF(final_timeout) }, > { "gethostbyname", opt_bool, LOFF(gethostbyname) }, > { "helo_data", opt_stringptr, LOFF(helo_data) }, >+#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME) >+ { "host_name_extract", opt_stringptr, LOFF(host_name_extract) }, >+# endif > { "hosts", opt_stringptr, LOFF(hosts) }, > { "hosts_avoid_esmtp", opt_stringptr, LOFF(hosts_avoid_esmtp) }, > { "hosts_avoid_pipelining", opt_stringptr, LOFF(hosts_avoid_pipelining) }, >@@ -84,6 +87,7 @@ > #if !defined(DISABLE_TLS) && !defined(DISABLE_OCSP) > { "hosts_request_ocsp", opt_stringptr, LOFF(hosts_request_ocsp) }, > #endif >+ { "hosts_require_alpn", opt_stringptr, LOFF(hosts_require_alpn) }, > { "hosts_require_auth", opt_stringptr, LOFF(hosts_require_auth) }, > #ifndef DISABLE_TLS > # ifdef SUPPORT_DANE >@@ -111,6 +115,7 @@ > { "lmtp_ignore_quota", opt_bool, LOFF(lmtp_ignore_quota) }, > { "max_rcpt", opt_int | opt_public, > OPT_OFF(transport_instance, max_addresses) }, >+ { "message_linelength_limit", opt_int, LOFF(message_linelength_limit) }, > { "multi_domain", opt_expand_bool | opt_public, > OPT_OFF(transport_instance, multi_domain) }, > { "port", opt_stringptr, LOFF(port) }, >@@ -122,12 +127,13 @@ > { "socks_proxy", opt_stringptr, LOFF(socks_proxy) }, > #endif > #ifndef DISABLE_TLS >+ { "tls_alpn", opt_stringptr, LOFF(tls_alpn) }, > { "tls_certificate", opt_stringptr, LOFF(tls_certificate) }, > { "tls_crl", opt_stringptr, LOFF(tls_crl) }, > { "tls_dh_min_bits", opt_int, LOFF(tls_dh_min_bits) }, > { "tls_privatekey", opt_stringptr, LOFF(tls_privatekey) }, > { "tls_require_ciphers", opt_stringptr, LOFF(tls_require_ciphers) }, >-# ifdef EXPERIMENTAL_TLS_RESUME >+# ifndef DISABLE_TLS_RESUME > { "tls_resumption_hosts", opt_stringptr, LOFF(tls_resumption_hosts) }, > # endif > { "tls_sni", opt_stringptr, LOFF(tls_sni) }, >@@ -162,23 +168,12 @@ > /* Default private options block for the smtp transport. */ > > smtp_transport_options_block smtp_transport_option_defaults = { >- .hosts = NULL, >- .fallback_hosts = NULL, >- .hostlist = NULL, >- .fallback_hostlist = NULL, >+ /* All non-mentioned elements 0/NULL/FALSE */ > .helo_data = US"$primary_hostname", >- .interface = NULL, >- .port = NULL, > .protocol = US"smtp", >- .dscp = NULL, >- .serialize_hosts = NULL, >- .hosts_try_auth = NULL, >- .hosts_require_auth = NULL, > .hosts_try_chunking = US"*", > #ifdef SUPPORT_DANE > .hosts_try_dane = US"*", >- .hosts_require_dane = NULL, >- .dane_require_tls_ciphers = NULL, > #endif > .hosts_try_fastopen = US"*", > #ifndef DISABLE_PRDR >@@ -186,19 +181,6 @@ > #endif > #ifndef DISABLE_OCSP > .hosts_request_ocsp = US"*", /* hosts_request_ocsp (except under DANE; tls_client_start()) */ >- .hosts_require_ocsp = NULL, >-#endif >- .hosts_require_tls = NULL, >- .hosts_avoid_tls = NULL, >- .hosts_verify_avoid_tls = NULL, >- .hosts_avoid_pipelining = NULL, >-#ifndef DISABLE_PIPE_CONNECT >- .hosts_pipe_connect = NULL, >-#endif >- .hosts_avoid_esmtp = NULL, >-#ifndef DISABLE_TLS >- .hosts_nopass_tls = NULL, >- .hosts_noproxy_tls = NULL, > #endif > .command_timeout = 5*60, > .connect_timeout = 5*60, >@@ -207,62 +189,29 @@ > .size_addition = 1024, > .hosts_max_try = 5, > .hosts_max_try_hardlimit = 50, >+ .message_linelength_limit = 998, > .address_retry_include_sender = TRUE, >- .allow_localhost = FALSE, >- .authenticated_sender_force = FALSE, >- .gethostbyname = FALSE, > .dns_qualify_single = TRUE, >- .dns_search_parents = FALSE, > .dnssec = { .request= US"*", .require=NULL }, > .delay_after_cutoff = TRUE, >- .hosts_override = FALSE, >- .hosts_randomize = FALSE, > .keepalive = TRUE, >- .lmtp_ignore_quota = FALSE, >- .expand_retry_include_ip_address = NULL, > .retry_include_ip_address = TRUE, >-#ifdef SUPPORT_SOCKS >- .socks_proxy = NULL, >-#endif > #ifndef DISABLE_TLS >- .tls_certificate = NULL, >- .tls_crl = NULL, >- .tls_privatekey = NULL, >- .tls_require_ciphers = NULL, >- .tls_sni = NULL, > .tls_verify_certificates = US"system", > .tls_dh_min_bits = EXIM_CLIENT_DH_DEFAULT_MIN_BITS, > .tls_tempfail_tryclear = TRUE, >-# ifdef EXPERIMENTAL_TLS_RESUME >- .tls_resumption_hosts = NULL, >-# endif >- .tls_verify_hosts = NULL, > .tls_try_verify_hosts = US"*", > .tls_verify_cert_hostnames = US"*", >+# ifndef DISABLE_TLS_RESUME >+ .host_name_extract = US"${if and {{match{$host}{.outlook.com\\$}} {match{$item}{\\N^250-([\\w.]+)\\s\\N}}} {$1}}", >+# endif > #endif > #ifdef SUPPORT_I18N > .utf8_downconvert = US"-1", > #endif > #ifndef DISABLE_DKIM > .dkim = >- {.dkim_domain = NULL, >- .dkim_identity = NULL, >- .dkim_private_key = NULL, >- .dkim_selector = NULL, >- .dkim_canon = NULL, >- .dkim_sign_headers = NULL, >- .dkim_strict = NULL, >- .dkim_hash = US"sha256", >- .dkim_timestamps = NULL, >- .dot_stuffed = FALSE, >- .force_bodyhash = FALSE, >-# ifdef EXPERIMENTAL_ARC >- .arc_signspec = NULL, >-# endif >- }, >-# ifdef EXPERIMENTAL_ARC >- .arc_sign = NULL, >-# endif >+ { .dkim_hash = US"sha256", }, > #endif > }; > >@@ -294,43 +243,39 @@ > void > smtp_deliver_init(void) > { >-if (!regex_PIPELINING) regex_PIPELINING = >- regex_must_compile(US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)", FALSE, TRUE); >- >-if (!regex_SIZE) regex_SIZE = >- regex_must_compile(US"\\n250[\\s\\-]SIZE(\\s|\\n|$)", FALSE, TRUE); >- >-if (!regex_AUTH) regex_AUTH = >- regex_must_compile(AUTHS_REGEX, FALSE, TRUE); >+struct list >+ { >+ const pcre2_code ** re; >+ const uschar * string; >+ } list[] = >+ { >+ { ®ex_AUTH, AUTHS_REGEX }, >+ { ®ex_CHUNKING, US"\\n250[\\s\\-]CHUNKING(\\s|\\n|$)" }, >+ { ®ex_DSN, US"\\n250[\\s\\-]DSN(\\s|\\n|$)" }, >+ { ®ex_IGNOREQUOTA, US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)" }, >+ { ®ex_PIPELINING, US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)" }, >+ { ®ex_SIZE, US"\\n250[\\s\\-]SIZE(\\s|\\n|$)" }, > > #ifndef DISABLE_TLS >-if (!regex_STARTTLS) regex_STARTTLS = >- regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE); >+ { ®ex_STARTTLS, US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)" }, > #endif >- >-if (!regex_CHUNKING) regex_CHUNKING = >- regex_must_compile(US"\\n250[\\s\\-]CHUNKING(\\s|\\n|$)", FALSE, TRUE); >- > #ifndef DISABLE_PRDR >-if (!regex_PRDR) regex_PRDR = >- regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE); >+ { ®ex_PRDR, US"\\n250[\\s\\-]PRDR(\\s|\\n|$)" }, > #endif >- > #ifdef SUPPORT_I18N >-if (!regex_UTF8) regex_UTF8 = >- regex_must_compile(US"\\n250[\\s\\-]SMTPUTF8(\\s|\\n|$)", FALSE, TRUE); >+ { ®ex_UTF8, US"\\n250[\\s\\-]SMTPUTF8(\\s|\\n|$)" }, > #endif >- >-if (!regex_DSN) regex_DSN = >- regex_must_compile(US"\\n250[\\s\\-]DSN(\\s|\\n|$)", FALSE, TRUE); >- >-if (!regex_IGNOREQUOTA) regex_IGNOREQUOTA = >- regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE); >- > #ifndef DISABLE_PIPE_CONNECT >-if (!regex_EARLY_PIPE) regex_EARLY_PIPE = >- regex_must_compile(US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)", FALSE, TRUE); >+ { ®ex_EARLY_PIPE, US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)" }, >+#endif >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ { ®ex_LIMITS, US"\\n250[\\s\\-]LIMITS\\s" }, > #endif >+ }; >+ >+for (struct list * l = list; l < list + nelem(list); l++) >+ if (!*l->re) >+ *l->re = regex_must_compile(l->string, FALSE, TRUE); > } > > >@@ -362,10 +307,6 @@ > { > smtp_transport_options_block *ob = SOB tblock->options_block; > >-errmsg = errmsg; /* Keep picky compilers happy */ >-uid = uid; >-gid = gid; >- > /* Pass back options if required. This interface is getting very messy. */ > > if (tf) >@@ -411,6 +352,7 @@ > smtp_transport_init(transport_instance *tblock) > { > smtp_transport_options_block *ob = SOB tblock->options_block; >+int old_pool = store_pool; > > /* Retry_use_local_part defaults FALSE if unset */ > >@@ -441,12 +383,14 @@ > /* If hosts_override is set and there are local hosts, set the global > flag that stops verify from showing router hosts. */ > >-if (ob->hosts_override && ob->hosts != NULL) tblock->overrides_hosts = TRUE; >+if (ob->hosts_override && ob->hosts) tblock->overrides_hosts = TRUE; > > /* If there are any fallback hosts listed, build a chain of host items > for them, but do not do any lookups at this time. */ > >-host_build_hostlist(&(ob->fallback_hostlist), ob->fallback_hosts, FALSE); >+store_pool = POOL_PERM; >+host_build_hostlist(&ob->fallback_hostlist, ob->fallback_hosts, FALSE); >+store_pool = old_pool; > } > > >@@ -740,7 +684,8 @@ > : string_copy(addr->message) > : addr->basic_errno > 0 > ? string_copy(US strerror(addr->basic_errno)) >- : NULL); >+ : NULL, >+ NULL); > > deliver_localpart = save_local; > deliver_domain = save_domain; >@@ -773,7 +718,19 @@ > static BOOL > smtp_reap_banner(smtp_context * sx) > { >-BOOL good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), >+BOOL good_response; >+#if defined(__linux__) && defined(TCP_QUICKACK) >+ { /* Hack to get QUICKACK disabled; has to be right after 3whs, and has to on->off */ >+ int sock = sx->cctx.sock; >+ struct pollfd p = {.fd = sock, .events = POLLOUT}; >+ if (poll(&p, 1, 1000) >= 0) /* retval test solely for compiler quitening */ >+ { >+ (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &on, sizeof(on)); >+ (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off)); >+ } >+ } >+#endif >+good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), > '2', (SOB sx->conn_args.ob)->command_timeout); > #ifdef EXPERIMENTAL_DSN_INFO > sx->smtp_greeting = string_copy(sx->buffer); >@@ -801,12 +758,111 @@ > #endif > #ifndef DISABLE_EVENT > (void) event_raise(sx->conn_args.tblock->event_action, >- US"smtp:ehlo", sx->buffer); >+ US"smtp:ehlo", sx->buffer, NULL); > #endif > return TRUE; > } > > >+/* Grab a string differentiating server behind a loadbalancer, for TLS >+resumption when such servers do not share a session-cache */ >+ >+static void >+ehlo_response_lbserver(smtp_context * sx, smtp_transport_options_block * ob) >+{ >+#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME) >+const uschar * s; >+uschar * save_item = iterate_item; >+ >+if (sx->conn_args.have_lbserver) >+ return; >+iterate_item = sx->buffer; >+s = expand_cstring(ob->host_name_extract); >+iterate_item = save_item; >+sx->conn_args.host_lbserver = s && !*s ? NULL : s; >+sx->conn_args.have_lbserver = TRUE; >+#endif >+} >+ >+ >+ >+/******************************************************************************/ >+ >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+/* If TLS, or TLS not offered, called with the EHLO response in the buffer. >+Check it for a LIMITS keyword and parse values into the smtp context structure. >+ >+We don't bother with peers that we won't talk TLS to, even though they can, >+just ignore their LIMITS advice (if any) and treat them as if they do not. >+This saves us dealing with a duplicate set of values. */ >+ >+static void >+ehlo_response_limits_read(smtp_context * sx) >+{ >+uschar * match; >+ >+/* matches up to just after the first space after the keyword */ >+ >+if (regex_match(regex_LIMITS, sx->buffer, -1, &match)) >+ for (const uschar * s = sx->buffer + Ustrlen(match); *s; ) >+ { >+ while (isspace(*s)) s++; >+ if (*s == '\n') break; >+ >+ if (strncmpic(s, US"MAILMAX=", 8) == 0) >+ { >+ sx->peer_limit_mail = atoi(CS (s += 8)); >+ while (isdigit(*s)) s++; >+ } >+ else if (strncmpic(s, US"RCPTMAX=", 8) == 0) >+ { >+ sx->peer_limit_rcpt = atoi(CS (s += 8)); >+ while (isdigit(*s)) s++; >+ } >+ else if (strncmpic(s, US"RCPTDOMAINMAX=", 14) == 0) >+ { >+ sx->peer_limit_rcptdom = atoi(CS (s += 14)); >+ while (isdigit(*s)) s++; >+ } >+ else >+ while (*s && !isspace(*s)) s++; >+ } >+} >+ >+/* Apply given values to the current connection */ >+static void >+ehlo_limits_apply(smtp_context * sx, >+ unsigned limit_mail, unsigned limit_rcpt, unsigned limit_rcptdom) >+{ >+if (limit_mail && limit_mail < sx->max_mail) sx->max_mail = limit_mail; >+if (limit_rcpt && limit_rcpt < sx->max_rcpt) sx->max_rcpt = limit_rcpt; >+if (limit_rcptdom) >+ { >+ DEBUG(D_transport) debug_printf("will treat as !multi_domain\n"); >+ sx->single_rcpt_domain = TRUE; >+ } >+} >+ >+/* Apply values from EHLO-resp to the current connection */ >+static void >+ehlo_response_limits_apply(smtp_context * sx) >+{ >+ehlo_limits_apply(sx, sx->peer_limit_mail, sx->peer_limit_rcpt, >+ sx->peer_limit_rcptdom); >+} >+ >+/* Apply values read from cache to the current connection */ >+static void >+ehlo_cache_limits_apply(smtp_context * sx) >+{ >+# ifndef DISABLE_PIPE_CONNECT >+ehlo_limits_apply(sx, sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt, >+ sx->ehlo_resp.limit_rcptdom); >+# endif >+} >+#endif /*EXPERIMENTAL_ESMTP_LIMITS*/ >+ >+/******************************************************************************/ > > #ifndef DISABLE_PIPE_CONNECT > static uschar * >@@ -820,19 +876,45 @@ > host->port == PORT_NONE ? sx->port : host->port); > } > >+/* Cache EHLO-response info for use by early-pipe. >+Called >+- During a normal flow on EHLO response (either cleartext or under TLS), >+ when we are willing to do PIPECONNECT and it is offered >+- During an early-pipe flow on receiving the actual EHLO response and noting >+ disparity versus the cached info used, when PIPECONNECT is still being offered >+ >+We assume that suitable values have been set in the sx.ehlo_resp structure for >+features and auths; we handle the copy of limits. */ >+ > static void >-write_ehlo_cache_entry(const smtp_context * sx) >+write_ehlo_cache_entry(smtp_context * sx) > { > open_db dbblock, * dbm_file; > >+# ifdef EXPERIMENTAL_ESMTP_LIMITS >+sx->ehlo_resp.limit_mail = sx->peer_limit_mail; >+sx->ehlo_resp.limit_rcpt = sx->peer_limit_rcpt; >+sx->ehlo_resp.limit_rcptdom = sx->peer_limit_rcptdom; >+# endif >+ > if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE))) > { > uschar * ehlo_resp_key = ehlo_cache_key(sx); > dbdata_ehlo_resp er = { .data = sx->ehlo_resp }; > >- HDEBUG(D_transport) debug_printf("writing clr %04x/%04x cry %04x/%04x\n", >- sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths, >- sx->ehlo_resp.crypted_features, sx->ehlo_resp.crypted_auths); >+ HDEBUG(D_transport) >+# ifdef EXPERIMENTAL_ESMTP_LIMITS >+ if (sx->ehlo_resp.limit_mail || sx->ehlo_resp.limit_rcpt || sx->ehlo_resp.limit_rcptdom) >+ debug_printf("writing clr %04x/%04x cry %04x/%04x lim %05d/%05d/%05d\n", >+ sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths, >+ sx->ehlo_resp.crypted_features, sx->ehlo_resp.crypted_auths, >+ sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt, >+ sx->ehlo_resp.limit_rcptdom); >+ else >+# endif >+ debug_printf("writing clr %04x/%04x cry %04x/%04x\n", >+ sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths, >+ sx->ehlo_resp.crypted_features, sx->ehlo_resp.crypted_auths); > > dbfn_write(dbm_file, ehlo_resp_key, &er, (int)sizeof(er)); > dbfn_close(dbm_file); >@@ -866,7 +948,7 @@ > uschar * ehlo_resp_key = ehlo_cache_key(sx); > dbdata_ehlo_resp * er; > >- if (!(er = dbfn_read(dbm_file, ehlo_resp_key))) >+ if (!(er = dbfn_read_enforce_length(dbm_file, ehlo_resp_key, sizeof(dbdata_ehlo_resp)))) > { DEBUG(D_transport) debug_printf("no ehlo-resp record\n"); } > else if (time(NULL) - er->time_stamp > retry_data_expire) > { >@@ -877,12 +959,26 @@ > } > else > { >+ DEBUG(D_transport) >+# ifdef EXPERIMENTAL_ESMTP_LIMITS >+ if (er->data.limit_mail || er->data.limit_rcpt || er->data.limit_rcptdom) >+ debug_printf("EHLO response bits from cache:" >+ " cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x lim %05d/%05d/%05d\n", >+ er->data.cleartext_features, er->data.cleartext_auths, >+ er->data.crypted_features, er->data.crypted_auths, >+ er->data.limit_mail, er->data.limit_rcpt, er->data.limit_rcptdom); >+ else >+# endif >+ debug_printf("EHLO response bits from cache:" >+ " cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x\n", >+ er->data.cleartext_features, er->data.cleartext_auths, >+ er->data.crypted_features, er->data.crypted_auths); >+ > sx->ehlo_resp = er->data; >+# ifdef EXPERIMENTAL_ESMTP_LIMITS >+ ehlo_cache_limits_apply(sx); >+# endif > dbfn_close(dbm_file); >- DEBUG(D_transport) debug_printf( >- "EHLO response bits from cache: cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x\n", >- er->data.cleartext_features, er->data.cleartext_auths, >- er->data.crypted_features, er->data.crypted_auths); > return TRUE; > } > dbfn_close(dbm_file); >@@ -968,6 +1064,8 @@ > if (tls_out.active.sock >= 0) rc = DEFER; > goto fail; > } >+ /*XXX EXPERIMENTAL_ESMTP_LIMITS ? */ >+ ehlo_response_lbserver(sx, sx->conn_args.ob); > } > > if (pending_EHLO) >@@ -984,8 +1082,9 @@ > goto fail; > } > >- /* Compare the actual EHLO response to the cached value we assumed; >- on difference, dump or rewrite the cache and arrange for a retry. */ >+ /* Compare the actual EHLO response extensions and AUTH methods to the cached >+ value we assumed; on difference, dump or rewrite the cache and arrange for a >+ retry. */ > > ap = tls_out.active.sock < 0 > ? &sx->ehlo_resp.cleartext_auths : &sx->ehlo_resp.crypted_auths; >@@ -995,6 +1094,10 @@ > | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE > | OPTION_UTF8 | OPTION_EARLY_PIPE > ); >+# ifdef EXPERIMENTAL_ESMTP_LIMITS >+ if (tls_out.active.sock >= 0 || !(peer_offered & OPTION_TLS)) >+ ehlo_response_limits_read(sx); >+# endif > if ( peer_offered != sx->peer_offered > || (authbits = study_ehlo_auths(sx)) != *ap) > { >@@ -1002,16 +1105,44 @@ > debug_printf("EHLO %s extensions changed, 0x%04x/0x%04x -> 0x%04x/0x%04x\n", > tls_out.active.sock < 0 ? "cleartext" : "crypted", > sx->peer_offered, *ap, peer_offered, authbits); >- *(tls_out.active.sock < 0 >- ? &sx->ehlo_resp.cleartext_features : &sx->ehlo_resp.crypted_features) = peer_offered; >- *ap = authbits; > if (peer_offered & OPTION_EARLY_PIPE) >+ { >+ *(tls_out.active.sock < 0 >+ ? &sx->ehlo_resp.cleartext_features : &sx->ehlo_resp.crypted_features) = >+ peer_offered; >+ *ap = authbits; > write_ehlo_cache_entry(sx); >+ } > else > invalidate_ehlo_cache_entry(sx); > > return OK; /* just carry on */ > } >+# ifdef EXPERIMENTAL_ESMTP_LIMITS >+ /* If we are handling LIMITS, compare the actual EHLO LIMITS values with the >+ cached values and invalidate cache if different. OK to carry on with >+ connect since values are advisory. */ >+ { >+ if ( (tls_out.active.sock >= 0 || !(peer_offered & OPTION_TLS)) >+ && ( sx->peer_limit_mail != sx->ehlo_resp.limit_mail >+ || sx->peer_limit_rcpt != sx->ehlo_resp.limit_rcpt >+ || sx->peer_limit_rcptdom != sx->ehlo_resp.limit_rcptdom >+ ) ) >+ { >+ HDEBUG(D_transport) >+ { >+ debug_printf("EHLO LIMITS changed:"); >+ if (sx->peer_limit_mail != sx->ehlo_resp.limit_mail) >+ debug_printf(" MAILMAX %u -> %u\n", sx->ehlo_resp.limit_mail, sx->peer_limit_mail); >+ else if (sx->peer_limit_rcpt != sx->ehlo_resp.limit_rcpt) >+ debug_printf(" RCPTMAX %u -> %u\n", sx->ehlo_resp.limit_rcpt, sx->peer_limit_rcpt); >+ else >+ debug_printf(" RCPTDOMAINMAX %u -> %u\n", sx->ehlo_resp.limit_rcptdom, sx->peer_limit_rcptdom); >+ } >+ invalidate_ehlo_cache_entry(sx); >+ } >+ } >+# endif > } > return OK; > >@@ -1133,7 +1264,7 @@ > /* The address was accepted */ > addr->host_used = sx->conn_args.host; > >- DEBUG(D_transport) debug_printf("%s expect rcpt\n", __FUNCTION__); >+ DEBUG(D_transport) debug_printf("%s expect rcpt for %s\n", __FUNCTION__, addr->address); > if (smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), > '2', ob->command_timeout)) > { >@@ -1334,10 +1465,17 @@ > host_item * host = sx->conn_args.host; /* host to deliver to */ > int rc; > >+/* Set up globals for error messages */ >+ >+authenticator_name = au->name; >+driver_srcfile = au->srcfile; >+driver_srcline = au->srcline; >+ > sx->outblock.authenticating = TRUE; > rc = (au->info->clientcode)(au, sx, ob->command_timeout, > sx->buffer, sizeof(sx->buffer)); > sx->outblock.authenticating = FALSE; >+driver_srcfile = authenticator_name = NULL; driver_srcline = 0; > DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n", au->name, rc); > > /* A temporary authentication failure must hold up delivery to >@@ -1688,8 +1826,9 @@ > current_local_identity = > smtp_local_identity(s_compare->current_sender_address, s_compare->tblock); > >-if (!(new_sender_address = deliver_get_sender_address(message_id))) >- return FALSE; >+if (!(new_sender_address = spool_sender_from_msgid(message_id))) >+ return FALSE; >+ > > message_local_identity = > smtp_local_identity(new_sender_address, s_compare->tblock); >@@ -1702,57 +1841,65 @@ > static unsigned > ehlo_response(uschar * buf, unsigned checks) > { >-size_t bsize = Ustrlen(buf); >+PCRE2_SIZE bsize = Ustrlen(buf); >+pcre2_match_data * md = pcre2_match_data_create(1, pcre_gen_ctx); > > /* debug_printf("%s: check for 0x%04x\n", __FUNCTION__, checks); */ > > #ifndef DISABLE_TLS > if ( checks & OPTION_TLS >- && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) >+ && pcre2_match(regex_STARTTLS, >+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) > #endif > checks &= ~OPTION_TLS; > > if ( checks & OPTION_IGNQ >- && pcre_exec(regex_IGNOREQUOTA, NULL, CS buf, bsize, 0, >- PCRE_EOPT, NULL, 0) < 0) >+ && pcre2_match(regex_IGNOREQUOTA, >+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) > checks &= ~OPTION_IGNQ; > > if ( checks & OPTION_CHUNKING >- && pcre_exec(regex_CHUNKING, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) >+ && pcre2_match(regex_CHUNKING, >+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) > checks &= ~OPTION_CHUNKING; > > #ifndef DISABLE_PRDR > if ( checks & OPTION_PRDR >- && pcre_exec(regex_PRDR, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) >+ && pcre2_match(regex_PRDR, >+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) > #endif > checks &= ~OPTION_PRDR; > > #ifdef SUPPORT_I18N > if ( checks & OPTION_UTF8 >- && pcre_exec(regex_UTF8, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) >+ && pcre2_match(regex_UTF8, >+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) > #endif > checks &= ~OPTION_UTF8; > > if ( checks & OPTION_DSN >- && pcre_exec(regex_DSN, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) >+ && pcre2_match(regex_DSN, >+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) > checks &= ~OPTION_DSN; > > if ( checks & OPTION_PIPE >- && pcre_exec(regex_PIPELINING, NULL, CS buf, bsize, 0, >- PCRE_EOPT, NULL, 0) < 0) >+ && pcre2_match(regex_PIPELINING, >+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) > checks &= ~OPTION_PIPE; > > if ( checks & OPTION_SIZE >- && pcre_exec(regex_SIZE, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) >+ && pcre2_match(regex_SIZE, >+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) > checks &= ~OPTION_SIZE; > > #ifndef DISABLE_PIPE_CONNECT > if ( checks & OPTION_EARLY_PIPE >- && pcre_exec(regex_EARLY_PIPE, NULL, CS buf, bsize, 0, >- PCRE_EOPT, NULL, 0) < 0) >+ && pcre2_match(regex_EARLY_PIPE, >+ (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) > #endif > checks &= ~OPTION_EARLY_PIPE; > >+pcre2_match_data_free(md); > /* debug_printf("%s: found 0x%04x\n", __FUNCTION__, checks); */ > return checks; > } >@@ -1877,6 +2024,36 @@ > > > >+#ifdef SUPPORT_DANE >+static int >+check_force_dane_conn(smtp_context * sx, smtp_transport_options_block * ob) >+{ >+int rc; >+if( sx->dane_required >+ || verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK >+ ) >+ switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required)) >+ { >+ case OK: sx->conn_args.dane = TRUE; >+ ob->tls_tempfail_tryclear = FALSE; /* force TLS */ >+ ob->tls_sni = sx->conn_args.host->name; /* force SNI */ >+ break; >+ case FAIL_FORCED: break; >+ default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER, >+ string_sprintf("DANE error: tlsa lookup %s", >+ rc_to_string(rc)), >+ rc, FALSE, &sx->delivery_start); >+# ifndef DISABLE_EVENT >+ (void) event_raise(sx->conn_args.tblock->event_action, >+ US"dane:fail", sx->dane_required >+ ? US"dane-required" : US"dnssec-invalid", >+ NULL); >+# endif >+ return rc; >+ } >+return OK; >+} >+#endif > > > /************************************************* >@@ -1885,7 +2062,7 @@ > > /* > Arguments: >- ctx connection context >+ sx connection context > suppress_tls if TRUE, don't attempt a TLS connection - this is set for > a second attempt after TLS initialization fails > >@@ -1909,43 +2086,31 @@ > uschar * tls_errstr; > #endif > >+/* Many lines of clearing individual elements of *sx that used to >+be here have been replaced by a full memset to zero (de41aff051). >+There are two callers, this file and verify.c . Now we only set >+up nonzero elements. */ >+ > sx->conn_args.ob = ob; > > sx->lmtp = strcmpic(ob->protocol, US"lmtp") == 0; > sx->smtps = strcmpic(ob->protocol, US"smtps") == 0; >-/* sx->ok = FALSE; */ > sx->send_rset = TRUE; > sx->send_quit = TRUE; > sx->setting_up = TRUE; > sx->esmtp = TRUE; >-/* sx->esmtp_sent = FALSE; */ >-#ifdef SUPPORT_I18N >-/* sx->utf8_needed = FALSE; */ >-#endif > sx->dsn_all_lasthop = TRUE; > #ifdef SUPPORT_DANE >-/* sx->conn_args.dane = FALSE; */ > sx->dane_required = > verify_check_given_host(CUSS &ob->hosts_require_dane, sx->conn_args.host) == OK; > #endif >-#ifndef DISABLE_PIPE_CONNECT >-/* sx->early_pipe_active = sx->early_pipe_ok = FALSE; */ >-/* sx->ehlo_resp.cleartext_features = sx->ehlo_resp.crypted_features = 0; */ >-/* sx->pending_BANNER = sx->pending_EHLO = sx->pending_MAIL = FALSE; */ >-#endif > >-if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999; >-/* sx->peer_offered = 0; */ >-/* sx->avoid_option = 0; */ >+if ((sx->max_mail = sx->conn_args.tblock->connection_max_messages) == 0) sx->max_mail = 999999; >+if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999; > sx->igquotstr = US""; > if (!sx->helo_data) sx->helo_data = ob->helo_data; >-#ifdef EXPERIMENTAL_DSN_INFO >-/* sx->smtp_greeting = NULL; */ >-/* sx->helo_response = NULL; */ >-#endif > > smtp_command = US"initial connection"; >-/* sx->buffer[0] = '\0'; */ > > /* Set up the buffer for reading SMTP response packets. */ > >@@ -1959,9 +2124,6 @@ > sx->outblock.buffer = sx->outbuffer; > sx->outblock.buffersize = sizeof(sx->outbuffer); > sx->outblock.ptr = sx->outbuffer; >-/* sx->outblock.cmd_count = 0; */ >-/* sx->outblock.authenticating = FALSE; */ >-/* sx->outblock.conn_args = NULL; */ > > /* Reset the parameters of a TLS session. */ > >@@ -1974,7 +2136,7 @@ > tls_out.sni = NULL; > #endif > tls_out.ocsp = OCSP_NOT_REQ; >-#ifdef EXPERIMENTAL_TLS_RESUME >+#ifndef DISABLE_TLS_RESUME > tls_out.resumption = 0; > #endif > tls_out.ver = NULL; >@@ -2003,32 +2165,14 @@ > const uschar * sni = US""; > > # ifdef SUPPORT_DANE >- /* Check if the message will be DANE-verified; if so force its SNI */ >+ /* Check if the message will be DANE-verified; if so force TLS and its SNI */ > > tls_out.dane_verified = FALSE; > smtp_port_for_connect(sx->conn_args.host, sx->port); > if ( sx->conn_args.host->dnssec == DS_YES >- && ( sx->dane_required >- || verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK >- ) ) >- switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required)) >- { >- case OK: sx->conn_args.dane = TRUE; >- ob->tls_tempfail_tryclear = FALSE; /* force TLS */ >- ob->tls_sni = sx->first_addr->domain; /* force SNI */ >- break; >- case FAIL_FORCED: break; >- default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER, >- string_sprintf("DANE error: tlsa lookup %s", >- rc_to_string(rc)), >- rc, FALSE, &sx->delivery_start); >-# ifndef DISABLE_EVENT >- (void) event_raise(sx->conn_args.tblock->event_action, >- US"dane:fail", sx->dane_required >- ? US"dane-required" : US"dnssec-invalid"); >-# endif >- return rc; >- } >+ && (rc = check_force_dane_conn(sx, ob)) != OK >+ ) >+ return rc; > # endif > > /* If the SNI or the DANE status required for the new message differs from the >@@ -2056,7 +2200,7 @@ > DEBUG(D_transport) > debug_printf("Closing proxied-TLS connection due to SNI mismatch\n"); > >- HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> QUIT\n"); >+ smtp_debug_cmd(US"QUIT", 0); > write(0, "QUIT\r\n", 6); > close(0); > continue_hostname = continue_proxy_cipher = NULL; >@@ -2077,6 +2221,11 @@ > if (sx->verify) > HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->conn_args.interface, sx->port); > >+ /* Arrange to report to calling process this is a new connection */ >+ >+ clearflag(sx->first_addr, af_cont_conn); >+ setflag(sx->first_addr, af_new_conn); >+ > /* Get the actual port the connection will use, into sx->conn_args.host */ > > smtp_port_for_connect(sx->conn_args.host, sx->port); >@@ -2090,27 +2239,8 @@ > if (sx->conn_args.host->dnssec == DS_YES) > { > int rc; >- if( sx->dane_required >- || verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK >- ) >- switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required)) >- { >- case OK: sx->conn_args.dane = TRUE; >- ob->tls_tempfail_tryclear = FALSE; /* force TLS */ >- ob->tls_sni = sx->first_addr->domain; /* force SNI */ >- break; >- case FAIL_FORCED: break; >- default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER, >- string_sprintf("DANE error: tlsa lookup %s", >- rc_to_string(rc)), >- rc, FALSE, &sx->delivery_start); >-# ifndef DISABLE_EVENT >- (void) event_raise(sx->conn_args.tblock->event_action, >- US"dane:fail", sx->dane_required >- ? US"dane-required" : US"dnssec-invalid"); >-# endif >- return rc; >- } >+ if ((rc = check_force_dane_conn(sx, ob)) != OK) >+ return rc; > } > else if (sx->dane_required) > { >@@ -2119,7 +2249,7 @@ > FAIL, FALSE, &sx->delivery_start); > # ifndef DISABLE_EVENT > (void) event_raise(sx->conn_args.tblock->event_action, >- US"dane:fail", US"dane-required"); >+ US"dane:fail", US"dane-required", NULL); > # endif > return FAIL; > } >@@ -2130,7 +2260,13 @@ > > sx->cctx.tls_ctx = NULL; > sx->inblock.cctx = sx->outblock.cctx = &sx->cctx; >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom = >+#endif > sx->avoid_option = sx->peer_offered = smtp_peer_options = 0; >+#ifndef DISABLE_CLIENT_CMD_LOG >+ client_cmd_log = NULL; >+#endif > > #ifndef DISABLE_PIPE_CONNECT > if ( verify_check_given_host(CUSS &ob->hosts_pipe_connect, >@@ -2140,6 +2276,7 @@ > the helo string might use it avoid doing early-pipelining. */ > > if ( !sx->helo_data >+ || sx->conn_args.interface > || !Ustrstr(sx->helo_data, "$sending_ip_address") > || Ustrstr(sx->helo_data, "def:sending_ip_address") > ) >@@ -2149,21 +2286,29 @@ > && sx->ehlo_resp.cleartext_features & OPTION_EARLY_PIPE) > { > DEBUG(D_transport) >- debug_printf("Using cached cleartext PIPE_CONNECT\n"); >+ debug_printf("Using cached cleartext PIPECONNECT\n"); > sx->early_pipe_active = TRUE; > sx->peer_offered = sx->ehlo_resp.cleartext_features; > } > } > else DEBUG(D_transport) >- debug_printf("helo needs $sending_ip_address\n"); >+ debug_printf("helo needs $sending_ip_address; avoid early-pipelining\n"); > > PIPE_CONNECT_RETRY: > if (sx->early_pipe_active) >+ { > sx->outblock.conn_args = &sx->conn_args; >+ (void) smtp_boundsock(&sx->conn_args); >+ } > else > #endif > { >- if ((sx->cctx.sock = smtp_connect(&sx->conn_args, NULL)) < 0) >+ blob lazy_conn = {.data = NULL}; >+ /* For TLS-connect, a TFO lazy-connect is useful since the Client Hello >+ can go on the TCP SYN. */ >+ >+ if ((sx->cctx.sock = smtp_connect(&sx->conn_args, >+ sx->smtps ? &lazy_conn : NULL)) < 0) > { > set_errno_nohost(sx->addrlist, > errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno, >@@ -2172,12 +2317,17 @@ > sx->send_quit = FALSE; > return DEFER; > } >+#ifdef TCP_QUICKACK >+ (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off, >+ sizeof(off)); >+#endif > } > /* Expand the greeting message while waiting for the initial response. (Makes > sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is >- delayed till here so that $sending_interface and $sending_port are set. */ >-/*XXX early-pipe: they still will not be. Is there any way to find out what they >-will be? Somehow I doubt it. */ >+ delayed till here so that $sending_ip_address and $sending_port are set. >+ Those will be known even for a TFO lazy-connect, having been set by the bind(). >+ For early-pipe, we are ok if binding to a local interface; otherwise (if >+ $sending_ip_address is seen in helo_data) we disabled early-pipe above. */ > > if (sx->helo_data) > if (!(sx->helo_data = expand_string(sx->helo_data))) >@@ -2217,10 +2367,6 @@ > else > #endif > { >-#ifdef TCP_QUICKACK >- (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off, >- sizeof(off)); >-#endif > if (!smtp_reap_banner(sx)) > goto RESPONSE_FAILED; > } >@@ -2230,7 +2376,7 @@ > uschar * s; > lookup_dnssec_authenticated = sx->conn_args.host->dnssec==DS_YES ? US"yes" > : sx->conn_args.host->dnssec==DS_NO ? US"no" : NULL; >- s = event_raise(sx->conn_args.tblock->event_action, US"smtp:connect", sx->buffer); >+ s = event_raise(sx->conn_args.tblock->event_action, US"smtp:connect", sx->buffer, NULL); > if (s) > { > set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, >@@ -2403,6 +2549,13 @@ > ) > #endif > ); >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ if (tls_out.active.sock >= 0 || !(sx->peer_offered & OPTION_TLS)) >+ { >+ ehlo_response_limits_read(sx); >+ ehlo_response_limits_apply(sx); >+ } >+#endif > #ifndef DISABLE_PIPE_CONNECT > if (sx->early_pipe_ok) > { >@@ -2411,12 +2564,13 @@ > if ( (sx->peer_offered & (OPTION_PIPE | OPTION_EARLY_PIPE)) > == (OPTION_PIPE | OPTION_EARLY_PIPE)) > { >- DEBUG(D_transport) debug_printf("PIPE_CONNECT usable in future for this IP\n"); >+ DEBUG(D_transport) debug_printf("PIPECONNECT usable in future for this IP\n"); > sx->ehlo_resp.cleartext_auths = study_ehlo_auths(sx); > write_ehlo_cache_entry(sx); > } > } > #endif >+ ehlo_response_lbserver(sx, ob); > } > > /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */ >@@ -2457,6 +2611,13 @@ > sx->inblock.cctx = sx->outblock.cctx = &sx->cctx; > smtp_command = big_buffer; > sx->peer_offered = smtp_peer_options; >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ /* Limits passed by cmdline over exec. */ >+ ehlo_limits_apply(sx, >+ sx->peer_limit_mail = continue_limit_mail, >+ sx->peer_limit_rcpt = continue_limit_rcpt, >+ sx->peer_limit_rcptdom = continue_limit_rcptdom); >+#endif > sx->helo_data = NULL; /* ensure we re-expand ob->helo_data */ > > /* For a continued connection with TLS being proxied for us, or a >@@ -2498,16 +2659,20 @@ > > #ifndef DISABLE_PIPE_CONNECT > /* If doing early-pipelining reap the banner and EHLO-response but leave >- the response for the STARTTLS we just sent alone. */ >+ the response for the STARTTLS we just sent alone. On fail, assume wrong >+ cached capability and retry with the pipelining disabled. */ > >- if (sx->early_pipe_active && sync_responses(sx, 2, 0) != 0) >+ if (sx->early_pipe_active) > { >- HDEBUG(D_transport) >- debug_printf("failed reaping pipelined cmd responses\n"); >- close(sx->cctx.sock); >- sx->cctx.sock = -1; >- sx->early_pipe_active = FALSE; >- goto PIPE_CONNECT_RETRY; >+ if (sync_responses(sx, 2, 0) != 0) >+ { >+ HDEBUG(D_transport) >+ debug_printf("failed reaping pipelined cmd responses\n"); >+ close(sx->cctx.sock); >+ sx->cctx.sock = -1; >+ sx->early_pipe_active = FALSE; >+ goto PIPE_CONNECT_RETRY; >+ } > } > #endif > >@@ -2536,14 +2701,13 @@ > else > TLS_NEGOTIATE: > { >+ sx->conn_args.sending_ip_address = sending_ip_address; > if (!tls_client_start(&sx->cctx, &sx->conn_args, sx->addrlist, &tls_out, &tls_errstr)) > { > /* TLS negotiation failed; give an error. From outside, this function may > be called again to try in clear on a new connection, if the options permit > it for this host. */ >-#ifdef USE_GNUTLS >- GNUTLS_CONN_FAILED: >-#endif >+ TLS_CONN_FAILED: > DEBUG(D_tls) debug_printf("TLS session fail: %s\n", tls_errstr); > > # ifdef SUPPORT_DANE >@@ -2554,7 +2718,7 @@ > sx->conn_args.host->name, sx->conn_args.host->address, tls_errstr); > # ifndef DISABLE_EVENT > (void) event_raise(sx->conn_args.tblock->event_action, >- US"dane:fail", US"validation-failure"); /* could do with better detail */ >+ US"dane:fail", US"validation-failure", NULL); /* could do with better detail */ > # endif > } > # endif >@@ -2564,8 +2728,25 @@ > sx->send_quit = FALSE; > goto TLS_FAILED; > } >+ sx->send_tlsclose = TRUE; >+ >+ /* TLS session is set up. Check the inblock fill level. If there is >+ content then as we have not yet done a tls read it must have arrived before >+ the TLS handshake, in-clear. That violates the sync requirement of the >+ STARTTLS RFC, so fail. */ > >- /* TLS session is set up */ >+ if (sx->inblock.ptr != sx->inblock.ptrend) >+ { >+ DEBUG(D_tls) >+ { >+ int i = sx->inblock.ptrend - sx->inblock.ptr; >+ debug_printf("unused data in input buffer after ack for STARTTLS:\n" >+ "'%.*s'%s\n", >+ i > 100 ? 100 : i, sx->inblock.ptr, i > 100 ? "..." : ""); >+ } >+ tls_errstr = US"synch error before connect"; >+ goto TLS_CONN_FAILED; >+ } > > smtp_peer_options_wrap = smtp_peer_options; > for (address_item * addr = sx->addrlist; addr; addr = addr->next) >@@ -2616,9 +2797,16 @@ > sx->peer_offered = sx->ehlo_resp.crypted_features; > if ((sx->early_pipe_active = > !!(sx->ehlo_resp.crypted_features & OPTION_EARLY_PIPE))) >- DEBUG(D_transport) debug_printf("Using cached crypted PIPE_CONNECT\n"); >+ DEBUG(D_transport) debug_printf("Using cached crypted PIPECONNECT\n"); > } > #endif >+#ifdef EXPERIMMENTAL_ESMTP_LIMITS >+ /* As we are about to send another EHLO, forget any LIMITS received so far. */ >+ sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom = 0; >+ if ((sx->max_mail = sx->conn_args.tblock->connection_max_message) == 0) sx->max_mail = 999999; >+ if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999; >+ sx->single_rcpt_domain = FALSE; >+#endif > > /* For SMTPS we need to wait for the initial OK response. */ > if (sx->smtps) >@@ -2670,7 +2858,7 @@ > Can it do that, with all the flexibility we need? */ > > tls_errstr = US"error on first read"; >- goto GNUTLS_CONN_FAILED; >+ goto TLS_CONN_FAILED; > } > #else > goto RESPONSE_FAILED; >@@ -2698,7 +2886,8 @@ > (void) event_raise(sx->conn_args.tblock->event_action, US"dane:fail", > smtp_peer_options & OPTION_TLS > ? US"validation-failure" /* could do with better detail */ >- : US"starttls-not-supported"); >+ : US"starttls-not-supported", >+ NULL); > # endif > goto TLS_FAILED; > } >@@ -2709,7 +2898,7 @@ > continued session down a previously-used socket, we haven't just done EHLO, so > we skip this. */ > >-if (continue_hostname == NULL >+if ( !continue_hostname > #ifndef DISABLE_TLS > || tls_out.active.sock >= 0 > #endif >@@ -2748,6 +2937,14 @@ > if (tls_out.active.sock >= 0) > sx->ehlo_resp.crypted_features = sx->peer_offered; > #endif >+ >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ if (tls_out.active.sock >= 0 || !(sx->peer_offered & OPTION_TLS)) >+ { >+ ehlo_response_limits_read(sx); >+ ehlo_response_limits_apply(sx); >+ } >+#endif > } > > /* Set for IGNOREQUOTA if the response to LHLO specifies support and the >@@ -2772,18 +2969,18 @@ > smtp_peer_options & OPTION_PIPE ? "" : "not "); > > if ( sx->peer_offered & OPTION_CHUNKING >- && verify_check_given_host(CUSS &ob->hosts_try_chunking, sx->conn_args.host) != OK) >- sx->peer_offered &= ~OPTION_CHUNKING; >+ && verify_check_given_host(CUSS &ob->hosts_try_chunking, sx->conn_args.host) == OK) >+ smtp_peer_options |= OPTION_CHUNKING; > >- if (sx->peer_offered & OPTION_CHUNKING) >+ if (smtp_peer_options & OPTION_CHUNKING) > DEBUG(D_transport) debug_printf("CHUNKING usable\n"); > > #ifndef DISABLE_PRDR > if ( sx->peer_offered & OPTION_PRDR >- && verify_check_given_host(CUSS &ob->hosts_try_prdr, sx->conn_args.host) != OK) >- sx->peer_offered &= ~OPTION_PRDR; >+ && verify_check_given_host(CUSS &ob->hosts_try_prdr, sx->conn_args.host) == OK) >+ smtp_peer_options |= OPTION_PRDR; > >- if (sx->peer_offered & OPTION_PRDR) >+ if (smtp_peer_options & OPTION_PRDR) > DEBUG(D_transport) debug_printf("PRDR usable\n"); > #endif > >@@ -2800,7 +2997,7 @@ > && ( sx->ehlo_resp.cleartext_features | sx->ehlo_resp.crypted_features) > & OPTION_EARLY_PIPE) > { >- DEBUG(D_transport) debug_printf("PIPE_CONNECT usable in future for this IP\n"); >+ DEBUG(D_transport) debug_printf("PIPECONNECT usable in future for this IP\n"); > sx->ehlo_resp.crypted_auths = study_ehlo_auths(sx); > write_ehlo_cache_entry(sx); > } >@@ -2915,7 +3112,7 @@ > > SEND_FAILED: > code = '4'; >- message = US string_sprintf("send() to %s [%s] failed: %s", >+ message = US string_sprintf("smtp send to %s [%s] failed: %s", > sx->conn_args.host->name, sx->conn_args.host->address, strerror(errno)); > sx->send_quit = FALSE; > yield = DEFER; >@@ -2981,7 +3178,11 @@ > #ifndef DISABLE_TLS > if (sx->cctx.tls_ctx) > { >- tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT); >+ if (sx->send_tlsclose) >+ { >+ tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT); >+ sx->send_tlsclose = FALSE; >+ } > sx->cctx.tls_ctx = NULL; > } > #endif >@@ -3004,9 +3205,10 @@ > sx->cctx.sock = -1; > > #ifndef DISABLE_EVENT >-(void) event_raise(sx->conn_args.tblock->event_action, US"tcp:close", NULL); >+(void) event_raise(sx->conn_args.tblock->event_action, US"tcp:close", NULL, NULL); > #endif > >+smtp_debug_cmd_report(); > continue_transport = NULL; > continue_hostname = NULL; > return yield; >@@ -3049,7 +3251,7 @@ > request that */ > > sx->prdr_active = FALSE; >-if (sx->peer_offered & OPTION_PRDR) >+if (smtp_peer_options & OPTION_PRDR) > for (address_item * addr = addrlist; addr; addr = addr->next) > if (addr->transport_return == PENDING_DEFER) > { >@@ -3077,7 +3279,7 @@ > > /* check if all addresses have DSN-lasthop flag; do not send RET and ENVID if so */ > for (sx->dsn_all_lasthop = TRUE, addr = addrlist, address_count = 0; >- addr && address_count < sx->max_rcpt; >+ addr && address_count < sx->max_rcpt; /*XXX maybe also || sx->single_rcpt_domain ? */ > addr = addr->next) if (addr->transport_return == PENDING_DEFER) > { > address_count++; >@@ -3165,6 +3367,9 @@ > smtp_write_mail_and_rcpt_cmds(smtp_context * sx, int * yield) > { > address_item * addr; >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+address_item * restart_addr = NULL; >+#endif > int address_count, pipe_limit; > int rc; > >@@ -3249,13 +3454,31 @@ > For verify we flush the pipeline after any (the only) rcpt address. */ > > for (addr = sx->first_addr, address_count = 0, pipe_limit = 100; >- addr && address_count < sx->max_rcpt; >+ addr && address_count < sx->max_rcpt; > addr = addr->next) if (addr->transport_return == PENDING_DEFER) > { > int cmds_sent; > BOOL no_flush; > uschar * rcpt_addr; > >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ if ( sx->single_rcpt_domain /* restriction on domains */ >+ && address_count > 0 /* not first being sent */ >+ && Ustrcmp(addr->domain, sx->first_addr->domain) != 0 /* dom diff from first */ >+ ) >+ { >+ DEBUG(D_transport) debug_printf("skipping different domain %s\n", addr->domain); >+ >+ /* Ensure the smtp-response reaper does not think the address had a RCPT >+ command sent for it. Reset to PENDING_DEFER in smtp_deliver(), where we >+ goto SEND_MESSAGE. */ >+ >+ addr->transport_return = SKIP; >+ if (!restart_addr) restart_addr = addr; /* note restart point */ >+ continue; /* skip this one */ >+ } >+#endif >+ > addr->dsn_aware = sx->peer_offered & OPTION_DSN > ? dsn_support_yes : dsn_support_no; > >@@ -3327,7 +3550,11 @@ > } > } /* Loop for next address */ > >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+sx->next_addr = restart_addr ? restart_addr : addr; >+#else > sx->next_addr = addr; >+#endif > return 0; > } > >@@ -3349,40 +3576,35 @@ > bufsiz size of buffer > pfd pipe filedescriptor array; [0] is comms to proxied process > timeout per-read timeout, seconds >+ host hostname of remote > > Does not return. > */ > > void > smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd, >- int timeout) >+ int timeout, const uschar * host) > { >-fd_set rfds, efds; >-int max_fd = MAX(pfd[0], tls_out.active.sock) + 1; >+struct pollfd p[2] = {{.fd = tls_out.active.sock, .events = POLLIN}, >+ {.fd = pfd[0], .events = POLLIN}}; > int rc, i; >+BOOL send_tls_shutdown = TRUE; > > close(pfd[1]); > if ((rc = exim_fork(US"tls-proxy"))) > _exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS); > >-set_process_info("proxying TLS connection for continued transport"); >-FD_ZERO(&rfds); >-FD_SET(tls_out.active.sock, &rfds); >-FD_SET(pfd[0], &rfds); >+set_process_info("proxying TLS connection for continued transport to %s\n", host); > >-for (int fd_bits = 3; fd_bits; ) >+do > { > time_t time_left = timeout; > time_t time_start = time(NULL); > > /* wait for data */ >- efds = rfds; > do > { >- struct timeval tv = { time_left, 0 }; >- >- rc = select(max_fd, >- (SELECT_ARG2_TYPE *)&rfds, NULL, (SELECT_ARG2_TYPE *)&efds, &tv); >+ rc = poll(p, 2, time_left * 1000); > > if (rc < 0 && errno == EINTR) > if ((time_left -= time(NULL) - time_start) > 0) continue; >@@ -3393,51 +3615,56 @@ > goto done; > } > >- if (FD_ISSET(tls_out.active.sock, &efds) || FD_ISSET(pfd[0], &efds)) >+ /* For errors where not readable, bomb out */ >+ >+ if (p[0].revents & POLLERR || p[1].revents & POLLERR) > { > DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n", >- FD_ISSET(pfd[0], &efds) ? "proxy" : "tls"); >- goto done; >+ p[0].revents & POLLERR ? "tls" : "proxy"); >+ if (!(p[0].revents & POLLIN || p[1].events & POLLIN)) >+ goto done; >+ DEBUG(D_transport) debug_printf("- but also readable; no exit yet\n"); > } > } >- while (rc < 0 || !(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds))); >+ while (rc < 0 || !(p[0].revents & POLLIN || p[1].revents & POLLIN)); > > /* handle inbound data */ >- if (FD_ISSET(tls_out.active.sock, &rfds)) >- if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0) >- { >- fd_bits &= ~1; >- FD_CLR(tls_out.active.sock, &rfds); >+ if (p[0].revents & POLLIN) >+ if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0) /* Expect -1 for EOF; */ >+ { /* that reaps the TLS Close Notify record */ >+ p[0].fd = -1; > shutdown(pfd[0], SHUT_WR); > timeout = 5; > } > else >- { > for (int nbytes = 0; rc - nbytes > 0; nbytes += i) > if ((i = write(pfd[0], buf + nbytes, rc - nbytes)) < 0) goto done; >- } >- else if (fd_bits & 1) >- FD_SET(tls_out.active.sock, &rfds); > >- /* handle outbound data */ >- if (FD_ISSET(pfd[0], &rfds)) >+ /* Handle outbound data. We cannot combine payload and the TLS-close >+ due to the limitations of the (pipe) channel feeding us. Maybe use a unix-domain >+ socket? */ >+ if (p[1].revents & POLLIN) > if ((rc = read(pfd[0], buf, bsize)) <= 0) > { >- fd_bits = 0; >- tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT); >- ct_ctx = NULL; >+ p[1].fd = -1; >+ >+# 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); >+ 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) > goto done; >- } >- else if (fd_bits & 2) >- FD_SET(pfd[0], &rfds); > } >+while (p[0].fd >= 0 || p[1].fd >= 0); > > done: >+ if (send_tls_shutdown) tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT); >+ ct_ctx = NULL; > testharness_pause_ms(100); /* let logging complete */ > exim_exit(EXIT_SUCCESS); > } >@@ -3503,31 +3730,34 @@ > int save_errno; > int rc; > >-BOOL pass_message = FALSE; > uschar *message = NULL; > uschar new_message_id[MESSAGE_ID_LENGTH + 1]; >-smtp_context * sx = store_get(sizeof(*sx), TRUE); /* tainted, for the data buffers */ >+smtp_context * sx = store_get(sizeof(*sx), GET_TAINTED); /* tainted, for the data buffers */ >+BOOL pass_message = FALSE; >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+BOOL mail_limit = FALSE; >+#endif > #ifdef SUPPORT_DANE > BOOL dane_held; > #endif >+BOOL tcw_done = FALSE, tcw = FALSE; > >-suppress_tls = suppress_tls; /* stop compiler warning when no TLS support */ > *message_defer = FALSE; > > memset(sx, 0, sizeof(*sx)); > sx->addrlist = addrlist; > sx->conn_args.host = host; >-sx->conn_args.host_af = host_af, >+sx->conn_args.host_af = host_af; > sx->port = defport; > sx->conn_args.interface = interface; > sx->helo_data = NULL; > sx->conn_args.tblock = tblock; >-/* sx->verify = FALSE; */ >+sx->conn_args.sock = -1; > gettimeofday(&sx->delivery_start, NULL); > sx->sync_addr = sx->first_addr = addrlist; > >+REPEAT_CONN: > #ifdef SUPPORT_DANE >-DANE_DOMAINS: > dane_held = FALSE; > #endif > >@@ -3572,8 +3802,8 @@ > yield ERROR. */ > > if (!transport_set_up_command(&transport_filter_argv, >- tblock->filter_command, TRUE, DEFER, addrlist, >- string_sprintf("%.50s transport", tblock->name), NULL)) >+ tblock->filter_command, TRUE, DEFER, addrlist, FALSE, >+ string_sprintf("%.50s transport filter", tblock->name), NULL)) > { > set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER, > FALSE, &sx->delivery_start); >@@ -3584,7 +3814,7 @@ > if ( transport_filter_argv > && *transport_filter_argv > && **transport_filter_argv >- && sx->peer_offered & OPTION_CHUNKING >+ && smtp_peer_options & OPTION_CHUNKING > #ifndef DISABLE_DKIM > /* When dkim signing, chunking is handled even with a transport-filter */ > && !(ob->dkim.dkim_private_key && ob->dkim.dkim_domain && ob->dkim.dkim_selector) >@@ -3592,7 +3822,7 @@ > #endif > ) > { >- sx->peer_offered &= ~OPTION_CHUNKING; >+ smtp_peer_options &= ~OPTION_CHUNKING; > DEBUG(D_transport) debug_printf("CHUNKING not usable due to transport filter\n"); > } > } >@@ -3669,7 +3899,7 @@ > If using CHUNKING, do not send a BDAT until we know how big a chunk we want > to send is. */ > >-if ( !(sx->peer_offered & OPTION_CHUNKING) >+if ( !(smtp_peer_options & OPTION_CHUNKING) > && (sx->ok || (pipelining_active && !mua_wrapper))) > { > int count = smtp_write_command(sx, SCMD_FLUSH, "DATA\r\n"); >@@ -3706,7 +3936,7 @@ > (Haven't been able to make it work using select() for writing yet.) */ > > if ( !sx->ok >- && (!(sx->peer_offered & OPTION_CHUNKING) || !pipelining_active)) >+ && (!(smtp_peer_options & OPTION_CHUNKING) || !pipelining_active)) > { > /* Save the first address of the next batch. */ > sx->first_addr = sx->next_addr; >@@ -3735,7 +3965,7 @@ > of responses. The callback needs a whole bunch of state so set up > a transport-context structure to be passed around. */ > >- if (sx->peer_offered & OPTION_CHUNKING) >+ if (smtp_peer_options & OPTION_CHUNKING) > { > tctx.check_string = tctx.escape_string = NULL; > tctx.options |= topt_use_bdat; >@@ -3760,10 +3990,10 @@ > transport_write_timeout = ob->data_timeout; > smtp_command = US"sending data block"; /* For error messages */ > DEBUG(D_transport|D_v) >- if (sx->peer_offered & OPTION_CHUNKING) >+ if (smtp_peer_options & OPTION_CHUNKING) > debug_printf(" will write message using CHUNKING\n"); > else >- debug_printf(" SMTP>> writing message and terminating \".\"\n"); >+ debug_printf(" SMTP>> (writing message)\n"); > transport_count = 0; > > #ifndef DISABLE_DKIM >@@ -3800,6 +4030,46 @@ > report_time_since(&t0, US"dkim_exim_sign_init (delta)"); > # endif > } >+#endif >+ >+ /* See if we can pipeline QUIT. Reasons not to are >+ - pipelining not active >+ - not ok to send quit >+ - errors in amtp transation responses >+ - more addrs to send for this message or this host >+ - this message was being retried >+ - more messages for this host >+ If we can, we want the message-write to not flush (the tail end of) its data out. */ >+ >+ if ( sx->pipelining_used >+ && (sx->ok && sx->completed_addr || smtp_peer_options & OPTION_CHUNKING) >+ && sx->send_quit >+ && !(sx->first_addr || f.continue_more) >+ && f.deliver_firsttime >+ ) >+ { >+ smtp_compare_t t_compare = >+ {.tblock = tblock, .current_sender_address = sender_address}; >+ >+ tcw_done = TRUE; >+ tcw = >+#ifndef DISABLE_TLS >+ ( tls_out.active.sock < 0 && !continue_proxy_cipher >+ || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK >+ ) >+ && >+#endif >+ transport_check_waiting(tblock->name, host->name, >+ tblock->connection_max_messages, new_message_id, >+ (oicf)smtp_are_same_identities, (void*)&t_compare); >+ if (!tcw) >+ { >+ HDEBUG(D_transport) debug_printf("will pipeline QUIT\n"); >+ tctx.options |= topt_no_flush; >+ } >+ } >+ >+#ifndef DISABLE_DKIM > sx->ok = dkim_transport_write_message(&tctx, &ob->dkim, CUSS &message); > #else > sx->ok = transport_write_message(&tctx, 0); >@@ -3828,7 +4098,49 @@ > > smtp_command = US"end of data"; > >- if (sx->peer_offered & OPTION_CHUNKING && sx->cmd_count > 1) >+ /* If we can pipeline a QUIT with the data them send it now. If a new message >+ for this host appeared in the queue while data was being sent, we will not see >+ it and it will have to wait for a queue run. If there was one but another >+ thread took it, we might attempt to send it - but locking of spoolfiles will >+ detect that. Use _MORE to get QUIT in FIN segment. */ >+ >+ if (tcw_done && !tcw) >+ { >+ /*XXX jgh 2021/03/10 google et. al screwup. G, at least, sends TCP FIN in response to TLS >+ close-notify. Under TLS 1.3, violating RFC. >+ However, TLS 1.2 does not have half-close semantics. */ >+ >+ if ( sx->cctx.tls_ctx >+#if 0 && !defined(DISABLE_TLS) >+ && Ustrcmp(tls_out.ver, "TLS1.3") != 0 >+#endif >+ || !f.deliver_firsttime >+ ) >+ { /* Send QUIT now and not later */ >+ (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n"); >+ sx->send_quit = FALSE; >+ } >+ else >+ { /* add QUIT to the output buffer */ >+ (void)smtp_write_command(sx, SCMD_MORE, "QUIT\r\n"); >+ sx->send_quit = FALSE; /* avoid sending it later */ >+ >+#ifndef DISABLE_TLS >+ if (sx->cctx.tls_ctx && sx->send_tlsclose) /* need to send TLS Close Notify */ >+ { >+# ifdef EXIM_TCP_CORK /* Use _CORK to get Close Notify in FIN segment */ >+ (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on)); >+# endif >+ tls_shutdown_wr(sx->cctx.tls_ctx); >+ sx->send_tlsclose = FALSE; /* avoid later repeat */ >+ } >+#endif >+ 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 */ >+ } >+ } >+ >+ if (smtp_peer_options & OPTION_CHUNKING && sx->cmd_count > 1) > { > /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */ > switch(sync_responses(sx, sx->cmd_count-1, 0)) >@@ -3920,9 +4232,9 @@ > !sx->lmtp > ) > { >- const uschar *s = string_printing(sx->buffer); >+ const uschar * s = string_printing(sx->buffer); > /* deconst cast ok here as string_printing was checked to have alloc'n'copied */ >- conf = (s == sx->buffer)? US string_copy(s) : US s; >+ conf = s == sx->buffer ? US string_copy(s) : US s; > } > > /* Process all transported addresses - for LMTP or PRDR, read a status for >@@ -4001,7 +4313,7 @@ > #ifndef DISABLE_PRDR > if (sx->prdr_active) setflag(addr, af_prdr_used); > #endif >- if (sx->peer_offered & OPTION_CHUNKING) setflag(addr, af_chunking_used); >+ if (smtp_peer_options & OPTION_CHUNKING) setflag(addr, af_chunking_used); > flag = '-'; > > #ifndef DISABLE_PRDR >@@ -4018,7 +4330,7 @@ > else > sprintf(CS sx->buffer, "%.500s\n", addr->unique); > >- DEBUG(D_deliver) debug_printf("S:journalling %s\n", sx->buffer); >+ DEBUG(D_deliver) debug_printf("S:journalling %s", sx->buffer); > len = Ustrlen(CS sx->buffer); > if (write(journal_fd, sx->buffer, len) != len) > log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for " >@@ -4072,6 +4384,7 @@ > "%s: %s", sx->buffer, strerror(errno)); > } > else if (addr->transport_return == DEFER) >+ /*XXX magic value -2 ? maybe host+message ? */ > retry_add_item(addr, addr->address_retry_key, -2); > } > #endif >@@ -4102,7 +4415,8 @@ > { > save_errno = errno; > message = NULL; >- sx->send_quit = check_response(host, &save_errno, addrlist->more_errno, >+ /* Clear send_quit flag if needed. Do not set. */ >+ sx->send_quit &= check_response(host, &save_errno, addrlist->more_errno, > sx->buffer, &code, &message, &pass_message); > goto FAILED; > } >@@ -4111,7 +4425,7 @@ > { > save_errno = errno; > code = '4'; >- message = string_sprintf("send() to %s [%s] failed: %s", >+ message = string_sprintf("smtp send to %s [%s] failed: %s", > host->name, host->address, message ? message : US strerror(save_errno)); > sx->send_quit = FALSE; > goto FAILED; >@@ -4147,6 +4461,22 @@ > message_error = Ustrncmp(smtp_command,"end ",4) == 0; > break; > >+#ifndef DISABLE_DKIM >+ case EACCES: >+ /* DKIM signing failure: avoid thinking we pipelined quit, >+ just abandon the message and close the socket. */ >+ >+ message_error = FALSE; >+# ifndef DISABLE_TLS >+ if (sx->cctx.tls_ctx) >+ { >+ tls_close(sx->cctx.tls_ctx, >+ sx->send_tlsclose ? TLS_SHUTDOWN_WAIT : TLS_SHUTDOWN_WONLY); >+ sx->cctx.tls_ctx = NULL; >+ } >+# endif >+ break; >+#endif > default: > message_error = FALSE; > break; >@@ -4187,8 +4517,15 @@ > > *message_defer = TRUE; > } >+#ifdef TIOCOUTQ >+ DEBUG(D_transport) if (sx->cctx.sock >= 0) >+ { >+ int n; >+ if (ioctl(sx->cctx.sock, TIOCOUTQ, &n) == 0) >+ debug_printf("%d bytes remain in socket output buffer\n", n); >+ } >+#endif > } >- > /* Otherwise, we have an I/O error or a timeout other than after MAIL or > ".", or some other transportation error. We defer all addresses and yield > DEFER, except for the case of failed add_headers expansion, or a transport >@@ -4255,84 +4592,99 @@ > sx->send_rset, f.continue_more, yield, sx->first_addr ? "not " : ""); > > if (sx->completed_addr && sx->ok && sx->send_quit) >- { >- BOOL more; >- smtp_compare_t t_compare; >- >- t_compare.tblock = tblock; >- t_compare.current_sender_address = sender_address; >- >- if ( sx->first_addr != NULL /* more addrs for this message */ >- || f.continue_more /* more addrs for coninued-host */ >- || ( >-#ifndef DISABLE_TLS >- ( tls_out.active.sock < 0 && !continue_proxy_cipher >- || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK >- ) >- && >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ if (mail_limit = continue_sequence >= sx->max_mail) >+ { >+ DEBUG(D_transport) >+ debug_printf("reached limit %u for MAILs per conn\n", sx->max_mail); >+ } >+ else > #endif >- transport_check_waiting(tblock->name, host->name, >- tblock->connection_max_messages, new_message_id, &more, >- (oicf)smtp_are_same_identities, (void*)&t_compare) >- ) ) > { >- uschar *msg; >- BOOL pass_message; >+ smtp_compare_t t_compare = >+ {.tblock = tblock, .current_sender_address = sender_address}; > >- if (sx->send_rset) >- if (! (sx->ok = smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0)) >- { >- msg = US string_sprintf("send() to %s [%s] failed: %s", host->name, >- host->address, strerror(errno)); >- sx->send_quit = FALSE; >- } >- else if (! (sx->ok = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), >- '2', ob->command_timeout))) >- { >- int code; >- sx->send_quit = check_response(host, &errno, 0, sx->buffer, &code, &msg, >- &pass_message); >- if (!sx->send_quit) >- { >- DEBUG(D_transport) debug_printf("H=%s [%s] %s\n", >- host->name, host->address, msg); >- } >- } >+ if ( sx->first_addr /* more addrs for this message */ >+ || f.continue_more /* more addrs for continued-host */ >+ || tcw_done && tcw /* more messages for host */ >+ || ( >+#ifndef DISABLE_TLS >+ ( tls_out.active.sock < 0 && !continue_proxy_cipher >+ || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK >+ ) >+ && >+#endif >+ transport_check_waiting(tblock->name, host->name, >+ sx->max_mail, new_message_id, >+ (oicf)smtp_are_same_identities, (void*)&t_compare) >+ ) ) >+ { >+ uschar *msg; >+ BOOL pass_message; > >- /* Either RSET was not needed, or it succeeded */ >+ if (sx->send_rset) >+ if (! (sx->ok = smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0)) >+ { >+ msg = US string_sprintf("smtp send to %s [%s] failed: %s", host->name, >+ host->address, strerror(errno)); >+ sx->send_quit = FALSE; >+ } >+ else if (! (sx->ok = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), >+ '2', ob->command_timeout))) >+ { >+ int code; >+ sx->send_quit = check_response(host, &errno, 0, sx->buffer, &code, &msg, >+ &pass_message); >+ if (!sx->send_quit) >+ { >+ DEBUG(D_transport) debug_printf("H=%s [%s] %s\n", >+ host->name, host->address, msg); >+ } >+ } > >- if (sx->ok) >- { >+ /* Either RSET was not needed, or it succeeded */ >+ >+ if (sx->ok) >+ { > #ifndef DISABLE_TLS >- int pfd[2]; >+ int pfd[2]; > #endif >- int socket_fd = sx->cctx.sock; >- >+ int socket_fd = sx->cctx.sock; > >- if (sx->first_addr != NULL) /* More addresses still to be sent */ >- { /* for this message */ >- continue_sequence++; /* Causes * in logging */ >- pipelining_active = sx->pipelining_used; /* was cleared at DATA */ >- goto SEND_MESSAGE; >- } >+ if (sx->first_addr) /* More addresses still to be sent */ >+ { /* for this message */ >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ /* Any that we marked as skipped, reset to do now */ >+ for (address_item * a = sx->first_addr; a; a = a->next) >+ if (a->transport_return == SKIP) >+ a->transport_return = PENDING_DEFER; >+#endif >+ continue_sequence++; /* for consistency */ >+ clearflag(sx->first_addr, af_new_conn); >+ setflag(sx->first_addr, af_cont_conn); /* Causes * in logging */ >+ pipelining_active = sx->pipelining_used; /* was cleared at DATA */ >+ goto SEND_MESSAGE; >+ } > >- /* Unless caller said it already has more messages listed for this host, >- pass the connection on to a new Exim process (below, the call to >- transport_pass_socket). If the caller has more ready, just return with >- the connection still open. */ >+ /* Unless caller said it already has more messages listed for this host, >+ pass the connection on to a new Exim process (below, the call to >+ transport_pass_socket). If the caller has more ready, just return with >+ the connection still open. */ > > #ifndef DISABLE_TLS >- if (tls_out.active.sock >= 0) >- if ( f.continue_more >- || verify_check_given_host(CUSS &ob->hosts_noproxy_tls, host) == OK) >- { >- /* Before passing the socket on, or returning to caller with it still >- open, we must shut down TLS. Not all MTAs allow for the continuation >- of the SMTP session when TLS is shut down. We test for this by sending >- a new EHLO. If we don't get a good response, we don't attempt to pass >- the socket on. */ >- >- tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT); >+ if (tls_out.active.sock >= 0) >+ if ( f.continue_more >+ || verify_check_given_host(CUSS &ob->hosts_noproxy_tls, host) == OK) >+ { >+ /* Before passing the socket on, or returning to caller with it still >+ open, we must shut down TLS. Not all MTAs allow for the continuation >+ of the SMTP session when TLS is shut down. We test for this by sending >+ a new EHLO. If we don't get a good response, we don't attempt to pass >+ the socket on. */ >+ >+ tls_close(sx->cctx.tls_ctx, >+ sx->send_tlsclose ? TLS_SHUTDOWN_WAIT : TLS_SHUTDOWN_WONLY); >+ sx->send_tlsclose = FALSE; > sx->cctx.tls_ctx = NULL; > tls_out.active.sock = -1; > smtp_peer_options = smtp_peer_options_wrap; >@@ -4342,116 +4694,110 @@ > && smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), > '2', ob->command_timeout); > >- if (sx->ok && f.continue_more) >- goto TIDYUP; /* More addresses for another run */ >- } >- else >- { >- /* Set up a pipe for proxying TLS for the new transport process */ >- >- smtp_peer_options |= OPTION_TLS; >- if ((sx->ok = socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0)) >- socket_fd = pfd[1]; >+ if (sx->ok && f.continue_more) >+ goto TIDYUP; /* More addresses for another run */ >+ } > else >- set_errno(sx->first_addr, errno, US"internal allocation problem", >- DEFER, FALSE, host, >+ { >+ /* Set up a pipe for proxying TLS for the new transport process */ >+ >+ smtp_peer_options |= OPTION_TLS; >+ if ((sx->ok = socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0)) >+ socket_fd = pfd[1]; >+ else >+ set_errno(sx->first_addr, errno, US"internal allocation problem", >+ DEFER, FALSE, host, > # ifdef EXPERIMENTAL_DSN_INFO >- sx->smtp_greeting, sx->helo_response, >+ sx->smtp_greeting, sx->helo_response, > # endif >- &sx->delivery_start); >- } >- else >+ &sx->delivery_start); >+ } >+ else > #endif >- if (f.continue_more) >- goto TIDYUP; /* More addresses for another run */ >- >- /* If the socket is successfully passed, we mustn't send QUIT (or >- indeed anything!) from here. */ >+ if (f.continue_more) >+ goto TIDYUP; /* More addresses for another run */ > >-/*XXX DSN_INFO: assume likely to do new HELO; but for greet we'll want to >-propagate it from the initial >-*/ >- if (sx->ok && transport_pass_socket(tblock->name, host->name, >- host->address, new_message_id, socket_fd)) >- { >- sx->send_quit = FALSE; >+ /* If the socket is successfully passed, we mustn't send QUIT (or >+ indeed anything!) from here. */ > >- /* We have passed the client socket to a fresh transport process. >- If TLS is still active, we need to proxy it for the transport we >- just passed the baton to. Fork a child to to do it, and return to >- get logging done asap. Which way to place the work makes assumptions >- about post-fork prioritisation which may not hold on all platforms. */ >-#ifndef DISABLE_TLS >- if (tls_out.active.sock >= 0) >+ /*XXX DSN_INFO: assume likely to do new HELO; but for greet we'll want to >+ propagate it from the initial >+ */ >+ if (sx->ok && transport_pass_socket(tblock->name, host->name, >+ host->address, new_message_id, socket_fd >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ , sx->peer_limit_mail, sx->peer_limit_rcpt, sx->peer_limit_rcptdom >+#endif >+ )) > { >- int pid = exim_fork(US"tls-proxy-interproc"); >- if (pid == 0) /* child; fork again to disconnect totally */ >- { >- /* does not return */ >- smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd, >- ob->command_timeout); >- } >+ sx->send_quit = FALSE; > >- if (pid > 0) /* parent */ >+ /* We have passed the client socket to a fresh transport process. >+ If TLS is still active, we need to proxy it for the transport we >+ just passed the baton to. Fork a child to to do it, and return to >+ get logging done asap. Which way to place the work makes assumptions >+ about post-fork prioritisation which may not hold on all platforms. */ >+#ifndef DISABLE_TLS >+ if (tls_out.active.sock >= 0) > { >- close(pfd[0]); >- /* tidy the inter-proc to disconn the proxy proc */ >- waitpid(pid, NULL, 0); >- tls_close(sx->cctx.tls_ctx, TLS_NO_SHUTDOWN); >- sx->cctx.tls_ctx = NULL; >- (void)close(sx->cctx.sock); >- sx->cctx.sock = -1; >- continue_transport = NULL; >- continue_hostname = NULL; >- goto TIDYUP; >+ int pid = exim_fork(US"tls-proxy-interproc"); >+ if (pid == 0) /* child; fork again to disconnect totally */ >+ { >+ /* does not return */ >+ smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd, >+ ob->command_timeout, host->name); >+ } >+ >+ if (pid > 0) /* parent */ >+ { >+ close(pfd[0]); >+ /* tidy the inter-proc to disconn the proxy proc */ >+ waitpid(pid, NULL, 0); >+ tls_close(sx->cctx.tls_ctx, TLS_NO_SHUTDOWN); >+ sx->cctx.tls_ctx = NULL; >+ (void)close(sx->cctx.sock); >+ sx->cctx.sock = -1; >+ continue_transport = NULL; >+ continue_hostname = NULL; >+ goto TIDYUP; >+ } >+ log_write(0, LOG_PANIC_DIE, "fork failed"); > } >- log_write(0, LOG_PANIC_DIE, "fork failed"); >- } > #endif >+ } > } >- } > >- /* If RSET failed and there are addresses left, they get deferred. */ >- else >- set_errno(sx->first_addr, errno, msg, DEFER, FALSE, host, >+ /* If RSET failed and there are addresses left, they get deferred. */ >+ else >+ set_errno(sx->first_addr, errno, msg, DEFER, FALSE, host, > #ifdef EXPERIMENTAL_DSN_INFO >- sx->smtp_greeting, sx->helo_response, >+ sx->smtp_greeting, sx->helo_response, > #endif >- &sx->delivery_start); >+ &sx->delivery_start); >+ } > } >- } > > /* End off tidily with QUIT unless the connection has died or the socket has >-been passed to another process. There has been discussion on the net about what >-to do after sending QUIT. The wording of the RFC suggests that it is necessary >-to wait for a response, but on the other hand, there isn't anything one can do >-with an error response, other than log it. Exim used to do that. However, >-further discussion suggested that it is positively advantageous not to wait for >-the response, but to close the session immediately. This is supposed to move >-the TCP/IP TIME_WAIT state from the server to the client, thereby removing some >-load from the server. (Hosts that are both servers and clients may not see much >-difference, of course.) Further discussion indicated that this was safe to do >-on Unix systems which have decent implementations of TCP/IP that leave the >-connection around for a while (TIME_WAIT) after the application has gone away. >-This enables the response sent by the server to be properly ACKed rather than >-timed out, as can happen on broken TCP/IP implementations on other OS. >- >-This change is being made on 31-Jul-98. After over a year of trouble-free >-operation, the old commented-out code was removed on 17-Sep-99. */ >+been passed to another process. */ > > SEND_QUIT: >-#ifdef TCP_CORK >-(void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_CORK, US &on, sizeof(on)); >+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 >+ if (sx->cctx.tls_ctx && sx->send_tlsclose) >+ { >+# ifdef EXIM_TCP_CORK /* Use _CORK to get TLS Close Notify in FIN segment */ >+ (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on)); >+# endif >+ tls_shutdown_wr(sx->cctx.tls_ctx); >+ sx->send_tlsclose = FALSE; >+ } > #endif >-if (sx->send_quit) (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n"); >+ } > > END_OFF: > >-#ifndef DISABLE_TLS >-tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT); >-sx->cctx.tls_ctx = NULL; >-#endif >- > /* Close the socket, and return the appropriate value, first setting > works because the NULL setting is passed back to the calling process, and > remote_max_parallel is forced to 1 when delivering over an existing connection, >@@ -4462,20 +4808,74 @@ > specified in the transports, and therefore not visible at top level, in which > case continue_more won't get set. */ > >-HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); > if (sx->send_quit) > { >+ /* This flushes data queued in the socket, being the QUIT and any TLS Close, >+ sending them along with the client FIN flag. Us (we hope) sending FIN first >+ means we (client) take the TIME_WAIT state, so the server (which likely has a >+ higher connection rate) does not have to. */ >+ >+ HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(shutdown)>>\n"); > shutdown(sx->cctx.sock, SHUT_WR); >- millisleep(20); >- testharness_pause_ms(200); >- if (fcntl(sx->cctx.sock, F_SETFL, O_NONBLOCK) == 0) >- for (int i = 16; read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && i > 0;) >- i--; /* drain socket */ > } >+ >+if (sx->send_quit || tcw_done && !tcw) >+ { >+ /* Wait for (we hope) ack of our QUIT, and a server FIN. Discard any data >+ received, then discard the socket. Any packet received after then, or receive >+ data still in the socket, will get a RST - hence the pause/drain. */ >+ >+ /* Reap the response to QUIT, timing out after one second */ >+ (void) smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', 1); >+#ifndef DISABLE_TLS >+ if (sx->cctx.tls_ctx) >+ { >+ int n; >+ >+ /* Reap the TLS Close Notify from the server, timing out after one second */ >+ sigalrm_seen = FALSE; >+ ALARM(1); >+ do >+ n = tls_read(sx->cctx.tls_ctx, sx->inbuffer, sizeof(sx->inbuffer)); >+ while (!sigalrm_seen && n > 0); >+ ALARM_CLR(0); >+ >+ if (sx->send_tlsclose) >+ { >+# ifdef EXIM_TCP_CORK >+ (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on)); >+# endif >+ tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT); >+ } >+ else >+ tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WONLY); >+ sx->cctx.tls_ctx = NULL; >+ } >+#endif >+ >+ /* Drain any trailing data from the socket before close, to avoid sending a RST */ >+ >+ if ( poll_one_fd(sx->cctx.sock, POLLIN, 20) != 0 /* 20ms */ >+ && fcntl(sx->cctx.sock, F_SETFL, O_NONBLOCK) == 0) >+ for (int i = 16, n; /* drain socket */ >+ (n = read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer))) > 0 && i > 0; >+ i--) HDEBUG(D_transport|D_acl|D_v) >+ { >+ int m = MIN(n, 64); >+ debug_printf_indent(" SMTP(drain %d bytes)<< %.*s\n", n, m, sx->inbuffer); >+ for (m = 0; m < n; m++) >+ debug_printf("0x%02x\n", sx->inbuffer[m]); >+ } >+ } >+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); > (void)close(sx->cctx.sock); >+sx->cctx.sock = -1; >+continue_transport = NULL; >+continue_hostname = NULL; >+smtp_debug_cmd_report(); > > #ifndef DISABLE_EVENT >-(void) event_raise(tblock->event_action, US"tcp:close", NULL); >+(void) event_raise(tblock->event_action, US"tcp:close", NULL, NULL); > #endif > > #ifdef SUPPORT_DANE >@@ -4492,15 +4892,30 @@ > to get the domain string for SNI */ > > sx->first_addr = a; >+ clearflag(a, af_cont_conn); >+ setflag(a, af_new_conn); /* clear * from logging */ > DEBUG(D_transport) debug_printf("DANE: go-around for %s\n", a->domain); > } > } >- goto DANE_DOMAINS; >+ continue_sequence = 1; /* for consistency */ >+ goto REPEAT_CONN; >+ } >+#endif >+ >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+if (mail_limit && sx->first_addr) >+ { >+ /* Reset the sequence count since we closed the connection. This is flagged >+ on the pipe back to the delivery process so that a non-continued-conn delivery >+ is logged. */ >+ >+ continue_sequence = 1; /* for consistency */ >+ clearflag(sx->first_addr, af_cont_conn); >+ setflag(sx->first_addr, af_new_conn); /* clear * from logging */ >+ goto REPEAT_CONN; > } > #endif > >-continue_transport = NULL; >-continue_hostname = NULL; > return yield; > > TIDYUP: >@@ -4662,6 +5077,22 @@ > cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0); > } > >+/* Check the restrictions on line length */ >+ >+if (max_received_linelength > ob->message_linelength_limit) >+ { >+ struct timeval now; >+ gettimeofday(&now, NULL); >+ >+ for (address_item * addr = addrlist; addr; addr = addr->next) >+ if (addr->transport_return == DEFER) >+ addr->transport_return = PENDING_DEFER; >+ >+ set_errno_nohost(addrlist, ERRNO_SMTPFORMAT, >+ US"message has lines too long for transport", FAIL, TRUE, &now); >+ goto END_TRANSPORT; >+ } >+ > /* Set the flag requesting that these hosts be added to the waiting > database if the delivery fails temporarily or if we are running with > queue_smtp or a 2-stage queue run. This gets unset for certain >@@ -4699,7 +5130,7 @@ > { > uschar *s = ob->hosts; > >- if (Ustrchr(s, '$') != NULL) >+ if (Ustrchr(s, '$')) > { > if (!(expanded_hosts = expand_string(s))) > { >@@ -5047,7 +5478,7 @@ > because connections to the same host from a different interface should be > treated separately. */ > >- host_af = Ustrchr(host->address, ':') == NULL ? AF_INET : AF_INET6; >+ host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET; > { > uschar * s = ob->interface; > if (s && *s) >@@ -5216,7 +5647,7 @@ > > if (expanded_hosts) > { >- thost = store_get(sizeof(host_item), FALSE); >+ thost = store_get(sizeof(host_item), GET_UNTAINTED); > *thost = *host; > thost->name = string_copy(host->name); > thost->address = string_copy(host->address); >diff -ur exim.orig/src/transports/smtp.h exim/src/transports/smtp.h >--- exim.orig/src/transports/smtp.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/transports/smtp.h 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > #define DELIVER_BUFFER_SIZE 4096 >@@ -13,96 +13,123 @@ > #define PENDING_OK (PENDING + OK) > > >+#ifndef DISABLE_TLS >+/* Flags structure for validity of TLS configuration */ >+ >+typedef struct { >+ BOOL conn_certs:1; /* certificates etc. loaded */ >+ BOOL cabundle:1; /* CA certificates loaded */ >+ BOOL crl:1; /* CRL loaded */ >+ BOOL pri_string:1; /* cipher priority-string cache loaded */ >+ BOOL dh:1; /* Diffie-Helman params loaded */ >+ BOOL ecdh:1; /* EC Diffie-Helman params loaded */ >+ >+ BOOL ca_rdn_emulate:1; /* do not advertise usable-cert list */ >+ BOOL ocsp_hook:1; /* need hshake callback on session */ >+ >+ void * libdata0; /* library-dependent preloaded data */ >+ void * libdata1; /* library-dependent preloaded data */ >+} exim_tlslib_state; >+#endif >+ >+ > /* Private structure for the private options and other private data. */ > > typedef struct { >- uschar *hosts; >- uschar *fallback_hosts; >- host_item *hostlist; >- host_item *fallback_hostlist; >- uschar *authenticated_sender; >- uschar *helo_data; >- uschar *interface; >- uschar *port; >- uschar *protocol; >- uschar *dscp; >- uschar *serialize_hosts; >- uschar *hosts_try_auth; >- uschar *hosts_require_auth; >- uschar *hosts_try_chunking; >+ uschar *hosts; >+ uschar *fallback_hosts; >+ host_item *hostlist; >+ host_item *fallback_hostlist; >+ uschar *authenticated_sender; >+ uschar *helo_data; >+ uschar *interface; >+ uschar *port; >+ uschar *protocol; >+ uschar *dscp; >+ uschar *serialize_hosts; >+ uschar *hosts_try_auth; >+ uschar *hosts_require_alpn; >+ uschar *hosts_require_auth; >+ uschar *hosts_try_chunking; > #ifdef SUPPORT_DANE >- uschar *hosts_try_dane; >- uschar *hosts_require_dane; >- uschar *dane_require_tls_ciphers; >+ uschar *hosts_try_dane; >+ uschar *hosts_require_dane; >+ uschar *dane_require_tls_ciphers; > #endif >- uschar *hosts_try_fastopen; >+ uschar *hosts_try_fastopen; > #ifndef DISABLE_PRDR >- uschar *hosts_try_prdr; >+ uschar *hosts_try_prdr; > #endif > #ifndef DISABLE_OCSP >- uschar *hosts_request_ocsp; >- uschar *hosts_require_ocsp; >+ uschar *hosts_request_ocsp; >+ uschar *hosts_require_ocsp; > #endif >- uschar *hosts_require_tls; >- uschar *hosts_avoid_tls; >- uschar *hosts_verify_avoid_tls; >- uschar *hosts_avoid_pipelining; >+ uschar *hosts_require_tls; >+ uschar *hosts_avoid_tls; >+ uschar *hosts_verify_avoid_tls; >+ uschar *hosts_avoid_pipelining; > #ifndef DISABLE_PIPE_CONNECT >- uschar *hosts_pipe_connect; >+ uschar *hosts_pipe_connect; > #endif >- uschar *hosts_avoid_esmtp; >+ uschar *hosts_avoid_esmtp; > #ifndef DISABLE_TLS >- uschar *hosts_nopass_tls; >- uschar *hosts_noproxy_tls; >+ uschar *hosts_nopass_tls; >+ uschar *hosts_noproxy_tls; > #endif >- int command_timeout; >- int connect_timeout; >- int data_timeout; >- int final_timeout; >- int size_addition; >- int hosts_max_try; >- int hosts_max_try_hardlimit; >- BOOL address_retry_include_sender; >- BOOL allow_localhost; >- BOOL authenticated_sender_force; >- BOOL gethostbyname; >- BOOL dns_qualify_single; >- BOOL dns_search_parents; >+ int command_timeout; >+ int connect_timeout; >+ int data_timeout; >+ int final_timeout; >+ int size_addition; >+ int hosts_max_try; >+ int hosts_max_try_hardlimit; >+ int message_linelength_limit; >+ BOOL address_retry_include_sender; >+ BOOL allow_localhost; >+ BOOL authenticated_sender_force; >+ BOOL gethostbyname; >+ BOOL dns_qualify_single; >+ BOOL dns_search_parents; > dnssec_domains dnssec; >- BOOL delay_after_cutoff; >- BOOL hosts_override; >- BOOL hosts_randomize; >- BOOL keepalive; >- BOOL lmtp_ignore_quota; >- uschar *expand_retry_include_ip_address; >- BOOL retry_include_ip_address; >+ BOOL delay_after_cutoff; >+ BOOL hosts_override; >+ BOOL hosts_randomize; >+ BOOL keepalive; >+ BOOL lmtp_ignore_quota; >+ uschar *expand_retry_include_ip_address; >+ BOOL retry_include_ip_address; > #ifdef SUPPORT_SOCKS >- uschar *socks_proxy; >+ uschar *socks_proxy; > #endif > #ifndef DISABLE_TLS >- uschar *tls_certificate; >- uschar *tls_crl; >- uschar *tls_privatekey; >- uschar *tls_require_ciphers; >-# ifdef EXPERIMENTAL_TLS_RESUME >- uschar *tls_resumption_hosts; >+ uschar *tls_alpn; >+ uschar *tls_certificate; >+ uschar *tls_crl; >+ uschar *tls_privatekey; >+ uschar *tls_require_ciphers; >+# ifndef DISABLE_TLS_RESUME >+ uschar *host_name_extract; >+ uschar *tls_resumption_hosts; > # endif >- const uschar *tls_sni; >- uschar *tls_verify_certificates; >- int tls_dh_min_bits; >- BOOL tls_tempfail_tryclear; >- uschar *tls_verify_hosts; >- uschar *tls_try_verify_hosts; >- uschar *tls_verify_cert_hostnames; >+ const uschar *tls_sni; >+ uschar *tls_verify_certificates; >+ int tls_dh_min_bits; >+ BOOL tls_tempfail_tryclear; >+ uschar *tls_verify_hosts; >+ uschar *tls_try_verify_hosts; >+ uschar *tls_verify_cert_hostnames; > #endif > #ifdef SUPPORT_I18N >- uschar *utf8_downconvert; >+ uschar *utf8_downconvert; > #endif > #ifndef DISABLE_DKIM > struct ob_dkim dkim; > #endif > #ifdef EXPERIMENTAL_ARC >- uschar *arc_sign; >+ uschar *arc_sign; >+#endif >+#ifndef DISABLE_TLS >+ exim_tlslib_state tls_preload; > #endif > } smtp_transport_options_block; > >@@ -147,14 +174,25 @@ > BOOL pending_BDAT:1; > BOOL RCPT_452:1; > BOOL good_RCPT:1; >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ BOOL single_rcpt_domain:1; >+#endif > BOOL completed_addr:1; > BOOL send_rset:1; > BOOL send_quit:1; >+ BOOL send_tlsclose:1; >+ >+ unsigned peer_offered; >+#ifdef EXPERIMENTAL_ESMTP_LIMITS >+ unsigned peer_limit_mail; >+ unsigned peer_limit_rcpt; >+ unsigned peer_limit_rcptdom; >+#endif > >+ unsigned max_mail; > int max_rcpt; > int cmd_count; > >- unsigned peer_offered; > unsigned avoid_option; > uschar * igquotstr; > uschar * helo_data; >@@ -163,6 +201,11 @@ > uschar * helo_response; > #endif > #ifndef DISABLE_PIPE_CONNECT >+ /* Info about the EHLO response stored to / retrieved from cache. When >+ operating early-pipe, we use the cached values. For each of plaintext and >+ crypted we store bitmaps for ESMTP features and AUTH methods. If the LIMITS >+ extension is built and usable them at least one of the limits values cached >+ is nonzero, and we use the values to constrain the connection. */ > ehlo_resp_precis ehlo_resp; > #endif > >diff -ur exim.orig/src/transports/smtp_socks.c exim/src/transports/smtp_socks.c >--- exim.orig/src/transports/smtp_socks.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/transports/smtp_socks.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ > /* Copyright (c) Jeremy Harris 2015 - 2018 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -160,20 +161,10 @@ > socks_opts * lim = &proxies[nproxies]; > long rnd, weights; > unsigned pri; >-static BOOL srandomed = FALSE; > > if (nproxies == 1) /* shortcut, if we have only 1 server */ > return (proxies[0].is_failed ? -1 : 0); > >-/* init random */ >-if (!srandomed) >- { >- struct timeval tv; >- gettimeofday(&tv, NULL); >- srandom((unsigned int)(tv.tv_usec/1000)); >- srandomed = TRUE; >- } >- > /* scan for highest pri */ > for (pri = 0, sd = proxies; sd < lim; sd++) > if (!sd->is_failed && sd->priority > pri) >@@ -186,7 +177,7 @@ > if (weights == 0) /* all servers failed */ > return -1; > >-for (rnd = random() % weights, i = 0; i < nproxies; i++) >+for (rnd = random_number(weights), i = 0; i < nproxies; i++) > { > sd = &proxies[i]; > if (!sd->is_failed && sd->priority == pri) >@@ -230,7 +221,7 @@ > uschar buf[24]; > socks_opts proxies[32]; /* max #proxies handled */ > unsigned nproxies; >-socks_opts * sob; >+socks_opts * sob = NULL; > unsigned size; > blob early_data; > >@@ -267,6 +258,7 @@ > while ((option = string_nextinlist(&proxy_spec, &subsep, NULL, 0))) > socks_option(sob, option); > } >+if (!sob) return -1; > > /* Set up the socks protocol method-selection message, > for sending on connection */ >@@ -282,7 +274,7 @@ > { > int idx; > host_item proxy; >- int proxy_af; >+ smtp_connect_args sc = {.sock = -1}; > > if ((idx = socks_get_proxy(proxies, nproxies)) < 0) > { >@@ -294,11 +286,16 @@ > > /* bodge up a host struct for the proxy */ > proxy.address = proxy.name = sob->proxy_host; >- proxy_af = Ustrchr(sob->proxy_host, ':') ? AF_INET6 : AF_INET; >+ proxy.port = sob->port; >+ >+ sc.tblock = tb; >+ sc.ob = ob; >+ sc.host = &proxy; >+ sc.host_af = Ustrchr(sob->proxy_host, ':') ? AF_INET6 : AF_INET; >+ sc.interface = interface; > > /*XXX we trust that the method-select command is idempotent */ >- if ((fd = smtp_sock_connect(&proxy, proxy_af, sob->port, >- interface, tb, sob->timeout, &early_data)) >= 0) >+ if ((fd = smtp_sock_connect(&sc, sob->timeout, &early_data)) >= 0) > { > proxy_local_address = string_copy(proxy.address); > proxy_local_port = sob->port; >@@ -330,7 +327,7 @@ > ) > goto proxy_err; > >- { >+ { > union sockaddr_46 sin; > (void) ip_addr(&sin, host_af, host->address, port); > >@@ -353,7 +350,7 @@ > &sin.v4.sin_port, sizeof(sin.v4.sin_port)); > size = 4+sizeof(sin.v4.sin_addr.s_addr)+sizeof(sin.v4.sin_port); > } >- } >+ } > > state = US"connect"; > HDEBUG(D_transport|D_acl|D_v) >diff -ur exim.orig/src/transports/tf_maildir.c exim/src/transports/tf_maildir.c >--- exim.orig/src/transports/tf_maildir.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/transports/tf_maildir.c 2022-06-23 16:41:10.000000000 +0300 >@@ -3,7 +3,7 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ >+/* Copyright (c) The Exim Maintainers 2020 - 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions in support of the use of maildirsize files for handling quotas in >@@ -140,24 +140,26 @@ > /* If the basic path matches maildirfolder_create_regex, we are dealing with > a subfolder, and should ensure that a maildirfolder file exists. */ > >-if (maildirfolder_create_regex != NULL) >+if (maildirfolder_create_regex) > { >- const uschar *error; >- int offset; >- const pcre *regex; >+ int err; >+ PCRE2_SIZE offset; >+ const pcre2_code * re; > > DEBUG(D_transport) debug_printf("checking for maildirfolder requirement\n"); > >- if (!(regex = pcre_compile(CS maildirfolder_create_regex, PCRE_COPT, >- CCSS &error, &offset, NULL))) >+ if (!(re = pcre2_compile((PCRE2_SPTR)maildirfolder_create_regex, >+ PCRE2_ZERO_TERMINATED, PCRE_COPT, &err, &offset, pcre_cmp_ctx))) > { >+ uschar errbuf[128]; >+ pcre2_get_error_message(err, errbuf, sizeof(errbuf)); > addr->message = string_sprintf("appendfile: regular expression " >- "error: %s at offset %d while compiling %s", error, offset, >+ "error: %s at offset %ld while compiling %s", errbuf, (long)offset, > maildirfolder_create_regex); > return FALSE; > } > >- if (pcre_exec(regex, NULL, CS path, Ustrlen(path), 0, 0, NULL, 0) >= 0) >+ if (regex_match(re, path, -1, NULL)) > { > uschar *fname = string_sprintf("%s/maildirfolder", path); > if (Ustat(fname, &statbuf) == 0) >@@ -250,7 +252,7 @@ > > off_t > maildir_compute_size(uschar *path, int *filecount, time_t *latest, >- const pcre *regex, const pcre *dir_regex, BOOL timestamp_only) >+ const pcre2_code *regex, const pcre2_code *dir_regex, BOOL timestamp_only) > { > DIR *dir; > off_t sum = 0; >@@ -269,8 +271,7 @@ > scan. We do the regex match first, because that avoids a stat() for names > we aren't interested in. */ > >- if (dir_regex != NULL && >- pcre_exec(dir_regex, NULL, CS name, Ustrlen(name), 0, 0, NULL, 0) < 0) >+ if (dir_regex && !regex_match(dir_regex, name, -1, NULL)) > { > DEBUG(D_transport) > debug_printf("skipping %s/%s: dir_regex does not match\n", path, name); >@@ -358,7 +359,7 @@ > > int > maildir_ensure_sizefile(uschar *path, appendfile_transport_options_block *ob, >- const pcre *regex, const pcre *dir_regex, off_t *returned_size, >+ const pcre2_code *regex, const pcre2_code *dir_regex, off_t *returned_size, > int *returned_filecount) > { > int count, fd; >diff -ur exim.orig/src/transports/tf_maildir.h exim/src/transports/tf_maildir.h >--- exim.orig/src/transports/tf_maildir.h 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/transports/tf_maildir.h 2022-06-23 16:41:10.000000000 +0300 >@@ -3,18 +3,19 @@ > *************************************************/ > > /* Copyright (c) University of Cambridge 1995 - 2009 */ >+/* Copyright (c) The Exim Maintainers 2021 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Header file for the functions that are used to support the use of > maildirsize files for quota handling in maildir directories. */ > >-extern off_t maildir_compute_size(uschar *, int *, time_t *, const pcre *, >- const pcre *, BOOL); >+extern off_t maildir_compute_size(uschar *, int *, time_t *, const pcre2_code *, >+ const pcre2_code *, BOOL); > extern BOOL maildir_ensure_directories(uschar *, address_item *, BOOL, int, > uschar *); > extern int maildir_ensure_sizefile(uschar *, >- appendfile_transport_options_block *, const pcre *, >- const pcre *, off_t *, int *); >+ appendfile_transport_options_block *, const pcre2_code *, >+ const pcre2_code *, off_t *, int *); > extern void maildir_record_length(int, int); > > /* End of tf_maildir.h */ >diff -ur exim.orig/src/tree.c exim/src/tree.c >--- exim.orig/src/tree.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/tree.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2021 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2015 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -30,7 +31,7 @@ > tree_add_nonrecipient(const uschar *s) > { > rmark rpoint = store_mark(); >-tree_node *node = store_get(sizeof(tree_node) + Ustrlen(s), is_tainted(s)); >+tree_node * node = store_get(sizeof(tree_node) + Ustrlen(s), s); > Ustrcpy(node->name, s); > node->data.ptr = NULL; > if (!tree_insertnode(&tree_nonrecipients, node)) store_reset(rpoint); >@@ -55,7 +56,7 @@ > tree_add_duplicate(const uschar *s, address_item *addr) > { > rmark rpoint = store_mark(); >-tree_node *node = store_get(sizeof(tree_node) + Ustrlen(s), is_tainted(s)); >+tree_node * node = store_get(sizeof(tree_node) + Ustrlen(s), s); > Ustrcpy(node->name, s); > node->data.ptr = addr; > if (!tree_insertnode(&tree_duplicates, node)) store_reset(rpoint); >@@ -81,7 +82,7 @@ > uschar s[256]; > sprintf(CS s, "T:%.200s:%s", h->name, h->address); > node = store_get(sizeof(tree_node) + Ustrlen(s), >- is_tainted(h->name) || is_tainted(h->address)); >+ is_tainted(h->name) || is_tainted(h->address) ? GET_TAINTED : GET_UNTAINTED); > Ustrcpy(node->name, s); > node->data.val = h->why; > if (h->status == hstatus_unusable_expired) node->data.val += 256; >@@ -373,7 +374,7 @@ > tree_add_var(uschar * name, uschar * val, void * ctx) > { > tree_node ** root = ctx; >-tree_node * node = store_get(sizeof(tree_node) + Ustrlen(name), is_tainted(name)); >+tree_node * node = store_get(sizeof(tree_node) + Ustrlen(name), name); > Ustrcpy(node->name, name); > node->data.ptr = val; > (void) tree_insertnode(root, node); >diff -ur exim.orig/src/utf8.c exim/src/utf8.c >--- exim.orig/src/utf8.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/utf8.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,6 +2,7 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2022 */ > /* Copyright (c) Jeremy Harris 2015 - 2018 */ > /* See the file NOTICE for conditions of use and distribution. */ > >@@ -133,7 +134,7 @@ > uschar * > string_localpart_utf8_to_alabel(const uschar * utf8, uschar ** err) > { >-size_t ucs4_len; >+size_t ucs4_len = 0; > punycode_uint * p; > size_t p_len; > uschar * res; >@@ -142,8 +143,13 @@ > if (!string_is_utf8(utf8)) return string_copy(utf8); > > p = (punycode_uint *) stringprep_utf8_to_ucs4(CCS utf8, -1, &ucs4_len); >+if (!p || !ucs4_len) >+ { >+ if (err) *err = US"l_u2a: bad UTF-8 input"; >+ return NULL; >+ } > p_len = ucs4_len*4; /* this multiplier is pure guesswork */ >-res = store_get(p_len+5, is_tainted(utf8)); >+res = store_get(p_len+5, utf8); > > res[0] = 'x'; res[1] = 'n'; res[2] = res[3] = '-'; > >@@ -172,7 +178,7 @@ > DEBUG(D_expand) debug_printf("l_a2u: '%s'\n", alabel); > alabel += 4; > p_len = Ustrlen(alabel); >-p = store_get((p_len+1) * sizeof(*p), is_tainted(alabel)); >+p = store_get((p_len+1) * sizeof(*p), alabel); > > if ((rc = punycode_decode(p_len, CCS alabel, &p_len, p, NULL)) != PUNYCODE_SUCCESS) > { >@@ -240,28 +246,29 @@ > > /* See a description in tls-openssl.c for an explanation of why this exists. > >-Arguments: a FILE* to print the results to >-Returns: nothing >+Arguments: string to append to >+Returns: string > */ > >-void >-utf8_version_report(FILE *f) >+gstring * >+utf8_version_report(gstring * g) > { > #ifdef SUPPORT_I18N_2008 >-fprintf(f, "Library version: IDN2: Compile: %s\n" >+g = string_fmt_append(g, "Library version: IDN2: Compile: %s\n" > " Runtime: %s\n", > IDN2_VERSION, > idn2_check_version(NULL)); >-fprintf(f, "Library version: Stringprep: Compile: %s\n" >+g = string_fmt_append(g, "Library version: Stringprep: Compile: %s\n" > " Runtime: %s\n", > STRINGPREP_VERSION, > stringprep_check_version(NULL)); > #else >-fprintf(f, "Library version: IDN: Compile: %s\n" >+g = string_fmt_append(g, "Library version: IDN: Compile: %s\n" > " Runtime: %s\n", > STRINGPREP_VERSION, > stringprep_check_version(NULL)); > #endif >+return g; > } > > #endif /* whole file */ >diff -ur exim.orig/src/verify.c exim/src/verify.c >--- exim.orig/src/verify.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/src/verify.c 2022-06-23 16:41:10.000000000 +0300 >@@ -2,8 +2,8 @@ > * Exim - an Internet mail transport agent * > *************************************************/ > >+/* Copyright (c) The Exim Maintainers 2020 - 2022 */ > /* Copyright (c) University of Cambridge 1995 - 2018 */ >-/* Copyright (c) The Exim Maintainers 2020 */ > /* See the file NOTICE for conditions of use and distribution. */ > > /* Functions concerned with verifying things. The original code for callout >@@ -19,27 +19,6 @@ > uschar ctbuffer[8192]; > > >-/* Structure for caching DNSBL lookups */ >- >-typedef struct dnsbl_cache_block { >- time_t expiry; >- dns_address *rhs; >- uschar *text; >- int rc; >- BOOL text_set; >-} dnsbl_cache_block; >- >- >-/* Anchor for DNSBL cache */ >- >-static tree_node *dnsbl_cache = NULL; >- >- >-/* Bits for match_type in one_check_dnsbl() */ >- >-#define MT_NOT 1 >-#define MT_ALL 2 >- > static uschar cutthrough_response(client_conn_ctx *, char, uschar **, int); > > >@@ -71,7 +50,7 @@ > > if (!(cache_record = dbfn_read_with_length(dbm_file, key, &length))) > { >- HDEBUG(D_verify) debug_printf("callout cache: no %s record found for %s\n", type, key); >+ HDEBUG(D_verify) debug_printf_indent("callout cache: no %s record found for %s\n", type, key); > return NULL; > } > >@@ -85,7 +64,7 @@ > > if (now - cache_record->time_stamp > expire) > { >- HDEBUG(D_verify) debug_printf("callout cache: %s record expired for %s\n", type, key); >+ HDEBUG(D_verify) debug_printf_indent("callout cache: %s record expired for %s\n", type, key); > return NULL; > } > >@@ -99,7 +78,7 @@ > { > if (length == sizeof(dbdata_callout_cache_obs)) > { >- dbdata_callout_cache *new = store_get(sizeof(dbdata_callout_cache), FALSE); >+ dbdata_callout_cache * new = store_get(sizeof(dbdata_callout_cache), GET_UNTAINTED); > memcpy(new, cache_record, length); > new->postmaster_stamp = new->random_stamp = new->time_stamp; > cache_record = new; >@@ -112,7 +91,7 @@ > cache_record->random_result = ccache_unknown; > } > >-HDEBUG(D_verify) debug_printf("callout cache: found %s record for %s\n", type, key); >+HDEBUG(D_verify) debug_printf_indent("callout cache: found %s record for %s\n", type, key); > return cache_record; > } > >@@ -134,16 +113,16 @@ > open_db dbblock; > open_db *dbm_file = NULL; > >-/* Open the callout cache database, it it exists, for reading only at this >+/* Open the callout cache database, if it exists, for reading only at this > stage, unless caching has been disabled. */ > > if (options & vopt_callout_no_cache) > { >- HDEBUG(D_verify) debug_printf("callout cache: disabled by no_cache\n"); >+ HDEBUG(D_verify) debug_printf_indent("callout cache: disabled by no_cache\n"); > } > else if (!(dbm_file = dbfn_open(US"callout", O_RDWR, &dbblock, FALSE, TRUE))) > { >- HDEBUG(D_verify) debug_printf("callout cache: not available\n"); >+ HDEBUG(D_verify) debug_printf_indent("callout cache: not available\n"); > } > else > { >@@ -174,7 +153,7 @@ > || *from_address == 0 && cache_record->result == ccache_reject_mfnull) > { > HDEBUG(D_verify) >- debug_printf("callout cache: domain gave initial rejection, or " >+ debug_printf_indent("callout cache: domain gave initial rejection, or " > "does not accept HELO or MAIL FROM:<>\n"); > setflag(addr, af_verify_nsfail); > addr->user_message = US"(result of an earlier callout reused)."; >@@ -195,14 +174,14 @@ > { > case ccache_accept: > HDEBUG(D_verify) >- debug_printf("callout cache: domain accepts random addresses\n"); >+ debug_printf_indent("callout cache: domain accepts random addresses\n"); > *failure_ptr = US"random"; > dbfn_close(dbm_file); > return TRUE; /* Default yield is OK */ > > case ccache_reject: > HDEBUG(D_verify) >- debug_printf("callout cache: domain rejects random addresses\n"); >+ debug_printf_indent("callout cache: domain rejects random addresses\n"); > *opt_ptr = options & ~vopt_callout_random; > new_domain_record->random_result = ccache_reject; > new_domain_record->random_stamp = cache_record->random_stamp; >@@ -210,7 +189,7 @@ > > default: > HDEBUG(D_verify) >- debug_printf("callout cache: need to check random address handling " >+ debug_printf_indent("callout cache: need to check random address handling " > "(not cached or cache expired)\n"); > dbfn_close(dbm_file); > return FALSE; >@@ -227,7 +206,7 @@ > { > setflag(addr, af_verify_pmfail); > HDEBUG(D_verify) >- debug_printf("callout cache: domain does not accept " >+ debug_printf_indent("callout cache: domain does not accept " > "RCPT TO:<postmaster@domain>\n"); > *yield = FAIL; > *failure_ptr = US"postmaster"; >@@ -239,7 +218,7 @@ > if (cache_record->postmaster_result == ccache_unknown) > { > HDEBUG(D_verify) >- debug_printf("callout cache: need to check RCPT " >+ debug_printf_indent("callout cache: need to check RCPT " > "TO:<postmaster@domain> (not cached or cache expired)\n"); > dbfn_close(dbm_file); > return FALSE; >@@ -250,7 +229,7 @@ > that the value in the cache record is preserved (with its old timestamp). > */ > >- HDEBUG(D_verify) debug_printf("callout cache: domain accepts RCPT " >+ HDEBUG(D_verify) debug_printf_indent("callout cache: domain accepts RCPT " > "TO:<postmaster@domain>\n"); > *pm_ptr = NULL; > new_domain_record->postmaster_result = ccache_accept; >@@ -274,12 +253,12 @@ > if (cache_address_record->result == ccache_accept) > { > HDEBUG(D_verify) >- debug_printf("callout cache: address record is positive\n"); >+ debug_printf_indent("callout cache: address record is positive\n"); > } > else > { > HDEBUG(D_verify) >- debug_printf("callout cache: address record is negative\n"); >+ debug_printf_indent("callout cache: address record is negative\n"); > addr->user_message = US"Previous (cached) callout verification failure"; > *failure_ptr = US"recipient"; > *yield = FAIL; >@@ -316,13 +295,13 @@ > if (dom_rec->result != ccache_unknown) > if (!(dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE, TRUE))) > { >- HDEBUG(D_verify) debug_printf("callout cache: not available\n"); >+ HDEBUG(D_verify) debug_printf_indent("callout cache: not available\n"); > } > else > { > (void)dbfn_write(dbm_file, domain, dom_rec, > (int)sizeof(dbdata_callout_cache)); >- HDEBUG(D_verify) debug_printf("wrote callout cache domain record for %s:\n" >+ HDEBUG(D_verify) debug_printf_indent("wrote callout cache domain record for %s:\n" > " result=%d postmaster=%d random=%d\n", > domain, > dom_rec->result, >@@ -339,13 +318,13 @@ > dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE, TRUE); > if (!dbm_file) > { >- HDEBUG(D_verify) debug_printf("no callout cache available\n"); >+ HDEBUG(D_verify) debug_printf_indent("no callout cache available\n"); > } > else > { > (void)dbfn_write(dbm_file, address_key, addr_rec, > (int)sizeof(dbdata_callout_cache_address)); >- HDEBUG(D_verify) debug_printf("wrote %s callout cache address record for %s\n", >+ HDEBUG(D_verify) debug_printf_indent("wrote %s callout cache address record for %s\n", > addr_rec->result == ccache_accept ? "positive" : "negative", > address_key); > } >@@ -421,7 +400,7 @@ > > if (done) > { >- address_item * na = store_get(sizeof(address_item), FALSE); >+ address_item * na = store_get(sizeof(address_item), GET_UNTAINTED); > *na = cutthrough.addr; > cutthrough.addr = *addr; > cutthrough.addr.host_used = &cutthrough.host; >@@ -466,6 +445,21 @@ > } > > >+ >+ >+/* A rcpt callout, or cached record of one, verified the address. >+Set $domain_data and $local_part_data to detainted versions. >+*/ >+static void >+callout_verified_rcpt(const address_item * addr) >+{ >+address_item a = {.address = addr->address}; >+if (deliver_split_address(&a) != OK) return; >+deliver_localpart_data = string_copy_taint(a.local_part, GET_UNTAINTED); >+deliver_domain_data = string_copy_taint(a.domain, GET_UNTAINTED); >+} >+ >+ > /************************************************* > * Do callout verification for an address * > *************************************************/ >@@ -568,6 +562,7 @@ > { > HDEBUG(D_verify) debug_printf("cannot callout via null transport\n"); > } >+ > else if (Ustrcmp(addr->transport->driver_name, "smtp") != 0) > log_write(0, LOG_MAIN|LOG_PANIC|LOG_CONFIG_FOR, "callout transport '%s': %s is non-smtp", > addr->transport->name, addr->transport->driver_name); >@@ -671,7 +666,7 @@ > log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address, > addr->message); > >- if (!sx) sx = store_get(sizeof(*sx), TRUE); /* tainted buffers */ >+ if (!sx) sx = store_get(sizeof(*sx), GET_TAINTED); /* tainted buffers */ > memset(sx, 0, sizeof(*sx)); > > sx->addrlist = sx->first_addr = addr; >@@ -681,6 +676,7 @@ > sx->conn_args.interface = interface; > sx->helo_data = tf->helo_data; > sx->conn_args.tblock = addr->transport; >+ sx->conn_args.sock = -1; > sx->verify = TRUE; > > tls_retry_connection: >@@ -835,7 +831,7 @@ > sx->cctx.sock = -1; > #ifndef DISABLE_EVENT > (void) event_raise(addr->transport->event_action, >- US"tcp:close", NULL); >+ US"tcp:close", NULL, NULL); > #endif > addr->address = main_address; > addr->transport_return = PENDING_DEFER; >@@ -1116,7 +1112,7 @@ > for (address_item * caddr = &cutthrough.addr, * parent = addr->parent; > parent; > caddr = caddr->parent, parent = parent->parent) >- *(caddr->parent = store_get(sizeof(address_item), FALSE)) = *parent; >+ *(caddr->parent = store_get(sizeof(address_item), GET_UNTAINTED)) = *parent; > > ctctx.outblock.buffer = ctbuffer; > ctctx.outblock.buffersize = sizeof(ctbuffer); >@@ -1146,8 +1142,9 @@ > HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); > (void)close(sx->cctx.sock); > sx->cctx.sock = -1; >+ smtp_debug_cmd_report(); > #ifndef DISABLE_EVENT >- (void) event_raise(addr->transport->event_action, US"tcp:close", NULL); >+ (void) event_raise(addr->transport->event_action, US"tcp:close", NULL, NULL); > #endif > } > } >@@ -1366,7 +1363,7 @@ > if(cutthrough.cctx.sock < 0 || cutthrough.callout_hold_only) > return FALSE; > >-HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> DATA\n"); >+smtp_debug_cmd(US"DATA", 0); > cutthrough_puts(US"DATA\r\n", 6); > cutthrough_flush_send(); > >@@ -1434,7 +1431,7 @@ > */ > client_conn_ctx tmp_ctx = cutthrough.cctx; > ctctx.outblock.ptr = ctbuffer; >- HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> QUIT\n"); >+ smtp_debug_cmd(US"QUIT", 0); > _cutthrough_puts(US"QUIT\r\n", 6); /* avoid recursion */ > _cutthrough_flush_send(); > cutthrough.cctx.sock = -1; /* avoid recursion via read timeout */ >@@ -1453,6 +1450,7 @@ > #endif > HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); > (void)close(fd); >+ smtp_debug_cmd_report(); > HDEBUG(D_acl) debug_printf_indent("----------- cutthrough shutdown (%s) ------------\n", why); > } > ctctx.outblock.ptr = ctbuffer; >@@ -1612,7 +1610,7 @@ > Arguments: > vaddr contains the address to verify; the next field in this block > must be NULL >- f if not NULL, write the result to this file >+ fp if not NULL, write the result to this file > options various option bits: > vopt_fake_sender => this sender verify is not for the real > sender (it was verify=sender=xxxx or an address from a >@@ -1663,9 +1661,9 @@ > BOOL success_on_redirect = (options & vopt_success_on_redirect) != 0; > int i; > int yield = OK; >-int verify_type = expn? v_expn : >- f.address_test_mode? v_none : >- options & vopt_is_recipient? v_recipient : v_sender; >+int verify_type = expn ? v_expn : >+ f.address_test_mode ? v_none : >+ options & vopt_is_recipient ? v_recipient : v_sender; > address_item *addr_list; > address_item *addr_new = NULL; > address_item *addr_remote = NULL; >@@ -1848,6 +1846,8 @@ > > if (rc == OK) > { >+ BOOL local_verify = FALSE; >+ > if (routed) *routed = TRUE; > if (callout > 0) > { >@@ -1874,72 +1874,76 @@ > transport's options, so as to mimic what would happen if we were really > sending a message to this address. */ > >- if ((tp = addr->transport) && !tp->info->local) >- { >- (void)(tp->setup)(tp, addr, &tf, 0, 0, NULL); >+ if ((tp = addr->transport)) >+ if (!tp->info->local) >+ { >+ (void)(tp->setup)(tp, addr, &tf, 0, 0, NULL); > >- /* If the transport has hosts and the router does not, or if the >- transport is configured to override the router's hosts, we must build a >- host list of the transport's hosts, and find the IP addresses */ >+ /* If the transport has hosts and the router does not, or if the >+ transport is configured to override the router's hosts, we must build a >+ host list of the transport's hosts, and find the IP addresses */ > >- if (tf.hosts && (!host_list || tf.hosts_override)) >- { >- uschar *s; >- const uschar *save_deliver_domain = deliver_domain; >- uschar *save_deliver_localpart = deliver_localpart; >- >- host_list = NULL; /* Ignore the router's hosts */ >- >- deliver_domain = addr->domain; >- deliver_localpart = addr->local_part; >- s = expand_string(tf.hosts); >- deliver_domain = save_deliver_domain; >- deliver_localpart = save_deliver_localpart; >- >- if (!s) >- { >- log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand list of hosts " >- "\"%s\" in %s transport for callout: %s", tf.hosts, >- tp->name, expand_string_message); >- } >- else >- { >- int flags; >- host_build_hostlist(&host_list, s, tf.hosts_randomize); >- >- /* Just ignore failures to find a host address. If we don't manage >- to find any addresses, the callout will defer. Note that more than >- one address may be found for a single host, which will result in >- additional host items being inserted into the chain. Hence we must >- save the next host first. */ >- >- flags = HOST_FIND_BY_A | HOST_FIND_BY_AAAA; >- if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE; >- if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS; >- >- for (host_item * host = host_list, * nexthost; host; host = nexthost) >- { >- nexthost = host->next; >- if (tf.gethostbyname || >- string_is_ip_address(host->name, NULL) != 0) >- (void)host_find_byname(host, NULL, flags, NULL, TRUE); >- else >+ if (tf.hosts && (!host_list || tf.hosts_override)) >+ { >+ uschar *s; >+ const uschar *save_deliver_domain = deliver_domain; >+ uschar *save_deliver_localpart = deliver_localpart; >+ >+ host_list = NULL; /* Ignore the router's hosts */ >+ >+ deliver_domain = addr->domain; >+ deliver_localpart = addr->local_part; >+ s = expand_string(tf.hosts); >+ deliver_domain = save_deliver_domain; >+ deliver_localpart = save_deliver_localpart; >+ >+ if (!s) >+ { >+ log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand list of hosts " >+ "\"%s\" in %s transport for callout: %s", tf.hosts, >+ tp->name, expand_string_message); >+ } >+ else >+ { >+ int flags; >+ host_build_hostlist(&host_list, s, tf.hosts_randomize); >+ >+ /* Just ignore failures to find a host address. If we don't manage >+ to find any addresses, the callout will defer. Note that more than >+ one address may be found for a single host, which will result in >+ additional host items being inserted into the chain. Hence we must >+ save the next host first. */ >+ >+ flags = HOST_FIND_BY_A | HOST_FIND_BY_AAAA; >+ if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE; >+ if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS; >+ >+ for (host_item * host = host_list, * nexthost; host; host = nexthost) > { >- const dnssec_domains * dsp = NULL; >- if (Ustrcmp(tp->driver_name, "smtp") == 0) >+ nexthost = host->next; >+ if (tf.gethostbyname || >+ string_is_ip_address(host->name, NULL) != 0) >+ (void)host_find_byname(host, NULL, flags, NULL, TRUE); >+ else > { >- smtp_transport_options_block * ob = >- (smtp_transport_options_block *) tp->options_block; >- dsp = &ob->dnssec; >- } >+ const dnssec_domains * dsp = NULL; >+ if (Ustrcmp(tp->driver_name, "smtp") == 0) >+ { >+ smtp_transport_options_block * ob = >+ (smtp_transport_options_block *) tp->options_block; >+ dsp = &ob->dnssec; >+ } > >- (void) host_find_bydns(host, NULL, flags, NULL, NULL, NULL, >- dsp, NULL, NULL); >+ (void) host_find_bydns(host, NULL, flags, NULL, NULL, NULL, >+ dsp, NULL, NULL); >+ } > } >- } >- } >- } >- } >+ } >+ } >+ } >+ else if ( options & vopt_quota >+ && Ustrcmp(tp->driver_name, "appendfile") == 0) >+ local_verify = TRUE; > > /* Can only do a callout if we have at least one host! If the callout > fails, it will have set ${sender,recipient}_verify_failure. */ >@@ -1963,13 +1967,25 @@ > #ifndef DISABLE_TLS > deliver_set_expansions(NULL); > #endif >+ if ( options & vopt_is_recipient >+ && rc == OK >+ /* set to "random", with OK, for an accepted random */ >+ && !recipient_verify_failure >+ ) >+ callout_verified_rcpt(addr); > } > } >+ else if (local_verify) >+ { >+ HDEBUG(D_verify) debug_printf("Attempting quota verification\n"); >+ >+ deliver_set_expansions(addr); >+ deliver_local(addr, TRUE); >+ rc = addr->transport_return; >+ } > else >- { > HDEBUG(D_verify) debug_printf("Cannot do callout: neither router nor " > "transport provided a host list, or transport is not smtp\n"); >- } > } > } > >@@ -2158,10 +2174,6 @@ > addr_list = addr->next; > > fprintf(fp, "%s", CS addr->address); >-#ifdef EXPERIMENTAL_SRS >- if(addr->prop.srs_sender) >- fprintf(fp, " [srs = %s]", addr->prop.srs_sender); >-#endif > > /* If the address is a duplicate, show something about it. */ > >@@ -2884,28 +2896,27 @@ > */ > > int >-check_host(void *arg, const uschar *ss, const uschar **valueptr, uschar **error) >+check_host(void * arg, const uschar * ss, const uschar ** valueptr, uschar ** error) > { >-check_host_block *cb = (check_host_block *)arg; >+check_host_block * cb = (check_host_block *)arg; > int mlen = -1; > int maskoffset; >-BOOL iplookup = FALSE; >-BOOL isquery = FALSE; >-BOOL isiponly = cb->host_name != NULL && cb->host_name[0] == 0; >-const uschar *t; >+BOOL iplookup = FALSE, isquery = FALSE; >+BOOL isiponly = cb->host_name && !cb->host_name[0]; >+const uschar * t; > uschar * semicolon, * endname, * opts; >-uschar **aliases; >+uschar ** aliases; > > /* Optimize for the special case when the pattern is "*". */ > >-if (*ss == '*' && ss[1] == 0) return OK; >+if (*ss == '*' && !ss[1]) return OK; > > /* If the pattern is empty, it matches only in the case when there is no host - > this can occur in ACL checking for SMTP input using the -bs option. In this > situation, the host address is the empty string. */ > >-if (cb->host_address[0] == 0) return (*ss == 0)? OK : FAIL; >-if (*ss == 0) return FAIL; >+if (!cb->host_address[0]) return *ss ? FAIL : OK; >+if (!*ss) return FAIL; > > /* If the pattern is precisely "@" then match against the primary host name, > provided that host name matching is permitted; if it's "@[]" match against the >@@ -2942,7 +2953,7 @@ > dots). */ > > for (t = ss; isdigit(*t) || *t == '.'; ) t++; >-if (*t == 0 || (*t == '/' && t != ss)) >+if (!*t || (*t == '/' && t != ss)) > { > *error = US"malformed IPv4 address or address mask"; > return ERROR; >@@ -3354,573 +3365,246 @@ > > > >-/************************************************* >-* Perform a single dnsbl lookup * >-*************************************************/ >- >-/* This function is called from verify_check_dnsbl() below. It is also called >-recursively from within itself when domain and domain_txt are different >-pointers, in order to get the TXT record from the alternate domain. >- >-Arguments: >- domain the outer dnsbl domain >- domain_txt alternate domain to lookup TXT record on success; when the >- same domain is to be used, domain_txt == domain (that is, >- the pointers must be identical, not just the text) >- keydomain the current keydomain (for debug message) >- prepend subdomain to lookup (like keydomain, but >- reversed if IP address) >- iplist the list of matching IP addresses, or NULL for "any" >- bitmask true if bitmask matching is wanted >- match_type condition for 'succeed' result >- 0 => Any RR in iplist (=) >- 1 => No RR in iplist (!=) >- 2 => All RRs in iplist (==) >- 3 => Some RRs not in iplist (!==) >- the two bits are defined as MT_NOT and MT_ALL >- defer_return what to return for a defer >- >-Returns: OK if lookup succeeded >- FAIL if not >+/**************************************************** >+ Verify a local user account for quota sufficiency >+****************************************************/ >+ >+/* The real work, done via a re-exec for privs, calls >+down to the transport for the quota check. >+ >+Route and transport (in recipient-verify mode) the >+given recipient. >+ >+A routing result indicating any transport type other than appendfile >+results in a fail. >+ >+Return, on stdout, a result string containing: >+- highlevel result code (OK, DEFER, FAIL) >+- errno >+- where string >+- message string > */ > >-static int >-one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain, >- uschar *prepend, uschar *iplist, BOOL bitmask, int match_type, >- int defer_return) >+void >+verify_quota(uschar * address) > { >-dns_answer * dnsa = store_get_dns_answer(); >-dns_scan dnss; >-tree_node *t; >-dnsbl_cache_block *cb; >-int old_pool = store_pool; >-uschar * query; >-int qlen; >- >-/* Construct the specific query domainname */ >- >-query = string_sprintf("%s.%s", prepend, domain); >-if ((qlen = Ustrlen(query)) >= 256) >- { >- log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long " >- "(ignored): %s...", query); >- return FAIL; >- } >+address_item vaddr = {.address = address}; >+BOOL routed; >+uschar * msg = US"\0"; >+int rc, len = 1; > >-/* Look for this query in the cache. */ >- >-if ( (t = tree_search(dnsbl_cache, query)) >- && (cb = t->data.ptr)->expiry > time(NULL) >- ) >- >-/* Previous lookup was cached */ >- >- { >- HDEBUG(D_dnsbl) debug_printf("dnslists: using result of previous lookup\n"); >- } >- >-/* If not cached from a previous lookup, we must do a DNS lookup, and >-cache the result in permanent memory. */ >- >-else >+if ((rc = verify_address(&vaddr, NULL, vopt_is_recipient | vopt_quota, >+ 1, 0, 0, NULL, NULL, &routed)) != OK) > { >- uint ttl = 3600; /* max TTL for positive cache entries */ >- >- store_pool = POOL_PERM; >- >- if (t) >+ uschar * where = recipient_verify_failure; >+ msg = acl_verify_message ? acl_verify_message : vaddr.message; >+ if (!msg) msg = US""; >+ if (rc == DEFER && vaddr.basic_errno == ERRNO_EXIMQUOTA) > { >- HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; "); >- } >- >- else >- { /* Set up a tree entry to cache the lookup */ >- t = store_get(sizeof(tree_node) + qlen + 1 + 1, is_tainted(query)); >- Ustrcpy(t->name, query); >- t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block), FALSE); >- (void)tree_insertnode(&dnsbl_cache, t); >- } >- >- /* Do the DNS lookup . */ >- >- HDEBUG(D_dnsbl) debug_printf("new DNS lookup for %s\n", query); >- cb->rc = dns_basic_lookup(dnsa, query, T_A); >- cb->text_set = FALSE; >- cb->text = NULL; >- cb->rhs = NULL; >- >- /* If the lookup succeeded, cache the RHS address. The code allows for >- more than one address - this was for complete generality and the possible >- use of A6 records. However, A6 records are no longer supported. Leave the code >- here, just in case. >- >- Quite apart from one A6 RR generating multiple addresses, there are DNS >- lists that return more than one A record, so we must handle multiple >- addresses generated in that way as well. >- >- Mark the cache entry with the "now" plus the minimum of the address TTLs, >- or the RFC 2308 negative-cache value from the SOA if none were found. */ >- >- switch (cb->rc) >- { >- case DNS_SUCCEED: >- { >- dns_address ** addrp = &cb->rhs; >- dns_address * da; >- for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; >- rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) >- if (rr->type == T_A && (da = dns_address_from_rr(dnsa, rr))) >- { >- *addrp = da; >- while (da->next) da = da->next; >- addrp = &da->next; >- if (ttl > rr->ttl) ttl = rr->ttl; >- } >- >- if (cb->rhs) >- { >- cb->expiry = time(NULL) + ttl; >- break; >- } >- >- /* If we didn't find any A records, change the return code. This can >- happen when there is a CNAME record but there are no A records for what >- it points to. */ >- >- cb->rc = DNS_NODATA; >- } >- /*FALLTHROUGH*/ >- >- case DNS_NOMATCH: >- case DNS_NODATA: >- { >- /* Although there already is a neg-cache layer maintained by >- dns_basic_lookup(), we have a dnslist cache entry allocated and >- tree-inserted. So we may as well use it. */ >- >- time_t soa_negttl = dns_expire_from_soa(dnsa, T_A); >- cb->expiry = soa_negttl ? soa_negttl : time(NULL) + ttl; >- break; >- } >- >- default: >- cb->expiry = time(NULL) + ttl; >- break; >+ rc = FAIL; /* DEFER -> FAIL */ >+ where = US"quota"; >+ vaddr.basic_errno = 0; > } >+ else if (!where) where = US""; > >- store_pool = old_pool; >- HDEBUG(D_dnsbl) debug_printf("dnslists: wrote cache entry, ttl=%d\n", >- (int)(cb->expiry - time(NULL))); >+ len = 5 + Ustrlen(msg) + 1 + Ustrlen(where); >+ msg = string_sprintf("%c%c%c%c%c%s%c%s", (uschar)rc, >+ (vaddr.basic_errno >> 24) & 0xff, (vaddr.basic_errno >> 16) & 0xff, >+ (vaddr.basic_errno >> 8) & 0xff, vaddr.basic_errno & 0xff, >+ where, '\0', msg); > } > >-/* We now have the result of the DNS lookup, either newly done, or cached >-from a previous call. If the lookup succeeded, check against the address >-list if there is one. This may be a positive equality list (introduced by >-"="), a negative equality list (introduced by "!="), a positive bitmask >-list (introduced by "&"), or a negative bitmask list (introduced by "!&").*/ >- >-if (cb->rc == DNS_SUCCEED) >- { >- dns_address * da = NULL; >- uschar *addlist = cb->rhs->address; >- >- /* For A and AAAA records, there may be multiple addresses from multiple >- records. For A6 records (currently not expected to be used) there may be >- multiple addresses from a single record. */ >- >- for (da = cb->rhs->next; da; da = da->next) >- addlist = string_sprintf("%s, %s", addlist, da->address); >- >- HDEBUG(D_dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %s)\n", >- query, addlist); >- >- /* Address list check; this can be either for equality, or via a bitmask. >- In the latter case, all the bits must match. */ >- >- if (iplist) >- { >- for (da = cb->rhs; da; da = da->next) >- { >- int ipsep = ','; >- const uschar *ptr = iplist; >- uschar *res; >- >- /* Handle exact matching */ >- >- if (!bitmask) >- { >- while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0))) >- if (Ustrcmp(CS da->address, res) == 0) >- break; >- } >- >- /* Handle bitmask matching */ >- >- else >- { >- int address[4]; >- int mask = 0; >- >- /* At present, all known DNS blocking lists use A records, with >- IPv4 addresses on the RHS encoding the information they return. I >- wonder if this will linger on as the last vestige of IPv4 when IPv6 >- is ubiquitous? Anyway, for now we use paranoia code to completely >- ignore IPv6 addresses. The default mask is 0, which always matches. >- We change this only for IPv4 addresses in the list. */ >- >- if (host_aton(da->address, address) == 1) mask = address[0]; >- >- /* Scan the returned addresses, skipping any that are IPv6 */ >- >- while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0))) >- { >- if (host_aton(res, address) != 1) continue; >- if ((address[0] & mask) == address[0]) break; >- } >- } >- >- /* If either >- >- (a) An IP address in an any ('=') list matched, or >- (b) No IP address in an all ('==') list matched >- >- then we're done searching. */ >- >- if (((match_type & MT_ALL) != 0) == (res == NULL)) break; >- } >- >- /* If da == NULL, either >+DEBUG(D_verify) debug_printf_indent("verify_quota: len %d\n", len); >+write(1, msg, len); >+return; >+} > >- (a) No IP address in an any ('=') list matched, or >- (b) An IP address in an all ('==') list didn't match > >- so behave as if the DNSBL lookup had not succeeded, i.e. the host is not on >- the list. */ >+/******************************************************************************/ > >- if ((match_type == MT_NOT || match_type == MT_ALL) != (da == NULL)) >- { >- HDEBUG(D_dnsbl) >- { >- uschar *res = NULL; >- switch(match_type) >- { >- case 0: >- res = US"was no match"; break; >- case MT_NOT: >- res = US"was an exclude match"; break; >- case MT_ALL: >- res = US"was an IP address that did not match"; break; >- case MT_NOT|MT_ALL: >- res = US"were no IP addresses that did not match"; break; >- } >- debug_printf("=> but we are not accepting this block class because\n"); >- debug_printf("=> there %s for %s%c%s\n", >- res, >- ((match_type & MT_ALL) == 0)? "" : "=", >- bitmask? '&' : '=', iplist); >- } >- return FAIL; >- } >- } >- >- /* Either there was no IP list, or the record matched, implying that the >- domain is on the list. We now want to find a corresponding TXT record. If an >- alternate domain is specified for the TXT record, call this function >- recursively to look that up; this has the side effect of re-checking that >- there is indeed an A record at the alternate domain. */ >- >- if (domain_txt != domain) >- return one_check_dnsbl(domain_txt, domain_txt, keydomain, prepend, NULL, >- FALSE, match_type, defer_return); >- >- /* If there is no alternate domain, look up a TXT record in the main domain >- if it has not previously been cached. */ >- >- if (!cb->text_set) >- { >- cb->text_set = TRUE; >- if (dns_basic_lookup(dnsa, query, T_TXT) == 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) >- { >- int len = (rr->data)[0]; >- if (len > 511) len = 127; >- store_pool = POOL_PERM; >- cb->text = string_sprintf("%.*s", len, CUS (rr->data+1)); >- store_pool = old_pool; >- break; >- } >- } >- >- dnslist_value = addlist; >- dnslist_text = cb->text; >- return OK; >- } >+/* Quota cache lookup. We use the callout hints db also for the quota cache. >+Return TRUE if a nonexpired record was found, having filled in the yield >+argument. >+*/ > >-/* There was a problem with the DNS lookup */ >+static BOOL >+cached_quota_lookup(const uschar * rcpt, int * yield, >+ int pos_cache, int neg_cache) >+{ >+open_db dbblock, *dbm_file = NULL; >+dbdata_callout_cache_address * cache_address_record; > >-if (cb->rc != DNS_NOMATCH && cb->rc != DNS_NODATA) >+if (!pos_cache && !neg_cache) >+ return FALSE; >+if (!(dbm_file = dbfn_open(US"callout", O_RDWR, &dbblock, FALSE, TRUE))) > { >- log_write(L_dnslist_defer, LOG_MAIN, >- "DNS list lookup defer (probably timeout) for %s: %s", query, >- (defer_return == OK)? US"assumed in list" : >- (defer_return == FAIL)? US"assumed not in list" : >- US"returned DEFER"); >- return defer_return; >+ HDEBUG(D_verify) debug_printf_indent("quota cache: not available\n"); >+ return FALSE; > } >- >-/* No entry was found in the DNS; continue for next domain */ >- >-HDEBUG(D_dnsbl) >+if (!(cache_address_record = (dbdata_callout_cache_address *) >+ get_callout_cache_record(dbm_file, rcpt, US"address", >+ pos_cache, neg_cache))) > { >- debug_printf("DNS lookup for %s failed\n", query); >- debug_printf("=> that means %s is not listed at %s\n", >- keydomain, domain); >+ dbfn_close(dbm_file); >+ return FALSE; > } >- >-return FAIL; >+if (cache_address_record->result == ccache_accept) >+ *yield = OK; >+dbfn_close(dbm_file); >+return TRUE; > } > >+/* Quota cache write */ > >+static void >+cache_quota_write(const uschar * rcpt, int yield, int pos_cache, int neg_cache) >+{ >+open_db dbblock, *dbm_file = NULL; >+dbdata_callout_cache_address cache_address_record; > >+if (!pos_cache && !neg_cache) >+ return; >+if (!(dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE, TRUE))) >+ { >+ HDEBUG(D_verify) debug_printf_indent("quota cache: not available\n"); >+ return; >+ } > >-/************************************************* >-* Check host against DNS black lists * >-*************************************************/ >- >-/* This function runs checks against a list of DNS black lists, until one >-matches. Each item on the list can be of the form >- >- domain=ip-address/key >- >-The domain is the right-most domain that is used for the query, for example, >-blackholes.mail-abuse.org. If the IP address is present, there is a match only >-if the DNS lookup returns a matching IP address. Several addresses may be >-given, comma-separated, for example: x.y.z=127.0.0.1,127.0.0.2. >- >-If no key is given, what is looked up in the domain is the inverted IP address >-of the current client host. If a key is given, it is used to construct the >-domain for the lookup. For example: >- >- dsn.rfc-ignorant.org/$sender_address_domain >- >-After finding a match in the DNS, the domain is placed in $dnslist_domain, and >-then we check for a TXT record for an error message, and if found, save its >-value in $dnslist_text. We also cache everything in a tree, to optimize >-multiple lookups. >+cache_address_record.result = yield == OK ? ccache_accept : ccache_reject; > >-The TXT record is normally looked up in the same domain as the A record, but >-when many lists are combined in a single DNS domain, this will not be a very >-specific message. It is possible to specify a different domain for looking up >-TXT records; this is given before the main domain, comma-separated. For >-example: >+(void)dbfn_write(dbm_file, rcpt, &cache_address_record, >+ (int)sizeof(dbdata_callout_cache_address)); >+HDEBUG(D_verify) debug_printf_indent("wrote %s quota cache record for %s\n", >+ yield == OK ? "positive" : "negative", rcpt); > >- dnslists = http.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.2 : \ >- socks.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.3 >+dbfn_close(dbm_file); >+return; >+} > >-The caching ensures that only one lookup in dnsbl.sorbs.net is done. > >-Note: an address for testing RBL is 192.203.178.39 >-Note: an address for testing DUL is 192.203.178.4 >-Note: a domain for testing RFCI is example.tld.dsn.rfc-ignorant.org >+/* To evaluate a local user's quota, starting in ACL, we need to >+fork & exec to regain privileges, to that we can change to the user's >+identity for access to their files. > > Arguments: >- where the acl type >- listptr the domain/address/data list >- log_msgptr log message on error >- >-Returns: OK successful lookup (i.e. the address is on the list), or >- lookup deferred after +include_unknown >- FAIL name not found, or no data found for the given type, or >- lookup deferred after +exclude_unknown (default) >- DEFER lookup failure, if +defer_unknown was set >+ rcpt Recipient account >+ pos_cache Number of seconds to cache a positive result (delivery >+ to be accepted). Zero to disable caching. >+ neg_cache Number of seconds to cache a negative result. Zero to disable. >+ msg Pointer to result string pointer >+ >+Return: OK/DEFER/FAIL code > */ > > int >-verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr) >+verify_quota_call(const uschar * rcpt, int pos_cache, int neg_cache, >+ uschar ** msg) > { >-int sep = 0; >-int defer_return = FAIL; >-const uschar *list = *listptr; >-uschar *domain; >-uschar revadd[128]; /* Long enough for IPv6 address */ >- >-/* Indicate that the inverted IP address is not yet set up */ >- >-revadd[0] = 0; >+int pfd[2], pid, save_errno, yield = FAIL; >+void (*oldsignal)(int); >+const uschar * where = US"socketpair"; > >-/* In case this is the first time the DNS resolver is being used. */ >+*msg = NULL; > >-dns_init(FALSE, FALSE, FALSE); /*XXX dnssec? */ >- >-/* Loop through all the domains supplied, until something matches */ >- >-while ((domain = string_nextinlist(&list, &sep, NULL, 0))) >+if (cached_quota_lookup(rcpt, &yield, pos_cache, neg_cache)) > { >- int rc; >- BOOL bitmask = FALSE; >- int match_type = 0; >- uschar *domain_txt; >- uschar *comma; >- uschar *iplist; >- uschar *key; >- >- HDEBUG(D_dnsbl) debug_printf("dnslists check: %s\n", domain); >- >- /* Deal with special values that change the behaviour on defer */ >- >- if (domain[0] == '+') >- { >- if (strcmpic(domain, US"+include_unknown") == 0) defer_return = OK; >- else if (strcmpic(domain, US"+exclude_unknown") == 0) defer_return = FAIL; >- else if (strcmpic(domain, US"+defer_unknown") == 0) defer_return = DEFER; >- else >- log_write(0, LOG_MAIN|LOG_PANIC, "unknown item in dnslist (ignored): %s", >- domain); >- continue; >- } >- >- /* See if there's explicit data to be looked up */ >- >- if ((key = Ustrchr(domain, '/'))) *key++ = 0; >- >- /* See if there's a list of addresses supplied after the domain name. This is >- introduced by an = or a & character; if preceded by = we require all matches >- and if preceded by ! we invert the result. */ >- >- if (!(iplist = Ustrchr(domain, '='))) >+ HDEBUG(D_verify) debug_printf_indent("quota cache: address record is %s\n", >+ yield == OK ? "positive" : "negative"); >+ if (yield != OK) > { >- bitmask = TRUE; >- iplist = Ustrchr(domain, '&'); >- } >- >- if (iplist) /* Found either = or & */ >- { >- if (iplist > domain && iplist[-1] == '!') /* Handle preceding ! */ >- { >- match_type |= MT_NOT; >- iplist[-1] = 0; >- } >- >- *iplist++ = 0; /* Terminate domain, move on */ >- >- /* If we found = (bitmask == FALSE), check for == or =& */ >- >- if (!bitmask && (*iplist == '=' || *iplist == '&')) >- { >- bitmask = *iplist++ == '&'; >- match_type |= MT_ALL; >- } >- } >- >- >- /* If there is a comma in the domain, it indicates that a second domain for >- looking up TXT records is provided, before the main domain. Otherwise we must >- set domain_txt == domain. */ >- >- domain_txt = domain; >- if ((comma = Ustrchr(domain, ','))) >- { >- *comma++ = 0; >- domain = comma; >+ recipient_verify_failure = US"quota"; >+ acl_verify_message = *msg = >+ US"Previous (cached) quota verification failure"; > } >+ return yield; >+ } > >- /* Check that what we have left is a sensible domain name. There is no reason >- why these domains should in fact use the same syntax as hosts and email >- domains, but in practice they seem to. However, there is little point in >- actually causing an error here, because that would no doubt hold up incoming >- mail. Instead, I'll just log it. */ >+if (pipe(pfd) != 0) >+ goto fail; > >- for (uschar * s = domain; *s; s++) >- if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_') >- { >- log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains " >- "strange characters - is this right?", domain); >- break; >- } >+where = US"fork"; >+oldsignal = signal(SIGCHLD, SIG_DFL); >+if ((pid = exim_fork(US"quota-verify")) < 0) >+ { >+ save_errno = errno; >+ close(pfd[pipe_write]); >+ close(pfd[pipe_read]); >+ errno = save_errno; >+ goto fail; >+ } > >- /* Check the alternate domain if present */ >+if (pid == 0) /* child */ >+ { >+ close(pfd[pipe_read]); >+ force_fd(pfd[pipe_write], 1); /* stdout to pipe */ >+ close(pfd[pipe_write]); >+ dup2(1, 0); >+ if (debug_fd > 0) force_fd(debug_fd, 2); > >- if (domain_txt != domain) for (uschar * s = domain_txt; *s; s++) >- if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_') >- { >- log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains " >- "strange characters - is this right?", domain_txt); >- break; >- } >+ child_exec_exim(CEE_EXEC_EXIT, FALSE, NULL, FALSE, 3, >+ US"-MCq", string_sprintf("%d", message_size), rcpt); >+ /*NOTREACHED*/ >+ } > >- /* If there is no key string, construct the query by adding the domain name >- onto the inverted host address, and perform a single DNS lookup. */ >+save_errno = errno; >+close(pfd[pipe_write]); > >- if (!key) >- { >- if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP) >- { >- *log_msgptr = string_sprintf >- ("cannot test auto-keyed dnslists condition in %s ACL", >- acl_wherenames[where]); >- return ERROR; >- } >- if (!sender_host_address) return FAIL; /* can never match */ >- if (revadd[0] == 0) invert_address(revadd, sender_host_address); >- rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd, >- iplist, bitmask, match_type, defer_return); >- if (rc == OK) >- { >- dnslist_domain = string_copy(domain_txt); >- dnslist_matched = string_copy(sender_host_address); >- HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n", >- sender_host_address, dnslist_domain); >- } >- if (rc != FAIL) return rc; /* OK or DEFER */ >+if (pid < 0) >+ { >+ DEBUG(D_verify) debug_printf_indent(" fork: %s\n", strerror(save_errno)); >+ } >+else >+ { >+ uschar buf[128]; >+ int n = read(pfd[pipe_read], buf, sizeof(buf)); >+ int status; >+ >+ waitpid(pid, &status, 0); >+ if (status == 0) >+ { >+ uschar * s; >+ >+ if (n > 0) yield = buf[0]; >+ if (n > 4) >+ save_errno = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4]; >+ if ((recipient_verify_failure = n > 5 >+ ? string_copyn_taint(buf+5, n-5, GET_UNTAINTED) : NULL)) >+ { >+ int m; >+ s = buf + 5 + Ustrlen(recipient_verify_failure) + 1; >+ m = n - (s - buf); >+ acl_verify_message = *msg = >+ m > 0 ? string_copyn_taint(s, m, GET_UNTAINTED) : NULL; >+ } >+ >+ DEBUG(D_verify) debug_printf_indent("verify call response:" >+ " len %d yield %s errno '%s' where '%s' msg '%s'\n", >+ n, rc_names[yield], strerror(save_errno), recipient_verify_failure, *msg); >+ >+ if ( yield == OK >+ || save_errno == 0 && Ustrcmp(recipient_verify_failure, "quota") == 0) >+ cache_quota_write(rcpt, yield, pos_cache, neg_cache); >+ else DEBUG(D_verify) >+ debug_printf_indent("result not cacheable\n"); > } >- >- /* If there is a key string, it can be a list of domains or IP addresses to >- be concatenated with the main domain. */ >- > else > { >- int keysep = 0; >- BOOL defer = FALSE; >- uschar *keydomain; >- uschar keyrevadd[128]; >- >- while ((keydomain = string_nextinlist(CUSS &key, &keysep, NULL, 0))) >- { >- uschar *prepend = keydomain; >- >- if (string_is_ip_address(keydomain, NULL) != 0) >- { >- invert_address(keyrevadd, keydomain); >- prepend = keyrevadd; >- } >- >- rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist, >- bitmask, match_type, defer_return); >- if (rc == OK) >- { >- dnslist_domain = string_copy(domain_txt); >- dnslist_matched = string_copy(keydomain); >- HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n", >- keydomain, dnslist_domain); >- return OK; >- } >- >- /* If the lookup deferred, remember this fact. We keep trying the rest >- of the list to see if we get a useful result, and if we don't, we return >- DEFER at the end. */ >- >- if (rc == DEFER) defer = TRUE; >- } /* continue with next keystring domain/address */ >- >- if (defer) return DEFER; >+ DEBUG(D_verify) >+ debug_printf_indent("verify call response: waitpid status 0x%04x\n", status); > } >- } /* continue with next dnsdb outer domain */ >+ } > >-return FAIL; >+close(pfd[pipe_read]); >+signal(SIGCHLD, oldsignal); >+errno = save_errno; >+return yield; >+ >+fail: >+DEBUG(D_verify) debug_printf_indent("verify_quota_call fail in %s\n", where); >+return yield; > } > >+ > /* vi: aw ai sw=2 > */ > /* End of verify.c */ >ТолÑко в exim/src: version.h >ТолÑко в exim/src: version.sh >diff -ur exim.orig/util/gen_pkcs3.c exim/util/gen_pkcs3.c >--- exim.orig/util/gen_pkcs3.c 2023-06-04 16:22:05.000000000 +0300 >+++ exim/util/gen_pkcs3.c 2022-06-23 16:41:10.000000000 +0300 >@@ -1,4 +1,5 @@ > /* Copyright (C) 2012,2016 Phil Pennock. >+ * Copyright (c) The Exim Maintainers 2021 > * This is distributed as part of Exim and licensed under the GPL. > * See the file "NOTICE" for more details. > */
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 46381
: 13372 |
13373
|
13374
|
13375