// SoftEther VPN Source Code - Developer Edition Master Branch
// Cedar Communication Module


// Proto_SSTP.c
// SSTP (Microsoft Secure Socket Tunneling Protocol) protocol stack

#include "Proto_SSTP.h"

#include "Connection.h"
#include "Proto_PPP.h"
#include "Server.h"

#include "Mayaqua/HTTP.h"
#include "Mayaqua/Kernel.h"
#include "Mayaqua/Memory.h"
#include "Mayaqua/Str.h"
#include "Mayaqua/Tick64.h"

const PROTO_IMPL *SstpGetProtoImpl()
{
	static const PROTO_IMPL impl =
	{
		SstpName,
		SstpOptions,
		NULL,
		SstpInit,
		SstpFree,
		NULL,
		SstpProcessData,
		NULL
	};

	return &impl;
}

const char *SstpName()
{
	return "SSTP";
}

const PROTO_OPTION *SstpOptions()
{
	static const PROTO_OPTION options[] =
	{
		{ .Name = NULL, .Type = PROTO_OPTION_UNKNOWN }
	};

	return options;
}

bool SstpInit(void **param, const LIST *options, CEDAR *cedar, INTERRUPT_MANAGER *im, SOCK_EVENT *se, const char *cipher, const char *hostname)
{
	if (param == NULL || options == NULL || cedar == NULL || im == NULL || se == NULL)
	{
		return false;
	}

	Debug("SstpInit(): cipher: %s, hostname: %s\n", cipher, hostname);

	*param = NewSstpServer(cedar, im, se, cipher, hostname);

	return true;
}

void SstpFree(void *param)
{
	FreeSstpServer(param);
}

bool SstpProcessData(void *param, TCP_RAW_DATA *in, FIFO *out)
{
	FIFO *recv_fifo;
	bool disconnected = false;
	SSTP_SERVER *server = param;

	if (server == NULL || in == NULL || out == NULL)
	{
		return false;
	}

	if (server->Status == SSTP_SERVER_STATUS_NOT_INITIALIZED)
	{
		HTTP_HEADER *header;
		char *header_str, date_str[MAX_SIZE];

		GetHttpDateStr(date_str, sizeof(date_str), SystemTime64());

		header = NewHttpHeader("HTTP/1.1", "200", "OK");
		AddHttpValue(header, NewHttpValue("Content-Length", "18446744073709551615"));
		AddHttpValue(header, NewHttpValue("Server", "Microsoft-HTTPAPI/2.0"));
		AddHttpValue(header, NewHttpValue("Date", date_str));

		header_str = HttpHeaderToStr(header);

		FreeHttpHeader(header);

		if (header_str == NULL)
		{
			return false;
		}

		WriteFifo(out, header_str, StrLen(header_str));

		Free(header_str);

		Copy(&server->ClientIp, &in->SrcIP, sizeof(server->ClientIp));
		server->ClientPort = in->SrcPort;
		Copy(&server->ServerIp, &in->DstIP, sizeof(server->ServerIp));
		server->ServerPort = in->DstPort;

		server->Status = SSTP_SERVER_STATUS_REQUEST_PENGING;

		return true;
	}

	recv_fifo = in->Data;

	while (recv_fifo->size >= 4)
	{
		UCHAR *first4;
		bool ok = false;
		UINT read_size = 0;

		// Read 4 bytes from the beginning of the received queue.
		first4 = ((UCHAR *)recv_fifo->p) + recv_fifo->pos;
		if (first4[0] == SSTP_VERSION_1)
		{
			const USHORT len = READ_USHORT(first4 + 2) & 0xFFF;
			if (len >= 4)
			{
				ok = true;

				if (recv_fifo->size >= len)
				{
					UCHAR *data;
					BLOCK *b;

					read_size = len;
					data = Malloc(read_size);

					ReadFifo(recv_fifo, data, read_size);

					b = NewBlock(data, read_size, 0);

					InsertQueue(server->RecvQueue, b);
				}
			}
		}

		if (read_size == 0)
		{
			break;
		}

		if (ok == false)
		{
			// Bad packet received, trigger disconnection.
			disconnected = true;
			break;
		}
	}

	// Process the timer interrupt
	SstpProcessInterrupt(server);

	if (server->Disconnected)
	{
		disconnected = true;
	}

	while (true)
	{
		BLOCK *b = GetNext(server->SendQueue);
		if (b == NULL)
		{
			break;
		}

		// Discard the data block if the transmission queue's size is greater than ~2.5 MB.
		if (b->PriorityQoS || (FifoSize(out) <= MAX_BUFFERING_PACKET_SIZE))
		{
			WriteFifo(out, b->Buf, b->Size);
		}

		FreeBlock(b);
	}

	if (disconnected)
	{
		return false;
	}

	return true;
}

