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