/* ============================================================ * 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 */ #define CORE_PRIVATE #include #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 100 #define MIN_GID 100 module AP_MODULE_DECLARE_DATA vhost_ldap_module; typedef enum { MVL_UNSET, MVL_DISABLED, MVL_ENABLED } mod_vhost_ldap_status_e; typedef struct mod_vhost_ldap_config_t { mod_vhost_ldap_status_e 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_deref; /* Set if we have found an Deref option */ 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; /* ScriptAlias */ char *uid; /* Suexec Uid */ char *gid; /* Suexec Gid */ } mod_vhost_ldap_request_t; char *attributes[] = { "apacheServerName", "apacheDocumentRoot", "apacheScriptAlias", "apacheSuexecUid", "apacheSuexecGid", "apacheServerAdmin", 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_VERSION); return OK; } static void * mod_vhost_ldap_create_server_config (apr_pool_t *p, server_rec *s) { mod_vhost_ldap_config_t *conf = (mod_vhost_ldap_config_t *)apr_pcalloc(p, sizeof (mod_vhost_ldap_config_t)); conf->enabled = MVL_UNSET; conf->have_ldap_url = 0; conf->have_deref = 0; conf->binddn = NULL; conf->bindpw = NULL; conf->deref = always; return conf; } static void * mod_vhost_ldap_merge_server_config(apr_pool_t *p, void *parentv, void *childv) { mod_vhost_ldap_config_t *parent = (mod_vhost_ldap_config_t *) parentv; mod_vhost_ldap_config_t *child = (mod_vhost_ldap_config_t *) childv; mod_vhost_ldap_config_t *conf = (mod_vhost_ldap_config_t *)apr_pcalloc(p, sizeof(mod_vhost_ldap_config_t)); if (child->enabled == MVL_UNSET) { conf->enabled = parent->enabled; } else { conf->enabled = child->enabled; } if (child->have_ldap_url) { conf->have_ldap_url = child->have_ldap_url; conf->url = child->url; conf->host = child->host; conf->port = child->port; conf->basedn = child->basedn; conf->scope = child->scope; conf->filter = child->filter; conf->secure = child->secure; } else { conf->have_ldap_url = parent->have_ldap_url; conf->url = parent->url; conf->host = parent->host; conf->port = parent->port; conf->basedn = parent->basedn; conf->scope = parent->scope; conf->filter = parent->filter; conf->secure = parent->secure; } if (child->have_deref) { conf->have_deref = child->have_deref; conf->deref = child->deref; } else { conf->have_deref = parent->have_deref; conf->deref = parent->deref; } conf->binddn = (child->binddn ? child->binddn : parent->binddn); conf->bindpw = (child->bindpw ? child->bindpw : parent->bindpw); return conf; } /* * 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 *conf = (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"; } } conf->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 (conf->host) { char *p = apr_palloc(cmd->pool, strlen(conf->host) + strlen(urld->lud_host) + 2); strcpy(p, urld->lud_host); strcat(p, " "); strcat(p, conf->host); conf->host = p; } else { conf->host = urld->lud_host? apr_pstrdup(cmd->pool, urld->lud_host) : "localhost"; } conf->basedn = urld->lud_dn? apr_pstrdup(cmd->pool, urld->lud_dn) : ""; conf->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. */ conf->filter = apr_pstrdup(cmd->pool, urld->lud_filter+1); conf->filter[strlen(conf->filter)-1] = '\0'; } else { conf->filter = apr_pstrdup(cmd->pool, urld->lud_filter); } } else { conf->filter = "objectClass=apacheConfig"; } /* "ldaps" indicates secure ldap connections desired */ if (strncasecmp(url, "ldaps", 5) == 0) { conf->secure = 1; conf->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 { conf->secure = 0; conf->port = urld->lud_port? urld->lud_port : LDAP_PORT; ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server, "LDAP: vhost_ldap not using SSL connections"); } conf->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 *conf = (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config, &vhost_ldap_module); conf->enabled = (enabled) ? MVL_ENABLED : MVL_DISABLED; return NULL; } static const char *mod_vhost_ldap_set_binddn(cmd_parms *cmd, void *dummy, const char *binddn) { mod_vhost_ldap_config_t *conf = (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config, &vhost_ldap_module); conf->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 *conf = (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config, &vhost_ldap_module); conf->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 *conf = (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) { conf->deref = never; conf->have_deref = 1; } else if (strcmp(deref, "searching") == 0) { conf->deref = searching; conf->have_deref = 1; } else if (strcmp(deref, "finding") == 0) { conf->deref = finding; conf->have_deref = 1; } else if (strcmp(deref, "always") == 0 || strcasecmp(deref, "on") == 0) { conf->deref = always; conf->have_deref = 1; } 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 compliant\n" "URL of the form ldap://host[:port]/basedn[?attrib[?scope[?filter]]].\n" "\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 be 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 *conf = (mod_vhost_ldap_config_t *)ap_get_module_config(r->server->module_config, &vhost_ldap_module); core_server_config * core = (core_server_config *) ap_get_module_config(r->server->module_config, &core_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); // mod_vhost_ldap is disabled or we don't have LDAP Url if ((conf->enabled != MVL_ENABLED)||(!conf->have_ldap_url)) { return DECLINED; } start_over: if (conf->host) { ldc = util_ldap_connection_find(r, conf->host, conf->port, conf->binddn, conf->bindpw, conf->deref, conf->secure); } else { ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r, "[mod_vhost_ldap.c] translate: no conf->host - weird...?"); return DECLINED; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, "[mod_vhost_ldap.c]: translating %s", r->uri); apr_snprintf(filtbuf, FILTER_LENGTH, "(&(%s)(|(apacheServerName=%s)(apacheServerAlias=%s)))", conf->filter, r->hostname, r->hostname); result = util_ldap_cache_getuserdn(r, ldc, conf->url, conf->basedn, conf->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_WARN|APLOG_NOERRNO, 0, r, "[mod_vhost_ldap.c] translate: " "translate failed; VHost %s; URI %s[%s]", r->hostname, r->uri, 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++; } } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, "[mod_vhost_ldap.c]: loaded from ldap: " "apacheServerName: %s, " "apacheServerAdmin: %s, " "apacheDocumentRoot: %s, " "apacheScriptAlias: %s, " "apacheSuexecUid: %s, " "apacheSuexecGid: %s" , req->name, req->admin, req->docroot, req->cgiroot, req->uid, req->gid); 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->uri, "cgi-bin/"); if (cgi && (cgi != r->uri + strspn(r->uri, "/"))) { 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 if (r->uri[0] == '/') { r->filename = apr_pstrcat (r->pool, req->docroot, r->uri, NULL); } else { return DECLINED; } 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); core->ap_document_root = apr_pstrdup(r->pool, 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 *conf = (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 or we don't have LDAP Url if ((conf->enabled != MVL_ENABLED)||(!conf->have_ldap_url)) { 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, mod_vhost_ldap_merge_server_config, mod_vhost_ldap_cmds, mod_vhost_ldap_register_hooks, };