/*
     etherscan - A real time network security monitor
     Copyright (C) 1993 Douglas Lee Schales, David K. Hess, David R. Safford

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

smtp.c - 05/23/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/tcp.h>
#include <ctype.h>
#include <sys/time.h>
#include <math.h>

extern float microsec(struct timeval);
extern struct timeval subtime(struct timeval, struct timeval);
extern struct timeval addtime(struct timeval, struct timeval);

struct ipaddr {
     unsigned int addr:32;
};

struct msgline {
     struct msgline *next;
     char *text;
};

struct smtp_sess {
     struct smtp_sess *next;
     struct smtp_sess *prev;
     struct in_addr srcaddr;
     struct in_addr dstaddr;
     unsigned short srcport;
     struct timeval tlp; /* time of last packet */
     struct timeval tbp; /* time between packets (cummulative) */
     unsigned long pktcnt;
     char buffer[1024];
     char maildst[1024];
     char mailsrc[1024];
     int rootmail;
     int passwdflag;
     int synflag;
     int logitflag;
     int bodyflag;
     int unusual;
     int bufptr;
     int state;
#define SMTP_INIT 0
#define SMTP_HELO 1
#define SMTP_MAIL 2
#define SMTP_RCPT 3
#define SMTP_DATA 4
     struct msgline *body, *last;
     struct timeval tll;  /* time of last line */
     struct timeval tbl;  /* time between lines (cummulative) */
     struct timeval tdel; /* time between last 2 lines */
     int datalinecnt;
     int linecnt;
};

extern char *inet_ntoa(struct in_addr);

extern char *gethostname(struct in_addr haddr);
extern void outhost(struct in_addr ha);

double normaltblavg = 750000.00;
double normaltblavg2 = 12500000000000.00;
double normalpplavg = 0.0450;
double normalpplavg2 = 0.0850;
int sesscnt = 0;

int
sigterm()
{
     fprintf(stdout, "\
normaltblavg  = %+.4lf\n\
normaltblavg2 = %+.4lf\n\
normalpplavg  = %+.4lf\n\
normalpplavg2 = %+.4lf\n",
	     normaltblavg, normaltblavg2, normalpplavg, normalpplavg2);
}

static void
checksession(struct smtp_sess *sp)
{
     double tblavg, pplavg;
     double normaltblstdev, normalpplstdev;
     double tblratio, pplratio;

     sesscnt++;

     if(sp->datalinecnt){
	  if((sp->passwdflag*100)/sp->datalinecnt > 25){
	       struct msgline *rove;

	       outtime(sp->tlp);
	       printf(" [smtp] ");
	       outhost(sp->srcaddr);
	       printf(".%d", sp->srcport);
	       outhost(sp->dstaddr);
	       printf(" %s -> %s passwd file? (%d%%)\n\n",
		      sp->mailsrc, sp->maildst,
		      (sp->passwdflag*100)/sp->datalinecnt);
	       for(rove=sp->body;rove;rove=rove->next)
		    printf("\t%s\n",rove->text);
	       printf("\n");
	       fflush(stdout);
	  }
     }

     if(!sp->mailsrc[0]){
	  if(sp->maildst[0])
	       sp->unusual++;
	  else if(sp->linecnt > 2)
	       sp->unusual++;
     }
     else if(sp->maildst[0]){
	  if(sp->linecnt > 4)
	       sp->unusual++; /* Not unusual, just want to check it */
     }
     else if(sp->linecnt > 3)
	  sp->unusual++;
	       
     if(sp->unusual && sp->linecnt > 2){
	  double normaltblvar, normalpplvar;
	  double tblvar, pplvar;

	  tblavg = (microsec(sp->tbl)/sp->linecnt);
	  pplavg = sp->pktcnt/sp->linecnt;

	  normaltblvar = normaltblavg2 - normaltblavg * normaltblavg;
	  normalpplvar = normalpplavg2 - normalpplavg * normalpplavg;
	  
	  tblvar = normaltblavg - tblavg;
	  tblvar *= tblvar;
	  pplvar = normalpplavg - pplavg;
	  pplvar *= pplvar;

	  normaltblstdev = sqrt(normaltblvar);
	  normalpplstdev = sqrt(normalpplvar);
	  tblratio = (tblavg - normaltblavg)/normaltblstdev;
	  pplratio = (pplavg - normalpplavg)/normalpplstdev;
	  if((sp->linecnt > 2 && tblratio > 2 && pplratio > 2) ||
	     (sp->linecnt == 2 && (tblratio > 2 || pplratio > 2))){

	       outtime(sp->tlp);
	       printf(" [smtp] ");
	       outhost(sp->srcaddr);
	       printf(".%d", sp->srcport);
	       outhost(sp->dstaddr);
	       printf(" %s -> %s connect? (t %+.4lf, p %+.4lf) %d %d\n",
		      sp->mailsrc, sp->maildst,
		      tblratio, pplratio,
		      sp->linecnt, sp->pktcnt);
	       fflush(stdout);
	  }
	  normaltblavg -= normaltblavg/sesscnt;
	  normaltblavg += tblavg / sesscnt;

	  normaltblavg2 -= normaltblavg2/sesscnt;
	  normaltblavg2 += tblavg*tblavg / sesscnt;

	  normalpplavg -= normalpplavg/sesscnt;
	  normalpplavg += pplavg / sesscnt;

	  normalpplavg2 -= normalpplavg2/sesscnt;
	  normalpplavg2 += pplavg*pplavg / sesscnt;
     }
     if(sp->logitflag == -1){
	  outtime(sp->tlp);
	  printf(" [smtp] ");
	  outhost(sp->srcaddr);
	  printf(".%d", sp->srcport);
	  outhost(sp->dstaddr);
	  printf("EHLO: %s -> %s\n", sp->mailsrc, sp->maildst);
	  fflush(stdout);
     }
     if(sp->rootmail){
	  outtime(sp->tlp);
	  printf(" [smtp] ");
	  outhost(sp->srcaddr);
	  printf(".%d", sp->srcport);
	  outhost(sp->dstaddr);
	  printf(" %s -> %s root mail\n", sp->mailsrc, sp->maildst);
	  fflush(stdout);
     }
}

