From nobody Wed Dec 13 20:06:15 2023 X-Original-To: dev-commits-src-branches@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4Sr61g5Qnfz54FLf; Wed, 13 Dec 2023 20:06:15 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4Sr61g4vJgz4YKc; Wed, 13 Dec 2023 20:06:15 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1702497975; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=OU+yZV42DG290VPQJws1IfD/R/XCs+Mp3wsNIDall9Q=; b=Z0ubPQC90/WBAEfBb5+sCWB0UPkTSMDp3Eg9hLkEnCHGdbXcezA1lg0RAuUk0ASdWEYsSx a7V8fHgxSE2JtFa0ZDAhQxN6Cnj42IIHpkJ3/ifkPl9LryYBPppcHNLr6JUytNPhrF1+6A GzdbMRiAkH/VTJsNPsq2fRcwX9I/8YiOuElSN6AHTiK5i66cSLzXBhAufyWCEWchjXnpfU mnbJ44kP5mSE4BuFlscx5FT/B6otjiAKfpEkjUn0Wg3HQAjs5wGUWjC7T1Hw3qL/unzEgt fyomsB5doP3IB9y51YAphGlRYkmod2NNi++YKOjqCbSya0atznomy/R8DI70CQ== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1702497975; a=rsa-sha256; cv=none; b=ixwrPr2uyY6q0k1tFlaxtIKuA8lzVjBmz3hsYs96nUVpXqNp5tKh0bJyAau3DDffMWWf/S y96z+Hc/yRHxDmliUdjqBHXOphqSo2rjcRK+2LaOoNja43UxUsEg9gDQX4z50LDIZG8n2+ yL2cqwA3HW1XsntDayZ0Z6/RrcONGWNTiOK59PHmbpqLu1n/wr4ptJZO9VMMskN14X3ls6 q3i0BsFRCGcFWbnmo0ut81A6C8Q5WXaVgwggeOvlEyf4ARfaDpmiy6+6ogIQn/D61Db5aV GkBnlfcuyoJ0Uv6fzMJreLJr/P8BR7xLxdP+qERzo36hoQvvCEsA0tafiTlFkg== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1702497975; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=OU+yZV42DG290VPQJws1IfD/R/XCs+Mp3wsNIDall9Q=; b=hXto+W3IlINt+2XeyuhiB4JEzUF+hGWr3c+EGq+G2YbKUsmzdZ52raZ81e1F+LokpGqIKy aIzJJUg2wzPmuL6SEzaGKMLRKs2ZRT9hso6uzdVjAGaNS+VngFObynAWB3jX3E+0ow2tvc yVd2tSGNVVwYDfL5yUlVBdLBbmZulXw8O2B4Vsv4SjV0yb44ikcNET2n0VfGNND6Was7Nr 2Ad9x0SRIpkWtSDiyBTJrI0sCiksUdmab9kpIe1sNdF6RPAMa0iUcgGr0ynz/OARX/cdBK 37SifWksXELNcIMdczjDF5ZIreklVzYtzhGZC5hRhlzbV5d7plfOlLwqBCmogQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4Sr61g40pbz1BXb; Wed, 13 Dec 2023 20:06:15 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.17.1/8.17.1) with ESMTP id 3BDK6Fls086761; Wed, 13 Dec 2023 20:06:15 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.17.1/8.17.1/Submit) id 3BDK6FkX086758; Wed, 13 Dec 2023 20:06:15 GMT (envelope-from git) Date: Wed, 13 Dec 2023 20:06:15 GMT Message-Id: <202312132006.3BDK6FkX086758@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Dag-Erling =?utf-8?Q?Sm=C3=B8rgrav?= Subject: git: 96fabd5cbc32 - stable/13 - Add nss_tacplus, a TACACS+ NSS module. List-Id: Commits to the stable branches of the FreeBSD src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-branches List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-dev-commits-src-branches@freebsd.org X-BeenThere: dev-commits-src-branches@freebsd.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: des X-Git-Repository: src X-Git-Refname: refs/heads/stable/13 X-Git-Reftype: branch X-Git-Commit: 96fabd5cbc32e3ddbe6ce926ee96ffe75ccbb153 Auto-Submitted: auto-generated The branch stable/13 has been updated by des: URL: https://cgit.FreeBSD.org/src/commit/?id=96fabd5cbc32e3ddbe6ce926ee96ffe75ccbb153 commit 96fabd5cbc32e3ddbe6ce926ee96ffe75ccbb153 Author: Dag-Erling Smørgrav AuthorDate: 2023-05-22 10:00:48 +0000 Commit: Dag-Erling Smørgrav CommitDate: 2023-12-13 16:08:13 +0000 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 (cherry picked from commit 6c5cdba1bafe77428b7721e49cc2b944885ec71a) --- lib/Makefile | 4 +- lib/nss_tacplus/Makefile | 9 ++ lib/nss_tacplus/nss_tacplus.8 | 86 +++++++++++++ lib/nss_tacplus/nss_tacplus.c | 273 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 371 insertions(+), 1 deletion(-) diff --git a/lib/Makefile b/lib/Makefile index e5dd8bf1c550..49a57d619fb9 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -105,7 +105,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. @@ -141,6 +142,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 diff --git a/lib/nss_tacplus/Makefile b/lib/nss_tacplus/Makefile new file mode 100644 index 000000000000..f39788cfbdea --- /dev/null +++ b/lib/nss_tacplus/Makefile @@ -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 diff --git a/lib/nss_tacplus/nss_tacplus.8 b/lib/nss_tacplus/nss_tacplus.8 new file mode 100644 index 000000000000..4aaff4b5dd3a --- /dev/null +++ b/lib/nss_tacplus/nss_tacplus.8 @@ -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. diff --git a/lib/nss_tacplus/nss_tacplus.c b/lib/nss_tacplus/nss_tacplus.c new file mode 100644 index 000000000000..a59332504bc0 --- /dev/null +++ b/lib/nss_tacplus/nss_tacplus.c @@ -0,0 +1,273 @@ +/*- + * Copyright (c) 2023 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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); +}