/*
  dsniff.c

  simple libnids-based sniffer, because DrHoney wanted one.

  this is for demonstration purposes and educational use only.
  
  Copyright (c) 1999 Dug Song <dugsong@monkey.org>
  All rights reserved, all wrongs reversed.

  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. The name of author may not be used to endorse or promote products
     derived from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED ``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.

  $Id: dsniff.c,v 1.54 2000/04/10 03:58:57 dugsong Exp $
*/

#include "config.h"

#include <sys/types.h>
#include <sys/stat.h>
#ifndef WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#endif
#include <netinet/ip.h>
#include <arpa/nameser.h>
#include <arpa/telnet.h>
#ifndef WIN32
#include <rpc/rpc.h>
#ifdef HAVE_RPC_RPCENT_H
#include <rpc/rpcent.h>
#endif
#else
#include <rpc.h>
#include <rpc/rpc_cut.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#ifdef HAVE_DB_185_H
#include <db_185.h>
#elif HAVE_DB_H
#include <db.h>
#endif
#ifndef WIN32
#include <md5.h>
#ifdef HAVE_LIBGEN_H
#include <libgen.h>
#endif
#include <nids.h>
#endif
#ifdef WIN32
#include "md5.h"
#include "nids.h"
#include "getopt.h"
#include <pcap.h>
#define snprintf _snprintf
#define strncasecmp	strnicmp
size_t strlcat(char *dst, const char *src, size_t siz);
size_t strlcpy(char *dst, const char *src, size_t siz);
int init_winsock(void);
void PrintDeviceList(const char* device);

#define S_IRUSR _S_IREAD       /* Read by owner.  */
#define S_IWUSR _S_IWRITE      /* Write by owner.  */
#define R_NEXT 7               /* seq */
#define R_NOOVERWRITE 8        /* put */
#define index strchr

struct udphdr {
         u_int16_t uh_sport;           /* source port */
         u_int16_t uh_dport;           /* destination port */
         u_int16_t uh_ulen;            /* udp length */
         u_int16_t uh_sum;             /* udp checksum */
};
#endif
#include "version.h"

#define MAX_LINES	6
#define MIN_SNAPLEN	1024

#define PMAPSIZE	64
#define XIDMAPSIZE	64

#define PROG_PMAP	100000
#define PROG_MOUNT	100005

#define pletohs(p)	((u_short)                      \
			 ((u_short)*((u_char *)p+1)<<8| \
			  (u_short)*((u_char *)p+0)<<0))
     
#define pletohl(p)	((u_long)*((u_char *)p+3)<<24|  \
			 (u_long)*((u_char *)p+2)<<16|  \
			 (u_long)*((u_char *)p+1)<<8|   \
			 (u_long)*((u_char *)p+0)<<0)

struct pmap_entry {
  u_long	dst;
  u_short	dport;
  int		prog;
};

struct xid_map_entry {
  u_long	xid;
  u_long	src;
  u_long	dst;
  int		prog;
  char	       *data;
};

struct dsniff_rec {
  u_long	src;
  u_long	dst;
  u_int		port;
  time_t	time;
  char		*data;
  int		len;
};

enum svc_proto {
  SVC_TCP = 1,
  SVC_UDP = 2,
  SVC_RPC = 4
};

struct svc {
  char	       *name;
  int	      (*decode)(u_char *, int);
  int		proto;
  int		ports[8];
};

/* Options. */
u_short	Opt_dns = 1;
int	Opt_read = 0;
int	Opt_write = 0;
int	Opt_snaplen = MIN_SNAPLEN;

/* Globals. */
static DB      *db;
static char	Buf[BUFSIZ];
static int	Lines = MAX_LINES;
static int	pmap_next = 0;
static int	pmap_hint = 0;
static int	xid_map_next = 0;
static int	xid_map_hint = 0;
static struct	pmap_entry pmap_map[PMAPSIZE];
static struct	xid_map_entry xid_map[XIDMAPSIZE];

/* Prototypes. */
#ifndef HAVE_DIRNAME
extern char *dirname(const char *path);
#endif
extern int base64_decode(char *in, char *out, int len);
extern u_char *libnet_host_lookup(u_long in, u_short use_name);

void
usage(void)
{
#ifdef WIN32
  fprintf(stderr, "Usage: dsniff [-n] [-D] [-i interface] [-s snaplen] [-r|-w file]\n");
#else
  fprintf(stderr, "Usage: dsniff [-n] [-i interface] [-s snaplen] [-r|-w file]\n");
#endif
  exit(1);
}

#ifdef DEBUG
/* from OpenBSD tcpdump: dump the buffer in `emacs-hexl' style */
void
print_hexl(const u_char *cp, unsigned int length, unsigned int offset)
{
  unsigned int i, j, jm;
  int c;
  char ln[128];
  
  printf("\n");
  for (i = 0; i < length; i += 0x10) {
    snprintf(ln, sizeof(ln), "  %04x: ", (unsigned int)(i + offset));
    jm = length - i;
    jm = jm > 16 ? 16 : jm;
    
    for (j = 0; j < jm; j++) {
      if ((j % 2) == 1)
	snprintf(ln + strlen(ln), sizeof(ln) - strlen(ln),
		 "%02x ", (unsigned int)cp[i+j]);
      else
	snprintf(ln + strlen(ln), sizeof(ln) - strlen(ln),
		 "%02x", (unsigned int)cp[i+j]);
    }
    for (; j < 16; j++) {
      if ((j % 2) == 1)
	snprintf(ln + strlen(ln), sizeof(ln) - strlen(ln), "   ");
      else
	snprintf(ln + strlen(ln), sizeof(ln) - strlen(ln), "  ");
    }
    snprintf(ln + strlen(ln), sizeof(ln) - strlen(ln), " ");
    
    for (j = 0; j < jm; j++) {
      c = cp[i+j];
      c = isprint(c) ? c : '.';
      snprintf(ln + strlen(ln), sizeof(ln) - strlen(ln), "%c", c);
    }
    printf("%s\n", ln);
  }
}
#endif /* DEBUG */

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

int
hex_decode(char *src, u_char *dst, int outsize)
{
  char *p, *pe;
  u_char *q, *qe, ch, cl;
  
  pe = src + strlen(src);
  qe = dst + outsize;
  
  for (p = src, q = dst; p < pe && q < qe && isxdigit((int)*p); p += 2) {
    ch = tolower(p[0]);
    cl = tolower(p[1]);
    
    if ((ch >= '0') && (ch <= '9')) ch -= '0';
    else if ((ch >= 'a') && (ch <= 'f')) ch -= 'a' - 10;
    else return (-1);
    
    if ((cl >= '0') && (cl <= '9')) cl -= '0';
    else if ((cl >= 'a') && (cl <= 'f')) cl -= 'a' - 10;
    else return (-1);
    
    *q++ = (ch << 4) | cl;
  }
  return (q - dst);
}

/* XXX - seriously lame portmap cache. ugh. */
void
pmap_enter(u_long dst, u_short dport)
{
  struct pmap_entry *pp;

  pp = &pmap_map[pmap_next];
  pp->dst = dst;
  pp->dport = dport;

  if (++pmap_next >= PMAPSIZE)
    pmap_next = 0;
}

int
pmap_find(u_long dst, u_short dport)
{
  struct pmap_entry *pp;
  int i;
  
  if (dport == 111)
    return (1);
  
  i = pmap_hint;
  do {
    pp = &pmap_map[i];
    if (pp->dst == dst && pp->dport == dport) {
      /* match */
      pmap_hint = i;
      return (1);
    }
    if (++i >= PMAPSIZE)
      i = 0;
  } while (i != pmap_hint);
  
  return (0);
}

/* xid_map borrowed from tcpdump's print-nfs.c */
void
xid_map_enter(u_long src, u_long dst, int xid, int prog, u_char *data)
{
  struct xid_map_entry *mp;
  
  mp = &xid_map[xid_map_next];
  
  if (++xid_map_next >= XIDMAPSIZE)
    xid_map_next = 0;
  
  mp->xid = xid;
  mp->src = src;
  mp->dst = dst;
  mp->prog = prog;
  mp->data = data;
}

