/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * Portions Copyright (c) 1999 Apple Computer, Inc. All Rights
 * Reserved. This file contains Original Code and/or Modifications of
 * Original Code as defined in and that are subject to the Apple Public
 * Source License Version 1.1 (the "License"). You may not use this file
 * except in compliance with the License. Please obtain a copy of the
 * License at http://www.apple.com/publicsource and read it before using
 * this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*
 * Glues the library routines to the stub routines
 * Copyright (C) 1989 by NeXT, Inc.
 */
#include <libc.h>
#include <string.h>
#include <syslog.h>
#include <netinfo/ni.h>
#include <rpc/pmap_clnt.h>
#include <rpc/pmap_prot.h>
#include <rpc/xdr.h>
#include <net/if.h>
#include <ctype.h>
#include <errno.h>
#include "clib.h"
#include "sys_interfaces.h"

#define LOCAL_PORT 1033

#define NI_TIMEOUT_SHORT 5	/* 5 second timeout for transactions */
#define NI_TIMEOUT_LONG 60 	/* 60 second timeout for writes */
#define NI_TRIES 5		/* number of retries per timeout (udp only) */
#define NI_SLEEPTIME 4	 	/* 4 second sleeptime, in case of errors */
#define NI_MAXSLEEPTIME 16 	/* 16 second max sleep time */
#define NI_MAXCONNTRIES 2 	/* Try to form a connection twice before sleeping */

/* Hack for determining if an IP address is a broadcast address. -GRS */
/* Note that addr is network byte order (big endian) - BKM */
 
#define IS_BROADCASTADDR(addr)	(((unsigned char *) &addr)[0] == 0xFF)

#ifndef INADDR_LOOPBACK
#define INADDR_LOOPBACK		(u_long)0x7f000001
#endif
#define debug(msg) syslog(LOG_ERR, msg)

#define clnt_debug(ni, msg) /* do nothing */

typedef struct ni_private
{
	int naddrs;		/* number of addresses */
	struct in_addr *addrs;	/* addresses of servers - network byte order */
	int whichwrite;	/* which one of the above is the master */
	ni_name *tags;		/* tags of servers */
	int pid;		/* pid, to detect forks */
	int tsock;		/* tcp socket */
	int tport;		/* tcp local port name - host byte order */
	CLIENT *tc;		/* tcp client */
	long tv_sec;		/* timeout for this call */
	long rtv_sec;		/* read timeout - 0 if default */
	long wtv_sec;		/* write timeout - 0 if default */
	int abort;		/* abort on timeout? */
	int needwrite;		/* need to lock writes? */
	int uid;		/* user id */
	ni_name passwd;		/* password */
} ni_private;

#define NIP(ni) ((ni_private *)(ni))

#define RCALLIT(a, b, c) callit((ni_private *)(a), (void *(*)())(b), (void *)c, 0)
#define WCALLIT(a, b, c) callit((ni_private *)(a), (void *(*)())(b), (void *)c, 1)

static const ni_name NAME_NAME = "name";
static const ni_name NAME_SERVES = "serves";
static const ni_name NAME_MACHINES = "machines";
static const ni_name NAME_IP_ADDRESS = "ip_address";
static const ni_name NAME_MASTER = "master";
static const ni_name NAME_USERS = "users";
static const ni_name NAME_UID = "uid";

static const ni_name NAME_DOMAIN_SERVERS = "domain_servers";

typedef struct getreg_stuff {
	nibind_getregister_res res;
	ni_private *ni;
} getreg_stuff;

static int socket_open(struct sockaddr_in *raddr, int, int, int, int, int);
extern int bindresvport(int, struct sockaddr_in *);

/*
 * Keep track of our port, in case somebody closes our socket on us.
 */
static int
getmyport(int sock)
{
	struct sockaddr_in sin;
	int sinlen;

	sinlen = sizeof(struct sockaddr_in);
	if (getsockname(sock, (struct sockaddr *)&sin, &sinlen) != 0) return -1;

	if (sin.sin_port == 0)
	{
		if (bind(sock, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) != 0) return -1;

		sinlen = sizeof(struct sockaddr_in);
		if (getsockname(sock, (struct sockaddr *)&sin, &sinlen) != 0) return -1;
	}

	return ntohs(sin.sin_port);
}


static void
createauth(ni_private *ni)
{
	if (ni->passwd != NULL && ni->tc != NULL)
	{
		auth_destroy(ni->tc->cl_auth);
		ni->tc->cl_auth = authunix_create(ni->passwd, ni->uid, 0, 0, NULL);
	}
}


static void
fixtimeout(struct timeval *tv, long sec, int tries)
{
	tv->tv_sec = sec / tries;
	tv->tv_usec = ((sec % tries) * 1000000) / tries;
}


static void
ni_settimeout(ni_private *ni, int timeout)
{
	struct timeval tv;

	tv.tv_sec = timeout;
	tv.tv_usec = 0;
	ni->tv_sec = timeout;
	if (ni->tc != NULL) clnt_control(ni->tc, CLSET_TIMEOUT, (char *)&tv);
}


/*
 * Connect to a given address/tag
 */
static int
connectit(ni_private *ni)
{
	struct sockaddr_in sin;
	int sock, islocal;
	CLIENT *cl;
	struct timeval tv;
	enum clnt_stat stat;
	nibind_getregister_res res;
	interface_list_t *ilist;

	sock = -1;
	bzero(&sin, sizeof(sin));
	sin.sin_port = 0;
	sin.sin_family = AF_INET;

	tv.tv_sec = ni->rtv_sec == 0 ? NI_TIMEOUT_SHORT : ni->rtv_sec;
	tv.tv_usec = 0;

	ni_settimeout(ni, tv.tv_sec);
	fixtimeout(&tv, ni->tv_sec, NI_TRIES);

	/*
	 * If connecting to local domain, try using the "well-known" port first.
	 */
	islocal = 0;

	if (!strcmp(ni->tags[0], "local"))
	{
		if (ni->addrs[0].s_addr == htonl(INADDR_LOOPBACK))
		{
			islocal = 1;
		}
		else
		{
			ilist = _libinfo_ni_sys_interfaces();
			if (_libinfo_ni_sys_is_my_address(ilist, &ni->addrs[0])) islocal = 1;
			_libinfo_ni_sys_interfaces_release(ilist);
		}

		if (islocal != 0)
		{
			sin.sin_port = htons(LOCAL_PORT);
			sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
			sock = socket_open(&sin, NI_PROG, NI_VERS, ni->tv_sec, NI_TRIES, IPPROTO_TCP);
		}
	}

	/*
	 * If connecting to a domain other than the local domain, * or if connection to local didn't work with local's well-known port, * then go through portmap & nibindd to find the port and connect.
	 */
	if (sock < 0)
	{
		sin.sin_port = 0;
		sin.sin_addr = ni->addrs[0];

		sock = socket_open(&sin, NIBIND_PROG, NIBIND_VERS, ni->tv_sec, NI_TRIES, IPPROTO_UDP);
		if (sock < 0) return 0;

		cl = clntudp_create(&sin, NIBIND_PROG, NIBIND_VERS, tv, &sock);
		if (cl == NULL)
		{
			close(sock);
			return 0;
		}

		tv.tv_sec = ni->rtv_sec == 0 ? NI_TIMEOUT_SHORT : ni->rtv_sec;
		tv.tv_usec = 0;

		stat = clnt_call(cl, NIBIND_GETREGISTER, (xdrproc_t)xdr_ni_name, (char *)&ni->tags[0], (xdrproc_t)xdr_nibind_getregister_res, (char *)&res, tv);
		clnt_destroy(cl);
		close(sock);
		if (stat != RPC_SUCCESS || res.status != NI_OK) return 0;

		sin.sin_port = htons(res.nibind_getregister_res_u.addrs.tcp_port);
		sock = socket_open(&sin, NI_PROG, NI_VERS, ni->tv_sec, NI_TRIES, IPPROTO_TCP);
	}

	if (sock < 0) return 0;

	cl = clnttcp_create(&sin, NI_PROG, NI_VERS, &sock, 0, 0);
	if (cl == NULL)
	{
		close(sock);
		return 0;
	}

	clnt_control(cl, CLSET_TIMEOUT, (char *)&tv);
	ni->tc = cl;
	ni->tsock = sock;
	ni->tport = getmyport(sock);
	createauth(ni);
	fcntl(ni->tsock, F_SETFD, 1);
	return 1;
}


