/*
#ident	"@(#)smail/src/transports:RELEASE-3_2_0_120:tcpsmtp.c,v 1.84 2004/08/17 19:49:09 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.
 */

/*
 * tcpsmtp.c:
 *      Send mail using the SMTP protocol over TCP/IP.
 *
 * See note in smtplib.c regarding logging of SMTP reply text.
 */

#include "defs.h"

/*
 * Compilation of this entire file depends on "HAVE_BSD_NETWORKING".
 */
#ifdef HAVE_BIND

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.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

#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include "../smail.h"
#include "../alloc.h"
#include "../list.h"
#include "../smailsock.h"
#include "../main.h"
#include "../parse.h"
#include "../addr.h"
#include "../bindsmtpth.h"
#include "../route.h"
#include "../spool.h"
#include "../exitcodes.h"
#include "../lookup.h"
#include "../smailstring.h"
#include "../dys.h"
#include "../transport.h"
#include "../smailconf.h"
#include "tcpsmtp.h"
#include "smtplib.h"
#include "../extern.h"
#include "../error.h"
#include "../debug.h"
#include "../log.h"
#include "../smailport.h"

/* functions local to this file */
#ifdef HAVE_BIND
static int tcpsmtp_bind_addr __P((struct addr *, struct addr **, struct addr **));
#endif
static int tcpsmtp_internal __P((struct addr *, struct addr **, struct addr **, struct addr **,
				 char *, struct in_addr *, int, char *, int, struct error **));
static char *set_short_timeout __P((char *, struct attribute *));
static char *set_long_timeout __P((char *, struct attribute *));
static char *set_timeout __P((time_t *, struct attribute *));
static int tcpsmtp_connect __P((char *, struct in_addr *, int, char *, char **));
static struct error *connect_failure __P((struct transport *, const char *));
static struct transport_hints *fake_addr_hints __P((char *, const char **));

static struct attr_table tcpsmtp_attributes[] = {
    { "short_timeout", t_proc, 0, NULL, (tup_t *)set_short_timeout, 0 },
    { "long_timeout", t_proc, 0, NULL, (tup_t *)set_long_timeout, 0 },
    { "service", t_string, 0, NULL, NULL, OFFSET(tcpsmtp_private, service) },
#ifdef HAVE_BIND
    { "use_bind", t_boolean, 0, NULL, NULL, TCPSMTP_USE_BIND },
    BIND_ATTRIBUTES(tcpsmtp_private, bindlib_attr),
#endif
};
static struct attr_table *end_tcpsmtp_attributes = ENDTABLE(tcpsmtp_attributes);


/*
 * tpd_tcpsmtp - transport to remote machine using SMTP on top of TCP/IP.
 *
 * The first address on the 'addr' list will contain routing info.  The
 * remaining addresses, if any, will be more recipients for the same message at
 * the same destination.
 */
void
tpd_tcpsmtp(addr, succeed, defer, fail)
    struct addr *addr;                  /* recipient addresses for transport */
    struct addr **succeed;              /* successful deliveries */
    struct addr **defer;                /* defer until a later queue run */
    struct addr **fail;                 /* failed deliveries */
{
    struct transport_hints *hints;
    struct transport_hints *mx_hints;
    struct ipaddr_hint *ip_addr;
    char *service;
    const char *error_text = NULL;
    struct error *lckerr = NULL;	/* lock error structure */
    struct error *connerr = NULL;	/* temporary for any connection error */
    struct error *primary_err = NULL;	/* error from primary MX, if any */
    struct tcpsmtp_private *priv;
    time_t started = 0;			/* connect start time for retry_host_unlock() */
    int defer_failure = TRUE;		/* always reset by retry_host_lock()... */

    priv = (struct tcpsmtp_private *) addr->transport->private;
    
    if (!(service = expand_string(priv->service, addr, (char *) NULL, (char *) NULL))) {
	insert_addr_list(addr, defer,
			 connect_failure(addr->transport,
					 xprintf("failed to expand service, %v", priv->service)));
	return;
    }
    /*
     * we have to copy the result to allow more calls to expand_string() since
     * it returns a pointer to its own private STR region that it reuses....
     */
    service = COPY_STRING(service);

