/*
 * Shared library add-on to iptables to add early socket matching support.
 *
 * Copyright (C) 2007 BalaBit IT Ltd.
 */
#include <stdio.h>
#include <xtables.h>
#include <linux/netfilter/xt_socket.h>

enum {
	O_TRANSPARENT = 0,
	O_NOWILDCARD = 1,
	O_RESTORESKMARK = 2,
};

static const struct xt_option_entry socket_mt_opts[] = {
	{.name = "transparent", .id = O_TRANSPARENT, .type = XTTYPE_NONE},
	XTOPT_TABLEEND,
};

static const struct xt_option_entry socket_mt_opts_v2[] = {
	{.name = "transparent", .id = O_TRANSPARENT, .type = XTTYPE_NONE},
	{.name = "nowildcard", .id = O_NOWILDCARD, .type = XTTYPE_NONE},
	XTOPT_TABLEEND,
};

static const struct xt_option_entry socket_mt_opts_v3[] = {
	{.name = "transparent", .id = O_TRANSPARENT, .type = XTTYPE_NONE},
	{.name = "nowildcard", .id = O_NOWILDCARD, .type = XTTYPE_NONE},
	{.name = "restore-skmark", .id = O_RESTORESKMARK, .type = XTTYPE_NONE},
	XTOPT_TABLEEND,
};

static void socket_mt_help(void)
{
	printf(
		"socket match options:\n"
		"  --transparent    Ignore non-transparent sockets\n\n");
}

static void socket_mt_help_v2(void)
{
	printf(
		"socket match options:\n"
		"  --nowildcard     Do not ignore LISTEN sockets bound on INADDR_ANY\n"
		"  --transparent    Ignore non-transparent sockets\n\n");
}

static void socket_mt_help_v3(void)
{
	printf(
		"socket match options:\n"
		"  --nowildcard     Do not ignore LISTEN sockets bound on INADDR_ANY\n"
		"  --transparent    Ignore non-transparent sockets\n"
		"  --restore-skmark Set the packet mark to the socket mark if\n"
		"                   the socket matches and transparent / \n"
		"                   nowildcard conditions are satisfied\n\n");
}

static void socket_mt_parse(struct xt_option_call *cb)
{
	struct xt_socket_mtinfo1 *info = cb->data;

	xtables_option_parse(cb);
	switch (cb->entry->id) {
	case O_TRANSPARENT:
		info->flags |= XT_SOCKET_TRANSPARENT;
		break;
	}
}

static void socket_mt_parse_v2(struct xt_option_call *cb)
{
	struct xt_socket_mtinfo2 *info = cb->data;

	xtables_option_parse(cb);
	switch (cb->entry->id) {
	case O_TRANSPARENT:
		info->flags |= XT_SOCKET_TRANSPARENT;
		break;
	case O_NOWILDCARD:
		info->flags |= XT_SOCKET_NOWILDCARD;
		break;
	}
}

static void socket_mt_parse_v3(struct xt_option_call *cb)
{
	struct xt_socket_mtinfo2 *info = cb->data;

	xtables_option_parse(cb);
	switch (cb->entry->id) {
	case O_TRANSPARENT:
		info->flags |= XT_SOCKET_TRANSPARENT;
		break;
	case O_NOWILDCARD:
		info->flags |= XT_SOCKET_NOWILDCARD;
		break;
	case O_RESTORESKMARK:
		info->flags |= XT_SOCKET_RESTORESKMARK;
		break;
	}
}

static void
socket_mt_save(const void *ip, const struct xt_entry_match *match)
{
	const struct xt_socket_mtinfo1 *info = (const void *)match->data;

	if (info->flags & XT_SOCKET_TRANSPARENT)
		printf(" --transparent");
}

static void
socket_mt_print(const void *ip, const struct xt_entry_match *match,
		int numeric)
{
	printf(" socket");
	socket_mt_save(ip, match);
}

static void
socket_mt_save_v2(const void *ip, const struct xt_entry_match *match)
{
	const struct xt_socket_mtinfo2 *info = (const void *)match->data;

	if (info->flags & XT_SOCKET_TRANSPARENT)
		printf(" --transparent");
	if (info->flags & XT_SOCKET_NOWILDCARD)
		printf(" --nowildcard");
}

static void
socket_mt_print_v2(const void *ip, const struct xt_entry_match *match,
		   int numeric)
{
	printf(" socket");
	socket_mt_save_v2(ip, match);
}

static void
socket_mt_save_v3(const void *ip, const struct xt_entry_match *match)
{
	const struct xt_socket_mtinfo3 *info = (const void *)match->data;

	if (info->flags & XT_SOCKET_TRANSPARENT)
		printf(" --transparent");
	if (info->flags & XT_SOCKET_NOWILDCARD)
		printf(" --nowildcard");
	if (info->flags & XT_SOCKET_RESTORESKMARK)
		printf(" --restore-skmark");
}

static void
socket_mt_print_v3(const void *ip, const struct xt_entry_match *match,
		   int numeric)
{
	printf(" socket");
	socket_mt_save_v3(ip, match);
}

static struct xtables_match socket_mt_reg[] = {
	{
		.name          = "socket",
		.revision      = 0,
		.family        = NFPROTO_IPV4,
		.version       = XTABLES_VERSION,
		.size          = XT_ALIGN(0),
		.userspacesize = XT_ALIGN(0),
	},
	{
		.name          = "socket",
		.revision      = 1,
		.family        = NFPROTO_UNSPEC,
		.version       = XTABLES_VERSION,
		.size          = XT_ALIGN(sizeof(struct xt_socket_mtinfo1)),
		.userspacesize = XT_ALIGN(sizeof(struct xt_socket_mtinfo1)),
		.help          = socket_mt_help,
		.print         = socket_mt_print,
		.save          = socket_mt_save,
		.x6_parse      = socket_mt_parse,
		.x6_options    = socket_mt_opts,
	},
	{
		.name          = "socket",
		.revision      = 2,
		.family        = NFPROTO_UNSPEC,
		.version       = XTABLES_VERSION,
		.size          = XT_ALIGN(sizeof(struct xt_socket_mtinfo2)),
		.userspacesize = XT_ALIGN(sizeof(struct xt_socket_mtinfo2)),
		.help          = socket_mt_help_v2,
		.print         = socket_mt_print_v2,
		.save          = socket_mt_save_v2,
		.x6_parse      = socket_mt_parse_v2,
		.x6_options    = socket_mt_opts_v2,
	},
	{
		.name          = "socket",
		.revision      = 3,
		.family        = NFPROTO_UNSPEC,
		.version       = XTABLES_VERSION,
		.size          = XT_ALIGN(sizeof(struct xt_socket_mtinfo2)),
		.userspacesize = XT_ALIGN(sizeof(struct xt_socket_mtinfo2)),
		.help          = socket_mt_help_v3,
		.print         = socket_mt_print_v3,
		.save          = socket_mt_save_v3,
		.x6_parse      = socket_mt_parse_v3,
		.x6_options    = socket_mt_opts_v3,
	},
};

void _init(void)
{
	xtables_register_matches(socket_mt_reg, ARRAY_SIZE(socket_mt_reg));
}
