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