pam_fshadow: allow the user to use arbitrary group numbers for username and domain parts.

New options username-index and domain-index are used to indicate
indices of the parenthesized groups used to extract the user and
the domain name. The default corresponds to 'user-index=1 domain-index=1'.

Additionally, change the behavior in case if the user name doesn't
match the regexp. Previous versions would fall back to plain authentication
in this case. New behavior is to reject access.

* pam_fshadow/pam_fshadow.c (pam_opt): New options
username-index and domain-index.
(pam_sm_authenticate): Move username splitting into a separate
function.
* doc/pam_fshadow.8in: Document the new options.
This commit is contained in:
Sergey Poznyakoff 2017-12-22 15:22:14 +02:00
parent fc8bf4028f
commit 55620228a8
2 changed files with 102 additions and 55 deletions

View file

@ -1,5 +1,5 @@
.\" This file is part of PAM-Modules -*- nroff -*-
.\" Copyright (C) 2001-2015 Sergey Poznyakoff
.\" Copyright (C) 2001-2017 Sergey Poznyakoff
.\"
.\" PAM-Modules is free software; you can redistribute it and/or modify
.\" it under the terms of the GNU General Public License as published by
@ -14,14 +14,17 @@
.\" You should have received a copy of the GNU General Public License
.\" along with PAM-Modules. If not, see <http://www.gnu.org/licenses/>.
.so config.so
.TH PAM_FSHADOW 8 "March 30, 2014" "PAM-MODULES" "Pam-Modules User Reference"
.TH PAM_FSHADOW 8 "December 22, 2017" "PAM-MODULES" "Pam-Modules User Reference"
.SH NAME
pam_fshadow \- use alternative passwd and/or shadow files
.SH SYNOPSIS
.nh
.na
\fBpam_fshadow\fR\
[\fBaudit\fR]\
[\fBdebug\fR[\fB=\fINUMBER\fR]]\
[\fBbasic\fR|\fBextended\fR]\
[\fBdomain\-index=\fIN\fR]\
[\fBignore\-case\fR|\fBicase\fR|\fBcase\fR]\
[\fBnopasswd\fR]\
[\fBnoshadow\fR]\
@ -29,9 +32,8 @@ pam_fshadow \- use alternative passwd and/or shadow files
[\fBrevert\-index\fR]\
[\fBsysconfdir=\fIDIR\fR]\
[\fBuse_authtok\fR]\
[\fBdebug\fR[\fB=\fINUMBER\fR]]\
[\fBwaitdebug\fR]\
[\fBaudit\fR]
[\fBusername\-index=\fIN\fR]\
[\fBwaitdebug\fR]
.ad
.hy
.SH DESCRIPTION
@ -54,7 +56,7 @@ provided to disable reading of either file. E.g. if \fBnoshadow\fR is
given, the module will expect all authentication information to be
stored in the \fBpasswd\fR file.
.PP
The \fBvirtual domain\fR mode select the \fBpasswd\fR,\fBshadow\fR
The \fBvirtual domain\fR mode selects the \fBpasswd\fR,\fBshadow\fR
pair to use depending on the user name. To that effect, the user name
is first split into the \fBlocal\fR and \fBauthentication domain\fR
parts using a regular expression supplied with the \fBregex\fR option.
@ -62,8 +64,7 @@ The configuration directory name is then constructed by concatenating the
system configuration directory, a directory separator character (\fB/\fR),
and the name of the authentication domain. The authentication then
proceeds as described above for the plain mode. If the supplied user name
does not match the regular expression, \fBpam_fshadow\fR falls back to
the plain mode.
does not match the regular expression, \fBpam_fshadow\fR refuses access.
.SH OPTIONS
.TP
\fBbasic\fR
@ -86,12 +87,12 @@ Use only \fBpasswd\fR file for authentication.
.TP
\fBregex=\fIEXPR\fR
Defines a regular expression for splitting user name into the proper
name and authentication domain. The expression must contain two
name and authentication domain. The expression must contain at least two
parentesized groups. If it matches, the group 1 will be used to
extract local user name and the group 2 will select the authentication
domain. The \fBrevert\-index\fR option changes this behavior, causing
group 1 to be used for authentication domain and group 2 for user
name. For example:
domain. These default group indices can be changed using the
\fBusername\-index\fR and \fBdomain\-index\fR options. Additionally the
\fBrevert\-index\fR option swaps the meaning of the two indices. For example:
.RS
.EX
regex=(.*)@(.*)
@ -100,9 +101,17 @@ regex=(.*)@(.*)
This regular expression will match user names like \fBsmith@domain\fR.
.TP
\fBusername\-index=\fIN\fR
Use \fIN\fRth parenthesized group of the regular expression as the
user name. Default is 1.
.TP
\fBdomain\-index=\fIN\fR
Use \fIN\fRth parenthesized group of the regular expression as the
group name. Default is 2.
.TP
\fBrevert\-index\fR
Use group #2 from the regular expression as the user name and group #1
as the authentication domain.
Swap indices of the username and domain part parenthesized groups in
the regexp.
.TP
\fBsysconfdir=\fIDIR\fR
Use \fIDIR\fR as the system configuration directory, instead of the

