Logo Search packages:      
Sourcecode: rainbow version File versions  Download package

nss-rainbow.c

#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;
}

Generated by  Doxygen 1.6.0   Back to index