void
ni_setabort(void *ni, int abort)
{
	if (ni == NULL) return;

	((ni_private *)ni)->abort = abort;
}


void
ni_setwritetimeout(void *ni, int timeout)
{
	if (ni == NULL) return;

	((ni_private *)ni)->wtv_sec = timeout;
}


void
ni_setreadtimeout(void *ni, int timeout)
{
	if (ni == NULL) return;

	((ni_private *)ni)->rtv_sec = timeout;
}


void
ni_needwrite(void *ni, int needwrite)
{
	if (ni == NULL) return;

	((ni_private *)ni)->needwrite = needwrite;
}


/*
 * Returns a client handle to the NetInfo server, if it's running
 */
static int
connectlocal(ni_private *ni)
{
	int printed = 0;

	ni->naddrs = 1;
	ni->addrs = (struct in_addr *)malloc(sizeof(struct in_addr));
	ni->addrs[0].s_addr = htonl(INADDR_LOOPBACK);
	ni->tags = (ni_name *)malloc(sizeof(ni_name));
	ni->tags[0] = ni_name_dup("local");
	ni->whichwrite = 0;

	while (connectit(ni) == 0)
	{
		if (printed == 0)
		{
			syslog(LOG_ERR, "NetInfo timeout connecting to local domain, sleeping");
			printed = 1;
		}

		sleep(NI_SLEEPTIME);
		/* wait forever */
	}

	if (printed != 0) syslog(LOG_INFO, "NetInfo connection to local domain waking");

	return 1;
}


/*
 * Destroy the client handle
 */
static void
clnt_kill(CLIENT *cl, int sock, int port)
{
	int save = 0;
	int p;

	p = getmyport(sock);

	if ((sock >= 0) && (p != -1) && (p != port))
	{
		/* Somebody has the reused the socket. */
		save = 1;
	}

	if (cl != NULL)
	{
		if (cl->cl_auth != NULL) auth_destroy(cl->cl_auth);
		clnt_destroy(cl);
	}

	if (save == 0) close(sock);
}


/*
 * Reinitialize everything
 */
static void
reinit(ni_private *ni)
{
	if (ni == NULL) return;

	if (ni->tc != NULL)
	{
		clnt_kill(ni->tc, ni->tsock, ni->tport);
		ni->tc = NULL;
	}

	ni->tsock = -1;
	ni->tport = -1;
	ni->pid = getpid();
}


/*
 * Switch to a new server
 */
static void
ni_switch(ni_private *ni, ni_index which)
{
	struct in_addr tmp_addr;
	ni_name tmp_tag;

	if (ni == NULL) return;
	if (which == 0) return;

	reinit(ni);
	tmp_addr = ni->addrs[0];
	tmp_tag = ni->tags[0];

	ni->addrs[0] = ni->addrs[which];
	ni->tags[0] = ni->tags[which];

	ni->addrs[which] = tmp_addr;
	ni->tags[which] = tmp_tag;

	if (ni->whichwrite == 0) ni->whichwrite = which;
	else if (ni->whichwrite == which) ni->whichwrite = 0;
}


/*
 * Swap two servers' positions
 */
static void
ni_swap(ni_private *ni, ni_index a, ni_index b)
{
	struct in_addr tmp_addr;
	ni_name tmp_tag;

	if (a == b) return;

	tmp_addr = ni->addrs[a];
	tmp_tag = ni->tags[a];

	ni->addrs[a] = ni->addrs[b];
	ni->tags[a] = ni->tags[b];

	ni->addrs[b] = tmp_addr;
	ni->tags[b] = tmp_tag;

	if (ni->whichwrite == a) ni->whichwrite = b;
	else if (ni->whichwrite == b) ni->whichwrite = a;
}

static ni_status
ni_ping(unsigned short port, struct in_addr addr)
{
	struct sockaddr_in sin;
	int sock;
	CLIENT *cl;
 struct timeval timeout, retry;
	enum clnt_stat stat;
 
 memset(&sin, 0, sizeof(struct sockaddr_in));
 
 sin.sin_family = AF_INET;
 sin.sin_port = port;
 sin.sin_addr = addr;
 
 timeout.tv_sec = NI_TIMEOUT_SHORT;
 timeout.tv_usec = 0;
 
 retry.tv_sec = 1;
 retry.tv_usec = 0;
 
	sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
 if (sock < 0) return NI_FAILED;
 
 cl = clntudp_create(&sin, NI_PROG, NI_VERS, timeout, &sock);
 if (cl == NULL)
 {
 close(sock);
 return NI_FAILED;
 }
 
 clnt_control(cl, CLSET_RETRY_TIMEOUT, (char *)&retry);
 
 stat = clnt_call(cl, _NI_PING, (xdrproc_t)xdr_void, (char *)NULL, (xdrproc_t)xdr_void, (char *)NULL, timeout);
 clnt_destroy(cl);
 close(sock);
 
 if (stat != RPC_SUCCESS) return NI_FAILED;
 
 return NI_OK;
}

/*
 * Callback routine for multi_call
 * XXX: should save returned port numbers
 */
static bool_t
eachresult(void *vstuff, struct sockaddr_in *sin, int which)
{
	ni_status status;
	getreg_stuff *stuff = (getreg_stuff *)vstuff;

	if (stuff->res.status != NI_OK) return FALSE;

 /*
 * Actually talk to server (netinfod) to make sure it is alive
 * before switching. If we got this far, nibindd gave us a
 * port number for an address and tag. We connect to that
 * port and do a ni_root() query before accepting the server.
 */
	status = ni_ping(htons(stuff->res.nibind_getregister_res_u.addrs.udp_port), stuff->ni->addrs[which]);
 if (status != NI_OK) return FALSE;

	ni_switch(stuff->ni, which);
	return TRUE;
}


/*
 * shuffle addresses
 */
static void
shuffle(ni_private *ni)
{
	int *shuffle;
	int i, j, rfd, rv, te;
	unsigned int re;
	static int initialized = 0;

	if (ni == NULL) return;
	if (ni->naddrs <= 1) return;

	rfd = open("/dev/random", O_RDONLY, 0);
	shuffle = (int *)malloc(ni->naddrs * sizeof(int));
	for (i = 0; i < ni->naddrs; i++) shuffle[i] = i;
	for (i = 0, j = ni->naddrs; j > 0; i++, j--)
	{
		/* get a random number */
		if ((rfd < 0) || (read(rfd, &rv, sizeof(rv)) != sizeof(rv)))
		{
			/* if we could not read from /dev/random */
			if (initialized == 0)
			{
				srandom(gethostid() ^ time(NULL));
				initialized = 1;
			}
			rv = random();
		}

		re = (unsigned int)rv % j;	/* pick one of the remaining entries */
		te = shuffle[re];		/* grab the random entry */
		shuffle[re] = shuffle[j-1];	/* the last entry moves to the random slot */ 
		shuffle[j-1] = te;		/* the last slot gets the random entry */
		ni_swap(ni, re, j-1);		/* and swap the actual NI addresses */
	}

	free(shuffle);
	if (rfd > 0) close(rfd);
	return;
}


