#define _GNU_SOURCE #define _SVID_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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. */ 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; }