//	***************************************************************************
//	***************************************************************************
//	***																		***
//	***	LEGAL DISCLAIMER AND RIGHTS POSTING:								***
//	***																		***
//	***	THIS IS A TOOL OF HACK BY JACK.										***
//	***	THIS IS RELEASED UNDER THE GNU PUBLIC LICENSE (GPL).				***
//	***	PLEASE READ THE GPL FOR OTHER DETAILS OF THIS LICENSE.				***
//	***	PLEASE ALSO WRITE SOFTWARE AND GIVE IT AWAY UNDER THE GPL.			***
//	*** THIS LICENSE MAY BE VIEWED AT http://www.fsf.org/copyleft/gpl.html	***
//	***																		***
//	***	I ACCEPT NO LIABILITY WHATSOEVER IN REGARDS TO YOUR USE OF THIS		***
//	***	_SOURCE_CODE_.  I DIDN'T WRITE THIS TO GET SUED, IT IS INTENDED		***
//	***	FOR EDUCATIONAL USE IN THE STUDY OF HOW THE INTERNET WORKS.			***
//	***																		***
//	*** CAVEAT EMPTOR AND YOU GET WHAT YOU PAY FOR!!!						***
//	***																		***
//	*** HAVE A NICE DAY!!!													***
//	***																		***
//	***************************************************************************
//	***************************************************************************
// 
//	Details of this program:
// 
// 		This program attempts to deliver a bid on an eBay item
// 		at a time somewhere near the specified bidtime (in seconds)
// 		before	the auction closes.  There is an attempt to correct
// 		the timing of the placement of the bid using reactivity and
// 		time-windowing in regards to observed latency.  This is
// 		all very ad-hoc, and has been fine-tuned by me by using
// 		the utility in a mode such that no actual bid is placed.
// 		Because the timing is rather loose, this program is not
// 		really reliable when using bidtimes of less than about 30
// 		seconds.  The unreliability has to do mostly with eBay and
// 		internet response times in general.  Also, in general, this
// 		program will work much better when given a high-bandwidth
// 		connection to the internet.  Slow connections increase
// 		latency and may cause a complete miss of the time window in
// 		which this program attempts to operate.
//
//	NOTE:	This program is a hack.  It is ugly.  It has bugs.
// 	
// 	NOTE:	AS PROVIDED, THIS PROGRAM WILL NOT ACTUALLY BID!!!
// 			TO MAKE THIS HAPPEN SEE THE #ifndef IN bidItem().
//
//		i.e. to build an UNREAL NO BIDDING VERSION of this program:
//		gcc -Wall -g -o sniper_nobidding sniper.c
//
// 	NOTE:	SMALL CHANGES TO THE eBay CGI output will very easily
// 			stifle the ability of this program to do its job.
// 			Take a look at the parsing!!!  It's very fragile!!!
// 	
//
// 	COLUMNS 166 TABSTOP 4
// 
// ***************************************************************************
// ***************************************************************************
 
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/signal.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define COMMAND_LEN				1024
#define QUERY_STRING_LEN		1024
#define KEY_LEN					1024
#define RUNOUT_BUF_LEN			8192
#define MIN_BIDTIME				5
#define LOG_BUF_SIZE			1024

#define HOSTNAME				"cgi.ebay.com"

inline char *Strncpy(char *dest, const char *src, size_t n)
{
	if (n < 1) return NULL;
	strncpy(dest, src, n - 1);
	dest[n - 1] = '\0';
	return dest;
}

char tkey[KEY_LEN];
FILE *logfile = NULL;

int log(FILE *fout, char *fmt, ...)
{
	char					logbuf[LOG_BUF_SIZE + 1];
	int						count;
	struct timeval			tv;
	struct tm				tm;
	va_list					argptr;
	
	gettimeofday(&tv, NULL);
	localtime_r((long *) &tv.tv_sec, &tm);

	count = strftime(logbuf, LOG_BUF_SIZE, "\n\n*** %Y""%m""%d %H""%M""%S", &tm);
	count += snprintf(logbuf + count, LOG_BUF_SIZE - count, ".%06ld ", tv.tv_usec);
	va_start(argptr, fmt);
	vsnprintf(logbuf + count, LOG_BUF_SIZE - count, fmt, argptr);
	va_end(argptr);
	
	logbuf[LOG_BUF_SIZE] = '\0';
	
	fprintf(stderr, logbuf + count);
	if (fout != NULL) { fprintf(fout, logbuf); fflush(fout); }
	
	return count;
}

