]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/serf/auth/auth.c
Update libdialog to 1.3-20180621
[FreeBSD/FreeBSD.git] / contrib / serf / auth / auth.c
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
30 static apr_status_t
31 default_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  */
50 static 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. */
103 static 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  */
127 static 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  */
227 typedef struct {
228     const char *header;
229     apr_pool_t *pool;
230     apr_hash_t *hdrs;
231 } auth_baton_t;
232
233 static 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. */
266 static 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. */
321 apr_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  */
432 void 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
453 const 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
474 serf__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 }