/* SPDX-License-Identifier: GPL-3.0-or-later */

#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <gnutls/gnutls.h>
#include "tls-proxy.h"

static char default_local_addr[] = "127.0.0.1";
static char default_upstream_addr[] = "127.0.0.1";
static char default_cert_path[] = "../certs/tt.cert.pem";
static char default_key_path[] = "../certs/tt.key.pem";

void help(char *argv[], struct args *a)
{
	printf("Usage: %s [parameters] [rundir]\n", argv[0]);
	printf("\nParameters:\n"
	       " -l, --local=[addr]     Server address to bind to (default: %s).\n"
	       " -p, --lport=[port]     Server port to bind to (default: %u).\n"
	       " -u, --upstream=[addr]  Upstream address (default: %s).\n"
	       " -d, --uport=[port]     Upstream port (default: %u).\n"
	       " -t, --cert=[path]      Path to certificate file (default: %s).\n"
	       " -k, --key=[path]       Path to key file (default: %s).\n"
	       " -c, --close=[N]        Close connection to client after\n"
	       "                        every N ms (default: %" PRIu64 ").\n"
	       " -f, --fail=[N]         Delay every Nth incoming connection by 10 sec,\n"
	       "                        0 disables delaying (default: 0).\n"
	       " -r, --rehandshake      Do TLS rehandshake after every 8 bytes\n"
	       "                        sent to the client (default: no).\n"
	       " -a, --acceptonly       Accept incoming connections, but don't\n"
	       "                        connect to upstream (default: no).\n"
	       " -v, --tls13            Force use of TLSv1.3. If not turned on,\n"
	       "                        TLSv1.2 will be used (default: no).\n"
	       ,
	       a->local_addr, a->local_port,
	       a->upstream, a->upstream_port,
	       a->cert_file, a->key_file,
	       a->close_timeout);
}

void init_args(struct args *a)
{
	a->local_addr = default_local_addr;
	a->local_port = 54000;
	a->upstream = default_upstream_addr;
	a->upstream_port = 53000;
	a->cert_file = default_cert_path;
	a->key_file = default_key_path;
	a->rehandshake = false;
	a->accept_only = false;
	a->tls_13 = false;
	a->close_connection = false;
	a->close_timeout = 1000;
	a->max_conn_sequence = 0; /* disabled */
}

int main(int argc, char **argv)
{
	long int li_value = 0;
	int c = 0, li = 0;
	struct option opts[] = {
		{"local",       required_argument, 0, 'l'},
		{"lport",       required_argument, 0, 'p'},
		{"upstream",    required_argument, 0, 'u'},
		{"uport",       required_argument, 0, 'd'},
		{"cert",        required_argument, 0, 't'},
		{"key",         required_argument, 0, 'k'},
		{"close",       required_argument, 0, 'c'},
		{"fail",        required_argument, 0, 'f'},
		{"rehandshake", no_argument, 0, 'r'},
		{"acceptonly",  no_argument, 0, 'a'},
#if GNUTLS_VERSION_NUMBER >= 0x030604
		{"tls13",       no_argument, 0, 'v'},
#endif
		{0, 0, 0, 0}
	};
	struct args args;
	init_args(&args);
	while ((c = getopt_long(argc, argv, "l:p:u:d:t:k:c:f:rav", opts, &li)) != -1) {
		switch (c)
		{
		case 'l':
			args.local_addr = optarg;
			break;
		case 'u':
			args.upstream = optarg;
			break;
		case 't':
			args.cert_file = optarg;
			break;
		case 'k':
			args.key_file = optarg;
			break;
		case 'p':
			li_value = strtol(optarg, NULL, 10);
			if (li_value <= 0 || li_value > UINT16_MAX) {
				printf("error: '-p' requires a positive"
						" number less or equal to 65535, not '%s'\n", optarg);
				return -1;
			}
			args.local_port = (uint16_t)li_value;
			break;
		case 'd':
			li_value = strtol(optarg, NULL, 10);
			if (li_value <= 0 || li_value > UINT16_MAX) {
				printf("error: '-d' requires a positive"
						" number less or equal to 65535, not '%s'\n", optarg);
				return -1;
			}
			args.upstream_port = (uint16_t)li_value;
			break;
		case 'c':
			li_value = strtol(optarg, NULL, 10);
			if (li_value <= 0) {
				printf("[system] error '-c' requires a positive"
						" number, not '%s'\n", optarg);
				return -1;
			}
			args.close_connection = true;
			args.close_timeout = li_value;
			break;
		case 'f':
			li_value = strtol(optarg, NULL, 10);
			if (li_value <= 0 || li_value > UINT32_MAX) {
				printf("error: '-f' requires a positive"
						" number less or equal to %i, not '%s'\n",
					        UINT32_MAX, optarg);
				return -1;
			}
			args.max_conn_sequence = (uint32_t)li_value;
			break;
		case 'r':
			args.rehandshake = true;
			break;
		case 'a':
			args.accept_only = true;
			break;
		case 'v':
#if GNUTLS_VERSION_NUMBER >= 0x030604
			args.tls_13 = true;
#endif
			break;
		default:
			init_args(&args);
			help(argv, &args);
			return -1;
		}
	}
	if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
		fprintf(stderr, "failed to set up SIGPIPE handler to ignore(%s)\n",
				strerror(errno));
	}
	struct tls_proxy_ctx *proxy = tls_proxy_allocate();
	if (!proxy) {
		fprintf(stderr, "can't allocate tls_proxy structure\n");
		return 1;
	}
	int res = tls_proxy_init(proxy, &args);
	if (res) {
		fprintf(stderr, "can't initialize tls_proxy structure\n");
		return res;
	}
	res = tls_proxy_start_listen(proxy);
	if (res) {
		fprintf(stderr, "error starting listen, error code: %i\n", res);
		return res;
	}
	fprintf(stdout, "Listen on                     %s#%u\n"
			"Upstream is expected on       %s#%u\n"
			"Certificate file              %s\n"
			"Key file                      %s\n"
			"Rehandshake                   %s\n"
			"Close                         %s\n"
			"Refuse incoming connections   every %ith%s\n"
			"Only accept, don't forward    %s\n"
			"Force TLSv1.3                 %s\n"
		        ,
			args.local_addr, args.local_port,
			args.upstream, args.upstream_port,
			args.cert_file, args.key_file,
			args.rehandshake ? "yes" : "no",
			args.close_connection ? "yes" : "no",
			args.max_conn_sequence, args.max_conn_sequence ? "" : " (disabled)",
			args.accept_only ? "yes" : "no",
#if GNUTLS_VERSION_NUMBER >= 0x030604
			args.tls_13 ? "yes" : "no"
#else
			"Not supported"
#endif
		);
	res = tls_proxy_run(proxy);
	tls_proxy_free(proxy);
	return res;
}

