/*
 * #ident	"@(#)smail/src:RELEASE-3_2_0_121:notify.c,v 1.84 2005/10/26 17:17:53 woods Exp"
 */

/*
 *    Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
 *    Copyright (C) 1992  Ronald S. Karr
 * 
 * See the file COPYING, distributed with smail, for restriction
 * and warranty information.
 */

/*
 * notify.c:
 *	keep track of errors and handle notification of the appropriate
 *	users for errors.
 *
 *	external functions: defer_delivery, fail_delivery, succeed_delivery,
 *			    error_delivery, notify
 */

#include "defs.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>
#include <signal.h>

#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
#  include <stdlib.h>
# endif
#endif

#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
#  include <memory.h>
# endif
# define _GNU_SOURCE			/* to see decl. of strsignal() */
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif

#if defined(POSIX_OS) || defined(UNIX_BSD) || defined(WAIT_USE_UNION)
# include <sys/wait.h>
#endif

#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif

#include "smail.h"
#include "config.h"
#include "list.h"
#include "smailstring.h"
#include "dys.h"
#include "parse.h"
#include "addr.h"
#include "main.h"
#include "log.h"
#include "spool.h"
#include "direct.h"
#include "route.h"
#include "alloc.h"
#include "transport.h"
#include "smailwait.h"
#include "child.h"
#include "exitcodes.h"
#include "extern.h"
#include "debug.h"
#include "smailport.h"

/* exported variables */
int send_to_postmaster = FALSE;		/* set TRUE to mail to postmaster */
int return_to_sender = FALSE;		/* set TRUE to mail log to sender */

/* functions local to this file */
static void print_failed_addresses __P((FILE *, struct addr *));
static void classify_addr __P((struct addr *, struct addr **, struct addr **));
static void notify_new_message __P((FILE *, char *, char *, char *, char *, char *));
static void write_notify_header __P((FILE *, struct addr *));

/* variables local to this file */
static char *addr_error_banner = "\
|------------------------- Failed addresses follow: ---------------------|\n";
static char *log_banner = "\
|------------------------- Transaction log summary follows: -------------|\n";
static char *text_banner = "\
|------------------------- Message text follows: ------------------------|\n";


/*
 * fail_delivery - log that delivery to the set of addresses failed
 *
 * XXX record in-queue delay too? -- it'll be boringly the same as the maximum
 * retry duration for the destination
 */
void
fail_delivery(addr)
    struct addr *addr;			/* list of failed addrs */
{
    register struct addr *cur;
    register struct addr *top;
    int where = WRITE_LOG_SYS;

    if (error_processing == TERMINAL) {
	where |= WRITE_LOG_TTY;
    }
    if (addr && exitvalue == EX_OK) {
	exitvalue = EX_UNAVAILABLE;
    }
    for (cur = addr; cur; cur = cur->succ) {
	/* find the top parent to log the original in_addr */
	for (top = cur; top->parent && top->parent->in_addr; top = top->parent) {
	    ;
	}
	if (top == cur) {
	    top = NULL;
	}
	/*
	 * Note we can't use quote() more than once in a parameter list since
	 * the storage it returns a pointer to is re-used on the next call.
	 *
	 * XXX We could invent and use "%q" to add the quote marks if necessary.
	 *
	 * We could also invent and use "%a" for making addresses RFC 821 compliant
	 */
#if (SMAIL_LOG_STYLE == 2)
	/* Default new log style (as of 3.1.29) */
	/* This is SMAIL_LOG_STYLE=2 */
	write_log(where,
		  "Failed TO:%s%s%v%s%v%s%v%s%v ERROR:(ERR%ld) %v",
		  quote(cur->next_addr ? cur->next_addr : cur->in_addr, FALSE, FALSE),
		  top ? " ORIG-TO:" : "", top ? top->in_addr : "",
		  cur->router ? " ROUTER:" : "", cur->router ? cur->router->name : "",
		  cur->director ? " DIRECTOR:" : "", cur->director ? cur->director->name : "",
		  cur->transport ? " TRANSPORT:" : "", cur->transport ? cur->transport->name : "",
		  cur->error->info & ERR_MASK,
		  cur->error->message);
#else
# if (!defined(SMAIL_LOG_STYLE)) || (SMAIL_LOG_STYLE == 1)
    /* Old log style (as used by 3.1.28) */
    /* This is SMAIL_LOG_STYLE=1 */
	write_log(where,
		  "%s %s%v%s failed: (ERR%ld) %v",
		  quote(cur->next_addr ? cur->next_addr : cur->in_addr, FALSE, FALSE),
		  top ? "(orig-to: " : "",
		  top ? top->in_addr : "",
		  top ? ") " : "",
		  cur->error->info & ERR_MASK,
		  cur->error->message);
# else
#  include "ERROR - SMAIL_LOG_STYLE set to invalid value."
# endif
#endif
	/*
	 * write MLOG X entries for failed recipients so they be ignored up by
	 * queue run scans (see log.c:decode_x_line() for record format details)
	 */
	if (cur->in_addr) {
	    write_log(WRITE_LOG_MLOG,
		      "Xfail: %s%s%s%s%s%s reason: (ERR%ld) %s",
		      (*cur->in_addr == '<') ? "" : "<",
		      cur->in_addr,
		      (*cur->in_addr == '<') ? "" : ">",	/* assume the address has a proper closing angle-bracket */
		      top ? " orig-to: <" : "",
		      top ? top->in_addr : "",
		      top ? ">" : "",
		      cur->error->info & ERR_MASK,
		      cur->error->message);
	}
    }
}

/*
 * defer_delivery - log that delivery to the specified addresses has been
 * deferred
 *
 * XXX record in-queue delay too?  Why bother?
 */
void
defer_delivery(addr)
    struct addr *addr;			/* list of succeeded addrs */
{
    register struct addr *cur;
    register struct addr *top;

    if (addr && exitvalue == EX_OK) {
	exitvalue = EX_TEMPFAIL;
    }

