/*
 * CDE - Common Desktop Environment
 *
 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these libraries and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 */
/* $TOG: connection.c /main/4 1999/10/14 17:47:12 mgreess $ */
/*
 *  (c) Copyright 1993, 1994 Hewlett-Packard Company
 *  (c) Copyright 1993, 1994 International Business Machines Corp.
 *  (c) Copyright 1993, 1994 Novell, Inc.
 *  (c) Copyright 1993, 1994 Sun Microsystems, Inc.
 */

/*
 * This file manages server connections.
 */

#include <EUSCompat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#ifdef HPUX
#include <sys/resource.h>
#endif
#include "connection.h"
#include "rtable2.h"
#include "rtable3.h"
#include "rtable4.h"
#include "cm.h"
#include "debug.h"
#include "agent.h"
#include "convert2-4.h"
#include "convert3-4.h"
#include "rpccalls.h"

#ifdef HPUX
#define MAX_COUNT	10
#else
#define MAX_COUNT	40
#endif

static struct timeval timeout_tv;
static struct timeval retry_tv;
static AUTH *unix_credential = NULL;
static tcp_count = 0;
static cl_count = 0;
static _DtCm_Client_Info *client_cache_head = NULL;
static _DtCm_Client_Info *client_cache_tail = NULL;

/*****************************************************************************
 * forward declaration of static functions.
 *****************************************************************************/
static void create_auth(CLIENT *cl);
static void destroy_auth(CLIENT *cl);
static _DtCm_Client_Info * get_client_info(char *host, int version);
static void destroy_target_list(_DtCm_Target_List *tlist);
static void destroy_client_info(_DtCm_Client_Info *ci);
static void insert_client_info(_DtCm_Client_Info *ci);
static void delete_client_info(_DtCm_Client_Info *oldci);
static void cleanup_some_connection(_DtCm_Client_Info *dontclose);
static void check_registration(_DtCm_Connection *conn);
static _DtCm_Client_Info * get_new_client_handle(_DtCm_Connection *conn);
static CSA_return_code get_client_handle(const char *host, const u_long prognum,
			u_long *vers_outp, const u_long vers_low,
			const u_long vers_high, char *nettype,
			CLIENT **clnt);
static CSA_return_code regstat4_to_dtcmstatus(Registration_Status_4 stat4);

extern CSA_return_code
_DtCm_create_udp_client(
	char *host,
	int version,
	int timeout,
	_DtCm_Client_Info **clnt)
{
	CSA_return_code	stat;
	_DtCm_Client_Info	*ci;
	u_long		vers_out;
	CLIENT		*cl=NULL;

	if (host == NULL || clnt == NULL)
		return (CSA_E_INVALID_PARAMETER);

	/* if client info is found, we have at least the udp handle */
	if (((*clnt) = get_client_info(host, version)) != NULL) {
		return (CSA_SUCCESS);
	}

#if defined(SunOS)
	cl = clnt_create_vers(host, TABLEPROG, 
		&vers_out, TABLEVERS_2, version, "udp");
	if (cl==NULL) {
		_DtCm_print_errmsg(clnt_spcreateerror(host));
		return (_DtCm_clntstat_to_csastat(rpc_createerr.cf_stat));
	}
#else
	stat = get_client_handle(host, (u_int)TABLEPROG, &vers_out, TABLEVERS_2,
		version, "udp", &cl);
	if (stat != CSA_SUCCESS)
		return (stat);
#endif

	/* if version is lower than requested, check the list again */
	if (vers_out < version) {
		if ((ci = get_client_info(host, vers_out)) != NULL) {
			clnt_destroy(cl);
			*clnt = ci;
			return (CSA_SUCCESS);
		}
	}

	create_auth(cl);

	/* Adjust Timeout */
	if (timeout==0) timeout = _DtCM_DEFAULT_TIMEOUT;
	timeout_tv.tv_sec =  timeout;
	timeout_tv.tv_usec = 0;
	clnt_control(cl, CLSET_TIMEOUT, (char*)&timeout_tv);		

	/*	UDP only!
		time rpc waits for server to reply before retransmission =
		'timeout'. since the retry timeout is set to timeout + 10;
		this guarantees there won't
		be any retransmisssions resulting in duplicate 
		transactions in the database.
	*/

	retry_tv.tv_sec =  timeout + 10;
	retry_tv.tv_usec = 0;
	clnt_control(cl, CLSET_RETRY_TIMEOUT, (char*)&retry_tv);

	if ((ci  = (_DtCm_Client_Info *)calloc(1, sizeof(_DtCm_Client_Info))) == NULL) {
		destroy_auth(cl);
		clnt_destroy(cl);
		return (CSA_E_INSUFFICIENT_MEMORY);
	}

	if ((ci->host = strdup(host)) == NULL) {
		destroy_auth(cl);
		clnt_destroy(cl);
		free(ci);
		return (CSA_E_INSUFFICIENT_MEMORY);
	}

	ci->udpcl = cl;
	ci->vers_out = vers_out;
	insert_client_info(ci);
	*clnt = ci;
	return (CSA_SUCCESS);
}

