summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--debian/changelog20
-rw-r--r--debian/control7
-rw-r--r--debian/copyright5
-rw-r--r--nss_tacplus.c255
4 files changed, 233 insertions, 54 deletions
diff --git a/debian/changelog b/debian/changelog
index d6199b2..6b20592 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,23 @@
+libnss-tacplus (1.0.3-1) unstable; urgency=low
+ * Added config variable "timeout" to limit time attempting to
+ connect to non-responding TACACS server.
+ * Added config variable "exclude_users" in /etc/tacplus_nss
+ to avoid looking up "local" user accounts via TACACS servers. This
+ improves overall system performance for local users, and avoids significant
+ delays when a TACACS server is unreachable.
+ * Fixed issues with ordering of multiple servers and secrets in config files.
+ libraries can connect to a TACACS+ server without being tacacs aware.
+ * Improved debugging messages.
+ * Minor corrections to Copyright and licensing
+ * Added vrf config variable, so NSS lookups work correctly$
+ * During login, send remote add IP address in AUTH request
+ * Configuration files should automatically be reparsed
+ if they change, for long-lived programs and daemons that use NSS.
+ * Added user_homedir config variable to allow per-user
+ home directories (unless per-command authorization is enabled)
+
+ -- Dave Olson <olson@cumulusnetworks.com> Thu, 23 Mar 2017 22:40:01 -0800
+
libnss-tacplus (1.0.2-1) unstable; urgency=low
* Improve debugging on server connections, and always try all
diff --git a/debian/control b/debian/control
index 3d95156..ea65d0b 100644
--- a/debian/control
+++ b/debian/control
@@ -1,14 +1,17 @@
Source: libnss-tacplus
Priority: optional
Maintainer: Dave Olson <olson@cumulusnetworks.com>
-Build-Depends: debhelper (>= 9), autotools-dev, libtac-dev, libtacplus-map-dev, libaudit-dev, autoconf, libpam-tacplus-dev, dpkg-dev (>= 1.16.1)
+Build-Depends: debhelper (>= 9), autotools-dev, libtac-dev (>= 1.4.1~),
+ libtacplus-map-dev, libaudit-dev, autoconf, libpam-tacplus-dev,
+ dpkg-dev (>= 1.16.1), git
Section: libs
Standards-Version: 3.9.6
Homepage: http://www.cumulusnetworks.com
Package: libnss-tacplus
Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, libtac2, libtacplus-map1, libaudit1
+Depends: ${shlibs:Depends}, ${misc:Depends}, libtac2 (>= 1.4.1~),
+ libtacplus-map1, libaudit1
Description: NSS module for TACACS+ authentication without local passwd entry
Performs getpwname and getpwuid lookups via NSS for users logged in via
tacacs authentication, and mapping done with libtacplus_map
diff --git a/debian/copyright b/debian/copyright
index 9b1b34a..710851e 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -3,8 +3,9 @@ Upstream-Name: libnss-tacplus
Source: http://www.cumulusnetworks.com
Files: *
-Copyright: 2015, 2016 Cumulus Networks, Inc. All rights reserved.,
- 2010 Pawel Krawczyk <pawel.krawczyk@hush.com> and Jeroen Nijhof <jeroen@jeroennijhof.nl>
+Copyright: 2015, 2016, 2017 Cumulus Networks, Inc. All rights reserved.,
+ 2010 Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ Jeroen Nijhof <jeroen@jeroennijhof.nl>
License: GPL-2+
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/nss_tacplus.c b/nss_tacplus.c
index 75cbdb7..201b329 100644
--- a/nss_tacplus.c
+++ b/nss_tacplus.c
@@ -1,5 +1,6 @@
/*
- * Copyright (C) 2014, 2015, 2016 Cumulus Networks, Inc. All rights reserved.
+ * Copyright (C) 2014, 2015, 2016, 2017 Cumulus Networks, Inc.
+ * All rights reserved.
* Author: Dave Olson <olson@cumulusnetworks.com>
*
* This program is free software; you can redistribute it and/or modify
@@ -36,6 +37,8 @@
#include <ctype.h>
#include <nss.h>
#include <libaudit.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
#include <tacplus/libtac.h>
#include <tacplus/map_tacplus_user.h>
@@ -59,7 +62,7 @@ struct pwbuf {
typedef struct {
struct addrinfo *addr;
- const char *key;
+ char *key;
} tacplus_server_t;
/* set from configuration file parsing */
@@ -67,27 +70,97 @@ static tacplus_server_t tac_srv[TAC_PLUS_MAXSERVERS];
static int tac_srv_no, tac_key_no;
static char tac_service[] = "shell";
static char tac_protocol[] = "ssh";
+static char tac_rhost[INET6_ADDRSTRLEN];
+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 conf_parsed = 0;
+static void get_remote_addr(void);
+
+#define MAX_INCL 8 /* max config level nesting */
+
+/* reset all config variables when we are going to re-parse */
+static void
+reset_config(void)
+{
+ int i, nservers;
+
+ /* reset the config variables that we use, freeing memory where needed */
+ nservers = tac_srv_no;
+ tac_srv_no = 0;
+ tac_key_no = 0;
+ vrfname[0] = '\0';
+ if(exclude_users[0])
+ (void)free(exclude_users);
+ exclude_users = NULL;
+ debug = 0;
+ use_tachome = 0;
+ tac_timeout = 0;
+ min_uid = ~0U;
+
+ for(i = 0; i < nservers; i++) {
+ if(tac_srv[i].key) {
+ free(tac_srv[i].key);
+ tac_srv[i].key = NULL;
+ }
+ tac_srv[i].addr = NULL;
+ }
+}
+
static int nss_tacplus_config(int *errnop, const char *cfile, int top)
{
FILE *conf;
char lbuf[256];
+ static struct stat lastconf[MAX_INCL];
+ static char *cfilelist[MAX_INCL];
+ struct stat st, *lst;
- if(conf_parsed > 1) /* 1: we've tried and thrown errors, 2, OK */
- return 0;
+ if(top > MAX_INCL) {
+ syslog(LOG_NOTICE, "%s: Config file include depth > %d, ignoring %s",
+ nssname, MAX_INCL, cfile);
+ return 1;
+ }
+ lst = &lastconf[top-1];
+ if(conf_parsed && top == 1) {
+ /*
+ * check to see if the config file(s) have changed since last time,
+ * in case we are part of a long-lived daemon. If any changed,
+ * reparse. If not, return the appropriate status (err or OK)
+ * This is somewhat complicated by the include file mechanism.
+ * When we have nested includes, we have to check all the config
+ * files we saw previously, not just the top level config file.
+ */
+ int i;
+ for(i=0; i < MAX_INCL; i++) {
+ struct stat *cst;
+ cst = &lastconf[i];
+ if(!cst->st_ino || !cfilelist[i]) /* end of files */
+ return conf_parsed == 2 ? 0 : 1;
+ if (stat(cfilelist[i], &st) || st.st_ino != cst->st_ino ||
+ st.st_mtime != cst->st_mtime || st.st_ctime != cst->st_ctime)
+ break; /* found removed or different file, so re-parse */
+ }
+ reset_config();
+ 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 */
+ cfilelist[top-1] = strdup(cfile);
conf = fopen(cfile, "r");
if(conf == NULL) {
*errnop = errno;
if(!conf_parsed && debug) /* debug because privileges may not allow */
syslog(LOG_DEBUG, "%s: can't open config file %s: %m",
nssname, cfile);
- goto err;
+ return 1;
}
+ if (fstat(fileno(conf), lst) != 0)
+ memset(lst, 0, sizeof *lst); /* avoid stale data, no warning */
while(fgets(lbuf, sizeof lbuf, conf)) {
if(*lbuf == '#' || isspace(*lbuf))
@@ -96,13 +169,16 @@ static int nss_tacplus_config(int *errnop, const char *cfile, int top)
if(!strncmp(lbuf, "include=", 8)) {
/*
* allow include files, useful for centralizing tacacs
- * server IP address and secret.
+ * server IP address and secret. When running non-privileged,
+ * may not be able to read one or more config files.
*/
- if(lbuf[8]) /* else treat as empty config, ignoring errors */
+ if(lbuf[8])
(void)nss_tacplus_config(errnop, &lbuf[8], top+1);
}
else if(!strncmp(lbuf, "debug=", 6))
debug = strtoul(lbuf+6, NULL, 0);
+ 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 */
@@ -157,6 +233,8 @@ static int nss_tacplus_config(int *errnop, const char *cfile, int top)
if (valid > (lbuf+8))
min_uid = (uid_t)uid;
}
+ else if(!strncmp(lbuf, "vrf=", 4))
+ strncpy(vrfname, lbuf + 4, sizeof(vrfname));
else if(!strncmp(lbuf, "server=", 7)) {
if(tac_srv_no < TAC_PLUS_MAXSERVERS) {
struct addrinfo hints, *servers, *server;
@@ -203,28 +281,36 @@ static int nss_tacplus_config(int *errnop, const char *cfile, int top)
}
fclose(conf);
- if(top == 1) {
- int n;
- if(tac_srv_no == 0 && debug)
- syslog(LOG_DEBUG, "%s:%s: no TACACS %s in config (or no perm),"
- " giving up",
- nssname, __FUNCTION__, tac_srv_no ? "service" :
- (*tac_service ? "server" : "service and no server"));
-
- for(n = 0; debug && n < tac_srv_no; n++)
- syslog(LOG_DEBUG, "%s: server[%d] { addr=%s, key='%s' }", nssname,
- n, tac_srv[n].addr ? tac_ntop(tac_srv[n].addr->ai_addr)
- : "unknown", tac_srv[n].key);
- }
return 0;
-
-err:
- if(conf)
- fclose(conf);
- return 1;
}
+/*
+ * Separate function so we can print first time we try to connect,
+ * rather than during config.
+ * Don't print at config, because often the uid lookup is one we
+ * skip due to min_uid, so no reason to clutter the log.
+ */
+static void print_servers(void)
+{
+ static int printed = 0;
+ int n;
+
+ if (printed || !debug)
+ return;
+ printed = 1;
+
+ if(tac_srv_no == 0)
+ syslog(LOG_DEBUG, "%s:%s: no TACACS %s in config (or no perm),"
+ " giving up",
+ nssname, __FUNCTION__, tac_srv_no ? "service" :
+ (*tac_service ? "server" : "service and no server"));
+
+ for(n = 0; n < tac_srv_no; n++)
+ syslog(LOG_DEBUG, "%s: server[%d] { addr=%s, key='%s' }", nssname,
+ n, tac_srv[n].addr ? tac_ntop(tac_srv[n].addr->ai_addr)
+ : "unknown", tac_srv[n].key);
+}
/*
* copy a passwd structure and it's strings, using the provided buffer
@@ -236,12 +322,15 @@ err:
*/
static int
pwcopy(char *buf, size_t len, struct passwd *srcpw, struct passwd *destpw,
- const char *usename)
+ const char *usename, uint16_t tachome)
{
- int needlen, cnt;
+ int needlen, cnt, origlen = len;
+ char *shell;
- if(!usename)
+ if(!usename) {
usename = srcpw->pw_name;
+ tachome = 0; /* early lookups; no tachome */
+ }
needlen = usename ? strlen(usename) + 1 : 1 +
srcpw->pw_dir ? strlen(srcpw->pw_dir) + 1 : 1 +
@@ -270,6 +359,14 @@ pwcopy(char *buf, size_t len, struct passwd *srcpw, struct passwd *destpw,
len -= cnt;
cnt = snprintf(buf, len, "%s", srcpw->pw_shell ? srcpw->pw_shell : "");
destpw->pw_shell = buf;
+ shell = strrchr(buf, '/');
+ shell = shell ? shell+1 : buf;
+ 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);
+ }
cnt++;
buf += cnt;
len -= cnt;
@@ -278,11 +375,28 @@ pwcopy(char *buf, size_t len, struct passwd *srcpw, struct passwd *destpw,
cnt++;
buf += cnt;
len -= cnt;
- cnt = snprintf(buf, len, "%s", srcpw->pw_dir ? srcpw->pw_dir : "");
+ if (tachome && usename) {
+ char *slash, dbuf[strlen(srcpw->pw_dir) + strlen(usename)];
+ snprintf(dbuf, sizeof dbuf, "%s", srcpw->pw_dir ? srcpw->pw_dir : "");
+ slash = strrchr(dbuf, '/');
+ if (slash) {
+ slash++;
+ snprintf(slash, sizeof dbuf - (slash-dbuf), "%s", usename);
+ }
+ cnt = snprintf(buf, len, "%s", dbuf);
+ }
+ else
+ 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)
+ syslog(LOG_DEBUG, "%s provided password buffer too small (%ld<%d)",
+ nssname, (long)origlen, origlen-(int)len);
+ return 1;
+ }
return 0;
}
@@ -330,11 +444,11 @@ recheck:
if(!ent->pw_name)
continue; /* shouldn't happen */
if(!strcmp(ent->pw_name, pb->name)) {
- retu = pwcopy(ubuf, sizeof(ubuf), ent, &upw, NULL);
+ retu = pwcopy(ubuf, sizeof(ubuf), ent, &upw, NULL, use_tachome);
matches++;
}
else if(!strcmp(ent->pw_name, tacuser)) {
- rett = pwcopy(tbuf, sizeof(tbuf), ent, &tpw, NULL);
+ rett = pwcopy(tbuf, sizeof(tbuf), ent, &tpw, NULL, use_tachome);
matches++;
}
}
@@ -350,9 +464,11 @@ recheck:
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);
+ ret = pwcopy(pb->buf, pb->buflen, &upw, pb->pw, pb->name,
+ use_tachome);
else if(tpw.pw_name && !rett)
- ret = pwcopy(pb->buf, pb->buflen, &tpw, pb->pw, pb->name);
+ ret = pwcopy(pb->buf, pb->buflen, &tpw, pb->pw, pb->name,
+ use_tachome);
}
if(ret)
*pb->errnop = ERANGE;
@@ -369,7 +485,8 @@ recheck:
* returns 0 on success
*/
static int
-find_pw_user(const char *logname, const char *tacuser, struct pwbuf *pb)
+find_pw_user(const char *logname, const char *tacuser, struct pwbuf *pb,
+ uint16_t usetachome)
{
FILE *pwfile;
struct passwd *ent;
@@ -393,7 +510,7 @@ find_pw_user(const char *logname, const char *tacuser, struct pwbuf *pb)
if(!ent->pw_name)
continue; /* shouldn't happen */
if(!strcmp(ent->pw_name, tacuser)) {
- ret = pwcopy(pb->buf, pb->buflen, ent, pb->pw, logname);
+ ret = pwcopy(pb->buf, pb->buflen, ent, pb->pw, logname, usetachome);
break;
}
}
@@ -451,12 +568,9 @@ connect_tacacs(struct tac_attrib **attr, int srvr)
{
int fd;
- if(!*tac_service) /* reported at config file processing */
- return -1;
-
- 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, NULL,
+ vrfname[0]?vrfname:NULL);
if(fd >= 0) {
- *attr = NULL; /* so tac_add_attr() allocates memory */
tac_add_attrib(attr, "service", tac_service);
if(tac_protocol[0])
tac_add_attrib(attr, "protocol", tac_protocol);
@@ -482,7 +596,7 @@ lookup_tacacs_user(struct pwbuf *pb)
{
struct areply arep;
int ret = 1, done = 0;
- struct tac_attrib *attr;
+ struct tac_attrib *attr = NULL;
int tac_fd, srvr;
if (exclude_users) {
@@ -506,6 +620,10 @@ lookup_tacacs_user(struct pwbuf *pb)
}
}
+ if(!*tac_service) /* reported at config file processing */
+ return ret;
+ print_servers();
+
for(srvr=0; srvr < tac_srv_no && !done; srvr++) {
arep.msg = NULL;
arep.attr = NULL;
@@ -516,13 +634,14 @@ lookup_tacacs_user(struct pwbuf *pb)
syslog(LOG_WARNING, "%s: failed to connect TACACS+ server %s,"
" ret=%d: %m", nssname, tac_srv[srvr].addr ?
tac_ntop(tac_srv[srvr].addr->ai_addr) : "unknown", tac_fd);
+ tac_free_attrib(&attr);
continue;
}
- ret = tac_author_send(tac_fd, pb->name, "", "", attr);
+ ret = tac_author_send(tac_fd, pb->name, "", tac_rhost, attr);
if(ret < 0) {
if(debug)
- syslog(LOG_WARNING, "%s: TACACS+ server %s send failed (%d) for"
- " user %s: %m", nssname, tac_srv[srvr].addr ?
+ 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);
}
@@ -547,11 +666,14 @@ lookup_tacacs_user(struct pwbuf *pb)
if(arep.status == AUTHOR_STATUS_PASS_ADD ||
arep.status == AUTHOR_STATUS_PASS_REPL) {
ret = got_tacacs_user(arep.attr, pb);
- if(debug)
+ if(debug>1)
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");
+ else if(debug)
+ syslog(LOG_DEBUG, "%s: TACACS+ server %s successful for user %s",
+ nssname, tac_ntop(tac_srv[srvr].addr->ai_addr), pb->name);
done = 1; /* break out of loop after arep cleanup */
}
else {
@@ -575,12 +697,13 @@ static int
lookup_mapped_uid(struct pwbuf *pb, uid_t uid, uid_t auid, int session)
{
char *loginname, mappedname[256];
+ uint16_t flag;
mappedname[0] = '\0';
loginname = lookup_mapuid(uid, auid, session,
- mappedname, sizeof mappedname);
+ mappedname, sizeof mappedname, &flag);
if(loginname)
- return find_pw_user(loginname, mappedname, pb);
+ return find_pw_user(loginname, mappedname, pb, flag & MAP_USERHOMEDIR);
return 1;
}
@@ -608,6 +731,8 @@ enum nss_status _nss_tacplus_getpwnam_r(const char *name, struct passwd *pw,
result = nss_tacplus_config(errnop, config_file, 1);
conf_parsed = result == 0 ? 2 : 1;
+ get_remote_addr();
+
if(result) { /* no config file, no servers, etc. */
/* this is a debug because privileges may not allow access */
if(debug)
@@ -628,6 +753,7 @@ enum nss_status _nss_tacplus_getpwnam_r(const char *name, struct passwd *pw,
if(!lookup)
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
@@ -638,8 +764,9 @@ enum nss_status _nss_tacplus_getpwnam_r(const char *name, struct passwd *pw,
* common case of wanting to use the original login name by non-root
* users.
*/
- char *mapname = lookup_mapname(name, -1, -1, NULL);
- if(mapname != name && !find_pw_user(name, mapname, &pbuf))
+ 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;
}
}
@@ -689,7 +816,7 @@ enum nss_status _nss_tacplus_getpwuid_r(uid_t uid, struct passwd *pw,
conf_parsed = ret == 0 ? 2 : 1;
if (min_uid != ~0U && uid < min_uid) {
- if(debug)
+ if(debug > 1)
syslog(LOG_DEBUG, "%s: uid %u < min_uid %u, don't lookup",
nssname, uid, min_uid);
return status;
@@ -717,3 +844,31 @@ enum nss_status _nss_tacplus_getpwuid_r(uid_t uid, struct passwd *pw,
status = NSS_STATUS_SUCCESS;
return status;
}
+
+static void get_remote_addr(void)
+{
+ struct sockaddr_storage addr;
+ socklen_t len = sizeof addr;
+ char ipstr[INET6_ADDRSTRLEN];
+
+ /* This is so we can fill in the rhost field when we talk to the
+ * TACACS+ server, when it's an ssh connection, so sites that refuse
+ * authorization unless from specific IP addresses will get that
+ * information. It's pretty much of a hack, but it works.
+ */
+ if (getpeername(0, (struct sockaddr*)&addr, &len) == -1)
+ return;
+
+ *ipstr = 0;
+ if (addr.ss_family == AF_INET) {
+ struct sockaddr_in *s = (struct sockaddr_in *)&addr;
+ inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr);
+ } else {
+ struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
+ inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr);
+ }
+
+ snprintf(tac_rhost, sizeof tac_rhost, "%s", ipstr);
+ if(debug > 1 && tac_rhost[0])
+ syslog(LOG_DEBUG, "%s: rhost=%s", nssname, tac_rhost);
+}