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