/*
#ident	"@(#)smail/src/routers:RELEASE-3_2_0_112:gethost.c,v 1.22 2000/10/02 02:06:42 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.
 */

/*
 * gethost.c:
 *	The "gethostbyname" and "gethostbyaddr" routing drivers.  These
 *	drivers call on the networking library functions of the same name.
 *	To match hostnames.
 *
 * Specifications of the gethostbyname routing driver:
 *
 *	associated transports:
 *	    No specific transport is set.  In general, this should
 *	    be used with a uux transport, such as uux or demand.
 *
 *	private data:
 *	    domain	- domain to strip from the end of the target
 *	    required	- a required domain, the target must end in this
 *
 *	private flags:
 *	    only_local_domain - must either not have a domain portion
 *			  or the domain must have been removed by "domain"
 *			  attribute.
 *
 *	algorithm:
 *	    Pass the target to gethostbyname(), perhaps stripping a domain
 *	    component as specified by the domain attribute.  If
 *	    gethostbyname() returns a match, the proper name for the host is
 *	    returned as the next_host value.  Any initial dot in the target
 *	    is ignored.
 *
 *	    Always returns one-hop routes; i.e., a next_host value is
 *	    returned, but no route.
 *
 * Specifications for the gethostbyaddr routing driver:
 *
 *	associated transports:
 *	    No specific transport is set.  In general, this should
 *	    be used with a uux transport, such as uux or demand.
 *
 *	private data: none.
 *
 *	private flags:
 *	    fail_if_error - if set, fail an address if the target is a domain
 *			  literal but the form does not match an INET
 *			  address form.
 *
 *	    check_for_local - if set, call islocalhost() on the hostname
 *			  found by gethostbyaddr() to determine if the INET
 *			  address matches the local host.  If the special
 *			  hostname 'localhost' is returned, this will always
 *			  be considered a match for the local host.  This is
 *			  set by default.
 *
 *	algorithm:
 *	    If the target is of the form [number.number...number] then
 *	    convert the number into an INET address and call gethostbyaddr()
 *	    to find a proper name for the host at that address.  If no such
 *	    host is found, then return the proper four digit form for
 *	    internet numbers, as returned by net_itoa(), in square brackets,
 *	    as the next_host value.
 *
 *	    Examples:
 *		uts.amdahl.com		- will never be matched
 *		[127.0.0.1]		- will generally return localhost
 *		[192.257]		- might return [192.0.1.1]
 */

#define NEED_SOCKETS
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include "defs.h"
#include "../smail.h"
#include "../main.h"
#include "../parse.h"
#include "../addr.h"
#include "../route.h"
#include "../transport.h"
#include "../lookup.h"
#include "../alloc.h"
#include "../dys.h"
#include "../smailconf.h"
#include "rtlib.h"
#include "gethost.h"
#include "../extern.h"
#include "../debug.h"
#include "../error.h"

#ifndef INADDR_NONE
# define INADDR_NONE		((unsigned long) (-1)) /* XXX 64bit too??? */
#endif
#ifndef INADDR_LOOPBACK
# define INADDR_LOOPBACK	((unsigned long) 0x7F000001)
#endif

/*
 * import h_errno; many systems don't define it in <netdb.h>
 */

#ifndef OBSOLETE_RESOLVER
extern int h_errno;
#endif

/* functions local to this file */
static int gethostbyname_lookup __P((struct router *, struct addr *, int, struct rt_info *, struct error **));
static int gethostbyaddr_lookup __P((struct router *, struct addr *, int, struct rt_info *, struct error **));
static int bad_form __P((struct router *, char *, struct error **));

static struct attr_table gethostbyname_attributes[] = {
    { "domain", t_string, NULL, NULL, OFFSET(gethostbyname_private, domain) },
    { "required", t_string, NULL, NULL, OFFSET(gethostbyname_private, required) },
    { "only_local_domain", t_boolean, NULL, NULL, GETHOST_ONLY_LOCAL },
};
static struct attr_table *end_gethostbyname_attributes =
    ENDTABLE(gethostbyname_attributes);

static struct attr_table gethostbyaddr_attributes[] = {
    { "fail_if_error", t_boolean, NULL, NULL, GETHOST_FAIL_IFERR },
    { "check_for_local", t_boolean, NULL, NULL, GETHOST_CHECK_LOCAL },
};
static struct attr_table *end_gethostbyaddr_attributes =
    ENDTABLE(gethostbyaddr_attributes);


/*
 * rtd_gethostbyname - call gethostbyname() for routing
 */
/* ARGSUSED */
void
rtd_gethostbyname(rp, in, out, defer, fail)
    struct router *rp;			/* router table entry */
    struct addr *in;			/* input addr structures */
    struct addr **out;			/* non-failed addr structures */
    struct addr **defer;		/* addrs to defer to a later time */
    struct addr **fail;			/* unresolvable addrs */
{
    rtd_standard(rp, in, out, defer, fail, gethostbyname_lookup);
}

