/*
 *	LSCAN match for Xtables
 *	Copyright © Jan Engelhardt, 2006 - 2009
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License; either version
 *	2 or 3 as published by the Free Software Foundation.
 */
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/skbuff.h>
#include <linux/stat.h>
#include <linux/tcp.h>
#include <linux/types.h>
#include <linux/version.h>
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter/xt_tcpudp.h>
#include "xt_lscan.h"
#include "compat_xtables.h"
#define PFX KBUILD_MODNAME ": "

enum {
	TCP_FLAGS_ALL3 = TCP_FLAG_FIN | TCP_FLAG_RST | TCP_FLAG_SYN,
	TCP_FLAGS_ALL4 = TCP_FLAGS_ALL3 | TCP_FLAG_ACK,
	TCP_FLAGS_ALL6 = TCP_FLAGS_ALL4 | TCP_FLAG_PSH | TCP_FLAG_URG,
};

/* Module parameters */
static unsigned int
	connmark_mask = ~0,
	packet_mask   = ~0,
	mark_seen     = 0x9,
	mark_synrcv   = 0x1,
	mark_closed   = 0x2,
	mark_synscan  = 0x3,
	mark_estab1   = 0x4,
	mark_estab2   = 0x5,
	mark_cnscan   = 0x6,
	mark_grscan   = 0x7,
	mark_valid    = 0x8;