// Process the SSTP control packet reception
void SstpProcessControlPacket(SSTP_SERVER *s, SSTP_PACKET *p)
{
	// Validate arguments
	if (s == NULL || p == NULL || p->IsControl == false)
	{
		return;
	}

	Debug("SSTP Control Packet Recv: Msg = %u, Num = %u\n", p->MessageType, LIST_NUM(p->AttributeList));

	switch (p->MessageType)
	{
	case SSTP_MSG_CALL_CONNECT_REQUEST:		// Receive a connection request from a client
		if (s->Aborting == false && s->Disconnecting == false)
		{
			if (s->Status == SSTP_SERVER_STATUS_REQUEST_PENGING)
			{
				SSTP_ATTRIBUTE *protocol_id = SstpFindAttribute(p, SSTP_ATTRIB_ENCAPSULATED_PROTOCOL_ID);
				if (protocol_id != NULL && protocol_id->DataSize == 2 &&
					READ_USHORT(protocol_id->Data) == SSTP_ENCAPSULATED_PROTOCOL_PPP)
				{
					// Accept the connection request by the PPP protocol
					SSTP_PACKET *ret;

					// Generation of random numbers
					Rand(s->SentNonce, SSTP_NONCE_SIZE);

					ret = SstpNewControlPacketWithAnAttribute(SSTP_MSG_CALL_CONNECT_ACK,
						SstpNewCryptoBindingRequestAttribute(CERT_HASH_PROTOCOL_SHA256, s->SentNonce));

					SstpSendPacket(s, ret);

					SstpFreePacket(ret);

					s->Status = SSTP_SERVER_STATUS_CONNECTED_PENDING;

					s->EstablishedCount++;
				}
				else
				{
					// Refuse to accept for a connection request other than the PPP protocol
					SSTP_PACKET *ret = SstpNewControlPacketWithAnAttribute(SSTP_MSG_CALL_CONNECT_NAK,
						SstpNewStatusInfoAttribute(SSTP_ATTRIB_ENCAPSULATED_PROTOCOL_ID, ATTRIB_STATUS_VALUE_NOT_SUPPORTED));

					SstpSendPacket(s, ret);

					SstpFreePacket(ret);
				}
			}
		}
		break;

	case SSTP_MSG_CALL_CONNECTED:			// Connection from the client complete
		if (s->Aborting == false && s->Disconnecting == false)
		{
			if (s->Status == SSTP_SERVER_STATUS_CONNECTED_PENDING)
			{
				s->Status = SSTP_SERVER_STATUS_ESTABLISHED;

				Debug("SSTP Connected.\n");
			}
		}
		break;

	case SSTP_MSG_CALL_DISCONNECT:			// Receive a disconnect request from the client
	case SSTP_MSG_CALL_DISCONNECT_ACK:
		s->DisconnectRecved = true;
		SstpDisconnect(s);
		break;

	case SSTP_MSG_CALL_ABORT:				// Receive a disconnect request from the client
		s->AbortReceived = true;
		SstpAbort(s);
		break;
	}
}

