root/usr/src/cmd/login/login.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2023 OmniOS Community Edition (OmniOSce) Association.
 */

/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */

/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */

/*      Copyright (c) 1987, 1988 Microsoft Corporation  */
/*        All Rights Reserved   */

/*
 * For a complete reference to login(1), see the manual page.  However,
 * login has accreted some intentionally undocumented options, which are
 * explained here:
 *
 * -a: This legacy flag appears to be unused.
 *
 * -f <username>: This flag was introduced by PSARC 1995/039 in support
 *    of Kerberos.  But it's not used by Sun's Kerberos implementation.
 *    It is however employed by zlogin(1), since it allows one to tell
 *    login: "This user is authenticated."  In the case of zlogin that's
 *    true because the zone always trusts the global zone.
 *
 * -z <zonename>: This flag is passed to login when zlogin(1) executes a
 *    zone login.  This tells login(1) to skip it's normal CONSOLE check
 *    (i.e. that the root login must be on /dev/console) and tells us the
 *    name of the zone from which the login is occurring.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <unistd.h>     /* For logfile locking */
#include <signal.h>
#include <stdio.h>
#include <sys/stat.h>
#include <string.h>
#include <deflt.h>
#include <grp.h>
#include <fcntl.h>
#include <lastlog.h>
#include <termio.h>
#include <utmpx.h>
#include <stdlib.h>
#include <wait.h>
#include <errno.h>
#include <ctype.h>
#include <syslog.h>
#include <ulimit.h>
#include <libgen.h>
#include <pwd.h>
#include <security/pam_appl.h>
#include <strings.h>
#include <libdevinfo.h>
#include <zone.h>
#include "login_audit.h"

#include <krb5_repository.h>
/*
 *
 *          *** Defines, Macros, and String Constants  ***
 *
 *
 */

#define ISSUEFILE "/etc/issue"  /* file to print before prompt */
#define NOLOGIN "/etc/nologin"  /* file to lock users out during shutdown */

/*
 * These need to be defined for UTMPX management.
 * If we add in the utility functions later, we
 * can remove them.
 */
#define __UPDATE_ENTRY  1
#define __LOGIN         2

/*
 * Intervals to sleep after failed login
 */
#ifndef SLEEPTIME
#define SLEEPTIME 4     /* sleeptime before login incorrect msg */
#endif
static int      Sleeptime = SLEEPTIME;

/*
 * seconds login disabled after allowable number of unsuccessful attempts
 */
#ifndef DISABLETIME
#define DISABLETIME     20
#endif
static int      Disabletime = DISABLETIME;

#define MAXTRYS         5

static int      retry = MAXTRYS;

/*
 * Login logging support
 */
#define LOGINLOG        "/var/adm/loginlog"     /* login log file */
#define LNAME_SIZE      20      /* size of logged logname */
#define TTYN_SIZE       15      /* size of logged tty name */
#define TIME_SIZE       30      /* size of logged time string */
#define ENT_SIZE        (LNAME_SIZE + TTYN_SIZE + TIME_SIZE + 3)
#define L_WAITTIME      5       /* waittime for log file to unlock */
#define LOGTRYS         10      /* depth of 'try' logging */

/*
 * String manipulation macros: SCPYN, SCPYL, EQN and ENVSTRNCAT
 * SCPYL is the safer version of SCPYN
 */
#define SCPYL(a, b)     (void) strlcpy(a, b, sizeof (a))
#define SCPYN(a, b)     (void) strncpy(a, b, sizeof (a))
#define EQN(a, b)       (strncmp(a, b, sizeof (a)-1) == 0)
#define ENVSTRNCAT(to, from) {int deflen; deflen = strlen(to); \
        (void) strncpy((to)+ deflen, (from), sizeof (to) - (1 + deflen)); }

/*
 * Other macros
 */
#define NMAX    sizeof (((struct utmpx *)0)->ut_name)
#define HMAX    sizeof (((struct utmpx *)0)->ut_host)
#define min(a, b)       (((a) < (b)) ? (a) : (b))

/*
 * Various useful files and string constants
 */
#define SHELL           "/usr/bin/sh"
#define SHELL2          "/sbin/sh"
#define SUBLOGIN        "<!sublogin>"
#define LASTLOG         "/var/adm/lastlog"
#define PROG_NAME       "login"
#define HUSHLOGIN       ".hushlogin"

/*
 * Array and Buffer sizes
 */
#define PBUFSIZE 8      /* max significant characters in a password */
#define MAXARGS 63      /* change value below if changing this */
#define MAXARGSWIDTH 2  /* log10(MAXARGS) */
#define MAXENV 1024
#define MAXLINE 2048

/*
 * Miscellaneous constants
 */
#define ROOTUID         0
#define ERROR           1
#define OK              0
#define LOG_ERROR       1
#define DONT_LOG_ERROR  0
#define TRUE            1
#define FALSE           0

/*
 * Counters for counting the number of failed login attempts
 */
static int trys = 0;
static int count = 1;

/*
 * error value for login_exit() audit output (0 == no audit record)
 */
static int      audit_error = 0;

/*
 * Externs a plenty
 */
extern  int     getsecretkey();

/*
 * The current user name
 */
static  char    user_name[NMAX];
static  char    minusnam[16] = "-";

/*
 * login_pid, used to find utmpx entry to update.
 */
static pid_t    login_pid;

/*
 * locale environments to be passed to shells.
 */
static char *localeenv[] = {
        "LANG",
        "LC_CTYPE", "LC_NUMERIC", "LC_TIME", "LC_COLLATE",
        "LC_MONETARY", "LC_MESSAGES", "LC_ALL", 0};
static int locale_envmatch(char *, char *);

/*
 * Environment variable support
 */
static  char    shell[256] = { "SHELL=" };
static  char    home[MAXPATHLEN] = { "HOME=" };
static  char    term[64] = { "TERM=" };
static  char    logname[30] = { "LOGNAME=" };
static  char    timez[100] = { "TZ=" };
static  char    hertz[10] = { "HZ=" };
static  char    path[MAXPATHLEN] = { "PATH=" };
static  char    *newenv[10+MAXARGS] =
        {home, path, logname, hertz, term, 0, 0};
static  char    **envinit = newenv;
static  int     basicenv;
static  char    *zero = (char *)0;
static  char    **envp;
#ifndef NO_MAIL
static  char    mail[30] = { "MAIL=/var/mail/" };
#endif
extern char **environ;
static  char inputline[MAXLINE];

#define MAX_ID_LEN 256
#define MAX_REPOSITORY_LEN 256
#define MAX_PAMSERVICE_LEN 256

static char identity[MAX_ID_LEN];
static char repository[MAX_REPOSITORY_LEN];
static char progname[MAX_PAMSERVICE_LEN];


/*
 * Strings used to prompt the user.
 */
static  char    loginmsg[] = "login: ";
static  char    passwdmsg[] = "Password:";
static  char    incorrectmsg[] = "Login incorrect\n";

/*
 * Password file support
 */
static  struct  passwd *pwd = NULL;
static  char    remote_host[HMAX];
static  char    zone_name[ZONENAME_MAX];

/*
 * Illegal passwd entries.
 */
static  struct  passwd nouser = { "", "no:password", (uid_t)-1 };

/*
 * Log file support
 */
static  char    *log_entry[LOGTRYS];
static  int     writelog = 0;
static  int     lastlogok = 0;
static  struct lastlog ll;
static  int     dosyslog = 0;
static  int     flogin = MAXTRYS;       /* flag for SYSLOG_FAILED_LOGINS */

/*
 * Default file toggles
 */
static  char    *Pndefault      = "/etc/default/login";
static  char    *Altshell       = NULL;
static  char    *Console        = NULL;
static  int     Passreqflag     = 0;

#define DEFUMASK        022
static  mode_t  Umask           = DEFUMASK;
static  char    *Def_tz         = NULL;
static  char    *tmp_tz         = NULL;
static  char    *Def_hertz      = NULL;
#define SET_FSIZ        2                       /* ulimit() command arg */
static  long    Def_ulimit      = 0;
#define MAX_TIMEOUT     (15 * 60)
#define DEF_TIMEOUT     (5 * 60)
static  unsigned Def_timeout    = DEF_TIMEOUT;
static  char    *Def_path       = NULL;
static  char    *Def_supath     = NULL;
#define DEF_PATH        "/usr/bin:"     /* same as PATH */
#define DEF_SUPATH      "/usr/sbin:/usr/bin" /* same as ROOTPATH */

/*
 * Defaults for updating expired passwords
 */
#define DEF_ATTEMPTS    3

/*
 * ttyprompt will point to the environment variable TTYPROMPT.
 * TTYPROMPT is set by ttymon if ttymon already wrote out the prompt.
 */
static  char    *ttyprompt = NULL;
static  char    *ttyn = NULL;

