1 /* ============================================================
2 * Copyright (c) 2003-2004, Ondrej Sury
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
20 * mod_vhost_ldap.c --- read virtual host config from LDAP directory
28 #include "http_config.h"
29 #include "http_core.h"
31 #include "http_request.h"
33 #include "apr_strings.h"
34 #include "apr_reslist.h"
35 #include "util_ldap.h"
37 #if !defined(APU_HAS_LDAP) && !defined(APR_HAS_LDAP)
38 #error mod_vhost_ldap requires APR-util to have LDAP support built in
41 #if !defined(WIN32) && !defined(OS2) && !defined(BEOS) && !defined(NETWARE)
42 #define HAVE_UNIX_SUEXEC
45 #ifdef HAVE_UNIX_SUEXEC
46 #include "unixd.h" /* Contains the suexec_identity hook used on Unix */
52 module AP_MODULE_DECLARE_DATA vhost_ldap_module;
55 MVL_UNSET, MVL_DISABLED, MVL_ENABLED
56 } mod_vhost_ldap_status_e;
58 typedef struct mod_vhost_ldap_config_t {
59 mod_vhost_ldap_status_e enabled; /* Is vhost_ldap enabled? */
61 /* These parameters are all derived from the VhostLDAPURL directive */
62 char *url; /* String representation of LDAP URL */
64 char *host; /* Name of the LDAP server (or space separated list) */
65 int port; /* Port of the LDAP server */
66 char *basedn; /* Base DN to do all searches from */
67 int scope; /* Scope of the search */
68 char *filter; /* Filter to further limit the search */
69 deref_options deref; /* how to handle alias dereferening */
71 char *binddn; /* DN to bind to server (can be NULL) */
72 char *bindpw; /* Password to bind to server (can be NULL) */
74 int have_deref; /* Set if we have found an Deref option */
75 int have_ldap_url; /* Set if we have found an LDAP url */
77 int secure; /* True if SSL connections are requested */
79 char *fallback; /* Fallback virtual host */
81 } mod_vhost_ldap_config_t;
83 typedef struct mod_vhost_ldap_request_t {
84 char *dn; /* The saved dn from a successful search */
85 char *name; /* ServerName */
86 char *admin; /* ServerAdmin */
87 char *docroot; /* DocumentRoot */
88 char *cgiroot; /* ScriptAlias */
89 char *uid; /* Suexec Uid */
90 char *gid; /* Suexec Gid */
91 } mod_vhost_ldap_request_t;
94 { "apacheServerName", "apacheDocumentRoot", "apacheScriptAlias", "apacheSuexecUid", "apacheSuexecGid", "apacheServerAdmin", 0 };
97 static APR_OPTIONAL_FN_TYPE(uldap_connection_close) *util_ldap_connection_close;
98 static APR_OPTIONAL_FN_TYPE(uldap_connection_find) *util_ldap_connection_find;
99 static APR_OPTIONAL_FN_TYPE(uldap_cache_comparedn) *util_ldap_cache_comparedn;
100 static APR_OPTIONAL_FN_TYPE(uldap_cache_compare) *util_ldap_cache_compare;
101 static APR_OPTIONAL_FN_TYPE(uldap_cache_checkuserid) *util_ldap_cache_checkuserid;
102 static APR_OPTIONAL_FN_TYPE(uldap_cache_getuserdn) *util_ldap_cache_getuserdn;
103 static APR_OPTIONAL_FN_TYPE(uldap_ssl_supported) *util_ldap_ssl_supported;
105 static void ImportULDAPOptFn(void)
107 util_ldap_connection_close = APR_RETRIEVE_OPTIONAL_FN(uldap_connection_close);
108 util_ldap_connection_find = APR_RETRIEVE_OPTIONAL_FN(uldap_connection_find);
109 util_ldap_cache_comparedn = APR_RETRIEVE_OPTIONAL_FN(uldap_cache_comparedn);
110 util_ldap_cache_compare = APR_RETRIEVE_OPTIONAL_FN(uldap_cache_compare);
111 util_ldap_cache_checkuserid = APR_RETRIEVE_OPTIONAL_FN(uldap_cache_checkuserid);
112 util_ldap_cache_getuserdn = APR_RETRIEVE_OPTIONAL_FN(uldap_cache_getuserdn);
113 util_ldap_ssl_supported = APR_RETRIEVE_OPTIONAL_FN(uldap_ssl_supported);
117 static int mod_vhost_ldap_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
119 /* make sure that mod_ldap (util_ldap) is loaded */
120 if (ap_find_linked_module("util_ldap.c") == NULL) {
121 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, s,
122 "Module mod_ldap missing. Mod_ldap (aka. util_ldap) "
123 "must be loaded in order for mod_vhost_ldap to function properly");
124 return HTTP_INTERNAL_SERVER_ERROR;
128 ap_add_version_component(p, MOD_VHOST_LDAP_VERSION);
134 mod_vhost_ldap_create_server_config (apr_pool_t *p, server_rec *s)
136 mod_vhost_ldap_config_t *conf =
137 (mod_vhost_ldap_config_t *)apr_pcalloc(p, sizeof (mod_vhost_ldap_config_t));
139 conf->enabled = MVL_UNSET;
140 conf->have_ldap_url = 0;
141 conf->have_deref = 0;
144 conf->deref = always;
145 conf->fallback = NULL;
151 mod_vhost_ldap_merge_server_config(apr_pool_t *p, void *parentv, void *childv)
153 mod_vhost_ldap_config_t *parent = (mod_vhost_ldap_config_t *) parentv;
154 mod_vhost_ldap_config_t *child = (mod_vhost_ldap_config_t *) childv;
155 mod_vhost_ldap_config_t *conf =
156 (mod_vhost_ldap_config_t *)apr_pcalloc(p, sizeof(mod_vhost_ldap_config_t));
158 if (child->enabled == MVL_UNSET) {
159 conf->enabled = parent->enabled;
161 conf->enabled = child->enabled;
164 if (child->have_ldap_url) {
165 conf->have_ldap_url = child->have_ldap_url;
166 conf->url = child->url;
167 conf->host = child->host;
168 conf->port = child->port;
169 conf->basedn = child->basedn;
170 conf->scope = child->scope;
171 conf->filter = child->filter;
172 conf->secure = child->secure;
174 conf->have_ldap_url = parent->have_ldap_url;
175 conf->url = parent->url;
176 conf->host = parent->host;
177 conf->port = parent->port;
178 conf->basedn = parent->basedn;
179 conf->scope = parent->scope;
180 conf->filter = parent->filter;
181 conf->secure = parent->secure;
183 if (child->have_deref) {
184 conf->have_deref = child->have_deref;
185 conf->deref = child->deref;
187 conf->have_deref = parent->have_deref;
188 conf->deref = parent->deref;
191 conf->binddn = (child->binddn ? child->binddn : parent->binddn);
192 conf->bindpw = (child->bindpw ? child->bindpw : parent->bindpw);
194 conf->fallback = (child->fallback ? child->fallback : parent->fallback);
200 * Use the ldap url parsing routines to break up the ldap url into
203 static const char *mod_vhost_ldap_parse_url(cmd_parms *cmd,
208 apr_ldap_url_desc_t *urld;
209 apr_ldap_err_t *result_err;
211 mod_vhost_ldap_config_t *conf =
212 (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
215 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
216 cmd->server, "[mod_vhost_ldap.c] url parse: `%s'",
219 #ifdef APR_HAS_LDAP /* for apache >= 2.2 */
220 result = apr_ldap_url_parse(cmd->pool, url, &(urld), &(result_err));
221 if (result != LDAP_SUCCESS) {
222 return result_err->reason;
225 result = apr_ldap_url_parse(url, &(urld));
226 if (result != LDAP_SUCCESS) {
228 case LDAP_URL_ERR_NOTLDAP:
229 return "LDAP URL does not begin with ldap://";
230 case LDAP_URL_ERR_NODN:
231 return "LDAP URL does not have a DN";
232 case LDAP_URL_ERR_BADSCOPE:
233 return "LDAP URL has an invalid scope";
234 case LDAP_URL_ERR_MEM:
235 return "Out of memory parsing LDAP URL";
237 return "Could not parse LDAP URL";
241 conf->url = apr_pstrdup(cmd->pool, url);
243 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
244 cmd->server, "[mod_vhost_ldap.c] url parse: Host: %s", urld->lud_host);
245 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
246 cmd->server, "[mod_vhost_ldap.c] url parse: Port: %d", urld->lud_port);
247 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
248 cmd->server, "[mod_vhost_ldap.c] url parse: DN: %s", urld->lud_dn);
249 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
250 cmd->server, "[mod_vhost_ldap.c] url parse: attrib: %s", urld->lud_attrs? urld->lud_attrs[0] : "(null)");
251 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
252 cmd->server, "[mod_vhost_ldap.c] url parse: scope: %s",
253 (urld->lud_scope == LDAP_SCOPE_SUBTREE? "subtree" :
254 urld->lud_scope == LDAP_SCOPE_BASE? "base" :
255 urld->lud_scope == LDAP_SCOPE_ONELEVEL? "onelevel" : "unknown"));
256 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
257 cmd->server, "[mod_vhost_ldap.c] url parse: filter: %s", urld->lud_filter);
259 /* Set all the values, or at least some sane defaults */
261 char *p = apr_palloc(cmd->pool, strlen(conf->host) + strlen(urld->lud_host) + 2);
262 strcpy(p, urld->lud_host);
264 strcat(p, conf->host);
268 conf->host = urld->lud_host? apr_pstrdup(cmd->pool, urld->lud_host) : "localhost";
270 conf->basedn = urld->lud_dn? apr_pstrdup(cmd->pool, urld->lud_dn) : "";
272 conf->scope = urld->lud_scope == LDAP_SCOPE_ONELEVEL ?
273 LDAP_SCOPE_ONELEVEL : LDAP_SCOPE_SUBTREE;
275 if (urld->lud_filter) {
276 if (urld->lud_filter[0] == '(') {
278 * Get rid of the surrounding parens; later on when generating the
279 * filter, they'll be put back.
281 conf->filter = apr_pstrdup(cmd->pool, urld->lud_filter+1);
282 conf->filter[strlen(conf->filter)-1] = '\0';
285 conf->filter = apr_pstrdup(cmd->pool, urld->lud_filter);
289 conf->filter = "objectClass=apacheConfig";
292 /* "ldaps" indicates secure ldap connections desired
294 if (strncasecmp(url, "ldaps", 5) == 0)
297 conf->port = urld->lud_port? urld->lud_port : LDAPS_PORT;
298 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server,
299 "LDAP: vhost_ldap using SSL connections");
304 conf->port = urld->lud_port? urld->lud_port : LDAP_PORT;
305 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server,
306 "LDAP: vhost_ldap not using SSL connections");
309 conf->have_ldap_url = 1;
310 #ifdef APU_HAS_LDAP /* free only required for older apr */
311 apr_ldap_free_urldesc(urld);
316 static const char *mod_vhost_ldap_set_enabled(cmd_parms *cmd, void *dummy, int enabled)
318 mod_vhost_ldap_config_t *conf =
319 (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
322 conf->enabled = (enabled) ? MVL_ENABLED : MVL_DISABLED;
327 static const char *mod_vhost_ldap_set_binddn(cmd_parms *cmd, void *dummy, const char *binddn)
329 mod_vhost_ldap_config_t *conf =
330 (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
333 conf->binddn = apr_pstrdup(cmd->pool, binddn);
337 static const char *mod_vhost_ldap_set_bindpw(cmd_parms *cmd, void *dummy, const char *bindpw)
339 mod_vhost_ldap_config_t *conf =
340 (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
343 conf->bindpw = apr_pstrdup(cmd->pool, bindpw);
347 static const char *mod_vhost_ldap_set_deref(cmd_parms *cmd, void *dummy, const char *deref)
349 mod_vhost_ldap_config_t *conf =
350 (mod_vhost_ldap_config_t *)ap_get_module_config (cmd->server->module_config,
353 if (strcmp(deref, "never") == 0 || strcasecmp(deref, "off") == 0) {
355 conf->have_deref = 1;
357 else if (strcmp(deref, "searching") == 0) {
358 conf->deref = searching;
359 conf->have_deref = 1;
361 else if (strcmp(deref, "finding") == 0) {
362 conf->deref = finding;
363 conf->have_deref = 1;
365 else if (strcmp(deref, "always") == 0 || strcasecmp(deref, "on") == 0) {
366 conf->deref = always;
367 conf->have_deref = 1;
370 return "Unrecognized value for VhostLDAPAliasDereference directive";
375 static const char *mod_vhost_ldap_set_fallback(cmd_parms *cmd, void *dummy, const char *fallback)
377 mod_vhost_ldap_config_t *conf =
378 (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
381 conf->fallback = apr_pstrdup(cmd->pool, fallback);
385 command_rec mod_vhost_ldap_cmds[] = {
386 AP_INIT_TAKE1("VhostLDAPURL", mod_vhost_ldap_parse_url, NULL, RSRC_CONF,
387 "URL to define LDAP connection. This should be an RFC 2255 compliant\n"
388 "URL of the form ldap://host[:port]/basedn[?attrib[?scope[?filter]]].\n"
390 "<li>Host is the name of the LDAP server. Use a space separated list of hosts \n"
391 "to specify redundant servers.\n"
392 "<li>Port is optional, and specifies the port to connect to.\n"
393 "<li>basedn specifies the base DN to start searches from\n"
396 AP_INIT_TAKE1 ("VhostLDAPBindDN", mod_vhost_ldap_set_binddn, NULL, RSRC_CONF,
397 "DN to use to bind to LDAP server. If not provided, will do an anonymous bind."),
399 AP_INIT_TAKE1("VhostLDAPBindPassword", mod_vhost_ldap_set_bindpw, NULL, RSRC_CONF,
400 "Password to use to bind to LDAP server. If not provided, will do an anonymous bind."),
402 AP_INIT_FLAG("VhostLDAPEnabled", mod_vhost_ldap_set_enabled, NULL, RSRC_CONF,
403 "Set to off to disable vhost_ldap, even if it's been enabled in a higher tree"),
405 AP_INIT_TAKE1("VhostLDAPDereferenceAliases", mod_vhost_ldap_set_deref, NULL, RSRC_CONF,
406 "Determines how aliases are handled during a search. Can be one of the"
407 "values \"never\", \"searching\", \"finding\", or \"always\". "
408 "Defaults to always."),
410 AP_INIT_TAKE1("VhostLDAPFallback", mod_vhost_ldap_set_fallback, NULL, RSRC_CONF,
411 "Set default virtual host which will be used when requested hostname"
412 "is not found in LDAP database. This option can be used to display"
413 "\"virtual host not found\" type of page."),
418 #define FILTER_LENGTH MAX_STRING_LEN
419 static int mod_vhost_ldap_translate_name(request_rec *r)
421 request_rec *top = (r->main)?r->main:r;
422 mod_vhost_ldap_request_t *reqc;
425 const char **vals = NULL;
426 char filtbuf[FILTER_LENGTH];
427 mod_vhost_ldap_config_t *conf =
428 (mod_vhost_ldap_config_t *)ap_get_module_config(r->server->module_config, &vhost_ldap_module);
429 core_server_config * core =
430 (core_server_config *) ap_get_module_config(r->server->module_config, &core_module);
431 util_ldap_connection_t *ldc = NULL;
433 const char *dn = NULL;
435 const char *hostname = NULL;
439 (mod_vhost_ldap_request_t *)apr_pcalloc(r->pool, sizeof(mod_vhost_ldap_request_t));
441 ap_set_module_config(r->request_config, &vhost_ldap_module, reqc);
443 // mod_vhost_ldap is disabled or we don't have LDAP Url
444 if ((conf->enabled != MVL_ENABLED)||(!conf->have_ldap_url)) {
451 ldc = util_ldap_connection_find(r, conf->host, conf->port,
452 conf->binddn, conf->bindpw, conf->deref,
456 ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r,
457 "[mod_vhost_ldap.c] translate: no conf->host - weird...?");
461 hostname = r->hostname;
465 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
466 "[mod_vhost_ldap.c]: translating %s", r->uri);
468 apr_snprintf(filtbuf, FILTER_LENGTH, "(&(%s)(|(apacheServerName=%s)(apacheServerAlias=%s)))", conf->filter, hostname, hostname);
470 result = util_ldap_cache_getuserdn(r, ldc, conf->url, conf->basedn, conf->scope,
471 attributes, filtbuf, &dn, &vals);
473 util_ldap_connection_close(ldc);
475 /* sanity check - if server is down, retry it up to 5 times */
476 if (result == LDAP_SERVER_DOWN) {
477 if (failures++ <= 5) {
482 if ((result == LDAP_NO_SUCH_OBJECT)) {
483 if (conf->fallback && (is_fallback++ <= 0)) {
484 ap_log_rerror(APLOG_MARK, APLOG_NOTICE|APLOG_NOERRNO, 0, r,
485 "[mod_vhost_ldap.c] translate: "
486 "virtual host %s not found, trying fallback %s",
487 hostname, conf->fallback);
488 hostname = conf->fallback;
492 ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r,
493 "[mod_vhost_ldap.c] translate: "
494 "virtual host %s not found",
500 /* handle bind failure */
501 if (result != LDAP_SUCCESS) {
502 ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r,
503 "[mod_vhost_ldap.c] translate: "
504 "translate failed; virtual host %s; URI %s [%s]",
505 hostname, r->uri, ldap_err2string(result));
509 /* mark the user and DN */
510 reqc->dn = apr_pstrdup(r->pool, dn);
515 while (attributes[i]) {
517 if (strcasecmp (attributes[i], "apacheServerName") == 0) {
518 reqc->name = apr_pstrdup (r->pool, vals[i]);
520 else if (strcasecmp (attributes[i], "apacheServerAdmin") == 0) {
521 reqc->admin = apr_pstrdup (r->pool, vals[i]);
523 else if (strcasecmp (attributes[i], "apacheDocumentRoot") == 0) {
524 reqc->docroot = apr_pstrdup (r->pool, vals[i]);
526 else if (strcasecmp (attributes[i], "apacheScriptAlias") == 0) {
527 reqc->cgiroot = apr_pstrdup (r->pool, vals[i]);
529 else if (strcasecmp (attributes[i], "apacheSuexecUid") == 0) {
530 reqc->uid = apr_pstrdup(r->pool, vals[i]);
532 else if (strcasecmp (attributes[i], "apacheSuexecGid") == 0) {
533 reqc->gid = apr_pstrdup(r->pool, vals[i]);
539 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
540 "[mod_vhost_ldap.c]: loaded from ldap: "
541 "apacheServerName: %s, "
542 "apacheServerAdmin: %s, "
543 "apacheDocumentRoot: %s, "
544 "apacheScriptAlias: %s, "
545 "apacheSuexecUid: %s, "
546 "apacheSuexecGid: %s",
547 reqc->name, reqc->admin, reqc->docroot, reqc->cgiroot, reqc->uid, reqc->gid);
549 if ((reqc->name == NULL)||(reqc->docroot == NULL)) {
550 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r,
551 "[mod_vhost_ldap.c] translate: "
552 "translate failed; ServerName or DocumentRoot not defined");
559 cgi = strstr(r->uri, "cgi-bin/");
560 if (cgi && (cgi != r->uri + strspn(r->uri, "/"))) {
565 r->filename = apr_pstrcat (r->pool, reqc->cgiroot, cgi + strlen("cgi-bin"), NULL);
566 r->handler = "cgi-script";
567 apr_table_setn(r->notes, "alias-forced-type", r->handler);
568 } else if (r->uri[0] == '/') {
569 r->filename = apr_pstrcat (r->pool, reqc->docroot, r->uri, NULL);
574 top->server->server_hostname = apr_pstrdup (top->pool, reqc->name);
577 top->server->server_admin = apr_pstrdup (top->pool, reqc->admin);
580 // set environment variables
581 e = top->subprocess_env;
582 apr_table_addn (e, "SERVER_ROOT", reqc->docroot);
584 core->ap_document_root = apr_pstrdup(top->pool, reqc->docroot);
586 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
587 "[mod_vhost_ldap.c]: translated to %s", r->filename);
592 #ifdef HAVE_UNIX_SUEXEC
593 static ap_unix_identity_t *mod_vhost_ldap_get_suexec_id_doer(const request_rec * r)
595 ap_unix_identity_t *ugid = NULL;
596 mod_vhost_ldap_config_t *conf =
597 (mod_vhost_ldap_config_t *)ap_get_module_config(r->server->module_config,
599 mod_vhost_ldap_request_t *req =
600 (mod_vhost_ldap_request_t *)ap_get_module_config(r->request_config,
606 // mod_vhost_ldap is disabled or we don't have LDAP Url
607 if ((conf->enabled != MVL_ENABLED)||(!conf->have_ldap_url)) {
611 if ((req == NULL)||(req->uid == NULL)||(req->gid == NULL)) {
615 if ((ugid = apr_palloc(r->pool, sizeof(ap_unix_identity_t))) == NULL) {
619 uid = (uid_t)atoll(req->uid);
620 gid = (gid_t)atoll(req->gid);
622 if ((uid < MIN_UID)||(gid < MIN_GID)) {
635 mod_vhost_ldap_register_hooks (apr_pool_t * p)
637 ap_hook_post_config(mod_vhost_ldap_post_config, NULL, NULL, APR_HOOK_MIDDLE);
638 ap_hook_translate_name(mod_vhost_ldap_translate_name, NULL, NULL, APR_HOOK_MIDDLE);
639 #ifdef HAVE_UNIX_SUEXEC
640 ap_hook_get_suexec_identity(mod_vhost_ldap_get_suexec_id_doer, NULL, NULL, APR_HOOK_MIDDLE);
643 ap_hook_optional_fn_retrieve(ImportULDAPOptFn,NULL,NULL,APR_HOOK_MIDDLE);
647 module AP_MODULE_DECLARE_DATA vhost_ldap_module = {
648 STANDARD20_MODULE_STUFF,
651 mod_vhost_ldap_create_server_config,
652 mod_vhost_ldap_merge_server_config,
654 mod_vhost_ldap_register_hooks,