2 * CDE - Common Desktop Environment
4 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
6 * These libraries and programs are free software; you can
7 * redistribute them and/or modify them under the terms of the GNU
8 * Lesser General Public License as published by the Free Software
9 * Foundation; either version 2 of the License, or (at your option)
12 * These libraries and programs are distributed in the hope that
13 * they will be useful, but WITHOUT ANY WARRANTY; without even the
14 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU Lesser General Public License for more
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with these libraries and programs; if not, write
20 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21 * Floor, Boston, MA 02110-1301 USA
23 /* $TOG: connection.c /main/4 1999/10/14 17:47:12 mgreess $ */
25 * (c) Copyright 1993, 1994 Hewlett-Packard Company
26 * (c) Copyright 1993, 1994 International Business Machines Corp.
27 * (c) Copyright 1993, 1994 Novell, Inc.
28 * (c) Copyright 1993, 1994 Sun Microsystems, Inc.
32 * This file manages server connections.
35 #include <EUSCompat.h>
43 #include <sys/resource.h>
45 #include "connection.h"
52 #include "convert2-4.h"
53 #include "convert3-4.h"
62 static struct timeval timeout_tv;
63 static struct timeval retry_tv;
64 static AUTH *unix_credential = NULL;
67 static _DtCm_Client_Info *client_cache_head = NULL;
68 static _DtCm_Client_Info *client_cache_tail = NULL;
70 /*****************************************************************************
71 * forward declaration of static functions.
72 *****************************************************************************/
73 static void create_auth(CLIENT *cl);
74 static void destroy_auth(CLIENT *cl);
75 static _DtCm_Client_Info * get_client_info(char *host, int version);
76 static void destroy_target_list(_DtCm_Target_List *tlist);
77 static void destroy_client_info(_DtCm_Client_Info *ci);
78 static void insert_client_info(_DtCm_Client_Info *ci);
79 static void delete_client_info(_DtCm_Client_Info *oldci);
80 static void cleanup_some_connection(_DtCm_Client_Info *dontclose);
81 static void check_registration(_DtCm_Connection *conn);
82 static _DtCm_Client_Info * get_new_client_handle(_DtCm_Connection *conn);
84 static CSA_return_code get_client_handle(const char *host, const u_int prognum,
85 u_long *vers_outp, const u_long vers_low,
86 const u_long vers_high, char *nettype,
89 static CSA_return_code get_client_handle(const char *host, const u_long prognum,
90 u_long *vers_outp, const u_long vers_low,
91 const u_long vers_high, char *nettype,
94 static CSA_return_code regstat4_to_dtcmstatus(Registration_Status_4 stat4);
96 extern CSA_return_code
97 _DtCm_create_udp_client(
101 _DtCm_Client_Info **clnt)
103 CSA_return_code stat;
104 _DtCm_Client_Info *ci;
108 if (host == NULL || clnt == NULL)
109 return (CSA_E_INVALID_PARAMETER);
111 /* if client info is found, we have at least the udp handle */
112 if (((*clnt) = get_client_info(host, version)) != NULL) {
113 return (CSA_SUCCESS);
116 #if defined(SunOS) || defined(USL)
117 cl = clnt_create_vers(host, TABLEPROG,
118 &vers_out, TABLEVERS_2, version, "udp");
120 _DtCm_print_errmsg(clnt_spcreateerror(host));
121 return (_DtCm_clntstat_to_csastat(rpc_createerr.cf_stat));
124 stat = get_client_handle(host, (u_int)TABLEPROG, &vers_out, TABLEVERS_2,
125 version, "udp", &cl);
126 if (stat != CSA_SUCCESS)
130 /* if version is lower than requested, check the list again */
131 if (vers_out < version) {
132 if ((ci = get_client_info(host, vers_out)) != NULL) {
135 return (CSA_SUCCESS);
142 if (timeout==0) timeout = _DtCM_DEFAULT_TIMEOUT;
143 timeout_tv.tv_sec = timeout;
144 timeout_tv.tv_usec = 0;
145 clnt_control(cl, CLSET_TIMEOUT, (char*)&timeout_tv);
148 time rpc waits for server to reply before retransmission =
149 'timeout'. since the retry timeout is set to timeout + 10;
150 this guarantees there won't
151 be any retransmisssions resulting in duplicate
152 transactions in the database.
155 retry_tv.tv_sec = timeout + 10;
156 retry_tv.tv_usec = 0;
157 clnt_control(cl, CLSET_RETRY_TIMEOUT, (char*)&retry_tv);
159 if ((ci = (_DtCm_Client_Info *)calloc(1, sizeof(_DtCm_Client_Info))) == NULL) {
162 return (CSA_E_INSUFFICIENT_MEMORY);
165 if ((ci->host = strdup(host)) == NULL) {
169 return (CSA_E_INSUFFICIENT_MEMORY);
173 ci->vers_out = vers_out;
174 insert_client_info(ci);
176 return (CSA_SUCCESS);
180 * Creates tcp client handle. Used for calls that potentially return
181 * large amount of data. If it fails to create a tcp client handle,
182 * a udp client handle will be returned.
184 extern CSA_return_code
185 _DtCm_create_tcp_client(
189 _DtCm_Client_Info **clnt)
191 CSA_return_code stat;
192 _DtCm_Client_Info *ci;
196 if (host == NULL || clnt == NULL)
197 return (CSA_E_INVALID_PARAMETER);
199 /* Get a udp client handle. This serves two purposes: */
200 /* - to get a udp handle for an old server which talks only udp */
201 /* - to invoke a server through inetd since only udp is registered.*/
203 if ((stat = _DtCm_create_udp_client(host, version, timeout, &ci))
206 } else if (ci->tcpcl) {
208 return (CSA_SUCCESS);
210 /* create tcp connection */
211 #if defined(SunOS) || defined(USL)
212 cl = clnt_create_vers(host, TABLEPROG, &vers_out,
213 TABLEVERS_2, version, "tcp");
215 stat = get_client_handle(host, (u_int)TABLEPROG, &vers_out,
216 TABLEVERS_2, version, "tcp", &cl);
219 /* if can't create tcp connection, use udp */
221 _DtCm_print_errmsg(clnt_spcreateerror(host));
223 return (CSA_SUCCESS);
229 if (timeout==0) timeout = _DtCM_DEFAULT_TIMEOUT;
230 timeout_tv.tv_sec = timeout;
231 timeout_tv.tv_usec = 0;
232 clnt_control(cl, CLSET_TIMEOUT, (char*)&timeout_tv);
234 /* dont need to set vers_out since it should
235 * be the same as that of the udp transport
238 if (++tcp_count > MAX_COUNT)
239 /* clean up tcp connections */
240 cleanup_some_connection(ci);
242 return (CSA_SUCCESS);
247 * Used instead of clnt_call by rtableX_clnt.c
249 * Might need locking for the client handle here since
250 * it might be purged if something's wrong
252 extern enum clnt_stat
254 _DtCm_Connection *conn,
262 _DtCm_Client_Info *ci;
263 _DtCm_Transport_Type ttype;
264 enum clnt_stat status = RPC_FAILED;
265 int retry = conn->retry;
268 if (conn->ci == NULL)
272 ci->last_used = time(0);
275 if (conn->use == udp_transport || ci->tcpcl == NULL)
276 ttype = udp_transport;
278 ttype = tcp_transport;
280 status = clnt_call((ttype == tcp_transport ? ci->tcpcl :
281 ci->udpcl), proc, inproc, in,
284 if ((ttype == udp_transport && status == RPC_TIMEDOUT) ||
285 (status == RPC_CANTRECV)) {
290 /* don't retry when stat is RPC_TIMEDOUT
291 * and transpart is tcp since if the server
292 * is down, stat would be something else
296 /* get new client handle */
297 if (get_new_client_handle(conn) == NULL)
301 /* purge the client handle */
302 delete_client_info(conn->ci);
310 if (status != RPC_SUCCESS && conn->ci != NULL) {
311 _DtCm_print_errmsg(clnt_sperror((ttype == tcp_transport ? ci->tcpcl :
312 ci->udpcl), ci->host));
318 extern CSA_return_code
319 _DtCm_add_registration(
320 _DtCm_Client_Info *ci,
322 unsigned long update_type)
324 _DtCm_Target_List *listp, *prev;
325 _DtCm_Target_List *listitem;
328 if (ci == NULL || cal == NULL)
329 return (CSA_E_INVALID_PARAMETER);
331 for (listp = prev = ci->tlist; listp != NULL;
332 prev = listp, listp = listp->next) {
333 if ((result = strcmp(listp->cal, cal)) == 0) {
334 /* registered already */
335 return (CSA_SUCCESS);
336 } else if (result > 0)
340 /* register the first time, insert in list in ascending order */
341 if ((listitem = (_DtCm_Target_List *)calloc(1, sizeof(_DtCm_Target_List))) == NULL)
342 return (CSA_E_INSUFFICIENT_MEMORY);
344 if ((listitem->cal = strdup(cal)) == NULL) {
346 return (CSA_E_INSUFFICIENT_MEMORY);
349 listitem->update_type = update_type;
351 if (prev == NULL || listp == prev)
352 ci->tlist = listitem;
354 prev->next = listitem;
355 listitem->next = listp;
359 return (CSA_SUCCESS);
363 _DtCm_remove_registration(_DtCm_Client_Info *ci, char *cal)
365 _DtCm_Target_List *listp, *prev;
366 _DtCm_Client_Info *c;
369 if (cal == NULL) return;
371 /* if found, just increment the number of registration */
372 for (listp = prev = ci->tlist; listp != NULL;
373 prev = listp, listp = listp->next) {
374 if ((result = strcmp(listp->cal, cal)) == 0) {
376 ci->tlist = listp->next;
378 prev->next = listp->next;
380 /* free target item */
385 * if no calendar is registered, close tcp connection
387 if (--(ci->nregistered) == 0) {
389 destroy_auth(ci->tcpcl);
390 clnt_destroy(ci->tcpcl);
395 /* find other tcp connection for the
398 for (c = client_cache_head; c != NULL;
400 if ((result = strcmp(c->host,
402 if (c->nregistered == 0 &&
404 destroy_auth(c->tcpcl);
405 clnt_destroy(c->tcpcl);
409 } else if (result > 0)
414 } else if (result > 0)
417 /* not found; impossible */
420 extern CSA_return_code
421 _DtCm_get_server_rpc_version(char *host, int *version)
423 CSA_return_code stat;
424 _DtCm_Client_Info *ci;
427 return (CSA_E_INVALID_PARAMETER);
430 if ((stat = _DtCm_create_tcp_client(host, TABLEVERS,
431 _DtCM_INITIAL_TIMEOUT, &ci)) == CSA_SUCCESS)
432 *version = ci->vers_out;
437 extern CSA_return_code
438 _DtCm_clntstat_to_csastat(enum clnt_stat clntstat)
441 #if defined(SunOS) || defined(USL)
442 case RPC_N2AXLATEFAILURE:
444 case RPC_UNKNOWNHOST:
445 return (CSA_X_DT_E_INVALID_SERVER_LOCATION);
446 case RPC_PROGNOTREGISTERED:
447 return (CSA_X_DT_E_SERVICE_NOT_REGISTERED);
449 return (CSA_X_DT_E_SERVER_TIMEOUT);
451 return (CSA_E_SERVICE_UNAVAILABLE);
455 /*****************************************************************************
456 * static functions used within the file
457 *****************************************************************************/
460 create_auth(CLIENT *cl)
462 /* Always cache the Unix style credentials. */
463 if (unix_credential == NULL)
464 #if defined(SunOS) || defined(USL)
465 unix_credential = authsys_create_default ();
467 unix_credential = authunix_create_default ();
470 cl->cl_auth = unix_credential;
474 destroy_auth(CLIENT *cl)
476 /* It is a no-op for unix-authentication because we always cache it.
477 * But we have to destroy it when secure RPC is used.
482 * Given a host name, find the _DtCm_Client_Info structure which contains
483 * both udp and tcp handle to the server running in the host.
485 static _DtCm_Client_Info *
486 get_client_info(char *host, int version)
488 _DtCm_Client_Info *ci;
491 if (host==NULL) return(NULL);
492 for (ci = client_cache_head; ci != NULL; ci = ci->next) {
493 if ((result = strcmp(ci->host, host)) == 0) {
494 if (ci->vers_out <= version)
496 } else if (result > 0)
503 destroy_target_list(_DtCm_Target_List *tlist)
505 _DtCm_Target_List *listp, *listitem;
507 for (listp = tlist; listp != NULL; ) {
518 destroy_client_info(_DtCm_Client_Info *ci)
520 if (ci==NULL) return;
522 if (ci->host != NULL)
525 destroy_auth(ci->tcpcl);
526 clnt_destroy(ci->tcpcl);
530 destroy_auth(ci->udpcl);
531 clnt_destroy(ci->udpcl);
533 destroy_target_list(ci->tlist);
539 * Dont limit the number of cached connections right now.
540 * Udp client handle does not use up file descriptor only space.
541 * Tcp client handle is kept open only when there's at least one
542 * calendar registered with the host and the user probably won't
543 * be browsing more than 50 calendar at the same time.
546 insert_client_info(_DtCm_Client_Info *ci)
548 _DtCm_Client_Info *citem;
550 if (++cl_count > MAX_COUNT)
551 cleanup_some_connection(ci);
553 /* insert new item alphabetically */
554 for (citem = client_cache_head; citem != NULL; citem = citem->next) {
555 /* there shouldn't be an entry with the same host name
556 * if there's, it would be picked up in get_client_info()
558 if (strcmp(citem->host, ci->host) > 0)
563 if (client_cache_head == NULL)
564 client_cache_head = client_cache_tail = ci;
566 ci->prev = client_cache_tail;
567 client_cache_tail->next = ci;
568 client_cache_tail = ci;
572 ci->prev = citem->prev;
573 if (citem == client_cache_head)
574 client_cache_head = ci;
576 citem->prev->next = ci;
581 fprintf(stderr, "%s: head = %d, tail = %d, newitem = %d\n",
582 "insert_client_info", client_cache_head,
583 client_cache_tail, ci);
584 fprintf(stderr, "tcp_count = %d, cl_count = %d\n", tcp_count, cl_count);
590 * remove the client info structure from the list
593 delete_client_info(_DtCm_Client_Info *oldci)
595 if (oldci == NULL) return;
597 if (oldci == client_cache_head) {
598 client_cache_head = oldci->next;
599 if (client_cache_head)
600 client_cache_head->prev = NULL;
601 } else if (oldci == client_cache_tail) {
602 client_cache_tail = oldci->prev;
603 if (client_cache_tail)
604 client_cache_tail->next = NULL;
606 oldci->prev->next = oldci->next;
607 oldci->next->prev = oldci->prev;
610 if (oldci == client_cache_tail)
611 client_cache_tail = NULL;
613 destroy_client_info(oldci);
616 fprintf(stderr, "%s: head = %d, tail = %d, olditem = %d\n",
617 "delete_client_info", client_cache_head,
618 client_cache_tail, oldci);
623 * Number of open tcp connections reaches the maximum.
624 * This is very unlikely in the normal case since
625 * a tcp connection is kept open if at least one calendar
626 * is registered with the host and a user would not be
627 * browsing a large number of calendars at one time.
628 * However, when a calendar is deselected in the calendar
629 * list on the multi-browser window, a lookup call using
630 * the tcp connection is made after the calendar is
631 * deregistered. This keeps the tcp connection open
632 * even if that's the last calendar registered with the
633 * host. This routine is used to clean up such tcp connections.
634 * This is a good time to clean up connections that are not
635 * used for a long time.
638 cleanup_some_connection(_DtCm_Client_Info *dontclose)
640 _DtCm_Client_Info *ci, *oldci;
641 int total = 0, deleted = 0, done = 0;
643 for (ci = client_cache_head; ci != NULL; )
647 /* clean up whole list */
648 if (ci != dontclose && ci->nregistered == 0) {
651 if (ci != dontclose && ci->nregistered == 0 &&
652 (ci->tcpcl || (!done && ci->tcpcl == NULL) ||
653 (ci->tcpcl==NULL && (time(NULL) - ci->last_used)>DAYSEC)))
661 delete_client_info(oldci);
666 fprintf(stderr, "%s: total = %d, deleted = %d\n",
667 "cleanup_tcp_connection", total, deleted);
673 * Deergister the first target:
674 * if it succeeded, the old server is still running, just re-register it;
675 * else assume that it's a new server so re-register the whole list again.
678 check_registration(_DtCm_Connection *conn)
680 _DtCm_Target_List *listp, *prev;
681 _DtCm_Transport_Type olduse;
682 CSA_return_code stat;
684 if (conn->ci->tlist == NULL)
688 conn->use = udp_transport;
689 conn->retry = B_FALSE;
690 if ((stat = _DtCm_do_unregistration(conn, conn->ci->tlist->cal,
691 conn->ci->tlist->update_type)) == CSA_SUCCESS) {
692 if (_DtCm_do_registration(conn, conn->ci->tlist->cal,
693 conn->ci->tlist->update_type) != CSA_SUCCESS)
695 conn->ci->nregistered--;
696 listp = conn->ci->tlist;
697 conn->ci->tlist = listp->next;
701 } else if (stat == CSA_E_CALLBACK_NOT_REGISTERED || stat == CSA_E_FAILURE) {
702 for (listp = prev = conn->ci->tlist; listp != NULL; ) {
703 if (_DtCm_do_registration(conn, listp->cal,
704 listp->update_type) != CSA_SUCCESS)
706 conn->ci->nregistered--;
708 conn->ci->tlist = prev = listp->next;
710 prev->next = listp->next;
711 /* free target item */
714 listp = (prev ? prev->next : NULL);
724 static _DtCm_Client_Info *
725 get_new_client_handle(_DtCm_Connection *conn)
730 if (conn == NULL) return(NULL);
732 oldver = conn->ci->vers_out;
734 /* always get a udp client handle first */
735 #if defined(SunOS) || defined(USL)
736 cl = clnt_create_vers(conn->ci->host, TABLEPROG, &(conn->ci->vers_out),
737 TABLEVERS_2, oldver, "udp");
739 _DtCm_print_errmsg(clnt_spcreateerror(conn->ci->host));
742 (void) get_client_handle(conn->ci->host, (u_int)TABLEPROG,
743 &(conn->ci->vers_out), TABLEVERS_2, oldver,
748 delete_client_info(conn->ci);
755 timeout_tv.tv_sec = _DtCM_INITIAL_TIMEOUT;
756 timeout_tv.tv_usec = 0;
757 clnt_control(cl, CLSET_TIMEOUT, (char *)&timeout_tv);
758 retry_tv.tv_sec = _DtCM_INITIAL_TIMEOUT + 10;
759 retry_tv.tv_usec = 0;
760 clnt_control(cl, CLSET_RETRY_TIMEOUT, (char *)&retry_tv);
762 destroy_auth(conn->ci->udpcl);
763 clnt_destroy(conn->ci->udpcl);
764 conn->ci->udpcl = cl;
767 /* check registration */
768 /* if there's anything wrong, nregistered could be zero */
769 check_registration(conn);
771 /* ci might be set to NULL if an rpc call failed */
772 if (conn->ci == NULL)
775 /* now deal with tcp handle */
777 /* get rid of old handle first */
778 if (conn->ci->tcpcl) {
779 destroy_auth(conn->ci->tcpcl);
780 clnt_destroy(conn->ci->tcpcl);
782 conn->ci->tcpcl = NULL;
785 if (conn->use == udp_transport) {
789 /* get a tcp client handle */
790 oldver = conn->ci->vers_out;
791 #if defined(SunOS) || defined(USL)
792 cl = clnt_create_vers(conn->ci->host, TABLEPROG,
793 &(conn->ci->vers_out), TABLEVERS_2, oldver, "tcp");
795 _DtCm_print_errmsg(clnt_spcreateerror(conn->ci->host));
797 (void) get_client_handle(conn->ci->host, (u_int)TABLEPROG,
798 &(conn->ci->vers_out), TABLEVERS_2, oldver, "tcp",
803 conn->ci->vers_out = oldver;
809 timeout_tv.tv_sec = _DtCM_INITIAL_TIMEOUT;
810 timeout_tv.tv_usec = 0;
811 clnt_control(cl, CLSET_TIMEOUT, (char *)&timeout_tv);
813 conn->ci->tcpcl = cl;
821 * Get a client handle to a server that supports the highest
822 * version between the given range.
824 static CSA_return_code
830 const u_long prognum,
833 const u_long vers_low,
834 const u_long vers_high,
841 enum clnt_stat status;
844 static int bumped = 0;
850 /* raise the soft limit of number of file descriptor */
851 getrlimit(RLIMIT_NOFILE, &rl);
852 rl.rlim_cur = rl.rlim_max;
853 setrlimit(RLIMIT_NOFILE, &rl);
859 * A longer timeout value may be necessay - for example if
860 * the system is bogged down and/or rpc.cmsd is not running.
862 * The value below is the same value used when a ToolTalk app connects
863 * to the ToolTalk database server (lib/tt/lib/db/tt_db_client.C).
872 for (vers = vers_high; vers >= vers_low; vers--) {
873 #if defined(__osf__) || defined(__hpux)
874 if ((cl = clnt_create((char *)host, prognum, vers, nettype)) != NULL) {
877 if ((cl = clnt_create(host, prognum, vers, nettype)) != NULL) {
879 clnt_control(cl, CLSET_TIMEOUT, (char *)&tv);
880 status = clnt_call(cl, 0, (xdrproc_t) xdr_void,
881 (char *)NULL, (xdrproc_t) xdr_void,
884 if (status == RPC_SUCCESS) {
889 * Set the timeout back to the original.
892 clnt_control(cl, CLSET_TIMEOUT, (char *)&tv);
894 return (CSA_SUCCESS);
895 } else if (status != RPC_PROGVERSMISMATCH) {
896 return (_DtCm_clntstat_to_csastat(status));
899 _DtCm_print_errmsg(clnt_spcreateerror((char *) host));
900 return (_DtCm_clntstat_to_csastat(rpc_createerr.cf_stat));
904 /* cannot find a server that supports a version in the given range */
905 /* Probably will never get here */
906 return (CSA_E_SERVICE_UNAVAILABLE);
909 static CSA_return_code
910 regstat4_to_dtcmstatus(Registration_Status_4 stat4)
914 return (CSA_SUCCESS);
917 return (CSA_SUCCESS);
920 return (CSA_E_CALENDAR_NOT_EXIST);
924 return (CSA_E_FAILURE);