    for (cur = addr; cur; cur = cur->succ) {

	/* find the top parent to log the original in_addr */
	for (top = cur; top->parent && top->parent->in_addr; top = top->parent) {
	    ;
	}
	if (top == cur) {
	    top = NULL;
	}
	/*
	 * if the only cause of the deferral is that the retry interval has not
	 * been reached we will be told not to log the cause...
	 */
	if (cur->error->info & ERR_DONTLOG) {
#ifndef NODEBUG
	    /* but, if debugging, generate a message for human reader */
	    if (errfile && debug > 1) {
		dprintf(errfile,
			"%v %s%v%s deferred: (ERR_DONTLOG|ERR%ld) %v\n",
			cur->next_addr ? cur->next_addr : cur->in_addr,
			top ? "(orig-to: " : "",
			top ? top->in_addr : "",
			top ? ") " : "",
			cur->error->info & ERR_MASK,
			cur->error->message);
	    }
#endif	/* ! NODEBUG */
	    continue;
	}
#if (SMAIL_LOG_STYLE == 2)
	/* Default new log style (as of 3.1.29) */
	/* This is SMAIL_LOG_STYLE=2 */
	write_log((cur->error->info & ERR_CONFERR) ? (WRITE_LOG_SYS | WRITE_LOG_TTY) : WRITE_LOG_SYS,
		  "Deferred TO:%s%s%v%s%v%s%v%s%v ERROR:(ERR%ld) %v",
		  quote(cur->next_addr ? cur->next_addr : cur->in_addr, FALSE, FALSE),
		  top ? " ORIG-TO:" : "", top ? top->in_addr : "",
		  cur->router ? " ROUTER:" : "", cur->router ? cur->router->name : "",
		  cur->director ? " DIRECTOR:" : "", cur->director ? cur->director->name : "",
		  cur->transport ? " TRANSPORT:" : "", cur->transport ? cur->transport->name : "",
		  cur->error->info & ERR_MASK,
		  cur->error->message);
#else
# if (!defined(SMAIL_LOG_STYLE)) || (SMAIL_LOG_STYLE == 1)
    /* Old log style (as used by 3.1.28) */
    /* This is SMAIL_LOG_STYLE=1 */
	write_log((cur->error->info & ERR_CONFERR) ? (WRITE_LOG_SYS | WRITE_LOG_TTY) : WRITE_LOG_SYS,
		  "%s %s%v%s deferred: (ERR%ld) %v",
		  quote(cur->next_addr ? cur->next_addr : cur->in_addr, FALSE, FALSE),
		  top ? "(orig-to: " : "",
		  top ? top->in_addr : "",
		  top ? ") " : "",
                  cur->error->info & ERR_MASK,
                  cur->error->message);
# else
#   include "ERROR - SMAIL_LOG_STYLE set to invalid value."
# endif
#endif
	/*
	 * write MLOG X entries for recipients experiencing deferred delivery
	 * so there is an easy and efficient to access record of activity all
	 * in one place.
	 */
	if (cur->in_addr) {
	    write_log(WRITE_LOG_MLOG,
		      "Xdefer: %s%s%s%s%s%s reason: (ERR%ld) %s",
		      (*cur->in_addr == '<') ? "" : "<",
		      cur->in_addr,
		      (*cur->in_addr == '<') ? "" : ">",	/* assume the address has a proper closing angle-bracket */
		      top ? " orig-to: <" : "",
		      top ? top->in_addr : "",
		      top ? ">" : "",
		      cur->error->info & ERR_MASK,
		      cur->error->message);
	}
    }
}

/*
 * succeed_delivery - log that delivery to the set of addresses succeeded
 */
void
succeed_delivery(addr)
    struct addr *addr;			/* list of succeeded addrs */
{
    register struct addr *cur;
    register struct addr *top;
    time_t now;
    unsigned long int delay;

    now = time((time_t *) NULL);
    if (now >= msg_mtime) {
	delay = now - msg_mtime;
    } else {
	DEBUG2(DBG_NOTIFY_LO, "succeed_delivery(): is the clock running backwards?  now:%lu < msg_mtime:%lu",
	       (unsigned long int) now, msg_mtime);
	delay = 1;
    }

    for (cur = addr; cur; cur = cur->succ) {
	/* find the top parent to log the original in_addr */
	for (top = cur; top->parent && top->parent->in_addr; top = top->parent) {
	    ;
	}
	if (top == cur) {
	    top = NULL;
	}
	if (dont_deliver) {
	    write_log(WRITE_LOG_SYS | WRITE_LOG_TTY, "Debugging: didn't deliver <%s>%s%v%s",
		      quote(cur->next_addr ? cur->next_addr : cur->in_addr, FALSE, FALSE),
		      top ? " orig-to: <" : "",
		      top ? top->in_addr : "",
		      top ? ">" : "");
	} else {
#if (SMAIL_LOG_STYLE == 2)
	    /* Default new log style (as of 3.1.29) */
	    /* This is SMAIL_LOG_STYLE=2 */
	    write_log(WRITE_LOG_SYS, "Delivered%s%v TO:%s%s%v%s%v TRANSPORT:%v DELAY:%s%lu",
		      cur->next_host ? " VIA:" : "",
		      cur->next_host ?  cur->next_host : "",
		      quote(cur->next_addr ? cur->next_addr : cur->in_addr, FALSE, FALSE),
		      top ? " ORIG-TO:" : "",
		      top ? top->in_addr : "",
		      cur->router ? " ROUTER:" : "",
		      cur->router ? cur->router->name : "",
		      cur->transport->name,
		      (deliver_mode == QUEUE_MESSAGE ||
		       queue_only ||
		       (msg_grade < min_delivery_grade) ||
		       (msg_grade > max_delivery_grade)) ? "Q:" : "",
		      delay);
#else
# if (!defined(SMAIL_LOG_STYLE)) || (SMAIL_LOG_STYLE == 1)
	    /* Old log style (as used by 3.1.28) */
	    /* This is SMAIL_LOG_STYLE=1 */
	    write_log(WRITE_LOG_SYS, "delivered%s%v\n|\t       to: %s%s%v%s%v\n|\ttransport: %v\n|\tdelay: %s%lu seconds",
		      cur->next_host ? "\n|\t      via: " : "",
		      cur->next_host ? cur->next_host : "",
		      quote(cur->next_addr ? cur->next_addr : cur->in_addr, FALSE, FALSE),
		      top ? "\n|\t  orig-to: " : "",
		      top ? top->in_addr : "",
		      cur->router ? "\n|\t   router: " : "",
		      cur->router ? cur->router->name : "",
		      cur->transport->name,
		      (deliver_mode == QUEUE_MESSAGE ||
		       queue_only ||
		       (msg_grade < min_delivery_grade) ||
		       (msg_grade > max_delivery_grade)) ? " (queued) " : "",
		      delay);
# else
#  include "ERROR - SMAIL_LOG_STYLE set to invalid value."
# endif
#endif
	}
	/*
	 * write MLOG X entries for all recipients that have already been
	 * delivered to so they can be ignored by queue run scans (see
	 * log.c:decode_x_line() for record format details)
	 */
	write_log(WRITE_LOG_MLOG,
		  "Xsucceed: %s%s%s%s%s%s",
		  (*cur->in_addr == '<') ? "" : "<",
		  cur->in_addr,
		  (*cur->in_addr == '<') ? "" : ">",	/* assume the address has a proper closing angle-bracket */
		  top ? " orig-to: <" : "",
		  top ? top->in_addr : "",
		  top ? ">" : "");
    }
}

