#include <stdlib.h>
#include <sys/errno.h>
#include <mach/mach.h>
#include "membership.h"
#include "membershipPriv.h"
#include <servers/bootstrap.h>
#include <libkern/OSByteOrder.h>
#ifdef DS_AVAILABLE
#include <xpc/xpc.h>
#include <xpc/private.h>
#include <opendirectory/odipc.h>
#include <pthread.h>
#ifdef __i386__
__attribute__((weak_import))
xpc_pipe_t xpc_pipe_create(const char *name, uint64_t flags);
__attribute__((weak_import))
void xpc_pipe_invalidate(xpc_pipe_t pipe);
__attribute__((weak_import))
int xpc_pipe_routine(xpc_pipe_t pipe, xpc_object_t message, xpc_object_t *reply);
#endif
#endif
static const uuid_t _user_compat_prefix = {0xff, 0xff, 0xee, 0xee, 0xdd, 0xdd, 0xcc, 0xcc, 0xbb, 0xbb, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00};
static const uuid_t _group_compat_prefix = {0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0x00, 0x00, 0x00, 0x00};
#define COMPAT_PREFIX_LEN (sizeof(uuid_t) - sizeof(id_t))
#ifdef DS_AVAILABLE
int _si_opendirectory_disabled;
static xpc_pipe_t __mbr_pipe;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
__private_extern__ xpc_object_t _od_rpc_call(const char *procname, xpc_object_t payload, xpc_pipe_t (*get_pipe)(bool));
#endif
#ifdef DS_AVAILABLE
static void
_mbr_fork_child(void)
{
if (__mbr_pipe != NULL) {
xpc_pipe_invalidate(__mbr_pipe);
__mbr_pipe = NULL;
}
pthread_mutex_unlock(&mutex);
}
#endif
#ifdef DS_AVAILABLE
static void
_mbr_fork_prepare(void)
{
pthread_mutex_lock(&mutex);
}
#endif
#ifdef DS_AVAILABLE
static void
_mbr_fork_parent(void)
{
pthread_mutex_unlock(&mutex);
}
#endif
#ifdef DS_AVAILABLE
XPC_RETURNS_RETAINED
static xpc_pipe_t
_mbr_xpc_pipe(bool resetPipe)
{
static dispatch_once_t once;
xpc_pipe_t pipe = NULL;
#ifdef __i386__
if (xpc_pipe_create == NULL) {
_si_opendirectory_disabled = 1;
return NULL;
}
#endif
dispatch_once(&once, ^(void) {
char *rc_xbs;
rc_xbs = getenv("RC_XBS");
if (rc_xbs != NULL && strcmp(rc_xbs, "YES") == 0) {
_si_opendirectory_disabled = 1;
return;
}
pthread_atfork(_mbr_fork_prepare, _mbr_fork_parent, _mbr_fork_child);
});
if (_si_opendirectory_disabled == 1) {
return NULL;
}
pthread_mutex_lock(&mutex);
if (resetPipe) {
xpc_release(__mbr_pipe);
__mbr_pipe = NULL;
}
if (__mbr_pipe == NULL) {
if (!issetugid() && getenv("OD_DEBUG_MODE") != NULL) {
__mbr_pipe = xpc_pipe_create(kODMachMembershipPortNameDebug, 0);
} else {
__mbr_pipe = xpc_pipe_create(kODMachMembershipPortName, XPC_PIPE_FLAG_PRIVILEGED);
}
}
if (__mbr_pipe != NULL) pipe = xpc_retain(__mbr_pipe);
pthread_mutex_unlock(&mutex);
return pipe;
}
#endif
static bool
_mbr_od_available(void)
{
#if DS_AVAILABLE
xpc_pipe_t pipe = _mbr_xpc_pipe(false);
if (pipe != NULL) {
xpc_release(pipe);
return true;
}
#endif
return false;
}
int
mbr_identifier_translate(int id_type, const void *identifier, size_t identifier_size, int target_type, void **result, int *rec_type)
{
#if DS_AVAILABLE
xpc_object_t payload, reply;
#endif
id_t tempID;
size_t identifier_len;
int rc = EIO;
if (identifier == NULL || result == NULL || identifier_size == 0) return EIO;
if (identifier_size == -1) {
identifier_size = strlen(identifier);
} else {
switch (id_type) {
case ID_TYPE_USERNAME:
case ID_TYPE_GROUPNAME:
case ID_TYPE_GROUP_NFS:
case ID_TYPE_USER_NFS:
case ID_TYPE_X509_DN:
case ID_TYPE_KERBEROS:
case ID_TYPE_NAME:
identifier_len = strlen(identifier);
if (identifier_size > identifier_len) {
identifier_size = identifier_len;
}
break;
}
}
switch (target_type) {
case ID_TYPE_GID:
case ID_TYPE_UID:
case ID_TYPE_UID_OR_GID:
if (id_type == ID_TYPE_UUID) {
const uint8_t *uu = identifier;
if (identifier_size != sizeof(uuid_t)) return EINVAL;
if (memcmp(uu, _user_compat_prefix, COMPAT_PREFIX_LEN) == 0) {
id_t *tempRes = malloc(sizeof(*tempRes));
memcpy(&tempID, &uu[COMPAT_PREFIX_LEN], sizeof(tempID));
(*tempRes) = ntohl(tempID);
(*result) = tempRes;
if (rec_type != NULL) {
(*rec_type) = MBR_REC_TYPE_USER;
}
return 0;
} else if (memcmp(uu, _group_compat_prefix, COMPAT_PREFIX_LEN) == 0) {
id_t *tempRes = malloc(sizeof(*tempRes));
memcpy(&tempID, &uu[COMPAT_PREFIX_LEN], sizeof(tempID));
(*tempRes) = ntohl(tempID);
(*result) = tempRes;
if (rec_type != NULL) {
(*rec_type) = MBR_REC_TYPE_GROUP;
}
return 0;
}
}
break;
case ID_TYPE_UUID:
switch (id_type) {
case ID_TYPE_UID:
if (identifier_size != sizeof(tempID)) return EINVAL;
tempID = *((id_t *) identifier);
if ((tempID == 0) || (_mbr_od_available() == false)) {
uint8_t *tempUU = malloc(sizeof(uuid_t));
uuid_copy(tempUU, _user_compat_prefix);
*((id_t *) &tempUU[COMPAT_PREFIX_LEN]) = htonl(tempID);
(*result) = tempUU;
if (rec_type != NULL) {
(*rec_type) = MBR_REC_TYPE_USER;
}
return 0;
}
break;
case ID_TYPE_GID:
if (identifier_size != sizeof(tempID)) return EINVAL;
tempID = *((id_t *) identifier);
if ((tempID == 0) || (_mbr_od_available() == false)) {
uint8_t *tempUU = malloc(sizeof(uuid_t));
uuid_copy(tempUU, _group_compat_prefix);
*((id_t *) &tempUU[COMPAT_PREFIX_LEN]) = htonl(tempID);
(*result) = tempUU;
if (rec_type != NULL) {
(*rec_type) = MBR_REC_TYPE_GROUP;
}
return 0;
}
break;
}
break;
}
#if DS_AVAILABLE
payload = xpc_dictionary_create(NULL, NULL, 0);
if (payload == NULL) return EIO;
xpc_dictionary_set_int64(payload, "requesting", target_type);
xpc_dictionary_set_int64(payload, "type", id_type);
xpc_dictionary_set_data(payload, "identifier", identifier, identifier_size);
reply = _od_rpc_call("mbr_identifier_translate", payload, _mbr_xpc_pipe);
if (reply != NULL) {
const void *reply_id;
size_t idLen;
rc = (int) xpc_dictionary_get_int64(reply, "error");
if (rc == 0) {
reply_id = xpc_dictionary_get_data(reply, "identifier", &idLen);
if (reply_id != NULL) {
char *identifier = malloc(idLen);
memcpy(identifier, reply_id, idLen); (*result) = identifier;
if (rec_type != NULL) {
(*rec_type) = (int) xpc_dictionary_get_int64(reply, "rectype");
}
} else {
(*result) = NULL;
rc = ENOENT;
}
}
xpc_release(reply);
}
xpc_release(payload);
#endif
return rc;
}
int
mbr_uid_to_uuid(uid_t id, uuid_t uu)
{
return mbr_identifier_to_uuid(ID_TYPE_UID, &id, sizeof(id), uu);
}
int
mbr_gid_to_uuid(gid_t id, uuid_t uu)
{
return mbr_identifier_to_uuid(ID_TYPE_GID, &id, sizeof(id), uu);
}
int
mbr_uuid_to_id(const uuid_t uu, uid_t *id, int *id_type)
{
id_t *result;
int local_type;
int rc;
rc = mbr_identifier_translate(ID_TYPE_UUID, uu, sizeof(uuid_t), ID_TYPE_UID_OR_GID, (void **) &result, &local_type);
if (rc == 0) {
switch (local_type) {
case MBR_REC_TYPE_GROUP:
(*id_type) = ID_TYPE_GID;
break;
case MBR_REC_TYPE_USER:
(*id_type) = ID_TYPE_UID;
break;
default:
(*id_type) = -1;
break;
}
(*id) = (*result);
free(result);
}
return rc;
}
int
mbr_sid_to_uuid(const nt_sid_t *sid, uuid_t uu)
{
#ifdef DS_AVAILABLE
return mbr_identifier_to_uuid(ID_TYPE_SID, sid, sizeof(*sid), uu);
#else
return EIO;
#endif
}
int
mbr_identifier_to_uuid(int id_type, const void *identifier, size_t identifier_size, uuid_t uu)
{
uint8_t *result;
int rc;
rc = mbr_identifier_translate(id_type, identifier, identifier_size, ID_TYPE_UUID, (void **) &result, NULL);
if (rc == 0) {
uuid_copy(uu, result);
free(result);
}
return rc;
}
int
mbr_uuid_to_sid_type(const uuid_t uu, nt_sid_t *sid, int *id_type)
{
#ifdef DS_AVAILABLE
void *result;
int local_type;
int rc;
rc = mbr_identifier_translate(ID_TYPE_UUID, uu, sizeof(uuid_t), ID_TYPE_SID, &result, &local_type);
if (rc == 0) {
memcpy(sid, result, sizeof(nt_sid_t));
if (id_type != NULL) {
switch (local_type) {
case MBR_REC_TYPE_USER:
(*id_type) = SID_TYPE_USER;
break;
case MBR_REC_TYPE_GROUP:
(*id_type) = SID_TYPE_GROUP;
break;
default:
break;
}
}
free(result);
}
return rc;
#else
return EIO;
#endif
}
int
mbr_uuid_to_sid(const uuid_t uu, nt_sid_t *sid)
{
#ifdef DS_AVAILABLE
int type, status;
type = 0;
status = mbr_uuid_to_sid_type(uu, sid, &type);
if (status != 0) return status;
return 0;
#else
return EIO;
#endif
}
int
mbr_check_membership(const uuid_t user, const uuid_t group, int *ismember)
{
return mbr_check_membership_ext(ID_TYPE_UUID, user, sizeof(uuid_t), ID_TYPE_UUID, group, 0, ismember);
}
int
mbr_check_membership_refresh(const uuid_t user, uuid_t group, int *ismember)
{
return mbr_check_membership_ext(ID_TYPE_UUID, user, sizeof(uuid_t), ID_TYPE_UUID, group, 1, ismember);
}
int
mbr_check_membership_ext(int userid_type, const void *userid, size_t userid_size, int groupid_type, const void *groupid, int refresh, int *isMember)
{
#ifdef DS_AVAILABLE
xpc_object_t payload, reply;
int rc = 0;
payload = xpc_dictionary_create(NULL, NULL, 0);
if (payload == NULL) return ENOMEM;
xpc_dictionary_set_int64(payload, "user_idtype", userid_type);
xpc_dictionary_set_data(payload, "user_id", userid, userid_size);
xpc_dictionary_set_int64(payload, "group_idtype", groupid_type);
switch (groupid_type) {
case ID_TYPE_GROUPNAME:
case ID_TYPE_GROUP_NFS:
xpc_dictionary_set_data(payload, "group_id", groupid, strlen(groupid));
break;
case ID_TYPE_GID:
xpc_dictionary_set_data(payload, "group_id", groupid, sizeof(id_t));
break;
case ID_TYPE_SID:
xpc_dictionary_set_data(payload, "group_id", groupid, sizeof(nt_sid_t));
break;
case ID_TYPE_UUID:
xpc_dictionary_set_data(payload, "group_id", groupid, sizeof(uuid_t));
break;
default:
rc = EINVAL;
break;
}
if (rc == 0) {
reply = _od_rpc_call("mbr_check_membership", payload, _mbr_xpc_pipe);
if (reply != NULL) {
rc = (int) xpc_dictionary_get_int64(reply, "error");
(*isMember) = xpc_dictionary_get_bool(reply, "ismember");
xpc_release(reply);
} else {
rc = EIO;
}
}
xpc_release(payload);
return rc;
#else
return EIO;
#endif
}
int
mbr_check_membership_by_id(uuid_t user, gid_t group, int *ismember)
{
return mbr_check_membership_ext(ID_TYPE_UUID, user, sizeof(uuid_t), ID_TYPE_GID, &group, 0, ismember);
}
int
mbr_reset_cache()
{
#ifdef DS_AVAILABLE
_od_rpc_call("mbr_cache_flush", NULL, _mbr_xpc_pipe);
return 0;
#else
return EIO;
#endif
}
int
mbr_user_name_to_uuid(const char *name, uuid_t uu)
{
return mbr_identifier_to_uuid(ID_TYPE_USERNAME, name, -1, uu);
}
int
mbr_group_name_to_uuid(const char *name, uuid_t uu)
{
return mbr_identifier_to_uuid(ID_TYPE_GROUPNAME, name, -1, uu);
}
int
mbr_check_service_membership(const uuid_t user, const char *servicename, int *ismember)
{
#ifdef DS_AVAILABLE
xpc_object_t payload, reply;
int result = EIO;
if (ismember == NULL || servicename == NULL) return EINVAL;
payload = xpc_dictionary_create(NULL, NULL, 0);
if (payload == NULL) return EIO;
xpc_dictionary_set_data(payload, "user_id", user, sizeof(uuid_t));
xpc_dictionary_set_int64(payload, "user_idtype", ID_TYPE_UUID);
xpc_dictionary_set_string(payload, "service", servicename);
reply = _od_rpc_call("mbr_check_service_membership", payload, _mbr_xpc_pipe);
if (reply != NULL) {
result = (int) xpc_dictionary_get_int64(reply, "error");
(*ismember) = xpc_dictionary_get_bool(reply, "ismember");
xpc_release(reply);
} else {
(*ismember) = 0;
}
xpc_release(payload);
return result;
#else
return EIO;
#endif
}
#ifdef DS_AVAILABLE
static char *
ConvertBytesToDecimal(char *buffer, unsigned long long value)
{
char *temp;
buffer[24] = '\0';
buffer[23] = '0';
if (value == 0)
return &buffer[23];
temp = &buffer[24];
while (value != 0)
{
temp--;
*temp = '0' + (value % 10);
value /= 10;
}
return temp;
}
#endif
int
mbr_sid_to_string(const nt_sid_t *sid, char *string)
{
#ifdef DS_AVAILABLE
char *current = string;
long long temp = 0;
int i;
char tempBuffer[25];
if (sid->sid_authcount > NTSID_MAX_AUTHORITIES) return EINVAL;
for (i = 0; i < 6; i++)
temp = (temp << 8) | sid->sid_authority[i];
current[0] = 'S';
current[1] = '-';
current += 2;
strcpy(current, ConvertBytesToDecimal(tempBuffer, sid->sid_kind));
current = current + strlen(current);
*current = '-';
current++;
strcpy(current, ConvertBytesToDecimal(tempBuffer, temp));
for(i=0; i < sid->sid_authcount; i++)
{
current = current + strlen(current);
*current = '-';
current++;
strcpy(current, ConvertBytesToDecimal(tempBuffer, sid->sid_authorities[i]));
}
return 0;
#else
return EIO;
#endif
}
int
mbr_string_to_sid(const char *string, nt_sid_t *sid)
{
#ifdef DS_AVAILABLE
char *current = (char *)string+2;
int count = 0;
long long temp;
if (string == NULL) return EINVAL;
memset(sid, 0, sizeof(nt_sid_t));
if (string[0] != 'S' || string[1] != '-') return EINVAL;
sid->sid_kind = strtol(current, ¤t, 10);
if (*current == '\0') return EINVAL;
current++;
temp = strtoll(current, ¤t, 10);
temp = OSSwapHostToBigInt64(temp);
memcpy(sid->sid_authority, ((char*)&temp)+2, 6);
while (*current != '\0' && count < NTSID_MAX_AUTHORITIES)
{
current++;
errno = 0;
sid->sid_authorities[count] = (u_int32_t)strtoll(current, ¤t, 10);
if ((sid->sid_authorities[count] == 0) && (errno == EINVAL)) {
return EINVAL;
}
count++;
}
if (*current != '\0') return EINVAL;
sid->sid_authcount = count;
return 0;
#else
return EIO;
#endif
}
int
mbr_uuid_to_string(const uuid_t uu, char *string)
{
uuid_unparse_upper(uu, string);
return 0;
}
int
mbr_string_to_uuid(const char *string, uuid_t uu)
{
return uuid_parse(string, uu);
}
int
mbr_set_identifier_ttl(int id_type, const void *identifier, size_t identifier_size, unsigned int seconds)
{
#ifdef DS_AVAILABLE
xpc_object_t payload, reply;
int rc = 0;
payload = xpc_dictionary_create(NULL, NULL, 0);
if (payload == NULL) return ENOMEM;
xpc_dictionary_set_int64(payload, "type", id_type);
xpc_dictionary_set_data(payload, "identifier", identifier, identifier_size);
xpc_dictionary_set_int64(payload, "ttl", seconds);
if (rc == 0) {
reply = _od_rpc_call("mbr_set_identifier_ttl", payload, _mbr_xpc_pipe);
if (reply != NULL) {
rc = (int) xpc_dictionary_get_int64(reply, "error");
xpc_release(reply);
} else {
rc = EIO;
}
}
xpc_release(payload);
return rc;
#else
return EIO;
#endif
}