/*******************************************************************************
 * Copyright (c) 2005, 2009 Intel Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Hoang M Nguyen, Intel - Initial API and Implementation
 *
 * $Id: TransportSupport.c,v 1.25 2009/08/26 14:59:53 jwest Exp $
 *
 *******************************************************************************/ 

#include "tptp/TransportSupport.h"

#include "tptp/TPTPSupportUtils.h"

#include "tptp/NoLog.h"

#ifdef MVS
	#include <sys/socket.h>
#endif

#ifndef _WIN32
	#include <netdb.h>
	#include <strings.h>
#else
	#define strcasecmp(str1, str2) lstrcmpi(str1, str2)
#endif

#define MAX_HOST_NAME_LENGTH 256

typedef struct {
	unsigned short addressLength;
	unsigned char *addresses;	   	
}tptp_IPAddresses_t;


int printTPTPCommand(tptp_basic_msg_header_ptr_t pMsg, int payLoadLength) ;
int printTPTPCommand_CONNECT_DATA(tptp_basic_msg_header_ptr_t pMsg, int payLoadLength) ;



/**
 *********************************************************
 *
 * @brief
 *    Add the message header to the message data/command before sending.
 *
 * Allocates a new buffer large enough for the cmd string plus the message
 * header, returning that new buffer and its length.
 *
 * Caller is responsible for freeing that buffer.
 *
 * @return
 *    0 - success
 *        buffer will point to the built command with the header
 *        bufferLength will contain the length of the new buffer
 *
 *********************************************************/
int addBasicMsgHeader(char *pCmdBlock, int cmdSize, char **pBuffer, int *pBufferLength, unsigned int flags)
{
	unsigned int magicNumber = 0;
	char *pMsg = NULL;
	char *pCurr = NULL;
	int len = 0;

	if ((cmdSize > 0) && (pCmdBlock == NULL))
	{
		/* Input values are inconsistent */
		return -1;
	}

	/* Allocate sufficient space to add the msg header to the command passed it. */
	len = cmdSize + sizeof(tptp_basic_msg_header_t);
	pMsg = (char*)tptp_malloc(len);
	if (!pMsg) return -1;

	/* add the magic number */
	magicNumber = MAGIC_NUMBER ;
	pCurr = writeUINTToBuffer(pMsg, magicNumber);

	/* add the flags */
	pCurr = writeUINTToBuffer(pCurr, flags);

	/* add the payload length */
	pCurr = writeUINTToBuffer(pCurr, cmdSize);

	/* copy data over */
	if ((cmdSize > 0) && (pCmdBlock !=NULL))
	{
		memcpy(pCurr, pCmdBlock, cmdSize);
	}

	/* return the new buffer & its length */
	*pBufferLength = len;
	*pBuffer = pMsg;

	return 0;
}


#ifndef DISPLAY_NOLOG_MSGS
#undef printThisEnvelope
int printThisEnvelope(tptp_basic_msg_header_ptr_t pMsg)
{
	return 0;
}
#else
/**
 *********************************************************
 *
 * @brief
 *    Print out the entire content of the message envelope
 *
 * @return
 *    0 - success
 *********************************************************/
