1
0
mirror of https://github.com/Yubico/yubico-pam.git synced 2025-01-20 19:52:16 +01:00

Merge branch 'mickael9-fix-issue-117'

This commit is contained in:
Klas Lindfors 2017-03-06 09:37:55 +01:00
commit 3c201edd83
No known key found for this signature in database
GPG Key ID: BCA00FD4B2168C0A
7 changed files with 133 additions and 59 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;
@ -232,7 +234,7 @@ authorize_user_token_ldap (struct cfg *cfg,
LDAP *ld = NULL;
LDAPMessage *result = NULL, *e;
BerElement *ber;
char *a;
char *attr_name;
char *attrs[2] = {NULL, NULL};
struct berval **vals;
@ -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,48 +324,63 @@ 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))
for (attr_name = ldap_first_attribute (ld, e, &ber);
attr_name != NULL; attr_name = ldap_next_attribute (ld, e, ber))
{
if ((vals = ldap_get_values_len (ld, e, a)) != NULL)
if (strcmp(attr_name, cfg->yubi_attr) != 0) {
DBG("Ignored non-requested attribute: %s", attr_name);
continue;
}
if ((vals = ldap_get_values_len (ld, e, attr_name)) != NULL)
{
yubi_attr_prefix_len = cfg->yubi_attr_prefix ? strlen(cfg->yubi_attr_prefix) : 0;
DBG("LDAP : Found %i values for %s - checking if any of them match '%s:%s'",
ldap_count_values_len(vals),
attr_name,
cfg->yubi_attr_prefix ? cfg->yubi_attr_prefix : "",
token_id ? token_id : "(null)");
/* Compare each value for the attribute against the token id. */
for (i = 0; vals[i] != NULL; i++)
{
DBG("LDAP : Found %i values - checking if any of them match '%s:%s:%s'",
ldap_count_values_len(vals),
vals[i]->bv_val,
cfg->yubi_attr_prefix ? cfg->yubi_attr_prefix : "", token_id);
DBG("LDAP : Checking value %i: %s:%s",
i + 1,
cfg->yubi_attr_prefix ? cfg->yubi_attr_prefix : "",
vals[i]->bv_val);
/* 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;
}
}
}
ldap_value_free_len (vals);
}
ldap_memfree (a);
ldap_memfree (attr_name);
}
if (ber != NULL)
ber_free (ber, 0);
@ -713,6 +727,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 +978,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)
{
@ -1058,12 +1105,6 @@ pam_sm_authenticate (pam_handle_t * pamh,
else
password = NULL;
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));
/* authorize the user with supplied token id */
if (cfg->ldapserver != NULL || cfg->ldap_uri != NULL)
valid_token = authorize_user_token_ldap (cfg, user, otp_id);
@ -1072,7 +1113,12 @@ 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));
switch (rc)
{
case YKCLIENT_OK:
@ -1089,21 +1135,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

@ -45,6 +45,7 @@ use constant RESULT_OK => {
my %objects = (
'base=uid=foo,ou=users,dc=example,dc=com' => {keys => ['vvincredible']},
'base=uid=test,ou=users,dc=example,dc=com' => {keys => ['cccccccfhcbe', 'ccccccbchvth']},
'base=uid=nokeys,ou=users,dc=example,dc=com' => {keys => []},
'sub:base=:(uid=test)' => {keys => ['cccccccfhcbe', 'ccccccbchvth'], dn => 'uid=test,out=users,dc=example,dc=com'},
);

View File

@ -66,6 +66,7 @@ static struct data {
{"test", "ccccccbchvthlivuitriujjifivbvtrjkjfirllluurj"},
{"foo", ""},
{"bar", ""},
{"nokeys", ""},
};
@ -274,6 +275,10 @@ static int test_authenticate_ldap5(void) {
return pam_sm_authenticate(6, 0, sizeof(ldap_cfg) / sizeof(char*), ldap_cfg);
}
static int test_authenticate_ldap6(void) {
return pam_sm_authenticate(7, 0, sizeof(ldap_cfg) / sizeof(char*), ldap_cfg);
}
static pid_t run_mock(const char *port, const char *type) {
pid_t pid = fork();
if(pid == 0) {
@ -354,6 +359,10 @@ int main(void) {
ret = 1007;
goto out;
}
if(test_authenticate_ldap6() != PAM_USER_UNKNOWN) {
ret = 1008;
goto out;
}
#endif
out:

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);