/* Shared library add-on to iptables to add portknocking matching support. */
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <iptables.h>

#include <linux/netfilter_ipv4/ipt_portkng.h>

/* Function which prints out usage message. */
static void
help(void)
{
	printf(
"portkng v%s options:\n"
"      --knock port[,port,port...]\n",
IPTABLES_VERSION);
}

static struct option opts[] = {
	{ "knock", 1, 0, '1' },
	{ 0 }
};

/* based on libipt_multiport.c */
static unsigned int
parse_multi_ports(const char *portstring, u_int16_t *ports, const char *proto)
{
	char *buffer, *cp, *next;
	unsigned int i;

	buffer = strdup(portstring);
	if (!buffer) exit_error(OTHER_PROBLEM, "strdup failed");

	for (cp=buffer, i=0; cp && i<IPT_PORTKNG_KNOCKS; cp=next,i++)
	{
		next=strchr(cp, ',');
		if (next) *next++='\0';
		ports[i] = parse_port(cp, proto);
	}
	if (cp) exit_error(PARAMETER_PROBLEM, "too many ports specified");
	free(buffer);
	return i;
}

/* based on libipt_multiport.c */
static char *
proto_to_name(u_int8_t proto)
{
	switch (proto) {
	case IPPROTO_TCP:
		return "tcp";
	case IPPROTO_UDP:
		return "udp";
	case IPPROTO_UDPLITE:
		return "udplite";
	case IPPROTO_SCTP:
		return "sctp";
	case IPPROTO_DCCP:
		return "dccp";
	default:
		return NULL;
	}
}

/* based on libipt_multiport.c */
static const char *
check_proto(const struct ipt_entry *entry)
{
	char *proto;

	if (entry->ip.invflags & IPT_INV_PROTO)
		exit_error(PARAMETER_PROBLEM,
			   "portkng only works with TCP");

	if ((proto = proto_to_name(entry->ip.proto)) != NULL)
		return proto;
	else if (!entry->ip.proto)
		exit_error(PARAMETER_PROBLEM,
			   "portkng needs `-p tcp'");
	else
		exit_error(PARAMETER_PROBLEM,
			   "portkng only works with TCP");
}

/* Function which parses command options; returns true if it
   ate an option */
static int
parse(int c, char **argv, int invert, unsigned int *flags,
      const struct ipt_entry *entry,
		unsigned int *nfcache,
      struct ipt_entry_match **match)
{
	struct ipt_portkng_info *info = (struct ipt_portkng_info *)(*match)->data;
	const char *proto;

	switch (c)
	{
	case '1':
		if (invert) 
			exit_error(PARAMETER_PROBLEM,
			           "portkng: unexpected `!' with --knock");
		
		proto = check_proto(entry);
		info->count = parse_multi_ports(argv[optind-1],
		                                info->ports, proto);

		break;
	default:
		return 0;
	}
	return 1;
}

static void
final_check(unsigned int flags)
{
	/* All very fine, lets trust user.. */
}

/* Prints out the matchinfo. */
static void
print(const struct ipt_ip *ip,
      const struct ipt_entry_match *match,
      int numeric)
{
	struct ipt_portkng_info *info = ((struct ipt_portkng_info *)match->data);
	int i;

	printf(" portkng on ");
	for(i=0; i<info->count; i++){
		if(i>0) printf(",");
		printf("%d", info->ports[i]);	
	}
	printf(" allow ");

}

/* Saves the data in parsable form to stdout. */
static void
save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
{
	struct ipt_portkng_info *info = ((struct ipt_portkng_info *)match->data);
	
	if(!info->count) printf("");
}

static struct iptables_match portkng_struct = { 
	.next		= NULL,
	.name		= "portkng",
	.version	= IPTABLES_VERSION,
	.size		= IPT_ALIGN(sizeof(struct ipt_portkng_info)),
	.userspacesize	= IPT_ALIGN(sizeof(struct ipt_portkng_info)),
	.help		= &help,
	.parse		= &parse,
	.final_check	= &final_check,
	.print		= &print,
	.save		= &save,
	.extra_opts	= opts
};

void _init(void)
{
	register_match(&portkng_struct);
}
