Updated to fedora-glibc-20050824T0705
[kopensolaris-gnu/glibc.git] / nscd / pwdcache.c
1 /* Cache handling for passwd lookup.
2    Copyright (C) 1998-2002, 2003, 2004, 2005 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
5
6    The GNU C Library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Lesser General Public
8    License as published by the Free Software Foundation; either
9    version 2.1 of the License, or (at your option) any later version.
10
11    The GNU C Library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Lesser General Public License for more details.
15
16    You should have received a copy of the GNU Lesser General Public
17    License along with the GNU C Library; if not, write to the Free
18    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19    02111-1307 USA.  */
20
21 #include <alloca.h>
22 #include <assert.h>
23 #include <errno.h>
24 #include <error.h>
25 #include <libintl.h>
26 #include <pwd.h>
27 #include <stdbool.h>
28 #include <stddef.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <time.h>
33 #include <unistd.h>
34 #include <sys/mman.h>
35 #include <stackinfo.h>
36
37 #include "nscd.h"
38 #include "dbg_log.h"
39
40 /* This is the standard reply in case the service is disabled.  */
41 static const pw_response_header disabled =
42 {
43   .version = NSCD_VERSION,
44   .found = -1,
45   .pw_name_len = 0,
46   .pw_passwd_len = 0,
47   .pw_uid = -1,
48   .pw_gid = -1,
49   .pw_gecos_len = 0,
50   .pw_dir_len = 0,
51   .pw_shell_len = 0
52 };
53
54 /* This is the struct describing how to write this record.  */
55 const struct iovec pwd_iov_disabled =
56 {
57   .iov_base = (void *) &disabled,
58   .iov_len = sizeof (disabled)
59 };
60
61
62 /* This is the standard reply in case we haven't found the dataset.  */
63 static const pw_response_header notfound =
64 {
65   .version = NSCD_VERSION,
66   .found = 0,
67   .pw_name_len = 0,
68   .pw_passwd_len = 0,
69   .pw_uid = -1,
70   .pw_gid = -1,
71   .pw_gecos_len = 0,
72   .pw_dir_len = 0,
73   .pw_shell_len = 0
74 };
75
76
77 static void
78 cache_addpw (struct database_dyn *db, int fd, request_header *req,
79              const void *key, struct passwd *pwd, uid_t owner,
80              struct hashentry *he, struct datahead *dh, int errval)
81 {
82   ssize_t total;
83   ssize_t written;
84   time_t t = time (NULL);
85
86   /* We allocate all data in one memory block: the iov vector,
87      the response header and the dataset itself.  */
88   struct dataset
89   {
90     struct datahead head;
91     pw_response_header resp;
92     char strdata[0];
93   } *dataset;
94
95   assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data));
96
97   if (pwd == NULL)
98     {
99       if (he != NULL && errval == EAGAIN)
100         {
101           /* If we have an old record available but cannot find one
102              now because the service is not available we keep the old
103              record and make sure it does not get removed.  */
104           if (reload_count != UINT_MAX && dh->nreloads == reload_count)
105             /* Do not reset the value if we never not reload the record.  */
106             dh->nreloads = reload_count - 1;
107
108           written = total = 0;
109         }
110       else
111         {
112           /* We have no data.  This means we send the standard reply for this
113              case.  */
114           written = total = sizeof (notfound);
115
116           if (fd != -1)
117             written = TEMP_FAILURE_RETRY (send (fd, &notfound, total,
118                                                 MSG_NOSIGNAL));
119
120           dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len);
121           /* If we cannot permanently store the result, so be it.  */
122           if (dataset != NULL)
123             {
124               dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
125               dataset->head.recsize = total;
126               dataset->head.notfound = true;
127               dataset->head.nreloads = 0;
128               dataset->head.usable = true;
129
130               /* Compute the timeout time.  */
131               dataset->head.timeout = t + db->negtimeout;
132
133               /* This is the reply.  */
134               memcpy (&dataset->resp, &notfound, total);
135
136               /* Copy the key data.  */
137               char *key_copy = memcpy (dataset->strdata, key, req->key_len);
138
139               /* If necessary, we also propagate the data to disk.  */
140               if (db->persistent)
141                 {
142                   // XXX async OK?
143                   uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
144                   msync ((void *) pval,
145                          ((uintptr_t) dataset & pagesize_m1)
146                          + sizeof (struct dataset) + req->key_len, MS_ASYNC);
147                 }
148
149               /* Now get the lock to safely insert the records.  */
150               pthread_rwlock_rdlock (&db->lock);
151
152               if (cache_add (req->type, key_copy, req->key_len,
153                              &dataset->head, true, db, owner) < 0)
154                 /* Ensure the data can be recovered.  */
155                 dataset->head.usable = false;
156
157
158               pthread_rwlock_unlock (&db->lock);
159
160               /* Mark the old entry as obsolete.  */
161               if (dh != NULL)
162                 dh->usable = false;
163             }
164           else
165             ++db->head->addfailed;
166         }
167     }
168   else
169     {
170       /* Determine the I/O structure.  */
171       size_t pw_name_len = strlen (pwd->pw_name) + 1;
172       size_t pw_passwd_len = strlen (pwd->pw_passwd) + 1;
173       size_t pw_gecos_len = strlen (pwd->pw_gecos) + 1;
174       size_t pw_dir_len = strlen (pwd->pw_dir) + 1;
175       size_t pw_shell_len = strlen (pwd->pw_shell) + 1;
176       char *cp;
177       const size_t key_len = strlen (key);
178       const size_t buf_len = 3 * sizeof (pwd->pw_uid) + key_len + 1;
179       char *buf = alloca (buf_len);
180       ssize_t n;
181
182       /* We need this to insert the `byuid' entry.  */
183       int key_offset;
184       n = snprintf (buf, buf_len, "%d%c%n%s", pwd->pw_uid, '\0',
185                     &key_offset, (char *) key) + 1;
186
187       written = total = (sizeof (struct dataset) + pw_name_len + pw_passwd_len
188                          + pw_gecos_len + pw_dir_len + pw_shell_len);
189
190       /* If we refill the cache, first assume the reconrd did not
191          change.  Allocate memory on the cache since it is likely
192          discarded anyway.  If it turns out to be necessary to have a
193          new record we can still allocate real memory.  */
194       bool alloca_used = false;
195       dataset = NULL;
196
197       if (he == NULL)
198         {
199           dataset = (struct dataset *) mempool_alloc (db, total + n);
200           if (dataset == NULL)
201             ++db->head->addfailed;
202         }
203
204       if (dataset == NULL)
205         {
206           /* We cannot permanently add the result in the moment.  But
207              we can provide the result as is.  Store the data in some
208              temporary memory.  */
209           dataset = (struct dataset *) alloca (total + n);
210
211           /* We cannot add this record to the permanent database.  */
212           alloca_used = true;
213         }
214
215       dataset->head.allocsize = total + n;
216       dataset->head.recsize = total - offsetof (struct dataset, resp);
217       dataset->head.notfound = false;
218       dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
219       dataset->head.usable = true;
220
221       /* Compute the timeout time.  */
222       dataset->head.timeout = t + db->postimeout;
223
224       dataset->resp.version = NSCD_VERSION;
225       dataset->resp.found = 1;
226       dataset->resp.pw_name_len = pw_name_len;
227       dataset->resp.pw_passwd_len = pw_passwd_len;
228       dataset->resp.pw_uid = pwd->pw_uid;
229       dataset->resp.pw_gid = pwd->pw_gid;
230       dataset->resp.pw_gecos_len = pw_gecos_len;
231       dataset->resp.pw_dir_len = pw_dir_len;
232       dataset->resp.pw_shell_len = pw_shell_len;
233
234       cp = dataset->strdata;
235
236       /* Copy the strings over into the buffer.  */
237       cp = mempcpy (cp, pwd->pw_name, pw_name_len);
238       cp = mempcpy (cp, pwd->pw_passwd, pw_passwd_len);
239       cp = mempcpy (cp, pwd->pw_gecos, pw_gecos_len);
240       cp = mempcpy (cp, pwd->pw_dir, pw_dir_len);
241       cp = mempcpy (cp, pwd->pw_shell, pw_shell_len);
242
243       /* Finally the stringified UID value.  */
244       memcpy (cp, buf, n);
245       char *key_copy = cp + key_offset;
246       assert (key_copy == (char *) rawmemchr (cp, '\0') + 1);
247
248       /* Now we can determine whether on refill we have to create a new
249          record or not.  */
250       if (he != NULL)
251         {
252           assert (fd == -1);
253
254           if (total + n == dh->allocsize
255               && total - offsetof (struct dataset, resp) == dh->recsize
256               && memcmp (&dataset->resp, dh->data,
257                          dh->allocsize - offsetof (struct dataset, resp)) == 0)
258             {
259               /* The data has not changed.  We will just bump the
260                  timeout value.  Note that the new record has been
261                  allocated on the stack and need not be freed.  */
262               dh->timeout = dataset->head.timeout;
263               ++dh->nreloads;
264             }
265           else
266             {
267               /* We have to create a new record.  Just allocate
268                  appropriate memory and copy it.  */
269               struct dataset *newp
270                 = (struct dataset *) mempool_alloc (db, total + n);
271               if (newp != NULL)
272                 {
273                   /* Adjust pointer into the memory block.  */
274                   cp = (char *) newp + (cp - (char *) dataset);
275
276                   dataset = memcpy (newp, dataset, total + n);
277                   alloca_used = false;
278                 }
279
280               /* Mark the old record as obsolete.  */
281               dh->usable = false;
282             }
283         }
284       else
285         {
286           /* We write the dataset before inserting it to the database
287              since while inserting this thread might block and so would
288              unnecessarily let the receiver wait.  */
289           assert (fd != -1);
290
291           written = writeall (fd, &dataset->resp, total);
292         }
293
294
295       /* Add the record to the database.  But only if it has not been
296          stored on the stack.  */
297       if (! alloca_used)
298         {
299           /* If necessary, we also propagate the data to disk.  */
300           if (db->persistent)
301             {
302               // XXX async OK?
303               uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
304               msync ((void *) pval,
305                      ((uintptr_t) dataset & pagesize_m1) + total + n,
306                      MS_ASYNC);
307             }
308
309           /* Now get the lock to safely insert the records.  */
310           pthread_rwlock_rdlock (&db->lock);
311
312           /* NB: in the following code we always must add the entry
313              marked with FIRST first.  Otherwise we end up with
314              dangling "pointers" in case a latter hash entry cannot be
315              added.  */
316           bool first = req->type == GETPWBYNAME;
317
318           /* If the request was by UID, add that entry first.  */
319           if (req->type != GETPWBYNAME)
320             {
321               if (cache_add (GETPWBYUID, cp, key_offset, &dataset->head, true,
322                              db, owner) < 0)
323                 {
324                   /* Could not allocate memory.  Make sure the data gets
325                      discarded.  */
326                   dataset->head.usable = false;
327                   goto out;
328                 }
329             }
330           /* If the key is different from the name add a separate entry.  */
331           else if (strcmp (key_copy, dataset->strdata) != 0)
332             {
333               if (cache_add (GETPWBYNAME, key_copy, key_len + 1,
334                              &dataset->head, first, db, owner) < 0)
335                 {
336                   /* Could not allocate memory.  Make sure the data gets
337                      discarded.  */
338                   dataset->head.usable = false;
339                   goto out;
340                 }
341
342               first = false;
343             }
344
345           /* We have to add the value for both, byname and byuid.  */
346           if (__builtin_expect (cache_add (GETPWBYNAME, dataset->strdata,
347                                            pw_name_len, &dataset->head, first,
348                                            db, owner) == 0, 1))
349             {
350               if (req->type == GETPWBYNAME)
351                 (void) cache_add (GETPWBYUID, cp, key_offset, &dataset->head,
352                                   req->type != GETPWBYNAME, db, owner);
353             }
354           else if (first)
355             /* Could not allocate memory.  Make sure the data gets
356                discarded.  */
357             dataset->head.usable = false;
358
359         out:
360           pthread_rwlock_unlock (&db->lock);
361         }
362     }
363
364   if (__builtin_expect (written != total, 0) && debug_level > 0)
365     {
366       char buf[256];
367       dbg_log (_("short write in %s: %s"),  __FUNCTION__,
368                strerror_r (errno, buf, sizeof (buf)));
369     }
370 }
371
372
373 union keytype
374 {
375   void *v;
376   uid_t u;
377 };
378
379
380 static int
381 lookup (int type, union keytype key, struct passwd *resultbufp, char *buffer,
382         size_t buflen, struct passwd **pwd)
383 {
384   if (type == GETPWBYNAME)
385     return __getpwnam_r (key.v, resultbufp, buffer, buflen, pwd);
386   else
387     return __getpwuid_r (key.u, resultbufp, buffer, buflen, pwd);
388 }
389
390
391 static void
392 addpwbyX (struct database_dyn *db, int fd, request_header *req,
393           union keytype key, const char *keystr, uid_t c_uid,
394           struct hashentry *he, struct datahead *dh)
395 {
396   /* Search for the entry matching the key.  Please note that we don't
397      look again in the table whether the dataset is now available.  We
398      simply insert it.  It does not matter if it is in there twice.  The
399      pruning function only will look at the timestamp.  */
400   size_t buflen = 1024;
401   char *buffer = (char *) alloca (buflen);
402   struct passwd resultbuf;
403   struct passwd *pwd;
404   bool use_malloc = false;
405   int errval = 0;
406
407   if (__builtin_expect (debug_level > 0, 0))
408     {
409       if (he == NULL)
410         dbg_log (_("Haven't found \"%s\" in password cache!"), keystr);
411       else
412         dbg_log (_("Reloading \"%s\" in password cache!"), keystr);
413     }
414
415 #if 0
416   uid_t oldeuid = 0;
417   if (db->secure)
418     {
419       oldeuid = geteuid ();
420       pthread_seteuid_np (c_uid);
421     }
422 #endif
423
424   while (lookup (req->type, key, &resultbuf, buffer, buflen, &pwd) != 0
425          && (errval = errno) == ERANGE)
426     {
427       char *old_buffer = buffer;
428       errno = 0;
429
430       if (__builtin_expect (buflen > 32768, 0))
431         {
432           buflen *= 2;
433           buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen);
434           if (buffer == NULL)
435             {
436               /* We ran out of memory.  We cannot do anything but
437                  sending a negative response.  In reality this should
438                  never happen.  */
439               pwd = NULL;
440               buffer = old_buffer;
441
442               /* We set the error to indicate this is (possibly) a
443                  temporary error and that it does not mean the entry
444                  is not available at all.  */
445               errval = EAGAIN;
446               break;
447             }
448           use_malloc = true;
449         }
450       else
451         /* Allocate a new buffer on the stack.  If possible combine it
452            with the previously allocated buffer.  */
453         buffer = (char *) extend_alloca (buffer, buflen, 2 * buflen);
454     }
455
456 #if 0
457   if (db->secure)
458     pthread_seteuid_np (oldeuid);
459 #endif
460
461   /* Add the entry to the cache.  */
462   cache_addpw (db, fd, req, keystr, pwd, c_uid, he, dh, errval);
463
464   if (use_malloc)
465     free (buffer);
466 }
467
468
469 void
470 addpwbyname (struct database_dyn *db, int fd, request_header *req,
471              void *key, uid_t c_uid)
472 {
473   union keytype u = { .v = key };
474
475   addpwbyX (db, fd, req, u, key, c_uid, NULL, NULL);
476 }
477
478
479 void
480 readdpwbyname (struct database_dyn *db, struct hashentry *he,
481                struct datahead *dh)
482 {
483   request_header req =
484     {
485       .type = GETPWBYNAME,
486       .key_len = he->len
487     };
488   union keytype u = { .v = db->data + he->key };
489
490   addpwbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
491 }
492
493
494 void
495 addpwbyuid (struct database_dyn *db, int fd, request_header *req,
496             void *key, uid_t c_uid)
497 {
498   char *ep;
499   uid_t uid = strtoul ((char *) key, &ep, 10);
500
501   if (*(char *) key == '\0' || *ep != '\0')  /* invalid numeric uid */
502     {
503       if (debug_level > 0)
504         dbg_log (_("Invalid numeric uid \"%s\"!"), (char *) key);
505
506       errno = EINVAL;
507       return;
508     }
509
510   union keytype u = { .u = uid };
511
512   addpwbyX (db, fd, req, u, key, c_uid, NULL, NULL);
513 }
514
515
516 void
517 readdpwbyuid (struct database_dyn *db, struct hashentry *he,
518               struct datahead *dh)
519 {
520   char *ep;
521   uid_t uid = strtoul (db->data + he->key, &ep, 10);
522
523   /* Since the key has been added before it must be OK.  */
524   assert (*(db->data + he->key) != '\0' && *ep == '\0');
525
526   request_header req =
527     {
528       .type = GETPWBYUID,
529       .key_len = he->len
530     };
531   union keytype u = { .u = uid };
532
533   addpwbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
534 }