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