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