// Process the SSTP received data packet
void SstpProcessDataPacket(SSTP_SERVER *s, SSTP_PACKET *p)
{
	PPP_SESSION *underlyingSession;

	// Validate arguments
	if (s == NULL || p == NULL || p->IsControl)
	{
		return;
	}

	//Debug("SSTP Data Packet Recv: Size = %u\n", p->DataSize);

	if (s->PPPThread == NULL)
	{
		// Create a thread to initialize the new PPP module
		underlyingSession = NewPPPSession(s->Cedar, &s->ClientIp, s->ClientPort, &s->ServerIp, s->ServerPort,
			s->TubeSend, s->TubeRecv, SSTP_IPC_POSTFIX, SSTP_IPC_CLIENT_NAME,
			s->ClientHostName, s->ClientCipherName, 0);
		s->PPPSession = underlyingSession;
		s->PPPThread = underlyingSession->SessionThread;
	}

	// Pass the received data to the PPP module
	TubeSendEx(s->TubeRecv, p->Data, p->DataSize, NULL, true);
	s->FlushRecvTube = true;
}

// Process the SSTP received packet
void SstpProcessPacket(SSTP_SERVER *s, SSTP_PACKET *p)
{
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return;
	}

	s->LastRecvTick = s->Now;

	if (p->IsControl)
	{
		// Control packet
		SstpProcessControlPacket(s, p);
	}
	else
	{
		// Data packet
		SstpProcessDataPacket(s, p);
	}
}

// Send a SSTP packet
void SstpSendPacket(SSTP_SERVER *s, SSTP_PACKET *p)
{
	BUF *b;
	BLOCK *block;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return;
	}

	if (p->IsControl)
	{
		Debug("SSTP Control Packet Send: Msg = %u, Num = %u\n", p->MessageType, LIST_NUM(p->AttributeList));
	}
	else
	{
		//Debug("SSTP Data Packet Send: Size=%u\n", p->DataSize);
	}

	b = SstpBuildPacket(p);
	if (b == NULL)
	{
		return;
	}

	block = NewBlock(b->Buf, b->Size, 0);
	block->PriorityQoS = p->IsControl;
	Free(b);

	InsertQueue(s->SendQueue, block);
}

