/*	$CoreSDI: packet.c,v 1.24 2001/10/05 22:04:06 claudio Exp $	*/

/*
 *   Copyright (c) 2000, 2001, Core SDI S.A., Argentina
 *   All rights reserved
 *  
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *  
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *   3. Neither name of the Core SDI S.A. nor the names of its contributors
 *      may be used to endorse or promote products derived from this software
 *      without specific prior written permission.
 *  
 *   THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 *   IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 *   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *   IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef WIN32		/* Unix */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <errno.h>
#ifndef USE_SELECT
#include <poll.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>

#else			/* Win32 */

#include <sys/types.h>
#include <time.h>
#include <io.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "exits.h"
#include "winsyslog.h"

#endif /* WIN32 */

#include <openssl/err.h>

#include "sysdep.h"
#include "log.h"
#include "packet.h"

#define PACKET_PLAIN_BUFSIZE		8192
#define PACKET_COMPR_BUFSIZE		8221	/* see buf_compress() */
#define PACKET_COMPR_LEVEL		6	/* see zlib manual */
#define PACKET_ENCRYPT			BUFFER_ENCRYPT
#define PACKET_DECRYPT			BUFFER_DECRYPT
#define PACKET_ENCRYPTION_ENABLED(p)	(p->pkt_cipher != NULL)
#define PACKET_LFLAGS(p)		(p->pkt_lflags)

typedef enum { PACKET_READ, PACKET_WRITE } PACKET_MODE;

static void _packet_poll (PACKET *, PACKET_MODE);
static void _packet_read (PACKET *);
static void _packet_write (PACKET *);
static void _packet_compress (PACKET *);
static void _packet_uncompress (PACKET *);
static void _packet_cipher (PACKET *, int);


/*
 * _packet_poll():
 *	Do a read or write poll.
 */
static void
_packet_poll(PACKET *p, PACKET_MODE mode)
{
	/* Poll is better than select */
#if defined(USE_SELECT) || defined(WIN32)
	fd_set set;

#ifdef WIN32
	set.fd_count = 0;	/* Windows VC cries if not initialized */
#endif

	FD_SET(p->pkt_infd, &set);
	if (select(p->pkt_infd + 1, mode ? NULL : &set, mode ? &set : NULL,
	    NULL, NULL) <= 0)

#else
	struct pollfd pfd;

	pfd.fd = p->pkt_infd;
	if (mode)
		pfd.events = POLLOUT;
	else
		pfd.events = POLLIN;
	if (poll(&pfd, 1, -1) != 1)
#endif /* USE_SELECT || WIN32 */
#ifndef WIN32
		fatal(EX_OSERR, "_packet_poll(): %s.", strerror(errno));
#else
		fatal(EX_OSERR, "_packet_poll(): %d.", WSAGetLastError());
#endif /* WIN32 */
}


/*
 * _packet_read():
 *	Read data from the peer.
 *	If encryption is not enabled:
 *		1. Read data and put it into the reading buffer.
 *	If encryption is enabled:
 *		1. Read data and put it into the encryption buffer;
 *		2. Decrypt encryption buffer and put data into the
 *		   compression buffer;
 *		3. Uncompress compression buffer and put data it into
 *		   the reading buffer.
 *	NOTE 1: Since the audit connection is half-duplex, we do not read
 *		anything until all outgoing data was sent to the peer
 *		(we use the same encryption and compression buffers for
 *		reading and writing).
 *	NOTE 2: Each transfer between both sides has the following stream
 *		format:
 *		<int32_t len><data 1><data 2>...<data len>
 */