/*
 * Pass inherited environment.  Used by telnetd in support of the telnet
 * ENVIRON option.
 */
static  boolean_t pflag = B_FALSE;
static  boolean_t uflag = B_FALSE;
static  boolean_t Rflag = B_FALSE;
static  boolean_t sflag = B_FALSE;
static  boolean_t Uflag = B_FALSE;
static  boolean_t tflag = B_FALSE;
static  boolean_t hflag = B_FALSE;
static  boolean_t rflag = B_FALSE;
static  boolean_t zflag = B_FALSE;

/*
 * Remote login support
 */
static  char    rusername[NMAX+1], lusername[NMAX+1];
static  char    terminal[MAXPATHLEN];

/*
 * Pre-authentication flag support
 */
static  int     fflag;

static char ** getargs(char *);

static int login_conv(int, const struct pam_message **,
    struct pam_response **, void *);

static struct pam_conv pam_conv = {login_conv, NULL};
static pam_handle_t *pamh;      /* Authentication handle */

/*
 * Function declarations
 */
static  void    turn_on_logging(void);
static  void    defaults(void);
static  void    usage(void);
static  void    process_rlogin(void);
static  void    login_authenticate();
static  void    setup_credentials(void);
static  void    adjust_nice(void);
static  void    update_utmpx_entry(int);
static  void    establish_user_environment(char **);
static  void    print_banner(void);
static  void    display_last_login_time(void);
static  void    exec_the_shell(void);
static  int     process_chroot_logins(void);
static  void    chdir_to_dir_user(void);
static  void    check_log(void);
static  void    validate_account(void);
static  void    doremoteterm(char *);
static  int     get_options(int, char **);
static  void    getstr(char *, int, char *);
static  int     legalenvvar(char *);
static  void    check_for_console(void);
static  void    check_for_dueling_unix(char *);
static  void    get_user_name(void);
static  uint_t  get_audit_id(void);
static  void    login_exit(int)__NORETURN;
static  int     logins_disabled(char *);
static  void    log_bad_attempts(void);
static  int     is_number(char *);

/*
 *                      *** main ***
 *
 *      The primary flow of control is directed in this routine.
 *      Control moves in line from top to bottom calling subfunctions
 *      which perform the bulk of the work.  Many of these calls exit
 *      when a fatal error is encountered and do not return to main.
 *
 *
 */

int
main(int argc, char *argv[], char **renvp)
{
        int sublogin;
        int pam_rc;

        login_pid = getpid();

        /*
         * Set up Defaults and flags
         */
        defaults();
        SCPYL(progname, PROG_NAME);

        /*
         * Set up default umask
         */
        if (Umask > ((mode_t)0777))
                Umask = DEFUMASK;
        (void) umask(Umask);

        /*
         * Set up default timeouts and delays
         */
        if (Def_timeout > MAX_TIMEOUT)
                Def_timeout = MAX_TIMEOUT;
        if (Sleeptime < 0 || Sleeptime > 5)
                Sleeptime = SLEEPTIME;

        (void) alarm(Def_timeout);

        /*
         * Ignore SIGQUIT and SIGINT and set nice to 0
         */
        (void) signal(SIGQUIT, SIG_IGN);
        (void) signal(SIGINT, SIG_IGN);
        (void) nice(0);

        /*
         * Set flag to disable the pid check if you find that you are
         * a subsystem login.
         */
        sublogin = 0;
        if (*renvp && strcmp(*renvp, SUBLOGIN) == 0)
                sublogin = 1;

        /*
         * Parse Arguments
         */
        if (get_options(argc, argv) == -1) {
                usage();
                audit_error = ADT_FAIL_VALUE_BAD_CMD;
                login_exit(1);
        }

        /*
         * if devicename is not passed as argument, call ttyname(0)
         */
        if (ttyn == NULL) {
                ttyn = ttyname(0);
                if (ttyn == NULL)
                        ttyn = "/dev/???";
        }

        /*
         * Call pam_start to initiate a PAM authentication operation
         */

        if ((pam_rc = pam_start(progname, user_name, &pam_conv, &pamh))
            != PAM_SUCCESS) {
                audit_error = ADT_FAIL_PAM + pam_rc;
                login_exit(1);
        }
        if ((pam_rc = pam_set_item(pamh, PAM_TTY, ttyn)) != PAM_SUCCESS) {
                audit_error = ADT_FAIL_PAM + pam_rc;
                login_exit(1);
        }
        if ((pam_rc = pam_set_item(pamh, PAM_RHOST, remote_host)) !=
            PAM_SUCCESS) {
                audit_error = ADT_FAIL_PAM + pam_rc;
                login_exit(1);
        }

        /*
         * We currently only support special handling of the KRB5 PAM repository
         */
        if ((Rflag && strlen(repository)) &&
            strcmp(repository, KRB5_REPOSITORY_NAME) == 0 &&
            (uflag && strlen(identity))) {
                krb5_repository_data_t krb5_data;
                pam_repository_t pam_rep_data;

                krb5_data.principal = identity;
                krb5_data.flags = SUNW_PAM_KRB5_ALREADY_AUTHENTICATED;

                pam_rep_data.type = repository;
                pam_rep_data.scope = (void *)&krb5_data;
                pam_rep_data.scope_len = sizeof (krb5_data);

                (void) pam_set_item(pamh, PAM_REPOSITORY,
                    (void *)&pam_rep_data);
        }

        /*
         * Open the log file which contains a record of successful and failed
         * login attempts
         */
        turn_on_logging();

        /*
         * say "hi" to syslogd ..
         */
        openlog("login", 0, LOG_AUTH);

        /*
         * Do special processing for -r (rlogin) flag
         */
        if (rflag)
                process_rlogin();

        /*
         * validate user
         */
        /* we are already authenticated. fill in what we must, then continue */
        if (fflag) {
                if ((pwd = getpwnam(user_name)) == NULL) {
                        audit_error = ADT_FAIL_VALUE_USERNAME;

                        log_bad_attempts();
                        (void) printf("Login failed: unknown user '%s'.\n",
                            user_name);
                        login_exit(1);
                }
        } else {
                /*
                 * Perform the primary login authentication activity.
                 */
                login_authenticate();
        }

        /* change root login, then we exec another login and try again */
        if (process_chroot_logins() != OK)
                login_exit(1);

        /*
         * If root login and not on system console then call exit(2)
         */
        check_for_console();

        /*
         * Check to see if a shutdown is in progress, if it is and
         * we are not root then throw the user off the system
         */
        if (logins_disabled(user_name) == TRUE) {
                audit_error = ADT_FAIL_VALUE_LOGIN_DISABLED;
                login_exit(1);
        }

        if (pwd->pw_uid == 0) {
                if (Def_supath != NULL)
                        Def_path = Def_supath;
                else
                        Def_path = DEF_SUPATH;
        }

        /*
         * Check account expiration and passwd aging
         */
        validate_account();

        /*
         * We only get here if we've been authenticated.
         */

        /*
         * Now we set up the environment for the new user, which includes
         * the users ulimit, nice value, ownership of this tty, uid, gid,
         * and environment variables.
         */
        if (Def_ulimit > 0L && ulimit(SET_FSIZ, Def_ulimit) < 0L)
                (void) printf("Could not set ULIMIT to %ld\n", Def_ulimit);

        /* di_devperm_login() sends detailed errors to syslog */
        if (di_devperm_login((const char *)ttyn, pwd->pw_uid, pwd->pw_gid,
            NULL) == -1) {
                (void) fprintf(stderr, "error processing /etc/logindevperm,"
                    " see syslog for more details\n");
        }

        adjust_nice();          /* passwd file can specify nice value */

        setup_credentials();    /* Set user credentials  - exits on failure */

        /*
         * NOTE: telnetd and rlogind rely upon this updating of utmpx
         * to indicate that the authentication completed  successfully,
         * pam_open_session was called and therefore they are required to
         * call pam_close_session.
         */
        update_utmpx_entry(sublogin);

        /* set the real (and effective) UID */
        if (setuid(pwd->pw_uid) == -1) {
                login_exit(1);
        }

        /*
         * Set up the basic environment for the exec.  This includes
         * HOME, PATH, LOGNAME, SHELL, TERM, TZ, HZ, and MAIL.
         */
        chdir_to_dir_user();

        establish_user_environment(renvp);

        (void) pam_end(pamh, PAM_SUCCESS);      /* Done using PAM */
        pamh = NULL;

        if (pwd->pw_uid == 0) {
                if (dosyslog) {
                        if (remote_host[0]) {
                                syslog(LOG_NOTICE, "ROOT LOGIN %s FROM %.*s",
                                    ttyn, HMAX, remote_host);
                        } else
                                syslog(LOG_NOTICE, "ROOT LOGIN %s", ttyn);
                }
        }
        closelog();

        (void) signal(SIGQUIT, SIG_DFL);
        (void) signal(SIGINT, SIG_DFL);

        /*
         * Display some useful information to the new user like the banner
         * and last login time if not a quiet login.
         */

        if (access(HUSHLOGIN, F_OK) != 0) {
                print_banner();
                display_last_login_time();
        }

        /*
         * Set SIGXCPU and SIGXFSZ to default disposition.
         * Shells inherit signal disposition from parent.
         * And the shells should have default dispositions
         * for the two below signals.
         */
        (void) signal(SIGXCPU, SIG_DFL);
        (void) signal(SIGXFSZ, SIG_DFL);

        /*
         * Now fire off the shell of choice
         */
        exec_the_shell();

        /*
         * All done
         */
        login_exit(1);
        return (0);
}


