If client tried IPv6, but service only listened on IPv4
[heimdal.git] / kcm / connect.c
blob9c85d8fce2244abefa6a057250c97d814fbb9495
1 /*
2 * Copyright (c) 1997-2005 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
34 #include "kcm_locl.h"
36 RCSID("$Id$");
38 struct descr {
39 int s;
40 int type;
41 char *path;
42 unsigned char *buf;
43 size_t size;
44 size_t len;
45 time_t timeout;
46 struct sockaddr_storage __ss;
47 struct sockaddr *sa;
48 socklen_t sock_len;
49 kcm_client peercred;
52 static void
53 init_descr(struct descr *d)
55 memset(d, 0, sizeof(*d));
56 d->sa = (struct sockaddr *)&d->__ss;
57 d->s = -1;
61 * re-initialize all `n' ->sa in `d'.
64 static void
65 reinit_descrs (struct descr *d, int n)
67 int i;
69 for (i = 0; i < n; ++i)
70 d[i].sa = (struct sockaddr *)&d[i].__ss;
74 * Update peer credentials from socket.
76 * SCM_CREDS can only be updated the first time there is read data to
77 * read from the filedescriptor, so if we read do it before this
78 * point, the cred data might not be is not there yet.
81 static int
82 update_client_creds(int s, kcm_client *peer)
84 #ifdef GETPEERUCRED
85 /* Solaris 10 */
87 ucred_t *peercred;
89 if (getpeerucred(s, &peercred) != 0) {
90 peer->uid = ucred_geteuid(peercred);
91 peer->gid = ucred_getegid(peercred);
92 peer->pid = 0;
93 ucred_free(peercred);
94 return 0;
97 #endif
98 #ifdef GETPEEREID
99 /* FreeBSD, OpenBSD */
101 uid_t uid;
102 gid_t gid;
104 if (getpeereid(s, &uid, &gid) == 0) {
105 peer->uid = uid;
106 peer->gid = gid;
107 peer->pid = 0;
108 return 0;
111 #endif
112 #ifdef SO_PEERCRED
113 /* Linux */
115 struct ucred pc;
116 socklen_t pclen = sizeof(pc);
118 if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, (void *)&pc, &pclen) == 0) {
119 peer->uid = pc.uid;
120 peer->gid = pc.gid;
121 peer->pid = pc.pid;
122 return 0;
125 #endif
126 #if defined(LOCAL_PEERCRED) && defined(XUCRED_VERSION)
128 struct xucred peercred;
129 socklen_t peercredlen = sizeof(peercred);
131 if (getsockopt(s, LOCAL_PEERCRED, 1,
132 (void *)&peercred, &peercredlen) == 0
133 && peercred.cr_version == XUCRED_VERSION)
135 peer->uid = peercred.cr_uid;
136 peer->gid = peercred.cr_gid;
137 peer->pid = 0;
138 return 0;
141 #endif
142 #if defined(SOCKCREDSIZE) && defined(SCM_CREDS)
143 /* NetBSD */
144 if (peer->uid == -1) {
145 struct msghdr msg;
146 socklen_t crmsgsize;
147 void *crmsg;
148 struct cmsghdr *cmp;
149 struct sockcred *sc;
151 memset(&msg, 0, sizeof(msg));
152 crmsgsize = CMSG_SPACE(SOCKCREDSIZE(NGROUPS));
153 if (crmsgsize == 0)
154 return 1 ;
156 crmsg = malloc(crmsgsize);
157 if (crmsg == NULL)
158 goto failed_scm_creds;
160 memset(crmsg, 0, crmsgsize);
162 msg.msg_control = crmsg;
163 msg.msg_controllen = crmsgsize;
165 if (recvmsg(s, &msg, 0) < 0) {
166 free(crmsg);
167 goto failed_scm_creds;
170 if (msg.msg_controllen == 0 || (msg.msg_flags & MSG_CTRUNC) != 0) {
171 free(crmsg);
172 goto failed_scm_creds;
175 cmp = CMSG_FIRSTHDR(&msg);
176 if (cmp->cmsg_level != SOL_SOCKET || cmp->cmsg_type != SCM_CREDS) {
177 free(crmsg);
178 goto failed_scm_creds;
181 sc = (struct sockcred *)(void *)CMSG_DATA(cmp);
183 peer->uid = sc->sc_euid;
184 peer->gid = sc->sc_egid;
185 peer->pid = 0;
187 free(crmsg);
188 return 0;
189 } else {
190 /* we already got the cred, just return it */
191 return 0;
193 failed_scm_creds:
194 #endif
195 krb5_warn(kcm_context, errno, "failed to determine peer identity");
196 return 1;
201 * Create the socket (family, type, port) in `d'
204 static void
205 init_socket(struct descr *d)
207 struct sockaddr_un un;
208 struct sockaddr *sa = (struct sockaddr *)&un;
209 krb5_socklen_t sa_size = sizeof(un);
211 init_descr (d);
213 un.sun_family = AF_UNIX;
215 if (socket_path != NULL)
216 d->path = socket_path;
217 else
218 d->path = _PATH_KCM_SOCKET;
220 strlcpy(un.sun_path, d->path, sizeof(un.sun_path));
222 d->s = socket(AF_UNIX, SOCK_STREAM, 0);
223 if (d->s < 0){
224 krb5_warn(kcm_context, errno, "socket(%d, %d, 0)", AF_UNIX, SOCK_STREAM);
225 d->s = -1;
226 return;
228 #if defined(HAVE_SETSOCKOPT) && defined(SOL_SOCKET) && defined(SO_REUSEADDR)
230 int one = 1;
231 setsockopt(d->s, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
233 #endif
234 #ifdef LOCAL_CREDS
236 int one = 1;
237 setsockopt(d->s, 0, LOCAL_CREDS, (void *)&one, sizeof(one));
239 #endif
241 d->type = SOCK_STREAM;
243 unlink(d->path);
245 if (bind(d->s, sa, sa_size) < 0) {
246 krb5_warn(kcm_context, errno, "bind %s", un.sun_path);
247 close(d->s);
248 d->s = -1;
249 return;
252 if (listen(d->s, SOMAXCONN) < 0) {
253 krb5_warn(kcm_context, errno, "listen %s", un.sun_path);
254 close(d->s);
255 d->s = -1;
256 return;
259 chmod(d->path, 0777);
261 return;
265 * Allocate descriptors for all the sockets that we should listen on
266 * and return the number of them.
269 static int
270 init_sockets(struct descr **desc)
272 struct descr *d;
273 size_t num = 0;
275 d = (struct descr *)malloc(sizeof(*d));
276 if (d == NULL) {
277 krb5_errx(kcm_context, 1, "malloc failed");
280 init_socket(d);
281 if (d->s != -1) {
282 kcm_log(5, "listening on domain socket %s", d->path);
283 num++;
286 reinit_descrs (d, num);
287 *desc = d;
289 return num;
293 * handle the request in `buf, len', from `addr' (or `from' as a string),
294 * sending a reply in `reply'.
297 static int
298 process_request(unsigned char *buf,
299 size_t len,
300 krb5_data *reply,
301 kcm_client *client)
303 krb5_data request;
305 if (len < 4) {
306 kcm_log(1, "malformed request from process %d (too short)",
307 client->pid);
308 return -1;
311 if (buf[0] != KCM_PROTOCOL_VERSION_MAJOR ||
312 buf[1] != KCM_PROTOCOL_VERSION_MINOR) {
313 kcm_log(1, "incorrect protocol version %d.%d from process %d",
314 buf[0], buf[1], client->pid);
315 return -1;
318 buf += 2;
319 len -= 2;
321 /* buf is now pointing at opcode */
323 request.data = buf;
324 request.length = len;
326 return kcm_dispatch(kcm_context, client, &request, reply);
330 * Handle the request in `buf, len' to socket `d'
333 static void
334 do_request(void *buf, size_t len, struct descr *d)
336 krb5_error_code ret;
337 krb5_data reply;
339 reply.length = 0;
341 ret = process_request(buf, len, &reply, &d->peercred);
342 if (reply.length != 0) {
343 unsigned char len[4];
344 struct msghdr msghdr;
345 struct iovec iov[2];
347 kcm_log(5, "sending %lu bytes to process %d",
348 (unsigned long)reply.length,
349 (int)d->peercred.pid);
351 memset (&msghdr, 0, sizeof(msghdr));
352 msghdr.msg_name = NULL;
353 msghdr.msg_namelen = 0;
354 msghdr.msg_iov = iov;
355 msghdr.msg_iovlen = sizeof(iov)/sizeof(*iov);
356 #if 0
357 msghdr.msg_control = NULL;
358 msghdr.msg_controllen = 0;
359 #endif
361 len[0] = (reply.length >> 24) & 0xff;
362 len[1] = (reply.length >> 16) & 0xff;
363 len[2] = (reply.length >> 8) & 0xff;
364 len[3] = reply.length & 0xff;
366 iov[0].iov_base = (void*)len;
367 iov[0].iov_len = 4;
368 iov[1].iov_base = reply.data;
369 iov[1].iov_len = reply.length;
371 if (sendmsg (d->s, &msghdr, 0) < 0) {
372 kcm_log (0, "sendmsg(%d): %d %s", (int)d->peercred.pid,
373 errno, strerror(errno));
374 krb5_data_free(&reply);
375 return;
378 krb5_data_free(&reply);
381 if (ret) {
382 kcm_log(0, "Failed processing %lu byte request from process %d",
383 (unsigned long)len, d->peercred.pid);
387 static void
388 clear_descr(struct descr *d)
390 if(d->buf)
391 memset(d->buf, 0, d->size);
392 d->len = 0;
393 if(d->s != -1)
394 close(d->s);
395 d->s = -1;
398 #define STREAM_TIMEOUT 4
401 * accept a new stream connection on `d[parent]' and store it in `d[child]'
404 static void
405 add_new_stream (struct descr *d, int parent, int child)
407 int s;
409 if (child == -1)
410 return;
412 d[child].peercred.pid = -1;
413 d[child].peercred.uid = -1;
414 d[child].peercred.gid = -1;
416 d[child].sock_len = sizeof(d[child].__ss);
417 s = accept(d[parent].s, d[child].sa, &d[child].sock_len);
418 if(s < 0) {
419 krb5_warn(kcm_context, errno, "accept");
420 return;
423 if (s >= FD_SETSIZE) {
424 krb5_warnx(kcm_context, "socket FD too large");
425 close (s);
426 return;
429 d[child].s = s;
430 d[child].timeout = time(NULL) + STREAM_TIMEOUT;
431 d[child].type = SOCK_STREAM;
435 * Grow `d' to handle at least `n'.
436 * Return != 0 if fails
439 static int
440 grow_descr (struct descr *d, size_t n)
442 if (d->size - d->len < n) {
443 unsigned char *tmp;
444 size_t grow;
446 grow = max(1024, d->len + n);
447 if (d->size + grow > max_request) {
448 kcm_log(0, "Request exceeds max request size (%lu bytes).",
449 (unsigned long)d->size + grow);
450 clear_descr(d);
451 return -1;
453 tmp = realloc (d->buf, d->size + grow);
454 if (tmp == NULL) {
455 kcm_log(0, "Failed to re-allocate %lu bytes.",
456 (unsigned long)d->size + grow);
457 clear_descr(d);
458 return -1;
460 d->size += grow;
461 d->buf = tmp;
463 return 0;
467 * Handle incoming data to the stream socket in `d[index]'
470 static void
471 handle_stream(struct descr *d, int index, int min_free)
473 unsigned char buf[1024];
474 int n;
475 int ret = 0;
477 if (d[index].timeout == 0) {
478 add_new_stream (d, index, min_free);
479 return;
482 if (update_client_creds(d[index].s, &d[index].peercred)) {
483 krb5_warnx(kcm_context, "failed to update peer identity");
484 clear_descr(d + index);
485 return;
488 if (d[index].peercred.uid == -1) {
489 krb5_warnx(kcm_context, "failed to determine peer identity");
490 clear_descr (d + index);
491 return;
494 n = recvfrom(d[index].s, buf, sizeof(buf), 0, NULL, NULL);
495 if (n < 0) {
496 krb5_warn(kcm_context, errno, "recvfrom");
497 return;
498 } else if (n == 0) {
499 krb5_warnx(kcm_context, "connection closed before end of data "
500 "after %lu bytes from process %ld",
501 (unsigned long) d[index].len, (long) d[index].peercred.pid);
502 clear_descr (d + index);
503 return;
505 if (grow_descr (&d[index], n))
506 return;
507 memcpy(d[index].buf + d[index].len, buf, n);
508 d[index].len += n;
509 if (d[index].len > 4) {
510 krb5_storage *sp;
511 int32_t len;
513 sp = krb5_storage_from_mem(d[index].buf, d[index].len);
514 if (sp == NULL) {
515 kcm_log (0, "krb5_storage_from_mem failed");
516 ret = -1;
517 } else {
518 krb5_ret_int32(sp, &len);
519 krb5_storage_free(sp);
520 if (d[index].len - 4 >= len) {
521 memmove(d[index].buf, d[index].buf + 4, d[index].len - 4);
522 ret = 1;
523 } else
524 ret = 0;
527 if (ret < 0)
528 return;
529 else if (ret == 1) {
530 do_request(d[index].buf, d[index].len, &d[index]);
531 clear_descr(d + index);
535 #ifdef HAVE_DOOR_CREATE
537 static void
538 kcm_door_server(void *cookie, char *argp, size_t arg_size,
539 door_desc_t *dp, uint_t n_desc)
541 kcm_client peercred;
542 door_cred_t cred;
543 krb5_error_code ret;
544 krb5_data reply;
545 size_t length;
546 char *p;
548 reply.length = 0;
550 p = NULL;
551 length = 0;
553 if (door_cred(&cred) != 0) {
554 kcm_log(0, "door_cred failed with %s", strerror(errno));
555 goto out;
558 peercred.uid = cred.dc_euid;
559 peercred.gid = cred.dc_egid;
560 peercred.pid = cred.dc_pid;
562 ret = process_request((unsigned char*)argp, arg_size, &reply, &peercred);
563 if (reply.length != 0) {
564 p = alloca(reply.length); /* XXX don't use alloca */
565 if (p) {
566 memcpy(p, reply.data, reply.length);
567 length = reply.length;
569 krb5_data_free(&reply);
572 out:
573 door_return(p, length, NULL, 0);
576 static void
577 kcm_setup_door(void)
579 int fd, ret;
580 char *path;
582 fd = door_create(kcm_door_server, NULL, 0);
583 if (fd < 0)
584 krb5_err(kcm_context, 1, errno, "Failed to create door");
586 if (door_path != NULL)
587 path = door_path;
588 else
589 path = _PATH_KCM_DOOR;
591 unlink(path);
592 ret = open(path, O_RDWR | O_CREAT, 0666);
593 if (ret < 0)
594 krb5_err(kcm_context, 1, errno, "Failed to create/open door");
595 close(ret);
597 ret = fattach(fd, path);
598 if (ret < 0)
599 krb5_err(kcm_context, 1, errno, "Failed to attach door");
602 #endif /* HAVE_DOOR_CREATE */
605 void
606 kcm_loop(void)
608 struct descr *d;
609 unsigned int ndescr;
611 #ifdef HAVE_DOOR_CREATE
612 kcm_setup_door();
613 #endif
615 ndescr = init_sockets(&d);
616 if (ndescr <= 0) {
617 krb5_warnx(kcm_context, "No sockets!");
618 #ifndef HAVE_DOOR_CREATE
619 exit(1);
620 #endif
622 while (exit_flag == 0){
623 struct timeval tmout;
624 fd_set fds;
625 int min_free = -1;
626 int max_fd = 0;
627 int i;
629 FD_ZERO(&fds);
630 for(i = 0; i < ndescr; i++) {
631 if (d[i].s >= 0){
632 if(d[i].type == SOCK_STREAM &&
633 d[i].timeout && d[i].timeout < time(NULL)) {
634 kcm_log(1, "Stream connection from %d expired after %lu bytes",
635 d[i].peercred.pid, (unsigned long)d[i].len);
636 clear_descr(&d[i]);
637 continue;
639 if (max_fd < d[i].s)
640 max_fd = d[i].s;
641 if (max_fd >= FD_SETSIZE)
642 krb5_errx(kcm_context, 1, "fd too large");
643 FD_SET(d[i].s, &fds);
644 } else if (min_free < 0 || i < min_free)
645 min_free = i;
647 if (min_free == -1) {
648 struct descr *tmp;
649 tmp = realloc(d, (ndescr + 4) * sizeof(*d));
650 if(tmp == NULL)
651 krb5_warnx(kcm_context, "No memory");
652 else {
653 d = tmp;
654 reinit_descrs (d, ndescr);
655 memset(d + ndescr, 0, 4 * sizeof(*d));
656 for(i = ndescr; i < ndescr + 4; i++)
657 init_descr (&d[i]);
658 min_free = ndescr;
659 ndescr += 4;
663 tmout.tv_sec = STREAM_TIMEOUT;
664 tmout.tv_usec = 0;
665 switch (select(max_fd + 1, &fds, 0, 0, &tmout)){
666 case 0:
667 kcm_run_events(kcm_context, time(NULL));
668 break;
669 case -1:
670 if (errno != EINTR)
671 krb5_warn(kcm_context, errno, "select");
672 break;
673 default:
674 for(i = 0; i < ndescr; i++) {
675 if(d[i].s >= 0 && FD_ISSET(d[i].s, &fds)) {
676 if (d[i].type == SOCK_STREAM)
677 handle_stream(d, i, min_free);
680 kcm_run_events(kcm_context, time(NULL));
681 break;
684 if (d->path != NULL)
685 unlink(d->path);
686 free(d);