#undef printThisEnvelope
int printThisEnvelope(tptp_basic_msg_header_ptr_t pMsg)
{
	unsigned int magicNumber = 0 ;
	unsigned int flags = 0 ;

	unsigned int payLoadLength = 0 ;

	/* point to the current processing info in the header */
	char *pCurr = NULL ;

	/* read in the magic number */
	pCurr = readUINTFromBuffer((unsigned char *) pMsg, &magicNumber);

	/* read in the flags */
	pCurr = readUINTFromBuffer(pCurr, &flags);

	/* read in the payload length */
	pCurr = readUINTFromBuffer(pCurr, &payLoadLength);

	if ((flags & CONNECT) != 0)
	{
		TPTP_LOG_DEBUG_MSG("CONNECTION REQUESTED COMMAND.") ;
		printTPTPCommand(pMsg, payLoadLength) ;
	}

	else if ((flags & CONNECT_DATA) != 0)
	{
		TPTP_LOG_DEBUG_MSG("DATA CONNECTION REQUESTED COMMAND.") ;
		printTPTPCommand_CONNECT_DATA(pMsg, payLoadLength) ;
	}

	else if ((flags & DISCONNECT) != 0)
	{
		TPTP_LOG_DEBUG_MSG("DISCONNECT REQUESTED COMMAND.") ;
	}

	else if ((flags & CONNECTION_COMPLETE) != 0)
	{
		int  connectionId = 0 ;
		unsigned char *pBuffer = (unsigned char *) pMsg ;
		pBuffer += sizeof(tptp_basic_msg_header_t) ;

		/* read in the connection id */
		readUINTFromBuffer(pBuffer, &connectionId);

		TPTP_LOG_DEBUG_MSG1("CONNECTION COMPLETE RESPONSE. Connection ID (%d).", connectionId) ;
	}

	else if (((flags & DATA_CONNECTION_COMPLETE) != 0) ||
		     ((flags & CONSOLE_CONNECT_COMPLETE) != 0))
	{
		int  connectionId = 0 ;
		int  uuidLength = 0 ;
		unsigned char *pBuffer = (unsigned char *) pMsg ;
		pBuffer += sizeof(tptp_basic_msg_header_t) ;

		/* read in the connection id */
		pBuffer = readUINTFromBuffer(pBuffer, &connectionId);


		if ((flags & DATA_CONNECTION_COMPLETE) != 0)
		{
			/* read in the uuid length */
			pBuffer = readUINTFromBuffer(pBuffer, &uuidLength);
			TPTP_LOG_DEBUG_MSG1("DATA CONNECTION COMPLETE RESPONSE. Connection ID (%d).", connectionId) ;
		}
		else
		{
			TPTP_LOG_DEBUG_MSG1("CONSOLE CONNECTION COMPLETE RESPONSE. Connection ID (%d).", connectionId) ;
		}

	}

	else if ((flags & CONNECTION_REFUSED) != 0)
	{
		TPTP_LOG_DEBUG_MSG("CONNECTION REFUSED RESPONSE.") ;
	}

	else
		printTPTPCommand(pMsg, payLoadLength) ;
	
	return 0 ;
}
#endif

/**
 *********************************************************
 *
 * @brief
 *    print out the content of the command (without the header)
 *
 * @return
 *    0
 *********************************************************/
int printTPTPCommand(tptp_basic_msg_header_ptr_t pMsg, int payLoadLength)
{
	char copyCmd[TPTP_DEFAULT_BUFFER_MAX_LENGTH] ;
	char *pCmd = (char *) pMsg ;

	if (payLoadLength > 0)
	{
		pCmd += sizeof(tptp_basic_msg_header_t) ;
		
		/* end with a NULL byte for using with printf */
		strncpy(copyCmd, pCmd, payLoadLength) ;
		copyCmd[payLoadLength] = '\0' ;
		TPTP_LOG_DEBUG_MSG1("COMMAND: %s.", copyCmd) ;
	}

	return 0 ;
}


/**
 *********************************************************
 *
 * @brief
 *    print out the content of the CONNECT_DATA command (without the header)
 *
 * @return
 *    0
 *********************************************************/
int printTPTPCommand_CONNECT_DATA(tptp_basic_msg_header_ptr_t pMsg, int payLoadLength)
{
	char copyCmd[TPTP_DEFAULT_BUFFER_MAX_LENGTH] ;
	char *pCmd = (char *) pMsg ;
	int dataPathType = 0, uuidLength = 0 ;

	if (payLoadLength > 0)
	{
		pCmd += sizeof(tptp_basic_msg_header_t) ;

		/* read in the data path type */
		pCmd = readUINTFromBuffer(pCmd, &dataPathType);

		if (payLoadLength > 4)
		{
			/* read in the uuid length */
			pCmd = readUINTFromBuffer(pCmd, &uuidLength);
		
			/* end with a NULL byte for using with printf */
			strncpy(copyCmd, pCmd, uuidLength) ;

			copyCmd[uuidLength] = '\0' ;
		}
		else
		{
			uuidLength = 0 ;
			strcpy(copyCmd, "") ;
		}

		TPTP_LOG_DEBUG_MSG2("COMMAND: dataPathType(%d) uid(%s).", 
			dataPathType, copyCmd) ;
	}

	return 0 ;
}



/**
 *********************************************************
 *
 * @brief
 *    extract and return the connection id from the CONNECT_COMPLETE response
 *
 * @return
 *    the connection id
 *********************************************************/

int getConnectionId(tptp_basic_msg_header_t *pMsg)
{
	int  connectionId = 0 ;
	unsigned char *pBuffer = (unsigned char *) pMsg ;
	pBuffer += sizeof(tptp_basic_msg_header_t) ;

	/* read in the connection id */
	readUINTFromBuffer(pBuffer, &connectionId);

	return (connectionId) ;
}


/**
 *********************************************************
 *
 * @brief
 *    check for validity of the given block and the given block id.
 *
 * @return
 *    true (non-zero) if the block is valid (e.g. match the given id)
 *    false (zero) if the block is not valid
 *********************************************************/
