/*
 * Copyright 2001 Niels Provos <provos@citi.umich.edu>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Niels Provos.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#include <sys/param.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/queue.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
#include <dnet.h>
#include <pcap.h>
#ifdef BSD
#include <pcap-int.h>
#endif
#include <event.h>

#include "buffer.h"
#include "g711.h"
#include "voip.h"

#define MAXTALKS	2
#define MAXSAMPLES	480
#define MAXBUFFER	(10000*MAXSAMPLES)

extern int lnet_sock;
extern int pcap_off;
extern int debug;

int16_t lastidnr = 0;

/* XXX - Seen cache */
ip_addr_t tcp_src;
ip_addr_t tcp_dst;
u_int32_t tcp_seqnr;

/* XXX - bad */
char phone_number[1024];

u_char voip_magic_data[2] = {0x80, 0x00};
u_char voip_magic_start[2] = {0x80, 0x80};

extern struct event voipev;

/* buffer handler */
bh bhd;

/* Evil - we'll just voip_session = bufid */
struct voip_session {
	struct in_addr src;
	struct in_addr dst;
	u_short sport;
	u_short dport;
	u_int32_t magic;
};

int
voip_seen(struct ip_hdr *ip, struct tcp_hdr *tcp)
{
	if (ip->ip_src == tcp_src &&
	    ip->ip_dst == tcp_dst &&
	    tcp->th_seq == tcp_seqnr)
		return (1);

	tcp_src = ip->ip_src;
	tcp_dst = ip->ip_dst;
	tcp_seqnr = tcp->th_seq;

	return (0);
}


void
voip_init(void)
{
	bhd = buffer_new(MAXTALKS, MAXSAMPLES, MAXBUFFER);
	buffer_init(bhd);
}

void
make_ident(ip_addr_t *src, u_short sport,
    ip_addr_t *dst, u_short dport,
    u_int32_t magic, bufid *pident)
{
	u_char *ident = (u_char *)pident;

	memset(ident, 0, sizeof(ident));
	memcpy(ident, src, sizeof(*src)); ident += sizeof(*src);
	memcpy(ident, dst, sizeof(*dst)); ident += sizeof(*dst);
	memcpy(ident, &sport, sizeof(sport)); ident += sizeof(sport);
	memcpy(ident, &dport, sizeof(dport)); ident += sizeof(dport);
	memcpy(ident, &magic, sizeof(magic)); ident += sizeof(magic);
}

void
voip_sniff_tcp(u_char *user,
	       const struct pcap_pkthdr *pkthdr, const u_char *pkt)
{
	struct ip_hdr *ip;
	struct tcp_hdr *tcp;
	struct voip_signal *voip;
	struct addr src, dst;
	u_char *p;
	char *cmd, *what;
	char extra[1024];
	char tmp[2];
	int len;
	char srcname[MAXHOSTNAMELEN], dstname[MAXHOSTNAMELEN];

	ip = (struct ip_hdr *)(pkt + pcap_off);
	tcp = (struct tcp_hdr *)(pkt + pcap_off + (ip->ip_hl<<2));
	voip = (struct voip_signal *)(pkt + pcap_off + (ip->ip_hl<<2) + 
				      (tcp->th_off<<2));
	p = (u_char *)(voip + 1);

	len = ntohs(ip->ip_len) - (ip->ip_hl<<2) - (tcp->th_off<<2);

	if (len < sizeof(struct voip_signal)) {
		if (debug)
			fprintf(stderr, "Bad packet length: %d\n", len);
		return;
	}

	if (voip_seen(ip, tcp))
		return;

	addr_pack(&src, ADDR_TYPE_IP, IP_ADDR_BITS, &ip->ip_src, IP_ADDR_LEN);
	addr_pack(&dst, ADDR_TYPE_IP, IP_ADDR_BITS, &ip->ip_dst, IP_ADDR_LEN);
	strlcpy(srcname, addr_ntoa(&src), sizeof (srcname));
	strlcpy(dstname, addr_ntoa(&dst), sizeof (dstname));

	what = cmd = "";
	extra[0] = '\0';
	switch(voip->proto) {
	case VOIP_IE_CON:
		what = "Connection";
		switch(voip->cmd) {
		case VOIP_CON_PICKUP:
			cmd = "Handset pick up";
			phone_number[0] = '\0';
			break;
		case VOIP_CON_HANGUP:
			cmd = "Handset hang up";
			break;
		case VOIP_CON_ALIVE:
			if (!debug)
				return;
			cmd = "Keep Alive";
			break;
		default:
			cmd = "Unknown command";
			break;
		}
		break;
	case VOIP_IE_USER:
		what = "User Input";
		switch (voip->cmd) {
		case VOIP_USER_DIAL:
			cmd = "Dial";
			if (len == sizeof(struct voip_signal) + 4) {
				snprintf(extra, sizeof(extra),
					 "(Digit %d)", p[0]);
			}
			tmp[0] = p[0] + '0';
			tmp[1] = '\0';
			strlcat(phone_number, tmp, sizeof (phone_number));
		}
		break;
	case VOIP_IE_DISPLAY:
		what = "Display";
		switch (voip->cmd) {
		case VOIP_DISPLAY_STATUS:
			cmd = "Status";
			if (len <= sizeof(struct voip_signal))
				break;
			len -= sizeof(struct voip_signal);
			if (len < voip->plen)
				break;
			snprintf(extra, sizeof(extra), "\n\t(%s)", (char *)p);
			break;
		default:
			cmd = "Unknown";
			break;
		}
		break;
	default:
		what = "unknown";
		cmd = "unknown";
		break;
	}

	fprintf(stderr,
		"%s -> %s: %s(0x%x) %s(0x%x) %s\n",
		srcname, dstname,
		what, voip->proto,
		cmd, voip->cmd,
		extra);
}

