diff -ru lighttpd-trunk/src/http_auth.c lighttpd-trunk.patched/src/http_auth.c --- lighttpd-trunk/src/http_auth.c 2007-06-28 15:00:13.000000000 +0200 +++ lighttpd-trunk.patched/src/http_auth.c 2007-06-28 14:50:13.000000000 +0200 @@ -699,84 +699,171 @@ #ifdef USE_LDAP LDAP *ldap; LDAPMessage *lm, *first; - char *dn; + const char *dn; int ret; char *attrs[] = { LDAP_NO_ATTRS, NULL }; size_t i; + const char *username_token = username->ptr; + unsigned int token_length = 0; + + + /* If we don't allow empty passwords, and this one is empty, there's + * no point in continuing. */ + if (p->conf.auth_ldap_allow_empty_pw != 1 && pw[0] == '\0') + return -1; /* for now we stay synchronous */ /* - * 1. connect anonymously (done in plugin init) - * 2. get DN for uid = username - * 3. auth against ldap server - * 4. (optional) check a field - * 5. disconnect + * 1. If we need to find the user DN + * a. connect anonymously (or using configured bind) (done in plugin init) + * b. get DN for uid = username + * 2. auth against ldap server + * 3. disconnect * */ /* check username * - * we have to protect us againt username which modifies out filter in - * a unpleasant way + * we have to protect us againt usernames which modify our filter or + * user-dn in an unpleasant way. Filters need to have the following + * characters escaped: ['\\', '*', '(', ')'] + * Custom DNs are tougher, because the escape list is longer, and + * there are specialized rules for characters at the beginning and + * end of the value. Escaped: [',', '+', '"', '\\', '<', '>', ';'] + * Also at the beginning of the string: [' ', '#'] + * At the end of the string: [' '] */ - for (i = 0; i < username->used - 1; i++) { - char c = username->ptr[i]; - - if (!isalpha(c) && - !isdigit(c)) { + buffer_copy_string(p->ldap_username, ""); - log_error_write(srv, __FILE__, __LINE__, "sbd", - "ldap: invalid character (a-zA-Z0-9 allowed) in username:", username, i); + /* Create a username escaped for the filter first. */ + /* LDAP filter escape rules */ + for (i = 0; i < username->used - 1; ++i) { + char c = username->ptr[i]; + char s[3] = {'\\',c,0}; - return -1; + if (('\\' != c) && + ('*' != c) && + ('(' != c) && + (')' != c)) { + + ++token_length; + } else { + + if (token_length) { + buffer_append_string_len(p->ldap_username, username_token, token_length); + token_length = 0; + } + buffer_append_string(p->ldap_username, s); + username_token = username->ptr + i + 1; } } - if (p->conf.auth_ldap_allow_empty_pw != 1 && pw[0] == '\0') - return -1; + /* If there's a token we never added to the buffer */ + if (token_length) { + buffer_append_string_len(p->ldap_username, username_token, token_length); + token_length = 0; + } /* build filter */ buffer_copy_string_buffer(p->ldap_filter, p->conf.ldap_filter_pre); - buffer_append_string_buffer(p->ldap_filter, username); + buffer_append_string_buffer(p->ldap_filter, p->ldap_username); buffer_append_string_buffer(p->ldap_filter, p->conf.ldap_filter_post); + if (p->conf.auth_ldap_userdn->used) { + buffer_copy_string(p->ldap_username, ""); + /* User-DN escape rules */ + for (i = 0; i < username->used - 1; ++i) { + char c = username->ptr[i]; + char s[3] = {'\\',c,0}; + + if ((0 == i) && + ((' ' == c) || + ('#' == c))) { + + buffer_copy_string(p->ldap_username, s); + username_token = username->ptr + i + 1; + } else if (((username->used - 2) == i) && + (' ' == c)) { + + if (token_length) { + buffer_append_string_len(p->ldap_username, username_token, token_length); + token_length = 0; + } + buffer_append_string(p->ldap_username, s); + username_token = username->ptr + i + 1; + } else if ((',' != c) && + ('+' != c) && + ('"' != c) && + ('\\' != c) && + ('<' != c) && + ('>' != c) && + (';' != c)) { - /* 2. */ - if (p->conf.ldap == NULL || - LDAP_SUCCESS != (ret = ldap_search_s(p->conf.ldap, p->conf.auth_ldap_basedn->ptr, LDAP_SCOPE_SUBTREE, p->ldap_filter->ptr, attrs, 0, &lm))) { - if (auth_ldap_init(srv, &p->conf) != HANDLER_GO_ON) - return -1; - if (LDAP_SUCCESS != (ret = ldap_search_s(p->conf.ldap, p->conf.auth_ldap_basedn->ptr, LDAP_SCOPE_SUBTREE, p->ldap_filter->ptr, attrs, 0, &lm))) { + ++token_length; + } else { - log_error_write(srv, __FILE__, __LINE__, "sssb", - "ldap:", ldap_err2string(ret), "filter:", p->ldap_filter); + if (token_length) { + buffer_append_string_len(p->ldap_username, username_token, token_length); + token_length = 0; + } + buffer_append_string(p->ldap_username, s); + username_token = username->ptr + i + 1; + } + } - return -1; + /* If there's a token we never added to the buffer */ + if (token_length) { + buffer_append_string_len(p->ldap_username, username_token, token_length); + token_length = 0; } + + /* build userdn */ + buffer_copy_string_buffer(p->ldap_userdn, p->conf.ldap_userdn_pre); + buffer_append_string_buffer(p->ldap_userdn, p->ldap_username); + buffer_append_string_buffer(p->ldap_userdn, p->conf.ldap_userdn_post); + + dn = p->ldap_userdn->ptr; } - if (NULL == (first = ldap_first_entry(p->conf.ldap, lm))) { - log_error_write(srv, __FILE__, __LINE__, "s", "ldap ..."); + if (!p->conf.auth_ldap_userdn->used) { - ldap_msgfree(lm); + /* 1b. */ + if (p->conf.ldap == NULL || + LDAP_SUCCESS != (ret = ldap_search_s(p->conf.ldap, p->conf.auth_ldap_basedn->ptr, LDAP_SCOPE_SUBTREE, p->ldap_filter->ptr, attrs, 0, &lm))) { + if (auth_ldap_init(srv, &p->conf) != HANDLER_GO_ON) + return -1; + if (LDAP_SUCCESS != (ret = ldap_search_s(p->conf.ldap, p->conf.auth_ldap_basedn->ptr, LDAP_SCOPE_SUBTREE, p->ldap_filter->ptr, attrs, 0, &lm))) { - return -1; - } + log_error_write(srv, __FILE__, __LINE__, "sssb", + "ldap:", ldap_err2string(ret), "filter:", p->ldap_filter); + + return -1; + } + } - if (NULL == (dn = ldap_get_dn(p->conf.ldap, first))) { - log_error_write(srv, __FILE__, __LINE__, "s", "ldap ..."); + if (NULL == (first = ldap_first_entry(p->conf.ldap, lm))) { + log_error_write(srv, __FILE__, __LINE__, "s", "ldap ..."); - ldap_msgfree(lm); + ldap_msgfree(lm); - return -1; - } + return -1; + } - ldap_msgfree(lm); + if (NULL == (dn = ldap_get_dn(p->conf.ldap, first))) { + log_error_write(srv, __FILE__, __LINE__, "s", "ldap ..."); + ldap_msgfree(lm); - /* 3. */ + return -1; + } + + ldap_msgfree(lm); + } + + + /* 2. */ if (NULL == (ldap = ldap_init(p->conf.auth_ldap_hostname->ptr, LDAP_PORT))) { log_error_write(srv, __FILE__, __LINE__, "ss", "ldap ...", strerror(errno)); return -1; @@ -810,7 +897,29 @@ return -1; } - /* 5. */ + /* If we're using the userdn, we haven't applied the filter previously, so + * we'll do it now. */ + if (p->conf.auth_ldap_userdn->used) { + if (LDAP_SUCCESS != (ret = ldap_search_s(ldap, dn, LDAP_SCOPE_BASE, p->ldap_filter->ptr, attrs, 0, &lm))) { + + log_error_write(srv, __FILE__, __LINE__, "sssb", + "ldap:", ldap_err2string(ret), "filter:", p->ldap_filter); + + return -1; + } + + if (NULL == (first = ldap_first_entry(p->conf.ldap, lm))) { + log_error_write(srv, __FILE__, __LINE__, "s", "ldap ..."); + + ldap_msgfree(lm); + + return -1; + } + + ldap_msgfree(lm); + } + + /* 3. */ ldap_unbind_s(ldap); /* everything worked, good, access granted */ diff -ru lighttpd-trunk/src/http_auth.h lighttpd-trunk.patched/src/http_auth.h --- lighttpd-trunk/src/http_auth.h 2007-06-28 15:00:13.000000000 +0200 +++ lighttpd-trunk.patched/src/http_auth.h 2007-06-28 14:50:13.000000000 +0200 @@ -33,6 +33,7 @@ buffer *auth_ldap_basedn; buffer *auth_ldap_binddn; buffer *auth_ldap_bindpw; + buffer *auth_ldap_userdn; buffer *auth_ldap_filter; buffer *auth_ldap_cafile; buffer *auth_ldap_cert; @@ -50,6 +51,8 @@ buffer *ldap_filter_pre; buffer *ldap_filter_post; + buffer *ldap_userdn_pre; + buffer *ldap_userdn_post; #endif } mod_auth_plugin_config; @@ -61,6 +64,8 @@ #ifdef USE_LDAP buffer *ldap_filter; + buffer *ldap_username; + buffer *ldap_userdn; #endif mod_auth_plugin_config **config_storage; diff -ru lighttpd-trunk/src/mod_auth.c lighttpd-trunk.patched/src/mod_auth.c --- lighttpd-trunk/src/mod_auth.c 2007-06-28 15:00:13.000000000 +0200 +++ lighttpd-trunk.patched/src/mod_auth.c 2007-06-28 14:59:39.000000000 +0200 @@ -42,6 +42,8 @@ p->auth_user = buffer_init(); #ifdef USE_LDAP p->ldap_filter = buffer_init(); + p->ldap_username = buffer_init(); + p->ldap_userdn = buffer_init(); #endif return p; @@ -58,6 +60,8 @@ buffer_free(p->auth_user); #ifdef USE_LDAP buffer_free(p->ldap_filter); + buffer_free(p->ldap_username); + buffer_free(p->ldap_userdn); #endif if (p->config_storage) { @@ -78,6 +82,7 @@ buffer_free(s->auth_ldap_basedn); buffer_free(s->auth_ldap_binddn); buffer_free(s->auth_ldap_bindpw); + buffer_free(s->auth_ldap_userdn); buffer_free(s->auth_ldap_filter); buffer_free(s->auth_ldap_cafile); buffer_free(s->auth_ldap_cert); @@ -86,6 +91,8 @@ #ifdef USE_LDAP buffer_free(s->ldap_filter_pre); buffer_free(s->ldap_filter_post); + buffer_free(s->ldap_userdn_pre); + buffer_free(s->ldap_userdn_post); if (s->ldap) ldap_unbind_s(s->ldap); #endif @@ -115,6 +122,7 @@ PATCH_OPTION(auth_ldap_basedn); PATCH_OPTION(auth_ldap_binddn); PATCH_OPTION(auth_ldap_bindpw); + PATCH_OPTION(auth_ldap_userdn); PATCH_OPTION(auth_ldap_filter); PATCH_OPTION(auth_ldap_cafile); PATCH_OPTION(auth_ldap_cert); @@ -125,6 +133,8 @@ PATCH_OPTION(ldap); PATCH_OPTION(ldap_filter_pre); PATCH_OPTION(ldap_filter_post); + PATCH_OPTION(ldap_userdn_pre); + PATCH_OPTION(ldap_userdn_post); #endif /* skip the first, the global context */ @@ -159,9 +169,17 @@ PATCH_OPTION(ldap); PATCH_OPTION(ldap_filter_pre); PATCH_OPTION(ldap_filter_post); + PATCH_OPTION(ldap_userdn_pre); + PATCH_OPTION(ldap_userdn_post); #endif } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.base-dn"))) { PATCH_OPTION(auth_ldap_basedn); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.bind-dn"))) { + PATCH_OPTION(auth_ldap_binddn); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.bind-pw"))) { + PATCH_OPTION(auth_ldap_bindpw); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.user-dn"))) { + PATCH_OPTION(auth_ldap_userdn); } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.filter"))) { PATCH_OPTION(auth_ldap_filter); } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.ca-file"))) { @@ -327,10 +345,11 @@ { "auth.backend.ldap.starttls", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 10 */ { "auth.backend.ldap.bind-dn", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 11 */ { "auth.backend.ldap.bind-pw", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 12 */ - { "auth.backend.ldap.allow-empty-pw", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 13 */ - { "auth.backend.htdigest.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 14 */ - { "auth.backend.htpasswd.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 15 */ - { "auth.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 16 */ + { "auth.backend.ldap.user-dn", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 13 */ + { "auth.backend.ldap.allow-empty-pw", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 14 */ + { "auth.backend.htdigest.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 15 */ + { "auth.backend.htpasswd.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 16 */ + { "auth.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 17 */ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } }; @@ -353,6 +372,7 @@ s->auth_ldap_basedn = buffer_init(); s->auth_ldap_binddn = buffer_init(); s->auth_ldap_bindpw = buffer_init(); + s->auth_ldap_userdn = buffer_init(); s->auth_ldap_filter = buffer_init(); s->auth_ldap_cafile = buffer_init(); s->auth_ldap_cert = buffer_init(); @@ -365,6 +385,8 @@ #ifdef USE_LDAP s->ldap_filter_pre = buffer_init(); s->ldap_filter_post = buffer_init(); + s->ldap_userdn_pre = buffer_init(); + s->ldap_userdn_post = buffer_init(); s->ldap = NULL; #endif @@ -381,10 +403,11 @@ cv[10].destination = &(s->auth_ldap_starttls); cv[11].destination = s->auth_ldap_binddn; cv[12].destination = s->auth_ldap_bindpw; - cv[13].destination = &(s->auth_ldap_allow_empty_pw); - cv[14].destination = s->auth_htdigest_userfile; - cv[15].destination = s->auth_htpasswd_userfile; - cv[16].destination = &(s->auth_debug); + cv[13].destination = s->auth_ldap_userdn; + cv[14].destination = &(s->auth_ldap_allow_empty_pw); + cv[15].destination = s->auth_htdigest_userfile; + cv[16].destination = s->auth_htpasswd_userfile; + cv[17].destination = &(s->auth_debug); p->config_storage[i] = s; ca = ((data_config *)srv->config_context->data[i])->value; @@ -655,18 +678,35 @@ } - /* 1. */ - if (s->auth_ldap_binddn->used) { - if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(s->ldap, s->auth_ldap_binddn->ptr, s->auth_ldap_bindpw->ptr))) { - log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret)); + /* If the user DN is supplied, we don't need to do an initial bind + * to find it. */ + if (s->auth_ldap_userdn->used) { + char *dollar; + + /* parse userdn */ + + if (NULL == (dollar = strchr(s->auth_ldap_userdn->ptr, '$'))) { + log_error_write(srv, __FILE__, __LINE__, "s", "ldap: auth.backend.ldap.user-dn is missing a replace-operator '$'"); return HANDLER_ERROR; } - } else { - if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(s->ldap, NULL, NULL))) { - log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret)); - return HANDLER_ERROR; + buffer_copy_string_len(s->ldap_userdn_pre, s->auth_ldap_userdn->ptr, dollar - s->auth_ldap_userdn->ptr); + buffer_copy_string(s->ldap_userdn_post, dollar+1); + } else { + /* 1. */ + if (s->auth_ldap_binddn->used) { + if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(s->ldap, s->auth_ldap_binddn->ptr, s->auth_ldap_bindpw->ptr))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret)); + + return HANDLER_ERROR; + } + } else { + if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(s->ldap, NULL, NULL))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret)); + + return HANDLER_ERROR; + } } } }