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