Back to index

glibc  2.9
pwdcache.c
Go to the documentation of this file.
00001 /* Cache handling for passwd lookup.
00002    Copyright (C) 1998-2005, 2006, 2007, 2008 Free Software Foundation, Inc.
00003    This file is part of the GNU C Library.
00004    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License as published
00008    by the Free Software Foundation; version 2 of the License, or
00009    (at your option) any later version.
00010 
00011    This program is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014    GNU General Public License for more details.
00015 
00016    You should have received a copy of the GNU General Public License
00017    along with this program; if not, write to the Free Software Foundation,
00018    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
00019 
00020 #include <alloca.h>
00021 #include <assert.h>
00022 #include <errno.h>
00023 #include <error.h>
00024 #include <libintl.h>
00025 #include <pwd.h>
00026 #include <stdbool.h>
00027 #include <stddef.h>
00028 #include <stdio.h>
00029 #include <stdlib.h>
00030 #include <string.h>
00031 #include <time.h>
00032 #include <unistd.h>
00033 #include <sys/mman.h>
00034 #include <sys/socket.h>
00035 #include <stackinfo.h>
00036 
00037 #include "nscd.h"
00038 #include "dbg_log.h"
00039 #ifdef HAVE_SENDFILE
00040 # include <kernel-features.h>
00041 #endif
00042 
00043 /* This is the standard reply in case the service is disabled.  */
00044 static const pw_response_header disabled =
00045 {
00046   .version = NSCD_VERSION,
00047   .found = -1,
00048   .pw_name_len = 0,
00049   .pw_passwd_len = 0,
00050   .pw_uid = -1,
00051   .pw_gid = -1,
00052   .pw_gecos_len = 0,
00053   .pw_dir_len = 0,
00054   .pw_shell_len = 0
00055 };
00056 
00057 /* This is the struct describing how to write this record.  */
00058 const struct iovec pwd_iov_disabled =
00059 {
00060   .iov_base = (void *) &disabled,
00061   .iov_len = sizeof (disabled)
00062 };
00063 
00064 
00065 /* This is the standard reply in case we haven't found the dataset.  */
00066 static const pw_response_header notfound =
00067 {
00068   .version = NSCD_VERSION,
00069   .found = 0,
00070   .pw_name_len = 0,
00071   .pw_passwd_len = 0,
00072   .pw_uid = -1,
00073   .pw_gid = -1,
00074   .pw_gecos_len = 0,
00075   .pw_dir_len = 0,
00076   .pw_shell_len = 0
00077 };
00078 
00079 
00080 static void
00081 cache_addpw (struct database_dyn *db, int fd, request_header *req,
00082             const void *key, struct passwd *pwd, uid_t owner,
00083             struct hashentry *he, struct datahead *dh, int errval)
00084 {
00085   ssize_t total;
00086   ssize_t written;
00087   time_t t = time (NULL);
00088 
00089   /* We allocate all data in one memory block: the iov vector,
00090      the response header and the dataset itself.  */
00091   struct dataset
00092   {
00093     struct datahead head;
00094     pw_response_header resp;
00095     char strdata[0];
00096   } *dataset;
00097 
00098   assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data));
00099 
00100   if (pwd == NULL)
00101     {
00102       if (he != NULL && errval == EAGAIN)
00103        {
00104          /* If we have an old record available but cannot find one
00105             now because the service is not available we keep the old
00106             record and make sure it does not get removed.  */
00107          if (reload_count != UINT_MAX && dh->nreloads == reload_count)
00108            /* Do not reset the value if we never not reload the record.  */
00109            dh->nreloads = reload_count - 1;
00110 
00111          written = total = 0;
00112        }
00113       else
00114        {
00115          /* We have no data.  This means we send the standard reply for this
00116             case.  */
00117          written = total = sizeof (notfound);
00118 
00119          if (fd != -1)
00120            written = TEMP_FAILURE_RETRY (send (fd, &notfound, total,
00121                                           MSG_NOSIGNAL));
00122 
00123          dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len,
00124                                IDX_result_data);
00125          /* If we cannot permanently store the result, so be it.  */
00126          if (dataset != NULL)
00127            {
00128              dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
00129              dataset->head.recsize = total;
00130              dataset->head.notfound = true;
00131              dataset->head.nreloads = 0;
00132              dataset->head.usable = true;
00133 
00134              /* Compute the timeout time.  */
00135              dataset->head.timeout = t + db->negtimeout;
00136 
00137              /* This is the reply.  */
00138              memcpy (&dataset->resp, &notfound, total);
00139 
00140              /* Copy the key data.  */
00141              char *key_copy = memcpy (dataset->strdata, key, req->key_len);
00142 
00143              /* If necessary, we also propagate the data to disk.  */
00144              if (db->persistent)
00145               {
00146                 // XXX async OK?
00147                 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
00148                 msync ((void *) pval,
00149                       ((uintptr_t) dataset & pagesize_m1)
00150                       + sizeof (struct dataset) + req->key_len, MS_ASYNC);
00151               }
00152 
00153              /* Now get the lock to safely insert the records.  */
00154              pthread_rwlock_rdlock (&db->lock);
00155 
00156              (void) cache_add (req->type, key_copy, req->key_len,
00157                             &dataset->head, true, db, owner, he == NULL);
00158 
00159              pthread_rwlock_unlock (&db->lock);
00160 
00161              /* Mark the old entry as obsolete.  */
00162              if (dh != NULL)
00163               dh->usable = false;
00164            }
00165          else
00166            ++db->head->addfailed;
00167        }
00168     }
00169   else
00170     {
00171       /* Determine the I/O structure.  */
00172       size_t pw_name_len = strlen (pwd->pw_name) + 1;
00173       size_t pw_passwd_len = strlen (pwd->pw_passwd) + 1;
00174       size_t pw_gecos_len = strlen (pwd->pw_gecos) + 1;
00175       size_t pw_dir_len = strlen (pwd->pw_dir) + 1;
00176       size_t pw_shell_len = strlen (pwd->pw_shell) + 1;
00177       char *cp;
00178       const size_t key_len = strlen (key);
00179       const size_t buf_len = 3 * sizeof (pwd->pw_uid) + key_len + 1;
00180       char *buf = alloca (buf_len);
00181       ssize_t n;
00182 
00183       /* We need this to insert the `byuid' entry.  */
00184       int key_offset;
00185       n = snprintf (buf, buf_len, "%d%c%n%s", pwd->pw_uid, '\0',
00186                   &key_offset, (char *) key) + 1;
00187 
00188       written = total = (offsetof (struct dataset, strdata)
00189                       + pw_name_len + pw_passwd_len
00190                       + pw_gecos_len + pw_dir_len + pw_shell_len);
00191 
00192       /* If we refill the cache, first assume the reconrd did not
00193         change.  Allocate memory on the cache since it is likely
00194         discarded anyway.  If it turns out to be necessary to have a
00195         new record we can still allocate real memory.  */
00196       bool alloca_used = false;
00197       dataset = NULL;
00198 
00199       if (he == NULL)
00200        {
00201          dataset = (struct dataset *) mempool_alloc (db, total + n,
00202                                                 IDX_result_data);
00203          if (dataset == NULL)
00204            ++db->head->addfailed;
00205        }
00206 
00207       if (dataset == NULL)
00208        {
00209          /* We cannot permanently add the result in the moment.  But
00210             we can provide the result as is.  Store the data in some
00211             temporary memory.  */
00212          dataset = (struct dataset *) alloca (total + n);
00213 
00214          /* We cannot add this record to the permanent database.  */
00215          alloca_used = true;
00216        }
00217 
00218       dataset->head.allocsize = total + n;
00219       dataset->head.recsize = total - offsetof (struct dataset, resp);
00220       dataset->head.notfound = false;
00221       dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
00222       dataset->head.usable = true;
00223 
00224       /* Compute the timeout time.  */
00225       dataset->head.timeout = t + db->postimeout;
00226 
00227       dataset->resp.version = NSCD_VERSION;
00228       dataset->resp.found = 1;
00229       dataset->resp.pw_name_len = pw_name_len;
00230       dataset->resp.pw_passwd_len = pw_passwd_len;
00231       dataset->resp.pw_uid = pwd->pw_uid;
00232       dataset->resp.pw_gid = pwd->pw_gid;
00233       dataset->resp.pw_gecos_len = pw_gecos_len;
00234       dataset->resp.pw_dir_len = pw_dir_len;
00235       dataset->resp.pw_shell_len = pw_shell_len;
00236 
00237       cp = dataset->strdata;
00238 
00239       /* Copy the strings over into the buffer.  */
00240       cp = mempcpy (cp, pwd->pw_name, pw_name_len);
00241       cp = mempcpy (cp, pwd->pw_passwd, pw_passwd_len);
00242       cp = mempcpy (cp, pwd->pw_gecos, pw_gecos_len);
00243       cp = mempcpy (cp, pwd->pw_dir, pw_dir_len);
00244       cp = mempcpy (cp, pwd->pw_shell, pw_shell_len);
00245 
00246       /* Finally the stringified UID value.  */
00247       memcpy (cp, buf, n);
00248       char *key_copy = cp + key_offset;
00249       assert (key_copy == (char *) rawmemchr (cp, '\0') + 1);
00250 
00251       assert (cp == dataset->strdata + total - offsetof (struct dataset,
00252                                                   strdata));
00253 
00254       /* Now we can determine whether on refill we have to create a new
00255         record or not.  */
00256       if (he != NULL)
00257        {
00258          assert (fd == -1);
00259 
00260 #if 0
00261          if (dataset->head.datasize == dh->allocsize
00262              && dataset->head.recsize == dh->recsize
00263              && memcmp (&dataset->resp, dh->data,
00264                       dh->allocsize - offsetof (struct dataset, resp)) == 0)
00265 #else
00266          if (dataset->head.allocsize != dh->allocsize)
00267            goto nnn;
00268          if (dataset->head.recsize != dh->recsize)
00269            goto nnn;
00270          if(memcmp (&dataset->resp, dh->data,
00271                       dh->allocsize - offsetof (struct dataset, resp)) == 0)
00272 #endif
00273            {
00274              /* The data has not changed.  We will just bump the
00275                timeout value.  Note that the new record has been
00276                allocated on the stack and need not be freed.  */
00277              dh->timeout = dataset->head.timeout;
00278              ++dh->nreloads;
00279            }
00280          else
00281            {
00282  nnn:;
00283              /* We have to create a new record.  Just allocate
00284                appropriate memory and copy it.  */
00285              struct dataset *newp
00286               = (struct dataset *) mempool_alloc (db, total + n,
00287                                               IDX_result_data);
00288              if (newp != NULL)
00289               {
00290                 /* Adjust pointer into the memory block.  */
00291                 cp = (char *) newp + (cp - (char *) dataset);
00292                 key_copy = (char *) newp + (key_copy - (char *) dataset);
00293 
00294                 dataset = memcpy (newp, dataset, total + n);
00295                 alloca_used = false;
00296               }
00297              else
00298               ++db->head->addfailed;
00299 
00300              /* Mark the old record as obsolete.  */
00301              dh->usable = false;
00302            }
00303        }
00304       else
00305        {
00306          /* We write the dataset before inserting it to the database
00307             since while inserting this thread might block and so would
00308             unnecessarily let the receiver wait.  */
00309          assert (fd != -1);
00310 
00311 #ifdef HAVE_SENDFILE
00312          if (__builtin_expect (db->mmap_used, 1) && !alloca_used)
00313            {
00314              assert (db->wr_fd != -1);
00315              assert ((char *) &dataset->resp > (char *) db->data);
00316              assert ((char *) &dataset->resp - (char *) db->head
00317                     + total
00318                     <= (sizeof (struct database_pers_head)
00319                           + db->head->module * sizeof (ref_t)
00320                           + db->head->data_size));
00321              written = sendfileall (fd, db->wr_fd,
00322                                  (char *) &dataset->resp
00323                                  - (char *) db->head, total);
00324 # ifndef __ASSUME_SENDFILE
00325              if (written == -1 && errno == ENOSYS)
00326               goto use_write;
00327 # endif
00328            }
00329          else
00330 # ifndef __ASSUME_SENDFILE
00331          use_write:
00332 # endif
00333 #endif
00334            written = writeall (fd, &dataset->resp, total);
00335        }
00336 
00337 
00338       /* Add the record to the database.  But only if it has not been
00339         stored on the stack.  */
00340       if (! alloca_used)
00341        {
00342          /* If necessary, we also propagate the data to disk.  */
00343          if (db->persistent)
00344            {
00345              // XXX async OK?
00346              uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
00347              msync ((void *) pval,
00348                    ((uintptr_t) dataset & pagesize_m1) + total + n,
00349                    MS_ASYNC);
00350            }
00351 
00352          /* Now get the lock to safely insert the records.  */
00353          pthread_rwlock_rdlock (&db->lock);
00354 
00355          /* NB: in the following code we always must add the entry
00356             marked with FIRST first.  Otherwise we end up with
00357             dangling "pointers" in case a latter hash entry cannot be
00358             added.  */
00359          bool first = true;
00360 
00361          /* If the request was by UID, add that entry first.  */
00362          if (req->type == GETPWBYUID)
00363            {
00364              if (cache_add (GETPWBYUID, cp, key_offset, &dataset->head, true,
00365                           db, owner, he == NULL) < 0)
00366               goto out;
00367 
00368              first = false;
00369            }
00370          /* If the key is different from the name add a separate entry.  */
00371          else if (strcmp (key_copy, dataset->strdata) != 0)
00372            {
00373              if (cache_add (GETPWBYNAME, key_copy, key_len + 1,
00374                           &dataset->head, true, db, owner, he == NULL) < 0)
00375               goto out;
00376 
00377              first = false;
00378            }
00379 
00380          /* We have to add the value for both, byname and byuid.  */
00381          if ((req->type == GETPWBYNAME || db->propagate)
00382              && __builtin_expect (cache_add (GETPWBYNAME, dataset->strdata,
00383                                          pw_name_len, &dataset->head,
00384                                          first, db, owner, he == NULL)
00385                                == 0, 1))
00386            {
00387              if (req->type == GETPWBYNAME && db->propagate)
00388               (void) cache_add (GETPWBYUID, cp, key_offset, &dataset->head,
00389                               false, db, owner, false);
00390            }
00391 
00392        out:
00393          pthread_rwlock_unlock (&db->lock);
00394        }
00395     }
00396 
00397   if (__builtin_expect (written != total, 0) && debug_level > 0)
00398     {
00399       char buf[256];
00400       dbg_log (_("short write in %s: %s"),  __FUNCTION__,
00401               strerror_r (errno, buf, sizeof (buf)));
00402     }
00403 }
00404 
00405 
00406 union keytype
00407 {
00408   void *v;
00409   uid_t u;
00410 };
00411 
00412 
00413 static int
00414 lookup (int type, union keytype key, struct passwd *resultbufp, char *buffer,
00415        size_t buflen, struct passwd **pwd)
00416 {
00417   if (type == GETPWBYNAME)
00418     return __getpwnam_r (key.v, resultbufp, buffer, buflen, pwd);
00419   else
00420     return __getpwuid_r (key.u, resultbufp, buffer, buflen, pwd);
00421 }
00422 
00423 
00424 static void
00425 addpwbyX (struct database_dyn *db, int fd, request_header *req,
00426          union keytype key, const char *keystr, uid_t c_uid,
00427          struct hashentry *he, struct datahead *dh)
00428 {
00429   /* Search for the entry matching the key.  Please note that we don't
00430      look again in the table whether the dataset is now available.  We
00431      simply insert it.  It does not matter if it is in there twice.  The
00432      pruning function only will look at the timestamp.  */
00433   size_t buflen = 1024;
00434   char *buffer = (char *) alloca (buflen);
00435   struct passwd resultbuf;
00436   struct passwd *pwd;
00437   bool use_malloc = false;
00438   int errval = 0;
00439 
00440   if (__builtin_expect (debug_level > 0, 0))
00441     {
00442       if (he == NULL)
00443        dbg_log (_("Haven't found \"%s\" in password cache!"), keystr);
00444       else
00445        dbg_log (_("Reloading \"%s\" in password cache!"), keystr);
00446     }
00447 
00448   while (lookup (req->type, key, &resultbuf, buffer, buflen, &pwd) != 0
00449         && (errval = errno) == ERANGE)
00450     {
00451       errno = 0;
00452 
00453       if (__builtin_expect (buflen > 32768, 0))
00454        {
00455          char *old_buffer = buffer;
00456          buflen *= 2;
00457          buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen);
00458          if (buffer == NULL)
00459            {
00460              /* We ran out of memory.  We cannot do anything but
00461                sending a negative response.  In reality this should
00462                never happen.  */
00463              pwd = NULL;
00464              buffer = old_buffer;
00465 
00466              /* We set the error to indicate this is (possibly) a
00467                temporary error and that it does not mean the entry
00468                is not available at all.  */
00469              errval = EAGAIN;
00470              break;
00471            }
00472          use_malloc = true;
00473        }
00474       else
00475        /* Allocate a new buffer on the stack.  If possible combine it
00476           with the previously allocated buffer.  */
00477        buffer = (char *) extend_alloca (buffer, buflen, 2 * buflen);
00478     }
00479 
00480   /* Add the entry to the cache.  */
00481   cache_addpw (db, fd, req, keystr, pwd, c_uid, he, dh, errval);
00482 
00483   if (use_malloc)
00484     free (buffer);
00485 }
00486 
00487 
00488 void
00489 addpwbyname (struct database_dyn *db, int fd, request_header *req,
00490             void *key, uid_t c_uid)
00491 {
00492   union keytype u = { .v = key };
00493 
00494   addpwbyX (db, fd, req, u, key, c_uid, NULL, NULL);
00495 }
00496 
00497 
00498 void
00499 readdpwbyname (struct database_dyn *db, struct hashentry *he,
00500               struct datahead *dh)
00501 {
00502   request_header req =
00503     {
00504       .type = GETPWBYNAME,
00505       .key_len = he->len
00506     };
00507   union keytype u = { .v = db->data + he->key };
00508 
00509   addpwbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
00510 }
00511 
00512 
00513 void
00514 addpwbyuid (struct database_dyn *db, int fd, request_header *req,
00515            void *key, uid_t c_uid)
00516 {
00517   char *ep;
00518   uid_t uid = strtoul ((char *) key, &ep, 10);
00519 
00520   if (*(char *) key == '\0' || *ep != '\0')  /* invalid numeric uid */
00521     {
00522       if (debug_level > 0)
00523         dbg_log (_("Invalid numeric uid \"%s\"!"), (char *) key);
00524 
00525       errno = EINVAL;
00526       return;
00527     }
00528 
00529   union keytype u = { .u = uid };
00530 
00531   addpwbyX (db, fd, req, u, key, c_uid, NULL, NULL);
00532 }
00533 
00534 
00535 void
00536 readdpwbyuid (struct database_dyn *db, struct hashentry *he,
00537              struct datahead *dh)
00538 {
00539   char *ep;
00540   uid_t uid = strtoul (db->data + he->key, &ep, 10);
00541 
00542   /* Since the key has been added before it must be OK.  */
00543   assert (*(db->data + he->key) != '\0' && *ep == '\0');
00544 
00545   request_header req =
00546     {
00547       .type = GETPWBYUID,
00548       .key_len = he->len
00549     };
00550   union keytype u = { .u = uid };
00551 
00552   addpwbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
00553 }