| /* ==================================================================== |
| * 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; |
| } |