struct xid_map_entry *
xid_map_find(u_long src, u_long dst, int xid)
{
  struct xid_map_entry *mp;
  int i;

  /* Start searching from where we last left off. */
  i = xid_map_hint;
  do {
    mp = &xid_map[i];
    if (mp->xid == xid && mp->src == src && mp->dst == dst) {
      /* match */
      xid_map_hint = i;
      return (mp);
    }
    if (++i >= XIDMAPSIZE)
      i = 0;
  } while (i != xid_map_hint);
  
  return (NULL);
}

DBT *
db_md5_key(struct dsniff_rec *d)
{
  static DBT key;
  static u_char hash[16];
  MD5_CTX ctx;

  MD5Init(&ctx);
  MD5Update(&ctx, (u_char *)&d->src, sizeof(d->src));
  MD5Update(&ctx, (u_char *)&d->dst, sizeof(d->dst));
  MD5Update(&ctx, (u_char *)&d->port, sizeof(d->port));
  MD5Update(&ctx, d->data, d->len);
  MD5Final(hash, &ctx);

  key.data = hash;
  key.size = sizeof(hash);

  return (&key);
}

/* Strip telnet options, as well as suboption data. */
int
strip_telopts(u_char *buf, int len)
{
  int i, j, subopt = 0;
  char *p, *q;
  
  for (i = j = 0; i < len; i++) {
    if (buf[i] == IAC) {
      if (++i >= len) break;
      else if (buf[i] > SB)
	i++;
      else if (buf[i] == SB) {
	/* XXX - check for autologin username. */
	p = buf + i + 1;
	if ((q = bufbuf(p, len - i, "\xff", 1)) != NULL) {
	  if ((p = bufbuf(p, q - p, "USER\x01", 5)) != NULL) {
	    p += 5;
	    buf[j++] = '[';
	    memcpy(buf + j, p, q - p); j += q - p;
	    buf[j++] = ']'; buf[j++] = '\n';
	  }
	}
	subopt = 1;
      }
      else if (buf[i] == SE) {
	if (!subopt) j = 0;
	subopt = 0;
      }
    }
    else if (!subopt) {
      /* XXX - convert isolated carriage returns to newlines. */
      if (buf[i] == '\r' && i + 1 < len && buf[i + 1] != '\n')
	buf[j++] = '\n';
      /* XXX - strip binary nulls. */
      else if (buf[i] != '\0')
	buf[j++] = buf[i];
    }
  }
  buf[j] = '\0';
  
  return (j);
}

/* Strip a string buffer down to a maximum number of lines. */
int
strip_lines(char *buf, int max_lines)
{
  char *p;
  int lines, nonascii;

  if (!buf) return (0);

  lines = nonascii = 0;
  
  for (p = buf; *p && lines < max_lines; p++) {
    if (*p == '\n') lines++;
    if (!isascii(*p)) nonascii++;
  }
  if (*p) *p = '\0';

  /* XXX - lame ciphertext heuristic */
  if (nonascii * 3 > p - buf)
    return (0);

  return (lines);
}

int
is_ascii_string(char *buf, int len)
{
  int i;

  for (i = 0; i < len; i++)
    if (!isascii(buf[i])) return (0);

  return (1);
}

int
asn1_type(u_char **buf)
{
  u_char *p = *buf;
  int i;

  i = *p++ & 0x1f;
  *buf = p;
  
  return (i);
}

int
asn1_len(u_char **buf)
{
  u_char *p;
  int len, num = 0;
  
  p = *buf;
  
  if (*p >= 128) {	/* Long form */
    len = *p++ & ~128;
    
    if (len == 1) {
      num = *p++;
    }
    else if (len == 2) {
      GETSHORT(num, p);
    }
    else if (len == 3) {
      p--; GETLONG(num, p);
      num &= 0xFFF;
    }
    else if (len== 4)
      GETLONG(num, p);
  }
  else num = *p++;	/* Short form */

  *buf = p;
  
  return (num);
}

int
decode_ftp(u_char *buf, int len)
{
  char *p;
  
  Buf[0] = '\0';

  if (!strip_telopts(buf, len))
    return (0);
  
  for (p = strtok(buf, "\r\n"); p != NULL; p = strtok(NULL, "\r\n")) {
    if (strncasecmp(p, "USER ", 5) == 0 ||
	strncasecmp(p, "ACCT ", 5) == 0 ||
	strncasecmp(p, "PASS ", 5) == 0) {
      strlcat(Buf, p, sizeof(Buf));
      strlcat(Buf, "\n", sizeof(Buf));
    }
  }
  if (strip_lines(Buf, Lines) < 2)
    return (0);
  
  return (strlen(Buf));
}

int
decode_telnet(u_char *buf, int len)
{
  if (!strip_telopts(buf, len))
    return (0);
  
  if (strip_lines(buf, Lines) < 2)
    return (0);
  
  strlcpy(Buf, buf, sizeof(Buf));
  
  return (strlen(Buf));
}

char *
http_req_dirname(char *req)
{
  char *uri, *vers;

  if ((uri = strchr(req, ' ')) == NULL)
    return (req);

  if ((vers = strrchr(uri, ' ')) == uri)
    vers = NULL;
  else if (vers[-1] == '/')
    return (req);
  else
    *vers++ = '\0';

  strcpy(req, dirname(req));
  strcat(req, "/");

  if (vers) {
    strcat(req, " ");
    strcat(req, vers);
  }
  return (req);
}  
  
int
decode_http(u_char *buf, int len)
{
  char *p, *s, *e;
  int i;

  Buf[0] = '\0';
  
  /* Process requests. */
  for (s = buf; (e = bufbuf(s, len, "\r\n\r\n", 4)) != NULL; s = e + 4) {
    len -= (e + 4) - s;
    *e = '\0';

    /* Check for auth info. */
    if (strstr(s, "uthorization: ") == NULL)
      continue;
    
    /* Process header. */
    for (p = strtok(s, "\r\n"); p != NULL; p = strtok(NULL, "\r\n")) {
      if (!strlen(p)) continue;
      
      if (strncasecmp(p, "GET ", 4) == 0 ||
	  strncasecmp(p, "POST ", 5) == 0) {
	p = http_req_dirname(p);
	strlcat(Buf, p, sizeof(Buf));
	strlcat(Buf, "\n", sizeof(Buf));
      }
      else if (strncasecmp(p, "Host: ", 6) == 0) {
	strlcat(Buf, p, sizeof(Buf));
	strlcat(Buf, "\n", sizeof(Buf));
      }
      else if (strncasecmp(p, "Authorization: Basic ", 21) == 0 ) {
	strlcat(Buf, p, sizeof(Buf));
	p += 21;
	i = base64_decode(p, p, strlen(p));
	p[i] = '\0';
	strlcat(Buf, " [", sizeof(Buf));
	strlcat(Buf, p, sizeof(Buf));
	strlcat(Buf, "]\n\n", sizeof(Buf));
      } 
      else if (strncasecmp(p, "Proxy-Authorization: Basic ", 27) == 0) {
	strlcat(Buf, p, sizeof(Buf));
	p += 27;
	i = base64_decode(p, p, strlen(p));
	p[i] = '\0';
	strlcat(Buf, " [", sizeof(Buf));
	strlcat(Buf, p, sizeof(Buf));
	strlcat(Buf, "]\n\n", sizeof(Buf));
      }
    }
  }
  return (strlen(Buf));
}

