1373 lines
30 KiB
C
1373 lines
30 KiB
C
/*
|
|
* Copyright (c) 2009 Kungliga Tekniska Högskolan
|
|
* (Royal Institute of Technology, Stockholm, Sweden).
|
|
* All rights reserved.
|
|
*
|
|
* Portions Copyright (c) 2009 Apple Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the Institute nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "hi_locl.h"
|
|
#include <assert.h>
|
|
#include <err.h>
|
|
|
|
#define MAX_PACKET_SIZE (128 * 1024)
|
|
|
|
struct heim_sipc {
|
|
int (*release)(heim_sipc ctx);
|
|
heim_ipc_callback callback;
|
|
void *userctx;
|
|
void *mech;
|
|
};
|
|
|
|
#if defined(__APPLE__) && defined(HAVE_GCD)
|
|
|
|
#include "heim_ipcServer.h"
|
|
#include "heim_ipc_reply.h"
|
|
#include "heim_ipc_async.h"
|
|
|
|
static dispatch_source_t timer;
|
|
static dispatch_queue_t timerq;
|
|
static uint64_t timeoutvalue;
|
|
|
|
static dispatch_queue_t eventq;
|
|
|
|
static dispatch_queue_t workq;
|
|
|
|
static void
|
|
default_timer_ev(void)
|
|
{
|
|
exit(0);
|
|
}
|
|
|
|
static void (*timer_ev)(void) = default_timer_ev;
|
|
|
|
static void
|
|
set_timer(void)
|
|
{
|
|
dispatch_source_set_timer(timer,
|
|
dispatch_time(DISPATCH_TIME_NOW,
|
|
timeoutvalue * NSEC_PER_SEC),
|
|
timeoutvalue * NSEC_PER_SEC, 1000000);
|
|
}
|
|
|
|
static void
|
|
init_globals(void)
|
|
{
|
|
static dispatch_once_t once;
|
|
dispatch_once(&once, ^{
|
|
timerq = dispatch_queue_create("hiem-sipc-timer-q", NULL);
|
|
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, timerq);
|
|
dispatch_source_set_event_handler(timer, ^{ timer_ev(); } );
|
|
|
|
workq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
|
eventq = dispatch_queue_create("heim-ipc.event-queue", NULL);
|
|
});
|
|
}
|
|
|
|
static void
|
|
suspend_timer(void)
|
|
{
|
|
dispatch_suspend(timer);
|
|
}
|
|
|
|
static void
|
|
restart_timer(void)
|
|
{
|
|
dispatch_sync(timerq, ^{ set_timer(); });
|
|
dispatch_resume(timer);
|
|
}
|
|
|
|
struct mach_service {
|
|
mach_port_t sport;
|
|
dispatch_source_t source;
|
|
dispatch_queue_t queue;
|
|
};
|
|
|
|
struct mach_call_ctx {
|
|
mach_port_t reply_port;
|
|
heim_icred cred;
|
|
heim_idata req;
|
|
};
|
|
|
|
|
|
static void
|
|
mach_complete_sync(heim_sipc_call ctx, int returnvalue, heim_idata *reply)
|
|
{
|
|
struct mach_call_ctx *s = (struct mach_call_ctx *)ctx;
|
|
heim_ipc_message_inband_t replyin;
|
|
mach_msg_type_number_t replyinCnt = 0;
|
|
heim_ipc_message_outband_t replyout = 0;
|
|
mach_msg_type_number_t replyoutCnt = 0;
|
|
|
|
if (returnvalue) {
|
|
/* on error, no reply */
|
|
} else if (reply->length < 2048) {
|
|
replyinCnt = reply->length;
|
|
memcpy(replyin, reply->data, replyinCnt);
|
|
} else {
|
|
vm_read(mach_task_self(),
|
|
(vm_address_t)reply->data, reply->length,
|
|
(vm_address_t *)&replyout, &replyoutCnt);
|
|
}
|
|
|
|
mheim_ripc_call_reply(s->reply_port, returnvalue,
|
|
replyin, replyinCnt,
|
|
replyout, replyoutCnt);
|
|
|
|
heim_ipc_free_cred(s->cred);
|
|
free(s->req.data);
|
|
free(s);
|
|
restart_timer();
|
|
}
|
|
|
|
static void
|
|
mach_complete_async(heim_sipc_call ctx, int returnvalue, heim_idata *reply)
|
|
{
|
|
struct mach_call_ctx *s = (struct mach_call_ctx *)ctx;
|
|
heim_ipc_message_inband_t replyin;
|
|
mach_msg_type_number_t replyinCnt = 0;
|
|
heim_ipc_message_outband_t replyout = 0;
|
|
mach_msg_type_number_t replyoutCnt = 0;
|
|
|
|
if (returnvalue) {
|
|
/* on error, no reply */
|
|
} else if (reply->length < 2048) {
|
|
replyinCnt = reply->length;
|
|
memcpy(replyin, reply->data, replyinCnt);
|
|
} else {
|
|
vm_read(mach_task_self(),
|
|
(vm_address_t)reply->data, reply->length,
|
|
(vm_address_t *)&replyout, &replyoutCnt);
|
|
}
|
|
|
|
mheim_aipc_acall_reply(s->reply_port, returnvalue,
|
|
replyin, replyinCnt,
|
|
replyout, replyoutCnt);
|
|
heim_ipc_free_cred(s->cred);
|
|
free(s->req.data);
|
|
free(s);
|
|
restart_timer();
|
|
}
|
|
|
|
|
|
kern_return_t
|
|
mheim_do_call(mach_port_t server_port,
|
|
audit_token_t client_creds,
|
|
mach_port_t reply_port,
|
|
heim_ipc_message_inband_t requestin,
|
|
mach_msg_type_number_t requestinCnt,
|
|
heim_ipc_message_outband_t requestout,
|
|
mach_msg_type_number_t requestoutCnt,
|
|
int *returnvalue,
|
|
heim_ipc_message_inband_t replyin,
|
|
mach_msg_type_number_t *replyinCnt,
|
|
heim_ipc_message_outband_t *replyout,
|
|
mach_msg_type_number_t *replyoutCnt)
|
|
{
|
|
heim_sipc ctx = dispatch_get_context(dispatch_get_current_queue());
|
|
struct mach_call_ctx *s;
|
|
kern_return_t kr;
|
|
uid_t uid;
|
|
gid_t gid;
|
|
pid_t pid;
|
|
au_asid_t session;
|
|
|
|
*replyout = NULL;
|
|
*replyoutCnt = 0;
|
|
*replyinCnt = 0;
|
|
|
|
s = malloc(sizeof(*s));
|
|
if (s == NULL)
|
|
return KERN_MEMORY_FAILURE; /* XXX */
|
|
|
|
s->reply_port = reply_port;
|
|
|
|
audit_token_to_au32(client_creds, NULL, &uid, &gid, NULL, NULL, &pid, &session, NULL);
|
|
|
|
kr = _heim_ipc_create_cred(uid, gid, pid, session, &s->cred);
|
|
if (kr) {
|
|
free(s);
|
|
return kr;
|
|
}
|
|
|
|
suspend_timer();
|
|
|
|
if (requestinCnt) {
|
|
s->req.data = malloc(requestinCnt);
|
|
memcpy(s->req.data, requestin, requestinCnt);
|
|
s->req.length = requestinCnt;
|
|
} else {
|
|
s->req.data = malloc(requestoutCnt);
|
|
memcpy(s->req.data, requestout, requestoutCnt);
|
|
s->req.length = requestoutCnt;
|
|
}
|
|
|
|
dispatch_async(workq, ^{
|
|
(ctx->callback)(ctx->userctx, &s->req, s->cred,
|
|
mach_complete_sync, (heim_sipc_call)s);
|
|
});
|
|
|
|
return MIG_NO_REPLY;
|
|
}
|
|
|
|
kern_return_t
|
|
mheim_do_call_request(mach_port_t server_port,
|
|
audit_token_t client_creds,
|
|
mach_port_t reply_port,
|
|
heim_ipc_message_inband_t requestin,
|
|
mach_msg_type_number_t requestinCnt,
|
|
heim_ipc_message_outband_t requestout,
|
|
mach_msg_type_number_t requestoutCnt)
|
|
{
|
|
heim_sipc ctx = dispatch_get_context(dispatch_get_current_queue());
|
|
struct mach_call_ctx *s;
|
|
kern_return_t kr;
|
|
uid_t uid;
|
|
gid_t gid;
|
|
pid_t pid;
|
|
au_asid_t session;
|
|
|
|
s = malloc(sizeof(*s));
|
|
if (s == NULL)
|
|
return KERN_MEMORY_FAILURE; /* XXX */
|
|
|
|
s->reply_port = reply_port;
|
|
|
|
audit_token_to_au32(client_creds, NULL, &uid, &gid, NULL, NULL, &pid, &session, NULL);
|
|
|
|
kr = _heim_ipc_create_cred(uid, gid, pid, session, &s->cred);
|
|
if (kr) {
|
|
free(s);
|
|
return kr;
|
|
}
|
|
|
|
suspend_timer();
|
|
|
|
if (requestinCnt) {
|
|
s->req.data = malloc(requestinCnt);
|
|
memcpy(s->req.data, requestin, requestinCnt);
|
|
s->req.length = requestinCnt;
|
|
} else {
|
|
s->req.data = malloc(requestoutCnt);
|
|
memcpy(s->req.data, requestout, requestoutCnt);
|
|
s->req.length = requestoutCnt;
|
|
}
|
|
|
|
dispatch_async(workq, ^{
|
|
(ctx->callback)(ctx->userctx, &s->req, s->cred,
|
|
mach_complete_async, (heim_sipc_call)s);
|
|
});
|
|
|
|
return KERN_SUCCESS;
|
|
}
|
|
|
|
static int
|
|
mach_init(const char *service, mach_port_t sport, heim_sipc ctx)
|
|
{
|
|
struct mach_service *s;
|
|
char *name;
|
|
|
|
init_globals();
|
|
|
|
s = calloc(1, sizeof(*s));
|
|
if (s == NULL)
|
|
return ENOMEM;
|
|
|
|
asprintf(&name, "heim-ipc-mach-%s", service);
|
|
|
|
s->queue = dispatch_queue_create(name, NULL);
|
|
free(name);
|
|
s->sport = sport;
|
|
|
|
s->source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV,
|
|
s->sport, 0, s->queue);
|
|
if (s->source == NULL) {
|
|
dispatch_release(s->queue);
|
|
free(s);
|
|
return ENOMEM;
|
|
}
|
|
ctx->mech = s;
|
|
|
|
dispatch_set_context(s->queue, ctx);
|
|
dispatch_set_context(s->source, s);
|
|
|
|
dispatch_source_set_event_handler(s->source, ^{
|
|
dispatch_mig_server(s->source, sizeof(union __RequestUnion__mheim_do_mheim_ipc_subsystem), mheim_ipc_server);
|
|
});
|
|
|
|
dispatch_source_set_cancel_handler(s->source, ^{
|
|
heim_sipc sctx = dispatch_get_context(dispatch_get_current_queue());
|
|
struct mach_service *st = sctx->mech;
|
|
mach_port_mod_refs(mach_task_self(), st->sport,
|
|
MACH_PORT_RIGHT_RECEIVE, -1);
|
|
dispatch_release(st->queue);
|
|
dispatch_release(st->source);
|
|
free(st);
|
|
free(sctx);
|
|
});
|
|
|
|
dispatch_resume(s->source);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
mach_release(heim_sipc ctx)
|
|
{
|
|
struct mach_service *s = ctx->mech;
|
|
dispatch_source_cancel(s->source);
|
|
dispatch_release(s->source);
|
|
return 0;
|
|
}
|
|
|
|
static mach_port_t
|
|
mach_checkin_or_register(const char *service)
|
|
{
|
|
mach_port_t mp;
|
|
kern_return_t kr;
|
|
|
|
kr = bootstrap_check_in(bootstrap_port, service, &mp);
|
|
if (kr == KERN_SUCCESS)
|
|
return mp;
|
|
|
|
#if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1050
|
|
/* Pre SnowLeopard version */
|
|
kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &mp);
|
|
if (kr != KERN_SUCCESS)
|
|
return MACH_PORT_NULL;
|
|
|
|
kr = mach_port_insert_right(mach_task_self(), mp, mp,
|
|
MACH_MSG_TYPE_MAKE_SEND);
|
|
if (kr != KERN_SUCCESS) {
|
|
mach_port_destroy(mach_task_self(), mp);
|
|
return MACH_PORT_NULL;
|
|
}
|
|
|
|
kr = bootstrap_register(bootstrap_port, rk_UNCONST(service), mp);
|
|
if (kr != KERN_SUCCESS) {
|
|
mach_port_destroy(mach_task_self(), mp);
|
|
return MACH_PORT_NULL;
|
|
}
|
|
|
|
return mp;
|
|
#else
|
|
return MACH_PORT_NULL;
|
|
#endif
|
|
}
|
|
|
|
|
|
#endif /* __APPLE__ && HAVE_GCD */
|
|
|
|
|
|
int
|
|
heim_sipc_launchd_mach_init(const char *service,
|
|
heim_ipc_callback callback,
|
|
void *user, heim_sipc *ctx)
|
|
{
|
|
#if defined(__APPLE__) && defined(HAVE_GCD)
|
|
mach_port_t sport = MACH_PORT_NULL;
|
|
heim_sipc c = NULL;
|
|
int ret;
|
|
|
|
*ctx = NULL;
|
|
|
|
sport = mach_checkin_or_register(service);
|
|
if (sport == MACH_PORT_NULL) {
|
|
ret = ENOENT;
|
|
goto error;
|
|
}
|
|
|
|
c = calloc(1, sizeof(*c));
|
|
if (c == NULL) {
|
|
ret = ENOMEM;
|
|
goto error;
|
|
}
|
|
c->release = mach_release;
|
|
c->userctx = user;
|
|
c->callback = callback;
|
|
|
|
ret = mach_init(service, sport, c);
|
|
if (ret)
|
|
goto error;
|
|
|
|
*ctx = c;
|
|
return 0;
|
|
error:
|
|
if (c)
|
|
free(c);
|
|
if (sport != MACH_PORT_NULL)
|
|
mach_port_mod_refs(mach_task_self(), sport,
|
|
MACH_PORT_RIGHT_RECEIVE, -1);
|
|
return ret;
|
|
#else /* !(__APPLE__ && HAVE_GCD) */
|
|
*ctx = NULL;
|
|
return EINVAL;
|
|
#endif /* __APPLE__ && HAVE_GCD */
|
|
}
|
|
|
|
struct client {
|
|
int fd;
|
|
heim_ipc_callback callback;
|
|
void *userctx;
|
|
int flags;
|
|
#define LISTEN_SOCKET 1
|
|
#define WAITING_READ 2
|
|
#define WAITING_WRITE 4
|
|
#define WAITING_CLOSE 8
|
|
|
|
#define HTTP_REPLY 16
|
|
#define DOOR_FD 32
|
|
|
|
#define INHERIT_MASK 0xffff0000
|
|
#define INCLUDE_ERROR_CODE (1 << 16)
|
|
#define ALLOW_HTTP (1<<17)
|
|
#define UNIX_SOCKET (1<<18)
|
|
unsigned calls;
|
|
size_t ptr, len;
|
|
uint8_t *inmsg;
|
|
size_t olen;
|
|
uint8_t *outmsg;
|
|
#ifdef HAVE_GCD
|
|
dispatch_source_t in;
|
|
dispatch_source_t out;
|
|
#endif
|
|
struct {
|
|
uid_t uid;
|
|
gid_t gid;
|
|
pid_t pid;
|
|
} unixrights;
|
|
};
|
|
|
|
#ifndef HAVE_GCD
|
|
static unsigned num_clients = 0;
|
|
static struct client **clients = NULL;
|
|
#endif
|
|
|
|
static void handle_read(struct client *);
|
|
static void handle_write(struct client *);
|
|
static int maybe_close(struct client *);
|
|
|
|
/*
|
|
* Update peer credentials from socket.
|
|
*
|
|
* SCM_CREDS can only be updated the first time there is read data to
|
|
* read from the filedescriptor, so if we read do it before this
|
|
* point, the cred data might not be is not there yet.
|
|
*/
|
|
|
|
static int
|
|
update_client_creds(struct client *c)
|
|
{
|
|
#ifdef HAVE_GETPEERUCRED
|
|
/* Solaris 10 */
|
|
{
|
|
ucred_t *peercred = NULL;
|
|
|
|
if (getpeerucred(c->fd, &peercred) == 0) {
|
|
c->unixrights.uid = ucred_geteuid(peercred);
|
|
c->unixrights.gid = ucred_getegid(peercred);
|
|
c->unixrights.pid = 0;
|
|
ucred_free(peercred);
|
|
return 1;
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef HAVE_GETPEEREID
|
|
/* FreeBSD, OpenBSD */
|
|
{
|
|
uid_t uid;
|
|
gid_t gid;
|
|
|
|
if (getpeereid(c->fd, &uid, &gid) == 0) {
|
|
c->unixrights.uid = uid;
|
|
c->unixrights.gid = gid;
|
|
c->unixrights.pid = 0;
|
|
return 1;
|
|
}
|
|
}
|
|
#endif
|
|
#if defined(SO_PEERCRED) && defined(__linux__)
|
|
/* Linux */
|
|
{
|
|
struct ucred pc;
|
|
socklen_t pclen = sizeof(pc);
|
|
|
|
if (getsockopt(c->fd, SOL_SOCKET, SO_PEERCRED, (void *)&pc, &pclen) == 0) {
|
|
c->unixrights.uid = pc.uid;
|
|
c->unixrights.gid = pc.gid;
|
|
c->unixrights.pid = pc.pid;
|
|
return 1;
|
|
}
|
|
}
|
|
#endif
|
|
#if defined(LOCAL_PEERCRED) && defined(XUCRED_VERSION)
|
|
{
|
|
struct xucred peercred;
|
|
socklen_t peercredlen = sizeof(peercred);
|
|
|
|
if (getsockopt(c->fd, LOCAL_PEERCRED, 1,
|
|
(void *)&peercred, &peercredlen) == 0
|
|
&& peercred.cr_version == XUCRED_VERSION)
|
|
{
|
|
c->unixrights.uid = peercred.cr_uid;
|
|
c->unixrights.gid = peercred.cr_gid;
|
|
c->unixrights.pid = 0;
|
|
return 1;
|
|
}
|
|
}
|
|
#endif
|
|
#if defined(SOCKCREDSIZE) && defined(SCM_CREDS)
|
|
/* NetBSD */
|
|
if (c->unixrights.uid == (uid_t)-1) {
|
|
struct msghdr msg;
|
|
socklen_t crmsgsize;
|
|
void *crmsg;
|
|
struct cmsghdr *cmp;
|
|
struct sockcred *sc;
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
crmsgsize = CMSG_SPACE(SOCKCREDSIZE(NGROUPS));
|
|
if (crmsgsize == 0)
|
|
return 1 ;
|
|
|
|
crmsg = malloc(crmsgsize);
|
|
if (crmsg == NULL)
|
|
goto failed_scm_creds;
|
|
|
|
memset(crmsg, 0, crmsgsize);
|
|
|
|
msg.msg_control = crmsg;
|
|
msg.msg_controllen = crmsgsize;
|
|
|
|
if (recvmsg(c->fd, &msg, 0) < 0) {
|
|
free(crmsg);
|
|
goto failed_scm_creds;
|
|
}
|
|
|
|
if (msg.msg_controllen == 0 || (msg.msg_flags & MSG_CTRUNC) != 0) {
|
|
free(crmsg);
|
|
goto failed_scm_creds;
|
|
}
|
|
|
|
cmp = CMSG_FIRSTHDR(&msg);
|
|
if (cmp->cmsg_level != SOL_SOCKET || cmp->cmsg_type != SCM_CREDS) {
|
|
free(crmsg);
|
|
goto failed_scm_creds;
|
|
}
|
|
|
|
sc = (struct sockcred *)(void *)CMSG_DATA(cmp);
|
|
|
|
c->unixrights.uid = sc->sc_euid;
|
|
c->unixrights.gid = sc->sc_egid;
|
|
c->unixrights.pid = 0;
|
|
|
|
free(crmsg);
|
|
return 1;
|
|
} else {
|
|
/* we already got the cred, just return it */
|
|
return 1;
|
|
}
|
|
failed_scm_creds:
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static struct client *
|
|
add_new_socket(int fd,
|
|
int flags,
|
|
heim_ipc_callback callback,
|
|
void *userctx)
|
|
{
|
|
struct client *c;
|
|
|
|
c = calloc(1, sizeof(*c));
|
|
if (c == NULL)
|
|
return NULL;
|
|
|
|
if (flags & LISTEN_SOCKET) {
|
|
c->fd = fd;
|
|
} else if (flags & DOOR_FD) {
|
|
c->fd = -1; /* cannot poll a door descriptor */
|
|
} else {
|
|
c->fd = accept(fd, NULL, NULL);
|
|
if(c->fd < 0) {
|
|
free(c);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
c->flags = flags;
|
|
c->callback = callback;
|
|
c->userctx = userctx;
|
|
|
|
socket_set_nonblocking(fd, 1);
|
|
|
|
#ifdef HAVE_GCD
|
|
init_globals();
|
|
|
|
c->in = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
|
|
c->fd, 0, eventq);
|
|
c->out = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE,
|
|
c->fd, 0, eventq);
|
|
|
|
dispatch_source_set_event_handler(c->in, ^{
|
|
int rw = (c->flags & WAITING_WRITE);
|
|
handle_read(c);
|
|
if (rw == 0 && (c->flags & WAITING_WRITE))
|
|
dispatch_resume(c->out);
|
|
if ((c->flags & WAITING_READ) == 0)
|
|
dispatch_suspend(c->in);
|
|
maybe_close(c);
|
|
});
|
|
dispatch_source_set_event_handler(c->out, ^{
|
|
handle_write(c);
|
|
if ((c->flags & WAITING_WRITE) == 0) {
|
|
dispatch_suspend(c->out);
|
|
}
|
|
maybe_close(c);
|
|
});
|
|
|
|
dispatch_resume(c->in);
|
|
#else
|
|
clients = erealloc(clients, sizeof(clients[0]) * (num_clients + 1));
|
|
clients[num_clients] = c;
|
|
num_clients++;
|
|
#endif
|
|
|
|
return c;
|
|
}
|
|
|
|
static int
|
|
maybe_close(struct client *c)
|
|
{
|
|
if (c->calls != 0)
|
|
return 0;
|
|
if (c->flags & (WAITING_READ|WAITING_WRITE))
|
|
return 0;
|
|
|
|
#ifdef HAVE_GCD
|
|
dispatch_source_cancel(c->in);
|
|
if ((c->flags & WAITING_READ) == 0)
|
|
dispatch_resume(c->in);
|
|
dispatch_release(c->in);
|
|
|
|
dispatch_source_cancel(c->out);
|
|
if ((c->flags & WAITING_WRITE) == 0)
|
|
dispatch_resume(c->out);
|
|
dispatch_release(c->out);
|
|
#endif
|
|
close(c->fd); /* ref count fd close */
|
|
free(c->inmsg);
|
|
free(c);
|
|
return 1;
|
|
}
|
|
|
|
|
|
struct socket_call {
|
|
heim_idata in;
|
|
struct client *c;
|
|
heim_icred cred;
|
|
};
|
|
|
|
static void
|
|
output_data(struct client *c, const void *data, size_t len)
|
|
{
|
|
if (c->olen + len < c->olen)
|
|
abort();
|
|
c->outmsg = erealloc(c->outmsg, c->olen + len);
|
|
memcpy(&c->outmsg[c->olen], data, len);
|
|
c->olen += len;
|
|
c->flags |= WAITING_WRITE;
|
|
}
|
|
|
|
static void
|
|
socket_complete(heim_sipc_call ctx, int returnvalue, heim_idata *reply)
|
|
{
|
|
struct socket_call *sc = (struct socket_call *)ctx;
|
|
struct client *c = sc->c;
|
|
|
|
/* double complete ? */
|
|
if (c == NULL)
|
|
abort();
|
|
|
|
if ((c->flags & WAITING_CLOSE) == 0) {
|
|
uint32_t u32;
|
|
|
|
/* length */
|
|
u32 = htonl(reply->length);
|
|
output_data(c, &u32, sizeof(u32));
|
|
|
|
/* return value */
|
|
if (c->flags & INCLUDE_ERROR_CODE) {
|
|
u32 = htonl(returnvalue);
|
|
output_data(c, &u32, sizeof(u32));
|
|
}
|
|
|
|
/* data */
|
|
output_data(c, reply->data, reply->length);
|
|
|
|
/* if HTTP, close connection */
|
|
if (c->flags & HTTP_REPLY) {
|
|
c->flags |= WAITING_CLOSE;
|
|
c->flags &= ~WAITING_READ;
|
|
}
|
|
}
|
|
|
|
c->calls--;
|
|
if (sc->cred)
|
|
heim_ipc_free_cred(sc->cred);
|
|
free(sc->in.data);
|
|
sc->c = NULL; /* so we can catch double complete */
|
|
free(sc);
|
|
|
|
maybe_close(c);
|
|
}
|
|
|
|
/* remove HTTP %-quoting from buf */
|
|
static int
|
|
de_http(char *buf)
|
|
{
|
|
unsigned char *p, *q;
|
|
for(p = q = (unsigned char *)buf; *p; p++, q++) {
|
|
if(*p == '%' && isxdigit(p[1]) && isxdigit(p[2])) {
|
|
unsigned int x;
|
|
if(sscanf((char *)p + 1, "%2x", &x) != 1)
|
|
return -1;
|
|
*q = x;
|
|
p += 2;
|
|
} else
|
|
*q = *p;
|
|
}
|
|
*q = '\0';
|
|
return 0;
|
|
}
|
|
|
|
static struct socket_call *
|
|
handle_http_tcp(struct client *c)
|
|
{
|
|
struct socket_call *cs;
|
|
char *s, *p, *t;
|
|
void *data;
|
|
char *proto;
|
|
int len;
|
|
|
|
s = (char *)c->inmsg;
|
|
|
|
p = strstr(s, "\r\n");
|
|
if (p == NULL)
|
|
return NULL;
|
|
|
|
*p = 0;
|
|
|
|
p = NULL;
|
|
t = strtok_r(s, " \t", &p);
|
|
if (t == NULL)
|
|
return NULL;
|
|
|
|
t = strtok_r(NULL, " \t", &p);
|
|
if (t == NULL)
|
|
return NULL;
|
|
|
|
data = malloc(strlen(t));
|
|
if (data == NULL)
|
|
return NULL;
|
|
|
|
if(*t == '/')
|
|
t++;
|
|
if(de_http(t) != 0) {
|
|
free(data);
|
|
return NULL;
|
|
}
|
|
proto = strtok_r(NULL, " \t", &p);
|
|
if (proto == NULL) {
|
|
free(data);
|
|
return NULL;
|
|
}
|
|
len = rk_base64_decode(t, data);
|
|
if(len <= 0){
|
|
const char *msg =
|
|
" 404 Not found\r\n"
|
|
"Server: Heimdal/" VERSION "\r\n"
|
|
"Cache-Control: no-cache\r\n"
|
|
"Pragma: no-cache\r\n"
|
|
"Content-type: text/html\r\n"
|
|
"Content-transfer-encoding: 8bit\r\n\r\n"
|
|
"<TITLE>404 Not found</TITLE>\r\n"
|
|
"<H1>404 Not found</H1>\r\n"
|
|
"That page doesn't exist, maybe you are looking for "
|
|
"<A HREF=\"http://www.h5l.org/\">Heimdal</A>?\r\n";
|
|
free(data);
|
|
output_data(c, proto, strlen(proto));
|
|
output_data(c, msg, strlen(msg));
|
|
return NULL;
|
|
}
|
|
|
|
cs = emalloc(sizeof(*cs));
|
|
cs->c = c;
|
|
cs->in.data = data;
|
|
cs->in.length = len;
|
|
c->ptr = 0;
|
|
|
|
{
|
|
const char *msg =
|
|
" 200 OK\r\n"
|
|
"Server: Heimdal/" VERSION "\r\n"
|
|
"Cache-Control: no-cache\r\n"
|
|
"Pragma: no-cache\r\n"
|
|
"Content-type: application/octet-stream\r\n"
|
|
"Content-transfer-encoding: binary\r\n\r\n";
|
|
output_data(c, proto, strlen(proto));
|
|
output_data(c, msg, strlen(msg));
|
|
}
|
|
|
|
return cs;
|
|
}
|
|
|
|
|
|
static void
|
|
handle_read(struct client *c)
|
|
{
|
|
ssize_t len;
|
|
uint32_t dlen;
|
|
|
|
assert((c->flags & DOOR_FD) == 0);
|
|
|
|
if (c->flags & LISTEN_SOCKET) {
|
|
add_new_socket(c->fd,
|
|
WAITING_READ | (c->flags & INHERIT_MASK),
|
|
c->callback,
|
|
c->userctx);
|
|
return;
|
|
}
|
|
|
|
if (c->ptr - c->len < 1024) {
|
|
c->inmsg = erealloc(c->inmsg,
|
|
c->len + 1024);
|
|
c->len += 1024;
|
|
}
|
|
|
|
len = read(c->fd, c->inmsg + c->ptr, c->len - c->ptr);
|
|
if (len <= 0) {
|
|
c->flags |= WAITING_CLOSE;
|
|
c->flags &= ~WAITING_READ;
|
|
return;
|
|
}
|
|
c->ptr += len;
|
|
if (c->ptr > c->len)
|
|
abort();
|
|
|
|
while (c->ptr >= sizeof(dlen)) {
|
|
struct socket_call *cs;
|
|
|
|
if((c->flags & ALLOW_HTTP) && c->ptr >= 4 &&
|
|
strncmp((char *)c->inmsg, "GET ", 4) == 0 &&
|
|
strncmp((char *)c->inmsg + c->ptr - 4, "\r\n\r\n", 4) == 0) {
|
|
|
|
/* remove the trailing \r\n\r\n so the string is NUL terminated */
|
|
c->inmsg[c->ptr - 4] = '\0';
|
|
|
|
c->flags |= HTTP_REPLY;
|
|
|
|
cs = handle_http_tcp(c);
|
|
if (cs == NULL) {
|
|
c->flags |= WAITING_CLOSE;
|
|
c->flags &= ~WAITING_READ;
|
|
break;
|
|
}
|
|
} else {
|
|
memcpy(&dlen, c->inmsg, sizeof(dlen));
|
|
dlen = ntohl(dlen);
|
|
|
|
if (dlen > MAX_PACKET_SIZE) {
|
|
c->flags |= WAITING_CLOSE;
|
|
c->flags &= ~WAITING_READ;
|
|
return;
|
|
}
|
|
if (dlen > c->ptr - sizeof(dlen)) {
|
|
break;
|
|
}
|
|
|
|
cs = emalloc(sizeof(*cs));
|
|
cs->c = c;
|
|
cs->in.data = emalloc(dlen);
|
|
memcpy(cs->in.data, c->inmsg + sizeof(dlen), dlen);
|
|
cs->in.length = dlen;
|
|
cs->cred = NULL;
|
|
|
|
c->ptr -= sizeof(dlen) + dlen;
|
|
memmove(c->inmsg,
|
|
c->inmsg + sizeof(dlen) + dlen,
|
|
c->ptr);
|
|
}
|
|
|
|
c->calls++;
|
|
|
|
if ((c->flags & UNIX_SOCKET) != 0) {
|
|
if (update_client_creds(c))
|
|
_heim_ipc_create_cred(c->unixrights.uid, c->unixrights.gid,
|
|
c->unixrights.pid, -1, &cs->cred);
|
|
}
|
|
|
|
c->callback(c->userctx, &cs->in,
|
|
cs->cred, socket_complete,
|
|
(heim_sipc_call)cs);
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_write(struct client *c)
|
|
{
|
|
ssize_t len;
|
|
|
|
len = write(c->fd, c->outmsg, c->olen);
|
|
if (len <= 0) {
|
|
c->flags |= WAITING_CLOSE;
|
|
c->flags &= ~(WAITING_WRITE);
|
|
} else if (c->olen != (size_t)len) {
|
|
memmove(&c->outmsg[0], &c->outmsg[len], c->olen - len);
|
|
c->olen -= len;
|
|
} else {
|
|
c->olen = 0;
|
|
free(c->outmsg);
|
|
c->outmsg = NULL;
|
|
c->flags &= ~(WAITING_WRITE);
|
|
}
|
|
}
|
|
|
|
|
|
#ifndef HAVE_GCD
|
|
|
|
static void
|
|
process_loop(void)
|
|
{
|
|
struct pollfd *fds;
|
|
unsigned n;
|
|
unsigned num_fds;
|
|
|
|
while (num_clients > 0) {
|
|
|
|
fds = malloc(num_clients * sizeof(fds[0]));
|
|
if(fds == NULL)
|
|
abort();
|
|
|
|
num_fds = num_clients;
|
|
|
|
for (n = 0 ; n < num_fds; n++) {
|
|
fds[n].fd = clients[n]->fd;
|
|
fds[n].events = 0;
|
|
if (clients[n]->flags & WAITING_READ)
|
|
fds[n].events |= POLLIN;
|
|
if (clients[n]->flags & WAITING_WRITE)
|
|
fds[n].events |= POLLOUT;
|
|
|
|
fds[n].revents = 0;
|
|
}
|
|
|
|
while (poll(fds, num_fds, -1) == -1) {
|
|
if (errno == EINTR || errno == EAGAIN)
|
|
continue;
|
|
err(1, "poll(2) failed");
|
|
}
|
|
|
|
for (n = 0 ; n < num_fds; n++) {
|
|
if (clients[n] == NULL)
|
|
continue;
|
|
if (fds[n].revents & POLLIN)
|
|
handle_read(clients[n]);
|
|
if (fds[n].revents & POLLOUT)
|
|
handle_write(clients[n]);
|
|
if (fds[n].revents & POLLERR)
|
|
clients[n]->flags |= WAITING_CLOSE;
|
|
}
|
|
|
|
n = 0;
|
|
while (n < num_clients) {
|
|
struct client *c = clients[n];
|
|
if (maybe_close(c)) {
|
|
if (n < num_clients - 1)
|
|
clients[n] = clients[num_clients - 1];
|
|
num_clients--;
|
|
} else
|
|
n++;
|
|
}
|
|
|
|
free(fds);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
static int
|
|
socket_release(heim_sipc ctx)
|
|
{
|
|
struct client *c = ctx->mech;
|
|
c->flags |= WAITING_CLOSE;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
heim_sipc_stream_listener(int fd, int type,
|
|
heim_ipc_callback callback,
|
|
void *user, heim_sipc *ctx)
|
|
{
|
|
heim_sipc ct;
|
|
struct client *c;
|
|
|
|
if ((type & HEIM_SIPC_TYPE_IPC) && (type & (HEIM_SIPC_TYPE_UINT32|HEIM_SIPC_TYPE_HTTP)))
|
|
return EINVAL;
|
|
|
|
ct = calloc(1, sizeof(*ct));
|
|
if (ct == NULL)
|
|
return ENOMEM;
|
|
|
|
switch (type) {
|
|
case HEIM_SIPC_TYPE_IPC:
|
|
c = add_new_socket(fd, LISTEN_SOCKET|WAITING_READ|INCLUDE_ERROR_CODE, callback, user);
|
|
break;
|
|
case HEIM_SIPC_TYPE_UINT32:
|
|
c = add_new_socket(fd, LISTEN_SOCKET|WAITING_READ, callback, user);
|
|
break;
|
|
case HEIM_SIPC_TYPE_HTTP:
|
|
case HEIM_SIPC_TYPE_UINT32|HEIM_SIPC_TYPE_HTTP:
|
|
c = add_new_socket(fd, LISTEN_SOCKET|WAITING_READ|ALLOW_HTTP, callback, user);
|
|
break;
|
|
default:
|
|
free(ct);
|
|
return EINVAL;
|
|
}
|
|
|
|
ct->mech = c;
|
|
ct->release = socket_release;
|
|
|
|
c->unixrights.uid = (uid_t) -1;
|
|
c->unixrights.gid = (gid_t) -1;
|
|
c->unixrights.pid = (pid_t) 0;
|
|
|
|
*ctx = ct;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
heim_sipc_service_unix(const char *service,
|
|
heim_ipc_callback callback,
|
|
void *user, heim_sipc *ctx)
|
|
{
|
|
struct sockaddr_un un;
|
|
const char *d = secure_getenv("HEIM_IPC_DIR");
|
|
int fd, ret;
|
|
|
|
if (strncasecmp(service, "UNIX:", sizeof("UNIX:") - 1) == 0)
|
|
service += sizeof("UNIX:") - 1;
|
|
|
|
un.sun_family = AF_UNIX;
|
|
|
|
if (snprintf(un.sun_path, sizeof(un.sun_path),
|
|
"%s/.heim_%s-socket", d ? d : _PATH_VARRUN,
|
|
service) > sizeof(un.sun_path) + sizeof("-s") - 1)
|
|
return ENAMETOOLONG;
|
|
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (fd < 0)
|
|
return errno;
|
|
|
|
socket_set_reuseaddr(fd, 1);
|
|
#ifdef LOCAL_CREDS
|
|
{
|
|
int one = 1;
|
|
(void) setsockopt(fd, 0, LOCAL_CREDS, (void *)&one, sizeof(one));
|
|
}
|
|
#endif
|
|
|
|
unlink(un.sun_path);
|
|
|
|
if (bind(fd, (struct sockaddr *)&un, sizeof(un)) < 0) {
|
|
close(fd);
|
|
return errno;
|
|
}
|
|
|
|
if (listen(fd, SOMAXCONN) < 0) {
|
|
close(fd);
|
|
return errno;
|
|
}
|
|
|
|
(void) chmod(un.sun_path, 0666);
|
|
|
|
ret = heim_sipc_stream_listener(fd, HEIM_SIPC_TYPE_IPC,
|
|
callback, user, ctx);
|
|
if (ret == 0) {
|
|
struct client *c = (*ctx)->mech;
|
|
c->flags |= UNIX_SOCKET;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef HAVE_DOOR_CREATE
|
|
#include <door.h>
|
|
|
|
#ifdef HAVE_SYS_MMAN_H
|
|
#include <sys/mman.h>
|
|
#endif
|
|
|
|
#include "heim_threads.h"
|
|
|
|
static HEIMDAL_thread_key door_key;
|
|
|
|
struct door_call {
|
|
heim_idata in;
|
|
door_desc_t *dp;
|
|
heim_icred cred;
|
|
};
|
|
|
|
struct door_reply {
|
|
int returnvalue;
|
|
size_t length;
|
|
unsigned char data[1];
|
|
};
|
|
|
|
static int
|
|
door_release(heim_sipc ctx)
|
|
{
|
|
struct client *c = ctx->mech;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
door_reply_destroy(void *data)
|
|
{
|
|
free(data);
|
|
}
|
|
|
|
static void
|
|
door_key_create(void *key)
|
|
{
|
|
int ret;
|
|
|
|
HEIMDAL_key_create((HEIMDAL_thread_key *)key, door_reply_destroy, ret);
|
|
}
|
|
|
|
static void
|
|
door_complete(heim_sipc_call ctx, int returnvalue, heim_idata *reply)
|
|
{
|
|
static heim_base_once_t once = HEIM_BASE_ONCE_INIT;
|
|
struct door_call *cs = (struct door_call *)ctx;
|
|
size_t rlen;
|
|
struct door_reply *r = NULL;
|
|
union {
|
|
struct door_reply reply;
|
|
char buffer[offsetof(struct door_reply, data) + BUFSIZ];
|
|
} replyBuf;
|
|
|
|
heim_base_once_f(&once, &door_key, door_key_create);
|
|
|
|
/* door_return() doesn't return; don't leak cred */
|
|
heim_ipc_free_cred(cs->cred);
|
|
|
|
error_reply:
|
|
rlen = offsetof(struct door_reply, data);
|
|
if (returnvalue == 0)
|
|
rlen += reply->length;
|
|
|
|
/* long replies (> BUFSIZ) are allocated from the heap */
|
|
if (rlen > BUFSIZ) {
|
|
int ret;
|
|
|
|
/* door_return() doesn't return, so stash reply buffer in TLS */
|
|
r = realloc(HEIMDAL_getspecific(door_key), rlen);
|
|
if (r == NULL) {
|
|
returnvalue = EAGAIN; /* don't leak ENOMEM to caller */
|
|
goto error_reply;
|
|
}
|
|
|
|
HEIMDAL_setspecific(door_key, r, ret);
|
|
} else {
|
|
r = &replyBuf.reply;
|
|
}
|
|
|
|
r->returnvalue = returnvalue;
|
|
if (r->returnvalue == 0) {
|
|
r->length = reply->length;
|
|
memcpy(r->data, reply->data, reply->length);
|
|
} else {
|
|
r->length = 0;
|
|
}
|
|
|
|
door_return((char *)r, rlen, NULL, 0);
|
|
}
|
|
|
|
static void
|
|
door_callback(void *cookie,
|
|
char *argp,
|
|
size_t arg_size,
|
|
door_desc_t *dp,
|
|
uint_t n_desc)
|
|
{
|
|
heim_sipc c = (heim_sipc)cookie;
|
|
struct door_call cs = { 0 };
|
|
ucred_t *peercred = NULL;
|
|
|
|
if (door_ucred(&peercred) < 0)
|
|
return;
|
|
|
|
_heim_ipc_create_cred(ucred_geteuid(peercred),
|
|
ucred_getegid(peercred),
|
|
ucred_getpid(peercred),
|
|
-1,
|
|
&cs.cred);
|
|
ucred_free(peercred);
|
|
|
|
cs.dp = dp;
|
|
cs.in.data = argp;
|
|
cs.in.length = arg_size;
|
|
|
|
c->callback(c->userctx, &cs.in, cs.cred, door_complete, (heim_sipc_call)&cs);
|
|
}
|
|
|
|
int
|
|
heim_sipc_service_door(const char *service,
|
|
heim_ipc_callback callback,
|
|
void *user, heim_sipc *ctx)
|
|
{
|
|
char path[PATH_MAX];
|
|
int fd = -1, dfd = -1, ret;
|
|
heim_sipc ct = NULL;
|
|
struct client *c = NULL;
|
|
|
|
ct = calloc(1, sizeof(*ct));
|
|
if (ct == NULL) {
|
|
ret = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
ct->release = door_release;
|
|
ct->userctx = user;
|
|
ct->callback = callback;
|
|
|
|
if (snprintf(path, sizeof(path), "/var/run/.heim_%s-door",
|
|
service) >= sizeof(path) + sizeof("-d") - 1) {
|
|
ret = ENAMETOOLONG;
|
|
goto cleanup;
|
|
}
|
|
fd = door_create(door_callback, ct, DOOR_REFUSE_DESC | DOOR_NO_CANCEL);
|
|
if (fd < 0) {
|
|
ret = errno;
|
|
goto cleanup;
|
|
}
|
|
|
|
fdetach(path);
|
|
dfd = open(path, O_RDWR | O_CREAT, 0666);
|
|
if (dfd < 0) {
|
|
ret = errno;
|
|
goto cleanup;
|
|
}
|
|
(void) fchmod(dfd, 0666); /* XXX */
|
|
|
|
if (fattach(fd, path) < 0) {
|
|
ret = errno;
|
|
goto cleanup;
|
|
}
|
|
|
|
c = add_new_socket(fd, DOOR_FD, callback, user);
|
|
ct->mech = c;
|
|
|
|
*ctx = ct;
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
if (ret != 0) {
|
|
free(ct);
|
|
free(c);
|
|
if (fd != -1)
|
|
close(fd);
|
|
}
|
|
if (dfd != -1)
|
|
close(dfd);
|
|
|
|
return ret;
|
|
}
|
|
#endif /* HAVE_DOOR_CREATE */
|
|
|
|
/**
|
|
* Set the idle timeout value
|
|
|
|
* The timeout event handler is triggered recurrently every idle
|
|
* period `t'. The default action is rather draconian and just calls
|
|
* exit(0), so you might want to change this to something more
|
|
* graceful using heim_sipc_set_timeout_handler().
|
|
*/
|
|
|
|
void
|
|
heim_sipc_timeout(time_t t)
|
|
{
|
|
#ifdef HAVE_GCD
|
|
static dispatch_once_t timeoutonce;
|
|
init_globals();
|
|
dispatch_sync(timerq, ^{
|
|
timeoutvalue = t;
|
|
set_timer();
|
|
});
|
|
dispatch_once(&timeoutonce, ^{ dispatch_resume(timer); });
|
|
#else
|
|
abort();
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Set the timeout event handler
|
|
*
|
|
* Replaces the default idle timeout action.
|
|
*/
|
|
|
|
void
|
|
heim_sipc_set_timeout_handler(void (*func)(void))
|
|
{
|
|
#ifdef HAVE_GCD
|
|
init_globals();
|
|
dispatch_sync(timerq, ^{ timer_ev = func; });
|
|
#else
|
|
abort();
|
|
#endif
|
|
}
|
|
|
|
|
|
void
|
|
heim_sipc_free_context(heim_sipc ctx)
|
|
{
|
|
(ctx->release)(ctx);
|
|
}
|
|
|
|
void
|
|
heim_ipc_main(void)
|
|
{
|
|
#ifdef HAVE_GCD
|
|
dispatch_main();
|
|
#else
|
|
process_loop();
|
|
#endif
|
|
}
|