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 password is available or the password is not appropriate, the user
will be denied access. 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:: urllist::
List of URL templates to be used. This is set by calling List of URL templates to be used. This is set by calling
ykclient_set_url_bases. The list should be in the format : ykclient_set_url_bases. The list should be in the format :

View File

@ -108,6 +108,7 @@ struct cfg
int verbose_otp; int verbose_otp;
int try_first_pass; int try_first_pass;
int use_first_pass; int use_first_pass;
int nullok;
const char *auth_file; const char *auth_file;
const char *capath; const char *capath;
const char *cainfo; const char *cainfo;
@ -136,8 +137,9 @@ struct cfg
#define DBG(x...) if (cfg->debug) { D(cfg->debug_file, x); } #define DBG(x...) if (cfg->debug) { D(cfg->debug_file, x); }
/* /*
* Authorize authenticated OTP_ID for login as USERNAME using * Authorize authenticated OTP_ID for login as USERNAME using AUTHFILE.
* AUTHFILE. Return -2 if the user is unknown, -1 if the OTP_ID does not match, 0 on internal failures, otherwise success. *
* Returns one of AUTH_FOUND, AUTH_NOT_FOUND, AUTH_NO_TOKENS, AUTH_ERROR.
*/ */
static int static int
authorize_user_token (struct cfg *cfg, authorize_user_token (struct cfg *cfg,
@ -145,7 +147,7 @@ authorize_user_token (struct cfg *cfg,
const char *otp_id, const char *otp_id,
pam_handle_t *pamh) pam_handle_t *pamh)
{ {
int retval; int retval = AUTH_ERROR;
if (cfg->auth_file) if (cfg->auth_file)
{ {
@ -167,7 +169,7 @@ authorize_user_token (struct cfg *cfg,
pwres = getpwnam_r (username, &pass, buf, buflen, &p); pwres = getpwnam_r (username, &pass, buf, buflen, &p);
if (p == NULL) { if (p == NULL) {
DBG ("getpwnam_r: %s", strerror(pwres)); DBG ("getpwnam_r: %s", strerror(pwres));
return 0; return AUTH_ERROR;
} }
/* Getting file from user home directory /* 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)) { if (! get_user_cfgfile_path (NULL, "authorized_yubikeys", p, &userfile)) {
DBG ("Failed figuring out per-user cfgfile"); DBG ("Failed figuring out per-user cfgfile");
return 0; return AUTH_ERROR;
} }
DBG ("Dropping privileges"); DBG ("Dropping privileges");
if(pam_modutil_drop_priv(pamh, &privs, p)) { if(pam_modutil_drop_priv(pamh, &privs, p)) {
DBG ("could not drop privileges"); 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); retval = check_user_token (userfile, username, otp_id, cfg->debug, cfg->debug_file);
if(pam_modutil_regain_priv(pamh, &privs)) { if(pam_modutil_regain_priv(pamh, &privs)) {
DBG (("could not restore privileges")); DBG ("could not restore privileges");
retval = 0;
goto free_out; goto free_out;
} }
@ -202,7 +202,7 @@ free_out:
/* /*
* This function will look in ldap id the token correspond to the * 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. * ldaps is only supported for ldap_uri based connections.
* ldap_cacertfile usually needs to be set for this to work. * 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 * If using ldap_uri, you can specify multiple failover hosts
* eg. * eg.
* ldap_uri=ldaps://host1.fqdn.example.com,ldaps://host2.fqdn.example.com * 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 static int
authorize_user_token_ldap (struct cfg *cfg, authorize_user_token_ldap (struct cfg *cfg,
const char *user, const char *user,
const char *token_id) const char *token_id)
{ {
int retval = 0; int retval = AUTH_ERROR;
#ifdef HAVE_LIBLDAP #ifdef HAVE_LIBLDAP
/* LDAPv2 is historical -- RFC3494. */ /* LDAPv2 is historical -- RFC3494. */
int protocol = LDAP_VERSION3; int protocol = LDAP_VERSION3;
@ -232,7 +234,7 @@ authorize_user_token_ldap (struct cfg *cfg,
LDAP *ld = NULL; LDAP *ld = NULL;
LDAPMessage *result = NULL, *e; LDAPMessage *result = NULL, *e;
BerElement *ber; BerElement *ber;
char *a; char *attr_name;
char *attrs[2] = {NULL, NULL}; char *attrs[2] = {NULL, NULL};
struct berval **vals; struct berval **vals;
@ -261,7 +263,6 @@ authorize_user_token_ldap (struct cfg *cfg,
if (rc != LDAP_SUCCESS) if (rc != LDAP_SUCCESS)
{ {
DBG ("ldap_initialize: %s", ldap_err2string (rc)); DBG ("ldap_initialize: %s", ldap_err2string (rc));
retval = 0;
goto done; goto done;
} }
} }
@ -270,7 +271,6 @@ authorize_user_token_ldap (struct cfg *cfg,
if ((ld = ldap_init (cfg->ldapserver, PORT_NUMBER)) == NULL) if ((ld = ldap_init (cfg->ldapserver, PORT_NUMBER)) == NULL)
{ {
DBG ("ldap_init"); DBG ("ldap_init");
retval = 0;
goto done; 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); i = (strlen(cfg->user_attr) + strlen(cfg->ldapdn) + strlen(user) + 3) * sizeof(char);
if ((find = malloc(i)) == NULL) { if ((find = malloc(i)) == NULL) {
DBG ("Failed allocating %zu bytes", i); DBG ("Failed allocating %zu bytes", i);
retval = 0;
goto done; goto done;
} }
sprintf (find, "%s=%s,%s", cfg->user_attr, user, cfg->ldapdn); 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)); DBG ("ldap_search_ext_s: %s", ldap_err2string (rc));
retval = 0;
goto done; goto done;
} }
/* Start looing for tokens */
retval = AUTH_NO_TOKENS;
e = ldap_first_entry (ld, result); e = ldap_first_entry (ld, result);
if (e == NULL) if (e == NULL)
{ {
DBG (("No result from LDAP search")); DBG (("No result from LDAP search"));
retval = -2;
} }
else else
{ {
retval = -1;
/* Iterate through each returned attribute. */ /* Iterate through each returned attribute. */
for (a = ldap_first_attribute (ld, e, &ber); for (attr_name = ldap_first_attribute (ld, e, &ber);
a != NULL; a = ldap_next_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; 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. */ /* Compare each value for the attribute against the token id. */
for (i = 0; vals[i] != NULL; i++) for (i = 0; vals[i] != NULL; i++)
{ {
DBG("LDAP : Found %i values - checking if any of them match '%s:%s:%s'", DBG("LDAP : Checking value %i: %s:%s",
ldap_count_values_len(vals), i + 1,
vals[i]->bv_val, cfg->yubi_attr_prefix ? cfg->yubi_attr_prefix : "",
cfg->yubi_attr_prefix ? cfg->yubi_attr_prefix : "", token_id); vals[i]->bv_val);
/* Only values containing this prefix are considered. */ /* 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 ((!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); DBG ("Token Found :: %s", vals[i]->bv_val);
retval = 1; retval = AUTH_FOUND;
} }
} }
} }
ldap_value_free_len (vals); ldap_value_free_len (vals);
} }
ldap_memfree (a); ldap_memfree (attr_name);
} }
if (ber != NULL) if (ber != NULL)
ber_free (ber, 0); 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; cfg->try_first_pass = 1;
if (strcmp (argv[i], "use_first_pass") == 0) if (strcmp (argv[i], "use_first_pass") == 0)
cfg->use_first_pass = 1; cfg->use_first_pass = 1;
if (strcmp (argv[i], "nullok") == 0)
cfg->nullok = 1;
if (strncmp (argv[i], "authfile=", 9) == 0) if (strncmp (argv[i], "authfile=", 9) == 0)
cfg->auth_file = argv[i] + 9; cfg->auth_file = argv[i] + 9;
if (strncmp (argv[i], "capath=", 7) == 0) if (strncmp (argv[i], "capath=", 7) == 0)
@ -962,6 +978,37 @@ pam_sm_authenticate (pam_handle_t * pamh,
goto done; 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) if (password == NULL)
{ {
@ -1058,12 +1105,6 @@ pam_sm_authenticate (pam_handle_t * pamh,
else else
password = NULL; 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 */ /* authorize the user with supplied token id */
if (cfg->ldapserver != NULL || cfg->ldap_uri != NULL) if (cfg->ldapserver != NULL || cfg->ldap_uri != NULL)
valid_token = authorize_user_token_ldap (cfg, user, otp_id); valid_token = authorize_user_token_ldap (cfg, user, otp_id);
@ -1072,7 +1113,12 @@ pam_sm_authenticate (pam_handle_t * pamh,
switch(valid_token) 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) switch (rc)
{ {
case YKCLIENT_OK: case YKCLIENT_OK:
@ -1089,21 +1135,26 @@ pam_sm_authenticate (pam_handle_t * pamh,
break; break;
} }
break; break;
case 0: case AUTH_ERROR:
DBG ("Internal error while validating user"); DBG ("Internal error while looking for user tokens");
retval = PAM_AUTHINFO_UNAVAIL; retval = PAM_AUTHINFO_UNAVAIL;
break; break;
case -1: case AUTH_NOT_FOUND:
DBG ("Unauthorized token for this user"); DBG ("Unauthorized token for this user");
retval = PAM_AUTH_ERR; retval = PAM_AUTH_ERR;
break; break;
case -2: case AUTH_NO_TOKENS:
DBG ("Unknown user"); DBG ("No tokens found for user");
if (cfg->nullok) {
retval = PAM_IGNORE;
} else {
retval = PAM_USER_UNKNOWN; retval = PAM_USER_UNKNOWN;
}
break; break;
default: default:
DBG ("Unhandled value for token-user validation"); DBG ("Unhandled value for token-user validation");
retval = PAM_AUTHINFO_UNAVAIL; retval = PAM_AUTHINFO_UNAVAIL;
break;
} }
done: done:

View File

@ -45,6 +45,7 @@ use constant RESULT_OK => {
my %objects = ( my %objects = (
'base=uid=foo,ou=users,dc=example,dc=com' => {keys => ['vvincredible']}, '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=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'}, '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"}, {"test", "ccccccbchvthlivuitriujjifivbvtrjkjfirllluurj"},
{"foo", ""}, {"foo", ""},
{"bar", ""}, {"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); 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) { static pid_t run_mock(const char *port, const char *type) {
pid_t pid = fork(); pid_t pid = fork();
if(pid == 0) { if(pid == 0) {
@ -354,6 +359,10 @@ int main(void) {
ret = 1007; ret = 1007;
goto out; goto out;
} }
if(test_authenticate_ldap6() != PAM_USER_UNKNOWN) {
ret = 1008;
goto out;
}
#endif #endif
out: out:

View File

@ -75,27 +75,27 @@ static void test_check_user_token(void) {
fclose(handle); fclose(handle);
ret = check_user_token(file, "foobar", "hhhvhvhdhbid", 1, stdout); 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); 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); 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); 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); 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); 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); 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); 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); 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); ret = check_user_token(file, "foo2", "", 1, stdout);
assert(ret == -1); assert(ret == AUTH_NOT_FOUND);
ret = check_user_token(file, "foo", "", 1, stdout); ret = check_user_token(file, "foo", "", 1, stdout);
assert(ret == -2); assert(ret == AUTH_NO_TOKENS);
remove(file); 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 * This function will look for users name with valid user token id.
* 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. *
* Returns one of AUTH_FOUND, AUTH_NOT_FOUND, AUTH_NO_TOKENS, AUTH_ERROR.
* *
* File format is as follows: * File format is as follows:
* <user-name>:<token_id>:<token_id> * <user-name>:<token_id>:<token_id>
@ -102,7 +103,7 @@ check_user_token (const char *authfile,
{ {
char buf[1024]; char buf[1024];
char *s_user, *s_token; char *s_user, *s_token;
int retval = 0; int retval = AUTH_ERROR;
int fd; int fd;
struct stat st; struct stat st;
FILE *opwfile; FILE *opwfile;
@ -136,7 +137,7 @@ check_user_token (const char *authfile,
return retval; return retval;
} }
retval = -2; retval = AUTH_NO_TOKENS;
while (fgets (buf, 1024, opwfile)) while (fgets (buf, 1024, opwfile))
{ {
char *saveptr = NULL; char *saveptr = NULL;
@ -155,17 +156,17 @@ check_user_token (const char *authfile,
{ {
if(verbose) if(verbose)
D (debug_file, "Matched user: %s", s_user); 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 do
{ {
s_token = strtok_r (NULL, ":", &saveptr); s_token = strtok_r (NULL, ":", &saveptr);
if(verbose) if(verbose)
D (debug_file, "Authorization token: %s", s_token); 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) if(verbose)
D (debug_file, "Match user/token as %s/%s", username, otp_id); D (debug_file, "Match user/token as %s/%s", username, otp_id);
return 1; return AUTH_FOUND;
} }
} }
while (s_token != NULL); while (s_token != NULL);

6
util.h
View File

@ -44,6 +44,12 @@
fprintf (file, "\n"); \ fprintf (file, "\n"); \
} while (0) } 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 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); int check_user_token(const char *authfile, const char *username, const char *otp_id, int verbose, FILE *debug_file);