blob: 51bb975c803a548452135584a92964b774da1c86 [file] [log] [blame] [raw]
/*
* msgsnarf.c
*
* Sniff chat messages (AIM, ICQ, IRC, MSN, Yahoo) on a network.
*
* Copyright (c) 1999 Dug Song <dugsong@monkey.org>
*
* $Id: msgsnarf.c,v 1.11 2001/03/15 08:33:04 dugsong Exp $
*/
#include "config.h"
#include <sys/types.h>
#include <sys/queue.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>
#include <err.h>
#include <libnet.h>
#include <nids.h>
#include <pcap.h>
#include <pcaputil.h>
#include "buf.h"
#include "decode.h"
#include "version.h"
struct client_info {
char *nick;
char *peer;
char *type;
in_addr_t ip;
SLIST_ENTRY(client_info) next;
};
SLIST_HEAD(, client_info) client_list;
int Opt_invert = 0;
regex_t *pregex = NULL;
static void
usage(void)
{
fprintf(stderr, "Version: " VERSION "\n"
"Usage: msgsnarf [-i interface] [[-v] pattern [expression]]\n");
exit(1);
}
static char *
timestamp(void)
{
static char stamp[32];
struct tm *tm;
time_t now;
time(&now);
tm = localtime(&now);
strftime(stamp, sizeof(stamp), "%b %e %T", tm);
return (stamp);
}
static int
regex_match(char *string)
{
return (pregex == NULL ||
((regexec(pregex, string, 0, NULL, 0) == 0) ^ Opt_invert));
}
struct flap {
u_char start;
u_char channel;
u_short seqnum;
u_short datalen;
};
struct snac {
u_short family;
u_short subtype;
u_short flags;
u_int32_t reqid;
};
static int
process_aim(struct client_info *info, u_char *data, int len)
{
struct buf *msg, *word, buf;
struct flap *flap;
struct snac *snac;
u_char c;
char *p;
int i, reply;
buf_init(&buf, data, len);
if (buf_cmp(&buf, "FLAPON\r\n\r\n", 10) == 0)
buf_skip(&buf, 10);
while (buf_len(&buf) > sizeof(*flap)) {
flap = (struct flap *)buf_ptr(&buf);
flap->datalen = ntohs(flap->datalen);
i = sizeof(*flap) + flap->datalen;
if ((msg = buf_tok(&buf, NULL, i)) == NULL)
break;
buf_skip(msg, sizeof(*flap));
snac = (struct snac *)buf_ptr(msg);
if (flap->start != 0x2a)
continue;
if (flap->channel == 0x01) {
if (buf_cmp(msg, "\x00\x00\x00\x01\x00\x01\x00", 7) == 0) {
buf_skip(msg, 7);
buf_get(msg, &c, 1);
if ((word = buf_getbuf(msg, 0, c)) != NULL) {
if (info->nick) free(info->nick);
info->nick = buf_strdup(word);
buf_free(word);
}
buf_skip(msg, 3);
buf_get(msg, &c, 1);
buf_skip(msg, c + 4);
if (buf_cmp(msg, "ICQ", 3) == 0)
info->type = "ICQ";
else info->type = "AIM";
}
}
else if (flap->channel == 0x02) {
if (buf_cmp(msg, "toc_send_im ", 12) == 0) {
buf_skip(msg, 12);
if ((word = buf_getword(msg, " ", 1)) == NULL)
continue;
buf_skip(msg, 1);
if (buf_len(msg) < 3) continue;
msg->end -= 2;
p = buf_strdup(msg);
if (regex_match(p))
printf("%s AIM %s > %.*s: %s\n",
timestamp(), info->nick,
buf_len(word), buf_ptr(word), p);
buf_free(word);
free(p);
}
else if (buf_cmp(msg, "IM_IN:", 6) == 0) {
buf_skip(msg, 6);
if ((word = buf_getword(msg, ":", 1)) == NULL)
continue;
buf_skip(msg, 2);
p = buf_strdup(msg);
if (regex_match(p))
printf("%s AIM %.*s > %s: %s\n",
timestamp(), buf_len(word),
buf_ptr(word), info->nick, p);
buf_free(word);
free(p);
}
else if (ntohs(snac->family) == 0x04) {
if (ntohs(snac->subtype) == 0x06)
reply = 0;
else if (ntohs(snac->subtype) == 0x07)
reply = 1;
else continue;
buf_skip(msg, sizeof(*snac) + 8);
buf_get(msg, &c, 1);
if ((word = buf_getbuf(msg, 0, c)) == NULL)
continue;
/* XXX - ugh, this is totally bogus. help! */
if (buf_cmp(msg, "\x00\x02", 2) == 0) {
buf_skip(msg, 17);
while (buf_cmp(msg, "\x00", 1) == 0)
buf_skip(msg, 1);
}
else if (buf_cmp(msg, "\x00\x05", 2) == 0) {
buf_skip(msg, 97);
}
else if (buf_cmp(msg, "\x00\x00", 2) == 0) {
if (buf_skip(msg, 145) < 0)
buf_skip(msg, 57);
}
p = buf_strdup(msg);
if (p && strlen(p) && regex_match(p)) {
if (reply) {
printf("%s %s %.*s > %s: %s\n",
timestamp(), info->type,
buf_len(word),
buf_ptr(word),
info->nick, p);
}
else printf("%s %s %s > %.*s: %s\n",
timestamp(), info->type,
info->nick, buf_len(word),
buf_ptr(word), p);
}
buf_free(word);
if (p) free(p);
}
}
}
return (len - buf_len(&buf));
}
static int
process_irc(struct client_info *info, u_char *data, int len)
{
struct buf *line, *word, *prefix, buf;
char *p;
int i;
buf_init(&buf, data, len);
while ((i = buf_index(&buf, "\n", 1)) >= 0) {
line = buf_tok(&buf, NULL, i);
buf_skip(&buf, 1);
if (line->base[line->end-1] == '\r')
line->end--;
if (buf_cmp(line, ":", 1) == 0) {
buf_skip(line, 1);
if ((prefix = buf_getword(line, " ", 1)) == NULL)
continue;
if ((i = buf_index(prefix, "!", 1)) < 0)
continue;
prefix->end = i;
}
else prefix = NULL;
if (buf_cmp(line, "JOIN ", 5) == 0 && prefix != NULL) {
buf_skip(line, 5);
if (buf_cmp(line, ":", 1) == 0)
buf_skip(line, 1);
printf("%s IRC *** %.*s ", timestamp(),
buf_len(prefix), buf_ptr(prefix));
prefix->offset = prefix->end + 1;
prefix->end = prefix->size;
printf("(%.*s) has joined channel %.*s\n",
buf_len(prefix), buf_ptr(prefix),
buf_len(line), buf_ptr(line));
}
else if (buf_cmp(line, "PART ", 5) == 0 && prefix != NULL) {
buf_skip(line, 5);
if (buf_cmp(line, ":", 1) == 0)
buf_skip(line, 1);
if ((word = buf_getword(line, " :", 2)) == NULL)
continue;
printf("%s IRC *** %.*s has left channel %.*s\n",
timestamp(), buf_len(prefix), buf_ptr(prefix),
buf_len(word), buf_ptr(word));
buf_free(word);
}
else if (buf_cmp(line, "QUIT ", 5) == 0 && prefix != NULL) {
buf_skip(line, 5);
if (buf_cmp(line, ":", 1) == 0)
buf_skip(line, 1);
printf("%s IRC *** Signoff: %.*s (%.*s)\n",
timestamp(), buf_len(prefix), buf_ptr(prefix),
buf_len(line), buf_ptr(line));
}
else if (buf_cmp(line, "NICK ", 5) == 0) {
buf_skip(line, 5);
if (buf_cmp(line, ":", 1) == 0)
buf_skip(line, 1);
if (prefix != NULL) {
printf("%s IRC *** %.*s is now known as %.*s\n",
timestamp(),
buf_len(prefix), buf_ptr(prefix),
buf_len(line), buf_ptr(line));
}
else {
if (info->nick) free(info->nick);
info->nick = buf_strdup(line);
}
}
else if (buf_cmp(line, "PRIVMSG ", 8) == 0) {
buf_skip(line, 8);
if ((word = buf_getword(line, " :", 2)) == NULL)
continue;
p = buf_strdup(line);
if (regex_match(p)) {
if (strncmp(p + 1, "ACTION ", 7) == 0) {
printf("%s IRC * Action: ",
timestamp());
if (prefix != NULL) {
printf("%.*s %s\n",
buf_len(prefix),
buf_ptr(prefix), p + 8);
}
else printf("%s %s\n",
info->nick, p + 8);
}
else {
if (prefix != NULL) {
printf("%s IRC %.*s > ",
timestamp(),
buf_len(prefix),
buf_ptr(prefix));
}
else printf("%s IRC %s > ",
timestamp(),
info->nick);
printf("%.*s: %s\n", buf_len(word),
buf_ptr(word), p);
}
}
buf_free(word);
free(p);
}
}
return (len - buf_len(&buf));
}
static int
process_msn(struct client_info *info, u_char *data, int len)
{
struct buf *word, *line, buf;
char *p;
int i, reply;
buf_init(&buf, data, len);
while ((i = buf_index(&buf, "\r\n", 2)) >= 0) {
line = buf_tok(&buf, NULL, i);
buf_skip(&buf, 2);
if (buf_cmp(line, "USR ", 4) == 0) {
if ((i = buf_index(line, "MD5 ", 4)) > 0) {
buf_skip(line, i + 4);
if (buf_cmp(line, "I ", 2) == 0) {
buf_skip(line, 2);
if (info->nick != NULL)
free(info->nick);
info->nick = buf_strdup(line);
}
}
}
else if (buf_cmp(line, "IRO ", 4) == 0) {
if ((i = buf_rindex(line, "1 ", 2)) < 0)
continue;
buf_skip(line, i + 2);
word = buf_getword(line, " ", 1);
if (info->peer != NULL) free(info->peer);
info->peer = buf_strdup(word);
buf_free(word);
}
else if (buf_cmp(line, "MSG ", 4) == 0) {
buf_skip(line, 4);
reply = 0;
if ((word = buf_getword(line, " ", 1)) == NULL)
continue;
if (buf_cmp(line, "N ", 2) == 0 ||
buf_cmp(line, "U ", 2) == 0) {
reply = 1;
}
else {
if (info->peer != NULL) free(info->peer);
info->peer = buf_strdup(word);
}
buf_free(word);
if ((i = buf_rindex(line, " ", 1)) < 0)
continue;
buf_skip(line, i + 1);
p = buf_strdup(line);
i = atoi(p); free(p);
if (i <= 0) continue;
if ((line = buf_tok(NULL, NULL, i)) == NULL)
break;
if (buf_index(line, "Content-Type: text/plain", 24) > 0) {
if ((i = buf_rindex(line, "\r\n\r\n", 4)) < 0)
continue;
buf_skip(line, i + 4);
p = buf_strdup(line);
if (regex_match(p)) {
if (reply) {
printf("%s MSN %s > %s: %s\n",
timestamp(), info->nick,
info->peer, p);
}
else printf("%s MSN %s > %s: %s\n",
timestamp(), info->peer,
info->nick, p);
}
free(p);
}
}
}
return (len - buf_len(&buf));
}
struct yhoo {
u_char version[8];
u_int32_t length; /* all fields little-endian */
u_int32_t service;
u_int32_t connid;
u_int32_t magic;
u_int32_t unknown;
u_int32_t type;
u_char nick1[36];
u_char nick2[36];
};
struct ymsg {
u_char version[8];
u_short length;
u_short type;
u_int32_t unknown1;
u_int32_t unknown2;
};
static int
process_yahoo(struct client_info *info, u_char *data, int len)
{
struct yhoo *yhoo;
struct ymsg *ymsg;
struct buf *msg, *nick1, *nick2, buf;
int i, reply;
char *p;
buf_init(&buf, data, len);
if (buf_cmp(&buf, "YMSG", 4) == 0) {
while (buf_len(&buf) > sizeof(*ymsg)) {
ymsg = (struct ymsg *)buf_ptr(&buf);
ymsg->length = ntohs(ymsg->length);
ymsg->type = ntohs(ymsg->type);
i = sizeof(*ymsg) + ymsg->length;
if ((msg = buf_tok(&buf, NULL, i)) == NULL)
break;
buf_skip(msg, sizeof(*ymsg));
if (ymsg->type != 0x06)
continue;
reply = (buf_cmp(msg, "1", 1) != 0);
buf_skip(msg, 3);
nick1 = buf_getword(msg, "\xc0\x80", 2);
buf_skip(msg, 3);
nick2 = buf_getword(msg, "\xc0\x80", 2);
buf_skip(msg, 4);
msg->end -= 2;
p = buf_strdup(msg);
if (regex_match(p) && nick1 && nick2 && msg) {
printf("%s Yahoo ", timestamp());
if (reply)
printf("%.*s > %.*s: %s\n",
buf_len(nick2), buf_ptr(nick2),
buf_len(nick1), buf_ptr(nick1),
p);
else printf("%.*s > %.*s: %s\n",
buf_len(nick1), buf_ptr(nick1),
buf_len(nick2), buf_ptr(nick2), p);
}
if (nick1) buf_free(nick1);
if (nick2) buf_free(nick2);
free(p);
}
}
else {
while (buf_len(&buf) > sizeof(*yhoo)) {
yhoo = (struct yhoo *)buf_ptr(&buf);
yhoo->length = pletohl(&yhoo->length);
yhoo->service = pletohl(&yhoo->service);
yhoo->type = pletohl(&yhoo->type);
yhoo->nick1[sizeof(yhoo->nick1) - 1] = '\0';
yhoo->nick2[sizeof(yhoo->nick2) - 1] = '\0';
i = sizeof(*yhoo) + yhoo->length;
if ((msg = buf_tok(&buf, NULL, i)) == NULL)
break;
buf_skip(msg, sizeof(*yhoo));
if (yhoo->service != 6 || yhoo->type > 1)
continue;
if ((nick1 = buf_getword(msg, ",", 1)) == NULL)
continue;
if (memcmp(yhoo->version, "YHOO", 4) == 0) {
buf_skip(msg, 1);
reply = 0;
}
else reply = 1;
p = buf_strdup(msg);
if (regex_match(p)) {
if (reply)
printf("%s Yahoo %.*s > %s: %s\n",
timestamp(),
buf_len(nick1), buf_ptr(nick1),
yhoo->nick2, p);
else
printf("%s Yahoo %s > %.*s: %s\n",
timestamp(), yhoo->nick2,
buf_len(nick1), buf_ptr(nick1),
buf_ptr(msg));
}
free(p);
}
}
return (len - buf_len(&buf));
}
static void
sniff_msgs(struct tcp_stream *ts, void **conn_save)
{
struct client_info *c;
int (*process_msgs)(struct client_info *, u_char *, int);
int i;
if (ts->addr.dest >= 6660 && ts->addr.dest <= 6680) {
process_msgs = process_irc;
}
else if (ts->addr.dest == 5190 || ts->addr.dest == 9898) {
process_msgs = process_aim;
}
else if (ts->addr.dest == 5050) {
process_msgs = process_yahoo;
}
else if (ts->addr.dest == 1863) {
process_msgs = process_msn;
}
else return;
switch (ts->nids_state) {
case NIDS_JUST_EST:
ts->server.collect = 1;
ts->client.collect = 1;
i = 0;
SLIST_FOREACH(c, &client_list, next) {
if (c->ip == ts->addr.saddr) {
i = 1; break;
}
}
if (i == 0) {
if ((c = malloc(sizeof(*c))) == NULL)
nids_params.no_mem("sniff_msgs");
c->ip = ts->addr.saddr;
c->nick = strdup("unknown");
SLIST_INSERT_HEAD(&client_list, c, next);
}
*conn_save = (void *)c;
break;
case NIDS_DATA:
c = (struct client_info *)*conn_save;
if (ts->server.count_new > 0) {
i = process_msgs(c, (u_char *)ts->server.data,
ts->server.count - ts->server.offset);
nids_discard(ts, i);
}
else if (ts->client.count_new > 0) {
i = process_msgs(c, (u_char *)ts->client.data,
ts->client.count - ts->client.offset);
nids_discard(ts, i);
}
fflush(stdout);
break;
default:
c = (struct client_info *)*conn_save;
if (ts->server.count > 0)
process_msgs(c, (u_char *)ts->server.data,
ts->server.count - ts->server.offset);
else if (ts->client.count > 0)
process_msgs(c, (u_char *)ts->client.data,
ts->client.count - ts->client.offset);
fflush(stdout);
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:hv?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);
SLIST_INIT(&client_list);
nids_register_tcp(sniff_msgs);
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);
}