int verbose_connect(char *host, int retry_time, int retry_count)
{
	int save_errno, sockfd, rc, count;
	struct sockaddr_in serv_addr;
	struct hostent *entry;

	log(logfile, "name");
	for (count = 0; count < 10; count++)
	{
		if ((entry = gethostbyname(host)) == NULL)
		{
			log(logfile, " gethostbyname errno %d\n", h_errno);
			sleep(1);
		} else break;
	}
	if (entry == NULL)										{ log(logfile, " gethostbyname retry out!!!\n"); return -1; }
	if (entry->h_addrtype != AF_INET)						{ log(logfile, " host is not AF_INET\n"); return -1; }

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	memcpy(&serv_addr.sin_addr.s_addr, entry->h_addr, 4);
	serv_addr.sin_port = htons(80);

	log(logfile, " connect");
	for ( ; retry_count > 0; retry_count--)
	{
		if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)	{ log(logfile, "socket errno %d\n", errno); return -1; }
		siginterrupt(SIGALRM, 1);
		alarm(retry_time);
		rc = connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(struct sockaddr_in));
		save_errno = errno;
		alarm(0);
		siginterrupt(SIGALRM, 0);
		if (rc == 0) break;
		log(logfile, " connect errno %d", save_errno);
		close(sockfd);
		sockfd = -1;
	}
	if (rc == 0) log(logfile, " OK ");
	return sockfd;
}
	
// attempt to match some input, while looking for "NO CARRIER"
// returns 0 on success, -1 on failure
int match(int fd, char *str)
{
	char	*cursor;
	char	c;

	fprintf(logfile, "\n\nmatch: %s\n\n", str);

	cursor = str;
	while (read(fd, &c, 1) > 0)
	{
		fputc(c, logfile);
		if (c == *cursor)
		{
			cursor++;
			if (*cursor == '\0') return 0;
		} else
		{
			cursor = str;
		}
	}
	return -1;
}

char *getuntilchar(int fd, char unt_c)
{
	static char buf[QUERY_STRING_LEN];
	int count;
	char c;

	fprintf(logfile, "\n\ngetuntilchar: %c\n\n", unt_c);

	count = 0;
	while (read(fd, &c, 1) > 0)
	{
		fputc(c, logfile);
		if (count >= QUERY_STRING_LEN) return NULL;
		if (c == unt_c)
		{
			buf[count] = '\0';
			return buf;
		} else
		{
			buf[count] = c;
		}
		count++;
	}
	return NULL;
}

void runout(int sockfd)
{
	int rc, count;
	char runout_buf[RUNOUT_BUF_LEN];

	fprintf(logfile, "\n\nrunout\n\n");
	fprintf(stderr, " runout");

	count = 0;
	for (rc = 0; (rc = read(sockfd, runout_buf, RUNOUT_BUF_LEN)) > 0; count += rc) fwrite(runout_buf, rc, 1, logfile);
	
	log(logfile, " %d bytes", count);
}

long getseconds(char *timestr)
{
	// October 2000: eBay has started using singular AND plural time identifiers in their CGI output
	// these constants were changed to their singular American English incarnation
	char *days = "day";
	char *hours = "hour";
	char *minutes = "min";
	char *seconds = "sec";

	long accum = 0;
	long this_num;
	char *cursor = timestr;

	//log(logfile, "timestr: \"%s\"", timestr);
	
	while (*cursor != '\0')
	{
		this_num = atol(cursor);

		// August 2001: eBay has again changed their CGI output
		// this time parsing scheme works, the old one does not...
		while (*cursor != '\0' && *cursor >= '0' && *cursor <= '9') cursor++;
		if (*cursor == '\0')
		{
			log(logfile, "time parse error");
			exit(1);
		}
		while (*cursor <= 'a' || *cursor >= 'z') cursor++;
		
		     if (!strncmp(cursor, days,		strlen(days))) 		{ cursor += strlen(days);		this_num *= 86400;	}
		else if (!strncmp(cursor, hours,	strlen(hours))) 	{ cursor += strlen(hours);		this_num *= 3600;	}
		else if (!strncmp(cursor, minutes,	strlen(minutes)))	{ cursor += strlen(minutes);	this_num *= 60;		}
		else if (!strncmp(cursor, seconds,	strlen(seconds)))	{ accum += this_num;			return accum;		}
		else return accum;
		
		accum += this_num;
		
		while (*cursor != '\0' && (*cursor < '0' || *cursor > '9')) cursor++;
	}

	return accum;
}

