--- /dev/null
+/* ============================================================
+ * Copyright (c) 2003-2004, Ondrej Sury
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * mod_vhost_ldap.c --- read virtual host config from LDAP directory
+ */
+
+#include <unistd.h>
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "apr_ldap.h"
+#include "apr_strings.h"
+#include "apr_reslist.h"
+#include "util_ldap.h"
+
+#ifndef APU_HAS_LDAP
+#error mod_vhost_ldap requires APR-util to have LDAP support built in
+#endif
+
+#if !defined(WIN32) && !defined(OS2) && !defined(BEOS) && !defined(NETWARE)
+#define HAVE_UNIX_SUEXEC
+#endif
+
+#ifdef HAVE_UNIX_SUEXEC
+#include "unixd.h" /* Contains the suexec_identity hook used on Unix */
+#endif
+
+#define MIN_UID 1000
+#define MIN_GID 1000
+
+module AP_MODULE_DECLARE_DATA vhost_ldap_module;
+
+typedef struct mod_vhost_ldap_config_t {
+ apr_pool_t *pool; /* Pool that this config is allocated from */
+#if APR_HAS_THREADS
+ apr_thread_mutex_t *lock; /* Lock for this config */
+#endif
+ int enabled; /* Is vhost_ldap enabled? */
+
+ /* These parameters are all derived from the VhostLDAPURL directive */
+ char *url; /* String representation of LDAP URL */
+
+ char *host; /* Name of the LDAP server (or space separated list) */
+ int port; /* Port of the LDAP server */
+ char *basedn; /* Base DN to do all searches from */
+ int scope; /* Scope of the search */
+ char *filter; /* Filter to further limit the search */
+ deref_options deref; /* how to handle alias dereferening */
+
+ char *binddn; /* DN to bind to server (can be NULL) */
+ char *bindpw; /* Password to bind to server (can be NULL) */
+
+ int have_ldap_url; /* Set if we have found an LDAP url */
+
+ int secure; /* True if SSL connections are requested */
+} mod_vhost_ldap_config_t;
+
+typedef struct mod_vhost_ldap_request_t {
+ char *dn; /* The saved dn from a successful search */
+ char *name; /* ServerName */
+ char *admin; /* ServerAdmin */
+ char *docroot; /* DocumentRoot */
+ char *cgiroot; /* ScripAlias */
+ char *uid; /* Suexec Uid */
+ char *gid; /* Suexec Gid */
+} mod_vhost_ldap_request_t;
+
+char *attributes[] =
+ { "apacheServerName", "apacheServerAdmin", "apacheDocumentRoot", "apacheScriptAlias", "apacheSuexecUid", "apacheSuexecGid", 0 };
+
+static int mod_vhost_ldap_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
+{
+ /* make sure that mod_ldap (util_ldap) is loaded */
+ if (ap_find_linked_module("util_ldap.c") == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, s,
+ "Module mod_ldap missing. Mod_ldap (aka. util_ldap) "
+ "must be loaded in order for mod_vhost_ldap to function properly");
+ return HTTP_INTERNAL_SERVER_ERROR;
+
+ }
+
+ ap_add_version_component(p, "mod_vhost_ldap/0.2.1");
+
+ return OK;
+}
+
+static void *
+mod_vhost_ldap_create_server_config (apr_pool_t *p, server_rec *s)
+{
+ mod_vhost_ldap_config_t *cfg =
+ (mod_vhost_ldap_config_t *)apr_pcalloc(p, sizeof (mod_vhost_ldap_config_t));
+
+ cfg->pool = p;
+
+#if APR_HAS_THREADS
+ apr_thread_mutex_create(&cfg->lock, APR_THREAD_MUTEX_DEFAULT, p);
+#endif
+
+ cfg->enabled = 0;
+ cfg->have_ldap_url = 0;
+ cfg->url = "";
+ cfg->host = NULL;
+ cfg->binddn = NULL;
+ cfg->bindpw = NULL;
+ cfg->deref = always;
+ cfg->secure = 0;
+
+ return cfg;
+}
+
+/*
+ * Use the ldap url parsing routines to break up the ldap url into
+ * host and port.
+ */
+static const char *mod_vhost_ldap_parse_url(cmd_parms *cmd,
+ void *dummy,
+ const char *url)
+{
+ int result;
+ apr_ldap_url_desc_t *urld;
+
+ mod_vhost_ldap_config_t *cfg =
+ (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
+ &vhost_ldap_module);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
+ cmd->server, "[mod_vhost_ldap.c] url parse: `%s'",
+ url);
+
+ result = apr_ldap_url_parse(url, &(urld));
+ if (result != LDAP_SUCCESS) {
+ switch (result) {
+ case LDAP_URL_ERR_NOTLDAP:
+ return "LDAP URL does not begin with ldap://";
+ case LDAP_URL_ERR_NODN:
+ return "LDAP URL does not have a DN";
+ case LDAP_URL_ERR_BADSCOPE:
+ return "LDAP URL has an invalid scope";
+ case LDAP_URL_ERR_MEM:
+ return "Out of memory parsing LDAP URL";
+ default:
+ return "Could not parse LDAP URL";
+ }
+ }
+ cfg->url = apr_pstrdup(cmd->pool, url);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
+ cmd->server, "[mod_vhost_ldap.c] url parse: Host: %s", urld->lud_host);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
+ cmd->server, "[mod_vhost_ldap.c] url parse: Port: %d", urld->lud_port);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
+ cmd->server, "[mod_vhost_ldap.c] url parse: DN: %s", urld->lud_dn);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
+ cmd->server, "[mod_vhost_ldap.c] url parse: attrib: %s", urld->lud_attrs? urld->lud_attrs[0] : "(null)");
+ ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
+ cmd->server, "[mod_vhost_ldap.c] url parse: scope: %s",
+ (urld->lud_scope == LDAP_SCOPE_SUBTREE? "subtree" :
+ urld->lud_scope == LDAP_SCOPE_BASE? "base" :
+ urld->lud_scope == LDAP_SCOPE_ONELEVEL? "onelevel" : "unknown"));
+ ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
+ cmd->server, "[mod_vhost_ldap.c] url parse: filter: %s", urld->lud_filter);
+
+ /* Set all the values, or at least some sane defaults */
+ if (cfg->host) {
+ char *p = apr_palloc(cmd->pool, strlen(cfg->host) + strlen(urld->lud_host) + 2);
+ strcpy(p, urld->lud_host);
+ strcat(p, " ");
+ strcat(p, cfg->host);
+ cfg->host = p;
+ }
+ else {
+ cfg->host = urld->lud_host? apr_pstrdup(cmd->pool, urld->lud_host) : "localhost";
+ }
+ cfg->basedn = urld->lud_dn? apr_pstrdup(cmd->pool, urld->lud_dn) : "";
+
+ cfg->scope = urld->lud_scope == LDAP_SCOPE_ONELEVEL ?
+ LDAP_SCOPE_ONELEVEL : LDAP_SCOPE_SUBTREE;
+
+ if (urld->lud_filter) {
+ if (urld->lud_filter[0] == '(') {
+ /*
+ * Get rid of the surrounding parens; later on when generating the
+ * filter, they'll be put back.
+ */
+ cfg->filter = apr_pstrdup(cmd->pool, urld->lud_filter+1);
+ cfg->filter[strlen(cfg->filter)-1] = '\0';
+ }
+ else {
+ cfg->filter = apr_pstrdup(cmd->pool, urld->lud_filter);
+ }
+ }
+ else {
+ cfg->filter = "objectClass=apacheConfig";
+ }
+
+ /* "ldaps" indicates secure ldap connections desired
+ */
+ if (strncasecmp(url, "ldaps", 5) == 0)
+ {
+ cfg->secure = 1;
+ cfg->port = urld->lud_port? urld->lud_port : LDAPS_PORT;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server,
+ "LDAP: vhost_ldap using SSL connections");
+ }
+ else
+ {
+ cfg->secure = 0;
+ cfg->port = urld->lud_port? urld->lud_port : LDAP_PORT;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
+ "LDAP: vhost_ldap not using SSL connections");
+ }
+
+ cfg->have_ldap_url = 1;
+ apr_ldap_free_urldesc(urld);
+ return NULL;
+}
+
+static const char *mod_vhost_ldap_set_enabled(cmd_parms *cmd, void *dummy, int enabled)
+{
+ mod_vhost_ldap_config_t *cfg =
+ (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
+ &vhost_ldap_module);
+
+ cfg->enabled = enabled;
+ return NULL;
+}
+
+static const char *mod_vhost_ldap_set_binddn(cmd_parms *cmd, void *dummy, const char *binddn)
+{
+ mod_vhost_ldap_config_t *cfg =
+ (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
+ &vhost_ldap_module);
+
+ cfg->binddn = apr_pstrdup(cmd->pool, binddn);
+ return NULL;
+}
+
+static const char *mod_vhost_ldap_set_bindpw(cmd_parms *cmd, void *dummy, const char *bindpw)
+{
+ mod_vhost_ldap_config_t *cfg =
+ (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
+ &vhost_ldap_module);
+
+ cfg->bindpw = apr_pstrdup(cmd->pool, bindpw);
+ return NULL;
+}
+
+static const char *mod_vhost_ldap_set_deref(cmd_parms *cmd, void *dummy, const char *deref)
+{
+ mod_vhost_ldap_config_t *cfg =
+ (mod_vhost_ldap_config_t *)ap_get_module_config (cmd->server->module_config,
+ &vhost_ldap_module);
+
+ if (strcmp(deref, "never") == 0 || strcasecmp(deref, "off") == 0) {
+ cfg->deref = never;
+ }
+ else if (strcmp(deref, "searching") == 0) {
+ cfg->deref = searching;
+ }
+ else if (strcmp(deref, "finding") == 0) {
+ cfg->deref = finding;
+ }
+ else if (strcmp(deref, "always") == 0 || strcasecmp(deref, "on") == 0) {
+ cfg->deref = always;
+ }
+ else {
+ return "Unrecognized value for VhostLDAPAliasDereference directive";
+ }
+ return NULL;
+}
+
+command_rec mod_vhost_ldap_cmds[] = {
+ AP_INIT_TAKE1("VhostLDAPURL", mod_vhost_ldap_parse_url, NULL, RSRC_CONF,
+ "URL to define LDAP connection. This should be an RFC 2255 complaint\n"
+ "URL of the form ldap://host[:port]/basedn[?attrib[?scope[?filter]]].\n"
+ "<ul>\n"
+ "<li>Host is the name of the LDAP server. Use a space separated list of hosts \n"
+ "to specify redundant servers.\n"
+ "<li>Port is optional, and specifies the port to connect to.\n"
+ "<li>basedn specifies the base DN to start searches from\n"
+ "</ul>\n"),
+
+ AP_INIT_TAKE1 ("VhostLDAPBindDN", mod_vhost_ldap_set_binddn, NULL, RSRC_CONF,
+ "DN to use to bind to LDAP server. If not provided, will do an anonymous bind."),
+
+ AP_INIT_TAKE1("VhostLDAPBindPassword", mod_vhost_ldap_set_bindpw, NULL, RSRC_CONF,
+ "Password to use to bind to LDAP server. If not provided, will do an anonymous bind."),
+
+ AP_INIT_FLAG("VhostLDAPEnabled", mod_vhost_ldap_set_enabled, NULL, RSRC_CONF,
+ "Set to off to disable vhost_ldap, even if it's been enabled in a higher tree"),
+
+ AP_INIT_TAKE1("VhostLDAPDereferenceAliases", mod_vhost_ldap_set_deref, NULL, RSRC_CONF,
+ "Determines how aliases are handled during a search. Can bo one of the"
+ "values \"never\", \"searching\", \"finding\", or \"always\". "
+ "Defaults to always."),
+
+ {NULL}
+};
+
+#define FILTER_LENGTH MAX_STRING_LEN
+static int
+mod_vhost_ldap_translate_name (request_rec * r)
+{
+ apr_table_t *e;
+ int failures = 0;
+ const char **vals = NULL;
+ char filtbuf[FILTER_LENGTH];
+ mod_vhost_ldap_config_t *cfg =
+ (mod_vhost_ldap_config_t *)ap_get_module_config(r->server->module_config, &vhost_ldap_module);
+
+ util_ldap_connection_t *ldc = NULL;
+ int result = 0;
+ const char *dn = NULL;
+ char *cgi;
+
+ mod_vhost_ldap_request_t *req =
+ (mod_vhost_ldap_request_t *)apr_pcalloc(r->pool, sizeof(mod_vhost_ldap_request_t));
+ ap_set_module_config(r->request_config, &vhost_ldap_module, req);
+
+ if (!cfg->enabled) {
+ return DECLINED;
+ }
+
+ if (!cfg->have_ldap_url) {
+ return DECLINED;
+ }
+
+start_over:
+
+ if (cfg->host) {
+ ldc = util_ldap_connection_find(r, cfg->host, cfg->port,
+ cfg->binddn, cfg->bindpw, cfg->deref,
+ cfg->secure);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r,
+ "[mod_vhost_ldap.c] translate: no sec->host - weird...?");
+ return DECLINED;
+ }
+
+ ap_log_rerror (APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
+ "[mod_vhost_ldap.c]: translating %s", r->parsed_uri.path);
+
+ apr_snprintf(filtbuf, FILTER_LENGTH, "(&(%s)(|(apacheServerName=%s)(apacheServerAlias=%s)))", cfg->filter, r->hostname, r->hostname);
+
+ result = util_ldap_cache_getuserdn(r, ldc, cfg->url, cfg->basedn, cfg->scope,
+ attributes, filtbuf, &dn, &vals);
+
+ util_ldap_connection_close(ldc);
+
+ /* sanity check - if server is down, retry it up to 5 times */
+ if (result == LDAP_SERVER_DOWN) {
+ if (failures++ <= 5) {
+ goto start_over;
+ }
+ }
+
+ /* handle bind failure */
+ if (result != LDAP_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r,
+ "[mod_vhost_ldap.c] translate: "
+ "translate failed; URI %s [%s][%s]",
+ r->parsed_uri.path, ldc->reason, ldap_err2string(result));
+ return DECLINED;
+ }
+
+ /* mark the user and DN */
+ req->dn = apr_pstrdup(r->pool, dn);
+
+ /* Optimize */
+ if (vals) {
+ int i = 0;
+ while (attributes[i]) {
+
+ if (strcasecmp (attributes[i], "apacheServerName") == 0) {
+ req->name = apr_pstrdup (r->pool, vals[i]);
+ }
+ else if (strcasecmp (attributes[i], "apacheServerAdmin") == 0) {
+ req->admin = apr_pstrdup (r->pool, vals[i]);
+ }
+ else if (strcasecmp (attributes[i], "apacheDocumentRoot") == 0) {
+ req->docroot = apr_pstrdup (r->pool, vals[i]);
+ }
+ else if (strcasecmp (attributes[i], "apacheScriptAlias") == 0) {
+ req->cgiroot = apr_pstrdup (r->pool, vals[i]);
+ }
+ else if (strcasecmp (attributes[i], "apacheSuexecUid") == 0) {
+ req->uid = apr_pstrdup(r->pool, vals[i]);
+ }
+ else if (strcasecmp (attributes[i], "apacheSuexecGid") == 0) {
+ req->gid = apr_pstrdup(r->pool, vals[i]);
+ }
+ i++;
+ }
+ }
+
+ if ((req->name == NULL)||(req->docroot == NULL)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r,
+ "[mod_vhost_ldap.c] translate: "
+ "translate failed; ServerName or DocumentRoot not defined");
+ return DECLINED;
+ }
+
+ cgi = NULL;
+
+ if (req->cgiroot) {
+ cgi = strstr(r->parsed_uri.path, "cgi-bin/");
+ if (cgi && (cgi != r->uri + strspn(r->parsed_uri.path, "/"))) {
+ cgi = NULL;
+ }
+
+ if (cgi) {
+ r->filename =
+ apr_pstrcat (r->pool, req->cgiroot, cgi + strlen("cgi-bin"), NULL);
+ r->handler = "cgi-script";
+ apr_table_setn(r->notes, "alias-forced-type", r->handler);
+ } else {
+
+ r->filename =
+ apr_pstrcat (r->pool, req->docroot, r->parsed_uri.path, NULL);
+ }
+ }
+
+ r->server->server_hostname = apr_pstrdup (r->pool, req->name);
+
+ if (req->admin) {
+ r->server->server_admin = apr_pstrdup (r->pool, req->admin);
+ }
+
+ // set environment variables
+ e = r->subprocess_env;
+ apr_table_addn (e, "SERVER_ROOT", req->docroot);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
+ "[mod_vhost_ldap.c]: translated to %s", r->filename);
+
+ return OK;
+}
+
+#ifdef HAVE_UNIX_SUEXEC
+static ap_unix_identity_t *mod_vhost_ldap_get_suexec_id_doer(const request_rec * r)
+{
+ ap_unix_identity_t *ugid = NULL;
+ mod_vhost_ldap_config_t *cfg =
+ (mod_vhost_ldap_config_t *)ap_get_module_config(r->server->module_config,
+ &vhost_ldap_module);
+ mod_vhost_ldap_request_t *req =
+ (mod_vhost_ldap_request_t *)ap_get_module_config(r->request_config,
+ &vhost_ldap_module);
+
+ uid_t uid = -1;
+ gid_t gid = -1;
+
+ // mod_vhost_ldap is disabled
+ if (!cfg->enabled) {
+ return NULL;
+ }
+
+ if ((req == NULL)||(req->uid == NULL)||(req->gid == NULL)) {
+ return NULL;
+ }
+
+ if ((ugid = apr_palloc(r->pool, sizeof(ap_unix_identity_t))) == NULL) {
+ return NULL;
+ }
+
+ uid = (uid_t)atoll(req->uid);
+ gid = (gid_t)atoll(req->gid);
+
+ if ((uid <= MIN_UID)||(gid <= MIN_GID)) {
+ return NULL;
+ }
+
+ ugid->uid = uid;
+ ugid->gid = gid;
+ ugid->userdir = 0;
+
+ return ugid;
+}
+#endif
+
+static void
+mod_vhost_ldap_register_hooks (apr_pool_t * p)
+{
+ ap_hook_post_config(mod_vhost_ldap_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_translate_name(mod_vhost_ldap_translate_name, NULL, NULL, APR_HOOK_MIDDLE);
+#ifdef HAVE_UNIX_SUEXEC
+ ap_hook_get_suexec_identity(mod_vhost_ldap_get_suexec_id_doer, NULL, NULL, APR_HOOK_MIDDLE);
+#endif
+}
+
+module AP_MODULE_DECLARE_DATA vhost_ldap_module = {
+ STANDARD20_MODULE_STUFF,
+ NULL,
+ NULL,
+ mod_vhost_ldap_create_server_config,
+ NULL,
+ mod_vhost_ldap_cmds,
+ mod_vhost_ldap_register_hooks,
+};