/*
 * error_delivery - log that an error notification was sent for the given list
 * of recipients.
 *
 * detect mail sent to owners and log the original address.
 *
 * XXX record in-queue delay too?  It should always be very short -- just the
 * time to discover the configuration error.
 */
void
error_delivery(addr)
    struct addr *addr;		/* recipients errors have been sent for */
{
    register struct addr *cur;
    register struct addr *top;

    for (cur = addr; cur; cur = cur->succ) {
	struct addr *log_addr = NULL;
	char *nxt_or_in_addr;

	/* find the top parent to log the original in_addr */
	for (top = cur; top->parent && top->parent->in_addr; top = top->parent) {
	    ;
	}
	if (top == cur) {
	    top = NULL;
	}
	/* choose the appropriate true_addr to log an "Error sent" entry for */
	if (top && top->true_addr) {
	    log_addr = top->true_addr;
	} else if (cur->true_addr) {
	    log_addr = cur->true_addr;
	}
	nxt_or_in_addr = COPY_STRING(quote(cur->next_addr ? cur->next_addr : cur->in_addr, FALSE, FALSE));
	if (log_addr) {
#if (SMAIL_LOG_STYLE == 2)
	    write_log(WRITE_LOG_SYS, "Error sent FOR:%v TO:%s(owner)%s%s",
		      log_addr->in_addr,
		      nxt_or_in_addr,
		      log_addr->parent ? " ORIG-TO:" : "",
		      log_addr->parent ? quote(log_addr->parent->in_addr, FALSE, FALSE) : "");
#else
	    write_log(WRITE_LOG_SYS, "%v ... error sent to %s(owner)%s%s%s",
		      log_addr->in_addr,
		      nxt_or_in_addr,
		      log_addr->parent ? " (orig-to " : "",
		      log_addr->parent ? quote(log_addr->parent->in_addr, FALSE, FALSE) : "",
		      log_addr->parent ? ")" : "");
#endif
	    if (log_addr && log_addr->error && log_addr->error->info & ERR_NPOSTMAST) {
#if (SMAIL_LOG_STYLE == 2)
		write_log(WRITE_LOG_SYS, "Error CCed FOR:%s TO:postmaster",
			  quote(log_addr->in_addr, FALSE, FALSE));
#else
		write_log(WRITE_LOG_SYS, "%s ... error CCed to postmaster",
			  quote(log_addr->in_addr, FALSE, FALSE));
#endif
	    }
	} else if (cur->error) {
	    if (cur->error->info & ERR_NSENDER) {
#if (SMAIL_LOG_STYLE == 2)
		write_log(WRITE_LOG_SYS, "Returned error FOR:%s TO:%v",
			  quote(top ? top->in_addr : cur->in_addr, FALSE, FALSE), sender);
#else
		write_log(WRITE_LOG_SYS, "%s ... error returned to %v",
			  quote(top ? top->in_addr : cur->in_addr, FALSE, FALSE), sender);
#endif
	    }
	    if (cur->error->info & ERR_NPOSTMAST) {
#if (SMAIL_LOG_STYLE == 2)
		write_log(WRITE_LOG_SYS, "Sent error FOR:%s TO:postmaster",
			  quote(top ? top->in_addr : cur->in_addr, FALSE, FALSE));
#else
		write_log(WRITE_LOG_SYS, "%s ... error sent to postmaster",
			  quote(top ? top->in_addr : cur->in_addr, FALSE, FALSE));
#endif
	    }
	} else {			/* XXX this should be impossible! */
#if (SMAIL_LOG_STYLE == 2)
		write_log(WRITE_LOG_SYS, "Sent error without error info FOR:%s (parent=%s)",
			  nxt_or_in_addr,
			  top ? quote(top->in_addr, FALSE, FALSE) : "(none)");
#else
		write_log(WRITE_LOG_SYS, "%s ... error sent without error info (parent=%s)",
			  nxt_or_in_addr,
			  top ? quote(top->in_addr, FALSE, FALSE) : "(none)");
#endif
	}
	xfree(nxt_or_in_addr);
	/*
	 * write MLOG X entries for recipients that error notifications have
	 * already been sent so so they can be ignored up by queue run scans
	 * (see log.c:decode_x_line() for record format details)
	 */
	write_log(WRITE_LOG_MLOG,
		  "Xsent_error: %s%s%s%s%s%s",
		  (*cur->in_addr == '<') ? "" : "<",
		  cur->in_addr,
		  (*cur->in_addr == '<') ? "" : ">",	/* assume the address has a proper closing angle-bracket */
		  top ? " orig-to: <" : "",
		  top ? top->in_addr : "",
		  top ? ">" : "");

    }
}


