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