diff options
Diffstat (limited to 'nss_mapname.c')
-rw-r--r-- | nss_mapname.c | 325 |
1 files changed, 298 insertions, 27 deletions
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; } |