Add missing #include.
[kopensolaris-gnu/glibc.git] / nis / nss_compat / compat-pwd.c
1 /* Copyright (C) 1996-1999,2001,2002,2003 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 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 <netdb.h>
24 #include <nss.h>
25 #include <nsswitch.h>
26 #include <pwd.h>
27 #include <stdio_ext.h>
28 #include <string.h>
29 #include <rpc/types.h>
30 #include <rpcsvc/ypclnt.h>
31 #include <bits/libc-lock.h>
32
33 #include "netgroup.h"
34
35 static service_user *ni;
36 static enum nss_status (*nss_setpwent) (int stayopen);
37 static enum nss_status (*nss_getpwnam_r) (const char *name,
38                                           struct passwd * pwd, char *buffer,
39                                           size_t buflen, int *errnop);
40 static enum nss_status (*nss_getpwuid_r) (uid_t uid, struct passwd * pwd,
41                                           char *buffer, size_t buflen,
42                                           int *errnop);
43 static enum nss_status (*nss_getpwent_r) (struct passwd * pwd, char *buffer,
44                                           size_t buflen, int *errnop);
45 static enum nss_status (*nss_endpwent) (void);
46
47 /* Get the declaration of the parser function.  */
48 #define ENTNAME pwent
49 #define STRUCTURE passwd
50 #define EXTERN_PARSER
51 #include <nss/nss_files/files-parse.c>
52
53 /* Structure for remembering -@netgroup and -user members ... */
54 #define BLACKLIST_INITIAL_SIZE 512
55 #define BLACKLIST_INCREMENT 256
56 struct blacklist_t
57 {
58   char *data;
59   int current;
60   int size;
61 };
62
63 struct ent_t
64 {
65   bool_t netgroup;
66   bool_t first;
67   bool_t files;
68   FILE *stream;
69   struct blacklist_t blacklist;
70   struct passwd pwd;
71   struct __netgrent netgrdata;
72 };
73 typedef struct ent_t ent_t;
74
75 static ent_t ext_ent = {0, 0, TRUE, NULL, {NULL, 0, 0},
76                         {NULL, NULL, 0, 0, NULL, NULL, NULL}};
77
78 /* Protect global state against multiple changers.  */
79 __libc_lock_define_initialized (static, lock)
80
81 /* Prototypes for local functions.  */
82 static void blacklist_store_name (const char *, ent_t *);
83 static int in_blacklist (const char *, int, ent_t *);
84
85 /* Initialize the NSS interface/functions. The calling function must
86    hold the lock.  */
87 static void
88 init_nss_interface (void)
89 {
90   if (__nss_database_lookup ("passwd_compat", NULL, "nis", &ni) >= 0)
91     {
92       nss_setpwent = __nss_lookup_function (ni, "setpwent");
93       nss_getpwnam_r = __nss_lookup_function (ni, "getpwnam_r");
94       nss_getpwuid_r = __nss_lookup_function (ni, "getpwuid_r");
95       nss_getpwent_r = __nss_lookup_function (ni, "getpwent_r");
96       nss_endpwent = __nss_lookup_function (ni, "endpwent");
97     }
98 }
99
100 static void
101 give_pwd_free (struct passwd *pwd)
102 {
103   if (pwd->pw_name != NULL)
104     free (pwd->pw_name);
105   if (pwd->pw_passwd != NULL)
106     free (pwd->pw_passwd);
107   if (pwd->pw_gecos != NULL)
108     free (pwd->pw_gecos);
109   if (pwd->pw_dir != NULL)
110     free (pwd->pw_dir);
111   if (pwd->pw_shell != NULL)
112     free (pwd->pw_shell);
113
114   memset (pwd, '\0', sizeof (struct passwd));
115 }
116
117 static size_t
118 pwd_need_buflen (struct passwd *pwd)
119 {
120   size_t len = 0;
121
122   if (pwd->pw_passwd != NULL)
123     len += strlen (pwd->pw_passwd) + 1;
124
125   if (pwd->pw_gecos != NULL)
126     len += strlen (pwd->pw_gecos) + 1;
127
128   if (pwd->pw_dir != NULL)
129     len += strlen (pwd->pw_dir) + 1;
130
131   if (pwd->pw_shell != NULL)
132     len += strlen (pwd->pw_shell) + 1;
133
134   return len;
135 }
136
137 static void
138 copy_pwd_changes (struct passwd *dest, struct passwd *src,
139                   char *buffer, size_t buflen)
140 {
141   if (src->pw_passwd != NULL && strlen (src->pw_passwd))
142     {
143       if (buffer == NULL)
144         dest->pw_passwd = strdup (src->pw_passwd);
145       else if (dest->pw_passwd &&
146                strlen (dest->pw_passwd) >= strlen (src->pw_passwd))
147         strcpy (dest->pw_passwd, src->pw_passwd);
148       else
149         {
150           dest->pw_passwd = buffer;
151           strcpy (dest->pw_passwd, src->pw_passwd);
152           buffer += strlen (dest->pw_passwd) + 1;
153           buflen = buflen - (strlen (dest->pw_passwd) + 1);
154         }
155     }
156
157   if (src->pw_gecos != NULL && strlen (src->pw_gecos))
158     {
159       if (buffer == NULL)
160         dest->pw_gecos = strdup (src->pw_gecos);
161       else if (dest->pw_gecos &&
162                strlen (dest->pw_gecos) >= strlen (src->pw_gecos))
163         strcpy (dest->pw_gecos, src->pw_gecos);
164       else
165         {
166           dest->pw_gecos = buffer;
167           strcpy (dest->pw_gecos, src->pw_gecos);
168           buffer += strlen (dest->pw_gecos) + 1;
169           buflen = buflen - (strlen (dest->pw_gecos) + 1);
170         }
171     }
172   if (src->pw_dir != NULL && strlen (src->pw_dir))
173     {
174       if (buffer == NULL)
175         dest->pw_dir = strdup (src->pw_dir);
176       else if (dest->pw_dir && strlen (dest->pw_dir) >= strlen (src->pw_dir))
177         strcpy (dest->pw_dir, src->pw_dir);
178       else
179         {
180           dest->pw_dir = buffer;
181           strcpy (dest->pw_dir, src->pw_dir);
182           buffer += strlen (dest->pw_dir) + 1;
183           buflen = buflen - (strlen (dest->pw_dir) + 1);
184         }
185     }
186
187   if (src->pw_shell != NULL && strlen (src->pw_shell))
188     {
189       if (buffer == NULL)
190         dest->pw_shell = strdup (src->pw_shell);
191       else if (dest->pw_shell &&
192                strlen (dest->pw_shell) >= strlen (src->pw_shell))
193         strcpy (dest->pw_shell, src->pw_shell);
194       else
195         {
196           dest->pw_shell = buffer;
197           strcpy (dest->pw_shell, src->pw_shell);
198           buffer += strlen (dest->pw_shell) + 1;
199           buflen = buflen - (strlen (dest->pw_shell) + 1);
200         }
201     }
202 }
203
204 static enum nss_status
205 internal_setpwent (ent_t *ent, int stayopen)
206 {
207   enum nss_status status = NSS_STATUS_SUCCESS;
208
209   ent->first = ent->netgroup = FALSE;
210   ent->files = TRUE;
211
212   /* If something was left over free it.  */
213   if (ent->netgroup)
214     __internal_endnetgrent (&ent->netgrdata);
215
216   if (ent->blacklist.data != NULL)
217     {
218       ent->blacklist.current = 1;
219       ent->blacklist.data[0] = '|';
220       ent->blacklist.data[1] = '\0';
221     }
222   else
223     ent->blacklist.current = 0;
224
225   if (ent->stream == NULL)
226     {
227       ent->stream = fopen ("/etc/passwd", "rm");
228
229       if (ent->stream == NULL)
230         status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
231       else
232         {
233           /* We have to make sure the file is  `closed on exec'.  */
234           int result, flags;
235
236           result = flags = fcntl (fileno_unlocked (ent->stream), F_GETFD, 0);
237           if (result >= 0)
238             {
239               flags |= FD_CLOEXEC;
240               result = fcntl (fileno_unlocked (ent->stream), F_SETFD, flags);
241             }
242           if (result < 0)
243             {
244               /* Something went wrong.  Close the stream and return a
245                  failure.  */
246               fclose (ent->stream);
247               ent->stream = NULL;
248               status = NSS_STATUS_UNAVAIL;
249             }
250           else
251             /* We take care of locking ourself.  */
252             __fsetlocking (ent->stream, FSETLOCKING_BYCALLER);
253         }
254     }
255   else
256     rewind (ent->stream);
257
258   give_pwd_free (&ent->pwd);
259
260   if (status == NSS_STATUS_SUCCESS && nss_setpwent)
261     return nss_setpwent (stayopen);
262
263   return status;
264 }
265
266
267 enum nss_status
268 _nss_compat_setpwent (int stayopen)
269 {
270   enum nss_status result;
271
272   __libc_lock_lock (lock);
273
274   if (ni == NULL)
275     init_nss_interface ();
276
277   result = internal_setpwent (&ext_ent, stayopen);
278
279   __libc_lock_unlock (lock);
280
281   return result;
282 }
283
284
285 static enum nss_status
286 internal_endpwent (ent_t *ent)
287 {
288   if (nss_endpwent)
289     nss_endpwent ();
290
291   if (ent->stream != NULL)
292     {
293       fclose (ent->stream);
294       ent->stream = NULL;
295     }
296
297   if (ent->netgroup)
298     __internal_endnetgrent (&ent->netgrdata);
299
300   ent->first = ent->netgroup = FALSE;
301
302   if (ent->blacklist.data != NULL)
303     {
304       ent->blacklist.current = 1;
305       ent->blacklist.data[0] = '|';
306       ent->blacklist.data[1] = '\0';
307     }
308   else
309     ent->blacklist.current = 0;
310
311   give_pwd_free (&ent->pwd);
312
313   return NSS_STATUS_SUCCESS;
314 }
315
316 enum nss_status
317 _nss_compat_endpwent (void)
318 {
319   enum nss_status result;
320
321   __libc_lock_lock (lock);
322
323   result = internal_endpwent (&ext_ent);
324
325   __libc_lock_unlock (lock);
326
327   return result;
328 }
329
330
331 static enum nss_status
332 getpwent_next_nss_netgr (const char *name, struct passwd *result, ent_t *ent,
333                          char *group, char *buffer, size_t buflen,
334                          int *errnop)
335 {
336   char *curdomain, *host, *user, *domain, *p2;
337   int status;
338   size_t p2len;
339
340   /* Leave function if NSS module does not support getpwnam_r,
341      we need this function here.  */
342   if (!nss_getpwnam_r)
343     return NSS_STATUS_UNAVAIL;
344
345   if (yp_get_default_domain (&curdomain) != YPERR_SUCCESS)
346     {
347       ent->netgroup = FALSE;
348       ent->first = FALSE;
349       give_pwd_free (&ent->pwd);
350       return NSS_STATUS_UNAVAIL;
351     }
352
353   if (ent->first == TRUE)
354     {
355       memset (&ent->netgrdata, 0, sizeof (struct __netgrent));
356       __internal_setnetgrent (group, &ent->netgrdata);
357       ent->first = FALSE;
358     }
359
360   while (1)
361     {
362       char *saved_cursor;
363
364       saved_cursor = ent->netgrdata.cursor;
365       status = __internal_getnetgrent_r (&host, &user, &domain,
366                                          &ent->netgrdata, buffer, buflen,
367                                          errnop);
368       if (status != 1)
369         {
370           __internal_endnetgrent (&ent->netgrdata);
371           ent->netgroup = 0;
372           give_pwd_free (&ent->pwd);
373           return NSS_STATUS_RETURN;
374         }
375
376       if (user == NULL || user[0] == '-')
377         continue;
378
379       if (domain != NULL && strcmp (curdomain, domain) != 0)
380         continue;
381
382       /* If name != NULL, we are called from getpwnam.  */
383       if (name != NULL)
384         if (strcmp (user, name) != 0)
385           continue;
386
387       p2len = pwd_need_buflen (&ent->pwd);
388       if (p2len > buflen)
389         {
390           *errnop = ERANGE;
391           return NSS_STATUS_TRYAGAIN;
392         }
393       p2 = buffer + (buflen - p2len);
394       buflen -= p2len;
395
396       if (nss_getpwnam_r (user, result, buffer, buflen, errnop) !=
397           NSS_STATUS_SUCCESS)
398         continue;
399
400       if (!in_blacklist (result->pw_name, strlen (result->pw_name), ent))
401         {
402           /* Store the User in the blacklist for possible the "+" at the
403              end of /etc/passwd */
404           blacklist_store_name (result->pw_name, ent);
405           copy_pwd_changes (result, &ent->pwd, p2, p2len);
406           break;
407         }
408     }
409
410   return NSS_STATUS_SUCCESS;
411 }
412
413 /* get the next user from NSS  (+ entry) */
414 static enum nss_status
415 getpwent_next_nss (struct passwd *result, ent_t *ent, char *buffer,
416                    size_t buflen, int *errnop)
417 {
418   enum nss_status status;
419   char *p2;
420   size_t p2len;
421
422   /* Return if NSS module does not support getpwent_r.  */
423   if (!nss_getpwent_r)
424     return NSS_STATUS_UNAVAIL;
425
426   p2len = pwd_need_buflen (&ent->pwd);
427   if (p2len > buflen)
428     {
429       *errnop = ERANGE;
430       return NSS_STATUS_TRYAGAIN;
431     }
432   p2 = buffer + (buflen - p2len);
433   buflen -= p2len;
434
435   if (ent->first)
436     ent->first = FALSE;
437
438   do
439     {
440       if ((status = nss_getpwent_r (result, buffer, buflen, errnop)) !=
441           NSS_STATUS_SUCCESS)
442         return status;
443     }
444   while (in_blacklist (result->pw_name, strlen (result->pw_name), ent));
445
446   copy_pwd_changes (result, &ent->pwd, p2, p2len);
447
448   return NSS_STATUS_SUCCESS;
449 }
450
451 /* This function handle the +user entrys in /etc/passwd */
452 static enum nss_status
453 getpwnam_plususer (const char *name, struct passwd *result, ent_t *ent,
454                    char *buffer, size_t buflen, int *errnop)
455 {
456   struct passwd pwd;
457   char *p;
458   size_t plen;
459
460   if (!nss_getpwnam_r)
461     return NSS_STATUS_UNAVAIL;
462
463   memset (&pwd, '\0', sizeof (struct passwd));
464
465   copy_pwd_changes (&pwd, result, NULL, 0);
466
467   plen = pwd_need_buflen (&pwd);
468   if (plen > buflen)
469     {
470       *errnop = ERANGE;
471       return NSS_STATUS_TRYAGAIN;
472     }
473   p = buffer + (buflen - plen);
474   buflen -= plen;
475
476   if (nss_getpwnam_r (name, result, buffer, buflen, errnop) !=
477       NSS_STATUS_SUCCESS)
478     return NSS_STATUS_NOTFOUND;
479
480   if (in_blacklist (result->pw_name, strlen (result->pw_name), ent))
481     return NSS_STATUS_NOTFOUND;
482
483   copy_pwd_changes (result, &pwd, p, plen);
484   give_pwd_free (&pwd);
485   /* We found the entry.  */
486   return NSS_STATUS_SUCCESS;
487 }
488
489 static enum nss_status
490 getpwent_next_file (struct passwd *result, ent_t *ent,
491                     char *buffer, size_t buflen, int *errnop)
492 {
493   struct parser_data *data = (void *) buffer;
494   while (1)
495     {
496       fpos_t pos;
497       char *p;
498       int parse_res;
499
500       do
501         {
502           fgetpos (ent->stream, &pos);
503           buffer[buflen - 1] = '\xff';
504           p = fgets_unlocked (buffer, buflen, ent->stream);
505           if (p == NULL && feof_unlocked (ent->stream))
506             return NSS_STATUS_NOTFOUND;
507
508           if (p == NULL || buffer[buflen - 1] != '\xff')
509             {
510               fsetpos (ent->stream, &pos);
511               *errnop = ERANGE;
512               return NSS_STATUS_TRYAGAIN;
513             }
514
515           /* Terminate the line for any case.  */
516           buffer[buflen - 1] = '\0';
517
518           /* Skip leading blanks.  */
519           while (isspace (*p))
520             ++p;
521         }
522       while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines.  */
523              /* Parse the line.  If it is invalid, loop to
524                 get the next line of the file to parse.  */
525              !(parse_res = _nss_files_parse_pwent (p, result, data, buflen,
526                                                    errnop)));
527
528       if (parse_res == -1)
529         {
530           /* The parser ran out of space.  */
531           fsetpos (ent->stream, &pos);
532           *errnop = ERANGE;
533           return NSS_STATUS_TRYAGAIN;
534         }
535
536       if (result->pw_name[0] != '+' && result->pw_name[0] != '-')
537         /* This is a real entry.  */
538         break;
539
540       /* -@netgroup */
541       if (result->pw_name[0] == '-' && result->pw_name[1] == '@'
542           && result->pw_name[2] != '\0')
543         {
544           /* XXX Do not use fixed length buffer.  */
545           char buf2[1024];
546           char *user, *host, *domain;
547           struct __netgrent netgrdata;
548
549           bzero (&netgrdata, sizeof (struct __netgrent));
550           __internal_setnetgrent (&result->pw_name[2], &netgrdata);
551           while (__internal_getnetgrent_r (&host, &user, &domain, &netgrdata,
552                                            buf2, sizeof (buf2), errnop))
553             {
554               if (user != NULL && user[0] != '-')
555                 blacklist_store_name (user, ent);
556             }
557           __internal_endnetgrent (&netgrdata);
558           continue;
559         }
560
561       /* +@netgroup */
562       if (result->pw_name[0] == '+' && result->pw_name[1] == '@'
563           && result->pw_name[2] != '\0')
564         {
565           enum nss_status status;
566
567           ent->netgroup = TRUE;
568           ent->first = TRUE;
569           copy_pwd_changes (&ent->pwd, result, NULL, 0);
570
571           status = getpwent_next_nss_netgr (NULL, result, ent,
572                                             &result->pw_name[2],
573                                             buffer, buflen, errnop);
574           if (status == NSS_STATUS_RETURN)
575             continue;
576           else
577             return status;
578         }
579
580       /* -user */
581       if (result->pw_name[0] == '-' && result->pw_name[1] != '\0'
582           && result->pw_name[1] != '@')
583         {
584           blacklist_store_name (&result->pw_name[1], ent);
585           continue;
586         }
587
588       /* +user */
589       if (result->pw_name[0] == '+' && result->pw_name[1] != '\0'
590           && result->pw_name[1] != '@')
591         {
592           size_t len = strlen (result->pw_name);
593           char buf[len];
594           enum nss_status status;
595
596           /* Store the User in the blacklist for the "+" at the end of
597              /etc/passwd */
598           memcpy (buf, &result->pw_name[1], len);
599           status = getpwnam_plususer (&result->pw_name[1], result, ent,
600                                       buffer, buflen, errnop);
601           blacklist_store_name (buf, ent);
602
603           if (status == NSS_STATUS_SUCCESS)     /* We found the entry. */
604             break;
605           else if (status == NSS_STATUS_RETURN  /* We couldn't parse the entry */
606                    || status == NSS_STATUS_NOTFOUND)    /* entry doesn't exist */
607             continue;
608           else
609             {
610               if (status == NSS_STATUS_TRYAGAIN)
611                 {
612                   /* The parser ran out of space */
613                   fsetpos (ent->stream, &pos);
614                   *errnop = ERANGE;
615                 }
616               return status;
617             }
618         }
619
620       /* +:... */
621       if (result->pw_name[0] == '+' && result->pw_name[1] == '\0')
622         {
623           ent->files = FALSE;
624           ent->first = TRUE;
625           copy_pwd_changes (&ent->pwd, result, NULL, 0);
626
627           return getpwent_next_nss (result, ent, buffer, buflen, errnop);
628         }
629     }
630
631   return NSS_STATUS_SUCCESS;
632 }
633
634
635 static enum nss_status
636 internal_getpwent_r (struct passwd *pw, ent_t *ent, char *buffer,
637                      size_t buflen, int *errnop)
638 {
639   if (ent->netgroup)
640     {
641       enum nss_status status;
642
643       /* We are searching members in a netgroup */
644       /* Since this is not the first call, we don't need the group name */
645       status = getpwent_next_nss_netgr (NULL, pw, ent, NULL, buffer, buflen,
646                                         errnop);
647       if (status == NSS_STATUS_RETURN)
648         return getpwent_next_file (pw, ent, buffer, buflen, errnop);
649       else
650         return status;
651     }
652   else if (ent->files)
653     return getpwent_next_file (pw, ent, buffer, buflen, errnop);
654   else
655     return getpwent_next_nss (pw, ent, buffer, buflen, errnop);
656
657 }
658
659 enum nss_status
660 _nss_compat_getpwent_r (struct passwd *pwd, char *buffer, size_t buflen,
661                         int *errnop)
662 {
663   enum nss_status result = NSS_STATUS_SUCCESS;
664
665   __libc_lock_lock (lock);
666
667   /* Be prepared that the setpwent function was not called before.  */
668   if (ni == NULL)
669     init_nss_interface ();
670
671   if (ext_ent.stream == NULL)
672     result = internal_setpwent (&ext_ent, 1);
673
674   if (result == NSS_STATUS_SUCCESS)
675     result = internal_getpwent_r (pwd, &ext_ent, buffer, buflen, errnop);
676
677   __libc_lock_unlock (lock);
678
679   return result;
680 }
681
682 /* Searches in /etc/passwd and the NIS/NIS+ map for a special user */
683 static enum nss_status
684 internal_getpwnam_r (const char *name, struct passwd *result, ent_t *ent,
685                      char *buffer, size_t buflen, int *errnop)
686 {
687   struct parser_data *data = (void *) buffer;
688
689   while (1)
690     {
691       fpos_t pos;
692       char *p;
693       int parse_res;
694
695       do
696         {
697           fgetpos (ent->stream, &pos);
698           buffer[buflen - 1] = '\xff';
699           p = fgets_unlocked (buffer, buflen, ent->stream);
700           if (p == NULL && feof_unlocked (ent->stream))
701             {
702               return NSS_STATUS_NOTFOUND;
703             }
704           if (p == NULL || buffer[buflen - 1] != '\xff')
705             {
706               fsetpos (ent->stream, &pos);
707               *errnop = ERANGE;
708               return NSS_STATUS_TRYAGAIN;
709             }
710
711           /* Terminate the line for any case.  */
712           buffer[buflen - 1] = '\0';
713
714           /* Skip leading blanks.  */
715           while (isspace (*p))
716             ++p;
717         }
718       while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines.  */
719              /* Parse the line.  If it is invalid, loop to
720                 get the next line of the file to parse.  */
721              !(parse_res = _nss_files_parse_pwent (p, result, data, buflen,
722                                                    errnop)));
723
724       if (parse_res == -1)
725         {
726           /* The parser ran out of space.  */
727           fsetpos (ent->stream, &pos);
728           *errnop = ERANGE;
729           return NSS_STATUS_TRYAGAIN;
730         }
731
732       /* This is a real entry.  */
733       if (result->pw_name[0] != '+' && result->pw_name[0] != '-')
734         {
735           if (strcmp (result->pw_name, name) == 0)
736             return NSS_STATUS_SUCCESS;
737           else
738             continue;
739         }
740
741       /* -@netgroup */
742       if (result->pw_name[0] == '-' && result->pw_name[1] == '@'
743           && result->pw_name[2] != '\0')
744         {
745           if (innetgr (&result->pw_name[2], NULL, name, NULL))
746             return NSS_STATUS_NOTFOUND;
747           continue;
748         }
749
750       /* +@netgroup */
751       if (result->pw_name[0] == '+' && result->pw_name[1] == '@'
752           && result->pw_name[2] != '\0')
753         {
754           enum nss_status status;
755
756           if (innetgr (&result->pw_name[2], NULL, name, NULL))
757             {
758               status = getpwnam_plususer (name, result, ent, buffer,
759                                           buflen, errnop);
760
761               if (status == NSS_STATUS_RETURN)
762                 continue;
763
764               return status;
765             }
766           continue;
767         }
768
769       /* -user */
770       if (result->pw_name[0] == '-' && result->pw_name[1] != '\0'
771           && result->pw_name[1] != '@')
772         {
773           if (strcmp (&result->pw_name[1], name) == 0)
774             return NSS_STATUS_NOTFOUND;
775           else
776             continue;
777         }
778
779       /* +user */
780       if (result->pw_name[0] == '+' && result->pw_name[1] != '\0'
781           && result->pw_name[1] != '@')
782         {
783           if (strcmp (name, &result->pw_name[1]) == 0)
784             {
785               enum nss_status status;
786
787               status = getpwnam_plususer (name, result, ent, buffer, buflen,
788                                           errnop);
789               if (status == NSS_STATUS_RETURN)
790                 /* We couldn't parse the entry */
791                 return NSS_STATUS_NOTFOUND;
792               else
793                 return status;
794             }
795         }
796
797       /* +:... */
798       if (result->pw_name[0] == '+' && result->pw_name[1] == '\0')
799         {
800           enum nss_status status;
801
802           status = getpwnam_plususer (name, result, ent,
803                                       buffer, buflen, errnop);
804           if (status == NSS_STATUS_SUCCESS)     /* We found the entry. */
805             break;
806           else if (status == NSS_STATUS_RETURN) /* We couldn't parse the entry */
807             return NSS_STATUS_NOTFOUND;
808           else
809             return status;
810         }
811     }
812   return NSS_STATUS_SUCCESS;
813 }
814
815 enum nss_status
816 _nss_compat_getpwnam_r (const char *name, struct passwd *pwd,
817                         char *buffer, size_t buflen, int *errnop)
818 {
819   enum nss_status result;
820   ent_t ent = {0, 0, TRUE, NULL, {NULL, 0, 0},
821                {NULL, NULL, 0, 0, NULL, NULL, NULL}};
822
823   if (name[0] == '-' || name[0] == '+')
824     return NSS_STATUS_NOTFOUND;
825
826   __libc_lock_lock (lock);
827
828   if (ni == NULL)
829     init_nss_interface ();
830
831   __libc_lock_unlock (lock);
832
833   result = internal_setpwent (&ent, 0);
834
835   if (result == NSS_STATUS_SUCCESS)
836     result = internal_getpwnam_r (name, pwd, &ent, buffer, buflen, errnop);
837
838   internal_endpwent (&ent);
839
840   return result;
841 }
842
843 /* This function handle the + entry in /etc/passwd for getpwuid */
844 static enum nss_status
845 getpwuid_plususer (uid_t uid, struct passwd *result, char *buffer,
846                    size_t buflen, int *errnop)
847 {
848   struct passwd pwd;
849   char *p;
850   size_t plen;
851
852   if (!nss_getpwuid_r)
853     return NSS_STATUS_UNAVAIL;
854
855   memset (&pwd, '\0', sizeof (struct passwd));
856
857   copy_pwd_changes (&pwd, result, NULL, 0);
858
859   plen = pwd_need_buflen (&pwd);
860   if (plen > buflen)
861     {
862       *errnop = ERANGE;
863       return NSS_STATUS_TRYAGAIN;
864     }
865   p = buffer + (buflen - plen);
866   buflen -= plen;
867
868   if (nss_getpwuid_r (uid, result, buffer, buflen, errnop) ==
869       NSS_STATUS_SUCCESS)
870     {
871       copy_pwd_changes (result, &pwd, p, plen);
872       give_pwd_free (&pwd);
873       /* We found the entry.  */
874       return NSS_STATUS_SUCCESS;
875     }
876   else
877     {
878       /* Give buffer the old len back */
879       buflen += plen;
880       give_pwd_free (&pwd);
881     }
882   return NSS_STATUS_RETURN;
883 }
884
885 /* Searches in /etc/passwd and the NSS subsystem for a special user id */
886 static enum nss_status
887 internal_getpwuid_r (uid_t uid, struct passwd *result, ent_t *ent,
888                      char *buffer, size_t buflen, int *errnop)
889 {
890   struct parser_data *data = (void *) buffer;
891
892   while (1)
893     {
894       fpos_t pos;
895       char *p;
896       int parse_res;
897
898       do
899         {
900           fgetpos (ent->stream, &pos);
901           buffer[buflen - 1] = '\xff';
902           p = fgets_unlocked (buffer, buflen, ent->stream);
903           if (p == NULL && feof_unlocked (ent->stream))
904             return NSS_STATUS_NOTFOUND;
905
906           if (p == NULL || buffer[buflen - 1] != '\xff')
907             {
908               fsetpos (ent->stream, &pos);
909               *errnop = ERANGE;
910               return NSS_STATUS_TRYAGAIN;
911             }
912
913           /* Terminate the line for any case.  */
914           buffer[buflen - 1] = '\0';
915
916           /* Skip leading blanks.  */
917           while (isspace (*p))
918             ++p;
919         }
920       while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines.  */
921              /* Parse the line.  If it is invalid, loop to
922                 get the next line of the file to parse.  */
923              !(parse_res = _nss_files_parse_pwent (p, result, data, buflen,
924                                                    errnop)));
925
926       if (parse_res == -1)
927         {
928           /* The parser ran out of space.  */
929           fsetpos (ent->stream, &pos);
930           *errnop = ERANGE;
931           return NSS_STATUS_TRYAGAIN;
932         }
933
934       /* This is a real entry.  */
935       if (result->pw_name[0] != '+' && result->pw_name[0] != '-')
936         {
937           if (result->pw_uid == uid)
938             return NSS_STATUS_SUCCESS;
939           else
940             continue;
941         }
942
943       /* -@netgroup */
944       if (result->pw_name[0] == '-' && result->pw_name[1] == '@'
945           && result->pw_name[2] != '\0')
946         {
947           /* -1, because we remove first two character of pw_name.  */
948           size_t len = strlen (result->pw_name) - 1;
949           char buf[len];
950           enum nss_status status;
951
952           memcpy (buf, &result->pw_name[2], len);
953
954           status = getpwuid_plususer (uid, result, buffer, buflen, errnop);
955           if (status == NSS_STATUS_SUCCESS &&
956               innetgr (buf, NULL, result->pw_name, NULL))
957             return NSS_STATUS_NOTFOUND;
958
959           continue;
960         }
961
962       /* +@netgroup */
963       if (result->pw_name[0] == '+' && result->pw_name[1] == '@'
964           && result->pw_name[2] != '\0')
965         {
966           /* -1, because we remove first two characters of pw_name.  */
967           size_t len = strlen (result->pw_name) - 1;
968           char buf[len];
969           enum nss_status status;
970
971           memcpy (buf, &result->pw_name[2], len);
972
973           status = getpwuid_plususer (uid, result, buffer, buflen, errnop);
974
975           if (status == NSS_STATUS_RETURN)
976             continue;
977
978           if (status == NSS_STATUS_SUCCESS)
979             {
980               if (innetgr (buf, NULL, result->pw_name, NULL))
981                 return NSS_STATUS_SUCCESS;
982             }
983           else if (status == NSS_STATUS_RETURN) /* We couldn't parse the entry */
984             return NSS_STATUS_NOTFOUND;
985           else
986             return status;
987
988           continue;
989         }
990
991       /* -user */
992       if (result->pw_name[0] == '-' && result->pw_name[1] != '\0'
993           && result->pw_name[1] != '@')
994         {
995           size_t len = strlen (result->pw_name);
996           char buf[len];
997           enum nss_status status;
998
999           memcpy (buf, &result->pw_name[1], len);
1000
1001           status = getpwuid_plususer (uid, result, buffer, buflen, errnop);
1002           if (status == NSS_STATUS_SUCCESS &&
1003               innetgr (buf, NULL, result->pw_name, NULL))
1004             return NSS_STATUS_NOTFOUND;
1005           continue;
1006         }
1007
1008       /* +user */
1009       if (result->pw_name[0] == '+' && result->pw_name[1] != '\0'
1010           && result->pw_name[1] != '@')
1011         {
1012           size_t len = strlen (result->pw_name);
1013           char buf[len];
1014           enum nss_status status;
1015
1016           memcpy (buf, &result->pw_name[1], len);
1017
1018           status = getpwuid_plususer (uid, result, buffer, buflen, errnop);
1019
1020           if (status == NSS_STATUS_RETURN)
1021             continue;
1022
1023           if (status == NSS_STATUS_SUCCESS)
1024             {
1025               if (strcmp (buf, result->pw_name) == 0)
1026                 return NSS_STATUS_SUCCESS;
1027             }
1028           else if (status == NSS_STATUS_RETURN) /* We couldn't parse the entry */
1029             return NSS_STATUS_NOTFOUND;
1030           else
1031             return status;
1032
1033           continue;
1034         }
1035
1036       /* +:... */
1037       if (result->pw_name[0] == '+' && result->pw_name[1] == '\0')
1038         {
1039           enum nss_status status;
1040
1041           status = getpwuid_plususer (uid, result, buffer, buflen, errnop);
1042           if (status == NSS_STATUS_SUCCESS)     /* We found the entry. */
1043             break;
1044           else if (status == NSS_STATUS_RETURN) /* We couldn't parse the entry */
1045             return NSS_STATUS_NOTFOUND;
1046           else
1047             return status;
1048         }
1049     }
1050   return NSS_STATUS_SUCCESS;
1051 }
1052
1053 enum nss_status
1054 _nss_compat_getpwuid_r (uid_t uid, struct passwd *pwd,
1055                         char *buffer, size_t buflen, int *errnop)
1056 {
1057   enum nss_status result;
1058   ent_t ent = {0, 0, TRUE, NULL, {NULL, 0, 0},
1059                {NULL, NULL, 0, 0, NULL, NULL, NULL}};
1060
1061   __libc_lock_lock (lock);
1062
1063   if (ni == NULL)
1064     init_nss_interface ();
1065
1066   __libc_lock_unlock (lock);
1067
1068   result = internal_setpwent (&ent, 0);
1069
1070   if (result == NSS_STATUS_SUCCESS)
1071     result = internal_getpwuid_r (uid, pwd, &ent, buffer, buflen, errnop);
1072
1073   internal_endpwent (&ent);
1074
1075   return result;
1076 }
1077
1078
1079 /* Support routines for remembering -@netgroup and -user entries.
1080    The names are stored in a single string with `|' as separator. */
1081 static void
1082 blacklist_store_name (const char *name, ent_t *ent)
1083 {
1084   int namelen = strlen (name);
1085   char *tmp;
1086
1087   /* first call, setup cache */
1088   if (ent->blacklist.size == 0)
1089     {
1090       ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
1091       ent->blacklist.data = malloc (ent->blacklist.size);
1092       if (ent->blacklist.data == NULL)
1093         return;
1094       ent->blacklist.data[0] = '|';
1095       ent->blacklist.data[1] = '\0';
1096       ent->blacklist.current = 1;
1097     }
1098   else
1099     {
1100       if (in_blacklist (name, namelen, ent))
1101         return;                 /* no duplicates */
1102
1103       if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
1104         {
1105           ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
1106           tmp = realloc (ent->blacklist.data, ent->blacklist.size);
1107           if (tmp == NULL)
1108             {
1109               free (ent->blacklist.data);
1110               ent->blacklist.size = 0;
1111               return;
1112             }
1113           ent->blacklist.data = tmp;
1114         }
1115     }
1116
1117   tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
1118   *tmp++ = '|';
1119   *tmp = '\0';
1120   ent->blacklist.current += namelen + 1;
1121
1122   return;
1123 }
1124
1125 /* returns TRUE if ent->blacklist contains name, else FALSE */
1126 static bool_t
1127 in_blacklist (const char *name, int namelen, ent_t *ent)
1128 {
1129   char buf[namelen + 3];
1130   char *cp;
1131
1132   if (ent->blacklist.data == NULL)
1133     return FALSE;
1134
1135   buf[0] = '|';
1136   cp = stpcpy (&buf[1], name);
1137   *cp++ = '|';
1138   *cp = '\0';
1139   return strstr (ent->blacklist.data, buf) != NULL;
1140 }