/* Copyright(C) 2000-2010 Проект ИОК

 *
 * Этот файл содержит информацию, являющуюся
 * собственностью компании Крипто Про.
 *
 * Любая часть этого файла не может быть скопирована,
 * исправлена, переведена на другие языки,
 * локализована или модифицирована любым способом,
 * откомпилирована, передана по сети с или на
 * любую компьютерную систему без предварительного
 * заключения соглашения с компанией Крипто Про.
 *
 * Программный код, содержащийся в этом файле, предназначен
 * исключительно для целей обучения и не может быть использован
 * для защиты информации.
 *
 * Компания Крипто-Про не несет никакой
 * ответственности за функционирование этого кода.
 */

//--------------------------------------------------------------------
// В данном примере устанавливается соединение сервера с web клиентом
// по защищенному каналу.
// Используемый сертификат должен создаваться как 
// Server Authentication Certificate.
//--------------------------------------------------------------------


#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <pthread.h>
#include <time.h>

#   include "CSP_WinDef.h"
#   include "CSP_WinCrypt.h"
#   include "CSP_Sspi.h"
#   include "CSP_SChannel.h"
#   include "WSMemSTDC.h"
#   include "Crypt.h"
#   include <sys/types.h>
#   ifdef _AIX
#	include <fcntl.h>
#   else
#	include <sys/fcntl.h>
#   endif
#   include <sys/stat.h>
#   include <sys/socket.h>
#   include <netinet/in.h>
#   include <arpa/inet.h>
#   include <netdb.h>
#   include <errno.h>
#   include <unistd.h>
#   include <signal.h>

#   define INVALID_SOCKET (-1)
#   define IS_SOCKET_ERROR(a) (a<0)
    typedef int SOCKET;
    typedef struct sockaddr_in SOCKADDR_IN;
    typedef struct sockaddr *LPSOCKADDR;
    static int WSAGetLastError()
    {
      return errno;
    }
#   define LocalAlloc(a, b) malloc(b)
#   define LocalFree free
#   define MoveMemory memmove
#   define closesocket close
#   define CloseHandle close
#   define INVALID_VALUE (-1)

#include "UTLS.h"
#include "WSKernelPart.h"

#define IO_BUFFER_SIZE  0x10000
//#define IO_BUFFER_SIZE  0x2000
#define OBJECT_NAME_LENGTH_MAX 256
#define min(a,b) ((a)<(b)?(a):(b))

// Опции пользователя.
static INT     iPortNumber     = 443;
static LPSTR   pszUserName     = NULL;
static DWORD   dwProtocol      = 0;
static LPSTR   pszFileName     = "Default.Htm";
static LPSTR   pszContName     = NULL;
static BOOL    fExportSession  = FALSE;
static BOOL    fImportSession  = FALSE;
static LPSTR   pszExportFileName  = NULL;
static LPSTR   pszImportFileName  = NULL;
static volatile PCtxtHandle phSaveContext = NULL;

static HCRYPTMODULE    hCPC2Crypt = NULL;
static CPC_CONFIG      CPCConfig;
static HCERTSTORE  hMyCertStore = 0;
static CredHandle hServerCreds;

static CHAR IoBuffer[IO_BUFFER_SIZE];
static DWORD cbIoBuffer = 0;

static utls_gost_handle hUTLS=0;
static unsigned sock_delay=10;
static pthread_attr_t thr_attr;
static int nErr=0;
static int nThreads=0;
static pthread_mutex_t thr_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_once_t save_session_once = PTHREAD_ONCE_INIT;
#ifdef KRN_EMUL
static HCRYPTPROV hProvExch=0;
static PDRTKTLS_DRV pKDrv=NULL;
#endif

static
HRESULT
CreateCredentials(
    LPSTR pszUserName,
    PCredHandle phCreds);

static void * ServerThread(void * arg);

static
BOOL
ParseRequest (
    IN PCHAR InputBuffer,
    IN INT InputBufferLength,
    OUT PCHAR ObjectName,
    OUT DWORD *pcbContentLength);

static
BOOL
SSPINegotiateLoop(
    SOCKET          Socket,
    PCtxtHandle     phContext,
    PCredHandle     phCred,
    BOOL            fDoInitialRead,
    BOOL            NewContext);

static
LONG
DisconnectFromClient(
    SOCKET          Socket, 
    PCredHandle     phCreds,
    CtxtHandle *    phContext);

static void PrintHexDump(DWORD length, PCHAR buffer);


//-------------------------------------------------------------
// Функция вывода информации об использовании данного примера.

static void Usage(void)
{
    printf("\n");
    printf("USAGE: webserver -u<user> \n");
    printf("\n");
    printf("    -u<user>        Name of user (in existing certificate)\n");
    
    exit(1);
} //Usage

//-------------------------------------------------------------
// Функция загрузки библиотеки.

//-------------------------------------------------------------
// Функция загрузки библиотеки.
static BOOL
InitUTLS(void)
{
    utls_gost_in gi;
    utls_gost_out go;

    memset(&gi, 0, sizeof(gi));
    memset(&go, 0, sizeof(go));
    cputls_init_gost(NULL, &gi, 0, &go);
    gi.allocatedMem = go.requiredMem;
    hUTLS = (utls_gost_handle)malloc(gi.allocatedMem);
    cputls_init_gost(hUTLS, &gi, 0, &go);

    memset(&CPCConfig, 0, sizeof(CPC_CONFIG));
    CPCConfig.cbSize = sizeof(CPC_CONFIG);
    CPCGetDefaultConfig(&CPCConfig, NULL);
    stdInitMemory(&CPCConfig.pArena, NULL, 0);

    CPC2CryptCreateProvider(&hCPC2Crypt, NULL);
    return TRUE;
}//InitUTLS

//-------------------------------------------------------------
// Функция разгрузки библиотеки.

static void
DoneUTLS(void)
{
    cputls_shutdown_gost(hUTLS,0);

    if(hCPC2Crypt->DestroyProvider)
    {
    	hCPC2Crypt->DestroyProvider(hCPC2Crypt);
	hCPC2Crypt = 0;
    }
    if(CPCConfig.pArena->pDoneMemory)
    {
	CPCConfig.pArena->pDoneMemory(CPCConfig.pArena);
    }
} //DoneUTLS

//-------------------------------------------------------------
// Функция main.
BOOL CreateSelfExportToken(LPCSTR ContName, utlsdb_token * pTok)
{
    HCRYPTPROV hProv;
    unsigned tlsdb_len=0;
    unsigned char * our_tlsdb=NULL;
    BOOL fRet=FALSE;
    if (!CryptAcquireContext(&hProv,ContName,0,PROV_GOST_2012_256,0))
	return FALSE;
    if (hUTLS->tlsdb.CreateProvFn(hCPC2Crypt,hProv,0,&pTok->Priv,0,&tlsdb_len))
	return FALSE;
    our_tlsdb=(unsigned char *)malloc(tlsdb_len);
    if (!our_tlsdb)
	return FALSE;
    if (hUTLS->tlsdb.CreateProvFn(hCPC2Crypt,hProv,0,&pTok->Priv,our_tlsdb,&tlsdb_len))
	goto done;
    if (hUTLS->tlsdb.deSerializePubKeyFn(hCPC2Crypt,hProv,our_tlsdb,tlsdb_len,0,&pTok->Pub,&CPCConfig))
	goto done;
    fRet=TRUE;	
done:    
    free(our_tlsdb);
    return fRet;
}