long getItemInfo(char *item, char *amount, char *user)
{
	int sockfd;
	char query_string[QUERY_STRING_LEN];
	char timestr[QUERY_STRING_LEN];
	long secs;
	char currently_buf[QUERY_STRING_LEN];
	char high_bidder_buf[QUERY_STRING_LEN];
	char *c_ptr;

	fprintf(logfile, "\n\n*** getItemInfo item %s amount %s user %s\n", item, amount, user);

	if ((sockfd = verbose_connect(HOSTNAME, 10, 5)) < 0)	{ log(logfile, "CONNECT FAILED!!!\n"); return -1; }
	
	snprintf(query_string, 256, "GET /aw-cgi/eBayISAPI.dll?ViewItem&item=%s\n", item);
	fprintf(logfile, "\n\nquery string:\n\n%s\n", query_string);
	write(sockfd, query_string, strlen(query_string));
	
	if (match(sockfd, "Currently") != 0)						return -1;
	if (match(sockfd, "<b>$") != 0)								return -1;
	Strncpy(currently_buf, getuntilchar(sockfd, '<'), QUERY_STRING_LEN);
	log(logfile, "$%s", currently_buf);
	c_ptr = currently_buf;

	if (match(sockfd, "# of bids") != 0)						return -1;
	if (match(sockfd, "<b>") != 0)								return -1;
	log(logfile, " bids: ");
	log(logfile, getuntilchar(sockfd, '<'));
	
	if (match(sockfd, "Time left") != 0)						return -1;
	if (match(sockfd, "<b>") != 0)								return -1;
	log(logfile, " Time: ");

	Strncpy(timestr, getuntilchar(sockfd, '<'), QUERY_STRING_LEN);
	log(logfile, timestr);
	log(logfile, " (%ld sec)", (secs = getseconds(timestr)));
	
	if (match(sockfd, "High bid") != 0)							return -1;
	if (match(sockfd, "<a href") != 0)							return -1;
	if (match(sockfd, ">") != 0)								return -1;
	log(logfile, " High: ");
	Strncpy(high_bidder_buf, getuntilchar(sockfd, '<'), QUERY_STRING_LEN);
	log(logfile, high_bidder_buf);
	
	if (strcmp(high_bidder_buf, user)) log(logfile, " (NOT %s)", user); else log(logfile, "!!!");
	if (atof(c_ptr) < 0.01)
	{
		log(logfile, "\nAMOUNT CONVERSION ERROR %s->%f\n", c_ptr, atof(c_ptr));
		exit(0);
	}
	if (atof(c_ptr) > atof(amount))
	{
		log(logfile, "\nMAXIMUM EXCEEDED %s > %s\n", c_ptr, amount);
		exit(0);
	}
	
	runout(sockfd);
	close(sockfd);

	return secs;
}

#define PRE_BID_PFX	"POST /aw-cgi/eBayISAPI.dll HTTP/1.0\n"											\
					"Referer: http://" HOSTNAME "/aw-cgi/eBayISAPI.dll?ViewItem&item=%s\n"			\
					"Connection: Keep-Alive\n"														\
					"User-Agent: Mozilla/4.7 [en] (X11; U; Linux 2.2.12 i686)\n"					\
					"Host: " HOSTNAME "\n"															\
					"Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*\n"	\
					"Accept-Encoding: gzip\n"														\
					"Accept-Language: en\n"															\
					"Accept-Charset: iso-8859-1,*,utf-8\n"											\
					"Content-type: application/x-www-form-urlencoded\n"