View file

@ -103,15 +103,15 @@ fgetpwent(FILE *fp)
#define CNTL_REGEX 0x0080
#define CNTL_REVERT_INDEX 0x0100
char *sysconfdir = SYSCONFDIR;
static regex_t rexp;
static char *sysconfdir = SYSCONFDIR;
static int cntl_flags = CNTL_PASSWD|CNTL_SHADOW;
static long debug_level = 0;
static regex_t rexp;
static const char *regex_str = NULL;
static int regex_flags = REG_EXTENDED;
static int username_index = 1;
static int domain_index = 2;
static long username_index = 1;
static long domain_index = 2;
struct pam_opt pam_opt[] = {
{ PAM_OPTSTR(debug), pam_opt_long, &debug_level },
@ -139,6 +139,8 @@ struct pam_opt pam_opt[] = {
{ .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 },
{ NULL }
};
@ -162,13 +164,23 @@ _pam_parse(pam_handle_t *pamh, int argc, const char **argv)
"either passwd or shadow must be true");
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) {
username_index = 2;
domain_index = 1;
long t = username_index;
username_index = domain_index;
domain_index = t;
}
if (regex_str) {
int rc;
if (rc = regcomp(&rexp, regex_str, regex_flags)) {
if ((rc = regcomp(&rexp, regex_str, regex_flags))) {
size_t s = regerror(rc, &rexp, NULL, 0);
char *buf = malloc (s);
if (buf) {
@ -182,11 +194,16 @@ _pam_parse(pam_handle_t *pamh, int argc, const char **argv)
"cannot compile regex `%s'",
regex_str);
retval = PAM_AUTHINFO_UNAVAIL;
} else if (rexp.re_nsub != 2) {
_pam_log(LOG_NOTICE,
"invalid regular expression `%s': "
"must contain two reference groups",
regex_str);
} 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
@ -438,11 +455,50 @@ copy_backref (pam_handle_t *pamh, const char *name,
/* --- 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 *username;
const char *input_username;
char *username;
char *password;
int retval = PAM_AUTH_ERR;
int rc;
@ -455,8 +511,8 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags,
confdir = sysconfdir;
/* Get the username */
retval = pam_get_user(pamh, &username, NULL);
if (retval != PAM_SUCCESS || !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);
@ -464,39 +520,21 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags,
}
if (cntl_flags & CNTL_REGEX) {
regmatch_t rmatch[3];
if (regexec(&rexp, username, 3, rmatch, 0) == 0) {
char *domain;
rc = copy_backref(pamh, "DOMAIN", username, rmatch,
domain_index, &domain);
if (rc != PAM_SUCCESS)
return rc;
rc = copy_backref(pamh, "USERNAME", username, rmatch,
username_index, (char **) &username);
if (rc != PAM_SUCCESS)
return rc;
confdir = mkfilename(sysconfdir, domain);
pam_set_data(pamh, "CONFDIR",
(void *)confdir, gray_cleanup_string);
} else {
DEBUG(1,("user name `%s' does not match regular "
"expression `%s'",
username,
regex_str));
}
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 (_pam_get_password(pamh, &password, "Password:"))
return PAM_SERVICE_ERR;
if (retval != PAM_SUCCESS) {
_pam_log(LOG_ERR, "Could not retrive user's password");
return -2;
}
if (cntl_flags & CNTL_PASSWD)
retval = verify_user_acct(confdir, username, &pwstr);
else