    for (hints = addr->tphint_list;
	 hints && !EQ(hints->hint_name, "mx"); /* skipt past non-MX hints */
	 hints = hints->succ) {
	;
    }
#ifdef HAVE_BIND
    /*
     * if there were no valid MX's in the hints list, look again
     */
    if (!hints && (addr->transport->flags & TCPSMTP_USE_BIND)) {
	if (tcpsmtp_bind_addr(addr, defer, fail) != SUCCEED) {
	    /* note addresses are already moved to defer or fail */
            return;
	}

	for (hints = addr->tphint_list;
	     hints && !EQ(hints->hint_name, "mx"); /* skip initial non-MX's */
	     hints = hints->succ) {
	    ;
	}
    }
#endif
    /*
     * in this case (either we weren't compiled with HAVE_BIND, or we weren't
     * supposed to use it, or it didn't help anyway) we concoct a fake MX hint
     * list for the hostname given the address(es) returned by gethostbyname()
     * (which might look in /etc/hosts, unlike the bindlib routines which will
     * not); or one for the literal IP address, if that's what it turns out
     * we're trying to deliver to.
     *
     * note we won't bother adding to any existing hints list since 
     */
    if (!hints) {
	hints = fake_addr_hints(addr->next_host, &error_text);
    }
    /*
     * we didn't find any address to which we can send this message! -- check
     * the retry timeout by using addr->next_host as the lock file and bounce
     * it if we're over the retry_duration limit, or just defer it otherwise...
     */
    if (!hints) {
	int lckret;
	char *oerror;
	
	oerror = xprintf("failed to find any IP addresses for '%v'%s%s",
			 addr->next_host,
			 error_text ? ": " : "",
			 error_text ? error_text : "");
	connerr = connect_failure(addr->transport, oerror);
	lckret = retry_host_lock(addr->transport, addr->next_host, &defer_failure, &lckerr);
	/* should a lock failure be included in the recorded error? */
	if (lckret != SUCCEED || !defer_failure) {
	    char *nerror;

	    /* merge the lock error with the above message */
	    nerror = xprintf("%s (%s)", oerror, lckerr->message);
	    xfree(oerror);
	    free_error(connerr);
	    connerr = connect_failure(addr->transport, nerror);
	    xfree(nerror);
	}
	if (lckret == SUCCEED) {
	    retry_host_unlock((time_t) 0, connerr);
	}
	insert_addr_list(addr, defer_failure ? defer : fail, connerr);

	return;
    }
    /*
     * MX hint list is already sorted by preference so just walk through it
     */
    for (mx_hints = hints; mx_hints; mx_hints = mx_hints->succ) {
	int tmp_result = SMTP_AGAIN;

	if (!EQ(mx_hints->hint_name, "mx")) {
	    continue;		/* ignore all but MX RRs (real or fake) */
	}

	/*
	 * Try each ip address of the target host...
	 */
# define mx_hint	((struct mx_transport_hint *) (mx_hints->private))

	/* XXX this loop should be in a separate function so we can use it
	 * below too, but unfortunately we use struct mx_transport_hint which
	 * isn't available from ip_address()
	 */
	for (ip_addr = mx_hint->ipaddrs; ip_addr; ip_addr = ip_addr->succ) {
	    char *lockhost;

	    /* always lock the target domain name if we're using the smarthost
	     * because we don't ever want the smarthost to be blocked waiting
	     * for a retry...
	     *
	     * Otherwise we lock the IP address because if we locked the
	     * hostname then we'd never be able to connect to the next IP
	     * address in the list since it's the same hostname and the retry
	     * time definitely won't have been reached next time around (unless
	     * it's set lower than the connect timeout!)
	     *
	     * Note we won't "optimize" the locking in the smart-host case.
	     * The benefit is low and the added logic is unnecessary
	     * complication, not to mention but that not doing so may help
	     * another runq process do another job with interleaved processing.
	     */
	    lockhost = COPY_STRING((addr->flags & ADDR_SMARTHOST) ? addr->target : inet_ntoa(ip_addr->addr));
	    if (retry_host_lock(addr->transport, lockhost, &defer_failure, &lckerr) != SUCCEED) {
		if (!defer_failure) {
		    /*
		     * if we didn't get the retry lock and if this message has
		     * past the retry duration, then move it to the fail list
		     * and just get out of here now...
		     */
		    lckerr->info |= ERR_NSOWNER;
		    insert_addr_list(addr, fail, lckerr);
		    /* XXX is unlock of a failed lock necessary? */
		    retry_host_unlock((time_t) 0, lckerr);
		    xfree(lockhost);
		    /*
		     * hopefully just ERR_173 is returned: "retry duration
		     * exceeded", but there's no sense in checking because
		     * we've already set the message up to bounce...
		     */
		    return;
		}
		if (!primary_err || primary_err == lckerr) {
		    primary_err = lckerr;	/* default to (new) lock error */
		}
		/* XXX is unlock of a failed lock necessary? */
		retry_host_unlock((time_t) 0, lckerr);
		xfree(lockhost);

		continue;		/* keep on truckin', try the next one */
	    }
	    /*
	     * if the target's retry file was successfully locked, then let's
	     * try to send the messsage...
	     */
	    connerr = NULL;
	    (void) time(&started);
	    tmp_result = tcpsmtp_internal(addr, succeed, defer, fail,
					  ip_addr->hostname, &ip_addr->addr,
					  AF_INET, service, defer_failure,
					  &connerr);
	    /*
	     * if this message won't be deferred then include retry file's
	     * error message too
	     */
	    if (connerr && connerr->message && !defer_failure) {
		char *new_msg;

		new_msg = xprintf("%s\n\tPrevious retry error:  %s", connerr->message, lckerr->message);
		connerr->message = new_msg;
		DEBUG1(DBG_DRIVER_LO, "keeping retry file's error msg as: '%v'", connerr->message);
	    }
	    if ((!primary_err || primary_err == lckerr) && connerr) {
		/*
		 * Remember the error for the first connection attempt as this
		 * is the one we'll log, overriding any lckerr saved above.
		 * Normally this will be for the primary MX host, unless it just
		 * failed or has recently failed and thus was passed over by
		 * the retry lock.
		 */
		primary_err = connerr;
	    }
	    switch (tmp_result) {
	    case SMTP_SUCCEED:
		/* explicitly pass a NULL error so the retry file is removed */
		retry_host_unlock(started, (struct error *) NULL);
		xfree(lockhost);
		return;
		/* NOTREACHED */

	    case SMTP_FAIL:		/* we've been told to go away */
		retry_host_unlock(started, connerr);
		xfree(lockhost);
		return;			/* we will probably bounce it... */
		/* NOTREACHED */

	    case SMTP_AGAIN:		/* we've been told to come back later */
		retry_host_unlock(started, connerr);
		xfree(lockhost);
		return;			/* so don't bother with any lesser MX */
		/* NOTREACHED */

	    case SMTP_NOCONNECT:
		retry_host_unlock(started, connerr);
		break;			/* continue with any remaining hosts */
	    }
	    xfree(lockhost);
	}
# undef mx_hint
    }
    /*
     * If we've never called tcpsmtp_internal(), or if it returned
     * SMTP_NOCONNECT, then we still have to do something to record the error
     * for these recipients....
     *
     * If we haven't yet set primary_err then all of the mx hints were either
     * not proper MX RRs, or none had (valid) IP addresses.
     *
     * This shouldn't be possible, but you never know....
     */
    if (!primary_err) {
	error_text = xprintf("failed to find any MX hosts with valid IP addresses for %v", addr->next_host);
	primary_err = connect_failure(addr->transport, error_text);
#if 0 /* XXX maybe not? */
 	defer_failure = FALSE;	/* force the bounce right away... */
#endif
    }
    insert_addr_list(addr, defer_failure ? defer : fail, primary_err);

