X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/a9a2ef63968bfb6e6237e6b435efd65ccb7cbe65..bb6a0f57e5c1a04c9c191af6a37184970003b1c2:/src/src/transport.c diff --git a/src/src/transport.c b/src/src/transport.c index aaef2f6d7..1e8bb4aa7 100644 --- a/src/src/transport.c +++ b/src/src/transport.c @@ -2,9 +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 - 2021 */ /* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* General functions concerned with transportation, and generic options for all transports. */ @@ -705,7 +706,7 @@ BOOL transport_headers_send(transport_ctx * tctx, BOOL (*sendfn)(transport_ctx * tctx, uschar * s, int len)) { -const uschar *list; +const uschar * list; transport_instance * tblock = tctx ? tctx->tblock : NULL; address_item * addr = tctx ? tctx->addr : NULL; @@ -760,15 +761,18 @@ for (header_line * h = header_list; h; h = h->next) if (h->type != htype_old) if (include_header) { + int len; if (tblock && tblock->rewrite_rules) { rmark reset_point = store_mark(); - header_line *hh; + header_line * hh; if ((hh = rewrite_header(h, NULL, NULL, tblock->rewrite_rules, tblock->rewrite_existflags, FALSE))) { - if (!sendfn(tctx, hh->text, hh->slen)) return FALSE; + len = hh->slen; + if (tctx->options & topt_truncate_headers && len > 998) len = 998; + if (!sendfn(tctx, hh->text, len)) return FALSE; store_reset(reset_point); continue; /* With the next header line */ } @@ -776,13 +780,15 @@ for (header_line * h = header_list; h; h = h->next) if (h->type != htype_old) /* Either no rewriting rules, or it didn't get rewritten */ - if (!sendfn(tctx, h->text, h->slen)) return FALSE; + len = h->slen; + if (tctx->options & topt_truncate_headers && len > 998) len = 998; + if (!sendfn(tctx, h->text, len)) return FALSE; } /* 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, @@ -798,8 +804,8 @@ Headers added to an address by a router are guaranteed to end with a newline. 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) { @@ -810,7 +816,7 @@ if (addr) { 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); } } } @@ -838,7 +844,7 @@ if (tblock && (list = CUS tblock->add_headers)) 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"); } @@ -1037,7 +1043,7 @@ if (tctx->options & topt_use_bdat) if (!(tctx->options & topt_no_body)) { if ((fsize = lseek(deliver_datafile, 0, SEEK_END)) < 0) return FALSE; - fsize -= SPOOL_DATA_START_OFFSET; + fsize -= spool_data_start_offset(message_id); if (size_limit > 0 && fsize > size_limit) fsize = size_limit; size = hsize + fsize; @@ -1095,7 +1101,7 @@ if ( f.spool_file_wireformat ) { ssize_t copied = 0; - off_t offset = SPOOL_DATA_START_OFFSET; + off_t offset = spool_data_start_offset(message_id); /* Write out any header data in the buffer */ @@ -1133,7 +1139,7 @@ if (!(tctx->options & topt_no_body)) nl_check_length = abs(nl_check_length); nl_partial_match = 0; - if (lseek(deliver_datafile, SPOOL_DATA_START_OFFSET, SEEK_SET) < 0) + if (lseek(deliver_datafile, spool_data_start_offset(message_id), SEEK_SET) < 0) return FALSE; while ( (len = MIN(DELIVER_IN_BUFFER_SIZE, size)) > 0 && (len = read(deliver_datafile, deliver_in_buffer, len)) > 0) @@ -1155,8 +1161,12 @@ f.spool_file_wireformat = FALSE; /* 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. */ @@ -1426,7 +1436,7 @@ if (yield) ? !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. */ @@ -1487,12 +1497,19 @@ Returns: nothing */ void -transport_update_waiting(host_item *hostlist, uschar *tpname) +transport_update_waiting(host_item * hostlist, uschar * tpname) { const uschar *prevname = US""; open_db dbblock; open_db *dbm_file; +if (!is_new_message_id(message_id)) + { + DEBUG(D_transport) debug_printf("message_id %s is not new format; " + "skipping wait-%s database update\n", tpname); + return; + } + DEBUG(D_transport) debug_printf("updating wait-%s database\n", tpname); /* Open the database for this transport */ @@ -1507,7 +1524,7 @@ that the message id is in each host record. */ for (host_item * host = hostlist; host; host = host->next) { BOOL already = FALSE; - dbdata_wait *host_record; + dbdata_wait * host_record; int host_length; uschar buffer[256]; @@ -1533,8 +1550,27 @@ for (host_item * host = hostlist; host; host = host->next) for (uschar * s = host_record->text; s < host_record->text + host_length; s += MESSAGE_ID_LENGTH) + { + /* If any ID is seen which is not new-format, wipe the record and + any continuations */ + + if (!is_new_message_id(s)) + { + DEBUG(D_hints_lookup) + debug_printf_indent("NOTE: old or corrupt message-id found in wait=%.200s" + " hints DB; deleting records for %s\n", tpname, host->name); + + (void) dbfn_delete(dbm_file, host->name); + for (int i = host_record->sequence - 1; i >= 0; i--) + (void) dbfn_delete(dbm_file, + (sprintf(CS buffer, "%.200s:%d", host->name, i), buffer)); + + host_record->count = host_record->sequence = 0; + break; + } if (Ustrncmp(s, message_id, MESSAGE_ID_LENGTH) == 0) { already = TRUE; break; } + } /* If we haven't found this message in the main record, search any continuation records that exist. */ @@ -1642,13 +1678,14 @@ typedef struct msgq_s } msgq_t; BOOL -transport_check_waiting(const uschar *transport_name, const uschar *hostname, - int local_message_max, uschar *new_message_id, oicf oicf_func, void *oicf_data) +transport_check_waiting(const uschar * transport_name, const uschar * hostname, + int local_message_max, uschar * new_message_id, + oicf oicf_func, void * oicf_data) { -dbdata_wait *host_record; +dbdata_wait * host_record; int host_length; open_db dbblock; -open_db *dbm_file; +open_db * dbm_file; int i; struct stat statbuf; @@ -1725,6 +1762,22 @@ while (1) for (i = 0; i < host_record->count; ++i) { + /* If any ID is seen which is not new-format, wipe the record and + any continuations */ + + if (!is_new_message_id(host_record->text + (i * MESSAGE_ID_LENGTH))) + { + uschar buffer[256]; + DEBUG(D_hints_lookup) + debug_printf_indent("NOTE: old or corrupt message-id found in wait=%.200s" + " hints DB; deleting records for %s\n", transport_name, hostname); + (void) dbfn_delete(dbm_file, hostname); + for (int i = host_record->sequence - 1; i >= 0; i--) + (void) dbfn_delete(dbm_file, + (sprintf(CS buffer, "%.200s:%d", hostname, i), buffer)); + dbfn_close(dbm_file); + goto retfalse; + } msgq[i].bKeep = TRUE; Ustrncpy_nt(msgq[i].message_id, host_record->text + (i * MESSAGE_ID_LENGTH), @@ -1875,8 +1928,8 @@ return FALSE; /* Just the regain-root-privilege exec portion */ void -transport_do_pass_socket(const uschar *transport_name, const uschar *hostname, - const uschar *hostaddress, uschar *id, int socket_fd) +transport_do_pass_socket(const uschar * transport_name, const uschar * hostname, + const uschar * hostaddress, uschar * id, int socket_fd) { int i = 13; const uschar **argv; @@ -2049,20 +2102,46 @@ else +/* 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 * *************************************************/ /* This function is called when a command line is to be parsed and executed directly, without the use of /bin/sh. It is called by the pipe transport, -the queryprogram router, and also from the main delivery code when setting up a +the queryprogram router, for any ${run } expansion, +and also from the main delivery code when setting up a transport filter process. The code for ETRN also makes use of this; in that case, no addresses are passed. Arguments: argvptr pointer to anchor for argv vector cmd points to the command string (modified IN PLACE) - expand_arguments true if expansion is to occur + flags bits for expand-args, allow taint, allow $recipients expand_failed error value to set if expansion fails; not relevant if addr == NULL addr chain of addresses, or NULL @@ -2075,8 +2154,8 @@ Returns: TRUE if all went well; otherwise an error will be */ BOOL -transport_set_up_command(const uschar ***argvptr, uschar *cmd, - BOOL expand_arguments, int expand_failed, address_item *addr, +transport_set_up_command(const uschar *** argvptr, const uschar * cmd, + unsigned flags, int expand_failed, address_item * addr, const uschar * etext, uschar ** errptr) { const uschar ** argv, * s; @@ -2152,13 +2231,15 @@ DEBUG(D_transport) debug_printf(" argv[%d] = '%s'\n", i, string_printing(argv[i])); } -if (expand_arguments) +if (flags & TSUC_EXPAND_ARGS) { - BOOL allow_dollar_recipients = addr && addr->parent - && Ustrcmp(addr->parent->address, "system-filter") == 0; + BOOL allow_dollar_recipients = (flags & TSUC_ALLOW_RECIPIENTS) + || (addr && addr->parent && Ustrcmp(addr->parent->address, "system-filter") == 0); /*XXX could we check this at caller? */ for (int i = 0; argv[i]; i++) { + DEBUG(D_expand) debug_printf_indent("arg %d\n", i); + /* Handle special fudge for passing an address list */ if (addr && @@ -2182,6 +2263,16 @@ if (expand_arguments) 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++; } @@ -2288,7 +2379,11 @@ if (expand_arguments) for (int address_pipe_i = 0; address_pipe_argv[address_pipe_i]; address_pipe_i++, argcount++) - argv[i++] = address_pipe_argv[address_pipe_i]; + { + 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--; @@ -2300,9 +2395,10 @@ if (expand_arguments) else { const uschar *expanded_arg; + BOOL enable_dollar_recipients_g = f.enable_dollar_recipients; f.enable_dollar_recipients = allow_dollar_recipients; expanded_arg = expand_cstring(argv[i]); - f.enable_dollar_recipients = FALSE; + f.enable_dollar_recipients = enable_dollar_recipients_g; if (!expanded_arg) { @@ -2317,6 +2413,17 @@ if (expand_arguments) 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 ( !(flags & TSUC_ALLOW_TAINTED_ARGS) + && arg_is_tainted(expanded_arg, i, addr, etext, errptr)) + return FALSE; argv[i] = expanded_arg; } } @@ -2325,7 +2432,10 @@ if (expand_arguments) { debug_printf("direct command after expansion:\n"); for (int i = 0; argv[i]; i++) - debug_printf(" argv[%d] = %s\n", i, string_printing(argv[i])); + { + debug_printf(" argv[%d] = '%s'\n", i, string_printing(argv[i])); + debug_print_taint(argv[i]); + } } }