18 buffer
*auth_ldap_hostname
;
19 buffer
*auth_ldap_basedn
;
20 buffer
*auth_ldap_binddn
;
21 buffer
*auth_ldap_bindpw
;
22 buffer
*auth_ldap_filter
;
23 buffer
*auth_ldap_cafile
;
24 unsigned short auth_ldap_starttls
;
25 unsigned short auth_ldap_allow_empty_pw
;
30 plugin_config
**config_storage
;
31 plugin_config conf
, *anon_conf
; /* this is only used as long as no handler_ctx is setup */
36 static handler_t
mod_authn_ldap_basic(server
*srv
, connection
*con
, void *p_d
, const http_auth_require_t
*require
, const buffer
*username
, const char *pw
);
38 INIT_FUNC(mod_authn_ldap_init
) {
39 static http_auth_backend_t http_auth_backend_ldap
=
40 { "ldap", mod_authn_ldap_basic
, NULL
, NULL
};
41 plugin_data
*p
= calloc(1, sizeof(*p
));
42 p
->ldap_filter
= buffer_init();
44 /* register http_auth_backend_ldap */
45 http_auth_backend_ldap
.p_d
= p
;
46 http_auth_backend_set(&http_auth_backend_ldap
);
51 FREE_FUNC(mod_authn_ldap_free
) {
56 if (!p
) return HANDLER_GO_ON
;
58 buffer_free(p
->ldap_filter
);
60 if (p
->config_storage
) {
62 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
63 plugin_config
*s
= p
->config_storage
[i
];
65 if (NULL
== s
) continue;
67 buffer_free(s
->auth_ldap_hostname
);
68 buffer_free(s
->auth_ldap_basedn
);
69 buffer_free(s
->auth_ldap_binddn
);
70 buffer_free(s
->auth_ldap_bindpw
);
71 buffer_free(s
->auth_ldap_filter
);
72 buffer_free(s
->auth_ldap_cafile
);
74 if (NULL
!= s
->ldap
) ldap_unbind_ext_s(s
->ldap
, NULL
, NULL
);
77 free(p
->config_storage
);
85 SETDEFAULTS_FUNC(mod_authn_ldap_set_defaults
) {
88 config_values_t cv
[] = {
89 { "auth.backend.ldap.hostname", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
90 { "auth.backend.ldap.base-dn", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
91 { "auth.backend.ldap.filter", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
92 { "auth.backend.ldap.ca-file", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
93 { "auth.backend.ldap.starttls", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 4 */
94 { "auth.backend.ldap.bind-dn", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 5 */
95 { "auth.backend.ldap.bind-pw", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 6 */
96 { "auth.backend.ldap.allow-empty-pw", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 7 */
97 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
100 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
102 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
103 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
106 s
= calloc(1, sizeof(plugin_config
));
108 s
->auth_ldap_hostname
= buffer_init();
109 s
->auth_ldap_basedn
= buffer_init();
110 s
->auth_ldap_binddn
= buffer_init();
111 s
->auth_ldap_bindpw
= buffer_init();
112 s
->auth_ldap_filter
= buffer_init();
113 s
->auth_ldap_cafile
= buffer_init();
114 s
->auth_ldap_starttls
= 0;
117 cv
[0].destination
= s
->auth_ldap_hostname
;
118 cv
[1].destination
= s
->auth_ldap_basedn
;
119 cv
[2].destination
= s
->auth_ldap_filter
;
120 cv
[3].destination
= s
->auth_ldap_cafile
;
121 cv
[4].destination
= &(s
->auth_ldap_starttls
);
122 cv
[5].destination
= s
->auth_ldap_binddn
;
123 cv
[6].destination
= s
->auth_ldap_bindpw
;
124 cv
[7].destination
= &(s
->auth_ldap_allow_empty_pw
);
126 p
->config_storage
[i
] = s
;
128 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
129 return HANDLER_ERROR
;
132 if (!buffer_string_is_empty(s
->auth_ldap_filter
)) {
133 if (*s
->auth_ldap_filter
->ptr
!= ','
134 && NULL
== strchr(s
->auth_ldap_filter
->ptr
, '$')) {
135 log_error_write(srv
, __FILE__
, __LINE__
, "s", "ldap: auth.backend.ldap.filter is missing a replace-operator '$'");
137 return HANDLER_ERROR
;
142 return HANDLER_GO_ON
;
147 static int mod_authn_ldap_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
149 plugin_config
*s
= p
->config_storage
[0];
151 PATCH(auth_ldap_hostname
);
152 PATCH(auth_ldap_basedn
);
153 PATCH(auth_ldap_binddn
);
154 PATCH(auth_ldap_bindpw
);
155 PATCH(auth_ldap_filter
);
156 PATCH(auth_ldap_cafile
);
157 PATCH(auth_ldap_starttls
);
158 PATCH(auth_ldap_allow_empty_pw
);
161 /* skip the first, the global context */
162 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
163 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
164 s
= p
->config_storage
[i
];
166 /* condition didn't match */
167 if (!config_check_cond(srv
, con
, dc
)) continue;
170 for (j
= 0; j
< dc
->value
->used
; j
++) {
171 data_unset
*du
= dc
->value
->data
[j
];
173 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.hostname"))) {
174 PATCH(auth_ldap_hostname
);
176 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.base-dn"))) {
177 PATCH(auth_ldap_basedn
);
178 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.filter"))) {
179 PATCH(auth_ldap_filter
);
180 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.ca-file"))) {
181 PATCH(auth_ldap_cafile
);
182 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.starttls"))) {
183 PATCH(auth_ldap_starttls
);
184 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.bind-dn"))) {
185 PATCH(auth_ldap_binddn
);
186 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.bind-pw"))) {
187 PATCH(auth_ldap_bindpw
);
188 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.allow-empty-pw"))) {
189 PATCH(auth_ldap_allow_empty_pw
);
198 static void mod_authn_ldap_err(server
*srv
, const char *file
, unsigned long line
, const char *fn
, int err
)
200 log_error_write(srv
,file
,line
,"sSss","ldap:",fn
,":",ldap_err2string(err
));
203 static void mod_authn_ldap_opt_err(server
*srv
, const char *file
, unsigned long line
, const char *fn
, LDAP
*ld
)
206 ldap_get_option(ld
, LDAP_OPT_ERROR_NUMBER
, &err
);
207 mod_authn_ldap_err(srv
, file
, line
, fn
, err
);
210 static LDAP
* mod_authn_ldap_host_init(server
*srv
, plugin_config
*s
) {
214 if (buffer_string_is_empty(s
->auth_ldap_hostname
)) return NULL
;
216 ld
= ldap_init(s
->auth_ldap_hostname
->ptr
, LDAP_PORT
);
218 log_error_write(srv
, __FILE__
, __LINE__
, "sss", "ldap:", "ldap_init():",
224 ret
= ldap_set_option(ld
, LDAP_OPT_PROTOCOL_VERSION
, &ret
);
225 if (LDAP_OPT_SUCCESS
!= ret
) {
226 mod_authn_ldap_err(srv
, __FILE__
, __LINE__
, "ldap_set_options()", ret
);
231 if (s
->auth_ldap_starttls
) {
232 /* if no CA file is given, it is ok, as we will use encryption
233 * if the server requires a CAfile it will tell us */
234 if (!buffer_string_is_empty(s
->auth_ldap_cafile
)) {
235 ret
= ldap_set_option(NULL
, LDAP_OPT_X_TLS_CACERTFILE
,
236 s
->auth_ldap_cafile
->ptr
);
237 if (LDAP_OPT_SUCCESS
!= ret
) {
238 mod_authn_ldap_err(srv
, __FILE__
, __LINE__
,
239 "ldap_set_option(LDAP_OPT_X_TLS_CACERTFILE)",
246 ret
= ldap_start_tls_s(ld
, NULL
, NULL
);
247 if (LDAP_OPT_SUCCESS
!= ret
) {
248 mod_authn_ldap_err(srv
,__FILE__
,__LINE__
,"ldap_start_tls_s()",ret
);
257 static int mod_authn_ldap_bind(server
*srv
, LDAP
*ld
, const char *dn
, const char *pw
) {
263 *((const char **)&creds
.bv_val
) = pw
; /*(cast away const)*/
264 creds
.bv_len
= strlen(pw
);
270 /* RFE: add functionality: LDAP_SASL_EXTERNAL (or GSS-SPNEGO, etc.) */
272 ret
= ldap_sasl_bind_s(ld
,dn
,LDAP_SASL_SIMPLE
,&creds
,NULL
,NULL
,NULL
);
273 if (ret
!= LDAP_SUCCESS
) {
274 mod_authn_ldap_err(srv
, __FILE__
, __LINE__
, "ldap_sasl_bind_s()", ret
);
277 int ret
= ldap_simple_bind_s(ld
, dn
, pw
);
278 if (ret
!= LDAP_SUCCESS
) {
279 mod_authn_ldap_err(srv
, __FILE__
, __LINE__
, "ldap_simple_bind_s()",ret
);
286 static LDAPMessage
* mod_authn_ldap_search(server
*srv
, plugin_config
*s
, char *base
, char *filter
) {
287 LDAPMessage
*lm
= NULL
;
288 char *attrs
[] = { LDAP_NO_ATTRS
, NULL
};
292 * 1. connect anonymously (if not already connected)
293 * (ldap connection is kept open unless connection-level error occurs)
294 * 2. issue search using filter
297 if (s
->ldap
!= NULL
) {
298 ret
= ldap_search_ext_s(s
->ldap
, base
, LDAP_SCOPE_SUBTREE
, filter
,
299 attrs
, 0, NULL
, NULL
, NULL
, 0, &lm
);
300 if (LDAP_SUCCESS
== ret
) {
302 } else if (LDAP_SERVER_DOWN
!= ret
) {
303 /* try again (or initial request);
304 * ldap lib sometimes fails for the first call but reconnects */
305 ret
= ldap_search_ext_s(s
->ldap
, base
, LDAP_SCOPE_SUBTREE
, filter
,
306 attrs
, 0, NULL
, NULL
, NULL
, 0, &lm
);
307 if (LDAP_SUCCESS
== ret
) {
312 ldap_unbind_ext_s(s
->ldap
, NULL
, NULL
);
315 s
->ldap
= mod_authn_ldap_host_init(srv
, s
);
316 if (NULL
== s
->ldap
) {
320 ret
= !buffer_string_is_empty(s
->auth_ldap_binddn
)
321 ? mod_authn_ldap_bind(srv
, s
->ldap
,
322 s
->auth_ldap_binddn
->ptr
,
323 s
->auth_ldap_bindpw
->ptr
)
324 : mod_authn_ldap_bind(srv
, s
->ldap
, NULL
, NULL
);
325 if (LDAP_SUCCESS
!= ret
) {
326 ldap_memfree(s
->ldap
);
331 ret
= ldap_search_ext_s(s
->ldap
, base
, LDAP_SCOPE_SUBTREE
, filter
,
332 attrs
, 0, NULL
, NULL
, NULL
, 0, &lm
);
333 if (LDAP_SUCCESS
!= ret
) {
334 log_error_write(srv
, __FILE__
, __LINE__
, "sSss",
335 "ldap:", ldap_err2string(ret
), "; filter:", filter
);
336 ldap_unbind_ext_s(s
->ldap
, NULL
, NULL
);
344 static char * mod_authn_ldap_get_dn(server
*srv
, plugin_config
*s
, char *base
, char *filter
) {
346 LDAPMessage
*lm
, *first
;
350 lm
= mod_authn_ldap_search(srv
, s
, base
, filter
);
355 ld
= s
->ldap
; /*(must be after mod_authn_ldap_search(); might reconnect)*/
357 count
= ldap_count_entries(ld
, lm
);
358 if (0 == count
) { /*(no entires found)*/
361 } else if (count
> 1) {
362 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
363 "ldap:", "more than one record returned. "
364 "you might have to refine the filter:", filter
);
367 if (NULL
== (first
= ldap_first_entry(ld
, lm
))) {
368 mod_authn_ldap_opt_err(srv
,__FILE__
,__LINE__
,"ldap_first_entry()",ld
);
373 if (NULL
== (dn
= ldap_get_dn(ld
, first
))) {
374 mod_authn_ldap_opt_err(srv
,__FILE__
,__LINE__
,"ldap_get_dn()",ld
);
383 static handler_t
mod_authn_ldap_basic(server
*srv
, connection
*con
, void *p_d
, const http_auth_require_t
*require
, const buffer
*username
, const char *pw
) {
384 plugin_data
*p
= (plugin_data
*)p_d
;
389 mod_authn_ldap_patch_connection(srv
, con
, p
);
391 if (pw
[0] == '\0' && !p
->conf
.auth_ldap_allow_empty_pw
)
392 return HANDLER_ERROR
;
396 * we have to protect againt username which modifies our filter in
400 for (size_t i
= 0, len
= buffer_string_length(username
); i
< len
; i
++) {
401 char c
= username
->ptr
[i
];
411 log_error_write(srv
, __FILE__
, __LINE__
, "sbd",
412 "ldap: invalid character (- _.@a-zA-Z0-9 allowed) "
413 "in username:", username
, i
);
415 con
->http_status
= 400; /* Bad Request */
417 return HANDLER_FINISHED
;
421 template = p
->conf
.auth_ldap_filter
;
422 if (buffer_string_is_empty(template)) {
423 return HANDLER_ERROR
;
426 /* build filter to get DN for uid = username */
427 buffer_string_set_length(p
->ldap_filter
, 0);
428 if (*template->ptr
== ',') {
429 /* special-case filter template beginning with ',' to be explicit DN */
430 buffer_append_string_len(p
->ldap_filter
, CONST_STR_LEN("uid="));
431 buffer_append_string_buffer(p
->ldap_filter
, username
);
432 buffer_append_string_buffer(p
->ldap_filter
, template);
433 dn
= p
->ldap_filter
->ptr
;
435 for (char *b
= template->ptr
, *d
; *b
; b
= d
+1) {
436 if (NULL
!= (d
= strchr(b
, '$'))) {
437 buffer_append_string_len(p
->ldap_filter
, b
, (size_t)(d
- b
));
438 buffer_append_string_buffer(p
->ldap_filter
, username
);
440 d
= template->ptr
+ buffer_string_length(template);
441 buffer_append_string_len(p
->ldap_filter
, b
, (size_t)(d
- b
));
446 /* ldap_search for DN (synchronous; blocking) */
447 dn
= mod_authn_ldap_get_dn(srv
, p
->anon_conf
,
448 p
->conf
.auth_ldap_basedn
->ptr
,
449 p
->ldap_filter
->ptr
);
451 return HANDLER_ERROR
;
455 /* auth against LDAP server (synchronous; blocking) */
457 ld
= mod_authn_ldap_host_init(srv
, &p
->conf
);
459 if (dn
!= p
->ldap_filter
->ptr
) ldap_memfree(dn
);
460 return HANDLER_ERROR
;
463 if (LDAP_SUCCESS
!= mod_authn_ldap_bind(srv
, ld
, dn
, pw
)) {
465 if (dn
!= p
->ldap_filter
->ptr
) ldap_memfree(dn
);
466 return HANDLER_ERROR
;
469 ldap_unbind_ext_s(ld
, NULL
, NULL
); /* disconnect */
470 if (dn
!= p
->ldap_filter
->ptr
) ldap_memfree(dn
);
471 return http_auth_match_rules(require
, username
->ptr
, NULL
, NULL
)
472 ? HANDLER_GO_ON
/* access granted */
476 int mod_authn_ldap_plugin_init(plugin
*p
);
477 int mod_authn_ldap_plugin_init(plugin
*p
) {
478 p
->version
= LIGHTTPD_VERSION_ID
;
479 p
->name
= buffer_init_string("authn_ldap");
480 p
->init
= mod_authn_ldap_init
;
481 p
->set_defaults
= mod_authn_ldap_set_defaults
;
482 p
->cleanup
= mod_authn_ldap_free
;