/*
 * Scandetd is daemon which tries to recognize port scanning. 
 * If it happens daemon sends e-mail to root@localhost  (by default) 
 * with following informations:
 * 
 * time 
 * host  
 * how many connetctions was made
 * port of first connection and port of last connection
 *
 * compile: gcc scandetd.c -o scandetd
 *
 * author: Michal Suszycki	mike@wizard.ae.krakow.pl
 *
 * You can change few define's and variables below this comment to tune
 * scandetd to your needs.
 *
 * If you have some problems with compiling try to  
 * change 2 lines:
 * #include <netinet/ip.h> to #include <linux/ip.h>
 * #include <netinet/tcp.h> to #include <linux/tcp.h>
 *
 * This code was based on IpLogger Package by Mike Edulla (medulla@infosoc.com)
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 1, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
//#include <netinet/ip.h>
#include <linux/ip.h>
//#include <netinet/tcp.h>
#include <linux/tcp.h>
#include <time.h>
#include <signal.h>
#include <string.h>

extern int errno;


/* how many hosts should I remember. If your server is heavily loaded it's
   good idea to increase this number a little bit
*/
#define HOW_MANY 6

/* how many connections should I recognize as scanning? */
#define SCAN 25

/* uncomment this to enable printing to log files scan warnings (using syslogd) */
//#define DOSYSLOG

/* uncoment this if you want to log every connection attempt (using syslogd) */
//#defind LOGCON

/* here you can define special port you want to ignore:
   if 'scanning' started and ended on the same port and this port is equal
   to NOPORT then 'scanning'  will be ignored. If you  notice for example that
   your server get a lot of fast connections (from one host) to www port you 
   can define NOPORT to 80 so there will be no false warnings 
*/ 
#define NOPORT 80

/* 
   If next connection arrived right after the previous one we have to count it.
   Default time is 1 second.
*/
#define SEC 1

/* We use this port for sending mail */
#define MAILPORT 25

/* we send mail to <user@host>: */
char *mail_to = "<root@localhost>";

/* IP of the machine which sends our mail */
char *mail_host = "127.0.0.1";

/* mail will be send from host: */
char *from_host = "localhost";


/* ----------- end of user's configuration  ---------------- */

#ifndef NOFILE
#define NOFILE 1024
#endif


char *hostlookup(int i)
{
	static char buff[256];
	struct in_addr ia;
	struct hostent *he;
	ia.s_addr = i;
	
	if (!(he = gethostbyaddr((char *)&ia, sizeof ia, AF_INET)))
		strncpy(buff,inet_ntoa(ia),sizeof buff);
	else
		strncpy(buff,he->h_name,sizeof buff);
	return buff;
}

char *servlookup(unsigned short port)
{
	struct servent *se;
   	static char buff[256];
      
   	se=getservbyport(port, "tcp");
   		if(se == NULL) sprintf(buff, "port %d", ntohs(port));
       		else sprintf(buff, "%s", se->s_name);
	return buff;
}
		  


struct ippkt{
	struct iphdr ip;
	struct tcphdr tcp;
} pkt;

struct host{
	unsigned int from;
	time_t t;
	time_t start;
	unsigned short low_port;
	unsigned short hi_port;
	int count;
} hosts[HOW_MANY];

void demonize()
{
	int fd, f;
	
	if (getppid() != 1){
		signal(SIGTTOU,SIG_IGN);
		signal(SIGTTIN,SIG_IGN);
		signal(SIGTSTP,SIG_IGN);
		f = fork();
		if (f < 0)
			exit(-1);
		
		if (f > 0)
			 exit (0);

	/* child process */		
	setpgrp();
	for (fd = 0 ; fd < NOFILE; fd++) close(fd);
	chdir("/");
	umask(0);
	return;
	}
}	


void init()
{
    int i;
    time_t now;
    now = time(NULL);
    for (i = 0; i < HOW_MANY; i++)
	hosts[i].t = now;
}

int allocate(int *p, unsigned int addr)
{
	int i, v = 0;
	time_t tmp = hosts[0].t;
	for( i = 0; i < HOW_MANY; i++){
		if (hosts[i].t <= tmp) {
			tmp = hosts[i].t;
			v = i;
		}
		if (hosts[i].from == addr){
			*p = 1;
			return i;
		}
	}
	*p = 0;
	return v;
}

