1 /* $OpenBSD: auth_subr.c,v 1.36 2009/01/15 13:14:30 millert Exp $ */ 2 3 /* 4 * Copyright (c) 2000-2002,2004 Todd C. Miller <Todd.Miller@courtesan.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 /*- 19 * Copyright (c) 1995,1996,1997 Berkeley Software Design, Inc. 20 * All rights reserved. 21 * 22 * Redistribution and use in source and binary forms, with or without 23 * modification, are permitted provided that the following conditions 24 * are met: 25 * 1. Redistributions of source code must retain the above copyright 26 * notice, this list of conditions and the following disclaimer. 27 * 2. Redistributions in binary form must reproduce the above copyright 28 * notice, this list of conditions and the following disclaimer in the 29 * documentation and/or other materials provided with the distribution. 30 * 3. All advertising materials mentioning features or use of this software 31 * must display the following acknowledgement: 32 * This product includes software developed by Berkeley Software Design, 33 * Inc. 34 * 4. The name of Berkeley Software Design, Inc. may not be used to endorse 35 * or promote products derived from this software without specific prior 36 * written permission. 37 * 38 * THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN, INC. ``AS IS'' AND 39 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 40 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 41 * ARE DISCLAIMED. IN NO EVENT SHALL BERKELEY SOFTWARE DESIGN, INC. BE LIABLE 42 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 43 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 44 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 45 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 46 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 47 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 48 * SUCH DAMAGE. 49 * 50 * BSDI $From: auth_subr.c,v 2.4 1999/09/08 04:10:40 prb Exp $ 51 */ 52 #include <sys/param.h> 53 #include <sys/time.h> 54 #include <sys/resource.h> 55 #include <sys/socket.h> 56 #include <sys/wait.h> 57 58 #include <ctype.h> 59 #include <err.h> 60 #include <errno.h> 61 #include <fcntl.h> 62 #include <paths.h> 63 #include <pwd.h> 64 #include <stdarg.h> 65 #include <stdio.h> 66 #include <stdlib.h> 67 #include <string.h> 68 #include <syslog.h> 69 #include <unistd.h> 70 71 #include <login_cap.h> 72 73 #define MAXSPOOLSIZE (8*1024) /* Spool up to 8K of back info */ 74 75 struct rmfiles { 76 struct rmfiles *next; 77 char *file; 78 }; 79 80 struct authopts { 81 struct authopts *next; 82 char *opt; 83 }; 84 85 struct authdata { 86 struct authdata *next; 87 void *ptr; 88 size_t len; 89 }; 90 91 struct auth_session_t { 92 char *name; /* name of use being authenticated */ 93 char *style; /* style of authentication used */ 94 char *class; /* class of user */ 95 char *service; /* type of service being performed */ 96 char *challenge; /* last challenge issued */ 97 int flags; /* see below */ 98 struct passwd *pwd; /* password entry for user */ 99 struct timeval now; /* time of authentication */ 100 101 int state; /* authenticated state */ 102 103 struct rmfiles *rmlist; /* list of files to remove on failure */ 104 struct authopts *optlist; /* list of options to scripts */ 105 struct authdata *data; /* additional data to send to scripts */ 106 107 char spool[MAXSPOOLSIZE]; /* data returned from login script */ 108 int index; /* how much returned thus far */ 109 110 int fd; /* connection to authenticator */ 111 112 va_list ap0; /* argument list to auth_call */ 113 va_list ap; /* additional arguments to auth_call */ 114 }; 115 116 /* 117 * Internal flags 118 */ 119 #define AF_INTERACTIVE 0x0001 /* This is an interactive session */ 120 121 /* 122 * We cannot include bsd_auth.h until we define the above structures 123 */ 124 #include <bsd_auth.h> 125 126 /* 127 * Internally used functions 128 */ 129 static void _add_rmlist(auth_session_t *, char *); 130 static void _auth_spool(auth_session_t *, int); 131 static void _recv_fd(auth_session_t *, int); 132 static char *_auth_next_arg(auth_session_t *); 133 /* 134 * Set up a known environment for all authentication scripts. 135 */ 136 static char *auth_environ[] = { 137 "PATH=" _PATH_DEFPATH, 138 "SHELL=" _PATH_BSHELL, 139 NULL, 140 }; 141 142 static char defservice[] = LOGIN_DEFSERVICE; 143 144 static va_list nilap; 145 146 /* 147 * Quick one liners that only exist to keep auth_session_t opaque 148 */ 149 void auth_setstate(auth_session_t *as, int s){ as->state = s; } 150 void auth_set_va_list(auth_session_t *as, va_list ap) { 151 #if defined(__GNUC__) && __GNUC__ >= 3 152 va_copy(as->ap, ap); 153 #else 154 as->ap = ap; 155 #endif 156 } 157 int auth_getstate(auth_session_t *as) { return (as->state); } 158 struct passwd *auth_getpwd(auth_session_t *as) { return (as->pwd); } 159 160 /* 161 * Open a new BSD Authentication session with the default service 162 * (which can be changed later). 163 */ 164 auth_session_t * 165 auth_open(void) 166 { 167 auth_session_t *as; 168 169 if ((as = malloc(sizeof(auth_session_t))) != NULL) { 170 memset(as, 0, sizeof(*as)); 171 as->service = defservice; 172 as->fd = -1; 173 } 174 175 return (as); 176 } 177 178 /* 179 * Clean the specified BSD Authentication session. 180 */ 181 void 182 auth_clean(auth_session_t *as) 183 { 184 struct rmfiles *rm; 185 struct authdata *data; 186 187 as->state = 0; 188 189 auth_clrenv(as); 190 191 /* 192 * Clean out the rmlist and remove specified files 193 */ 194 while ((rm = as->rmlist) != NULL) { 195 as->rmlist = rm->next; 196 unlink(rm->file); 197 free(rm); 198 } 199 200 /* 201 * Clean out data 202 */ 203 while ((data = as->data) != NULL) { 204 if (as->data->len) 205 memset(as->data->ptr, 0, as->data->len); 206 as->data = data->next; 207 free(data); 208 } 209 210 auth_setitem(as, AUTHV_ALL, NULL); 211 212 if (as->pwd != NULL) { 213 memset(as->pwd->pw_passwd, 0, strlen(as->pwd->pw_passwd)); 214 free(as->pwd); 215 as->pwd = NULL; 216 } 217 218 if (as->fd != -1) { 219 close(as->fd); 220 as->fd = -1; 221 } 222 } 223 224 /* 225 * Close the specified BSD Authentication session. 226 * Return 0 if not authenticated. 227 */ 228 int 229 auth_close(auth_session_t *as) 230 { 231 struct rmfiles *rm; 232 struct authopts *opt; 233 struct authdata *data; 234 int s; 235 236 /* 237 * Save our return value 238 */ 239 s = as->state & AUTH_ALLOW; 240 241 if (s == 0) 242 as->index = 0; 243 244 auth_setenv(as); 245 246 247 /* 248 * Clean out the rmlist and remove specified files if the 249 * authentication failed 250 */ 251 while ((rm = as->rmlist) != NULL) { 252 as->rmlist = rm->next; 253 if (s == 0) 254 unlink(rm->file); 255 free(rm); 256 } 257 258 /* 259 * Clean out the opt list 260 */ 261 while ((opt = as->optlist) != NULL) { 262 as->optlist = opt->next; 263 free(opt); 264 } 265 266 /* 267 * Clean out data 268 */ 269 while ((data = as->data) != NULL) { 270 if (as->data->len) 271 memset(as->data->ptr, 0, as->data->len); 272 as->data = data->next; 273 free(data); 274 } 275 276 if (as->pwd != NULL) { 277 memset(as->pwd->pw_passwd, 0, strlen(as->pwd->pw_passwd)); 278 free(as->pwd); 279 as->pwd = NULL; 280 } 281 282 /* 283 * Clean up random variables 284 */ 285 if (as->service && as->service != defservice) 286 free(as->service); 287 if (as->challenge) 288 free(as->challenge); 289 if (as->class) 290 free(as->class); 291 if (as->style) 292 free(as->style); 293 if (as->name) 294 free(as->name); 295 296 free(as); 297 return (s); 298 } 299 300 /* 301 * Request a challenge for the session. 302 * The name and style must have already been specified 303 */ 304 char * 305 auth_challenge(auth_session_t *as) 306 { 307 char path[MAXPATHLEN]; 308 int len; 309 310 if (as == NULL || as->style == NULL || as->name == NULL) 311 return (NULL); 312 313 len = snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", as->style); 314 if (len < 0 || len >= sizeof(path)) 315 return (NULL); 316 317 as->state = 0; 318 319 if (as->challenge) { 320 free(as->challenge); 321 as->challenge = NULL; 322 } 323 324 auth_call(as, path, as->style, "-s", "challenge", as->name, 325 as->class, (char *)NULL); 326 if (as->state & AUTH_CHALLENGE) 327 as->challenge = auth_getvalue(as, "challenge"); 328 as->state = 0; 329 as->index = 0; /* toss our data */ 330 return (as->challenge); 331 } 332 333 /* 334 * Set/unset the requested environment variables. 335 * Mark the variables as set so they will not be set a second time. 336 * XXX - should provide a way to detect setenv() failure. 337 */ 338 void 339 auth_setenv(auth_session_t *as) 340 { 341 char *line, *name; 342 343 /* 344 * Set any environment variables we were asked for 345 */ 346 for (line = as->spool; line < as->spool + as->index;) { 347 if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) { 348 if (isblank(line[sizeof(BI_SETENV) - 1])) { 349 /* only do it once! */ 350 line[0] = 'd'; line[1] = 'i'; line[2] = 'd'; 351 line += sizeof(BI_SETENV) - 1; 352 for (name = line; isblank(*name); ++name) 353 ; 354 for (line = name; *line && !isblank(*line); 355 ++line) 356 ; 357 if (*line) 358 *line++ = '\0'; 359 for (; isblank(*line); ++line) 360 ; 361 if (*line != '\0' && setenv(name, line, 1)) 362 _warn("setenv(%s, %s)", name, line); 363 } 364 } else 365 if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) { 366 if (isblank(line[sizeof(BI_UNSETENV) - 1])) { 367 /* only do it once! */ 368 line[2] = 'd'; line[3] = 'i'; line[4] = 'd'; 369 line += sizeof(BI_UNSETENV) - 1; 370 for (name = line; isblank(*name); ++name) 371 ; 372 for (line = name; *line && !isblank(*line); 373 ++line) 374 ; 375 if (*line) 376 *line++ = '\0'; 377 unsetenv(name); 378 } 379 } 380 while (*line++) 381 ; 382 } 383 } 384 385 /* 386 * Clear out any requested environment variables. 387 */ 388 void 389 auth_clrenv(auth_session_t *as) 390 { 391 char *line; 392 393 for (line = as->spool; line < as->spool + as->index;) { 394 if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) { 395 if (isblank(line[sizeof(BI_SETENV) - 1])) { 396 line[0] = 'i'; line[1] = 'g'; line[2] = 'n'; 397 } 398 } else 399 if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) { 400 if (isblank(line[sizeof(BI_UNSETENV) - 1])) { 401 line[2] = 'i'; line[3] = 'g'; line[4] = 'n'; 402 } 403 } 404 while (*line++) 405 ; 406 } 407 } 408 409 char * 410 auth_getitem(auth_session_t *as, auth_item_t item) 411 { 412 if (as != NULL) { 413 switch (item) { 414 case AUTHV_CHALLENGE: 415 return (as->challenge); 416 case AUTHV_CLASS: 417 return (as->class); 418 case AUTHV_NAME: 419 return (as->name); 420 case AUTHV_SERVICE: 421 return (as->service ? as->service : defservice); 422 case AUTHV_STYLE: 423 return (as->style); 424 case AUTHV_INTERACTIVE: 425 return ((as->flags & AF_INTERACTIVE) ? "True" : NULL); 426 default: 427 break; 428 } 429 } 430 return (NULL); 431 } 432 433 int 434 auth_setitem(auth_session_t *as, auth_item_t item, char *value) 435 { 436 if (as == NULL) { 437 errno = EINVAL; 438 return (-1); 439 } 440 441 switch (item) { 442 case AUTHV_ALL: 443 if (value != NULL) { 444 errno = EINVAL; 445 return (-1); 446 } 447 auth_setitem(as, AUTHV_CHALLENGE, NULL); 448 auth_setitem(as, AUTHV_CLASS, NULL); 449 auth_setitem(as, AUTHV_NAME, NULL); 450 auth_setitem(as, AUTHV_SERVICE, NULL); 451 auth_setitem(as, AUTHV_STYLE, NULL); 452 auth_setitem(as, AUTHV_INTERACTIVE, NULL); 453 return (0); 454 455 case AUTHV_CHALLENGE: 456 if (value == as->challenge) 457 return (0); 458 if (value != NULL && (value = strdup(value)) == NULL) 459 return (-1); 460 if (as->challenge) 461 free(as->challenge); 462 as->challenge = value; 463 return (0); 464 465 case AUTHV_CLASS: 466 if (value == as->class) 467 return (0); 468 if (value != NULL && (value = strdup(value)) == NULL) 469 return (-1); 470 if (as->class) 471 free(as->class); 472 as->class = value; 473 return (0); 474 475 case AUTHV_NAME: 476 if (value == as->name) 477 return (0); 478 if (value != NULL && (value = strdup(value)) == NULL) 479 return (-1); 480 if (as->name) 481 free(as->name); 482 as->name = value; 483 return (0); 484 485 case AUTHV_SERVICE: 486 if (value == as->service) 487 return (0); 488 if (value == NULL || strcmp(value, defservice) == 0) 489 value = defservice; 490 else if ((value = strdup(value)) == NULL) 491 return (-1); 492 if (as->service && as->service != defservice) 493 free(as->service); 494 as->service = value; 495 return (0); 496 497 case AUTHV_STYLE: 498 if (value == as->style) 499 return (0); 500 if (value == NULL || strchr(value, '/') != NULL || 501 (value = strdup(value)) == NULL) 502 return (-1); 503 if (as->style) 504 free(as->style); 505 as->style = value; 506 return (0); 507 508 case AUTHV_INTERACTIVE: 509 if (value == NULL) 510 as->flags &= ~AF_INTERACTIVE; 511 else 512 as->flags |= ~AF_INTERACTIVE; 513 return (0); 514 515 default: 516 errno = EINVAL; 517 return (-1); 518 } 519 } 520 521 int 522 auth_setoption(auth_session_t *as, char *n, char *v) 523 { 524 struct authopts *opt; 525 size_t len = strlen(n) + strlen(v) + 2; 526 int ret; 527 528 if ((opt = malloc(sizeof(*opt) + len)) == NULL) 529 return (-1); 530 531 opt->opt = (char *)(opt + 1); 532 533 ret = snprintf(opt->opt, len, "%s=%s", n, v); 534 if (ret < 0 || ret >= len) { 535 free(opt); 536 errno = ENAMETOOLONG; 537 return (-1); 538 } 539 opt->next = as->optlist; 540 as->optlist = opt; 541 return(0); 542 } 543 544 void 545 auth_clroptions(auth_session_t *as) 546 { 547 struct authopts *opt; 548 549 while ((opt = as->optlist) != NULL) { 550 as->optlist = opt->next; 551 free(opt); 552 } 553 } 554 555 void 556 auth_clroption(auth_session_t *as, char *option) 557 { 558 struct authopts *opt, *oopt; 559 int len; 560 561 len = strlen(option); 562 563 if ((opt = as->optlist) == NULL) 564 return; 565 566 if (strncmp(opt->opt, option, len) == 0 && 567 (opt->opt[len] == '=' || opt->opt[len] == '\0')) { 568 as->optlist = opt->next; 569 free(opt); 570 return; 571 } 572 573 while ((oopt = opt->next) != NULL) { 574 if (strncmp(oopt->opt, option, len) == 0 && 575 (oopt->opt[len] == '=' || oopt->opt[len] == '\0')) { 576 opt->next = oopt->next; 577 free(oopt); 578 return; 579 } 580 opt = oopt; 581 } 582 } 583 584 int 585 auth_setdata(auth_session_t *as, void *ptr, size_t len) 586 { 587 struct authdata *data, *dp; 588 589 if (len <= 0) 590 return (0); 591 592 if ((data = malloc(sizeof(*data) + len)) == NULL) 593 return (-1); 594 595 data->next = NULL; 596 data->len = len; 597 data->ptr = data + 1; 598 memcpy(data->ptr, ptr, len); 599 600 if (as->data == NULL) 601 as->data = data; 602 else { 603 for (dp = as->data; dp->next != NULL; dp = dp->next) 604 ; 605 dp->next = data; 606 } 607 return (0); 608 } 609 610 int 611 auth_setpwd(auth_session_t *as, struct passwd *pwd) 612 { 613 char *instance; 614 615 if (pwd == NULL && as->pwd == NULL && as->name == NULL) 616 return (-1); /* true failure */ 617 618 if (pwd == NULL) { 619 /* 620 * If we were not passed in a pwd structure we need to 621 * go find one for ourself. Always look up the username 622 * (if it is defined) in the passwd database to see if there 623 * is an entry for the user. If not, either use the current 624 * entry or simply return a 1 which implies there is 625 * no user by that name here. This is not a failure, just 626 * a point of information. 627 */ 628 if (as->name == NULL) 629 return (0); 630 if ((pwd = getpwnam(as->name)) == NULL) { 631 instance = strchr(as->name, '/'); 632 if (instance == NULL) 633 return (as->pwd ? 0 : 1); 634 if (strcmp(instance, "/root") == 0) 635 pwd = getpwnam(instance + 1); 636 if (pwd == NULL) 637 return (as->pwd ? 0 : 1); 638 } 639 } 640 if ((pwd = pw_dup(pwd)) == NULL) 641 return (-1); /* true failure */ 642 if (as->pwd) { 643 memset(as->pwd->pw_passwd, 0, strlen(as->pwd->pw_passwd)); 644 free(as->pwd); 645 } 646 as->pwd = pwd; 647 return (0); 648 } 649 650 char * 651 auth_getvalue(auth_session_t *as, char *what) 652 { 653 char *line, *v, *value; 654 int n, len; 655 656 len = strlen(what); 657 658 for (line = as->spool; line < as->spool + as->index;) { 659 if (strncasecmp(line, BI_VALUE, sizeof(BI_VALUE)-1) != 0) 660 goto next; 661 line += sizeof(BI_VALUE) - 1; 662 663 if (!isblank(*line)) 664 goto next; 665 666 while (isblank(*++line)) 667 ; 668 669 if (strncmp(line, what, len) != 0 || 670 !isblank(line[len])) 671 goto next; 672 line += len; 673 while (isblank(*++line)) 674 ; 675 value = strdup(line); 676 if (value == NULL) 677 return (NULL); 678 679 /* 680 * XXX - There should be a more standardized 681 * routine for doing this sort of thing. 682 */ 683 for (line = v = value; *line; ++line) { 684 if (*line == '\\') { 685 switch (*++line) { 686 case 'r': 687 *v++ = '\r'; 688 break; 689 case 'n': 690 *v++ = '\n'; 691 break; 692 case 't': 693 *v++ = '\t'; 694 break; 695 case '0': case '1': case '2': 696 case '3': case '4': case '5': 697 case '6': case '7': 698 n = *line - '0'; 699 if (isdigit(line[1])) { 700 ++line; 701 n <<= 3; 702 n |= *line-'0'; 703 } 704 if (isdigit(line[1])) { 705 ++line; 706 n <<= 3; 707 n |= *line-'0'; 708 } 709 break; 710 default: 711 *v++ = *line; 712 break; 713 } 714 } else 715 *v++ = *line; 716 } 717 *v = '\0'; 718 return (value); 719 next: 720 while (*line++) 721 ; 722 } 723 return (NULL); 724 } 725 726 quad_t 727 auth_check_expire(auth_session_t *as) 728 { 729 if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) { 730 as->state &= ~AUTH_ALLOW; 731 as->state |= AUTH_EXPIRED; /* XXX */ 732 return (-1); 733 } 734 735 if (as->pwd == NULL) 736 return (0); 737 738 if (as->pwd && (quad_t)as->pwd->pw_expire != 0) { 739 if (as->now.tv_sec == 0) 740 gettimeofday(&as->now, (struct timezone *)NULL); 741 if ((quad_t)as->now.tv_sec >= (quad_t)as->pwd->pw_expire) { 742 as->state &= ~AUTH_ALLOW; 743 as->state |= AUTH_EXPIRED; 744 } 745 if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_expire) 746 return (-1); 747 return ((quad_t)as->pwd->pw_expire - (quad_t)as->now.tv_sec); 748 } 749 return (0); 750 } 751 752 quad_t 753 auth_check_change(auth_session_t *as) 754 { 755 if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) { 756 as->state &= ~AUTH_ALLOW; 757 as->state |= AUTH_PWEXPIRED; /* XXX */ 758 return (-1); 759 } 760 761 if (as->pwd == NULL) 762 return (0); 763 764 if (as->pwd && (quad_t)as->pwd->pw_change) { 765 if (as->now.tv_sec == 0) 766 gettimeofday(&as->now, (struct timezone *)NULL); 767 if (as->now.tv_sec >= (quad_t)as->pwd->pw_change) { 768 as->state &= ~AUTH_ALLOW; 769 as->state |= AUTH_PWEXPIRED; 770 } 771 if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_change) 772 return (-1); 773 return ((quad_t)as->pwd->pw_change - (quad_t)as->now.tv_sec); 774 } 775 return (0); 776 } 777 778 /* 779 * The down and dirty call to the login script 780 * okay contains the default return value, typically 0 but 781 * is AUTH_OKAY for approval like scripts. 782 * 783 * Internally additional trailing arguments can be read from as->ap 784 * Options will be placed just after the first argument (not including path). 785 * 786 * Any data will be sent to (and freed by) the script 787 */ 788 int 789 auth_call(auth_session_t *as, char *path, ...) 790 { 791 char *line; 792 struct authdata *data; 793 struct authopts *opt; 794 pid_t pid; 795 int status; 796 int okay; 797 int pfd[2]; 798 int argc; 799 char *argv[64]; /* 64 args should be more than enough */ 800 #define Nargc (sizeof(argv)/sizeof(argv[0])) 801 802 va_start(as->ap0, path); 803 804 argc = 0; 805 if ((argv[argc] = _auth_next_arg(as)) != NULL) 806 ++argc; 807 808 if (as->fd != -1) { 809 argv[argc++] = "-v"; 810 argv[argc++] = "fd=4"; /* AUTH_FD, see below */ 811 } 812 for (opt = as->optlist; opt != NULL; opt = opt->next) { 813 if (argc < Nargc - 2) { 814 argv[argc++] = "-v"; 815 argv[argc++] = opt->opt; 816 } else { 817 syslog(LOG_ERR, "too many authentication options"); 818 goto fail; 819 } 820 } 821 while (argc < Nargc - 1 && (argv[argc] = _auth_next_arg(as))) 822 ++argc; 823 824 if (argc >= Nargc - 1 && _auth_next_arg(as)) { 825 if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) { 826 va_end(as->ap0); 827 memset(&(as->ap0), 0, sizeof(as->ap0)); 828 } 829 if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) { 830 va_end(as->ap); 831 memset(&(as->ap), 0, sizeof(as->ap)); 832 } 833 syslog(LOG_ERR, "too many arguments"); 834 goto fail; 835 } 836 837 argv[argc] = NULL; 838 839 if (secure_path(path) < 0) { 840 syslog(LOG_ERR, "%s: path not secure", path); 841 _warnx("invalid script: %s", path); 842 goto fail; 843 } 844 845 if (socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd) < 0) { 846 syslog(LOG_ERR, "unable to create backchannel %m"); 847 _warnx("internal resource failure"); 848 goto fail; 849 } 850 851 switch (pid = fork()) { 852 case -1: 853 syslog(LOG_ERR, "%s: %m", path); 854 _warnx("internal resource failure"); 855 close(pfd[0]); 856 close(pfd[1]); 857 goto fail; 858 case 0: 859 #define COMM_FD 3 860 #define AUTH_FD 4 861 if (dup2(pfd[1], COMM_FD) < 0) 862 err(1, "dup of backchannel"); 863 if (as->fd != -1) { 864 if (dup2(as->fd, AUTH_FD) < 0) 865 err(1, "dup of auth fd"); 866 closefrom(AUTH_FD + 1); 867 } else 868 closefrom(COMM_FD + 1); 869 execve(path, argv, auth_environ); 870 syslog(LOG_ERR, "%s: %m", path); 871 err(1, "%s", path); 872 default: 873 close(pfd[1]); 874 if (as->fd != -1) { 875 close(as->fd); /* so child has only ref */ 876 as->fd = -1; 877 } 878 while ((data = as->data) != NULL) { 879 as->data = data->next; 880 if (data->len > 0) { 881 write(pfd[0], data->ptr, data->len); 882 memset(data->ptr, 0, data->len); 883 } 884 free(data); 885 } 886 as->index = 0; 887 _auth_spool(as, pfd[0]); 888 close(pfd[0]); 889 status = 0; 890 while (waitpid(pid, &status, 0) == -1 && errno == EINTR) 891 ; 892 if (pid < 0) { 893 if (errno != ECHILD) { 894 syslog(LOG_ERR, "%s: waitpid: %m", path); 895 _warnx("internal failure"); 896 goto fail; 897 } 898 } else if (!WIFEXITED(status)) 899 goto fail; 900 } 901 902 /* 903 * Now scan the spooled data 904 * It is easier to wait for all the data before starting 905 * to scan it. 906 */ 907 for (line = as->spool; line < as->spool + as->index;) { 908 if (!strncasecmp(line, BI_REJECT, sizeof(BI_REJECT)-1)) { 909 line += sizeof(BI_REJECT) - 1; 910 if (!*line || *line == ' ' || *line == '\t') { 911 while (*line == ' ' || *line == '\t') 912 ++line; 913 if (!strcasecmp(line, "silent")) { 914 as->state = AUTH_SILENT; 915 break; 916 } 917 if (!strcasecmp(line, "challenge")) { 918 as->state = AUTH_CHALLENGE; 919 break; 920 } 921 if (!strcasecmp(line, "expired")) { 922 as->state = AUTH_EXPIRED; 923 break; 924 } 925 if (!strcasecmp(line, "pwexpired")) { 926 as->state = AUTH_PWEXPIRED; 927 break; 928 } 929 } 930 break; 931 } else if (!strncasecmp(line, BI_AUTH, sizeof(BI_AUTH)-1)) { 932 line += sizeof(BI_AUTH) - 1; 933 if (!*line || *line == ' ' || *line == '\t') { 934 while (*line == ' ' || *line == '\t') 935 ++line; 936 if (*line == '\0') 937 as->state |= AUTH_OKAY; 938 else if (!strcasecmp(line, "root")) 939 as->state |= AUTH_ROOTOKAY; 940 else if (!strcasecmp(line, "secure")) 941 as->state |= AUTH_SECURE; 942 } 943 } else if (!strncasecmp(line, BI_REMOVE, sizeof(BI_REMOVE)-1)) { 944 line += sizeof(BI_REMOVE) - 1; 945 while (*line == ' ' || *line == '\t') 946 ++line; 947 if (*line) 948 _add_rmlist(as, line); 949 } 950 while (*line++) 951 ; 952 } 953 954 if (WEXITSTATUS(status)) 955 as->state &= ~AUTH_ALLOW; 956 957 okay = as->state & AUTH_ALLOW; 958 959 if (!okay) 960 auth_clrenv(as); 961 962 if (0) { 963 fail: 964 auth_clrenv(as); 965 as->state = 0; 966 okay = -1; 967 } 968 969 while ((data = as->data) != NULL) { 970 as->data = data->next; 971 free(data); 972 } 973 974 if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) { 975 va_end(as->ap0); 976 memset(&(as->ap0), 0, sizeof(as->ap0)); 977 } 978 979 if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) { 980 va_end(as->ap); 981 memset(&(as->ap), 0, sizeof(as->ap)); 982 } 983 return (okay); 984 } 985 986 static void 987 _recv_fd(auth_session_t *as, int fd) 988 { 989 struct msghdr msg; 990 struct cmsghdr *cmp; 991 union { 992 struct cmsghdr hdr; 993 char buf[CMSG_SPACE(sizeof(int))]; 994 } cmsgbuf; 995 996 memset(&msg, 0, sizeof(msg)); 997 msg.msg_control = &cmsgbuf.buf; 998 msg.msg_controllen = sizeof(cmsgbuf.buf); 999 if (recvmsg(fd, &msg, 0) < 0) 1000 syslog(LOG_ERR, "recvmsg: %m"); 1001 else if (msg.msg_flags & MSG_TRUNC) 1002 syslog(LOG_ERR, "message truncated"); 1003 else if (msg.msg_flags & MSG_CTRUNC) 1004 syslog(LOG_ERR, "control message truncated"); 1005 else if ((cmp = CMSG_FIRSTHDR(&msg)) == NULL) 1006 syslog(LOG_ERR, "missing control message"); 1007 else { 1008 if (cmp->cmsg_level != SOL_SOCKET) 1009 syslog(LOG_ERR, "unexpected cmsg_level %d", 1010 cmp->cmsg_level); 1011 else if (cmp->cmsg_type != SCM_RIGHTS) 1012 syslog(LOG_ERR, "unexpected cmsg_type %d", 1013 cmp->cmsg_type); 1014 else if (cmp->cmsg_len != CMSG_LEN(sizeof(int))) 1015 syslog(LOG_ERR, "bad cmsg_len %d", 1016 cmp->cmsg_len); 1017 else { 1018 if (as->fd != -1) 1019 close(as->fd); 1020 as->fd = *(int *)CMSG_DATA(cmp); 1021 } 1022 } 1023 } 1024 1025 static void 1026 _auth_spool(auth_session_t *as, int fd) 1027 { 1028 ssize_t r; 1029 char *b, *s; 1030 1031 for (s = as->spool + as->index; as->index < sizeof(as->spool) - 1; ) { 1032 r = read(fd, as->spool + as->index, 1033 sizeof(as->spool) - as->index); 1034 if (r <= 0) { 1035 as->spool[as->index] = '\0'; 1036 return; 1037 } 1038 b = as->spool + as->index; 1039 as->index += r; 1040 /* 1041 * Convert newlines into NULs to allow easy scanning of the 1042 * file and receive an fd if there is a BI_FDPASS message. 1043 * XXX - checking for BI_FDPASS here is annoying but 1044 * we need to avoid the read() slurping in control data. 1045 */ 1046 while (r-- > 0) { 1047 if (*b++ == '\n') { 1048 b[-1] = '\0'; 1049 if (strcasecmp(s, BI_FDPASS) == 0) 1050 _recv_fd(as, fd); 1051 s = b; 1052 } 1053 } 1054 } 1055 1056 syslog(LOG_ERR, "Overflowed backchannel spool buffer"); 1057 errx(1, "System error in authentication program"); 1058 } 1059 1060 static void 1061 _add_rmlist(auth_session_t *as, char *file) 1062 { 1063 struct rmfiles *rm; 1064 int i = strlen(file) + 1; 1065 1066 if ((rm = malloc(sizeof(struct rmfiles) + i)) == NULL) { 1067 syslog(LOG_ERR, "Failed to allocate rmfiles: %m"); 1068 return; 1069 } 1070 rm->file = (char *)(rm + 1); 1071 rm->next = as->rmlist; 1072 strlcpy(rm->file, file, i); 1073 as->rmlist = rm; 1074 } 1075 1076 static char * 1077 _auth_next_arg(auth_session_t *as) 1078 { 1079 char *arg; 1080 1081 if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) { 1082 if ((arg = va_arg(as->ap0, char *)) != NULL) 1083 return (arg); 1084 va_end(as->ap0); 1085 memset(&(as->ap0), 0, sizeof(as->ap0)); 1086 } 1087 if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) { 1088 if ((arg = va_arg(as->ap, char *)) != NULL) 1089 return (arg); 1090 va_end(as->ap); 1091 memset(&(as->ap), 0, sizeof(as->ap)); 1092 } 1093 return (NULL); 1094 } 1095