1 /* 2 * Copyright (C) 2014-2023 Yubico AB - See COPYING 3 */ 4 5 /* Define which PAM interfaces we provide */ 6 #define PAM_SM_AUTH 7 8 /* Include PAM headers */ 9 #include <security/pam_appl.h> 10 #include <security/pam_modules.h> 11 12 #include <fcntl.h> 13 #include <sys/types.h> 14 #include <sys/stat.h> 15 #include <unistd.h> 16 #include <stdint.h> 17 #include <stdlib.h> 18 #include <syslog.h> 19 #include <pwd.h> 20 #include <string.h> 21 #include <errno.h> 22 23 #include "debug.h" 24 #include "drop_privs.h" 25 #include "util.h" 26 27 #define free_const(a) free((void *) (uintptr_t) (a)) 28 29 /* If secure_getenv is not defined, define it here */ 30 #ifndef HAVE_SECURE_GETENV 31 char *secure_getenv(const char *); 32 char *secure_getenv(const char *name) { 33 (void) name; 34 return NULL; 35 } 36 #endif 37 38 static void parse_cfg(int flags __unused, int argc, const char **argv, cfg_t *cfg) { 39 int i; 40 41 memset(cfg, 0, sizeof(cfg_t)); 42 cfg->debug_file = DEFAULT_DEBUG_FILE; 43 cfg->userpresence = -1; 44 cfg->userverification = -1; 45 cfg->pinverification = -1; 46 47 for (i = 0; i < argc; i++) { 48 if (strncmp(argv[i], "max_devices=", 12) == 0) { 49 sscanf(argv[i], "max_devices=%u", &cfg->max_devs); 50 } else if (strcmp(argv[i], "manual") == 0) { 51 cfg->manual = 1; 52 } else if (strcmp(argv[i], "debug") == 0) { 53 cfg->debug = 1; 54 } else if (strcmp(argv[i], "nouserok") == 0) { 55 cfg->nouserok = 1; 56 } else if (strcmp(argv[i], "openasuser") == 0) { 57 cfg->openasuser = 1; 58 } else if (strcmp(argv[i], "alwaysok") == 0) { 59 cfg->alwaysok = 1; 60 } else if (strcmp(argv[i], "interactive") == 0) { 61 cfg->interactive = 1; 62 } else if (strcmp(argv[i], "cue") == 0) { 63 cfg->cue = 1; 64 } else if (strcmp(argv[i], "nodetect") == 0) { 65 cfg->nodetect = 1; 66 } else if (strcmp(argv[i], "expand") == 0) { 67 cfg->expand = 1; 68 } else if (strncmp(argv[i], "userpresence=", 13) == 0) { 69 sscanf(argv[i], "userpresence=%d", &cfg->userpresence); 70 } else if (strncmp(argv[i], "userverification=", 17) == 0) { 71 sscanf(argv[i], "userverification=%d", &cfg->userverification); 72 } else if (strncmp(argv[i], "pinverification=", 16) == 0) { 73 sscanf(argv[i], "pinverification=%d", &cfg->pinverification); 74 } else if (strncmp(argv[i], "authfile=", 9) == 0) { 75 cfg->auth_file = argv[i] + 9; 76 } else if (strcmp(argv[i], "sshformat") == 0) { 77 cfg->sshformat = 1; 78 } else if (strncmp(argv[i], "authpending_file=", 17) == 0) { 79 cfg->authpending_file = argv[i] + 17; 80 } else if (strncmp(argv[i], "origin=", 7) == 0) { 81 cfg->origin = argv[i] + 7; 82 } else if (strncmp(argv[i], "appid=", 6) == 0) { 83 cfg->appid = argv[i] + 6; 84 } else if (strncmp(argv[i], "prompt=", 7) == 0) { 85 cfg->prompt = argv[i] + 7; 86 } else if (strncmp(argv[i], "cue_prompt=", 11) == 0) { 87 cfg->cue_prompt = argv[i] + 11; 88 } else if (strncmp(argv[i], "debug_file=", 11) == 0) { 89 const char *filename = argv[i] + 11; 90 debug_close(cfg->debug_file); 91 cfg->debug_file = debug_open(filename); 92 } 93 } 94 95 debug_dbg(cfg, "called."); 96 debug_dbg(cfg, "flags %d argc %d", flags, argc); 97 for (i = 0; i < argc; i++) { 98 debug_dbg(cfg, "argv[%d]=%s", i, argv[i]); 99 } 100 debug_dbg(cfg, "max_devices=%d", cfg->max_devs); 101 debug_dbg(cfg, "debug=%d", cfg->debug); 102 debug_dbg(cfg, "interactive=%d", cfg->interactive); 103 debug_dbg(cfg, "cue=%d", cfg->cue); 104 debug_dbg(cfg, "nodetect=%d", cfg->nodetect); 105 debug_dbg(cfg, "userpresence=%d", cfg->userpresence); 106 debug_dbg(cfg, "userverification=%d", cfg->userverification); 107 debug_dbg(cfg, "pinverification=%d", cfg->pinverification); 108 debug_dbg(cfg, "manual=%d", cfg->manual); 109 debug_dbg(cfg, "nouserok=%d", cfg->nouserok); 110 debug_dbg(cfg, "openasuser=%d", cfg->openasuser); 111 debug_dbg(cfg, "alwaysok=%d", cfg->alwaysok); 112 debug_dbg(cfg, "sshformat=%d", cfg->sshformat); 113 debug_dbg(cfg, "expand=%d", cfg->expand); 114 debug_dbg(cfg, "authfile=%s", cfg->auth_file ? cfg->auth_file : "(null)"); 115 debug_dbg(cfg, "authpending_file=%s", 116 cfg->authpending_file ? cfg->authpending_file : "(null)"); 117 debug_dbg(cfg, "origin=%s", cfg->origin ? cfg->origin : "(null)"); 118 debug_dbg(cfg, "appid=%s", cfg->appid ? cfg->appid : "(null)"); 119 debug_dbg(cfg, "prompt=%s", cfg->prompt ? cfg->prompt : "(null)"); 120 } 121 122 static void interactive_prompt(pam_handle_t *pamh, const cfg_t *cfg) { 123 char *tmp = NULL; 124 125 tmp = converse(pamh, PAM_PROMPT_ECHO_ON, 126 cfg->prompt != NULL ? cfg->prompt : DEFAULT_PROMPT); 127 128 free(tmp); 129 } 130 131 static char *resolve_authfile_path(const cfg_t *cfg, const struct passwd *user, 132 int *openasuser) { 133 char *authfile = NULL; 134 const char *dir = NULL; 135 const char *path = NULL; 136 137 *openasuser = geteuid() == 0; /* user files, drop privileges */ 138 139 if (cfg->auth_file == NULL) { 140 if ((dir = secure_getenv(DEFAULT_AUTHFILE_DIR_VAR)) == NULL) { 141 debug_dbg(cfg, "Variable %s is not set, using default", 142 DEFAULT_AUTHFILE_DIR_VAR); 143 dir = user->pw_dir; 144 path = cfg->sshformat ? DEFAULT_AUTHFILE_DIR_SSH "/" DEFAULT_AUTHFILE_SSH 145 : DEFAULT_AUTHFILE_DIR "/" DEFAULT_AUTHFILE; 146 } else { 147 debug_dbg(cfg, "Variable %s set to %s", DEFAULT_AUTHFILE_DIR_VAR, dir); 148 *openasuser = 0; /* documented exception, require explicit openasuser */ 149 path = cfg->sshformat ? DEFAULT_AUTHFILE_SSH : DEFAULT_AUTHFILE; 150 if (!cfg->openasuser) { 151 debug_dbg(cfg, "WARNING: not dropping privileges when reading the " 152 "authentication file, please consider setting " 153 "openasuser=1 in the module configuration"); 154 } 155 } 156 } else { 157 dir = user->pw_dir; 158 path = cfg->auth_file; 159 } 160 161 if (dir == NULL || *dir != '/' || path == NULL || 162 asprintf(&authfile, "%s/%s", dir, path) == -1) 163 authfile = NULL; 164 165 return authfile; 166 } 167 168 /* PAM entry point for authentication verification */ 169 int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, 170 const char **argv) { 171 172 struct passwd *pw = NULL, pw_s; 173 const char *user = NULL; 174 175 cfg_t cfg_st; 176 cfg_t *cfg = &cfg_st; 177 char buffer[BUFSIZE]; 178 int pgu_ret, gpn_ret; 179 int retval = PAM_ABORT; 180 device_t *devices = NULL; 181 unsigned n_devices = 0; 182 int openasuser = 0; 183 int should_free_origin = 0; 184 int should_free_appid = 0; 185 int should_free_auth_file = 0; 186 int should_free_authpending_file = 0; 187 188 parse_cfg(flags, argc, argv, cfg); 189 190 PAM_MODUTIL_DEF_PRIVS(privs); 191 192 if (!cfg->origin) { 193 if (!cfg->sshformat) { 194 strcpy(buffer, DEFAULT_ORIGIN_PREFIX); 195 if (gethostname(buffer + strlen(DEFAULT_ORIGIN_PREFIX), 196 BUFSIZE - strlen(DEFAULT_ORIGIN_PREFIX)) == -1) { 197 debug_dbg(cfg, "Unable to get host name"); 198 retval = PAM_SYSTEM_ERR; 199 goto done; 200 } 201 } else { 202 strcpy(buffer, SSH_ORIGIN); 203 } 204 debug_dbg(cfg, "Origin not specified, using \"%s\"", buffer); 205 cfg->origin = strdup(buffer); 206 if (!cfg->origin) { 207 debug_dbg(cfg, "Unable to allocate memory"); 208 retval = PAM_BUF_ERR; 209 goto done; 210 } else { 211 should_free_origin = 1; 212 } 213 } 214 215 if (!cfg->appid) { 216 debug_dbg(cfg, "Appid not specified, using the value of origin (%s)", 217 cfg->origin); 218 cfg->appid = strdup(cfg->origin); 219 if (!cfg->appid) { 220 debug_dbg(cfg, "Unable to allocate memory"); 221 retval = PAM_BUF_ERR; 222 goto done; 223 } else { 224 should_free_appid = 1; 225 } 226 } 227 228 if (cfg->max_devs == 0) { 229 debug_dbg(cfg, "Maximum number of devices not set. Using default (%d)", 230 MAX_DEVS); 231 cfg->max_devs = MAX_DEVS; 232 } 233 #if WITH_FUZZING 234 if (cfg->max_devs > 256) 235 cfg->max_devs = 256; 236 #endif 237 238 devices = calloc(cfg->max_devs, sizeof(device_t)); 239 if (!devices) { 240 debug_dbg(cfg, "Unable to allocate memory"); 241 retval = PAM_BUF_ERR; 242 goto done; 243 } 244 245 pgu_ret = pam_get_user(pamh, &user, NULL); 246 if (pgu_ret != PAM_SUCCESS || user == NULL) { 247 debug_dbg(cfg, "Unable to get username from PAM"); 248 retval = PAM_CONV_ERR; 249 goto done; 250 } 251 252 debug_dbg(cfg, "Requesting authentication for user %s", user); 253 254 gpn_ret = getpwnam_r(user, &pw_s, buffer, sizeof(buffer), &pw); 255 if (gpn_ret != 0 || pw == NULL || pw->pw_dir == NULL || 256 pw->pw_dir[0] != '/') { 257 debug_dbg(cfg, "Unable to retrieve credentials for user %s, (%s)", user, 258 strerror(errno)); 259 retval = PAM_SYSTEM_ERR; 260 goto done; 261 } 262 263 debug_dbg(cfg, "Found user %s", user); 264 debug_dbg(cfg, "Home directory for %s is %s", user, pw->pw_dir); 265 266 // Perform variable expansion. 267 if (cfg->expand && cfg->auth_file) { 268 if ((cfg->auth_file = expand_variables(cfg->auth_file, user)) == NULL) { 269 debug_dbg(cfg, "Failed to perform variable expansion"); 270 retval = PAM_BUF_ERR; 271 goto done; 272 } 273 should_free_auth_file = 1; 274 } 275 // Resolve default or relative paths. 276 if (!cfg->auth_file || cfg->auth_file[0] != '/') { 277 char *tmp = resolve_authfile_path(cfg, pw, &openasuser); 278 if (tmp == NULL) { 279 debug_dbg(cfg, "Could not resolve authfile path"); 280 retval = PAM_BUF_ERR; 281 goto done; 282 } 283 if (should_free_auth_file) { 284 free_const(cfg->auth_file); 285 } 286 cfg->auth_file = tmp; 287 should_free_auth_file = 1; 288 } 289 290 debug_dbg(cfg, "Using authentication file %s", cfg->auth_file); 291 292 if (!openasuser) { 293 openasuser = geteuid() == 0 && cfg->openasuser; 294 } 295 if (openasuser) { 296 debug_dbg(cfg, "Dropping privileges"); 297 if (pam_modutil_drop_priv(pamh, &privs, pw)) { 298 debug_dbg(cfg, "Unable to switch user to uid %i", pw->pw_uid); 299 retval = PAM_SYSTEM_ERR; 300 goto done; 301 } 302 debug_dbg(cfg, "Switched to uid %i", pw->pw_uid); 303 } 304 retval = get_devices_from_authfile(cfg, user, devices, &n_devices); 305 306 if (openasuser) { 307 if (pam_modutil_regain_priv(pamh, &privs)) { 308 debug_dbg(cfg, "could not restore privileges"); 309 retval = PAM_SYSTEM_ERR; 310 goto done; 311 } 312 debug_dbg(cfg, "Restored privileges"); 313 } 314 315 if (retval != PAM_SUCCESS) { 316 goto done; 317 } 318 319 // Determine the full path for authpending_file in order to emit touch request 320 // notifications 321 if (!cfg->authpending_file) { 322 int actual_size = 323 snprintf(buffer, BUFSIZE, DEFAULT_AUTHPENDING_FILE_PATH, getuid()); 324 if (actual_size >= 0 && actual_size < BUFSIZE) { 325 cfg->authpending_file = strdup(buffer); 326 } 327 if (!cfg->authpending_file) { 328 debug_dbg(cfg, "Unable to allocate memory for the authpending_file, " 329 "touch request notifications will not be emitted"); 330 } else { 331 should_free_authpending_file = 1; 332 } 333 } else { 334 if (strlen(cfg->authpending_file) == 0) { 335 debug_dbg(cfg, "authpending_file is set to an empty value, touch request " 336 "notifications will be disabled"); 337 cfg->authpending_file = NULL; 338 } 339 } 340 341 int authpending_file_descriptor = -1; 342 if (cfg->authpending_file) { 343 debug_dbg(cfg, "Touch request notifications will be emitted via '%s'", 344 cfg->authpending_file); 345 346 // Open (or create) the authpending_file to indicate that we start waiting 347 // for a touch 348 authpending_file_descriptor = 349 open(cfg->authpending_file, 350 O_RDONLY | O_CREAT | O_CLOEXEC | O_NOFOLLOW | O_NOCTTY, 0664); 351 if (authpending_file_descriptor < 0) { 352 debug_dbg(cfg, "Unable to emit 'authentication started' notification: %s", 353 strerror(errno)); 354 } 355 } 356 357 if (cfg->manual == 0) { 358 if (cfg->interactive) { 359 interactive_prompt(pamh, cfg); 360 } 361 retval = do_authentication(cfg, devices, n_devices, pamh); 362 } else { 363 retval = do_manual_authentication(cfg, devices, n_devices, pamh); 364 } 365 366 // Close the authpending_file to indicate that we stop waiting for a touch 367 if (authpending_file_descriptor >= 0) { 368 if (close(authpending_file_descriptor) < 0) { 369 debug_dbg(cfg, "Unable to emit 'authentication stopped' notification: %s", 370 strerror(errno)); 371 } 372 } 373 374 done: 375 free_devices(devices, n_devices); 376 377 if (should_free_origin) { 378 free_const(cfg->origin); 379 cfg->origin = NULL; 380 } 381 382 if (should_free_appid) { 383 free_const(cfg->appid); 384 cfg->appid = NULL; 385 } 386 387 if (should_free_auth_file) { 388 free_const(cfg->auth_file); 389 cfg->auth_file = NULL; 390 } 391 392 if (should_free_authpending_file) { 393 free_const(cfg->authpending_file); 394 cfg->authpending_file = NULL; 395 } 396 397 if (cfg->alwaysok && retval != PAM_SUCCESS) { 398 debug_dbg(cfg, "alwaysok needed (otherwise return with %d)", retval); 399 retval = PAM_SUCCESS; 400 } 401 debug_dbg(cfg, "done. [%s]", pam_strerror(pamh, retval)); 402 403 debug_close(cfg->debug_file); 404 cfg->debug_file = DEFAULT_DEBUG_FILE; 405 406 return retval; 407 } 408 409 PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, 410 const char **argv) { 411 (void) pamh; 412 (void) flags; 413 (void) argc; 414 (void) argv; 415 416 return PAM_SUCCESS; 417 } 418 419 #ifdef PAM_MODULE_ENTRY 420 PAM_MODULE_ENTRY("pam_u2f"); 421 #endif 422