static void
_packet_read(PACKET *p)
{
#ifndef WIN32	/* Unix */
	BUFFER *buffer;
	int32_t to_read, readed;

	/* Do not read until all util data in the buffer was readed */
	if (buf_util_space(p->pkt_rbuf) == 0) {

		/* Flush any writing data before reading */
		_packet_write(p);

		/* Select appropiate buffer */
		buffer = (PACKET_ENCRYPTION_ENABLED(p)) ?
		    p->pkt_cbuf : p->pkt_rbuf;

		/* Read no. of incoming data bytes */
		_packet_poll(p, PACKET_READ);
		readed = read(p->pkt_infd, &to_read, sizeof(int32_t));
		if (readed != sizeof(int32_t)) {
			if (readed < 0)
				fatal(EX_OSERR, "Read failed: %s.",
				    strerror(errno));
			else if (readed != 0)
				fatal(EX_OSERR, "Read failed.");

			fatal(EX_IOERR, "Connection closed by peer.");
		}

		to_read = ntohl(to_read);
		if (to_read > buf_size(buffer))
			fatal(EX_PROTOCOL, "Protocol violation.");

		/* Read data */
		buf_reset(buffer);
		readed = buf_read(buffer, p->pkt_infd, to_read);
		if (readed < 0)
			fatal(EX_OSERR, "Read failed: %s.", strerror(errno));
		if (readed == 0)
			fatal(EX_IOERR, "Connection closed by peer.");
		if (readed != to_read)
			fatal(EX_PROTOCOL, "Protocol violation.");

		if (PACKET_ENCRYPTION_ENABLED(p)) {
			_packet_cipher(p, PACKET_DECRYPT);
			_packet_uncompress(p);
		}
	}

#else		/* Win32 */
	BUFFER *buffer;
	int32_t to_read, readed;

	/* Do not read until all util data in the buffer was readed */
	if (buf_util_space(p->pkt_rbuf) == 0) {

		/* Flush any writing data before reading */
		_packet_write(p);

		/* Select appropiate buffer */
		buffer = (PACKET_ENCRYPTION_ENABLED(p)) ?
		    p->pkt_cbuf : p->pkt_rbuf;

		/* Read no. of incoming data bytes */
		_packet_poll(p, PACKET_READ);
		readed = recv(p->pkt_infd, (char *) &to_read, sizeof(int32_t),
		    0);
		if (readed != sizeof(int32_t)) {
			if (readed < SOCKET_ERROR)
				fatal(EX_OSERR, "Read failed: %d.",
				    WSAGetLastError());
			else if (readed != 0)
				fatal(EX_OSERR, "Read failed.");

			fatal(EX_IOERR, "Connection closed by peer.");
		}

		to_read = ntohl(to_read);
		if (to_read > buf_size(buffer))
			fatal(EX_PROTOCOL, "Protocol violation.");

		/* Read data */
		buf_reset(buffer);
		readed = buf_read(buffer, p->pkt_infd, to_read);
		if (readed == SOCKET_ERROR )
			fatal(EX_OSERR, "Read failed: %d.", WSAGetLastError());
		if (readed == 0)
			fatal(EX_IOERR, "Connection closed by peer.");
		if (readed != to_read)
			fatal(EX_PROTOCOL, "Protocol violation.");

		if (PACKET_ENCRYPTION_ENABLED(p)) {
			_packet_cipher(p, PACKET_DECRYPT);
			_packet_uncompress(p);
		}
	}


#endif /* WIN32 */
}


/*
 * _packet_write():
 *	Flush writing buffer (send it to the peer).
 *	If encryption is not enabled:
 *		1. Send writing buffer directly to the peer.
 *	If encryption is enabled:
 *		1. Compress writing buffer;
 *		2. Encrypt compression buffer;
 *		3. Send encryption buffer to the peer.
 *	NOTE 1: This function acts like a flush.
 *	NOTE 2: Written stream:
 *		<int32_t len><byte 1><byte 2>...<byte len>
 */