/*
 * Creates tcp client handle.  Used for calls that potentially return
 * large amount of data.  If it fails to create a tcp client handle,
 * a udp client handle will be returned.
 */
extern CSA_return_code
_DtCm_create_tcp_client(
	char *host,
	int version,
	int timeout,
	_DtCm_Client_Info **clnt)
{
	CSA_return_code	stat;
	_DtCm_Client_Info	*ci;
	u_long		vers_out;
	CLIENT		*cl=NULL;

	if (host == NULL || clnt == NULL)
		return (CSA_E_INVALID_PARAMETER);

	/* Get a udp client handle.  This serves two purposes:	       	   */ 
	/* - to get a udp handle for an old server which talks only udp    */
	/* - to invoke a server through inetd since only udp is registered.*/

	if ((stat = _DtCm_create_udp_client(host, version, timeout, &ci))
	    != CSA_SUCCESS) {
		return (stat);
	} else if (ci->tcpcl) {
		*clnt = ci;
		return (CSA_SUCCESS);
	} else {
		/* create tcp connection */
#if defined(SunOS)
		cl = clnt_create_vers(host, TABLEPROG, &vers_out,
			TABLEVERS_2, version, "tcp");
#else
		stat = get_client_handle(host, (u_int)TABLEPROG, &vers_out,
			TABLEVERS_2, version, "tcp", &cl);
#endif

		/* if can't create tcp connection, use udp */
		if (cl==NULL) {
			_DtCm_print_errmsg(clnt_spcreateerror(host));
			*clnt = ci;
			return (CSA_SUCCESS);
		}

		create_auth(cl);

		/* Adjust Timeout */
		if (timeout==0) timeout = _DtCM_DEFAULT_TIMEOUT;
		timeout_tv.tv_sec =  timeout;
		timeout_tv.tv_usec = 0;
		clnt_control(cl, CLSET_TIMEOUT, (char*)&timeout_tv);		

		/* don't need to set vers_out since it should
		 * be the same as that of the udp transport
		 */
		ci->tcpcl = cl;
		if (++tcp_count > MAX_COUNT)
			/* clean up tcp connections */
			cleanup_some_connection(ci);
		*clnt = ci;
		return (CSA_SUCCESS);
	}
}

/*
 * Used instead of clnt_call by rtableX_clnt.c
 *
 * Might need locking for the client handle here since
 * it might be purged if something's wrong
 */
extern enum clnt_stat
_DtCm_clnt_call(
	_DtCm_Connection *conn,
	u_long proc,
	xdrproc_t inproc,
	caddr_t in,
	xdrproc_t outproc,
	caddr_t out,
	struct timeval tout)
{
	_DtCm_Client_Info	*ci;
	_DtCm_Transport_Type	ttype;
	enum clnt_stat status = RPC_FAILED;
	int retry = conn->retry;

	while (B_TRUE) {
		if (conn->ci == NULL)
			break;
		else {
			ci = conn->ci;
			ci->last_used = time(0);
		}

		if (conn->use == udp_transport || ci->tcpcl == NULL)
			ttype = udp_transport;
		else
			ttype = tcp_transport;

		status = clnt_call((ttype == tcp_transport ? ci->tcpcl :
				ci->udpcl), proc, inproc, in,
				outproc, out, tout);

		if ((ttype == udp_transport && status == RPC_TIMEDOUT) ||
		     (status == RPC_CANTRECV)) {

			if (retry) {
				retry--;

				/* don't retry when stat is RPC_TIMEDOUT
				 * and transpart is tcp since if the server
				 * is down, stat would be something else
				 * like RPC_CANTRECV
				 */

				/* get new client handle */
				if (get_new_client_handle(conn) == NULL)
					break;

			} else {
				/* purge the client handle */
				delete_client_info(conn->ci);
				conn->ci = NULL;
				break;
			}
		} else
			break;
	}