/*
 * notify - notify all users who are to receive a note about errors
 *
 * also, note any configuration errors for messages in the defer list and set
 * call_freeze_message if any are found.  If any addrs exist in the defer list,
 * set some_deferred_addrs to prevent main from calling unlink_spool().
 *
 * If multiple recipients are to receive bounces separate messages are created
 * so as to simply error handling of bounce deliveries.  This is done through
 * one smail child process by sending all the messages in one group via the
 * Batched-SMTP protocol.
 */
void
notify(defer, fail)
    struct addr *defer;			/* list of deferred addresses */
    struct addr *fail;			/* list of failed addresses */
{
    register struct addr *cur;		/* current addr being processed */
    struct addr *next = NULL;		/* next addr to process */
    struct addr *to_owner = NULL;	/* addrs to send to addr owners */
    struct addr *verify = NULL;		/* list of owner addrs to verify */
    struct addr *to_other = NULL;	/* to sender and/or postmaster */
    static char *mailargs[] = {		/* setup argv for exec("smail") */
	NULL,
	"-bS",				/* use Batched-SMTP mode */
	NULL,				/* will be -v$debug if in debug mode */
	NULL,				/* will be -D$arg_debug_file if -D */
	NULL,
    };
    FILE *f;				/* stdin to child process */
    pid_t pid;				/* child process id */
    int status;				/* child process status */
    char *extra_info = NULL;		/* additional info for subject and body */

    DEBUG(DBG_NOTIFY_HI, "notify(): called\n");

    if (!defer && !fail) {
	DEBUG(DBG_NOTIFY_HI, "notify(): done -- nothing to do.\n");
	return;
    }

    /* set the exec name to the smail binary */
    mailargs[0] = smail;
    if (debug) {
	mailargs[2] = xprintf("-v%d", debug); /* XXX this wee bit of memory will leak */
    }
    if (arg_debug_file) {
	mailargs[3] = xprintf("-D%s", arg_debug_file); /* XXX this wee bit of memory will also leak */
    }

    /*
     * for deferred addresses, don't return anything, though note any
     * configuration errors and freeze the message if any were found.
     */
    for (cur = defer; cur; cur = cur->succ) {
	some_deferred_addrs = TRUE;	/* main should not call unlink_spool */
	if (!cur->error) {
	    panic(EX_SOFTWARE, "notify(): called for a deferred message without any error info (in_addr=%v)", cur->in_addr);
	}
	if (cur->error->info & ERR_CONFERR) {
	    write_log(WRITE_LOG_SYS | WRITE_LOG_PANIC, "notify: freezing a deferred message due to a local configuration error.");
	    call_freeze_message = TRUE;	/* config error, freeze the message in error/ */
	}
    }

    if (!fail) {
	DEBUG(DBG_NOTIFY_MID, "notify(): done -- handled deferred addresses only.\n");
	return;
    }

    /*
     * Put failed addresses in two lists, one list going to the sender or the
     * postmaster, the other list going to the owner of address.  The list of
     * owners will have to be verified.
     */
    for (cur = fail; cur; cur = next) {
	DEBUG1(DBG_NOTIFY_MID, "notify(): classifying %v.\n", cur->in_addr)
	next = cur->succ;
	if (!cur->error) {
	    panic(EX_SOFTWARE, "notify(): called for failed message without error info (in_addr=%v)", cur->in_addr);
	}
	classify_addr(cur, &to_other, &verify);
    }

    /*
     * Move entries from the verify list to the to_owner list if they are valid
     * owner address.  If some don't verify, retry classify_addr() until they
     * do...
     */
    while (verify) {
	struct addr *new_list = NULL;	/* unverified owner addrs */
	int oexitval = exitvalue;	/* resolve_addr_list() can clobber this... */

	DEBUG1(DBG_NOTIFY_MID, "notify(): verifying owner address(es) (first is %v).\n", verify->work_addr);
	/*
	 * treat defer and fail lists as identical, since we cannot
	 * reasonably handle defer addresses here.
	 */
	resolve_addr_list(verify, &to_owner, &new_list, &new_list, FALSE);
	exitvalue = oexitval;

	/* take any failed/deferred addrs and send through classify_addr */
	verify = NULL;
	for (cur = new_list; cur; cur = next) {
	    DEBUG1(DBG_NOTIFY_MID, "notify(): re-classifying %v.\n", cur->in_addr)
	    next = cur->succ;
	    if (!cur->error) {
		panic(EX_SOFTWARE, "notify(): attempt to re-classify address without error info (in_addr=%v)", cur->in_addr);
	    }
	    cur->flags |= ADDR_VRFY_ONLY;
	    classify_addr(cur, &to_other, &verify);
	}
    }

    /*
     * sort the list of verified addresses by their in_addr field, which will
     * be the owner address the notice is being sent to
     */
    if (to_owner) {
	to_owner = addr_sort(to_owner, OFFSET(addr, in_addr));
    }

    if (return_to_sender) {
	/*
	 * if return_to_sender is on but the error_processing flag
	 * doesn't call for a return message, then turn the flag off.
	 */
	if (error_processing != MAIL_BACK && error_processing != WRITE_BACK) {
	    extra_info = "(no error processing requested)";
	    return_to_sender = FALSE;
	}
	/* turn it off, as well, for really low priority messages */
	if (islower((int) msg_grade) && msg_grade >= 'n') {
	    extra_info = "(low priority message)";
	    return_to_sender = FALSE;
	}
    }

    /*
     * lastly, if the sender was given as the special string <> or <+>,
     * then an error message failed.  Rather than risk an infinite
     * loop here, defer the message rather than delivering a recursive
     * error message.
     *
     * error message loops can still exist if SMTP is not used or if
     * an intermediate site does not correctly handle the special
     * sender string <>.
     */
    if (error_sender) {
	if (return_to_sender || send_to_postmaster || to_owner) {
	    write_log(WRITE_LOG_SYS, "notify: freezing a double-bounce message.");
	    call_freeze_message = TRUE;
	    return;
	}
    }

    /* if nobody needs to be notified, then nothing needs to be done */
    if (! to_owner && ! to_other && ! return_to_sender) {
	DEBUG(DBG_NOTIFY_LO, "notify: done. (nobody to send a notification to)\n");
	return;
    }

