xref: /freebsd/lib/nss_tacplus/nss_tacplus.c (revision 0be82d56b44fbe157d4bf1c125eca6625486d17f)
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_DIR		"/"
28 #define	DEF_SHELL	"/bin/sh"
29 
30 ns_mtab *nss_module_register(const char *, unsigned int *,
31     nss_module_unregister_fn *);
32 
33 static void
34 tacplus_error(struct tac_handle *h, const char *func)
35 {
36 	if (h == NULL)
37 		syslog(LOG_ERR, "%s(): %m", func);
38 	else
39 		syslog(LOG_ERR, "%s(): %s", func, tac_strerror(h));
40 }
41 
42 static pthread_key_t tacplus_key;
43 
44 static void
45 tacplus_fini(void *p)
46 {
47 	struct tac_handle **h = p;
48 
49 	tac_close(*h);
50 	free(h);
51 }
52 
53 static void
54 tacplus_keyinit(void)
55 {
56 	(void)pthread_key_create(&tacplus_key, tacplus_fini);
57 }
58 
59 static struct tac_handle *
60 tacplus_get_handle(void)
61 {
62 	static pthread_once_t keyinit = PTHREAD_ONCE_INIT;
63 	static struct tac_handle *sth;
64 	struct tac_handle **h = &sth;
65 	int ret;
66 
67 	if (__isthreaded && !pthread_main_np()) {
68 		if ((ret = pthread_once(&keyinit, tacplus_keyinit)) != 0)
69 			return (NULL);
70 		if ((h = pthread_getspecific(tacplus_key)) == NULL) {
71 			if ((h = calloc(1, sizeof(*h))) == NULL)
72 				return (NULL);
73 			if ((pthread_setspecific(tacplus_key, h)) != 0) {
74 				free(h);
75 				return (NULL);
76 			}
77 		}
78 	}
79 	if (*h == NULL) {
80 		if ((*h = tac_open()) == NULL) {
81 			tacplus_error(*h, "tac_open");
82 			return (NULL);
83 		}
84 		if (tac_config(*h, NULL) != 0) {
85 			tacplus_error(*h, "tac_config");
86 			tac_close(*h);
87 			*h = NULL;
88 			return (NULL);
89 		}
90 	}
91 	return (*h);
92 }
93 
94 static char *
95 tacplus_copystr(const char *str, char **buffer, size_t *bufsize)
96 {
97 	char *copy = *buffer;
98 	size_t len = strlen(str) + 1;
99 
100 	if (len > *bufsize) {
101 		errno = ERANGE;
102 		return (NULL);
103 	}
104 	memcpy(copy, str, len);
105 	*buffer += len;
106 	*bufsize -= len;
107 	return (copy);
108 }
109 
110 static int
111 tacplus_getpwnam_r(const char *name, struct passwd *pwd, char *buffer,
112     size_t bufsize)
113 {
114 	struct tac_handle *h;
115 	char *av, *key, *value, *end;
116 	intmax_t num;
117 	int i, ret;
118 
119 	if ((h = tacplus_get_handle()) == NULL)
120 		return (NS_UNAVAIL);
121 	ret = tac_create_author(h, TAC_AUTHEN_METH_NOT_SET,
122 	    TAC_AUTHEN_TYPE_NOT_SET, TAC_AUTHEN_SVC_LOGIN);
123 	if (ret < 0) {
124 		tacplus_error(h, "tac_create_author");
125 		return (NS_TRYAGAIN);
126 	}
127 	if (tac_set_user(h, name) < 0) {
128 		tacplus_error(h, "tac_set_user");
129 		return (NS_TRYAGAIN);
130 	}
131 	if (tac_set_av(h, 0, "service=shell") < 0) {
132 		tacplus_error(h, "tac_set_av");
133 		return (NS_TRYAGAIN);
134 	}
135 	ret = tac_send_author(h);
136 	switch (TAC_AUTHOR_STATUS(ret)) {
137 	case TAC_AUTHOR_STATUS_PASS_ADD:
138 	case TAC_AUTHOR_STATUS_PASS_REPL:
139 		/* found */
140 		break;
141 	case TAC_AUTHOR_STATUS_FAIL:
142 		return (NS_NOTFOUND);
143 	case TAC_AUTHOR_STATUS_ERROR:
144 		return (NS_UNAVAIL);
145 	default:
146 		tacplus_error(h, "tac_send_author");
147 		return (NS_UNAVAIL);
148 	}
149 	memset(pwd, 0, sizeof(*pwd));
150 
151 	/* copy name */
152 	pwd->pw_name = tacplus_copystr(name, &buffer, &bufsize);
153 	if (pwd->pw_name == NULL)
154 		return (NS_RETURN);
155 
156 	/* no password */
157 	pwd->pw_passwd = tacplus_copystr("*", &buffer, &bufsize);
158 	if (2 > bufsize)
159 		return (NS_RETURN);
160 
161 	/* default uid and gid */
162 	pwd->pw_uid = DEF_UID;
163 	pwd->pw_gid = DEF_GID;
164 
165 	/* get attribute-value pairs from TACACS+ response */
166 	for (i = 0; i < TAC_AUTHEN_AV_COUNT(ret); i++) {
167 		if ((av = tac_get_av(h, i)) == NULL) {
168 			tacplus_error(h, "tac_get_av");
169 			return (NS_UNAVAIL);
170 		}
171 		key = av;
172 		if ((value = strchr(av, '=')) == NULL) {
173 			free(av);
174 			return (NS_RETURN);
175 		}
176 		*value++ = '\0';
177 		if (strcasecmp(key, "uid") == 0) {
178 			num = strtoimax(value, &end, 10);
179 			if (end == value || *end != '\0' ||
180 			    num < 0 || num > (intmax_t)UID_MAX) {
181 				errno = EINVAL;
182 				free(av);
183 				return (NS_RETURN);
184 			}
185 			pwd->pw_uid = num;
186 		} else if (strcasecmp(key, "gid") == 0) {
187 			num = strtoimax(value, &end, 10);
188 			if (end == value || *end != '\0' ||
189 			    num < 0 || num > (intmax_t)GID_MAX) {
190 				errno = EINVAL;
191 				free(av);
192 				return (NS_RETURN);
193 			}
194 			pwd->pw_gid = num;
195 		} else if (strcasecmp(av, "gecos") == 0) {
196 			pwd->pw_gecos = tacplus_copystr(value, &buffer,
197 			    &bufsize);
198 			if (pwd->pw_gecos == NULL) {
199 				free(av);
200 				return (NS_RETURN);
201 			}
202 		} else if (strcasecmp(av, "home") == 0) {
203 			pwd->pw_dir = tacplus_copystr(value, &buffer,
204 			    &bufsize);
205 			if (pwd->pw_dir == NULL) {
206 				free(av);
207 				return (NS_RETURN);
208 			}
209 		} else if (strcasecmp(av, "shell") == 0) {
210 			pwd->pw_shell = tacplus_copystr(value, &buffer,
211 			    &bufsize);
212 			if (pwd->pw_shell == NULL) {
213 				free(av);
214 				return (NS_RETURN);
215 			}
216 		}
217 		free(av);
218 	}
219 
220 	/* gecos equal to name if none was provided */
221 	if (pwd->pw_gecos == NULL)
222 		pwd->pw_gecos = pwd->pw_name;
223 
224 	/* default home directory if none was provided */
225 	if (pwd->pw_dir == NULL)
226 		pwd->pw_dir = tacplus_copystr(DEF_DIR, &buffer, &bufsize);
227 	if (pwd->pw_dir == NULL)
228 		return (NS_RETURN);
229 
230 	/* default shell if none was provided */
231 	if (pwd->pw_shell == NULL)
232 		pwd->pw_shell = tacplus_copystr(DEF_SHELL, &buffer, &bufsize);
233 	if (pwd->pw_shell == NULL)
234 		return (NS_RETURN);
235 
236 	/* done! */
237 	return (NS_SUCCESS);
238 }
239 
240 static int
241 nss_tacplus_getpwnam_r(void *retval, void *mdata __unused, va_list ap)
242 {
243 	char *name = va_arg(ap, char *);
244 	struct passwd *pwd = va_arg(ap, struct passwd *);
245 	char *buffer = va_arg(ap, char *);
246 	size_t bufsize = va_arg(ap, size_t);
247 	int *result = va_arg(ap, int *);
248 	int ret;
249 
250 	errno = 0;
251 	ret = tacplus_getpwnam_r(name, pwd, buffer, bufsize);
252 	if (ret == NS_SUCCESS) {
253 		*(void **)retval = pwd;
254 		*result = 0;
255 	} else {
256 		*(void **)retval = NULL;
257 		*result = errno;
258 	}
259 	return (ret);
260 }
261 
262 ns_mtab *
263 nss_module_register(const char *name __unused, unsigned int *plen,
264     nss_module_unregister_fn *unreg)
265 {
266 	static ns_mtab mtab[] = {
267 		{ "passwd", "getpwnam_r", &nss_tacplus_getpwnam_r, NULL },
268 	};
269 
270 	*plen = nitems(mtab);
271 	*unreg = NULL;
272 	return (mtab);
273 }
274