/*
 * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
 * unrestricted use provided that this legend is included on all tape
 * media and as a part of the software program in whole or part.  Users
 * may copy or modify Sun RPC without charge, but are not authorized
 * to license or distribute it to anyone else except as part of a product or
 * program developed by the user.
 * 
 * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
 * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
 * 
 * Sun RPC is provided with no support and without any obligation on the
 * part of Sun Microsystems, Inc. to assist in its use, correction,
 * modification or enhancement.
 * 
 * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
 * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
 * OR ANY PART THEREOF.
 * 
 * In no event will Sun Microsystems, Inc. be liable for any lost revenue
 * or profits or other special, indirect and consequential damages, even if
 * Sun has been advised of the possibility of such damages.
 * 
 * Sun Microsystems, Inc.
 * 2550 Garcia Avenue
 * Mountain View, California  94043
 */

/*
 * Copyright (c) 1989, 1994 by Sun Microsystems, Inc.
 */

#ident	"@(#)netdir.c	1.18	94/08/29 SMI"

/*
 * netdir.c
 *
 * This is the library routines that do the name to address
 * translation.
 */

#include "../rpc/rpc_mt.h"		/* for MT declarations only */
#include <stdio.h>
#include <sys/types.h>
#include <errno.h>
#include <tiuser.h>
#include <netdir.h>
#include <netconfig.h>
#include <string.h>
#include <sys/file.h>
#include <dlfcn.h>
#include <rpc/trace.h>
#include <malloc.h>
#include <syslog.h>

/* messaging stuff. */

const extern char __nsl_dom[];
extern char * dgettext(const char *, const char *);

static struct translator {
	struct nd_addrlist	*(*gbn)();	/* _netdir_getbyname	*/
	struct nd_hostservlist 	*(*gba)();	/* _netdir_getbyaddr	*/
	int			(*opt)();	/* _netdir_options	*/
	char			*(*t2u)();	/* _taddr2uaddr		*/
	struct netbuf		*(*u2t)();	/* _uaddr2taddr		*/
	void			*tr_fd;		/* dyn library handle	*/
	char			*tr_name;	/* Full path		*/
	struct translator	*next;
};

/*
 * xlate_lock protects xlate_list during updates only.  The xlate_list linked
 * list is pre-pended when new entries are added, so threads that are already
 * using the list will continue correctly to the end of the list.
 */
static struct translator *xlate_list = NULL;
static mutex_t xlate_lock = DEFAULTMUTEX;

static struct translator *load_xlate();

static void *sockhandle;
static int printed;

/*
 * This is the common data (global data) that is exported
 * by public interfaces. It has been moved here from nd_comdata.c
 * which no longer exists. This fixes the problem for applications
 * that do not link directly with -lnsl but dlopen a shared object
 * that has a NEEDED dependency on -lnsl and uses the netdir
 * interface.
 */

#undef	_nderror

int	_nderror;

#ifdef _REENTRANT

int *
__nderror()
{
	static thread_key_t nderror_key = 0;
	register int *ret;

	if (_thr_main())
		return (&_nderror);
	ret = (int *)_t_tsdalloc(&nderror_key, sizeof (int));
	/* if _t_tsdalloc fails we return the address of _nderror */
	return (ret ? ret : &_nderror);
}

#define	_nderror	(*(__nderror()))

#else
static char	*__nderrbuf;
#endif /* _REENTRANT */

/*
 * Adds a translator library to the xlate_list, but first check to see if
 * it's already on the list.  Must be called while holding xlate_lock.
 */
void
add_to_xlate_list(translate)
struct translator *translate;
{
	struct translator	*t;

	for (t = xlate_list; t; t = t->next) {
		if (translate->gbn == t->gbn) {	/* already in list? */
			return;
		}
	}
	translate->next = xlate_list;
	xlate_list = translate;
}


