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