    /*
     * open a channel to a new smail process, operating in bsmtp mode,
     * and send it messages for all of the recipients.
     */
    if ((pid = open_child(mailargs, (char **) NULL, &f, (FILE **) NULL, -1,
			  CHILD_MINENV|CHILD_RETRY, prog_euid, prog_egid)) < 0) {
	/* XXX should this be a panic() call? */
	write_log(WRITE_LOG_PANIC, "notify(): open_child() failed: %s", strerror(errno));
	/* if we can't notify, let humans interfere later */
	call_freeze_message = TRUE;
	return;
    }

    DEBUG1(DBG_NOTIFY_MID, "notify(): initiating BSMTP session with child PID %ld.\n", (long int) pid);

    /* start up the session */
    dprintf(f, "HELO %v\n", primary_name);

    /*
     * if any owners are to receive a message, then send to each
     * owner in turn.
     */
    for (cur = to_owner; cur; /* done-at-end */) {
	char *last_owner;
	struct addr *in = cur;

	DEBUG1(DBG_NOTIFY_LO, "notify(): sending to address owner: %v\n", cur->in_addr);
	if (send_to_postmaster) {
	    DEBUG(DBG_NOTIFY_LO, "      ... also sending to postmaster\n");
	}
	if (return_to_sender) {
	    DEBUG1(DBG_NOTIFY_LO, "     ... also returning to sender: %v\n", sender);
	}

	/* initiate a new message */
	notify_new_message(f, cur->in_addr,
			   (send_to_postmaster) ? "postmaster" : (char *) NULL,	/* cc1 */
			   (return_to_sender) ? sender : (char *) NULL,		/* cc2 */
			   (send_to_postmaster && return_to_sender) ? "sending to address owner, postmaster, and sender" :
			   	((send_to_postmaster) ? "sending to address owner and postmaster" : 
				 ((return_to_sender) ? "sending to address owner and returning to sender" :
				  "sending to address owner")),
			   extra_info);
	(void) fputs(addr_error_banner, f);
	last_owner = cur->in_addr;
	/*
	 * write out all/any of the address errors for messages with the same
	 * owner
	 */
	do {
	    print_failed_addresses(f, cur);
	    cur = cur->succ;
	} while (cur && EQIC(last_owner, cur->in_addr));
	send_log(f, log_banner);
	(void) fputs(text_banner, f);
	write_notify_header(f, in);
	putc('\n', f);
	if (islower((int) msg_grade)) {
	    fputs("[[ ... low-priority message, body not included ... ]]\n", f);
	} else {
	    write_body(f, (long) PUT_DOTS);
	}
	(void) fputs(".\n", f);		/* finish the message */
	if (cur) {			/* inner loop may have gobbled them all */
	    cur = cur->succ;
	}
    }

    /* if the postmaster is to receive mail, then send a copy */
    if (to_other && send_to_postmaster) {

	DEBUG(DBG_NOTIFY_LO, "notify(): sending to postmaster\n");
	if (return_to_sender) {
	    DEBUG1(DBG_NOTIFY_LO, "     ... also returning to sender: %v\n", sender);
	}

	/* initiate a new message */
	notify_new_message(f, "postmaster",
			   (return_to_sender) ? sender : (char *) NULL,	/* cc1 */
			   (char *) NULL,				/* cc2 */
			   (return_to_sender) ? "sending to postmaster and returning to sender" : "sending to postmaster",
			   extra_info);
	/* write messages in to_other destined to the postmaster */
	(void) fputs(addr_error_banner, f);
	for (cur = to_other; cur; cur = cur->succ) {
	    print_failed_addresses(f, cur);
	}
	send_log(f, log_banner);
	(void) fputs(text_banner, f);
	write_notify_header(f, to_other);
	putc('\n', f);
	if (islower((int) msg_grade)) {
	    fputs("[[ ... low-priority message, body not included ... ]]\n", f);
	} else {
	    write_body(f, (long) PUT_DOTS);
	}
	(void) fputs(".\n", f);		/* finish the message */
    }

    /* if the sender is to receive mail, then send a copy */
    if (return_to_sender) {

	DEBUG1(DBG_NOTIFY_LO, "notify(): returning to sender: %v\n", sender);
	if (send_to_postmaster) {
	    DEBUG(DBG_NOTIFY_LO, "      ... also sending to postmaster\n");
	}

	/* initiate a new message */
	notify_new_message(f, sender,
			   (send_to_postmaster) ? "postmaster" : (char *) NULL,	/* cc1 */
			   (char *) NULL,					/* cc2 */
			   (send_to_postmaster) ? "returning to sender and postmaster" : "returning to sender",
			   extra_info);
	(void) fputs(addr_error_banner, f);
	/*
	 * if to_other is not empty then these are the failed addresses to
	 * mention, otherwise we mention every address in the original fail
	 * list.
	 */
	for (cur = to_other ? to_other : fail; cur; cur = cur->succ) {
	    print_failed_addresses(f, cur);
	}
	send_log(f, log_banner);
	(void) fputs(text_banner, f);
	write_notify_header(f, to_other ? to_other : fail);
	putc('\n', f);
	/*
	 * a message grade between a and z causes body not to be returned.
	 * This is handy when notification of errors is desired, but
	 * when the overhead of sending the entire message back is not
	 * necessary.
	 *
	 * Note that grades >= 'n' have the return_to_sender flag turned off
	 * completely above.
	 */
	if (islower((int) msg_grade)) {
	    fputs("[[ ... low-priority message, body not included ... ]]\n", f);
	} else {
	    write_body(f, (long) PUT_DOTS);
	}
	(void) fputs(".\n", f);		/* finish the message */
    }

    DEBUG1(DBG_NOTIFY_MID, "notify(): sending BSMTP 'QUIT' and closing child PID %ld.\n", (long int) pid);

    (void) fputs("QUIT\n", f);

