5 #if defined(HAVE_LDAP_H) && defined(HAVE_LBER_H) && defined(HAVE_LIBLDAP) && defined(HAVE_LIBLBER)
11 #include "http_auth.h"
21 buffer
*auth_ldap_hostname
;
22 buffer
*auth_ldap_basedn
;
23 buffer
*auth_ldap_binddn
;
24 buffer
*auth_ldap_bindpw
;
25 buffer
*auth_ldap_filter
;
26 buffer
*auth_ldap_cafile
;
27 unsigned short auth_ldap_starttls
;
28 unsigned short auth_ldap_allow_empty_pw
;
33 plugin_config
**config_storage
;
34 plugin_config conf
, *anon_conf
; /* this is only used as long as no handler_ctx is setup */
39 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
);
41 INIT_FUNC(mod_authn_ldap_init
) {
42 static http_auth_backend_t http_auth_backend_ldap
=
43 { "ldap", mod_authn_ldap_basic
, NULL
, NULL
};
44 plugin_data
*p
= calloc(1, sizeof(*p
));
45 p
->ldap_filter
= buffer_init();
47 /* register http_auth_backend_ldap */
48 http_auth_backend_ldap
.p_d
= p
;
49 http_auth_backend_set(&http_auth_backend_ldap
);
54 FREE_FUNC(mod_authn_ldap_free
) {
59 if (!p
) return HANDLER_GO_ON
;
61 buffer_free(p
->ldap_filter
);
63 if (p
->config_storage
) {
65 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
66 plugin_config
*s
= p
->config_storage
[i
];
68 if (NULL
== s
) continue;
70 buffer_free(s
->auth_ldap_hostname
);
71 buffer_free(s
->auth_ldap_basedn
);
72 buffer_free(s
->auth_ldap_binddn
);
73 buffer_free(s
->auth_ldap_bindpw
);
74 buffer_free(s
->auth_ldap_filter
);
75 buffer_free(s
->auth_ldap_cafile
);
77 if (NULL
!= s
->ldap
) ldap_unbind_ext_s(s
->ldap
, NULL
, NULL
);
80 free(p
->config_storage
);
88 SETDEFAULTS_FUNC(mod_authn_ldap_set_defaults
) {
91 config_values_t cv
[] = {
92 { "auth.backend.ldap.hostname", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
93 { "auth.backend.ldap.base-dn", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
94 { "auth.backend.ldap.filter", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
95 { "auth.backend.ldap.ca-file", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
96 { "auth.backend.ldap.starttls", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 4 */
97 { "auth.backend.ldap.bind-dn", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 5 */
98 { "auth.backend.ldap.bind-pw", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 6 */
99 { "auth.backend.ldap.allow-empty-pw", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 7 */
100 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
103 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
105 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
106 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
109 s
= calloc(1, sizeof(plugin_config
));
111 s
->auth_ldap_hostname
= buffer_init();
112 s
->auth_ldap_basedn
= buffer_init();
113 s
->auth_ldap_binddn
= buffer_init();
114 s
->auth_ldap_bindpw
= buffer_init();
115 s
->auth_ldap_filter
= buffer_init();
116 s
->auth_ldap_cafile
= buffer_init();
117 s
->auth_ldap_starttls
= 0;
120 cv
[0].destination
= s
->auth_ldap_hostname
;
121 cv
[1].destination
= s
->auth_ldap_basedn
;
122 cv
[2].destination
= s
->auth_ldap_filter
;
123 cv
[3].destination
= s
->auth_ldap_cafile
;
124 cv
[4].destination
= &(s
->auth_ldap_starttls
);
125 cv
[5].destination
= s
->auth_ldap_binddn
;
126 cv
[6].destination
= s
->auth_ldap_bindpw
;
127 cv
[7].destination
= &(s
->auth_ldap_allow_empty_pw
);
129 p
->config_storage
[i
] = s
;
131 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
132 return HANDLER_ERROR
;
135 if (!buffer_string_is_empty(s
->auth_ldap_filter
)) {
136 if (*s
->auth_ldap_filter
->ptr
!= ','
137 && NULL
== strchr(s
->auth_ldap_filter
->ptr
, '$')) {
138 log_error_write(srv
, __FILE__
, __LINE__
, "s", "ldap: auth.backend.ldap.filter is missing a replace-operator '$'");
140 return HANDLER_ERROR
;
145 return HANDLER_GO_ON
;
150 static int mod_authn_ldap_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
152 plugin_config
*s
= p
->config_storage
[0];
154 PATCH(auth_ldap_hostname
);
155 PATCH(auth_ldap_basedn
);
156 PATCH(auth_ldap_binddn
);
157 PATCH(auth_ldap_bindpw
);
158 PATCH(auth_ldap_filter
);
159 PATCH(auth_ldap_cafile
);
160 PATCH(auth_ldap_starttls
);
161 PATCH(auth_ldap_allow_empty_pw
);
164 /* skip the first, the global context */
165 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
166 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
167 s
= p
->config_storage
[i
];
169 /* condition didn't match */
170 if (!config_check_cond(srv
, con
, dc
)) continue;
173 for (j
= 0; j
< dc
->value
->used
; j
++) {
174 data_unset
*du
= dc
->value
->data
[j
];
176 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.hostname"))) {
177 PATCH(auth_ldap_hostname
);
179 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.base-dn"))) {
180 PATCH(auth_ldap_basedn
);
181 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.filter"))) {
182 PATCH(auth_ldap_filter
);
183 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.ca-file"))) {
184 PATCH(auth_ldap_cafile
);
185 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.starttls"))) {
186 PATCH(auth_ldap_starttls
);
187 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.bind-dn"))) {
188 PATCH(auth_ldap_binddn
);
189 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.bind-pw"))) {
190 PATCH(auth_ldap_bindpw
);
191 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.ldap.allow-empty-pw"))) {
192 PATCH(auth_ldap_allow_empty_pw
);
201 static void mod_authn_ldap_err(server
*srv
, const char *file
, unsigned long line
, const char *fn
, int err
)
203 log_error_write(srv
,file
,line
,"sSss","ldap:",fn
,":",ldap_err2string(err
));
206 static void mod_authn_ldap_opt_err(server
*srv
, const char *file
, unsigned long line
, const char *fn
, LDAP
*ld
)
209 ldap_get_option(ld
, LDAP_OPT_ERROR_NUMBER
, &err
);
210 mod_authn_ldap_err(srv
, file
, line
, fn
, err
);
213 static LDAP
* mod_authn_ldap_host_init(server
*srv
, plugin_config
*s
) {
217 if (buffer_string_is_empty(s
->auth_ldap_hostname
)) return NULL
;
219 ld
= ldap_init(s
->auth_ldap_hostname
->ptr
, LDAP_PORT
);
221 log_error_write(srv
, __FILE__
, __LINE__
, "sss", "ldap:", "ldap_init():",
227 ret
= ldap_set_option(ld
, LDAP_OPT_PROTOCOL_VERSION
, &ret
);
228 if (LDAP_OPT_SUCCESS
!= ret
) {
229 mod_authn_ldap_err(srv
, __FILE__
, __LINE__
, "ldap_set_options()", ret
);
234 if (s
->auth_ldap_starttls
) {
235 /* if no CA file is given, it is ok, as we will use encryption
236 * if the server requires a CAfile it will tell us */
237 if (!buffer_string_is_empty(s
->auth_ldap_cafile
)) {
238 ret
= ldap_set_option(NULL
, LDAP_OPT_X_TLS_CACERTFILE
,
239 s
->auth_ldap_cafile
->ptr
);
240 if (LDAP_OPT_SUCCESS
!= ret
) {
241 mod_authn_ldap_err(srv
, __FILE__
, __LINE__
,
242 "ldap_set_option(LDAP_OPT_X_TLS_CACERTFILE)",
249 ret
= ldap_start_tls_s(ld
, NULL
, NULL
);
250 if (LDAP_OPT_SUCCESS
!= ret
) {
251 mod_authn_ldap_err(srv
,__FILE__
,__LINE__
,"ldap_start_tls_s()",ret
);
260 static int mod_authn_ldap_bind(server
*srv
, LDAP
*ld
, const char *dn
, const char *pw
) {
266 *((const char **)&creds
.bv_val
) = pw
; /*(cast away const)*/
267 creds
.bv_len
= strlen(pw
);
273 /* RFE: add functionality: LDAP_SASL_EXTERNAL (or GSS-SPNEGO, etc.) */
275 ret
= ldap_sasl_bind_s(ld
,dn
,LDAP_SASL_SIMPLE
,&creds
,NULL
,NULL
,NULL
);
276 if (ret
!= LDAP_SUCCESS
) {
277 mod_authn_ldap_err(srv
, __FILE__
, __LINE__
, "ldap_sasl_bind_s()", ret
);
280 int ret
= ldap_simple_bind_s(ld
, dn
, pw
);
281 if (ret
!= LDAP_SUCCESS
) {
282 mod_authn_ldap_err(srv
, __FILE__
, __LINE__
, "ldap_simple_bind_s()",ret
);
289 static LDAPMessage
* mod_authn_ldap_search(server
*srv
, plugin_config
*s
, char *base
, char *filter
) {
290 LDAPMessage
*lm
= NULL
;
291 char *attrs
[] = { LDAP_NO_ATTRS
, NULL
};
295 * 1. connect anonymously (if not already connected)
296 * (ldap connection is kept open unless connection-level error occurs)
297 * 2. issue search using filter
300 if (s
->ldap
!= NULL
) {
301 ret
= ldap_search_ext_s(s
->ldap
, base
, LDAP_SCOPE_SUBTREE
, filter
,
302 attrs
, 0, NULL
, NULL
, NULL
, 0, &lm
);
303 if (LDAP_SUCCESS
== ret
) {
305 } else if (LDAP_SERVER_DOWN
!= ret
) {
306 /* try again (or initial request);
307 * ldap lib sometimes fails for the first call but reconnects */
308 ret
= ldap_search_ext_s(s
->ldap
, base
, LDAP_SCOPE_SUBTREE
, filter
,
309 attrs
, 0, NULL
, NULL
, NULL
, 0, &lm
);
310 if (LDAP_SUCCESS
== ret
) {
315 ldap_unbind_ext_s(s
->ldap
, NULL
, NULL
);
318 s
->ldap
= mod_authn_ldap_host_init(srv
, s
);
319 if (NULL
== s
->ldap
) {
323 ret
= !buffer_string_is_empty(s
->auth_ldap_binddn
)
324 ? mod_authn_ldap_bind(srv
, s
->ldap
,
325 s
->auth_ldap_binddn
->ptr
,
326 s
->auth_ldap_bindpw
->ptr
)
327 : mod_authn_ldap_bind(srv
, s
->ldap
, NULL
, NULL
);
328 if (LDAP_SUCCESS
!= ret
) {
329 ldap_memfree(s
->ldap
);
334 ret
= ldap_search_ext_s(s
->ldap
, base
, LDAP_SCOPE_SUBTREE
, filter
,
335 attrs
, 0, NULL
, NULL
, NULL
, 0, &lm
);
336 if (LDAP_SUCCESS
!= ret
) {
337 log_error_write(srv
, __FILE__
, __LINE__
, "sSss",
338 "ldap:", ldap_err2string(ret
), "; filter:", filter
);
339 ldap_unbind_ext_s(s
->ldap
, NULL
, NULL
);
347 static char * mod_authn_ldap_get_dn(server
*srv
, plugin_config
*s
, char *base
, char *filter
) {
349 LDAPMessage
*lm
, *first
;
353 lm
= mod_authn_ldap_search(srv
, s
, base
, filter
);
358 ld
= s
->ldap
; /*(must be after mod_authn_ldap_search(); might reconnect)*/
360 count
= ldap_count_entries(ld
, lm
);
361 if (0 == count
) { /*(no entires found)*/
364 } else if (count
> 1) {
365 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
366 "ldap:", "more than one record returned. "
367 "you might have to refine the filter:", filter
);
370 if (NULL
== (first
= ldap_first_entry(ld
, lm
))) {
371 mod_authn_ldap_opt_err(srv
,__FILE__
,__LINE__
,"ldap_first_entry()",ld
);
376 if (NULL
== (dn
= ldap_get_dn(ld
, first
))) {
377 mod_authn_ldap_opt_err(srv
,__FILE__
,__LINE__
,"ldap_get_dn()",ld
);
386 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
) {
387 plugin_data
*p
= (plugin_data
*)p_d
;
392 mod_authn_ldap_patch_connection(srv
, con
, p
);
394 if (pw
[0] == '\0' && !p
->conf
.auth_ldap_allow_empty_pw
)
395 return HANDLER_ERROR
;
399 * we have to protect againt username which modifies our filter in
403 for (size_t i
= 0, len
= buffer_string_length(username
); i
< len
; i
++) {
404 char c
= username
->ptr
[i
];
414 log_error_write(srv
, __FILE__
, __LINE__
, "sbd",
415 "ldap: invalid character (- _.@a-zA-Z0-9 allowed) "
416 "in username:", username
, i
);
418 con
->http_status
= 400; /* Bad Request */
420 return HANDLER_FINISHED
;
424 template = p
->conf
.auth_ldap_filter
;
425 if (buffer_string_is_empty(template)) {
426 return HANDLER_ERROR
;
429 /* build filter to get DN for uid = username */
430 buffer_string_set_length(p
->ldap_filter
, 0);
431 if (*template->ptr
== ',') {
432 /* special-case filter template beginning with ',' to be explicit DN */
433 buffer_append_string_len(p
->ldap_filter
, CONST_STR_LEN("uid="));
434 buffer_append_string_buffer(p
->ldap_filter
, username
);
435 buffer_append_string_buffer(p
->ldap_filter
, template);
436 dn
= p
->ldap_filter
->ptr
;
438 for (char *b
= template->ptr
, *d
; *b
; b
= d
+1) {
439 if (NULL
!= (d
= strchr(b
, '$'))) {
440 buffer_append_string_len(p
->ldap_filter
, b
, (size_t)(d
- b
));
441 buffer_append_string_buffer(p
->ldap_filter
, username
);
443 d
= template->ptr
+ buffer_string_length(template);
444 buffer_append_string_len(p
->ldap_filter
, b
, (size_t)(d
- b
));
449 /* ldap_search for DN (synchronous; blocking) */
450 dn
= mod_authn_ldap_get_dn(srv
, p
->anon_conf
,
451 p
->conf
.auth_ldap_basedn
->ptr
,
452 p
->ldap_filter
->ptr
);
454 return HANDLER_ERROR
;
458 /* auth against LDAP server (synchronous; blocking) */
460 ld
= mod_authn_ldap_host_init(srv
, &p
->conf
);
462 if (dn
!= p
->ldap_filter
->ptr
) ldap_memfree(dn
);
463 return HANDLER_ERROR
;
466 if (LDAP_SUCCESS
!= mod_authn_ldap_bind(srv
, ld
, dn
, pw
)) {
468 if (dn
!= p
->ldap_filter
->ptr
) ldap_memfree(dn
);
469 return HANDLER_ERROR
;
472 ldap_unbind_ext_s(ld
, NULL
, NULL
); /* disconnect */
473 if (dn
!= p
->ldap_filter
->ptr
) ldap_memfree(dn
);
474 return http_auth_match_rules(require
, username
->ptr
, NULL
, NULL
)
475 ? HANDLER_GO_ON
/* access granted */
479 int mod_authn_ldap_plugin_init(plugin
*p
);
480 int mod_authn_ldap_plugin_init(plugin
*p
) {
481 p
->version
= LIGHTTPD_VERSION_ID
;
482 p
->name
= buffer_init_string("authn_ldap");
483 p
->init
= mod_authn_ldap_init
;
484 p
->set_defaults
= mod_authn_ldap_set_defaults
;
485 p
->cleanup
= mod_authn_ldap_free
;
494 int mod_authn_ldap_plugin_init(plugin
*p
);
495 int mod_authn_ldap_plugin_init(plugin
*p
) {