login_auth.c revision 25670
1/*- 2 * Copyright (c) 1996 by 3 * Sean Eric Fagan <sef@kithrup.com> 4 * David Nugent <davidn@blaze.net.au> 5 * All rights reserved. 6 * 7 * Portions copyright (c) 1995,1997 by 8 * Berkeley Software Design, Inc. 9 * All rights reserved. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, is permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice immediately at the beginning of the file, without modification, 16 * this list of conditions, and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 3. This work was done expressly for inclusion into FreeBSD. Other use 21 * is permitted provided this notation is included. 22 * 4. Absolutely no warranty of function or purpose is made by the authors. 23 * 5. Modifications may be freely made to this file providing the above 24 * conditions are met. 25 * 26 * Low-level routines relating to the user capabilities database 27 * 28 * $Id: login_auth.c,v 1.6 1997/02/22 15:08:18 peter Exp $ 29 */ 30 31#include <sys/types.h> 32#include <sys/time.h> 33#include <sys/resource.h> 34#include <sys/stat.h> 35#include <sys/param.h> 36#include <errno.h> 37#include <fcntl.h> 38#include <limits.h> 39#include <stdio.h> 40#include <ctype.h> 41#include <pwd.h> 42#include <stdlib.h> 43#include <string.h> 44#include <syslog.h> 45#include <unistd.h> 46#include <login_cap.h> 47#include <stdarg.h> 48#include <paths.h> 49#include <sys/socket.h> 50#include <sys/wait.h> 51#include <err.h> 52#include <libutil.h> 53 54#ifdef LOGIN_CAP_AUTH 55/* 56 * Comment from BSDI's authenticate.c module: 57 * NOTE: THIS MODULE IS TO BE DEPRECATED. FUTURE VERSIONS OF BSD/OS WILL 58 * HAVE AN UPDATED API, THOUGH THESE FUNCTIONS WILL CONTINUE TO BE AVAILABLE 59 * FOR BACKWARDS COMPATABILITY 60 */ 61 62 63#define AUTHMAXSPOOL (8 * 1024) /* Max size of authentication data */ 64#define AUTHCOMM_FD 3 /* Handle used to read/write auth data */ 65 66struct rmfiles { 67 struct rmfiles *next; 68 char file[1]; 69}; 70 71struct authopts { 72 struct authopts *next; 73 char opt[1]; 74}; 75 76static char *spoolbuf = NULL; 77static int spoolidx = 0; 78static struct rmfiles *rmfirst = NULL; 79static struct authopts *optfirst = NULL; 80 81 82/* 83 * Setup a known environment for all authentication scripts. 84 */ 85 86static char *auth_environ[] = { 87 "PATH=" _PATH_DEFPATH, 88 "SHELL=" _PATH_BSHELL, 89 NULL, 90}; 91 92 93 94/* 95 * nextline() 96 * Get the next line from the data buffer collected from 97 * the authentication program. This function relies on the 98 * fact that lines are nul terminated. 99 */ 100 101static char * 102nextline(int *idx) 103{ 104 char *ptr = NULL; 105 106 if (spoolbuf != NULL && *idx < spoolidx) { 107 ptr = spoolbuf + *idx; 108 *idx += strlen(ptr) + 1; 109 } 110 return ptr; 111} 112 113 114/* 115 * spooldata() 116 * Read data returned on authentication backchannel and 117 * stuff it into our spool buffer. We also replace \n with nul 118 * to make parsing easier later. 119 */ 120 121static int 122spooldata(int fd) 123{ 124 125 if (spoolbuf) 126 free(spoolbuf); 127 spoolidx = 0; 128 129 if (spoolbuf == NULL && (spoolbuf = malloc(AUTHMAXSPOOL)) == NULL) 130 syslog(LOG_ERR, "authbuffer malloc: %m"); 131 132 else while (spoolidx < sizeof(spoolbuf) - 1) { 133 int r = read(fd, spoolbuf + spoolidx, sizeof(spoolbuf)-spoolidx); 134 char *b; 135 136 if (r <= 0) { 137 spoolbuf[spoolidx] = '\0'; 138 return 0; 139 } 140 /* 141 * Convert newlines into NULs to allow 142 * easier scanning of the file. 143 */ 144 while ((b = memchr(spoolbuf + spoolidx, '\n', r)) != NULL) 145 *b = '\0'; 146 spoolidx += r; 147 } 148 return -1; 149} 150 151 152/* 153 * auth_check() 154 * Starts an auth_script() for the given <user>, with a class <class>, 155 * style <style>, and service <service>. <style> is necessary, 156 * as are <user> and <class>, but <service> is optional -- it defaults 157 * to "login". 158 * Since auth_script() expects an execl'able program name, authenticate() 159 * also concatenates <style> to _PATH_AUTHPROG. 160 * Lastly, calls auth_scan(0) to see if there are any "reject" statements, 161 * or lack of "auth" statements. 162 * Returns -1 on error, 0 on rejection, and >0 on success. 163 * (See AUTH_* for the return values.) 164 * 165 */ 166 167int 168auth_check(const char *name, const char *clss, const char *style, 169 const char *service, int *status) 170{ 171 int _status; 172 173 if (status == NULL) 174 status = &_status; 175 *status = 0; 176 177 if (style != NULL) { 178 char path[MAXPATHLEN]; 179 180 if (service == NULL) 181 service = LOGIN_DEFSERVICE; 182 183 snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", style); 184 if (auth_script(path, style, "-s", service, name, clss, 0)) 185 status = 0; 186 else 187 *status = auth_scan(0); 188 189 return *status & AUTH_ALLOW; 190 } 191 return -1; 192} 193 194 195int 196auth_response(const char *name, const char *class, const char *style, 197 const char *service, int *status, 198 const char *challenge, const char *response) 199{ 200 int _status; 201 202 if (status == NULL) 203 status = &_status; 204 *status = 0; 205 206 if (style != NULL) { 207 int datalen; 208 char *data; 209 210 if (service == NULL) 211 service = LOGIN_DEFSERVICE; 212 213 datalen = strlen(challenge) + strlen(response) + 2; 214 215 if ((data = malloc(datalen)) == NULL) { 216 syslog(LOG_ERR, "auth_response: %m"); 217 warnx("internal resource failure"); 218 } else { 219 char path[MAXPATHLEN]; 220 221 snprintf(data, datalen, "%s%c%s", challenge, 0, response); 222 snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", style); 223 if (auth_script_data(data, datalen, path, style, "-s", service, 224 name, class, 0)) 225 *status = 0; 226 else 227 *status = auth_scan(0); 228 free(data); 229 return (*status & AUTH_ALLOW); 230 } 231 } 232 return -1; 233} 234 235 236int 237auth_approve(login_cap_t *lc, const char *name, const char *service) 238{ 239 int r = -1; 240 char path[MAXPATHLEN]; 241 242 if (lc == NULL) { 243 if (strlen(name) > MAXPATHLEN) { 244 syslog(LOG_ERR, "%s: username too long", name); 245 warnx("username too long"); 246 } else { 247 struct passwd *pwd; 248 char *p; 249 250 pwd = getpwnam(name); 251 if (pwd == NULL && (p = strchr(name, '.')) != NULL) { 252 int i = p - name; 253 254 if (i >= MAXPATHLEN) 255 i = MAXPATHLEN - 1; 256 strncpy(path, name, i); 257 path[i] = '\0'; 258 pwd = getpwnam(path); /* Fixed bug in BSDI code... */ 259 } 260 if ((lc = login_getpwclass(pwd ? pwd->pw_class : NULL)) == NULL) 261 warnx("unable to classify user '%s'", name); 262 } 263 } 264 265 if (lc != NULL) { 266 char *approve; 267 char *s; 268 269 if (service != NULL) 270 service = LOGIN_DEFSERVICE; 271 272 snprintf(path, sizeof(path), "approve-%s", service); 273 274 if ((approve = login_getcapstr(lc, s = path, NULL, NULL)) == NULL && 275 (approve = login_getcapstr(lc, s = "approve", NULL, NULL)) == NULL) 276 r = AUTH_OKAY; 277 else { 278 279 if (approve[0] != '/') { 280 syslog(LOG_ERR, "Invalid %s script: %s", s, approve); 281 warnx("invalid path to approval script"); 282 } else { 283 char *s; 284 285 s = strrchr(approve, '/') + 1; 286 if (auth_script(approve, s, name, 287 lc->lc_class, service, 0) == 0 && 288 (r = auth_scan(AUTH_OKAY) & AUTH_ALLOW) != 0) 289 auth_env(); 290 } 291 } 292 } 293 return r; 294} 295 296 297void 298auth_env(void) 299{ 300 int idx = 0; 301 char *line; 302 303 while ((line = nextline(&idx)) != NULL) { 304 if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) { 305 line += sizeof(BI_SETENV) - 1; 306 if (*line && isspace(*line)) { 307 char *name; 308 char ch, *p; 309 310 while (*line && isspace(*line)) 311 ++line; 312 name = line; 313 while (*line && !isspace(*line)) 314 ++line; 315 ch = *(p = line); 316 if (*line) 317 ++line; 318 if (setenv(name, line, 1)) 319 warn("setenv(%s, %s)", name, line); 320 *p = ch; 321 } 322 } 323 } 324} 325 326 327char * 328auth_value(const char *what) 329{ 330 int idx = 0; 331 char *line; 332 333 while ((line = nextline(&idx)) != NULL) { 334 if (!strncasecmp(line, BI_VALUE, sizeof(BI_VALUE)-1)) { 335 char *name; 336 337 line += sizeof(BI_VALUE) - 1; 338 while (*line && isspace(*line)) 339 ++line; 340 name = line; 341 if (*line) { 342 int i; 343 char ch, *p; 344 345 ch = *(p = line); 346 *line++ = '\0'; 347 i = strcmp(name, what); 348 *p = ch; 349 if (i == 0) 350 return auth_mkvalue(line); 351 } 352 } 353 } 354 return NULL; 355} 356 357char * 358auth_mkvalue(const char *value) 359{ 360 char *big, *p; 361 362 big = malloc(strlen(value) * 4 + 1); 363 if (big != NULL) { 364 for (p = big; *value; ++value) { 365 switch (*value) { 366 case '\r': 367 *p++ = '\\'; 368 *p++ = 'r'; 369 break; 370 case '\n': 371 *p++ = '\\'; 372 *p++ = 'n'; 373 break; 374 case '\\': 375 *p++ = '\\'; 376 *p++ = *value; 377 break; 378 case '\t': 379 case ' ': 380 if (p == big) 381 *p++ = '\\'; 382 *p++ = *value; 383 break; 384 default: 385 if (!isprint(*value)) { 386 *p++ = '\\'; 387 *p++ = ((*value >> 6) & 0x3) + '0'; 388 *p++ = ((*value >> 3) & 0x7) + '0'; 389 *p++ = ((*value ) & 0x7) + '0'; 390 } else 391 *p++ = *value; 392 break; 393 } 394 } 395 *p = '\0'; 396 big = realloc(big, strlen(big) + 1); 397 } 398 return big; 399} 400 401 402#define NARGC 63 403static int 404_auth_script(const char *data, int nbytes, const char *path, va_list ap) 405{ 406 int r, argc, status; 407 int pfd[2]; 408 pid_t pid; 409 struct authopts *e; 410 char *argv[NARGC+1]; 411 412 r = -1; 413 argc = 0; 414 for (e = optfirst; argc < (NARGC - 1) && e != NULL; e = e->next) { 415 argv[argc++] = "-v"; 416 argv[argc++] = e->opt; 417 } 418 while (argc < NARGC && (argv[argc] = va_arg(ap, char *)) != NULL) 419 ++argc; 420 argv[argc] = NULL; 421 422 if (argc >= NARGC && va_arg(ap, char *)) 423 syslog(LOG_ERR, "too many arguments"); 424 else if (_secure_path(path, 0, 0) < 0) { 425 syslog(LOG_ERR, "%s: path not secure", path); 426 warnx("invalid script: %s", path); 427 } else if (socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd) < 0) { 428 syslog(LOG_ERR, "unable to create backchannel %m"); 429 warnx("internal resource failure"); 430 } else switch (pid = fork()) { 431 case -1: /* fork() failure */ 432 close(pfd[0]); 433 close(pfd[1]); 434 syslog(LOG_ERR, "fork %s: %m", path); 435 warnx("internal resource failure"); 436 break; 437 case 0: /* child process */ 438 close(pfd[0]); 439 if (pfd[1] != AUTHCOMM_FD) { 440 if (dup2(pfd[1], AUTHCOMM_FD) < 0) 441 err(1, "dup backchannel"); 442 close(pfd[1]); 443 } 444 for (r = getdtablesize(); --r > AUTHCOMM_FD; ) 445 close(r); 446 execve(path, argv, auth_environ); 447 syslog(LOG_ERR, "exec %s: %m", path); 448 err(1, path); 449 default: /* parent */ 450 close(pfd[1]); 451 if (data && nbytes) 452 write(pfd[0], data, nbytes); 453 r = spooldata(pfd[0]); 454 close(pfd[0]); 455 if (waitpid(pid, &status, 0) < 0) { 456 syslog(LOG_ERR, "%s: waitpid: %m", path); 457 warnx("internal failure"); 458 r = -1; 459 } else { 460 if (r != 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) 461 r = -1; 462 } 463 /* kill the buffer if it is of no use */ 464 if (r != 0) { 465 free(spoolbuf); 466 spoolbuf = NULL; 467 spoolidx = 0; 468 } 469 break; 470 } 471 return r; 472} 473 474 475 476/* 477 * auth_script() 478 * Runs an authentication program with specified arguments. 479 * It sets up file descriptor 3 for the program to write to; 480 * it stashes the output somewhere. The output of the program 481 * consists of statements: 482 * reject [challenge|silent] 483 * authorize [root|secure] 484 * setenv <name> [<value>] 485 * remove <file> 486 * 487 * Terribly exciting, isn't it? 488 * Output cannot exceed AUTHMAXSPOOL characters. 489 */ 490 491int 492auth_script(const char *path, ...) 493{ 494 int r; 495 va_list ap; 496 497 va_start(ap, path); 498 r = _auth_script(NULL, 0, path, ap); 499 va_end(ap); 500 return r; 501} 502 503 504int 505auth_script_data(const char *data, int nbytes, const char *path, ...) 506{ 507 int r; 508 va_list ap; 509 510 va_start(ap, path); 511 r = _auth_script(data, nbytes, path, ap); 512 va_end(ap); 513 return r; 514} 515 516 517static void 518add_rmlist(const char *file) 519{ 520 struct rmfiles *rm; 521 522 if ((rm = malloc(sizeof(struct rmfiles) + strlen(file) + 1)) == NULL) 523 syslog(LOG_ERR, "add_rmfile malloc: %m"); 524 else { 525 strcpy(rm->file, file); 526 rm->next = rmfirst; 527 rmfirst = rm; 528 } 529} 530 531 532int 533auth_scan(int okay) 534{ 535 int idx = 0; 536 char *line; 537 538 while ((line = nextline(&idx)) != NULL) { 539 if (!strncasecmp(line, BI_REJECT, sizeof(BI_REJECT)-1)) { 540 line += sizeof(BI_REJECT) - 1; 541 while (*line && isspace(*line)) 542 ++line; 543 if (*line) { 544 if (!strcasecmp(line, "silent")) 545 return AUTH_SILENT; 546 if (!strcasecmp(line, "challenge")) 547 return AUTH_CHALLENGE; 548 } 549 return 0; 550 } else if (!strncasecmp(line, BI_AUTH, sizeof(BI_AUTH)-1)) { 551 line += sizeof(BI_AUTH) - 1; 552 while (*line && isspace(*line)) 553 ++line; 554 if (*line == '\0') 555 okay |= AUTH_OKAY; 556 else if (!strcasecmp(line, "root")) 557 okay |= AUTH_ROOTOKAY; 558 else if (!strcasecmp(line, "secure")) 559 okay |= AUTH_SECURE; 560 } 561 else if (!strncasecmp(line, BI_REMOVE, sizeof(BI_REMOVE)-1)) { 562 line += sizeof(BI_REMOVE) - 1; 563 while (*line && isspace(*line)) 564 ++line; 565 if (*line) 566 add_rmlist(line); 567 } 568 } 569 570 return okay; 571} 572 573 574int 575auth_setopt(const char *n, const char *v) 576{ 577 int r; 578 struct authopts *e; 579 580 if ((e = malloc(sizeof(*e) + strlen(n) + strlen(v) + 1)) == NULL) 581 r = -1; 582 else { 583 sprintf(e->opt, "%s=%s", n, v); 584 e->next = optfirst; 585 optfirst = e; 586 r = 0; 587 } 588 return r; 589} 590 591 592void 593auth_clropts(void) 594{ 595 struct authopts *e; 596 597 while ((e = optfirst) != NULL) { 598 optfirst = e->next; 599 free(e); 600 } 601} 602 603 604void 605auth_rmfiles(void) 606{ 607 struct rmfiles *rm; 608 609 while ((rm = rmfirst) != NULL) { 610 unlink(rm->file); 611 rmfirst = rm->next; 612 free(rm); 613 } 614} 615 616#endif 617 618 619/* 620 * auth_checknologin() 621 * Checks for the existance of a nologin file in the login_cap 622 * capability <lc>. If there isn't one specified, then it checks 623 * to see if this class should just ignore nologin files. Lastly, 624 * it tries to print out the default nologin file, and, if such 625 * exists, it exits. 626 */ 627 628void 629auth_checknologin(login_cap_t *lc) 630{ 631 char *file; 632 633 /* Do we ignore a nologin file? */ 634 if (login_getcapbool(lc, "ignorenologin", 0)) 635 return; 636 637 /* Note that <file> will be "" if there is no nologin capability */ 638 if ((file = login_getcapstr(lc, "nologin", "", NULL)) == NULL) 639 exit(1); 640 641 /* 642 * *file is true IFF there was a "nologin" capability 643 * Note that auth_cat() returns 1 only if the specified 644 * file exists, and is readable. E.g., /.nologin exists. 645 */ 646 if ((*file && auth_cat(file)) || auth_cat(_PATH_NOLOGIN)) 647 exit(1); 648} 649 650 651/* 652 * auth_cat() 653 * Checks for the readability of <file>; if it can be opened for 654 * reading, it prints it out to stdout, and then exits. Otherwise, 655 * it returns 0 (meaning no nologin file). 656 */ 657 658int 659auth_cat(const char *file) 660{ 661 int fd, count; 662 char buf[BUFSIZ]; 663 664 if ((fd = open(file, O_RDONLY)) < 0) 665 return 0; 666 while ((count = read(fd, buf, sizeof(buf))) > 0) 667 (void)write(fileno(stdout), buf, count); 668 close(fd); 669 return 1; 670} 671