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