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