static int
rebind(ni_private *ni)
{
	enum clnt_stat stat;
	getreg_stuff stuff;
	int sleeptime = NI_SLEEPTIME;
	int printed = 0;
	int nlocal;
	int nnetwork;
	interface_list_t *ilist;
	int i;

	if (ni->naddrs == 1)
	{
		ni->whichwrite = 0;
		return 1;
	}

	/*
	 * Majka - 1994.04.27
	 * re-order the servers so that:
	 * servers on the local host are at the start of the list, then
	 * servers on the local network are next, then
	 * all other servers are next
	 */

	ilist = _libinfo_ni_sys_interfaces();

	/*
	 * shuffle addresses
	 */
	shuffle(ni);

	/*
	 * move local servers to the head of the list
	 */
	nlocal = 0;
	for (i = nlocal; i < ni->naddrs; i++)
	{
		if (_libinfo_ni_sys_is_my_address(ilist, &ni->addrs[i]))
		{
			ni_swap(ni, nlocal, i);
			nlocal++;
		}
	}

	/*
	 * move servers on this network to follow local servers
	 */
	nnetwork = nlocal;
 	for (i = nnetwork; i < ni->naddrs; i++)
	{
		if (_libinfo_ni_sys_is_my_network(ilist, &ni->addrs[i]) || IS_BROADCASTADDR(ni->addrs[i].s_addr))
		{
			ni_swap(ni, nnetwork, i);
			nnetwork++;
		}
	}

	_libinfo_ni_sys_interfaces_release(ilist);

	stuff.ni = ni;
	for (;;)
	{
		/*
		 * call local servers first
		 */
		if (nlocal > 0)
		{
			for (i = 0; i < nlocal; i++)
			{
				syslog(LOG_DEBUG, "NetInfo connect call to: %s/%s (local %d)", inet_ntoa(ni->addrs[i]), ni->tags[i], i);
			}

			stat = multi_call(nlocal, ni->addrs, NIBIND_PROG, NIBIND_VERS, NIBIND_GETREGISTER, (xdrproc_t)xdr_ni_name, (void *)ni->tags, sizeof(ni_name), (xdrproc_t)xdr_nibind_getregister_res, (void *)&stuff, eachresult, NI_TIMEOUT_SHORT);
			if (stat == RPC_SUCCESS) break;
		}

		/*
		 * call local servers and this network's servers
		 */
		if (nnetwork > nlocal)
		{
			for (i = 0; i < nnetwork; i++)
			{
				syslog(LOG_DEBUG, "NetInfo connect call to: %s/%s (network %d)", inet_ntoa(ni->addrs[i]), ni->tags[i], i);
			}

			stat = multi_call(nnetwork, ni->addrs, NIBIND_PROG, NIBIND_VERS, NIBIND_GETREGISTER, (xdrproc_t)xdr_ni_name, (void *)ni->tags, sizeof(ni_name), (xdrproc_t)xdr_nibind_getregister_res, (void *)&stuff, eachresult, NI_TIMEOUT_SHORT);
			if (stat == RPC_SUCCESS) break;
		}

		/*
		 * call all servers
		 */
		for (i = 0; i < ni->naddrs; i++)
		{
			syslog(LOG_DEBUG, "NetInfo connect call to: %s/%s (world %d)", inet_ntoa(ni->addrs[i]), ni->tags[i], i);
		}
		stat = multi_call(ni->naddrs, ni->addrs, NIBIND_PROG, NIBIND_VERS, NIBIND_GETREGISTER, (xdrproc_t)xdr_ni_name, (void *)ni->tags, sizeof(ni_name), (xdrproc_t)xdr_nibind_getregister_res, (void *)&stuff, eachresult, (ni->rtv_sec == 0) ? NI_TIMEOUT_SHORT : ni->rtv_sec);
		if (stat == RPC_SUCCESS) break;

		if (ni->abort) return 0;

		if (printed == 0)
		{
			if (ni->whichwrite >= 0)
			{
				syslog(LOG_WARNING, 
					"NetInfo connect timeout (domain with master %s/%s), sleeping", inet_ntoa(ni->addrs[ni->whichwrite]), ni->tags[ni->whichwrite]);
			}
			else
			{
				syslog(LOG_WARNING, "NetInfo connect timeout (domain with server %s/%s), sleeping", inet_ntoa(ni->addrs[0]), ni->tags[0]);
			}

			printed = 1;
		}

		sleep(sleeptime);
		if (sleeptime < NI_MAXSLEEPTIME)
		{
			/* backoff */
			sleeptime *= 2; 
		}
	}

	syslog(LOG_INFO, "NetInfo connected to %s/%s", inet_ntoa(ni->addrs[0]), ni->tags[0]);

	return 1;
}


/*
 * Confirm that our tcp socket is still valid
 */
static int
confirm_tcp(ni_private *ni, int needwrite)
{
	int p;

	if (ni->tsock != -1)
	{
		p = getmyport(ni->tsock);
		if ((p != -1) && (p == ni->tport))
		{
			/* All is well */
			return 1;
		}

		if (p == -1)
		{
			/* Socket has died. Close it so it doesn't become a zombie. */
			close(ni->tsock);
		}

		/*
		 * Somebody has reused our socket.
		 */
		if (ni->tc != NULL)
		{
			if (ni->tc->cl_auth != NULL) auth_destroy(ni->tc->cl_auth);
			clnt_destroy(ni->tc);
			ni->tc = NULL;
		}
	}

	if ((needwrite == 0) && (rebind(ni) == 0) && (ni->abort != 0)) return 0;

	return connectit(ni);
}


static int
setmaster(ni_private *ni)
{
	ni_id root;
	ni_namelist nl;
	ni_name sep;
	ni_idlist idl;
	ni_name master;
	ni_index i;
	ni_index j;
	ni_id id;
	struct in_addr addr;
	int needwrite;

	if (ni->naddrs == 1)
	{
		/* One server - must be the master */
		ni->whichwrite = 0;
		return 1;
	}

	needwrite = ni->needwrite;
	ni->needwrite = 0;
	if (ni_root(ni, &root) != NI_OK)
	{
		ni->needwrite = needwrite;
		return 0;
	}

	NI_INIT(&nl);
	if (ni_lookupprop(ni, &root, NAME_MASTER, &nl) != NI_OK)
	{
		ni->needwrite = needwrite;
		return 0;
	}

	if (nl.ninl_len == 0)
	{
		ni->needwrite = needwrite;
		return 0;
	}

	sep = index(nl.ninl_val[0], '/');
	if (sep == NULL)
	{
		ni->needwrite = needwrite;
		return 0;
	}

	*sep++ = 0;
	master = nl.ninl_val[0];

	NI_INIT(&idl);
	if (ni_lookup(ni, &root, NAME_NAME, NAME_MACHINES, &idl) != NI_OK)
	{
		ni->needwrite = needwrite;
		ni_namelist_free(&nl);
		return 0;
	}

	if (idl.niil_len < 1)
	{
		ni->needwrite = needwrite;
		return 0;
	}

	id.nii_object = idl.niil_val[0];
	ni_idlist_free(&idl);

	NI_INIT(&idl);
	if (ni_lookup(ni, &id, NAME_NAME, master, &idl) != NI_OK)
	{
		ni_namelist_free(&nl);
		ni->needwrite = needwrite;
		return 0;
	}

	ni_namelist_free(&nl);
	if (idl.niil_len < 1)
	{
		ni->needwrite = needwrite;
		return 0;
	}

	id.nii_object = idl.niil_val[0];
	ni_idlist_free(&idl);
	NI_INIT(&nl);
	if (ni_lookupprop(ni, &id, NAME_IP_ADDRESS, &nl) != NI_OK) return 0;

	for (i = 0; i < nl.ninl_len; i++)
	{
		addr.s_addr = inet_addr(nl.ninl_val[i]);
		for (j = 0; j < ni->naddrs; j++)
		{
			if (addr.s_addr == ni->addrs[j].s_addr)
			{
				ni->whichwrite = j;
				ni_namelist_free(&nl);
				ni->needwrite = needwrite;
				return 1;
			}
		}
	}

	ni->needwrite = needwrite;
	ni_namelist_free(&nl);
	return 0;
}


