
/* Copyright (C) Gerhard Fuernkranz 1992 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/errno.h>
#include <sys/signal.h>
#ifdef SYSV
#include <sys/cred.h>
#include <sys/proc.h>
#endif				/* SYSV */
#include <sys/user.h>		/* XXX - TOM */
#include <sys/stropts.h>
#include <sys/stream.h>
#include <sys/strlog.h>
#include <sys/log.h>
#include <netinet/nihdr.h>
#include <sys/dlpi.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <net/if.h>
#include <net/strioc.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/ip_icmp.h>
#include <netinet/in_var.h>
#include <netinet/tcp.h>
/* #include <netinet/tcpip.h> */
#include <sys/kmem.h>

/* Include the code for the matcher machine */

#include "matcher.c"

static mblk_t *prog;

static int ipaccopen (), ipaccclose (), ipaccrput (), ipaccwput ();

/* The T-streams module. */

static struct module_info ipaccm_info =
{
  0, "ipacc", 0, INFPSZ, 4096, 1024,
};

static struct qinit ipaccurinit =
{ipaccrput, NULL, ipaccopen, ipaccclose, NULL, &ipaccm_info, NULL};

static struct qinit ipaccuwinit =
{ipaccwput, NULL, ipaccopen, ipaccclose, NULL, &ipaccm_info, NULL};

struct streamtab ipaccinfo =
{&ipaccurinit, &ipaccuwinit, NULL, NULL};

/* Open routine of the T-module. */

static int
ipaccopen (q, dev, flag, sflag)
     queue_t *q;
{
  return 0;
}

/* Close routine of the T-module. */

static int
ipaccclose (q)
     queue_t *q;
{
  return 0;
}

/* Put routine of the iupcc-module. */

static int
ipaccrput (q, bp)
     queue_t *q;
     mblk_t *bp;
{
  union N_primitives *p;
  struct ip *ip;
  struct tcphdr *ti;
  struct iocblk *io;
  unsigned short sport, dport;
  int hlen;

  switch (bp->b_datap->db_type)
    {

    case M_PROTO:
    case M_PCPROTO:

      p = (void *) bp->b_rptr;
      switch (p->prim_type)
	{
	case N_UNITDATA_REQ:
	case N_UNITDATA_IND:

	  if (bp->b_cont->b_wptr - bp->b_cont->b_rptr < sizeof (struct ip) + 4)
	    if (!pullupmsg (bp->b_cont, sizeof (struct ip) + 4))
	      {
		freemsg (bp);
		return;
	      }

	  ip = (void *) bp->b_cont->b_rptr;
	  hlen = ip->ip_hl << 2;

	  if (bp->b_cont->b_wptr - bp->b_cont->b_rptr < hlen + 4)
	    {
	      if (!pullupmsg (bp->b_cont, hlen + 4))
		{
		  freemsg (bp);
		  return;
		}
	      ip = (void *) bp->b_cont->b_rptr;
	    }

	  ti = (struct tcphdr *) ((char *) ip + hlen);
	  sport = ntohs (ti->th_sport);
	  dport = ntohs (ti->th_dport);

	  /* ICMP packets are generally allowed - with the exception of ICMP
	     redirect messages. */

	  if (ip->ip_p == IPPROTO_ICMP)
	    {
	      struct icmp *icp = (struct icmp *) ((char *) ip + hlen);

	      if (icp->icmp_type > ICMP_MAXTYPE ||
		  icp->icmp_type == ICMP_REDIRECT)
		{
		  /* Message is not allowed - discard it */
		  freemsg (bp);
		  return;
		}
	    }

	  /* Other packets will be fed thru the filter program. */

	  else if (!matcher (ip->ip_src.s_addr, ip->ip_dst.s_addr,
			     sport, dport, ip->ip_p))
	    {
	      /* Message is not allowed - discard it */
	      freemsg (bp);
	      return;
	    }
	}
      break;
    }

  putnext (q, bp);
}

static int
ipaccwput (q, bp)
     queue_t *q;
     mblk_t *bp;
{
  union N_primitives *p;
  struct ip *ip;
  struct tcphdr *ti;
  struct iocblk *io;
  unsigned short sport, dport;

  switch (bp->b_datap->db_type)
    {

    case M_IOCTL:
      io = (void *) bp->b_rptr;
      if (io->ioc_cmd == ('I' << 24 | 'P' << 16 | 'A' << 8 | 0))
	{
	  mblk_t *tmp;
	  int s;
	  if (bp->b_cont == NULL)
	    {
	      bp->b_datap->db_type = M_IOCNAK;
	      io->ioc_error = EINVAL;
	      qreply (q, bp);
	      return;
	    }
	  if ((io->ioc_error = drv_priv (io->ioc_cr)) != 0)
	    {
	      bp->b_datap->db_type = M_IOCNAK;
	      qreply (q, bp);
	      return;
	    }
	  if ((tmp = dupmsg (bp->b_cont)) != NULL)
	    {
	      if (!pullupmsg (tmp, -1))
		{
		  freemsg (tmp);
		  tmp = NULL;
		}
	    }
	  if (tmp == NULL)
	    {
	      bp->b_datap->db_type = M_IOCNAK;
	      io->ioc_error = ENOSR;
	      qreply (q, bp);
	      return;
	    }

	  s = splstr ();
	  if (prog)
	    freemsg (prog);
	  prog = tmp;
	  set_pc ((unsigned short *) tmp->b_rptr);
	  splx (s);

	  /* call OK, ack it */

	  bp->b_datap->db_type = M_IOCACK;
	  qreply (q, bp);
	  return;
	}
      break;

    case M_PROTO:
    case M_PCPROTO:

      p = (void *) bp->b_rptr;
      switch (p->prim_type)
	{
	case N_UNITDATA_REQ:
	case N_UNITDATA_IND:

	  if (bp->b_cont->b_wptr - bp->b_cont->b_rptr < sizeof (struct ip) + 4)
	    if (!pullupmsg (bp->b_cont, sizeof (struct ip) + 4))
	      {
		freemsg (bp);
		return;
	      }

	  ip = (void *) bp->b_cont->b_rptr;
	  ti = (struct tcphdr *) (ip + 1);
	  sport = ntohs (ti->th_sport);
	  dport = ntohs (ti->th_dport);

	  /* ICMP packets are generally allowed - with the exception of ICMP
	     redirect messages. */

	  if (ip->ip_p == IPPROTO_ICMP)
	    {
	      struct icmp *icp = (struct icmp *) (ip + 1);

	      if (icp->icmp_type > ICMP_MAXTYPE ||
		  icp->icmp_type == ICMP_REDIRECT)
		{
		  /* Message is not allowed - discard it */
		  freemsg (bp);
		  return;
		}
	    }

	  /* Other packets will be fed thru the filter program. */

	  else if (!matcher (ip->ip_src.s_addr, ip->ip_dst.s_addr,
			     sport, dport, ip->ip_p))
	    {
	      /* Message is not allowed - discard it */
	      freemsg (bp);
	      return;
	    }
	}
      break;
    }

  putnext (q, bp);
}
