ff19912a0acceb6cbebe8a8ae2b200fa0d21b57e
[platform/upstream/glibc.git] / nis / nss_compat / compat-grp.c
1 /* Copyright (C) 1996,1997,1998,1999,2001,2002, 2003 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3    Contributed by Thorsten Kukuk <kukuk@suse.de>, 1996.
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 <ctype.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <grp.h>
24 #include <nss.h>
25 #include <nsswitch.h>
26 #include <stdio_lock.h>
27 #include <string.h>
28 #include <rpc/types.h>
29 #include <bits/libc-lock.h>
30
31 static service_user *ni;
32 static enum nss_status (*nss_setgrent) (int stayopen);
33 static enum nss_status (*nss_getgrnam_r) (const char *name,
34                                           struct group * grp, char *buffer,
35                                           size_t buflen, int *errnop);
36 static enum nss_status (*nss_getgrgid_r) (gid_t gid, struct group * grp,
37                                           char *buffer, size_t buflen,
38                                           int *errnop);
39 static enum nss_status (*nss_getgrent_r) (struct group * grp, char *buffer,
40                                           size_t buflen, int *errnop);
41 static enum nss_status (*nss_endgrent) (void);
42
43 /* Get the declaration of the parser function.  */
44 #define ENTNAME grent
45 #define STRUCTURE group
46 #define EXTERN_PARSER
47 #include <nss/nss_files/files-parse.c>
48
49 /* Structure for remembering -group members ... */
50 #define BLACKLIST_INITIAL_SIZE 512
51 #define BLACKLIST_INCREMENT 256
52 struct blacklist_t
53 {
54   char *data;
55   int current;
56   int size;
57 };
58
59 struct ent_t
60 {
61   bool_t files;
62   FILE *stream;
63   struct blacklist_t blacklist;
64 };
65 typedef struct ent_t ent_t;
66
67 static ent_t ext_ent = {TRUE, NULL, {NULL, 0, 0}};
68
69 /* Protect global state against multiple changers.  */
70 __libc_lock_define_initialized (static, lock)
71
72 /* Prototypes for local functions.  */
73 static void blacklist_store_name (const char *, ent_t *);
74 static int in_blacklist (const char *, int, ent_t *);
75
76 /* Initialize the NSS interface/functions. The calling function must
77    hold the lock.  */
78 static void
79 init_nss_interface (void)
80 {
81   if (__nss_database_lookup ("group_compat", NULL, "nis", &ni) >= 0)
82     {
83       nss_setgrent = __nss_lookup_function (ni, "setgrent");
84       nss_getgrnam_r = __nss_lookup_function (ni, "getgrnam_r");
85       nss_getgrgid_r = __nss_lookup_function (ni, "getgrgid_r");
86       nss_getgrent_r = __nss_lookup_function (ni, "getgrent_r");
87       nss_endgrent = __nss_lookup_function (ni, "endgrent");
88     }
89 }
90
91 static enum nss_status
92 internal_setgrent (ent_t *ent, int stayopen)
93 {
94   enum nss_status status = NSS_STATUS_SUCCESS;
95
96   ent->files = TRUE;
97
98   if (ent->blacklist.data != NULL)
99     {
100       ent->blacklist.current = 1;
101       ent->blacklist.data[0] = '|';
102       ent->blacklist.data[1] = '\0';
103     }
104   else
105     ent->blacklist.current = 0;
106
107   if (ent->stream == NULL)
108     {
109       ent->stream = fopen ("/etc/group", "rm");
110
111       if (ent->stream == NULL)
112         status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
113       else
114         {
115           /* We have to make sure the file is  `closed on exec'.  */
116           int result, flags;
117
118           result = flags = fcntl (fileno_unlocked (ent->stream), F_GETFD, 0);
119           if (result >= 0)
120             {
121               flags |= FD_CLOEXEC;
122               result = fcntl (fileno_unlocked (ent->stream), F_SETFD, flags);
123             }
124           if (result < 0)
125             {
126               /* Something went wrong.  Close the stream and return a
127                  failure.  */
128               fclose (ent->stream);
129               ent->stream = NULL;
130               status = NSS_STATUS_UNAVAIL;
131             }
132           else
133             /* We take care of locking ourself.  */
134             __fsetlocking (ent->stream, FSETLOCKING_BYCALLER);
135         }
136     }
137   else
138     rewind (ent->stream);
139
140   if (status == NSS_STATUS_SUCCESS && nss_setgrent)
141     return nss_setgrent (stayopen);
142
143   return status;
144 }
145
146
147 enum nss_status
148 _nss_compat_setgrent (int stayopen)
149 {
150   enum nss_status result;
151
152   __libc_lock_lock (lock);
153
154   if (ni == NULL)
155     init_nss_interface ();
156
157   result = internal_setgrent (&ext_ent, stayopen);
158
159   __libc_lock_unlock (lock);
160
161   return result;
162 }
163
164
165 static enum nss_status
166 internal_endgrent (ent_t *ent)
167 {
168   if (nss_endgrent)
169     nss_endgrent ();
170
171   if (ent->stream != NULL)
172     {
173       fclose (ent->stream);
174       ent->stream = NULL;
175     }
176
177   if (ent->blacklist.data != NULL)
178     {
179       ent->blacklist.current = 1;
180       ent->blacklist.data[0] = '|';
181       ent->blacklist.data[1] = '\0';
182     }
183   else
184     ent->blacklist.current = 0;
185
186   return NSS_STATUS_SUCCESS;
187 }
188
189 enum nss_status
190 _nss_compat_endgrent (void)
191 {
192   enum nss_status result;
193
194   __libc_lock_lock (lock);
195
196   result = internal_endgrent (&ext_ent);
197
198   __libc_lock_unlock (lock);
199
200   return result;
201 }
202
203 /* get the next group from NSS  (+ entry) */
204 static enum nss_status
205 getgrent_next_nss (struct group *result, ent_t *ent, char *buffer,
206                    size_t buflen, int *errnop)
207 {
208   if (!nss_getgrent_r)
209     return NSS_STATUS_UNAVAIL;
210
211   do
212     {
213       enum nss_status status;
214
215       if ((status = nss_getgrent_r (result, buffer, buflen, errnop)) !=
216           NSS_STATUS_SUCCESS)
217         return status;
218     }
219   while (in_blacklist (result->gr_name, strlen (result->gr_name), ent));
220
221   return NSS_STATUS_SUCCESS;
222 }
223
224 /* This function handle the +group entrys in /etc/group */
225 static enum nss_status
226 getgrnam_plusgroup (const char *name, struct group *result, ent_t *ent,
227                     char *buffer, size_t buflen, int *errnop)
228 {
229   if (!nss_getgrnam_r)
230     return NSS_STATUS_UNAVAIL;
231
232   if (nss_getgrnam_r (name, result, buffer, buflen, errnop) !=
233       NSS_STATUS_SUCCESS)
234     return NSS_STATUS_NOTFOUND;
235
236   if (in_blacklist (result->gr_name, strlen (result->gr_name), ent))
237     return NSS_STATUS_NOTFOUND;
238
239   /* We found the entry.  */
240   return NSS_STATUS_SUCCESS;
241 }
242
243 static enum nss_status
244 getgrent_next_file (struct group *result, ent_t *ent,
245                     char *buffer, size_t buflen, int *errnop)
246 {
247   struct parser_data *data = (void *) buffer;
248   while (1)
249     {
250       fpos_t pos;
251       int parse_res = 0;
252       char *p;
253
254       do
255         {
256           fgetpos (ent->stream, &pos);
257           buffer[buflen - 1] = '\xff';
258           p = fgets_unlocked (buffer, buflen, ent->stream);
259           if (p == NULL && feof_unlocked (ent->stream))
260             return NSS_STATUS_NOTFOUND;
261
262           if (p == NULL || buffer[buflen - 1] != '\xff')
263             {
264               fsetpos (ent->stream, &pos);
265               *errnop = ERANGE;
266               return NSS_STATUS_TRYAGAIN;
267             }
268
269           /* Terminate the line for any case.  */
270           buffer[buflen - 1] = '\0';
271
272           /* Skip leading blanks.  */
273           while (isspace (*p))
274             ++p;
275         }
276       while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */
277              /* Parse the line.  If it is invalid, loop to
278                 get the next line of the file to parse.  */
279              !(parse_res = _nss_files_parse_grent (p, result, data, buflen,
280                                                    errnop)));
281
282       if (parse_res == -1)
283         {
284           /* The parser ran out of space.  */
285           fsetpos (ent->stream, &pos);
286           *errnop = ERANGE;
287           return NSS_STATUS_TRYAGAIN;
288         }
289
290       if (result->gr_name[0] != '+' && result->gr_name[0] != '-')
291         /* This is a real entry.  */
292         break;
293
294       /* -group */
295       if (result->gr_name[0] == '-' && result->gr_name[1] != '\0'
296           && result->gr_name[1] != '@')
297         {
298           blacklist_store_name (&result->gr_name[1], ent);
299           continue;
300         }
301
302       /* +group */
303       if (result->gr_name[0] == '+' && result->gr_name[1] != '\0'
304           && result->gr_name[1] != '@')
305         {
306           size_t len = strlen (result->gr_name);
307           char buf[len];
308           enum nss_status status;
309
310           /* Store the group in the blacklist for the "+" at the end of
311              /etc/group */
312           memcpy (buf, &result->gr_name[1], len);
313           status = getgrnam_plusgroup (&result->gr_name[1], result, ent,
314                                        buffer, buflen, errnop);
315           blacklist_store_name (buf, ent);
316           if (status == NSS_STATUS_SUCCESS)     /* We found the entry. */
317             break;
318           else if (status == NSS_STATUS_RETURN  /* We couldn't parse the entry */
319                    || status == NSS_STATUS_NOTFOUND)    /* No group in NIS */
320             continue;
321           else
322             {
323               if (status == NSS_STATUS_TRYAGAIN)
324                 {
325                   /* The parser ran out of space.  */
326                   fsetpos (ent->stream, &pos);
327                   *errnop = ERANGE;
328                 }
329               return status;
330             }
331         }
332
333       /* +:... */
334       if (result->gr_name[0] == '+' && result->gr_name[1] == '\0')
335         {
336           ent->files = FALSE;
337
338           return getgrent_next_nss (result, ent, buffer, buflen, errnop);
339         }
340     }
341
342   return NSS_STATUS_SUCCESS;
343 }
344
345
346 enum nss_status
347 _nss_compat_getgrent_r (struct group *grp, char *buffer, size_t buflen,
348                         int *errnop)
349 {
350   enum nss_status result = NSS_STATUS_SUCCESS;
351
352   __libc_lock_lock (lock);
353
354   /* Be prepared that the setgrent function was not called before.  */
355   if (ni == NULL)
356     init_nss_interface ();
357
358   if (ext_ent.stream == NULL)
359     result = internal_setgrent (&ext_ent, 1);
360
361   if (result == NSS_STATUS_SUCCESS)
362     {
363       if (ext_ent.files)
364         result = getgrent_next_file (grp, &ext_ent, buffer, buflen, errnop);
365       else
366         result = getgrent_next_nss (grp, &ext_ent, buffer, buflen, errnop);
367     }
368   __libc_lock_unlock (lock);
369
370   return result;
371 }
372
373 /* Searches in /etc/group and the NIS/NIS+ map for a special group */
374 static enum nss_status
375 internal_getgrnam_r (const char *name, struct group *result, ent_t *ent,
376                      char *buffer, size_t buflen, int *errnop)
377 {
378   struct parser_data *data = (void *) buffer;
379   while (1)
380     {
381       fpos_t pos;
382       int parse_res = 0;
383       char *p;
384
385       do
386         {
387           fgetpos (ent->stream, &pos);
388           buffer[buflen - 1] = '\xff';
389           p = fgets_unlocked (buffer, buflen, ent->stream);
390           if (p == NULL && feof_unlocked (ent->stream))
391             return NSS_STATUS_NOTFOUND;
392
393           if (p == NULL || buffer[buflen - 1] != '\xff')
394             {
395               fsetpos (ent->stream, &pos);
396               *errnop = ERANGE;
397               return NSS_STATUS_TRYAGAIN;
398             }
399
400           /* Terminate the line for any case.  */
401           buffer[buflen - 1] = '\0';
402
403           /* Skip leading blanks.  */
404           while (isspace (*p))
405             ++p;
406         }
407       while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */
408              /* Parse the line.  If it is invalid, loop to
409                 get the next line of the file to parse.  */
410              !(parse_res = _nss_files_parse_grent (p, result, data, buflen,
411                                                    errnop)));
412
413       if (parse_res == -1)
414         {
415           /* The parser ran out of space.  */
416           fsetpos (ent->stream, &pos);
417           *errnop = ERANGE;
418           return NSS_STATUS_TRYAGAIN;
419         }
420
421       /* This is a real entry.  */
422       if (result->gr_name[0] != '+' && result->gr_name[0] != '-')
423         {
424           if (strcmp (result->gr_name, name) == 0)
425             return NSS_STATUS_SUCCESS;
426           else
427             continue;
428         }
429
430       /* -group */
431       if (result->gr_name[0] == '-' && result->gr_name[1] != '\0')
432         {
433           if (strcmp (&result->gr_name[1], name) == 0)
434             return NSS_STATUS_NOTFOUND;
435           else
436             continue;
437         }
438
439       /* +group */
440       if (result->gr_name[0] == '+' && result->gr_name[1] != '\0')
441         {
442           if (strcmp (name, &result->gr_name[1]) == 0)
443             {
444               enum nss_status status;
445
446               status = getgrnam_plusgroup (name, result, ent,
447                                            buffer, buflen, errnop);
448               if (status == NSS_STATUS_RETURN)
449                 /* We couldn't parse the entry */
450                 continue;
451               else
452                 return status;
453             }
454         }
455       /* +:... */
456       if (result->gr_name[0] == '+' && result->gr_name[1] == '\0')
457         {
458           enum nss_status status;
459
460           status = getgrnam_plusgroup (name, result, ent,
461                                        buffer, buflen, errnop);
462           if (status == NSS_STATUS_RETURN)
463             /* We couldn't parse the entry */
464             continue;
465           else
466             return status;
467         }
468     }
469
470   return NSS_STATUS_SUCCESS;
471 }
472
473 enum nss_status
474 _nss_compat_getgrnam_r (const char *name, struct group *grp,
475                         char *buffer, size_t buflen, int *errnop)
476 {
477   ent_t ent = {TRUE, NULL, {NULL, 0, 0}};
478   enum nss_status result;
479
480   if (name[0] == '-' || name[0] == '+')
481     return NSS_STATUS_NOTFOUND;
482
483   __libc_lock_lock (lock);
484
485   if (ni == NULL)
486     init_nss_interface ();
487
488   __libc_lock_unlock (lock);
489
490   result = internal_setgrent (&ent, 0);
491
492   if (result == NSS_STATUS_SUCCESS)
493     result = internal_getgrnam_r (name, grp, &ent, buffer, buflen, errnop);
494
495   internal_endgrent (&ent);
496
497   return result;
498 }
499
500 /* Searches in /etc/group and the NIS/NIS+ map for a special group id */
501 static enum nss_status
502 internal_getgrgid_r (gid_t gid, struct group *result, ent_t *ent,
503                      char *buffer, size_t buflen, int *errnop)
504 {
505   struct parser_data *data = (void *) buffer;
506   while (1)
507     {
508       fpos_t pos;
509       int parse_res = 0;
510       char *p;
511
512       do
513         {
514           fgetpos (ent->stream, &pos);
515           buffer[buflen - 1] = '\xff';
516           p = fgets_unlocked (buffer, buflen, ent->stream);
517           if (p == NULL && feof_unlocked (ent->stream))
518             return NSS_STATUS_NOTFOUND;
519
520           if (p == NULL || buffer[buflen - 1] != '\xff')
521             {
522               fsetpos (ent->stream, &pos);
523               *errnop = ERANGE;
524               return NSS_STATUS_TRYAGAIN;
525             }
526
527           /* Terminate the line for any case.  */
528           buffer[buflen - 1] = '\0';
529
530           /* Skip leading blanks.  */
531           while (isspace (*p))
532             ++p;
533         }
534       while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */
535              /* Parse the line.  If it is invalid, loop to
536                 get the next line of the file to parse.  */
537              !(parse_res = _nss_files_parse_grent (p, result, data, buflen,
538                                                    errnop)));
539
540       if (parse_res == -1)
541         {
542           /* The parser ran out of space.  */
543           fsetpos (ent->stream, &pos);
544           *errnop = ERANGE;
545           return NSS_STATUS_TRYAGAIN;
546         }
547
548       /* This is a real entry.  */
549       if (result->gr_name[0] != '+' && result->gr_name[0] != '-')
550         {
551           if (result->gr_gid == gid)
552             return NSS_STATUS_SUCCESS;
553           else
554             continue;
555         }
556
557       /* -group */
558       if (result->gr_name[0] == '-' && result->gr_name[1] != '\0')
559         {
560           blacklist_store_name (&result->gr_name[1], ent);
561           continue;
562         }
563
564       /* +group */
565       if (result->gr_name[0] == '+' && result->gr_name[1] != '\0')
566         {
567           enum nss_status status;
568
569           /* Store the group in the blacklist for the "+" at the end of
570              /etc/group */
571           blacklist_store_name (&result->gr_name[1], ent);
572           status = getgrnam_plusgroup (&result->gr_name[1], result, ent,
573                                        buffer, buflen, errnop);
574           if (status == NSS_STATUS_SUCCESS && result->gr_gid == gid)
575             break;
576           else
577             continue;
578         }
579       /* +:... */
580       if (result->gr_name[0] == '+' && result->gr_name[1] == '\0')
581         {
582           enum nss_status status;
583
584           status = nss_getgrgid_r (gid, result, buffer, buflen, errnop);
585           if (status == NSS_STATUS_RETURN) /* We couldn't parse the entry */
586             return NSS_STATUS_NOTFOUND;
587           else
588             return status;
589         }
590     }
591
592   return NSS_STATUS_SUCCESS;
593 }
594
595 enum nss_status
596 _nss_compat_getgrgid_r (gid_t gid, struct group *grp,
597                         char *buffer, size_t buflen, int *errnop)
598 {
599   ent_t ent = {TRUE, NULL, {NULL, 0, 0}};
600   enum nss_status result;
601
602   __libc_lock_lock (lock);
603
604   if (ni == NULL)
605     init_nss_interface ();
606
607   __libc_lock_unlock (lock);
608
609   result = internal_setgrent (&ent, 0);
610
611   if (result == NSS_STATUS_SUCCESS)
612     result = internal_getgrgid_r (gid, grp, &ent, buffer, buflen, errnop);
613
614   internal_endgrent (&ent);
615
616   return result;
617 }
618
619
620 /* Support routines for remembering -@netgroup and -user entries.
621    The names are stored in a single string with `|' as separator. */
622 static void
623 blacklist_store_name (const char *name, ent_t *ent)
624 {
625   int namelen = strlen (name);
626   char *tmp;
627
628   /* first call, setup cache */
629   if (ent->blacklist.size == 0)
630     {
631       ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
632       ent->blacklist.data = malloc (ent->blacklist.size);
633       if (ent->blacklist.data == NULL)
634         return;
635       ent->blacklist.data[0] = '|';
636       ent->blacklist.data[1] = '\0';
637       ent->blacklist.current = 1;
638     }
639   else
640     {
641       if (in_blacklist (name, namelen, ent))
642         return;                 /* no duplicates */
643
644       if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
645         {
646           ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
647           tmp = realloc (ent->blacklist.data, ent->blacklist.size);
648           if (tmp == NULL)
649             {
650               free (ent->blacklist.data);
651               ent->blacklist.size = 0;
652               return;
653             }
654           ent->blacklist.data = tmp;
655         }
656     }
657
658   tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
659   *tmp++ = '|';
660   *tmp = '\0';
661   ent->blacklist.current += namelen + 1;
662
663   return;
664 }
665
666 /* returns TRUE if ent->blacklist contains name, else FALSE */
667 static bool_t
668 in_blacklist (const char *name, int namelen, ent_t *ent)
669 {
670   char buf[namelen + 3];
671   char *cp;
672
673   if (ent->blacklist.data == NULL)
674     return FALSE;
675
676   buf[0] = '|';
677   cp = stpcpy (&buf[1], name);
678   *cp++ = '|';
679   *cp = '\0';
680   return strstr (ent->blacklist.data, buf) != NULL;
681 }