/* The GPL applies to this program.
  In addition, as a special exception, the copyright holders give
  permission to link the code of portions of this program with the
  OpenSSL library under certain conditions as described in each
  individual source file, and distribute linked combinations
  including the two.
  You must obey the GNU General Public License in all respects
  for all of the code used other than OpenSSL.  If you modify
  file(s) with this exception, you may extend this exception to your
  version of the file(s), but you are not obligated to do so.  If you
  do not wish to do so, delete this exception statement from your
  version.  If you delete this exception statement from all source
  files in the program, then also delete it here.
*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#ifndef NO_SSL
#include <openssl/ssl.h>
#include "mssl.h"
#endif
#include <arpa/inet.h>

#include "gen.h"
#include "http.h"
#include "io.h"
#include "str.h"
#include "mem.h"
#include "tcp.h"
#include "res.h"
#include "utils.h"
#include "error.h"

static volatile int stop = 0;

int quiet = 0;
char machine_readable = 0;
char nagios_mode = 0;

char last_error[ERROR_BUFFER_SIZE];

void version(void)
{
	fprintf(stderr, "HTTPing v" VERSION ", (C) 2003-2005 folkert@vanheusden.com\n");
#ifndef NO_SSL
	fprintf(stderr, "SSL support included\n");
#endif
}

void usage(void)
{
	fprintf(stderr, "\n-g url		url (e.g. -g http://localhost/)\n");
	fprintf(stderr, "-h hostname	hostname (e.g. localhost)\n");
	fprintf(stderr, "-p portnr	portnumber (e.g. 80)\n");
	fprintf(stderr, "-x host:port	hostname+portnumber of proxyserver\n");
	fprintf(stderr, "-c count	how many times to connect\n");
	fprintf(stderr, "-i interval	delay between each connect\n");
	fprintf(stderr, "-t timeout	timeout (default: 30s)\n");
	fprintf(stderr, "-s		show statuscodes\n");
	fprintf(stderr, "-G		do a GET request instead of HEAD (read the\n");
	fprintf(stderr, "		contents of the page as well)\n");
	fprintf(stderr, "-b		show transfer speed in KB/s (use with -G)\n");
	fprintf(stderr, "-B		like -b but use compression if available\n");
	fprintf(stderr, "-L x		limit the amount of data transferred (for -b)\n");
	fprintf(stderr, "		to 'x' (in bytes)\n");
	fprintf(stderr, "-X		show the number of KB transferred (for -b)\n");
#ifndef NO_SSL
	fprintf(stderr, "-l		connect using SSL\n");
	fprintf(stderr, "-z		show fingerprint (SSL)\n");
#endif
	fprintf(stderr, "-f		flood connect (no delays)\n");
	fprintf(stderr, "-a		audible ping\n");
	fprintf(stderr, "-m		give machine parseable output (see\n");
	fprintf(stderr, "		also -o and -e)\n");
	fprintf(stderr, "-o rc,rc,...	what http results codes indicate 'ok'\n");
	fprintf(stderr, "		coma seperated WITHOUT spaces inbetween\n");
	fprintf(stderr, "		default is 200, use with -e\n");
	fprintf(stderr, "-e str		string to display when http result code\n");
	fprintf(stderr, "		doesn't match\n");
	fprintf(stderr, "-I str		use 'str' for the UserAgent header\n");
	fprintf(stderr, "-R str		use 'str' for the Referer header\n");
	fprintf(stderr, "-r		resolve hostname only once (usefull when\n");
	fprintf(stderr, "		pinging roundrobin DNS: also takes the first\n");
	fprintf(stderr, "		DNS lookup out of the loop so that the first\n");
	fprintf(stderr, "		measurement is also correct)\n");
	fprintf(stderr, "-n warn,crit	Nagios-mode: return 1 when avg. response time\n");
	fprintf(stderr, "		>= warn, 2 if >= crit, otherwhise return 0\n");
	fprintf(stderr, "-N x		Nagios mode 2: return 0 when all fine, 'x'\n");
	fprintf(stderr, "		when anything failes\n");
	fprintf(stderr, "-y ip[:port]   bind to ip-address (and thus interface) [/port]\n");
	fprintf(stderr, "-q		quiet, only returncode\n");
	fprintf(stderr, "-V		show the version\n\n");
}

void emit_error()
{
	if (!quiet && !machine_readable && !nagios_mode)
	{
		printf("%s", last_error);
	}

	if (!nagios_mode)
		last_error[0] = 0x00;
}

void handler(int sig)
{
	stop = 1;
}

int main(int argc, char *argv[])
{
	char *hostname = NULL;
	char *proxy = NULL, *proxyhost = NULL;
	int proxyport = 8080;
	int portnr = 80;
	char *get = NULL, *request;
	int req_len;
	int c;
	int count = -1, curncount = 0;
	int wait = 1;
	int audible = 0;
	int ok = 0, err = 0;
	double min = 999999999999999.0, avg = 0.0, max = 0.0;
	int timeout=30;
	char show_statuscodes = 0;
	char use_ssl = 0;
	SSL_CTX *client_ctx = NULL;
	char *ok_str = "200";
	char *err_str = "-1";
	char *useragent = NULL;
	char *referer = NULL;
	char *host;
	int port;
	struct sockaddr_in addr;
	char resolve_once = 0;
	char have_resolved = 0;
	double nagios_warn=0.0, nagios_crit=0.0;
	int nagios_exit_code = 2;
	double avg_httping_time = -1.0;
	int get_instead_of_head = 0;
	char *buffer;
	int page_size = sysconf(_SC_PAGESIZE);
	char show_Bps = 0, ask_compression = 0;
	int Bps_min = 1 << 30, Bps_max = 0;
	long long int Bps_avg = 0;
	int Bps_limit = -1;
	char show_bytes_xfer = 0, show_fp = 0;
	int fd = -1;
	SSL *ssl_h = NULL;
	BIO *s_bio;
	struct sockaddr_in bind_to;
	char bind_to_valid = 0;

	if (page_size == -1)
		page_size = 4096;

	buffer = (char *)mymalloc(page_size, "receive buffer");

	while((c = getopt(argc, argv, "y:XL:bBg:h:p:c:i:Gx:t:o:e:falqsmV?I:R:rn:N:z")) != -1)
	{
		switch(c)
		{
			case 'y':
				{
					char *dummy = strchr(optarg, ':');

					bind_to_valid = 1;

					memset(&bind_to, 0x00, sizeof(bind_to));

					if (dummy)
					{
						bind_to.sin_port = atoi(dummy + 1);
						*dummy = 0x00;
					}

					if (inet_aton(optarg, &bind_to.sin_addr) == 0)
					{
						error_exit("cannot convert ip address '%s' (for -y)\n", optarg);
					}
				}
				break;

			case 'z':
				show_fp = 1;
				break;

			case 'X':
				show_bytes_xfer = 1;
				break;

			case 'L':
				Bps_limit = atoi(optarg);
				break;

			case 'B':
				show_Bps = 1;
				ask_compression = 1;
				break;

			case 'b':
				show_Bps = 1;
				break;

			case 'e':
				err_str = optarg;
				break;

			case 'o':
				ok_str = optarg;
				break;

			case 'x':
				proxy = optarg;
				break;

			case 'g':
				get = optarg;
				break;

			case 'r':
				resolve_once = 1;
				break;

			case 'h':
				hostname = optarg;
				break;

			case 'p':
				portnr = atoi(optarg);
				break;

			case 'c':
				count = atoi(optarg);
				break;

			case 'i':
				wait = atoi(optarg);
				break;

			case 't':
				timeout = atoi(optarg);
				break;

			case 'I':
				useragent = optarg;
				break;

			case 'R':
				referer = optarg;
				break;

			case 'a':
				audible = 1;
				break;

			case 'f':
				wait = 0;
				break;

			case 'G':
				get_instead_of_head = 1;
				break;

#ifndef NO_SSL
			case 'l':
				use_ssl = 1;
				break;
#endif

			case 'm':
				machine_readable = 1;
				break;

			case 'q':
				quiet = 1;
				break;

			case 's':
				show_statuscodes = 1;
				break;

			case 'V':
				version();
				return 0;

			case 'n':
				{
					char *dummy = strchr(optarg, ',');
					if (nagios_mode) error_exit("-n and -N are mutual exclusive\n");
					nagios_mode = 1;
					if (!dummy)
						error_exit("-n: missing parameter\n");
					nagios_warn = atof(optarg);
					nagios_crit = atof(dummy + 1);
				} break;
			case 'N':
				if (nagios_mode) error_exit("-n and -N are mutual exclusive\n");
				nagios_mode = 2;
				nagios_exit_code = atoi(optarg);
				break;

			case '?':
			default:
				version();
				usage();
				return 1;
		}
	}

	last_error[0] = 0x00;

#ifndef NO_SSL
	if (use_ssl && portnr == 80)
		portnr = 443;
#endif

	if (!get_instead_of_head && show_Bps)
		error_exit("-b/-B can only be used when also using -G\n");

	if (get != NULL && hostname == NULL)
	{
		char *slash, *colon;
		char *getcopy = mystrdup(get, "get request");
		char *http_string = "http://";
		int http_string_len = 7;

#ifndef NO_SSL
		if (use_ssl)
		{
			http_string_len = 8;
			http_string = "https://";
		}
#endif

		if (strncasecmp(getcopy, http_string, http_string_len) != 0)
		{
			fprintf(stderr, "'%s' is a strange URL\n", getcopy);
			fprintf(stderr, "Expected: %s...\n", http_string);
			return 2;
		}

		slash = strchr(&getcopy[http_string_len], '/');
		if (slash)
			*slash = 0x00;

		colon = strchr(&getcopy[http_string_len], ':');
		if (colon)
		{
			*colon = 0x00;
			portnr = atoi(colon + 1);
		}

		hostname = &getcopy[http_string_len];
	}

	if (hostname == NULL)
	{
		usage();
		error_exit("No hostname/getrequest given\n");
	}

	if (get == NULL)
	{
#ifndef NO_SSL
		if (use_ssl)
		{
			get = mymalloc(8 /* http:// */ + strlen(hostname) + 1 /* colon */ + 5 /* portnr */ + 1 /* / */ + 1 /* 0x00 */, "get");
			sprintf(get, "https://%s:%d/", hostname, portnr);
		}
		else
		{
#endif
			get = mymalloc(7 /* http:// */ + strlen(hostname) + 1 /* colon */ + 5 /* portnr */ + 1 /* / */ + 1 /* 0x00 */, "get");
			sprintf(get, "http://%s:%d/", hostname, portnr);
#ifndef NO_SSL
		}
