mailing list of musl libc
 help / color / mirror / code / Atom feed
* [PATCH] support alternate backends for the passwd and group dbs
@ 2015-02-23  2:35 Josiah Worcester
  2015-02-23  2:58 ` Josiah Worcester
  0 siblings, 1 reply; 8+ messages in thread
From: Josiah Worcester @ 2015-02-23  2:35 UTC (permalink / raw)
  To: musl; +Cc: Josiah Worcester

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    | 140 +++++++++++++++++++++++++++++++++++++++++++++++-
 src/passwd/getpw_a.c    | 114 ++++++++++++++++++++++++++++++++++++++-
 src/passwd/nscd.h       |  38 +++++++++++++
 src/passwd/nscd_query.c | 125 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 415 insertions(+), 2 deletions(-)
 create mode 100644 src/passwd/nscd.h
 create mode 100644 src/passwd/nscd_query.c

diff --git a/src/passwd/getgr_a.c b/src/passwd/getgr_a.c
index 805e28c..2f01a16 100644
--- a/src/passwd/getgr_a.c
+++ b/src/passwd/getgr_a.c
@@ -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) {
+			char *tmp = realloc(*buf, len);
+			if (!tmp) {
+				rv = errno;
+				goto cleanup_f;
+			}
+			*buf = tmp;
+			*size = len;
+		}
+
+		if (fread(*buf, 1, len, f) < len) {
+			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;
diff --git a/src/passwd/getpw_a.c b/src/passwd/getpw_a.c
index 21efc5c..9af6cab 100644
--- a/src/passwd/getpw_a.c
+++ b/src/passwd/getpw_a.c
@@ -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) {
+			char *tmp = realloc(*buf, len);
+			if (!tmp) {
+				rv = errno;
+				goto cleanup_f;
+			}
+			*buf = tmp;
+			*size = len;
+		}
+
+		if (fread(*buf, 1, len, f) < len) {
+			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
index 0000000..102f0b4
--- /dev/null
+++ b/src/passwd/nscd.h
@@ -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
index 0000000..539cc2a
--- /dev/null
+++ b/src/passwd/nscd_query.c
@@ -0,0 +1,125 @@
+#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"
+};
+
+static ssize_t full_sendmsg(int fd, struct msghdr *m, int flags)
+{
+	ssize_t n, count = 0;
+
+	while (m->msg_iovlen) {
+		n = sendmsg(fd, m, flags);
+		if(n < 0) return n;
+		count += n;
+
+		while (n) {
+			if (n >= m->msg_iov[0].iov_len) {
+				n -= m->msg_iov[0].iov_len;
+				m->msg_iov++;
+				m->msg_iovlen--;
+			} else {
+				m->msg_iov[0].iov_len -= n;
+				n = 0;
+			}
+		}
+	}
+	return count;
+}
+
+FILE *__nscd_query(int32_t req, const char *key, int32_t *buf, size_t len, int *swap)
+{
+	size_t i;
+	int fd;
+	int res = 0;
+	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) {
+			res = -1;
+		}
+		goto error;
+	}
+
+	if (full_sendmsg(fd, &msg, MSG_NOSIGNAL) < 0)
+		goto error;
+
+	f = fdopen(fd, "r");
+
+	if (fread(buf, 1, len, f) < len) {
+		/* 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 (!feof(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 {
+			fclose(f);
+			res = -1;
+			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) {
+		res = -1;
+		errno = EIO;
+		goto error;
+	}
+
+	return f;
+error:
+	fclose(f);
+	return (FILE*)res;
+}
-- 
2.1.4



^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH] support alternate backends for the passwd and group dbs
  2015-02-23  2:35 [PATCH] support alternate backends for the passwd and group dbs Josiah Worcester
@ 2015-02-23  2:58 ` Josiah Worcester
  2015-02-23  6:08   ` Rich Felker
  0 siblings, 1 reply; 8+ messages in thread
From: Josiah Worcester @ 2015-02-23  2:58 UTC (permalink / raw)
  To: musl; +Cc: Josiah Worcester

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    | 140 +++++++++++++++++++++++++++++++++++++++++++++++-
 src/passwd/getpw_a.c    | 114 ++++++++++++++++++++++++++++++++++++++-
 src/passwd/nscd.h       |  38 +++++++++++++
 src/passwd/nscd_query.c | 125 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 415 insertions(+), 2 deletions(-)
 create mode 100644 src/passwd/nscd.h
 create mode 100644 src/passwd/nscd_query.c

diff --git a/src/passwd/getgr_a.c b/src/passwd/getgr_a.c
index 805e28c..2d3ed1d 100644
--- a/src/passwd/getgr_a.c
+++ b/src/passwd/getgr_a.c
@@ -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) {
+			char *tmp = realloc(*buf, len);
+			if (!tmp) {
+				rv = errno;
+				goto cleanup_f;
+			}
+			*buf = tmp;
+			*size = len;
+		}
+
+		if (fread(*buf, 1, len, f) < len) {
+			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;
diff --git a/src/passwd/getpw_a.c b/src/passwd/getpw_a.c
index 21efc5c..9af6cab 100644
--- a/src/passwd/getpw_a.c
+++ b/src/passwd/getpw_a.c
@@ -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) {
+			char *tmp = realloc(*buf, len);
+			if (!tmp) {
+				rv = errno;
+				goto cleanup_f;
+			}
+			*buf = tmp;
+			*size = len;
+		}
+
+		if (fread(*buf, 1, len, f) < len) {
+			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
index 0000000..102f0b4
--- /dev/null
+++ b/src/passwd/nscd.h
@@ -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
index 0000000..539cc2a
--- /dev/null
+++ b/src/passwd/nscd_query.c
@@ -0,0 +1,125 @@
+#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"
+};
+
+static ssize_t full_sendmsg(int fd, struct msghdr *m, int flags)
+{
+	ssize_t n, count = 0;
+
+	while (m->msg_iovlen) {
+		n = sendmsg(fd, m, flags);
+		if(n < 0) return n;
+		count += n;
+
+		while (n) {
+			if (n >= m->msg_iov[0].iov_len) {
+				n -= m->msg_iov[0].iov_len;
+				m->msg_iov++;
+				m->msg_iovlen--;
+			} else {
+				m->msg_iov[0].iov_len -= n;
+				n = 0;
+			}
+		}
+	}
+	return count;
+}
+
+FILE *__nscd_query(int32_t req, const char *key, int32_t *buf, size_t len, int *swap)
+{
+	size_t i;
+	int fd;
+	int res = 0;
+	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) {
+			res = -1;
+		}
+		goto error;
+	}
+
+	if (full_sendmsg(fd, &msg, MSG_NOSIGNAL) < 0)
+		goto error;
+
+	f = fdopen(fd, "r");
+
+	if (fread(buf, 1, len, f) < len) {
+		/* 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 (!feof(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 {
+			fclose(f);
+			res = -1;
+			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) {
+		res = -1;
+		errno = EIO;
+		goto error;
+	}
+
+	return f;
+error:
+	fclose(f);
+	return (FILE*)res;
+}
-- 
2.1.4



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] support alternate backends for the passwd and group dbs
  2015-02-23  2:58 ` Josiah Worcester
