mirror of
https://git.FreeBSD.org/src.git
synced 2024-11-23 07:31:31 +00:00
Add nss_tacplus, a TACACS+ NSS module.
MFC after: 1 week Sponsored by: Klara, Inc. Reviewed by: imp Differential Revision: https://reviews.freebsd.org/D40133
This commit is contained in:
parent
951beab15d
commit
6c5cdba1ba
@ -109,7 +109,8 @@ SUBDIR= ${SUBDIR_BOOTSTRAP} \
|
||||
liby \
|
||||
libz \
|
||||
libzstd \
|
||||
ncurses
|
||||
ncurses \
|
||||
nss_tacplus
|
||||
|
||||
# Inter-library dependencies. When the makefile for a library contains LDADD
|
||||
# libraries, those libraries should be listed as build order dependencies here.
|
||||
@ -145,6 +146,7 @@ SUBDIR_DEPEND_liblzma= libthr
|
||||
.if ${MK_OFED} != "no"
|
||||
SUBDIR_DEPEND_libpcap= ofed
|
||||
.endif
|
||||
SUBDIR_DEPEND_nss_tacplus= libtacplus
|
||||
|
||||
.if !defined(COMPAT_32BIT)
|
||||
SUBDIR+= flua
|
||||
|
9
lib/nss_tacplus/Makefile
Normal file
9
lib/nss_tacplus/Makefile
Normal file
@ -0,0 +1,9 @@
|
||||
LIB= nss_tacplus
|
||||
SRCS= ${LIB}.8
|
||||
SHLIB_MAJOR= 1
|
||||
SHLIB_NAME= ${LIB}.so.${SHLIB_MAJOR}
|
||||
LIBADD= tacplus
|
||||
MK_INSTALLIB= no
|
||||
MAN= ${LIB}.8
|
||||
|
||||
.include <bsd.lib.mk>
|
86
lib/nss_tacplus/nss_tacplus.8
Normal file
86
lib/nss_tacplus/nss_tacplus.8
Normal file
@ -0,0 +1,86 @@
|
||||
.\"-
|
||||
.\" Copyright (c) 2023 Klara, Inc.
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: BSD-2-Clause
|
||||
.\"
|
||||
.Dd May 17, 2023
|
||||
.Dt NSS_TACPLUS 8
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm nss_tacplus
|
||||
.Nd TACACS+ nsswitch module
|
||||
.Sh SYNOPSIS
|
||||
.Ic passwd : files tacplus
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm
|
||||
module is a loadable NSS module which provides a minimal identity
|
||||
service using a TACACS+ backend.
|
||||
.Pp
|
||||
Due to the limitations of the TACACS+ protocol, the functionality
|
||||
provided by the
|
||||
.Nm
|
||||
module is very limited: it can look up a user by name, but not by uid,
|
||||
and it cannot enumerate users.
|
||||
.Pp
|
||||
To look up a user, the
|
||||
.Nm
|
||||
module submits an authorization request with authentication method
|
||||
.Dv TAC_PLUS_AUTHEN_METH_NOT_SET ,
|
||||
authentication type
|
||||
.Dv TAC_PLUS_AUTHEN_TYPE_NOT_SET ,
|
||||
and authentication service
|
||||
.Dv TAC_PLUS_AUTHEN_SVC_LOGIN ,
|
||||
for the
|
||||
.Dq shell
|
||||
service.
|
||||
If the response status is either
|
||||
.Dv TAC_PLUS_AUTHOR_STATUS_PASS_ADD
|
||||
or
|
||||
.Dv TAC_PLUS_AUTHOR_STATUS_PASS_REPL ,
|
||||
the user is considered to exist and the
|
||||
.Nm
|
||||
module fills out a
|
||||
.Vt struct passwd
|
||||
for it.
|
||||
.Pp
|
||||
The following attributes, if included in the response from the TACACS+
|
||||
server, are used to construct the response:
|
||||
.Bl -tag -width GECOS
|
||||
.It Va UID
|
||||
Numeric user ID.
|
||||
Must be between 0 and
|
||||
.Dv UID_MAX .
|
||||
Defaults to 65534.
|
||||
.It Va GID
|
||||
Numeric primary group ID.
|
||||
Must be between 0 and
|
||||
.Dv GID_MAX .
|
||||
Defaults to 65534.
|
||||
.It Va GECOS
|
||||
Display name.
|
||||
If not provided, the user name is used instead.
|
||||
.It Va HOME
|
||||
Home directory.
|
||||
Defaults to
|
||||
.Pa / .
|
||||
.It Va SHELL
|
||||
Shell.
|
||||
Defaults to
|
||||
.Pa /bin/sh .
|
||||
.El
|
||||
.Pp
|
||||
Case is ignored when matching attribute names.
|
||||
If an attribute is included multiple times, the last value takes
|
||||
effect.
|
||||
.Sh SEE ALSO
|
||||
.Xr libtacplus 3 ,
|
||||
.Xr tacplus.conf 5 ,
|
||||
.Xr pam_tacplus 8
|
||||
.Sh HISTORY
|
||||
.An -nosplit
|
||||
The
|
||||
.Nm
|
||||
module and this manual page were written by
|
||||
.An Dag-Erling Smørgrav Aq Mt des@FreeBSD.org
|
||||
for Klara Systems.
|
273
lib/nss_tacplus/nss_tacplus.c
Normal file
273
lib/nss_tacplus/nss_tacplus.c
Normal file
@ -0,0 +1,273 @@
|
||||
/*-
|
||||
* Copyright (c) 2023 Klara, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/limits.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <pthread.h>
|
||||
#include <pthread_np.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
|
||||
#include <nsswitch.h>
|
||||
#include <pwd.h>
|
||||
#include <taclib.h>
|
||||
|
||||
extern int __isthreaded;
|
||||
|
||||
#define DEF_UID 65534
|
||||
#define DEF_GID 65534
|
||||
#define DEF_DIR "/"
|
||||
#define DEF_SHELL "/bin/sh"
|
||||
|
||||
ns_mtab *nss_module_register(const char *, unsigned int *,
|
||||
nss_module_unregister_fn *);
|
||||
|
||||
static void
|
||||
tacplus_error(struct tac_handle *h, const char *func)
|
||||
{
|
||||
if (h == NULL)
|
||||
syslog(LOG_ERR, "%s(): %m", func);
|
||||
else
|
||||
syslog(LOG_ERR, "%s(): %s", func, tac_strerror(h));
|
||||
}
|
||||
|
||||
static pthread_key_t tacplus_key;
|
||||
|
||||
static void
|
||||
tacplus_fini(void *p)
|
||||
{
|
||||
struct tac_handle **h = p;
|
||||
|
||||
tac_close(*h);
|
||||
free(h);
|
||||
}
|
||||
|
||||
static void
|
||||
tacplus_keyinit(void)
|
||||
{
|
||||
(void)pthread_key_create(&tacplus_key, tacplus_fini);
|
||||
}
|
||||
|
||||
static struct tac_handle *
|
||||
tacplus_get_handle(void)
|
||||
{
|
||||
static pthread_once_t keyinit = PTHREAD_ONCE_INIT;
|
||||
static struct tac_handle *sth;
|
||||
struct tac_handle **h = &sth;
|
||||
int ret;
|
||||
|
||||
if (__isthreaded && !pthread_main_np()) {
|
||||
if ((ret = pthread_once(&keyinit, tacplus_keyinit)) != 0)
|
||||
return (NULL);
|
||||
if ((h = pthread_getspecific(tacplus_key)) == NULL) {
|
||||
if ((h = calloc(1, sizeof(*h))) == NULL)
|
||||
return (NULL);
|
||||
if ((pthread_setspecific(tacplus_key, h)) != 0) {
|
||||
free(h);
|
||||
return (NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (*h == NULL) {
|
||||
if ((*h = tac_open()) == NULL) {
|
||||
tacplus_error(*h, "tac_open");
|
||||
return (NULL);
|
||||
}
|
||||
if (tac_config(*h, NULL) != 0) {
|
||||
tacplus_error(*h, "tac_config");
|
||||
tac_close(*h);
|
||||
*h = NULL;
|
||||
return (NULL);
|
||||
}
|
||||
}
|
||||
return (*h);
|
||||
}
|
||||
|
||||
static char *
|
||||
tacplus_copystr(const char *str, char **buffer, size_t *bufsize)
|
||||
{
|
||||
char *copy = *buffer;
|
||||
size_t len = strlen(str) + 1;
|
||||
|
||||
if (len > *bufsize) {
|
||||
errno = ERANGE;
|
||||
return (NULL);
|
||||
}
|
||||
memcpy(copy, str, len);
|
||||
*buffer += len;
|
||||
*bufsize -= len;
|
||||
return (copy);
|
||||
}
|
||||
|
||||
static int
|
||||
tacplus_getpwnam_r(const char *name, struct passwd *pwd, char *buffer,
|
||||
size_t bufsize)
|
||||
{
|
||||
struct tac_handle *h;
|
||||
char *av, *key, *value, *end;
|
||||
intmax_t num;
|
||||
int i, ret;
|
||||
|
||||
if ((h = tacplus_get_handle()) == NULL)
|
||||
return (NS_UNAVAIL);
|
||||
ret = tac_create_author(h, TAC_AUTHEN_METH_NOT_SET,
|
||||
TAC_AUTHEN_TYPE_NOT_SET, TAC_AUTHEN_SVC_LOGIN);
|
||||
if (ret < 0) {
|
||||
tacplus_error(h, "tac_create_author");
|
||||
return (NS_TRYAGAIN);
|
||||
}
|
||||
if (tac_set_user(h, name) < 0) {
|
||||
tacplus_error(h, "tac_set_user");
|
||||
return (NS_TRYAGAIN);
|
||||
}
|
||||
if (tac_set_av(h, 0, "service=shell") < 0) {
|
||||
tacplus_error(h, "tac_set_av");
|
||||
return (NS_TRYAGAIN);
|
||||
}
|
||||
ret = tac_send_author(h);
|
||||
switch (TAC_AUTHOR_STATUS(ret)) {
|
||||
case TAC_AUTHOR_STATUS_PASS_ADD:
|
||||
case TAC_AUTHOR_STATUS_PASS_REPL:
|
||||
/* found */
|
||||
break;
|
||||
case TAC_AUTHOR_STATUS_FAIL:
|
||||
return (NS_NOTFOUND);
|
||||
case TAC_AUTHOR_STATUS_ERROR:
|
||||
return (NS_UNAVAIL);
|
||||
default:
|
||||
tacplus_error(h, "tac_send_author");
|
||||
return (NS_UNAVAIL);
|
||||
}
|
||||
memset(pwd, 0, sizeof(*pwd));
|
||||
|
||||
/* copy name */
|
||||
pwd->pw_name = tacplus_copystr(name, &buffer, &bufsize);
|
||||
if (pwd->pw_name == NULL)
|
||||
return (NS_RETURN);
|
||||
|
||||
/* no password */
|
||||
pwd->pw_passwd = tacplus_copystr("*", &buffer, &bufsize);
|
||||
if (2 > bufsize)
|
||||
return (NS_RETURN);
|
||||
|
||||
/* default uid and gid */
|
||||
pwd->pw_uid = DEF_UID;
|
||||
pwd->pw_gid = DEF_GID;
|
||||
|
||||
/* get attribute-value pairs from TACACS+ response */
|
||||
for (i = 0; i < TAC_AUTHEN_AV_COUNT(ret); i++) {
|
||||
if ((av = tac_get_av(h, i)) == NULL) {
|
||||
tacplus_error(h, "tac_get_av");
|
||||
return (NS_UNAVAIL);
|
||||
}
|
||||
key = av;
|
||||
if ((value = strchr(av, '=')) == NULL) {
|
||||
free(av);
|
||||
return (NS_RETURN);
|
||||
}
|
||||
*value++ = '\0';
|
||||
if (strcasecmp(key, "uid") == 0) {
|
||||
num = strtoimax(value, &end, 10);
|
||||
if (end == value || *end != '\0' ||
|
||||
num < 0 || num > (intmax_t)UID_MAX) {
|
||||
errno = EINVAL;
|
||||
free(av);
|
||||
return (NS_RETURN);
|
||||
}
|
||||
pwd->pw_uid = num;
|
||||
} else if (strcasecmp(key, "gid") == 0) {
|
||||
num = strtoimax(value, &end, 10);
|
||||
if (end == value || *end != '\0' ||
|
||||
num < 0 || num > (intmax_t)GID_MAX) {
|
||||
errno = EINVAL;
|
||||
free(av);
|
||||
return (NS_RETURN);
|
||||
}
|
||||
pwd->pw_gid = num;
|
||||
} else if (strcasecmp(av, "gecos") == 0) {
|
||||
pwd->pw_gecos = tacplus_copystr(value, &buffer,
|
||||
&bufsize);
|
||||
if (pwd->pw_gecos == NULL) {
|
||||
free(av);
|
||||
return (NS_RETURN);
|
||||
}
|
||||
} else if (strcasecmp(av, "home") == 0) {
|
||||
pwd->pw_dir = tacplus_copystr(value, &buffer,
|
||||
&bufsize);
|
||||
if (pwd->pw_dir == NULL) {
|
||||
free(av);
|
||||
return (NS_RETURN);
|
||||
}
|
||||
} else if (strcasecmp(av, "shell") == 0) {
|
||||
pwd->pw_shell = tacplus_copystr(value, &buffer,
|
||||
&bufsize);
|
||||
if (pwd->pw_shell == NULL) {
|
||||
free(av);
|
||||
return (NS_RETURN);
|
||||
}
|
||||
}
|
||||
free(av);
|
||||
}
|
||||
|
||||
/* gecos equal to name if none was provided */
|
||||
if (pwd->pw_gecos == NULL)
|
||||
pwd->pw_gecos = pwd->pw_name;
|
||||
|
||||
/* default home directory if none was provided */
|
||||
if (pwd->pw_dir == NULL)
|
||||
pwd->pw_dir = tacplus_copystr(DEF_DIR, &buffer, &bufsize);
|
||||
if (pwd->pw_dir == NULL)
|
||||
return (NS_RETURN);
|
||||
|
||||
/* default shell if none was provided */
|
||||
if (pwd->pw_shell == NULL)
|
||||
pwd->pw_shell = tacplus_copystr(DEF_SHELL, &buffer, &bufsize);
|
||||
if (pwd->pw_shell == NULL)
|
||||
return (NS_RETURN);
|
||||
|
||||
/* done! */
|
||||
return (NS_SUCCESS);
|
||||
}
|
||||
|
||||
static int
|
||||
nss_tacplus_getpwnam_r(void *retval, void *mdata __unused, va_list ap)
|
||||
{
|
||||
char *name = va_arg(ap, char *);
|
||||
struct passwd *pwd = va_arg(ap, struct passwd *);
|
||||
char *buffer = va_arg(ap, char *);
|
||||
size_t bufsize = va_arg(ap, size_t);
|
||||
int *result = va_arg(ap, int *);
|
||||
int ret;
|
||||
|
||||
errno = 0;
|
||||
ret = tacplus_getpwnam_r(name, pwd, buffer, bufsize);
|
||||
if (ret == NS_SUCCESS) {
|
||||
*(void **)retval = pwd;
|
||||
*result = 0;
|
||||
} else {
|
||||
*(void **)retval = NULL;
|
||||
*result = errno;
|
||||
}
|
||||
return (ret);
|
||||
}
|
||||
|
||||
ns_mtab *
|
||||
nss_module_register(const char *name __unused, unsigned int *plen,
|
||||
nss_module_unregister_fn *unreg)
|
||||
{
|
||||
static ns_mtab mtab[] = {
|
||||
{ "passwd", "getpwnam_r", &nss_tacplus_getpwnam_r, NULL },
|
||||
};
|
||||
|
||||
*plen = nitems(mtab);
|
||||
*unreg = NULL;
|
||||
return (mtab);
|
||||
}
|
Loading…
Reference in New Issue
Block a user