	if (status != RPC_SUCCESS && conn->ci != NULL) {
		_DtCm_print_errmsg(clnt_sperror((ttype == tcp_transport ? ci->tcpcl :
				ci->udpcl), ci->host));
	}
	conn->stat = status;
	return status;
}

extern CSA_return_code
_DtCm_add_registration(
	_DtCm_Client_Info *ci,
	char *cal,
	unsigned long update_type)
{
	_DtCm_Target_List *listp, *prev;
	_DtCm_Target_List *listitem;
	int result;

	if (ci == NULL || cal == NULL)
		return (CSA_E_INVALID_PARAMETER);

	for (listp = prev = ci->tlist; listp != NULL;
	     prev = listp, listp = listp->next) {
		if ((result = strcmp(listp->cal, cal)) == 0) {
			/* registered already */
			return (CSA_SUCCESS);
		} else if (result > 0)
			break;
	}

	/* register the first time, insert in list in ascending order */
	if ((listitem = (_DtCm_Target_List *)calloc(1, sizeof(_DtCm_Target_List))) == NULL)
		return (CSA_E_INSUFFICIENT_MEMORY);

	if ((listitem->cal = strdup(cal)) == NULL) {
		free(listitem);
		return (CSA_E_INSUFFICIENT_MEMORY);
	}

	listitem->update_type = update_type;

	if (prev == NULL || listp == prev)
		ci->tlist = listitem;
	else
		prev->next = listitem;
	listitem->next = listp;

	ci->nregistered++;

	return (CSA_SUCCESS);
}

extern void
_DtCm_remove_registration(_DtCm_Client_Info *ci, char *cal)
{
	_DtCm_Target_List *listp, *prev;
	_DtCm_Client_Info *c;
	int result;

	if (cal == NULL) return;

	/* if found, just increment the number of registration */
	for (listp = prev = ci->tlist; listp != NULL;
	     prev = listp, listp = listp->next) {
		if ((result = strcmp(listp->cal, cal)) == 0) {
			if (listp == prev)
				ci->tlist = listp->next;
			else
				prev->next = listp->next;

			/* free target item */
			free(listp->cal);
			free(listp);

			/*
			 * if no calendar is registered, close tcp connection
			 */
			if (--(ci->nregistered) == 0) {
				if (ci->tcpcl) {
					destroy_auth(ci->tcpcl);
					clnt_destroy(ci->tcpcl);
					ci->tcpcl = NULL;
					tcp_count--;
				}

				/* find other tcp connection for the
				 * same host
				 */
				for (c = client_cache_head; c != NULL;
				    c = c->next) {
					if ((result = strcmp(c->host,
					    ci->host)) == 0) {
						if (c->nregistered == 0 &&
						    c->tcpcl) {
							destroy_auth(c->tcpcl);
							clnt_destroy(c->tcpcl);
							c->tcpcl = NULL;
							tcp_count--;
						}
					} else if (result > 0)
						break;
				}
			}
			return;
		} else if (result > 0)
			break;
	}
	/* not found; impossible */
}

extern CSA_return_code
_DtCm_get_server_rpc_version(char *host, int *version)
{
	CSA_return_code stat;
	_DtCm_Client_Info *ci;

	if (host == NULL) {
		return (CSA_E_INVALID_PARAMETER);
	}

	if ((stat = _DtCm_create_tcp_client(host, TABLEVERS,
	    _DtCM_INITIAL_TIMEOUT, &ci)) == CSA_SUCCESS)
		*version = ci->vers_out;

	return (stat);
}

extern CSA_return_code
_DtCm_clntstat_to_csastat(enum clnt_stat clntstat)
{
	switch (clntstat) {
#if defined(SunOS)
	case RPC_N2AXLATEFAILURE:
#endif
	case RPC_UNKNOWNHOST:
		return (CSA_X_DT_E_INVALID_SERVER_LOCATION);
	case RPC_PROGNOTREGISTERED:
		return (CSA_X_DT_E_SERVICE_NOT_REGISTERED);
	case RPC_TIMEDOUT:
		return (CSA_X_DT_E_SERVER_TIMEOUT);
	default:
		return (CSA_E_SERVICE_UNAVAILABLE);
	}
}

/*****************************************************************************
 * static functions used within the file
 *****************************************************************************/

