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
36RCSID("$Id: connect.c 16314 2005-11-29 19:03:50Z lha $");
37
38struct 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
52static void
53init_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
64static void
65reinit_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
81static int
82update_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
204static void
205init_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
269static int
270init_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
297static int
298process_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
333static void
334do_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
387static void
388clear_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
404static void
405add_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
439static int
440grow_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
470static void
471handle_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
537static void
538kcm_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
576static void
577kcm_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
605void
606kcm_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