From: Graham Leggett Date: Wed, 9 Apr 2008 11:46:46 +0000 (+0000) Subject: mod_auth_form: Add a module capable of allowing end users to log X-Git-Tag: 2.3.0~746 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=786afc76bdd8fd5ef9ea750c279b3e264f554911;p=thirdparty%2Fapache%2Fhttpd.git mod_auth_form: Add a module capable of allowing end users to log in using an HTML form, storing the credentials within mod_session. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@646285 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index daf40246db1..86700ea5fe3 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,10 @@ Changes with Apache 2.3.0 [ When backported to 2.2.x, remove entry from this file ] + *) mod_auth_form: Add a module capable of allowing end users to log + in using an HTML form, storing the credentials within mod_session. + [Graham Leggett] + *) Add a function to the http filters that is able to parse an HTML form request with the type of application/x-www-form-urlencoded. [Graham Leggett] diff --git a/docs/manual/mod/mod_auth_form.xml b/docs/manual/mod/mod_auth_form.xml new file mode 100644 index 00000000000..c6020b5ada9 --- /dev/null +++ b/docs/manual/mod/mod_auth_form.xml @@ -0,0 +1,719 @@ + + + + + + + + + +mod_auth_form +Form authentication +Base +mod_auth_form.c +auth_form_module + + + Warning +

Form authentication depends on the mod_session + modules, and these modules make use of HTTP cookies, and as such can fall + victim to Cross Site Scripting attacks, or expose potentially private + information to clients. Please ensure that the relevant risks have + been taken into account before enabling the session functionality on + your server.

+
+ +

This module allows the use of an HTML login form to restrict access + by looking up users in the given providers. HTML forms require + significantly more configuration than the alternatives, however an + HTML login form can provide a much friendlier experience for end users. +

+ +

HTTP Basic authentication is provided by + mod_auth_basic, and HTTP Digest Authentication is + provided by mod_auth_digest. This module should + usually be combined with at least one authentication module + such as mod_authn_file and one authorization + module such as mod_authz_user.

+ +

Once the user has been successfully authenticated, the user's login + details will be stored in a suitably configured session, + as provided by the mod_session module.

+ +
+mod_session +AuthName +AuthType +Require +Reject +Satisfy (Deprecated) +<SatisfyAll> +<SatisfyOne> +Authentication howto + +
Basic Configuration + +

To protect a particular URL with mod_auth_form, you need to + decide where you will store your session, and you will need to + decide what method you will use to authenticate. In this simple example, the + login details will be stored in a session based on + mod_session_cookie, and authentication will be attempted against + a file using mod_authn_file. If authentication is unsuccessful, + the user will be redirected to the form login page.

+ + Basic example + AuthFormProvider file
+ AuthUserFile conf/passwd
+ AuthType form
+ AuthName realm
+ AuthFormLoginRequiredLocation http://example.com/login.html
+ Session On
+ SessionCookieName session path=/
+ SessionCryptoPassphrase secret
+
+ +

The directive AuthType will enable + the mod_auth_form authentication when set to the value form. + The directives AuthFormProvider and + AuthUserFile specify that usernames + and passwords should be checked against the chosen file.

+ +

The directives Session, + SessionCookieName and + SessionCryptoPassphrase create an + encrypted session stored within an HTTP cookie on the browser. For more information + on the different options for configuring a session, read the documentation for + mod_session.

+ +

In the simple example above, a URL has been protected by + mod_auth_form, but the user has yet to be given an opportunity to + enter their username and password. Options for doing so include providing a + dedicated standalone login page for this purpose, or for providing the login + page inline.

+
+ +
Standalone Login + +

The login form can be hosted as a standalone page, or can be provided inline on + the same page.

+ +

When configuring the login as a standalone page, unsuccessful authentication + attempts should be redirected to a login form created by the website for this purpose, + using the AuthFormLoginRequiredLocation + directive. Typically this login page will contain an HTML form, asking the user to + provide their usename and password.

+ + Example login form + <form method="POST" action="/dologin.html">
+ Username: <input type="text" name="httpd_username" value="" />
+ Password: <input type="password" name="httpd_password" value="" />
+ <input type="submit" name="login" value="Login" />
+ </form>
+
+ +

The part that does the actual login is handled by the form-login-handler. + The action of the form should point at this handler, which is configured within + Apache as follows:

+ + Form login handler example + <Location /dologin.html> + + SetHandler form-login-handler
+ AuthFormLoginRequiredLocation http://example.com/login.html
+ AuthFormLoginSuccessLocation http://example.com/success.html
+ AuthFormProvider file
+ AuthUserFile conf/passwd
+ AuthType form
+ AuthName realm
+ Session On
+ SessionCookieName session path=/
+ SessionCryptoPassphrase secret
+
+ </Location> +
+ +

The URLs specified by the + AuthFormLoginRequiredLocation directive will typically + point to a page explaining to the user that their login attempt was unsuccessful, and they + should try again, while the AuthFormLoginSuccessLocation + directive specifies the URL the user should be redirected to on success.

+ +

Alternatively, the URL to redirect the user to on success can be embedded within the login + form, as in the example below. As a result, the same form-login-handler can be + reused for different areas of a website.

+ + Example login form with location + <form method="POST" action="/dologin.html">
+ + Username: <input type="text" name="httpd_username" value="" />
+ Password: <input type="password" name="httpd_password" value="" />
+ <input type="submit" name="login" value="Login" />
+ <input type="hidden" name="httpd_location" value="http://example.com/success.html" />
+
+ </form>
+
+ +
+ +
Inline Login + + Warning +

A risk exists that under certain circumstances, the login form configured + using inline login may be submitted more than once, revealing login credentials to + the application running underneath. The administrator must ensure that the underlying + application is properly secured to prevent abuse. If in doubt, use the + standalone login configuration.

+
+ +

As an alternative to having a dedicated login page for a website, it is possible to + configure mod_auth_form to authenticate users inline, without being + redirected to another page. This allows the state of the current page to be preserved + during the login attempt. This can be useful in a situation where a time limited + session is in force, and the session times out in the middle of the user request. The + user can be re-authenticated in place, and they can continue where they left off.

+ +

When a page protected by mod_auth_form is accessed, and the user is + not logged in, and no AuthFormLoginRequiredLocation + directive is specified as described in the section above, an HTTP_UNAUTHORIZED + status code is returned to the browser in the normal way indicating to the user that + they are not authorised to view the page.

+ +

To configure inline authentication, the administrator overrides the error document + returned by the HTTP_UNAUTHORIZED status code with a custom error document, + containing the login form, as follows.

+ + Basic inline example + AuthFormProvider file
+ ErrorDocument 401 /login.shtml
+ AuthUserFile conf/passwd
+ AuthType form
+ AuthName realm
+ AuthFormLoginRequiredLocation http://example.com/login.html
+ Session On
+ SessionCookieName session path=/
+ SessionCryptoPassphrase secret
+
+ +

The error document file should contain the login form used to log in, as + per the example below. The key difference in this form is that the HTML + form action is left blank. This has the effect of submitting the form to + the original protected URL, without the page having to know what that + URL is.

