/*
 *	httpReflector.c
 *	Release $Name: MATRIXSSL_1_0_BETA $
 *
 *	Simple example program for MatrixSSL
 *	Accepts a HTTPS request and echos the response back to the sender.
 *
 *	Copyright (c) PeerSec Networks, 2002-2004. All Rights Reserved.
 *	The latest version of this code is available at http://www.matrixssl.org
 *
 *	This software is open source; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This General Public License does NOT permit incorporating this software 
 *	into proprietary programs.  If you are unable to comply with the GPL, a 
 *	commercial license for this software may be purchased from PeerSec Networks
 *	at http://www.peersec.com
 *	
 *	This program is distributed in WITHOUT ANY WARRANTY; without even the 
 *	implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 *	See the GNU General Public License for more details.
 *	
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *	http://www.gnu.org/copyleft/gpl.html
 */
/******************************************************************************/

#include <stdlib.h>
#include <stdio.h>

/******************************************************************************/
/*
	OS specific macros
*/
#if WIN32
	#include			<windows.h>
	#define				fcntl(A, B, C)
	#define				MSG_NOSIGNAL	0
#elif LINUX
	#include			<netinet/in.h>
	#include			<fcntl.h>
	typedef int			WSADATA;
	typedef int			SOCKET;
	#define closesocket close
	#define MAKEWORD(A, B)
	#define WSAStartup(A, B)
	#define WSACleanup()
	#define INVALID_SOCKET -1
#endif /* OS macros */

#ifndef min
#define min(a,b)	(((a) < (b)) ? (a) : (b))
#endif /* min */

/******************************************************************************/

#include "../matrixSsl.h"

#define HTTPS_PORT	4433
static char keyfile[] = "privkey.pem";
static char certfile[] = "cert.pem";

static const char response[] = "HTTP/1.1 200 OK\r\n"
		"Server: PeerSec Networks MatrixSSL\r\n"
		"Pragma: no-cache\r\n"
		"Cache-Control: no-cache\r\n"
		"Content-type: text/plain\r\n"
		"\r\n"
		"PeerSec Networks\n"
		"Successful MatrixSSL request:\n";

static const char quitString[] = "GET /quit";

#define myAssert(C)		if (C) ; else {printf("%s:%d myAssert(%s)\n",\
						__FILE__, __LINE__, #C); breakpoint(); }

/******************************************************************************/

static void setSocketBlock(SOCKET fd);
static void setSocketNonblock(SOCKET fd);
static void shutdownSocket(SOCKET fd);
static void breakpoint();

/******************************************************************************/

int main(int argc, char **argv)
{
	ssl_t				*ssl;
	sslKeys_t			*keys;
	sslBuf_t			in, out;
	WSADATA				wsaData;
	SOCKET				fd, nfd;
	struct sockaddr_in	addr;
	int					len, bytes, rc, quit;
	unsigned char		error, alertLevel, alertDescription;
/*
	Initialize Windows sockets (no-op on other platforms)
*/
	WSAStartup(MAKEWORD(1,1), &wsaData);
	fd = INVALID_SOCKET;
	memset(&in, 0x0, sizeof(sslBuf_t));
	memset(&out, 0x0, sizeof(sslBuf_t));
/*
	Initialize the MatrixSSL Library, and read in the public key (certificate)
	and private key.
*/
	matrixSslOpen();
	keys = matrixSslReadKeys(certfile, keyfile, NULL);
	if (keys == NULL) {
		fprintf(stderr, "Error reading or parsing %s or %s.\n", 
			certfile, keyfile);
		goto exit;
	}
	quit = 0;
/*
	Create incoming and outgoing buffers.  MatrixSSL doesn't use internal
	buffers, to allow more optimized usage of memory in the calling app.
	We make these buffers 16K + 5, the maximum size of an SSL record with
	header.  Could optimize by starting small and growing when necessary.
	Normally, we would associate buffers with each connection, but since
	we only support one at a time in this example, we do it once here.
*/
	in.size = SSL_MAX_RECORD_SIZE;
	in.buf = in.start = in.end = malloc(in.size);
	out.size = SSL_MAX_RECORD_SIZE;
	out.buf = out.start = out.end = malloc(out.size);
/*
	Create a listening socket on port HTTPS_PORT
*/
	memset((char *) &addr, 0x0, sizeof(struct sockaddr_in));
	addr.sin_family = AF_INET;
	addr.sin_port = htons((short)HTTPS_PORT);
	addr.sin_addr.s_addr = INADDR_ANY;
	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		fprintf(stderr, "Error creating listen socket\n");
		goto exit;
	}
