Logo Search packages:      
Sourcecode: mailavenger version File versions  Download package

synfp.C

/* $Id$ */

/*
 *
 * Copyright (C) 2003 David Mazieres (dm@uun.org)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

#include "async.h"
#include "serial.h"
#include "parseopt.h"
#include "rawnet.h"

#if USE_SYNFP

#include <net/if.h>
#ifdef HAVE_SYS_SOCKIO_H
#include <sys/sockio.h>
#endif /* HAVE_SYS_SOCKIO_H */

#ifndef HAVE_BPF_U_INT32
typedef u_int32_t bpf_u_int32;
#endif /* !HAVE_BPF_U_INT32 */

#ifndef HAVE_PCAP_FREECODE
#define pcap_freecode(x)
#endif /* !HAVE_PCAP_FREECODE */

bool
synfp::pktok (const u_char *ip, const u_char *e)
{
  if (ip + 20 > e || ip[0]>>4 != 4 || ip[9] != IPPROTO_TCP)
    return false;

  u_int len = (ip[0] & 0xf) << 2;
  if (len < 20 || ip + len > e)
    return false;

  return cksum (ip, len) == 0xffff;
}

bool
synfp::ifnames (vec<str> *ifs, in_addr targ)
{
  int s;
  if ((s = socket (AF_INET, SOCK_DGRAM, 0)) < 0) {
    warn ("socket: %m\n");
    return false;
  }

  char buf[0x10000];
  ifconf ifc;
  ifc.ifc_buf = buf;
  ifc.ifc_len = sizeof (buf);

  if (ioctl (s, SIOCGIFCONF, &ifc) < 0) {
    warn ("SIOCGIFCONF: %m\n");
    ::close (s);
    return false;
  }

  /* The +64 is for large addresses (e.g., IPv6), in which sa_len
   * may be greater than the struct sockaddr inside ifreq. */
  if (ifc.ifc_len + sizeof (struct ifreq) + 64 > sizeof (buf)) {
    warn ("SIOCGIFCONF: result too large\n");
    ::close (s);
    return false;
  }

  bhash<str> seen;
  for (str *sp = ifs->base (); sp < ifs->lim (); sp++)
    seen.insert (*sp);

  char *p = ifc.ifc_buf, *e = p + ifc.ifc_len;
  while (p < e) {
    struct ifreq *ifrp = (struct ifreq *) p;
    struct ifreq ifr = *ifrp;
#ifndef HAVE_SA_LEN
    p += sizeof (ifr);
#else /* !HAVE_SA_LEN */
    p += sizeof (ifrp->ifr_name)
      + max (sizeof (ifrp->ifr_addr), (size_t) ifrp->ifr_addr.sa_len);
#endif /* !HAVE_SA_LEN */
    if (ifrp->ifr_addr.sa_family != AF_INET)
      continue;
    in_addr a = ((struct sockaddr_in *) &ifrp->ifr_addr)->sin_addr;
    if (targ.s_addr != htonl (INADDR_ANY)) {
      if (targ.s_addr != a.s_addr)
      continue;
    }
    else if (a.s_addr == htonl (INADDR_LOOPBACK))
      continue;
    str ifn (strbuf ("%.*s", (int) sizeof (ifr.ifr_name), ifr.ifr_name));
    if (ioctl (s, SIOCGIFFLAGS, &ifr) < 0) {
      warn ("SIOCGIFFLAGS (%s): %m\n", ifn.cstr ());
      continue;
    }
    if ((ifr.ifr_flags & IFF_UP) && seen.insert (ifn))
      ifs->push_back (ifn);
  }

  ::close (s);

  return true;
}

