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