Prefer https to http for gnu.org and fsf.org URLs
[platform/upstream/glibc.git] / nss / nss_compat / compat-spwd.c
1 /* Copyright (C) 1996-2019 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    <https://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 <shadow.h>
26 #include <stdio_ext.h>
27 #include <string.h>
28 #include <libc-lock.h>
29 #include <kernel-features.h>
30
31 #include "netgroup.h"
32 #include "nisdomain.h"
33
34 static service_user *ni;
35 static enum nss_status (*nss_setspent) (int stayopen);
36 static enum nss_status (*nss_getspnam_r) (const char *name, struct spwd * sp,
37                                           char *buffer, size_t buflen,
38                                           int *errnop);
39 static enum nss_status (*nss_getspent_r) (struct spwd * sp, char *buffer,
40                                           size_t buflen, int *errnop);
41 static enum nss_status (*nss_endspent) (void);
42
43 /* Get the declaration of the parser function.  */
44 #define ENTNAME spent
45 #define STRUCTURE spwd
46 #define EXTERN_PARSER
47 #include <nss/nss_files/files-parse.c>
48
49 /* Structure for remembering -@netgroup and -user members ... */
50 #define BLACKLIST_INITIAL_SIZE 512
51 #define BLACKLIST_INCREMENT 256
52 struct blacklist_t
53 {
54   char *data;
55   int current;
56   int size;
57 };
58
59 struct ent_t
60 {
61   bool netgroup;
62   bool files;
63   bool first;
64   enum nss_status setent_status;
65   FILE *stream;
66   struct blacklist_t blacklist;
67   struct spwd pwd;
68   struct __netgrent netgrdata;
69 };
70 typedef struct ent_t ent_t;
71
72 static ent_t ext_ent = { false, true, false, NSS_STATUS_SUCCESS, NULL,
73                          { NULL, 0, 0},
74                          { NULL, NULL, 0, 0, 0, 0, 0, 0, 0}};
75
76 /* Protect global state against multiple changers.  */
77 __libc_lock_define_initialized (static, lock)
78
79 /* Prototypes for local functions.  */
80 static void blacklist_store_name (const char *, ent_t *);
81 static bool in_blacklist (const char *, int, ent_t *);
82
83 /* Initialize the NSS interface/functions. The calling function must
84    hold the lock.  */
85 static void
86 init_nss_interface (void)
87 {
88   if (__nss_database_lookup2 ("shadow_compat", "passwd_compat",
89                               "nis", &ni) >= 0)
90     {
91       nss_setspent = __nss_lookup_function (ni, "setspent");
92       nss_getspnam_r = __nss_lookup_function (ni, "getspnam_r");
93       nss_getspent_r = __nss_lookup_function (ni, "getspent_r");
94       nss_endspent = __nss_lookup_function (ni, "endspent");
95     }
96 }
97
98 static void
99 give_spwd_free (struct spwd *pwd)
100 {
101   free (pwd->sp_namp);
102   free (pwd->sp_pwdp);
103
104   memset (pwd, '\0', sizeof (struct spwd));
105   pwd->sp_warn = -1;
106   pwd->sp_inact = -1;
107   pwd->sp_expire = -1;
108   pwd->sp_flag = ~0ul;
109 }
110
111 static int
112 spwd_need_buflen (struct spwd *pwd)
113 {
114   int len = 0;
115
116   if (pwd->sp_pwdp != NULL)
117     len += strlen (pwd->sp_pwdp) + 1;
118
119   return len;
120 }
121
122 static void
123 copy_spwd_changes (struct spwd *dest, struct spwd *src,
124                    char *buffer, size_t buflen)
125 {
126   if (src->sp_pwdp != NULL && strlen (src->sp_pwdp))
127     {
128       if (buffer == NULL)
129         dest->sp_pwdp = strdup (src->sp_pwdp);
130       else if (dest->sp_pwdp
131                && strlen (dest->sp_pwdp) >= strlen (src->sp_pwdp))
132         strcpy (dest->sp_pwdp, src->sp_pwdp);
133       else
134         {
135           dest->sp_pwdp = buffer;
136           strcpy (dest->sp_pwdp, src->sp_pwdp);
137           buffer += strlen (dest->sp_pwdp) + 1;
138           buflen = buflen - (strlen (dest->sp_pwdp) + 1);
139         }
140     }
141   if (src->sp_lstchg != 0)
142     dest->sp_lstchg = src->sp_lstchg;
143   if (src->sp_min != 0)
144     dest->sp_min = src->sp_min;
145   if (src->sp_max != 0)
146     dest->sp_max = src->sp_max;
147   if (src->sp_warn != -1)
148     dest->sp_warn = src->sp_warn;
149   if (src->sp_inact != -1)
150     dest->sp_inact = src->sp_inact;
151   if (src->sp_expire != -1)
152     dest->sp_expire = src->sp_expire;
153   if (src->sp_flag != ~0ul)
154     dest->sp_flag = src->sp_flag;
155 }
156
157 static enum nss_status
158 internal_setspent (ent_t *ent, int stayopen, int needent)
159 {
160   enum nss_status status = NSS_STATUS_SUCCESS;
161
162   ent->first = ent->netgroup = 0;
163   ent->files = true;
164
165   /* If something was left over free it.  */
166   if (ent->netgroup)
167     __internal_endnetgrent (&ent->netgrdata);
168
169   if (ent->blacklist.data != NULL)
170     {
171       ent->blacklist.current = 1;
172       ent->blacklist.data[0] = '|';
173       ent->blacklist.data[1] = '\0';
174     }
175   else
176     ent->blacklist.current = 0;
177
178   if (ent->stream == NULL)
179     {
180       ent->stream = fopen ("/etc/shadow", "rme");
181
182       if (ent->stream == NULL)
183         status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
184       else
185         /* We take care of locking ourself.  */
186         __fsetlocking (ent->stream, FSETLOCKING_BYCALLER);
187     }
188   else
189     rewind (ent->stream);
190
191   give_spwd_free (&ent->pwd);
192
193   if (needent && status == NSS_STATUS_SUCCESS && nss_setspent)
194     ent->setent_status = nss_setspent (stayopen);
195
196   return status;
197 }
198
199
200 enum nss_status
201 _nss_compat_setspent (int stayopen)
202 {
203   enum nss_status result;
204
205   __libc_lock_lock (lock);
206
207   if (ni == NULL)
208     init_nss_interface ();
209
210   result = internal_setspent (&ext_ent, stayopen, 1);
211
212   __libc_lock_unlock (lock);
213
214   return result;
215 }
216
217
218 static enum nss_status
219 internal_endspent (ent_t *ent)
220 {
221   if (ent->stream != NULL)
222     {
223       fclose (ent->stream);
224       ent->stream = NULL;
225     }
226
227   if (ent->netgroup)
228     __internal_endnetgrent (&ent->netgrdata);
229
230   ent->first = ent->netgroup = false;
231   ent->files = true;
232
233   if (ent->blacklist.data != NULL)
234     {
235       ent->blacklist.current = 1;
236       ent->blacklist.data[0] = '|';
237       ent->blacklist.data[1] = '\0';
238     }
239   else
240     ent->blacklist.current = 0;
241
242   give_spwd_free (&ent->pwd);
243
244   return NSS_STATUS_SUCCESS;
245 }
246
247 enum nss_status
248 _nss_compat_endspent (void)
249 {
250   enum nss_status result;
251
252   __libc_lock_lock (lock);
253
254   if (nss_endspent)
255     nss_endspent ();
256
257   result = internal_endspent (&ext_ent);
258
259   __libc_lock_unlock (lock);
260
261   return result;
262 }
263
264
265 static enum nss_status
266 getspent_next_nss_netgr (const char *name, struct spwd *result, ent_t *ent,
267                          char *group, char *buffer, size_t buflen,
268                          int *errnop)
269 {
270   char *curdomain = NULL, *host, *user, *domain, *p2;
271   size_t p2len;
272
273   if (!nss_getspnam_r)
274     return NSS_STATUS_UNAVAIL;
275
276   /* If the setpwent call failed, say so.  */
277   if (ent->setent_status != NSS_STATUS_SUCCESS)
278     return ent->setent_status;
279
280   if (ent->first)
281     {
282       memset (&ent->netgrdata, 0, sizeof (struct __netgrent));
283       __internal_setnetgrent (group, &ent->netgrdata);
284       ent->first = false;
285     }
286
287   while (1)
288     {
289       enum nss_status status;
290
291       status = __internal_getnetgrent_r (&host, &user, &domain,
292                                          &ent->netgrdata, buffer, buflen,
293                                          errnop);
294       if (status != 1)
295         {
296           __internal_endnetgrent (&ent->netgrdata);
297           ent->netgroup = false;
298           give_spwd_free (&ent->pwd);
299           return NSS_STATUS_RETURN;
300         }
301
302       if (user == NULL || user[0] == '-')
303         continue;
304
305       if (domain != NULL)
306         {
307           if (curdomain == NULL
308               && __nss_get_default_domain (&curdomain) != 0)
309             {
310               __internal_endnetgrent (&ent->netgrdata);
311               ent->netgroup = false;
312               give_spwd_free (&ent->pwd);
313               return NSS_STATUS_UNAVAIL;
314             }
315           if (strcmp (curdomain, domain) != 0)
316             continue;
317         }
318
319       /* If name != NULL, we are called from getpwnam */
320       if (name != NULL)
321         if (strcmp (user, name) != 0)
322           continue;
323
324       p2len = spwd_need_buflen (&ent->pwd);
325       if (p2len > buflen)
326         {
327           *errnop = ERANGE;
328           return NSS_STATUS_TRYAGAIN;
329         }
330       p2 = buffer + (buflen - p2len);
331       buflen -= p2len;
332
333       if (nss_getspnam_r (user, result, buffer, buflen, errnop)
334           != NSS_STATUS_SUCCESS)
335         continue;
336
337       if (!in_blacklist (result->sp_namp, strlen (result->sp_namp), ent))
338         {
339           /* Store the User in the blacklist for possible the "+" at the
340              end of /etc/passwd */
341           blacklist_store_name (result->sp_namp, ent);
342           copy_spwd_changes (result, &ent->pwd, p2, p2len);
343           break;
344         }
345     }
346
347   return NSS_STATUS_SUCCESS;
348 }
349
350
351 static enum nss_status
352 getspent_next_nss (struct spwd *result, ent_t *ent,
353                    char *buffer, size_t buflen, int *errnop)
354 {
355   enum nss_status status;
356   char *p2;
357   size_t p2len;
358
359   if (!nss_getspent_r)
360     return NSS_STATUS_UNAVAIL;
361
362   p2len = spwd_need_buflen (&ent->pwd);
363   if (p2len > buflen)
364     {
365       *errnop = ERANGE;
366       return NSS_STATUS_TRYAGAIN;
367     }
368   p2 = buffer + (buflen - p2len);
369   buflen -= p2len;
370   do
371     {
372       if ((status = nss_getspent_r (result, buffer, buflen, errnop))
373           != NSS_STATUS_SUCCESS)
374         return status;
375     }
376   while (in_blacklist (result->sp_namp, strlen (result->sp_namp), ent));
377
378   copy_spwd_changes (result, &ent->pwd, p2, p2len);
379
380   return NSS_STATUS_SUCCESS;
381 }
382
383
384 /* This function handle the +user entrys in /etc/shadow */
385 static enum nss_status
386 getspnam_plususer (const char *name, struct spwd *result, ent_t *ent,
387                    char *buffer, size_t buflen, int *errnop)
388 {
389   if (!nss_getspnam_r)
390     return NSS_STATUS_UNAVAIL;
391
392   struct spwd pwd;
393   memset (&pwd, '\0', sizeof (struct spwd));
394   pwd.sp_warn = -1;
395   pwd.sp_inact = -1;
396   pwd.sp_expire = -1;
397   pwd.sp_flag = ~0ul;
398
399   copy_spwd_changes (&pwd, result, NULL, 0);
400
401   size_t plen = spwd_need_buflen (&pwd);
402   if (plen > buflen)
403     {
404       *errnop = ERANGE;
405       return NSS_STATUS_TRYAGAIN;
406     }
407   char *p = buffer + (buflen - plen);
408   buflen -= plen;
409
410   enum nss_status status = nss_getspnam_r (name, result, buffer, buflen,
411                                            errnop);
412   if (status != NSS_STATUS_SUCCESS)
413     return status;
414
415   if (in_blacklist (result->sp_namp, strlen (result->sp_namp), ent))
416     return NSS_STATUS_NOTFOUND;
417
418   copy_spwd_changes (result, &pwd, p, plen);
419   give_spwd_free (&pwd);
420   /* We found the entry.  */
421   return NSS_STATUS_SUCCESS;
422 }
423
424
425 static enum nss_status
426 getspent_next_file (struct spwd *result, ent_t *ent,
427                     char *buffer, size_t buflen, int *errnop)
428 {
429   struct parser_data *data = (void *) buffer;
430   while (1)
431     {
432       fpos_t pos;
433       int parse_res = 0;
434       char *p;
435
436       do
437         {
438           /* We need at least 3 characters for one line.  */
439           if (__glibc_unlikely (buflen < 3))
440             {
441             erange:
442               *errnop = ERANGE;
443               return NSS_STATUS_TRYAGAIN;
444             }
445
446           fgetpos (ent->stream, &pos);
447           buffer[buflen - 1] = '\xff';
448           p = fgets_unlocked (buffer, buflen, ent->stream);
449           if (p == NULL && feof_unlocked (ent->stream))
450             return NSS_STATUS_NOTFOUND;
451
452           if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
453             {
454             erange_reset:
455               fsetpos (ent->stream, &pos);
456               goto erange;
457             }
458
459           /* Skip leading blanks.  */
460           while (isspace (*p))
461             ++p;
462         }
463       while (*p == '\0' || *p == '#'    /* Ignore empty and comment lines.  */
464              /* Parse the line.  If it is invalid, loop to
465                 get the next line of the file to parse.  */
466              || !(parse_res = _nss_files_parse_spent (p, result, data,
467                                                       buflen, errnop)));
468
469       if (__glibc_unlikely (parse_res == -1))
470         /* The parser ran out of space.  */
471         goto erange_reset;
472
473       if (result->sp_namp[0] != '+' && result->sp_namp[0] != '-')
474         /* This is a real entry.  */
475         break;
476
477       /* -@netgroup */
478       if (result->sp_namp[0] == '-' && result->sp_namp[1] == '@'
479           && result->sp_namp[2] != '\0')
480         {
481           /* XXX Do not use fixed length buffers.  */
482           char buf2[1024];
483           char *user, *host, *domain;
484           struct __netgrent netgrdata;
485
486           memset (&netgrdata, 0, sizeof (struct __netgrent));
487           __internal_setnetgrent (&result->sp_namp[2], &netgrdata);
488           while (__internal_getnetgrent_r (&host, &user, &domain,
489                                            &netgrdata, buf2, sizeof (buf2),
490                                            errnop))
491             {
492               if (user != NULL && user[0] != '-')
493                 blacklist_store_name (user, ent);
494             }
495           __internal_endnetgrent (&netgrdata);
496           continue;
497         }
498
499       /* +@netgroup */
500       if (result->sp_namp[0] == '+' && result->sp_namp[1] == '@'
501           && result->sp_namp[2] != '\0')
502         {
503           int status;
504
505           ent->netgroup = true;
506           ent->first = true;
507           copy_spwd_changes (&ent->pwd, result, NULL, 0);
508
509           status = getspent_next_nss_netgr (NULL, result, ent,
510                                             &result->sp_namp[2],
511                                             buffer, buflen, errnop);
512           if (status == NSS_STATUS_RETURN)
513             continue;
514           else
515             return status;
516         }
517
518       /* -user */
519       if (result->sp_namp[0] == '-' && result->sp_namp[1] != '\0'
520           && result->sp_namp[1] != '@')
521         {
522           blacklist_store_name (&result->sp_namp[1], ent);
523           continue;
524         }
525
526       /* +user */
527       if (result->sp_namp[0] == '+' && result->sp_namp[1] != '\0'
528           && result->sp_namp[1] != '@')
529         {
530           size_t len = strlen (result->sp_namp);
531           char buf[len];
532           enum nss_status status;
533
534           /* Store the User in the blacklist for the "+" at the end of
535              /etc/passwd */
536           memcpy (buf, &result->sp_namp[1], len);
537           status = getspnam_plususer (&result->sp_namp[1], result, ent,
538                                       buffer, buflen, errnop);
539           blacklist_store_name (buf, ent);
540
541           if (status == NSS_STATUS_SUCCESS)     /* We found the entry. */
542             break;
543           /* We couldn't parse the entry */
544           else if (status == NSS_STATUS_RETURN
545                    /* entry doesn't exist */
546                    || status == NSS_STATUS_NOTFOUND)
547             continue;
548           else
549             {
550               if (status == NSS_STATUS_TRYAGAIN)
551                 {
552                   fsetpos (ent->stream, &pos);
553                   *errnop = ERANGE;
554                 }
555               return status;
556             }
557         }
558
559       /* +:... */
560       if (result->sp_namp[0] == '+' && result->sp_namp[1] == '\0')
561         {
562           ent->files = false;
563           ent->first = true;
564           copy_spwd_changes (&ent->pwd, result, NULL, 0);
565
566           return getspent_next_nss (result, ent, buffer, buflen, errnop);
567         }
568     }
569
570   return NSS_STATUS_SUCCESS;
571 }
572
573
574 static enum nss_status
575 internal_getspent_r (struct spwd *pw, ent_t *ent,
576                      char *buffer, size_t buflen, int *errnop)
577 {
578   if (ent->netgroup)
579     {
580       enum nss_status status;
581
582       /* We are searching members in a netgroup */
583       /* Since this is not the first call, we don't need the group name */
584       status = getspent_next_nss_netgr (NULL, pw, ent, NULL, buffer,
585                                         buflen, errnop);
586
587       if (status == NSS_STATUS_RETURN)
588         return getspent_next_file (pw, ent, buffer, buflen, errnop);
589       else
590         return status;
591     }
592   else if (ent->files)
593     return getspent_next_file (pw, ent, buffer, buflen, errnop);
594   else
595     return getspent_next_nss (pw, ent, buffer, buflen, errnop);
596 }
597
598
599 enum nss_status
600 _nss_compat_getspent_r (struct spwd *pwd, char *buffer, size_t buflen,
601                         int *errnop)
602 {
603   enum nss_status result = NSS_STATUS_SUCCESS;
604
605   __libc_lock_lock (lock);
606
607   /* Be prepared that the setpwent function was not called before.  */
608   if (ni == NULL)
609     init_nss_interface ();
610
611   if (ext_ent.stream == NULL)
612     result = internal_setspent (&ext_ent, 1, 1);
613
614   if (result == NSS_STATUS_SUCCESS)
615     result = internal_getspent_r (pwd, &ext_ent, buffer, buflen, errnop);
616
617   __libc_lock_unlock (lock);
618
619   return result;
620 }
621
622
623 /* Searches in /etc/passwd and the NIS/NIS+ map for a special user */
624 static enum nss_status
625 internal_getspnam_r (const char *name, struct spwd *result, ent_t *ent,
626                      char *buffer, size_t buflen, int *errnop)
627 {
628   struct parser_data *data = (void *) buffer;
629
630   while (1)
631     {
632       fpos_t pos;
633       char *p;
634       int parse_res;
635
636       do
637         {
638           /* We need at least 3 characters for one line.  */
639           if (__glibc_unlikely (buflen < 3))
640             {
641             erange:
642               *errnop = ERANGE;
643               return NSS_STATUS_TRYAGAIN;
644             }
645
646           fgetpos (ent->stream, &pos);
647           buffer[buflen - 1] = '\xff';
648           p = fgets_unlocked (buffer, buflen, ent->stream);
649           if (p == NULL && feof_unlocked (ent->stream))
650             return NSS_STATUS_NOTFOUND;
651
652           if (p == NULL || buffer[buflen - 1] != '\xff')
653             {
654             erange_reset:
655               fsetpos (ent->stream, &pos);
656               goto erange;
657             }
658
659           /* Terminate the line for any case.  */
660           buffer[buflen - 1] = '\0';
661
662           /* Skip leading blanks.  */
663           while (isspace (*p))
664             ++p;
665         }
666       while (*p == '\0' || *p == '#' /* Ignore empty and comment lines.  */
667              /* Parse the line.  If it is invalid, loop to
668                 get the next line of the file to parse.  */
669              || !(parse_res = _nss_files_parse_spent (p, result, data, buflen,
670                                                       errnop)));
671
672       if (__glibc_unlikely (parse_res == -1))
673         /* The parser ran out of space.  */
674         goto erange_reset;
675
676       /* This is a real entry.  */
677       if (result->sp_namp[0] != '+' && result->sp_namp[0] != '-')
678         {
679           if (strcmp (result->sp_namp, name) == 0)
680             return NSS_STATUS_SUCCESS;
681           else
682             continue;
683         }
684
685       /* -@netgroup */
686       /* If the loaded NSS module does not support this service, add
687          all users from a +@netgroup entry to the blacklist, too.  */
688       if (result->sp_namp[0] == '-' && result->sp_namp[1] == '@'
689           && result->sp_namp[2] != '\0')
690         {
691           if (innetgr (&result->sp_namp[2], NULL, name, NULL))
692             return NSS_STATUS_NOTFOUND;
693           continue;
694         }
695
696       /* +@netgroup */
697       if (result->sp_namp[0] == '+' && result->sp_namp[1] == '@'
698           && result->sp_namp[2] != '\0')
699         {
700           enum nss_status status;
701
702           if (innetgr (&result->sp_namp[2], NULL, name, NULL))
703             {
704               status = getspnam_plususer (name, result, ent, buffer,
705                                           buflen, errnop);
706
707               if (status == NSS_STATUS_RETURN)
708                 continue;
709
710               return status;
711             }
712           continue;
713         }
714
715       /* -user */
716       if (result->sp_namp[0] == '-' && result->sp_namp[1] != '\0'
717           && result->sp_namp[1] != '@')
718         {
719           if (strcmp (&result->sp_namp[1], name) == 0)
720             return NSS_STATUS_NOTFOUND;
721           else
722             continue;
723         }
724
725       /* +user */
726       if (result->sp_namp[0] == '+' && result->sp_namp[1] != '\0'
727           && result->sp_namp[1] != '@')
728         {
729           if (strcmp (name, &result->sp_namp[1]) == 0)
730             {
731               enum nss_status status;
732
733               status = getspnam_plususer (name, result, ent,
734                                           buffer, buflen, errnop);
735
736               if (status == NSS_STATUS_RETURN)
737                 /* We couldn't parse the entry */
738                 return NSS_STATUS_NOTFOUND;
739               else
740                 return status;
741             }
742         }
743
744       /* +:... */
745       if (result->sp_namp[0] == '+' && result->sp_namp[1] == '\0')
746         {
747           enum nss_status status;
748
749           status = getspnam_plususer (name, result, ent,
750                                       buffer, buflen, errnop);
751
752           if (status == NSS_STATUS_SUCCESS)
753             /* We found the entry. */
754             break;
755           else if (status == NSS_STATUS_RETURN)
756             /* We couldn't parse the entry */
757             return NSS_STATUS_NOTFOUND;
758           else
759             return status;
760         }
761     }
762   return NSS_STATUS_SUCCESS;
763 }
764
765
766 enum nss_status
767 _nss_compat_getspnam_r (const char *name, struct spwd *pwd,
768                         char *buffer, size_t buflen, int *errnop)
769 {
770   enum nss_status result;
771   ent_t ent = { false, true, false, NSS_STATUS_SUCCESS, NULL, { NULL, 0, 0},
772                 { NULL, NULL, 0, 0, 0, 0, 0, 0, 0}};
773
774   if (name[0] == '-' || name[0] == '+')
775     return NSS_STATUS_NOTFOUND;
776
777   __libc_lock_lock (lock);
778
779   if (ni == NULL)
780     init_nss_interface ();
781
782   __libc_lock_unlock (lock);
783
784   result = internal_setspent (&ent, 0, 0);
785
786   if (result == NSS_STATUS_SUCCESS)
787     result = internal_getspnam_r (name, pwd, &ent, buffer, buflen, errnop);
788
789   internal_endspent (&ent);
790
791   return result;
792 }
793
794
795 /* Support routines for remembering -@netgroup and -user entries.
796    The names are stored in a single string with `|' as separator. */
797 static void
798 blacklist_store_name (const char *name, ent_t *ent)
799 {
800   int namelen = strlen (name);
801   char *tmp;
802
803   /* first call, setup cache */
804   if (ent->blacklist.size == 0)
805     {
806       ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
807       ent->blacklist.data = malloc (ent->blacklist.size);
808       if (ent->blacklist.data == NULL)
809         return;
810       ent->blacklist.data[0] = '|';
811       ent->blacklist.data[1] = '\0';
812       ent->blacklist.current = 1;
813     }
814   else
815     {
816       if (in_blacklist (name, namelen, ent))
817         return;                 /* no duplicates */
818
819       if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
820         {
821           ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
822           tmp = realloc (ent->blacklist.data, ent->blacklist.size);
823           if (tmp == NULL)
824             {
825               free (ent->blacklist.data);
826               ent->blacklist.size = 0;
827               return;
828             }
829           ent->blacklist.data = tmp;
830         }
831     }
832
833   tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
834   *tmp++ = '|';
835   *tmp = '\0';
836   ent->blacklist.current += namelen + 1;
837
838   return;
839 }
840
841
842 /* Returns whether ent->blacklist contains name.  */
843 static bool
844 in_blacklist (const char *name, int namelen, ent_t *ent)
845 {
846   char buf[namelen + 3];
847   char *cp;
848
849   if (ent->blacklist.data == NULL)
850     return false;
851
852   buf[0] = '|';
853   cp = stpcpy (&buf[1], name);
854   *cp++ = '|';
855   *cp = '\0';
856   return strstr (ent->blacklist.data, buf) != NULL;
857 }