/* @(#)src/transport.c	1.22 8/3/92 04:48:37 */

/*
 *    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.
 */

/*
 * transport.c:
 *	process and deliver remote addresses.
 *
 *	The routines in this file are used after an address has been
 *	routed and resolved and associated with a particular transport.
 *	This file provides some support routines for transport drivers.
 *
 *	external functions:  assign_transports, call_transports,
 *			     write_message, remote_from_line, local_from_line,
 *			     find_transport, find_transport_driver
 */
#include <stdio.h>
#include <ctype.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "defs.h"
#include "smail.h"
#include "addr.h"
#include "transport.h"
#include "log.h"
#include "spool.h"
#include "dys.h"
#include "exitcodes.h"
#include "smailconf.h"
#include "parse.h"
#ifndef DEPEND
# include "extern.h"
# include "debug.h"
# include "error.h"
#endif

#ifdef	UNIX_BSD
# include <sys/file.h>
#endif

/* variables exported from this file */
char *path_to_sender = NULL;		/* uucp-style route to sender */
int cached_transports = FALSE;		/* TRUE if cache_transports() called */

/* functions local to this file */
static int assign_compare();
static int write_bsmtp_prologue();
static int write_bsmtp_epilogue();
static void build_path_to_sender();
static char *tp_remove_header();
static char *tp_insert_header();
static char *tp_append_header();
static char *transport_driv_function();


/*
 * assign_transports - assign instances of transports for addresses
 *
 * given a list of addresses as input, produce a list assigning a list
 * of remote addresses to specific instances of a transport.  One
 * instance essentially maps onto exactly one call to the underlying
 * transport driver.
 *
 * NOTE:  This routine breaks the forward links between addr structures.
 */
struct assign_transport *
assign_transports(list)
    struct addr *list;			/* list of addresses */
{
    extern void qsort();
    register struct addr *aq;		/* temp */
    register struct assign_transport *asn; /* list of assigned transports */
    register struct addr **aap;		/* points to array for sorting */
    register int i;			/* index */
    int ct;				/* count of items in input list */
    int host_ct;			/* count of hosts in instance */
    int addr_ct;			/* count of addrs in instance */
    int char_ct = 0;			/* count of chars in instance */
    struct transport *last_transport = NULL; /* last assigned transport */
    char *last_host;			/* last assigned host */

    /* count the number of input items */
    for (ct = 0, aq = list; aq; aq = aq->succ) {
	ct++;
    }

    DEBUG1(DBG_REMOTE_HI, "assign_transports called with %d addrs\n", ct);
    /* create an array and copy remote information to it for sorting */
    aap = (struct addr **)xmalloc(ct * sizeof(*aap));
    for (aq = list, i = 0; aq; aq = aq->succ, i++) {
	aap[i] = aq;
    }

    /* NOTE: qsort does not appear to return anything */
    qsort((char *)aap, ct, sizeof(*aap), assign_compare);

    /*
     * we now have an array sorted by transport and host.
     * pass through that array and assign addr structures to
     * specific transport instances, taking into account
     * max_addr and max_hosts for the transports.
     */
    for (asn = NULL, i = ct-1; i >= 0; --i) {
	/* possible reasons to switch to a new instance */
	if (
	    /* we have a different transport */
	    aap[i]->transport != last_transport ||

	    /* maximum number of hosts per transport call exceeded */
	    (last_transport->max_hosts &&
	      aap[i]->next_host && last_host &&
	      !EQIC(aap[i]->next_host, last_host) &&
	      last_transport->max_hosts < ++host_ct) ||

	    /* maximum number of addrs per transport call exceeded */
	    (last_transport->max_addrs &&
	      last_transport->max_addrs < ++addr_ct) ||

	    /* maximum number of chars of addr per call exceeded */
	    (last_transport->max_chars &&
	      last_transport->max_chars <
		   (char_ct += (int)strlen(aap[i]->next_addr) + 3)))
	{
	    /* initialize for a new instance */
	    struct assign_transport *newasn;

	    DEBUG1(DBG_REMOTE_HI, "new instance ... transport=<%s>\n",
		   aap[i]->transport->name);
	    newasn = (struct assign_transport *)xmalloc(sizeof(*newasn));
	    last_transport = aap[i]->transport;
	    last_host = aap[i]->next_host;
	    host_ct = 1;
	    addr_ct = 1;
	    char_ct = strlen(aap[i]->next_addr) + 3;
	    /* fill in the first entry of the instance */
	    aap[i]->succ = NULL;
	    newasn->succ = asn;
	    asn = newasn;
	} else {
	    /* add the addr to the previous instance */
	    aap[i]->succ = asn->addr;
	}
	asn->addr = aap[i];
	DEBUG2(DBG_REMOTE_HI, "adding...host=<%s>, addr=<%s>\n",
	       asn->addr->next_host, asn->addr->next_addr);
    }