int isValidTPTPBlock(tptp_object * pObj, tptp_uint32 tptpBlockId)
{
	int isValid = 1 ;

	if ( (pObj == NULL) || (pObj->data == NULL) )
	{
		isValid = 0 ;
	}
	else if ( pObj->objectID != tptpBlockId )
	{
		isValid = 0 ;
	}

	return (isValid) ;
}

/* IPV4-ONLY */
long tptp_getLocalIPAddress() {
	char localhost[MAX_HOST_NAME_LENGTH];
	struct hostent *hpLocal;
	long result;

	/* On WinDoze we have to initialize the sockets */
	if (initForSocketCalls()) {
		return -1;
	}

	/* Get the local host informantion */
	gethostname(localhost,MAX_HOST_NAME_LENGTH);
	if((hpLocal=gethostbyname((char*)localhost))==NULL) {
		long error;
#ifdef _WIN32
		error=WSAGetLastError();
#else
		error=errno;
#endif
		//ra_setLastError(GETLOCALHOST_FAILED, error);
		return -1;
	}

	memcpy(&result, hpLocal->h_addr_list[0], sizeof(long));

	return result;
}

short tptp_getLocalIPAddressesIPv4M(tptp_IPAddresses_t *addr) {
	char localhost[MAX_HOST_NAME_LENGTH];
	struct hostent *hpLocal;
	int count, i;

	if(initForSocketCalls()) {
		return -1;
	}
	/* Get the local host informantion */
	gethostname(localhost,MAX_HOST_NAME_LENGTH);
	if((hpLocal=gethostbyname((char*)localhost))==NULL) {
		long error;
#ifdef _WIN32
		error=WSAGetLastError();
#else
		error=errno;
#endif
		//ra_setLastError(GETLOCALHOST_FAILED, error);
		return -1;
	}

	/* how big is each address */
	addr->addressLength=hpLocal->h_length;

	/* Count the number of addresses */
	for(count=0; hpLocal->h_addr_list[count]!='\0'; count++);

	/* Allocate and load the "addr" with the addresses */
	addr->addresses=(unsigned char*)tptp_malloc((count+1)*addr->addressLength+1);

	i=0;
	while(i<count) {
		memcpy((&addr->addresses[i*addr->addressLength]), hpLocal->h_addr_list[i], addr->addressLength);
		i++;
	}
	/* Add 127.0.0.1 */
	*((unsigned char*)(&addr->addresses[count*addr->addressLength]))=0x7F;
	*((unsigned char*)(&addr->addresses[count*addr->addressLength]+1))=0x00;
	*((unsigned char*)(&addr->addresses[count*addr->addressLength]+2))=0x00;
	*((unsigned char*)(&addr->addresses[count*addr->addressLength]+3))=0x01;

	/* Null the last address */
	addr->addresses[(count+1)*addr->addressLength]='\0';

	return 0;
}

int isLocalHostIPv4M(char* name) {
	tptp_IPAddresses_t localAddresses;
	struct hostent *he;
	unsigned char *addr, *cur;
	int len, res;
	
	if (name == NULL) return 0;
	
	he = gethostbyname(name);
	if (he == NULL || he->h_addr_list == NULL || he->h_addr_list[0] == '\0') return 0;
	
	addr = he->h_addr_list[0];
	
	if(tptp_getLocalIPAddressesIPv4M(&localAddresses) != 0) return 0;
	
	res = 0;
	len = localAddresses.addressLength;
	cur = localAddresses.addresses;
	
	while(*cur) {
		if(cur[0]==addr[0] && cur[1]==addr[1] && cur[2]==addr[2] && cur[3] == addr[3]) {
			res = 1;
			break;
		}

		cur+=len;
	}

	tptp_free(localAddresses.addresses);
	
	return res;
}

