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