static void *
callit(ni_private *ni, void *(*stub)(), void *args, int needwrite)
{
	void *resp;
	struct rpc_err err;
	int i;
	int sleeptime = 0;
	int printed = 0;

	if (getpid() != ni->pid) reinit(ni);

	if (needwrite || ni->needwrite)
	{
		if (ni->whichwrite >= 0)
		{
			ni_switch(ni, ni->whichwrite);
		}
		else
		{
			if (setmaster(ni) == 0) return NULL;
			ni_switch(ni, ni->whichwrite);
		}

		if (needwrite == 0)
		{
			ni_settimeout(ni, (ni->rtv_sec == 0 ? NI_TIMEOUT_SHORT : ni->rtv_sec));
			needwrite = 1;
		}
		else
		{
			ni_settimeout(ni, (ni->wtv_sec == 0 ? NI_TIMEOUT_LONG : ni->wtv_sec));
		}
	}
	else
	{
		ni_settimeout(ni, (ni->rtv_sec == 0 ? NI_TIMEOUT_SHORT : ni->rtv_sec));
	}

	for (;;)
	{
		/*
		 * Try more than once, in case server closed connection.
		 */
		for (i = 0; i < NI_MAXCONNTRIES; i++)
		{
			if (!confirm_tcp(ni, needwrite)) break;

			if ((resp = (*stub)(args, ni->tc)) != NULL)
			{
				if (printed != 0) syslog(LOG_INFO, "NetInfo connected to %s/%s", inet_ntoa(ni->addrs[0]), ni->tags[0]);
				return resp;
			}

			if (ni->tc != NULL)
			{
				clnt_geterr(ni->tc, &err);
				if (err.re_status != RPC_CANTRECV) break;
			}

			if ((i + 1) < NI_MAXCONNTRIES)
			{
				/* Server closed connection. Reinit and try again. */
				reinit(ni);
			}
		}

		if (err.re_status == RPC_PROCUNAVAIL) return NULL;

		if (needwrite || ni->abort)
		{
			/*
			 * We time out for writes or if it is explicitly
			 * requested.
			 */
			if (ni->abort) reinit(ni);

			syslog(LOG_ERR, "NetInfo connection failed for server %s/%s", inet_ntoa(ni->addrs[0]), ni->tags[0]);
			return NULL;
		}

		if (printed != 0)
		{
			if (ni->tc != NULL)
			{
				if ((sleeptime != 0) || (err.re_status != RPC_TIMEDOUT))
				{
					/*
					 * Do not print message on
					 * first timeout. It is likely
					 * we will find another server soon.
					 * Let's not needlessly alarm the
					 * poor user!
					 */
					syslog(LOG_ERR, "%s on connection to %s/%s", clnt_sperror(ni->tc,"NetInfo connection timeout"), inet_ntoa(ni->addrs[0]), ni->tags[0]);
					printed = 1;
				}
				else
				{
					/* first attempt failed */
					syslog(LOG_NOTICE, "%s on initial connection to %s/%s", clnt_sperror(ni->tc,"NetInfo connection timeout"), inet_ntoa(ni->addrs[0]), ni->tags[0]);
				}
			}
			else
			{
				syslog(LOG_ERR, "NetInfo connection failed for server %s/%s", inet_ntoa(ni->addrs[0]), ni->tags[0]);
				printed = 1;
			}
		}

		if (sleeptime > 0)
		{
			sleep(sleeptime);
			 /* backoff */
			if (sleeptime < NI_MAXSLEEPTIME) sleeptime *= 2;
		}
		else
		{
			/*
			 * Do not sleep on the first timeout.
			 * It is likely we will find another server quickly.
			 */
			sleeptime = NI_SLEEPTIME;
		}

		reinit(ni);
		rebind(ni);
	}
}


static void
ni_clear(ni_private *ni)
{
 ni->needwrite = 0;
 ni->naddrs = 0;
 ni->addrs = NULL;
 ni->tags = NULL;
 ni->tc = NULL;
 ni->tsock = -1;
 ni->tport = -1;
 ni->whichwrite = -1;
 ni->passwd = NULL;
}


static void *
ni_alloc(void)
{
	ni_private *ni;

	ni = (ni_private *)malloc(sizeof(*ni));
	ni->naddrs = 0;
	ni->whichwrite = -1;
	ni->pid = getpid();
	ni->tsock = -1;
	ni->tport = -1;
	ni->tc = NULL;
	ni->tv_sec = NI_TIMEOUT_SHORT;
	ni->rtv_sec = 0;
	ni->wtv_sec = 0;
	ni->abort = 0;
	ni->passwd = NULL;
	ni->uid = getuid();
	ni->needwrite = 0;
	return (void *)ni;
}


void *
_ni_dup(void *ni)
{
	ni_private *dupni;
	ni_index i;

	if (ni == NULL) return NULL;

	dupni = (ni_private *)ni_alloc();
	*dupni = *NIP(ni);
	ni_clear(dupni);
	dupni->naddrs = NIP(ni)->naddrs;
	dupni->whichwrite = NIP(ni)->whichwrite;
	if (dupni->naddrs > 0)
	{
		dupni->addrs = ((struct in_addr *) malloc(NIP(ni)->naddrs * sizeof(struct in_addr)));
		bcopy(NIP(ni)->addrs, dupni->addrs, NIP(ni)->naddrs * sizeof(struct in_addr));
		dupni->tags = ((ni_name *) malloc(NIP(ni)->naddrs * sizeof(ni_name)));
		for (i = 0; i < NIP(ni)->naddrs; i++)
		{
			dupni->tags[i] = ni_name_dup(NIP(ni)->tags[i]);
		}
	}

	if (NIP(ni)->passwd != NULL) dupni->passwd = ni_name_dup(NIP(ni)->passwd);
	return (void *)dupni;
}


static int
match(ni_name domain, ni_name domtag, ni_name *tag)
{
	int len = strlen(domain);
	ni_name sep;

	sep = index(domtag, '/');
	if (sep == NULL) return 0;

	if ((strncmp(domain, domtag, len) == 0) && (domtag[len] == '/'))
	{
		*tag = ni_name_dup(sep + 1);
		return 1;
	}

	return 0;
}