void
addline(struct smtp_sess *sp)
{
     struct msgline *new;

     new = (struct msgline *)malloc(sizeof(struct msgline));
     new->text = (char *)malloc(strlen(sp->buffer)+1);
     new->next = (struct msgline *)0;
     strcpy(new->text, sp->buffer);
     if(!sp->body)
	  sp->last = sp->body = new;
     else {
	  sp->last->next = new;
	  sp->last = new;
     }
}

static void
checkbuffer(struct smtp_sess *sp)
{

     sp->linecnt++;


     if(sp->state == SMTP_DATA){
	  if(strcmp(sp->buffer, ".") == 0)
	       sp->state = SMTP_HELO;
	  else {
	       char *cp = sp->buffer;
	       int coloncnt = 0;
	       int pwflg = 1;

	       if(!sp->bodyflag){
		    if(sp->buffer[0] == 0){
			 sp->bodyflag = 1;
			 return;
		    }
		    for(cp=sp->buffer;*cp && !isspace(*cp) && *cp != ':';cp++)
			 ;
		    if(sp->buffer[0] != ':' && *cp == ':'){ /* Possible Header */
			 addline(sp);
			 for(cp++;*cp && isspace(*cp);cp++)
			      ;
			 while(*cp){
			      if(*cp == '"')
				   cp++;
			      else if(*cp == '<')
				   cp++;
			      else if(*cp == '/' || *cp == '|'){
				   if(*cp == '/'){
					for(cp++;*cp && *cp != '/' && *cp != '=';cp++)
					     ;
				   }
				   if(*cp == '/' || !*cp || *cp == '|'){
					int i = strlen(sp->buffer) -1;
					for(;i>=0;i--){
					     if(sp->buffer[i] == '"')
						  break;
					     if(sp->buffer[i] == '@')
						  break;
					}
					if(i>=0 && sp->buffer[i] != '@'){
					     outtime(sp->tlp);
					     printf(" [smtp] ");
					     outhost(sp->srcaddr);
					     printf(".%d", sp->srcport);
					     outhost(sp->dstaddr);
					     printf(" %s -> %s; Header %s\n", sp->mailsrc, sp->maildst, sp->buffer);
					     fflush(stdout);
					}
				   }
				   break;
			      }
			      else
				   break;
			 }
		    }
	       }

	       sp->datalinecnt++;
	       for(cp=sp->buffer;*cp && isspace(*cp);cp++)
		    ;
	       for(;*cp;cp++){
		    if(*cp == ':')
			 coloncnt++;
		    else switch(coloncnt){
		    case 0: /* username */
			 if(!isalnum(*cp))
			      pwflg = 0;
			 break;
		    case 1: /* passwd */
			 break;
		    case 2: /* uid */
			 if(!isdigit(*cp))
			      pwflg = 0;
			 break;
		    case 3: /* gid */
			 if(!isdigit(*cp))
			      pwflg = 0;
			 break;
		    case 4: /* gecos */
			 break;
		    case 5: /* homedir */
			 break;
		    case 6: /* shell */
			 break;
		    default:
			 break;
		    }
	       }
			 
	       if((coloncnt == 6) && pwflg){
		    if(isspace(sp->buffer[0]))
			 addline(sp);
		    sp->passwdflag++;
	       }
	  }
     }
     else if(strncasecmp(sp->buffer, "HELP", 4) == 0 &&
	     strlen(sp->buffer) < 16){
	  sp->unusual++;
	  outtime(sp->tlp);
	  printf(" [smtp] ");
	  outhost(sp->srcaddr);
	  printf(".%d", sp->srcport);
	  outhost(sp->dstaddr);
	  printf(" cmd %s\n", sp->buffer);
	  fflush(stdout);
     }
     else if(strncasecmp(sp->buffer, "DEBUG", 5) == 0 ||
	     strncasecmp(sp->buffer, "WIZ", 3) == 0){
	  sp->unusual++;
	  outtime(sp->tlp);
	  printf(" [smtp] ");
	  outhost(sp->srcaddr);
	  printf(".%d", sp->srcport);
	  outhost(sp->dstaddr);
	  printf(" cmd %s\n", sp->buffer);
	  fflush(stdout);
     }
     else if(strncasecmp(sp->buffer, "MAIL", 4) == 0 ||
	     strncasecmp(sp->buffer, "SEND", 4) == 0){
          char *cp = sp->mailsrc;

	  addline(sp);
	  if(sp->state != SMTP_INIT && sp->state != SMTP_HELO){
	  }
	  sp->state = SMTP_MAIL;
	  strncpy(sp->mailsrc, sp->buffer+5, 1023);
	  sp->mailsrc[1023] = 0;

	  for(;*cp && *cp != ':';cp++)
	       ;
	  if(*cp)
	       cp++;

	  for(;*cp && (isspace(*cp) || *cp == '"' || *cp == '<');cp++)
	       ;

	  if(strncmp(cp,"root",4) == 0 &&
	     (!*(cp+4) || *(cp+4) == '>' || *(cp+4) == '"')){
	       sp->unusual++;
	       sp->rootmail = 1;
	       outtime(sp->tlp);
	       printf(" [smtp] ");
	       outhost(sp->srcaddr);
	       printf(".%d", sp->srcport);
	       outhost(sp->dstaddr);
	       printf(" root mail: %s\n",
		      sp->mailsrc);
	       fflush(stdout);
	  }
	  else if(*cp == '|' || *cp == '/'){
	       if(*cp == '/'){
		    for(cp++;*cp && *cp != '/' && *cp != '=';cp++)
			 ;
	       }
	       if(*cp == '/' || !*cp || *cp == '|'){
		    sp->unusual++;
		    outtime(sp->tlp);
		    printf(" [smtp] ");
		    outhost(sp->srcaddr);
		    printf(".%d", sp->srcport);
		    outhost(sp->dstaddr);
		    printf(" mail from %s: %s\n",
			   *cp == '|' ? "program" : "file",
			   sp->mailsrc);
		    fflush(stdout);
	       }
	  }
     }
     else if(strncasecmp(sp->buffer, "RCPT", 4) == 0){
          char *cp = sp->maildst;

	  addline(sp);
	  if(sp->state != SMTP_MAIL){
	  }
	  sp->state = SMTP_RCPT;
	  strncpy(sp->maildst, sp->buffer+5, 1023);
	  sp->maildst[1023] = 0;

	  for(;*cp && *cp != ':';cp++)
	       ;
	  cp++;

	  for(;*cp && (isspace(*cp) || *cp == '"' || *cp == '<');cp++)
	       ;
	  if(*cp == '/'){
	       while(*cp == '/')
		    cp++;
	       for(;*cp && *cp != '/' && *cp != '=';cp++)
		    ;
	       if(*cp != '='){ /* not an X.400 address */
		    sp->unusual++;
		    outtime(sp->tlp);
		    printf(" [smtp] ");
		    outhost(sp->srcaddr);
		    printf(".%d", sp->srcport);
		    outhost(sp->dstaddr);
		    printf(" mail to file: %s\n",
			   sp->maildst);
		    fflush(stdout);
	       }
	  }
	  if(*cp == '|'){
	       sp->unusual++;
	       outtime(sp->tlp);
	       printf(" [smtp] ");
	       outhost(sp->srcaddr);
	       printf(".%d", sp->srcport);
	       outhost(sp->dstaddr);
	       printf(" mail to program: %s\n", sp->maildst);
	       fflush(stdout);
	  }
     }
     else if(strncasecmp(sp->buffer, "DATA", 4) == 0){
	  sp->state = SMTP_DATA;
     }
     else if(strncasecmp(sp->buffer, "HELO", 4) == 0 ||
	     strncasecmp(sp->buffer, "EHLO", 4) == 0){
	  if(sp->state != SMTP_INIT){
	  }
	  sp->state = SMTP_HELO;
     }
     else if(strcmp(sp->buffer, ".") == 0){
	  if(sp->state != SMTP_DATA){
	  }
	  sp->state = SMTP_HELO;
     }
     else if(strncasecmp(sp->buffer, "VRFY", 4) == 0 ||
	     strncasecmp(sp->buffer, "EXPN", 4) == 0){
	  sp->unusual++;
	  outtime(sp->tlp);
	  printf(" [smtp] ");
	  outhost(sp->srcaddr);
	  printf(".%d", sp->srcport);
	  outhost(sp->dstaddr);
	  printf(" %s\n", sp->buffer);
	  fflush(stdout);
     }
     else if(strncasecmp(sp->buffer, "QUIT", 4) == 0 ||
	     strncasecmp(sp->buffer, "RSET", 4) == 0 ||
	     strncasecmp(sp->buffer, "NOOP", 4) == 0 ||
	     strncasecmp(sp->buffer, "VERB", 4) == 0 ||
	     strncasecmp(sp->buffer, "ONEX", 4) == 0 ||
	     strncasecmp(sp->buffer, "ISOC", 4) == 0 ||
	     strncasecmp(sp->buffer, "MULT", 4) == 0){
	  if(sp->state != SMTP_INIT && sp->state != SMTP_HELO){
	  }
     }
     else if(sp->logitflag || ((sp->linecnt < 5) && sp->synflag)){
	  if(!sp->tdel.tv_sec && !sp->tdel.tv_usec){
	       sp->state = SMTP_DATA; /* Musta missed DATA line */
	       sp->logitflag = 0;
	  }
	  else if(sp->bufptr == -1)
	       sp->state = SMTP_DATA; /* Musta missed DATA line */
	  else {
	       int i;
	       for(i=0;sp->buffer[i] && !isspace(sp->buffer[i]);i++)
		    if(sp->buffer[i] == ':')
			 break;
	       if(sp->buffer[i] == ':' && isspace(sp->buffer[i+1])){
		    sp->state = SMTP_DATA; /* Musta missed DATA line */
		    for(i++;sp->buffer[i] && isspace(sp->buffer[i]);i++)
			 ;
		    while(sp->buffer[i]){
			 if(sp->buffer[i] == '"')
			      i++;
			 else if(sp->buffer[i] == '<')
			      i++;
			 else if((sp->buffer[i] == '/' && sp->buffer[i+1] != '=') ||
				 sp->buffer[i] == '|'){
			      outtime(sp->tlp);
			      printf(" [smtp] ");
			      outhost(sp->srcaddr);
			      printf(".%d", sp->srcport);
			      outhost(sp->dstaddr);
			      printf("%s -> %s; Header %s\n", sp->mailsrc, sp->maildst, sp->buffer);
			      fflush(stdout);
			      break;
			 }
			 else
			      break;
		    }
	       }
	       else {
		    sp->unusual++;
		    sp->logitflag = 1;
		    outtime(sp->tlp);
		    printf(" [smtp] ");
		    outhost(sp->srcaddr);
		    printf(".%d", sp->srcport);
		    outhost(sp->dstaddr);
		    printf(" unknown cmd: %s\n", sp->buffer);
		    fflush(stdout);
	       }
	  }
     }
}