@ 2015-02-23  6:08   ` Rich Felker
  2015-02-23  6:18     ` Solar Designer
  0 siblings, 1 reply; 8+ messages in thread
From: Rich Felker @ 2015-02-23  6:08 UTC (permalink / raw)
  To: musl

[-- Attachment #1: Type: text/plain, Size: 1071 bytes --]

On Sun, Feb 22, 2015 at 08:58:10PM -0600, Josiah Worcester wrote:
> 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).

I'm committing with the attached additional changes amended in as
discussed on #musl. With these changes it at least passes the
following checks:

- With no nscd running, lookups return negative with no error.

- With bad nscd running and dropping connections with no reply,
  reverse-endian fallback code retries the query and then fails with
  EIO indicating inability to provide a definitive negative answer.

- Replies that don't match the query produce EIO.

- Replies that do match the query produce successful results.

Tested using the attached fake-nscd.c. This code still needs further
testing before release to ensure that we're not introducing
significant bugs.

Rich

[-- Attachment #2: nscd-addl-changes.diff --]
[-- Type: text/plain, Size: 3391 bytes --]

diff --git a/src/passwd/getgr_a.c b/src/passwd/getgr_a.c
index 2d3ed1d..7738c3c 100644
--- a/src/passwd/getgr_a.c
+++ b/src/passwd/getgr_a.c
@@ -96,7 +96,7 @@ int __getgr_a(const char *name, gid_t gid, struct group *gr, char **buf, size_t
 			grlist_len += name_len;
 		}
 
-		if (len > *size) {
+		if (len > *size || !*buf) {
 			char *tmp = realloc(*buf, len);
 			if (!tmp) {
 				rv = errno;
@@ -106,7 +106,7 @@ int __getgr_a(const char *name, gid_t gid, struct group *gr, char **buf, size_t
 			*size = len;
 		}
 
-		if (fread(*buf, 1, len, f) < len) {
+		if (!fread(*buf, len, 1, f)) {
 			rv = ferror(f) ? errno : EIO;
 			goto cleanup_f;
 		}
diff --git a/src/passwd/getpw_a.c b/src/passwd/getpw_a.c
index 9af6cab..b04663d 100644
--- a/src/passwd/getpw_a.c
+++ b/src/passwd/getpw_a.c
@@ -90,7 +90,7 @@ int __getpw_a(const char *name, uid_t uid, struct passwd *pw, char **buf, size_t
 		    + passwdbuf[PWGECOSLEN] + passwdbuf[PWDIRLEN]
 		    + passwdbuf[PWSHELLLEN];
 
-		if (len > *size) {
+		if (len > *size || !*buf) {
 			char *tmp = realloc(*buf, len);
 			if (!tmp) {
 				rv = errno;
@@ -100,7 +100,7 @@ int __getpw_a(const char *name, uid_t uid, struct passwd *pw, char **buf, size_t
 			*size = len;
 		}
 
-		if (fread(*buf, 1, len, f) < len) {
+		if (!fread(*buf, len, 1, f)) {
 			rv = ferror(f) ? errno : EIO;
 			goto cleanup_f;
 		}
diff --git a/src/passwd/nscd_query.c b/src/passwd/nscd_query.c
index 539cc2a..f8d0fc1 100644
--- a/src/passwd/nscd_query.c
+++ b/src/passwd/nscd_query.c
@@ -14,34 +14,10 @@ static const struct {
 	"/var/run/nscd/socket"
 };
 
-static ssize_t full_sendmsg(int fd, struct msghdr *m, int flags)
-{
-	ssize_t n, count = 0;
-
-	while (m->msg_iovlen) {
-		n = sendmsg(fd, m, flags);
-		if(n < 0) return n;
-		count += n;
-
-		while (n) {
-			if (n >= m->msg_iov[0].iov_len) {
-				n -= m->msg_iov[0].iov_len;
-				m->msg_iov++;
-				m->msg_iovlen--;
-			} else {
-				m->msg_iov[0].iov_len -= n;
-				n = 0;
-			}
-		}
-	}
-	return count;
-}
-
 FILE *__nscd_query(int32_t req, const char *key, int32_t *buf, size_t len, int *swap)
 {
 	size_t i;
 	int fd;
-	int res = 0;
 	FILE *f = 0;
 	int32_t req_buf[REQ_LEN] = {
 		NSCDVERSION,
@@ -71,23 +47,24 @@ retry:
 		 * that is precisely what happened
 		 */
 		if (errno == EACCES || errno == ECONNREFUSED || errno == ENOENT) {
-			res = -1;
+			close(fd);
+			return (FILE *)-1;
 		}
 		goto error;
 	}
 
-	if (full_sendmsg(fd, &msg, MSG_NOSIGNAL) < 0)
+	if (sendmsg(fd, &msg, MSG_NOSIGNAL) < 0)
 		goto error;
 
-	f = fdopen(fd, "r");
+	if(!(f = fdopen(fd, "r"))) goto error;
 
-	if (fread(buf, 1, len, f) < len) {
+	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 (!feof(f)) goto error;
+		if (ferror(f)) goto error;
 		if (!*swap) {
 			fclose(f);
 			for (i = 0; i < sizeof(req_buf)/sizeof(req_buf[0]); i++) {
@@ -96,8 +73,7 @@ retry:
 			*swap = 1;
 			goto retry;
 		} else {
-			fclose(f);
-			res = -1;
+			errno = EIO;
 			goto error;
 		}
 	}
@@ -113,13 +89,12 @@ retry:
 	 * response.
 	 */
 	if(buf[0] != NSCDVERSION) {
-		res = -1;
 		errno = EIO;
 		goto error;
 	}
 
 	return f;
 error:
-	fclose(f);
-	return (FILE*)res;
+	if (f) fclose(f); else close(fd);
+	return 0;
 }

[-- Attachment #3: fake-nscd.c --]
[-- Type: text/plain, Size: 675 bytes --]

#include <sys/socket.h>
#include <sys/un.h>
#include <inttypes.h>
#include <unistd.h>

#define STRINGS "foobar\0\0\0/\0/bin/sh"

static const struct reply {
	uint32_t header[9];
	char strings[sizeof STRINGS];
} reply = {
	{ 2, 1, 7, 1, 12345, 678910, 1, 2, 8 },
	STRINGS
};

int main()
{
	struct sockaddr_un sun = {
		.sun_family = AF_UNIX,
		.sun_path = "/var/run/nscd/socket"
	};
	int s = socket(AF_UNIX, SOCK_STREAM, 0);
	umask(0);
	bind(s, (void *)&sun, sizeof sun);
	listen(s, 1);
	for (;;) {
		char buf[256];
		int c = accept(s, (void *)&(struct sockaddr_un){0}, &(socklen_t){sizeof sun});
		read(c, buf, sizeof buf);
		write(c, &reply, sizeof reply);
		close(c);
	}
}

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] support alternate backends for the passwd and group dbs
  2015-02-23  6:08   ` Rich Felker
@ 2015-02-23  6:18     ` Solar Designer
  2015-02-23  6:24       ` Josiah Worcester
  2015-02-23  7:04       ` Rich Felker
  0 siblings, 2 replies; 8+ messages in thread
From: Solar Designer @ 2015-02-23  6:18 UTC (permalink / raw)
  To: musl

On Mon, Feb 23, 2015 at 01:08:40AM -0500, Rich Felker wrote:
> On Sun, Feb 22, 2015 at 08:58:10PM -0600, Josiah Worcester wrote:
> > 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).
> 
> I'm committing with the attached additional changes [...]

Hmm.  I guess this was discussed before, but I am surprised.  Wasn't
nscd intended for caching rather than to provide a fallback?  If so,
does musl intentionally re-purpose it?

Alexander


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] support alternate backends for the passwd and group dbs
  2015-02-23  6:18     ` Solar Designer
@ 2015-02-23  6:24       ` Josiah Worcester
  2015-02-23  7:04       ` Rich Felker
  1 sibling, 0 replies; 8+ messages in thread
From: Josiah Worcester @ 2015-02-23  6:24 UTC (permalink / raw)
  To: musl

[-- Attachment #1: Type: text/plain, Size: 1258 bytes --]

On Feb 23, 2015 12:18 AM, "Solar Designer" <solar@openwall.com> wrote:
>
> On Mon, Feb 23, 2015 at 01:08:40AM -0500, Rich Felker wrote:
> > On Sun, Feb 22, 2015 at 08:58:10PM -0600, Josiah Worcester wrote:
> > > 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).
> >
> > I'm committing with the attached additional changes [...]
>
> Hmm.  I guess this was discussed before, but I am surprised.  Wasn't
> nscd intended for caching rather than to provide a fallback?  If so,
> does musl intentionally re-purpose it?
>
> Alexander

Yes, musl is intentionally repurposing it. By using it as an extra place to
look it is much harder to create a situation where getpwuid et al change
their results because of an inability to talk to the daemon. Additionally,
musl's normal path is file lookup, which should not be slow (and hence has
no need for a cache).

And of course, by reusing the protocol, musl gets compatibility with the
backends configured on glibc systems for free.

[-- Attachment #2: Type: text/html, Size: 1559 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] support alternate backends for the passwd and group dbs
  2015-02-23  6:18     ` Solar Designer
  2015-02-23  6:24       ` Josiah Worcester
@ 2015-02-23  7:04       ` Rich Felker
  2015-02-23 16:01         ` M Farkas-Dyck
  1 sibling, 1 reply; 8+ messages in thread
From: Rich Felker @ 2015-02-23  7:04 UTC (permalink / raw)
  To: Solar Designer; +Cc: musl

On Mon, Feb 23, 2015 at 09:18:43AM +0300, Solar Designer wrote:
> On Mon, Feb 23, 2015 at 01:08:40AM -0500, Rich Felker wrote:
> > On Sun, Feb 22, 2015 at 08:58:10PM -0600, Josiah Worcester wrote:
> > > 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).
> > 
> > I'm committing with the attached additional changes [...]
> 
> Hmm.  I guess this was discussed before, but I am surprised.  Wasn't
> nscd intended for caching rather than to provide a fallback?  If so,
> does musl intentionally re-purpose it?

There were multiple discussions of how to support alternate backends
in the past, and the main two candidates were a new text-based
protocol over a unix socket that returns the result in passwd/group
file form, and repurposing nscd protocol. While I originally preferred
the former, using nscd has the advantage that, on existing glibc
systems with non-default (possibly even custom nss modules) backends,
everything works out of the box. Using a new protocol/new daemon would
require installing that daemon before any musl-linked binaries could
lookup users/groups, and would require significant custom glue to
integrate with custom site-local backends.

The intended usage for musl binaries running on glibc systems is to
use whatever nscd is already running. Systems with a network-based
user/group db backend should already be running nscd; if not it's easy
to add. Systems just using passwd/group files don't need it anyway.

For musl-native systems, the user is intended to have some choice. The
options should eventually include at least:

1. A featureful generic nscd implementation that uses one or more nss
   modules (optionally static linked into the daemon or dynamically
   loaded) as its backend(s).

2. A simple nscd implementation that just does NIS and LDAP.

At this time neither exists, but it's possible to use glibc nscd if
really needed.

Rich


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] support alternate backends for the passwd and group dbs
  2015-02-23  7:04       ` Rich Felker
@ 2015-02-23 16:01         ` M Farkas-Dyck
  2015-02-23 18:15           ` Rich Felker
  0 siblings, 1 reply; 8+ messages in thread
From: M Farkas-Dyck @ 2015-02-23 16:01 UTC (permalink / raw)
  To: musl

On 23/02/2015, Rich Felker <dalias@libc.org> wrote:
> There were multiple discussions of how to support alternate backends
> in the past, and the main two candidates were a new text-based
> protocol over a unix socket that returns the result in passwd/group
> file form, and repurposing nscd protocol. While I originally preferred
> the former, using nscd has the advantage that, on existing glibc
> systems with non-default (possibly even custom nss modules) backends,
> everything works out of the box. Using a new protocol/new daemon would
> require installing that daemon before any musl-linked binaries could
> lookup users/groups, and would require significant custom glue to
> integrate with custom site-local backends.

For nonstatic non-nsc backends, one could alternately have files in
question on synthetic filesystem, e.g. 9p, so backend program would
simply be synthetic filesystem server and musl wouldn't need to care.


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] support alternate backends for the passwd and group dbs
  2015-02-23 16:01         ` M Farkas-Dyck
@ 2015-02-23 18:15           ` Rich Felker
  0 siblings, 0 replies; 8+ messages in thread
From: Rich Felker @ 2015-02-23 18:15 UTC (permalink / raw)
  To: M Farkas-Dyck; +Cc: musl

On Mon, Feb 23, 2015 at 11:01:11AM -0500, M Farkas-Dyck wrote:
> On 23/02/2015, Rich Felker <dalias@libc.org> wrote:
> > There were multiple discussions of how to support alternate backends
> > in the past, and the main two candidates were a new text-based
> > protocol over a unix socket that returns the result in passwd/group
> > file form, and repurposing nscd protocol. While I originally preferred
> > the former, using nscd has the advantage that, on existing glibc
> > systems with non-default (possibly even custom nss modules) backends,
> > everything works out of the box. Using a new protocol/new daemon would
> > require installing that daemon before any musl-linked binaries could
> > lookup users/groups, and would require significant custom glue to
> > integrate with custom site-local backends.
> 
> For nonstatic non-nsc backends, one could alternately have files in
> question on synthetic filesystem, e.g. 9p, so backend program would
> simply be synthetic filesystem server and musl wouldn't need to care.

Generally the only time you use non-static backends is when your user
database is so huge that parsing /etc/passwd over and over would be
prohibitively slow. Think of university or corporate user database
with tens or hundreds of thousands of users. A synthetic filesystem
does not help with this any more than a generated passwd file in /etc
would help; the goal is to get rid of the linear-search-on-each-lookup
performance problem.

Rich


^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2015-02-23 18:15 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-02-23  2:35 [PATCH] support alternate backends for the passwd and group dbs Josiah Worcester
2015-02-23  2:58 ` Josiah Worcester
2015-02-23  6:08   ` Rich Felker
2015-02-23  6:18     ` Solar Designer
2015-02-23  6:24       ` Josiah Worcester
2015-02-23  7:04       ` Rich Felker
2015-02-23 16:01         ` M Farkas-Dyck
2015-02-23 18:15           ` Rich Felker

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/musl/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).