Complete rewrite.
[kopensolaris-gnu/glibc.git] / nis / nis_call.c
1 /* Copyright (C) 1997 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 <fcntl.h>
21 #include <string.h>
22 #include <rpc/rpc.h>
23 #include <rpc/auth.h>
24 #include <rpcsvc/nis.h>
25 #include <sys/socket.h>
26 #include <netinet/in.h>
27 #include <arpa/inet.h>
28 #include "nis_intern.h"
29
30 static struct timeval TIMEOUT = {10, 0};
31
32 struct dir_binding
33 {
34   CLIENT *clnt;                  /* RPC CLIENT handle */
35   nis_server *server_val;        /* List of servers */
36   u_int server_len;              /* # of servers */
37   u_int server_used;             /* Which server we are bind in the moment ? */
38   u_int trys;                    /* How many server have we tried ? */
39   bool_t master_only;            /* Is only binded to the master */
40   bool_t use_auth;               /* Do we use AUTH ? */
41   bool_t use_udp;                /* Do we use UDP ? */
42   time_t create;                 /* Binding creation time */
43   struct sockaddr_in addr;       /* Server's IP address */
44   int socket;                    /* Server's local socket */
45   unsigned short port;           /* Local port */
46 };
47 typedef struct dir_binding dir_binding;
48
49 static inline u_int
50 __nis_ping (const nis_server *serv, u_int serv_len)
51 {
52   return 0;
53 }
54
55 static unsigned long
56 inetstr2int (const char *str)
57 {
58   char buffer[strlen (str) + 3];
59   size_t buflen;
60   size_t i, j;
61
62   buflen = stpcpy (buffer, str) - buffer;
63
64   j = 0;
65   for (i = 0; i < buflen; ++i)
66     if (buffer[i] == '.')
67       {
68         ++j;
69         if (j == 4)
70           {
71             buffer[i] = '\0';
72             break;
73           }
74       }
75
76   return inet_addr (buffer);
77 }
78
79 static void
80 __bind_destroy (dir_binding *bind)
81 {
82   if (bind->clnt != NULL)
83     {
84       if (bind->use_auth)
85         auth_destroy (bind->clnt->cl_auth);
86       clnt_destroy (bind->clnt);
87     }
88   free (bind->server_val);
89   free (bind);
90 }
91
92 static nis_error
93 __bind_next (dir_binding *bind)
94 {
95   if (bind->trys >= bind->server_len)
96     return NIS_FAIL;
97   
98   bind->server_used++;
99   if (bind->server_used >= bind->server_len)
100     bind->server_used = 0;
101
102   if (bind->clnt != NULL)
103     {
104       if (bind->use_auth)
105         auth_destroy (bind->clnt->cl_auth);
106       clnt_destroy (bind->clnt);
107       bind->clnt = NULL;
108     }
109   
110   return NIS_SUCCESS;
111 }
112
113 static nis_error
114 __bind_connect (dir_binding *dbp)
115 {
116   struct sockaddr_in check;
117   nis_server *serv;
118   int checklen;
119   u_int i;
120
121   if (dbp == NULL)
122     return NIS_FAIL;
123
124   serv = &dbp->server_val[dbp->server_used];
125
126   memset (&dbp->addr, '\0', sizeof (dbp->addr));
127   dbp->addr.sin_family = AF_INET;
128   for (i = 0; i < serv->ep.ep_len; ++i)
129     {
130       if (strcmp (serv->ep.ep_val[i].family, "inet") == 0)
131         {
132           if (dbp->use_udp)
133             {
134               if (strcmp (serv->ep.ep_val[i].proto, "udp") == 0)
135                 dbp->addr.sin_addr.s_addr =
136                   inetstr2int (serv->ep.ep_val[i].uaddr);
137               else
138                 continue;
139             }
140           else
141             if (strcmp (serv->ep.ep_val[i].proto, "tcp") == 0)
142               dbp->addr.sin_addr.s_addr =
143                 inetstr2int (serv->ep.ep_val[i].uaddr);
144         }
145       else
146         continue;
147     }
148   if (dbp->addr.sin_addr.s_addr == 0)
149     return NIS_FAIL;
150
151   dbp->socket = RPC_ANYSOCK;
152   if (dbp->use_udp)
153     dbp->clnt = clntudp_create (&dbp->addr, NIS_PROG, NIS_VERSION,
154                                  TIMEOUT, &dbp->socket);
155   else
156     dbp->clnt = clnttcp_create (&dbp->addr, NIS_PROG, NIS_VERSION,
157                                  &dbp->socket, 0, 0);
158   
159   if (dbp->clnt == NULL)
160     return NIS_RPCERROR;
161   
162   clnt_control (dbp->clnt, CLSET_TIMEOUT, (caddr_t)&TIMEOUT);
163   /* If the program exists, close the socket */
164   if (fcntl (dbp->socket, F_SETFD, 1) == -1)
165     perror (_("fcntl: F_SETFD"));
166   
167   if (dbp->use_auth)
168     {
169 #if defined(HAVE_SECURE_RPC)
170       if (serv->key_type == NIS_PK_DH)
171         {
172           char netname[MAXNETNAMELEN+1];
173           char *p;
174           
175           p = stpcpy (netname, "unix.");
176           strncpy (p, serv->name,MAXNETNAMELEN-5);
177           netname[MAXNETNAMELEN] = '\0';
178           p = strchr (netname, '.');
179           *p = '@';
180           dbp->clnt->cl_auth =
181             authdes_pk_create (netname, &serv->pkey, 300, NULL, NULL);
182           if (!dbp->clnt->cl_auth)
183             dbp->clnt->cl_auth = authunix_create_default ();
184         }
185       else
186 #endif
187         dbp->clnt->cl_auth = authunix_create_default ();
188       dbp->use_auth = TRUE;
189     }
190   
191   /* Get port for sanity checks later */
192   checklen = sizeof (struct sockaddr_in);
193   memset (&check, 0, checklen);
194   if (dbp->use_udp)
195     bind (dbp->socket, (struct sockaddr *)&check, checklen);
196   check.sin_family = AF_INET;
197   if (!getsockname (dbp->socket, (struct sockaddr *)&check, &checklen))
198     dbp->port = check.sin_port;
199
200   dbp->create = time (NULL);
201
202   return NIS_SUCCESS;
203 }
204
205 static dir_binding *
206 __bind_create (const nis_server *serv_val, u_int serv_len, u_long flags)
207 {
208   dir_binding *dbp;
209   u_int i;
210   
211   dbp = calloc (1, sizeof (dir_binding));
212   if (dbp == NULL)
213     return NULL;
214   
215   dbp->server_len = serv_len;
216   dbp->server_val = calloc (1, sizeof (nis_server) * serv_len);
217   if (dbp->server_val == NULL)
218     {
219       free (dbp);
220       return NULL;
221     }
222   
223   for (i = 0; i < serv_len; ++i)
224     {
225       if (serv_val[i].name != NULL)
226         dbp->server_val[i].name = strdup (serv_val[i].name);
227       
228       dbp->server_val[i].ep.ep_len = serv_val[i].ep.ep_len;
229       if (dbp->server_val[i].ep.ep_len > 0)
230         {
231           unsigned long j;
232           
233           dbp->server_val[i].ep.ep_val =
234             malloc (serv_val[i].ep.ep_len * sizeof (endpoint));
235           for (j = 0; j < dbp->server_val[i].ep.ep_len; ++j)
236             {
237               if (serv_val[i].ep.ep_val[j].uaddr)
238                 dbp->server_val[i].ep.ep_val[j].uaddr =
239                   strdup (serv_val[i].ep.ep_val[j].uaddr);
240               else
241                 dbp->server_val[i].ep.ep_val[j].uaddr = NULL;
242               if (serv_val[i].ep.ep_val[j].family)
243                 dbp->server_val[i].ep.ep_val[j].family =
244                   strdup (serv_val[i].ep.ep_val[j].family);
245               else
246                 dbp->server_val[i].ep.ep_val[j].family = NULL;
247               if (serv_val[i].ep.ep_val[j].proto)
248                 dbp->server_val[i].ep.ep_val[j].proto =
249                   strdup (serv_val[i].ep.ep_val[j].proto);
250               else
251                 dbp->server_val[i].ep.ep_val[j].proto = NULL;
252             }
253         }
254       else
255         dbp->server_val[i].ep.ep_val = NULL;
256       dbp->server_val[i].key_type = serv_val[i].key_type;
257       dbp->server_val[i].pkey.n_len = serv_val[i].pkey.n_len;
258       if (serv_val[i].pkey.n_len > 0)
259         {
260           dbp->server_val[i].pkey.n_bytes =
261             malloc (serv_val[i].pkey.n_len);
262           if (dbp->server_val[i].pkey.n_bytes == NULL)
263             return NULL;
264           memcpy (dbp->server_val[i].pkey.n_bytes, serv_val[i].pkey.n_bytes,
265                   serv_val[i].pkey.n_len);
266         }
267       else
268         dbp->server_val[i].pkey.n_bytes = NULL;
269     }
270   
271   dbp->server_used = __nis_ping (dbp->server_val, dbp->server_len);
272   if (flags & USE_DGRAM)
273     dbp->use_udp = TRUE;
274   else
275     dbp->use_udp = FALSE;
276
277   if (flags & NO_AUTHINFO)
278     dbp->use_auth = FALSE;
279   else
280     dbp->use_auth = TRUE;
281
282   if (flags & MASTER_ONLY)
283     dbp->master_only = TRUE;
284   else
285     dbp->master_only = FALSE;
286
287   dbp->trys = 1;
288
289   return dbp;
290 }
291
292 nis_error
293 __do_niscall2 (const nis_server *server, u_int server_len, u_long prog,
294                xdrproc_t xargs, caddr_t req, xdrproc_t xres, caddr_t resp,
295                u_long flags)
296 {
297   enum clnt_stat result;
298   nis_error retcode;
299   dir_binding *dbp;
300
301   if (flags & MASTER_ONLY) 
302     server_len = 1;
303   
304   dbp = __bind_create (server, server_len, flags);
305   while (__bind_connect (dbp) != NIS_SUCCESS)
306     {
307       if (__bind_next (dbp) != NIS_SUCCESS)
308         {
309           __bind_destroy (dbp);
310           return NIS_NAMEUNREACHABLE;
311         }
312     }
313
314   do
315     {
316     again:
317       result = clnt_call (dbp->clnt, prog, xargs, req, xres, resp, TIMEOUT);
318       
319       if (result != RPC_SUCCESS)
320         {
321           clnt_perror (dbp->clnt, "__do_niscall2: clnt_call");
322           __bind_destroy (dbp);
323           retcode = NIS_RPCERROR;
324         }
325       else
326         {
327           switch (prog)
328             {
329             case NIS_LOOKUP:
330             case NIS_ADD:
331             case NIS_MODIFY:
332             case NIS_REMOVE:
333             case NIS_IBLIST:
334             case NIS_IBADD:
335             case NIS_IBMODIFY:
336             case NIS_IBREMOVE:
337             case NIS_IBFIRST:
338             case NIS_IBNEXT:
339               if ((((nis_result *)xres)->status != NIS_SUCCESS) &&
340                   (((nis_result *)xres)->status != NIS_S_SUCCESS))
341                 if (__bind_next (dbp) == NIS_SUCCESS)
342                   goto again;
343             case NIS_FINDDIRECTORY:
344               if (((fd_result *)xres)->status != NIS_SUCCESS)
345                 if (__bind_next (dbp) == NIS_SUCCESS)
346                   goto again;
347               break;
348 #if 0
349             case NIS_STATUS: /* nis_taglist */
350             case NIS_SERVSTATE:
351               break;
352             case NIS_DUMPLOG: /* log_result */
353             case NIS_DUMP:
354               break;
355             case NIS_CHECKPOINT: /* cp_result */
356               break;
357 #endif
358             default:
359               break;
360             }
361           __bind_destroy (dbp);
362           retcode = NIS_SUCCESS;
363         }
364     }
365   while ((flags & HARD_LOOKUP) && retcode == NIS_RPCERROR);
366   
367   return retcode; 
368 }
369
370 static directory_obj *
371 rec_dirsearch (const_nis_name name, directory_obj *dir, u_long flags)
372 {
373   fd_result *fd_res;
374   XDR xdrs;
375   char domain [strlen (name) + 3];
376
377   nis_domain_of_r (name, domain, sizeof (domain));
378   if (strncmp (domain, "org_dir.", 8) == 0)
379     {
380       char tmp[strlen (name) + 3];
381
382       nis_domain_of_r (domain, tmp, sizeof (tmp));
383       strcpy (domain, tmp);
384     }
385   else
386     if (strncmp (domain, "groups_dir.", 11) == 0)
387       {
388         char tmp[strlen (name) + 3];
389
390         nis_domain_of_r (domain, tmp, sizeof (tmp));
391         strcpy (domain, tmp);
392       }
393     else
394       {
395         /* We have no grous_dir or org_dir, so try the complete name */
396         strcpy (domain, name);
397       }
398
399   switch (nis_dir_cmp (domain, dir->do_name))
400     {
401     case SAME_NAME:
402       return dir;
403     case NOT_SEQUENTIAL:
404       /* NOT_SEQUENTIAL means, go one up and try it there ! */
405     case HIGHER_NAME:
406       { /* We need data from a parent domain */
407         directory_obj *obj;
408         char ndomain [strlen (name) + 3];
409
410         nis_domain_of_r (dir->do_name, ndomain, sizeof (ndomain));
411
412         /* The root server of our domain is a replica of the parent
413            domain ! (Now I understand why a root server must be a
414            replica of the parent domain) */
415         fd_res = __nis_finddirectory (dir, ndomain);
416         if (fd_res->status != NIS_SUCCESS)
417           {
418             nis_free_directory (dir);
419             xdr_free((xdrproc_t)xdr_fd_result, (caddr_t)fd_res);
420             return NULL;
421           }
422         __cache_add (fd_res);
423         obj = calloc(1, sizeof(directory_obj));
424         xdrmem_create(&xdrs, fd_res->dir_data.dir_data_val,
425                       fd_res->dir_data.dir_data_len, XDR_DECODE);
426         xdr_directory_obj(&xdrs, obj);
427         xdr_destroy(&xdrs);
428         xdr_free((xdrproc_t)xdr_fd_result, (caddr_t)fd_res);
429         if (obj != NULL)
430           {
431             /* We have found a NIS+ server serving ndomain, now
432                let us search for "name" */
433             nis_free_directory (dir);
434             return rec_dirsearch (name, obj, flags);
435           }
436         else
437           {
438             /* Ups, very bad. Are we already the root server ? */
439             nis_free_directory (dir);
440             return NULL;
441           }
442       }
443     break;
444     case LOWER_NAME:
445       {
446         directory_obj *obj;
447         char leaf [strlen (name) + 3];
448         char ndomain [strlen (name) + 3];
449         char *cp;
450         
451         do
452           {
453             if (strlen (domain) == 0)
454               {
455                 nis_free_directory (dir);
456                 return NULL;
457               }
458             nis_leaf_of_r (domain, leaf, sizeof (leaf));
459             nis_domain_of_r (domain, ndomain, sizeof (ndomain));
460             strcpy (domain, ndomain);
461           }
462         while (nis_dir_cmp (domain, dir->do_name) != SAME_NAME);
463         cp = strchr (leaf, '\0');
464         *cp++ = '.';
465         strcpy (cp, domain);
466         
467         fd_res = __nis_finddirectory (dir, leaf);
468         if (fd_res->status != NIS_SUCCESS)
469           {
470             nis_free_directory (dir);
471             xdr_free((xdrproc_t)xdr_fd_result, (caddr_t)fd_res);
472             return NULL;
473           }
474         __cache_add (fd_res);
475         obj = calloc(1, sizeof(directory_obj));
476         xdrmem_create(&xdrs, fd_res->dir_data.dir_data_val,
477                       fd_res->dir_data.dir_data_len, XDR_DECODE);
478         xdr_directory_obj(&xdrs, obj);
479         xdr_destroy(&xdrs);
480         xdr_free((xdrproc_t)xdr_fd_result, (caddr_t)fd_res);
481         if (obj != NULL)
482           {
483             /* We have found a NIS+ server serving ndomain, now
484                let us search for "name" */
485             nis_free_directory (dir);
486             return rec_dirsearch (name, obj, flags);
487           }
488       }
489     break;
490     case BAD_NAME:
491       nis_free_directory (dir);
492       return NULL;
493     }
494   nis_free_directory (dir);
495   return NULL;
496 }
497
498 nis_error
499 __do_niscall (const_nis_name name, u_long prog, xdrproc_t xargs,
500               caddr_t req, xdrproc_t xres, caddr_t resp, u_long flags)
501 {
502   nis_error retcode;
503   directory_obj *dir = NULL;
504   nis_server *server;
505   u_int server_len;
506
507   if (name == NULL)
508     return NIS_BADNAME;
509
510   if ((flags & NO_CACHE) !=  NO_CACHE)
511     dir = __cache_search (name);
512   
513   if (dir == NULL)
514     {
515       dir = readColdStartFile ();
516       if (dir == NULL) /* No /var/nis/NIS_COLD_START->no NIS+ installed */
517         return NIS_UNAVAIL;
518       
519       dir = rec_dirsearch (name, dir, flags);
520       if (dir == NULL)
521         return NIS_NOTFOUND;
522     }
523
524   if (flags & MASTER_ONLY) 
525     {
526       server = dir->do_servers.do_servers_val;
527       server_len = 1;
528     }
529   else
530     {
531       server = dir->do_servers.do_servers_val;
532       server_len = dir->do_servers.do_servers_len;
533     }
534   
535   
536   retcode = __do_niscall2 (server, server_len, prog, xargs, req, xres, resp,
537                            flags);
538   
539   nis_free_directory (dir);
540
541   return retcode;
542 }