1
/* Copyright 2009 Justin Erenkrantz and Greg Stein
3
* Licensed under the Apache License, Version 2.0 (the "License");
4
* you may not use this file except in compliance with the License.
5
* You may obtain a copy of the License at
7
* http://www.apache.org/licenses/LICENSE-2.0
9
* Unless required by applicable law or agreed to in writing, software
10
* distributed under the License is distributed on an "AS IS" BASIS,
11
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
* See the License for the specific language governing permissions and
13
* limitations under the License.
17
#include "serf_private.h"
21
#include <apr_base64.h>
22
#include <apr_strings.h>
25
default_auth_response_handler(int code,
26
serf_connection_t *conn,
27
serf_request_t *request,
28
serf_bucket_t *response,
34
static const serf__authn_scheme_t serf_authn_schemes[] = {
40
serf__init_basic_connection,
41
serf__handle_basic_auth,
42
serf__setup_request_basic_auth,
43
default_auth_response_handler,
50
serf__init_basic_connection,
51
serf__handle_basic_auth,
52
serf__setup_request_basic_auth,
53
default_auth_response_handler,
60
serf__init_digest_connection,
61
serf__handle_digest_auth,
62
serf__setup_request_digest_auth,
63
serf__validate_response_digest_auth,
70
serf__init_digest_connection,
71
serf__handle_digest_auth,
72
serf__setup_request_digest_auth,
73
serf__validate_response_digest_auth,
81
serf__init_kerb_connection,
82
serf__handle_kerb_auth,
83
serf__setup_request_kerb_auth,
84
serf__validate_response_kerb_auth,
87
/* ADD NEW AUTHENTICATION IMPLEMENTATIONS HERE (as they're written) */
95
* Baton passed to the response header callback function
101
serf_request_t *request;
102
serf_bucket_t *response;
105
const serf__authn_scheme_t *scheme;
106
const char *last_scheme_name;
109
/* Reads and discards all bytes in the response body. */
110
static apr_status_t discard_body(serf_bucket_t *response)
117
status = serf_bucket_read(response, SERF_READ_ALL_AVAIL, &data, &len);
128
* handle_auth_header is called for each header in the response. It filters
129
* out the Authenticate headers (WWW or Proxy depending on what's needed) and
130
* tries to find a matching scheme handler.
132
* Returns a non-0 value of a matching handler was found.
134
static int handle_auth_header(void *baton,
138
auth_baton_t *ab = baton;
139
int scheme_found = FALSE;
140
const char *auth_name;
141
const char *auth_attr;
142
const serf__authn_scheme_t *scheme = NULL;
143
serf_connection_t *conn = ab->request->conn;
144
serf_context_t *ctx = conn->ctx;
146
/* We're only interested in xxxx-Authenticate headers. */
147
if (strcmp(key, ab->header) != 0)
150
/* Extract the authentication scheme name, and prepare for reading
152
auth_attr = strchr(header, ' ');
154
auth_name = apr_pstrmemdup(ab->pool, header, auth_attr - header);
160
ab->last_scheme_name = auth_name;
162
/* Find the matching authentication handler.
163
Note that we don't reuse the auth scheme stored in the context,
164
as that may have changed. (ex. fallback from ntlm to basic.) */
165
for (scheme = serf_authn_schemes; scheme->code != 0; ++scheme) {
166
if (ab->code == scheme->code &&
167
strcmp(auth_name, scheme->name) == 0 &&
168
ctx->authn_types & scheme->type) {
169
serf__auth_handler_func_t handler = scheme->handle_func;
170
apr_status_t status = 0;
172
/* If this is the first time we use this scheme on this connection,
173
make sure to initialize the authentication handler first. */
174
if (ab->code == 401 && ctx->authn_info.scheme != scheme) {
175
status = scheme->init_ctx_func(ab->code, ctx, ctx->pool);
177
status = scheme->init_conn_func(ab->code, conn, conn->pool);
180
ctx->authn_info.scheme = scheme;
182
ctx->authn_info.scheme = NULL;
185
else if (ab->code == 407 && ctx->proxy_authn_info.scheme != scheme) {
186
status = scheme->init_ctx_func(ab->code, ctx, ctx->pool);
188
status = scheme->init_conn_func(ab->code, conn, conn->pool);
191
ctx->proxy_authn_info.scheme = scheme;
193
ctx->proxy_authn_info.scheme = NULL;
200
status = handler(ab->code, ab->request, ab->response,
201
header, auth_attr, ab->baton, ctx->pool);
204
/* If the authentication fails, cache the error for now. Try the
205
next available scheme. If there's none raise the error. */
207
scheme_found = FALSE;
216
/* If a matching scheme handler was found, we can stop iterating
217
over the response headers - so return a non-0 value. */
221
/* Dispatch authentication handling. This function matches the possible
222
authentication mechanisms with those available. Server and proxy
223
authentication are evaluated separately. */
224
static apr_status_t dispatch_auth(int code,
225
serf_request_t *request,
226
serf_bucket_t *response,
232
if (code == 401 || code == 407) {
233
auth_baton_t ab = { 0 };
234
const char *auth_hdr;
237
ab.status = APR_SUCCESS;
238
ab.request = request;
239
ab.response = response;
243
/* Before iterating over all authn headers, check if there are any. */
245
ab.header = "WWW-Authenticate";
247
ab.header = "Proxy-Authenticate";
249
hdrs = serf_bucket_response_get_headers(response);
250
auth_hdr = serf_bucket_headers_get(hdrs, ab.header);
253
return SERF_ERROR_AUTHN_FAILED;
256
/* Iterate over all headers. Try to find a matching authentication scheme
259
Note: it is possible to have multiple Authentication: headers. We do
260
not want to combine them (per normal header combination rules) as that
261
would make it hard to parse. Instead, we want to individually parse
262
and handle each header in the response, looking for one that we can
265
serf_bucket_headers_do(hdrs,
268
if (ab.status != APR_SUCCESS)
271
if (!ab.scheme || ab.scheme->name == NULL) {
272
/* No matching authentication found. */
273
return SERF_ERROR_AUTHN_NOT_SUPPORTED;
276
/* Validate the response authn headers if needed. */
283
/* Read the headers of the response and try the available
284
handlers if authentication or validation is needed. */
285
apr_status_t serf__handle_auth_response(int *consumed_response,
286
serf_request_t *request,
287
serf_bucket_t *response,
294
*consumed_response = 0;
296
status = serf_bucket_response_status(response, &sl);
297
if (SERF_BUCKET_READ_ERROR(status)) {
300
if (!sl.version && (APR_STATUS_IS_EOF(status) ||
301
APR_STATUS_IS_EAGAIN(status))) {
305
status = serf_bucket_response_wait_for_headers(response);
307
if (!APR_STATUS_IS_EOF(status)) {
311
/* If status is APR_EOF, there were no headers to read.
312
This can be ok in some situations, and it definitely
313
means there's no authentication requested now. */
317
if (sl.code == 401 || sl.code == 407) {
318
/* Authentication requested. */
320
/* Don't bother handling the authentication request if the response
321
wasn't received completely yet. Serf will call serf__handle_auth_response
322
again when more data is received. */
323
status = discard_body(response);
324
*consumed_response = 1;
326
/* Discard all response body before processing authentication. */
327
if (!APR_STATUS_IS_EOF(status)) {
331
status = dispatch_auth(sl.code, request, response, baton, pool);
332
if (status != APR_SUCCESS) {
336
/* Requeue the request with the necessary auth headers. */
337
/* ### Application doesn't know about this request! */
338
serf_connection_priority_request_create(request->conn,
340
request->setup_baton);
349
* base64 encode the authentication data and build an authentication
350
* header in this format:
351
* [SCHEME] [BASE64 of auth DATA]
353
void serf__encode_auth_header(const char **header,
355
const char *data, apr_size_t data_len,
358
apr_size_t encoded_len, scheme_len;
361
encoded_len = apr_base64_encode_len(data_len);
362
scheme_len = strlen(scheme);
364
ptr = apr_palloc(pool, encoded_len + scheme_len + 1);
367
apr_cpystrn(ptr, scheme, scheme_len + 1);
371
apr_base64_encode(ptr, data, data_len);