diff options
Diffstat (limited to 'src/login.c')
-rw-r--r-- | src/login.c | 1357 |
1 files changed, 1357 insertions, 0 deletions
diff --git a/src/login.c b/src/login.c new file mode 100644 index 0000000..00508cd --- /dev/null +++ b/src/login.c @@ -0,0 +1,1357 @@ +/* + * Copyright (c) 1989 - 1994, Julianne Frances Haugh + * Copyright (c) 1996 - 2001, Marek Michałkiewicz + * Copyright (c) 2001 - 2006, Tomasz Kłoczko + * Copyright (c) 2007 - 2012, Nicolas François + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the copyright holders or contributors may not be used to + * endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <config.h> + +#ident "$Id$" + +#include <errno.h> +#include <grp.h> +#ifndef USE_PAM +#include <lastlog.h> +#endif /* !USE_PAM */ +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <assert.h> +#include "defines.h" +#include "faillog.h" +#include "failure.h" +#include "getdef.h" +#include "prototypes.h" +#include "pwauth.h" +/*@-exitarg@*/ +#include "exitcodes.h" + +#ifdef USE_PAM +#include "pam_defs.h" + +static pam_handle_t *pamh = NULL; + +#define PAM_FAIL_CHECK if (retcode != PAM_SUCCESS) { \ + fprintf(stderr,"\n%s\n",pam_strerror(pamh, retcode)); \ + SYSLOG((LOG_ERR,"%s",pam_strerror(pamh, retcode))); \ + (void) pam_end(pamh, retcode); \ + exit(1); \ + } +#define PAM_END { retcode = pam_close_session(pamh,0); \ + (void) pam_end(pamh,retcode); } + +#endif /* USE_PAM */ + +#ifndef USE_PAM +/* + * Needed for MkLinux DR1/2/2.1 - J. + */ +#ifndef LASTLOG_FILE +#define LASTLOG_FILE "/var/log/lastlog" +#endif +#endif /* !USE_PAM */ + +/* + * Global variables + */ +const char *Prog; + +static const char *hostname = ""; +static /*@null@*/ /*@only@*/char *username = NULL; +static int reason = PW_LOGIN; + +#ifndef USE_PAM +static struct lastlog ll; +#endif /* !USE_PAM */ +static bool pflg = false; +static bool fflg = false; + +#ifdef RLOGIN +static bool rflg = false; +#else /* RLOGIN */ +#define rflg false +#endif /* !RLOGIN */ +static bool hflg = false; +static bool preauth_flag = false; + +static bool amroot; +static char tmsg[256]; + +/* + * External identifiers. + */ + +extern char **newenvp; +extern size_t newenvc; +extern char **environ; + +#ifndef ALARM +#define ALARM 60 +#endif + +#ifndef RETRIES +#define RETRIES 3 +#endif + +/* local function prototypes */ +static void usage (void); +static void setup_tty (void); +static void process_flags (int argc, char *const *argv); +static /*@observer@*/const char *get_failent_user (/*@returned@*/const char *user); +static void update_utmp (const char *user, + const char *tty, + const char *host, +#ifdef USE_UTMPX + /*@null@*/const struct utmpx *utent +#else + /*@null@*/const struct utmp *utent +#endif + ); + +#ifndef USE_PAM +static struct faillog faillog; + +static void bad_time_notify (void); +static void check_nologin (bool login_to_root); +#else +static void get_pam_user (char **ptr_pam_user); +#endif + +static void init_env (void); +static RETSIGTYPE alarm_handler (int); + +/* + * usage - print login command usage and exit + * + * login [ name ] + * login -r hostname (for rlogind) + * login -h hostname (for telnetd, etc.) + * login -f name (for pre-authenticated login: datakit, xterm, etc.) + */ +static void usage (void) +{ + fprintf (stderr, _("Usage: %s [-p] [name]\n"), Prog); + if (!amroot) { + exit (1); + } + fprintf (stderr, _(" %s [-p] [-h host] [-f name]\n"), Prog); +#ifdef RLOGIN + fprintf (stderr, _(" %s [-p] -r host\n"), Prog); +#endif /* RLOGIN */ + exit (1); +} + +static void setup_tty (void) +{ + TERMIO termio; + + if (GTTY (0, &termio) == 0) { /* get terminal characteristics */ + int erasechar; + int killchar; + + /* + * Add your favorite terminal modes here ... + */ + termio.c_lflag |= ISIG | ICANON | ECHO | ECHOE; + termio.c_iflag |= ICRNL; + +#if defined(ECHOKE) && defined(ECHOCTL) + termio.c_lflag |= ECHOKE | ECHOCTL; +#endif +#if defined(ECHOPRT) && defined(NOFLSH) && defined(TOSTOP) + termio.c_lflag &= ~(ECHOPRT | NOFLSH | TOSTOP); +#endif +#ifdef ONLCR + termio.c_oflag |= ONLCR; +#endif + + /* leave these values unchanged if not specified in login.defs */ + erasechar = getdef_num ("ERASECHAR", (int) termio.c_cc[VERASE]); + killchar = getdef_num ("KILLCHAR", (int) termio.c_cc[VKILL]); + termio.c_cc[VERASE] = (cc_t) erasechar; + termio.c_cc[VKILL] = (cc_t) killchar; + /* Make sure the values were valid. + * getdef_num cannot validate this. + */ + if (erasechar != (int) termio.c_cc[VERASE]) { + fprintf (stderr, + _("configuration error - cannot parse %s value: '%d'"), + "ERASECHAR", erasechar); + exit (1); + } + if (killchar != (int) termio.c_cc[VKILL]) { + fprintf (stderr, + _("configuration error - cannot parse %s value: '%d'"), + "KILLCHAR", killchar); + exit (1); + } + + /* + * ttymon invocation prefers this, but these settings + * won't come into effect after the first username login + */ + (void) STTY (0, &termio); + } +} + + +#ifndef USE_PAM +/* + * Tell the user that this is not the right time to login at this tty + */ +static void bad_time_notify (void) +{ + (void) puts (_("Invalid login time")); + (void) fflush (stdout); +} + +static void check_nologin (bool login_to_root) +{ + const char *fname; + + /* + * Check to see if system is turned off for non-root users. + * This would be useful to prevent users from logging in + * during system maintenance. We make sure the message comes + * out for root so she knows to remove the file if she's + * forgotten about it ... + */ + fname = getdef_str ("NOLOGINS_FILE"); + if ((NULL != fname) && (access (fname, F_OK) == 0)) { + FILE *nlfp; + + /* + * Cat the file if it can be opened, otherwise just + * print a default message + */ + nlfp = fopen (fname, "r"); + if (NULL != nlfp) { + int c; + while ((c = getc (nlfp)) != EOF) { + if (c == '\n') { + (void) putchar ('\r'); + } + + (void) putchar (c); + } + (void) fflush (stdout); + (void) fclose (nlfp); + } else { + (void) puts (_("\nSystem closed for routine maintenance")); + } + /* + * Non-root users must exit. Root gets the message, but + * gets to login. + */ + + if (!login_to_root) { + closelog (); + exit (0); + } + (void) puts (_("\n[Disconnect bypassed -- root login allowed.]")); + } +} +#endif /* !USE_PAM */ + +static void process_flags (int argc, char *const *argv) +{ + int arg; + int flag; + + /* + * Check the flags for proper form. Every argument starting with + * "-" must be exactly two characters long. This closes all the + * clever rlogin, telnet, and getty holes. + */ + for (arg = 1; arg < argc; arg++) { + if (argv[arg][0] == '-' && strlen (argv[arg]) > 2) { + usage (); + } + if (strcmp(argv[arg], "--") == 0) { + break; /* stop checking on a "--" */ + } + } + + /* + * Process options. + */ + while ((flag = getopt (argc, argv, "d:fh:pr:")) != EOF) { + switch (flag) { + case 'd': + /* "-d device" ignored for compatibility */ + break; + case 'f': + fflg = true; + break; + case 'h': + hflg = true; + hostname = optarg; + reason = PW_TELNET; + break; +#ifdef RLOGIN + case 'r': + rflg = true; + hostname = optarg; + reason = PW_RLOGIN; + break; +#endif /* RLOGIN */ + case 'p': + pflg = true; + break; + default: + usage (); + } + } + +#ifdef RLOGIN + /* + * Neither -h nor -f should be combined with -r. + */ + + if (rflg && (hflg || fflg)) { + usage (); + } +#endif /* RLOGIN */ + + /* + * Allow authentication bypass only if real UID is zero. + */ + + if ((rflg || fflg || hflg) && !amroot) { + fprintf (stderr, _("%s: Permission denied.\n"), Prog); + exit (1); + } + + /* + * Get the user name. + */ + if (optind < argc) { + assert (NULL == username); + username = xstrdup (argv[optind]); + strzero (argv[optind]); + ++optind; + } + +#ifdef RLOGIN + if (rflg && (NULL != username)) { + usage (); + } +#endif /* RLOGIN */ + if (fflg && (NULL == username)) { + usage (); + } + +} + + +static void init_env (void) +{ +#ifndef USE_PAM + const char *cp; +#endif + char *tmp; + + tmp = getenv ("LANG"); + if (NULL != tmp) { + addenv ("LANG", tmp); + } + + /* + * Add the timezone environmental variable so that time functions + * work correctly. + */ + tmp = getenv ("TZ"); + if (NULL != tmp) { + addenv ("TZ", tmp); + } +#ifndef USE_PAM + else { + cp = getdef_str ("ENV_TZ"); + if (NULL != cp) { + addenv (('/' == *cp) ? tz (cp) : cp, NULL); + } + } +#endif /* !USE_PAM */ + /* + * Add the clock frequency so that profiling commands work + * correctly. + */ + tmp = getenv ("HZ"); + if (NULL != tmp) { + addenv ("HZ", tmp); + } +#ifndef USE_PAM + else { + cp = getdef_str ("ENV_HZ"); + if (NULL != cp) { + addenv (cp, NULL); + } + } +#endif /* !USE_PAM */ +} + + +static RETSIGTYPE alarm_handler (unused int sig) +{ + write (STDERR_FILENO, tmsg, strlen (tmsg)); + _exit (0); +} + +#ifdef USE_PAM +/* + * get_pam_user - Get the username according to PAM + * + * ptr_pam_user shall point to a malloc'ed string (or NULL). + */ +static void get_pam_user (char **ptr_pam_user) +{ + int retcode; + void *ptr_user; + + assert (NULL != ptr_pam_user); + + retcode = pam_get_item (pamh, PAM_USER, (const void **)&ptr_user); + PAM_FAIL_CHECK; + + if (NULL != *ptr_pam_user) { + free (*ptr_pam_user); + } + if (NULL != ptr_user) { + *ptr_pam_user = xstrdup ((const char *)ptr_user); + } else { + *ptr_pam_user = NULL; + } +} +#endif + +/* + * get_failent_user - Return a string that can be used to log failure + * from an user. + * + * This will be either the user argument, or "UNKNOWN". + * + * It is quite common to mistyped the password for username, and passwords + * should not be logged. + */ +static /*@observer@*/const char *get_failent_user (/*@returned@*/const char *user) +{ + const char *failent_user = "UNKNOWN"; + bool log_unkfail_enab = getdef_bool("LOG_UNKFAIL_ENAB"); + + if ((NULL != user) && ('\0' != user[0])) { + if ( log_unkfail_enab + || (getpwnam (user) != NULL)) { + failent_user = user; + } + } + + return failent_user; +} + +/* + * update_utmp - Update or create an utmp entry in utmp, wtmp, utmpw, and + * wtmpx + * + * utent should be the utmp entry returned by get_current_utmp (or + * NULL). + */ +static void update_utmp (const char *user, + const char *tty, + const char *host, +#ifdef USE_UTMPX + /*@null@*/const struct utmpx *utent +#else + /*@null@*/const struct utmp *utent +#endif + ) +{ +#ifdef USE_UTMPX + struct utmpx *utx = prepare_utmpx (user, tty, host, utent); +#else + struct utmp *ut = prepare_utmp (user, tty, host, utent); +#endif /* USE_UTMPX */ + +#ifndef USE_UTMPX + (void) setutmp (ut); /* make entry in the utmp & wtmp files */ + free (ut); +#else + (void) setutmpx (utx); /* make entry in the utmpx & wtmpx files */ + free (utx); +#endif /* USE_UTMPX */ +} + +/* + * login - create a new login session for a user + * + * login is typically called by getty as the second step of a + * new user session. getty is responsible for setting the line + * characteristics to a reasonable set of values and getting + * the name of the user to be logged in. login may also be + * called to create a new user session on a pty for a variety + * of reasons, such as X servers or network logins. + * + * the flags which login supports are + * + * -p - preserve the environment + * -r - perform autologin protocol for rlogin + * -f - do not perform authentication, user is preauthenticated + * -h - the name of the remote host + */ +int main (int argc, char **argv) +{ + const char *tmptty; + char tty[BUFSIZ]; + +#ifdef RLOGIN + char term[128] = ""; +#endif /* RLOGIN */ +#if defined(HAVE_STRFTIME) && !defined(USE_PAM) + char ptime[80]; +#endif + unsigned int delay; + unsigned int retries; + bool subroot = false; +#ifndef USE_PAM + bool is_console; +#endif + int err; + unsigned int timeout; + const char *cp; + const char *tmp; + char fromhost[512]; + struct passwd *pwd = NULL; + char **envp = environ; + const char *failent_user; +#ifdef USE_UTMPX + /*@null@*/struct utmpx *utent; +#else + /*@null@*/struct utmp *utent; +#endif + +#ifdef USE_PAM + int retcode; + pid_t child; + char *pam_user = NULL; +#else + struct spwd *spwd = NULL; +#endif + /* + * Some quick initialization. + */ + + sanitize_env (); + + (void) setlocale (LC_ALL, ""); + (void) bindtextdomain (PACKAGE, LOCALEDIR); + (void) textdomain (PACKAGE); + + initenv (); + + amroot = (getuid () == 0); + Prog = Basename (argv[0]); + + if (geteuid() != 0) { + fprintf (stderr, _("%s: Cannot possibly work without effective root\n"), Prog); + exit (1); + } + + process_flags (argc, argv); + + if ((isatty (0) == 0) || (isatty (1) == 0) || (isatty (2) == 0)) { + exit (1); /* must be a terminal */ + } + + utent = get_current_utmp (); + /* + * Be picky if run by normal users (possible if installed setuid + * root), but not if run by root. This way it still allows logins + * even if your getty is broken, or if something corrupts utmp, + * but users must "exec login" which will use the existing utmp + * entry (will not overwrite remote hostname). --marekm + */ + if (!amroot && (NULL == utent)) { + (void) puts (_("No utmp entry. You must exec \"login\" from the lowest level \"sh\"")); + exit (1); + } + /* NOTE: utent might be NULL afterwards */ + + tmptty = ttyname (0); + if (NULL == tmptty) { + tmptty = "UNKNOWN"; + } + STRFCPY (tty, tmptty); + +#ifndef USE_PAM + is_console = console (tty); +#endif + + if (rflg || hflg) { + /* + * Add remote hostname to the environment. I think + * (not sure) I saw it once on Irix. --marekm + */ + addenv ("REMOTEHOST", hostname); + } + if (fflg) { + preauth_flag = true; + } + if (hflg) { + reason = PW_RLOGIN; + } +#ifdef RLOGIN + if (rflg) { + assert (NULL == username); + username = xmalloc (USER_NAME_MAX_LENGTH + 1); + username[USER_NAME_MAX_LENGTH] = '\0'; + if (do_rlogin (hostname, username, USER_NAME_MAX_LENGTH, term, sizeof term)) { + preauth_flag = true; + } else { + free (username); + username = NULL; + } + } +#endif /* RLOGIN */ + + OPENLOG ("login"); + + setup_tty (); + +#ifndef USE_PAM + (void) umask (getdef_num ("UMASK", GETDEF_DEFAULT_UMASK)); + + { + /* + * Use the ULIMIT in the login.defs file, and if + * there isn't one, use the default value. The + * user may have one for themselves, but otherwise, + * just take what you get. + */ + long limit = getdef_long ("ULIMIT", -1L); + + if (limit != -1) { + set_filesize_limit (limit); + } + } + +#endif + /* + * The entire environment will be preserved if the -p flag + * is used. + */ + if (pflg) { + while (NULL != *envp) { /* add inherited environment, */ + addenv (*envp, NULL); /* some variables change later */ + envp++; + } + } + +#ifdef RLOGIN + if (term[0] != '\0') { + addenv ("TERM", term); + } else +#endif /* RLOGIN */ + { + /* preserve TERM from getty */ + if (!pflg) { + tmp = getenv ("TERM"); + if (NULL != tmp) { + addenv ("TERM", tmp); + } + } + } + + init_env (); + + if (optind < argc) { /* now set command line variables */ + set_env (argc - optind, &argv[optind]); + } + + if (rflg || hflg) { + cp = hostname; +#if defined(HAVE_STRUCT_UTMP_UT_HOST) || defined(USE_UTMPX) + } else if ((NULL != utent) && ('\0' != utent->ut_host[0])) { + cp = utent->ut_host; +#endif /* HAVE_STRUCT_UTMP_UT_HOST */ + } else { + cp = ""; + } + + if ('\0' != *cp) { + snprintf (fromhost, sizeof fromhost, + " on '%.100s' from '%.200s'", tty, cp); + } else { + snprintf (fromhost, sizeof fromhost, + " on '%.100s'", tty); + } + + top: + /* only allow ALARM sec. for login */ + timeout = getdef_unum ("LOGIN_TIMEOUT", ALARM); + snprintf (tmsg, sizeof tmsg, + _("\nLogin timed out after %u seconds.\n"), timeout); + (void) signal (SIGALRM, alarm_handler); + if (timeout > 0) { + (void) alarm (timeout); + } + + environ = newenvp; /* make new environment active */ + delay = getdef_unum ("FAIL_DELAY", 1); + retries = getdef_unum ("LOGIN_RETRIES", RETRIES); + +#ifdef USE_PAM + retcode = pam_start ("login", username, &conv, &pamh); + if (retcode != PAM_SUCCESS) { + fprintf (stderr, + _("login: PAM Failure, aborting: %s\n"), + pam_strerror (pamh, retcode)); + SYSLOG ((LOG_ERR, "Couldn't initialize PAM: %s", + pam_strerror (pamh, retcode))); + exit (99); + } + + /* + * hostname & tty are either set to NULL or their correct values, + * depending on how much we know. We also set PAM's fail delay to + * ours. + * + * PAM_RHOST and PAM_TTY are used for authentication, only use + * information coming from login or from the caller (e.g. no utmp) + */ + retcode = pam_set_item (pamh, PAM_RHOST, hostname); + PAM_FAIL_CHECK; + retcode = pam_set_item (pamh, PAM_TTY, tty); + PAM_FAIL_CHECK; +#ifdef HAS_PAM_FAIL_DELAY + retcode = pam_fail_delay (pamh, 1000000 * delay); + PAM_FAIL_CHECK; +#endif + /* if fflg, then the user has already been authenticated */ + if (!fflg) { + unsigned int failcount = 0; + char hostn[256]; + char loginprompt[256]; /* That's one hell of a prompt :) */ + + /* Make the login prompt look like we want it */ + if (gethostname (hostn, sizeof (hostn)) == 0) { + snprintf (loginprompt, + sizeof (loginprompt), + _("%s login: "), hostn); + } else { + strncpy (loginprompt, _("login: "), + sizeof (loginprompt)); + } + + retcode = pam_set_item (pamh, PAM_USER_PROMPT, loginprompt); + PAM_FAIL_CHECK; + + /* if we didn't get a user on the command line, + set it to NULL */ + get_pam_user (&pam_user); + if ((NULL != pam_user) && ('\0' == pam_user[0])) { + retcode = pam_set_item (pamh, PAM_USER, NULL); + PAM_FAIL_CHECK; + } + + /* + * There may be better ways to deal with some of + * these conditions, but at least this way I don't + * think we'll be giving away information. Perhaps + * someday we can trust that all PAM modules will + * pay attention to failure count and get rid of + * MAX_LOGIN_TRIES? + */ + failcount = 0; + while (true) { + bool failed = false; + + failcount++; +#ifdef HAS_PAM_FAIL_DELAY + if (delay > 0) { + retcode = pam_fail_delay(pamh, 1000000*delay); + PAM_FAIL_CHECK; + } +#endif + + retcode = pam_authenticate (pamh, 0); + + get_pam_user (&pam_user); + failent_user = get_failent_user (pam_user); + + if (retcode == PAM_MAXTRIES) { + SYSLOG ((LOG_NOTICE, + "TOO MANY LOGIN TRIES (%u)%s FOR '%s'", + failcount, fromhost, failent_user)); + fprintf (stderr, + _("Maximum number of tries exceeded (%u)\n"), + failcount); + PAM_END; + exit(0); + } else if (retcode == PAM_ABORT) { + /* Serious problems, quit now */ + (void) fputs (_("login: abort requested by PAM\n"), stderr); + SYSLOG ((LOG_ERR,"PAM_ABORT returned from pam_authenticate()")); + PAM_END; + exit(99); + } else if (retcode != PAM_SUCCESS) { + SYSLOG ((LOG_NOTICE,"FAILED LOGIN (%u)%s FOR '%s', %s", + failcount, fromhost, failent_user, + pam_strerror (pamh, retcode))); + failed = true; + } + + if (!failed) { + break; + } + +#ifdef WITH_AUDIT + audit_fd = audit_open (); + audit_log_acct_message (audit_fd, + AUDIT_USER_LOGIN, + NULL, /* Prog. name */ + "login", + failent_user, + AUDIT_NO_ID, + hostname, + NULL, /* addr */ + tty, + 0); /* result */ + close (audit_fd); +#endif /* WITH_AUDIT */ + + (void) puts (""); + (void) puts (_("Login incorrect")); + + if (failcount >= retries) { + SYSLOG ((LOG_NOTICE, + "TOO MANY LOGIN TRIES (%u)%s FOR '%s'", + failcount, fromhost, failent_user)); + fprintf (stderr, + _("Maximum number of tries exceeded (%u)\n"), + failcount); + PAM_END; + exit(0); + } + + /* + * Let's give it another go around. + * Even if a username was given on the command + * line, prompt again for the username. + */ + retcode = pam_set_item (pamh, PAM_USER, NULL); + PAM_FAIL_CHECK; + } + + /* We don't get here unless they were authenticated above */ + (void) alarm (0); + } + + /* Check the account validity */ + retcode = pam_acct_mgmt (pamh, 0); + if (retcode == PAM_NEW_AUTHTOK_REQD) { + retcode = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK); + } + PAM_FAIL_CHECK; + + /* Open the PAM session */ + get_pam_user (&pam_user); + retcode = pam_open_session (pamh, hushed (pam_user) ? PAM_SILENT : 0); + PAM_FAIL_CHECK; + + /* Grab the user information out of the password file for future usage + * First get the username that we are actually using, though. + * + * From now on, we will discard changes of the user (PAM_USER) by + * PAM APIs. + */ + get_pam_user (&pam_user); + if (NULL != username) { + free (username); + } + username = xstrdup (pam_user); + failent_user = get_failent_user (username); + + pwd = xgetpwnam (username); + if (NULL == pwd) { + SYSLOG ((LOG_ERR, "cannot find user %s", failent_user)); + fprintf (stderr, + _("Cannot find user (%s)\n"), + username); + exit (1); + } + + /* This set up the process credential (group) and initialize the + * supplementary group access list. + * This has to be done before pam_setcred + */ + if (setup_groups (pwd) != 0) { + exit (1); + } + + retcode = pam_setcred (pamh, PAM_ESTABLISH_CRED); + PAM_FAIL_CHECK; + /* NOTE: If pam_setcred changes PAM_USER, this will not be taken + * into account. + */ + +#else /* ! USE_PAM */ + while (true) { /* repeatedly get login/password pairs */ + bool failed; + /* user_passwd is always a pointer to this constant string + * or a passwd or shadow password that will be memzero by + * pw_free / spw_free. + * Do not free() user_passwd. */ + const char *user_passwd = "!"; + + /* Do some cleanup to avoid keeping entries we do not need + * anymore. */ + if (NULL != pwd) { + pw_free (pwd); + pwd = NULL; + } + if (NULL != spwd) { + spw_free (spwd); + spwd = NULL; + } + + failed = false; /* haven't failed authentication yet */ + if (NULL == username) { /* need to get a login id */ + if (subroot) { + closelog (); + exit (1); + } + preauth_flag = false; + username = xmalloc (USER_NAME_MAX_LENGTH + 1); + username[USER_NAME_MAX_LENGTH] = '\0'; + login_prompt (_("\n%s login: "), username, USER_NAME_MAX_LENGTH); + + if ('\0' == username[0]) { + /* Prompt for a new login */ + free (username); + username = NULL; + continue; + } + } + /* Get the username to be used to log failures */ + failent_user = get_failent_user (username); + + pwd = xgetpwnam (username); + if (NULL == pwd) { + preauth_flag = false; + failed = true; + } else { + user_passwd = pwd->pw_passwd; + /* + * If the encrypted password begins with a "!", + * the account is locked and the user cannot + * login, even if they have been + * "pre-authenticated." + */ + if ( ('!' == user_passwd[0]) + || ('*' == user_passwd[0])) { + failed = true; + } + } + + if (strcmp (user_passwd, SHADOW_PASSWD_STRING) == 0) { + spwd = xgetspnam (username); + if (NULL != spwd) { + user_passwd = spwd->sp_pwdp; + } else { + /* The user exists in passwd, but not in + * shadow. SHADOW_PASSWD_STRING indicates + * that the password shall be in shadow. + */ + SYSLOG ((LOG_WARN, + "no shadow password for '%s'%s", + username, fromhost)); + } + } + + /* + * The -r and -f flags provide a name which has already + * been authenticated by some server. + */ + if (preauth_flag) { + goto auth_ok; + } + + if (pw_auth (user_passwd, username, reason, (char *) 0) == 0) { + goto auth_ok; + } + + SYSLOG ((LOG_WARN, "invalid password for '%s' %s", + failent_user, fromhost)); + failed = true; + + auth_ok: + /* + * This is the point where all authenticated users wind up. + * If you reach this far, your password has been + * authenticated and so on. + */ + if ( !failed + && (NULL != pwd) + && (0 == pwd->pw_uid) + && !is_console) { + SYSLOG ((LOG_CRIT, "ILLEGAL ROOT LOGIN %s", fromhost)); + failed = true; + } + if ( !failed + && !login_access (username, ('\0' != *hostname) ? hostname : tty)) { + SYSLOG ((LOG_WARN, "LOGIN '%s' REFUSED %s", + username, fromhost)); + failed = true; + } + if ( (NULL != pwd) + && getdef_bool ("FAILLOG_ENAB") + && !failcheck (pwd->pw_uid, &faillog, failed)) { + SYSLOG ((LOG_CRIT, + "exceeded failure limit for '%s' %s", + username, fromhost)); + failed = true; + } + if (!failed) { + break; + } + + /* don't log non-existent users */ + if ((NULL != pwd) && getdef_bool ("FAILLOG_ENAB")) { + failure (pwd->pw_uid, tty, &faillog); + } + if (getdef_str ("FTMP_FILE") != NULL) { +#ifdef USE_UTMPX + struct utmpx *failent = + prepare_utmpx (failent_user, + tty, + /* FIXME: or fromhost? */hostname, + utent); +#else /* !USE_UTMPX */ + struct utmp *failent = + prepare_utmp (failent_user, + tty, + hostname, + utent); +#endif /* !USE_UTMPX */ + failtmp (failent_user, failent); + free (failent); + } + + retries--; + if (retries <= 0) { + SYSLOG ((LOG_CRIT, "REPEATED login failures%s", + fromhost)); + } + + /* + * If this was a passwordless account and we get here, login + * was denied (securetty, faillog, etc.). There was no + * password prompt, so do it now (will always fail - the bad + * guys won't see that the passwordless account exists at + * all). --marekm + */ + if (user_passwd[0] == '\0') { + pw_auth ("!", username, reason, (char *) 0); + } + + /* + * Authentication of this user failed. + * The username must be confirmed in the next try. + */ + free (username); + username = NULL; + + /* + * Wait a while (a la SVR4 /usr/bin/login) before attempting + * to login the user again. If the earlier alarm occurs + * before the sleep() below completes, login will exit. + */ + if (delay > 0) { + (void) sleep (delay); + } + + (void) puts (_("Login incorrect")); + + /* allow only one attempt with -r or -f */ + if (rflg || fflg || (retries <= 0)) { + closelog (); + exit (1); + } + } /* while (true) */ +#endif /* ! USE_PAM */ + assert (NULL != username); + assert (NULL != pwd); + + (void) alarm (0); /* turn off alarm clock */ + +#ifndef USE_PAM /* PAM does this */ + /* + * porttime checks moved here, after the user has been + * authenticated. now prints a message, as suggested + * by Ivan Nejgebauer <ian@unsux.ns.ac.yu>. --marekm + */ + if ( getdef_bool ("PORTTIME_CHECKS_ENAB") + && !isttytime (username, tty, time ((time_t *) 0))) { + SYSLOG ((LOG_WARN, "invalid login time for '%s'%s", + username, fromhost)); + closelog (); + bad_time_notify (); + exit (1); + } + + check_nologin (pwd->pw_uid == 0); +#endif + + if (getenv ("IFS")) { /* don't export user IFS ... */ + addenv ("IFS= \t\n", NULL); /* ... instead, set a safe IFS */ + } + + if (pwd->pw_shell[0] == '*') { /* subsystem root */ + pwd->pw_shell++; /* skip the '*' */ + subsystem (pwd); /* figure out what to execute */ + subroot = true; /* say I was here again */ + endpwent (); /* close all of the file which were */ + endgrent (); /* open in the original rooted file */ + endspent (); /* system. they will be re-opened */ +#ifdef SHADOWGRP + endsgent (); /* in the new rooted file system */ +#endif + goto top; /* go do all this all over again */ + } + +#ifdef WITH_AUDIT + audit_fd = audit_open (); + audit_log_acct_message (audit_fd, + AUDIT_USER_LOGIN, + NULL, /* Prog. name */ + "login", + username, + AUDIT_NO_ID, + hostname, + NULL, /* addr */ + tty, + 1); /* result */ + close (audit_fd); +#endif /* WITH_AUDIT */ + +#ifndef USE_PAM /* pam_lastlog handles this */ + if ( getdef_bool ("LASTLOG_ENAB") + && pwd->pw_uid <= (uid_t) getdef_ulong ("LASTLOG_UID_MAX", 0xFFFFFFFFUL)) { + /* give last login and log this one */ + dolastlog (&ll, pwd, tty, hostname); + } +#endif + +#ifndef USE_PAM /* PAM handles this as well */ + /* + * Have to do this while we still have root privileges, otherwise we + * don't have access to /etc/shadow. + */ + if (NULL != spwd) { /* check for age of password */ + if (expire (pwd, spwd)) { + /* The user updated her password, get the new + * entries. + * Use the x variants because we need to keep the + * entry for a long time, and there might be other + * getxxyyy in between. + */ + pw_free (pwd); + pwd = xgetpwnam (username); + if (NULL == pwd) { + SYSLOG ((LOG_ERR, + "cannot find user %s after update of expired password", + username)); + exit (1); + } + spw_free (spwd); + spwd = xgetspnam (username); + } + } + setup_limits (pwd); /* nice, ulimit etc. */ +#endif /* ! USE_PAM */ + chown_tty (pwd); + +#ifdef USE_PAM + /* + * We must fork before setuid() because we need to call + * pam_close_session() as root. + */ + (void) signal (SIGINT, SIG_IGN); + child = fork (); + if (child < 0) { + /* error in fork() */ + fprintf (stderr, _("%s: failure forking: %s"), + Prog, strerror (errno)); + PAM_END; + exit (0); + } else if (child != 0) { + /* + * parent - wait for child to finish, then cleanup + * session + */ + wait (NULL); + PAM_END; + exit (0); + } + /* child */ +#endif + + /* If we were init, we need to start a new session */ + if (getppid() == 1) { + setsid(); + if (ioctl(0, TIOCSCTTY, 1) != 0) { + fprintf (stderr, _("TIOCSCTTY failed on %s"), tty); + } + } + + /* + * The utmp entry needs to be updated to indicate the new status + * of the session, the new PID and SID. + */ + update_utmp (username, tty, hostname, utent); + + /* The pwd and spwd entries for the user have been copied. + * + * Close all the files so that unauthorized access won't occur. + */ + endpwent (); /* stop access to password file */ + endgrent (); /* stop access to group file */ + endspent (); /* stop access to shadow passwd file */ +#ifdef SHADOWGRP + endsgent (); /* stop access to shadow group file */ +#endif + + /* Drop root privileges */ +#ifndef USE_PAM + if (setup_uid_gid (pwd, is_console)) +#else + /* The group privileges were already dropped. + * See setup_groups() above. + */ + if (change_uid (pwd)) +#endif + { + exit (1); + } + + setup_env (pwd); /* set env vars, cd to the home dir */ + +#ifdef USE_PAM + { + const char *const *env; + + env = (const char *const *) pam_getenvlist (pamh); + while ((NULL != env) && (NULL != *env)) { + addenv (*env, NULL); + env++; + } + } +#endif + + (void) setlocale (LC_ALL, ""); + (void) bindtextdomain (PACKAGE, LOCALEDIR); + (void) textdomain (PACKAGE); + + if (!hushed (username)) { + addenv ("HUSHLOGIN=FALSE", NULL); + /* + * pam_unix, pam_mail and pam_lastlog should take care of + * this + */ +#ifndef USE_PAM + motd (); /* print the message of the day */ + if ( getdef_bool ("FAILLOG_ENAB") + && (0 != faillog.fail_cnt)) { + failprint (&faillog); + /* Reset the lockout times if logged in */ + if ( (0 != faillog.fail_max) + && (faillog.fail_cnt >= faillog.fail_max)) { + (void) puts (_("Warning: login re-enabled after temporary lockout.")); + SYSLOG ((LOG_WARN, + "login '%s' re-enabled after temporary lockout (%d failures)", + username, (int) faillog.fail_cnt)); + } + } + if ( getdef_bool ("LASTLOG_ENAB") + && pwd->pw_uid <= (uid_t) getdef_ulong ("LASTLOG_UID_MAX", 0xFFFFFFFFUL) + && (ll.ll_time != 0)) { + time_t ll_time = ll.ll_time; + +#ifdef HAVE_STRFTIME + (void) strftime (ptime, sizeof (ptime), + "%a %b %e %H:%M:%S %z %Y", + localtime (&ll_time)); + printf (_("Last login: %s on %s"), + ptime, ll.ll_line); +#else + printf (_("Last login: %.19s on %s"), + ctime (&ll_time), ll.ll_line); +#endif +#ifdef HAVE_LL_HOST /* __linux__ || SUN4 */ + if ('\0' != ll.ll_host[0]) { + printf (_(" from %.*s"), + (int) sizeof ll.ll_host, ll.ll_host); + } +#endif + printf (".\n"); + } + agecheck (spwd); + + mailcheck (); /* report on the status of mail */ +#endif /* !USE_PAM */ + } else { + addenv ("HUSHLOGIN=TRUE", NULL); + } + + ttytype (tty); + + (void) signal (SIGQUIT, SIG_DFL); /* default quit signal */ + (void) signal (SIGTERM, SIG_DFL); /* default terminate signal */ + (void) signal (SIGALRM, SIG_DFL); /* default alarm signal */ + (void) signal (SIGHUP, SIG_DFL); /* added this. --marekm */ + (void) signal (SIGINT, SIG_DFL); /* default interrupt signal */ + + if (0 == pwd->pw_uid) { + SYSLOG ((LOG_NOTICE, "ROOT LOGIN %s", fromhost)); + } else if (getdef_bool ("LOG_OK_LOGINS")) { + SYSLOG ((LOG_INFO, "'%s' logged in %s", username, fromhost)); + } + closelog (); + tmp = getdef_str ("FAKE_SHELL"); + if (NULL != tmp) { + err = shell (tmp, pwd->pw_shell, newenvp); /* fake shell */ + } else { + /* exec the shell finally */ + err = shell (pwd->pw_shell, (char *) 0, newenvp); + } + + return ((err == ENOENT) ? E_CMD_NOTFOUND : E_CMD_NOEXEC); +} + |