| /* Copyright 2009 Justin Erenkrantz and Greg Stein |
| * |
| * Licensed 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 "auth.h" |
| |
| #include <apr.h> |
| #include <apr_base64.h> |
| #include <apr_strings.h> |
| |
| static apr_status_t |
| default_auth_response_handler(int code, |
| serf_connection_t *conn, |
| serf_request_t *request, |
| serf_bucket_t *response, |
| apr_pool_t *pool) |
| { |
| return APR_SUCCESS; |
| } |
| |
| static const serf__authn_scheme_t serf_authn_schemes[] = { |
| { |
| 401, |
| "Basic", |
| SERF_AUTHN_BASIC, |
| serf__init_basic, |
| serf__init_basic_connection, |
| serf__handle_basic_auth, |
| serf__setup_request_basic_auth, |
| default_auth_response_handler, |
| }, |
| { |
| 407, |
| "Basic", |
| SERF_AUTHN_BASIC, |
| serf__init_basic, |
| serf__init_basic_connection, |
| serf__handle_basic_auth, |
| serf__setup_request_basic_auth, |
| default_auth_response_handler, |
| }, |
| { |
| 401, |
| "Digest", |
| SERF_AUTHN_DIGEST, |
| serf__init_digest, |
| serf__init_digest_connection, |
| serf__handle_digest_auth, |
| serf__setup_request_digest_auth, |
| serf__validate_response_digest_auth, |
| }, |
| { |
| 407, |
| "Digest", |
| SERF_AUTHN_DIGEST, |
| serf__init_digest, |
| serf__init_digest_connection, |
| serf__handle_digest_auth, |
| serf__setup_request_digest_auth, |
| serf__validate_response_digest_auth, |
| }, |
| #ifdef SERF_HAVE_KERB |
| { |
| 401, |
| "Negotiate", |
| SERF_AUTHN_NEGOTIATE, |
| serf__init_kerb, |
| serf__init_kerb_connection, |
| serf__handle_kerb_auth, |
| serf__setup_request_kerb_auth, |
| serf__validate_response_kerb_auth, |
| }, |
| #endif |
| /* ADD NEW AUTHENTICATION IMPLEMENTATIONS HERE (as they're written) */ |
| |
| /* sentinel */ |
| { 0 } |
| }; |
| |
| |
| /** |
| * Baton passed to the response header callback function |
| */ |
| typedef struct { |
| int code; |
| apr_status_t status; |
| const char *header; |
| serf_request_t *request; |
| serf_bucket_t *response; |
| void *baton; |
| apr_pool_t *pool; |
| const serf__authn_scheme_t *scheme; |
| const char *last_scheme_name; |
| } auth_baton_t; |
| |
| /* Reads and discards all bytes in the response body. */ |
| static apr_status_t discard_body(serf_bucket_t *response) |
| { |
| apr_status_t status; |
| const char *data; |
| apr_size_t len; |
| |
| while (1) { |
| status = serf_bucket_read(response, SERF_READ_ALL_AVAIL, &data, &len); |
| |
| if (status) { |
| return status; |
| } |
| |
| /* feed me */ |
| } |
| } |
| |
| /** |
| * handle_auth_header is called for each header in the response. It filters |
| * out the Authenticate headers (WWW or Proxy depending on what's needed) and |
| * tries to find a matching scheme handler. |
| * |
| * Returns a non-0 value of a matching handler was found. |
| */ |
| static int handle_auth_header(void *baton, |
| const char *key, |
| const char *header) |
| { |
| auth_baton_t *ab = baton; |
| int scheme_found = FALSE; |
| const char *auth_name; |
| const char *auth_attr; |
| const serf__authn_scheme_t *scheme = NULL; |
| serf_connection_t *conn = ab->request->conn; |
| serf_context_t *ctx = conn->ctx; |
| |
| /* We're only interested in xxxx-Authenticate headers. */ |
| if (strcmp(key, ab->header) != 0) |
| return 0; |
| |
| /* Extract the authentication scheme name, and prepare for reading |
| the attributes. */ |
| auth_attr = strchr(header, ' '); |
| if (auth_attr) { |
| auth_name = apr_pstrmemdup(ab->pool, header, auth_attr - header); |
| ++auth_attr; |
| } |
| else |
| auth_name = header; |
| |
| ab->last_scheme_name = auth_name; |
| |
| /* Find the matching authentication handler. |
| Note that we don't reuse the auth scheme stored in the context, |
| as that may have changed. (ex. fallback from ntlm to basic.) */ |
| for (scheme = serf_authn_schemes; scheme->code != 0; ++scheme) { |
| if (ab->code == scheme->code && |
| strcmp(auth_name, scheme->name) == 0 && |
| ctx->authn_types & scheme->type) { |
| serf__auth_handler_func_t handler = scheme->handle_func; |
| apr_status_t status = 0; |
| |
| /* If this is the first time we use this scheme on this connection, |
| make sure to initialize the authentication handler first. */ |
| if (ab->code == 401 && ctx->authn_info.scheme != scheme) { |
| status = scheme->init_ctx_func(ab->code, ctx, ctx->pool); |
| if (!status) { |
| status = scheme->init_conn_func(ab->code, conn, conn->pool); |
| |
| if (!status) |
| ctx->authn_info.scheme = scheme; |
| else |
| ctx->authn_info.scheme = NULL; |
| } |
| } |
| else if (ab->code == 407 && ctx->proxy_authn_info.scheme != scheme) { |
| status = scheme->init_ctx_func(ab->code, ctx, ctx->pool); |
| if (!status) { |
| status = scheme->init_conn_func(ab->code, conn, conn->pool); |
| |
| if (!status) |
| ctx->proxy_authn_info.scheme = scheme; |
| else |
| ctx->proxy_authn_info.scheme = NULL; |
| } |
| } |
| |
| if (!status) { |
| scheme_found = TRUE; |
| ab->scheme = scheme; |
| status = handler(ab->code, ab->request, ab->response, |
| header, auth_attr, ab->baton, ctx->pool); |
| } |
| |
| /* If the authentication fails, cache the error for now. Try the |
| next available scheme. If there's none raise the error. */ |
| if (status) { |
| scheme_found = FALSE; |
| scheme = NULL; |
| ab->status = status; |
| } |
| |
| break; |
| } |
| } |
| |
| /* If a matching scheme handler was found, we can stop iterating |
| over the response headers - so return a non-0 value. */ |
| return scheme_found; |
| } |
| |
| /* Dispatch authentication handling. This function matches the possible |
| authentication mechanisms with those available. Server and proxy |
| authentication are evaluated separately. */ |
| static apr_status_t dispatch_auth(int code, |
| serf_request_t *request, |
| serf_bucket_t *response, |
| void *baton, |
| apr_pool_t *pool) |
| { |
| serf_bucket_t *hdrs; |
| |
| if (code == 401 || code == 407) { |
| auth_baton_t ab = { 0 }; |
| const char *auth_hdr; |
| |
| ab.code = code; |
| ab.status = APR_SUCCESS; |
| ab.request = request; |
| ab.response = response; |
| ab.baton = baton; |
| ab.pool = pool; |
| |
| /* Before iterating over all authn headers, check if there are any. */ |
| if (code == 401) |
| ab.header = "WWW-Authenticate"; |
| else |
| ab.header = "Proxy-Authenticate"; |
| |
| hdrs = serf_bucket_response_get_headers(response); |
| auth_hdr = serf_bucket_headers_get(hdrs, ab.header); |
| |
| if (!auth_hdr) { |
| return SERF_ERROR_AUTHN_FAILED; |
| } |
| |
| /* Iterate over all headers. Try to find a matching authentication scheme |
| handler. |
| |
| Note: it is possible to have multiple Authentication: headers. We do |
| not want to combine them (per normal header combination rules) as that |
| would make it hard to parse. Instead, we want to individually parse |
| and handle each header in the response, looking for one that we can |
| work with. |
| */ |
| serf_bucket_headers_do(hdrs, |
| handle_auth_header, |
| &ab); |
| if (ab.status != APR_SUCCESS) |
| return ab.status; |
| |
| if (!ab.scheme || ab.scheme->name == NULL) { |
| /* No matching authentication found. */ |
| return SERF_ERROR_AUTHN_NOT_SUPPORTED; |
| } |
| } else { |
| /* Validate the response authn headers if needed. */ |
| |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| /* Read the headers of the response and try the available |
| handlers if authentication or validation is needed. */ |
| apr_status_t serf__handle_auth_response(int *consumed_response, |
| serf_request_t *request, |
| serf_bucket_t *response, |
| void *baton, |
| apr_pool_t *pool) |
| { |
| apr_status_t status; |
| serf_status_line sl; |
| |
| *consumed_response = 0; |
| |
| status = serf_bucket_response_status(response, &sl); |
| if (SERF_BUCKET_READ_ERROR(status)) { |
| return status; |
| } |
| if (!sl.version && (APR_STATUS_IS_EOF(status) || |
| APR_STATUS_IS_EAGAIN(status))) { |
| return status; |
| } |
| |
| status = serf_bucket_response_wait_for_headers(response); |
| if (status) { |
| if (!APR_STATUS_IS_EOF(status)) { |
| return status; |
| } |
| |
| /* If status is APR_EOF, there were no headers to read. |
| This can be ok in some situations, and it definitely |
| means there's no authentication requested now. */ |
| return APR_SUCCESS; |
| } |
| |
| if (sl.code == 401 || sl.code == 407) { |
| /* Authentication requested. */ |
| |
| /* Don't bother handling the authentication request if the response |
| wasn't received completely yet. Serf will call serf__handle_auth_response |
| again when more data is received. */ |
| status = discard_body(response); |
| *consumed_response = 1; |
| |
| /* Discard all response body before processing authentication. */ |
| if (!APR_STATUS_IS_EOF(status)) { |
| return status; |
| } |
| |
| status = dispatch_auth(sl.code, request, response, baton, pool); |
| if (status != APR_SUCCESS) { |
| return status; |
| } |
| |
| /* Requeue the request with the necessary auth headers. */ |
| /* ### Application doesn't know about this request! */ |
| serf_connection_priority_request_create(request->conn, |
| request->setup, |
| request->setup_baton); |
| |
| return APR_EOF; |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| /** |
| * base64 encode the authentication data and build an authentication |
| * header in this format: |
| * [SCHEME] [BASE64 of auth DATA] |
| */ |
| void serf__encode_auth_header(const char **header, |
| const char *scheme, |
| const char *data, apr_size_t data_len, |
| apr_pool_t *pool) |
| { |
| apr_size_t encoded_len, scheme_len; |
| char *ptr; |
| |
| encoded_len = apr_base64_encode_len(data_len); |
| scheme_len = strlen(scheme); |
| |
| ptr = apr_palloc(pool, encoded_len + scheme_len + 1); |
| *header = ptr; |
| |
| apr_cpystrn(ptr, scheme, scheme_len + 1); |
| ptr += scheme_len; |
| *ptr++ = ' '; |
| |
| apr_base64_encode(ptr, data, data_len); |
| } |