/*
 * This routine is the main routine it resolves host/service/xprt triples
 * into a bunch of netbufs that should connect you to that particular
 * service. RPC uses it to contact the binder service (rpcbind)
 */
int
netdir_getbyname(tp, serv, addrs)
	struct netconfig	*tp;	/* The network config entry	*/
	struct nd_hostserv	*serv;	/* the service, host pair	*/
	struct nd_addrlist	**addrs; /* the answer			*/
{
	struct translator	*t;	/* pointer to translator list	*/
	struct nd_addrlist	*nn;	/* the results			*/
	char			*lr;	/* routines to try		*/
	int			i;	/* counts the routines		*/

	trace1(TR_netdir_getbyname, 0);
	for (i = 0; i < tp->nc_nlookups; i++) {
		lr = *((tp->nc_lookups) + i);
		for (t = xlate_list; t; t = t->next) {
			if (strcmp(lr, t->tr_name) == 0) {
				nn = (*(t->gbn))(tp, serv);
				if (nn) {
					*addrs = nn;
					trace1(TR_netdir_getbyname, 1);
					return (0);
				} else {
					if (_nderror < 0) {
						trace1(TR_netdir_getbyname, 1);
						return (_nderror);
					}
					break;
				}
			}
		}
		/* If we didn't find it try loading it */
		if (!t) {
			if ((t = load_xlate(lr)) != NULL) {
				/* add it to the list */
				mutex_lock(&xlate_lock);
				add_to_xlate_list(t);
				mutex_unlock(&xlate_lock);
				nn = (*(t->gbn))(tp, serv);
				if (nn) {
					*addrs = nn;
					trace1(TR_netdir_getbyname, 1);
					return (0);
				} else {
					if (_nderror < 0) {
						trace1(TR_netdir_getbyname, 1);
						return (_nderror);
					}
				}
			} else {
				if (_nderror == ND_SYSTEM) { /* retry cache */
					_nderror = ND_OK;
					i--;
					continue;
				}
			}
		}
	}
	trace1(TR_netdir_getbyname, 1);
	return (_nderror);	/* No one works */
}

/*
 * This routine is similar to the one above except that it tries to resolve
 * the name by the address passed.
 */
int
netdir_getbyaddr(tp, serv, addr)
	struct netconfig	*tp;	/* The netconfig entry		*/
	struct nd_hostservlist	**serv;	/* the answer(s)		*/
	struct netbuf		*addr;	/* the address we have		*/
{
	struct translator	*t;	/* pointer to translator list	*/
	struct nd_hostservlist	*hs;	/* the results			*/
	char			*lr;	/* routines to try		*/
	int			i;	/* counts the routines		*/

	trace1(TR_netdir_getbyaddr, 0);
	for (i = 0; i < tp->nc_nlookups; i++) {
		lr = *((tp->nc_lookups) + i);
		for (t = xlate_list; t; t = t->next) {
			if (strcmp(lr, t->tr_name) == 0) {
				hs = (*(t->gba))(tp, addr);
				if (hs) {
					*serv = hs;
					trace1(TR_netdir_getbyaddr, 1);
					return (0);
				} else {
					if (_nderror < 0) {
						trace1(TR_netdir_getbyaddr, 1);
						return (_nderror);
					}
					break;
				}
			}
		}
		/* If we didn't find it try loading it */
		if (!t) {
			if ((t = load_xlate(lr)) != NULL) {
				/* add it to the list */
				mutex_lock(&xlate_lock);
				add_to_xlate_list(t);
				mutex_unlock(&xlate_lock);
				hs = (*(t->gba))(tp, addr);
				if (hs) {
					*serv = hs;
					trace1(TR_netdir_getbyaddr, 1);
					return (0);
				} else {
					if (_nderror < 0) {
						trace1(TR_netdir_getbyaddr, 1);
						return (_nderror);
					}
				}
			} else {
				if (_nderror == ND_SYSTEM) { /* retry cache */
					_nderror = ND_OK;
					i--;
					continue;
				}
			}
		}
	}
	trace1(TR_netdir_getbyaddr, 1);
	return (_nderror);	/* No one works */
}

