Update copyright notices with scripts/update-copyrights
[jlayton/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 enum nss_status
239 nscd_getnetgrent (struct __netgrent *datap, char *buffer, size_t buflen,
240                   int *errnop)
241 {
242   if (datap->cursor >= datap->data + datap->data_size)
243     return NSS_STATUS_UNAVAIL;
244
245   datap->type = triple_val;
246   datap->val.triple.host = datap->cursor;
247   datap->cursor = (char *) __rawmemchr (datap->cursor, '\0') + 1;
248   datap->val.triple.user = datap->cursor;
249   datap->cursor = (char *) __rawmemchr (datap->cursor, '\0') + 1;
250   datap->val.triple.domain = datap->cursor;
251   datap->cursor = (char *) __rawmemchr (datap->cursor, '\0') + 1;
252
253   return NSS_STATUS_SUCCESS;
254 }
255 #endif
256
257 int
258 internal_function
259 __internal_getnetgrent_r (char **hostp, char **userp, char **domainp,
260                           struct __netgrent *datap,
261                           char *buffer, size_t buflen, int *errnop)
262 {
263   enum nss_status (*fct) (struct __netgrent *, char *, size_t, int *);
264
265   /* Initialize status to return if no more functions are found.  */
266   enum nss_status status = NSS_STATUS_NOTFOUND;
267
268   /* Run through available functions, starting with the same function last
269      run.  We will repeat each function as long as it succeeds, and then go
270      on to the next service action.  */
271   int no_more = datap->nip == NULL;
272   if (! no_more)
273     {
274 #ifdef USE_NSCD
275       /* This bogus function pointer is a special marker left by
276          __nscd_setnetgrent to tell us to use the data it left
277          before considering any modules.  */
278       if (datap->nip == (service_user *) -1l)
279         fct = nscd_getnetgrent;
280       else
281 #endif
282         {
283           fct = __nss_lookup_function (datap->nip, "getnetgrent_r");
284           no_more = fct == NULL;
285         }
286     }
287
288   while (! no_more)
289     {
290       status = DL_CALL_FCT (*fct, (datap, buffer, buflen, &errno));
291
292       if (status == NSS_STATUS_RETURN)
293         {
294           /* This was the last one for this group.  Look at next group
295              if available.  */
296           int found = 0;
297           while (datap->needed_groups != NULL && ! found)
298             {
299               struct name_list *tmp = datap->needed_groups;
300               datap->needed_groups = datap->needed_groups->next;
301               tmp->next = datap->known_groups;
302               datap->known_groups = tmp;
303
304               found = __internal_setnetgrent_reuse (datap->known_groups->name,
305                                                     datap, errnop);
306             }
307
308           if (found && datap->nip != NULL)
309             {
310               fct = __nss_lookup_function (datap->nip, "getnetgrent_r");
311               if (fct != NULL)
312                 continue;
313             }
314         }
315       else if (status == NSS_STATUS_SUCCESS && datap->type == group_val)
316         {
317           /* The last entry was a name of another netgroup.  */
318           struct name_list *namep;
319
320           /* Ignore if we've seen the name before.  */
321           for (namep = datap->known_groups; namep != NULL;
322                namep = namep->next)
323             if (strcmp (datap->val.group, namep->name) == 0)
324               break;
325           if (namep == NULL)
326             for (namep = datap->needed_groups; namep != NULL;
327                  namep = namep->next)
328               if (strcmp (datap->val.group, namep->name) == 0)
329                 break;
330           if (namep != NULL)
331             /* Really ignore.  */
332             continue;
333
334           size_t group_len = strlen (datap->val.group) + 1;
335           namep = (struct name_list *) malloc (sizeof (struct name_list)
336                                                + group_len);
337           if (namep == NULL)
338             /* We are out of memory.  */
339             status = NSS_STATUS_RETURN;
340           else
341             {
342               namep->next = datap->needed_groups;
343               memcpy (namep->name, datap->val.group, group_len);
344               datap->needed_groups = namep;
345               /* And get the next entry.  */
346               continue;
347             }
348         }
349
350       break;
351     }
352
353   if (status == NSS_STATUS_SUCCESS)
354     {
355       *hostp = (char *) datap->val.triple.host;
356       *userp = (char *) datap->val.triple.user;
357       *domainp = (char *) datap->val.triple.domain;
358     }
359
360   return status == NSS_STATUS_SUCCESS ? 1 : 0;
361 }
362 libc_hidden_def (__internal_getnetgrent_r)
363
364 /* The real entry point.  */
365 int
366 __getnetgrent_r (char **hostp, char **userp, char **domainp,
367                  char *buffer, size_t buflen)
368 {
369   enum nss_status status;
370
371   __libc_lock_lock (lock);
372
373   status = __internal_getnetgrent_r (hostp, userp, domainp, &dataset,
374                                      buffer, buflen, &errno);
375
376   __libc_lock_unlock (lock);
377
378   return status;
379 }
380 weak_alias (__getnetgrent_r, getnetgrent_r)
381 \f
382 /* Test whether given (host,user,domain) triple is in NETGROUP.  */
383 int
384 innetgr (const char *netgroup, const char *host, const char *user,
385          const char *domain)
386 {
387 #ifdef USE_NSCD
388   if (__nss_not_use_nscd_netgroup > 0
389       && ++__nss_not_use_nscd_netgroup > NSS_NSCD_RETRY)
390     __nss_not_use_nscd_netgroup = 0;
391
392   if (!__nss_not_use_nscd_netgroup
393       && !__nss_database_custom[NSS_DBSIDX_netgroup])
394     {
395       int result = __nscd_innetgr (netgroup, host, user, domain);
396       if (result >= 0)
397         return result;
398     }
399 #endif
400
401   union
402   {
403     enum nss_status (*f) (const char *, struct __netgrent *);
404     void *ptr;
405   } setfct;
406   void (*endfct) (struct __netgrent *);
407   int (*getfct) (struct __netgrent *, char *, size_t, int *);
408   struct __netgrent entry;
409   int result = 0;
410   const char *current_group = netgroup;
411
412   memset (&entry, '\0', sizeof (entry));
413
414   /* Walk through the services until we found an answer or we shall
415      not work further.  We can do some optimization here.  Since all
416      services must provide the `setnetgrent' function we can do all
417      the work during one walk through the service list.  */
418   while (1)
419     {
420       int no_more = setup (&setfct.ptr, &entry.nip);
421       while (! no_more)
422         {
423           assert (entry.data == NULL);
424
425           /* Open netgroup.  */
426           enum nss_status status = DL_CALL_FCT (*setfct.f,
427                                                 (current_group, &entry));
428
429           if (status == NSS_STATUS_SUCCESS
430               && (getfct = __nss_lookup_function (entry.nip, "getnetgrent_r"))
431                  != NULL)
432             {
433               char buffer[1024];
434
435               while (DL_CALL_FCT (*getfct,
436                                   (&entry, buffer, sizeof buffer, &errno))
437                      == NSS_STATUS_SUCCESS)
438                 {
439                   if (entry.type == group_val)
440                     {
441                       /* Make sure we haven't seen the name before.  */
442                       struct name_list *namep;
443
444                       for (namep = entry.known_groups; namep != NULL;
445                            namep = namep->next)
446                         if (strcmp (entry.val.group, namep->name) == 0)
447                           break;
448                       if (namep == NULL)
449                         for (namep = entry.needed_groups; namep != NULL;
450                              namep = namep->next)
451                           if (strcmp (entry.val.group, namep->name) == 0)
452                             break;
453                       if (namep == NULL
454                           && strcmp (netgroup, entry.val.group) != 0)
455                         {
456                           size_t group_len = strlen (entry.val.group) + 1;
457                           namep =
458                             (struct name_list *) malloc (sizeof (*namep)
459                                                          + group_len);
460                           if (namep == NULL)
461                             {
462                               /* Out of memory, simply return.  */
463                               result = -1;
464                               break;
465                             }
466
467                           namep->next = entry.needed_groups;
468                           memcpy (namep->name, entry.val.group, group_len);
469                           entry.needed_groups = namep;
470                         }
471                     }
472                   else
473                     {
474                       if ((entry.val.triple.host == NULL || host == NULL
475                            || __strcasecmp (entry.val.triple.host, host) == 0)
476                           && (entry.val.triple.user == NULL || user == NULL
477                               || strcmp (entry.val.triple.user, user) == 0)
478                           && (entry.val.triple.domain == NULL || domain == NULL
479                               || __strcasecmp (entry.val.triple.domain,
480                                                domain) == 0))
481                         {
482                           result = 1;
483                           break;
484                         }
485                     }
486                 }
487
488               /* If we found one service which does know the given
489                  netgroup we don't try further.  */
490               status = NSS_STATUS_RETURN;
491             }
492
493           /* Free all resources of the service.  */
494           endfct = __nss_lookup_function (entry.nip, "endnetgrent");
495           if (endfct != NULL)
496             DL_CALL_FCT (*endfct, (&entry));
497
498           if (result != 0)
499             break;
500
501           /* Look for the next service.  */
502           no_more = __nss_next2 (&entry.nip, "setnetgrent", NULL,
503                                  &setfct.ptr, status, 0);
504         }
505
506       if (result == 0 && entry.needed_groups != NULL)
507         {
508           struct name_list *tmp = entry.needed_groups;
509           entry.needed_groups = tmp->next;
510           tmp->next = entry.known_groups;
511           entry.known_groups = tmp;
512           current_group = tmp->name;
513           continue;
514         }
515
516       /* No way out.  */
517       break;
518     }
519
520   /* Free the memory.  */
521   free_memory (&entry);
522
523   return result == 1;
524 }
525 libc_hidden_def (innetgr)