/*
 * DETECT UDP SP00FiNG ON OUR FREEBSD BOX VIA KLD
 * ----------------------------------------------
 *
 * This is a partial porting of my linux lkm to detect spoofing from our box..
 * to another system....
 *
 * This kld detects only UDP Spoofing. Other implementations are possible
 * changing pru_send function of the protocol you are interested in... You
 * can use this kld to understand how to do that on another protocol or you 
 * can write me for other implementations.
 * 
 * Set MY_IP, MY_SECOND_IP with your address/es...
 * 
 * This kld modifies a function of udp pr_usrreqs structure... other **bsd
 * systems have a function to interface socket routines with protocols.
 * 
 * Notes: TCP implementation is possible via kld
 *	  IGMP & ICMP are also possible but they use pru_send in common
 *	  so check inp->inp_ip_p for IPPROTO_IGMP, IPPROTO_ICMP and so
 *	  on...
 * 
 * idea & code by pIGpEN [pigpen@s0ftpj.org, deadhead@sikurezza.org]
 *
 * s0ftpr0ject - digital security for y2k
 * www.s0ftpj.org
 *
 * sikurezza.org - italian security mailing list
 * www.sikurezza.org
 *
 */

/* 
 * pay attention: this code is compatible only with new kernels with
 * for example jail support... so you have to change pru_send to have
 * it working on old versions.
 * 
 * uname -a
 *
 * FreeBSD storpio.cameretta.pig 4.0-19990705-CURRENT FreeBSD 4.0-19990705-
 * CURRENT #4 ..... i386
 *
 * If you wanna a porting of this code and you have no time to do that 
 * write to me at: deadhead@sikurezza.org with subject "PORTING A KLD"
 * 
 */

#define MY_IP 		"192.168.1.2"
#define MY_SECOND_IP	"192.168.1.2"
/* my machine has only an ip address and no other eth# or ip aliases */
#define LOOPBACK	"127.0.0.1"
#define DONT_PASS

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/protosw.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_pcb.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <netinet/udp_var.h>


extern struct 	protosw 	inetsw[];
extern struct 	udpstat 	udpstat;
static int 	udpcksum	=	1;


static int  	 s_load	 		__P((struct module *, int, void *));

static int	 udp_send		__P((struct socket * , int , 
					     struct mbuf *, struct sockaddr *, 
					     struct mbuf *, struct proc *));

static int	 (*old_udp_send)	__P((struct socket * , int ,
					     struct mbuf *, struct sockaddr *,
					     struct mbuf *, struct proc *));

static int	 udp_output		__P((struct inpcb    *, struct mbuf *,
					     struct sockaddr *, struct mbuf *,
					     struct proc *));

static u_int32_t inaton			__P((const char *));


/* ipfw macro... inet_ntoa() also works well from here it's the same thing */

#define print_ip(a)	printf("%d.%d.%d.%d",				\
				(int)(ntohl(a.s_addr)  >> 24) & 0xFF,	\
				(int)(ntohl(a.s_addr)  >> 16) & 0xFF,	\
				(int)(ntohl(a.s_addr)  >> 8 ) & 0xFF,	\
				(int)(ntohl(a.s_addr))  & 0xFF);

static int
s_load (struct module *module, int cmd, void *arg)
{
 int s;

 switch(cmd) {
	case MOD_LOAD:
	  s = splnet();
	  old_udp_send = inetsw[ip_protox[IPPROTO_UDP]].pr_usrreqs->pru_send;
	  inetsw[ip_protox[IPPROTO_UDP]].pr_usrreqs->pru_send = udp_send;
	  splx(s);
	  break;
			
	case MOD_UNLOAD:
	  s = splnet();
	  inetsw[ip_protox[IPPROTO_UDP]].pr_usrreqs->pru_send = old_udp_send;
	  splx(s);
	  break;
 }

 return 0;
}

static moduledata_t s_mod_1 = {
	        "udp_mod",
	        s_load,
	        0
};

DECLARE_MODULE(udp_mod, s_mod_1, SI_SUB_PSEUDO, SI_ORDER_ANY);


static int
udp_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr,
	    struct mbuf *control, struct proc *p)
{
	struct inpcb *inp;