/*
 *                      *** Utility functions ***
 */



/*
 * donothing & catch    - Signal catching functions
 */

/*ARGSUSED*/
static void
donothing(int sig)
{
        if (pamh)
                (void) pam_end(pamh, PAM_ABORT);
}

#ifdef notdef
static  int     intrupt;

/*ARGSUSED*/
static void
catch(int sig)
{
        ++intrupt;
}
#endif

/*
 *                      *** Bad login logging support ***
 */

/*
 * badlogin()           - log to the log file 'trys'
 *                        unsuccessful attempts
 */

static void
badlogin(void)
{
        int retval, count1, fildes;

        /*
         * Tries to open the log file. If succeed, lock it and write
         * in the failed attempts
         */
        if ((fildes = open(LOGINLOG, O_APPEND|O_WRONLY)) != -1) {

                (void) sigset(SIGALRM, donothing);
                (void) alarm(L_WAITTIME);
                retval = lockf(fildes, F_LOCK, 0L);
                (void) alarm(0);
                (void) sigset(SIGALRM, SIG_DFL);
                if (retval == 0) {
                        for (count1 = 0; count1 < trys; count1++)
                                (void) write(fildes, log_entry[count1],
                                    (unsigned)strlen(log_entry[count1]));
                        (void) lockf(fildes, F_ULOCK, 0L);
                }
                (void) close(fildes);
        }
}


/*
 * log_bad_attempts     - log each bad login attempt - called from
 *                        login_authenticate.  Exits when the maximum attempt
 *                        count is exceeded.
 */

static void
log_bad_attempts(void)
{
        time_t timenow;

        if (trys >= LOGTRYS)
                return;
        if (writelog) {
                (void) time(&timenow);
                (void) strncat(log_entry[trys], user_name, LNAME_SIZE);
                (void) strncat(log_entry[trys], ":", (size_t)1);
                (void) strncat(log_entry[trys], ttyn, TTYN_SIZE);
                (void) strncat(log_entry[trys], ":", (size_t)1);
                (void) strncat(log_entry[trys], ctime(&timenow), TIME_SIZE);
                trys++;
        }
        if (count > flogin) {
                if ((pwd = getpwnam(user_name)) != NULL) {
                        if (remote_host[0]) {
                                syslog(LOG_NOTICE,
                                    "Login failure on %s from %.*s, "
                                    "%.*s", ttyn, HMAX, remote_host,
                                    NMAX, user_name);
                        } else {
                                syslog(LOG_NOTICE,
                                    "Login failure on %s, %.*s",
                                    ttyn, NMAX, user_name);
                        }
                } else {
                        if (remote_host[0]) {
                                syslog(LOG_NOTICE,
                                    "Login failure on %s from %.*s",
                                    ttyn, HMAX, remote_host);
                        } else {
                                syslog(LOG_NOTICE,
                                    "Login failure on %s", ttyn);
                        }
                }
        }
}


/*
 * turn_on_logging      - if the logfile exist, turn on attempt logging and
 *                        initialize the string storage area
 */

static void
turn_on_logging(void)
{
        struct stat dbuf;
        int i;

        if (stat(LOGINLOG, &dbuf) == 0) {
                writelog = 1;
                for (i = 0; i < LOGTRYS; i++) {
                        if (!(log_entry[i] = malloc((size_t)ENT_SIZE))) {
                                writelog = 0;
                                break;
                        }
                        *log_entry[i] = '\0';
                }
        }
}


/*
 * login_conv():
 *      This is the conv (conversation) function called from
 *      a PAM authentication module to print error messages
 *      or garner information from the user.
 */
static int
login_conv(int num_msg, const struct pam_message **msg,
    struct pam_response **response, void *appdata_ptr)
{
        const struct pam_message *m;
        struct pam_response *r;
        char *temp;
        int k, i;

        if (num_msg <= 0)
                return (PAM_CONV_ERR);

        *response = calloc(num_msg, sizeof (struct pam_response));
        if (*response == NULL)
                return (PAM_BUF_ERR);

        k = num_msg;
        m = *msg;
        r = *response;
        while (k--) {

                switch (m->msg_style) {

                case PAM_PROMPT_ECHO_OFF:
                        errno = 0;
                        temp = getpassphrase(m->msg);
                        if (temp != NULL) {
                                if (errno == EINTR)
                                        return (PAM_CONV_ERR);

                                r->resp = strdup(temp);
                                if (r->resp == NULL) {
                                        /* free responses */
                                        r = *response;
                                        for (i = 0; i < num_msg; i++, r++) {
                                                if (r->resp)
                                                        free(r->resp);
                                        }
                                        free(*response);
                                        *response = NULL;
                                        return (PAM_BUF_ERR);
                                }
                        }

                        m++;
                        r++;
                        break;

                case PAM_PROMPT_ECHO_ON:
                        if (m->msg != NULL)
                                (void) fputs(m->msg, stdout);
                        r->resp = calloc(1, PAM_MAX_RESP_SIZE);
                        if (r->resp == NULL) {
                                /* free responses */
                                r = *response;
                                for (i = 0; i < num_msg; i++, r++) {
                                        if (r->resp)
                                                free(r->resp);
                                }
                                free(*response);
                                *response = NULL;
                                return (PAM_BUF_ERR);
                        }
                        /*
                         * The response might include environment variables
                         * information. We should store that information in
                         * envp if there is any; otherwise, envp is set to
                         * NULL.
                         */
                        bzero((void *)inputline, MAXLINE);

                        envp = getargs(inputline);

                        /* If we read in any input, process it. */
                        if (inputline[0] != '\0') {
                                int len;

                                if (envp != (char **)NULL)
                                        /*
                                         * If getargs() did not return NULL,
                                         * *envp is the first string in
                                         * inputline. envp++ makes envp point
                                         * to environment variables information
                                         *  or be NULL.
                                         */
                                        envp++;

                                (void) strncpy(r->resp, inputline,
                                    PAM_MAX_RESP_SIZE-1);
                                r->resp[PAM_MAX_RESP_SIZE-1] = '\0';
                                len = strlen(r->resp);
                                if (r->resp[len-1] == '\n')
                                        r->resp[len-1] = '\0';
                        } else {
                                login_exit(1);
                        }
                        m++;
                        r++;
                        break;

                case PAM_ERROR_MSG:
                        if (m->msg != NULL) {
                                (void) fputs(m->msg, stderr);
                                (void) fputs("\n", stderr);
                        }
                        m++;
                        r++;
                        break;
                case PAM_TEXT_INFO:
                        if (m->msg != NULL) {
                                (void) fputs(m->msg, stdout);
                                (void) fputs("\n", stdout);
                        }
                        m++;
                        r++;
                        break;

                default:
                        break;
                }
        }
        return (PAM_SUCCESS);
}

/*
 * verify_passwd - Authenticates the user.
 *      Returns: PAM_SUCCESS if authentication successful,
 *               PAM error code if authentication fails.
 */

static int
verify_passwd(void)
{
        int error;
        const char *user;
        int flag = (Passreqflag ? PAM_DISALLOW_NULL_AUTHTOK : 0);

        /*
         * PAM authenticates the user for us.
         */
        error = pam_authenticate(pamh, flag);

        /* get the user_name from the pam handle */
        (void) pam_get_item(pamh, PAM_USER, (const void **)&user);

        if (user == NULL || *user == '\0')
                return (PAM_SYSTEM_ERR);

        SCPYL(user_name, user);
        check_for_dueling_unix(user_name);

        if (((pwd = getpwnam(user_name)) == NULL) &&
            (error != PAM_USER_UNKNOWN)) {
                return (PAM_SYSTEM_ERR);
        }

        return (error);
}

/*
 * quotec               - Called by getargs
 */

