]> granicus.if.org Git - apache/blob - modules/aaa/mod_auth_form.c
Add lots of unique tags to error log messages
[apache] / modules / aaa / mod_auth_form.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 #include "apr_strings.h"
18 #include "apr_md5.h"                /* for apr_password_validate */
19 #include "apr_lib.h"                /* for apr_isspace */
20 #include "apr_base64.h"             /* for apr_base64_decode et al */
21 #define APR_WANT_STRFUNC            /* for strcasecmp */
22 #include "apr_want.h"
23
24 #include "ap_config.h"
25 #include "httpd.h"
26 #include "http_config.h"
27 #include "http_core.h"
28 #include "http_log.h"
29 #include "http_protocol.h"
30 #include "http_request.h"
31 #include "ap_provider.h"
32 #include "util_md5.h"
33
34 #include "mod_auth.h"
35 #include "mod_session.h"
36 #include "mod_request.h"
37
38 #define FORM_LOGIN_HANDLER "form-login-handler"
39 #define FORM_LOGOUT_HANDLER "form-logout-handler"
40 #define FORM_REDIRECT_HANDLER "form-redirect-handler"
41 #define MOD_AUTH_FORM_HASH "site"
42
43 static int (*ap_session_load_fn) (request_rec * r, session_rec ** z) = NULL;
44 static apr_status_t (*ap_session_get_fn)(request_rec * r, session_rec * z,
45         const char *key, const char **value) = NULL;
46 static apr_status_t (*ap_session_set_fn)(request_rec * r, session_rec * z,
47         const char *key, const char *value) = NULL;
48 static void (*ap_request_insert_filter_fn) (request_rec * r) = NULL;
49 static void (*ap_request_remove_filter_fn) (request_rec * r) = NULL;
50
51 typedef struct {
52     authn_provider_list *providers;
53     char *dir;
54     int authoritative;
55     int authoritative_set;
56     const char *site;
57     int site_set;
58     const char *username;
59     int username_set;
60     const char *password;
61     int password_set;
62     apr_size_t form_size;
63     int form_size_set;
64     int fakebasicauth;
65     int fakebasicauth_set;
66     const char *location;
67     int location_set;
68     const char *method;
69     int method_set;
70     const char *mimetype;
71     int mimetype_set;
72     const char *body;
73     int body_set;
74     int disable_no_store;
75     int disable_no_store_set;
76     const char *loginsuccess;
77     int loginsuccess_set;
78     const char *loginrequired;
79     int loginrequired_set;
80     const char *logout;
81     int logout_set;
82 } auth_form_config_rec;
83
84 static void *create_auth_form_dir_config(apr_pool_t * p, char *d)
85 {
86     auth_form_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
87
88     conf->dir = d;
89     /* Any failures are fatal. */
90     conf->authoritative = 1;
91
92     /* form size defaults to 8k */
93     conf->form_size = HUGE_STRING_LEN;
94
95     /* default form field names */
96     conf->username = "httpd_username";
97     conf->password = "httpd_password";
98     conf->location = "httpd_location";
99     conf->method = "httpd_method";
100     conf->mimetype = "httpd_mimetype";
101     conf->body = "httpd_body";
102
103     return conf;
104 }
105
106 static void *merge_auth_form_dir_config(apr_pool_t * p, void *basev, void *addv)
107 {
108     auth_form_config_rec *new = (auth_form_config_rec *) apr_pcalloc(p, sizeof(auth_form_config_rec));
109     auth_form_config_rec *add = (auth_form_config_rec *) addv;
110     auth_form_config_rec *base = (auth_form_config_rec *) basev;
111
112     new->providers = !add->providers ? base->providers : add->providers;
113     new->authoritative = (add->authoritative_set == 0) ? base->authoritative : add->authoritative;
114     new->authoritative_set = add->authoritative_set || base->authoritative_set;
115     new->site = (add->site_set == 0) ? base->site : add->site;
116     new->site_set = add->site_set || base->site_set;
117     new->username = (add->username_set == 0) ? base->username : add->username;
118     new->username_set = add->username_set || base->username_set;
119     new->password = (add->password_set == 0) ? base->password : add->password;
120     new->password_set = add->password_set || base->password_set;
121     new->location = (add->location_set == 0) ? base->location : add->location;
122     new->location_set = add->location_set || base->location_set;
123     new->form_size = (add->form_size_set == 0) ? base->form_size : add->form_size;
124     new->form_size_set = add->form_size_set || base->form_size_set;
125     new->fakebasicauth = (add->fakebasicauth_set == 0) ? base->fakebasicauth : add->fakebasicauth;
126     new->fakebasicauth_set = add->fakebasicauth_set || base->fakebasicauth_set;
127     new->method = (add->method_set == 0) ? base->method : add->method;
128     new->method_set = add->method_set || base->method_set;
129     new->mimetype = (add->mimetype_set == 0) ? base->mimetype : add->mimetype;
130     new->mimetype_set = add->mimetype_set || base->mimetype_set;
131     new->body = (add->body_set == 0) ? base->body : add->body;
132     new->body_set = add->body_set || base->body_set;
133     new->disable_no_store = (add->disable_no_store_set == 0) ? base->disable_no_store : add->disable_no_store;
134     new->disable_no_store_set = add->disable_no_store_set || base->disable_no_store_set;
135     new->loginsuccess = (add->loginsuccess_set == 0) ? base->loginsuccess : add->loginsuccess;
136     new->loginsuccess_set = add->loginsuccess_set || base->loginsuccess_set;
137     new->loginrequired = (add->loginrequired_set == 0) ? base->loginrequired : add->loginrequired;
138     new->loginrequired_set = add->loginrequired_set || base->loginrequired_set;
139     new->logout = (add->logout_set == 0) ? base->logout : add->logout;
140     new->logout_set = add->logout_set || base->logout_set;
141
142     return new;
143 }
144
145 static const char *add_authn_provider(cmd_parms * cmd, void *config,
146                                            const char *arg)
147 {
148     auth_form_config_rec *conf = (auth_form_config_rec *) config;
149     authn_provider_list *newp;
150
151     newp = apr_pcalloc(cmd->pool, sizeof(authn_provider_list));
152     newp->provider_name = apr_pstrdup(cmd->pool, arg);
153
154     /* lookup and cache the actual provider now */
155     newp->provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
156                                         newp->provider_name,
157                                         AUTHN_PROVIDER_VERSION);
158
159     if (newp->provider == NULL) {
160         /*
161          * by the time they use it, the provider should be loaded and
162          * registered with us.
163          */
164         return apr_psprintf(cmd->pool,
165                             "Unknown Authn provider: %s",
166                             newp->provider_name);
167     }
168
169     if (!newp->provider->check_password) {
170         /* if it doesn't provide the appropriate function, reject it */
171         return apr_psprintf(cmd->pool,
172                             "The '%s' Authn provider doesn't support "
173                             "Form Authentication", newp->provider_name);
174     }
175
176     if (!ap_session_load_fn || !ap_session_get_fn || !ap_session_set_fn) {
177         ap_session_load_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_load);
178         ap_session_get_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_get);
179         ap_session_set_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_set);
180         if (!ap_session_load_fn || !ap_session_get_fn || !ap_session_set_fn) {
181             return "You must load mod_session to enable the mod_auth_form "
182                    "functions";
183         }
184     }
185
186     if (!ap_request_insert_filter_fn || !ap_request_remove_filter_fn) {
187         ap_request_insert_filter_fn = APR_RETRIEVE_OPTIONAL_FN(ap_request_insert_filter);
188         ap_request_remove_filter_fn = APR_RETRIEVE_OPTIONAL_FN(ap_request_remove_filter);
189         if (!ap_request_insert_filter_fn || !ap_request_remove_filter_fn) {
190             return "You must load mod_request to enable the mod_auth_form "
191                    "functions";
192         }
193     }
194
195     /* Add it to the list now. */
196     if (!conf->providers) {
197         conf->providers = newp;
198     }
199     else {
200         authn_provider_list *last = conf->providers;
201
202         while (last->next) {
203             last = last->next;
204         }
205         last->next = newp;
206     }
207
208     return NULL;
209 }
210
211 /**
212  * Sanity check a given string that it exists, is not empty,
213  * and does not contain special characters.
214  */
215 static const char *check_string(cmd_parms * cmd, const char *string)
216 {
217     if (!string || !*string || ap_strchr_c(string, '=') || ap_strchr_c(string, '&')) {
218         return apr_pstrcat(cmd->pool, cmd->directive->directive,
219                            " cannot be empty, or contain '=' or '&'.",
220                            NULL);
221     }
222     return NULL;
223 }
224
225 static const char *set_cookie_form_location(cmd_parms * cmd, void *config, const char *location)
226 {
227     auth_form_config_rec *conf = (auth_form_config_rec *) config;
228     conf->location = location;
229     conf->location_set = 1;
230     return check_string(cmd, location);
231 }
232
233 static const char *set_cookie_form_username(cmd_parms * cmd, void *config, const char *username)
234 {
235     auth_form_config_rec *conf = (auth_form_config_rec *) config;
236     conf->username = username;
237     conf->username_set = 1;
238     return check_string(cmd, username);
239 }
240
241 static const char *set_cookie_form_password(cmd_parms * cmd, void *config, const char *password)
242 {
243     auth_form_config_rec *conf = (auth_form_config_rec *) config;
244     conf->password = password;
245     conf->password_set = 1;
246     return check_string(cmd, password);
247 }
248
249 static const char *set_cookie_form_method(cmd_parms * cmd, void *config, const char *method)
250 {
251     auth_form_config_rec *conf = (auth_form_config_rec *) config;
252     conf->method = method;
253     conf->method_set = 1;
254     return check_string(cmd, method);
255 }
256
257 static const char *set_cookie_form_mimetype(cmd_parms * cmd, void *config, const char *mimetype)
258 {
259     auth_form_config_rec *conf = (auth_form_config_rec *) config;
260     conf->mimetype = mimetype;
261     conf->mimetype_set = 1;
262     return check_string(cmd, mimetype);
263 }
264
265 static const char *set_cookie_form_body(cmd_parms * cmd, void *config, const char *body)
266 {
267     auth_form_config_rec *conf = (auth_form_config_rec *) config;
268     conf->body = body;
269     conf->body_set = 1;
270     return check_string(cmd, body);
271 }
272
273 static const char *set_cookie_form_size(cmd_parms * cmd, void *config,
274                                              const char *arg)
275 {
276     auth_form_config_rec *conf = config;
277     apr_off_t size;
278
279     if (APR_SUCCESS != apr_strtoff(&size, arg, NULL, 10)
280         || size < 0 || size > APR_SIZE_MAX) {
281         return "AuthCookieFormSize must be a size in bytes, or zero.";
282     }
283     conf->form_size = (apr_size_t)size;
284     conf->form_size_set = 1;
285
286     return NULL;
287 }
288
289 static const char *set_login_required_location(cmd_parms * cmd, void *config, const char *loginrequired)
290 {
291     auth_form_config_rec *conf = (auth_form_config_rec *) config;
292     conf->loginrequired = loginrequired;
293     conf->loginrequired_set = 1;
294     return NULL;
295 }
296
297 static const char *set_login_success_location(cmd_parms * cmd, void *config, const char *loginsuccess)
298 {
299     auth_form_config_rec *conf = (auth_form_config_rec *) config;
300     conf->loginsuccess = loginsuccess;
301     conf->loginsuccess_set = 1;
302     return NULL;
303 }
304
305 static const char *set_logout_location(cmd_parms * cmd, void *config, const char *logout)
306 {
307     auth_form_config_rec *conf = (auth_form_config_rec *) config;
308     conf->logout = logout;
309     conf->logout_set = 1;
310     return NULL;
311 }
312
313 static const char *set_site_passphrase(cmd_parms * cmd, void *config, const char *site)
314 {
315     auth_form_config_rec *conf = (auth_form_config_rec *) config;
316     conf->site = site;
317     conf->site_set = 1;
318     return NULL;
319 }
320
321 static const char *set_authoritative(cmd_parms * cmd, void *config, int flag)
322 {
323     auth_form_config_rec *conf = (auth_form_config_rec *) config;
324     conf->authoritative = flag;
325     conf->authoritative_set = 1;
326     return NULL;
327 }
328
329 static const char *set_fake_basic_auth(cmd_parms * cmd, void *config, int flag)
330 {
331     auth_form_config_rec *conf = (auth_form_config_rec *) config;
332     conf->fakebasicauth = flag;
333     conf->fakebasicauth_set = 1;
334     return NULL;
335 }
336
337 static const char *set_disable_no_store(cmd_parms * cmd, void *config, int flag)
338 {
339     auth_form_config_rec *conf = (auth_form_config_rec *) config;
340     conf->disable_no_store = flag;
341     conf->disable_no_store_set = 1;
342     return NULL;
343 }
344
345 static const command_rec auth_form_cmds[] =
346 {
347     AP_INIT_ITERATE("AuthFormProvider", add_authn_provider, NULL, OR_AUTHCFG,
348                     "specify the auth providers for a directory or location"),
349     AP_INIT_TAKE1("AuthFormUsername", set_cookie_form_username, NULL, OR_AUTHCFG,
350                   "The field of the login form carrying the username"),
351     AP_INIT_TAKE1("AuthFormPassword", set_cookie_form_password, NULL, OR_AUTHCFG,
352                   "The field of the login form carrying the password"),
353     AP_INIT_TAKE1("AuthFormLocation", set_cookie_form_location, NULL, OR_AUTHCFG,
354                   "The field of the login form carrying the URL to redirect on "
355                   "successful login."),
356     AP_INIT_TAKE1("AuthFormMethod", set_cookie_form_method, NULL, OR_AUTHCFG,
357                   "The field of the login form carrying the original request method."),
358     AP_INIT_TAKE1("AuthFormMimetype", set_cookie_form_mimetype, NULL, OR_AUTHCFG,
359                   "The field of the login form carrying the original request mimetype."),
360     AP_INIT_TAKE1("AuthFormBody", set_cookie_form_body, NULL, OR_AUTHCFG,
361                   "The field of the login form carrying the urlencoded original request "
362                   "body."),
363     AP_INIT_TAKE1("AuthFormSize", set_cookie_form_size, NULL, ACCESS_CONF,
364                   "Maximum size of body parsed by the form parser"),
365     AP_INIT_TAKE1("AuthFormLoginRequiredLocation", set_login_required_location,
366                   NULL, OR_AUTHCFG,
367                   "If set, redirect the browser to this URL rather than "
368                   "return 401 Not Authorized."),
369     AP_INIT_TAKE1("AuthFormLoginSuccessLocation", set_login_success_location,
370                   NULL, OR_AUTHCFG,
371                   "If set, redirect the browser to this URL when a login "
372                   "processed by the login handler is successful."),
373     AP_INIT_TAKE1("AuthFormLogoutLocation", set_logout_location,
374                   NULL, OR_AUTHCFG,
375                   "The URL of the logout successful page. An attempt to access an "
376                   "URL handled by the handler " FORM_LOGOUT_HANDLER " will result "
377                   "in an redirect to this page after logout."),
378     AP_INIT_TAKE1("AuthFormSitePassphrase", set_site_passphrase,
379                   NULL, OR_AUTHCFG,
380                   "If set, use this passphrase to determine whether the user should "
381                   "be authenticated. Bypasses the user authentication check on "
382                   "every website hit, and is useful for high traffic sites."),
383     AP_INIT_FLAG("AuthFormAuthoritative", set_authoritative,
384                  NULL, OR_AUTHCFG,
385                  "Set to 'Off' to allow access control to be passed along to "
386                  "lower modules if the UserID is not known to this module"),
387     AP_INIT_FLAG("AuthFormFakeBasicAuth", set_fake_basic_auth,
388                  NULL, OR_AUTHCFG,
389                  "Set to 'On' to pass through authentication to the rest of the "
390                  "server as a basic authentication header."),
391     AP_INIT_FLAG("AuthFormDisableNoStore", set_disable_no_store,
392                  NULL, OR_AUTHCFG,
393                  "Set to 'on' to stop the sending of a Cache-Control no-store header with "
394                  "the login screen. This allows the browser to cache the credentials, but "
395                  "at the risk of it being possible for the login form to be resubmitted "
396                  "and revealed to the backend server through XSS. Use at own risk."),
397     {NULL}
398 };
399
400 module AP_MODULE_DECLARE_DATA auth_form_module;
401
402 static void note_cookie_auth_failure(request_rec * r)
403 {
404     auth_form_config_rec *conf = ap_get_module_config(r->per_dir_config,
405                                                       &auth_form_module);
406
407     if (conf->location && ap_strchr_c(conf->location, ':')) {
408         apr_table_setn(r->err_headers_out, "Location", conf->location);
409     }
410 }
411
412 static int hook_note_cookie_auth_failure(request_rec * r,
413                                          const char *auth_type)
414 {
415     if (strcasecmp(auth_type, "form"))
416         return DECLINED;
417
418     note_cookie_auth_failure(r);
419     return OK;
420 }
421
422 /**
423  * Set the auth username and password into the main request
424  * notes table.
425  */
426 static void set_notes_auth(request_rec * r,
427                                 const char *user, const char *pw,
428                                 const char *method, const char *mimetype)
429 {
430     apr_table_t *notes = NULL;
431     const char *authname;
432
433     /* find the main request */
434     while (r->main) {
435         r = r->main;
436     }
437     /* find the first redirect */
438     while (r->prev) {
439         r = r->prev;
440     }
441     notes = r->notes;
442
443     /* have we isolated the user and pw before? */
444     authname = ap_auth_name(r);
445     if (user) {
446         apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-user", NULL), user);
447     }
448     if (pw) {
449         apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-pw", NULL), pw);
450     }
451     if (method) {
452         apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-method", NULL), method);
453     }
454     if (mimetype) {
455         apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-mimetype", NULL), mimetype);
456     }
457
458 }
459
460 /**
461  * Get the auth username and password from the main request
462  * notes table, if present.
463  */
464 static void get_notes_auth(request_rec * r,
465                            const char **user, const char **pw,
466                            const char **method, const char **mimetype)
467 {
468     const char *authname;
469
470     /* find the main request */
471     while (r->main) {
472         r = r->main;
473     }
474     /* find the first redirect */
475     while (r->prev) {
476         r = r->prev;
477     }
478
479     /* have we isolated the user and pw before? */
480     authname = ap_auth_name(r);
481     if (user) {
482         *user = (char *) apr_table_get(r->notes, apr_pstrcat(r->pool, authname, "-user", NULL));
483     }
484     if (pw) {
485         *pw = (char *) apr_table_get(r->notes, apr_pstrcat(r->pool, authname, "-pw", NULL));
486     }
487     if (method) {
488         *method = (char *) apr_table_get(r->notes, apr_pstrcat(r->pool, authname, "-method", NULL));
489     }
490     if (mimetype) {
491         *mimetype = (char *) apr_table_get(r->notes, apr_pstrcat(r->pool, authname, "-mimetype", NULL));
492     }
493
494     ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, r,
495                   "from notes: user: %s, pw: %s, method: %s, mimetype: %s",
496                   user ? *user : "<null>", pw ? *pw : "<null>",
497                   method ? *method : "<null>", mimetype ? *mimetype : "<null>");
498
499 }
500
501 /**
502  * Set the auth username and password into the session.
503  *
504  * If either the username, or the password are NULL, the username
505  * and/or password will be removed from the session.
506  */
507 static apr_status_t set_session_auth(request_rec * r,
508                                      const char *user, const char *pw, const char *site)
509 {
510     const char *hash = NULL;
511     const char *authname = ap_auth_name(r);
512     session_rec *z = NULL;
513
514     if (site) {
515         hash = ap_md5(r->pool,
516                       (unsigned char *) apr_pstrcat(r->pool, user, ":", site, NULL));
517     }
518
519     ap_session_load_fn(r, &z);
520     ap_session_set_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_USER, NULL), user);
521     ap_session_set_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_PW, NULL), pw);
522     ap_session_set_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_AUTH_FORM_HASH, NULL), hash);
523
524     return APR_SUCCESS;
525
526 }
527
528 /**
529  * Get the auth username and password from the main request
530  * notes table, if present.
531  */
532 static apr_status_t get_session_auth(request_rec * r,
533                                      const char **user, const char **pw, const char **hash)
534 {
535     const char *authname = ap_auth_name(r);
536     session_rec *z = NULL;
537     ap_session_load_fn(r, &z);
538
539     if (user) {
540         ap_session_get_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_USER, NULL), user);
541     }
542     if (pw) {
543         ap_session_get_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_PW, NULL), pw);
544     }
545     if (hash) {
546         ap_session_get_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_AUTH_FORM_HASH, NULL), hash);
547     }
548
549     /* set the user, even though the user is unauthenticated at this point */
550     if (user && *user) {
551         r->user = (char *) *user;
552     }
553
554     ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
555                   "from session: " MOD_SESSION_USER ": %s, " MOD_SESSION_PW
556                   ": %s, " MOD_AUTH_FORM_HASH ": %s",
557                   user ? *user : "<null>", pw ? *pw : "<null>",
558                   hash ? *hash : "<null>");
559
560     return APR_SUCCESS;
561
562 }
563
564 /**
565  * Isolate the username and password in a POSTed form with the
566  * username in the "username" field, and the password in the
567  * "password" field.
568  *
569  * If either the username or the password is missing, this
570  * function will return HTTP_UNAUTHORIZED.
571  *
572  * The location field is considered optional, and will be returned
573  * if present.
574  */
575 static int get_form_auth(request_rec * r,
576                              const char *username,
577                              const char *password,
578                              const char *location,
579                              const char *method,
580                              const char *mimetype,
581                              const char *body,
582                              const char **sent_user,
583                              const char **sent_pw,
584                              const char **sent_loc,
585                              const char **sent_method,
586                              const char **sent_mimetype,
587                              apr_bucket_brigade **sent_body,
588                              auth_form_config_rec * conf)
589 {
590     /* sanity check - are we a POST request? */
591
592     /* find the username and password in the form */
593     apr_array_header_t *pairs = NULL;
594     apr_off_t len;
595     apr_size_t size;
596     int res;
597     char *buffer;
598
599     /* have we isolated the user and pw before? */
600     get_notes_auth(r, sent_user, sent_pw, sent_method, sent_mimetype);
601     if (*sent_user && *sent_pw) {
602         return OK;
603     }
604
605     res = ap_parse_form_data(r, NULL, &pairs, -1, conf->form_size);
606     if (res != OK) {
607         return res;
608     }
609     while (pairs && !apr_is_empty_array(pairs)) {
610         ap_form_pair_t *pair = (ap_form_pair_t *) apr_array_pop(pairs);
611         if (username && !strcmp(pair->name, username) && sent_user) {
612             apr_brigade_length(pair->value, 1, &len);
613             size = (apr_size_t) len;
614             buffer = apr_palloc(r->pool, size + 1);
615             apr_brigade_flatten(pair->value, buffer, &size);
616             buffer[len] = 0;
617             *sent_user = buffer;
618         }
619         else if (password && !strcmp(pair->name, password) && sent_pw) {
620             apr_brigade_length(pair->value, 1, &len);
621             size = (apr_size_t) len;
622             buffer = apr_palloc(r->pool, size + 1);
623             apr_brigade_flatten(pair->value, buffer, &size);
624             buffer[len] = 0;
625             *sent_pw = buffer;
626         }
627         else if (location && !strcmp(pair->name, location) && sent_loc) {
628             apr_brigade_length(pair->value, 1, &len);
629             size = (apr_size_t) len;
630             buffer = apr_palloc(r->pool, size + 1);
631             apr_brigade_flatten(pair->value, buffer, &size);
632             buffer[len] = 0;
633             *sent_loc = buffer;
634         }
635         else if (method && !strcmp(pair->name, method) && sent_method) {
636             apr_brigade_length(pair->value, 1, &len);
637             size = (apr_size_t) len;
638             buffer = apr_palloc(r->pool, size + 1);
639             apr_brigade_flatten(pair->value, buffer, &size);
640             buffer[len] = 0;
641             *sent_method = buffer;
642         }
643         else if (mimetype && !strcmp(pair->name, mimetype) && sent_mimetype) {
644             apr_brigade_length(pair->value, 1, &len);
645             size = (apr_size_t) len;
646             buffer = apr_palloc(r->pool, size + 1);
647             apr_brigade_flatten(pair->value, buffer, &size);
648             buffer[len] = 0;
649             *sent_mimetype = buffer;
650         }
651         else if (body && !strcmp(pair->name, body) && sent_body) {
652             *sent_body = pair->value;
653         }
654     }
655
656     /* set the user, even though the user is unauthenticated at this point */
657     if (*sent_user) {
658         r->user = (char *) *sent_user;
659     }
660
661     /* a missing username or missing password means auth denied */
662     if (!sent_user || !*sent_user || !sent_pw || !*sent_pw) {
663         return HTTP_UNAUTHORIZED;
664     }
665
666     /*
667      * save away the username, password, mimetype and method, so that they
668      * are available should the auth need to be run again.
669      */
670     set_notes_auth(r, *sent_user, *sent_pw, sent_method ? *sent_method : NULL,
671                    sent_mimetype ? *sent_mimetype : NULL);
672
673     return OK;
674 }
675
676 /* These functions return 0 if client is OK, and proper error status
677  * if not... either HTTP_UNAUTHORIZED, if we made a check, and it failed, or
678  * HTTP_INTERNAL_SERVER_ERROR, if things are so totally confused that we
679  * couldn't figure out how to tell if the client is authorized or not.
680  *
681  * If they return DECLINED, and all other modules also decline, that's
682  * treated by the server core as a configuration error, logged and
683  * reported as such.
684  */
685
686
687 /**
688  * Given a username and site passphrase hash from the session, determine
689  * whether the site passphrase is valid for this session.
690  *
691  * If the site passphrase is NULL, or if the sent_hash is NULL, this
692  * function returns DECLINED.
693  *
694  * If the site passphrase hash does not match the sent hash, this function
695  * returns AUTH_USER_NOT_FOUND.
696  *
697  * On success, returns OK.
698  */
699 static int check_site(request_rec * r, const char *site, const char *sent_user, const char *sent_hash)
700 {
701
702     if (site && sent_user && sent_hash) {
703         const char *hash = ap_md5(r->pool,
704                       (unsigned char *) apr_pstrcat(r->pool, sent_user, ":", site, NULL));
705
706         if (!strcmp(sent_hash, hash)) {
707             return OK;
708         }
709         else {
710             return AUTH_USER_NOT_FOUND;
711         }
712     }
713
714     return DECLINED;
715
716 }
717
718 /**
719  * Given a username and password (extracted externally from a cookie), run
720  * the authnz hooks to determine whether this request is authorized.
721  *
722  * Return an HTTP code.
723  */
724 static int check_authn(request_rec * r, const char *sent_user, const char *sent_pw)
725 {
726     authn_status auth_result;
727     authn_provider_list *current_provider;
728     auth_form_config_rec *conf = ap_get_module_config(r->per_dir_config,
729                                                       &auth_form_module);
730
731     current_provider = conf->providers;
732     do {
733         const authn_provider *provider;
734
735         /*
736          * For now, if a provider isn't set, we'll be nice and use the file
737          * provider.
738          */
739         if (!current_provider) {
740             provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
741                                           AUTHN_DEFAULT_PROVIDER,
742                                           AUTHN_PROVIDER_VERSION);
743
744             if (!provider || !provider->check_password) {
745                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01806)
746                               "no authn provider configured");
747                 auth_result = AUTH_GENERAL_ERROR;
748                 break;
749             }
750             apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, AUTHN_DEFAULT_PROVIDER);
751         }
752         else {
753             provider = current_provider->provider;
754             apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, current_provider->provider_name);
755         }
756
757         if (!sent_user || !sent_pw) {
758             auth_result = AUTH_USER_NOT_FOUND;
759             break;
760         }
761
762         auth_result = provider->check_password(r, sent_user, sent_pw);
763
764         apr_table_unset(r->notes, AUTHN_PROVIDER_NAME_NOTE);
765
766         /* Something occured. Stop checking. */
767         if (auth_result != AUTH_USER_NOT_FOUND) {
768             break;
769         }
770
771         /* If we're not really configured for providers, stop now. */
772         if (!conf->providers) {
773             break;
774         }
775
776         current_provider = current_provider->next;
777     } while (current_provider);
778
779     if (auth_result != AUTH_GRANTED) {
780         int return_code;
781
782         /* If we're not authoritative, then any error is ignored. */
783         if (!(conf->authoritative) && auth_result != AUTH_DENIED) {
784             return DECLINED;
785         }
786
787         switch (auth_result) {
788         case AUTH_DENIED:
789             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01807)
790                           "user '%s': authentication failure for \"%s\": "
791                           "password Mismatch",
792                           sent_user, r->uri);
793             return_code = HTTP_UNAUTHORIZED;
794             break;
795         case AUTH_USER_NOT_FOUND:
796             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01808)
797                           "user '%s' not found: %s", sent_user, r->uri);
798             return_code = HTTP_UNAUTHORIZED;
799             break;
800         case AUTH_GENERAL_ERROR:
801         default:
802             /*
803              * We'll assume that the module has already said what its error
804              * was in the logs.
805              */
806             return_code = HTTP_INTERNAL_SERVER_ERROR;
807             break;
808         }
809
810         /* If we're returning 403, tell them to try again. */
811         if (return_code == HTTP_UNAUTHORIZED) {
812             note_cookie_auth_failure(r);
813         }
814
815 /* TODO: Flag the user somehow as to the reason for the failure */
816
817         return return_code;
818     }
819
820     return OK;
821
822 }
823
824 /* fake the basic authentication header if configured to do so */
825 static void fake_basic_authentication(request_rec *r, auth_form_config_rec *conf,
826                                       const char *user, const char *pw)
827 {
828     if (conf->fakebasicauth) {
829         char *basic = apr_pstrcat(r->pool, user, ":", pw, NULL);
830         apr_size_t size = (apr_size_t) strlen(basic);
831         char *base64 = apr_palloc(r->pool,
832                                   apr_base64_encode_len(size + 1) * sizeof(char));
833         apr_base64_encode(base64, basic, size);
834         apr_table_setn(r->headers_in, "Authorization",
835                        apr_pstrcat(r->pool, "Basic ", base64, NULL));
836     }
837 }
838
839 /**
840  * Must we use form authentication? If so, extract the cookie and run
841  * the authnz hooks to determine if the login is valid.
842  *
843  * If the login is not valid, a 401 Not Authorized will be returned. It
844  * is up to the webmaster to ensure this screen displays a suitable login
845  * form to give the user the opportunity to log in.
846  */
847 static int authenticate_form_authn(request_rec * r)
848 {
849     auth_form_config_rec *conf = ap_get_module_config(r->per_dir_config,
850                                                       &auth_form_module);
851     const char *sent_user = NULL, *sent_pw = NULL, *sent_hash = NULL;
852     const char *sent_loc = NULL, *sent_method = "GET", *sent_mimetype = NULL;
853     const char *current_auth = NULL;
854     apr_status_t res;
855     int rv = HTTP_UNAUTHORIZED;
856
857     /* Are we configured to be Form auth? */
858     current_auth = ap_auth_type(r);
859     if (!current_auth || strcasecmp(current_auth, "form")) {
860         return DECLINED;
861     }
862
863     /*
864      * XSS security warning: using cookies to store private data only works
865      * when the administrator has full control over the source website. When
866      * in forward-proxy mode, websites are public by definition, and so can
867      * never be secure. Abort the auth attempt in this case.
868      */
869     if (PROXYREQ_PROXY == r->proxyreq) {
870         ap_log_rerror(APLOG_MARK, APLOG_ERR,
871                       0, r, APLOGNO(01809) "form auth cannot be used for proxy "
872                       "requests due to XSS risk, access denied: %s", r->uri);
873         return HTTP_INTERNAL_SERVER_ERROR;
874     }
875
876     /* We need an authentication realm. */
877     if (!ap_auth_name(r)) {
878         ap_log_rerror(APLOG_MARK, APLOG_ERR,
879                       0, r, APLOGNO(01810) "need AuthName: %s", r->uri);
880         return HTTP_INTERNAL_SERVER_ERROR;
881     }
882
883     r->ap_auth_type = (char *) current_auth;
884
885     /* try get the username and password from the notes, if present */
886     get_notes_auth(r, &sent_user, &sent_pw, &sent_method, &sent_mimetype);
887     if (!sent_user || !sent_pw || !*sent_user || !*sent_pw) {
888
889         /* otherwise try get the username and password from a session, if present */
890         res = get_session_auth(r, &sent_user, &sent_pw, &sent_hash);
891
892     }
893     else {
894         res = APR_SUCCESS;
895     }
896
897     /* first test whether the site passphrase matches */
898     if (APR_SUCCESS == res && sent_user && sent_hash && sent_pw) {
899         rv = check_site(r, conf->site, sent_user, sent_hash);
900         if (OK == rv) {
901             fake_basic_authentication(r, conf, sent_user, sent_pw);
902             return OK;
903         }
904     }
905
906     /* otherwise test for a normal password match */
907     if (APR_SUCCESS == res && sent_user && sent_pw) {
908         rv = check_authn(r, sent_user, sent_pw);
909         if (OK == rv) {
910             fake_basic_authentication(r, conf, sent_user, sent_pw);
911             return OK;
912         }
913     }
914
915     /*
916      * If we reach this point, the request should fail with access denied,
917      * except for one potential scenario:
918      *
919      * If the request is a POST, and the posted form contains user defined fields
920      * for a username and a password, and the username and password are correct,
921      * then return the response obtained by a GET to this URL.
922      *
923      * If an additional user defined location field is present in the form,
924      * instead of a GET of the current URL, redirect the browser to the new
925      * location.
926      *
927      * As a further option, if the user defined fields for the type of request,
928      * the mime type of the body of the request, and the body of the request
929      * itself are present, replace this request with a new request of the given
930      * type and with the given body.
931      *
932      * Otherwise access is denied.
933      *
934      * Reading the body requires some song and dance, because the input filters
935      * are not yet configured. To work around this problem, we create a
936      * subrequest and use that to create a sane filter stack we can read the
937      * form from.
938      *
939      * The main request is then capped with a kept_body input filter, which has
940      * the effect of guaranteeing the input stack can be safely read a second time.
941      *
942      */
943     if (HTTP_UNAUTHORIZED == rv && r->method_number == M_POST && ap_is_initial_req(r)) {
944         request_rec *rr;
945         apr_bucket_brigade *sent_body = NULL;
946
947         /* create a subrequest of our current uri */
948         rr = ap_sub_req_lookup_uri(r->uri, r, r->input_filters);
949         rr->headers_in = r->headers_in;
950
951         /* run the insert_filters hook on the subrequest to ensure a body read can
952          * be done properly.
953          */
954         ap_run_insert_filter(rr);
955
956         /* parse the form by reading the subrequest */
957         rv = get_form_auth(rr, conf->username, conf->password, conf->location,
958                            conf->method, conf->mimetype, conf->body,
959                            &sent_user, &sent_pw, &sent_loc, &sent_method,
960                            &sent_mimetype, &sent_body, conf);
961
962         /* make sure any user detected within the subrequest is saved back to
963          * the main request.
964          */
965         r->user = apr_pstrdup(r->pool, rr->user);
966
967         /* we cannot clean up rr at this point, as memory allocated to rr is
968          * referenced from the main request. It will be cleaned up when the
969          * main request is cleaned up.
970          */
971
972         /* insert the kept_body filter on the main request to guarantee the
973          * input filter stack cannot be read a second time, optionally inject
974          * a saved body if one was specified in the login form.
975          */
976         if (sent_body && sent_mimetype) {
977             apr_table_set(r->headers_in, "Content-Type", sent_mimetype);
978             r->kept_body = sent_body;
979         }
980         else {
981             r->kept_body = apr_brigade_create(r->pool, r->connection->bucket_alloc);
982         }
983         ap_request_insert_filter_fn(r);
984
985         /* did the form ask to change the method? if so, switch in the redirect handler
986          * to relaunch this request as the subrequest with the new method. If the
987          * form didn't specify a method, the default value GET will force a redirect.
988          */
989         if (sent_method && strcmp(r->method, sent_method)) {
990             r->handler = FORM_REDIRECT_HANDLER;
991         }
992
993         /* check the authn in the main request, based on the username found */
994         if (OK == rv) {
995             rv = check_authn(r, sent_user, sent_pw);
996             if (OK == rv) {
997                 fake_basic_authentication(r, conf, sent_user, sent_pw);
998                 set_session_auth(r, sent_user, sent_pw, conf->site);
999                 if (sent_loc) {
1000                     apr_table_set(r->headers_out, "Location", sent_loc);
1001                     return HTTP_MOVED_TEMPORARILY;
1002                 }
1003                 if (conf->loginsuccess) {
1004                     apr_table_set(r->headers_out, "Location", conf->loginsuccess);
1005                     return HTTP_MOVED_TEMPORARILY;
1006                 }
1007             }
1008         }
1009
1010     }
1011
1012     /*
1013      * did the admin prefer to be redirected to the login page on failure
1014      * instead?
1015      */
1016     if (HTTP_UNAUTHORIZED == rv && conf->loginrequired) {
1017         apr_table_set(r->headers_out, "Location", conf->loginrequired);
1018         return HTTP_MOVED_TEMPORARILY;
1019     }
1020
1021     /* did the user ask to be redirected on login success? */
1022     if (sent_loc) {
1023         apr_table_set(r->headers_out, "Location", sent_loc);
1024         rv = HTTP_MOVED_TEMPORARILY;
1025     }
1026
1027
1028     /*
1029      * potential security issue: if we return a login to the browser, we must
1030      * send a no-store to make sure a well behaved browser will not try and
1031      * send the login details a second time if the back button is pressed.
1032      *
1033      * if the user has full control over the backend, the
1034      * AuthCookieDisableNoStore can be used to turn this off.
1035      */
1036     if (HTTP_UNAUTHORIZED == rv && !conf->disable_no_store) {
1037         apr_table_addn(r->headers_out, "Cache-Control", "no-store");
1038         apr_table_addn(r->err_headers_out, "Cache-Control", "no-store");
1039     }
1040
1041     return rv;
1042
1043 }
1044
1045 /**
1046  * Handle a login attempt.
1047  *
1048  * If the login session is either missing or form authnz is unsuccessful, a
1049  * 401 Not Authorized will be returned to the browser. The webmaster
1050  * is expected to insert a login form into the 401 Not Authorized
1051  * error screen.
1052  *
1053  * If the webmaster wishes, they can point the form submission at this
1054  * handler, which will redirect the user to the correct page on success.
1055  * On failure, the 401 Not Authorized error screen will be redisplayed,
1056  * where the login attempt can be repeated.
1057  *
1058  */
1059 static int authenticate_form_login_handler(request_rec * r)
1060 {
1061     auth_form_config_rec *conf;
1062
1063     const char *sent_user = NULL, *sent_pw = NULL, *sent_loc = NULL;
1064     int rv;
1065
1066     if (strcmp(r->handler, FORM_LOGIN_HANDLER)) {
1067         return DECLINED;
1068     }
1069
1070     if (r->method_number != M_POST) {
1071         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01811)
1072           "the " FORM_LOGIN_HANDLER " only supports the POST method for %s",
1073                       r->uri);
1074         return HTTP_METHOD_NOT_ALLOWED;
1075     }
1076
1077     conf = ap_get_module_config(r->per_dir_config, &auth_form_module);
1078
1079     rv = get_form_auth(r, conf->username, conf->password, conf->location,
1080                        NULL, NULL, NULL,
1081                        &sent_user, &sent_pw, &sent_loc,
1082                        NULL, NULL, NULL, conf);
1083     if (OK == rv) {
1084         rv = check_authn(r, sent_user, sent_pw);
1085         if (OK == rv) {
1086             set_session_auth(r, sent_user, sent_pw, conf->site);
1087             if (sent_loc) {
1088                 apr_table_set(r->headers_out, "Location", sent_loc);
1089                 return HTTP_MOVED_TEMPORARILY;
1090             }
1091             if (conf->loginsuccess) {
1092                 apr_table_set(r->headers_out, "Location", conf->loginsuccess);
1093                 return HTTP_MOVED_TEMPORARILY;
1094             }
1095             return HTTP_OK;
1096         }
1097     }
1098
1099     /* did we prefer to be redirected to the login page on failure instead? */
1100     if (HTTP_UNAUTHORIZED == rv && conf->loginrequired) {
1101         apr_table_set(r->headers_out, "Location", conf->loginrequired);
1102         return HTTP_MOVED_TEMPORARILY;
1103     }
1104
1105     return rv;
1106
1107 }
1108
1109 /**
1110  * Handle a logout attempt.
1111  *
1112  * If an attempt is made to access this URL, any username and password
1113  * embedded in the session is deleted.
1114  *
1115  * This has the effect of logging the person out.
1116  *
1117  * If a logout URI has been specified, this function will create an
1118  * internal redirect to this page.
1119  */
1120 static int authenticate_form_logout_handler(request_rec * r)
1121 {
1122     auth_form_config_rec *conf;
1123
1124     if (strcmp(r->handler, FORM_LOGOUT_HANDLER)) {
1125         return DECLINED;
1126     }
1127
1128     conf = ap_get_module_config(r->per_dir_config, &auth_form_module);
1129
1130     /* remove the username and password, effectively logging the user out */
1131     set_session_auth(r, NULL, NULL, NULL);
1132
1133     /*
1134      * make sure the logout page is never cached - otherwise the logout won't
1135      * work!
1136      */
1137     apr_table_addn(r->headers_out, "Cache-Control", "no-store");
1138     apr_table_addn(r->err_headers_out, "Cache-Control", "no-store");
1139
1140     /* if set, internal redirect to the logout page */
1141     if (conf->logout) {
1142         apr_table_addn(r->headers_out, "Location", conf->logout);
1143         return HTTP_TEMPORARY_REDIRECT;
1144     }
1145
1146     return HTTP_OK;
1147
1148 }
1149
1150 /**
1151  * Handle a redirect attempt.
1152  *
1153  * If during a form login, the method, mimetype and request body are
1154  * specified, this handler will ensure that this request is included
1155  * as an internal redirect.
1156  *
1157  */
1158 static int authenticate_form_redirect_handler(request_rec * r)
1159 {
1160
1161     request_rec *rr = NULL;
1162     const char *sent_method = NULL, *sent_mimetype = NULL;
1163
1164     if (strcmp(r->handler, FORM_REDIRECT_HANDLER)) {
1165         return DECLINED;
1166     }
1167
1168     /* get the method and mimetype from the notes */
1169     get_notes_auth(r, NULL, NULL, &sent_method, &sent_mimetype);
1170
1171     if (r->kept_body && sent_method && sent_mimetype) {
1172
1173         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01812)
1174           "internal redirect to method '%s' and body mimetype '%s' for the "
1175                       "uri: %s", sent_method, sent_mimetype, r->uri);
1176
1177         rr = ap_sub_req_method_uri(sent_method, r->uri, r, r->output_filters);
1178         r->status = ap_run_sub_req(rr);
1179
1180     }
1181     else {
1182         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01813)
1183         "internal redirect requested but one or all of method, mimetype or "
1184                       "body are NULL: %s", r->uri);
1185         return HTTP_INTERNAL_SERVER_ERROR;
1186     }
1187
1188     /* return the underlying error, or OK on success */
1189     return r->status == HTTP_OK || r->status == OK ? OK : r->status;
1190
1191 }
1192
1193 static void register_hooks(apr_pool_t * p)
1194 {
1195 #if AP_MODULE_MAGIC_AT_LEAST(20080403,1)
1196     ap_hook_check_authn(authenticate_form_authn, NULL, NULL, APR_HOOK_MIDDLE,
1197                         AP_AUTH_INTERNAL_PER_CONF);
1198 #else
1199     ap_hook_check_user_id(authenticate_form_authn, NULL, NULL, APR_HOOK_MIDDLE);
1200 #endif
1201     ap_hook_handler(authenticate_form_login_handler, NULL, NULL, APR_HOOK_MIDDLE);
1202     ap_hook_handler(authenticate_form_logout_handler, NULL, NULL, APR_HOOK_MIDDLE);
1203     ap_hook_handler(authenticate_form_redirect_handler, NULL, NULL, APR_HOOK_MIDDLE);
1204
1205     ap_hook_note_auth_failure(hook_note_cookie_auth_failure, NULL, NULL,
1206                               APR_HOOK_MIDDLE);
1207 }
1208
1209 AP_DECLARE_MODULE(auth_form) =
1210 {
1211     STANDARD20_MODULE_STUFF,
1212     create_auth_form_dir_config, /* dir config creater */
1213     merge_auth_form_dir_config,  /* dir merger --- default is to override */
1214     NULL,                        /* server config */
1215     NULL,                        /* merge server config */
1216     auth_form_cmds,              /* command apr_table_t */
1217     register_hooks               /* register hooks */
1218 };