/*
 * Soft:        Perform a GET query to a remote HTTP/HTTPS server.
 *              Set a timer to compute global remote server response
 *              time.
 *
 * Part:        Main entry point.
 *
 * Authors:     Alexandre Cassen, <acassen@linux-vs.org>
 *
 *              This program is distributed in the hope that it will be useful,
 *              but WITHOUT ANY WARRANTY; without even the implied warranty of
 *              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *              See the GNU General Public License for more details.
 *
 *              This program is free software; you can redistribute it and/or
 *              modify it under the terms of the GNU General Public License
 *              as published by the Free Software Foundation; either version
 *              2 of the License, or (at your option) any later version.
 *
 * Copyright (C) 2001-2017 Alexandre Cassen, <acassen@gmail.com>
 */

#include "config.h"

/* system includes */
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>

/* keepalived includes */
#include "utils.h"
#include "signals.h"
#include "git-commit.h"

/* genhash includes */
#include "include/main.h"


/* global var */
REQ *req = NULL;
int exit_code;

/* Terminate handler */
static void
sigend(__attribute__((unused)) void *v, __attribute__((unused)) int sig)
{
	/* register the terminate thread */
	thread_add_terminate_event(master);
}

/* Initialize signal handler */
static void
signal_init(void)
{
	signal_set(SIGHUP, sigend, NULL);
	signal_set(SIGINT, sigend, NULL);
	signal_set(SIGTERM, sigend, NULL);
	signal_ignore(SIGPIPE);
}

/* Usage function */
static void
usage(const char *prog)
{
	enum feat_hashes i;

	fprintf(stderr, "%s", VERSION_STRING);
#ifdef GIT_COMMIT
	fprintf(stderr, ", git commit %s", GIT_COMMIT);
#endif
	fprintf(stderr, "\n\n%s\n\n", COPYRIGHT_STRING);
	fprintf(stderr,
		"Usage: %s COMMAND [OPTIONS]\n"
		"Commands:\n"
		"   -s server-address -p port -u url\n"
		"   -S -s server-address -p port -u url\n"
		"   -h\n"
		"   -r\n\n", prog);
	fprintf(stderr,
		"Options:\n"
		"Either long or short options are allowed.\n"
		"   --use-ssl         -S       Use SSL connection to remote server.\n"
#ifdef _HAVE_SSL_SET_TLSEXT_HOST_NAME_
		"   --use-sni         -I       Use SNI during SSL handshake (uses virtualhost setting; see -V).\n"
#endif
		"   --server          -s       Use the specified remote server address.\n"
		"   --port            -p       Use the specified remote server port.\n"
		"   --url             -u       Use the specified remote server url.\n"
		"   --use-virtualhost -V       Use the specified virtualhost in GET query.\n"
		"   --hash            -H       Use the specified hash algorithm.\n"
		"   --verbose         -v       Use verbose mode output.\n"
		"   --help            -h       Display this short inlined help screen.\n"
		"   --release         -r       Display the release number.\n"
		"   --fwmark          -m       Use the specified FW mark.\n"
		"   --protocol        -P       Use the specified HTTP protocol - '1.0', 1.0c', '1.1'.\n"
		"                                1.0c means 1.0 with 'Connection: close'\n"
		"   --timeout         -t       Timeout in seconds\n");
	fprintf(stderr, "\nSupported hash algorithms:\n");
	for (i = hash_first; i < hash_guard; i++)
		fprintf(stderr, "  %s%s\n",
			hashes[i].id, i == hash_default ? " (default)": "");
}