/*
 * This is the library routine to do transport specific stuff.
 * The code is same as the other similar routines except that it does
 * not bother to try whole bunch of routines since if the first
 * libray cannot resolve the option, then no one can.
 */
int
netdir_options(tp, option, fd, par)
	struct netconfig	*tp;	/* the netconfig entry		*/
	int			option;	/* option number		*/
	int			fd;	/* open file descriptor		*/
	char			*par;	/* parameters if any		*/
{
	struct translator	*t;	/* pointer to translator list	*/
	char			*lr;	/* routines to try		*/
	int			i;	/* counts the routines		*/

	trace3(TR_netdir_options, 0, option, fd);
	for (i = 0; i < tp->nc_nlookups; i++) {
		lr = *((tp->nc_lookups) + i);
		for (t = xlate_list; t; t = t->next) {
			if (strcmp(lr, t->tr_name) == 0) {
				trace3(TR_netdir_options, 1, option, fd);
				return ((*(t->opt))(tp, option, fd, par));
			}
		}
		/* If we didn't find it try loading it */
		if (!t) {
			if ((t = load_xlate(lr)) != NULL) {
				/* add it to the list */
				mutex_lock(&xlate_lock);
				add_to_xlate_list(t);
				mutex_unlock(&xlate_lock);
				trace3(TR_netdir_options, 1, option, fd);
				return ((*(t->opt))(tp, option, fd, par));
			} else {
				if (_nderror == ND_SYSTEM) { /* retry cache */
					_nderror = ND_OK;
					i--;
					continue;
				}
			}
		}
	}
	trace3(TR_netdir_options, 1, option, fd);
	return (_nderror);	/* No one works */
}

/*
 * This is the library routine for translating universal addresses to
 * transport specific addresses. Again it uses the same code as above
 * to search for the appropriate translation routine. Only it doesn't
 * bother trying a whole bunch of routines since either the transport
 * can translate it or it can't.
 */
struct netbuf *
uaddr2taddr(tp, addr)
	struct netconfig	*tp;	/* the netconfig entry		*/
	char			*addr;	/* The address in question	*/
{
	struct translator	*t;	/* pointer to translator list 	*/
	struct netbuf		*x;	/* the answer we want 		*/
	char			*lr;	/* routines to try		*/
	int			i;	/* counts the routines		*/

	trace1(TR_uaddr2taddr, 0);
	for (i = 0; i < tp->nc_nlookups; i++) {
		lr = *((tp->nc_lookups) + i);
		for (t = xlate_list; t; t = t->next) {
			if (strcmp(lr, t->tr_name) == 0) {
				x = (*(t->u2t))(tp, addr);
				if (x) {
					trace1(TR_uaddr2taddr, 1);
					return (x);
				}
				if (_nderror < 0) {
					trace1(TR_uaddr2taddr, 1);
					return (0);
				}
			}
		}
		/* If we didn't find it try loading it */
		if (!t) {
			if ((t = load_xlate(lr)) != NULL) {
				/* add it to the list */
				mutex_lock(&xlate_lock);
				add_to_xlate_list(t);
				mutex_unlock(&xlate_lock);
				x = (*(t->u2t))(tp, addr);
				if (x) {
					trace1(TR_uaddr2taddr, 1);
					return (x);
				}
				if (_nderror < 0) {
					trace1(TR_uaddr2taddr, 1);
					return (0);
				}
			} else {
				if (_nderror == ND_SYSTEM) { /* retry cache */
					_nderror = ND_OK;
					i--;
					continue;
				}
			}
		}
	}
	trace1(TR_uaddr2taddr, 1);
	return (0);	/* No one works */
}

