This commit makes significant changes to pam_login_access(8) to bring it

up to par with the Linux pam_access(8).

Like the Linux pam_access(8) our pam_login_access(8) is a service module
for pam(3) that allows a administrator to limit access from specified
remote hosts or terminals. Unlike the Linux pam_access, pam_login_access
is missing some features which are added by this commit:

Access file can now be specified. The default remains /etc/access.conf.
The syntax is consistent with Linux pam_access.

By default usernames are matched. If the username fails to match a match
against a group name is attempted. The new nodefgroup module option will
only match a username and no attempt to match a group name is made.
Group names must be specified in brackets, "()" when nodefgroup is
specified. Otherwise the old backward compatible behavior is used.
This is consistent with Linux pam_access.

A new field separator module option allows the replacement of the default
colon (:) with any other character. This facilitates potential future
specification of X displays. This is also consistent with Linux pam_access.

A new list separator module option to replace the default space/comma/tab
with another character. This too is consistent with Linux pam_access.

Linux pam_access options not implemented in this commit are the debug
and audit options. These will be implemented at a later date.

Reviewed by:	bjk, bcr (for manpages)
Approved by:	des (blanket, implicit)
MFC after:	1 month
Differential Revision:	https://reviews.freebsd.org/D23198
This commit is contained in:
cy 2020-02-18 11:27:08 +00:00
parent 70e579827c
commit 4bbdfe4e6e
5 changed files with 160 additions and 72 deletions

View File

@ -1,7 +1,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd January 27, 2020
.Dd January 30, 2020
.Dt LOGIN.ACCESS 5
.Os
.Sh NAME
@ -34,6 +34,13 @@ character.
.Pp
The second field should be a list of one or more login names,
group names, or ALL (always matches).
Group names must be enclosed in
parentheses if the pam module specification for
.Pa pam_login_access
specifies the
.Pa nodefgroup
option.
Otherwise, group names will only match if no usernames match.
.Pp
The third field should be a list
of one or more tty names (for non-networked logins), host names, domain

View File

