1 /*
2  * mailsnarf.c
3  *
4  * Sniff mail on a network, saving messages in Berkeley mbox format.
5  *
6  * Copyright (c) 1999 Dug Song <dugsong@monkey.org>
7  *
8  * $Id: mailsnarf.c,v 1.38 2001/03/15 08:33:04 dugsong Exp $
9  */
10 
11 #include "config.h"
12 
13 #include <sys/types.h>
14 
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <string.h>
19 #include <time.h>
20 #include <regex.h>
21 #include <err.h>
22 #include <libnet.h>
23 #include <nids.h>
24 #include <pcap.h>
25 
26 #include "pcaputil.h"
27 #include "buf.h"
28 #include "version.h"
29 
30 /* bogus SMTP state machine */
31 enum {
32 	SMTP_NONE = 0,
33 	SMTP_HELO,
34 	SMTP_MAIL,
35 	SMTP_RCPT,
36 	SMTP_DATA
37 };
38 
39 /* likewise, POP. */
40 enum {
41 	POP_NONE = 0,
42 	POP_RETR,
43 	POP_DATA
44 };
45 
46 struct smtp_info {
47 	int state;
48 	char *from;
49 };
50 
51 struct pop_info {
52 	int state;
53 };
54 
55 int	 Opt_invert = 0;
56 regex_t	*pregex = NULL;
57 
58 static void
usage(void)59 usage(void)
60 {
61 	fprintf(stderr, "Version: " VERSION "\n"
62 		"Usage: mailsnarf [-i interface] [[-v] pattern [expression]]\n");
63 	exit(1);
64 }
65 
66 static int
regex_match(char * string)67 regex_match(char *string)
68 {
69 	return (pregex == NULL ||
70 		((regexec(pregex, string, 0, NULL, 0) == 0) ^ Opt_invert));
71 }
72 
73 static char *
grep_mail_address(char * buf)74 grep_mail_address(char *buf)
75 {
76 	char *p, *q;
77 
78 	if ((p = strchr(buf, '<')) != NULL) {
79 		p++;
80 		if ((q = strchr(p, '>')) != NULL)
81 			*q = '\0';
82 		if (strlen(p) > 0)
83 			return (strdup(p));
84 	}
85 	return (NULL);
86 }
87 
88 static void
print_mbox_msg(char * from,char * msg)89 print_mbox_msg(char *from, char *msg)
90 {
91 	char *p;
92 	time_t t;
93 
94 	t = time(NULL);
95 
96 	if (from == NULL)
97 		from = "mailsnarf";
98 
99 	printf("From %s %s", from, ctime(&t));
100 
101 	while ((p = strsep(&msg, "\n")) != NULL) {
102 		if (strncmp(p, "From ", 5) == 0)
103 			putchar('>');
104 		for (; *p != '\r' && *p != '\0'; p++)
105 			putchar(*p);
106 		putchar('\n');
107 	}
108 	putchar('\n');
109 	fflush(stdout);
110 }
111 
112 static int
process_pop_client(struct pop_info * pop,char * data,int len)113 process_pop_client(struct pop_info *pop, char *data, int len)
114 {
115 	struct buf *line, buf;
116 	int i;
117 
118 	buf_init(&buf, data, len);
119 
120 	while ((i = buf_index(&buf, "\r\n", 2)) >= 0) {
121 		line = buf_tok(&buf, NULL, i + 2);
122 		line->base[line->end] = '\0';
123 
124 		if (strncasecmp(buf_ptr(line), "RETR ", 5) == 0) {
125 			pop->state = POP_RETR;
126 		}
127 		else pop->state = POP_NONE;
128 	}
129 	return (len - buf_len(&buf));
130 }
131 
132 static int
process_pop_server(struct pop_info * pop,char * data,int len)133 process_pop_server(struct pop_info *pop, char *data, int len)
134 {
135 	struct buf *line, *body, buf;
136 	int i;
137 
138 	buf_init(&buf, data, len);
139 
140 	if (pop->state == POP_NONE)
141 		return (len);
142 
143 	if (pop->state == POP_RETR) {
144 		if ((i = buf_index(&buf, "\r\n", 2)) < 0)
145 			return (0);
146 
147 		line = buf_tok(&buf, NULL, i + 2);
148 
149 		if (buf_cmp(line, "+OK", 3) == 0) {
150 			pop->state = POP_DATA;
151 		}
152 		else pop->state = POP_NONE;
153 	}
154 	if (pop->state == POP_DATA) {
155 		if ((i = buf_index(&buf, "\r\n.\r\n", 5)) >= 0) {
156 			body = buf_tok(&buf, NULL, i);
157 			buf_skip(&buf, 5);
158 			body->base[body->end] = '\0';
159 
160 			if (regex_match(buf_ptr(body)))
161 				print_mbox_msg(NULL, buf_ptr(body));
162 
163 			pop->state = POP_NONE;
164 		}
165 	}
166 	return (len - buf_len(&buf));
167 }
168 
169 static int
process_smtp_client(struct smtp_info * smtp,char * data,int len)170 process_smtp_client(struct smtp_info *smtp, char *data, int len)
171 {
172 	struct buf *line, *body, buf;
173 	char *p;
174 	int i;
175 
176 	buf_init(&buf, data, len);
177 
178 	if (smtp->state != SMTP_DATA) {
179 		while ((i = buf_index(&buf, "\r\n", 2)) >= 0) {
180 			line = buf_tok(&buf, NULL, i + 2);
181 			line->base[line->end] = '\0';
182 			p = buf_ptr(line);
183 
184 			if (strncasecmp(p, "RSET", 4) == 0) {
185 				smtp->state = SMTP_HELO;
186 			}
187 			else if (smtp->state == SMTP_NONE &&
188 				 (strncasecmp(p, "HELO", 4) == 0 ||
189 				  strncasecmp(p, "EHLO", 4) == 0)) {
190 				smtp->state = SMTP_HELO;
191 			}
192 			else if (smtp->state == SMTP_HELO &&
193 				 (strncasecmp(p, "MAIL ", 5) == 0 ||
194 				  strncasecmp(p, "SEND ", 5) == 0 ||
195 				  strncasecmp(p, "SAML ", 5) == 0)) {
196 				smtp->from = grep_mail_address(p);
197 				smtp->state = SMTP_MAIL;
198 			}
199 			else if (smtp->state == SMTP_MAIL &&
200 				 strncasecmp(p, "RCPT ", 5) == 0) {
201 				smtp->state = SMTP_RCPT;
202 			}
203 			else if (smtp->state == SMTP_RCPT &&
204 				 strncasecmp(p, "DATA", 4) == 0) {
205 				smtp->state = SMTP_DATA;
206 				break;
207 			}
208 		}
209 	}
210 	if (smtp->state == SMTP_DATA) {
211 		if ((i = buf_index(&buf, "\r\n.\r\n", 5)) >= 0) {
212 			body = buf_tok(&buf, NULL, i);
213 			buf_skip(&buf, 5);
214 			body->base[body->end] = '\0';
215 
216 			if (regex_match(buf_ptr(body)))
217 				print_mbox_msg(smtp->from, buf_ptr(body));
218 
219 			if (smtp->from) {
220 				free(smtp->from);
221 				smtp->from = NULL;
222 			}
223 			smtp->state = SMTP_HELO;
224 		}
225 	}
226 	return (len - buf_len(&buf));
227 }
228 
229 static void
sniff_pop_session(struct tcp_stream * ts,struct pop_info ** pop_save)230 sniff_pop_session(struct tcp_stream *ts, struct pop_info **pop_save)
231 {
232 	struct pop_info *pop;
233 	int i;
234 
235 	if (ts->addr.dest != 110 && ts->addr.source != 110 &&	/* POP3 */
236 	    ts->addr.dest != 109 && ts->addr.source != 109 &&	/* POP2 */
237 	    ts->addr.dest != 1109 && ts->addr.source != 1109)	/* KPOP */
238 		return;
239 
240 	switch (ts->nids_state) {
241 
242 	case NIDS_JUST_EST:
243 		ts->server.collect = 1;
244 		ts->client.collect = 1;
245 
246 		if ((pop = (struct pop_info *) malloc(sizeof(*pop))) == NULL)
247 			nids_params.no_mem("sniff_pop_session");
248 
249 		pop->state = POP_NONE;
250 		*pop_save = pop;
251 		break;
252 
253 	case NIDS_DATA:
254 		pop = *pop_save;
255 
256 		if (ts->server.count_new > 0) {
257 			i = process_pop_client(pop, ts->server.data,
258 					       ts->server.count -
259 					       ts->server.offset);
260 			nids_discard(ts, i);
261 		}
262 		else if (ts->client.count_new > 0) {
263 			i = process_pop_server(pop, ts->client.data,
264 					       ts->client.count -
265 					       ts->client.offset);
266 			nids_discard(ts, i);
267 		}
268 		break;
269 
270 	default:
271 		pop = *pop_save;
272 
273 		if (ts->server.count > 0)
274 			process_pop_client(pop, ts->server.data,
275 					   ts->server.count -
276 					   ts->server.offset);
277 		else if (ts->client.count > 0)
278 			process_pop_server(pop, ts->client.data,
279 					   ts->client.count -
280 					   ts->client.offset);
281 		free(pop);
282 		break;
283 	}
284 }
285 
286 /* XXX - Minimal SMTP FSM. We don't even consider server responses. */
287 static void
sniff_smtp_client(struct tcp_stream * ts,struct smtp_info ** smtp_save)288 sniff_smtp_client(struct tcp_stream *ts, struct smtp_info **smtp_save)
289 {
290 	struct smtp_info *smtp;
291 	int i;
292 
293 	if (ts->addr.dest != 25)
294 		return;
295 
296 	switch (ts->nids_state) {
297 
298 	case NIDS_JUST_EST:
299 		ts->server.collect = 1;
300 
301 		if ((smtp = (struct smtp_info *)malloc(sizeof(*smtp))) == NULL)
302 			nids_params.no_mem("sniff_smtp_client");
303 
304 		smtp->state = SMTP_NONE;
305 		smtp->from = NULL;
306 		*smtp_save = smtp;
307 		break;
308 
309 	case NIDS_DATA:
310 		smtp = *smtp_save;
311 
312 		if (ts->server.count_new > 0) {
313 			i = process_smtp_client(smtp, ts->server.data,
314 						ts->server.count -
315 						ts->server.offset);
316 			nids_discard(ts, i);
317 		}
318 		break;
319 
320 	default:
321 		smtp = *smtp_save;
322 
323 		if (ts->server.count > 0) {
324 			process_smtp_client(smtp, ts->server.data,
325 					    ts->server.count -
326 					    ts->server.offset);
327 		}
328 		if (smtp->from)
329 			free(smtp->from);
330 		free(smtp);
331 		break;
332 	}
333 }
334 
335 static void
null_syslog(int type,int errnum,struct ip * iph,void * data)336 null_syslog(int type, int errnum, struct ip *iph, void *data)
337 {
338 }
339 
340 int
main(int argc,char * argv[])341 main(int argc, char *argv[])
342 {
343 	extern char *optarg;
344 	extern int optind;
345 	int c;
346 
347 	while ((c = getopt(argc, argv, "i:vh?V")) != -1) {
348 		switch (c) {
349 		case 'i':
350 			nids_params.device = optarg;
351 			break;
352 		case 'v':
353 			Opt_invert = 1;
354 			break;
355 		default:
356 			usage();
357 		}
358 	}
359 	argc -= optind;
360 	argv += optind;
361 
362 	if (argc > 0 && strlen(argv[0])) {
363 		if ((pregex = (regex_t *) malloc(sizeof(*pregex))) == NULL)
364 			err(1, "malloc");
365 		if (regcomp(pregex, argv[0], REG_EXTENDED|REG_NOSUB) != 0)
366 			errx(1, "invalid regular expression");
367 	}
368 	if (argc > 1)
369 		nids_params.pcap_filter = copy_argv(argv + 1);
370 
371 	nids_params.scan_num_hosts = 0;
372 	nids_params.syslog = null_syslog;
373 
374 	if (!nids_init())
375 		errx(1, "%s", nids_errbuf);
376 
377 	nids_register_tcp(sniff_smtp_client);
378 	nids_register_tcp(sniff_pop_session);
379 
380 	if (nids_params.pcap_filter != NULL) {
381 		warnx("listening on %s [%s]", nids_params.device,
382 		      nids_params.pcap_filter);
383 	}
384 	else warnx("listening on %s", nids_params.device);
385 
386 	nids_run();
387 
388 	/* NOTREACHED */
389 
390 	exit(0);
391 }
392