BOOL ExportSessionToFile(CtxtHandle * phContext, utlsdb_token * pTok, LPCSTR pszFileName)
{
    FILE * f=NULL;
    BOOL fRet=FALSE;
    size_t szread=0;
    SecBuffer SB;
    SB.cbBuffer=0;
    SB.pvBuffer=NULL;
    if (hUTLS->pSSPI->ExportSecurityContext(phContext,
				0,
				&SB,(void **)pTok))
	return FALSE;				
    SB.pvBuffer=malloc(SB.cbBuffer);
    if (!SB.pvBuffer)
	return FALSE;
    if (hUTLS->pSSPI->ExportSecurityContext(phContext,
				0,
				&SB,(void **)pTok))
	goto done;			
    f=fopen(pszFileName,"w");
    if (!f)
	goto done;
    while (szread<SB.cbBuffer)
	szread+=fwrite(((LPBYTE)SB.pvBuffer)+szread,1,SB.cbBuffer-szread,f);	
    fRet=TRUE;
done:    
    if (f)
	fclose(f);
    free(SB.pvBuffer);
    return fRet;
}

BOOL ImportSessionFromFile(LPCSTR pszFileName, utlsdb_token * pTok, CtxtHandle * phContext,CredHandle * pCreds)
{
    FILE * f=NULL;
    SecBuffer SB;
    SECURITY_STATUS scRet;
    size_t szlen,bread=0;
    f=fopen(pszFileName,"r");

    if (!f)
	return FALSE;
    fseek(f,0,SEEK_END);	
    szlen=(size_t)ftell(f);
    SB.pvBuffer=malloc(szlen);
    if (SB.pvBuffer == NULL)
    {
	fclose(f);
	return FALSE;
    }
    fseek(f,0,SEEK_SET);
    while (bread<szlen)
	bread+=fread(((LPBYTE)SB.pvBuffer)+bread,1,szlen-bread,f);
    fclose(f);
    SB.BufferType=SECBUFFER_DATA;
    SB.cbBuffer=(DWORD)szlen;
    scRet=hUTLS->pSSPI->ImportSecurityContext(NULL,&SB,pTok,phContext);
    free(SB.pvBuffer);
    if (scRet != STATUS_SUCCESS)
	return FALSE;
    scRet = hUTLS->pSSPI->QueryContextAttributes(phContext,
					     CPUTLS_ATTR_CREDENTIAL_HANDLE,
                                             (PVOID)pCreds);
    if (scRet != STATUS_SUCCESS)
    {
	return FALSE;
    }
    return TRUE;
}

BOOL InitializeDriver(PDRTKTLS_DRV *ppKDrv,HCRYPTPROV hProv, BOOL fCreateSA)
{
    PDRTKTLS_DRV pKDrv=NULL;
    BYTE * pbRandomBlob = NULL;
    DWORD dwLen=0;
    unsigned kdrv_len=0;
    BOOL ret=FALSE;
    kinit_gost(NULL,&kdrv_len);
    pKDrv=(PDRTKTLS_DRV)malloc(kdrv_len);
    if (!pKDrv)
	return FALSE;
    if (kinit_gost(pKDrv,&kdrv_len))
    {
	free(pKDrv);
	return FALSE;
    }

    if (fCreateSA)
    {
	if (!CryptGetProvParam(hProv,PP_RANDOM, NULL,&dwLen,0))
	    goto done;
	pbRandomBlob=(BYTE *)malloc(dwLen);	
	if (!pbRandomBlob)
	    goto done;
	if (!CryptGetProvParam(hProv,PP_RANDOM, pbRandomBlob,&dwLen,0))
	    goto done;
	if (kset_rng(pKDrv,pbRandomBlob,dwLen))
	    goto done;
	if (kcreate_SA(pKDrv))
	    goto done;
    }
    ret=TRUE;	
done:    
    free(pbRandomBlob);
    if (!ret)	
    {
	kdone_gost(pKDrv);	
	free(pKDrv);
    }
    else
	*ppKDrv=pKDrv;
	
    return ret;
}

int sig_ign_restart(int signum)
{
    struct sigaction my_sig;
    memset(&my_sig,0,sizeof(my_sig));
    my_sig.sa_handler=SIG_IGN;
    my_sig.sa_flags=SA_RESTART;
    return sigaction(signum,&my_sig,NULL);
}

