diff options
Diffstat (limited to 'map_common.c')
-rw-r--r-- | map_common.c | 476 |
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; +} |