static struct smtp_sess *head = (struct smtp_sess *)0;

smtp_dump(struct ip *ip, struct tcphdr *tcp, struct timeval timebuf)
{
     struct smtp_sess *rove, *prev, *next;
     struct timeval delta;
     static int sigflag = 0;

     for(rove = head;rove;rove=rove->next){
	  if(memcmp((char *)&ip->ip_dst.s_addr,(char *)&rove->dstaddr,4)==0 &&
	     memcmp((char *)&ip->ip_src.s_addr,(char *)&rove->srcaddr,4)==0 &&
	     memcmp((char *)&tcp->th_sport,(char *)&rove->srcport, 2) == 0){
	       break;
	  }
     }
     if(!rove && (tcp->th_flags & (TH_RST|TH_FIN)))
	  return;
     if(!rove){
	  rove = (struct smtp_sess *)malloc(sizeof(struct smtp_sess));
	  memcpy((char *)&rove->dstaddr.s_addr, (char *)&ip->ip_dst, 4);
	  memcpy((char *)&rove->srcaddr.s_addr, (char *)&ip->ip_src, 4);
	  memcpy((char *)&rove->srcport, (char *)&tcp->th_sport, 2);
	  rove->tlp = timebuf;
	  rove->tbp.tv_sec = rove->tbp.tv_usec = 0;
	  rove->tll = timebuf;
	  rove->tbl.tv_sec = rove->tbl.tv_usec = 0;
	  rove->tdel.tv_sec = rove->tdel.tv_usec = 0;
	  rove->pktcnt = 0;
	  rove->bufptr = 0;
	  rove->bodyflag = 0;
	  rove->passwdflag =
	       rove->rootmail = rove->logitflag = 0;
	  rove->synflag = (tcp->th_flags & (TH_SYN|TH_ACK)) == TH_SYN ?
	       1 : 0;
	  rove->state = SMTP_INIT;
	  rove->linecnt = rove->datalinecnt = 0;
	  rove->unusual = 0;
	  rove->body = (struct msgline *)0;
	  rove->maildst[0] = rove->mailsrc[0] = 0;
	  rove->next = head;
	  rove->prev = (struct smtp_sess *)0;
	  if(head)
	       head->prev = rove;
	  head = rove;
     }
     delta = subtime(timebuf,rove->tlp);
     rove->tbp = addtime(rove->tbp, delta);
     rove->pktcnt++;
     rove->tlp = timebuf;

     if(!(tcp->th_flags & ~(TH_PUSH|TH_ACK))){
	  unsigned char offset;
	  int len;
	  char *bp;
	  int i;

	  char *offp;
	  char off;

	  offp = ((char *)&tcp->th_ack)+4;
	  memcpy(&off, offp, 1);
          offset = (off & 0xf0) >> 2;

	  len = ip->ip_len - offset - sizeof(struct ip);
	  bp = ((char *)tcp)+offset;
	  for(i=0;i<len;i++){
	       if(bp[i] == '\n' || bp[i] == '\r'){
		    if(i+1 < len && (bp[i+1] == '\n' || bp[i+1] == '\r'))
			 i++;
		    rove->buffer[rove->bufptr] = 0;
		    delta = subtime(timebuf, rove->tll);
		    rove->tdel = delta;
		    rove->tbl = addtime(rove->tbl, delta);
		    rove->tll = timebuf;
		    if(i+1 < len)
			 rove->bufptr = -1;
		    checkbuffer(rove);
		    rove->bufptr = 0;
	       }
	       else if(rove->bufptr == 1023){
/*		    outtime(rove->tlp);
		    printf(" [smtp] ");
		    outhost(rove->srcaddr);
		    printf(".%d", rove->srcport);
		    outhost(rove->dstaddr);
		    printf(" long line\n");
*/
	       }
	       else if(rove->bufptr < 1023)
		    rove->buffer[rove->bufptr++] = bp[i];
	  }
     }
     else if(tcp->th_flags & TH_RST ||
	     tcp->th_flags & TH_FIN){
	  struct msgline *mrove, *mcurr;

	  rove->tll = timebuf;
	  checksession(rove);

	  for(mrove=rove->body;mrove;){
	       mcurr = mrove;
	       mrove = mcurr->next;
	       free(mcurr->text);
	       free(mcurr);
	  }
	  if(rove == head)
	       head = rove->next;
	  if(rove->prev)
	       rove->prev->next = rove->next;
	  if(rove->next)
	       rove->next->prev = rove->prev;
	  free(rove);
     }

     next = head;
     while(next){
	  if(timebuf.tv_sec - next->tlp.tv_sec > 300){
	       struct msgline *mrove, *mcurr;
	       struct smtp_sess *nextnext = next->next;
	       checksession(next);
	       for(mrove=next->body;mrove;){
		    mcurr = mrove;
		    mrove = mcurr->next;
		    free(mcurr->text);
		    free(mcurr);
	       }
	       if(next == head)
		    head = next->next;
	       if(next->prev)
		    next->prev->next = next->next;
	       if(next->next)
		    next->next->prev = next->prev;
	       free(next);
	       next = nextnext;
	  }
	  else {
	       next = next->next;
	  }
     }
}
