pam-u2f.c revision 1.1
1/* 2 * Copyright (C) 2014-2019 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 <stdlib.h> 17#include <syslog.h> 18#include <pwd.h> 19#include <string.h> 20#include <errno.h> 21 22#include "util.h" 23#include "drop_privs.h" 24 25/* If secure_getenv is not defined, define it here */ 26#ifndef HAVE_SECURE_GETENV 27char *secure_getenv(const char *); 28char *secure_getenv(const char *name) { 29 (void) name; 30 return NULL; 31} 32#endif 33 34static void parse_cfg(int flags, int argc, const char **argv, cfg_t *cfg) { 35 struct stat st; 36 FILE *file = NULL; 37 int fd = -1; 38 int i; 39 40 memset(cfg, 0, sizeof(cfg_t)); 41 cfg->debug_file = stderr; 42 cfg->userpresence = -1; 43 cfg->userverification = -1; 44 cfg->pinverification = -1; 45 46 for (i = 0; i < argc; i++) { 47 if (strncmp(argv[i], "max_devices=", 12) == 0) 48 sscanf(argv[i], "max_devices=%u", &cfg->max_devs); 49 if (strcmp(argv[i], "manual") == 0) 50 cfg->manual = 1; 51 if (strcmp(argv[i], "debug") == 0) 52 cfg->debug = 1; 53 if (strcmp(argv[i], "nouserok") == 0) 54 cfg->nouserok = 1; 55 if (strcmp(argv[i], "openasuser") == 0) 56 cfg->openasuser = 1; 57 if (strcmp(argv[i], "alwaysok") == 0) 58 cfg->alwaysok = 1; 59 if (strcmp(argv[i], "interactive") == 0) 60 cfg->interactive = 1; 61 if (strcmp(argv[i], "cue") == 0) 62 cfg->cue = 1; 63 if (strcmp(argv[i], "nodetect") == 0) 64 cfg->nodetect = 1; 65 if (strncmp(argv[i], "userpresence=", 13) == 0) 66 sscanf(argv[i], "userpresence=%d", &cfg->userpresence); 67 if (strncmp(argv[i], "userverification=", 17) == 0) 68 sscanf(argv[i], "userverification=%d", &cfg->userverification); 69 if (strncmp(argv[i], "pinverification=", 16) == 0) 70 sscanf(argv[i], "pinverification=%d", &cfg->pinverification); 71 if (strncmp(argv[i], "authfile=", 9) == 0) 72 cfg->auth_file = argv[i] + 9; 73 if (strncmp(argv[i], "authpending_file=", 17) == 0) 74 cfg->authpending_file = argv[i] + 17; 75 if (strncmp(argv[i], "origin=", 7) == 0) 76 cfg->origin = argv[i] + 7; 77 if (strncmp(argv[i], "appid=", 6) == 0) 78 cfg->appid = argv[i] + 6; 79 if (strncmp(argv[i], "prompt=", 7) == 0) 80 cfg->prompt = argv[i] + 7; 81 if (strncmp(argv[i], "cue_prompt=", 11) == 0) 82 cfg->cue_prompt = argv[i] + 11; 83 if (strncmp(argv[i], "debug_file=", 11) == 0) { 84 const char *filename = argv[i] + 11; 85 if (strncmp(filename, "stdout", 6) == 0) { 86 cfg->debug_file = stdout; 87 } else if (strncmp(filename, "stderr", 6) == 0) { 88 cfg->debug_file = stderr; 89 } else if (strncmp(filename, "syslog", 6) == 0) { 90 cfg->debug_file = (FILE *) -1; 91 } else { 92 fd = open(filename, 93 O_WRONLY | O_APPEND | O_CLOEXEC | O_NOFOLLOW | O_NOCTTY); 94 if (fd >= 0 && (fstat(fd, &st) == 0) && S_ISREG(st.st_mode)) { 95 file = fdopen(fd, "a"); 96 if (file != NULL) { 97 cfg->debug_file = file; 98 cfg->is_custom_debug_file = 1; 99 file = NULL; 100 fd = -1; 101 } 102 } 103 } 104 } 105 } 106 107 if (cfg->debug) { 108 D(cfg->debug_file, "called."); 109 D(cfg->debug_file, "flags %d argc %d", flags, argc); 110 for (i = 0; i < argc; i++) { 111 D(cfg->debug_file, "argv[%d]=%s", i, argv[i]); 112 } 113 D(cfg->debug_file, "max_devices=%d", cfg->max_devs); 114 D(cfg->debug_file, "debug=%d", cfg->debug); 115 D(cfg->debug_file, "interactive=%d", cfg->interactive); 116 D(cfg->debug_file, "cue=%d", cfg->cue); 117 D(cfg->debug_file, "nodetect=%d", cfg->nodetect); 118 D(cfg->debug_file, "userpresence=%d", cfg->userpresence); 119 D(cfg->debug_file, "userverification=%d", cfg->userverification); 120 D(cfg->debug_file, "pinverification=%d", cfg->pinverification); 121 D(cfg->debug_file, "manual=%d", cfg->manual); 122 D(cfg->debug_file, "nouserok=%d", cfg->nouserok); 123 D(cfg->debug_file, "openasuser=%d", cfg->openasuser); 124 D(cfg->debug_file, "alwaysok=%d", cfg->alwaysok); 125 D(cfg->debug_file, "authfile=%s", 126 cfg->auth_file ? cfg->auth_file : "(null)"); 127 D(cfg->debug_file, "authpending_file=%s", 128 cfg->authpending_file ? cfg->authpending_file : "(null)"); 129 D(cfg->debug_file, "origin=%s", cfg->origin ? cfg->origin : "(null)"); 130 D(cfg->debug_file, "appid=%s", cfg->appid ? cfg->appid : "(null)"); 131 D(cfg->debug_file, "prompt=%s", cfg->prompt ? cfg->prompt : "(null)"); 132 } 133 134 if (fd != -1) 135 close(fd); 136 137 if (file != NULL) 138 fclose(file); 139} 140 141#ifdef DBG 142#undef DBG 143#endif 144#define DBG(...) \ 145 if (cfg->debug) { \ 146 D(cfg->debug_file, __VA_ARGS__); \ 147 } 148 149/* PAM entry point for authentication verification */ 150int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, 151 const char **argv) { 152 153 struct passwd *pw = NULL, pw_s; 154 const char *user = NULL; 155 156 cfg_t cfg_st; 157 cfg_t *cfg = &cfg_st; 158 char buffer[BUFSIZE]; 159 char *buf = NULL; 160 char *authfile_dir; 161 size_t authfile_dir_len; 162 int pgu_ret, gpn_ret; 163 int retval = PAM_IGNORE; 164 device_t *devices = NULL; 165 unsigned n_devices = 0; 166 int openasuser = 0; 167 int should_free_origin = 0; 168 int should_free_appid = 0; 169 int should_free_auth_file = 0; 170 int should_free_authpending_file = 0; 171 PAM_MODUTIL_DEF_PRIVS(privs); 172 173 parse_cfg(flags, argc, argv, cfg); 174 175 if (!cfg->origin) { 176 strcpy(buffer, DEFAULT_ORIGIN_PREFIX); 177 178 if (gethostname(buffer + strlen(DEFAULT_ORIGIN_PREFIX), 179 BUFSIZE - strlen(DEFAULT_ORIGIN_PREFIX)) == -1) { 180 DBG("Unable to get host name"); 181 goto done; 182 } 183 DBG("Origin not specified, using \"%s\"", buffer); 184 cfg->origin = strdup(buffer); 185 if (!cfg->origin) { 186 DBG("Unable to allocate memory"); 187 goto done; 188 } else { 189 should_free_origin = 1; 190 } 191 } 192 193 if (!cfg->appid) { 194 DBG("Appid not specified, using the same value of origin (%s)", 195 cfg->origin); 196 cfg->appid = strdup(cfg->origin); 197 if (!cfg->appid) { 198 DBG("Unable to allocate memory") 199 goto done; 200 } else { 201 should_free_appid = 1; 202 } 203 } 204 205 if (cfg->max_devs == 0) { 206 DBG("Maximum devices number not set. Using default (%d)", MAX_DEVS); 207 cfg->max_devs = MAX_DEVS; 208 } 209 210 devices = calloc(cfg->max_devs, sizeof(device_t)); 211 if (!devices) { 212 DBG("Unable to allocate memory"); 213 retval = PAM_IGNORE; 214 goto done; 215 } 216 217 pgu_ret = pam_get_user(pamh, &user, NULL); 218 if (pgu_ret != PAM_SUCCESS || user == NULL) { 219 DBG("Unable to access user %s", user); 220 retval = PAM_CONV_ERR; 221 goto done; 222 } 223 224 DBG("Requesting authentication for user %s", user); 225 226 gpn_ret = getpwnam_r(user, &pw_s, buffer, sizeof(buffer), &pw); 227 if (gpn_ret != 0 || pw == NULL || pw->pw_dir == NULL || 228 pw->pw_dir[0] != '/') { 229 DBG("Unable to retrieve credentials for user %s, (%s)", user, 230 strerror(errno)); 231 retval = PAM_USER_UNKNOWN; 232 goto done; 233 } 234 235 DBG("Found user %s", user); 236 DBG("Home directory for %s is %s", user, pw->pw_dir); 237 238 if (!cfg->auth_file) { 239 buf = NULL; 240 authfile_dir = secure_getenv(DEFAULT_AUTHFILE_DIR_VAR); 241 if (!authfile_dir) { 242 DBG("Variable %s is not set. Using default value ($HOME/.config/)", 243 DEFAULT_AUTHFILE_DIR_VAR); 244 authfile_dir_len = 245 strlen(pw->pw_dir) + strlen("/.config") + strlen(DEFAULT_AUTHFILE) + 1; 246 buf = malloc(sizeof(char) * (authfile_dir_len)); 247 248 if (!buf) { 249 DBG("Unable to allocate memory"); 250 retval = PAM_IGNORE; 251 goto done; 252 } 253 254 /* Opening a file in a users $HOME, need to drop privs for security */ 255 openasuser = geteuid() == 0 ? 1 : 0; 256 257 snprintf(buf, authfile_dir_len, "%s/.config%s", pw->pw_dir, 258 DEFAULT_AUTHFILE); 259 } else { 260 DBG("Variable %s set to %s", DEFAULT_AUTHFILE_DIR_VAR, authfile_dir); 261 authfile_dir_len = strlen(authfile_dir) + strlen(DEFAULT_AUTHFILE) + 1; 262 buf = malloc(sizeof(char) * (authfile_dir_len)); 263 264 if (!buf) { 265 DBG("Unable to allocate memory"); 266 retval = PAM_IGNORE; 267 goto done; 268 } 269 270 snprintf(buf, authfile_dir_len, "%s%s", authfile_dir, DEFAULT_AUTHFILE); 271 272 if (!cfg->openasuser) { 273 DBG("WARNING: not dropping privileges when reading %s, please " 274 "consider setting openasuser=1 in the module configuration", 275 buf); 276 } 277 } 278 279 DBG("Using authentication file %s", buf); 280 281 cfg->auth_file = buf; /* cfg takes ownership */ 282 should_free_auth_file = 1; 283 buf = NULL; 284 } else { 285 if (cfg->auth_file[0] != '/') { 286 /* Individual authorization mapping by user: auth_file is not 287 absolute path, so prepend user home dir. */ 288 openasuser = geteuid() == 0 ? 1 : 0; 289 290 authfile_dir_len = 291 strlen(pw->pw_dir) + strlen("/") + strlen(cfg->auth_file) + 1; 292 buf = malloc(sizeof(char) * (authfile_dir_len)); 293 294 if (!buf) { 295 DBG("Unable to allocate memory"); 296 retval = PAM_IGNORE; 297 goto done; 298 } 299 300 snprintf(buf, authfile_dir_len, "%s/%s", pw->pw_dir, cfg->auth_file); 301 302 cfg->auth_file = buf; /* update cfg */ 303 should_free_auth_file = 1; 304 buf = NULL; 305 } 306 307 DBG("Using authentication file %s", cfg->auth_file); 308 } 309 310 if (!openasuser) { 311 openasuser = geteuid() == 0 && cfg->openasuser; 312 } 313 if (openasuser) { 314 DBG("Dropping privileges"); 315 if (pam_modutil_drop_priv(pamh, &privs, pw)) { 316 DBG("Unable to switch user to uid %i", pw->pw_uid); 317 retval = PAM_IGNORE; 318 goto done; 319 } 320 DBG("Switched to uid %i", pw->pw_uid); 321 } 322 retval = 323 get_devices_from_authfile(cfg->auth_file, user, cfg->max_devs, cfg->debug, 324 cfg->debug_file, devices, &n_devices); 325 if (openasuser) { 326 if (pam_modutil_regain_priv(pamh, &privs)) { 327 DBG("could not restore privileges"); 328 retval = PAM_IGNORE; 329 goto done; 330 } 331 DBG("Restored privileges"); 332 } 333 334 if (retval != 1) { 335 // for nouserok; make sure errors in get_devices_from_authfile don't 336 // result in valid devices 337 n_devices = 0; 338 } 339 340 if (n_devices == 0) { 341 if (cfg->nouserok) { 342 DBG("Found no devices but nouserok specified. Skipping authentication"); 343 retval = PAM_SUCCESS; 344 goto done; 345 } else if (retval != 1) { 346 DBG("Unable to get devices from file %s", cfg->auth_file); 347 retval = PAM_AUTHINFO_UNAVAIL; 348 goto done; 349 } else { 350 DBG("Found no devices. Aborting."); 351 retval = PAM_AUTHINFO_UNAVAIL; 352 goto done; 353 } 354 } 355 356 // Determine the full path for authpending_file in order to emit touch request 357 // notifications 358 if (!cfg->authpending_file) { 359 int actual_size = 360 snprintf(buffer, BUFSIZE, DEFAULT_AUTHPENDING_FILE_PATH, getuid()); 361 if (actual_size >= 0 && actual_size < BUFSIZE) { 362 cfg->authpending_file = strdup(buffer); 363 } 364 if (!cfg->authpending_file) { 365 DBG("Unable to allocate memory for the authpending_file, touch request " 366 "notifications will not be emitted"); 367 } else { 368 should_free_authpending_file = 1; 369 } 370 } else { 371 if (strlen(cfg->authpending_file) == 0) { 372 DBG("authpending_file is set to an empty value, touch request " 373 "notifications will be disabled"); 374 cfg->authpending_file = NULL; 375 } 376 } 377 378 int authpending_file_descriptor = -1; 379 if (cfg->authpending_file) { 380 DBG("Using file '%s' for emitting touch request notifications", 381 cfg->authpending_file); 382 383 // Open (or create) the authpending_file to indicate that we start waiting 384 // for a touch 385 authpending_file_descriptor = 386 open(cfg->authpending_file, 387 O_RDONLY | O_CREAT | O_CLOEXEC | O_NOFOLLOW | O_NOCTTY, 0664); 388 if (authpending_file_descriptor < 0) { 389 DBG("Unable to emit 'authentication started' notification by opening the " 390 "file '%s', (%s)", 391 cfg->authpending_file, strerror(errno)); 392 } 393 } 394 395 if (cfg->manual == 0) { 396 if (cfg->interactive) { 397 converse(pamh, PAM_PROMPT_ECHO_ON, 398 cfg->prompt != NULL ? cfg->prompt : DEFAULT_PROMPT); 399 } 400 401 retval = do_authentication(cfg, devices, n_devices, pamh); 402 } else { 403 retval = do_manual_authentication(cfg, devices, n_devices, pamh); 404 } 405 406 // Close the authpending_file to indicate that we stop waiting for a touch 407 if (authpending_file_descriptor >= 0) { 408 if (close(authpending_file_descriptor) < 0) { 409 DBG("Unable to emit 'authentication stopped' notification by closing the " 410 "file '%s', (%s)", 411 cfg->authpending_file, strerror(errno)); 412 } 413 } 414 415 if (retval != 1) { 416 DBG("do_authentication returned %d", retval); 417 retval = PAM_AUTH_ERR; 418 goto done; 419 } 420 421 retval = PAM_SUCCESS; 422 423done: 424 free_devices(devices, n_devices); 425 426 if (buf) { 427 free(buf); 428 buf = NULL; 429 } 430 431 if (should_free_origin) { 432 free((char *) cfg->origin); 433 cfg->origin = NULL; 434 } 435 436 if (should_free_appid) { 437 free((char *) cfg->appid); 438 cfg->appid = NULL; 439 } 440 441 if (should_free_auth_file) { 442 free((char *) cfg->auth_file); 443 cfg->auth_file = NULL; 444 } 445 446 if (should_free_authpending_file) { 447 free((char *) cfg->authpending_file); 448 cfg->authpending_file = NULL; 449 } 450 451 if (cfg->alwaysok && retval != PAM_SUCCESS) { 452 DBG("alwaysok needed (otherwise return with %d)", retval); 453 retval = PAM_SUCCESS; 454 } 455 DBG("done. [%s]", pam_strerror(pamh, retval)); 456 457 if (cfg->is_custom_debug_file) { 458 fclose(cfg->debug_file); 459 } 460 461 return retval; 462} 463 464PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, 465 const char **argv) { 466 (void) pamh; 467 (void) flags; 468 (void) argc; 469 (void) argv; 470 471 return PAM_SUCCESS; 472} 473