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"
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 };
96 static int mod_vhost_ldap_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
98 /* make sure that mod_ldap (util_ldap) is loaded */
99 if (ap_find_linked_module("util_ldap.c") == NULL) {
100 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, s,
101 "Module mod_ldap missing. Mod_ldap (aka. util_ldap) "
102 "must be loaded in order for mod_vhost_ldap to function properly");
103 return HTTP_INTERNAL_SERVER_ERROR;
107 ap_add_version_component(p, MOD_VHOST_LDAP_VERSION);
113 mod_vhost_ldap_create_server_config (apr_pool_t *p, server_rec *s)
115 mod_vhost_ldap_config_t *conf =
116 (mod_vhost_ldap_config_t *)apr_pcalloc(p, sizeof (mod_vhost_ldap_config_t));
118 conf->enabled = MVL_UNSET;
119 conf->have_ldap_url = 0;
120 conf->have_deref = 0;
123 conf->deref = always;
124 conf->fallback = NULL;
130 mod_vhost_ldap_merge_server_config(apr_pool_t *p, void *parentv, void *childv)
132 mod_vhost_ldap_config_t *parent = (mod_vhost_ldap_config_t *) parentv;
133 mod_vhost_ldap_config_t *child = (mod_vhost_ldap_config_t *) childv;
134 mod_vhost_ldap_config_t *conf =
135 (mod_vhost_ldap_config_t *)apr_pcalloc(p, sizeof(mod_vhost_ldap_config_t));
137 if (child->enabled == MVL_UNSET) {
138 conf->enabled = parent->enabled;
140 conf->enabled = child->enabled;
143 if (child->have_ldap_url) {
144 conf->have_ldap_url = child->have_ldap_url;
145 conf->url = child->url;
146 conf->host = child->host;
147 conf->port = child->port;
148 conf->basedn = child->basedn;
149 conf->scope = child->scope;
150 conf->filter = child->filter;
151 conf->secure = child->secure;
153 conf->have_ldap_url = parent->have_ldap_url;
154 conf->url = parent->url;
155 conf->host = parent->host;
156 conf->port = parent->port;
157 conf->basedn = parent->basedn;
158 conf->scope = parent->scope;
159 conf->filter = parent->filter;
160 conf->secure = parent->secure;
162 if (child->have_deref) {
163 conf->have_deref = child->have_deref;
164 conf->deref = child->deref;
166 conf->have_deref = parent->have_deref;
167 conf->deref = parent->deref;
170 conf->binddn = (child->binddn ? child->binddn : parent->binddn);
171 conf->bindpw = (child->bindpw ? child->bindpw : parent->bindpw);
173 conf->fallback = (child->fallback ? child->fallback : parent->fallback);
179 * Use the ldap url parsing routines to break up the ldap url into
182 static const char *mod_vhost_ldap_parse_url(cmd_parms *cmd,
187 apr_ldap_url_desc_t *urld;
189 mod_vhost_ldap_config_t *conf =
190 (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
193 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
194 cmd->server, "[mod_vhost_ldap.c] url parse: `%s'",
197 result = apr_ldap_url_parse(url, &(urld));
198 if (result != LDAP_SUCCESS) {
200 case LDAP_URL_ERR_NOTLDAP:
201 return "LDAP URL does not begin with ldap://";
202 case LDAP_URL_ERR_NODN:
203 return "LDAP URL does not have a DN";
204 case LDAP_URL_ERR_BADSCOPE:
205 return "LDAP URL has an invalid scope";
206 case LDAP_URL_ERR_MEM:
207 return "Out of memory parsing LDAP URL";
209 return "Could not parse LDAP URL";
212 conf->url = apr_pstrdup(cmd->pool, url);
214 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
215 cmd->server, "[mod_vhost_ldap.c] url parse: Host: %s", urld->lud_host);
216 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
217 cmd->server, "[mod_vhost_ldap.c] url parse: Port: %d", urld->lud_port);
218 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
219 cmd->server, "[mod_vhost_ldap.c] url parse: DN: %s", urld->lud_dn);
220 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
221 cmd->server, "[mod_vhost_ldap.c] url parse: attrib: %s", urld->lud_attrs? urld->lud_attrs[0] : "(null)");
222 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
223 cmd->server, "[mod_vhost_ldap.c] url parse: scope: %s",
224 (urld->lud_scope == LDAP_SCOPE_SUBTREE? "subtree" :
225 urld->lud_scope == LDAP_SCOPE_BASE? "base" :
226 urld->lud_scope == LDAP_SCOPE_ONELEVEL? "onelevel" : "unknown"));
227 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
228 cmd->server, "[mod_vhost_ldap.c] url parse: filter: %s", urld->lud_filter);
230 /* Set all the values, or at least some sane defaults */
232 char *p = apr_palloc(cmd->pool, strlen(conf->host) + strlen(urld->lud_host) + 2);
233 strcpy(p, urld->lud_host);
235 strcat(p, conf->host);
239 conf->host = urld->lud_host? apr_pstrdup(cmd->pool, urld->lud_host) : "localhost";
241 conf->basedn = urld->lud_dn? apr_pstrdup(cmd->pool, urld->lud_dn) : "";
243 conf->scope = urld->lud_scope == LDAP_SCOPE_ONELEVEL ?
244 LDAP_SCOPE_ONELEVEL : LDAP_SCOPE_SUBTREE;
246 if (urld->lud_filter) {
247 if (urld->lud_filter[0] == '(') {
249 * Get rid of the surrounding parens; later on when generating the
250 * filter, they'll be put back.
252 conf->filter = apr_pstrdup(cmd->pool, urld->lud_filter+1);
253 conf->filter[strlen(conf->filter)-1] = '\0';
256 conf->filter = apr_pstrdup(cmd->pool, urld->lud_filter);
260 conf->filter = "objectClass=apacheConfig";
263 /* "ldaps" indicates secure ldap connections desired
265 if (strncasecmp(url, "ldaps", 5) == 0)
268 conf->port = urld->lud_port? urld->lud_port : LDAPS_PORT;
269 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server,
270 "LDAP: vhost_ldap using SSL connections");
275 conf->port = urld->lud_port? urld->lud_port : LDAP_PORT;
276 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server,
277 "LDAP: vhost_ldap not using SSL connections");
280 conf->have_ldap_url = 1;
281 apr_ldap_free_urldesc(urld);
285 static const char *mod_vhost_ldap_set_enabled(cmd_parms *cmd, void *dummy, int enabled)
287 mod_vhost_ldap_config_t *conf =
288 (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
291 conf->enabled = (enabled) ? MVL_ENABLED : MVL_DISABLED;
296 static const char *mod_vhost_ldap_set_binddn(cmd_parms *cmd, void *dummy, const char *binddn)
298 mod_vhost_ldap_config_t *conf =
299 (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
302 conf->binddn = apr_pstrdup(cmd->pool, binddn);
306 static const char *mod_vhost_ldap_set_bindpw(cmd_parms *cmd, void *dummy, const char *bindpw)
308 mod_vhost_ldap_config_t *conf =
309 (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
312 conf->bindpw = apr_pstrdup(cmd->pool, bindpw);
316 static const char *mod_vhost_ldap_set_deref(cmd_parms *cmd, void *dummy, const char *deref)
318 mod_vhost_ldap_config_t *conf =
319 (mod_vhost_ldap_config_t *)ap_get_module_config (cmd->server->module_config,
322 if (strcmp(deref, "never") == 0 || strcasecmp(deref, "off") == 0) {
324 conf->have_deref = 1;
326 else if (strcmp(deref, "searching") == 0) {
327 conf->deref = searching;
328 conf->have_deref = 1;
330 else if (strcmp(deref, "finding") == 0) {
331 conf->deref = finding;
332 conf->have_deref = 1;
334 else if (strcmp(deref, "always") == 0 || strcasecmp(deref, "on") == 0) {
335 conf->deref = always;
336 conf->have_deref = 1;
339 return "Unrecognized value for VhostLDAPAliasDereference directive";
344 static const char *mod_vhost_ldap_set_fallback(cmd_parms *cmd, void *dummy, const char *fallback)
346 mod_vhost_ldap_config_t *conf =
347 (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
350 conf->fallback = apr_pstrdup(cmd->pool, fallback);
354 command_rec mod_vhost_ldap_cmds[] = {
355 AP_INIT_TAKE1("VhostLDAPURL", mod_vhost_ldap_parse_url, NULL, RSRC_CONF,
356 "URL to define LDAP connection. This should be an RFC 2255 compliant\n"
357 "URL of the form ldap://host[:port]/basedn[?attrib[?scope[?filter]]].\n"
359 "<li>Host is the name of the LDAP server. Use a space separated list of hosts \n"
360 "to specify redundant servers.\n"
361 "<li>Port is optional, and specifies the port to connect to.\n"
362 "<li>basedn specifies the base DN to start searches from\n"
365 AP_INIT_TAKE1 ("VhostLDAPBindDN", mod_vhost_ldap_set_binddn, NULL, RSRC_CONF,
366 "DN to use to bind to LDAP server. If not provided, will do an anonymous bind."),
368 AP_INIT_TAKE1("VhostLDAPBindPassword", mod_vhost_ldap_set_bindpw, NULL, RSRC_CONF,
369 "Password to use to bind to LDAP server. If not provided, will do an anonymous bind."),
371 AP_INIT_FLAG("VhostLDAPEnabled", mod_vhost_ldap_set_enabled, NULL, RSRC_CONF,
372 "Set to off to disable vhost_ldap, even if it's been enabled in a higher tree"),
374 AP_INIT_TAKE1("VhostLDAPDereferenceAliases", mod_vhost_ldap_set_deref, NULL, RSRC_CONF,
375 "Determines how aliases are handled during a search. Can be one of the"
376 "values \"never\", \"searching\", \"finding\", or \"always\". "
377 "Defaults to always."),
379 AP_INIT_TAKE1("VhostLDAPFallback", mod_vhost_ldap_set_fallback, NULL, RSRC_CONF,
380 "Set default virtual host which will be used when requested hostname"
381 "is not found in LDAP database. This option can be used to display"
382 "\"virtual host not found\" type of page."),
387 #define FILTER_LENGTH MAX_STRING_LEN
388 static int mod_vhost_ldap_translate_name(request_rec *r)
390 request_rec *top = (r->main)?r->main:r;
391 mod_vhost_ldap_request_t *reqc;
394 const char **vals = NULL;
395 char filtbuf[FILTER_LENGTH];
396 mod_vhost_ldap_config_t *conf =
397 (mod_vhost_ldap_config_t *)ap_get_module_config(r->server->module_config, &vhost_ldap_module);
398 core_server_config * core =
399 (core_server_config *) ap_get_module_config(r->server->module_config, &core_module);
400 util_ldap_connection_t *ldc = NULL;
402 const char *dn = NULL;
404 const char *hostname = NULL;
408 (mod_vhost_ldap_request_t *)apr_pcalloc(r->pool, sizeof(mod_vhost_ldap_request_t));
410 ap_set_module_config(r->request_config, &vhost_ldap_module, reqc);
412 // mod_vhost_ldap is disabled or we don't have LDAP Url
413 if ((conf->enabled != MVL_ENABLED)||(!conf->have_ldap_url)) {
420 ldc = util_ldap_connection_find(r, conf->host, conf->port,
421 conf->binddn, conf->bindpw, conf->deref,
425 ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r,
426 "[mod_vhost_ldap.c] translate: no conf->host - weird...?");
430 hostname = r->hostname;
434 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
435 "[mod_vhost_ldap.c]: translating %s", r->uri);
437 apr_snprintf(filtbuf, FILTER_LENGTH, "(&(%s)(|(apacheServerName=%s)(apacheServerAlias=%s)))", conf->filter, hostname, hostname);
439 result = util_ldap_cache_getuserdn(r, ldc, conf->url, conf->basedn, conf->scope,
440 attributes, filtbuf, &dn, &vals);
442 util_ldap_connection_close(ldc);
444 /* sanity check - if server is down, retry it up to 5 times */
445 if (result == LDAP_SERVER_DOWN) {
446 if (failures++ <= 5) {
451 if ((result == LDAP_NO_SUCH_OBJECT)) {
452 if (conf->fallback && (is_fallback++ <= 0)) {
453 ap_log_rerror(APLOG_MARK, APLOG_NOTICE|APLOG_NOERRNO, 0, r,
454 "[mod_vhost_ldap.c] translate: "
455 "virtual host %s not found, trying fallback %s",
456 hostname, conf->fallback);
457 hostname = conf->fallback;
461 ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r,
462 "[mod_vhost_ldap.c] translate: "
463 "virtual host %s not found",
469 /* handle bind failure */
470 if (result != LDAP_SUCCESS) {
471 ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r,
472 "[mod_vhost_ldap.c] translate: "
473 "translate failed; virtual host %s; URI %s [%s]",
474 hostname, r->uri, ldap_err2string(result));
478 /* mark the user and DN */
479 reqc->dn = apr_pstrdup(r->pool, dn);
484 while (attributes[i]) {
486 if (strcasecmp (attributes[i], "apacheServerName") == 0) {
487 reqc->name = apr_pstrdup (r->pool, vals[i]);
489 else if (strcasecmp (attributes[i], "apacheServerAdmin") == 0) {
490 reqc->admin = apr_pstrdup (r->pool, vals[i]);
492 else if (strcasecmp (attributes[i], "apacheDocumentRoot") == 0) {
493 reqc->docroot = apr_pstrdup (r->pool, vals[i]);
495 else if (strcasecmp (attributes[i], "apacheScriptAlias") == 0) {
496 reqc->cgiroot = apr_pstrdup (r->pool, vals[i]);
498 else if (strcasecmp (attributes[i], "apacheSuexecUid") == 0) {
499 reqc->uid = apr_pstrdup(r->pool, vals[i]);
501 else if (strcasecmp (attributes[i], "apacheSuexecGid") == 0) {
502 reqc->gid = apr_pstrdup(r->pool, vals[i]);
508 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
509 "[mod_vhost_ldap.c]: loaded from ldap: "
510 "apacheServerName: %s, "
511 "apacheServerAdmin: %s, "
512 "apacheDocumentRoot: %s, "
513 "apacheScriptAlias: %s, "
514 "apacheSuexecUid: %s, "
515 "apacheSuexecGid: %s",
516 reqc->name, reqc->admin, reqc->docroot, reqc->cgiroot, reqc->uid, reqc->gid);
518 if ((reqc->name == NULL)||(reqc->docroot == NULL)) {
519 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r,
520 "[mod_vhost_ldap.c] translate: "
521 "translate failed; ServerName or DocumentRoot not defined");
528 cgi = strstr(r->uri, "cgi-bin/");
529 if (cgi && (cgi != r->uri + strspn(r->uri, "/"))) {
534 r->filename = apr_pstrcat (r->pool, reqc->cgiroot, cgi + strlen("cgi-bin"), NULL);
535 r->handler = "cgi-script";
536 apr_table_setn(r->notes, "alias-forced-type", r->handler);
537 } else if (r->uri[0] == '/') {
538 r->filename = apr_pstrcat (r->pool, reqc->docroot, r->uri, NULL);
543 top->server->server_hostname = apr_pstrdup (top->pool, reqc->name);
546 top->server->server_admin = apr_pstrdup (top->pool, reqc->admin);
549 // set environment variables
550 e = top->subprocess_env;
551 apr_table_addn (e, "SERVER_ROOT", reqc->docroot);
553 core->ap_document_root = apr_pstrdup(top->pool, reqc->docroot);
555 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
556 "[mod_vhost_ldap.c]: translated to %s", r->filename);
561 #ifdef HAVE_UNIX_SUEXEC
562 static ap_unix_identity_t *mod_vhost_ldap_get_suexec_id_doer(const request_rec * r)
564 ap_unix_identity_t *ugid = NULL;
565 mod_vhost_ldap_config_t *conf =
566 (mod_vhost_ldap_config_t *)ap_get_module_config(r->server->module_config,
568 mod_vhost_ldap_request_t *req =
569 (mod_vhost_ldap_request_t *)ap_get_module_config(r->request_config,
575 // mod_vhost_ldap is disabled or we don't have LDAP Url
576 if ((conf->enabled != MVL_ENABLED)||(!conf->have_ldap_url)) {
580 if ((req == NULL)||(req->uid == NULL)||(req->gid == NULL)) {
584 if ((ugid = apr_palloc(r->pool, sizeof(ap_unix_identity_t))) == NULL) {
588 uid = (uid_t)atoll(req->uid);
589 gid = (gid_t)atoll(req->gid);
591 if ((uid < MIN_UID)||(gid < MIN_GID)) {
604 mod_vhost_ldap_register_hooks (apr_pool_t * p)
606 ap_hook_post_config(mod_vhost_ldap_post_config, NULL, NULL, APR_HOOK_MIDDLE);
607 ap_hook_translate_name(mod_vhost_ldap_translate_name, NULL, NULL, APR_HOOK_MIDDLE);
608 #ifdef HAVE_UNIX_SUEXEC
609 ap_hook_get_suexec_identity(mod_vhost_ldap_get_suexec_id_doer, NULL, NULL, APR_HOOK_MIDDLE);
613 module AP_MODULE_DECLARE_DATA vhost_ldap_module = {
614 STANDARD20_MODULE_STUFF,
617 mod_vhost_ldap_create_server_config,
618 mod_vhost_ldap_merge_server_config,
620 mod_vhost_ldap_register_hooks,