diff options
Diffstat (limited to 'nss/nss-rainbow.c')
-rw-r--r-- | nss/nss-rainbow.c | 478 |
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; +} |