    if ((status = close_child(f, (FILE *) NULL, pid)) != 0) {
	/* XXX should these be a panic() calls? */
	if (status == -1) {
	    write_log(WRITE_LOG_PANIC,
		      "notify(): unable to reap status for BSMTP child PID# [%ld]: %s",
		      (long int) pid,
		      strerror(errno));
	} else if (WIFEXITED(status)) {
	    if (WEXITSTATUS(status) != 0) {
		write_log(WRITE_LOG_SYS,
			  "notify(): BSMTP child PID# [%ld] exit status %s (%d)",
			  (long int) pid,
			  strsysexit(WEXITSTATUS(status)),
			  WEXITSTATUS(status));
	    }
	} else if (WIFSIGNALED(status)) {
	    char signm[SIG2STR_MAX];

	    if (sig2str(WTERMSIG(status), signm) == -1) {
		sprintf(signm, "#%d", WTERMSIG(status));
	    }
	    write_log(((WTERMSIG(status) != SIGTERM) ? WRITE_LOG_PANIC : 0) | WRITE_LOG_SYS,
		      "notify(): BSMTP PID# [%ld] killed by signal SIG%s %s: %s",
		      (long int) pid,
		      signm,
		      WCOREDUMP(status) ? "and dumped core" : "(no core)",
		      strsignal(WTERMSIG(status)));
	} else if (WIFSTOPPED(status)) {
	    char signm[SIG2STR_MAX];

	    /* in theory we'll hopefully never see stopped processes... */
	    if (sig2str(WTERMSIG(status), signm) == -1) {
		sprintf(signm, "#%d", WSTOPSIG(status));
	    }
	    write_log(WRITE_LOG_SYS | WRITE_LOG_PANIC,
		      "notify(): BSMTP PID# [%ld] stopped unexpectedly by signal SIG%d: %s",
		      (long int) pid,
		      signm,
		      strsignal(WSTOPSIG(status)));
	}
	/*
	 * this isn't the best, but if we can't send the bounce, let humans
	 * interfere later
	 */
	call_freeze_message = TRUE;
	return;					/* ??? */
    }

    /* note the addresses for which error messages have been sent */
    error_delivery(to_owner);
    error_delivery(to_other);

    return;
}

static void
print_failed_addresses(f, cur)
    FILE *f;
    register struct addr *cur;
{
    register struct addr *top;

    /*
     * find the top (but not immediate) parent to log the original in_addr
     */
    for (top = cur; top->parent && top->parent->in_addr; top = top->parent) {
	;
    }
    if (top == cur || (cur->parent && top == cur->parent)) {
	top = NULL;
    }
    /*
     * NOTE: we want to leave the error->message text as-is -- it may contain
     * newlines which we want to preserve (i.e. not escape) in the output.
     */
    /* XXX impossible? (a parent should never have true_addr?) */
    if (top && top->true_addr && top->true_addr->error) {
	dprintf(f, " address '%v'%s%v%s (orig:t) failed:\n\t%s\n",
		top->true_addr->in_addr ? top->true_addr->in_addr : "(unknown)",
		top->true_addr->parent ? " (derived from: " : "",
		top->true_addr->parent ? (top->true_addr->parent->in_addr ? top->true_addr->parent->in_addr : "(unknown)") : "",
		top->true_addr->parent ? ")" : "",
		top->true_addr->error->message);
    }
    /* XXX impossible? (a parent should never have error info?) */
    if (top && top->error) {
	dprintf(f, " address '%v'%s%v%s (orig) failed:\n\t%s\n",
		top->in_addr ? top->in_addr : "(unknown)",
		top->parent ? " (derived from: " : "",
		top->parent ? (top->parent->in_addr ? top->parent->in_addr : "(unknown)") : "",
		top->parent ? ")" : "",
		top->error->message);
    }
    /* most common */
    if (cur->error) {
	if (cur->director && EQ(cur->director->name, ERROR_DIRECTOR_NM)) {
	    /*
	     * avoid some redundant and confusing information... the error
	     * director provides all the details in its message for use in SMTP
	     * responses so we need only show that whole message here.
	     */
	    dprintf(f, " %v\n", cur->error->message);
	} else {
	    dprintf(f, " address '%v'%s%v%s failed:\n\t%s\n",
		    cur->in_addr ? cur->in_addr : "(unknown)",
		    cur->parent ? " (derived from: " : "",
		    cur->parent ? (cur->parent->in_addr ? cur->parent->in_addr : "(unknown)") : "",
		    cur->parent ? ")" : "",
		    cur->error->message);
	}
    }
    /* common */
    if (cur->true_addr && cur->true_addr->error) {
	dprintf(f, " address '%v'%s%v%s (t) failed:\n\t%s\n",
		cur->true_addr->in_addr ? cur->true_addr->in_addr : "(unknown)",
		cur->true_addr->parent ? " (derived from: " : "",
		cur->true_addr->parent ? (cur->true_addr->parent->in_addr ? cur->true_addr->parent->in_addr : "(unknown)") : "",
		cur->true_addr->parent ? ")" : "",
		cur->true_addr->error->message);
    }
    /* XXX impossible? (the parent should never have error info?) */
    if (cur->parent && cur->parent->error) {
	dprintf(f, " address '%v'%s%v%s (p) failed:\n\t%s\n",
		cur->parent->in_addr ? cur->parent->in_addr : "(unknown)",
		cur->parent->parent ? " (derived from: " : "",
		cur->parent->parent ? (cur->parent->parent->in_addr ? cur->parent->parent->in_addr : "(unknown)") : "",
		cur->parent ? ")" : "",
		cur->parent->error->message);
    }
    /* XXX impossible? (the parent should never have true_addr?) */
    if (cur->parent && cur->parent->true_addr && cur->parent->true_addr->error) {
	dprintf(f, " address '%v'%s%v%s (p->t) failed:\n\t%s\n",
		cur->parent->true_addr->in_addr ? cur->parent->true_addr->in_addr : "(unknown)",
		cur->parent->true_addr->parent ? " (derived from: " : "",
		cur->parent->true_addr->parent ? (cur->parent->true_addr->parent->in_addr ? cur->parent->true_addr->parent->in_addr : "(unknown)") : "",
		cur->parent->true_addr->parent ? ")" : "",
		cur->parent->true_addr->error->message);
    }
    /* XXX impossible? (true_addr should never have true_addr) */
    if (cur->true_addr && cur->true_addr->true_addr && cur->true_addr->true_addr->error) {
	dprintf(f, " address '%v'%s%v%s (t->t->p) failed:\n\t%s\n",
		cur->true_addr->true_addr->in_addr ? cur->true_addr->true_addr->in_addr : "(unknown)",
		cur->true_addr->true_addr->parent ? " (derived from: " : "",
		cur->true_addr->true_addr->parent ? (cur->true_addr->true_addr->parent->in_addr ? cur->true_addr->true_addr->parent->in_addr : "(unknown)") : "",
		cur->true_addr->true_addr->parent ? ")" : "",
		cur->true_addr->true_addr->error->message);
    }
    /* XXX impossible? (parent (of true_addr) should never have error info?) */
    if (cur->true_addr && cur->true_addr->parent && cur->true_addr->parent->error) {
	dprintf(f, " address '%v'%s%v%s (t->p->p) failed:\n\t%s\n",
		cur->true_addr->parent->in_addr ? cur->true_addr->parent->in_addr : "(unknown)",
		cur->true_addr->parent->parent ? " (derived from: " : "",
		cur->true_addr->parent->parent ? (cur->true_addr->parent->parent->in_addr ? cur->true_addr->parent->parent->in_addr : "(unknown)") : "",
		top->true_addr->parent->parent ? ")" : "",
		cur->true_addr->parent->error->message);
    }
    (void) fputc('\n', f);

    return;
}


