/*  Stealth module by Derek Callaway <super@innu.org> -- S@IRC
 *  Original patch by Sean Trifero <sean@innu.org> -- solar@IRC 
 *  $Id: stealth.c,v 1.3 2000/03/22 15:04:44 super Exp $
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/proc_fs.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/firewall.h>
#include <linux/ip_fw.h>
#include <asm/uaccess.h>
#ifdef __SMP__
#define SLOT_NUMBER() (cpu_number_map[smp_processor_id()]*2 + !in_interrupt())
#else
#define SLOT_NUMBER() (!in_interrupt())
#endif
#define STEALTH_INPUT_CHAIN stealth_chains
#define STEALTH_FORWARD_CHAIN (stealth_chains->next)
#define STEALTH_OUTPUT_CHAIN (stealth_chains->next->next)

/* kernel space variables */
static char kmsg[3][3] = { "1\n\0", "1\n\0", "1\n\0" };
static char *tcp_ignore_ack = kmsg[0];
static char *tcp_ignore_bogus = kmsg[1];
static char *tcp_ignore_synfin = kmsg[2];

/* structures from linux/net/ipv4/ip_fw.c */

/* Head of linked list of fw rules */
struct ip_counters
{
  __u64 pcnt, bcnt;            /* Packet and byte counters */
};

struct ip_reent
{
  struct ip_chain *prevchain;  /* Pointer to referencing chain */
  struct ip_fwkernel *prevrule;        /* Pointer to referencing rule */
  struct ip_counters counters;
};

struct ip_chain
{
  ip_chainlabel label;         /* Defines the label for each block */
  struct ip_chain *next;       /* Pointer to next block */
  struct ip_fwkernel *chain;   /* Pointer to first rule in block */
  __u32 refcount;              /* Number of refernces to block */
  int policy;                  /* Default rule for chain.  Only *
                                * used in built in chains */
  struct ip_reent reent[0];    /* Actually several of these */
};

static struct ip_chain *stealth_chains;

int stealth_check (struct iphdr *, const char *, __u16 *, struct ip_chain *,
                  struct sk_buff *, unsigned int, int);

int
stealth_input_check (struct firewall_ops *this, int pf, struct device *dev,
                    void *phdr, void *arg, struct sk_buff **pskb)
{
  return (stealth_check (phdr, dev->name,
                        arg, STEALTH_INPUT_CHAIN, *pskb, SLOT_NUMBER (), 0));
}

int
stealth_output_check (struct firewall_ops *this, int pf, struct device *dev,
                     void *phdr, void *arg, struct sk_buff **pksb)
{
  return (FW_SKIP);
}

int
stealth_forward_check (struct firewall_ops *this, int pf, struct device *dev,
                      void *phdr, void *arg, struct sk_buff **pksb)
{
  return (FW_SKIP);
}

struct firewall_ops ipfw_ops = {
  NULL,
  stealth_output_check,
  stealth_input_check,
  stealth_forward_check,
  PF_INET,
  31337                                /* This priority should be adequate. */
};

