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
30static apr_status_t
31default_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 */
50static 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. */
103static 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 */
127static 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 */
227typedef struct {
228    const char *header;
229    apr_pool_t *pool;
230    apr_hash_t *hdrs;
231} auth_baton_t;
232
233static 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. */
266static 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. */
321apr_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 */
432void 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
453const 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
474serf__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