login.c revision 97376
1/*-
2 * Copyright (c) 1980, 1987, 1988, 1991, 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 * Copyright (c) 2002 Networks Associates Technologies, Inc.
5 * All rights reserved.
6 *
7 * Portions of this software were developed for the FreeBSD Project by
8 * ThinkSec AS and NAI Labs, the Security Research Division of Network
9 * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
10 * ("CBOSS"), as part of the DARPA CHATS research program.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 *    notice, 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. All advertising materials mentioning features or use of this software
21 *    must display the following acknowledgement:
22 *	This product includes software developed by the University of
23 *	California, Berkeley and its contributors.
24 * 4. Neither the name of the University nor the names of its contributors
25 *    may be used to endorse or promote products derived from this software
26 *    without specific prior written permission.
27 *
28 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 * SUCH DAMAGE.
39 */
40
41#if 0
42#ifndef lint
43static char sccsid[] = "@(#)login.c	8.4 (Berkeley) 4/2/94";
44#endif
45#endif
46
47#include <sys/cdefs.h>
48__FBSDID("$FreeBSD: head/usr.bin/login/login.c 97376 2002-05-28 06:46:37Z des $");
49
50/*
51 * login [ name ]
52 * login -h hostname	(for telnetd, etc.)
53 * login -f name	(for pre-authenticated login: datakit, xterm, etc.)
54 */
55
56#include <sys/copyright.h>
57#include <sys/param.h>
58#include <sys/file.h>
59#include <sys/stat.h>
60#include <sys/time.h>
61#include <sys/resource.h>
62#include <sys/wait.h>
63
64#include <err.h>
65#include <errno.h>
66#include <grp.h>
67#include <libutil.h>
68#include <login_cap.h>
69#include <pwd.h>
70#include <setjmp.h>
71#include <signal.h>
72#include <stdio.h>
73#include <stdlib.h>
74#include <string.h>
75#include <syslog.h>
76#include <ttyent.h>
77#include <unistd.h>
78
79#include <security/pam_appl.h>
80#include <security/openpam.h>
81
82#include "login.h"
83#include "pathnames.h"
84
85static int		 auth_pam(void);
86static void		 bail(int, int);
87static int		 export(const char *);
88static void		 export_pam_environment(void);
89static int		 motd(const char *);
90static void		 badlogin(char *);
91static char		*getloginname(void);
92static void		 pam_syslog(const char *);
93static void		 pam_cleanup(void);
94static void		 refused(const char *, const char *, int);
95static const char	*stypeof(char *);
96static void		 sigint(int);
97static void		 timedout(int);
98static void		 usage(void);
99
100#define	TTYGRPNAME		"tty"			/* group to own ttys */
101#define	DEFAULT_BACKOFF		3
102#define	DEFAULT_RETRIES		10
103#define	DEFAULT_PROMPT		"login: "
104#define	DEFAULT_PASSWD_PROMPT	"Password:"
105#define	TERM_UNKNOWN		"su"
106#define	DEFAULT_WARN		(2L * 7L * 86400L)	/* Two weeks */
107#define NO_SLEEP_EXIT		0
108#define SLEEP_EXIT		5
109
110/*
111 * This bounds the time given to login.  Not a define so it can
112 * be patched on machines where it's too small.
113 */
114static u_int		timeout = 300;
115
116/* Buffer for signal handling of timeout */
117static jmp_buf		 timeout_buf;
118
119struct passwd		*pwd;
120static int		 failures;
121
122static char		*envinit[1];	/* empty environment list */
123
124/*
125 * Command line flags and arguments
126 */
127static int		 fflag;		/* -f: do not perform authentication */
128static int		 hflag;		/* -h: login from remote host */
129static char		*hostname;	/* hostname from command line */
130static int		 pflag;		/* -p: preserve environment */
131
132/*
133 * User name
134 */
135static char		*username;	/* user name */
136static char		*olduser;	/* previous user name */
137
138/*
139 * Prompts
140 */
141static char		 default_prompt[] = DEFAULT_PROMPT;
142static const char	*prompt;
143static char		 default_passwd_prompt[] = DEFAULT_PASSWD_PROMPT;
144static const char	*passwd_prompt;
145
146static char		*tty;
147
148/*
149 * PAM data
150 */
151static pam_handle_t	*pamh = NULL;
152static struct pam_conv	 pamc = { openpam_ttyconv, NULL };
153static int		 pam_err;
154static int		 pam_silent = PAM_SILENT;
155static int		 pam_cred_established;
156static int		 pam_session_established;
157
158int
159main(int argc, char *argv[])
160{
161	struct group *gr;
162	struct stat st;
163	int retries, backoff;
164	int ask, ch, cnt, quietlog, rootlogin, rval;
165	uid_t uid, euid;
166	gid_t egid;
167	char *term;
168	char *p, *ttyn;
169	char tname[sizeof(_PATH_TTY) + 10];
170	char *arg0;
171	const char *shell = NULL;
172	login_cap_t *lc = NULL;
173	pid_t pid;
174
175	(void)signal(SIGQUIT, SIG_IGN);
176	(void)signal(SIGINT, SIG_IGN);
177	(void)signal(SIGHUP, SIG_IGN);
178	if (setjmp(timeout_buf)) {
179		if (failures)
180			badlogin(username);
181		(void)fprintf(stderr, "Login timed out after %d seconds\n",
182		    timeout);
183		bail(NO_SLEEP_EXIT, 0);
184	}
185	(void)signal(SIGALRM, timedout);
186	(void)alarm(timeout);
187	(void)setpriority(PRIO_PROCESS, 0, 0);
188
189	openlog("login", LOG_ODELAY, LOG_AUTH);
190
191	uid = getuid();
192	euid = geteuid();
193	egid = getegid();
194
195	while ((ch = getopt(argc, argv, "fh:p")) != -1)
196		switch (ch) {
197		case 'f':
198			fflag = 1;
199			break;
200		case 'h':
201			if (uid != 0)
202				errx(1, "-h option: %s", strerror(EPERM));
203			if (strlen(optarg) >= MAXHOSTNAMELEN)
204				errx(1, "-h option: %s: exceeds maximum "
205				    "hostname size", optarg);
206			hflag = 1;
207			hostname = optarg;
208			break;
209		case 'p':
210			pflag = 1;
211			break;
212		case '?':
213		default:
214			if (uid == 0)
215				syslog(LOG_ERR, "invalid flag %c", ch);
216			usage();
217		}
218	argc -= optind;
219	argv += optind;
220
221	if (argc > 0) {
222		username = strdup(*argv);
223		if (username == NULL)
224			err(1, "strdup()");
225		ask = 0;
226	} else {
227		ask = 1;
228	}
229
230	for (cnt = getdtablesize(); cnt > 2; cnt--)
231		(void)close(cnt);
232
233	/*
234	 * Get current TTY
235	 */
236	ttyn = ttyname(STDIN_FILENO);
237	if (ttyn == NULL || *ttyn == '\0') {
238		(void)snprintf(tname, sizeof(tname), "%s??", _PATH_TTY);
239		ttyn = tname;
240	}
241	if ((tty = strrchr(ttyn, '/')) != NULL)
242		++tty;
243	else
244		tty = ttyn;
245
246	/*
247	 * Get "login-retries" & "login-backoff" from default class
248	 */
249	lc = login_getclass(NULL);
250	prompt = login_getcapstr(lc, "prompt",
251	    default_prompt, default_prompt);
252	passwd_prompt = login_getcapstr(lc, "passwd_prompt",
253	    default_passwd_prompt, default_passwd_prompt);
254	retries = login_getcapnum(lc, "login-retries",
255	    DEFAULT_RETRIES, DEFAULT_RETRIES);
256	backoff = login_getcapnum(lc, "login-backoff",
257	    DEFAULT_BACKOFF, DEFAULT_BACKOFF);
258	login_close(lc);
259	lc = NULL;
260
261	/*
262	 * Try to authenticate the user until we succeed or time out.
263	 */
264	for (cnt = 0;; ask = 1) {
265		if (ask) {
266			fflag = 0;
267			if (olduser != NULL)
268				free(olduser);
269			olduser = username;
270			username = getloginname();
271		}
272		rootlogin = 0;
273
274		/*
275		 * Note if trying multiple user names; log failures for
276		 * previous user name, but don't bother logging one failure
277		 * for nonexistent name (mistyped username).
278		 */
279		if (failures && strcmp(olduser, username) != 0) {
280			if (failures > (pwd ? 0 : 1))
281				badlogin(olduser);
282		}
283
284		/*
285		 * Load the PAM policy and set some variables
286		 */
287		pam_err = pam_start("login", username, &pamc, &pamh);
288		if (pam_err != PAM_SUCCESS) {
289			pam_syslog("pam_start()");
290			bail(NO_SLEEP_EXIT, 1);
291		}
292		pam_err = pam_set_item(pamh, PAM_TTY, tty);
293		if (pam_err != PAM_SUCCESS) {
294			pam_syslog("pam_set_item(PAM_TTY)");
295			bail(NO_SLEEP_EXIT, 1);
296		}
297		pam_err = pam_set_item(pamh, PAM_RHOST, hostname);
298		if (pam_err != PAM_SUCCESS) {
299			pam_syslog("pam_set_item(PAM_RHOST)");
300			bail(NO_SLEEP_EXIT, 1);
301		}
302
303		pwd = getpwnam(username);
304		if (pwd != NULL && pwd->pw_uid == 0)
305			rootlogin = 1;
306
307		/*
308		 * If the -f option was specified and the caller is
309		 * root or the caller isn't changing their uid, don't
310		 * authenticate.
311		 */
312		if (pwd != NULL && fflag &&
313		    (uid == (uid_t)0 || uid == (uid_t)pwd->pw_uid)) {
314			/* already authenticated */
315			rval = 0;
316		} else {
317			fflag = 0;
318			(void)setpriority(PRIO_PROCESS, 0, -4);
319			rval = auth_pam();
320			(void)setpriority(PRIO_PROCESS, 0, 0);
321		}
322
323		if (pwd && rval == 0)
324			break;
325
326		pam_cleanup();
327
328		(void)printf("Login incorrect\n");
329		failures++;
330
331		/*
332		 * Allow up to 'retry' (10) attempts, but start
333		 * backing off after 'backoff' (3) attempts.
334		 */
335		if (++cnt > backoff) {
336			if (cnt >= retries) {
337				badlogin(username);
338				bail(SLEEP_EXIT, 1);
339			}
340			sleep((u_int)((cnt - backoff) * 5));
341		}
342	}
343
344	/* committed to login -- turn off timeout */
345	(void)alarm((u_int)0);
346	(void)signal(SIGHUP, SIG_DFL);
347
348	endpwent();
349
350	/*
351	 * Establish the login class.
352	 */
353	lc = login_getpwclass(pwd);
354
355	quietlog = login_getcapbool(lc, "hushlogin", 0);
356	if (!quietlog)
357		pam_silent = 0;
358
359	/*
360	 * Switching needed for NFS with root access disabled.
361	 *
362	 * XXX: This change fails to modify the additional groups for the
363	 * process, and as such, may restrict rights normally granted
364	 * through those groups.
365	 */
366	(void)setegid(pwd->pw_gid);
367	(void)seteuid(rootlogin ? 0 : pwd->pw_uid);
368	if (!*pwd->pw_dir || chdir(pwd->pw_dir) < 0) {
369		if (login_getcapbool(lc, "requirehome", 0))
370			refused("Home directory not available", "HOMEDIR", 1);
371		if (chdir("/") < 0)
372			refused("Cannot find root directory", "ROOTDIR", 1);
373		if (!quietlog || *pwd->pw_dir)
374			printf("No home directory.\nLogging in with home = \"/\".\n");
375		pwd->pw_dir = strdup("/");
376		if (pwd->pw_dir == NULL) {
377			syslog(LOG_NOTICE, "strdup(): %m");
378			bail(SLEEP_EXIT, 1);
379		}
380	}
381	(void)seteuid(euid);
382	(void)setegid(egid);
383	if (!quietlog)
384		quietlog = access(_PATH_HUSHLOGIN, F_OK) == 0;
385
386	shell = login_getcapstr(lc, "shell", pwd->pw_shell, pwd->pw_shell);
387	if (*pwd->pw_shell == '\0')
388		pwd->pw_shell = strdup(_PATH_BSHELL);
389	if (pwd->pw_shell == NULL) {
390		syslog(LOG_NOTICE, "strdup(): %m");
391		bail(SLEEP_EXIT, 1);
392	}
393	if (*shell == '\0')   /* Not overridden */
394		shell = pwd->pw_shell;
395	if ((shell = strdup(shell)) == NULL) {
396		syslog(LOG_NOTICE, "strdup(): %m");
397		bail(SLEEP_EXIT, 1);
398	}
399
400	/*
401	 * Set device protections, depending on what terminal the
402	 * user is logged in. This feature is used on Suns to give
403	 * console users better privacy.
404	 */
405	login_fbtab(tty, pwd->pw_uid, pwd->pw_gid);
406
407	/*
408	 * Clear flags of the tty.  None should be set, and when the
409	 * user sets them otherwise, this can cause the chown to fail.
410	 * Since it isn't clear that flags are useful on character
411	 * devices, we just clear them.
412	 */
413	if (ttyn != tname && chflags(ttyn, 0) && errno != EOPNOTSUPP)
414		syslog(LOG_ERR, "chflags(%s): %m", ttyn);
415	if (ttyn != tname && chown(ttyn, pwd->pw_uid,
416	    (gr = getgrnam(TTYGRPNAME)) ? gr->gr_gid : pwd->pw_gid))
417		syslog(LOG_ERR, "chmod(%s): %m", ttyn);
418
419	/*
420	 * Exclude cons/vt/ptys only, assume dialup otherwise
421	 * TODO: Make dialup tty determination a library call
422	 * for consistency (finger etc.)
423	 */
424	if (hflag && isdialuptty(tty))
425		syslog(LOG_INFO, "DIALUP %s, %s", tty, pwd->pw_name);
426
427#ifdef LOGALL
428	/*
429	 * Syslog each successful login, so we don't have to watch
430	 * hundreds of wtmp or lastlogin files.
431	 */
432	if (hflag)
433		syslog(LOG_INFO, "login from %s on %s as %s",
434		       hostname, tty, pwd->pw_name);
435	else
436		syslog(LOG_INFO, "login on %s as %s",
437		       tty, pwd->pw_name);
438#endif
439
440	/*
441	 * If fflag is on, assume caller/authenticator has logged root
442	 * login.
443	 */
444	if (rootlogin && fflag == 0) {
445		if (hflag)
446			syslog(LOG_NOTICE, "ROOT LOGIN (%s) ON %s FROM %s",
447			    username, tty, hostname);
448		else
449			syslog(LOG_NOTICE, "ROOT LOGIN (%s) ON %s",
450			    username, tty);
451	}
452
453	/*
454	 * Destroy environment unless user has requested its
455	 * preservation - but preserve TERM in all cases
456	 */
457	term = getenv("TERM");
458	if (!pflag)
459		environ = envinit;
460	if (term != NULL)
461		setenv("TERM", term, 0);
462
463	/*
464	 * PAM modules might add supplementary groups during pam_setcred().
465	 */
466	if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) != 0) {
467		syslog(LOG_ERR, "setusercontext() failed - exiting");
468		bail(NO_SLEEP_EXIT, 1);
469	}
470
471	pam_err = pam_setcred(pamh, pam_silent|PAM_ESTABLISH_CRED);
472	if (pam_err != PAM_SUCCESS) {
473		pam_syslog("pam_setcred()");
474		bail(NO_SLEEP_EXIT, 1);
475	}
476	pam_cred_established = 1;
477
478	pam_err = pam_open_session(pamh, pam_silent);
479	if (pam_err != PAM_SUCCESS) {
480		pam_syslog("pam_open_session()");
481		bail(NO_SLEEP_EXIT, 1);
482	}
483	pam_session_established = 1;
484
485	/*
486	 * We must fork() before setuid() because we need to call
487	 * pam_close_session() as root.
488	 */
489	pid = fork();
490	if (pid < 0) {
491		err(1, "fork");
492	} else if (pid != 0) {
493		/*
494		 * Parent: wait for child to finish, then clean up
495		 * session.
496		 */
497		wait(NULL);
498		bail(NO_SLEEP_EXIT, 0);
499	}
500
501	/*
502	 * NOTICE: We are now in the child process!
503	 */
504
505	/*
506	 * Add any environment variables the PAM modules may have set.
507	 */
508	export_pam_environment();
509
510	/*
511	 * We're done with PAM now; our parent will deal with the rest.
512	 */
513	pam_end(pamh, 0);
514	pamh = NULL;
515
516	/*
517	 * We don't need to be root anymore, so set the login name and
518	 * the UID.
519	 */
520	if (setlogin(username) != 0) {
521		syslog(LOG_ERR, "setlogin(%s): %m - exiting", username);
522		bail(NO_SLEEP_EXIT, 1);
523	}
524	if (setusercontext(lc, pwd, pwd->pw_uid,
525	    LOGIN_SETALL & ~(LOGIN_SETLOGIN|LOGIN_SETGROUP)) != 0) {
526		syslog(LOG_ERR, "setusercontext() failed - exiting");
527		exit(1);
528	}
529
530	(void)setenv("SHELL", pwd->pw_shell, 1);
531	(void)setenv("HOME", pwd->pw_dir, 1);
532	(void)setenv("TERM", stypeof(tty), 0);
533	(void)setenv("LOGNAME", username, 1);
534	(void)setenv("USER", username, 1);
535	(void)setenv("PATH", rootlogin ? _PATH_STDPATH : _PATH_DEFPATH, 0);
536
537	if (!quietlog) {
538		const char *cw;
539
540		cw = login_getcapstr(lc, "copyright", NULL, NULL);
541		if (cw == NULL || motd(cw) == -1)
542			(void)printf("%s", copyright);
543
544		(void)printf("\n");
545
546		cw = login_getcapstr(lc, "welcome", NULL, NULL);
547		if (cw != NULL && access(cw, F_OK) == 0)
548			motd(cw);
549		else
550			motd(_PATH_MOTDFILE);
551
552		if (login_getcapbool(lc, "nocheckmail", 0) == 0) {
553			/* $MAIL may have been set by class. */
554			cw = getenv("MAIL");
555			if (cw == NULL) {
556				asprintf((char **)&cw, "%s/%s",
557				    _PATH_MAILDIR, pwd->pw_name);
558			}
559			if (cw && stat(cw, &st) == 0 && st.st_size != 0)
560				(void)printf("You have %smail.\n",
561				    (st.st_mtime > st.st_atime) ? "new " : "");
562			if (getenv("MAIL") == NULL)
563				free((char *)cw);
564		}
565	}
566
567	login_close(lc);
568
569	(void)signal(SIGALRM, SIG_DFL);
570	(void)signal(SIGQUIT, SIG_DFL);
571	(void)signal(SIGINT, SIG_DFL);
572	(void)signal(SIGTSTP, SIG_IGN);
573
574	/*
575	 * Login shells have a leading '-' in front of argv[0]
576	 */
577	p = strrchr(pwd->pw_shell, '/');
578	if (asprintf(&arg0, "-%s", p ? p + 1 : pwd->pw_shell) >= MAXPATHLEN) {
579		syslog(LOG_ERR, "user: %s: shell exceeds maximum pathname size",
580		    username);
581		errx(1, "shell exceeds maximum pathname size");
582	} else if (arg0 == NULL) {
583		err(1, "asprintf()");
584	}
585
586	execlp(shell, arg0, (char *)0);
587	err(1, "%s", shell);
588
589	/*
590	 * That's it, folks!
591	 */
592}
593
594/*
595 * Attempt to authenticate the user using PAM.  Returns 0 if the user is
596 * authenticated, or 1 if not authenticated.  If some sort of PAM system
597 * error occurs (e.g., the "/etc/pam.conf" file is missing) then this
598 * function returns -1.  This can be used as an indication that we should
599 * fall back to a different authentication mechanism.
600 */
601static int
602auth_pam(void)
603{
604	const char *tmpl_user;
605	const void *item;
606	int rval;
607
608	pam_err = pam_authenticate(pamh, pam_silent);
609	switch (pam_err) {
610
611	case PAM_SUCCESS:
612		/*
613		 * With PAM we support the concept of a "template"
614		 * user.  The user enters a login name which is
615		 * authenticated by PAM, usually via a remote service
616		 * such as RADIUS or TACACS+.  If authentication
617		 * succeeds, a different but related "template" name
618		 * is used for setting the credentials, shell, and
619		 * home directory.  The name the user enters need only
620		 * exist on the remote authentication server, but the
621		 * template name must be present in the local password
622		 * database.
623		 *
624		 * This is supported by two various mechanisms in the
625		 * individual modules.  However, from the application's
626		 * point of view, the template user is always passed
627		 * back as a changed value of the PAM_USER item.
628		 */
629		pam_err = pam_get_item(pamh, PAM_USER, &item);
630		if (pam_err == PAM_SUCCESS) {
631			tmpl_user = (const char *)item;
632			if (strcmp(username, tmpl_user) != 0)
633				pwd = getpwnam(tmpl_user);
634		} else {
635			pam_syslog("pam_get_item(PAM_USER)");
636		}
637		rval = 0;
638		break;
639
640	case PAM_AUTH_ERR:
641	case PAM_USER_UNKNOWN:
642	case PAM_MAXTRIES:
643		rval = 1;
644		break;
645
646	default:
647		pam_syslog("pam_authenticate()");
648		rval = -1;
649		break;
650	}
651
652	if (rval == 0) {
653		pam_err = pam_acct_mgmt(pamh, pam_silent);
654		switch (pam_err) {
655		case PAM_SUCCESS:
656			break;
657		case PAM_NEW_AUTHTOK_REQD:
658			pam_err = pam_chauthtok(pamh,
659			    pam_silent|PAM_CHANGE_EXPIRED_AUTHTOK);
660			if (pam_err != PAM_SUCCESS) {
661				pam_syslog("pam_chauthtok()");
662				rval = 1;
663			}
664			break;
665		default:
666			pam_syslog("pam_acct_mgmt()");
667			rval = 1;
668			break;
669		}
670	}
671
672	if (rval != 0) {
673		pam_end(pamh, pam_err);
674		pamh = NULL;
675	}
676	return (rval);
677}
678
679/*
680 * Export any environment variables PAM modules may have set
681 */
682static void
683export_pam_environment()
684{
685	char **pam_env;
686	char **pp;
687
688	pam_env = pam_getenvlist(pamh);
689	if (pam_env != NULL) {
690		for (pp = pam_env; *pp != NULL; pp++) {
691			(void)export(*pp);
692			free(*pp);
693		}
694	}
695}
696
697/*
698 * Perform sanity checks on an environment variable:
699 * - Make sure there is an '=' in the string.
700 * - Make sure the string doesn't run on too long.
701 * - Do not export certain variables.  This list was taken from the
702 *   Solaris pam_putenv(3) man page.
703 * Then export it.
704 */
705static int
706export(const char *s)
707{
708	static const char *noexport[] = {
709		"SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH",
710		"IFS", "PATH", NULL
711	};
712	const char **pp;
713	size_t n;
714
715	if (strlen(s) > 1024 || strchr(s, '=') == NULL)
716		return (0);
717	if (strncmp(s, "LD_", 3) == 0)
718		return (0);
719	for (pp = noexport; *pp != NULL; pp++) {
720		n = strlen(*pp);
721		if (s[n] == '=' && strncmp(s, *pp, n) == 0)
722			return (0);
723	}
724	(void)putenv(s);
725	return (1);
726}
727
728static void
729usage()
730{
731
732	(void)fprintf(stderr, "usage: login [-fp] [-h hostname] [username]\n");
733	exit(1);
734}
735
736/*
737 * Prompt user and read login name from stdin.
738 */
739static char *
740getloginname()
741{
742	char *nbuf, *p;
743	int ch;
744
745	nbuf = malloc(MAXLOGNAME);
746	if (nbuf == NULL)
747		err(1, "malloc()");
748	do {
749		(void)printf("%s", prompt);
750		for (p = nbuf; (ch = getchar()) != '\n'; ) {
751			if (ch == EOF) {
752				badlogin(username);
753				bail(NO_SLEEP_EXIT, 0);
754			}
755			if (p < nbuf + MAXLOGNAME - 1)
756				*p++ = ch;
757		}
758	} while (p == nbuf);
759
760	*p = '\0';
761	if (nbuf[0] == '-') {
762		pam_silent = 0;
763		memmove(nbuf, nbuf + 1, strlen(nbuf));
764	} else {
765		pam_silent = PAM_SILENT;
766	}
767	return nbuf;
768}
769
770/*
771 * SIGINT handler for motd().
772 */
773static volatile int motdinterrupt;
774static void
775sigint(int signo __unused)
776{
777	motdinterrupt = 1;
778}
779
780/*
781 * Display the contents of a file (such as /etc/motd).
782 */
783static int
784motd(const char *motdfile)
785{
786	sig_t oldint;
787	FILE *f;
788	int ch;
789
790	if ((f = fopen(motdfile, "r")) == NULL)
791		return (-1);
792	motdinterrupt = 0;
793	oldint = signal(SIGINT, sigint);
794	while ((ch = fgetc(f)) != EOF && !motdinterrupt)
795		putchar(ch);
796	signal(SIGINT, oldint);
797	if (ch != EOF || ferror(f)) {
798		fclose(f);
799		return (-1);
800	}
801	fclose(f);
802	return (0);
803}
804
805/*
806 * SIGALRM handler, to enforce login prompt timeout.
807 *
808 * XXX This can potentially confuse the hell out of PAM.  We should
809 * XXX instead implement a conversation function that returns
810 * XXX PAM_CONV_ERR when interrupted by a signal, and have the signal
811 * XXX handler just set a flag.
812 */
813static void
814timedout(int signo __unused)
815{
816
817	longjmp(timeout_buf, signo);
818}
819
820void
821badlogin(char *name)
822{
823
824	if (failures == 0)
825		return;
826	if (hflag) {
827		syslog(LOG_NOTICE, "%d LOGIN FAILURE%s FROM %s",
828		    failures, failures > 1 ? "S" : "", hostname);
829		syslog(LOG_AUTHPRIV|LOG_NOTICE,
830		    "%d LOGIN FAILURE%s FROM %s, %s",
831		    failures, failures > 1 ? "S" : "", hostname, name);
832	} else {
833		syslog(LOG_NOTICE, "%d LOGIN FAILURE%s ON %s",
834		    failures, failures > 1 ? "S" : "", tty);
835		syslog(LOG_AUTHPRIV|LOG_NOTICE,
836		    "%d LOGIN FAILURE%s ON %s, %s",
837		    failures, failures > 1 ? "S" : "", tty, name);
838	}
839	failures = 0;
840}
841
842const char *
843stypeof(char *ttyid)
844{
845	struct ttyent *t;
846
847	if (ttyid != NULL && *ttyid != '\0') {
848		t = getttynam(ttyid);
849		if (t != NULL && t->ty_type != NULL)
850			return (t->ty_type);
851	}
852	return (TERM_UNKNOWN);
853}
854
855void
856refused(const char *msg, const char *rtype, int lout)
857{
858
859	if (msg != NULL)
860	    printf("%s.\n", msg);
861	if (hflag)
862		syslog(LOG_NOTICE, "LOGIN %s REFUSED (%s) FROM %s ON TTY %s",
863		    pwd->pw_name, rtype, hostname, tty);
864	else
865		syslog(LOG_NOTICE, "LOGIN %s REFUSED (%s) ON TTY %s",
866		    pwd->pw_name, rtype, tty);
867	if (lout)
868		bail(SLEEP_EXIT, 1);
869}
870
871/*
872 * Log a PAM error
873 */
874void
875pam_syslog(const char *msg)
876{
877	syslog(LOG_ERR, "%s: %s", msg, pam_strerror(pamh, pam_err));
878}
879
880/*
881 * Shut down PAM
882 */
883void
884pam_cleanup()
885{
886
887	if (pamh != NULL) {
888		if (pam_session_established) {
889			pam_err = pam_close_session(pamh, 0);
890			if (pam_err != PAM_SUCCESS)
891				pam_syslog("pam_close_session()");
892		}
893		pam_session_established = 0;
894		if (pam_cred_established) {
895			pam_err = pam_setcred(pamh, pam_silent|PAM_DELETE_CRED);
896			if (pam_err != PAM_SUCCESS)
897				pam_syslog("pam_setcred()");
898		}
899		pam_cred_established = 0;
900		pam_end(pamh, pam_err);
901		pamh = NULL;
902	}
903}
904
905/*
906 * Exit, optionally after sleeping a few seconds
907 */
908void
909bail(int sec, int eval)
910{
911
912	pam_cleanup();
913	(void)sleep(sec);
914	exit(eval);
915}
916