/*
 * Copyright (c) 1986-1992 by Sun Microsystems Inc.
 */


/*
 * This module implements the gethostbyYY and XXXhostent user interface.
 * The gethostbyYY routines are implemented in terms of the netdir_getbyYY
 * interface, whereas the XXXhostent implementation goes through
 * the modules which implement the back-ends for each naming system in
 * the order specified by the switch configuration file, until the name is
 * resolved, a hard error encountered or it runs out of all modules.
 */

#include <sys/types.h>
#include <rpc/trace.h>
#include <netdb.h>
#include <sys/tiuser.h>
#include <limits.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/file.h>
#include <dlfcn.h>
#include <netdir.h>
#include <nsswitch.h>
#include <stdlib.h>
#include <malloc.h>

int h_errno;

static struct translator {
	struct hostent *(*hpgetent)();
	void *(*hpsetent)();
	void *(*hpendent)();
	char	module_name[_POSIX_PATH_MAX];
	void	*module_handle;
	struct translator	*next;
};

/* default policy is the same as in SunOS 4.1 */
#define	DEF_ACTION {1, 0, 0, 0}
static struct __nsw_lookup lkp2 = { "nis", DEF_ACTION, NULL, NULL};
static struct __nsw_lookup lkp1 = { "files", DEF_ACTION, NULL, &lkp2};
static struct __nsw_switchconfig hosts_default = { 0, "hosts", 2, &lkp1 };

static struct translator *nsxlate_list = NULL;
static struct __nsw_switchconfig *conf = NULL;
static int set_flag = 0;
static int stayopen;

/* the following two are directly set/used only by the *ent() routines */
static struct __nsw_lookup *cur_ns = NULL;
static struct translator *cur_xlate;

/*
 * Internet version.
 */

#define	HOSTMAXALIASES	20

/*
 * different than the one in netdir.h.
 * compliant with MAXALIASES in SunOS 4.1.
 */

static struct hostdata {
	char	hostalias[HOSTMAXALIASES][64];
	char	*alias_list[HOSTMAXALIASES + 1];
#define	HOSTADDRSIZE    4   /* assumed == sizeof u_long */
	char    hostaddr[MAXADDRS][HOSTADDRSIZE];
	char    *addr_list[MAXADDRS + 1];
	struct	sockaddr_in nbuf;
	struct	hostent host;
	char	hostname[MAXHOSTNAMELEN];
} *hostdata, *__hostdata();

extern struct netconfig *__rpc_getconfip();
static int err_conv(); /* converts _nderror from netdir to h_errno */

static struct translator *load_xlate();

/*
 * Internal routine to allocate hostdata on heap, instead of
 * putting lots of stuff into the data segment.
 */
static struct hostdata *
__hostdata()
{
	register struct hostdata *d = hostdata;

	trace1(TR___hostdata, 0);
	if (d == 0) {
		d = (struct hostdata *)calloc(1, sizeof (struct hostdata));
		hostdata = d;
	}
	trace1(TR___hostdata, 1);
	return (d);
}

/*
 * gethostby*() routines have been written in terms of netdir_getby*() to
 * have a uniform policy for getting hostnames. Now, this creates a weird
 * situation, that set/gethostent() routines directly follow the switch
 * configuration policy for hosts. The netdir_getby*() routines follow
 * the switch policy for hosts, if /usr/lib/switch.so exists as a nametoaddr
 * library in the /etc/netconfig file.
 */

