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