+ + Example inline login form + <form method="POST" action="">
+ + Username: <input type="text" name="httpd_username" value="" />
+ Password: <input type="password" name="httpd_password" value="" />
+ <input type="submit" name="login" value="Login" />
+
+ </form>
+
+ +

When the end user has filled in their login details, the form will make + an HTTP POST request to the original password protected URL. + mod_auth_form will intercept this POST request, and if + HTML fields are found present for the username and password, the user + will be logged in, and the original password protected URL will be returned + to the user as a GET request.

+ +
+ +
Inline Login with Body Preservation + +

A limitation of the inline login technique described above is that should an + HTML form POST have resulted in the request to authenticate or reauthentiate, the + contents of the original form posted by the browser will be lost. Depending on + the function of the website, this could present significant inconvenience for the + end user.

+ +

mod_auth_form addresses this by allowing the method and body + of the original request to be embedded in the login form, and if authentication + is successful, the original method and body will be retried by Apache, preserving + the state of the original request.

+ +

To enable body preservation, add three additional fields to the login form as + per the example below.

+ + Example with body preservation + <form method="POST" action="">
+ + Username: <input type="text" name="httpd_username" value="" />
+ Password: <input type="password" name="httpd_password" value="" />
+ <input type="submit" name="login" value="Login" />
+ <input type="hidden" name="httpd_method" value="POST" />
+ <input type="hidden" name="httpd_mimetype" value="application/x-www-form-urlencoded" />
+ <input type="hidden" name="httpd_body" value="name1=value1&name2=value2" />

+
+ </form> +
+ +

How the method, mimetype and body of the original request are embedded within the + login form will depend on the platform and technology being used within the website. +

+ +

One option is to use the mod_include module along with the + KeptBodySize directive, along with a suitable + CGI script, to embed the variables in the form.

+ +

Another option is to render the login form using a CGI script or other dynamic + technology.

+ + CGI example + AuthFormProvider file
+ ErrorDocument 401 /cgi-bin/login.cgi
+ ...
+
+ +
+ +
Logging Out + +

To enable a user to log out of a particular session, configure a page to + be handled by the form-logout-handler. Any attempt to access this + URL will cause the username and password to be removed from the current + session, effectively logging the user out.

+ +

By setting the + AuthFormLogoutLocation directive, + a URL can be specified that the browser will be redirected to on successful + logout. This URL might explain to the user that they have been logged out, and + give the user the option to log in again.

+ + Basic logout example + SetHandler form-logout-handler
+ AuthName realm
+ AuthFormLogoutLocation http://example.com/loggedout.html
+ Session On
+ SessionCookieName session path=/
+ SessionCryptoPassphrase secret
+
+ +

Note that logging a user out does not delete the session, it merely removes + the username and password from the session. If this results in an empty session, + the nett effect will be the removal of that session, but this is not + guaranteed. If you want to guarantee the removal of a session, set the + SessionMaxAge directive to a small + value, like 1. (Setting the directive to zero would mean no session age limit). +

+ + Basic session expiry example + SetHandler form-logout-handler
+ AuthFormLogoutLocation http://example.com/loggedout.html
+ Session On
+ SessionMaxAge 1
+ SessionCookieName session path=/
+ SessionCryptoPassphrase secret
+
+ +
+ + +AuthFormProvider +Sets the authentication provider(s) for this location +AuthFormProvider provider-name +[provider-name] ... +AuthFormProvider file +directory.htaccess + +AuthConfig + + +

The AuthFormProvider directive sets + which provider is used to authenticate the users for this location. + The default file provider is implemented + by the mod_authn_file module. Make sure + that the chosen provider module is present in the server.

+ + Example + <Location /secure>
+ + AuthType form
+ AuthName "private area"
+ AuthFormProvider dbm
+ AuthDBMType SDBM
+ AuthDBMUserFile /www/etc/dbmpasswd
+ Require valid-user
+ ...
+
+ </Location> +
+ +

Providers are implemented by mod_authn_dbm, + mod_authn_file, mod_authn_dbd, + and mod_authnz_ldap.

+
+
+ + +AuthFormAuthoritative +Sets whether authorization and authentication are passed to +lower level modules +AuthFormAuthoritative On|Off +AuthFormAuthoritative On +directory.htaccess + +AuthConfig + + +

Normally, each authorization module listed in AuthFormProvider will attempt + to verify the user, and if the user is not found in any provider, + access will be denied. Setting the + AuthFormAuthoritative directive explicitly + to Off allows for both authentication and + authorization to be passed on to other non-provider-based modules + if there is no userID or rule + matching the supplied userID. This should only be necessary when + combining mod_auth_form with third-party modules + that are not configured with the AuthFormProvider + directive. When using such modules, the order of processing + is determined in the modules' source code and is not configurable.

+
+
+ + +AuthFormUsername +The name of a form field carrying the login username +AuthFormUsername fieldname +httpd_username +directory + +Available in Apache 2.3.0 and later + + +

The AuthFormUsername directive specifies + the name of an HTML field which, if present, will contain the username to be used to log + in.

+
+
+ + +AuthFormPassword +The name of a form field carrying the login password +AuthFormPassword fieldname +httpd_password +directory + +Available in Apache 2.3.0 and later + + +

The AuthFormPassword directive specifies + the name of an HTML field which, if present, will contain the password to be used to log + in.

+
+
+ + +AuthFormLocation +The name of a form field carrying a URL to redirect to on successful login +AuthFormLocation fieldname +httpd_location +directory + +Available in Apache 2.3.0 and later + + +

The AuthFormLocation directive specifies + the name of an HTML field which, if present, will contain a URL to redirect the browser to + should login be successful.

+
+
+ + +AuthFormMethod +The name of a form field carrying the method of the request to attempt on successful login +AuthFormMethod fieldname +httpd_method +directory + +Available in Apache 2.3.0 and later + + +

The AuthFormMethod directive specifies + the name of an HTML field which, if present, will contain the method of the request to + to submit should login be successful.

+ +

By populating the form with fields described by + AuthFormMethod, + AuthFormMimetype and + AuthFormBody, a website can retry + a request that may have been interrupted by the login screen, or by a session + timeout.

+
+
+ + +AuthFormMimetype +The name of a form field carrying the mimetype of the body of the request to attempt on successful login +AuthFormMimetype fieldname +httpd_mimetype +directory + +Available in Apache 2.3.0 and later + + +

The AuthFormMethod directive specifies + the name of an HTML field which, if present, will contain the method of the request to + to submit should login be successful.

+ +

By populating the form with fields described by + AuthFormMethod, + AuthFormMimetype and + AuthFormBody, a website can retry + a request that may have been interrupted by the login screen, or by a session + timeout.

+
+
+ + +AuthFormBody +The name of a form field carrying the body of the request to attempt on successful login +AuthFormBody fieldname +httpd_body +directory + +Available in Apache 2.3.0 and later + + +

The AuthFormMethod directive specifies + the name of an HTML field which, if present, will contain the method of the request to + to submit should login be successful.

+ +