// Process the timer interrupt
void SstpProcessInterrupt(SSTP_SERVER *s)
{
	UINT64 sstpTimeout = SSTP_TIMEOUT;
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	s->Now = Tick64();

	s->FlushRecvTube = false;

	// Process the received packet
	while (true)
	{
		BLOCK *b = GetNext(s->RecvQueue);
		SSTP_PACKET *p;

		if (b == NULL)
		{
			break;
		}

		p = SstpParsePacket(b->Buf, b->Size);
		if (p == NULL)
		{
			// Disconnect the SSTP since a bad packet received
			SstpAbort(s);
		}
		else
		{
			// Process the received packet
			SstpProcessPacket(s, p);

			SstpFreePacket(p);
		}

		FreeBlock(b);
	}

	if (s->FlushRecvTube)
	{
		TubeFlush(s->TubeRecv);
	}

	// Transmit a packet that the PPP module is trying to send via the SSTP
	while (true)
	{
		TUBEDATA *d = TubeRecvAsync(s->TubeSend);
		SSTP_PACKET *p;
		if (d == NULL)
		{
			break;
		}

		p = SstpNewDataPacket(d->Data, d->DataSize);

		SstpSendPacket(s, p);

		SstpFreePacket(p);

		FreeTubeData(d);
	}

	if (s->Status == SSTP_SERVER_STATUS_ESTABLISHED)
	{
		if (s->Disconnecting == false && s->Aborting == false)
		{
			// Periodic transmission of Echo Request
			if (s->NextSendEchoRequestTick == 0 || s->NextSendEchoRequestTick <= s->Now)
			{
				UINT64 next_interval = (UINT64)(SSTP_ECHO_SEND_INTERVAL_MIN + Rand32() % (SSTP_ECHO_SEND_INTERVAL_MAX - SSTP_ECHO_SEND_INTERVAL_MIN));
				SSTP_PACKET *p;

				s->NextSendEchoRequestTick = s->Now + next_interval;
				AddInterrupt(s->Interrupt, s->NextSendEchoRequestTick);

				p = SstpNewControlPacket(SSTP_MSG_ECHO_REQUEST);

				SstpSendPacket(s, p);

				SstpFreePacket(p);
			}
		}
	}

	if (s->PPPSession != NULL && s->PPPSession->DataTimeout > sstpTimeout)
	{
		sstpTimeout = s->PPPSession->DataTimeout;
	}

	if ((s->LastRecvTick + sstpTimeout) <= s->Now)
	{
		// Disconnect the SSTP because a timeout occurred
		SstpAbort(s);
		s->Disconnected = true;
	}

	if (IsTubeConnected(s->TubeRecv) == false || IsTubeConnected(s->TubeSend) == false)
	{
		// Disconnect the SSTP since the PPP module is disconnected
		SstpDisconnect(s);
	}

	if (s->Disconnecting)
	{
		// Normal disconnection process
		if (s->DisconnectSent == false)
		{
			// Send a Disconnect
			SSTP_PACKET *ret = SstpNewControlPacket(s->DisconnectRecved ? SSTP_MSG_CALL_DISCONNECT_ACK : SSTP_MSG_CALL_DISCONNECT);

			SstpSendPacket(s, ret);

			SstpFreePacket(ret);

			s->DisconnectSent = true;
		}
	}

	if (s->Aborting)
	{
		// Abnormal disconnection processing
		if (s->AbortSent == false)
		{
			// Send the Abort
			SSTP_PACKET *ret = SstpNewControlPacket(SSTP_MSG_CALL_ABORT);

			SstpSendPacket(s, ret);

			SstpFreePacket(ret);

			s->AbortSent = true;
		}
	}

	if (s->DisconnectSent && s->DisconnectRecved)
	{
		// Disconnect after exchanging the Disconnect each other
		s->Disconnected = true;
	}

	if (s->AbortSent && s->AbortReceived)
	{
		// Disconnect after exchanging the Abort each other
		s->Disconnected = true;
	}
}

// Create a new SSTP control packet with an Attribute
SSTP_PACKET *SstpNewControlPacketWithAnAttribute(USHORT message_type, SSTP_ATTRIBUTE *a)
{
	SSTP_PACKET *p = SstpNewControlPacket(message_type);

	if (a != NULL)
	{
		Add(p->AttributeList, a);
	}

	return p;
}

// Create a new SSTP control packet
SSTP_PACKET *SstpNewControlPacket(USHORT message_type)
{
	SSTP_PACKET *p = ZeroMalloc(sizeof(SSTP_PACKET));

	p->IsControl = true;
	p->MessageType = message_type;
	p->Version = SSTP_VERSION_1;
	p->AttributeList = NewListFast(NULL);

	return p;
}

// Create a new SSTP data packet
SSTP_PACKET *SstpNewDataPacket(UCHAR *data, UINT size)
{
	SSTP_PACKET *p = ZeroMalloc(sizeof(SSTP_PACKET));

	p->IsControl = false;
	p->Data = Clone(data, size);
	p->DataSize = size;

	return p;
}

// Get the Attribute with the specified ID from SSTP packet
SSTP_ATTRIBUTE *SstpFindAttribute(SSTP_PACKET *p, UCHAR attribute_id)
{
	UINT i;
	// Validate arguments
	if (p == NULL)
	{
		return NULL;
	}

	for (i = 0;i < LIST_NUM(p->AttributeList);i++)
	{
		SSTP_ATTRIBUTE *a = LIST_DATA(p->AttributeList, i);

		if (a->AttributeId == attribute_id)
		{
			return a;
		}
	}

	return NULL;
}