bool
synfp::ifaddrs (vec<in_addr> *addrs, str ifname)
{
  int s;
  if ((s = socket (AF_INET, SOCK_DGRAM, 0)) < 0) {
    warn ("socket: %m\n");
    return false;
  }

  char buf[0x10000];
  ifconf ifc;
  ifc.ifc_buf = buf;
  ifc.ifc_len = sizeof (buf);

  if (ioctl (s, SIOCGIFCONF, &ifc) < 0) {
    warn ("SIOCGIFCONF: %m\n");
    ::close (s);
    return false;
  }

  /* The +64 is for large addresses (e.g., IPv6), in which sa_len
   * may be greater than the struct sockaddr inside ifreq. */
  if (ifc.ifc_len + sizeof (struct ifreq) + 64 > sizeof (buf)) {
    warn ("SIOCGIFCONF: result too large\n");
    ::close (s);
    return false;
  }

  bhash<in_addr> seen;
  for (in_addr *ap = addrs->base (); ap < addrs->lim (); ap++)
    seen.insert (*ap);

  char *p = ifc.ifc_buf, *e = p + ifc.ifc_len;
  while (p < e) {
    struct ifreq *ifrp = (struct ifreq *) p;
    struct ifreq ifr = *ifrp;
#ifndef HAVE_SA_LEN
    p += sizeof (ifr);
#else /* !HAVE_SA_LEN */
    p += sizeof (ifrp->ifr_name)
      + max (sizeof (ifrp->ifr_addr), (size_t) ifrp->ifr_addr.sa_len);
#endif /* !HAVE_SA_LEN */
    if (ifrp->ifr_addr.sa_family != AF_INET)
      continue;
    str ifn (strbuf ("%.*s", (int) sizeof (ifr.ifr_name), ifr.ifr_name));
    if (ifname && ifn != ifname)
      continue;
    in_addr a = ((struct sockaddr_in *) &ifrp->ifr_addr)->sin_addr;
    if (ioctl (s, SIOCGIFFLAGS, &ifr) < 0) {
      warn ("SIOCGIFFLAGS (%s): %m\n", ifn.cstr ());
      continue;
    }
    if ((ifr.ifr_flags & IFF_UP) && seen.insert (a))
      addrs->push_back (a);
  }

  ::close (s);

  return true;
}

synfp::synfp ()
  : pch (NULL), hdrlen (-1), fd (-1)
{
}

synfp::~synfp ()
{
  close ();
}

void
synfp::close ()
{
  if (fd >= 0) {
    fdcb (fd, selread, NULL);
    fd = -1;
  }
  if (pch) {
    pcap_close (pch);
    pch = NULL;
  }
  hdrlen = -1;
}

bool
synfp::setfilter (str dev, str filstr)
{
  close ();

  char *cdev;
  char errbuf[PCAP_ERRBUF_SIZE];
  if (dev)
    cdev = const_cast<char *> (dev.cstr ());
  else if (!(cdev = pcap_lookupdev (errbuf))) {
    warn ("pcap_lookupdev: %s\n", errbuf);
    return false;
  }

  bpf_u_int32 net, mask;
  if (pcap_lookupnet (cdev, &net, &mask, errbuf) < 0) {
    warn ("pcap_lookupnet: %s\n", errbuf);
    return false;
  }

  // XXX - this kind of sucks, but 1ms is smallest timeout
  pch = pcap_open_live (cdev, 150, 0, 1, errbuf);
  if (!pch) {
    warn ("pcap_open_live: %s\n", errbuf);
    return false;
  }

#if SYNFP_DEBUG
  warn ("filter: %s\n", filstr.cstr ());
#endif /* SYNFP_DEBUG */
  bpf_program filter;
  if (pcap_compile (pch, &filter, const_cast<char *> (filstr.cstr ()),
                1, mask) < 0) {
    warn ("failed to compile PCAP filter: %s\n", filstr.cstr ());
    pcap_close (pch);
    pch = NULL;
    return false;
  }

  if (pcap_setfilter (pch, &filter)) {
    warn ("failed to set PCAP filter\n");
    pcap_freecode (&filter);
    pcap_close (pch);
    pch = NULL;
    return false;
  }

  pcap_freecode (&filter);
  fd = pcap_fileno (pch);
  return true;
}

bool
synfp::init (str dev, in_addr addr, int port)
{
  strbuf sb;
  sb << "tcp[13] & 0x12 == 0x2";
  if (port)
    sb << " and dst port " << port;
  if (addr.s_addr != htonl (INADDR_ANY))
    sb << " and dst host " << inet_ntoa (addr);
  else {
    vec<in_addr> av;
    if (myipaddrs (&av) && !av.empty ()) {
      sb << " and (dst host " << inet_ntoa (av.pop_front ());
      while (!av.empty ())
      sb << " or dst host " << inet_ntoa (av.pop_front ());
      sb << ")";
    }
  }

  return setfilter (dev, sb);
}