By populating the form with fields described by + AuthFormMethod, + AuthFormMimetype and + AuthFormBody, a website can retry + a request that may have been interrupted by the login screen, or by a session + timeout.

+
+
+ + +AuthFormSize +The largest size of the form in bytes that will be parsed for the login details +AuthFormBody size +8192 +directory + +Available in Apache 2.3.0 and later + + +

The AuthFormSize directive specifies + the maximum size of the body of the request that will be parsed to find the login form.

+ +

If a login request arrives that exceeds this size, the whole request will be aborted + with the HTTP response code HTTP_REQUEST_TOO_LARGE.

+ +

If you have populated the form with fields described by + AuthFormMethod, + AuthFormMimetype and + AuthFormBody, you probably want to set this + field to a similar size as the KeptBodySize + directive.

+ +
+
+ + +AuthFormLoginRequiredLocation +The URL of the page to be redirected to should login be required +AuthFormLoginRequiredLocation url +none +directory + +Available in Apache 2.3.0 and later + + +

The AuthFormLoginRequiredLocation directive + specifies the URL to redirect to should the user not be authorised to view a page. By default, + if a user is not authorised to view a page, the HTTP response code HTTP_UNAUTHORIZED + will be returned with the page specified by the + ErrorDocument directive. This directive overrides this + default.

+ +

Use this directive if you have a dedicated login page to redirect users to.

+ +
+
+ + +AuthFormLoginSuccessLocation +The URL of the page to be redirected to should login be successful +AuthFormLoginSuccessLocation url +none +directory + +Available in Apache 2.3.0 and later + + +

The AuthFormLoginSuccessLocation directive + specifies the URL to redirect to should the user have logged in successfully. This directive + can be overridden if a form field has been defined containing another URL using the + AuthFormLocation directive.

+ +

Use this directive if you have a dedicated login URL, and you have not embedded the + destination page in the login form.

+ +
+
+ + +AuthFormLogoutLocation +The URI of the page on the server to redirect to after a user has logged out +AuthFormLogoutLocation uri +none +directory + +Available in Apache 2.3.0 and later + + +

The AuthFormLogoutLocation directive + specifies the URI of a page on the server to redirect to internally should the user + attempt log out.

+ +

When a URI is accessed that is served by the handler form-logout-handler, + the page specified by this directive will be shown to the end user. For example:

+ + Example + <Location /logout>
+ + SetHandler form-logout-handler
+ AuthFormLogoutLocation /logout.html
+ Session on
+ ... +
+ </Location> +
+ +

An attempt to access the URI /logout/ will result in the user being logged + out, and the page /logout.html will be displayed. Make sure that the page + logout.html is not password protected, otherwise the page will not be + displayed.

+ +
+
+ + +AuthFormFakeBasicAuth +Fake a Basic Authentication header +AuthFormFakeBasicAuth On|Off +AuthFormFakeBasicAuth Off +directory + +Available in Apache 2.3.0 and later + + +

The AuthFormFakeBasicAuth flag + determines whether a Basic Authentication header will be added to + the request headers. This can be used to expose the username and password to + an underlying application, without the underlying application having to be aware + of how the login was achieved.

+ +
+
+ + +AuthFormLogoutLocation +The URL to redirect to after a user has logged out +AuthFormLogoutLocation uri +none +directory + +Available in Apache 2.3.0 and later + + +

The AuthFormLogoutLocation directive + specifies the URL of a page on the server to redirect to should the user attempt to log out.

+ +

When a URI is accessed that is served by the handler form-logout-handler, + the page specified by this directive will be shown to the end user. For example:

+ + Example + <Location /logout>
+ + SetHandler form-logout-handler
+ AuthFormLogoutLocation http://example.com/loggedout.html
+ Session on
+ ... +
+ </Location> +
+ +

An attempt to access the URI /logout/ will result in the user being logged + out, and the page /logout.html will be displayed. Make sure that the page + logout.html is not password protected, otherwise the page will not be + displayed.

+ +
+
+ + +AuthFormDisableNoStore +Disable the CacheControl no-store header on the login page +AuthFormDisableNoStore On|Off +AuthFormDisableNoStore Off +directory + +Available in Apache 2.3.0 and later + + +

The AuthFormDisableNoStore flag + disables the sending of a Cache-Control no-store header with the + error 401 page returned when the user is not yet logged in. The purpose of the header + is to make it difficult for an ecmascript application to attempt to resubmit the + login form, and reveal the username and password to the backend application. Disable + at your own risk.

+ +
+
+ + +AuthFormSitePassphrase +Bypass authentication checks for high traffic sites +AuthFormSitePassphrase secret +none +directory + +Available in Apache 2.3.0 and later + + +

The AuthFormSitePassphrase directive + specifies a passphrase which, if present in the user session, causes Apache to + bypass authentication checks for the given URL. It can be used on high traffic websites + to reduce the load induced on authentication infrastructure.

+ +

The passphrase can be inserted into a user session by adding this directive to the + configuration for the form-login-handler. The form-login-handler + itself will always run the authentication checks, regardless of whether a passphrase + is specified or not.

+ + Warning +

If the session is exposed to the user through the use of + mod_session_cookie, and the session is not protected with + mod_session_crypto, the passphrase is open to potential exposure + through a dictionary attack. Regardless of how the session is configured, + ensure that this directive is not used within URL spaces where private user data + could be exposed, or sensitive transactions can be conducted. Use at own risk.

