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