    /* all done, free temps and return the list */
    xfree((char *)aap);
    return asn;
}

/*
 * compare two struct addrs for transport and then hostname
 */
static int
assign_compare(a, b)
    struct addr **a;
    struct addr **b;
{
    register int ret = strcmp((*a)->transport->name, (*b)->transport->name);

    if (ret) {
	/* transports are unequal, return value just based on that */
	return ret;
    }

    /* if next_host not defined, the addrs are equal */
    if ((*a)->next_host == NULL || (*b)->next_host == NULL) {
	return 0;
    }

    /* otherwise, return value is based on host */
    return strcmpic((*a)->next_host, (*b)->next_host);
}


/*
 * call_transports - call transport drivers for assigned transports
 *
 * given a list produced by assign_transports, call the transport drivers
 * to actually perform delivery to addresses.
 */
void
call_transports(asn, defer, fail)
    register struct assign_transport *asn; /* list of assigned instances */
    struct addr **defer;		/* addrs to defer to a later time */
    struct addr **fail;			/* unresolvable addrs */
{
    struct addr *succeed;		/* succeeded addrs from drivers */
    struct addr *tp_defer;		/* defer addrs from drivers */
    struct addr *tp_fail;		/* fail addrs from drivers */
    struct addr *shadow_list = NULL;	/* shadow delivery list */
    struct assign_transport *sh_asn;	/* shadow transport instances */

