Set errno to ENOENT if we have no more entries.
[kopensolaris-gnu/glibc.git] / nis / nss_compat / compat-initgroups.c
1 /* Copyright (C) 1998 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3    Contributed by Thorsten Kukuk <kukuk@vt.uni-paderborn.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 Library General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    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    Library General Public License for more details.
14
15    You should have received a copy of the GNU Library General Public
16    License along with the GNU C Library; see the file COPYING.LIB.  If not,
17    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18    Boston, MA 02111-1307, USA.  */
19
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <nss.h>
23 #include <grp.h>
24 #include <ctype.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <rpcsvc/yp.h>
28 #include <rpcsvc/ypclnt.h>
29 #include <rpcsvc/nis.h>
30 #include <nsswitch.h>
31
32 #include "nss-nisplus.h"
33 #include "nisplus-parser.h"
34
35 static service_user *ni = NULL;
36 static bool_t use_nisplus = FALSE; /* default: group_compat: nis */
37 static nis_name grptable = NULL; /* Name of the group table */
38 static size_t grptablelen = 0;
39
40 /* Get the declaration of the parser function.  */
41 #define ENTNAME grent
42 #define STRUCTURE group
43 #define EXTERN_PARSER
44 #include <nss/nss_files/files-parse.c>
45
46 /* Structure for remembering -group members ... */
47 #define BLACKLIST_INITIAL_SIZE 512
48 #define BLACKLIST_INCREMENT 256
49 struct blacklist_t
50   {
51     char *data;
52     int current;
53     int size;
54   };
55
56 struct ent_t
57   {
58     bool_t nis;
59     bool_t nis_first;
60     char *oldkey;
61     int oldkeylen;
62     nis_result *result;
63     FILE *stream;
64     struct blacklist_t blacklist;
65 };
66 typedef struct ent_t ent_t;
67
68
69 /* Prototypes for local functions.  */
70 static void blacklist_store_name (const char *, ent_t *);
71 static int in_blacklist (const char *, int, ent_t *);
72
73 static enum nss_status
74 _nss_first_init (void)
75 {
76   if (ni == NULL)
77     {
78       __nss_database_lookup ("group_compat", NULL, "nis", &ni);
79       use_nisplus = (strcmp (ni->name, "nisplus") == 0);
80     }
81
82   if (grptable == NULL)
83     {
84       static const char key[] = "group.org_dir.";
85       const char *local_dir = nis_local_directory ();
86       size_t len_local_dir = strlen (local_dir);
87
88       grptable = malloc (sizeof (key) + len_local_dir);
89       if (grptable == NULL)
90         return NSS_STATUS_TRYAGAIN;
91
92       grptablelen = ((char *) mempcpy (mempcpy (grptable,
93                                                 key, sizeof (key) - 1),
94                                        local_dir, len_local_dir + 1)
95                      - grptable) - 1;
96     }
97
98   return NSS_STATUS_SUCCESS;
99 }
100
101 static enum nss_status
102 internal_setgrent (ent_t *ent)
103 {
104   enum nss_status status = NSS_STATUS_SUCCESS;
105
106   ent->nis = ent->nis_first = 0;
107
108   if (_nss_first_init () != NSS_STATUS_SUCCESS)
109     return NSS_STATUS_UNAVAIL;
110
111   if (ent->oldkey != NULL)
112     {
113       free (ent->oldkey);
114       ent->oldkey = NULL;
115       ent->oldkeylen = 0;
116     }
117
118   if (ent->result != NULL)
119     {
120       nis_freeresult (ent->result);
121       ent->result = NULL;
122     }
123
124   if (ent->blacklist.data != NULL)
125     {
126       ent->blacklist.current = 1;
127       ent->blacklist.data[0] = '|';
128       ent->blacklist.data[1] = '\0';
129     }
130   else
131     ent->blacklist.current = 0;
132
133   if (ent->stream == NULL)
134     {
135       ent->stream = fopen ("/etc/group", "r");
136
137       if (ent->stream == NULL)
138         status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
139       else
140         {
141           /* We have to make sure the file is  `closed on exec'.  */
142           int result, flags;
143
144           result = flags = fcntl (fileno (ent->stream), F_GETFD, 0);
145           if (result >= 0)
146             {
147               flags |= FD_CLOEXEC;
148               result = fcntl (fileno (ent->stream), F_SETFD, flags);
149             }
150           if (result < 0)
151             {
152               /* Something went wrong.  Close the stream and return a
153                  failure.  */
154               fclose (ent->stream);
155               ent->stream = NULL;
156               status = NSS_STATUS_UNAVAIL;
157             }
158         }
159     }
160   else
161     rewind (ent->stream);
162
163   return status;
164 }
165
166
167 static enum nss_status
168 internal_endgrent (ent_t *ent)
169 {
170   if (ent->stream != NULL)
171     {
172       fclose (ent->stream);
173       ent->stream = NULL;
174     }
175
176   ent->nis = ent->nis_first = 0;
177
178   if (ent->oldkey != NULL)
179     {
180       free (ent->oldkey);
181       ent->oldkey = NULL;
182       ent->oldkeylen = 0;
183     }
184
185   if (ent->result != NULL)
186     {
187       nis_freeresult (ent->result);
188       ent->result = NULL;
189     }
190
191   if (ent->blacklist.data != NULL)
192     {
193       ent->blacklist.current = 1;
194       ent->blacklist.data[0] = '|';
195       ent->blacklist.data[1] = '\0';
196     }
197   else
198     ent->blacklist.current = 0;
199
200   return NSS_STATUS_SUCCESS;
201 }
202
203 static enum nss_status
204 getgrent_next_nis (struct group *result, ent_t *ent, char *buffer,
205                    size_t buflen, int *errnop)
206 {
207   struct parser_data *data = (void *) buffer;
208   char *domain;
209   char *outkey, *outval;
210   int outkeylen, outvallen, parse_res;
211   char *p;
212
213   if (yp_get_default_domain (&domain) != YPERR_SUCCESS)
214     {
215       ent->nis = 0;
216       return NSS_STATUS_NOTFOUND;
217     }
218
219   do
220     {
221       char *save_oldkey;
222       int save_oldlen;
223       bool_t save_nis_first;
224
225       if (ent->nis_first)
226         {
227           if (yp_first (domain, "group.byname", &outkey, &outkeylen,
228                         &outval, &outvallen) != YPERR_SUCCESS)
229             {
230               ent->nis = 0;
231               *errnop = ENOENT;
232               return NSS_STATUS_UNAVAIL;
233             }
234
235           if ( buflen < ((size_t) outvallen + 1))
236             {
237               free (outval);
238               *errnop = ERANGE;
239               return NSS_STATUS_TRYAGAIN;
240             }
241
242           save_oldkey = ent->oldkey;
243           save_oldlen = ent->oldkeylen;
244           save_nis_first = TRUE;
245           ent->oldkey = outkey;
246           ent->oldkeylen = outkeylen;
247           ent->nis_first = FALSE;
248         }
249       else
250         {
251           if (yp_next (domain, "group.byname", ent->oldkey, ent->oldkeylen,
252                        &outkey, &outkeylen, &outval, &outvallen)
253               != YPERR_SUCCESS)
254             {
255               ent->nis = 0;
256               *errnop = ENOENT;
257               return NSS_STATUS_NOTFOUND;
258             }
259
260           if ( buflen < ((size_t) outvallen + 1))
261             {
262               free (outval);
263               *errnop = ERANGE;
264               return NSS_STATUS_TRYAGAIN;
265             }
266
267           save_oldkey = ent->oldkey;
268           save_oldlen = ent->oldkeylen;
269           save_nis_first = FALSE;
270           ent->oldkey = outkey;
271           ent->oldkeylen = outkeylen;
272         }
273
274       /* Copy the found data to our buffer...  */
275       p = strncpy (buffer, outval, buflen);
276
277       /* ...and free the data.  */
278       free (outval);
279
280       while (isspace (*p))
281         ++p;
282
283       parse_res = _nss_files_parse_grent (p, result, data, buflen, errnop);
284       if (parse_res == -1)
285         {
286           free (ent->oldkey);
287           ent->oldkey = save_oldkey;
288           ent->oldkeylen = save_oldlen;
289           ent->nis_first = save_nis_first;
290           *errnop = ERANGE;
291           return NSS_STATUS_TRYAGAIN;
292         }
293       else
294         {
295           if (!save_nis_first)
296             free (save_oldkey);
297         }
298
299       if (parse_res &&
300           in_blacklist (result->gr_name, strlen (result->gr_name), ent))
301         parse_res = 0; /* if result->gr_name in blacklist,search next entry */
302     }
303   while (!parse_res);
304
305   return NSS_STATUS_SUCCESS;
306 }
307
308 static enum nss_status
309 getgrent_next_nisplus (struct group *result, ent_t *ent, char *buffer,
310                        size_t buflen, int *errnop)
311 {
312   int parse_res;
313
314   do
315     {
316       nis_result *save_oldres;
317       bool_t save_nis_first;
318
319       if (ent->nis_first)
320         {
321           save_oldres = ent->result;
322           save_nis_first = TRUE;
323           ent->result = nis_first_entry(grptable);
324           if (niserr2nss (ent->result->status) != NSS_STATUS_SUCCESS)
325             {
326               ent->nis = 0;
327               return niserr2nss (ent->result->status);
328             }
329           ent->nis_first = FALSE;
330         }
331       else
332         {
333           nis_result *res;
334
335           save_oldres = ent->result;
336           save_nis_first = FALSE;
337           res = nis_next_entry(grptable, &ent->result->cookie);
338           ent->result = res;
339           if (niserr2nss (ent->result->status) != NSS_STATUS_SUCCESS)
340             {
341               ent->nis = 0;
342               return niserr2nss (ent->result->status);
343             }
344         }
345       parse_res = _nss_nisplus_parse_grent (ent->result, 0, result,
346                                             buffer, buflen, errnop);
347       if (parse_res == -1)
348         {
349           nis_freeresult (ent->result);
350           ent->result = save_oldres;
351           ent->nis_first = save_nis_first;
352           *errnop = ERANGE;
353           return NSS_STATUS_TRYAGAIN;
354         }
355       else
356         {
357           if (!save_nis_first)
358             nis_freeresult (save_oldres);
359         }
360
361       if (parse_res &&
362           in_blacklist (result->gr_name, strlen (result->gr_name), ent))
363         parse_res = 0; /* if result->gr_name in blacklist,search next entry */
364     }
365   while (!parse_res);
366
367   return NSS_STATUS_SUCCESS;
368 }
369
370 /* This function handle the +group entrys in /etc/group */
371 static enum nss_status
372 getgrnam_plusgroup (const char *name, struct group *result, char *buffer,
373                     size_t buflen, int *errnop)
374 {
375   struct parser_data *data = (void *) buffer;
376   int parse_res;
377
378   if (use_nisplus) /* Do the NIS+ query here */
379     {
380       nis_result *res;
381       char buf[strlen (name) + 24 + grptablelen];
382
383       sprintf(buf, "[name=%s],%s", name, grptable);
384       res = nis_list(buf, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL);
385       if (niserr2nss (res->status) != NSS_STATUS_SUCCESS)
386         {
387           enum nss_status status =  niserr2nss (res->status);
388
389           nis_freeresult (res);
390           return status;
391         }
392       parse_res = _nss_nisplus_parse_grent (res, 0, result, buffer, buflen,
393                                             errnop);
394       if (parse_res == -1)
395         {
396           nis_freeresult (res);
397           *errnop = ERANGE;
398           return NSS_STATUS_TRYAGAIN;
399         }
400       nis_freeresult (res);
401     }
402   else /* Use NIS */
403     {
404       char *domain, *outval, *p;
405       int outvallen;
406
407       if (yp_get_default_domain (&domain) != YPERR_SUCCESS)
408         {
409           *errnop = ENOENT;
410           return NSS_STATUS_NOTFOUND;
411         }
412
413       if (yp_match (domain, "group.byname", name, strlen (name),
414                     &outval, &outvallen) != YPERR_SUCCESS)
415         {
416           *errnop = ENOENT;
417           return NSS_STATUS_NOTFOUND;
418         }
419
420       if (buflen < ((size_t) outvallen + 1))
421         {
422           free (outval);
423           *errnop = ERANGE;
424           return NSS_STATUS_TRYAGAIN;
425         }
426
427       /* Copy the found data to our buffer...  */
428       p = strncpy (buffer, outval, buflen);
429
430       /* ... and free the data.  */
431       free (outval);
432       while (isspace (*p))
433         ++p;
434       parse_res = _nss_files_parse_grent (p, result, data, buflen, errnop);
435       if (parse_res == -1)
436         return NSS_STATUS_TRYAGAIN;
437     }
438
439   if (parse_res)
440     /* We found the entry.  */
441     return NSS_STATUS_SUCCESS;
442   else
443     return NSS_STATUS_RETURN;
444 }
445
446 static enum nss_status
447 getgrent_next_file (struct group *result, ent_t *ent,
448                     char *buffer, size_t buflen, int *errnop)
449 {
450   struct parser_data *data = (void *) buffer;
451   while (1)
452     {
453       fpos_t pos;
454       int parse_res = 0;
455       char *p;
456
457       do
458         {
459           fgetpos (ent->stream, &pos);
460           buffer[buflen - 1] = '\xff';
461           p = fgets (buffer, buflen, ent->stream);
462           if (p == NULL && feof (ent->stream))
463             {
464               *errnop = ENOENT;
465               return NSS_STATUS_NOTFOUND;
466             }
467           if (p == NULL || buffer[buflen - 1] != '\xff')
468             {
469               fsetpos (ent->stream, &pos);
470               *errnop = ERANGE;
471               return NSS_STATUS_TRYAGAIN;
472             }
473
474           /* Terminate the line for any case.  */
475           buffer[buflen - 1] = '\0';
476
477           /* Skip leading blanks.  */
478           while (isspace (*p))
479             ++p;
480         }
481       while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */
482       /* Parse the line.  If it is invalid, loop to
483          get the next line of the file to parse.  */
484              !(parse_res = _nss_files_parse_grent (p, result, data, buflen,
485                                                    errnop)));
486
487       if (parse_res == -1)
488         {
489           /* The parser ran out of space.  */
490           fsetpos (ent->stream, &pos);
491           *errnop = ERANGE;
492           return NSS_STATUS_TRYAGAIN;
493         }
494
495       if (result->gr_name[0] != '+' && result->gr_name[0] != '-')
496         /* This is a real entry.  */
497         break;
498
499       /* -group */
500       if (result->gr_name[0] == '-' && result->gr_name[1] != '\0'
501           && result->gr_name[1] != '@')
502         {
503           blacklist_store_name (&result->gr_name[1], ent);
504           continue;
505         }
506
507       /* +group */
508       if (result->gr_name[0] == '+' && result->gr_name[1] != '\0'
509           && result->gr_name[1] != '@')
510         {
511           enum nss_status status;
512
513           /* Store the group in the blacklist for the "+" at the end of
514              /etc/group */
515           blacklist_store_name (&result->gr_name[1], ent);
516           status = getgrnam_plusgroup (&result->gr_name[1], result, buffer,
517                                        buflen, errnop);
518           if (status == NSS_STATUS_SUCCESS) /* We found the entry. */
519             break;
520           else
521             if (status == NSS_STATUS_RETURN /* We couldn't parse the entry */
522                 || status == NSS_STATUS_NOTFOUND) /* No group in NIS */
523               continue;
524             else
525               {
526                 if (status == NSS_STATUS_TRYAGAIN)
527                   {
528                     /* The parser ran out of space.  */
529                     fsetpos (ent->stream, &pos);
530                     *errnop = ERANGE;
531                   }
532                 return status;
533               }
534         }
535
536       /* +:... */
537       if (result->gr_name[0] == '+' && result->gr_name[1] == '\0')
538         {
539           ent->nis = TRUE;
540           ent->nis_first = TRUE;
541
542           if (use_nisplus)
543             return getgrent_next_nisplus (result, ent, buffer, buflen, errnop);
544           else
545             return getgrent_next_nis (result, ent, buffer, buflen, errnop);
546         }
547     }
548
549   return NSS_STATUS_SUCCESS;
550 }
551
552
553 static enum nss_status
554 internal_getgrent_r (struct group *gr, ent_t *ent, char *buffer,
555                      size_t buflen, int *errnop)
556 {
557   if (ent->nis)
558     {
559       if (use_nisplus)
560         return getgrent_next_nisplus (gr, ent, buffer, buflen, errnop);
561       else
562         return getgrent_next_nis (gr, ent, buffer, buflen, errnop);
563     }
564   else
565     return getgrent_next_file (gr, ent, buffer, buflen, errnop);
566 }
567
568 enum nss_status
569 _nss_compat_initgroups (const char *user, gid_t group, long int *start,
570                         long int *size, gid_t *groups, long int limit,
571                         int *errnop)
572 {
573   struct group grpbuf, *g;
574   size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
575   char *tmpbuf;
576   enum nss_status status;
577   ent_t intern = {0, 0, NULL, 0, NULL, NULL, {NULL, 0, 0}};
578
579   status = internal_setgrent (&intern);
580   if (status != NSS_STATUS_SUCCESS)
581     return status;
582
583   tmpbuf = __alloca (buflen);
584
585   do
586     {
587       while ((status =
588               internal_getgrent_r (&grpbuf, &intern, tmpbuf, buflen,
589                                    errnop)) == NSS_STATUS_TRYAGAIN
590              && *errnop == ERANGE)
591         {
592           buflen *= 2;
593           tmpbuf = __alloca (buflen);
594         }
595
596       if (status != NSS_STATUS_SUCCESS)
597         goto done;
598
599       g = &grpbuf;
600       if (g->gr_gid != group)
601         {
602           char **m;
603
604           for (m = g->gr_mem; *m != NULL; ++m)
605             if (strcmp (*m, user) == 0)
606               {
607                 /* Matches user.  Insert this group.  */
608                 if (*start == *size && limit <= 0)
609                   {
610                     /* Need a bigger buffer.  */
611                     groups = realloc (groups, *size * sizeof (*groups));
612                     if (groups == NULL)
613                       goto done;
614                     *size *= 2;
615                   }
616
617                 groups[*start] = g->gr_gid;
618                 *start += 1;
619
620                 if (*start == limit)
621                   /* Can't take any more groups; stop searching.  */
622                   goto done;
623
624                 break;
625               }
626         }
627     }
628   while (status == NSS_STATUS_SUCCESS);
629
630 done:
631   internal_endgrent (&intern);
632
633   return NSS_STATUS_SUCCESS;
634 }
635
636
637 /* Support routines for remembering -@netgroup and -user entries.
638    The names are stored in a single string with `|' as separator. */
639 static void
640 blacklist_store_name (const char *name, ent_t *ent)
641 {
642   int namelen = strlen (name);
643   char *tmp;
644
645   /* first call, setup cache */
646   if (ent->blacklist.size == 0)
647     {
648       ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
649       ent->blacklist.data = malloc (ent->blacklist.size);
650       if (ent->blacklist.data == NULL)
651         return;
652       ent->blacklist.data[0] = '|';
653       ent->blacklist.data[1] = '\0';
654       ent->blacklist.current = 1;
655     }
656   else
657     {
658       if (in_blacklist (name, namelen, ent))
659         return;                 /* no duplicates */
660
661       if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
662         {
663           ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
664           tmp = realloc (ent->blacklist.data, ent->blacklist.size);
665           if (tmp == NULL)
666             {
667               free (ent->blacklist.data);
668               ent->blacklist.size = 0;
669               return;
670             }
671           ent->blacklist.data = tmp;
672         }
673     }
674
675   tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
676   *tmp++ = '|';
677   *tmp = '\0';
678   ent->blacklist.current += namelen + 1;
679
680   return;
681 }
682
683 /* returns TRUE if ent->blacklist contains name, else FALSE */
684 static bool_t
685 in_blacklist (const char *name, int namelen, ent_t *ent)
686 {
687   char buf[namelen + 3];
688   char *cp;
689
690   if (ent->blacklist.data == NULL)
691     return FALSE;
692
693   buf[0] = '|';
694   cp = stpcpy (&buf[1], name);
695   *cp++= '|';
696   *cp = '\0';
697   return strstr (ent->blacklist.data, buf) != NULL;
698 }