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
tacplus_error(struct tac_handle * h,const char * func)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
tacplus_fini(void * p)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
tacplus_keyinit(void)55 tacplus_keyinit(void)
56 {
57 (void)pthread_key_create(&tacplus_key, tacplus_fini);
58 }
59
60 static struct tac_handle *
tacplus_get_handle(void)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 *
tacplus_copystr(const char * str,char ** buffer,size_t * bufsize)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
tacplus_getpwnam_r(const char * name,struct passwd * pwd,char * buffer,size_t bufsize)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
nss_tacplus_getpwnam_r(void * retval,void * mdata __unused,va_list ap)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
nss_tacplus_setpwent(void * retval __unused,void * mdata __unused,va_list ap __unused)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
nss_tacplus_getpwent_r(void * retval,void * mdata __unused,va_list ap)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
nss_tacplus_endpwent(void * retval __unused,void * mdata __unused,va_list ap __unused)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 *
nss_module_register(const char * name __unused,unsigned int * plen,nss_module_unregister_fn * unreg)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