@ -31,29 +31,26 @@ __FBSDID("$FreeBSD$");
#include "pam_login_access.h"
#define _PATH_LOGACCESS "/etc/login.access"
/* Delimiters for fields and for lists of users, ttys or hosts. */
static char fs[] = ":"; /* field separator */
static char sep[] = ", \t"; /* list-element separator */
/* Constants to be used in assignments only, not in comparisons... */
#define YES 1
#define NO 0
static int from_match(const char *, const char *);
static int from_match(const char *, const char *, struct pam_login_access_options *);
static int list_match(char *, const char *,
int (*)(const char *, const char *));
int (*)(const char *, const char *,
struct pam_login_access_options *),
struct pam_login_access_options *);
static int netgroup_match(const char *, const char *, const char *);
static int string_match(const char *, const char *);
static int user_match(const char *, const char *);
static int user_match(const char *, const char *, struct pam_login_access_options *);
static int group_match(const char *, const char *);
/* login_access - match username/group and host/tty with access control file */
int
login_access(const char *user, const char *from)
login_access(const char *user, const char *from,
struct pam_login_access_options *login_access_opts)
{
FILE *fp;
char line[BUFSIZ];
@ -63,6 +60,7 @@ login_access(const char *user, const char *from)
int match = NO;
int end;
int lineno = 0; /* for diagnostics */
const char *fieldsep = login_access_opts->fieldsep;
/*
* Process the table one line at a time and stop at the first match.
@ -72,12 +70,12 @@ login_access(const char *user, const char *from)
* non-existing table means no access control.
*/
if ((fp = fopen(_PATH_LOGACCESS, "r")) != NULL) {
if ((fp = fopen(login_access_opts->accessfile, "r")) != NULL) {
while (!match && fgets(line, sizeof(line), fp)) {
lineno++;
if (line[end = strlen(line) - 1] != '\n') {
syslog(LOG_ERR, "%s: line %d: missing newline or line too long",
_PATH_LOGACCESS, lineno);
login_access_opts->accessfile, lineno);
continue;
}
if (line[0] == '#')
@ -87,25 +85,25 @@ login_access(const char *user, const char *from)
line[end] = 0; /* strip trailing whitespace */
if (line[0] == 0) /* skip blank lines */
continue;
if (!(perm = strtok(line, fs))
|| !(users = strtok((char *) 0, fs))
|| !(froms = strtok((char *) 0, fs))
|| strtok((char *) 0, fs)) {
syslog(LOG_ERR, "%s: line %d: bad field count", _PATH_LOGACCESS,
if (!(perm = strtok(line, fieldsep))
|| !(users = strtok((char *) 0, fieldsep))
|| !(froms = strtok((char *) 0, fieldsep))
|| strtok((char *) 0, fieldsep)) {
syslog(LOG_ERR, "%s: line %d: bad field count", login_access_opts->accessfile,
lineno);
continue;
}
if (perm[0] != '+' && perm[0] != '-') {
syslog(LOG_ERR, "%s: line %d: bad first field", _PATH_LOGACCESS,
syslog(LOG_ERR, "%s: line %d: bad first field", login_access_opts->accessfile,
lineno);
continue;
}
match = (list_match(froms, from, from_match)
&& list_match(users, user, user_match));
match = (list_match(froms, from, from_match, login_access_opts)
&& list_match(users, user, user_match, login_access_opts));
}
(void) fclose(fp);
} else if (errno != ENOENT) {
syslog(LOG_ERR, "cannot open %s: %m", _PATH_LOGACCESS);
syslog(LOG_ERR, "cannot open %s: %m", login_access_opts->accessfile);
}
return (match == 0 || (line[0] == '+'));
}
@ -114,10 +112,12 @@ login_access(const char *user, const char *from)
static int
list_match(char *list, const char *item,
int (*match_fn)(const char *, const char *))
int (*match_fn)(const char *, const char *, struct pam_login_access_options *),
struct pam_login_access_options *login_access_opts)
{
char *tok;
int match = NO;
const char *listsep = login_access_opts->listsep;
/*
* Process tokens one at a time. We have exhausted all possible matches
@ -126,19 +126,22 @@ list_match(char *list, const char *item,
* the match is affected by any exceptions.
*/
for (tok = strtok(list, sep); tok != NULL; tok = strtok((char *) 0, sep)) {
for (tok = strtok(list, listsep); tok != NULL; tok = strtok((char *) 0, listsep)) {
if (strcmp(tok, "EXCEPT") == 0) /* EXCEPT: give up */
break;
if ((match = (*match_fn)(tok, item)) != 0) /* YES */
if ((match = (*match_fn)(tok, item, login_access_opts)) != 0) /* YES */
break;
}
/* Process exceptions to matches. */
if (match != NO) {
while ((tok = strtok((char *) 0, sep)) && strcmp(tok, "EXCEPT"))
while ((tok = strtok((char *) 0, listsep)) && strcmp(tok, "EXCEPT")) {
/* VOID */ ;
if (tok == NULL || list_match((char *) 0, item, match_fn) == NO)
return (match);
if (tok == NULL || list_match((char *) 0, item, match_fn,
login_access_opts) == NO) {
return (match);
}
}
}
return (NO);
}
@ -170,16 +173,49 @@ netgroup_match(const char *group, const char *machine, const char *user)
return (NO);
}
/* user_match - match a username against one token */
/* group_match - match a group against one token */
static int
user_match(const char *tok, const char *string)
int
group_match(const char *tok, const char *username)
{
struct group *group;
struct passwd *passwd;
gid_t *grouplist;
int ngroups = NGROUPS;
int i;
int i, ret, ngroups = NGROUPS;
if ((passwd = getpwnam(username)) == NULL)
return (NO);
errno = 0;
if ((group = getgrnam(tok)) == NULL) {
if (errno != 0)
syslog(LOG_ERR, "getgrnam() failed for %s: %s", username, strerror(errno));
else
syslog(LOG_NOTICE, "group not found: %s", username);
return (NO);
}
if ((grouplist = calloc(ngroups, sizeof(gid_t))) == NULL) {
syslog(LOG_ERR, "cannot allocate memory for grouplist: %s", username);
return (NO);
}
ret = NO;
if (getgrouplist(username, passwd->pw_gid, grouplist, &ngroups) != 0)
syslog(LOG_ERR, "getgrouplist() failed for %s", username);
for (i = 0; i < ngroups; i++)
if (grouplist[i] == group->gr_gid)
ret = YES;
free(grouplist);
return (ret);
}
/* user_match - match a username against one token */
static int
user_match(const char *tok, const char *string,
struct pam_login_access_options *login_access_opts)
{
size_t stringlen;
char *grpstr;
int rc;
/*
* If a token has the magic value "ALL" the match always succeeds.
@ -189,39 +225,18 @@ user_match(const char *tok, const char *string)
if (tok[0] == '@') { /* netgroup */
return (netgroup_match(tok + 1, (char *) 0, string));
} else if (tok[0] == '(' && tok[(stringlen = strlen(&tok[1]))] == ')') { /* group */
if ((grpstr = strndup(&tok[1], stringlen - 1)) == NULL) {
syslog(LOG_ERR, "cannot allocate memory for %s", string);
return (NO);
}
rc = group_match(grpstr, string);
free(grpstr);
return (rc);
} else if (string_match(tok, string)) { /* ALL or exact match */
return (YES);
} else {
if ((passwd = getpwnam(string)) == NULL)
return (NO);
errno = 0;
if ((group = getgrnam(tok)) == NULL) {/* try group membership */
if (errno != 0) {
syslog(LOG_ERR, "getgrnam() failed for %s: %s", string, strerror(errno));
} else {
syslog(LOG_NOTICE, "group not found: %s", string);
}
return (NO);
}
errno = 0;
if ((grouplist = calloc(ngroups, sizeof(gid_t))) == NULL) {
if (errno == ENOMEM) {
syslog(LOG_ERR, "cannot allocate memory for grouplist: %s", string);
}
return (NO);
}
if (getgrouplist(string, passwd->pw_gid, grouplist, &ngroups) != 0) {
syslog(LOG_ERR, "getgrouplist() failed for %s", string);
free(grouplist);
return (NO);
}
for (i = 0; i < ngroups; i++) {
if (grouplist[i] == group->gr_gid) {
free(grouplist);
return (YES);
}
}
free(grouplist);
} else if (login_access_opts->defgroup == true) {/* try group membership */
return (group_match(tok, string));
}
return (NO);
}
@ -229,7 +244,8 @@ user_match(const char *tok, const char *string)
/* from_match - match a host or tty against a list of tokens */
static int
from_match(const char *tok, const char *string)
from_match(const char *tok, const char *string,
struct pam_login_access_options *login_access_opts __unused)
{
int tok_len;
int str_len;

View File

@ -34,7 +34,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd January 24, 2002
.Dd January 30, 2020
.Dt PAM_LOGIN_ACCESS 8
.Os
.Sh NAME
@ -63,13 +63,46 @@ The
.Pa login.access
account management component
.Pq Fn pam_sm_acct_mgmt ,
returns success if and only the user is allowed to log in on the
returns success if and only the user is allowed to login on the
specified tty (in the case of a local login) or from the specified
remote host (in the case of a remote login), according to the
restrictions listed in
.Xr login.access 5 .
.Bl -tag -width ".Cm accessfile=pathname"
.It Cm accessfile Ns = Ns Ar pathname
specifies a non-standard location for the
.Pa login.access
configuration file
(normally located in
.Pa /etc/login.access ) .
.It Cm nodefgroup
makes tokens not enclosed in parentheses only match users, requiring groups
to be specified in parentheses.
Without
.Cm nodefgroup
user and group names are intermingled, with user entries taking precedence
over group entries.
This is not backwards compatible with legacy
.Pa login.access
configuration files.
However this mitigates confusion between users and
groups of the same name.
.It Cm fieldsep Ns = Ns Ar separators
changes the field separator from the default ":".
More than one separator
may be specified.
.It Cm listsep Ns = Ns Ar separators
changes the field separator from the default space (''), tab (\\t) and
comma (,).
More than one separator may be specified.
For example, listsep=;
will replace the default with a semicolon (;).
This option may be useful when specifying Active Directory groupnames which
typically contain spaces.
.El
.Sh SEE ALSO
.Xr pam 3 ,
.Xr syslog 3 ,
.Xr login.access 5 ,
.Xr pam.conf 5
.Sh AUTHORS

View File

@ -42,6 +42,7 @@ __FBSDID("$FreeBSD$");
#define _BSD_SOURCE
#include <sys/param.h>
#include <sys/types.h>
#include <syslog.h>
#include <unistd.h>
@ -51,13 +52,25 @@ __FBSDID("$FreeBSD$");
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <security/pam_mod_misc.h>
#include <security/openpam.h>
#include "pam_login_access.h"
#define OPT_ACCESSFILE "accessfile"
#define OPT_NOAUDIT "noaudit"
#define OPT_FIELDSEP "fieldsep"
#define OPT_LISTSEP "listsep"
#define OPT_NODEFGROUP "nodefgroup"
#define _PATH_LOGACCESS "/etc/login.access"
#define _FIELD_SEPARATOR ":"
#define _LIST_SEPARATOR ", \t"
PAM_EXTERN int
pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused,
int argc __unused, const char *argv[] __unused)
{
struct pam_login_access_options login_access_opts;
const void *rhost, *tty, *user;
char hostname[MAXHOSTNAMELEN];
int pam_err;
@ -80,25 +93,33 @@ pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused,
return (pam_err);
gethostname(hostname, sizeof hostname);
login_access_opts.defgroup = openpam_get_option(pamh, OPT_NODEFGROUP) == NULL ? true : false;
login_access_opts.audit = openpam_get_option(pamh, OPT_NOAUDIT) == NULL ? true : false;
if ((login_access_opts.accessfile = openpam_get_option(pamh, OPT_ACCESSFILE)) == NULL)
login_access_opts.accessfile = _PATH_LOGACCESS;
if ((login_access_opts.fieldsep = openpam_get_option(pamh, OPT_FIELDSEP)) == NULL)
login_access_opts.fieldsep = _FIELD_SEPARATOR;
if ((login_access_opts.listsep = openpam_get_option(pamh, OPT_LISTSEP)) == NULL)
login_access_opts.listsep = _LIST_SEPARATOR;
if (rhost != NULL && *(const char *)rhost != '\0') {
PAM_LOG("Checking login.access for user %s from host %s",
(const char *)user, (const char *)rhost);
if (login_access(user, rhost) != 0)
if (login_access(user, rhost, &login_access_opts) != 0)
return (PAM_SUCCESS);
PAM_VERBOSE_ERROR("%s is not allowed to log in from %s",
(const char *)user, (const char *)rhost);
} else if (tty != NULL && *(const char *)tty != '\0') {
PAM_LOG("Checking login.access for user %s on tty %s",
(const char *)user, (const char *)tty);
if (login_access(user, tty) != 0)
if (login_access(user, tty, &login_access_opts) != 0)
return (PAM_SUCCESS);
PAM_VERBOSE_ERROR("%s is not allowed to log in on %s",
(const char *)user, (const char *)tty);
} else {
PAM_LOG("Checking login.access for user %s",
(const char *)user);
if (login_access(user, "***unknown***") != 0)
if (login_access(user, "***unknown***", &login_access_opts) != 0)
return (PAM_SUCCESS);
PAM_VERBOSE_ERROR("%s is not allowed to log in",
(const char *)user);

View File

@ -38,4 +38,15 @@
* $FreeBSD$
*/
extern int login_access(const char *, const char *);
#include <stdbool.h>
struct pam_login_access_options {
bool defgroup;
bool audit;
const char *accessfile;
/* Delimiters for fields and for lists of users, ttys or hosts. */
const char *fieldsep; /* field separator */
const char *listsep; /* list-element separator */
};
extern int login_access(const char *, const char *, struct pam_login_access_options *);