xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/ipc/server.c (revision d3273b5b76f5afaafe308cead5511dbb8df8c5e9)
1 /*	$NetBSD: server.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 #include <assert.h>
40 #include <err.h>
41 
42 #define MAX_PACKET_SIZE (128 * 1024)
43 
44 struct heim_sipc {
45     int (*release)(heim_sipc ctx);
46     heim_ipc_callback callback;
47     void *userctx;
48     void *mech;
49 };
50 
51 #if defined(__APPLE__) && defined(HAVE_GCD)
52 
53 #include "heim_ipcServer.h"
54 #include "heim_ipc_reply.h"
55 #include "heim_ipc_async.h"
56 
57 static dispatch_source_t timer;
58 static dispatch_queue_t timerq;
59 static uint64_t timeoutvalue;
60 
61 static dispatch_queue_t eventq;
62 
63 static dispatch_queue_t workq;
64 
65 static void
default_timer_ev(void)66 default_timer_ev(void)
67 {
68     exit(0);
69 }
70 
71 static void (*timer_ev)(void) = default_timer_ev;
72 
73 static void
set_timer(void)74 set_timer(void)
75 {
76     dispatch_source_set_timer(timer,
77 			      dispatch_time(DISPATCH_TIME_NOW,
78 					    timeoutvalue * NSEC_PER_SEC),
79 			      timeoutvalue * NSEC_PER_SEC, 1000000);
80 }
81 
82 static void
init_globals(void)83 init_globals(void)
84 {
85     static dispatch_once_t once;
86     dispatch_once(&once, ^{
87 	timerq = dispatch_queue_create("hiem-sipc-timer-q", NULL);
88         timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, timerq);
89 	dispatch_source_set_event_handler(timer, ^{ timer_ev(); } );
90 
91 	workq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
92 	eventq = dispatch_queue_create("heim-ipc.event-queue", NULL);
93     });
94 }
95 
96 static void
suspend_timer(void)97 suspend_timer(void)
98 {
99     dispatch_suspend(timer);
100 }
101 
102 static void
restart_timer(void)103 restart_timer(void)
104 {
105     dispatch_sync(timerq, ^{ set_timer(); });
106     dispatch_resume(timer);
107 }
108 
109 struct mach_service {
110     mach_port_t sport;
111     dispatch_source_t source;
112     dispatch_queue_t queue;
113 };
114 
115 struct mach_call_ctx {
116     mach_port_t reply_port;
117     heim_icred cred;
118     heim_idata req;
119 };
120 
121 
122 static void
mach_complete_sync(heim_sipc_call ctx,int returnvalue,heim_idata * reply)123 mach_complete_sync(heim_sipc_call ctx, int returnvalue, heim_idata *reply)
124 {
125     struct mach_call_ctx *s = (struct mach_call_ctx *)ctx;
126     heim_ipc_message_inband_t replyin;
127     mach_msg_type_number_t replyinCnt;
128     heim_ipc_message_outband_t replyout;
129     mach_msg_type_number_t replyoutCnt;
130     kern_return_t kr;
131 
132     if (returnvalue) {
133 	/* on error, no reply */
134 	replyinCnt = 0;
135 	replyout = 0; replyoutCnt = 0;
136 	kr = KERN_SUCCESS;
137     } else if (reply->length < 2048) {
138 	replyinCnt = reply->length;
139 	memcpy(replyin, reply->data, replyinCnt);
140 	replyout = 0; replyoutCnt = 0;
141 	kr = KERN_SUCCESS;
142     } else {
143 	replyinCnt = 0;
144 	kr = vm_read(mach_task_self(),
145 		     (vm_address_t)reply->data, reply->length,
146 		     (vm_address_t *)&replyout, &replyoutCnt);
147     }
148 
149     mheim_ripc_call_reply(s->reply_port, returnvalue,
150 			  replyin, replyinCnt,
151 			  replyout, replyoutCnt);
152 
153     heim_ipc_free_cred(s->cred);
154     free(s->req.data);
155     free(s);
156     restart_timer();
157 }
158 
159 static void
mach_complete_async(heim_sipc_call ctx,int returnvalue,heim_idata * reply)160 mach_complete_async(heim_sipc_call ctx, int returnvalue, heim_idata *reply)
161 {
162     struct mach_call_ctx *s = (struct mach_call_ctx *)ctx;
163     heim_ipc_message_inband_t replyin;
164     mach_msg_type_number_t replyinCnt;
165     heim_ipc_message_outband_t replyout;
166     mach_msg_type_number_t replyoutCnt;
167     kern_return_t kr;
168 
169     if (returnvalue) {
170 	/* on error, no reply */
171 	replyinCnt = 0;
172 	replyout = 0; replyoutCnt = 0;
173 	kr = KERN_SUCCESS;
174     } else if (reply->length < 2048) {
175 	replyinCnt = reply->length;
176 	memcpy(replyin, reply->data, replyinCnt);
177 	replyout = 0; replyoutCnt = 0;
178 	kr = KERN_SUCCESS;
179     } else {
180 	replyinCnt = 0;
181 	kr = vm_read(mach_task_self(),
182 		     (vm_address_t)reply->data, reply->length,
183 		     (vm_address_t *)&replyout, &replyoutCnt);
184     }
185 
186     kr = mheim_aipc_acall_reply(s->reply_port, returnvalue,
187 				replyin, replyinCnt,
188 				replyout, replyoutCnt);
189     heim_ipc_free_cred(s->cred);
190     free(s->req.data);
191     free(s);
192     restart_timer();
193 }
194 
195 
196 kern_return_t
mheim_do_call(mach_port_t server_port,audit_token_t client_creds,mach_port_t reply_port,heim_ipc_message_inband_t requestin,mach_msg_type_number_t requestinCnt,heim_ipc_message_outband_t requestout,mach_msg_type_number_t requestoutCnt,int * returnvalue,heim_ipc_message_inband_t replyin,mach_msg_type_number_t * replyinCnt,heim_ipc_message_outband_t * replyout,mach_msg_type_number_t * replyoutCnt)197 mheim_do_call(mach_port_t server_port,
198 	      audit_token_t client_creds,
199 	      mach_port_t reply_port,
200 	      heim_ipc_message_inband_t requestin,
201 	      mach_msg_type_number_t requestinCnt,
202 	      heim_ipc_message_outband_t requestout,
203 	      mach_msg_type_number_t requestoutCnt,
204 	      int *returnvalue,
205 	      heim_ipc_message_inband_t replyin,
206 	      mach_msg_type_number_t *replyinCnt,
207 	      heim_ipc_message_outband_t *replyout,
208 	      mach_msg_type_number_t *replyoutCnt)
209 {
210     heim_sipc ctx = dispatch_get_context(dispatch_get_current_queue());
211     struct mach_call_ctx *s;
212     kern_return_t kr;
213     uid_t uid;
214     gid_t gid;
215     pid_t pid;
216     au_asid_t session;
217 
218     *replyout = NULL;
219     *replyoutCnt = 0;
220     *replyinCnt = 0;
221 
222     s = malloc(sizeof(*s));
223     if (s == NULL)
224 	return KERN_MEMORY_FAILURE; /* XXX */
225 
226     s->reply_port = reply_port;
227 
228     audit_token_to_au32(client_creds, NULL, &uid, &gid, NULL, NULL, &pid, &session, NULL);
229 
230     kr = _heim_ipc_create_cred(uid, gid, pid, session, &s->cred);
231     if (kr) {
232 	free(s);
233 	return kr;
234     }
235 
236     suspend_timer();
237 
238     if (requestinCnt) {
239 	s->req.data = malloc(requestinCnt);
240 	memcpy(s->req.data, requestin, requestinCnt);
241 	s->req.length = requestinCnt;
242     } else {
243 	s->req.data = malloc(requestoutCnt);
244 	memcpy(s->req.data, requestout, requestoutCnt);
245 	s->req.length = requestoutCnt;
246     }
247 
248     dispatch_async(workq, ^{
249 	(ctx->callback)(ctx->userctx, &s->req, s->cred,
250 			mach_complete_sync, (heim_sipc_call)s);
251     });
252 
253     return MIG_NO_REPLY;
254 }
255 
256 kern_return_t
mheim_do_call_request(mach_port_t server_port,audit_token_t client_creds,mach_port_t reply_port,heim_ipc_message_inband_t requestin,mach_msg_type_number_t requestinCnt,heim_ipc_message_outband_t requestout,mach_msg_type_number_t requestoutCnt)257 mheim_do_call_request(mach_port_t server_port,
258 		      audit_token_t client_creds,
259 		      mach_port_t reply_port,
260 		      heim_ipc_message_inband_t requestin,
261 		      mach_msg_type_number_t requestinCnt,
262 		      heim_ipc_message_outband_t requestout,
263 		      mach_msg_type_number_t requestoutCnt)
264 {
265     heim_sipc ctx = dispatch_get_context(dispatch_get_current_queue());
266     struct mach_call_ctx *s;
267     kern_return_t kr;
268     uid_t uid;
269     gid_t gid;
270     pid_t pid;
271     au_asid_t session;
272 
273     s = malloc(sizeof(*s));
274     if (s == NULL)
275 	return KERN_MEMORY_FAILURE; /* XXX */
276 
277     s->reply_port = reply_port;
278 
279     audit_token_to_au32(client_creds, NULL, &uid, &gid, NULL, NULL, &pid, &session, NULL);
280 
281     kr = _heim_ipc_create_cred(uid, gid, pid, session, &s->cred);
282     if (kr) {
283 	free(s);
284 	return kr;
285     }
286 
287     suspend_timer();
288 
289     if (requestinCnt) {
290 	s->req.data = malloc(requestinCnt);
291 	memcpy(s->req.data, requestin, requestinCnt);
292 	s->req.length = requestinCnt;
293     } else {
294 	s->req.data = malloc(requestoutCnt);
295 	memcpy(s->req.data, requestout, requestoutCnt);
296 	s->req.length = requestoutCnt;
297     }
298 
299     dispatch_async(workq, ^{
300 	(ctx->callback)(ctx->userctx, &s->req, s->cred,
301 			mach_complete_async, (heim_sipc_call)s);
302     });
303 
304     return KERN_SUCCESS;
305 }
306 
307 static int
mach_init(const char * service,mach_port_t sport,heim_sipc ctx)308 mach_init(const char *service, mach_port_t sport, heim_sipc ctx)
309 {
310     struct mach_service *s;
311     char *name;
312 
313     init_globals();
314 
315     s = calloc(1, sizeof(*s));
316     if (s == NULL)
317 	return ENOMEM;
318 
319     asprintf(&name, "heim-ipc-mach-%s", service);
320 
321     s->queue = dispatch_queue_create(name, NULL);
322     free(name);
323     s->sport = sport;
324 
325     s->source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV,
326 				       s->sport, 0, s->queue);
327     if (s->source == NULL) {
328 	dispatch_release(s->queue);
329 	free(s);
330 	return ENOMEM;
331     }
332     ctx->mech = s;
333 
334     dispatch_set_context(s->queue, ctx);
335     dispatch_set_context(s->source, s);
336 
337     dispatch_source_set_event_handler(s->source, ^{
338 	    dispatch_mig_server(s->source, sizeof(union __RequestUnion__mheim_do_mheim_ipc_subsystem), mheim_ipc_server);
339 	});
340 
341     dispatch_source_set_cancel_handler(s->source, ^{
342 	    heim_sipc sctx = dispatch_get_context(dispatch_get_current_queue());
343 	    struct mach_service *st = sctx->mech;
344 	    mach_port_mod_refs(mach_task_self(), st->sport,
345 			       MACH_PORT_RIGHT_RECEIVE, -1);
346 	    dispatch_release(st->queue);
347 	    dispatch_release(st->source);
348 	    free(st);
349 	    free(sctx);
350 	});
351 
352     dispatch_resume(s->source);
353 
354     return 0;
355 }
356 
357 static int
mach_release(heim_sipc ctx)358 mach_release(heim_sipc ctx)
359 {
360     struct mach_service *s = ctx->mech;
361     dispatch_source_cancel(s->source);
362     dispatch_release(s->source);
363     return 0;
364 }
365 
366 static mach_port_t
mach_checkin_or_register(const char * service)367 mach_checkin_or_register(const char *service)
368 {
369     mach_port_t mp;
370     kern_return_t kr;
371 
372     kr = bootstrap_check_in(bootstrap_port, service, &mp);
373     if (kr == KERN_SUCCESS)
374 	return mp;
375 
376 #if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1050
377     /* Pre SnowLeopard version */
378     kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &mp);
379     if (kr != KERN_SUCCESS)
380 	return MACH_PORT_NULL;
381 
382     kr = mach_port_insert_right(mach_task_self(), mp, mp,
383 				MACH_MSG_TYPE_MAKE_SEND);
384     if (kr != KERN_SUCCESS) {
385 	mach_port_destroy(mach_task_self(), mp);
386 	return MACH_PORT_NULL;
387     }
388 
389     kr = bootstrap_register(bootstrap_port, rk_UNCONST(service), mp);
390     if (kr != KERN_SUCCESS) {
391 	mach_port_destroy(mach_task_self(), mp);
392 	return MACH_PORT_NULL;
393     }
394 
395     return mp;
396 #else
397     return MACH_PORT_NULL;
398 #endif
399 }
400 
401 
402 #endif /* __APPLE__ && HAVE_GCD */
403 
404 
405 int
heim_sipc_launchd_mach_init(const char * service,heim_ipc_callback callback,void * user,heim_sipc * ctx)406 heim_sipc_launchd_mach_init(const char *service,
407 			    heim_ipc_callback callback,
408 			    void *user, heim_sipc *ctx)
409 {
410 #if defined(__APPLE__) && defined(HAVE_GCD)
411     mach_port_t sport = MACH_PORT_NULL;
412     heim_sipc c = NULL;
413     int ret;
414 
415     *ctx = NULL;
416 
417     sport = mach_checkin_or_register(service);
418     if (sport == MACH_PORT_NULL) {
419 	ret = ENOENT;
420 	goto error;
421     }
422 
423     c = calloc(1, sizeof(*c));
424     if (c == NULL) {
425 	ret = ENOMEM;
426 	goto error;
427     }
428     c->release = mach_release;
429     c->userctx = user;
430     c->callback = callback;
431 
432     ret = mach_init(service, sport, c);
433     if (ret)
434 	goto error;
435 
436     *ctx = c;
437     return 0;
438  error:
439     if (c)
440 	free(c);
441     if (sport != MACH_PORT_NULL)
442 	mach_port_mod_refs(mach_task_self(), sport,
443 			   MACH_PORT_RIGHT_RECEIVE, -1);
444     return ret;
445 #else /* !(__APPLE__ && HAVE_GCD) */
446     *ctx = NULL;
447     return EINVAL;
448 #endif /* __APPLE__ && HAVE_GCD */
449 }
450 
451 struct client {
452     int fd;
453     heim_ipc_callback callback;
454     void *userctx;
455     int flags;
456 #define LISTEN_SOCKET	1
457 #define WAITING_READ	2
458 #define WAITING_WRITE	4
459 #define WAITING_CLOSE	8
460 
461 #define HTTP_REPLY	16
462 
463 #define INHERIT_MASK	0xffff0000
464 #define INCLUDE_ERROR_CODE (1 << 16)
465 #define ALLOW_HTTP	(1<<17)
466 #define UNIX_SOCKET	(1<<18)
467     unsigned calls;
468     size_t ptr, len;
469     uint8_t *inmsg;
470     size_t olen;
471     uint8_t *outmsg;
472 #ifdef HAVE_GCD
473     dispatch_source_t in;
474     dispatch_source_t out;
475 #endif
476     struct {
477 	uid_t uid;
478 	gid_t gid;
479 	pid_t pid;
480     } unixrights;
481 };
482 
483 #ifndef HAVE_GCD
484 static unsigned num_clients = 0;
485 static struct client **clients = NULL;
486 #endif
487 
488 static void handle_read(struct client *);
489 static void handle_write(struct client *);
490 static int maybe_close(struct client *);
491 
492 /*
493  * Update peer credentials from socket.
494  *
495  * SCM_CREDS can only be updated the first time there is read data to
496  * read from the filedescriptor, so if we read do it before this
497  * point, the cred data might not be is not there yet.
498  */
499 
500 static int
update_client_creds(struct client * c)501 update_client_creds(struct client *c)
502 {
503 #ifdef HAVE_GETPEERUCRED
504     /* Solaris 10 */
505     {
506 	ucred_t *peercred;
507 
508 	if (getpeerucred(c->fd, &peercred) != 0) {
509 	    c->unixrights.uid = ucred_geteuid(peercred);
510 	    c->unixrights.gid = ucred_getegid(peercred);
511 	    c->unixrights.pid = 0;
512 	    ucred_free(peercred);
513 	    return 1;
514 	}
515     }
516 #endif
517 #ifdef HAVE_GETPEEREID
518     /* FreeBSD, OpenBSD */
519     {
520 	uid_t uid;
521 	gid_t gid;
522 
523 	if (getpeereid(c->fd, &uid, &gid) == 0) {
524 	    c->unixrights.uid = uid;
525 	    c->unixrights.gid = gid;
526 	    c->unixrights.pid = 0;
527 	    return 1;
528 	}
529     }
530 #endif
531 #if defined(SO_PEERCRED) && defined(__linux__)
532     /* Linux */
533     {
534 	struct ucred pc;
535 	socklen_t pclen = sizeof(pc);
536 
537 	if (getsockopt(c->fd, SOL_SOCKET, SO_PEERCRED, (void *)&pc, &pclen) == 0) {
538 	    c->unixrights.uid = pc.uid;
539 	    c->unixrights.gid = pc.gid;
540 	    c->unixrights.pid = pc.pid;
541 	    return 1;
542 	}
543     }
544 #endif
545 #if defined(LOCAL_PEERCRED) && defined(XUCRED_VERSION)
546     {
547 	struct xucred peercred;
548 	socklen_t peercredlen = sizeof(peercred);
549 
550 	if (getsockopt(c->fd, LOCAL_PEERCRED, 1,
551 		       (void *)&peercred, &peercredlen) == 0
552 	    && peercred.cr_version == XUCRED_VERSION)
553 	{
554 	    c->unixrights.uid = peercred.cr_uid;
555 	    c->unixrights.gid = peercred.cr_gid;
556 	    c->unixrights.pid = 0;
557 	    return 1;
558 	}
559     }
560 #endif
561 #if defined(SOCKCREDSIZE) && defined(SCM_CREDS)
562     /* NetBSD */
563     if (c->unixrights.uid == (uid_t)-1) {
564 	struct msghdr msg;
565 	socklen_t crmsgsize;
566 	void *crmsg;
567 	struct cmsghdr *cmp;
568 	struct sockcred *sc;
569 
570 	memset(&msg, 0, sizeof(msg));
571 	crmsgsize = CMSG_SPACE(SOCKCREDSIZE(NGROUPS));
572 	if (crmsgsize == 0)
573 	    return 1 ;
574 
575 	crmsg = malloc(crmsgsize);
576 	if (crmsg == NULL)
577 	    goto failed_scm_creds;
578 
579 	memset(crmsg, 0, crmsgsize);
580 
581 	msg.msg_control = crmsg;
582 	msg.msg_controllen = crmsgsize;
583 
584 	if (recvmsg(c->fd, &msg, 0) < 0) {
585 	    free(crmsg);
586 	    goto failed_scm_creds;
587 	}
588 
589 	if (msg.msg_controllen == 0 || (msg.msg_flags & MSG_CTRUNC) != 0) {
590 	    free(crmsg);
591 	    goto failed_scm_creds;
592 	}
593 
594 	cmp = CMSG_FIRSTHDR(&msg);
595 	if (cmp->cmsg_level != SOL_SOCKET || cmp->cmsg_type != SCM_CREDS) {
596 	    free(crmsg);
597 	    goto failed_scm_creds;
598 	}
599 
600 	sc = (struct sockcred *)(void *)CMSG_DATA(cmp);
601 
602 	c->unixrights.uid = sc->sc_euid;
603 	c->unixrights.gid = sc->sc_egid;
604 	c->unixrights.pid = 0;
605 
606 	free(crmsg);
607 	return 1;
608     } else {
609 	/* we already got the cred, just return it */
610 	return 1;
611     }
612  failed_scm_creds:
613 #endif
614     return 0;
615 }
616 
617 
618 static struct client *
add_new_socket(int fd,int flags,heim_ipc_callback callback,void * userctx)619 add_new_socket(int fd,
620 	       int flags,
621 	       heim_ipc_callback callback,
622 	       void *userctx)
623 {
624     struct client *c;
625     int fileflags;
626 
627     c = calloc(1, sizeof(*c));
628     if (c == NULL)
629 	return NULL;
630 
631     if (flags & LISTEN_SOCKET) {
632 	c->fd = fd;
633     } else {
634 	c->fd = accept(fd, NULL, NULL);
635 	if(c->fd < 0) {
636 	    free(c);
637 	    return NULL;
638 	}
639     }
640 
641     c->flags = flags;
642     c->callback = callback;
643     c->userctx = userctx;
644 
645     fileflags = fcntl(c->fd, F_GETFL, 0);
646     fcntl(c->fd, F_SETFL, fileflags | O_NONBLOCK);
647 
648 #ifdef HAVE_GCD
649     init_globals();
650 
651     c->in = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
652 				   c->fd, 0, eventq);
653     c->out = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE,
654 				    c->fd, 0, eventq);
655 
656     dispatch_source_set_event_handler(c->in, ^{
657 	    int rw = (c->flags & WAITING_WRITE);
658 	    handle_read(c);
659 	    if (rw == 0 && (c->flags & WAITING_WRITE))
660 		dispatch_resume(c->out);
661 	    if ((c->flags & WAITING_READ) == 0)
662 		dispatch_suspend(c->in);
663 	    maybe_close(c);
664 	});
665     dispatch_source_set_event_handler(c->out, ^{
666 	    handle_write(c);
667 	    if ((c->flags & WAITING_WRITE) == 0) {
668 		dispatch_suspend(c->out);
669 	    }
670 	    maybe_close(c);
671 	});
672 
673     dispatch_resume(c->in);
674 #else
675     clients = erealloc(clients, sizeof(clients[0]) * (num_clients + 1));
676     clients[num_clients] = c;
677     num_clients++;
678 #endif
679 
680     return c;
681 }
682 
683 static int
maybe_close(struct client * c)684 maybe_close(struct client *c)
685 {
686     if (c->calls != 0)
687 	return 0;
688     if (c->flags & (WAITING_READ|WAITING_WRITE))
689 	return 0;
690 
691 #ifdef HAVE_GCD
692     dispatch_source_cancel(c->in);
693     if ((c->flags & WAITING_READ) == 0)
694 	dispatch_resume(c->in);
695     dispatch_release(c->in);
696 
697     dispatch_source_cancel(c->out);
698     if ((c->flags & WAITING_WRITE) == 0)
699 	dispatch_resume(c->out);
700     dispatch_release(c->out);
701 #endif
702     close(c->fd); /* ref count fd close */
703     free(c);
704     return 1;
705 }
706 
707 
708 struct socket_call {
709     heim_idata in;
710     struct client *c;
711     heim_icred cred;
712 };
713 
714 static void
output_data(struct client * c,const void * data,size_t len)715 output_data(struct client *c, const void *data, size_t len)
716 {
717     if (c->olen + len < c->olen)
718 	abort();
719     c->outmsg = erealloc(c->outmsg, c->olen + len);
720     memcpy(&c->outmsg[c->olen], data, len);
721     c->olen += len;
722     c->flags |= WAITING_WRITE;
723 }
724 
725 static void
socket_complete(heim_sipc_call ctx,int returnvalue,heim_idata * reply)726 socket_complete(heim_sipc_call ctx, int returnvalue, heim_idata *reply)
727 {
728     struct socket_call *sc = (struct socket_call *)ctx;
729     struct client *c = sc->c;
730 
731     /* double complete ? */
732     if (c == NULL)
733 	abort();
734 
735     if ((c->flags & WAITING_CLOSE) == 0) {
736 	uint32_t u32;
737 
738 	/* length */
739 	u32 = htonl(reply->length);
740 	output_data(c, &u32, sizeof(u32));
741 
742 	/* return value */
743 	if (c->flags & INCLUDE_ERROR_CODE) {
744 	    u32 = htonl(returnvalue);
745 	    output_data(c, &u32, sizeof(u32));
746 	}
747 
748 	/* data */
749 	output_data(c, reply->data, reply->length);
750 
751 	/* if HTTP, close connection */
752 	if (c->flags & HTTP_REPLY) {
753 	    c->flags |= WAITING_CLOSE;
754 	    c->flags &= ~WAITING_READ;
755 	}
756     }
757 
758     c->calls--;
759     if (sc->cred)
760 	heim_ipc_free_cred(sc->cred);
761     free(sc->in.data);
762     sc->c = NULL; /* so we can catch double complete */
763     free(sc);
764 
765     maybe_close(c);
766 }
767 
768 /* remove HTTP %-quoting from buf */
769 static int
de_http(char * buf)770 de_http(char *buf)
771 {
772     unsigned char *p, *q;
773     for(p = q = (unsigned char *)buf; *p; p++, q++) {
774 	if(*p == '%' && isxdigit(p[1]) && isxdigit(p[2])) {
775 	    unsigned int x;
776 	    if(sscanf((char *)p + 1, "%2x", &x) != 1)
777 		return -1;
778 	    *q = x;
779 	    p += 2;
780 	} else
781 	    *q = *p;
782     }
783     *q = '\0';
784     return 0;
785 }
786 
787 static struct socket_call *
handle_http_tcp(struct client * c)788 handle_http_tcp(struct client *c)
789 {
790     struct socket_call *cs;
791     char *s, *p, *t;
792     void *data;
793     char *proto;
794     int len;
795 
796     s = (char *)c->inmsg;
797 
798     p = strstr(s, "\r\n");
799     if (p == NULL)
800 	return NULL;
801 
802     *p = 0;
803 
804     p = NULL;
805     t = strtok_r(s, " \t", &p);
806     if (t == NULL)
807 	return NULL;
808 
809     t = strtok_r(NULL, " \t", &p);
810     if (t == NULL)
811 	return NULL;
812 
813     data = malloc(strlen(t));
814     if (data == NULL)
815 	return NULL;
816 
817     if(*t == '/')
818 	t++;
819     if(de_http(t) != 0) {
820 	free(data);
821 	return NULL;
822     }
823     proto = strtok_r(NULL, " \t", &p);
824     if (proto == NULL) {
825 	free(data);
826 	return NULL;
827     }
828     len = rk_base64_decode(t, data);
829     if(len <= 0){
830 	const char *msg =
831 	    " 404 Not found\r\n"
832 	    "Server: Heimdal/" VERSION "\r\n"
833 	    "Cache-Control: no-cache\r\n"
834 	    "Pragma: no-cache\r\n"
835 	    "Content-type: text/html\r\n"
836 	    "Content-transfer-encoding: 8bit\r\n\r\n"
837 	    "<TITLE>404 Not found</TITLE>\r\n"
838 	    "<H1>404 Not found</H1>\r\n"
839 	    "That page doesn't exist, maybe you are looking for "
840 	    "<A HREF=\"http://www.h5l.org/\">Heimdal</A>?\r\n";
841 	free(data);
842 	output_data(c, proto, strlen(proto));
843 	output_data(c, msg, strlen(msg));
844 	return NULL;
845     }
846 
847     cs = emalloc(sizeof(*cs));
848     cs->c = c;
849     cs->in.data = data;
850     cs->in.length = len;
851     c->ptr = 0;
852 
853     {
854 	const char *msg =
855 	    " 200 OK\r\n"
856 	    "Server: Heimdal/" VERSION "\r\n"
857 	    "Cache-Control: no-cache\r\n"
858 	    "Pragma: no-cache\r\n"
859 	    "Content-type: application/octet-stream\r\n"
860 	    "Content-transfer-encoding: binary\r\n\r\n";
861 	output_data(c, proto, strlen(proto));
862 	output_data(c, msg, strlen(msg));
863     }
864 
865     return cs;
866 }
867 
868 
869 static void
handle_read(struct client * c)870 handle_read(struct client *c)
871 {
872     ssize_t len;
873     uint32_t dlen;
874 
875     if (c->flags & LISTEN_SOCKET) {
876 	add_new_socket(c->fd,
877 		       WAITING_READ | (c->flags & INHERIT_MASK),
878 		       c->callback,
879 		       c->userctx);
880 	return;
881     }
882 
883     if (c->ptr - c->len < 1024) {
884 	c->inmsg = erealloc(c->inmsg,
885 			    c->len + 1024);
886 	c->len += 1024;
887     }
888 
889     len = read(c->fd, c->inmsg + c->ptr, c->len - c->ptr);
890     if (len <= 0) {
891 	c->flags |= WAITING_CLOSE;
892 	c->flags &= ~WAITING_READ;
893 	return;
894     }
895     c->ptr += len;
896     if (c->ptr > c->len)
897 	abort();
898 
899     while (c->ptr >= sizeof(dlen)) {
900 	struct socket_call *cs;
901 
902 	if((c->flags & ALLOW_HTTP) && c->ptr >= 4 &&
903 	   strncmp((char *)c->inmsg, "GET ", 4) == 0 &&
904 	   strncmp((char *)c->inmsg + c->ptr - 4, "\r\n\r\n", 4) == 0) {
905 
906 	    /* remove the trailing \r\n\r\n so the string is NUL terminated */
907 	    c->inmsg[c->ptr - 4] = '\0';
908 
909 	    c->flags |= HTTP_REPLY;
910 
911 	    cs = handle_http_tcp(c);
912 	    if (cs == NULL) {
913 		c->flags |= WAITING_CLOSE;
914 		c->flags &= ~WAITING_READ;
915 		break;
916 	    }
917 	} else {
918 	    memcpy(&dlen, c->inmsg, sizeof(dlen));
919 	    dlen = ntohl(dlen);
920 
921 	    if (dlen > MAX_PACKET_SIZE) {
922 		c->flags |= WAITING_CLOSE;
923 		c->flags &= ~WAITING_READ;
924 		return;
925 	    }
926 	    if (dlen > c->ptr - sizeof(dlen)) {
927 		break;
928 	    }
929 
930 	    cs = emalloc(sizeof(*cs));
931 	    cs->c = c;
932 	    cs->in.data = emalloc(dlen);
933 	    memcpy(cs->in.data, c->inmsg + sizeof(dlen), dlen);
934 	    cs->in.length = dlen;
935 
936 	    c->ptr -= sizeof(dlen) + dlen;
937 	    memmove(c->inmsg,
938 		    c->inmsg + sizeof(dlen) + dlen,
939 		    c->ptr);
940 	}
941 
942 	c->calls++;
943 
944 	if ((c->flags & UNIX_SOCKET) != 0) {
945 	    if (update_client_creds(c))
946 		_heim_ipc_create_cred(c->unixrights.uid, c->unixrights.gid,
947 				      c->unixrights.pid, -1, &cs->cred);
948 	}
949 
950 	c->callback(c->userctx, &cs->in,
951 		    cs->cred, socket_complete,
952 		    (heim_sipc_call)cs);
953     }
954 }
955 
956 static void
handle_write(struct client * c)957 handle_write(struct client *c)
958 {
959     ssize_t len;
960 
961     len = write(c->fd, c->outmsg, c->olen);
962     if (len <= 0) {
963 	c->flags |= WAITING_CLOSE;
964 	c->flags &= ~(WAITING_WRITE);
965     } else if (c->olen != (size_t)len) {
966 	memmove(&c->outmsg[0], &c->outmsg[len], c->olen - len);
967 	c->olen -= len;
968     } else {
969 	c->olen = 0;
970 	free(c->outmsg);
971 	c->outmsg = NULL;
972 	c->flags &= ~(WAITING_WRITE);
973     }
974 }
975 
976 
977 #ifndef HAVE_GCD
978 
979 static void
process_loop(void)980 process_loop(void)
981 {
982     struct pollfd *fds;
983     unsigned n;
984     unsigned num_fds;
985 
986     while (num_clients > 0) {
987 
988 	fds = malloc(num_clients * sizeof(fds[0]));
989 	if(fds == NULL)
990 	    abort();
991 
992 	num_fds = num_clients;
993 
994 	for (n = 0 ; n < num_fds; n++) {
995 	    fds[n].fd = clients[n]->fd;
996 	    fds[n].events = 0;
997 	    if (clients[n]->flags & WAITING_READ)
998 		fds[n].events |= POLLIN;
999 	    if (clients[n]->flags & WAITING_WRITE)
1000 		fds[n].events |= POLLOUT;
1001 
1002 	    fds[n].revents = 0;
1003 	}
1004 
1005 	while (poll(fds, num_fds, -1) == -1) {
1006             if (errno == EINTR || errno == EAGAIN)
1007                 continue;
1008             err(1, "poll(2) failed");
1009         }
1010 
1011 	for (n = 0 ; n < num_fds; n++) {
1012 	    if (clients[n] == NULL)
1013 		continue;
1014 	    if (fds[n].revents & POLLERR) {
1015 		clients[n]->flags |= WAITING_CLOSE;
1016 		continue;
1017 	    }
1018 
1019 	    if (fds[n].revents & POLLIN)
1020 		handle_read(clients[n]);
1021 	    if (fds[n].revents & POLLOUT)
1022 		handle_write(clients[n]);
1023 	}
1024 
1025 	n = 0;
1026 	while (n < num_clients) {
1027 	    struct client *c = clients[n];
1028 	    if (maybe_close(c)) {
1029 		if (n < num_clients - 1)
1030 		    clients[n] = clients[num_clients - 1];
1031 		num_clients--;
1032 	    } else
1033 		n++;
1034 	}
1035 
1036 	free(fds);
1037     }
1038 }
1039 
1040 #endif
1041 
1042 static int
socket_release(heim_sipc ctx)1043 socket_release(heim_sipc ctx)
1044 {
1045     struct client *c = ctx->mech;
1046     c->flags |= WAITING_CLOSE;
1047     return 0;
1048 }
1049 
1050 int
heim_sipc_stream_listener(int fd,int type,heim_ipc_callback callback,void * user,heim_sipc * ctx)1051 heim_sipc_stream_listener(int fd, int type,
1052 			  heim_ipc_callback callback,
1053 			  void *user, heim_sipc *ctx)
1054 {
1055     heim_sipc ct = calloc(1, sizeof(*ct));
1056     struct client *c;
1057 
1058     if ((type & HEIM_SIPC_TYPE_IPC) && (type & (HEIM_SIPC_TYPE_UINT32|HEIM_SIPC_TYPE_HTTP)))
1059 	return EINVAL;
1060 
1061     switch (type) {
1062     case HEIM_SIPC_TYPE_IPC:
1063 	c = add_new_socket(fd, LISTEN_SOCKET|WAITING_READ|INCLUDE_ERROR_CODE, callback, user);
1064 	break;
1065     case HEIM_SIPC_TYPE_UINT32:
1066 	c = add_new_socket(fd, LISTEN_SOCKET|WAITING_READ, callback, user);
1067 	break;
1068     case HEIM_SIPC_TYPE_HTTP:
1069     case HEIM_SIPC_TYPE_UINT32|HEIM_SIPC_TYPE_HTTP:
1070 	c = add_new_socket(fd, LISTEN_SOCKET|WAITING_READ|ALLOW_HTTP, callback, user);
1071 	break;
1072     default:
1073 	free(ct);
1074 	return EINVAL;
1075     }
1076 
1077     ct->mech = c;
1078     ct->release = socket_release;
1079 
1080     c->unixrights.uid = (uid_t) -1;
1081     c->unixrights.gid = (gid_t) -1;
1082     c->unixrights.pid = (pid_t) 0;
1083 
1084     *ctx = ct;
1085     return 0;
1086 }
1087 
1088 int
heim_sipc_service_unix(const char * service,heim_ipc_callback callback,void * user,heim_sipc * ctx)1089 heim_sipc_service_unix(const char *service,
1090 		       heim_ipc_callback callback,
1091 		       void *user, heim_sipc *ctx)
1092 {
1093     struct sockaddr_un un;
1094     int fd, ret;
1095 
1096     un.sun_family = AF_UNIX;
1097 
1098     snprintf(un.sun_path, sizeof(un.sun_path),
1099 	     "/var/run/.heim_%s-socket", service);
1100     fd = socket(AF_UNIX, SOCK_STREAM, 0);
1101     if (fd < 0)
1102 	return errno;
1103 
1104     socket_set_reuseaddr(fd, 1);
1105 #ifdef LOCAL_CREDS
1106     {
1107 	int one = 1;
1108 	setsockopt(fd, 0, LOCAL_CREDS, (void *)&one, sizeof(one));
1109     }
1110 #endif
1111 
1112     unlink(un.sun_path);
1113 
1114     if (bind(fd, (struct sockaddr *)&un, sizeof(un)) < 0) {
1115 	close(fd);
1116 	return errno;
1117     }
1118 
1119     if (listen(fd, SOMAXCONN) < 0) {
1120 	close(fd);
1121 	return errno;
1122     }
1123 
1124     chmod(un.sun_path, 0666);
1125 
1126     ret = heim_sipc_stream_listener(fd, HEIM_SIPC_TYPE_IPC,
1127 				    callback, user, ctx);
1128     if (ret == 0) {
1129 	struct client *c = (*ctx)->mech;
1130 	c->flags |= UNIX_SOCKET;
1131     }
1132 
1133     return ret;
1134 }
1135 
1136 /**
1137  * Set the idle timeout value
1138 
1139  * The timeout event handler is triggered recurrently every idle
1140  * period `t'. The default action is rather draconian and just calls
1141  * exit(0), so you might want to change this to something more
1142  * graceful using heim_sipc_set_timeout_handler().
1143  */
1144 
1145 void
heim_sipc_timeout(time_t t)1146 heim_sipc_timeout(time_t t)
1147 {
1148 #ifdef HAVE_GCD
1149     static dispatch_once_t timeoutonce;
1150     init_globals();
1151     dispatch_sync(timerq, ^{
1152 	    timeoutvalue = t;
1153 	    set_timer();
1154 	});
1155     dispatch_once(&timeoutonce, ^{  dispatch_resume(timer); });
1156 #else
1157     abort();
1158 #endif
1159 }
1160 
1161 /**
1162  * Set the timeout event handler
1163  *
1164  * Replaces the default idle timeout action.
1165  */
1166 
1167 void
heim_sipc_set_timeout_handler(void (* func)(void))1168 heim_sipc_set_timeout_handler(void (*func)(void))
1169 {
1170 #ifdef HAVE_GCD
1171     init_globals();
1172     dispatch_sync(timerq, ^{ timer_ev = func; });
1173 #else
1174     abort();
1175 #endif
1176 }
1177 
1178 
1179 void
heim_sipc_free_context(heim_sipc ctx)1180 heim_sipc_free_context(heim_sipc ctx)
1181 {
1182     (ctx->release)(ctx);
1183 }
1184 
1185 void
heim_ipc_main(void)1186 heim_ipc_main(void)
1187 {
1188 #ifdef HAVE_GCD
1189     dispatch_main();
1190 #else
1191     process_loop();
1192 #endif
1193 }
1194 
1195