static int
quotec(void)
{
        int c, i, num;

        switch (c = getc(stdin)) {

                case 'n':
                        c = '\n';
                        break;

                case 'r':
                        c = '\r';
                        break;

                case 'v':
                        c = '\013';
                        break;

                case 'b':
                        c = '\b';
                        break;

                case 't':
                        c = '\t';
                        break;

                case 'f':
                        c = '\f';
                        break;

                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                        for (num = 0, i = 0; i < 3; i++) {
                                num = num * 8 + (c - '0');
                                if ((c = getc(stdin)) < '0' || c > '7')
                                        break;
                        }
                        (void) ungetc(c, stdin);
                        c = num & 0377;
                        break;

                default:
                        break;
        }
        return (c);
}

/*
 * getargs              - returns an input line.  Exits if EOF encountered.
 */
#define WHITESPACE      0
#define ARGUMENT        1

static char **
getargs(char *input_line)
{
        static char envbuf[MAXLINE];
        static char *args[MAXARGS];
        char *ptr, **answer;
        int c;
        int state;
        char *p = input_line;

        ptr = envbuf;
        answer = &args[0];
        state = WHITESPACE;

        while ((c = getc(stdin)) != EOF && answer < &args[MAXARGS-1]) {

                *(input_line++) = c;

                switch (c) {

                case '\n':
                        if (ptr == &envbuf[0])
                                return ((char **)NULL);
                        *input_line = *ptr = '\0';
                        *answer = NULL;
                        return (&args[0]);

                case ' ':
                case '\t':
                        if (state == ARGUMENT) {
                                *ptr++ = '\0';
                                state = WHITESPACE;
                        }
                        break;

                case '\\':
                        c = quotec();
                        /* FALLTHROUGH */

                default:
                        if (state == WHITESPACE) {
                                *answer++ = ptr;
                                state = ARGUMENT;
                        }
                        *ptr++ = c;
                }

                /* Attempt at overflow, exit */
                if (input_line - p >= MAXLINE - 1 ||
                    ptr >= &envbuf[sizeof (envbuf) - 1]) {
                        audit_error = ADT_FAIL_VALUE_INPUT_OVERFLOW;
                        login_exit(1);
                }
        }

        /*
         * If we left loop because an EOF was received or we've overflown
         * args[], exit immediately.
         */
        login_exit(0);
        /* NOTREACHED */
}

/*
 * get_user_name        - Gets the user name either passed in, or from the
 *                        login: prompt.
 */

static void
get_user_name(void)
{
        FILE    *fp;

        if ((fp = fopen(ISSUEFILE, "r")) != NULL) {
                char    *ptr, buffer[BUFSIZ];
                while ((ptr = fgets(buffer, sizeof (buffer), fp)) != NULL) {
                        (void) fputs(ptr, stdout);
                }
                (void) fclose(fp);
        }

        /*
         * if TTYPROMPT is not set, use our own prompt
         * otherwise, use ttyprompt. We just set PAM_USER_PROMPT
         * and let the module do the prompting.
         */

        if ((ttyprompt == NULL) || (*ttyprompt == '\0'))
                (void) pam_set_item(pamh, PAM_USER_PROMPT, (void *)loginmsg);
        else
                (void) pam_set_item(pamh, PAM_USER_PROMPT, (void *)ttyprompt);

        envp = &zero; /* XXX: is this right? */
}


/*
 * Check_for_dueling_unix   -   Check to see if the another login is talking
 *                              to the line we've got open as a login port
 *                              Exits if we're talking to another unix system
 */

static void
check_for_dueling_unix(char *inputline)
{
        if (EQN(loginmsg, inputline) || EQN(passwdmsg, inputline) ||
            EQN(incorrectmsg, inputline)) {
                (void) printf("Looking at a login line.\n");
                login_exit(8);
        }
}

/*
 * logins_disabled -    if the file /etc/nologin exists and the user is not
 *                      root then do not permit them to login
 */
static int
logins_disabled(char *user_name)
{
        FILE    *nlfd;
        int     c;
        if (!EQN("root", user_name) &&
            ((nlfd = fopen(NOLOGIN, "r")) != (FILE *)NULL)) {
                while ((c = getc(nlfd)) != EOF)
                        (void) putchar(c);
                (void) fflush(stdout);
                (void) sleep(5);
                return (TRUE);
        }
        return (FALSE);
}

#define DEFAULT_CONSOLE "/dev/console"

/*
 * check_for_console -  Checks if we're getting a root login on the
 *                      console, or a login from the global zone. Exits if not.
 *
 * If CONSOLE is set to /dev/console in /etc/default/login, then root logins
 * on /dev/vt/# are permitted as well. /dev/vt/# does not exist in non-global
 * zones, but checking them does no harm.
 */
static void
check_for_console(void)
{
        const char *consoles[] = { "/dev/console", "/dev/vt/", NULL };
        int i;

        if (pwd == NULL || pwd->pw_uid != 0 || zflag != B_FALSE ||
            Console == NULL)
                return;

        if (strcmp(Console, DEFAULT_CONSOLE) == 0) {
                for (i = 0; consoles[i] != NULL; i ++) {
                        if (strncmp(ttyn, consoles[i],
                            strlen(consoles[i])) == 0)
                                return;
                }
        } else {
                if (strcmp(ttyn, Console) == 0)
                        return;
        }

        (void) printf("Not on system console\n");

        audit_error = ADT_FAIL_VALUE_CONSOLE;
        login_exit(10);

}

/*
 * List of environment variables or environment variable prefixes that should
 * not be propagated across logins, such as when the login -p option is used.
 */
static const char *const illegal[] = {
        "SHELL=",
        "HOME=",
        "LOGNAME=",
#ifndef NO_MAIL
        "MAIL=",
#endif
        "CDPATH=",
        "IFS=",
        "PATH=",
        "LD_",
        "SMF_",
        NULL
};

/*
 * legalenvvar          - Is it legal to insert this environmental variable?
 */

static int
legalenvvar(char *s)
{
        const char *const *p;

        for (p = &illegal[0]; *p; p++) {
                if (strncmp(s, *p, strlen(*p)) == 0)
                        return (0);
        }

        return (1);
}


/*
 * getstr               - Get a string from standard input
 *                        Calls exit if read(2) fails.
 */

static void
getstr(char *buf, int cnt, char *err)
{
        char c;

        do {
                if (read(0, &c, 1) != 1)
                        login_exit(1);
                *buf++ = c;
        } while (--cnt > 1 && c != 0);

        *buf = 0;
        err = err;      /* For lint */
}


/*
 * defaults             - read defaults
 */

static void
defaults(void)
{
        int  flags;
        char *ptr;

        if (defopen(Pndefault) == 0) {
                /*
                 * ignore case
                 */
                flags = defcntl(DC_GETFLAGS, 0);
                TURNOFF(flags, DC_CASE);
                (void) defcntl(DC_SETFLAGS, flags);

                if ((Console = defread("CONSOLE=")) != NULL)
                        Console = strdup(Console);

                if ((Altshell = defread("ALTSHELL=")) != NULL)
                        Altshell = strdup(Altshell);

                if ((ptr = defread("PASSREQ=")) != NULL &&
                    strcasecmp("YES", ptr) == 0)
                                Passreqflag = 1;

                if ((Def_tz = defread("TIMEZONE=")) != NULL)
                        Def_tz = strdup(Def_tz);

                if ((Def_hertz = defread("HZ=")) != NULL)
                        Def_hertz = strdup(Def_hertz);

                if ((Def_path   = defread("PATH=")) != NULL)
                        Def_path = strdup(Def_path);

                if ((Def_supath = defread("SUPATH=")) != NULL)
                        Def_supath = strdup(Def_supath);

                if ((ptr = defread("ULIMIT=")) != NULL)
                        Def_ulimit = atol(ptr);

                if ((ptr = defread("TIMEOUT=")) != NULL)
                        Def_timeout = (unsigned)atoi(ptr);

                if ((ptr = defread("UMASK=")) != NULL)
                        if (sscanf(ptr, "%lo", &Umask) != 1)
                                Umask = DEFUMASK;

                if ((ptr = defread("SLEEPTIME=")) != NULL) {
                        if (is_number(ptr))
                                Sleeptime = atoi(ptr);
                }

                if ((ptr = defread("DISABLETIME=")) != NULL) {
                        if (is_number(ptr))
                                Disabletime = atoi(ptr);
                }

                if ((ptr = defread("SYSLOG=")) != NULL)
                        dosyslog = strcmp(ptr, "YES") == 0;

                if ((ptr = defread("RETRIES=")) != NULL) {
                        if (is_number(ptr))
                                retry = atoi(ptr);
                }

                if ((ptr = defread("SYSLOG_FAILED_LOGINS=")) != NULL) {
                        if (is_number(ptr))
                                flogin = atoi(ptr);
                        else
                                flogin = retry;
                } else
                        flogin = retry;
                (void) defopen((char *)NULL);
        }
}


/*
 * get_options(argc, argv)
 *                      - parse the cmd line.
 *                      - return 0 if successful, -1 if failed.
 *                      Calls login_exit() on misuse of -r, -h, and -z flags
 */

