xref: /freebsd/crypto/heimdal/lib/ipc/client.c (revision 8aac90f18aef7c9eea906c3ff9a001ca7b94f375)
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 	return errno;
337 
338     return 0;
339 }
340 
341 static int
342 common_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 
360 static int
361 unix_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 
376 static int
377 unix_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 
416 int
417 common_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 
429 static int
430 door_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 
442 static int
443 door_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 
476 struct 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 
485 struct 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 
495 struct heim_ipc {
496     struct hipc_ops *ops;
497     void *ctx;
498 };
499 
500 
501 int
502 heim_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 
539 void
540 heim_ipc_free_context(heim_ipc ctx)
541 {
542     (ctx->ops->release)(ctx->ctx);
543     free(ctx);
544 }
545 
546 int
547 heim_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 
555 int
556 heim_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