Sun Jun 23 19:42:05 1996 Ulrich Drepper <drepper@cygnus.com>
[kopensolaris-gnu/glibc.git] / nss / nsswitch.c
1 /* Copyright (C) 1996 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
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
17 not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.  */
19
20 #include <ctype.h>
21 #include <dlfcn.h>
22 #include <netdb.h>
23 #include <libc-lock.h>
24 #include <search.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include "nsswitch.h"
30 #include "../elf/link.h"        /* We need some help from ld.so.  */
31
32 /* Prototypes for the local functions.  */
33 static void nss_init (void);
34 static void *nss_lookup_function (service_user *ni, const char *fct_name);
35 static int nss_find_entry (struct entry **knownp, const char *key,
36                            void **valp);
37 static void nss_insert_entry (struct entry **knownp, const char *key,
38                               void *val);
39 static name_database *nss_parse_file (const char *fname);
40 static name_database_entry *nss_getline (char *line);
41 static service_library *nss_new_service (name_database *database,
42                                          const char *name);
43
44
45 __libc_lock_define_initialized (static, lock);
46
47
48 /* Global variable.  */
49 struct __res_state _res;
50
51
52 /* Known aliases for service names.  */
53 static struct {
54   const char *alias;
55   const char *value;
56 } service_alias[] =
57 {
58   { "nis+", "nisplus" },
59   { "yp", "nis" }
60 };
61
62
63 /* Nonzero if the sevices are already initialized.  */
64 static int nss_initialized;
65
66
67 /* The root of the whole data base.  */
68 static name_database *service_table;
69
70
71 static void
72 nss_init (void)
73 {
74   /* Prevent multiple threads to change the service table.  */
75   __libc_lock_lock (lock);
76
77   if (service_table == NULL)
78     service_table = nss_parse_file (_PATH_NSSWITCH_CONF);
79
80   __libc_lock_unlock (lock);
81 }
82
83
84 /* -1 == database not found
85     0 == database entry pointer stored */
86 int
87 __nss_database_lookup (const char *database, service_user **ni)
88 {
89   /* Return first `service_user' entry for DATABASE.
90      XXX Will use perfect hashing function for known databases.  */
91   name_database_entry *entry;
92
93   /* Test whether configuration data is available.  */
94   if (service_table == NULL)
95     {
96       if (nss_initialized == 0)
97         nss_init ();
98
99       if (service_table == NULL)
100         return -1;
101     }
102
103   /* XXX Could use some faster mechanism here.  But each database is
104      only requested once and so this might not be critical.  */
105   for (entry = service_table->entry; entry != NULL; entry = entry->next)
106     if (strcmp (database, entry->name) == 0)
107       break;
108
109   if (entry == NULL || (*ni = entry->service) == NULL)
110     return -1;
111
112   return 0;
113 }
114
115
116 /* -1 == not found
117     0 == adjusted for next function */
118 int
119 __nss_lookup (service_user **ni, const char *fct_name, void **fctp)
120 {
121   *fctp = nss_lookup_function (*ni, fct_name);
122
123   while (*fctp == NULL
124          && nss_next_action (*ni, NSS_STATUS_UNAVAIL) == NSS_ACTION_CONTINUE
125          && (*ni)->next != NULL)
126     {
127       *ni = (*ni)->next;
128
129       *fctp = nss_lookup_function (*ni, fct_name);
130     }
131
132   return *fctp != NULL ? 0 : -1;
133 }
134
135
136 /* -1 == not found
137     0 == adjusted for next function
138     1 == finished */
139 int
140 __nss_next (service_user **ni, const char *fct_name, void **fctp, int status,
141             int all_values)
142 {
143   if (all_values)
144     {
145       if (nss_next_action (*ni, NSS_STATUS_TRYAGAIN) == NSS_ACTION_RETURN
146           && nss_next_action (*ni, NSS_STATUS_UNAVAIL) == NSS_ACTION_RETURN
147           && nss_next_action (*ni, NSS_STATUS_NOTFOUND) == NSS_ACTION_RETURN
148           && nss_next_action (*ni, NSS_STATUS_SUCCESS) == NSS_ACTION_RETURN)
149         return 1;
150     }
151   else
152     {
153       /* This is really only for debugging.  */
154        if (NSS_STATUS_TRYAGAIN > status || status > NSS_STATUS_SUCCESS)
155          __libc_fatal ("illegal status in " __FUNCTION__);
156
157        if (nss_next_action (*ni, status) == NSS_ACTION_RETURN)
158          return 1;
159     }
160
161   if ((*ni)->next == NULL)
162     return -1;
163
164   do
165     {
166       *ni = (*ni)->next;
167
168       *fctp = nss_lookup_function (*ni, fct_name);
169     }
170   while (*fctp == NULL
171          && nss_next_action (*ni, NSS_STATUS_UNAVAIL) == NSS_ACTION_CONTINUE
172          && (*ni)->next != NULL);
173
174   return *fctp != NULL ? 0 : -1;
175 }
176
177
178 static int
179 nss_dlerror_run (void (*operate) (void))
180 {
181   const char *last_errstring = NULL;
182   const char *last_object_name = NULL;
183
184   (void) _dl_catch_error (&last_errstring, &last_object_name, operate);
185
186   return last_errstring != NULL;
187 }
188
189
190 static void *
191 nss_lookup_function (service_user *ni, const char *fct_name)
192 {
193   void *result;
194
195   /* Determine whether current function is loaded.  */
196   if (nss_find_entry (&ni->known, fct_name, &result) >= 0)
197     return result;
198
199   /* If we failed to allocate the needed data structures for the
200      service return an error.  This should only happen when we are out
201      of memory.  */
202   if (ni->library == NULL)
203     return NULL;
204
205   /* We now modify global data.  Protect it.  */
206   __libc_lock_lock (lock);
207
208   if (ni->library->lib_handle == NULL)
209     {
210       /* Load the shared library.  */
211       size_t shlen = (7 + strlen (ni->library->name) + 3
212                       + sizeof (NSS_SHLIB_REVISION));
213       char shlib_name[shlen];
214
215       void do_open (void)
216         {
217           /* The used function is found in GNU ld.so.  XXX The first
218              argument to _dl_open used to be `_dl_loaded'.  But this
219              does (currently) not work.  */
220           ni->library->lib_handle = _dl_open (shlib_name, RTLD_LAZY);
221         }
222
223       /* Construct name.  */
224       __stpcpy (__stpcpy (__stpcpy (shlib_name, "libnss_"), ni->library->name),
225                 ".so" NSS_SHLIB_REVISION);
226
227       if (nss_dlerror_run (do_open) != 0)
228         /* Failed to load the library.  */
229         ni->library->lib_handle = (void *) -1;
230     }
231
232   if (ni->library->lib_handle == (void *) -1)
233     /* Library not found => function not found.  */
234     result = NULL;
235   else
236     {
237       /* Get the desired function.  Again,  GNU ld.so magic ahead.  */
238       size_t namlen = (5 + strlen (ni->library->name) + 1
239                        + strlen (fct_name) + 1);
240       char name[namlen];
241       struct link_map *map = ni->library->lib_handle;
242       Elf32_Addr loadbase;
243       const Elf32_Sym *ref = NULL;
244       void get_sym (void)
245         {
246           struct link_map *scope[2] = { map, NULL };
247           loadbase = _dl_lookup_symbol (name, &ref, scope, map->l_name, 0, 0);
248         }
249
250       __stpcpy (__stpcpy (__stpcpy (__stpcpy (name, "_nss_"),
251                                     ni->library->name),
252                           "_"),
253                 fct_name);
254
255       result = (nss_dlerror_run (get_sym)
256                 ? NULL : (void *) (loadbase + ref->st_value));
257     }
258
259   /* Remember function pointer for the usage.  */
260   nss_insert_entry (&ni->known, fct_name, result);
261
262   /* Remove the lock.  */
263   __libc_lock_unlock (lock);
264
265   return result;
266 }
267
268
269 static int
270 known_compare (const void *p1, const void *p2)
271 {
272   known_function *v1 = (known_function *) p1;
273   known_function *v2 = (known_function *) p2;
274
275   return strcmp (v1->fct_name, v2->fct_name);
276 }
277
278
279 static int
280 nss_find_entry (struct entry **knownp, const char *key, void **valp)
281 {
282   known_function looking_for = { fct_name: key };
283   struct entry **found;
284
285   found = __tfind (&looking_for, (const void **) knownp, known_compare);
286
287   if (found == NULL)
288     return -1;
289
290   *valp = ((known_function *) (*found)->key)->fct_ptr;
291
292   return 0;
293 }
294
295
296 static void
297 nss_insert_entry (struct entry **knownp, const char *key, void *val)
298 {
299   known_function *to_insert;
300
301   to_insert = (known_function *) malloc (sizeof (known_function));
302   if (to_insert == NULL)
303     return;
304
305   to_insert->fct_name = key;
306   to_insert->fct_ptr = val;
307
308   __tsearch (to_insert, (void **) knownp, known_compare);
309 }
310
311
312 static name_database *
313 nss_parse_file (const char *fname)
314 {
315   FILE *fp;
316   name_database *result;
317   name_database_entry *last;
318   char *line;
319   size_t len;
320
321   /* Open the configuration file.  */
322   fp = fopen (fname, "r");
323   if (fp == NULL)
324     return NULL;
325
326   result = (name_database *) malloc (sizeof (name_database));
327   if (result == NULL)
328     return NULL;
329
330   result->entry = NULL;
331   result->library = NULL;
332   last = NULL;
333   line = NULL;
334   len = 0;
335   do
336     {
337       name_database_entry *this;
338       ssize_t n;
339       char *cp;
340
341       n = getline (&line, &len, fp);
342       if (n < 0)
343         break;
344       if (line[n - 1] == '\n')
345         line[n - 1] = '\0';
346
347       /* Because the file format does not know any form of quoting we
348          can search forward for the next '#' character and if found
349          make it terminating the line.  */
350       cp = strchr (line, '#');
351       if (cp != NULL)
352         *cp = '\0';
353
354       /* If the line is blank it is ignored.  */
355       if (line[0] == '\0')
356         continue;
357
358       /* Each line completely specifies the actions for a database.  */
359       this = nss_getline (line);
360       if (this != NULL)
361         {
362           if (last != NULL)
363             last->next = this;
364           else
365             result->entry = this;
366
367           last = this;
368         }
369     }
370   while (!feof (fp));
371
372   /* Free the buffer.  */
373   free (line);
374   /* Close configuration file.  */
375   fclose (fp);
376
377   /* Now create for each service we could use an entry in LIBRARY list.  */
378   for (last = result->entry; last != NULL; last = last->next)
379     {
380       service_user *runp;
381
382       for (runp = last->service; runp != NULL; runp = runp->next)
383         runp->library = nss_new_service (result, runp->name);
384     }
385
386   return result;
387 }
388
389
390 static name_database_entry *
391 nss_getline (char *line)
392 {
393   const char *name;
394   name_database_entry *result;
395   service_user *last;
396
397   /* Ignore leading white spaces.  ATTENTION: this is different from
398      what is implemented in Solaris.  The Solaris man page says a line
399      beginning with a white space character is ignored.  We regard
400      this as just another misfeature in Solaris.  */
401   while (isspace (line[0]))
402     ++line;
403
404   /* Recognize `<database> ":"'.  */
405   name = line;
406   while (line[0] != '\0' && !isspace (line[0]) && line[0] != ':')
407     ++line;
408   if (line[0] == '\0' || name == line)
409     /* Syntax error.  */
410     return NULL;
411   *line++ = '\0';
412
413   result = (name_database_entry *) malloc (sizeof (name_database_entry));
414   if (result == NULL)
415     return NULL;
416
417   result->name = strdup (name);
418   if (result->name == NULL)
419     {
420       free (result);
421       return NULL;
422     }
423   result->service = NULL;
424   result->next = NULL;
425   last = NULL;
426
427   /* Read the source names: `<source> ( "[" <status> "=" <action> "]" )*'.  */
428   while (1)
429     {
430       service_user *new_service;
431       size_t n;
432
433       while (isspace (line[0]))
434         ++line;
435       if (line[0] == '\0')
436         /* No source specified.  */
437         return result;
438
439       /* Read <source> identifier.  */
440       name = line;
441       while (line[0] != '\0' && !isspace (line[0]) && line[0] != '[')
442         ++line;
443       if (name == line)
444         return result;
445
446
447       new_service = (service_user *) malloc (sizeof (service_user));
448       if (new_service == NULL)
449         return result;
450
451       /* Test whether the source name is one of the aliases.  */
452       for (n = 0; n < sizeof (service_alias) / sizeof (service_alias[0]); ++n)
453         if (strncmp (service_alias[n].alias, name, line - name) == 0
454             && service_alias[n].alias[line - name] == '\0')
455           break;
456
457       if (n < sizeof (service_alias) / sizeof (service_alias[0]))
458         new_service->name = service_alias[n].value;
459       else
460         {
461           char *source = (char *) malloc (line - name + 1);
462           if (source == NULL)
463             {
464               free (new_service);
465               return result;
466             }
467           memcpy (source, name, line - name);
468           source[line - name] = '\0';
469
470           new_service->name = source;
471         }
472
473       /* Set default actions.  */
474       new_service->actions[2 + NSS_STATUS_TRYAGAIN] = NSS_ACTION_CONTINUE;
475       new_service->actions[2 + NSS_STATUS_UNAVAIL] = NSS_ACTION_CONTINUE;
476       new_service->actions[2 + NSS_STATUS_NOTFOUND] = NSS_ACTION_CONTINUE;
477       new_service->actions[2 + NSS_STATUS_SUCCESS] = NSS_ACTION_RETURN;
478       new_service->library = NULL;
479       new_service->known = NULL;
480       new_service->next = NULL;
481
482       while (isspace (line[0]))
483         ++line;
484
485       if (line[0] == '[')
486         {
487           int status;
488
489           /* Read criterions.  */
490           do
491             ++line;
492           while (line[0] != '\0' && isspace (line[0]));
493
494           do
495             {
496               /* Read status name.  */
497               name = line;
498               while (line[0] != '\0' && !isspace (line[0]) && line[0] != '='
499                      && line[0] != ']')
500                 ++line;
501
502               /* Compare with known statii.  */
503               if (line - name == 7)
504                 {
505                   if (strncasecmp (name, "SUCCESS", 7) == 0)
506                     status = NSS_STATUS_SUCCESS;
507                   else if (strncasecmp (name, "UNAVAIL", 7) == 0)
508                     status = NSS_STATUS_UNAVAIL;
509                   else
510                     return result;
511                 }
512               else if (line - name == 8)
513                 {
514                   if (strncasecmp (name, "NOTFOUND", 8) == 0)
515                     status = NSS_STATUS_NOTFOUND;
516                   else if (strncasecmp (name, "TRYAGAIN", 8) == 0)
517                     status = NSS_STATUS_TRYAGAIN;
518                   else
519                     return result;
520                 }
521               else
522                 return result;
523
524               while (isspace (line[0]))
525                 ++line;
526               if (line[0] != '=')
527                 return result;
528               do
529                 ++line;
530               while (isspace (line[0]));
531
532               name = line;
533               while (line[0] != '\0' && !isspace (line[0]) && line[0] != '='
534                      && line[0] != ']')
535                 ++line;
536
537               if (line - name == 6 && strncasecmp (name, "RETURN", 6) == 0)
538                 new_service->actions[2 + status] = NSS_ACTION_RETURN;
539               else if (line - name == 8
540                        && strncasecmp (name, "CONTINUE", 8) == 0)
541                 new_service->actions[2 + status] = NSS_ACTION_CONTINUE;
542               else
543                 return result;
544
545               /* Match white spaces.  */
546               while (isspace (line[0]))
547                 ++line;
548             }
549           while (line[0] != ']');
550
551           /* Skip the ']'.  */
552           ++line;
553         }
554
555       if (last == NULL)
556         result->service = new_service;
557       else
558         last->next = new_service;
559       last = new_service;
560     }
561   /* NOTREACHED */
562   return NULL;
563 }
564
565
566 static service_library *
567 nss_new_service (name_database *database, const char *name)
568 {
569   service_library **currentp = &database->library;
570
571   while (*currentp != NULL)
572     {
573       if (strcmp ((*currentp)->name, name) == 0)
574         return *currentp;
575       currentp = &(*currentp)->next;
576     }
577
578   /* We have to add the new service.  */
579   *currentp = (service_library *) malloc (sizeof (service_library));
580   if (*currentp == NULL)
581     return NULL;
582
583   (*currentp)->name = name;
584   (*currentp)->lib_handle = NULL;
585   (*currentp)->next = NULL;
586
587   return *currentp;
588 }