diff --git a/lib/nss_tacplus/nss_tacplus.8 b/lib/nss_tacplus/nss_tacplus.8 index 4aaff4b5dd3a..f5c87a836f9a 100644 --- a/lib/nss_tacplus/nss_tacplus.8 +++ b/lib/nss_tacplus/nss_tacplus.8 @@ -1,86 +1,89 @@ .\"- .\" Copyright (c) 2023 Klara, Inc. .\" .\" SPDX-License-Identifier: BSD-2-Clause .\" -.Dd May 17, 2023 +.Dd June 13, 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 CLASS +Login class. +Left blank if not provided. .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 index a59332504bc0..e18ffe2315ce 100644 --- a/lib/nss_tacplus/nss_tacplus.c +++ b/lib/nss_tacplus/nss_tacplus.c @@ -1,273 +1,285 @@ /*- * 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_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); } 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); }