about summary refs log tree commit diff
path: root/src/passwd/getgr_a.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/passwd/getgr_a.c')
-rw-r--r--src/passwd/getgr_a.c140
1 files changed, 139 insertions, 1 deletions
diff --git a/src/passwd/getgr_a.c b/src/passwd/getgr_a.c
index 805e28c9..7738c3c3 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 || !*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;