summaryrefslogtreecommitdiff
path: root/map_common.c
diff options
context:
space:
mode:
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;
+}