int
decode_pop(u_char *buf, int len)
{
  char *p;
  int i, j;
  
  Buf[0] = '\0';

  for (p = strtok(buf, "\r\n"); p != NULL; p = strtok(NULL, "\r\n")) {
    if (strncasecmp(p, "AUTH PLAIN", 10) == 0 ||
	strncasecmp(p, "AUTH LOGIN", 10) == 0) {
      strlcat(Buf, p, sizeof(Buf));
      strlcat(Buf, "\n", sizeof(Buf));
      
      /* Decode SASL auth. */
      for (i = 0; i < 2 && (p = strtok(NULL, "\r\n")) != NULL; i++) {
	strlcat(Buf, p, sizeof(Buf));
	j = base64_decode(p, p, strlen(p));
	p[j] = '\0';
	strlcat(Buf, " [", sizeof(Buf));
	strlcat(Buf, p, sizeof(Buf));
	strlcat(Buf, "]\n", sizeof(Buf));
      }
    }
    /* Save regular POP2, POP3 auth info. */
    else if (strncasecmp(p, "USER ", 5) == 0 ||
	     strncasecmp(p, "PASS ", 5) == 0 ||
	     strncasecmp(p, "HELO ", 5) == 0) {
      strlcat(Buf, p, sizeof(Buf));
      strlcat(Buf, "\n", sizeof(Buf));
    }
  }
  return (strlen(Buf));
}

/* Contributed by Felix von Leitner <felix@convergence.de>. */
int
decode_nntp(u_char *buf, int len)
{
  char *p;
  
  Buf[0] = '\0';
  
  for (p = strtok(buf, "\r\n"); p != NULL; p = strtok(NULL, "\r\n")) {
    if (strncasecmp(p, "AUTHINFO ", 9) == 0) {
      strlcat(Buf, p, sizeof(Buf));
      strlcat(Buf, "\n", sizeof(Buf));
    }
  }
  return (strlen(Buf));
}

int
decode_imap(u_char *buf, int len)
{
  char *p;
  
  Buf[0] = '\0';

  for (p = strtok(buf, "\r\n"); p != NULL; p = strtok(NULL, "\r\n")) {
    if ((p = strchr(p, ' ')) != NULL) {
      p++;
      if (strncasecmp(p, "LOGIN ", 6) == 0) {
	strlcat(Buf, p, sizeof(Buf));
	strlcat(Buf, "\n", sizeof(Buf));
      }
    }
  }
  return (strlen(Buf));
}

int
decode_rlogin(u_char *buf, int len)
{
  char *p, *q;

  p = buf + 1;				/* Skip first NULL */

  strlcpy(Buf, "[", sizeof(Buf));
  strlcat(Buf, p, sizeof(Buf));		/* Local username */
  strlcat(Buf, ":", sizeof(Buf));
  p += strlen(p) + 1;

  strlcat(Buf, p, sizeof(Buf));		/* Remote username */
  strlcat(Buf, "]\n", sizeof(Buf));
  p += strlen(p) + 1;

  p += strlen(p) + 1;			/* Skip term info */

  if ((q = strstr(p, "\xff\xffss")) != NULL)	/* Skip window size */
    p += 12;
  
  for (p = strtok(p, "\r\n"); p != NULL; p = strtok(NULL, "\r\n")) {
    strlcat(Buf, p, sizeof(Buf));
    strlcat(Buf, "\n", sizeof(Buf));
  }
  if (!strip_lines(Buf, Lines))
    return (0);
  
  return (strlen(Buf));
}

int
decode_socks(u_char *buf, int len)
{
  u_char *p;
  int i, n;

  p = buf;

  if (len < 4 || *p++ != 5)		/* SOCKS version */
    return (0);
  
  if ((n = *p++) > len - 5)		/* nmethods */
    return (0);
  
  for (i = 0; i < n; i++)		/* USERNAME/PASSWORD method? */
    if (p[i] == 2) break;

  if (i == n) return (0);
  
  p += n;
  if (*p++ != 1) return (0);		/* USERNAME/PASSWORD version */
  
  n = *p++;
  if (n > len - (p - buf))
    return (0);
  
  memmove(p - 1, p, n); p[n - 1] = '\0';
  snprintf(Buf, sizeof(Buf), "%s ", p - 1);
  p += n;
  
  n = *p++;
  if (n > len - (p - buf))
    return (0);
  
  memmove(p - 1, p, n); p[n - 1] = '\0';
  strlcat(Buf, p - 1, sizeof(Buf));
  strlcat(Buf, "\n", sizeof(Buf));
  
  return (strlen(Buf));
}

/* stolen from CVS scramble.c */
static unsigned char cvs_shifts[] = {
  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
  16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
  114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87,
  111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105,
  41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35,
  125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56,
  36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48,
  58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223,
  225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190,
  199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193,
  174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212,
  207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246,
  192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176,
  227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127,
  182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195,
  243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152
};

int
decode_cvs(u_char *buf, int len)
{
  char *p;
  int i, linenum = 0;
  
  Buf[0] = '\0';

  if (strip_lines(buf, 5) != 5 || strncmp(buf, "BEGIN ", 6) != 0)
    return (0);
  
  for (p = strtok(buf, "\n"); p != NULL; p = strtok(NULL, "\n")) {
    linenum++;
    strlcat(Buf, p, sizeof(Buf));
    
    if (linenum == 4) {
      if (p[0] != 'A')
	return (0);
      for (i = 1; p[i] != '\0'; i++)
	p[i] = cvs_shifts[(u_char)(p[i])];
      strlcat(Buf, " [", sizeof(Buf));
      strlcat(Buf, p + 1, sizeof(Buf));
      strlcat(Buf, "]", sizeof(Buf));
    }
    strlcat(Buf, "\n", sizeof(Buf));
  }
  return (strlen(Buf));
}

/* XXX - need to investigate AOL's FLAP, SNAC layers more... */
int
decode_aim(u_char *buf, int len)
{
  static char *aim_xor = "Tic/Toc";
  int i, j;
  char *p;

  Buf[0] = '\0';
  
  if ((p = bufbuf(buf, len, "toc_signon ", 11)) == NULL)
    return (0);
  
  i = 0;
  for (p = strtok(p, " "); p != NULL; p = strtok(NULL, " ")) {
    strlcat(Buf, p, sizeof(Buf));
    strlcat(Buf, " ", sizeof(Buf));
    if (++i > 4) break;
  }
  if (p == NULL)
    return (0);
  
  strlcat(Buf, "[", sizeof(Buf));

  j = hex_decode(p + 2, p, strlen(p));
  for (i = 0; i < j; i++)
    p[i] = p[i] ^ aim_xor[i % 7];
  p[j] = '\0';
  strlcat(Buf, p, sizeof(Buf));
  
  strlcat(Buf, "]\n", sizeof(Buf));

  len = strlen(Buf);

  if (!is_ascii_string(Buf, len))
    return (0);
  
  return (len);
}

/* see http://www.algonet.se/~henisak/icq/ for details... */

#define ICQ2_UIN_OFFSET		6
#define ICQ2_CMD_OFFSET		2
#define ICQ2_PASS_OFFSET	16

#define ICQ5_UIN_OFFSET		6
#define ICQ5_CMD_OFFSET		14
#define ICQ5_CKSUM_OFFSET	20
#define ICQ5_PASS_OFFSET	34