	inp = sotoinpcb(so);
	if (inp == 0) {
		m_freem(m);
		return EINVAL;
	}
	return udp_output(inp, m, addr, control, p);
}


static int
udp_output(inp, m, addr, control, p)
	register struct inpcb *inp;
	register struct mbuf *m;
	struct sockaddr *addr;
	struct mbuf *control;
	struct proc *p;
{
	register struct udpiphdr *ui;
	register int len = m->m_pkthdr.len;
	struct in_addr laddr;
	struct sockaddr_in *sin;
	int s = 0, error = 0;

	if (control)
		m_freem(control);		/* XXX */

	if (len + sizeof(struct udpiphdr) > IP_MAXPACKET) {
		error = EMSGSIZE;
		goto release;
	}

	if (addr) {
		sin = (struct sockaddr_in *)addr;
		prison_remote_ip(p, 0, &sin->sin_addr.s_addr);
		laddr = inp->inp_laddr;
		if (inp->inp_faddr.s_addr != INADDR_ANY) {
			error = EISCONN;
			goto release;
		}
		/*
		 * Must block input while temporarily connected.
		 */
		s = splnet();
		error = in_pcbconnect(inp, addr, p);
		if (error) {
			splx(s);
			goto release;
		}
	} else {
		if (inp->inp_faddr.s_addr == INADDR_ANY) {
			error = ENOTCONN;
			goto release;
		}
	}
	/*
	 * Calculate data length and get a mbuf
	 * for UDP and IP headers.
	 */
	M_PREPEND(m, sizeof(struct udpiphdr), M_DONTWAIT);
	if (m == 0) {
		error = ENOBUFS;
		if (addr)
			splx(s);
		goto release;
	}

	/*
	 * Fill in mbuf with extended UDP header
	 * and addresses and length put into network format.
	 */
	ui = mtod(m, struct udpiphdr *);
	bzero(ui->ui_x1, sizeof(ui->ui_x1));
	ui->ui_pr = IPPROTO_UDP;
	ui->ui_len = htons((u_short)len + sizeof (struct udphdr));
	ui->ui_src = inp->inp_laddr;
	ui->ui_dst = inp->inp_faddr;
	ui->ui_sport = inp->inp_lport;
	ui->ui_dport = inp->inp_fport;
	ui->ui_ulen = ui->ui_len;

	/*
	 * Stuff checksum and output datagram.
	 */
	ui->ui_sum = 0;
	if (udpcksum) {
	    if ((ui->ui_sum = in_cksum(m, sizeof (struct udpiphdr) + len)) == 0)
		ui->ui_sum = 0xffff;
	}
	((struct ip *)ui)->ip_len = sizeof (struct udpiphdr) + len;
	((struct ip *)ui)->ip_ttl = inp->inp_ip_ttl;	/* XXX */
	((struct ip *)ui)->ip_tos = inp->inp_ip_tos;	/* XXX */

	if(ui->ui_src.s_addr != inaton(MY_IP) 		&& 
	   ui->ui_src.s_addr != inaton(MY_SECOND_IP) 	&& 
	   ui->ui_src.s_addr != inaton(LOOPBACK)) {
	 printf("UDP Spoofing detected as: "); 
	 print_ip(ui->ui_src);
	 printf(" to ");
	 print_ip(ui->ui_dst);
#ifdef DONT_PASS		
	 printf(" Packet not accepted to be sent\n");
	 goto release;
#endif
	}
	
	udpstat.udps_opackets++;
	
	error = ip_output(m, inp->inp_options, &inp->inp_route,
	    inp->inp_socket->so_options & (SO_DONTROUTE | SO_BROADCAST),
	    inp->inp_moptions);

	if (addr) {
		in_pcbdisconnect(inp);
		inp->inp_laddr = laddr;	/* XXX rehash? */
		splx(s);
	}
	return (error);

release:
	m_freem(m);
	return (error);
}

u_int32_t inaton(const char *str)
{
	unsigned long l;
	unsigned int val;
	int i;

	l = 0;

	for(i=0; i < 4; i++) {
		l <<= 8;
		if(*str != '\0') {
			val = 0;
			while(*str != '\0' && *str != '.') {
				val *= 10;
				val += *str - '0';
				str++;
			}
			l |= val;
			if(*str != '\0')
				str++;
		}
	}
	return(htonl(l));
}