    return;
}

#ifdef HAVE_BIND

/*
 * tcpsmtp_bind_addr -- call bind_addr() for next_host
 *
 * return:
 *	SUCCEED if DNS lookups OK
 *	FAIL if DNS failed (addresses moved to fail or defer as appropriate)
 */
static int
tcpsmtp_bind_addr(addr, defer, fail)
    struct addr *addr;
    struct addr **defer;
    struct addr **fail;
{
    struct tcpsmtp_private *priv;
    struct rt_info rt_info;
    struct error *error;
    struct addr **notnow;		/* list to move to if problems */
    struct transport_hints **h;
    char *what;
    int result;

    priv = (struct tcpsmtp_private *)addr->transport->private;

    rt_info.next_host = NULL;
    rt_info.route = NULL;
    rt_info.transport = NULL;
    rt_info.tphint_list = NULL;

    what = xprintf("transport %v", addr->transport->name);
    result = bind_addr(addr->next_host, addr->transport->flags,
                       &priv->bindlib_attr, what, &rt_info, &error);
    xfree(what);
    if (rt_info.next_host) {
        xfree(rt_info.next_host);
    }
    if (rt_info.route) {
        xfree(rt_info.route);
    }

    notnow = NULL;
    switch (result) {
    case DB_SUCCEED:
        /* Found a successful match.  Search for end of hint list */
	for (h = &addr->tphint_list; *h; h = &(*h)->succ) {
            continue;
	}
        /* Append hints to address's list. */
        *h = rt_info.tphint_list;
        break;

    case DB_NOMATCH:
        /*
	 * No match was found -- don't do anything rash just yet as there may
	 * still be hope we can find an IP address yet.
	 */
        break;

    case DB_FAIL:
        /* The address should be failed, with an error of some kind. */
	error->info |= ERR_NSOWNER;
        notnow = fail;
        break;

    case DB_AGAIN:
        /* Routing for this address should be reattempted later. */
        notnow = defer;
	break;

    case FILE_NOMATCH:
        /* The file was not found, don't match any addresses. */
        break;

    case FILE_FAIL:
        /* Permanent router error, this is a configuration error. */
        error->info |= ERR_CONFERR;
        notnow = fail;
        break;

    case FILE_AGAIN:
        /* Temporary router database error, retry all addresses. */
        notnow = defer;
        break;
    }