    for (; asn; asn = asn->succ) {
	time_t started;
	struct transport_driver *driver;
	register struct transport *tp = asn->addr->transport;

	/*
	 * don't deliver to remote transports if the hop_count would
	 * pass beyond maximum.
	 */
	if (hop_count >= max_hop_count && !(tp->flags&LOCAL_TPORT)) {
	    /*
	     * ERR_145 - maximum hop count exceeded
	     *
	     * DESCRIPTION
	     *      If a remote transport were performed, the maximum hop
	     *      count, as set by the max_hop_cound attribute in the
	     *      config file, would be exceeded.  This condition is
	     *      assumed to be a result of a loop condition where two
	     *      machines are sending mail back and forth to each other.
	     *
	     * ACTIONS
	     *      An error message is sent back to the sender stating the
	     *      error.  Note that if one of the sites back to the sender
	     *      has a shorter value for max_hop_count, then the error
	     *      message will fail to reach the sender though, hopefully,
	     *      a message will be sent back to the postmaster at the
	     *      local site.
	     *
	     * RESOLUTION
	     *      Determine which two sites are causing the loop condition
	     *      and remove or correct the forward or alias from one of
	     *      those two sites.  If the problem is not actually a loop
	     *      condition but a long path, a shorter path should be
	     *      used.  It is unlikely that paths longer than 10 or 15
	     *      hops should ever be required.
	     */
	    struct error *loop_error =
		note_error(ERR_NSENDER|ERR_145,
			   "loop detection: maximum hop count exceeded");
	    register struct addr *cur;

	    /* NOTE:  the sequence of operations here IS correct */
	    for (cur = asn->addr; cur; cur = cur->succ) {
		cur->error = loop_error;
	    }
	    fail_delivery(asn->addr);
	    insert_addr_list(asn->addr, fail, loop_error);
	    continue;
	}

	/* lookup the driver */
	driver = find_transport_driver(tp->driver);

	if (driver == NULL) {
	    /* configuration error, the driver does not exist */
	    /*
	     * ERR_146 - transport driver not found
	     *
	     * DESCRIPTION
	     *      The driver name was not in the table of transport
	     *      drivers.
	     *
	     * ACTIONS
	     *      Defer addresses with configuration errors.
	     *
	     * RESOLUTION
	     *      The postmaster must check the transport configuration
	     *      before delivery can be performed.
	     */
	    insert_addr_list(asn->addr,
			     defer,
			     note_error(ERR_CONFERR|ERR_146,
					xprintf(
					   "transport %s: driver %s not found",
						tp->name, tp->driver)));
	    continue;
	}

	/* determine official time of attempt */
	time(&started);

	/* call the transport */
	succeed = NULL;
	tp_defer = NULL;
	tp_fail = NULL;

	DEBUG2(DBG_REMOTE_LO, "transport %s uses driver %s\n",
	       tp->name, tp->driver);
	(*driver->driver)(asn->addr, &succeed, &tp_defer, &tp_fail);

	/* subject transport-deferred addresses to retry duration limits */
	tp_defer = retry_addr_after(started, tp_defer, fail);

	/* look at successful deliveries */
	if (succeed) {
	    retry_addr_finished(succeed);
	    /* log successes */
	    succeed_delivery(succeed);
	    if (tp->shadow) {
		/* on successful delivery, call the shadow transport */
		struct transport *shadow_tp = find_transport(tp->shadow);

		if (shadow_tp == NULL) {
		    send_to_postmaster = TRUE;
		    write_log(LOG_SYS|LOG_MLOG,
			      "transport %s: shadow transport %s not found",
			      tp->name, tp->shadow);
		} else {
		    /* insert in shadow delivery list, with new transport */
		    register struct addr *cur;
		    register struct addr *next;

		    for (cur = succeed; cur; cur = next) {
			next = cur->succ;
			cur->flags |= ADDR_SHADOW;
			cur->transport = shadow_tp;
			cur->succ = shadow_list;
			shadow_list = cur;
		    }
		}
	    }
	}

	/* look at deferred deliveries */
	if (tp_defer) {
	    retry_addr_finished(tp_defer);
	    insert_addr_list(tp_defer, defer, (struct error *)NULL);
	}

	/* look at failed deliveries */
	if (tp_fail) {
	    retry_addr_finished(tp_fail);
	    fail_delivery(tp_fail);	/* log them */
	    if (tp->error_transport == NULL) {
		insert_addr_list(tp_fail, fail, (struct error *)NULL);
	    } else {
		/* on failed delivery, call the error transport */
		struct transport *error_tp =
		    find_transport(tp->error_transport);

		if (error_tp == NULL) {
		    send_to_postmaster = TRUE;
		    write_log(LOG_SYS|LOG_MLOG,
			      "transport %s: error transport %s not found",
			      tp->name, tp->error_transport);
		} else {
		    /* insert in shadow delivery list, with new transport */
		    register struct addr *cur;
		    register struct addr *next;

		    for (cur = tp_fail; cur; cur = next) {
			next = cur->succ;
			cur->transport = error_tp;
			cur->succ = shadow_list;
			shadow_list = cur;
		    }
		}
	    }
	}
    }

    /* if there are shadow deliveries to be made, assign them and transport */
    if (shadow_list == NULL) {
	return;				/* no shadows, just return */
    }