/*
 * classify_addr - determine who is to be sent an error message
 *
 * If the postmaster, or the sender, is to receive an error, link into the
 * to_other list, and set the send_to_postmaster and/or return_to_sender
 * global variables appropriately.
 *
 * If we need to send to an owner, determine if an owner string is
 * defined and link an addr structure for that owner into the to_owner
 * list.
 */
static void
classify_addr(addr, to_other, to_owner)
    register struct addr *addr;		/* input address */
    struct addr **to_other;		/* list to sender or postmaster */
    struct addr **to_owner;		/* list to address owner */
{
    int link_to_other = FALSE;		/* TRUE to link into to_other list */

    if (error_copy_postmaster) {	/* ugly hack to enable universal postmaster notification */
	addr->error->info |= ERR_NPOSTMAST;
    }
    if (addr->error->info & (ERR_NSENDER|ERR_NPOSTMAST)) {
	/* send to either the sender or the postmaster */
	if (addr->in_addr) {
	    link_to_other = TRUE;
	}

	/* note which one */
	if (addr->error->info & ERR_NSENDER) {
	    return_to_sender = TRUE;
	}
	if (addr->error->info & ERR_NPOSTMAST) {
	    send_to_postmaster = TRUE;
	}
    }
    /*
     * try bouncing to an address owner, if one is defined (and valid)
     */
    if (addr->error->info & (ERR_NSOWNER|ERR_NPOWNER)) {
	register char *s = NULL;	/* expanded owner string */
	register struct addr *cur;	/* addr ptr for scanning parents */
	char *work_addr = NULL;		/* output from preparse_address() */

	/*
	 * first find the closest parent associated with a director that
	 * defines an owner string...
	 */
	for (cur = addr->parent; cur; cur = cur->parent) {
	    char *error;		/* temp for storing parse error */

	    /*
	     * must have a reasonable, expandable owner address
	     */
	    if (cur->director &&
		cur->director->owner &&
		(s = expand_string(cur->director->owner,
				   (struct addr *) NULL,
				   cur->home,
				   cur->remainder)) &&
		(work_addr = preparse_address(s, &error)))
	    {
		DEBUG2(DBG_NOTIFY_MID, "classify_addr(): for %v: found a valid owner address: %v.\n", addr->in_addr, cur->director->owner);
		break;
	    }
	}
	/*
	 * if we didn't walk off the end of the parent list then an owner was
	 * found for the address, so build a new entry in to_owner list...
	 */
	if (cur) {
	    struct addr *new = alloc_addr();

	    new->in_addr = COPY_STRING(s);	/* copy owner address */
	    /*
	     * if this addr has already been seen by classify_addr() use
	     * the true_addr assigned previously, otherwise this is the
	     * first time through, so assign the original addr
	     */
	    if (addr->true_addr) {
		new->true_addr = addr->true_addr;
	    } else {
		new->true_addr = addr;		/* keep track of true addr */
	    }
	    new->work_addr = work_addr;		/* use the preparsed address */
	    /* disable use of smartuser driver, and only allow verify */
	    new->flags = ADDR_SMARTUSER | ADDR_VRFY_ONLY;
	    /*
	     * if resolve_addr_list() fails to verify this list then prepare to
	     * look higher up for a parent that has an associated owner when
	     * re-classifying...
	     */
	    new->parent = cur->parent;
	    /* finally link it into the to_owner list */
	    new->succ = *to_owner;
	    *to_owner = new;
	} else {
	    /*
	     * otherwise no owner was found for the address, bounce to the
	     * sender and/or postmaster...
	     */
	    if (addr->true_addr) {
		int perhaps_use_true = FALSE; /* true to see link, if not linked */
		struct error *true_error = addr->true_addr->error;

		if ((addr->error->info & ERR_NSOWNER) ||
		    (addr->error->info & ERR_NPOWNER))
		{
		    perhaps_use_true = TRUE;
		}

		/* only link if this has not been linked to to_other before */
		if (perhaps_use_true) {
		    if (! (true_error->info & (ERR_NSENDER|ERR_NPOSTMAST)) ) {
			/* link the true address, not this address */
			addr = addr->true_addr;
		    }
		}
	    }
	    link_to_other = TRUE;
	    if (addr->error->info & ERR_NSOWNER) {
		addr->error->info |= ERR_NSENDER;
		return_to_sender = TRUE;
	    }
	    if (addr->error->info & ERR_NPOWNER) {
		addr->error->info |= ERR_NPOSTMAST;
		send_to_postmaster = TRUE;
	    }
	}
    } else if (! return_to_sender && addr->in_addr) {
	/* otherwise if it is valid then make sure it goes somewhere */
	link_to_other = TRUE;
	send_to_postmaster = TRUE;
    }