static void
create_auth(CLIENT *cl)
{
	/* Always cache the Unix style credentials. */
	if (unix_credential == NULL)
#if defined(SunOS)
		unix_credential = authsys_create_default ();
#else
		unix_credential = authunix_create_default ();
#endif

	cl->cl_auth = unix_credential;
}

static void
destroy_auth(CLIENT *cl)
{
	/* It is a no-op for unix-authentication because we always cache it.
	 * But we have to destroy it when secure RPC is used.
	 */
}

/*
 * Given a host name, find the _DtCm_Client_Info structure which contains
 * both udp and tcp handle to the server running in the host.
 */
static _DtCm_Client_Info *
get_client_info(char *host, int version)
{
	_DtCm_Client_Info *ci;
	int result;

	if (host==NULL) return(NULL);
	for (ci = client_cache_head; ci != NULL; ci = ci->next) {
		if ((result = strcmp(ci->host, host)) == 0) {
			if (ci->vers_out <= version)
				return(ci); 
		} else if (result > 0)
			break;
	}
	return(NULL);
}

static void
destroy_target_list(_DtCm_Target_List *tlist)
{
	_DtCm_Target_List *listp, *listitem;

	for (listp = tlist; listp != NULL; ) {
		listitem = listp;
		listp = listp->next;

		if (listitem->cal)
			free(listitem->cal);
		free(listitem);
	}
}

static void
destroy_client_info(_DtCm_Client_Info *ci)
{
	if (ci==NULL) return;

	if (ci->host != NULL)
		free(ci->host);
	if (ci->tcpcl) {
		destroy_auth(ci->tcpcl);
		clnt_destroy(ci->tcpcl);
		tcp_count--;
	}
	if (ci->udpcl) {
		destroy_auth(ci->udpcl);
		clnt_destroy(ci->udpcl);
	}
	destroy_target_list(ci->tlist);
	free(ci);
	cl_count--;
}

/*
 * Don't limit the number of cached connections right now.
 * Udp client handle does not use up file descriptor only space.
 * Tcp client handle is kept open only when there's at least one
 * calendar registered with the host and the user probably won't
 * be browsing more than 50 calendar at the same time.
 */
static void
insert_client_info(_DtCm_Client_Info *ci)
{
	_DtCm_Client_Info *citem;

	if (++cl_count > MAX_COUNT)
		cleanup_some_connection(ci);

	/* insert new item alphabetically */
	for (citem = client_cache_head; citem != NULL; citem = citem->next) {
		/* there shouldn't be an entry with the same host name
		 * if there's, it would be picked up in get_client_info()
		 */
		if (strcmp(citem->host, ci->host) > 0)
			break;
	}

	if (citem == NULL) {
		if (client_cache_head == NULL)
			client_cache_head = client_cache_tail = ci;
		else {
			ci->prev = client_cache_tail;
			client_cache_tail->next = ci;
			client_cache_tail = ci;
		}
	} else {
		ci->next = citem;
		ci->prev = citem->prev;
		if (citem == client_cache_head)
			client_cache_head = ci;
		else
			citem->prev->next = ci;
		citem->prev = ci;
	}

#ifdef CM_DEBUG
	fprintf(stderr, "%s: head = %d, tail = %d, newitem = %d\n",
		"insert_client_info", client_cache_head,
		client_cache_tail, ci);
	fprintf(stderr, "tcp_count = %d, cl_count = %d\n", tcp_count, cl_count);
#endif

}

/*
 * remove the client info structure from the list
 */
static void
delete_client_info(_DtCm_Client_Info *oldci)
{
	if (oldci == NULL) return;

	if (oldci == client_cache_head) {
		client_cache_head = oldci->next;
		if (client_cache_head)
			client_cache_head->prev = NULL;
	} else if (oldci == client_cache_tail) {
		client_cache_tail = oldci->prev;
		if (client_cache_tail)
			client_cache_tail->next = NULL;
	} else {
		oldci->prev->next = oldci->next;
		oldci->next->prev = oldci->prev;
	}

	if (oldci == client_cache_tail)
		client_cache_tail = NULL;

	destroy_client_info(oldci);

#ifdef CM_DEBUG
	fprintf(stderr, "%s: head = %d, tail = %d, olditem = %d\n",
		"delete_client_info", client_cache_head,
		client_cache_tail, oldci);
#endif
}