    /* assign shadow deliveries */
    for (sh_asn = assign_transports(shadow_list);
	 sh_asn;
	 sh_asn = sh_asn->succ)
    {
	struct transport_driver *driver;
	register struct transport *tp = sh_asn->addr->transport;
	struct addr *dummy_defer;

	/* lookup the driver */
	driver = find_transport_driver(tp->driver);

	if (driver == NULL) {
	    /* configuration error: for shadows, send to postmaster */
	    send_to_postmaster = TRUE;
	    write_log(LOG_SYS|LOG_MLOG,
		      "shadow transport %s: driver %s not found",
		      tp->name, tp->driver);
	    continue;
	}

	/* call the shadow transport */
	DEBUG2(DBG_REMOTE_LO, "calling driver %s from shadow transport %s\n",
	       tp->driver, tp->name);
	succeed = NULL;
	tp_fail = NULL;
	(*driver->driver)(sh_asn->addr, &succeed, &dummy_defer, &tp_fail);

	/* log failed addresses */
	if (fail) {
	    struct addr *cur;
	    struct addr *next;

	    for (cur = tp_fail; cur; cur = next) {
		next = cur->succ;
		write_log(LOG_SYS, "%s ... shadow transport %s failed: %s",
			  cur->in_addr, cur->transport->name, cur->next_addr);
		if ((cur->flags & ADDR_SHADOW) == 0) {
		    /*
		     * put failed deliveries from an error_transport on
		     * output fail list
		     */
		    cur->succ = *fail;
		    *fail = cur;
		}
	    }
	}
    }
}


/*
 * cache_transports - call cache entrypoints for all transports
 *
 * cache information used by transport drivers.  This can be called
 * when it is determined that there will be an attempt to deliver more
 * than one mail message, to increase the overall efficiency of the
 * mailer.
 *
 * Daemons can call this periodically to recache stale data.
 */
void
cache_transports()
{
    struct transport *tp;		/* temp for stepping thru transports */
    struct transport_driver *driver;

    for (tp = transports; tp; tp = tp->succ) {
	driver = find_transport_driver(tp->driver);
	if (driver && driver->cache) {
	    (*driver->cache)(tp);
	}
    }
    cached_transports = TRUE;
}

#ifdef notyet
/*
 * finish_transports - free resources used by all directors
 *
 * free information that was cached by transports or used by
 * transports in the process of delivering messages.  Transports can
 * cache data for efficiency, or can maintain state between
 * invocations.  This function is called when transports will no
 * longer be needed, allowing transports to free any resources that
 * they were using that will no longer be needed.  For example, it is
 * a good idea for transports to close any files that they opened, as
 * file descriptors are a precious resource in some machines.
 */
void
finish_transports()
{
    struct transports *tp;		/* temp for stepping thru transports */
    struct transport_driver *driver;

    for (tp = transports; tp; tp = tp->succ) {
	driver = find_transport_driver(tp->driver);
	if (driver->finish) {
	    (*driver->finish)(dp);
	}
    }
    cached_transports = FALSE;
}
#endif


/*
 * write_message - write out the current message to an open file
 *
 * this takes a transport file entry and a list of addr structures
 * and writes the message in the manner specified in the transport
 * file entry.  It is intended to be called from transport drivers.
 *
 * If BSMTP_TPORT is set in the transport flags, an SMTP envelope
 * is placed around the message.  However, result codes are not
 * read from the destination so this is not useful for interactive
 * SMTP.  The list of addr structures is only used for writing the
 * SMTP envelope.  HBSMTP_TPORT generates an SMTP envelope without
 * the initial HELO or the final QUIT commands.
 *
 * NOTE:  Everybody still needs to call fflush after calling
 *	  write_message().
 *
 * return READ_FAIL, WRITE_FAIL or SUCCEED
 */
int
write_message(f, tp, addr)
    FILE *f;				/* write on this file */
    struct transport *tp;		/* transport file entry */
    struct addr *addr;			/* list of addresses */
{
    int fail;				/* returned failure codes */

    DEBUG(DBG_HEADER_LO, "\nEnvelope text\n");
    /*
     * put out the initial smtp commands up to the DATA command
     */
    if (tp->flags & (BSMTP_TPORT | HBSMTP_TPORT)) {
#ifndef NODEBUG
	if (debug >= DBG_HEADER_LO && errfile) {
	    (void) write_bsmtp_prologue(errfile, tp, addr);
	}
#endif	/* NODEBUG */
	if (write_bsmtp_prologue(f, tp, addr) == FAIL) {
	    return WRITE_FAIL;
	}
	tp->flags |= PUT_DOTS;		/* force use of SMTP dot protocol */
    }