    /* if link_to_other was set anywhere above, link into to_other list */
    if (link_to_other) {
	addr->succ = *to_other;
	*to_other = addr;
    }
}

static void
notify_new_message(f, to, cc1, cc2, subject_to, extra_info)
    FILE *f;
    char *to;				/* recipient */
    char *cc1;				/* show 1st carbon copy address */
    char *cc2;				/* show 2nd carbon copy address */
    char *subject_to;			/* message for inclusion in subject */
    char *extra_info;			/* optional additional text for subject */
{
    int form;
    char *error;			/* temp to store error message */
    char *new_recip = NULL;

    DEBUG5(DBG_NOTIFY_LO, "notify_new_message: sending to: %v%s%s%s%s\n",
	  to,
	  cc1 ? ", cc1:" : "", cc1 ? cc1 : "",
	  cc2 ? ", cc2:" : "", cc2 ? cc2 : "");
    /*
     * Make sure the recipient has a proper RFC-822-form address.
     */
    form = parse_address(to, (char **) NULL, &error, (int *) NULL);
    switch (form) {
    case LOCAL:
	/* qualify the address with visible_name */
	new_recip = xprintf("%v@%v", to, visible_name);
	break;
    case MAILBOX:
	break;				/* all's well */
    default:
	new_recip = xprintf("PostMaster@%v", visible_name);
	break;
    }
    /*
     * initiate a new message
     *
     * NOTE:  <+> is a smail-internal address form that causes the
     * sender to be considered local, while the SMTP <> address is
     * considered remote.  This is actually very important here.
     */
    (void) dprintf(f, "MAIL FROM:<+>\n");

    /* state the recipient */
    (void) dprintf(f, "RCPT TO:<%s>\n", new_recip ? new_recip : to);

    /* start the message text */
    (void) dprintf(f, "DATA\n");

    /* write out a complete RFC-822 header */
    (void) dprintf(f, "From: Error from MAILER-DAEMON at %v <>\n", visible_name);
    (void) dprintf(f, "To: <%s>\n", new_recip ? new_recip : to);
    if (new_recip) {
	xfree(new_recip);
    }
    if (cc1 == NULL && cc2) {
	cc1 = cc2;
	cc2 = NULL;
    }
    if (cc1) {
	(void) dprintf(f, "Cc: <%s>", cc1);
	if (cc2) {
	    (void) dprintf(f, ", <%s>", cc2);
	}
	(void) putc('\n', f);
    }
    switch (form) {
    case LOCAL:
    case MAILBOX:
	(void) dprintf(f, "Subject: mail failed, %s%s\n", subject_to, extra_info ? extra_info : "");
	(void) fputs("Summary: enclosed message is returned due to delivery errors.\n", f);
	break;
    case FAIL:
	(void) dprintf(f, "Subject: mail failed (see summary), %s%s\n", subject_to, extra_info ? extra_info : "");
	(void) dprintf(f, "Summary: bounce message not sent to <%v>\n", to);
	(void) dprintf(f, "         due to addressing parse error: %v.\n", error);
	break;
    default:
	(void) dprintf(f, "Subject: mail failed (see summary), %s%s\n", subject_to, extra_info ? extra_info : "");
	(void) dprintf(f, "Summary: bounce message not sent to <%v>\n", to);
	(void) dprintf(f, "         due to unsuitable addressing form %s [%d].\n",
		       (form == RFC_ROUTE) ? "RFC_ROUTE" :
		       (form == RFC_ENDROUTE) ? "RFC_ENDROUTE" :
		       (form == UUCP_ROUTE) ? "UUCP_ROUTE" :
		       (form == PCT_MAILBOX) ? "PCT_MAILBOX" :
		       (form == BERKENET) ? "BERKENET" :
		       (form == DECNET) ? "DECNET" : "<bad-form!>",
		       form);
	break;
    }
    (void) dprintf(f, "Reference: <%s@%v>\n", message_id, primary_name);
    /* a blank line ends the main message header.... */
    (void) putc('\n', f);

    /*
     * write out a user-friendly notice for ordinary recipients
     * (other forms will be directed at the local postmaster)
     *
     * XXX this, or at least all but the first and third paragraphs, should be
     * a user-configurable variable so that it can be localized....
     */
    switch (form) {
    case LOCAL:
    case MAILBOX:
	(void) dprintf(f, "\
This message is from the Smail program running on the host %v.\n\
\n\
I'm sorry to have to inform you the message being returned below could\n\
not be delivered to one or more of the destinations it was addressed to.\n\
\n\
The specific addresses which could not be delivered to, and the reasons\n\
for the failure, are given in the first section below.  The full\n\
transaction log summary that follows provides timestamps and other\n\
relevant information which may be useful in tracing and correcting the\n\
cause of the failure.\n\
\n\
Please do not delete this message unless and until you fully understand\n\
why it has been sent!  This is the final notification you will receive\n\
regarding delivery problems with message enclosed below which you either\n\
sent, or are responsible for in some way.\n\
\n\
For further assistance, please send this message to your local <postmaster>.\n\
\n\
If you do so, please include the problem report below and all the headers of\n\
your original message.  You may delete this instructional text and any of the\n\
body of your original message if you wish, but please do not delete or change\n\
any of the headers in the original message, and also please do not delete or\n\
change any of the other sections of this report.  Please also explain as best\n\
as possible what you do not understand about this report.\n\
\n", primary_name);
	break;
    }

    return;
}

static void
write_notify_header(f, in)
    FILE *f;
    struct addr *in;			/* XXX this is a list! */
{
    struct transport tport;		/* write_header() only needs this temporarily */

    (void) memset((char *) &tport, '\0', sizeof(tport)); /* XXX hope NULLs are all 0 bits */

    /* XXX we should walk the whole list and assign them all, but write_header() only uses first one */
    in->transport = &tport;
    tport.name = "error";
    tport.flags = PUT_RECEIVED | STRICT_TPORT | LOCAL_TPORT;

    write_header(f, in);

    /* XXX we should clear the whole list */
    in->transport = NULL;

    return;
}

/* 
 * Local Variables:
 * c-file-style: "smail"
 * End:
 */


syntax highlighted by Code2HTML, v. 0.9.1