/*
 * by Jakub Wartak 2006 <vnull@pcnet.com.pl>
 * Licensed under GPLv2.
 *
 * most mogacy polaczyc tcpdump -w na routerze
 * z jakims analizatorem czytajacym z FIFO 
 */
 
// TODO: syslog


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
#include <signal.h>
#include <errno.h>

/* MTU ? */
#define BUFLEN 1200
#define VERSION "0.0.1"

static int mode, run, fifook, debuglvl;
static time_t startup;
static unsigned long packets, bytes;

enum {
	M_SENDER 	= 0x1,
	M_RECIVER 	= 0x2
};

static void usage(char *progname)
{
	fprintf(stderr, "Remote PCAP Daemon version %s by vnull 2006\n", VERSION);
	fprintf(stderr, "Usage: %s <-r|-s -d [ip]> [-p <portno] [-f <fifo>]\n\n", progname);
	fprintf(stderr, "\t-r\t\t\t- reciver mode\n");
	fprintf(stderr, "\t-s\t\t\t- sender mode\n");
	fprintf(stderr, "\t-d <ip>\t\t\t- destination IP (sender mode only)\n");
	fprintf(stderr, "\t-p <portno>\t\t- UDP port to use (default: 32001)\n");
	fprintf(stderr, "\t-f <fifo>\t\t- FIFO file name (default: /tmp/rpcapd.fifo)\n");
	fprintf(stderr, "\t-u <uid_id>\t\t- switch to UID after initialization\n");
	fprintf(stderr, "\t-g <gid_id>\t\t- switch to GID after initialization\n");
	fprintf(stderr, "\t-x\t\t\t- don't detach ( run foreground )\n");
	fprintf(stderr, "\t-D <debuglvl>\t\t- enable debug messagess, enforces -x\n");
	fprintf(stderr, "\n");
	exit(-1);
}

void die(char *s)
{
	perror(s);
	exit(1);
}

