svn commit: r358070 - head/lib/libpam/modules/pam_login_access

Cy Schubert cy at FreeBSD.org
Tue Feb 18 11:27:10 UTC 2020


Author: cy
Date: Tue Feb 18 11:27:08 2020
New Revision: 358070
URL: https://svnweb.freebsd.org/changeset/base/358070

Log:
  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

Modified:
  head/lib/libpam/modules/pam_login_access/login.access.5
  head/lib/libpam/modules/pam_login_access/login_access.c
  head/lib/libpam/modules/pam_login_access/pam_login_access.8
  head/lib/libpam/modules/pam_login_access/pam_login_access.c
  head/lib/libpam/modules/pam_login_access/pam_login_access.h

Modified: head/lib/libpam/modules/pam_login_access/login.access.5
==============================================================================
--- head/lib/libpam/modules/pam_login_access/login.access.5	Tue Feb 18 11:27:05 2020	(r358069)
+++ head/lib/libpam/modules/pam_login_access/login.access.5	Tue Feb 18 11:27:08 2020	(r358070)
@@ -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

Modified: head/lib/libpam/modules/pam_login_access/login_access.c
==============================================================================
--- head/lib/libpam/modules/pam_login_access/login_access.c	Tue Feb 18 11:27:05 2020	(r358069)
+++ head/lib/libpam/modules/pam_login_access/login_access.c	Tue Feb 18 11:27:08 2020	(r358070)
@@ -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,17 +173,50 @@ netgroup_match(const char *group, const char *machine,
     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.
      * Otherwise, return YES if the token fully matches the username, or if
@@ -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 (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);
-	    }
+    } 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);
 	}
-	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);
+	rc = group_match(grpstr, string);
+	free(grpstr);
+	return (rc);
+    } else if (string_match(tok, string)) {	/* ALL or exact match */
+	return (YES);
+    } 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;

Modified: head/lib/libpam/modules/pam_login_access/pam_login_access.8
==============================================================================
--- head/lib/libpam/modules/pam_login_access/pam_login_access.8	Tue Feb 18 11:27:05 2020	(r358069)
+++ head/lib/libpam/modules/pam_login_access/pam_login_access.8	Tue Feb 18 11:27:08 2020	(r358070)
@@ -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

Modified: head/lib/libpam/modules/pam_login_access/pam_login_access.c
==============================================================================
--- head/lib/libpam/modules/pam_login_access/pam_login_access.c	Tue Feb 18 11:27:05 2020	(r358069)
+++ head/lib/libpam/modules/pam_login_access/pam_login_access.c	Tue Feb 18 11:27:08 2020	(r358070)
@@ -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 __unuse
 		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);

Modified: head/lib/libpam/modules/pam_login_access/pam_login_access.h
==============================================================================
--- head/lib/libpam/modules/pam_login_access/pam_login_access.h	Tue Feb 18 11:27:05 2020	(r358069)
+++ head/lib/libpam/modules/pam_login_access/pam_login_access.h	Tue Feb 18 11:27:08 2020	(r358070)
@@ -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 *);


More information about the svn-src-all mailing list