/* Microsoft SSPI based authentication routines Copyright (C) 2004-2005, Vladimir Berezniker @ http://public.xdi.org/=vmpn Copyright (C) 2007, Yves Martin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include "config.h" #include "ne_utils.h" #include "ne_string.h" #include "ne_socket.h" #include "ne_sspi.h" #ifdef HAVE_SSPI #define SEC_SUCCESS(Status) ((Status) >= 0) #ifndef SECURITY_ENTRYPOINT /* Missing in MingW 3.7 */ #define SECURITY_ENTRYPOINT "InitSecurityInterfaceA" #endif struct SSPIContextStruct { CtxtHandle context; char *serverName; CredHandle credentials; int continueNeeded; int authfinished; char *mechanism; int ntlm; ULONG maxTokenSize; }; typedef struct SSPIContextStruct SSPIContext; static ULONG negotiateMaxTokenSize = 0; static ULONG ntlmMaxTokenSize = 0; static HINSTANCE hSecDll = NULL; static PSecurityFunctionTable pSFT = NULL; static int initialized = 0; /* * Query specified package for it's maximum token size. */ static int getMaxTokenSize(char *package, ULONG * maxTokenSize) { SECURITY_STATUS status; SecPkgInfo *packageSecurityInfo = NULL; status = pSFT->QuerySecurityPackageInfo(package, &packageSecurityInfo); if (status == SEC_E_OK) { *maxTokenSize = packageSecurityInfo->cbMaxToken; if (pSFT->FreeContextBuffer(packageSecurityInfo) != SEC_E_OK) { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Unable to free security package info."); } } else { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: QuerySecurityPackageInfo [failed] [%x].", status); return -1; } return 0; } /* * Initialize all the SSPI data */ static void initDll(HINSTANCE hSecDll) { INIT_SECURITY_INTERFACE initSecurityInterface = NULL; initSecurityInterface = (INIT_SECURITY_INTERFACE) GetProcAddress(hSecDll, SECURITY_ENTRYPOINT); if (initSecurityInterface == NULL) { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Obtaining security interface [fail].\n"); initialized = -1; return; } else { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Obtaining security interface [ok].\n"); } pSFT = (initSecurityInterface) (); if (pSFT == NULL) { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Security Function Table [fail].\n"); initialized = -2; return; } else { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Security Function Table [ok].\n"); } if (getMaxTokenSize("Negotiate", &negotiateMaxTokenSize)) { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Unable to get negotiate maximum packet size"); initialized = -3; } if (getMaxTokenSize("NTLM", &ntlmMaxTokenSize)) { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Unable to get negotiate maximum packet size"); initialized = -3; } } /* * This function needs to be called at least once before using any other. */ int ne_sspi_init(void) { if (initialized) { return 0; } NE_DEBUG(NE_DBG_SOCKET, "sspiInit\n"); NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Loading security dll.\n"); hSecDll = LoadLibrary("security.dll"); if (hSecDll == NULL) { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Loading of security dll [fail].\n"); } else { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Loading of security dll [ok].\n"); initDll(hSecDll); if (initialized == 0) { initialized = 1; } } NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: sspiInit [%d].\n", initialized); if (initialized < 0) { return initialized; } else { return 0; } } /* * This function can be called to free resources used by SSPI. */ int ne_sspi_deinit(void) { NE_DEBUG(NE_DBG_SOCKET, "sspi: DeInit\n"); if (initialized <= 0) { return initialized; } pSFT = NULL; if (hSecDll != NULL) { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Unloading security dll.\n"); if (FreeLibrary(hSecDll)) { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Unloading of security dll [ok].\n"); } else { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Unloading of security dll [fail].\n"); return -1; } hSecDll = NULL; } initialized = 0; return 0; } /* * Simplification wrapper around AcquireCredentialsHandle as most of * the parameters do not change. */ static int acquireCredentialsHandle(CredHandle * credentials, char *package) { SECURITY_STATUS status; TimeStamp timestamp; status = pSFT->AcquireCredentialsHandle(NULL, package, SECPKG_CRED_OUTBOUND, NULL, NULL, NULL, NULL, credentials, ×tamp); if (status != SEC_E_OK) { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: AcquireCredentialsHandle [fail] [%x].\n", status); return -1; } return 0; } /* * Wrapper around initializeSecurityContext. Supplies several * default parameters as well as logging in case of errors. */ static SECURITY_STATUS initializeSecurityContext(CredHandle * credentials, CtxtHandle * context, char *spn, ULONG contextReq, SecBufferDesc * inBuffer, CtxtHandle * newContext, SecBufferDesc * outBuffer) { ULONG contextAttributes; SECURITY_STATUS status; status = pSFT->InitializeSecurityContext(credentials, context, spn, contextReq, 0, SECURITY_NETWORK_DREP, inBuffer, 0, newContext, outBuffer, &contextAttributes, NULL); if (!SEC_SUCCESS(status)) { if (status == SEC_E_INVALID_TOKEN) { NE_DEBUG(NE_DBG_HTTPAUTH, "InitializeSecurityContext [fail] SEC_E_INVALID_TOKEN.\n"); } else if (status == SEC_E_UNSUPPORTED_FUNCTION) { NE_DEBUG(NE_DBG_HTTPAUTH, "InitializeSecurityContext [fail] SEC_E_UNSUPPORTED_FUNCTION.\n"); } else { NE_DEBUG(NE_DBG_HTTPAUTH, "InitializeSecurityContext [fail] [%x].\n", status); } } return status; } /* * Validates that the pointer is not NULL and converts it to its real type. */ static int getContext(void *context, SSPIContext **sspiContext) { if (!context) { return -1; } *sspiContext = context; return 0; } /* * Verifies that the buffer descriptor point only to one buffer and * returns the pointer to it. */ static int getSingleBufferDescriptor(SecBufferDesc *secBufferDesc, SecBuffer **secBuffer) { if (secBufferDesc->cBuffers != 1) { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: fillBufferDescriptor " "[fail] numbers of descriptor buffers. 1 != [%d].\n", secBufferDesc->cBuffers); return -1; } *secBuffer = secBufferDesc->pBuffers; return 0; } /* * Decodes BASE64 string into SSPI SecBuffer */ static int base64ToBuffer(const char *token, SecBufferDesc * secBufferDesc) { SecBuffer *buffer; if (getSingleBufferDescriptor(secBufferDesc, &buffer)) { return -1; } buffer->BufferType = SECBUFFER_TOKEN; buffer->cbBuffer = ne_unbase64(token, (unsigned char **) &buffer->pvBuffer); if (buffer->cbBuffer == 0) { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Unable to decode BASE64 SSPI token.\n"); return -1; } return 0; } /* * Creates a SecBuffer of a specified size. */ static int makeBuffer(SecBufferDesc * secBufferDesc, ULONG size) { SecBuffer *buffer; if (getSingleBufferDescriptor(secBufferDesc, &buffer)) { return -1; } buffer->BufferType = SECBUFFER_TOKEN; buffer->cbBuffer = size; buffer->pvBuffer = ne_calloc(size); return 0; } /* * Frees data allocated in the buffer. */ static int freeBuffer(SecBufferDesc * secBufferDesc) { SecBuffer *buffer; if (getSingleBufferDescriptor(secBufferDesc, &buffer)) { return -1; } if (buffer->cbBuffer > 0 && buffer->pvBuffer) { ne_free(buffer->pvBuffer); buffer->cbBuffer = 0; buffer->pvBuffer = NULL; } return 0; } /* * Canonicalize a server host name if possible. * The returned pointer must be freed after usage. */ static char *canonical_hostname(const char *serverName) { const char *hostname; ne_sock_addr *addr; addr = ne_addr_resolve(serverName, NE_ADDR_CANON); if (ne_addr_result(addr) || ne_addr_canonical(addr) == NULL) { /* Lookup failed */ char buf[256]; NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Could not resolve IP address for `%s': %s\n", serverName, ne_addr_error(addr, buf, sizeof buf)); hostname = ne_strdup(serverName); } else { hostname = ne_strdup(ne_addr_canonical(addr)); } ne_addr_destroy(addr); return hostname; } /* * Create a context to authenticate to specified server, using either * ntlm or negotiate. */ int ne_sspi_create_context(void **context, char *serverName, int ntlm) { SSPIContext *sspiContext; char *canonicalName; if (initialized <= 0) { return -1; } sspiContext = ne_calloc(sizeof(SSPIContext)); sspiContext->continueNeeded = 0; if (ntlm) { sspiContext->mechanism = "NTLM"; sspiContext->serverName = ne_strdup(serverName); sspiContext->maxTokenSize = ntlmMaxTokenSize; } else { sspiContext->mechanism = "Negotiate"; /* Canonicalize to conform to GSSAPI behavior */ canonicalName = canonical_hostname(serverName); sspiContext->serverName = ne_concat("HTTP/", canonicalName, NULL); ne_free(canonicalName); NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Created context with SPN '%s'\n", sspiContext->serverName); sspiContext->maxTokenSize = negotiateMaxTokenSize; } sspiContext->ntlm = ntlm; sspiContext->authfinished = 0; *context = sspiContext; return 0; } /* * Resets the context */ static void resetContext(SSPIContext * sspiContext) { pSFT->DeleteSecurityContext(&(sspiContext->context)); #if defined(_MSC_VER) && _MSC_VER <= 1200 pSFT->FreeCredentialHandle(&(sspiContext->credentials)); #else pSFT->FreeCredentialsHandle(&(sspiContext->credentials)); #endif sspiContext->continueNeeded = 0; } /* * Initializes supplied SecBufferDesc to point to supplied SecBuffer * that is also initialized; */ static void initSingleEmptyBuffer(SecBufferDesc * bufferDesc, SecBuffer * buffer) { buffer->BufferType = SECBUFFER_EMPTY; buffer->cbBuffer = 0; buffer->pvBuffer = NULL; bufferDesc->cBuffers = 1; bufferDesc->ulVersion = SECBUFFER_VERSION; bufferDesc->pBuffers = buffer; } /* * Destroys the supplied context. */ int ne_sspi_destroy_context(void *context) { int status; SSPIContext *sspiContext; if (initialized <= 0) { return -1; } status = getContext(context, &sspiContext); if (status) { return status; } resetContext(sspiContext); if (sspiContext->serverName) { ne_free(sspiContext->serverName); sspiContext->serverName = NULL; } ne_free(sspiContext); return 0; } int ne_sspi_clear_context(void *context) { int status; SSPIContext *sspiContext; if (initialized <= 0) { return -1; } status = getContext(context, &sspiContext); if (status) { return status; } sspiContext->authfinished = 0; sspiContext->continueNeeded = 0; return 0; } /* * Processes received authentication tokens as well as supplies the * response token. */ int ne_sspi_authenticate(void *context, const char *base64Token, char **responseToken) { SecBufferDesc outBufferDesc; SecBuffer outBuffer; int status; SECURITY_STATUS securityStatus; ULONG contextFlags; SSPIContext *sspiContext; if (initialized <= 0) { return -1; } status = getContext(context, &sspiContext); if (status) { return status; } /* TODO: Not sure what flags should be set. joe: this needs to be * driven by the ne_auth interface; the GSSAPI code needs similar * flags. */ contextFlags = ISC_REQ_CONFIDENTIALITY | ISC_REQ_MUTUAL_AUTH; initSingleEmptyBuffer(&outBufferDesc, &outBuffer); status = makeBuffer(&outBufferDesc, sspiContext->maxTokenSize); if (status) { return status; } if (base64Token) { SecBufferDesc inBufferDesc; SecBuffer inBuffer; if (!sspiContext->continueNeeded) { freeBuffer(&outBufferDesc); NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Got an unexpected token.\n"); return -1; } initSingleEmptyBuffer(&inBufferDesc, &inBuffer); status = base64ToBuffer(base64Token, &inBufferDesc); if (status) { freeBuffer(&outBufferDesc); return status; } securityStatus = initializeSecurityContext(&sspiContext->credentials, &(sspiContext->context), sspiContext->serverName, contextFlags, &inBufferDesc, &(sspiContext->context), &outBufferDesc); if (securityStatus == SEC_E_OK) { sspiContext->authfinished = 1; } freeBuffer(&inBufferDesc); } else { if (sspiContext->continueNeeded) { freeBuffer(&outBufferDesc); NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Expected a token from server.\n"); return -1; } if (sspiContext->authfinished && (sspiContext->credentials.dwLower || sspiContext->credentials.dwUpper)) { if (sspiContext->authfinished) { freeBuffer(&outBufferDesc); sspiContext->authfinished = 0; NE_DEBUG(NE_DBG_HTTPAUTH,"sspi: failing because starting over from failed try.\n"); return -1; } sspiContext->authfinished = 0; } /* Reset any existing context since we are starting over */ resetContext(sspiContext); if (acquireCredentialsHandle (&sspiContext->credentials, sspiContext->mechanism) != SEC_E_OK) { freeBuffer(&outBufferDesc); NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: acquireCredentialsHandle failed.\n"); return -1; } securityStatus = initializeSecurityContext(&sspiContext->credentials, NULL, sspiContext->serverName, contextFlags, NULL, &(sspiContext->context), &outBufferDesc); } if (securityStatus == SEC_I_COMPLETE_AND_CONTINUE || securityStatus == SEC_I_COMPLETE_NEEDED) { SECURITY_STATUS compleStatus = pSFT->CompleteAuthToken(&(sspiContext->context), &outBufferDesc); if (compleStatus != SEC_E_OK) { freeBuffer(&outBufferDesc); NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: CompleteAuthToken failed.\n"); return -1; } } if (securityStatus == SEC_I_COMPLETE_AND_CONTINUE || securityStatus == SEC_I_CONTINUE_NEEDED) { sspiContext->continueNeeded = 1; } else { sspiContext->continueNeeded = 0; } if (!(securityStatus == SEC_I_COMPLETE_AND_CONTINUE || securityStatus == SEC_I_COMPLETE_NEEDED || securityStatus == SEC_I_CONTINUE_NEEDED || securityStatus == SEC_E_OK)) { NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: initializeSecurityContext [failed] [%x].\n", securityStatus); freeBuffer(&outBufferDesc); return -1; } *responseToken = ne_base64(outBufferDesc.pBuffers->pvBuffer, outBufferDesc.pBuffers->cbBuffer); freeBuffer(&outBufferDesc); return 0; } #endif /* HAVE_SSPI */