#endif
	}

	if (proxy)
	{
		char *dummy = strchr(proxy, ':');
		proxyhost = proxy;
		if (dummy)
		{
			*dummy=0x00;
			proxyport = atoi(dummy + 1);
		}

		if (!quiet && !nagios_mode)
			fprintf(stderr, "Using proxyserver: %s:%d\n", proxyhost, proxyport);
	}

#ifndef NO_SSL
	if (use_ssl)
	{
		client_ctx = initialize_ctx();
		if (!client_ctx)
		{
			snprintf(last_error, ERROR_BUFFER_SIZE, "problem creating SSL context\n");
			goto error_exit;
		}
	}
#endif

	request = mymalloc(strlen(get) + 4096, "request");
	sprintf(request, "%s %s HTTP/1.0\r\n", get_instead_of_head?"GET":"HEAD", get);
	if (useragent)
		sprintf(&request[strlen(request)], "User-Agent: %s\r\n", useragent);
	else
		sprintf(&request[strlen(request)], "User-Agent: HTTPing v" VERSION "\r\n");
	sprintf(&request[strlen(request)], "Host: %s\r\n", hostname);
	if (referer)
		sprintf(&request[strlen(request)], "Referer: %s\r\n", referer);
	if (ask_compression)
		sprintf(&request[strlen(request)], "Accept-Encoding: gzip,deflate\r\n");
	strcat(request, "\r\n");
	req_len = strlen(request);

	if (!quiet && !machine_readable && !nagios_mode)
		printf("PING %s:%d (%s):\n", hostname, portnr, get);

	signal(SIGINT, handler);
	signal(SIGTERM, handler);

	timeout *= 1000;	/* change to ms */

	host = proxyhost?proxyhost:hostname;
	port = proxyhost?proxyport:portnr;

	if (resolve_once)
	{
		memset(&addr, 0x00, sizeof(addr));

		if (resolve_host(host, &addr) == -1)
		{
			err++;
			emit_error();
			have_resolved = 1;
		}
	}

	while((curncount < count || count == -1) && stop == 0)
	{
		double ms;
		double dstart, dend;
		char *reply;
		int Bps = 0;
		char is_compressed = 0;
		long long int bytes_transferred = 0;

		dstart = get_ts();

		for(;;)
		{
			char *fp = NULL;
			int rc;
			char *sc = NULL;

			curncount++;

			if (!resolve_once || (resolve_once == 1 && have_resolved == 0))
			{
				memset(&addr, 0x00, sizeof(addr));

				if (resolve_host(host, &addr) == -1)
				{
					err++;
					emit_error();
					break;
				}

				have_resolved = 1;
			}

			fd = connect_to(bind_to_valid?(struct sockaddr *)&bind_to:NULL, &addr, port, timeout);

			if (fd == -3)	/* ^C pressed */
				break;

			if (fd < 0)
				emit_error();

			if (fd >= 0)
			{
				/* set socket to low latency */
				if (set_tcp_low_latency(fd) == -1)
					break;

				/* set fd blocking */
				if (set_fd_blocking(fd) == -1)
					break;

#ifndef NO_SSL
				if (use_ssl)
				{
					int rc;

					rc = connect_ssl(fd, client_ctx, &ssl_h, &s_bio, timeout);
					if (rc != 0)
					{
						close(fd);
						fd = rc;
					}
				}
#endif
			}

			if (fd < 0)
			{
				if (fd == -2)
					snprintf(last_error, ERROR_BUFFER_SIZE, "timeout connecting to host\n");

				emit_error();

				err++;

				break;
			}

#ifndef NO_SSL
			if (use_ssl)
				rc = WRITE_SSL(ssl_h, request, req_len);
			else
#endif
				rc = mywrite(fd, request, req_len, timeout);
			if (rc != req_len)
			{
				if (rc == -1)
					snprintf(last_error, ERROR_BUFFER_SIZE, "error sending request to host\n");
				else if (rc == -2)
					snprintf(last_error, ERROR_BUFFER_SIZE, "timeout sending to host\n");
				else if (rc == -3)
				{/* ^C */}
				else if (rc == 0)
					snprintf(last_error, ERROR_BUFFER_SIZE, "connection prematurely closed by peer\n");

				emit_error();

				close(fd);
				err++;

				break;
			}

			rc = get_HTTP_headers(fd, ssl_h, &reply, timeout);

			if ((show_statuscodes || machine_readable) && reply != NULL)
			{
				/* statuscode is in first line behind
				 * 'HTTP/1.x'
				 */
				char *dummy = strchr(reply, ' ');

				if (dummy)
				{
					sc = strdup(dummy + 1);

					/* lines are normally terminated with a
					 * CR/LF
					 */
					dummy = strchr(sc, '\r');
					if (dummy)
						*dummy = 0x00;
					dummy = strchr(sc, '\n');
					if (dummy)
						*dummy = 0x00;
				}
			}

			if (ask_compression && reply != NULL)
			{
				char *encoding = strstr(reply, "\nContent-Encoding:");
				if (encoding)
				{
					char *dummy = strchr(encoding + 1, '\n');
					if (dummy) *dummy = 0x00;

					if (strstr(encoding, "gzip") == 0 ||
							strstr(encoding, "deflate") == 0)
					{
						is_compressed = 1;
					}
				}
			}

			free(reply);

			if (rc < 0)
			{
				if (rc == RC_SHORTREAD)
					snprintf(last_error, ERROR_BUFFER_SIZE, "error receiving reply from host\n");
				else if (rc == RC_TIMEOUT)
					snprintf(last_error, ERROR_BUFFER_SIZE, "timeout receiving reply from host\n");

				emit_error();

				close(fd);
				err++;

				break;
			}

			ok++;

			if (get_instead_of_head)
			{
				double dl_start = get_ts(), dl_end;

				for(;;)
				{
					int rc = read(fd, buffer, page_size);

					if (rc == -1)
					{
						if (errno != EINTR && errno != EAGAIN)
							error_exit("read failed");
					}
					else if (rc == 0)
						break;

					bytes_transferred += rc;

					if (Bps_limit != -1 && bytes_transferred >= Bps_limit)
						break;
				}

				dl_end = get_ts();

				Bps = bytes_transferred / max(dl_end - dl_start, 0.000001);
				Bps_min = min(Bps_min, Bps);
				Bps_max = max(Bps_max, Bps);
				Bps_avg += Bps;
			}

			dend = get_ts();

#ifndef NO_SSL
			if (use_ssl)
			{
				if (show_fp && ssl_h != NULL)
				{
					fp = get_fingerprint(ssl_h);
				}

				if (close_ssl_connection(ssl_h, fd) == -1)
				{
					snprintf(last_error, ERROR_BUFFER_SIZE, "error shutting down ssl\n");
					emit_error();
				}

				SSL_free(ssl_h);
			}
#endif
			close(fd);

			ms = (dend - dstart) * 1000.0;
			avg += ms;
			min = min > ms ? ms : min;
			max = max < ms ? ms : max;

			if (machine_readable)
			{
				if (sc)
				{
					char *dummy = strchr(sc, ' ');

					if (dummy) *dummy = 0x00;

					if (strstr(ok_str, sc))
						printf("%f", ms);
					else
						printf("%s", err_str);

					if (show_statuscodes)
						printf(" %s", sc);
				}
				else
				{
					printf("%s", err_str);
				}
				if(audible)
					putchar('\a');
				printf("\n");
			}
			else if (!quiet && !nagios_mode)
			{
				printf("connected to %s:%d, seq=%d time=%.2f ms %s", hostname, portnr, curncount-1, ms, sc?sc:"");

				if (show_Bps)
				{
					printf(" %dKB/s", Bps / 1024);
					if (show_bytes_xfer)
						printf(" %dKB", (int)(bytes_transferred / 1024));
					if (ask_compression)
					{
						printf(" (");
						if (!is_compressed)
							printf("not ");
						printf("compressed)");
					}
				}

				if (use_ssl && show_fp && fp != NULL)
				{
					printf(" %s", fp);
					free(fp);
				}
				if(audible)
					putchar('\a');
				printf("\n");
			}

			free(sc);

			break;
		}

		if (curncount != count && !stop)
			sleep(wait);
	}

	avg_httping_time = avg / (double)ok;

	if (!quiet && !machine_readable && !nagios_mode)
	{
		printf("--- %s ping statistics ---\n", get);
		if (count == -1)
			printf("%d connects, %d ok, %3.2f%% failed\n", curncount, ok, (((double)err) / ((double)curncount)) * 100.0);
		else
			printf("%d connects, %d ok, %3.2f%% failed\n", curncount, ok, (((double)err) / ((double)count)) * 100.0);

		if (ok > 0)
		{
			printf("round-trip min/avg/max = %.1f/%.1f/%.1f ms\n", min, avg_httping_time, max);

			if (show_Bps)
				printf("Transfer speed: min/avg/max = %d/%d/%d KB\n", Bps_min / 1024, (int)(Bps_avg / ok) / 1024, Bps_max / 1024);
		}
	}

error_exit:
	if (nagios_mode == 1)
	{
		if (ok == 0)
		{
			printf("CRITICAL - connecting failed: %s", last_error);
			return 2;
		}
		else if (avg_httping_time >= nagios_crit)
		{
			printf("CRITICAL - average httping-time is %.1f\n", avg_httping_time);
			return 2;
		}
		else if (avg_httping_time >= nagios_warn)
		{
			printf("WARNING - average httping-time is %.1f\n", avg_httping_time);
			return 1;
		}

		printf("OK - average httping-time is %.1f (%s)\n", avg_httping_time, last_error);
	}
	else if (nagios_mode == 2)
	{
		if (ok && last_error[0] == 0x00)
		{
			printf("OK - all fine, avg httping time is %.1f\n", avg_httping_time);
			return 0;
		}

		printf("%s: - failed: %s", nagios_exit_code == 1?"WARNING":(nagios_exit_code == 2?"CRITICAL":"ERROR"), last_error);
		return nagios_exit_code;
	}

	if (ok)
		return 0;
	else
		return 127;
}


syntax highlighted by Code2HTML, v. 0.9.1