]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - crypto/heimdal/kcm/connect.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / crypto / heimdal / kcm / connect.c
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