1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2020 - 2022 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
12 #include "autoreply.h"
16 /* Options specific to the autoreply transport. They must be in alphabetic
17 order (note that "_" comes before the lower case letters). Those starting
18 with "*" are not settable by the user but are used by the option-reading
19 software for alternative value types. Some options are publicly visible and so
20 are stored in the driver instance block. These are flagged with opt_public. */
21 #define LOFF(field) OPT_OFF(autoreply_transport_options_block, field)
23 optionlist autoreply_transport_options[] = {
24 { "bcc", opt_stringptr, LOFF(bcc) },
25 { "cc", opt_stringptr, LOFF(cc) },
26 { "file", opt_stringptr, LOFF(file) },
27 { "file_expand", opt_bool, LOFF(file_expand) },
28 { "file_optional", opt_bool, LOFF(file_optional) },
29 { "from", opt_stringptr, LOFF(from) },
30 { "headers", opt_stringptr, LOFF(headers) },
31 { "log", opt_stringptr, LOFF(logfile) },
32 { "mode", opt_octint, LOFF(mode) },
33 { "never_mail", opt_stringptr, LOFF(never_mail) },
34 { "once", opt_stringptr, LOFF(oncelog) },
35 { "once_file_size", opt_int, LOFF(once_file_size) },
36 { "once_repeat", opt_stringptr, LOFF(once_repeat) },
37 { "reply_to", opt_stringptr, LOFF(reply_to) },
38 { "return_message", opt_bool, LOFF(return_message) },
39 { "subject", opt_stringptr, LOFF(subject) },
40 { "text", opt_stringptr, LOFF(text) },
41 { "to", opt_stringptr, LOFF(to) },
44 /* Size of the options list. An extern variable has to be used so that its
45 address can appear in the tables drtables.c. */
47 int autoreply_transport_options_count =
48 sizeof(autoreply_transport_options)/sizeof(optionlist);
54 autoreply_transport_options_block autoreply_transport_option_defaults = {0};
55 void autoreply_transport_init(transport_instance *tblock) {}
56 BOOL autoreply_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
58 #else /*!MACRO_PREDEF*/
61 /* Default private options block for the autoreply transport.
62 All non-mentioned lements zero/null/false. */
64 autoreply_transport_options_block autoreply_transport_option_defaults = {
70 /* Type of text for the checkexpand() function */
72 enum { cke_text, cke_hdr, cke_file };
76 /*************************************************
77 * Initialization entry point *
78 *************************************************/
80 /* Called for each instance, after its options have been read, to
81 enable consistency checks to be done, or anything else that needs
85 autoreply_transport_init(transport_instance *tblock)
88 autoreply_transport_options_block *ob =
89 (autoreply_transport_options_block *)(tblock->options_block);
92 /* If a fixed uid field is set, then a gid field must also be set. */
94 if (tblock->uid_set && !tblock->gid_set && tblock->expand_gid == NULL)
95 log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
96 "user set without group for the %s transport", tblock->name);
102 /*************************************************
103 * Expand string and check *
104 *************************************************/
106 /* If the expansion fails, the error is set up in the address. Expanded
107 strings must be checked to ensure they contain only printing characters
108 and white space. If not, the function fails.
112 addr address that is being worked on
113 name transport name, for error text
114 type type, for checking content:
116 cke_hdr => header, allow \n + whitespace
117 cke_file => file name, no non-printers allowed
119 Returns: expanded string if expansion succeeds;
124 checkexpand(uschar *s, address_item *addr, uschar *name, int type)
126 uschar *ss = expand_string(s);
130 addr->transport_return = FAIL;
131 addr->message = string_sprintf("Expansion of \"%s\" failed in %s transport: "
132 "%s", s, name, expand_string_message);
136 if (type != cke_text) for (uschar * t = ss; *t != 0; t++)
140 if (mac_isprint(c)) continue;
141 if (type == cke_hdr && c == '\n' && (t[1] == ' ' || t[1] == '\t')) continue;
142 sp = string_printing(s);
143 addr->transport_return = FAIL;
144 addr->message = string_sprintf("Expansion of \"%s\" in %s transport "
145 "contains non-printing character %d", sp, name, c);
155 /*************************************************
156 * Check a header line for never_mail *
157 *************************************************/
159 /* This is called to check to, cc, and bcc for addresses in the never_mail
160 list. Any that are found are removed.
163 list list of addresses to be checked
164 never_mail an address list, already expanded
166 Returns: edited replacement address list, or NULL, or original
170 check_never_mail(uschar * list, const uschar * never_mail)
172 rmark reset_point = store_mark();
173 uschar * newlist = string_copy(list);
174 uschar * s = newlist;
179 uschar *error, *next;
180 uschar *e = parse_find_address_end(s, FALSE);
182 int start, end, domain, rc;
184 /* Temporarily terminate the string at the address end while extracting
185 the operative address within. */
188 next = parse_extract_address(s, &error, &start, &end, &domain, FALSE);
191 /* If there is some kind of syntax error, just give up on this header
196 /* See if the address is on the never_mail list */
198 rc = match_address_list(next, /* address to check */
199 TRUE, /* start caseless */
200 FALSE, /* don't expand the list */
201 &never_mail, /* the list */
202 NULL, /* no caching */
203 -1, /* no expand setup */
204 0, /* separator from list */
205 NULL); /* no lookup value return */
207 if (rc == OK) /* Remove this address */
210 debug_printf("discarding recipient %s (matched never_mail)\n", next);
212 if (terminator == ',') e++;
213 memmove(s, e, Ustrlen(e) + 1);
215 else /* Skip over this address */
218 if (terminator == ',') s++;
222 /* If no addresses were removed, retrieve the memory used and return
227 store_reset(reset_point);
231 /* Check to see if we removed the last address, leaving a terminating comma
232 that needs to be removed */
234 s = newlist + Ustrlen(newlist);
235 while (s > newlist && (isspace(s[-1]) || s[-1] == ',')) s--;
238 /* Check to see if there any addresses left; if not, return NULL */
241 while (s && isspace(*s)) s++;
245 store_reset(reset_point);
251 /*************************************************
253 *************************************************/
255 /* See local README for interface details. This transport always returns
256 FALSE, indicating that the top address has the status for all - though in fact
257 this transport can handle only one address at at time anyway. */
260 autoreply_transport_entry(
261 transport_instance *tblock, /* data for this instantiation */
262 address_item *addr) /* address we are working on */
268 EXIM_DB * dbm_file = NULL;
269 BOOL file_expand, return_message;
270 uschar *from, *reply_to, *to, *cc, *bcc, *subject, *headers, *text, *file;
271 uschar *logfile, *oncelog;
272 uschar *cache_buff = NULL;
273 uschar *cache_time = NULL;
274 uschar *message_id = NULL;
276 time_t now = time(NULL);
277 time_t once_repeat_sec = 0;
281 autoreply_transport_options_block *ob =
282 (autoreply_transport_options_block *)(tblock->options_block);
284 DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name);
286 /* Set up for the good case */
288 addr->transport_return = OK;
289 addr->basic_errno = 0;
291 /* If the address is pointing to a reply block, then take all the data
292 from that block. It has typically been set up by a mail filter processing
293 router. Otherwise, the data must be supplied by this transport, and
294 it has to be expanded here. */
298 DEBUG(D_transport) debug_printf("taking data from address\n");
299 from = addr->reply->from;
300 reply_to = addr->reply->reply_to;
301 to = addr->reply->to;
302 cc = addr->reply->cc;
303 bcc = addr->reply->bcc;
304 subject = addr->reply->subject;
305 headers = addr->reply->headers;
306 text = addr->reply->text;
307 file = addr->reply->file;
308 logfile = addr->reply->logfile;
309 oncelog = addr->reply->oncelog;
310 once_repeat_sec = addr->reply->once_repeat;
311 file_expand = addr->reply->file_expand;
312 expand_forbid = addr->reply->expand_forbid;
313 return_message = addr->reply->return_message;
317 uschar *oncerepeat = ob->once_repeat;
319 DEBUG(D_transport) debug_printf("taking data from transport\n");
321 reply_to = ob->reply_to;
325 subject = ob->subject;
326 headers = ob->headers;
329 logfile = ob->logfile;
330 oncelog = ob->oncelog;
331 file_expand = ob->file_expand;
332 return_message = ob->return_message;
334 if ( from && !(from = checkexpand(from, addr, tblock->name, cke_hdr))
335 || reply_to && !(reply_to = checkexpand(reply_to, addr, tblock->name, cke_hdr))
336 || to && !(to = checkexpand(to, addr, tblock->name, cke_hdr))
337 || cc && !(cc = checkexpand(cc, addr, tblock->name, cke_hdr))
338 || bcc && !(bcc = checkexpand(bcc, addr, tblock->name, cke_hdr))
339 || subject && !(subject = checkexpand(subject, addr, tblock->name, cke_hdr))
340 || headers && !(headers = checkexpand(headers, addr, tblock->name, cke_text))
341 || text && !(text = checkexpand(text, addr, tblock->name, cke_text))
342 || file && !(file = checkexpand(file, addr, tblock->name, cke_file))
343 || logfile && !(logfile = checkexpand(logfile, addr, tblock->name, cke_file))
344 || oncelog && !(oncelog = checkexpand(oncelog, addr, tblock->name, cke_file))
345 || oncerepeat && !(oncerepeat = checkexpand(oncerepeat, addr, tblock->name, cke_file))
350 if ((once_repeat_sec = readconf_readtime(oncerepeat, 0, FALSE)) < 0)
352 addr->transport_return = FAIL;
353 addr->message = string_sprintf("Invalid time value \"%s\" for "
354 "\"once_repeat\" in %s transport", oncerepeat, tblock->name);
359 /* If the never_mail option is set, we have to scan all the recipients and
360 remove those that match. */
364 const uschar *never_mail = expand_string(ob->never_mail);
368 addr->transport_return = FAIL;
369 addr->message = string_sprintf("Failed to expand \"%s\" for "
370 "\"never_mail\" in %s transport", ob->never_mail, tblock->name);
374 if (to) to = check_never_mail(to, never_mail);
375 if (cc) cc = check_never_mail(cc, never_mail);
376 if (bcc) bcc = check_never_mail(bcc, never_mail);
378 if (!to && !cc && !bcc)
381 debug_printf("*** all recipients removed by never_mail\n");
386 /* If the -N option is set, can't do any more. */
391 debug_printf("*** delivery by %s transport bypassed by -N option\n",
397 /* If the oncelog field is set, we send want to send only one message to the
398 given recipient(s). This works only on the "To" field. If there is no "To"
399 field, the message is always sent. If the To: field contains more than one
400 recipient, the effect might not be quite as envisaged. If once_file_size is
401 set, instead of a dbm file, we use a regular file containing a circular buffer
404 if (oncelog && *oncelog && to)
408 if (is_tainted(oncelog))
410 addr->transport_return = DEFER;
411 addr->basic_errno = EACCES;
412 addr->message = string_sprintf("Tainted '%s' (once file for %s transport)"
413 " not permitted", oncelog, tblock->name);
417 /* Handle fixed-size cache file. */
419 if (ob->once_file_size > 0)
424 cache_fd = Uopen(oncelog, O_CREAT|O_RDWR, ob->mode);
425 if (cache_fd < 0 || fstat(cache_fd, &statbuf) != 0)
427 addr->transport_return = DEFER;
428 addr->basic_errno = errno;
429 addr->message = string_sprintf("Failed to %s \"once\" file %s when "
430 "sending message from %s transport: %s",
431 cache_fd < 0 ? "open" : "stat", oncelog, tblock->name, strerror(errno));
435 /* Get store in the temporary pool and read the entire file into it. We get
436 an amount of store that is big enough to add the new entry on the end if we
439 cache_size = statbuf.st_size;
440 add_size = sizeof(time_t) + Ustrlen(to) + 1;
441 cache_buff = store_get(cache_size + add_size, oncelog);
443 if (read(cache_fd, cache_buff, cache_size) != cache_size)
445 addr->transport_return = DEFER;
446 addr->basic_errno = errno;
447 addr->message = US"error while reading \"once\" file";
451 DEBUG(D_transport) debug_printf("%d bytes read from %s\n", cache_size, oncelog);
453 /* Scan the data for this recipient. Each entry in the file starts with
454 a time_t sized time value, followed by the address, followed by a binary
455 zero. If we find a match, put the time into "then", and the place where it
456 was found into "cache_time". Otherwise, "then" is left at zero. */
458 for (uschar * p = cache_buff; p < cache_buff + cache_size; p = nextp)
460 uschar *s = p + sizeof(time_t);
461 nextp = s + Ustrlen(s) + 1;
462 if (Ustrcmp(to, s) == 0)
464 memcpy(&then, p, sizeof(time_t));
471 /* Use a DBM file for the list of previous recipients. */
475 EXIM_DATUM key_datum, result_datum;
476 uschar * dirname, * s;
478 dirname = (s = Ustrrchr(oncelog, '/'))
479 ? string_copyn(oncelog, s - oncelog) : NULL;
480 if (!(dbm_file = exim_dbopen(oncelog, dirname, O_RDWR|O_CREAT, ob->mode)))
482 addr->transport_return = DEFER;
483 addr->basic_errno = errno;
484 addr->message = string_sprintf("Failed to open %s file %s when sending "
485 "message from %s transport: %s", EXIM_DBTYPE, oncelog, tblock->name,
490 exim_datum_init(&key_datum); /* Some DBM libraries need datums */
491 exim_datum_init(&result_datum); /* to be cleared */
492 exim_datum_data_set(&key_datum, (void *) to);
493 exim_datum_size_set(&key_datum, Ustrlen(to) + 1);
495 if (exim_dbget(dbm_file, &key_datum, &result_datum))
497 /* If the datum size is that of a binary time, we are in the new world
498 where messages are sent periodically. Otherwise the file is an old one,
499 where the datum was filled with a tod_log time, which is assumed to be
500 different in size. For that, only one message is ever sent. This change
501 introduced at Exim 3.00. In a couple of years' time the test on the size
504 if (exim_datum_size_get(&result_datum) == sizeof(time_t))
505 memcpy(&then, exim_datum_data_get(&result_datum), sizeof(time_t));
511 /* Either "then" is set zero, if no message has yet been sent, or it
512 is set to the time of the last sending. */
514 if (then != 0 && (once_repeat_sec <= 0 || now - then < once_repeat_sec))
517 if (is_tainted(logfile))
519 addr->transport_return = DEFER;
520 addr->basic_errno = EACCES;
521 addr->message = string_sprintf("Tainted '%s' (logfile for %s transport)"
522 " not permitted", logfile, tblock->name);
526 DEBUG(D_transport) debug_printf("message previously sent to %s%s\n", to,
527 (once_repeat_sec > 0)? " and repeat time not reached" : "");
528 log_fd = logfile ? Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode) : -1;
531 uschar *ptr = log_buffer;
532 sprintf(CS ptr, "%s\n previously sent to %.200s\n", tod_stamp(tod_log), to);
534 if(write(log_fd, log_buffer, ptr - log_buffer) != ptr-log_buffer
536 DEBUG(D_transport) debug_printf("Problem writing log file %s for %s "
537 "transport\n", logfile, tblock->name);
542 DEBUG(D_transport) debug_printf("%s %s\n", (then <= 0)?
543 "no previous message sent to" : "repeat time reached for", to);
546 /* We are going to send a message. Ensure any requested file is available. */
549 if (is_tainted(file))
551 addr->transport_return = DEFER;
552 addr->basic_errno = EACCES;
553 addr->message = string_sprintf("Tainted '%s' (file for %s transport)"
554 " not permitted", file, tblock->name);
557 if (!(ff = Ufopen(file, "rb")) && !ob->file_optional)
559 addr->transport_return = DEFER;
560 addr->basic_errno = errno;
561 addr->message = string_sprintf("Failed to open file %s when sending "
562 "message from %s transport: %s", file, tblock->name, strerror(errno));
567 /* Make a subprocess to send the message */
569 if ((pid = child_open_exim(&fd, US"autoreply")) < 0)
571 /* Creation of child failed; defer this delivery. */
573 addr->transport_return = DEFER;
574 addr->basic_errno = errno;
575 addr->message = string_sprintf("Failed to create child process to send "
576 "message from %s transport: %s", tblock->name, strerror(errno));
577 DEBUG(D_transport) debug_printf("%s\n", addr->message);
578 if (dbm_file) exim_dbclose(dbm_file);
582 /* Create the message to be sent - recipients are taken from the headers,
583 as the -t option is used. The "headers" stuff *must* be last in case there
584 are newlines in it which might, if placed earlier, screw up other headers. */
586 fp = fdopen(fd, "wb");
588 if (from) fprintf(fp, "From: %s\n", from);
589 if (reply_to) fprintf(fp, "Reply-To: %s\n", reply_to);
590 if (to) fprintf(fp, "To: %s\n", to);
591 if (cc) fprintf(fp, "Cc: %s\n", cc);
592 if (bcc) fprintf(fp, "Bcc: %s\n", bcc);
593 if (subject) fprintf(fp, "Subject: %s\n", subject);
595 /* Generate In-Reply-To from the message_id header; there should
596 always be one, but code defensively. */
598 for (h = header_list; h; h = h->next)
599 if (h->type == htype_id) break;
603 message_id = Ustrchr(h->text, ':') + 1;
604 while (isspace(*message_id)) message_id++;
605 fprintf(fp, "In-Reply-To: %s", message_id);
608 moan_write_references(fp, message_id);
610 /* Add an Auto-Submitted: header */
612 fprintf(fp, "Auto-Submitted: auto-replied\n");
614 /* Add any specially requested headers */
616 if (headers) fprintf(fp, "%s\n", headers);
621 fprintf(fp, "%s", CS text);
622 if (text[Ustrlen(text)-1] != '\n') fprintf(fp, "\n");
627 while (Ufgets(big_buffer, big_buffer_size, ff) != NULL)
631 uschar *s = expand_string(big_buffer);
635 debug_printf("error while expanding line from file:\n %s\n %s\n",
636 big_buffer, expand_string_message);
638 fprintf(fp, "%s", s ? CS s : CS big_buffer);
640 else fprintf(fp, "%s", CS big_buffer);
645 /* Copy the original message if required, observing the return size
646 limit if we are returning the body. */
650 uschar *rubric = tblock->headers_only
651 ? US"------ This is a copy of the message's header lines.\n"
653 ? US"------ This is a copy of the body of the message, without the headers.\n"
654 : US"------ This is a copy of the message, including all the headers.\n";
655 transport_ctx tctx = {
656 .u = {.fd = fileno(fp)},
659 .check_string = NULL,
660 .escape_string = NULL,
661 .options = (tblock->body_only ? topt_no_headers : 0)
662 | (tblock->headers_only ? topt_no_body : 0)
663 | (tblock->return_path_add ? topt_add_return_path : 0)
664 | (tblock->delivery_date_add ? topt_add_delivery_date : 0)
665 | (tblock->envelope_to_add ? topt_add_envelope_to : 0)
669 if (bounce_return_size_limit > 0 && !tblock->headers_only)
672 int max = (bounce_return_size_limit/DELIVER_IN_BUFFER_SIZE + 1) *
673 DELIVER_IN_BUFFER_SIZE;
674 if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max)
677 "------ The body of the message is " OFF_T_FMT " characters long; only the first\n"
678 "------ %d or so are included here.\n\n", rubric, statbuf.st_size,
681 else fprintf(fp, "\n%s\n", rubric);
683 else fprintf(fp, "\n%s\n", rubric);
687 transport_write_message(&tctx, bounce_return_size_limit);
690 /* End the message and wait for the child process to end; no timeout. */
693 rc = child_close(pid, 0);
695 /* Update the "sent to" log whatever the yield. This errs on the side of
696 missing out a message rather than risking sending more than one. We either have
697 cache_fd set to a fixed size, circular buffer file, or dbm_file set to an open
698 DBM file (or neither, if "once" is not set). */
700 /* Update fixed-size cache file. If cache_time is set, we found a previous
701 entry; that is the spot into which to put the current time. Otherwise we have
702 to add a new record; remove the first one in the file if the file is too big.
703 We always rewrite the entire file in a single write operation. This is
704 (hopefully) going to be the safest thing because there is no interlocking
705 between multiple simultaneous deliveries. */
709 uschar *from = cache_buff;
710 int size = cache_size;
712 if (lseek(cache_fd, 0, SEEK_SET) == 0)
716 cache_time = from + size;
717 memcpy(cache_time + sizeof(time_t), to, add_size - sizeof(time_t));
720 if (cache_size > 0 && size > ob->once_file_size)
722 from += sizeof(time_t) + Ustrlen(from + sizeof(time_t)) + 1;
723 size -= (from - cache_buff);
727 memcpy(cache_time, &now, sizeof(time_t));
728 if(write(cache_fd, from, size) != size)
729 DEBUG(D_transport) debug_printf("Problem writing cache file %s for %s "
730 "transport\n", oncelog, tblock->name);
734 /* Update DBM file */
738 EXIM_DATUM key_datum, value_datum;
739 exim_datum_init(&key_datum); /* Some DBM libraries need to have */
740 exim_datum_init(&value_datum); /* cleared datums. */
741 exim_datum_data_set(&key_datum, to);
742 exim_datum_size_set(&key_datum, Ustrlen(to) + 1);
744 /* Many OS define the datum value, sensibly, as a void *. However, there
745 are some which still have char *. By casting this address to a char * we
746 can avoid warning messages from the char * systems. */
748 exim_datum_data_set(&value_datum, &now);
749 exim_datum_size_set(&value_datum, sizeof(time_t));
750 exim_dbput(dbm_file, &key_datum, &value_datum);
753 /* If sending failed, defer to try again - but if once is set the next
754 try will skip, of course. However, if there were no recipients in the
755 message, we do not fail. */
758 if (rc == EXIT_NORECIPIENTS)
760 DEBUG(D_any) debug_printf("%s transport: message contained no recipients\n",
765 addr->transport_return = DEFER;
766 addr->message = string_sprintf("Failed to send message from %s "
767 "transport (%d)", tblock->name, rc);
771 /* Log the sending of the message if successful and required. If the file
772 fails to open, it's hard to know what to do. We cannot write to the Exim
773 log from here, since we may be running under an unprivileged uid. We don't
774 want to fail the delivery, since the message has been successfully sent. For
775 the moment, ignore open failures. Write the log entry as a single write() to a
776 file opened for appending, in order to avoid interleaving of output from
777 different processes. The log_buffer can be used exactly as for main log
782 int log_fd = Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode);
785 gstring gs = { .size = LOG_BUFFER_SIZE, .ptr = 0, .s = log_buffer }, *g = &gs;
787 /* Use taint-unchecked routines for writing into log_buffer, trusting
788 that we'll never expand it. */
790 DEBUG(D_transport) debug_printf("logging message details\n");
791 g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "%s\n", tod_stamp(tod_log));
793 g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " From: %s\n", from);
795 g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " To: %s\n", to);
797 g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " Cc: %s\n", cc);
799 g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " Bcc: %s\n", bcc);
801 g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " Subject: %s\n", subject);
803 g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " %s\n", headers);
804 if(write(log_fd, g->s, g->ptr) != g->ptr || close(log_fd))
805 DEBUG(D_transport) debug_printf("Problem writing log file %s for %s "
806 "transport\n", logfile, tblock->name);
808 else DEBUG(D_transport) debug_printf("Failed to open log file %s for %s "
809 "transport: %s\n", logfile, tblock->name, strerror(errno));
813 if (dbm_file) exim_dbclose(dbm_file);
814 if (cache_fd > 0) (void)close(cache_fd);
816 DEBUG(D_transport) debug_printf("%s transport succeeded\n", tblock->name);
821 #endif /*!MACRO_PREDEF*/
822 /* End of transport/autoreply.c */