static void
_packet_write(PACKET *p)
{
#ifndef WIN32	/* Unix */
	BUFFER *buffer;
	int32_t to_write, to_write_net, written;

	/* Do nothing if there are not data to write */
	if (buf_util_space(p->pkt_wbuf) > 0) {
		if (PACKET_ENCRYPTION_ENABLED(p)) {
			_packet_compress(p);
			_packet_cipher(p, PACKET_ENCRYPT);
			buffer = p->pkt_cbuf;
		} else
			buffer = p->pkt_wbuf;

		/* Write no. of sending data bytes (in network order) */
		to_write = buf_util_space(buffer);
		to_write_net = htonl(to_write);
		_packet_poll(p, PACKET_WRITE);
		written = write(p->pkt_outfd, &to_write_net, sizeof(int32_t));
		if (written != sizeof(int32_t))
			fatal(EX_OSERR, "Write failed: %s.", strerror(errno));

		/* Write data */
		while (to_write) {
			_packet_poll(p, PACKET_WRITE);
			written = buf_write(buffer, p->pkt_outfd);
			if (written <= 0)
				fatal(EX_OSERR, "Write failed: %s.",
				    strerror(errno));
			to_write -= written;
		}

		/* All data was sent, reset writing buffer */
		buf_reset(p->pkt_wbuf);
	}

#else 		/* Win32 */
	BUFFER *buffer;
	int32_t to_write, to_write_net, written;

	/* Do nothing if there are not data to write */
	if (buf_util_space(p->pkt_wbuf) > 0) {
		if (PACKET_ENCRYPTION_ENABLED(p)) {
			_packet_compress(p);
			_packet_cipher(p, PACKET_ENCRYPT);
			buffer = p->pkt_cbuf;
		} else
			buffer = p->pkt_wbuf;

		/* Write no. of sending data bytes (in network order) */
		to_write = buf_util_space(buffer);
		to_write_net = htonl(to_write);
		_packet_poll(p, PACKET_WRITE);
		written = send(p->pkt_outfd, (char *) &to_write_net,
		    sizeof(int32_t), 0);
		if (written != sizeof(int32_t))
			fatal(EX_OSERR, "Write failed: %d.", WSAGetLastError());

		/* Write data */
		while (to_write) {
			_packet_poll(p, PACKET_WRITE);
			written = buf_write(buffer, p->pkt_outfd);
			if (written == SOCKET_ERROR || written == 0)
				fatal(EX_OSERR, "Write failed: %d.",
				    WSAGetLastError());

			to_write -= written;
		}

		/* All data was sent, reset writing buffer */
		buf_reset(p->pkt_wbuf);
	}
#endif /* WIN32 */
}


/*
 * _packet_compress():
 *	Compress writing buffer and put data into the compression buffer.
 */
static void
_packet_compress(PACKET *p)
{
	int status;

	if (buf_compress(p->pkt_zbuf,
	    p->pkt_wbuf, PACKET_COMPR_LEVEL, &status) < 0)
		fatal(EX_OSERR, "Compression error: [ %i ]: %s.", status,
		    strerror(errno));
}


/*
 * _packet_uncompress():
 *	Uncompress compression buffer and put data into the reading buffer.
 */
static void
_packet_uncompress(PACKET *p)
{
	int status;

	if (buf_uncompress(p->pkt_rbuf, p->pkt_zbuf, &status) < 0)
		fatal(EX_OSERR, "Uncompression error: [ %i ]: %s.",
		    status, strerror(errno));
}


/*
 * _packet_cipher():
 *	Encrypt/decrypt compression/encryption buffer and put data into
 *	the encryption/compression buffer.
 */
static void
_packet_cipher(PACKET *p, int enc)
{
	BUFFER *dst, *src;

	switch(enc) {
	case PACKET_ENCRYPT:
		dst = p->pkt_cbuf;
		src = p->pkt_zbuf;
		break;
	case PACKET_DECRYPT:
		dst = p->pkt_zbuf;
		src = p->pkt_cbuf;
		break;
	default:
		return;
	}
	buf_cipher(dst, src, p->pkt_cipher, p->pkt_key, enc);
}


/*
 * init_cryptostrings():
 *	Load libcrypto lookup table and error strings.
 */
void
init_cryptostrings()
{
	OpenSSL_add_all_algorithms();
	ERR_load_crypto_strings();
}


/*
 * release_cryptostrings()
 *	Release libcrypto lookup table and error strings.
 */
void
release_cryptostrings()
{
	ERR_free_strings();
	EVP_cleanup();
}


/*
 * packet_init():
 *	Create PACKET structure and return a pointer to it,
 *	on error NULL pointer is returned.
 *	Encryption is not enabled by default, to use it you should
 *	call packet_init_encrypt() after this function.
 */