/*
	Make sure the socket is not inherited by exec'd processes
	Set the REUSE flag to minimize the number of sockets in TIME_WAIT
*/
	fcntl(fd, F_SETFD, FD_CLOEXEC);
	rc = 1;
	setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&rc, sizeof(rc));

	if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
		fprintf(stderr, 
			"Can't bind to port %d, port in use or insufficient privilege\n", 
			HTTPS_PORT);
		goto exit;
	}
	if (listen(fd, SOMAXCONN) < 0) {
		fprintf(stderr, "Error listening on socket\n");
		goto exit;
	}
/*
	Turn on blocking mode for the listening socket
*/
	setSocketBlock(fd);
/*
	Wait for an incoming connection
*/
acceptMore:
	len = sizeof(struct sockaddr_in);
	if ((nfd = accept(fd, (struct sockaddr *) &addr, &len)) < 0) {
		fprintf(stderr, "Error accepting new socket\n");
		goto exit;
	}
/*
	nfd is the newly accepted socket.  Set the close on exec flag
	and the blocking flag.
*/
	setSocketBlock(nfd);
/*
	Create a new SSL session for the new socket
*/	
	ssl = matrixSslNewSession(keys, SSL_FLAGS_SERVER);
	myAssert(ssl);
/*
	Reset the buffers for this connection
*/
	in.start = in.end = in.buf;
	out.start = out.end = out.buf;
/*
	Read in as many bytes as we can hold
*/
readMore:
	bytes = recv(nfd, in.end, (int)((in.buf + in.size) - in.end), MSG_NOSIGNAL);
	if (bytes <= 0) {
		goto closeSocket;
	}
	in.end += bytes;
/*
	We've read in at least one byte, see if we can decode an entire record
*/
decodeMore:
	rc = matrixSslDecode(ssl, &in, &out, &error, &alertLevel, &alertDescription);
	switch (rc) {
/*
	Successfully decoded an application data record, and placed in out buf
*/
	case SSL_PROCESS_DATA:
		break;
/*
	We've decoded a record that requires a response
	The out buffer contains the encoded response that we must send back
	before decoding any more data
*/
	case SSL_SEND_RESPONSE:
		while (out.start < out.end) {
			bytes = send(nfd, out.start, (int)(out.end - out.start), MSG_NOSIGNAL);
			if (bytes <= 0) {
				myAssert(bytes > 0);
				goto closeSocket;
			}
			out.start += bytes;
		}
		goto decodeMore;
/*
	There was an error decoding the data, or encoding the out buffer.
	There may be a response data in the out buffer, so try to send.
	We try a single hail-mary send of the data, and then close the socket.
	Since we're closing on error, we don't worry too much about a clean flush.
*/
	case SSL_ERROR:
		if (out.start < out.end) {
			setSocketNonblock(nfd);
			send(nfd, out.start, (int)(out.end - out.start), MSG_NOSIGNAL);
		}
		goto closeSocket;
/*
	We've decoded an alert.  The level and description passed into
	matrixSslDecode are filled in with the specifics.
*/
	case SSL_ALERT:
		if (alertDescription != SSL_ALERT_CLOSE_NOTIFY) {
			fprintf(stderr, 
				"Closing connection on alert level %d, description %d.\n",
				alertLevel, alertDescription);
		}
		goto closeSocket;
/*
	We have a partial record, need to read more data from socket and
	try decoding again.
*/
	case SSL_PARTIAL:
		goto readMore;
/*
	The out buffer is too small to fit the decoded or response
	data.  Increase the size of the buffer and call decode again
*/
	case SSL_FULL:
		//Buffer is already at max length, so just exit
		matrixSslEncodeClosureAlert(ssl, &out);
		setSocketNonblock(nfd);
		send(nfd, out.start, (int)(out.end - out.start), MSG_NOSIGNAL);
		goto closeSocket;
	default:
		myAssert(0);
		goto closeSocket;
	}
