1 /*
2 Microsoft SSPI based authentication routines
3 Copyright (C) 2004-2005, Vladimir Berezniker @ http://public.xdi.org/=vmpn
4 Copyright (C) 2007, Yves Martin <ymartin59@free.fr>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
15
16 You should have received a copy of the GNU Library General Public
17 License along with this library; if not, write to the Free
18 Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
19 MA 02111-1307, USA
20
21 */
22
23 #include "config.h"
24
25 #include "ne_utils.h"
26 #include "ne_string.h"
27 #include "ne_socket.h"
28 #include "ne_sspi.h"
29
30 #ifdef HAVE_SSPI
31
32 #define SEC_SUCCESS(Status) ((Status) >= 0)
33
34 #ifndef SECURITY_ENTRYPOINT /* Missing in MingW 3.7 */
35 #define SECURITY_ENTRYPOINT "InitSecurityInterfaceA"
36 #endif
37
38 struct SSPIContextStruct {
39 CtxtHandle context;
40 char *serverName;
41 CredHandle credentials;
42 int continueNeeded;
43 int authfinished;
44 char *mechanism;
45 int ntlm;
46 ULONG maxTokenSize;
47 };
48
49 typedef struct SSPIContextStruct SSPIContext;
50
51 static ULONG negotiateMaxTokenSize = 0;
52 static ULONG ntlmMaxTokenSize = 0;
53 static HINSTANCE hSecDll = NULL;
54 static PSecurityFunctionTable pSFT = NULL;
55 static int initialized = 0;
56
57 /*
58 * Query specified package for it's maximum token size.
59 */
getMaxTokenSize(char * package,ULONG * maxTokenSize)60 static int getMaxTokenSize(char *package, ULONG * maxTokenSize)
61 {
62 SECURITY_STATUS status;
63 SecPkgInfo *packageSecurityInfo = NULL;
64
65 status = pSFT->QuerySecurityPackageInfo(package, &packageSecurityInfo);
66 if (status == SEC_E_OK) {
67 *maxTokenSize = packageSecurityInfo->cbMaxToken;
68 if (pSFT->FreeContextBuffer(packageSecurityInfo) != SEC_E_OK) {
69 NE_DEBUG(NE_DBG_HTTPAUTH,
70 "sspi: Unable to free security package info.");
71 }
72 } else {
73 NE_DEBUG(NE_DBG_HTTPAUTH,
74 "sspi: QuerySecurityPackageInfo [failed] [%x].", status);
75 return -1;
76 }
77
78 return 0;
79 }
80
81 /*
82 * Initialize all the SSPI data
83 */
initDll(HINSTANCE hSecDll)84 static void initDll(HINSTANCE hSecDll)
85 {
86 INIT_SECURITY_INTERFACE initSecurityInterface = NULL;
87
88 initSecurityInterface =
89 (INIT_SECURITY_INTERFACE) GetProcAddress(hSecDll,
90 SECURITY_ENTRYPOINT);
91
92 if (initSecurityInterface == NULL) {
93 NE_DEBUG(NE_DBG_HTTPAUTH,
94 "sspi: Obtaining security interface [fail].\n");
95 initialized = -1;
96 return;
97 } else {
98 NE_DEBUG(NE_DBG_HTTPAUTH,
99 "sspi: Obtaining security interface [ok].\n");
100 }
101
102 pSFT = (initSecurityInterface) ();
103
104 if (pSFT == NULL) {
105 NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Security Function Table [fail].\n");
106 initialized = -2;
107 return;
108 } else {
109 NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Security Function Table [ok].\n");
110 }
111
112 if (getMaxTokenSize("Negotiate", &negotiateMaxTokenSize)) {
113 NE_DEBUG(NE_DBG_HTTPAUTH,
114 "sspi: Unable to get negotiate maximum packet size");
115 initialized = -3;
116 }
117
118 if (getMaxTokenSize("NTLM", &ntlmMaxTokenSize)) {
119 NE_DEBUG(NE_DBG_HTTPAUTH,
120 "sspi: Unable to get negotiate maximum packet size");
121 initialized = -3;
122 }
123 }
124
125 /*
126 * This function needs to be called at least once before using any other.
127 */
ne_sspi_init(void)128 int ne_sspi_init(void)
129 {
130 if (initialized) {
131 return 0;
132 }
133
134 NE_DEBUG(NE_DBG_SOCKET, "sspiInit\n");
135 NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Loading security dll.\n");
136 hSecDll = LoadLibrary("security.dll");
137
138 if (hSecDll == NULL) {
139 NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Loading of security dll [fail].\n");
140 } else {
141 NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Loading of security dll [ok].\n");
142 initDll(hSecDll);
143 if (initialized == 0) {
144 initialized = 1;
145 }
146 }
147
148 NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: sspiInit [%d].\n", initialized);
149 if (initialized < 0) {
150 return initialized;
151 } else {
152 return 0;
153 }
154 }
155
156 /*
157 * This function can be called to free resources used by SSPI.
158 */
ne_sspi_deinit(void)159 int ne_sspi_deinit(void)
160 {
161 NE_DEBUG(NE_DBG_SOCKET, "sspi: DeInit\n");
162 if (initialized <= 0) {
163 return initialized;
164 }
165
166 pSFT = NULL;
167
168 if (hSecDll != NULL) {
169 NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Unloading security dll.\n");
170 if (FreeLibrary(hSecDll)) {
171 NE_DEBUG(NE_DBG_HTTPAUTH,
172 "sspi: Unloading of security dll [ok].\n");
173 } else {
174 NE_DEBUG(NE_DBG_HTTPAUTH,
175 "sspi: Unloading of security dll [fail].\n");
176 return -1;
177 }
178 hSecDll = NULL;
179 }
180
181 initialized = 0;
182 return 0;
183 }
184
185 /*
186 * Simplification wrapper arround AcquireCredentialsHandle as most of
187 * the parameters do not change.
188 */
acquireCredentialsHandle(CredHandle * credentials,char * package)189 static int acquireCredentialsHandle(CredHandle * credentials, char *package)
190 {
191 SECURITY_STATUS status;
192 TimeStamp timestamp;
193
194 status =
195 pSFT->AcquireCredentialsHandle(NULL, package, SECPKG_CRED_OUTBOUND,
196 NULL, NULL, NULL, NULL, credentials,
197 ×tamp);
198
199 if (status != SEC_E_OK) {
200 NE_DEBUG(NE_DBG_HTTPAUTH,
201 "sspi: AcquireCredentialsHandle [fail] [%x].\n", status);
202 return -1;
203 }
204
205 return 0;
206 }
207
208 /*
209 * Wrapper arround initializeSecurityContext. Supplies several
210 * default parameters as well as logging in case of errors.
211 */
212 static SECURITY_STATUS
initializeSecurityContext(CredHandle * credentials,CtxtHandle * context,char * spn,ULONG contextReq,SecBufferDesc * inBuffer,CtxtHandle * newContext,SecBufferDesc * outBuffer)213 initializeSecurityContext(CredHandle * credentials, CtxtHandle * context,
214 char *spn, ULONG contextReq,
215 SecBufferDesc * inBuffer, CtxtHandle * newContext,
216 SecBufferDesc * outBuffer)
217 {
218 ULONG contextAttributes;
219 SECURITY_STATUS status;
220
221 status =
222 pSFT->InitializeSecurityContext(credentials, context, spn, contextReq,
223 0, SECURITY_NETWORK_DREP, inBuffer, 0,
224 newContext, outBuffer,
225 &contextAttributes, NULL);
226
227 if (!SEC_SUCCESS(status)) {
228 if (status == SEC_E_INVALID_TOKEN) {
229 NE_DEBUG(NE_DBG_HTTPAUTH,
230 "InitializeSecurityContext [fail] SEC_E_INVALID_TOKEN.\n");
231 } else if (status == SEC_E_UNSUPPORTED_FUNCTION) {
232 NE_DEBUG(NE_DBG_HTTPAUTH,
233 "InitializeSecurityContext [fail] SEC_E_UNSUPPORTED_FUNCTION.\n");
234 } else {
235 NE_DEBUG(NE_DBG_HTTPAUTH,
236 "InitializeSecurityContext [fail] [%x].\n", status);
237 }
238 }
239
240 return status;
241 }
242
243 /*
244 * Validates that the pointer is not NULL and converts it to its real type.
245 */
getContext(void * context,SSPIContext ** sspiContext)246 static int getContext(void *context, SSPIContext **sspiContext)
247 {
248 if (!context) {
249 return -1;
250 }
251
252 *sspiContext = context;
253 return 0;
254 }
255
256 /*
257 * Verifies that the buffer descriptor point only to one buffer and
258 * returns the pointer to it.
259 */
getSingleBufferDescriptor(SecBufferDesc * secBufferDesc,SecBuffer ** secBuffer)260 static int getSingleBufferDescriptor(SecBufferDesc *secBufferDesc,
261 SecBuffer **secBuffer)
262 {
263 if (secBufferDesc->cBuffers != 1) {
264 NE_DEBUG(NE_DBG_HTTPAUTH,
265 "sspi: fillBufferDescriptor "
266 "[fail] numbers of descriptor buffers. 1 != [%d].\n",
267 secBufferDesc->cBuffers);
268 return -1;
269 }
270
271 *secBuffer = secBufferDesc->pBuffers;
272 return 0;
273 }
274
275 /*
276 * Decodes BASE64 string into SSPI SecBuffer
277 */
base64ToBuffer(const char * token,SecBufferDesc * secBufferDesc)278 static int base64ToBuffer(const char *token, SecBufferDesc * secBufferDesc)
279 {
280 SecBuffer *buffer;
281 if (getSingleBufferDescriptor(secBufferDesc, &buffer)) {
282 return -1;
283 }
284
285 buffer->BufferType = SECBUFFER_TOKEN;
286 buffer->cbBuffer =
287 ne_unbase64(token, (unsigned char **) &buffer->pvBuffer);
288
289 if (buffer->cbBuffer == 0) {
290 NE_DEBUG(NE_DBG_HTTPAUTH,
291 "sspi: Unable to decode BASE64 SSPI token.\n");
292 return -1;
293 }
294
295 return 0;
296 }
297
298 /*
299 * Creates a SecBuffer of a specified size.
300 */
makeBuffer(SecBufferDesc * secBufferDesc,ULONG size)301 static int makeBuffer(SecBufferDesc * secBufferDesc, ULONG size)
302 {
303 SecBuffer *buffer;
304 if (getSingleBufferDescriptor(secBufferDesc, &buffer)) {
305 return -1;
306 }
307
308 buffer->BufferType = SECBUFFER_TOKEN;
309 buffer->cbBuffer = size;
310 buffer->pvBuffer = ne_calloc(size);
311
312 return 0;
313 }
314
315 /*
316 * Frees data allocated in the buffer.
317 */
freeBuffer(SecBufferDesc * secBufferDesc)318 static int freeBuffer(SecBufferDesc * secBufferDesc)
319 {
320 SecBuffer *buffer;
321 if (getSingleBufferDescriptor(secBufferDesc, &buffer)) {
322 return -1;
323 }
324
325 if (buffer->cbBuffer > 0 && buffer->pvBuffer) {
326 ne_free(buffer->pvBuffer);
327 buffer->cbBuffer = 0;
328 buffer->pvBuffer = NULL;
329 }
330
331 return 0;
332 }
333
334 /*
335 * Canonicalize a server host name if possible.
336 * The returned pointer must be freed after usage.
337 */
canonical_hostname(const char * serverName)338 static char *canonical_hostname(const char *serverName)
339 {
340 char *hostname;
341 ne_sock_addr *addresses;
342
343 /* DNS resolution. It would be useful to be able to use the
344 * AI_CANONNAME flag where getaddrinfo() is available, but the
345 * reverse-lookup is sufficient and simpler. */
346 addresses = ne_addr_resolve(serverName, 0);
347 if (ne_addr_result(addresses)) {
348 /* Lookup failed */
349 char buf[256];
350 NE_DEBUG(NE_DBG_HTTPAUTH,
351 "sspi: Could not resolve IP address for `%s': %s\n",
352 serverName, ne_addr_error(addresses, buf, sizeof buf));
353 hostname = ne_strdup(serverName);
354 } else {
355 char hostbuffer[256];
356 const ne_inet_addr *address = ne_addr_first(addresses);
357
358 if (ne_iaddr_reverse(address, hostbuffer, sizeof hostbuffer) == 0) {
359 hostname = ne_strdup(hostbuffer);
360 } else {
361 NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Could not resolve host name"
362 "from IP address for `%s'\n", serverName);
363 hostname = ne_strdup(serverName);
364 }
365 }
366
367 ne_addr_destroy(addresses);
368 return hostname;
369 }
370
371 /*
372 * Create a context to authenticate to specified server, using either
373 * ntlm or negotiate.
374 */
ne_sspi_create_context(void ** context,char * serverName,int ntlm)375 int ne_sspi_create_context(void **context, char *serverName, int ntlm)
376 {
377 SSPIContext *sspiContext;
378 char *canonicalName;
379
380 if (initialized <= 0) {
381 return -1;
382 }
383
384 sspiContext = ne_calloc(sizeof(SSPIContext));
385 sspiContext->continueNeeded = 0;
386
387 if (ntlm) {
388 sspiContext->mechanism = "NTLM";
389 sspiContext->serverName = ne_strdup(serverName);
390 sspiContext->maxTokenSize = ntlmMaxTokenSize;
391 } else {
392 sspiContext->mechanism = "Negotiate";
393 /* Canonicalize to conform to GSSAPI behavior */
394 canonicalName = canonical_hostname(serverName);
395 sspiContext->serverName = ne_concat("HTTP/", canonicalName, NULL);
396 ne_free(canonicalName);
397 NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Created context with SPN '%s'\n",
398 sspiContext->serverName);
399 sspiContext->maxTokenSize = negotiateMaxTokenSize;
400 }
401
402 sspiContext->ntlm = ntlm;
403 sspiContext->authfinished = 0;
404 *context = sspiContext;
405 return 0;
406 }
407
408 /*
409 * Resets the context
410 */
resetContext(SSPIContext * sspiContext)411 static void resetContext(SSPIContext * sspiContext)
412 {
413 pSFT->DeleteSecurityContext(&(sspiContext->context));
414 #if defined(_MSC_VER) && _MSC_VER <= 1200
415 pSFT->FreeCredentialHandle(&(sspiContext->credentials));
416 #else
417 pSFT->FreeCredentialsHandle(&(sspiContext->credentials));
418 #endif
419 sspiContext->continueNeeded = 0;
420 }
421
422 /*
423 * Initializes supplied SecBufferDesc to point to supplied SecBuffer
424 * that is also initialized;
425 */
426 static void
initSingleEmptyBuffer(SecBufferDesc * bufferDesc,SecBuffer * buffer)427 initSingleEmptyBuffer(SecBufferDesc * bufferDesc, SecBuffer * buffer)
428 {
429 buffer->BufferType = SECBUFFER_EMPTY;
430 buffer->cbBuffer = 0;
431 buffer->pvBuffer = NULL;
432
433 bufferDesc->cBuffers = 1;
434 bufferDesc->ulVersion = SECBUFFER_VERSION;
435 bufferDesc->pBuffers = buffer;
436
437 }
438
439 /*
440 * Destroyes the supplied context.
441 */
ne_sspi_destroy_context(void * context)442 int ne_sspi_destroy_context(void *context)
443 {
444
445 int status;
446 SSPIContext *sspiContext;
447
448 if (initialized <= 0) {
449 return -1;
450 }
451
452 status = getContext(context, &sspiContext);
453 if (status) {
454 return status;
455 }
456
457 resetContext(sspiContext);
458 if (sspiContext->serverName) {
459 ne_free(sspiContext->serverName);
460 sspiContext->serverName = NULL;
461 }
462
463 ne_free(sspiContext);
464 return 0;
465 }
ne_sspi_clear_context(void * context)466 int ne_sspi_clear_context(void *context)
467 {
468 int status;
469 SSPIContext *sspiContext;
470
471 if (initialized <= 0) {
472 return -1;
473 }
474
475 status = getContext(context, &sspiContext);
476 if (status) {
477 return status;
478 }
479 sspiContext->authfinished = 0;
480 return 0;
481 }
482 /*
483 * Processes received authentication tokens as well as supplies the
484 * response token.
485 */
ne_sspi_authenticate(void * context,const char * base64Token,char ** responseToken)486 int ne_sspi_authenticate(void *context, const char *base64Token, char **responseToken)
487 {
488 SecBufferDesc outBufferDesc;
489 SecBuffer outBuffer;
490 int status;
491 SECURITY_STATUS securityStatus;
492 ULONG contextFlags;
493
494 SSPIContext *sspiContext;
495 if (initialized <= 0) {
496 return -1;
497 }
498
499 status = getContext(context, &sspiContext);
500 if (status) {
501 return status;
502 }
503
504 /* TODO: Not sure what flags should be set. joe: this needs to be
505 * driven by the ne_auth interface; the GSSAPI code needs similar
506 * flags. */
507 contextFlags = ISC_REQ_CONFIDENTIALITY | ISC_REQ_MUTUAL_AUTH;
508
509 initSingleEmptyBuffer(&outBufferDesc, &outBuffer);
510 status = makeBuffer(&outBufferDesc, sspiContext->maxTokenSize);
511 if (status) {
512 return status;
513 }
514
515 if (base64Token) {
516 SecBufferDesc inBufferDesc;
517 SecBuffer inBuffer;
518
519 if (!sspiContext->continueNeeded) {
520 freeBuffer(&outBufferDesc);
521 NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Got an unexpected token.\n");
522 return -1;
523 }
524
525 initSingleEmptyBuffer(&inBufferDesc, &inBuffer);
526
527 status = base64ToBuffer(base64Token, &inBufferDesc);
528 if (status) {
529 freeBuffer(&outBufferDesc);
530 return status;
531 }
532
533 securityStatus =
534 initializeSecurityContext(&sspiContext->credentials,
535 &(sspiContext->context),
536 sspiContext->serverName, contextFlags,
537 &inBufferDesc, &(sspiContext->context),
538 &outBufferDesc);
539 if (securityStatus == SEC_E_OK)
540 {
541 sspiContext->authfinished = 1;
542 }
543 freeBuffer(&inBufferDesc);
544 } else {
545 if (sspiContext->continueNeeded) {
546 freeBuffer(&outBufferDesc);
547 NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Expected a token from server.\n");
548 return -1;
549 }
550 if (sspiContext->authfinished && (sspiContext->credentials.dwLower || sspiContext->credentials.dwUpper)) {
551 if (sspiContext->authfinished)
552 {
553 freeBuffer(&outBufferDesc);
554 sspiContext->authfinished = 0;
555 NE_DEBUG(NE_DBG_HTTPAUTH,"sspi: failing because starting over from failed try.\n");
556 return -1;
557 }
558 sspiContext->authfinished = 0;
559 }
560
561 /* Reset any existing context since we are starting over */
562 resetContext(sspiContext);
563
564 if (acquireCredentialsHandle
565 (&sspiContext->credentials, sspiContext->mechanism) != SEC_E_OK) {
566 freeBuffer(&outBufferDesc);
567 NE_DEBUG(NE_DBG_HTTPAUTH,
568 "sspi: acquireCredentialsHandle failed.\n");
569 return -1;
570 }
571
572 securityStatus =
573 initializeSecurityContext(&sspiContext->credentials, NULL,
574 sspiContext->serverName, contextFlags,
575 NULL, &(sspiContext->context),
576 &outBufferDesc);
577 }
578
579 if (securityStatus == SEC_I_COMPLETE_AND_CONTINUE
580 || securityStatus == SEC_I_COMPLETE_NEEDED) {
581 SECURITY_STATUS compleStatus =
582 pSFT->CompleteAuthToken(&(sspiContext->context), &outBufferDesc);
583
584 if (compleStatus != SEC_E_OK) {
585 freeBuffer(&outBufferDesc);
586 NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: CompleteAuthToken failed.\n");
587 return -1;
588 }
589 }
590
591 if (securityStatus == SEC_I_COMPLETE_AND_CONTINUE
592 || securityStatus == SEC_I_CONTINUE_NEEDED) {
593 sspiContext->continueNeeded = 1;
594 } else {
595 sspiContext->continueNeeded = 0;
596 }
597
598 if (!(securityStatus == SEC_I_COMPLETE_AND_CONTINUE
599 || securityStatus == SEC_I_COMPLETE_NEEDED
600 || securityStatus == SEC_I_CONTINUE_NEEDED
601 || securityStatus == SEC_E_OK)) {
602 NE_DEBUG(NE_DBG_HTTPAUTH,
603 "sspi: initializeSecurityContext [failed] [%x].\n",
604 securityStatus);
605 freeBuffer(&outBufferDesc);
606 return -1;
607 }
608
609 *responseToken = ne_base64(outBufferDesc.pBuffers->pvBuffer,
610 outBufferDesc.pBuffers->cbBuffer);
611 freeBuffer(&outBufferDesc);
612
613 return 0;
614 }
615 #endif /* HAVE_SSPI */
616