static void loginpam_acct(struct login_context *cxt) { int rc; pam_handle_t *pamh = cxt->pamh; rc = pam_acct_mgmt(pamh, 0); if (rc == PAM_NEW_AUTHTOK_REQD) rc = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); if (is_pam_failure(rc)) loginpam_err(pamh, rc); /* * Grab the user information out of the password file for future use. * First get the username that we are actually using, though. */ rc = loginpam_get_username(pamh, &cxt->username); if (is_pam_failure(rc)) loginpam_err(pamh, rc); if (!cxt->username || !*cxt->username) { warnx(_("\nSession setup problem, abort.")); syslog(LOG_ERR, _("NULL user name in %s:%d. Abort."), __FUNCTION__, __LINE__); pam_end(pamh, PAM_SYSTEM_ERR); sleepexit(EXIT_FAILURE); } }
static void init_groups (const struct passwd* pw, gid_t* groups, int num_groups) { int retval; errno = 0; if (num_groups) { retval = setgroups (num_groups, groups); } else { retval = initgroups (pw->pw_name, pw->pw_gid); } if (retval == -1) { cleanup_pam (PAM_ABORT); err (EXIT_FAILURE, _("cannot set groups")); } endgrent (); retval = pam_setcred (pamh, PAM_ESTABLISH_CRED); if (is_pam_failure(retval)) { errx (EXIT_FAILURE, "%s", pam_strerror (pamh, retval)); } else { _pam_cred_established = 1; } }
static pam_handle_t *init_loginpam(struct login_context *cxt) { pam_handle_t *pamh = NULL; int rc; /* * username is initialized to NULL and if specified on the command line * it is set. Therefore, we are safe not setting it to anything */ rc = pam_start(cxt->remote ? "remote" : "login", cxt->username, &cxt->conv, &pamh); if (rc != PAM_SUCCESS) { warnx(_("PAM failure, aborting: %s"), pam_strerror(pamh, rc)); syslog(LOG_ERR, _("Couldn't initialize PAM: %s"), pam_strerror(pamh, rc)); sleepexit(EXIT_FAILURE); } /* hostname & tty are either set to NULL or their correct values, * depending on how much we know */ rc = pam_set_item(pamh, PAM_RHOST, cxt->hostname); if (is_pam_failure(rc)) loginpam_err(pamh, rc); rc = pam_set_item(pamh, PAM_TTY, cxt->tty_name); if (is_pam_failure(rc)) loginpam_err(pamh, rc); /* * [email protected]: Provide a user prompt to PAM so that * the "login: "******"Password: " string (yet). */ rc = pam_set_item(pamh, PAM_USER_PROMPT, loginpam_get_prompt(cxt)); if (is_pam_failure(rc)) loginpam_err(pamh, rc); /* we need't the original username. We have to follow PAM. */ free(cxt->username); cxt->username = NULL; cxt->pamh = pamh; return pamh; }
/* * Note that the position of the pam_setcred() call is discussable: * * - the PAM docs recommend pam_setcred() before pam_open_session() * - but the original RFC http://www.opengroup.org/rfc/mirror-rfc/rfc86.0.txt * uses pam_setcred() after pam_open_session() * * The old login versions (before year 2011) followed the RFC. This is probably * not optimal, because there could be a dependence between some session modules * and the user's credentials. * * The best is probably to follow openssh and call pam_setcred() before and * after pam_open_session(). -- [email protected] (18-Nov-2011) * */ static void loginpam_session(struct login_context *cxt) { int rc; pam_handle_t *pamh = cxt->pamh; rc = pam_setcred(pamh, PAM_ESTABLISH_CRED); if (is_pam_failure(rc)) loginpam_err(pamh, rc); rc = pam_open_session(pamh, 0); if (is_pam_failure(rc)) { pam_setcred(cxt->pamh, PAM_DELETE_CRED); loginpam_err(pamh, rc); } rc = pam_setcred(pamh, PAM_REINITIALIZE_CRED); if (is_pam_failure(rc)) { pam_close_session(pamh, 0); loginpam_err(pamh, rc); } }
static void authenticate (const struct passwd* pw) { const struct passwd* lpw = NULL; const char* cp, *srvname = NULL; int retval; switch (su_mode) { case SU_MODE: srvname = simulate_login ? PAM_SRVNAME_SU_L : PAM_SRVNAME_SU; break; case RUNUSER_MODE: srvname = simulate_login ? PAM_SRVNAME_RUNUSER_L : PAM_SRVNAME_RUNUSER; break; default: abort(); break; } retval = pam_start (srvname, pw->pw_name, &conv, &pamh); if (is_pam_failure(retval)) { goto done; } if (isatty (0) && (cp = ttyname (0)) != NULL) { const char* tty; if (strncmp (cp, "/dev/", 5) == 0) { tty = cp + 5; } else { tty = cp; } retval = pam_set_item (pamh, PAM_TTY, tty); if (is_pam_failure(retval)) { goto done; } } lpw = current_getpwuid (); if (lpw && lpw->pw_name) { retval = pam_set_item (pamh, PAM_RUSER, (const void*) lpw->pw_name); if (is_pam_failure(retval)) { goto done; } } if (su_mode == RUNUSER_MODE) { /* * This is the only difference between runuser(1) and su(1). The command * runuser(1) does not required authentication, because user is root. */ if (restricted) { errx(EXIT_FAILURE, _("may not be used by non-root users")); } return; } retval = pam_authenticate (pamh, 0); if (is_pam_failure(retval)) { goto done; } retval = pam_acct_mgmt (pamh, 0); if (retval == PAM_NEW_AUTHTOK_REQD) { /* Password has expired. Offer option to change it. */ retval = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK); } done: log_syslog(pw, !is_pam_failure(retval)); if (is_pam_failure(retval)) { const char* msg; log_btmp(pw); msg = pam_strerror(pamh, retval); pam_end(pamh, retval); sleep (getlogindefs_num ("FAIL_DELAY", 1)); errx (EXIT_FAILURE, "%s", msg ? msg : _("incorrect password")); } }
static void create_watching_parent (void) { pid_t child; sigset_t ourset; struct sigaction oldact[3]; int status = 0; int retval; retval = pam_open_session (pamh, 0); if (is_pam_failure(retval)) { cleanup_pam (retval); errx (EXIT_FAILURE, _("cannot open session: %s"), pam_strerror (pamh, retval)); } else { _pam_session_opened = 1; } memset(oldact, 0, sizeof(oldact)); child = fork (); if (child == (pid_t) - 1) { cleanup_pam (PAM_ABORT); err (EXIT_FAILURE, _("cannot create child process")); } /* the child proceeds to run the shell */ if (child == 0) { return; } /* In the parent watch the child. */ /* su without pam support does not have a helper that keeps sitting on any directory so let's go to /. */ if (chdir ("/") != 0) { warn (_("cannot change directory to %s"), "/"); } sigfillset (&ourset); if (sigprocmask (SIG_BLOCK, &ourset, NULL)) { warn (_("cannot block signals")); caught_signal = true; } if (!caught_signal) { struct sigaction action; action.sa_handler = su_catch_sig; sigemptyset (&action.sa_mask); action.sa_flags = 0; sigemptyset (&ourset); if (!same_session) { if (sigaddset(&ourset, SIGINT) || sigaddset(&ourset, SIGQUIT)) { warn (_("cannot set signal handler")); caught_signal = true; } } if (!caught_signal && (sigaddset(&ourset, SIGTERM) || sigaddset(&ourset, SIGALRM) || sigaction(SIGTERM, &action, &oldact[0]) || sigprocmask(SIG_UNBLOCK, &ourset, NULL))) { warn (_("cannot set signal handler")); caught_signal = true; } if (!caught_signal && !same_session && (sigaction(SIGINT, &action, &oldact[1]) || sigaction(SIGQUIT, &action, &oldact[2]))) { warn (_("cannot set signal handler")); caught_signal = true; } } if (!caught_signal) { pid_t pid; for (;;) { pid = waitpid (child, &status, WUNTRACED); if (pid != (pid_t) - 1 && WIFSTOPPED (status)) { kill (getpid (), SIGSTOP); /* once we get here, we must have resumed */ kill (pid, SIGCONT); } else { break; } } if (pid != (pid_t) - 1) { if (WIFSIGNALED (status)) { fprintf (stderr, "%s%s\n", strsignal (WTERMSIG (status)), WCOREDUMP (status) ? _(" (core dumped)") : ""); status = WTERMSIG (status) + 128; } else { status = WEXITSTATUS (status); } } else if (caught_signal) { status = caught_signal + 128; } else { status = 1; } } else { status = 1; } if (caught_signal) { fprintf (stderr, _("\nSession terminated, killing shell...")); kill (child, SIGTERM); } cleanup_pam (PAM_SUCCESS); if (caught_signal) { sleep (2); kill (child, SIGKILL); fprintf (stderr, _(" ...killed.\n")); /* Let's terminate itself with the received signal. * * It seems that shells use WIFSIGNALED() rather than our exit status * value to detect situations when is necessary to cleanup (reset) * terminal settings (kzak -- Jun 2013). */ switch (caught_signal) { case SIGTERM: sigaction(SIGTERM, &oldact[0], NULL); break; case SIGINT: sigaction(SIGINT, &oldact[1], NULL); break; case SIGQUIT: sigaction(SIGQUIT, &oldact[2], NULL); break; default: /* just in case that signal stuff initialization failed and * caught_signal = true */ caught_signal = SIGKILL; break; } kill(getpid(), caught_signal); } exit (status); }
static void loginpam_auth(struct login_context *cxt) { int rc, show_unknown; unsigned int retries, failcount = 0; const char *hostname = cxt->hostname ? cxt->hostname : cxt->tty_name ? cxt->tty_name : "<unknown>"; pam_handle_t *pamh = cxt->pamh; /* if we didn't get a user on the command line, set it to NULL */ loginpam_get_username(pamh, &cxt->username); show_unknown = getlogindefs_bool("LOG_UNKFAIL_ENAB", 0); retries = getlogindefs_num("LOGIN_RETRIES", LOGIN_MAX_TRIES); /* * 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 LOGIN_MAX_TRIES? */ rc = pam_authenticate(pamh, 0); while ((++failcount < retries) && ((rc == PAM_AUTH_ERR) || (rc == PAM_USER_UNKNOWN) || (rc == PAM_CRED_INSUFFICIENT) || (rc == PAM_AUTHINFO_UNAVAIL))) { if (rc == PAM_USER_UNKNOWN && !show_unknown) /* * Logging unknown usernames may be a security issue if * a user enters her password instead of her login name. */ cxt->username = NULL; else loginpam_get_username(pamh, &cxt->username); syslog(LOG_NOTICE, _("FAILED LOGIN %u FROM %s FOR %s, %s"), failcount, hostname, cxt->username ? cxt->username : "******", pam_strerror(pamh, rc)); log_btmp(cxt); log_audit(cxt, 0); fprintf(stderr, _("Login incorrect\n\n")); pam_set_item(pamh, PAM_USER, NULL); rc = pam_authenticate(pamh, 0); } if (is_pam_failure(rc)) { if (rc == PAM_USER_UNKNOWN && !show_unknown) cxt->username = NULL; else loginpam_get_username(pamh, &cxt->username); if (rc == PAM_MAXTRIES) syslog(LOG_NOTICE, _("TOO MANY LOGIN TRIES (%u) FROM %s FOR %s, %s"), failcount, hostname, cxt->username ? cxt->username : "******", pam_strerror(pamh, rc)); else syslog(LOG_NOTICE, _("FAILED LOGIN SESSION FROM %s FOR %s, %s"), hostname, cxt->username ? cxt->username : "******", pam_strerror(pamh, rc)); log_btmp(cxt); log_audit(cxt, 0); fprintf(stderr, _("\nLogin incorrect\n")); pam_end(pamh, rc); sleepexit(EXIT_SUCCESS); } }