/* Command line parser */
static int
parse_cmdline(int argc, char **argv, REQ * req_obj)
{
	int c;
	enum feat_hashes i;
	struct addrinfo hint, *res = NULL;
	int ret;
	void *ptr;
	char *endptr;
	long port_num;
	const char *start;

	memset(&hint, '\0', sizeof hint);

	hint.ai_family = PF_UNSPEC;
	hint.ai_flags = AI_NUMERICHOST;

	struct option long_options[] = {
		{"release",		no_argument,       0, 'r'},
		{"help",		no_argument,       0, 'h'},
		{"verbose",		no_argument,       0, 'v'},
		{"use-ssl",		no_argument,       0, 'S'},
#ifdef _HAVE_SSL_SET_TLSEXT_HOST_NAME_
		{"use-sni",		no_argument,       0, 'I'},
#endif
		{"server",		required_argument, 0, 's'},
		{"hash",		required_argument, 0, 'H'},
		{"use-virtualhost",	required_argument, 0, 'V'},
		{"port",		required_argument, 0, 'p'},
		{"url",			required_argument, 0, 'u'},
		{"fwmark",		required_argument, 0, 'm'},
		{"protocol",		required_argument, 0, 'P'},
		{"timeout",		required_argument, 0, 't'},
		{0, 0, 0, 0}
	};

	/* Parse the command line arguments */
	while ((c = getopt_long (argc, argv, "rhvSs:H:V:p:u:m:P:t:"
#ifdef _HAVE_SSL_SET_TLSEXT_HOST_NAME_
							       "I"
#endif
				  , long_options, NULL)) != EOF) {
		switch (c) {
		case 'r':
			fprintf(stderr, "%s", VERSION_STRING);
#ifdef GIT_COMMIT
			fprintf(stderr, ", git commit %s", GIT_COMMIT);
#endif
			fprintf(stderr, "\n");
			break;
		case 'h':
			usage(argv[0]);
			break;
		case 'v':
			req_obj->verbose = 1;
			break;
		case 'S':
			req_obj->ssl = 1;
			break;
#ifdef _HAVE_SSL_SET_TLSEXT_HOST_NAME_
		case 'I':
			req_obj->sni = 1;
			break;
#endif
		case 's':
			if ((ret = getaddrinfo(optarg, NULL, &hint, &res)) != 0){
				fprintf(stderr, "server should be an IP, not %s\n", optarg);
				return CMD_LINE_ERROR;
			} else {
				if(res->ai_family == AF_INET) {
					req_obj->dst = res;
					ptr = &((struct sockaddr_in *) res->ai_addr)->sin_addr;
					inet_ntop (res->ai_family, ptr, req_obj->ipaddress, INET_ADDRSTRLEN);
				} else if (res->ai_family == AF_INET6) {
					req_obj->dst = res;
					ptr = &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr;
					inet_ntop (res->ai_family, ptr, req_obj->ipaddress, INET6_ADDRSTRLEN);
				} else {
					fprintf(stderr, "server should be an IP, not %s\n", optarg);
					freeaddrinfo(res);
					return CMD_LINE_ERROR;
				}
			}
			break;
		case 'H':
			for (i = hash_first; i < hash_guard; i++)
				if (!strcasecmp(optarg, hashes[i].id)) {
					req_obj->hash = i;
					break;
				}
			if (i == hash_guard) {
				fprintf(stderr, "unknown hash algorithm: %s\n", optarg);
				return CMD_LINE_ERROR;
			}
			break;
		case 'V':
			req_obj->vhost = optarg;
			break;
		case 'p':
			port_num = strtol(optarg, &endptr, 10);
			if (*endptr || port_num <= 0 || port_num > 65535) {
				fprintf(stderr, "invalid port number '%s'\n", optarg);
				return CMD_LINE_ERROR;
			}
			req_obj->addr_port = htons(port_num);
			break;
		case 'u':
			req_obj->url = optarg;
			break;
		case 'm':
#ifdef _WITH_SO_MARK_
			start = optarg + strspn(optarg, " \t");
			req_obj->mark = (unsigned)strtoul(start, &endptr, 10);
			if (*endptr || start[0] == '-' || start[0] == ' ') {
				fprintf(stderr, "invalid fwmark '%s'\n", optarg);
				return CMD_LINE_ERROR;
			}
#else
			fprintf(stderr, "genhash built without fwmark support\n");
			return CMD_LINE_ERROR;
#endif
			break;
		case 'P':
			if (!strcmp(optarg, "1.0"))
				req_obj->http_protocol = HTTP_PROTOCOL_1_0;
			else if (!strcmp(optarg, "1.0c") || !strcmp(optarg, "1.0C"))
				req_obj->http_protocol = HTTP_PROTOCOL_1_0C;
			else if (!strcmp(optarg, "1.1"))
				req_obj->http_protocol = HTTP_PROTOCOL_1_1;
			/* 1.0k and 1.1k are for test purposes and are not expected to be used */
			else if (!strcmp(optarg, "1.0k"))
				req_obj->http_protocol = HTTP_PROTOCOL_1_0K;
			else if (!strcmp(optarg, "1.1k"))
				req_obj->http_protocol = HTTP_PROTOCOL_1_1K;
			else {
				fprintf(stderr, "invalid HTTP protocol version '%s'\n", optarg);
				return CMD_LINE_ERROR;
			}
			break;
		case 't':
			start = optarg + strspn(optarg, " \t");
			req_obj->timeout = (unsigned)strtoul(start, &endptr, 10);
			if (*endptr || start[0] == '-' || !start[0]) {
				fprintf(stderr, "invalid timeout '%s'\n", optarg);
				return CMD_LINE_ERROR;
			}
			req_obj->timeout *= TIMER_HZ;
			break;
		default:
			usage(argv[0]);
			return CMD_LINE_ERROR;
		}
	}

	/* check unexpected arguments */
	if (optind < argc) {
		fprintf(stderr, "Unexpected argument(s): ");
		while (optind < argc)
			printf("%s ", argv[optind++]);
		printf("\n");
		return CMD_LINE_ERROR;
	}

	return CMD_LINE_SUCCESS;
}

