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
default_timer_ev(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
set_timer(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
init_globals(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
suspend_timer(void)94 suspend_timer(void)
95 {
96 dispatch_suspend(timer);
97 }
98
99 static void
restart_timer(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
mach_complete_sync(heim_sipc_call ctx,int returnvalue,heim_idata * reply)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
mach_complete_async(heim_sipc_call ctx,int returnvalue,heim_idata * reply)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
mheim_do_call(mach_port_t server_port,audit_token_t client_creds,mach_port_t reply_port,heim_ipc_message_inband_t requestin,mach_msg_type_number_t requestinCnt,heim_ipc_message_outband_t requestout,mach_msg_type_number_t requestoutCnt,int * returnvalue,heim_ipc_message_inband_t replyin,mach_msg_type_number_t * replyinCnt,heim_ipc_message_outband_t * replyout,mach_msg_type_number_t * replyoutCnt)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
mheim_do_call_request(mach_port_t server_port,audit_token_t client_creds,mach_port_t reply_port,heim_ipc_message_inband_t requestin,mach_msg_type_number_t requestinCnt,heim_ipc_message_outband_t requestout,mach_msg_type_number_t requestoutCnt)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
mach_init(const char * service,mach_port_t sport,heim_sipc ctx)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
mach_release(heim_sipc ctx)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
mach_checkin_or_register(const char * service)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
heim_sipc_launchd_mach_init(const char * service,heim_ipc_callback callback,void * user,heim_sipc * ctx)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
update_client_creds(struct client * c)498 update_client_creds(struct client *c)
499 {
500 #ifdef HAVE_GETPEERUCRED
501 /* Solaris 10 */
502 {
503 ucred_t *peercred;
504
505 if (getpeerucred(c->fd, &peercred) != 0) {
506 c->unixrights.uid = ucred_geteuid(peercred);
507 c->unixrights.gid = ucred_getegid(peercred);
508 c->unixrights.pid = 0;
509 ucred_free(peercred);
510 return 1;
511 }
512 }
513 #endif
514 #ifdef HAVE_GETPEEREID
515 /* FreeBSD, OpenBSD */
516 {
517 uid_t uid;
518 gid_t gid;
519
520 if (getpeereid(c->fd, &uid, &gid) == 0) {
521 c->unixrights.uid = uid;
522 c->unixrights.gid = gid;
523 c->unixrights.pid = 0;
524 return 1;
525 }
526 }
527 #endif
528 #ifdef SO_PEERCRED
529 /* Linux */
530 {
531 struct ucred pc;
532 socklen_t pclen = sizeof(pc);
533
534 if (getsockopt(c->fd, SOL_SOCKET, SO_PEERCRED, (void *)&pc, &pclen) == 0) {
535 c->unixrights.uid = pc.uid;
536 c->unixrights.gid = pc.gid;
537 c->unixrights.pid = pc.pid;
538 return 1;
539 }
540 }
541 #endif
542 #if defined(LOCAL_PEERCRED) && defined(XUCRED_VERSION)
543 {
544 struct xucred peercred;
545 socklen_t peercredlen = sizeof(peercred);
546
547 if (getsockopt(c->fd, LOCAL_PEERCRED, 1,
548 (void *)&peercred, &peercredlen) == 0
549 && peercred.cr_version == XUCRED_VERSION)
550 {
551 c->unixrights.uid = peercred.cr_uid;
552 c->unixrights.gid = peercred.cr_gid;
553 c->unixrights.pid = peercred.cr_pid;
554 return 1;
555 }
556 }
557 #endif
558 #if defined(SOCKCREDSIZE) && defined(SCM_CREDS)
559 /* NetBSD */
560 if (c->unixrights.uid == (uid_t)-1) {
561 struct msghdr msg;
562 socklen_t crmsgsize;
563 void *crmsg;
564 struct cmsghdr *cmp;
565 struct sockcred *sc;
566
567 memset(&msg, 0, sizeof(msg));
568 crmsgsize = CMSG_SPACE(SOCKCREDSIZE(CMGROUP_MAX));
569 if (crmsgsize == 0)
570 return 1 ;
571
572 crmsg = malloc(crmsgsize);
573 if (crmsg == NULL)
574 goto failed_scm_creds;
575
576 memset(crmsg, 0, crmsgsize);
577
578 msg.msg_control = crmsg;
579 msg.msg_controllen = crmsgsize;
580
581 if (recvmsg(c->fd, &msg, 0) < 0) {
582 free(crmsg);
583 goto failed_scm_creds;
584 }
585
586 if (msg.msg_controllen == 0 || (msg.msg_flags & MSG_CTRUNC) != 0) {
587 free(crmsg);
588 goto failed_scm_creds;
589 }
590
591 cmp = CMSG_FIRSTHDR(&msg);
592 if (cmp->cmsg_level != SOL_SOCKET || cmp->cmsg_type != SCM_CREDS) {
593 free(crmsg);
594 goto failed_scm_creds;
595 }
596
597 sc = (struct sockcred *)(void *)CMSG_DATA(cmp);
598
599 c->unixrights.uid = sc->sc_euid;
600 c->unixrights.gid = sc->sc_egid;
601 c->unixrights.pid = 0;
602
603 free(crmsg);
604 return 1;
605 } else {
606 /* we already got the cred, just return it */
607 return 1;
608 }
609 failed_scm_creds:
610 #endif
611 return 0;
612 }
613
614
615 static struct client *
add_new_socket(int fd,int flags,heim_ipc_callback callback,void * userctx)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
maybe_close(struct client * c)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
output_data(struct client * c,const void * data,size_t len)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
socket_complete(heim_sipc_call ctx,int returnvalue,heim_idata * reply)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
de_http(char * buf)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 *
handle_http_tcp(struct client * c)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
handle_read(struct client * c)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
handle_write(struct client * c)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
process_loop(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
socket_release(heim_sipc ctx)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
heim_sipc_stream_listener(int fd,int type,heim_ipc_callback callback,void * user,heim_sipc * ctx)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
heim_sipc_service_unix(const char * service,heim_ipc_callback callback,void * user,heim_sipc * ctx)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
heim_sipc_timeout(time_t t)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
heim_sipc_set_timeout_handler(void (* func)(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
heim_sipc_free_context(heim_sipc ctx)1173 heim_sipc_free_context(heim_sipc ctx)
1174 {
1175 (ctx->release)(ctx);
1176 }
1177
1178 void
heim_ipc_main(void)1179 heim_ipc_main(void)
1180 {
1181 #ifdef HAVE_GCD
1182 dispatch_main();
1183 #else
1184 process_loop();
1185 #endif
1186 }
1187
1188