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