static  int
get_options(int argc, char *argv[])
{
        int     c;
        int     errflg = 0;
        char    sflagname[NMAX+1];
        const   char *flags_message = "Only one of -r, -h and -z allowed\n";

        while ((c = getopt(argc, argv, "u:s:R:f:h:r:pad:t:U:z:")) != -1) {
                switch (c) {
                case 'a':
                        break;

                case 'd':
                        /*
                         * Must be root to pass in device name
                         * otherwise we exit() as punishment for trying.
                         */
                        if (getuid() != 0 || geteuid() != 0) {
                                audit_error = ADT_FAIL_VALUE_DEVICE_PERM;
                                login_exit(1);  /* sigh */
                                /*NOTREACHED*/
                        }
                        ttyn = optarg;
                        break;

                case 'h':
                        if (hflag || rflag || zflag) {
                                (void) fprintf(stderr, flags_message);
                                login_exit(1);
                        }
                        hflag = B_TRUE;
                        SCPYL(remote_host, optarg);
                        if (argv[optind]) {
                                if (argv[optind][0] != '-') {
                                        SCPYL(terminal, argv[optind]);
                                        optind++;
                                } else {
                                        /*
                                         * Allow "login -h hostname -" to
                                         * skip setting up an username as "-".
                                         */
                                        if (argv[optind][1] == '\0')
                                                optind++;
                                }

                        }
                        SCPYL(progname, "telnet");
                        break;

                case 'r':
                        if (hflag || rflag || zflag) {
                                (void) fprintf(stderr, flags_message);
                                login_exit(1);
                        }
                        rflag = B_TRUE;
                        SCPYL(remote_host, optarg);
                        SCPYL(progname, "rlogin");
                        break;

                case 'p':
                        pflag = B_TRUE;
                        break;

                case 'f':
                        /*
                         * Must be root to bypass authentication
                         * otherwise we exit() as punishment for trying.
                         */
                        if (getuid() != 0 || geteuid() != 0) {
                                audit_error = ADT_FAIL_VALUE_AUTH_BYPASS;

                                login_exit(1);  /* sigh */
                                /*NOTREACHED*/
                        }
                        /* save fflag user name for future use */
                        SCPYL(user_name, optarg);
                        fflag = B_TRUE;
                        break;
                case 'u':
                        if (!strlen(optarg)) {
                                (void) fprintf(stderr,
                                    "Empty string supplied with -u\n");
                                login_exit(1);
                        }
                        SCPYL(identity, optarg);
                        uflag = B_TRUE;
                        break;
                case 's':
                        if (!strlen(optarg)) {
                                (void) fprintf(stderr,
                                    "Empty string supplied with -s\n");
                                login_exit(1);
                        }
                        SCPYL(sflagname, optarg);
                        sflag = B_TRUE;
                        break;
                case 'R':
                        if (!strlen(optarg)) {
                                (void) fprintf(stderr,
                                    "Empty string supplied with -R\n");
                                login_exit(1);
                        }
                        SCPYL(repository, optarg);
                        Rflag = B_TRUE;
                        break;
                case 't':
                        if (!strlen(optarg)) {
                                (void) fprintf(stderr,
                                    "Empty string supplied with -t\n");
                                login_exit(1);
                        }
                        SCPYL(terminal, optarg);
                        tflag = B_TRUE;
                        break;
                case 'U':
                        /*
                         * Kerberized rlogind may fork us with
                         * -U "" if the rlogin client used the "-a"
                         * option to send a NULL username.  This is done
                         * to force login to prompt for a user/password.
                         * However, if Kerberos auth was used, we dont need
                         * to prompt, so we will accept the option and
                         * handle the situation later.
                         */
                        SCPYL(rusername, optarg);
                        Uflag = B_TRUE;
                        break;
                case 'z':
                        if (hflag || rflag || zflag) {
                                (void) fprintf(stderr, flags_message);
                                login_exit(1);
                        }
                        (void) snprintf(zone_name, sizeof (zone_name),
                            "zone:%s", optarg);
                        SCPYL(progname, "zlogin");
                        zflag = B_TRUE;
                        break;
                default:
                        errflg++;
                        break;
                }       /* end switch */
        }               /* end while */

        /*
         * If the 's svcname' flag was used, override the progname
         * value that is to be used in the pam_start call.
         */
        if (sflag)
                SCPYL(progname, sflagname);

        /*
         * get the prompt set by ttymon
         */
        ttyprompt = getenv("TTYPROMPT");

        if ((ttyprompt != NULL) && (*ttyprompt != '\0')) {
                /*
                 * if ttyprompt is set, there should be data on
                 * the stream already.
                 */
                if ((envp = getargs(inputline)) != (char **)NULL) {
                        /*
                         * don't get name if name passed as argument.
                         */
                        SCPYL(user_name, *envp++);
                }
        } else if (optind < argc) {
                SCPYL(user_name, argv[optind]);
                (void) SCPYL(inputline, user_name);
                (void) strlcat(inputline, "   \n", sizeof (inputline));
                envp = &argv[optind+1];

                if (!fflag)
                        SCPYL(lusername, user_name);
        }

        if (errflg)
                return (-1);
        return (0);
}

/*
 * usage                - Print usage message
 *
 */
static void
usage(void)
{
        (void) fprintf(stderr,
            "usage:\n"
            "    login [-p] [-d device] [-R repository] [-s service]\n"
            "\t[-t terminal]  [-u identity] [-U ruser]\n"
            "\t[-h hostname [terminal] | -r hostname] [name [environ]...]\n");

}

/*
 * doremoteterm         - Sets the appropriate ioctls for a remote terminal
 */
static char     *speeds[] = {
        "0", "50", "75", "110", "134", "150", "200", "300",
        "600", "1200", "1800", "2400", "4800", "9600", "19200", "38400",
        "57600", "76800", "115200", "153600", "230400", "307200", "460800",
        "921600", "1000000", "1152000", "1500000", "2000000", "2500000",
        "3000000", "3500000", "4000000"
};

#define NSPEEDS (sizeof (speeds) / sizeof (speeds[0]))


static void
doremoteterm(char *term)
{
        struct termios tp;
        char *cp = strchr(term, '/'), **cpp;
        char *speed;

        (void) ioctl(0, TCGETS, &tp);

        if (cp) {
                *cp++ = '\0';
                speed = cp;
                cp = strchr(speed, '/');

                if (cp)
                        *cp++ = '\0';

                for (cpp = speeds; cpp < &speeds[NSPEEDS]; cpp++) {
                        if (strcmp(*cpp, speed) == 0) {
                                (void) cfsetospeed(&tp, cpp-speeds);
                                break;
                        }
                }
        }

        tp.c_lflag |= ECHO|ICANON;
        tp.c_iflag |= IGNPAR|ICRNL;

        (void) ioctl(0, TCSETS, &tp);

}

/*
 * Process_rlogin               - Does the work that rlogin and telnet
 *                                need done
 */
static void
process_rlogin(void)
{
        /*
         * If a Kerberized rlogin was initiated, then these fields
         * must be read by rlogin daemon itself and passed down via
         * cmd line args.
         */
        if (!Uflag && !strlen(rusername))
                getstr(rusername, sizeof (rusername), "remuser");
        if (!strlen(lusername))
                getstr(lusername, sizeof (lusername), "locuser");
        if (!tflag && !strlen(terminal))
                getstr(terminal, sizeof (terminal), "Terminal type");

        if (strlen(terminal))
                doremoteterm(terminal);

        /* fflag has precedence over stuff passed by rlogind */
        if (fflag || getuid()) {
                pwd = &nouser;
                return;
        } else {
                if (pam_set_item(pamh, PAM_USER, lusername) != PAM_SUCCESS)
                        login_exit(1);

                pwd = getpwnam(lusername);
                if (pwd == NULL) {
                        pwd = &nouser;
                        return;
                }
        }

        /*
         * Update PAM on the user name
         */
        if (strlen(lusername) &&
            pam_set_item(pamh, PAM_USER, lusername) != PAM_SUCCESS)
                login_exit(1);

        if (strlen(rusername) &&
            pam_set_item(pamh, PAM_RUSER, rusername) != PAM_SUCCESS)
                login_exit(1);

        SCPYL(user_name, lusername);
        envp = &zero;
        lusername[0] = '\0';
}

/*
 *              *** Account validation routines ***
 *
 */

/*
 * validate_account             - This is the PAM version of validate.
 */

