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 */
78 } mod_vhost_ldap_config_t;
80 typedef struct mod_vhost_ldap_request_t {
81 char *dn; /* The saved dn from a successful search */
82 char *name; /* ServerName */
83 char *admin; /* ServerAdmin */
84 char *docroot; /* DocumentRoot */
85 char *cgiroot; /* ScriptAlias */
86 char *uid; /* Suexec Uid */
87 char *gid; /* Suexec Gid */
88 } mod_vhost_ldap_request_t;
91 { "apacheServerName", "apacheDocumentRoot", "apacheScriptAlias", "apacheSuexecUid", "apacheSuexecGid", "apacheServerAdmin", 0 };
93 static int mod_vhost_ldap_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
95 /* make sure that mod_ldap (util_ldap) is loaded */
96 if (ap_find_linked_module("util_ldap.c") == NULL) {
97 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, s,
98 "Module mod_ldap missing. Mod_ldap (aka. util_ldap) "
99 "must be loaded in order for mod_vhost_ldap to function properly");
100 return HTTP_INTERNAL_SERVER_ERROR;
104 ap_add_version_component(p, MOD_VHOST_LDAP_VERSION);
110 mod_vhost_ldap_create_server_config (apr_pool_t *p, server_rec *s)
112 mod_vhost_ldap_config_t *conf =
113 (mod_vhost_ldap_config_t *)apr_pcalloc(p, sizeof (mod_vhost_ldap_config_t));
115 conf->enabled = MVL_UNSET;
116 conf->have_ldap_url = 0;
117 conf->have_deref = 0;
120 conf->deref = always;
126 mod_vhost_ldap_merge_server_config(apr_pool_t *p, void *parentv, void *childv)
128 mod_vhost_ldap_config_t *parent = (mod_vhost_ldap_config_t *) parentv;
129 mod_vhost_ldap_config_t *child = (mod_vhost_ldap_config_t *) childv;
130 mod_vhost_ldap_config_t *conf =
131 (mod_vhost_ldap_config_t *)apr_pcalloc(p, sizeof(mod_vhost_ldap_config_t));
133 if (child->enabled == MVL_UNSET) {
134 conf->enabled = parent->enabled;
136 conf->enabled = child->enabled;
139 if (child->have_ldap_url) {
140 conf->have_ldap_url = child->have_ldap_url;
141 conf->url = child->url;
142 conf->host = child->host;
143 conf->port = child->port;
144 conf->basedn = child->basedn;
145 conf->scope = child->scope;
146 conf->filter = child->filter;
147 conf->secure = child->secure;
149 conf->have_ldap_url = parent->have_ldap_url;
150 conf->url = parent->url;
151 conf->host = parent->host;
152 conf->port = parent->port;
153 conf->basedn = parent->basedn;
154 conf->scope = parent->scope;
155 conf->filter = parent->filter;
156 conf->secure = parent->secure;
158 if (child->have_deref) {
159 conf->have_deref = child->have_deref;
160 conf->deref = child->deref;
162 conf->have_deref = parent->have_deref;
163 conf->deref = parent->deref;
166 conf->binddn = (child->binddn ? child->binddn : parent->binddn);
167 conf->bindpw = (child->bindpw ? child->bindpw : parent->bindpw);
173 * Use the ldap url parsing routines to break up the ldap url into
176 static const char *mod_vhost_ldap_parse_url(cmd_parms *cmd,
181 apr_ldap_url_desc_t *urld;
183 mod_vhost_ldap_config_t *conf =
184 (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
187 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
188 cmd->server, "[mod_vhost_ldap.c] url parse: `%s'",
191 result = apr_ldap_url_parse(url, &(urld));
192 if (result != LDAP_SUCCESS) {
194 case LDAP_URL_ERR_NOTLDAP:
195 return "LDAP URL does not begin with ldap://";
196 case LDAP_URL_ERR_NODN:
197 return "LDAP URL does not have a DN";
198 case LDAP_URL_ERR_BADSCOPE:
199 return "LDAP URL has an invalid scope";
200 case LDAP_URL_ERR_MEM:
201 return "Out of memory parsing LDAP URL";
203 return "Could not parse LDAP URL";
206 conf->url = apr_pstrdup(cmd->pool, url);
208 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
209 cmd->server, "[mod_vhost_ldap.c] url parse: Host: %s", urld->lud_host);
210 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
211 cmd->server, "[mod_vhost_ldap.c] url parse: Port: %d", urld->lud_port);
212 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
213 cmd->server, "[mod_vhost_ldap.c] url parse: DN: %s", urld->lud_dn);
214 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
215 cmd->server, "[mod_vhost_ldap.c] url parse: attrib: %s", urld->lud_attrs? urld->lud_attrs[0] : "(null)");
216 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
217 cmd->server, "[mod_vhost_ldap.c] url parse: scope: %s",
218 (urld->lud_scope == LDAP_SCOPE_SUBTREE? "subtree" :
219 urld->lud_scope == LDAP_SCOPE_BASE? "base" :
220 urld->lud_scope == LDAP_SCOPE_ONELEVEL? "onelevel" : "unknown"));
221 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
222 cmd->server, "[mod_vhost_ldap.c] url parse: filter: %s", urld->lud_filter);
224 /* Set all the values, or at least some sane defaults */
226 char *p = apr_palloc(cmd->pool, strlen(conf->host) + strlen(urld->lud_host) + 2);
227 strcpy(p, urld->lud_host);
229 strcat(p, conf->host);
233 conf->host = urld->lud_host? apr_pstrdup(cmd->pool, urld->lud_host) : "localhost";
235 conf->basedn = urld->lud_dn? apr_pstrdup(cmd->pool, urld->lud_dn) : "";
237 conf->scope = urld->lud_scope == LDAP_SCOPE_ONELEVEL ?
238 LDAP_SCOPE_ONELEVEL : LDAP_SCOPE_SUBTREE;
240 if (urld->lud_filter) {
241 if (urld->lud_filter[0] == '(') {
243 * Get rid of the surrounding parens; later on when generating the
244 * filter, they'll be put back.
246 conf->filter = apr_pstrdup(cmd->pool, urld->lud_filter+1);
247 conf->filter[strlen(conf->filter)-1] = '\0';
250 conf->filter = apr_pstrdup(cmd->pool, urld->lud_filter);
254 conf->filter = "objectClass=apacheConfig";
257 /* "ldaps" indicates secure ldap connections desired
259 if (strncasecmp(url, "ldaps", 5) == 0)
262 conf->port = urld->lud_port? urld->lud_port : LDAPS_PORT;
263 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server,
264 "LDAP: vhost_ldap using SSL connections");
269 conf->port = urld->lud_port? urld->lud_port : LDAP_PORT;
270 ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server,
271 "LDAP: vhost_ldap not using SSL connections");
274 conf->have_ldap_url = 1;
275 apr_ldap_free_urldesc(urld);
279 static const char *mod_vhost_ldap_set_enabled(cmd_parms *cmd, void *dummy, int enabled)
281 mod_vhost_ldap_config_t *conf =
282 (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
285 conf->enabled = (enabled) ? MVL_ENABLED : MVL_DISABLED;
290 static const char *mod_vhost_ldap_set_binddn(cmd_parms *cmd, void *dummy, const char *binddn)
292 mod_vhost_ldap_config_t *conf =
293 (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
296 conf->binddn = apr_pstrdup(cmd->pool, binddn);
300 static const char *mod_vhost_ldap_set_bindpw(cmd_parms *cmd, void *dummy, const char *bindpw)
302 mod_vhost_ldap_config_t *conf =
303 (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
306 conf->bindpw = apr_pstrdup(cmd->pool, bindpw);
310 static const char *mod_vhost_ldap_set_deref(cmd_parms *cmd, void *dummy, const char *deref)
312 mod_vhost_ldap_config_t *conf =
313 (mod_vhost_ldap_config_t *)ap_get_module_config (cmd->server->module_config,
316 if (strcmp(deref, "never") == 0 || strcasecmp(deref, "off") == 0) {
318 conf->have_deref = 1;
320 else if (strcmp(deref, "searching") == 0) {
321 conf->deref = searching;
322 conf->have_deref = 1;
324 else if (strcmp(deref, "finding") == 0) {
325 conf->deref = finding;
326 conf->have_deref = 1;
328 else if (strcmp(deref, "always") == 0 || strcasecmp(deref, "on") == 0) {
329 conf->deref = always;
330 conf->have_deref = 1;
333 return "Unrecognized value for VhostLDAPAliasDereference directive";
338 command_rec mod_vhost_ldap_cmds[] = {
339 AP_INIT_TAKE1("VhostLDAPURL", mod_vhost_ldap_parse_url, NULL, RSRC_CONF,
340 "URL to define LDAP connection. This should be an RFC 2255 compliant\n"
341 "URL of the form ldap://host[:port]/basedn[?attrib[?scope[?filter]]].\n"
343 "<li>Host is the name of the LDAP server. Use a space separated list of hosts \n"
344 "to specify redundant servers.\n"
345 "<li>Port is optional, and specifies the port to connect to.\n"
346 "<li>basedn specifies the base DN to start searches from\n"
349 AP_INIT_TAKE1 ("VhostLDAPBindDN", mod_vhost_ldap_set_binddn, NULL, RSRC_CONF,
350 "DN to use to bind to LDAP server. If not provided, will do an anonymous bind."),
352 AP_INIT_TAKE1("VhostLDAPBindPassword", mod_vhost_ldap_set_bindpw, NULL, RSRC_CONF,
353 "Password to use to bind to LDAP server. If not provided, will do an anonymous bind."),
355 AP_INIT_FLAG("VhostLDAPEnabled", mod_vhost_ldap_set_enabled, NULL, RSRC_CONF,
356 "Set to off to disable vhost_ldap, even if it's been enabled in a higher tree"),
358 AP_INIT_TAKE1("VhostLDAPDereferenceAliases", mod_vhost_ldap_set_deref, NULL, RSRC_CONF,
359 "Determines how aliases are handled during a search. Can be one of the"
360 "values \"never\", \"searching\", \"finding\", or \"always\". "
361 "Defaults to always."),
366 #define FILTER_LENGTH MAX_STRING_LEN
368 mod_vhost_ldap_translate_name (request_rec * r)
372 const char **vals = NULL;
373 char filtbuf[FILTER_LENGTH];
374 mod_vhost_ldap_config_t *conf =
375 (mod_vhost_ldap_config_t *)ap_get_module_config(r->server->module_config, &vhost_ldap_module);
376 core_server_config * core =
377 (core_server_config *) ap_get_module_config(r->server->module_config, &core_module);
378 util_ldap_connection_t *ldc = NULL;
380 const char *dn = NULL;
383 mod_vhost_ldap_request_t *req =
384 (mod_vhost_ldap_request_t *)apr_pcalloc(r->pool, sizeof(mod_vhost_ldap_request_t));
385 ap_set_module_config(r->request_config, &vhost_ldap_module, req);
387 // mod_vhost_ldap is disabled or we don't have LDAP Url
388 if ((conf->enabled != MVL_ENABLED)||(!conf->have_ldap_url)) {
395 ldc = util_ldap_connection_find(r, conf->host, conf->port,
396 conf->binddn, conf->bindpw, conf->deref,
400 ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r,
401 "[mod_vhost_ldap.c] translate: no conf->host - weird...?");
405 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
406 "[mod_vhost_ldap.c]: translating %s", r->uri);
408 apr_snprintf(filtbuf, FILTER_LENGTH, "(&(%s)(|(apacheServerName=%s)(apacheServerAlias=%s)))", conf->filter, r->hostname, r->hostname);
410 result = util_ldap_cache_getuserdn(r, ldc, conf->url, conf->basedn, conf->scope,
411 attributes, filtbuf, &dn, &vals);
413 util_ldap_connection_close(ldc);
415 /* sanity check - if server is down, retry it up to 5 times */
416 if (result == LDAP_SERVER_DOWN) {
417 if (failures++ <= 5) {
422 /* handle bind failure */
423 if (result != LDAP_SUCCESS) {
424 ap_log_rerror(APLOG_MARK, APLOG_WARN|APLOG_NOERRNO, 0, r,
425 "[mod_vhost_ldap.c] translate: "
426 "translate failed; VHost %s; URI %s[%s]",
427 r->hostname, r->uri, ldap_err2string(result));
431 /* mark the user and DN */
432 req->dn = apr_pstrdup(r->pool, dn);
437 while (attributes[i]) {
439 if (strcasecmp (attributes[i], "apacheServerName") == 0) {
440 req->name = apr_pstrdup (r->pool, vals[i]);
442 else if (strcasecmp (attributes[i], "apacheServerAdmin") == 0) {
443 req->admin = apr_pstrdup (r->pool, vals[i]);
445 else if (strcasecmp (attributes[i], "apacheDocumentRoot") == 0) {
446 req->docroot = apr_pstrdup (r->pool, vals[i]);
448 else if (strcasecmp (attributes[i], "apacheScriptAlias") == 0) {
449 req->cgiroot = apr_pstrdup (r->pool, vals[i]);
451 else if (strcasecmp (attributes[i], "apacheSuexecUid") == 0) {
452 req->uid = apr_pstrdup(r->pool, vals[i]);
454 else if (strcasecmp (attributes[i], "apacheSuexecGid") == 0) {
455 req->gid = apr_pstrdup(r->pool, vals[i]);
461 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
462 "[mod_vhost_ldap.c]: loaded from ldap: "
463 "apacheServerName: %s, "
464 "apacheServerAdmin: %s, "
465 "apacheDocumentRoot: %s, "
466 "apacheScriptAlias: %s, "
467 "apacheSuexecUid: %s, "
468 "apacheSuexecGid: %s"
469 , req->name, req->admin, req->docroot, req->cgiroot, req->uid, req->gid);
471 if ((req->name == NULL)||(req->docroot == NULL)) {
472 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r,
473 "[mod_vhost_ldap.c] translate: "
474 "translate failed; ServerName or DocumentRoot not defined");
481 cgi = strstr(r->uri, "cgi-bin/");
482 if (cgi && (cgi != r->uri + strspn(r->uri, "/"))) {
487 r->filename = apr_pstrcat (r->pool, req->cgiroot, cgi + strlen("cgi-bin"), NULL);
488 r->handler = "cgi-script";
489 apr_table_setn(r->notes, "alias-forced-type", r->handler);
490 } else if (r->uri[0] == '/') {
491 r->filename = apr_pstrcat (r->pool, req->docroot, r->uri, NULL);
496 r->server->server_hostname = apr_pstrdup (r->pool, req->name);
499 r->server->server_admin = apr_pstrdup (r->pool, req->admin);
502 // set environment variables
503 e = r->subprocess_env;
504 apr_table_addn (e, "SERVER_ROOT", req->docroot);
506 core->ap_document_root = apr_pstrdup(r->pool, req->docroot);
508 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
509 "[mod_vhost_ldap.c]: translated to %s", r->filename);
514 #ifdef HAVE_UNIX_SUEXEC
515 static ap_unix_identity_t *mod_vhost_ldap_get_suexec_id_doer(const request_rec * r)
517 ap_unix_identity_t *ugid = NULL;
518 mod_vhost_ldap_config_t *conf =
519 (mod_vhost_ldap_config_t *)ap_get_module_config(r->server->module_config,
521 mod_vhost_ldap_request_t *req =
522 (mod_vhost_ldap_request_t *)ap_get_module_config(r->request_config,
528 // mod_vhost_ldap is disabled or we don't have LDAP Url
529 if ((conf->enabled != MVL_ENABLED)||(!conf->have_ldap_url)) {
533 if ((req == NULL)||(req->uid == NULL)||(req->gid == NULL)) {
537 if ((ugid = apr_palloc(r->pool, sizeof(ap_unix_identity_t))) == NULL) {
541 uid = (uid_t)atoll(req->uid);
542 gid = (gid_t)atoll(req->gid);
544 if ((uid < MIN_UID)||(gid < MIN_GID)) {
557 mod_vhost_ldap_register_hooks (apr_pool_t * p)
559 ap_hook_post_config(mod_vhost_ldap_post_config, NULL, NULL, APR_HOOK_MIDDLE);
560 ap_hook_translate_name(mod_vhost_ldap_translate_name, NULL, NULL, APR_HOOK_MIDDLE);
561 #ifdef HAVE_UNIX_SUEXEC
562 ap_hook_get_suexec_identity(mod_vhost_ldap_get_suexec_id_doer, NULL, NULL, APR_HOOK_MIDDLE);
566 module AP_MODULE_DECLARE_DATA vhost_ldap_module = {
567 STANDARD20_MODULE_STUFF,
570 mod_vhost_ldap_create_server_config,
571 mod_vhost_ldap_merge_server_config,
573 mod_vhost_ldap_register_hooks,