From dae9380ac7ebb8c490c1e85da93e85fed368ba02 Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Tue, 28 Oct 2014 16:27:28 +0100 Subject: [PATCH 01/18] added a better ldap handling, and to allow query active directory --- pam_yubico.c | 79 +++++++++++++++++++++++++++++++++-------------- tests/util_test.c | 20 ++++++++++++ util.c | 46 +++++++++++++++++++++++++++ util.h | 3 ++ 4 files changed, 125 insertions(+), 23 deletions(-) diff --git a/pam_yubico.c b/pam_yubico.c index 48308bd..db3cbcd 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -113,6 +113,10 @@ struct cfg const char *urllist; const char *ldapserver; const char *ldap_uri; + int ldap_bind_no_anonymous; + const char *ldap_bind_user; + const char *ldap_bind_password; + const char *ldap_filter; const char *ldapdn; const char *user_attr; const char *yubi_attr; @@ -206,6 +210,7 @@ free_out: static int authorize_user_token_ldap (struct cfg *cfg, const char *user, + const char *password, const char *token_id) { int retval = 0; @@ -221,15 +226,12 @@ authorize_user_token_ldap (struct cfg *cfg, struct berval **vals; int i, rc; + const char *filter = NULL; char *find = NULL; + int scope = LDAP_SCOPE_BASE; #endif DBG(("called")); #ifdef HAVE_LIBLDAP - - if (cfg->user_attr == NULL) { - DBG (("Trying to look up user to YubiKey mapping in LDAP, but user_attr not set!")); - return 0; - } if (cfg->yubi_attr == NULL) { DBG (("Trying to look up user to YubiKey mapping in LDAP, but yubi_attr not set!")); return 0; @@ -265,7 +267,16 @@ authorize_user_token_ldap (struct cfg *cfg, ldap_set_option (ld, LDAP_OPT_PROTOCOL_VERSION, &protocol); /* Bind anonymously to the LDAP server. */ - rc = ldap_simple_bind_s (ld, NULL, NULL); + if (cfg->ldap_bind_user && cfg->ldap_bind_password) { + DBG (("try bind with: %s:%s", cfg->ldap_bind_user, cfg->ldap_bind_password)); + rc = ldap_simple_bind_s (ld, cfg->ldap_bind_user, cfg->ldap_bind_password); + } else if (cfg->ldap_bind_no_anonymous) { + DBG (("try bind with: %s:%s", user, password)); + rc = ldap_simple_bind_s (ld, user, password); + } else { + DBG (("try bind anonymous")); + rc = ldap_simple_bind_s (ld, NULL, NULL); + } if (rc != LDAP_SUCCESS) { DBG (("ldap_simple_bind_s: %s", ldap_err2string (rc))); @@ -274,22 +285,29 @@ authorize_user_token_ldap (struct cfg *cfg, } /* Allocation of memory for search strings depending on input size */ - i = (strlen(cfg->user_attr) + strlen(cfg->ldapdn) + strlen(user) + 3) * sizeof(char); - if ((find = malloc(i)) == NULL) { - DBG (("Failed allocating %i bytes", i)); - retval = 0; - goto done; + find = strdup(cfg->ldapdn); // allow free later-:) + if (cfg->user_attr && cfg->yubi_attr) { + i = (strlen(cfg->user_attr) + strlen(cfg->ldapdn) + strlen(user) + 3) * sizeof(char); + if ((find = malloc(i)) == NULL) { + DBG (("Failed allocating %i bytes", i)); + retval = 0; + goto done; + } + sprintf (find, "%s=%s,%s", cfg->user_attr, user, cfg->ldapdn); + filter = NULL; + } + if (cfg->ldap_filter) { + filter = filter_printf(cfg->ldap_filter, user); + scope = LDAP_SCOPE_SUBTREE; } - - sprintf (find, "%s=%s,%s", cfg->user_attr, user, cfg->ldapdn); - attrs[0] = (char *) cfg->yubi_attr; - DBG(("LDAP : look up object '%s', ask for attribute '%s'", find, cfg->yubi_attr)); + DBG(("LDAP : look up object base='%s' filter='%s', ask for attribute '%s'", find, + filter ? filter:"(null)", cfg->yubi_attr)); /* Search for the entry. */ - if ((rc = ldap_search_ext_s (ld, find, LDAP_SCOPE_BASE, - NULL, attrs, 0, NULL, NULL, LDAP_NO_LIMIT, + if ((rc = ldap_search_ext_s (ld, find, scope, + filter, attrs, 0, NULL, NULL, LDAP_NO_LIMIT, LDAP_NO_LIMIT, &result)) != LDAP_SUCCESS) { DBG (("ldap_search_ext_s: %s", ldap_err2string (rc))); @@ -313,10 +331,10 @@ authorize_user_token_ldap (struct cfg *cfg, { if ((vals = ldap_get_values_len (ld, e, a)) != NULL) { - DBG(("LDAP : Found %i values - checking if any of them match '%s%s'", - ldap_count_values_len(vals), - cfg->yubi_attr_prefix ? cfg->yubi_attr_prefix : "", - token_id)); + 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)); yubi_attr_prefix_len = cfg->yubi_attr_prefix ? strlen(cfg->yubi_attr_prefix) : 0; @@ -350,6 +368,8 @@ authorize_user_token_ldap (struct cfg *cfg, /* free memory allocated for search strings */ if (find != NULL) free(find); + if (filter != NULL) + free(filter); #else DBG (("Trying to use LDAP, but this function is not compiled in pam_yubico!!")); @@ -666,6 +686,14 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) cfg->ldapserver = argv[i] + 11; if (strncmp (argv[i], "ldap_uri=", 9) == 0) cfg->ldap_uri = argv[i] + 9; + if (strncmp (argv[i], "ldap_bind_no_anonymous", sizeof("ldap_bind_no_anonymous")-1) == 0) + cfg->ldap_bind_no_anonymous = 1; + if (strncmp (argv[i], "ldap_bind_user=", sizeof("ldap_bind_user=")-1) == 0) + cfg->ldap_bind_user = argv[i] + sizeof("ldap_bind_user=")-1; + if (strncmp (argv[i], "ldap_bind_password=", sizeof("ldap_bind_password=")-1) == 0) + cfg->ldap_bind_password = argv[i] + sizeof("ldap_bind_password=")-1; + if (strncmp (argv[i], "ldap_filter=", sizeof("ldap_filter=")-1) == 0) + cfg->ldap_filter = argv[i] + sizeof("ldap_filter=")-1; if (strncmp (argv[i], "ldapdn=", 7) == 0) cfg->ldapdn = argv[i] + 7; if (strncmp (argv[i], "user_attr=", 10) == 0) @@ -700,6 +728,10 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) D (("authfile=%s", cfg->auth_file ? cfg->auth_file : "(null)")); D (("ldapserver=%s", cfg->ldapserver ? cfg->ldapserver : "(null)")); D (("ldap_uri=%s", cfg->ldap_uri ? cfg->ldap_uri : "(null)")); + D (("ldap_bind_no_anonymous=%d", cfg->ldap_bind_no_anonymous)); + D (("ldap_bind_user=%s", cfg->ldap_bind_user ? cfg->ldap_bind_user : "(null)")); + D (("ldap_bind_password=%s", cfg->ldap_bind_password ? cfg->ldap_bind_password : "(null)")); + D (("ldap_filter=%s", cfg->ldap_filter ? cfg->ldap_filter : "(null)")); D (("ldapdn=%s", cfg->ldapdn ? cfg->ldapdn : "(null)")); D (("user_attr=%s", cfg->user_attr ? cfg->user_attr : "(null)")); D (("yubi_attr=%s", cfg->yubi_attr ? cfg->yubi_attr : "(null)")); @@ -922,9 +954,10 @@ pam_sm_authenticate (pam_handle_t * pamh, DBG (("OTP: %s ID: %s ", otp, otp_id)); /* user entered their system password followed by generated OTP? */ + char *onlypasswd = NULL; if (password_len > TOKEN_OTP_LEN + cfg->token_id_length) { - char *onlypasswd = strdup (password); + onlypasswd = strdup (password); if (! onlypasswd) { retval = PAM_BUF_ERR; @@ -969,7 +1002,7 @@ pam_sm_authenticate (pam_handle_t * pamh, /* 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); + valid_token = authorize_user_token_ldap (cfg, user, onlypasswd, otp_id); else valid_token = authorize_user_token (cfg, user, otp_id, pamh); diff --git a/tests/util_test.c b/tests/util_test.c index e044e6a..8ae3649 100644 --- a/tests/util_test.c +++ b/tests/util_test.c @@ -125,7 +125,27 @@ static void test_load_chalresp_state(void) { #endif /* HAVE_CR */ +static void test_filter_printf(void) { + assert(filter_result_len("meno %u", "doof", NULL) == 9); + assert(filter_result_len("meno %u %u", "doof", NULL) == 14); + assert(filter_result_len("%u meno %u", "doof", NULL) == 14); + assert(filter_result_len("%u me %u no %u", "doof", NULL) == 20); + assert(filter_result_len("meno %w %%u", "doof", NULL) == 13); + assert(filter_result_len("meno %w %%u meno", "doof", NULL) == 18); + assert(filter_result_len("meno ", "doof", NULL) == 5); + + assert(!strcmp(filter_printf("meno %u", "doof"), "meno doof")); + assert(!strcmp(filter_printf("meno %u %u", "doof"), "meno doof doof")); + assert(!strcmp(filter_printf("%u meno %u", "doof"), "doof meno doof")); + assert(!strcmp(filter_printf("%u me %u no %u", "doof"), "doof me doof no doof")); + assert(!strcmp(filter_printf("meno %w %%u", "doof"), "meno %w %doof")); + assert(!strcmp(filter_printf("meno %w %%u meno", "doof"), "meno %w %doof meno")); + assert(!strcmp(filter_printf("meno ", "doof"), "meno ")); + printf("test_filter_printf OK\n"); +} + int main (void) { + test_filter_printf(); test_get_user_cfgfile_path(); test_check_user_token(); #if HAVE_CR diff --git a/util.c b/util.c index ecfacf6..45bcf55 100644 --- a/util.c +++ b/util.c @@ -35,6 +35,9 @@ #include #include #include +#include +#include +#include #include #include @@ -460,3 +463,46 @@ write_chalresp_state(FILE *f, CR_STATE *state) return 0; } #endif /* HAVE_CR */ + + +int filter_result_len(const char *filter, const char *user, char *output) { + int user_len = strlen(user); + int filter_len = strlen(filter); + const char *result; + int result_len = 0; + const char *percent_sign; + for (result = filter ; (percent_sign = strchr(result, '%')) ; result = percent_sign) { + if ((percent_sign + 1 - filter) > filter_len) { + break; + } + if (output) { + memcpy(output, result, percent_sign - result); + output += percent_sign - result; + } + if (*(percent_sign+1) == 'u') { + if (output) { + memcpy(output, user, user_len); + output += user_len; + } + result_len += (percent_sign - result) + user_len; + ++percent_sign; // skip u + } else { + if (output) { + *output++ = '%'; + } + result_len += percent_sign + 1 - result; + } + ++percent_sign; + } + if (output) { + memcpy(output, result, ((filter+filter_len)-result) + 1); + } + return result_len + (filter+filter_len-result); +} + +const char *filter_printf(const char *filter, const char *user) { + char *result = malloc(filter_result_len(filter, user, NULL) + 1); + filter_result_len(filter, user, result); + return result; +} + diff --git a/util.h b/util.h index edb81ac..0d19e98 100644 --- a/util.h +++ b/util.h @@ -96,4 +96,7 @@ int challenge_response(YK_KEY *yk, int slot, #endif /* HAVE_CR */ +int filter_result_len(const char *filter, const char *user, char *output); +const char *filter_printf(const char *filter, const char *user); + #endif /* __PAM_YUBICO_UTIL_H_INCLUDED__ */ From 37553c41ce33c0e2555f407e225033d7f6949b8f Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Wed, 29 Oct 2014 13:25:29 +0100 Subject: [PATCH 02/18] enable that openvpn can now run without any local user --- README | 21 ++++++++++++- pam_yubico.c | 88 ++++++++++++++++++++++++++++++++++++++++++---------- util.c | 2 +- util.h | 2 +- 4 files changed, 94 insertions(+), 19 deletions(-) diff --git a/README b/README index 10abff2..0696387 100644 --- a/README +++ b/README @@ -345,5 +345,24 @@ Examples If you want to use the Yubikey to authenticate you on linux console logins, add the following to the top of `/etc/pam.d/login`: - auth sufficient pam_yubico.so id=16 debug + auth sufficient pam_yubico.so id=16 debug + +OpenVPN and ActiveDirectory + +create file '/etc/pam.d/openvpn': +auth required pam_yubico.so ldap_uri=ldap://ldap-srv debug id=19 yubi_attr=pager + ldapdn=dc=ad,dc=next-audience,dc=net + ldap_filter=(&(sAMAccountName=%u)(memberOf=CN=mygroup,OU=DefaultUser,DC=adivser,DC=net)) + ldap_bind_no_anonymous ldap_bind_user_filter=%u@adviser.com try_first_pass +account required pam_yubico.so + +create file 'openvpn.conf' +plugin openvpn-plugin-auth-pam.so openvpn + + +Feedback +-------- + +If you want to discuss anything related to the Yubico PAM module, +please e-mail the mailing list yubico-devel@googlegroups.com. diff --git a/pam_yubico.c b/pam_yubico.c index db3cbcd..e137ad6 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -114,6 +114,7 @@ struct cfg const char *ldapserver; const char *ldap_uri; int ldap_bind_no_anonymous; + const char *ldap_bind_user_filter; const char *ldap_bind_user; const char *ldap_bind_password; const char *ldap_filter; @@ -226,7 +227,7 @@ authorize_user_token_ldap (struct cfg *cfg, struct berval **vals; int i, rc; - const char *filter = NULL; + char *filter = NULL; char *find = NULL; int scope = LDAP_SCOPE_BASE; #endif @@ -263,16 +264,24 @@ authorize_user_token_ldap (struct cfg *cfg, } /* LDAPv2 is historical -- RFC3494. */ + ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); protocol = LDAP_VERSION3; ldap_set_option (ld, LDAP_OPT_PROTOCOL_VERSION, &protocol); /* Bind anonymously to the LDAP server. */ if (cfg->ldap_bind_user && cfg->ldap_bind_password) { - DBG (("try bind with: %s:%s", cfg->ldap_bind_user, cfg->ldap_bind_password)); + DBG (("try bind with: %s:[%s]", cfg->ldap_bind_user, cfg->ldap_bind_password)); rc = ldap_simple_bind_s (ld, cfg->ldap_bind_user, cfg->ldap_bind_password); } else if (cfg->ldap_bind_no_anonymous) { - DBG (("try bind with: %s:%s", user, password)); - rc = ldap_simple_bind_s (ld, user, password); + char *tmp_user; + if (cfg->ldap_bind_user_filter) { + tmp_user = filter_printf(cfg->ldap_bind_user_filter, user); + } else { + tmp_user = strdup(user); + } + DBG (("try bind with: %s:[XXXXX]", tmp_user, password)); + rc = ldap_simple_bind_s (ld, tmp_user, password); + free(tmp_user); } else { DBG (("try bind anonymous")); rc = ldap_simple_bind_s (ld, NULL, NULL); @@ -331,16 +340,16 @@ authorize_user_token_ldap (struct cfg *cfg, { if ((vals = ldap_get_values_len (ld, e, a)) != NULL) { - 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)); - yubi_attr_prefix_len = cfg->yubi_attr_prefix ? strlen(cfg->yubi_attr_prefix) : 0; /* 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)); + /* 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))) { @@ -690,6 +699,8 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) cfg->ldap_bind_no_anonymous = 1; if (strncmp (argv[i], "ldap_bind_user=", sizeof("ldap_bind_user=")-1) == 0) cfg->ldap_bind_user = argv[i] + sizeof("ldap_bind_user=")-1; + if (strncmp (argv[i], "ldap_bind_user_filter=", sizeof("ldap_bind_user_filter=")-1) == 0) + cfg->ldap_bind_user_filter = argv[i] + sizeof("ldap_bind_user_filter=")-1; if (strncmp (argv[i], "ldap_bind_password=", sizeof("ldap_bind_password=")-1) == 0) cfg->ldap_bind_password = argv[i] + sizeof("ldap_bind_password=")-1; if (strncmp (argv[i], "ldap_filter=", sizeof("ldap_filter=")-1) == 0) @@ -768,6 +779,7 @@ pam_sm_authenticate (pam_handle_t * pamh, size_t templates = 0; char *urls[10]; char *tmpurl = NULL; + char *onlypasswd = NULL; parse_cfg (flags, argc, argv, cfg); @@ -954,7 +966,6 @@ pam_sm_authenticate (pam_handle_t * pamh, DBG (("OTP: %s ID: %s ", otp, otp_id)); /* user entered their system password followed by generated OTP? */ - char *onlypasswd = NULL; if (password_len > TOKEN_OTP_LEN + cfg->token_id_length) { onlypasswd = strdup (password); @@ -970,7 +981,6 @@ pam_sm_authenticate (pam_handle_t * pamh, "setting item PAM_AUTHTOK")); retval = pam_set_item (pamh, PAM_AUTHTOK, onlypasswd); - free (onlypasswd); if (retval != PAM_SUCCESS) { DBG (("set_item returned error: %s", pam_strerror (pamh, retval))); @@ -1029,6 +1039,8 @@ pam_sm_authenticate (pam_handle_t * pamh, } done: + if (onlypasswd) + free(onlypasswd); if (templates > 0) { size_t i; @@ -1047,7 +1059,8 @@ done: retval = PAM_SUCCESS; } DBG (("done. [%s]", pam_strerror (pamh, retval))); - pam_set_data (pamh, "yubico_setcred_return", (void*) (intptr_t) retval, NULL); + pam_set_data (pamh, "yubico_setcred_return", (void*)(intptr_t)retval, NULL); + pam_set_data (pamh, "yubico_used_ldap", (void*)(intptr_t)cfg->ldap_bind_no_anonymous, NULL); return retval; } @@ -1058,16 +1071,59 @@ pam_sm_setcred (pam_handle_t * pamh, int flags, int argc, const char **argv) return PAM_SUCCESS; } +PAM_EXTERN int +pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) +{ + int use_ldap = -1; + int rc = pam_get_data(pamh, "yubico_used_ldap", (const void**)&use_ldap); + if (rc == PAM_SUCCESS && use_ldap) { + int retval; + rc = pam_get_data(pamh, "yubico_setcred_return", (const void**)&retval); + if (rc == PAM_SUCCESS && retval == PAM_SUCCESS) { + D (("pam_sm_acct_mgmt returing PAM_SUCCESS")); + return PAM_SUCCESS; + } + } + D (("pam_sm_acct_mgmt returing PAM_AUTH_ERR:%d", use_ldap)); + return PAM_AUTH_ERR; +} + +PAM_EXTERN int +pam_sm_open_session(pam_handle_t *pamh, int flags, + int argc, const char *argv[]) +{ + + D(("pam_sm_open_session")); + return (PAM_SUCCESS); +} + +PAM_EXTERN int +pam_sm_close_session(pam_handle_t *pamh, int flags, + int argc, const char *argv[]) +{ + D(("pam_sm_close_session")); + return (PAM_SUCCESS); +} + +PAM_EXTERN int +pam_sm_chauthtok(pam_handle_t *pamh, int flags, + int argc, const char *argv[]) +{ + D(("pam_sm_chauthtok")); + return (PAM_SERVICE_ERR); +} + + #ifdef PAM_STATIC struct pam_module _pam_yubico_modstruct = { "pam_yubico", pam_sm_authenticate, pam_sm_setcred, - NULL, - NULL, - NULL, - NULL + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok }; #endif diff --git a/util.c b/util.c index 45bcf55..0b017dd 100644 --- a/util.c +++ b/util.c @@ -500,7 +500,7 @@ int filter_result_len(const char *filter, const char *user, char *output) { return result_len + (filter+filter_len-result); } -const char *filter_printf(const char *filter, const char *user) { +char *filter_printf(const char *filter, const char *user) { char *result = malloc(filter_result_len(filter, user, NULL) + 1); filter_result_len(filter, user, result); return result; diff --git a/util.h b/util.h index 0d19e98..04c23b9 100644 --- a/util.h +++ b/util.h @@ -97,6 +97,6 @@ int challenge_response(YK_KEY *yk, int slot, #endif /* HAVE_CR */ int filter_result_len(const char *filter, const char *user, char *output); -const char *filter_printf(const char *filter, const char *user); +char *filter_printf(const char *filter, const char *user); #endif /* __PAM_YUBICO_UTIL_H_INCLUDED__ */ From aa87979eb84adb3adef170dac6ff2285ba43cd26 Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Thu, 20 Nov 2014 22:40:55 +0100 Subject: [PATCH 03/18] integrate https://github.com/Yubico/yubico-pam/pull/39/files --- README | 17 +++++++++-------- pam_yubico.c | 46 +++++++++++++++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/README b/README index 0696387..3d7dd19 100644 --- a/README +++ b/README @@ -20,7 +20,7 @@ later. This has introduced a dependency of libykpers-1 from the ykpersonalize package. Pass `--without-cr` to `configure` to avoid this dependency. -The development community is co-ordinated via +The development community is co-ordinated via https://github.com/Yubico/yubico-pam[the GitHub project page]. The project is licensed under a BSD license. See the file COPYING for @@ -67,7 +67,7 @@ It in turn requires Curl, which you need to have installed, and https://developers.yubico.com/yubico-c[libyubikey]. The new Challenge-Response offline authentication requires libykpers-1 -from the +from the https://developers.yubico.com/yubikey-personalization[yubikey-personalization] project : The build system uses Autoconf, to set up the build system run: @@ -152,7 +152,7 @@ service, go https://upgrade.yubico.com/getapikey[here]. debug:: to enable debug output to stdout. -alwaysok:: +alwaysok:: to enable all authentication attempts to succeed (aka presentation mode). @@ -319,10 +319,10 @@ information including the OTP and ID of your token to the shell - copy the ID into your config file and you should be up and going. ------ -Yubikey for `youruser': +Yubikey for `youruser': [pam_yubico.c:pam_sm_authenticate(867)] conv returned 44 bytes [pam_yubico.c:pam_sm_authenticate(885)] Skipping first 0 bytes. Length is 44, token_id set to 12 and token OTP always 32. -[pam_yubico.c:pam_sm_authenticate(892)] OTP: ccccccclabcabkhbdncicglfltnukadfoifadfhhhhfe ID: cccccclabcab +[pam_yubico.c:pam_sm_authenticate(892)] OTP: ccccccclabcabkhbdncicglfltnukadfoifadfhhhhfe ID: cccccclabcab ------ @@ -348,11 +348,12 @@ logins, add the following to the top of `/etc/pam.d/login`: auth sufficient pam_yubico.so id=16 debug OpenVPN and ActiveDirectory +--------------------------- create file '/etc/pam.d/openvpn': -auth required pam_yubico.so ldap_uri=ldap://ldap-srv debug id=19 yubi_attr=pager - ldapdn=dc=ad,dc=next-audience,dc=net - ldap_filter=(&(sAMAccountName=%u)(memberOf=CN=mygroup,OU=DefaultUser,DC=adivser,DC=net)) +auth required pam_yubico.so ldap_uri=ldap://ldap-srv debug id=19 yubi_attr=pager + ldapdn=dc=ad,dc=next-audience,dc=net + ldap_filter=(&(sAMAccountName=%u)(memberOf=CN=mygroup,OU=DefaultUser,DC=adivser,DC=net)) ldap_bind_no_anonymous ldap_bind_user_filter=%u@adviser.com try_first_pass account required pam_yubico.so diff --git a/pam_yubico.c b/pam_yubico.c index e137ad6..bfa3c4c 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -118,6 +118,7 @@ struct cfg const char *ldap_bind_user; const char *ldap_bind_password; const char *ldap_filter; + const char *ldap_cacertfile; const char *ldapdn; const char *user_attr; const char *yubi_attr; @@ -198,8 +199,10 @@ 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. * - * For the moment ldaps is not supported. ldap serve can be on a - * remote host. + * ldaps is only supported for ldap_uri based connections. + * ldap_cacertfile usually needs to be set for this to work. + * + * ldap serve can be on a remote host. * * You need the following parameters in you pam config: * ldapserver= OR ldap_uri= @@ -207,6 +210,9 @@ free_out: * user_attr= * yubi_attr= * + * If using ldap_uri, you can specify multiple failover hosts + * eg. + * ldap_uri=ldaps://host1.fqdn.example.com,ldaps://host2.fqdn.example.com */ static int authorize_user_token_ldap (struct cfg *cfg, @@ -248,7 +254,7 @@ authorize_user_token_ldap (struct cfg *cfg, rc = ldap_initialize (&ld, cfg->ldap_uri); if (rc != LDAP_SUCCESS) { - DBG (("ldap_init: %s", ldap_err2string (rc))); + DBG (("ldap_initialize: %s", ldap_err2string (rc))); retval = 0; goto done; } @@ -268,18 +274,22 @@ authorize_user_token_ldap (struct cfg *cfg, protocol = LDAP_VERSION3; ldap_set_option (ld, LDAP_OPT_PROTOCOL_VERSION, &protocol); + if (cfg->ldap_uri && cfg->ldap_cacertfile) { + /* Set CA CERTFILE. This makes ldaps work when using ldap_uri */ + ldap_set_option (0, LDAP_OPT_X_TLS_CACERTFILE, cfg->ldap_cacertfile); + } /* Bind anonymously to the LDAP server. */ if (cfg->ldap_bind_user && cfg->ldap_bind_password) { - DBG (("try bind with: %s:[%s]", cfg->ldap_bind_user, cfg->ldap_bind_password)); + DBG (("try bind with: %s:[%s]", cfg->ldap_bind_user, cfg->ldap_bind_password)); rc = ldap_simple_bind_s (ld, cfg->ldap_bind_user, cfg->ldap_bind_password); } else if (cfg->ldap_bind_no_anonymous) { char *tmp_user; if (cfg->ldap_bind_user_filter) { tmp_user = filter_printf(cfg->ldap_bind_user_filter, user); } else { - tmp_user = strdup(user); + tmp_user = strdup(user); } - DBG (("try bind with: %s:[XXXXX]", tmp_user, password)); + DBG (("try bind with: %s:[XXXXX]", tmp_user, password)); rc = ldap_simple_bind_s (ld, tmp_user, password); free(tmp_user); } else { @@ -294,7 +304,6 @@ authorize_user_token_ldap (struct cfg *cfg, } /* Allocation of memory for search strings depending on input size */ - find = strdup(cfg->ldapdn); // allow free later-:) if (cfg->user_attr && cfg->yubi_attr) { i = (strlen(cfg->user_attr) + strlen(cfg->ldapdn) + strlen(user) + 3) * sizeof(char); if ((find = malloc(i)) == NULL) { @@ -304,14 +313,16 @@ authorize_user_token_ldap (struct cfg *cfg, } sprintf (find, "%s=%s,%s", cfg->user_attr, user, cfg->ldapdn); filter = NULL; - } + } else { + find = strdup(cfg->ldapdn); // allow free later-:) + } if (cfg->ldap_filter) { filter = filter_printf(cfg->ldap_filter, user); scope = LDAP_SCOPE_SUBTREE; } attrs[0] = (char *) cfg->yubi_attr; - DBG(("LDAP : look up object base='%s' filter='%s', ask for attribute '%s'", find, + DBG(("LDAP : look up object base='%s' filter='%s', ask for attribute '%s'", find, filter ? filter:"(null)", cfg->yubi_attr)); /* Search for the entry. */ @@ -346,7 +357,7 @@ authorize_user_token_ldap (struct cfg *cfg, 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), + ldap_count_values_len(vals), vals[i]->bv_val, cfg->yubi_attr_prefix ? cfg->yubi_attr_prefix : "", token_id)); @@ -705,6 +716,14 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) cfg->ldap_bind_password = argv[i] + sizeof("ldap_bind_password=")-1; if (strncmp (argv[i], "ldap_filter=", sizeof("ldap_filter=")-1) == 0) cfg->ldap_filter = argv[i] + sizeof("ldap_filter=")-1; + if (strncmp (argv[i], "ldap_cacertfile=", sizeof("ldap_cacertfile=")-1) == 0) + cfg->ldap_cacertfile = (char *) argv[i] + sizeof("ldap_cacertfile=")-1; + /* compatible with https://github.com/Yubico/yubico-pam/pull/39/files */ + if (strncmp (argv[i], "binddn=", sizeof("binddn=")-1) == 0) + cfg->ldap_bind_user = (char *) argv[i] + sizeof("binddn=")-1; + if (strncmp (argv[i], "bindpw=", sizeof("bindpw=")-1) == 0) + cfg->ldap_bind_password = (char *) argv[i] + sizeof("bindpw=")-1; + /* compatible with https://github.com/Yubico/yubico-pam/pull/39/files */ if (strncmp (argv[i], "ldapdn=", 7) == 0) cfg->ldapdn = argv[i] + 7; if (strncmp (argv[i], "user_attr=", 10) == 0) @@ -743,6 +762,7 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) D (("ldap_bind_user=%s", cfg->ldap_bind_user ? cfg->ldap_bind_user : "(null)")); D (("ldap_bind_password=%s", cfg->ldap_bind_password ? cfg->ldap_bind_password : "(null)")); D (("ldap_filter=%s", cfg->ldap_filter ? cfg->ldap_filter : "(null)")); + D (("ldap_cacertfile=%s", cfg->ldap_cacertfile ? cfg->ldap_cacertfile : "(null)")); D (("ldapdn=%s", cfg->ldapdn ? cfg->ldapdn : "(null)")); D (("user_attr=%s", cfg->user_attr ? cfg->user_attr : "(null)")); D (("yubi_attr=%s", cfg->yubi_attr ? cfg->yubi_attr : "(null)")); @@ -1059,7 +1079,7 @@ done: retval = PAM_SUCCESS; } DBG (("done. [%s]", pam_strerror (pamh, retval))); - pam_set_data (pamh, "yubico_setcred_return", (void*)(intptr_t)retval, NULL); + pam_set_data (pamh, "yubico_setcred_return", (void*)(intptr_t)retval, NULL); pam_set_data (pamh, "yubico_used_ldap", (void*)(intptr_t)cfg->ldap_bind_no_anonymous, NULL); return retval; @@ -1072,7 +1092,7 @@ pam_sm_setcred (pam_handle_t * pamh, int flags, int argc, const char **argv) } PAM_EXTERN int -pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) +pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { int use_ldap = -1; int rc = pam_get_data(pamh, "yubico_used_ldap", (const void**)&use_ldap); @@ -1082,7 +1102,7 @@ pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) if (rc == PAM_SUCCESS && retval == PAM_SUCCESS) { D (("pam_sm_acct_mgmt returing PAM_SUCCESS")); return PAM_SUCCESS; - } + } } D (("pam_sm_acct_mgmt returing PAM_AUTH_ERR:%d", use_ldap)); return PAM_AUTH_ERR; From 878c62dd5672cfe15947f0ab708e86e8ef11a83b Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Mon, 16 Feb 2015 10:10:53 +0100 Subject: [PATCH 04/18] drop extra options for bind dn and password --- pam_yubico.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pam_yubico.c b/pam_yubico.c index 78e368e..f2705f0 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -723,13 +723,7 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) if (strncmp (argv[i], "ldap_filter=", sizeof("ldap_filter=")-1) == 0) cfg->ldap_filter = argv[i] + sizeof("ldap_filter=")-1; if (strncmp (argv[i], "ldap_cacertfile=", sizeof("ldap_cacertfile=")-1) == 0) - cfg->ldap_cacertfile = (char *) argv[i] + sizeof("ldap_cacertfile=")-1; - /* compatible with https://github.com/Yubico/yubico-pam/pull/39/files */ - if (strncmp (argv[i], "binddn=", sizeof("binddn=")-1) == 0) - cfg->ldap_bind_user = (char *) argv[i] + sizeof("binddn=")-1; - if (strncmp (argv[i], "bindpw=", sizeof("bindpw=")-1) == 0) - cfg->ldap_bind_password = (char *) argv[i] + sizeof("bindpw=")-1; - /* compatible with https://github.com/Yubico/yubico-pam/pull/39/files */ + cfg->ldap_cacertfile = (char *) argv[i] + sizeof("ldap_cacertfile=")-1; if (strncmp (argv[i], "ldapdn=", 7) == 0) cfg->ldapdn = argv[i] + 7; if (strncmp (argv[i], "user_attr=", 10) == 0) From 6cd0f0c1b36ae510ceb0b69ce92bf4cade4f05e2 Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Mon, 16 Feb 2015 13:16:27 +0100 Subject: [PATCH 05/18] simplify filter_result_len() and fixup warnings --- util.c | 64 +++++++++++++++++++++++++++------------------------------- util.h | 2 +- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/util.c b/util.c index 453ea6e..bc684d3 100644 --- a/util.c +++ b/util.c @@ -465,40 +465,37 @@ write_chalresp_state(FILE *f, CR_STATE *state) } #endif /* HAVE_CR */ - -int filter_result_len(const char *filter, const char *user, char *output) { - int user_len = strlen(user); - int filter_len = strlen(filter); - const char *result; - int result_len = 0; - const char *percent_sign; - for (result = filter ; (percent_sign = strchr(result, '%')) ; result = percent_sign) { - if ((percent_sign + 1 - filter) > filter_len) { - break; +size_t filter_result_len(const char *filter, const char *user, char *output) { + const char *part = NULL; + char *ptr = output; + size_t result = 0; + do + { + size_t len; + part = strstr(filter, "%u"); + if(part) + len = part - filter; + else + len = strlen(filter); + if (output) + { + strncpy(ptr, filter, len); + ptr += len; + } + result += len; + filter += len + 2; + if(part != NULL) + { + if(output) + { + strncpy(ptr, user, strlen(user)); + ptr += strlen(user); + } + result += strlen(user); + } } - if (output) { - memcpy(output, result, percent_sign - result); - output += percent_sign - result; - } - if (*(percent_sign+1) == 'u') { - if (output) { - memcpy(output, user, user_len); - output += user_len; - } - result_len += (percent_sign - result) + user_len; - ++percent_sign; // skip u - } else { - if (output) { - *output++ = '%'; - } - result_len += percent_sign + 1 - result; - } - ++percent_sign; - } - if (output) { - memcpy(output, result, ((filter+filter_len)-result) + 1); - } - return result_len + (filter+filter_len-result); + while(part != NULL); + return result; } char *filter_printf(const char *filter, const char *user) { @@ -506,4 +503,3 @@ char *filter_printf(const char *filter, const char *user) { filter_result_len(filter, user, result); return result; } - diff --git a/util.h b/util.h index 04c23b9..f8ef521 100644 --- a/util.h +++ b/util.h @@ -96,7 +96,7 @@ int challenge_response(YK_KEY *yk, int slot, #endif /* HAVE_CR */ -int filter_result_len(const char *filter, const char *user, char *output); +size_t filter_result_len(const char *filter, const char *user, char *output); char *filter_printf(const char *filter, const char *user); #endif /* __PAM_YUBICO_UTIL_H_INCLUDED__ */ From 4faddc54f49630b224b9fa786f99bbd451a0c9a5 Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Wed, 4 Mar 2015 08:45:03 +0100 Subject: [PATCH 06/18] conform to other code --- pam_yubico.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pam_yubico.c b/pam_yubico.c index f2705f0..97761b8 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -712,18 +712,18 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) cfg->ldapserver = argv[i] + 11; if (strncmp (argv[i], "ldap_uri=", 9) == 0) cfg->ldap_uri = argv[i] + 9; - if (strncmp (argv[i], "ldap_bind_no_anonymous", sizeof("ldap_bind_no_anonymous")-1) == 0) + if (strncmp (argv[i], "ldap_bind_no_anonymous", 22) == 0) cfg->ldap_bind_no_anonymous = 1; - if (strncmp (argv[i], "ldap_bind_user=", sizeof("ldap_bind_user=")-1) == 0) - cfg->ldap_bind_user = argv[i] + sizeof("ldap_bind_user=")-1; - if (strncmp (argv[i], "ldap_bind_user_filter=", sizeof("ldap_bind_user_filter=")-1) == 0) - cfg->ldap_bind_user_filter = argv[i] + sizeof("ldap_bind_user_filter=")-1; - if (strncmp (argv[i], "ldap_bind_password=", sizeof("ldap_bind_password=")-1) == 0) - cfg->ldap_bind_password = argv[i] + sizeof("ldap_bind_password=")-1; - if (strncmp (argv[i], "ldap_filter=", sizeof("ldap_filter=")-1) == 0) - cfg->ldap_filter = argv[i] + sizeof("ldap_filter=")-1; - if (strncmp (argv[i], "ldap_cacertfile=", sizeof("ldap_cacertfile=")-1) == 0) - cfg->ldap_cacertfile = (char *) argv[i] + sizeof("ldap_cacertfile=")-1; + if (strncmp (argv[i], "ldap_bind_user=", 15) == 0) + cfg->ldap_bind_user = argv[i] + 15; + if (strncmp (argv[i], "ldap_bind_user_filter=", 22) == 0) + cfg->ldap_bind_user_filter = argv[i] + 22; + if (strncmp (argv[i], "ldap_bind_password=", 19) == 0) + cfg->ldap_bind_password = argv[i] + 19; + if (strncmp (argv[i], "ldap_filter=", 12) == 0) + cfg->ldap_filter = argv[i] + 12; + if (strncmp (argv[i], "ldap_cacertfile=", 16) == 0) + cfg->ldap_cacertfile = (char *) argv[i] + 16; if (strncmp (argv[i], "ldapdn=", 7) == 0) cfg->ldapdn = argv[i] + 7; if (strncmp (argv[i], "user_attr=", 10) == 0) From 3819b404304423208c6036e8ee58796ca0759c2b Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Wed, 4 Mar 2015 08:46:52 +0100 Subject: [PATCH 07/18] don't use c++ style comments --- pam_yubico.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pam_yubico.c b/pam_yubico.c index 97761b8..c054be4 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -314,7 +314,7 @@ authorize_user_token_ldap (struct cfg *cfg, sprintf (find, "%s=%s,%s", cfg->user_attr, user, cfg->ldapdn); filter = NULL; } else { - find = strdup(cfg->ldapdn); // allow free later-:) + find = strdup(cfg->ldapdn); /* allow free later */ } if (cfg->ldap_filter) { filter = filter_printf(cfg->ldap_filter, user); From f6e1ec9483d084629baeb6d621f4f0f01ba3d490 Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Wed, 4 Mar 2015 09:11:09 +0100 Subject: [PATCH 08/18] don't throw const away in cast --- pam_yubico.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pam_yubico.c b/pam_yubico.c index c054be4..d19c7fd 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -723,7 +723,7 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) if (strncmp (argv[i], "ldap_filter=", 12) == 0) cfg->ldap_filter = argv[i] + 12; if (strncmp (argv[i], "ldap_cacertfile=", 16) == 0) - cfg->ldap_cacertfile = (char *) argv[i] + 16; + cfg->ldap_cacertfile = argv[i] + 16; if (strncmp (argv[i], "ldapdn=", 7) == 0) cfg->ldapdn = argv[i] + 7; if (strncmp (argv[i], "user_attr=", 10) == 0) From 0d3a4f4f75c17dc023faa52f194d3ee12e3e677c Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Wed, 4 Mar 2015 09:14:14 +0100 Subject: [PATCH 09/18] consider all success as valid session --- pam_yubico.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/pam_yubico.c b/pam_yubico.c index d19c7fd..606b8ce 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -1089,7 +1089,6 @@ done: } DBG (("done. [%s]", pam_strerror (pamh, retval))); pam_set_data (pamh, "yubico_setcred_return", (void*)(intptr_t)retval, NULL); - pam_set_data (pamh, "yubico_used_ldap", (void*)(intptr_t)cfg->ldap_bind_no_anonymous, NULL); if (resp) { @@ -1110,17 +1109,13 @@ pam_sm_setcred (pam_handle_t * pamh, int flags, int argc, const char **argv) PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { - int use_ldap = -1; - int rc = pam_get_data(pamh, "yubico_used_ldap", (const void**)&use_ldap); - if (rc == PAM_SUCCESS && use_ldap) { - int retval; - rc = pam_get_data(pamh, "yubico_setcred_return", (const void**)&retval); - if (rc == PAM_SUCCESS && retval == PAM_SUCCESS) { - D (("pam_sm_acct_mgmt returing PAM_SUCCESS")); - return PAM_SUCCESS; - } + int retval; + int rc = pam_get_data(pamh, "yubico_setcred_return", (const void**)&retval); + if (rc == PAM_SUCCESS && retval == PAM_SUCCESS) { + D (("pam_sm_acct_mgmt returing PAM_SUCCESS")); + return PAM_SUCCESS; } - D (("pam_sm_acct_mgmt returing PAM_AUTH_ERR:%d", use_ldap)); + D (("pam_sm_acct_mgmt returing PAM_AUTH_ERR:%d", rc)); return PAM_AUTH_ERR; } From 5709a4479a77408b92977fc46ff6d63e5aa54189 Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Wed, 4 Mar 2015 09:14:53 +0100 Subject: [PATCH 10/18] drop the ldap_bind_no_anonymous feature Sending the users password out in another direction is scary and requires more thought. Ideally this should be solved by using ldap GSSAPI or similar instead of sending a cleartext password over the network. --- pam_yubico.c | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/pam_yubico.c b/pam_yubico.c index 606b8ce..f060d17 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -113,8 +113,6 @@ struct cfg const char *urllist; const char *ldapserver; const char *ldap_uri; - int ldap_bind_no_anonymous; - const char *ldap_bind_user_filter; const char *ldap_bind_user; const char *ldap_bind_password; const char *ldap_filter; @@ -282,16 +280,6 @@ authorize_user_token_ldap (struct cfg *cfg, if (cfg->ldap_bind_user && cfg->ldap_bind_password) { DBG (("try bind with: %s:[%s]", cfg->ldap_bind_user, cfg->ldap_bind_password)); rc = ldap_simple_bind_s (ld, cfg->ldap_bind_user, cfg->ldap_bind_password); - } else if (cfg->ldap_bind_no_anonymous) { - char *tmp_user; - if (cfg->ldap_bind_user_filter) { - tmp_user = filter_printf(cfg->ldap_bind_user_filter, user); - } else { - tmp_user = strdup(user); - } - DBG (("try bind with: %s:[XXXXX]", tmp_user, password)); - rc = ldap_simple_bind_s (ld, tmp_user, password); - free(tmp_user); } else { DBG (("try bind anonymous")); rc = ldap_simple_bind_s (ld, NULL, NULL); @@ -712,12 +700,8 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) cfg->ldapserver = argv[i] + 11; if (strncmp (argv[i], "ldap_uri=", 9) == 0) cfg->ldap_uri = argv[i] + 9; - if (strncmp (argv[i], "ldap_bind_no_anonymous", 22) == 0) - cfg->ldap_bind_no_anonymous = 1; if (strncmp (argv[i], "ldap_bind_user=", 15) == 0) cfg->ldap_bind_user = argv[i] + 15; - if (strncmp (argv[i], "ldap_bind_user_filter=", 22) == 0) - cfg->ldap_bind_user_filter = argv[i] + 22; if (strncmp (argv[i], "ldap_bind_password=", 19) == 0) cfg->ldap_bind_password = argv[i] + 19; if (strncmp (argv[i], "ldap_filter=", 12) == 0) @@ -758,7 +742,6 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) D (("authfile=%s", cfg->auth_file ? cfg->auth_file : "(null)")); D (("ldapserver=%s", cfg->ldapserver ? cfg->ldapserver : "(null)")); D (("ldap_uri=%s", cfg->ldap_uri ? cfg->ldap_uri : "(null)")); - D (("ldap_bind_no_anonymous=%d", cfg->ldap_bind_no_anonymous)); D (("ldap_bind_user=%s", cfg->ldap_bind_user ? cfg->ldap_bind_user : "(null)")); D (("ldap_bind_password=%s", cfg->ldap_bind_password ? cfg->ldap_bind_password : "(null)")); D (("ldap_filter=%s", cfg->ldap_filter ? cfg->ldap_filter : "(null)")); From 336351188bf7bfaf8eca9edc8cc1e9d4945accab Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Wed, 4 Mar 2015 09:27:21 +0100 Subject: [PATCH 11/18] drop password parameter for authorized_user_token_ldap() --- pam_yubico.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pam_yubico.c b/pam_yubico.c index f060d17..1f41339 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -215,7 +215,6 @@ free_out: static int authorize_user_token_ldap (struct cfg *cfg, const char *user, - const char *password, const char *token_id) { int retval = 0; @@ -1021,7 +1020,7 @@ pam_sm_authenticate (pam_handle_t * pamh, /* authorize the user with supplied token id */ if (cfg->ldapserver != NULL || cfg->ldap_uri != NULL) - valid_token = authorize_user_token_ldap (cfg, user, onlypasswd, otp_id); + valid_token = authorize_user_token_ldap (cfg, user, otp_id); else valid_token = authorize_user_token (cfg, user, otp_id, pamh); From 26cc397ba5a6855cd4247e8f4e69c235709bf4ab Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Wed, 4 Mar 2015 09:51:55 +0100 Subject: [PATCH 12/18] no need for a temp pointer when writing output of filter_result_len() --- util.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/util.c b/util.c index bc684d3..8112862 100644 --- a/util.c +++ b/util.c @@ -467,7 +467,6 @@ write_chalresp_state(FILE *f, CR_STATE *state) size_t filter_result_len(const char *filter, const char *user, char *output) { const char *part = NULL; - char *ptr = output; size_t result = 0; do { @@ -477,19 +476,19 @@ size_t filter_result_len(const char *filter, const char *user, char *output) { len = part - filter; else len = strlen(filter); - if (output) + if (output != NULL) { - strncpy(ptr, filter, len); - ptr += len; + strncpy(output, filter, len); + output += len; } result += len; filter += len + 2; if(part != NULL) { - if(output) + if(output != NULL) { - strncpy(ptr, user, strlen(user)); - ptr += strlen(user); + strncpy(output, user, strlen(user)); + output += strlen(user); } result += strlen(user); } From 686a47accf17205cc446b2ffce1637cf08072262 Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Wed, 4 Mar 2015 10:24:46 +0100 Subject: [PATCH 13/18] drop authfile for ldap_cfg --- tests/pam_test.c | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/pam_test.c b/tests/pam_test.c index 6047f06..e347b85 100644 --- a/tests/pam_test.c +++ b/tests/pam_test.c @@ -63,7 +63,6 @@ static struct data { static const char *ldap_cfg[] = { "id=1", "urllist=http://localhost:"YKVAL_PORT2"/wsapi/2/verify;http://localhost:"YKVAL_PORT1"/wsapi/2/verify", - "authfile="AUTHFILE, "ldap_uri=ldap://localhost:"LDAP_PORT, "ldapdn=ou=users,dc=example,dc=com", "user_attr=uid", From 58382bf7fc14f032609bef63d86e3ad5961cce88 Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Wed, 4 Mar 2015 10:48:09 +0100 Subject: [PATCH 14/18] make sure filter is null terminated and return length including null --- tests/util_test.c | 14 +++++++------- util.c | 15 +++++++++------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/tests/util_test.c b/tests/util_test.c index 8ae3649..6e8627d 100644 --- a/tests/util_test.c +++ b/tests/util_test.c @@ -126,13 +126,13 @@ static void test_load_chalresp_state(void) { #endif /* HAVE_CR */ static void test_filter_printf(void) { - assert(filter_result_len("meno %u", "doof", NULL) == 9); - assert(filter_result_len("meno %u %u", "doof", NULL) == 14); - assert(filter_result_len("%u meno %u", "doof", NULL) == 14); - assert(filter_result_len("%u me %u no %u", "doof", NULL) == 20); - assert(filter_result_len("meno %w %%u", "doof", NULL) == 13); - assert(filter_result_len("meno %w %%u meno", "doof", NULL) == 18); - assert(filter_result_len("meno ", "doof", NULL) == 5); + assert(filter_result_len("meno %u", "doof", NULL) == 10); + assert(filter_result_len("meno %u %u", "doof", NULL) == 15); + assert(filter_result_len("%u meno %u", "doof", NULL) == 15); + assert(filter_result_len("%u me %u no %u", "doof", NULL) == 21); + assert(filter_result_len("meno %w %%u", "doof", NULL) == 14); + assert(filter_result_len("meno %w %%u meno", "doof", NULL) == 19); + assert(filter_result_len("meno ", "doof", NULL) == 6); assert(!strcmp(filter_printf("meno %u", "doof"), "meno doof")); assert(!strcmp(filter_printf("meno %u %u", "doof"), "meno doof doof")); diff --git a/util.c b/util.c index 8112862..a669bd8 100644 --- a/util.c +++ b/util.c @@ -476,16 +476,16 @@ size_t filter_result_len(const char *filter, const char *user, char *output) { len = part - filter; else len = strlen(filter); - if (output != NULL) + if (output) { strncpy(output, filter, len); output += len; } result += len; filter += len + 2; - if(part != NULL) + if(part) { - if(output != NULL) + if(output) { strncpy(output, user, strlen(user)); output += strlen(user); @@ -493,12 +493,15 @@ size_t filter_result_len(const char *filter, const char *user, char *output) { result += strlen(user); } } - while(part != NULL); - return result; + while(part); + + if(output) + *output = '\0'; + return(result + 1); } char *filter_printf(const char *filter, const char *user) { - char *result = malloc(filter_result_len(filter, user, NULL) + 1); + char *result = malloc(filter_result_len(filter, user, NULL)); filter_result_len(filter, user, result); return result; } From 71339bb8a1925329c7304592618dac1b4ad469f9 Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Wed, 4 Mar 2015 11:11:32 +0100 Subject: [PATCH 15/18] allow ldap search with no base --- pam_yubico.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pam_yubico.c b/pam_yubico.c index 1f41339..5ee30e9 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -240,8 +240,8 @@ authorize_user_token_ldap (struct cfg *cfg, DBG (("Trying to look up user to YubiKey mapping in LDAP, but yubi_attr not set!")); return 0; } - if (cfg->ldapdn == NULL) { - DBG (("Trying to look up user to YubiKey mapping in LDAP, but ldapdn not set!")); + if (cfg->user_attr && cfg->ldapdn == NULL) { + DBG (("Trying to look up user to YubiKey mapping in LDAP, user_attr set but ldapdn not set!")); return 0; } @@ -291,7 +291,7 @@ authorize_user_token_ldap (struct cfg *cfg, } /* Allocation of memory for search strings depending on input size */ - if (cfg->user_attr && cfg->yubi_attr) { + if (cfg->user_attr && cfg->yubi_attr && cfg->ldapdn) { i = (strlen(cfg->user_attr) + strlen(cfg->ldapdn) + strlen(user) + 3) * sizeof(char); if ((find = malloc(i)) == NULL) { DBG (("Failed allocating %i bytes", i)); @@ -300,7 +300,7 @@ authorize_user_token_ldap (struct cfg *cfg, } sprintf (find, "%s=%s,%s", cfg->user_attr, user, cfg->ldapdn); filter = NULL; - } else { + } else if (cfg->ldapdn) { find = strdup(cfg->ldapdn); /* allow free later */ } if (cfg->ldap_filter) { From 35c8acce6e15b7f1b2491e9de8a429b8bfbbfef8 Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Wed, 4 Mar 2015 11:12:16 +0100 Subject: [PATCH 16/18] simple ldap filter test --- tests/aux/ldap.pl | 19 ++++++++++++++----- tests/pam_test.c | 35 ++++++++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/tests/aux/ldap.pl b/tests/aux/ldap.pl index 1fad4a4..c05b369 100755 --- a/tests/aux/ldap.pl +++ b/tests/aux/ldap.pl @@ -43,8 +43,9 @@ use constant RESULT_OK => { }; my %objects = ( - 'uid=foo,ou=users,dc=example,dc=com' => ['vvincredible'], - 'uid=test,ou=users,dc=example,dc=com' =>['cccccccfhcbe', 'ccccccbchvth'], + 'base=uid=foo,ou=users,dc=example,dc=com' => {keys => ['vvincredible']}, + 'base=uid=test,ou=users,dc=example,dc=com' => {keys => ['cccccccfhcbe', 'ccccccbchvth']}, + 'sub:base=:(uid=test)' => {keys => ['cccccccfhcbe', 'ccccccbchvth'], dn => 'uid=test,out=users,dc=example,dc=com'}, ); sub bind { @@ -56,14 +57,22 @@ sub bind { sub search { my $self = shift; my $reqData = shift; - my $base = $reqData->{'baseObject'}; - my $id = $objects{$base}; + my $id; + my $base; + if($reqData->{'scope'} == 0) { + $base = $reqData->{'baseObject'}; + $id = $objects{'base=' . $base}; + } elsif($reqData->{'scope'} == 2) { + my $match = $reqData->{'filter'}->{'equalityMatch'}; + $id = $objects{'sub:base=' . $reqData->{'baseObject'} . ':(' . $match->{'attributeDesc'} . '=' . $match->{'assertionValue'} . ')'}; + $base = $id->{'dn'}; + } my @entries; if($id) { my $entry = Net::LDAP::Entry->new; $entry->dn($base); $entry->add(objectClass => [ "person" ]); - $entry->add(yubiKeyId => $id); + $entry->add(yubiKeyId => $id->{'keys'}); push @entries, $entry; } return RESULT_OK, @entries; diff --git a/tests/pam_test.c b/tests/pam_test.c index e347b85..5f4e0c1 100644 --- a/tests/pam_test.c +++ b/tests/pam_test.c @@ -70,6 +70,15 @@ static const char *ldap_cfg[] = { "debug" }; +static const char *ldap_cfg2[] = { + "id=1", + "urllist=http://localhost:"YKVAL_PORT1"/wsapi/2/verify;http://localhost:"YKVAL_PORT2"/wsapi/2/verify", + "ldap_uri=ldap://localhost:"LDAP_PORT, + "ldap_filter=(uid=%u)", + "yubi_attr=yubiKeyId", + "debug" +}; + static const struct data *test_get_data(void *id) { return &_data[(long)id]; } @@ -211,6 +220,10 @@ static int test_authenticate_ldap2(void) { return pam_sm_authenticate(4, 0, sizeof(ldap_cfg) / sizeof(char*), ldap_cfg); } +static int test_authenticate_ldap3(void) { + return pam_sm_authenticate(4, 0, sizeof(ldap_cfg2) / sizeof(char*), ldap_cfg2); +} + static pid_t run_mock(const char *port, const char *type) { pid_t pid = fork(); if(pid == 0) { @@ -250,28 +263,32 @@ int main(void) { ret = 5; goto out; } -#ifdef HAVE_LIBLDAP - if(test_authenticate_ldap1() != PAM_SUCCESS) { + if(test_authenticate3() != PAM_SUCCESS) { ret = 6; goto out; } +#ifdef HAVE_LIBLDAP + if(test_authenticate_ldap1() != PAM_SUCCESS) { + ret = 1001; + goto out; + } if(test_authenticate_ldap_fail1() != PAM_USER_UNKNOWN) { - ret = 7; + ret = 1002; goto out; } if(test_authenticate_ldap_fail2() != PAM_AUTH_ERR) { - ret = 8; + ret = 1003; goto out; } if(test_authenticate_ldap2() != PAM_SUCCESS) { - ret = 9; + ret = 1004; + goto out; + } + if(test_authenticate_ldap3() != PAM_SUCCESS) { + ret = 1005; goto out; } #endif - if(test_authenticate3() != PAM_SUCCESS) { - ret = 10; - goto out; - } out: kill(child, 9); From 951d02252d0e0e28edf994f90a847917f851f638 Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Wed, 4 Mar 2015 13:08:30 +0100 Subject: [PATCH 17/18] use ldap_bind_user and ldap_bind_password in example --- README | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README b/README index 813effc..74ba6cd 100644 --- a/README +++ b/README @@ -342,14 +342,16 @@ OpenVPN and ActiveDirectory --------------------------- create file '/etc/pam.d/openvpn': -auth required pam_yubico.so ldap_uri=ldap://ldap-srv debug id=19 yubi_attr=pager - ldapdn=dc=ad,dc=next-audience,dc=net - ldap_filter=(&(sAMAccountName=%u)(memberOf=CN=mygroup,OU=DefaultUser,DC=adivser,DC=net)) - ldap_bind_no_anonymous ldap_bind_user_filter=%u@adviser.com try_first_pass -account required pam_yubico.so + + auth required pam_yubico.so ldap_uri=ldap://ldap-srv debug id=19 yubi_attr=pager + ldapdn=dc=ad,dc=next-audience,dc=net + ldap_filter=(&(sAMAccountName=%u)(memberOf=CN=mygroup,OU=DefaultUser,DC=adivser,DC=net)) + ldap_bind_user=bind_user ldap_bind_password=bind_password try_first_pass + account required pam_yubico.so create file 'openvpn.conf' -plugin openvpn-plugin-auth-pam.so openvpn + + plugin openvpn-plugin-auth-pam.so openvpn Feedback From 77067004e830724442004c458c0d8f59e18f7272 Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Wed, 4 Mar 2015 13:52:31 +0100 Subject: [PATCH 18/18] update documentation with new ldap parameters --- pam_yubico.8.txt | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pam_yubico.8.txt b/pam_yubico.8.txt index 07de2d5..d1e253c 100644 --- a/pam_yubico.8.txt +++ b/pam_yubico.8.txt @@ -57,10 +57,10 @@ This argument is used to show the OTP (One Time Password) when it is entered, i. Specify the LDAP server URI (e.g. ldap://localhost). *ldap_server*=_server_:: -Specify the LDAP server host (default LDAP port is used). *Deprecated. Use "ldap_uri" instead.* +Specify the LDAP server host (default LDAP port is used). *Deprecated. Use 'ldap_uri' instead.* *ldapdn*=_dn_:: -The dn where the users are stored (eg: ou=users,dc=domain,dc=com). +The dn where the users are stored (eg: ou=users,dc=domain,dc=com). If 'ldap_filter' is used this is the base from which the subtree search will be performed. *user_attr*=_attr_:: The LDAP attribute used to store user names (eg:cn). @@ -74,12 +74,26 @@ The prefix of the LDAP attribute's value, in case of a generic attribute, used t *token_id_length*=_length_:: Length of ID prefixing the OTP (this is 12 if using the YubiCloud). +*ldap_bind_user*=_user_:: +The user to attempt a LDAP bind as. + +*ldap_bind_password*=_password_:: +The password to use on LDAP bind. + +*ldap_filter*=_filter_:: +An ldap filter to use for attempting to find the correct object in LDAP. In this string %u will be replaced with the username. + +*ldap_cacertfile*=_cacertfile_:: +Ca certfile for the LDAP connection. + == EXAMPLES auth sufficient pam_yubico.so id=16 debug auth required pam_yubico.so mode=challenge-response + auth required pam_yubico.so id=16 ldap_uri=ldaps://ldap.example.com ldap_filter=(uid=%u) yubi_attr=yubiKeyId + == BUGS Report yubico-pam bugs in the issue tracker: https://github.com/Yubico/yubico-pam/issues