| /* |
| * 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); |
| } |