aboutsummaryrefslogtreecommitdiff
path: root/REORG.TODO/nscd/pwdcache.c
diff options
context:
space:
mode:
Diffstat (limited to 'REORG.TODO/nscd/pwdcache.c')
-rw-r--r--REORG.TODO/nscd/pwdcache.c552
1 files changed, 552 insertions, 0 deletions
diff --git a/REORG.TODO/nscd/pwdcache.c b/REORG.TODO/nscd/pwdcache.c
new file mode 100644
index 0000000000..721f4c617b
--- /dev/null
+++ b/REORG.TODO/nscd/pwdcache.c
@@ -0,0 +1,552 @@
+/* Cache handling for passwd lookup.
+ Copyright (C) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>. */
+
+#include <alloca.h>
+#include <assert.h>
+#include <errno.h>
+#include <error.h>
+#include <libintl.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <stackinfo.h>
+
+#include "nscd.h"
+#include "dbg_log.h"
+#ifdef HAVE_SENDFILE
+# include <kernel-features.h>
+#endif
+
+/* This is the standard reply in case the service is disabled. */
+static const pw_response_header disabled =
+{
+ .version = NSCD_VERSION,
+ .found = -1,
+ .pw_name_len = 0,
+ .pw_passwd_len = 0,
+ .pw_uid = -1,
+ .pw_gid = -1,
+ .pw_gecos_len = 0,
+ .pw_dir_len = 0,
+ .pw_shell_len = 0
+};
+
+/* This is the struct describing how to write this record. */
+const struct iovec pwd_iov_disabled =
+{
+ .iov_base = (void *) &disabled,
+ .iov_len = sizeof (disabled)
+};
+
+
+/* This is the standard reply in case we haven't found the dataset. */
+static const pw_response_header notfound =
+{
+ .version = NSCD_VERSION,
+ .found = 0,
+ .pw_name_len = 0,
+ .pw_passwd_len = 0,
+ .pw_uid = -1,
+ .pw_gid = -1,
+ .pw_gecos_len = 0,
+ .pw_dir_len = 0,
+ .pw_shell_len = 0
+};
+
+
+static time_t
+cache_addpw (struct database_dyn *db, int fd, request_header *req,
+ const void *key, struct passwd *pwd, uid_t owner,
+ struct hashentry *const he, struct datahead *dh, int errval)
+{
+ bool all_written = true;
+ ssize_t total;
+ time_t t = time (NULL);
+
+ /* We allocate all data in one memory block: the iov vector,
+ the response header and the dataset itself. */
+ struct dataset
+ {
+ struct datahead head;
+ pw_response_header resp;
+ char strdata[0];
+ } *dataset;
+
+ assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data));
+
+ time_t timeout = MAX_TIMEOUT_VALUE;
+ if (pwd == NULL)
+ {
+ if (he != NULL && errval == EAGAIN)
+ {
+ /* If we have an old record available but cannot find one
+ now because the service is not available we keep the old
+ record and make sure it does not get removed. */
+ if (reload_count != UINT_MAX && dh->nreloads == reload_count)
+ /* Do not reset the value if we never not reload the record. */
+ dh->nreloads = reload_count - 1;
+
+ /* Reload with the same time-to-live value. */
+ timeout = dh->timeout = t + db->postimeout;
+
+ total = 0;
+ }
+ else
+ {
+ /* We have no data. This means we send the standard reply for this
+ case. */
+ total = sizeof (notfound);
+
+ if (fd != -1
+ && TEMP_FAILURE_RETRY (send (fd, &notfound, total,
+ MSG_NOSIGNAL)) != total)
+ all_written = false;
+
+ /* If we have a transient error or cannot permanently store
+ the result, so be it. */
+ if (errno == EAGAIN || __builtin_expect (db->negtimeout == 0, 0))
+ {
+ /* Mark the old entry as obsolete. */
+ if (dh != NULL)
+ dh->usable = false;
+ }
+ else if ((dataset = mempool_alloc (db, (sizeof (struct dataset)
+ + req->key_len), 1)) != NULL)
+ {
+ timeout = datahead_init_neg (&dataset->head,
+ (sizeof (struct dataset)
+ + req->key_len), total,
+ db->negtimeout);
+
+ /* This is the reply. */
+ memcpy (&dataset->resp, &notfound, total);
+
+ /* Copy the key data. */
+ char *key_copy = memcpy (dataset->strdata, key, req->key_len);
+
+ /* If necessary, we also propagate the data to disk. */
+ if (db->persistent)
+ {
+ // XXX async OK?
+ uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
+ msync ((void *) pval,
+ ((uintptr_t) dataset & pagesize_m1)
+ + sizeof (struct dataset) + req->key_len, MS_ASYNC);
+ }
+
+ (void) cache_add (req->type, key_copy, req->key_len,
+ &dataset->head, true, db, owner, he == NULL);
+
+ pthread_rwlock_unlock (&db->lock);
+
+ /* Mark the old entry as obsolete. */
+ if (dh != NULL)
+ dh->usable = false;
+ }
+ }
+ }
+ else
+ {
+ /* Determine the I/O structure. */
+ size_t pw_name_len = strlen (pwd->pw_name) + 1;
+ size_t pw_passwd_len = strlen (pwd->pw_passwd) + 1;
+ size_t pw_gecos_len = strlen (pwd->pw_gecos) + 1;
+ size_t pw_dir_len = strlen (pwd->pw_dir) + 1;
+ size_t pw_shell_len = strlen (pwd->pw_shell) + 1;
+ char *cp;
+ const size_t key_len = strlen (key);
+ const size_t buf_len = 3 * sizeof (pwd->pw_uid) + key_len + 1;
+ char *buf = alloca (buf_len);
+ ssize_t n;
+
+ /* We need this to insert the `byuid' entry. */
+ int key_offset;
+ n = snprintf (buf, buf_len, "%d%c%n%s", pwd->pw_uid, '\0',
+ &key_offset, (char *) key) + 1;
+
+ total = (offsetof (struct dataset, strdata)
+ + pw_name_len + pw_passwd_len
+ + pw_gecos_len + pw_dir_len + pw_shell_len);
+
+ /* If we refill the cache, first assume the reconrd did not
+ change. Allocate memory on the cache since it is likely
+ discarded anyway. If it turns out to be necessary to have a
+ new record we can still allocate real memory. */
+ bool alloca_used = false;
+ dataset = NULL;
+
+ if (he == NULL)
+ {
+ /* Prevent an INVALIDATE request from pruning the data between
+ the two calls to cache_add. */
+ if (db->propagate)
+ pthread_mutex_lock (&db->prune_run_lock);
+ dataset = (struct dataset *) mempool_alloc (db, total + n, 1);
+ }
+
+ if (dataset == NULL)
+ {
+ if (he == NULL && db->propagate)
+ pthread_mutex_unlock (&db->prune_run_lock);
+
+ /* We cannot permanently add the result in the moment. But
+ we can provide the result as is. Store the data in some
+ temporary memory. */
+ dataset = (struct dataset *) alloca (total + n);
+
+ /* We cannot add this record to the permanent database. */
+ alloca_used = true;
+ }
+
+ timeout = datahead_init_pos (&dataset->head, total + n,
+ total - offsetof (struct dataset, resp),
+ he == NULL ? 0 : dh->nreloads + 1,
+ db->postimeout);
+
+ dataset->resp.version = NSCD_VERSION;
+ dataset->resp.found = 1;
+ dataset->resp.pw_name_len = pw_name_len;
+ dataset->resp.pw_passwd_len = pw_passwd_len;
+ dataset->resp.pw_uid = pwd->pw_uid;
+ dataset->resp.pw_gid = pwd->pw_gid;
+ dataset->resp.pw_gecos_len = pw_gecos_len;
+ dataset->resp.pw_dir_len = pw_dir_len;
+ dataset->resp.pw_shell_len = pw_shell_len;
+
+ cp = dataset->strdata;
+
+ /* Copy the strings over into the buffer. */
+ cp = mempcpy (cp, pwd->pw_name, pw_name_len);
+ cp = mempcpy (cp, pwd->pw_passwd, pw_passwd_len);
+ cp = mempcpy (cp, pwd->pw_gecos, pw_gecos_len);
+ cp = mempcpy (cp, pwd->pw_dir, pw_dir_len);
+ cp = mempcpy (cp, pwd->pw_shell, pw_shell_len);
+
+ /* Finally the stringified UID value. */
+ memcpy (cp, buf, n);
+ char *key_copy = cp + key_offset;
+ assert (key_copy == (char *) rawmemchr (cp, '\0') + 1);
+
+ assert (cp == dataset->strdata + total - offsetof (struct dataset,
+ strdata));
+
+ /* Now we can determine whether on refill we have to create a new
+ record or not. */
+ if (he != NULL)
+ {
+ assert (fd == -1);
+
+ if (dataset->head.allocsize == dh->allocsize
+ && dataset->head.recsize == dh->recsize
+ && memcmp (&dataset->resp, dh->data,
+ dh->allocsize - offsetof (struct dataset, resp)) == 0)
+ {
+ /* The data has not changed. We will just bump the
+ timeout value. Note that the new record has been
+ allocated on the stack and need not be freed. */
+ dh->timeout = dataset->head.timeout;
+ ++dh->nreloads;
+ }
+ else
+ {
+ /* We have to create a new record. Just allocate
+ appropriate memory and copy it. */
+ struct dataset *newp
+ = (struct dataset *) mempool_alloc (db, total + n, 1);
+ if (newp != NULL)
+ {
+ /* Adjust pointer into the memory block. */
+ cp = (char *) newp + (cp - (char *) dataset);
+ key_copy = (char *) newp + (key_copy - (char *) dataset);
+
+ dataset = memcpy (newp, dataset, total + n);
+ alloca_used = false;
+ }
+
+ /* Mark the old record as obsolete. */
+ dh->usable = false;
+ }
+ }
+ else
+ {
+ /* We write the dataset before inserting it to the database
+ since while inserting this thread might block and so would
+ unnecessarily let the receiver wait. */
+ assert (fd != -1);
+
+#ifdef HAVE_SENDFILE
+ if (__builtin_expect (db->mmap_used, 1) && !alloca_used)
+ {
+ assert (db->wr_fd != -1);
+ assert ((char *) &dataset->resp > (char *) db->data);
+ assert ((char *) dataset - (char *) db->head
+ + total
+ <= (sizeof (struct database_pers_head)
+ + db->head->module * sizeof (ref_t)
+ + db->head->data_size));
+ ssize_t written = sendfileall (fd, db->wr_fd,
+ (char *) &dataset->resp
+ - (char *) db->head,
+ dataset->head.recsize);
+ if (written != dataset->head.recsize)
+ {
+# ifndef __ASSUME_SENDFILE
+ if (written == -1 && errno == ENOSYS)
+ goto use_write;
+# endif
+ all_written = false;
+ }
+ }
+ else
+# ifndef __ASSUME_SENDFILE
+ use_write:
+# endif
+#endif
+ if (writeall (fd, &dataset->resp, dataset->head.recsize)
+ != dataset->head.recsize)
+ all_written = false;
+ }
+
+
+ /* Add the record to the database. But only if it has not been
+ stored on the stack. */
+ if (! alloca_used)
+ {
+ /* If necessary, we also propagate the data to disk. */
+ if (db->persistent)
+ {
+ // XXX async OK?
+ uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
+ msync ((void *) pval,
+ ((uintptr_t) dataset & pagesize_m1) + total + n,
+ MS_ASYNC);
+ }
+
+ /* NB: in the following code we always must add the entry
+ marked with FIRST first. Otherwise we end up with
+ dangling "pointers" in case a latter hash entry cannot be
+ added. */
+ bool first = true;
+
+ /* If the request was by UID, add that entry first. */
+ if (req->type == GETPWBYUID)
+ {
+ if (cache_add (GETPWBYUID, cp, key_offset, &dataset->head, true,
+ db, owner, he == NULL) < 0)
+ goto out;
+
+ first = false;
+ }
+ /* If the key is different from the name add a separate entry. */
+ else if (strcmp (key_copy, dataset->strdata) != 0)
+ {
+ if (cache_add (GETPWBYNAME, key_copy, key_len + 1,
+ &dataset->head, true, db, owner, he == NULL) < 0)
+ goto out;
+
+ first = false;
+ }
+
+ /* We have to add the value for both, byname and byuid. */
+ if ((req->type == GETPWBYNAME || db->propagate)
+ && __builtin_expect (cache_add (GETPWBYNAME, dataset->strdata,
+ pw_name_len, &dataset->head,
+ first, db, owner, he == NULL)
+ == 0, 1))
+ {
+ if (req->type == GETPWBYNAME && db->propagate)
+ (void) cache_add (GETPWBYUID, cp, key_offset, &dataset->head,
+ false, db, owner, false);
+ }
+
+ out:
+ pthread_rwlock_unlock (&db->lock);
+ if (he == NULL && db->propagate)
+ pthread_mutex_unlock (&db->prune_run_lock);
+ }
+ }
+
+ if (__builtin_expect (!all_written, 0) && debug_level > 0)
+ {
+ char buf[256];
+ dbg_log (_("short write in %s: %s"), __FUNCTION__,
+ strerror_r (errno, buf, sizeof (buf)));
+ }
+
+ return timeout;
+}
+
+
+union keytype
+{
+ void *v;
+ uid_t u;
+};
+
+
+static int
+lookup (int type, union keytype key, struct passwd *resultbufp, char *buffer,
+ size_t buflen, struct passwd **pwd)
+{
+ if (type == GETPWBYNAME)
+ return __getpwnam_r (key.v, resultbufp, buffer, buflen, pwd);
+ else
+ return __getpwuid_r (key.u, resultbufp, buffer, buflen, pwd);
+}
+
+
+static time_t
+addpwbyX (struct database_dyn *db, int fd, request_header *req,
+ union keytype key, const char *keystr, uid_t c_uid,
+ struct hashentry *he, struct datahead *dh)
+{
+ /* Search for the entry matching the key. Please note that we don't
+ look again in the table whether the dataset is now available. We
+ simply insert it. It does not matter if it is in there twice. The
+ pruning function only will look at the timestamp. */
+ size_t buflen = 1024;
+ char *buffer = (char *) alloca (buflen);
+ struct passwd resultbuf;
+ struct passwd *pwd;
+ bool use_malloc = false;
+ int errval = 0;
+
+ if (__glibc_unlikely (debug_level > 0))
+ {
+ if (he == NULL)
+ dbg_log (_("Haven't found \"%s\" in password cache!"), keystr);
+ else
+ dbg_log (_("Reloading \"%s\" in password cache!"), keystr);
+ }
+
+ while (lookup (req->type, key, &resultbuf, buffer, buflen, &pwd) != 0
+ && (errval = errno) == ERANGE)
+ {
+ errno = 0;
+
+ if (__glibc_unlikely (buflen > 32768))
+ {
+ char *old_buffer = buffer;
+ buflen *= 2;
+ buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen);
+ if (buffer == NULL)
+ {
+ /* We ran out of memory. We cannot do anything but
+ sending a negative response. In reality this should
+ never happen. */
+ pwd = NULL;
+ buffer = old_buffer;
+
+ /* We set the error to indicate this is (possibly) a
+ temporary error and that it does not mean the entry
+ is not available at all. */
+ errval = EAGAIN;
+ break;
+ }
+ use_malloc = true;
+ }
+ else
+ /* Allocate a new buffer on the stack. If possible combine it
+ with the previously allocated buffer. */
+ buffer = (char *) extend_alloca (buffer, buflen, 2 * buflen);
+ }
+
+ /* Add the entry to the cache. */
+ time_t timeout = cache_addpw (db, fd, req, keystr, pwd, c_uid, he, dh,
+ errval);
+
+ if (use_malloc)
+ free (buffer);
+
+ return timeout;
+}
+
+
+void
+addpwbyname (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t c_uid)
+{
+ union keytype u = { .v = key };
+
+ addpwbyX (db, fd, req, u, key, c_uid, NULL, NULL);
+}
+
+
+time_t
+readdpwbyname (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh)
+{
+ request_header req =
+ {
+ .type = GETPWBYNAME,
+ .key_len = he->len
+ };
+ union keytype u = { .v = db->data + he->key };
+
+ return addpwbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
+}
+
+
+void
+addpwbyuid (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t c_uid)
+{
+ char *ep;
+ uid_t uid = strtoul ((char *) key, &ep, 10);
+
+ if (*(char *) key == '\0' || *ep != '\0') /* invalid numeric uid */
+ {
+ if (debug_level > 0)
+ dbg_log (_("Invalid numeric uid \"%s\"!"), (char *) key);
+
+ errno = EINVAL;
+ return;
+ }
+
+ union keytype u = { .u = uid };
+
+ addpwbyX (db, fd, req, u, key, c_uid, NULL, NULL);
+}
+
+
+time_t
+readdpwbyuid (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh)
+{
+ char *ep;
+ uid_t uid = strtoul (db->data + he->key, &ep, 10);
+
+ /* Since the key has been added before it must be OK. */
+ assert (*(db->data + he->key) != '\0' && *ep == '\0');
+
+ request_header req =
+ {
+ .type = GETPWBYUID,
+ .key_len = he->len
+ };
+ union keytype u = { .u = uid };
+
+ return addpwbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
+}