void
voip_sniff_udp(u_char *user,
	       const struct pcap_pkthdr *pkthdr, const u_char *pkt)
{
	struct ip_hdr *ip;
	struct udp_hdr *udp;
	struct voip_hdr *voip;
	bufid ident;
	char srcname[MAXHOSTNAMELEN], dstname[MAXHOSTNAMELEN];
	u_char *p;
	u_int16_t buf[1024];
	int i, len;

	ip = (struct ip_hdr *)(pkt + pcap_off);
	udp = (struct udp_hdr *)(pkt + pcap_off + (ip->ip_hl<<2));
	voip = (struct voip_hdr *)(udp + 1);
	p = (u_char *)(voip + 1);

	len = ntohs(udp->uh_ulen) - sizeof(struct udp_hdr);
	if (len < sizeof (struct voip_hdr)) {
		if (debug > 1)
			fprintf(stderr, "Bad packet: Len %d\n", len);
		return;
	}

	voip->id = ntohs(voip->id);
	voip->seqnr = ntohl(voip->seqnr);

	if (memcmp(voip_magic_data, voip->magic, sizeof(voip_magic_data))) {
		if (memcmp(voip_magic_start, voip->magic,
			   sizeof(voip_magic_start))) {
			if (debug > 1)
				fprintf(stderr,
					"Bad packet: Magic: %02x %02x\n",
					voip->magic[0], voip->magic[1]);
			return;
		}

		if (lastidnr != voip->id) {
			struct addr src, dst;
			lastidnr = voip->id;
			
			addr_pack(&src, ADDR_TYPE_IP, IP_ADDR_BITS,
			    &ip->ip_src, IP_ADDR_LEN);
			addr_pack(&dst, ADDR_TYPE_IP, IP_ADDR_BITS,
			    &ip->ip_dst, IP_ADDR_LEN);
			strlcpy(srcname, addr_ntoa(&src), sizeof (srcname));
			strlcpy(dstname, addr_ntoa(&dst), sizeof (dstname));

			fprintf(stderr,
				"Client: %s -> %s:\n"
				"\tid %d, seqnr: %u, 0x%0x\n"
				"\tDialed number: %s\n",
				srcname, dstname,
				voip->id, voip->seqnr,
				ntohl(voip->magicnr),
 				phone_number);

			buffer_clear(bhd);
		}
	}

	len -= sizeof (struct voip_hdr);

	for (i = 0; i < len; i++) {
		buf[i] = ulaw2linear(p[i]);
	}

	make_ident(&ip->ip_src, udp->uh_sport, 
		   &ip->ip_dst, udp->uh_dport, voip->magicnr,
		   &ident);

	buffer_enqueue(bhd, &ident, voip->id, voip->seqnr, buf, len);
	
	return;
}

void
voip_sniff(u_char *user, const struct pcap_pkthdr *pkthdr, const u_char *pkt)
{
	struct ip_hdr *ip;

	ip = (struct ip_hdr *)(pkt + pcap_off);
	switch(ip->ip_p) {
	case IPPROTO_UDP:
		voip_sniff_udp(user, pkthdr, pkt);
		break;
	case IPPROTO_TCP:
		voip_sniff_tcp(user, pkthdr, pkt);
		break;
	default:
		break;
	}
}


syntax highlighted by Code2HTML, v. 0.9.1