#include <stdlib.h>
#include <mach/mach.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <rpc/types.h>
#include <rpc/xdr.h>
#include <grp.h>
#include <pwd.h>
#include <netinet/in.h>
#include <sys/param.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <servers/bootstrap.h>
#include "_lu_types.h"
#include "lookup.h"
#include "lu_utils.h"
#include "lu_overrides.h"
#define GROUP_CACHE_SIZE 10
#define DEFAULT_GROUP_CACHE_TTL 10
static pthread_mutex_t _group_cache_lock = PTHREAD_MUTEX_INITIALIZER;
static void *_group_cache[GROUP_CACHE_SIZE] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
static unsigned int _group_cache_best_before[GROUP_CACHE_SIZE] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
static unsigned int _group_cache_index = 0;
static unsigned int _group_cache_ttl = DEFAULT_GROUP_CACHE_TTL;
static pthread_mutex_t _group_lock = PTHREAD_MUTEX_INITIALIZER;
#define MEMBERD_NAME "com.apple.memberd"
static mach_port_t mbr_port = MACH_PORT_NULL;
typedef uint32_t GIDArray[16];
extern kern_return_t _mbr_GetGroups(mach_port_t server, uint32_t uid, uint32_t *numGroups, GIDArray gids);
#define GR_GET_NAME 1
#define GR_GET_GID 2
#define GR_GET_ENT 3
static void
free_group_data(struct group *g)
{
char **mem;
if (g == NULL) return;
if (g->gr_name != NULL) free(g->gr_name);
if (g->gr_passwd != NULL) free(g->gr_passwd);
mem = g->gr_mem;
if (mem != NULL)
{
while (*mem != NULL) free(*mem++);
free(g->gr_mem);
}
}
static void
free_group(struct group *g)
{
if (g == NULL) return;
free_group_data(g);
free(g);
}
static void
free_lu_thread_info_group(void *x)
{
struct lu_thread_info *tdata;
if (x == NULL) return;
tdata = (struct lu_thread_info *)x;
if (tdata->lu_entry != NULL)
{
free_group((struct group *)tdata->lu_entry);
tdata->lu_entry = NULL;
}
_lu_data_free_vm_xdr(tdata);
free(tdata);
}
static struct group *
extract_group(XDR *xdr)
{
int i, j, nkeys, nvals, status;
char *key, **vals;
struct group *g;
if (xdr == NULL) return NULL;
if (!xdr_int(xdr, &nkeys)) return NULL;
g = (struct group *)calloc(1, sizeof(struct group));
g->gr_gid = -2;
for (i = 0; i < nkeys; i++)
{
key = NULL;
vals = NULL;
nvals = 0;
status = _lu_xdr_attribute(xdr, &key, &vals, &nvals);
if (status < 0)
{
free_group(g);
return NULL;
}
if (nvals == 0)
{
free(key);
continue;
}
j = 0;
if ((g->gr_name == NULL) && (!strcmp("name", key)))
{
g->gr_name = vals[0];
j = 1;
}
else if ((g->gr_passwd == NULL) && (!strcmp("passwd", key)))
{
g->gr_passwd = vals[0];
j = 1;
}
else if ((g->gr_gid == (gid_t)-2) && (!strcmp("gid", key)))
{
g->gr_gid = atoi(vals[0]);
if ((g->gr_gid == 0) && (strcmp(vals[0], "0"))) g->gr_gid = -2;
}
else if ((g->gr_mem == NULL) && (!strcmp("users", key)))
{
g->gr_mem = vals;
j = nvals;
vals = NULL;
}
free(key);
if (vals != NULL)
{
for (; j < nvals; j++) free(vals[j]);
free(vals);
}
}
if (g->gr_name == NULL) g->gr_name = strdup("");
if (g->gr_passwd == NULL) g->gr_passwd = strdup("");
if (g->gr_mem == NULL) g->gr_mem = (char **)calloc(1, sizeof(char *));
return g;
}
static struct group *
copy_group(struct group *in)
{
struct group *g;
int i, len;
if (in == NULL) return NULL;
g = (struct group *)calloc(1, sizeof(struct group));
g->gr_name = LU_COPY_STRING(in->gr_name);
g->gr_passwd = LU_COPY_STRING(in->gr_passwd);
g->gr_gid = in->gr_gid;
len = 0;
if (in->gr_mem != NULL)
{
for (len = 0; in->gr_mem[len] != NULL; len++);
}
g->gr_mem = (char **)calloc(len + 1, sizeof(char *));
for (i = 0; i < len; i++)
{
g->gr_mem[i] = strdup(in->gr_mem[i]);
}
return g;
}
static int
copy_group_r(struct group *in, struct group *out, char *buffer, int buflen)
{
int i, len, hsize;
unsigned long addr;
char *bp, *ap;
if (in == NULL) return -1;
if (out == NULL) return -1;
if (buffer == NULL) buflen = 0;
hsize = 0;
if (in->gr_name != NULL) hsize += (strlen(in->gr_name) + 1);
if (in->gr_passwd != NULL) hsize += (strlen(in->gr_passwd) + 1);
hsize += sizeof(char *);
len = 0;
if (in->gr_mem != NULL)
{
for (len = 0; in->gr_mem[len] != NULL; len++)
{
hsize += sizeof(char *);
hsize += (strlen(in->gr_mem[len]) + 1);
}
}
if (hsize > buflen) return -1;
bp = buffer;
out->gr_name = NULL;
if (in->gr_name != NULL)
{
out->gr_name = bp;
hsize = strlen(in->gr_name) + 1;
memmove(bp, in->gr_name, hsize);
bp += hsize;
}
out->gr_passwd = NULL;
if (in->gr_passwd != NULL)
{
out->gr_passwd = bp;
hsize = strlen(in->gr_passwd) + 1;
memmove(bp, in->gr_passwd, hsize);
bp += hsize;
}
out->gr_gid = in->gr_gid;
out->gr_mem = NULL;
ap = bp + ((len + 1) * sizeof(char *));
if (in->gr_mem != NULL)
{
out->gr_mem = (char **)bp;
for (i = 0; i < len; i++)
{
addr = (unsigned long)ap;
memmove(bp, &addr, sizeof(unsigned long));
bp += sizeof(unsigned long);
hsize = strlen(in->gr_mem[i]) + 1;
memmove(ap, in->gr_mem[i], hsize);
ap += hsize;
}
}
memset(bp, 0, sizeof(unsigned long));
bp = ap;
return 0;
}
static void
recycle_group(struct lu_thread_info *tdata, struct group *in)
{
struct group *g;
if (tdata == NULL) return;
g = (struct group *)tdata->lu_entry;
if (in == NULL)
{
free_group(g);
tdata->lu_entry = NULL;
}
if (tdata->lu_entry == NULL)
{
tdata->lu_entry = in;
return;
}
free_group_data(g);
g->gr_name = in->gr_name;
g->gr_passwd = in->gr_passwd;
g->gr_gid = in->gr_gid;
g->gr_mem = in->gr_mem;
free(in);
}
__private_extern__ unsigned int
get_group_cache_ttl()
{
return _group_cache_ttl;
}
__private_extern__ void
set_group_cache_ttl(unsigned int ttl)
{
int i;
pthread_mutex_lock(&_group_cache_lock);
_group_cache_ttl = ttl;
if (ttl == 0)
{
for (i = 0; i < GROUP_CACHE_SIZE; i++)
{
if (_group_cache[i] == NULL) continue;
free_group((struct group *)_group_cache[i]);
_group_cache[i] = NULL;
_group_cache_best_before[i] = 0;
}
}
pthread_mutex_unlock(&_group_cache_lock);
}
static void
cache_group(struct group *gr)
{
struct timeval now;
struct group *grcache;
if (_group_cache_ttl == 0) return;
if (gr == NULL) return;
pthread_mutex_lock(&_group_cache_lock);
grcache = copy_group(gr);
gettimeofday(&now, NULL);
if (_group_cache[_group_cache_index] != NULL)
free_group((struct group *)_group_cache[_group_cache_index]);
_group_cache[_group_cache_index] = grcache;
_group_cache_best_before[_group_cache_index] = now.tv_sec + _group_cache_ttl;
_group_cache_index = (_group_cache_index + 1) % GROUP_CACHE_SIZE;
pthread_mutex_unlock(&_group_cache_lock);
}
static struct group *
cache_getgrnam(const char *name)
{
int i;
struct group *gr, *res;
struct timeval now;
if (_group_cache_ttl == 0) return NULL;
if (name == NULL) return NULL;
pthread_mutex_lock(&_group_cache_lock);
gettimeofday(&now, NULL);
for (i = 0; i < GROUP_CACHE_SIZE; i++)
{
if (_group_cache_best_before[i] == 0) continue;
if ((unsigned int)now.tv_sec > _group_cache_best_before[i]) continue;
gr = (struct group *)_group_cache[i];
if (gr->gr_name == NULL) continue;
if (!strcmp(name, gr->gr_name))
{
res = copy_group(gr);
pthread_mutex_unlock(&_group_cache_lock);
return res;
}
}
pthread_mutex_unlock(&_group_cache_lock);
return NULL;
}
static struct group *
cache_getgrgid(int gid)
{
int i;
struct group *gr, *res;
struct timeval now;
if (_group_cache_ttl == 0) return NULL;
pthread_mutex_lock(&_group_cache_lock);
gettimeofday(&now, NULL);
for (i = 0; i < GROUP_CACHE_SIZE; i++)
{
if (_group_cache_best_before[i] == 0) continue;
if ((unsigned int)now.tv_sec > _group_cache_best_before[i]) continue;
gr = (struct group *)_group_cache[i];
if ((gid_t)gid == gr->gr_gid)
{
res = copy_group(gr);
pthread_mutex_unlock(&_group_cache_lock);
return res;
}
}
pthread_mutex_unlock(&_group_cache_lock);
return NULL;
}
static struct group *
lu_getgrgid(int gid)
{
struct group *g;
unsigned int datalen;
XDR inxdr;
static int proc = -1;
int count;
char *lookup_buf;
if (proc < 0)
{
if (_lookup_link(_lu_port, "getgrgid", &proc) != KERN_SUCCESS)
{
return NULL;
}
}
gid = htonl(gid);
datalen = 0;
lookup_buf = NULL;
if (_lookup_all(_lu_port, proc, (unit *)&gid, 1, &lookup_buf, &datalen) != KERN_SUCCESS)
{
return NULL;
}
datalen *= BYTES_PER_XDR_UNIT;
if ((lookup_buf == NULL) || (datalen == 0)) return NULL;
xdrmem_create(&inxdr, lookup_buf, datalen, XDR_DECODE);
count = 0;
if (!xdr_int(&inxdr, &count))
{
xdr_destroy(&inxdr);
vm_deallocate(mach_task_self(), (vm_address_t)lookup_buf, datalen);
return NULL;
}
if (count == 0)
{
xdr_destroy(&inxdr);
vm_deallocate(mach_task_self(), (vm_address_t)lookup_buf, datalen);
return NULL;
}
g = extract_group(&inxdr);
xdr_destroy(&inxdr);
vm_deallocate(mach_task_self(), (vm_address_t)lookup_buf, datalen);
return g;
}
static struct group *
lu_getgrnam(const char *name)
{
struct group *g;
unsigned int datalen;
char namebuf[_LU_MAXLUSTRLEN + BYTES_PER_XDR_UNIT];
XDR outxdr;
XDR inxdr;
static int proc = -1;
int count;
char *lookup_buf;
if (proc < 0)
{
if (_lookup_link(_lu_port, "getgrnam", &proc) != KERN_SUCCESS)
{
return NULL;
}
}
xdrmem_create(&outxdr, namebuf, sizeof(namebuf), XDR_ENCODE);
if (!xdr__lu_string(&outxdr, (_lu_string *)&name))
{
xdr_destroy(&outxdr);
return NULL;
}
datalen = 0;
lookup_buf = NULL;
if (_lookup_all(_lu_port, proc, (unit *)namebuf, xdr_getpos(&outxdr) / BYTES_PER_XDR_UNIT, &lookup_buf, &datalen) != KERN_SUCCESS)
{
return NULL;
}
xdr_destroy(&outxdr);
datalen *= BYTES_PER_XDR_UNIT;
if ((lookup_buf == NULL) || (datalen == 0)) return NULL;
xdrmem_create(&inxdr, lookup_buf, datalen, XDR_DECODE);
count = 0;
if (!xdr_int(&inxdr, &count))
{
xdr_destroy(&inxdr);
vm_deallocate(mach_task_self(), (vm_address_t)lookup_buf, datalen);
return NULL;
}
if (count == 0)
{
xdr_destroy(&inxdr);
vm_deallocate(mach_task_self(), (vm_address_t)lookup_buf, datalen);
return NULL;
}
g = extract_group(&inxdr);
xdr_destroy(&inxdr);
vm_deallocate(mach_task_self(), (vm_address_t)lookup_buf, datalen);
return g;
}
static int
_add_group(int gid, int *list, int *listcount, int max, int dupok, int laststatus)
{
int i, n, addit, status;
if (laststatus != 0) return laststatus;
status = 0;
addit = 1;
n = *listcount;
if (dupok == 0)
{
for (i = 0; (i < n) && (addit == 1); i++)
{
if (list[i] == gid) addit = 0;
}
}
if (addit == 0) return 0;
if (n >= max) return -1;
list[n] = gid;
*listcount = n + 1;
return 0;
}
int
_old_getgrouplist(const char *uname, int basegid, int *groups, int *grpcnt)
{
struct group *grp;
int i, status, maxgroups;
status = 0;
maxgroups = *grpcnt;
*grpcnt = 0;
status = _add_group(basegid, groups, grpcnt, maxgroups, 0, status);
status = _add_group(basegid, groups, grpcnt, maxgroups, 1, status);
setgrent();
while ((grp = getgrent()))
{
if (grp->gr_gid == (gid_t)basegid) continue;
for (i = 0; grp->gr_mem[i]; i++)
{
if (!strcmp(grp->gr_mem[i], uname))
{
status = _add_group(grp->gr_gid, groups, grpcnt, maxgroups, 0, status);
break;
}
}
}
endgrent();
return status;
}
static int
_mbr_running()
{
kern_return_t status;
status = bootstrap_look_up(bootstrap_port, MEMBERD_NAME, &mbr_port);
if (status != KERN_SUCCESS) return 0;
if (mbr_port == MACH_PORT_NULL) return 0;
return 1;
}
#define MAXPWBUF (MAXLOGNAME + 1 + _PASSWORD_LEN + 1 + MAXPATHLEN + 1 + MAXPATHLEN + 1 + 4098)
static int
mbr_getgrouplist(const char *name, int basegid, int *groups, int *grpcnt, int dupbase)
{
struct passwd p, *res;
char buf[MAXPWBUF];
kern_return_t kstatus;
uint32_t i, count;
int pwstatus;
GIDArray gids;
int status, maxgroups;
status = 0;
if (mbr_port == MACH_PORT_NULL) return status;
if (name == NULL) return status;
if (groups == NULL) return status;
if (grpcnt == NULL) return status;
maxgroups = *grpcnt;
*grpcnt = 0;
status = _add_group(basegid, groups, grpcnt, maxgroups, 0, status);
if (dupbase != 0) status = _add_group(basegid, groups, grpcnt, maxgroups, 1, status);
if (status != 0) return status;
memset(&p, 0, sizeof(struct passwd));
memset(buf, 0, sizeof(buf));
res = NULL;
pwstatus = getpwnam_r(name, &p, buf, MAXPWBUF, &res);
if (pwstatus != 0) return status;
if (res == NULL) return status;
count = 0;
kstatus = _mbr_GetGroups(mbr_port, p.pw_uid, &count, gids);
if (kstatus != KERN_SUCCESS) return status;
for (i = 0; (i < count) && (status == 0); i++)
{
status = _add_group(gids[i], groups, grpcnt, maxgroups, 0, status);
}
return status;
}
static int
lu_getgrouplist(const char *name, int basegid, int *groups, int *grpcnt, int dupbase)
{
unsigned int datalen;
XDR outxdr;
XDR inxdr;
static int proc = -1;
char *lookup_buf;
char namebuf[_LU_MAXLUSTRLEN + BYTES_PER_XDR_UNIT];
int gid;
int i, count;
int status, maxgroups;
status = 0;
if (name == NULL) return status;
if (groups == NULL) return status;
if (grpcnt == NULL) return status;
maxgroups = *grpcnt;
*grpcnt = 0;
status = _add_group(basegid, groups, grpcnt, maxgroups, 0, status);
if (dupbase != 0) status = _add_group(basegid, groups, grpcnt, maxgroups, 1, status);
if (status != 0) return status;
if (proc < 0)
{
if (_lookup_link(_lu_port, "initgroups", &proc) != KERN_SUCCESS) return status;
}
xdrmem_create(&outxdr, namebuf, sizeof(namebuf), XDR_ENCODE);
if (!xdr__lu_string(&outxdr, (_lu_string *)&name))
{
xdr_destroy(&outxdr);
return status;
}
datalen = 0;
lookup_buf = NULL;
if (_lookup_all(_lu_port, proc, (unit *)namebuf, xdr_getpos(&outxdr) / BYTES_PER_XDR_UNIT, &lookup_buf, &datalen) != KERN_SUCCESS)
{
xdr_destroy(&outxdr);
return status;
}
xdr_destroy(&outxdr);
datalen *= BYTES_PER_XDR_UNIT;
if ((lookup_buf == NULL) || (datalen == 0)) return 0;
xdrmem_create(&inxdr, lookup_buf, datalen, XDR_DECODE);
if (!xdr_int(&inxdr, &count))
{
xdr_destroy(&inxdr);
vm_deallocate(mach_task_self(), (vm_address_t)lookup_buf, datalen);
return status;
}
for (i = 0; (i < count) && (status == 0); i++)
{
if (!xdr_int(&inxdr, &gid)) break;
status = _add_group(gid, groups, grpcnt, maxgroups, 0, status);
}
xdr_destroy(&inxdr);
vm_deallocate(mach_task_self(), (vm_address_t)lookup_buf, datalen);
return status;
}
static int
getgrouplist_internal(const char *name, int basegid, int *groups, int *grpcnt, int dupbase)
{
if (_mbr_running())
{
return mbr_getgrouplist(name, basegid, groups, grpcnt, dupbase);
}
if (_lu_running())
{
return lu_getgrouplist(name, basegid, groups, grpcnt, dupbase);
}
return _old_getgrouplist(name, basegid, groups, grpcnt);
}
int
getgrouplist(const char *uname, int agroup, int *groups, int *grpcnt)
{
return getgrouplist_internal(uname, agroup, groups, grpcnt, 1);
}
static void
lu_endgrent(void)
{
struct lu_thread_info *tdata;
tdata = _lu_data_create_key(_lu_data_key_group, free_lu_thread_info_group);
_lu_data_free_vm_xdr(tdata);
}
static int
lu_setgrent(void)
{
lu_endgrent();
return 1;
}
static struct group *
lu_getgrent()
{
struct group *g;
static int proc = -1;
struct lu_thread_info *tdata;
tdata = _lu_data_create_key(_lu_data_key_group, free_lu_thread_info_group);
if (tdata == NULL)
{
tdata = (struct lu_thread_info *)calloc(1, sizeof(struct lu_thread_info));
_lu_data_set_key(_lu_data_key_group, tdata);
}
if (tdata->lu_vm == NULL)
{
if (proc < 0)
{
if (_lookup_link(_lu_port, "getgrent", &proc) != KERN_SUCCESS)
{
lu_endgrent();
return NULL;
}
}
if (_lookup_all(_lu_port, proc, NULL, 0, &(tdata->lu_vm), &(tdata->lu_vm_length)) != KERN_SUCCESS)
{
lu_endgrent();
return NULL;
}
tdata->lu_vm_length *= 4;
if (tdata->lu_xdr != NULL)
{
xdr_destroy(tdata->lu_xdr);
free(tdata->lu_xdr);
}
tdata->lu_xdr = (XDR *)calloc(1, sizeof(XDR));
xdrmem_create(tdata->lu_xdr, tdata->lu_vm, tdata->lu_vm_length, XDR_DECODE);
if (!xdr_int(tdata->lu_xdr, &tdata->lu_vm_cursor))
{
lu_endgrent();
return NULL;
}
}
if (tdata->lu_vm_cursor == 0)
{
lu_endgrent();
return NULL;
}
g = extract_group(tdata->lu_xdr);
if (g == NULL)
{
lu_endgrent();
return NULL;
}
tdata->lu_vm_cursor--;
return g;
}
static struct group *
getgr_internal(const char *name, gid_t gid, int source)
{
struct group *res = NULL;
int from_cache;
from_cache = 0;
res = NULL;
switch (source)
{
case GR_GET_NAME:
res = cache_getgrnam(name);
break;
case GR_GET_GID:
res = cache_getgrgid(gid);
break;
default: res = NULL;
}
if (res != NULL)
{
from_cache = 1;
}
else if (_lu_running())
{
switch (source)
{
case GR_GET_NAME:
res = lu_getgrnam(name);
break;
case GR_GET_GID:
res = lu_getgrgid(gid);
break;
case GR_GET_ENT:
res = lu_getgrent();
break;
default: res = NULL;
}
}
else
{
pthread_mutex_lock(&_group_lock);
switch (source)
{
case GR_GET_NAME:
res = copy_group(_old_getgrnam(name));
break;
case GR_GET_GID:
res = copy_group(_old_getgrgid(gid));
break;
case GR_GET_ENT:
res = copy_group(_old_getgrent());
break;
default: res = NULL;
}
pthread_mutex_unlock(&_group_lock);
}
if (from_cache == 0) cache_group(res);
return res;
}
static struct group *
getgr(const char *name, gid_t gid, int source)
{
struct group *res = NULL;
struct lu_thread_info *tdata;
tdata = _lu_data_create_key(_lu_data_key_group, free_lu_thread_info_group);
if (tdata == NULL)
{
tdata = (struct lu_thread_info *)calloc(1, sizeof(struct lu_thread_info));
_lu_data_set_key(_lu_data_key_group, tdata);
}
res = getgr_internal(name, gid, source);
recycle_group(tdata, res);
return (struct group *)tdata->lu_entry;
}
static int
getgr_r(const char *name, gid_t gid, int source, struct group *grp, char *buffer, size_t bufsize, struct group **result)
{
struct group *res = NULL;
int status;
*result = NULL;
errno = 0;
res = getgr_internal(name, gid, source);
if (res == NULL) return -1;
status = copy_group_r(res, grp, buffer, bufsize);
free_group(res);
if (status != 0)
{
errno = ERANGE;
return -1;
}
*result = grp;
return 0;
}
int
initgroups(const char *name, int basegid)
{
int status, ngroups, groups[NGROUPS];
ngroups = NGROUPS;
status = getgrouplist_internal(name, basegid, groups, &ngroups, 0);
if (status < 0) return status;
return setgroups(ngroups, groups);
}
struct group *
getgrnam(const char *name)
{
return getgr(name, -2, GR_GET_NAME);
}
struct group *
getgrgid(gid_t gid)
{
return getgr(NULL, gid, GR_GET_GID);
}
struct group *
getgrent(void)
{
return getgr(NULL, -2, GR_GET_ENT);
}
int
setgrent(void)
{
if (_lu_running()) lu_setgrent();
else _old_setgrent();
return 1;
}
void
endgrent(void)
{
if (_lu_running()) lu_endgrent();
else _old_endgrent();
}
int
getgrnam_r(const char *name, struct group *grp, char *buffer, size_t bufsize, struct group **result)
{
return getgr_r(name, -2, GR_GET_NAME, grp, buffer, bufsize, result);
}
int
getgrgid_r(gid_t gid, struct group *grp, char *buffer, size_t bufsize, struct group **result)
{
return getgr_r(NULL, gid, GR_GET_GID, grp, buffer, bufsize, result);
}