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