bool
synfp::init (str dev, const vec<sockaddr_in> &addrs)
{
  vec<in_addr> lav;
  if ((dev && !ifaddrs (&lav, dev))
      || (!dev && !myipaddrs (&lav)))
    return false;
  bhash<in_addr> local;
  for (in_addr *ap = lav.base (); ap < lav.lim (); ap++)
    local.insert (*ap);

  bhash<sockaddr_in> seen;
  bhash<u_int16_t> seen_port;
  bool any = false;

  strbuf sb;
  sb << "tcp[13] & 0x12 == 0x2 and (";
  for (const sockaddr_in *ap = addrs.base (); ap < addrs.lim (); ap++) {
    if (ap->sin_addr.s_addr != htonl (INADDR_ANY))
      continue;
    if (!seen.insert (*ap))
      continue;
    seen_port.insert (ntohs (ap->sin_port));
    if (any)
      sb << " or ";
    else
      any = true;
    sb.fmt ("dst port %d", ntohs (ap->sin_port));
  }
  for (const sockaddr_in *ap = addrs.base (); ap < addrs.lim (); ap++) {
    if (seen_port[ntohs (ap->sin_port)] || !seen.insert (*ap)
      || !local[ap->sin_addr])
      continue;
    if (any)
      sb << " or ";
    else
      any = true;
    sb.fmt ("(dst port %d and dst host ", ntohs (ap->sin_port));
    sb << inet_ntoa (ap->sin_addr);
    sb << ")";
  }
  sb << ")";

  if (!any)
    return false;

  return setfilter (dev, sb);
}

int
synfp::getfp (str *fp, sockaddr_in *sinp, sockaddr_in *dsinp)
{
  pcap_pkthdr hdr;
  const u_char *buf = pcap_next (pch, &hdr);
  if (!buf)
    return -1;

  const u_char *e = buf + hdr.caplen;
  if (!sethdrlen (buf, e))
    return false;
  const u_char *ip = buf + hdrlen;
  const u_char *tcp = ip + ((ip[0] & 0xf) << 2);
  if (tcp + 20 > e)
    return 0;
  else {
    const u_char *tcpe = tcp + ((tcp[12] & 0xf0) >> 2);
    if (tcpe > e)
      return 0;
    e = tcpe;
  }

  sinp->sin_family = AF_INET;
  memcpy (&sinp->sin_addr, ip + 12, 4);
  memcpy (&sinp->sin_port, tcp, 2);
  if (dsinp) {
    dsinp->sin_family = AF_INET;
    memcpy (&dsinp->sin_addr, ip + 16, 4);
    memcpy (&dsinp->sin_port, tcp + 2, 2);
  }

  strbuf sb ("%d:%d:%d:%d:", getshort (tcp + 14), ip[8],
           !!(ip[6] & 0x40), getshort (ip + 2));

  const u_char *op = tcp + 20;
  const char *sep = "";
  while (op < e) {
    if (*op > 1 && (op + 2 > e || op + op[1] > e || op[1] < 2))
      goto done;
    switch (*op) {
    case 0:
      goto done;
    case 1:
      sb << sep << "N";
      sep = ",";
      op++;
      continue;
    case 2:
      if (op[1] != 4)
      goto done;
      sb.fmt ("%sM%d", sep, getshort (op + 2));
      sep = ",";
      break;
    case 3:
      if (op[1] != 3)
      goto done;
      sb.fmt ("%sW%d", sep, op[2]);
      sep = ",";
      break;
    case 4:
      if (op[1] != 2)
      goto done;
      sb << sep << "S";
      sep = ",";
      break;
    case 8:
      if (op[1] != 10)
      goto done;
      sb << sep << "T";
      if (!getint (op + 2))
      sb << "0";
      sep = ",";
      break;
    }
    op += op[1];
  }

 done:
  *fp = sb;
  return true;
}

