summaryrefslogtreecommitdiffstats
path: root/src/transports/autoreply.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/transports/autoreply.c885
1 files changed, 885 insertions, 0 deletions
diff --git a/src/transports/autoreply.c b/src/transports/autoreply.c
new file mode 100644
index 0000000..bc38168
--- /dev/null
+++ b/src/transports/autoreply.c
@@ -0,0 +1,885 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* 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. */
+
+optionlist autoreply_transport_options[] = {
+ { "bcc", opt_stringptr,
+ (void *)offsetof(autoreply_transport_options_block, bcc) },
+ { "cc", opt_stringptr,
+ (void *)offsetof(autoreply_transport_options_block, cc) },
+ { "file", opt_stringptr,
+ (void *)offsetof(autoreply_transport_options_block, file) },
+ { "file_expand", opt_bool,
+ (void *)offsetof(autoreply_transport_options_block, file_expand) },
+ { "file_optional", opt_bool,
+ (void *)offsetof(autoreply_transport_options_block, file_optional) },
+ { "from", opt_stringptr,
+ (void *)offsetof(autoreply_transport_options_block, from) },
+ { "headers", opt_stringptr,
+ (void *)offsetof(autoreply_transport_options_block, headers) },
+ { "log", opt_stringptr,
+ (void *)offsetof(autoreply_transport_options_block, logfile) },
+ { "mode", opt_octint,
+ (void *)offsetof(autoreply_transport_options_block, mode) },
+ { "never_mail", opt_stringptr,
+ (void *)offsetof(autoreply_transport_options_block, never_mail) },
+ { "once", opt_stringptr,
+ (void *)offsetof(autoreply_transport_options_block, oncelog) },
+ { "once_file_size", opt_int,
+ (void *)offsetof(autoreply_transport_options_block, once_file_size) },
+ { "once_repeat", opt_stringptr,
+ (void *)offsetof(autoreply_transport_options_block, once_repeat) },
+ { "reply_to", opt_stringptr,
+ (void *)offsetof(autoreply_transport_options_block, reply_to) },
+ { "return_message", opt_bool,
+ (void *)offsetof(autoreply_transport_options_block, return_message) },
+ { "subject", opt_stringptr,
+ (void *)offsetof(autoreply_transport_options_block, subject) },
+ { "text", opt_stringptr,
+ (void *)offsetof(autoreply_transport_options_block, text) },
+ { "to", opt_stringptr,
+ (void *)offsetof(autoreply_transport_options_block, 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. */
+
+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 */
+};
+
+
+
+/* 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 *t;
+uschar *ss = expand_string(s);
+
+if (ss == NULL)
+ {
+ 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 (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:
+ listptr points to the list of addresses
+ never_mail an address list, already expanded
+
+Returns: nothing
+*/
+
+static void
+check_never_mail(uschar **listptr, const uschar *never_mail)
+{
+uschar *s = *listptr;
+
+while (*s != 0)
+ {
+ 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 == NULL) 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);
+ if (terminator == ',') e++;
+ memmove(s, e, Ustrlen(e) + 1);
+ }
+ else /* Skip over this address */
+ {
+ s = e;
+ if (terminator == ',') s++;
+ }
+ }
+
+/* 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 = 0;
+
+/* Check to see if there any addresses left; if not, set NULL */
+
+s = *listptr;
+while (s != 0 && isspace(*s)) s++;
+if (*s == 0) *listptr = 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 != NULL)
+ {
+ 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)
+ {
+ once_repeat_sec = readconf_readtime(oncerepeat, 0, FALSE);
+ if (once_repeat_sec < 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) check_never_mail(&to, never_mail);
+ if (cc) check_never_mail(&cc, never_mail);
+ if (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 != 0 && to)
+ {
+ time_t then = 0;
+
+ /* Handle fixed-size cache file. */
+
+ if (ob->once_file_size > 0)
+ {
+ uschar * p, * 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->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);
+
+ 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 (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 = string_copy(oncelog);
+ uschar * s;
+
+ if ((s = Ustrrchr(dirname, '/'))) *s = '\0';
+ EXIM_DBOPEN(oncelog, dirname, O_RDWR|O_CREAT, ob->mode, &dbm_file);
+ if (!dbm_file)
+ {
+ addr->transport_return = DEFER;
+ 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(key_datum) = CS to;
+ EXIM_DATUM_SIZE(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(result_datum) == sizeof(time_t))
+ memcpy(&then, EXIM_DATUM_DATA(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;
+ 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)
+ {
+ ff = Ufopen(file, "rb");
+ if (!ff && !ob->file_optional)
+ {
+ addr->transport_return = DEFER;
+ 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 */
+
+pid = child_open_exim(&fd);
+
+/* Creation of child failed; defer this delivery. */
+
+if (pid < 0)
+ {
+ addr->transport_return = DEFER;
+ 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);
+ }
+
+/* Generate a References header if there is at least one of Message-ID:,
+References:, or In-Reply-To: (see RFC 2822). */
+
+for (h = header_list; h; h = h->next)
+ if (h->type != htype_old && strncmpic(US"References:", h->text, 11) == 0)
+ break;
+
+if (!h)
+ for (h = header_list; h; h = h->next)
+ if (h->type != htype_old && strncmpic(US"In-Reply-To:", h->text, 12) == 0)
+ break;
+
+/* We limit the total length of references. Although there is no fixed
+limit, some systems do not like headers growing beyond recognition.
+Keep the first message ID for the thread root and the last few for
+the position inside the thread, up to a maximum of 12 altogether. */
+
+if (h || message_id)
+ {
+ fprintf(fp, "References:");
+ if (h)
+ {
+ uschar *s, *id, *error;
+ uschar *referenced_ids[12];
+ int reference_count = 0;
+ int i;
+
+ s = Ustrchr(h->text, ':') + 1;
+ f.parse_allow_group = FALSE;
+ while (*s != 0 && (s = parse_message_id(s, &id, &error)) != NULL)
+ {
+ if (reference_count == nelem(referenced_ids))
+ {
+ memmove(referenced_ids + 1, referenced_ids + 2,
+ sizeof(referenced_ids) - 2*sizeof(uschar *));
+ referenced_ids[reference_count - 1] = id;
+ }
+ else referenced_ids[reference_count++] = id;
+ }
+ for (i = 0; i < reference_count; ++i) fprintf(fp, " %s", referenced_ids[i]);
+ }
+
+ /* The message id will have a newline on the end of it. */
+
+ if (message_id) fprintf(fp, " %s", message_id);
+ else fprintf(fp, "\n");
+ }
+
+/* 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(key_datum) = CS to;
+ EXIM_DATUM_SIZE(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);
+ }
+
+/* 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)
+ {
+ uschar *ptr = log_buffer;
+ DEBUG(D_transport) debug_printf("logging message details\n");
+ sprintf(CS ptr, "%s\n", tod_stamp(tod_log));
+ while(*ptr) ptr++;
+ if (from)
+ {
+ (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
+ " From: %s\n", from);
+ while(*ptr) ptr++;
+ }
+ if (to)
+ {
+ (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
+ " To: %s\n", to);
+ while(*ptr) ptr++;
+ }
+ if (cc)
+ {
+ (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
+ " Cc: %s\n", cc);
+ while(*ptr) ptr++;
+ }
+ if (bcc)
+ {
+ (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
+ " Bcc: %s\n", bcc);
+ while(*ptr) ptr++;
+ }
+ if (subject)
+ {
+ (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
+ " Subject: %s\n", subject);
+ while(*ptr) ptr++;
+ }
+ if (headers)
+ {
+ (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
+ " %s\n", headers);
+ 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);
+ }
+ 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 */