/*
  mailsnarf.c

  Sniff mail on a network, saving messages in Berkeley mbox format.

  Copyright (c) 1999 Dug Song <dugsong@monkey.org>
  
  $Id: mailsnarf.c,v 1.31 2000/06/14 04:42:50 dugsong Exp $
*/

#include "config.h"

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <regex.h>
#ifdef HAVE_ERR_H
#include <err.h>
#endif
#include <libnet.h>
#include <nids.h>
#include "version.h"

/* bogus SMTP state machine */
enum {
	SMTP_NONE = 0,
	SMTP_HELO,
	SMTP_MAIL,
	SMTP_RCPT,
	SMTP_DATA
};

/* likewise, POP. */
enum {
	POP_NONE = 0,
	POP_RETR,
	POP_DATA
};

struct smtp_info {
	int smtp_state;
	char *from;
};

struct pop_info {
	int pop_state;
};

int	 Opt_invert = 0;
regex_t	*pregex = NULL;

void
usage(void)
{
	fprintf(stderr, "Version: " VERSION "\n"
		"Usage: mailsnarf [-i interface] [[-v] pattern]\n");
	exit(1);
}

u_char *
bufbuf(u_char *big, int blen, u_char *little, int llen)
{
	u_char *p;
	
	for (p = big; p <= big + blen - llen; p++) {
		if (memcmp(p, little, llen) == 0)
			return (p);
	}
	return (NULL);
}

char *
grep_mail_address(char *buf)
{
	char *p, *q;
	
	if ((p = strchr(buf, '<')) != NULL) {
		p++;
		if ((q = strchr(p, '>')) != NULL)
			*q = '\0';
		if (strlen(p) > 0)
			return (strdup(p));
	}
	return (NULL);
}

void
print_mbox_msg(char *from, char *buf)
{
	char *s;
	time_t t;

	if (pregex != NULL &&
	    regexec(pregex, buf, 0, NULL, 0) != 0 && !Opt_invert)
		return;
	
	t = time(NULL);
	printf("From %s %s", from ? from : "mailsnarf", ctime(&t));
		
	for (s = strtok(buf, "\n"); s != NULL; s = strtok(NULL, "\n")) {
		if (strncmp(s, "From ", 5) == 0)
			putchar('>');
		for (; *s != '\r' && *s != '\0'; s++)
			putchar(*s);
		putchar('\n');
	}
	putchar('\n');
	fflush(stdout);
}

int
process_pop_client(struct pop_info *msg, char *buf, int len)
{
	char *s, *e;
	
	for (s = buf; (e = bufbuf(s, len, "\r\n", 2)) != NULL; s = e + 2) {
		len -= ((e + 2) - s);
		*e = '\0';
		
		if (strncasecmp(s, "RETR ", 5) == 0) {
			msg->pop_state = POP_RETR;
		}
		else msg->pop_state = POP_NONE;
	}
	return (s - buf);
}

int
process_pop_server(struct pop_info *msg, char *buf, int len)
{
	char *p, *q;
	int discard = 0;
	
	if (msg->pop_state == POP_NONE) {
		discard = len;
	}
	else if (msg->pop_state == POP_RETR) {
		if ((p = bufbuf(buf, len, "\r\n", 2)) == NULL)
			return (0);
		
		*p = '\0'; p += 2;
		
		if (strncmp(buf, "+OK", 3) == 0) {
			msg->pop_state = POP_DATA;
		}
		else msg->pop_state = POP_NONE;
		
		discard = (p - buf);
	}
	if (msg->pop_state == POP_DATA) {
		p = buf + discard;
		
		if ((q = bufbuf(p, len - discard, "\r\n.\r\n", 5)) != NULL) {
			*q = '\0';
			print_mbox_msg(NULL, p);
			discard += (q - p + 5);
			msg->pop_state = POP_NONE;
		}
	}
	return (discard);
}

int
process_smtp_client(struct smtp_info *msg, char *buf, int len)
{
	char *s, *e;
	int i, discard = 0;
	
	if (msg->smtp_state != SMTP_DATA) {
		for (s = buf;  (e = bufbuf(s, len, "\r\n", 2)) != NULL;
		     s = e + 2) {
			i = (e + 2) - s;
			discard += i; len -= i;
			*e = '\0';
			
			if (strncasecmp(s, "RSET", 4) == 0) {
				msg->smtp_state = SMTP_HELO;
			}
			else if (msg->smtp_state == SMTP_NONE &&
				 (strncasecmp(s, "HELO", 4) == 0 ||
				  strncasecmp(s, "EHLO", 4) == 0)) {
				msg->smtp_state = SMTP_HELO;
			}
			else if (msg->smtp_state == SMTP_HELO &&
				 (strncasecmp(s, "MAIL ", 5) == 0 ||
				  strncasecmp(s, "SEND ", 5) == 0 ||
				  strncasecmp(s, "SAML ", 5) == 0)) {
				msg->from = grep_mail_address(s);
				msg->smtp_state = SMTP_MAIL;
			}
			else if (msg->smtp_state == SMTP_MAIL &&
				 strncasecmp(s, "RCPT ", 5) == 0) {
				msg->smtp_state = SMTP_RCPT;
			}
			else if (msg->smtp_state == SMTP_RCPT &&
				 strncasecmp(s, "DATA", 4) == 0) {
				msg->smtp_state = SMTP_DATA;
				break;
			}
		}
		buf = s;
	}
	if (msg->smtp_state == SMTP_DATA) {
		if ((e = bufbuf(buf, len, "\r\n.\r\n", 5)) != NULL) {
			i = (e + 5) - buf;
			discard += i; len -= i;
			*e = '\0';
			
			print_mbox_msg(msg->from, buf);
			
			if (msg->from) {
				free(msg->from);
				msg->from = NULL;
			}
		}
	}
	return (discard);
}

