pam-modules/pam_fshadow/pam_fshadow.c
Sergey Poznyakoff 203524c85d pam_fshadow: new option skip-password
* pam_fshadow/pam_fshadow.c (pam_opt): New option skip-password.
(_pam_parse,verify_user_acct,verify_user_pass): Fix log messages.
(verify_user_pass): Skip password verification, if requested.
(pam_sm_authenticate): Don't try to obtain password, if skip-password
is given.
Bugfix: even if password hash is given in passwd and verified successfully,
verify the shadow file too, this time with password set to NULL, so that
the actual account status is taken into account.
* doc/pam-modules.texi: Document skip-password
* doc/pam_fshadow.8in: Likewise.
2022-02-03 18:48:41 +02:00

619 lines
15 KiB
C

/* This file is part of pam-modules.
* Copyright (C) 2001-2022 Sergey Poznyakoff
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* pam_fshadow */
#include <graypam.h>
#include <time.h>
#include <pwd.h>
#ifdef HAVE_SHADOW_H
#include <shadow.h>
#endif
#if defined(HAVE_CRYPT_H)
# include <crypt.h>
#else
extern char *crypt(const char *, const char *);
#endif
#define PAM_SM_AUTH
#define PAM_SM_ACCOUNT
#include <security/pam_modules.h>
#if !HAVE_FGETPWENT
static struct passwd *
fgetpwent(FILE *fp)
{
static char *buffer;
static size_t buflen;
static struct passwd pwbuf;
size_t pos = 0;
int c;
size_t off[6];
int i = 0;
while ((c = fgetc(fp)) != EOF) {
if (pos == buflen) {
char *nb;
size_t ns;
if (buflen == 0)
ns = 128;
else {
ns = ns * 2;
if (ns < buflen) {
errno = ENOMEM;
return NULL;
}
}
nb = realloc(buffer, ns);
if (!nb)
return NULL;
buffer = nb;
buflen = ns;
}
if (c == '\n') {
buffer[pos++] = 0;
if (i != sizeof(off)/sizeof(off[0]))
continue;
break;
}
if (c == ':') {
buffer[pos++] = 0;
if (i < sizeof(off)/sizeof(off[0]))
off[i++] = pos;
} else
buffer[pos++] = c;
}
if (pos == 0)
return NULL;
pwbuf.pw_name = buffer;
pwbuf.pw_passwd = buffer + off[0];
pwbuf.pw_uid = strtoul(buffer + off[1], NULL, 10);
pwbuf.pw_gid = strtoul(buffer + off[2], NULL, 10);
pwbuf.pw_gecos = buffer + off[3];
pwbuf.pw_dir = buffer + off[4];
pwbuf.pw_shell = buffer + off[5];
return &pwbuf;
}
#endif
#define CNTL_AUTHTOK 0x0010
#define CNTL_PASSWD 0x0020
#define CNTL_SHADOW 0x0040
#define CNTL_REGEX 0x0080
#define CNTL_REVERT_INDEX 0x0100
#define CNTL_SKIP_PASSWORD 0x0200
static regex_t rexp;
static char *sysconfdir = SYSCONFDIR;
static int cntl_flags = CNTL_PASSWD|CNTL_SHADOW;
static long debug_level = 0;
static const char *regex_str = NULL;
static int regex_flags = REG_EXTENDED;
static long username_index = 1;
static long domain_index = 2;
struct pam_opt pam_opt[] = {
{ PAM_OPTSTR(debug), pam_opt_long, &debug_level },
{ PAM_OPTSTR(debug), pam_opt_const, &debug_level, { 1 } },
{ PAM_OPTSTR(audit), pam_opt_const, &debug_level, { 100 } },
{ PAM_OPTSTR(waitdebug), pam_opt_null, NULL, { 0 },
gray_wait_debug_fun },
{ PAM_OPTSTR(use_authtok), pam_opt_bitmask, &cntl_flags,
{ CNTL_AUTHTOK } },
{ PAM_OPTSTR(sysconfdir), pam_opt_string, &sysconfdir },
{ PAM_OPTSTR(regex), pam_opt_string, &regex_str },
{ PAM_OPTSTR(extended), pam_opt_bitmask, &regex_flags,
{ .value = REG_EXTENDED } },
{ PAM_OPTSTR(basic), pam_opt_bitmask_rev, &regex_flags,
{ .value = REG_EXTENDED } },
{ PAM_OPTSTR(icase), pam_opt_bitmask, &regex_flags,
{ .value = REG_ICASE } },
{ PAM_OPTSTR(ignore-case), pam_opt_bitmask, &regex_flags,
{ .value = REG_ICASE } },
{ PAM_OPTSTR(case), pam_opt_bitmask_rev, &regex_flags,
{ .value = REG_ICASE } },
{ PAM_OPTSTR(passwd), pam_opt_bool, &cntl_flags,
{ .value = CNTL_PASSWD } },
{ PAM_OPTSTR(shadow), pam_opt_bool, &cntl_flags,
{ .value = CNTL_SHADOW } },
{ PAM_OPTSTR(revert-index), pam_opt_bool, &cntl_flags,
{ .value = CNTL_REVERT_INDEX } },
{ PAM_OPTSTR(username-index), pam_opt_long, &username_index },
{ PAM_OPTSTR(domain-index), pam_opt_long, &domain_index },
{ PAM_OPTSTR(skip-password), pam_opt_bool, &cntl_flags,
{ .value = CNTL_SKIP_PASSWORD } },
{ NULL }
};
static int
_pam_parse(pam_handle_t *pamh, int argc, const char **argv)
{
int retval = PAM_SUCCESS;
memset(&rexp, 0, sizeof(rexp));
regex_str = NULL;
regex_flags = REG_EXTENDED;
username_index = 1;
domain_index = 2;
gray_log_init(0, MODULE_NAME, LOG_AUTHPRIV);
if (gray_parseopt(pam_opt, argc, argv))
return PAM_AUTHINFO_UNAVAIL;
if ((cntl_flags & (CNTL_PASSWD|CNTL_SHADOW)) == 0) {
_pam_log(LOG_CRIT,
"at least one of passwd or shadow must be set");
return PAM_AUTHINFO_UNAVAIL;
}
if (username_index <= 0) {
_pam_log(LOG_CRIT, "username-index out of range");
return PAM_AUTHINFO_UNAVAIL;
}
if (domain_index <= 0) {
_pam_log(LOG_CRIT, "domain-index out of range");
return PAM_AUTHINFO_UNAVAIL;
}
if (cntl_flags & CNTL_REVERT_INDEX) {
long t = username_index;
username_index = domain_index;
domain_index = t;
}
if (regex_str) {
int rc;
if ((rc = regcomp(&rexp, regex_str, regex_flags))) {
size_t s = regerror(rc, &rexp, NULL, 0);
char *buf = malloc (s);
if (buf) {
regerror(rc, &rexp, buf, s);
_pam_log(LOG_NOTICE,
"cannot compile regex `%s': %s",
regex_str, buf);
free (buf);
} else
_pam_log(LOG_NOTICE,
"cannot compile regex `%s'",
regex_str);
retval = PAM_AUTHINFO_UNAVAIL;
} else if (!(username_index <= rexp.re_nsub
&& domain_index <= rexp.re_nsub)) {
if (username_index > rexp.re_nsub)
_pam_log(LOG_NOTICE,
"not enough parenthesized groups"
" to satisfy username-index");
if (domain_index > rexp.re_nsub)
_pam_log(LOG_NOTICE,
"not enough parenthesized groups"
" to satisfy domain-index");
regfree(&rexp);
retval = PAM_AUTHINFO_UNAVAIL;
} else
cntl_flags |= CNTL_REGEX;
}
return retval;
}
static int
_pam_get_password(pam_handle_t *pamh, char **password, const char *prompt)
{
char *item, *token;
int retval;
struct pam_message msg[3], *pmsg[3];
struct pam_response *resp;
int i, replies;
DEBUG(90,("enter _pam_get_password"));
if (cntl_flags & CNTL_AUTHTOK) {
/*
* get the password from the PAM item
*/
retval = pam_get_item(pamh, PAM_AUTHTOK,
(const void **) &item);
if (retval != PAM_SUCCESS) {
/* very strange. */
_pam_log(LOG_ALERT,
"can't retrieve password item: %s",
pam_strerror(pamh, retval));
return retval;
} else if (item != NULL) {
*password = item;
item = NULL;
return PAM_SUCCESS;
} else
return PAM_AUTHTOK_RECOVER_ERR;
}
/*
* ask user for the password
*/
/* prepare to converse */
i = 0;
pmsg[i] = &msg[i];
msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
msg[i++].msg = (const void*)prompt;
replies = 1;
/* run conversation */
resp = NULL;
token = NULL;
retval = gray_converse(pamh, i, pmsg, &resp);
if (resp != NULL) {
if (retval == PAM_SUCCESS) { /* a good conversation */
token = XSTRDUP(resp[i - replies].resp);
pam_set_item(pamh, PAM_AUTHTOK, token);
DEBUG(100,("app returned [%s]", token));
PAM_DROP_REPLY(resp, 1);
} else {
_pam_log(LOG_ERR, "conversation error: %s",
pam_strerror(pamh, retval));
}
} else {
retval = (retval == PAM_SUCCESS)
? PAM_AUTHTOK_RECOVER_ERR : retval;
}
if (retval == PAM_SUCCESS) {
/*
* keep password as data specific to this module. pam_end()
* will arrange to clean it up.
*/
retval = pam_set_data(pamh, "password",
(void *)token,
gray_cleanup_string);
if (retval != PAM_SUCCESS) {
_pam_log(LOG_CRIT,
"can't keep password: %s",
pam_strerror(pamh, retval));
gray_pam_delete(token);
} else {
*password = token;
token = NULL; /* break link to password */
}
} else {
_pam_log(LOG_ERR,
"unable to obtain a password: %s",
pam_strerror(pamh, retval));
}
DEBUG(90,("exit _pam_get_password: %d", retval));
return retval;
}
char *
mkfilename(const char *dir, const char *name)
{
int len = strlen(dir) + strlen(name);
char *p = malloc(len+2);
if (!p) {
_pam_log(LOG_EMERG, "not enough memory");
abort ();
}
sprintf(p, "%s/%s", dir, name);
return p;
}
int
verify_user_acct(const char *confdir, const char *username, char **pwd)
{
char *filename = mkfilename(confdir, "passwd");
FILE *fp;
int retval;
DEBUG(10,("Looking up user `%s' in `%s'",
username, filename));
*pwd = NULL;
fp = fopen (filename, "r");
if (fp) {
struct passwd *pw;
while ((pw = fgetpwent(fp)) != NULL) {
if (strcmp (pw->pw_name, username) == 0)
break;
}
fclose(fp);
if (!pw) {
_pam_log(LOG_ERR, "user %s not found in %s",
username, filename);
retval = PAM_USER_UNKNOWN;
} else {
if (pw->pw_passwd && strlen(pw->pw_passwd) > 1) {
if (debug_level && !(cntl_flags & CNTL_SKIP_PASSWORD)) {
_pam_debug("user %s: password hash obtained from %s",
username, filename);
}
*pwd = strdup(pw->pw_passwd);
}
retval = PAM_SUCCESS;
}
} else {
_pam_log(LOG_ERR, "can't open %s: %s",
filename, strerror(errno));
retval = PAM_SERVICE_ERR;
}
free(filename);
return retval;
}
int
verify_user_pass(const char *confdir, const char *username,
const char *password)
{
#if defined(HAVE_FGETSPENT) && defined(HAVE_STRUCT_SPWD)
struct spwd *sp = NULL;
time_t curdays;
FILE *fp;
int retval = PAM_AUTH_ERR;
char *shadow = mkfilename(confdir, "shadow");
if (debug_level == 100 && password)
_pam_debug("Verifying user `%s' with password `%s' in `%s'",
username, password, shadow);
else if (debug_level >= 10)
_pam_debug("Verifying user `%s' in `%s'%s",
username, shadow,
password ? "" : " (password verification turned off)");
fp = fopen(shadow, "r");
if (!fp) {
_pam_log(LOG_ERR,
"can't open %s: %s", shadow, strerror(errno));
free(shadow);
return PAM_SERVICE_ERR;
}
while ((sp = fgetspent(fp)) != NULL
&& strcmp(sp->sp_namp, username))
;
fclose(fp);
if (!sp) {
_pam_log(LOG_ERR,
"entry for %s not found in %s",
username, shadow);
free(shadow);
return PAM_USER_UNKNOWN;
}
/* We have the user's information, now let's check if his account
has expired */
curdays = time(NULL) / (60 * 60 * 24);
if (sp->sp_min != -1 && curdays < sp->sp_lstchg + sp->sp_min)
retval = PAM_AUTHTOK_ERR;
else if (sp->sp_max != -1 && sp->sp_inact != -1 && sp->sp_lstchg != 0
&& curdays > sp->sp_lstchg + sp->sp_max + sp->sp_inact)
/* Password is too old */
retval = PAM_ACCT_EXPIRED;
#if defined(HAVE_STRUCT_SPWD_SP_EXPIRE)
else if (sp->sp_expire != -1 && sp->sp_lstchg != 0
&& curdays > sp->sp_expire)
/* Account has expired */
retval = PAM_ACCT_EXPIRED;
#endif
else if (password == NULL)
/* No password verification is required */
retval = PAM_SUCCESS;
else if (strcmp(sp->sp_pwdp, crypt(password, sp->sp_pwdp)) == 0)
retval = PAM_SUCCESS;
else
retval = PAM_AUTH_ERR;
free(shadow);
return retval;
#else
return PAM_AUTH_ERR;
#endif
}
static int
copy_backref (pam_handle_t *pamh, const char *name,
const char *buf, regmatch_t rmatch[3], int index, char **pstr)
{
char *str;
size_t size;
int rc;
if (rmatch[index].rm_so == -1)
size = 0;
else
size = rmatch[index].rm_eo - rmatch[index].rm_so;
str = malloc (size + 1);
if (!str) {
_pam_log(LOG_CRIT, "not enough memory");
return PAM_SYSTEM_ERR;
}
rc = pam_set_data(pamh, name, (void *)str, gray_cleanup_string);
if (rc != PAM_SUCCESS) {
_pam_log(LOG_CRIT,
"can't keep data [%s]: %s",
name,
pam_strerror(pamh, rc));
gray_pam_delete(str);
} else {
if (size != 0)
memcpy(str, buf + rmatch[index].rm_so, size);
str[size] = 0;
*pstr = str;
}
return rc;
}
/* --- authentication management functions (only) --- */
static int
translate(pam_handle_t *pamh, char const *input,
char **ret_username, char **ret_confdir)
{
size_t nmatch = (domain_index > username_index
? domain_index : username_index) + 1;
regmatch_t *rmatch;
int rc;
rmatch = calloc(nmatch, sizeof(rmatch[0]));
if (!rmatch) {
_pam_log(LOG_ERR, "out of memory");
return PAM_SERVICE_ERR;
}
if (regexec(&rexp, input, nmatch, rmatch, 0) == 0) {
char *domain;
if ((rc = copy_backref(pamh, "DOMAIN", input, rmatch,
domain_index, &domain)) == PAM_SUCCESS
&& ((rc = copy_backref(pamh, "USERNAME", input, rmatch,
username_index, ret_username))
== PAM_SUCCESS)) {
*ret_confdir = mkfilename(sysconfdir, domain);
pam_set_data(pamh, "CONFDIR",
(void *)*ret_confdir, gray_cleanup_string);
}
} else {
DEBUG(1,("user name `%s' does not match regular "
"expression `%s'",
input,
regex_str));
rc = PAM_AUTH_ERR;
}
free(rmatch);
return rc;
}
PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
const char *input_username;
char *username;
char *password;
int retval = PAM_AUTH_ERR;
int rc;
char *confdir;
char *pwstr = NULL;
/* parse arguments */
if ((rc = _pam_parse(pamh, argc, argv)) != PAM_SUCCESS)
return rc;
confdir = sysconfdir;
/* Get the username */
retval = pam_get_user(pamh, &input_username, NULL);
if (retval != PAM_SUCCESS || !input_username) {
DEBUG(1,("can not get the username"));
if (cntl_flags & CNTL_REGEX)
regfree(&rexp);
return PAM_SERVICE_ERR;
}
if (cntl_flags & CNTL_REGEX) {
retval = translate(pamh, input_username, &username, &confdir);
regfree(&rexp);
} else {
retval = PAM_SUCCESS;
username = (char *) input_username;
confdir = sysconfdir;
}
if (retval != PAM_SUCCESS)
return retval;
/* Get the password */
if (cntl_flags & CNTL_SKIP_PASSWORD)
password = NULL;
else if ((retval = _pam_get_password(pamh, &password, "Password:")) != PAM_SUCCESS)
return retval;
if (cntl_flags & CNTL_PASSWD)
retval = verify_user_acct(confdir, username, &pwstr);
else
retval = PAM_SUCCESS;
if (retval == PAM_SUCCESS) {
if (password) {
if (pwstr) {
if (strcmp(pwstr, crypt(password, pwstr)) == 0) {
password = NULL;
retval = PAM_SUCCESS;
} else
retval = PAM_AUTH_ERR;
free(pwstr);
}
}
if (retval == PAM_SUCCESS && (cntl_flags & CNTL_SHADOW))
retval = verify_user_pass(confdir, username, password);
}
switch (retval) {
case PAM_ACCT_EXPIRED:
_pam_log(LOG_NOTICE, "user '%s': account expired", username);
break;
case PAM_SUCCESS:
_pam_log(LOG_NOTICE, "user '%s' granted access", username);
if (username != (char *) input_username)
pam_set_item(pamh, PAM_USER, username);
break;
default:
_pam_log(LOG_NOTICE, "user '%s' failed to authenticate",
username);
}
return retval;
}
PAM_EXTERN
int pam_sm_setcred(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
return PAM_SUCCESS;
}
PAM_EXTERN
int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
return PAM_SUCCESS;
}
#ifdef PAM_STATIC
/* static module data */
struct pam_module _pam_fshadow_modstruct = {
"pam_fshadow",
pam_sm_authenticate,
pam_sm_setcred,
NULL,
NULL,
NULL,
NULL,
};
#endif