// Disconnect the SSTP normally
void SstpDisconnect(SSTP_SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	s->Disconnecting = true;
}

// Disconnect the SSTP abnormally
void SstpAbort(SSTP_SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	s->Aborting = true;
}

// Create a Crypto Binding Request Attribute
SSTP_ATTRIBUTE *SstpNewCryptoBindingRequestAttribute(UCHAR hash_protocol_bitmask, UCHAR *nonce_32bytes)
{
	SSTP_ATTRIBUTE *a;
	UCHAR uc;
	BUF *b = NewBuf();

	uc = 0;
	WriteBuf(b, &uc, 1);
	WriteBuf(b, &uc, 1);
	WriteBuf(b, &uc, 1);
	WriteBuf(b, &hash_protocol_bitmask, 1);

	WriteBuf(b, nonce_32bytes, SSTP_NONCE_SIZE);

	a = SstpNewAttribute(SSTP_ATTRIB_CRYPTO_BINDING_REQ, b->Buf, b->Size);

	FreeBuf(b);

	return a;
}

// Create a Status Info Attribute
SSTP_ATTRIBUTE *SstpNewStatusInfoAttribute(UCHAR attrib_id, UINT status)
{
	SSTP_ATTRIBUTE *a;
	UCHAR uc;
	BUF *b = NewBuf();

	uc = 0;
	WriteBuf(b, &uc, 1);
	WriteBuf(b, &uc, 1);
	WriteBuf(b, &uc, 1);
	WriteBuf(b, &attrib_id, 1);

	WriteBufInt(b, status);

	a = SstpNewAttribute(SSTP_ATTRIB_STATUS_INFO, b->Buf, b->Size);

	FreeBuf(b);

	return a;
}

// Create a New Attribute
SSTP_ATTRIBUTE *SstpNewAttribute(UCHAR attribute_id, UCHAR *data, UINT data_size)
{
	SSTP_ATTRIBUTE *a = ZeroMalloc(sizeof(SSTP_ATTRIBUTE));

	a->AttributeId = attribute_id;
	a->Data = Clone(data, data_size);
	a->DataSize = data_size;

	return a;
}

// Build the Attribute
BUF *SstpBuildAttribute(SSTP_ATTRIBUTE *a)
{
	UCHAR uc;
	USHORT us;
	BUF *b;
	// Validate arguments
	if (a == NULL)
	{
		return NULL;
	}

	b = NewBuf();

	// Reserved
	uc = 0;
	WriteBuf(b, &uc, sizeof(UCHAR));

	// Attribute ID
	uc = a->AttributeId;
	WriteBuf(b, &uc, sizeof(UCHAR));

	// LengthPacket
	a->TotalLength = a->DataSize + 4;
	us = (USHORT)a->TotalLength;
	us = Endian16(us);
	WriteBuf(b, &us, sizeof(USHORT));

	// Data
	WriteBuf(b, a->Data, a->DataSize);

	return b;
}

// Build the Attribute list
BUF *SstpBuildAttributeList(LIST *o, USHORT message_type)
{
	UINT i;
	BUF *b;
	USHORT us;
	// Validate arguments
	if (o == NULL)
	{
		return NULL;
	}

	b = NewBuf();

	us = Endian16(message_type);
	WriteBuf(b, &us, sizeof(USHORT));

	us = Endian16((USHORT)LIST_NUM(o));
	WriteBuf(b, &us, sizeof(USHORT));

	for (i = 0;i < LIST_NUM(o);i++)
	{
		SSTP_ATTRIBUTE *a = LIST_DATA(o, i);
		BUF *ab = SstpBuildAttribute(a);

		if (ab != NULL)
		{
			WriteBufBuf(b, ab);

			FreeBuf(ab);
		}
	}

	return b;
}

