From 1f453d2ecf9a34bc16701177d8ac7f19a119d96e Mon Sep 17 00:00:00 2001 From: James Canete Date: Mon, 5 Nov 2007 02:30:04 +0000 Subject: [PATCH] Add schannel support (win32 only) Fix a couple compile warnings. Extend features timeout to 5 seconds. --- SConstruct | 5 +- src/auth.c | 2 +- src/sock.c | 6 +- src/stanza.c | 4 +- src/tls_schannel.c | 542 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 552 insertions(+), 7 deletions(-) create mode 100644 src/tls_schannel.c diff --git a/SConstruct b/SConstruct index e9ac231..d883715 100644 --- a/SConstruct +++ b/SConstruct @@ -47,6 +47,7 @@ Sources = Split(""" util.c thread.c snprintf.c + tls_schannel.c oocontext.cpp oostanza.cpp """) @@ -71,7 +72,9 @@ Examples = Split(""" env = Environment() if env['CC'] == 'gcc': env.Append(CCFLAGS=["-g", "-Wall"]) - +if env['CC'] == 'cl': + env.Append(CCFLAGS=["/MDd", "/ZI"]) + expatenv = env.Copy() # feature defs expatenv.Append(CCFLAGS=" -DXML_DTD") diff --git a/src/auth.c b/src/auth.c index 8ae6e47..03bd141 100644 --- a/src/auth.c +++ b/src/auth.c @@ -25,7 +25,7 @@ #endif /* FIXME: these should be configurable */ -#define FEATURES_TIMEOUT 2000 /* 2 seconds */ +#define FEATURES_TIMEOUT 5000 /* 5 seconds */ #define BIND_TIMEOUT 2000 /* 2 seconds */ #define SESSION_TIMEOUT 15000 /* 15 seconds */ #define LEGACY_TIMEOUT 2000 /* 2 seconds */ diff --git a/src/sock.c b/src/sock.c index 92b3270..f0ff4aa 100644 --- a/src/sock.c +++ b/src/sock.c @@ -160,7 +160,7 @@ int sock_connect_error(const sock_t sock) len = sizeof(int); - ret = getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len); + ret = getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&error, &len); if (ret < 0) return ret; return error; } @@ -179,8 +179,8 @@ void sock_srv_lookup(const char *service, const char *proto, const char *domain, void (WINAPI * pDnsRecordListFree)(PDNS_RECORD, DNS_FREE_TYPE); if (hdnsapi = LoadLibrary("dnsapi.dll")) { - pDnsQuery_A = GetProcAddress(hdnsapi, "DnsQuery_A"); - pDnsRecordListFree = GetProcAddress(hdnsapi, "DnsRecordListFree"); + pDnsQuery_A = (void *)GetProcAddress(hdnsapi, "DnsQuery_A"); + pDnsRecordListFree = (void *)GetProcAddress(hdnsapi, "DnsRecordListFree"); if (pDnsQuery_A && pDnsRecordListFree) { PDNS_RECORD dnsrecords = NULL; diff --git a/src/stanza.c b/src/stanza.c index cb93af2..b6c46c8 100644 --- a/src/stanza.c +++ b/src/stanza.c @@ -345,10 +345,10 @@ int xmpp_stanza_get_attribute_count(xmpp_stanza_t * const stanza) } int xmpp_stanza_get_attributes(xmpp_stanza_t * const stanza, - char **attr, int attrlen) + const char **attr, int attrlen) { hash_iterator_t *iter; - char *key; + const char *key; int num = 0; if (stanza->attributes == NULL) { diff --git a/src/tls_schannel.c b/src/tls_schannel.c new file mode 100644 index 0000000..81f4c03 --- /dev/null +++ b/src/tls_schannel.c @@ -0,0 +1,542 @@ +/* tls.c +** libstrophe XMPP client library -- TLS abstraction schannel impl. +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include "common.h" +#include "tls.h" +#include "sock.h" + +#define SECURITY_WIN32 +#include +#include + +static HANDLE hsec32 = NULL; +static SecurityFunctionTable *sft = NULL; +static CredHandle hcred; +static SecPkgInfo *spi; +static int init = 0; + +struct _tls { + xmpp_ctx_t *ctx; + sock_t sock; + CtxtHandle hctxt; + SecPkgContext_StreamSizes spcss; + + unsigned char *recvbuffer; + unsigned int recvbuffermaxlen; + unsigned int recvbufferpos; + + unsigned char *readybuffer; + unsigned int readybufferpos; + unsigned int readybufferlen; + + unsigned char *sendbuffer; + unsigned int sendbuffermaxlen; + unsigned int sendbufferlen; + unsigned int sendbufferpos; + + SECURITY_STATUS lasterror; +}; + +void tls_initialize(void) +{ + PSecurityFunctionTable (*pInitSecurityInterface)(void); + SCHANNEL_CRED scred; + int ret; + + if (!(hsec32 = LoadLibrary ("secur32.dll"))) { + return; + } + + if (!(pInitSecurityInterface = + (void *)GetProcAddress(hsec32, "InitSecurityInterfaceA"))) { + FreeLibrary(hsec32); + hsec32 = NULL; + return; + } + + sft = pInitSecurityInterface(); + + if (!sft) { + sft = NULL; + FreeLibrary(hsec32); + hsec32 = NULL; + return; + } + + ret = sft->QuerySecurityPackageInfo(UNISP_NAME, &spi); + + memset(&scred, 0, sizeof(scred)); + scred.dwVersion = SCHANNEL_CRED_VERSION; + scred.grbitEnabledProtocols = SP_PROT_TLS1_CLIENT; + + sft->AcquireCredentialsHandleA(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, + NULL, &scred, NULL, NULL, &hcred, NULL); + + init = 1; + + return; +} + +void tls_shutdown(void) +{ + if (init) { + sft->FreeCredentialsHandle(&hcred); + } + + sft = NULL; + + if (hsec32) { + FreeLibrary(hsec32); + hsec32 = NULL; + } + + return; +} + +tls_t *tls_new(xmpp_ctx_t *ctx, sock_t sock) +{ + tls_t *tls = xmpp_alloc(ctx, sizeof(*tls)); + + if (tls) { + memset(tls, 0, sizeof(*tls)); + tls->ctx = ctx; + tls->sock = sock; + } + + return tls; +} + +void tls_free(tls_t *tls) +{ + if (tls->recvbuffer) { + xmpp_free(tls->ctx, tls->recvbuffer); + } + + if (tls->readybuffer) { + xmpp_free(tls->ctx, tls->readybuffer); + } + + if (tls->sendbuffer) { + xmpp_free(tls->ctx, tls->sendbuffer); + } + + xmpp_free(tls->ctx, tls); + return; +} + +int tls_set_credentials(tls_t *tls, const char *cafilename) +{ + return -1; +} + +int tls_start(tls_t *tls) +{ + ULONG ctxtreq = 0, ctxtattr = 0; + SecBufferDesc sbdin, sbdout; + SecBuffer sbin[2], sbout[1]; + SECURITY_STATUS ret; + int sent; + char *name = NULL; + + /* search the ctx's conns for our sock, and use the domain there as our + * name */ + { + xmpp_connlist_t *listentry = tls->ctx->connlist; + + while (listentry) { + xmpp_conn_t *conn = listentry->conn; + + if (conn->sock == tls->sock) { + name = strdup(conn->domain); + listentry = NULL; + } else { + listentry = listentry->next; + } + } + } + + ctxtreq = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT + | ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR + | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM + | ISC_REQ_MANUAL_CRED_VALIDATION | ISC_REQ_INTEGRITY; + + memset(&(sbout[0]), 0, sizeof(sbout[0])); + sbout[0].BufferType = SECBUFFER_TOKEN; + + memset(&sbdout, 0, sizeof(sbdout)); + sbdout.ulVersion = SECBUFFER_VERSION; + sbdout.cBuffers = 1; + sbdout.pBuffers = sbout; + + memset(&(sbin[0]), 0, sizeof(sbin[0])); + sbin[0].BufferType = SECBUFFER_TOKEN; + sbin[0].pvBuffer = xmpp_alloc(tls->ctx, spi->cbMaxToken); + sbin[0].cbBuffer = spi->cbMaxToken; + + memset(&(sbin[1]), 0, sizeof(sbin[1])); + sbin[1].BufferType = SECBUFFER_EMPTY; + + memset(&sbdin, 0, sizeof(sbdin)); + sbdin.ulVersion = SECBUFFER_VERSION; + sbdin.cBuffers = 2; + sbdin.pBuffers = sbin; + + ret = sft->InitializeSecurityContextA(&hcred, NULL, name, ctxtreq, 0, 0, + NULL, 0, &(tls->hctxt), &sbdout, + &ctxtattr, NULL); + + while (ret == SEC_I_CONTINUE_NEEDED + || ret == SEC_I_INCOMPLETE_CREDENTIALS) { + unsigned char *p = sbin[0].pvBuffer; + int len = 0, inbytes = 0; + + if (sbdout.pBuffers[0].cbBuffer) { + sent = sock_write(tls->sock, sbdout.pBuffers[0].pvBuffer, + sbdout.pBuffers[0].cbBuffer); + sft->FreeContextBuffer(sbdout.pBuffers[0].pvBuffer); + sbdout.pBuffers[0].pvBuffer = NULL; + sbdout.pBuffers[0].cbBuffer = 0; + } + + /* poll for a bit until the remote server stops sending data, ie it + * finishes sending the token */ + inbytes = 0; + { + fd_set fds; + struct timeval tv; + + tv.tv_sec = 2; + tv.tv_usec = 0; + + FD_ZERO(&fds); + FD_SET(tls->sock, &fds); + + select(tls->sock, &fds, NULL, NULL, &tv); + } + + while (inbytes != -1) { + fd_set fds; + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 1000; + + FD_ZERO(&fds); + FD_SET(tls->sock, &fds); + + select(tls->sock, &fds, NULL, NULL, &tv); + + inbytes = sock_read(tls->sock, p, spi->cbMaxToken - len); + + if (inbytes > 0) { + len += inbytes; + p += inbytes; + } + } + + sbin[0].cbBuffer = len; + + ret = sft->InitializeSecurityContextA(&hcred, &(tls->hctxt), name, + ctxtreq, 0, 0, &sbdin, 0, + &(tls->hctxt), &sbdout, + &ctxtattr, NULL); + } + + if (ret == SEC_E_OK) { + if (sbdout.pBuffers[0].cbBuffer) { + sent = sock_write(tls->sock, sbdout.pBuffers[0].pvBuffer, + sbdout.pBuffers[0].cbBuffer); + sft->FreeContextBuffer(sbdout.pBuffers[0].pvBuffer); + sbdout.pBuffers[0].pvBuffer = NULL; + sbdout.pBuffers[0].cbBuffer = 0; + } + } + + xmpp_free(tls->ctx, sbin[0].pvBuffer); + + if (ret != SEC_E_OK) { + xmpp_debug(tls->ctx, "TLSS", + "Couldn't initialize security context: error %d", ret); + tls->lasterror = ret; + return 0; + } + + sft->QueryContextAttributes(&(tls->hctxt), SECPKG_ATTR_STREAM_SIZES, + &(tls->spcss)); + + tls->recvbuffermaxlen = tls->spcss.cbHeader + tls->spcss.cbMaximumMessage + + tls->spcss.cbTrailer; + tls->recvbuffer = xmpp_alloc(tls->ctx, tls->recvbuffermaxlen); + tls->recvbufferpos = 0; + + tls->sendbuffermaxlen = tls->spcss.cbHeader + tls->spcss.cbMaximumMessage + + tls->spcss.cbTrailer; + tls->sendbuffer = xmpp_alloc(tls->ctx, tls->sendbuffermaxlen); + tls->sendbufferpos = 0; + tls->sendbufferlen = 0; + + tls->readybuffer = xmpp_alloc(tls->ctx, tls->spcss.cbMaximumMessage); + tls->readybufferpos = 0; + tls->readybufferlen = 0; + + return 1; +} + +int tls_stop(tls_t *tls) +{ + return -1; +} + +int tls_error(tls_t *tls) +{ + return tls->lasterror; +} + +int tls_is_recoverable(int error) +{ + return (error == SEC_E_OK || error == SEC_E_INCOMPLETE_MESSAGE + || error == WSAEWOULDBLOCK || error == WSAEMSGSIZE + || error == WSAEINPROGRESS); +} + +int tls_read(tls_t *tls, void * const buff, const size_t len) +{ + int bytes; + + /* first, if we've got some ready data, put that in the buffer */ + if (tls->readybufferpos < tls->readybufferlen) + { + if (len < tls->readybufferlen - tls->readybufferpos) { + bytes = len; + } else { + bytes = tls->readybufferlen - tls->readybufferpos; + } + + memcpy(buff, tls->readybuffer + tls->readybufferpos, bytes); + + if (len < tls->readybufferlen - tls->readybufferpos) { + tls->readybufferpos += bytes; + return bytes; + } else { + unsigned char *newbuff = buff; + int read; + tls->readybufferpos += bytes; + newbuff += bytes; + read = tls_read(tls, newbuff, len - bytes); + + if (read == -1) { + if (tls_is_recoverable(tls->lasterror)) { + return bytes; + } + + return -1; + } + + return bytes + read; + } + } + + /* next, top up our recv buffer */ + bytes = sock_read(tls->sock, tls->recvbuffer + tls->recvbufferpos, + tls->recvbuffermaxlen - tls->recvbufferpos); + + if (bytes == -1) { + if (!tls_is_recoverable(sock_error())) { + tls->lasterror = sock_error(); + return -1; + } + } + + if (bytes > 0) { + tls->recvbufferpos += bytes; + } + + /* next, try to decrypt the recv buffer */ + if (tls->recvbufferpos > 0) { + SecBufferDesc sbddec; + SecBuffer sbdec[4]; + int ret; + + memset(&sbddec, 0, sizeof(sbddec)); + sbddec.ulVersion = SECBUFFER_VERSION; + sbddec.cBuffers = 4; + sbddec.pBuffers = sbdec; + + memset(&(sbdec[0]), 0, sizeof(sbdec[0])); + sbdec[0].BufferType = SECBUFFER_DATA; + sbdec[0].pvBuffer = tls->recvbuffer; + sbdec[0].cbBuffer = tls->recvbufferpos; + + memset(&(sbdec[1]), 0, sizeof(sbdec[1])); + sbdec[1].BufferType = SECBUFFER_EMPTY; + + memset(&(sbdec[2]), 0, sizeof(sbdec[2])); + sbdec[2].BufferType = SECBUFFER_EMPTY; + + memset(&(sbdec[3]), 0, sizeof(sbdec[3])); + sbdec[3].BufferType = SECBUFFER_EMPTY; + + ret = sft->DecryptMessage(&(tls->hctxt), &sbddec, 0, NULL); + + if (ret == SEC_E_OK) { + memcpy(tls->readybuffer, sbdec[1].pvBuffer, sbdec[1].cbBuffer); + tls->readybufferpos = 0; + tls->readybufferlen = sbdec[1].cbBuffer; + /* have we got some data left over? If so, copy it to the start + * of the recv buffer */ + if (sbdec[3].BufferType == SECBUFFER_EXTRA) { + memcpy(tls->recvbuffer, sbdec[3].pvBuffer, sbdec[3].cbBuffer); + tls->recvbufferpos = sbdec[3].cbBuffer; + } else { + tls->recvbufferpos = 0; + } + + return tls_read(tls, buff, len); + } else if (ret == SEC_E_INCOMPLETE_MESSAGE) { + tls->lasterror = SEC_E_INCOMPLETE_MESSAGE; + return -1; + } else if (ret == SEC_I_RENEGOTIATE) { + ret = tls_start(tls); + if (!ret) + { + return -1; + } + + /* fake an incomplete message so we're called again */ + tls->lasterror = SEC_E_INCOMPLETE_MESSAGE; + return -1; + } + + /* something bad happened, so we bail */ + tls->lasterror = ret; + return -1; + } + + tls->lasterror = SEC_E_INCOMPLETE_MESSAGE; + + return -1; +} + +static int tls_clear_pending_write(tls_t *tls) +{ + /* clear pending writes first */ + if (tls->sendbufferpos < tls->sendbufferlen) + { + int bytes; + + bytes = sock_write(tls->sock, tls->sendbuffer + tls->sendbufferpos, + tls->sendbufferlen - tls->sendbufferpos); + + if (bytes == -1) { + tls->lasterror = sock_error(); + return -1; + } else if (bytes > 0) { + tls->sendbufferpos += bytes; + } + + if (tls->sendbufferpos < tls->sendbufferlen) { + return 0; + } + } + + return 1; +} + +int tls_write(tls_t *tls, const void * const buff, const size_t len) +{ + SecBufferDesc sbdenc; + SecBuffer sbenc[4]; + unsigned char *sendbuffer; + const unsigned char *p = buff; + int sent = 0, ret, remain = len; + + ret = tls_clear_pending_write(tls); + if (ret <= 0) { + return ret; + } + + tls->sendbufferpos = 0; + tls->sendbufferlen = 0; + + memset(&sbdenc, 0, sizeof(sbdenc)); + sbdenc.ulVersion = SECBUFFER_VERSION; + sbdenc.cBuffers = 4; + sbdenc.pBuffers = sbenc; + + memset(&(sbenc[0]), 0, sizeof(sbenc[0])); + sbenc[0].BufferType = SECBUFFER_STREAM_HEADER; + + memset(&(sbenc[1]), 0, sizeof(sbenc[1])); + sbenc[1].BufferType = SECBUFFER_DATA; + + memset(&(sbenc[2]), 0, sizeof(sbenc[2])); + sbenc[2].BufferType = SECBUFFER_STREAM_TRAILER; + + memset(&(sbenc[3]), 0, sizeof(sbenc[3])); + sbenc[3].BufferType = SECBUFFER_EMPTY; + + sbenc[0].pvBuffer = tls->sendbuffer; + sbenc[0].cbBuffer = tls->spcss.cbHeader; + + sbenc[1].pvBuffer = tls->sendbuffer + tls->spcss.cbHeader; + + while (remain > 0) + { + if (remain > tls->spcss.cbMaximumMessage) { + sbenc[1].cbBuffer = tls->spcss.cbMaximumMessage; + } else { + sbenc[1].cbBuffer = remain; + } + + sbenc[2].pvBuffer = (unsigned char *)sbenc[1].pvBuffer + + sbenc[1].cbBuffer; + sbenc[2].cbBuffer = tls->spcss.cbTrailer; + + memcpy(sbenc[1].pvBuffer, p, sbenc[1].cbBuffer); + p += tls->spcss.cbMaximumMessage; + + tls->sendbufferlen = sbenc[0].cbBuffer + sbenc[1].cbBuffer + + sbenc[2].cbBuffer; + + ret = sft->EncryptMessage(&(tls->hctxt), 0, &sbdenc, 0); + + if (ret != SEC_E_OK) { + tls->lasterror = ret; + return -1; + } + + tls->sendbufferpos = 0; + + ret = tls_clear_pending_write(tls); + + if (ret == -1) { + return -1; + } + + if (remain > tls->spcss.cbMaximumMessage) { + sent += tls->spcss.cbMaximumMessage; + remain -= tls->spcss.cbMaximumMessage; + } else { + sent += remain; + remain = 0; + } + + if (ret == 0) { + return sent; + } + + } + + return sent; +}