source: valtobtest/subversion-1.6.2/subversion/libsvn_ra_serf/win32_auth_sspi.c @ 3

Last change on this file since 3 was 3, checked in by valtob, 15 years ago

subversion source 1.6.2 as test

File size: 12.6 KB
Line 
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 ***/
71static svn_atomic_t sspi_initialized = 0;
72static PSecurityFunctionTable sspi = NULL;
73static 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 */
78static svn_error_t *
79initialize_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. */
92static svn_error_t *
93sspi_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
113svn_error_t *
114init_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
142svn_error_t *
143handle_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
184svn_error_t *
185setup_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
199svn_error_t *
200sspi_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
295svn_error_t *
296init_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
325svn_error_t *
326handle_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
367svn_error_t *
368setup_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 */
Note: See TracBrowser for help on using the repository browser.