PACKET *
packet_init(int infd, int outfd)
{
	PACKET *p;

	if (infd < 0 || outfd < 0) {
		errno = EINVAL;
		return (NULL);
	}

	/* Create PACKET structure */
	p = (PACKET *) calloc(1, sizeof(PACKET));
	if (p == NULL)
		return (NULL);

	/* Create reading and writing buffers */
	p->pkt_rbuf = buf_create(PACKET_PLAIN_BUFSIZE);
	p->pkt_wbuf = buf_create(PACKET_PLAIN_BUFSIZE);
	if (p->pkt_rbuf == NULL || p->pkt_wbuf == NULL) {
		packet_release(p);
		return (NULL);
	}

	p->pkt_outfd = outfd;
	p->pkt_infd = infd;

	return (p);
}


/*
 * packet_init_encrypt():
 *	Enable encryption/decryption; return 0 on success and -1
 *	on error.
 *	NOTE 1: Any contents in the reading and writing buffers are
 *		destroyed; sync with other side before calling this
 *		funcion (see packet_flush(3)).
 *	NOTE 2: Since 'key' data is copied into the PACKET struct
 *		you should destroy it after calling this function.
 */
int
packet_init_encrypt(PACKET *p, const EVP_CIPHER *type,
		    unsigned char *key, ssize_t keylen)
{
	int32_t buflen;

	if (p == NULL || type == NULL || keylen <= 0) {
		errno = EINVAL;
		return (-1);
	}

	/* Setup compression and encryption buffers */
	buflen = PACKET_COMPR_BUFSIZE / EVP_CIPHER_block_size(type);
	if (buflen * EVP_CIPHER_block_size(type) < PACKET_COMPR_BUFSIZE)
		buflen++;
	buflen *= EVP_CIPHER_block_size(type);
	p->pkt_cbuf = buf_create(buflen);
	p->pkt_zbuf = buf_create(PACKET_COMPR_BUFSIZE);
	if (p->pkt_cbuf == NULL || p->pkt_zbuf == NULL)
		return (-1);

	/* Setup session key and cipher type */
	memcpy(p->pkt_key, key,
	    (keylen > sizeof(p->pkt_key)) ? sizeof(p->pkt_key) : keylen);
	p->pkt_cipher = type;

	buf_reset(p->pkt_rbuf);
	buf_reset(p->pkt_wbuf);

	return (0);
}


/*
 * packet_release():
 *	Release packet structure, flush any pending data.
 *	NOTE: before release, it cleans reading, writing, and
 *	      compression buffers, and session key.
 */
void
packet_release(PACKET *p)
{
	if (p != NULL) {
		_packet_write(p);
		buf_release(p->pkt_rbuf, BUFFER_CLEAN);
		buf_release(p->pkt_wbuf, BUFFER_CLEAN);
		buf_release(p->pkt_zbuf, BUFFER_CLEAN);
		buf_release(p->pkt_cbuf, BUFFER_NOCLEAN);
		bzero(p->pkt_key, sizeof(p->pkt_key));
		free(p);
	}
}


/*
 * packet_flush():
 *	Flush any pending data on the writing buffer.
 */
void
packet_flush(PACKET *p)
{
	if (p != NULL)
		_packet_write(p);
}


/*
 * packet_put_raw():
 *	Put raw data into the writing buffer; if the buffer gets
 *	full before all data was stored, its data is sent to the peer.
 *	NOTE: Data on the buffer is flushed only when it is full,
 *	      and this don't means that _all_ data passed to this
 *	      function was sent (notice that _packet_read() flushes
 *	      all outgoing data before read).
 */
void
packet_put_raw(PACKET *p, const void *_data, ssize_t size)
{
	ssize_t	 written;
	void	*data;

	if (p == NULL || _data == NULL || size <= 0)
		return;

	data = (void *) _data;
	while (size) {
		written = buf_put_raw(p->pkt_wbuf, data, size);
		if (written < 0)
			fatal(EX_SOFTWARE, "Invalid internal packet structure. "
			    "Connection aborted.");
		size -= written;
		(char *) data += written;
		if (size)
			_packet_write(p);
	}

}


/*
 * packet_put_int32():
 *	Put a 32 bit integer value into the writing buffer (network order).
 */
void
packet_put_int32(PACKET *p, int32_t value)
{
	value = htonl(value);
	packet_put_raw(p, &value, sizeof(int32_t));
}