    if (notnow) {
        insert_addr_list(addr, notnow, error);
        return FAIL;
    }

    return SUCCEED;
}

#endif  /* HAVE_BIND */

/*
 * tcpsmtp_internal -- the guts of sending to a given host
 *
 * return:
 *	SMTP_SUCCEED on successful message send
 *
 *		- each "addr" is moved to "succeed" if sent successfully, or
 *		  moved to "defer" or "failure" if error encountered during
 *		  delivery.
 *
 *	SMTP_FAIL if the connection should not be retried
 *
 *		- "addr" is moved to "fail".
 *
 *	SMTP_AGAIN if the connection should be retried later
 *
 *		- "addr" is moved to "defer" or "fail" depending on "defer_failure"
 *
 *	SMTP_NOCONNECT if another MX host should be tried (or the connection
 *	should be retried later)
 *
 *		- "addr" is left alone (i.e. defer_failure is ignored).
 *
 *	ep is always set if return != SMTP_SUCCEED
 */
static int
tcpsmtp_internal(addr, succeed, defer, fail, hostname, ip_addr, family, service, defer_failure, ep)
    struct addr *addr;                  /* recipient addresses for transport */
    struct addr **succeed;              /* successful deliveries */
    struct addr **defer;                /* defer until a later queue run */
    struct addr **fail;                 /* failed deliveries */
    char *hostname;			/* name of the host to connect to */
    struct in_addr *ip_addr;		/* IP address of the host to connect to */
    short family;			/* address family */
    char *service;			/* service to use */
    int defer_failure;			/* whether or not we should keep this one */
    struct error **ep;			/* error structure */
{
    struct transport *tp = addr->transport;
    struct tcpsmtp_private *priv;
    int s = -1;				/* socket */
    int s2 = -1;			/* dup of s */
    struct smtp smtpbuf;		/* SMTP description buffer */
    char *error_text;
    int success;
    struct addr *ap;
    int try_ehlo;			/* allow EHLO, if supported */

    priv = (struct tcpsmtp_private *)tp->private;

    /*
     * NOTICE: be careful with the newlines on DEBUG() in here!!!!
     */
    DEBUG4(DBG_DRIVER_LO, "tcpsmtp driver for transport %v:\n  attempting to connect to host %v[%s]/%v...",
	   addr->transport->name, hostname, inet_ntoa(*ip_addr), service);

