Make directory search faster.
[kopensolaris-gnu/glibc.git] / nis / nis_call.c
1 /* Copyright (C) 1997, 1998 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3    Contributed by Thorsten Kukuk <kukuk@vt.uni-paderborn.de>, 1997.
4
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14
15    You should have received a copy of the GNU Library General Public
16    License along with the GNU C Library; see the file COPYING.LIB.  If not,
17    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18    Boston, MA 02111-1307, USA.  */
19
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <string.h>
23 #include <rpc/rpc.h>
24 #include <rpc/auth.h>
25 #include <rpcsvc/nis.h>
26 #include <sys/socket.h>
27 #include <netinet/in.h>
28 #include <arpa/inet.h>
29 #include "nis_intern.h"
30
31 static struct timeval RPCTIMEOUT = {10, 0};
32 static struct timeval UDPTIMEOUT = {5, 0};
33
34 extern u_short __pmap_getnisport (struct sockaddr_in *address, u_long program,
35                                   u_long version, u_int protocol);
36
37 unsigned long
38 inetstr2int (const char *str)
39 {
40   char buffer[strlen (str) + 3];
41   size_t buflen;
42   size_t i, j;
43
44   buflen = stpcpy (buffer, str) - buffer;
45
46   j = 0;
47   for (i = 0; i < buflen; ++i)
48     if (buffer[i] == '.')
49       {
50         ++j;
51         if (j == 4)
52           {
53             buffer[i] = '\0';
54             break;
55           }
56       }
57
58   return inet_addr (buffer);
59 }
60
61 static void
62 __bind_destroy (dir_binding *bind)
63 {
64   if (bind->clnt != NULL)
65     {
66       if (bind->use_auth)
67         auth_destroy (bind->clnt->cl_auth);
68       clnt_destroy (bind->clnt);
69     }
70   free (bind->server_val);
71   free (bind);
72 }
73
74 static nis_error
75 __bind_next (dir_binding *bind)
76 {
77   u_int j;
78
79   if (bind->clnt != NULL)
80     {
81       if (bind->use_auth)
82         auth_destroy (bind->clnt->cl_auth);
83       clnt_destroy (bind->clnt);
84       bind->clnt = NULL;
85     }
86
87   if (bind->trys >= bind->server_len)
88     return NIS_FAIL;
89
90   for (j = bind->current_ep + 1;
91        j < bind->server_val[bind->server_used].ep.ep_len; ++j)
92     if (strcmp (bind->server_val[bind->server_used].ep.ep_val[j].family,
93                 "inet") == 0)
94       if (strcmp (bind->server_val[bind->server_used].ep.ep_val[j].proto,
95                   "-") == 0)
96         {
97           bind->current_ep = j;
98           return NIS_SUCCESS;
99         }
100
101   ++bind->trys;
102   ++bind->server_used;
103   if (bind->server_used >= bind->server_len)
104     bind->server_used = 0;
105
106   for (j = 0; j < bind->server_val[bind->server_used].ep.ep_len; ++j)
107     if (strcmp (bind->server_val[bind->server_used].ep.ep_val[j].family,
108                 "inet") == 0)
109       if (strcmp (bind->server_val[bind->server_used].ep.ep_val[j].proto,
110                   "-") == 0)
111         {
112           bind->current_ep = j;
113           return NIS_SUCCESS;
114         }
115
116   return NIS_FAIL;
117 }
118
119 static nis_error
120 __bind_connect (dir_binding *dbp)
121 {
122   struct sockaddr_in check;
123   nis_server *serv;
124   int checklen;
125
126   if (dbp == NULL)
127     return NIS_FAIL;
128
129   serv = &dbp->server_val[dbp->server_used];
130
131   memset (&dbp->addr, '\0', sizeof (dbp->addr));
132   dbp->addr.sin_family = AF_INET;
133
134   dbp->addr.sin_addr.s_addr =
135     inetstr2int (serv->ep.ep_val[dbp->current_ep].uaddr);
136
137   if (dbp->addr.sin_addr.s_addr == 0)
138     return NIS_FAIL;
139
140   /* Check, if the host is online and rpc.nisd is running. Much faster
141      then the clnt*_create functions: */
142   if (__pmap_getnisport (&dbp->addr, NIS_PROG, NIS_VERSION, IPPROTO_UDP) == 0)
143     return NIS_RPCERROR;
144
145   dbp->socket = RPC_ANYSOCK;
146   if (dbp->use_udp)
147     dbp->clnt = clntudp_create (&dbp->addr, NIS_PROG, NIS_VERSION,
148                                  UDPTIMEOUT, &dbp->socket);
149   else
150     dbp->clnt = clnttcp_create (&dbp->addr, NIS_PROG, NIS_VERSION,
151                                  &dbp->socket, 0, 0);
152
153   if (dbp->clnt == NULL)
154     return NIS_RPCERROR;
155
156   clnt_control (dbp->clnt, CLSET_TIMEOUT, (caddr_t)&RPCTIMEOUT);
157   /* If the program exists, close the socket */
158   if (fcntl (dbp->socket, F_SETFD, 1) == -1)
159     perror (_("fcntl: F_SETFD"));
160
161   if (dbp->use_auth)
162     {
163       if (serv->key_type == NIS_PK_DH && key_secretkey_is_set ())
164         {
165           char netname[MAXNETNAMELEN+1];
166           char *p;
167
168           p = stpcpy (netname, "unix.");
169           strncpy (p, serv->name,MAXNETNAMELEN-5);
170           netname[MAXNETNAMELEN] = '\0';
171           p = strchr (netname, '.');
172           *p = '@';
173           dbp->clnt->cl_auth =
174             authdes_pk_create (netname, &serv->pkey, 300, NULL, NULL);
175           if (!dbp->clnt->cl_auth)
176             dbp->clnt->cl_auth = authunix_create_default ();
177         }
178       else
179         dbp->clnt->cl_auth = authunix_create_default ();
180       dbp->use_auth = TRUE;
181     }
182
183   /* Get port for sanity checks later */
184   checklen = sizeof (struct sockaddr_in);
185   memset (&check, 0, checklen);
186   if (dbp->use_udp)
187     bind (dbp->socket, (struct sockaddr *)&check, checklen);
188   check.sin_family = AF_INET;
189   if (!getsockname (dbp->socket, (struct sockaddr *)&check, &checklen))
190     dbp->port = check.sin_port;
191
192   dbp->create = time (NULL);
193
194   return NIS_SUCCESS;
195 }
196
197 static dir_binding *
198 __bind_create (const nis_server *serv_val, u_int serv_len, u_long flags,
199                cache2_info *cinfo)
200 {
201   dir_binding *dbp;
202   u_int i;
203
204   dbp = calloc (1, sizeof (dir_binding));
205   if (dbp == NULL)
206     return NULL;
207
208   dbp->server_len = serv_len;
209   dbp->server_val = calloc (1, sizeof (nis_server) * serv_len);
210   if (dbp->server_val == NULL)
211     {
212       free (dbp);
213       return NULL;
214     }
215
216   if (flags & USE_DGRAM)
217     dbp->use_udp = TRUE;
218   else
219     dbp->use_udp = FALSE;
220
221   if (flags & NO_AUTHINFO)
222     dbp->use_auth = FALSE;
223   else
224     dbp->use_auth = TRUE;
225
226   if (flags & MASTER_ONLY)
227     dbp->master_only = TRUE;
228   else
229     dbp->master_only = FALSE;
230
231   /* We try the first server */
232   dbp->trys = 1;
233
234   for (i = 0; i < serv_len; ++i)
235     {
236       if (serv_val[i].name != NULL)
237         dbp->server_val[i].name = strdup (serv_val[i].name);
238
239       dbp->server_val[i].ep.ep_len = serv_val[i].ep.ep_len;
240       if (dbp->server_val[i].ep.ep_len > 0)
241         {
242           unsigned long j;
243
244           dbp->server_val[i].ep.ep_val =
245             malloc (serv_val[i].ep.ep_len * sizeof (endpoint));
246           for (j = 0; j < dbp->server_val[i].ep.ep_len; ++j)
247             {
248               if (serv_val[i].ep.ep_val[j].uaddr)
249                 dbp->server_val[i].ep.ep_val[j].uaddr =
250                   strdup (serv_val[i].ep.ep_val[j].uaddr);
251               else
252                 dbp->server_val[i].ep.ep_val[j].uaddr = NULL;
253               if (serv_val[i].ep.ep_val[j].family)
254                 dbp->server_val[i].ep.ep_val[j].family =
255                   strdup (serv_val[i].ep.ep_val[j].family);
256               else
257                 dbp->server_val[i].ep.ep_val[j].family = NULL;
258               if (serv_val[i].ep.ep_val[j].proto)
259                 dbp->server_val[i].ep.ep_val[j].proto =
260                   strdup (serv_val[i].ep.ep_val[j].proto);
261               else
262                 dbp->server_val[i].ep.ep_val[j].proto = NULL;
263             }
264         }
265       else
266         dbp->server_val[i].ep.ep_val = NULL;
267       dbp->server_val[i].key_type = serv_val[i].key_type;
268       dbp->server_val[i].pkey.n_len = serv_val[i].pkey.n_len;
269       if (serv_val[i].pkey.n_len > 0)
270         {
271           dbp->server_val[i].pkey.n_bytes =
272             malloc (serv_val[i].pkey.n_len);
273           if (dbp->server_val[i].pkey.n_bytes == NULL)
274             return NULL;
275           memcpy (dbp->server_val[i].pkey.n_bytes, serv_val[i].pkey.n_bytes,
276                   serv_val[i].pkey.n_len);
277         }
278       else
279         dbp->server_val[i].pkey.n_bytes = NULL;
280     }
281
282   dbp->class = -1;
283   if (cinfo != NULL && cinfo->server_used >= 0)
284     {
285       dbp->server_used = cinfo->server_used;
286       dbp->current_ep = cinfo->current_ep;
287       dbp->class = cinfo->class;
288     }
289   else if (__nis_findfastest (dbp) < 1)
290     {
291       __bind_destroy (dbp);
292       return NULL;
293     }
294
295   return dbp;
296 }
297
298 nis_error
299 __do_niscall2 (const nis_server *server, u_int server_len, u_long prog,
300                xdrproc_t xargs, caddr_t req, xdrproc_t xres, caddr_t resp,
301                u_long flags, nis_cb *cb, cache2_info *cinfo)
302 {
303   enum clnt_stat result;
304   nis_error retcode;
305   dir_binding *dbp;
306
307   if (flags & MASTER_ONLY)
308     server_len = 1;
309
310   dbp = __bind_create (server, server_len, flags, cinfo);
311   if (dbp == NULL)
312     return NIS_NAMEUNREACHABLE;
313   while (__bind_connect (dbp) != NIS_SUCCESS)
314     {
315       if (__bind_next (dbp) != NIS_SUCCESS)
316         {
317           __bind_destroy (dbp);
318           return NIS_NAMEUNREACHABLE;
319         }
320     }
321
322   do
323     {
324     again:
325       result = clnt_call (dbp->clnt, prog, xargs, req, xres, resp, RPCTIMEOUT);
326
327       if (result != RPC_SUCCESS)
328         {
329           __bind_destroy (dbp);
330           retcode = NIS_RPCERROR;
331         }
332       else
333         {
334           switch (prog)
335             {
336             case NIS_IBLIST:
337               if ((((nis_result *)resp)->status == NIS_CBRESULTS) &&
338                   (cb != NULL))
339                 {
340                   __nis_do_callback(dbp, &((nis_result *)resp)->cookie, cb);
341                   break;
342                 }
343               /* Yes, this is correct. If we doesn't have to start
344                  a callback, look if we have to search another server */
345             case NIS_LOOKUP:
346             case NIS_ADD:
347             case NIS_MODIFY:
348             case NIS_REMOVE:
349             case NIS_IBADD:
350             case NIS_IBMODIFY:
351             case NIS_IBREMOVE:
352             case NIS_IBFIRST:
353             case NIS_IBNEXT:
354               if ((((nis_result *)resp)->status == NIS_NOTFOUND) ||
355                   (((nis_result *)resp)->status == NIS_NOSUCHNAME) ||
356                   (((nis_result *)resp)->status == NIS_NOT_ME))
357                 {
358                   if (__bind_next (dbp) == NIS_SUCCESS)
359                     {
360                       while (__bind_connect (dbp) != NIS_SUCCESS)
361                         {
362                           if (__bind_next (dbp) != NIS_SUCCESS)
363                             {
364                               __bind_destroy (dbp);
365                               return NIS_SUCCESS;
366                             }
367                         }
368                     }
369                   else
370                     break; /* No more servers to search in */
371                   goto again;
372                 }
373               break;
374             case NIS_FINDDIRECTORY:
375               if ((((fd_result *)resp)->status == NIS_NOTFOUND) ||
376                   (((fd_result *)resp)->status == NIS_NOSUCHNAME) ||
377                   (((fd_result *)resp)->status == NIS_NOT_ME))
378                 {
379                   if (__bind_next (dbp) == NIS_SUCCESS)
380                     {
381                       while (__bind_connect (dbp) != NIS_SUCCESS)
382                         {
383                           if (__bind_next (dbp) != NIS_SUCCESS)
384                             {
385                               __bind_destroy (dbp);
386                               return NIS_SUCCESS;
387                             }
388                         }
389                     }
390                   else
391                     break; /* No more servers to search in */
392                   goto again;
393                 }
394               break;
395             case NIS_DUMPLOG: /* log_result */
396             case NIS_DUMP:
397               if ((((log_result *)resp)->lr_status == NIS_NOTFOUND) ||
398                   (((log_result *)resp)->lr_status == NIS_NOSUCHNAME) ||
399                   (((log_result *)resp)->lr_status == NIS_NOT_ME))
400                 {
401                   if (__bind_next (dbp) == NIS_SUCCESS)
402                     {
403                       while (__bind_connect (dbp) != NIS_SUCCESS)
404                         {
405                           if (__bind_next (dbp) != NIS_SUCCESS)
406                             {
407                               __bind_destroy (dbp);
408                               return NIS_SUCCESS;
409                             }
410                         }
411                     }
412                   else
413                     break; /* No more servers to search in */
414                   goto again;
415                 }
416               break;
417             default:
418               break;
419             }
420           __bind_destroy (dbp);
421           retcode = NIS_SUCCESS;
422         }
423     }
424   while ((flags & HARD_LOOKUP) && retcode == NIS_RPCERROR);
425
426   return retcode;
427 }
428
429 static directory_obj *
430 rec_dirsearch (const_nis_name name, directory_obj *dir, u_long flags,
431                nis_error *status)
432 {
433   fd_result *fd_res;
434   XDR xdrs;
435
436   switch (nis_dir_cmp (name, dir->do_name))
437     {
438     case SAME_NAME:
439       *status = NIS_SUCCESS;
440       return dir;
441     case NOT_SEQUENTIAL:
442       /* NOT_SEQUENTIAL means, go one up and try it there ! */
443     case HIGHER_NAME:
444       { /* We need data from a parent domain */
445         directory_obj *obj;
446         char ndomain [strlen (name) + 3];
447
448         nis_domain_of_r (dir->do_name, ndomain, sizeof (ndomain));
449
450         /* The root server of our domain is a replica of the parent
451            domain ! (Now I understand why a root server must be a
452            replica of the parent domain) */
453         fd_res = __nis_finddirectory (dir, ndomain);
454         *status = fd_res->status;
455         if (fd_res->status != NIS_SUCCESS)
456           {
457             /* Try the current directory obj, maybe it works */
458             __free_fdresult (fd_res);
459             return dir;
460           }
461         obj = calloc(1, sizeof(directory_obj));
462         xdrmem_create(&xdrs, fd_res->dir_data.dir_data_val,
463                       fd_res->dir_data.dir_data_len, XDR_DECODE);
464         xdr_directory_obj(&xdrs, obj);
465         xdr_destroy(&xdrs);
466         __free_fdresult (fd_res);
467         if (obj != NULL)
468           {
469             /* We have found a NIS+ server serving ndomain, now
470                let us search for "name" */
471             nis_free_directory (dir);
472             return rec_dirsearch (name, obj, flags, status);
473           }
474         else
475           {
476             /* Ups, very bad. Are we already the root server ? */
477             nis_free_directory (dir);
478             return NULL;
479           }
480       }
481     break;
482     case LOWER_NAME:
483       {
484         directory_obj *obj;
485         char leaf [strlen (name) + 3];
486         char domain [strlen (name) + 3];
487         char ndomain [strlen (name) + 3];
488         char *cp;
489         u_int run = 0;
490
491         strcpy (domain, name);
492
493         do
494           {
495             if (strlen (domain) == 0)
496               {
497                 nis_free_directory (dir);
498                 return NULL;
499               }
500             nis_leaf_of_r (domain, leaf, sizeof (leaf));
501             nis_domain_of_r (domain, ndomain, sizeof (ndomain));
502             strcpy (domain, ndomain);
503             ++run;
504           }
505         while (nis_dir_cmp (domain, dir->do_name) != SAME_NAME);
506
507         if (run == 1)
508           {
509             /* We have found the directory above. Use it. */
510             return dir;
511           }
512
513         cp = strchr (leaf, '\0');
514         *cp++ = '.';
515         strcpy (cp, domain);
516
517         fd_res = __nis_finddirectory (dir, leaf);
518         *status = fd_res->status;
519         if (fd_res->status != NIS_SUCCESS)
520           {
521             /* Try the current directory object, maybe it works */
522             __free_fdresult (fd_res);
523             return dir;
524           }
525         obj = calloc(1, sizeof(directory_obj));
526         xdrmem_create(&xdrs, fd_res->dir_data.dir_data_val,
527                       fd_res->dir_data.dir_data_len, XDR_DECODE);
528         xdr_directory_obj(&xdrs, obj);
529         xdr_destroy(&xdrs);
530         __free_fdresult (fd_res);
531         if (obj != NULL)
532           {
533             /* We have found a NIS+ server serving ndomain, now
534                let us search for "name" */
535             nis_free_directory (dir);
536             return rec_dirsearch (name, obj, flags, status);
537           }
538       }
539     break;
540     case BAD_NAME:
541       nis_free_directory (dir);
542       *status = NIS_BADNAME;
543       return NULL;
544     }
545   nis_free_directory (dir);
546   *status = NIS_FAIL;
547   return NULL;
548 }
549
550 /* We try to query the current server for the searched object,
551    maybe he know about it ? */
552 static directory_obj *
553 first_shoot (const_nis_name name, directory_obj *dir, u_long flags)
554 {
555   directory_obj *obj;
556   fd_result *fd_res;
557   XDR xdrs;
558   char domain [strlen (name) + 3];
559
560   if (nis_dir_cmp (name, dir->do_name) == SAME_NAME)
561     return dir;
562
563   nis_domain_of_r (name, domain, sizeof (domain));
564
565   if (nis_dir_cmp (domain, dir->do_name) == SAME_NAME)
566     return dir;
567
568   fd_res = __nis_finddirectory (dir, domain);
569   if (fd_res->status != NIS_SUCCESS)
570     {
571       __free_fdresult (fd_res);
572       return NULL;
573     }
574   obj = calloc(1, sizeof(directory_obj));
575   if (obj == NULL)
576     return NULL;
577   xdrmem_create(&xdrs, fd_res->dir_data.dir_data_val,
578                 fd_res->dir_data.dir_data_len, XDR_DECODE);
579   xdr_directory_obj(&xdrs, obj);
580   xdr_destroy(&xdrs);
581   __free_fdresult (fd_res);
582   if (obj != NULL)
583     {
584       nis_free_directory (dir);
585       return obj;
586     }
587   return NULL;
588 }
589
590 nis_error
591 __do_niscall (const_nis_name name, u_long prog, xdrproc_t xargs,
592               caddr_t req, xdrproc_t xres, caddr_t resp, u_long flags,
593               nis_cb *cb)
594 {
595   nis_error retcode;
596   directory_obj *dir = NULL;
597   nis_server *server;
598   u_int server_len;
599   cache2_info cinfo = {-1, -1, -1};
600   int saved_errno = errno;
601
602   if (name == NULL)
603     return NIS_BADNAME;
604
605   /* Search in local cache. In the moment, we ignore the fastest server */
606   if (!(flags & NO_CACHE))
607     dir = __nis_cache_search (name, flags, &cinfo);
608
609   if (dir == NULL)
610     {
611       nis_error status;
612       directory_obj *obj;
613
614       dir = readColdStartFile ();
615       if (dir == NULL) /* No /var/nis/NIS_COLD_START->no NIS+ installed */
616         {
617           __set_errno (saved_errno);
618           return NIS_UNAVAIL;
619         }
620
621       /* Try at first, if servers in "dir" know our object */
622       obj = first_shoot (name, dir, flags);
623       if (obj == NULL)
624         {
625           dir = rec_dirsearch (name, dir, flags, &status);
626           if (dir == NULL)
627             {
628               __set_errno (saved_errno);
629               return status;
630             }
631         }
632       else
633         dir = obj;
634     }
635
636   if (flags & MASTER_ONLY)
637     {
638       server = dir->do_servers.do_servers_val;
639       server_len = 1;
640     }
641   else
642     {
643       server = dir->do_servers.do_servers_val;
644       server_len = dir->do_servers.do_servers_len;
645     }
646
647
648   retcode = __do_niscall2 (server, server_len, prog, xargs, req, xres, resp,
649                            flags, cb, &cinfo);
650
651   nis_free_directory (dir);
652
653   __set_errno (saved_errno);
654
655   return retcode;
656 }