    /*
     * write out a From<space> line, if required
     */
    if (tp->flags & PUT_FROM) {
	/* but what form do we need? */
	if (tp->flags & LOCAL_XFORM) {
#ifndef NODEBUG
	    if (debug >= DBG_HEADER_LO && errfile) {
		(void)fputs(local_from_line(), errfile);
	    }
#endif	/* NODEBUG */
	    if (fputs(local_from_line(), f) == EOF) {
		return WRITE_FAIL;
	    }
	} else {
#ifndef NODEBUG
	    if (debug >= DBG_HEADER_LO && errfile) {
		(void)fputs(remote_from_line(), errfile);
	    }
#endif	/* NODEBUG */
	    if (fputs(remote_from_line(), f) == EOF) {
		return WRITE_FAIL;
	    }
	}
	DEBUG(DBG_HEADER_LO, "\n");
	/* put the end of line */
	if (tp->flags & PUT_CRLF) {
	    if (putc('\r', f) == EOF) {
		return WRITE_FAIL;
	    }
	}
	if (putc('\n', f) == EOF) {
	    return WRITE_FAIL;
	}
    }

    /*
     * write out the header according to the transport flags
     */
    if (write_header(f, addr) == FAIL) {
	return WRITE_FAIL;
    }
    if ((tp->flags & PUT_CRLF) && putc('\r', f) == EOF) {
	return WRITE_FAIL;
    }
    if (putc('\n', f) == EOF) {
	return WRITE_FAIL;
    }

    if (tp->flags & DEBUG_TPORT) {
	/* if we are debugging, don't dump the body, dump interesting
	 * debugging info instead */
	extern char **save_argv;	/* import this from main */
	char **av;

	(void) fprintf(f, "|---- original flags ----|\n");
	for (av = save_argv; *av; av++) {
	    (void) fprintf(f, "%s\n", *av);
	}
	(void) fprintf(f, "|---- interesting info ----|\n");
	(void) fprintf(f,"\
	message_id=%s\n\
	spool_dir=%s\n\
	spool_fn=%s\n\
	transport=%s\n\
	next_host=%s\n\
	msg_grade=%c\n",
		       message_id,
		       spool_dir,
		       spool_fn,
		       tp->name,
		       (addr && addr->next_host)? addr->next_host: "(null)",
		       msg_grade);

	send_log(f, TRUE, "|---- per-message log ----|\n");
    } else {

	DEBUG(DBG_HEADER_LO, "... message body ...\n");

	/*
	 * write out the body.
	 */
	if ((fail = write_body(f, tp->flags)) != SUCCEED) {
	    return fail;
	}
    }

    /*
     * finish off the SMTP envelope
     */
    if (tp->flags & (BSMTP_TPORT | HBSMTP_TPORT)) {
#ifndef NODEBUG
	if (debug >= DBG_HEADER_LO && errfile) {
	    (void) write_bsmtp_epilogue(errfile, tp, addr);
	}
#endif	/* NODEBUG */
	if (write_bsmtp_epilogue(f, tp) == FAIL) {
	    return WRITE_FAIL;
	}
    }
    DEBUG(DBG_HEADER_LO, "\n");

    return SUCCEED;			/* everything went fine */
}

/*
 * write_bsmtp_prologue - write the initial SMTP commands for BSMTP transports
 *
 * write out the HELO, MAIL FROM, RCPT TO and DATA commands.
 */
static int
write_bsmtp_prologue(f, tp, addr)
    FILE *f;				/* file to write commands to */
    struct transport *tp;		/* transport entry */
    struct addr *addr;			/* recipient addr structures */
{
    /*
     * how do we end a line?
     */
    char *eol = (tp->flags & PUT_CRLF)? "\r\n": "\n";
    char *error;