/** IPV4-ONLY */
BOOL tptp_checkHost(network_list_t *list, unsigned char *address) {
	network_list_node_t *current;

	//If the list or host address is not specified then return TRUE.
	//this means configuration is not specified.	
	if (list == NULL) {
		return TRUE;
	}

	if (address == NULL) {
		return TRUE;
	}

	current = list->head;

	/* Look at the host list in order and find a matching rule */
	while(current) {
		tptp_IPAddresses_t localAddresses;
		network_t *entry=current->entry;
		/* Is this an allow rule */

		switch(entry->wildcard) {
		case TPTP_HW_ALL:
			if(entry->allow) {
				return TRUE;
			}
			return FALSE;
			break;
		case TPTP_HW_LOCAL:
			/* We need to continuously recheck the IP address of the local machine as this can change
			   on modern operating systems without a reboot
			*/
			if(tptp_getLocalIPAddressesIPv4M(&localAddresses)==0) {
				int len=localAddresses.addressLength;
				unsigned char *current;
				current=localAddresses.addresses;
				while(*current) {
					if((current[0]==address[0])
						&& (current[1] == address[1])
						&& (current[2] == address[2])
						&& (current[3] == address[3])) {
						tptp_free(localAddresses.addresses);
						if(entry->allow) {
							return TRUE;
						}
						return FALSE;
					}
					current+=len;
				}
				tptp_free(localAddresses.addresses);
			}
			break;
		/* We are currently going to ignore UNKNOWN, and KNOWN posibilities */
		case TPTP_HW_UNKNOWN:
			break;
		case TPTP_HW_KNOWN:
			break;
		case TPTP_HW_NET:
			{
				/* Check to see if the host is OK based upon net and mask.  If the mask and the remote address
				   and-ed together equal the net then it is a match */
				if((entry->mask[0] & address[0]) == entry->net[0]
					&& (entry->mask[1] & address[1]) == entry->net[1]
					&& (entry->mask[2] & address[2]) == entry->net[2]
					&& (entry->mask[3] & address[3]) == entry->net[3]) {
					if(entry->allow) {
						return TRUE;
					}
					return FALSE;
				}
			}
			break;
		case TPTP_HW_NAMED:
			{
				char hostnameEntry[256]; /* max hostname length is 256 */
				char hostnameAllowed[256];
				char **aliases;

				/* 1 hostent structure per thread,  */
				struct hostent *hostentTemp;

				/* Resolve the listed IP address in the config file */
				hostentTemp = gethostbyname(entry->hostname);
				if(hostentTemp != NULL) {
					strcpy(hostnameAllowed, hostentTemp->h_name);
				}

				/* Resolve the remote host IP address */
				hostentTemp = gethostbyaddr((char*)address, 4 * sizeof(char), AF_INET);

				if(hostentTemp != NULL) {
					strcpy(hostnameEntry, hostentTemp->h_name);
					aliases = hostentTemp->h_aliases; /* Check the aliases */
				}
				else {
					hostnameEntry[0] = '\0';
					aliases = NULL;
				}

				/* First, check the hostname, if identical, check if it is set to ALLOW */
				if(strcasecmp(hostnameEntry, hostnameAllowed)==0) {
					return (entry->allow) ? TRUE : FALSE;
				}

				/* Next if aliases are present, check the aliases */
				while(aliases && *aliases) {
					if(strcasecmp(*aliases, hostnameAllowed)==0) {
						return (entry->allow) ? TRUE : FALSE;
					}

					aliases++;
				}

				if (isLocalHostIPv4M(hostnameAllowed) && isLocalHostIPv4M(hostnameEntry)) {
					return entry->allow ? TRUE : FALSE;
				}
			}
			break;
		}
		current=current->next;
	}

	return FALSE;
}

