1 | /* |
---|
2 | * win32_auth_sspi.c : authn implementation through SSPI |
---|
3 | * |
---|
4 | * ==================================================================== |
---|
5 | * Copyright (c) 2007 CollabNet. All rights reserved. |
---|
6 | * |
---|
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. |
---|
12 | * |
---|
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 | * ==================================================================== |
---|
17 | */ |
---|
18 | |
---|
19 | /* TODO: |
---|
20 | - remove NTLM dependency so we can reuse SSPI for Kerberos later. */ |
---|
21 | |
---|
22 | /* |
---|
23 | * NTLM authentication for HTTP |
---|
24 | * |
---|
25 | * 1. C --> S: GET |
---|
26 | * |
---|
27 | * C <-- S: 401 Authentication Required |
---|
28 | * WWW-Authenticate: NTLM |
---|
29 | * |
---|
30 | * -> Initialize the NTLM authentication handler. |
---|
31 | * |
---|
32 | * 2. C --> S: GET |
---|
33 | * Authorization: NTLM <Base64 encoded Type 1 message> |
---|
34 | * sspi_ctx->state = sspi_auth_in_progress; |
---|
35 | * |
---|
36 | * C <-- S: 401 Authentication Required |
---|
37 | * WWW-Authenticate: NTLM <Base64 encoded Type 2 message> |
---|
38 | * |
---|
39 | * 3. C --> S: GET |
---|
40 | * Authorization: NTLM <Base64 encoded Type 3 message> |
---|
41 | * sspi_ctx->state = sspi_auth_completed; |
---|
42 | * |
---|
43 | * C <-- S: 200 Ok |
---|
44 | * |
---|
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. |
---|
48 | * |
---|
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. |
---|
53 | */ |
---|
54 | |
---|
55 | /*** Includes ***/ |
---|
56 | #include <string.h> |
---|
57 | |
---|
58 | #include <apr.h> |
---|
59 | #include <apr_base64.h> |
---|
60 | |
---|
61 | #include "svn_error.h" |
---|
62 | |
---|
63 | #include "ra_serf.h" |
---|
64 | #include "win32_auth_sspi.h" |
---|
65 | |
---|
66 | #include "private/svn_atomic.h" |
---|
67 | |
---|
68 | #ifdef SVN_RA_SERF_SSPI_ENABLED |
---|
69 | |
---|
70 | /*** Global variables ***/ |
---|
71 | static svn_atomic_t sspi_initialized = 0; |
---|
72 | static PSecurityFunctionTable sspi = NULL; |
---|
73 | static unsigned int ntlm_maxtokensize = 0; |
---|
74 | |
---|
75 | /* Loads the SSPI function table we can use to call SSPI's public functions. |
---|
76 | * Accepted by svn_atomic__init_once() |
---|
77 | */ |
---|
78 | static svn_error_t * |
---|
79 | initialize_sspi(apr_pool_t* pool) |
---|
80 | { |
---|
81 | sspi = InitSecurityInterface(); |
---|
82 | |
---|
83 | if (sspi) |
---|
84 | return SVN_NO_ERROR; |
---|
85 | |
---|
86 | return svn_error_createf |
---|
87 | (SVN_ERR_RA_SERF_SSPI_INITIALISATION_FAILED, NULL, |
---|
88 | "SSPI Initialization failed."); |
---|
89 | } |
---|
90 | |
---|
91 | /* Calculates the maximum token size based on the authentication protocol. */ |
---|
92 | static svn_error_t * |
---|
93 | sspi_maxtokensize(char *auth_pkg, unsigned int *maxtokensize) |
---|
94 | { |
---|
95 | SECURITY_STATUS status; |
---|
96 | SecPkgInfo *sec_pkg_info = NULL; |
---|
97 | |
---|
98 | status = sspi->QuerySecurityPackageInfo(auth_pkg, |
---|
99 | &sec_pkg_info); |
---|
100 | if (status == SEC_E_OK) |
---|
101 | { |
---|
102 | *maxtokensize = sec_pkg_info->cbMaxToken; |
---|
103 | sspi->FreeContextBuffer(sec_pkg_info); |
---|
104 | } |
---|
105 | else |
---|
106 | return svn_error_createf |
---|
107 | (SVN_ERR_RA_SERF_SSPI_INITIALISATION_FAILED, NULL, |
---|
108 | "SSPI Initialization failed."); |
---|
109 | |
---|
110 | return SVN_NO_ERROR; |
---|
111 | } |
---|
112 | |
---|
113 | svn_error_t * |
---|
114 | init_sspi_connection(svn_ra_serf__session_t *session, |
---|
115 | svn_ra_serf__connection_t *conn, |
---|
116 | apr_pool_t *pool) |
---|
117 | { |
---|
118 | const char *tmp; |
---|
119 | apr_size_t tmp_len; |
---|
120 | |
---|
121 | SVN_ERR(svn_atomic__init_once(&sspi_initialized, initialize_sspi, pool)); |
---|
122 | |
---|
123 | conn->sspi_context = (serf_sspi_context_t*) |
---|
124 | apr_palloc(pool, sizeof(serf_sspi_context_t)); |
---|
125 | conn->sspi_context->ctx.dwLower = 0; |
---|
126 | conn->sspi_context->ctx.dwUpper = 0; |
---|
127 | conn->sspi_context->state = sspi_auth_not_started; |
---|
128 | |
---|
129 | /* Setup the initial request to the server with an SSPI header */ |
---|
130 | SVN_ERR(sspi_get_credentials(NULL, 0, &tmp, &tmp_len, |
---|
131 | conn->sspi_context)); |
---|
132 | svn_ra_serf__encode_auth_header("NTLM", &conn->auth_value, tmp, tmp_len, |
---|
133 | pool); |
---|
134 | conn->auth_header = "Authorization"; |
---|
135 | |
---|
136 | /* Make serf send the initial requests one by one */ |
---|
137 | serf_connection_set_max_outstanding_requests(conn->conn, 1); |
---|
138 | |
---|
139 | return SVN_NO_ERROR; |
---|
140 | } |
---|
141 | |
---|
142 | svn_error_t * |
---|
143 | handle_sspi_auth(svn_ra_serf__session_t *session, |
---|
144 | svn_ra_serf__connection_t *conn, |
---|
145 | serf_request_t *request, |
---|
146 | serf_bucket_t *response, |
---|
147 | char *auth_hdr, |
---|
148 | char *auth_attr, |
---|
149 | apr_pool_t *pool) |
---|
150 | { |
---|
151 | const char *tmp; |
---|
152 | char *base64_token, *token = NULL, *last; |
---|
153 | apr_size_t tmp_len, token_len = 0; |
---|
154 | |
---|
155 | base64_token = apr_strtok(auth_attr, " ", &last); |
---|
156 | if (base64_token) |
---|
157 | { |
---|
158 | token_len = apr_base64_decode_len(base64_token); |
---|
159 | token = apr_palloc(pool, token_len); |
---|
160 | apr_base64_decode(token, base64_token); |
---|
161 | } |
---|
162 | |
---|
163 | /* We can get a whole batch of 401 responses from the server, but we should |
---|
164 | only start the authentication phase once, so if we started authentication |
---|
165 | ignore all responses with initial NTLM authentication header. */ |
---|
166 | if (!token && conn->sspi_context->state != sspi_auth_not_started) |
---|
167 | return SVN_NO_ERROR; |
---|
168 | |
---|
169 | SVN_ERR(sspi_get_credentials(token, token_len, &tmp, &tmp_len, |
---|
170 | conn->sspi_context)); |
---|
171 | |
---|
172 | svn_ra_serf__encode_auth_header(session->auth_protocol->auth_name, |
---|
173 | &conn->auth_value, tmp, tmp_len, pool); |
---|
174 | conn->auth_header = "Authorization"; |
---|
175 | |
---|
176 | /* If the handshake is finished tell serf it can send as much requests as it |
---|
177 | likes. */ |
---|
178 | if (conn->sspi_context->state == sspi_auth_completed) |
---|
179 | serf_connection_set_max_outstanding_requests(conn->conn, 0); |
---|
180 | |
---|
181 | return SVN_NO_ERROR; |
---|
182 | } |
---|
183 | |
---|
184 | svn_error_t * |
---|
185 | setup_request_sspi_auth(svn_ra_serf__connection_t *conn, |
---|
186 | serf_bucket_t *hdrs_bkt) |
---|
187 | { |
---|
188 | /* Take the default authentication header for this connection, if any. */ |
---|
189 | if (conn->auth_header && conn->auth_value) |
---|
190 | { |
---|
191 | serf_bucket_headers_setn(hdrs_bkt, conn->auth_header, conn->auth_value); |
---|
192 | conn->auth_header = NULL; |
---|
193 | conn->auth_value = NULL; |
---|
194 | } |
---|
195 | |
---|
196 | return SVN_NO_ERROR; |
---|
197 | } |
---|
198 | |
---|
199 | svn_error_t * |
---|
200 | sspi_get_credentials(char *token, apr_size_t token_len, const char **buf, |
---|
201 | apr_size_t *buf_len, serf_sspi_context_t *sspi_ctx) |
---|
202 | { |
---|
203 | SecBuffer in_buf, out_buf; |
---|
204 | SecBufferDesc in_buf_desc, out_buf_desc; |
---|
205 | SECURITY_STATUS status; |
---|
206 | DWORD ctx_attr; |
---|
207 | TimeStamp expires; |
---|
208 | CredHandle creds; |
---|
209 | char *target = NULL; |
---|
210 | CtxtHandle *ctx = &(sspi_ctx->ctx); |
---|
211 | |
---|
212 | if (ntlm_maxtokensize == 0) |
---|
213 | sspi_maxtokensize("NTLM", &ntlm_maxtokensize); |
---|
214 | /* Prepare inbound buffer. */ |
---|
215 | in_buf.BufferType = SECBUFFER_TOKEN; |
---|
216 | in_buf.cbBuffer = token_len; |
---|
217 | in_buf.pvBuffer = token; |
---|
218 | in_buf_desc.cBuffers = 1; |
---|
219 | in_buf_desc.ulVersion = SECBUFFER_VERSION; |
---|
220 | in_buf_desc.pBuffers = &in_buf; |
---|
221 | |
---|
222 | /* Prepare outbound buffer. */ |
---|
223 | out_buf.BufferType = SECBUFFER_TOKEN; |
---|
224 | out_buf.cbBuffer = ntlm_maxtokensize; |
---|
225 | out_buf.pvBuffer = (char*)malloc(ntlm_maxtokensize); |
---|
226 | out_buf_desc.cBuffers = 1; |
---|
227 | out_buf_desc.ulVersion = SECBUFFER_VERSION; |
---|
228 | out_buf_desc.pBuffers = &out_buf; |
---|
229 | |
---|
230 | /* Try to accept the server token. */ |
---|
231 | status = sspi->AcquireCredentialsHandle(NULL, /* current user */ |
---|
232 | "NTLM", |
---|
233 | SECPKG_CRED_OUTBOUND, |
---|
234 | NULL, NULL, |
---|
235 | NULL, NULL, |
---|
236 | &creds, |
---|
237 | &expires); |
---|
238 | |
---|
239 | if (status != SEC_E_OK) |
---|
240 | return svn_error_createf |
---|
241 | (SVN_ERR_RA_SERF_SSPI_INITIALISATION_FAILED, NULL, |
---|
242 | "SSPI Initialization failed."); |
---|
243 | |
---|
244 | status = sspi->InitializeSecurityContext(&creds, |
---|
245 | ctx != NULL && ctx->dwLower != 0 |
---|
246 | ? ctx |
---|
247 | : NULL, |
---|
248 | target, |
---|
249 | ISC_REQ_REPLAY_DETECT | |
---|
250 | ISC_REQ_SEQUENCE_DETECT | |
---|
251 | ISC_REQ_CONFIDENTIALITY | |
---|
252 | ISC_REQ_DELEGATE, |
---|
253 | 0, |
---|
254 | SECURITY_NATIVE_DREP, |
---|
255 | &in_buf_desc, |
---|
256 | 0, |
---|
257 | ctx, |
---|
258 | &out_buf_desc, |
---|
259 | &ctx_attr, |
---|
260 | &expires); |
---|
261 | |
---|
262 | /* Finish authentication if SSPI requires so. */ |
---|
263 | if (status == SEC_I_COMPLETE_NEEDED |
---|
264 | || status == SEC_I_COMPLETE_AND_CONTINUE) |
---|
265 | { |
---|
266 | if (sspi->CompleteAuthToken != NULL) |
---|
267 | sspi->CompleteAuthToken(ctx, &out_buf_desc); |
---|
268 | } |
---|
269 | |
---|
270 | *buf = out_buf.pvBuffer; |
---|
271 | *buf_len = out_buf.cbBuffer; |
---|
272 | |
---|
273 | switch (status) |
---|
274 | { |
---|
275 | case SEC_E_OK: |
---|
276 | case SEC_I_COMPLETE_NEEDED: |
---|
277 | sspi_ctx->state = sspi_auth_completed; |
---|
278 | break; |
---|
279 | |
---|
280 | case SEC_I_CONTINUE_NEEDED: |
---|
281 | case SEC_I_COMPLETE_AND_CONTINUE: |
---|
282 | sspi_ctx->state = sspi_auth_in_progress; |
---|
283 | break; |
---|
284 | |
---|
285 | default: |
---|
286 | return svn_error_createf(SVN_ERR_AUTHN_FAILED, NULL, |
---|
287 | "Authentication failed with error 0x%x.", status); |
---|
288 | } |
---|
289 | |
---|
290 | return SVN_NO_ERROR; |
---|
291 | } |
---|
292 | |
---|
293 | /* Proxy authentication */ |
---|
294 | |
---|
295 | svn_error_t * |
---|
296 | init_proxy_sspi_connection(svn_ra_serf__session_t *session, |
---|
297 | svn_ra_serf__connection_t *conn, |
---|
298 | apr_pool_t *pool) |
---|
299 | { |
---|
300 | const char *tmp; |
---|
301 | apr_size_t tmp_len; |
---|
302 | |
---|
303 | SVN_ERR(svn_atomic__init_once(&sspi_initialized, initialize_sspi, pool)); |
---|
304 | |
---|
305 | conn->proxy_sspi_context = (serf_sspi_context_t*) |
---|
306 | apr_palloc(pool, sizeof(serf_sspi_context_t)); |
---|
307 | conn->proxy_sspi_context->ctx.dwLower = 0; |
---|
308 | conn->proxy_sspi_context->ctx.dwUpper = 0; |
---|
309 | conn->proxy_sspi_context->state = sspi_auth_not_started; |
---|
310 | |
---|
311 | /* Setup the initial request to the server with an SSPI header */ |
---|
312 | SVN_ERR(sspi_get_credentials(NULL, 0, &tmp, &tmp_len, |
---|
313 | conn->proxy_sspi_context)); |
---|
314 | svn_ra_serf__encode_auth_header("NTLM", &conn->proxy_auth_value, tmp, |
---|
315 | tmp_len, |
---|
316 | pool); |
---|
317 | conn->proxy_auth_header = "Proxy-Authorization"; |
---|
318 | |
---|
319 | /* Make serf send the initial requests one by one */ |
---|
320 | serf_connection_set_max_outstanding_requests(conn->conn, 1); |
---|
321 | |
---|
322 | return SVN_NO_ERROR; |
---|
323 | } |
---|
324 | |
---|
325 | svn_error_t * |
---|
326 | handle_proxy_sspi_auth(svn_ra_serf__session_t *session, |
---|
327 | svn_ra_serf__connection_t *conn, |
---|
328 | serf_request_t *request, |
---|
329 | serf_bucket_t *response, |
---|
330 | char *auth_hdr, |
---|
331 | char *auth_attr, |
---|
332 | apr_pool_t *pool) |
---|
333 | { |
---|
334 | const char *tmp; |
---|
335 | char *base64_token, *token = NULL, *last; |
---|
336 | apr_size_t tmp_len, token_len = 0; |
---|
337 | |
---|
338 | base64_token = apr_strtok(auth_attr, " ", &last); |
---|
339 | if (base64_token) |
---|
340 | { |
---|
341 | token_len = apr_base64_decode_len(base64_token); |
---|
342 | token = apr_palloc(pool, token_len); |
---|
343 | apr_base64_decode(token, base64_token); |
---|
344 | } |
---|
345 | |
---|
346 | /* We can get a whole batch of 401 responses from the server, but we should |
---|
347 | only start the authentication phase once, so if we started authentication |
---|
348 | ignore all responses with initial NTLM authentication header. */ |
---|
349 | if (!token && conn->proxy_sspi_context->state != sspi_auth_not_started) |
---|
350 | return SVN_NO_ERROR; |
---|
351 | |
---|
352 | SVN_ERR(sspi_get_credentials(token, token_len, &tmp, &tmp_len, |
---|
353 | conn->proxy_sspi_context)); |
---|
354 | |
---|
355 | svn_ra_serf__encode_auth_header(session->proxy_auth_protocol->auth_name, |
---|
356 | &conn->proxy_auth_value, tmp, tmp_len, pool); |
---|
357 | conn->proxy_auth_header = "Proxy-Authorization"; |
---|
358 | |
---|
359 | /* If the handshake is finished tell serf it can send as much requests as it |
---|
360 | likes. */ |
---|
361 | if (conn->proxy_sspi_context->state == sspi_auth_completed) |
---|
362 | serf_connection_set_max_outstanding_requests(conn->conn, 0); |
---|
363 | |
---|
364 | return SVN_NO_ERROR; |
---|
365 | } |
---|
366 | |
---|
367 | svn_error_t * |
---|
368 | setup_request_proxy_sspi_auth(svn_ra_serf__connection_t *conn, |
---|
369 | serf_bucket_t *hdrs_bkt) |
---|
370 | { |
---|
371 | /* Take the default authentication header for this connection, if any. */ |
---|
372 | if (conn->proxy_auth_header && conn->proxy_auth_value) |
---|
373 | { |
---|
374 | serf_bucket_headers_setn(hdrs_bkt, conn->proxy_auth_header, |
---|
375 | conn->proxy_auth_value); |
---|
376 | conn->proxy_auth_header = NULL; |
---|
377 | conn->proxy_auth_value = NULL; |
---|
378 | } |
---|
379 | |
---|
380 | return SVN_NO_ERROR; |
---|
381 | } |
---|
382 | |
---|
383 | #endif /* SVN_RA_SERF_SSPI_ENABLED */ |
---|