]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/serf/auth/auth.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / serf / auth / auth.c
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
25 static apr_status_t
26 default_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  */
45 static 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. */
98 static 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  */
122 static 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  */
222 typedef struct {
223     const char *header;
224     apr_pool_t *pool;
225     apr_hash_t *hdrs;
226 } auth_baton_t;
227
228 static 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. */
261 static 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. */
316 apr_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  */
427 void 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
448 const 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
469 serf__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 }