libtacplus: Allow additional AV pairs to be configured.
* Replace hand-rolled input tokenizer with openpam_readlinev() which supports line continuations and has better quoting and escaping. * Simplify string handling by merging struct clnt_str and struct srvr_str into just struct tac_str. * Each server entry in the configuration file can now have up to 255 AV pairs which will be appended to the ones returned by the server in response to a successful authorization request. This allows nss_tacplus(8) to be used with servers which do not provide identity information beyond confirming the existence of the user. This adds a dependency on libpam, however libtacplus is currently only used by pam_tacplus(8) (which is already always used with libpam) and the very recently added nss_tacplus(8) (which is extremely niche). In the longer term it might be a good idea to split this out into a separate library. MFC after: 1 week Sponsored by: Klara, Inc. Reviewed by: pauamma_gundo.com, markj Differential Revision: https://reviews.freebsd.org/D40285 Relnotes: yes
This commit is contained in:
parent
9247238cc4
commit
21850106fd
7
UPDATING
7
UPDATING
@ -27,6 +27,13 @@ NOTE TO PEOPLE WHO THINK THAT FreeBSD 14.x IS SLOW:
|
|||||||
world, or to merely disable the most expensive debugging functionality
|
world, or to merely disable the most expensive debugging functionality
|
||||||
at runtime, run "ln -s 'abort:false,junk:false' /etc/malloc.conf".)
|
at runtime, run "ln -s 'abort:false,junk:false' /etc/malloc.conf".)
|
||||||
|
|
||||||
|
20230613:
|
||||||
|
Improvements to libtacplus(8) mean that tacplus.conf(5) now
|
||||||
|
follows POSIX shell syntax rules. This may cause TACACS+
|
||||||
|
authentication to fail if the shared secret contains a single
|
||||||
|
quote, double quote, or backslash character which isn't
|
||||||
|
already properly quoted or escaped.
|
||||||
|
|
||||||
20230612:
|
20230612:
|
||||||
Belatedly switch the default nvme block device on x86 from nvd to nda.
|
Belatedly switch the default nvme block device on x86 from nvd to nda.
|
||||||
nda created nvd compatibility links by default, so this should be a
|
nda created nvd compatibility links by default, so this should be a
|
||||||
|
@ -28,7 +28,7 @@ LIB= tacplus
|
|||||||
SRCS= taclib.c
|
SRCS= taclib.c
|
||||||
INCS= taclib.h
|
INCS= taclib.h
|
||||||
CFLAGS+= -Wall
|
CFLAGS+= -Wall
|
||||||
LIBADD= md
|
LIBADD= md pam
|
||||||
SHLIB_MAJOR= 5
|
SHLIB_MAJOR= 5
|
||||||
MAN= libtacplus.3 tacplus.conf.5
|
MAN= libtacplus.3 tacplus.conf.5
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ __FBSDID("$FreeBSD$");
|
|||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <md5.h>
|
#include <md5.h>
|
||||||
@ -47,32 +48,34 @@ __FBSDID("$FreeBSD$");
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
#include <security/openpam.h>
|
||||||
|
|
||||||
#include "taclib_private.h"
|
#include "taclib_private.h"
|
||||||
|
|
||||||
static int add_str_8(struct tac_handle *, u_int8_t *,
|
static int add_str_8(struct tac_handle *, u_int8_t *,
|
||||||
struct clnt_str *);
|
struct tac_str *);
|
||||||
static int add_str_16(struct tac_handle *, u_int16_t *,
|
static int add_str_16(struct tac_handle *, u_int16_t *,
|
||||||
struct clnt_str *);
|
struct tac_str *);
|
||||||
static int protocol_version(int, int, int);
|
static int protocol_version(int, int, int);
|
||||||
static void close_connection(struct tac_handle *);
|
static void close_connection(struct tac_handle *);
|
||||||
static int conn_server(struct tac_handle *);
|
static int conn_server(struct tac_handle *);
|
||||||
static void crypt_msg(struct tac_handle *, struct tac_msg *);
|
static void crypt_msg(struct tac_handle *, struct tac_msg *);
|
||||||
static void *dup_str(struct tac_handle *, const struct srvr_str *,
|
static void *dup_str(struct tac_handle *, const struct tac_str *,
|
||||||
size_t *);
|
size_t *);
|
||||||
static int establish_connection(struct tac_handle *);
|
static int establish_connection(struct tac_handle *);
|
||||||
static void free_str(struct clnt_str *);
|
static void free_str(struct tac_str *);
|
||||||
static void generr(struct tac_handle *, const char *, ...)
|
static void generr(struct tac_handle *, const char *, ...)
|
||||||
__printflike(2, 3);
|
__printflike(2, 3);
|
||||||
static void gen_session_id(struct tac_msg *);
|
static void gen_session_id(struct tac_msg *);
|
||||||
static int get_srvr_end(struct tac_handle *);
|
static int get_srvr_end(struct tac_handle *);
|
||||||
static int get_srvr_str(struct tac_handle *, const char *,
|
static int get_str(struct tac_handle *, const char *,
|
||||||
struct srvr_str *, size_t);
|
struct tac_str *, size_t);
|
||||||
static void init_clnt_str(struct clnt_str *);
|
static void init_str(struct tac_str *);
|
||||||
static void init_srvr_str(struct srvr_str *);
|
|
||||||
static int read_timed(struct tac_handle *, void *, size_t,
|
static int read_timed(struct tac_handle *, void *, size_t,
|
||||||
const struct timeval *);
|
const struct timeval *);
|
||||||
static int recv_msg(struct tac_handle *);
|
static int recv_msg(struct tac_handle *);
|
||||||
static int save_str(struct tac_handle *, struct clnt_str *,
|
static int save_str(struct tac_handle *, struct tac_str *,
|
||||||
const void *, size_t);
|
const void *, size_t);
|
||||||
static int send_msg(struct tac_handle *);
|
static int send_msg(struct tac_handle *);
|
||||||
static int split(char *, char *[], int, char *, size_t);
|
static int split(char *, char *[], int, char *, size_t);
|
||||||
@ -90,7 +93,7 @@ static void create_msg(struct tac_handle *, int, int, int);
|
|||||||
* for the next time.
|
* for the next time.
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
add_str_8(struct tac_handle *h, u_int8_t *fld, struct clnt_str *cs)
|
add_str_8(struct tac_handle *h, u_int8_t *fld, struct tac_str *cs)
|
||||||
{
|
{
|
||||||
u_int16_t len;
|
u_int16_t len;
|
||||||
|
|
||||||
@ -114,7 +117,7 @@ add_str_8(struct tac_handle *h, u_int8_t *fld, struct clnt_str *cs)
|
|||||||
* for the next time.
|
* for the next time.
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
add_str_16(struct tac_handle *h, u_int16_t *fld, struct clnt_str *cs)
|
add_str_16(struct tac_handle *h, u_int16_t *fld, struct tac_str *cs)
|
||||||
{
|
{
|
||||||
size_t len;
|
size_t len;
|
||||||
|
|
||||||
@ -236,7 +239,7 @@ close_connection(struct tac_handle *h)
|
|||||||
static int
|
static int
|
||||||
conn_server(struct tac_handle *h)
|
conn_server(struct tac_handle *h)
|
||||||
{
|
{
|
||||||
const struct tac_server *srvp = &h->servers[h->cur_server];
|
struct tac_server *srvp = &h->servers[h->cur_server];
|
||||||
int flags;
|
int flags;
|
||||||
|
|
||||||
if ((h->fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
|
if ((h->fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
|
||||||
@ -357,7 +360,7 @@ crypt_msg(struct tac_handle *h, struct tac_msg *msg)
|
|||||||
* though they have no content.
|
* though they have no content.
|
||||||
*/
|
*/
|
||||||
static void *
|
static void *
|
||||||
dup_str(struct tac_handle *h, const struct srvr_str *ss, size_t *len)
|
dup_str(struct tac_handle *h, const struct tac_str *ss, size_t *len)
|
||||||
{
|
{
|
||||||
unsigned char *p;
|
unsigned char *p;
|
||||||
|
|
||||||
@ -404,10 +407,10 @@ establish_connection(struct tac_handle *h)
|
|||||||
* Free a client string, obliterating its contents first for security.
|
* Free a client string, obliterating its contents first for security.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
free_str(struct clnt_str *cs)
|
free_str(struct tac_str *cs)
|
||||||
{
|
{
|
||||||
if (cs->data != NULL) {
|
if (cs->data != NULL) {
|
||||||
memset(cs->data, 0, cs->len);
|
memset_s(cs->data, cs->len, 0, cs->len);
|
||||||
free(cs->data);
|
free(cs->data);
|
||||||
cs->data = NULL;
|
cs->data = NULL;
|
||||||
cs->len = 0;
|
cs->len = 0;
|
||||||
@ -458,8 +461,8 @@ get_srvr_end(struct tac_handle *h)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
get_srvr_str(struct tac_handle *h, const char *field,
|
get_str(struct tac_handle *h, const char *field,
|
||||||
struct srvr_str *ss, size_t len)
|
struct tac_str *ss, size_t len)
|
||||||
{
|
{
|
||||||
if (h->srvr_pos + len > ntohl(h->response.length)) {
|
if (h->srvr_pos + len > ntohl(h->response.length)) {
|
||||||
generr(h, "Invalid length field in %s response from server "
|
generr(h, "Invalid length field in %s response from server "
|
||||||
@ -474,19 +477,12 @@ get_srvr_str(struct tac_handle *h, const char *field,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
init_clnt_str(struct clnt_str *cs)
|
init_str(struct tac_str *cs)
|
||||||
{
|
{
|
||||||
cs->data = NULL;
|
cs->data = NULL;
|
||||||
cs->len = 0;
|
cs->len = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
init_srvr_str(struct srvr_str *ss)
|
|
||||||
{
|
|
||||||
ss->data = NULL;
|
|
||||||
ss->len = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
read_timed(struct tac_handle *h, void *buf, size_t len,
|
read_timed(struct tac_handle *h, void *buf, size_t len,
|
||||||
const struct timeval *deadline)
|
const struct timeval *deadline)
|
||||||
@ -599,7 +595,7 @@ recv_msg(struct tac_handle *h)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
save_str(struct tac_handle *h, struct clnt_str *cs, const void *data,
|
save_str(struct tac_handle *h, struct tac_str *cs, const void *data,
|
||||||
size_t len)
|
size_t len)
|
||||||
{
|
{
|
||||||
free_str(cs);
|
free_str(cs);
|
||||||
@ -689,84 +685,14 @@ send_msg(struct tac_handle *h)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Destructively split a string into fields separated by white space.
|
|
||||||
* `#' at the beginning of a field begins a comment that extends to the
|
|
||||||
* end of the string. Fields may be quoted with `"'. Inside quoted
|
|
||||||
* strings, the backslash escapes `\"' and `\\' are honored.
|
|
||||||
*
|
|
||||||
* Pointers to up to the first maxfields fields are stored in the fields
|
|
||||||
* array. Missing fields get NULL pointers.
|
|
||||||
*
|
|
||||||
* The return value is the actual number of fields parsed, and is always
|
|
||||||
* <= maxfields.
|
|
||||||
*
|
|
||||||
* On a syntax error, places a message in the msg string, and returns -1.
|
|
||||||
*/
|
|
||||||
static int
|
static int
|
||||||
split(char *str, char *fields[], int maxfields, char *msg, size_t msglen)
|
tac_add_server_av(struct tac_handle *h, const char *host, int port,
|
||||||
{
|
const char *secret, int timeout, int flags, const char *const *avs)
|
||||||
char *p;
|
|
||||||
int i;
|
|
||||||
static const char ws[] = " \t";
|
|
||||||
|
|
||||||
for (i = 0; i < maxfields; i++)
|
|
||||||
fields[i] = NULL;
|
|
||||||
p = str;
|
|
||||||
i = 0;
|
|
||||||
while (*p != '\0') {
|
|
||||||
p += strspn(p, ws);
|
|
||||||
if (*p == '#' || *p == '\0')
|
|
||||||
break;
|
|
||||||
if (i >= maxfields) {
|
|
||||||
snprintf(msg, msglen, "line has too many fields");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (*p == '"') {
|
|
||||||
char *dst;
|
|
||||||
|
|
||||||
dst = ++p;
|
|
||||||
fields[i] = dst;
|
|
||||||
while (*p != '"') {
|
|
||||||
if (*p == '\\') {
|
|
||||||
p++;
|
|
||||||
if (*p != '"' && *p != '\\' &&
|
|
||||||
*p != '\0') {
|
|
||||||
snprintf(msg, msglen,
|
|
||||||
"invalid `\\' escape");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (*p == '\0') {
|
|
||||||
snprintf(msg, msglen,
|
|
||||||
"unterminated quoted string");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
*dst++ = *p++;
|
|
||||||
}
|
|
||||||
*dst = '\0';
|
|
||||||
p++;
|
|
||||||
if (*p != '\0' && strspn(p, ws) == 0) {
|
|
||||||
snprintf(msg, msglen, "quoted string not"
|
|
||||||
" followed by white space");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fields[i] = p;
|
|
||||||
p += strcspn(p, ws);
|
|
||||||
if (*p != '\0')
|
|
||||||
*p++ = '\0';
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
tac_add_server(struct tac_handle *h, const char *host, int port,
|
|
||||||
const char *secret, int timeout, int flags)
|
|
||||||
{
|
{
|
||||||
struct tac_server *srvp;
|
struct tac_server *srvp;
|
||||||
|
const char *p;
|
||||||
|
size_t len;
|
||||||
|
int i;
|
||||||
|
|
||||||
if (h->num_servers >= MAXSERVERS) {
|
if (h->num_servers >= MAXSERVERS) {
|
||||||
generr(h, "Too many TACACS+ servers specified");
|
generr(h, "Too many TACACS+ servers specified");
|
||||||
@ -792,8 +718,46 @@ tac_add_server(struct tac_handle *h, const char *host, int port,
|
|||||||
return -1;
|
return -1;
|
||||||
srvp->timeout = timeout;
|
srvp->timeout = timeout;
|
||||||
srvp->flags = flags;
|
srvp->flags = flags;
|
||||||
|
srvp->navs = 0;
|
||||||
|
for (i = 0; avs[i] != NULL; i++) {
|
||||||
|
if (i >= MAXAVPAIRS) {
|
||||||
|
generr(h, "too many AV pairs");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
for (p = avs[i], len = 0; is_arg(*p); p++)
|
||||||
|
len++;
|
||||||
|
if (p == avs[i] || *p != '=') {
|
||||||
|
generr(h, "invalid AV pair %d", i);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
while (*p++ != '\0')
|
||||||
|
len++;
|
||||||
|
if ((srvp->avs[i].data = xstrdup(h, avs[i])) == NULL)
|
||||||
|
goto fail;
|
||||||
|
srvp->avs[i].len = len;
|
||||||
|
srvp->navs++;
|
||||||
|
}
|
||||||
h->num_servers++;
|
h->num_servers++;
|
||||||
return 0;
|
return 0;
|
||||||
|
fail:
|
||||||
|
memset_s(srvp->secret, strlen(srvp->secret), 0, strlen(srvp->secret));
|
||||||
|
free(srvp->secret);
|
||||||
|
srvp->secret = NULL;
|
||||||
|
for (i = 0; i < srvp->navs; i++) {
|
||||||
|
free(srvp->avs[i].data);
|
||||||
|
srvp->avs[i].data = NULL;
|
||||||
|
srvp->avs[i].len = 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
tac_add_server(struct tac_handle *h, const char *host, int port,
|
||||||
|
const char *secret, int timeout, int flags)
|
||||||
|
{
|
||||||
|
const char *const *avs = { NULL };
|
||||||
|
|
||||||
|
return tac_add_server_av(h, host, port, secret, timeout, flags, avs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -821,12 +785,22 @@ tac_close(struct tac_handle *h)
|
|||||||
free(h);
|
free(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
freev(char **fields, int nfields)
|
||||||
|
{
|
||||||
|
if (fields != NULL) {
|
||||||
|
while (nfields-- > 0)
|
||||||
|
free(fields[nfields]);
|
||||||
|
free(fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
tac_config(struct tac_handle *h, const char *path)
|
tac_config(struct tac_handle *h, const char *path)
|
||||||
{
|
{
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
char buf[MAXCONFLINE];
|
char **fields;
|
||||||
int linenum;
|
int linenum, nfields;
|
||||||
int retval;
|
int retval;
|
||||||
|
|
||||||
if (path == NULL)
|
if (path == NULL)
|
||||||
@ -836,46 +810,23 @@ tac_config(struct tac_handle *h, const char *path)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
linenum = 0;
|
linenum = nfields = 0;
|
||||||
while (fgets(buf, sizeof buf, fp) != NULL) {
|
fields = NULL;
|
||||||
int len;
|
while ((fields = openpam_readlinev(fp, &linenum, &nfields)) != NULL) {
|
||||||
char *fields[4];
|
|
||||||
int nfields;
|
|
||||||
char msg[ERRSIZE];
|
|
||||||
char *host, *res;
|
char *host, *res;
|
||||||
char *port_str;
|
char *port_str;
|
||||||
char *secret;
|
char *secret;
|
||||||
char *timeout_str;
|
char *timeout_str;
|
||||||
char *options_str;
|
|
||||||
char *end;
|
char *end;
|
||||||
unsigned long timeout;
|
unsigned long timeout;
|
||||||
int port;
|
int port;
|
||||||
int options;
|
int options;
|
||||||
|
int i;
|
||||||
|
|
||||||
linenum++;
|
if (nfields == 0) {
|
||||||
len = strlen(buf);
|
freev(fields, nfields);
|
||||||
/* We know len > 0, else fgets would have returned NULL. */
|
|
||||||
if (buf[len - 1] != '\n') {
|
|
||||||
if (len >= sizeof buf - 1)
|
|
||||||
generr(h, "%s:%d: line too long", path,
|
|
||||||
linenum);
|
|
||||||
else
|
|
||||||
generr(h, "%s:%d: missing newline", path,
|
|
||||||
linenum);
|
|
||||||
retval = -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
buf[len - 1] = '\0';
|
|
||||||
|
|
||||||
/* Extract the fields from the line. */
|
|
||||||
nfields = split(buf, fields, 4, msg, sizeof msg);
|
|
||||||
if (nfields == -1) {
|
|
||||||
generr(h, "%s:%d: %s", path, linenum, msg);
|
|
||||||
retval = -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (nfields == 0)
|
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
if (nfields < 2) {
|
if (nfields < 2) {
|
||||||
generr(h, "%s:%d: missing shared secret", path,
|
generr(h, "%s:%d: missing shared secret", path,
|
||||||
linenum);
|
linenum);
|
||||||
@ -884,8 +835,6 @@ tac_config(struct tac_handle *h, const char *path)
|
|||||||
}
|
}
|
||||||
host = fields[0];
|
host = fields[0];
|
||||||
secret = fields[1];
|
secret = fields[1];
|
||||||
timeout_str = fields[2];
|
|
||||||
options_str = fields[3];
|
|
||||||
|
|
||||||
/* Parse and validate the fields. */
|
/* Parse and validate the fields. */
|
||||||
res = host;
|
res = host;
|
||||||
@ -901,7 +850,10 @@ tac_config(struct tac_handle *h, const char *path)
|
|||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
port = 0;
|
port = 0;
|
||||||
if (timeout_str != NULL) {
|
i = 2;
|
||||||
|
if (nfields > i && strlen(fields[i]) > 0 &&
|
||||||
|
strspn(fields[i], "0123456789") == strlen(fields[i])) {
|
||||||
|
timeout_str = fields[i];
|
||||||
timeout = strtoul(timeout_str, &end, 10);
|
timeout = strtoul(timeout_str, &end, 10);
|
||||||
if (timeout_str[0] == '\0' || *end != '\0') {
|
if (timeout_str[0] == '\0' || *end != '\0') {
|
||||||
generr(h, "%s:%d: invalid timeout", path,
|
generr(h, "%s:%d: invalid timeout", path,
|
||||||
@ -909,22 +861,17 @@ tac_config(struct tac_handle *h, const char *path)
|
|||||||
retval = -1;
|
retval = -1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
i++;
|
||||||
} else
|
} else
|
||||||
timeout = TIMEOUT;
|
timeout = TIMEOUT;
|
||||||
options = 0;
|
options = 0;
|
||||||
if (options_str != NULL) {
|
if (nfields > i &&
|
||||||
if (strcmp(options_str, "single-connection") == 0)
|
strcmp(fields[i], "single-connection") == 0) {
|
||||||
options |= TAC_SRVR_SINGLE_CONNECT;
|
options |= TAC_SRVR_SINGLE_CONNECT;
|
||||||
else {
|
i++;
|
||||||
generr(h, "%s:%d: invalid option \"%s\"",
|
}
|
||||||
path, linenum, options_str);
|
if (tac_add_server_av(h, host, port, secret, timeout,
|
||||||
retval = -1;
|
options, (const char *const *)(fields + i)) == -1) {
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (tac_add_server(h, host, port, secret, timeout,
|
|
||||||
options) == -1) {
|
|
||||||
char msg[ERRSIZE];
|
char msg[ERRSIZE];
|
||||||
|
|
||||||
strcpy(msg, h->errmsg);
|
strcpy(msg, h->errmsg);
|
||||||
@ -932,9 +879,10 @@ tac_config(struct tac_handle *h, const char *path)
|
|||||||
retval = -1;
|
retval = -1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
memset_s(secret, strlen(secret), 0, strlen(secret));
|
||||||
|
freev(fields, nfields);
|
||||||
}
|
}
|
||||||
/* Clear out the buffer to wipe a possible copy of a shared secret */
|
freev(fields, nfields);
|
||||||
memset(buf, 0, sizeof buf);
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
@ -1040,17 +988,17 @@ tac_open(void)
|
|||||||
h->num_servers = 0;
|
h->num_servers = 0;
|
||||||
h->cur_server = 0;
|
h->cur_server = 0;
|
||||||
h->errmsg[0] = '\0';
|
h->errmsg[0] = '\0';
|
||||||
init_clnt_str(&h->user);
|
init_str(&h->user);
|
||||||
init_clnt_str(&h->port);
|
init_str(&h->port);
|
||||||
init_clnt_str(&h->rem_addr);
|
init_str(&h->rem_addr);
|
||||||
init_clnt_str(&h->data);
|
init_str(&h->data);
|
||||||
init_clnt_str(&h->user_msg);
|
init_str(&h->user_msg);
|
||||||
for (i=0; i<MAXAVPAIRS; i++) {
|
for (i=0; i<MAXAVPAIRS; i++) {
|
||||||
init_clnt_str(&(h->avs[i]));
|
init_str(&(h->avs[i]));
|
||||||
init_srvr_str(&(h->srvr_avs[i]));
|
init_str(&(h->srvr_avs[i]));
|
||||||
}
|
}
|
||||||
init_srvr_str(&h->srvr_msg);
|
init_str(&h->srvr_msg);
|
||||||
init_srvr_str(&h->srvr_data);
|
init_str(&h->srvr_data);
|
||||||
}
|
}
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
@ -1093,8 +1041,8 @@ tac_send_authen(struct tac_handle *h)
|
|||||||
/* Scan the optional fields in the reply. */
|
/* Scan the optional fields in the reply. */
|
||||||
ar = &h->response.u.authen_reply;
|
ar = &h->response.u.authen_reply;
|
||||||
h->srvr_pos = offsetof(struct tac_authen_reply, rest[0]);
|
h->srvr_pos = offsetof(struct tac_authen_reply, rest[0]);
|
||||||
if (get_srvr_str(h, "msg", &h->srvr_msg, ntohs(ar->msg_len)) == -1 ||
|
if (get_str(h, "msg", &h->srvr_msg, ntohs(ar->msg_len)) == -1 ||
|
||||||
get_srvr_str(h, "data", &h->srvr_data, ntohs(ar->data_len)) == -1 ||
|
get_str(h, "data", &h->srvr_data, ntohs(ar->data_len)) == -1 ||
|
||||||
get_srvr_end(h) == -1)
|
get_srvr_end(h) == -1)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
@ -1114,6 +1062,7 @@ tac_send_author(struct tac_handle *h)
|
|||||||
char dbgstr[64];
|
char dbgstr[64];
|
||||||
struct tac_author_request *areq = &h->request.u.author_request;
|
struct tac_author_request *areq = &h->request.u.author_request;
|
||||||
struct tac_author_response *ares = &h->response.u.author_response;
|
struct tac_author_response *ares = &h->response.u.author_response;
|
||||||
|
struct tac_server *srvp;
|
||||||
|
|
||||||
h->request.length =
|
h->request.length =
|
||||||
htonl(offsetof(struct tac_author_request, rest[0]));
|
htonl(offsetof(struct tac_author_request, rest[0]));
|
||||||
@ -1147,23 +1096,25 @@ tac_send_author(struct tac_handle *h)
|
|||||||
/* Send the message and retrieve the reply. */
|
/* Send the message and retrieve the reply. */
|
||||||
if (send_msg(h) == -1 || recv_msg(h) == -1)
|
if (send_msg(h) == -1 || recv_msg(h) == -1)
|
||||||
return -1;
|
return -1;
|
||||||
|
srvp = &h->servers[h->cur_server];
|
||||||
|
|
||||||
/* Update the offset in the response packet based on av pairs count */
|
/* Update the offset in the response packet based on av pairs count */
|
||||||
h->srvr_pos = offsetof(struct tac_author_response, rest[0]) +
|
h->srvr_pos = offsetof(struct tac_author_response, rest[0]) +
|
||||||
ares->av_cnt;
|
ares->av_cnt;
|
||||||
|
|
||||||
/* Scan the optional fields in the response. */
|
/* Scan the optional fields in the response. */
|
||||||
if (get_srvr_str(h, "msg", &h->srvr_msg, ntohs(ares->msg_len)) == -1 ||
|
if (get_str(h, "msg", &h->srvr_msg, ntohs(ares->msg_len)) == -1 ||
|
||||||
get_srvr_str(h, "data", &h->srvr_data, ntohs(ares->data_len)) ==-1)
|
get_str(h, "data", &h->srvr_data, ntohs(ares->data_len)) ==-1)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* Get each AV pair (just setting pointers, not malloc'ing) */
|
/* Get each AV pair (just setting pointers, not malloc'ing) */
|
||||||
clear_srvr_avs(h);
|
clear_srvr_avs(h);
|
||||||
for (i=0; i<ares->av_cnt; i++) {
|
for (i=0; i<ares->av_cnt; i++) {
|
||||||
snprintf(dbgstr, sizeof dbgstr, "av-pair-%d", i);
|
snprintf(dbgstr, sizeof dbgstr, "av-pair-%d", i);
|
||||||
if (get_srvr_str(h, dbgstr, &(h->srvr_avs[i]),
|
if (get_str(h, dbgstr, &(h->srvr_avs[i]),
|
||||||
ares->rest[i]) == -1)
|
ares->rest[i]) == -1)
|
||||||
return -1;
|
return -1;
|
||||||
|
h->srvr_navs++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Should have ended up at the end */
|
/* Should have ended up at the end */
|
||||||
@ -1174,7 +1125,7 @@ tac_send_author(struct tac_handle *h)
|
|||||||
if (!h->single_connect)
|
if (!h->single_connect)
|
||||||
close_connection(h);
|
close_connection(h);
|
||||||
|
|
||||||
return ares->av_cnt << 8 | ares->status;
|
return (h->srvr_navs + srvp->navs) << 8 | ares->status;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
@ -1208,8 +1159,8 @@ tac_send_acct(struct tac_handle *h)
|
|||||||
|
|
||||||
/* reply */
|
/* reply */
|
||||||
h->srvr_pos = offsetof(struct tac_acct_reply, rest[0]);
|
h->srvr_pos = offsetof(struct tac_acct_reply, rest[0]);
|
||||||
if (get_srvr_str(h, "msg", &h->srvr_msg, ntohs(ar->msg_len)) == -1 ||
|
if (get_str(h, "msg", &h->srvr_msg, ntohs(ar->msg_len)) == -1 ||
|
||||||
get_srvr_str(h, "data", &h->srvr_data, ntohs(ar->data_len)) == -1 ||
|
get_str(h, "data", &h->srvr_data, ntohs(ar->data_len)) == -1 ||
|
||||||
get_srvr_end(h) == -1)
|
get_srvr_end(h) == -1)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
@ -1272,41 +1223,41 @@ tac_set_av(struct tac_handle *h, u_int index, const char *av)
|
|||||||
char *
|
char *
|
||||||
tac_get_av(struct tac_handle *h, u_int index)
|
tac_get_av(struct tac_handle *h, u_int index)
|
||||||
{
|
{
|
||||||
if (index >= MAXAVPAIRS)
|
struct tac_server *srvp;
|
||||||
return NULL;
|
|
||||||
return dup_str(h, &(h->srvr_avs[index]), NULL);
|
if (index < h->srvr_navs)
|
||||||
|
return dup_str(h, &h->srvr_avs[index], NULL);
|
||||||
|
index -= h->srvr_navs;
|
||||||
|
srvp = &h->servers[h->cur_server];
|
||||||
|
if (index < srvp->navs)
|
||||||
|
return xstrdup(h, srvp->avs[index].data);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *
|
char *
|
||||||
tac_get_av_value(struct tac_handle *h, const char *attribute)
|
tac_get_av_value(struct tac_handle *h, const char *attribute)
|
||||||
{
|
{
|
||||||
int i, len;
|
int i, attr_len;
|
||||||
const char *ch, *end;
|
|
||||||
const char *candidate;
|
|
||||||
int candidate_len;
|
|
||||||
int found_seperator;
|
int found_seperator;
|
||||||
struct srvr_str srvr;
|
char *ch, *end;
|
||||||
|
struct tac_str *candidate;
|
||||||
|
struct tac_str value;
|
||||||
|
struct tac_server *srvp = &h->servers[h->cur_server];
|
||||||
|
|
||||||
if (attribute == NULL || ((len = strlen(attribute)) == 0))
|
if (attribute == NULL || (attr_len = strlen(attribute)) == 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
for (i=0; i<MAXAVPAIRS; i++) {
|
for (i = 0; i < h->srvr_navs + srvp->navs; i++) {
|
||||||
candidate = h->srvr_avs[i].data;
|
if (i < h->srvr_navs)
|
||||||
candidate_len = h->srvr_avs[i].len;
|
candidate = &h->srvr_avs[i];
|
||||||
|
else
|
||||||
|
candidate = &srvp->avs[i - h->srvr_navs];
|
||||||
|
|
||||||
/*
|
if (attr_len < candidate->len &&
|
||||||
* Valid 'srvr_avs' guaranteed to be contiguous starting at
|
strncmp(candidate->data, attribute, attr_len) == 0) {
|
||||||
* index 0 (not necessarily the case with 'avs'). Break out
|
|
||||||
* when the "end" of the list has been reached.
|
|
||||||
*/
|
|
||||||
if (!candidate)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (len < candidate_len &&
|
ch = candidate->data + attr_len;
|
||||||
!strncmp(candidate, attribute, len)) {
|
end = candidate->data + candidate->len;
|
||||||
|
|
||||||
ch = candidate + len;
|
|
||||||
end = candidate + candidate_len;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Sift out the white space between A and V (should not
|
* Sift out the white space between A and V (should not
|
||||||
@ -1333,9 +1284,9 @@ tac_get_av_value(struct tac_handle *h, const char *attribute)
|
|||||||
* dup_str() will handle srvr.len == 0 correctly.
|
* dup_str() will handle srvr.len == 0 correctly.
|
||||||
*/
|
*/
|
||||||
if (found_seperator == 1) {
|
if (found_seperator == 1) {
|
||||||
srvr.len = end - ch;
|
value.len = end - ch;
|
||||||
srvr.data = ch;
|
value.data = ch;
|
||||||
return dup_str(h, &srvr, NULL);
|
return dup_str(h, &value, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1354,8 +1305,10 @@ static void
|
|||||||
clear_srvr_avs(struct tac_handle *h)
|
clear_srvr_avs(struct tac_handle *h)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
for (i=0; i<MAXAVPAIRS; i++)
|
|
||||||
init_srvr_str(&(h->srvr_avs[i]));
|
for (i = 0; i < h->srvr_navs; i++)
|
||||||
|
init_str(&(h->srvr_avs[i]));
|
||||||
|
h->srvr_navs = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,30 +60,8 @@
|
|||||||
#define TAC_UNENCRYPTED 0x01
|
#define TAC_UNENCRYPTED 0x01
|
||||||
#define TAC_SINGLE_CONNECT 0x04
|
#define TAC_SINGLE_CONNECT 0x04
|
||||||
|
|
||||||
struct tac_server {
|
struct tac_str {
|
||||||
struct sockaddr_in addr; /* Address of server */
|
char *data;
|
||||||
char *secret; /* Shared secret */
|
|
||||||
int timeout; /* Timeout in seconds */
|
|
||||||
int flags;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* An optional string of bytes specified by the client for inclusion in
|
|
||||||
* a request. The data is always a dynamically allocated copy that
|
|
||||||
* belongs to the library. It is copied into the request packet just
|
|
||||||
* before sending the request.
|
|
||||||
*/
|
|
||||||
struct clnt_str {
|
|
||||||
void *data;
|
|
||||||
size_t len;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* An optional string of bytes from a server response. The data resides
|
|
||||||
* in the response packet itself, and must not be freed.
|
|
||||||
*/
|
|
||||||
struct srvr_str {
|
|
||||||
const void *data;
|
|
||||||
size_t len;
|
size_t len;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -173,6 +151,15 @@ struct tac_msg {
|
|||||||
} u;
|
} u;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct tac_server {
|
||||||
|
struct sockaddr_in addr; /* Address of server */
|
||||||
|
char *secret; /* Shared secret */
|
||||||
|
int timeout; /* Timeout in seconds */
|
||||||
|
int flags;
|
||||||
|
unsigned int navs;
|
||||||
|
struct tac_str avs[MAXAVPAIRS];
|
||||||
|
};
|
||||||
|
|
||||||
struct tac_handle {
|
struct tac_handle {
|
||||||
int fd; /* Socket file descriptor */
|
int fd; /* Socket file descriptor */
|
||||||
struct tac_server servers[MAXSERVERS]; /* Servers to contact */
|
struct tac_server servers[MAXSERVERS]; /* Servers to contact */
|
||||||
@ -182,20 +169,30 @@ struct tac_handle {
|
|||||||
int last_seq_no;
|
int last_seq_no;
|
||||||
char errmsg[ERRSIZE]; /* Most recent error message */
|
char errmsg[ERRSIZE]; /* Most recent error message */
|
||||||
|
|
||||||
struct clnt_str user;
|
struct tac_str user;
|
||||||
struct clnt_str port;
|
struct tac_str port;
|
||||||
struct clnt_str rem_addr;
|
struct tac_str rem_addr;
|
||||||
struct clnt_str data;
|
struct tac_str data;
|
||||||
struct clnt_str user_msg;
|
struct tac_str user_msg;
|
||||||
struct clnt_str avs[MAXAVPAIRS];
|
struct tac_str avs[MAXAVPAIRS];
|
||||||
|
|
||||||
struct tac_msg request;
|
struct tac_msg request;
|
||||||
struct tac_msg response;
|
struct tac_msg response;
|
||||||
|
|
||||||
int srvr_pos; /* Scan position in response body */
|
int srvr_pos; /* Scan position in response body */
|
||||||
struct srvr_str srvr_msg;
|
unsigned int srvr_navs;
|
||||||
struct srvr_str srvr_data;
|
struct tac_str srvr_msg;
|
||||||
struct srvr_str srvr_avs[MAXAVPAIRS];
|
struct tac_str srvr_data;
|
||||||
|
struct tac_str srvr_avs[MAXAVPAIRS];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define is_alpha(ch) /* alphabetical */ \
|
||||||
|
(((ch) >= 'A' && (ch) <= 'Z') || ((ch) >= 'a' && (ch) <= 'z'))
|
||||||
|
#define is_num(ch) /* numerical */ \
|
||||||
|
((ch) >= '0' && (ch) <= '9')
|
||||||
|
#define is_alnum(ch) /* alphanumerical */ \
|
||||||
|
(is_alpha(ch) || is_num(ch))
|
||||||
|
#define is_arg(ch) /* valid in an argument name */ \
|
||||||
|
(is_alnum(ch) || (ch) == '_' || (ch) == '-')
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" $FreeBSD$
|
.\" $FreeBSD$
|
||||||
.\"
|
.\"
|
||||||
.Dd July 29, 1998
|
.Dd June 13, 2023
|
||||||
.Dt TACPLUS.CONF 5
|
.Dt TACPLUS.CONF 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@ -46,23 +46,9 @@ Leading
|
|||||||
white space is ignored, as are empty lines and lines containing
|
white space is ignored, as are empty lines and lines containing
|
||||||
only comments.
|
only comments.
|
||||||
.Pp
|
.Pp
|
||||||
A TACACS+ server is described by two to four fields on a line.
|
A TACACS+ server is described by a minimum of two fields on a line.
|
||||||
The
|
The fields are separated by whitespace and follow the same rules for
|
||||||
fields are separated by white space.
|
comments, quoting, escaping, and line continuation as the POSIX shell.
|
||||||
The
|
|
||||||
.Ql #
|
|
||||||
character at the beginning of a field begins a comment, which extends
|
|
||||||
to the end of the line.
|
|
||||||
A field may be enclosed in double quotes,
|
|
||||||
in which case it may contain white space and/or begin with the
|
|
||||||
.Ql #
|
|
||||||
character.
|
|
||||||
Within a quoted string, the double quote character can
|
|
||||||
be represented by
|
|
||||||
.Ql \e\&" ,
|
|
||||||
and the backslash can be represented by
|
|
||||||
.Ql \e\e .
|
|
||||||
No other escape sequences are supported.
|
|
||||||
.Pp
|
.Pp
|
||||||
The first field specifies
|
The first field specifies
|
||||||
the server host, either as a fully qualified domain name or as a
|
the server host, either as a fully qualified domain name or as a
|
||||||
@ -83,12 +69,11 @@ An empty secret disables the
|
|||||||
normal encryption mechanism, causing all data to cross the network in
|
normal encryption mechanism, causing all data to cross the network in
|
||||||
cleartext.
|
cleartext.
|
||||||
.Pp
|
.Pp
|
||||||
The third field contains a decimal integer specifying the timeout
|
The optional third field may contain a decimal integer specifying the
|
||||||
in seconds for communicating with the server.
|
timeout in seconds for communicating with the server.
|
||||||
The timeout applies
|
The timeout applies
|
||||||
separately to each connect, write, and read operation.
|
separately to each connect, write, and read operation.
|
||||||
If this field
|
If this field is omitted, it defaults to 3 seconds.
|
||||||
is omitted, it defaults to 3 seconds.
|
|
||||||
.Pp
|
.Pp
|
||||||
The optional fourth field may contain the string
|
The optional fourth field may contain the string
|
||||||
.Ql single-connection .
|
.Ql single-connection .
|
||||||
@ -98,6 +83,11 @@ sessions.
|
|||||||
Some older TACACS+ servers become confused if this option
|
Some older TACACS+ servers become confused if this option
|
||||||
is specified.
|
is specified.
|
||||||
.Pp
|
.Pp
|
||||||
|
Any subsequent fields must be of the form
|
||||||
|
.Ar attribute Ns = Ns Ar value
|
||||||
|
and will be appended to authorization responses as if they had been
|
||||||
|
sent by the server.
|
||||||
|
.Pp
|
||||||
Up to 10 TACACS+ servers may be specified.
|
Up to 10 TACACS+ servers may be specified.
|
||||||
The servers are tried in
|
The servers are tried in
|
||||||
order, until a valid response is received or the list is exhausted.
|
order, until a valid response is received or the list is exhausted.
|
||||||
@ -120,11 +110,13 @@ shared secrets, it should not be readable except by root.
|
|||||||
tacserver.domain.com OurLittleSecret
|
tacserver.domain.com OurLittleSecret
|
||||||
|
|
||||||
# A server using a non-standard port, with an increased timeout and
|
# A server using a non-standard port, with an increased timeout and
|
||||||
# the "single-connection" option.
|
# the "single-connection" option, and overrides for the for uid, gid
|
||||||
auth.domain.com:4333 "Don't tell!!" 15 single-connection
|
# and shell attributes.
|
||||||
|
auth.domain.com:4333 "Don't tell!!" 15 single-connection \e
|
||||||
|
uid=1001 gid=20 shell="/usr/local/bin/zsh"
|
||||||
|
|
||||||
# A server specified by its IP address:
|
# A server specified by its IP address:
|
||||||
192.168.27.81 $X*#..38947ax-+=
|
192.168.27.81 $X*#..38947ax-+= shell="/sbin/nologin"
|
||||||
.Ed
|
.Ed
|
||||||
.Sh SEE ALSO
|
.Sh SEE ALSO
|
||||||
.Xr libtacplus 3
|
.Xr libtacplus 3
|
||||||
|
@ -402,7 +402,7 @@ _DP_c+= ssp_nonshared
|
|||||||
.endif
|
.endif
|
||||||
_DP_stats= sbuf pthread
|
_DP_stats= sbuf pthread
|
||||||
_DP_stdthreads= pthread
|
_DP_stdthreads= pthread
|
||||||
_DP_tacplus= md
|
_DP_tacplus= md pam
|
||||||
_DP_ncursesw= tinfow
|
_DP_ncursesw= tinfow
|
||||||
_DP_formw= ncursesw
|
_DP_formw= ncursesw
|
||||||
_DP_nvpair= spl
|
_DP_nvpair= spl
|
||||||
|
Loading…
Reference in New Issue
Block a user