// (c) Blu3Viper, david@kalifornia.com, freely redistributable in original
// form.  please do not distribute modified code without sending me a patch
//
// short hack to parse the specified file and report who sent what
//
// can starve resources, use with caution.  the entire logfile is read
// into memory.

// read file
//  tokenise by EOL
//   skip on field marks looking for keywords
//    drop target flags
//   print targets
// print summary
// closefile

// gcc -l smlp -O6 -mamdk6 -march=amdk6 -funroll-all-loops \
//  -fomit-frame-pointer -o smlp
//
// DO NOT use -ffast-math if you want commafy() to work.  if you do use it
// commas will not be printed, but you will see an increase in lines/sec

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>

static char rcsid[] = "#Id: smlp.c,v 0.4 1998/06/21 06:29:04 david Exp #";

char *commafy(unsigned long long val);

int main(int argc, char *argv[])
{
struct stat st;
struct tm *tm;
struct timeval tv1, tv2;
int seconds;
unsigned long long microseconds;
time_t timep;

struct tflags {
	char *date, *from, *to, *relay, *stat, *ctladdr, *msgid, *lmid;
	int size, class, pri, nrcpts, noqueue;
	} tflags;

struct summary {
	unsigned int msent, mreceived;
	unsigned failures, deferred, rejected;
	unsigned int bytesin, bytesout;		// not properly implemented yet
	unsigned int tdelay, txdelay;
	unsigned int local, smtp;
	unsigned int lines;
	} summary;
	
char *bigblock, *bbp, *line, *lp, *tp;
char *logfile;
int options=0;
int fd, ef;

line=NULL;

if(argc<2) {
	fprintf(stdout, "usage: smlp [-options] <logfile>\n");
	fprintf(stdout, "       print headers with the following targets:");
	fprintf(stdout, "       -c  ctladdr\n");
	fprintf(stdout, "       -d  delivery status\n");
	fprintf(stdout, "       -l  message ID (local)\n");
	fprintf(stdout, "       -r  relay\n");
	fprintf(stdout, "       -S  summary (default)\n");
	fprintf(stdout, "       -s  sender\n");
	fprintf(stdout, "       -t  recipient\n");
	fprintf(stdout, "       -u  message ID (uniqe)\n\n");
	
	fprintf(stdout, "  example: smlp -Irs /var/log/mail\n");
	exit(-1);
	}

if(argc==2) options=32;	// summary only
	else {
	tp=argv[1];
	if(*tp=='-') tp++;
	do {
		switch (*tp) {
			case 'c':
				options|=1; break;
			case 'f':
				options|=2; break;
			case 'I':
				options|=4; break;
			case 'm':
				options|=8; break;
			case 'r':
				options|=16; break;
			case 'S':
				options|=32; break;
			case 's':
				options|=64; break;
			case 't':
				options|=128; break;
			default:
				break;
			}
		tp++;
		} while(*tp);
	}

logfile=argv[argc-1];
gettimeofday(&tv1, NULL);	// record start time

fd=open(logfile, O_RDONLY);
if(fd<0) {
	fprintf(stderr, "ewps...logfile open err: %s(%i)\n", logfile, errno);
	exit(-1);
	}
//	else fprintf(stdout, "opened fd=%i\n", fd);

ef=stat(logfile, &st);
if(ef<0) {
	fprintf(stderr, "err stating logfile: %i\n", errno);
	exit(-1);
	}

if(st.st_size <1) {
	fprintf(stderr, "logfile size: %li\n", st.st_size);
	exit(-1);
	}
//	else fprintf(stdout, "size=%li\n", st.st_size);

bigblock=malloc(st.st_size+2);
if(!bigblock) {
	fprintf(stderr, "malloc failed requesting %li bytes\n", st.st_size);
	exit(-1);
	}

ef=read(fd, bigblock, st.st_size);
if (ef<st.st_size) {
	fprintf(stderr, "error reading in logfile, bytes tried/read %li/%i\n",
		st.st_size, ef);
	exit(-1);
	}

*(bigblock+st.st_size+1)='\n';
*(bigblock+st.st_size+2)='\n';

bzero(&summary, sizeof(summary));

// we're ready to walk the entries now.
bbp=strtok(bigblock, "\n");
if(!bbp) {
	fprintf(stderr, "eek, logfile is not terminated\n");
	exit(-1);
	}

do {
	if(!bbp)
  		continue;
  		
  	summary.lines++;
	bzero(&tflags, sizeof(tflags));
  	lp=bbp;
  	
  	// get the entry date
  	tflags.date=lp;
  	*(lp+15)='\0'; lp+=16;	// this could write outside our mem space..
  	
  	// skip the host, we really should check the return value
  	lp=strchr(lp, ' ')+1;  	
  	
  	// is this line the sendmail deamon?  goto next line
  	if(strncmp(lp, "sendmail", 8)) continue;
  	
  	// jump past the daemon name
  	lp=strchr(lp, ' ')+1;
  	
  	// NOQUEUE, lmid, etc
  	if(strncmp(lp, "NOQUEUE:", 8)==0) {
  		tflags.noqueue=1;
  		summary.failures++;
  		// one of these days..print them
  		continue;
  		}

  	if(strncmp(lp, "runqueue:", 9)==0) {
  		continue;
  		}

  	if(strstr(lp, "uleset")!=NULL) {
  		summary.rejected++;
  		continue;
  		}

  	if(strncmp(lp, "gethostbyaddr:", 14)==0) {
  		continue;
  		}

  	// short message ID
  	tflags.lmid=lp;
  	
  	*(lp+8)='\0'; lp+=10;	// this could write outside our mem space..
  	
  	// either from or to
  	switch (*lp) {
  		case 'f':
  			if(strncmp(lp, "from=", 5)!=0) continue;
  			tflags.from=lp+5;
  			summary.mreceived++;
  			
  			lp=strchr(lp, ' ')+1;
  			*(lp-2)='\0';

			if(*lp=='s') {
				tflags.size=atoi(lp+6);
				summary.bytesout+=tflags.size;
				}

  			lp=strchr(lp, ' ')+1;
  			if(*lp=='c') tflags.class=atoi(lp+5);
  			
  			lp=strchr(lp, ' ')+1;
  			if(*lp=='p') tflags.pri=atoi(lp+4);
  			
  			lp=strchr(lp, ' ')+1;
  			if(*lp=='n') tflags.nrcpts=atoi(lp+7);
  			
  			lp=strchr(lp, ' ')+1;
  			if(*lp=='m') {
  				tflags.msgid=lp+6;
  				lp=strchr(lp, ' ')+1;
  				*(lp-2)='\0';
  				if(*lp=='b') lp=strchr(lp, ' ')+1;
  				if(*lp=='p') lp=strchr(lp, ' ')+1;
  				}

  			if(*lp=='r') tflags.relay=lp+6;
  			break;
  			
  		case 't':
  			if(strncmp(lp, "to=", 3)!=0) continue;
  			lp+=3;
  			tflags.to=lp;
  			summary.msent++;
  			
  			if(*lp=='"') lp=strchr(lp+1, '"');
  			lp=strchr(lp, ',')+2;
  			*(lp-2)='\0';
  			
  			// ctladdr
  			if (*lp == 'c'){
  				lp+=8;
  				tflags.ctladdr=lp;
  				lp=strchr(lp, ',')+1;
  				*(lp-1)='\0';
  				}
  			
  			lp+=6;
  			
  			// delay
  			tp=strchr(lp, '+');
  			if(tp && (tp-lp <8)) {	// check for days
				summary.tdelay+=(atoi(lp)*86400);
  				lp=tp+1;
  				}
  			summary.tdelay+=atoi(lp)*3600;lp+=3;
  			summary.tdelay+=atoi(lp)*60;lp+=3;
  			summary.tdelay+=atoi(lp);
  			
  			lp=strchr(lp, ' ')+1;
  			
  			// xdelay
  			if(*lp =='x'){
  				lp=strchr(lp, '=')+1;
	  			tp=strchr(lp, '+');
  				if(tp && (tp-lp <8)) {	// check for days
  					summary.txdelay+=(atoi(lp)*86400);
  					lp=tp+1;
  					}
	  			summary.txdelay+=atoi(lp)*3600;lp+=3;
  				summary.txdelay+=atoi(lp)*60;lp+=3;
  				summary.txdelay+=atoi(lp);
  				lp=strchr(lp, ' ')+8;
  				}
  			
  			// mailer
  			switch (*lp) {
  				case 'p':	// program
  				case 'l':	// local
  					summary.local+=1;
  					break;
  				case 's':	// smtp
  				case 'e':	// esmtp
  					summary.smtp+=1;
  					break;
  				default:
  					break;
  				}
  			
  			lp=strchr(lp, ' ')+1;
  			
  			// relay
  			if(*lp == 'r'){
  				lp+=6;
  				tflags.relay=lp;
  				lp=strchr(lp, ' ')+1;
  				if(*lp != 's') lp=strchr(lp, ' ')+1;
  				*(lp-2)='\0';
  				}

  			// status
  			lp+=5;
  			tflags.stat=lp;
  			if(*lp== 'D') summary.deferred++;
  			break;
  			
  		default:
  			if(strstr(lp, "DSN: ")==0) summary.failures++;
  			if(strstr(lp, "postmaster notify: ")==0) summary.failures++;
  			if(strstr(lp, "return to sender: ")==0) summary.failures++;
  			if(strstr(lp, "SYSERR")==0) summary.failures++;
  			if(strstr(lp, "User unknown")==0) summary.failures++;
  			if(strstr(lp, "timeout waiting for input")==0) summary.failures++;
  			
  			break;
  		}
  	
        ef=0;
	if(options&4) {
		fprintf(stdout, "lmid=%s ", tflags.lmid); ef=1;
		}
	if(options&1 && tflags.ctladdr) {
		fprintf(stdout, "ctrladdr=%s ", tflags.ctladdr); ef=1;
		}
	if(options&2 && tflags.from) {
		fprintf(stdout, "from=%s ", tflags.from); ef=1;
		}
	if(options&128 && tflags.to) {
		fprintf(stdout, "to=%s ", tflags.to); ef=1;
		}
	if(options&8 && tflags.msgid) {
		fprintf(stdout, "msgid=%s ", tflags.msgid); ef=1;
		}
	if(options&16 && tflags.relay) {
		fprintf(stdout, "relay=%s ", tflags.relay); ef=1;
		}
	if(options&64 && tflags.stat) {
		fprintf(stdout, "stat=%s ", tflags.stat); ef=1;
		}
	if((options-32)!=0 && ef) {
		fprintf(stdout, "\n"); ef=1;
		}
	
	} while ((bbp=strtok(NULL, "\n")));

if(options&32) {
	tp=malloc(65);
	gettimeofday(&tv2, NULL);
	seconds=tv2.tv_sec-tv1.tv_sec;
	if((tv2.tv_sec >tv1.tv_sec) && (tv2.tv_usec > tv1.tv_usec)) {
		microseconds=tv2.tv_usec-tv1.tv_usec;
		}
		else if(tv2.tv_sec >tv1.tv_sec) {
			microseconds=(tv2.tv_usec+1000000)-tv1.tv_usec;
			}
			else microseconds=tv2.tv_usec-tv1.tv_usec;
	
	ef=time(NULL)-timep;
	timep=time(NULL);
	tm=localtime(&timep);
	strftime(tp, 64, "%A(%H:%M), %B %d, %Y", tm);
	if(options!=32) fprintf(stdout, "\n");
	fprintf(stdout, "Summary for file: %s\n", logfile);
	fprintf(stdout, "Rundate: %s\n", tp);
	fprintf(stdout, "Runtime: %i.%06.6li seconds\n", seconds, microseconds);
	fprintf(stdout, "--------------------------------------------\n");
	fprintf(stdout, "     messages sent: %12.12s\n", commafy(summary.msent));
	fprintf(stdout, "          received: %12.12s\n", commafy(summary.mreceived));
	fprintf(stdout, " delivery failures: %12.12s\n", commafy(summary.failures));
	fprintf(stdout, "          deferred: %12.12s\n", commafy(summary.deferred));
	fprintf(stdout, " rejected(ruleset): %12.12s\n", commafy(summary.rejected));
	fprintf(stdout, " bytes transmitted: %12.12s\n", commafy(summary.bytesout));
	fprintf(stdout, "    local delivery: %12.12s\n", commafy(summary.local));
	fprintf(stdout, "     smtp delivery: %12.12s\n", commafy(summary.smtp));
	fprintf(stdout, "logfile line count: %12.12s\n\n", commafy(summary.lines));
	}
exit(0);
}

char *commafy(unsigned long long val)
{
char *buf1, *buf2;
int len, i, p2;

buf1=malloc(40);
memset(buf1, 0, 40);
buf2=malloc(40);
memset(buf2, 0, 40);
sprintf(buf1, "%Ld", val);
len=strlen(buf1);
p2=0;

for(i=len; i>=0; i--){
	*(buf2+(40-p2)) = *(buf1+i);
	p2++;
	if( (i) && (len-i) && (((len-i)/3) == ((float)(len-i)/3)) ) {
		*(buf2+(40-p2)) = ',';
		p2++;
		}
	}

return(buf2+(41-p2));
}
                    