support alternate backends for the passwd and group dbs
authorJosiah Worcester <josiahw@gmail.com>
Mon, 23 Feb 2015 02:58:10 +0000 (20:58 -0600)
committerRich Felker <dalias@aerifal.cx>
Mon, 23 Feb 2015 06:02:14 +0000 (01:02 -0500)
when we fail to find the entry in the commonly accepted files,  we
query a server over a Unix domain socket on /var/run/nscd/socket.
the protocol used here is compatible with glibc's nscd protocol on
most systems (all that use 32-bit numbers for all the protocol fields,
which appears to be everything but Alpha).

src/passwd/getgr_a.c
src/passwd/getpw_a.c
src/passwd/nscd.h [new file with mode: 0644]
src/passwd/nscd_query.c [new file with mode: 0644]

index 805e28c..7738c3c 100644 (file)
@@ -1,5 +1,21 @@
 #include <pthread.h>
+#include <byteswap.h>
+#include <string.h>
+#include <unistd.h>
 #include "pwf.h"
+#include "nscd.h"
+
+static char *itoa(char *p, uint32_t x)
+{
+       // number of digits in a uint32_t + NUL
+       p += 11;
+       *--p = 0;
+       do {
+               *--p = '0' + x % 10;
+               x /= 10;
+       } while (x);
+       return p;
+}
 
 int __getgr_a(const char *name, gid_t gid, struct group *gr, char **buf, size_t *size, char ***mem, size_t *nmem, struct group **res)
 {
@@ -10,7 +26,6 @@ int __getgr_a(const char *name, gid_t gid, struct group *gr, char **buf, size_t
        *res = 0;
 
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
-
        f = fopen("/etc/group", "rbe");
        if (!f) {
                rv = errno;
@@ -25,6 +40,129 @@ int __getgr_a(const char *name, gid_t gid, struct group *gr, char **buf, size_t
        }
        fclose(f);
 
+       if (!*res && (rv == 0 || rv == ENOENT || rv == ENOTDIR)) {
+               int32_t req = name ? GETGRBYNAME : GETGRBYGID;
+               int32_t i;
+               const char *key;
+               int32_t groupbuf[GR_LEN] = {0};
+               size_t len = 0;
+               size_t grlist_len = 0;
+               char gidbuf[11] = {0};
+               int swap = 0;
+               char *ptr;
+
+               if (name) {
+                       key = name;
+               } else {
+                       if (gid < 0 || gid > UINT32_MAX) {
+                               rv = 0;
+                               goto done;
+                       }
+                       key = itoa(gidbuf, gid);
+               }
+
+               f = __nscd_query(req, key, groupbuf, sizeof groupbuf, &swap);
+               if (!f) { rv = errno; goto done; }
+               if (f == (FILE*)-1) { rv = 0; goto done; }
+
+               if (!groupbuf[GRFOUND]) { rv = 0; goto cleanup_f; }
+
+               if (!groupbuf[GRNAMELEN] || !groupbuf[GRPASSWDLEN]) {
+                       rv = EIO;
+                       goto cleanup_f;
+               }
+
+               if (groupbuf[GRNAMELEN] > SIZE_MAX - groupbuf[GRPASSWDLEN]) {
+                       rv = ENOMEM;
+                       goto cleanup_f;
+               }
+               len = groupbuf[GRNAMELEN] + groupbuf[GRPASSWDLEN];
+
+               for (i = 0; i < groupbuf[GRMEMCNT]; i++) {
+                       uint32_t name_len;
+                       if (fread(&name_len, sizeof name_len, 1, f) < 1) {
+                               rv = ferror(f) ? errno : EIO;
+                               goto cleanup_f;
+                       }
+                       if (swap) {
+                               name_len = bswap_32(name_len);
+                       }
+                       if (name_len > SIZE_MAX - grlist_len
+                       || name_len > SIZE_MAX - len) {
+                               rv = ENOMEM;
+                               goto cleanup_f;
+                       }
+                       len += name_len;
+                       grlist_len += name_len;
+               }
+
+               if (len > *size || !*buf) {
+                       char *tmp = realloc(*buf, len);
+                       if (!tmp) {
+                               rv = errno;
+                               goto cleanup_f;
+                       }
+                       *buf = tmp;
+                       *size = len;
+               }
+
+               if (!fread(*buf, len, 1, f)) {
+                       rv = ferror(f) ? errno : EIO;
+                       goto cleanup_f;
+               }
+
+               if (groupbuf[GRMEMCNT] + 1 > *nmem) {
+                       if (groupbuf[GRMEMCNT] + 1 > SIZE_MAX/sizeof(char*)) {
+                               rv = ENOMEM;
+                               goto cleanup_f;
+                       }
+                       char **tmp = realloc(*mem, (groupbuf[GRMEMCNT]+1)*sizeof(char*));
+                       if (!tmp) {
+                               rv = errno;
+                               goto cleanup_f;
+                       }
+                       *mem = tmp;
+                       *nmem = groupbuf[GRMEMCNT] + 1;
+               }
+
+               if (groupbuf[GRMEMCNT]) {
+                       mem[0][0] = *buf + groupbuf[GRNAMELEN] + groupbuf[GRPASSWDLEN];
+                       for (ptr = mem[0][0], i = 0; ptr != mem[0][0]+grlist_len; ptr++)
+                               if (!*ptr) mem[0][++i] = ptr+1;
+                       mem[0][i] = 0;
+
+                       if (i != groupbuf[GRMEMCNT]) {
+                               rv = EIO;
+                               goto cleanup_f;
+                       }
+               } else {
+                       mem[0][0] = 0;
+               }
+
+               gr->gr_name = *buf;
+               gr->gr_passwd = gr->gr_name + groupbuf[GRNAMELEN];
+               gr->gr_gid = groupbuf[GRGID];
+               gr->gr_mem = *mem;
+
+               if (gr->gr_passwd[-1]
+               || gr->gr_passwd[groupbuf[GRPASSWDLEN]-1]) {
+                       rv = EIO;
+                       goto cleanup_f;
+               }
+
+               if (name && strcmp(name, gr->gr_name)
+               || !name && gid != gr->gr_gid) {
+                       rv = EIO;
+                       goto cleanup_f;
+               }
+
+               *res = gr;
+
+cleanup_f:
+               fclose(f);
+               goto done;
+       }
+
 done:
        pthread_setcancelstate(cs, 0);
        if (rv) errno = rv;
index 21efc5c..b04663d 100644 (file)
@@ -1,5 +1,21 @@
-#include "pwf.h"
 #include <pthread.h>
+#include <byteswap.h>
+#include <string.h>
+#include <unistd.h>
+#include "pwf.h"
+#include "nscd.h"
+
+static char *itoa(char *p, uint32_t x)
+{
+       // number of digits in a uint32_t + NUL
+       p += 11;
+       *--p = 0;
+       do {
+               *--p = '0' + x % 10;
+               x /= 10;
+       } while (x);
+       return p;
+}
 
 int __getpw_a(const char *name, uid_t uid, struct passwd *pw, char **buf, size_t *size, struct passwd **res)
 {
@@ -24,6 +40,102 @@ int __getpw_a(const char *name, uid_t uid, struct passwd *pw, char **buf, size_t
        }
        fclose(f);
 
+       if (!*res && (rv == 0 || rv == ENOENT || rv == ENOTDIR)) {
+               int32_t req = name ? GETPWBYNAME : GETPWBYUID;
+               const char *key;
+               int32_t passwdbuf[PW_LEN] = {0};
+               size_t len = 0;
+               char uidbuf[11] = {0};
+
+               if (name) {
+                       key = name;
+               } else {
+                       /* uid outside of this range can't be queried with the
+                        * nscd interface, but might happen if uid_t ever
+                        * happens to be a larger type (this is not true as of
+                        * now)
+                        */
+                       if(uid < 0 || uid > UINT32_MAX) {
+                               rv = 0;
+                               goto done;
+                       }
+                       key = itoa(uidbuf, uid);
+               }
+
+               f = __nscd_query(req, key, passwdbuf, sizeof passwdbuf, (int[]){0});
+               if (!f) { rv = errno; goto done; }
+               if (f == (FILE*)-1) { rv = 0; goto done; }
+
+               if(!passwdbuf[PWFOUND]) { rv = 0; goto cleanup_f; }
+
+               /* A zero length response from nscd is invalid. We ignore
+                * invalid responses and just report an error, rather than
+                * trying to do something with them.
+                */
+               if (!passwdbuf[PWNAMELEN] || !passwdbuf[PWPASSWDLEN]
+               || !passwdbuf[PWGECOSLEN] || !passwdbuf[PWDIRLEN]
+               || !passwdbuf[PWSHELLLEN]) {
+                       rv = EIO;
+                       goto cleanup_f;
+               }
+
+               if ((passwdbuf[PWNAMELEN]|passwdbuf[PWPASSWDLEN]
+                    |passwdbuf[PWGECOSLEN]|passwdbuf[PWDIRLEN]
+                    |passwdbuf[PWSHELLLEN]) >= SIZE_MAX/8) {
+                       rv = ENOMEM;
+                       goto cleanup_f;
+               }
+
+               len = passwdbuf[PWNAMELEN] + passwdbuf[PWPASSWDLEN]
+                   + passwdbuf[PWGECOSLEN] + passwdbuf[PWDIRLEN]
+                   + passwdbuf[PWSHELLLEN];
+
+               if (len > *size || !*buf) {
+                       char *tmp = realloc(*buf, len);
+                       if (!tmp) {
+                               rv = errno;
+                               goto cleanup_f;
+                       }
+                       *buf = tmp;
+                       *size = len;
+               }
+
+               if (!fread(*buf, len, 1, f)) {
+                       rv = ferror(f) ? errno : EIO;
+                       goto cleanup_f;
+               }
+
+               pw->pw_name = *buf;
+               pw->pw_passwd = pw->pw_name + passwdbuf[PWNAMELEN];
+               pw->pw_gecos = pw->pw_passwd + passwdbuf[PWPASSWDLEN];
+               pw->pw_dir = pw->pw_gecos + passwdbuf[PWGECOSLEN];
+               pw->pw_shell = pw->pw_dir + passwdbuf[PWDIRLEN];
+               pw->pw_uid = passwdbuf[PWUID];
+               pw->pw_gid = passwdbuf[PWGID];
+
+               /* Don't assume that nscd made sure to null terminate strings.
+                * It's supposed to, but malicious nscd should be ignored
+                * rather than causing a crash.
+                */
+               if (pw->pw_passwd[-1] || pw->pw_gecos[-1] || pw->pw_dir[-1]
+               || pw->pw_shell[passwdbuf[PWSHELLLEN]-1]) {
+                       rv = EIO;
+                       goto cleanup_f;
+               }
+
+               if (name && strcmp(name, pw->pw_name)
+               || !name && uid != pw->pw_uid) {
+                       rv = EIO;
+                       goto cleanup_f;
+               }
+
+
+               *res = pw;
+cleanup_f:
+               fclose(f);
+               goto done;
+       }
+
 done:
        pthread_setcancelstate(cs, 0);
        if (rv) errno = rv;
diff --git a/src/passwd/nscd.h b/src/passwd/nscd.h
new file mode 100644 (file)
index 0000000..102f0b4
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef NSCD_H
+#define NSCD_H
+
+#include <stdint.h>
+
+#define NSCDVERSION 2
+#define GETPWBYNAME 0
+#define GETPWBYUID 1
+#define GETGRBYNAME 2
+#define GETGRBYGID 3
+
+#define REQVERSION 0
+#define REQTYPE 1
+#define REQKEYLEN 2
+#define REQ_LEN 3
+
+#define PWVERSION 0
+#define PWFOUND 1
+#define PWNAMELEN 2
+#define PWPASSWDLEN 3
+#define PWUID 4
+#define PWGID 5
+#define PWGECOSLEN 6
+#define PWDIRLEN 7
+#define PWSHELLLEN 8
+#define PW_LEN 9
+
+#define GRVERSION 0
+#define GRFOUND 1
+#define GRNAMELEN 2
+#define GRPASSWDLEN 3
+#define GRGID 4
+#define GRMEMCNT 5
+#define GR_LEN 6
+
+FILE *__nscd_query(int32_t req, const char *key, int32_t *buf, size_t len, int *swap);
+
+#endif
diff --git a/src/passwd/nscd_query.c b/src/passwd/nscd_query.c
new file mode 100644 (file)
index 0000000..f8d0fc1
--- /dev/null
@@ -0,0 +1,100 @@
+#include <sys/socket.h>
+#include <byteswap.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include "nscd.h"
+
+static const struct {
+       short sun_family;
+       char sun_path[21];
+} addr = {
+       AF_UNIX,
+       "/var/run/nscd/socket"
+};
+
+FILE *__nscd_query(int32_t req, const char *key, int32_t *buf, size_t len, int *swap)
+{
+       size_t i;
+       int fd;
+       FILE *f = 0;
+       int32_t req_buf[REQ_LEN] = {
+               NSCDVERSION,
+               req,
+               strlen(key)+1
+       };
+       struct msghdr msg = {
+               .msg_iov = (struct iovec[]){
+                       {&req_buf, sizeof(req_buf)},
+                       {(char*)key, strlen(key)+1}
+               },
+               .msg_iovlen = 2
+       };
+
+       if (strlen(key) > INT32_MAX - 1) {
+               return (FILE*)-1;
+       }
+
+       *swap = 0;
+retry:
+
+       fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+       if (fd < 0) return NULL;
+
+       if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+               /* If there isn't a running nscd we return -1 to indicate that
+                * that is precisely what happened
+                */
+               if (errno == EACCES || errno == ECONNREFUSED || errno == ENOENT) {
+                       close(fd);
+                       return (FILE *)-1;
+               }
+               goto error;
+       }
+
+       if (sendmsg(fd, &msg, MSG_NOSIGNAL) < 0)
+               goto error;
+
+       if(!(f = fdopen(fd, "r"))) goto error;
+
+       if (!fread(buf, len, 1, f)) {
+               /* If the VERSION entry mismatches nscd will disconnect. The
+                * most likely cause is that the endianness mismatched. So, we
+                * byteswap and try once more. (if we already swapped, just
+                * fail out)
+                */
+               if (ferror(f)) goto error;
+               if (!*swap) {
+                       fclose(f);
+                       for (i = 0; i < sizeof(req_buf)/sizeof(req_buf[0]); i++) {
+                               req_buf[i] = bswap_32(req_buf[i]);
+                       }
+                       *swap = 1;
+                       goto retry;
+               } else {
+                       errno = EIO;
+                       goto error;
+               }
+       }
+
+       if (*swap) {
+               for (i = 0; i < len/sizeof(buf[0]); i++) {
+                       buf[i] = bswap_32(buf[i]);
+               }
+       }
+
+       /* The first entry in every nscd response is the version number. This
+        * really shouldn't happen, and is evidence of some form of malformed
+        * response.
+        */
+       if(buf[0] != NSCDVERSION) {
+               errno = EIO;
+               goto error;
+       }
+
+       return f;
+error:
+       if (f) fclose(f); else close(fd);
+       return 0;
+}