/*
 * packet_put_string():
 *	Put a string into the writing buffer.
 *	NOTE: A string is coded as follows:
 *		<int32_t len><char d1><char d2>...<char dlen>
 *	      The final \0 is not included.
 */
void
packet_put_string(PACKET *p, const char *string)
{
	int32_t len;

	len = strlen(string);
	packet_put_int32(p, len);
	if (len)
		packet_put_raw(p, string, len);
}


/*
 * packet_get_raw():
 *	Retrieve data from the reading buffer (if this buffer is
 *	empty read from the peer), blocks until all wanted data
 *	was readed.
 */
void
packet_get_raw(PACKET *p, void *data, ssize_t size)
{
	ssize_t readed;

	if (p == NULL || data == NULL || size <= 0)
		return;

	while (size) {
		readed = buf_get_raw(p->pkt_rbuf, data, size);
		size -= readed;
		(char *) data += readed;
		if (size)
			_packet_read(p);
	}

}


/*
 * packet_get_int32():
 *	Retrieve a 32 bit integer value from the reading buffer.
 *	NOTE: See packet_get_raw().
 */
void
packet_get_int32(PACKET *p, int32_t *value)
{
	packet_get_raw(p, value, sizeof(int32_t));
	*value = ntohl(*value);
}


/*
 * packet_get_string():
 *	Retrieve a string from the reading buffer.
 *	NOTE: See packet_get_raw().
 */
void
packet_get_string(PACKET *p, char *string, ssize_t size)
{
	int32_t	string_len;
	char	lost;

	packet_get_int32(p, &string_len);

	if (string_len) {
		if (string_len < size)
			size = string_len;
		
		packet_get_raw(p, string, size);
		string_len -= size;
		string += size;

		/* Retrieve remaining lost string data */
		while(string_len--)
			packet_get_raw(p, &lost, 1);
	}

	*string = '\0';
}


/*
 * packet_write()
 *	Read data from a file descriptor at specified offset and
 *	send it to a packet connection; return last offset written.
 *	If specified offset is less than last file offset the operation
 *	is not done and -1 is returned.
 *	NOTE: The receiving PACKET should use packet_read().
 */
off_t
packet_write(PACKET *p, int fd, off_t offset)
{
	char	buf[PACKET_PLAIN_BUFSIZE];
	int32_t	readed, total, to_read;
	off_t	last_off;

	last_off = lseek(fd, 0, SEEK_END);
	if (last_off < 0 || lseek(fd, offset, SEEK_SET) < 0)
		fatal(EX_OSERR, "packet_write(): %s.", strerror(errno));
	total = (int32_t) (last_off - offset);
	if (total < 0)
		return (-1);
	packet_put_int32(p, total);
	while (total > 0) {
		to_read = (total > sizeof(buf)) ? sizeof(buf) : total;
		readed = read(fd, buf, to_read);
		if (readed < 0)
			fatal(EX_OSERR, "packet_write(): %s.", strerror(errno));
		packet_put_raw(p, buf, readed);
		total -= readed;
	}
	return (last_off);
}


/*
 * packet_read()
 *	Read data from packet and write it into a file descriptor.
 *	NOTE: On windows, the file descriptor should point to a file.
 */
void
packet_read(PACKET *p, int fd)
{
	char	buf[PACKET_PLAIN_BUFSIZE];
	int32_t	total, to_read, written;
		
	packet_get_int32(p, &total);
	while (total) {
		to_read = (total > sizeof(buf)) ? sizeof(buf) : total;
		packet_get_raw(p, buf, to_read);
		written = write(fd, buf, to_read);
		if (written < 0)
			fatal(EX_OSERR, "packet_read(): %s.", strerror(errno));
		total -= to_read;
	}
}


/*
 * resolve():
 * XXX: must change: use getaddrinfo(3).
 */
in_addr_t
resolve(char *host)   
{
        struct hostent *he;
        in_addr_t ip;
                                
        if ((ip = inet_addr(host)) == INADDR_NONE) { 
                if ((he = gethostbyname(host)) == NULL)
                        return (0);
                memcpy(&ip, he->h_addr, sizeof(in_addr_t));
        }
        return (ip);
}


