xref: /freebsd/crypto/heimdal/kcm/connect.c (revision ad30f8e79bd1007cc2476e491bd21b4f5e389e0a)
1 /*
2  * Copyright (c) 1997-2005 Kungliga Tekniska H�gskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
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.
16  *
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.
20  *
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.
32  */
33 
34 #include "kcm_locl.h"
35 
36 RCSID("$Id: connect.c 16314 2005-11-29 19:03:50Z lha $");
37 
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;
50 };
51 
52 static void
53 init_descr(struct descr *d)
54 {
55     memset(d, 0, sizeof(*d));
56     d->sa = (struct sockaddr *)&d->__ss;
57     d->s = -1;
58 }
59 
60 /*
61  * re-initialize all `n' ->sa in `d'.
62  */
63 
64 static void
65 reinit_descrs (struct descr *d, int n)
66 {
67     int i;
68 
69     for (i = 0; i < n; ++i)
70 	d[i].sa = (struct sockaddr *)&d[i].__ss;
71 }
72 
73 /*
74  * Update peer credentials from socket.
75  *
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.
79  */
80 
81 static int
82 update_client_creds(int s, kcm_client *peer)
83 {
84 #ifdef GETPEERUCRED
85     /* Solaris 10 */
86     {
87 	ucred_t *peercred;
88 
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;
95 	}
96     }
97 #endif
98 #ifdef GETPEEREID
99     /* FreeBSD, OpenBSD */
100     {
101 	uid_t uid;
102 	gid_t gid;
103 
104 	if (getpeereid(s, &uid, &gid) == 0) {
105 	    peer->uid = uid;
106 	    peer->gid = gid;
107 	    peer->pid = 0;
108 	    return 0;
109 	}
110     }
111 #endif
112 #ifdef SO_PEERCRED
113     /* Linux */
114     {
115 	struct ucred pc;
116 	socklen_t pclen = sizeof(pc);
117 
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;
123 	}
124     }
125 #endif
126 #if defined(LOCAL_PEERCRED) && defined(XUCRED_VERSION)
127     {
128 	struct xucred peercred;
129 	socklen_t peercredlen = sizeof(peercred);
130 
131 	if (getsockopt(s, LOCAL_PEERCRED, 1,
132 		       (void *)&peercred, &peercredlen) == 0
133 	    && peercred.cr_version == XUCRED_VERSION)
134 	{
135 	    peer->uid = peercred.cr_uid;
136 	    peer->gid = peercred.cr_gid;
137 	    peer->pid = 0;
138 	    return 0;
139 	}
140     }
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;
150 
151 	memset(&msg, 0, sizeof(msg));
152 	crmsgsize = CMSG_SPACE(SOCKCREDSIZE(CMGROUP_MAX));
153 	if (crmsgsize == 0)
154 	    return 1 ;
155 
156 	crmsg = malloc(crmsgsize);
157 	if (crmsg == NULL)
158 	    goto failed_scm_creds;
159 
160 	memset(crmsg, 0, crmsgsize);
161 
162 	msg.msg_control = crmsg;
163 	msg.msg_controllen = crmsgsize;
164 
165 	if (recvmsg(s, &msg, 0) < 0) {
166 	    free(crmsg);
167 	    goto failed_scm_creds;
168 	}
169 
170 	if (msg.msg_controllen == 0 || (msg.msg_flags & MSG_CTRUNC) != 0) {
171 	    free(crmsg);
172 	    goto failed_scm_creds;
173 	}
174 
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;
179 	}
180 
181 	sc = (struct sockcred *)(void *)CMSG_DATA(cmp);
182 
183 	peer->uid = sc->sc_euid;
184 	peer->gid = sc->sc_egid;
185 	peer->pid = 0;
186 
187 	free(crmsg);
188 	return 0;
189     } else {
190 	/* we already got the cred, just return it */
191 	return 0;
192     }
193  failed_scm_creds:
194 #endif
195     krb5_warn(kcm_context, errno, "failed to determine peer identity");
196     return 1;
197 }
198 
199 
200 /*
201  * Create the socket (family, type, port) in `d'
202  */
203 
204 static void
205 init_socket(struct descr *d)
206 {
207     struct sockaddr_un un;
208     struct sockaddr *sa = (struct sockaddr *)&un;
209     krb5_socklen_t sa_size = sizeof(un);
210 
211     init_descr (d);
212 
213     un.sun_family = AF_UNIX;
214 
215     if (socket_path != NULL)
216 	d->path = socket_path;
217     else
218 	d->path = _PATH_KCM_SOCKET;
219 
220     strlcpy(un.sun_path, d->path, sizeof(un.sun_path));
221 
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;
227     }
228 #if defined(HAVE_SETSOCKOPT) && defined(SOL_SOCKET) && defined(SO_REUSEADDR)
229     {
230 	int one = 1;
231 	setsockopt(d->s, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
232     }
233 #endif
234 #ifdef LOCAL_CREDS
235     {
236 	int one = 1;
237 	setsockopt(d->s, 0, LOCAL_CREDS, (void *)&one, sizeof(one));
238     }
239 #endif
240 
241     d->type = SOCK_STREAM;
242 
243     unlink(d->path);
244 
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;
250     }
251 
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;
257     }
258 
259     chmod(d->path, 0777);
260 
261     return;
262 }
263 
264 /*
265  * Allocate descriptors for all the sockets that we should listen on
266  * and return the number of them.
267  */
268 
269 static int
270 init_sockets(struct descr **desc)
271 {
272     struct descr *d;
273     size_t num = 0;
274 
275     d = (struct descr *)malloc(sizeof(*d));
276     if (d == NULL) {
277 	krb5_errx(kcm_context, 1, "malloc failed");
278     }
279 
280     init_socket(d);
281     if (d->s != -1) {
282 	kcm_log(5, "listening on domain socket %s", d->path);
283 	num++;
284     }
285 
286     reinit_descrs (d, num);
287     *desc = d;
288 
289     return num;
290 }
291 
292 /*
293  * handle the request in `buf, len', from `addr' (or `from' as a string),
294  * sending a reply in `reply'.
295  */
296 
297 static int
298 process_request(unsigned char *buf,
299 		size_t len,
300 		krb5_data *reply,
301 		kcm_client *client)
302 {
303     krb5_data request;
304 
305     if (len < 4) {
306 	kcm_log(1, "malformed request from process %d (too short)",
307 		client->pid);
308 	return -1;
309     }
310 
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;
316     }
317 
318     buf += 2;
319     len -= 2;
320 
321     /* buf is now pointing at opcode */
322 
323     request.data = buf;
324     request.length = len;
325 
326     return kcm_dispatch(kcm_context, client, &request, reply);
327 }
328 
329 /*
330  * Handle the request in `buf, len' to socket `d'
331  */
332 
333 static void
334 do_request(void *buf, size_t len, struct descr *d)
335 {
336     krb5_error_code ret;
337     krb5_data reply;
338 
339     reply.length = 0;
340 
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];
346 
347 	kcm_log(5, "sending %lu bytes to process %d",
348 		(unsigned long)reply.length,
349 		(int)d->peercred.pid);
350 
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
360 
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;
365 
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;
370 
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;
376 	}
377 
378 	krb5_data_free(&reply);
379     }
380 
381     if (ret) {
382 	kcm_log(0, "Failed processing %lu byte request from process %d",
383 		(unsigned long)len, d->peercred.pid);
384     }
385 }
386 
387 static void
388 clear_descr(struct descr *d)
389 {
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;
396 }
397 
398 #define STREAM_TIMEOUT 4
399 
400 /*
401  * accept a new stream connection on `d[parent]' and store it in `d[child]'
402  */
403 
404 static void
405 add_new_stream (struct descr *d, int parent, int child)
406 {
407     int s;
408 
409     if (child == -1)
410 	return;
411 
412     d[child].peercred.pid = -1;
413     d[child].peercred.uid = -1;
414     d[child].peercred.gid = -1;
415 
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;
421     }
422 
423     if (s >= FD_SETSIZE) {
424 	krb5_warnx(kcm_context, "socket FD too large");
425 	close (s);
426 	return;
427     }
428 
429     d[child].s = s;
430     d[child].timeout = time(NULL) + STREAM_TIMEOUT;
431     d[child].type = SOCK_STREAM;
432 }
433 
434 /*
435  * Grow `d' to handle at least `n'.
436  * Return != 0 if fails
437  */
438 
439 static int
440 grow_descr (struct descr *d, size_t n)
441 {
442     if (d->size - d->len < n) {
443 	unsigned char *tmp;
444 	size_t grow;
445 
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;
452 	}
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;
459 	}
460 	d->size += grow;
461 	d->buf = tmp;
462     }
463     return 0;
464 }
465 
466 /*
467  * Handle incoming data to the stream socket in `d[index]'
468  */
469 
470 static void
471 handle_stream(struct descr *d, int index, int min_free)
472 {
473     unsigned char buf[1024];
474     int n;
475     int ret = 0;
476 
477     if (d[index].timeout == 0) {
478 	add_new_stream (d, index, min_free);
479 	return;
480     }
481 
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;
486     }
487 
488     if (d[index].peercred.uid == -1) {
489 	krb5_warnx(kcm_context, "failed to determine peer identity");
490 	clear_descr (d + index);
491 	return;
492     }
493 
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;
504     }
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;
512 
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;
525 	}
526     }
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);
532     }
533 }
534 
535 #ifdef HAVE_DOOR_CREATE
536 
537 static void
538 kcm_door_server(void  *cookie, char *argp, size_t arg_size,
539 		door_desc_t *dp, uint_t n_desc)
540 {
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;
547 
548     reply.length = 0;
549 
550     p = NULL;
551     length = 0;
552 
553     if (door_cred(&cred) != 0) {
554 	kcm_log(0, "door_cred failed with %s", strerror(errno));
555 	goto out;
556     }
557 
558     peercred.uid = cred.dc_euid;
559     peercred.gid = cred.dc_egid;
560     peercred.pid = cred.dc_pid;
561 
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;
568 	}
569 	krb5_data_free(&reply);
570     }
571 
572  out:
573     door_return(p, length, NULL, 0);
574 }
575 
576 static void
577 kcm_setup_door(void)
578 {
579     int fd, ret;
580     char *path;
581 
582     fd = door_create(kcm_door_server, NULL, 0);
583     if (fd < 0)
584 	krb5_err(kcm_context, 1, errno, "Failed to create door");
585 
586     if (door_path != NULL)
587 	path = door_path;
588     else
589 	path = _PATH_KCM_DOOR;
590 
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);
596 
597     ret = fattach(fd, path);
598     if (ret < 0)
599 	krb5_err(kcm_context, 1, errno, "Failed to attach door");
600 
601 }
602 #endif /* HAVE_DOOR_CREATE */
603 
604 
605 void
606 kcm_loop(void)
607 {
608     struct descr *d;
609     int ndescr;
610 
611 #ifdef HAVE_DOOR_CREATE
612     kcm_setup_door();
613 #endif
614 
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
621     }
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;
628 
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;
638 		}
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;
646 	}
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;
660 	    }
661 	}
662 
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);
678 		}
679 	    }
680 	    kcm_run_events(kcm_context, time(NULL));
681 	    break;
682 	}
683     }
684     if (d->path != NULL)
685 	unlink(d->path);
686     free(d);
687 }
688 
689