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