static void
add_addr_tag(ni_private *ni, ni_name addrtag)
{
	struct in_addr addr;
	ni_name tag;
	char *slash;

	slash = strchr(addrtag, '/');
	if (slash == NULL) return;

	tag = slash + 1;
	if (tag[0] == '\0') return;

	*slash = '\0';

	if (inet_aton(addrtag, &addr) == 0) return;

	if (ni->naddrs == 0)
	{
		ni->addrs = (struct in_addr *)calloc(1, sizeof(struct in_addr));
		if (ni->addrs == NULL) return;

		ni->tags = (ni_name *)calloc(1, sizeof(ni_name));
		if (ni->tags == NULL) return;
	}
	else
	{
		ni->addrs = (struct in_addr *)realloc(ni->addrs, ((ni->naddrs + 1) * sizeof(struct in_addr)));
		if (ni->addrs == NULL) return;

		ni->tags = (ni_name *)realloc(ni->tags, ((ni->naddrs + 1) * sizeof(ni_name)));
		if (ni->tags == NULL) return;
	}

	ni->addrs[ni->naddrs] = addr;
	ni->tags[ni->naddrs] = ni_name_dup(tag);
	ni->naddrs++;
}

static int
addaddr(void *ni, ni_index ido, ni_name tag, ni_private *target_ni)
{
	ni_id id;
	ni_namelist nl;
	struct in_addr addr;
	int i;
	ni_status status;

	if (ni == NULL) return 0;
	if (tag == NULL) return 0;
	if (target_ni == NULL) return 0;

	id.nii_object = ido;
	NI_INIT(&nl);

	status = ni_lookupprop(ni, &id, NAME_IP_ADDRESS, &nl);
	if (status != NI_OK) return 0;

	if (nl.ni_namelist_len == 0) return 0;

	if (target_ni->naddrs == 0) 
	{
		target_ni->addrs = (struct in_addr *)malloc(nl.ni_namelist_len * sizeof(struct in_addr));
		target_ni->tags = (ni_name *)malloc(nl.ni_namelist_len * sizeof(ni_name));
	}
	else
	{
		target_ni->addrs = (struct in_addr *)realloc(target_ni->addrs, ((target_ni->naddrs + nl.ni_namelist_len) * sizeof(struct in_addr)));
		target_ni->tags = (ni_name *)realloc(target_ni->tags, ((target_ni->naddrs + nl.ni_namelist_len) * sizeof(ni_name)));
	}

	for (i = 0; i < nl.ni_namelist_len; i++)
	{
		addr.s_addr = inet_addr(nl.ni_namelist_val[i]);
		target_ni->addrs[target_ni->naddrs] = addr;
		target_ni->tags[target_ni->naddrs] = ni_name_dup(tag);
		target_ni->naddrs++;
	}

	ni_namelist_free(&nl);
	return 1;
}

static int
get_daddr(ni_private *ni, ni_name dom, ni_private *target_ni)
{
	ni_id nid;
	ni_idlist ids;
	ni_entrylist entries;
	ni_proplist pl;
	ni_index i;
	ni_index j;
	ni_name tag;

	if (ni == NULL) return 0;
	if (dom == NULL) return 0;
	if (target_ni == NULL) return 0;

	if (!strcmp(dom, "."))
	{
		/* check for server list */
		NI_INIT(&pl);
		if (ni_statistics(ni, &pl) == NI_OK)
		{
			i = ni_proplist_match(pl, NAME_DOMAIN_SERVERS, NULL);
			if (i != NI_INDEX_NULL)
			{
				if (pl.ni_proplist_val[i].nip_val.ni_namelist_len > 0)
				{
					for (j = 0; j < pl.ni_proplist_val[i].nip_val.ni_namelist_len; j++)
					{
						add_addr_tag(target_ni, pl.ni_proplist_val[i].nip_val.ni_namelist_val[j]);
					}

					ni_proplist_free(&pl);
					return 1;
				}
			}

			ni_proplist_free(&pl);
		}
	}

	if (ni_root(ni, &nid) != NI_OK) return 0;

	NI_INIT(&ids);
	if (ni_lookup(ni, &nid, NAME_NAME, NAME_MACHINES, &ids) != NI_OK) return 0;

	nid.nii_object = ids.niil_val[0];
	ni_idlist_free(&ids);

	NI_INIT(&entries);
	if (ni_list(ni, &nid, NAME_SERVES, &entries) != NI_OK) return 0;

	for (i = 0; i < entries.niel_len; i++)
	{
		if (entries.niel_val[i].names == NULL) continue;

		for (j = 0; j < entries.niel_val[i].names->ni_namelist_len; j++)
		{
			if (match(dom, entries.niel_val[i].names->ni_namelist_val[j], &tag))
			{
				addaddr(ni, entries.niel_val[i].id, tag, target_ni);
				ni_name_free(&tag);
			}
		}
	}

	ni_entrylist_free(&entries);
	return (target_ni->naddrs > 0);
}

static ni_status
getparent(ni_private *oldni, ni_private **newni)
{
	ni_rparent_res *resp;
	ni_private *ni = NULL;
	ni_private *dupni;
	int found = 0;
	ni_index i;
	struct in_addr raddr;
	int printed = 0;
	int inlist = 0;

	if (oldni == NULL) return 0;
	if (newni == NULL) return 0;

	while (found == 0)
	{
		/*
		 * First, find our parent, any parent
		 */
		for (;;)
		{
			resp = RCALLIT(oldni, _ni_rparent_2, NULL);
			if (resp == NULL) return NI_FAILED;
			if (resp->status != NI_NORESPONSE) break;

			if (printed != 0)
			{
				syslog(LOG_WARNING, "NetInfo timeout finding server for parent of %s/%s, sleeping", 				inet_ntoa(oldni->addrs[0]), oldni->tags[0]);
				printed = 1;
			}

			sleep(NI_SLEEPTIME);
		}

		if (printed)
		{
			raddr.s_addr = htonl(resp->ni_rparent_res_u.binding.addr);

			syslog(LOG_INFO, "NetInfo %s/%s found parent %s/%s", 				inet_ntoa(oldni->addrs[0]), oldni->tags[0], 				inet_ntoa(raddr), resp->ni_rparent_res_u.binding.tag);
		}

		if (resp->status != NI_OK) return resp->status;

		ni = ni_alloc();
		*ni = *oldni;
		ni_clear(ni);
		ni->naddrs = 1;
		ni->addrs = (struct in_addr *)malloc(sizeof(struct in_addr));
		ni->addrs[0].s_addr=htonl(resp->ni_rparent_res_u.binding.addr);
		ni->tags = (ni_name *)malloc(sizeof(ni_name));
		ni->tags[0] = ni_name_dup(resp->ni_rparent_res_u.binding.tag);

		xdr_free((xdrproc_t)xdr_ni_rparent_res, (void *)resp);

		dupni = ni;
		ni = ni_alloc();
		*ni = *dupni;
		ni_clear(ni);
		if (get_daddr(dupni, ".", ni) == 0) 
		{
			if (oldni->abort == 1)
			{
				ni_free(dupni);
				break;
			}
		}
		else
		{
			/*
			 * Make sure returned parent is head of list
			 */
			for (i = 0; i < ni->naddrs; i++)
			{
				if (ni->addrs[i].s_addr == dupni->addrs[0].s_addr)
				{
					ni_switch(ni, i);
					inlist++;
					break;
				}
			}

			/*
			 * Reuse dupni client info
			 */
			ni->tsock = dupni->tsock;
			ni->tport = dupni->tport;
			ni->tc = dupni->tc;
			dupni->tsock = -1;
			dupni->tport = -1;
			dupni->tc = NULL;
			found = 1;

			/*
			 * If returned parent wasn't in list, it's a rogue.
			 * Log an error and drop the connection.
			 */
			if (inlist == 0)
			{
				syslog(LOG_ERR, "Rogue NetInfo server detected: %s/%s", 				inet_ntoa(dupni->addrs[0]), dupni->tags[0]);
				reinit(ni);
			}

		}
		ni_free(dupni);
	}

	if (found)
	{
		*newni = ni;
		return NI_OK;
	}

	if (ni != NULL) ni_free(ni);
	return NI_FAILED;
}


