summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-