Только в 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 +#define PCRE2_CODE_UNIT_WIDTH 8 +#include /* 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 @@ : 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 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=: 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 +# include #else - #if !defined(RADIUS_LIB_RADIUSCLIENT) && !defined(RADIUS_LIB_RADIUSCLIENTNEW) - # define RADIUS_LIB_RADIUSCLIENT - #endif - - #ifdef RADIUS_LIB_RADIUSCLIENTNEW - # include - #else - # include - #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 +# else +# include +# 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 - * 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 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/ */ +#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 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 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: \n"); + g = string_cat(g, US"Compiler: \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"), TRUE); + received_protocol = string_copy_taint( + exim_str_fail_toolong(argrest, EXIM_DRIVERNAME_MAX, "-p"), + GET_TAINTED); else { (void) exim_str_fail_toolong(argrest, (EXIM_DRIVERNAME_MAX+1+EXIM_HOSTNAME_MAX), "-p:"); - 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/]: 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-: message waiting information; 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-: message waiting information; 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 \n", name, options); -printf(" = retry | misc | wait- | callout | ratelimit | tls\n"); -exit(1); +printf(" = retry | misc | wait- | 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 #endif +#ifdef EXIM_HAVE_INOTIFY +# include +#endif +#ifdef EXIM_HAVE_KEVENT +# include +#endif + /* C99 integer types, figure out how to undo this if needed for older systems */ #include @@ -515,7 +523,8 @@ /* The header from the PCRE regex package */ -#include +#define PCRE2_CODE_UNIT_WIDTH 8 +#include /* 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 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{}{}{}} @@ -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 variables */ +/* check auth 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", auth_vars[i], &e); + assert_variable_notin(US"auth", US auth_vars[i], &e); -/* check regex variables */ +/* check regex variables. assert_variable_notin() treats as const. */ for (int i = 0; i < REGEX_VARS; i++) if (regex_vars[i]) - assert_variable_notin(US"regex", regex_vars[i], &e); + assert_variable_notin(US"regex", 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])) /* \NL 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"", @@ -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 @@ -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]:""); + uschar * value = US(argv[i] ? argv[i] : ""); 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 ""); -*(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 2003 - 2015 +/* + * Copyright (c) The Exim Maintainers 2015 - 2022 + * Copyright (c) Tom Kistner 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\\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 chunks, 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 2004 - 2015 +/* + * Copyright (c) The Exim Maintainers 2015 - 2022 + * Copyright (c) Tom Kistner 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 * Copyright (C) 2016 - 2020 Jeremy Harris * @@ -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 + +#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 2003-2015 +/* + * Copyright (c) The Exim Maintainers 2016 - 2022 + * Copyright (c) Tom Kistner 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)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 variables as empty. Initialize $0 empty and set it as the only set numerical variable. The authenticator may set $auth @@ -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 */ +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 2003 - 2015 +/* + * Copyright (c) The Exim Maintainers 2016 - 2022 + * Copyright (c) Tom Kistner 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 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 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 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 "--()" it is also quoted for the given . +*/ 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 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/ @@ -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"", - 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"", + 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 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:\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: (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:\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. */