int
main(int argc, char *argv[])
{
    INT i;
    INT iOption;
    PCHAR pszOption;
    SOCKET ListenSocket = INVALID_SOCKET;
    SOCKET Socket = INVALID_SOCKET;
    SOCKADDR_IN address;
    SOCKADDR_IN remoteAddress;
    INT remoteSockaddrLength;
    INT cConnections=0;
    int rv=127;
    utlsdb_token tok;
#ifndef KRN_EMUL    
    HCRYPTPROV hProvExch=0;
    PDRTKTLS_DRV pKDrv=NULL;
#endif    
    int err;

    CtxtHandle      hContext;
    BOOL            fContextInitialized = FALSE;


    //
    // Разбор командной строки.
    //

    if(argc <= 1)
    {
        Usage();
    }
    sig_ign_restart(SIGHUP);
    sig_ign_restart(SIGPIPE);

    for(i = 1; i < argc; i++) 
    {
        if(argv[i][0] == '/') argv[i][0] = '-';

        if(argv[i][0] != '-') 
        {
            printf("**** Invalid argument \"%s\"\n", argv[i]);
            Usage();
	    return 1;
        }

        iOption = argv[i][1];
        pszOption = &argv[i][2];

        switch(iOption) 
        {
        
        case 'u':
            pszUserName = pszOption;
            break;
       case 'f':
            pszFileName = pszOption;
            break;
        case 'c':
            pszContName = pszOption;
            break;
        case 'e':
            fExportSession = TRUE;
            pszExportFileName = pszOption;
            break;
        case 'i':
            fImportSession = TRUE;
            pszImportFileName = pszOption;
            break;
        case 'p':
            iPortNumber = atoi(pszOption);
            break;
	case 't':    
	    sock_delay = atoi(pszOption);
	    break;
        
	default:
            printf("**** Invalid option \"%s\"\n", argv[i]);
            Usage();
	    return 1;
        }
    }
    if(!InitUTLS())
    {
        printf("Error initializing the security library\n");
        goto cleanup;
    }
    pthread_attr_init(&thr_attr);
    pthread_attr_setdetachstate(&thr_attr,1);
    if (!CryptAcquireContext(&hProvExch,NULL,NULL,PROV_GOST_2012_256,CRYPT_VERIFYCONTEXT))
	goto cleanup;
    if (!InitializeDriver(&pKDrv,hProvExch,TRUE))
	goto cleanup;

    //
    // Создание мандатов сервера.
    //
    if (fImportSession)
    {
	utlsdb_token tok;
	CtxtHandle hContext;
	if (!CreateSelfExportToken(pszContName, &tok))
	{
	    printf("Cannot create self-export token\n");
	    exit(1);
	}

	if (!ImportSessionFromFile(pszImportFileName, &tok, &hContext,&hServerCreds))
	{
	    printf("Error importing session from %s\n",pszImportFileName);
	    exit(1);
	}
	hUTLS->pSSPI->DeleteSecurityContext(&hContext);
    }
    else
    {
	if(CreateCredentials(pszUserName, &hServerCreds))
	{
	    printf("Error creating credentials\n");
	    exit(1);
	}

    }

    ListenSocket = socket( AF_INET, SOCK_STREAM, 0 );
    if ( ListenSocket == INVALID_SOCKET )
    {
	printf( "socket() failed for ListenSocket: %d\n", WSAGetLastError( ) );
	exit(1);
    }

    memset( &address,0, sizeof(address) );
    address.sin_family = AF_INET;
    address.sin_port = htons( (short)iPortNumber );    // порт https
    address.sin_addr.s_addr = 0;

    {
	int optval=1;
	setsockopt(ListenSocket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    }
    err = bind(ListenSocket, (LPSOCKADDR) &address, sizeof(address));
    if (IS_SOCKET_ERROR(err)){
	printf("bind failed: %d\n", WSAGetLastError());
	exit(1);
    }

    err = listen(ListenSocket, 1000);
    if (IS_SOCKET_ERROR(err)){
	printf("listen failed: %d\n", WSAGetLastError());
	exit(1);
    }
    for (;;) {
	//PSecBuffer pDataBuffer;
	time_t t;
	fd_set fds;
	struct timeval tv;
	int ret;
	pthread_t thr;


	fContextInitialized = FALSE;

	//
	// В первую очередь получение соединения.
	//

	printf("\nWaiting for connection %d\n", ++cConnections);
	t=time(NULL);
	do
	{
	    tv.tv_sec = sock_delay;
	    tv.tv_usec = 0;
	    rv=0;
	    FD_ZERO(&fds);
	    FD_SET(ListenSocket,&fds);
	    ret = select(ListenSocket+1,&fds,NULL,NULL,&tv);
	    if (ret == 0 || time(NULL)-t >= sock_delay)
	    {
		printf("Exit by timeout\n");
		pthread_mutex_lock(&thr_mutex);
		printf("Thread errors:%d, unfinished threads:%d\n",nErr,nThreads);
		if (nErr)
		    rv|=1;
		if (nThreads)    
		    rv|=2;
		goto cleanup;    
	    }
	    if (ret < 0)
	    {
		int err=errno;
		if (err == EAGAIN || err == EINTR)
		    continue;
		printf("Error in select:%d\n", err);    
		exit(1);
	    }
	}
	while(ret <= 0); 

	remoteSockaddrLength = sizeof(remoteAddress);

	Socket = accept(ListenSocket,
			(LPSOCKADDR)&remoteAddress,
			&remoteSockaddrLength);
	if(Socket == INVALID_SOCKET)
	{
	    printf( "accept() failed: %d\n", WSAGetLastError( ) );
	    goto cleanup;
	}

	printf("Socket connection established\n");
	pthread_create(&thr,&thr_attr,ServerThread,(void*)(ULONG_PTR)Socket);
    }


    

cleanup:
    // Освобождение дескриптора SSPI мандатов.
    hUTLS->pSSPI->FreeCredentialsHandle(&hServerCreds);

    // Закрытие хранилища сертификатов "MY".
    if(hMyCertStore)
    {
        CertCloseStore(hMyCertStore, 0);
    }

    DoneUTLS();
    if (pKDrv)	
    {
	kdone_gost(pKDrv);
	free(pKDrv); 	
    }
    if (hProvExch)
	CryptReleaseContext(hProvExch,0);
    return rv;
} //main


static BOOL FileToBuffer(const char * filename,void ** ppvBuffer, LPDWORD pcbBuffer)
{
    FILE * f=NULL;
    long flen=0;
    BOOL fRet=FALSE;
    void * pvBuffer=NULL;
    if (!filename)
	return FALSE;
    f=fopen(filename,"r");	
    if (!f)
	return FALSE;
    if (fseek(f,0,SEEK_END)!=0)
	goto end;
    flen=ftell(f);
    if (flen == -1)
	goto end;
    if (fseek(f,0,SEEK_SET)!=0)	
	goto end;
    pvBuffer=malloc(flen);
    if (!pvBuffer)
	goto end;
    if (fread(pvBuffer,flen,1,f)!=1)	
	goto end;
    fRet=TRUE;	
    *pcbBuffer=(DWORD)flen;
    *ppvBuffer=pvBuffer;
end:
    if (f)
	fclose(f);
    if (!fRet && pvBuffer)
	free(pvBuffer);

    return fRet;
}

static BOOL ErrorToBuffer(const char * filename,void ** ppvBuffer, LPDWORD pcbBuffer)
{
    static const char format[]="File %s not found\n";
    char * lpszBuffer = NULL;
    DWORD cbBuffer = (DWORD)(sizeof(format)+strlen(filename)-2);
    lpszBuffer = (char *)malloc(cbBuffer);
    if (!lpszBuffer)
	return FALSE;
    snprintf(lpszBuffer,cbBuffer,format,filename);
    *pcbBuffer = cbBuffer - 1;
    *ppvBuffer=lpszBuffer;
    return TRUE;
}


void CreateDriverContext(PDRTKTLS_DRV pDrv,PDRTKTLS_CTX *ppKCTX)
{
    unsigned kctx_len=0;
    kinit_ctx(pDrv,NULL,&kctx_len);
    *ppKCTX=(PDRTKTLS_CTX)malloc(kctx_len);
    kinit_ctx(pDrv,*ppKCTX,&kctx_len);
}
    
void CreateAndExchangeSA(PDRTKTLS_DRV pKDrv,PDRTKTLS_CTX pKCtx,HCRYPTPROV hProv,utlsdb_token * pTok)
{
    unsigned tlsdb_len=0;
    unsigned char * our_tlsdb=NULL;
    unsigned char * their_tlsdb=NULL;
    unsigned their_tlsdb_len=0;
    hUTLS->tlsdb.CreateEphemFn(hCPC2Crypt,hProv,0,&pTok->Priv,0,&tlsdb_len);
    our_tlsdb=(unsigned char *)malloc(tlsdb_len);
    hUTLS->tlsdb.CreateEphemFn(hCPC2Crypt,hProv,0,&pTok->Priv,our_tlsdb,&tlsdb_len);
    kexport_SA(pKDrv,NULL,&their_tlsdb_len);
    their_tlsdb=(unsigned char *)malloc(their_tlsdb_len);
    kexport_SA(pKDrv,their_tlsdb,&their_tlsdb_len);
    hUTLS->tlsdb.deSerializePubKeyFn(hCPC2Crypt,hProv,their_tlsdb,their_tlsdb_len,0,&pTok->Pub,&CPCConfig);
    free(their_tlsdb);
    kimport_SA(pKCtx,our_tlsdb,tlsdb_len);
    free(our_tlsdb);
}


static BOOL SendBufferKrn(
    SOCKET Socket, 
    PCredHandle phCreds, 
    PCtxtHandle phContext,
    void *pvBuffer, 
    DWORD cbBuffer)
{
    unsigned char * pbKernelKeys=NULL;
    unsigned cbKernelKeys=0;
    utlsdb_token tok;
#ifndef KRN_EMUL    
    HCRYPTPROV hProvExch=0;
    PDRTKTLS_DRV pKDrv=NULL;
#endif    
    PDRTKTLS_CTX pKCTX=NULL;
    SECURITY_STATUS scRet=0;
    BOOL fRet=FALSE;
    SecBuffer SB,SB2;

    SB.pvBuffer=NULL;
#ifndef KRN_EMUL    
    if (!CryptAcquireContext(&hProvExch,NULL,NULL,PROV_GOST_2012_256,CRYPT_VERIFYCONTEXT))
	goto cleanup;
    if (!InitializeDriver(&pKDrv,hProvExch,FALSE))
	goto cleanup;
#endif	
    CreateDriverContext(pKDrv,&pKCTX);
    CreateAndExchangeSA(pKDrv,pKCTX,hProvExch,&tok);
    scRet=hUTLS->pSSPI->ExportSecurityContext(phContext,
				CPUTLS_CONTEXT_EXPORT_TO_KERNEL,
				&SB,(void **)&tok);
    if (scRet)
	goto cleanup;
    SB.pvBuffer=malloc(SB.cbBuffer);
    if (!SB.pvBuffer)
	goto cleanup;
    scRet=hUTLS->pSSPI->ExportSecurityContext(phContext,
				CPUTLS_CONTEXT_EXPORT_TO_KERNEL,
				&SB,(void **)&tok);
    if (scRet)
	goto cleanup;
    scRet=kimport_keys(pKCTX,SB.pvBuffer,SB.cbBuffer);
    if (scRet)
	goto cleanup;
    scRet=kwritefile(pKCTX,Socket,pvBuffer,cbBuffer);
    if (scRet)
	goto cleanup;
    scRet=kexport_keys(pKCTX,NULL,&cbKernelKeys);
    if (scRet)
	goto cleanup;
    pbKernelKeys=(unsigned char *)malloc(cbKernelKeys);	
    if (!pbKernelKeys)
	goto cleanup;
    scRet=kexport_keys(pKCTX,pbKernelKeys,&cbKernelKeys);
    if (scRet)
	goto cleanup;
    SB2.pvBuffer=pbKernelKeys;	
    SB2.cbBuffer=cbKernelKeys;
    SB2.BufferType=SECBUFFER_DATA;
    scRet=hUTLS->pSSPI->ImportSecurityContext(NULL,&SB2,&tok,phContext);
    if (scRet)
	goto cleanup;
    fRet=TRUE;
cleanup: 
    free(SB.pvBuffer);
    free(pbKernelKeys);
    if (pKCTX)	
    {
	kdone_ctx(pKCTX);
	free(pKCTX);
    }
#ifndef KRN_EMUL    
    if (pKDrv)	
    {
	kdone_gost(pKDrv);
	free(pKDrv); 	
    }
#endif	

    hUTLS->tlsdb.DestroyPubKeyFn(0,&tok.Pub);
    hUTLS->tlsdb.DestroyPrivKeyFn(0,&tok.Priv);
    return fRet;	
}

//-------------------------------------------------------------
// Функция создания соединения с web сервером.
static void SaveSessionOnce(void)
{
    if (fExportSession)
    {
	utlsdb_token tok;
	if (!CreateSelfExportToken(pszContName, &tok))
	{
	    printf("Cannot create self-export token\n");
	    exit(2);
	}

	if (!ExportSessionToFile(phSaveContext, &tok, pszExportFileName))
	{
	    printf("Cannot export context to the file\n");
	    exit(2);
	}
    }
}

static void * ServerThread(void * arg)
{
    SOCKET Socket = (SOCKET)(ULONG_PTR)arg;
    INT err;
    INT i;
    utlsdb_token tok;

    CHAR objectName[OBJECT_NAME_LENGTH_MAX];
    DWORD currentDirectoryLength;
    INT bytesSent;
    DWORD bytesRead;
    DWORD cbContentLength;

    CtxtHandle      hContext;
    BOOL            fContextInitialized = FALSE;
    SecBufferDesc   Message;
    SecBuffer       Buffers[4];
    DWORD extra_buf=0;
    SecBufferDesc   MessageOut;
    SecBuffer       OutBuffers[4];
    SecPkgContext_StreamSizes Sizes;
    SECURITY_STATUS scRet = SEC_E_INTERNAL_ERROR;
    LPVOID pvFileBuffer=NULL;
    DWORD cbFileBuffer=0;
    BOOL bFileFound = FALSE;

    Message.ulVersion = SECBUFFER_VERSION;
    Message.cBuffers = 4;
    Message.pBuffers = Buffers;

    Buffers[0].BufferType = SECBUFFER_EMPTY;
    Buffers[1].BufferType = SECBUFFER_EMPTY;
    Buffers[2].BufferType = SECBUFFER_EMPTY;
    Buffers[3].BufferType = SECBUFFER_EMPTY;

    MessageOut.ulVersion = SECBUFFER_VERSION;
    MessageOut.cBuffers = 4;
    MessageOut.pBuffers = OutBuffers;

    OutBuffers[0].BufferType = SECBUFFER_EMPTY;
    OutBuffers[1].BufferType = SECBUFFER_EMPTY;
    OutBuffers[2].BufferType = SECBUFFER_EMPTY;
    OutBuffers[3].BufferType = SECBUFFER_EMPTY;



    //
    // Вычисление текущей директории для получения необходимых
    // файлов из нее.
    //

    if(!getcwd(objectName, OBJECT_NAME_LENGTH_MAX))
    {
	printf( "getcwd failed: %d\n", WSAGetLastError( ) );
	exit(1);
    }
    currentDirectoryLength=strlen(objectName);

    pthread_mutex_lock(&thr_mutex);
    nThreads++;
    pthread_mutex_unlock(&thr_mutex);

    PSecBuffer pDataBuffer;
    fd_set fds;
    struct timeval tv;
    int ret;


    fContextInitialized = FALSE;

    cbIoBuffer = 0;

    if(!SSPINegotiateLoop(Socket,
			  &hContext,
			  &hServerCreds,
			  TRUE,
			  TRUE))
    {
	printf("Couldn't connect\n");
	goto cleanup;
    }

    phSaveContext=&hContext;
    pthread_once(&save_session_once,SaveSessionOnce);
    fContextInitialized = TRUE;

    //
    // Вычисление размера заголовка:
    //

    scRet = hUTLS->pSSPI->QueryContextAttributes(&hContext, SECPKG_ATTR_STREAM_SIZES, &Sizes);


    if(scRet != SEC_E_OK)
    {
	printf("Couldn't get Sizes\n");
	goto cleanup;
    }


    //
    // Получение HTTP запроса от клиента.  
    //

    do {
	Buffers[0].pvBuffer = IoBuffer;
	Buffers[0].cbBuffer = cbIoBuffer;
	Buffers[0].BufferType = SECBUFFER_DATA;

	Buffers[1].BufferType = SECBUFFER_EMPTY;
	Buffers[2].BufferType = SECBUFFER_EMPTY;
	Buffers[3].BufferType = SECBUFFER_EMPTY;

	scRet = hUTLS->pSSPI->DecryptMessage(&hContext, &Message, 0, NULL);

	if(scRet == SEC_E_INCOMPLETE_MESSAGE)
	{
	    err = recv(Socket, IoBuffer + cbIoBuffer, IO_BUFFER_SIZE - cbIoBuffer, 0);
	    if (IS_SOCKET_ERROR(err) || (err == 0))
	    {
		  printf("recv failed: %d %d\n", err, WSAGetLastError());
		  goto cleanup;
	    }

	    printf("\nReceived %d (request) bytes from client\n", err);
		    
	    PrintHexDump(16, IoBuffer+cbIoBuffer);
		    
	    cbIoBuffer += err;
	}
    }
    while(scRet == SEC_E_INCOMPLETE_MESSAGE);

    if(scRet == SEC_I_CONTEXT_EXPIRED)
    {
	// Клиент закрыл сессию
	goto cleanup;
    }
	
    if(scRet != SEC_E_OK)
    {
	printf("Couldn't decrypt, error %lx\n", (unsigned long)scRet);
	goto cleanup;
    }
    cbIoBuffer = 0;
    //CPCSP-2729: первое сообщение может придти пустым
    if (Buffers[2].BufferType == SECBUFFER_EXTRA)
	extra_buf = 2;
    else if (Buffers[3].BufferType == SECBUFFER_EXTRA)
	extra_buf = 3;
    else 
	extra_buf = 0;

    if (Buffers[1].cbBuffer == 0 && extra_buf) 
    {
	Buffers[0].pvBuffer = Buffers[extra_buf].pvBuffer;
	Buffers[0].cbBuffer = Buffers[extra_buf].cbBuffer;
	Buffers[0].BufferType = SECBUFFER_DATA;

	Buffers[1].BufferType = SECBUFFER_EMPTY;
	Buffers[2].BufferType = SECBUFFER_EMPTY;
	Buffers[3].BufferType = SECBUFFER_EMPTY;

	scRet = hUTLS->pSSPI->DecryptMessage(&hContext, &Message, 0, NULL);
	if(scRet != SEC_E_OK)
	{
	    printf("Couldn't decrypt, error %lx\n", (unsigned long)scRet);
	    goto cleanup;
	}
    }


    #if 0
    // Расположение буфера данных.
    pDataBuffer  = NULL;
    for(i = 1; i < 4; i++)
      {
	if(Buffers[i].BufferType == SECBUFFER_DATA)
	  {
	    pDataBuffer = &Buffers[i];
	    break;
	  }
      }
    #else
    pDataBuffer=&Buffers[1];
    #endif    
    if(pDataBuffer == NULL)
      {
        scRet = SEC_E_INTERNAL_ERROR;
	goto cleanup;
      }

    // Проверка того, что данные в выходном буфере являются строкой,
    // заканчивающейся нулем, а также вывод этих данных.
    ((CHAR *) pDataBuffer->pvBuffer)[pDataBuffer->cbBuffer] = '\0';
    printf("\nMessage is: '%s'\n", (const char *)pDataBuffer->pvBuffer);


    // Обработка запроса для определения объекта запроса.
	
    if(!ParseRequest(
		     pDataBuffer->pvBuffer,
		     pDataBuffer->cbBuffer,
		     objectName+currentDirectoryLength,
		     &cbContentLength))
    {
        scRet = SEC_E_INTERNAL_ERROR;
	printf("Unable to parse message\n");
	goto cleanup;
    }

    if (!FileToBuffer(objectName,&pvFileBuffer,&cbFileBuffer))
    {
	printf("Cannot bufferize:%s\n",objectName);
	if (!ErrorToBuffer(objectName,&pvFileBuffer,&cbFileBuffer)) 
        {
            scRet = SEC_E_INTERNAL_ERROR;
	    goto cleanup;
        }
    }
    else
	bFileFound = TRUE;
    
    ZeroMemory(IoBuffer, Sizes.cbHeader);
    if (bFileFound)
	i = snprintf(IoBuffer + Sizes.cbHeader,IO_BUFFER_SIZE,
		    "HTTP/1.0 200 OK\r\nContent-Length: %d\r\n\r\n",
		    cbFileBuffer);
    else
	i = snprintf(IoBuffer + Sizes.cbHeader,IO_BUFFER_SIZE,
		    "HTTP/1.0 404 Not found\r\nContent-Length: %d\r\n\r\n",
		    cbFileBuffer);

    //
    // Построение буферов таким образом, чтобы заголовок и содержимое 
    // файла полностью в них поместились.
    //

    Buffers[0].pvBuffer = IoBuffer;
    Buffers[0].cbBuffer = Sizes.cbHeader;
    Buffers[0].BufferType = SECBUFFER_STREAM_HEADER;

    Buffers[1].pvBuffer = IoBuffer + Sizes.cbHeader;
    Buffers[1].cbBuffer = i;
    Buffers[1].BufferType = SECBUFFER_DATA;

    Buffers[2].pvBuffer = IoBuffer + Sizes.cbHeader + i;
    Buffers[2].cbBuffer = Sizes.cbTrailer;
    Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER;

    Buffers[3].BufferType = SECBUFFER_EMPTY;

    //
    // Построение буферов таким образом, чтобы заголовок и содержимое 
    // файла полностью в них поместились.
    //

    Buffers[0].pvBuffer = IoBuffer;
    Buffers[0].cbBuffer = Sizes.cbHeader;
    Buffers[0].BufferType = SECBUFFER_STREAM_HEADER;

    Buffers[1].pvBuffer = IoBuffer + Sizes.cbHeader;
    Buffers[1].cbBuffer = i;
    Buffers[1].BufferType = SECBUFFER_DATA;

    Buffers[2].pvBuffer = IoBuffer + Sizes.cbHeader + i;
    Buffers[2].cbBuffer = Sizes.cbTrailer;
    Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER;

    Buffers[3].BufferType = SECBUFFER_EMPTY;

    scRet = hUTLS->pSSPI->EncryptMessage(&hContext, 0, &Message, 0);

    if ( FAILED( scRet ) )
    {
	printf(" EncryptMessage failed with 0x%x\n", scRet );
	goto cleanup;
    }


    err = send( Socket,
		IoBuffer,
		Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer,
		0 );

    printf("\nSend %d header bytes to client\n", Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer);
    PrintHexDump(16, IoBuffer);
	
    if (IS_SOCKET_ERROR(err)){
	printf( "send failed: %d\n", WSAGetLastError( ) );
        scRet = SEC_E_INTERNAL_ERROR;
	goto cleanup;
    }

    //
    // Чтение и посылка данных из файла.
    //
    if (!SendBufferKrn(Socket,&hServerCreds,&hContext,pvFileBuffer,cbFileBuffer))
    {
        scRet = SEC_E_INTERNAL_ERROR;
	goto cleanup;
    }
    scRet=SEC_E_OK;

    cleanup:

    if(fContextInitialized)
    {
	scRet = DisconnectFromClient(Socket, &hServerCreds, &hContext);

	if(scRet == SEC_E_OK)
	{
	    fContextInitialized = FALSE;
	    Socket = INVALID_SOCKET;
	}
	else
	{
	    printf("Error disconnecting from server\n");
	    pthread_mutex_lock(&thr_mutex);
	    nErr++;
	    pthread_mutex_unlock(&thr_mutex);
	}
    }
    else  
    {
	pthread_mutex_lock(&thr_mutex);
	nErr++;
	pthread_mutex_unlock(&thr_mutex);
    }

    // Освобождение дескриптора SSPI контекста.
    if(fContextInitialized)
      {
	hUTLS->pSSPI->DeleteSecurityContext(&hContext);
      }

    // Закрытие сокета.
    if(Socket != INVALID_SOCKET)
      {
	closesocket(Socket);
      }
    free(pvFileBuffer);
    pthread_mutex_lock(&thr_mutex);  
    nThreads--;
    pthread_mutex_unlock(&thr_mutex);
    return (void *)(LONG_PTR)scRet;  

} // WebServer

//-------------------------------------------------------------
// Функция установления связи.

static
BOOL
SSPINegotiateLoop(
    SOCKET          Socket,
    PCtxtHandle     phContext,
    PCredHandle     phCred,
    BOOL            fDoInitialRead,
    BOOL            NewContext)
{
    TimeStamp            tsExpiry;
    SECURITY_STATUS      scRet;
    SecBufferDesc        InBuffer;
    SecBufferDesc        OutBuffer;
    SecBuffer            InBuffers[2];
    SecBuffer            OutBuffers[1];
    DWORD                err = 0;

    BOOL                 fDoRead;
    BOOL                 fInitContext = NewContext;

    DWORD                dwSSPIFlags;
    unsigned long        dwSSPIOutFlags;

    fDoRead = fDoInitialRead;

    dwSSPIFlags =   ASC_REQ_SEQUENCE_DETECT        |
                    ASC_REQ_REPLAY_DETECT      |
                    ASC_REQ_CONFIDENTIALITY  |
                    ASC_REQ_EXTENDED_ERROR    |
                    ASC_REQ_ALLOCATE_MEMORY  |
                    ASC_REQ_STREAM;

    

    //
    //  Установка OutBuffer для вызова InitializeSecurityContext
    //

    OutBuffer.cBuffers = 1;
    OutBuffer.pBuffers = OutBuffers;
    OutBuffer.ulVersion = SECBUFFER_VERSION;


    scRet = SEC_I_CONTINUE_NEEDED;

    while( scRet == SEC_I_CONTINUE_NEEDED ||
            scRet == SEC_E_INCOMPLETE_MESSAGE ||
            scRet == SEC_I_INCOMPLETE_CREDENTIALS) 
    {

        if(0 == cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE)
        {
            if(fDoRead)
            {
                err = recv(Socket, IoBuffer+cbIoBuffer, IO_BUFFER_SIZE, 0);

                if (IS_SOCKET_ERROR(err) || err == 0)
                {
                    printf(" recv failed: %d %d\n", err, WSAGetLastError() );
                    return FALSE;
                }
                else
                {
                    printf("\nReceived %d (handshake) bytes from client\n", err);
                        PrintHexDump(min(16, err), IoBuffer+cbIoBuffer);
                   
                    cbIoBuffer += err;
                }
            }
            else
            {
                fDoRead = TRUE;
            }
        }

        //
        // InBuffers[1] используется для получения дополнительных данные,
        //  которые не были обработаны SSPI/SCHANNEL на данном шаге цикла.
        //

        InBuffers[0].pvBuffer = IoBuffer;
        InBuffers[0].cbBuffer = cbIoBuffer;
        InBuffers[0].BufferType = SECBUFFER_TOKEN;

        InBuffers[1].pvBuffer   = NULL;
        InBuffers[1].cbBuffer   = 0;
        InBuffers[1].BufferType = SECBUFFER_EMPTY;

        InBuffer.cBuffers        = 2;
        InBuffer.pBuffers        = InBuffers;
        InBuffer.ulVersion       = SECBUFFER_VERSION;


        //
        // Инициализация производится таким образом, чтобы pvBuffer содержал NULL. 
	// Это сделано для того, чтобы 
	// в случае неудачи не было необходимости выполнять
	// освобождение памяти.
        //

        OutBuffers[0].pvBuffer   = NULL;
        OutBuffers[0].BufferType = SECBUFFER_TOKEN;
        OutBuffers[0].cbBuffer   = 0;


        scRet = hUTLS->pSSPI->AcceptSecurityContext(
                        phCred,
                        (fInitContext?NULL:phContext),
                        &InBuffer,
                        dwSSPIFlags,
                        SECURITY_NATIVE_DREP,
                        (fInitContext?phContext:NULL),
                        &OutBuffer,
                        &dwSSPIOutFlags,
                        &tsExpiry);



        fInitContext = FALSE;


        if ( scRet == SEC_E_OK ||
             scRet == SEC_I_CONTINUE_NEEDED ||
             (FAILED(scRet) && (0 != (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR))))
        {
            if  (OutBuffers[0].cbBuffer != 0    &&
                 OutBuffers[0].pvBuffer != NULL )
            {
                //
                // Посылка ответа серверу
                //
                err = send( Socket,
                            OutBuffers[0].pvBuffer,
                            OutBuffers[0].cbBuffer,
                            0 );

                printf("\nSend %d handshake bytes to client\n", OutBuffers[0].cbBuffer);
                    PrintHexDump(16, OutBuffers[0].pvBuffer);
              
                hUTLS->pSSPI->FreeContextBuffer( OutBuffers[0].pvBuffer );
                OutBuffers[0].pvBuffer = NULL;
            }
        }


        if ( scRet == SEC_E_OK )
        {


            if ( InBuffers[1].BufferType == SECBUFFER_EXTRA )
            {

                    memcpy(IoBuffer,
                           (LPBYTE) (IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer)),
                            InBuffers[1].cbBuffer);
                    cbIoBuffer = InBuffers[1].cbBuffer;
            }
            else
            {
                cbIoBuffer = 0;
            }
	    {
		//The sample of using tsExpiry parametre. Valid only on last call 
		//of AcceptSecurityContext
		double hi = tsExpiry.HighPart;
		double lo = tsExpiry.LowPart;
		    // Convert 100-ns interval since January 1, 1601 (UTC) 
		    // to 1-sec interval science January 1, 1970, UTC
		time_t clock_1970 = (time_t)(
			((ldexp(hi, 32) + lo)*100.e-9)
			- 11644473600. //SystemTimeToFileTime({.wYear = 1970, .wMonth = 1, .wDay = 1}... 
		    );
		    // Convert UTC time_t to local time string
		printf("Security Context of this session valid till %s (local time)\n", ctime(&clock_1970));
	    }
	    {
		// Sample for usage QueryContextAttributes() & FileTimeToSystemTime()
		FILETIME   ft;
		SYSTEMTIME st;
		unsigned hi;
		unsigned lo;
		SecPkgContext_Lifespan ls;

		scRet = hUTLS->pSSPI->QueryContextAttributes(phContext,
					SECPKG_ATTR_LIFESPAN, &ls);
		hi = ls.tsStart.HighPart;
		lo = ls.tsStart.LowPart;
		memcpy(&ft, &ls.tsStart, sizeof(ft));
		FileTimeToSystemTime(&ft,&st);
		printf("Connection start {%x, %x}: %d/%d/%d %d:%d:%d.%03d UTC\n", 
			    hi, lo,
			    st.wYear, st.wMonth, st.wDay,
			    st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);

		hi = ls.tsExpiry.HighPart;
		lo = ls.tsExpiry.LowPart;
		memcpy(&ft, &ls.tsExpiry, sizeof(ft));
		FileTimeToSystemTime(&ft,&st);
		printf("Connection expiry {%x, %x}: %d/%d/%d %d:%d:%d.%03d UTC\n", 
			    hi, lo,
			    st.wYear, st.wMonth, st.wDay,
			    st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
	    }
            return TRUE;
        }
        else if (FAILED(scRet) && (scRet != SEC_E_INCOMPLETE_MESSAGE))
        {

            printf("Accept Security Context Failed with error code %lx\n", (unsigned long)scRet);
            return FALSE;

        }



        if ( scRet != SEC_E_INCOMPLETE_MESSAGE &&
             scRet != SEC_I_INCOMPLETE_CREDENTIALS)
        {


            if ( InBuffers[1].BufferType == SECBUFFER_EXTRA )
            {



                memcpy(IoBuffer,
                       (LPBYTE) (IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer)),
                        InBuffers[1].cbBuffer);
                cbIoBuffer = InBuffers[1].cbBuffer;
            }
            else
            {
                //
                // подготовка к следующему получению данных
                //

                cbIoBuffer = 0;
            }
        }
    }

    return FALSE;
} //SSPINegotiateLoop


