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