X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/8e9fdd6369f0a7a81f0ca195e24edd372f7ca3ef..44b6e099b76f403a55e77650821f8a69e9d2682e:/src/src/transport.c diff --git a/src/src/transport.c b/src/src/transport.c index d92ad4c37..d6cedf911 100644 --- a/src/src/transport.c +++ b/src/src/transport.c @@ -2,8 +2,10 @@ * Exim - an Internet mail transport agent * *************************************************/ +/* Copyright (c) The Exim Maintainers 2020 - 2022 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* General functions concerned with transportation, and generic options for all transports. */ @@ -252,7 +254,6 @@ for (int i = 0; i < 100; i++) 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 @@ -265,6 +266,7 @@ for (int i = 0; i < 100; i++) } else /* Timeout wanted. */ { + sigalrm_seen = FALSE; ALARM(local_timeout); rc = tpt_write(fd, block, len, more, tctx->options); save_errno = errno; @@ -279,8 +281,7 @@ for (int i = 0; i < 100; i++) 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--; } @@ -651,7 +652,7 @@ so that we don't handle it again. */ 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; @@ -675,7 +676,7 @@ if (ppp) return TRUE; /* 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; @@ -782,7 +783,7 @@ for (header_line * h = header_list; h; h = h->next) if (h->type != htype_old) /* 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 +799,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 +811,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 +839,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"); } @@ -884,7 +885,7 @@ transport_write_timeout non-zero. 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; @@ -903,6 +904,7 @@ Arguments: 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 @@ -956,10 +958,10 @@ if (!(tctx->options & topt_no_headers)) if (tctx->options & topt_add_return_path) { - uschar buffer[ADDRESS_MAXLENGTH + 20]; - int n = sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH, - return_path); - if (!write_chunk(tctx, buffer, n)) goto bad; + int n; + uschar * s = string_sprintf("Return-path: <%.*s>\n%n", + EXIM_EMAILADDR_MAX, return_path, &n); + if (!write_chunk(tctx, s, n)) goto bad; } /* Add envelope-to: if requested */ @@ -1154,13 +1156,18 @@ 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. */ -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)); } @@ -1188,7 +1195,8 @@ transport_write_message(transport_ctx * tctx, int size_limit) { BOOL last_filter_was_NL = TRUE; BOOL save_spool_file_wireformat = f.spool_file_wireformat; -int rc, len, yield, fd_read, fd_write, save_errno; +BOOL yield; +int rc, len, fd_read, fd_write, save_errno; int pfd[2] = {-1, -1}; pid_t filter_pid, write_pid; @@ -1232,10 +1240,10 @@ write_pid = (pid_t)(-1); { int bits = fcntl(tctx->u.fd, F_GETFD); - (void)fcntl(tctx->u.fd, F_SETFD, bits | FD_CLOEXEC); + (void) fcntl(tctx->u.fd, F_SETFD, bits | FD_CLOEXEC); filter_pid = child_open(USS transport_filter_argv, NULL, 077, - &fd_write, &fd_read, FALSE); - (void)fcntl(tctx->u.fd, F_SETFD, bits & ~FD_CLOEXEC); + &fd_write, &fd_read, FALSE, US"transport-filter"); + (void) fcntl(tctx->u.fd, F_SETFD, bits & ~FD_CLOEXEC); } if (filter_pid < 0) goto TIDY_UP; /* errno set */ @@ -1248,7 +1256,7 @@ via a(nother) pipe. While writing to the filter, we do not do the CRLF, smtp dots, or check string processing. */ if (pipe(pfd) != 0) goto TIDY_UP; /* errno set */ -if ((write_pid = exim_fork(US"transport filter")) == 0) +if ((write_pid = exim_fork(US"tpt-filter-writer")) == 0) { BOOL rc; (void)close(fd_read); @@ -1257,7 +1265,7 @@ if ((write_pid = exim_fork(US"transport filter")) == 0) 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); @@ -1272,7 +1280,7 @@ if ((write_pid = exim_fork(US"transport filter")) == 0) != sizeof(struct timeval) ) rc = FALSE; /* compiler quietening */ - exim_underbar_exit(0, US"tpt-filter"); + exim_underbar_exit(EXIT_SUCCESS); } save_errno = errno; @@ -1320,6 +1328,7 @@ for (;;) ALARM_CLR(0); if (sigalrm_seen) { + DEBUG(D_transport) debug_printf("timed out reading from filter\n"); errno = ETIMEDOUT; f.transport_filter_timed_out = TRUE; goto TIDY_UP; @@ -1392,11 +1401,10 @@ if (write_pid > 0) 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; } } @@ -1423,7 +1431,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. */ @@ -1438,7 +1446,7 @@ DEBUG(D_transport) { debug_printf("end of filtering transport writing: yield=%d\n", yield); if (!yield) - debug_printf("errno=%d more_errno=%d\n", errno, tctx->addr->more_errno); + debug_printf(" errno=%d more_errno=%d\n", errno, tctx->addr->more_errno); } return yield; @@ -1518,7 +1526,7 @@ for (host_item * host = hostlist; host; host = host->next) 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; } @@ -1567,7 +1575,7 @@ for (host_item * host = hostlist; host; host = host->next) { 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 @@ -1582,7 +1590,7 @@ for (host_item * host = hostlist; host; host = host->next) 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; } @@ -1596,7 +1604,8 @@ for (host_item * host = hostlist; host; host = host->next) /* 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 */ @@ -1624,7 +1633,6 @@ Arguments: 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 @@ -1640,7 +1648,7 @@ typedef struct msgq_s 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; @@ -1650,13 +1658,12 @@ open_db *dbm_file; int i; struct stat statbuf; -*more = FALSE; - DEBUG(D_transport) { debug_printf("transport_check_waiting entered\n"); debug_printf(" sequence=%d local_max=%d global_max=%d\n", continue_sequence, local_message_max, connection_max_messages); + acl_level++; } /* Do nothing if we have hit the maximum number that can be send down one @@ -1666,23 +1673,23 @@ if (connection_max_messages >= 0) local_message_max = connection_max_messages; if (local_message_max > 0 && continue_sequence >= local_message_max) { DEBUG(D_transport) - debug_printf("max messages for one connection reached: returning\n"); - return FALSE; + debug_printf_indent("max messages for one connection reached: returning\n"); + goto retfalse; } /* Open the waiting information database. */ if (!(dbm_file = dbfn_open(string_sprintf("wait-%.200s", transport_name), O_RDWR, &dbblock, TRUE, TRUE))) - return FALSE; + goto retfalse; /* See if there is a record for this host; if not, there's nothing to do. */ if (!(host_record = dbfn_read(dbm_file, hostname))) { dbfn_close(dbm_file); - DEBUG(D_transport) debug_printf("no messages waiting for %s\n", hostname); - return FALSE; + DEBUG(D_transport) debug_printf_indent("no messages waiting for %s\n", hostname); + goto retfalse; } /* If the data in the record looks corrupt, just log something and @@ -1693,7 +1700,7 @@ if (host_record->count > WAIT_NAME_MAX) dbfn_close(dbm_file); log_write(0, LOG_MAIN|LOG_PANIC, "smtp-wait database entry for %s has bad " "count=%d (max=%d)", hostname, host_record->count, WAIT_NAME_MAX); - return FALSE; + goto retfalse; } /* Scan the message ids in the record from the end towards the beginning, @@ -1717,7 +1724,7 @@ while (1) /* 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; @@ -1725,12 +1732,13 @@ while (1) { msgq[i].bKeep = TRUE; - Ustrncpy_nt(msgq[i].message_id, host_record->text + (i * MESSAGE_ID_LENGTH), + Ustrncpy_nt(msgq[i].message_id, host_record->text + (i * MESSAGE_ID_LENGTH), MESSAGE_ID_LENGTH); msgq[i].message_id[MESSAGE_ID_LENGTH] = 0; } /* first thing remove current message id if it exists */ + /*XXX but what if it has un-sent addrs? */ for (i = 0; i < msgq_count; ++i) if (Ustrcmp(msgq[i].message_id, message_id) == 0) @@ -1830,8 +1838,8 @@ while (1) if (host_length <= 0) { dbfn_close(dbm_file); - DEBUG(D_transport) debug_printf("waiting messages already delivered\n"); - return FALSE; + DEBUG(D_transport) debug_printf_indent("waiting messages already delivered\n"); + goto retfalse; } /* we were not able to find an acceptable message, nor was there a @@ -1842,7 +1850,7 @@ while (1) { Ustrcpy(new_message_id, message_id); dbfn_close(dbm_file); - return FALSE; + goto retfalse; } } /* we need to process a continuation record */ @@ -1854,13 +1862,16 @@ record if required, close the database, and return TRUE. */ 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); +DEBUG(D_transport) {acl_level--; debug_printf("transport_check_waiting: TRUE\n"); } return TRUE; + +retfalse: +DEBUG(D_transport) {acl_level--; debug_printf("transport_check_waiting: FALSE\n"); } +return FALSE; } /************************************************* @@ -1872,9 +1883,21 @@ void transport_do_pass_socket(const uschar *transport_name, const uschar *hostname, const uschar *hostaddress, uschar *id, int socket_fd) { -int i = 20; +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. */ @@ -1893,11 +1916,31 @@ if (smtp_peer_options & OPTION_TLS) argv[i++] = sending_ip_address; argv[i++] = string_sprintf("%d", sending_port); argv[i++] = tls_out.active.sock >= 0 ? tls_out.cipher : continue_proxy_cipher; + + if (tls_out.sni) + { + argv[i++] = +#ifdef SUPPORT_DANE + tls_out.dane_verified ? US"-MCr" : +#endif + US"-MCs"; + argv[i++] = tls_out.sni; + } } else 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"; @@ -1905,6 +1948,17 @@ if (queue_run_pid != (pid_t)0) 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; @@ -1923,6 +1977,7 @@ if (socket_fd != 0) 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)); @@ -1947,14 +2002,24 @@ Returns: FALSE if fork fails; TRUE otherwise 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"); -if ((pid = exim_fork(US"continued-transport interproc")) == 0) +#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 test harness, wait for a bit to allow the previous process time to finish, @@ -1962,10 +2027,7 @@ if ((pid = exim_fork(US"continued-transport interproc")) == 0) automatic comparison. */ if ((pid = exim_fork(US"continued-transport")) != 0) - { - DEBUG(D_transport) debug_printf("transport_pass_socket succeeded (final-pid %d)\n", pid); _exit(EXIT_SUCCESS); - } testharness_pause_ms(1000); transport_do_pass_socket(transport_name, hostname, hostaddress, @@ -1980,7 +2042,6 @@ if (pid > 0) { int rc; while ((rc = wait(&status)) != pid && (rc >= 0 || errno != ECHILD)); - DEBUG(D_transport) debug_printf("transport_pass_socket succeeded (inter-pid %d)\n", pid); return TRUE; } else @@ -1993,6 +2054,31 @@ 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 * *************************************************/ @@ -2010,6 +2096,7 @@ Arguments: 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 @@ -2019,15 +2106,12 @@ 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, - 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 @@ -2036,7 +2120,7 @@ delivery batch option is set. */ 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 @@ -2044,33 +2128,30 @@ trailing space at the start and end. Double-quoted arguments can contain \\ and 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; @@ -2104,16 +2185,16 @@ DEBUG(D_transport) 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++) { + DEBUG(D_expand) debug_printf_indent("arg %d\n", 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)) { @@ -2134,6 +2215,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++; } @@ -2145,14 +2236,13 @@ if (expand_arguments) /* 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; @@ -2161,13 +2251,12 @@ if (expand_arguments) 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\" " @@ -2176,32 +2265,29 @@ if (expand_arguments) 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; @@ -2211,8 +2297,9 @@ if (expand_arguments) } /* 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; @@ -2222,12 +2309,12 @@ if (expand_arguments) } /* 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 */ @@ -2239,15 +2326,16 @@ if (expand_arguments) ); /* 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--; @@ -2259,9 +2347,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) { @@ -2276,6 +2365,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 ( !allow_tainted_args + && arg_is_tainted(expanded_arg, i, addr, etext, errptr)) + return FALSE; argv[i] = expanded_arg; } } @@ -2283,14 +2383,30 @@ if (expand_arguments) 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 */