/*
 * Number of open tcp connections reaches the maximum.
 * This is very unlikely in the normal case since
 * a tcp connection is kept open if at least one calendar
 * is registered with the host and a user would not be
 * browsing a large number of calendars at one time.
 * However, when a calendar is deselected in the calendar
 * list on the multi-browser window, a lookup call using
 * the tcp connection is made after the calendar is
 * deregistered.  This keeps the tcp connection open
 * even if that's the last calendar registered with the
 * host.  This routine is used to clean up such tcp connections.
 * This is a good time to clean up connections that are not
 * used for a long time.
 */
static void
cleanup_some_connection(_DtCm_Client_Info *dontclose)
{
	_DtCm_Client_Info *ci, *oldci;
	int total = 0, deleted = 0, done = 0;

	for (ci = client_cache_head; ci != NULL; )
	{
		total++;
#ifdef HPUX
		/* clean up whole list */
		if (ci != dontclose && ci->nregistered == 0) {
#else

		if (ci != dontclose && ci->nregistered == 0 &&
		    (ci->tcpcl || (!done && ci->tcpcl == NULL) ||
		     (ci->tcpcl==NULL && (time(NULL) - ci->last_used)>DAYSEC)))
		{
			if (!done) done = 1;
#endif

			deleted++;
			oldci = ci;
			ci = ci->next;
			delete_client_info(oldci);
		} else
			ci = ci->next;
	}
#ifdef CM_DEBUG
	fprintf(stderr, "%s: total = %d, deleted = %d\n",
		"cleanup_tcp_connection", total, deleted);
#endif
}

/*
 * check registration
 * Deergister the first target:
 * if it succeeded, the old server is still running, just re-register it;
 * else assume that it's a new server so re-register the whole list again.
 */
static void
check_registration(_DtCm_Connection *conn)
{
	_DtCm_Target_List *listp, *prev;
	_DtCm_Transport_Type olduse;
	CSA_return_code	stat;

	if (conn->ci->tlist == NULL)
		return;

	olduse = conn->use;
	conn->use = udp_transport;
	conn->retry = B_FALSE;
	if ((stat = _DtCm_do_unregistration(conn, conn->ci->tlist->cal,
	    conn->ci->tlist->update_type)) == CSA_SUCCESS) {
		if (_DtCm_do_registration(conn, conn->ci->tlist->cal,
			conn->ci->tlist->update_type) != CSA_SUCCESS)
		{
			conn->ci->nregistered--;
			listp =  conn->ci->tlist;
			conn->ci->tlist = listp->next;
			free(listp->cal);
			free(listp);
		}
	} else if (stat == CSA_E_CALLBACK_NOT_REGISTERED || stat == CSA_E_FAILURE) {
		for (listp = prev = conn->ci->tlist; listp != NULL; ) {
			if (_DtCm_do_registration(conn, listp->cal,
			    listp->update_type) != CSA_SUCCESS)
			{
				conn->ci->nregistered--;
				if (listp == prev)
					conn->ci->tlist = prev = listp->next;
				else
					prev->next = listp->next;
				/* free target item */
				free(listp->cal);
				free(listp);
				listp = (prev ? prev->next : NULL);
			} else {
				prev = listp;
				listp = listp->next;
			}
		}
	}
	conn->use = olduse;
}

static _DtCm_Client_Info *
get_new_client_handle(_DtCm_Connection *conn)
{
	CLIENT *cl;
	int oldver;

	if (conn == NULL) return(NULL);

	oldver = conn->ci->vers_out;

	/* always get a udp client handle first */
#if defined(SunOS)
	cl = clnt_create_vers(conn->ci->host, TABLEPROG, &(conn->ci->vers_out),
			TABLEVERS_2, oldver, "udp");
	if (cl == NULL) {
		_DtCm_print_errmsg(clnt_spcreateerror(conn->ci->host));
	}
#else
	(void) get_client_handle(conn->ci->host, (u_int)TABLEPROG,
			&(conn->ci->vers_out), TABLEVERS_2, oldver,
			"udp", &cl);
#endif

	if (cl == NULL) {
		delete_client_info(conn->ci);
		conn->ci = NULL;
		return(NULL);
	} else {
		create_auth(cl);

		/* adjust timeout */
		timeout_tv.tv_sec = _DtCM_INITIAL_TIMEOUT;
		timeout_tv.tv_usec = 0;
		clnt_control(cl, CLSET_TIMEOUT, (char *)&timeout_tv);
		retry_tv.tv_sec = _DtCM_INITIAL_TIMEOUT + 10;
		retry_tv.tv_usec = 0;
		clnt_control(cl, CLSET_RETRY_TIMEOUT, (char *)&retry_tv);

		destroy_auth(conn->ci->udpcl);
		clnt_destroy(conn->ci->udpcl);
		conn->ci->udpcl = cl;
	}

	/* check registration */
	/* if there's anything wrong, nregistered could be zero */
	check_registration(conn);

	/* ci might be set to NULL if an rpc call failed */
	if (conn->ci == NULL)
		return (NULL);

	/* now deal with tcp handle */

	/* get rid of old handle first */
	if (conn->ci->tcpcl) {
		destroy_auth(conn->ci->tcpcl);
		clnt_destroy(conn->ci->tcpcl);
		tcp_count--;
		conn->ci->tcpcl = NULL;
	}

	if (conn->use == udp_transport) {
		return(conn->ci);
	} else {

		/* get a tcp client handle */
		oldver = conn->ci->vers_out;
#if defined(SunOS)
		cl = clnt_create_vers(conn->ci->host, TABLEPROG,
			&(conn->ci->vers_out), TABLEVERS_2, oldver, "tcp");
		if (cl == NULL)
			_DtCm_print_errmsg(clnt_spcreateerror(conn->ci->host));
#else
		(void) get_client_handle(conn->ci->host, (u_int)TABLEPROG,
			&(conn->ci->vers_out), TABLEVERS_2, oldver, "tcp",
			&cl);
#endif

		if (cl == NULL) {
			conn->ci->vers_out = oldver;
			return(NULL);
		} else {
			create_auth(cl);

			/* adjust timeout */
			timeout_tv.tv_sec = _DtCM_INITIAL_TIMEOUT;
			timeout_tv.tv_usec = 0;
			clnt_control(cl, CLSET_TIMEOUT, (char *)&timeout_tv);

			conn->ci->tcpcl = cl;
			tcp_count++;
			return(conn->ci);
		}
	}
}

/*
 * Get a client handle to a server that supports the highest
 * version between the given range.
 */
static CSA_return_code
get_client_handle(
	const char *host,
	const u_long prognum,
	u_long *vers_outp,
	const u_long vers_low,
	const u_long vers_high,
	char *nettype,
	CLIENT **clnt)
{
	CLIENT	*cl;
	u_int	vers;
	struct timeval tv;
	enum clnt_stat status;

#ifdef HPUX
	static int bumped = 0;
	struct rlimit rl;

	if (bumped == 0) {
		bumped = 1;

		/* raise the soft limit of number of file descriptor */
		getrlimit(RLIMIT_NOFILE, &rl);
		rl.rlim_cur = rl.rlim_max;
		setrlimit(RLIMIT_NOFILE, &rl);
	}
#endif

	tv.tv_sec = 1;
	tv.tv_usec = 0;

	*clnt = NULL;
	for (vers = vers_high; vers >= vers_low; vers--) {
#if defined(__hpux)
	        if ((cl = clnt_create((char *)host, prognum, vers, nettype)) != NULL) {
#else

		if ((cl = clnt_create(host, prognum, vers, nettype)) != NULL) {
#endif
			clnt_control(cl, CLSET_TIMEOUT, (char *)&tv);
			status = clnt_call(cl, 0, (xdrproc_t) xdr_void, 
					   (char *)NULL, (xdrproc_t) xdr_void,
					   (char *)NULL, tv);

			if (status == RPC_SUCCESS) {
				*vers_outp = vers;
				*clnt = cl;
				return (CSA_SUCCESS);
			} else if (status != RPC_PROGVERSMISMATCH) {
				return (_DtCm_clntstat_to_csastat(status));
			}
		} else {
			_DtCm_print_errmsg(clnt_spcreateerror((char *) host));
			return (_DtCm_clntstat_to_csastat(rpc_createerr.cf_stat));
		}
	}

	/* cannot find a server that supports a version in the given range */
	/* Probably will never get here */
	return (CSA_E_SERVICE_UNAVAILABLE);
}

static CSA_return_code
regstat4_to_dtcmstatus(Registration_Status_4 stat4)
{
	switch (stat4) {
	case registered_4:
		return (CSA_SUCCESS);

	case deregistered_4:
		return (CSA_SUCCESS);

	case reg_notable_4:
		return (CSA_E_CALENDAR_NOT_EXIST);

	case failed_4:
	case confused_4:
		return (CSA_E_FAILURE);
	}
}