bool
synfp::sethdrlen (const u_char *pkt, const u_char *e)
{
  if (hdrlen >= 0 && pktok (pkt + hdrlen, e))
    return true;

  int newlen = -1;

  int dlt = pcap_datalink (pch);
  switch (pcap_datalink (pch)) {
  case DLT_EN10MB:
    newlen = 14;
    break;
  }

  if (newlen >= 0) {
    if (newlen == hdrlen)
      return false;
    hdrlen = newlen;
    return pktok (pkt + hdrlen, e);
  }

  for (const u_char *ip = pkt; ip + 40 <= e; ip++)
    if (pktok (ip, e)) {
      hdrlen = ip - pkt;
      warn ("guessing %d-byte header for unknown datalink type %d\n",
          hdrlen, dlt);
      return true;
    }
  return false;
}

synfp_collect::synfp_collect (u_int msec, u_int bs)
  : tmo (NULL), bufsize (bs)
{
  extern int fd_set_bytes;
  delay.tv_sec = msec / 1000;
  delay.tv_nsec = (msec % 1000) * 1000000;
  fds = static_cast<fd_set *> (xmalloc (fd_set_bytes));
  bzero (fds, fd_set_bytes);
}

synfp_collect::~synfp_collect ()
{
  for (cbentry_t *cbe = cbs.tab.first (); cbe; cbe = cbs.tab.next (cbe))
    (*cbe->val.cb) (NULL);
  if (tmo)
    timecb_remove (tmo);
  xfree (fds);
}

bool
synfp_collect::init (const sockaddr_in &sin)
{
  pfv.clear ();

  vec<str> ifnames;
  synfp::ifnames (&ifnames, sin.sin_addr);
  if (ifnames.empty ())
    ifnames.push_back (NULL);

  for (str *sp = ifnames.base (); sp < ifnames.lim (); sp++) {
    if (*sp)
      warn << "listening for SYN fingerprints on " << *sp << "\n";
    else
      warn << "listening for SYN fingerprints on default pcap interface\n";
    ref<synfp> s = New refcounted<synfp>;
    if (!s->init (*sp, sin.sin_addr, ntohs (sin.sin_port)))
      continue;
    pfv.push_back (s);
    fdcb (s->fd, selread, wrap (this, &synfp_collect::input, pfv.size () - 1));
  }

  return !pfv.empty ();
}

bool
synfp_collect::init (const vec<sockaddr_in> &av)
{
  pfv.clear ();

  in_addr inaddr_any;
  inaddr_any.s_addr = htonl (INADDR_ANY);
  vec<str> ifnames;
  synfp::ifnames (&ifnames, inaddr_any);
  if (ifnames.empty ())
    ifnames.push_back (NULL);

  for (str *sp = ifnames.base (); sp < ifnames.lim (); sp++) {
    if (*sp)
      warn << "listening for SYN fingerprints on " << *sp << "\n";
    else
      warn << "listening for SYN fingerprints on default pcap interface\n";
    ref<synfp> s = New refcounted<synfp>;
    if (!s->init (*sp, av))
      continue;
    pfv.push_back (s);
    fdcb (s->fd, selread, wrap (this, &synfp_collect::input, pfv.size () - 1));
  }

  return !pfv.empty ();
}

void
synfp_collect::input (int i)
{
  for (int j = 0; j < 5; j++) {
    str fp;
    sockaddr_in sin;
    int res = pfv[i]->getfp (&fp, &sin);
    if (res < 0)
      return;
    else if (!res)
      continue;

#if SYNFP_DEBUG
    warn ("synfp-input %s:%d %s\n", inet_ntoa (sin.sin_addr),
        ntohs (sin.sin_port), fp.cstr ());
#endif

    bool found = false;
    for (cbentry_t *cbe = cbs.tab[sin], *ncbe; cbe; cbe = ncbe) {
      ncbe = cbs.tab.nextkeq (cbe);
      found = true;
      ::cbs cb = cbe->val.cb;
      cbs.dealloc (cbe);
      (*cb) (fp);
    }

    fps.insert (sin, fp);
    while (fps.size > bufsize) {
      fpentry_t *fpe = fps.lru.first;
#if SYNFP_DEBUG
      warn ("synfp-delete %s:%d %s (#%d)\n", inet_ntoa (fpe->sin.sin_addr),
          ntohs (fpe->sin.sin_port), fpe->val.cstr (), fps.size);
#endif
      fps.dealloc (fpe);
    }
    
    /* Check for readability, because (at least on OpenBSD) pcap can
     * wait longer than than the to_ms arg of pcap_open_live. */
    static timeval ztv;
    FD_SET (pfv[i]->fd, fds);
    res = select (pfv[i]->fd + 1, fds, NULL, NULL, &ztv);
    FD_CLR (pfv[i]->fd, fds);
    if (res < 1)
      break;
  }
}