void
sniff_pop_session(struct tcp_stream *ts, struct pop_info **msg_save)
{
	struct pop_info *msg;
	int i;
	
	if (ts->addr.dest != 110 && ts->addr.source != 110 &&	/* POP3 */
	    ts->addr.dest != 109 && ts->addr.source != 109 &&	/* POP2 */
	    ts->addr.dest != 1109 && ts->addr.source != 1109)	/* KPOP */
		return;
	
	switch (ts->nids_state) {
		
	case NIDS_JUST_EST:
		ts->server.collect = 1;
		ts->client.collect = 1;
		
		if ((msg = (struct pop_info *) malloc(sizeof(*msg))) == NULL)
			nids_params.no_mem("sniff_pop_session");
		
		msg->pop_state = POP_NONE;
		*msg_save = msg;
		break;

	case NIDS_DATA:
		msg = *msg_save;
		
		if (ts->server.count_new > 0) {
			i = process_pop_client(msg, ts->server.data,
					       ts->server.count -
					       ts->server.offset);
			nids_discard(ts, i);
		}
		else if (ts->client.count_new > 0) {
			i = process_pop_server(msg, ts->client.data,
					       ts->client.count -
					       ts->client.offset);
			nids_discard(ts, i);
		}
		break;
		
	default:
		msg = *msg_save;
		
		if (ts->server.count > 0)
			process_pop_client(msg, ts->server.data,
					   ts->server.count -
					   ts->server.offset);
		else if (ts->client.count > 0)
			process_pop_server(msg, ts->client.data,
					   ts->client.count -
					   ts->client.offset);
		free(msg);
		break;
	}
}

/* XXX - Minimal SMTP FSM. We don't even consider server responses. */
void
sniff_smtp_client(struct tcp_stream *ts, struct smtp_info **msg_save)
{
	struct smtp_info *msg;
	int i;
	
	if (ts->addr.dest != 25)
		return;
	
	switch (ts->nids_state) {
		
	case NIDS_JUST_EST:
		ts->server.collect = 1;
		
		if ((msg = (struct smtp_info *) malloc(sizeof(*msg))) == NULL)
			nids_params.no_mem("sniff_smtp_client");
		
		msg->smtp_state = SMTP_NONE;
		msg->from = NULL;
		*msg_save = msg;
		break;
		
	case NIDS_DATA:
		msg = *msg_save;
		
		if (ts->server.count_new > 0) {
			i = process_smtp_client(msg, ts->server.data,
						ts->server.count -
						ts->server.offset);
			nids_discard(ts, i);
		}
		break;
		
	default:
		msg = *msg_save;
		
		if (ts->server.count > 0) {
			process_smtp_client(msg, ts->server.data,
					    ts->server.count -
					    ts->server.offset);
		}
		if (msg->from)
			free(msg->from);
		free(msg);
		break;
	}
}

void
null_syslog(int type, int errnum, struct ip *iph, void *data)
{
}

int
main(int argc, char *argv[])
{
	int c;
	
	while ((c = getopt(argc, argv, "i:vh?V")) != -1) {
		switch (c) {
		case 'i':
			nids_params.device = optarg;
			break;
		case 'v':
			Opt_invert = 1;
			break;
		default:
			usage();
		}
	}
	argc -= optind;
	argv += optind;
	
	if (argc == 1) {
		if ((pregex = (regex_t *) malloc(sizeof(*pregex))) == NULL)
			err(1, "malloc");
		if (regcomp(pregex, argv[0], REG_EXTENDED|REG_NOSUB) != 0)
			errx(1, "invalid regular expression");
	}
	else if (argc != 0) usage();
	
	nids_params.scan_num_hosts = 0;
	nids_params.syslog = null_syslog;
	
	if (!nids_init())
		errx(1, "%s", nids_errbuf);
	
	nids_register_tcp(sniff_smtp_client);
	nids_register_tcp(sniff_pop_session);

	warnx("listening on %s", nids_params.device);
	nids_run();
	
	/* NOTREACHED */
	
	exit(0);
}

/* 5000. */