const u_char icq5_table [] = {
  0x59, 0x60, 0x37, 0x6B, 0x65, 0x62, 0x46, 0x48, 0x53, 0x61, 0x4C,
  0x59, 0x60, 0x57, 0x5B, 0x3D, 0x5E, 0x34, 0x6D, 0x36, 0x50, 0x3F,
  0x6F, 0x67, 0x53, 0x61, 0x4C, 0x59, 0x40, 0x47, 0x63, 0x39, 0x50,
  0x5F, 0x5F, 0x3F, 0x6F, 0x47, 0x43, 0x69, 0x48, 0x33, 0x31, 0x64,
  0x35, 0x5A, 0x4A, 0x42, 0x56, 0x40, 0x67, 0x53, 0x41, 0x07, 0x6C,
  0x49, 0x58, 0x3B, 0x4D, 0x46, 0x68, 0x43, 0x69, 0x48, 0x33, 0x31,
  0x44, 0x65, 0x62, 0x46, 0x48, 0x53, 0x41, 0x07, 0x6C, 0x69, 0x48,
  0x33, 0x51, 0x54, 0x5D, 0x4E, 0x6C, 0x49, 0x38, 0x4B, 0x55, 0x4A,
  0x62, 0x46, 0x48, 0x33, 0x51, 0x34, 0x6D, 0x36, 0x50, 0x5F, 0x5F,
  0x5F, 0x3F, 0x6F, 0x47, 0x63, 0x59, 0x40, 0x67, 0x33, 0x31, 0x64,
  0x35, 0x5A, 0x6A, 0x52, 0x6E, 0x3C, 0x51, 0x34, 0x6D, 0x36, 0x50,
  0x5F, 0x5F, 0x3F, 0x4F, 0x37, 0x4B, 0x35, 0x5A, 0x4A, 0x62, 0x66,
  0x58, 0x3B, 0x4D, 0x66, 0x58, 0x5B, 0x5D, 0x4E, 0x6C, 0x49, 0x58,
  0x3B, 0x4D, 0x66, 0x58, 0x3B, 0x4D, 0x46, 0x48, 0x53, 0x61, 0x4C,
  0x59, 0x40, 0x67, 0x33, 0x31, 0x64, 0x55, 0x6A, 0x32, 0x3E, 0x44,
  0x45, 0x52, 0x6E, 0x3C, 0x31, 0x64, 0x55, 0x6A, 0x52, 0x4E, 0x6C,
  0x69, 0x48, 0x53, 0x61, 0x4C, 0x39, 0x30, 0x6F, 0x47, 0x63, 0x59,
  0x60, 0x57, 0x5B, 0x3D, 0x3E, 0x64, 0x35, 0x3A, 0x3A, 0x5A, 0x6A,
  0x52, 0x4E, 0x6C, 0x69, 0x48, 0x53, 0x61, 0x6C, 0x49, 0x58, 0x3B,
  0x4D, 0x46, 0x68, 0x63, 0x39, 0x50, 0x5F, 0x5F, 0x3F, 0x6F, 0x67,
  0x53, 0x41, 0x25, 0x41, 0x3C, 0x51, 0x54, 0x3D, 0x5E, 0x54, 0x5D,
  0x4E, 0x4C, 0x39, 0x50, 0x5F, 0x5F, 0x5F, 0x3F, 0x6F, 0x47, 0x43,
  0x69, 0x48, 0x33, 0x51, 0x54, 0x5D, 0x6E, 0x3C, 0x31, 0x64, 0x35,
  0x5A, 0x00, 0x00
};

/* Contributed by Anonymous. */
int 
decode_sniffer(u_char *buf, int len)
{
  u_int i, opcode;
  
  if (len < 36 || buf[0] != 5)
    return (0);
  
  opcode = pletohs(&buf[6]);
  
  if (opcode == 260) {
    if (buf[32] == 0)
      return (strlcpy(Buf, "[]\n", sizeof(Buf)));
  }
  else if (opcode == 261) {
    if (pletohl(&buf[32]) == -1)
      return (strlcpy(Buf, "[]\n", sizeof(Buf)));
  }
  else return (0);
  
  buf[len - 3]= '\0'; strtok(&buf[32], "\r\n");
  snprintf(Buf, sizeof(Buf), "%s [", &buf[32]);
  len = strlen(Buf);
  i = base64_decode(&buf[32], &Buf[len], sizeof(Buf) - len - 3);
  Buf[len + i] = '\0';
  strlcat(Buf, "]\n", sizeof(Buf));
  
  return (strlen(Buf));
}

int
decode_icq(u_char *buf, int len)
{
  u_int uin;
  char *p;

  if (len < 2)
    return (0);
  
  switch (pletohs(buf)) {
  case 2:
    if (len < ICQ2_PASS_OFFSET + 1)
      return (0);
    
    if (pletohs(&buf[ICQ2_CMD_OFFSET]) != 1000)
      return (0);

    uin = pletohs(&buf[ICQ2_UIN_OFFSET]);
    p = buf + ICQ2_PASS_OFFSET;
    break;
    
  case 5:
    {
      u_long a1, a2, a3, a4, a5, c, key, i, k;
      
      if (len < ICQ5_PASS_OFFSET + 1)
	return (0);
      
      c = pletohl(&buf[ICQ5_CKSUM_OFFSET]);
      
      a1 = c & 0x0001f000; a1 = a1 >> 0x0c;
      a2 = c & 0x07c007c0; a2 = a2 >> 0x01;
      a3 = c & 0x003e0001; a3 = a3 << 0x0a;
      a4 = c & 0xf8000000; a4 = a4 >> 0x10;
      a5 = c & 0x0000083e; a5 = a5 << 0x0f;
      
      key = len * 0x68656C6C;
      key += a1 + a2 + a3 + a4 + a5;
      
      for (i = 0x0a; i < len + 3; i += 4) {
	k = key + icq5_table[i & 0xff];
	if (i != 0x16) {
	  buf[i] ^= (u_char)(k & 0xff);
	  buf[i + 1] ^= (u_char)((k & 0xff00) >> 8);
	}
	if (i != 0x12) {
	  buf[i + 2] ^= (u_char)((k & 0xff0000) >> 16);
	  buf[i + 3] ^= (u_char)((k & 0xff000000) >> 24);
	}
      }
      if (pletohs(&buf[ICQ5_CMD_OFFSET]) != 1000)
	return (0);
      
      uin = pletohl(&buf[ICQ5_UIN_OFFSET]);
      p = buf + ICQ5_PASS_OFFSET;
    }
    break;

  default:
    return (0);
  }
  snprintf(Buf, sizeof(Buf), "%d %s\n", uin, p);

  len = strlen(Buf);

  if (!is_ascii_string(Buf, len))
    return (0);
  
  return (len);
}

int
decode_napster(u_char *buf, int len)
{
  u_short i, type;

  if (len < 4) return (0);
  
  i = pletohs(buf);
  type = pletohs(&buf[2]);

  if (type != 2) return (0);
  if (i > len - 4) return (0);

  buf[4 + i] = '\0';
  
  strlcpy(Buf, buf + 4, sizeof(Buf));
  strlcat(Buf, "\n", sizeof(Buf));

  i = strlen(Buf);

  if (!is_ascii_string(Buf, i))
    return (0);
  
  return (i);
}

int
decode_irc(u_char *buf, int len)
{
  int got_auth = 0;
  char *p, *q;
  
  Buf[0] = '\0';
  
  for (p = strtok(buf, "\r\n"); p != NULL; p = strtok(NULL, "\r\n")) {
    if (p[0] == ';') {
      if ((q = strchr(p, ' ')) == NULL)
	continue;
      q++;
    }
    else q = p;
    
   if (strncasecmp(q, "USER ", 5) == 0 ||
             strncasecmp(q, "NICK ", 5) == 0) {
      strlcat(Buf, p, sizeof(Buf));
      strlcat(Buf, "\n", sizeof(Buf));
    }
    else if (strncasecmp(q, "PASS ", 5) == 0 ||
             strncasecmp(q, "OPER ", 5) == 0 ||
             (strncasecmp(q, "MODE ", 5) == 0 && strstr(q, " +k ") != NULL) ||
             (strncasecmp(q, "JOIN ", 5) == 0 &&
              (q = strchr(q + 5, ' ')) != NULL && q[1] != '\0')) {
      got_auth = 1;
      strlcat(Buf, p, sizeof(Buf));
      strlcat(Buf, "\n", sizeof(Buf));
    }
  }
  if (!got_auth)
    return (0);
  
  return (strlen(Buf));
}