/*
 * This is the library routine for translating transport specific
 * addresses to universal addresses. Again it uses the same code as above
 * to search for the appropriate translation routine. Only it doesn't
 * bother trying a whole bunch of routines since either the transport
 * can translate it or it can't.
 */
char *
taddr2uaddr(tp, addr)
	struct netconfig	*tp;	/* the netconfig entry		*/
	struct netbuf		*addr;	/* The address in question	*/
{
	struct translator	*t;	/* pointer to translator list	*/
	char			*lr;	/* routines to try		*/
	char			*x;	/* the answer			*/
	int			i;	/* counts the routines		*/

	trace1(TR_taddr2uaddr, 0);
	for (i = 0; i < tp->nc_nlookups; i++) {
		lr = *((tp->nc_lookups) + i);
		for (t = xlate_list; t; t = t->next) {
			if (strcmp(lr, t->tr_name) == 0) {
				x = (*(t->t2u))(tp, addr);
				if (x) {
					trace1(TR_taddr2uaddr, 1);
					return (x);
				}
				if (_nderror < 0) {
					trace1(TR_taddr2uaddr, 1);
					return (0);
				}
			}
		}
		/* If we didn't find it try loading it */
		if (!t) {
			if ((t = load_xlate(lr)) != NULL) {
				/* add it to the list */
				mutex_lock(&xlate_lock);
				add_to_xlate_list(t);
				mutex_unlock(&xlate_lock);
				x = (*(t->t2u))(tp, addr);
				if (x) {
					trace1(TR_taddr2uaddr, 1);
					return (x);
				}
				if (_nderror < 0) {
					trace1(TR_taddr2uaddr, 1);
					return (0);
				}
			} else {
				if (_nderror == ND_SYSTEM) { /* retry cache */
					_nderror = ND_OK;
					i--;
					continue;
				}
			}
		}
	}
	trace1(TR_taddr2uaddr, 1);
	return (0);	/* No one works */
}

/*
 * This is the routine that frees the objects that these routines allocate.
 */
void
netdir_free(ptr, type)
	void	*ptr;	/* generic pointer	*/
	int	type;	/* thing we are freeing */
{
	struct netbuf		*na;
	struct nd_addrlist	*nas;
	struct nd_hostserv	*hs;
	struct nd_hostservlist	*hss;
	int			i;

	trace2(TR_netdir_free, 0, type);
	if (ptr == NULL) {
		trace2(TR_netdir_free, 1, type);
		return;
	}
	switch (type) {
	case ND_ADDR :
		na = (struct netbuf *) ptr;
		if (na->buf)
			free(na->buf);
		free((char *)na);
		break;

	case ND_ADDRLIST :
		nas = (struct nd_addrlist *) ptr;
		for (na = nas->n_addrs, i = 0; i < nas->n_cnt; i++, na++) {
			if (na->buf)
				free(na->buf);
		}
		free((char *)nas->n_addrs);
		free((char *)nas);
		break;

	case ND_HOSTSERV :
		hs = (struct nd_hostserv *) ptr;
		if (hs->h_host)
			free(hs->h_host);
		if (hs->h_serv)
			free(hs->h_serv);
		free((char *)hs);
		break;

	case ND_HOSTSERVLIST :
		hss = (struct nd_hostservlist *) ptr;
		for (hs = hss->h_hostservs, i = 0; i < hss->h_cnt; i++, hs++) {
			if (hs->h_host)
				free(hs->h_host);
			if (hs->h_serv)
				free(hs->h_serv);
		}
		free((char *)hss->h_hostservs);
		free((char *)hss);
		break;

	default :
		_nderror = ND_UKNWN;
		break;
	}
	trace2(TR_netdir_free, 1, type);
}

/*
 * load_xlate is a routine that will attempt to dynamically link in the
 * file specified by the network configuration structure.
 */
