#ifndef LEAN_CLIENT
#include "k5_mig_client.h"
#include "krb5-ipc.h"
#include "k5_mig_request.h"
#include "k5_mig_replyServer.h"
#include "k5-thread.h"
#include <mach/mach.h>
#include <mach/task_special_ports.h>
#include <servers/bootstrap.h>
#include <dlfcn.h>
#define KIPC_SERVICE_COUNT 2
typedef struct k5_ipc_connection {
const char *service_id;
mach_port_t port;
} *k5_ipc_connection;
typedef struct k5_ipc_connection_info {
struct k5_ipc_connection connections[KIPC_SERVICE_COUNT];
boolean_t server_died;
k5_ipc_stream reply_stream;
} *k5_ipc_connection_info;
static const char *k5_ipc_known_services[KIPC_SERVICE_COUNT] = {
"edu.mit.Kerberos.CCacheServer",
"edu.mit.Kerberos.KerberosAgent" };
typedef struct k5_ipc_service_port {
const char *service_id;
mach_port_t service_port;
} k5_ipc_service_port;
static k5_mutex_t g_service_ports_mutex = K5_MUTEX_PARTIAL_INITIALIZER;
static k5_ipc_service_port g_service_ports[KIPC_SERVICE_COUNT] = {
{ "edu.mit.Kerberos.CCacheServer", MACH_PORT_NULL },
{ "edu.mit.Kerberos.KerberosAgent", MACH_PORT_NULL } };
static int use_uid = 0;
static uid_t targetuid;
static void
clear_cache(void)
{
k5_ipc_connection_info cinfo;
int i;
for (i = 0; i < KIPC_SERVICE_COUNT; i++) {
if (MACH_PORT_VALID (g_service_ports[i].service_port)) {
mach_port_destroy (mach_task_self (),
g_service_ports[i].service_port);
g_service_ports[i].service_port = MACH_PORT_NULL;
}
}
cinfo = k5_getspecific (K5_KEY_IPC_CONNECTION_INFO);
if (cinfo) {
for (i = 0; i < KIPC_SERVICE_COUNT; i++) {
if (MACH_PORT_VALID (cinfo->connections[i].port))
mach_port_deallocate(mach_task_self(),
cinfo->connections[i].port);
cinfo->connections[i].port = MACH_PORT_NULL;
}
}
}
void
krb5_ipc_client_set_target_uid(uid_t uid)
{
int err;
err = k5_mutex_lock (&g_service_ports_mutex);
if (!err) {
use_uid = 1;
targetuid = uid;
clear_cache();
k5_mutex_unlock (&g_service_ports_mutex);
}
}
void
krb5_ipc_client_clear_target(void)
{
int err;
err = k5_mutex_lock (&g_service_ports_mutex);
if (!err) {
use_uid = 0;
clear_cache();
k5_mutex_unlock (&g_service_ports_mutex);
}
}
static void k5_ipc_client_cinfo_free (void *io_cinfo)
{
if (io_cinfo) {
k5_ipc_connection_info cinfo = io_cinfo;
int i;
for (i = 0; i < KIPC_SERVICE_COUNT; i++) {
if (MACH_PORT_VALID (cinfo->connections[i].port)) {
mach_port_mod_refs (mach_task_self(),
cinfo->connections[i].port,
MACH_PORT_RIGHT_SEND, -1 );
cinfo->connections[i].port = MACH_PORT_NULL;
}
}
free (cinfo);
}
}
static int k5_ipc_client_cinfo_allocate (k5_ipc_connection_info *out_cinfo)
{
int err = 0;
k5_ipc_connection_info cinfo = NULL;
cinfo = malloc (sizeof (*cinfo));
if (!cinfo) { err = ENOMEM; }
if (!err) {
int i;
cinfo->server_died = 0;
cinfo->reply_stream = NULL;
for (i = 0; i < KIPC_SERVICE_COUNT; i++) {
cinfo->connections[i].service_id = k5_ipc_known_services[i];
cinfo->connections[i].port = MACH_PORT_NULL;
}
}
if (!err) {
*out_cinfo = cinfo;
cinfo = NULL;
}
k5_ipc_client_cinfo_free (cinfo);
return err;
}
#pragma mark -
MAKE_INIT_FUNCTION(k5_cli_ipc_thread_init);
MAKE_FINI_FUNCTION(k5_cli_ipc_thread_fini);
static int k5_cli_ipc_thread_init (void)
{
int err = 0;
err = k5_key_register (K5_KEY_IPC_CONNECTION_INFO,
k5_ipc_client_cinfo_free);
if (!err) {
err = k5_mutex_finish_init (&g_service_ports_mutex);
}
return err;
}
static void k5_cli_ipc_thread_fini (void)
{
int err = 0;
err = k5_mutex_lock (&g_service_ports_mutex);
if (!err) {
clear_cache();
k5_mutex_unlock (&g_service_ports_mutex);
}
k5_key_delete (K5_KEY_IPC_CONNECTION_INFO);
k5_mutex_destroy (&g_service_ports_mutex);
}
kern_return_t
bootstrap_look_up_per_user(mach_port_t, name_t, uid_t, mach_port_t *);
static int
get_service_port(char *service, mach_port_t *sport)
{
int ret;
if (use_uid) {
mach_port_t rootbs;
ret = task_get_bootstrap_port(mach_task_self(), &rootbs);
if (ret)
return ret;
while (1) {
kern_return_t kr;
mach_port_t parent;
kr = bootstrap_parent(rootbs, &parent);
if (kr == KERN_SUCCESS) {
if (parent == MACH_PORT_NULL || parent == rootbs)
break;
} else
break;
mach_port_deallocate(mach_task_self(), rootbs);
rootbs = parent;
}
ret = bootstrap_look_up_per_user(rootbs, service, targetuid,
sport);
mach_port_deallocate(mach_task_self(), rootbs);
} else {
mach_port_t boot_port;
ret = task_get_bootstrap_port(mach_task_self(), &boot_port);
if (ret)
return ret;
ret = bootstrap_look_up (boot_port, service, sport);
mach_port_deallocate(mach_task_self(), boot_port);
}
return ret;
}
#pragma mark -
static kern_return_t k5_ipc_client_lookup_server (const char *in_service_id,
boolean_t in_launch_if_necessary,
boolean_t in_use_cached_port,
mach_port_t *out_service_port)
{
kern_return_t err = 0;
kern_return_t lock_err = 0;
mach_port_t k5_service_port = MACH_PORT_NULL;
boolean_t found_entry = 0;
int i;
in_launch_if_necessary = 1;
if (!in_service_id ) { err = EINVAL; }
if (!out_service_port) { err = EINVAL; }
if (!err) {
lock_err = k5_mutex_lock (&g_service_ports_mutex);
if (lock_err) { err = lock_err; }
}
for (i = 0; !err && i < KIPC_SERVICE_COUNT; i++) {
if (!strcmp (in_service_id, g_service_ports[i].service_id)) {
found_entry = 1;
if (in_use_cached_port) {
k5_service_port = g_service_ports[i].service_port;
}
break;
}
}
if (!err && (!MACH_PORT_VALID (k5_service_port) || !in_use_cached_port)) {
char *service = NULL;
if (!err && !in_launch_if_necessary) {
char *lookup = NULL;
mach_port_t lookup_port = MACH_PORT_NULL;
int w = asprintf (&lookup, "%s%s",
in_service_id, K5_MIG_LOOKUP_SUFFIX);
if (w < 0) { err = ENOMEM; }
if (!err) {
err = get_service_port (lookup, &lookup_port);
free (lookup);
}
if (MACH_PORT_VALID (lookup_port)) {
mach_port_deallocate (mach_task_self (), lookup_port);
}
}
if (!err) {
int w = asprintf (&service, "%s%s",
in_service_id, K5_MIG_SERVICE_SUFFIX);
if (w < 0) { err = ENOMEM; }
}
if (!err) {
err = get_service_port (service, &k5_service_port);
free (service);
if (!err && found_entry) {
if (!err && MACH_PORT_VALID (g_service_ports[i].service_port)) {
mach_port_deallocate (mach_task_self (),
g_service_ports[i].service_port);
}
g_service_ports[i].service_port = k5_service_port;
}
}
}
if (!err) {
*out_service_port = k5_service_port;
}
if (!lock_err) { k5_mutex_unlock (&g_service_ports_mutex); }
return err;
}
#pragma mark -
static boolean_t k5_ipc_reply_demux (mach_msg_header_t *request,
mach_msg_header_t *reply)
{
boolean_t handled = 0;
if (CALL_INIT_FUNCTION (k5_cli_ipc_thread_init) != 0) {
return 0;
}
if (!handled && request->msgh_id == MACH_NOTIFY_NO_SENDERS) {
k5_ipc_connection_info cinfo = k5_getspecific (K5_KEY_IPC_CONNECTION_INFO);
if (cinfo) {
cinfo->server_died = 1;
}
handled = 1;
}
if (!handled) {
handled = k5_ipc_reply_server (request, reply);
}
return handled;
}
kern_return_t k5_ipc_client_reply (mach_port_t in_reply_port,
k5_ipc_inl_reply_t in_inl_reply,
mach_msg_type_number_t in_inl_replyCnt,
k5_ipc_ool_reply_t in_ool_reply,
mach_msg_type_number_t in_ool_replyCnt)
{
kern_return_t err = KERN_SUCCESS;
k5_ipc_connection_info cinfo = NULL;
if (!err) {
err = CALL_INIT_FUNCTION (k5_cli_ipc_thread_init);
}
if (!err) {
cinfo = k5_getspecific (K5_KEY_IPC_CONNECTION_INFO);
if (!cinfo || !cinfo->reply_stream) { err = EINVAL; }
}
if (!err) {
if (in_inl_replyCnt) {
err = k5_ipc_stream_write (cinfo->reply_stream,
in_inl_reply, in_inl_replyCnt);
} else if (in_ool_replyCnt) {
err = k5_ipc_stream_write (cinfo->reply_stream,
in_ool_reply, in_ool_replyCnt);
} else {
err = EINVAL;
}
}
if (in_ool_replyCnt) { vm_deallocate (mach_task_self (),
(vm_address_t) in_ool_reply,
in_ool_replyCnt); }
return err;
}
#pragma mark -
int32_t k5_ipc_send_request (const char *in_service_id,
int32_t in_launch_server,
k5_ipc_stream in_request_stream,
k5_ipc_stream *out_reply_stream)
{
int err = 0;
int32_t done = 0;
int32_t try_count = 0;
mach_port_t server_port = MACH_PORT_NULL;
k5_ipc_connection_info cinfo = NULL;
k5_ipc_connection connection = NULL;
mach_port_t reply_port = MACH_PORT_NULL;
const char *inl_request = NULL;
mach_msg_type_number_t inl_request_length = 0;
k5_ipc_ool_request_t ool_request = NULL;
mach_msg_type_number_t ool_request_length = 0;
if (!in_request_stream) { err = EINVAL; }
if (!out_reply_stream ) { err = EINVAL; }
if (!err) {
err = CALL_INIT_FUNCTION (k5_cli_ipc_thread_init);
}
if (!err) {
mach_msg_type_number_t request_length = k5_ipc_stream_size (in_request_stream);
if (request_length > K5_IPC_MAX_INL_MSG_SIZE) {
err = vm_read (mach_task_self (),
(vm_address_t) k5_ipc_stream_data (in_request_stream),
request_length,
(vm_address_t *) &ool_request,
&ool_request_length);
} else {
inl_request_length = request_length;
inl_request = k5_ipc_stream_data (in_request_stream);
}
}
if (!err) {
cinfo = k5_getspecific (K5_KEY_IPC_CONNECTION_INFO);
if (!cinfo) {
err = k5_ipc_client_cinfo_allocate (&cinfo);
if (!err) {
err = k5_setspecific (K5_KEY_IPC_CONNECTION_INFO, cinfo);
}
}
if (!err) {
int i, found = 0;
for (i = 0; i < KIPC_SERVICE_COUNT; i++) {
if (!strcmp (in_service_id, cinfo->connections[i].service_id)) {
found = 1;
connection = &cinfo->connections[i];
break;
}
}
if (!found) { err = EINVAL; }
}
}
if (!err) {
err = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE,
&reply_port);
}
while (!err && !done) {
if (!err && !MACH_PORT_VALID (connection->port)) {
err = k5_ipc_client_lookup_server (in_service_id, in_launch_server,
try_count == 0 ? TRUE : FALSE,
&server_port);
if (err)
continue;
err = k5_ipc_client_create_client_connection(server_port,
&connection->port);
if (err)
continue;
}
if (!err) {
err = k5_ipc_client_request (connection->port, reply_port,
inl_request, inl_request_length,
ool_request, ool_request_length);
}
if (err == MACH_SEND_INVALID_DEST) {
if (try_count < 2) {
try_count++;
err = 0;
}
if (MACH_PORT_VALID (connection->port)) {
mach_port_mod_refs (mach_task_self(), connection->port,
MACH_PORT_RIGHT_SEND, -1 );
connection->port = MACH_PORT_NULL;
}
} else {
done = 1;
ool_request = NULL;
ool_request_length = 0;
}
}
if (!err) {
err = k5_ipc_stream_new (&cinfo->reply_stream);
}
if (!err) {
mach_port_t old_notification_target = MACH_PORT_NULL;
err = mach_port_request_notification (mach_task_self (), reply_port,
MACH_NOTIFY_NO_SENDERS, 1,
reply_port,
MACH_MSG_TYPE_MAKE_SEND_ONCE,
&old_notification_target);
if (!err && old_notification_target != MACH_PORT_NULL) {
mach_port_deallocate (mach_task_self (), old_notification_target);
}
}
if (!err) {
cinfo->server_died = 0;
err = mach_msg_server_once (k5_ipc_reply_demux, K5_IPC_MAX_MSG_SIZE,
reply_port, MACH_MSG_TIMEOUT_NONE);
if (!err && cinfo->server_died) {
err = ENOTCONN;
}
}
if (err == BOOTSTRAP_UNKNOWN_SERVICE && !in_launch_server) {
err = 0;
}
if (!err) {
*out_reply_stream = cinfo->reply_stream;
cinfo->reply_stream = NULL;
}
if (reply_port != MACH_PORT_NULL) {
mach_port_destroy (mach_task_self (), reply_port);
}
if (ool_request_length) {
vm_deallocate (mach_task_self (),
(vm_address_t) ool_request, ool_request_length);
}
if (cinfo && cinfo->reply_stream) {
k5_ipc_stream_release (cinfo->reply_stream);
cinfo->reply_stream = NULL;
}
return err;
}
#endif