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