/*
 * rtb_gethostbyname - read the configuration file attributes
 */
char *
rtb_gethostbyname(rp, attrs)
    struct router *rp;			/* director entry being defined */
    struct attribute *attrs;		/* list of per-driver attributes */
{
    char *error;
    static struct gethostbyname_private gethostbyname_template = {
	NULL,				/* domain */
	NULL,				/* required */
    };
    struct gethostbyname_private *priv;	/* new private structure */

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

    rp->private = (char *)priv;
    /* fill in the attributes of the private data */
    error = fill_attributes((char *)priv,
			    attrs,
			    &rp->flags,
			    gethostbyname_attributes,
			    end_gethostbyname_attributes);

    if (error) {
	return error;
    } else {
	return NULL;
    }
}

/*
 * rtp_gethostbyname - dump the configuration attributes
 */
void
rtp_gethostbyname(f, rp)
     FILE * f;
     struct router *rp;
{
    (void) dump_standard_config(f,
				rp->private,
				rp->name,
				rp->flags,
				gethostbyname_attributes,
				end_gethostbyname_attributes);
}



/*
 * gethostbyname_lookup - route to a target using gethostbyname
 *
 * Use gethostbyname() to match hosts accessible through TCP/IP.
 *
 * Return one of the following values:
 *
 *	DB_SUCCEED	Matched the target host.
 *	DB_NOMATCH	Did not match the target host.
 */
/*ARGSUSED*/
static int
gethostbyname_lookup(rp, addr, fl, rt_info, error_p)
    struct router *rp;			/* router table entry */
    struct addr *addr;			/* addr structure */
    int fl;				/* flags from rt[dv]_standard */
    struct rt_info *rt_info;		/* return route info here */
    struct error **error_p;		/* return lookup error here */
{
    struct hostent *hostentp;		/* host file entry */
    char *copy_target;			/* copy of the target */
    char *free_target;			/* free from this point */
    register char *p;			/* temp */
    struct gethostbyname_private *priv;

    priv = (struct gethostbyname_private *)rp->private;

    if (priv->required) {
	if (match_end_domain(priv->required, addr->target) == NULL) {
	    /* did not end in a required domain */
	    return DB_NOMATCH;
	}
    }

    free_target = copy_target = COPY_STRING(addr->target);
    if (copy_target[0] == '.') {
	/* ignore initial dot */
	copy_target++;
    }

    /*
     * strip any optional domain
     */
    if (priv->domain) {
	char *domain_part = match_end_domain(priv->domain, copy_target);
	if (domain_part) {
	    DEBUG1(DBG_DRIVER_HI, "strip \"%s\"\n", domain_part);
	    *domain_part = '\0';
	}
    }

    /* if we only want local names and there is any dot left, punt it */
    if ((rp->flags & GETHOST_ONLY_LOCAL) && index(copy_target, '.') != NULL) {
	xfree(free_target);
	return DB_NOMATCH;
    }

    /* look for a match, mapping upper case to lower case */
    for (p = copy_target; *p; p++) {
	if (isupper(*p)) {
	    *p = tolower(*p);
	}
    }

    hostentp = gethostbyname(copy_target);
    xfree(free_target);

    if (hostentp) {
	rt_info->next_host = hostentp->h_name;
	rt_info->matchlen = strlen(addr->target);

	return DB_SUCCEED;
    }

    return DB_NOMATCH;
}


/*
 * rtd_gethostbyaddr - call gethostbyaddr() for routing domain literals
 */
void
rtd_gethostbyaddr(rp, in, out, defer, fail)
    struct router *rp;			/* router table entry */
    struct addr *in;			/* input addr structures */
    struct addr **out;			/* non-failed addr structures */
    struct addr **defer;		/* addrs to defer to a later time */
    struct addr **fail;			/* unresolvable addrs */
{
    rtd_standard(rp, in, out, defer, fail, gethostbyaddr_lookup);
}

/*
 * rtb_gethostbyaddr - read the configuration file attributes
 */
char *
rtb_gethostbyaddr(rp, attrs)
    struct router *rp;			/* director entry being defined */
    struct attribute *attrs;		/* list of per-driver attributes */
{
    char *error;

    /* no private storage */
    rp->private = NULL;
    /* fill in the attributes of the private data */
    error = fill_attributes((char *)NULL,
			    attrs,
			    &rp->flags,
			    gethostbyaddr_attributes,
			    end_gethostbyaddr_attributes);

    if (error) {
	return error;
    } else {
	return NULL;
    }
}

/*
 * rtp_gethostbyaddr - dump the configuration attributes
 */
void
rtp_gethostbyaddr(f, rp)
     FILE * f;
     struct router *rp;
{
    (void) dump_standard_config(f,
				rp->private,
				rp->name,
				rp->flags,
				gethostbyaddr_attributes,
				end_gethostbyaddr_attributes);
}


