xref: /freebsd/lib/nss_tacplus/nss_tacplus.c (revision e0c4386e)
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 
23 extern 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 
31 ns_mtab *nss_module_register(const char *, unsigned int *,
32     nss_module_unregister_fn *);
33 
34 static void
35 tacplus_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 
43 static pthread_key_t tacplus_key;
44 
45 static void
46 tacplus_fini(void *p)
47 {
48 	struct tac_handle **h = p;
49 
50 	tac_close(*h);
51 	free(h);
52 }
53 
54 static void
55 tacplus_keyinit(void)
56 {
57 	(void)pthread_key_create(&tacplus_key, tacplus_fini);
58 }
59 
60 static struct tac_handle *
61 tacplus_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 
95 static char *
96 tacplus_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 
111 static int
112 tacplus_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 
252 static int
253 nss_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 
274 static int
275 nss_tacplus_setpwent(void *retval __unused, void *mdata __unused,
276     va_list ap __unused)
277 {
278 	return (NS_SUCCESS);
279 }
280 
281 static int
282 nss_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 
295 static int
296 nss_tacplus_endpwent(void *retval __unused, void *mdata __unused,
297     va_list ap __unused)
298 {
299 	return (NS_SUCCESS);
300 }
301 
302 ns_mtab *
303 nss_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