1/*-
2 * Copyright (c) 2023 Klara, Inc.
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <sys/param.h>
8#include <sys/limits.h>
9
10#include <errno.h>
11#include <inttypes.h>
12#include <pthread.h>
13#include <pthread_np.h>
14#include <stdarg.h>
15#include <stdlib.h>
16#include <string.h>
17#include <syslog.h>
18
19#include <nsswitch.h>
20#include <pwd.h>
21#include <taclib.h>
22
23extern int __isthreaded;
24
25#define	DEF_UID		65534
26#define	DEF_GID		65534
27#define	DEF_CLASS	""
28#define	DEF_DIR		"/"
29#define	DEF_SHELL	"/bin/sh"
30
31ns_mtab *nss_module_register(const char *, unsigned int *,
32    nss_module_unregister_fn *);
33
34static void
35tacplus_error(struct tac_handle *h, const char *func)
36{
37	if (h == NULL)
38		syslog(LOG_ERR, "%s(): %m", func);
39	else
40		syslog(LOG_ERR, "%s(): %s", func, tac_strerror(h));
41}
42
43static pthread_key_t tacplus_key;
44
45static void
46tacplus_fini(void *p)
47{
48	struct tac_handle **h = p;
49
50	tac_close(*h);
51	free(h);
52}
53
54static void
55tacplus_keyinit(void)
56{
57	(void)pthread_key_create(&tacplus_key, tacplus_fini);
58}
59
60static struct tac_handle *
61tacplus_get_handle(void)
62{
63	static pthread_once_t keyinit = PTHREAD_ONCE_INIT;
64	static struct tac_handle *sth;
65	struct tac_handle **h = &sth;
66	int ret;
67
68	if (__isthreaded && !pthread_main_np()) {
69		if ((ret = pthread_once(&keyinit, tacplus_keyinit)) != 0)
70			return (NULL);
71		if ((h = pthread_getspecific(tacplus_key)) == NULL) {
72			if ((h = calloc(1, sizeof(*h))) == NULL)
73				return (NULL);
74			if ((pthread_setspecific(tacplus_key, h)) != 0) {
75				free(h);
76				return (NULL);
77			}
78		}
79	}
80	if (*h == NULL) {
81		if ((*h = tac_open()) == NULL) {
82			tacplus_error(*h, "tac_open");
83			return (NULL);
84		}
85		if (tac_config(*h, NULL) != 0) {
86			tacplus_error(*h, "tac_config");
87			tac_close(*h);
88			*h = NULL;
89			return (NULL);
90		}
91	}
92	return (*h);
93}
94
95static char *
96tacplus_copystr(const char *str, char **buffer, size_t *bufsize)
97{
98	char *copy = *buffer;
99	size_t len = strlen(str) + 1;
100
101	if (len > *bufsize) {
102		errno = ERANGE;
103		return (NULL);
104	}
105	memcpy(copy, str, len);
106	*buffer += len;
107	*bufsize -= len;
108	return (copy);
109}
110
111static int
112tacplus_getpwnam_r(const char *name, struct passwd *pwd, char *buffer,
113    size_t bufsize)
114{
115	struct tac_handle *h;
116	char *av, *key, *value, *end;
117	intmax_t num;
118	int i, ret;
119
120	if ((h = tacplus_get_handle()) == NULL)
121		return (NS_UNAVAIL);
122	ret = tac_create_author(h, TAC_AUTHEN_METH_NOT_SET,
123	    TAC_AUTHEN_TYPE_NOT_SET, TAC_AUTHEN_SVC_LOGIN);
124	if (ret < 0) {
125		tacplus_error(h, "tac_create_author");
126		return (NS_TRYAGAIN);
127	}
128	if (tac_set_user(h, name) < 0) {
129		tacplus_error(h, "tac_set_user");
130		return (NS_TRYAGAIN);
131	}
132	if (tac_set_av(h, 0, "service=shell") < 0) {
133		tacplus_error(h, "tac_set_av");
134		return (NS_TRYAGAIN);
135	}
136	ret = tac_send_author(h);
137	switch (TAC_AUTHOR_STATUS(ret)) {
138	case TAC_AUTHOR_STATUS_PASS_ADD:
139	case TAC_AUTHOR_STATUS_PASS_REPL:
140		/* found */
141		break;
142	case TAC_AUTHOR_STATUS_FAIL:
143		return (NS_NOTFOUND);
144	case TAC_AUTHOR_STATUS_ERROR:
145		return (NS_UNAVAIL);
146	default:
147		tacplus_error(h, "tac_send_author");
148		return (NS_UNAVAIL);
149	}
150	memset(pwd, 0, sizeof(*pwd));
151
152	/* copy name */
153	pwd->pw_name = tacplus_copystr(name, &buffer, &bufsize);
154	if (pwd->pw_name == NULL)
155		return (NS_RETURN);
156
157	/* no password */
158	pwd->pw_passwd = tacplus_copystr("*", &buffer, &bufsize);
159	if (2 > bufsize)
160		return (NS_RETURN);
161
162	/* default uid and gid */
163	pwd->pw_uid = DEF_UID;
164	pwd->pw_gid = DEF_GID;
165
166	/* get attribute-value pairs from TACACS+ response */
167	for (i = 0; i < TAC_AUTHEN_AV_COUNT(ret); i++) {
168		if ((av = tac_get_av(h, i)) == NULL) {
169			tacplus_error(h, "tac_get_av");
170			return (NS_UNAVAIL);
171		}
172		key = av;
173		if ((value = strchr(av, '=')) == NULL) {
174			free(av);
175			return (NS_RETURN);
176		}
177		*value++ = '\0';
178		if (strcasecmp(key, "uid") == 0) {
179			num = strtoimax(value, &end, 10);
180			if (end == value || *end != '\0' ||
181			    num < 0 || num > (intmax_t)UID_MAX) {
182				errno = EINVAL;
183				free(av);
184				return (NS_RETURN);
185			}
186			pwd->pw_uid = num;
187		} else if (strcasecmp(key, "gid") == 0) {
188			num = strtoimax(value, &end, 10);
189			if (end == value || *end != '\0' ||
190			    num < 0 || num > (intmax_t)GID_MAX) {
191				errno = EINVAL;
192				free(av);
193				return (NS_RETURN);
194			}
195			pwd->pw_gid = num;
196		} else if (strcasecmp(av, "class") == 0) {
197			pwd->pw_class = tacplus_copystr(value, &buffer,
198			    &bufsize);
199			if (pwd->pw_class == NULL) {
200				free(av);
201				return (NS_RETURN);
202			}
203		} else if (strcasecmp(av, "gecos") == 0) {
204			pwd->pw_gecos = tacplus_copystr(value, &buffer,
205			    &bufsize);
206			if (pwd->pw_gecos == NULL) {
207				free(av);
208				return (NS_RETURN);
209			}
210		} else if (strcasecmp(av, "home") == 0) {
211			pwd->pw_dir = tacplus_copystr(value, &buffer,
212			    &bufsize);
213			if (pwd->pw_dir == NULL) {
214				free(av);
215				return (NS_RETURN);
216			}
217		} else if (strcasecmp(av, "shell") == 0) {
218			pwd->pw_shell = tacplus_copystr(value, &buffer,
219			    &bufsize);
220			if (pwd->pw_shell == NULL) {
221				free(av);
222				return (NS_RETURN);
223			}
224		}
225		free(av);
226	}
227
228	/* default class if none was provided */
229	if (pwd->pw_class == NULL)
230		pwd->pw_class = tacplus_copystr(DEF_CLASS, &buffer, &bufsize);
231
232	/* gecos equal to name if none was provided */
233	if (pwd->pw_gecos == NULL)
234		pwd->pw_gecos = pwd->pw_name;
235
236	/* default home directory if none was provided */
237	if (pwd->pw_dir == NULL)
238		pwd->pw_dir = tacplus_copystr(DEF_DIR, &buffer, &bufsize);
239	if (pwd->pw_dir == NULL)
240		return (NS_RETURN);
241
242	/* default shell if none was provided */
243	if (pwd->pw_shell == NULL)
244		pwd->pw_shell = tacplus_copystr(DEF_SHELL, &buffer, &bufsize);
245	if (pwd->pw_shell == NULL)
246		return (NS_RETURN);
247
248	/* done! */
249	return (NS_SUCCESS);
250}
251
252static int
253nss_tacplus_getpwnam_r(void *retval, void *mdata __unused, va_list ap)
254{
255	char *name = va_arg(ap, char *);
256	struct passwd *pwd = va_arg(ap, struct passwd *);
257	char *buffer = va_arg(ap, char *);
258	size_t bufsize = va_arg(ap, size_t);
259	int *result = va_arg(ap, int *);
260	int ret;
261
262	errno = 0;
263	ret = tacplus_getpwnam_r(name, pwd, buffer, bufsize);
264	if (ret == NS_SUCCESS) {
265		*(void **)retval = pwd;
266		*result = 0;
267	} else {
268		*(void **)retval = NULL;
269		*result = errno;
270	}
271	return (ret);
272}
273
274static int
275nss_tacplus_setpwent(void *retval __unused, void *mdata __unused,
276    va_list ap __unused)
277{
278	return (NS_SUCCESS);
279}
280
281static int
282nss_tacplus_getpwent_r(void *retval, void *mdata __unused, va_list ap)
283{
284	struct passwd *pwd __unused = va_arg(ap, struct passwd *);
285	char *buffer __unused = va_arg(ap, char *);
286	size_t bufsize __unused = va_arg(ap, size_t);
287	int *result = va_arg(ap, int *);
288
289	*(void **)retval = NULL;
290	*result = 0;
291	return (NS_SUCCESS);
292
293}
294
295static int
296nss_tacplus_endpwent(void *retval __unused, void *mdata __unused,
297    va_list ap __unused)
298{
299	return (NS_SUCCESS);
300}
301
302ns_mtab *
303nss_module_register(const char *name __unused, unsigned int *plen,
304    nss_module_unregister_fn *unreg)
305{
306	static ns_mtab mtab[] = {
307		{ "passwd", "getpwnam_r", &nss_tacplus_getpwnam_r, NULL },
308		{ "passwd", "setpwent", &nss_tacplus_setpwent, NULL },
309		{ "passwd", "getpwent_r", &nss_tacplus_getpwent_r, NULL },
310		{ "passwd", "endpwent", &nss_tacplus_endpwent, NULL },
311	};
312
313	*plen = nitems(mtab);
314	*unreg = NULL;
315	return (mtab);
316}
317