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(peer_t peer,
27 serf_connection_t *conn,
28 serf_request_t *request,
29 serf_bucket_t *response,
35 static const serf__authn_scheme_t serf_authn_schemes[] = {
41 serf__init_basic_connection,
42 serf__handle_basic_auth,
43 serf__setup_request_basic_auth,
44 default_auth_response_handler,
51 serf__init_basic_connection,
52 serf__handle_basic_auth,
53 serf__setup_request_basic_auth,
54 default_auth_response_handler,
61 serf__init_digest_connection,
62 serf__handle_digest_auth,
63 serf__setup_request_digest_auth,
64 serf__validate_response_digest_auth,
71 serf__init_digest_connection,
72 serf__handle_digest_auth,
73 serf__setup_request_digest_auth,
74 serf__validate_response_digest_auth,
82 serf__init_kerb_connection,
83 serf__handle_kerb_auth,
84 serf__setup_request_kerb_auth,
85 serf__validate_response_kerb_auth,
92 serf__init_kerb_connection,
93 serf__handle_kerb_auth,
94 serf__setup_request_kerb_auth,
95 serf__validate_response_kerb_auth,
98 /* ADD NEW AUTHENTICATION IMPLEMENTATIONS HERE (as they're written) */
106 * Baton passed to the response header callback function
112 serf_request_t *request;
113 serf_bucket_t *response;
116 const serf__authn_scheme_t *scheme;
117 const char *last_scheme_name;
120 /* Reads and discards all bytes in the response body. */
121 static apr_status_t discard_body(serf_bucket_t *response)
128 status = serf_bucket_read(response, SERF_READ_ALL_AVAIL, &data, &len);
139 * handle_auth_header is called for each header in the response. It filters
140 * out the Authenticate headers (WWW or Proxy depending on what's needed) and
141 * tries to find a matching scheme handler.
143 * Returns a non-0 value of a matching handler was found.
145 static int handle_auth_header(void *baton,
149 auth_baton_t *ab = baton;
150 int scheme_found = FALSE;
151 const char *auth_name;
152 const char *auth_attr;
153 const serf__authn_scheme_t *scheme = NULL;
154 serf_connection_t *conn = ab->request->conn;
155 serf_context_t *ctx = conn->ctx;
157 /* We're only interested in xxxx-Authenticate headers. */
158 if (strcmp(key, ab->header) != 0)
161 /* Extract the authentication scheme name, and prepare for reading
163 auth_attr = strchr(header, ' ');
165 auth_name = apr_pstrmemdup(ab->pool, header, auth_attr - header);
171 ab->last_scheme_name = auth_name;
173 /* Find the matching authentication handler.
174 Note that we don't reuse the auth scheme stored in the context,
175 as that may have changed. (ex. fallback from ntlm to basic.) */
176 for (scheme = serf_authn_schemes; scheme->code != 0; ++scheme) {
177 if (! (ab->code == scheme->code &&
178 ctx->authn_types & scheme->type))
181 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
182 "Client supports: %s\n", scheme->name);
183 if (strcmp(auth_name, scheme->name) == 0) {
184 serf__auth_handler_func_t handler = scheme->handle_func;
185 apr_status_t status = 0;
187 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
188 "... matched: %s\n", scheme->name);
189 /* If this is the first time we use this scheme on this connection,
190 make sure to initialize the authentication handler first. */
191 if (ab->code == 401 && ctx->authn_info.scheme != scheme) {
192 status = scheme->init_ctx_func(ab->code, ctx, ctx->pool);
194 status = scheme->init_conn_func(ab->code, conn, conn->pool);
197 ctx->authn_info.scheme = scheme;
199 ctx->authn_info.scheme = NULL;
202 else if (ab->code == 407 && ctx->proxy_authn_info.scheme != scheme) {
203 status = scheme->init_ctx_func(ab->code, ctx, ctx->pool);
205 status = scheme->init_conn_func(ab->code, conn, conn->pool);
208 ctx->proxy_authn_info.scheme = scheme;
210 ctx->proxy_authn_info.scheme = NULL;
217 status = handler(ab->code, ab->request, ab->response,
218 header, auth_attr, ab->baton, ctx->pool);
221 /* If the authentication fails, cache the error for now. Try the
222 next available scheme. If there's none raise the error. */
224 scheme_found = FALSE;
227 /* Let the caller now if the authentication setup was succesful
235 /* If a matching scheme handler was found, we can stop iterating
236 over the response headers - so return a non-0 value. */
240 /* Dispatch authentication handling. This function matches the possible
241 authentication mechanisms with those available. Server and proxy
242 authentication are evaluated separately. */
243 static apr_status_t dispatch_auth(int code,
244 serf_request_t *request,
245 serf_bucket_t *response,
251 if (code == 401 || code == 407) {
252 auth_baton_t ab = { 0 };
253 const char *auth_hdr;
256 ab.status = APR_SUCCESS;
257 ab.request = request;
258 ab.response = response;
262 /* Before iterating over all authn headers, check if there are any. */
264 ab.header = "WWW-Authenticate";
266 ab.header = "Proxy-Authenticate";
268 hdrs = serf_bucket_response_get_headers(response);
269 auth_hdr = serf_bucket_headers_get(hdrs, ab.header);
272 return SERF_ERROR_AUTHN_FAILED;
274 serf__log_skt(AUTH_VERBOSE, __FILE__, request->conn->skt,
275 "%s authz required. Response header(s): %s\n",
276 code == 401 ? "Server" : "Proxy", auth_hdr);
278 /* Iterate over all headers. Try to find a matching authentication scheme
281 Note: it is possible to have multiple Authentication: headers. We do
282 not want to combine them (per normal header combination rules) as that
283 would make it hard to parse. Instead, we want to individually parse
284 and handle each header in the response, looking for one that we can
287 serf_bucket_headers_do(hdrs,
290 if (ab.status != APR_SUCCESS)
293 if (!ab.scheme || ab.scheme->name == NULL) {
294 /* No matching authentication found. */
295 return SERF_ERROR_AUTHN_NOT_SUPPORTED;
302 /* Read the headers of the response and try the available
303 handlers if authentication or validation is needed. */
304 apr_status_t serf__handle_auth_response(int *consumed_response,
305 serf_request_t *request,
306 serf_bucket_t *response,
313 *consumed_response = 0;
315 /* TODO: the response bucket was created by the application, not at all
316 guaranteed that this is of type response_bucket!! */
317 status = serf_bucket_response_status(response, &sl);
318 if (SERF_BUCKET_READ_ERROR(status)) {
321 if (!sl.version && (APR_STATUS_IS_EOF(status) ||
322 APR_STATUS_IS_EAGAIN(status))) {
326 status = serf_bucket_response_wait_for_headers(response);
328 if (!APR_STATUS_IS_EOF(status)) {
332 /* If status is APR_EOF, there were no headers to read.
333 This can be ok in some situations, and it definitely
334 means there's no authentication requested now. */
338 if (sl.code == 401 || sl.code == 407) {
339 /* Authentication requested. */
341 /* Don't bother handling the authentication request if the response
342 wasn't received completely yet. Serf will call serf__handle_auth_response
343 again when more data is received. */
344 status = discard_body(response);
345 *consumed_response = 1;
347 /* Discard all response body before processing authentication. */
348 if (!APR_STATUS_IS_EOF(status)) {
352 status = dispatch_auth(sl.code, request, response, baton, pool);
353 if (status != APR_SUCCESS) {
357 /* Requeue the request with the necessary auth headers. */
358 /* ### Application doesn't know about this request! */
359 serf_connection_priority_request_create(request->conn,
361 request->setup_baton);
365 /* Validate the response authn headers if needed. */
366 serf__validate_response_func_t validate_resp;
367 serf_connection_t *conn = request->conn;
368 serf_context_t *ctx = conn->ctx;
369 apr_status_t resp_status = APR_SUCCESS;
371 if (ctx->authn_info.scheme) {
372 validate_resp = ctx->authn_info.scheme->validate_response_func;
373 resp_status = validate_resp(HOST, sl.code, conn, request, response,
376 if (!resp_status && ctx->proxy_authn_info.scheme) {
377 validate_resp = ctx->proxy_authn_info.scheme->validate_response_func;
378 resp_status = validate_resp(PROXY, sl.code, conn, request, response,
382 /* If there was an error in the final step of the authentication,
383 consider the reponse body as invalid and discard it. */
384 status = discard_body(response);
385 *consumed_response = 1;
386 if (!APR_STATUS_IS_EOF(status)) {
389 /* The whole body was discarded, now return our error. */
398 * base64 encode the authentication data and build an authentication
399 * header in this format:
400 * [SCHEME] [BASE64 of auth DATA]
402 void serf__encode_auth_header(const char **header,
404 const char *data, apr_size_t data_len,
407 apr_size_t encoded_len, scheme_len;
410 encoded_len = apr_base64_encode_len(data_len);
411 scheme_len = strlen(scheme);
413 ptr = apr_palloc(pool, encoded_len + scheme_len + 1);
416 apr_cpystrn(ptr, scheme, scheme_len + 1);
420 apr_base64_encode(ptr, data, data_len);