diff -ru lighttpd-1.4.15/src/http_auth.c lighttpd-1.4.15.patched/src/http_auth.c --- lighttpd-1.4.15/src/http_auth.c 2007-04-13 17:26:31.000000000 +0200 +++ lighttpd-1.4.15.patched/src/http_auth.c 2007-06-28 12:22:06.000000000 +0200 @@ -698,84 +698,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; @@ -809,7 +896,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-1.4.15/src/http_auth.h lighttpd-1.4.15.patched/src/http_auth.h --- lighttpd-1.4.15/src/http_auth.h 2007-02-08 17:34:46.000000000 +0100 +++ lighttpd-1.4.15.patched/src/http_auth.h 2007-06-27 14:39:21.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; unsigned short auth_ldap_starttls; @@ -48,6 +49,8 @@ buffer *ldap_filter_pre; buffer *ldap_filter_post; + buffer *ldap_userdn_pre; + buffer *ldap_userdn_post; #endif } mod_auth_plugin_config; @@ -59,6 +62,8 @@ #ifdef USE_LDAP buffer *ldap_filter; + buffer *ldap_username; + buffer *ldap_userdn; #endif mod_auth_plugin_config **config_storage; diff -ru lighttpd-1.4.15/src/mod_auth.c lighttpd-1.4.15.patched/src/mod_auth.c --- lighttpd-1.4.15/src/mod_auth.c 2007-02-08 17:34:46.000000000 +0100 +++ lighttpd-1.4.15.patched/src/mod_auth.c 2007-06-28 12:08:04.000000000 +0200 @@ -37,6 +37,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; @@ -53,6 +55,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) { @@ -73,12 +77,15 @@ 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); #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 @@ -110,6 +117,7 @@ PATCH(auth_ldap_basedn); PATCH(auth_ldap_binddn); PATCH(auth_ldap_bindpw); + PATCH(auth_ldap_userdn); PATCH(auth_ldap_filter); PATCH(auth_ldap_cafile); PATCH(auth_ldap_starttls); @@ -118,6 +126,8 @@ PATCH(ldap); PATCH(ldap_filter_pre); PATCH(ldap_filter_post); + PATCH(ldap_userdn_pre); + PATCH(ldap_userdn_post); #endif /* skip the first, the global context */ @@ -152,9 +162,17 @@ PATCH(ldap); PATCH(ldap_filter_pre); PATCH(ldap_filter_post); + PATCH(ldap_userdn_pre); + PATCH(ldap_userdn_post); #endif } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.base-dn"))) { PATCH(auth_ldap_basedn); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.bind-dn"))) { + PATCH(auth_ldap_binddn); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.bind-pw"))) { + PATCH(auth_ldap_bindpw); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.user-dn"))) { + PATCH(auth_ldap_userdn); } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.filter"))) { PATCH(auth_ldap_filter); } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.ca-file"))) { @@ -305,20 +323,21 @@ config_values_t cv[] = { { "auth.backend", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ - { "auth.backend.plain.groupfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, - { "auth.backend.plain.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, - { "auth.require", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, - { "auth.backend.ldap.hostname", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, - { "auth.backend.ldap.base-dn", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, - { "auth.backend.ldap.filter", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, - { "auth.backend.ldap.ca-file", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, - { "auth.backend.ldap.starttls", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, - { "auth.backend.ldap.bind-dn", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { "auth.backend.plain.groupfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { "auth.backend.plain.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + { "auth.require", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 3 */ + { "auth.backend.ldap.hostname", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 4 */ + { "auth.backend.ldap.base-dn", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 5 */ + { "auth.backend.ldap.filter", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 6 */ + { "auth.backend.ldap.ca-file", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 7 */ + { "auth.backend.ldap.starttls", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 8 */ + { "auth.backend.ldap.bind-dn", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 9 */ { "auth.backend.ldap.bind-pw", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 10 */ - { "auth.backend.ldap.allow-empty-pw", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, - { "auth.backend.htdigest.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, - { "auth.backend.htpasswd.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, - { "auth.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 13 */ + { "auth.backend.ldap.user-dn", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 11 */ + { "auth.backend.ldap.allow-empty-pw", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 12 */ + { "auth.backend.htdigest.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 13 */ + { "auth.backend.htpasswd.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 14 */ + { "auth.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 15 */ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } }; @@ -341,6 +360,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_starttls = 0; @@ -351,6 +371,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 @@ -365,10 +387,11 @@ cv[8].destination = &(s->auth_ldap_starttls); cv[9].destination = s->auth_ldap_binddn; cv[10].destination = s->auth_ldap_bindpw; - cv[11].destination = &(s->auth_ldap_allow_empty_pw); - cv[12].destination = s->auth_htdigest_userfile; - cv[13].destination = s->auth_htpasswd_userfile; - cv[14].destination = &(s->auth_debug); + cv[11].destination = s->auth_ldap_userdn; + cv[12].destination = &(s->auth_ldap_allow_empty_pw); + cv[13].destination = s->auth_htdigest_userfile; + cv[14].destination = s->auth_htpasswd_userfile; + cv[15].destination = &(s->auth_debug); p->config_storage[i] = s; ca = ((data_config *)srv->config_context->data[i])->value; @@ -617,18 +640,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; + } } } }