Use internal _xdr_* functions, check for NULL pointers, use NIS+
[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         char leaf [strlen (name) + 3];
488         char domain [strlen (name) + 3];
489         char ndomain [strlen (name) + 3];
490         char *cp;
491         u_int run = 0;
492
493         strcpy (domain, name);
494
495         do
496           {
497             if (strlen (domain) == 0)
498               {
499                 nis_free_directory (dir);
500                 return NULL;
501               }
502             nis_leaf_of_r (domain, leaf, sizeof (leaf));
503             nis_domain_of_r (domain, ndomain, sizeof (ndomain));
504             strcpy (domain, ndomain);
505             ++run;
506           }
507         while (nis_dir_cmp (domain, dir->do_name) != SAME_NAME);
508
509         if (run == 1)
510           {
511             /* We have found the directory above. Use it. */
512             return dir;
513           }
514
515         cp = strchr (leaf, '\0');
516         *cp++ = '.';
517         strcpy (cp, domain);
518
519         fd_res = __nis_finddirectory (dir, leaf);
520         *status = fd_res->status;
521         if (fd_res->status != NIS_SUCCESS)
522           {
523             /* Try the current directory object, maybe it works */
524             __free_fdresult (fd_res);
525             return dir;
526           }
527         obj = calloc(1, sizeof(directory_obj));
528         xdrmem_create(&xdrs, fd_res->dir_data.dir_data_val,
529                       fd_res->dir_data.dir_data_len, XDR_DECODE);
530         _xdr_directory_obj(&xdrs, obj);
531         xdr_destroy(&xdrs);
532         __free_fdresult (fd_res);
533         if (obj != NULL)
534           {
535             /* We have found a NIS+ server serving ndomain, now
536                let us search for "name" */
537             nis_free_directory (dir);
538             return rec_dirsearch (name, obj, flags, status);
539           }
540       }
541     break;
542     case BAD_NAME:
543       nis_free_directory (dir);
544       *status = NIS_BADNAME;
545       return NULL;
546     }
547   nis_free_directory (dir);
548   *status = NIS_FAIL;
549   return NULL;
550 }
551
552 /* We try to query the current server for the searched object,
553    maybe he know about it ? */
554 static directory_obj *
555 first_shoot (const_nis_name name, directory_obj *dir, u_long flags)
556 {
557   directory_obj *obj;
558   fd_result *fd_res;
559   XDR xdrs;
560   char domain [strlen (name) + 3];
561
562   if (nis_dir_cmp (name, dir->do_name) == SAME_NAME)
563     return dir;
564
565   nis_domain_of_r (name, domain, sizeof (domain));
566
567   if (nis_dir_cmp (domain, dir->do_name) == SAME_NAME)
568     return dir;
569
570   fd_res = __nis_finddirectory (dir, domain);
571   if (fd_res->status != NIS_SUCCESS)
572     {
573       __free_fdresult (fd_res);
574       return NULL;
575     }
576   obj = calloc(1, sizeof(directory_obj));
577   if (obj == NULL)
578     return NULL;
579   xdrmem_create(&xdrs, fd_res->dir_data.dir_data_val,
580                 fd_res->dir_data.dir_data_len, XDR_DECODE);
581   _xdr_directory_obj (&xdrs, obj);
582   xdr_destroy (&xdrs);
583   __free_fdresult (fd_res);
584   if (obj != NULL)
585     {
586       nis_free_directory (dir);
587       return obj;
588     }
589   return NULL;
590 }
591
592 nis_error
593 __do_niscall (const_nis_name name, u_long prog, xdrproc_t xargs,
594               caddr_t req, xdrproc_t xres, caddr_t resp, u_long flags,
595               nis_cb *cb)
596 {
597   nis_error retcode;
598   directory_obj *dir = NULL;
599   nis_server *server;
600   u_int server_len;
601   cache2_info cinfo = {-1, -1, -1};
602   int saved_errno = errno;
603
604   if (name == NULL)
605     return NIS_BADNAME;
606
607   /* Search in local cache. In the moment, we ignore the fastest server */
608   if (!(flags & NO_CACHE))
609     dir = __nis_cache_search (name, flags, &cinfo);
610
611   if (dir == NULL)
612     {
613       nis_error status;
614       directory_obj *obj;
615
616       dir = readColdStartFile ();
617       if (dir == NULL) /* No /var/nis/NIS_COLD_START->no NIS+ installed */
618         {
619           __set_errno (saved_errno);
620           return NIS_UNAVAIL;
621         }
622
623       /* Try at first, if servers in "dir" know our object */
624       obj = first_shoot (name, dir, flags);
625       if (obj == NULL)
626         {
627           dir = rec_dirsearch (name, dir, flags, &status);
628           if (dir == NULL)
629             {
630               __set_errno (saved_errno);
631               return status;
632             }
633         }
634       else
635         dir = obj;
636     }
637
638   if (flags & MASTER_ONLY)
639     {
640       server = dir->do_servers.do_servers_val;
641       server_len = 1;
642     }
643   else
644     {
645       server = dir->do_servers.do_servers_val;
646       server_len = dir->do_servers.do_servers_len;
647     }
648
649
650   retcode = __do_niscall2 (server, server_len, prog, xargs, req, xres, resp,
651                            flags, cb, &cinfo);
652
653   nis_free_directory (dir);
654
655   __set_errno (saved_errno);
656
657   return retcode;
658 }