// only for debugging 
void show(int a)
{
	int i;
	
	for (i = 0; i < HOW_MANY; i++){
		printf("Host %s, time %ld, count=%d, l=%d,",
			hostlookup(hosts[i].from),hosts[i].t, hosts[i].count,
			ntohs(hosts[i].low_port));
		printf("hi = %d\n",ntohs(hosts[i].hi_port));
	}		
	exit (0);
}

void no_zombie(int i)
{
	wait(NULL);
}

int send_mail(struct host *bad)
{
	static struct sockaddr_in sa;
	int s, i, low, high;
	char buf[1024], combuf[256];
	
	char *comm[] = { "HELO ", 			from_host,
			 "MAIL FROM: SCANDETD@",	from_host,
			 "RCPT TO:"		,	mail_to,
			 "DATA"			,	" "
			};
	
	i = fork();
	
	if (i < 0) return -1;
	if (!i) return 0;
	
	low = ntohs(bad->low_port);
	high = ntohs(bad->hi_port);
	sprintf(buf,"%sPossible port scanning from %s,\n"
		"I counted %d connections.\nFirst connection was made on %d port and the last one on %d port.\r\n.\r\n",
		ctime(&bad->start),hostlookup(bad->from),bad->count, low, high);
					

	sa.sin_port = htons(MAILPORT);
	sa.sin_family = AF_INET;
	if ((sa.sin_addr.s_addr = inet_addr(mail_host)) == -1)
		exit (-1);
	
	bzero(&sa.sin_zero, 8);
	if ((s = socket(AF_INET,SOCK_STREAM,0)) < 0)
		exit (-1);
	
	if (connect(s,(struct sockaddr *) &sa, sizeof (struct sockaddr)) < 0)
		exit (-1);
	
	for (i = 0; i < 8 ; i += 2){
		sprintf(combuf,"%s%s\n",comm[i],comm[i+1]);
		if (write(s,combuf,strlen(combuf)) < 0 ){
			close(s);
			exit(-1);
		}
		sleep(1);
	}
	if (write(s,buf,strlen(buf)) < 0) exit(-1);
	sleep(1);
	if (write(s,"QUIT\n",5) < 0) exit (-1);
	
	close(s);
	exit(0);
}
	

void main(int argc, char **argv)
{
	int s, index, was;
	time_t now;
	
	demonize();

	init();
	s = socket(AF_INET, SOCK_RAW, 6);

#ifdef DOSYSLOG
	openlog("scandetd", 0, LOG_LOCAL2);
	syslog(LOG_NOTICE,"scandetd started and ready");
#endif
//	signal(SIGINT,show);
	
/* to avoid zombies */
	signal(SIGCHLD,no_zombie);

	while(1){
		read(s, (struct ippkt*) &pkt, sizeof(pkt));
		now = time(NULL);
		
		if (pkt.tcp.syn == 1 &&  pkt.tcp.ack == 0){
		
#ifdef LOGCON
			syslog(LOG_NOTICE,"%s connecion attempt from %s",
				servlookup(pkt.tcp.dest),
				hostlookup(pkt.ip.saddr)
				);
#endif	
			index = allocate(&was,pkt.ip.saddr);
			
			if (!was){
				if (hosts[index].count >= SCAN
#ifdef NOPORT				
					&& hosts[index].low_port != htons(NOPORT)
					&& hosts[index].hi_port  != htons(NOPORT)
#endif
					){
					send_mail(&hosts[index]);
#ifdef DOSYSLOG			
				syslog(LOG_NOTICE,"Possible port scanning from %s",
					hostlookup(hosts[index].from));
#endif
				}
				hosts[index].from = pkt.ip.saddr;
				hosts[index].low_port = pkt.tcp.dest; 
				hosts[index].hi_port = pkt.tcp.dest;
				hosts[index].count = 1;
				hosts[index].t = now;
				hosts[index].start = now;
				continue;
			}
		
	/* if this connection was right after previous we must count it */
			else if (now - SEC <= hosts[index].t){
				hosts[index].count++;
				hosts[index].hi_port = pkt.tcp.dest;

			}
		hosts[index].t = now;
		}
	}
}

