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_lib.h"                /* for apr_isspace */
19#include "apr_base64.h"             /* for apr_base64_decode et al */
20#define APR_WANT_STRFUNC            /* for strcasecmp */
21#include "apr_want.h"
22
23#include "ap_config.h"
24#include "httpd.h"
25#include "http_config.h"
26#include "http_core.h"
27#include "http_log.h"
28#include "http_protocol.h"
29#include "http_request.h"
30#include "ap_provider.h"
31#include "util_md5.h"
32#include "ap_expr.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
43static int (*ap_session_load_fn) (request_rec * r, session_rec ** z) = NULL;
44static apr_status_t (*ap_session_get_fn)(request_rec * r, session_rec * z,
45        const char *key, const char **value) = NULL;
46static apr_status_t (*ap_session_set_fn)(request_rec * r, session_rec * z,
47        const char *key, const char *value) = NULL;
48static void (*ap_request_insert_filter_fn) (request_rec * r) = NULL;
49static void (*ap_request_remove_filter_fn) (request_rec * r) = NULL;
50
51typedef 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    ap_expr_info_t *loginsuccess;
77    int loginsuccess_set;
78    ap_expr_info_t *loginrequired;
79    int loginrequired_set;
80    ap_expr_info_t *logout;
81    int logout_set;
82} auth_form_config_rec;
83
84static 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
106static 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
145static 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 = 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 */
215static 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
225static 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
233static 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
241static 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
249static 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
257static 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
265static 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
273static 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
289static 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    const char *err;
293
294    conf->loginrequired = ap_expr_parse_cmd(cmd, loginrequired, AP_EXPR_FLAG_STRING_RESULT,
295                                        &err, NULL);
296    if (err) {
297        return apr_psprintf(cmd->pool,
298                            "Could not parse login required expression '%s': %s",
299                            loginrequired, err);
300    }
301    conf->loginrequired_set = 1;
302
303    return NULL;
304}
305
306static const char *set_login_success_location(cmd_parms * cmd, void *config, const char *loginsuccess)
307{
308    auth_form_config_rec *conf = (auth_form_config_rec *) config;
309    const char *err;
310
311    conf->loginsuccess = ap_expr_parse_cmd(cmd, loginsuccess, AP_EXPR_FLAG_STRING_RESULT,
312                                        &err, NULL);
313    if (err) {
314        return apr_psprintf(cmd->pool,
315                            "Could not parse login success expression '%s': %s",
316                            loginsuccess, err);
317    }
318    conf->loginsuccess_set = 1;
319
320    return NULL;
321}
322
323static const char *set_logout_location(cmd_parms * cmd, void *config, const char *logout)
324{
325    auth_form_config_rec *conf = (auth_form_config_rec *) config;
326    const char *err;
327
328    conf->logout = ap_expr_parse_cmd(cmd, logout, AP_EXPR_FLAG_STRING_RESULT,
329                                        &err, NULL);
330    if (err) {
331        return apr_psprintf(cmd->pool,
332                            "Could not parse logout required expression '%s': %s",
333                            logout, err);
334    }
335    conf->logout_set = 1;
336
337    return NULL;
338}
339
340static const char *set_site_passphrase(cmd_parms * cmd, void *config, const char *site)
341{
342    auth_form_config_rec *conf = (auth_form_config_rec *) config;
343    conf->site = site;
344    conf->site_set = 1;
345    return NULL;
346}
347
348static const char *set_authoritative(cmd_parms * cmd, void *config, int flag)
349{
350    auth_form_config_rec *conf = (auth_form_config_rec *) config;
351    conf->authoritative = flag;
352    conf->authoritative_set = 1;
353    return NULL;
354}
355
356static const char *set_fake_basic_auth(cmd_parms * cmd, void *config, int flag)
357{
358    auth_form_config_rec *conf = (auth_form_config_rec *) config;
359    conf->fakebasicauth = flag;
360    conf->fakebasicauth_set = 1;
361    return NULL;
362}
363
364static const char *set_disable_no_store(cmd_parms * cmd, void *config, int flag)
365{
366    auth_form_config_rec *conf = (auth_form_config_rec *) config;
367    conf->disable_no_store = flag;
368    conf->disable_no_store_set = 1;
369    return NULL;
370}
371
372static const command_rec auth_form_cmds[] =
373{
374    AP_INIT_ITERATE("AuthFormProvider", add_authn_provider, NULL, OR_AUTHCFG,
375                    "specify the auth providers for a directory or location"),
376    AP_INIT_TAKE1("AuthFormUsername", set_cookie_form_username, NULL, OR_AUTHCFG,
377                  "The field of the login form carrying the username"),
378    AP_INIT_TAKE1("AuthFormPassword", set_cookie_form_password, NULL, OR_AUTHCFG,
379                  "The field of the login form carrying the password"),
380    AP_INIT_TAKE1("AuthFormLocation", set_cookie_form_location, NULL, OR_AUTHCFG,
381                  "The field of the login form carrying the URL to redirect on "
382                  "successful login."),
383    AP_INIT_TAKE1("AuthFormMethod", set_cookie_form_method, NULL, OR_AUTHCFG,
384                  "The field of the login form carrying the original request method."),
385    AP_INIT_TAKE1("AuthFormMimetype", set_cookie_form_mimetype, NULL, OR_AUTHCFG,
386                  "The field of the login form carrying the original request mimetype."),
387    AP_INIT_TAKE1("AuthFormBody", set_cookie_form_body, NULL, OR_AUTHCFG,
388                  "The field of the login form carrying the urlencoded original request "
389                  "body."),
390    AP_INIT_TAKE1("AuthFormSize", set_cookie_form_size, NULL, ACCESS_CONF,
391                  "Maximum size of body parsed by the form parser"),
392    AP_INIT_TAKE1("AuthFormLoginRequiredLocation", set_login_required_location,
393                  NULL, OR_AUTHCFG,
394                  "If set, redirect the browser to this URL rather than "
395                  "return 401 Not Authorized."),
396    AP_INIT_TAKE1("AuthFormLoginSuccessLocation", set_login_success_location,
397                  NULL, OR_AUTHCFG,
398                  "If set, redirect the browser to this URL when a login "
399                  "processed by the login handler is successful."),
400    AP_INIT_TAKE1("AuthFormLogoutLocation", set_logout_location,
401                  NULL, OR_AUTHCFG,
402                  "The URL of the logout successful page. An attempt to access an "
403                  "URL handled by the handler " FORM_LOGOUT_HANDLER " will result "
404                  "in an redirect to this page after logout."),
405    AP_INIT_TAKE1("AuthFormSitePassphrase", set_site_passphrase,
406                  NULL, OR_AUTHCFG,
407                  "If set, use this passphrase to determine whether the user should "
408                  "be authenticated. Bypasses the user authentication check on "
409                  "every website hit, and is useful for high traffic sites."),
410    AP_INIT_FLAG("AuthFormAuthoritative", set_authoritative,
411                 NULL, OR_AUTHCFG,
412                 "Set to 'Off' to allow access control to be passed along to "
413                 "lower modules if the UserID is not known to this module"),
414    AP_INIT_FLAG("AuthFormFakeBasicAuth", set_fake_basic_auth,
415                 NULL, OR_AUTHCFG,
416                 "Set to 'On' to pass through authentication to the rest of the "
417                 "server as a basic authentication header."),
418    AP_INIT_FLAG("AuthFormDisableNoStore", set_disable_no_store,
419                 NULL, OR_AUTHCFG,
420                 "Set to 'on' to stop the sending of a Cache-Control no-store header with "
421                 "the login screen. This allows the browser to cache the credentials, but "
422                 "at the risk of it being possible for the login form to be resubmitted "
423                 "and revealed to the backend server through XSS. Use at own risk."),
424    {NULL}
425};
426
427module AP_MODULE_DECLARE_DATA auth_form_module;
428
429static void note_cookie_auth_failure(request_rec * r)
430{
431    auth_form_config_rec *conf = ap_get_module_config(r->per_dir_config,
432                                                      &auth_form_module);
433
434    if (conf->location && ap_strchr_c(conf->location, ':')) {
435        apr_table_setn(r->err_headers_out, "Location", conf->location);
436    }
437}
438
439static int hook_note_cookie_auth_failure(request_rec * r,
440                                         const char *auth_type)
441{
442    if (strcasecmp(auth_type, "form"))
443        return DECLINED;
444
445    note_cookie_auth_failure(r);
446    return OK;
447}
448
449/**
450 * Set the auth username and password into the main request
451 * notes table.
452 */
453static void set_notes_auth(request_rec * r,
454                                const char *user, const char *pw,
455                                const char *method, const char *mimetype)
456{
457    apr_table_t *notes = NULL;
458    const char *authname;
459
460    /* find the main request */
461    while (r->main) {
462        r = r->main;
463    }
464    /* find the first redirect */
465    while (r->prev) {
466        r = r->prev;
467    }
468    notes = r->notes;
469
470    /* have we isolated the user and pw before? */
471    authname = ap_auth_name(r);
472    if (user) {
473        apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-user", NULL), user);
474    }
475    if (pw) {
476        apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-pw", NULL), pw);
477    }
478    if (method) {
479        apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-method", NULL), method);
480    }
481    if (mimetype) {
482        apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-mimetype", NULL), mimetype);
483    }
484
485}
486
487/**
488 * Get the auth username and password from the main request
489 * notes table, if present.
490 */
491static void get_notes_auth(request_rec *r,
492                           const char **user, const char **pw,
493                           const char **method, const char **mimetype)
494{
495    const char *authname;
496    request_rec *m = r;
497
498    /* find the main request */
499    while (m->main) {
500        m = m->main;
501    }
502    /* find the first redirect */
503    while (m->prev) {
504        m = m->prev;
505    }
506
507    /* have we isolated the user and pw before? */
508    authname = ap_auth_name(m);
509    if (user) {
510        *user = (char *) apr_table_get(m->notes, apr_pstrcat(m->pool, authname, "-user", NULL));
511    }
512    if (pw) {
513        *pw = (char *) apr_table_get(m->notes, apr_pstrcat(m->pool, authname, "-pw", NULL));
514    }
515    if (method) {
516        *method = (char *) apr_table_get(m->notes, apr_pstrcat(m->pool, authname, "-method", NULL));
517    }
518    if (mimetype) {
519        *mimetype = (char *) apr_table_get(m->notes, apr_pstrcat(m->pool, authname, "-mimetype", NULL));
520    }
521
522    /* set the user, even though the user is unauthenticated at this point */
523    if (user && *user) {
524        r->user = (char *) *user;
525    }
526
527    ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, r,
528                  "from notes: user: %s, pw: %s, method: %s, mimetype: %s",
529                  user ? *user : "<null>", pw ? *pw : "<null>",
530                  method ? *method : "<null>", mimetype ? *mimetype : "<null>");
531
532}
533
534/**
535 * Set the auth username and password into the session.
536 *
537 * If either the username, or the password are NULL, the username
538 * and/or password will be removed from the session.
539 */
540static apr_status_t set_session_auth(request_rec * r,
541                                     const char *user, const char *pw, const char *site)
542{
543    const char *hash = NULL;
544    const char *authname = ap_auth_name(r);
545    session_rec *z = NULL;
546
547    if (site) {
548        hash = ap_md5(r->pool,
549                      (unsigned char *) apr_pstrcat(r->pool, user, ":", site, NULL));
550    }
551
552    ap_session_load_fn(r, &z);
553    ap_session_set_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_USER, NULL), user);
554    ap_session_set_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_PW, NULL), pw);
555    ap_session_set_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_AUTH_FORM_HASH, NULL), hash);
556
557    return APR_SUCCESS;
558
559}
560
561/**
562 * Get the auth username and password from the main request
563 * notes table, if present.
564 */
565static apr_status_t get_session_auth(request_rec * r,
566                                     const char **user, const char **pw, const char **hash)
567{
568    const char *authname = ap_auth_name(r);
569    session_rec *z = NULL;
570    ap_session_load_fn(r, &z);
571
572    if (user) {
573        ap_session_get_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_USER, NULL), user);
574    }
575    if (pw) {
576        ap_session_get_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_PW, NULL), pw);
577    }
578    if (hash) {
579        ap_session_get_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_AUTH_FORM_HASH, NULL), hash);
580    }
581
582    /* set the user, even though the user is unauthenticated at this point */
583    if (user && *user) {
584        r->user = (char *) *user;
585    }
586
587    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
588                  "from session: " MOD_SESSION_USER ": %s, " MOD_SESSION_PW
589                  ": %s, " MOD_AUTH_FORM_HASH ": %s",
590                  user ? *user : "<null>", pw ? *pw : "<null>",
591                  hash ? *hash : "<null>");
592
593    return APR_SUCCESS;
594
595}
596
597/**
598 * Isolate the username and password in a POSTed form with the
599 * username in the "username" field, and the password in the
600 * "password" field.
601 *
602 * If either the username or the password is missing, this
603 * function will return HTTP_UNAUTHORIZED.
604 *
605 * The location field is considered optional, and will be returned
606 * if present.
607 */
608static int get_form_auth(request_rec * r,
609                             const char *username,
610                             const char *password,
611                             const char *location,
612                             const char *method,
613                             const char *mimetype,
614                             const char *body,
615                             const char **sent_user,
616                             const char **sent_pw,
617                             const char **sent_loc,
618                             const char **sent_method,
619                             const char **sent_mimetype,
620                             apr_bucket_brigade **sent_body,
621                             auth_form_config_rec * conf)
622{
623    /* sanity check - are we a POST request? */
624
625    /* find the username and password in the form */
626    apr_array_header_t *pairs = NULL;
627    apr_off_t len;
628    apr_size_t size;
629    int res;
630    char *buffer;
631
632    /* have we isolated the user and pw before? */
633    get_notes_auth(r, sent_user, sent_pw, sent_method, sent_mimetype);
634    if (*sent_user && *sent_pw) {
635        return OK;
636    }
637
638    res = ap_parse_form_data(r, NULL, &pairs, -1, conf->form_size);
639    if (res != OK) {
640        return res;
641    }
642    while (pairs && !apr_is_empty_array(pairs)) {
643        ap_form_pair_t *pair = (ap_form_pair_t *) apr_array_pop(pairs);
644        if (username && !strcmp(pair->name, username) && sent_user) {
645            apr_brigade_length(pair->value, 1, &len);
646            size = (apr_size_t) len;
647            buffer = apr_palloc(r->pool, size + 1);
648            apr_brigade_flatten(pair->value, buffer, &size);
649            buffer[len] = 0;
650            *sent_user = buffer;
651        }
652        else if (password && !strcmp(pair->name, password) && sent_pw) {
653            apr_brigade_length(pair->value, 1, &len);
654            size = (apr_size_t) len;
655            buffer = apr_palloc(r->pool, size + 1);
656            apr_brigade_flatten(pair->value, buffer, &size);
657            buffer[len] = 0;
658            *sent_pw = buffer;
659        }
660        else if (location && !strcmp(pair->name, location) && sent_loc) {
661            apr_brigade_length(pair->value, 1, &len);
662            size = (apr_size_t) len;
663            buffer = apr_palloc(r->pool, size + 1);
664            apr_brigade_flatten(pair->value, buffer, &size);
665            buffer[len] = 0;
666            *sent_loc = buffer;
667        }
668        else if (method && !strcmp(pair->name, method) && sent_method) {
669            apr_brigade_length(pair->value, 1, &len);
670            size = (apr_size_t) len;
671            buffer = apr_palloc(r->pool, size + 1);
672            apr_brigade_flatten(pair->value, buffer, &size);
673            buffer[len] = 0;
674            *sent_method = buffer;
675        }
676        else if (mimetype && !strcmp(pair->name, mimetype) && sent_mimetype) {
677            apr_brigade_length(pair->value, 1, &len);
678            size = (apr_size_t) len;
679            buffer = apr_palloc(r->pool, size + 1);
680            apr_brigade_flatten(pair->value, buffer, &size);
681            buffer[len] = 0;
682            *sent_mimetype = buffer;
683        }
684        else if (body && !strcmp(pair->name, body) && sent_body) {
685            *sent_body = pair->value;
686        }
687    }
688
689    /* set the user, even though the user is unauthenticated at this point */
690    if (*sent_user) {
691        r->user = (char *) *sent_user;
692    }
693
694    /* a missing username or missing password means auth denied */
695    if (!sent_user || !*sent_user || !sent_pw || !*sent_pw) {
696        return HTTP_UNAUTHORIZED;
697    }
698
699    /*
700     * save away the username, password, mimetype and method, so that they
701     * are available should the auth need to be run again.
702     */
703    set_notes_auth(r, *sent_user, *sent_pw, sent_method ? *sent_method : NULL,
704                   sent_mimetype ? *sent_mimetype : NULL);
705
706    return OK;
707}
708
709/* These functions return 0 if client is OK, and proper error status
710 * if not... either HTTP_UNAUTHORIZED, if we made a check, and it failed, or
711 * HTTP_INTERNAL_SERVER_ERROR, if things are so totally confused that we
712 * couldn't figure out how to tell if the client is authorized or not.
713 *
714 * If they return DECLINED, and all other modules also decline, that's
715 * treated by the server core as a configuration error, logged and
716 * reported as such.
717 */
718
719
720/**
721 * Given a username and site passphrase hash from the session, determine
722 * whether the site passphrase is valid for this session.
723 *
724 * If the site passphrase is NULL, or if the sent_hash is NULL, this
725 * function returns DECLINED.
726 *
727 * If the site passphrase hash does not match the sent hash, this function
728 * returns AUTH_USER_NOT_FOUND.
729 *
730 * On success, returns OK.
731 */
732static int check_site(request_rec * r, const char *site, const char *sent_user, const char *sent_hash)
733{
734
735    if (site && sent_user && sent_hash) {
736        const char *hash = ap_md5(r->pool,
737                      (unsigned char *) apr_pstrcat(r->pool, sent_user, ":", site, NULL));
738
739        if (!strcmp(sent_hash, hash)) {
740            return OK;
741        }
742        else {
743            return AUTH_USER_NOT_FOUND;
744        }
745    }
746
747    return DECLINED;
748
749}
750
751/**
752 * Given a username and password (extracted externally from a cookie), run
753 * the authnz hooks to determine whether this request is authorized.
754 *
755 * Return an HTTP code.
756 */
757static int check_authn(request_rec * r, const char *sent_user, const char *sent_pw)
758{
759    authn_status auth_result;
760    authn_provider_list *current_provider;
761    auth_form_config_rec *conf = ap_get_module_config(r->per_dir_config,
762                                                      &auth_form_module);
763
764    current_provider = conf->providers;
765    do {
766        const authn_provider *provider;
767
768        /*
769         * For now, if a provider isn't set, we'll be nice and use the file
770         * provider.
771         */
772        if (!current_provider) {
773            provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
774                                          AUTHN_DEFAULT_PROVIDER,
775                                          AUTHN_PROVIDER_VERSION);
776
777            if (!provider || !provider->check_password) {
778                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01806)
779                              "no authn provider configured");
780                auth_result = AUTH_GENERAL_ERROR;
781                break;
782            }
783            apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, AUTHN_DEFAULT_PROVIDER);
784        }
785        else {
786            provider = current_provider->provider;
787            apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, current_provider->provider_name);
788        }
789
790        if (!sent_user || !sent_pw) {
791            auth_result = AUTH_USER_NOT_FOUND;
792            break;
793        }
794
795        auth_result = provider->check_password(r, sent_user, sent_pw);
796
797        apr_table_unset(r->notes, AUTHN_PROVIDER_NAME_NOTE);
798
799        /* Something occured. Stop checking. */
800        if (auth_result != AUTH_USER_NOT_FOUND) {
801            break;
802        }
803
804        /* If we're not really configured for providers, stop now. */
805        if (!conf->providers) {
806            break;
807        }
808
809        current_provider = current_provider->next;
810    } while (current_provider);
811
812    if (auth_result != AUTH_GRANTED) {
813        int return_code;
814
815        /* If we're not authoritative, then any error is ignored. */
816        if (!(conf->authoritative) && auth_result != AUTH_DENIED) {
817            return DECLINED;
818        }
819
820        switch (auth_result) {
821        case AUTH_DENIED:
822            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01807)
823                          "user '%s': authentication failure for \"%s\": "
824                          "password Mismatch",
825                          sent_user, r->uri);
826            return_code = HTTP_UNAUTHORIZED;
827            break;
828        case AUTH_USER_NOT_FOUND:
829            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01808)
830                          "user '%s' not found: %s", sent_user, r->uri);
831            return_code = HTTP_UNAUTHORIZED;
832            break;
833        case AUTH_GENERAL_ERROR:
834        default:
835            /*
836             * We'll assume that the module has already said what its error
837             * was in the logs.
838             */
839            return_code = HTTP_INTERNAL_SERVER_ERROR;
840            break;
841        }
842
843        /* If we're returning 403, tell them to try again. */
844        if (return_code == HTTP_UNAUTHORIZED) {
845            note_cookie_auth_failure(r);
846        }
847
848/* TODO: Flag the user somehow as to the reason for the failure */
849
850        return return_code;
851    }
852
853    return OK;
854
855}
856
857/* fake the basic authentication header if configured to do so */
858static void fake_basic_authentication(request_rec *r, auth_form_config_rec *conf,
859                                      const char *user, const char *pw)
860{
861    if (conf->fakebasicauth) {
862        char *basic = apr_pstrcat(r->pool, user, ":", pw, NULL);
863        apr_size_t size = (apr_size_t) strlen(basic);
864        char *base64 = apr_palloc(r->pool,
865                                  apr_base64_encode_len(size + 1) * sizeof(char));
866        apr_base64_encode(base64, basic, size);
867        apr_table_setn(r->headers_in, "Authorization",
868                       apr_pstrcat(r->pool, "Basic ", base64, NULL));
869    }
870}
871
872/**
873 * Must we use form authentication? If so, extract the cookie and run
874 * the authnz hooks to determine if the login is valid.
875 *
876 * If the login is not valid, a 401 Not Authorized will be returned. It
877 * is up to the webmaster to ensure this screen displays a suitable login
878 * form to give the user the opportunity to log in.
879 */
880static int authenticate_form_authn(request_rec * r)
881{
882    auth_form_config_rec *conf = ap_get_module_config(r->per_dir_config,
883                                                      &auth_form_module);
884    const char *sent_user = NULL, *sent_pw = NULL, *sent_hash = NULL;
885    const char *sent_loc = NULL, *sent_method = "GET", *sent_mimetype = NULL;
886    const char *current_auth = NULL;
887    const char *err;
888    apr_status_t res;
889    int rv = HTTP_UNAUTHORIZED;
890
891    /* Are we configured to be Form auth? */
892    current_auth = ap_auth_type(r);
893    if (!current_auth || strcasecmp(current_auth, "form")) {
894        return DECLINED;
895    }
896
897    /*
898     * XSS security warning: using cookies to store private data only works
899     * when the administrator has full control over the source website. When
900     * in forward-proxy mode, websites are public by definition, and so can
901     * never be secure. Abort the auth attempt in this case.
902     */
903    if (PROXYREQ_PROXY == r->proxyreq) {
904        ap_log_rerror(APLOG_MARK, APLOG_ERR,
905                      0, r, APLOGNO(01809) "form auth cannot be used for proxy "
906                      "requests due to XSS risk, access denied: %s", r->uri);
907        return HTTP_INTERNAL_SERVER_ERROR;
908    }
909
910    /* We need an authentication realm. */
911    if (!ap_auth_name(r)) {
912        ap_log_rerror(APLOG_MARK, APLOG_ERR,
913                      0, r, APLOGNO(01810) "need AuthName: %s", r->uri);
914        return HTTP_INTERNAL_SERVER_ERROR;
915    }
916
917    r->ap_auth_type = (char *) current_auth;
918
919    /* try get the username and password from the notes, if present */
920    get_notes_auth(r, &sent_user, &sent_pw, &sent_method, &sent_mimetype);
921    if (!sent_user || !sent_pw || !*sent_user || !*sent_pw) {
922
923        /* otherwise try get the username and password from a session, if present */
924        res = get_session_auth(r, &sent_user, &sent_pw, &sent_hash);
925
926    }
927    else {
928        res = APR_SUCCESS;
929    }
930
931    /* first test whether the site passphrase matches */
932    if (APR_SUCCESS == res && sent_user && sent_hash && sent_pw) {
933        rv = check_site(r, conf->site, sent_user, sent_hash);
934        if (OK == rv) {
935            fake_basic_authentication(r, conf, sent_user, sent_pw);
936            return OK;
937        }
938    }
939
940    /* otherwise test for a normal password match */
941    if (APR_SUCCESS == res && sent_user && sent_pw) {
942        rv = check_authn(r, sent_user, sent_pw);
943        if (OK == rv) {
944            fake_basic_authentication(r, conf, sent_user, sent_pw);
945            return OK;
946        }
947    }
948
949    /*
950     * If we reach this point, the request should fail with access denied,
951     * except for one potential scenario:
952     *
953     * If the request is a POST, and the posted form contains user defined fields
954     * for a username and a password, and the username and password are correct,
955     * then return the response obtained by a GET to this URL.
956     *
957     * If an additional user defined location field is present in the form,
958     * instead of a GET of the current URL, redirect the browser to the new
959     * location.
960     *
961     * As a further option, if the user defined fields for the type of request,
962     * the mime type of the body of the request, and the body of the request
963     * itself are present, replace this request with a new request of the given
964     * type and with the given body.
965     *
966     * Otherwise access is denied.
967     *
968     * Reading the body requires some song and dance, because the input filters
969     * are not yet configured. To work around this problem, we create a
970     * subrequest and use that to create a sane filter stack we can read the
971     * form from.
972     *
973     * The main request is then capped with a kept_body input filter, which has
974     * the effect of guaranteeing the input stack can be safely read a second time.
975     *
976     */
977    if (HTTP_UNAUTHORIZED == rv && r->method_number == M_POST && ap_is_initial_req(r)) {
978        request_rec *rr;
979        apr_bucket_brigade *sent_body = NULL;
980
981        /* create a subrequest of our current uri */
982        rr = ap_sub_req_lookup_uri(r->uri, r, r->input_filters);
983        rr->headers_in = r->headers_in;
984
985        /* run the insert_filters hook on the subrequest to ensure a body read can
986         * be done properly.
987         */
988        ap_run_insert_filter(rr);
989
990        /* parse the form by reading the subrequest */
991        rv = get_form_auth(rr, conf->username, conf->password, conf->location,
992                           conf->method, conf->mimetype, conf->body,
993                           &sent_user, &sent_pw, &sent_loc, &sent_method,
994                           &sent_mimetype, &sent_body, conf);
995
996        /* make sure any user detected within the subrequest is saved back to
997         * the main request.
998         */
999        r->user = apr_pstrdup(r->pool, rr->user);
1000
1001        /* we cannot clean up rr at this point, as memory allocated to rr is
1002         * referenced from the main request. It will be cleaned up when the
1003         * main request is cleaned up.
1004         */
1005
1006        /* insert the kept_body filter on the main request to guarantee the
1007         * input filter stack cannot be read a second time, optionally inject
1008         * a saved body if one was specified in the login form.
1009         */
1010        if (sent_body && sent_mimetype) {
1011            apr_table_set(r->headers_in, "Content-Type", sent_mimetype);
1012            r->kept_body = sent_body;
1013        }
1014        else {
1015            r->kept_body = apr_brigade_create(r->pool, r->connection->bucket_alloc);
1016        }
1017        ap_request_insert_filter_fn(r);
1018
1019        /* did the form ask to change the method? if so, switch in the redirect handler
1020         * to relaunch this request as the subrequest with the new method. If the
1021         * form didn't specify a method, the default value GET will force a redirect.
1022         */
1023        if (sent_method && strcmp(r->method, sent_method)) {
1024            r->handler = FORM_REDIRECT_HANDLER;
1025        }
1026
1027        /* check the authn in the main request, based on the username found */
1028        if (OK == rv) {
1029            rv = check_authn(r, sent_user, sent_pw);
1030            if (OK == rv) {
1031                fake_basic_authentication(r, conf, sent_user, sent_pw);
1032                set_session_auth(r, sent_user, sent_pw, conf->site);
1033                if (sent_loc) {
1034                    apr_table_set(r->headers_out, "Location", sent_loc);
1035                    return HTTP_MOVED_TEMPORARILY;
1036                }
1037                if (conf->loginsuccess) {
1038                    const char *loginsuccess = ap_expr_str_exec(r,
1039                            conf->loginsuccess, &err);
1040                    if (!err) {
1041                        apr_table_set(r->headers_out, "Location", loginsuccess);
1042                        return HTTP_MOVED_TEMPORARILY;
1043                    }
1044                    else {
1045                        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02339)
1046                                      "Can't evaluate login success expression: %s", err);
1047                        return HTTP_INTERNAL_SERVER_ERROR;
1048                    }
1049                }
1050            }
1051        }
1052
1053    }
1054
1055    /*
1056     * did the admin prefer to be redirected to the login page on failure
1057     * instead?
1058     */
1059    if (HTTP_UNAUTHORIZED == rv && conf->loginrequired) {
1060        const char *loginrequired = ap_expr_str_exec(r,
1061                conf->loginrequired, &err);
1062        if (!err) {
1063            apr_table_set(r->headers_out, "Location", loginrequired);
1064            return HTTP_MOVED_TEMPORARILY;
1065        }
1066        else {
1067            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02340)
1068                          "Can't evaluate login required expression: %s", err);
1069            return HTTP_INTERNAL_SERVER_ERROR;
1070        }
1071    }
1072
1073    /* did the user ask to be redirected on login success? */
1074    if (sent_loc) {
1075        apr_table_set(r->headers_out, "Location", sent_loc);
1076        rv = HTTP_MOVED_TEMPORARILY;
1077    }
1078
1079
1080    /*
1081     * potential security issue: if we return a login to the browser, we must
1082     * send a no-store to make sure a well behaved browser will not try and
1083     * send the login details a second time if the back button is pressed.
1084     *
1085     * if the user has full control over the backend, the
1086     * AuthCookieDisableNoStore can be used to turn this off.
1087     */
1088    if (HTTP_UNAUTHORIZED == rv && !conf->disable_no_store) {
1089        apr_table_addn(r->headers_out, "Cache-Control", "no-store");
1090        apr_table_addn(r->err_headers_out, "Cache-Control", "no-store");
1091    }
1092
1093    return rv;
1094
1095}
1096
1097/**
1098 * Handle a login attempt.
1099 *
1100 * If the login session is either missing or form authnz is unsuccessful, a
1101 * 401 Not Authorized will be returned to the browser. The webmaster
1102 * is expected to insert a login form into the 401 Not Authorized
1103 * error screen.
1104 *
1105 * If the webmaster wishes, they can point the form submission at this
1106 * handler, which will redirect the user to the correct page on success.
1107 * On failure, the 401 Not Authorized error screen will be redisplayed,
1108 * where the login attempt can be repeated.
1109 *
1110 */
1111static int authenticate_form_login_handler(request_rec * r)
1112{
1113    auth_form_config_rec *conf;
1114    const char *err;
1115
1116    const char *sent_user = NULL, *sent_pw = NULL, *sent_loc = NULL;
1117    int rv;
1118
1119    if (strcmp(r->handler, FORM_LOGIN_HANDLER)) {
1120        return DECLINED;
1121    }
1122
1123    if (r->method_number != M_POST) {
1124        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01811)
1125          "the " FORM_LOGIN_HANDLER " only supports the POST method for %s",
1126                      r->uri);
1127        return HTTP_METHOD_NOT_ALLOWED;
1128    }
1129
1130    conf = ap_get_module_config(r->per_dir_config, &auth_form_module);
1131
1132    rv = get_form_auth(r, conf->username, conf->password, conf->location,
1133                       NULL, NULL, NULL,
1134                       &sent_user, &sent_pw, &sent_loc,
1135                       NULL, NULL, NULL, conf);
1136    if (OK == rv) {
1137        rv = check_authn(r, sent_user, sent_pw);
1138        if (OK == rv) {
1139            set_session_auth(r, sent_user, sent_pw, conf->site);
1140            if (sent_loc) {
1141                apr_table_set(r->headers_out, "Location", sent_loc);
1142                return HTTP_MOVED_TEMPORARILY;
1143            }
1144            if (conf->loginsuccess) {
1145                const char *loginsuccess = ap_expr_str_exec(r,
1146                        conf->loginsuccess, &err);
1147                if (!err) {
1148                    apr_table_set(r->headers_out, "Location", loginsuccess);
1149                    return HTTP_MOVED_TEMPORARILY;
1150                }
1151                else {
1152                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02341)
1153                                  "Can't evaluate login success expression: %s", err);
1154                    return HTTP_INTERNAL_SERVER_ERROR;
1155                }
1156            }
1157            return HTTP_OK;
1158        }
1159    }
1160
1161    /* did we prefer to be redirected to the login page on failure instead? */
1162    if (HTTP_UNAUTHORIZED == rv && conf->loginrequired) {
1163        const char *loginrequired = ap_expr_str_exec(r,
1164                conf->loginrequired, &err);
1165        if (!err) {
1166            apr_table_set(r->headers_out, "Location", loginrequired);
1167            return HTTP_MOVED_TEMPORARILY;
1168        }
1169        else {
1170            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02342)
1171                          "Can't evaluate login required expression: %s", err);
1172            return HTTP_INTERNAL_SERVER_ERROR;
1173        }
1174    }
1175
1176    return rv;
1177
1178}
1179
1180/**
1181 * Handle a logout attempt.
1182 *
1183 * If an attempt is made to access this URL, any username and password
1184 * embedded in the session is deleted.
1185 *
1186 * This has the effect of logging the person out.
1187 *
1188 * If a logout URI has been specified, this function will create an
1189 * internal redirect to this page.
1190 */
1191static int authenticate_form_logout_handler(request_rec * r)
1192{
1193    auth_form_config_rec *conf;
1194    const char *err;
1195
1196    if (strcmp(r->handler, FORM_LOGOUT_HANDLER)) {
1197        return DECLINED;
1198    }
1199
1200    conf = ap_get_module_config(r->per_dir_config, &auth_form_module);
1201
1202    /* remove the username and password, effectively logging the user out */
1203    set_session_auth(r, NULL, NULL, NULL);
1204
1205    /*
1206     * make sure the logout page is never cached - otherwise the logout won't
1207     * work!
1208     */
1209    apr_table_addn(r->headers_out, "Cache-Control", "no-store");
1210    apr_table_addn(r->err_headers_out, "Cache-Control", "no-store");
1211
1212    /* if set, internal redirect to the logout page */
1213    if (conf->logout) {
1214        const char *logout = ap_expr_str_exec(r,
1215                conf->logout, &err);
1216        if (!err) {
1217            apr_table_addn(r->headers_out, "Location", logout);
1218            return HTTP_TEMPORARY_REDIRECT;
1219        }
1220        else {
1221            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02343)
1222                          "Can't evaluate logout expression: %s", err);
1223            return HTTP_INTERNAL_SERVER_ERROR;
1224        }
1225    }
1226
1227    return HTTP_OK;
1228
1229}
1230
1231/**
1232 * Handle a redirect attempt.
1233 *
1234 * If during a form login, the method, mimetype and request body are
1235 * specified, this handler will ensure that this request is included
1236 * as an internal redirect.
1237 *
1238 */
1239static int authenticate_form_redirect_handler(request_rec * r)
1240{
1241
1242    request_rec *rr = NULL;
1243    const char *sent_method = NULL, *sent_mimetype = NULL;
1244
1245    if (strcmp(r->handler, FORM_REDIRECT_HANDLER)) {
1246        return DECLINED;
1247    }
1248
1249    /* get the method and mimetype from the notes */
1250    get_notes_auth(r, NULL, NULL, &sent_method, &sent_mimetype);
1251
1252    if (r->kept_body && sent_method && sent_mimetype) {
1253
1254        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01812)
1255          "internal redirect to method '%s' and body mimetype '%s' for the "
1256                      "uri: %s", sent_method, sent_mimetype, r->uri);
1257
1258        rr = ap_sub_req_method_uri(sent_method, r->uri, r, r->output_filters);
1259        r->status = ap_run_sub_req(rr);
1260
1261    }
1262    else {
1263        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01813)
1264        "internal redirect requested but one or all of method, mimetype or "
1265                      "body are NULL: %s", r->uri);
1266        return HTTP_INTERNAL_SERVER_ERROR;
1267    }
1268
1269    /* return the underlying error, or OK on success */
1270    return r->status == HTTP_OK || r->status == OK ? OK : r->status;
1271
1272}
1273
1274static void register_hooks(apr_pool_t * p)
1275{
1276#if AP_MODULE_MAGIC_AT_LEAST(20080403,1)
1277    ap_hook_check_authn(authenticate_form_authn, NULL, NULL, APR_HOOK_MIDDLE,
1278                        AP_AUTH_INTERNAL_PER_CONF);
1279#else
1280    ap_hook_check_user_id(authenticate_form_authn, NULL, NULL, APR_HOOK_MIDDLE);
1281#endif
1282    ap_hook_handler(authenticate_form_login_handler, NULL, NULL, APR_HOOK_MIDDLE);
1283    ap_hook_handler(authenticate_form_logout_handler, NULL, NULL, APR_HOOK_MIDDLE);
1284    ap_hook_handler(authenticate_form_redirect_handler, NULL, NULL, APR_HOOK_MIDDLE);
1285
1286    ap_hook_note_auth_failure(hook_note_cookie_auth_failure, NULL, NULL,
1287                              APR_HOOK_MIDDLE);
1288}
1289
1290AP_DECLARE_MODULE(auth_form) =
1291{
1292    STANDARD20_MODULE_STUFF,
1293    create_auth_form_dir_config, /* dir config creater */
1294    merge_auth_form_dir_config,  /* dir merger --- default is to override */
1295    NULL,                        /* server config */
1296    NULL,                        /* merge server config */
1297    auth_form_cmds,              /* command apr_table_t */
1298    register_hooks               /* register hooks */
1299};
1300