static struct translator *
load_xlate(name)
	char	*name;		/* file name to load */
{
	struct translator	*t;
	static struct xlate_list {
		char *library;
		struct xlate_list *next;
	} *xlistp = NULL;
	struct xlate_list *xlp, **xlastp;
	static mutex_t xlist_lock = DEFAULTMUTEX;

	trace1(TR_load_xlate, 0);
	mutex_lock(&xlist_lock);
	/*
	 * We maintain a list of libraries we have loaded.  Loading a library
	 * twice is double-plus ungood!
	 */
	for (xlp = xlistp, xlastp = &xlistp; xlp != NULL;
			xlastp = &xlp->next, xlp = xlp->next) {
		if (xlp->library != NULL) {
			if (strcmp(xlp->library, name) == 0) {
				_nderror = ND_SYSTEM;	/* seen this lib */
				mutex_unlock(&xlist_lock);
				trace1(TR_load_xlate, 1);
				return (0);
			}
		}
	}
	t = (struct translator *) malloc(sizeof (struct translator));
	if (!t) {
		_nderror = ND_NOMEM;
		mutex_unlock(&xlist_lock);
		trace1(TR_load_xlate, 1);
		return (0);
	}
	t->tr_name = strdup(name);
	if (!t->tr_name) {
		_nderror = ND_NOMEM;
		free((char *)t);
		mutex_unlock(&xlist_lock);
		trace1(TR_load_xlate, 1);
		return (NULL);
	}

	t->tr_fd = dlopen(name, RTLD_LAZY);
	if (t->tr_fd == NULL) {
		_nderror = ND_OPEN;
		goto error;
	}

	/* Resolve the getbyname symbol */
	t->gbn = (struct nd_addrlist *(*)())dlsym(t->tr_fd,
				"_netdir_getbyname");
	if (!(t->gbn)) {
		_nderror = ND_NOSYM;
		goto error;
	}

	/* resolve the getbyaddr symbol */
	t->gba = (struct nd_hostservlist *(*)())dlsym(t->tr_fd,
				"_netdir_getbyaddr");
	if (!(t->gba)) {
		_nderror = ND_NOSYM;
		goto error;
	}

	/* resolve the taddr2uaddr symbol */
	t->t2u = (char *(*)())dlsym(t->tr_fd, "_taddr2uaddr");
	if (!(t->t2u)) {
		_nderror = ND_NOSYM;
		goto error;
	}

	/* resolve the uaddr2taddr symbol */
	t->u2t = (struct netbuf *(*)())dlsym(t->tr_fd, "_uaddr2taddr");
	if (!(t->u2t)) {
		_nderror = ND_NOSYM;
		goto error;
	}

	/* resolve the netdir_options symbol */
	t->opt = (int (*)())dlsym(t->tr_fd, "_netdir_options");
	if (!(t->opt)) {
		_nderror = ND_NOSYM;
		goto error;
	}
	/*
	 * Add this library to the list of loaded libraries.
	 */
	*xlastp = (struct xlate_list *)malloc(sizeof (struct xlate_list));
	if (*xlastp == NULL) {
		_nderror = ND_NOMEM;
		goto error;
	}
	(*xlastp)->library = strdup(name);
	if ((*xlastp)->library == NULL) {
		_nderror = ND_NOMEM;
		free(*xlastp);
		goto error;
	}
	(*xlastp)->next = NULL;
	mutex_unlock(&xlist_lock);
	trace1(TR_load_xlate, 1);
	return (t);
error:
	if (t->tr_fd != NULL)
		dlclose(t->tr_fd);
	free(t->tr_name);
	free((char *)t);
	mutex_unlock(&xlist_lock);
	trace1(TR_load_xlate, 1);
	return (NULL);
}


#define	NDERR_BUFSZ	512

