summaryrefslogtreecommitdiff
path: root/nss_tacplus.c
diff options
context:
space:
mode:
Diffstat (limited to 'nss_tacplus.c')
-rw-r--r--nss_tacplus.c664
1 files changed, 570 insertions, 94 deletions
diff --git a/nss_tacplus.c b/nss_tacplus.c
index 79e62b9..3a7e09c 100644
--- a/nss_tacplus.c
+++ b/nss_tacplus.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014, 2015, 2016, 2017 Cumulus Networks, Inc.
+ * Copyright (C) 2014, 2015, 2016, 2017, 2018, 2019 Cumulus Networks, Inc.
* All rights reserved.
* Author: Dave Olson <olson@cumulusnetworks.com>
*
@@ -37,6 +37,8 @@
#include <ctype.h>
#include <nss.h>
#include <libaudit.h>
+#include <stddef.h>
+#include <grp.h>
#include <sys/socket.h>
#include <sys/stat.h>
@@ -63,6 +65,7 @@ struct pwbuf {
typedef struct {
struct addrinfo *addr;
char *key;
+ unsigned not_resp;
} tacplus_server_t;
/* set from configuration file parsing */
@@ -75,8 +78,12 @@ static char vrfname[64];
static char *exclude_users;
static uid_t min_uid = ~0U; /* largest possible */
static int debug;
-uint16_t use_tachome;
+static int printed_srvs;
+static uint16_t use_tachome;
static int conf_parsed = 0;
+static struct sockaddr src_sockaddr;
+static struct addrinfo src_addr_info;
+static struct addrinfo *src_addr;
static void get_remote_addr(void);
@@ -98,9 +105,11 @@ reset_config(void)
exclude_users = NULL;
}
debug = 0;
+ printed_srvs = 0;
use_tachome = 0;
tac_timeout = 0;
min_uid = ~0U;
+ src_addr = NULL;
for(i = 0; i < nservers; i++) {
if(tac_srv[i].key) {
@@ -108,9 +117,30 @@ reset_config(void)
tac_srv[i].key = NULL;
}
tac_srv[i].addr = NULL;
+ tac_srv[i].not_resp = 0;
}
}
+/* Convert ip address string to address info.
+ * It returns 0 on success, or -1 otherwise
+ * It supports ipv4 only.
+ */
+static int str_to_ipv4(const char *srcaddr, struct addrinfo *p_addr_info)
+{
+ struct sockaddr_in *s_in;
+
+ s_in = (struct sockaddr_in *)p_addr_info->ai_addr;
+ s_in->sin_family = AF_INET;
+ s_in->sin_addr.s_addr = INADDR_ANY;
+
+ if (inet_pton(AF_INET, srcaddr, &(s_in->sin_addr)) == 1) {
+ p_addr_info->ai_family = AF_INET;
+ p_addr_info->ai_addrlen = sizeof (struct sockaddr_in);
+ return 0;
+ }
+ return -1;
+}
+
static int nss_tacplus_config(int *errnop, const char *cfile, int top)
{
FILE *conf;
@@ -146,8 +176,8 @@ static int nss_tacplus_config(int *errnop, const char *cfile, int top)
break; /* found removed or different file, so re-parse */
}
reset_config();
- syslog(LOG_NOTICE, "%s: Configuration file(s) have changed, re-initializing",
- nssname);
+ syslog(LOG_NOTICE, "%s: Configuration file(s) have changed, "
+ "re-initializing", nssname);
}
/* don't check for failures, we'll just skip, don't want to error out */
@@ -181,11 +211,15 @@ static int nss_tacplus_config(int *errnop, const char *cfile, int top)
else if (!strncmp (lbuf, "user_homedir=", 13))
use_tachome = (uint16_t)strtoul(lbuf+13, NULL, 0);
else if (!strncmp (lbuf, "timeout=", 8)) {
- tac_timeout = (int)strtoul(lbuf+8, NULL, 0);
- if (tac_timeout < 0) /* explict neg values disable poll() use */
- tac_timeout = 0;
- else /* poll() only used if timeout is explictly set */
+ char *argend;
+ int val = (unsigned)strtol(lbuf+8, &argend, 0);
+ if (argend != (lbuf+8) && val >= 0) {
+ tac_timeout = val;
tac_readtimeout_enable = 1;
+ }
+ else
+ syslog(LOG_WARNING, "%s: invalid parameter value (%s)",
+ nssname, lbuf);
}
/*
* This next group is here to prevent a warning in the
@@ -203,8 +237,9 @@ static int nss_tacplus_config(int *errnop, const char *cfile, int top)
if((tac_srv[tac_key_no].key = strdup(lbuf+7)))
tac_key_no++;
else
- syslog(LOG_ERR, "%s: unable to copy server secret %s",
- nssname, lbuf+7);
+ /* don't log the actual line, it's a security issue */
+ syslog(LOG_ERR, "%s: unable to copy server secret %d: %m",
+ nssname, tac_key_no);
}
/* handle case where 'secret=' was given after a 'server='
* parameter, fill in the current secret */
@@ -214,6 +249,21 @@ static int nss_tacplus_config(int *errnop, const char *cfile, int top)
tac_srv[i].key = strdup(lbuf+7);
}
}
+ else if (!strncmp (lbuf, "source_ip=", 10)) {
+ const char *srcip = lbuf + 10;
+ /* if source ip address, convert it to addr info */
+ memset (&src_addr_info, 0, sizeof (struct addrinfo));
+ memset (&src_sockaddr, 0, sizeof (struct sockaddr));
+ src_addr_info.ai_addr = &src_sockaddr;
+ if (str_to_ipv4 (srcip, &src_addr_info) == 0)
+ src_addr = &src_addr_info;
+ else {
+ src_addr = NULL; /* for re-parsing or errors */
+ syslog(LOG_WARNING,
+ "%s: unable to convert %s to an IPv4 address", nssname,
+ lbuf);
+ }
+ }
else if(!strncmp(lbuf, "exclude_users=", 14)) {
/*
* Don't lookup users in this comma-separated list for both
@@ -251,7 +301,7 @@ static int nss_tacplus_config(int *errnop, const char *cfile, int top)
port = strchr(server_buf, ':');
if(port != NULL) {
*port = '\0';
- port++;
+ port++;
}
if((rv = getaddrinfo(server_buf, (port == NULL) ?
"49" : port, &hints, &servers)) == 0) {
@@ -278,8 +328,12 @@ static int nss_tacplus_config(int *errnop, const char *cfile, int top)
}
}
else if(debug) /* ignore unrecognized lines, unless debug on */
- syslog(LOG_WARNING, "%s: unrecognized parameter: %s",
- nssname, lbuf);
+ /* Don't complain about acct_all, may be set in shared
+ * config, even if we don't use it.
+ */
+ if(strncmp(lbuf, "acct_all=", 9))
+ syslog(LOG_WARNING, "%s: unrecognized parameter: %s",
+ nssname, lbuf);
}
fclose(conf);
@@ -287,6 +341,26 @@ static int nss_tacplus_config(int *errnop, const char *cfile, int top)
return 0;
}
+/* common get config code at each entry point */
+static int
+get_config(int *errp, int level)
+{
+ int result = nss_tacplus_config(errp, config_file, 1);
+ conf_parsed = result == 0 ? 2 : 1;
+
+ get_remote_addr();
+
+ /* no config file, no servers, etc.
+ * this is a debug because privileges may not allow access
+ * for included files.
+ */
+ if(debug && result)
+ syslog(LOG_DEBUG, "%s: bad config or server line for nss_tacplus",
+ nssname);
+ return result;
+}
+
+
/*
* Separate function so we can print first time we try to connect,
* rather than during config.
@@ -295,23 +369,23 @@ static int nss_tacplus_config(int *errnop, const char *cfile, int top)
*/
static void print_servers(void)
{
- static int printed = 0;
int n;
- if (printed || !debug)
+ if (printed_srvs || !debug)
return;
- printed = 1;
+ printed_srvs = 1;
if(tac_srv_no == 0)
- syslog(LOG_DEBUG, "%s:%s: no TACACS %s in config (or no perm),"
+ syslog(LOG_DEBUG, "%s:%s: no TACACS %s in config (or no permission),"
" giving up",
nssname, __FUNCTION__, tac_srv_no ? "service" :
(*tac_service ? "server" : "service and no server"));
+ /* do not log tac_srv[n].key; it's a security issue */
for(n = 0; n < tac_srv_no; n++)
- syslog(LOG_DEBUG, "%s: server[%d] { addr=%s, key='%s' }", nssname,
+ syslog(LOG_DEBUG, "%s: server[%d] { addr=%s }", nssname,
n, tac_srv[n].addr ? tac_ntop(tac_srv[n].addr->ai_addr)
- : "unknown", tac_srv[n].key);
+ : "unknown");
}
/*
@@ -366,8 +440,8 @@ pwcopy(char *buf, size_t len, struct passwd *srcpw, struct passwd *destpw,
if (tachome && *shell == 'r') {
tachome = 0;
if(debug > 1)
- syslog(LOG_DEBUG, "%s tacacs login %s with user_homedir not allowed; "
- "shell is %s", nssname, srcpw->pw_name, buf);
+ syslog(LOG_DEBUG, "%s tacacs login %s with user_homedir not allowed"
+ "; shell is %s", nssname, srcpw->pw_name, buf);
}
cnt++;
buf += cnt;
@@ -391,7 +465,6 @@ pwcopy(char *buf, size_t len, struct passwd *srcpw, struct passwd *destpw,
cnt = snprintf(buf, len, "%s", srcpw->pw_dir ? srcpw->pw_dir : "");
destpw->pw_dir = buf;
cnt++;
- buf += cnt;
len -= cnt;
if(len < 0) {
if(debug)
@@ -417,17 +490,17 @@ pwcopy(char *buf, size_t len, struct passwd *srcpw, struct passwd *destpw,
*
* If not found, then try to map to a localuser tacacsN where N <= to the
* TACACS+ privilege level, using the APIs in libtacplus_map.so
- * algorithm in update_mapuser()
+ * algorithm in update_mapuser(), but only considering tacacs users.
* Returns 0 on success, else 1
*/
static int
find_pw_userpriv(unsigned priv, struct pwbuf *pb)
{
FILE *pwfile;
- struct passwd upw, tpw, *ent;
- int matches, ret, retu, rett;
+ struct passwd tpw, *ent;
+ int matches, ret, rett;
unsigned origpriv = priv;
- char ubuf[pb->buflen], tbuf[pb->buflen];
+ char tbuf[pb->buflen];
char tacuser[9]; /* "tacacs" followed by 1-2 digits */
tacuser[0] = '\0';
@@ -440,16 +513,10 @@ find_pw_userpriv(unsigned priv, struct pwbuf *pb)
recheck:
snprintf(tacuser, sizeof tacuser, "tacacs%u", priv);
- tpw.pw_name = upw.pw_name = NULL;
- retu = 0, rett = 0;
- for(matches=0; matches < 2 && (ent = fgetpwent(pwfile)); ) {
- if(!ent->pw_name)
- continue; /* shouldn't happen */
- if(!strcmp(ent->pw_name, pb->name)) {
- retu = pwcopy(ubuf, sizeof(ubuf), ent, &upw, NULL, use_tachome);
- matches++;
- }
- else if(!strcmp(ent->pw_name, tacuser)) {
+ tpw.pw_name = NULL;
+ rett = 0;
+ for(matches=0; !matches && (ent = fgetpwent(pwfile)); ) {
+ if(ent->pw_name && !strcmp(ent->pw_name, tacuser)) {
rett = pwcopy(tbuf, sizeof(tbuf), ent, &tpw, NULL, use_tachome);
matches++;
}
@@ -465,12 +532,10 @@ recheck:
if(priv != origpriv && debug)
syslog(LOG_DEBUG, "%s: local user not found at privilege=%u,"
" using %s", nssname, origpriv, tacuser);
- if(upw.pw_name && !retu)
- ret = pwcopy(pb->buf, pb->buflen, &upw, pb->pw, pb->name,
- use_tachome);
- else if(tpw.pw_name && !rett)
+ if(tpw.pw_name && !rett) {
ret = pwcopy(pb->buf, pb->buflen, &tpw, pb->pw, pb->name,
use_tachome);
+ }
}
if(ret)
*pb->errnop = ERANGE;
@@ -570,7 +635,7 @@ connect_tacacs(struct tac_attrib **attr, int srvr)
{
int fd;
- fd = tac_connect_single(tac_srv[srvr].addr, tac_srv[srvr].key, NULL,
+ fd = tac_connect_single(tac_srv[srvr].addr, tac_srv[srvr].key, src_addr,
vrfname[0]?vrfname:NULL);
if(fd >= 0) {
tac_add_attrib(attr, "service", tac_service);
@@ -582,6 +647,25 @@ connect_tacacs(struct tac_attrib **attr, int srvr)
return fd;
}
+/*
+ * If all servers have been unresponsive, clear that state, so we try
+ * them all. It might have been transient.
+ */
+static void tac_chk_anyresp(void)
+{
+ int i, anyok=0;
+
+ for(i = 0; i < tac_srv_no; i++) {
+ if (!tac_srv[i].not_resp)
+ anyok++;
+ }
+ if (!anyok) {
+ for(i = 0; i < tac_srv_no; i++)
+ tac_srv[i].not_resp = 0;
+ }
+}
+
+
/*
* lookup the user on a TACACS server. Returns 0 on successful lookup, else 1
@@ -626,12 +710,16 @@ lookup_tacacs_user(struct pwbuf *pb)
return ret;
print_servers();
+ tac_chk_anyresp();
for(srvr=0; srvr < tac_srv_no && !done; srvr++) {
+ if (tac_srv[srvr].not_resp)
+ continue; /* don't retry if previously not responding */
arep.msg = NULL;
arep.attr = NULL;
arep.status = TAC_PLUS_AUTHOR_STATUS_ERROR; /* if author_send fails */
tac_fd = connect_tacacs(&attr, srvr);
if (tac_fd < 0) {
+ tac_srv[srvr].not_resp = 1;
if(debug)
syslog(LOG_WARNING, "%s: failed to connect TACACS+ server %s,"
" ret=%d: %m", nssname, tac_srv[srvr].addr ?
@@ -642,10 +730,10 @@ lookup_tacacs_user(struct pwbuf *pb)
ret = tac_author_send(tac_fd, pb->name, "", tac_rhost, attr);
if(ret < 0) {
if(debug)
- syslog(LOG_WARNING, "%s: TACACS+ server %s authorization failed (%d) "
- " user (%s)", nssname, tac_srv[srvr].addr ?
- tac_ntop(tac_srv[srvr].addr->ai_addr) : "unknown", ret,
- pb->name);
+ syslog(LOG_WARNING, "%s: TACACS+ server %s authorization "
+ "failed (%d) user (%s)", nssname, tac_srv[srvr].addr ?
+ tac_ntop(tac_srv[srvr].addr->ai_addr) : "unknown", ret,
+ pb->name);
}
else {
errno = 0;
@@ -669,12 +757,12 @@ lookup_tacacs_user(struct pwbuf *pb)
arep.status == AUTHOR_STATUS_PASS_REPL) {
ret = got_tacacs_user(arep.attr, pb);
if(debug>1)
- syslog(LOG_DEBUG, "%s: TACACS+ server %s successful for user %s."
+ syslog(LOG_DEBUG, "%s: TACACS+ server %s successful for user %s"
" local lookup %s", nssname,
tac_ntop(tac_srv[srvr].addr->ai_addr), pb->name,
- ret?"OK":"no match");
+ ret?"copy problem":"OK");
else if(debug)
- syslog(LOG_DEBUG, "%s: TACACS+ server %s successful for user %s",
+ syslog(LOG_DEBUG, "%s: TACACS+ server %s success on user %s",
nssname, tac_ntop(tac_srv[srvr].addr->ai_addr), pb->name);
done = 1; /* break out of loop after arep cleanup */
}
@@ -700,13 +788,16 @@ lookup_mapped_uid(struct pwbuf *pb, uid_t uid, uid_t auid, int session)
{
char *loginname, mappedname[256];
uint16_t flag;
+ int ret = 1;
mappedname[0] = '\0';
loginname = lookup_mapuid(uid, auid, session,
mappedname, sizeof mappedname, &flag);
- if(loginname)
- return find_pw_user(loginname, mappedname, pb, flag & MAP_USERHOMEDIR);
- return 1;
+ if(loginname) {
+ ret = find_pw_user(loginname, mappedname, pb, flag & MAP_USERHOMEDIR);
+ free(loginname);
+ }
+ return ret;
}
/*
@@ -727,51 +818,47 @@ enum nss_status _nss_tacplus_getpwnam_r(const char *name, struct passwd *pw,
char *buffer, size_t buflen, int *errnop)
{
enum nss_status status = NSS_STATUS_NOTFOUND;
- int result;
+ int result, lookup;
struct pwbuf pbuf;
- result = nss_tacplus_config(errnop, config_file, 1);
- conf_parsed = result == 0 ? 2 : 1;
-
- get_remote_addr();
+ result = get_config(errnop, 1);
+ if(result)
+ return status;
- if(result) { /* no config file, no servers, etc. */
- /* this is a debug because privileges may not allow access */
- if(debug)
- syslog(LOG_DEBUG, "%s: bad config or server line for nss_tacplus",
- nssname);
+ /* marshal the args for the lower level functions */
+ pbuf.name = (char *)name;
+ pbuf.pw = pw;
+ pbuf.buf = buffer;
+ pbuf.buflen = buflen;
+ pbuf.errnop = errnop;
+
+ lookup = lookup_tacacs_user(&pbuf);
+ if(!lookup) {
+ status = NSS_STATUS_SUCCESS;
+ if (errnop)
+ *errnop = 0;
}
- else {
- int lookup;
-
- /* marshal the args for the lower level functions */
- pbuf.name = (char *)name;
- pbuf.pw = pw;
- pbuf.buf = buffer;
- pbuf.buflen = buflen;
- pbuf.errnop = errnop;
-
- lookup = lookup_tacacs_user(&pbuf);
- if(!lookup)
+ else if(lookup == 1) { /* 2 means exclude_users match */
+ uint16_t flag;
+ /*
+ * If we can't contact a tacacs server (either not configured, or
+ * more likely, we aren't running as root and the config for the
+ * server is not readable by our uid for security reasons), see if
+ * we can find the user via the mapping database, and if so, use
+ * that. This will work for non-root users as long as the requested
+ * name is in use (that is, logged in), which will be the most
+ * common case of wanting to use the original login name by non-root
+ * users.
+ */
+ char *mapname = lookup_mapname(name, -1, -1, NULL, &flag);
+ if(mapname != name && !find_pw_user(name, mapname, &pbuf,
+ flag & MAP_USERHOMEDIR))
status = NSS_STATUS_SUCCESS;
- else if(lookup == 1) { /* 2 means exclude_users match */
- uint16_t flag;
- /*
- * If we can't contact a tacacs server (either not configured, or
- * more likely, we aren't running as root and the config for the
- * server is not readable by our uid for security reasons), see if
- * we can find the user via the mapping database, and if so, use
- * that. This will work for non-root users as long as the requested
- * name is in use (that is, logged in), which will be the most
- * common case of wanting to use the original login name by non-root
- * users.
- */
- char *mapname = lookup_mapname(name, -1, -1, NULL, &flag);
- if(mapname != name && !find_pw_user(name, mapname, &pbuf,
- flag & MAP_USERHOMEDIR))
- status = NSS_STATUS_SUCCESS;
- }
}
+
+ if (status == NSS_STATUS_SUCCESS && errnop)
+ *errnop = 0; /* be sane, no stale errno */
+
return status;
}
@@ -811,12 +898,11 @@ enum nss_status _nss_tacplus_getpwuid_r(uid_t uid, struct passwd *pw,
{
struct pwbuf pb;
enum nss_status status = NSS_STATUS_NOTFOUND;
- int session, ret;
+ int session;
uid_t auid;
- ret = nss_tacplus_config(errnop, config_file, 1);
- conf_parsed = ret == 0 ? 2 : 1;
-
+ (void) get_config(errnop, 1);
+ /* config errors aren't fatal here */
if (min_uid != ~0U && uid < min_uid) {
if(debug > 1)
syslog(LOG_DEBUG, "%s: uid %u < min_uid %u, don't lookup",
@@ -874,3 +960,393 @@ static void get_remote_addr(void)
if(debug > 1 && tac_rhost[0])
syslog(LOG_DEBUG, "%s: rhost=%s", nssname, tac_rhost);
}
+
+/* start of infrastructure for getgr* routines */
+
+static int copyuser(char **grmem, const char *user, long long *blen,
+ int *err)
+{
+ size_t l = strlen(user) + 1;
+ *blen -= l;
+ if (*blen < 0) {
+ *err = ERANGE;
+ return 1;
+ }
+ strcpy(*grmem, user);
+ *grmem += l;
+ return 0;
+}
+
+/*
+* Return a char ** list of strings of usernames from the mapping db
+* that for each username such as tacacs15, looks up that name and
+* replaces it with usernames logged in and mapped to that name,
+* for replacing the gr_mem field for getgrent(), etc.
+* Passed the original gr_mem array straight from the group file.
+* 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.
+*/
+static char **
+fixup_gr_mem(const char *grnam, const char **gr_in, char *buf,
+ size_t *lenp, int *err)
+{
+ int nadded = 0, midx = 0;
+ long long l = 0, len = *lenp; /* size_t unsigned on some systems */
+ const char **in;
+ char **out = NULL, *mem = buf;
+ char **gr_mem;
+ const unsigned align = sizeof(void *) - 1;
+
+ *err = 0;
+
+ /* set up to copy to supplied buffer */
+ l = (((ptrdiff_t)mem + align) & align);
+ len -= align;
+ mem += align;
+ gr_mem = (char **)mem;
+
+ if (!gr_in)
+ goto done;
+
+ /* sort of ugly to have to do this twice, to reserve enough
+ * space in gr_mem, but it's not too expensive, no connects
+ * to a tacacs server.
+ */
+ for (in=gr_in; in && *in; in++) {
+ char *mapnames = lookup_all_mapped(*in);
+ if (mapnames) { /* comma separated list was returned */
+ char *saved, *tok;
+ tok = strtok_r(mapnames, ",", &saved);
+ while (tok) {
+ nadded++;
+ tok = strtok_r(NULL, ",", &saved);
+ }
+ }
+ else
+ nadded++;
+ }
+ l = sizeof *gr_mem * (nadded+1);
+ len -= l;
+ mem += l;
+
+ /* do the copying in this loop */
+ for (in=gr_in; in && *in; in++) {
+ char *mapnames = lookup_all_mapped(*in);
+ if (mapnames) { /* comma separated list was returned */
+ char *saved, *tok;
+ tok = strtok_r(mapnames, ",", &saved);
+ while (tok) {
+ gr_mem[midx] = mem;
+ if(copyuser(&mem, tok, &len, err))
+ goto done;
+ midx++;
+ tok = strtok_r(NULL, ",", &saved);
+ }
+ }
+ else { /* just copy what was returned to the buffer */
+ gr_mem[midx] = mem;
+ if(copyuser(&mem, *in, &len, err))
+ goto done;
+ midx++;
+ }
+ }
+
+ done:
+ gr_mem[midx] = NULL; /* terminate the list */
+
+ if (*err) {
+ syslog(LOG_WARNING, "%s: group %s members truncated due to short"
+ " buffer %lld characters", nssname, grnam, (long long)*lenp);
+ *lenp = 0;
+ } else {
+ out = gr_mem;
+ *lenp = mem - buf;
+ }
+ return out;
+}
+
+/* end of infrastructure for getgr* routines */
+/*
+* 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_tacplus_setgrent(void)
+{
+ enum nss_status status = NSS_STATUS_NOTFOUND;
+ static const char *grpname = "/etc/group";
+ int error, errval;
+
+ error = get_config(&errval, 1);
+ if(error)
+ return errval == 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_tacplus_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;
+ char *nm = entry->gr_name ? entry->gr_name : "(nil)";
+ size_t usedbuf;
+
+ 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 each member in the group, see if it's a tacacs mapped name, and if
+ * so, replace it with the login name(s) mapped to it, if any, otherwise
+ * leave it as is.
+ */
+ for (memlen=members=0, grusr=entry->gr_mem; grusr && *grusr; grusr++) {
+ members++;
+ memlen += strlen(*grusr) + 1;
+ }
+
+ usedbuf = len;
+
+ /* substitute login names (if any) for mapped users */
+ new_grmem = fixup_gr_mem(nm, (const char **)entry->gr_mem,
+ buf, &usedbuf, errp);
+ 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_tacplus_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;
+ int localerr;
+
+ if (!gr_result) {
+ if (errnop)
+ *errnop = EFAULT;
+ return status;
+ }
+
+ ret = get_config(errnop, 1);
+ if(ret)
+ return errnop && *errnop == ENOENT ? NSS_STATUS_UNAVAIL : status;
+
+ if (!grent) {
+ status = _nss_tacplus_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, &localerr);
+ if (errnop)
+ *errnop = localerr;
+ return ret;
+}
+
+__attribute__((visibility("default")))
+enum nss_status _nss_tacplus_getgrnam_r(const char *name, struct group *gr,
+ char *buffer, size_t buflen,
+ int *errnop)
+{
+ enum nss_status status = NSS_STATUS_NOTFOUND;
+ int ret;
+ struct group *ent;
+ int localerr;
+
+ if (!gr) {
+ if (errnop)
+ *errnop = EFAULT;
+ return status;
+ }
+
+ ret = get_config(errnop, 1);
+ if(ret)
+ return errnop && *errnop == ENOENT ? NSS_STATUS_UNAVAIL : status;
+
+ if (_nss_tacplus_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, &localerr);
+ if (errnop)
+ *errnop = localerr;
+ break;
+ }
+ }
+
+ return status;
+}
+
+__attribute__((visibility("default")))
+enum nss_status _nss_tacplus_getgrgid_r(gid_t gid, struct group *gr,
+ char *buffer, size_t buflen,
+ int *errnop)
+{
+ int ret;
+ enum nss_status status = NSS_STATUS_NOTFOUND;
+ struct group *ent;
+ int localerr;
+
+ if (!gr) {
+ if (errnop)
+ *errnop = EFAULT;
+ return status;
+ }
+
+ ret = get_config(errnop, 1);
+ if(ret)
+ return errnop && *errnop == ENOENT ? NSS_STATUS_UNAVAIL : status;
+
+ if (_nss_tacplus_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, &localerr);
+ if (errnop)
+ *errnop = localerr;
+ break;
+ }
+ }
+
+ return status;
+}