1
0
mirror of https://github.com/Yubico/yubico-pam.git synced 2025-02-01 10:52:17 +01:00

Return early if the user has no authorized tokens

Currently, if a user has no associated tokens, we still prompt for an
OTP challenge and attempt to verify it.

This adds a check earlier to avoid the useless prompt in that case.

The `nullok` option is also added. It changes the return value from
PAM_USER_UNKNOWN to PAM_IGNORE. (fixes #97)

Finally, some constants have been turned to symbolic form for clarity
and debugging output is improved.
This commit is contained in:
Mickaël Thomas 2017-02-26 17:53:46 +01:00
parent 0ce0e63d26
commit 7b6aad719a
5 changed files with 100 additions and 44 deletions

6
README
View File

@ -171,6 +171,12 @@ stacked modules password and will never prompt the user - if no
password is available or the password is not appropriate, the user
will be denied access.
nullok::
If set, don't fail when there are no tokens declared for the user
in the authorization mapping files or in LDAP.
This can be used to make YubiKey authentication optional unless
the user has associated tokens.
urllist::
List of URL templates to be used. This is set by calling
ykclient_set_url_bases. The list should be in the format :

View File

@ -108,6 +108,7 @@ struct cfg
int verbose_otp;
int try_first_pass;
int use_first_pass;
int nullok;
const char *auth_file;
const char *capath;
const char *cainfo;
@ -136,8 +137,9 @@ struct cfg
#define DBG(x...) if (cfg->debug) { D(cfg->debug_file, x); }
/*
* Authorize authenticated OTP_ID for login as USERNAME using
* AUTHFILE. Return -2 if the user is unknown, -1 if the OTP_ID does not match, 0 on internal failures, otherwise success.
* Authorize authenticated OTP_ID for login as USERNAME using AUTHFILE.
*
* Returns one of AUTH_FOUND, AUTH_NOT_FOUND, AUTH_NO_TOKENS, AUTH_ERROR.
*/
static int
authorize_user_token (struct cfg *cfg,
@ -145,7 +147,7 @@ authorize_user_token (struct cfg *cfg,
const char *otp_id,
pam_handle_t *pamh)
{
int retval;
int retval = AUTH_ERROR;
if (cfg->auth_file)
{
@ -167,7 +169,7 @@ authorize_user_token (struct cfg *cfg,
pwres = getpwnam_r (username, &pass, buf, buflen, &p);
if (p == NULL) {
DBG ("getpwnam_r: %s", strerror(pwres));
return 0;
return AUTH_ERROR;
}
/* Getting file from user home directory
@ -175,21 +177,19 @@ authorize_user_token (struct cfg *cfg,
*/
if (! get_user_cfgfile_path (NULL, "authorized_yubikeys", p, &userfile)) {
DBG ("Failed figuring out per-user cfgfile");
return 0;
return AUTH_ERROR;
}
DBG ("Dropping privileges");
if(pam_modutil_drop_priv(pamh, &privs, p)) {
DBG ("could not drop privileges");
retval = 0;
goto free_out;
goto free_out;
}
retval = check_user_token (userfile, username, otp_id, cfg->debug, cfg->debug_file);
if(pam_modutil_regain_priv(pamh, &privs)) {
DBG (("could not restore privileges"));
retval = 0;
DBG ("could not restore privileges");
goto free_out;
}
@ -202,7 +202,7 @@ free_out:
/*
* This function will look in ldap id the token correspond to the
* requested user. It will returns 0 for failure and 1 for success.
* requested user.
*
* ldaps is only supported for ldap_uri based connections.
* ldap_cacertfile usually needs to be set for this to work.
@ -218,13 +218,15 @@ free_out:
* If using ldap_uri, you can specify multiple failover hosts
* eg.
* ldap_uri=ldaps://host1.fqdn.example.com,ldaps://host2.fqdn.example.com
*
* Returns one of AUTH_FOUND, AUTH_NOT_FOUND, AUTH_NO_TOKENS, AUTH_ERROR.
*/
static int
authorize_user_token_ldap (struct cfg *cfg,
const char *user,
const char *token_id)
{
int retval = 0;
int retval = AUTH_ERROR;
#ifdef HAVE_LIBLDAP
/* LDAPv2 is historical -- RFC3494. */
int protocol = LDAP_VERSION3;
@ -261,7 +263,6 @@ authorize_user_token_ldap (struct cfg *cfg,
if (rc != LDAP_SUCCESS)
{
DBG ("ldap_initialize: %s", ldap_err2string (rc));
retval = 0;
goto done;
}
}
@ -270,7 +271,6 @@ authorize_user_token_ldap (struct cfg *cfg,
if ((ld = ldap_init (cfg->ldapserver, PORT_NUMBER)) == NULL)
{
DBG ("ldap_init");
retval = 0;
goto done;
}
}
@ -301,7 +301,6 @@ authorize_user_token_ldap (struct cfg *cfg,
i = (strlen(cfg->user_attr) + strlen(cfg->ldapdn) + strlen(user) + 3) * sizeof(char);
if ((find = malloc(i)) == NULL) {
DBG ("Failed allocating %zu bytes", i);
retval = 0;
goto done;
}
sprintf (find, "%s=%s,%s", cfg->user_attr, user, cfg->ldapdn);
@ -325,19 +324,19 @@ authorize_user_token_ldap (struct cfg *cfg,
{
DBG ("ldap_search_ext_s: %s", ldap_err2string (rc));
retval = 0;
goto done;
}
/* Start looing for tokens */
retval = AUTH_NO_TOKENS;
e = ldap_first_entry (ld, result);
if (e == NULL)
{
DBG (("No result from LDAP search"));
retval = -2;
}
else
{
retval = -1;
/* Iterate through each returned attribute. */
for (a = ldap_first_attribute (ld, e, &ber);
a != NULL; a = ldap_next_attribute (ld, e, ber))
@ -357,10 +356,15 @@ authorize_user_token_ldap (struct cfg *cfg,
/* Only values containing this prefix are considered. */
if ((!cfg->yubi_attr_prefix || !strncmp (cfg->yubi_attr_prefix, vals[i]->bv_val, yubi_attr_prefix_len)))
{
if(!strncmp (token_id, vals[i]->bv_val + yubi_attr_prefix_len, strlen (vals[i]->bv_val + yubi_attr_prefix_len)))
/* We have found at least one possible token ID so change the default return value to AUTH_NOT_FOUND */
if (retval == AUTH_NO_TOKENS)
{
retval = AUTH_NOT_FOUND;
}
if(token_id && !strncmp (token_id, vals[i]->bv_val + yubi_attr_prefix_len, strlen (vals[i]->bv_val + yubi_attr_prefix_len)))
{
DBG ("Token Found :: %s", vals[i]->bv_val);
retval = 1;
retval = AUTH_FOUND;
}
}
}
@ -713,6 +717,8 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg)
cfg->try_first_pass = 1;
if (strcmp (argv[i], "use_first_pass") == 0)
cfg->use_first_pass = 1;
if (strcmp (argv[i], "nullok") == 0)
cfg->nullok = 1;
if (strncmp (argv[i], "authfile=", 9) == 0)
cfg->auth_file = argv[i] + 9;
if (strncmp (argv[i], "capath=", 7) == 0)
@ -962,6 +968,37 @@ pam_sm_authenticate (pam_handle_t * pamh,
goto done;
}
}
/* check if the user has at least one associated token id */
/* we set otp_id to NULL so that no matches will ever be found
* but AUTH_NO_TOKENS will be returned if there are no tokens for the user */
if (cfg->ldapserver != NULL || cfg->ldap_uri != NULL)
valid_token = authorize_user_token_ldap (cfg, user, NULL);
else
valid_token = authorize_user_token (cfg, user, NULL, pamh);
switch(valid_token)
{
case AUTH_ERROR:
DBG ("Internal error while looking for user tokens");
retval = PAM_AUTHINFO_UNAVAIL;
goto done;
case AUTH_NOT_FOUND:
/* User has associated tokens, so continue */
DBG ("Tokens found for user");
break;
case AUTH_NO_TOKENS:
DBG ("No tokens found for user");
if (cfg->nullok) {
retval = PAM_IGNORE;
} else {
retval = PAM_USER_UNKNOWN;
}
goto done;
default:
DBG ("Unhandled value while looking for user tokens");
retval = PAM_AUTHINFO_UNAVAIL;
goto done;
}
if (password == NULL)
{
@ -1066,7 +1103,8 @@ pam_sm_authenticate (pam_handle_t * pamh,
switch(valid_token)
{
case 1:
case AUTH_FOUND:
DBG ("Token is associated to the user. Validating the OTP...");
rc = ykclient_request (ykc, otp);
DBG ("ykclient return value (%d): %s", rc, ykclient_strerror (rc));
DBG ("ykclient url used: %s", ykclient_get_last_url(ykc));
@ -1087,21 +1125,26 @@ pam_sm_authenticate (pam_handle_t * pamh,
break;
}
break;
case 0:
DBG ("Internal error while validating user");
case AUTH_ERROR:
DBG ("Internal error while looking for user tokens");
retval = PAM_AUTHINFO_UNAVAIL;
break;
case -1:
case AUTH_NOT_FOUND:
DBG ("Unauthorized token for this user");
retval = PAM_AUTH_ERR;
break;
case -2:
DBG ("Unknown user");
retval = PAM_USER_UNKNOWN;
case AUTH_NO_TOKENS:
DBG ("No tokens found for user");
if (cfg->nullok) {
retval = PAM_IGNORE;
} else {
retval = PAM_USER_UNKNOWN;
}
break;
default:
DBG ("Unhandled value for token-user validation");
retval = PAM_AUTHINFO_UNAVAIL;
break;
}
done:

View File

@ -75,27 +75,27 @@ static void test_check_user_token(void) {
fclose(handle);
ret = check_user_token(file, "foobar", "hhhvhvhdhbid", 1, stdout);
assert(ret == 1);
assert(ret == AUTH_FOUND);
ret = check_user_token(file, "foobar", "hnhbhnhbhnhb", 1, stdout);
assert(ret == 1);
assert(ret == AUTH_FOUND);
ret = check_user_token(file, "foobar", "hnhbhnhbhnhc", 1, stdout);
assert(ret == -1);
assert(ret == AUTH_NOT_FOUND);
ret = check_user_token(file, "kaka", "hihbhdhrhbhj", 1, stdout);
assert(ret == 1);
assert(ret == AUTH_FOUND);
ret = check_user_token(file, "bar", "hnhbhnhbhnhb", 1, stdout);
assert(ret == 1);
assert(ret == AUTH_FOUND);
ret = check_user_token(file, "foo", "hdhrhbhjhvhu", 1, stdout);
assert(ret == -2);
assert(ret == AUTH_NO_TOKENS);
ret = check_user_token(file, "foo2", "cccccccccccc", 1, stdout);
assert(ret == 1);
assert(ret == AUTH_FOUND);
ret = check_user_token(file, "foo2", "vvvvvvvvvvvv", 1, stdout);
assert(ret == 1);
assert(ret == AUTH_FOUND);
ret = check_user_token(file, "foo2", "vvvvvvvvvvcc", 1, stdout);
assert(ret == -1);
assert(ret == AUTH_NOT_FOUND);
ret = check_user_token(file, "foo2", "", 1, stdout);
assert(ret == -1);
assert(ret == AUTH_NOT_FOUND);
ret = check_user_token(file, "foo", "", 1, stdout);
assert(ret == -2);
assert(ret == AUTH_NO_TOKENS);
remove(file);
}

15
util.c
View File

@ -85,8 +85,9 @@ get_user_cfgfile_path(const char *common_path, const char *filename, const struc
/*
* This function will look for users name with valid user token id. It
* will returns -2 if the user is unknown, -1 if the token do not match the user line, 0 for internal failure and 1 for success.
* This function will look for users name with valid user token id.
*
* Returns one of AUTH_FOUND, AUTH_NOT_FOUND, AUTH_NO_TOKENS, AUTH_ERROR.
*
* File format is as follows:
* <user-name>:<token_id>:<token_id>
@ -102,7 +103,7 @@ check_user_token (const char *authfile,
{
char buf[1024];
char *s_user, *s_token;
int retval = 0;
int retval = AUTH_ERROR;
int fd;
struct stat st;
FILE *opwfile;
@ -136,7 +137,7 @@ check_user_token (const char *authfile,
return retval;
}
retval = -2;
retval = AUTH_NO_TOKENS;
while (fgets (buf, 1024, opwfile))
{
char *saveptr = NULL;
@ -155,17 +156,17 @@ check_user_token (const char *authfile,
{
if(verbose)
D (debug_file, "Matched user: %s", s_user);
retval = -1; //We found at least one line for the user
retval = AUTH_NOT_FOUND; /* We found at least one line for the user */
do
{
s_token = strtok_r (NULL, ":", &saveptr);
if(verbose)
D (debug_file, "Authorization token: %s", s_token);
if (s_token && strcmp (otp_id, s_token) == 0)
if (s_token && otp_id && strcmp (otp_id, s_token) == 0)
{
if(verbose)
D (debug_file, "Match user/token as %s/%s", username, otp_id);
return 1;
return AUTH_FOUND;
}
}
while (s_token != NULL);

6
util.h
View File

@ -44,6 +44,12 @@
fprintf (file, "\n"); \
} while (0)
/* Return values for authorize_user_token and authorize_user_token_ldap */
#define AUTH_NO_TOKENS -2 /* The user has no associated tokens */
#define AUTH_ERROR 0 /* Internal error when looking up associated tokens */
#define AUTH_FOUND 1 /* The requested token is associated to the user */
#define AUTH_NOT_FOUND -1 /* The requested token is not associated to the user */
int get_user_cfgfile_path(const char *common_path, const char *filename, const struct passwd *user, char **fn);
int check_user_token(const char *authfile, const char *username, const char *otp_id, int verbose, FILE *debug_file);