static int do_auth(struct login_ctx *ctx, const char *cmd) { struct duo_config cfg; struct passwd *pw; struct in_addr addr; duo_t *duo; duo_code_t code; const char *config, *p, *duouser; const char *ip, *host = NULL; char buf[64]; int i, flags, ret, prompts, matched; int headless = 0; if ((pw = getpwuid(ctx->uid)) == NULL) die("Who are you?"); duouser = ctx->duouser ? ctx->duouser : pw->pw_name; config = ctx->config ? ctx->config : DUO_CONF; flags = 0; duo_config_default(&cfg); /* Load our private config. */ if ((i = duo_parse_config(config, __ini_handler, &cfg)) != 0 || (!cfg.apihost || !cfg.apihost[0] || !cfg.skey || !cfg.skey[0] || !cfg.ikey || !cfg.ikey[0])) { switch (i) { case -2: fprintf(stderr, "%s must be readable only by " "user '%s'\n", config, pw->pw_name); break; case -1: fprintf(stderr, "Couldn't open %s: %s\n", config, strerror(errno)); break; case 0: fprintf(stderr, "Missing host, ikey, or skey in %s\n", config); break; default: fprintf(stderr, "Parse error in %s, line %d\n", config, i); break; } /* Implicit "safe" failmode for local configuration errors */ if (cfg.failmode == DUO_FAIL_SAFE) { return (EXIT_SUCCESS); } return (EXIT_FAILURE); } prompts = cfg.prompts; /* Check group membership. */ matched = duo_check_groups(pw, cfg.groups, cfg.groups_cnt); if (matched == -1) { return (EXIT_FAILURE); } else if (matched == 0) { return (EXIT_SUCCESS); } /* Check for remote login host */ if ((host = ip = getenv("SSH_CONNECTION")) != NULL || (host = ip = (char *)ctx->host) != NULL) { if (inet_aton(ip, &addr)) { strlcpy(buf, ip, sizeof(buf)); ip = strtok(buf, " "); host = ip; } else { ip = (cfg.local_ip_fallback ? duo_local_ip() : NULL); } } /* Honor configured http_proxy */ if (cfg.http_proxy != NULL) { setenv("http_proxy", cfg.http_proxy, 1); } /* Try Duo auth. */ if ((duo = duo_open(cfg.apihost, cfg.ikey, cfg.skey, "login_duo/" PACKAGE_VERSION, cfg.noverify ? "" : cfg.cafile, cfg.https_timeout)) == NULL) { duo_log(LOG_ERR, "Couldn't open Duo API handle", pw->pw_name, host, NULL); return (EXIT_FAILURE); } /* Special handling for non-interactive sessions */ if ((p = getenv("SSH_ORIGINAL_COMMAND")) != NULL || !isatty(STDIN_FILENO)) { /* Try to support automatic one-shot login */ duo_set_conv_funcs(duo, NULL, NULL, NULL); flags = (DUO_FLAG_SYNC|DUO_FLAG_AUTO); prompts = 1; headless = 1; } else if (cfg.autopush) { /* Special handling for autopush */ duo_set_conv_funcs(duo, NULL, __autopush_status_fn, NULL); flags = (DUO_FLAG_SYNC|DUO_FLAG_AUTO); } if (cfg.accept_env) { flags |= DUO_FLAG_ENV; } ret = EXIT_FAILURE; for (i = 0; i < prompts; i++) { code = duo_login(duo, duouser, host, flags, cfg.pushinfo ? cmd : NULL); if (code == DUO_FAIL) { duo_log(LOG_WARNING, "Failed Duo login", duouser, host, duo_geterr(duo)); if ((flags & DUO_FLAG_SYNC) == 0) { printf("\n"); } /* The autopush failed, fall back to regular process */ if (cfg.autopush && i == 0) { flags = 0; duo_reset_conv_funcs(duo); } /* Keep going */ continue; } /* Terminal conditions */ if (code == DUO_OK) { if ((p = duo_geterr(duo)) != NULL) { duo_log(LOG_WARNING, "Skipped Duo login", duouser, host, p); } else { duo_log(LOG_INFO, "Successful Duo login", duouser, host, NULL); } if (cfg.motd && !headless) { _print_motd(); } ret = EXIT_SUCCESS; } else if (code == DUO_ABORT) { duo_log(LOG_WARNING, "Aborted Duo login", duouser, host, duo_geterr(duo)); } else if (cfg.failmode == DUO_FAIL_SAFE && (code == DUO_CONN_ERROR || code == DUO_CLIENT_ERROR || code == DUO_SERVER_ERROR)) { duo_log(LOG_WARNING, "Failsafe Duo login", duouser, host, duo_geterr(duo)); ret = EXIT_SUCCESS; } else { duo_log(LOG_ERR, "Error in Duo login", duouser, host, duo_geterr(duo)); } break; } duo_close(duo); return (ret); }
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int pam_flags, int argc, const char *argv[]) { struct duo_config cfg; struct passwd *pw; struct in_addr addr; duo_t *duo; duo_code_t code; duopam_const char *config, *cmd, *p, *service, *user; const char *ip, *host; int i, flags, pam_err, matched; duo_config_default(&cfg); /* Parse configuration */ config = DUO_CONF; for (i = 0; i < argc; i++) { if (strncmp("conf=", argv[i], 5) == 0) { config = argv[i] + 5; } else if (strcmp("debug", argv[i]) == 0) { duo_debug = 1; } else { duo_syslog(LOG_ERR, "Invalid pam_duo option: '%s'", argv[i]); return (PAM_SERVICE_ERR); } } i = duo_parse_config(config, __ini_handler, &cfg); if (i == -2) { duo_syslog(LOG_ERR, "%s must be readable only by user 'root'", config); return (cfg.failmode == DUO_FAIL_SAFE ? PAM_SUCCESS : PAM_SERVICE_ERR); } else if (i == -1) { duo_syslog(LOG_ERR, "Couldn't open %s: %s", config, strerror(errno)); return (cfg.failmode == DUO_FAIL_SAFE ? PAM_SUCCESS : PAM_SERVICE_ERR); } else if (i > 0) { duo_syslog(LOG_ERR, "Parse error in %s, line %d", config, i); return (cfg.failmode == DUO_FAIL_SAFE ? PAM_SUCCESS : PAM_SERVICE_ERR); } else if (!cfg.apihost || !cfg.apihost[0] || !cfg.skey || !cfg.skey[0] || !cfg.ikey || !cfg.ikey[0]) { duo_syslog(LOG_ERR, "Missing host, ikey, or skey in %s", config); return (cfg.failmode == DUO_FAIL_SAFE ? PAM_SUCCESS : PAM_SERVICE_ERR); } /* Check user */ if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || (pw = getpwnam(user)) == NULL) { return (PAM_USER_UNKNOWN); } /* XXX - Service-specific behavior */ flags = 0; cmd = NULL; if (pam_get_item(pamh, PAM_SERVICE, (duopam_const void **) (duopam_const void *)&service) != PAM_SUCCESS) { return (PAM_SERVICE_ERR); } if (strcmp(service, "sshd") == 0) { /* * Disable incremental status reporting for sshd :-( * OpenSSH accumulates PAM_TEXT_INFO from modules to send in * an SSH_MSG_USERAUTH_BANNER post-auth, not real-time! */ flags |= DUO_FLAG_SYNC; } else if (strcmp(service, "sudo") == 0) { cmd = getenv("SUDO_COMMAND"); } else if (strcmp(service, "su") == 0) { /* Check calling user for Duo auth, just like sudo */ if ((pw = getpwuid(getuid())) == NULL) { return (PAM_USER_UNKNOWN); } user = pw->pw_name; } /* Check group membership */ matched = duo_check_groups(pw, cfg.groups, cfg.groups_cnt); if (matched == -1) { return (PAM_SERVICE_ERR); } else if (matched == 0) { return (PAM_SUCCESS); } /* Grab the remote host */ ip = NULL; pam_get_item(pamh, PAM_RHOST, (duopam_const void **)(duopam_const void *)&ip); host = ip; /* PAM is weird, check to see if PAM_RHOST is IP or hostname */ if (ip == NULL) { ip = ""; /* XXX inet_addr needs a non-null IP */ } if (!inet_aton(ip, &addr)) { /* We have a hostname, don't try to resolve, check fallback */ ip = (cfg.local_ip_fallback ? duo_local_ip() : NULL); } /* Honor configured http_proxy */ if (cfg.http_proxy != NULL) { setenv("http_proxy", cfg.http_proxy, 1); } /* Try Duo auth */ if ((duo = duo_open(cfg.apihost, cfg.ikey, cfg.skey, "pam_duo/" PACKAGE_VERSION, cfg.noverify ? "" : cfg.cafile, DUO_NO_TIMEOUT)) == NULL) { duo_log(LOG_ERR, "Couldn't open Duo API handle", user, host, NULL); return (PAM_SERVICE_ERR); } duo_set_conv_funcs(duo, __duo_prompt, __duo_status, pamh); if (cfg.autopush) { flags |= DUO_FLAG_AUTO; } pam_err = PAM_SERVICE_ERR; for (i = 0; i < cfg.prompts; i++) { code = duo_login(duo, user, host, flags, cfg.pushinfo ? cmd : NULL, cfg.suffix); if (code == DUO_FAIL) { duo_log(LOG_WARNING, "Failed Duo login", user, host, duo_geterr(duo)); if ((flags & DUO_FLAG_SYNC) == 0) { pam_info(pamh, "%s", ""); } /* Keep going */ continue; } /* Terminal conditions */ if (code == DUO_OK) { if ((p = duo_geterr(duo)) != NULL) { duo_log(LOG_WARNING, "Skipped Duo login", user, host, p); } else { duo_log(LOG_INFO, "Successful Duo login", user, host, NULL); } pam_err = PAM_SUCCESS; } else if (code == DUO_ABORT) { duo_log(LOG_WARNING, "Aborted Duo login", user, host, duo_geterr(duo)); pam_err = PAM_ABORT; } else if (cfg.failmode == DUO_FAIL_SAFE && (code == DUO_CONN_ERROR || code == DUO_CLIENT_ERROR || code == DUO_SERVER_ERROR)) { duo_log(LOG_WARNING, "Failsafe Duo login", user, host, duo_geterr(duo)); pam_err = PAM_SUCCESS; } else { duo_log(LOG_ERR, "Error in Duo login", user, host, duo_geterr(duo)); pam_err = PAM_SERVICE_ERR; } break; } if (i == MAX_PROMPTS) { pam_err = PAM_MAXTRIES; } duo_close(duo); return (pam_err); }
static int do_auth(struct login_ctx *ctx, const char *cmd) { struct duo_config cfg; struct passwd *pw; struct in_addr addr; duo_t *duo; duo_code_t code; const char *config, *p, *duouser; const char *ip, *host = NULL; char buf[64]; int i, flags, ret, prompts, matched; int headless = 0; if ((pw = getpwuid(ctx->uid)) == NULL) { die("Who are you?"); } duouser = ctx->duouser ? ctx->duouser : pw->pw_name; config = ctx->config ? ctx->config : DUO_CONF; flags = 0; duo_config_default(&cfg); /* Load our private config. */ if ((i = duo_parse_config(config, __ini_handler, &cfg)) != 0 || (!cfg.apihost || !cfg.apihost[0] || !cfg.skey || !cfg.skey[0] || !cfg.ikey || !cfg.ikey[0])) { switch (i) { case -2: fprintf(stderr, "%s must be readable only by " "user '%s'\n", config, pw->pw_name); break; case -1: fprintf(stderr, "Couldn't open %s: %s\n", config, strerror(errno)); break; case 0: fprintf(stderr, "Missing host, ikey, or skey in %s\n", config); break; default: fprintf(stderr, "Parse error in %s, line %d\n", config, i); break; } /* Implicit "safe" failmode for local configuration errors */ if (cfg.failmode == DUO_FAIL_SAFE) { return (EXIT_SUCCESS); } return (EXIT_FAILURE); } #ifdef OPENSSL_FIPS /* * When fips_mode is configured, invoke OpenSSL's FIPS_mode_set() API. Note * that in some environments, FIPS may be enabled system-wide, causing FIPS * operation to be enabled automatically when OpenSSL is initialized. The * fips_mode option is an experimental feature allowing explicit entry to FIPS * operation in cases where it isn't enabled globally at the OS level (for * example, when integrating directly with the OpenSSL FIPS Object Module). */ if(!FIPS_mode_set(cfg.fips_mode)) { /* The smallest size buff can be according to the openssl docs */ char buff[256]; int error = ERR_get_error(); ERR_error_string_n(error, buff, sizeof(buff)); duo_syslog(LOG_ERR, "Unable to start fips_mode: %s", buff); return (EXIT_FAILURE); } #else if(cfg.fips_mode) { duo_syslog(LOG_ERR, "FIPS mode flag specified, but OpenSSL not built with FIPS support. Failing the auth."); return (EXIT_FAILURE); } #endif prompts = cfg.prompts; /* Check group membership. */ matched = duo_check_groups(pw, cfg.groups, cfg.groups_cnt); if (matched == -1) { close_config(&cfg); return (EXIT_FAILURE); } else if (matched == 0) { close_config(&cfg); return (EXIT_SUCCESS); } /* Use GECOS field if called for */ if ((cfg.send_gecos || cfg.gecos_username_pos >= 0) && !ctx->duouser) { if (strlen(pw->pw_gecos) > 0) { if (cfg.gecos_username_pos >= 0) { duouser = duo_split_at(pw->pw_gecos, cfg.gecos_delim, cfg.gecos_username_pos); if (duouser == NULL || (strcmp(duouser, "") == 0)) { duo_log(LOG_DEBUG, "Could not parse GECOS field", pw->pw_name, NULL, NULL); duouser = pw->pw_name; } } else { duouser = pw->pw_gecos; } } else { duo_log(LOG_WARNING, "Empty GECOS field", pw->pw_name, NULL, NULL); } } /* Check for remote login host */ if ((host = ip = getenv("SSH_CONNECTION")) != NULL || (host = ip = (char *)ctx->host) != NULL) { if (inet_aton(ip, &addr)) { strlcpy(buf, ip, sizeof(buf)); ip = strtok(buf, " "); host = ip; } else { if (cfg.local_ip_fallback) { host = duo_local_ip(); } } } /* Try Duo auth. */ if ((duo = duo_open(cfg.apihost, cfg.ikey, cfg.skey, "login_duo/" PACKAGE_VERSION, cfg.noverify ? "" : cfg.cafile, cfg.https_timeout, cfg.http_proxy)) == NULL) { duo_log(LOG_ERR, "Couldn't open Duo API handle", pw->pw_name, host, NULL); close_config(&cfg); return (EXIT_FAILURE); } /* Special handling for non-interactive sessions */ if ((p = getenv("SSH_ORIGINAL_COMMAND")) != NULL || !isatty(STDIN_FILENO)) { /* Try to support automatic one-shot login */ duo_set_conv_funcs(duo, NULL, NULL, NULL); flags = (DUO_FLAG_SYNC|DUO_FLAG_AUTO); prompts = 1; headless = 1; } else if (cfg.autopush) { /* Special handling for autopush */ duo_set_conv_funcs(duo, NULL, __autopush_status_fn, NULL); flags = (DUO_FLAG_SYNC|DUO_FLAG_AUTO); } if (cfg.accept_env) { flags |= DUO_FLAG_ENV; } ret = EXIT_FAILURE; for (i = 0; i < prompts; i++) { code = duo_login(duo, duouser, host, flags, cfg.pushinfo ? cmd : NULL, cfg.failmode); if (code == DUO_FAIL) { duo_log(LOG_WARNING, "Failed Duo login", duouser, host, duo_geterr(duo)); if ((flags & DUO_FLAG_SYNC) == 0) { printf("\n"); } /* The autopush failed, fall back to regular process */ if (cfg.autopush && i == 0) { flags = 0; duo_reset_conv_funcs(duo); } /* Keep going */ continue; } /* Terminal conditions */ if (code == DUO_OK) { if ((p = duo_geterr(duo)) != NULL) { duo_log(LOG_WARNING, "Skipped Duo login", duouser, host, p); } else { duo_log(LOG_INFO, "Successful Duo login", duouser, host, NULL); } if (cfg.motd && !headless) { _print_motd(); } ret = EXIT_SUCCESS; } else if (code == DUO_ABORT) { duo_log(LOG_WARNING, "Aborted Duo login", duouser, host, duo_geterr(duo)); } else if (code == DUO_FAIL_SAFE_ALLOW) { duo_log(LOG_WARNING, "Failsafe Duo login", duouser, host, duo_geterr(duo)); ret = EXIT_SUCCESS; } else if (code == DUO_FAIL_SECURE_DENY) { duo_log(LOG_WARNING, "Failsecure Duo login", duouser, host, duo_geterr(duo)); } else { duo_log(LOG_ERR, "Error in Duo login", duouser, host, duo_geterr(duo)); } break; } duo_close(duo); close_config(&cfg); return (ret); }