int
stealth_check (struct iphdr *ip,
              const char *rif,
              __u16 * redirport,
              struct ip_chain *chain,
              struct sk_buff *skb, unsigned int slot, int testing)
{
  struct tcphdr *tcp;
  if (ip->protocol != IPPROTO_TCP)
    {
      /* Not a TCP packet, don't worry about it. */
      return (FW_SKIP);
    }
  tcp = (struct tcphdr *) ((__u32 *) ip + ip->ihl);
  /* Check for those nasty unused bit fields. */
  if ((*tcp_ignore_bogus != '0') && (tcp->res1))
    {
      /* I know goto statement should be avoided but I
       * seriously doubt that this will evolve into
       * spaghetti code. Furthermore, these jumps are
       * extremely localized. */
      goto tcp_bad_flags;
    }
  switch (tcp->fin)
    {
    case 0:
      goto done;
    default:
      if (((*tcp_ignore_synfin != '0') && (tcp->syn))
         || ((*tcp_ignore_ack != '0') && (tcp->psh) && (tcp->urg)))
       {
         goto tcp_bad_flags;
       }
    }
  if ((*tcp_ignore_bogus != '0') && (!(tcp->ack || tcp->syn || tcp->rst)))
    {
      goto tcp_bad_flags;
    }
  goto done;
tcp_bad_flags:
#ifdef CONFIG_TCPIP_STACK_LOG
  printk (KERN_INFO
         "Packet log: badflag DENY %s PROTO=TCP %d.%d.%d.%d:%d "
         "%d.%d.%d.%d:%d L=%hu:%u:%u S=0x%2.2hX I=%hu:%u:%u "
         "T=%hu %c%c%c%c%c%c%c%c\n",
         skb->dev->name, NIPQUAD (skb->nh.iph->saddr),
         ntohs (skb->h.th->source), NIPQUAD (skb->nh.iph->daddr),
         ntohs (skb->h.th->dest), ntohs (skb->nh.iph->tot_len), skb->len,
         skb->len - skb->h.th->doff * 4, skb->nh.iph->tos,
         ntohs (skb->nh.iph->id), ntohl (skb->h.th->seq),
         ntohl (skb->h.th->ack_seq), skb->nh.iph->ttl,
         skb->h.th->res1 ? '1' : '.', skb->h.th->res2 ? '2' : '.',
         skb->h.th->ack ? 'A' : '.', skb->h.th->syn ? 'S' : '.',
         skb->h.th->fin ? 'F' : '.', skb->h.th->rst ? 'R' : '.',
         skb->h.th->psh ? 'P' : '.', skb->h.th->urg ? 'U' : '.');
}
#endif
/* FW_BLOCK acts like ipchains -j DENY */
return (FW_BLOCK);
done:
/* FW_SKIP allows the next chain to evaluate the traffic. */
return (FW_SKIP);
}

static ssize_t
module_output (struct file *file, char *buf, size_t len, loff_t * offset)
{
  static unsigned short done = 0;
  unsigned short x = 0;
  /* user land storage */
  char umsg[2] = { 0 };
  switch (file->f_dentry->d_name.name[11])
    {
    case 'a':
      x = 0;
      break;
    case 'b':
      x = 1;
      break;
    case 's':
      x = 2;
    }
  if (done)
    return (done = 0);
  umsg[0] = kmsg[x][0], umsg[1] = '\n';
  put_user (umsg[0], buf);
  put_user (umsg[1], buf + 1);
  put_user (umsg[2], buf + 2);
  return (done = 2);
}

static ssize_t
module_input (struct file *file, const char *buf, size_t length,
             loff_t * offset)
{
  unsigned short x = 0;
  switch (file->f_dentry->d_name.name[11])
    {
    case 'a':
      x = 0;
      break;
    case 'b':
      x = 1;
      break;
    case 's':
      x = 2;
    }
  get_user (kmsg[x][0], buf);
  kmsg[x][1] = 0;
  return (2);
}

static int
module_permission (struct inode *inode, int op)
{
  if (op == 4 || (op == 2 && current->euid == 0))
    return (0);
  return (-EACCES);
}

int
module_open (struct inode *inode, struct file *file)
{
  MOD_INC_USE_COUNT;
  return (0);
}

int
module_close (struct inode *inode, struct file *file)
{
  MOD_DEC_USE_COUNT;
  return (0);
}

/* <linux/fs.h> */

static struct file_operations fops =
  { NULL, module_output, module_input, NULL, NULL, NULL, NULL, module_open,
    NULL, module_close };
static struct inode_operations iops =
  { &fops, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, module_permission };

/* <linux/proc_fs.h> */

static struct proc_dir_entry bogus =
  { 0, 16, "tcp_ignore_bogus", S_IFREG | S_IRUGO | S_IWUSR, 1, 0, 0, 0, &iops,
    NULL };
static struct proc_dir_entry synfin =
  { 0, 17, "tcp_ignore_synfin", S_IFREG | S_IRUGO | S_IWUSR, 1, 0, 0, 0,
    &iops, NULL };
static struct proc_dir_entry ack =
  { 0, 14, "tcp_ignore_ack", S_IFREG | S_IRUGO | S_IWUSR, 1, 0, 0, 0, &iops,
    NULL };

int
init_module (void)
{
  proc_net_register (&bogus);
  proc_net_register (&synfin);
  proc_net_register (&ack);
  register_firewall (PF_INET, &ipfw_ops);
  return (0);
}

void
cleanup_module (void)
{
  proc_net_unregister (bogus.low_ino);
  proc_net_unregister (synfin.low_ino);
  proc_net_unregister (ack.low_ino);
  unregister_firewall (PF_INET, &ipfw_ops);
}