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