+
+ +
+
+ +
diff --git a/modules/aaa/config.m4 b/modules/aaa/config.m4 index 847cf30f3af..b5a5b39cd05 100644 --- a/modules/aaa/config.m4 +++ b/modules/aaa/config.m4 @@ -56,6 +56,7 @@ APACHE_MODULE(access_compat, mod_access compatibility, , , yes) dnl these are the front-end authentication modules APACHE_MODULE(auth_basic, basic authentication, , , yes) +APACHE_MODULE(auth_form, form authentication, , , yes) APACHE_MODULE(auth_digest, RFC2617 Digest authentication, , , most, [ APR_CHECK_APR_DEFINE(APR_HAS_RANDOM) if test $ac_cv_define_APR_HAS_RANDOM = "no"; then diff --git a/modules/aaa/mod_auth_form.c b/modules/aaa/mod_auth_form.c new file mode 100644 index 00000000000..9e7a06a4725 --- /dev/null +++ b/modules/aaa/mod_auth_form.c @@ -0,0 +1,1133 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr_strings.h" +#include "apr_md5.h" /* for apr_password_validate */ +#include "apr_lib.h" /* for apr_isspace */ +#include "apr_base64.h" /* for apr_base64_decode et al */ +#define APR_WANT_STRFUNC /* for strcasecmp */ +#include "apr_want.h" + +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_protocol.h" +#include "http_request.h" +#include "ap_provider.h" +#include "util_md5.h" + +#include "mod_auth.h" +#include "mod_session.h" + +#define LOG_PREFIX "mod_auth_form: " +#define FORM_LOGIN_HANDLER "form-login-handler" +#define FORM_LOGOUT_HANDLER "form-logout-handler" +#define FORM_REDIRECT_HANDLER "form-redirect-handler" +#define MOD_AUTH_FORM_HASH "site" + +static int (*ap_session_load_fn) (request_rec * r, session_rec ** z) = NULL; +static void (*ap_session_get_fn) (request_rec * r, session_rec * z, + const char *key, const char **value) = NULL; +static void (*ap_session_set_fn) (request_rec * r, session_rec * z, + const char *key, const char *value) = NULL; + +typedef struct { + authn_provider_list *providers; + char *dir; + int authoritative; + int authoritative_set; + const char *site; + int site_set; + const char *username; + int username_set; + const char *password; + int password_set; + apr_off_t form_size; + int form_size_set; + int fakebasicauth; + int fakebasicauth_set; + const char *location; + int location_set; + const char *method; + int method_set; + const char *mimetype; + int mimetype_set; + const char *body; + int body_set; + int disable_no_store; + int disable_no_store_set; + const char *loginsuccess; + int loginsuccess_set; + const char *loginrequired; + int loginrequired_set; + const char *logout; + int logout_set; +} auth_form_config_rec; + +static void *create_auth_form_dir_config(apr_pool_t * p, char *d) +{ + auth_form_config_rec *conf = apr_pcalloc(p, sizeof(*conf)); + + conf->dir = d; + /* Any failures are fatal. */ + conf->authoritative = 1; + + /* form size defaults to 8k */ + conf->form_size = HUGE_STRING_LEN; + + /* default form field names */ + conf->username = "httpd_username"; + conf->password = "httpd_password"; + conf->location = "httpd_location"; + conf->method = "httpd_method"; + conf->mimetype = "httpd_mimetype"; + conf->body = "httpd_body"; + + return conf; +} + +static void *merge_auth_form_dir_config(apr_pool_t * p, void *basev, void *addv) +{ + auth_form_config_rec *new = (auth_form_config_rec *) apr_pcalloc(p, sizeof(auth_form_config_rec)); + auth_form_config_rec *add = (auth_form_config_rec *) addv; + auth_form_config_rec *base = (auth_form_config_rec *) basev; + + new->providers = !add->providers ? base->providers : add->providers; + new->authoritative = (add->authoritative_set == 0) ? base->authoritative : add->authoritative; + new->authoritative_set = add->authoritative_set || base->authoritative_set; + new->site = (add->site_set == 0) ? base->site : add->site; + new->site_set = add->site_set || base->site_set; + new->username = (add->username_set == 0) ? base->username : add->username; + new->username_set = add->username_set || base->username_set; + new->password = (add->password_set == 0) ? base->password : add->password; + new->password_set = add->password_set || base->password_set; + new->location = (add->location_set == 0) ? base->location : add->location; + new->location_set = add->location_set || base->location_set; + new->form_size = (add->form_size_set == 0) ? base->form_size : add->form_size; + new->form_size_set = add->form_size_set || base->form_size_set; + new->fakebasicauth = (add->fakebasicauth_set == 0) ? base->fakebasicauth : add->fakebasicauth; + new->fakebasicauth_set = add->fakebasicauth_set || base->fakebasicauth_set; + new->method = (add->method_set == 0) ? base->method : add->method; + new->method_set = add->method_set || base->method_set; + new->mimetype = (add->mimetype_set == 0) ? base->mimetype : add->mimetype; + new->mimetype_set = add->mimetype_set || base->mimetype_set; + new->body = (add->body_set == 0) ? base->body : add->body; + new->body_set = add->body_set || base->body_set; + new->disable_no_store = (add->disable_no_store_set == 0) ? base->disable_no_store : add->disable_no_store; + new->disable_no_store_set = add->disable_no_store_set || base->disable_no_store_set; + new->loginsuccess = (add->loginsuccess_set == 0) ? base->loginsuccess : add->loginsuccess; + new->loginsuccess_set = add->loginsuccess_set || base->loginsuccess_set; + new->loginrequired = (add->loginrequired_set == 0) ? base->loginrequired : add->loginrequired; + new->loginrequired_set = add->loginrequired_set || base->loginrequired_set; + new->logout = (add->logout_set == 0) ? base->logout : add->logout; + new->logout_set = add->logout_set || base->logout_set; + + return new; +} + +static const char *add_authn_provider(cmd_parms * cmd, void *config, + const char *arg) +{ + auth_form_config_rec *conf = (auth_form_config_rec *) config; + authn_provider_list *newp; + + newp = apr_pcalloc(cmd->pool, sizeof(authn_provider_list)); + newp->provider_name = apr_pstrdup(cmd->pool, arg); + + /* lookup and cache the actual provider now */ + newp->provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP, + newp->provider_name, "0"); + + if (newp->provider == NULL) { + /* + * by the time they use it, the provider should be loaded and + * registered with us. + */ + return apr_psprintf(cmd->pool, + "Unknown Authn provider: %s", + newp->provider_name); + } + + if (!newp->provider->check_password) { + /* if it doesn't provide the appropriate function, reject it */ + return apr_psprintf(cmd->pool, + "The '%s' Authn provider doesn't support " + "Form Authentication", newp->provider_name); + } + + if (!ap_session_load_fn || !ap_session_get_fn || !ap_session_set_fn) { + ap_session_load_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_load); + ap_session_get_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_get); + ap_session_set_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_set); + if (!ap_session_load_fn || !ap_session_get_fn || !ap_session_set_fn) { + return "You must load mod_session to enable the mod_auth_form " + "functions"; + } + } + + /* Add it to the list now. */ + if (!conf->providers) { + conf->providers = newp; + } + else { + authn_provider_list *last = conf->providers; + + while (last->next) { + last = last->next; + } + last->next = newp; + } + + return NULL; +} + +/** + * Sanity check a given string that it exists, is not empty, + * and does not contain special characters. + */ +static const char *check_string(cmd_parms * cmd, const char *string) +{ + if (!string || !*string || ap_strchr_c(string, '=') || ap_strchr_c(string, '&')) { + return apr_pstrcat(cmd->pool, cmd->directive->directive, + " cannot be empty, or contain '=' or '&'.", + NULL); + } + return NULL; +} + +static const char *set_cookie_form_location(cmd_parms * cmd, void *config, const char *location) +{ + auth_form_config_rec *conf = (auth_form_config_rec *) config; + conf->location = location; + conf->location_set = 1; + return check_string(cmd, location); +} + +static const char *set_cookie_form_username(cmd_parms * cmd, void *config, const char *username) +{ + auth_form_config_rec *conf = (auth_form_config_rec *) config; + conf->username = username; + conf->username_set = 1; + return check_string(cmd, username); +} + +static const char *set_cookie_form_password(cmd_parms * cmd, void *config, const char *password) +{ + auth_form_config_rec *conf = (auth_form_config_rec *) config; + conf->password = password; + conf->password_set = 1; + return check_string(cmd, password); +} + +static const char *set_cookie_form_method(cmd_parms * cmd, void *config, const char *method) +{ + auth_form_config_rec *conf = (auth_form_config_rec *) config; + conf->method = method; + conf->method_set = 1; + return check_string(cmd, method); +} + +static const char *set_cookie_form_mimetype(cmd_parms * cmd, void *config, const char *mimetype) +{ + auth_form_config_rec *conf = (auth_form_config_rec *) config; + conf->mimetype = mimetype; + conf->mimetype_set = 1; + return check_string(cmd, mimetype); +} + +static const char *set_cookie_form_body(cmd_parms * cmd, void *config, const char *body) +{ + auth_form_config_rec *conf = (auth_form_config_rec *) config; + conf->body = body; + conf->body_set = 1; + return check_string(cmd, body); +} + +static const char *set_cookie_form_size(cmd_parms * cmd, void *config, + const char *arg) +{ + auth_form_config_rec *conf = config; + + if (APR_SUCCESS != apr_strtoff(&(conf->form_size), arg, NULL, 0) + || conf->form_size < 0) { + return "AuthCookieFormSize must be a size in bytes, or zero."; + } + conf->form_size_set = 1; + + return NULL; +} + +static const char *set_login_required_location(cmd_parms * cmd, void *config, const char *loginrequired) +{ + auth_form_config_rec *conf = (auth_form_config_rec *) config; + conf->loginrequired = loginrequired; + conf->loginrequired_set = 1; + return NULL; +} + +static const char *set_login_success_location(cmd_parms * cmd, void *config, const char *loginsuccess) +{ + auth_form_config_rec *conf = (auth_form_config_rec *) config; + conf->loginsuccess = loginsuccess; + conf->loginsuccess_set = 1; + return NULL; +} + +static const char *set_logout_location(cmd_parms * cmd, void *config, const char *logout) +{ + auth_form_config_rec *conf = (auth_form_config_rec *) config; + conf->logout = logout; + conf->logout_set = 1; + return NULL; +} + +static const char *set_site_passphrase(cmd_parms * cmd, void *config, const char *site) +{ + auth_form_config_rec *conf = (auth_form_config_rec *) config; + conf->site = site; + conf->site_set = 1; + return NULL; +} + +static const char *set_authoritative(cmd_parms * cmd, void *config, int flag) +{ + auth_form_config_rec *conf = (auth_form_config_rec *) config; + conf->authoritative = flag; + conf->authoritative_set = 1; + return NULL; +} + +static const char *set_fake_basic_auth(cmd_parms * cmd, void *config, int flag) +{ + auth_form_config_rec *conf = (auth_form_config_rec *) config; + conf->fakebasicauth = flag; + conf->fakebasicauth_set = 1; + return NULL; +} + +static const char *set_disable_no_store(cmd_parms * cmd, void *config, int flag) +{ + auth_form_config_rec *conf = (auth_form_config_rec *) config; + conf->disable_no_store = flag; + conf->disable_no_store_set = 1; + return NULL; +} + +static const command_rec auth_form_cmds[] = +{ + AP_INIT_ITERATE("AuthFormProvider", add_authn_provider, NULL, OR_AUTHCFG, + "specify the auth providers for a directory or location"), + AP_INIT_TAKE1("AuthFormUsername", set_cookie_form_username, NULL, OR_AUTHCFG, + "The field of the login form carrying the username"), + AP_INIT_TAKE1("AuthFormPassword", set_cookie_form_password, NULL, OR_AUTHCFG, + "The field of the login form carrying the password"), + AP_INIT_TAKE1("AuthFormLocation", set_cookie_form_location, NULL, OR_AUTHCFG, + "The field of the login form carrying the URL to redirect on " + "successful login."), + AP_INIT_TAKE1("AuthFormMethod", set_cookie_form_method, NULL, OR_AUTHCFG, + "The field of the login form carrying the original request method."), + AP_INIT_TAKE1("AuthFormMimetype", set_cookie_form_mimetype, NULL, OR_AUTHCFG, + "The field of the login form carrying the original request mimetype."), + AP_INIT_TAKE1("AuthFormBody", set_cookie_form_body, NULL, OR_AUTHCFG, + "The field of the login form carrying the urlencoded original request " + "body."), + AP_INIT_TAKE1("AuthFormSize", set_cookie_form_size, NULL, ACCESS_CONF, + "Maximum size of body parsed by the form parser"), + AP_INIT_TAKE1("AuthFormLoginRequiredLocation", set_login_required_location, + NULL, OR_AUTHCFG, + "If set, redirect the browser to this URL rather than " + "return 401 Not Authorized."), + AP_INIT_TAKE1("AuthFormLoginSuccessLocation", set_login_success_location, + NULL, OR_AUTHCFG, + "If set, redirect the browser to this URL when a login " + "processed by the login handler is successful."), + AP_INIT_TAKE1("AuthFormLogoutLocation", set_logout_location, + NULL, OR_AUTHCFG, + "The URL of the logout successful page. An attempt to access an " + "URL handled by the handler " FORM_LOGOUT_HANDLER " will result " + "in an redirect to this page after logout."), + AP_INIT_TAKE1("AuthFormSitePassphrase", set_site_passphrase, + NULL, OR_AUTHCFG, + "If set, use this passphrase to determine whether the user should " + "be authenticated. Bypasses the user authentication check on " + "every website hit, and is useful for high traffic sites."), + AP_INIT_FLAG("AuthFormAuthoritative", set_authoritative, + NULL, OR_AUTHCFG, + "Set to 'Off' to allow access control to be passed along to " + "lower modules if the UserID is not known to this module"), + AP_INIT_FLAG("AuthFormFakeBasicAuth", set_fake_basic_auth, + NULL, OR_AUTHCFG, + "Set to 'On' to pass through authentication to the rest of the " + "server as a basic authentication header."), + AP_INIT_FLAG("AuthFormDisableNoStore", set_disable_no_store, + NULL, OR_AUTHCFG, + "Set to 'on' to stop the sending of a Cache-Control no-store header with " + "the login screen. This allows the browser to cache the credentials, but " + "at the risk of it being possible for the login form to be resubmitted " + "and revealed to the backend server through XSS. Use at own risk."), + {NULL} +}; + +module AP_MODULE_DECLARE_DATA auth_form_module; + +/* These functions return 0 if client is OK, and proper error status + * if not... either HTTP_UNAUTHORIZED, if we made a check, and it failed, or + * HTTP_INTERNAL_SERVER_ERROR, if things are so totally confused that we + * couldn't figure out how to tell if the client is authorized or not. + * + * If they return DECLINED, and all other modules also decline, that's + * treated by the server core as a configuration error, logged and + * reported as such. + */ + +static void note_cookie_auth_failure(request_rec * r) +{ + auth_form_config_rec *conf = ap_get_module_config(r->per_dir_config, + &auth_form_module); + + if (conf->location && ap_strchr_c(conf->location, ':')) { + apr_table_setn(r->err_headers_out, "Location", conf->location); + } +} + +/** + * Set the auth username and password into the main request + * notes table. + */ +static void set_notes_auth(request_rec * r, + const char *user, const char *pw, + const char *method, const char *mimetype) +{ + apr_table_t *notes = NULL; + const char *authname; + + /* find the main request */ + while (r->main) { + r = r->main; + } + /* find the first redirect */ + while (r->prev) { + r = r->prev; + } + notes = r->notes; + + /* have we isolated the user and pw before? */ + authname = ap_auth_name(r); + if (user) { + apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-user", NULL), user); + } + if (pw) { + apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-pw", NULL), pw); + } + if (method) { + apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-method", NULL), method); + } + if (mimetype) { + apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-mimetype", NULL), mimetype); + } + +} + +/** + * Get the auth username and password from the main request + * notes table, if present. + */ +static void get_notes_auth(request_rec * r, + const char **user, const char **pw, + const char **method, const char **mimetype) +{ + const char *authname; + + /* find the main request */ + while (r->main) { + r = r->main; + } + /* find the first redirect */ + while (r->prev) { + r = r->prev; + } + + /* have we isolated the user and pw before? */ + authname = ap_auth_name(r); + if (user) { + *user = (char *) apr_table_get(r->notes, apr_pstrcat(r->pool, authname, "-user", NULL)); + } + if (pw) { + *pw = (char *) apr_table_get(r->notes, apr_pstrcat(r->pool, authname, "-pw", NULL)); + } + if (method) { + *method = (char *) apr_table_get(r->notes, apr_pstrcat(r->pool, authname, "-method", NULL)); + } + if (mimetype) { + *mimetype = (char *) apr_table_get(r->notes, apr_pstrcat(r->pool, authname, "-mimetype", NULL)); + } + +} + +/** + * Set the auth username and password into the session. + * + * If either the username, or the password are NULL, the username + * and/or password will be removed from the session. + */ +static apr_status_t set_session_auth(request_rec * r, + const char *user, const char *pw, const char *site) +{ + const char *hash = NULL; + const char *authname = ap_auth_name(r); + session_rec *z = NULL; + + if (site) { + hash = ap_md5(r->pool, + (unsigned char *) apr_pstrcat(r->pool, user, ":", site, NULL)); + } + + ap_session_load_fn(r, &z); + ap_session_set(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_USER, NULL), user); + ap_session_set(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_PW, NULL), pw); + ap_session_set(r, z, apr_pstrcat(r->pool, authname, "-" MOD_AUTH_FORM_HASH, NULL), hash); + + return APR_SUCCESS; + +} + +/** + * Get the auth username and password from the main request + * notes table, if present. + */ +static apr_status_t get_session_auth(request_rec * r, + const char **user, const char **pw, const char **hash) +{ + const char *authname = ap_auth_name(r); + session_rec *z = NULL; + ap_session_load_fn(r, &z); + + if (user) { + ap_session_get(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_USER, NULL), user); + } + if (pw) { + ap_session_get(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_PW, NULL), pw); + } + if (hash) { + ap_session_get(r, z, apr_pstrcat(r->pool, authname, "-" MOD_AUTH_FORM_HASH, NULL), hash); + } + + return APR_SUCCESS; + +} + +/** + * Isolate the username and password in a POSTed form with the + * username in the "username" field, and the password in the + * "password" field. + * + * If either the username or the password is missing, this + * function will return HTTP_UNAUTHORIZED. + * + * The location field is considered optional, and will be returned + * if present. + */ +static int get_form_auth(request_rec * r, + const char *username, + const char *password, + const char *location, + const char *method, + const char *mimetype, + const char *body, + const char **sent_user, + const char **sent_pw, + const char **sent_loc, + const char **sent_method, + const char **sent_mimetype, + auth_form_config_rec * conf) +{ + /* sanity check - are we a POST request? */ + + /* find the username and password in the form */ + apr_array_header_t *pairs = NULL; + apr_off_t len; + apr_size_t size; + apr_bucket_brigade *kept_body = NULL; + + /* have we isolated the user and pw before? */ + get_notes_auth(r, sent_user, sent_pw, sent_method, sent_mimetype); + if (*sent_user && *sent_pw) { + return OK; + } + + int res = ap_parse_request_form(r, &pairs, -1, conf->form_size); + if (res != OK) { + return res; + } + while (pairs && !apr_is_empty_array(pairs)) { + ap_form_pair_t *pair = (ap_form_pair_t *) apr_array_pop(pairs); + if (username && !strcmp(pair->name, username) && sent_user) { + apr_brigade_length(pair->value, 1, &len); + char *buffer = apr_palloc(r->pool, len + 1); + size = (apr_size_t) len; + apr_brigade_flatten(pair->value, buffer, &size); + buffer[len] = 0; + *sent_user = buffer; + } + else if (password && !strcmp(pair->name, password) && sent_pw) { + apr_brigade_length(pair->value, 1, &len); + char *buffer = apr_palloc(r->pool, len + 1); + size = (apr_size_t) len; + apr_brigade_flatten(pair->value, buffer, &size); + buffer[len] = 0; + *sent_pw = buffer; + } + else if (location && !strcmp(pair->name, location) && sent_loc) { + apr_brigade_length(pair->value, 1, &len); + char *buffer = apr_palloc(r->pool, len + 1); + size = (apr_size_t) len; + apr_brigade_flatten(pair->value, buffer, &size); + buffer[len] = 0; + *sent_loc = buffer; + } + else if (method && !strcmp(pair->name, method) && sent_method) { + apr_brigade_length(pair->value, 1, &len); + char *buffer = apr_palloc(r->pool, len + 1); + size = (apr_size_t) len; + apr_brigade_flatten(pair->value, buffer, &size); + buffer[len] = 0; + *sent_method = buffer; + } + else if (mimetype && !strcmp(pair->name, mimetype) && sent_mimetype) { + apr_brigade_length(pair->value, 1, &len); + char *buffer = apr_palloc(r->pool, len + 1); + size = (apr_size_t) len; + apr_brigade_flatten(pair->value, buffer, &size); + buffer[len] = 0; + *sent_mimetype = buffer; + } + else if (body && !strcmp(pair->name, body)) { + kept_body = pair->value; + } + } + + /* set the user, even though the user is unauthenticated at this point */ + if (*sent_user) { + r->user = (char *) *sent_user; + } + + /* a missing username or missing password means auth denied */ + if (!sent_user || !*sent_user || !sent_pw || !*sent_pw) { + return HTTP_UNAUTHORIZED; + } + + /* was a body specified? */ + if (kept_body && sent_mimetype && *sent_mimetype) { + apr_off_t length = 0; + + /* + * replace the body, the content-type of the body, and the + * content-length of the body specified in the form to the current + * request. This is safe because in order to get here at all, the + * existing body must already be completely read in. + */ + apr_table_set(r->headers_in, "Content-Type", *sent_mimetype); + apr_brigade_length(kept_body, 1, &length); + apr_table_set(r->headers_in, "Content-Length", apr_off_t_toa(r->pool, length)); + r->kept_body = kept_body; + + } + + /* + * save away the username, password, mimetype and method, so that they + * are available should the auth need to be run again. + */ + set_notes_auth(r, *sent_user, *sent_pw, *sent_method, *sent_mimetype); + + return OK; +} + +/** + * Given a username and site passphrase hash from the session, determine + * whether the site passphrase is valid for this session. + * + * If the site passphrase is NULL, or if the sent_hash is NULL, this + * function returns DECLINED. + * + * If the site passphrase hash does not match the sent hash, this function + * returns AUTH_USER_NOT_FOUND. + * + * On success, returns OK. + */ +static int check_site(request_rec * r, const char *site, const char *sent_user, const char *sent_hash) +{ + + if (site && sent_user && sent_hash) { + const char *hash = ap_md5(r->pool, + (unsigned char *) apr_pstrcat(r->pool, sent_user, ":", site, NULL)); + +// if (APR_SUCCESS == apr_password_validate(apr_pstrcat(r->pool, sent_user, ":", site, NULL), +// sent_hash)) { + if (!strcmp(sent_hash, hash)) { + return OK; + } + else { + return AUTH_USER_NOT_FOUND; + } + } + + return DECLINED; + +} + +/** + * Given a username and password (extracted externally from a cookie), run + * the authnz hooks to determine whether this request is authorized. + * + * Return an HTTP code. + */ +static int check_auth(request_rec * r, const char *sent_user, const char *sent_pw) +{ + authn_status auth_result; + authn_provider_list *current_provider; + auth_form_config_rec *conf = ap_get_module_config(r->per_dir_config, + &auth_form_module); + + current_provider = conf->providers; + do { + const authn_provider *provider; + + /* + * For now, if a provider isn't set, we'll be nice and use the file + * provider. + */ + if (!current_provider) { + provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP, + AUTHN_DEFAULT_PROVIDER, "0"); + + if (!provider || !provider->check_password) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "no authn provider configured"); + auth_result = AUTH_GENERAL_ERROR; + break; + } + apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, AUTHN_DEFAULT_PROVIDER); + } + else { + provider = current_provider->provider; + apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, current_provider->provider_name); + } + + if (!sent_user || !sent_pw) { + auth_result = AUTH_USER_NOT_FOUND; + break; + } + + auth_result = provider->check_password(r, sent_user, sent_pw); + + apr_table_unset(r->notes, AUTHN_PROVIDER_NAME_NOTE); + + /* Something occured. Stop checking. */ + if (auth_result != AUTH_USER_NOT_FOUND) { + break; + } + + /* If we're not really configured for providers, stop now. */ + if (!conf->providers) { + break; + } + + current_provider = current_provider->next; + } while (current_provider); + + if (auth_result != AUTH_GRANTED) { + int return_code; + + /* If we're not authoritative, then any error is ignored. */ + if (!(conf->authoritative) && auth_result != AUTH_DENIED) { + return DECLINED; + } + + switch (auth_result) { + case AUTH_DENIED: + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "user '%s': authentication failure for \"%s\": " + "password Mismatch", + sent_user, r->uri); + return_code = HTTP_UNAUTHORIZED; + break; + case AUTH_USER_NOT_FOUND: + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "user '%s' not found: %s", sent_user, r->uri); + return_code = HTTP_UNAUTHORIZED; + break; + case AUTH_GENERAL_ERROR: + default: + /* + * We'll assume that the module has already said what its error + * was in the logs. + */ + return_code = HTTP_INTERNAL_SERVER_ERROR; + break; + } + + /* If we're returning 403, tell them to try again. */ + if (return_code == HTTP_UNAUTHORIZED) { + note_cookie_auth_failure(r); + } + +/* TODO: Flag the user somehow as to the reason for the failure */ + + return return_code; + } + + return OK; + +} + +/* fake the basic authentication header if configured to do so */ +static void fake_basic_authentication(request_rec *r, auth_form_config_rec *conf, + const char *user, const char *pw) +{ + if (conf->fakebasicauth) { + char *basic = apr_pstrcat(r->pool, user, ":", pw, NULL); + apr_size_t size = (apr_size_t) strlen(basic); + char *base64 = apr_palloc(r->pool, + apr_base64_encode_len(size + 1) * sizeof(char)); + apr_base64_encode(base64, basic, size); + apr_table_set(r->headers_in, "Authorization", + apr_pstrcat(r->pool, "Basic ", base64, NULL)); + } +} + +/** + * Must we use form authentication? If so, extract the cookie and run + * the authnz hooks to determine if the login is valid. + * + * If the login is not valid, a 401 Not Authorized will be returned. It + * is up to the webmaster to ensure this screen displays a suitable login + * form to give the user the opportunity to log in. + */ +static int authenticate_form_user(request_rec * r) +{ + auth_form_config_rec *conf = ap_get_module_config(r->per_dir_config, + &auth_form_module); + const char *sent_user = NULL, *sent_pw = NULL, *sent_loc = NULL, *sent_method = NULL, + *sent_mimetype = NULL, *sent_hash = NULL; + const char *current_auth = NULL; + apr_status_t res; + int rv = HTTP_UNAUTHORIZED; + int rv2 = HTTP_UNAUTHORIZED; + + /* Are we configured to be Form auth? */ + current_auth = ap_auth_type(r); + if (!current_auth || strcasecmp(current_auth, "form")) { + return DECLINED; + } + + /* + * XSS security warning: using cookies to store private data only works + * when the administrator has full control over the source website. When + * in forward-proxy mode, websites are public by definition, and so can + * never be secure. Abort the auth attempt in this case. + */ + if (PROXYREQ_PROXY == r->proxyreq) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, + 0, r, LOG_PREFIX "form auth cannot be used for proxy " + "requests due to XSS risk, access denied: %s", r->uri); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* We need an authentication realm. */ + if (!ap_auth_name(r)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, + 0, r, LOG_PREFIX "need AuthName: %s", r->uri); + return HTTP_INTERNAL_SERVER_ERROR; + } + + r->ap_auth_type = (char *) current_auth; + + /* try get the username and password from a session, if present */ + res = get_session_auth(r, &sent_user, &sent_pw, &sent_hash); + + /* first test whether the site passphrase matches */ + if (APR_SUCCESS == res && sent_user && sent_hash && sent_pw) { + rv = check_site(r, conf->site, sent_user, sent_hash); + if (OK == rv) { + fake_basic_authentication(r, conf, sent_user, sent_pw); + return OK; + } + } + + /* otherwise test for a normal password match */ + if (APR_SUCCESS == res && sent_user && sent_pw) { + rv = check_auth(r, sent_user, sent_pw); + if (OK == rv) { + fake_basic_authentication(r, conf, sent_user, sent_pw); + return OK; + } + } + + /* + * If we reach this point, the request should fail with access denied, + * except for one potential scenario: + * + * If the request is a POST, and the posted form contains user defined fields + * for a username and a password, and the username and password are correct, + * then return the response obtained by a GET to this URL. + * + * If an additional user defined location field is present in the form, + * instead of a GET of the current URL, redirect the browser to the new + * location. + * + * As a further option, if the user defined fields for the type of request, + * the mime type of the body of the request, and the body of the request + * itself are present, replace this request with a new request of the given + * type and with the given body. + * + * Otherwise access is denied. + */ + if (r->method_number == M_POST) { + rv2 = get_form_auth(r, conf->username, conf->password, conf->location, + conf->method, conf->mimetype, conf->body, + &sent_user, &sent_pw, &sent_loc, &sent_method, + &sent_mimetype, conf); + if (OK == rv2) { + rv = check_auth(r, sent_user, sent_pw); + if (OK == rv) { + fake_basic_authentication(r, conf, sent_user, sent_pw); + set_session_auth(r, sent_user, sent_pw, conf->site); + } + } + } + + /* + * did the admin prefer to be redirected to the login page on failure + * instead? + */ + if (HTTP_UNAUTHORIZED == rv && conf->loginrequired) { + apr_table_set(r->headers_out, "Location", conf->loginrequired); + return HTTP_MOVED_PERMANENTLY; + } + + /* did the user ask to be redirected on login success? */ + if (sent_loc) { + apr_table_set(r->headers_out, "Location", sent_loc); + rv = HTTP_MOVED_PERMANENTLY; + } + + /* + * If the user has submitted a sent method along with their form, switch + * in the redirect handler. The redirect handler will replace the form + * login request with the request given inside the login form. + */ + if (OK == rv) { + if (sent_method && sent_mimetype && r->kept_body) { + r->handler = FORM_REDIRECT_HANDLER; + } + else if (sent_method || sent_mimetype || r->kept_body) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, + 0, r, LOG_PREFIX "the login form must contain a field for all " + "three of the method, mimetype and body for the original request " + "to be redirected, reverting to GET: %s", r->uri); + } + } + + /* + * potential security issue: if we return a login to the browser, we must + * send a no-store to make sure a well behaved browser will not try and + * send the login details a second time if the back button is pressed. + * + * if the user has full control over the backend, the + * AuthCookieDisableNoStore can be used to turn this off. + */ + if (HTTP_UNAUTHORIZED == rv && !conf->disable_no_store) { + apr_table_addn(r->headers_out, "Cache-Control", "no-store"); + apr_table_addn(r->err_headers_out, "Cache-Control", "no-store"); + } + + return rv; + +} + +/** + * Handle a login attempt. + * + * If the login session is either missing or form authnz is unsuccessful, a + * 401 Not Authorized will be returned to the browser. The webmaster + * is expected to insert a login form into the 401 Not Authorized + * error screen. + * + * If the webmaster wishes, they can point the form submission at this + * handler, which will redirect the user to the correct page on success. + * On failure, the 401 Not Authorized error screen will be redisplayed, + * where the login attempt can be repeated. + * + */ +static int authenticate_form_login_handler(request_rec * r) +{ + + auth_form_config_rec *conf = ap_get_module_config(r->per_dir_config, + &auth_form_module); + + const char *sent_user = NULL, *sent_pw = NULL, *sent_loc = NULL; + int rv; + + if (strcmp(r->handler, FORM_LOGIN_HANDLER)) { + return DECLINED; + } + + if (r->method_number != M_POST) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "the " FORM_LOGIN_HANDLER " only supports the POST method for %s", + r->uri); + return HTTP_METHOD_NOT_ALLOWED; + } + + rv = get_form_auth(r, conf->username, conf->password, conf->location, + NULL, NULL, NULL, + &sent_user, &sent_pw, &sent_loc, + NULL, NULL, conf); + if (OK == rv) { + rv = check_auth(r, sent_user, sent_pw); + if (OK == rv) { + set_session_auth(r, sent_user, sent_pw, conf->site); + if (sent_loc) { + apr_table_set(r->headers_out, "Location", sent_loc); + return HTTP_MOVED_PERMANENTLY; + } + if (conf->loginsuccess) { + apr_table_set(r->headers_out, "Location", conf->loginsuccess); + return HTTP_MOVED_PERMANENTLY; + } + return HTTP_OK; + } + } + + /* did we prefer to be redirected to the login page on failure instead? */ + if (HTTP_UNAUTHORIZED == rv && conf->loginrequired) { + apr_table_set(r->headers_out, "Location", conf->loginrequired); + return HTTP_MOVED_PERMANENTLY; + } + + return rv; + +} + +/** + * Handle a logout attempt. + * + * If an attempt is made to access this URL, any username and password + * embedded in the session is deleted. + * + * This has the effect of logging the person out. + * + * If a logout URI has been specified, this function will create an + * internal redirect to this page. + */ +static int authenticate_form_logout_handler(request_rec * r) +{ + + auth_form_config_rec *conf = ap_get_module_config(r->per_dir_config, + &auth_form_module); + + if (strcmp(r->handler, FORM_LOGOUT_HANDLER)) { + return DECLINED; + } + + /* remove the username and password, effectively logging the user out */ + set_session_auth(r, NULL, NULL, NULL); + + /* + * make sure the logout page is never cached - otherwise the logout won't + * work! + */ + apr_table_addn(r->headers_out, "Cache-Control", "no-store"); + apr_table_addn(r->err_headers_out, "Cache-Control", "no-store"); + + /* if set, internal redirect to the logout page */ + if (conf->logout) { + apr_table_addn(r->headers_out, "Location", conf->logout); + return HTTP_TEMPORARY_REDIRECT; + } + + return HTTP_OK; + +} + +/** + * Handle a redirect attempt. + * + * If during a form login, the method, mimetype and request body are + * specified, this handler will ensure that this request is included + * as an internal redirect. + * + */ +static int authenticate_form_redirect_handler(request_rec * r) +{ + + request_rec *rr = NULL; + const char *sent_method = NULL, *sent_mimetype = NULL; + + if (strcmp(r->handler, FORM_REDIRECT_HANDLER)) { + return DECLINED; + } + + /* get the method and mimetype from the notes */ + get_notes_auth(r, NULL, NULL, &sent_method, &sent_mimetype); + + if (r->kept_body && sent_method && sent_mimetype) { + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, LOG_PREFIX + "internal redirect to method '%s' and body mimetype '%s' for the " + "uri: %s", sent_method, sent_mimetype, r->uri); + + rr = ap_sub_req_method_uri(sent_method, r->uri, r, r->output_filters); + r->status = ap_run_sub_req(rr); + + } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "internal redirect requested but one or all of method, mimetype or " + "body are NULL: %s", r->uri); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* return the underlying error, or OK on success */ + return r->status == HTTP_OK || r->status == OK ? OK : r->status; + +} + +static void register_hooks(apr_pool_t * p) +{ +#if AP_MODULE_MAGIC_AT_LEAST(20080403,1) + ap_hook_check_authn(authenticate_form_user, NULL, NULL, APR_HOOK_MIDDLE, + AP_AUTH_INTERNAL_PER_CONF); +#else + ap_hook_check_user_id(authenticate_form_user, NULL, NULL, APR_HOOK_MIDDLE); +#endif + ap_hook_handler(authenticate_form_login_handler, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_handler(authenticate_form_logout_handler, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_handler(authenticate_form_redirect_handler, NULL, NULL, APR_HOOK_MIDDLE); +} + +module AP_MODULE_DECLARE_DATA auth_form_module = +{ + STANDARD20_MODULE_STUFF, + create_auth_form_dir_config, /* dir config creater */ + merge_auth_form_dir_config, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + auth_form_cmds, /* command apr_table_t */ + register_hooks /* register hooks */ +};