module_param(connmark_mask, uint, S_IRUGO | S_IWUSR);
module_param(packet_mask,   uint, S_IRUGO | S_IWUSR);
module_param(mark_seen,     uint, S_IRUGO | S_IWUSR);
module_param(mark_synrcv,   uint, S_IRUGO | S_IWUSR);
module_param(mark_closed,   uint, S_IRUGO | S_IWUSR);
module_param(mark_synscan,  uint, S_IRUGO | S_IWUSR);
module_param(mark_estab1,   uint, S_IRUGO | S_IWUSR);
module_param(mark_estab2,   uint, S_IRUGO | S_IWUSR);
module_param(mark_cnscan,   uint, S_IRUGO | S_IWUSR);
module_param(mark_grscan,   uint, S_IRUGO | S_IWUSR);
module_param(mark_valid,    uint, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(connmark_mask, "only set specified bits in connection mark");
MODULE_PARM_DESC(packet_mask,   "only set specified bits in packet mark");
MODULE_PARM_DESC(mark_seen,     "nfmark value for packet-seen state");
MODULE_PARM_DESC(mark_synrcv,   "connmark value for SYN Received state");
MODULE_PARM_DESC(mark_closed,   "connmark value for closed state");
MODULE_PARM_DESC(mark_synscan,  "connmark value for SYN Scan state");
MODULE_PARM_DESC(mark_estab1,   "connmark value for Established-1 state");
MODULE_PARM_DESC(mark_estab2,   "connmark value for Established-2 state");
MODULE_PARM_DESC(mark_cnscan,   "connmark value for Connect Scan state");
MODULE_PARM_DESC(mark_grscan,   "connmark value for Grab Scan state");
MODULE_PARM_DESC(mark_valid,    "connmark value for Valid state");

/* TCP flag functions */
static inline bool tflg_ack4(const struct tcphdr *th)
{
	return (tcp_flag_word(th) & TCP_FLAGS_ALL4) == TCP_FLAG_ACK;
}

static inline bool tflg_ack6(const struct tcphdr *th)
{
	return (tcp_flag_word(th) & TCP_FLAGS_ALL6) == TCP_FLAG_ACK;
}

static inline bool tflg_fin(const struct tcphdr *th)
{
	return (tcp_flag_word(th) & TCP_FLAGS_ALL3) == TCP_FLAG_FIN;
}

static inline bool tflg_rst(const struct tcphdr *th)
{
	return (tcp_flag_word(th) & TCP_FLAGS_ALL3) == TCP_FLAG_RST;
}

static inline bool tflg_rstack(const struct tcphdr *th)
{
	return (tcp_flag_word(th) & TCP_FLAGS_ALL4) ==
	       (TCP_FLAG_ACK | TCP_FLAG_RST);
}

static inline bool tflg_syn(const struct tcphdr *th)
{
	return (tcp_flag_word(th) & TCP_FLAGS_ALL4) == TCP_FLAG_SYN;
}

static inline bool tflg_synack(const struct tcphdr *th)
{
	return (tcp_flag_word(th) & TCP_FLAGS_ALL4) ==
	       (TCP_FLAG_SYN | TCP_FLAG_ACK);
}

/* lscan functions */
static inline bool lscan_mt_stealth(const struct tcphdr *th)
{
	/*
	 * "Connection refused" replies to our own probes must not be matched.
	 */
	if (tflg_rstack(th))
		return false;

	if (tflg_rst(th) && printk_ratelimit()) {
		printk(KERN_WARNING PFX "Warning: Pure RST received\n");
		return false;
	}

	/*
	 * -p tcp ! --syn -m conntrack --ctstate INVALID: Looking for non-start
	 * packets that are not associated with any connection -- this will
	 * match most scan types (NULL, XMAS, FIN) and ridiculous flag
	 * combinations (SYN-RST, SYN-FIN, SYN-FIN-RST, FIN-RST, etc.).
	 */
	return !tflg_syn(th);
}

static inline unsigned int lscan_mt_full(int mark,
    enum ip_conntrack_info ctstate, bool loopback, const struct tcphdr *tcph,
    unsigned int payload_len)
{
	if (mark == mark_estab2) {
		/*
		 * -m connmark --mark $ESTAB2
		 */
		if (tflg_ack4(tcph) && payload_len == 0)
			return mark; /* keep mark */
		else if (tflg_rst(tcph) || tflg_fin(tcph))
			return mark_grscan;
		else
			return mark_valid;
	} else if (mark == mark_estab1) {
		/*
		 * -m connmark --mark $ESTAB1
		 */
		if (tflg_rst(tcph) || tflg_fin(tcph))
			return mark_cnscan;
		else if (!loopback && tflg_ack4(tcph) && payload_len == 0)
			return mark_estab2;
		else
			return mark_valid;
	} else if (mark == mark_synrcv) {
		/*
		 * -m connmark --mark $SYN
		 */
		if (loopback && tflg_synack(tcph))
			return mark; /* keep mark */
		else if (loopback && tflg_rstack(tcph))
			return mark_closed;
		else if (tflg_ack6(tcph))
			return mark_estab1;
		else
			return mark_synscan;
	} else if (ctstate == IP_CT_NEW && tflg_syn(tcph)) {
		/*
		 * -p tcp --syn --ctstate NEW
		 */
		return mark_synrcv;
	}
	return mark;
}

static bool
lscan_mt(const struct sk_buff *skb, struct xt_action_param *par)
{
	const struct xt_lscan_mtinfo *info = par->matchinfo;
	enum ip_conntrack_info ctstate;
	const struct tcphdr *tcph;
	struct nf_conn *ctdata;
	struct tcphdr tcph_buf;

	tcph = skb_header_pointer(skb, par->thoff, sizeof(tcph_buf), &tcph_buf);
	if (tcph == NULL)
		return false;

	/* Check for invalid packets: -m conntrack --ctstate INVALID */
	if ((ctdata = nf_ct_get(skb, &ctstate)) == NULL) {
		if (info->match_stealth)
			return lscan_mt_stealth(tcph);
		/*
		 * If @ctdata is NULL, we cannot match the other scan
		 * types, return.
		 */
		return false;
	}

	/*
	 * If -m lscan was previously applied to this packet, the rules we
	 * simulate must not be run through again. And for speedup, do not call
	 * it either when the connection is already VALID.
	 */
	if ((ctdata->mark & connmark_mask) == mark_valid ||
	     (skb_nfmark(skb) & packet_mask) != mark_seen) {
		unsigned int n;

		n = lscan_mt_full(ctdata->mark & connmark_mask, ctstate,
		    par->state->in == init_net.loopback_dev, tcph,
		    skb->len - par->thoff - 4 * tcph->doff);

		ctdata->mark = (ctdata->mark & ~connmark_mask) | n;
		skb_nfmark(skb) = (skb_nfmark(skb) & ~packet_mask) ^ mark_seen;
	}

	return (info->match_syn && ctdata->mark == mark_synscan) ||
	       (info->match_cn && ctdata->mark == mark_cnscan) ||
	       (info->match_gr && ctdata->mark == mark_grscan);
}

static int lscan_mt_check(const struct xt_mtchk_param *par)
{
	const struct xt_lscan_mtinfo *info = par->matchinfo;

	if ((info->match_stealth & ~1) || (info->match_syn & ~1) ||
	    (info->match_cn & ~1) || (info->match_gr & ~1)) {
		printk(KERN_WARNING PFX "Invalid flags\n");
		return -EINVAL;
	}
	return 0;
}

static struct xt_match lscan_mt_reg[] __read_mostly = {
	{
		.name       = "lscan",
		.revision   = 0,
		.family     = NFPROTO_IPV4,
		.match      = lscan_mt,
		.checkentry = lscan_mt_check,
		.matchsize  = sizeof(struct xt_lscan_mtinfo),
		.proto      = IPPROTO_TCP,
		.me         = THIS_MODULE,
	},
	{
		.name       = "lscan",
		.revision   = 0,
		.family     = NFPROTO_IPV6,
		.match      = lscan_mt,
		.checkentry = lscan_mt_check,
		.matchsize  = sizeof(struct xt_lscan_mtinfo),
		.proto      = IPPROTO_TCP,
		.me         = THIS_MODULE,
	},
};

static int __init lscan_mt_init(void)
{
	return xt_register_matches(lscan_mt_reg,
	       ARRAY_SIZE(lscan_mt_reg));
}

static void __exit lscan_mt_exit(void)
{
	xt_unregister_matches(lscan_mt_reg, ARRAY_SIZE(lscan_mt_reg));
}

module_init(lscan_mt_init);
module_exit(lscan_mt_exit);
MODULE_AUTHOR("Jan Engelhardt ");
MODULE_DESCRIPTION("Xtables: Low-level scan (e.g. nmap) match");
MODULE_LICENSE("GPL");
MODULE_ALIAS("ipt_lscan");
MODULE_ALIAS("ip6t_lscan");
