blob: 4c982de7050392191363ba1a506d12d6872e7b05 [file] [log] [blame] [raw]
/*
* 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.38 2001/03/15 08:33:04 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>
#include <err.h>
#include <libnet.h>
#include <nids.h>
#include <pcap.h>
#include "pcaputil.h"
#include "buf.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 state;
char *from;
};
struct pop_info {
int state;
};
int Opt_invert = 0;
regex_t *pregex = NULL;
static void
usage(void)
{
fprintf(stderr, "Version: " VERSION "\n"
"Usage: mailsnarf [-i interface] [[-v] pattern [expression]]\n");
exit(1);
}
static int
regex_match(char *string)
{
return (pregex == NULL ||
((regexec(pregex, string, 0, NULL, 0) == 0) ^ Opt_invert));
}
static 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);
}
static void
print_mbox_msg(char *from, char *msg)
{
char *p;
time_t t;
t = time(NULL);
if (from == NULL)
from = "mailsnarf";
printf("From %s %s", from, ctime(&t));
while ((p = strsep(&msg, "\n")) != NULL) {
if (strncmp(p, "From ", 5) == 0)
putchar('>');
for (; *p != '\r' && *p != '\0'; p++)
putchar(*p);
putchar('\n');
}
putchar('\n');
fflush(stdout);
}
static int
process_pop_client(struct pop_info *pop, char *data, int len)
{
struct buf *line, buf;
int i;
buf_init(&buf, (u_char *)data, len);
while ((i = buf_index(&buf, "\r\n", 2)) >= 0) {
line = buf_tok(&buf, NULL, i + 2);
line->base[line->end] = '\0';
if (strncasecmp((char *)buf_ptr(line), "RETR ", 5) == 0) {
pop->state = POP_RETR;
}
else pop->state = POP_NONE;
}
return (len - buf_len(&buf));
}
static int
process_pop_server(struct pop_info *pop, char *data, int len)
{
struct buf *line, *body, buf;
int i;
buf_init(&buf, (u_char *)data, len);
if (pop->state == POP_NONE)
return (len);
if (pop->state == POP_RETR) {
if ((i = buf_index(&buf, "\r\n", 2)) < 0)
return (0);
line = buf_tok(&buf, NULL, i + 2);
if (buf_cmp(line, "+OK", 3) == 0) {
pop->state = POP_DATA;
}
else pop->state = POP_NONE;
}
if (pop->state == POP_DATA) {
if ((i = buf_index(&buf, "\r\n.\r\n", 5)) >= 0) {
body = buf_tok(&buf, NULL, i);
buf_skip(&buf, 5);
body->base[body->end] = '\0';
if (regex_match((char *)buf_ptr(body)))
print_mbox_msg(NULL, (char *)buf_ptr(body));
pop->state = POP_NONE;
}
}
return (len - buf_len(&buf));
}
static int
process_smtp_client(struct smtp_info *smtp, char *data, int len)
{
struct buf *line, *body, buf;
char *p;
int i;
buf_init(&buf, (u_char *)data, len);
if (smtp->state != SMTP_DATA) {
while ((i = buf_index(&buf, "\r\n", 2)) >= 0) {
line = buf_tok(&buf, NULL, i + 2);
line->base[line->end] = '\0';
p = (char *)buf_ptr(line);
if (strncasecmp(p, "RSET", 4) == 0) {
smtp->state = SMTP_HELO;
}
else if (smtp->state == SMTP_NONE &&
(strncasecmp(p, "HELO", 4) == 0 ||
strncasecmp(p, "EHLO", 4) == 0)) {
smtp->state = SMTP_HELO;
}
else if (smtp->state == SMTP_HELO &&
(strncasecmp(p, "MAIL ", 5) == 0 ||
strncasecmp(p, "SEND ", 5) == 0 ||
strncasecmp(p, "SAML ", 5) == 0)) {
smtp->from = grep_mail_address(p);
smtp->state = SMTP_MAIL;
}
else if (smtp->state == SMTP_MAIL &&
strncasecmp(p, "RCPT ", 5) == 0) {
smtp->state = SMTP_RCPT;
}
else if (smtp->state == SMTP_RCPT &&
strncasecmp(p, "DATA", 4) == 0) {
smtp->state = SMTP_DATA;
break;
}
}
}
if (smtp->state == SMTP_DATA) {
if ((i = buf_index(&buf, "\r\n.\r\n", 5)) >= 0) {
body = buf_tok(&buf, NULL, i);
buf_skip(&buf, 5);
body->base[body->end] = '\0';
if (regex_match((char *)buf_ptr(body)))
print_mbox_msg(smtp->from, (char *)buf_ptr(body));
if (smtp->from) {
free(smtp->from);
smtp->from = NULL;
}
smtp->state = SMTP_HELO;
}
}
return (len - buf_len(&buf));
}
static void
sniff_pop_session(struct tcp_stream *ts, struct pop_info **pop_save)
{
struct pop_info *pop;
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 ((pop = (struct pop_info *) malloc(sizeof(*pop))) == NULL)
nids_params.no_mem("sniff_pop_session");
pop->state = POP_NONE;
*pop_save = pop;
break;
case NIDS_DATA:
pop = *pop_save;
if (ts->server.count_new > 0) {
i = process_pop_client(pop, ts->server.data,
ts->server.count -
ts->server.offset);
nids_discard(ts, i);
}
else if (ts->client.count_new > 0) {
i = process_pop_server(pop, ts->client.data,
ts->client.count -
ts->client.offset);
nids_discard(ts, i);
}
break;
default:
pop = *pop_save;
if (ts->server.count > 0)
process_pop_client(pop, ts->server.data,
ts->server.count -
ts->server.offset);
else if (ts->client.count > 0)
process_pop_server(pop, ts->client.data,
ts->client.count -
ts->client.offset);
free(pop);
break;
}
}
/* XXX - Minimal SMTP FSM. We don't even consider server responses. */
static void
sniff_smtp_client(struct tcp_stream *ts, struct smtp_info **smtp_save)
{
struct smtp_info *smtp;
int i;
if (ts->addr.dest != 25)
return;
switch (ts->nids_state) {
case NIDS_JUST_EST:
ts->server.collect = 1;
if ((smtp = (struct smtp_info *)malloc(sizeof(*smtp))) == NULL)
nids_params.no_mem("sniff_smtp_client");
smtp->state = SMTP_NONE;
smtp->from = NULL;
*smtp_save = smtp;
break;
case NIDS_DATA:
smtp = *smtp_save;
if (ts->server.count_new > 0) {
i = process_smtp_client(smtp, ts->server.data,
ts->server.count -
ts->server.offset);
nids_discard(ts, i);
}
break;
default:
smtp = *smtp_save;
if (ts->server.count > 0) {
process_smtp_client(smtp, ts->server.data,
ts->server.count -
ts->server.offset);
}
if (smtp->from)
free(smtp->from);
free(smtp);
break;
}
}
static void
null_syslog(int type, int errnum, struct ip *iph, void *data)
{
}
int
main(int argc, char *argv[])
{
extern char *optarg;
extern int optind;
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 > 0 && strlen(argv[0])) {
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");
}
if (argc > 1)
nids_params.pcap_filter = copy_argv(argv + 1);
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);
if (nids_params.pcap_filter != NULL) {
warnx("listening on %s [%s]", nids_params.device,
nids_params.pcap_filter);
}
else warnx("listening on %s", nids_params.device);
nids_run();
/* NOTREACHED */
exit(0);
}