/*
 * gethostbyaddr_lookup - route to an INET literal address.
 *
 * Match domain literal targets that specify INET addresses.
 *
 * Return one of the following values:
 *
 *	DB_SUCCEED	Matched the target.
 *	DB_NOMATCH	Did not match the target.
 *	DB_FAIL		Fail the address with the specified error.
 */
/*ARGSUSED*/
static int
gethostbyaddr_lookup(rp, addr, fl, rt_info, error_p)
    struct router *rp;			/* router table entry */
    struct addr *addr;			/* addr structure */
    int fl;				/* flags from rt[dv]_standard */
    struct rt_info *rt_info;		/* return route info here */
    struct error **error_p;		/* return lookup error here */
{
    register char *p;			/* temp */
    struct in_addr inet_s;		/* internet address */

    p = addr->target;

    DEBUG2(DBG_DRIVER_HI, "router %s: gethostbyaddr_lookup called for %s.\n", rp->name, p);

    /* check for a domain literal form */
    if (!strchr(p, '[') && !strchr(p, ']')) {
	/* definitely not a domain literal */
	DEBUG2(DBG_DRIVER_HI, "router %s: gethostbyaddr_lookup(%s) not a literal IP address.\n", rp->name, p);
	return DB_NOMATCH;
    }
    /*
     * We go to some extra trouble to check if what we've been handed is a true
     * and proper literal IP address in the expected form because that's what
     * we expect, and only what we expect.
     */
    if (*p != '[') {
	return bad_form(rp, addr->target, error_p);
    }

    p = index(p, ']');
    if (!p || *(p+1) != '\0') {
	return bad_form(rp, addr->target, error_p);
    }

    /* see if the inet library likes the address */
    *p = '\0';				/* chop off ending `]', for now */
    if (!inet_aton(addr->target + 1, &inet_s)) {
	*p = ']';			/* put it back */
	return bad_form(rp, addr->target, error_p);
    }
    *p = ']';				/* put it back */

    /*
     * provide a canonical representation in "dotted quad form"....
     */
    rt_info->next_host = xprintf("[%s]", inet_ntoa(inet_s));

    if (fl & RT_VERIFY) {
	/* Only verifying, this is far enough */
	return DB_SUCCEED;
    }

    if (rp->flags & GETHOST_CHECK_LOCAL) {
	if (islocalhost(rt_info->next_host)) {
	    struct addr *new;

	    DEBUG2(DBG_DRIVER_LO, "router %s: '%s' is a local host, will force reparse.\n", rp->name, rt_info->next_host);
	    /*
	     * It seems like a good idea to record the fact that the old
	     * address is the parent of the new address (mostly so that the
	     * parent address will appear in the logs).  The usual place where
	     * that happens is in a director (not a router), and the usual way
	     * of doing that is to allocate a new addr structure, fill the
	     * newly allocated struct with the new info, and adjust pointers to
	     * link the new struct into an appropriate list and leave the old
	     * struct in limbo.  But we don't have access to the linked list
	     * pointers, so we cheat, taking advantage of the way we know we
	     * will be called from rtd_standard, which does have access to the
	     * lists.  What we do is allocate a new addr structure, copy the
	     * old information to the newly allocated structure where it is not
	     * on any of the linked lists, and modify the current addr
	     * structure to contain the new information.  After that, the old
	     * information is in limbo and the new information is linked into a
	     * list, which is what we want.
	     */
	    new = alloc_addr();
	    /* new = addr; */
	    (void) memcpy((char *) new, (char *) addr, sizeof(*addr));
	    addr->parent = new;
	    rt_info->matchlen = strlen(addr->target);
	    rt_info->next_host = NULL; /* force reparse of remainder */
	}
    }

    return DB_SUCCEED;
}


static int
bad_form(rp, target, error_p)
    struct router *rp;			/* router structure */
    char *target;			/* malformed target */
    struct error **error_p;		/* error structure to fill in */
{
    char *error_text;

    if (rp->flags & GETHOST_FAIL_IFERR) {
	/*
	 * ERR_157 - Malformed domain literal
	 *
	 * DESCRIPTION
	 *      A domain literal did not match the form of an INET address
	 *      and the `fail_if_error' attribute was set indicating that
	 *      addresses with such targets are to be failed.
	 *
	 * ACTIONS
	 *      The address is failed and returned to the sender or to the
	 *      address owner.
	 *
	 * RESOLUTION
	 *      The user should supply a correctly formed INET domain
	 *      literal.
	 */
	error_text = xprintf("router %s: Malformed literal IP address: %s",
			     rp->name, target);

	DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

	*error_p = note_error(ERR_NSOWNER|ERR_157, error_text);

	return DB_FAIL;
    }

    DEBUG2(DBG_DRIVER_LO, "router %s: gethostbyaddr not failing malformed literal IP: %s.\n", rp->name, target);

    return DB_NOMATCH;
}
