xref: /freebsd/crypto/heimdal/lib/ipc/client.c (revision 8ddb146abcdf061be9f2c0db7e391697dafad85c)
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 
46 static dispatch_once_t jobqinited = 0;
47 static dispatch_queue_t jobq = NULL;
48 static dispatch_queue_t syncq;
49 
50 struct mach_ctx {
51     mach_port_t server;
52     char *name;
53 };
54 
55 static int
56 mach_release(void *ctx);
57 
58 static int
59 mach_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 
92 static int
93 mach_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 
175 struct 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 
183 kern_return_t
184 mheim_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 
219 static int
220 mach_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 
302 static int
303 mach_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 
315 struct path_ctx {
316     char *path;
317     int fd;
318 };
319 
320 static int common_release(void *);
321 
322 static int
323 connect_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 	close(s->fd);
337 	return errno;
338     }
339 
340     return 0;
341 }
342 
343 static int
344 common_path_init(const char *service,
345 		 const char *file,
346 		 void **ctx)
347 {
348     struct path_ctx *s;
349 
350     s = malloc(sizeof(*s));
351     if (s == NULL)
352 	return ENOMEM;
353     s->fd = -1;
354 
355     asprintf(&s->path, "/var/run/.heim_%s-%s", service, file);
356 
357     *ctx = s;
358 
359     return 0;
360 }
361 
362 static int
363 unix_socket_init(const char *service,
364 		 void **ctx)
365 {
366     int ret;
367 
368     ret = common_path_init(service, "socket", ctx);
369     if (ret)
370 	return ret;
371     ret = connect_unix(*ctx);
372     if (ret)
373 	common_release(*ctx);
374 
375     return ret;
376 }
377 
378 static int
379 unix_socket_ipc(void *ctx,
380 		const heim_idata *req, heim_idata *rep,
381 		heim_icred *cred)
382 {
383     struct path_ctx *s = ctx;
384     uint32_t len = htonl(req->length);
385     uint32_t rv;
386     int retval;
387 
388     if (cred)
389 	*cred = NULL;
390 
391     rep->data = NULL;
392     rep->length = 0;
393 
394     if (net_write(s->fd, &len, sizeof(len)) != sizeof(len))
395 	return -1;
396     if (net_write(s->fd, req->data, req->length) != (ssize_t)req->length)
397 	return -1;
398 
399     if (net_read(s->fd, &len, sizeof(len)) != sizeof(len))
400 	return -1;
401     if (net_read(s->fd, &rv, sizeof(rv)) != sizeof(rv))
402 	return -1;
403     retval = ntohl(rv);
404 
405     rep->length = ntohl(len);
406     if (rep->length > 0) {
407 	rep->data = malloc(rep->length);
408 	if (rep->data == NULL)
409 	    return -1;
410 	if (net_read(s->fd, rep->data, rep->length) != (ssize_t)rep->length)
411 	    return -1;
412     } else
413 	rep->data = NULL;
414 
415     return retval;
416 }
417 
418 int
419 common_release(void *ctx)
420 {
421     struct path_ctx *s = ctx;
422     if (s->fd >= 0)
423 	close(s->fd);
424     free(s->path);
425     free(s);
426     return 0;
427 }
428 
429 #ifdef HAVE_DOOR
430 
431 static int
432 door_init(const char *service,
433 	  void **ctx)
434 {
435     ret = common_path_init(context, service, "door", ctx);
436     if (ret)
437 	return ret;
438     ret = connect_door(*ctx);
439     if (ret)
440 	common_release(*ctx);
441     return ret;
442 }
443 
444 static int
445 door_ipc(void *ctx,
446 	 const heim_idata *request, heim_idata *response,
447 	 heim_icred *cred)
448 {
449     door_arg_t arg;
450     int ret;
451 
452     arg.data_ptr = request->data;
453     arg.data_size = request->length;
454     arg.desc_ptr = NULL;
455     arg.desc_num = 0;
456     arg.rbuf = NULL;
457     arg.rsize = 0;
458 
459     ret = door_call(fd, &arg);
460     close(fd);
461     if (ret != 0)
462 	return errno;
463 
464     response->data = malloc(arg.rsize);
465     if (response->data == NULL) {
466 	munmap(arg.rbuf, arg.rsize);
467 	return ENOMEM;
468     }
469     memcpy(response->data, arg.rbuf, arg.rsize);
470     response->length = arg.rsize;
471     munmap(arg.rbuf, arg.rsize);
472 
473     return ret;
474 }
475 
476 #endif
477 
478 struct hipc_ops {
479     const char *prefix;
480     int (*init)(const char *, void **);
481     int (*release)(void *);
482     int (*ipc)(void *,const heim_idata *, heim_idata *, heim_icred *);
483     int (*async)(void *, const heim_idata *, void *,
484 		 void (*)(void *, int, heim_idata *, heim_icred));
485 };
486 
487 struct hipc_ops ipcs[] = {
488 #if defined(__APPLE__) && defined(HAVE_GCD)
489     { "MACH", mach_init, mach_release, mach_ipc, mach_async },
490 #endif
491 #ifdef HAVE_DOOR
492     { "DOOR", door_init, common_release, door_ipc, NULL }
493 #endif
494     { "UNIX", unix_socket_init, common_release, unix_socket_ipc, NULL }
495 };
496 
497 struct heim_ipc {
498     struct hipc_ops *ops;
499     void *ctx;
500 };
501 
502 
503 int
504 heim_ipc_init_context(const char *name, heim_ipc *ctx)
505 {
506     unsigned int i;
507     int ret, any = 0;
508 
509     for(i = 0; i < sizeof(ipcs)/sizeof(ipcs[0]); i++) {
510 	size_t prefix_len = strlen(ipcs[i].prefix);
511 	heim_ipc c;
512 	if(strncmp(ipcs[i].prefix, name, prefix_len) == 0
513 	   && name[prefix_len] == ':')  {
514 	} else if (strncmp("ANY:", name, 4) == 0) {
515 	    prefix_len = 3;
516 	    any = 1;
517 	} else
518 	    continue;
519 
520 	c = calloc(1, sizeof(*c));
521 	if (c == NULL)
522 	    return ENOMEM;
523 
524 	c->ops = &ipcs[i];
525 
526 	ret = (c->ops->init)(name + prefix_len + 1, &c->ctx);
527 	if (ret) {
528 	    free(c);
529 	    if (any)
530 		continue;
531 	    return ret;
532 	}
533 
534 	*ctx = c;
535 	return 0;
536     }
537 
538     return ENOENT;
539 }
540 
541 void
542 heim_ipc_free_context(heim_ipc ctx)
543 {
544     (ctx->ops->release)(ctx->ctx);
545     free(ctx);
546 }
547 
548 int
549 heim_ipc_call(heim_ipc ctx, const heim_idata *snd, heim_idata *rcv,
550 	      heim_icred *cred)
551 {
552     if (cred)
553 	*cred = NULL;
554     return (ctx->ops->ipc)(ctx->ctx, snd, rcv, cred);
555 }
556 
557 int
558 heim_ipc_async(heim_ipc ctx, const heim_idata *snd, void *userctx,
559 	       void (*func)(void *, int, heim_idata *, heim_icred))
560 {
561     if (ctx->ops->async == NULL) {
562 	heim_idata rcv;
563 	heim_icred cred = NULL;
564 	int ret;
565 
566 	ret = (ctx->ops->ipc)(ctx->ctx, snd, &rcv, &cred);
567 	(*func)(userctx, ret, &rcv, cred);
568 	heim_ipc_free_cred(cred);
569 	free(rcv.data);
570 	return ret;
571     } else {
572 	return (ctx->ops->async)(ctx->ctx, snd, userctx, func);
573     }
574 }
575