login_pam.c revision 1.29 1 /* $NetBSD: login_pam.c,v 1.29 2025/09/07 21:31:20 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
40 static char sccsid[] = "@(#)login.c 8.4 (Berkeley) 4/2/94";
41 #endif
42 __RCSID("$NetBSD: login_pam.c,v 1.29 2025/09/07 21:31:20 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
84 static int rootterm(char *);
85 #endif
86 static void usage(void) __attribute__((__noreturn__));
87
88 static 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
95 static struct passwd pwres;
96 static char pwbuf[1024];
97 static struct group grs, *grp;
98 static char grbuf[1024];
99 extern char **environ;
100
101 int
102 main(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
380 skip_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 environment
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
657 static void
658 usage(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
667 static int
668 rootterm(char *ttyn)
669 {
670 struct ttyent *t;
671
672 return ((t = getttynam(ttyn)) && t->ty_status & TTY_SECURE);
673 }
674 #endif
675