bottleneck testcase based on rubbos
[bottlenecks.git] / rubbos / app / httpd-2.0.64 / modules / experimental / mod_auth_ldap.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * mod_auth_ldap.c: LDAP authentication module
19  * 
20  * Original code from auth_ldap module for Apache v1.3:
21  * Copyright 1998, 1999 Enbridge Pipelines Inc. 
22  * Copyright 1999-2001 Dave Carrigan
23  */
24
25 #include <apr_ldap.h>
26 #include <apr_strings.h>
27 #include <apr_xlate.h>
28 #define APR_WANT_STRFUNC
29 #include <apr_want.h>
30
31 #include "ap_config.h"
32 #if APR_HAVE_UNISTD_H
33 /* for getpid() */
34 #include <unistd.h>
35 #endif
36 #include <ctype.h>
37
38 #include "httpd.h"
39 #include "http_config.h"
40 #include "http_core.h"
41 #include "http_log.h"
42 #include "http_protocol.h"
43 #include "http_request.h"
44 #include "util_ldap.h"
45
46 #ifndef APU_HAS_LDAP
47 #error mod_auth_ldap requires APR-util to have LDAP support built in
48 #endif
49
50 /* per directory configuration */
51 typedef struct {
52     apr_pool_t *pool;                   /* Pool that this config is allocated from */
53 #if APR_HAS_THREADS
54     apr_thread_mutex_t *lock;           /* Lock for this config */
55 #endif
56     int auth_authoritative;             /* Is this auth method the one and only? */
57     int enabled;                        /* Is auth_ldap enabled in this directory? */
58
59     /* These parameters are all derived from the AuthLDAPURL directive */
60     char *url;                          /* String representation of the 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     char *attribute;                    /* Attribute to search for */
66     char **attributes;                  /* Array of all the attributes to return */
67     int scope;                          /* Scope of the search */
68     char *filter;                       /* Filter to further limit the search  */
69     deref_options deref;                /* how to handle alias dereferening */
70     char *binddn;                       /* DN to bind to server (can be NULL) */
71     char *bindpw;                       /* Password to bind to server (can be NULL) */
72
73     int frontpage_hack;                 /* Hack for frontpage support */
74     int user_is_dn;                     /* If true, connection->user is DN instead of userid */
75     int compare_dn_on_server;           /* If true, will use server to do DN compare */
76
77     int have_ldap_url;                  /* Set if we have found an LDAP url */
78  
79     apr_array_header_t *groupattr;      /* List of Group attributes */
80     int group_attrib_is_dn;             /* If true, the group attribute is the DN, otherwise, 
81                                            it's the exact string passed by the HTTP client */
82
83     int secure;                     /* True if SSL connections are requested */
84 } mod_auth_ldap_config_t;
85
86 typedef struct mod_auth_ldap_request_t {
87     char *dn;                           /* The saved dn from a successful search */
88     char *user;                         /* The username provided by the client */
89 } mod_auth_ldap_request_t;
90
91 /* maximum group elements supported */
92 #define GROUPATTR_MAX_ELTS 10
93
94 struct mod_auth_ldap_groupattr_entry_t {
95     char *name;
96 };
97
98 module AP_MODULE_DECLARE_DATA auth_ldap_module;
99
100 /* function prototypes */
101 void mod_auth_ldap_build_filter(char *filtbuf, 
102                                 request_rec *r, 
103                                 mod_auth_ldap_config_t *sec);
104 int mod_auth_ldap_check_user_id(request_rec *r);
105 int mod_auth_ldap_auth_checker(request_rec *r);
106 void *mod_auth_ldap_create_dir_config(apr_pool_t *p, char *d);
107
108 /* ---------------------------------------- */
109
110 static apr_hash_t *charset_conversions = NULL;
111 static char *to_charset = NULL;           /* UTF-8 identifier derived from the charset.conv file */
112
113 /* Derive a code page ID give a language name or ID */
114 static char* derive_codepage_from_lang (apr_pool_t *p, char *language)
115 {
116     int lang_len;
117     int check_short = 0;
118     char *charset;
119     
120     if (!language)          /* our default codepage */
121         return apr_pstrdup(p, "ISO-8859-1");
122     else
123         lang_len = strlen(language);
124     
125     charset = (char*) apr_hash_get(charset_conversions, language, APR_HASH_KEY_STRING);
126
127     if (!charset) {
128         language[2] = '\0';
129         charset = (char*) apr_hash_get(charset_conversions, language, APR_HASH_KEY_STRING);
130     }
131
132     if (charset) {
133         charset = apr_pstrdup(p, charset);
134     }
135
136     return charset;
137 }
138
139 static apr_xlate_t* get_conv_set (request_rec *r)
140 {
141     char *lang_line = (char*)apr_table_get(r->headers_in, "accept-language");
142     char *lang;
143     apr_xlate_t *convset;
144
145     if (lang_line) {
146         lang_line = apr_pstrdup(r->pool, lang_line);
147         for (lang = lang_line;*lang;lang++) {
148             if ((*lang == ',') || (*lang == ';')) {
149                 *lang = '\0';
150                 break;
151             }
152         }
153         lang = derive_codepage_from_lang(r->pool, lang_line);
154
155         if (lang && (apr_xlate_open(&convset, to_charset, lang, r->pool) == APR_SUCCESS)) {
156             return convset;
157         }
158     }
159
160     return NULL;
161 }
162
163
164 /*
165  * Build the search filter, or at least as much of the search filter that
166  * will fit in the buffer. We don't worry about the buffer not being able
167  * to hold the entire filter. If the buffer wasn't big enough to hold the
168  * filter, ldap_search_s will complain, but the only situation where this
169  * is likely to happen is if the client sent a really, really long
170  * username, most likely as part of an attack.
171  *
172  * The search filter consists of the filter provided with the URL,
173  * combined with a filter made up of the attribute provided with the URL,
174  * and the actual username passed by the HTTP client. For example, assume
175  * that the LDAP URL is
176  * 
177  *   ldap://ldap.airius.com/ou=People, o=Airius?uid??(posixid=*)
178  *
179  * Further, assume that the userid passed by the client was `userj'.  The
180  * search filter will be (&(posixid=*)(uid=userj)).
181  */
182 #define FILTER_LENGTH MAX_STRING_LEN
183 void mod_auth_ldap_build_filter(char *filtbuf, 
184                                 request_rec *r, 
185                                 mod_auth_ldap_config_t *sec)
186 {
187     char *p, *q, *filtbuf_end;
188     char *user;
189     apr_xlate_t *convset = NULL;
190     apr_size_t inbytes;
191     apr_size_t outbytes;
192     char *outbuf;
193
194     if (r->user != NULL) {
195         user = apr_pstrdup (r->pool, r->user);
196     }
197     else
198         return;
199
200     if (charset_conversions) {
201         convset = get_conv_set(r);
202     }
203
204     if (convset) {
205         inbytes = strlen(user);
206         outbytes = (inbytes+1)*3;
207         outbuf = apr_pcalloc(r->pool, outbytes);
208
209         /* Convert the user name to UTF-8.  This is only valid for LDAP v3 */
210         if (apr_xlate_conv_buffer(convset, user, &inbytes, outbuf, &outbytes) == APR_SUCCESS) {
211             user = apr_pstrdup(r->pool, outbuf);
212         }
213     }
214
215     /* 
216      * Create the first part of the filter, which consists of the 
217      * config-supplied portions.
218      */
219     apr_snprintf(filtbuf, FILTER_LENGTH, "(&(%s)(%s=", sec->filter, sec->attribute);
220
221     /* 
222      * Now add the client-supplied username to the filter, ensuring that any
223      * LDAP filter metachars are escaped.
224      */
225     filtbuf_end = filtbuf + FILTER_LENGTH - 1;
226 #if APR_HAS_MICROSOFT_LDAPSDK
227     for (p = user, q=filtbuf + strlen(filtbuf);
228          *p && q < filtbuf_end; ) {
229         if (strchr("*()\\", *p) != NULL) {
230             if ( q + 3 >= filtbuf_end)
231               break;  /* Don't write part of escape sequence if we can't write all of it */
232             *q++ = '\\';
233             switch ( *p++ )
234             {
235               case '*':
236                 *q++ = '2';
237                 *q++ = 'a';
238                 break;
239               case '(':
240                 *q++ = '2';
241                 *q++ = '8';
242                 break;
243               case ')':
244                 *q++ = '2';
245                 *q++ = '9';
246                 break;
247               case '\\':
248                 *q++ = '5';
249                 *q++ = 'c';
250                 break;
251            }
252         }
253         else
254             *q++ = *p++;
255     }
256 #else
257     for (p = user, q=filtbuf + strlen(filtbuf);
258          *p && q < filtbuf_end; *q++ = *p++) {
259         if (strchr("*()\\", *p) != NULL) {
260             *q++ = '\\';
261             if (q >= filtbuf_end) {
262               break;
263             }
264         }
265     }
266 #endif
267     *q = '\0';
268
269     /* 
270      * Append the closing parens of the filter, unless doing so would 
271      * overrun the buffer.
272      */
273     if (q + 2 <= filtbuf_end)
274         strcat(filtbuf, "))");
275 }
276
277 static apr_status_t mod_auth_ldap_cleanup_connection_close(void *param)
278 {
279     util_ldap_connection_t *ldc = param;
280     util_ldap_connection_close(ldc);
281     return APR_SUCCESS;
282 }
283
284
285 /*
286  * Authentication Phase
287  * --------------------
288  *
289  * This phase authenticates the credentials the user has sent with
290  * the request (ie the username and password are checked). This is done
291  * by making an attempt to bind to the LDAP server using this user's
292  * DN and the supplied password.
293  *
294  */
295 int mod_auth_ldap_check_user_id(request_rec *r)
296 {
297     int failures = 0;
298     const char **vals = NULL;
299     char filtbuf[FILTER_LENGTH];
300     mod_auth_ldap_config_t *sec =
301         (mod_auth_ldap_config_t *)ap_get_module_config(r->per_dir_config, &auth_ldap_module);
302
303     util_ldap_connection_t *ldc = NULL;
304     const char *sent_pw;
305     int result = 0;
306     const char *dn = NULL;
307
308     mod_auth_ldap_request_t *req =
309         (mod_auth_ldap_request_t *)apr_pcalloc(r->pool, sizeof(mod_auth_ldap_request_t));
310     ap_set_module_config(r->request_config, &auth_ldap_module, req);
311
312     if (!sec->enabled) {
313         return DECLINED;
314     }
315
316     /* 
317      * Basic sanity checks before any LDAP operations even happen.
318      */
319     if (!sec->have_ldap_url) {
320         return DECLINED;
321     }
322
323 start_over:
324
325     /* There is a good AuthLDAPURL, right? */
326     if (sec->host) {
327         ldc = util_ldap_connection_find(r, sec->host, sec->port,
328                                        sec->binddn, sec->bindpw, sec->deref,
329                                        sec->secure);
330     }
331     else {
332         ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r, 
333                       "[%d] auth_ldap authenticate: no sec->host - weird...?", getpid());
334         return sec->auth_authoritative? HTTP_UNAUTHORIZED : DECLINED;
335     }
336
337     ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
338                   "[%d] auth_ldap authenticate: using URL %s", getpid(), sec->url);
339
340     /* Get the password that the client sent */
341     if ((result = ap_get_basic_auth_pw(r, &sent_pw))) {
342         ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
343                       "[%d] auth_ldap authenticate: "
344                       "ap_get_basic_auth_pw() returns %d", getpid(), result);
345         util_ldap_connection_close(ldc);
346         return result;
347     }
348
349     if (r->user == NULL) {
350         ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
351                       "[%d] auth_ldap authenticate: no user specified", getpid());
352         util_ldap_connection_close(ldc);
353         return sec->auth_authoritative? HTTP_UNAUTHORIZED : DECLINED;
354     }
355
356     /* build the username filter */
357     mod_auth_ldap_build_filter(filtbuf, r, sec);
358
359     /* do the user search */
360     result = util_ldap_cache_checkuserid(r, ldc, sec->url, sec->basedn, sec->scope,
361                                          sec->attributes, filtbuf, sent_pw, &dn, &vals);
362     util_ldap_connection_close(ldc);
363
364     /* sanity check - if server is down, retry it up to 5 times */
365     if (result == LDAP_SERVER_DOWN) {
366         if (failures++ <= 5) {
367             goto start_over;
368         }
369     }
370
371     /* handle bind failure */
372     if (result != LDAP_SUCCESS) {
373         ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r, 
374                       "[%d] auth_ldap authenticate: "
375                       "user %s authentication failed; URI %s [%s][%s]",
376                       getpid(), r->user, r->uri, ldc->reason, ldap_err2string(result));
377         if ((LDAP_INVALID_CREDENTIALS == result) || sec->auth_authoritative) {
378             ap_note_basic_auth_failure(r);
379             return HTTP_UNAUTHORIZED;
380         }
381         else {
382             return DECLINED;
383         }
384     }
385
386     /* mark the user and DN */
387     req->dn = apr_pstrdup(r->pool, dn);
388     req->user = r->user;
389     if (sec->user_is_dn) {
390         r->user = req->dn;
391     }
392
393     /* add environment variables */
394     if (sec->attributes && vals) {
395         apr_table_t *e = r->subprocess_env;
396         int i = 0;
397         while (sec->attributes[i]) {
398             char *str = apr_pstrcat(r->pool, "AUTHENTICATE_", sec->attributes[i], NULL);
399             int j = 13;
400             while (str[j]) {
401                 if (str[j] >= 'a' && str[j] <= 'z') {
402                     str[j] = str[j] - ('a' - 'A');
403                 }
404                 j++;
405             }
406             apr_table_setn(e, str, vals[i]);
407             i++;
408         }
409     }
410
411     ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
412                   "[%d] auth_ldap authenticate: accepting %s", getpid(), r->user);
413
414     return OK;
415 }
416
417
418 /*
419  * Authorisation Phase
420  * -------------------
421  *
422  * After checking whether the username and password are correct, we need
423  * to check whether that user is authorised to view this resource. The
424  * require directive is used to do this:
425  *
426  *  require valid-user          Any authenticated is allowed in.
427  *  require user <username>     This particular user is allowed in.
428  *  require group <groupname>   The user must be a member of this group
429  *                              in order to be allowed in.
430  *  require dn <dn>             The user must have the following DN in the
431  *                              LDAP tree to be let in.
432  *
433  */
434 int mod_auth_ldap_auth_checker(request_rec *r)
435 {
436     int result = 0;
437     mod_auth_ldap_request_t *req =
438         (mod_auth_ldap_request_t *)ap_get_module_config(r->request_config,
439         &auth_ldap_module);
440     mod_auth_ldap_config_t *sec =
441         (mod_auth_ldap_config_t *)ap_get_module_config(r->per_dir_config, 
442         &auth_ldap_module);
443
444     util_ldap_connection_t *ldc = NULL;
445     int m = r->method_number;
446
447     const apr_array_header_t *reqs_arr = ap_requires(r);
448     require_line *reqs = reqs_arr ? (require_line *)reqs_arr->elts : NULL;
449
450     register int x;
451     const char *t;
452     char *w, *value;
453     int method_restricted = 0;
454
455     if (!sec->enabled) {
456         return DECLINED;
457     }
458
459     if (!sec->have_ldap_url) {
460         return DECLINED;
461     }
462
463     /*
464      * It is possible that we've skipped mod_auth_ldap's
465      * check_user_id hook, but still get here. In that
466      * case, the req request_config struct hasn't been initialized
467      * causing problems when we try to use req->dn and/or req->name
468      * below. So we simply create one.
469      *
470      * Unlike 2.2, we don't try to search or populate it.
471      */
472     if (!req) {
473         ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r, 
474                       "[%d] auth_ldap authorise: "
475                       "no req struct - skipped mod_auth_ldap_check_user_id?",
476                       getpid());
477
478         req = (mod_auth_ldap_request_t *)apr_pcalloc(r->pool,
479                                                      sizeof(mod_auth_ldap_request_t));
480         ap_set_module_config(r->request_config, &auth_ldap_module, req);
481     }
482
483     if (sec->host) {
484         ldc = util_ldap_connection_find(r, sec->host, sec->port,
485                                        sec->binddn, sec->bindpw, sec->deref,
486                                        sec->secure);
487         apr_pool_cleanup_register(r->pool, ldc,
488                                   mod_auth_ldap_cleanup_connection_close,
489                                   apr_pool_cleanup_null);
490     }
491     else {
492         ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r, 
493                       "[%d] auth_ldap authorise: no sec->host - weird...?", getpid());
494         return sec->auth_authoritative? HTTP_UNAUTHORIZED : DECLINED;
495     }
496
497     /* 
498      * If there are no elements in the group attribute array, the default should be
499      * member and uniquemember; populate the array now.
500      */
501     if (sec->groupattr->nelts == 0) {
502         struct mod_auth_ldap_groupattr_entry_t *grp;
503 #if APR_HAS_THREADS
504         apr_thread_mutex_lock(sec->lock);
505 #endif
506         grp = apr_array_push(sec->groupattr);
507         grp->name = "member";
508         grp = apr_array_push(sec->groupattr);
509         grp->name = "uniquemember";
510 #if APR_HAS_THREADS
511         apr_thread_mutex_unlock(sec->lock);
512 #endif
513     }
514
515     if (!reqs_arr) {
516         ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
517                       "[%d] auth_ldap authorise: no requirements array", getpid());
518         return sec->auth_authoritative? HTTP_UNAUTHORIZED : DECLINED;
519     }
520
521     /* Loop through the requirements array until there's no elements
522      * left, or something causes a return from inside the loop */
523     for(x=0; x < reqs_arr->nelts; x++) {
524         if (! (reqs[x].method_mask & (1 << m))) {
525             continue;
526         }
527         method_restricted = 1;
528         
529         t = reqs[x].requirement;
530         w = ap_getword_white(r->pool, &t);    
531
532         if (strcmp(w, "valid-user") == 0) {
533             /*
534              * Valid user will always be true if we authenticated with ldap,
535              * but when using front page, valid user should only be true if
536              * he exists in the frontpage password file. This hack will get
537              * auth_ldap to look up the user in the the pw file to really be
538              * sure that he's valid. Naturally, it requires mod_auth to be
539              * compiled in, but if mod_auth wasn't in there, then the need
540              * for this hack wouldn't exist anyway.
541              */
542             if (sec->frontpage_hack) {
543                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
544                               "[%d] auth_ldap authorise: "
545                               "deferring authorisation to mod_auth (FP Hack)", 
546                               getpid());
547                 return OK;
548             }
549             else {
550                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
551                               "[%d] auth_ldap authorise: "
552                               "successful authorisation because user "
553                               "is valid-user", getpid());
554                 return OK;
555             }
556         }
557         else if (strcmp(w, "user") == 0) {
558             if (req->dn == NULL || strlen(req->dn) == 0) {
559                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
560                               "[%d] auth_ldap authorise: "
561                               "require user: user's DN has not been defined; failing authorisation", 
562                               getpid());
563                 return sec->auth_authoritative? HTTP_UNAUTHORIZED : DECLINED;
564             }
565             /* 
566              * First do a whole-line compare, in case it's something like
567              *   require user Babs Jensen
568              */
569             result = util_ldap_cache_compare(r, ldc, sec->url, req->dn, sec->attribute, t);
570             switch(result) {
571                 case LDAP_COMPARE_TRUE: {
572                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
573                                   "[%d] auth_ldap authorise: "
574                                   "require user: authorisation successful", getpid());
575                     return OK;
576                 }
577                 default: {
578                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
579                                   "[%d] auth_ldap authorise: require user: "
580                                   "authorisation failed [%s][%s]", getpid(),
581                                   ldc->reason, ldap_err2string(result));
582                 }
583             }
584             /* 
585              * Now break apart the line and compare each word on it 
586              */
587             while (t[0]) {
588                 w = ap_getword_conf(r->pool, &t);
589                 result = util_ldap_cache_compare(r, ldc, sec->url, req->dn, sec->attribute, w);
590                 switch(result) {
591                     case LDAP_COMPARE_TRUE: {
592                         ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
593                                       "[%d] auth_ldap authorise: "
594                                       "require user: authorisation successful", getpid());
595                         return OK;
596                     }
597                     default: {
598                         ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
599                                       "[%d] auth_ldap authorise: "
600                                       "require user: authorisation failed [%s][%s]",
601                                       getpid(), ldc->reason, ldap_err2string(result));
602                     }
603                 }
604             }
605         }
606         else if (strcmp(w, "dn") == 0) {
607             if (req->dn == NULL || strlen(req->dn) == 0) {
608                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
609                               "[%d] auth_ldap authorise: "
610                               "require dn: user's DN has not been defined; failing authorisation", 
611                               getpid());
612                 return sec->auth_authoritative? HTTP_UNAUTHORIZED : DECLINED;
613             }
614
615             result = util_ldap_cache_comparedn(r, ldc, sec->url, req->dn, t, sec->compare_dn_on_server);
616             switch(result) {
617                 case LDAP_COMPARE_TRUE: {
618                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
619                                   "[%d] auth_ldap authorise: "
620                                   "require dn: authorisation successful", getpid());
621                     return OK;
622                 }
623                 default: {
624                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
625                                   "[%d] auth_ldap authorise: "
626                                   "require dn \"%s\": LDAP error [%s][%s]",
627                                   getpid(), t, ldc->reason, ldap_err2string(result));
628                 }
629             }
630         }
631         else if (strcmp(w, "group") == 0) {
632             struct mod_auth_ldap_groupattr_entry_t *ent = (struct mod_auth_ldap_groupattr_entry_t *) sec->groupattr->elts;
633             int i;
634
635             if (sec->group_attrib_is_dn) {
636                 if (req->dn == NULL || strlen(req->dn) == 0) {
637                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
638                                   "[%d] auth_ldap authorise: require group: user's DN has not been defined; failing authorisation", 
639                                   getpid());
640                     return sec->auth_authoritative? HTTP_UNAUTHORIZED : DECLINED;
641                 }
642             }
643             else {
644                 if (req->user == NULL || strlen(req->user) == 0) {
645                     /* We weren't called in the authentication phase, so we didn't have a 
646                      * chance to set the user field. Do so now. */
647                     req->user = r->user;
648                 }
649             }
650
651             ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
652                           "[%d] auth_ldap authorise: require group: testing for group membership in \"%s\"", 
653                           getpid(), t);
654
655             for (i = 0; i < sec->groupattr->nelts; i++) {
656                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
657                               "[%d] auth_ldap authorise: require group: testing for %s: %s (%s)", getpid(),
658                               ent[i].name, sec->group_attrib_is_dn ? req->dn : req->user, t);
659
660                 result = util_ldap_cache_compare(r, ldc, sec->url, t, ent[i].name, 
661                                      sec->group_attrib_is_dn ? req->dn : req->user);
662                 switch(result) {
663                     case LDAP_COMPARE_TRUE: {
664                         ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
665                                       "[%d] auth_ldap authorise: require group: "
666                                       "authorisation successful (attribute %s) [%s][%s]",
667                                       getpid(), ent[i].name, ldc->reason, ldap_err2string(result));
668                         return OK;
669                     }
670                     default: {
671                         ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
672                                       "[%d] auth_ldap authorise: require group \"%s\": "
673                                       "authorisation failed [%s][%s]",
674                                       getpid(), t, ldc->reason, ldap_err2string(result));
675                     }
676                 }
677             }
678         }
679         else if (strcmp(w, "ldap-attribute") == 0) {
680             if (req->dn == NULL || strlen(req->dn) == 0) {
681                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
682                               "[%d] auth_ldap authorise: "
683                               "require ldap-attribute: user's DN has not been defined; failing authorisation", 
684                               getpid());
685                 return sec->auth_authoritative? HTTP_UNAUTHORIZED : DECLINED;
686             }
687             while (t[0]) {
688                 w = ap_getword(r->pool, &t, '=');
689                 value = ap_getword_conf(r->pool, &t);
690
691                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
692                               "[%d] auth_ldap authorise: checking attribute"
693                               " %s has value %s", getpid(), w, value);
694                 result = util_ldap_cache_compare(r, ldc, sec->url, req->dn,
695                                                  w, value);
696                 switch(result) {
697                     case LDAP_COMPARE_TRUE: {
698                         ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 
699                                       0, r, "[%d] auth_ldap authorise: "
700                                       "require attribute: authorisation "
701                                       "successful", getpid());
702                         return OK;
703                     }
704                     default: {
705                         ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 
706                                       0, r, "[%d] auth_ldap authorise: "
707                                       "require attribute: authorisation "
708                                       "failed [%s][%s]", getpid(), 
709                                       ldc->reason, ldap_err2string(result));
710                     }
711                 }
712             }
713         }
714     }
715
716     if (!method_restricted) {
717         ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
718                       "[%d] auth_ldap authorise: agreeing because non-restricted", 
719                       getpid());
720         return OK;
721     }
722
723     if (!sec->auth_authoritative) {
724         ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
725                       "[%d] auth_ldap authorise: declining to authorise", getpid());
726         return DECLINED;
727     }
728
729     ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, 
730                   "[%d] auth_ldap authorise: authorisation denied", getpid());
731     ap_note_basic_auth_failure (r);
732
733     return HTTP_UNAUTHORIZED;
734 }
735
736
737 /* ---------------------------------------- */
738 /* config directives */
739
740
741 void *mod_auth_ldap_create_dir_config(apr_pool_t *p, char *d)
742 {
743     mod_auth_ldap_config_t *sec = 
744         (mod_auth_ldap_config_t *)apr_pcalloc(p, sizeof(mod_auth_ldap_config_t));
745
746     sec->pool = p;
747 #if APR_HAS_THREADS
748     apr_thread_mutex_create(&sec->lock, APR_THREAD_MUTEX_DEFAULT, p);
749 #endif
750     sec->auth_authoritative = 1;
751     sec->enabled = 1;
752     sec->groupattr = apr_array_make(p, GROUPATTR_MAX_ELTS, 
753                                    sizeof(struct mod_auth_ldap_groupattr_entry_t));
754
755     sec->have_ldap_url = 0;
756     sec->url = "";
757     sec->host = NULL;
758     sec->binddn = NULL;
759     sec->bindpw = NULL;
760     sec->deref = always;
761     sec->group_attrib_is_dn = 1;
762
763     sec->frontpage_hack = 0;
764     sec->secure = 0;
765
766     sec->user_is_dn = 0;
767     sec->compare_dn_on_server = 0;
768
769     return sec;
770 }
771
772 /* 
773  * Use the ldap url parsing routines to break up the ldap url into
774  * host and port.
775  */
776 static const char *mod_auth_ldap_parse_url(cmd_parms *cmd, 
777                                     void *config,
778                                     const char *url)
779 {
780     int result;
781     apr_ldap_url_desc_t *urld;
782
783     mod_auth_ldap_config_t *sec = config;
784
785     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
786                  cmd->server, "[%d] auth_ldap url parse: `%s'", 
787                  getpid(), url);
788
789     result = apr_ldap_url_parse(url, &(urld));
790     if (result != LDAP_SUCCESS) {
791         switch (result) {
792         case LDAP_URL_ERR_NOTLDAP:
793             return "LDAP URL does not begin with ldap://";
794         case LDAP_URL_ERR_NODN:
795             return "LDAP URL does not have a DN";
796         case LDAP_URL_ERR_BADSCOPE:
797             return "LDAP URL has an invalid scope";
798         case LDAP_URL_ERR_MEM:
799             return "Out of memory parsing LDAP URL";
800         default:
801             return "Could not parse LDAP URL";
802         }
803     }
804     sec->url = apr_pstrdup(cmd->pool, url);
805
806     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
807                  cmd->server, "[%d] auth_ldap url parse: Host: %s", getpid(), urld->lud_host);
808     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
809                  cmd->server, "[%d] auth_ldap url parse: Port: %d", getpid(), urld->lud_port);
810     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
811                  cmd->server, "[%d] auth_ldap url parse: DN: %s", getpid(), urld->lud_dn);
812     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
813                  cmd->server, "[%d] auth_ldap url parse: attrib: %s", getpid(), urld->lud_attrs? urld->lud_attrs[0] : "(null)");
814     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
815                  cmd->server, "[%d] auth_ldap url parse: scope: %s", getpid(), 
816                  (urld->lud_scope == LDAP_SCOPE_SUBTREE? "subtree" : 
817                  urld->lud_scope == LDAP_SCOPE_BASE? "base" : 
818                  urld->lud_scope == LDAP_SCOPE_ONELEVEL? "onelevel" : "unknown"));
819     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0,
820                  cmd->server, "[%d] auth_ldap url parse: filter: %s", getpid(), urld->lud_filter);
821
822     /* Set all the values, or at least some sane defaults */
823     if (sec->host) {
824         char *p = apr_palloc(cmd->pool, strlen(sec->host) + strlen(urld->lud_host) + 2);
825         strcpy(p, urld->lud_host);
826         strcat(p, " ");
827         strcat(p, sec->host);
828         sec->host = p;
829     }
830     else {
831         sec->host = urld->lud_host? apr_pstrdup(cmd->pool, urld->lud_host) : "localhost";
832     }
833     sec->basedn = urld->lud_dn? apr_pstrdup(cmd->pool, urld->lud_dn) : "";
834     if (urld->lud_attrs && urld->lud_attrs[0]) {
835         int i = 1;
836         while (urld->lud_attrs[i]) {
837             i++;
838         }
839         sec->attributes = apr_pcalloc(cmd->pool, sizeof(char *) * (i+1));
840         i = 0;
841         while (urld->lud_attrs[i]) {
842             sec->attributes[i] = apr_pstrdup(cmd->pool, urld->lud_attrs[i]);
843             i++;
844         }
845         sec->attribute = sec->attributes[0];
846     }
847     else {
848         sec->attribute = "uid";
849     }
850
851     sec->scope = urld->lud_scope == LDAP_SCOPE_ONELEVEL ?
852         LDAP_SCOPE_ONELEVEL : LDAP_SCOPE_SUBTREE;
853
854     if (urld->lud_filter) {
855         if (urld->lud_filter[0] == '(') {
856             /* 
857              * Get rid of the surrounding parens; later on when generating the
858              * filter, they'll be put back.
859              */
860             sec->filter = apr_pstrdup(cmd->pool, urld->lud_filter+1);
861             sec->filter[strlen(sec->filter)-1] = '\0';
862         }
863         else {
864             sec->filter = apr_pstrdup(cmd->pool, urld->lud_filter);
865         }
866     }
867     else {
868         sec->filter = "objectclass=*";
869     }
870
871       /* "ldaps" indicates secure ldap connections desired
872       */
873     if (strncasecmp(url, "ldaps", 5) == 0)
874     {
875         sec->secure = 1;
876         sec->port = urld->lud_port? urld->lud_port : LDAPS_PORT;
877         ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, cmd->server,
878                      "LDAP: auth_ldap using SSL connections");
879     }
880     else
881     {
882         sec->secure = 0;
883         sec->port = urld->lud_port? urld->lud_port : LDAP_PORT;
884         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, 
885                      "LDAP: auth_ldap not using SSL connections");
886     }
887
888     sec->have_ldap_url = 1;
889     apr_ldap_free_urldesc(urld);
890     return NULL;
891 }
892
893 static const char *mod_auth_ldap_set_deref(cmd_parms *cmd, void *config, const char *arg)
894 {
895     mod_auth_ldap_config_t *sec = config;
896
897     if (strcmp(arg, "never") == 0 || strcasecmp(arg, "off") == 0) {
898         sec->deref = never;
899     }
900     else if (strcmp(arg, "searching") == 0) {
901         sec->deref = searching;
902     }
903     else if (strcmp(arg, "finding") == 0) {
904         sec->deref = finding;
905     }
906     else if (strcmp(arg, "always") == 0 || strcasecmp(arg, "on") == 0) {
907         sec->deref = always;
908     }
909     else {
910         return "Unrecognized value for AuthLDAPAliasDereference directive";
911     }
912     return NULL;
913 }
914
915 static const char *mod_auth_ldap_add_group_attribute(cmd_parms *cmd, void *config, const char *arg)
916 {
917     struct mod_auth_ldap_groupattr_entry_t *new;
918
919     mod_auth_ldap_config_t *sec = config;
920
921     if (sec->groupattr->nelts > GROUPATTR_MAX_ELTS)
922         return "Too many AuthLDAPGroupAttribute directives";
923
924     new = apr_array_push(sec->groupattr);
925     new->name = apr_pstrdup(cmd->pool, arg);
926   
927     return NULL;
928 }
929
930 static const char *set_charset_config(cmd_parms *cmd, void *config, const char *arg)
931 {
932     ap_set_module_config(cmd->server->module_config, &auth_ldap_module,
933                          (void *)arg);
934     return NULL;
935 }
936
937
938 command_rec mod_auth_ldap_cmds[] = {
939     AP_INIT_TAKE1("AuthLDAPURL", mod_auth_ldap_parse_url, NULL, OR_AUTHCFG, 
940                   "URL to define LDAP connection. This should be an RFC 2255 complaint\n"
941                   "URL of the form ldap://host[:port]/basedn[?attrib[?scope[?filter]]].\n"
942                   "<ul>\n"
943                   "<li>Host is the name of the LDAP server. Use a space separated list of hosts \n"
944                   "to specify redundant servers.\n"
945                   "<li>Port is optional, and specifies the port to connect to.\n"
946                   "<li>basedn specifies the base DN to start searches from\n"
947                   "<li>Attrib specifies what attribute to search for in the directory. If not "
948                   "provided, it defaults to <b>uid</b>.\n"
949                   "<li>Scope is the scope of the search, and can be either <b>sub</b> or "
950                   "<b>one</b>. If not provided, the default is <b>sub</b>.\n"
951                   "<li>Filter is a filter to use in the search. If not provided, "
952                   "defaults to <b>(objectClass=*)</b>.\n"
953                   "</ul>\n"
954                   "Searches are performed using the attribute and the filter combined. "
955                   "For example, assume that the\n"
956                   "LDAP URL is <b>ldap://ldap.airius.com/ou=People, o=Airius?uid?sub?(posixid=*)</b>. "
957                   "Searches will\n"
958                   "be done using the filter <b>(&((posixid=*))(uid=<i>username</i>))</b>, "
959                   "where <i>username</i>\n"
960                   "is the user name passed by the HTTP client. The search will be a subtree "
961                   "search on the branch <b>ou=People, o=Airius</b>."),
962
963     AP_INIT_TAKE1("AuthLDAPBindDN", ap_set_string_slot,
964                   (void *)APR_OFFSETOF(mod_auth_ldap_config_t, binddn), OR_AUTHCFG,
965                   "DN to use to bind to LDAP server. If not provided, will do an anonymous bind."),
966
967     AP_INIT_TAKE1("AuthLDAPBindPassword", ap_set_string_slot,
968                   (void *)APR_OFFSETOF(mod_auth_ldap_config_t, bindpw), OR_AUTHCFG,
969                   "Password to use to bind to LDAP server. If not provided, will do an anonymous bind."),
970
971     AP_INIT_FLAG("AuthLDAPRemoteUserIsDN", ap_set_flag_slot,
972                  (void *)APR_OFFSETOF(mod_auth_ldap_config_t, user_is_dn), OR_AUTHCFG,
973                  "Set to 'on' to set the REMOTE_USER environment variable to be the full "
974                  "DN of the remote user. By default, this is set to off, meaning that "
975                  "the REMOTE_USER variable will contain whatever value the remote user sent."),
976
977     AP_INIT_FLAG("AuthLDAPAuthoritative", ap_set_flag_slot,
978                  (void *)APR_OFFSETOF(mod_auth_ldap_config_t, auth_authoritative), OR_AUTHCFG,
979                  "Set to 'off' to allow access control to be passed along to lower modules if "
980                  "the UserID and/or group is not known to this module"),
981
982     AP_INIT_FLAG("AuthLDAPCompareDNOnServer", ap_set_flag_slot,
983                  (void *)APR_OFFSETOF(mod_auth_ldap_config_t, compare_dn_on_server), OR_AUTHCFG,
984                  "Set to 'on' to force auth_ldap to do DN compares (for the \"require dn\" "
985                  "directive) using the server, and set it 'off' to do the compares locally "
986                  "(at the expense of possible false matches). See the documentation for "
987                  "a complete description of this option."),
988
989     AP_INIT_ITERATE("AuthLDAPGroupAttribute", mod_auth_ldap_add_group_attribute, NULL, OR_AUTHCFG,
990                     "A list of attributes used to define group membership - defaults to "
991                     "member and uniquemember"),
992
993     AP_INIT_FLAG("AuthLDAPGroupAttributeIsDN", ap_set_flag_slot,
994                  (void *)APR_OFFSETOF(mod_auth_ldap_config_t, group_attrib_is_dn), OR_AUTHCFG,
995                  "If set to 'on', auth_ldap uses the DN that is retrieved from the server for"
996                  "subsequent group comparisons. If set to 'off', auth_ldap uses the string"
997                  "provided by the client directly. Defaults to 'on'."),
998
999     AP_INIT_TAKE1("AuthLDAPDereferenceAliases", mod_auth_ldap_set_deref, NULL, OR_AUTHCFG,
1000                   "Determines how aliases are handled during a search. Can bo one of the"
1001                   "values \"never\", \"searching\", \"finding\", or \"always\". "
1002                   "Defaults to always."),
1003
1004     AP_INIT_FLAG("AuthLDAPEnabled", ap_set_flag_slot,
1005                  (void *)APR_OFFSETOF(mod_auth_ldap_config_t, enabled), OR_AUTHCFG,
1006                  "Set to off to disable auth_ldap, even if it's been enabled in a higher tree"),
1007  
1008     AP_INIT_FLAG("AuthLDAPFrontPageHack", ap_set_flag_slot,
1009                  (void *)APR_OFFSETOF(mod_auth_ldap_config_t, frontpage_hack), OR_AUTHCFG,
1010                  "Set to 'on' to support Microsoft FrontPage"),
1011
1012     AP_INIT_TAKE1("AuthLDAPCharsetConfig", set_charset_config, NULL, RSRC_CONF,
1013                   "Character set conversion configuration file. If omitted, character set"
1014                   "conversion is disabled."),
1015
1016     {NULL}
1017 };
1018
1019 static int auth_ldap_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
1020 {
1021     ap_configfile_t *f;
1022     char l[MAX_STRING_LEN];
1023     const char *charset_confname = ap_get_module_config(s->module_config,
1024                                                       &auth_ldap_module);
1025     apr_status_t status;
1026     
1027     /*
1028     mod_auth_ldap_config_t *sec = (mod_auth_ldap_config_t *)
1029                                     ap_get_module_config(s->module_config, 
1030                                                          &auth_ldap_module);
1031
1032     if (sec->secure)
1033     {
1034         if (!util_ldap_ssl_supported(s))
1035         {
1036             ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, 
1037                      "LDAP: SSL connections (ldaps://) not supported by utilLDAP");
1038             return(!OK);
1039         }
1040     }
1041     */
1042
1043     /* make sure that mod_ldap (util_ldap) is loaded */
1044     if (ap_find_linked_module("util_ldap.c") == NULL) {
1045         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, s,
1046                      "Module mod_ldap missing. Mod_ldap (aka. util_ldap) "
1047                      "must be loaded in order for mod_auth_ldap to function properly");
1048         return HTTP_INTERNAL_SERVER_ERROR;
1049
1050     }
1051
1052     if (!charset_confname) {
1053         return OK;
1054     }
1055
1056     charset_confname = ap_server_root_relative(p, charset_confname);
1057     if (!charset_confname) {
1058         ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s,
1059                      "Invalid charset conversion config path %s", 
1060                      (const char *)ap_get_module_config(s->module_config,
1061                                                         &auth_ldap_module));
1062         return HTTP_INTERNAL_SERVER_ERROR;
1063     }
1064     if ((status = ap_pcfg_openfile(&f, ptemp, charset_confname)) 
1065                 != APR_SUCCESS) {
1066         ap_log_error(APLOG_MARK, APLOG_ERR, status, s,
1067                      "could not open charset conversion config file %s.", 
1068                      charset_confname);
1069         return HTTP_INTERNAL_SERVER_ERROR;
1070     }
1071
1072     charset_conversions = apr_hash_make(p);
1073
1074     while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {
1075         const char *ll = l;
1076         char *lang;
1077
1078         if (l[0] == '#') {
1079             continue;
1080         }
1081         lang = ap_getword_conf(p, &ll);
1082         ap_str_tolower(lang);
1083
1084         if (ll[0]) {
1085             char *charset = ap_getword_conf(p, &ll);
1086             apr_hash_set(charset_conversions, lang, APR_HASH_KEY_STRING, charset);
1087         }
1088     }
1089     ap_cfg_closefile(f);
1090     
1091     to_charset = derive_codepage_from_lang (p, "utf-8");
1092     if (to_charset == NULL) {
1093         ap_log_error(APLOG_MARK, APLOG_ERR, status, s,
1094                      "could not find the UTF-8 charset in the file %s.", 
1095                      charset_confname);
1096         return HTTP_INTERNAL_SERVER_ERROR;
1097     }
1098
1099     return OK;
1100 }
1101
1102 static void mod_auth_ldap_register_hooks(apr_pool_t *p)
1103 {
1104     ap_hook_post_config(auth_ldap_post_config,NULL,NULL,APR_HOOK_MIDDLE);
1105     ap_hook_check_user_id(mod_auth_ldap_check_user_id, NULL, NULL, APR_HOOK_MIDDLE);
1106     ap_hook_auth_checker(mod_auth_ldap_auth_checker, NULL, NULL, APR_HOOK_MIDDLE);
1107 }
1108
1109 module auth_ldap_module = {
1110    STANDARD20_MODULE_STUFF,
1111    mod_auth_ldap_create_dir_config,     /* dir config creater */
1112    NULL,                                /* dir merger --- default is to override */
1113    NULL,                                /* server config */
1114    NULL,                                /* merge server config */
1115    mod_auth_ldap_cmds,                  /* command table */
1116    mod_auth_ldap_register_hooks,        /* set up request processing hooks */
1117 };