/*
     udplogger - A UDP traffic logger
     Copyright (C) 1993 Douglas Lee Schales, David K. Hess, David R. Safford

     Please see the file `COPYING' for the complete copyright notice.

udplog.c - 03/20/93

*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <sys/time.h>
#include <netdb.h>
#include <memory.h>

extern char *optarg;
extern int optind, opterr;

#include "defs.h"
#include "version.h"

struct ipaddr {
     unsigned int addr:32;
};

struct tcpsynout {
     struct timeval tp;
     unsigned long ipsrcaddr;
     unsigned long ipdstaddr;
     unsigned long tcpseq;
     unsigned short tcpsrcport;
     unsigned short tcpdstport;
};

static void etherhandler(char *, int, struct timeval);
static void udplog(struct ip *, struct timeval);
static void udplogbin(struct tcpsynout *);
static void udplogascii(struct tcpsynout *);
static int isactive(struct tcpsynout *);
static void udpage(struct timeval);
static char *getudpportname(unsigned short);
extern char *gettimestr(struct timeval);

extern void pushfilter(int);

#define LOGBINARY 0
#define LOGASCII 1
static int logtype = LOGBINARY;
static FILE *logfile = stdout;

int resolve = 1;

static int timeout = 300;

extern char *getdefaultif(char *);

main(int argc, char **argv)
{
     char *eif = getdefaultif((char *)0);
     int fd;
     int c;
     int promisc=1;
     
     while((c=getopt(argc, argv, "pni:abf:t:v")) != -1){
	  switch(c){
	  case 'a':
	       logtype = LOGASCII;
	       break;
	  case 'b':
	       logtype = LOGBINARY;
	       break;
	  case 'n':
	       resolve = 0;
	       break;
	  case 'p':
	       promisc = 0;
	       break;
	  case 'i':
	       eif = optarg;
	       break;
	  case 'f':
	       if(!(logfile = fopen(optarg, "a"))){
		    perror(optarg);
		    exit(1);
	       }
	       break;
	  case 't':
	       timeout = atoi(optarg);
	       if(!timeout){
		    fprintf(stderr, "Invalid timeout specified: %s\n",
			    optarg);
		    exit(1);
	       }
	       break;
	  case 'v':
	       printf("udplogger, version %s\n", toolversion);
	       exit(0);
	       break;
	  default:
	       fprintf(stderr, "Unknown option: %c\n", c);
	       exit(1);
	  }
     }

     fd = opennit(eif, promisc, pushfilter);
     procpkts(fd, etherhandler);
}

void
etherhandler(char *pkt, int size, struct timeval tp)
{
     udplog((struct ip *)(pkt + 14), tp);
}


void
udplog(struct ip *ip, struct timeval tp)
{
     struct udphdr *udp;
     struct tcpsynout outbuf;

     udp = (struct udphdr *)(((char *)ip) + sizeof(struct ip));

     memset((char *)&outbuf.tcpseq, 0, sizeof(long));
     memcpy((char *)&outbuf.tp, (char *)&tp, sizeof(struct timeval));
     memcpy((char *)&outbuf.ipsrcaddr, (char *)&ip->ip_src, 4);
     memcpy((char *)&outbuf.ipdstaddr, (char *)&ip->ip_dst, 4);
     memcpy((char *)&outbuf.tcpsrcport, (char *)&udp->uh_sport, 2);
     memcpy((char *)&outbuf.tcpdstport, (char *)&udp->uh_dport, 2);

     if(!isactive(&outbuf)){
	  switch(logtype){
	  case LOGBINARY:
	       udplogbin(&outbuf);
	       break;
	  case LOGASCII:
	       udplogascii(&outbuf);
	       break;
	  default:
	       fprintf(stderr, "Log type unknown: %d\n", logtype);
	       break;
	  }
     }
     udpage(tp);
}

void
udplogbin(struct tcpsynout *outbuf)
{
     write(fileno(logfile), (char *)outbuf, sizeof(struct tcpsynout));
}

void
udplogascii(struct tcpsynout *outbuf)
{
     struct in_addr haddr;

     fputs(gettimestr(outbuf->tp), logfile);
     fprintf(logfile, " %8X ", outbuf->tcpseq);
     memcpy(&haddr.s_addr, &outbuf->ipsrcaddr, 4);
     fprintf(logfile, "%-21s", resolve ? cgethostbyaddr(haddr) : inet_ntoa(haddr));
     fputc(' ', logfile);
     fputs(getudpportname(outbuf->tcpsrcport), logfile);
     fputs(" -> ", logfile);
     memcpy(&haddr.s_addr, &outbuf->ipdstaddr, 4);
     fprintf(logfile, "%-21s", resolve ? cgethostbyaddr(haddr) : inet_ntoa(haddr));
     fputc(' ', logfile);
     fputs(getudpportname(outbuf->tcpdstport), logfile);;
     fputc('\n', logfile);
     fflush(logfile);
}

char *
getudpportname(unsigned short p)
{
     struct servent *se;
     static char result[80];

     if(resolve && (se = getservbyport((unsigned int)ntohs(p), "udp")))
	  strcpy(result, se->s_name);
     else
	  sprintf(result, "%u", (unsigned int)ntohs(p));
     
     return result;
}

struct udpactive {
     struct udpactive *next;
     struct tcpsynout buf;
};

struct udpactive *udphead = (struct udpactive *)0;

int
isactive(struct tcpsynout *buf)
{
     struct udpactive *rove;

     for(rove=udphead;rove;rove=rove->next){
	  if(!memcmp(&rove->buf.ipsrcaddr, &buf->ipsrcaddr, 4) &&
	     !memcmp(&rove->buf.ipdstaddr, &buf->ipdstaddr, 4) &&
	     !memcmp(&rove->buf.tcpsrcport, &buf->tcpsrcport, 2) &&
	     !memcmp(&rove->buf.tcpdstport, &buf->tcpdstport, 2)){
	       memcpy(&rove->buf.tp, &buf->tp, sizeof(struct timeval));
	       return 1;
	  }
	  /* Response? */
	  if(!memcmp(&rove->buf.ipsrcaddr, &buf->ipdstaddr, 4) &&
	     !memcmp(&rove->buf.ipdstaddr, &buf->ipsrcaddr, 4) &&
	     !memcmp(&rove->buf.tcpsrcport, &buf->tcpdstport, 2) &&
	     !memcmp(&rove->buf.tcpdstport, &buf->tcpsrcport, 2)){
	       memcpy(&rove->buf.tp, &buf->tp, sizeof(struct timeval));
	       return 1;
	  }
     }
     rove = (struct udpactive *)malloc(sizeof(struct udpactive));
     rove->next = udphead;
     udphead = rove;
     memcpy(&rove->buf, buf, sizeof(struct tcpsynout));

     return 0;
}

void
udpage(struct timeval tp)
{
     struct udpactive *rove, *prev, *temp;

     prev=(struct udpactive *)0;
     rove=udphead;
     while(rove){
	  if((tp.tv_sec - rove->buf.tp.tv_sec) > timeout){
	       if(prev){
		    prev->next = rove->next;
		    free(rove);
		    rove=prev->next;
	       }
	       else {
		    udphead = rove->next;
		    free(rove);
		    rove=udphead->next;
	       }
	  }
	  else {
	       prev=rove;
	       rove=rove->next;
	  }
     }
}

