Пример #1
0
PAM_EXTERN int
pam_sm_open_session(pam_handle_t *pamh, int flags,
		    int argc, const char **argv)
{
	const struct passwd *pwent;
	const char *user;
	struct stat di;
	void (*sh)(int);
	pid_t child;
	int res;

	/* Who are we talking about anyway? */
	res = pam_get_user(pamh, &user, NULL);
	if (res != PAM_SUCCESS)
		return res;

	/* Fetch passwd entry */
	pwent = getpwnam(user);
	if (!pwent)
	{
		pam_error(pamh, "User not found in passwd?");
		return PAM_CRED_INSUFFICIENT;
	}

	openlog("pam_mkhomedir", LOG_PID, LOG_AUTH);
	if (stat(pwent->pw_dir, &di))
		return pam_mkhd_copy(pamh, pwent, "/etc/skel", pwent->pw_dir);

	return PAM_SUCCESS;
}
Пример #2
0
context::context(const std::string& service, const std::string& username)
{
    auto s = app::clone(service), u = username.size() ? app::clone(username) : nullptr;
    pam_conv conv = { despatch, this };

    _M_code = pam_start(s.get(), u.get(), &conv, &_M_pamh);
    if(errc(_M_code) != errc::success) throw pam_error(_M_code);
}
Пример #3
0
/* now the session stuff */
PAM_EXTERN int
pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
		     int argc, const char **argv)
{
    int retval;
    char *user_name;
    struct passwd *pwd;
    int ctrl;
    struct pam_limit_s pl;

    D(("called."));

    memset(&pl, 0, sizeof(pl));

    ctrl = _pam_parse(pamh, argc, argv, &pl);
    retval = pam_get_item( pamh, PAM_USER, (void*) &user_name );
    if ( user_name == NULL || retval != PAM_SUCCESS ) {
        pam_syslog(pamh, LOG_CRIT, "open_session - error recovering username");
        return PAM_SESSION_ERR;
     }

    pwd = getpwnam(user_name);
    if (!pwd) {
        if (ctrl & PAM_DEBUG_ARG)
            pam_syslog(pamh, LOG_WARNING,
		       "open_session username '%s' does not exist", user_name);
        return PAM_SESSION_ERR;
    }

    retval = init_limits(&pl);
    if (retval != PAM_SUCCESS) {
        pam_syslog(pamh, LOG_WARNING, "cannot initialize");
        return PAM_ABORT;
    }

    retval = parse_config_file(pamh, pwd->pw_name, ctrl, &pl);
    if (retval == PAM_IGNORE) {
	D(("the configuration file has an applicable '<domain> -' entry"));
	return PAM_SUCCESS;
    }
    if (retval != PAM_SUCCESS) {
        pam_syslog(pamh, LOG_WARNING, "error parsing the configuration file");
        return retval;
    }

    if (ctrl & PAM_DO_SETREUID) {
	setreuid(pwd->pw_uid, -1);
    }
    retval = setup_limits(pamh, pwd->pw_name, pwd->pw_uid, ctrl, &pl);
    if (retval & LOGIN_ERR)
	pam_error(pamh, _("Too many logins for '%s'."), pwd->pw_name);
    if (retval != LIMITED_OK) {
        return PAM_PERM_DENIED;
    }

    return PAM_SUCCESS;
}
Пример #4
0
static void test_pam_prompt(void **state)
{
	struct pwrap_test_ctx *test_ctx;
	int rv;
	char *response;
	int resp_array[2];

	test_ctx = (struct pwrap_test_ctx *) *state;

	memset(resp_array, 0, sizeof(resp_array));

	test_ctx->conv.conv = pwrap_echo_conv;
	test_ctx->conv.appdata_ptr = resp_array;

	rv = pam_start("matrix", "trinity",
		       &test_ctx->conv, &test_ctx->ph);
	assert_int_equal(rv, PAM_SUCCESS);

	rv = pam_prompt(test_ctx->ph, PAM_PROMPT_ECHO_OFF, &response, "no echo");
	assert_int_equal(rv, PAM_SUCCESS);
	assert_string_equal(response, "echo off: no echo");
	free(response);

	rv = vprompt_test_fn(test_ctx->ph, PAM_PROMPT_ECHO_OFF, &response, "no echo");
	assert_int_equal(rv, PAM_SUCCESS);
	assert_string_equal(response, "echo off: no echo");
	free(response);

	rv = pam_prompt(test_ctx->ph, PAM_PROMPT_ECHO_ON, &response, "echo");
	assert_int_equal(rv, PAM_SUCCESS);
	assert_string_equal(response, "echo on: echo");
	free(response);

	rv = vprompt_test_fn(test_ctx->ph, PAM_PROMPT_ECHO_ON, &response, "echo");
	assert_int_equal(rv, PAM_SUCCESS);
	assert_string_equal(response, "echo on: echo");
	free(response);

	assert_int_equal(resp_array[0], 0);
	pam_info(test_ctx->ph, "info");
	assert_int_equal(resp_array[0], 1);

	assert_int_equal(resp_array[1], 0);
	pam_error(test_ctx->ph, "error");
	assert_int_equal(resp_array[1], 1);
}
Пример #5
0
/* now the session stuff */
PAM_EXTERN int
pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
		     int argc, const char **argv)
{
    int retval;
    int i;
    int glob_rc;
    char *user_name;
    struct passwd *pwd;
    int ctrl;
    struct pam_limit_s plstruct;
    struct pam_limit_s *pl = &plstruct;
    glob_t globbuf;
    const char *oldlocale;

    D(("called."));

    memset(pl, 0, sizeof(*pl));
    memset(&globbuf, 0, sizeof(globbuf));

    ctrl = _pam_parse(pamh, argc, argv, pl);
    retval = pam_get_item( pamh, PAM_USER, (void*) &user_name );
    if ( user_name == NULL || retval != PAM_SUCCESS ) {
        pam_syslog(pamh, LOG_CRIT, "open_session - error recovering username");
        return PAM_SESSION_ERR;
     }

    pwd = pam_modutil_getpwnam(pamh, user_name);
    if (!pwd) {
        if (ctrl & PAM_DEBUG_ARG)
            pam_syslog(pamh, LOG_WARNING,
		       "open_session username '%s' does not exist", user_name);
        return PAM_USER_UNKNOWN;
    }

    retval = init_limits(pamh, pl, ctrl);
    if (retval != PAM_SUCCESS) {
        pam_syslog(pamh, LOG_WARNING, "cannot initialize");
        return PAM_ABORT;
    }

    retval = parse_config_file(pamh, pwd->pw_name, pwd->pw_uid, pwd->pw_gid, ctrl, pl);
    if (retval == PAM_IGNORE) {
	D(("the configuration file ('%s') has an applicable '<domain> -' entry", CONF_FILE));
	return PAM_SUCCESS;
    }
    if (retval != PAM_SUCCESS || pl->conf_file != NULL)
	/* skip reading limits.d if config file explicitely specified */
	goto out;

    /* Read subsequent *.conf files, if they exist. */

    /* set the LC_COLLATE so the sorting order doesn't depend
	on system locale */

    oldlocale = setlocale(LC_COLLATE, "C");
    glob_rc = glob(LIMITS_CONF_GLOB, GLOB_ERR, NULL, &globbuf);

    if (oldlocale != NULL)
	setlocale (LC_COLLATE, oldlocale);

    if (!glob_rc) {
	/* Parse the *.conf files. */
	for (i = 0; globbuf.gl_pathv[i] != NULL; i++) {
	    pl->conf_file = globbuf.gl_pathv[i];
    	    retval = parse_config_file(pamh, pwd->pw_name, pwd->pw_uid, pwd->pw_gid, ctrl, pl);
    	    if (retval == PAM_IGNORE) {
		D(("the configuration file ('%s') has an applicable '<domain> -' entry", pl->conf_file));
		globfree(&globbuf);
		return PAM_SUCCESS;
      	    }
	    if (retval != PAM_SUCCESS)
		goto out;
        }
    }

out:
    globfree(&globbuf);
    if (retval != PAM_SUCCESS)
    {
       	pam_syslog(pamh, LOG_WARNING, "error parsing the configuration file: '%s' ",CONF_FILE);
	return retval;
    }

    retval = setup_limits(pamh, pwd->pw_name, pwd->pw_uid, ctrl, pl);
    if (retval & LOGIN_ERR)
	pam_error(pamh, _("Too many logins for '%s'."), pwd->pw_name);
    if (retval != LIMITED_OK) {
        return PAM_PERM_DENIED;
    }

    return PAM_SUCCESS;
}
Пример #6
0
int
pam_sm_chauthtok (pam_handle_t *pamh, int flags, int argc, const char **argv)
{
  struct passwd *pwd;
  const char *newpass;
  const char *user;
    int retval, tries;
  options_t options;

  memset (&options, 0, sizeof (options));

  /* Set some default values, which could be overwritten later.  */
  options.remember = 10;
  options.tries = 1;

  /* Parse parameters for module */
  for ( ; argc-- > 0; argv++)
    parse_option (pamh, *argv, &options);

  if (options.debug)
    pam_syslog (pamh, LOG_DEBUG, "pam_sm_chauthtok entered");


  if (options.remember == 0)
    return PAM_IGNORE;

  retval = pam_get_user (pamh, &user, NULL);
  if (retval != PAM_SUCCESS)
    return retval;

  if (user == NULL || strlen (user) == 0)
    {
      if (options.debug)
	pam_syslog (pamh, LOG_DEBUG,
		    "User is not known to system");

      return PAM_USER_UNKNOWN;
    }

  if (flags & PAM_PRELIM_CHECK)
    {
      if (options.debug)
	pam_syslog (pamh, LOG_DEBUG,
		    "pam_sm_chauthtok(PAM_PRELIM_CHECK)");

      return PAM_SUCCESS;
    }

  pwd = pam_modutil_getpwnam (pamh, user);
  if (pwd == NULL)
    return PAM_USER_UNKNOWN;

  if ((strcmp(pwd->pw_passwd, "x") == 0)  ||
      ((pwd->pw_passwd[0] == '#') &&
       (pwd->pw_passwd[1] == '#') &&
       (strcmp(pwd->pw_name, pwd->pw_passwd + 2) == 0)))
    {
      struct spwd *spw = pam_modutil_getspnam (pamh, user);
      if (spw == NULL)
	return PAM_USER_UNKNOWN;

      retval = save_old_pass (pamh, user, pwd->pw_uid, spw->sp_pwdp,
			      options.remember, options.debug);
      if (retval != PAM_SUCCESS)
	return retval;
    }
  else
    {
      retval = save_old_pass (pamh, user, pwd->pw_uid, pwd->pw_passwd,
			      options.remember, options.debug);
      if (retval != PAM_SUCCESS)
	return retval;
    }

  newpass = NULL;
  tries = 0;
  while ((newpass == NULL) && (tries < options.tries))
    {
      retval = pam_get_authtok (pamh, PAM_AUTHTOK, &newpass, NULL);
      if (retval != PAM_SUCCESS && retval != PAM_TRY_AGAIN)
	{
	  if (retval == PAM_CONV_AGAIN)
	    retval = PAM_INCOMPLETE;
	  return retval;
	}
      tries++;

      if (options.debug)
	{
	  if (newpass)
	    pam_syslog (pamh, LOG_DEBUG, "got new auth token");
	  else
	    pam_syslog (pamh, LOG_DEBUG, "got no auth token");
	}

      if (newpass == NULL || retval == PAM_TRY_AGAIN)
	continue;

      if (options.debug)
	pam_syslog (pamh, LOG_DEBUG, "check against old password file");

      if (check_old_pass (pamh, user, newpass,
			  options.debug) != PAM_SUCCESS)
	{
	  if (getuid() || options.enforce_for_root ||
	      (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
	    {
	      pam_error (pamh,
		         _("Password has been already used. Choose another."));
	      newpass = NULL;
	      /* Remove password item, else following module will use it */
	      pam_set_item (pamh, PAM_AUTHTOK, (void *) NULL);
	    }
	  else
	    pam_info (pamh,
		       _("Password has been already used."));
	}
    }

  if (newpass == NULL && tries >= options.tries)
    {
      if (options.debug)
	pam_syslog (pamh, LOG_DEBUG, "Aborted, too many tries");
      return PAM_MAXTRIES;
    }

  return PAM_SUCCESS;
}
Пример #7
0
PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t *pamh, int flags,
                 int argc, const char **argv)
{
        int ctrl;
        struct module_options options;

        memset(&options, 0, sizeof(options));
        options.retry_times = CO_RETRY_TIMES;

        ctrl = _pam_parse(pamh, &options, argc, argv);
        if (ctrl < 0)
                return PAM_BUF_ERR;

        if (flags & PAM_PRELIM_CHECK) {
                /* Check for passwd dictionary
                 * We cannot do that, since the original path is compiled
                 * into the cracklib library and we don't know it.
                 */
                return PAM_SUCCESS;
        } else if (flags & PAM_UPDATE_AUTHTOK) {
                int retval;
                const void *oldtoken;
                const char *user;
                int tries;

                retval = pam_get_user(pamh, &user, NULL);
                if (retval != PAM_SUCCESS || user == NULL) {
                        if (ctrl & PAM_DEBUG_ARG)
                                pam_syslog(pamh, LOG_ERR, "Can not get username");
                        return PAM_AUTHTOK_ERR;
                }

                retval = pam_get_item(pamh, PAM_OLDAUTHTOK, &oldtoken);
                if (retval != PAM_SUCCESS) {
                        if (ctrl & PAM_DEBUG_ARG)
                                pam_syslog(pamh, LOG_ERR, "Can not get old passwd");
                        oldtoken = NULL;
                }

                tries = 0;
                while (tries < options.retry_times) {
                        void *auxerror;
                        const char *newtoken = NULL;

                        tries++;

                        /* Planned modus operandi:
                         * Get a passwd.
                         * Verify it against libpwquality.
                         * If okay get it a second time.
                         * Check to be the same with the first one.
                         * set PAM_AUTHTOK and return
                         */

                        retval = pam_get_authtok_noverify(pamh, &newtoken, NULL);
                        if (retval != PAM_SUCCESS) {
                                pam_syslog(pamh, LOG_ERR, "pam_get_authtok_noverify returned error: %s",
                                        pam_strerror(pamh, retval));
                                continue;
                        } else if (newtoken == NULL) { /* user aborted password change, quit */
                                return PAM_AUTHTOK_ERR;
                        }

                        /* now test this passwd against libpwquality */
                        retval = pwquality_check(options.pwq, newtoken, oldtoken, user, &auxerror);

                        if (retval < 0) {
                                const char *msg;
                                char buf[PWQ_MAX_ERROR_MESSAGE_LEN];
                                msg = pwquality_strerror(buf, sizeof(buf), retval, auxerror);
                                if (ctrl & PAM_DEBUG_ARG)
                                        pam_syslog(pamh, LOG_DEBUG, "bad password: %s", msg);
                                pam_error(pamh, _("BAD PASSWORD: %s"), msg);

                                if (getuid() || options.enforce_for_root ||
                                    (flags & PAM_CHANGE_EXPIRED_AUTHTOK)) {
                                        pam_set_item(pamh, PAM_AUTHTOK, NULL);
                                        retval = PAM_AUTHTOK_ERR;
                                        continue;
                                }
                        } else {
                                if (ctrl & PAM_DEBUG_ARG)
                                        pam_syslog(pamh, LOG_DEBUG, "password score: %d", retval);
                        }

                        retval = pam_get_authtok_verify(pamh, &newtoken, NULL);
                        if (retval != PAM_SUCCESS) {
                                pam_syslog(pamh, LOG_ERR, "pam_get_authtok_verify returned error: %s",
                                pam_strerror(pamh, retval));
                                pam_set_item(pamh, PAM_AUTHTOK, NULL);
                                continue;
                        } else if (newtoken == NULL) {      /* user aborted password change, quit */
                                return PAM_AUTHTOK_ERR;
                        }

                        return PAM_SUCCESS;
                }

                pam_set_item (pamh, PAM_AUTHTOK, NULL);

                /* if we have only one try, we can use the real reason,
                 * else say that there were too many tries. */
                if (options.retry_times > 1)
                        return PAM_MAXTRIES;
                else
                        return retval;
        } else {
                if (ctrl & PAM_DEBUG_ARG)
                        pam_syslog(pamh, LOG_NOTICE, "UNKNOWN flags setting %02X",flags);
        }

        return PAM_SERVICE_ERR;
}
Пример #8
0
static int
pam_mkhd_copy(pam_handle_t *pamh, const struct passwd *pwent,
	const char *from, const char *to)
{
	char *newfrom, *newto;
	struct dirent *dentry;
	struct stat di;
	ssize_t len;
	DIR *dirp;
	int ffd, tfd, ret;

	if (lstat(from, &di))
	{
		pam_error(pamh, "Unable to stat %s: %s", from, strerror(errno));
		return PAM_PERM_DENIED;
	}

	if (S_ISDIR(di.st_mode))
	{
		if (mkdir(to, di.st_mode & 07777))
		{
			pam_error(pamh, "Creating directory %s failed: %s",
				to, strerror(errno));
			return PAM_PERM_DENIED;
		}

		if (chown(to, pwent->pw_uid, pwent->pw_gid))
		{
			pam_error(pamh, "Setting ownership of %s failed: %s",
				to, strerror(errno));
			return PAM_PERM_DENIED;
		}

		dirp = opendir(from);
		if (!dirp)
		{
			pam_error(pamh, "Unable to open %s: %s", from,
				strerror(errno));
			return PAM_PERM_DENIED;
		}

		while ((dentry = readdir(dirp)))
		{
			if (dentry->d_name[0] == '.' &&
			    (dentry->d_name[1] == '\0' ||
				(dentry->d_name[1] == '.' &&
				 dentry->d_name[2] == '\0')))
				continue;

			newfrom = malloc(strlen(from) + strlen(dentry->d_name) +
				2);
			if (!newfrom)
			{
				closedir(dirp);
				return PAM_PERM_DENIED;
			}

			memset(newfrom, 0, strlen(from) + strlen(dentry->d_name) + 2);
			strlcpy(newfrom, from, strlen(from) + 1);
			newfrom[strlen(from)] = '/';
			strlcpy(newfrom + strlen(from) + 1, dentry->d_name, strlen(dentry->d_name) + 1);
			newfrom[strlen(from) + strlen(dentry->d_name) + 2] =
				'\0';

			newto = malloc(strlen(to) + strlen(dentry->d_name) + 2);
			if (!newto)
			{
				free(newfrom);
				closedir(dirp);
				return PAM_PERM_DENIED;
			}

			strlcpy(newto, to, strlen(to) + 1);
			newto[strlen(to)] = '/';
			strlcpy(newto + strlen(to) + 1, dentry->d_name, strlen(dentry->d_name) + 1);
			newto[strlen(to) + strlen(dentry->d_name) + 2] = '\0';

			ret = pam_mkhd_copy(pamh, pwent, newfrom, newto);

			free(newto);
			free(newfrom);

			if (ret != PAM_SUCCESS)
			{
				closedir(dirp);
				return ret;
			}
		}

		closedir(dirp);
	}
	else if (S_ISLNK(di.st_mode))
	{
		newto = malloc(di.st_size) + 1;
		memset(newto, 0, di.st_size + 1);

		if (readlink(from, newto, di.st_size + 1) < 0)
		{
			pam_error(pamh, "Readlink on %s failed: %s", from,
				strerror(errno));
			free(newto);
			return PAM_PERM_DENIED;
		}

		if (symlink(newto, to))
		{
			pam_error(pamh, "Creating symlink %s failed: %s", to,
				strerror(errno));
			free(newto);
			return PAM_PERM_DENIED;
		}

		free(newto);

		if (lchmod(to, di.st_mode & 07777))
		{
			pam_error(pamh, "Changing permissions of symlink %s failed: %s",
				to, strerror(errno));
			return PAM_PERM_DENIED;
		}

		if (lchown(to, pwent->pw_uid, pwent->pw_gid))
		{
			pam_error(pamh, "Changing ownership of symlink %s failed: %s",
				to, strerror(errno));
			return PAM_PERM_DENIED;
		}
	}
	else if (S_ISREG(di.st_mode))
	{
		ffd = open(from, O_RDONLY, 0);
		if (ffd == -1)
		{
			pam_error(pamh, "Opening %s for reading failed: %s",
				from, strerror(errno));
			return PAM_PERM_DENIED;
		}

		tfd = open(to, O_WRONLY | O_CREAT | O_TRUNC, di.st_mode & 07777);
		if (ffd == -1)
		{
			pam_error(pamh, "Opening %s for writing failed: %s",
				to, strerror(errno));
			close(ffd);
			return PAM_PERM_DENIED;
		}

		newto = malloc(65536);
		if (!newto)
		{
			pam_error(pamh, "Unable to allocate buffer: %s",
				strerror(errno));
			close(tfd);
			close(ffd);
			return PAM_PERM_DENIED;
		}

		while ((len = read(ffd, newto, 65546)) > 0)
		{
			if (write(tfd, newto, len) == -1)
			{
				pam_error(pamh, "Unable to write to %s: %s",
					to, strerror(errno));
				free(newto);
				close(tfd);
				close(ffd);
				return PAM_PERM_DENIED;
			}
		}

		free(newto);
		if (len == -1)
		{
			pam_error(pamh, "Unable to read from %s: %s", from,
				strerror(errno));
			close(tfd);
			close(ffd);
			return PAM_PERM_DENIED;
		}

		if (close(tfd))
		{
			pam_error(pamh, "Unable to close %s: %s", to,
				strerror(errno));
			close(ffd);
			return PAM_PERM_DENIED;
		}

		if (close(ffd))
		{
			pam_error(pamh, "Unable to close %s: %s", from,
				strerror(errno));
			return PAM_PERM_DENIED;
		}

		if (chown(to, pwent->pw_uid, pwent->pw_gid))
		{
			pam_error(pamh, "Changing ownership of symlink %s failed: %s",
				to, strerror(errno));
			return PAM_PERM_DENIED;
		}
	}

	return PAM_SUCCESS;
}
Пример #9
0
// CALLED BY PAM_AUTHENTICATE
PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags,
				   int argc, const char **argv)
{
    module_config *cfg = NULL;
    user_config *user_cfg = NULL;
    int retval;
    unsigned int trial;
    const char *authtok = NULL;

    retval = pam_get_item(pamh, PAM_AUTHTOK, (const void **) &authtok);
    if (retval != PAM_SUCCESS || (authtok != NULL && !strcmp(authtok, AUTHTOK_INCORRECT))) {
        D(("Previous authentication failed, let's stop here!"));
	return PAM_AUTH_ERR;
    }

    retval = parse_config(pamh, argc, argv, &cfg);

    //CHECK PAM CONFIGURATION
    if (retval == CONFIG_ERROR) {
        D(("Invalid configuration"));
	pam_syslog(pamh, LOG_ERR, "Invalid parameters to pam_2fa module");
	pam_error(pamh, "Sorry, 2FA Pam Module is misconfigured, please contact admins!\n");
        return PAM_AUTH_ERR;
    }

    // Get User configuration
    user_cfg = get_user_config(pamh, cfg);
    if(!user_cfg) {
	pam_syslog(pamh, LOG_INFO, "Unable to get user configuration");
        // cleanup
        free_config(cfg);
	return PAM_AUTH_ERR;
    }

    const auth_mod *available_mods[4] = { NULL, NULL, NULL, NULL };
    int menu_len = 0;

    if (cfg->gauth_enabled && user_cfg->gauth_login[0] != '\0') {
#ifdef HAVE_CURL
	++menu_len;
	available_mods[menu_len] = &gauth_auth;
#else
	DBG(("GAuth configured, but CURL not compiled (should never happen!)"));
#endif
    }
    if (cfg->sms_enabled && user_cfg->sms_mobile[0] != '\0') {
	++menu_len;
	available_mods[menu_len] = &sms_auth;
    }
    if (cfg->yk_enabled && user_cfg->yk_publicids) {
#ifdef HAVE_YKCLIENT
	++menu_len;
	available_mods[menu_len] = &yk_auth;
#else
	DBG(("Yubikey configured, but ykclient not compiled (should never happen!)"));
#endif
    }

    retval = PAM_AUTH_ERR;
    for (trial = 0; trial < cfg->retry && retval != PAM_SUCCESS; ++trial) {
        const auth_mod *selected_auth_mod = NULL;
        char *user_input = NULL;
        if (menu_len > 1) {
            size_t user_input_len;
            int i = 1;

            pam_info(pamh, "Login for %s:\n", user_cfg->username);
            for (i = 1; i <= menu_len; ++i) {
                pam_info(pamh, "        %d. %s", i, available_mods[i]->name);
            }

            if (pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &user_input, "\nOption (1-%d): ", menu_len) != PAM_SUCCESS) {
        	pam_syslog(pamh, LOG_INFO, "Unable to get 2nd factors for user '%s'", user_cfg->username);
        	pam_error(pamh, "Unable to get user input");
        	retval = PAM_AUTH_ERR;
		break;
            }

            user_input_len = user_input ? strlen(user_input) : 0;
            for (i = 1; i <= menu_len; ++i) {
                if (available_mods[i]->pre_auth == NULL && available_mods[i]->otp_len) {
                    if (user_input_len == available_mods[i]->otp_len) {
                        selected_auth_mod = available_mods[i];
                        break;
                    }
                }
            }
            if (selected_auth_mod == NULL) {
                if (user_input_len == 1 && user_input[0] >= '1' && user_input[0] <= menu_len + '0') {
                    selected_auth_mod = available_mods[user_input[0] - '0'];
                    free(user_input);
                    user_input = NULL;
                } else {
                    pam_error(pamh, "Invalid input");
                    free(user_input);
                    user_input = NULL;
                }
            }
        } else if (menu_len == 1) {
            selected_auth_mod = available_mods[1];
        } else {
	    pam_syslog(pamh, LOG_INFO, "No supported 2nd factor for user '%s'", user_cfg->username);
	    pam_error(pamh, "No supported 2nd factors for user '%s'", user_cfg->username);
	    retval = PAM_AUTH_ERR;
            break;
        }
        if (selected_auth_mod != NULL) {
            void * pre_auth_data = NULL;
            if (selected_auth_mod->pre_auth != NULL) {
                 pre_auth_data = selected_auth_mod->pre_auth(pamh, user_cfg, cfg);
                 if (pre_auth_data == NULL)
                     continue;
            }
            if (user_input == NULL) {
                if (pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &user_input, "%s", selected_auth_mod->prompt) != PAM_SUCCESS) {
                    pam_syslog(pamh, LOG_INFO, "Unable to get %s", selected_auth_mod->prompt);
                    pam_error(pamh, "Unable to get user input");
                    free(pre_auth_data);
                    retval = PAM_AUTH_ERR;
                    break;
                }
            }
            retval = selected_auth_mod->do_auth(pamh, user_cfg, cfg, user_input, pre_auth_data);
            free(user_input);
        }
    }

    // final cleanup
    free_user_config(user_cfg);
    free_config(cfg);
    return retval;
}