int preBidItem(char *item, char *amount)
{
	int sockfd;
	char query_string[QUERY_STRING_LEN];
	char eBayISAPICommand[COMMAND_LEN];
	char *key;
	char *cursor;
	char catter[2];

	fprintf(logfile, "\n\n*** preBidItem item %s amount %s\n", item, amount);

	if ((sockfd = verbose_connect(HOSTNAME, 6, 5)) < 0)	{ log(logfile, "CONNECT FAILED!!!\n"); return -1; }

	snprintf(eBayISAPICommand, COMMAND_LEN, "MfcISAPICommand=MakeBid&item=%s&maxbid=%s\n",
		item, amount);

	snprintf(query_string, QUERY_STRING_LEN,
		PRE_BID_PFX
		"Content-length: %d\n"
		"%s\n",
		item, strlen(eBayISAPICommand), eBayISAPICommand);

	fprintf(logfile, "\n\nquery string: %s\n", query_string);
	write(sockfd, query_string, strlen(query_string));

	log(logfile, "sent pre-bid\n");
	//printf(query_string);

	if (match(sockfd, "<input type=hidden name=key value=\"") != 0)		return -1;
	key = getuntilchar(sockfd, '\"');
	log(logfile, "  reported key is: %s\n", key);
	
	// search for quantity here

	*tkey ='\0';
	catter[1] = '\0';
	for (cursor = key; *cursor != '\0'; cursor++)
	{
		if (*cursor == '$')
		{
			strcat(tkey, "\%24");
		} else
		{
			*catter = *cursor;
			strcat(tkey, catter);
		}
	}

	fprintf(logfile, "\n\ntranslated key is: %s\n\n", tkey);
	fprintf(stderr, "translated key is: %s\n", tkey);
	
	runout(sockfd);
	close(sockfd);
	
	log(logfile, " closed socket\n");
	
	return 0;
}

#define BID_PFX		"POST /aw-cgi/eBayISAPI.dll HTTP/1.0\n"											\
					"Referer: http://" HOSTNAME "/aw-cgi/eBayISAPI.dll\n"							\
					"Connection: Keep-Alive\n"														\
					"User-Agent: Mozilla/4.7 [en] (X11; U; Linux 2.2.12 i686)\n"					\
					"Host: " HOSTNAME "\n"															\
					"Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*\n"	\
					"Accept-Encoding: gzip\n"														\
					"Accept-Language: en\n"															\
					"Accept-Charset: iso-8859-1,*,utf-8\n"											\
					"Content-type: application/x-www-form-urlencoded\n"

int bidItem(char *item, char *amount, char *quantity, char *user, char *password)
{
	int sockfd;
	char query_string[QUERY_STRING_LEN];
	char eBayISAPICommand[COMMAND_LEN];

	fprintf(logfile, "\n\n*** bidItem item %s amount %s quantity %s user %s password %s\n", item, amount, quantity, user, password);

	if ((sockfd = verbose_connect(HOSTNAME, 6, 5)) < 0)	{ log(logfile, "CONNECT FAILED!!!\n"); return -1; }

	snprintf(eBayISAPICommand, COMMAND_LEN,
		"MfcISAPICommand=AcceptBid&item=%s&key=%s&maxbid=%s&quant=%s&userid=%s&pass=%s\n",
		item, tkey, amount, quantity, user, password);
	
	snprintf(query_string, QUERY_STRING_LEN,
		BID_PFX
		"Content-length: %d\n"
		"%s\n",
		strlen(eBayISAPICommand), eBayISAPICommand);

	fprintf(logfile, "\n\nquery string: %s\n", query_string);
	write(sockfd, query_string, strlen(query_string));

	runout(sockfd);
	close(sockfd);

	log(logfile, " BID COMPLETED!!!\n");

	return 0;
}

void sig_alarm(int sig)
{
	signal(SIGALRM, sig_alarm);
	log(logfile, " SIGALRM");
	sig = sig;
}

void sig_term(int sig)
{
	signal(SIGTERM, sig_term);
	log(logfile, " handling SIGTERM...\n");
	sig = sig;
	exit(0);
}