//-------------------------------------------------------------
// Функция обработки запроса.
static
BOOL
ParseRequest (
    IN PCHAR InputBuffer,
    IN INT InputBufferLength,
    OUT PCHAR ObjectName,
    OUT DWORD *pcbContentLength)
{
    PCHAR s = InputBuffer;
    DWORD i;

    *pcbContentLength = 0;

    while ( (INT)(s - InputBuffer) < InputBufferLength )
    {

        // Обработка команды

        //
        // Первоначально определяется начинается ли данная строчка 
        // с командны GET.
        //

        while(*s != '\0' && *s != ' ' && *s != '\t')
        {
            *s = (CHAR)toupper(*s);
            s++;
        }

        if(*s == '\0' || *s == ' ' || *s == '\t')
        {
            *s = '\0';
//            printf("Verb is :%s\n", InputBuffer);

            //
            // Это команда GET. Пропуск пробелов и символов табуляции.
            //
            for ( s++; *s == ' ' || *s == '\t'; s++ );

            //
            // Получение имени объекта.
            //

            for ( i = 0; *s != 0xA && *s != 0xD && *s != ' ' && *s != '\0'; s++, i++ ) {
                ObjectName[i] = *s;
            }

            ObjectName[i] = '\0';

            //
            // Разбор завершен.
            //

            if(strcmp(ObjectName, "/") == 0)
            {
                strncpy(ObjectName,"/Default.Htm",OBJECT_NAME_LENGTH_MAX);
            }
            if(strcmp(InputBuffer, "POST") == 0)
            {
                char * content_length;
                DWORD cbContent = 0;
                // Поиск длины содержимого;
                content_length = strstr(s, "Content-Length: ");
                if(content_length)
                {
                    cbContent = atoi(content_length+16);
                    printf("Content Length is %d\n", cbContent);
                    *pcbContentLength = cbContent;
                }
            }
            return TRUE;
        }

        //
        // Пропус концы строки и продолжение разбора.
        //

        while ( *s != 0xA && *s != 0xD )
        {
            s++;
        }

        s++;

        if ( *s == 0xD || *s == 0xA )
        {
            s++;
        }
    }

    return FALSE;

} // ParseRequest



