blob: 898ed888631dd5bfa4b9b70e301e6925c2e8e3e8 [file] [log] [blame]
/* ====================================================================
* 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 <serf.h>
#include <serf_private.h>
#include "apr_errno.h"
#include "auth.h"
static bool validate_user_authn(const serf__authn_scheme_t *scheme)
{
return (scheme->type & *serf__authn_user__type_mask
&& scheme->user_magic == serf__authn_user__magic);
}
static serf__authn_info_t *get_authn_info(int code, serf_connection_t *conn)
{
switch (code)
{
case SERF_AUTHN_CODE_HOST:
return serf__get_authn_info_for_server(conn);
case SERF_AUTHN_CODE_PROXY:
return &conn->ctx->proxy_authn_info;
}
/* FIXME: ??? Shouldn't be possible. */
return NULL;
}
/* Used for serf__authn_info_t::baton */
struct authn_baton_wrapper {
/* The connection's pipelining state before we changed it. */
int pipelining;
/* Was pipelining reset to its previous value?. */
bool pipelining_was_reset;
/* The user-defined scheme's per-connection baton. */
void *user_authn_baton;
};
/* Common callback parameter validation. */
typedef apr_status_t (*callback_fn_t)();
static apr_status_t validate_handler(serf_config_t *config,
const serf__authn_scheme_t *scheme,
int peer_id,
const char *callback_name,
callback_fn_t callback_func,
serf__authn_info_t *authn_info)
{
if (!validate_user_authn(scheme)) {
serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, config,
"Not a user-defined scheme: %s\n", scheme->name);
return APR_EINVAL;
}
if (!scheme->user_init_conn_func) {
serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, config,
"User-defined scheme %s: missing callback: %s\n",
scheme->name, callback_name);
return APR_ENOTIMPL;
}
if (!authn_info) {
serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, config,
"User-defined scheme %s %s: invalid peer: %d\n",
scheme->name, callback_name, peer_id);
return APR_ERANGE;
}
return APR_SUCCESS;
}
/* Reset request pipelining on the connection if required. */
static void maybe_reset_pipelining(const serf__authn_scheme_t *scheme,
struct authn_baton_wrapper *authn_baton,
serf_connection_t *conn,
apr_status_t status,
int reset_pipelining)
{
if (!reset_pipelining
|| status != APR_SUCCESS
|| authn_baton->pipelining_was_reset
|| (scheme->user_flags & SERF_AUTHN_FLAG_PIPE))
{
serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, conn->config,
"User-defined scheme %s: no pipelining change for %s\n",
scheme->name, conn->host_url);
return;
}
serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, conn->config,
"User-defined scheme %s: pipelining reset to %s for %s\n",
scheme->name,
authn_baton->pipelining ? "on" : "off",
conn->host_url);
serf__connection_set_pipelining(conn, authn_baton->pipelining);
authn_baton->pipelining_was_reset = true;
}
apr_status_t
serf__authn_user__init_conn(const serf__authn_scheme_t *scheme,
int code,
serf_connection_t *conn,
apr_pool_t *pool)
{
serf__authn_info_t *const authn_info = get_authn_info(code, conn);
struct authn_baton_wrapper *authn_baton;
apr_status_t status;
status = validate_handler(conn->config, scheme, code, "init-conn",
scheme->user_init_conn_func,
authn_info);
if (status)
return status;
serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, conn->config,
"User-defined scheme %s: callback: init-conn\n",
scheme->name);
authn_baton = authn_info->baton;
if (authn_baton == NULL) {
apr_pool_t *scratch_pool;
serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, conn->config,
"User-defined scheme %s: create authn baton\n",
scheme->name);
apr_pool_create(&scratch_pool, pool);
authn_baton = apr_pcalloc(pool, sizeof(*authn_baton));
status = scheme->user_init_conn_func(&authn_baton->user_authn_baton,
scheme->user_baton, code,
pool, scratch_pool);
apr_pool_destroy(scratch_pool);
if (status == APR_SUCCESS)
authn_info->baton = authn_baton;
}
/* Turn off pipelining if the scheme requires it. */
if (status == APR_SUCCESS
&& !(scheme->user_flags & SERF_AUTHN_FLAG_PIPE)) {
serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, conn->config,
"User-defined scheme %s: pipelining off for %s\n",
scheme->name, conn->host_url);
authn_baton->pipelining = serf__connection_set_pipelining(conn, 0);
authn_baton->pipelining_was_reset = false;
} else {
authn_baton->pipelining_was_reset = true;
}
return status;
}
apr_status_t
serf__authn_user__handle(const serf__authn_scheme_t *scheme,
int code,
serf_request_t *request,
serf_bucket_t *response,
const char *auth_hdr,
const char *auth_attr,
apr_pool_t *pool)
{
serf_connection_t *const conn = request->conn;
serf__authn_info_t *const authn_info = get_authn_info(code, conn);
serf_context_t *const ctx = conn->ctx;
struct authn_baton_wrapper *authn_baton;
int reset_pipelining = 0;
char *username, *password;
apr_pool_t *scratch_pool;
apr_hash_t *auth_param;
apr_status_t status;
status = validate_handler(conn->config, scheme, code, "handle-auth",
scheme->user_handle_func,
authn_info);
if (status)
return status;
serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, ctx->config,
"User-defined scheme %s: callback: handle-auth\n",
scheme->name);
/* The credentials callback must be set if the scheme requires it. */
if (scheme->user_flags & SERF_AUTHN_FLAG_CREDS) {
bool failed = false;
if (!ctx->cred_cb) {
serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, conn->config,
"User-defined scheme %s handle-auth:"
" missing credentials callback\n",
scheme->name);
failed = true;
}
if (!scheme->user_get_realm_func) {
serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, ctx->config,
"User-defined scheme %s handle-auth:"
" missing callback: get-realm\n",
scheme->name);
failed = true;
}
if (failed)
return SERF_ERROR_AUTHN_FAILED;
}
authn_baton = authn_info->baton;
apr_pool_create(&scratch_pool, pool);
status = APR_SUCCESS;
auth_param = serf__parse_authn_parameters(auth_attr, scratch_pool);
if (scheme->user_flags & SERF_AUTHN_FLAG_CREDS) {
const char *realm_name;
status = scheme->user_get_realm_func(&realm_name,
scheme->user_baton,
authn_baton->user_authn_baton,
auth_hdr, auth_param,
scratch_pool, scratch_pool);
if (!status) {
const char *const realm = serf__construct_realm(
SERF__PEER_FROM_CODE(code), conn, realm_name, scratch_pool);
status = serf__provide_credentials(ctx,
&username, &password,
request,
code, scheme->name,
realm, scratch_pool);
}
if (status)
goto cleanup;
} else {
username = password = NULL;
}
status = scheme->user_handle_func(&reset_pipelining,
scheme->user_baton,
authn_baton->user_authn_baton, code,
auth_hdr, auth_param,
SERF__HEADER_FROM_CODE(code),
username, password,
request, response,
pool, scratch_pool);
cleanup:
maybe_reset_pipelining(scheme, authn_baton, conn, status, reset_pipelining);
apr_pool_destroy(scratch_pool);
return status;
}
apr_status_t
serf__authn_user__setup_request(const serf__authn_scheme_t *scheme,
peer_t peer,
int code, /* Ignored, always 0. */
serf_connection_t *conn,
serf_request_t *request,
const char *method,
const char *uri,
serf_bucket_t *hdrs_bkt)
{
const int peer_id = SERF__CODE_FROM_PEER(peer);
serf__authn_info_t *const authn_info = get_authn_info(peer_id, conn);
struct authn_baton_wrapper *authn_baton;
int reset_pipelining = 0;
apr_pool_t *scratch_pool;
apr_status_t status;
status = validate_handler(conn->config, scheme, peer_id, "setup-request",
scheme->user_setup_request_func,
authn_info);
if (status)
return status;
if (!authn_info->baton) {
serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, conn->config,
"User-defined scheme %s setup-request: no authn data\n",
scheme->name);
return APR_ERANGE;
}
serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, conn->config,
"User-defined scheme %s: callback: setup-request\n",
scheme->name);
authn_baton = authn_info->baton;
apr_pool_create(&scratch_pool, conn->pool);
status = scheme->user_setup_request_func(&reset_pipelining,
scheme->user_baton,
authn_baton->user_authn_baton,
conn, request,
method, uri, hdrs_bkt,
scratch_pool);
maybe_reset_pipelining(scheme, authn_baton, conn, status, reset_pipelining);
apr_pool_destroy(scratch_pool);
return status;
}
apr_status_t
serf__authn_user__validate_response(const serf__authn_scheme_t *scheme,
peer_t peer,
int code,
serf_connection_t *conn,
serf_request_t *request,
serf_bucket_t *response,
apr_pool_t *pool)
{
const int peer_id = SERF__CODE_FROM_PEER(peer);
const char *const info_header = SERF__INFO_HEADER_FROM_PEER(peer);
serf__authn_info_t *const authn_info = get_authn_info(peer_id, conn);
serf_bucket_t *headers = serf_bucket_response_get_headers(response);
apr_hash_t *info_params = NULL;
int reset_pipelining = 0;
struct authn_baton_wrapper *authn_baton;
const char *auth_attr;
apr_pool_t *scratch_pool;
apr_status_t status;
status = validate_handler(conn->config, scheme, peer_id,
"validate-response",
scheme->user_validate_response_func,
authn_info);
if (status)
return status;
if (!authn_info->baton) {
serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, conn->config,
"User-defined scheme %s validate-response: no authn data\n",
scheme->name);
return APR_ERANGE;
}
serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, conn->config,
"User-defined scheme %s: callback: validate-response\n",
scheme->name);
authn_baton = authn_info->baton;
apr_pool_create(&scratch_pool, conn->pool);
auth_attr = serf_bucket_headers_get(headers, info_header);
if (auth_attr) {
info_params = serf__parse_authn_parameters(auth_attr, scratch_pool);
}
status = scheme->user_validate_response_func(&reset_pipelining,
scheme->user_baton,
authn_baton->user_authn_baton,
code, conn, info_params,
request, response,
scratch_pool);
maybe_reset_pipelining(scheme, authn_baton, conn, status, reset_pipelining);
apr_pool_destroy(scratch_pool);
return status;
}