#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "win32.h"

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#endif
#ifdef TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#else
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
#endif
#ifndef HAVE_GETTIMEOFDAY
#include "gettimeofday.h"
#endif
#ifndef HAVE_SETTIMEOFDAY
#include "settimeofday.h"
#endif

#include "htp.h"

/*
 * SYNOPSIS:
 *   static char *mime_encode(
 *                            const unsigned char *data
 *                           );
 *
 * ARGUMENTS:
 *   const unsigned char *data  Data to MIME64 encode.
 *
 * RETURN VALUE:
 *   This function returns a pointer to the MIME64 encoded data.
 *   The return buffer has been allocated with malloc() and must be free()'d.
 *
 * NOTES:
 *   Free this buffer after use.
 *
 */
static char *mime_encode(const unsigned char *data) {
	static char mimeabet[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	unsigned long buffer;
	size_t datalen, retlen, outlen;
	char *ret;
	int i, x=0;

	datalen = strlen((char *) data);

	/* Size of actual charectars to fill. */
	outlen = (int) (((double) datalen) * 1.3333333 + 0.9);

	/* Size of buffer, atleast as large as "outlen" but probably larger. */
	retlen = (int) ((((double) datalen) / 3.0 + 0.9) * 4);

	ret = malloc(retlen + 1);

	if (ret == NULL) {
		return(NULL);
	}

	for (i = 0; i < datalen; i += 3) {
		buffer = (data[i] << 16) | (data[i + 1] << 8) | data[i + 2];
		ret[x++] = mimeabet[(buffer & 0xfc0000) >> 18];
		if (x >= outlen) break;                                     
		ret[x++] = mimeabet[(buffer & 0x3f000) >> 12];
		if (x >= outlen) break;                                                                      
		ret[x++] = mimeabet[(buffer & 0xfc0) >> 6];
		if (x >= outlen) break;
		ret[x++] = mimeabet[buffer & 0x3f];
		if (x >= outlen) break;
	}

	while (x != retlen) {
		ret[x++]='=';
	}

	ret[x] = '\0';

	return(ret);
}

/*
 * SYNOPSIS:
 *   static time_t get_gmtoffset(void);
 *
 * ARGUMENTS:
 *   (none)
 *
 * RETURN VALUE:
 *   This function return a time_t value that can be added to a localtime
 *   time_t value to produce a GMT time_t value.
 *
 *   -1 is returned on error.
 *
 * NOTES:
 *   Calculate the difference between localtime and GMT/UTC.
 *
 */
static time_t get_gmtoffset(void) {
	time_t retval, gmt;

	retval = gmt = time(NULL);

	retval -= mktime(gmtime(&gmt));

	return(retval);
}

/*
 * SYNOPSIS:
 *   static time_t mktime_from_rfc2616(
 *                                     const char *date
 *                                    );
 *
 * ARGUMENTS:
 *   const char *date        Date in RFC2616 compliant format
 *
 * RETURN VALUE:
 *   This function returns the time_t value for the date string passed
 *   (which should be at GMT/UTC).
 *
 * NOTES:
 *
 */
static time_t mktime_from_rfc2616(const char *date) {
	char *monthinfo[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
	struct tm timeinfo;
	time_t currtime = -1;
	unsigned int i;
	time_t gmtoffset;

	/* Require a valid parameter. */
	if (date == NULL) {
		return(-1);
	}

	/* Reject short strings. */
	if (strlen(date) < 24) {
		return(-1);
	}

	currtime = time(NULL);

	/* Fill in values from RFC-compliant string and adjust to localtime. */
	timeinfo.tm_mday = atoi(date + 5);
	timeinfo.tm_year = atoi(date + 12) - 1900;
	timeinfo.tm_hour = atoi(date + 17);
	timeinfo.tm_min = atoi(date + 20);
	timeinfo.tm_sec = atoi(date + 23);
	timeinfo.tm_isdst = 0;

	/* Determine month. */
	timeinfo.tm_mon = -1;
	for (i = 0; i < (sizeof(monthinfo) / sizeof(monthinfo[0])); i++) {
		if (strncmp(date + 8, monthinfo[i], 3) == 0) {
			timeinfo.tm_mon = i;
			break;
		}
	}

	/* Reject invalid months. */
	if (timeinfo.tm_mon < 0) {
		return(-1);
	}

	/* Calculate GMT offset. */
	gmtoffset = get_gmtoffset();

	/* Convert the time to a value. */
	currtime = mktime(&timeinfo);
	currtime += gmtoffset;
#ifdef _USE_WIN32_
	/* Win32 (Mingw32?) timezone calculations are screwy */
	if (timeinfo.tm_isdst) {
		currtime -= 3600;
	}
#endif

	return(currtime);
}

/*
 * SYNOPSIS:
 *   double set_clock(
 *                    const struct timeval *tvdata
 *                    int allow_adj,
 *                    int ignoreusec
 *                   );
 *
 * ARGUMENTS:
 *   const struct timeval *tvdata  Pointer to time value to set clock to
 *                                 (GMT/UTC)
 *   int allow_adj           Set to 1 if you want to allow time to be "Slew"'d
 *                           if the OS supports this.
 *   int ignoreusec          Set to non-zero if you want to ignore the usec
 *                           field in the `tvinfo' parameter.
 *
 * RETURN VALUE:
 *   This function returns the number of seconds the clock was adjusted.
 *   0.0 is returned on failure to adjust the clock.
 *
 * NOTES:
 *   0.0 is returned if the clock cannot be set, this is the same return
 *   value as if no adjustment was needed.
 *
 */
double set_clock(const struct timeval *tvdata, int allow_adj, int ignoreusec) {
	struct timeval tvdelta;
	struct timeval tvinfo;
	double retval = 0.0;
	int chkret = -1;

	gettimeofday(&tvdelta, NULL);

	tvinfo.tv_sec = tvdata->tv_sec;
	tvinfo.tv_usec = tvdata->tv_usec;

	if (ignoreusec) {
		tvinfo.tv_usec = tvdelta.tv_usec;
	}

	tvdelta.tv_sec = tvinfo.tv_sec - tvdelta.tv_sec;
	tvdelta.tv_usec = tvinfo.tv_usec - tvdelta.tv_usec;

	if (tvdelta.tv_sec == 0 && tvdelta.tv_usec == 0) {
#ifdef HAVE_SYSLOG
		syslog(LOG_NOTICE, "Skipping adjustment, clock is already correct.");
		return(retval);
#endif
	}

#ifdef HAVE_ADJTIME
	if ((tvdelta.tv_sec < 3 && tvdelta.tv_sec > -3) && allow_adj) {
		chkret = adjtime(&tvdelta, NULL);
		if (chkret >= 0) {
			retval = tvdelta.tv_sec + (((double) tvdelta.tv_usec) / 1000000.0);
#ifdef HAVE_SYSLOG
			syslog(LOG_NOTICE, "Adjusting clock by %f seconds (slew).", retval);
#endif
		}
	}
#endif

	if (chkret < 0) {
		chkret = settimeofday(&tvinfo, NULL);
		if (chkret >= 0) {
			retval = tvdelta.tv_sec + (((double) tvdelta.tv_usec) / 1000000.0);
#ifdef HAVE_SYSLOG
			syslog(LOG_NOTICE, "Adjusting clock by %f seconds (step).", retval);
#endif
		}
	}

	return(retval);
}

/*
 * SYNOPSIS:
 *   static time_t get_server_time(
 *                                 const char *host,
 *                                 unsigned int port,
 *                                 const char *proxyhost,
 *                                 unsigned int proxyport
 *                                );
 *
 * ARGUMENTS:
 *   const char *host        Destination host
 *   unsigned int port       Destination port
 *   const char *proxyhost   Proxy to send request through
 *   unsigned int proxyport  Proxy port to connect
 *   const char *credentials Authentication credentials (NULL for no auth)
 *
 * RETURN VALUE:
 *   This function returns the GMT/UTC time_t value that is specified
 *   in the remote hosts HTTP/1.1 header reply.  (time_t) -1 is returned
 *   on error.
 *
 * NOTES:
 *   If `proxyhost' is not NULL all connections are made through `proxyhost'
 *   on the `proxyport' port.  If `proxyhost' is NULL, connections are made
 *   directly (and proxyport is ignored.)
 *
 */
static time_t get_server_time(const char *host, unsigned int port, const char *proxyhost, unsigned int proxyport, const char *credentials) {
	unsigned int         server_s;		/* Server socket descriptor */
	struct addrinfo      *addrs = NULL, *curr; /* Name resolution structure */
	struct sockaddr      *server_addr;	/* Server Internet address */
	struct sockaddr_in   server_addr_v4;	/* Server Internet address (IPv4) */
	struct sockaddr_in6  server_addr_v6;	/* Server Internet address (IPv6) */
	socklen_t            server_addr_len;
	char                 out_buf[2048];	/* Output buffer for HEAD request */
	char                 in_buf[2048];	/* Input buffer for response */
	int 		     retcode;		/* Return code */

	char			remote_time[32] = {0};
	char			*pdate;
	const char		*connect_host = NULL;
	unsigned int		connect_port = 0;
	time_t			remote_timeval = -1, retval = -1;
	int                     gai_ret;

	if (proxyhost == NULL) {
		connect_host = host;
		connect_port = port;
	} else {
		connect_host = proxyhost;
		connect_port = proxyport;
	}

	gai_ret = getaddrinfo(connect_host, "http", NULL, &addrs);
	if (gai_ret != 0 || addrs == NULL) {
		return(-1);
	}

	for (curr = addrs; curr; curr = curr->ai_next) {
		/* Create a socket */
		server_s = socket(curr->ai_family, SOCK_STREAM, 0);

		/* The web server socket's address information */
		switch (curr->ai_family) {
			case AF_INET:
				server_addr = (struct sockaddr *) &server_addr_v4;
				memcpy(server_addr, curr->ai_addr, curr->ai_addrlen);
				server_addr_v4.sin_port = htons(connect_port);
				server_addr_len = sizeof(server_addr_v4);
				break;
			case AF_INET6:
				server_addr = (struct sockaddr *) &server_addr_v6;
				memcpy(server_addr, curr->ai_addr, curr->ai_addrlen);
				server_addr_v6.sin6_port = htons(connect_port);
				server_addr_len = sizeof(server_addr_v6);
				break;
		}

		/* Do a connect (connect() blocks) */
		retcode = connect(server_s, server_addr, server_addr_len);
		if ( retcode < 0 ) {
			close(server_s);

			continue;
		}

		break;
	}

	freeaddrinfo(addrs);

	if (retcode < 0) {
		close(server_s);

		return(-1);
	}

	/* Send the most simple HEAD request */
	if (proxyhost == NULL) {
		snprintf(out_buf, sizeof(out_buf), "HEAD / HTTP/1.0\r\n\r\n");
	} else if (credentials == NULL) {
		snprintf(out_buf, sizeof(out_buf), "HEAD http://%s:%i/ HTTP/1.0\r\n\r\n", host, port);
	} else {
		snprintf(out_buf, sizeof(out_buf), "HEAD http://%s:%i/ HTTP/1.0\r\nProxy-Authorization: Basic %s\r\n\r\n", host, port, credentials);
	}

	send(server_s, out_buf, strlen(out_buf), 0);

	/* Receive data from the web server */
	/* The return code from recv() is the number of bytes received */
	retcode = recv(server_s, in_buf, sizeof(in_buf), 0);
	if ( retcode <= 0 ) {
		close(server_s);
		return(-1);
	}

	if ((pdate = (char *) strstr(in_buf, "Date: ")) != NULL) {
		strncpy(remote_time, pdate +  6, 29);

		remote_timeval = mktime_from_rfc2616(remote_time);

		retval = remote_timeval;
	} else {
	}

	close(server_s);

	return(retval);
}

/*
 * SYNOPSIS:
 *   static int htp_net_init(void);
 *
 * ARGUMENTS:
 *   (none)
 *
 * RETURN VALUE:
 *   0 is returned on success (network is ready to go), otherwise -1 is
 *   returned (network access will fail.)
 *
 * NOTES:
 *   This function is largely needed for Windows only.
 *
 */
static int htp_net_init(void) {
#ifdef _USE_WIN32_  
	WSADATA wsaData;

	if (WSAStartup(MAKEWORD(2, 0), &wsaData)!=0) {
		return(-1);                           
	}
	if (wsaData.wVersion!=MAKEWORD(2, 0)) {
		/* Cleanup Winsock stuff */    
		WSACleanup();              
		return(-1);  
	}
#endif
	return(0);
}

/*
 * SYNOPSIS:
 *   int htp_init(void);
 *
 * ARGUMENTS:
 *   (none)
 *
 * RETURN VALUE:
 *   0 is returned on success (htp routines are usable), otherwise -1 is
 *   returned (htp routines will fail.)
 *
 * NOTES:
 *   This must be among the first things called from your `main' program.
 *
 */
int htp_init(void) {
	if (htp_net_init() < 0) {
		fprintf(stderr, "Error: Couldn't initialze network routines.\n");
		return(-1);
	}

	return(0);
}

/*
 * SYNOPSIS:
 *   static int compare_time(
 *                           const void *a,
 *                           const void *b
 *                          );
 *
 * ARGUMENTS:
 *   const void *a           A pointer to a `struct timeval' value.
 *   const void *b           Another pointer to a `struct timeval' value.
 *
 * RETURN VALUE:
 *   This function returns -1, 0, or 1 if the `struct timeval' value pointed
 *   to by a is less than, equal to, or greater than (respectively) the
 *   `struct timeval' value pointed to be b.
 *
 * NOTES:
 *   This function is meant to be passed as the 4th (func) argument to
 *   qsort().
 *
 */
static int compare_time(const void *a, const void *b) {
	const struct timeval *timea = 0, *timeb = 0;

	timea = a;
	timeb = b;

	if (timea->tv_sec < timeb->tv_sec) {
		return(-1);
	}

	if (timea->tv_sec > timeb->tv_sec) {
		return(1);
	}

	/* timea->tv_sec MUST EQUAL timeb->tv_sec. */

	if (timea->tv_usec < timeb->tv_usec) {
		return(-1);
	}

	if (timea->tv_usec > timeb->tv_usec) {
		return(1);
	}

	return(0);
}

/*
 * SYNOPSIS:
 *   struct timeval *htp_calctime(
 *                                struct timeserver_st *timeservers,
 *                                unsigned int totaltimeservers,
 *                                const char *proxyhost,
 *                                unsigned int proxyport
 *                               );
 *
 * ARGUMENTS:
 *   struct timeserver_st *timeservers      Pointer to the first element of
 *                                          an array of `struct timeserver_st'
 *                                          variables.
 *   unsigned int totaltimeservers          Number of entries in timeservers
 *                                          array.
 *   const char *proxyhost                  Proxy hostname to connect through.
 *   unsigned int proxyport                 Port to connect to proxy on.
 *   const char *username                   Username for authentication (NULL
 *                                          for no authentication.)
 *   const char *password                   Password for authentication (NULL
 *                                          for no authentication.)
 *
 * RETURN VALUE:
 *   This function returns the best approximation of the current time in
 *   UTC/GMT.  NULL is returned on failure.
 *
 * NOTES:
 *   If more than 2 timeservers are specified the time values outside of one
 *   standard deviation (+/- 34% about the mean) are discarded.  The average
 *   of all the (non-excluded) values is returned.
 *
 *   This function accounts for the time it takes for itself to execute in
 *   its return value.
 *
 *   This function returns a static buffer, if you wish to have two calls
 *   to it you must store the values elsewhere.
 *
 */
struct timeval *htp_calctime(struct timeserver_st *timeservers, unsigned int totaltimeservers, const char *proxyhost, unsigned int proxyport, const char *username, const char *password) {
	unsigned int timeind = 0, meantimeind = 0;
	unsigned int totaltimes = 0, totalstddevtimes = 0;
	unsigned int port = 0;
	time_t timeval = -1;
	static struct timeval retval;
	struct timeval timevals[128];
	struct timeval start_time, end_time;
	struct timeval avgtime, stddevavgtime;
	struct timeval *mintime, *maxtime, *meantime;
	struct timeval *newtimeval = NULL;
	struct timeval *tmptime;
	long newusecs = 0;
	char *host = NULL;
	char *authcredentials = NULL, tmpbuf[128];
	int stddevtimes = 0;

	if (gettimeofday(&start_time, NULL) < 0) {
		return(NULL);
	}

	if (username != NULL && password != NULL) {
		snprintf(tmpbuf, sizeof(tmpbuf), "%s:%s", username, password);
		authcredentials = mime_encode((unsigned char *) tmpbuf);
	}

	for (timeind = 0; timeind < totaltimeservers; timeind++) {	
		host = timeservers[timeind].host;
		port = timeservers[timeind].port;

		timeval = get_server_time(host, port, proxyhost, proxyport, authcredentials);
		if (timeval < 0) {
			continue;
		}

		/* Remove the amount of time spent so far. */
		gettimeofday(&end_time, NULL);
		timevals[totaltimes].tv_sec = timeval - (end_time.tv_sec - start_time.tv_sec);
		newusecs = end_time.tv_usec - start_time.tv_usec;
		while (newusecs < 0) {
			newusecs += 1000000;
			timevals[totaltimes].tv_sec--;
		}
		timevals[totaltimes].tv_usec = newusecs;

		totaltimes++;
	}

	/* Return failure if no time servers could be reached. */
	if (totaltimes == 0) {
		if (authcredentials != NULL) {
			free(authcredentials);
		}
		return(NULL);
	}

	/* Find the mean time index. */
	meantimeind = totaltimes / 2;

	/* If there's only one time, don't bother with anything else. */
	if (totaltimes == 1) {
		newtimeval = &timevals[0];
	} else {
		qsort(&timevals, totaltimes, sizeof(timevals[0]), compare_time);
		mintime = &timevals[0];
		maxtime = &timevals[totaltimes - 1];
		meantime = &timevals[meantimeind];

		avgtime.tv_sec = 0;
		avgtime.tv_usec = 0;

		for (timeind = 0; timeind < totaltimes; timeind++) {
			tmptime = &timevals[timeind];
			avgtime.tv_sec += tmptime->tv_sec - mintime->tv_sec; /* This trickery is to avoid overflowing the type. */
			avgtime.tv_usec += tmptime->tv_usec;
		}
		avgtime.tv_usec /= totaltimes;
		avgtime.tv_sec /= totaltimes;
		while (avgtime.tv_usec >= 1000000) {
			avgtime.tv_sec++;
			avgtime.tv_usec -= 1000000;
		}
		avgtime.tv_sec += mintime->tv_sec; /* Part of the same silly trickery. */

		/* If there are only 2 values, we can do no more. */
		if (totaltimes == 2) {
			newtimeval = &avgtime;
		} else {
			/* Initialize those values.. yeah.. */
			stddevavgtime.tv_sec = 0;
			stddevavgtime.tv_usec = 0;

			/* Otherwise we only include values within one standard deviation */
			stddevtimes = ((double) totaltimes) * 0.34; /* One standard deviation, +/- 34% about the mean. */

			for (timeind = (meantimeind - stddevtimes); timeind < (meantimeind + stddevtimes); timeind++) {
				tmptime = &timevals[timeind];
				stddevavgtime.tv_sec += tmptime->tv_sec - mintime->tv_sec;
				stddevavgtime.tv_usec += tmptime->tv_usec;
				totalstddevtimes++;
			}
			stddevavgtime.tv_sec /= totalstddevtimes;
			stddevavgtime.tv_usec /= totalstddevtimes;
			while (stddevavgtime.tv_usec >= 1000000) {
				stddevavgtime.tv_sec++;
				stddevavgtime.tv_usec -= 1000000;
			}
			stddevavgtime.tv_sec += mintime->tv_sec;

			newtimeval = &stddevavgtime;
		}
	}

	if (newtimeval <= 0) {
		if (authcredentials != NULL) {
			free(authcredentials);
		}
		return(NULL);
	}

	/* Add back the time this process took. */
	gettimeofday(&end_time, NULL);
	retval.tv_sec = newtimeval->tv_sec + (end_time.tv_sec - start_time.tv_sec);
	newusecs = start_time.tv_usec + newtimeval->tv_usec;
	while (newusecs >= 1000000) {
		newusecs -= 1000000;
		retval.tv_sec++;
	}
	retval.tv_usec = newusecs;

	/* Free our buffers. */
	if (authcredentials != NULL) {
		free(authcredentials);
	}

	return(&retval);
}
