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