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 = 0;
120    heim_ipc_message_outband_t replyout = 0;
121    mach_msg_type_number_t replyout_length = 0;
122    int ret, errorcode = -1, 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((void *)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	free(c);
266	return EINVAL;
267    }
268
269    c->queue = dispatch_queue_create("heim-ipc-async-client", NULL);
270    c->source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, c->mp, 0, c->queue);
271    dispatch_queue_set_specific(c->queue, (void *)mheim_ado_acall_reply, c, NULL);
272
273    dispatch_source_set_event_handler(c->source, ^{
274	    dispatch_mig_server(c->source,
275				sizeof(union __RequestUnion__mheim_ado_mheim_aipc_subsystem),
276				mheim_aipc_server);
277	});
278
279    dispatch_source_set_cancel_handler(c->source, ^{
280	    mach_port_mod_refs(mach_task_self(), c->mp,
281			       MACH_PORT_RIGHT_RECEIVE, -1);
282	    dispatch_release(c->queue);
283	    dispatch_release(c->source);
284	    free(c);
285	});
286
287    c->func = func;
288    c->userctx = userctx;
289
290    dispatch_resume(c->source);
291
292    /* ok, send the message */
293    if (request->length < sizeof(requestin)) {
294	memcpy(requestin, request->data, request->length);
295	requestin_length = (mach_msg_type_number_t)request->length;
296    } else {
297	ret = vm_read(mach_task_self(),
298		      (vm_address_t)request->data, request->length,
299		      (vm_address_t *)&requestout, &requestout_length);
300	if (ret)
301	    return ENOMEM;
302    }
303
304    while (retries < 2) {
305	__block mach_port_t sport;
306
307	dispatch_sync(syncq, ^{ sport = ipc->server; });
308
309	ret = mheim_ipc_call_request(sport, c->mp,
310				     requestin, requestin_length,
311				     requestout, requestout_length);
312	if (ret == MACH_SEND_INVALID_DEST) {
313	    ret = look_up(ipc->name, &sport);
314	    if (ret) {
315		dispatch_source_cancel(c->source);
316		return ret;
317	    }
318	    mach_port_deallocate(mach_task_self(), ipc->server);
319	    ipc->server = sport;
320	    retries++;
321	} else if (ret) {
322	    dispatch_source_cancel(c->source);
323	    return ret;
324	} else
325	    break;
326    }
327    if (retries >= 2) {
328	dispatch_source_cancel(c->source);
329	return EINVAL;
330    }
331
332    return 0;
333}
334
335static int
336mach_release(void *ctx)
337{
338    struct mach_ctx *ipc = ctx;
339    if (ipc->server != MACH_PORT_NULL)
340	mach_port_deallocate(mach_task_self(), ipc->server);
341    free(ipc->name);
342    free(ipc);
343    return 0;
344}
345
346#endif
347
348struct path_ctx {
349    char *path;
350    int fd;
351};
352
353static int common_release(void *);
354
355static int
356connect_unix(struct path_ctx *s)
357{
358    struct sockaddr_un addr;
359
360    addr.sun_family = AF_UNIX;
361    strlcpy(addr.sun_path, s->path, sizeof(addr.sun_path));
362
363    s->fd = socket(AF_UNIX, SOCK_STREAM, 0);
364    if (s->fd < 0)
365	return errno;
366    rk_cloexec(s->fd);
367    socket_set_nopipe(s->fd, 1);
368
369    if (connect(s->fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
370	close(s->fd);
371	s->fd = -1;
372	return errno;
373    }
374
375    return 0;
376}
377
378static int
379common_path_init(const char *service,
380		 const char *file,
381		 void **ctx)
382{
383    struct path_ctx *s;
384
385    s = malloc(sizeof(*s));
386    if (s == NULL)
387	return ENOMEM;
388    s->fd = -1;
389
390    asprintf(&s->path, "/var/run/.heim_%s-%s", service, file);
391
392    *ctx = s;
393
394    return 0;
395}
396
397static int
398unix_socket_init(const char *service,
399		 void **ctx)
400{
401    int ret;
402
403    ret = common_path_init(service, "socket", ctx);
404    if (ret)
405	return ret;
406    ret = connect_unix(*ctx);
407    if (ret)
408	common_release(*ctx);
409
410    return ret;
411}
412
413static int
414unix_socket_ipc(void *ctx,
415		const heim_idata *req, heim_idata *rep,
416		heim_icred *cred)
417{
418    struct path_ctx *s = ctx;
419    uint32_t len = htonl(req->length);
420    uint32_t rv;
421    int retval;
422
423    if (cred)
424	*cred = NULL;
425
426    rep->data = NULL;
427    rep->length = 0;
428
429    if (net_write(s->fd, &len, sizeof(len)) != sizeof(len))
430	return -1;
431    if (net_write(s->fd, req->data, req->length) != (ssize_t)req->length)
432	return -1;
433
434    if (net_read(s->fd, &len, sizeof(len)) != sizeof(len))
435	return -1;
436    if (net_read(s->fd, &rv, sizeof(rv)) != sizeof(rv))
437	return -1;
438    retval = ntohl(rv);
439
440    rep->length = ntohl(len);
441    if (rep->length > MAX_PACKET_SIZE) {
442	rep->length = 0;
443	return EINVAL;
444    } else {
445	rep->data = malloc(rep->length);
446	if (rep->data == NULL)
447	    return -1;
448	if (net_read(s->fd, rep->data, rep->length) != (ssize_t)rep->length)
449	    return -1;
450    }
451
452    return retval;
453}
454
455int
456common_release(void *ctx)
457{
458    struct path_ctx *s = ctx;
459    if (s->fd >= 0)
460	close(s->fd);
461    free(s->path);
462    free(s);
463    return 0;
464}
465
466#ifdef HAVE_DOOR
467
468static int
469door_init(const char *service,
470	  void **ctx)
471{
472    ret = common_path_init(context, service, "door", ctx);
473    if (ret)
474	return ret;
475    ret = connect_door(*ctx);
476    if (ret)
477	common_release(*ctx);
478    return ret;
479}
480
481static int
482door_ipc(void *ctx,
483	 const heim_idata *request, heim_idata *response,
484	 heim_icred *cred)
485{
486    door_arg_t arg;
487    int ret;
488
489    arg.data_ptr = request->data;
490    arg.data_size = request->length;
491    arg.desc_ptr = NULL;
492    arg.desc_num = 0;
493    arg.rbuf = NULL;
494    arg.rsize = 0;
495
496    ret = door_call(fd, &arg);
497    close(fd);
498    if (ret != 0)
499	return errno;
500
501    response->data = malloc(arg.rsize);
502    if (response->data == NULL) {
503	munmap(arg.rbuf, arg.rsize);
504	return ENOMEM;
505    }
506    memcpy(response->data, arg.rbuf, arg.rsize);
507    response->length = arg.rsize;
508    munmap(arg.rbuf, arg.rsize);
509
510    return ret;
511}
512
513#endif
514
515struct hipc_ops {
516    const char *prefix;
517    int (*init)(const char *, void **);
518    int (*release)(void *);
519    int (*ipc)(void *,const heim_idata *, heim_idata *, heim_icred *);
520    int (*async)(void *, const heim_idata *, void *,
521		 void (*)(void *, int, heim_idata *, heim_icred));
522};
523
524struct hipc_ops ipcs[] = {
525#if defined(__APPLE__) && defined(HAVE_GCD)
526    { "MACH", mach_init, mach_release, mach_ipc, mach_async },
527#endif
528#ifdef HAVE_DOOR
529    { "DOOR", door_init, common_release, door_ipc, NULL }
530#endif
531    { "UNIX", unix_socket_init, common_release, unix_socket_ipc, NULL }
532};
533
534struct heim_ipc {
535    struct hipc_ops *ops;
536    void *ctx;
537};
538
539
540int
541heim_ipc_init_context(const char *name, heim_ipc *ctx)
542{
543    unsigned int i;
544    int ret, any = 0;
545
546    for(i = 0; i < sizeof(ipcs)/sizeof(ipcs[0]); i++) {
547	size_t prefix_len = strlen(ipcs[i].prefix);
548	heim_ipc c;
549	if(strncmp(ipcs[i].prefix, name, prefix_len) == 0
550	   && name[prefix_len] == ':')  {
551	} else if (strncmp("ANY:", name, 4) == 0) {
552	    prefix_len = 3;
553	    any = 1;
554	} else
555	    continue;
556
557	c = calloc(1, sizeof(*c));
558	if (c == NULL)
559	    return ENOMEM;
560
561	c->ops = &ipcs[i];
562
563	ret = (c->ops->init)(name + prefix_len + 1, &c->ctx);
564	if (ret) {
565	    free(c);
566	    if (any)
567		continue;
568	    return ret;
569	}
570
571	*ctx = c;
572	return 0;
573    }
574
575    return ENOENT;
576}
577
578void
579heim_ipc_free_context(heim_ipc ctx)
580{
581    (ctx->ops->release)(ctx->ctx);
582    free(ctx);
583}
584
585int
586heim_ipc_call(heim_ipc ctx, const heim_idata *snd, heim_idata *rcv,
587	      heim_icred *cred)
588{
589    if (cred)
590	*cred = NULL;
591    return (ctx->ops->ipc)(ctx->ctx, snd, rcv, cred);
592}
593
594int
595heim_ipc_async(heim_ipc ctx, const heim_idata *snd, void *userctx,
596	       void (*func)(void *, int, heim_idata *, heim_icred))
597{
598    if (ctx->ops->async == NULL) {
599	heim_idata rcv;
600	heim_icred cred = NULL;
601	int ret;
602
603	ret = (ctx->ops->ipc)(ctx->ctx, snd, &rcv, &cred);
604	(*func)(userctx, ret, &rcv, cred);
605	heim_ipc_free_cred(cred);
606	free(rcv.data);
607	return ret;
608    } else {
609	return (ctx->ops->async)(ctx->ctx, snd, userctx, func);
610    }
611}
612