Exemple #1
0
int
main(int argc, char *argv[])
{
	duo_t *duo;
	duo_code_t code;
	char *host, *ikey, *skey, *username;
	int i, flags, retries;

	if (argc != 2) {
		fprintf(stderr, "usage: %s <username>\n", argv[0]);
		exit(1);
	}
	username = argv[1];
	flags = 0;
	retries = 3;
	
	if ((host = getenv("DUO_API_HOST")) == NULL ||
            (ikey = getenv("DUO_IKEY")) == NULL ||
            (skey = getenv("DUO_SKEY")) == NULL) {
		fprintf(stderr, "missing DUO_API_HOST or DUO_IKEY or "
                    "DUO_SKEY environment\n");
		exit(1);
	}
	if ((duo = duo_open(host, ikey, skey, "testduo")) == NULL) {
		fprintf(stderr, "duo_open failed\n");
		exit(1);
	}
	if (getenv("DUO_NO_VERIFY")) {
		duo_set_ssl_verify(duo, 0);
	}
	if (getenv("DUO_SYNC")) {
		flags |= DUO_FLAG_SYNC;
	}
	if (getenv("DUO_AUTO")) {
		    flags |= DUO_FLAG_AUTO;
		    retries = 1;
	}
	for (i = 0; i < retries; i++) {
		code = duo_login(duo, username, NULL, flags, "test");
		
		printf("\n");
		
		if (code == DUO_OK) {
			fprintf(stderr, "(log) OK %s\n", duo_geterr(duo));
		} else if (code == DUO_FAIL) {
			fprintf(stderr, "(log) FAIL %s\n", duo_geterr(duo));
		} else if (code == DUO_ABORT) {
			fprintf(stderr, "(log) ABORT %s\n", duo_geterr(duo));
		} else {
			fprintf(stderr, "(log) ERROR(%d) %s\n", code, duo_geterr(duo));
		}
		if (code == DUO_OK || code != DUO_FAIL)
			break;
	}
	duo_close(duo);
		
	return (0);
}
Exemple #2
0
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);
}
Exemple #3
0
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);
}
Exemple #4
0
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);
}
Exemple #5
0
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;
	duo_t *duo;
	duo_code_t code;
	duopam_const char *config, *cmd, *ip, *p, *service, *user;
	int i, flags, pam_err;

	memset(&cfg, 0, sizeof(cfg));
        cfg.failmode = DUO_FAIL_SAFE;

	/* 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) {
			options |= PAM_OPT_DEBUG;
		} else if (strcmp("try_first_pass", argv[i]) == 0) {
			options |= PAM_OPT_TRY_FIRST_PASS;
		} else if (strcmp("use_first_pass", argv[i]) == 0) {
			options |= PAM_OPT_USE_FIRST_PASS|PAM_OPT_TRY_FIRST_PASS;
		} else if (strcmp("use_uid", argv[i]) == 0) {
			options |= PAM_OPT_USE_UID;
		} else if (strcmp("push", argv[i]) == 0) {
			options |= PAM_OPT_PUSH;
		} else {
			_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) {
		_syslog(LOG_ERR, "%s must be readable only by user 'root'",
		    config);
		return (PAM_SERVICE_ERR);
	} else if (i == -1) {
		_syslog(LOG_ERR, "Couldn't open %s: %s",
		    config, strerror(errno));
		return (PAM_SERVICE_ERR);
	} else if (i > 0) {
		_syslog(LOG_ERR, "Parse error in %s, line %d", config, i);
		return (PAM_SERVICE_ERR);
	} else if (!cfg.host || !cfg.host[0] ||
            !cfg.skey || !cfg.skey[0] || !cfg.ikey || !cfg.ikey[0]) {
		_syslog(LOG_ERR, "Missing host, ikey, or skey in %s", config);
		return (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 (options & PAM_OPT_USE_UID) {
                /* Check calling user for Duo auth, just like sudo */
                if ((pw = getpwuid(getuid())) == NULL) {
                        return (PAM_USER_UNKNOWN);
                }
                user = pw->pw_name;
	}

        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");
        }
	/* Check group membership */
	if (cfg.groups_cnt > 0) {
		int matched = 0;

		if (ga_init(pw->pw_name, pw->pw_gid) < 0) {
			_log(LOG_ERR, "Couldn't get groups",
			    pw->pw_name, NULL, strerror(errno));
			return (PAM_SERVICE_ERR);
		}
		for (i = 0; i < cfg.groups_cnt; i++) {
			if (ga_match_pattern_list(cfg.groups[i])) {
				matched = 1;
				break;
			}
		}
		ga_free();

		/* User in configured groups for Duo auth? */
		if (!matched)
			return (PAM_SUCCESS);
	}

	ip = NULL;
	pam_get_item(pamh, PAM_RHOST,
	    (duopam_const void **)(duopam_const void *)&ip);

	/* Honor configured http_proxy */
	if (cfg.http_proxy != NULL) {
		setenv("http_proxy", cfg.http_proxy, 1);
	}

	/* Try Duo auth */
	if ((duo = duo_open(cfg.host, cfg.ikey, cfg.skey,
                    "pam_duo/" PACKAGE_VERSION,
                    cfg.noverify ? "" : cfg.cafile)) == NULL) {
		_log(LOG_ERR, "Couldn't open Duo API handle", user, ip, NULL);
		return (PAM_SERVICE_ERR);
	}
	duo_set_conv_funcs(duo, __duo_prompt, __duo_status, pamh);

	pam_err = PAM_SERVICE_ERR;
	
	for (i = 0; i < MAX_RETRIES; i++) {
		code = duo_login(duo, user, ip, flags,
                    cfg.pushinfo ? cmd : NULL);
		if (code == DUO_FAIL) {
			_log(LOG_WARNING, "Failed Duo login",
			    user, ip, 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) {
				_log(LOG_WARNING, "Skipped Duo login",
				    user, ip, p);
			} else {
				_log(LOG_INFO, "Successful Duo login",
				    user, ip, NULL);
			}
			pam_err = PAM_SUCCESS;
		} else if (code == DUO_ABORT) {
			_log(LOG_WARNING, "Aborted Duo login",
			    user, ip, 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)) {
			_log(LOG_WARNING, "Failsafe Duo login",
			    user, ip, duo_geterr(duo));
			pam_err = PAM_SUCCESS;
		} else {
			_log(LOG_ERR, "Error in Duo login",
			    user, ip, duo_geterr(duo));
			pam_err = PAM_SERVICE_ERR;
		}
		break;
	}
	if (i == MAX_RETRIES) {
		pam_err = PAM_MAXTRIES;
	}
	duo_close(duo);
	
	return (pam_err);
}