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