void *
ni_connect(struct sockaddr_in *sin, const char *tag)
{
	void *ni;

	if (sin == NULL) return NULL;
	if (tag == NULL) return NULL;

	ni = ni_alloc();
	NIP(ni)->naddrs = 1;
	NIP(ni)->addrs = (struct in_addr *)malloc(sizeof(struct in_addr));
	NIP(ni)->addrs[0] = sin->sin_addr;
	NIP(ni)->tags = (ni_name *)malloc(sizeof(ni_name));
	NIP(ni)->tags[0] = ni_name_dup(tag);
	return ni;
}


ni_status
ni_addrtag(void *ni, struct sockaddr_in *addr, ni_name *tag)
{
	if (ni == NULL) return NI_FAILED;
	if (addr == NULL) return NI_FAILED;
	if (tag == NULL) return NI_FAILED;

	if (!confirm_tcp(ni, 0)) return NI_FAILED;

	*tag = ni_name_dup(NIP(ni)->tags[0]);
	addr->sin_addr = NIP(ni)->addrs[0];
	addr->sin_port = htons(NIP(ni)->tport);
	addr->sin_family = AF_INET;
	bzero(addr->sin_zero, sizeof(addr->sin_zero));

	return NI_OK;
}


void *
ni_new(void *oldni, const char *domain)
{
	ni_private *ni;
	ni_status status;
	ni_name sep, addr, tag;
	struct sockaddr_in sin;
	struct hostent *he;

	if (domain == NULL) return NULL;

	sep = index(domain, '@');
	if (sep != NULL)
	{
		tag = strncpy((char *)malloc(sep - domain + 1), domain, sep - domain);
		tag[sep - domain] = '\0';

		addr = NULL;
		asprintf(&addr, "%s", sep + 1);

		sin.sin_addr.s_addr = inet_addr(addr);
		if (sin.sin_addr.s_addr == INADDR_NONE)
		{
			he = gethostbyname(addr);
			if (he == NULL)
			{
				free(addr);
				free(tag);
				return NULL;
			}

			bcopy(he->h_addr_list[0], &sin.sin_addr.s_addr, he->h_length);
		}

		ni = ni_connect(&sin, tag);
		free(addr);
		free(tag);

		return (void *)ni;
	}

	ni = NULL;

	if (oldni == NULL)
	{
		ni = ni_alloc();
		if (connectlocal(ni) == 0)
		{
			free(ni);
			return NULL;
		}

		if (strcmp(domain, ".") == 0) return (void *)ni;

		if (strcmp(domain, "..") == 0)
		{
			oldni = ni;
			status = getparent((ni_private *)oldni, &ni);
			ni_free(oldni);
			if (status != NI_OK) return NULL;
			return (void *)ni;
		}

		ni_free(ni);
		return NULL;
	}

	if (strcmp(domain, "..") == 0)
	{
		status = getparent((ni_private *)oldni, &ni);
		if (status != NI_OK) return NULL;
		return (void *)ni;
	}

	ni = ni_alloc();
	*ni = *NIP(oldni);
	ni_clear(ni);
	if (get_daddr(oldni, (ni_name)domain, ni) == 0)
	{
		ni_free(ni);
		return NULL;
	}

	return (void *)ni;
}


void
ni_free(void *ni)
{
	ni_index i;

	if (ni == NULL) return;

	if (NIP(ni)->tc != NULL) clnt_kill(NIP(ni)->tc, NIP(ni)->tsock, NIP(ni)->tport);

	if (NIP(ni)->naddrs > 0)
	{
		for (i = 0; i < NIP(ni)->naddrs; i++) ni_name_free(&NIP(ni)->tags[i]);
		free(NIP(ni)->tags);
		free(NIP(ni)->addrs);
	}

	if (NIP(ni)->passwd != NULL) ni_name_free(&NIP(ni)->passwd);

	free(ni);
}


/*
 * The rest of these are just wrappers that end up doing
 * RPC calls to the local NetInfo server.
 */
ni_status
ni_statistics(void *ni, ni_proplist *pl)
{
	ni_proplist *resp;

	if (ni == NULL) return NI_FAILED;
	if (pl == NULL) return NI_FAILED;

	resp = (ni_proplist *)RCALLIT(ni, _ni_statistics_2, NULL);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_statistics");
		return NI_FAILED;
	}

	*pl = *resp;
	return NI_OK;
}


ni_status
ni_root(void *ni, ni_id *id)
{
	ni_id_res *resp;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;

	resp = RCALLIT(ni, _ni_root_2, id);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_root");
		return NI_FAILED;
	}

	if (resp->status == NI_OK) *id = resp->ni_id_res_u.id;

	return resp->status;
}


ni_status
ni_self(void *ni, ni_id *id)
{
	ni_id_res *resp;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;

	resp = RCALLIT(ni, _ni_self_2, id);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_self");
		return NI_FAILED;
	}

	if (resp->status == NI_OK) *id = resp->ni_id_res_u.id;

	return resp->status;
}


ni_status
ni_parent(void *ni, ni_id *id, ni_index *parent_id_p)
{
	ni_parent_res *resp;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;
	if (parent_id_p == NULL) return NI_FAILED;

	resp = RCALLIT(ni, _ni_parent_2, id);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_parent");
		return NI_FAILED;
	}

	if (resp->status == NI_OK)
	{
		*parent_id_p = resp->ni_parent_res_u.stuff.object_id;
		*id = resp->ni_parent_res_u.stuff.self_id;
	}

	return resp->status;
}


ni_status
ni_children(void *ni, ni_id *id, ni_idlist *children)
{
	ni_children_res *resp;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;
	if (children == NULL) return NI_FAILED;

	resp = RCALLIT(ni, _ni_children_2, id);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_children");
		return NI_FAILED;
	}

	if (resp->status == NI_OK)
	{
		*children = resp->ni_children_res_u.stuff.children;
		*id = resp->ni_children_res_u.stuff.self_id;
	}

	return resp->status;
}


ni_status
ni_create(void *ni, ni_id *parent_id, ni_proplist pl, ni_id *child_id_p, ni_index where)
{
	ni_create_args args;
	ni_create_res *resp;

	if (ni == NULL) return NI_FAILED;
	if (parent_id == NULL) return NI_FAILED;
	if (child_id_p == NULL) return NI_FAILED;

	args.id = *parent_id;
	args.props = pl;
	args.where = where;
	args.target_id = NULL;

	resp = WCALLIT(ni, _ni_create_2, &args);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_create");
		return NI_FAILED;
	}

	if (resp->status == NI_OK)
	{
		*child_id_p = resp->ni_create_res_u.stuff.id;
		*parent_id = resp->ni_create_res_u.stuff.self_id;
	}

	return resp->status;
}


ni_status
ni_destroy(void *ni, ni_id *parent_id, ni_id self_id)
{
	ni_id_res *resp;
	ni_destroy_args args;

	if (ni == NULL) return NI_FAILED;
	if (parent_id == NULL) return NI_FAILED;

	args.parent_id = *parent_id;
	args.self_id = self_id;

	resp = WCALLIT(ni, _ni_destroy_2, &args);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_destroy");
		return NI_FAILED;
	}

	if (resp->status == NI_OK) *parent_id = resp->ni_id_res_u.id;

	return resp->status;
}


ni_status
ni_write(void *ni, ni_id *self_id, ni_proplist pl)
{
	ni_proplist_stuff args;
	ni_id_res *resp;

	if (ni == NULL) return NI_FAILED;
	if (self_id == NULL) return NI_FAILED;

	args.id = *self_id;
	args.props = pl;

	resp = WCALLIT(ni, _ni_write_2, &args);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_write");
		return NI_FAILED;
	}

	if (resp->status == NI_OK) *self_id = resp->ni_id_res_u.id;

	return resp->status;
}


