938 lines
24 KiB
C
938 lines
24 KiB
C
/*
|
|
* Copyright information at end of file.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <malloc.h>
|
|
#include <pwd.h>
|
|
#include <shadow.h>
|
|
#include <limits.h>
|
|
#include <utmp.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <ctype.h>
|
|
#include <syslog.h>
|
|
#include <sys/resource.h>
|
|
#ifdef HAVE_NIS
|
|
#include <rpcsvc/ypclnt.h>
|
|
#endif
|
|
|
|
#include <security/_pam_macros.h>
|
|
#include <security/pam_modules.h>
|
|
#include <security/pam_ext.h>
|
|
#include <security/pam_modutil.h>
|
|
|
|
#include "pam_cc_compat.h"
|
|
#include "pam_inline.h"
|
|
#include "support.h"
|
|
#include "passverify.h"
|
|
|
|
/* this is a front-end for module-application conversations */
|
|
|
|
int _make_remark(pam_handle_t * pamh, unsigned long long ctrl,
|
|
int type, const char *text)
|
|
{
|
|
int retval = PAM_SUCCESS;
|
|
|
|
if (off(UNIX__QUIET, ctrl)) {
|
|
retval = pam_prompt(pamh, type, NULL, "%s", text);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
static int _unix_strtoi(const char *str, int minval, int *result)
|
|
{
|
|
char *ep;
|
|
long value = strtol(str, &ep, 10);
|
|
if (value < minval || value > INT_MAX || str == ep || *ep != '\0') {
|
|
*result = minval;
|
|
return -1;
|
|
}
|
|
*result = (int)value;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* set the control flags for the UNIX module.
|
|
*/
|
|
|
|
unsigned long long _set_ctrl(pam_handle_t *pamh, int flags, int *remember,
|
|
int *rounds, int *pass_min_len, int argc,
|
|
const char **argv)
|
|
{
|
|
unsigned long long ctrl;
|
|
char *val;
|
|
int j;
|
|
|
|
D(("called."));
|
|
|
|
ctrl = UNIX_DEFAULTS; /* the default selection of options */
|
|
|
|
/* set some flags manually */
|
|
|
|
if (getuid() == 0 && !(flags & PAM_CHANGE_EXPIRED_AUTHTOK)) {
|
|
D(("IAMROOT"));
|
|
set(UNIX__IAMROOT, ctrl);
|
|
}
|
|
if (flags & PAM_UPDATE_AUTHTOK) {
|
|
D(("UPDATE_AUTHTOK"));
|
|
set(UNIX__UPDATE, ctrl);
|
|
}
|
|
if (flags & PAM_PRELIM_CHECK) {
|
|
D(("PRELIM_CHECK"));
|
|
set(UNIX__PRELIM, ctrl);
|
|
}
|
|
if (flags & PAM_SILENT) {
|
|
D(("SILENT"));
|
|
set(UNIX__QUIET, ctrl);
|
|
}
|
|
|
|
/* preset encryption method with value from /etc/login.defs */
|
|
val = pam_modutil_search_key(pamh, LOGIN_DEFS, "ENCRYPT_METHOD");
|
|
if (val) {
|
|
for (j = 0; j < UNIX_CTRLS_; ++j) {
|
|
if (unix_args[j].token && unix_args[j].is_hash_algo
|
|
&& !strncasecmp(val, unix_args[j].token, strlen(unix_args[j].token))) {
|
|
break;
|
|
}
|
|
}
|
|
if (j >= UNIX_CTRLS_) {
|
|
pam_syslog(pamh, LOG_WARNING, "unrecognized ENCRYPT_METHOD value [%s]", val);
|
|
} else {
|
|
ctrl &= unix_args[j].mask; /* for turning things off */
|
|
ctrl |= unix_args[j].flag; /* for turning things on */
|
|
}
|
|
free (val);
|
|
}
|
|
|
|
/* now parse the arguments to this module */
|
|
|
|
for (; argc-- > 0; ++argv) {
|
|
const char *str = NULL;
|
|
|
|
D(("pam_unix arg: %s", *argv));
|
|
|
|
for (j = 0; j < UNIX_CTRLS_; ++j) {
|
|
if (unix_args[j].token
|
|
&& (str = pam_str_skip_prefix_len(*argv,
|
|
unix_args[j].token,
|
|
strlen(unix_args[j].token))) != NULL) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (str == NULL) {
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"unrecognized option [%s]", *argv);
|
|
} else {
|
|
/* special cases */
|
|
if (j == UNIX_REMEMBER_PASSWD) {
|
|
if (remember == NULL) {
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"option remember not allowed for this module type");
|
|
continue;
|
|
}
|
|
if (_unix_strtoi(str, -1, remember)) {
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"option remember invalid [%s]", str);
|
|
continue;
|
|
}
|
|
if (*remember > 400)
|
|
*remember = 400;
|
|
} else if (j == UNIX_MIN_PASS_LEN) {
|
|
if (pass_min_len == NULL) {
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"option minlen not allowed for this module type");
|
|
continue;
|
|
}
|
|
if (_unix_strtoi(str, 0, pass_min_len)) {
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"option minlen invalid [%s]", str);
|
|
continue;
|
|
}
|
|
} else if (j == UNIX_ALGO_ROUNDS) {
|
|
if (rounds == NULL) {
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"option rounds not allowed for this module type");
|
|
continue;
|
|
}
|
|
if (_unix_strtoi(str, 0, rounds)) {
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"option rounds invalid [%s]", str);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
ctrl &= unix_args[j].mask; /* for turning things off */
|
|
ctrl |= unix_args[j].flag; /* for turning things on */
|
|
}
|
|
}
|
|
|
|
if (UNIX_DES_CRYPT(ctrl)
|
|
&& pass_min_len && *pass_min_len > 8)
|
|
{
|
|
pam_syslog (pamh, LOG_NOTICE, "Password minlen reset to 8 characters");
|
|
*pass_min_len = 8;
|
|
}
|
|
|
|
if (flags & PAM_DISALLOW_NULL_AUTHTOK) {
|
|
D(("DISALLOW_NULL_AUTHTOK"));
|
|
set(UNIX__NONULL, ctrl);
|
|
}
|
|
|
|
/* Read number of rounds for sha256, sha512 and yescrypt */
|
|
if (off(UNIX_ALGO_ROUNDS, ctrl) && rounds != NULL) {
|
|
const char *key = NULL;
|
|
if (on(UNIX_YESCRYPT_PASS, ctrl))
|
|
key = "YESCRYPT_COST_FACTOR";
|
|
else if (on(UNIX_SHA256_PASS, ctrl) || on(UNIX_SHA512_PASS, ctrl))
|
|
key = "SHA_CRYPT_MAX_ROUNDS";
|
|
else
|
|
key = NULL;
|
|
|
|
if (key != NULL) {
|
|
val = pam_modutil_search_key(pamh, LOGIN_DEFS, key);
|
|
if (val) {
|
|
if (_unix_strtoi(val, 0, rounds))
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"option %s invalid [%s]", key, val);
|
|
else
|
|
set(UNIX_ALGO_ROUNDS, ctrl);
|
|
free (val);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Set default rounds for blowfish, gost-yescrypt and yescrypt */
|
|
if (off(UNIX_ALGO_ROUNDS, ctrl) && rounds != NULL) {
|
|
if (on(UNIX_BLOWFISH_PASS, ctrl) ||
|
|
on(UNIX_GOST_YESCRYPT_PASS, ctrl) ||
|
|
on(UNIX_YESCRYPT_PASS, ctrl)) {
|
|
*rounds = 5;
|
|
set(UNIX_ALGO_ROUNDS, ctrl);
|
|
}
|
|
}
|
|
|
|
/* Enforce sane "rounds" values */
|
|
if (on(UNIX_ALGO_ROUNDS, ctrl)) {
|
|
if (on(UNIX_GOST_YESCRYPT_PASS, ctrl) ||
|
|
on(UNIX_YESCRYPT_PASS, ctrl)) {
|
|
if (*rounds < 3)
|
|
*rounds = 3;
|
|
else if (*rounds > 11)
|
|
*rounds = 11;
|
|
} else if (on(UNIX_BLOWFISH_PASS, ctrl)) {
|
|
if (*rounds < 4)
|
|
*rounds = 4;
|
|
else if (*rounds > 31)
|
|
*rounds = 31;
|
|
} else if (on(UNIX_SHA256_PASS, ctrl) || on(UNIX_SHA512_PASS, ctrl)) {
|
|
if ((*rounds < 1000) || (*rounds == INT_MAX)) {
|
|
/* don't care about bogus values */
|
|
*rounds = 0;
|
|
unset(UNIX_ALGO_ROUNDS, ctrl);
|
|
} else if (*rounds >= 10000000) {
|
|
*rounds = 9999999;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* auditing is a more sensitive version of debug */
|
|
|
|
if (on(UNIX_AUDIT, ctrl)) {
|
|
set(UNIX_DEBUG, ctrl);
|
|
}
|
|
/* return the set of flags */
|
|
|
|
D(("done."));
|
|
return ctrl;
|
|
}
|
|
|
|
/* ************************************************************** *
|
|
* Useful non-trivial functions *
|
|
* ************************************************************** */
|
|
|
|
/*
|
|
* the following is used to keep track of the number of times a user fails
|
|
* to authenticate themself.
|
|
*/
|
|
|
|
#define FAIL_PREFIX "-UN*X-FAIL-"
|
|
#define UNIX_MAX_RETRIES 3
|
|
|
|
struct _pam_failed_auth {
|
|
char *user; /* user that's failed to be authenticated */
|
|
char *name; /* attempt from user with name */
|
|
int uid; /* uid of calling user */
|
|
int euid; /* euid of calling process */
|
|
int count; /* number of failures so far */
|
|
};
|
|
|
|
#ifndef PAM_DATA_REPLACE
|
|
#error "Need to get an updated libpam 0.52 or better"
|
|
#endif
|
|
|
|
static void _cleanup_failures(pam_handle_t * pamh, void *fl, int err)
|
|
{
|
|
int quiet;
|
|
const void *service = NULL;
|
|
const void *ruser = NULL;
|
|
const void *rhost = NULL;
|
|
const void *tty = NULL;
|
|
struct _pam_failed_auth *failure;
|
|
|
|
D(("called"));
|
|
|
|
quiet = err & PAM_DATA_SILENT; /* should we log something? */
|
|
err &= PAM_DATA_REPLACE; /* are we just replacing data? */
|
|
failure = (struct _pam_failed_auth *) fl;
|
|
|
|
if (failure != NULL) {
|
|
|
|
if (!quiet && !err) { /* under advisement from Sun,may go away */
|
|
|
|
/* log the number of authentication failures */
|
|
if (failure->count > 1) {
|
|
(void) pam_get_item(pamh, PAM_SERVICE,
|
|
&service);
|
|
(void) pam_get_item(pamh, PAM_RUSER,
|
|
&ruser);
|
|
(void) pam_get_item(pamh, PAM_RHOST,
|
|
&rhost);
|
|
(void) pam_get_item(pamh, PAM_TTY,
|
|
&tty);
|
|
pam_syslog(pamh, LOG_NOTICE,
|
|
"%d more authentication failure%s; "
|
|
"logname=%s uid=%d euid=%d "
|
|
"tty=%s ruser=%s rhost=%s "
|
|
"%s%s",
|
|
failure->count - 1, failure->count == 2 ? "" : "s",
|
|
failure->name, failure->uid, failure->euid,
|
|
tty ? (const char *)tty : "", ruser ? (const char *)ruser : "",
|
|
rhost ? (const char *)rhost : "",
|
|
(failure->user && failure->user[0] != '\0')
|
|
? " user=" : "",
|
|
failure->user ? failure->user : ""
|
|
);
|
|
|
|
if (failure->count > UNIX_MAX_RETRIES) {
|
|
pam_syslog(pamh, LOG_NOTICE,
|
|
"service(%s) ignoring max retries; %d > %d",
|
|
service == NULL ? "**unknown**" : (const char *)service,
|
|
failure->count,
|
|
UNIX_MAX_RETRIES);
|
|
}
|
|
}
|
|
}
|
|
_pam_delete(failure->user); /* tidy up */
|
|
_pam_delete(failure->name); /* tidy up */
|
|
free(failure);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* _unix_getpwnam() searches only /etc/passwd and NIS to find user information
|
|
*/
|
|
static void _unix_cleanup(pam_handle_t *pamh UNUSED, void *data, int error_status UNUSED)
|
|
{
|
|
free(data);
|
|
}
|
|
|
|
int _unix_getpwnam(pam_handle_t *pamh, const char *name,
|
|
int files, int nis, struct passwd **ret)
|
|
{
|
|
char *buf = NULL;
|
|
int matched = 0;
|
|
|
|
if (!matched && files && strchr(name, ':') == NULL) {
|
|
FILE *passwd;
|
|
|
|
passwd = fopen("/etc/passwd", "re");
|
|
if (passwd != NULL) {
|
|
size_t n = 0, userlen;
|
|
ssize_t r;
|
|
|
|
userlen = strlen(name);
|
|
|
|
while ((r = getline(&buf, &n, passwd)) != -1) {
|
|
if ((size_t)r > userlen && (buf[userlen] == ':') &&
|
|
(strncmp(name, buf, userlen) == 0)) {
|
|
char *p;
|
|
|
|
p = buf + strlen(buf) - 1;
|
|
while (isspace((unsigned char)*p) && (p >= buf)) {
|
|
*p-- = '\0';
|
|
}
|
|
matched = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!matched) {
|
|
_pam_drop(buf);
|
|
}
|
|
fclose(passwd);
|
|
}
|
|
}
|
|
|
|
#if defined(HAVE_NIS) && defined(HAVE_YP_GET_DEFAULT_DOMAIN) && defined (HAVE_YP_BIND) && defined (HAVE_YP_MATCH) && defined (HAVE_YP_UNBIND)
|
|
if (!matched && nis) {
|
|
char *userinfo = NULL, *domain = NULL;
|
|
int len = 0, i;
|
|
len = yp_get_default_domain(&domain);
|
|
if (len == YPERR_SUCCESS) {
|
|
len = yp_bind(domain);
|
|
}
|
|
if (len == YPERR_SUCCESS) {
|
|
i = yp_match(domain, "passwd.byname", name,
|
|
strlen(name), &userinfo, &len);
|
|
yp_unbind(domain);
|
|
if (i == YPERR_SUCCESS && (buf = strdup(userinfo)) != NULL) {
|
|
matched = 1;
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
/* we don't have NIS support, make compiler happy. */
|
|
(void) nis;
|
|
#endif
|
|
|
|
if (matched && (ret != NULL)) {
|
|
char *slogin, *spasswd, *suid, *sgid, *sgecos, *shome, *sshell, *p;
|
|
size_t retlen;
|
|
|
|
*ret = NULL;
|
|
|
|
slogin = buf;
|
|
|
|
spasswd = strchr(slogin, ':');
|
|
if (spasswd == NULL) {
|
|
goto fail;
|
|
}
|
|
*spasswd++ = '\0';
|
|
|
|
suid = strchr(spasswd, ':');
|
|
if (suid == NULL) {
|
|
goto fail;
|
|
}
|
|
*suid++ = '\0';
|
|
|
|
sgid = strchr(suid, ':');
|
|
if (sgid == NULL) {
|
|
goto fail;
|
|
}
|
|
*sgid++ = '\0';
|
|
|
|
sgecos = strchr(sgid, ':');
|
|
if (sgecos == NULL) {
|
|
goto fail;
|
|
}
|
|
*sgecos++ = '\0';
|
|
|
|
shome = strchr(sgecos, ':');
|
|
if (shome == NULL) {
|
|
goto fail;
|
|
}
|
|
*shome++ = '\0';
|
|
|
|
sshell = strchr(shome, ':');
|
|
if (sshell == NULL) {
|
|
goto fail;
|
|
}
|
|
*sshell++ = '\0';
|
|
|
|
retlen = sizeof(struct passwd) +
|
|
strlen(slogin) + 1 +
|
|
strlen(spasswd) + 1 +
|
|
strlen(sgecos) + 1 +
|
|
strlen(shome) + 1 +
|
|
strlen(sshell) + 1;
|
|
*ret = calloc(retlen, sizeof(char));
|
|
if (*ret == NULL) {
|
|
goto fail;
|
|
}
|
|
|
|
(*ret)->pw_uid = strtol(suid, &p, 10);
|
|
if ((strlen(suid) == 0) || (*p != '\0')) {
|
|
goto fail;
|
|
}
|
|
|
|
(*ret)->pw_gid = strtol(sgid, &p, 10);
|
|
if ((strlen(sgid) == 0) || (*p != '\0')) {
|
|
goto fail;
|
|
}
|
|
|
|
p = ((char*)(*ret)) + sizeof(struct passwd);
|
|
(*ret)->pw_name = strcpy(p, slogin);
|
|
p += strlen(p) + 1;
|
|
(*ret)->pw_passwd = strcpy(p, spasswd);
|
|
p += strlen(p) + 1;
|
|
(*ret)->pw_gecos = strcpy(p, sgecos);
|
|
p += strlen(p) + 1;
|
|
(*ret)->pw_dir = strcpy(p, shome);
|
|
p += strlen(p) + 1;
|
|
(*ret)->pw_shell = strcpy(p, sshell);
|
|
|
|
_pam_drop(buf);
|
|
if (asprintf(&buf, "_pam_unix_getpwnam_%s", name) < 0) {
|
|
buf = NULL;
|
|
goto fail;
|
|
}
|
|
|
|
if (pam_set_data(pamh, buf,
|
|
*ret, _unix_cleanup) != PAM_SUCCESS) {
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
_pam_drop(buf);
|
|
return matched;
|
|
fail:
|
|
_pam_drop(buf);
|
|
_pam_drop(*ret);
|
|
return matched;
|
|
}
|
|
|
|
/*
|
|
* _unix_comsefromsource() is a quick check to see if information about a given
|
|
* user comes from a particular source (just files and nis for now)
|
|
*
|
|
*/
|
|
int _unix_comesfromsource(pam_handle_t *pamh,
|
|
const char *name, int files, int nis)
|
|
{
|
|
return _unix_getpwnam(pamh, name, files, nis, NULL);
|
|
}
|
|
|
|
/*
|
|
* verify the password of a user
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
static int _unix_run_helper_binary(pam_handle_t *pamh, const char *passwd,
|
|
unsigned long long ctrl, const char *user)
|
|
{
|
|
int retval, child, fds[2];
|
|
struct sigaction newsa, oldsa;
|
|
|
|
D(("called."));
|
|
/* create a pipe for the password */
|
|
if (pipe(fds) != 0) {
|
|
D(("could not make pipe"));
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
|
|
if (off(UNIX_NOREAP, ctrl)) {
|
|
/*
|
|
* This code arranges that the demise of the child does not cause
|
|
* the application to receive a signal it is not expecting - which
|
|
* may kill the application or worse.
|
|
*
|
|
* The "noreap" module argument is provided so that the admin can
|
|
* override this behavior.
|
|
*/
|
|
memset(&newsa, '\0', sizeof(newsa));
|
|
newsa.sa_handler = SIG_DFL;
|
|
sigaction(SIGCHLD, &newsa, &oldsa);
|
|
}
|
|
|
|
/* fork */
|
|
child = fork();
|
|
if (child == 0) {
|
|
static char *envp[] = { NULL };
|
|
const char *args[] = { NULL, NULL, NULL, NULL };
|
|
|
|
/* XXX - should really tidy up PAM here too */
|
|
|
|
/* reopen stdin as pipe */
|
|
if (dup2(fds[0], STDIN_FILENO) != STDIN_FILENO) {
|
|
pam_syslog(pamh, LOG_ERR, "dup2 of %s failed: %m", "stdin");
|
|
_exit(PAM_AUTHINFO_UNAVAIL);
|
|
}
|
|
|
|
if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_IGNORE_FD,
|
|
PAM_MODUTIL_PIPE_FD,
|
|
PAM_MODUTIL_PIPE_FD) < 0) {
|
|
_exit(PAM_AUTHINFO_UNAVAIL);
|
|
}
|
|
|
|
/* must set the real uid to 0 so the helper will not error
|
|
out if pam is called from setuid binary (su, sudo...) */
|
|
if (setuid(0) == -1) {
|
|
D(("setuid failed"));
|
|
if (geteuid() == 0) {
|
|
_exit(PAM_AUTHINFO_UNAVAIL);
|
|
}
|
|
}
|
|
|
|
/* exec binary helper */
|
|
args[0] = CHKPWD_HELPER;
|
|
args[1] = user;
|
|
if (off(UNIX__NONULL, ctrl)) { /* this means we've succeeded */
|
|
args[2]="nullok";
|
|
} else {
|
|
args[2]="nonull";
|
|
}
|
|
|
|
DIAG_PUSH_IGNORE_CAST_QUAL;
|
|
execve(CHKPWD_HELPER, (char *const *) args, envp);
|
|
DIAG_POP_IGNORE_CAST_QUAL;
|
|
|
|
/* should not get here: exit with error */
|
|
D(("helper binary is not available"));
|
|
_exit(PAM_AUTHINFO_UNAVAIL);
|
|
} else if (child > 0) {
|
|
/* wait for child */
|
|
/* if the stored password is NULL */
|
|
int rc=0;
|
|
if (passwd != NULL) { /* send the password to the child */
|
|
size_t len = strlen(passwd);
|
|
|
|
if (len > PAM_MAX_RESP_SIZE)
|
|
len = PAM_MAX_RESP_SIZE;
|
|
if (write(fds[1], passwd, len) == -1 ||
|
|
write(fds[1], "", 1) == -1) {
|
|
pam_syslog (pamh, LOG_ERR, "Cannot send password to helper: %m");
|
|
retval = PAM_AUTH_ERR;
|
|
}
|
|
passwd = NULL;
|
|
} else { /* blank password */
|
|
if (write(fds[1], "", 1) == -1) {
|
|
pam_syslog (pamh, LOG_ERR, "Cannot send password to helper: %m");
|
|
retval = PAM_AUTH_ERR;
|
|
}
|
|
}
|
|
close(fds[0]); /* close here to avoid possible SIGPIPE above */
|
|
close(fds[1]);
|
|
/* wait for helper to complete: */
|
|
while ((rc=waitpid(child, &retval, 0)) < 0 && errno == EINTR);
|
|
if (rc<0) {
|
|
pam_syslog(pamh, LOG_ERR, "unix_chkpwd waitpid returned %d: %m", rc);
|
|
retval = PAM_AUTH_ERR;
|
|
} else if (!WIFEXITED(retval)) {
|
|
pam_syslog(pamh, LOG_ERR, "unix_chkpwd abnormal exit: %d", retval);
|
|
retval = PAM_AUTH_ERR;
|
|
} else {
|
|
retval = WEXITSTATUS(retval);
|
|
}
|
|
} else {
|
|
D(("fork failed"));
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
retval = PAM_AUTH_ERR;
|
|
}
|
|
|
|
if (off(UNIX_NOREAP, ctrl)) {
|
|
sigaction(SIGCHLD, &oldsa, NULL); /* restore old signal handler */
|
|
}
|
|
|
|
D(("returning %d", retval));
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* _unix_blankpasswd() is a quick check for a blank password
|
|
*
|
|
* returns TRUE if user does not have a password
|
|
* - to avoid prompting for one in such cases (CG)
|
|
*/
|
|
|
|
int
|
|
_unix_blankpasswd (pam_handle_t *pamh, unsigned long long ctrl, const char *name)
|
|
{
|
|
struct passwd *pwd = NULL;
|
|
char *salt = NULL;
|
|
int daysleft;
|
|
int retval;
|
|
int blank = 0;
|
|
int execloop;
|
|
int nonexistent_check = 1;
|
|
|
|
D(("called"));
|
|
|
|
/*
|
|
* This function does not have to be too smart if something goes
|
|
* wrong, return FALSE and let this case be treated somewhere
|
|
* else (CG)
|
|
*/
|
|
|
|
if (on(UNIX_NULLRESETOK, ctrl)) {
|
|
retval = _unix_verify_user(pamh, ctrl, name, &daysleft);
|
|
if (retval == PAM_NEW_AUTHTOK_REQD) {
|
|
/* password reset is enforced, allow authentication with empty password */
|
|
pam_syslog(pamh, LOG_DEBUG, "user [%s] has expired blank password, enabling nullok", name);
|
|
set(UNIX__NULLOK, ctrl);
|
|
}
|
|
}
|
|
|
|
if (on(UNIX__NONULL, ctrl))
|
|
return 0; /* will fail but don't let on yet */
|
|
|
|
/* UNIX passwords area */
|
|
|
|
/*
|
|
* Execute this loop twice: one checking the password hash of an existing
|
|
* user and another one for a non-existing user. This way the runtimes
|
|
* are equal, making it more difficult to differentiate existing from
|
|
* non-existing users.
|
|
*/
|
|
for (execloop = 0; execloop < 2; ++execloop) {
|
|
retval = get_pwd_hash(pamh, name, &pwd, &salt);
|
|
|
|
if (retval == PAM_UNIX_RUN_HELPER) {
|
|
if (_unix_run_helper_binary(pamh, NULL, ctrl, name) == PAM_SUCCESS)
|
|
blank = nonexistent_check;
|
|
} else if (retval == PAM_USER_UNKNOWN) {
|
|
name = "root";
|
|
nonexistent_check = 0;
|
|
continue;
|
|
} else if (salt != NULL) {
|
|
if (strlen(salt) == 0)
|
|
blank = nonexistent_check;
|
|
}
|
|
name = "pam_unix_non_existent:";
|
|
/* non-existent user check will not affect the blank value */
|
|
}
|
|
|
|
/* tidy up */
|
|
if (salt)
|
|
_pam_delete(salt);
|
|
|
|
return blank;
|
|
}
|
|
|
|
int _unix_verify_password(pam_handle_t * pamh, const char *name
|
|
,const char *p, unsigned long long ctrl)
|
|
{
|
|
struct passwd *pwd = NULL;
|
|
char *salt = NULL;
|
|
char *data_name;
|
|
char pw[PAM_MAX_RESP_SIZE + 1];
|
|
int retval;
|
|
|
|
|
|
D(("called"));
|
|
|
|
if (off(UNIX_NODELAY, ctrl)) {
|
|
D(("setting delay"));
|
|
(void) pam_fail_delay(pamh, 2000000); /* 2 sec delay for on failure */
|
|
}
|
|
|
|
/* locate the entry for this user */
|
|
|
|
D(("locating user's record"));
|
|
|
|
retval = get_pwd_hash(pamh, name, &pwd, &salt);
|
|
|
|
if (asprintf(&data_name, "%s%s", FAIL_PREFIX, name) < 0) {
|
|
pam_syslog(pamh, LOG_CRIT, "no memory for data-name");
|
|
data_name = NULL;
|
|
}
|
|
|
|
if (p != NULL && strlen(p) > PAM_MAX_RESP_SIZE) {
|
|
memset(pw, 0, sizeof(pw));
|
|
p = strncpy(pw, p, sizeof(pw) - 1);
|
|
}
|
|
|
|
if (retval != PAM_SUCCESS) {
|
|
if (retval == PAM_UNIX_RUN_HELPER) {
|
|
D(("running helper binary"));
|
|
retval = _unix_run_helper_binary(pamh, p, ctrl, name);
|
|
} else {
|
|
D(("user's record unavailable"));
|
|
p = NULL;
|
|
if (on(UNIX_AUDIT, ctrl)) {
|
|
/* this might be a typo and the user has given a password
|
|
instead of a username. Careful with this. */
|
|
pam_syslog(pamh, LOG_NOTICE,
|
|
"check pass; user (%s) unknown", name);
|
|
} else {
|
|
name = NULL;
|
|
if (on(UNIX_DEBUG, ctrl) || pwd == NULL) {
|
|
pam_syslog(pamh, LOG_NOTICE,
|
|
"check pass; user unknown");
|
|
} else {
|
|
/* don't log failure as another pam module can succeed */
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
retval = verify_pwd_hash(pamh, p, salt, off(UNIX__NONULL, ctrl));
|
|
}
|
|
|
|
if (retval == PAM_SUCCESS) {
|
|
if (data_name) /* reset failures */
|
|
pam_set_data(pamh, data_name, NULL, _cleanup_failures);
|
|
} else {
|
|
if (data_name != NULL) {
|
|
struct _pam_failed_auth *new = NULL;
|
|
const struct _pam_failed_auth *old = NULL;
|
|
|
|
/* get a failure recorder */
|
|
|
|
new = (struct _pam_failed_auth *)
|
|
malloc(sizeof(struct _pam_failed_auth));
|
|
|
|
if (new != NULL) {
|
|
|
|
const char *login_name;
|
|
const void *void_old;
|
|
|
|
|
|
login_name = pam_modutil_getlogin(pamh);
|
|
if (login_name == NULL) {
|
|
login_name = "";
|
|
}
|
|
|
|
new->user = strdup(name ? name : "");
|
|
new->uid = getuid();
|
|
new->euid = geteuid();
|
|
new->name = strdup(login_name);
|
|
|
|
/* any previous failures for this user ? */
|
|
if (pam_get_data(pamh, data_name, &void_old)
|
|
== PAM_SUCCESS)
|
|
old = void_old;
|
|
else
|
|
old = NULL;
|
|
|
|
if (old != NULL) {
|
|
new->count = old->count + 1;
|
|
if (new->count >= UNIX_MAX_RETRIES) {
|
|
retval = PAM_MAXTRIES;
|
|
}
|
|
} else {
|
|
const void *service=NULL;
|
|
const void *ruser=NULL;
|
|
const void *rhost=NULL;
|
|
const void *tty=NULL;
|
|
|
|
(void) pam_get_item(pamh, PAM_SERVICE,
|
|
&service);
|
|
(void) pam_get_item(pamh, PAM_RUSER,
|
|
&ruser);
|
|
(void) pam_get_item(pamh, PAM_RHOST,
|
|
&rhost);
|
|
(void) pam_get_item(pamh, PAM_TTY,
|
|
&tty);
|
|
|
|
pam_syslog(pamh, LOG_NOTICE,
|
|
"authentication failure; "
|
|
"logname=%s uid=%d euid=%d "
|
|
"tty=%s ruser=%s rhost=%s "
|
|
"%s%s",
|
|
new->name, new->uid, new->euid,
|
|
tty ? (const char *)tty : "",
|
|
ruser ? (const char *)ruser : "",
|
|
rhost ? (const char *)rhost : "",
|
|
(new->user && new->user[0] != '\0')
|
|
? " user=" : "",
|
|
new->user ? new->user : ""
|
|
);
|
|
new->count = 1;
|
|
}
|
|
|
|
pam_set_data(pamh, data_name, new, _cleanup_failures);
|
|
|
|
} else {
|
|
pam_syslog(pamh, LOG_CRIT,
|
|
"no memory for failure recorder");
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
pam_overwrite_array(pw); /* clear memory of the password */
|
|
if (data_name)
|
|
_pam_delete(data_name);
|
|
if (salt)
|
|
_pam_delete(salt);
|
|
|
|
D(("done [%d].", retval));
|
|
|
|
return retval;
|
|
}
|
|
|
|
int
|
|
_unix_verify_user(pam_handle_t *pamh,
|
|
unsigned long long ctrl,
|
|
const char *name,
|
|
int *daysleft)
|
|
{
|
|
int retval;
|
|
struct spwd *spent;
|
|
struct passwd *pwent;
|
|
|
|
retval = get_account_info(pamh, name, &pwent, &spent);
|
|
if (retval == PAM_USER_UNKNOWN) {
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"could not identify user (from getpwnam(%s))",
|
|
name);
|
|
return retval;
|
|
}
|
|
|
|
if (retval == PAM_SUCCESS && spent == NULL)
|
|
return PAM_SUCCESS;
|
|
|
|
if (retval == PAM_UNIX_RUN_HELPER) {
|
|
retval = _unix_run_verify_binary(pamh, ctrl, name, daysleft);
|
|
if (retval == PAM_AUTHINFO_UNAVAIL &&
|
|
on(UNIX_BROKEN_SHADOW, ctrl))
|
|
return PAM_SUCCESS;
|
|
} else if (retval != PAM_SUCCESS) {
|
|
if (on(UNIX_BROKEN_SHADOW,ctrl))
|
|
return PAM_SUCCESS;
|
|
else
|
|
return retval;
|
|
} else
|
|
retval = check_shadow_expiry(pamh, spent, daysleft);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* ****************************************************************** *
|
|
* Copyright (c) Jan Rękorajski 1999.
|
|
* Copyright (c) Andrew G. Morgan 1996-8.
|
|
* Copyright (c) Alex O. Yuriev, 1996.
|
|
* Copyright (c) Cristian Gafton 1996.
|
|
* Copyright (c) Red Hat, Inc. 2007.
|
|
*
|
|
* 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, and the entire permission notice in its entirety,
|
|
* including the disclaimer of warranties.
|
|
* 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 author may not be used to endorse or promote
|
|
* products derived from this software without specific prior
|
|
* written permission.
|
|
*
|
|
* ALTERNATIVELY, this product may be distributed under the terms of
|
|
* the GNU Public License, in which case the provisions of the GPL are
|
|
* required INSTEAD OF the above restrictions. (This clause is
|
|
* necessary due to a potential bad interaction between the GPL and
|
|
* the restrictions contained in a BSD-style copyright.)
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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.
|
|
*/
|