//-------------------------------------------------------------
// Функция создания мандатов.
static
HRESULT
CreateCredentials(
    LPSTR pszUserName,              // in
    PCredHandle phCreds)            // out
{
    SCHANNEL_CRED   SchannelCred;
    TimeStamp       tsExpiry;
    SECURITY_STATUS Status;
    PCCERT_CONTEXT  pCertContext = NULL;

    if(pszUserName == NULL || strlen(pszUserName) == 0)
    {
        printf("**** No user name specified!\n");
        return SEC_E_NO_CREDENTIALS;
    }

    // Открытие хранилища сертификатов "MY".
        hMyCertStore = CertOpenSystemStore(0, "MY");
        
        if(!hMyCertStore)
        {
            printf("**** Error 0x%x returned by CertOpenSystemStore\n", 
                GetLastError());
            return SEC_E_NO_CREDENTIALS;
        }
    
    // Поиск сертификата. В данном примере осуществляется поиск сертификата 
    // по subject name, содержащем заданное в командной строке имя пользователя.
    pCertContext = CertFindCertificateInStore(hMyCertStore, 
                                              X509_ASN_ENCODING, 
                                              0,
                                              CERT_FIND_SUBJECT_STR_A,
                                              pszUserName,
                                              NULL);
    if(pCertContext == NULL)
    {
        printf("**** Error 0x%x returned by CertFindCertificateInStore\n",
            GetLastError());
        return SEC_E_NO_CREDENTIALS;
    }


    // Построение структуры Schannel мандатов. 
    // В данном примере определяются используемые протокол и сертификат.
    
    ZeroMemory(&SchannelCred, sizeof(SchannelCred));

    SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;

    SchannelCred.cCreds = 1;
    SchannelCred.paCred = &pCertContext;

    SchannelCred.grbitEnabledProtocols = dwProtocol;


    // Создание SSPI мандата.
    Status = hUTLS->pSSPI->AcquireCredentialsHandle(
                        NULL,                   // Имя администратора
                        UNISP_NAME_A,           // Имя пакета
                        SECPKG_CRED_INBOUND,    // Флаг, определяющий использование
                        NULL,                   // Указатель на идентификатор пароля
                        &SchannelCred,          // Данные пакета
                        NULL,                   // Указатель на функицю GetKey()
                        NULL,                   // Значения, передаваемые функции GetKey()
                        phCreds,                // (out) Даскриптор мандата
                        &tsExpiry);             // (out) Период актуальности (необязательно)
    if(Status != SEC_E_OK)
    {
        printf("**** Error 0x%x returned by AcquireCredentialsHandle\n", Status);
        return Status;
    }

    // Освобождение контекста сертификата. В Schannel уже создана его копия.
    if(pCertContext)
    {
        CertFreeCertificateContext(pCertContext);
    }


    return SEC_E_OK;
} //CreateCredentials 

