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 librararies 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;
69 static char *svcfmt = "Error on server %s\n";
71 /*****************************************************************************
72 * forward declaration of static functions.
73 *****************************************************************************/
74 static void create_auth(CLIENT *cl);
75 static void destroy_auth(CLIENT *cl);
76 static _DtCm_Client_Info * get_client_info(char *host, int version);
77 static void destroy_target_list(_DtCm_Target_List *tlist);
78 static void destroy_client_info(_DtCm_Client_Info *ci);
79 static void insert_client_info(_DtCm_Client_Info *ci);
80 static void delete_client_info(_DtCm_Client_Info *oldci);
81 static void cleanup_some_connection(_DtCm_Client_Info *dontclose);
82 static void check_registration(_DtCm_Connection *conn);
83 static _DtCm_Client_Info * get_new_client_handle(_DtCm_Connection *conn);
85 static CSA_return_code get_client_handle(const char *host, const u_int prognum,
86 u_long *vers_outp, const u_long vers_low,
87 const u_long vers_high, char *nettype,
90 static CSA_return_code get_client_handle(const char *host, const u_long prognum,
91 u_long *vers_outp, const u_long vers_low,
92 const u_long vers_high, char *nettype,
95 static CSA_return_code regstat4_to_dtcmstatus(Registration_Status_4 stat4);
97 extern CSA_return_code
98 _DtCm_create_udp_client(
102 _DtCm_Client_Info **clnt)
104 CSA_return_code stat;
105 _DtCm_Client_Info *ci;
109 if (host == NULL || clnt == NULL)
110 return (CSA_E_INVALID_PARAMETER);
112 /* if client info is found, we have at least the udp handle */
113 if (((*clnt) = get_client_info(host, version)) != NULL) {
114 return (CSA_SUCCESS);
117 #if defined(SunOS) || defined(USL) || defined(__uxp__)
118 cl = clnt_create_vers(host, TABLEPROG,
119 &vers_out, TABLEVERS_2, version, "udp");
121 _DtCm_print_errmsg(clnt_spcreateerror(host));
122 return (_DtCm_clntstat_to_csastat(rpc_createerr.cf_stat));
125 stat = get_client_handle(host, (u_int)TABLEPROG, &vers_out, TABLEVERS_2,
126 version, "udp", &cl);
127 if (stat != CSA_SUCCESS)
131 /* if version is lower than requested, check the list again */
132 if (vers_out < version) {
133 if ((ci = get_client_info(host, vers_out)) != NULL) {
136 return (CSA_SUCCESS);
143 if (timeout==0) timeout = _DtCM_DEFAULT_TIMEOUT;
144 timeout_tv.tv_sec = timeout;
145 timeout_tv.tv_usec = 0;
146 clnt_control(cl, CLSET_TIMEOUT, (char*)&timeout_tv);
149 time rpc waits for server to reply before retransmission =
150 'timeout'. since the retry timeout is set to timeout + 10;
151 this guarantees there won't
152 be any retransmisssions resulting in duplicate
153 transactions in the database.
156 retry_tv.tv_sec = timeout + 10;
157 retry_tv.tv_usec = 0;
158 clnt_control(cl, CLSET_RETRY_TIMEOUT, (char*)&retry_tv);
160 if ((ci = (_DtCm_Client_Info *)calloc(1, sizeof(_DtCm_Client_Info))) == NULL) {
163 return (CSA_E_INSUFFICIENT_MEMORY);
166 if ((ci->host = strdup(host)) == NULL) {
170 return (CSA_E_INSUFFICIENT_MEMORY);
174 ci->vers_out = vers_out;
175 insert_client_info(ci);
177 return (CSA_SUCCESS);
181 * Creates tcp client handle. Used for calls that potentially return
182 * large amount of data. If it fails to create a tcp client handle,
183 * a udp client handle will be returned.
185 extern CSA_return_code
186 _DtCm_create_tcp_client(
190 _DtCm_Client_Info **clnt)
192 CSA_return_code stat;
193 _DtCm_Client_Info *ci;
197 if (host == NULL || clnt == NULL)
198 return (CSA_E_INVALID_PARAMETER);
200 /* Get a udp client handle. This serves two purposes: */
201 /* - to get a udp handle for an old server which talks only udp */
202 /* - to invoke a server through inetd since only udp is registered.*/
204 if ((stat = _DtCm_create_udp_client(host, version, timeout, &ci))
207 } else if (ci->tcpcl) {
209 return (CSA_SUCCESS);
211 /* create tcp connection */
212 #if defined(SunOS) || defined(USL) || defined(__uxp__)
213 cl = clnt_create_vers(host, TABLEPROG, &vers_out,
214 TABLEVERS_2, version, "tcp");
216 stat = get_client_handle(host, (u_int)TABLEPROG, &vers_out,
217 TABLEVERS_2, version, "tcp", &cl);
220 /* if can't create tcp connection, use udp */
222 _DtCm_print_errmsg(clnt_spcreateerror(host));
224 return (CSA_SUCCESS);
230 if (timeout==0) timeout = _DtCM_DEFAULT_TIMEOUT;
231 timeout_tv.tv_sec = timeout;
232 timeout_tv.tv_usec = 0;
233 clnt_control(cl, CLSET_TIMEOUT, (char*)&timeout_tv);
235 /* dont need to set vers_out since it should
236 * be the same as that of the udp transport
239 if (++tcp_count > MAX_COUNT)
240 /* clean up tcp connections */
241 cleanup_some_connection(ci);
243 return (CSA_SUCCESS);
248 * Used instead of clnt_call by rtableX_clnt.c
250 * Might need locking for the client handle here since
251 * it might be purged if something's wrong
253 extern enum clnt_stat
255 _DtCm_Connection *conn,
263 _DtCm_Client_Info *ci;
264 _DtCm_Transport_Type ttype;
265 enum clnt_stat status = RPC_FAILED;
267 int retry = conn->retry;
270 if (conn->ci == NULL)
274 ci->last_used = time(0);
277 if (conn->use == udp_transport || ci->tcpcl == NULL)
278 ttype = udp_transport;
280 ttype = tcp_transport;
282 status = clnt_call((ttype == tcp_transport ? ci->tcpcl :
283 ci->udpcl), proc, inproc, in,
286 if ((ttype == udp_transport && status == RPC_TIMEDOUT) ||
287 (status == RPC_CANTRECV)) {
292 /* don't retry when stat is RPC_TIMEDOUT
293 * and transpart is tcp since if the server
294 * is down, stat would be something else
298 /* get new client handle */
299 if (get_new_client_handle(conn) == NULL)
303 /* purge the client handle */
304 delete_client_info(conn->ci);
312 if (status != RPC_SUCCESS && conn->ci != NULL) {
313 _DtCm_print_errmsg(clnt_sperror((ttype == tcp_transport ? ci->tcpcl :
314 ci->udpcl), ci->host));
320 extern CSA_return_code
321 _DtCm_add_registration(
322 _DtCm_Client_Info *ci,
324 unsigned long update_type)
326 _DtCm_Target_List *listp, *prev;
327 _DtCm_Target_List *listitem;
331 if (ci == NULL || cal == NULL)
332 return (CSA_E_INVALID_PARAMETER);
334 for (listp = prev = ci->tlist; listp != NULL;
335 prev = listp, listp = listp->next) {
336 if ((result = strcmp(listp->cal, cal)) == 0) {
337 /* registered already */
338 return (CSA_SUCCESS);
339 } else if (result > 0)
343 /* register the first time, insert in list in ascending order */
344 if ((listitem = (_DtCm_Target_List *)calloc(1, sizeof(_DtCm_Target_List))) == NULL)
345 return (CSA_E_INSUFFICIENT_MEMORY);
347 if ((listitem->cal = strdup(cal)) == NULL) {
349 return (CSA_E_INSUFFICIENT_MEMORY);
352 listitem->update_type = update_type;
354 if (prev == NULL || listp == prev)
355 ci->tlist = listitem;
357 prev->next = listitem;
358 listitem->next = listp;
362 return (CSA_SUCCESS);
366 _DtCm_remove_registration(_DtCm_Client_Info *ci, char *cal)
368 _DtCm_Target_List *listp, *prev;
369 _DtCm_Client_Info *c;
372 if (cal == NULL) return;
374 /* if found, just increment the number of registration */
375 for (listp = prev = ci->tlist; listp != NULL;
376 prev = listp, listp = listp->next) {
377 if ((result = strcmp(listp->cal, cal)) == 0) {
379 ci->tlist = listp->next;
381 prev->next = listp->next;
383 /* free target item */
388 * if no calendar is registered, close tcp connection
390 if (--(ci->nregistered) == 0) {
392 destroy_auth(ci->tcpcl);
393 clnt_destroy(ci->tcpcl);
398 /* find other tcp connection for the
401 for (c = client_cache_head; c != NULL;
403 if ((result = strcmp(c->host,
405 if (c->nregistered == 0 &&
407 destroy_auth(c->tcpcl);
408 clnt_destroy(c->tcpcl);
412 } else if (result > 0)
417 } else if (result > 0)
420 /* not found; impossible */
423 extern CSA_return_code
424 _DtCm_get_server_rpc_version(char *host, int *version)
426 CSA_return_code stat;
427 _DtCm_Client_Info *ci;
430 return (CSA_E_INVALID_PARAMETER);
433 if ((stat = _DtCm_create_tcp_client(host, TABLEVERS,
434 _DtCM_INITIAL_TIMEOUT, &ci)) == CSA_SUCCESS)
435 *version = ci->vers_out;
440 extern CSA_return_code
441 _DtCm_clntstat_to_csastat(enum clnt_stat clntstat)
444 #if defined(SunOS) || defined(USL) || defined(__uxp__)
445 case RPC_N2AXLATEFAILURE:
447 case RPC_UNKNOWNHOST:
448 return (CSA_X_DT_E_INVALID_SERVER_LOCATION);
449 case RPC_PROGNOTREGISTERED:
450 return (CSA_X_DT_E_SERVICE_NOT_REGISTERED);
452 return (CSA_X_DT_E_SERVER_TIMEOUT);
454 return (CSA_E_SERVICE_UNAVAILABLE);
458 /*****************************************************************************
459 * static functions used within the file
460 *****************************************************************************/
463 create_auth(CLIENT *cl)
465 /* Always cache the Unix style credentials. */
466 if (unix_credential == NULL)
467 #if defined(SunOS) || defined(USL) || defined(__uxp__)
468 unix_credential = authsys_create_default ();
470 unix_credential = authunix_create_default ();
473 cl->cl_auth = unix_credential;
477 destroy_auth(CLIENT *cl)
479 /* It is a no-op for unix-authentication because we always cache it.
480 * But we have to destroy it when secure RPC is used.
485 * Given a host name, find the _DtCm_Client_Info structure which contains
486 * both udp and tcp handle to the server running in the host.
488 static _DtCm_Client_Info *
489 get_client_info(char *host, int version)
491 _DtCm_Client_Info *ci;
494 if (host==NULL) return(NULL);
495 for (ci = client_cache_head; ci != NULL; ci = ci->next) {
496 if ((result = strcmp(ci->host, host)) == 0) {
497 if (ci->vers_out <= version)
499 } else if (result > 0)
506 destroy_target_list(_DtCm_Target_List *tlist)
508 _DtCm_Target_List *listp, *listitem;
510 for (listp = tlist; listp != NULL; ) {
521 destroy_client_info(_DtCm_Client_Info *ci)
523 if (ci==NULL) return;
525 if (ci->host != NULL)
528 destroy_auth(ci->tcpcl);
529 clnt_destroy(ci->tcpcl);
533 destroy_auth(ci->udpcl);
534 clnt_destroy(ci->udpcl);
536 destroy_target_list(ci->tlist);
542 * Dont limit the number of cached connections right now.
543 * Udp client handle does not use up file descriptor only space.
544 * Tcp client handle is kept open only when there's at least one
545 * calendar registered with the host and the user probably won't
546 * be browsing more than 50 calendar at the same time.
549 insert_client_info(_DtCm_Client_Info *ci)
551 _DtCm_Client_Info *citem;
553 if (++cl_count > MAX_COUNT)
554 cleanup_some_connection(ci);
556 /* insert new item alphabetically */
557 for (citem = client_cache_head; citem != NULL; citem = citem->next) {
558 /* there shouldn't be an entry with the same host name
559 * if there's, it would be picked up in get_client_info()
561 if (strcmp(citem->host, ci->host) > 0)
566 if (client_cache_head == NULL)
567 client_cache_head = client_cache_tail = ci;
569 ci->prev = client_cache_tail;
570 client_cache_tail->next = ci;
571 client_cache_tail = ci;
575 ci->prev = citem->prev;
576 if (citem == client_cache_head)
577 client_cache_head = ci;
579 citem->prev->next = ci;
584 fprintf(stderr, "%s: head = %d, tail = %d, newitem = %d\n",
585 "insert_client_info", client_cache_head,
586 client_cache_tail, ci);
587 fprintf(stderr, "tcp_count = %d, cl_count = %d\n", tcp_count, cl_count);
593 * remove the client info structure from the list
596 delete_client_info(_DtCm_Client_Info *oldci)
598 if (oldci == NULL) return;
600 if (oldci == client_cache_head) {
601 client_cache_head = oldci->next;
602 if (client_cache_head)
603 client_cache_head->prev = NULL;
604 } else if (oldci == client_cache_tail) {
605 client_cache_tail = oldci->prev;
606 if (client_cache_tail)
607 client_cache_tail->next = NULL;
609 oldci->prev->next = oldci->next;
610 oldci->next->prev = oldci->prev;
613 if (oldci == client_cache_tail)
614 client_cache_tail = NULL;
616 destroy_client_info(oldci);
619 fprintf(stderr, "%s: head = %d, tail = %d, olditem = %d\n",
620 "delete_client_info", client_cache_head,
621 client_cache_tail, oldci);
626 * Number of open tcp connections reaches the maximum.
627 * This is very unlikely in the normal case since
628 * a tcp connection is kept open if at least one calendar
629 * is registered with the host and a user would not be
630 * browsing a large number of calendars at one time.
631 * However, when a calendar is deselected in the calendar
632 * list on the multi-browser window, a lookup call using
633 * the tcp connection is made after the calendar is
634 * deregistered. This keeps the tcp connection open
635 * even if that's the last calendar registered with the
636 * host. This routine is used to clean up such tcp connections.
637 * This is a good time to clean up connections that are not
638 * used for a long time.
641 cleanup_some_connection(_DtCm_Client_Info *dontclose)
643 _DtCm_Client_Info *ci, *oldci;
644 int total = 0, deleted = 0, done = 0;
646 for (ci = client_cache_head; ci != NULL; )
650 /* clean up whole list */
651 if (ci != dontclose && ci->nregistered == 0) {
654 if (ci != dontclose && ci->nregistered == 0 &&
655 (ci->tcpcl || (!done && ci->tcpcl == NULL) ||
656 (ci->tcpcl==NULL && (time(NULL) - ci->last_used)>DAYSEC)))
664 delete_client_info(oldci);
669 fprintf(stderr, "%s: total = %d, deleted = %d\n",
670 "cleanup_tcp_connection", total, deleted);
676 * Deergister the first target:
677 * if it succeeded, the old server is still running, just re-register it;
678 * else assume that it's a new server so re-register the whole list again.
681 check_registration(_DtCm_Connection *conn)
683 _DtCm_Target_List *listp, *prev;
684 _DtCm_Transport_Type olduse;
685 CSA_return_code stat;
687 if (conn->ci->tlist == NULL)
691 conn->use = udp_transport;
692 conn->retry = B_FALSE;
693 if ((stat = _DtCm_do_unregistration(conn, conn->ci->tlist->cal,
694 conn->ci->tlist->update_type)) == CSA_SUCCESS) {
695 if (_DtCm_do_registration(conn, conn->ci->tlist->cal,
696 conn->ci->tlist->update_type) != CSA_SUCCESS)
698 conn->ci->nregistered--;
699 listp = conn->ci->tlist;
700 conn->ci->tlist = listp->next;
704 } else if (stat == CSA_E_CALLBACK_NOT_REGISTERED || stat == CSA_E_FAILURE) {
705 for (listp = prev = conn->ci->tlist; listp != NULL; ) {
706 if (_DtCm_do_registration(conn, listp->cal,
707 listp->update_type) != CSA_SUCCESS)
709 conn->ci->nregistered--;
711 conn->ci->tlist = prev = listp->next;
713 prev->next = listp->next;
714 /* free target item */
717 listp = (prev ? prev->next : NULL);
727 static _DtCm_Client_Info *
728 get_new_client_handle(_DtCm_Connection *conn)
733 if (conn == NULL) return(NULL);
735 oldver = conn->ci->vers_out;
737 /* always get a udp client handle first */
738 #if defined(SunOS) || defined(USL) || defined(__uxp__)
739 cl = clnt_create_vers(conn->ci->host, TABLEPROG, &(conn->ci->vers_out),
740 TABLEVERS_2, oldver, "udp");
742 _DtCm_print_errmsg(clnt_spcreateerror(conn->ci->host));
745 (void) get_client_handle(conn->ci->host, (u_int)TABLEPROG,
746 &(conn->ci->vers_out), TABLEVERS_2, oldver,
751 delete_client_info(conn->ci);
758 timeout_tv.tv_sec = _DtCM_INITIAL_TIMEOUT;
759 timeout_tv.tv_usec = 0;
760 clnt_control(cl, CLSET_TIMEOUT, (char *)&timeout_tv);
761 retry_tv.tv_sec = _DtCM_INITIAL_TIMEOUT + 10;
762 retry_tv.tv_usec = 0;
763 clnt_control(cl, CLSET_RETRY_TIMEOUT, (char *)&retry_tv);
765 destroy_auth(conn->ci->udpcl);
766 clnt_destroy(conn->ci->udpcl);
767 conn->ci->udpcl = cl;
770 /* check registration */
771 /* if there's anything wrong, nregistered could be zero */
772 check_registration(conn);
774 /* ci might be set to NULL if an rpc call failed */
775 if (conn->ci == NULL)
778 /* now deal with tcp handle */
780 /* get rid of old handle first */
781 if (conn->ci->tcpcl) {
782 destroy_auth(conn->ci->tcpcl);
783 clnt_destroy(conn->ci->tcpcl);
785 conn->ci->tcpcl = NULL;
788 if (conn->use == udp_transport) {
792 /* get a tcp client handle */
793 oldver = conn->ci->vers_out;
794 #if defined(SunOS) || defined(USL) || defined(__uxp__)
795 cl = clnt_create_vers(conn->ci->host, TABLEPROG,
796 &(conn->ci->vers_out), TABLEVERS_2, oldver, "tcp");
798 _DtCm_print_errmsg(clnt_spcreateerror(conn->ci->host));
800 (void) get_client_handle(conn->ci->host, (u_int)TABLEPROG,
801 &(conn->ci->vers_out), TABLEVERS_2, oldver, "tcp",
806 conn->ci->vers_out = oldver;
812 timeout_tv.tv_sec = _DtCM_INITIAL_TIMEOUT;
813 timeout_tv.tv_usec = 0;
814 clnt_control(cl, CLSET_TIMEOUT, (char *)&timeout_tv);
816 conn->ci->tcpcl = cl;
824 * Get a client handle to a server that supports the highest
825 * version between the given range.
827 static CSA_return_code
833 const u_long prognum,
836 const u_long vers_low,
837 const u_long vers_high,
844 enum clnt_stat status;
847 static int bumped = 0;
853 /* raise the soft limit of number of file descriptor */
854 getrlimit(RLIMIT_NOFILE, &rl);
855 rl.rlim_cur = rl.rlim_max;
856 setrlimit(RLIMIT_NOFILE, &rl);
862 * A longer timeout value may be necessay - for example if
863 * the system is bogged down and/or rpc.cmsd is not running.
865 * The value below is the same value used when a ToolTalk app connects
866 * to the ToolTalk database server (lib/tt/lib/db/tt_db_client.C).
875 for (vers = vers_high; vers >= vers_low; vers--) {
876 #if defined(__osf__) || defined(__hpux)
877 if ((cl = clnt_create((char *)host, prognum, vers, nettype)) != NULL) {
880 if ((cl = clnt_create(host, prognum, vers, nettype)) != NULL) {
882 clnt_control(cl, CLSET_TIMEOUT, (char *)&tv);
883 status = clnt_call(cl, 0, (xdrproc_t) xdr_void,
884 (char *)NULL, (xdrproc_t) xdr_void,
887 if (status == RPC_SUCCESS) {
892 * Set the timeout back to the original.
895 clnt_control(cl, CLSET_TIMEOUT, (char *)&tv);
897 return (CSA_SUCCESS);
898 } else if (status != RPC_PROGVERSMISMATCH) {
899 return (_DtCm_clntstat_to_csastat(status));
902 _DtCm_print_errmsg(clnt_spcreateerror((char *) host));
903 return (_DtCm_clntstat_to_csastat(rpc_createerr.cf_stat));
907 /* cannot find a server that supports a version in the given range */
908 /* Probably will never get here */
909 return (CSA_E_SERVICE_UNAVAILABLE);
912 static CSA_return_code
913 regstat4_to_dtcmstatus(Registration_Status_4 stat4)
917 return (CSA_SUCCESS);
920 return (CSA_SUCCESS);
923 return (CSA_E_CALENDAR_NOT_EXIST);
927 return (CSA_E_FAILURE);