/*
  urlsnarf.c
  
  Sniff the network for HTTP request URLs, output in CLF format.

  Copyright (c) 1999 Dug Song <dugsong@monkey.org>

  $Id: urlsnarf.c,v 1.26 2000/06/14 16:16:02 dugsong Exp $
*/

#include "config.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#ifdef HAVE_ERR_H
#include <err.h>
#endif
#include <libnet.h>
#include <nids.h>
#include "base64.h"
#include "version.h"

/* Globals. */
u_short		Opt_dns = 1;

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

/* Locate substring in a binary string. */
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);
}

/* XXX - blech. */
char *
get_request_time(void)
{
	static char tstr[32], sign;
	struct tm *t, gmt;
	time_t tt = time(NULL);
	int days, hours, tz, len;
	
	gmt = *gmtime(&tt);
	t = localtime(&tt);
	
	days = t->tm_yday - gmt.tm_yday;
	hours = ((days < -1 ? 24 : 1 < days ? -24 : days * 24) +
		 t->tm_hour - gmt.tm_hour);
	tz = hours * 60 + t->tm_min - gmt.tm_min;
	
	len = strftime(tstr, sizeof(tstr), "%e/%b/%Y:%X", t);
	if (len < 0 || len > sizeof(tstr) - 5)
		return (NULL);
	
	if (tz < 0) {
		sign = '-';
		tz = -tz;
	}
	else sign = '+';
	
	snprintf(tstr + len, sizeof(tstr) - len, " %c%.2d%.2d",
		 sign, tz / 60, tz % 60);
	
	return (tstr);
}

int
http_method_len(char *buf)
{
	static char *methods[] = { "GET", "HEAD", "POST", "PUT", "DELETE",
				   "TRACE", "CONNECT", NULL };
	char **pp;
	int i;
	
	for (pp = methods; *pp != NULL; pp++) {
		i = strlen(*pp);
		if (strncmp(buf, *pp, i) == 0)
			return (i);
	}
	return (0);
}

int
process_http_request(struct tuple4 *addr, u_char *buf, int len)
{
	char *p, *s, *e;
	char *method, *uri, *vhost, *reqtime, *user, *referer, *agent;
	int i, discard = 0;
	
	/* Process requests. */
	for (s = buf; (e = bufbuf(s, len, "\r\n\r\n", 4)) != NULL; s = e + 4) {
		i = (e + 4) - s;
		discard += i; len -= i;
		*e = '\0';
		
		method = uri = vhost = reqtime = user = referer = agent = NULL;
		
		/* Process header. */
		for (p = strtok(s, "\r\n"); p != NULL;
		     p = strtok(NULL, "\r\n")) {
			if ((i = http_method_len(p)) != 0) {
				method = p;
				if (strlen(p) - i > 0) {
					p[i] = '\0';
					uri = p + i + 1;
				}
			}
			else if (strncasecmp(p, "Authorization: Basic ", 21)
				 == 0) {
				p += 21;
				i = base64_pton(p, p, strlen(p));
				p[i] = '\0';
				user = p;
				if ((p = strchr(p, ':')) != NULL) *p = '\0';
			}
			else if (strncasecmp(p, "Host: ", 6) == 0)
				vhost = p + 6;
			else if (strncasecmp(p, "Referer: ", 9) == 0)
				referer = p + 9;
			else if (strncasecmp(p, "User-Agent: ", 12) == 0)
				agent = p + 12;
		}
		reqtime = get_request_time();
		
		if (uri != NULL && strncasecmp(uri, "http://", 7) == 0)
			vhost = "";
		else if (vhost == NULL)
			vhost = libnet_host_lookup(addr->daddr, Opt_dns);
		
		if (method != NULL && uri != NULL &&
		    vhost != NULL && reqtime != NULL) {
			printf("%s - %s [%s] \"%s %s%s%s\" - - "
			       "\"%s\" \"%s\"\n",
			       libnet_host_lookup(addr->saddr, Opt_dns),
			       (user ? user : "-"), reqtime, method,
			       (*vhost ? "http://" : vhost), vhost, uri,
			       (referer ? referer : "-"),
			       (agent ? agent : "-"));
		}
	}
	return (discard);
}

void
sniff_http_client(struct tcp_stream *ts, void **yoda)
{
	int i;
	
	/* Only handle HTTP client traffic. */
	if (ts->addr.dest != 80 && ts->addr.dest != 3128 &&
	    ts->addr.dest != 8080)
		return;
	
	switch (ts->nids_state) {
	case NIDS_JUST_EST:
		/* Collect data. */
		ts->server.collect = 1;
		
	case NIDS_DATA:
		if (ts->server.count_new != 0) {
			i = process_http_request(&ts->addr, ts->server.data,
						 ts->server.count -
						 ts->server.offset);
			nids_discard(ts, i);
		}
		break;
		
	default:
		if (ts->server.count != 0) {
			process_http_request(&ts->addr, ts->server.data,
					     ts->server.count -
					     ts->server.offset);
		}
		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:nh?V")) != -1) {
		switch (c) {
		case 'i':
			nids_params.device = optarg;
			break;
		case 'n':
			Opt_dns = 0;
			break;
		default:
			usage();
		}
	}
	argc -= optind;
	argv += optind;
	
	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_http_client);

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

/* 5000. */