    /*
     * adjust the next_host for the address, so that log entries will
     * reflect the last MX host to be tried.
     */
    for (ap = addr; ap; ap = ap->succ) {
	if (ap->next_host == NULL || !EQIC(ap->next_host, hostname)) {
	    if (ap->next_host) {
		xfree(ap->next_host);
	    }
	    ap->next_host = COPY_STRING(hostname);
	}
    }
    /* Some gateways just terminate the conection when they receive an EHLO.
     * We may have to re-connect if they exhibit this bug....
     *
     * (NOTE: EHLO may not be used in smtplib.c if HAVE_EHLO is not defined.)
     */
    for (try_ehlo = 1, success = 0; !success && try_ehlo >= 0; --try_ehlo) {
	int result;

	if ((s = tcpsmtp_connect(hostname, ip_addr, family, service, &error_text)) >= 0) {
	    
	    if ((s2 = dup(s)) < 0) {
		    error_text = xprintf("dup: %s", strerror(errno));
		(void) close(s);
		s = -1;
	    }
	}
	if (s < 0) {
	    DEBUG1(DBG_DRIVER_LO, "\n    failed: %s\n", error_text);
	    *ep = connect_failure(tp, error_text);

	    return SMTP_NOCONNECT;	/* this time we mean connect() failed! */
	}

	smtpbuf.in = fdopen(s, "r");	/* XXX error check? */
	smtpbuf.out = fdopen(s2, "w");	/* XXX error check? */
	smtpbuf.server_name = hostname;
	smtpbuf.short_timeout = priv->short_timeout;
	smtpbuf.long_timeout = priv->long_timeout;
	smtpbuf.nl = "\r\n";		/* XXX why pass this around? */
	tp->flags |= PUT_CRLF;
	smtpbuf.tp = tp;
	smtpbuf.max_size = 0;

	DEBUG(DBG_DRIVER_LO, "connected\n");

	/* log every successful connection we make for posterity */
	write_log(WRITE_LOG_SYS,
		  "opened %v connection to %v[%s]",
		  service,
		  hostname,
		  inet_ntoa(*ip_addr));

	DEBUG(DBG_DRIVER_LO, "    initiating SMTP conversation...");
	switch ((result = smtp_startup(&smtpbuf, ep, try_ehlo))) {
	case SMTP_SUCCEED:
	    DEBUG(DBG_DRIVER_LO, " initiated, about to deliver...");
	    success = 1;		/* we're connected! */
	    break;

	case SMTP_AGAIN:		/* server says try again */
	    DEBUG1(DBG_DRIVER_LO, "\n  SMTP startup failed with a temporary reject:\n    %v\n", (*ep)->message);
	    (void) fclose(smtpbuf.in);
	    (void) fclose(smtpbuf.out);
	    insert_addr_list(addr, defer_failure ? defer : fail, *ep);

	    return SMTP_AGAIN;		/* SMTP server found -- skip rest ala Postfix */
	    /* NOTREACHED */

	case SMTP_NOCONNECT:		/* read timeout or disconnect */
	    DEBUG1(DBG_DRIVER_LO, "\n  link broken by:\n    %v\n", (*ep)->message);
	    (void) fclose(smtpbuf.in);
	    (void) fclose(smtpbuf.out);

	    return SMTP_NOCONNECT;
	    /* NOTREACHED */

	case SMTP_EHLO_FAIL:		/* server hung up on us! */
	    DEBUG1(DBG_DRIVER_LO, "\n  link to '%v' broken by EHLO\n", hostname);
	    (void) fclose(smtpbuf.in);
	    (void) fclose(smtpbuf.out);
	    /*
	     * write a message to the log file because it is interesting
	     * to know which mailers exhibit this bug....
	     */
	    write_log(WRITE_LOG_SYS, "link to '%v[%s]' broken by EHLO!",
		      hostname,
		      inet_ntoa(*ip_addr));

	    break;			/* back around to try again without EHLO */

	case SMTP_FAIL:			/* remote server says "no!"... */
	    DEBUG1(DBG_DRIVER_LO, "\n  SMTP startup failed with permanent reject:\n    %v\n", (*ep)->message);
	    (void) fclose(smtpbuf.in);
	    (void) fclose(smtpbuf.out);
	    /* bouncy... bouncy... */
	    insert_addr_list(addr, fail, *ep);

	    return SMTP_FAIL;
	    /* NOTREACHED */

	default:			/* defensive programming! */
	    DEBUG1(DBG_DRIVER_LO,
		   "\n  WARNING!  INTERNAL ERROR DETECTED:\n    %v.\n",
		   (*ep && (*ep)->message) ? (*ep)->message : "(no error message given)");
	    (void) fclose(smtpbuf.in);
	    (void) fclose(smtpbuf.out);
	    write_log(WRITE_LOG_PANIC, "IMPOSSIBLE result (%d) from smtp_startup() for %v: %s",
		      result, hostname,
		      (*ep && (*ep)->message) ? (*ep)->message : "(no error message given)");
	    insert_addr_list(addr, defer_failure ? defer : fail, *ep);

	    return SMTP_AGAIN;
	    /* NOTREACHED */
	}
    }

