/* 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_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"
typedefstruct {
authn_provider_list *providers; char *dir; int authoritative; int authoritative_set; constchar *site; int site_set; constchar *username; int username_set; constchar *password; int password_set;
apr_size_t form_size; int form_size_set; int fakebasicauth; int fakebasicauth_set; constchar *location; int location_set; constchar *method; int method_set; constchar *mimetype; int mimetype_set; constchar *body; int body_set; int disable_no_store; int disable_no_store_set;
ap_expr_info_t *loginsuccess; int loginsuccess_set;
ap_expr_info_t *loginrequired; int loginrequired_set;
ap_expr_info_t *logout; int logout_set;
} auth_form_config_rec;
/* lookup and cache the actual provider now */
newp->provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
newp->provider_name,
AUTHN_PROVIDER_VERSION);
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);
}
/* 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.
*/ staticconstchar *check_string(cmd_parms * cmd, constchar *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;
}
staticconst 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}
};
/** * 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, constchar *user, constchar *pw, constchar *site)
{ constchar *hash = NULL; constchar *authname = ap_auth_name(r);
session_rec *z = NULL;
/** * 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.
*/ staticint get_form_auth(request_rec * r, constchar *username, constchar *password, constchar *location, constchar *method, constchar *mimetype, constchar *body, constchar **sent_user, constchar **sent_pw, constchar **sent_loc, constchar **sent_method, constchar **sent_mimetype,
apr_bucket_brigade **sent_body,
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; int res; char *buffer;
/* have we isolated the user and pw before? */
get_notes_auth(r, sent_user, sent_pw, sent_method, sent_mimetype); if (sent_user && *sent_user && sent_pw && *sent_pw) { return OK;
}
/* set the user, even though the user is unauthenticated at this point */ if (sent_user && *sent_user) {
r->user = (char *) *sent_user;
}
/* a missing username or missing password means auth denied */ if (!sent_user || !*sent_user) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02982) "form parsed, but username field '%s' was missing or empty, unauthorized",
username);
return HTTP_UNAUTHORIZED;
} if (!sent_pw || !*sent_pw) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02983) "form parsed, but password field '%s' was missing or empty, unauthorized",
password);
return HTTP_UNAUTHORIZED;
}
/* * 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_method : NULL,
sent_mimetype ? *sent_mimetype : NULL);
return OK;
}
/* 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.
*/
/** * 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.
*/ staticint check_site(request_rec * r, constchar *site, constchar *sent_user, constchar *sent_hash)
{
/** * 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.
*/ staticint check_authn(request_rec * r, constchar *sent_user, constchar *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,
AUTHN_PROVIDER_VERSION);
/* 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, APLOGNO(01807) "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, APLOGNO(01808) "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 401, 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 */ staticvoid fake_basic_authentication(request_rec *r, auth_form_config_rec *conf, constchar *user, constchar *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_setn(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.
*/ staticint authenticate_form_authn(request_rec * r)
{
auth_form_config_rec *conf = ap_get_module_config(r->per_dir_config,
&auth_form_module); constchar *sent_user = NULL, *sent_pw = NULL, *sent_hash = NULL; constchar *sent_loc = NULL, *sent_method = "GET", *sent_mimetype = NULL; constchar *current_auth = NULL; constchar *err;
apr_status_t res; int rv = HTTP_UNAUTHORIZED;
/* Are we configured to be Form auth? */
current_auth = ap_auth_type(r); if (!current_auth || ap_cstr_casecmp(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, APLOGNO(01809) "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, APLOGNO(01810) "need AuthName: %s", r->uri); return HTTP_INTERNAL_SERVER_ERROR;
}
r->ap_auth_type = (char *) current_auth;
/* try get the username and password from the notes, if present */
get_notes_auth(r, &sent_user, &sent_pw, &sent_method, &sent_mimetype); if (!sent_user || !sent_pw || !*sent_user || !*sent_pw) {
/* otherwise try get the username and password from a session, if present */
res = get_session_auth(r, &sent_user, &sent_pw, &sent_hash);
} else {
res = APR_SUCCESS;
}
/* 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_authn(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. * * Reading the body requires some song and dance, because the input filters * are not yet configured. To work around this problem, we create a * subrequest and use that to create a sane filter stack we can read the * form from. * * The main request is then capped with a kept_body input filter, which has * the effect of guaranteeing the input stack can be safely read a second time. *
*/ if (HTTP_UNAUTHORIZED == rv && r->method_number == M_POST && ap_is_initial_req(r)) {
request_rec *rr;
apr_bucket_brigade *sent_body = NULL;
/* create a subrequest of our current uri */
rr = ap_sub_req_lookup_uri(r->uri, r, r->input_filters);
rr->headers_in = r->headers_in;
/* run the insert_filters hook on the subrequest to ensure a body read can * be done properly.
*/
ap_run_insert_filter(rr);
/* parse the form by reading the subrequest */
rv = get_form_auth(rr, conf->username, conf->password, conf->location,
conf->method, conf->mimetype, conf->body,
&sent_user, &sent_pw, &sent_loc, &sent_method,
&sent_mimetype, &sent_body, conf);
/* make sure any user detected within the subrequest is saved back to * the main request.
*/
r->user = apr_pstrdup(r->pool, rr->user);
/* we cannot clean up rr at this point, as memory allocated to rr is * referenced from the main request. It will be cleaned up when the * main request is cleaned up.
*/
/* insert the kept_body filter on the main request to guarantee the * input filter stack cannot be read a second time, optionally inject * a saved body if one was specified in the login form.
*/ if (sent_body && sent_mimetype) {
apr_table_set(r->headers_in, "Content-Type", sent_mimetype);
r->kept_body = sent_body;
} else {
r->kept_body = apr_brigade_create(r->pool, r->connection->bucket_alloc);
}
ap_request_insert_filter_fn(r);
/* did the form ask to change the method? if so, switch in the redirect handler * to relaunch this request as the subrequest with the new method. If the * form didn't specify a method, the default value GET will force a redirect.
*/ if (sent_method && strcmp(r->method, sent_method)) {
r->handler = FORM_REDIRECT_HANDLER;
}
/* check the authn in the main request, based on the username found */ if (OK == rv) {
rv = check_authn(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); if (sent_loc) {
apr_table_set(r->headers_out, "Location", sent_loc); return HTTP_MOVED_TEMPORARILY;
} if (conf->loginsuccess) { constchar *loginsuccess = ap_expr_str_exec(r,
conf->loginsuccess, &err); if (!err) {
apr_table_set(r->headers_out, "Location", loginsuccess); return HTTP_MOVED_TEMPORARILY;
} else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02339) "Can't evaluate login success expression: %s", err); return HTTP_INTERNAL_SERVER_ERROR;
}
}
}
}
}
/* * did the admin prefer to be redirected to the login page on failure * instead?
*/ if (HTTP_UNAUTHORIZED == rv && conf->loginrequired) { constchar *loginrequired = ap_expr_str_exec(r,
conf->loginrequired, &err); if (!err) {
apr_table_set(r->headers_out, "Location", loginrequired); return HTTP_MOVED_TEMPORARILY;
} else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02340) "Can't evaluate login required expression: %s", err); return HTTP_INTERNAL_SERVER_ERROR;
}
}
/* 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_TEMPORARILY;
}
/* * 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. *
*/ staticint authenticate_form_login_handler(request_rec * r)
{
auth_form_config_rec *conf; constchar *err;
if (strcmp(r->handler, FORM_LOGIN_HANDLER)) { return DECLINED;
}
if (r->method_number != M_POST) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01811) "the " FORM_LOGIN_HANDLER " only supports the POST method for %s",
r->uri); return HTTP_METHOD_NOT_ALLOWED;
}
/* did we prefer to be redirected to the login page on failure instead? */ if (HTTP_UNAUTHORIZED == rv && conf->loginrequired) { constchar *loginrequired = ap_expr_str_exec(r,
conf->loginrequired, &err); if (!err) {
apr_table_set(r->headers_out, "Location", loginrequired); return HTTP_MOVED_TEMPORARILY;
} else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02342) "Can't evaluate login required expression: %s", err); return HTTP_INTERNAL_SERVER_ERROR;
}
}
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.
*/ staticint authenticate_form_logout_handler(request_rec * r)
{
auth_form_config_rec *conf; constchar *err;
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) { constchar *logout = ap_expr_str_exec(r,
conf->logout, &err); if (!err) {
apr_table_addn(r->headers_out, "Location", logout); return HTTP_TEMPORARY_REDIRECT;
} else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02343) "Can't evaluate logout expression: %s", err); return HTTP_INTERNAL_SERVER_ERROR;
}
}
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. *
*/ staticint authenticate_form_redirect_handler(request_rec * r)
{
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, APLOGNO(01812) "internal redirect to method '%s' and body mimetype '%s' for the " "uri: %s", sent_method, sent_mimetype, r->uri);
} else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01813) "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;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.