/* XXX - dag nasty. anyone have SQL*Net v2/Net8 protocol specs? */
int
decode_oracle(u_char *buf, int len)
{
  u_char *p, *q;
  u_short i, j;

  p = buf;
  
  /* Save TNS connect string. */
  i = ntohs(*(u_short *)p);
  if (i >= len) return (0);
  for (q = p + i; q != p && q[-1]; q--) ;
  p[i] = '\0';
  strlcpy(Buf, q, sizeof(Buf));
  strlcat(Buf, "\n", sizeof(Buf));
  p += i;

  /* XXX - skip initial username message. */
  if ((p = bufbuf(p, len, "(TNS V", 6)) == NULL) return (0);
  if ((i = len - (p - buf)) <= 0) return (0);
  if ((p = memchr(p, ')', i)) == NULL) return (0);

  /* Parse auth messages. */
  for (p++; p - buf < len; p += i) {
    i = ntohs(*(u_short *)p);
    if (i > len - (p - buf) || i < 56)	/* XXX */
      break;
    if (memcmp(p + 4, "\x06\x00\x00\x00\x00\x00\x03\x3a", 8) != 0)
      continue;
    j = ntohs(*(u_short *)(p + 19));
    for (q = p + i; q != p && q[-1]; q--) ;
    q[j] = '\0';
    strlcat(Buf, q, sizeof(Buf));
    strlcat(Buf, "\n", sizeof(Buf));
  }
  return (strlen(Buf));
}

/* XXX - lame. */
int
decode_x11(u_char *buf, int len)
{
  char *p, *q;
  int i;

  p = buf + 12;

  if (strncmp(p, "MIT-MAGIC-COOKIE-1", 18) != 0 || len < 36)
    return (0);
  
  strlcpy(Buf, "MIT-MAGIC-COOKIE-1 ", sizeof(Buf));
  
  p += 20;
  len -= 20;
  q = Buf + 19;
  
  for (i = 0; i < 16 && i < len; i++)
    sprintf(q + (i * 2), "%.2x", (u_char)p[i]);
  strlcat(Buf, "\n", sizeof(Buf));
  
  return (strlen(Buf));
}

#define ASN1_INTEGER	2
#define ASN1_STRING	4
#define ASN1_SEQUENCE	16

int
decode_snmp(u_char *buf, int len)
{
  u_char *p = buf;
  int i, vers;

  if (len < 20) return (0);
  
  if (asn1_type(&p) != ASN1_SEQUENCE)
    return (0);
  i = asn1_len(&p);		/* XXX - skip sequence length */
  
  if (asn1_type(&p) != ASN1_INTEGER)
    return (0);
  if (asn1_len(&p) != 1)	/* XXX - check version length (always 1?) */
    return (0);
  vers = *p++;

  if (asn1_type(&p) != ASN1_STRING)
    return (0);
  i = asn1_len(&p);
  if ((p - buf) + i > len)
    return (0);
  p[i] = '\0';

  snprintf(Buf, sizeof(Buf), "[version %d]\n%s\n", vers + 1, p);
  
  return (strlen(Buf));
}

int
decode_ldap(u_char *buf, int len)
{
  u_char *p, *q;
  int i, type, alen = len;
  char *dn, *auth;

  Buf[0] = '\0';

  for (q = buf; q + 10 < buf + len; q += alen) {
    p = q;
    type = asn1_type(&p);
    if ((alen = asn1_len(&p)) <= 0) break;
    alen += (p - q);
    
    if (type != ASN1_SEQUENCE)		/* LDAPMessage */
      continue;
    
    if (asn1_type(&p) != ASN1_INTEGER)	/* messageID */
      continue;
    p += asn1_len(&p);

    if (*p != 0x60) continue;		/* op [APPLICATION 0] */
    p += 2;

    if (asn1_type(&p) != ASN1_INTEGER)	/* version */
      continue;
    p += asn1_len(&p);
    
    if (asn1_type(&p) != ASN1_STRING)	/* name */
      continue;
    if ((i = asn1_len(&p)) <= 0)
      continue;

    p--; memmove(p, p + 1, i); p[i] = '\0';	/* XXX - i suk */
    dn = p;
    p += (i + 1);

    if (*p++ != 0x80) continue;		/* simple auth [0] */
    if ((i = asn1_len(&p)) <= 0)
      continue;
    p--; memmove(p, p + 1, i); p[i] = '\0';	/* XXX - i suk */
    auth = p;

    snprintf(Buf, sizeof(Buf) - strlen(Buf), "%s\n%s\n", dn, auth);
  }
  return (strlen(Buf));
}

/* thanks to Pascal Longpre <longprep@HOTMAIL.COM> for his BUGTRAQ post
   on pcAnywhere encryption, and for providing me with traffic traces. */
int
decode_pcanywhere(u_char *buf, int len)
{
  u_char *p, *end;
  int i;
  
  Buf[0] = '\0';

  if (len < 6 || *(u_long *)buf != 0)
    return (0);

  /* Version 7, no encryption. */
  if (buf[4] != 0x8d) {
    buf[len - 1] = '\0';
    if ((p = index(buf + 4, 0x6f)) != NULL) *p = '\0';
    
    for (p = strtok(buf + 5, "\r"); p != NULL; p = strtok(NULL, "\r")) {
      strlcat(Buf, p, sizeof(Buf));
      strlcat(Buf, "\n", sizeof(Buf));
    }
    return (strlen(Buf));
  }
  /* Version 9, lame encryption. */
  end = buf + len;
  for (p = buf + 5; end - p > 2; p++) {
    if (*p++ != 0x06)
      break;

    if (p > end || *p > 56 || end - p - 1 < *p)
      break;
    
    for (i = *p++ - 1; i > 0; i--)
      p[i] = p[i-1] ^ p[i] ^ (i - 1);
    p[0] ^= 0xab;
    
    i = *--p; memmove(p, p + 1, i); p[i] = '\0';	/* XXX - i suk */
    
    if (is_ascii_string(p, i)) {
      strlcat(Buf, p, sizeof(Buf));
      strlcat(Buf, "\n", sizeof(Buf));
    }
    p += i;
  }
  return (strlen(Buf));
}

/* thanks to <jeremie@monkey.org> for providing me with traffic traces. */
int
decode_citrix(u_char *buf, int len)
{
  static u_char ica_magic[] = { 0x32, 0x26, 0x85, 0x92, 0x58 };
  u_char key, *p, *end;
  int i, lines;

  end = buf + len;
  i = 0;
  
  for (p = buf; p != NULL && end - p > 60 && i < sizeof(Buf); ) {
    if ((p = bufbuf(p, len, ica_magic, 5)) == NULL)
      break;
    
    key = p[17]; p += 60;
    Buf[i++] = (key | 'C') ^ p[0];
    lines = 0;
    
    for (p++; p < end; p += 2) {
      if (i > sizeof(Buf) - 1)
	break;
      
      if ((Buf[i++] = p[0] ^ p[1] ^ key) == '\0') {
	Buf[i - 1] = '\n';
	if (++lines >= 3)
	  break;
      }
    }
  }
  Buf[i] = '\0';
  
  return (i);
}

struct smbhdr {
  u_char	proto[4];
  u_char	cmd;
  u_char	err[4];
  u_char	flags1;
  u_short	flags2;
  u_short	pad[6];
  u_short	tid, pid, uid, mid;
};

int
decode_smb(u_char *buf, int len)
{
  struct smbhdr *smb;
  int i, j, k;
  u_char *p, *q, *end;
  char *user, *pass;

  Buf[0] = '\0';
  
  /* Skip NetBIOS session request. */
  if (len < 4 || buf[0] != 0x81) return (0);
  buf += 2;
  GETSHORT(i, buf); len -= 4;
  if (len < i) return (0);
  buf += i; len -= i;
  end = buf + len;
  
  /* Parse SMBs. */
  for (p = buf; p < end; p += i) {
    GETLONG(i, p);
    if (i > end - p || i < sizeof(*smb) + 32)
      continue;
    
    smb = (struct smbhdr *)p;
    if (memcmp(smb->proto, "\xffSMB", 4) != 0 || smb->cmd != 0x73)
      continue;
    
    user = pass = NULL;
    q = (u_char *)(smb + 1);
    
    if (*q == 10) {		/* Pre NT LM 0.12 */
      q += 15; j = pletohs(q); q += 2;
      if (j > i - (sizeof(*smb) + 15 + 6))
	continue;
      pass = q + 6;
      user = pass + j;
    }
    else if (*q == 13) {	/* NT LM 0.12 */
      q += 15; j = pletohs(q);
      q += 2;  k = pletohs(q);
      if (j > i - ((q - p) + 12) || k > i - ((q - p) + 11))
	continue;
      pass = q + 12;
      user = pass + j + k;
    }
    else continue;

    /* XXX - skip null IPC sessions, etc. */
    if (user && pass && strlen(user) && is_ascii_string(pass, j - 1)) {
      strlcat(Buf, user, sizeof(Buf));
      strlcat(Buf, " ", sizeof(Buf));
      strlcat(Buf, pass, sizeof(Buf));
      strlcat(Buf, "\n", sizeof(Buf));
    }
  }
  return (strlen(Buf));
}

