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