summaryrefslogtreecommitdiff
path: root/map_common.c
diff options
context:
space:
mode:
authorDave Olson <olson@cumulusnetworks.com>2018-04-02 11:01:09 -0700
committerDave Olson <olson@cumulusnetworks.com>2018-04-10 12:33:12 -0700
commit5630f132b8898fa1ec3236a0ad796755127881c9 (patch)
tree2480dc22d6bd3e99084aa85e5679e06b71a33ea0 /map_common.c
parent7f2e226bde6e6cd5cc7b29f7385a8c14a9b8b66f (diff)
downloadlibnss-mapuser-5630f132b8898fa1ec3236a0ad796755127881c9.tar.gz
libnss-mapuser-5630f132b8898fa1ec3236a0ad796755127881c9.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).
Diffstat (limited to 'map_common.c')
-rw-r--r--map_common.c476
1 files changed, 440 insertions, 36 deletions
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;
+}