/* Decode mount filehandle into nfsshell format. :-) */
int
decode_mountd(u_char *path, u_char *buf, int len)
{
  int i;
  char fh[128];

  if (len < 32) return (0);
  
  for (i = 0; i < 32; i++) {
    sprintf(fh + (i * 3), "%.2x ", buf[i]);
  }
  fh[95] = '\0';

  return (snprintf(Buf, sizeof(Buf), "%s [%s]\n", path, fh));
}

struct svc svc_map[] = {
  { "ftp",	decode_ftp,	SVC_TCP, { 21, 0 } },
  { "telnet",	decode_telnet,	SVC_TCP, { 23, 0 } },
  { "http",	decode_http,	SVC_TCP, { 80, 3128, 8080, 0 } },
  { "pop",	decode_pop,	SVC_TCP, { 109, 110, 0 } },
  { "nntp",	decode_nntp,	SVC_TCP, { 119, 0 } },
  { "smb",	decode_smb,	SVC_TCP, { 139, 0 } },
  { "imap",	decode_imap,	SVC_TCP, { 143,	220, 0 } },
  { "snmp",	decode_snmp,	SVC_UDP, { 161, 0 } },
  { "ldap",	decode_ldap,	SVC_TCP, { 389, 0 } },
  { "rlogin",	decode_rlogin,	SVC_TCP, { 513, 514, 0 } },
  { "socks",	decode_socks,	SVC_TCP, { 1080, 0 } },
  { "citrix",	decode_citrix,	SVC_TCP, { 1494, 0 } },
  { "oracle",	decode_oracle,	SVC_TCP, { 1521, 1526, 0 } },
  { "sniffer",	decode_sniffer,	SVC_UDP, { 2001, 0 } },
  { "cvs",	decode_cvs,	SVC_TCP, { 2401, 0 } },
  { "icq",	decode_icq,	SVC_UDP, { 4000, 0 } },
  { "napster",	decode_napster,	SVC_TCP, { 4444, 5555, 6666, 7777, 8888, 0 } },
  { "aim",	decode_aim,	SVC_TCP, { 5190, 9898, 0 } },
  { "pcanywhere", decode_pcanywhere,	SVC_TCP, { 5631, 65301, 0 } },
  { "x11",	decode_x11,	SVC_TCP, { 6000, 6001, 6002, 6003, 6004, 6005, 0 } },
  { "irc",	decode_irc,	SVC_TCP, { 6667, 6668, 6669, 0 } },
  { "mountd",	NULL,		SVC_RPC, { PROG_MOUNT, 0 } },
  { NULL }
};

struct svc *
svc_find(int proto, int port)
{
  struct svc *s;
  int *p;

  for (s = svc_map; s->name != NULL; s++) {
    if ((s->proto & proto) || proto == 0) {
      for (p = s->ports; *p != 0; p++)
	if (*p == port) return (s);
    }
  }
  return (NULL);
}

void
print_rec(struct dsniff_rec *dr)
{
  char tstring[24];
  struct tm *tm;
  char *src, *dst;
  struct svc *s;

  tm = localtime(&dr->time);
  strftime(tstring, sizeof(tstring), "%x %X", tm);

  src = libnet_host_lookup(dr->src, Opt_dns);
  dst = libnet_host_lookup(dr->dst, Opt_dns);
  if ((s = svc_find(0, dr->port)) == NULL)
    return;
  
  printf("-----------------\n");
  printf("%s %s -> %s (%s)\n", tstring, src, dst, s->name);
  fwrite(dr->data, 1, dr->len, stdout);
  printf("\n");
  fflush(stdout);
}

void
print_rec_uniq(struct dsniff_rec *dr)
{
  DBT data;

  dr->time = time(NULL);

  data.size = sizeof(*dr) + dr->len;
  
  if ((data.data = malloc(data.size)) == NULL)
    nids_params.no_mem("print_rec");

  /* XXX - who needs portable sniffer logs anyhow? */
  *((struct dsniff_rec *)data.data) = *dr;
  memcpy((char *)data.data + sizeof(*dr), dr->data, dr->len);

  if (db->put(db, db_md5_key(dr), &data, R_NOOVERWRITE) == 0) {
    if (!Opt_write)
      print_rec(dr);
    db->sync(db, 0);
  }
}
  
void
print_db(DB *db)
{
  DBT key, data;
  struct dsniff_rec *dr;

  while (db->seq(db, &key, &data, R_NEXT) == 0) {
    dr = (struct dsniff_rec *)data.data;
    dr->data = (char *)data.data + sizeof(*dr);
    print_rec(dr);
  }
}

void
process_client(int proto, struct tuple4 *addr, u_char *buf, int len)
{
  struct svc *s;
  struct dsniff_rec dr;

  if (len <= 0 || (s = svc_find(proto, addr->dest)) == NULL)
    return;
  
  if ((dr.len = s->decode(buf, len)) > 0) {
    dr.src = addr->saddr;
    dr.dst = addr->daddr;
    dr.port = addr->dest;
    dr.data = Buf;
    
    print_rec_uniq(&dr);
  }
}

struct rpc_record {
  u_long rm;
  u_char *data;
  int len;
};

struct rpc_call {
  int xid;
  int prog;
  int proc;
  u_char *cred;
  u_char *verf;
  u_char *args;
};

struct rpc_reply { /* accepted, successful only */
  int xid;
  u_char *verf;
  u_char *results;
};

void
process_rpc_call(struct tuple4 *addr, u_char *buf, int len)
{
  struct rpc_msg *rm;
  struct rpc_call rc;
  struct dsniff_rec dr;
  u_char *p;
  int i;

  if (len < 32) return;
  
  rm = (struct rpc_msg *)buf;
  rc.xid = ntohl(rm->rm_xid);
  rc.prog = ntohl(rm->rm_call.cb_prog);
  rc.proc = ntohl(rm->rm_call.cb_proc);
  rc.cred = (u_char *)&rm->rm_call.cb_cred;

  p = rc.cred + 4;			/* skip cred type */
  GETLONG(i, p); p += i;		/* skip cred data */
  if ((p - buf) + 4 > len) return;
  rc.verf = p; p += 4;			/* skip verf type */
  GETLONG(i, p); p += i;		/* skip verf data */
  if ((len -= (p - buf)) < 4) return;
  rc.args = p;
  
  switch (rc.prog) {
  case PROG_MOUNT:
    if (rc.proc != 1) return;
    if ((i = ntohl(*((u_long *)rc.args))) > 1024 || len < i + 4)
      return;
    if ((p = malloc(i + 1)) == NULL)
      nids_params.no_mem("process_rpc_client");
    strlcpy(p, rc.args + 4, i + 1);
    xid_map_enter(addr->saddr, addr->daddr, rc.xid, rc.prog, p);
    return;
    break;
  case PROG_PMAP:
    if (rc.proc != 3) return;
    if (ntohl(*((u_long *)rc.args)) == PROG_MOUNT)
      xid_map_enter(addr->saddr, addr->daddr, rc.xid, rc.prog, NULL);
    return;
    break;
  default:
    return;
    break;
  }
  /* for future handling of other RPC services */
  dr.src = addr->saddr;
  dr.dst = addr->daddr;
  dr.port = rc.prog;
  dr.data = Buf;
  
  print_rec_uniq(&dr);
}

