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