#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 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.
 *
 *   This function does not recalculate the difference everytime it is called
 *   so it's possible that it will return the wrong value if your GMT offset
 *   changes while the process is loaded.
 *
 */
static time_t get_gmtoffset(void) {
	static time_t retval = -1;

	/* Return saved value. */
	if (retval != -1) {
		return(retval);
	}

	time(&retval);

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

	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);
	}

	/* Fill in values from RFC-compliant string. */
	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; /* The date is in UTC and has no DST. */

	/* 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();

	/* Create GMT value */
	currtime = mktime(&timeinfo) + gmtoffset;

	return(currtime);
}

/*
 * SYNOPSIS:
 *   double set_clock(
 *                    struct timeval *tvinfo,
 *                    int allow_adj
 *                   );
 *
 * ARGUMENTS:
 *   struct timeval *tvinfo  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.
 *
 * 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(struct timeval *tvinfo, int allow_adj) {
	struct timeval tvdelta;
	double retval = 0.0;
	int chkret = -1;

	gettimeofday(&tvdelta, NULL);

#ifdef HAVE_ADJTIME
	if (allow_adj) {
		tvdelta.tv_sec = tvinfo->tv_sec - tvdelta.tv_sec;
		tvdelta.tv_usec = tvinfo->tv_usec - tvdelta.tv_usec;
	}

	if ((tvdelta.tv_sec < 60 && tvdelta.tv_sec > -60) && 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 = (tvinfo->tv_sec - tvdelta.tv_sec) + (((double) (tvinfo->tv_usec - 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
 *
 * 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) {
	unsigned int         server_s;		/* Server socket descriptor */
	struct sockaddr_in   server_addr;	/* Server Internet address */
	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;
	struct			hostent	*hostinfo;
	time_t			remote_timeval = -1, retval = -1;

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

	hostinfo = gethostbyname(connect_host);
	if (!hostinfo) {
		return(-1);
	}

	/* Create a socket */
	server_s = socket(AF_INET, SOCK_STREAM, 0);

	/* The web server socket's address information */
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(connect_port);
	server_addr.sin_addr = *(struct in_addr *)*hostinfo -> h_addr_list;

	/* Do a connect (connect() blocks) */
	retcode = connect(server_s, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if ( retcode < 0 ) {
		return(-1);
	}

	/* Send the most simple HEAD request */
	if (proxyhost == NULL) {
		snprintf(out_buf, sizeof(out_buf), "HEAD / HTTP/1.0\r\nPragma: no-cache\r\nCache-Control: Max-age=0\r\n\r\n");
	} else {
		snprintf(out_buf, sizeof(out_buf), "HEAD http://%s:%i/ HTTP/1.0\r\nPragma: no-cache\r\nCache-Control: Max-age=0\r\n\r\n", host, port);
	}
	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.
 *
 * 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) {
	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;
	int stddevtimes = 0;

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

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

		timeval = get_server_time(host, port, proxyhost, proxyport);
		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) {
		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 {

			/* 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) {
		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;

	return(&retval);
}
