Fix "buffer to small" problems and memory leaks.
[kopensolaris-gnu/glibc.git] / nis / nss_compat / compat-grp.c
1 /* Copyright (C) 1996, 1997 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3    Contributed by Thorsten Kukuk <kukuk@vt.uni-paderborn.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 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 <nss.h>
22 #include <grp.h>
23 #include <ctype.h>
24 #include <bits/libc-lock.h>
25 #include <string.h>
26 #include <rpcsvc/yp.h>
27 #include <rpcsvc/ypclnt.h>
28 #include <rpcsvc/nis.h>
29 #include <rpcsvc/nislib.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 static ent_t ext_ent = {0, 0, NULL, 0, NULL, NULL, {NULL, 0, 0}};
69
70 /* Protect global state against multiple changers.  */
71 __libc_lock_define_initialized (static, lock)
72
73 /* Prototypes for local functions.  */
74 static void blacklist_store_name (const char *, ent_t *);
75 static int in_blacklist (const char *, int, ent_t *);
76
77 static enum nss_status
78 _nss_first_init (void)
79 {
80   if (ni == NULL)
81     {
82       __nss_database_lookup ("group_compat", NULL, "nis", &ni);
83       use_nisplus = (strcmp (ni->name, "nisplus") == 0);
84     }
85
86   if (grptable == NULL)
87     {
88       char buf [20 + strlen (nis_local_directory ())];
89       char *p;
90
91       p = stpcpy (buf, "group.org_dir.");
92       p = stpcpy (p, nis_local_directory ());
93       grptable = strdup (buf);
94       if (grptable == NULL)
95         return NSS_STATUS_TRYAGAIN;
96       grptablelen = strlen (grptable);
97     }
98
99   return NSS_STATUS_SUCCESS;
100 }
101
102 static enum nss_status
103 internal_setgrent (ent_t *ent)
104 {
105   enum nss_status status = NSS_STATUS_SUCCESS;
106
107   ent->nis = ent->nis_first = 0;
108
109   if (_nss_first_init () != NSS_STATUS_SUCCESS)
110     return NSS_STATUS_UNAVAIL;
111
112   if (ent->oldkey != NULL)
113     {
114       free (ent->oldkey);
115       ent->oldkey = NULL;
116       ent->oldkeylen = 0;
117     }
118
119   if (ent->result != NULL)
120     {
121       nis_freeresult (ent->result);
122       ent->result = NULL;
123     }
124
125   ent->blacklist.current = 0;
126   if (ent->blacklist.data != NULL)
127     ent->blacklist.data[0] = '\0';
128
129   if (ent->stream == NULL)
130     {
131       ent->stream = fopen ("/etc/group", "r");
132
133       if (ent->stream == NULL)
134         status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
135     }
136   else
137     rewind (ent->stream);
138
139   return status;
140 }
141
142
143 enum nss_status
144 _nss_compat_setgrent (void)
145 {
146   enum nss_status result;
147
148   __libc_lock_lock (lock);
149
150   result = internal_setgrent (&ext_ent);
151
152   __libc_lock_unlock (lock);
153
154   return result;
155 }
156
157
158 static enum nss_status
159 internal_endgrent (ent_t *ent)
160 {
161   if (ent->stream != NULL)
162     {
163       fclose (ent->stream);
164       ent->stream = NULL;
165     }
166
167   ent->nis = ent->nis_first = 0;
168
169   if (ent->oldkey != NULL)
170     {
171       free (ent->oldkey);
172       ent->oldkey = NULL;
173       ent->oldkeylen = 0;
174     }
175
176   if (ent->result != NULL)
177     {
178       nis_freeresult (ent->result);
179       ent->result = NULL;
180     }
181
182   ent->blacklist.current = 0;
183   if (ent->blacklist.data != NULL)
184     ent->blacklist.data[0] = '\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 static enum nss_status
204 getgrent_next_nis (struct group *result, ent_t *ent, char *buffer,
205                    size_t buflen)
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               return NSS_STATUS_UNAVAIL;
232             }
233           save_oldkey = ent->oldkey;
234           save_oldlen = ent->oldkeylen;
235           save_nis_first = TRUE;
236           ent->oldkey = outkey;
237           ent->oldkeylen = outkeylen;
238           ent->nis_first = FALSE;
239         }
240       else
241         {
242           if (yp_next (domain, "group.byname", ent->oldkey, ent->oldkeylen,
243                        &outkey, &outkeylen, &outval, &outvallen)
244               != YPERR_SUCCESS)
245             {
246               ent->nis = 0;
247               return NSS_STATUS_NOTFOUND;
248             }
249
250           save_oldkey = ent->oldkey;
251           save_oldlen = ent->oldkeylen;
252           save_nis_first = FALSE;
253           ent->oldkey = outkey;
254           ent->oldkeylen = outkeylen;
255         }
256
257       /* Copy the found data to our buffer  */
258       p = strncpy (buffer, outval, buflen);
259
260       /* ...and free the data.  */
261       free (outval);
262
263       while (isspace (*p))
264         ++p;
265
266       if ((parse_res = _nss_files_parse_grent (p, result, data, buflen)) == -1)
267         {
268           free (ent->oldkey);
269           ent->oldkey = save_oldkey;
270           ent->oldkeylen = save_oldlen;
271           ent->nis_first = save_nis_first;
272           __set_errno (ERANGE);
273           return NSS_STATUS_TRYAGAIN;
274         }
275       else
276         {
277           if (!save_nis_first)
278             free (save_oldkey);
279         }
280       
281       if (parse_res &&
282           in_blacklist (result->gr_name, strlen (result->gr_name), ent))
283         parse_res = 0; /* if result->gr_name in blacklist,search next entry */
284     }
285   while (!parse_res);
286
287   return NSS_STATUS_SUCCESS;
288 }
289
290 static enum nss_status
291 getgrent_next_nisplus (struct group *result, ent_t *ent, char *buffer,
292                        size_t buflen)
293 {
294   int parse_res;
295
296   do
297     {
298       nis_result *save_oldres;
299       bool_t save_nis_first;
300       
301       if (ent->nis_first)
302         {
303           save_oldres = ent->result;
304           save_nis_first = TRUE;
305           ent->result = nis_first_entry(grptable);
306           if (niserr2nss (ent->result->status) != NSS_STATUS_SUCCESS)
307             {
308               ent->nis = 0;
309               return niserr2nss (ent->result->status);
310             }
311           ent->nis_first = FALSE;
312         }
313       else
314         {
315           nis_result *res;
316
317           save_oldres = ent->result;
318           save_nis_first = FALSE;
319           res = nis_next_entry(grptable, &ent->result->cookie);
320           ent->result = res;
321           if (niserr2nss (ent->result->status) != NSS_STATUS_SUCCESS)
322             {
323               ent->nis = 0;
324               return niserr2nss (ent->result->status);
325             }
326         }
327       if ((parse_res = _nss_nisplus_parse_grent (ent->result, 0, result, 
328                                                  buffer, buflen)) == -1)
329         {
330           nis_freeresult (ent->result);
331           ent->result = save_oldres;
332           ent->nis_first = save_nis_first;
333           __set_errno (ERANGE);
334           return NSS_STATUS_TRYAGAIN;
335         }
336       else
337         {
338           if (!save_nis_first)
339             nis_freeresult (save_oldres);
340         }
341
342       if (parse_res &&
343           in_blacklist (result->gr_name, strlen (result->gr_name), ent))
344         parse_res = 0; /* if result->gr_name in blacklist,search next entry */
345     }
346   while (!parse_res);
347
348   return NSS_STATUS_SUCCESS;
349 }
350
351 /* This function handle the +group entrys in /etc/group */
352 static enum nss_status
353 getgrent_next_file_plusgroup (struct group *result, char *buffer,
354                               size_t buflen)
355 {
356   struct parser_data *data = (void *) buffer;
357   int parse_res;
358
359   if (use_nisplus) /* Do the NIS+ query here */
360     {
361       nis_result *res;
362       char buf[strlen (result->gr_name) + 24 + grptablelen];
363
364       sprintf(buf, "[name=%s],%s", &result->gr_name[1], grptable);
365       res = nis_list(buf, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL);
366       if (niserr2nss (res->status) != NSS_STATUS_SUCCESS)
367         {
368           enum nss_status status =  niserr2nss (res->status);
369
370           nis_freeresult (res);
371           return status;
372         }
373       if ((parse_res = _nss_nisplus_parse_grent (res, 0, result, buffer, 
374                                                  buflen)) == -1)
375         {
376           __set_errno (ERANGE);
377           nis_freeresult (res);
378           return NSS_STATUS_TRYAGAIN;
379         }
380       nis_freeresult (res);
381     }
382   else /* Use NIS */
383     {
384       char *domain, *outval, *p;
385       int outvallen;
386
387       if (yp_get_default_domain (&domain) != YPERR_SUCCESS)
388         return NSS_STATUS_TRYAGAIN;
389
390       if (yp_match (domain, "group.byname", &result->gr_name[1],
391                     strlen (result->gr_name) - 1, &outval, &outvallen)
392           != YPERR_SUCCESS)
393         return NSS_STATUS_TRYAGAIN;
394       p = strncpy (buffer, outval,
395                    buflen < (size_t) outvallen ? buflen : (size_t) outvallen);
396       free (outval);
397       while (isspace (*p))
398         p++;
399       if ((parse_res = _nss_files_parse_grent (p, result, data, buflen)) == -1)
400         {
401           __set_errno (ERANGE);
402           return NSS_STATUS_TRYAGAIN;
403         }
404     }
405
406   if (parse_res)
407     /* We found the entry.  */
408     return NSS_STATUS_SUCCESS;
409   else
410     return NSS_STATUS_RETURN;
411 }
412
413
414 static enum nss_status
415 getgrent_next_file (struct group *result, ent_t *ent,
416                     char *buffer, size_t buflen)
417 {
418   struct parser_data *data = (void *) buffer;
419   while (1)
420     {
421       fpos_t pos;
422       int parse_res = 0;
423       char *p;
424
425       do
426         {
427           fgetpos (ent->stream, &pos);
428           p = fgets (buffer, buflen, ent->stream);
429           if (p == NULL)
430             {
431               if (feof (ent->stream))
432                 return NSS_STATUS_NOTFOUND;
433               else
434                 {
435                   __set_errno (ERANGE);
436                   return NSS_STATUS_TRYAGAIN;
437                 }
438             }
439
440           /* Terminate the line for any case.  */
441           buffer[buflen - 1] = '\0';
442
443           /* Skip leading blanks.  */
444           while (isspace (*p))
445             ++p;
446         }
447       while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */
448       /* Parse the line.  If it is invalid, loop to
449          get the next line of the file to parse.  */
450              !(parse_res = _nss_files_parse_grent (p, result, data, buflen)));
451
452       if (parse_res == -1)
453         {
454           /* The parser ran out of space.  */
455           fsetpos (ent->stream, &pos);
456           __set_errno (ERANGE);
457           return NSS_STATUS_TRYAGAIN;
458         }
459
460       if (result->gr_name[0] != '+' && result->gr_name[0] != '-')
461         /* This is a real entry.  */
462         break;
463
464       /* -group */
465       if (result->gr_name[0] == '-' && result->gr_name[1] != '\0'
466           && result->gr_name[1] != '@')
467         {
468           blacklist_store_name (&result->gr_name[1], ent);
469           continue;
470         }
471
472       /* +group */
473       if (result->gr_name[0] == '+' && result->gr_name[1] != '\0'
474           && result->gr_name[1] != '@')
475         {
476           enum nss_status status;
477
478           status = getgrent_next_file_plusgroup (result, buffer, buflen);
479           if (status == NSS_STATUS_SUCCESS) /* We found the entry. */
480             break;
481           else
482             if (status == NSS_STATUS_RETURN) /* We couldn't parse the entry */
483               continue;
484             else
485               return status;
486         }
487
488       /* +:... */
489       if (result->gr_name[0] == '+' && result->gr_name[1] == '\0')
490         {
491           ent->nis = TRUE;
492           ent->nis_first = TRUE;
493
494           if (use_nisplus)
495             return getgrent_next_nisplus (result, ent, buffer, buflen);
496           else
497             return getgrent_next_nis (result, ent, buffer, buflen);
498         }
499     }
500
501   return NSS_STATUS_SUCCESS;
502 }
503
504
505 static enum nss_status
506 internal_getgrent_r (struct group *gr, ent_t *ent, char *buffer,
507                      size_t buflen)
508 {
509   if (ent->nis)
510     {
511       if (use_nisplus)
512         return getgrent_next_nisplus (gr, ent, buffer, buflen);
513       else
514         return getgrent_next_nis (gr, ent, buffer, buflen);
515     }
516   else
517     return getgrent_next_file (gr, ent, buffer, buflen);
518 }
519
520 enum nss_status
521 _nss_compat_getgrent_r (struct group *grp, char *buffer, size_t buflen)
522 {
523   enum nss_status status = NSS_STATUS_SUCCESS;
524
525   __libc_lock_lock (lock);
526
527   /* Be prepared that the setgrent function was not called before.  */
528   if (ext_ent.stream == NULL)
529     status = internal_setgrent (&ext_ent);
530
531   if (status == NSS_STATUS_SUCCESS)
532     status = internal_getgrent_r (grp, &ext_ent, buffer, buflen);
533
534   __libc_lock_unlock (lock);
535
536   return status;
537 }
538
539
540 enum nss_status
541 _nss_compat_getgrnam_r (const char *name, struct group *grp,
542                         char *buffer, size_t buflen)
543 {
544   ent_t ent = {0, 0, NULL, 0, NULL, NULL, {NULL, 0, 0}};
545   enum nss_status status;
546
547   if (name[0] == '-' || name[0] == '+')
548     return NSS_STATUS_NOTFOUND;
549
550   __libc_lock_lock (lock);
551
552   status = internal_setgrent (&ent);
553
554   __libc_lock_unlock (lock);
555
556   if (status != NSS_STATUS_SUCCESS)
557     return status;
558
559   while ((status = internal_getgrent_r (grp, &ent, buffer, buflen))
560          == NSS_STATUS_SUCCESS)
561     if (strcmp (grp->gr_name, name) == 0)
562       break;
563
564   internal_endgrent (&ent);
565   return status;
566 }
567
568
569 enum nss_status
570 _nss_compat_getgrgid_r (gid_t gid, struct group *grp,
571                         char *buffer, size_t buflen)
572 {
573   ent_t ent = {0, 0, NULL, 0, NULL, NULL, {NULL, 0, 0}};
574   enum nss_status status;
575
576   __libc_lock_lock (lock);
577
578   status = internal_setgrent (&ent);
579
580   __libc_lock_unlock (lock);
581
582   if (status != NSS_STATUS_SUCCESS)
583     return status;
584
585   while ((status = internal_getgrent_r (grp, &ent, buffer, buflen))
586          == NSS_STATUS_SUCCESS)
587     if (grp->gr_gid == gid && grp->gr_name[0] != '+' && grp->gr_name[0] != '-')
588       break;
589
590   internal_endgrent (&ent);
591   return status;
592 }
593
594
595 /* Support routines for remembering -@netgroup and -user entries.
596    The names are stored in a single string with `|' as separator. */
597 static void
598 blacklist_store_name (const char *name, ent_t *ent)
599 {
600   int namelen = strlen (name);
601   char *tmp;
602
603   /* first call, setup cache */
604   if (ent->blacklist.size == 0)
605     {
606       ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
607       ent->blacklist.data = malloc (ent->blacklist.size);
608       if (ent->blacklist.data == NULL)
609         return;
610       ent->blacklist.data[0] = '|';
611       ent->blacklist.data[1] = '\0';
612       ent->blacklist.current = 1;
613     }
614   else
615     {
616       if (in_blacklist (name, namelen, ent))
617         return;                 /* no duplicates */
618
619       if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
620         {
621           ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
622           tmp = realloc (ent->blacklist.data, ent->blacklist.size);
623           if (tmp == NULL)
624             {
625               free (ent->blacklist.data);
626               ent->blacklist.size = 0;
627               return;
628             }
629           ent->blacklist.data = tmp;
630         }
631     }
632
633   tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
634   *tmp++ = '|';
635   *tmp = '\0';
636   ent->blacklist.current += namelen + 1;
637
638   return;
639 }
640
641 /* returns TRUE if ent->blacklist contains name, else FALSE */
642 static bool_t
643 in_blacklist (const char *name, int namelen, ent_t *ent)
644 {
645   char buf[namelen + 3];
646   char *cp;
647
648   if (ent->blacklist.data == NULL)
649     return FALSE;
650
651   buf[0] = '|';
652   cp = stpcpy (&buf[1], name);
653   *cp++= '|';
654   *cp = '\0';
655   return strstr (ent->blacklist.data, buf) != NULL;
656 }