int
main(int argc, char **argv)
{
	const char *url_default = "/";

#ifdef _MEM_CHECK_
	mem_log_init("Genhash", "Genhash process");
	enable_mem_log_termination();
#endif

	/* Allocate the room */
	req = (REQ *) MALLOC(sizeof (REQ));

	/* Preset (potentially) non-zero defaults */
	req->hash = hash_default;
	req->http_protocol = HTTP_PROTOCOL_1_0;
	req->timeout = HTTP_CNX_TIMEOUT * TIMER_HZ;

	/* Command line parser */
	if (!parse_cmdline(argc, argv, req)) {
		FREE(req);
		exit(1);
	}

	/* Check minimum configuration need */
	if (!req->dst && !req->addr_port && !req->url) {
		freeaddrinfo(req->dst);
		FREE(req);
		exit(1);
	}

	if(!req->url)
		req->url = url_default;

	/* Init the reference timer */
	req->ref_time = timer_long(timer_now());
#ifdef _GENHASH_DEBUG_
	fprintf(stderr, "Reference timer = %lu\n", req->ref_time);
#endif

	/* Init SSL context */
	init_ssl();

	/* Create the master thread */
	master = thread_make_master();

	/* Signal handling initialization  */
	signal_init();

	/* Register the GET request */
	init_sock();

	/*
	 * Processing the master thread queues,
	 * return and execute one ready thread.
	 * Run until error, used for debuging only.
	 * Note that not calling launch_thread_scheduler()
	 * does not activate SIGCHLD handling, however,
	 * this is no issue here.
	 */
	process_threads(master);

	/* Finalize output informations */
	if (req->verbose)
		printf("Global response time for [%s] =%lu\n",
			    req->url, req->response_time - req->ref_time);

	/* exit cleanly */
	thread_destroy_master(master);
	SSL_CTX_free(req->ctx);
	free_sock(sock);
	freeaddrinfo(req->dst);
	FREE(req);
	exit(exit_code);
}
