From e90fcc54809db2591dc083f43ef54c6ec8c60847 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 18:16:13 +0200 Subject: Adding upstream version 4.96. Signed-off-by: Daniel Baumann --- src/transports/autoreply.c | 821 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 821 insertions(+) create mode 100644 src/transports/autoreply.c (limited to 'src/transports/autoreply.c') diff --git a/src/transports/autoreply.c b/src/transports/autoreply.c new file mode 100644 index 0000000..211e328 --- /dev/null +++ b/src/transports/autoreply.c @@ -0,0 +1,821 @@ +/************************************************* +* 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. */ + + +#include "../exim.h" +#include "autoreply.h" + + + +/* Options specific to the autoreply transport. They must be in alphabetic +order (note that "_" comes before the lower case letters). Those starting +with "*" are not settable by the user but are used by the option-reading +software for alternative value types. Some options are publicly visible and so +are stored in the driver instance block. These are flagged with opt_public. */ +#define LOFF(field) OPT_OFF(autoreply_transport_options_block, field) + +optionlist autoreply_transport_options[] = { + { "bcc", opt_stringptr, LOFF(bcc) }, + { "cc", opt_stringptr, LOFF(cc) }, + { "file", opt_stringptr, LOFF(file) }, + { "file_expand", opt_bool, LOFF(file_expand) }, + { "file_optional", opt_bool, LOFF(file_optional) }, + { "from", opt_stringptr, LOFF(from) }, + { "headers", opt_stringptr, LOFF(headers) }, + { "log", opt_stringptr, LOFF(logfile) }, + { "mode", opt_octint, LOFF(mode) }, + { "never_mail", opt_stringptr, LOFF(never_mail) }, + { "once", opt_stringptr, LOFF(oncelog) }, + { "once_file_size", opt_int, LOFF(once_file_size) }, + { "once_repeat", opt_stringptr, LOFF(once_repeat) }, + { "reply_to", opt_stringptr, LOFF(reply_to) }, + { "return_message", opt_bool, LOFF(return_message) }, + { "subject", opt_stringptr, LOFF(subject) }, + { "text", opt_stringptr, LOFF(text) }, + { "to", opt_stringptr, LOFF(to) }, +}; + +/* Size of the options list. An extern variable has to be used so that its +address can appear in the tables drtables.c. */ + +int autoreply_transport_options_count = + sizeof(autoreply_transport_options)/sizeof(optionlist); + + +#ifdef MACRO_PREDEF + +/* Dummy values */ +autoreply_transport_options_block autoreply_transport_option_defaults = {0}; +void autoreply_transport_init(transport_instance *tblock) {} +BOOL autoreply_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;} + +#else /*!MACRO_PREDEF*/ + + +/* Default private options block for the autoreply transport. +All non-mentioned lements zero/null/false. */ + +autoreply_transport_options_block autoreply_transport_option_defaults = { + .mode = 0600, +}; + + + +/* Type of text for the checkexpand() function */ + +enum { cke_text, cke_hdr, cke_file }; + + + +/************************************************* +* Initialization entry point * +*************************************************/ + +/* Called for each instance, after its options have been read, to +enable consistency checks to be done, or anything else that needs +to be set up. */ + +void +autoreply_transport_init(transport_instance *tblock) +{ +/* +autoreply_transport_options_block *ob = + (autoreply_transport_options_block *)(tblock->options_block); +*/ + +/* If a fixed uid field is set, then a gid field must also be set. */ + +if (tblock->uid_set && !tblock->gid_set && tblock->expand_gid == NULL) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG, + "user set without group for the %s transport", tblock->name); +} + + + + +/************************************************* +* Expand string and check * +*************************************************/ + +/* If the expansion fails, the error is set up in the address. Expanded +strings must be checked to ensure they contain only printing characters +and white space. If not, the function fails. + +Arguments: + s string to expand + addr address that is being worked on + name transport name, for error text + type type, for checking content: + cke_text => no check + cke_hdr => header, allow \n + whitespace + cke_file => file name, no non-printers allowed + +Returns: expanded string if expansion succeeds; + NULL otherwise +*/ + +static uschar * +checkexpand(uschar *s, address_item *addr, uschar *name, int type) +{ +uschar *ss = expand_string(s); + +if (!ss) + { + addr->transport_return = FAIL; + addr->message = string_sprintf("Expansion of \"%s\" failed in %s transport: " + "%s", s, name, expand_string_message); + return NULL; + } + +if (type != cke_text) for (uschar * t = ss; *t != 0; t++) + { + int c = *t; + const uschar * sp; + if (mac_isprint(c)) continue; + if (type == cke_hdr && c == '\n' && (t[1] == ' ' || t[1] == '\t')) continue; + sp = string_printing(s); + addr->transport_return = FAIL; + addr->message = string_sprintf("Expansion of \"%s\" in %s transport " + "contains non-printing character %d", sp, name, c); + return NULL; + } + +return ss; +} + + + + +/************************************************* +* Check a header line for never_mail * +*************************************************/ + +/* This is called to check to, cc, and bcc for addresses in the never_mail +list. Any that are found are removed. + +Arguments: + list list of addresses to be checked + never_mail an address list, already expanded + +Returns: edited replacement address list, or NULL, or original +*/ + +static uschar * +check_never_mail(uschar * list, const uschar * never_mail) +{ +rmark reset_point = store_mark(); +uschar * newlist = string_copy(list); +uschar * s = newlist; +BOOL hit = FALSE; + +while (*s) + { + uschar *error, *next; + uschar *e = parse_find_address_end(s, FALSE); + int terminator = *e; + int start, end, domain, rc; + + /* Temporarily terminate the string at the address end while extracting + the operative address within. */ + + *e = 0; + next = parse_extract_address(s, &error, &start, &end, &domain, FALSE); + *e = terminator; + + /* If there is some kind of syntax error, just give up on this header + line. */ + + if (!next) break; + + /* See if the address is on the never_mail list */ + + rc = match_address_list(next, /* address to check */ + TRUE, /* start caseless */ + FALSE, /* don't expand the list */ + &never_mail, /* the list */ + NULL, /* no caching */ + -1, /* no expand setup */ + 0, /* separator from list */ + NULL); /* no lookup value return */ + + if (rc == OK) /* Remove this address */ + { + DEBUG(D_transport) + debug_printf("discarding recipient %s (matched never_mail)\n", next); + hit = TRUE; + if (terminator == ',') e++; + memmove(s, e, Ustrlen(e) + 1); + } + else /* Skip over this address */ + { + s = e; + if (terminator == ',') s++; + } + } + +/* 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 = newlist + Ustrlen(newlist); +while (s > newlist && (isspace(s[-1]) || s[-1] == ',')) s--; +*s = 0; + +/* Check to see if there any addresses left; if not, return NULL */ + +s = newlist; +while (s && isspace(*s)) s++; +if (*s) + return newlist; + +store_reset(reset_point); +return NULL; +} + + + +/************************************************* +* Main entry point * +*************************************************/ + +/* See local README for interface details. This transport always returns +FALSE, indicating that the top address has the status for all - though in fact +this transport can handle only one address at at time anyway. */ + +BOOL +autoreply_transport_entry( + transport_instance *tblock, /* data for this instantiation */ + address_item *addr) /* address we are working on */ +{ +int fd, pid, rc; +int cache_fd = -1; +int cache_size = 0; +int add_size = 0; +EXIM_DB * dbm_file = NULL; +BOOL file_expand, return_message; +uschar *from, *reply_to, *to, *cc, *bcc, *subject, *headers, *text, *file; +uschar *logfile, *oncelog; +uschar *cache_buff = NULL; +uschar *cache_time = NULL; +uschar *message_id = NULL; +header_line *h; +time_t now = time(NULL); +time_t once_repeat_sec = 0; +FILE *fp; +FILE *ff = NULL; + +autoreply_transport_options_block *ob = + (autoreply_transport_options_block *)(tblock->options_block); + +DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name); + +/* Set up for the good case */ + +addr->transport_return = OK; +addr->basic_errno = 0; + +/* If the address is pointing to a reply block, then take all the data +from that block. It has typically been set up by a mail filter processing +router. Otherwise, the data must be supplied by this transport, and +it has to be expanded here. */ + +if (addr->reply) + { + DEBUG(D_transport) debug_printf("taking data from address\n"); + from = addr->reply->from; + reply_to = addr->reply->reply_to; + to = addr->reply->to; + cc = addr->reply->cc; + bcc = addr->reply->bcc; + subject = addr->reply->subject; + headers = addr->reply->headers; + text = addr->reply->text; + file = addr->reply->file; + logfile = addr->reply->logfile; + oncelog = addr->reply->oncelog; + once_repeat_sec = addr->reply->once_repeat; + file_expand = addr->reply->file_expand; + expand_forbid = addr->reply->expand_forbid; + return_message = addr->reply->return_message; + } +else + { + uschar *oncerepeat = ob->once_repeat; + + DEBUG(D_transport) debug_printf("taking data from transport\n"); + from = ob->from; + reply_to = ob->reply_to; + to = ob->to; + cc = ob->cc; + bcc = ob->bcc; + subject = ob->subject; + headers = ob->headers; + text = ob->text; + file = ob->file; + logfile = ob->logfile; + oncelog = ob->oncelog; + file_expand = ob->file_expand; + return_message = ob->return_message; + + if ( from && !(from = checkexpand(from, addr, tblock->name, cke_hdr)) + || reply_to && !(reply_to = checkexpand(reply_to, addr, tblock->name, cke_hdr)) + || to && !(to = checkexpand(to, addr, tblock->name, cke_hdr)) + || cc && !(cc = checkexpand(cc, addr, tblock->name, cke_hdr)) + || bcc && !(bcc = checkexpand(bcc, addr, tblock->name, cke_hdr)) + || subject && !(subject = checkexpand(subject, addr, tblock->name, cke_hdr)) + || headers && !(headers = checkexpand(headers, addr, tblock->name, cke_text)) + || text && !(text = checkexpand(text, addr, tblock->name, cke_text)) + || file && !(file = checkexpand(file, addr, tblock->name, cke_file)) + || logfile && !(logfile = checkexpand(logfile, addr, tblock->name, cke_file)) + || oncelog && !(oncelog = checkexpand(oncelog, addr, tblock->name, cke_file)) + || oncerepeat && !(oncerepeat = checkexpand(oncerepeat, addr, tblock->name, cke_file)) + ) + return FALSE; + + if (oncerepeat) + 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 +remove those that match. */ + +if (ob->never_mail) + { + const uschar *never_mail = expand_string(ob->never_mail); + + if (!never_mail) + { + addr->transport_return = FAIL; + addr->message = string_sprintf("Failed to expand \"%s\" for " + "\"never_mail\" in %s transport", ob->never_mail, tblock->name); + return FALSE; + } + + 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) + { + DEBUG(D_transport) + debug_printf("*** all recipients removed by never_mail\n"); + return OK; + } + } + +/* If the -N option is set, can't do any more. */ + +if (f.dont_deliver) + { + DEBUG(D_transport) + debug_printf("*** delivery by %s transport bypassed by -N option\n", + tblock->name); + return FALSE; + } + + +/* If the oncelog field is set, we send want to send only one message to the +given recipient(s). This works only on the "To" field. If there is no "To" +field, the message is always sent. If the To: field contains more than one +recipient, the effect might not be quite as envisaged. If once_file_size is +set, instead of a dbm file, we use a regular file containing a circular buffer +recipient cache. */ + +if (oncelog && *oncelog && to) + { + time_t then = 0; + + if (is_tainted(oncelog)) + { + addr->transport_return = DEFER; + addr->basic_errno = EACCES; + addr->message = string_sprintf("Tainted '%s' (once file for %s transport)" + " not permitted", oncelog, tblock->name); + goto END_OFF; + } + + /* Handle fixed-size cache file. */ + + if (ob->once_file_size > 0) + { + uschar * nextp; + struct stat statbuf; + + cache_fd = Uopen(oncelog, O_CREAT|O_RDWR, ob->mode); + if (cache_fd < 0 || fstat(cache_fd, &statbuf) != 0) + { + addr->transport_return = DEFER; + addr->basic_errno = errno; + addr->message = string_sprintf("Failed to %s \"once\" file %s when " + "sending message from %s transport: %s", + cache_fd < 0 ? "open" : "stat", oncelog, tblock->name, strerror(errno)); + goto END_OFF; + } + + /* Get store in the temporary pool and read the entire file into it. We get + an amount of store that is big enough to add the new entry on the end if we + need to do that. */ + + cache_size = statbuf.st_size; + add_size = sizeof(time_t) + Ustrlen(to) + 1; + cache_buff = store_get(cache_size + add_size, oncelog); + + if (read(cache_fd, cache_buff, cache_size) != cache_size) + { + addr->transport_return = DEFER; + addr->basic_errno = errno; + addr->message = US"error while reading \"once\" file"; + goto END_OFF; + } + + DEBUG(D_transport) debug_printf("%d bytes read from %s\n", cache_size, oncelog); + + /* Scan the data for this recipient. Each entry in the file starts with + a time_t sized time value, followed by the address, followed by a binary + zero. If we find a match, put the time into "then", and the place where it + was found into "cache_time". Otherwise, "then" is left at zero. */ + + for (uschar * p = cache_buff; p < cache_buff + cache_size; p = nextp) + { + uschar *s = p + sizeof(time_t); + nextp = s + Ustrlen(s) + 1; + if (Ustrcmp(to, s) == 0) + { + memcpy(&then, p, sizeof(time_t)); + cache_time = p; + break; + } + } + } + + /* Use a DBM file for the list of previous recipients. */ + + else + { + EXIM_DATUM key_datum, result_datum; + uschar * dirname, * s; + + dirname = (s = Ustrrchr(oncelog, '/')) + ? string_copyn(oncelog, s - oncelog) : NULL; + if (!(dbm_file = exim_dbopen(oncelog, dirname, O_RDWR|O_CREAT, ob->mode))) + { + addr->transport_return = DEFER; + addr->basic_errno = errno; + addr->message = string_sprintf("Failed to open %s file %s when sending " + "message from %s transport: %s", EXIM_DBTYPE, oncelog, tblock->name, + strerror(errno)); + goto END_OFF; + } + + 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 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, + where the datum was filled with a tod_log time, which is assumed to be + different in size. For that, only one message is ever sent. This change + introduced at Exim 3.00. In a couple of years' time the test on the size + can be abolished. */ + + if (exim_datum_size_get(&result_datum) == sizeof(time_t)) + memcpy(&then, exim_datum_data_get(&result_datum), sizeof(time_t)); + else + then = now; + } + } + + /* Either "then" is set zero, if no message has yet been sent, or it + is set to the time of the last sending. */ + + if (then != 0 && (once_repeat_sec <= 0 || now - then < once_repeat_sec)) + { + int log_fd; + if (is_tainted(logfile)) + { + addr->transport_return = DEFER; + addr->basic_errno = EACCES; + addr->message = string_sprintf("Tainted '%s' (logfile for %s transport)" + " not permitted", logfile, tblock->name); + goto END_OFF; + } + + DEBUG(D_transport) debug_printf("message previously sent to %s%s\n", to, + (once_repeat_sec > 0)? " and repeat time not reached" : ""); + log_fd = logfile ? Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode) : -1; + if (log_fd >= 0) + { + uschar *ptr = log_buffer; + sprintf(CS ptr, "%s\n previously sent to %.200s\n", tod_stamp(tod_log), to); + while(*ptr) ptr++; + if(write(log_fd, log_buffer, ptr - log_buffer) != ptr-log_buffer + || close(log_fd)) + DEBUG(D_transport) debug_printf("Problem writing log file %s for %s " + "transport\n", logfile, tblock->name); + } + goto END_OFF; + } + + DEBUG(D_transport) debug_printf("%s %s\n", (then <= 0)? + "no previous message sent to" : "repeat time reached for", to); + } + +/* We are going to send a message. Ensure any requested file is available. */ +if (file) + { + if (is_tainted(file)) + { + addr->transport_return = DEFER; + addr->basic_errno = EACCES; + addr->message = string_sprintf("Tainted '%s' (file for %s transport)" + " not permitted", file, tblock->name); + return FALSE; + } + if (!(ff = Ufopen(file, "rb")) && !ob->file_optional) + { + addr->transport_return = DEFER; + addr->basic_errno = errno; + addr->message = string_sprintf("Failed to open file %s when sending " + "message from %s transport: %s", file, tblock->name, strerror(errno)); + return FALSE; + } + } + +/* Make a subprocess to send the message */ + +if ((pid = child_open_exim(&fd, US"autoreply")) < 0) + { + /* Creation of child failed; defer this delivery. */ + + addr->transport_return = DEFER; + addr->basic_errno = errno; + 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); + return FALSE; + } + +/* Create the message to be sent - recipients are taken from the headers, +as the -t option is used. The "headers" stuff *must* be last in case there +are newlines in it which might, if placed earlier, screw up other headers. */ + +fp = fdopen(fd, "wb"); + +if (from) fprintf(fp, "From: %s\n", from); +if (reply_to) fprintf(fp, "Reply-To: %s\n", reply_to); +if (to) fprintf(fp, "To: %s\n", to); +if (cc) fprintf(fp, "Cc: %s\n", cc); +if (bcc) fprintf(fp, "Bcc: %s\n", bcc); +if (subject) fprintf(fp, "Subject: %s\n", subject); + +/* Generate In-Reply-To from the message_id header; there should +always be one, but code defensively. */ + +for (h = header_list; h; h = h->next) + if (h->type == htype_id) break; + +if (h) + { + message_id = Ustrchr(h->text, ':') + 1; + while (isspace(*message_id)) message_id++; + fprintf(fp, "In-Reply-To: %s", message_id); + } + +moan_write_references(fp, message_id); + +/* Add an Auto-Submitted: header */ + +fprintf(fp, "Auto-Submitted: auto-replied\n"); + +/* Add any specially requested headers */ + +if (headers) fprintf(fp, "%s\n", headers); +fprintf(fp, "\n"); + +if (text) + { + fprintf(fp, "%s", CS text); + if (text[Ustrlen(text)-1] != '\n') fprintf(fp, "\n"); + } + +if (ff) + { + while (Ufgets(big_buffer, big_buffer_size, ff) != NULL) + { + if (file_expand) + { + uschar *s = expand_string(big_buffer); + DEBUG(D_transport) + { + if (!s) + debug_printf("error while expanding line from file:\n %s\n %s\n", + big_buffer, expand_string_message); + } + fprintf(fp, "%s", s ? CS s : CS big_buffer); + } + else fprintf(fp, "%s", CS big_buffer); + } + (void) fclose(ff); + } + +/* Copy the original message if required, observing the return size +limit if we are returning the body. */ + +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"; + transport_ctx tctx = { + .u = {.fd = fileno(fp)}, + .tblock = tblock, + .addr = addr, + .check_string = NULL, + .escape_string = NULL, + .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) + | topt_not_socket + }; + + if (bounce_return_size_limit > 0 && !tblock->headers_only) + { + struct stat statbuf; + int max = (bounce_return_size_limit/DELIVER_IN_BUFFER_SIZE + 1) * + DELIVER_IN_BUFFER_SIZE; + if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max) + { + fprintf(fp, "\n%s" +"------ The body of the message is " OFF_T_FMT " characters long; only the first\n" +"------ %d or so are included here.\n\n", rubric, statbuf.st_size, + (max/1000)*1000); + } + else fprintf(fp, "\n%s\n", rubric); + } + else fprintf(fp, "\n%s\n", rubric); + + fflush(fp); + transport_count = 0; + transport_write_message(&tctx, bounce_return_size_limit); + } + +/* End the message and wait for the child process to end; no timeout. */ + +(void)fclose(fp); +rc = child_close(pid, 0); + +/* Update the "sent to" log whatever the yield. This errs on the side of +missing out a message rather than risking sending more than one. We either have +cache_fd set to a fixed size, circular buffer file, or dbm_file set to an open +DBM file (or neither, if "once" is not set). */ + +/* Update fixed-size cache file. If cache_time is set, we found a previous +entry; that is the spot into which to put the current time. Otherwise we have +to add a new record; remove the first one in the file if the file is too big. +We always rewrite the entire file in a single write operation. This is +(hopefully) going to be the safest thing because there is no interlocking +between multiple simultaneous deliveries. */ + +if (cache_fd >= 0) + { + uschar *from = cache_buff; + int size = cache_size; + + if (lseek(cache_fd, 0, SEEK_SET) == 0) + { + if (!cache_time) + { + cache_time = from + size; + memcpy(cache_time + sizeof(time_t), to, add_size - sizeof(time_t)); + size += add_size; + + if (cache_size > 0 && size > ob->once_file_size) + { + from += sizeof(time_t) + Ustrlen(from + sizeof(time_t)) + 1; + size -= (from - cache_buff); + } + } + + memcpy(cache_time, &now, sizeof(time_t)); + if(write(cache_fd, from, size) != size) + DEBUG(D_transport) debug_printf("Problem writing cache file %s for %s " + "transport\n", oncelog, tblock->name); + } + } + +/* Update DBM file */ + +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_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_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 +try will skip, of course. However, if there were no recipients in the +message, we do not fail. */ + +if (rc != 0) + if (rc == EXIT_NORECIPIENTS) + { + DEBUG(D_any) debug_printf("%s transport: message contained no recipients\n", + tblock->name); + } + else + { + addr->transport_return = DEFER; + addr->message = string_sprintf("Failed to send message from %s " + "transport (%d)", tblock->name, rc); + goto END_OFF; + } + +/* Log the sending of the message if successful and required. If the file +fails to open, it's hard to know what to do. We cannot write to the Exim +log from here, since we may be running under an unprivileged uid. We don't +want to fail the delivery, since the message has been successfully sent. For +the moment, ignore open failures. Write the log entry as a single write() to a +file opened for appending, in order to avoid interleaving of output from +different processes. The log_buffer can be used exactly as for main log +writing. */ + +if (logfile) + { + int log_fd = Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode); + if (log_fd >= 0) + { + gstring gs = { .size = LOG_BUFFER_SIZE, .ptr = 0, .s = log_buffer }, *g = &gs; + + /* Use taint-unchecked routines for writing into log_buffer, trusting + that we'll never expand it. */ + + DEBUG(D_transport) debug_printf("logging message details\n"); + g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "%s\n", tod_stamp(tod_log)); + if (from) + g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " From: %s\n", from); + if (to) + g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " To: %s\n", to); + if (cc) + g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " Cc: %s\n", cc); + if (bcc) + g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " Bcc: %s\n", bcc); + if (subject) + g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " Subject: %s\n", subject); + if (headers) + g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " %s\n", headers); + if(write(log_fd, g->s, g->ptr) != g->ptr || close(log_fd)) + DEBUG(D_transport) debug_printf("Problem writing log file %s for %s " + "transport\n", logfile, tblock->name); + } + else DEBUG(D_transport) debug_printf("Failed to open log file %s for %s " + "transport: %s\n", logfile, tblock->name, strerror(errno)); + } + +END_OFF: +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); + +return FALSE; +} + +#endif /*!MACRO_PREDEF*/ +/* End of transport/autoreply.c */ -- cgit v1.2.3