#ifdef _REENTRANT
static char *
buf()
{
	static thread_key_t nderrbuf_key = 0;
	char *__nderrbuf = 0;

	trace1(TR___buf, 0);
	if (_thr_getspecific(nderrbuf_key, (void *)&__nderrbuf) != 0) {
		if (_thr_keycreate(&nderrbuf_key, free) != 0) {
			return (0);
		}
	}
	if (!__nderrbuf) {
		if (_thr_setspecific(nderrbuf_key, (void **)(__nderrbuf =
		    (char *)malloc(NDERR_BUFSZ))) != 0) {
			if (__nderrbuf)
				(void) free(__nderrbuf);
			return (0);
		}
		__nderrbuf[0] = '\0';	/* initialize to NULL string */
	}
	trace1(TR___buf, 1);
	return (__nderrbuf);
}
#else
static char *
buf()
{
	trace1(TR___buf, 0);
	if (__nderrbuf == NULL)
		__nderrbuf = (char *)malloc(NDERR_BUFSZ);
	trace1(TR___buf, 1);
	return (__nderrbuf);
}
#endif /* _REENTRANT */

/*
 * This is a routine that returns a string related to the current
 * error in _nderror.
 */
char *
netdir_sperror()
{
	char	*str;
	static char buf_main[NDERR_BUFSZ];

	trace1(TR_netdir_sperror, 0);
	if (_thr_main())
		str = buf_main;
	else {
		str = buf();
		if (str == NULL) {
			trace1(TR_netdir_sperror, 1);
			return (NULL);
		}
	}
	switch (_nderror) {
	case ND_NOMEM :
		(void) sprintf(str,
			dgettext(__nsl_dom, "n2a: memory allocation failed"));
		break;
	case ND_OK :
		(void) sprintf(str,
			dgettext(__nsl_dom, "n2a: successful completion"));
		break;
	case ND_NOHOST :
		(void) sprintf(str,
			dgettext(__nsl_dom, "n2a: hostname not found"));
		break;
	case ND_NOSERV :
		(void) sprintf(str,
			dgettext(__nsl_dom, "n2a: service name not found"));
		break;
	case ND_NOSYM :
		(void) sprintf(str, "%s : %s ",
			dgettext(__nsl_dom,
			"n2a: symbol missing in shared object"),
			dlerror());
		break;
	case ND_OPEN :
		(void) sprintf(str, "%s - %s ",
			dgettext(__nsl_dom, "n2a: couldn't open shared object"),
			dlerror());
		break;
	case ND_ACCESS :
		(void) sprintf(str,
			dgettext(__nsl_dom,
			"n2a: access denied for shared object"));
		break;
	case ND_UKNWN :
		(void) sprintf(str,
			dgettext(__nsl_dom,
			"n2a: attempt to free unknown object"));
		break;
	case ND_BADARG :
		(void) sprintf(str,
			dgettext(__nsl_dom,
			"n2a: bad arguments passed to routine"));
		break;
	case ND_NOCTRL:
		(void) sprintf(str,
			dgettext(__nsl_dom, "n2a: unknown option passed"));
		break;
	case ND_FAILCTRL:
		(void) sprintf(str,
			dgettext(__nsl_dom, "n2a: control operation failed"));
		break;
	case ND_SYSTEM:
		(void) sprintf(str, "%s: %s",
			dgettext(__nsl_dom, "n2a: system error"),
			strerror(errno));
		break;
	default :
		(void) sprintf(str, "%s#%d",
			dgettext(__nsl_dom, "n2a: unknown error "), _nderror);
		break;
	}
	trace1(TR_netdir_sperror, 1);
	return (str);
}

/*
 * This is a routine that prints out strings related to the current
 * error in _nderror. Like perror() it takes a string to print with a
 * colon first.
 */
void
netdir_perror(s)
	char	*s;
{
	char	*err;

	trace1(TR_netdir_perror, 0);
	err = netdir_sperror();
	fprintf(stderr, "%s: %s\n", s, err ? err : "n2a: error");
	trace1(TR_netdir_perror, 1);
	return;
}
