NPTL: Remove gratuitous Linuxisms from gai_misc.h.
[platform/upstream/glibc.git] / inet / getnetgrent_r.c
1 /* Copyright (C) 1996-2014 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3
4    The GNU C Library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public
6    License as published by the Free Software Foundation; either
7    version 2.1 of the License, or (at your option) any later version.
8
9    The GNU C Library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13
14    You should have received a copy of the GNU Lesser General Public
15    License along with the GNU C Library; if not, see
16    <http://www.gnu.org/licenses/>.  */
17
18 #include <assert.h>
19 #include <atomic.h>
20 #include <bits/libc-lock.h>
21 #include <errno.h>
22 #include <netdb.h>
23 #include <stdbool.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include "netgroup.h"
27 #include "nsswitch.h"
28 #include <sysdep.h>
29 #include <nscd/nscd_proto.h>
30
31
32 /* Protect above variable against multiple uses at the same time.  */
33 __libc_lock_define_initialized (static, lock)
34
35 /* The whole information for the set/get/endnetgrent functions are
36    kept in this structure.  */
37 static struct __netgrent dataset;
38
39 /* The lookup function for the first entry of this service.  */
40 extern int __nss_netgroup_lookup (service_user **nipp, const char *name,
41                                   void **fctp) internal_function;
42
43 /* Set up NIP to run through the services.  Return nonzero if there are no
44    services (left).  */
45 static int
46 setup (void **fctp, service_user **nipp)
47 {
48   /* Remember the first service_entry, it's always the same.  */
49   static bool startp_initialized;
50   static service_user *startp;
51   int no_more;
52
53   if (!startp_initialized)
54     {
55       /* Executing this more than once at the same time must yield the
56          same result every time.  So we need no locking.  */
57       no_more = __nss_netgroup_lookup (nipp, "setnetgrent", fctp);
58       startp = no_more ? (service_user *) -1 : *nipp;
59 #ifdef PTR_MANGLE
60       PTR_MANGLE (startp);
61 #endif
62       atomic_write_barrier ();
63       startp_initialized = true;
64     }
65   else
66     {
67       service_user *nip = startp;
68 #ifdef PTR_DEMANGLE
69       PTR_DEMANGLE (nip);
70 #endif
71       if (nip == (service_user *) -1)
72         /* No services at all.  */
73         return 1;
74
75       /* Reset to the beginning of the service list.  */
76       *nipp = nip;
77       /* Look up the first function.  */
78       no_more = __nss_lookup (nipp, "setnetgrent", NULL, fctp);
79     }
80   return no_more;
81 }
82 \f
83 /* Free used memory.  */
84 static void
85 free_memory (struct __netgrent *data)
86 {
87   while (data->known_groups != NULL)
88     {
89       struct name_list *tmp = data->known_groups;
90       data->known_groups = data->known_groups->next;
91       free (tmp);
92     }
93
94   while (data->needed_groups != NULL)
95     {
96       struct name_list *tmp = data->needed_groups;
97       data->needed_groups = data->needed_groups->next;
98       free (tmp);
99     }
100 }
101 \f
102 static void
103 endnetgrent_hook (struct __netgrent *datap)
104 {
105   enum nss_status (*endfct) (struct __netgrent *);
106
107   if (datap->nip == NULL || datap->nip == (service_user *) -1l)
108     return;
109
110   endfct = __nss_lookup_function (datap->nip, "endnetgrent");
111   if (endfct != NULL)
112     (void) (*endfct) (datap);
113   datap->nip = NULL;
114 }
115
116 static int
117 internal_function
118 __internal_setnetgrent_reuse (const char *group, struct __netgrent *datap,
119                               int *errnop)
120 {
121   union
122   {
123     enum nss_status (*f) (const char *, struct __netgrent *);
124     void *ptr;
125   } fct;
126   enum nss_status status = NSS_STATUS_UNAVAIL;
127   struct name_list *new_elem;
128
129   /* Free data from previous service.  */
130   endnetgrent_hook (datap);
131
132   /* Cycle through all the services and run their setnetgrent functions.  */
133   int no_more = setup (&fct.ptr, &datap->nip);
134   while (! no_more)
135     {
136       assert (datap->data == NULL);
137
138       /* Ignore status, we force check in `__nss_next2'.  */
139       status = DL_CALL_FCT (*fct.f, (group, datap));
140
141       service_user *old_nip = datap->nip;
142       no_more = __nss_next2 (&datap->nip, "setnetgrent", NULL, &fct.ptr,
143                              status, 0);
144
145       if (status == NSS_STATUS_SUCCESS && ! no_more)
146         {
147           enum nss_status (*endfct) (struct __netgrent *);
148
149           endfct = __nss_lookup_function (old_nip, "endnetgrent");
150           if (endfct != NULL)
151             (void) DL_CALL_FCT (*endfct, (datap));
152         }
153     }
154
155   /* Add the current group to the list of known groups.  */
156   size_t group_len = strlen (group) + 1;
157   new_elem = (struct name_list *) malloc (sizeof (struct name_list)
158                                           + group_len);
159   if (new_elem == NULL)
160     {
161       *errnop = errno;
162       status = NSS_STATUS_TRYAGAIN;
163     }
164   else
165     {
166       new_elem->next = datap->known_groups;
167       memcpy (new_elem->name, group, group_len);
168       datap->known_groups = new_elem;
169     }
170
171   return status == NSS_STATUS_SUCCESS;
172 }
173
174 int
175 internal_function
176 __internal_setnetgrent (const char *group, struct __netgrent *datap)
177 {
178   /* Free list of all netgroup names from last run.  */
179   free_memory (datap);
180
181   return __internal_setnetgrent_reuse (group, datap, &errno);
182 }
183 libc_hidden_def (__internal_setnetgrent)
184
185 static int
186 nscd_setnetgrent (const char *group)
187 {
188 #ifdef USE_NSCD
189   if (__nss_not_use_nscd_netgroup > 0
190       && ++__nss_not_use_nscd_netgroup > NSS_NSCD_RETRY)
191     __nss_not_use_nscd_netgroup = 0;
192
193   if (!__nss_not_use_nscd_netgroup
194       && !__nss_database_custom[NSS_DBSIDX_netgroup])
195     return __nscd_setnetgrent (group, &dataset);
196 #endif
197   return -1;
198 }
199
200 int
201 setnetgrent (const char *group)
202 {
203   int result;
204
205   __libc_lock_lock (lock);
206
207   result = nscd_setnetgrent (group);
208   if (result < 0)
209     result = __internal_setnetgrent (group, &dataset);
210
211   __libc_lock_unlock (lock);
212
213   return result;
214 }
215
216 void
217 internal_function
218 __internal_endnetgrent (struct __netgrent *datap)
219 {
220   endnetgrent_hook (datap);
221   /* Now free list of all netgroup names from last run.  */
222   free_memory (datap);
223 }
224 libc_hidden_def (__internal_endnetgrent)
225
226
227 void
228 endnetgrent (void)
229 {
230   __libc_lock_lock (lock);
231
232   __internal_endnetgrent (&dataset);
233
234   __libc_lock_unlock (lock);
235 }
236
237 #ifdef USE_NSCD
238 static const char *
239 get_nonempty_val (const char *in)
240 {
241   if (*in == '\0')
242     return NULL;
243   return in;
244 }
245
246 static enum nss_status
247 nscd_getnetgrent (struct __netgrent *datap, char *buffer, size_t buflen,
248                   int *errnop)
249 {
250   if (datap->cursor >= datap->data + datap->data_size)
251     return NSS_STATUS_UNAVAIL;
252
253   datap->type = triple_val;
254   datap->val.triple.host = get_nonempty_val (datap->cursor);
255   datap->cursor = (char *) __rawmemchr (datap->cursor, '\0') + 1;
256   datap->val.triple.user = get_nonempty_val (datap->cursor);
257   datap->cursor = (char *) __rawmemchr (datap->cursor, '\0') + 1;
258   datap->val.triple.domain = get_nonempty_val (datap->cursor);
259   datap->cursor = (char *) __rawmemchr (datap->cursor, '\0') + 1;
260
261   return NSS_STATUS_SUCCESS;
262 }
263 #endif
264
265 int
266 internal_function
267 __internal_getnetgrent_r (char **hostp, char **userp, char **domainp,
268                           struct __netgrent *datap,
269                           char *buffer, size_t buflen, int *errnop)
270 {
271   enum nss_status (*fct) (struct __netgrent *, char *, size_t, int *);
272
273   /* Initialize status to return if no more functions are found.  */
274   enum nss_status status = NSS_STATUS_NOTFOUND;
275
276   /* Run through available functions, starting with the same function last
277      run.  We will repeat each function as long as it succeeds, and then go
278      on to the next service action.  */
279   int no_more = datap->nip == NULL;
280   if (! no_more)
281     {
282 #ifdef USE_NSCD
283       /* This bogus function pointer is a special marker left by
284          __nscd_setnetgrent to tell us to use the data it left
285          before considering any modules.  */
286       if (datap->nip == (service_user *) -1l)
287         fct = nscd_getnetgrent;
288       else
289 #endif
290         {
291           fct = __nss_lookup_function (datap->nip, "getnetgrent_r");
292           no_more = fct == NULL;
293         }
294
295       while (! no_more)
296         {
297           status = DL_CALL_FCT (*fct, (datap, buffer, buflen, &errno));
298
299           if (status == NSS_STATUS_RETURN
300               /* The service returned a NOTFOUND, but there are more groups that
301                  we need to resolve before we give up.  */
302               || (status == NSS_STATUS_NOTFOUND && datap->needed_groups != NULL))
303             {
304               /* This was the last one for this group.  Look at next group
305                  if available.  */
306               int found = 0;
307               while (datap->needed_groups != NULL && ! found)
308                 {
309                   struct name_list *tmp = datap->needed_groups;
310                   datap->needed_groups = datap->needed_groups->next;
311                   tmp->next = datap->known_groups;
312                   datap->known_groups = tmp;
313
314                   found = __internal_setnetgrent_reuse (datap->known_groups->name,
315                                                         datap, errnop);
316                 }
317
318               if (found && datap->nip != NULL)
319                 {
320                   fct = __nss_lookup_function (datap->nip, "getnetgrent_r");
321                   if (fct != NULL)
322                     continue;
323                 }
324             }
325           else if (status == NSS_STATUS_SUCCESS && datap->type == group_val)
326             {
327               /* The last entry was a name of another netgroup.  */
328               struct name_list *namep;
329
330               /* Ignore if we've seen the name before.  */
331               for (namep = datap->known_groups; namep != NULL;
332                    namep = namep->next)
333                 if (strcmp (datap->val.group, namep->name) == 0)
334                   break;
335               if (namep == NULL)
336                 for (namep = datap->needed_groups; namep != NULL;
337                      namep = namep->next)
338                   if (strcmp (datap->val.group, namep->name) == 0)
339                     break;
340               if (namep != NULL)
341                 /* Really ignore.  */
342                 continue;
343
344               size_t group_len = strlen (datap->val.group) + 1;
345               namep = (struct name_list *) malloc (sizeof (struct name_list)
346                                                   + group_len);
347               if (namep == NULL)
348                 /* We are out of memory.  */
349                 status = NSS_STATUS_RETURN;
350               else
351                 {
352                   namep->next = datap->needed_groups;
353                   memcpy (namep->name, datap->val.group, group_len);
354                   datap->needed_groups = namep;
355                   /* And get the next entry.  */
356                   continue;
357                 }
358             }
359           break;
360         }
361     }
362
363   if (status == NSS_STATUS_SUCCESS)
364     {
365       *hostp = (char *) datap->val.triple.host;
366       *userp = (char *) datap->val.triple.user;
367       *domainp = (char *) datap->val.triple.domain;
368     }
369
370   return status == NSS_STATUS_SUCCESS ? 1 : 0;
371 }
372 libc_hidden_def (__internal_getnetgrent_r)
373
374 /* The real entry point.  */
375 int
376 __getnetgrent_r (char **hostp, char **userp, char **domainp,
377                  char *buffer, size_t buflen)
378 {
379   enum nss_status status;
380
381   __libc_lock_lock (lock);
382
383   status = __internal_getnetgrent_r (hostp, userp, domainp, &dataset,
384                                      buffer, buflen, &errno);
385
386   __libc_lock_unlock (lock);
387
388   return status;
389 }
390 weak_alias (__getnetgrent_r, getnetgrent_r)
391 \f
392 /* Test whether given (host,user,domain) triple is in NETGROUP.  */
393 int
394 innetgr (const char *netgroup, const char *host, const char *user,
395          const char *domain)
396 {
397 #ifdef USE_NSCD
398   if (__nss_not_use_nscd_netgroup > 0
399       && ++__nss_not_use_nscd_netgroup > NSS_NSCD_RETRY)
400     __nss_not_use_nscd_netgroup = 0;
401
402   if (!__nss_not_use_nscd_netgroup
403       && !__nss_database_custom[NSS_DBSIDX_netgroup])
404     {
405       int result = __nscd_innetgr (netgroup, host, user, domain);
406       if (result >= 0)
407         return result;
408     }
409 #endif
410
411   union
412   {
413     enum nss_status (*f) (const char *, struct __netgrent *);
414     void *ptr;
415   } setfct;
416   void (*endfct) (struct __netgrent *);
417   int (*getfct) (struct __netgrent *, char *, size_t, int *);
418   struct __netgrent entry;
419   int result = 0;
420   const char *current_group = netgroup;
421
422   memset (&entry, '\0', sizeof (entry));
423
424   /* Walk through the services until we found an answer or we shall
425      not work further.  We can do some optimization here.  Since all
426      services must provide the `setnetgrent' function we can do all
427      the work during one walk through the service list.  */
428   while (1)
429     {
430       int no_more = setup (&setfct.ptr, &entry.nip);
431       while (! no_more)
432         {
433           assert (entry.data == NULL);
434
435           /* Open netgroup.  */
436           enum nss_status status = DL_CALL_FCT (*setfct.f,
437                                                 (current_group, &entry));
438
439           if (status == NSS_STATUS_SUCCESS
440               && (getfct = __nss_lookup_function (entry.nip, "getnetgrent_r"))
441                  != NULL)
442             {
443               char buffer[1024];
444
445               while (DL_CALL_FCT (*getfct,
446                                   (&entry, buffer, sizeof buffer, &errno))
447                      == NSS_STATUS_SUCCESS)
448                 {
449                   if (entry.type == group_val)
450                     {
451                       /* Make sure we haven't seen the name before.  */
452                       struct name_list *namep;
453
454                       for (namep = entry.known_groups; namep != NULL;
455                            namep = namep->next)
456                         if (strcmp (entry.val.group, namep->name) == 0)
457                           break;
458                       if (namep == NULL)
459                         for (namep = entry.needed_groups; namep != NULL;
460                              namep = namep->next)
461                           if (strcmp (entry.val.group, namep->name) == 0)
462                             break;
463                       if (namep == NULL
464                           && strcmp (netgroup, entry.val.group) != 0)
465                         {
466                           size_t group_len = strlen (entry.val.group) + 1;
467                           namep =
468                             (struct name_list *) malloc (sizeof (*namep)
469                                                          + group_len);
470                           if (namep == NULL)
471                             {
472                               /* Out of memory, simply return.  */
473                               result = -1;
474                               break;
475                             }
476
477                           namep->next = entry.needed_groups;
478                           memcpy (namep->name, entry.val.group, group_len);
479                           entry.needed_groups = namep;
480                         }
481                     }
482                   else
483                     {
484                       if ((entry.val.triple.host == NULL || host == NULL
485                            || __strcasecmp (entry.val.triple.host, host) == 0)
486                           && (entry.val.triple.user == NULL || user == NULL
487                               || strcmp (entry.val.triple.user, user) == 0)
488                           && (entry.val.triple.domain == NULL || domain == NULL
489                               || __strcasecmp (entry.val.triple.domain,
490                                                domain) == 0))
491                         {
492                           result = 1;
493                           break;
494                         }
495                     }
496                 }
497
498               /* If we found one service which does know the given
499                  netgroup we don't try further.  */
500               status = NSS_STATUS_RETURN;
501             }
502
503           /* Free all resources of the service.  */
504           endfct = __nss_lookup_function (entry.nip, "endnetgrent");
505           if (endfct != NULL)
506             DL_CALL_FCT (*endfct, (&entry));
507
508           if (result != 0)
509             break;
510
511           /* Look for the next service.  */
512           no_more = __nss_next2 (&entry.nip, "setnetgrent", NULL,
513                                  &setfct.ptr, status, 0);
514         }
515
516       if (result == 0 && entry.needed_groups != NULL)
517         {
518           struct name_list *tmp = entry.needed_groups;
519           entry.needed_groups = tmp->next;
520           tmp->next = entry.known_groups;
521           entry.known_groups = tmp;
522           current_group = tmp->name;
523           continue;
524         }
525
526       /* No way out.  */
527       break;
528     }
529
530   /* Free the memory.  */
531   free_memory (&entry);
532
533   return result == 1;
534 }
535 libc_hidden_def (innetgr)