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