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