void
process_rpc_reply(struct tuple4 *addr, u_char *buf, int len)
{
  struct rpc_msg *rm;
  struct rpc_reply rr;
  struct xid_map_entry *xp;
  struct dsniff_rec dr;
  u_char *p;
  int i;

  rm = (struct rpc_msg *)buf;
  rr.xid = ntohl(rm->rm_xid);
  
  if ((xp = xid_map_find(addr->daddr, addr->saddr, rr.xid)) == NULL)
    return;

  if (len > 20 && ntohl(rm->rm_reply.rp_stat) == MSG_ACCEPTED) {
    rr.verf = (u_char *)&rm->rm_reply.rp_acpt.ar_verf;
    p = rr.verf + 4;				/* skip verf type */
    GETLONG(i, p); p += i;			/* skip verf data */
    if ((p - buf) + i + 4 < len) {
      GETLONG(i, p);				/* get accept_stat */
      if (i == SUCCESS) {
	dr.len = 0;
	switch (xp->prog) {
	case PROG_MOUNT:
	  dr.len = decode_mountd(xp->data, p, len - (p - buf));
	  break;
	case PROG_PMAP:
	  pmap_enter(addr->saddr, ntohs(*((u_short *)p)));
	  break;
	}
	if (dr.len) {
	  dr.src = addr->daddr;
	  dr.dst = addr->saddr;
	  dr.port = xp->prog;
	  dr.data = Buf;
	  print_rec_uniq(&dr);
	}
      }
    }
  }
  free(xp->data);
  memset(xp, 0, sizeof(*xp));
}

/* libnids needs a nids_register_udp()... */
void
sniff_udp_client(struct ip *ip)
{
  struct udphdr *udp;
  struct tuple4 addr;
  u_char *buf;
  int len, ip_hl = ip->ip_hl * 4;
  
  len = ntohs(ip->ip_len) - ip_hl;
  
  if (ip->ip_p != IPPROTO_UDP || len < sizeof(*udp))
    return;
  
  buf = (u_char *)ip + ip_hl;
  udp = (struct udphdr *)buf;
  
  if (len != ntohs(udp->uh_ulen))
    return;
  
  buf += sizeof(*udp); len -= sizeof(*udp);
  
  /* XXX - ugly but fast. */
  switch (ntohs(udp->uh_dport)) {
  case 161:		/* SNMP */
    break;
  case 2001:		/* NAI Sniffer */
    break;
  case 4000:		/* ICQ */
    break;
  default:
    return;
    break;
  }
  addr.saddr = ip->ip_src.s_addr;
  addr.daddr = ip->ip_dst.s_addr;
  addr.source = ntohs(udp->uh_sport);
  addr.dest = ntohs(udp->uh_dport);
  
  process_client(SVC_UDP, &addr, buf, len);
}

void
sniff_udp_rpc(struct ip *ip)
{
  struct udphdr *udp;
  struct rpc_msg *rm;
  struct tuple4 addr;
  u_char *buf;
  int len, ip_hl = ip->ip_hl * 4;
  
  len = ntohs(ip->ip_len) - ip_hl;
  
  if (ip->ip_p != IPPROTO_UDP || len < sizeof(*udp))
    return;
  
  buf = (u_char *)ip + ip_hl;
  udp = (struct udphdr *)buf;
  
  if (len != ntohs(udp->uh_ulen))
    return;
  
  buf += sizeof(*udp); len -= sizeof(*udp);

  if (len < 8) return;
  
  addr.saddr = ip->ip_src.s_addr;
  addr.daddr = ip->ip_dst.s_addr;
  addr.source = ntohs(udp->uh_sport);
  addr.dest = ntohs(udp->uh_dport);

  rm = (struct rpc_msg *)buf;
  
  /* Only handle RPC call/replies for registered RPC services. */
  if (pmap_find(addr.daddr, addr.dest) &&
      ntohl(rm->rm_direction) == CALL) {
    process_rpc_call(&addr, buf, len);
  }
  else if (pmap_find(addr.saddr, addr.source) &&
	   ntohl(rm->rm_direction) == REPLY) {
    process_rpc_reply(&addr, buf, len);
  }
}

void
sniff_tcp_client(struct tcp_stream *ts, void **whatever)
{
  int len;
  
  /* XXX - ugly but fast. */
  switch (ts->addr.dest) {
  case 21:				/* FTP */
  case 23:				/* Telnet */
  case 80: case 3128: case 8080:	/* HTTP */
  case 109: case 110:			/* POP */
  case 119:				/* NNTP */
  case 139:				/* SMB */
    break;
  case 143: case 220:			/* IMAP */
  case 389:				/* LDAP */
  case 513: case 514:			/* R-commands */
    break;
  case 1080:				/* SOCKS */
    break;
  case 1494:				/* Citrix ICA */
    break;
  case 1521: case 1526:			/* Oracle SQL*Net */
    break;
  case 2401:				/* CVS pserver */
    break;
  case 4444: case 5555: case 6666: case 7777: case 8888:	/* napster */
    break;
  case 5190: case 9898:			/* XXX - AOL, gaim */
    break;
  case 5631: case 65301:		/* pcAnywhere */
    break;
  case 6000: case 6001: case 6002: case 6003: case 6004: case 6005: /* X11 */
    break;
  case 6667: case 6668: case 6669:	/* IRC */
    break;
  default:
    return;
    break;
  }  
  switch (ts->nids_state) {
    
  case NIDS_JUST_EST:
    ts->server.collect = 1;
    break;
    
  case NIDS_DATA:
    len = ts->server.count - ts->server.offset;
    if (len >= Opt_snaplen) {
      if (ts->server.bufsize > len)
	ts->server.data[len] = '\0';
      process_client(SVC_TCP, &ts->addr, ts->server.data, len);
      ts->server.collect = 0;
    }
    else nids_discard(ts, 0);
    break;
    
  default:
    len = ts->server.count - ts->server.offset;
    if (ts->server.count != 0) {
      if (ts->server.bufsize > len)
	ts->server.data[len] = '\0';
      process_client(SVC_TCP, &ts->addr, ts->server.data, len);
    }
    break;
  }
}

#define FRAGLEN(x)	(x & 0x7fffffff)
#define LASTFRAG(x)	(x & (1 << 31))

void
sniff_tcp_rpc(struct tcp_stream *ts, void **conn_save)
{
  struct rpc_msg *rm;
  struct rpc_record *rr;
  struct tuple4 addr;
  u_char *buf;
  int i, is_client;

  /* Only handle RPC call/replies for registered RPC services. */
  if (!pmap_find(ts->addr.daddr, ts->addr.dest))
    return;
  
  switch (ts->nids_state) {
    
  case NIDS_JUST_EST:
    ts->client.collect = 1;
    ts->server.collect = 1;
    
    if ((rr = calloc(sizeof(*rr) * 2, 1)) == NULL)
      nids_params.no_mem("sniff_tcp_rpc");
    
    *conn_save = (void *)rr;
    break;
    
  case NIDS_DATA:
    is_client = ts->server.count_new;

    if (is_client) {
      rr = (struct rpc_record *)*conn_save;
      buf = ts->server.data;
      i = ts->server.count;
      addr = ts->addr;
    }
    else {
      rr = (struct rpc_record *)*conn_save + 1;
      buf = ts->client.data;
      i = ts->client.count;
      addr.saddr = ts->addr.daddr;
      addr.daddr = ts->addr.saddr;
      addr.source = ts->addr.dest;
      addr.dest = ts->addr.source;
    }

    /* Process RPC records. */
    if (rr->rm == 0) {
      rr->rm = ntohl(*((u_long *)buf));
    }
    /* Check for complete fragment. */
    if (i < 4 + FRAGLEN(rr->rm)) {
      nids_discard(ts, 0);
      return;
    }
    /* We have a complete fragment. Build up message. */
    if (rr->data == NULL && (rr->data = malloc(FRAGLEN(rr->rm))) == NULL)
      nids_params.no_mem("sniff_tcp_rpc");
    else if ((rr->data = realloc(rr->data, rr->len + FRAGLEN(rr->rm))) == NULL)
      nids_params.no_mem("sniff_tcp_rpc");
    
    memcpy(rr->data + rr->len, buf + 4, FRAGLEN(rr->rm));
    rr->len += FRAGLEN(rr->rm);

    /* Check for complete message. */
    if (!LASTFRAG(rr->rm)) {
      rr->rm = 0;
      nids_discard(ts, 4 + FRAGLEN(rr->rm));
      return;
    }
    rr->rm = 0;

    if (rr->len < 8) return;
    rm = (struct rpc_msg *)rr->data;
    
    if (is_client && ntohl(rm->rm_direction) == CALL) {
      process_rpc_call(&addr, rr->data, rr->len);
    }
    else if (!is_client && ntohl(rm->rm_direction) == REPLY &&
	     ntohl(rm->rm_reply.rp_stat) == MSG_ACCEPTED) {
      process_rpc_reply(&addr, rr->data, rr->len);
    }
    break;
    
  default:
    rr = (struct rpc_record *)*conn_save;
    for (i = 0; i < 2; i++) {
      if (rr[i].data != NULL)
	free(rr[i].data);
    }
    free(rr);
    ts->client.collect = ts->server.collect = 0;
    break;
  }
}

