login.c revision 94203
11590Srgrimes/*- 21590Srgrimes * Copyright (c) 1980, 1987, 1988, 1991, 1993, 1994 31590Srgrimes * The Regents of the University of California. All rights reserved. 489615Sdes * Copyright (c) 2002 Networks Associates Technologies, Inc. 589615Sdes * All rights reserved. 61590Srgrimes * 789615Sdes * Portions of this software were developed for the FreeBSD Project by 889615Sdes * ThinkSec AS and NAI Labs, the Security Research Division of Network 989615Sdes * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 1089615Sdes * ("CBOSS"), as part of the DARPA CHATS research program. 1189615Sdes * 121590Srgrimes * Redistribution and use in source and binary forms, with or without 131590Srgrimes * modification, are permitted provided that the following conditions 141590Srgrimes * are met: 151590Srgrimes * 1. Redistributions of source code must retain the above copyright 161590Srgrimes * notice, this list of conditions and the following disclaimer. 171590Srgrimes * 2. Redistributions in binary form must reproduce the above copyright 181590Srgrimes * notice, this list of conditions and the following disclaimer in the 191590Srgrimes * documentation and/or other materials provided with the distribution. 201590Srgrimes * 3. All advertising materials mentioning features or use of this software 211590Srgrimes * must display the following acknowledgement: 221590Srgrimes * This product includes software developed by the University of 231590Srgrimes * California, Berkeley and its contributors. 241590Srgrimes * 4. Neither the name of the University nor the names of its contributors 251590Srgrimes * may be used to endorse or promote products derived from this software 261590Srgrimes * without specific prior written permission. 271590Srgrimes * 281590Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 291590Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 301590Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 311590Srgrimes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 321590Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 331590Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 341590Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 351590Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 361590Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 371590Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 381590Srgrimes * SUCH DAMAGE. 391590Srgrimes */ 401590Srgrimes 4187628Sdwmalone#if 0 4287628Sdwmalone#ifndef lint 4387628Sdwmalonestatic char sccsid[] = "@(#)login.c 8.4 (Berkeley) 4/2/94"; 4487628Sdwmalone#endif 4587628Sdwmalone#endif 4687628Sdwmalone 4787233Smarkm#include <sys/cdefs.h> 4887233Smarkm__FBSDID("$FreeBSD: head/usr.bin/login/login.c 94203 2002-04-08 11:07:51Z ru $"); 4987233Smarkm 501590Srgrimes/* 511590Srgrimes * login [ name ] 521590Srgrimes * login -h hostname (for telnetd, etc.) 531590Srgrimes * login -f name (for pre-authenticated login: datakit, xterm, etc.) 541590Srgrimes */ 551590Srgrimes 5687233Smarkm#include <sys/copyright.h> 5787180Smarkm#include <sys/param.h> 5887180Smarkm#include <sys/file.h> 591590Srgrimes#include <sys/stat.h> 601590Srgrimes#include <sys/time.h> 611590Srgrimes#include <sys/resource.h> 6287180Smarkm#include <sys/wait.h> 631590Srgrimes 641590Srgrimes#include <err.h> 651590Srgrimes#include <errno.h> 661590Srgrimes#include <grp.h> 6740102Smarkm#include <libutil.h> 6841079Sjdp#include <login_cap.h> 691590Srgrimes#include <pwd.h> 701590Srgrimes#include <setjmp.h> 711590Srgrimes#include <signal.h> 721590Srgrimes#include <stdio.h> 731590Srgrimes#include <stdlib.h> 741590Srgrimes#include <string.h> 751590Srgrimes#include <syslog.h> 761590Srgrimes#include <ttyent.h> 771590Srgrimes#include <unistd.h> 781590Srgrimes 7941279Sjdp#include <security/pam_appl.h> 8091714Sdes#include <security/openpam.h> 813702Spst 8287173Smarkm#include "login.h" 831590Srgrimes#include "pathnames.h" 841590Srgrimes 8557339Sshin/* wrapper for KAME-special getnameinfo() */ 8657339Sshin#ifndef NI_WITHSCOPEID 8757339Sshin#define NI_WITHSCOPEID 0 8857339Sshin#endif 8957339Sshin 9089994Sdesstatic int auth_pam(void); 9189994Sdesstatic void bail(int, int); 9289994Sdesstatic int export(const char *); 9389994Sdesstatic void export_pam_environment(void); 9489994Sdesstatic int motd(const char *); 9589994Sdesstatic void badlogin(char *); 9689994Sdesstatic char *getloginname(void); 9789994Sdesstatic void pam_syslog(const char *); 9889994Sdesstatic void pam_cleanup(void); 9989994Sdesstatic void refused(const char *, const char *, int); 10089994Sdesstatic const char *stypeof(char *); 10189994Sdesstatic void sigint(int); 10289994Sdesstatic void timedout(int); 10389994Sdesstatic void usage(void); 1041590Srgrimes 10587173Smarkm#define TTYGRPNAME "tty" /* group to own ttys */ 10687173Smarkm#define DEFAULT_BACKOFF 3 10787173Smarkm#define DEFAULT_RETRIES 10 10876786Sobrien#define DEFAULT_PROMPT "login: " 10976786Sobrien#define DEFAULT_PASSWD_PROMPT "Password:" 11089994Sdes#define TERM_UNKNOWN "su" 11187173Smarkm#define DEFAULT_WARN (2L * 7L * 86400L) /* Two weeks */ 11289994Sdes#define NO_SLEEP_EXIT 0 11389994Sdes#define SLEEP_EXIT 5 1141590Srgrimes 1151590Srgrimes/* 1161590Srgrimes * This bounds the time given to login. Not a define so it can 1171590Srgrimes * be patched on machines where it's too small. 1181590Srgrimes */ 11989994Sdesstatic u_int timeout = 300; 1201590Srgrimes 12142272Seivind/* Buffer for signal handling of timeout */ 12289994Sdesstatic jmp_buf timeout_buf; 12342272Seivind 12489994Sdesstruct passwd *pwd; 12589994Sdesstatic int failures; 1261590Srgrimes 12789994Sdesstatic char *envinit[1]; /* empty environment list */ 12889994Sdes 12989994Sdes/* 13089994Sdes * Command line flags and arguments 13189994Sdes */ 13289994Sdesstatic int fflag; /* -f: do not perform authentication */ 13389994Sdesstatic int hflag; /* -h: login from remote host */ 13489994Sdesstatic char *hostname; /* hostname from command line */ 13589994Sdesstatic int pflag; /* -p: preserve environment */ 13689994Sdes 13789994Sdes/* 13889994Sdes * User name 13989994Sdes */ 14089994Sdesstatic char *username; /* user name */ 14189994Sdesstatic char *olduser; /* previous user name */ 14289994Sdes 14389994Sdes/* 14489994Sdes * Prompts 14589994Sdes */ 14689994Sdesstatic char default_prompt[] = DEFAULT_PROMPT; 14794203Srustatic const char *prompt; 14889994Sdesstatic char default_passwd_prompt[] = DEFAULT_PASSWD_PROMPT; 14994203Srustatic const char *passwd_prompt; 15089994Sdes 15189994Sdesstatic char *tty; 15289994Sdes 15389994Sdes/* 15489994Sdes * PAM data 15589994Sdes */ 15689994Sdesstatic pam_handle_t *pamh = NULL; 15791714Sdesstatic struct pam_conv pamc = { openpam_ttyconv, NULL }; 15889994Sdesstatic int pam_err; 15989994Sdesstatic int pam_silent = PAM_SILENT; 16089994Sdesstatic int pam_cred_established; 16189994Sdesstatic int pam_session_established; 16289994Sdes 1631590Srgrimesint 16489994Sdesmain(int argc, char *argv[]) 1651590Srgrimes{ 1661590Srgrimes struct group *gr; 1671590Srgrimes struct stat st; 16889994Sdes int retries, backoff; 16989994Sdes int ask, ch, cnt, quietlog, rootlogin, rval; 17035559Speter uid_t uid, euid; 17146007Sache gid_t egid; 17289994Sdes char *term; 17345431Sbrian char *p, *ttyn; 17442272Seivind char tname[sizeof(_PATH_TTY) + 10]; 17594203Sru char *arg0; 17694203Sru const char *shell = NULL; 17721528Sdavidn login_cap_t *lc = NULL; 17874874Smarkm pid_t pid; 1791590Srgrimes 18042272Seivind (void)signal(SIGQUIT, SIG_IGN); 18142272Seivind (void)signal(SIGINT, SIG_IGN); 18276942Sguido (void)signal(SIGHUP, SIG_IGN); 18342272Seivind if (setjmp(timeout_buf)) { 18442272Seivind if (failures) 18589994Sdes badlogin(username); 18676788Sobrien (void)fprintf(stderr, "Login timed out after %d seconds\n", 18776788Sobrien timeout); 18889994Sdes bail(NO_SLEEP_EXIT, 0); 18942272Seivind } 1901590Srgrimes (void)signal(SIGALRM, timedout); 1911590Srgrimes (void)alarm(timeout); 1921590Srgrimes (void)setpriority(PRIO_PROCESS, 0, 0); 1931590Srgrimes 1941590Srgrimes openlog("login", LOG_ODELAY, LOG_AUTH); 1951590Srgrimes 1961590Srgrimes uid = getuid(); 19735557Speter euid = geteuid(); 19846007Sache egid = getegid(); 19989994Sdes 20024360Simp while ((ch = getopt(argc, argv, "fh:p")) != -1) 2011590Srgrimes switch (ch) { 2021590Srgrimes case 'f': 2031590Srgrimes fflag = 1; 2041590Srgrimes break; 2051590Srgrimes case 'h': 20689994Sdes if (uid != 0) 2071590Srgrimes errx(1, "-h option: %s", strerror(EPERM)); 20889994Sdes if (strlen(optarg) >= MAXHOSTNAMELEN) 20981555Smike errx(1, "-h option: %s: exceeds maximum " 21081555Smike "hostname size", optarg); 21189994Sdes hflag = 1; 2121590Srgrimes hostname = optarg; 2131590Srgrimes break; 2141590Srgrimes case 'p': 2151590Srgrimes pflag = 1; 2161590Srgrimes break; 2171590Srgrimes case '?': 2181590Srgrimes default: 21989994Sdes if (uid == 0) 2201590Srgrimes syslog(LOG_ERR, "invalid flag %c", ch); 22127605Scharnier usage(); 2221590Srgrimes } 2231590Srgrimes argc -= optind; 2241590Srgrimes argv += optind; 2251590Srgrimes 22689994Sdes if (argc > 0) { 22789994Sdes username = strdup(*argv); 22889994Sdes if (username == NULL) 22989994Sdes err(1, "strdup()"); 2301590Srgrimes ask = 0; 23189994Sdes } else { 2321590Srgrimes ask = 1; 23389994Sdes } 2341590Srgrimes 2351590Srgrimes for (cnt = getdtablesize(); cnt > 2; cnt--) 2361590Srgrimes (void)close(cnt); 2371590Srgrimes 23889994Sdes /* 23989994Sdes * Get current TTY 24089994Sdes */ 2411590Srgrimes ttyn = ttyname(STDIN_FILENO); 2421590Srgrimes if (ttyn == NULL || *ttyn == '\0') { 2431590Srgrimes (void)snprintf(tname, sizeof(tname), "%s??", _PATH_TTY); 2441590Srgrimes ttyn = tname; 2451590Srgrimes } 24623985Sdavidn if ((tty = strrchr(ttyn, '/')) != NULL) 2471590Srgrimes ++tty; 2481590Srgrimes else 2491590Srgrimes tty = ttyn; 2501590Srgrimes 25123985Sdavidn /* 25223985Sdavidn * Get "login-retries" & "login-backoff" from default class 25323985Sdavidn */ 25423985Sdavidn lc = login_getclass(NULL); 25589994Sdes prompt = login_getcapstr(lc, "prompt", 25689994Sdes default_prompt, default_prompt); 25776786Sobrien passwd_prompt = login_getcapstr(lc, "passwd_prompt", 25887173Smarkm default_passwd_prompt, default_passwd_prompt); 25989994Sdes retries = login_getcapnum(lc, "login-retries", 26089994Sdes DEFAULT_RETRIES, DEFAULT_RETRIES); 26189994Sdes backoff = login_getcapnum(lc, "login-backoff", 26289994Sdes DEFAULT_BACKOFF, DEFAULT_BACKOFF); 26323985Sdavidn login_close(lc); 26423985Sdavidn lc = NULL; 26521528Sdavidn 26689994Sdes /* 26789994Sdes * Try to authenticate the user until we succeed or time out. 26889994Sdes */ 2691590Srgrimes for (cnt = 0;; ask = 1) { 2701590Srgrimes if (ask) { 2711590Srgrimes fflag = 0; 27289994Sdes if (olduser != NULL) 27389994Sdes free(olduser); 27489994Sdes olduser = username; 27589994Sdes username = getloginname(); 2761590Srgrimes } 2771590Srgrimes rootlogin = 0; 27821528Sdavidn 2791590Srgrimes /* 2801590Srgrimes * Note if trying multiple user names; log failures for 2811590Srgrimes * previous user name, but don't bother logging one failure 2821590Srgrimes * for nonexistent name (mistyped username). 2831590Srgrimes */ 28489994Sdes if (failures && strcmp(olduser, username) != 0) { 2851590Srgrimes if (failures > (pwd ? 0 : 1)) 28689994Sdes badlogin(olduser); 2871590Srgrimes } 2881590Srgrimes 28923985Sdavidn /* 29089994Sdes * Load the PAM policy and set some variables 2911590Srgrimes */ 29289994Sdes pam_err = pam_start("login", username, &pamc, &pamh); 29389994Sdes if (pam_err != PAM_SUCCESS) { 29489994Sdes pam_syslog("pam_start()"); 29589994Sdes bail(NO_SLEEP_EXIT, 1); 2963205Spst } 29789994Sdes pam_err = pam_set_item(pamh, PAM_TTY, tty); 29889994Sdes if (pam_err != PAM_SUCCESS) { 29989994Sdes pam_syslog("pam_set_item(PAM_TTY)"); 30089994Sdes bail(NO_SLEEP_EXIT, 1); 30189994Sdes } 30289994Sdes pam_err = pam_set_item(pamh, PAM_RHOST, hostname); 30389994Sdes if (pam_err != PAM_SUCCESS) { 30489994Sdes pam_syslog("pam_set_item(PAM_RHOST)"); 30589994Sdes bail(NO_SLEEP_EXIT, 1); 30689994Sdes } 30789994Sdes 30889994Sdes pwd = getpwnam(username); 30941279Sjdp if (pwd != NULL && pwd->pw_uid == 0) 31041279Sjdp rootlogin = 1; 3111590Srgrimes 3121590Srgrimes /* 31389994Sdes * If the -f option was specified and the caller is 31489994Sdes * root or the caller isn't changing their uid, don't 31589994Sdes * authenticate. 3161590Srgrimes */ 31789994Sdes if (pwd != NULL && fflag && 31889994Sdes (uid == (uid_t)0 || uid == (uid_t)pwd->pw_uid)) { 31989994Sdes /* already authenticated */ 32089994Sdes rval = 0; 32189994Sdes } else { 32289994Sdes fflag = 0; 32389994Sdes (void)setpriority(PRIO_PROCESS, 0, -4); 32489994Sdes rval = auth_pam(); 32589994Sdes (void)setpriority(PRIO_PROCESS, 0, 0); 3261590Srgrimes } 3271590Srgrimes 32889994Sdes if (pwd && rval == 0) 32989994Sdes break; 33089994Sdes 33189994Sdes pam_cleanup(); 33289994Sdes 3331590Srgrimes (void)printf("Login incorrect\n"); 3341590Srgrimes failures++; 33523985Sdavidn 33623985Sdavidn /* 33789994Sdes * Allow up to 'retry' (10) attempts, but start 33889994Sdes * backing off after 'backoff' (3) attempts. 33923985Sdavidn */ 34023985Sdavidn if (++cnt > backoff) { 34123985Sdavidn if (cnt >= retries) { 3421590Srgrimes badlogin(username); 34389994Sdes bail(SLEEP_EXIT, 1); 3441590Srgrimes } 34538374Sjkoshy sleep((u_int)((cnt - backoff) * 5)); 3461590Srgrimes } 3471590Srgrimes } 3481590Srgrimes 3491590Srgrimes /* committed to login -- turn off timeout */ 3501590Srgrimes (void)alarm((u_int)0); 35176942Sguido (void)signal(SIGHUP, SIG_DFL); 3521590Srgrimes 3531590Srgrimes endpwent(); 3541590Srgrimes 35541279Sjdp /* 35641279Sjdp * Establish the login class. 35741279Sjdp */ 35841279Sjdp lc = login_getpwclass(pwd); 35941279Sjdp 36021528Sdavidn quietlog = login_getcapbool(lc, "hushlogin", 0); 36189994Sdes 36283519Srwatson /* 36383519Srwatson * Switching needed for NFS with root access disabled. 36483519Srwatson * 36583519Srwatson * XXX: This change fails to modify the additional groups for the 36683519Srwatson * process, and as such, may restrict rights normally granted 36783519Srwatson * through those groups. 36883519Srwatson */ 36946007Sache (void)setegid(pwd->pw_gid); 37035557Speter (void)seteuid(rootlogin ? 0 : pwd->pw_uid); 37121528Sdavidn if (!*pwd->pw_dir || chdir(pwd->pw_dir) < 0) { 37226021Spst if (login_getcapbool(lc, "requirehome", 0)) 37323985Sdavidn refused("Home directory not available", "HOMEDIR", 1); 37424485Sdavidn if (chdir("/") < 0) 37523985Sdavidn refused("Cannot find root directory", "ROOTDIR", 1); 37623985Sdavidn if (!quietlog || *pwd->pw_dir) 37723985Sdavidn printf("No home directory.\nLogging in with home = \"/\".\n"); 37887173Smarkm pwd->pw_dir = strdup("/"); 37987173Smarkm if (pwd->pw_dir == NULL) { 38087173Smarkm syslog(LOG_NOTICE, "strdup(): %m"); 38189994Sdes bail(SLEEP_EXIT, 1); 38287173Smarkm } 3831590Srgrimes } 38435557Speter (void)seteuid(euid); 38546007Sache (void)setegid(egid); 38621528Sdavidn if (!quietlog) 38721528Sdavidn quietlog = access(_PATH_HUSHLOGIN, F_OK) == 0; 38889994Sdes 38976788Sobrien shell = login_getcapstr(lc, "shell", pwd->pw_shell, pwd->pw_shell); 39023985Sdavidn if (*pwd->pw_shell == '\0') 39187173Smarkm pwd->pw_shell = strdup(_PATH_BSHELL); 39287173Smarkm if (pwd->pw_shell == NULL) { 39387173Smarkm syslog(LOG_NOTICE, "strdup(): %m"); 39489994Sdes bail(SLEEP_EXIT, 1); 39587173Smarkm } 39623985Sdavidn if (*shell == '\0') /* Not overridden */ 39723985Sdavidn shell = pwd->pw_shell; 39823985Sdavidn if ((shell = strdup(shell)) == NULL) { 39981555Smike syslog(LOG_NOTICE, "strdup(): %m"); 40089994Sdes bail(SLEEP_EXIT, 1); 40123985Sdavidn } 40221528Sdavidn 4032224Sguido /* 4042224Sguido * Set device protections, depending on what terminal the 4052224Sguido * user is logged in. This feature is used on Suns to give 4062224Sguido * console users better privacy. 4072224Sguido */ 4082224Sguido login_fbtab(tty, pwd->pw_uid, pwd->pw_gid); 4092224Sguido 41050124Simp /* 41150124Simp * Clear flags of the tty. None should be set, and when the 41250124Simp * user sets them otherwise, this can cause the chown to fail. 41350124Simp * Since it isn't clear that flags are useful on character 41450124Simp * devices, we just clear them. 41550124Simp */ 41689994Sdes if (ttyn != tname && chflags(ttyn, 0) && errno != EOPNOTSUPP) 41789994Sdes syslog(LOG_ERR, "chflags(%s): %m", ttyn); 41889994Sdes if (ttyn != tname && chown(ttyn, pwd->pw_uid, 41950124Simp (gr = getgrnam(TTYGRPNAME)) ? gr->gr_gid : pwd->pw_gid)) 42050124Simp syslog(LOG_ERR, "chmod(%s): %m", ttyn); 4211590Srgrimes 42223985Sdavidn /* 42323985Sdavidn * Exclude cons/vt/ptys only, assume dialup otherwise 42423985Sdavidn * TODO: Make dialup tty determination a library call 42523985Sdavidn * for consistency (finger etc.) 42623985Sdavidn */ 42789994Sdes if (hflag && isdialuptty(tty)) 4281590Srgrimes syslog(LOG_INFO, "DIALUP %s, %s", tty, pwd->pw_name); 4291590Srgrimes 4303205Spst#ifdef LOGALL 4313205Spst /* 43289994Sdes * Syslog each successful login, so we don't have to watch 43389994Sdes * hundreds of wtmp or lastlogin files. 4343205Spst */ 43589994Sdes if (hflag) 43623985Sdavidn syslog(LOG_INFO, "login from %s on %s as %s", 43789994Sdes hostname, tty, pwd->pw_name); 43823985Sdavidn else 43923985Sdavidn syslog(LOG_INFO, "login on %s as %s", 44023985Sdavidn tty, pwd->pw_name); 44121528Sdavidn#endif 44221528Sdavidn 44323985Sdavidn /* 44489994Sdes * If fflag is on, assume caller/authenticator has logged root 44589994Sdes * login. 44623985Sdavidn */ 44789994Sdes if (rootlogin && fflag == 0) { 44889994Sdes if (hflag) 44923985Sdavidn syslog(LOG_NOTICE, "ROOT LOGIN (%s) ON %s FROM %s", 45089994Sdes username, tty, hostname); 45123985Sdavidn else 45223985Sdavidn syslog(LOG_NOTICE, "ROOT LOGIN (%s) ON %s", 45376788Sobrien username, tty); 45423985Sdavidn } 45523985Sdavidn 45623985Sdavidn /* 45789994Sdes * Destroy environment unless user has requested its 45889994Sdes * preservation - but preserve TERM in all cases 45923985Sdavidn */ 46089994Sdes term = getenv("TERM"); 46121528Sdavidn if (!pflag) 46221528Sdavidn environ = envinit; 46389994Sdes if (term != NULL) 46489994Sdes setenv("TERM", term, 0); 46521528Sdavidn 46623985Sdavidn /* 46774874Smarkm * PAM modules might add supplementary groups during pam_setcred(). 46874874Smarkm */ 46974874Smarkm if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) != 0) { 47074874Smarkm syslog(LOG_ERR, "setusercontext() failed - exiting"); 47189994Sdes bail(NO_SLEEP_EXIT, 1); 47274874Smarkm } 47374874Smarkm 47489994Sdes pam_err = pam_setcred(pamh, pam_silent|PAM_ESTABLISH_CRED); 47589994Sdes if (pam_err != PAM_SUCCESS) { 47689994Sdes pam_syslog("pam_setcred()"); 47789994Sdes bail(NO_SLEEP_EXIT, 1); 47889994Sdes } 47989994Sdes pam_cred_established = 1; 48089994Sdes 48189994Sdes pam_err = pam_open_session(pamh, pam_silent); 48289994Sdes if (pam_err != PAM_SUCCESS) { 48389994Sdes pam_syslog("pam_open_session()"); 48489994Sdes bail(NO_SLEEP_EXIT, 1); 48589994Sdes } 48689994Sdes pam_session_established = 1; 48774874Smarkm 48889994Sdes /* 48989994Sdes * We must fork() before setuid() because we need to call 49089994Sdes * pam_close_session() as root. 49189994Sdes */ 49289994Sdes pid = fork(); 49389994Sdes if (pid < 0) { 49489994Sdes err(1, "fork"); 49589994Sdes } else if (pid != 0) { 49674874Smarkm /* 49789994Sdes * Parent: wait for child to finish, then clean up 49889994Sdes * session. 49974874Smarkm */ 50089994Sdes wait(NULL); 50189994Sdes bail(NO_SLEEP_EXIT, 0); 50274874Smarkm } 50374874Smarkm 50474874Smarkm /* 50589994Sdes * NOTICE: We are now in the child process! 50621528Sdavidn */ 50789994Sdes 50889994Sdes /* 50989994Sdes * Add any environment variables the PAM modules may have set. 51089994Sdes */ 51189994Sdes export_pam_environment(); 51289994Sdes 51389994Sdes /* 51489994Sdes * We're done with PAM now; our parent will deal with the rest. 51589994Sdes */ 51691714Sdes pam_end(pamh, 0); 51789994Sdes pamh = NULL; 51889994Sdes 51989994Sdes /* 52089994Sdes * We don't need to be root anymore, so set the login name and 52189994Sdes * the UID. 52289994Sdes */ 52341279Sjdp if (setlogin(username) != 0) { 52441279Sjdp syslog(LOG_ERR, "setlogin(%s): %m - exiting", username); 52589994Sdes bail(NO_SLEEP_EXIT, 1); 52641279Sjdp } 52741279Sjdp if (setusercontext(lc, pwd, pwd->pw_uid, 52874874Smarkm LOGIN_SETALL & ~(LOGIN_SETLOGIN|LOGIN_SETGROUP)) != 0) { 52921528Sdavidn syslog(LOG_ERR, "setusercontext() failed - exiting"); 53021528Sdavidn exit(1); 5313205Spst } 53221528Sdavidn 53323148Sache (void)setenv("SHELL", pwd->pw_shell, 1); 53421528Sdavidn (void)setenv("HOME", pwd->pw_dir, 1); 53589994Sdes (void)setenv("TERM", stypeof(tty), 0); 53641279Sjdp (void)setenv("LOGNAME", username, 1); 53741279Sjdp (void)setenv("USER", username, 1); 53821528Sdavidn (void)setenv("PATH", rootlogin ? _PATH_STDPATH : _PATH_DEFPATH, 0); 53921528Sdavidn 5401590Srgrimes if (!quietlog) { 54194203Sru const char *cw; 54223985Sdavidn 54323985Sdavidn cw = login_getcapstr(lc, "copyright", NULL, NULL); 54489994Sdes if (cw == NULL || motd(cw) == -1) 54589994Sdes (void)printf("%s", copyright); 54623985Sdavidn 54723985Sdavidn (void)printf("\n"); 54823985Sdavidn 54921528Sdavidn cw = login_getcapstr(lc, "welcome", NULL, NULL); 55089994Sdes if (cw != NULL && access(cw, F_OK) == 0) 55189994Sdes motd(cw); 55289994Sdes else 55389994Sdes motd(_PATH_MOTDFILE); 55423985Sdavidn 55586450Srwatson if (login_getcapbool(lc, "nocheckmail", 0) == 0) { 55686450Srwatson /* $MAIL may have been set by class. */ 55786450Srwatson cw = getenv("MAIL"); 55889994Sdes if (cw == NULL) { 55994203Sru asprintf((char **)&cw, "%s/%s", 56086450Srwatson _PATH_MAILDIR, pwd->pw_name); 56189994Sdes } 56289994Sdes if (cw && stat(cw, &st) == 0 && st.st_size != 0) 56386450Srwatson (void)printf("You have %smail.\n", 56486450Srwatson (st.st_mtime > st.st_atime) ? "new " : ""); 56589994Sdes if (getenv("MAIL") == NULL) 56694203Sru free((char *)cw); 56786450Srwatson } 5681590Srgrimes } 5691590Srgrimes 57021528Sdavidn login_close(lc); 5713205Spst 5721590Srgrimes (void)signal(SIGALRM, SIG_DFL); 5731590Srgrimes (void)signal(SIGQUIT, SIG_DFL); 5741590Srgrimes (void)signal(SIGINT, SIG_DFL); 5751590Srgrimes (void)signal(SIGTSTP, SIG_IGN); 5761590Srgrimes 57723985Sdavidn /* 57823985Sdavidn * Login shells have a leading '-' in front of argv[0] 57923985Sdavidn */ 58089994Sdes p = strrchr(pwd->pw_shell, '/'); 58189994Sdes if (asprintf(&arg0, "-%s", p ? p + 1 : pwd->pw_shell) >= MAXPATHLEN) { 58281555Smike syslog(LOG_ERR, "user: %s: shell exceeds maximum pathname size", 58381555Smike username); 58481555Smike errx(1, "shell exceeds maximum pathname size"); 58589994Sdes } else if (arg0 == NULL) { 58689994Sdes err(1, "asprintf()"); 58781555Smike } 58823985Sdavidn 58989994Sdes execlp(shell, arg0, (char *)0); 59021528Sdavidn err(1, "%s", shell); 59189994Sdes 59289994Sdes /* 59389994Sdes * That's it, folks! 59489994Sdes */ 5951590Srgrimes} 5961590Srgrimes 59741279Sjdp/* 59841279Sjdp * Attempt to authenticate the user using PAM. Returns 0 if the user is 59941279Sjdp * authenticated, or 1 if not authenticated. If some sort of PAM system 60041279Sjdp * error occurs (e.g., the "/etc/pam.conf" file is missing) then this 60141279Sjdp * function returns -1. This can be used as an indication that we should 60241279Sjdp * fall back to a different authentication mechanism. 60341279Sjdp */ 60441279Sjdpstatic int 60589994Sdesauth_pam(void) 60641279Sjdp{ 60741279Sjdp const char *tmpl_user; 60841279Sjdp const void *item; 60941279Sjdp int rval; 61041279Sjdp 61189994Sdes pam_err = pam_authenticate(pamh, pam_silent); 61289994Sdes switch (pam_err) { 61341279Sjdp 61441279Sjdp case PAM_SUCCESS: 61541279Sjdp /* 61641279Sjdp * With PAM we support the concept of a "template" 61741279Sjdp * user. The user enters a login name which is 61841279Sjdp * authenticated by PAM, usually via a remote service 61941279Sjdp * such as RADIUS or TACACS+. If authentication 62041279Sjdp * succeeds, a different but related "template" name 62141279Sjdp * is used for setting the credentials, shell, and 62241279Sjdp * home directory. The name the user enters need only 62341279Sjdp * exist on the remote authentication server, but the 62441279Sjdp * template name must be present in the local password 62541279Sjdp * database. 62641279Sjdp * 62741279Sjdp * This is supported by two various mechanisms in the 62841279Sjdp * individual modules. However, from the application's 62941279Sjdp * point of view, the template user is always passed 63041279Sjdp * back as a changed value of the PAM_USER item. 63141279Sjdp */ 63289994Sdes pam_err = pam_get_item(pamh, PAM_USER, &item); 63389994Sdes if (pam_err == PAM_SUCCESS) { 63489994Sdes tmpl_user = (const char *)item; 63541279Sjdp if (strcmp(username, tmpl_user) != 0) 63641279Sjdp pwd = getpwnam(tmpl_user); 63789994Sdes } else { 63889994Sdes pam_syslog("pam_get_item(PAM_USER)"); 63989994Sdes } 64041279Sjdp rval = 0; 64141279Sjdp break; 64241279Sjdp 64341279Sjdp case PAM_AUTH_ERR: 64441279Sjdp case PAM_USER_UNKNOWN: 64541279Sjdp case PAM_MAXTRIES: 64641279Sjdp rval = 1; 64741279Sjdp break; 64841279Sjdp 64941279Sjdp default: 65089994Sdes pam_syslog("pam_authenticate()"); 65141279Sjdp rval = -1; 65241279Sjdp break; 65341279Sjdp } 65474874Smarkm 65574874Smarkm if (rval == 0) { 65689994Sdes pam_err = pam_acct_mgmt(pamh, pam_silent); 65789994Sdes switch (pam_err) { 65889994Sdes case PAM_SUCCESS: 65989994Sdes break; 66089994Sdes case PAM_NEW_AUTHTOK_REQD: 66189994Sdes pam_err = pam_chauthtok(pamh, 66289994Sdes pam_silent|PAM_CHANGE_EXPIRED_AUTHTOK); 66389994Sdes if (pam_err != PAM_SUCCESS) { 66489994Sdes pam_syslog("pam_chauthtok()"); 66574874Smarkm rval = 1; 66674874Smarkm } 66789994Sdes break; 66889994Sdes default: 66989994Sdes pam_syslog("pam_acct_mgmt()"); 67074874Smarkm rval = 1; 67189994Sdes break; 67274874Smarkm } 67341279Sjdp } 67474874Smarkm 67574874Smarkm if (rval != 0) { 67689994Sdes pam_end(pamh, pam_err); 67774874Smarkm pamh = NULL; 67874874Smarkm } 67989994Sdes return (rval); 68041279Sjdp} 68172215Snectar 68289994Sdes/* 68389994Sdes * Export any environment variables PAM modules may have set 68489994Sdes */ 68589994Sdesstatic void 68687177Smarkmexport_pam_environment() 68772215Snectar{ 68889994Sdes char **pam_env; 68989994Sdes char **pp; 69072215Snectar 69189994Sdes pam_env = pam_getenvlist(pamh); 69289994Sdes if (pam_env != NULL) { 69389994Sdes for (pp = pam_env; *pp != NULL; pp++) { 69489994Sdes (void)export(*pp); 69589994Sdes free(*pp); 69689994Sdes } 69772215Snectar } 69872215Snectar} 69972215Snectar 70072215Snectar/* 70189994Sdes * Perform sanity checks on an environment variable: 70272215Snectar * - Make sure there is an '=' in the string. 70372215Snectar * - Make sure the string doesn't run on too long. 70472215Snectar * - Do not export certain variables. This list was taken from the 70572215Snectar * Solaris pam_putenv(3) man page. 70689994Sdes * Then export it. 70772215Snectar */ 70872215Snectarstatic int 70989994Sdesexport(const char *s) 71072215Snectar{ 71172215Snectar static const char *noexport[] = { 71272215Snectar "SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH", 71372215Snectar "IFS", "PATH", NULL 71472215Snectar }; 71572215Snectar const char **pp; 71672215Snectar size_t n; 71772215Snectar 71872215Snectar if (strlen(s) > 1024 || strchr(s, '=') == NULL) 71989994Sdes return (0); 72072215Snectar if (strncmp(s, "LD_", 3) == 0) 72189994Sdes return (0); 72272215Snectar for (pp = noexport; *pp != NULL; pp++) { 72372215Snectar n = strlen(*pp); 72472215Snectar if (s[n] == '=' && strncmp(s, *pp, n) == 0) 72589994Sdes return (0); 72672215Snectar } 72789994Sdes (void)putenv(s); 72889994Sdes return (1); 72972215Snectar} 73041279Sjdp 73127605Scharnierstatic void 73287177Smarkmusage() 73327605Scharnier{ 73476791Sobrien 73527605Scharnier (void)fprintf(stderr, "usage: login [-fp] [-h hostname] [username]\n"); 73627605Scharnier exit(1); 73727605Scharnier} 7381590Srgrimes 73923985Sdavidn/* 74089994Sdes * Prompt user and read login name from stdin. 74174874Smarkm */ 74289994Sdesstatic char * 74387177Smarkmgetloginname() 7441590Srgrimes{ 74589994Sdes char *nbuf, *p; 7461590Srgrimes int ch; 7471590Srgrimes 74889994Sdes nbuf = malloc(MAXLOGNAME); 74989994Sdes if (nbuf == NULL) 75089994Sdes err(1, "malloc()"); 75189994Sdes do { 75281555Smike (void)printf("%s", prompt); 7531590Srgrimes for (p = nbuf; (ch = getchar()) != '\n'; ) { 7541590Srgrimes if (ch == EOF) { 7551590Srgrimes badlogin(username); 75689994Sdes bail(NO_SLEEP_EXIT, 0); 7571590Srgrimes } 75889994Sdes if (p < nbuf + MAXLOGNAME - 1) 7591590Srgrimes *p++ = ch; 7601590Srgrimes } 76189994Sdes } while (p == nbuf); 76289994Sdes 76389994Sdes *p = '\0'; 76489994Sdes if (nbuf[0] == '-') { 76589994Sdes pam_silent = 0; 76689994Sdes memmove(nbuf, nbuf + 1, strlen(nbuf)); 76789994Sdes } else { 76889994Sdes pam_silent = PAM_SILENT; 7691590Srgrimes } 77089994Sdes return nbuf; 7711590Srgrimes} 7721590Srgrimes 77389994Sdes/* 77489994Sdes * SIGINT handler for motd(). 77589994Sdes */ 77689994Sdesstatic volatile int motdinterrupt; 77789994Sdesstatic void 77889994Sdessigint(int signo __unused) 7791590Srgrimes{ 78021528Sdavidn motdinterrupt = 1; 78121528Sdavidn} 78221528Sdavidn 78389994Sdes/* 78489994Sdes * Display the contents of a file (such as /etc/motd). 78589994Sdes */ 78689994Sdesstatic int 78789994Sdesmotd(const char *motdfile) 78821528Sdavidn{ 7891590Srgrimes sig_t oldint; 79089994Sdes FILE *f; 79189994Sdes int ch; 7921590Srgrimes 79389994Sdes if ((f = fopen(motdfile, "r")) == NULL) 79489994Sdes return (-1); 79521528Sdavidn motdinterrupt = 0; 7961590Srgrimes oldint = signal(SIGINT, sigint); 79789994Sdes while ((ch = fgetc(f)) != EOF && !motdinterrupt) 79889994Sdes putchar(ch); 79989994Sdes signal(SIGINT, oldint); 80089994Sdes if (ch != EOF || ferror(f)) { 80189994Sdes fclose(f); 80289994Sdes return (-1); 80389994Sdes } 80489994Sdes fclose(f); 80589994Sdes return (0); 8061590Srgrimes} 8071590Srgrimes 80889994Sdes/* 80989994Sdes * SIGALRM handler, to enforce login prompt timeout. 81089994Sdes * 81189994Sdes * XXX This can potentially confuse the hell out of PAM. We should 81289994Sdes * XXX instead implement a conversation function that returns 81389994Sdes * XXX PAM_CONV_ERR when interrupted by a signal, and have the signal 81489994Sdes * XXX handler just set a flag. 81589994Sdes */ 81689994Sdesstatic void 81789994Sdestimedout(int signo __unused) 8181590Srgrimes{ 81976788Sobrien 82042272Seivind longjmp(timeout_buf, signo); 8211590Srgrimes} 8221590Srgrimes 8231590Srgrimesvoid 82489994Sdesbadlogin(char *name) 8251590Srgrimes{ 8261590Srgrimes 8271590Srgrimes if (failures == 0) 8281590Srgrimes return; 82989994Sdes if (hflag) { 8301590Srgrimes syslog(LOG_NOTICE, "%d LOGIN FAILURE%s FROM %s", 83189994Sdes failures, failures > 1 ? "S" : "", hostname); 8321590Srgrimes syslog(LOG_AUTHPRIV|LOG_NOTICE, 8331590Srgrimes "%d LOGIN FAILURE%s FROM %s, %s", 83489994Sdes failures, failures > 1 ? "S" : "", hostname, name); 8351590Srgrimes } else { 8361590Srgrimes syslog(LOG_NOTICE, "%d LOGIN FAILURE%s ON %s", 8371590Srgrimes failures, failures > 1 ? "S" : "", tty); 8381590Srgrimes syslog(LOG_AUTHPRIV|LOG_NOTICE, 8391590Srgrimes "%d LOGIN FAILURE%s ON %s, %s", 8401590Srgrimes failures, failures > 1 ? "S" : "", tty, name); 8411590Srgrimes } 84242272Seivind failures = 0; 8431590Srgrimes} 8441590Srgrimes 84587173Smarkmconst char * 84689994Sdesstypeof(char *ttyid) 8471590Srgrimes{ 8481590Srgrimes struct ttyent *t; 84923985Sdavidn 85081555Smike if (ttyid != NULL && *ttyid != '\0') { 85181555Smike t = getttynam(ttyid); 85281555Smike if (t != NULL && t->ty_type != NULL) 85381555Smike return (t->ty_type); 85481555Smike } 85589994Sdes return (TERM_UNKNOWN); 8561590Srgrimes} 8571590Srgrimes 8581590Srgrimesvoid 85989994Sdesrefused(const char *msg, const char *rtype, int lout) 86023985Sdavidn{ 86123985Sdavidn 86223985Sdavidn if (msg != NULL) 86323985Sdavidn printf("%s.\n", msg); 86489994Sdes if (hflag) 86523985Sdavidn syslog(LOG_NOTICE, "LOGIN %s REFUSED (%s) FROM %s ON TTY %s", 86689994Sdes pwd->pw_name, rtype, hostname, tty); 86723985Sdavidn else 86823985Sdavidn syslog(LOG_NOTICE, "LOGIN %s REFUSED (%s) ON TTY %s", 86976788Sobrien pwd->pw_name, rtype, tty); 87023985Sdavidn if (lout) 87189994Sdes bail(SLEEP_EXIT, 1); 87223985Sdavidn} 87323985Sdavidn 87489994Sdes/* 87589994Sdes * Log a PAM error 87689994Sdes */ 87723985Sdavidnvoid 87889994Sdespam_syslog(const char *msg) 8791590Srgrimes{ 88089994Sdes syslog(LOG_ERR, "%s: %s", msg, pam_strerror(pamh, pam_err)); 88189994Sdes} 88223985Sdavidn 88389994Sdes/* 88489994Sdes * Shut down PAM 88589994Sdes */ 88689994Sdesvoid 88789994Sdespam_cleanup() 88889994Sdes{ 88989994Sdes 89089994Sdes if (pamh != NULL) { 89189994Sdes if (pam_session_established) { 89289994Sdes pam_err = pam_close_session(pamh, 0); 89389994Sdes if (pam_err != PAM_SUCCESS) 89489994Sdes pam_syslog("pam_close_session()"); 89589994Sdes } 89689994Sdes pam_session_established = 0; 89789994Sdes if (pam_cred_established) { 89889994Sdes pam_err = pam_setcred(pamh, pam_silent|PAM_DELETE_CRED); 89989994Sdes if (pam_err != PAM_SUCCESS) 90089994Sdes pam_syslog("pam_setcred()"); 90189994Sdes } 90289994Sdes pam_cred_established = 0; 90389994Sdes pam_end(pamh, pam_err); 90489994Sdes pamh = NULL; 90589994Sdes } 90689994Sdes} 90789994Sdes 90889994Sdes/* 90989994Sdes * Exit, optionally after sleeping a few seconds 91089994Sdes */ 91189994Sdesvoid 91289994Sdesbail(int sec, int eval) 91389994Sdes{ 91489994Sdes 91589994Sdes pam_cleanup(); 91689994Sdes (void)sleep(sec); 9171590Srgrimes exit(eval); 9181590Srgrimes} 919