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                                        &timestamp);
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