// Building the SSTP packet
BUF *SstpBuildPacket(SSTP_PACKET *p)
{
	BUF *b;
	UCHAR uc;
	USHORT us;
	// Validate arguments
	if (p == NULL)
	{
		return NULL;
	}

	b = NewBuf();

	if (p->IsControl)
	{
		BUF *ab;

		if (p->Data != NULL)
		{
			Free(p->Data);
		}

		ab = SstpBuildAttributeList(p->AttributeList, p->MessageType);
		p->Data = ab->Buf;
		p->DataSize = ab->Size;
		Free(ab);
	}

	// Version
	uc = SSTP_VERSION_1;
	WriteBuf(b, &uc, sizeof(UCHAR));

	// Flag
	uc = p->IsControl ? 1 : 0;
	WriteBuf(b, &uc, sizeof(UCHAR));

	// Length Packet
	us = Endian16(p->DataSize + 4);
	WriteBuf(b, &us, sizeof(USHORT));

	// Data
	WriteBuf(b, p->Data, p->DataSize);

	return b;
}

// Parse the SSTP packet
SSTP_PACKET *SstpParsePacket(UCHAR *data, UINT size)
{
	SSTP_PACKET *p;
	USHORT len;
	// Validate arguments
	if (data == NULL || size == 0)
	{
		return NULL;
	}

	if (size < 4)
	{
		return NULL;
	}

	p = ZeroMalloc(sizeof(SSTP_PACKET));

	// Version
	p->Version = *((UCHAR *)data);
	data++;
	size--;

	if (p->Version != SSTP_VERSION_1)
	{
		// Invalid version
		SstpFreePacket(p);
		return NULL;
	}

	// Flag
	if ((*((UCHAR *)data)) & 0x01)
	{
		p->IsControl = true;
	}
	data++;
	size--;

	// Length
	len = READ_USHORT(data) & 0xFFF;
	data += sizeof(USHORT);
	size -= sizeof(USHORT);

	if (len < 4)
	{
		// Invalid size
		SstpFreePacket(p);
		return NULL;
	}

	if (((UINT)(len - 4)) > size)
	{
		// Oversized
		SstpFreePacket(p);
		return NULL;
	}

	// Data
	p->DataSize = len - 4;
	p->Data = Clone(data, p->DataSize);

	if (p->IsControl)
	{
		// Parse the Attribute list
		p->AttributeList = SstpParseAttributeList(p->Data, p->DataSize, p);

		if (p->AttributeList == NULL)
		{
			// Failure of parsing list
			SstpFreePacket(p);
			return NULL;
		}
	}

	return p;
}

// Parse the Attribute list
LIST *SstpParseAttributeList(UCHAR *data, UINT size, SSTP_PACKET *p)
{
	LIST *o;
	USHORT us;
	UINT num;
	// Validate arguments
	if (size == 0 || data == NULL || p == NULL)
	{
		return NULL;
	}

	if (size < 4)
	{
		return NULL;
	}

	// Message Type
	us = READ_USHORT(data);
	p->MessageType = us;
	data += sizeof(USHORT);
	size -= sizeof(USHORT);

	// Num Attributes
	num = READ_USHORT(data);
	data += sizeof(USHORT);
	size -= sizeof(USHORT);

	// Attributes List
	o = NewListFast(NULL);

	while (LIST_NUM(o) < num)
	{
		SSTP_ATTRIBUTE *a = SstpParseAttribute(data, size);

		if (a == NULL)
		{
			SstpFreeAttributeList(o);
			return NULL;
		}

		if (a->TotalLength > size)
		{
			SstpFreeAttribute(a);
			SstpFreeAttributeList(o);
			return NULL;
		}

		Add(o, a);

		data += a->TotalLength;
		size -= a->TotalLength;
	}

	return o;
}