    if (dont_deliver) {
        insert_addr_list(addr, succeed, (struct error *) NULL);
	DEBUG(DBG_DRIVER_LO, "\n    DEBUGGING -- delivery FAKED, saying goodbye...")
        smtp_shutdown(&smtpbuf);
    } else {
	success = smtp_send(&smtpbuf, addr, succeed, defer, fail, defer_failure, ep);
	if (success == SUCCEED) {
	    DEBUG(DBG_DRIVER_LO, "\n    delivery successful, saying goodbye...")
	    smtp_shutdown(&smtpbuf);
	    DEBUG(DBG_DRIVER_LO, " done.\n")
	} else {
	    /*
	     * note we are not defensive here -- the code in smtp_send() is
	     * quite simple to verify manually since it can only return SUCCEED
	     * or FAIL; and in this case we only dereference when debugging.
	     */
	    DEBUG1(DBG_DRIVER_LO, "\n    delivery FAILED: %v\n", (*ep)->message);
	}
    }

    /* all done */
    (void) fclose(smtpbuf.in);
    (void) fclose(smtpbuf.out);

    /*
     * Errors in the smtp_send() phase should normally only affect the specific
     * recipients for this message and if we returned a failure code here then
     * a host retry file would be created, and we don't want that because since
     * there isn't really anything necessarily wrong with the destination host
     * itself a retry file would cause all mail to that host to be delayed.
     */
    return SMTP_SUCCEED;
}

/*
 * tpb_tcpsmtp - read the configuration file attributes
 */
char *
tpb_tcpsmtp(tp, attrs)
    struct transport *tp;               /* transport entry being defined */
    struct attribute *attrs;            /* list of per-driver attributes */
{
    char *error;
    static struct tcpsmtp_private tcpsmtp_template = {
        5 * 60,                         /* short timeout, 5 minutes */
        2 * 60 * 60,                    /* long timeout, 2 hours */
        "smtp",                         /* use the "smtp" service */
#ifdef HAVE_BIND
        BIND_TEMPLATE_ATTRIBUTES,
#endif
    };
    struct tcpsmtp_private *priv;       /* new tcpsmtp_private structure */
    char *fn = xprintf("tcpsmtp transport: %v", tp->name);

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

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

    return NULL;
}



/*
 * tpp_tcpsmtp - dump the configuration attributes
 */
void
tpp_tcpsmtp(f, tp)
     FILE * f;
     struct transport *tp;
{
    struct tcpsmtp_private *priv;

    (void) dump_standard_config(f,
				tp->private,
				tp->name,
				tp->flags,
				tcpsmtp_attributes,
				end_tcpsmtp_attributes);
    /* Deal with the proc config attributes */
    priv = (struct tcpsmtp_private *) tp->private;
    fprintf(f, "\tshort_timeout=%s,\n", ltoival((time_t) priv->short_timeout));
    fprintf(f, "\tlong_timeout=%s,\n", ltoival((time_t) priv->long_timeout));
}



static char *
set_short_timeout(struct_p, attr)
    char *struct_p;                     /* passed private structure */
    struct attribute *attr;             /* parsed attribute */
{
    struct tcpsmtp_private *priv = (struct tcpsmtp_private *)struct_p;