//-------------------------------------------------------------
// Функция, выполняющая разрыв соединения с клиентом.
static
LONG
DisconnectFromClient(
    SOCKET          Socket, 
    PCredHandle     phCreds,
    CtxtHandle *    phContext)
{
    DWORD           dwType;
    PCHAR           pbMessage;
    DWORD           cbMessage;
    DWORD           cbData;

    SecBufferDesc   OutBuffer;
    SecBuffer       OutBuffers[1];
    DWORD           dwSSPIFlags;
    unsigned long   dwSSPIOutFlags;
    TimeStamp       tsExpiry;
    DWORD           Status;

    //
    // Уведомление schannel о закрытии соединения.
    //

    dwType = SCHANNEL_SHUTDOWN;

    OutBuffers[0].pvBuffer   = &dwType;
    OutBuffers[0].BufferType = SECBUFFER_TOKEN;
    OutBuffers[0].cbBuffer   = sizeof(dwType);

    OutBuffer.cBuffers  = 1;
    OutBuffer.pBuffers  = OutBuffers;
    OutBuffer.ulVersion = SECBUFFER_VERSION;

    Status = hUTLS->pSSPI->ApplyControlToken(phContext, &OutBuffer);

    if(FAILED(Status)) 
    {
        printf("**** Error 0x%x returned by ApplyControlToken\n", Status);
        goto cleanup;
    }

    //
    // Построение SSL сообщения, являющегося уведомлением о закрытии.
    //

    dwSSPIFlags =   ASC_REQ_SEQUENCE_DETECT     |
                    ASC_REQ_REPLAY_DETECT       |
                    ASC_REQ_CONFIDENTIALITY     |
                    ASC_REQ_EXTENDED_ERROR      |
                    ASC_REQ_ALLOCATE_MEMORY     |
                    ASC_REQ_STREAM;

    OutBuffers[0].pvBuffer   = NULL;
    OutBuffers[0].BufferType = SECBUFFER_TOKEN;
    OutBuffers[0].cbBuffer   = 0;

    OutBuffer.cBuffers  = 1;
    OutBuffer.pBuffers  = OutBuffers;
    OutBuffer.ulVersion = SECBUFFER_VERSION;

    Status = hUTLS->pSSPI->AcceptSecurityContext(
                    phCreds,
                    phContext,
                    NULL,
                    dwSSPIFlags,
                    SECURITY_NATIVE_DREP,
                    NULL,
                    &OutBuffer,
                    &dwSSPIOutFlags,
                    &tsExpiry);

    if(FAILED(Status)) 
    {
        printf("**** Error 0x%x returned by AcceptSecurityContext\n", Status);
        goto cleanup;
    }

    pbMessage = OutBuffers[0].pvBuffer;
    cbMessage = OutBuffers[0].cbBuffer;


    //
    // Посылка этого сообщения клиенту.
    //

    if(pbMessage != NULL && cbMessage != 0)
    {
        cbData = send(Socket, pbMessage, cbMessage, 0);
        if(IS_SOCKET_ERROR(cbData)|| cbData == 0)
        {
            Status = WSAGetLastError();
            printf("**** Error %d sending close notify\n", Status);
            goto cleanup;
        }

        printf("\n%d bytes of handshake data sent\n", cbData);

            PrintHexDump(min(16, cbData), pbMessage);
       
        // Освобождение выходного буфера.
        hUTLS->pSSPI->FreeContextBuffer(pbMessage);
    }
    

cleanup:

    // Освобождение закрытого контекста.
    hUTLS->pSSPI->DeleteSecurityContext(phContext);

    // Закрытие сокета.
    closesocket(Socket);

    return Status;
} //DisconnectFromClient

