aboutsummaryrefslogtreecommitdiff
path: root/lib/nss_tacplus/nss_tacplus.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/nss_tacplus/nss_tacplus.c')
-rw-r--r--lib/nss_tacplus/nss_tacplus.c316
1 files changed, 316 insertions, 0 deletions
diff --git a/lib/nss_tacplus/nss_tacplus.c b/lib/nss_tacplus/nss_tacplus.c
new file mode 100644
index 000000000000..238d7bf301ad
--- /dev/null
+++ b/lib/nss_tacplus/nss_tacplus.c
@@ -0,0 +1,316 @@
+/*-
+ * 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_CLASS ""
+#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, "class") == 0) {
+ pwd->pw_class = tacplus_copystr(value, &buffer,
+ &bufsize);
+ if (pwd->pw_class == NULL) {
+ free(av);
+ return (NS_RETURN);
+ }
+ } 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);
+ }
+
+ /* default class if none was provided */
+ if (pwd->pw_class == NULL)
+ pwd->pw_class = tacplus_copystr(DEF_CLASS, &buffer, &bufsize);
+
+ /* 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);
+}
+
+static int
+nss_tacplus_setpwent(void *retval __unused, void *mdata __unused,
+ va_list ap __unused)
+{
+ return (NS_SUCCESS);
+}
+
+static int
+nss_tacplus_getpwent_r(void *retval, void *mdata __unused, va_list ap)
+{
+ struct passwd *pwd __unused = va_arg(ap, struct passwd *);
+ char *buffer __unused = va_arg(ap, char *);
+ size_t bufsize __unused = va_arg(ap, size_t);
+ int *result = va_arg(ap, int *);
+
+ *(void **)retval = NULL;
+ *result = 0;
+ return (NS_SUCCESS);
+
+}
+
+static int
+nss_tacplus_endpwent(void *retval __unused, void *mdata __unused,
+ va_list ap __unused)
+{
+ return (NS_SUCCESS);
+}
+
+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 },
+ { "passwd", "setpwent", &nss_tacplus_setpwent, NULL },
+ { "passwd", "getpwent_r", &nss_tacplus_getpwent_r, NULL },
+ { "passwd", "endpwent", &nss_tacplus_endpwent, NULL },
+ };
+
+ *plen = nitems(mtab);
+ *unreg = NULL;
+ return (mtab);
+}