static void
validate_account(void)
{
        int     error;
        int     flag;
        int     tries;          /* new password retries */

        (void) alarm(0);        /* give user time to come up with password */

        check_log();

        if (Passreqflag)
                flag = PAM_DISALLOW_NULL_AUTHTOK;
        else
                flag = 0;

        if ((error = pam_acct_mgmt(pamh, flag)) != PAM_SUCCESS) {
                if (error == PAM_NEW_AUTHTOK_REQD) {
                        tries = 1;
                        error = PAM_AUTHTOK_ERR;
                        while (error == PAM_AUTHTOK_ERR &&
                            tries <= DEF_ATTEMPTS) {
                                if (tries > 1)
                                        (void) printf("Try again\n\n");

                                (void) printf("Choose a new password.\n");

                                error = pam_chauthtok(pamh,
                                    PAM_CHANGE_EXPIRED_AUTHTOK);
                                if (error == PAM_TRY_AGAIN) {
                                        (void) sleep(1);
                                        error = pam_chauthtok(pamh,
                                            PAM_CHANGE_EXPIRED_AUTHTOK);
                                }
                                tries++;
                        }

                        if (error != PAM_SUCCESS) {
                                if (dosyslog)
                                        syslog(LOG_CRIT,
                                            "change password failure: %s",
                                            pam_strerror(pamh, error));
                                audit_error = ADT_FAIL_PAM + error;
                                login_exit(1);
                        } else {
                                audit_success(ADT_passwd, pwd, zone_name);
                        }
                } else {
                        (void) printf(incorrectmsg);

                        if (dosyslog)
                                syslog(LOG_CRIT,
                                    "login account failure: %s",
                                    pam_strerror(pamh, error));
                        audit_error = ADT_FAIL_PAM + error;
                        login_exit(1);
                }
        }
}

/*
 * Check_log    - This is really a hack because PAM checks the log, but login
 *                wants to know if the log is okay and PAM doesn't have
 *                a module independent way of handing this info back.
 */

static void
check_log(void)
{
        int fdl;
        long long offset;

        offset = (long long) pwd->pw_uid * (long long) sizeof (struct lastlog);

        if ((fdl = open(LASTLOG, O_RDWR|O_CREAT, 0444)) >= 0) {
                if (llseek(fdl, offset, SEEK_SET) == offset &&
                    read(fdl, (char *)&ll, sizeof (ll)) == sizeof (ll) &&
                    ll.ll_time != 0)
                        lastlogok = 1;
                (void) close(fdl);
        }
}

/*
 * chdir_to_dir_user    - Now chdir after setuid/setgid have happened to
 *                        place us in the user's home directory just in
 *                        case it was protected and the first chdir failed.
 *                        No chdir errors should happen at this point because
 *                        all failures should have happened on the first
 *                        time around.
 */

static void
chdir_to_dir_user(void)
{
        if (chdir(pwd->pw_dir) < 0) {
                if (chdir("/") < 0) {
                        (void) printf("No directory!\n");
                        /*
                         * This probably won't work since we can't get to /.
                         */
                        if (dosyslog) {
                                if (remote_host[0]) {
                                        syslog(LOG_CRIT,
                                            "LOGIN FAILURES ON %s FROM %.*s ",
                                            " %.*s", ttyn, HMAX,
                                            remote_host, NMAX, pwd->pw_name);
                                } else {
                                        syslog(LOG_CRIT,
                                            "LOGIN FAILURES ON %s, %.*s",
                                            ttyn, NMAX, pwd->pw_name);
                                }
                        }
                        closelog();
                        (void) sleep(Disabletime);
                        exit(1);
                } else {
                        (void) printf("No directory! Logging in with home=/\n");
                        pwd->pw_dir = "/";
                }
        }
}


/*
 * login_authenticate   - Performs the main authentication work
 *                        1. Prints the login prompt
 *                        2. Requests and verifys the password
 *                        3. Checks the port password
 */

static void
login_authenticate(void)
{
        const char *user;
        int err;
        int login_successful = 0;

        do {
                /* if scheme broken, then nothing to do but quit */
                if (pam_get_item(pamh, PAM_USER, (const void **)&user) !=
                    PAM_SUCCESS) {
                        exit(1);
                }

                /*
                 * only get name from utility if it is not already
                 * supplied by pam_start or a pam_set_item.
                 */
                if (!user || !user[0]) {
                        /* use call back to get user name */
                        get_user_name();
                }

                err = verify_passwd();

                /*
                 * If root login and not on system console then call exit(2)
                 */
                check_for_console();

                switch (err) {
                case PAM_SUCCESS:
                case PAM_NEW_AUTHTOK_REQD:
                        /*
                         * Officially, pam_authenticate() shouldn't return this
                         * but it's probably the right thing to return if
                         * PAM_DISALLOW_NULL_AUTHTOK is set so the user will
                         * be forced to change password later in this code.
                         */
                        count = 0;
                        login_successful = 1;
                        break;
                case PAM_MAXTRIES:
                        count = retry;
                        /*FALLTHROUGH*/
                case PAM_AUTH_ERR:
                case PAM_AUTHINFO_UNAVAIL:
                case PAM_USER_UNKNOWN:
                        audit_failure(get_audit_id(), ADT_FAIL_PAM + err, pwd,
                            remote_host, ttyn, zone_name);
                        log_bad_attempts();
                        break;
                case PAM_ABORT:
                        log_bad_attempts();
                        (void) sleep(Disabletime);
                        (void) printf(incorrectmsg);

                        audit_error = ADT_FAIL_PAM + err;
                        login_exit(1);
                        /*NOTREACHED*/
                default:        /* Some other PAM error */
                        audit_error = ADT_FAIL_PAM + err;
                        login_exit(1);
                        /*NOTREACHED*/
                }

                if (login_successful)
                        break;

                /* sleep after bad passwd */
                if (count)
                        (void) sleep(Sleeptime);
                (void) printf(incorrectmsg);
                /* force name to be null in this case */
                if (pam_set_item(pamh, PAM_USER, NULL) != PAM_SUCCESS)
                        login_exit(1);
                if (pam_set_item(pamh, PAM_RUSER, NULL) != PAM_SUCCESS)
                        login_exit(1);
        } while (count++ < retry);

        if (count >= retry) {
                audit_failure(get_audit_id(), ADT_FAIL_VALUE_MAX_TRIES, pwd,
                    remote_host, ttyn, zone_name);
                /*
                 * If logging is turned on, output the
                 * string storage area to the log file,
                 * and sleep for Disabletime
                 * seconds before exiting.
                 */
                if (writelog)
                        badlogin();
                if (dosyslog) {
                        if ((pwd = getpwnam(user_name)) != NULL) {
                                if (remote_host[0]) {
                                        syslog(LOG_CRIT,
                                            "REPEATED LOGIN FAILURES ON %s "
                                            "FROM %.*s, %.*s",
                                            ttyn, HMAX, remote_host, NMAX,
                                            user_name);
                                } else {
                                        syslog(LOG_CRIT,
                                            "REPEATED LOGIN FAILURES ON "
                                            "%s, %.*s",
                                            ttyn, NMAX, user_name);
                                }
                        } else {
                                if (remote_host[0]) {
                                        syslog(LOG_CRIT,
                                            "REPEATED LOGIN FAILURES ON %s "
                                            "FROM %.*s",
                                            ttyn, HMAX, remote_host);
                                } else {
                                        syslog(LOG_CRIT,
                                            "REPEATED LOGIN FAILURES ON %s",
                                            ttyn);
                                }
                        }
                }
                (void) sleep(Disabletime);
                exit(1);
        }

}

/*
 *                      *** Credential Related routines ***
 *
 */

/*
 * setup_credentials            - sets the group ID, initializes the groups
 *                                and sets up the secretkey.
 *                                Exits if a failure occurrs.
 */


/*
 * setup_credentials            - PAM does all the work for us on this one.
 */

