fdwalk should return 0 on an empty directory
[kopensolaris-gnu/glibc.git] / inet / getnetgrent_r.c
1 /* Copyright (C) 1996, 1997, 1998, 1999, 2002, 2004, 2005
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, write to the Free
17    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18    02111-1307 USA.  */
19
20 #include <assert.h>
21 #include <bits/libc-lock.h>
22 #include <errno.h>
23 #include <netdb.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include "netgroup.h"
27 #include "nsswitch.h"
28
29
30 /* Protect above variable against multiple uses at the same time.  */
31 __libc_lock_define_initialized (static, lock)
32
33 /* The whole information for the set/get/endnetgrent functions are
34    kept in this structure.  */
35 static struct __netgrent dataset;
36
37 /* The lookup function for the first entry of this service.  */
38 extern int __nss_netgroup_lookup (service_user **nipp, const char *name,
39                                   void **fctp) internal_function;
40
41 /* Set up NIP to run through the services.  Return nonzero if there are no
42    services (left).  */
43 static int
44 setup (void **fctp, service_user **nipp)
45 {
46   /* Remember the first service_entry, it's always the same.  */
47   static service_user *startp;
48   int no_more;
49
50   if (startp == NULL)
51     {
52       /* Executing this more than once at the same time must yield the
53          same result every time.  So we need no locking.  */
54       no_more = __nss_netgroup_lookup (nipp, "setnetgrent", fctp);
55       startp = no_more ? (service_user *) -1 : *nipp;
56     }
57   else if (startp == (service_user *) -1)
58     /* No services at all.  */
59     return 1;
60   else
61     {
62       /* Reset to the beginning of the service list.  */
63       *nipp = startp;
64       /* Look up the first function.  */
65       no_more = __nss_lookup (nipp, "setnetgrent", fctp);
66     }
67   return no_more;
68 }
69 \f
70 /* Free used memory.  */
71 static void
72 free_memory (struct __netgrent *data)
73 {
74   while (data->known_groups != NULL)
75     {
76       struct name_list *tmp = data->known_groups;
77       data->known_groups = data->known_groups->next;
78       free (tmp);
79     }
80
81   while (data->needed_groups != NULL)
82     {
83       struct name_list *tmp = data->needed_groups;
84       data->needed_groups = data->needed_groups->next;
85       free (tmp);
86     }
87 }
88 \f
89 static void
90 endnetgrent_hook (struct __netgrent *datap)
91 {
92   enum nss_status (*endfct) (struct __netgrent *);
93
94   if (datap->nip == NULL)
95     return;
96
97   endfct = __nss_lookup_function (datap->nip, "endnetgrent");
98   if (endfct != NULL)
99     (void) (*endfct) (datap);
100   datap->nip = NULL;
101 }
102
103 static int
104 internal_function
105 __internal_setnetgrent_reuse (const char *group, struct __netgrent *datap,
106                               int *errnop)
107 {
108   union
109   {
110     enum nss_status (*f) (const char *, struct __netgrent *);
111     void *ptr;
112   } fct;
113   enum nss_status status = NSS_STATUS_UNAVAIL;
114   struct name_list *new_elem;
115
116   /* Free data from previous service.  */
117   endnetgrent_hook (datap);
118
119   /* Cycle through all the services and run their setnetgrent functions.  */
120   int no_more = setup (&fct.ptr, &datap->nip);
121   while (! no_more)
122     {
123       assert (datap->data == NULL);
124
125       /* Ignore status, we force check in `__nss_next'.  */
126       status = (*fct.f) (group, datap);
127
128       service_user *old_nip = datap->nip;
129       no_more = __nss_next (&datap->nip, "setnetgrent", &fct.ptr, status, 0);
130
131       if (status == NSS_STATUS_SUCCESS && ! no_more)
132         {
133           enum nss_status (*endfct) (struct __netgrent *);
134
135           endfct = __nss_lookup_function (old_nip, "endnetgrent");
136           if (endfct != NULL)
137             (void) (*endfct) (datap);
138         }
139     }
140
141   /* Add the current group to the list of known groups.  */
142   size_t group_len = strlen (group) + 1;
143   new_elem = (struct name_list *) malloc (sizeof (struct name_list)
144                                           + group_len);
145   if (new_elem == NULL)
146     {
147       *errnop = errno;
148       status = NSS_STATUS_TRYAGAIN;
149     }
150   else
151     {
152       new_elem->next = datap->known_groups;
153       memcpy (new_elem->name, group, group_len);
154       datap->known_groups = new_elem;
155     }
156
157   return status == NSS_STATUS_SUCCESS;
158 }
159
160 int internal_setnetgrent (const char *group, struct __netgrent *datap);
161 libc_hidden_proto (internal_setnetgrent)
162
163 int
164 internal_setnetgrent (const char *group, struct __netgrent *datap)
165 {
166   /* Free list of all netgroup names from last run.  */
167   free_memory (datap);
168
169   return __internal_setnetgrent_reuse (group, datap, &errno);
170 }
171 libc_hidden_def (internal_setnetgrent)
172 strong_alias (internal_setnetgrent, __internal_setnetgrent)
173
174 int
175 setnetgrent (const char *group)
176 {
177   int result;
178
179   __libc_lock_lock (lock);
180
181   result = internal_setnetgrent (group, &dataset);
182
183   __libc_lock_unlock (lock);
184
185   return result;
186 }
187
188 void internal_endnetgrent (struct __netgrent *datap);
189 libc_hidden_proto (internal_endnetgrent)
190
191 void
192 internal_endnetgrent (struct __netgrent *datap)
193 {
194   endnetgrent_hook (datap);
195   /* Now free list of all netgroup names from last run.  */
196   free_memory (datap);
197 }
198 libc_hidden_def (internal_endnetgrent)
199 strong_alias (internal_endnetgrent, __internal_endnetgrent)
200
201
202 void
203 endnetgrent (void)
204 {
205   __libc_lock_lock (lock);
206
207   internal_endnetgrent (&dataset);
208
209   __libc_lock_unlock (lock);
210 }
211
212
213 int internal_getnetgrent_r (char **hostp, char **userp, char **domainp,
214                             struct __netgrent *datap,
215                             char *buffer, size_t buflen, int *errnop);
216 libc_hidden_proto (internal_getnetgrent_r)
217
218 int
219 internal_getnetgrent_r (char **hostp, char **userp, char **domainp,
220                           struct __netgrent *datap,
221                           char *buffer, size_t buflen, int *errnop)
222 {
223   enum nss_status (*fct) (struct __netgrent *, char *, size_t, int *);
224
225   /* Initialize status to return if no more functions are found.  */
226   enum nss_status status = NSS_STATUS_NOTFOUND;
227
228   /* Run through available functions, starting with the same function last
229      run.  We will repeat each function as long as it succeeds, and then go
230      on to the next service action.  */
231   int no_more = (datap->nip == NULL
232                  || (fct = __nss_lookup_function (datap->nip, "getnetgrent_r"))
233                     == NULL);
234   while (! no_more)
235     {
236       status = (*fct) (datap, buffer, buflen, &errno);
237
238       if (status == NSS_STATUS_RETURN)
239         {
240           /* This was the last one for this group.  Look at next group
241              if available.  */
242           int found = 0;
243           while (datap->needed_groups != NULL && ! found)
244             {
245               struct name_list *tmp = datap->needed_groups;
246               datap->needed_groups = datap->needed_groups->next;
247               tmp->next = datap->known_groups;
248               datap->known_groups = tmp;
249
250               found = __internal_setnetgrent_reuse (datap->known_groups->name,
251                                                     datap, errnop);
252             }
253
254           if (found && datap->nip != NULL)
255             {
256               fct = __nss_lookup_function (datap->nip, "getnetgrent_r");
257               if (fct != NULL)
258                 continue;
259             }
260         }
261       else if (status == NSS_STATUS_SUCCESS && datap->type == group_val)
262         {
263           /* The last entry was a name of another netgroup.  */
264           struct name_list *namep;
265
266           /* Ignore if we've seen the name before.  */
267           for (namep = datap->known_groups; namep != NULL;
268                namep = namep->next)
269             if (strcmp (datap->val.group, namep->name) == 0)
270               break;
271           if (namep != NULL)
272             /* Really ignore.  */
273             continue;
274
275           size_t group_len = strlen (datap->val.group) + 1;
276           namep = (struct name_list *) malloc (sizeof (struct name_list)
277                                                + group_len);
278           if (namep == NULL)
279             /* We are out of memory.  */
280             status = NSS_STATUS_RETURN;
281           else
282             {
283               namep->next = datap->needed_groups;
284               memcpy (namep->name, datap->val.group, group_len);
285               datap->needed_groups = namep;
286               /* And get the next entry.  */
287               continue;
288             }
289         }
290
291       break;
292     }
293
294   if (status == NSS_STATUS_SUCCESS)
295     {
296       *hostp = (char *) datap->val.triple.host;
297       *userp = (char *) datap->val.triple.user;
298       *domainp = (char *) datap->val.triple.domain;
299     }
300
301   return status == NSS_STATUS_SUCCESS ? 1 : 0;
302 }
303 libc_hidden_def (internal_getnetgrent_r)
304 strong_alias (internal_getnetgrent_r, __internal_getnetgrent_r)
305
306 /* The real entry point.  */
307 int
308 __getnetgrent_r (char **hostp, char **userp, char **domainp,
309                  char *buffer, size_t buflen)
310 {
311   enum nss_status status;
312
313   __libc_lock_lock (lock);
314
315   status = internal_getnetgrent_r (hostp, userp, domainp, &dataset,
316                                    buffer, buflen, &errno);
317
318   __libc_lock_unlock (lock);
319
320   return status;
321 }
322 weak_alias (__getnetgrent_r, getnetgrent_r)
323 \f
324 /* Test whether given (host,user,domain) triple is in NETGROUP.  */
325 int
326 innetgr (const char *netgroup, const char *host, const char *user,
327          const char *domain)
328 {
329   union
330   {
331     int (*f) (const char *, struct __netgrent *);
332     void *ptr;
333   } setfct;
334   void (*endfct) (struct __netgrent *);
335   int (*getfct) (struct __netgrent *, char *, size_t, int *);
336   struct __netgrent entry;
337   int result = 0;
338   const char *current_group = netgroup;
339   int real_entry = 0;
340
341   memset (&entry, '\0', sizeof (entry));
342
343   /* Walk through the services until we found an answer or we shall
344      not work further.  We can do some optimization here.  Since all
345      services must provide the `setnetgrent' function we can do all
346      the work during one walk through the service list.  */
347   while (1)
348     {
349       int no_more = setup (&setfct.ptr, &entry.nip);
350       while (! no_more)
351         {
352           assert (entry.data == NULL);
353
354           /* Open netgroup.  */
355           enum nss_status status = (*setfct.f) (current_group, &entry);
356
357           if (status == NSS_STATUS_SUCCESS
358               && (getfct = __nss_lookup_function (entry.nip, "getnetgrent_r"))
359                  != NULL)
360             {
361               char buffer[1024];
362
363               while ((*getfct) (&entry, buffer, sizeof buffer, &errno)
364                      == NSS_STATUS_SUCCESS)
365                 {
366                   if (entry.type == group_val)
367                     {
368                       /* Make sure we haven't seen the name before.  */
369                       struct name_list *namep;
370
371                       for (namep = entry.known_groups; namep != NULL;
372                            namep = namep->next)
373                         if (strcmp (entry.val.group, namep->name) == 0)
374                           break;
375                       if (namep == NULL
376                           && strcmp (netgroup, entry.val.group) != 0)
377                         {
378                           size_t group_len = strlen (entry.val.group) + 1;
379                           namep =
380                             (struct name_list *) malloc (sizeof (*namep)
381                                                          + group_len);
382                           if (namep == NULL)
383                             {
384                               /* Out of memory, simply return.  */
385                               result = -1;
386                               break;
387                             }
388
389                           namep->next = entry.needed_groups;
390                           memcpy (namep->name, entry.val.group, group_len);
391                           entry.needed_groups = namep;
392                         }
393                     }
394                   else
395                     {
396                       real_entry = 1;
397
398                       if ((entry.val.triple.host == NULL || host == NULL
399                            || __strcasecmp (entry.val.triple.host, host) == 0)
400                           && (entry.val.triple.user == NULL || user == NULL
401                               || strcmp (entry.val.triple.user, user) == 0)
402                           && (entry.val.triple.domain == NULL || domain == NULL
403                               || __strcasecmp (entry.val.triple.domain,
404                                                domain) == 0))
405                         {
406                           result = 1;
407                           break;
408                         }
409                     }
410                 }
411
412               /* If we found one service which does know the given
413                  netgroup we don't try further.  */
414               status = NSS_STATUS_RETURN;
415             }
416
417           /* Free all resources of the service.  */
418           endfct = __nss_lookup_function (entry.nip, "endnetgrent");
419           if (endfct != NULL)
420             (*endfct) (&entry);
421
422           if (result != 0)
423             break;
424
425           /* Look for the next service.  */
426           no_more = __nss_next (&entry.nip, "setnetgrent",
427                                 &setfct.ptr, status, 0);
428         }
429
430       if (result == 0 && entry.needed_groups != NULL)
431         {
432           struct name_list *tmp = entry.needed_groups;
433           entry.needed_groups = tmp->next;
434           tmp->next = entry.known_groups;
435           entry.known_groups = tmp;
436           current_group = entry.known_groups->name;
437           continue;
438         }
439
440       /* No way out.  */
441       break;
442     }
443
444   /* Free the memory.  */
445   free_memory (&entry);
446
447   return result == 1;
448 }
449 libc_hidden_def (innetgr)