struct hostent *
gethostbyname(nam)
	register const char *nam;
{
	register struct hostdata *d = __hostdata();
	struct nd_hostserv service;
	struct netconfig *nconf;
	struct nd_addrlist *addrs;
	struct netbuf *na;
	struct sockaddr_in *sa;
	int count, i, neterr;

	trace1(TR_gethostbyname, 0);
	if (d == 0) {
		trace1(TR_gethostbyname, 1);
		return ((struct hostent*)NULL);
	}
	service.h_host = (char *)nam;
	service.h_serv = NULL; /* netdir_getbyname of tcpip optimized here */
	if ((nconf = __rpc_getconfip("udp")) == NULL &&
	    (nconf = __rpc_getconfip("tcp")) == NULL) {
		trace1(TR_gethostbyname, 1);
		return ((struct hostent *)NULL);
	} else if ((neterr = netdir_getbyname(nconf, &service, &addrs)) != 0) {
#ifdef DEBUG
		fprintf(stderr, "gethostbyname: %s\n", netdir_sperror());
#endif
		(void) freenetconfigent(nconf);
		h_errno = err_conv(neterr);
		trace1(TR_gethostbyname, 1);
		return ((struct hostent *)NULL);
	}
	(void) freenetconfigent(nconf);

	/*
	 * build up list of all adresses in hostent form.
	 * This is INTERNET SPECIFIC.
	 */

	count = addrs->n_cnt;
	d->host.h_aliases = d->alias_list;
	d->addr_list[0] = d->hostaddr[0];
	d->host.h_addr_list = d->addr_list;
	for (na = addrs->n_addrs, i = 0; count && na && i < MAXADDRS;
		count--, na++) {
		sa = (struct sockaddr_in *)na->buf;
		d->addr_list[i] = d->hostaddr[i];
		*((u_long *)d->hostaddr[i++]) = (u_long)sa->sin_addr.s_addr;
	}
	d->addr_list[i] = NULL;
	d->host.h_name = (char *)nam;
	d->host.h_addrtype = AF_INET;
	d->host.h_length = HOSTADDRSIZE;
	*(d->host.h_aliases) = NULL; /* we can't figure out aliases here */
	netdir_free((char *)addrs, ND_ADDRLIST);
	trace1(TR_gethostbyname, 1);
	return (&d->host);
}

struct hostent *
gethostbyaddr(addr, length, type)
	const char *addr;
	register int length;
	register int type;
{
	register struct hostdata *d = __hostdata();
	struct netconfig *nconf;
	struct nd_hostservlist *addrs;
	struct nd_hostserv *hs;
	struct netbuf nbuf;
	struct sockaddr_in sa;
	struct t_info tinfo;
	int	fd, neterr, i, na;
	char *la;

	trace1(TR_gethostbyaddr, 0);
	if (d == 0) {
		trace1(TR_gethostbyaddr, 1);
		return ((struct hostent *)NULL);
	}
	sa.sin_addr.s_addr = *(u_long *)addr;
	sa.sin_family = AF_INET;
	sa.sin_port = 0; /* netdir_getbyaddr of tcpip optimized for this case */
	sa.sin_zero[0] = '\0';
	nbuf.buf = (char *)&d->nbuf;
	memmove(nbuf.buf, (char *)&sa, sizeof (sa));
	if ((nconf = __rpc_getconfip("udp")) == NULL &&
		(nconf = __rpc_getconfip("tcp")) == NULL) {
		trace1(TR_gethostbyaddr, 1);
		return ((struct hostent *)NULL);
	} else {
		if ((fd = t_open(nconf->nc_device, O_RDWR, &tinfo)) == -1) {
			(void) freenetconfigent(nconf);
			trace1(TR_gethostbyaddr, 1);
			return ((struct hostent *)NULL);
		}
		(void) t_close(fd);
		nbuf.maxlen = tinfo.addr;
		nbuf.len = nbuf.maxlen; /* ??? */
		if ((neterr = netdir_getbyaddr(nconf, &addrs, &nbuf)) != 0) {
#ifdef DEBUG
			fprintf(stderr, "gethostbyaddr: %s\n",
				netdir_sperror());
#endif
			(void) freenetconfigent(nconf);
			h_errno = err_conv(neterr);
			trace1(TR_gethostbyaddr, 1);
			return ((struct hostent *)NULL);
		}
	}
	(void) freenetconfigent(nconf);

	hs = addrs->h_hostservs;
	if (! hs) {
		h_errno = ND_NOHOST;
		return ((struct hostent *)NULL);
	}
	d->host.h_name = hs->h_host;
	(void) memcpy(d->hostname, hs->h_host, sizeof (d->hostname));
	d->host.h_name = d->hostname;
	d->addr_list[0] = d->hostaddr[0];
	d->host.h_addr_list = d->addr_list;
	memmove(d->addr_list[0], addr, length);
	d->addr_list[1] = NULL;	/* cannot figure out other addresses */
	d->host.h_addrtype = AF_INET;
	d->host.h_length = length;

	d->host.h_aliases = d->alias_list;
	/*
	 * Assumption: the netdir backends, tcpip.so and switch.so,
	 * sort the vector of (host, serv) pairs in such a way that
	 * all pairs with the same host name are contiguous.
	 */
	la = hs->h_host;
	for (i = 0, na = 0; (i < addrs->h_cnt) && (na < HOSTMAXALIASES);
			i++, hs++)
		if (strcmp(la, hs->h_host) != 0) {
			d->alias_list[na] = d->hostalias[na];
			memcpy(d->hostalias[na++], hs->h_host,
				strlen(hs->h_host));
			la = hs->h_host;
		}
	d->alias_list[na] = NULL;
	netdir_free((char *)addrs, ND_HOSTSERVLIST);
	trace1(TR_gethostbyaddr, 1);
	return (&d->host);
}