void
null_syslog(int type, int errnum, struct ip *iph, void *data)
{
  /* XXX - quiet libnids. */
}

int
main(int argc, char *argv[])
{
  int c;
  char *dbname = NULL;
#ifdef WIN32
  char *dev = NULL;
  char err[PCAP_ERRBUF_SIZE];
#endif

#ifdef WIN32
  while ((c = getopt(argc, argv, "i:nDs:r:w:h?V")) != -1) {
#else
  while ((c = getopt(argc, argv, "i:ns:r:w:h?V")) != -1) {
#endif
    switch (c) {
#ifdef WIN32
    case 'D':
    dev = pcap_lookupdev(err);
	if (dev == NULL)
		perror(err);
		
      PrintDeviceList(dev);
      exit(0);
#endif      
	case 'i':
      nids_params.device = optarg;
      break;
    case 'n':
      Opt_dns = 0;
      break;
    case 's':
      if ((Opt_snaplen = atoi(optarg)) == 0)
	usage();
      break;
    case 'r':
      Opt_read = 1;
      dbname = optarg;
      break;
    case 'w':
      Opt_write = 1;
      dbname = optarg;
      break;
    case 'V':
      fprintf(stderr, "Version: %s\n", VERSION);
      usage();
      break;
    default:
      usage();
    }
  }
  argc -= optind;
  argv += optind;

  if (argc != 0)
    usage();
#ifdef WIN32
  if(!init_winsock()){
	  perror("init_winsock");
	  exit(1);
  }
#endif

  if (Opt_read) {
    if ((db = dbopen(dbname, O_RDONLY, 0, DB_BTREE, NULL)) == NULL) {
      perror("dbopen");
      exit(1);
    }
    print_db(db);
    exit(0);
  }
  if ((db = dbopen(dbname, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR,
		   DB_BTREE, NULL)) == NULL) {
    perror("dbopen");
    exit(1);
  }
  memset(&pmap_map, 0, sizeof(pmap_map));
  memset(&xid_map, 0, sizeof(xid_map));
  
  nids_params.scan_num_hosts = 0;
#ifndef WIN32
  nids_params.syslog = null_syslog;
#endif

  if (!nids_init()) {
    fprintf (stderr, "%s\n", nids_errbuf);
    exit(1);
  }
  nids_register_ip(sniff_udp_client);
  nids_register_ip(sniff_udp_rpc);
  nids_register_tcp(sniff_tcp_client);
  nids_register_tcp(sniff_tcp_rpc);
  
  nids_run();

  /* NOTREACHED */
  
  exit(0);
}

#ifdef WIN32
int init_winsock(void) {
WORD wVersionRequested;
WSADATA wsaData;

        wVersionRequested = MAKEWORD(2, 0);
        if (WSAStartup(wVersionRequested, &wsaData)) {
                fprintf(stderr, "Error: Unable to find a useable winsock.dll!\n");
                return 0;
        }

        if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0) {
                fprintf(stderr, "Error: Unable to find a 2.0 compatible version of winsock!\n");
                WSACleanup();
                return 0;
        }

        return 1;
}

u_char *libnet_host_lookup(u_long in, u_short use_name)
{
    static u_char hostname[512], hostname2[512];
    static u_short which;
    u_char *p;

    struct hostent *host_ent = NULL;
    struct in_addr addr;

    /*
     *  Swap to the other buffer.  We swap static buffers to avoid having to
     *  pass in a char *.  This makes the code that calls this function more
     *  intuitive, but makes this function ugly.  This function is seriously
     *  non-reentrant.  For threaded applications (or for signal handler code)
     *  use host_lookup_r().
     */
    which++;

    if (use_name == 1)
    {
        addr.s_addr = in;
        host_ent = gethostbyaddr((char *)&addr, sizeof(struct in_addr), AF_INET);
    }
    if (!host_ent)
    {

        p = (u_char *)&in;
        sprintf(((which % 2) ? hostname : hostname2),  "%d.%d.%d.%d",
                (p[0] & 255), (p[1] & 255), (p[2] & 255), (p[3] & 255));
    }
    else if (use_name == 1)
    {
        strncpy(((which % 2) ? hostname : hostname2), host_ent->h_name,
                                                        sizeof(hostname));
    }
    return (which % 2) ? (hostname) : (hostname2);
}

void PrintDeviceList(const char* device)
{
        DWORD dwVersion;
        DWORD dwWindowsMajorVersion;
        const WCHAR* t;
        const char* t95;
        int i=0;
        int DescPos=0;
        char *Desc;
        int n=1;

        dwVersion=GetVersion();
        dwWindowsMajorVersion =  (DWORD)(LOBYTE(LOWORD(dwVersion)));
		/* Windows 95. */
        if (dwVersion >= 0x80000000 && dwWindowsMajorVersion >= 4)
        {
                t95=(char*)device;

                while(*(t95+DescPos)!=0 || *(t95+DescPos-1)!=0){
                        DescPos++;
                }

                Desc=(char*)t95+DescPos+1;
				printf("Interface\tDevice\t\tDescription\n-------------------------------------------\n");
                printf("%d  ",n++);
				
                while (!(t95[i]==0 && t95[i-1]==0))
                {
                        if (t95[i]==0){
                                putchar(' ');
                                putchar('(');
                                while(*Desc!=0){
                                        putchar(*Desc);
                                        Desc++;
                                }
                                Desc++;
                                putchar(')');
                                putchar('\n');
                        }
                        else putchar(t95[i]);

                        if((t95[i]==0) && (t95[i+1]!=0)){
                                printf("%d ",n++);
                        }

                        i++;
                }
                putchar('\n');
        }

        else{           
				/* WinNT. */
                t=(WCHAR*)device;
                while(*(t+DescPos)!=0 || *(t+DescPos-1)!=0){
                        DescPos++;
                }

                DescPos<<=1;
                Desc=(char*)t+DescPos+2;
				printf("Interface\tDevice\t\tDescription\n-------------------------------------------\n");
                printf("%d  ",n++);
				
                while (!(t[i]==0 && t[i-1]==0))
                {
                        if (t[i]==0)
                        {
                                putchar(' ');
                                putchar('(');
                                while(*Desc!=0){
                                        putchar(*Desc);
                                        Desc++;
                                }
                                Desc++;
                                putchar(')');
                                putchar('\n');
                        }
                        else putchar(t[i]);

                        if(t[i]==0 && t[i+1]!=0)printf("%d ",n++);

                        i++;
                }
                putchar('\n');
        }
}

#endif
/* 5000. */