    if (! (tp->flags & HBSMTP_TPORT) &&
	fprintf(f, "HELO %s%s", primary_name, eol) == EOF)
    {
	return FAIL;
    }

    /*
     * determine which sender form is appropriate
     */
    if (error_sender) {
	/* if the special SMTP sender forms <> or <+> were given, pass along */
	if (fprintf(f, "MAIL FROM:<%s>%s",
		    ((islocal && tp->flags & LOCAL_XFORM)? "+": ""),
		    eol) == EOF) {
	    return FAIL;
	}
    } else {
	if (fprintf(f, "MAIL FROM:<%s>%s", get_sender_addr(tp), eol) == EOF) {
	    return FAIL;
	}
    }

    /*
     * write out a RCPT TO: line for each recipient address
     */
    while (addr) {
	if (fprintf(f, "RCPT TO:<%s>%s", addr->next_addr, eol) == EOF) {
	    return FAIL;
	}
	addr = addr->succ;
    }

    if (fprintf(f, "DATA%s", eol) == EOF) {
	return FAIL;
    }

    return SUCCEED;
}

/*
 * get_sender_addr - return a sender address for use a particular transport
 *
 * Return a transformation of the sender address based on the various
 * transport attributes.
 *
 * The returned storage should not be freed and may be reused in
 * subsequent calls to get_sender_addr.
 */
char *
get_sender_addr(tp)
    struct transport *tp;
{
    static struct str str;
    static int inited = 0;
    char *path;
    int form;
    int flags;
    char *err;

    if (! inited) {
	inited = 1;
	STR_INIT(&str);
    } else {
	str.i = 0;
    }

    if (tp->flags & UUCP_XFORM ||
	(tp->flags & PUT_FROM && !(tp->flags & LOCAL_XFORM)))
    {
	path = build_partial_uucp_route(sender, &err, 0);
	if (path) {
	    str_printf(&str, "%s!%s%N", uucp_name, path);
	    xfree(path);
	} else {

	    /*
	     * bad sender path, prepend host! anyway.
	     */

	    str_printf(&str, "%s!%s%N", uucp_name, sender);
	}

	return str.p;
    }

    if (tp->flags & LOCAL_XFORM) {
	return sender;
    }


    /* INET_XFORM or no specified transform */
    form = parse_address(sender, (char **)0, &err, &flags);
    switch (form) {
    case FAIL:
    case RFC_ROUTE:
    case RFC_ENDROUTE:
    case MAILBOX:
    case PARSE_ERROR:
	return sender;

    default:
	str_printf(&str, "%s@%s%N", sender, visible_name);
	return str.p;
    }
}

/*
 * write_bsmtp_epilogue - write tailing commands for BSMTP transports
 *
 * finish off the data command and write out the QUIT command.
 */
static int
write_bsmtp_epilogue(f, tp)
    FILE *f;
    struct transport *tp;
{
    if (tp->flags & HBSMTP_TPORT) {
	if (fprintf(f, (tp->flags&PUT_CRLF)? ".\r\n": ".\n") == EOF) {
	    return FAIL;
	}
	return SUCCEED;
    }
    if (tp->flags & PUT_CRLF) {
	if (fprintf(f, ".\r\nQUIT\r\n") == EOF) {
	    return FAIL;
	}
    } else {
	if (fprintf(f, ".\nQUIT\n") == EOF) {
	    return FAIL;
	}
    }

    return SUCCEED;
}


/*
 * remote_from_line - build a From_ line for remote transports
 */
char *
remote_from_line()
{
    static char *from_line = NULL;	/* saved From_ line */

    if (from_line) {
	return from_line;		/* exists, use it */
    }
    if (path_to_sender == NULL) {
	build_path_to_sender();
    }
    from_line = xprintf("From %s %s remote from %s",
			path_to_sender, unix_date(), uucp_name);
    return from_line;
}

/*
 * local_from_line - build a From_ line for local transports
 */
