update from main archive 961220
[kopensolaris-gnu/glibc.git] / nis / nss_compat / compat-pwd.c
1 /* Copyright (C) 1996 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 <pwd.h>
22 #include <errno.h>
23 #include <ctype.h>
24 #include <netdb.h>
25 #include <string.h>
26 #include <libc-lock.h>
27 #include <rpcsvc/yp.h>
28 #include <rpcsvc/ypclnt.h>
29
30 #include "netgroup.h"
31
32 /* Get the declaration of the parser function.  */
33 #define ENTNAME pwent
34 #define STRUCTURE passwd
35 #define EXTERN_PARSER
36 #include "../../nss/nss_files/files-parse.c"
37
38 /* Structure for remembering -@netgroup and -user members ... */
39 #define BLACKLIST_INITIAL_SIZE 512
40 #define BLACKLIST_INCREMENT 256
41 struct blacklist_t
42   {
43     char *data;
44     int current;
45     int size;
46   };
47
48 struct ent_t
49   {
50     bool_t netgroup;
51     bool_t nis;
52     bool_t first;
53     char *oldkey;
54     int oldkeylen;
55     FILE *stream;
56     struct blacklist_t blacklist;
57     struct passwd pwd;
58     struct __netgrent netgrdata;
59   };
60 typedef struct ent_t ent_t;
61
62 static ent_t ext_ent = {0, 0, 0, NULL, 0, NULL, {NULL, 0, 0},
63                         {NULL, NULL, 0, 0, NULL, NULL, NULL}};
64
65 /* Protect global state against multiple changers.  */
66 __libc_lock_define_initialized (static, lock)
67
68 /* Prototypes for local functions.  */
69 static void blacklist_store_name (const char *, ent_t *);
70 static int in_blacklist (const char *, int, ent_t *);
71
72 static void
73 give_pwd_free (struct passwd *pwd)
74 {
75   if (pwd->pw_name != NULL)
76     free (pwd->pw_name);
77   if (pwd->pw_passwd != NULL)
78     free (pwd->pw_passwd);
79   if (pwd->pw_gecos != NULL)
80     free (pwd->pw_gecos);
81   if (pwd->pw_dir != NULL)
82     free (pwd->pw_dir);
83   if (pwd->pw_shell != NULL)
84     free (pwd->pw_shell);
85
86   memset (pwd, '\0', sizeof (struct passwd));
87 }
88
89 static size_t
90 pwd_need_buflen (struct passwd *pwd)
91 {
92   size_t len = 0;
93
94   if (pwd->pw_passwd != NULL)
95     len += strlen (pwd->pw_passwd) + 1;
96
97   if (pwd->pw_gecos != NULL)
98     len += strlen (pwd->pw_gecos) + 1;
99
100   if (pwd->pw_dir != NULL)
101     len += strlen (pwd->pw_dir) + 1;
102
103   if (pwd->pw_shell != NULL)
104     len += strlen (pwd->pw_shell) + 1;
105
106   return len;
107 }
108
109 static void
110 copy_pwd_changes (struct passwd *dest, struct passwd *src,
111                   char *buffer, size_t buflen)
112 {
113   if (src->pw_passwd != NULL && strlen (src->pw_passwd))
114     {
115       if (buffer == NULL)
116         dest->pw_passwd = strdup (src->pw_passwd);
117       else if (dest->pw_passwd &&
118                strlen (dest->pw_passwd) >= strlen (src->pw_passwd))
119         strcpy (dest->pw_passwd, src->pw_passwd);
120       else
121         {
122           dest->pw_passwd = buffer;
123           strcpy (dest->pw_passwd, src->pw_passwd);
124           buffer += strlen (dest->pw_passwd) + 1;
125           buflen = buflen - (strlen (dest->pw_passwd) + 1);
126         }
127     }
128
129   if (src->pw_gecos != NULL && strlen (src->pw_gecos))
130     {
131       if (buffer == NULL)
132         dest->pw_gecos = strdup (src->pw_gecos);
133       else if (dest->pw_gecos &&
134                strlen (dest->pw_gecos) >= strlen (src->pw_gecos))
135         strcpy (dest->pw_gecos, src->pw_gecos);
136       else
137         {
138           dest->pw_gecos = buffer;
139           strcpy (dest->pw_gecos, src->pw_gecos);
140           buffer += strlen (dest->pw_gecos) + 1;
141           buflen = buflen - (strlen (dest->pw_gecos) + 1);
142         }
143     }
144   if (src->pw_dir != NULL && strlen (src->pw_dir))
145     {
146       if (buffer == NULL)
147         dest->pw_dir = strdup (src->pw_dir);
148       else if (dest->pw_dir &&
149                strlen (dest->pw_dir) >= strlen (src->pw_dir))
150         strcpy (dest->pw_dir, src->pw_dir);
151       else
152         {
153           dest->pw_dir = buffer;
154           strcpy (dest->pw_dir, src->pw_dir);
155           buffer += strlen (dest->pw_dir) + 1;
156           buflen = buflen - (strlen (dest->pw_dir) + 1);
157         }
158     }
159
160   if (src->pw_shell != NULL && strlen (src->pw_shell))
161     {
162       if (buffer == NULL)
163         dest->pw_shell = strdup (src->pw_shell);
164       else if (dest->pw_shell &&
165                strlen (dest->pw_shell) >= strlen (src->pw_shell))
166         strcpy (dest->pw_shell, src->pw_shell);
167       else
168         {
169           dest->pw_shell = buffer;
170           strcpy (dest->pw_shell, src->pw_shell);
171           buffer += strlen (dest->pw_shell) + 1;
172           buflen = buflen - (strlen (dest->pw_shell) + 1);
173         }
174     }
175 }
176
177 static enum nss_status
178 internal_setpwent (ent_t *ent)
179 {
180   enum nss_status status = NSS_STATUS_SUCCESS;
181
182   ent->nis = ent->first = ent->netgroup = 0;
183
184   /* If something was left over free it.  */
185   if (ent->netgroup)
186     __internal_endnetgrent (&ent->netgrdata);
187
188   if (ent->oldkey != NULL)
189     {
190       free (ent->oldkey);
191       ent->oldkey = NULL;
192       ent->oldkeylen = 0;
193     }
194
195   ent->blacklist.current = 0;
196   if (ent->blacklist.data != NULL)
197     ent->blacklist.data[0] = '\0';
198
199   if (ent->stream == NULL)
200     {
201       ent->stream = fopen ("/etc/passwd", "r");
202
203       if (ent->stream == NULL)
204         status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
205     }
206   else
207     rewind (ent->stream);
208
209   give_pwd_free (&ent->pwd);
210
211   return status;
212 }
213
214
215 enum nss_status
216 _nss_compat_setpwent (void)
217 {
218   enum nss_status result;
219
220   __libc_lock_lock (lock);
221
222   result = internal_setpwent (&ext_ent);
223
224   __libc_lock_unlock (lock);
225
226   return result;
227 }
228
229
230 static enum nss_status
231 internal_endpwent (ent_t *ent)
232 {
233   if (ent->stream != NULL)
234     {
235       fclose (ent->stream);
236       ent->stream = NULL;
237     }
238
239   ent->nis = ent->first = ent->netgroup = 0;
240
241   if (ent->oldkey != NULL)
242     {
243       free (ent->oldkey);
244       ent->oldkey = NULL;
245       ent->oldkeylen = 0;
246     }
247
248   ent->blacklist.current = 0;
249   if (ent->blacklist.data != NULL)
250     ent->blacklist.data[0] = '\0';
251
252   give_pwd_free (&ent->pwd);
253
254   return NSS_STATUS_SUCCESS;
255 }
256
257 enum nss_status
258 _nss_compat_endpwent (void)
259 {
260   enum nss_status result;
261
262   __libc_lock_lock (lock);
263
264   if (ext_ent.netgroup)
265     __internal_endnetgrent (&ext_ent.netgrdata);
266
267   result = internal_endpwent (&ext_ent);
268
269   __libc_lock_unlock (lock);
270
271   return result;
272 }
273
274 static enum nss_status
275 getpwent_next_netgr (struct passwd *result, ent_t *ent, char *group,
276                      char *buffer, size_t buflen)
277 {
278   struct parser_data *data = (void *) buffer;
279   char *ypdomain, *host, *user, *domain, *outval, *p, *p2;
280   int status, outvallen;
281   size_t p2len;
282
283   if (yp_get_default_domain (&ypdomain) != YPERR_SUCCESS)
284     {
285       ent->netgroup = 0;
286       ent->first = 0;
287       give_pwd_free (&ent->pwd);
288       return NSS_STATUS_UNAVAIL;
289     }
290
291   if (ent->first == TRUE)
292     {
293       bzero (&ent->netgrdata, sizeof (struct __netgrent));
294       __internal_setnetgrent (group, &ent->netgrdata);
295       ent->first = FALSE;
296     }
297
298   while (1)
299     {
300       status = __internal_getnetgrent_r (&host, &user, &domain,
301                                          &ent->netgrdata, buffer, buflen);
302       if (status != 1)
303         {
304           __internal_endnetgrent (&ent->netgrdata);
305           ent->netgroup = 0;
306           give_pwd_free (&ent->pwd);
307           return NSS_STATUS_RETURN;
308         }
309
310       if (user == NULL || user[0] == '-')
311         continue;
312
313       if (domain != NULL && strcmp (ypdomain, domain) != 0)
314         continue;
315
316       if (yp_match (ypdomain, "passwd.byname", user,
317                     strlen (user), &outval, &outvallen)
318           != YPERR_SUCCESS)
319         continue;
320
321       p2len = pwd_need_buflen (&ent->pwd);
322       if (p2len > buflen)
323         {
324           __set_errno (ERANGE);
325           return NSS_STATUS_TRYAGAIN;
326         }
327       p2 = buffer + (buflen - p2len);
328       buflen -= p2len;
329       p = strncpy (buffer, outval, buflen);
330       while (isspace (*p))
331         p++;
332       free (outval);
333       if (_nss_files_parse_pwent (p, result, data, buflen))
334         {
335           copy_pwd_changes (result, &ent->pwd, p2, p2len);
336           break;
337         }
338     }
339
340   return NSS_STATUS_SUCCESS;
341 }
342
343 static enum nss_status
344 getpwent_next_nis (struct passwd *result, ent_t *ent, char *buffer,
345                    size_t buflen)
346 {
347   struct parser_data *data = (void *) buffer;
348   char *domain, *outkey, *outval, *p, *p2;
349   int outkeylen, outvallen;
350   size_t p2len;
351
352   if (yp_get_default_domain (&domain) != YPERR_SUCCESS)
353     {
354       ent->nis = 0;
355       give_pwd_free (&ent->pwd);
356       return NSS_STATUS_UNAVAIL;
357     }
358
359   p2len = pwd_need_buflen (&ent->pwd);
360   if (p2len > buflen)
361     {
362       __set_errno (ERANGE);
363       return NSS_STATUS_TRYAGAIN;
364     }
365   p2 = buffer + (buflen - p2len);
366   buflen -= p2len;
367   do
368     {
369       if (ent->first)
370         {
371           if (yp_first (domain, "passwd.byname", &outkey, &outkeylen,
372                         &outval, &outvallen) != YPERR_SUCCESS)
373             {
374               ent->nis = 0;
375               give_pwd_free (&ent->pwd);
376               return NSS_STATUS_UNAVAIL;
377             }
378
379           ent->oldkey = outkey;
380           ent->oldkeylen = outkeylen;
381           ent->first = FALSE;
382         }
383       else
384         {
385           if (yp_next (domain, "passwd.byname", ent->oldkey, ent->oldkeylen,
386                        &outkey, &outkeylen, &outval, &outvallen)
387               != YPERR_SUCCESS)
388             {
389               ent->nis = 0;
390               give_pwd_free (&ent->pwd);
391               return NSS_STATUS_NOTFOUND;
392             }
393
394           free (ent->oldkey);
395           ent->oldkey = outkey;
396           ent->oldkeylen = outkeylen;
397         }
398
399       /* Copy the found data to our buffer  */
400       p = strncpy (buffer, outval, buflen);
401
402       /* ...and free the data.  */
403       free (outval);
404
405       while (isspace (*p))
406         ++p;
407     }
408   while (!_nss_files_parse_pwent (p, result, data, buflen));
409
410   copy_pwd_changes (result, &ent->pwd, p2, p2len);
411
412   if (!in_blacklist (result->pw_name, strlen (result->pw_name), ent))
413     return NSS_STATUS_SUCCESS;
414   else
415     return NSS_STATUS_NOTFOUND;
416 }
417
418
419 static enum nss_status
420 getpwent_next_file (struct passwd *result, ent_t *ent,
421                     char *buffer, size_t buflen)
422 {
423   struct parser_data *data = (void *) buffer;
424   while (1)
425     {
426       char *p, *p2;
427       size_t p2len;
428
429       do
430         {
431           p = fgets (buffer, buflen, ent->stream);
432           if (p == NULL)
433             return NSS_STATUS_NOTFOUND;
434
435           /* Terminate the line for any case.  */
436           buffer[buflen - 1] = '\0';
437
438           /* Skip leading blanks.  */
439           while (isspace (*p))
440             ++p;
441         }
442       while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines.  */
443       /* Parse the line.  If it is invalid, loop to
444          get the next line of the file to parse.  */
445              !_nss_files_parse_pwent (p, result, data, buflen));
446
447       if (result->pw_name[0] != '+' && result->pw_name[0] != '-')
448         /* This is a real entry.  */
449         break;
450
451       /* -@netgroup */
452       if (result->pw_name[0] == '-' && result->pw_name[1] == '@'
453           && result->pw_name[2] != '\0')
454         {
455           char *user, *host, *domain;
456
457           setnetgrent (&result->pw_name[2]);
458           while (getnetgrent (&host, &user, &domain))
459             {
460               if (user != NULL && user[0] != '-')
461                 blacklist_store_name (user, ent);
462             }
463           endnetgrent ();
464           continue;
465         }
466
467       /* +@netgroup */
468       if (result->pw_name[0] == '+' && result->pw_name[1] == '@'
469           && result->pw_name[2] != '\0')
470         {
471           int status;
472
473           ent->netgroup = TRUE;
474           ent->first = TRUE;
475           copy_pwd_changes (&ent->pwd, result, NULL, 0);
476
477           status = getpwent_next_netgr (result, ent, &result->pw_name[2],
478                                         buffer, buflen);
479           if (status == NSS_STATUS_RETURN)
480             continue;
481           else
482             return status;
483         }
484
485       /* -user */
486       if (result->pw_name[0] == '-' && result->pw_name[1] != '\0'
487           && result->pw_name[1] != '@')
488         {
489           blacklist_store_name (&result->pw_name[1], ent);
490           continue;
491         }
492
493       /* +user */
494       if (result->pw_name[0] == '+' && result->pw_name[1] != '\0'
495           && result->pw_name[1] != '@')
496         {
497           char *domain;
498           char *outval;
499           int outvallen;
500           struct passwd pwd;
501
502           memset (&pwd, '\0', sizeof (struct passwd));
503
504           if (yp_get_default_domain (&domain) != YPERR_SUCCESS)
505             /* XXX Should we regard this as an fatal error?  I don't
506                think so.  Just continue working.  --drepper@gnu  */
507             continue;
508
509           if (yp_match (domain, "passwd.byname", &result->pw_name[1],
510                         strlen (result->pw_name) - 1, &outval, &outvallen)
511               != YPERR_SUCCESS)
512             continue;
513
514           copy_pwd_changes (&pwd, result, NULL, 0);
515
516           p2len = pwd_need_buflen (&pwd);
517           if (p2len > buflen)
518             {
519               __set_errno (ERANGE);
520               return NSS_STATUS_TRYAGAIN;
521             }
522           p2 = buffer + (buflen - p2len);
523           buflen -= p2len;
524           p = strncpy (buffer, outval, buflen);
525           while (isspace (*p))
526             p++;
527           free (outval);
528           if (_nss_files_parse_pwent (p, result, data, buflen))
529             {
530               copy_pwd_changes (result, &pwd, p2, p2len);
531               give_pwd_free (&pwd);
532               /* We found the entry.  */
533               break;
534             }
535           else
536             {
537               /* Give buffer the old len back */
538               buflen += p2len;
539               give_pwd_free (&pwd);
540             }
541         }
542
543       /* +:... */
544       if (result->pw_name[0] == '+' && result->pw_name[1] == '\0')
545         {
546           ent->nis = TRUE;
547           ent->first = TRUE;
548           copy_pwd_changes (&ent->pwd, result, NULL, 0);
549
550           return getpwent_next_nis (result, ent, buffer, buflen);
551         }
552     }
553
554   return NSS_STATUS_SUCCESS;
555 }
556
557
558 static enum nss_status
559 internal_getpwent_r (struct passwd *pw, ent_t *ent, char *buffer,
560                      size_t buflen)
561 {
562   if (ent->netgroup)
563     {
564       int status;
565
566       /* We are searching members in a netgroup */
567       /* Since this is not the first call, we don't need the group name */
568       status = getpwent_next_netgr (pw, ent, NULL, buffer, buflen);
569       if (status == NSS_STATUS_RETURN)
570         return getpwent_next_file (pw, ent, buffer, buflen);
571       else
572         return status;
573     }
574   else if (ent->nis)
575     return getpwent_next_nis (pw, ent, buffer, buflen);
576   else
577     return getpwent_next_file (pw, ent, buffer, buflen);
578 }
579
580 enum nss_status
581 _nss_compat_getpwent_r (struct passwd *pwd, char *buffer,
582                         size_t buflen)
583 {
584   enum nss_status status = NSS_STATUS_SUCCESS;
585
586   __libc_lock_lock (lock);
587
588   /* Be prepared that the setpwent function was not called before.  */
589   if (ext_ent.stream == NULL)
590     status = internal_setpwent (&ext_ent);
591
592   if (status == NSS_STATUS_SUCCESS)
593     status = internal_getpwent_r (pwd, &ext_ent, buffer, buflen);
594
595   __libc_lock_unlock (lock);
596
597   return status;
598 }
599
600
601 enum nss_status
602 _nss_compat_getpwnam_r (const char *name, struct passwd *pwd,
603                         char *buffer, size_t buflen)
604 {
605   ent_t ent = {0, 0, 0, NULL, 0, NULL, {NULL, 0, 0},
606                {NULL, NULL, 0, 0, NULL, NULL, NULL}};
607   enum nss_status status;
608
609   if (name[0] == '-' || name[0] == '+')
610     return NSS_STATUS_NOTFOUND;
611
612
613   status = internal_setpwent (&ent);
614   if (status != NSS_STATUS_SUCCESS)
615     return status;
616
617   while ((status = internal_getpwent_r (pwd, &ent, buffer, buflen))
618          == NSS_STATUS_SUCCESS)
619     if (strcmp (pwd->pw_name, name) == 0)
620       break;
621
622   internal_endpwent (&ent);
623   return status;
624 }
625
626
627 enum nss_status
628 _nss_compat_getpwuid_r (uid_t uid, struct passwd *pwd,
629                         char *buffer, size_t buflen)
630 {
631   ent_t ent = {0, 0, 0, NULL, 0, NULL, {NULL, 0, 0},
632                {NULL, NULL, 0, 0, NULL, NULL, NULL}};
633   enum nss_status status;
634
635   status = internal_setpwent (&ent);
636   if (status != NSS_STATUS_SUCCESS)
637     return status;
638
639   while ((status = internal_getpwent_r (pwd, &ent, buffer, buflen))
640          == NSS_STATUS_SUCCESS)
641     if (pwd->pw_uid == uid && pwd->pw_name[0] != '+' && pwd->pw_name[0] != '-')
642       break;
643
644   internal_endpwent (&ent);
645   return status;
646 }
647
648
649 /* Support routines for remembering -@netgroup and -user entries.
650    The names are stored in a single string with `|' as separator. */
651 static void
652 blacklist_store_name (const char *name, ent_t *ent)
653 {
654   int namelen = strlen (name);
655   char *tmp;
656
657   /* first call, setup cache */
658   if (ent->blacklist.size == 0)
659     {
660       ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
661       ent->blacklist.data = malloc (ent->blacklist.size);
662       if (ent->blacklist.data == NULL)
663         return;
664       ent->blacklist.data[0] = '|';
665       ent->blacklist.data[1] = '\0';
666       ent->blacklist.current = 1;
667     }
668   else
669     {
670       if (in_blacklist (name, namelen, ent))
671         return;                 /* no duplicates */
672
673       if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
674         {
675           ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
676           tmp = realloc (ent->blacklist.data, ent->blacklist.size);
677           if (tmp == NULL)
678             {
679               free (ent->blacklist.data);
680               ent->blacklist.size = 0;
681               return;
682             }
683           ent->blacklist.data = tmp;
684         }
685     }
686
687   tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
688   *tmp++ = '|';
689   *tmp = '\0';
690   ent->blacklist.current += namelen + 1;
691
692   return;
693 }
694
695 /* returns TRUE if ent->blacklist contains name, else FALSE */
696 static bool_t
697 in_blacklist (const char *name, int namelen, ent_t *ent)
698 {
699   char buf[namelen + 3];
700
701   if (ent->blacklist.data == NULL)
702     return FALSE;
703
704   stpcpy (stpcpy (stpcpy (buf, "|"), name), "|");
705   return strstr (ent->blacklist.data, buf) != NULL;
706 }