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.
66 #include <apr_base64.h>
68 #include "svn_error.h"
71 #include "win32_auth_sspi.h"
73 #ifdef SVN_RA_SERF_SSPI_ENABLED
75 /*** Global variables ***/
76 HANDLE security_dll
= INVALID_HANDLE_VALUE
;
77 INIT_SECURITY_INTERFACE InitSecurityInterface_
;
78 static PSecurityFunctionTable sspi
= NULL
;
79 static unsigned int ntlm_maxtokensize
= 0;
81 #define SECURITY_DLL "security.dll"
83 /* Loads security.dll in memory on the first call. Afterwards the
84 function table SSPI is loaded which we can use it to call SSPI's
89 if (security_dll
!= INVALID_HANDLE_VALUE
)
92 security_dll
= LoadLibrary(SECURITY_DLL
);
93 if (security_dll
!= INVALID_HANDLE_VALUE
)
95 /* Load the function(s) */
96 InitSecurityInterface_
=
97 (INIT_SECURITY_INTERFACE
)GetProcAddress(security_dll
,
98 "InitSecurityInterfaceA");
99 sspi
= InitSecurityInterface_();
105 /* Initialization failed, clean up and raise error */
107 FreeLibrary(security_dll
);
109 return svn_error_createf
110 (SVN_ERR_RA_SERF_SSPI_INITIALISATION_FAILED
, NULL
,
111 "SSPI Initialization failed.");
114 /* Calculates the maximum token size based on the authentication protocol. */
116 sspi_maxtokensize(char *auth_pkg
, unsigned int *maxtokensize
)
118 SECURITY_STATUS status
;
119 SecPkgInfo
*sec_pkg_info
= NULL
;
121 status
= sspi
->QuerySecurityPackageInfo(auth_pkg
,
123 if (status
== SEC_E_OK
)
125 *maxtokensize
= sec_pkg_info
->cbMaxToken
;
126 sspi
->FreeContextBuffer(sec_pkg_info
);
129 return svn_error_createf
130 (SVN_ERR_RA_SERF_SSPI_INITIALISATION_FAILED
, NULL
,
131 "SSPI Initialization failed.");
137 init_sspi_connection(svn_ra_serf__session_t
*session
,
138 svn_ra_serf__connection_t
*conn
,
144 SVN_ERR(load_security_dll());
146 conn
->sspi_context
= (serf_sspi_context_t
*)
147 apr_palloc(pool
, sizeof(serf_sspi_context_t
));
148 conn
->sspi_context
->ctx
.dwLower
= 0;
149 conn
->sspi_context
->ctx
.dwUpper
= 0;
150 conn
->sspi_context
->state
= sspi_auth_not_started
;
152 /* Setup the initial request to the server with an SSPI header */
153 SVN_ERR(sspi_get_credentials(NULL
, 0, &tmp
, &tmp_len
,
154 conn
->sspi_context
));
155 svn_ra_serf__encode_auth_header("NTLM", &conn
->auth_value
, tmp
, tmp_len
,
157 conn
->auth_header
= "Authorization";
159 /* Make serf send the initial requests one by one */
160 serf_connection_set_max_outstanding_requests(conn
->conn
, 1);
166 handle_sspi_auth(svn_ra_serf__session_t
*session
,
167 svn_ra_serf__connection_t
*conn
,
168 serf_request_t
*request
,
169 serf_bucket_t
*response
,
175 char *base64_token
, *token
= NULL
, *last
;
176 apr_size_t tmp_len
, token_len
= 0;
178 base64_token
= apr_strtok(auth_attr
, " ", &last
);
181 token_len
= apr_base64_decode_len(base64_token
);
182 token
= apr_palloc(pool
, token_len
);
183 apr_base64_decode(token
, base64_token
);
186 /* We can get a whole batch of 401 responses from the server, but we should
187 only start the authentication phase once, so if we started authentication
188 ignore all responses with initial NTLM authentication header. */
189 if (!token
&& conn
->sspi_context
->state
!= sspi_auth_not_started
)
192 SVN_ERR(sspi_get_credentials(token
, token_len
, &tmp
, &tmp_len
,
193 conn
->sspi_context
));
195 svn_ra_serf__encode_auth_header(session
->auth_protocol
->auth_name
,
196 &conn
->auth_value
, tmp
, tmp_len
, pool
);
197 conn
->auth_header
= "Authorization";
199 /* If the handshake is finished tell serf it can send as much requests as it
201 if (conn
->sspi_context
->state
== sspi_auth_completed
)
202 serf_connection_set_max_outstanding_requests(conn
->conn
, 0);
208 setup_request_sspi_auth(svn_ra_serf__connection_t
*conn
,
209 serf_bucket_t
*hdrs_bkt
)
211 /* Take the default authentication header for this connection, if any. */
212 if (conn
->auth_header
&& conn
->auth_value
)
214 serf_bucket_headers_setn(hdrs_bkt
, conn
->auth_header
, conn
->auth_value
);
215 conn
->auth_header
= NULL
;
216 conn
->auth_value
= NULL
;
223 sspi_get_credentials(char *token
, apr_size_t token_len
, const char **buf
,
224 apr_size_t
*buf_len
, serf_sspi_context_t
*sspi_ctx
)
226 SecBuffer in_buf
, out_buf
;
227 SecBufferDesc in_buf_desc
, out_buf_desc
;
228 SECURITY_STATUS status
;
233 CtxtHandle
*ctx
= &(sspi_ctx
->ctx
);
235 if (ntlm_maxtokensize
== 0)
236 sspi_maxtokensize("NTLM", &ntlm_maxtokensize
);
237 /* Prepare inbound buffer. */
238 in_buf
.BufferType
= SECBUFFER_TOKEN
;
239 in_buf
.cbBuffer
= token_len
;
240 in_buf
.pvBuffer
= token
;
241 in_buf_desc
.cBuffers
= 1;
242 in_buf_desc
.ulVersion
= SECBUFFER_VERSION
;
243 in_buf_desc
.pBuffers
= &in_buf
;
245 /* Prepare outbound buffer. */
246 out_buf
.BufferType
= SECBUFFER_TOKEN
;
247 out_buf
.cbBuffer
= ntlm_maxtokensize
;
248 out_buf
.pvBuffer
= (char*)malloc(ntlm_maxtokensize
);
249 out_buf_desc
.cBuffers
= 1;
250 out_buf_desc
.ulVersion
= SECBUFFER_VERSION
;
251 out_buf_desc
.pBuffers
= &out_buf
;
253 /* Try to accept the server token. */
254 status
= sspi
->AcquireCredentialsHandle(NULL
, /* current user */
256 SECPKG_CRED_OUTBOUND
,
262 if (status
!= SEC_E_OK
)
263 return svn_error_createf
264 (SVN_ERR_RA_SERF_SSPI_INITIALISATION_FAILED
, NULL
,
265 "SSPI Initialization failed.");
267 status
= sspi
->InitializeSecurityContext(&creds
,
268 ctx
!= NULL
&& ctx
->dwLower
!= 0
272 ISC_REQ_REPLAY_DETECT
|
273 ISC_REQ_SEQUENCE_DETECT
|
274 ISC_REQ_CONFIDENTIALITY
|
277 SECURITY_NATIVE_DREP
,
285 /* Finish authentication if SSPI requires so. */
286 if (status
== SEC_I_COMPLETE_NEEDED
287 || status
== SEC_I_COMPLETE_AND_CONTINUE
)
289 if (sspi
->CompleteAuthToken
!= NULL
)
290 sspi
->CompleteAuthToken(ctx
, &out_buf_desc
);
293 *buf
= out_buf
.pvBuffer
;
294 *buf_len
= out_buf
.cbBuffer
;
299 case SEC_I_COMPLETE_NEEDED
:
300 sspi_ctx
->state
= sspi_auth_completed
;
303 case SEC_I_CONTINUE_NEEDED
:
304 case SEC_I_COMPLETE_AND_CONTINUE
:
305 sspi_ctx
->state
= sspi_auth_in_progress
;
309 return svn_error_createf(SVN_ERR_AUTHN_FAILED
, NULL
,
310 "Authentication failed with error 0x%x.", status
);
316 #endif /* SVN_RA_SERF_SSPI_ENABLED */