From owner-svn-src-stable@freebsd.org Thu Mar 19 03:37:04 2020 Return-Path: Delivered-To: svn-src-stable@mailman.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.nyi.freebsd.org (Postfix) with ESMTP id A581427928E; Thu, 19 Mar 2020 03:37:04 +0000 (UTC) (envelope-from cy@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (4096 bits) client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 48jXdr44Dmz4Bcn; Thu, 19 Mar 2020 03:37:04 +0000 (UTC) (envelope-from cy@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 18BF34BDF; Thu, 19 Mar 2020 03:37:04 +0000 (UTC) (envelope-from cy@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id 02J3b427047338; Thu, 19 Mar 2020 03:37:04 GMT (envelope-from cy@FreeBSD.org) Received: (from cy@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id 02J3b3Uc047331; Thu, 19 Mar 2020 03:37:03 GMT (envelope-from cy@FreeBSD.org) Message-Id: <202003190337.02J3b3Uc047331@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: cy set sender to cy@FreeBSD.org using -f From: Cy Schubert Date: Thu, 19 Mar 2020 03:37:03 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-11@freebsd.org Subject: svn commit: r359117 - in stable: 11/lib/libpam/modules/pam_login_access 12/lib/libpam/modules/pam_login_access X-SVN-Group: stable-11 X-SVN-Commit-Author: cy X-SVN-Commit-Paths: in stable: 11/lib/libpam/modules/pam_login_access 12/lib/libpam/modules/pam_login_access X-SVN-Commit-Revision: 359117 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-stable@freebsd.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: SVN commit messages for all the -stable branches of the src tree List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 19 Mar 2020 03:37:04 -0000 Author: cy Date: Thu Mar 19 03:37:02 2020 New Revision: 359117 URL: https://svnweb.freebsd.org/changeset/base/359117 Log: MFC r358070: 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) Differential Revision: https://reviews.freebsd.org/D23198 Modified: stable/11/lib/libpam/modules/pam_login_access/login.access.5 stable/11/lib/libpam/modules/pam_login_access/login_access.c stable/11/lib/libpam/modules/pam_login_access/pam_login_access.8 stable/11/lib/libpam/modules/pam_login_access/pam_login_access.c stable/11/lib/libpam/modules/pam_login_access/pam_login_access.h Directory Properties: stable/11/ (props changed) Changes in other areas also in this revision: Modified: stable/12/lib/libpam/modules/pam_login_access/login.access.5 stable/12/lib/libpam/modules/pam_login_access/login_access.c stable/12/lib/libpam/modules/pam_login_access/pam_login_access.8 stable/12/lib/libpam/modules/pam_login_access/pam_login_access.c stable/12/lib/libpam/modules/pam_login_access/pam_login_access.h Directory Properties: stable/12/ (props changed) Modified: stable/11/lib/libpam/modules/pam_login_access/login.access.5 ============================================================================== --- stable/11/lib/libpam/modules/pam_login_access/login.access.5 Thu Mar 19 03:31:12 2020 (r359116) +++ stable/11/lib/libpam/modules/pam_login_access/login.access.5 Thu Mar 19 03:37:02 2020 (r359117) @@ -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: stable/11/lib/libpam/modules/pam_login_access/login_access.c ============================================================================== --- stable/11/lib/libpam/modules/pam_login_access/login_access.c Thu Mar 19 03:31:12 2020 (r359116) +++ stable/11/lib/libpam/modules/pam_login_access/login_access.c Thu Mar 19 03:37:02 2020 (r359117) @@ -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: stable/11/lib/libpam/modules/pam_login_access/pam_login_access.8 ============================================================================== --- stable/11/lib/libpam/modules/pam_login_access/pam_login_access.8 Thu Mar 19 03:31:12 2020 (r359116) +++ stable/11/lib/libpam/modules/pam_login_access/pam_login_access.8 Thu Mar 19 03:37:02 2020 (r359117) @@ -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: stable/11/lib/libpam/modules/pam_login_access/pam_login_access.c ============================================================================== --- stable/11/lib/libpam/modules/pam_login_access/pam_login_access.c Thu Mar 19 03:31:12 2020 (r359116) +++ stable/11/lib/libpam/modules/pam_login_access/pam_login_access.c Thu Mar 19 03:37:02 2020 (r359117) @@ -40,6 +40,7 @@ __FBSDID("$FreeBSD$"); #define _BSD_SOURCE #include +#include #include #include @@ -49,13 +50,25 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #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; @@ -78,25 +91,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: stable/11/lib/libpam/modules/pam_login_access/pam_login_access.h ============================================================================== --- stable/11/lib/libpam/modules/pam_login_access/pam_login_access.h Thu Mar 19 03:31:12 2020 (r359116) +++ stable/11/lib/libpam/modules/pam_login_access/pam_login_access.h Thu Mar 19 03:37:02 2020 (r359117) @@ -36,4 +36,15 @@ * $FreeBSD$ */ -extern int login_access(const char *, const char *); +#include + +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 *);