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