1/*	$NetBSD$	*/
2
3/*
4 * Copyright (c) 2009 Kungliga Tekniska H�gskolan
5 * (Royal Institute of Technology, Stockholm, Sweden).
6 * All rights reserved.
7 *
8 * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 *
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 *
17 * 2. Redistributions in binary form must reproduce the above copyright
18 *    notice, this list of conditions and the following disclaimer in the
19 *    documentation and/or other materials provided with the distribution.
20 *
21 * 3. Neither the name of the Institute nor the names of its contributors
22 *    may be used to endorse or promote products derived from this software
23 *    without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
36 */
37
38#include "hi_locl.h"
39
40#if defined(__APPLE__) && defined(HAVE_GCD)
41
42#include "heim_ipc.h"
43#include "heim_ipc_asyncServer.h"
44
45#include <dispatch/dispatch.h>
46#include <mach/mach.h>
47
48static dispatch_once_t jobqinited = 0;
49static dispatch_queue_t jobq = NULL;
50static dispatch_queue_t syncq;
51
52struct mach_ctx {
53    mach_port_t server;
54    char *name;
55};
56
57static int
58mach_release(void *ctx);
59
60static int
61mach_init(const char *service, void **ctx)
62{
63    struct mach_ctx *ipc;
64    mach_port_t sport;
65    int ret;
66
67    dispatch_once(&jobqinited, ^{
68	    jobq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
69	    syncq = dispatch_queue_create("heim-ipc-syncq", NULL);
70	});
71
72    ret = bootstrap_look_up(bootstrap_port, service, &sport);
73    if (ret)
74	return ret;
75
76    ipc = malloc(sizeof(*ipc));
77    if (ipc == NULL) {
78	mach_port_destroy(mach_task_self(), sport);
79	return ENOMEM;
80    }
81
82    ipc->server = sport;
83    ipc->name = strdup(service);
84    if (ipc->name == NULL) {
85	mach_release(ipc);
86	return ENOMEM;
87    }
88
89    *ctx = ipc;
90
91    return 0;
92}
93
94static int
95mach_ipc(void *ctx,
96	 const heim_idata *request, heim_idata *response,
97	 heim_icred *cred)
98{
99    struct mach_ctx *ipc = ctx;
100    heim_ipc_message_inband_t requestin;
101    mach_msg_type_number_t requestin_length = 0;
102    heim_ipc_message_outband_t requestout = NULL;
103    mach_msg_type_number_t requestout_length = 0;
104    heim_ipc_message_inband_t replyin;
105    mach_msg_type_number_t replyin_length;
106    heim_ipc_message_outband_t replyout;
107    mach_msg_type_number_t replyout_length;
108    int ret, errorcode, retries = 0;
109
110    memcpy(requestin, request->data, request->length);
111    requestin_length = request->length;
112
113    while (retries < 2) {
114	__block mach_port_t sport;
115
116	dispatch_sync(syncq, ^{ sport = ipc->server; });
117
118	ret = mheim_ipc_call(sport,
119			     requestin, requestin_length,
120			     requestout, requestout_length,
121			     &errorcode,
122			     replyin, &replyin_length,
123			     &replyout, &replyout_length);
124	if (ret == MACH_SEND_INVALID_DEST) {
125	    mach_port_t nport;
126	    /* race other threads to get a new port */
127	    ret = bootstrap_look_up(bootstrap_port, ipc->name, &nport);
128	    if (ret)
129		return ret;
130	    dispatch_sync(syncq, ^{
131		    /* check if we lost the race to lookup the port */
132		    if (sport != ipc->server) {
133			mach_port_deallocate(mach_task_self(), nport);
134		    } else {
135			mach_port_deallocate(mach_task_self(), ipc->server);
136			ipc->server = nport;
137		    }
138		});
139	    retries++;
140	} else if (ret) {
141	    return ret;
142	} else
143	    break;
144    }
145    if (retries >= 2)
146	return EINVAL;
147
148    if (errorcode) {
149	if (replyout_length)
150	    vm_deallocate (mach_task_self (), (vm_address_t) replyout,
151			   replyout_length);
152	return errorcode;
153    }
154
155    if (replyout_length) {
156	response->data = malloc(replyout_length);
157	if (response->data == NULL) {
158	    vm_deallocate (mach_task_self (), (vm_address_t) replyout,
159			   replyout_length);
160	    return ENOMEM;
161	}
162	memcpy(response->data, replyout, replyout_length);
163	response->length = replyout_length;
164	vm_deallocate (mach_task_self (), (vm_address_t) replyout,
165		       replyout_length);
166    } else {
167	response->data = malloc(replyin_length);
168	if (response->data == NULL)
169	    return ENOMEM;
170	memcpy(response->data, replyin, replyin_length);
171	response->length = replyin_length;
172    }
173
174    return 0;
175}
176
177struct async_client {
178    mach_port_t mp;
179    dispatch_source_t source;
180    dispatch_queue_t queue;
181    void (*func)(void *, int, heim_idata *, heim_icred);
182    void *userctx;
183};
184
185kern_return_t
186mheim_ado_acall_reply(mach_port_t server_port,
187		      audit_token_t client_creds,
188		      int returnvalue,
189		      heim_ipc_message_inband_t replyin,
190		      mach_msg_type_number_t replyinCnt,
191		      heim_ipc_message_outband_t replyout,
192		      mach_msg_type_number_t replyoutCnt)
193{
194    struct async_client *c = dispatch_get_context(dispatch_get_current_queue());
195    heim_idata response;
196
197    if (returnvalue) {
198	response.data = NULL;
199	response.length = 0;
200    } else if (replyoutCnt) {
201	response.data = replyout;
202	response.length = replyoutCnt;
203    } else {
204	response.data = replyin;
205	response.length = replyinCnt;
206    }
207
208    (*c->func)(c->userctx, returnvalue, &response, NULL);
209
210    if (replyoutCnt)
211	vm_deallocate (mach_task_self (), (vm_address_t) replyout, replyoutCnt);
212
213    dispatch_source_cancel(c->source);
214
215    return 0;
216
217
218}
219
220
221static int
222mach_async(void *ctx, const heim_idata *request, void *userctx,
223	   void (*func)(void *, int, heim_idata *, heim_icred))
224{
225    struct mach_ctx *ipc = ctx;
226    heim_ipc_message_inband_t requestin;
227    mach_msg_type_number_t requestin_length = 0;
228    heim_ipc_message_outband_t requestout = NULL;
229    mach_msg_type_number_t requestout_length = 0;
230    int ret, retries = 0;
231    kern_return_t kr;
232    struct async_client *c;
233
234    /* first create the service that will catch the reply from the server */
235    /* XXX these object should be cached and reused */
236
237    c = malloc(sizeof(*c));
238    if (c == NULL)
239	return ENOMEM;
240
241    kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &c->mp);
242    if (kr != KERN_SUCCESS)
243	return EINVAL;
244
245    c->queue = dispatch_queue_create("heim-ipc-async-client", NULL);
246    c->source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, c->mp, 0, c->queue);
247    dispatch_set_context(c->queue, c);
248
249    dispatch_source_set_event_handler(c->source, ^{
250	    dispatch_mig_server(c->source,
251				sizeof(union __RequestUnion__mheim_ado_mheim_aipc_subsystem),
252				mheim_aipc_server);
253	});
254
255    dispatch_source_set_cancel_handler(c->source, ^{
256	    mach_port_mod_refs(mach_task_self(), c->mp,
257			       MACH_PORT_RIGHT_RECEIVE, -1);
258	    dispatch_release(c->queue);
259	    dispatch_release(c->source);
260	    free(c);
261	});
262
263    c->func = func;
264    c->userctx = userctx;
265
266    dispatch_resume(c->source);
267
268    /* ok, send the message */
269
270    memcpy(requestin, request->data, request->length);
271    requestin_length = request->length;
272
273    while (retries < 2) {
274	__block mach_port_t sport;
275
276	dispatch_sync(syncq, ^{ sport = ipc->server; });
277
278	ret = mheim_ipc_call_request(sport, c->mp,
279				     requestin, requestin_length,
280				     requestout, requestout_length);
281	if (ret == MACH_SEND_INVALID_DEST) {
282	    ret = bootstrap_look_up(bootstrap_port, ipc->name, &sport);
283	    if (ret) {
284		dispatch_source_cancel(c->source);
285		return ret;
286	    }
287	    mach_port_deallocate(mach_task_self(), ipc->server);
288	    ipc->server = sport;
289	    retries++;
290	} else if (ret) {
291	    dispatch_source_cancel(c->source);
292	    return ret;
293	} else
294	    break;
295    }
296    if (retries >= 2) {
297	dispatch_source_cancel(c->source);
298	return EINVAL;
299    }
300
301    return 0;
302}
303
304static int
305mach_release(void *ctx)
306{
307    struct mach_ctx *ipc = ctx;
308    if (ipc->server != MACH_PORT_NULL)
309	mach_port_deallocate(mach_task_self(), ipc->server);
310    free(ipc->name);
311    free(ipc);
312    return 0;
313}
314
315#endif
316
317struct path_ctx {
318    char *path;
319    int fd;
320};
321
322static int common_release(void *);
323
324static int
325connect_unix(struct path_ctx *s)
326{
327    struct sockaddr_un addr;
328
329    addr.sun_family = AF_UNIX;
330    strlcpy(addr.sun_path, s->path, sizeof(addr.sun_path));
331
332    s->fd = socket(AF_UNIX, SOCK_STREAM, 0);
333    if (s->fd < 0)
334	return errno;
335    rk_cloexec(s->fd);
336
337    if (connect(s->fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
338	close(s->fd);
339	return errno;
340    }
341
342    return 0;
343}
344
345static int
346common_path_init(const char *service,
347		 const char *file,
348		 void **ctx)
349{
350    struct path_ctx *s;
351
352    s = malloc(sizeof(*s));
353    if (s == NULL)
354	return ENOMEM;
355    s->fd = -1;
356
357    asprintf(&s->path, "/var/run/.heim_%s-%s", service, file);
358
359    *ctx = s;
360
361    return 0;
362}
363
364static int
365unix_socket_init(const char *service,
366		 void **ctx)
367{
368    int ret;
369
370    ret = common_path_init(service, "socket", ctx);
371    if (ret)
372	return ret;
373    ret = connect_unix(*ctx);
374    if (ret)
375	common_release(*ctx);
376
377    return ret;
378}
379
380static int
381unix_socket_ipc(void *ctx,
382		const heim_idata *req, heim_idata *rep,
383		heim_icred *cred)
384{
385    struct path_ctx *s = ctx;
386    uint32_t len = htonl(req->length);
387    uint32_t rv;
388    int retval;
389
390    if (cred)
391	*cred = NULL;
392
393    rep->data = NULL;
394    rep->length = 0;
395
396    if (net_write(s->fd, &len, sizeof(len)) != sizeof(len))
397	return -1;
398    if (net_write(s->fd, req->data, req->length) != req->length)
399	return -1;
400
401    if (net_read(s->fd, &len, sizeof(len)) != sizeof(len))
402	return -1;
403    if (net_read(s->fd, &rv, sizeof(rv)) != sizeof(rv))
404	return -1;
405    retval = ntohl(rv);
406
407    rep->length = ntohl(len);
408    if (rep->length > 0) {
409	rep->data = malloc(rep->length);
410	if (rep->data == NULL)
411	    return -1;
412	if (net_read(s->fd, rep->data, rep->length) != rep->length)
413	    return -1;
414    } else
415	rep->data = NULL;
416
417    return retval;
418}
419
420int
421common_release(void *ctx)
422{
423    struct path_ctx *s = ctx;
424    if (s->fd >= 0)
425	close(s->fd);
426    free(s->path);
427    free(s);
428    return 0;
429}
430
431#ifdef HAVE_DOOR
432
433static int
434door_init(const char *service,
435	  void **ctx)
436{
437    ret = common_path_init(context, service, "door", ctx);
438    if (ret)
439	return ret;
440    ret = connect_door(*ctx);
441    if (ret)
442	common_release(*ctx);
443    return ret;
444}
445
446static int
447door_ipc(void *ctx,
448	 const heim_idata *request, heim_idata *response,
449	 heim_icred *cred)
450{
451    door_arg_t arg;
452    int ret;
453
454    arg.data_ptr = request->data;
455    arg.data_size = request->length;
456    arg.desc_ptr = NULL;
457    arg.desc_num = 0;
458    arg.rbuf = NULL;
459    arg.rsize = 0;
460
461    ret = door_call(fd, &arg);
462    close(fd);
463    if (ret != 0)
464	return errno;
465
466    response->data = malloc(arg.rsize);
467    if (response->data == NULL) {
468	munmap(arg.rbuf, arg.rsize);
469	return ENOMEM;
470    }
471    memcpy(response->data, arg.rbuf, arg.rsize);
472    response->length = arg.rsize;
473    munmap(arg.rbuf, arg.rsize);
474
475    return ret;
476}
477
478#endif
479
480struct hipc_ops {
481    const char *prefix;
482    int (*init)(const char *, void **);
483    int (*release)(void *);
484    int (*ipc)(void *,const heim_idata *, heim_idata *, heim_icred *);
485    int (*async)(void *, const heim_idata *, void *,
486		 void (*)(void *, int, heim_idata *, heim_icred));
487};
488
489struct hipc_ops ipcs[] = {
490#if defined(__APPLE__) && defined(HAVE_GCD)
491    { "MACH", mach_init, mach_release, mach_ipc, mach_async },
492#endif
493#ifdef HAVE_DOOR
494    { "DOOR", door_init, common_release, door_ipc }
495#endif
496    { "UNIX", unix_socket_init, common_release, unix_socket_ipc }
497};
498
499struct heim_ipc {
500    struct hipc_ops *ops;
501    void *ctx;
502};
503
504
505int
506heim_ipc_init_context(const char *name, heim_ipc *ctx)
507{
508    unsigned int i;
509    int ret, any = 0;
510
511    for(i = 0; i < sizeof(ipcs)/sizeof(ipcs[0]); i++) {
512	size_t prefix_len = strlen(ipcs[i].prefix);
513	heim_ipc c;
514	if(strncmp(ipcs[i].prefix, name, prefix_len) == 0
515	   && name[prefix_len] == ':')  {
516	} else if (strncmp("ANY:", name, 4) == 0) {
517	    prefix_len = 3;
518	    any = 1;
519	} else
520	    continue;
521
522	c = calloc(1, sizeof(*c));
523	if (c == NULL)
524	    return ENOMEM;
525
526	c->ops = &ipcs[i];
527
528	ret = (c->ops->init)(name + prefix_len + 1, &c->ctx);
529	if (ret) {
530	    free(c);
531	    if (any)
532		continue;
533	    return ret;
534	}
535
536	*ctx = c;
537	return 0;
538    }
539
540    return ENOENT;
541}
542
543void
544heim_ipc_free_context(heim_ipc ctx)
545{
546    (ctx->ops->release)(ctx->ctx);
547    free(ctx);
548}
549
550int
551heim_ipc_call(heim_ipc ctx, const heim_idata *send, heim_idata *recv,
552	      heim_icred *cred)
553{
554    if (cred)
555	*cred = NULL;
556    return (ctx->ops->ipc)(ctx->ctx, send, recv, cred);
557}
558
559int
560heim_ipc_async(heim_ipc ctx, const heim_idata *send, void *userctx,
561	       void (*func)(void *, int, heim_idata *, heim_icred))
562{
563    if (ctx->ops->async == NULL) {
564	heim_idata recv;
565	heim_icred cred = NULL;
566	int ret;
567
568	ret = (ctx->ops->ipc)(ctx->ctx, send, &recv, &cred);
569	(*func)(userctx, ret, &recv, cred);
570	heim_ipc_free_cred(cred);
571	free(recv.data);
572	return ret;
573    } else {
574	return (ctx->ops->async)(ctx->ctx, send, userctx, func);
575    }
576}
577