1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* See the file NOTICE for conditions of use and distribution. */
10 #include "autoreply.h"
14 /* Options specific to the autoreply transport. They must be in alphabetic
15 order (note that "_" comes before the lower case letters). Those starting
16 with "*" are not settable by the user but are used by the option-reading
17 software for alternative value types. Some options are publicly visible and so
18 are stored in the driver instance block. These are flagged with opt_public. */
19 #define LOFF(field) OPT_OFF(autoreply_transport_options_block, field)
21 optionlist autoreply_transport_options[] = {
22 { "bcc", opt_stringptr, LOFF(bcc) },
23 { "cc", opt_stringptr, LOFF(cc) },
24 { "file", opt_stringptr, LOFF(file) },
25 { "file_expand", opt_bool, LOFF(file_expand) },
26 { "file_optional", opt_bool, LOFF(file_optional) },
27 { "from", opt_stringptr, LOFF(from) },
28 { "headers", opt_stringptr, LOFF(headers) },
29 { "log", opt_stringptr, LOFF(logfile) },
30 { "mode", opt_octint, LOFF(mode) },
31 { "never_mail", opt_stringptr, LOFF(never_mail) },
32 { "once", opt_stringptr, LOFF(oncelog) },
33 { "once_file_size", opt_int, LOFF(once_file_size) },
34 { "once_repeat", opt_stringptr, LOFF(once_repeat) },
35 { "reply_to", opt_stringptr, LOFF(reply_to) },
36 { "return_message", opt_bool, LOFF(return_message) },
37 { "subject", opt_stringptr, LOFF(subject) },
38 { "text", opt_stringptr, LOFF(text) },
39 { "to", opt_stringptr, LOFF(to) },
42 /* Size of the options list. An extern variable has to be used so that its
43 address can appear in the tables drtables.c. */
45 int autoreply_transport_options_count =
46 sizeof(autoreply_transport_options)/sizeof(optionlist);
52 autoreply_transport_options_block autoreply_transport_option_defaults = {0};
53 void autoreply_transport_init(transport_instance *tblock) {}
54 BOOL autoreply_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
56 #else /*!MACRO_PREDEF*/
59 /* Default private options block for the autoreply transport. */
61 autoreply_transport_options_block autoreply_transport_option_defaults = {
73 NULL, /* once_repeat */
74 NULL, /* never_mail */
76 0, /* once_file_size */
77 FALSE, /* file_expand */
78 FALSE, /* file_optional */
79 FALSE /* return message */
84 /* Type of text for the checkexpand() function */
86 enum { cke_text, cke_hdr, cke_file };
90 /*************************************************
91 * Initialization entry point *
92 *************************************************/
94 /* Called for each instance, after its options have been read, to
95 enable consistency checks to be done, or anything else that needs
99 autoreply_transport_init(transport_instance *tblock)
102 autoreply_transport_options_block *ob =
103 (autoreply_transport_options_block *)(tblock->options_block);
106 /* If a fixed uid field is set, then a gid field must also be set. */
108 if (tblock->uid_set && !tblock->gid_set && tblock->expand_gid == NULL)
109 log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
110 "user set without group for the %s transport", tblock->name);
116 /*************************************************
117 * Expand string and check *
118 *************************************************/
120 /* If the expansion fails, the error is set up in the address. Expanded
121 strings must be checked to ensure they contain only printing characters
122 and white space. If not, the function fails.
126 addr address that is being worked on
127 name transport name, for error text
128 type type, for checking content:
130 cke_hdr => header, allow \n + whitespace
131 cke_file => file name, no non-printers allowed
133 Returns: expanded string if expansion succeeds;
138 checkexpand(uschar *s, address_item *addr, uschar *name, int type)
140 uschar *ss = expand_string(s);
144 addr->transport_return = FAIL;
145 addr->message = string_sprintf("Expansion of \"%s\" failed in %s transport: "
146 "%s", s, name, expand_string_message);
150 if (type != cke_text) for (uschar * t = ss; *t != 0; t++)
154 if (mac_isprint(c)) continue;
155 if (type == cke_hdr && c == '\n' && (t[1] == ' ' || t[1] == '\t')) continue;
156 sp = string_printing(s);
157 addr->transport_return = FAIL;
158 addr->message = string_sprintf("Expansion of \"%s\" in %s transport "
159 "contains non-printing character %d", sp, name, c);
169 /*************************************************
170 * Check a header line for never_mail *
171 *************************************************/
173 /* This is called to check to, cc, and bcc for addresses in the never_mail
174 list. Any that are found are removed.
177 listptr points to the list of addresses
178 never_mail an address list, already expanded
184 check_never_mail(uschar **listptr, const uschar *never_mail)
186 uschar *s = *listptr;
190 uschar *error, *next;
191 uschar *e = parse_find_address_end(s, FALSE);
193 int start, end, domain, rc;
195 /* Temporarily terminate the string at the address end while extracting
196 the operative address within. */
199 next = parse_extract_address(s, &error, &start, &end, &domain, FALSE);
202 /* If there is some kind of syntax error, just give up on this header
207 /* See if the address is on the never_mail list */
209 rc = match_address_list(next, /* address to check */
210 TRUE, /* start caseless */
211 FALSE, /* don't expand the list */
212 &never_mail, /* the list */
213 NULL, /* no caching */
214 -1, /* no expand setup */
215 0, /* separator from list */
216 NULL); /* no lookup value return */
218 if (rc == OK) /* Remove this address */
221 debug_printf("discarding recipient %s (matched never_mail)\n", next);
222 if (terminator == ',') e++;
223 memmove(s, e, Ustrlen(e) + 1);
225 else /* Skip over this address */
228 if (terminator == ',') s++;
232 /* Check to see if we removed the last address, leaving a terminating comma
233 that needs to be removed */
235 s = *listptr + Ustrlen(*listptr);
236 while (s > *listptr && (isspace(s[-1]) || s[-1] == ',')) s--;
239 /* Check to see if there any addresses left; if not, set NULL */
242 while (s != 0 && isspace(*s)) s++;
243 if (*s == 0) *listptr = NULL;
248 /*************************************************
250 *************************************************/
252 /* See local README for interface details. This transport always returns
253 FALSE, indicating that the top address has the status for all - though in fact
254 this transport can handle only one address at at time anyway. */
257 autoreply_transport_entry(
258 transport_instance *tblock, /* data for this instantiation */
259 address_item *addr) /* address we are working on */
265 EXIM_DB *dbm_file = NULL;
266 BOOL file_expand, return_message;
267 uschar *from, *reply_to, *to, *cc, *bcc, *subject, *headers, *text, *file;
268 uschar *logfile, *oncelog;
269 uschar *cache_buff = NULL;
270 uschar *cache_time = NULL;
271 uschar *message_id = NULL;
273 time_t now = time(NULL);
274 time_t once_repeat_sec = 0;
278 autoreply_transport_options_block *ob =
279 (autoreply_transport_options_block *)(tblock->options_block);
281 DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name);
283 /* Set up for the good case */
285 addr->transport_return = OK;
286 addr->basic_errno = 0;
288 /* If the address is pointing to a reply block, then take all the data
289 from that block. It has typically been set up by a mail filter processing
290 router. Otherwise, the data must be supplied by this transport, and
291 it has to be expanded here. */
295 DEBUG(D_transport) debug_printf("taking data from address\n");
296 from = addr->reply->from;
297 reply_to = addr->reply->reply_to;
298 to = addr->reply->to;
299 cc = addr->reply->cc;
300 bcc = addr->reply->bcc;
301 subject = addr->reply->subject;
302 headers = addr->reply->headers;
303 text = addr->reply->text;
304 file = addr->reply->file;
305 logfile = addr->reply->logfile;
306 oncelog = addr->reply->oncelog;
307 once_repeat_sec = addr->reply->once_repeat;
308 file_expand = addr->reply->file_expand;
309 expand_forbid = addr->reply->expand_forbid;
310 return_message = addr->reply->return_message;
314 uschar *oncerepeat = ob->once_repeat;
316 DEBUG(D_transport) debug_printf("taking data from transport\n");
318 reply_to = ob->reply_to;
322 subject = ob->subject;
323 headers = ob->headers;
326 logfile = ob->logfile;
327 oncelog = ob->oncelog;
328 file_expand = ob->file_expand;
329 return_message = ob->return_message;
331 if ( from && !(from = checkexpand(from, addr, tblock->name, cke_hdr))
332 || reply_to && !(reply_to = checkexpand(reply_to, addr, tblock->name, cke_hdr))
333 || to && !(to = checkexpand(to, addr, tblock->name, cke_hdr))
334 || cc && !(cc = checkexpand(cc, addr, tblock->name, cke_hdr))
335 || bcc && !(bcc = checkexpand(bcc, addr, tblock->name, cke_hdr))
336 || subject && !(subject = checkexpand(subject, addr, tblock->name, cke_hdr))
337 || headers && !(headers = checkexpand(headers, addr, tblock->name, cke_text))
338 || text && !(text = checkexpand(text, addr, tblock->name, cke_text))
339 || file && !(file = checkexpand(file, addr, tblock->name, cke_file))
340 || logfile && !(logfile = checkexpand(logfile, addr, tblock->name, cke_file))
341 || oncelog && !(oncelog = checkexpand(oncelog, addr, tblock->name, cke_file))
342 || oncerepeat && !(oncerepeat = checkexpand(oncerepeat, addr, tblock->name, cke_file))
348 once_repeat_sec = readconf_readtime(oncerepeat, 0, FALSE);
349 if (once_repeat_sec < 0)
351 addr->transport_return = FAIL;
352 addr->message = string_sprintf("Invalid time value \"%s\" for "
353 "\"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) check_never_mail(&to, never_mail);
375 if (cc) check_never_mail(&cc, never_mail);
376 if (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, is_tainted(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 = string_copy(oncelog);
479 if ((s = Ustrrchr(dirname, '/'))) *s = '\0';
480 EXIM_DBOPEN(oncelog, dirname, O_RDWR|O_CREAT, ob->mode, &dbm_file);
483 addr->transport_return = DEFER;
484 addr->basic_errno = errno;
485 addr->message = string_sprintf("Failed to open %s file %s when sending "
486 "message from %s transport: %s", EXIM_DBTYPE, oncelog, tblock->name,
491 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries need datums */
492 EXIM_DATUM_INIT(result_datum); /* to be cleared */
493 EXIM_DATUM_DATA(key_datum) = CS to;
494 EXIM_DATUM_SIZE(key_datum) = Ustrlen(to) + 1;
496 if (EXIM_DBGET(dbm_file, key_datum, result_datum))
498 /* If the datum size is that of a binary time, we are in the new world
499 where messages are sent periodically. Otherwise the file is an old one,
500 where the datum was filled with a tod_log time, which is assumed to be
501 different in size. For that, only one message is ever sent. This change
502 introduced at Exim 3.00. In a couple of years' time the test on the size
505 if (EXIM_DATUM_SIZE(result_datum) == sizeof(time_t))
506 memcpy(&then, EXIM_DATUM_DATA(result_datum), sizeof(time_t));
512 /* Either "then" is set zero, if no message has yet been sent, or it
513 is set to the time of the last sending. */
515 if (then != 0 && (once_repeat_sec <= 0 || now - then < once_repeat_sec))
518 if (is_tainted(logfile))
520 addr->transport_return = DEFER;
521 addr->basic_errno = EACCES;
522 addr->message = string_sprintf("Tainted '%s' (logfile for %s transport)"
523 " not permitted", logfile, tblock->name);
527 DEBUG(D_transport) debug_printf("message previously sent to %s%s\n", to,
528 (once_repeat_sec > 0)? " and repeat time not reached" : "");
529 log_fd = logfile ? Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode) : -1;
532 uschar *ptr = log_buffer;
533 sprintf(CS ptr, "%s\n previously sent to %.200s\n", tod_stamp(tod_log), to);
535 if(write(log_fd, log_buffer, ptr - log_buffer) != ptr-log_buffer
537 DEBUG(D_transport) debug_printf("Problem writing log file %s for %s "
538 "transport\n", logfile, tblock->name);
543 DEBUG(D_transport) debug_printf("%s %s\n", (then <= 0)?
544 "no previous message sent to" : "repeat time reached for", to);
547 /* We are going to send a message. Ensure any requested file is available. */
550 if (is_tainted(file))
552 addr->transport_return = DEFER;
553 addr->basic_errno = EACCES;
554 addr->message = string_sprintf("Tainted '%s' (file for %s transport)"
555 " not permitted", file, tblock->name);
558 if (!(ff = Ufopen(file, "rb")) && !ob->file_optional)
560 addr->transport_return = DEFER;
561 addr->basic_errno = errno;
562 addr->message = string_sprintf("Failed to open file %s when sending "
563 "message from %s transport: %s", file, tblock->name, strerror(errno));
568 /* Make a subprocess to send the message */
570 if ((pid = child_open_exim(&fd, US"autoreply")) < 0)
572 /* Creation of child failed; defer this delivery. */
574 addr->transport_return = DEFER;
575 addr->basic_errno = errno;
576 addr->message = string_sprintf("Failed to create child process to send "
577 "message from %s transport: %s", tblock->name, strerror(errno));
578 DEBUG(D_transport) debug_printf("%s\n", addr->message);
579 if (dbm_file) EXIM_DBCLOSE(dbm_file);
583 /* Create the message to be sent - recipients are taken from the headers,
584 as the -t option is used. The "headers" stuff *must* be last in case there
585 are newlines in it which might, if placed earlier, screw up other headers. */
587 fp = fdopen(fd, "wb");
589 if (from) fprintf(fp, "From: %s\n", from);
590 if (reply_to) fprintf(fp, "Reply-To: %s\n", reply_to);
591 if (to) fprintf(fp, "To: %s\n", to);
592 if (cc) fprintf(fp, "Cc: %s\n", cc);
593 if (bcc) fprintf(fp, "Bcc: %s\n", bcc);
594 if (subject) fprintf(fp, "Subject: %s\n", subject);
596 /* Generate In-Reply-To from the message_id header; there should
597 always be one, but code defensively. */
599 for (h = header_list; h; h = h->next)
600 if (h->type == htype_id) break;
604 message_id = Ustrchr(h->text, ':') + 1;
605 while (isspace(*message_id)) message_id++;
606 fprintf(fp, "In-Reply-To: %s", message_id);
609 moan_write_references(fp, message_id);
611 /* Add an Auto-Submitted: header */
613 fprintf(fp, "Auto-Submitted: auto-replied\n");
615 /* Add any specially requested headers */
617 if (headers) fprintf(fp, "%s\n", headers);
622 fprintf(fp, "%s", CS text);
623 if (text[Ustrlen(text)-1] != '\n') fprintf(fp, "\n");
628 while (Ufgets(big_buffer, big_buffer_size, ff) != NULL)
632 uschar *s = expand_string(big_buffer);
636 debug_printf("error while expanding line from file:\n %s\n %s\n",
637 big_buffer, expand_string_message);
639 fprintf(fp, "%s", s ? CS s : CS big_buffer);
641 else fprintf(fp, "%s", CS big_buffer);
646 /* Copy the original message if required, observing the return size
647 limit if we are returning the body. */
651 uschar *rubric = (tblock->headers_only)?
652 US"------ This is a copy of the message's header lines.\n"
653 : (tblock->body_only)?
654 US"------ This is a copy of the body of the message, without the headers.\n"
656 US"------ This is a copy of the message, including all the headers.\n";
657 transport_ctx tctx = {
658 .u = {.fd = fileno(fp)},
661 .check_string = NULL,
662 .escape_string = NULL,
663 .options = (tblock->body_only ? topt_no_headers : 0)
664 | (tblock->headers_only ? topt_no_body : 0)
665 | (tblock->return_path_add ? topt_add_return_path : 0)
666 | (tblock->delivery_date_add ? topt_add_delivery_date : 0)
667 | (tblock->envelope_to_add ? topt_add_envelope_to : 0)
671 if (bounce_return_size_limit > 0 && !tblock->headers_only)
674 int max = (bounce_return_size_limit/DELIVER_IN_BUFFER_SIZE + 1) *
675 DELIVER_IN_BUFFER_SIZE;
676 if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max)
679 "------ The body of the message is " OFF_T_FMT " characters long; only the first\n"
680 "------ %d or so are included here.\n\n", rubric, statbuf.st_size,
683 else fprintf(fp, "\n%s\n", rubric);
685 else fprintf(fp, "\n%s\n", rubric);
689 transport_write_message(&tctx, bounce_return_size_limit);
692 /* End the message and wait for the child process to end; no timeout. */
695 rc = child_close(pid, 0);
697 /* Update the "sent to" log whatever the yield. This errs on the side of
698 missing out a message rather than risking sending more than one. We either have
699 cache_fd set to a fixed size, circular buffer file, or dbm_file set to an open
700 DBM file (or neither, if "once" is not set). */
702 /* Update fixed-size cache file. If cache_time is set, we found a previous
703 entry; that is the spot into which to put the current time. Otherwise we have
704 to add a new record; remove the first one in the file if the file is too big.
705 We always rewrite the entire file in a single write operation. This is
706 (hopefully) going to be the safest thing because there is no interlocking
707 between multiple simultaneous deliveries. */
711 uschar *from = cache_buff;
712 int size = cache_size;
714 if (lseek(cache_fd, 0, SEEK_SET) == 0)
718 cache_time = from + size;
719 memcpy(cache_time + sizeof(time_t), to, add_size - sizeof(time_t));
722 if (cache_size > 0 && size > ob->once_file_size)
724 from += sizeof(time_t) + Ustrlen(from + sizeof(time_t)) + 1;
725 size -= (from - cache_buff);
729 memcpy(cache_time, &now, sizeof(time_t));
730 if(write(cache_fd, from, size) != size)
731 DEBUG(D_transport) debug_printf("Problem writing cache file %s for %s "
732 "transport\n", oncelog, tblock->name);
736 /* Update DBM file */
740 EXIM_DATUM key_datum, value_datum;
741 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries need to have */
742 EXIM_DATUM_INIT(value_datum); /* cleared datums. */
743 EXIM_DATUM_DATA(key_datum) = CS to;
744 EXIM_DATUM_SIZE(key_datum) = Ustrlen(to) + 1;
746 /* Many OS define the datum value, sensibly, as a void *. However, there
747 are some which still have char *. By casting this address to a char * we
748 can avoid warning messages from the char * systems. */
750 EXIM_DATUM_DATA(value_datum) = CS (&now);
751 EXIM_DATUM_SIZE(value_datum) = (int)sizeof(time_t);
752 EXIM_DBPUT(dbm_file, key_datum, value_datum);
755 /* If sending failed, defer to try again - but if once is set the next
756 try will skip, of course. However, if there were no recipients in the
757 message, we do not fail. */
760 if (rc == EXIT_NORECIPIENTS)
762 DEBUG(D_any) debug_printf("%s transport: message contained no recipients\n",
767 addr->transport_return = DEFER;
768 addr->message = string_sprintf("Failed to send message from %s "
769 "transport (%d)", tblock->name, rc);
773 /* Log the sending of the message if successful and required. If the file
774 fails to open, it's hard to know what to do. We cannot write to the Exim
775 log from here, since we may be running under an unprivileged uid. We don't
776 want to fail the delivery, since the message has been successfully sent. For
777 the moment, ignore open failures. Write the log entry as a single write() to a
778 file opened for appending, in order to avoid interleaving of output from
779 different processes. The log_buffer can be used exactly as for main log
784 int log_fd = Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode);
787 gstring gs = { .size = LOG_BUFFER_SIZE, .ptr = 0, .s = log_buffer }, *g = &gs;
789 /* Use taint-unchecked routines for writing into log_buffer, trusting
790 that we'll never expand it. */
792 DEBUG(D_transport) debug_printf("logging message details\n");
793 g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "%s\n", tod_stamp(tod_log));
795 g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " From: %s\n", from);
797 g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " To: %s\n", to);
799 g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " Cc: %s\n", cc);
801 g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " Bcc: %s\n", bcc);
803 g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " Subject: %s\n", subject);
805 g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " %s\n", headers);
806 if(write(log_fd, g->s, g->ptr) != g->ptr || close(log_fd))
807 DEBUG(D_transport) debug_printf("Problem writing log file %s for %s "
808 "transport\n", logfile, tblock->name);
810 else DEBUG(D_transport) debug_printf("Failed to open log file %s for %s "
811 "transport: %s\n", logfile, tblock->name, strerror(errno));
815 if (dbm_file) EXIM_DBCLOSE(dbm_file);
816 if (cache_fd > 0) (void)close(cache_fd);
818 DEBUG(D_transport) debug_printf("%s transport succeeded\n", tblock->name);
823 #endif /*!MACRO_PREDEF*/
824 /* End of transport/autoreply.c */