Update copyright notices with scripts/update-copyrights
[jlayton/glibc.git] / nis / nss_compat / compat-initgroups.c
1 /* Copyright (C) 1998-2014 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3    Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998.
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 <alloca.h>
20 #include <ctype.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <grp.h>
24 #include <nss.h>
25 #include <stdio_ext.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <rpc/types.h>
29 #include <sys/param.h>
30 #include <nsswitch.h>
31 #include <bits/libc-lock.h>
32 #include <kernel-features.h>
33
34 static service_user *ni;
35 /* Type of the lookup function.  */
36 static enum nss_status (*nss_initgroups_dyn) (const char *, gid_t,
37                                               long int *, long int *,
38                                               gid_t **, long int, int *);
39 static enum nss_status (*nss_getgrnam_r) (const char *name,
40                                           struct group * grp, char *buffer,
41                                           size_t buflen, int *errnop);
42 static enum nss_status (*nss_getgrgid_r) (gid_t gid, struct group * grp,
43                                           char *buffer, size_t buflen,
44                                           int *errnop);
45 static enum nss_status (*nss_setgrent) (int stayopen);
46 static enum nss_status (*nss_getgrent_r) (struct group * grp, char *buffer,
47                                           size_t buflen, int *errnop);
48 static enum nss_status (*nss_endgrent) (void);
49
50 /* Protect global state against multiple changers.  */
51 __libc_lock_define_initialized (static, lock)
52
53
54 /* Get the declaration of the parser function.  */
55 #define ENTNAME grent
56 #define STRUCTURE group
57 #define EXTERN_PARSER
58 #include <nss/nss_files/files-parse.c>
59
60 /* Structure for remembering -group members ... */
61 #define BLACKLIST_INITIAL_SIZE 512
62 #define BLACKLIST_INCREMENT 256
63 struct blacklist_t
64 {
65   char *data;
66   int current;
67   int size;
68 };
69
70 struct ent_t
71 {
72   bool files;
73   bool need_endgrent;
74   bool skip_initgroups_dyn;
75   FILE *stream;
76   struct blacklist_t blacklist;
77 };
78 typedef struct ent_t ent_t;
79
80
81 /* Positive if O_CLOEXEC is supported, negative if it is not supported,
82    zero if it is still undecided.  This variable is shared with the
83    other compat functions.  */
84 #ifdef __ASSUME_O_CLOEXEC
85 # define __compat_have_cloexec 1
86 #else
87 # ifdef O_CLOEXEC
88 extern int __compat_have_cloexec;
89 # else
90 #  define __compat_have_cloexec -1
91 # endif
92 #endif
93
94 /* Prototypes for local functions.  */
95 static void blacklist_store_name (const char *, ent_t *);
96 static int in_blacklist (const char *, int, ent_t *);
97
98 /* Initialize the NSS interface/functions. The calling function must
99    hold the lock.  */
100 static void
101 init_nss_interface (void)
102 {
103   __libc_lock_lock (lock);
104
105   /* Retest.  */
106   if (ni == NULL
107       && __nss_database_lookup ("group_compat", NULL, "nis", &ni) >= 0)
108     {
109       nss_initgroups_dyn = __nss_lookup_function (ni, "initgroups_dyn");
110       nss_getgrnam_r = __nss_lookup_function (ni, "getgrnam_r");
111       nss_getgrgid_r = __nss_lookup_function (ni, "getgrgid_r");
112       nss_setgrent = __nss_lookup_function (ni, "setgrent");
113       nss_getgrent_r = __nss_lookup_function (ni, "getgrent_r");
114       nss_endgrent = __nss_lookup_function (ni, "endgrent");
115     }
116
117   __libc_lock_unlock (lock);
118 }
119
120 static enum nss_status
121 internal_setgrent (ent_t *ent)
122 {
123   enum nss_status status = NSS_STATUS_SUCCESS;
124
125   ent->files = true;
126
127   if (ni == NULL)
128     init_nss_interface ();
129
130   if (ent->blacklist.data != NULL)
131     {
132       ent->blacklist.current = 1;
133       ent->blacklist.data[0] = '|';
134       ent->blacklist.data[1] = '\0';
135     }
136   else
137     ent->blacklist.current = 0;
138
139   ent->stream = fopen ("/etc/group", "rme");
140
141   if (ent->stream == NULL)
142     status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
143   else
144     {
145       /* We have to make sure the file is  `closed on exec'.  */
146       int result = 0;
147
148       if (__compat_have_cloexec <= 0)
149         {
150           int flags;
151           result = flags = fcntl (fileno_unlocked (ent->stream), F_GETFD, 0);
152           if (result >= 0)
153             {
154 #if defined O_CLOEXEC && !defined __ASSUME_O_CLOEXEC
155               if (__compat_have_cloexec == 0)
156                 __compat_have_cloexec = (flags & FD_CLOEXEC) ? 1 : -1;
157
158               if (__compat_have_cloexec < 0)
159 #endif
160                 {
161                   flags |= FD_CLOEXEC;
162                   result = fcntl (fileno_unlocked (ent->stream), F_SETFD,
163                                   flags);
164                 }
165             }
166         }
167
168       if (result < 0)
169         {
170           /* Something went wrong.  Close the stream and return a
171              failure.  */
172           fclose (ent->stream);
173           ent->stream = NULL;
174           status = NSS_STATUS_UNAVAIL;
175         }
176       else
177         /* We take care of locking ourself.  */
178         __fsetlocking (ent->stream, FSETLOCKING_BYCALLER);
179     }
180
181   return status;
182 }
183
184
185 static enum nss_status
186 internal_endgrent (ent_t *ent)
187 {
188   if (ent->stream != NULL)
189     {
190       fclose (ent->stream);
191       ent->stream = NULL;
192     }
193
194   if (ent->blacklist.data != NULL)
195     {
196       ent->blacklist.current = 1;
197       ent->blacklist.data[0] = '|';
198       ent->blacklist.data[1] = '\0';
199     }
200   else
201     ent->blacklist.current = 0;
202
203   if (ent->need_endgrent && nss_endgrent != NULL)
204     nss_endgrent ();
205
206   return NSS_STATUS_SUCCESS;
207 }
208
209 /* Add new group record.  */
210 static void
211 add_group (long int *start, long int *size, gid_t **groupsp, long int limit,
212            gid_t gid)
213 {
214   gid_t *groups = *groupsp;
215
216   /* Matches user.  Insert this group.  */
217   if (__builtin_expect (*start == *size, 0))
218     {
219       /* Need a bigger buffer.  */
220       gid_t *newgroups;
221       long int newsize;
222
223       if (limit > 0 && *size == limit)
224         /* We reached the maximum.  */
225         return;
226
227       if (limit <= 0)
228         newsize = 2 * *size;
229       else
230         newsize = MIN (limit, 2 * *size);
231
232       newgroups = realloc (groups, newsize * sizeof (*groups));
233       if (newgroups == NULL)
234         return;
235       *groupsp = groups = newgroups;
236       *size = newsize;
237     }
238
239   groups[*start] = gid;
240   *start += 1;
241 }
242
243 /* This function checks, if the user is a member of this group and if
244    yes, add the group id to the list.  Return nonzero is we couldn't
245    handle the group because the user is not in the member list.  */
246 static int
247 check_and_add_group (const char *user, gid_t group, long int *start,
248                      long int *size, gid_t **groupsp, long int limit,
249                      struct group *grp)
250 {
251   char **member;
252
253   /* Don't add main group to list of groups.  */
254   if (grp->gr_gid == group)
255     return 0;
256
257   for (member = grp->gr_mem; *member != NULL; ++member)
258     if (strcmp (*member, user) == 0)
259       {
260         add_group (start, size, groupsp, limit, grp->gr_gid);
261         return 0;
262       }
263
264   return 1;
265 }
266
267 /* Get the next group from NSS  (+ entry). If the NSS module supports
268    initgroups_dyn, get all entries at once.  */
269 static enum nss_status
270 getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user,
271                    gid_t group, long int *start, long int *size,
272                    gid_t **groupsp, long int limit, int *errnop)
273 {
274   enum nss_status status;
275   struct group grpbuf;
276
277   /* Try nss_initgroups_dyn if supported. We also need getgrgid_r.
278      If this function is not supported, step through the whole group
279      database with getgrent_r.  */
280   if (! ent->skip_initgroups_dyn)
281     {
282       long int mystart = 0;
283       long int mysize = limit <= 0 ? *size : limit;
284       gid_t *mygroups = malloc (mysize * sizeof (gid_t));
285
286       if (mygroups == NULL)
287         return NSS_STATUS_TRYAGAIN;
288
289       /* For every gid in the list we get from the NSS module,
290          get the whole group entry. We need to do this, since we
291          need the group name to check if it is in the blacklist.
292          In worst case, this is as twice as slow as stepping with
293          getgrent_r through the whole group database. But for large
294          group databases this is faster, since the user can only be
295          in a limited number of groups.  */
296       if (nss_initgroups_dyn (user, group, &mystart, &mysize, &mygroups,
297                               limit, errnop) == NSS_STATUS_SUCCESS)
298         {
299           status = NSS_STATUS_NOTFOUND;
300
301           /* If there is no blacklist we can trust the underlying
302              initgroups implementation.  */
303           if (ent->blacklist.current <= 1)
304             for (int i = 0; i < mystart; i++)
305               add_group (start, size, groupsp, limit, mygroups[i]);
306           else
307             {
308               /* A temporary buffer. We use the normal buffer, until we find
309                  an entry, for which this buffer is to small.  In this case, we
310                  overwrite the pointer with one to a bigger buffer.  */
311               char *tmpbuf = buffer;
312               size_t tmplen = buflen;
313               bool use_malloc = false;
314
315               for (int i = 0; i < mystart; i++)
316                 {
317                   while ((status = nss_getgrgid_r (mygroups[i], &grpbuf,
318                                                    tmpbuf, tmplen, errnop))
319                          == NSS_STATUS_TRYAGAIN
320                          && *errnop == ERANGE)
321                     {
322                       if (__libc_use_alloca (tmplen * 2))
323                         {
324                           if (tmpbuf == buffer)
325                             {
326                               tmplen *= 2;
327                               tmpbuf = __alloca (tmplen);
328                             }
329                           else
330                             tmpbuf = extend_alloca (tmpbuf, tmplen, tmplen * 2);
331                         }
332                       else
333                         {
334                           tmplen *= 2;
335                           char *newbuf = realloc (use_malloc ? tmpbuf : NULL, tmplen);
336
337                           if (newbuf == NULL)
338                             {
339                               status = NSS_STATUS_TRYAGAIN;
340                               goto done;
341                             }
342                           use_malloc = true;
343                           tmpbuf = newbuf;
344                         }
345                     }
346
347                   if (__builtin_expect  (status != NSS_STATUS_NOTFOUND, 1))
348                     {
349                       if (__builtin_expect  (status != NSS_STATUS_SUCCESS, 0))
350                         goto done;
351
352                       if (!in_blacklist (grpbuf.gr_name,
353                                          strlen (grpbuf.gr_name), ent)
354                           && check_and_add_group (user, group, start, size,
355                                                   groupsp, limit, &grpbuf))
356                         {
357                           if (nss_setgrent != NULL)
358                             {
359                               nss_setgrent (1);
360                               ent->need_endgrent = true;
361                             }
362                           ent->skip_initgroups_dyn = true;
363
364                           goto iter;
365                         }
366                     }
367                 }
368
369               status = NSS_STATUS_NOTFOUND;
370
371  done:
372               if (use_malloc)
373                 free (tmpbuf);
374             }
375
376           free (mygroups);
377
378           return status;
379         }
380
381       free (mygroups);
382     }
383
384   /* If we come here, the NSS module does not support initgroups_dyn
385      or we were confronted with a split group.  In these cases we have
386      to step through the whole list ourself.  */
387  iter:
388   do
389     {
390       if ((status = nss_getgrent_r (&grpbuf, buffer, buflen, errnop)) !=
391           NSS_STATUS_SUCCESS)
392         break;
393     }
394   while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
395
396   if (status == NSS_STATUS_SUCCESS)
397     check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
398
399   return status;
400 }
401
402 static enum nss_status
403 internal_getgrent_r (ent_t *ent, char *buffer, size_t buflen, const char *user,
404                      gid_t group, long int *start, long int *size,
405                      gid_t **groupsp, long int limit, int *errnop)
406 {
407   struct parser_data *data = (void *) buffer;
408   struct group grpbuf;
409
410   if (!ent->files)
411     return getgrent_next_nss (ent, buffer, buflen, user, group,
412                               start, size, groupsp, limit, errnop);
413
414   while (1)
415     {
416       fpos_t pos;
417       int parse_res = 0;
418       char *p;
419
420       do
421         {
422           /* We need at least 3 characters for one line.  */
423           if (__builtin_expect (buflen < 3, 0))
424             {
425             erange:
426               *errnop = ERANGE;
427               return NSS_STATUS_TRYAGAIN;
428             }
429
430           fgetpos (ent->stream, &pos);
431           buffer[buflen - 1] = '\xff';
432           p = fgets_unlocked (buffer, buflen, ent->stream);
433           if (p == NULL && feof_unlocked (ent->stream))
434             return NSS_STATUS_NOTFOUND;
435
436           if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
437             {
438             erange_reset:
439               fsetpos (ent->stream, &pos);
440               goto erange;
441             }
442
443           /* Terminate the line for any case.  */
444           buffer[buflen - 1] = '\0';
445
446           /* Skip leading blanks.  */
447           while (isspace (*p))
448             ++p;
449         }
450       while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */
451              /* Parse the line.  If it is invalid, loop to
452                 get the next line of the file to parse.  */
453              !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen,
454                                                    errnop)));
455
456       if (__builtin_expect (parse_res == -1, 0))
457         /* The parser ran out of space.  */
458         goto erange_reset;
459
460       if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-')
461         /* This is a real entry.  */
462         break;
463
464       /* -group */
465       if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0'
466           && grpbuf.gr_name[1] != '@')
467         {
468           blacklist_store_name (&grpbuf.gr_name[1], ent);
469           continue;
470         }
471
472       /* +group */
473       if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0'
474           && grpbuf.gr_name[1] != '@')
475         {
476           if (in_blacklist (&grpbuf.gr_name[1],
477                             strlen (&grpbuf.gr_name[1]), ent))
478             continue;
479           /* Store the group in the blacklist for the "+" at the end of
480              /etc/group */
481           blacklist_store_name (&grpbuf.gr_name[1], ent);
482           if (nss_getgrnam_r == NULL)
483             return NSS_STATUS_UNAVAIL;
484           else if (nss_getgrnam_r (&grpbuf.gr_name[1], &grpbuf, buffer,
485                                    buflen, errnop) != NSS_STATUS_SUCCESS)
486             continue;
487
488           check_and_add_group (user, group, start, size, groupsp,
489                                limit, &grpbuf);
490
491           return NSS_STATUS_SUCCESS;
492         }
493
494       /* +:... */
495       if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
496         {
497           /* If the selected module does not support getgrent_r or
498              initgroups_dyn, abort. We cannot find the needed group
499              entries.  */
500           if (nss_initgroups_dyn == NULL || nss_getgrgid_r == NULL)
501             {
502               if (nss_setgrent != NULL)
503                 {
504                   nss_setgrent (1);
505                   ent->need_endgrent = true;
506                 }
507               ent->skip_initgroups_dyn = true;
508
509               if (nss_getgrent_r == NULL)
510                 return NSS_STATUS_UNAVAIL;
511             }
512
513           ent->files = false;
514
515           return getgrent_next_nss (ent, buffer, buflen, user, group,
516                                     start, size, groupsp, limit, errnop);
517         }
518     }
519
520   check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
521
522   return NSS_STATUS_SUCCESS;
523 }
524
525
526 enum nss_status
527 _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
528                             long int *size, gid_t **groupsp, long int limit,
529                             int *errnop)
530 {
531   size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
532   char *tmpbuf;
533   enum nss_status status;
534   ent_t intern = { true, false, false, NULL, {NULL, 0, 0} };
535   bool use_malloc = false;
536
537   status = internal_setgrent (&intern);
538   if (status != NSS_STATUS_SUCCESS)
539     return status;
540
541   tmpbuf = __alloca (buflen);
542
543   do
544     {
545       while ((status = internal_getgrent_r (&intern, tmpbuf, buflen,
546                                             user, group, start, size,
547                                             groupsp, limit, errnop))
548              == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
549         if (__libc_use_alloca (buflen * 2))
550           tmpbuf = extend_alloca (tmpbuf, buflen, 2 * buflen);
551         else
552           {
553             buflen *= 2;
554             char *newbuf = realloc (use_malloc ? tmpbuf : NULL, buflen);
555             if (newbuf == NULL)
556               {
557                 status = NSS_STATUS_TRYAGAIN;
558                 goto done;
559               }
560             use_malloc = true;
561             tmpbuf = newbuf;
562           }
563     }
564   while (status == NSS_STATUS_SUCCESS);
565
566   status = NSS_STATUS_SUCCESS;
567
568  done:
569   if (use_malloc)
570     free (tmpbuf);
571
572   internal_endgrent (&intern);
573
574   return status;
575 }
576
577
578 /* Support routines for remembering -@netgroup and -user entries.
579    The names are stored in a single string with `|' as separator. */
580 static void
581 blacklist_store_name (const char *name, ent_t *ent)
582 {
583   int namelen = strlen (name);
584   char *tmp;
585
586   /* First call, setup cache.  */
587   if (ent->blacklist.size == 0)
588     {
589       ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
590       ent->blacklist.data = malloc (ent->blacklist.size);
591       if (ent->blacklist.data == NULL)
592         return;
593       ent->blacklist.data[0] = '|';
594       ent->blacklist.data[1] = '\0';
595       ent->blacklist.current = 1;
596     }
597   else
598     {
599       if (in_blacklist (name, namelen, ent))
600         return;                 /* no duplicates */
601
602       if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
603         {
604           ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
605           tmp = realloc (ent->blacklist.data, ent->blacklist.size);
606           if (tmp == NULL)
607             {
608               free (ent->blacklist.data);
609               ent->blacklist.size = 0;
610               return;
611             }
612           ent->blacklist.data = tmp;
613         }
614     }
615
616   tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
617   *tmp++ = '|';
618   *tmp = '\0';
619   ent->blacklist.current += namelen + 1;
620
621   return;
622 }
623
624 /* returns TRUE if ent->blacklist contains name, else FALSE */
625 static bool_t
626 in_blacklist (const char *name, int namelen, ent_t *ent)
627 {
628   char buf[namelen + 3];
629   char *cp;
630
631   if (ent->blacklist.data == NULL)
632     return FALSE;
633
634   buf[0] = '|';
635   cp = stpcpy (&buf[1], name);
636   *cp++ = '|';
637   *cp = '\0';
638   return strstr (ent->blacklist.data, buf) != NULL;
639 }