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