    return set_timeout(&priv->short_timeout, attr);
}

static char *
set_long_timeout(struct_p, attr)
    char *struct_p;                     /* passed private structure */
    struct attribute *attr;             /* parsed attribute */
{
    struct tcpsmtp_private *priv = (struct tcpsmtp_private *)struct_p;

    return set_timeout(&priv->long_timeout, attr);
}

static char *
set_timeout(timeout, attr)
    time_t *timeout;			/* set this timeout variable */
    struct attribute *attr;		/* parsed attribute */
{
    time_t l;

    l = ivaltol(attr->value);
    if (l < 0) {
	return xprintf("%s: %v: malformed interval",
		       attr->name, attr->value);
    }
    *timeout = l;
    if (*timeout != l) {		/* impossible? */
        return xprintf("%s: %v: interval too large", attr->name, attr->value);
    }

    return NULL;
}


/*
 * tcpsmtp_connect - return a socket connected to the remote host
 */
/* ARGSUSED */
static int
tcpsmtp_connect(remote_host, ip_addr, family, service, error)
    char *remote_host GCC_UNUSED_HACK;
    struct in_addr *ip_addr;
    int family;
    char *service;
    char **error;
{
    static in_port_t port = 0;		/* port to connect to */
    struct servent *smtp_service;       /* service entry */
    struct sockaddr_in saddrin;		/* inet socket address */
    static char *save_error = NULL;     /* keep handle to free error msgs */
    int s;                              /* socket */
    char *error_text;

    if (isdigit((int) *service)) {
        error_text = NULL;
        port = htons((in_port_t) c_atol(service, &error_text));
	/* XXX we should do more range checking than just this! */
        if (error_text) {
            *error = xprintf("invalid port: %v: %s", service, error_text);
            return -1;
        }
    } else if (port == 0) {		/* port is static -- only do this once! */
        smtp_service = getservbyname(service, "tcp");
        if (! smtp_service) {
            *error = xprintf("service name %v not found", service);
            return -1;
        }
        port = smtp_service->s_port;
    }

    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        if (save_error) {
            xfree(save_error);
        }
        *error = save_error = xprintf("socket: %s", strerror(errno));
        return -1;
    }

    if (sending_name) {
	struct hostent *hostentp;	/* XXX should maybe cache this? */
	struct sockaddr_in lsaddrin;	/* local (source) inet socket address */

	if (!(hostentp = gethostbyname(sending_name))) {
	    if (save_error) {
		xfree(save_error);
	    }
	    *error = save_error = xprintf("gethostbyname(%v): %s", sending_name, hstrerror(h_errno));
	    return -1;
	}
	memset((char *) &lsaddrin, '\0', sizeof(lsaddrin));
	memcpy((char *) &lsaddrin.sin_addr, hostentp->h_addr_list[0], sizeof(struct in_addr));
	lsaddrin.sin_family = family;
	lsaddrin.sin_port = 0;		/* any old port will do.... */
	DEBUG2(DBG_DRIVER_MID, "\n   binding socket to local source addr (%v)[%s]... ", sending_name, inet_ntoa(lsaddrin.sin_addr));
	if (bind(s, (struct sockaddr *) &lsaddrin, (socklen_t) sizeof(lsaddrin)) < 0) {
	    if (save_error) {
		xfree(save_error);
	    }
	    *error = save_error = xprintf("bind(%v[%s]): %s", sending_name, inet_ntoa(lsaddrin.sin_addr), strerror(errno));
	    return -1;
	}
    }

    memset((char *) &saddrin, '\0', sizeof(saddrin));
    saddrin.sin_addr = *ip_addr;
    saddrin.sin_family = family;
    saddrin.sin_port = port;

    DEBUG3(DBG_DRIVER_HI, "\n   connecting socket to %s[%s]/%d... ",
	   remote_host, inet_ntoa(saddrin.sin_addr), ntohs(saddrin.sin_port));

    if (connect(s, (struct sockaddr *) &saddrin, (socklen_t) sizeof(saddrin)) < 0) {
        if (save_error) {
            xfree(save_error);
        }
        *error = save_error = xprintf("connect(%s:%s): %s", remote_host, service, strerror(errno));
        (void) close(s);
        return -1;
    }

    return s;
}

