/*
#ident	"@(#)smail/src/directors:RELEASE-3_2_0_120:error.c,v 1.8 2004/08/18 20:33:26 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.
 */

/*
 * error.c:
 *	Generate permanent or temporary errors with specified text.
 *
 * Specifications for the "error" directing driver:
 *
 *	private attribute data:
 *
 *	    defaulttext (string):  default text to use if none is specified
 *		by the administrator.
 *
 *	    matchdirector (string):  names the director that this
 *		instance of the error driver is a match for.
 *
 */

#include "defs.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <ctype.h>
#include <assert.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
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

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

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

#include "../smail.h"
#include "../alloc.h"
#include "../list.h"
#include "../config.h"
#include "../main.h"
#include "../parse.h"
#include "../addr.h"
#include "../field.h"
#include "../log.h"
#include "../exitcodes.h"
#include "../lookup.h"
#include "../smailstring.h"
#include "../dys.h"
#include "../direct.h"
#include "../transport.h"
#include "../smailconf.h"
#include "error.h"
#include "../extern.h"
#include "../debug.h"
#include "../error.h"
#include "../smailport.h"

static struct attr_table error_attributes[] = {
    { "defaulttext", t_string, 0, NULL, NULL, OFFSET(error_private, defaulttext) },
    { "matchdirector", t_string, 0, NULL, NULL, OFFSET(error_private, matchdirector) },
};
static struct attr_table *end_error_attributes = ENDTABLE(error_attributes);

static struct error_private error_template = {
    NULL,				/* defaulttext */
    NULL,				/* matchdirector */
};

/*
 * dtb_error - read the configuration file attributes
 */
char *
dtb_error(dp, attrs)
    struct director *dp;		/* director entry being defined */
    struct attribute *attrs;		/* list of per-driver attributes */
{
    char *error;
    struct error_private *priv;	/* new error_private structure */
    char *fn = xprintf("error director: %v", dp->name);

    /* copy the template private data */
    priv = (struct error_private *) xmalloc(sizeof(*priv));
    (void) memcpy((char *) priv, (char *) &error_template, sizeof(*priv));

    dp->private = (char *) priv;
    /* fill in the attributes of the private data */
    error = fill_attributes((char *)priv,
			    attrs,
			    &dp->flags,
			    error_attributes,
			    end_error_attributes,
			    fn);
    xfree(fn);
    if (error) {
	return error;
    }

    return NULL;
}


/*
 * dtp_error - dump error config
 */
void
dtp_error(fp, dp)
     FILE *fp;
     struct director *dp;
{
    (void) dump_standard_config(fp,
				(dp->private) ? dp->private : (char *) &error_template,
				dp->name,
				dp->flags,
				error_attributes,
				end_error_attributes);
}


/*
 * dtd_error - error driver
 */
/*ARGSUSED*/
struct addr *
dtd_error(dp, in, out, new, defer, fail)
    struct director *dp;		/* director entry */
    struct addr *in;			/* input local-form addrs */
    struct addr **out GCC_UNUSED_HACK;	/* output resolved addrs */
    struct addr **new GCC_UNUSED_HACK;	/* output new addrs to resolve */
    struct addr **defer;		/* addrs to defer to a later time */
    struct addr **fail;			/* unresolvable addrs */
{
    struct error_private *priv;		/* private storage */
    struct addr *pass = NULL;		/* addr structures to return */
    struct addr *cur;			/* addr being processed */
    struct addr *next;			/* next addr to process */
    unsigned int do_defer = FALSE;	/* defer instead of fail? */

    DEBUG1(DBG_DRIVER_HI, "dtd_error(%d) called\n", dp->name);

    if (!(priv = (struct error_private *) dp->private)) {
	priv = &error_template;
    }
    for (cur = in; cur; cur = next) {
	next = cur->succ;
	if (strncmpic(cur->remainder, ":defer:", sizeof(":defer:") - 1) == 0) {
	    do_defer = TRUE;
	}
	if (do_defer ||
	    (strncmpic(cur->remainder, ":error:", sizeof(":error:") - 1) == 0) ||
	    (strncmpic(cur->remainder, ":fail:", sizeof(":fail:") - 1) == 0)) {
	    unsigned int found_director = FALSE;
	    struct addr *paddr;
	    char *errtxt;		/* pointer to original error text */
	    char *exptxt;		/* pointer to expanded error text */

	    if (priv->matchdirector) {
		/*
		 * scan back through the parent addresses for something that
		 * was handled by the specified director.
		 */
		for (paddr = cur; paddr; paddr = paddr->parent) {
		    if (EQ(priv->matchdirector, paddr->director->name)) {
			found_director = TRUE;
			break;
		    }
		}
	    } else {
		/*
		 * find the first parent to log as the original in_addr
		 *
		 * Note we don't want the original parent here -- just the one
		 * that was turned into the error or defer address
		 */
		paddr = cur;
		if (paddr->parent && paddr->parent->in_addr) {
		    paddr = paddr->parent;
		}
	    }
	    if (priv->matchdirector && !found_director) {
		DEBUG3(DBG_DRIVER_MID, "dtd_error(%v) skipping %v, not derived from a %v director\n",
		       dp->name, cur->in_addr, priv->matchdirector)
		continue;
	    }
	    cur->director = dp;		/* matched if we reached this point */
	    errtxt = strchr(cur->remainder + 1, ':') + 1;
	    assert(errtxt);
	    while (isspace((int) *errtxt)) {
		errtxt++;
	    }
	    if (! *errtxt && priv->defaulttext) {
		errtxt = priv->defaulttext;
	    }
	    /* XXX what will expand_string("", ...) return? */
	    exptxt = expand_string(errtxt, in->parent, (char *) NULL, (char *) NULL);
	    if (exptxt == NULL) {
		/*
		 * ERR_192 - error text expansion failed
		 *
		 * DESCRIPTION
		 *      The error text part of a :defer:text or :fail:text
		 *      address failed to be expanded by expand_string().
		 *
		 * ACTIONS
		 *      Fail the address and send an error to the address owner
		 *      or to the postmaster.  Report the parent address which
		 *      produced the :defer:text or :fail:text address in the
		 *      error message.
		 *
		 * RESOLUTION
		 *      The postmaster or address owner should correct the address.
		 */
		cur->error = note_error(ERR_NPOWNER | ERR_192,
					xprintf("director %v: error text expansion failed (for parent %v): '%s'",
						dp->name, in->parent->in_addr, errtxt));
		cur->succ = *fail;
		*fail = cur;
		continue;
	    }
	    cur->error = note_error(ERR_NSOWNER | ERR_193,
				    xprintf("address '%v' %s: %s",
					    paddr->in_addr,
					    do_defer ? "deferred" : "failed",
					    exptxt));
	    if (do_defer) {
		cur->succ = *defer;
		*defer = cur;
	    } else {
		cur->succ = *fail;
		*fail = cur;
	    }
	} else {
	    cur->succ = pass;
	    pass = cur;
	    continue;
	}
    }

    return pass;
}

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