2
* win32_auth_sspi.c : authn implementation through SSPI
4
* ====================================================================
5
* Copyright (c) 2007 CollabNet. All rights reserved.
7
* This software is licensed as described in the file COPYING, which
8
* you should have received as part of this distribution. The terms
9
* are also available at http://subversion.tigris.org/license-1.html.
10
* If newer versions of this license are posted there, you may use a
11
* newer version instead, at your option.
13
* This software consists of voluntary contributions made by many
14
* individuals. For exact contribution history, see the revision
15
* history and logs, available at http://subversion.tigris.org/.
16
* ====================================================================
20
- remove NTLM dependency so we can reuse SSPI for Kerberos later. */
23
* NTLM authentication for HTTP
27
* C <-- S: 401 Authentication Required
28
* WWW-Authenticate: NTLM
30
* -> Initialize the NTLM authentication handler.
33
* Authorization: NTLM <Base64 encoded Type 1 message>
34
* sspi_ctx->state = sspi_auth_in_progress;
36
* C <-- S: 401 Authentication Required
37
* WWW-Authenticate: NTLM <Base64 encoded Type 2 message>
40
* Authorization: NTLM <Base64 encoded Type 3 message>
41
* sspi_ctx->state = sspi_auth_completed;
45
* This handshake is required for every new connection. If the handshake is
46
* completed successfully, all other requested on the same connection will
47
* be authenticated without needing to pass the WWW-Authenticate header.
49
* Note: Step 1 of the handshake will only happen on the first connection, once
50
* we know the server requires NTLM authentication, the initial requests on the
51
* other connections will include the NTLM Type 1 message, so we start at
52
* step 2 in the handshake.
59
#include <apr_base64.h>
61
#include "svn_error.h"
64
#include "win32_auth_sspi.h"
66
#include "private/svn_atomic.h"
68
#ifdef SVN_RA_SERF_SSPI_ENABLED
70
/*** Global variables ***/
71
static svn_atomic_t sspi_initialized = 0;
72
static PSecurityFunctionTable sspi = NULL;
73
static unsigned int ntlm_maxtokensize = 0;
75
/* Loads the SSPI function table we can use to call SSPI's public functions.
76
* Accepted by svn_atomic__init_once()
79
initialize_sspi(void *baton, apr_pool_t* pool)
81
sspi = InitSecurityInterface();
86
return svn_error_createf
87
(SVN_ERR_RA_SERF_SSPI_INITIALISATION_FAILED, NULL,
88
"SSPI Initialization failed.");
91
/* Calculates the maximum token size based on the authentication protocol. */
93
sspi_maxtokensize(char *auth_pkg, unsigned int *maxtokensize)
95
SECURITY_STATUS status;
96
SecPkgInfo *sec_pkg_info = NULL;
98
status = sspi->QuerySecurityPackageInfo(auth_pkg,
100
if (status == SEC_E_OK)
102
*maxtokensize = sec_pkg_info->cbMaxToken;
103
sspi->FreeContextBuffer(sec_pkg_info);
106
return svn_error_createf
107
(SVN_ERR_RA_SERF_SSPI_INITIALISATION_FAILED, NULL,
108
"SSPI Initialization failed.");
114
init_sspi_connection(svn_ra_serf__session_t *session,
115
svn_ra_serf__connection_t *conn,
121
SVN_ERR(svn_atomic__init_once(&sspi_initialized,
122
initialize_sspi, NULL, pool));
124
conn->sspi_context = (serf_sspi_context_t*)
125
apr_palloc(pool, sizeof(serf_sspi_context_t));
126
conn->sspi_context->ctx.dwLower = 0;
127
conn->sspi_context->ctx.dwUpper = 0;
128
conn->sspi_context->state = sspi_auth_not_started;
130
/* Setup the initial request to the server with an SSPI header */
131
SVN_ERR(sspi_get_credentials(NULL, 0, &tmp, &tmp_len,
132
conn->sspi_context));
133
svn_ra_serf__encode_auth_header("NTLM", &conn->auth_value, tmp, tmp_len,
135
conn->auth_header = "Authorization";
137
/* Make serf send the initial requests one by one */
138
serf_connection_set_max_outstanding_requests(conn->conn, 1);
144
handle_sspi_auth(svn_ra_serf__session_t *session,
145
svn_ra_serf__connection_t *conn,
146
serf_request_t *request,
147
serf_bucket_t *response,
153
char *base64_token, *token = NULL, *last;
154
apr_size_t tmp_len, token_len = 0;
156
base64_token = apr_strtok(auth_attr, " ", &last);
159
token_len = apr_base64_decode_len(base64_token);
160
token = apr_palloc(pool, token_len);
161
apr_base64_decode(token, base64_token);
164
/* We can get a whole batch of 401 responses from the server, but we should
165
only start the authentication phase once, so if we started authentication
166
ignore all responses with initial NTLM authentication header. */
167
if (!token && conn->sspi_context->state != sspi_auth_not_started)
170
SVN_ERR(sspi_get_credentials(token, token_len, &tmp, &tmp_len,
171
conn->sspi_context));
173
svn_ra_serf__encode_auth_header(session->auth_protocol->auth_name,
174
&conn->auth_value, tmp, tmp_len, pool);
175
conn->auth_header = "Authorization";
177
/* If the handshake is finished tell serf it can send as much requests as it
179
if (conn->sspi_context->state == sspi_auth_completed)
180
serf_connection_set_max_outstanding_requests(conn->conn, 0);
186
setup_request_sspi_auth(svn_ra_serf__connection_t *conn,
187
serf_bucket_t *hdrs_bkt)
189
/* Take the default authentication header for this connection, if any. */
190
if (conn->auth_header && conn->auth_value)
192
serf_bucket_headers_setn(hdrs_bkt, conn->auth_header, conn->auth_value);
193
conn->auth_header = NULL;
194
conn->auth_value = NULL;
201
sspi_get_credentials(char *token, apr_size_t token_len, const char **buf,
202
apr_size_t *buf_len, serf_sspi_context_t *sspi_ctx)
204
SecBuffer in_buf, out_buf;
205
SecBufferDesc in_buf_desc, out_buf_desc;
206
SECURITY_STATUS status;
211
CtxtHandle *ctx = &(sspi_ctx->ctx);
213
if (ntlm_maxtokensize == 0)
214
sspi_maxtokensize("NTLM", &ntlm_maxtokensize);
215
/* Prepare inbound buffer. */
216
in_buf.BufferType = SECBUFFER_TOKEN;
217
in_buf.cbBuffer = token_len;
218
in_buf.pvBuffer = token;
219
in_buf_desc.cBuffers = 1;
220
in_buf_desc.ulVersion = SECBUFFER_VERSION;
221
in_buf_desc.pBuffers = &in_buf;
223
/* Prepare outbound buffer. */
224
out_buf.BufferType = SECBUFFER_TOKEN;
225
out_buf.cbBuffer = ntlm_maxtokensize;
226
out_buf.pvBuffer = (char*)malloc(ntlm_maxtokensize);
227
out_buf_desc.cBuffers = 1;
228
out_buf_desc.ulVersion = SECBUFFER_VERSION;
229
out_buf_desc.pBuffers = &out_buf;
231
/* Try to accept the server token. */
232
status = sspi->AcquireCredentialsHandle(NULL, /* current user */
234
SECPKG_CRED_OUTBOUND,
240
if (status != SEC_E_OK)
241
return svn_error_createf
242
(SVN_ERR_RA_SERF_SSPI_INITIALISATION_FAILED, NULL,
243
"SSPI Initialization failed.");
245
status = sspi->InitializeSecurityContext(&creds,
246
ctx != NULL && ctx->dwLower != 0
250
ISC_REQ_REPLAY_DETECT |
251
ISC_REQ_SEQUENCE_DETECT |
252
ISC_REQ_CONFIDENTIALITY |
255
SECURITY_NATIVE_DREP,
263
/* Finish authentication if SSPI requires so. */
264
if (status == SEC_I_COMPLETE_NEEDED
265
|| status == SEC_I_COMPLETE_AND_CONTINUE)
267
if (sspi->CompleteAuthToken != NULL)
268
sspi->CompleteAuthToken(ctx, &out_buf_desc);
271
*buf = out_buf.pvBuffer;
272
*buf_len = out_buf.cbBuffer;
277
case SEC_I_COMPLETE_NEEDED:
278
sspi_ctx->state = sspi_auth_completed;
281
case SEC_I_CONTINUE_NEEDED:
282
case SEC_I_COMPLETE_AND_CONTINUE:
283
sspi_ctx->state = sspi_auth_in_progress;
287
return svn_error_createf(SVN_ERR_AUTHN_FAILED, NULL,
288
"Authentication failed with error 0x%x.", status);
294
/* Proxy authentication */
297
init_proxy_sspi_connection(svn_ra_serf__session_t *session,
298
svn_ra_serf__connection_t *conn,
304
SVN_ERR(svn_atomic__init_once(&sspi_initialized,
305
initialize_sspi, NULL, pool));
307
conn->proxy_sspi_context = (serf_sspi_context_t*)
308
apr_palloc(pool, sizeof(serf_sspi_context_t));
309
conn->proxy_sspi_context->ctx.dwLower = 0;
310
conn->proxy_sspi_context->ctx.dwUpper = 0;
311
conn->proxy_sspi_context->state = sspi_auth_not_started;
313
/* Setup the initial request to the server with an SSPI header */
314
SVN_ERR(sspi_get_credentials(NULL, 0, &tmp, &tmp_len,
315
conn->proxy_sspi_context));
316
svn_ra_serf__encode_auth_header("NTLM", &conn->proxy_auth_value, tmp,
319
conn->proxy_auth_header = "Proxy-Authorization";
321
/* Make serf send the initial requests one by one */
322
serf_connection_set_max_outstanding_requests(conn->conn, 1);
328
handle_proxy_sspi_auth(svn_ra_serf__session_t *session,
329
svn_ra_serf__connection_t *conn,
330
serf_request_t *request,
331
serf_bucket_t *response,
337
char *base64_token, *token = NULL, *last;
338
apr_size_t tmp_len, token_len = 0;
340
base64_token = apr_strtok(auth_attr, " ", &last);
343
token_len = apr_base64_decode_len(base64_token);
344
token = apr_palloc(pool, token_len);
345
apr_base64_decode(token, base64_token);
348
/* We can get a whole batch of 401 responses from the server, but we should
349
only start the authentication phase once, so if we started authentication
350
ignore all responses with initial NTLM authentication header. */
351
if (!token && conn->proxy_sspi_context->state != sspi_auth_not_started)
354
SVN_ERR(sspi_get_credentials(token, token_len, &tmp, &tmp_len,
355
conn->proxy_sspi_context));
357
svn_ra_serf__encode_auth_header(session->proxy_auth_protocol->auth_name,
358
&conn->proxy_auth_value, tmp, tmp_len, pool);
359
conn->proxy_auth_header = "Proxy-Authorization";
361
/* If the handshake is finished tell serf it can send as much requests as it
363
if (conn->proxy_sspi_context->state == sspi_auth_completed)
364
serf_connection_set_max_outstanding_requests(conn->conn, 0);
370
setup_request_proxy_sspi_auth(svn_ra_serf__connection_t *conn,
371
serf_bucket_t *hdrs_bkt)
373
/* Take the default authentication header for this connection, if any. */
374
if (conn->proxy_auth_header && conn->proxy_auth_value)
376
serf_bucket_headers_setn(hdrs_bkt, conn->proxy_auth_header,
377
conn->proxy_auth_value);
378
conn->proxy_auth_header = NULL;
379
conn->proxy_auth_value = NULL;
385
#endif /* SVN_RA_SERF_SSPI_ENABLED */