1 /* auth.c: ra_serf authentication handling
3 * ====================================================================
4 * Copyright (c) 2007 CollabNet. All rights reserved.
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution. The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
12 * This software consists of voluntary contributions made by many
13 * individuals. For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
21 #include <apr_base64.h>
24 #include "win32_auth_sspi.h"
25 #include "svn_private_config.h"
27 /*** Forward declarations. ***/
30 handle_basic_auth(svn_ra_serf__session_t
*session
,
31 svn_ra_serf__connection_t
*conn
,
32 serf_request_t
*request
,
33 serf_bucket_t
*response
,
39 init_basic_connection(svn_ra_serf__session_t
*session
,
40 svn_ra_serf__connection_t
*conn
,
44 setup_request_basic_auth(svn_ra_serf__connection_t
*conn
,
45 serf_bucket_t
*hdrs_bkt
);
48 handle_proxy_basic_auth(svn_ra_serf__session_t
*session
,
49 svn_ra_serf__connection_t
*conn
,
50 serf_request_t
*request
,
51 serf_bucket_t
*response
,
57 init_proxy_basic_connection(svn_ra_serf__session_t
*session
,
58 svn_ra_serf__connection_t
*conn
,
62 setup_request_proxy_basic_auth(svn_ra_serf__connection_t
*conn
,
63 serf_bucket_t
*hdrs_bkt
);
65 /*** Global variables. ***/
66 static const svn_ra_serf__auth_protocol_t serf_auth_protocols
[] = {
70 init_basic_connection
,
72 setup_request_basic_auth
,
77 init_proxy_basic_connection
,
78 handle_proxy_basic_auth
,
79 setup_request_proxy_basic_auth
,
81 #ifdef SVN_RA_SERF_SSPI_ENABLED
87 setup_request_sspi_auth
,
89 #endif /* SVN_RA_SERF_SSPI_ENABLED */
91 /* ADD NEW AUTHENTICATION IMPLEMENTATIONS HERE (as they're written) */
100 * base64 encode the authentication data and build an authentication
101 * header in this format:
102 * [PROTOCOL] [BASE64 AUTH DATA]
105 svn_ra_serf__encode_auth_header(const char * protocol
, char **header
,
106 const char * data
, apr_size_t data_len
,
109 apr_size_t encoded_len
, proto_len
;
112 encoded_len
= apr_base64_encode_len(data_len
);
113 proto_len
= strlen(protocol
);
115 *header
= apr_palloc(pool
, encoded_len
+ proto_len
+ 1);
118 apr_cpystrn(ptr
, protocol
, proto_len
+ 1);
122 apr_base64_encode(ptr
, data
, data_len
);
127 svn_ra_serf__handle_auth(int code
,
128 svn_ra_serf__session_t
*session
,
129 svn_ra_serf__connection_t
*conn
,
130 serf_request_t
*request
,
131 serf_bucket_t
*response
,
135 const svn_ra_serf__auth_protocol_t
*prot
;
136 char *auth_name
, *auth_attr
, *auth_hdr
, *header
, *header_attr
;
138 hdrs
= serf_bucket_response_get_headers(response
);
140 auth_hdr
= (char*)serf_bucket_headers_get(hdrs
, "WWW-Authenticate");
141 else if (code
== 407)
142 auth_hdr
= (char*)serf_bucket_headers_get(hdrs
, "Proxy-Authenticate");
146 if (session
->auth_protocol
)
147 return svn_error_createf(SVN_ERR_AUTHN_FAILED
, NULL
,
148 "%s Authentication failed",
149 session
->auth_protocol
->auth_name
);
151 return svn_error_create(SVN_ERR_AUTHN_FAILED
, NULL
, NULL
);
154 /* If multiple *-Authenticate headers are found, serf will combine them into
155 one header, with the values separated by a comma. */
156 header
= apr_strtok(auth_hdr
, ",", &header_attr
);
160 svn_boolean_t proto_found
= FALSE
;
161 auth_name
= apr_strtok(header
, " ", &auth_attr
);
163 /* Find the matching authentication handler.
164 Note that we don't reuse the auth protocol stored in the session,
165 as that may have changed. (ex. fallback from ntlm to basic.) */
166 for (prot
= serf_auth_protocols
; prot
->code
!= 0; ++prot
)
168 if (code
== prot
->code
&& strcmp(auth_name
, prot
->auth_name
) == 0)
170 svn_serf__auth_handler_func_t handler
= prot
->handle_func
;
171 svn_error_t
*err
= NULL
;
173 /* If this is the first time we use this protocol in this session,
174 make sure to initialize the authentication part of the session
176 if (code
== 401 && session
->auth_protocol
!= prot
)
178 err
= prot
->init_conn_func(session
, conn
, session
->pool
);
179 if (err
== SVN_NO_ERROR
)
180 session
->auth_protocol
= prot
;
182 session
->auth_protocol
= NULL
;
184 else if (code
== 407 && session
->proxy_auth_protocol
!= prot
)
186 err
= prot
->init_conn_func(session
, conn
, session
->pool
);
187 if (err
== SVN_NO_ERROR
)
188 session
->proxy_auth_protocol
= prot
;
190 session
->proxy_auth_protocol
= NULL
;
193 if (err
== SVN_NO_ERROR
)
196 err
= handler(session
, conn
, request
, response
,
197 header
, auth_attr
, session
->pool
);
201 /* If authentication fails, just try the next available
203 svn_error_clear(err
);
213 header
= apr_strtok(auth_hdr
, ",", &header_attr
);
216 if (prot
->auth_name
== NULL
)
218 /* Support more authentication mechanisms. */
219 return svn_error_createf(SVN_ERR_AUTHN_FAILED
, NULL
,
220 "%s authentication not supported.\n"
221 "Authentication failed", auth_name
);
228 handle_basic_auth(svn_ra_serf__session_t
*session
,
229 svn_ra_serf__connection_t
*conn
,
230 serf_request_t
*request
,
231 serf_bucket_t
*response
,
237 char *last
, *realm_name
;
238 svn_auth_cred_simple_t
*simple_creds
;
248 attr
= apr_strtok(auth_attr
, "=", &last
);
249 if (strcmp(attr
, "realm") == 0)
251 realm_name
= apr_strtok(NULL
, "=", &last
);
252 if (realm_name
[0] == '\"')
254 apr_size_t realm_len
;
256 realm_len
= strlen(realm_name
);
257 if (realm_name
[realm_len
- 1] == '\"')
259 realm_name
[realm_len
- 1] = '\0';
266 return svn_error_create
267 (SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
268 _("Missing 'realm' attribute in Authorization header."));
272 return svn_error_create
273 (SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
274 _("Missing 'realm' attribute in Authorization header."));
277 if (session
->repos_url
.port_str
)
279 port
= session
->repos_url
.port
;
283 port
= apr_uri_port_of_scheme(session
->repos_url
.scheme
);
286 session
->realm
= apr_psprintf(session
->pool
, "<%s://%s:%d> %s",
287 session
->repos_url
.scheme
,
288 session
->repos_url
.hostname
,
292 SVN_ERR(svn_auth_first_credentials(&creds
,
293 &session
->auth_state
,
294 SVN_AUTH_CRED_SIMPLE
,
296 session
->wc_callbacks
->auth_baton
,
301 SVN_ERR(svn_auth_next_credentials(&creds
,
306 session
->auth_attempts
++;
308 if (!creds
|| session
->auth_attempts
> 4)
310 /* No more credentials. */
311 return svn_error_create(SVN_ERR_AUTHN_FAILED
, NULL
,
312 "No more credentials or we tried too many times.\n"
313 "Authentication failed");
316 simple_creds
= creds
;
318 tmp
= apr_pstrcat(session
->pool
,
319 simple_creds
->username
, ":", simple_creds
->password
, NULL
);
320 tmp_len
= strlen(tmp
);
322 svn_ra_serf__encode_auth_header(session
->auth_protocol
->auth_name
,
323 &session
->auth_value
, tmp
, tmp_len
, pool
);
324 session
->auth_header
= "Authorization";
326 /* FIXME Come up with a cleaner way of changing the connection auth. */
327 for (i
= 0; i
< session
->num_conns
; i
++)
329 session
->conns
[i
]->auth_header
= session
->auth_header
;
330 session
->conns
[i
]->auth_value
= session
->auth_value
;
337 init_basic_connection(svn_ra_serf__session_t
*session
,
338 svn_ra_serf__connection_t
*conn
,
341 conn
->auth_header
= session
->auth_header
;
342 conn
->auth_value
= session
->auth_value
;
348 setup_request_basic_auth(svn_ra_serf__connection_t
*conn
,
349 serf_bucket_t
*hdrs_bkt
)
351 /* Take the default authentication header for this connection, if any. */
352 if (conn
->auth_header
&& conn
->auth_value
)
354 serf_bucket_headers_setn(hdrs_bkt
, conn
->auth_header
, conn
->auth_value
);
361 handle_proxy_basic_auth(svn_ra_serf__session_t
*session
,
362 svn_ra_serf__connection_t
*conn
,
363 serf_request_t
*request
,
364 serf_bucket_t
*response
,
373 tmp
= apr_pstrcat(session
->pool
,
374 session
->proxy_username
, ":",
375 session
->proxy_password
, NULL
);
376 tmp_len
= strlen(tmp
);
378 session
->proxy_auth_attempts
++;
380 if (session
->proxy_auth_attempts
> 1)
382 /* No more credentials. */
383 return svn_error_create(SVN_ERR_AUTHN_FAILED
, NULL
,
384 "Proxy authentication failed");
387 svn_ra_serf__encode_auth_header(session
->proxy_auth_protocol
->auth_name
,
388 &session
->proxy_auth_value
,
390 session
->proxy_auth_header
= "Proxy-Authorization";
392 /* FIXME Come up with a cleaner way of changing the connection auth. */
393 for (i
= 0; i
< session
->num_conns
; i
++)
395 session
->conns
[i
]->proxy_auth_header
= session
->proxy_auth_header
;
396 session
->conns
[i
]->proxy_auth_value
= session
->proxy_auth_value
;
403 init_proxy_basic_connection(svn_ra_serf__session_t
*session
,
404 svn_ra_serf__connection_t
*conn
,
407 conn
->proxy_auth_header
= session
->proxy_auth_header
;
408 conn
->proxy_auth_value
= session
->proxy_auth_value
;
414 setup_request_proxy_basic_auth(svn_ra_serf__connection_t
*conn
,
415 serf_bucket_t
*hdrs_bkt
)
417 /* Take the default authentication header for this connection, if any. */
418 if (conn
->proxy_auth_header
&& conn
->proxy_auth_value
)
420 serf_bucket_headers_setn(hdrs_bkt
, conn
->proxy_auth_header
,
421 conn
->proxy_auth_value
);