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