Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/nss/nss-rainbow.c
diff options
context:
space:
mode:
Diffstat (limited to 'nss/nss-rainbow.c')
-rw-r--r--nss/nss-rainbow.c478
1 files changed, 478 insertions, 0 deletions
diff --git a/nss/nss-rainbow.c b/nss/nss-rainbow.c
new file mode 100644
index 0000000..f3e646b
--- /dev/null
+++ b/nss/nss-rainbow.c
@@ -0,0 +1,478 @@
+#define _GNU_SOURCE
+#define _SVID_SOURCE
+#include <nss.h>
+#include <stdio.h>
+#include <pwd.h>
+#include <grp.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+
+/* code generators */
+#include "cgen.h"
+
+#include "buf.h"
+#include "nat.h"
+#include "slist.h"
+
+/* constants */
+#include "config/shell.h"
+#include "config/debug.h"
+#include "config/spool.h"
+
+static unsigned char g_uids[65536];
+static unsigned short g_uid = 0;
+
+/* I'm going to assume that every appropriately named dirent represents a user.
+ * We may need to revise this, e.g. by checking for the existence of home dir.
+ * We're also going to be evil and use the scandir() traversal to directly
+ * record allocated uids. <MS> */
+int
+uid_scanner(const struct dirent* d) {
+ unsigned long val;
+ if(parse_nat(&val, d->d_name, strlen(d->d_name)) == 1) return 0;
+ if(val < 10000 || val > 60000) return 0;
+ g_uids[val] = 1;
+ return 0;
+}
+
+/* implementation */
+enum nss_status
+_nss_rainbow_endpwent(void) {
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status
+_nss_rainbow_setpwent(void) {
+ for (unsigned short i = 10000; i < 60000; i++)
+ g_uids[i] = 0;
+ g_uid = 0;
+ struct dirent** ents;
+ CHK(scandir(SPOOL "/uid_pool", &ents, uid_scanner, versionsort) == -1,
+ "Unable to extract uids from $RAINBOW_SPOOL.", out_error_spool);
+ return NSS_STATUS_SUCCESS;
+out_error_spool:
+ return NSS_STATUS_UNAVAIL;
+}
+
+int check_gid_in_range(gid_t gid)
+{
+ TST(gid < 10000 || gid > 60000, errno = ENOENT,
+ "Gid outside [10000, 60000].", out_error);
+ return 0;
+out_error:
+ return 1;
+}
+
+
+int
+read_gid_for_uid(unsigned long uid, unsigned long* gid) {
+ char gid_path[NAME_MAX+1];
+ char gid_name[NAME_MAX+1];
+ size_t gid_path_len;
+ ssize_t gid_name_len;
+ char* gid_path_str;
+ unsigned long val;
+
+ gid_path_len = gid_name_len = NAME_MAX+1;
+ gid_path_str = gid_path;
+
+ CHK(format_buf(&gid_path_str, &gid_path_len, SPOOL "/uid_to_gid/%d", uid) == 1,
+ "Unable to calculate gid-path.", out_err);
+ LET(gid_name_len = readlink(gid_path, gid_name, gid_name_len), gid_name_len == -1,
+ "Unable to read gid-path.", out_err);
+ CHK(parse_nat(&val, gid_name, gid_name_len) == 1,
+ "Unable to parse gid_buf into a gid.", out_err);
+ CHK(check_gid_in_range(val) == 1,
+ "gid not in valid range.", out_err);
+
+ *gid = val;
+ return 0;
+out_err:
+ return 1;
+}
+
+enum nss_status
+_nss_rainbow_getpwent_r(struct passwd *result, char* buf, size_t buflen, int *errnop) {
+ /* Getting errno and the return code correct in this function is a pain
+ * because we need to return
+ *
+ * NSS_STATUS_TRYAGAIN + ERANGE if we run out of space in buf
+ * NSS_STATUS_NOTFOUND + ENOENT if we run out of uids
+ * NSS_STATUS_SUCCESS + _____, if we win
+ * or try another uid, if we lose
+ */
+
+begin:
+ errno = 0;
+
+ /* Locate the next reserved uid. */
+ while (true){
+ g_uid++;
+ TST(!g_uid, errno = ENOENT,
+ "No more uids to check.", out_error);
+ if (g_uids[g_uid]) break;
+ }
+
+ result->pw_uid = g_uid;
+
+ unsigned long gid;
+ CHK(read_gid_for_uid(g_uid, &gid) == 1,
+ "Unable to calculate gid for uid.", begin);
+ result->pw_gid = gid;
+
+ result->pw_dir = buf;
+ CHK(format_buf(&buf, &buflen, SPOOL "/uid_to_home_dir/%d", g_uid) == 1,
+ "Unable to calculate home dir.", maybe_error);
+
+ result->pw_gecos = result->pw_name = result->pw_passwd = buf;
+ CHK(format_buf(&buf, &buflen, "%d", g_uid) == 1,
+ "Not enough buffer for $USER.", maybe_error);
+
+ result->pw_shell = buf;
+ CHK(write_buf(&buf, &buflen, SHELL) == 1,
+ "Shell string constant too long.", maybe_error);
+
+ return NSS_STATUS_SUCCESS;
+
+maybe_error:
+ if(errno == ERANGE) { g_uid--; goto out_error; }
+ goto begin;
+
+out_error:
+ *errnop = errno;
+ if (errno == ERANGE) return NSS_STATUS_TRYAGAIN;
+ if (errno == ENOENT) return NSS_STATUS_NOTFOUND;
+ return NSS_STATUS_UNAVAIL;
+}
+
+enum nss_status _nss_rainbow_getpwuid_r(uid_t uid, struct passwd *result, char* buf, size_t buflen, int *errnop) {
+ if (uid < 10000)
+ return NSS_STATUS_NOTFOUND;
+
+ result->pw_dir = buf;
+ CHK(format_buf(&buf, &buflen, SPOOL "/uid_to_home_dir/%d", uid) == 1,
+ "Unable to calculate home dir.", out_error_errno);
+
+ struct stat st;
+ CHK(stat(result->pw_dir, &st) == -1,
+ "Stat failed for homebuf.", out_error_errno);
+ result->pw_uid = uid;
+
+ unsigned long gid;
+ CHK(read_gid_for_uid(uid, &gid) == 1,
+ "Unable to read gid for uid.", out_error_errno);
+ result->pw_gid = gid;
+
+ result->pw_gecos = result->pw_name = result->pw_passwd = buf;
+ CHK(format_buf(&buf, &buflen, "%d", uid) == 1,
+ "Unable to calculate user name.", out_error_errno);
+
+ result->pw_shell = buf;
+ CHK(write_buf(&buf, &buflen, SHELL) == 1,
+ "Shell string constant too long.", out_error_errno);
+
+ return NSS_STATUS_SUCCESS;
+
+out_error_errno:
+ *errnop = errno;
+ return NSS_STATUS_TRYAGAIN;
+}
+
+enum nss_status _nss_rainbow_getpwnam_r(const char * name, struct passwd *result, char* buf, size_t buflen, int *errnop) {
+ unsigned long val;
+ CHK(parse_nat(&val, name, strlen(name)) == 1,
+ "Unable to parse name into a uid.", out_error_name);
+
+ TST(val < 10000 || val > 60000, errno = EINVAL,
+ "Name specifies a uid outside [10000, 60000].", out_error_name);
+
+ return _nss_rainbow_getpwuid_r((uid_t)val, result, buf, buflen, errnop);
+
+out_error_name:
+ *errnop = errno;
+ return NSS_STATUS_NOTFOUND;
+}
+
+struct member_row {
+ struct slist list;
+ char* name;
+};
+struct gid_row {
+ struct slist list;
+ struct member_row *members;
+ gid_t gid;
+};
+
+struct gid_row* g_gids;
+struct gid_row* g_cur_gid;
+
+void member_row_init(struct member_row* self) {
+ self->name = NULL;
+ slist_init(&self->list);
+}
+
+void gid_row_init(struct gid_row* self) {
+ slist_init(&self->list);
+ self->members = NULL;
+ self->gid = -1;
+}
+
+void gid_row_add_member(struct gid_row* self, struct member_row* mr) {
+ slist_extend(&self->members->list, &mr->list);
+}
+
+int members_scanner(const struct dirent* d) {
+ size_t member_len = strlen(d->d_name);
+ const char* member_str = d->d_name;
+
+ CHK(member_len == 0
+ || (member_len == 1 && d->d_name[0] == '.')
+ || (member_len == 2 && d->d_name[0] == '.' && d->d_name[1] == '.'),
+ "Invalid data-gid-to-member member path.", exit);
+
+ /* g_cur_gid now points to the appropriate gid_row node.
+ * There's no way that we could already have our current (uid, gid) row.
+ * I think. :)
+ * Therefore, we'll immediately insert it. */
+ struct member_row* mr;
+
+ LET(mr = calloc(1, sizeof(struct member_row)), !mr,
+ "Unable to allocate new member_row node.", exit);
+ member_row_init(mr);
+
+ LET(mr->name = malloc(member_len+1), !mr->name,
+ "Unable to allocate new member_row->name.", exit);
+ strncpy(mr->name, member_str, member_len+1);
+
+ gid_row_add_member(g_cur_gid, mr);
+
+exit:
+ return 0;
+}
+
+int
+gid_to_members_scanner(const struct dirent* d) {
+ unsigned long gid;
+ struct dirent** ents;
+ char path[NAME_MAX+1];
+ size_t path_len = NAME_MAX + 1;
+ char* path_str = path;
+ struct gid_row* gr;
+
+
+ CHK(path_len == 0
+ || (path_len == 1 && d->d_name[0] == '.')
+ || (path_len == 2 && d->d_name[0] == '.' && d->d_name[1] == '.'),
+ "Invalid data-gid-to-member gid dir.", exit);
+
+ CHK(parse_nat(&gid, d->d_name, strlen(d->d_name)) == 1,
+ "Unable to parse d->d_name into a gid.", exit);
+ CHK(gid < 10000 || gid > 60000,
+ "gid not in valid range.", exit);
+
+ CHK(format_buf(&path_str, &path_len, SPOOL "/gid_to_members/%d", gid) == 1,
+ "Unable to calculate data-gid-to-members path.", exit);
+
+ /* We might have already allocated a gid_row for our gid.
+ * Therefore, search for it. */
+ bool find_gid(struct gid_row* iter) {
+ if (iter->gid == gid) { g_cur_gid = iter; return 0; } else return 1;
+ }
+ slist_search(struct gid_row, list, g_gids, found, not_found, find_gid);
+
+not_found:
+ LET(gr = calloc(1, sizeof(struct gid_row)), !gr,
+ "Unable to allocate new gid_row node.", exit);
+ gid_row_init(gr);
+ gr->gid = gid;
+
+ LET(gr->members = calloc(1, sizeof(struct member_row)), !gr->members,
+ "Unable to allocate new gid_row->members node.", exit);
+ member_row_init(gr->members);
+
+ slist_extend(&g_gids->list, &gr->list);
+ g_cur_gid = gr;
+
+found:
+ /* g_cur_gid now points to an appropriate gid_row for us to fill in. */
+ CHK(scandir(path, &ents, members_scanner, versionsort) == -1,
+ "Unable to scan members dir from $RAINBOW_SPOOL/gid_to_members/$GID.", exit);
+exit:
+ return 0;
+}
+
+enum nss_status _nss_rainbow_setgrent(void) {
+ struct dirent** ents;
+ LET(g_gids = calloc(1, sizeof(struct gid_row)), !g_gids,
+ "Unable to allocate new g_gids node.", out_error);
+ gid_row_init(g_gids);
+ CHK(scandir(SPOOL "/gid_to_members", &ents, gid_to_members_scanner, versionsort) == -1,
+ "Unable to extract gid->members dirs from $RAINBOW_SPOOL.", out_error);
+
+ g_cur_gid = container_of(g_gids->list.next, struct gid_row, list);
+ return NSS_STATUS_SUCCESS;
+out_error:
+ return NSS_STATUS_UNAVAIL;
+}
+
+enum nss_status _nss_rainbow_endgrent(void) {
+ void free_gid(struct gid_row* gid_iter) {
+ void free_member(struct member_row* member_iter) {
+ free(member_iter->name);
+ free(member_iter);
+ }
+ slist_for(struct member_row, list, gid_iter->members, free_member);
+ free(gid_iter);
+ }
+ slist_for(struct gid_row, list, g_gids, free_gid);
+ gid_row_init(g_gids);
+
+ return NSS_STATUS_SUCCESS;
+}
+/*
+struct group
+{
+ char *gr_name;
+ char *gr_passwd;
+ __gid_t gr_gid;
+ char **gr_mem;
+};
+*/
+int fill_member(struct member_row* member_iter, struct group* result, char** buf, size_t* buflen, size_t* member_count) {
+ result->gr_mem[*member_count] = *buf;
+ CHK(format_buf(buf, buflen, "%s", member_iter->name) == 1,
+ "Unable to write a group member.", out_error);
+ *member_count += 1;
+
+ return 0;
+out_error:
+ return 1;
+}
+
+int
+fill_group(struct group* result, char* buf, size_t buflen, struct gid_row* gid_iter) {
+ result->gr_gid = gid_iter->gid;
+
+ result->gr_name = result->gr_passwd = buf;
+ CHK(format_buf(&buf, &buflen, "%d", gid_iter->gid) == 1,
+ "Unable to calculate group name.", out_error);
+
+ /* count the number of members */
+ size_t member_count = 0;
+ void count_member(struct member_row* iter) { (void) iter; member_count++; }
+ slist_for(struct member_row, list, gid_iter->members, count_member);
+
+ /* reserve space for the member-pointers */
+ size_t member_size = (member_count+1)* sizeof(char*);
+ result->gr_mem = (char**)buf;
+ TST(buflen < member_size, errno = ERANGE,
+ "Not enough space for group member pointers.", out_error);
+ result->gr_mem[member_count] = 0;
+ buf += member_size;
+ buflen -= member_size;
+
+ /* fill in the members and swing the pointers */
+ member_count = 0;
+ slist_while(struct member_row, list, gid_iter->members, out_error,
+ fill_member, result, &buf, &buflen, &member_count);
+
+ return 0;
+
+out_error:
+ return 1;
+}
+
+enum nss_status
+_nss_rainbow_getgrent_r(struct group *result, char *buf, size_t buflen, int *errnop) {
+ if (&g_cur_gid->list == &g_gids->list)
+ return NSS_STATUS_NOTFOUND;
+
+ CHK(fill_group(result, buf, buflen, g_cur_gid) == 1,
+ "Unable to fill in group struct.", out_error_errno);
+ g_cur_gid = container_of(g_cur_gid->list.next, struct gid_row, list);
+
+ return NSS_STATUS_SUCCESS;
+
+out_error_errno:
+ *errnop = errno;
+ return NSS_STATUS_TRYAGAIN;
+}
+
+enum nss_status
+_nss_rainbow_getgrgid_r(gid_t gid, struct group *result, char *buf, size_t buflen, int *errnop) {
+ /* Getting errno and the return code exactly right is a pain.
+ * The problem is that we need
+ *
+ * NSS_STATUS_NOTFOUND + ENOENT if we can't find the result
+ * NSS_STATUS_TRYAGAIN + ERANGE if buf isn't big enough
+ * NSS_STATUS_??? + ____ if we can't setgrent()
+ * NSS_STATUS_NOTFOUND + ____ if we have bogus data
+ * NSS_SUCCESS + ____ otherwise.
+ */
+
+ enum nss_status ret, tmp;
+
+ LET(ret = _nss_rainbow_setgrent(), ret != NSS_STATUS_SUCCESS,
+ "Unable to setgrent().", out_error);
+
+ TST(check_gid_in_range(gid) == 1, ret = NSS_STATUS_NOTFOUND,
+ "Gid outside [10000, 60000].", out_error);
+
+ /* look up group data */
+ struct gid_row* gid_iter = NULL;
+ bool find_gid(struct gid_row* iter, struct gid_row** result) {
+ if (iter->gid == gid) { *result = iter; return 0; } else return 1;
+ }
+
+ slist_search(struct gid_row, list, g_gids, resume, resume,
+ find_gid, &gid_iter);
+resume:
+
+ TST(&gid_iter->list == NULL, ret = NSS_STATUS_NOTFOUND,
+ "Gid not found.", out_dealloc);
+
+ CHK(fill_group(result, buf, buflen, gid_iter) == 1,
+ "Unable to fill in group struct.", maybe_range);
+
+ ret = NSS_STATUS_SUCCESS;
+ goto out_dealloc;
+
+maybe_range:
+ ret = (errno == ERANGE) ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND;
+
+out_error:
+ *errnop = errno;
+
+out_dealloc:
+ tmp = _nss_rainbow_endgrent();
+ TST(ret == NSS_STATUS_SUCCESS && tmp != NSS_STATUS_SUCCESS, ret = tmp,
+ "Unable to deallocate internal group data.", out);
+
+out:
+ return ret;
+}
+
+enum nss_status
+_nss_rainbow_getgrnam_r(const char* name, struct group *result, char *buf, size_t buflen, int *errnop) {
+ unsigned long val;
+
+ CHK(parse_nat(&val, name, strlen(name)) == 1,
+ "Unable to parse name into a gid.", out_error);
+
+ CHK(check_gid_in_range(val) == 1,
+ "Name specifies a gid outside [10000, 60000].", out_error);
+
+ return _nss_rainbow_getgrgid_r((gid_t)val, result, buf, buflen, errnop);
+out_error:
+ *errnop = errno;
+ return NSS_STATUS_NOTFOUND;
+}