static struct error *
connect_failure(tp, connect_error_text)
    struct transport *tp;
    const char *connect_error_text;
{
    char *error_text;

    /*
     * ERR_148 - smtp connection failure
     *
     * DESCRIPTION
     *      We failed to connect to the smtp service of the remote
     *      host.  The reason is stored in `error'.
     *
     * ACTIONS
     *      The input addresses are deferred.
     *
     * RESOLUTION
     *      Hopefully we will connect on a retry.
     */
    error_text = xprintf("transport %v: %s", tp->name, connect_error_text);
    return note_error(ERR_148 | ERR_NSOWNER, error_text);
}


/*
 * fake_addr_hints - get the IP#(s) for a given host and concoct MX hints
 *
 * if the remote host name is of the literal form, eg. "[192.2.12.3]" then use
 * an explicit inet address.
 */
static struct transport_hints *
fake_addr_hints(remote_host, errorp)
    char *remote_host;			/* hostname to look up (writable) */
    const char **errorp;
{
    struct in_addr s_inet;		/* internet address */
    struct hostent hostent;		/* addr for remote host */
    struct hostent *hp;			/* addr for remote host */
    struct transport_hints *hints;
    struct mx_transport_hint *mx_hints;
    struct ipaddr_hint *ip_addr;
    int i;

    if (remote_host[0] == '[') {
        /* INET addr literal address */
        char *p = strchr(remote_host, ']');

	/*
	 * Note that because of parsing in addr.c:check_target_and_remainder()
	 * there shouldn't be any possibility of errors by this point but we'll
	 * check things just in case...
	 */
        if (p == NULL || p[1] != '\0') {
            *errorp = "Invalid literal host address form";
            return NULL;
        } else {
            *p = '\0';
	    if (!inet_aton(remote_host + 1, &s_inet)) {
		*errorp = "Invalid literal host address value";
		*p = ']';
		return NULL;
	    }
            *p = ']';
        }
	hostent.h_name = remote_host;
	hostent.h_aliases = NULL;		/* XXX not perfect */
	hostent.h_addrtype = AF_INET;
	hostent.h_length = sizeof(struct in_addr);
	hostent.h_addr_list = (char **) xmalloc(2 * sizeof(char *));
	hostent.h_addr_list[0] = (char *) &(s_inet.s_addr);
	hostent.h_addr_list[1] = NULL;
	hp = &hostent;
    } else {
        hp = gethostbyname(remote_host);
        if (!hp) {
	    /*
	     * NOTE: if you get a compiler warning about hstrerror() not being
	     * declared, or one warning about "illegal combination of pointer
	     * and integer", then you are not compiling with the headers from a
	     * modern enough resolver library!  (regardless of what you think
	     * you're doing ;-)
	     */
	    *errorp = hstrerror(h_errno);
            return NULL;
        }
    }
    mx_hints = (struct mx_transport_hint *) xmalloc(sizeof(*mx_hints));
    mx_hints->preference = 0;
    mx_hints->implicit = 1;
    mx_hints->exchanger = COPY_STRING(hp->h_name);
    mx_hints->ipaddrs = NULL;

    for (i = 0; hp->h_addr_list[i]; i++) {
	struct ipaddr_hint **ah;

	ip_addr = (struct ipaddr_hint *) xmalloc(sizeof(*ip_addr));
	ip_addr->succ = NULL;
	ip_addr->hostname = COPY_STRING(hp->h_name); /* XXX not perfect */
	memcpy((char *) &(ip_addr->addr), hp->h_addr_list[i], sizeof(ip_addr->addr));
	ah = &mx_hints->ipaddrs;
	while (*ah) {
	    ah = &(*ah)->succ;
	}
	(*ah) = ip_addr;
    }
    hints = (struct transport_hints *) xmalloc(sizeof(*mx_hints));
    hints->succ = NULL;
    hints->hint_name = "mx";
    hints->private = (char *) mx_hints;

    return hints;
}

#endif /* HAVE_BSD_NETWORKING */

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