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