int main(int argc, char *argv[])
{
	long secs;
	time_t start, latency;
	char *item;
	char *amount;
	char *user;
	char *password;
	char *quantity;
	long bidtime;
	int retry_count;
	int now;
	char logfilename[128];
	int errcnt = 0;

	if (argc != 7)
	{
		log(logfile, "usage: %s item amount-each quantity username password bidtime(secs)|now\n", argv[0]);
		exit(1);
	}
	
	// init variables
	item = argv[1];
	amount = argv[2];
	quantity = argv[3];
	user = argv[4];
	password = argv[5];
	now = !strcmp("now", argv[6]);
	if (!now) bidtime = atol(argv[6]);
	else bidtime = 0;
	if (!now && bidtime > 0 && bidtime < MIN_BIDTIME)
	{
		log(logfile, "NOTE: rounded bid time up to %d seconds\n", MIN_BIDTIME);
		bidtime = MIN_BIDTIME;
	}
	
	// init globals
	*tkey = '\0';

	sprintf(logfilename, "sniper.log.%s", item);
	logfile = fopen(logfilename, "a");
	if (logfile == NULL)
	{
		fprintf(stderr, "unable to open a log file\n");
		exit(1);
	}
	
	start = time(NULL);

	log(logfile, "******************************************************************************************************************\n");
	log(logfile, "***\n");
	log(logfile, "*** eBay sniper A TOOL OF HACK BY JACK!!!\n");
	log(logfile, "***\n");
	log(logfile, "*** source code is distributed under the GNU Public License (GPL)\n");
	log(logfile, "***\n");
	log(logfile, "*** item %s amount %s quantity %s user %s password %s bidtime %ld\n",
			item, amount, quantity, user, password, bidtime);
	log(logfile, "***\n");
	log(logfile, "*** program start time %s", ctime(&start));
	log(logfile, "***\n");
	log(logfile, "******************************************************************************************************************\n");
	
	signal(SIGALRM, sig_alarm);
	signal(SIGTERM, sig_term);
	
	if (!now)
	{
		log(logfile, "*** WATCHING item %s amount-each %s quantity %s user %s password %s bidtime %ld\n",
			item, amount, quantity, user, password, bidtime);

		while (1)
		{
			start = time(NULL);
			secs = getItemInfo(item, amount, user);
			latency = time(NULL) - start;
		
			// error
			if (secs < 0)
			{
				errcnt++;
				log(logfile, " ERROR %d!!!\n");
				if (errcnt > 50)
				{
					log(logfile, "ERRORED OUT!!!\n");
					exit(1);
				}
				sleep(1);
				continue;
			}

			secs -= bidtime;
		
			// it's time!!!
			if (secs < 0) break;
			// if we're less than a minute away, get the key for the bid
			if (secs < 60) if (strlen(tkey) == 0) { fprintf(stderr, "\n"); preBidItem(item, amount); }
			// laggy connection at end of auction?
			if (secs > 7 && secs < 60 && latency > 5) { log(logfile, " latency %ld NO SLEEP\n", latency); continue; }
			// special handling when we're right on the cusp
			if (secs < 8)
			{
				secs -= latency;
				if (secs < 0) secs = 0;
				log(logfile, " latency %ld CLOSE!!! SLEEP %ld", latency, secs);
				if (secs > 0) sleep(secs);
				break;

			// else logarithmic decay by twos
			} else secs /= 2;

			log(logfile, " latency %ld sleep %ld\n", latency, secs);
			sleep(secs);
		}
	}
	
	log(logfile, " IT'S TIME!!!\n");
	
	if (strlen(tkey) == 0) { fprintf(stderr, "\n"); preBidItem(item, amount); }
	
	log(logfile, "*** BIDDING!!! item %s amount %s quantity %s user %s password %s\n",
		item, amount, quantity, user, password);

	for (retry_count = 0; retry_count < 3; retry_count++)
		if (bidItem(item, amount, quantity, user, password) != 0) log(logfile, " BID FAILED!!!");
		else break;
	
	// view item after bid
	log(logfile, "*** POST-BID GET INFO!!! ");
	secs = getItemInfo(item, amount, user);

	log(logfile, "\nexiting\n");
	exit(0);
}