//-------------------------------------------------------------
// Функция перевода в 16-ую систему исчесления.

static void 
PrintHexDump(DWORD length, PCHAR buffer)
{
    DWORD i,count,index;
    static const CHAR rgbDigits[]="0123456789abcdef";
    unsigned char b;
    CHAR rgbLine[100];
    char cbLine;

    for(index = 0; length; length -= count, buffer += count, index += count) 
    {
        count = (length > 16) ? 16:length;

        snprintf(rgbLine, 100, "%4.4x  ",index);
        cbLine = 6;

        for(i=0;i<count;i++) 
        {
	    b=(unsigned char)buffer[i];
            rgbLine[cbLine++] = rgbDigits[b >> 4];
            rgbLine[cbLine++] = rgbDigits[b & 0x0f];
            if(i == 7) 
            {
                rgbLine[cbLine++] = ':';
            } 
            else 
            {
                rgbLine[cbLine++] = ' ';
            }
        }
        for(; i < 16; i++) 
        {
            rgbLine[cbLine++] = ' ';
            rgbLine[cbLine++] = ' ';
            rgbLine[cbLine++] = ' ';
        }

        rgbLine[cbLine++] = ' ';

        for(i = 0; i < count; i++) 
        {
	    b=(unsigned char)buffer[i];
            if( b  < 32 || b > 126 || b == '%') 
            {
                rgbLine[cbLine++] = '.';
            } 
            else 
            {
                rgbLine[cbLine++] = (char)b;
            }
        }

        rgbLine[cbLine] = 0;
        printf("%s\n", rgbLine);
    }
} // Конец PrintHexDump