static int
err_conv(nerr)
int nerr;
{
	trace1(TR_err_conv, 0);
	switch (nerr) {
	case ND_TRY_AGAIN:
		trace1(TR_err_conv, 1);
		return (TRY_AGAIN);
	case ND_NO_RECOVERY:
		trace1(TR_err_conv, 1);
		return (NO_RECOVERY);
	case ND_NO_DATA:
		trace1(TR_err_conv, 1);
		return (NO_DATA);
	case ND_NOHOST:
		trace1(TR_err_conv, 1);
		return (HOST_NOT_FOUND);
	default:
		trace1(TR_err_conv, 1);
		return (0);
	}
}

sethostent(stay)
	int stay;
{
	enum __nsw_parse_err pserr;
	int	nserr = __NSW_SUCCESS;
	struct translator *xlate;

	trace2(TR_sethostent, 0, stay);
	stayopen |= stay;
	if (!conf &&
		(conf = __nsw_getconfig(__NSW_HOSTS_DB, &pserr)) == NULL)
		conf = &hosts_default;
	/*
	 * if gethostent was the previous call and it was in the
	 * naming service other than the "first" one, "endhostent" it
	 * and reset the current naming service to the first one.
	 */
	if (cur_ns && (cur_ns != conf->lookups)) {
		xlate = load_xlate(&nserr, cur_ns->service_name);
		if (xlate && xlate->hpendent)
			(*(xlate->hpendent))(&nserr);
	}
	/*
	 * set the first naming service as the current one, and
	 * load its op vector.
	 */
	cur_ns = conf->lookups;
	if (cur_ns) {
		cur_xlate = load_xlate(&nserr, cur_ns->service_name);
		if (cur_xlate && cur_xlate->hpsetent)
			(*(cur_xlate->hpsetent))(&nserr, stayopen);
	} else
		cur_xlate = NULL;
	set_flag = 1;
	trace2(TR_sethostent, 1, stay);
	return (0);
}