void
synfp_collect::service ()
{
  timespec old = tsnow - delay;

  cbentry_t *cbe;
  while ((cbe = cbs.lru.first) && cbe->val.ts < old) {
    (*cbe->val.cb) (NULL);
    cbs.dealloc (cbe);
  }

  if (cbe && !tmo)
    tmo = delaycb (delay.tv_sec, delay.tv_nsec,
               wrap (this, &synfp_collect::timeout));
}

void
synfp_collect::timeout ()
{
  tmo = NULL;
  service ();
}

void
synfp_collect::lookup (const sockaddr_in &sin, ::cbs cb)
{
  if (fpentry_t *fpe = fps.tab[sin]) {
#if SYNFP_DEBUG
    warn ("synfp-lookup %s:%d %s\n", inet_ntoa (sin.sin_addr),
        ntohs (sin.sin_port), fpe->val.cstr ());
#endif
    (*cb) (fpe->val);
    //fps.dealloc (fpe);
  }
  else {
#if SYNFP_DEBUG
    warn ("synfp-lookup %s:%d queued\n", inet_ntoa (sin.sin_addr),
        ntohs (sin.sin_port));
#endif
    cbs.insert (sin, cb);
    service ();
  }
}

str
synfp_collect::lookup (const sockaddr_in &sin)
{
  if (fpentry_t *fpe = fps.tab[sin])
    return fpe->val;
  return NULL;
}


static void
test (synfp *s)
{
  int i = 5;
  do {
    str fp;
    sockaddr_in sin;
    sockaddr_in dsin;
    
    int res = s->getfp (&fp, &sin, &dsin);

    if (res == 1) {
      const char *os = synos_guess (fp);
      warnx ("%s:%d", inet_ntoa (sin.sin_addr), ntohs (sin.sin_port));
      warnx (" %s:%d %s%s%s\n",
           inet_ntoa (dsin.sin_addr), ntohs (dsin.sin_port),
           fp.cstr (), os ? " " : "", os ? os : "");
    }
    else if (res == -1)
      return;

  } while (--i);
}

static void synfp_usage () __attribute__ ((noreturn));
static void
synfp_usage ()
{
  warnx << "usage: " << progname << " --synfp"
      << " [port [ip-addr [if-name ...]]]\n";
  exit (1);
}

void
synfp_test (int argc, char **argv)
{
  argc++;
  argv--;

  int port = 0;
  in_addr addr;
  vec<str> ifnames;

  if (argc >= 2 && !convertint (argv[1], &port))
    synfp_usage ();
  addr.s_addr = htonl (INADDR_ANY);
  if (argc >= 3)
    if (inet_aton (argv[2], &addr) < 1)
      synfp_usage ();
  for (int i = 3; i < argc; i++)
    ifnames.push_back (argv[i]);
    
  if (ifnames.empty ())
    if (!synfp::ifnames (&ifnames, addr))
      exit (1);
  if (ifnames.empty ())
    ifnames.push_back (NULL);

  for (str *sp = ifnames.base (); sp < ifnames.lim (); sp++) {
    warn << "listening on " << *sp << "\n";
    synfp *s = New synfp;
    if (!s->init (*sp, addr, port))
      exit (1);
    fdcb (s->fd, selread, wrap (test, s));
  }
}

#endif /* USE_SYNFP */

Generated by  Doxygen 1.6.0   Back to index