]> andersk Git - mod-vhost-ldap.git/blob - mod_vhost_ldap.c
apacheConfig is STRUCTURAL objectClass and not AUXILIARY
[mod-vhost-ldap.git] / mod_vhost_ldap.c
1 /* ============================================================
2  * Copyright (c) 2003-2004, Ondrej Sury
3  * All rights reserved.
4  *
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
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  * 
17  */
18
19 /*
20  * mod_vhost_ldap.c --- read virtual host config from LDAP directory
21  */
22
23 #define CORE_PRIVATE
24 #define MOD_VHOST_LDAP_VERSION "mod_vhost_ldap/0.2.4"
25
26 #include <unistd.h>
27
28 #include "httpd.h"
29 #include "http_config.h"
30 #include "http_core.h"
31 #include "http_log.h"
32 #include "http_request.h"
33 #include "apr_ldap.h"
34 #include "apr_strings.h"
35 #include "apr_reslist.h"
36 #include "util_ldap.h"
37
38 #ifndef APU_HAS_LDAP
39 #error mod_vhost_ldap requires APR-util to have LDAP support built in
40 #endif
41
42 #if !defined(WIN32) && !defined(OS2) && !defined(BEOS) && !defined(NETWARE)
43 #define HAVE_UNIX_SUEXEC
44 #endif
45
46 #ifdef HAVE_UNIX_SUEXEC
47 #include "unixd.h"              /* Contains the suexec_identity hook used on Unix */
48 #endif
49
50 #define MIN_UID 100
51 #define MIN_GID 100
52
53 module AP_MODULE_DECLARE_DATA vhost_ldap_module;
54
55 typedef struct mod_vhost_ldap_config_t {
56     apr_pool_t *pool;                   /* Pool that this config is allocated from */
57 #if APR_HAS_THREADS
58     apr_thread_mutex_t *lock;           /* Lock for this config */
59 #endif
60     int enabled;                        /* Is vhost_ldap enabled? */
61
62     /* These parameters are all derived from the VhostLDAPURL directive */
63     char *url;                          /* String representation of LDAP URL */
64
65     char *host;                         /* Name of the LDAP server (or space separated list) */
66     int port;                           /* Port of the LDAP server */
67     char *basedn;                       /* Base DN to do all searches from */
68     int scope;                          /* Scope of the search */
69     char *filter;                       /* Filter to further limit the search  */
70     deref_options deref;                /* how to handle alias dereferening */
71
72     char *binddn;                       /* DN to bind to server (can be NULL) */
73     char *bindpw;                       /* Password to bind to server (can be NULL) */
74
75     int have_ldap_url;                  /* Set if we have found an LDAP url */
76
77     int secure;                         /* True if SSL connections are requested */
78 } mod_vhost_ldap_config_t;
79
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;
89
90 char *attributes[] =
91   { "apacheServerName", "apacheServerAdmin", "apacheDocumentRoot", "apacheScriptAlias", "apacheSuexecUid", "apacheSuexecGid", 0 };
92
93 static int mod_vhost_ldap_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
94 {
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;
101
102     }
103
104     ap_add_version_component(p, MOD_VHOST_LDAP_VERSION);
105
106     return OK;
107 }
108
109 static void *
110 mod_vhost_ldap_create_server_config (apr_pool_t *p, server_rec *s)
111 {
112     mod_vhost_ldap_config_t *cfg =
113         (mod_vhost_ldap_config_t *)apr_pcalloc(p, sizeof (mod_vhost_ldap_config_t));
114
115     cfg->pool = p;
116
117 #if APR_HAS_THREADS
118     apr_thread_mutex_create(&cfg->lock, APR_THREAD_MUTEX_DEFAULT, p);
119 #endif
120
121     cfg->enabled = 0;
122     cfg->have_ldap_url = 0;
123     cfg->url = "";
124     cfg->host = NULL;
125     cfg->binddn = NULL;
126     cfg->bindpw = NULL;
127     cfg->deref = always;
128     cfg->secure = 0;
129
130     return cfg;
131 }
132
133 /* 
134  * Use the ldap url parsing routines to break up the ldap url into
135  * host and port.
136  */
137 static const char *mod_vhost_ldap_parse_url(cmd_parms *cmd, 
138                                             void *dummy,
139                                             const char *url)
140 {
141     int result;
142     apr_ldap_url_desc_t *urld;
143
144     mod_vhost_ldap_config_t *cfg =
145         (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
146                                                         &vhost_ldap_module);
147
148     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
149                  cmd->server, "[mod_vhost_ldap.c] url parse: `%s'", 
150                  url);
151
152     result = apr_ldap_url_parse(url, &(urld));
153     if (result != LDAP_SUCCESS) {
154         switch (result) {
155         case LDAP_URL_ERR_NOTLDAP:
156             return "LDAP URL does not begin with ldap://";
157         case LDAP_URL_ERR_NODN:
158             return "LDAP URL does not have a DN";
159         case LDAP_URL_ERR_BADSCOPE:
160             return "LDAP URL has an invalid scope";
161         case LDAP_URL_ERR_MEM:
162             return "Out of memory parsing LDAP URL";
163         default:
164             return "Could not parse LDAP URL";
165         }
166     }
167     cfg->url = apr_pstrdup(cmd->pool, url);
168
169     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
170                  cmd->server, "[mod_vhost_ldap.c] url parse: Host: %s", urld->lud_host);
171     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
172                  cmd->server, "[mod_vhost_ldap.c] url parse: Port: %d", urld->lud_port);
173     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
174                  cmd->server, "[mod_vhost_ldap.c] url parse: DN: %s", urld->lud_dn);
175     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
176                  cmd->server, "[mod_vhost_ldap.c] url parse: attrib: %s", urld->lud_attrs? urld->lud_attrs[0] : "(null)");
177     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
178                  cmd->server, "[mod_vhost_ldap.c] url parse: scope: %s", 
179                  (urld->lud_scope == LDAP_SCOPE_SUBTREE? "subtree" : 
180                  urld->lud_scope == LDAP_SCOPE_BASE? "base" : 
181                  urld->lud_scope == LDAP_SCOPE_ONELEVEL? "onelevel" : "unknown"));
182     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
183                  cmd->server, "[mod_vhost_ldap.c] url parse: filter: %s", urld->lud_filter);
184
185     /* Set all the values, or at least some sane defaults */
186     if (cfg->host) {
187         char *p = apr_palloc(cmd->pool, strlen(cfg->host) + strlen(urld->lud_host) + 2);
188         strcpy(p, urld->lud_host);
189         strcat(p, " ");
190         strcat(p, cfg->host);
191         cfg->host = p;
192     }
193     else {
194         cfg->host = urld->lud_host? apr_pstrdup(cmd->pool, urld->lud_host) : "localhost";
195     }
196     cfg->basedn = urld->lud_dn? apr_pstrdup(cmd->pool, urld->lud_dn) : "";
197
198     cfg->scope = urld->lud_scope == LDAP_SCOPE_ONELEVEL ?
199         LDAP_SCOPE_ONELEVEL : LDAP_SCOPE_SUBTREE;
200
201     if (urld->lud_filter) {
202         if (urld->lud_filter[0] == '(') {
203             /* 
204              * Get rid of the surrounding parens; later on when generating the
205              * filter, they'll be put back.
206              */
207             cfg->filter = apr_pstrdup(cmd->pool, urld->lud_filter+1);
208             cfg->filter[strlen(cfg->filter)-1] = '\0';
209         }
210         else {
211             cfg->filter = apr_pstrdup(cmd->pool, urld->lud_filter);
212         }
213     }
214     else {
215         cfg->filter = "objectClass=apacheConfig";
216     }
217
218       /* "ldaps" indicates secure ldap connections desired
219       */
220     if (strncasecmp(url, "ldaps", 5) == 0)
221     {
222         cfg->secure = 1;
223         cfg->port = urld->lud_port? urld->lud_port : LDAPS_PORT;
224         ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server,
225                      "LDAP: vhost_ldap using SSL connections");
226     }
227     else
228     {
229         cfg->secure = 0;
230         cfg->port = urld->lud_port? urld->lud_port : LDAP_PORT;
231         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, 
232                      "LDAP: vhost_ldap not using SSL connections");
233     }
234
235     cfg->have_ldap_url = 1;
236     apr_ldap_free_urldesc(urld);
237     return NULL;
238 }
239
240 static const char *mod_vhost_ldap_set_enabled(cmd_parms *cmd, void *dummy, int enabled)
241 {
242     mod_vhost_ldap_config_t *cfg =
243         (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
244                                                          &vhost_ldap_module);
245
246     cfg->enabled = enabled;
247     return NULL;
248 }
249
250 static const char *mod_vhost_ldap_set_binddn(cmd_parms *cmd, void *dummy, const char *binddn)
251 {
252     mod_vhost_ldap_config_t *cfg =
253         (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
254                                                          &vhost_ldap_module);
255
256     cfg->binddn = apr_pstrdup(cmd->pool, binddn);
257     return NULL;
258 }
259
260 static const char *mod_vhost_ldap_set_bindpw(cmd_parms *cmd, void *dummy, const char *bindpw)
261 {
262     mod_vhost_ldap_config_t *cfg =
263         (mod_vhost_ldap_config_t *)ap_get_module_config(cmd->server->module_config,
264                                                          &vhost_ldap_module);
265
266     cfg->bindpw = apr_pstrdup(cmd->pool, bindpw);
267     return NULL;
268 }
269
270 static const char *mod_vhost_ldap_set_deref(cmd_parms *cmd, void *dummy, const char *deref)
271 {
272     mod_vhost_ldap_config_t *cfg = 
273         (mod_vhost_ldap_config_t *)ap_get_module_config (cmd->server->module_config,
274                                                          &vhost_ldap_module);
275
276     if (strcmp(deref, "never") == 0 || strcasecmp(deref, "off") == 0) {
277         cfg->deref = never;
278     }
279     else if (strcmp(deref, "searching") == 0) {
280         cfg->deref = searching;
281     }
282     else if (strcmp(deref, "finding") == 0) {
283         cfg->deref = finding;
284     }
285     else if (strcmp(deref, "always") == 0 || strcasecmp(deref, "on") == 0) {
286         cfg->deref = always;
287     }
288     else {
289         return "Unrecognized value for VhostLDAPAliasDereference directive";
290     }
291     return NULL;
292 }
293
294 command_rec mod_vhost_ldap_cmds[] = {
295     AP_INIT_TAKE1("VhostLDAPURL", mod_vhost_ldap_parse_url, NULL, RSRC_CONF,
296                   "URL to define LDAP connection. This should be an RFC 2255 compliant\n"
297                   "URL of the form ldap://host[:port]/basedn[?attrib[?scope[?filter]]].\n"
298                   "<ul>\n"
299                   "<li>Host is the name of the LDAP server. Use a space separated list of hosts \n"
300                   "to specify redundant servers.\n"
301                   "<li>Port is optional, and specifies the port to connect to.\n"
302                   "<li>basedn specifies the base DN to start searches from\n"
303                   "</ul>\n"),
304
305     AP_INIT_TAKE1 ("VhostLDAPBindDN", mod_vhost_ldap_set_binddn, NULL, RSRC_CONF,
306                    "DN to use to bind to LDAP server. If not provided, will do an anonymous bind."),
307     
308     AP_INIT_TAKE1("VhostLDAPBindPassword", mod_vhost_ldap_set_bindpw, NULL, RSRC_CONF,
309                   "Password to use to bind to LDAP server. If not provided, will do an anonymous bind."),
310
311     AP_INIT_FLAG("VhostLDAPEnabled", mod_vhost_ldap_set_enabled, NULL, RSRC_CONF,
312                  "Set to off to disable vhost_ldap, even if it's been enabled in a higher tree"),
313
314     AP_INIT_TAKE1("VhostLDAPDereferenceAliases", mod_vhost_ldap_set_deref, NULL, RSRC_CONF,
315                   "Determines how aliases are handled during a search. Can be one of the"
316                   "values \"never\", \"searching\", \"finding\", or \"always\". "
317                   "Defaults to always."),
318
319     {NULL}
320 };
321
322 #define FILTER_LENGTH MAX_STRING_LEN
323 static int
324 mod_vhost_ldap_translate_name (request_rec * r)
325 {
326     apr_table_t *e;
327     int failures = 0;
328     const char **vals = NULL;
329     char filtbuf[FILTER_LENGTH];
330     mod_vhost_ldap_config_t *cfg =
331         (mod_vhost_ldap_config_t *)ap_get_module_config(r->server->module_config, &vhost_ldap_module);
332     core_server_config * core =
333         (core_server_config *) ap_get_module_config(r->server->module_config, &core_module);
334     util_ldap_connection_t *ldc = NULL;
335     int result = 0;
336     const char *dn = NULL;
337     char *cgi;
338
339     mod_vhost_ldap_request_t *req =
340         (mod_vhost_ldap_request_t *)apr_pcalloc(r->pool, sizeof(mod_vhost_ldap_request_t));
341     ap_set_module_config(r->request_config, &vhost_ldap_module, req);
342
343     if (!cfg->enabled) {
344         return DECLINED;
345     }
346
347     if (!cfg->have_ldap_url) {
348         return DECLINED;
349     }
350
351 start_over:
352
353     if (cfg->host) {
354         ldc = util_ldap_connection_find(r, cfg->host, cfg->port,
355                                         cfg->binddn, cfg->bindpw, cfg->deref,
356                                         cfg->secure);
357     }
358     else {
359         ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r, 
360                       "[mod_vhost_ldap.c] translate: no sec->host - weird...?");
361         return DECLINED;
362     }
363
364     ap_log_rerror (APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
365                    "[mod_vhost_ldap.c]: translating %s", r->parsed_uri.path);
366
367     apr_snprintf(filtbuf, FILTER_LENGTH, "(&(%s)(|(apacheServerName=%s)(apacheServerAlias=%s)))", cfg->filter, r->hostname, r->hostname);
368
369     result = util_ldap_cache_getuserdn(r, ldc, cfg->url, cfg->basedn, cfg->scope,
370                                        attributes, filtbuf, &dn, &vals);
371
372     util_ldap_connection_close(ldc);
373
374     /* sanity check - if server is down, retry it up to 5 times */
375     if (result == LDAP_SERVER_DOWN) {
376         if (failures++ <= 5) {
377             goto start_over;
378         }
379     }
380
381     /* handle bind failure */
382     if (result != LDAP_SUCCESS) {
383         ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r, 
384                       "[mod_vhost_ldap.c] translate: "
385                       "translate failed; URI %s [%s][%s]",
386                       r->parsed_uri.path, ldc->reason, ldap_err2string(result));
387         return DECLINED;
388     }
389
390     /* mark the user and DN */
391     req->dn = apr_pstrdup(r->pool, dn);
392
393     /* Optimize */
394     if (vals) {
395         int i = 0;
396         while (attributes[i]) {
397
398             if (strcasecmp (attributes[i], "apacheServerName") == 0) {
399                 req->name = apr_pstrdup (r->pool, vals[i]);
400             }
401             else if (strcasecmp (attributes[i], "apacheServerAdmin") == 0) {
402                 req->admin = apr_pstrdup (r->pool, vals[i]);
403             }
404             else if (strcasecmp (attributes[i], "apacheDocumentRoot") == 0) {
405                 req->docroot = apr_pstrdup (r->pool, vals[i]);
406             }
407             else if (strcasecmp (attributes[i], "apacheScriptAlias") == 0) {
408                 req->cgiroot = apr_pstrdup (r->pool, vals[i]);
409             }
410             else if (strcasecmp (attributes[i], "apacheSuexecUid") == 0) {
411                 req->uid = apr_pstrdup(r->pool, vals[i]);
412             }
413             else if (strcasecmp (attributes[i], "apacheSuexecGid") == 0) {
414                 req->gid = apr_pstrdup(r->pool, vals[i]);
415             }
416             i++;
417         }
418     }
419
420     if ((req->name == NULL)||(req->docroot == NULL)) {
421         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, 
422                       "[mod_vhost_ldap.c] translate: "
423                       "translate failed; ServerName or DocumentRoot not defined");
424         return DECLINED;
425     }
426
427     cgi = NULL;
428   
429     if (req->cgiroot) {
430         cgi = strstr(r->parsed_uri.path, "cgi-bin/");
431         if (cgi && (cgi != r->uri + strspn(r->parsed_uri.path, "/"))) {
432             cgi = NULL;
433         }
434     }
435
436     if (cgi) {
437         r->filename =
438             apr_pstrcat (r->pool, req->cgiroot, cgi + strlen("cgi-bin"), NULL);
439         r->handler = "cgi-script";
440         apr_table_setn(r->notes, "alias-forced-type", r->handler);
441     } else {
442         r->filename =
443             apr_pstrcat (r->pool, req->docroot, r->parsed_uri.path, NULL);
444     }
445
446     r->server->server_hostname = apr_pstrdup (r->pool, req->name);
447
448     if (req->admin) {
449         r->server->server_admin = apr_pstrdup (r->pool, req->admin);
450     }
451
452     // set environment variables
453     e = r->subprocess_env;
454     apr_table_addn (e, "SERVER_ROOT", req->docroot);
455
456     core->ap_document_root = apr_pstrdup(r->pool, req->docroot);
457
458     ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
459                   "[mod_vhost_ldap.c]: translated to %s", r->filename);
460
461     return OK;
462 }
463
464 #ifdef HAVE_UNIX_SUEXEC
465 static ap_unix_identity_t *mod_vhost_ldap_get_suexec_id_doer(const request_rec * r)
466 {
467   ap_unix_identity_t *ugid = NULL;
468   mod_vhost_ldap_config_t *cfg = 
469       (mod_vhost_ldap_config_t *)ap_get_module_config(r->server->module_config,
470                                                       &vhost_ldap_module);
471   mod_vhost_ldap_request_t *req =
472       (mod_vhost_ldap_request_t *)ap_get_module_config(r->request_config,
473                                                        &vhost_ldap_module);
474
475   uid_t uid = -1;
476   gid_t gid = -1;
477
478   // mod_vhost_ldap is disabled
479   if (!cfg->enabled) {
480       return NULL;
481   }
482
483   if ((req == NULL)||(req->uid == NULL)||(req->gid == NULL)) {
484       return NULL;
485   }
486
487   if ((ugid = apr_palloc(r->pool, sizeof(ap_unix_identity_t))) == NULL) {
488       return NULL;
489   }
490
491   uid = (uid_t)atoll(req->uid);
492   gid = (gid_t)atoll(req->gid);
493
494   if ((uid < MIN_UID)||(gid < MIN_GID)) {
495       return NULL;
496   }
497
498   ugid->uid = uid;
499   ugid->gid = gid;
500   ugid->userdir = 0;
501   
502   return ugid;
503 }
504 #endif
505
506 static void
507 mod_vhost_ldap_register_hooks (apr_pool_t * p)
508 {
509     ap_hook_post_config(mod_vhost_ldap_post_config, NULL, NULL, APR_HOOK_MIDDLE);
510     ap_hook_translate_name(mod_vhost_ldap_translate_name, NULL, NULL, APR_HOOK_MIDDLE);
511 #ifdef HAVE_UNIX_SUEXEC
512     ap_hook_get_suexec_identity(mod_vhost_ldap_get_suexec_id_doer, NULL, NULL, APR_HOOK_MIDDLE);
513 #endif
514 }
515
516 module AP_MODULE_DECLARE_DATA vhost_ldap_module = {
517   STANDARD20_MODULE_STUFF,
518   NULL,
519   NULL,
520   mod_vhost_ldap_create_server_config,
521   NULL,
522   mod_vhost_ldap_cmds,
523   mod_vhost_ldap_register_hooks,
524 };
This page took 0.081472 seconds and 5 git commands to generate.