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
41struct 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
54static dispatch_source_t timer;
55static dispatch_queue_t timerq;
56static uint64_t timeoutvalue;
57
58static dispatch_queue_t eventq;
59
60static dispatch_queue_t workq;
61
62static void
63default_timer_ev(void)
64{
65    exit(0);
66}
67
68static void (*timer_ev)(void) = default_timer_ev;
69
70static void
71set_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
79static void
80init_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
93static void
94suspend_timer(void)
95{
96    dispatch_suspend(timer);
97}
98
99static void
100restart_timer(void)
101{
102    dispatch_sync(timerq, ^{ set_timer(); });
103    dispatch_resume(timer);
104}
105
106struct mach_service {
107    mach_port_t sport;
108    dispatch_source_t source;
109    dispatch_queue_t queue;
110};
111
112struct mach_call_ctx {
113    mach_port_t reply_port;
114    heim_icred cred;
115    heim_idata req;
116};
117
118
119static void
120mach_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
156static void
157mach_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
193kern_return_t
194mheim_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
253kern_return_t
254mheim_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
304static int
305mach_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
354static int
355mach_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
363static mach_port_t
364mach_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
402int
403heim_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
448struct 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
481static unsigned num_clients = 0;
482static struct client **clients = NULL;
483#endif
484
485static void handle_read(struct client *);
486static void handle_write(struct client *);
487static 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
497static int
498update_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 = 0;
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
615static struct client *
616add_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
680static int
681maybe_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
705struct socket_call {
706    heim_idata in;
707    struct client *c;
708    heim_icred cred;
709};
710
711static void
712output_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
722static void
723socket_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 */
766static int
767de_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
784static struct socket_call *
785handle_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
866static void
867handle_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
953static void
954handle_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
976static void
977process_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
1035static int
1036socket_release(heim_sipc ctx)
1037{
1038    struct client *c = ctx->mech;
1039    c->flags |= WAITING_CLOSE;
1040    return 0;
1041}
1042
1043int
1044heim_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
1081int
1082heim_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
1138void
1139heim_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
1160void
1161heim_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
1172void
1173heim_sipc_free_context(heim_sipc ctx)
1174{
1175    (ctx->release)(ctx);
1176}
1177
1178void
1179heim_ipc_main(void)
1180{
1181#ifdef HAVE_GCD
1182    dispatch_main();
1183#else
1184    process_loop();
1185#endif
1186}
1187
1188