int create_udpsock(void)
{
	int s;
	if ((s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) {
		die("socket");
	}
	return s;
}

void init_si(struct sockaddr_in *si, int port)
{
    	memset(si, 0, sizeof(struct sockaddr_in));
    	si->sin_family = AF_INET;
    	si->sin_port = htons(port);
}

int init_socket(struct sockaddr_in *si, int port, char *ip)
{
	int s = create_udpsock();
	init_si(si, port);
	
	if(debuglvl) {
		printf("Created socket with fd %d\n", s);
	}

	if(mode == M_SENDER) {
						
		if (inet_aton(ip, &(si->sin_addr))==0) {
			fprintf(stderr, "inet_aton() failed: bad IP given, unable to convert\n");
			exit(1);
    	}
		
	} else if(mode == M_RECIVER) {
		si->sin_addr.s_addr = htonl(INADDR_ANY);
	    if (bind(s, (struct sockaddr *)si, sizeof(struct sockaddr_in))==-1) {
    		die("bind");
		}
	}
	return s;
}

/*
 * SEKCJA OBSLUGI SYGNALOW
 */
void handle_sigend(int no)
{
	int diff = time(NULL) - startup;
	printf("Terminating. Uptime = %d seconds\n", diff);
	exit(1);
}

void float2strbytes(unsigned long b, char buf[], int bufmax)
{
	char *app = "B/s";
	if(b > 1024) {
		b %= 1024;
		if(b > 1024*1024) {
			b %= 1024*1024;
			if(b > 1024*1024*1024) {
				b %= 1024*1024*1024;
				app = "GB/s";
			} else {
				app = "MB/s";
			}
		} else {
			app = "KB/s";
		}
	}
	snprintf(buf, bufmax, "%ld%s", b, app);
	return;
}

void handle_sigusr1(int no)
{
	/* stats */
	int diff = time(NULL) - startup;
	int avgp = packets / diff;
	unsigned long avgb = bytes / diff;
	char buf[32];
	
	float2strbytes(avgb, buf, sizeof(buf)-1);
	printf("AveragePPS=%d AverageRate=%s Uptime=%d\n", avgp, buf, diff);
}

void handle_sigpipe(int no)
{
	/* co 2s bedziemy sprawdzac potok ( probowac znow pisac do niego ) */
	if(fifook) {
		alarm(2);
		printf("SIGALARM! fifook was 1 will be 0, sched alarm\n");
	}
	/* ustawiamy znacznik ze cos jest nie tak z FIFO */
	fifook = 0;
	printf("SIGPIPE! fifook=0\n");
}

void handle_sigalrm(int no)
{
	fifook = 1;
	printf("SIGALARM! fifook=1\n");
}

void init_signals(void)
{
	signal(SIGINT, handle_sigend);
	signal(SIGTERM, handle_sigend); 
	signal(SIGUSR1, handle_sigusr1);
	signal(SIGPIPE, handle_sigpipe); 
	signal(SIGALRM, handle_sigalrm); 

      	signal(SIGCHLD,SIG_IGN);
        signal(SIGTSTP,SIG_IGN); /* tty */
}



static int init_fifo(char *name)
{
	int f, flags, err = remove(name);
	if(err == -1 && errno  != ENOENT) 
		die("remove()");
	
	if(mkfifo(name, 0600) == -1) 
		die("mkfifo()");
	
	if(mode == M_SENDER) {
		flags = O_RDONLY|O_SYNC;
	} else {
		flags = O_WRONLY|O_SYNC;
	}
	
	if((f = open(name, flags)) == -1)
		die("open()");
		
	return f;
}

int main(int argc, char **argv)
{
	int ret, s, i, f, optch, port = 32001, uid=-1, gid=-1, dontfork=0;
	struct sockaddr_in si;
	/* defaulty */
   	char buf[BUFLEN], *fifoname = "/tmp/rpcapd.fifo", *ip = "127.0.0.1";
	static char optstring[] = "xrsd:p:f:Vu:g:D:";

	/* defaulty #2 */
	mode = 0;
	debuglvl = 0;
	packets = bytes = 0;
		
	while((optch = getopt(argc, argv, optstring)) != -1) {
		switch(optch) {
		case 'r':
			mode = M_RECIVER;
			break;
		case 's':
			mode = M_SENDER;
			break;
		case 'd':
			ip = strdup(optarg);
			break;
		case 'p':
			port = atoi(optarg);
			break;
		case 'f':
			fifoname = strdup(optarg);
			break;
		case 'V':
			usage(argv[0]);
			break;
		case 'u':
			uid = atoi(optarg);
			break;
		case 'g':
			gid = atoi(optarg);
			break;
		case 'D':
			debuglvl = atoi(optarg);
			dontfork = 1;
			printf("Enabling debug mode, level=%d\n", debuglvl);
			break;
		case 'x':
			dontfork = 1;
			break;
		default:
			usage(argv[0]);
			/* i tak nigdy do tego nie dojdzie */
			break;
		}
	}
	
	if(!mode) {
		fprintf(stderr, "You must specify sender/reciver mode!\n\n");
		usage(argv[0]);
	}
	
	/* sekcja inicjalizacyjna */
	
	// TODO:  syslog
	if(dontfork==0) {
		ret = fork();
		if(ret == -1) {
			die("fork()");
		}

		if(ret != 0 ) {
			printf("parent: daemon mode enabled - forking\n");
			exit(-1);
		}

		/* child */

  		if (setsid() < 0) {
	    		die("setsid()");
    		}
	}

	startup = time(NULL);
	/* fifook == 1 => mozna pisac do FIFO i jest ok */
	run = fifook = 1;
	init_signals();
	s = init_socket(&si, port, ip);
	f = init_fifo(fifoname);

	if(gid != -1) {
		if(setgid(gid) < 0) {
			die("setgid()");
		}
	}

	if(uid != -1) {
		if(setuid(uid) < 0) {
			die("setuid()");
		}
	}

  	
	while(run) {
		ssize_t len;
    	
		if(mode == M_RECIVER) {
			struct sockaddr_in other;
			socklen_t slen = sizeof(other);
			
			if ((len = recvfrom(s, buf, BUFLEN, 0, (struct sockaddr *)&other, &slen))==-1) {
    				die("recvfrom()");
	
			}
    			
			if(debuglvl==2) {
				printf("Received packet from %s:%d, len=%ld\n", 
					inet_ntoa(other.sin_addr), ntohs(other.sin_port), len);
			}

			if(fifook) {
				/* jezeli jest wszystko jest OK z fifo to piszemy 
			 	* moze sie tez zdarzyc ze dostaniemy po tym writcie SIGPIPE
			 	* jezeli nikt nie bedzie czytal FIFO, wtedy to co 2s 
			 	* bedziemy probowac znow pisac i moze sie okazac ze bedzie OK
			 	* albo ze znow jest zle i wtedy znow za 2s ....
			 	*/
				write(f, buf, len);
				packets++;
				bytes += len;
			}
		} else if(mode == M_SENDER) {
			socklen_t slen2 = sizeof(si);
			len =  read(f, buf, BUFLEN);
			
			packets++;
			bytes += len;
			
			struct in_addr *ia = &(si.sin_addr);

			if(debuglvl==2) {
				printf("sending %ld bytes data=%d\n", len, ia->s_addr);
			}
			
			if (sendto(s, buf, len, 0, (struct sockaddr *)&si, slen2)==-1) {
				die("send()");
    		}
		}
    }

    close(f);
    close(s);
		
    return 0;
}