static void
setup_credentials(void)
{
        int     error = 0;

        /* set the real (and effective) GID */
        if (setgid(pwd->pw_gid) == -1) {
                login_exit(1);
        }

        /*
         * Initialize the supplementary group access list.
         */
        if ((user_name[0] == '\0') ||
            (initgroups(user_name, pwd->pw_gid) == -1)) {
                audit_error = ADT_FAIL_VALUE_PROGRAM;
                login_exit(1);
        }

        if ((error = pam_setcred(pamh, zflag ? PAM_REINITIALIZE_CRED :
            PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
                audit_error = ADT_FAIL_PAM + error;
                login_exit(error);
        }

        /*
         * Record successful login and fork process that records logout.
         * We have to do this after setting credentials because pam_setcred()
         * loads key audit info into the cred, but before setuid() so audit
         * system calls will work.
         */
        audit_success(get_audit_id(), pwd, zone_name);
}

static uint_t
get_audit_id(void)
{
        if (rflag)
                return (ADT_rlogin);
        else if (hflag)
                return (ADT_telnet);
        else if (zflag)
                return (ADT_zlogin);

        return (ADT_login);
}

/*
 *
 *              *** Routines to get a new user set up and running ***
 *
 *                      Things to do when starting up a new user:
 *                              adjust_nice
 *                              update_utmpx_entry
 *                              establish_user_environment
 *                              print_banner
 *                              display_last_login_time
 *                              exec_the_shell
 *
 */


/*
 * adjust_nice          - Set the nice (process priority) value if the
 *                        gecos value contains an appropriate value.
 */

static void
adjust_nice(void)
{
        int pri, mflg, i;

        if (strncmp("pri=", pwd->pw_gecos, 4) == 0) {
                pri = 0;
                mflg = 0;
                i = 4;

                if (pwd->pw_gecos[i] == '-') {
                        mflg++;
                        i++;
                }

                while (pwd->pw_gecos[i] >= '0' && pwd->pw_gecos[i] <= '9')
                        pri = (pri * 10) + pwd->pw_gecos[i++] - '0';

                if (mflg)
                        pri = -pri;

                (void) nice(pri);
        }
}

/*
 * update_utmpx_entry   - Searchs for the correct utmpx entry, making an
 *                        entry there if it finds one, otherwise exits.
 */

static void
update_utmpx_entry(int sublogin)
{
        int err;
        const char *user;
        static char *errmsg = "No utmpx entry. "
            "You must exec \"login\" from the lowest level \"shell\".";
        int tmplen;
        struct utmpx *u = NULL;
        struct utmpx utmpx;
        char *ttyntail;

        /*
         * If we're not a sublogin then
         * we'll get an error back if our PID doesn't match the PID of the
         * entry we are updating, otherwise if its a sublogin the flags
         * field is set to 0, which means we just write a matching entry
         * (without checking the pid), or a new entry if an entry doesn't
         * exist.
         */

        if ((err = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
                audit_error = ADT_FAIL_PAM + err;
                login_exit(1);
        }

        if ((err = pam_get_item(pamh, PAM_USER, (const void **)&user)) !=
            PAM_SUCCESS) {
                audit_error = ADT_FAIL_PAM + err;
                login_exit(1);
        }

        (void) memset((void *)&utmpx, 0, sizeof (utmpx));
        (void) time(&utmpx.ut_tv.tv_sec);
        utmpx.ut_pid = getpid();

        if (rflag || hflag) {
                SCPYN(utmpx.ut_host, remote_host);
                tmplen = strlen(remote_host) + 1;
                if (tmplen < sizeof (utmpx.ut_host))
                        utmpx.ut_syslen = tmplen;
                else
                        utmpx.ut_syslen = sizeof (utmpx.ut_host);
        } else if (zflag) {
                /*
                 * If this is a login from another zone, put the
                 * zone:<zonename> string in the utmpx entry.
                 */
                SCPYN(utmpx.ut_host, zone_name);
                tmplen = strlen(zone_name) + 1;
                if (tmplen < sizeof (utmpx.ut_host))
                        utmpx.ut_syslen = tmplen;
                else
                        utmpx.ut_syslen = sizeof (utmpx.ut_host);
        } else {
                utmpx.ut_syslen = 0;
        }

        SCPYN(utmpx.ut_user, user);

        /* skip over "/dev/" */
        ttyntail = basename(ttyn);

        while ((u = getutxent()) != NULL) {
                if ((u->ut_type == INIT_PROCESS ||
                    u->ut_type == LOGIN_PROCESS ||
                    u->ut_type == USER_PROCESS) &&
                    ((sublogin && strncmp(u->ut_line, ttyntail,
                    sizeof (u->ut_line)) == 0) ||
                    u->ut_pid == login_pid)) {
                        SCPYN(utmpx.ut_line, (ttyn+sizeof ("/dev/")-1));
                        (void) memcpy(utmpx.ut_id, u->ut_id,
                            sizeof (utmpx.ut_id));
                        utmpx.ut_exit.e_exit = u->ut_exit.e_exit;
                        utmpx.ut_type = USER_PROCESS;
                        (void) pututxline(&utmpx);
                        break;
                }
        }
        endutxent();

        if (u == (struct utmpx *)NULL) {
                if (!sublogin) {
                        /*
                         * no utmpx entry already setup
                         * (init or rlogind/telnetd)
                         */
                        (void) puts(errmsg);

                        audit_error = ADT_FAIL_VALUE_PROGRAM;
                        login_exit(1);
                }
        } else {
                /* Now attempt to write out this entry to the wtmp file if */
                /* we were successful in getting it from the utmpx file and */
                /* the wtmp file exists.                                   */
                updwtmpx(WTMPX_FILE, &utmpx);
        }
}



/*
 * process_chroot_logins        - Chroots to the specified subdirectory and
 *                                re executes login.
 */

static int
process_chroot_logins(void)
{
        /*
         * If the shell field starts with a '*', do a chroot to the home
         * directory and perform a new login.
         */

        if (*pwd->pw_shell == '*') {
                (void) pam_end(pamh, PAM_SUCCESS);      /* Done using PAM */
                pamh = NULL;                            /* really done */
                if (chroot(pwd->pw_dir) < 0) {
                        (void) printf("No Root Directory\n");

                        audit_failure(get_audit_id(),
                            ADT_FAIL_VALUE_CHDIR_FAILED,
                            pwd, remote_host, ttyn, zone_name);

                        return (ERROR);
                }
                /*
                 * Set the environment flag <!sublogin> so that the next login
                 * knows that it is a sublogin.
                 */
                envinit[0] = SUBLOGIN;
                envinit[1] = (char *)NULL;
                (void) printf("Subsystem root: %s\n", pwd->pw_dir);
                (void) execle("/usr/bin/login", "login", (char *)0,
                    &envinit[0]);
                (void) execle("/etc/login", "login", (char *)0, &envinit[0]);
                (void) printf("No /usr/bin/login or /etc/login on root\n");

                audit_error = ADT_FAIL_VALUE_PROGRAM;

                login_exit(1);
        }
        return (OK);
}

/*
 * establish_user_environment   - Set up the new users enviornment
 */

static void
establish_user_environment(char **renvp)
{
        int i, j, k, l_index, length, idx = 0;
        char *endptr;
        char **lenvp;
        char **pam_env;

        lenvp = environ;
        while (*lenvp++)
                ;

        /* count the number of PAM environment variables set by modules */
        if ((pam_env = pam_getenvlist(pamh)) != 0) {
                for (idx = 0; pam_env[idx] != 0; idx++)
                                ;
        }

        envinit = (char **)calloc(lenvp - environ + 10 + MAXARGS + idx,
            sizeof (char *));
        if (envinit == NULL) {
                (void) printf("Calloc failed - out of swap space.\n");
                login_exit(8);
        }

        /*
         * add PAM environment variables first so they
         * can be overwritten at login's discretion.
         * check for illegal environment variables.
         */
        idx = 0;        basicenv = 0;
        if (pam_env != 0) {
                while (pam_env[idx] != 0) {
                        if (legalenvvar(pam_env[idx])) {
                                envinit[basicenv] = pam_env[idx];
                                basicenv++;
                        }
                        idx++;
                }
        }
        (void) memcpy(&envinit[basicenv], newenv, sizeof (newenv));

        /* Set up environment */
        if (rflag) {
                ENVSTRNCAT(term, terminal);
        } else if (hflag) {
                if (strlen(terminal)) {
                        ENVSTRNCAT(term, terminal);
                }
        } else {
                char *tp = getenv("TERM");

                if ((tp != NULL) && (*tp != '\0'))
                        ENVSTRNCAT(term, tp);
        }

        ENVSTRNCAT(logname, pwd->pw_name);

        /*
         * There are three places to get timezone info.  init.c sets
         * TZ if the file /etc/default/init contains a value for TZ.
         * login.c looks in the file /etc/default/login for a
         * variable called TIMEZONE being set.  If TIMEZONE has a
         *  value, TZ is set to that value; no environment variable
         * TIMEZONE is set, only TZ.  If neither of these methods
         * work to set TZ, then the library routines  will default
         * to using the file /usr/lib/locale/TZ/localtime.
         *
         * There is a priority set up here.  If /etc/default/init has
         * a value for TZ, that value remains top priority.  If the
         * file /etc/default/login has TIMEZONE set, that has second
         * highest priority not overriding the value of TZ in
         * /etc/default/init.  The reason for this priority is that the
         * file /etc/default/init is supposed to be sourced by
         * /etc/profile.  We are doing the "sourcing" prematurely in
         * init.c.  Additionally, a login C shell doesn't source the
         * file /etc/profile thus not sourcing /etc/default/init thus not
         * allowing an adminstrator to globally set TZ for all users
         */
        if (Def_tz != NULL)     /* Is there a TZ from defaults/login? */
                tmp_tz = Def_tz;

        if ((Def_tz = getenv("TZ")) != NULL) {
                ENVSTRNCAT(timez, Def_tz);
        } else if (tmp_tz != NULL) {
                Def_tz = tmp_tz;
                ENVSTRNCAT(timez, Def_tz);
        }

        if (Def_hertz == NULL)
                (void) sprintf(hertz + strlen(hertz), "%lu", HZ);
        else
                ENVSTRNCAT(hertz, Def_hertz);

        if (Def_path == NULL)
                (void) strlcat(path, DEF_PATH, sizeof (path));
        else
                ENVSTRNCAT(path, Def_path);

        ENVSTRNCAT(home, pwd->pw_dir);

        /*
         * Find the end of the basic environment
         */
        for (basicenv = 0; envinit[basicenv] != NULL; basicenv++)
                ;

        /*
         * If TZ has a value, add it.
         */
        if (strcmp(timez, "TZ=") != 0)
                envinit[basicenv++] = timez;

        if (*pwd->pw_shell == '\0') {
                /*
                 * If possible, use the primary default shell,
                 * otherwise, use the secondary one.
                 */
                if (access(SHELL, X_OK) == 0)
                        pwd->pw_shell = SHELL;
                else
                        pwd->pw_shell = SHELL2;
        } else if (Altshell != NULL && strcmp(Altshell, "YES") == 0) {
                envinit[basicenv++] = shell;
                ENVSTRNCAT(shell, pwd->pw_shell);
        }

#ifndef NO_MAIL
        envinit[basicenv++] = mail;
        (void) strlcat(mail, pwd->pw_name, sizeof (mail));
#endif

        /*
         * Pick up locale environment variables, if any.
         */
        lenvp = renvp;
        while (*lenvp != NULL) {
                j = 0;
                while (localeenv[j] != 0) {
                        /*
                         * locale_envmatch() returns 1 if
                         * *lenvp is localenev[j] and valid.
                         */
                        if (locale_envmatch(localeenv[j], *lenvp) == 1) {
                                envinit[basicenv++] = *lenvp;
                                break;
                        }
                        j++;
                }
                lenvp++;
        }

        /*
         * If '-p' flag, then try to pass on allowable environment
         * variables.  Note that by processing this first, what is
         * passed on the final "login:" line may over-ride the invocation
         * values.  XXX is this correct?
         */
        if (pflag) {
                for (lenvp = renvp; *lenvp; lenvp++) {
                        if (!legalenvvar(*lenvp)) {
                                continue;
                        }
                        /*
                         * If this isn't 'xxx=yyy', skip it.  XXX
                         */
                        if ((endptr = strchr(*lenvp, '=')) == NULL) {
                                continue;
                        }
                        length = endptr + 1 - *lenvp;
                        for (j = 0; j < basicenv; j++) {
                                if (strncmp(envinit[j], *lenvp, length) == 0) {
                                        /*
                                         * Replace previously established value
                                         */
                                        envinit[j] = *lenvp;
                                        break;
                                }
                        }
                        if (j == basicenv) {
                                /*
                                 * It's a new definition, so add it at the end.
                                 */
                                envinit[basicenv++] = *lenvp;
                        }
                }
        }

        /*
         * Add in all the environment variables picked up from the
         * argument list to "login" or from the user response to the
         * "login" request, if any.
         */

        if (envp == NULL)
                goto switch_env;        /* done */

        for (j = 0, k = 0, l_index = 0;
            *envp != NULL && j < (MAXARGS-1);
            j++, envp++) {

                /*
                 * Scan each string provided.  If it doesn't have the
                 * format xxx=yyy, then add the string "Ln=" to the beginning.
                 */
                if ((endptr = strchr(*envp, '=')) == NULL) {
                        /*
                         * This much to be malloc'd:
                         *   strlen(*envp) + 1 char for 'L' +
                         *   MAXARGSWIDTH + 1 char for '=' + 1 for null char;
                         *
                         * total = strlen(*envp) + MAXARGSWIDTH + 3
                         */
                        int total = strlen(*envp) + MAXARGSWIDTH + 3;
                        envinit[basicenv+k] = malloc(total);
                        if (envinit[basicenv+k] == NULL) {
                                (void) printf("%s: malloc failed\n", PROG_NAME);
                                login_exit(1);
                        }
                        (void) snprintf(envinit[basicenv+k], total, "L%d=%s",
                            l_index, *envp);

                        k++;
                        l_index++;
                } else  {
                        if (!legalenvvar(*envp)) { /* this env var permited? */
                                continue;
                        } else {

                                /*
                                 * Check to see whether this string replaces
                                 * any previously defined string
                                 */
                                for (i = 0, length = endptr + 1 - *envp;
                                    i < basicenv + k; i++) {
                                        if (strncmp(*envp, envinit[i], length)
                                            == 0) {
                                                envinit[i] = *envp;
                                                break;
                                        }
                                }

                                /*
                                 * If it doesn't, place it at the end of
                                 * environment array.
                                 */
                                if (i == basicenv+k) {
                                        envinit[basicenv+k] = *envp;
                                        k++;
                                }
                        }
                }
        }               /* for (j = 0 ... ) */

switch_env:
        /*
         * Switch to the new environment.
         */
        environ = envinit;
}

/*
 * print_banner         - Print the banner at start up
 *                         Do not turn on DOBANNER ifdef.  This is not
 *                         relevant to SunOS.
 */

static void
print_banner(void)
{
#ifdef DOBANNER
        uname(&un);
#if i386
        (void) printf("UNIX System V/386 Release %s\n%s\n"
            "Copyright (C) 1984, 1986, 1987, 1988 AT&T\n"
            "Copyright (C) 1987, 1988 Microsoft Corp.\nAll Rights Reserved\n",
            un.release, un.nodename);
#elif sun
        (void) printf("SunOS Release %s Sun Microsystems %s\n%s\n"
            "Copyright (c) 1984, 1986, 1987, 1988 AT&T\n"
            "Copyright (c) 1988, 1989, 1990, 1991 Sun Microsystems\n"
            "All Rights Reserved\n",
            un.release, un.machine, un.nodename);
#else
        (void) printf("UNIX System V Release %s AT&T %s\n%s\n"
            "Copyright (c) 1984, 1986, 1987, 1988 AT&T\nAll Rights Reserved\n",
            un.release, un.machine, un.nodename);
#endif /* i386 */
#endif /* DOBANNER */
}

/*
 * display_last_login_time      - Advise the user the time and date
 *                                that this login-id was last used.
 */

static void
display_last_login_time(void)
{
        if (lastlogok) {
                (void) printf("Last login: %.*s ", 24-5, ctime(&ll.ll_time));

                if (*ll.ll_host != '\0')
                        (void) printf("from %.*s\n", sizeof (ll.ll_host),
                            ll.ll_host);
                else
                        (void) printf("on %.*s\n", sizeof (ll.ll_line),
                            ll.ll_line);
        }
}

/*
 * exec_the_shell       - invoke the specified shell or start up program
 */

static void
exec_the_shell(void)
{
        char *endptr;
        int i;

        (void) strlcat(minusnam, basename(pwd->pw_shell),
            sizeof (minusnam));

        /*
         * Exec the shell
         */
        (void) execl(pwd->pw_shell, minusnam, (char *)0);

        /*
         * pwd->pw_shell was not an executable object file, maybe it
         * is a shell proceedure or a command line with arguments.
         * If so, turn off the SHELL= environment variable.
         */
        for (i = 0; envinit[i] != NULL; ++i) {
                if ((envinit[i] == shell) &&
                    ((endptr = strchr(shell, '=')) != NULL))
                        (*++endptr) = '\0';
                }

        if (access(pwd->pw_shell, R_OK|X_OK) == 0) {
                (void) execl(SHELL, "sh", pwd->pw_shell, (char *)0);
                (void) execl(SHELL2, "sh", pwd->pw_shell, (char *)0);
        }

        (void) printf("No shell\n");
}

/*
 * login_exit           - Call exit()  and terminate.
 *                        This function is here for PAM so cleanup can
 *                        be done before the process exits.
 */
static void
login_exit(int exit_code)
{
        if (pamh)
                (void) pam_end(pamh, PAM_ABORT);

        if (audit_error)
                audit_failure(get_audit_id(), audit_error,
                    pwd, remote_host, ttyn, zone_name);

        exit(exit_code);
        /*NOTREACHED*/
}

/*
 * Check if lenv and penv matches or not.
 */
static int
locale_envmatch(char *lenv, char *penv)
{
        while ((*lenv == *penv) && *lenv && *penv != '=') {
                lenv++;
                penv++;
        }

        /*
         * '/' is eliminated for security reason.
         */
        if (*lenv == '\0' && *penv == '=' && *(penv + 1) != '/')
                return (1);
        return (0);
}

static int
is_number(char *ptr)
{
        while (*ptr != '\0') {
                if (!isdigit(*ptr))
                        return (0);
                ptr++;
        }
        return (1);
}