/*
	We assert that all the data has been decoded from the socket buffer.
	This is specific to this simple example, where we don't expect 
	pipelined or chunked HTTP request data to follow.
*/
	myAssert(in.start == in.end);
	in.start = in.end = in.buf;
/*
	If the incoming data starts with the quitString, quit the application
	after this request
*/
	if (memcmp(out.start, quitString, min((int)(out.end - out.start), 
			(int)strlen(quitString))) == 0) {
		quit++;
	}
/*
	Copy the canned response header and decoded data from socket into
	the in buffer, and reset the out buffer for the encoded response
*/
	bytes = (int)strlen(response);
	memcpy(in.end, response, bytes);
	in.end += bytes;
	bytes = (int)(out.end - out.start);
	memcpy(in.end, out.start, bytes);
	in.end += bytes;
	out.start = out.end = out.buf;
/*
	Encode the response
*/
	bytes = matrixSslEncode(ssl, in.start, (int)(in.end - in.start), &out);
	if (bytes < 0) {
		myAssert(bytes < 0);
		goto closeSocket;
	}
/*
	Send all data in the response
*/
sendMoreResponse:
	bytes = send(nfd, out.start, (int)(out.end - out.start), MSG_NOSIGNAL);
	if (bytes <= 0) {
		myAssert(bytes > 0);
		goto closeSocket;
	}
	out.start += bytes;
	if (out.start != out.end) {
		goto sendMoreResponse;
	}
/*
	Send a closure alert for clean shutdown of remote SSL connection
	This is for good form, some implementations just close the socket
*/
	out.start = out.end = out.buf;
	matrixSslEncodeClosureAlert(ssl, &out);
	setSocketNonblock(nfd);
	send(nfd, out.start, (int)(out.end - out.start), MSG_NOSIGNAL);
/*
	Close the socket
*/
closeSocket:
	matrixSslDeleteSession(ssl);
	shutdownSocket(nfd);
/*
	If we haven't seen the quit message, accept another connection
*/
	if (!quit) {
		goto acceptMore;
	}
/*
	Close listening socket, free remaining items
*/
exit:
	fprintf(stdout, "Press any key to exit...\n");
	getchar();
	shutdownSocket(fd);
	if (in.buf) {
		free(in.buf);
	}
	if (out.buf) {
		free(out.buf);
	}
	matrixSslFreeKeys(keys);
	matrixSslClose();
	WSACleanup();

	return 0;
}

/******************************************************************************/
/*
	Turn on socket blocking mode (and set CLOEXEC on LINUX for kicks).
*/
static void setSocketBlock(SOCKET fd)
{
#if _WIN32
	int		block = 0;
	ioctlsocket(fd, FIONBIO, &block);
#elif LINUX
	fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK);
	fcntl(fd, F_SETFD, FD_CLOEXEC);
#endif
}

/******************************************************************************/
/*
	Turn off socket blocking mode.
*/
static void setSocketNonblock(SOCKET fd)
{
#if _WIN32
	int		block = 1;
	ioctlsocket(fd, FIONBIO, &block);
#elif LINUX
	fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
#endif
}

/******************************************************************************/
/*
	Set the socket to non blocking mode and perform a few extra tricks
	to make sure the socket closes down cross platform
*/
static void shutdownSocket(SOCKET fd)
{
	char	buf[32];

	if (fd != INVALID_SOCKET) {
		setSocketNonblock(fd);
		if (shutdown(fd, 1) >= 0) {
			while (recv(fd, buf, sizeof(buf), 0) > 0);
		}
		closesocket(fd);
	}
}

/******************************************************************************/
/*
	Set a breakpoint in this function to catch asserts.
	This function is called whenever an assert is triggered.  Useful because
	VisualStudio often won't show the right line of code if DebugBreak() is 
	called directly, and abort() may not be desireable on LINUX.
*/
static void breakpoint()
{
	static int preventInline = 0;
#if _WIN32
	DebugBreak();
#elif LINUX
	abort();
#endif
}

/******************************************************************************/
