1 /* ====================================================================
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 * ====================================================================
19 */
20
21 #include "serf.h"
22 #include "serf_private.h"
23 #include "auth.h"
24
25 #include <apr.h>
26 #include <apr_base64.h>
27 #include <apr_strings.h>
28 #include <apr_lib.h>
29
30 static apr_status_t
default_auth_response_handler(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)31 default_auth_response_handler(const serf__authn_scheme_t *scheme,
32 peer_t peer,
33 int code,
34 serf_connection_t *conn,
35 serf_request_t *request,
36 serf_bucket_t *response,
37 apr_pool_t *pool)
38 {
39 return APR_SUCCESS;
40 }
41
42 /* These authentication schemes are in order of decreasing security, the topmost
43 scheme will be used first when the server supports it.
44
45 Each set of handlers should support both server (401) and proxy (407)
46 authentication.
47
48 Use lower case for the scheme names to enable case insensitive matching.
49 */
50 static const serf__authn_scheme_t serf_authn_schemes[] = {
51 #ifdef SERF_HAVE_SPNEGO
52 {
53 "Negotiate",
54 "negotiate",
55 SERF_AUTHN_NEGOTIATE,
56 serf__init_spnego,
57 serf__init_spnego_connection,
58 serf__handle_spnego_auth,
59 serf__setup_request_spnego_auth,
60 serf__validate_response_spnego_auth,
61 },
62 #ifdef WIN32
63 {
64 "NTLM",
65 "ntlm",
66 SERF_AUTHN_NTLM,
67 serf__init_spnego,
68 serf__init_spnego_connection,
69 serf__handle_spnego_auth,
70 serf__setup_request_spnego_auth,
71 serf__validate_response_spnego_auth,
72 },
73 #endif /* #ifdef WIN32 */
74 #endif /* SERF_HAVE_SPNEGO */
75 {
76 "Digest",
77 "digest",
78 SERF_AUTHN_DIGEST,
79 serf__init_digest,
80 serf__init_digest_connection,
81 serf__handle_digest_auth,
82 serf__setup_request_digest_auth,
83 serf__validate_response_digest_auth,
84 },
85 {
86 "Basic",
87 "basic",
88 SERF_AUTHN_BASIC,
89 serf__init_basic,
90 serf__init_basic_connection,
91 serf__handle_basic_auth,
92 serf__setup_request_basic_auth,
93 default_auth_response_handler,
94 },
95 /* ADD NEW AUTHENTICATION IMPLEMENTATIONS HERE (as they're written) */
96
97 /* sentinel */
98 { 0 }
99 };
100
101
102 /* Reads and discards all bytes in the response body. */
discard_body(serf_bucket_t * response)103 static apr_status_t discard_body(serf_bucket_t *response)
104 {
105 apr_status_t status;
106 const char *data;
107 apr_size_t len;
108
109 while (1) {
110 status = serf_bucket_read(response, SERF_READ_ALL_AVAIL, &data, &len);
111
112 if (status) {
113 return status;
114 }
115
116 /* feed me */
117 }
118 }
119
120 /**
121 * handle_auth_header is called for each header in the response. It filters
122 * out the Authenticate headers (WWW or Proxy depending on what's needed) and
123 * tries to find a matching scheme handler.
124 *
125 * Returns a non-0 value of a matching handler was found.
126 */
handle_auth_headers(int code,void * baton,apr_hash_t * hdrs,serf_request_t * request,serf_bucket_t * response,apr_pool_t * pool)127 static int handle_auth_headers(int code,
128 void *baton,
129 apr_hash_t *hdrs,
130 serf_request_t *request,
131 serf_bucket_t *response,
132 apr_pool_t *pool)
133 {
134 const serf__authn_scheme_t *scheme;
135 serf_connection_t *conn = request->conn;
136 serf_context_t *ctx = conn->ctx;
137 apr_status_t status;
138
139 status = SERF_ERROR_AUTHN_NOT_SUPPORTED;
140
141 /* Find the matching authentication handler.
142 Note that we don't reuse the auth scheme stored in the context,
143 as that may have changed. (ex. fallback from ntlm to basic.) */
144 for (scheme = serf_authn_schemes; scheme->name != 0; ++scheme) {
145 const char *auth_hdr;
146 serf__auth_handler_func_t handler;
147 serf__authn_info_t *authn_info;
148
149 if (! (ctx->authn_types & scheme->type))
150 continue;
151
152 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
153 "Client supports: %s\n", scheme->name);
154
155 auth_hdr = apr_hash_get(hdrs, scheme->key, APR_HASH_KEY_STRING);
156
157 if (!auth_hdr)
158 continue;
159
160 if (code == 401) {
161 authn_info = serf__get_authn_info_for_server(conn);
162 } else {
163 authn_info = &ctx->proxy_authn_info;
164 }
165
166 if (authn_info->failed_authn_types & scheme->type) {
167 /* Skip this authn type since we already tried it before. */
168 continue;
169 }
170
171 /* Found a matching scheme */
172 status = APR_SUCCESS;
173
174 handler = scheme->handle_func;
175
176 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
177 "... matched: %s\n", scheme->name);
178
179 /* If this is the first time we use this scheme on this context and/or
180 this connection, make sure to initialize the authentication handler
181 first. */
182 if (authn_info->scheme != scheme) {
183 status = scheme->init_ctx_func(code, ctx, ctx->pool);
184 if (!status) {
185 status = scheme->init_conn_func(scheme, code, conn,
186 conn->pool);
187 if (!status)
188 authn_info->scheme = scheme;
189 else
190 authn_info->scheme = NULL;
191 }
192 }
193
194 if (!status) {
195 const char *auth_attr = strchr(auth_hdr, ' ');
196 if (auth_attr) {
197 auth_attr++;
198 }
199
200 status = handler(code, request, response,
201 auth_hdr, auth_attr, baton, ctx->pool);
202 }
203
204 if (status == APR_SUCCESS)
205 break;
206
207 /* No success authenticating with this scheme, try the next.
208 If no more authn schemes are found the status of this scheme will be
209 returned.
210 */
211 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
212 "%s authentication failed.\n", scheme->name);
213
214 /* Clear per-request auth_baton when switching to next auth scheme. */
215 request->auth_baton = NULL;
216
217 /* Remember failed auth types to skip in future. */
218 authn_info->failed_authn_types |= scheme->type;
219 }
220
221 return status;
222 }
223
224 /**
225 * Baton passed to the store_header_in_dict callback function
226 */
227 typedef struct {
228 const char *header;
229 apr_pool_t *pool;
230 apr_hash_t *hdrs;
231 } auth_baton_t;
232
store_header_in_dict(void * baton,const char * key,const char * header)233 static int store_header_in_dict(void *baton,
234 const char *key,
235 const char *header)
236 {
237 auth_baton_t *ab = baton;
238 const char *auth_attr;
239 char *auth_name, *c;
240
241 /* We're only interested in xxxx-Authenticate headers. */
242 if (strcasecmp(key, ab->header) != 0)
243 return 0;
244
245 /* Extract the authentication scheme name. */
246 auth_attr = strchr(header, ' ');
247 if (auth_attr) {
248 auth_name = apr_pstrmemdup(ab->pool, header, auth_attr - header);
249 }
250 else
251 auth_name = apr_pstrmemdup(ab->pool, header, strlen(header));
252
253 /* Convert scheme name to lower case to enable case insensitive matching. */
254 for (c = auth_name; *c != '\0'; c++)
255 *c = (char)apr_tolower(*c);
256
257 apr_hash_set(ab->hdrs, auth_name, APR_HASH_KEY_STRING,
258 apr_pstrdup(ab->pool, header));
259
260 return 0;
261 }
262
263 /* Dispatch authentication handling. This function matches the possible
264 authentication mechanisms with those available. Server and proxy
265 authentication are evaluated separately. */
dispatch_auth(int code,serf_request_t * request,serf_bucket_t * response,void * baton,apr_pool_t * pool)266 static apr_status_t dispatch_auth(int code,
267 serf_request_t *request,
268 serf_bucket_t *response,
269 void *baton,
270 apr_pool_t *pool)
271 {
272 serf_bucket_t *hdrs;
273
274 if (code == 401 || code == 407) {
275 auth_baton_t ab = { 0 };
276 const char *auth_hdr;
277
278 ab.hdrs = apr_hash_make(pool);
279 ab.pool = pool;
280
281 /* Before iterating over all authn headers, check if there are any. */
282 if (code == 401)
283 ab.header = "WWW-Authenticate";
284 else
285 ab.header = "Proxy-Authenticate";
286
287 hdrs = serf_bucket_response_get_headers(response);
288 auth_hdr = serf_bucket_headers_get(hdrs, ab.header);
289
290 if (!auth_hdr) {
291 return SERF_ERROR_AUTHN_FAILED;
292 }
293 serf__log_skt(AUTH_VERBOSE, __FILE__, request->conn->skt,
294 "%s authz required. Response header(s): %s\n",
295 code == 401 ? "Server" : "Proxy", auth_hdr);
296
297
298 /* Store all WWW- or Proxy-Authenticate headers in a dictionary.
299
300 Note: it is possible to have multiple Authentication: headers. We do
301 not want to combine them (per normal header combination rules) as that
302 would make it hard to parse. Instead, we want to individually parse
303 and handle each header in the response, looking for one that we can
304 work with.
305 */
306 serf_bucket_headers_do(hdrs,
307 store_header_in_dict,
308 &ab);
309
310 /* Iterate over all authentication schemes, in order of decreasing
311 security. Try to find a authentication schema the server support. */
312 return handle_auth_headers(code, baton, ab.hdrs,
313 request, response, pool);
314 }
315
316 return APR_SUCCESS;
317 }
318
319 /* Read the headers of the response and try the available
320 handlers if authentication or validation is needed. */
serf__handle_auth_response(int * consumed_response,serf_request_t * request,serf_bucket_t * response,void * baton,apr_pool_t * pool)321 apr_status_t serf__handle_auth_response(int *consumed_response,
322 serf_request_t *request,
323 serf_bucket_t *response,
324 void *baton,
325 apr_pool_t *pool)
326 {
327 apr_status_t status;
328 serf_status_line sl;
329
330 *consumed_response = 0;
331
332 /* TODO: the response bucket was created by the application, not at all
333 guaranteed that this is of type response_bucket!! */
334 status = serf_bucket_response_status(response, &sl);
335 if (SERF_BUCKET_READ_ERROR(status)) {
336 return status;
337 }
338 if (!sl.version && (APR_STATUS_IS_EOF(status) ||
339 APR_STATUS_IS_EAGAIN(status))) {
340 return status;
341 }
342
343 status = serf_bucket_response_wait_for_headers(response);
344 if (status) {
345 if (!APR_STATUS_IS_EOF(status)) {
346 return status;
347 }
348
349 /* If status is APR_EOF, there were no headers to read.
350 This can be ok in some situations, and it definitely
351 means there's no authentication requested now. */
352 return APR_SUCCESS;
353 }
354
355 if (sl.code == 401 || sl.code == 407) {
356 /* Authentication requested. */
357
358 /* Don't bother handling the authentication request if the response
359 wasn't received completely yet. Serf will call serf__handle_auth_response
360 again when more data is received. */
361 status = discard_body(response);
362 *consumed_response = 1;
363
364 /* Discard all response body before processing authentication. */
365 if (!APR_STATUS_IS_EOF(status)) {
366 return status;
367 }
368
369 status = dispatch_auth(sl.code, request, response, baton, pool);
370 if (status != APR_SUCCESS) {
371 return status;
372 }
373
374 /* Requeue the request with the necessary auth headers. */
375 /* ### Application doesn't know about this request! */
376 if (request->ssltunnel) {
377 serf__ssltunnel_request_create(request->conn,
378 request->setup,
379 request->setup_baton);
380 } else {
381 serf_connection_priority_request_create(request->conn,
382 request->setup,
383 request->setup_baton);
384 }
385
386 return APR_EOF;
387 } else {
388 serf__validate_response_func_t validate_resp;
389 serf_connection_t *conn = request->conn;
390 serf_context_t *ctx = conn->ctx;
391 serf__authn_info_t *authn_info;
392 apr_status_t resp_status = APR_SUCCESS;
393
394
395 /* Validate the response server authn headers. */
396 authn_info = serf__get_authn_info_for_server(conn);
397 if (authn_info->scheme) {
398 validate_resp = authn_info->scheme->validate_response_func;
399 resp_status = validate_resp(authn_info->scheme, HOST, sl.code,
400 conn, request, response, pool);
401 }
402
403 /* Validate the response proxy authn headers. */
404 authn_info = &ctx->proxy_authn_info;
405 if (!resp_status && authn_info->scheme) {
406 validate_resp = authn_info->scheme->validate_response_func;
407 resp_status = validate_resp(authn_info->scheme, PROXY, sl.code,
408 conn, request, response, pool);
409 }
410
411 if (resp_status) {
412 /* If there was an error in the final step of the authentication,
413 consider the reponse body as invalid and discard it. */
414 status = discard_body(response);
415 *consumed_response = 1;
416 if (!APR_STATUS_IS_EOF(status)) {
417 return status;
418 }
419 /* The whole body was discarded, now return our error. */
420 return resp_status;
421 }
422 }
423
424 return APR_SUCCESS;
425 }
426
427 /**
428 * base64 encode the authentication data and build an authentication
429 * header in this format:
430 * [SCHEME] [BASE64 of auth DATA]
431 */
serf__encode_auth_header(const char ** header,const char * scheme,const char * data,apr_size_t data_len,apr_pool_t * pool)432 void serf__encode_auth_header(const char **header,
433 const char *scheme,
434 const char *data, apr_size_t data_len,
435 apr_pool_t *pool)
436 {
437 apr_size_t encoded_len, scheme_len;
438 char *ptr;
439
440 encoded_len = apr_base64_encode_len(data_len);
441 scheme_len = strlen(scheme);
442
443 ptr = apr_palloc(pool, encoded_len + scheme_len + 1);
444 *header = ptr;
445
446 apr_cpystrn(ptr, scheme, scheme_len + 1);
447 ptr += scheme_len;
448 *ptr++ = ' ';
449
450 apr_base64_encode(ptr, data, data_len);
451 }
452
serf__construct_realm(peer_t peer,serf_connection_t * conn,const char * realm_name,apr_pool_t * pool)453 const char *serf__construct_realm(peer_t peer,
454 serf_connection_t *conn,
455 const char *realm_name,
456 apr_pool_t *pool)
457 {
458 if (peer == HOST) {
459 return apr_psprintf(pool, "<%s://%s:%d> %s",
460 conn->host_info.scheme,
461 conn->host_info.hostname,
462 conn->host_info.port,
463 realm_name);
464 } else {
465 serf_context_t *ctx = conn->ctx;
466
467 return apr_psprintf(pool, "<http://%s:%d> %s",
468 ctx->proxy_address->hostname,
469 ctx->proxy_address->port,
470 realm_name);
471 }
472 }
473
serf__get_authn_info_for_server(serf_connection_t * conn)474 serf__authn_info_t *serf__get_authn_info_for_server(serf_connection_t *conn)
475 {
476 serf_context_t *ctx = conn->ctx;
477 serf__authn_info_t *authn_info;
478
479 authn_info = apr_hash_get(ctx->server_authn_info, conn->host_url,
480 APR_HASH_KEY_STRING);
481
482 if (!authn_info) {
483 authn_info = apr_pcalloc(ctx->pool, sizeof(serf__authn_info_t));
484 apr_hash_set(ctx->server_authn_info,
485 apr_pstrdup(ctx->pool, conn->host_url),
486 APR_HASH_KEY_STRING, authn_info);
487 }
488
489 return authn_info;
490 }
491