ni_status
ni_read(void *ni, ni_id *self_id, ni_proplist *pl)
{
	ni_proplist_res *resp;

	if (ni == NULL) return NI_FAILED;
	if (self_id == NULL) return NI_FAILED;
	if (pl == NULL) return NI_FAILED;

	resp = RCALLIT(ni, _ni_read_2, self_id);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_read");
		return NI_FAILED;
	}

	if (resp->status == NI_OK)
	{
		*self_id = resp->ni_proplist_res_u.stuff.id;
		*pl = resp->ni_proplist_res_u.stuff.props;
	}

	return resp->status;
}


ni_status
ni_lookup(void *ni, ni_id *id, ni_name_const pname, ni_name_const pval, ni_idlist *hits)
{
	ni_lookup_res *resp;
	ni_lookup_args args;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;
	if (pname == NULL) return NI_FAILED;
	/* pval may be NULL */
	if (hits == NULL) return NI_FAILED;

	args.id = *id;
	args.key = (ni_name)pname;
	args.value = (ni_name)pval;

	resp = RCALLIT(ni, _ni_lookup_2, &args);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_lookup");
		return NI_FAILED;
	}

	if (resp->status == NI_OK)
	{
		*hits = resp->ni_lookup_res_u.stuff.idlist;
		*id = resp->ni_lookup_res_u.stuff.self_id;
	}

	return resp->status;
}


ni_status
ni_lookupread(void *ni, ni_id *id, ni_name_const pname, ni_name_const pval, ni_proplist *props)
{
	ni_proplist_res *resp;
	ni_lookup_args args;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;
	if (pname == NULL) return NI_FAILED;
	/* pval may be NULL*/
	if (props == NULL) return NI_FAILED;

	args.id = *id;
	args.key = (ni_name)pname;
	args.value = (ni_name)pval;

	resp = RCALLIT(ni, _ni_lookupread_2, &args);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_lookupread");
		return NI_FAILED;
	}

	if (resp->status == NI_OK)
	{
		*props = resp->ni_proplist_res_u.stuff.props;
		*id = resp->ni_proplist_res_u.stuff.id;
	}

	return resp->status;
}


ni_status
ni_list(void *ni, ni_id *id, ni_name_const pname, ni_entrylist *entries)
{
	ni_list_res *resp;
	ni_name_args args;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;
	if (pname == NULL) return NI_FAILED;
	if (entries == NULL) return NI_FAILED;

	args.id = *id;
	args.name = (ni_name)pname;

	resp = RCALLIT(ni, _ni_list_2, &args);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_list");
		return NI_FAILED;
	}

	if (resp->status == NI_OK)
	{
		*entries = resp->ni_list_res_u.stuff.entries;
		*id = resp->ni_list_res_u.stuff.self_id;
	}

	return resp->status;
}


ni_status
ni_listall(void *ni, ni_id *id, ni_proplist_list *entries)
{
	ni_listall_res *resp;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;
	if (entries == NULL) return NI_FAILED;

	resp = RCALLIT(ni, _ni_listall_2, id);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_listall");
		return NI_FAILED;
	}

	if (resp->status == NI_OK)
	{
		*entries = resp->ni_listall_res_u.stuff.entries;
		*id = resp->ni_listall_res_u.stuff.self_id;
	}

	return resp->status;
}


ni_status
ni_readprop(void *ni, ni_id *id, ni_index which, ni_namelist *propval_p)
{
	ni_namelist_res *resp;
	ni_prop_args args;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;
	if (propval_p == NULL) return NI_FAILED;

	args.id = *id;
	args.prop_index = which;

	resp = RCALLIT(ni, _ni_readprop_2, &args);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_readprop");
		return NI_FAILED;
	}

	if (resp->status == NI_OK)
	{
		*propval_p = resp->ni_namelist_res_u.stuff.values;
		*id = resp->ni_namelist_res_u.stuff.self_id;
	}

	return resp->status;
}


ni_status
ni_writeprop(void *ni, ni_id *id, ni_index which, ni_namelist propval)
{
	ni_id_res *resp;
	ni_writeprop_args args;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;

	args.id = *id;
	args.prop_index = which;
	args.values = propval;

	resp = WCALLIT(ni, _ni_writeprop_2, &args);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_writeprop");
		return NI_FAILED;
	}

	if (resp->status == NI_OK) *id = resp->ni_id_res_u.id;

	return resp->status;
}


ni_status
ni_listprops(void *ni, ni_id *id, ni_namelist *propnames)
{
	ni_namelist_res *resp;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;
	if (propnames == NULL) return NI_FAILED;

	resp = RCALLIT(ni, _ni_listprops_2, id);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_listprops");
		return NI_FAILED;
	}

	if (resp->status == NI_OK)
	{
		*propnames = resp->ni_namelist_res_u.stuff.values;
		*id = resp->ni_namelist_res_u.stuff.self_id;
	}

	return resp->status;
}


ni_status
ni_createprop(void *ni, ni_id *id, ni_property prop, ni_index where)
{
	ni_id_res *resp;
	ni_createprop_args args;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;

	args.id = *id;
	args.prop = prop;
	args.where = where;

	resp = WCALLIT(ni, _ni_createprop_2, &args);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_createprop");
		return NI_FAILED;
	}

	if (resp->status == NI_OK) *id = resp->ni_id_res_u.id;

	return resp->status;
}


ni_status
ni_destroyprop(void *ni, ni_id *id, ni_index which)
{
	ni_id_res *resp;
	ni_prop_args args;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;

	args.id = *id;
	args.prop_index = which;

	resp = WCALLIT(ni, _ni_destroyprop_2, &args);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_destroyprop");
		return NI_FAILED;
	}

	if (resp->status == NI_OK) *id = resp->ni_id_res_u.id;

	return resp->status;
}


ni_status
ni_renameprop(void *ni, ni_id *id, ni_index prop_index, ni_name_const name)
{
	ni_id_res *resp;
	ni_propname_args args;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;
	if (name == NULL) return NI_FAILED;

	args.id = *id;
	args.prop_index = prop_index;
	args.name = (ni_name)name;

	resp = WCALLIT(ni, _ni_renameprop_2, &args);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_renameprop");
		return NI_FAILED;
	}

	if (resp->status == NI_OK) *id = resp->ni_id_res_u.id;

	return resp->status;
}


ni_status
ni_createname(void *ni, ni_id *id, ni_index prop_index, ni_name_const name, ni_index where)
{
	ni_id_res *resp;
	ni_createname_args args;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;
	if (name == NULL) return NI_FAILED;

	args.id = *id;
	args.prop_index = prop_index;
	args.name = (ni_name)name;
	args.where = where;

	resp = WCALLIT(ni, _ni_createname_2, &args);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_createname");
		return NI_FAILED;
	}

	if (resp->status == NI_OK) *id = resp->ni_id_res_u.id;

	return resp->status;
}


ni_status
ni_destroyname(void *ni, ni_id *id, ni_index prop_index, ni_index name_index)
{
	ni_id_res *resp;
	ni_nameindex_args args;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;

	args.id = *id;
	args.prop_index = prop_index;
	args.name_index = name_index;

	resp = WCALLIT(ni, _ni_destroyname_2, &args);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_destroyname");
		return NI_FAILED;
	}

	if (resp->status == NI_OK) *id = resp->ni_id_res_u.id;

	return resp->status;
}


