summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Olson <olson@cumulusnetworks.com>2018-04-02 11:01:09 -0700
committerDave Olson <olson@cumulusnetworks.com>2018-04-02 20:40:02 -0700
commit1e5742369aedc8708d5dbe4411ffd5bf4b10537a (patch)
tree2480dc22d6bd3e99084aa85e5679e06b71a33ea0
parent556625e62b692b723cc6809d2374c3da9616dc3d (diff)
downloadlibnss-mapuser-1e5742369aedc8708d5dbe4411ffd5bf4b10537a.tar.gz
libnss-mapuser-1e5742369aedc8708d5dbe4411ffd5bf4b10537a.zip
Add VSA shell:priv-lvl support for privileged radius user logins
Ticket: CM-19457 Reviewed By: roopa Testing Done: lots of variations of login, su, sudo, automated radius tests Now we always read the map files. If session is set, we try that file first, so that a user always sees their name, same as tacplus. If that's the wrong file, read through all of the map files, look for the correct match based on either name+session or auid+session, depending on getpwnam or getpwuid entry point Ignore same set of users as tacacs, including new radius_priv_user account for the privileged RADIUS user. create and delete the mapuser files from libpam-radius-auth now; we need to have the mapping file written early enough for the pam interfaces to get the correct info. Using the pam_script is too limiting, and since we are creating the database in libpam-radius-auth now, we'll delete it there as well to keep things symmetric, so delete the script and the references to the scripts A significant part of this effort was adding getgrent, getgrgid, and getgrnam support, so that the radius users are put into the netshow (unprivileged) and netedit and sudo (privileged) groups at login. A lot of restructuring went in as part of that, and cleaned up some longstanding bugs, including return values for the getpw* routines. Also cleaned up some whitespace issues. Also renamed some globals (debug, min_uid, init_common()) that might collide with other programs, so that when I build unstripped and normal visibility shared libs, they won't collide with programs calling the functions (saw this with "debug" and bgpd, for example).
-rw-r--r--Makefile3
-rw-r--r--debian/changelog4
-rw-r--r--debian/control4
-rw-r--r--debian/libnss-mapuser.postinst8
-rw-r--r--debian/mapuser6
-rwxr-xr-xdebian/rules6
-rw-r--r--map_common.c476
-rw-r--r--map_common.h19
-rw-r--r--nss_mapname.c325
-rw-r--r--nss_mapuid.c134
-rw-r--r--nss_mapuser.583
-rw-r--r--nss_mapuser.887
-rw-r--r--nss_mapuser.conf1
-rwxr-xr-xpam_script_ses_close85
-rwxr-xr-xpam_script_ses_open65
15 files changed, 907 insertions, 399 deletions
diff --git a/Makefile b/Makefile
index d32c8f5..f2fd8f9 100644
--- a/Makefile
+++ b/Makefile
@@ -26,8 +26,9 @@ endif
CPPFLAGS = -D_FORTIFY_SOURCE=2
CFLAGS = $(CPPFLAGS) ${OPTFLAGS} -fPIC -fstack-protector-strong \
-Wformat -Werror=format-security -Wall $(FVISIBILITY)
+LDLIBS = -laudit
LDFLAGS = -shared -fPIC -DPIC \
- -Wl,-z -Wl,relro -Wl,-z -Wl,now -Wl,-soname -Wl,$@
+ -Wl,-z -Wl,relro -Wl,-z -Wl,now -Wl,-soname -Wl,$@ $(LDLIBS)
all: $(NSSNAMELIB) $(NSSUIDLIB)
diff --git a/debian/changelog b/debian/changelog
index f93e11d..50032b5 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -2,6 +2,10 @@ libnss-mapuser (1.0.0-cl3u3) RELEASED; urgency=low
* Closes CM-19866 - Fixed exclude_users not skipped, and added
more system accounts to exclude_users: www-data,man, tacacs[0-9]*.
+ * New Enabled - When Vendor Specific Option containing shell:priv-lvl
+ is present, and the value is 15, map to user radius_priv_user, and
+ give that user account more privileges, similar to tacplus client
+ privilege 15.
-- dev-support <dev-support@cumulusnetworks.com> Mon, 26 Feb 2018 09:51:44 -0800
diff --git a/debian/control b/debian/control
index c383d86..d0ad69d 100644
--- a/debian/control
+++ b/debian/control
@@ -1,14 +1,14 @@
Source: libnss-mapuser
Priority: optional
Maintainer: dev-support <dev-support@cumulusnetworks.com>
-Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.16.1), git
+Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.16.1), libaudit-dev, git
Section: libs
Standards-Version: 3.9.6
Homepage: http://www.cumulusnetworks.com
Package: libnss-mapuser
Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, adduser
+Depends: ${shlibs:Depends}, ${misc:Depends}, libaudit1, adduser
Description: NSS modules to map any requested username to a local account
Performs getpwname and getpwuid lookups via NSS for systems like RADIUS
where it is not possible to do a username lookup without authentication
diff --git a/debian/libnss-mapuser.postinst b/debian/libnss-mapuser.postinst
index 2e9b04f..ee6a70d 100644
--- a/debian/libnss-mapuser.postinst
+++ b/debian/libnss-mapuser.postinst
@@ -19,11 +19,19 @@ case "$1" in
-e '/^passwd:/s/\s\s*/&mapuid /' \
-e '/^passwd:.*#/s/#.*/ mapname &/' \
-e '/^passwd:[^#]*$/s/$/ mapname &/' \
+ -e '/^group:.*#/s/#.*/ mapname &/' \
+ -e '/^group:[^#]*$/s/: */& mapname /' \
/etc/nsswitch.conf
fi
addgroup --quiet $rgroup 2>&1 | grep -v 'already exists'
adduser --quiet --firstuid 1000 --disabled-login --ingroup $rgroup \
--gecos "radius user" radius_user 2>&1 | grep -v 'already exists'
+ adduser --quiet --firstuid 1000 --disabled-login --ingroup $rgroup \
+ --gecos "radius privileged user" radius_priv_user 2>&1 | grep -v 'already exists'
+ # regular radius logins can run net show commands
+ adduser --quiet radius_user netshow
+ # privileged radius logins can run net config commands, as well as show
+ adduser --quiet radius_priv_user netedit
exit 0
)
;;
diff --git a/debian/mapuser b/debian/mapuser
deleted file mode 100644
index 69d2137..0000000
--- a/debian/mapuser
+++ /dev/null
@@ -1,6 +0,0 @@
-Name: libnss-mapuser uses this to maintain the session uid => user mapping
-Default: yes
-Priority: 257
-Session-Type: Additional
-Session:
- optional pam_script.so dir=/usr/share/mapuser
diff --git a/debian/rules b/debian/rules
index ed7dbc0..cb1f417 100755
--- a/debian/rules
+++ b/debian/rules
@@ -16,9 +16,3 @@ export DEB_BUILD_MAINT_OPTIONS = hardening=+all
# No configuration needed
override_dh_auto_configure:
-override_dh_install:
- dh_installdirs /usr/share/pam-configs /usr/share/mapuser
- install -p -m 755 pam_script_ses* debian/libnss-mapuser/usr/share/mapuser
- install -p -m 444 debian/mapuser \
- debian/libnss-mapuser/usr/share/pam-configs/
- dh_install
diff --git a/map_common.c b/map_common.c
index 6313c88..830d85a 100644
--- a/map_common.c
+++ b/map_common.c
@@ -25,6 +25,11 @@
#include "map_common.h"
#include <sys/stat.h>
+#include <stddef.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <libaudit.h>
static const char config_file[] = "/etc/nss_mapuser.conf";
@@ -35,30 +40,43 @@ static const char config_file[] = "/etc/nss_mapuser.conf";
char *exclude_users; /* don't lookup these users */
char *mappeduser;
char *mapped_priv_user;
-uid_t min_uid = DEF_MIN_UID;
-int debug;
+uid_t map_min_uid = DEF_MIN_UID;
+int map_debug;
static int conf_parsed = 0;
static const char *libname; /* for syslogs, set in each library */
+static const char dbdir[] = "/run/mapuser/";
+
+/*
+ * If you aren't using glibc or a variant that supports this,
+ * and you have a system that supports the BSD getprogname(),
+ * you can replace this use with getprogname()
+ */
+extern const char *__progname;
/* reset all config variables when we are going to re-parse */
static void reset_config(void)
{
+ void *p;
+
/* reset the config variables that we use, freeing memory where needed */
if (exclude_users) {
- (void)free(exclude_users);
+ p = exclude_users;
exclude_users = NULL;
+ (void)free(p);
}
if (mappeduser) {
- (void)free(mappeduser);
+ p = mappeduser;
mappeduser = NULL;
+ (void)free(p);
}
if (mapped_priv_user) {
- (void)free(mapped_priv_user);
+ p = mapped_priv_user;
mapped_priv_user = NULL;
+ (void)free(p);
}
- debug = 0;
- min_uid = DEF_MIN_UID;
+ map_debug = 0;
+ map_min_uid = DEF_MIN_UID;
}
/*
@@ -84,7 +102,7 @@ int nss_mapuser_config(int *errnop, const char *lname)
return 2; /* nothing to reparse */
reset_config();
conf_parsed = 0;
- if (debug && conf_parsed)
+ if (map_debug && conf_parsed)
syslog(LOG_DEBUG,
"%s: Configuration file changed, re-initializing",
libname);
@@ -106,9 +124,9 @@ int nss_mapuser_config(int *errnop, const char *lname)
if (*lbuf == '#' || isspace(*lbuf))
continue; /* skip comments, white space lines, etc. */
strtok(lbuf, " \t\n\r\f"); /* terminate buffer at first whitespace */
- if (!strncmp(lbuf, "debug=", 6))
- debug = strtoul(lbuf + 6, NULL, 0);
- else if (!strncmp(lbuf, "exclude_users=", 14)) {
+ if (!strncmp(lbuf, "debug=", 6)) {
+ map_debug = strtoul(lbuf + 6, NULL, 0);
+ } else if (!strncmp(lbuf, "exclude_users=", 14)) {
/*
* Don't lookup users in this comma-separated list for both
* robustness and performnce. Typically root and other commonly
@@ -122,7 +140,7 @@ int nss_mapuser_config(int *errnop, const char *lname)
} else if (!strncmp(lbuf, "mapped_priv_user=", 17)) {
/* the user we are mapping to */
mapped_priv_user = strdup(lbuf + 17);
- } else if (!strncmp(lbuf, "min_uid=", 8)) {
+ } else if (!strncmp(lbuf, "map_min_uid=", 8)) {
/*
* Don't lookup uids that are local, typically set to either
* 0 or smallest always local user's uid
@@ -131,40 +149,52 @@ int nss_mapuser_config(int *errnop, const char *lname)
char *valid;
uid = strtoul(lbuf + 8, &valid, 0);
if (valid > (lbuf + 8))
- min_uid = (uid_t) uid;
- } else if (debug) /* ignore unrecognized lines, unless debug on */
+ map_min_uid = (uid_t) uid;
+ } else if (map_debug) /* ignore unrecognized lines, unless map_debug on */
syslog(LOG_WARNING, "%s: unrecognized parameter: %s",
libname, lbuf);
}
fclose(conf);
conf_parsed = 1;
- return mappeduser ? 0 : 1; /* can't do anything without this */
+ /* can't do anything without at least one of these */
+ return (mappeduser || mapped_priv_user) ? 0 : 1;
+}
+
+uint32_t get_sessionid(void)
+{
+ int fd = -1, cnt;
+ uint32_t id = 0U;
+ static char buf[12];
+
+ fd = open("/proc/self/sessionid", O_RDONLY);
+ if (fd != -1) {
+ cnt = read(fd, buf, sizeof(buf));
+ close(fd);
+ }
+ if (fd != -1 && cnt > 0) {
+ id = strtoul(buf, NULL, 0);
+ }
+ return id;
}
/*
* copy a passwd structure and it's strings, using the provided buffer
* for the strings.
- * usename is used for the new pw_name, the last part of the homedir,
+ * user name is used for the new pw_name, the last part of the homedir,
* and the GECOS field.
* For strings, if pointer is null, use an empty string.
* Returns 0 if everything fit, otherwise 1.
*/
int
-pwcopy(char *buf, size_t len, struct passwd *srcpw, struct passwd *destpw,
- const char *usename)
+pwcopy(char *buf, size_t len, const char *usename, struct passwd *srcpw,
+ struct passwd *destpw)
{
int needlen, cnt, origlen = len;
char *shell;
- if (!mappeduser) {
- if (debug)
- syslog(LOG_DEBUG, "%s: empty mapped_user, failing",
- libname);
- return 1;
- }
if (!usename) { /* this should never happen */
- if (debug)
+ if (map_debug)
syslog(LOG_DEBUG, "%s: empty username, failing",
libname);
return 1;
@@ -175,7 +205,7 @@ pwcopy(char *buf, size_t len, struct passwd *srcpw, struct passwd *destpw,
strlen(srcpw->pw_shell) + 1 : 1 + 2 + /* for 'x' in passwd */
12; /* for the "Mapped user" in gecos */
if (needlen > len) {
- if (debug)
+ if (map_debug)
syslog(LOG_DEBUG,
"%s provided password buffer too small (%ld<%d)",
libname, (long)len, needlen);
@@ -227,7 +257,7 @@ pwcopy(char *buf, size_t len, struct passwd *srcpw, struct passwd *destpw,
buf += cnt;
len -= cnt;
if (len < 0) {
- if (debug)
+ if (map_debug)
syslog(LOG_DEBUG,
"%s provided password buffer too small (%ld<%d)",
libname, (long)origlen, origlen - (int)len);
@@ -238,12 +268,16 @@ pwcopy(char *buf, size_t len, struct passwd *srcpw, struct passwd *destpw,
}
/*
- * This passes in a fixed
- * name for UID lookups, where we have the mapped name from the
- * map file.
- * returns 0 on success
+ * pb->name is non-NULL when we have the name and want to look it up
+ * from the mapping. mapuid will be the auid if we found it in the
+ * files, otherwise will be what was passed down, which should
+ * be the UID we are looking up when pb->name is NULL, when it's
+ * the uid lookup, and otherwise should be -1 when pb->name is not NULL.
+ * Returns 0 on success, 1 if uid not found in mapping files (even if
+ * uid matches the radius mapping users; let nss_files handle that).
*/
-int get_pw_mapuser(const char *name, struct pwbuf *pb)
+static int
+get_pw_mapuser(const char *name, struct pwbuf *pb, uid_t mapuid, int privileged)
{
FILE *pwfile;
struct passwd *ent;
@@ -256,18 +290,388 @@ int get_pw_mapuser(const char *name, struct pwbuf *pb)
return 1;
}
- pb->pw->pw_name = NULL; /* be paranoid */
for (ret = 1; ret && (ent = fgetpwent(pwfile));) {
if (!ent->pw_name)
continue; /* shouldn't happen */
- if (!strcmp(ent->pw_name, mappeduser)) {
- ret = pwcopy(pb->buf, pb->buflen, ent, pb->pw, name);
+ if (!strcmp(ent->pw_name, name) ||
+ !strcmp(ent->pw_name, privileged ? mapped_priv_user :
+ mappeduser) || ent->pw_uid == mapuid) {
+ ret =
+ pwcopy(pb->buf, pb->buflen, pb->name, ent, pb->pw);
break;
}
}
fclose(pwfile);
- if (ret)
+ if (ret) {
*pb->errnop = ERANGE;
+ }
+
+ return ret;
+}
+
+/*
+ * Read the requested session file (in the dbdir by intent), verify the
+ * uid matches, and setup the passwd structure with the username found
+ * in the file.
+ */
+static int chk_session_file(char *session, uid_t uid, struct pwbuf *pb)
+{
+ char rbuf[256], user[64], sessfile[sizeof dbdir + 12];
+ FILE *mapf;
+ uid_t auid = 0;
+ int ret = 1, privileged = 0;;
+ int gotinfo = 0; /* user, session, auid */
+
+ snprintf(sessfile, sizeof sessfile, "%s%s", dbdir, session);
+
+ mapf = fopen(sessfile, "r");
+ if (!mapf) {
+ if (map_debug > 2)
+ syslog(LOG_DEBUG,
+ "%s: session map file %s open fails: %m",
+ libname, sessfile);
+ return ret;
+ }
+ user[0] = '\0';
+ while (gotinfo != 4 && fgets(rbuf, sizeof rbuf, mapf)) {
+ strtok(rbuf, " \t\n\r\f"); /* terminate buffer at first whitespace */
+ if (!strncmp("user=", rbuf, 5)) {
+ if (pb->name && strcmp(rbuf + 5, pb->name))
+ break;
+ snprintf(user, sizeof user, "%s", rbuf + 5);
+ gotinfo++;
+ } else if (!strncmp("auid=", rbuf, 5)) {
+ char *ok;
+ uid_t fuid = (uid_t) strtoul(rbuf + 5, &ok, 10);
+ if (ok != (rbuf + 5)) {
+ gotinfo++;
+ if (uid != -1 && fuid != uid) {
+ auid = fuid;
+ break; /* getpwuid, but uid/auid mismatch, nogo */
+ } else
+ auid = fuid;
+ }
+ } else if (!strcasecmp("privileged=yes", rbuf)) {
+ privileged = 1;
+ gotinfo++;
+ } else if (!strcasecmp("privileged=no", rbuf))
+ gotinfo++;
+ else if (!strncmp("session=", rbuf, 8)) {
+ /* structural problem, so log warning */
+ if (strcmp(session, rbuf + 8)) {
+ syslog(LOG_WARNING,
+ "%s: session field \"%s\" mismatch in %s",
+ libname, rbuf, sessfile);
+ } else
+ gotinfo++;
+ }
+ }
+ fclose(mapf);
+
+ if (auid && user[0]) { /* otherwise not a match */
+ if (!pb->name)
+ pb->name = user; /* uid lookups */
+ ret = get_pw_mapuser(user, pb, auid, privileged);
+ }
+
+ return ret;
+}
+
+/*
+ * find mapping for this sessionid; if uid == -1, we are doing name lookup
+ * and will find in uid; else we are doing name lookup.
+ */
+int find_mapped_name(struct pwbuf *pb, uid_t uid, uint32_t session)
+{
+ char sess[11];
+
+ snprintf(sess, sizeof sess, "%u", session);
+ return chk_session_file(sess, uid, pb);
+}
+
+/*
+ * Called when we don't have a sessionid, or the sessionid we have
+ * doesn't match a mapped user (from find_mapped_name() above),
+ * so we need to look through all the mapping files.
+ * As with find_mapped_name(), if uid == -1, we are looking up from
+ * the name, otherwise we are looking up from the uid.
+ */
+int find_mappingfile(struct pwbuf *pb, uid_t uid)
+{
+ DIR *dir;
+ struct dirent *ent;
+ int ret = 1;
+
+ dir = opendir(dbdir);
+ if (!dir) { /* can happen if no mapped users logged in */
+ if (map_debug > 1)
+ syslog(LOG_DEBUG,
+ "%s: Unable to open mapping directory %s: %m",
+ libname, dbdir);
+ return 1;
+ }
+ /* Loop through all numeric files in dbdir, check for matching uid */
+ while (ret && (ent = readdir(dir))) {
+ if (!isdigit(ent->d_name[0]) || ent->d_type != DT_REG)
+ continue; /* sanity check on session file */
+ ret = chk_session_file(ent->d_name, uid, pb);
+ }
+ closedir(dir);
return ret;
}
+
+/*
+ * Used when there are no mapping entries, just create an entry from
+ * the default radius user
+ * This is needed so that ssh and login accept the username, and continue.
+ */
+int make_mapuser(struct pwbuf *pb, const char *name)
+{
+ int ret;
+ ret = get_pw_mapuser(mappeduser, pb, (uid_t) - 1, 0);
+ return ret;
+}
+
+static char
+*_getcmdname(void)
+{
+ static char buf[64];
+ char *rv = NULL;
+ int ret, fd;
+
+ if (*buf)
+ return buf;
+
+ fd = open("/proc/self/comm", O_RDONLY);
+ if (fd == -1) {
+ if (map_debug)
+ syslog(LOG_DEBUG,
+ "%s: failed to open /proc/self/comm: %m",
+ libname);
+ } else {
+ ret = read(fd, buf, sizeof buf);
+ if (ret <= 0) {
+ if (map_debug)
+ syslog(LOG_DEBUG,
+ "%s: read /proc/self/comm ret %d: %m",
+ libname, ret);
+ } else {
+ (void)strtok(buf, "\n\r ");
+ rv = buf;
+ }
+ }
+
+ return rv;
+}
+
+static int chk_progs(const char *pname)
+{
+ static const char *progs[] =
+ { "useradd", "usermod", "userdel", "adduser",
+ "deluser", NULL
+ };
+ const char **prog;
+ int ret = 0;
+
+ for (prog = &progs[0]; pname && *prog && !ret; prog++) {
+ if (!strcmp(pname, *prog)) {
+ if (map_debug > 1)
+ syslog(LOG_DEBUG,
+ "%s: running from %s, skip lookup",
+ libname, *prog);
+ ret = 1;
+ }
+ }
+ return ret;
+}
+
+/*
+ * the useradd family will not add/mod/del users correctly with
+ * the mapuid functionality, so return immediately if we are
+ * running as part of those processes. Same for adduser, deluser
+ * adduser and deluser are often perl scripts, so check for "comm"
+ * name from /proc, also, unless already matched from progname.
+ */
+int skip_program(void)
+{
+ return chk_progs(__progname) || chk_progs(_getcmdname());
+}
+
+/*
+ * All the entry points have this same common prolog, so put it here
+ */
+int map_init_common(int *errnop, const char *plugname)
+{
+ if (skip_program())
+ return 1;
+
+ if (nss_mapuser_config(errnop, plugname) == 1) {
+ *errnop = ENOENT;
+ syslog(LOG_NOTICE, "%s: bad configuration", plugname);
+ return 1;
+ }
+ return 0;
+}
+
+/* Open a session file, get the username and the privilege level,
+ * and if the privilege level matches, fill in usrbuf with name.
+ * 0 on errors or no match, 1 if match and usrbuf is valid
+ */
+int get_priv_usr(const char *fname, unsigned prbits, char *usrbuf)
+{
+ char buf[256], user[64], sessfile[sizeof dbdir + 12];
+ FILE *map;
+ int privmatch = 0;
+
+ snprintf(sessfile, sizeof sessfile, "%s%s", dbdir, fname);
+
+ map = fopen(sessfile, "r");
+ if (!map) {
+ syslog(LOG_WARNING, "%s: session map file %s open fails: %m",
+ libname, sessfile);
+ return 0;
+ }
+ user[0] = '\0';
+ while (fgets(buf, sizeof buf, map)) {
+ strtok(buf, " \t\n\r\f"); /* terminate buffer at first whitespace */
+ if (!strncmp("user=", buf, 5)) {
+ snprintf(user, sizeof user, "%s", buf + 5);
+ } else if (!strcasecmp("privileged=yes", buf)) {
+ if (prbits & PRIV_MATCH)
+ privmatch = 1;
+ } else if (!strcasecmp("privileged=no", buf)) {
+ if (prbits & UNPRIV_MATCH)
+ privmatch = 1;
+ }
+ }
+ fclose(map);
+ if (privmatch && user[0] && usrbuf)
+ strcpy(usrbuf, user);
+ return privmatch == 1;
+}
+
+
+/*
+* Return a char ** list of strings of usernames from the mapping files
+* that match priviliged=yes|no, for replacing the gr_mem field for
+* getgrent(), etc.
+* Makes one sanity check to be sure the listed PID is still active
+* before adding the username to the list.
+* Passed the original gr_mem array, which will
+* include the mappeduser or mapped_priv_user (or both).
+* All strings go into buf, and we return ERANGE in *err if there
+* isn't enough room.
+* The allocated memory will leak, but it's re-used on each call, so
+* not too signficant, and if endgrent() gets called, we'll clean up.
+*/
+char **fixup_gr_mem(const char *grnam, const char **gr_in, char *buf,
+ size_t * lenp, int *err, unsigned privbits)
+{
+ DIR *dir = NULL;
+ struct dirent *ent;
+ int ret = 1, nmemb, nadded = 0, midx, j;
+ const int nmax = 64; /* max members we'll add per group */
+ long long l = 0, len = *lenp; /* size_t unsigned on some systems */
+ const char **in;
+ char **out, *mem = buf;
+ char **gr_mem;
+ char newmembers[nmax][128];
+ const unsigned align = sizeof(void *) - 1;
+
+ *err = 0;
+ if (!gr_in)
+ goto done;
+
+ dir = opendir(dbdir);
+ if (!dir) {
+ /*
+ * Usually because no mapped users logged in. Could
+ * return ENOENT, but may as well just act like compat
+ */
+ goto done;
+ }
+
+ /* Loop through all numeric files in dbdir, check for matching uid */
+ while (ret && nadded < nmax && (ent = readdir(dir))) {
+ char usr[64];
+ if (!isdigit(ent->d_name[0]) || ent->d_type != DT_REG)
+ continue; /* sanity check on session file */
+ if (get_priv_usr(ent->d_name, privbits, usr)) {
+ int n;
+ int dup = 0;
+ for (n=0; n < nadded; n++) {
+ if (!strcmp(newmembers[n], usr)) {
+ dup++;
+ break;
+ }
+ }
+ if (dup)
+ continue;
+ l = snprintf(newmembers[nadded++], sizeof newmembers[0],
+ "%s", usr);
+ if (l >= sizeof newmembers[0])
+ syslog(LOG_WARNING,
+ "%s: group %s, member %s truncated to"
+ " %ld characters", libname, grnam, usr,
+ sizeof newmembers[0]);
+ }
+ }
+
+ if (!nadded)
+ goto done;
+
+ if (nadded == nmax && ent) {
+ syslog(LOG_WARNING,
+ "%s: Only adding %d members to"
+ " group %s", libname, nmax, grnam);
+ }
+ for (nmemb=0, in=gr_in; in && *in; in++)
+ nmemb++;
+
+ /* copy the original list first; maybe make a common routine later */
+ l = (((ptrdiff_t)mem + align) & align);
+ len -= align;
+ mem += align;
+ gr_mem = (char **)mem;
+ l = sizeof *gr_mem * (nmemb+nadded+1);
+ len -= l;
+ mem += l;
+
+ for (midx=0, in=gr_in; in && *in; in++) {
+ l = strlen(*in) + 1;
+ len -= l;
+ if (len < 0) {
+ *err = ERANGE;
+ goto done;
+ }
+ gr_mem[midx] = mem;
+ mem += l;
+ strcpy(gr_mem[midx++], *in);
+ }
+ /* now same for users we are adding */
+ for(j=0; j<nadded; j++) {
+ l = strlen(newmembers[j]) + 1;
+ len -= l;
+ if (len < 0) {
+ *err = ERANGE;
+ goto done;
+ }
+ gr_mem[midx] = mem;
+ mem += l;
+ strcpy(gr_mem[midx++], newmembers[j]);
+ }
+ gr_mem[midx] = NULL; /* terminate the list */
+ done:
+
+ if (dir)
+ closedir(dir);
+ if (*err || !nadded) {
+ out = NULL;
+ *lenp = 0;
+ } else {
+ out = gr_mem;
+ *lenp = mem - buf;
+ }
+ return out;
+}
diff --git a/map_common.h b/map_common.h
index b3401ea..e8e051d 100644
--- a/map_common.h
+++ b/map_common.h
@@ -49,10 +49,19 @@ struct pwbuf {
extern char *exclude_users;
extern char *mappeduser;
extern char *mapped_priv_user;
-extern uid_t min_uid;
-extern int debug;
+extern uid_t map_min_uid;
+extern int map_debug;
extern int nss_mapuser_config(int *errnop, const char *lname);
-extern int pwcopy(char *buf, size_t len, struct passwd *srcpw,
- struct passwd *destpw, const char *usename);
-extern int get_pw_mapuser(const char *name, struct pwbuf *pb);
+extern uint32_t get_sessionid(void);
+extern int skip_program(void);
+extern int find_mappingfile(struct pwbuf *pb, uid_t uid);
+extern int find_mapped_name(struct pwbuf *pb, uid_t uid, uint32_t session);
+extern int make_mapuser(struct pwbuf *pb, const char *name);
+extern int map_init_common(int *errnop, const char *plugname);
+extern char **fixup_gr_mem(const char *name, const char **gr_in, char *buf,
+ size_t * lp, int *err, unsigned privbits);
+extern void cleanup_gr_mem(void);
+
+#define PRIV_MATCH 2
+#define UNPRIV_MATCH 1
diff --git a/nss_mapname.c b/nss_mapname.c
index e974b75..46a64bc 100644
--- a/nss_mapname.c
+++ b/nss_mapname.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 Cumulus Networks, Inc.
+ * Copyright (C) 2017, 2018 Cumulus Networks, Inc.
* All rights reserved.
* Author: Dave Olson <olson@cumulusnetworks.com>
*
@@ -19,12 +19,18 @@
/*
* This plugin implements getpwnam_r for NSS to map any user
- * name to a fixed account (from the configuration file). The
- * fixed account is used to get the base of the home directory,
+ * name to one of two account (from the configuration file).
+ * The base mapping is done if no shell:prv-lvl attribute is
+ * received, or if the value is less than 15. If the attribute
+ * is present with the value 15, then we map to the privileged
+ * mapping account, which typically has the ability to run
+ * configuration commands.
+ * The fixed account is used to get the base of the home directory,
* and for the uid and gid. All other fields are replaced, and
* the password is always returned as 'x' (disabled). The assumption
* is that any authentication and authorization will be done via PAM
- * using some mechanism other than the local password file.
+ * using some mechanism other than the local password file, such as
+ * RADIUS
*
* Because it will match any account, this should always be the
* last module in /etc/nsswitch.conf for the passwd entry
@@ -37,17 +43,12 @@
#include "map_common.h"
#include <stdbool.h>
+#include <fcntl.h>
+#include <grp.h>
static const char *nssname = "nss_mapuser"; /* for syslogs */
/*
- * If you aren't using glibc or a variant that supports this,
- * and you have a system that supports the BSD getprogname(),
- * you can replace this use with getprogname()
- */
-extern const char *__progname;
-
-/*
* This is an NSS entry point.
* We map any username given to the account listed in the configuration file
* We only fail if we can't read the configuration file, or the username
@@ -63,21 +64,11 @@ enum nss_status _nss_mapname_getpwnam_r(const char *name, struct passwd *pw,
enum nss_status status = NSS_STATUS_NOTFOUND;
struct pwbuf pbuf;
bool islocal = 0;
+ unsigned session;
- /*
- * the useradd family will not add/mod/del users correctly with
- * the mapuid functionality, so return immediately if we are
- * running as part of those processes.
- */
- if (__progname && (!strcmp(__progname, "useradd") ||
- !strcmp(__progname, "usermod") ||
- !strcmp(__progname, "userdel")))
- return status;
-
- if (nss_mapuser_config(errnop, nssname) == 1) {
- syslog(LOG_NOTICE, "%s: bad configuration", nssname);
- return status;
- }
+ if (map_init_common(errnop, nssname))
+ return errnop
+ && *errnop == ENOENT ? NSS_STATUS_UNAVAIL : status;
/*
* Ignore any name starting with tacacs[0-9] in case a
@@ -109,7 +100,7 @@ enum nss_status _nss_mapname_getpwnam_r(const char *name, struct passwd *pw,
}
}
if (islocal) {
- if (debug > 1)
+ if (map_debug > 1)
syslog(LOG_DEBUG, "%s: skipped excluded user: %s",
nssname, name);
return 2;
@@ -122,8 +113,288 @@ enum nss_status _nss_mapname_getpwnam_r(const char *name, struct passwd *pw,
pbuf.buflen = buflen;
pbuf.errnop = errnop;
- if (!get_pw_mapuser(name, &pbuf))
+ session = get_sessionid();
+ if (session && !find_mapped_name(&pbuf, (uid_t) - 1, session))
+ status = NSS_STATUS_SUCCESS;
+ if (status != NSS_STATUS_SUCCESS) {
+ /* lookup by some unrelated process, try dir lookup */
+ if (!find_mappingfile(&pbuf, (uid_t) - 1))
+ status = NSS_STATUS_SUCCESS;
+ else if (!make_mapuser(&pbuf, name))
+ status = NSS_STATUS_SUCCESS;
+ }
+
+ return status;
+}
+
+/*
+ * The group routines are here so we can substitute mappings for radius_user
+ * and radius_priv_user when reading /etc/group, so that we can make
+ * users members of the appropriate groups for various privileged
+ * (and unprivileged) tasks.
+ * Ideally, we'd be able to use the getgr* routines specifying compat,
+ * but the NSS plugin infrastructure doesn't support that, so we have to
+ * read /etc/group directly, and then do our substitutions.
+ *
+ * This won't work if the RADIUS users are in LDAP group and/or password
+ * files, but that's the way it goes.
+ *
+ * For the intended purpose, it works well enough.
+ *
+ * We need getgrent() for this one, because initgroups needs it, unlike
+ * the password file.
+ */
+
+static FILE *grent;
+
+__attribute__ ((visibility("default")))
+enum nss_status _nss_mapname_setgrent(void)
+{
+ enum nss_status status = NSS_STATUS_NOTFOUND;
+ static const char *grpname = "/etc/group";
+ int error, *errnop = &error;
+
+ if (map_init_common(errnop, nssname))
+ return errnop
+ && *errnop == ENOENT ? NSS_STATUS_UNAVAIL : status;
+
+ if (grent) {
+ rewind(grent);
status = NSS_STATUS_SUCCESS;
+ goto done;
+ }
+
+ grent = fopen(grpname, "r");
+ if (!grent) {
+ syslog(LOG_WARNING, "%s: failed to open %s: %m",
+ nssname, grpname);
+ status = NSS_STATUS_UNAVAIL;
+ } else {
+ status = NSS_STATUS_SUCCESS;
+ /* don't leave fd open across execs */
+ (void)fcntl(fileno(grent), F_SETFD, FD_CLOEXEC);
+ }
+ done:
+ return status;
+}
+
+__attribute__ ((visibility("default")))
+enum nss_status _nss_mapname_endgrent(void)
+{
+ if (grent) {
+ FILE *f = grent;
+ grent = NULL;
+ (void)fclose(f);
+ }
+ return NSS_STATUS_SUCCESS;
+}
+
+/*
+ * do the fixups and copies, using the passed in buffer. result must
+ * have been checked to be sure it's non-NULL before calling.
+ */
+static int fixup_grent(struct group *entry, struct group *result, char *buf,
+ size_t lenbuf, int *errp)
+{
+ char **grusr, **new_grmem = NULL;
+ struct group *newg;
+ long long l, len; /* size_t unsigned on some systems */
+ int err, members, memlen;
+ int ret = NSS_STATUS_NOTFOUND;
+ unsigned pmatch = 0;
+ char *nm = entry->gr_name ? entry->gr_name : "(nil)";
+
+ if (!result) /* should always be non-NULL, just cautious */
+ return ret;
+
+ len = lenbuf;
+ if (!errp) /* to reduce checks below */
+ errp = &err;
+ *errp = 0;
+
+ newg = (struct group *)buf;
+ len -= sizeof *newg;
+ buf += sizeof *newg;
+ if (len < 0) {
+ *errp = ENOMEM;
+ return ret;
+ }
+ newg->gr_gid = entry->gr_gid;
+ l = snprintf(buf, len, "%s", entry->gr_name);
+ newg->gr_name = buf;
+ len -= l + 1;
+ buf += l + 1;
+ if (len > 0) {
+ l = snprintf(buf, len, "%s", entry->gr_passwd);
+ newg->gr_passwd = buf;
+ len -= l + 1;
+ buf += l + 1;
+ }
+ if (len < 0) {
+ *errp = ENOMEM;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ for (memlen = members = 0, grusr = entry->gr_mem; grusr && *grusr;
+ grusr++) {
+ if (mapped_priv_user && !strcmp(mapped_priv_user, *grusr))
+ pmatch |= PRIV_MATCH;
+ else if (mappeduser && !strcmp(mappeduser, *grusr))
+ pmatch |= UNPRIV_MATCH;
+ members++;
+ memlen += strlen(*grusr) + 1;
+ }
+ if (pmatch) { /* one or both mapped users are in gr_mem */
+ size_t usedbuf = len;
+ new_grmem = fixup_gr_mem(nm, (const char **)entry->gr_mem,
+ buf, &usedbuf, errp, pmatch);
+ buf += usedbuf;
+ len -= usedbuf;
+ if (errp) {
+ if (*errp == ERANGE)
+ ret = NSS_STATUS_TRYAGAIN;
+ else if (*errp == ENOENT)
+ ret = NSS_STATUS_UNAVAIL;
+
+ } else if (len < 0) {
+ *errp = ERANGE;
+ ret = NSS_STATUS_TRYAGAIN;
+ }
+ }
+ if (*errp)
+ goto done;
+ *result = *newg;
+ if (new_grmem)
+ result->gr_mem = new_grmem;
+ else {
+ char **sav, **entgr, *usrbuf;
+ len -= (members + 1) * sizeof *new_grmem;
+ len -= memlen;
+ if (len < 0) {
+ *errp = ERANGE;
+ ret = NSS_STATUS_TRYAGAIN;
+ goto done;
+ }
+ sav = result->gr_mem = (char **)buf;
+ buf += (members + 1) * sizeof *new_grmem;
+ usrbuf = buf;
+
+ for (entgr = entry->gr_mem; entgr && *entgr; entgr++, sav++) {
+ *sav = usrbuf;
+ usrbuf += strlen(*entgr) + 1;
+ strcpy(*sav, *entgr);
+ }
+
+ *sav = NULL;
+ }
+ ret = NSS_STATUS_SUCCESS;
+ done:
+ return ret;
+}
+
+/*
+ * No locking needed because our only global is the dirent * for
+ * the runuser directory, and our use of that should be thread safe
+ */
+__attribute__ ((visibility("default")))
+enum nss_status _nss_mapname_getgrent_r(struct group *gr_result,
+ char *buffer, size_t buflen,
+ int *errnop)
+{
+ enum nss_status status = NSS_STATUS_NOTFOUND;
+ struct group *ent;
+ int ret = 1;
+
+ if (!gr_result) {
+ if (errnop)
+ *errnop = EFAULT;
+ return status;
+ }
+
+ if (map_init_common(errnop, nssname))
+ return errnop
+ && *errnop == ENOENT ? NSS_STATUS_UNAVAIL : status;
+
+ if (!grent) {
+ status = _nss_mapname_setgrent();
+ if (status != NSS_STATUS_SUCCESS)
+ return status;
+ }
+
+ ent = fgetgrent(grent);
+ if (!ent) {
+ int e = errno;
+ if (ferror(grent)) {
+ syslog(LOG_WARNING,
+ "%s: error reading group information: %m",
+ nssname);
+ errno = e;
+ } else
+ errno = 0;
+ return status;
+ }
+ ret = fixup_grent(ent, gr_result, buffer, buflen, errnop);
+ return ret;
+}
+
+__attribute__ ((visibility("default")))
+enum nss_status _nss_mapname_getgrnam_r(const char *name, struct group *gr,
+ char *buffer, size_t buflen,
+ int *errnop)
+{
+ enum nss_status status = NSS_STATUS_NOTFOUND;
+ struct group *ent;
+
+ if (!gr) {
+ if (errnop)
+ *errnop = EFAULT;
+ return status;
+ }
+
+ if (map_init_common(errnop, nssname))
+ return errnop
+ && *errnop == ENOENT ? NSS_STATUS_UNAVAIL : status;
+
+ if (_nss_mapname_setgrent() != NSS_STATUS_SUCCESS)
+ return status;
+
+ for (ent = fgetgrent(grent); ent; ent = fgetgrent(grent)) {
+ if (!strcmp(ent->gr_name, name)) {
+ status = fixup_grent(ent, gr, buffer, buflen, errnop);
+ break;
+ }
+ }
+
+ return status;
+}
+
+__attribute__ ((visibility("default")))
+enum nss_status _nss_mapname_getgrgid_r(gid_t gid, struct group *gr,
+ char *buffer, size_t buflen,
+ int *errnop)
+{
+ enum nss_status status = NSS_STATUS_NOTFOUND;
+ struct group *ent;
+
+ if (!gr) {
+ if (errnop)
+ *errnop = EFAULT;
+ return status;
+ }
+
+ if (map_init_common(errnop, nssname))
+ return errnop
+ && *errnop == ENOENT ? NSS_STATUS_UNAVAIL : status;
+
+ if (_nss_mapname_setgrent() != NSS_STATUS_SUCCESS)
+ return status;
+
+ for (ent = fgetgrent(grent); ent; ent = fgetgrent(grent)) {
+ if (ent->gr_gid == gid) {
+ status = fixup_grent(ent, gr, buffer, buflen, errnop);
+ break;
+ }
+ }
return status;
}
diff --git a/nss_mapuid.c b/nss_mapuid.c
index cc6c5ec..acee3f5 100644
--- a/nss_mapuid.c
+++ b/nss_mapuid.c
@@ -43,119 +43,8 @@
#include "map_common.h"
#include <sys/types.h>
-#include <fcntl.h>
-#include <dirent.h>
-#include <ctype.h>
static const char *nssname = "nss_mapuid"; /* for syslogs */
-static const char dbdir[] = "/run/mapuser/";
-
-/*
- * If you aren't using glibc or a variant that supports this,
- * and you have a system that supports the BSD getprogname(),
- * you can replace this use with getprogname()
- */
-extern const char *__progname;
-
-/*
- * Read the requested session file (in the dbdir by intent), verify the
- * uid matches, and setup the passwd structure with the username found
- * in the file.
- */
-static int chk_session_file(char *sfile, uid_t uid, struct pwbuf *pb)
-{
- char rbuf[256], user[64];
- FILE *mapf;
- uid_t auid = 0;
- int ret = 1;
-
- mapf = fopen(sfile, "r");
- if (!mapf) {
- if (debug)
- syslog(LOG_DEBUG,
- "%s: session map file %s open fails: %m",
- nssname, sfile);
- return ret;
- }
- user[0] = '\0';
- while (fgets(rbuf, sizeof rbuf, mapf)) {
- strtok(rbuf, " \t\n\r\f"); /* terminate buffer at first whitespace */
- if (!strncmp("user=", rbuf, 5)) { /* should precede auid */
- snprintf(user, sizeof user, "%s", rbuf + 5);
- if (auid) /* found out of order, but now have both */
- break;
- } else if (!strncmp("auid=", rbuf, 5)) {
- uid_t fuid = (uid_t) strtoul(rbuf + 5, NULL, 10);
- if (fuid && uid == fuid) {
- auid = fuid;
- if (user[0])
- break; /* normal ordering, else keep looking for user */
- }
- }
- }
- fclose(mapf);
-
- if (auid && user[0]) /* otherwise not a match */
- ret = get_pw_mapuser(user, pb); /* should always succeed */
-
- return ret;
-}
-
-/* find mapping for this sessionid */
-static int find_mapped_name(struct pwbuf *pb, uid_t uid, uint32_t session)
-{
- char sessfile[sizeof dbdir + 11];
-
- snprintf(sessfile, sizeof sessfile, "%s%u", dbdir, session);
- return chk_session_file(sessfile, uid, pb);
-}
-
-static int find_mappingfile(struct pwbuf *pb, uid_t uid)
-{
- DIR *dir;
- struct dirent *ent;
- int ret = 1;
-
- dir = opendir(dbdir);
- if (!dir) { /* can happen if no mapped users logged in */
- if (debug > 1)
- syslog(LOG_DEBUG,
- "%s: Unable to open mapping directory %s: %m",
- nssname, dbdir);
- return 1;
- }
-
- /* Loop through all numeric files in dbdir, check for matching uid */
- while (ret && (ent = readdir(dir))) {
- char sessfile[sizeof dbdir + 11];
- if (!isdigit(ent->d_name[0])) /* sanity check on session file */
- continue;
- snprintf(sessfile, sizeof sessfile, "%s%s", dbdir, ent->d_name);
- ret = chk_session_file(sessfile, uid, pb);
- }
- if (ret && debug)
- syslog(LOG_DEBUG, "%s: uid %u mapping not found in map files",
- nssname, uid);
- closedir(dir);
- return ret;
-}
-
-static uint32_t get_sessionid(void)
-{
- int fd = -1, cnt;
- uint32_t id = 0U;
- static char buf[12];
-
- fd = open("/proc/self/sessionid", O_RDONLY);
- if (fd != -1) {
- cnt = read(fd, buf, sizeof(buf));
- close(fd);
- }
- if (fd != -1 && cnt > 0) {
- id = strtoul(buf, NULL, 0);
- }
- return id;
-}
/*
* This is an NSS entry point.
@@ -189,27 +78,14 @@ enum nss_status _nss_mapuid_getpwuid_r(uid_t uid, struct passwd *pw,
enum nss_status status = NSS_STATUS_NOTFOUND;
uint32_t session;
- /*
- * the useradd family will not add/mod/del users correctly with
- * the mapuid functionality, so return immediately if we are
- * running as part of those processes.
- */
- if (__progname && (!strcmp(__progname, "useradd") ||
- !strcmp(__progname, "usermod") ||
- !strcmp(__progname, "userdel")))
+ if (map_init_common(errnop, nssname))
return status;
- /* this can happen for permission reasons, do don't complain except
- * at debug */
- if (nss_mapuser_config(errnop, nssname) == 1) {
- return status; /* syslog already done */
- }
-
- if (min_uid != ~0U && uid < min_uid) {
- if (debug > 1)
+ if (map_min_uid != ~0U && uid < map_min_uid) {
+ if (map_debug > 1)
syslog(LOG_DEBUG,
- "%s: uid %u < min_uid %u, don't lookup", nssname,
- uid, min_uid);
+ "%s: uid %u < map_min_uid %u, don't lookup",
+ nssname, uid, map_min_uid);
return status;
}
diff --git a/nss_mapuser.5 b/nss_mapuser.5
index 3e17aec..6e63138 100644
--- a/nss_mapuser.5
+++ b/nss_mapuser.5
@@ -1,21 +1,22 @@
.TH nss_mapuser 5
-.\" Copyright 2017 Cumulus Networks, Inc. All rights reserved.
+.\" Copyright 2017, 2018 Cumulus Networks, Inc. All rights reserved.
.SH NAME
nss_mapuser.conf \- NSS mapuser configuration file
.SH DESCRIPTION
-This is the configuration file for the NSS mapuser plugins.
+This is the configuration file for the NSS mapuser and mapuid plugins.
See the
.BR nss_mapuser (8)
-manpage for more general information on the plugin.
-This configuration file controls debug settings, the local account used
-for mapping users, and which usernames (accounts) and uids are skipped.
-.PP
+manpage for more general information on the plugins.
+.P
+This configuration file controls debug settings, the local accounts used for mapping
+users, and the list of usernames (accounts) and uids that are skipped (not looked up by
+this plugin).
.TP
.I debug=NUMBER
Output lookup debugging information via syslog(3).
.TP
.I exclude_users=user1,user2...
-Usernames (accounts) comma separate list. This is used by mapname NSS plugin getpwuid()
+Usernames (accounts) comma separate list. This is used by the NSS mapuser plugin getpwuid()
entry point. The account to be looked up is checked against this list. If a match is
found, an immediate NOTFOUND status is returned. This reduces overhead for the standard
local user accounts. The
@@ -23,19 +24,48 @@ local user accounts. The
and
.I mapped_priv_user
fields from the
-configuration file are always skipped, as are any names starting with
+configuration file are always skipped, as are any names starting with
.BR tacacs[0-9] .
.TP
+.I min_uid=NUMBER
+UID's passed to the NSS mapuid plugin getpwuid() entry point that are below this value
+cause an immediate NOTFOUND status to be returned. This reduces
+overhead for the standard local user accounts.
+.BR NOTE :
+The value must be less than the uid of the mapped account names below.
+.TP
+.I mapped_priv_user=NAME
+This is the local account used as a template for privileged logins
+(the RADIUS VSA
+.BR shell:priv-lvl=15 )
+was returned by the server.
+This must be a local account (found in
+.IR /etc/passwd ).
+When a uid or name match is found, this account information is read from
+.I /etc/passwd
+and used as a template for the matching account. The default at installation is
+.BR radius_priv_user .
+.TP
.I mapped_user=NAME
-This is the local account which mapping uses as a template. It must be a local
-account (found in
+This is the local account used as a template for unprivileged logins
+(either no privilege attribute was returned by the server, or the privilege level is
+is in the range 0-14. This must be a local account (found in
.IR /etc/passwd ).
When a uid or name match is found, this account information is read from
.I /etc/passwd
-and used as a template for the matching account. The
+and used as a template for the matching account. The default at installation is
+.BR radius_user .
+.P
+For the
+.I map_user
+and
+.I map_priv_user
+accounts, the user information that is returned via the
+.BR getpwnam (3)
+group of account lookup routines has the
.B pw_name
field (user account name)
-is replaced with the original (login) name, and the original name is
+replaced with the original (login) name, and the original name is
inserted at the beginning of the
.B pw_gecos
field. The
@@ -43,8 +73,12 @@ field. The
(home directory)
field replaces the last component of the directory path with the original login
name.
-.IP
-When changing this field to a different name than the default, be sure the account exists in
+.P
+When changing the
+.I map_user
+or
+.I map_priv_user
+fields to a different account than the default, be sure the account exists in
.IR /etc/passwd ,
and that the account was created as disabled or locked (does not have a legal password, so
the
@@ -54,11 +88,22 @@ account can not be used for logins. When using
to create these accounts, use the
.B --disabled-login
argument to disable login for the account.
-.TP
-.I min_uid=NUMBER
-UID's passed to the mapuid NSS plugin getpwuid() entry point that are below this value
-cause an immediate NOTFOUND status to be returned. This reduces
-overhead for the standard local user accounts.
+.P
+At installation, the
+.I map_user
+user account
+.B radius_user
+is added to the
+.I netshow
+group so that the user can run NCLU
+.B net show
+commands. Similarly, the
+.I map_priv_user
+user account
+.B radius_priv_user
+is added to the
+.I netedit
+group so that the user can run NCLU configuration commands.
.SH "SEE ALSO"
.BR adduser (8),
.BR pam_radius_auth (8),
diff --git a/nss_mapuser.8 b/nss_mapuser.8
index 1b258c5..73e0275 100644
--- a/nss_mapuser.8
+++ b/nss_mapuser.8
@@ -1,5 +1,5 @@
.TH nss_mapuser 8
-.\" Copyright 2017 Cumulus Networks, Inc. All rights reserved.
+.\" Copyright 2017, 2018 Cumulus Networks, Inc. All rights reserved.
.SH NAME
libnss_mapname.so.2 \- NSS mapuser plugin
.br
@@ -14,16 +14,50 @@ provide enough information to define a linux account (uid, gid, home directory).
The traditional method was to add all RADIUS users to the local
.I /etc/passwd
file, or to enable them via other means such as LDAP.
+.P
These plugins allow RADIUS users to login with no configuration other than the
initial setup of the RADIUS client, and these plugins.
-.PP
+.P
The plugins work by mapping user accounts to a named account in a configuration
file, and using the named account as a template for the requested account.
-.PP
-The named account (default is
-.I radius_user)
+.P
+The named accounts
must be present in
-.IR /etc/passwd .
+.IR /etc/passwd ,
+and the groups set up correctly in
+.IR /etc/group
+for these plugins to work correctly.
+.P
+The default accounts are
+.I radius_priv_user
+for privileged logins with
+the RADIUS VSA
+.BR shell:priv-lvl=15 )
+attribute, and
+.I radius_user
+for logins without that attribute, or with the privilege level 0-14.
+The accounts are created when the debian package is installed.
+.P
+The mapname plugin also supplies NSS functions for the group file, in
+order to map RADIUS logins into appropriate groups. For this to work,
+the two RADIUS accounts above are added to the
+.BR sudo ,
+.BR netshow ,
+and
+.B netedit
+groups during the installation of the debian packge. The privileged account
+is made a member of the
+.B sudo
+and
+.B netedit
+groups, while the unprivileged account is made a member of the
+.B netshow
+group. This can be verified after logging in by using the
+.IR id (1),
+or
+.IR groups (1)
+command to list the groups of which you are a member.
+.P
The
.B pw_name
field (user account name)
@@ -67,19 +101,31 @@ file with the same UID.
There are two separate plugins,
.B libnss_mapname
for user account names
-.RI ( getpwnam() (3)),
+.RI ( getpwnam() (3)
+and
+.RI ( getpwnam_r() (3)),
+as well as
+.RI ( getgrnam() (3),
+.RI ( getpgram_r() (3)),
+and
+.RI ( getpgrent() (3)),
and
.B libnss_mapuid
for uid
-.RI ( getpwuid() (3)),
-Two separate plugins are required.
-.PP
+.RI ( getpwuid() (3)
+and
+.RI ( getpwuid_r() (3)).
+.P
+Two separate plugins are required due to ordering requirements in
+.IR /etc/nsswitch.conf .
+.P
The name lookup
.B mapuser
must be the last method used (last plugin on the
.B passwd
database), because it will always produce a successful lookup on
-any user account name, unless there are configuration or other errors.
+any user account name, unless the name has has been excluded, or if
+there are configuration or other errors.
.PP
The uid lookup
.B mapuid
@@ -87,24 +133,29 @@ must be the first method used (first plugin on the
.B passwd
database), because the uid will always match a local account from
.IR /etc/passwd ,
-any user account name, unless there are configuration or other errors.
+any user account name, unless limited by the minimum uid configuration, or
+if there are configuration or other errors.
.PP
-The flat file database is created using the
-.B pam_script
-plugin. In addition to creating and deleting files at session start and
-end, the open script will also create the home directory using
-.IR mkhomedir_helper .
+The flat file database used by these plugins is created and removed by the
+.B pam_radius_auth
+plugin from the libpam-radius-auth package.
+In addition to creating and deleting files at session start and end, the
+.B pam_radius_auth
+plugin will also create the home directory using the
+.I mkhomedir_helper
+program.
.SH "SEE ALSO"
.BR adduser (8),
.BR mkhomedir_helper (8),
.BR pam_radius_auth (8),
-.BR pam_script (8),
.BR nss_mapuser (5),
.BR nsswitch.conf (5),
.BR getpwuid (3),
.BR getpwnam (3),
.BR getent (1).
.SH FILES
+.I /etc/nsswitch.conf
+- configuration file for NSS plugins. It is modified at package installation
.I /etc/nss_mapuser.conf
- mapuser NSS plugin configuration parameters.
.br
diff --git a/nss_mapuser.conf b/nss_mapuser.conf
index 2685ac0..c5f2098 100644
--- a/nss_mapuser.conf
+++ b/nss_mapuser.conf
@@ -35,3 +35,4 @@ exclude_users=root,daemon,nobody,cron,sshd,cumulus,quagga,frr,snmp,www-data,ntp,
# Map all usernames to the radius_user account (use the uid, gid, shell, and
# base of the home directory from the cumulus entry in /etc/passwd).
mapped_user=radius_user
+mapped_priv_user=radius_priv_user
diff --git a/pam_script_ses_close b/pam_script_ses_close
deleted file mode 100755
index 8340543..0000000
--- a/pam_script_ses_close
+++ /dev/null
@@ -1,85 +0,0 @@
-#! /bin/bash
-# Copyright 2017 Cumulus Networks, Inc. All rights reserved
-#
-# 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, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-
-# This script is invoked via pam_script.so for session close, to
-# clean up the mapping setup on session open. The info is used
-# in the libnss_mapuser getpwuid() entry point.
-
-# auid is currently unused, but must match the uid of the mapped_user
-# in the libnss_mapuser database for this to be valid
-
-# For this to work, pam_loginuid.so must be used, so both the
-# loginuid and the sessionid are unique values > 0
-
-dbdir=/run/mapuser
-mkdir -p $dbdir
-
-read sess < /proc/$$/sessionid
-read auid < /proc/$$/loginuid
-
-# never map root user, or when loginuid isn't set, or when
-# we aren't doing mapping (env variable not set)
-if [ "$auid" -eq 0 ]; then exit 0; fi
-
-# for debugging, if needed
-#DEBUG logger -t mapuser $0 user=$PAM_USER pid=$$ session="$sess" auid="$auid"
-
-if [ "$sess" -le 0 ] ; then
- logger -t $0 sessionid not set, no mapuser cleanup for \
- PID $$ user $PAM_USER
- exit 0 # never trigger an error
-fi
-
-file=$dbdir/$sess
-if [ -e $file ]; then
- IFS='=
-' read tag fauid <<< $(grep '^auid=' $file)
- IFS='=
-' read tag fsess <<< $(grep '^session=' $file)
- # If info doesn't match, report it, and don't clean up
- if [ "$auid" != "$fauid" -o "$sess" != "$fsess" ]; then
- logger -t $0 "Session $sess mismatch auid $auid,$fauid session $sess,$fsess"
- else
- uid=$(id -u)
- if [ "$uid" -ne 0 ]; then # shouldn't happen from pam_script
- logger -t $0 called with UID=$uid, no cleanup
- exit 0
- fi
- pids=( $(egrep -w $fsess /proc/[1-9]*/sessionid | \
- sed -e 's,/proc/,,' -e 's,/.*,,') )
- clean=1
- for pid in ${pids[*]}; do
- [ $pid -eq $$ ] && continue # skip ourselve
- read cmd 2>/dev/null < /proc/$pid/comm # ignore exited egrep, sed
- [ -z "$cmd" ] && continue # pid exited
- msg="$msg PID $pid comm=$cmd"
- case "$cmd" in
- sshd|sudo|login|su|telnetd) ;;
- *) clean=0 ; cleancmd="$cmd" ;;
- esac
- done
- #DEBUG logger -t $0 sess=$fsess clean=$clean cmd=$cleancmd has $msg active
- [ $clean -eq 1 ] && {
- #DEBUG logger -t $0 cleanup session $fsess
- rm -f $file
- }
- fi
-fi
-
-# always succeed, this should not cause sessions shutdown errors
-exit 0
diff --git a/pam_script_ses_open b/pam_script_ses_open
deleted file mode 100755
index 731e250..0000000
--- a/pam_script_ses_open
+++ /dev/null
@@ -1,65 +0,0 @@
-#! /bin/bash
-# Copyright 2017 Cumulus Networks, Inc. All rights reserved
-#
-# 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, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-
-# This script is invoked via pam_script.so for session open, used for mapping
-# RADIUS usernames to the mapped uid, for libnss_mapuser getpwuid() entry
-# point.
-
-# auid is currently unused, but must match the uid of the mapped_user
-# in the libnss_mapuser database for this to be valid
-
-# For this to work, pam_loginuid.so must be used, so both the
-# loginuid and the sessionid are unique values > 0
-
-umask 022 # want everything world-readable.
-
-dbdir=/run/mapuser
-mkdir -p $dbdir
-
-read sess < /proc/$$/sessionid
-read auid < /proc/$$/loginuid
-
-# for debugging, if needed
-# logger -t mapuser $0 called with $PAM_USER pid=$$ session="$sess" auid="$auid"
-
-# never map root user, or when loginuid isn't set, or when
-# we aren't doing mapping (env variable not set)
-if [ "$auid" -eq 0 ]; then exit 0; fi
-
-# handle this one differently, since it means something is
-# configured wrong.
-if [ "$sess" -le 0 ] ; then
- logger -t $0 sessionid not set, no mapping possible for \
- PID $$ user $PAM_USER
- exit 0 # still allow the session
-fi
-
-# if user's home directory doesn't exist, create it and populate
-# it with the standard skeleton files.
-hdir=$(eval echo ~$PAM_USER)
-[ -d "$hdir" ] || /sbin/mkhomedir_helper $PAM_USER
-
-# don't overwrite if it already exists. Happens when sudo or su
-# is run from an existing mapped session.
-[ -s $dbdir/$sess ] || \
- date +"%FT%T.%N%nuser=$PAM_USER%npid=$$%nauid=$auid%nsession=$sess%nhome=$hdir" \
- > $dbdir/$sess
-
-# always succeed, this should not block sessions on errors
-exit 0
-