char *
local_from_line()
{
    static char *from_line = NULL;	/* saved From_ line */

    if (from_line) {
	return from_line;		/* exists, use it */
    }
    if (path_to_sender == NULL) {
	build_path_to_sender();
    }
    from_line = xprintf("From %s %s", path_to_sender, unix_date());
    return from_line;
}

/*
 * build_path_to_sender - build a !-route version of sender address
 */
static void
build_path_to_sender()
{
    char *error;

    path_to_sender = build_partial_uucp_route(sender, &error);
    if (path_to_sender == NULL) {
	write_log(LOG_SYS|LOG_MLOG,
		  "error building path to sender, sender=%s, error=%s",
		  sender, error);
	path_to_sender = "Postmaster";
    }
}


/*
 * find_transport - given a transport's name, return the transport structure
 *
 * return NULL if no transport of that name exists.
 */
struct transport *
find_transport(name)
    register char *name;		/* search key */
{
    register struct transport *tp;	/* temp for stepping thru transports */

    /* loop through all the transports */
    for (tp = transports; tp; tp = tp->succ) {
	if (EQ(tp->name, name)) {
	    /* found the transport in question */
	    return tp;
	}
    }

    return NULL;			/* transport not found */
}

/*
 * find_transport_driver - given a driver's name, return the driver structure
 *
 * return NULL if driver does not exist.
 */
struct transport_driver *
find_transport_driver(name)
    register char *name;		/* search key */
{
    register struct transport_driver *tdp; /* pointer to table of drivers */

    for (tdp = transport_drivers; tdp->name; tdp++) {
	if (EQ(tdp->name, name)) {
	    return tdp;			/* found the driver */
	}
    }

    return NULL;			/* driver not found */
}


/*
 * read_transport_file - read transport file
 *
 * read the transport file and build the transport list describing the
 * entries.  Return an error message or NULL.
 *
 * The entries from the transport file are prepended to the built-in
 * transports.
 */
char *
read_transport_file()
{
    FILE *f;				/* open transport file */
    char *error;			/* error from read_standard_file() */
    struct transport **tpp;
    struct stat statbuf;
    static struct attr_table transport_generic[] = {
	{ "driver", t_string, NULL, NULL, OFFSET(transport, driver) },
	{ "max_addrs", t_int, NULL, NULL, OFFSET(transport, max_addrs) },
	{ "max_hosts", t_int, NULL, NULL, OFFSET(transport, max_hosts) },
	{ "max_chars", t_int, NULL, NULL, OFFSET(transport, max_chars) },
	{ "retry_dir", t_string, NULL, NULL, OFFSET(transport, retry_dir) },
	{ "shadow", t_string, NULL, NULL, OFFSET(transport, shadow) },
	{ "error_transport", t_string, NULL, NULL,
	  OFFSET(transport, error_transport) },
	{ "strict", t_boolean, NULL, NULL, STRICT_TPORT },
	{ "uucp", t_boolean, NULL, NULL, UUCP_XFORM },
	{ "inet", t_boolean, NULL, NULL, INET_XFORM },
	{ "received", t_boolean, NULL, NULL, PUT_RECEIVED },
	{ "return_path", t_boolean, NULL, NULL, PUT_RETURNPATH },
	{ "from", t_boolean, NULL, NULL, PUT_FROM },
	{ "local", t_boolean, NULL, NULL, LOCAL_TPORT|LOCAL_XFORM },
	{ "local_xform", t_boolean, NULL, NULL, LOCAL_XFORM },
	{ "crlf", t_boolean, NULL, NULL, PUT_CRLF },
	{ "bsmtp", t_boolean, NULL, NULL, BSMTP_TPORT },
	{ "hbsmtp", t_boolean, NULL, NULL, HBSMTP_TPORT },
	{ "dots", t_boolean, NULL, NULL, PUT_DOTS },
	{ "debug", t_boolean, NULL, NULL, DEBUG_TPORT },
	{ "unix_from_hack", t_boolean, NULL, NULL, UNIX_FROM_HACK },
	{ "uucp_from_hack", t_boolean, NULL, NULL, UNIX_FROM_HACK },
	{ "remove_header", t_proc, NULL, (tup *)tp_remove_header, 0 },
	{ "insert_header", t_proc, NULL, (tup *)tp_insert_header, 0 },
	{ "append_header", t_proc, NULL, (tup *)tp_append_header, 0 },
    };
    struct attr_table *end_transport_generic = ENDTABLE(transport_generic);
    static struct transport transport_template = {
	NULL,				/* name */
	"pipe",				/* driver, a reasonable default */
	NULL,				/* succ will be assigned */
	PUT_RECEIVED,			/* flags */
	1,				/* max_addrs */
	1,				/* max_hosts */
	2000,				/* max_chars, about half of NCARGS? */
	NULL,				/* shadow */
	NULL,				/* error_transport */
	NULL,				/* private */
    };

    /*
     * try to open the transport file, stat file if possible
     */
    if (transport_file == NULL || EQ(transport_file, "-")) {
	return NULL;
    }
    f = fopen(transport_file, "r");
    if (f == NULL) {
	if (require_configs) {
	    return xprintf("%s: %s", transport_file, strerrno());
	}

	add_config_stat(transport_file, (struct stat *)NULL);
	return NULL;
    }

    (void)fstat(fileno(f), &statbuf);
    add_config_stat(transport_file, &statbuf);

    /* call read_standard_file to do the real work */
    error = read_standard_file(f,
			       (char *)&transport_template,
			       sizeof(struct transport),
			       OFFSET(transport, name),
			       OFFSET(transport, flags),
			       OFFSET(transport, succ),
			       transport_generic,
			       end_transport_generic,
			       transport_driv_function,
			       (char **)&transports);

    /* finish up */
    (void) fclose(f);

    for (tpp = &transports; *tpp; tpp = &(*tpp)->succ)
	;
    *tpp = builtin_transports;

    /* return any error message */
    if (error) {
	return xprintf("%s: %s", transport_file, error);
    }
    return NULL;
}