ni_status
ni_writename(void *ni, ni_id *id, ni_index prop_index, ni_index name_index, ni_name_const name)
{
	ni_id_res *resp;
	ni_writename_args args;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;
	if (name == NULL) return NI_FAILED;

	args.id = *id;
	args.prop_index = prop_index;
	args.name_index = name_index;
	args.name = (ni_name)name;

	resp = WCALLIT(ni, _ni_writename_2, &args);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_writename");
		return NI_FAILED;
	}

	if (resp->status == NI_OK) *id = resp->ni_id_res_u.id;

	return resp->status;
}


ni_status
ni_readname(void *ni, ni_id *id, ni_index prop_index, ni_index name_index, ni_name *name)
{
	ni_readname_res *resp;
	ni_nameindex_args args;

	if (ni == NULL) return NI_FAILED;
	if (id == NULL) return NI_FAILED;
	if (name == NULL) return NI_FAILED;

	args.id = *id;
	args.prop_index = prop_index;
	args.name_index = name_index;

	resp = RCALLIT(ni, _ni_readname_2, &args);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_readname");
		return NI_FAILED;
	}

	if (resp->status == NI_OK)
	{
		*id = resp->ni_readname_res_u.stuff.id;
		*name = resp->ni_readname_res_u.stuff.name;
	}

	return resp->status;
}


ni_status
ni_resync(void *ni)
{
	ni_status *resp;

	if (ni == NULL) return NI_FAILED;

	resp = (ni_status *)RCALLIT(ni, _ni_resync_2, NULL);
	if (resp == NULL)
	{
		clnt_debug(ni, "_ni_resync");
		return NI_FAILED;
	}

	return *resp;
}


ni_status
ni_setuser(void *ni, ni_name_const user)
{
	ni_id id;
	ni_idlist ids;
	ni_namelist nl;
	char *p;
	ni_status status;

	if (ni == NULL) return NI_FAILED;

	if (user == NULL)
	{
		NIP(ni)->uid = getuid();
		return ni_setpassword(ni, NULL);
	}

	status = ni_root(ni, &id);
	if (status != NI_OK) return NI_NOUSER;

	NI_INIT(&ids);
	status = ni_lookup(ni, &id, NAME_NAME, NAME_USERS, &ids);
	if (status != NI_OK) return NI_NOUSER;

	id.nii_object = ids.niil_val[0];
	ni_idlist_free(&ids);

	NI_INIT(&ids);
	status = ni_lookup(ni, &id, NAME_NAME, user, &ids);
	if (status != NI_OK) return NI_NOUSER;

	id.nii_object = ids.niil_val[0];
	ni_idlist_free(&ids);

	NI_INIT(&nl);
	status = ni_lookupprop(ni, &id, NAME_UID, &nl);
	if (status != NI_OK) return NI_NOUSER;

	if (nl.ninl_len == 0) return NI_NOUSER;

	for (p = nl.ninl_val[0]; *p; p++)
	{
		if (isdigit(*p) == 0)
		{
			ni_namelist_free(&nl);
			return NI_NOUSER;
		}
	}

	NIP(ni)->uid = atoi(nl.ninl_val[0]);
	if (NIP(ni)->passwd == NULL) NIP(ni)->passwd = ni_name_dup("");

	createauth(NIP(ni));
	return NI_OK;
}


ni_status
ni_setpassword(void *ni, ni_name_const passwd)
{
	char *p;

	if (ni == NULL) return NI_FAILED;

	if (NIP(ni)->passwd != NULL) ni_name_free(&NIP(ni)->passwd);

	if (passwd == NULL)
	{
		NIP(ni)->passwd = NULL;
		if (NIP(ni)->tc != NULL)
		{
			auth_destroy(NIP(ni)->tc->cl_auth);
			NIP(ni)->tc->cl_auth = authnone_create();
		}
		return NI_OK;
	}

	NIP(ni)->passwd = ni_name_dup(passwd);

	/* Our trivial encryption scheme */
	for (p = NIP(ni)->passwd; *p; p++) *p = ~(*p);
	createauth(NIP(ni));
	return NI_OK;
}


/*
 * 	The procedure pmap_getport_to below is derived
 *	from Sun Microsystems RPC source code. As such the following
 *	statement applies to it.:
 *
 * 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
 */
/*
 * Client interface to pmap rpc service.
 *
 * Find the mapped port for program,version.
 * Calls the pmap service remotely to do the lookup.
 * Returns 0 if no map exists. 
 */ 
static u_short
pmap_getport_to(address, program, version, protocol, timeout_secs, ntries)
	struct sockaddr_in *address;
	u_long program;
	u_long version;
	u_int protocol;
	int timeout_secs;
	int ntries;
{
	u_short port = 0;
	int sock = -1;
	register CLIENT *client;
	struct pmap parms;
	struct timeval timeout;

	sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (sock < 0) return 0;

	address->sin_port = htons(PMAPPORT);
	timeout.tv_usec = ((timeout_secs % ntries) * 1000000) / ntries;
	timeout.tv_sec = (timeout_secs / ntries);

	client = clntudp_bufcreate(address, PMAPPROG, PMAPVERS, timeout, &sock, RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
	if (client != NULL)
	{
		parms.pm_prog = program;
		parms.pm_vers = version;
		parms.pm_prot = protocol;
		parms.pm_port = 0; /* not needed or used */
		timeout.tv_usec = 0;
		timeout.tv_sec = timeout_secs;

		if (CLNT_CALL(client, PMAPPROC_GETPORT, (xdrproc_t)xdr_pmap, (char *)&parms, (xdrproc_t)xdr_u_short, (char *)&port, timeout) != RPC_SUCCESS)
		{
			rpc_createerr.cf_stat = RPC_PMAPFAILURE;
			clnt_geterr(client, &rpc_createerr.cf_error);
			port = 0;
		}
		else if (port == 0)
		{
			rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
		}
	}

	if (client != NULL) clnt_destroy(client);

	close(sock);
	address->sin_port = 0;
	return port;
}


/*
 * Open a socket, but do not use the default portmap timeout
 */
static int
socket_open(struct sockaddr_in *raddr, int prog, int vers, int timeout, int ntries, int proto)
{
	struct sockaddr_in bindsin;
	int sock;
	int reuse = 1;
	u_short port;

	memset(&bindsin, 0, sizeof(bindsin));

	/*
	 * If no port number given ask the pmap for one
	 */
	if (raddr->sin_port == 0)
	{
		port = pmap_getport_to(raddr, prog, vers, IPPROTO_UDP, timeout, ntries);
		if (port == 0) return -1;

		raddr->sin_port = htons(port);
	}

	sock = socket(AF_INET, proto == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM, proto);
	if (sock < 0) return -1;

	if ((bindresvport(sock, NULL) < 0) && (errno == EADDRNOTAVAIL))
	{
		/* XXX - we're hitting this case way too often under load */
		/* fail gracefully: some address is better than none most of the time */
		syslog(LOG_DEBUG, "Libinfo[%s():%d] bindresvport(): %m", __func__, __LINE__);
		if (bind(sock, (struct sockaddr *)&bindsin, sizeof(bindsin)) < 0)
		{
			/* The system is really sad now if it can't give me any address... */
			syslog(LOG_DEBUG, "Libinfo[%s():%d] bind(): %m", __func__, __LINE__);
			close(sock);
			return -1;
		}
	}

	setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(int));
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));

	if (proto == IPPROTO_TCP)
	{
		if (connect(sock, (struct sockaddr *)raddr, sizeof(*raddr)) < 0)
		{
			syslog(LOG_DEBUG, "Libinfo[%s():%d] connect(): %m", __func__, __LINE__);
			close(sock);
			return -1;
		}
	}

	return sock;
}
