1 /* ============================================================
2 * Copyright (c) 2003-2004, Andreas Brenk
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or
6 * without modification, are permitted provided that the
7 * following conditions are met:
8 *
9 * * Redistributions of source code must retain the above
10 * copyright notice, this list of conditions and the
11 * following disclaimer.
12 * * Redistributions in binary form must reproduce the
13 * above copyright notice, this list of conditions and
14 * the following disclaimer in the documentation and/or
15 * other materials provided with the distribution.
16 * * Neither the names of the copyright holders nor the
17 * names of its contributors may be used to endorse or
18 * promote products derived from this software without
19 * specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
22 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
23 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
24 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
26 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 * POSSIBILITY OF SUCH DAMAGE.
35 * ============================================================
36 */
37
38 /*
39 * mod_cfg_ldap.c --- read vhost config from an LDAP directory
40 */
41
42 #include "httpd.h"
43 #include "http_config.h"
44 #include "http_core.h"
45 #include "http_log.h"
46 #include "http_request.h"
47 #include "apr_strings.h"
48 #include "ldap.h"
49
50 #define CFG_LDAP_NO_SUCH_VHOST "cfg_ldap_no_such_vhost"
51
52 #define HOST_NOT_FOUND 0
53 #define HOST_FOUND 1
54 #define MULTIPLE_HOST_FOUND 2
55
56 module AP_MODULE_DECLARE_DATA cfg_ldap_module;
57
58 typedef struct cfg_ldap_cfg
59 {
60 int enabled;
61 apr_time_t cachettl;
62 const char *hostname;
63 int port;
64 int usetls;
65 const char *cert_auth_file;
66 const char *username;
67 const char *password;
68 const char *basedn;
69 const char *filter;
70 } cfg_ldap_cfg;
71
72 typedef struct cfg_ldap_vhost
73 {
74 const char *name;
75 const char *admin;
76 const char *docroot;
77 apr_time_t timestamp;
78 } cfg_ldap_vhost;
79
80 typedef struct cfg_ldap_novhost
81 {
82 apr_time_t timestamp;
83 } cfg_ldap_novhost;
84
85 static apr_pool_t *pool = NULL;
86 static apr_hash_t *cache = NULL;
87 static LDAP *ld = NULL;
88
89 static const char *attrs[] =
90 { "apacheServerName", "apacheServerAdmin", "apacheDocumentRoot", 0 };
91
92 static void
cfg_ldap_init_ldap(apr_pool_t * p,server_rec * s)93 cfg_ldap_init_ldap (apr_pool_t * p, server_rec * s)
94 {
95 int failures = 0;
96
97 cfg_ldap_cfg *cfg;
98 cfg =
99 (cfg_ldap_cfg *) ap_get_module_config (s->module_config,
100 &cfg_ldap_module);
101
102 int rc;
103
104 do
105 {
106 if (cfg->usetls)
107 {
108 ld = ldap_init (cfg->hostname, LDAPS_PORT);
109
110 int version = LDAP_VERSION3;
111
112 if ((rc =
113 ldap_set_option (ld, LDAP_OPT_PROTOCOL_VERSION,
114 &version)) != LDAP_OPT_SUCCESS)
115 {
116 ap_log_error (APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, 0, NULL,
117 "mod_cfg_ldap: Setting LDAP version option failed: %s",
118 ldap_err2string (rc));
119 ldap_unbind (ld);
120 ld = NULL;
121 return;
122 }
123
124 int SSLmode = LDAP_OPT_X_TLS_HARD;
125 if ((rc =
126 ldap_set_option (ld, LDAP_OPT_X_TLS,
127 &SSLmode)) != LDAP_SUCCESS)
128 {
129 ldap_unbind_s (ld);
130 ap_log_error (APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, 0, NULL,
131 "mod_cfg_ldap: ldap_set_option - LDAP_OPT_X_TLS_HARD failed");
132 ld = NULL;
133 return;
134 }
135
136 if ((rc =
137 ldap_set_option (NULL, LDAP_OPT_X_TLS_CACERTFILE,
138 cfg->cert_auth_file)) != LDAP_SUCCESS)
139 {
140 ldap_unbind_s (ld);
141 ap_log_error (APLOG_MARK, APLOG_CRIT, 0, s,
142 "mod_cfg_ldap: Invalid LDAPTrustedCA directive (%s): %s",
143 cfg->cert_auth_file, ldap_err2string (rc));
144 ld = NULL;
145 return;
146 }
147 }
148 else
149 {
150 ld = ldap_init (cfg->hostname, LDAP_PORT);
151 }
152
153 rc = ldap_simple_bind_s (ld, cfg->username, cfg->password);
154
155 // TODO extract "5" to MAX_FAILURES
156 if (rc == LDAP_SERVER_DOWN && failures++ <= 5)
157 {
158 ap_log_error (APLOG_MARK, APLOG_WARNING, 0, s,
159 "cfg_ldap: unknown ldap error %d", rc);
160 break;
161 }
162 }
163 while (!rc == LDAP_SUCCESS);
164 }
165
166 static void *
cfg_ldap_create_server_config(apr_pool_t * p,server_rec * s)167 cfg_ldap_create_server_config (apr_pool_t * p, server_rec * s)
168 {
169 cfg_ldap_cfg *cfg;
170 cfg = (cfg_ldap_cfg *) apr_pcalloc (p, sizeof (cfg_ldap_cfg));
171 cfg->enabled = 0;
172
173 return (void *) cfg;
174 }
175
176 static apr_status_t
cfg_ldap_child_exit(void * data)177 cfg_ldap_child_exit (void *data)
178 {
179 ldap_unbind (ld);
180
181 return APR_SUCCESS;
182 }
183
184 static void
cfg_ldap_child_init(apr_pool_t * p,server_rec * s)185 cfg_ldap_child_init (apr_pool_t * p, server_rec * s)
186 {
187 cfg_ldap_cfg *cfg;
188 cfg =
189 (cfg_ldap_cfg *) ap_get_module_config (s->module_config,
190 &cfg_ldap_module);
191
192 if (!cfg->enabled)
193 {
194 return;
195 }
196
197 if (pool == NULL)
198 {
199 apr_pool_create (&pool, NULL);
200 }
201
202 if (cache == NULL)
203 {
204 cache = apr_hash_make (p);
205 }
206
207 if (ld == NULL)
208 {
209 cfg_ldap_init_ldap (p, s);
210 }
211
212 apr_pool_cleanup_register (p, NULL, cfg_ldap_child_exit,
213 cfg_ldap_child_exit);
214 }
215
216 static cfg_ldap_vhost *
cfg_ldap_read_vhost_from_ldap(apr_pool_t * p,server_rec * s,char * hostname)217 cfg_ldap_read_vhost_from_ldap (apr_pool_t * p, server_rec * s, char *hostname)
218 {
219 cfg_ldap_cfg *cfg;
220 cfg =
221 (cfg_ldap_cfg *) ap_get_module_config (s->module_config,
222 &cfg_ldap_module);
223
224 cfg_ldap_vhost *vhost;
225 vhost = (cfg_ldap_vhost *) apr_pcalloc (p, sizeof (cfg_ldap_vhost));
226 vhost->timestamp = apr_time_now ();
227 vhost->name = CFG_LDAP_NO_SUCH_VHOST;
228
229 char *filter;
230 filter =
231 apr_pstrcat (p, "(&(|(apacheServerName=", hostname,
232 ")(apacheServerAlias=", hostname, "))", cfg->filter, ")",
233 NULL);
234
235 int rc;
236 LDAPMessage *res;
237 rc = ldap_search_s (ld, cfg->basedn, LDAP_SCOPE_SUBTREE, filter,
238 (char **) &attrs, 0, &res);
239
240 if (!rc == LDAP_SUCCESS)
241 {
242 if (rc == LDAP_SERVER_DOWN)
243 {
244 cfg_ldap_init_ldap (p, s);
245 return cfg_ldap_read_vhost_from_ldap (p, s, hostname);
246 }
247 ap_log_error (APLOG_MARK, APLOG_WARNING, 0, s,
248 "cfg_ldap: unknown ldap error %d", rc);
249 return vhost;
250 }
251
252 LDAPMessage *entry;
253 entry = ldap_first_entry (ld, res);
254
255 // count vhosts entries
256 int count = ldap_count_entries (ld, res);
257 if (count == 0)
258 switch (count)
259 {
260 case HOST_NOT_FOUND:
261 return vhost;
262 break;
263 case HOST_FOUND:
264 break;
265 case MULTIPLE_HOST_FOUND:
266 default:
267 ap_log_error (APLOG_MARK, APLOG_WARNING, 0, s,
268 "cfg_ldap: more than one entry for %s", hostname);
269 return vhost;
270 break;
271 }
272
273 char *attr;
274 BerElement *ber;
275 char **val = NULL;
276 char *vhost_name = NULL;
277 char *vhost_admin = NULL;
278 char *vhost_docroot = NULL;
279
280 for (attr = ldap_first_attribute (ld, entry, &ber);
281 attr != NULL; attr = ldap_next_attribute (ld, entry, ber))
282 {
283 val = ldap_get_values (ld, entry, attr);
284 if (strcasecmp (attr, "apacheServerName") == 0)
285 {
286 vhost_name = apr_pstrdup (p, val[0]);
287 }
288 if (strcasecmp (attr, "apacheServerAdmin") == 0)
289 {
290 vhost_admin = apr_pstrdup (p, val[0]);
291 }
292 if (strcasecmp (attr, "apacheDocumentRoot") == 0)
293 {
294 vhost_docroot = apr_pstrdup (p, val[0]);
295 }
296 }
297
298 ldap_value_free (val);
299 ldap_memfree (attr);
300
301 if (ber != NULL)
302 {
303 ber_free (ber, 0);
304 }
305
306 vhost->name = apr_pstrdup (p, vhost_name);
307 if (vhost_admin != NULL)
308 {
309 vhost->admin = apr_pstrdup (p, vhost_admin);
310 }
311 else
312 {
313 vhost->admin = apr_pstrdup (p, s->server_admin);
314 }
315 vhost->docroot = apr_pstrdup (p, vhost_docroot);
316
317 return vhost;
318 }
319
320 static int
cfg_ldap_translate_name(request_rec * r)321 cfg_ldap_translate_name (request_rec * r)
322 {
323 apr_table_t *e;
324 cfg_ldap_cfg *cfg;
325 cfg_ldap_vhost *vhost;
326
327 cfg =
328 (cfg_ldap_cfg *) ap_get_module_config (r->server->module_config,
329 &cfg_ldap_module);
330
331 // mod_cfg_ldap is disabled
332 if (!cfg->enabled)
333 {
334 return DECLINED;
335 }
336
337 vhost = apr_hash_get (cache, r->hostname, APR_HASH_KEY_STRING);
338
339 if (vhost == NULL)
340 {
341 vhost =
342 cfg_ldap_read_vhost_from_ldap (pool, r->server,
343 apr_pstrdup (r->pool, r->hostname));
344 apr_hash_set (cache, r->hostname, APR_HASH_KEY_STRING, vhost);
345 }
346 else
347 {
348 if ((vhost->timestamp + cfg->cachettl) < apr_time_now ())
349 {
350 apr_hash_set (cache, r->hostname, APR_HASH_KEY_STRING, NULL);
351 return cfg_ldap_translate_name (r);
352 }
353 }
354
355 // host not found
356 if (strcasecmp (vhost->name, CFG_LDAP_NO_SUCH_VHOST) == 0)
357 {
358 return DECLINED;
359 }
360
361 r->filename =
362 apr_pstrcat (r->pool, vhost->docroot, r->parsed_uri.path, NULL);
363 r->server->server_hostname = apr_pstrdup (r->pool, vhost->name);
364 r->server->server_admin = apr_pstrdup (r->pool, vhost->admin);
365
366 // set environment variables
367 e = r->subprocess_env;
368 apr_table_addn (e, "SERVER_ROOT", vhost->docroot);
369
370 return OK;
371 }
372
373 static const char *
set_cfg_ldap_enabled(cmd_parms * cmd,void * mconfig,const int enabled)374 set_cfg_ldap_enabled (cmd_parms * cmd, void *mconfig, const int enabled)
375 {
376 cfg_ldap_cfg *cfg;
377 cfg =
378 (cfg_ldap_cfg *) ap_get_module_config (cmd->server->module_config,
379 &cfg_ldap_module);
380 cfg->enabled = enabled;
381
382 return NULL;
383 }
384
385 static const char *
set_cfg_ldap_hostname(cmd_parms * cmd,void * mconfig,const char * hostname)386 set_cfg_ldap_hostname (cmd_parms * cmd, void *mconfig, const char *hostname)
387 {
388 cfg_ldap_cfg *cfg;
389 cfg =
390 (cfg_ldap_cfg *) ap_get_module_config (cmd->server->module_config,
391 &cfg_ldap_module);
392 cfg->hostname = hostname;
393
394 return NULL;
395 }
396
397 static const char *
set_cfg_ldap_usetls(cmd_parms * cmd,void * mconfig,const int usetls)398 set_cfg_ldap_usetls (cmd_parms * cmd, void *mconfig, const int usetls)
399 {
400 cfg_ldap_cfg *cfg;
401 cfg = (cfg_ldap_cfg *) ap_get_module_config (cmd->server->module_config,
402 &cfg_ldap_module);
403
404 cfg->usetls = usetls;
405 return NULL;
406 }
407
408 static const char *
set_cfg_ldap_cert_auth(cmd_parms * cmd,void * dummy,const char * file)409 set_cfg_ldap_cert_auth (cmd_parms * cmd, void *dummy, const char *file)
410 {
411 cfg_ldap_cfg *cfg;
412 cfg = (cfg_ldap_cfg *) ap_get_module_config (cmd->server->module_config,
413 &cfg_ldap_module);
414
415 const char *err = ap_check_cmd_context (cmd, GLOBAL_ONLY);
416 if (err != NULL)
417 {
418 return err;
419 }
420
421 ap_log_error (APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, cmd->server,
422 "LDAP: SSL trusted certificate authority file - %s", file);
423 cfg->cert_auth_file = ap_server_root_relative (cmd->pool, file);
424
425 return NULL;
426 }
427
428 static const char *
set_cfg_ldap_username(cmd_parms * cmd,void * mconfig,const char * username)429 set_cfg_ldap_username (cmd_parms * cmd, void *mconfig, const char *username)
430 {
431 cfg_ldap_cfg *cfg;
432 cfg =
433 (cfg_ldap_cfg *) ap_get_module_config (cmd->server->module_config,
434 &cfg_ldap_module);
435 cfg->username = username;
436
437 return NULL;
438 }
439
440 static const char *
set_cfg_ldap_password(cmd_parms * cmd,void * mconfig,const char * password)441 set_cfg_ldap_password (cmd_parms * cmd, void *mconfig, const char *password)
442 {
443 cfg_ldap_cfg *cfg;
444 cfg =
445 (cfg_ldap_cfg *) ap_get_module_config (cmd->server->module_config,
446 &cfg_ldap_module);
447 cfg->password = password;
448
449 return NULL;
450 }
451
452 static const char *
set_cfg_ldap_basedn(cmd_parms * cmd,void * mconfig,const char * basedn)453 set_cfg_ldap_basedn (cmd_parms * cmd, void *mconfig, const char *basedn)
454 {
455 cfg_ldap_cfg *cfg;
456 cfg =
457 (cfg_ldap_cfg *) ap_get_module_config (cmd->server->module_config,
458 &cfg_ldap_module);
459 cfg->basedn = basedn;
460
461 return NULL;
462 }
463
464 static const char *
set_cfg_ldap_filter(cmd_parms * cmd,void * mconfig,const char * filter)465 set_cfg_ldap_filter (cmd_parms * cmd, void *mconfig, const char *filter)
466 {
467 cfg_ldap_cfg *cfg;
468 cfg = (cfg_ldap_cfg *) ap_get_module_config (cmd->server->module_config,
469 &cfg_ldap_module);
470 cfg->filter = filter;
471
472 return NULL;
473 }
474
475 static const char *
set_cfg_ldap_cachettl(cmd_parms * cmd,void * mconfig,const char * cachettl)476 set_cfg_ldap_cachettl (cmd_parms * cmd, void *mconfig, const char *cachettl)
477 {
478 cfg_ldap_cfg *cfg;
479 cfg =
480 (cfg_ldap_cfg *) ap_get_module_config (cmd->server->module_config,
481 &cfg_ldap_module);
482 cfg->cachettl = 1000 * 1000 * atol (cachettl);
483
484 return NULL;
485 }
486
487 static const command_rec cfg_ldap_cmds[] = {
488 AP_INIT_FLAG ("EnableCfgLdap", set_cfg_ldap_enabled, NULL, RSRC_CONF,
489 "enable mod_cfg_ldap"),
490 AP_INIT_TAKE1 ("CfgLdapServer", set_cfg_ldap_hostname, NULL, RSRC_CONF,
491 "the LDAP server to connect to"),
492 AP_INIT_FLAG ("CfgLdapUseTLS", set_cfg_ldap_usetls, NULL, RSRC_CONF,
493 "whether to use an encrypted connection to the LDAP server"),
494 AP_INIT_TAKE1 ("CfgLdapTrustedCA", set_cfg_ldap_cert_auth, NULL, RSRC_CONF,
495 "Sets the file containing the trusted Certificate Authority certificate. "
496 "Used to validate the LDAP server certificate for SSL connections."),
497 AP_INIT_TAKE1 ("CfgLdapBindDN", set_cfg_ldap_username, NULL, RSRC_CONF,
498 "the DN to bind with"),
499 AP_INIT_TAKE1 ("CfgLdapCredentials", set_cfg_ldap_password, NULL, RSRC_CONF,
500 "the credentials to bind with"),
501 AP_INIT_TAKE1 ("CfgLdapBaseDN", set_cfg_ldap_basedn, NULL, RSRC_CONF,
502 "the base DN to use in searches"),
503 AP_INIT_TAKE1 ("CfgLdapFilter", set_cfg_ldap_filter, NULL, RSRC_CONF,
504 "an additional filter to use in searches"),
505 AP_INIT_TAKE1 ("CfgLdapCacheTTL", set_cfg_ldap_cachettl, NULL, RSRC_CONF,
506 "number of seconds a config is cached"),
507 {NULL}
508 };
509
510 static void
cfg_ldap_register_hooks(apr_pool_t * p)511 cfg_ldap_register_hooks (apr_pool_t * p)
512 {
513 ap_hook_child_init (cfg_ldap_child_init, NULL, NULL, APR_HOOK_MIDDLE);
514 ap_hook_translate_name (cfg_ldap_translate_name, NULL, NULL,
515 APR_HOOK_MIDDLE);
516 }
517
518 module AP_MODULE_DECLARE_DATA cfg_ldap_module = {
519 STANDARD20_MODULE_STUFF,
520 NULL,
521 NULL,
522 cfg_ldap_create_server_config,
523 NULL,
524 cfg_ldap_cmds,
525 cfg_ldap_register_hooks,
526 };
527