struct hostent *
gethostent()
{
	struct hostent *hp = NULL;
	int nserr;

	trace1(TR_gethostent, 0);
	if (!set_flag)
		sethostent(0);
repeat:
	nserr = __NSW_SUCCESS;
	if (!cur_xlate && cur_ns)
		cur_xlate = load_xlate(&nserr, cur_ns->service_name);
	/*
	 * success status returned by load_xlate is not the same as the
	 * success specified by the switch, hence take action only on
	 * non-success error codes from load_xlate.
	 */
	if (!cur_xlate &&
		((__NSW_ACTION(cur_ns, nserr) == __NSW_RETURN) ||
		(cur_ns->next == NULL))) {
		trace1(TR_gethostent, 1);
		return (NULL);
	}
	if (!cur_xlate ||
		(!cur_xlate->hpgetent &&
		__NSW_ACTION(cur_ns, __NSW_UNAVAIL) == __NSW_CONTINUE)) {
		cur_ns = cur_ns->next;
		nserr = __NSW_SUCCESS;
		cur_xlate = load_xlate(&nserr, cur_ns->service_name);
		goto repeat;
	}
	nserr = __NSW_SUCCESS;
	if (cur_xlate->hpgetent)
		hp = (*(cur_xlate->hpgetent))(&nserr);
	else
		nserr = __NSW_UNAVAIL;
	if (__NSW_ACTION(cur_ns, nserr) == __NSW_RETURN) {
		trace1(TR_gethostent, 1);
		return (hp);
	}
	/*
	 * The getent failed and the action points to the next naming service,
	 * or the policy setter is a weirdo and asked to continue on
	 * success. Again go to the next naming service.
	 */
	if (cur_xlate->hpendent)
		(*(cur_xlate->hpendent))(&nserr);
	if (cur_ns->next == NULL) {
		trace1(TR_gethostent, 1);
		return (NULL);
	}
	cur_ns = cur_ns->next;
	cur_xlate = load_xlate(&nserr, cur_ns->service_name);
	if (cur_xlate && cur_xlate->hpsetent)
		(*(cur_xlate->hpsetent))(&nserr, stayopen);
	goto repeat;
}

endhostent()
{
	int nserr;

	trace1(TR_endhostent, 0);
	if (cur_xlate && cur_xlate->hpendent)
		(*(cur_xlate->hpendent))(&nserr);
	cur_ns = NULL;
	cur_xlate = NULL;
	set_flag = 0;
	stayopen = 0;
	trace1(TR_endhostent, 1);
	return (0);
}

static struct translator *
load_xlate(nserr, module_name)
	int *nserr;
	char *module_name;
{
	struct  translator   *xlate;
	char    full_mod_name[_POSIX_PATH_MAX];
	char    *mp = full_mod_name;
	void    *mod_handle;

	trace1(TR_load_xlate, 0);
	for (xlate = nsxlate_list; xlate; xlate = xlate->next)
		if (strcmp(module_name, xlate->module_name) == 0) {
			trace1(TR_load_xlate, 1);
			return (xlate);
		}
	memset(mp, 0, _POSIX_PATH_MAX);
	sprintf(mp, "%s%s%s", __NSW_LIB, module_name, ".so");
	mod_handle = dlopen(mp, RTLD_LAZY);
	if (mod_handle == NULL) {
		*nserr = __NSW_UNAVAIL;
		trace1(TR_load_xlate, 1);
		return (NULL);
	}
	xlate = (struct translator *)malloc(sizeof (struct translator));
	if (xlate == NULL) {
		*nserr = __NSW_UNAVAIL; /* XXX: really means something else ! */
		trace1(TR_load_xlate, 1);
		return (NULL);
	}
	xlate->next = nsxlate_list;
	nsxlate_list = xlate;
	xlate->module_handle = mod_handle;
	strcpy(xlate->module_name, module_name);
	xlate->hpgetent = (struct hostent *(*)())dlsym(mod_handle,
							"_gethostent");
	xlate->hpsetent = (void *(*)())dlsym(mod_handle, "_sethostent");
	xlate->hpendent = (void *(*)())dlsym(mod_handle, "_endhostent");
	*nserr = __NSW_SUCCESS;
	trace1(TR_load_xlate, 1);
	return (xlate);
}
