82a26efc2581f5d6fa2ada4f540311359b37c03c
[kopensolaris-gnu/glibc.git] / nscd / pwdcache.c
1 /* Copyright (c) 1998 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3    Contributed by Thorsten Kukuk <kukuk@vt.uni-paderborn.de>, 1998.
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 <errno.h>
21 #include <malloc.h>
22 #include <pthread.h>
23 #include <pwd.h>
24 #include <string.h>
25 #include <rpcsvc/nis.h>
26 #include <sys/types.h>
27
28 #include "dbg_log.h"
29 #include "nscd.h"
30
31 static unsigned long int modulo = 211;
32 static unsigned long int postimeout = 600;
33 static unsigned long int negtimeout = 20;
34
35 static unsigned long int poshit = 0;
36 static unsigned long int posmiss = 0;
37 static unsigned long int neghit = 0;
38 static unsigned long int negmiss = 0;
39
40 struct pwdhash
41 {
42   time_t create;
43   struct pwdhash *next;
44   struct passwd *pwd;
45 };
46 typedef struct pwdhash pwdhash;
47
48 struct uidhash
49 {
50   struct uidhash *next;
51   struct pwdhash *pwptr;
52 };
53 typedef struct uidhash uidhash;
54
55 struct neghash
56 {
57   time_t create;
58   struct neghash *next;
59   char *key;
60 };
61 typedef struct neghash neghash;
62
63 static pwdhash *pwdtbl;
64 static uidhash *uidtbl;
65 static neghash *negtbl;
66
67 static pthread_rwlock_t pwdlock = PTHREAD_RWLOCK_INITIALIZER;
68 static pthread_rwlock_t neglock = PTHREAD_RWLOCK_INITIALIZER;
69
70 static void *pwdtable_update (void *);
71 static void *negtable_update (void *);
72
73 void
74 get_pw_stat (stat_response_header *stat)
75 {
76   stat->pw_poshit = poshit;
77   stat->pw_posmiss = posmiss;
78   stat->pw_neghit = neghit;
79   stat->pw_negmiss = negmiss;
80   stat->pw_size = modulo;
81   stat->pw_posttl = postimeout;
82   stat->pw_negttl = negtimeout;
83 }
84
85 void
86 set_pwd_modulo (unsigned long int mod)
87 {
88   modulo = mod;
89 }
90
91 void
92 set_pos_pwd_ttl (unsigned long int ttl)
93 {
94   postimeout = ttl;
95 }
96
97 void
98 set_neg_pwd_ttl (unsigned long int ttl)
99 {
100   negtimeout = ttl;
101 }
102
103 int
104 cache_pwdinit ()
105 {
106   pthread_attr_t attr;
107   pthread_t thread;
108
109   pwdtbl = calloc (modulo, sizeof (pwdhash));
110   if (pwdtbl == NULL)
111     return -1;
112   uidtbl = calloc (modulo, sizeof (pwdhash));
113   if (uidtbl == NULL)
114     return -1;
115   negtbl = calloc (modulo, sizeof (neghash));
116   if (negtbl == NULL)
117     return -1;
118
119   pthread_attr_init (&attr);
120   pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
121
122   pthread_create (&thread, NULL, pwdtable_update, &attr);
123   pthread_create (&thread, NULL, negtable_update, &attr);
124
125   pthread_attr_destroy (&attr);
126
127   return 0;
128 }
129
130 static struct passwd *
131 save_pwd (struct passwd *src)
132 {
133   struct passwd *dest;
134
135   dest = calloc (1, sizeof (struct passwd));
136   dest->pw_name = strdup (src->pw_name);
137   dest->pw_passwd = strdup (src->pw_passwd);
138   dest->pw_uid = src->pw_uid;
139   dest->pw_gid = src->pw_gid;
140   dest->pw_gecos = strdup (src->pw_gecos);
141   dest->pw_dir = strdup (src->pw_dir);
142   dest->pw_shell = strdup (src->pw_shell);
143
144   return dest;
145 }
146
147 static void
148 free_pwd (struct passwd *src)
149 {
150   free (src->pw_name);
151   free (src->pw_passwd);
152   free (src->pw_gecos);
153   free (src->pw_dir);
154   free (src->pw_shell);
155   free (src);
156 }
157
158 static int
159 add_cache (struct passwd *pwd)
160 {
161   pwdhash *work;
162   unsigned long int hash = __nis_hash (pwd->pw_name,
163                                        strlen (pwd->pw_name)) % modulo;
164
165   if (debug_flag)
166     dbg_log (_("add_cache (%s)"), pwd->pw_name);
167
168   work = &pwdtbl[hash];
169
170   if (pwdtbl[hash].pwd == NULL)
171     pwdtbl[hash].pwd = save_pwd (pwd);
172   else
173     {
174       while (work->next != NULL)
175         work = work->next;
176
177       work->next = calloc (1, sizeof (pwdhash));
178       work->next->pwd = save_pwd (pwd);
179       work = work->next;
180     }
181   /* Set a pointer from the pwuid hash table to the pwname hash table */
182   time (&work->create);
183   uidtbl[pwd->pw_uid % modulo].pwptr = work;
184
185   return 0;
186 }
187
188 static struct passwd *
189 cache_search_name (const char *name)
190 {
191   pwdhash *work;
192   unsigned long int hash = __nis_hash (name, strlen (name)) % modulo;
193
194   work = &pwdtbl[hash];
195
196   while (work->pwd != NULL)
197     {
198       if (strcmp (work->pwd->pw_name, name) == 0)
199         return work->pwd;
200       if (work->next != NULL)
201         work = work->next;
202       else
203         return NULL;
204     }
205   return NULL;
206 }
207
208 static struct passwd *
209 cache_search_uid (uid_t uid)
210 {
211   uidhash *work;
212
213   work = &uidtbl[uid % modulo];
214
215   while (work->pwptr != NULL)
216     {
217       if (work->pwptr->pwd->pw_uid == uid)
218         return work->pwptr->pwd;
219       if (work->next != NULL)
220         work = work->next;
221       else
222         return NULL;
223     }
224   return NULL;
225 }
226
227 static int
228 add_negcache (char *key)
229 {
230   neghash *work;
231   unsigned long int hash = __nis_hash (key, strlen (key)) % modulo;
232
233   if (debug_flag)
234     dbg_log (_("add_netgache (%s|%ld)"), key, hash);
235
236   work = &negtbl[hash];
237
238   if (negtbl[hash].key == NULL)
239     {
240       negtbl[hash].key = strdup (key);
241       negtbl[hash].next = NULL;
242     }
243   else
244     {
245       while (work->next != NULL)
246         work = work->next;
247
248       work->next = calloc (1, sizeof (neghash));
249       work->next->key = strdup (key);
250       work = work->next;
251     }
252   /* Set a pointer from the pwuid hash table to the pwname hash table */
253   time (&work->create);
254
255   return 0;
256 }
257
258 static int
259 cache_search_neg (const char *key)
260 {
261   neghash *work;
262   unsigned long int hash = __nis_hash (key, strlen (key)) % modulo;
263
264   work = &negtbl[hash];
265
266   if (debug_flag)
267     dbg_log (_("cache_search_neg (%s|%ld)"), key, hash);
268
269   while (work->key != NULL)
270     {
271       if (strcmp (work->key, key) == 0)
272         return 1;
273       if (work->next != NULL)
274         work = work->next;
275       else
276         return 0;
277     }
278   return 0;
279 }
280
281 void *
282 cache_getpwnam (void *v_param)
283 {
284   param_t *param = (param_t *)v_param;
285   struct passwd *pwd, resultbuf;
286
287   pthread_rwlock_rdlock (&pwdlock);
288   pwd = cache_search_name (param->key);
289
290   /* I don't like it to hold the read only lock longer, but it is
291      necessary to avoid to much malloc/free/strcpy.  */
292
293   if (pwd != NULL)
294     {
295       if (debug_flag)
296         dbg_log (_("Found \"%s\" in cache !"), param->key);
297
298       ++poshit;
299       pw_send_answer (param->conn, pwd);
300       close_socket (param->conn);
301
302       pthread_rwlock_unlock (&pwdlock);
303       pwd = &resultbuf;
304     }
305   else
306     {
307       int status;
308       int buflen = 1024;
309       char *buffer = malloc (buflen);
310
311       if (debug_flag)
312         dbg_log (_("Doesn't found \"%s\" in cache !"), param->key);
313
314       pthread_rwlock_unlock (&pwdlock);
315
316       pthread_rwlock_rdlock (&neglock);
317       status = cache_search_neg (param->key);
318       pthread_rwlock_unlock (&neglock);
319
320       if (status == 0)
321         {
322           while (buffer != NULL
323                  && (getpwnam_r (param->key, &resultbuf, buffer, buflen, &pwd)
324                      != 0)
325                  && errno == ERANGE)
326             {
327               errno = 0;
328               buflen += 1024;
329               buffer = realloc (buffer, buflen);
330             }
331
332           if (buffer != NULL && pwd != NULL)
333             {
334               struct passwd *tmp;
335
336               ++posmiss;
337               pthread_rwlock_wrlock (&pwdlock);
338               /* While we are waiting on the lock, somebody else could
339                  add this entry.  */
340               tmp = cache_search_name (param->key);
341               if (tmp == NULL)
342                 add_cache (pwd);
343               pthread_rwlock_unlock (&pwdlock);
344             }
345           else
346             {
347               ++negmiss;
348               pthread_rwlock_wrlock (&neglock);
349               add_negcache (param->key);
350               pthread_rwlock_unlock (&neglock);
351             }
352         }
353       else
354         ++neghit;
355       pw_send_answer (param->conn, pwd);
356       close_socket (param->conn);
357       if (buffer != NULL)
358         free (buffer);
359     }
360   free (param->key);
361   free (param);
362   return NULL;
363 }
364
365 void *
366 cache_pw_disabled (void *v_param)
367 {
368   param_t *param = (param_t *)v_param;
369
370   pw_send_disabled (param->conn);
371   return NULL;
372 }
373
374 void *
375 cache_getpwuid (void *v_param)
376 {
377   param_t *param = (param_t *)v_param;
378   struct passwd *pwd, resultbuf;
379   uid_t uid = strtol (param->key, NULL, 10);
380
381   pthread_rwlock_rdlock (&pwdlock);
382   pwd = cache_search_uid (uid);
383
384   /* I don't like it to hold the read only lock longer, but it is
385      necessary to avoid to much malloc/free/strcpy.  */
386
387   if (pwd != NULL)
388     {
389       if (debug_flag)
390         dbg_log (_("Found \"%d\" in cache !"), uid);
391
392       ++poshit;
393       pw_send_answer (param->conn, pwd);
394       close_socket (param->conn);
395
396       pthread_rwlock_unlock (&pwdlock);
397     }
398   else
399     {
400       int buflen = 1024;
401       char *buffer = malloc (buflen);
402       int status;
403
404       if (debug_flag)
405         dbg_log (_("Doesn't found \"%d\" in cache !"), uid);
406
407       pthread_rwlock_unlock (&pwdlock);
408
409       pthread_rwlock_rdlock (&neglock);
410       status = cache_search_neg (param->key);
411       pthread_rwlock_unlock (&neglock);
412
413       if (status == 0)
414         {
415           while (buffer != NULL
416                  && (getpwuid_r (uid, &resultbuf, buffer, buflen, &pwd) != 0)
417                  && errno == ERANGE)
418             {
419               errno = 0;
420               buflen += 1024;
421               buffer = realloc (buffer, buflen);
422             }
423
424           if (buffer != NULL && pwd != NULL)
425             {
426               struct passwd *tmp;
427
428               ++posmiss;
429               pthread_rwlock_wrlock (&pwdlock);
430               /* While we are waiting on the lock, somebody else could
431                  add this entry.  */
432               tmp = cache_search_uid (uid);
433               if (tmp == NULL)
434                 add_cache (pwd);
435               pthread_rwlock_unlock (&pwdlock);
436             }
437           else
438             {
439               ++negmiss;
440               pthread_rwlock_wrlock (&neglock);
441               add_negcache (param->key);
442               pthread_rwlock_unlock (&neglock);
443             }
444         }
445       else
446         ++neghit;
447
448       pw_send_answer (param->conn, pwd);
449       close_socket (param->conn);
450       if (buffer != NULL)
451         free (buffer);
452     }
453   free (param->key);
454   free (param);
455   return NULL;
456 }
457
458 void *
459 pwdtable_update (void *v)
460 {
461   time_t now;
462   int i;
463
464   sleep (20);
465
466   while (!do_shutdown)
467     {
468       if (debug_flag > 2)
469         dbg_log (_("(pwdtable_update) Wait for write lock!"));
470
471       pthread_rwlock_wrlock (&pwdlock);
472
473       if (debug_flag > 2)
474         dbg_log (_("(pwdtable_update) Have write lock"));
475
476       time (&now);
477       for (i = 0; i < modulo; ++i)
478         {
479           pwdhash *work = &pwdtbl[i];
480
481           while (work && work->pwd)
482             {
483               if ((now - work->create) >= postimeout)
484                 {
485                   uidhash *uh = &uidtbl[work->pwd->pw_uid % modulo];
486
487                   if (debug_flag)
488                     dbg_log (_("Give \"%s\" free"), work->pwd->pw_name);
489
490                   while (uh != NULL && uh->pwptr)
491                     {
492                       if (uh->pwptr->pwd->pw_uid == work->pwd->pw_uid)
493                         {
494                           if (debug_flag)
495                             dbg_log (_("Give uid for \"%s\" free"),
496                                      work->pwd->pw_name);
497                           if (uh->next != NULL)
498                             {
499                               uidhash *tmp = uh->next;
500                               uh->pwptr = tmp->pwptr;
501                               uh->next = tmp->next;
502                               free (tmp);
503                             }
504                           else
505                             uh->pwptr = NULL;
506                         }
507                       uh = uh->next;
508                     }
509
510                   free_pwd (work->pwd);
511                   if (work->next != NULL)
512                     {
513                       pwdhash *tmp = work->next;
514                       work->create = tmp->create;
515                       work->next = tmp->next;
516                       work->pwd = tmp->pwd;
517                       free (tmp);
518                     }
519                   else
520                     work->pwd = NULL;
521                 }
522               work = work->next;
523             }
524         }
525       if (debug_flag > 2)
526         dbg_log (_("(pwdtable_update) Release wait lock"));
527       pthread_rwlock_unlock (&pwdlock);
528       sleep (20);
529     }
530   return NULL;
531 }
532
533 void *
534 negtable_update (void *v)
535 {
536   time_t now;
537   int i;
538
539   sleep (30);
540
541   while (!do_shutdown)
542     {
543       if (debug_flag > 2)
544         dbg_log (_("(negtable_update) Wait for write lock!"));
545
546       pthread_rwlock_wrlock (&neglock);
547
548       if (debug_flag)
549         dbg_log (_("(negtable_update) Have write lock"));
550
551       time (&now);
552       for (i = 0; i < modulo; ++i)
553         {
554           neghash *work = &negtbl[i];
555
556           while (work && work->key)
557             {
558               if ((now - work->create) >= negtimeout)
559                 {
560                   if (debug_flag)
561                     dbg_log (_("Give \"%s\" free"), work->key);
562
563                   free (work->key);
564
565                   if (work->next != NULL)
566                     {
567                       neghash *tmp = work->next;
568                       work->create = tmp->create;
569                       work->next = tmp->next;
570                       work->key = tmp->key;
571                       free (tmp);
572                     }
573                   else
574                     work->key = NULL;
575                 }
576               work = work->next;
577             }
578         }
579       if (debug_flag)
580         dbg_log (_("(negtable_update) Release wait lock"));
581
582       pthread_rwlock_unlock (&neglock);
583       sleep (10);
584     }
585   return NULL;
586 }