2b3ae0bbe10d7cb2e381be7414c883d9f792d898
[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   if (nss_initialized == 0)
90     nss_init ();
91
92   /* Test whether configuration data is available.  */
93   if (service_table)
94     {
95       /* Return first `service_user' entry for DATABASE.
96          XXX Will use perfect hashing function for known databases.  */
97       name_database_entry *entry;
98
99       /* XXX Could use some faster mechanism here.  But each database is
100          only requested once and so this might not be critical.  */
101       for (entry = service_table->entry; entry != NULL; entry = entry->next)
102         if (strcmp (database, entry->name) == 0)
103           {
104             *ni = entry->service;
105             return 0;
106           }
107     }
108
109   /* No configuration data is available, either because nsswitch.conf
110      doesn't exist or because it doesn't have a line for this database.
111      Use a default equivalent to:
112         database: compat [NOTFOUND=return] dns [NOTFOUND=return] files
113      */
114   {
115 #define DEFAULT_SERVICE(name, next)                                           \
116     static service_user default_##name =                                      \
117       {                                                                       \
118         #name,                                                                \
119         {                                                                     \
120           NSS_ACTION_CONTINUE,                                                \
121           NSS_ACTION_CONTINUE,                                                \
122           NSS_ACTION_RETURN,                                                  \
123           NSS_ACTION_RETURN,                                                  \
124         },                                                                    \
125         NULL, NULL,                                                           \
126         next                                                                  \
127       }
128     DEFAULT_SERVICE (files, NULL);
129     DEFAULT_SERVICE (dns, &default_files);
130     DEFAULT_SERVICE (compat, &default_dns);
131     *ni = &default_compat;
132   }
133   return 0;
134 }
135
136
137 /* -1 == not found
138     0 == adjusted for next function */
139 int
140 __nss_lookup (service_user **ni, const char *fct_name, void **fctp)
141 {
142   *fctp = nss_lookup_function (*ni, fct_name);
143
144   while (*fctp == NULL
145          && nss_next_action (*ni, NSS_STATUS_UNAVAIL) == NSS_ACTION_CONTINUE
146          && (*ni)->next != NULL)
147     {
148       *ni = (*ni)->next;
149
150       *fctp = nss_lookup_function (*ni, fct_name);
151     }
152
153   return *fctp != NULL ? 0 : -1;
154 }
155
156
157 /* -1 == not found
158     0 == adjusted for next function
159     1 == finished */
160 int
161 __nss_next (service_user **ni, const char *fct_name, void **fctp, int status,
162             int all_values)
163 {
164   if (all_values)
165     {
166       if (nss_next_action (*ni, NSS_STATUS_TRYAGAIN) == NSS_ACTION_RETURN
167           && nss_next_action (*ni, NSS_STATUS_UNAVAIL) == NSS_ACTION_RETURN
168           && nss_next_action (*ni, NSS_STATUS_NOTFOUND) == NSS_ACTION_RETURN
169           && nss_next_action (*ni, NSS_STATUS_SUCCESS) == NSS_ACTION_RETURN)
170         return 1;
171     }
172   else
173     {
174       /* This is really only for debugging.  */
175        if (NSS_STATUS_TRYAGAIN > status || status > NSS_STATUS_SUCCESS)
176          __libc_fatal ("illegal status in " __FUNCTION__);
177
178        if (nss_next_action (*ni, status) == NSS_ACTION_RETURN)
179          return 1;
180     }
181
182   if ((*ni)->next == NULL)
183     return -1;
184
185   do
186     {
187       *ni = (*ni)->next;
188
189       *fctp = nss_lookup_function (*ni, fct_name);
190     }
191   while (*fctp == NULL
192          && nss_next_action (*ni, NSS_STATUS_UNAVAIL) == NSS_ACTION_CONTINUE
193          && (*ni)->next != NULL);
194
195   return *fctp != NULL ? 0 : -1;
196 }
197
198
199 static int
200 nss_dlerror_run (void (*operate) (void))
201 {
202   const char *last_errstring = NULL;
203   const char *last_object_name = NULL;
204
205   (void) _dl_catch_error (&last_errstring, &last_object_name, operate);
206
207   return last_errstring != NULL;
208 }
209
210
211 static void *
212 nss_lookup_function (service_user *ni, const char *fct_name)
213 {
214   void *result;
215
216   /* Determine whether current function is loaded.  */
217   if (nss_find_entry (&ni->known, fct_name, &result) >= 0)
218     return result;
219
220   /* We now modify global data.  Protect it.  */
221   __libc_lock_lock (lock);
222
223   if (ni->library == NULL)
224     {
225       /* This service has not yet been used.  Fetch the service library
226          for it, creating a new one if need be.  If there is no service
227          table from the file, this static variable holds the head of the
228          service_library list made from the default configuration.  */
229       static name_database default_table;
230       ni->library = nss_new_service (service_table ?: &default_table,
231                                      ni->name);
232       if (ni->library == NULL)
233         {
234           /* This only happens when out of memory.  */
235           __libc_lock_unlock (lock);
236           return NULL;
237         }
238     }
239
240   if (ni->library->lib_handle == NULL)
241     {
242       /* Load the shared library.  */
243       size_t shlen = (7 + strlen (ni->library->name) + 3
244                       + sizeof (NSS_SHLIB_REVISION));
245       char shlib_name[shlen];
246
247       void do_open (void)
248         {
249           /* The used function is found in GNU ld.so.  XXX The first
250              argument to _dl_open used to be `_dl_loaded'.  But this
251              does (currently) not work.  */
252           ni->library->lib_handle = _dl_open (shlib_name, RTLD_LAZY);
253         }
254
255       /* Construct name.  */
256       __stpcpy (__stpcpy (__stpcpy (shlib_name, "libnss_"), ni->library->name),
257                 ".so" NSS_SHLIB_REVISION);
258
259       if (nss_dlerror_run (do_open) != 0)
260         /* Failed to load the library.  */
261         ni->library->lib_handle = (void *) -1;
262     }
263
264   if (ni->library->lib_handle == (void *) -1)
265     /* Library not found => function not found.  */
266     result = NULL;
267   else
268     {
269       /* Get the desired function.  Again,  GNU ld.so magic ahead.  */
270       size_t namlen = (5 + strlen (ni->library->name) + 1
271                        + strlen (fct_name) + 1);
272       char name[namlen];
273       struct link_map *map = ni->library->lib_handle;
274       Elf32_Addr loadbase;
275       const Elf32_Sym *ref = NULL;
276       void get_sym (void)
277         {
278           struct link_map *scope[2] = { map, NULL };
279           loadbase = _dl_lookup_symbol (name, &ref, scope, map->l_name, 0, 0);
280         }
281
282       __stpcpy (__stpcpy (__stpcpy (__stpcpy (name, "_nss_"),
283                                     ni->library->name),
284                           "_"),
285                 fct_name);
286
287       result = (nss_dlerror_run (get_sym)
288                 ? NULL : (void *) (loadbase + ref->st_value));
289     }
290
291   /* Remember function pointer for the usage.  */
292   nss_insert_entry (&ni->known, fct_name, result);
293
294   /* Remove the lock.  */
295   __libc_lock_unlock (lock);
296
297   return result;
298 }
299
300
301 static int
302 known_compare (const void *p1, const void *p2)
303 {
304   known_function *v1 = (known_function *) p1;
305   known_function *v2 = (known_function *) p2;
306
307   return strcmp (v1->fct_name, v2->fct_name);
308 }
309
310
311 static int
312 nss_find_entry (struct entry **knownp, const char *key, void **valp)
313 {
314   known_function looking_for = { fct_name: key };
315   struct entry **found;
316
317   found = __tfind (&looking_for, (const void **) knownp, known_compare);
318
319   if (found == NULL)
320     return -1;
321
322   *valp = ((known_function *) (*found)->key)->fct_ptr;
323
324   return 0;
325 }
326
327
328 static void
329 nss_insert_entry (struct entry **knownp, const char *key, void *val)
330 {
331   known_function *to_insert;
332
333   to_insert = (known_function *) malloc (sizeof (known_function));
334   if (to_insert == NULL)
335     return;
336
337   to_insert->fct_name = key;
338   to_insert->fct_ptr = val;
339
340   __tsearch (to_insert, (void **) knownp, known_compare);
341 }
342
343
344 static name_database *
345 nss_parse_file (const char *fname)
346 {
347   FILE *fp;
348   name_database *result;
349   name_database_entry *last;
350   char *line;
351   size_t len;
352
353   /* Open the configuration file.  */
354   fp = fopen (fname, "r");
355   if (fp == NULL)
356     return NULL;
357
358   result = (name_database *) malloc (sizeof (name_database));
359   if (result == NULL)
360     return NULL;
361
362   result->entry = NULL;
363   result->library = NULL;
364   last = NULL;
365   line = NULL;
366   len = 0;
367   do
368     {
369       name_database_entry *this;
370       ssize_t n;
371       char *cp;
372
373       n = getline (&line, &len, fp);
374       if (n < 0)
375         break;
376       if (line[n - 1] == '\n')
377         line[n - 1] = '\0';
378
379       /* Because the file format does not know any form of quoting we
380          can search forward for the next '#' character and if found
381          make it terminating the line.  */
382       cp = strchr (line, '#');
383       if (cp != NULL)
384         *cp = '\0';
385
386       /* If the line is blank it is ignored.  */
387       if (line[0] == '\0')
388         continue;
389
390       /* Each line completely specifies the actions for a database.  */
391       this = nss_getline (line);
392       if (this != NULL)
393         {
394           if (last != NULL)
395             last->next = this;
396           else
397             result->entry = this;
398
399           last = this;
400         }
401     }
402   while (!feof (fp));
403
404   /* Free the buffer.  */
405   free (line);
406   /* Close configuration file.  */
407   fclose (fp);
408
409   return result;
410 }
411
412
413 static name_database_entry *
414 nss_getline (char *line)
415 {
416   const char *name;
417   name_database_entry *result;
418   service_user *last;
419
420   /* Ignore leading white spaces.  ATTENTION: this is different from
421      what is implemented in Solaris.  The Solaris man page says a line
422      beginning with a white space character is ignored.  We regard
423      this as just another misfeature in Solaris.  */
424   while (isspace (line[0]))
425     ++line;
426
427   /* Recognize `<database> ":"'.  */
428   name = line;
429   while (line[0] != '\0' && !isspace (line[0]) && line[0] != ':')
430     ++line;
431   if (line[0] == '\0' || name == line)
432     /* Syntax error.  */
433     return NULL;
434   *line++ = '\0';
435
436   result = (name_database_entry *) malloc (sizeof (name_database_entry));
437   if (result == NULL)
438     return NULL;
439
440   result->name = strdup (name);
441   if (result->name == NULL)
442     {
443       free (result);
444       return NULL;
445     }
446   result->service = NULL;
447   result->next = NULL;
448   last = NULL;
449
450   /* Read the source names: `<source> ( "[" <status> "=" <action> "]" )*'.  */
451   while (1)
452     {
453       service_user *new_service;
454       size_t n;
455
456       while (isspace (line[0]))
457         ++line;
458       if (line[0] == '\0')
459         /* No source specified.  */
460         return result;
461
462       /* Read <source> identifier.  */
463       name = line;
464       while (line[0] != '\0' && !isspace (line[0]) && line[0] != '[')
465         ++line;
466       if (name == line)
467         return result;
468
469
470       new_service = (service_user *) malloc (sizeof (service_user));
471       if (new_service == NULL)
472         return result;
473
474       /* Test whether the source name is one of the aliases.  */
475       for (n = 0; n < sizeof (service_alias) / sizeof (service_alias[0]); ++n)
476         if (strncmp (service_alias[n].alias, name, line - name) == 0
477             && service_alias[n].alias[line - name] == '\0')
478           break;
479
480       if (n < sizeof (service_alias) / sizeof (service_alias[0]))
481         new_service->name = service_alias[n].value;
482       else
483         {
484           char *source = (char *) malloc (line - name + 1);
485           if (source == NULL)
486             {
487               free (new_service);
488               return result;
489             }
490           memcpy (source, name, line - name);
491           source[line - name] = '\0';
492
493           new_service->name = source;
494         }
495
496       /* Set default actions.  */
497       new_service->actions[2 + NSS_STATUS_TRYAGAIN] = NSS_ACTION_CONTINUE;
498       new_service->actions[2 + NSS_STATUS_UNAVAIL] = NSS_ACTION_CONTINUE;
499       new_service->actions[2 + NSS_STATUS_NOTFOUND] = NSS_ACTION_CONTINUE;
500       new_service->actions[2 + NSS_STATUS_SUCCESS] = NSS_ACTION_RETURN;
501       new_service->library = NULL;
502       new_service->known = NULL;
503       new_service->next = NULL;
504
505       while (isspace (line[0]))
506         ++line;
507
508       if (line[0] == '[')
509         {
510           int status;
511
512           /* Read criterions.  */
513           do
514             ++line;
515           while (line[0] != '\0' && isspace (line[0]));
516
517           do
518             {
519               /* Read status name.  */
520               name = line;
521               while (line[0] != '\0' && !isspace (line[0]) && line[0] != '='
522                      && line[0] != ']')
523                 ++line;
524
525               /* Compare with known statii.  */
526               if (line - name == 7)
527                 {
528                   if (strncasecmp (name, "SUCCESS", 7) == 0)
529                     status = NSS_STATUS_SUCCESS;
530                   else if (strncasecmp (name, "UNAVAIL", 7) == 0)
531                     status = NSS_STATUS_UNAVAIL;
532                   else
533                     return result;
534                 }
535               else if (line - name == 8)
536                 {
537                   if (strncasecmp (name, "NOTFOUND", 8) == 0)
538                     status = NSS_STATUS_NOTFOUND;
539                   else if (strncasecmp (name, "TRYAGAIN", 8) == 0)
540                     status = NSS_STATUS_TRYAGAIN;
541                   else
542                     return result;
543                 }
544               else
545                 return result;
546
547               while (isspace (line[0]))
548                 ++line;
549               if (line[0] != '=')
550                 return result;
551               do
552                 ++line;
553               while (isspace (line[0]));
554
555               name = line;
556               while (line[0] != '\0' && !isspace (line[0]) && line[0] != '='
557                      && line[0] != ']')
558                 ++line;
559
560               if (line - name == 6 && strncasecmp (name, "RETURN", 6) == 0)
561                 new_service->actions[2 + status] = NSS_ACTION_RETURN;
562               else if (line - name == 8
563                        && strncasecmp (name, "CONTINUE", 8) == 0)
564                 new_service->actions[2 + status] = NSS_ACTION_CONTINUE;
565               else
566                 return result;
567
568               /* Match white spaces.  */
569               while (isspace (line[0]))
570                 ++line;
571             }
572           while (line[0] != ']');
573
574           /* Skip the ']'.  */
575           ++line;
576         }
577
578       if (last == NULL)
579         result->service = new_service;
580       else
581         last->next = new_service;
582       last = new_service;
583     }
584   /* NOTREACHED */
585   return NULL;
586 }
587
588
589 static service_library *
590 nss_new_service (name_database *database, const char *name)
591 {
592   service_library **currentp = &database->library;
593
594   while (*currentp != NULL)
595     {
596       if (strcmp ((*currentp)->name, name) == 0)
597         return *currentp;
598       currentp = &(*currentp)->next;
599     }
600
601   /* We have to add the new service.  */
602   *currentp = (service_library *) malloc (sizeof (service_library));
603   if (*currentp == NULL)
604     return NULL;
605
606   (*currentp)->name = name;
607   (*currentp)->lib_handle = NULL;
608   (*currentp)->next = NULL;
609
610   return *currentp;
611 }