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