static char *
tp_remove_header(struct_p, attr)
    char *struct_p;			/* passed transport structure */
    struct attribute *attr;		/* attribute from transports file */
{
    struct transport *tp = (struct transport *)struct_p;
    struct list *lp;

    lp = (struct list *)xmalloc(sizeof(*lp));
    lp->text = attr->value;
    lp->succ = tp->hdrremove;
    tp->hdrremove = lp;
    return NULL;
}

static char *
tp_insert_header(struct_p, attr)
    char *struct_p;			/* passed transport structure */
    struct attribute *attr;		/* attribute from transports file */
{
    struct transport *tp = (struct transport *)struct_p;
    struct list *lp;

    lp = (struct list *)xmalloc(sizeof(*lp));
    lp->text = attr->value;
    lp->succ = tp->hdrinsert;
    tp->hdrinsert = lp;
    return NULL;
}

static char *
tp_append_header(struct_p, attr)
    char *struct_p;			/* passed transport structure */
    struct attribute *attr;		/* attribute from transports file */
{
    struct transport *tp = (struct transport *)struct_p;
    struct list *lp;

    lp = (struct list *)xmalloc(sizeof(*lp));
    lp->text = attr->value;
    lp->succ = tp->hdrappend;
    tp->hdrappend = lp;
    return NULL;
}

static char *
transport_driv_function(struct_p, driver_attrs)
    char *struct_p;			/* passed transport structure */
    struct attribute *driver_attrs;	/* driver-specific attributes */
{
    struct transport *tp = (struct transport *)struct_p;
    struct transport_driver *drv;

    if (tp->driver == NULL) {
	return xprintf("transport %s: no driver attribute", tp->name);
    }
    drv = find_transport_driver(tp->driver);
    if (drv == NULL) {
	return xprintf("transport %s: unknown driver: %s",
		       tp->name, tp->driver);
    }
    if (drv->builder) {
	return (*drv->builder)(tp, driver_attrs);
    }

    return NULL;
}