BOOL tptp_checkHostIPv6(network_list_t *list, struct sockaddr_storage * addr) {
	network_list_node_t *current;
	int x = 0;
	unsigned char * ipaddr;
	int family = 0;
	int r = 0;
	int l = 0;

	// Used by net/mask
	char * hostname;
	struct sockaddr_storage ** addrs;
	int size = 0;

	//If the list or host address is not specified then return TRUE.
	//this means configuration is not specified.	
	if (list == NULL) {
		return TRUE;
	}

	if (addr == NULL) {
		return TRUE;
	}

	current = list->head;

	/* Look at the host list in order and find a matching rule */
	while(current) {
		network_t *entry=current->entry;
		/* Is this an allow rule */

		switch(entry->wildcard) {

		case TPTP_HW_ALL:
			if(entry->allow) {
				return TRUE;
			}
			return FALSE;
			break;
		case TPTP_HW_LOCAL:	

			if(isAddrLocal(addr)) {
				if(entry->allow) {
					return TRUE;
				}
				return FALSE;
			} 
			break;
		/* We are currently going to ignore UNKNOWN, and KNOWN posibilities */
		case TPTP_HW_UNKNOWN:
			break;
		case TPTP_HW_KNOWN:
			break;
		case TPTP_HW_MAX_VALUE:
			break;
		case TPTP_HW_NET:
			{
				getByteFormatFromSockAddr(addr, (void **)&ipaddr, &family);
				
				// If rule is IPv4 and addr is IPv4
				if(entry->netAndMaskAreIpv4 && addr->ss_family == PF_INET) {

					/* Check to see if the host is OK based upon net and mask.  If the mask and the remote address
					   and-ed together equal the net then it is a match */
					if((entry->mask[0] & ipaddr[0]) == entry->net[0]
						&& (entry->mask[1] & ipaddr[1]) == entry->net[1]
						&& (entry->mask[2] & ipaddr[2]) == entry->net[2]
						&& (entry->mask[3] & ipaddr[3]) == entry->net[3]) {

							tptp_free(ipaddr);
							if(entry->allow) {
								return TRUE;
							} else {
								return FALSE;
							}
					}
				
				// If rule is IPv6 and IP is IPv6
				} else if(!entry->netAndMaskAreIpv4 && addr->ss_family == PF_INET6){

					r = 1;
					for(x = 0; x < 16; x++) {
						if( (entry->maskipv6[x] & ipaddr[x]) == entry->netipv6[x]) {
							// No mismatch, this is good.
						} else {
							// We've found a mismatch. Set false and break.
							r = 0;
							break;
						}
					}
					
					// If we have a match...
					if(r) {
						if(entry->allow) {
							return TRUE;
						}
						return FALSE;						
					}
				} else if(entry->netAndMaskAreIpv4 && addr->ss_family == PF_INET6) {

					// For IPv4 net masks, we need to resolve the host name, because Linux listens on both IPv4 and IPv6
					// with IPv6 mapped addreses
					hostname = convertAddrToHostname(addr);
					
					
					if(hostname != NULL) {
						convertHostnameToAddrs(hostname, &addrs, &size);
						
						// For each of the addresses we resolved from the hostname....
						r = FALSE;
						for(x = 0; x < size; x++) {
							// If the address is IPv4
							if(addrs[x]->ss_family == PF_INET) {

								getByteFormatFromSockAddr(addrs[x], (void **)&ipaddr, &family);
								
								if((entry->mask[0] & ipaddr[0]) == entry->net[0]
									&& (entry->mask[1] & ipaddr[1]) == entry->net[1]
									&& (entry->mask[2] & ipaddr[2]) == entry->net[2]
									&& (entry->mask[3] & ipaddr[3]) == entry->net[3]) {
										// Found a match
										tptp_free(ipaddr);
										r = TRUE;
										break;
								} else {
									tptp_free(ipaddr);
								}
								
							}

						}

						freeAddressList(addrs, size);

						tptp_free(hostname);

						// If we found a match...
						if(r == TRUE) {
							if(entry->allow) {
								return TRUE;
							} else {
								return FALSE;
							}							
						}

					}
				}
				
			}
			break;
		case TPTP_HW_NAMED:
			{
				char *hostname;

				struct sockaddr_storage ** addrs;
				int numAddrs = 0;
				int x = 0;

				// Convert the config host name to a list of addresses and compare them
				convertHostnameToAddrs(entry->hostname, &addrs, &numAddrs);
				for(x = 0; x < numAddrs; x++) {
					if(compareAddresses(addr, addrs[x])) {
						freeAddressList(addrs, numAddrs);
						
						if(entry->allow) {						
							return TRUE;
						}
						return FALSE;						
						
					}
				}

				// If we didn't match above, and the IP address is actually an IPv4 (coded as an IPv6 addy) then convert
				// and try again.
				if(isAddressIPv6RemappedToIPv4(addr)) {
					struct sockaddr_storage *addr4 = convertIPv6RemappedAddresstoIPv4(addr);

					for(x = 0; x < numAddrs; x++) {
						if(compareAddresses(addr4, addrs[x])) {
							freeAddressList(addrs, numAddrs);

							if(entry->allow) {
								return TRUE;
							}
							return FALSE;

						}
					}
				}

				freeAddressList(addrs, numAddrs);
				
				if(isAddrLocal(addr)) {
					return (entry->allow) ? TRUE : FALSE;					
				}
				
				
				hostname = convertAddrToHostname(addr);				
				
				if(hostname != NULL) {
				
					if(strcasecmp(hostname, entry->hostname) == 0) {
						tptp_free(hostname);
						return (entry->allow) ? TRUE : FALSE;					
					}
					
					r = FALSE;
					
					l = strlen(entry->hostname);
					
					// If the resolved host name is greater than or equal to the size of the entry host name 
					if(l <= strlen(hostname)) {
						
						// If the entire 'entry' matches most of the resolve hostname
						if(strncmp(entry->hostname, hostname, l) == 0) {

							
							if(hostname[l] ==0 || hostname[l] == '.') {
								r = TRUE;
							}
							
						}
						
					}

					tptp_free(hostname);
					
					if(r) {
						return (entry->allow) ? TRUE : FALSE;
					}
					
					
					
				}

			}
			break;
		}
		current=current->next;
	}

	return FALSE;
}
