]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/serf/auth/auth.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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(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  */
44 static 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. */
97 static 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  */
121 static 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  */
209 typedef struct {
210     const char *header;
211     apr_pool_t *pool;
212     apr_hash_t *hdrs;
213 } auth_baton_t;
214
215 static 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. */
248 static 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. */
303 apr_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  */
414 void 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
435 const 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
456 serf__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 }