1/* $NetBSD: login_pam.c,v 1.28 2022/01/24 09:14:37 andvar Exp $ */ 2 3/*- 4 * Copyright (c) 1980, 1987, 1988, 1991, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32#include <sys/cdefs.h> 33#ifndef lint 34__COPYRIGHT("@(#) Copyright (c) 1980, 1987, 1988, 1991, 1993, 1994\ 35 The Regents of the University of California. All rights reserved."); 36#endif /* not lint */ 37 38#ifndef lint 39#if 0 40static char sccsid[] = "@(#)login.c 8.4 (Berkeley) 4/2/94"; 41#endif 42__RCSID("$NetBSD: login_pam.c,v 1.28 2022/01/24 09:14:37 andvar Exp $"); 43#endif /* not lint */ 44 45/* 46 * login [ name ] 47 * login -h hostname (for telnetd, etc.) 48 * login -f name (for pre-authenticated login: datakit, xterm, etc.) 49 */ 50 51#include <sys/param.h> 52#include <sys/stat.h> 53#include <sys/time.h> 54#include <sys/resource.h> 55#include <sys/file.h> 56#include <sys/wait.h> 57#include <sys/socket.h> 58 59#include <err.h> 60#include <errno.h> 61#include <grp.h> 62#include <pwd.h> 63#include <setjmp.h> 64#include <signal.h> 65#include <stdio.h> 66#include <stdlib.h> 67#include <string.h> 68#include <syslog.h> 69#include <time.h> 70#include <ttyent.h> 71#include <tzfile.h> 72#include <unistd.h> 73#include <util.h> 74#include <login_cap.h> 75#include <vis.h> 76 77#include <security/pam_appl.h> 78#include <security/openpam.h> 79 80#include "pathnames.h" 81#include "common.h" 82 83#if 0 84static int rootterm(char *); 85#endif 86static void usage(void) __attribute__((__noreturn__)); 87 88static struct pam_conv pamc = { openpam_ttyconv, NULL }; 89 90#define TTYGRPNAME "tty" /* name of group to own ttys */ 91 92#define DEFAULT_BACKOFF 3 93#define DEFAULT_RETRIES 10 94 95static struct passwd pwres; 96static char pwbuf[1024]; 97static struct group grs, *grp; 98static char grbuf[1024]; 99extern char **environ; 100 101int 102main(int argc, char *argv[]) 103{ 104 struct stat st; 105 int ask, ch, cnt, fflag, pflag, quietlog, rootlogin; 106 int auth_passed; 107 uid_t uid, saved_uid; 108 gid_t saved_gid, saved_gids[NGROUPS_MAX]; 109 int nsaved_gids; 110 char *domain, *p, *ttyn; 111 char tbuf[MAXPATHLEN + 2], tname[sizeof(_PATH_TTY) + 10]; 112 char localhost[MAXHOSTNAMELEN + 1]; 113 int login_retries = DEFAULT_RETRIES, 114 login_backoff = DEFAULT_BACKOFF; 115 char *shell = NULL; 116 login_cap_t *lc = NULL; 117 pam_handle_t *pamh = NULL; 118 int pam_err; 119 sig_t oint, oabrt, oquit, oalrm; 120 const void *newuser; 121 int pam_silent = PAM_SILENT; 122 pid_t xpid, pid; 123 int status; 124 char *saved_term; 125 char **pamenv; 126 127 tbuf[0] = '\0'; 128 nested = NULL; 129 130 oabrt = signal(SIGABRT, SIG_IGN); 131 oalrm = signal(SIGALRM, timedout); 132 oint = signal(SIGINT, SIG_IGN); 133 oquit = signal(SIGQUIT, SIG_IGN); 134 135 (void)alarm(timeout); 136 (void)setpriority(PRIO_PROCESS, 0, 0); 137 138 openlog("login", 0, LOG_AUTH); 139 140 /* 141 * -p is used by getty to tell login not to destroy the environment 142 * -f is used to skip a second login authentication 143 * -h is used by other servers to pass the name of the remote host to 144 * login so that it may be placed in utmp/utmpx and wtmp/wtmpx 145 * -a in addition to -h, a server my supply -a to pass the actual 146 * server address. 147 */ 148 domain = NULL; 149 if (gethostname(localhost, sizeof(localhost)) < 0) 150 syslog(LOG_ERR, "couldn't get local hostname: %m"); 151 else 152 domain = strchr(localhost, '.'); 153 localhost[sizeof(localhost) - 1] = '\0'; 154 155 fflag = pflag = 0; 156 have_ss = 0; 157 uid = getuid(); 158 while ((ch = getopt(argc, argv, "a:fh:p")) != -1) 159 switch (ch) { 160 case 'a': 161 if (uid) { 162 errno = EPERM; 163 err(EXIT_FAILURE, "-a option"); 164 } 165 decode_ss(optarg); 166 break; 167 case 'f': 168 fflag = 1; 169 break; 170 case 'h': 171 if (uid) { 172 errno = EPERM; 173 err(EXIT_FAILURE, "-h option"); 174 } 175 if (domain && (p = strchr(optarg, '.')) != NULL && 176 strcasecmp(p, domain) == 0) 177 *p = '\0'; 178 hostname = optarg; 179 break; 180 case 'p': 181 pflag = 1; 182 break; 183 default: 184 case '?': 185 usage(); 186 break; 187 } 188 189 setproctitle(NULL); 190 argc -= optind; 191 argv += optind; 192 193 if (*argv) { 194 username = trimloginname(*argv); 195 ask = 0; 196 } else 197 ask = 1; 198 199#ifdef F_CLOSEM 200 (void)fcntl(3, F_CLOSEM, 0); 201#else 202 for (cnt = getdtablesize(); cnt > 2; cnt--) 203 (void)close(cnt); 204#endif 205 206 ttyn = ttyname(STDIN_FILENO); 207 if (ttyn == NULL || *ttyn == '\0') { 208 (void)snprintf(tname, sizeof(tname), "%s??", _PATH_TTY); 209 ttyn = tname; 210 } 211 if ((tty = strstr(ttyn, "/pts/")) != NULL) 212 ++tty; 213 else if ((tty = strrchr(ttyn, '/')) != NULL) 214 ++tty; 215 else 216 tty = ttyn; 217 218 if (issetugid()) { 219 nested = strdup(user_from_uid(getuid(), 0)); 220 if (nested == NULL) { 221 syslog(LOG_ERR, "strdup: %m"); 222 sleepexit(EXIT_FAILURE); 223 } 224 } 225 226 /* Get "login-retries" and "login-backoff" from default class */ 227 if ((lc = login_getclass(NULL)) != NULL) { 228 login_retries = (int)login_getcapnum(lc, "login-retries", 229 DEFAULT_RETRIES, DEFAULT_RETRIES); 230 login_backoff = (int)login_getcapnum(lc, "login-backoff", 231 DEFAULT_BACKOFF, DEFAULT_BACKOFF); 232 login_close(lc); 233 lc = NULL; 234 } 235 236 237 for (cnt = 0;; ask = 1) { 238 if (ask) { 239 fflag = 0; 240 username = trimloginname(getloginname()); 241 } 242 rootlogin = 0; 243 auth_passed = 0; 244 245 /* 246 * Note if trying multiple user names; log failures for 247 * previous user name, but don't bother logging one failure 248 * for nonexistent name (mistyped username). 249 */ 250 if (failures && strcmp(tbuf, username)) { 251 if (failures > (pwd ? 0 : 1)) 252 badlogin(tbuf); 253 failures = 0; 254 } 255 256#define PAM_END(msg) do { \ 257 syslog(LOG_ERR, "%s: %s", msg, pam_strerror(pamh, pam_err)); \ 258 warnx("%s: %s", msg, pam_strerror(pamh, pam_err)); \ 259 pam_end(pamh, pam_err); \ 260 sleepexit(EXIT_FAILURE); \ 261} while (0) 262 263 pam_err = pam_start("login", username, &pamc, &pamh); 264 if (pam_err != PAM_SUCCESS) { 265 if (pamh != NULL) 266 PAM_END("pam_start"); 267 /* Things went really bad... */ 268 syslog(LOG_ERR, "pam_start failed: %s", 269 pam_strerror(pamh, pam_err)); 270 errx(EXIT_FAILURE, "pam_start failed"); 271 } 272 273#define PAM_SET_ITEM(item, var) do { \ 274 pam_err = pam_set_item(pamh, (item), (var)); \ 275 if (pam_err != PAM_SUCCESS) \ 276 PAM_END("pam_set_item(" # item ")"); \ 277} while (0) 278 279 /* 280 * Fill hostname tty, and nested user 281 */ 282 PAM_SET_ITEM(PAM_RHOST, hostname); 283 PAM_SET_ITEM(PAM_TTY, tty); 284 if (nested) 285 PAM_SET_ITEM(PAM_NUSER, nested); 286 if (have_ss) 287 PAM_SET_ITEM(PAM_SOCKADDR, &ss); 288 289 /* 290 * Don't check for errors, because we don't want to give 291 * out any information. 292 */ 293 pwd = NULL; 294 (void)getpwnam_r(username, &pwres, pwbuf, sizeof(pwbuf), &pwd); 295 296 /* 297 * Establish the class now, before we might goto 298 * within the next block. pwd can be NULL since it 299 * falls back to the "default" class if it is. 300 */ 301 lc = login_getclass(pwd ? pwd->pw_class : NULL); 302 303 /* 304 * if we have a valid account name, and it doesn't have a 305 * password, or the -f option was specified and the caller 306 * is root or the caller isn't changing their uid, don't 307 * authenticate. 308 */ 309 if (pwd) { 310 if (pwd->pw_uid == 0) 311 rootlogin = 1; 312 313 if (fflag && (uid == 0 || uid == pwd->pw_uid)) { 314 /* already authenticated */ 315 auth_passed = 1; 316 goto skip_auth; 317 } 318 } 319 320 (void)setpriority(PRIO_PROCESS, 0, -4); 321 322 switch(pam_err = pam_authenticate(pamh, pam_silent)) { 323 case PAM_SUCCESS: 324 /* 325 * PAM can change the user, refresh 326 * username, pwd, and lc. 327 */ 328 pam_err = pam_get_item(pamh, PAM_USER, &newuser); 329 if (pam_err != PAM_SUCCESS) 330 PAM_END("pam_get_item(PAM_USER)"); 331 332 username = newuser; 333 /* 334 * Don't check for errors, because we don't want to give 335 * out any information. 336 */ 337 pwd = NULL; 338 (void)getpwnam_r(username, &pwres, pwbuf, sizeof(pwbuf), 339 &pwd); 340 lc = login_getpwclass(pwd); 341 auth_passed = 1; 342 343 switch (pam_err = pam_acct_mgmt(pamh, pam_silent)) { 344 case PAM_SUCCESS: 345 break; 346 347 case PAM_NEW_AUTHTOK_REQD: 348 pam_err = pam_chauthtok(pamh, 349 pam_silent|PAM_CHANGE_EXPIRED_AUTHTOK); 350 351 if (pam_err != PAM_SUCCESS) 352 PAM_END("pam_chauthtok"); 353 break; 354 355 case PAM_AUTH_ERR: 356 case PAM_USER_UNKNOWN: 357 case PAM_MAXTRIES: 358 auth_passed = 0; 359 break; 360 361 default: 362 PAM_END("pam_acct_mgmt"); 363 break; 364 } 365 break; 366 367 case PAM_AUTH_ERR: 368 case PAM_USER_UNKNOWN: 369 case PAM_MAXTRIES: 370 auth_passed = 0; 371 break; 372 373 default: 374 PAM_END("pam_authenticate"); 375 break; 376 } 377 378 (void)setpriority(PRIO_PROCESS, 0, 0); 379 380skip_auth: 381 /* 382 * If the user exists and authentication passed, 383 * get out of the loop and login the user. 384 */ 385 if (pwd && auth_passed) 386 break; 387 388 (void)printf("Login incorrect or refused on this terminal.\n"); 389 failures++; 390 cnt++; 391 /* 392 * We allow login_retries tries, but after login_backoff 393 * we start backing off. These default to 10 and 3 394 * respectively. 395 */ 396 if (cnt > login_backoff) { 397 if (cnt >= login_retries) { 398 badlogin(username); 399 pam_end(pamh, PAM_SUCCESS); 400 sleepexit(EXIT_FAILURE); 401 } 402 sleep((u_int)((cnt - login_backoff) * 5)); 403 } 404 } 405 406 /* committed to login -- turn off timeout */ 407 (void)alarm((u_int)0); 408 409 endpwent(); 410 411 quietlog = login_getcapbool(lc, "hushlogin", 0); 412 413 /* 414 * Temporarily give up special privileges so we can change 415 * into NFS-mounted homes that are exported for non-root 416 * access and have mode 7x0 417 */ 418 saved_uid = geteuid(); 419 saved_gid = getegid(); 420 nsaved_gids = getgroups(NGROUPS_MAX, saved_gids); 421 422 (void)setegid(pwd->pw_gid); 423 if (initgroups(username, pwd->pw_gid) == -1) { 424 syslog(LOG_ERR, "initgroups failed"); 425 pam_end(pamh, PAM_SUCCESS); 426 exit(EXIT_FAILURE); 427 } 428 (void)seteuid(pwd->pw_uid); 429 430 if (chdir(pwd->pw_dir) != 0) { 431 if (login_getcapbool(lc, "requirehome", 0)) { 432 (void)printf("Home directory %s required\n", 433 pwd->pw_dir); 434 pam_end(pamh, PAM_SUCCESS); 435 exit(EXIT_FAILURE); 436 } 437 438 (void)printf("No home directory %s!\n", pwd->pw_dir); 439 if (chdir("/") == -1) { 440 pam_end(pamh, PAM_SUCCESS); 441 exit(EXIT_FAILURE); 442 } 443 pwd->pw_dir = __UNCONST("/"); 444 (void)printf("Logging in with home = \"/\".\n"); 445 } 446 447 if (!quietlog) { 448 quietlog = access(_PATH_HUSHLOGIN, F_OK) == 0; 449 pam_silent = quietlog ? PAM_SILENT : 0; 450 } 451 452 /* regain special privileges */ 453 (void)setegid(saved_gid); 454 (void)seteuid(saved_uid); 455 if (setgroups(nsaved_gids, saved_gids) == -1) { 456 syslog(LOG_ERR, "setgroups failed: %m"); 457 pam_end(pamh, PAM_SUCCESS); 458 exit(EXIT_FAILURE); 459 } 460 461 (void)getgrnam_r(TTYGRPNAME, &grs, grbuf, sizeof(grbuf), &grp); 462 (void)chown(ttyn, pwd->pw_uid, 463 (grp != NULL) ? grp->gr_gid : pwd->pw_gid); 464 465 if (ttyaction(ttyn, "login", pwd->pw_name)) 466 (void)printf("Warning: ttyaction failed.\n"); 467 468 /* Nothing else left to fail -- really log in. */ 469 update_db(quietlog, rootlogin, fflag); 470 471 if (nested == NULL && setusercontext(lc, pwd, pwd->pw_uid, 472 LOGIN_SETLOGIN) != 0) { 473 syslog(LOG_ERR, "setusercontext failed"); 474 pam_end(pamh, PAM_SUCCESS); 475 exit(EXIT_FAILURE); 476 } 477 478 /* 479 * Establish groups 480 */ 481 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) != 0) { 482 syslog(LOG_ERR, "setusercontext failed"); 483 pam_end(pamh, PAM_SUCCESS); 484 exit(EXIT_FAILURE); 485 } 486 487 pam_err = pam_setcred(pamh, pam_silent|PAM_ESTABLISH_CRED); 488 if (pam_err != PAM_SUCCESS) 489 PAM_END("pam_setcred"); 490 491 pam_err = pam_open_session(pamh, pam_silent); 492 if (pam_err != PAM_SUCCESS) 493 PAM_END("pam_open_session"); 494 495 /* 496 * Fork because we need to call pam_closesession as root. 497 * Make sure signals cannot kill the parent. 498 * This has been handled in the beginning of main. 499 */ 500 501 switch(pid = fork()) { 502 case -1: 503 pam_err = pam_close_session(pamh, 0); 504 if (pam_err != PAM_SUCCESS) { 505 syslog(LOG_ERR, "pam_close_session: %s", 506 pam_strerror(pamh, pam_err)); 507 warnx("pam_close_session: %s", 508 pam_strerror(pamh, pam_err)); 509 } 510 syslog(LOG_ERR, "fork failed: %m"); 511 warn("fork failed"); 512 pam_end(pamh, pam_err); 513 exit(EXIT_FAILURE); 514 break; 515 516 case 0: /* Child */ 517 break; 518 519 default: 520 /* 521 * Parent: wait for the child to terminate 522 * and call pam_close_session. 523 */ 524 if ((xpid = waitpid(pid, &status, 0)) != pid) { 525 pam_err = pam_close_session(pamh, 0); 526 if (pam_err != PAM_SUCCESS) { 527 syslog(LOG_ERR, 528 "pam_close_session: %s", 529 pam_strerror(pamh, pam_err)); 530 warnx("pam_close_session: %s", 531 pam_strerror(pamh, pam_err)); 532 } 533 pam_end(pamh, pam_err); 534 if (xpid != -1) 535 warnx("wrong PID: %d != %d", pid, xpid); 536 else 537 warn("wait for pid %d failed", pid); 538 exit(EXIT_FAILURE); 539 } 540 541 (void)signal(SIGABRT, oabrt); 542 (void)signal(SIGALRM, oalrm); 543 (void)signal(SIGINT, oint); 544 (void)signal(SIGQUIT, oquit); 545 if ((pam_err = pam_close_session(pamh, 0)) != PAM_SUCCESS) { 546 syslog(LOG_ERR, "pam_close_session: %s", 547 pam_strerror(pamh, pam_err)); 548 warnx("pam_close_session: %s", 549 pam_strerror(pamh, pam_err)); 550 } 551 pam_end(pamh, PAM_SUCCESS); 552 exit(EXIT_SUCCESS); 553 break; 554 } 555 556 /* 557 * The child: starting here, we don't have to care about 558 * handling PAM issues if we exit, the parent will do the 559 * job when we exit. 560 * 561 * Destroy environment unless user has requested its preservation. 562 * Try to preserve TERM anyway. 563 */ 564 saved_term = getenv("TERM"); 565 if (!pflag) { 566 environ = envinit; 567 if (saved_term) 568 setenv("TERM", saved_term, 0); 569 } 570 571 if (*pwd->pw_shell == '\0') 572 pwd->pw_shell = __UNCONST(_PATH_BSHELL); 573 574 shell = login_getcapstr(lc, "shell", pwd->pw_shell, pwd->pw_shell); 575 if (*shell == '\0') 576 shell = pwd->pw_shell; 577 578 if ((pwd->pw_shell = strdup(shell)) == NULL) { 579 syslog(LOG_ERR, "Cannot alloc mem"); 580 exit(EXIT_FAILURE); 581 } 582 583 (void)setenv("HOME", pwd->pw_dir, 1); 584 (void)setenv("SHELL", pwd->pw_shell, 1); 585 if (term[0] == '\0') { 586 const char *tt = stypeof(tty); 587 588 if (tt == NULL) 589 tt = login_getcapstr(lc, "term", NULL, NULL); 590 591 /* unknown term -> "su" */ 592 (void)strlcpy(term, tt != NULL ? tt : "su", sizeof(term)); 593 } 594 (void)setenv("TERM", term, 0); 595 (void)setenv("LOGNAME", pwd->pw_name, 1); 596 (void)setenv("USER", pwd->pw_name, 1); 597 598 /* 599 * Add PAM environement 600 */ 601 if ((pamenv = pam_getenvlist(pamh)) != NULL) { 602 char **envitem; 603 604 for (envitem = pamenv; *envitem; envitem++) { 605 if (putenv(*envitem) == -1) 606 free(*envitem); 607 } 608 609 free(pamenv); 610 } 611 612 /* This drops root privs */ 613 if (setusercontext(lc, pwd, pwd->pw_uid, 614 (LOGIN_SETALL & ~LOGIN_SETLOGIN)) != 0) { 615 syslog(LOG_ERR, "setusercontext failed"); 616 exit(EXIT_FAILURE); 617 } 618 619 if (!quietlog) { 620 const char *fname; 621 622 fname = login_getcapstr(lc, "copyright", NULL, NULL); 623 if (fname != NULL && access(fname, F_OK) == 0) 624 motd(fname); 625 else 626 (void)printf("%s", copyrightstr); 627 628 fname = login_getcapstr(lc, "welcome", NULL, NULL); 629 if (fname == NULL || access(fname, F_OK) != 0) 630 fname = _PATH_MOTDFILE; 631 motd(fname); 632 633 (void)snprintf(tbuf, 634 sizeof(tbuf), "%s/%s", _PATH_MAILDIR, pwd->pw_name); 635 if (stat(tbuf, &st) == 0 && st.st_size != 0) 636 (void)printf("You have %smail.\n", 637 (st.st_mtime > st.st_atime) ? "new " : ""); 638 } 639 640 login_close(lc); 641 642 643 tbuf[0] = '-'; 644 (void)strlcpy(tbuf + 1, (p = strrchr(pwd->pw_shell, '/')) ? 645 p + 1 : pwd->pw_shell, sizeof(tbuf) - 1); 646 647 (void)signal(SIGABRT, oabrt); 648 (void)signal(SIGALRM, oalrm); 649 (void)signal(SIGINT, oint); 650 (void)signal(SIGQUIT, oquit); 651 (void)signal(SIGTSTP, SIG_IGN); 652 653 execlp(pwd->pw_shell, tbuf, NULL); 654 err(EXIT_FAILURE, "%s", pwd->pw_shell); 655} 656 657static void 658usage(void) 659{ 660 (void)fprintf(stderr, 661 "Usage: %s [-fp] [-a address] [-h hostname] [username]\n", 662 getprogname()); 663 exit(EXIT_FAILURE); 664} 665 666#if 0 667static int 668rootterm(char *ttyn) 669{ 670 struct ttyent *t; 671 672 return ((t = getttynam(ttyn)) && t->ty_status & TTY_SECURE); 673} 674#endif 675