// Parse the Attribute
SSTP_ATTRIBUTE *SstpParseAttribute(UCHAR *data, UINT size)
{
	SSTP_ATTRIBUTE *a;
	// Validate arguments
	if (data == NULL || size == 0)
	{
		return NULL;
	}

	a = ZeroMalloc(sizeof(SSTP_ATTRIBUTE));

	if (size < 4)
	{
		SstpFreeAttribute(a);
		return NULL;
	}

	data++;
	size--;

	// Attribute ID
	a->AttributeId = *((UCHAR *)data);
	data++;
	size--;

	// Length
	a->TotalLength = READ_USHORT(data) & 0xFFF;
	data += sizeof(USHORT);
	size -= sizeof(USHORT);

	if (a->TotalLength < 4)
	{
		// Length fraud
		SstpFreeAttribute(a);
		return NULL;
	}

	a->DataSize = a->TotalLength - 4;
	if (a->DataSize > size)
	{
		// Length excess
		SstpFreeAttribute(a);
		return NULL;
	}

	a->Data = Clone(data, a->DataSize);

	return a;
}

// Release the Attribute
void SstpFreeAttribute(SSTP_ATTRIBUTE *a)
{
	// Validate arguments
	if (a == NULL)
	{
		return;
	}

	Free(a->Data);

	Free(a);
}

// Release the Attribute list
void SstpFreeAttributeList(LIST *o)
{
	UINT i;
	// Validate arguments
	if (o == NULL)
	{
		return;
	}

	for (i = 0;i < LIST_NUM(o);i++)
	{
		SSTP_ATTRIBUTE *a = LIST_DATA(o, i);

		SstpFreeAttribute(a);
	}

	ReleaseList(o);
}

// Release the SSTP packet
void SstpFreePacket(SSTP_PACKET *p)
{
	// Validate arguments
	if (p == NULL)
	{
		return;
	}

	if (p->AttributeList != NULL)
	{
		SstpFreeAttributeList(p->AttributeList);
	}

	if (p->Data != NULL)
	{
		Free(p->Data);
	}

	Free(p);
}

// Create a SSTP server
SSTP_SERVER *NewSstpServer(CEDAR *cedar, INTERRUPT_MANAGER *im, SOCK_EVENT *se, const char *cipher, const char *hostname)
{
	SSTP_SERVER *s = ZeroMalloc(sizeof(SSTP_SERVER));

	s->Status = SSTP_SERVER_STATUS_NOT_INITIALIZED;

	s->Now = Tick64();
	s->LastRecvTick = s->Now;

	s->Cedar = cedar;
	s->Interrupt = im;
	s->SockEvent = se;

	StrCpy(s->ClientHostName, sizeof(s->ClientHostName), hostname);
	StrCpy(s->ClientCipherName, sizeof(s->ClientCipherName), cipher);

	NewTubePair(&s->TubeSend, &s->TubeRecv, 0);
	SetTubeSockEvent(s->TubeSend, se);

	s->RecvQueue = NewQueueFast();
	s->SendQueue = NewQueueFast();

	return s;
}

// Release the SSTP server
void FreeSstpServer(SSTP_SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	TubeDisconnect(s->TubeRecv);
	TubeDisconnect(s->TubeSend);

	WaitThread(s->PPPThread, INFINITE);
	ReleaseThread(s->PPPThread);

	while (true)
	{
		BLOCK *b = GetNext(s->RecvQueue);

		if (b == NULL)
		{
			break;
		}

		FreeBlock(b);
	}

	while (true)
	{
		BLOCK *b = GetNext(s->SendQueue);

		if (b == NULL)
		{
			break;
		}

		FreeBlock(b);
	}

	ReleaseQueue(s->RecvQueue);
	ReleaseQueue(s->SendQueue);

	ReleaseTube(s->TubeSend);
	ReleaseTube(s->TubeRecv);

	Free(s);
}
