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

tcpspy.c

/*
 * tcpspy.c - TCP/IP connection monitor.
 *
 * This file is part of tcpspy, a TCP/IP connection monitor.
 *
 * Copyright (c) 2000, 2001, 2002 Tim J. Robbins. 
 * 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. 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 ``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: tcpspy.c,v 1.39.1.3 2001/07/02 11:55:36 tim Stab $
 */

#include <arpa/inet.h>
#include <assert.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <grp.h>
#include <limits.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SYSLOG_NAMES
#include <syslog.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#include "log.h"
#include "rcsid.h"
#include "rule.h"

RCSID("$Id: tcpspy.c,v 1.39.1.3 2001/07/02 11:55:36 tim Stab $");

/*
 * Defaults for compile-time settings. Descriptions of these are in
 * the Makefile.
 */
#ifndef CONNTABLE_BUCKETS
#define CONNTABLE_BUCKETS 5003
#endif
#ifndef FACILITY
#define FACILITY LOG_USER
#endif

int gotrule = 0;

static int stopped = 0, showprocs = 0;
static int facility = FACILITY;

/*
 * This structure holds everything we need to know about a connection. We
 * use unsigned long instead of (for example) uid_t, ino_t to make hashing
 * easier.
 */
typedef struct conn {
      unsigned long lcl;
      unsigned long lclp;
      unsigned long rmt;
      unsigned long rmtp;
      unsigned long uid;
      unsigned long ino;

      char exe[PATH_MAX];
      
      struct conn *next;
} conn_t;

typedef struct conntable {
      conn_t *buckets[CONNTABLE_BUCKETS];
} conntable_t;

static int ct_init (conntable_t *ct);
static int ct_hash (conn_t *c);
static int ct_add (conntable_t *ct, conn_t *c);
static int ct_find (conntable_t *ct, conn_t *c);
static int ct_read (conntable_t *ct);
static int ct_free (conntable_t *ct);
static void huntinode (ino_t i, char *buf, size_t bufsize);
static void logconn (conn_t *c, const char *action);
static void compare (conntable_t *old, conntable_t *new);
static void stopsig (int signal);

/*
 * stopsig ()
 *
 * Stop the program when certain signals are received. 
 */
static void stopsig (int signo)
{
      logmsg ("caught signal %d, shutting down", signo);
      stopped = 1;
}

/*
 * ct_init ()
 *
 * Initialise a connection table (hashtable).
 */
static int ct_init (conntable_t *ct)
{
      int i;
      
      assert (ct != NULL);

      for (i = 0; i < CONNTABLE_BUCKETS; i++)
            ct->buckets[i] = NULL;

      return 1;
}

/*
 * ct_hash ()
 *
 * Simple hash function for connections.
 */
static int ct_hash (conn_t *c)
{
      unsigned long h;

      assert (c != NULL);

      h = c->lcl ^ c->lclp ^ c->rmt ^ c->rmtp ^ c->uid ^ c->ino;

      return h % CONNTABLE_BUCKETS;
}

/*
 * ct_add ()
 *
 * Add a connection to the connection table.
 */
static int ct_add (conntable_t *ct, conn_t *c)
{
      conn_t *old, *newc;
      int bi;
      
      assert (ct != NULL);
      assert (c != NULL);

      newc = (conn_t *) malloc (sizeof (conn_t));
      if (newc == NULL) panic ("memory exhausted");   

      memcpy (newc, c, sizeof (conn_t));

      bi = ct_hash (c);
      old = ct->buckets[bi];
      ct->buckets[bi] = newc;
      ct->buckets[bi]->next = old;

      return 1;
}

/*
 * ct_find ()
 * 
 * Find a connection in a table, return nonzero if found, zero otherwise.
 */
static int ct_find (conntable_t *ct, conn_t *c)
{
      conn_t *bucket;
      
      assert (ct != NULL);
      assert (c != NULL);

      bucket = ct->buckets[ct_hash (c)];
      while (bucket != NULL) {
            if ((c->lcl == bucket->lcl) && (c->lclp == bucket->lclp) && 
                  (c->rmt == bucket->rmt) && (c->rmtp == bucket->rmtp) &&
                  (c->uid == bucket->uid) && (c->ino == bucket->ino)) {
                  return 1;
            }
            bucket = bucket->next;
      }

      return 0;
}

/*
 * ct_read ()
 * 
 * Read /proc/net/tcp and add all connections to the table if connections
 * of that type are being watched.
 */
static int ct_read (conntable_t *ct)
{
      static FILE *fp = NULL;
      char buf[1024];
      conn_t c;
      
      assert (ct != NULL);

      if (fp == NULL) {
            fp = fopen ("/proc/net/tcp", "r");
            if (fp == NULL) panic ("/proc/net/tcp: %s", strerror (errno));
      }
      rewind (fp);

      if (fgets (buf, sizeof (buf), fp) == NULL)
            panic ("/proc/net/tcp: missing header");
      
      while (fgets (buf, sizeof (buf), fp) != NULL) {
            unsigned long st;

            if (sscanf (buf, "%*d: %lx:%lx %lx:%lx %lx %*x:%*x %*x:%*x %*x %lu %*d %lu", &c.lcl, &c.lclp, &c.rmt, &c.rmtp, &st, &c.uid, &c.ino) != 7) {
                  logmsg ("/proc/net/tcp: warning: incomplete line");
                  continue;
            }
            if ((c.ino == 0) || (st != TCP_ESTABLISHED)) continue;
            if (showprocs != 0)
                  huntinode ((ino_t) c.ino, c.exe, sizeof (c.exe));

            if (ct_add (ct, &c) == 0)
                  return 0;
      }

      return 1;
}

/*
 * ct_free ()
 *
 * Free a connection table.
 */
static int ct_free (conntable_t *ct)
{
      int i;

      assert (ct != NULL);

      for (i = 0; i < CONNTABLE_BUCKETS; i++) {
            conn_t *c0, *c1;

            c0 = ct->buckets[i];
            while (c0 != NULL) {
                  c1 = c0->next;
                  free (c0);
                  c0 = c1;
            }
            ct->buckets[i] = NULL;
      }

      return 1;
}

/*
 * huntinode ()
 *
 * Find names processes using an inode and put them in a buffer.
 */
static void huntinode (ino_t i, char *buf, size_t bufsize)
{
      DIR *procdir;
      struct dirent *procent;
      
      assert (buf != NULL);
      *buf = '\0';

      if ((procdir = opendir ("/proc")) == NULL)
            panic ("/proc: %s", strerror (errno));
      while ((procent = readdir (procdir)) != NULL) {
            char fdbuf[PATH_MAX];
            DIR *fddir;
            struct dirent *fdent;

            /*
             * No test needed for "." and ".." since they don't begin
             * with digits.
             */
            if (! isdigit (*procent->d_name))
                  continue;
            
            snprintf (fdbuf, sizeof (fdbuf), "/proc/%s/fd", 
                        procent->d_name);
            
            /*
             * We're don't always run as root, we may get EPERM here,
             * ignore it.
             */
            if ((fddir = opendir (fdbuf)) == NULL)
                  continue;

            while ((fdent = readdir (fddir)) != NULL) {
                  int len;
                  char lnkbuf[PATH_MAX], lnktgt[PATH_MAX];
                  char exebuf[PATH_MAX], exetgt[PATH_MAX];
                  ino_t this_ino;
                  
                  if (! isdigit (*fdent->d_name))
                        continue;
                  snprintf (lnkbuf, sizeof (lnkbuf), "%s/%s", fdbuf, 
                              fdent->d_name);
                  len = readlink (lnkbuf, lnktgt, sizeof (lnktgt) - 1);
                  if (len < 0)
                        continue;
                  lnktgt[len] = '\0';
                  if (sscanf (lnktgt, "socket:[%lu]", &this_ino) != 1)
                        continue;
                  if (this_ino != i)
                        continue;

                  snprintf (exebuf, sizeof (exebuf), "/proc/%s/exe", 
                              procent->d_name);
                  len = readlink (exebuf, exetgt, sizeof (exetgt) - 1);
                  if (len < 0)
                        continue;
                  exetgt[len] = '\0';

                  strncpy (buf, exetgt, bufsize);
                  buf[bufsize - 1] = '\0';
            }

            closedir (fddir);
      }
      closedir (procdir);
}

/*
 * logconn ()
 *
 * Log a connection/disconnection.
 */
static void logconn (conn_t *c, const char *action)
{
      struct in_addr in;
      char laddr[16], raddr[16]; /* assume IPv4 nnn.nnn.nnn.nnn\0 */
      char *n;
      struct passwd *pw;
      char uidbuf[32];
      char *user;
      struct servent *se;
      char lport[64], rport[64];

      assert (c != NULL);
      assert (action != NULL);

      if ((gotrule != 0) && (rule_eval (c->uid, c->lcl, c->lclp, c->rmt, c->rmtp, (showprocs != 0) ? c->exe : NULL) == 0))
            return;

      in.s_addr = c->lcl;
      n = inet_ntoa (in);
      strncpy (laddr, n, sizeof (laddr));
      laddr[sizeof (laddr) - 1] = '\0';
      in.s_addr = c->rmt;
      n = inet_ntoa (in);
      strncpy (raddr, n, sizeof (raddr));
      raddr[sizeof (raddr) - 1] = '\0';

      snprintf (lport, sizeof (lport), "%lu", c->lclp);
      snprintf (rport, sizeof (rport), "%lu", c->rmtp);

      if ((se = getservbyport (htons (c->lclp), "tcp")) != NULL) {
            strncpy (lport, se->s_name, sizeof (lport));
            lport[sizeof (lport) - 1] = '\0';
      }
      if ((se = getservbyport (htons (c->rmtp), "tcp")) != NULL) {
            strncpy (rport, se->s_name, sizeof (rport));
            rport[sizeof (rport) - 1] = '\0';
      }

      if ((pw = getpwuid ((uid_t) c->uid)) == NULL) {
            snprintf (uidbuf, sizeof (uidbuf),
                        "(uid %u)", (unsigned int)(uid_t)c->uid);
            user = uidbuf;
      } else {
            user = pw->pw_name;
      }

      if (showprocs != 0)
            logmsg ("%s: proc %.30s, user %s, local %s:%s, remote %s:%s",
                  action, *c->exe != '\0' ? c->exe : "(unknown)", 
                  user, laddr, lport, raddr, rport);
      else
            logmsg ("%s: user %s, local %s:%s, remote %s:%s",
                        action, user, laddr, lport, raddr, rport);
}

/*
 * compare ()
 *
 * Compare the `old' and `new' tables, logging any differences.
 */
static void compare (conntable_t *old, conntable_t *new)
{
      int i;
      
      assert (old != NULL);
      assert (new != NULL);
      
      for (i = 0; i < CONNTABLE_BUCKETS; i++) {
            conn_t *bucket;

            bucket = old->buckets[i];
            while (bucket != NULL) {
                  if (ct_find (new, bucket) == 0)
                        logconn (bucket, "disconnect");     
                  bucket = bucket->next;
            }
      }

      for (i = 0; i < CONNTABLE_BUCKETS; i++) {
            conn_t *bucket;

            bucket = new->buckets[i];
            while (bucket != NULL) {
                  if (ct_find (old, bucket) == 0)
                        logconn (bucket, "connect");
                  bucket = bucket->next;
            }
      }
}

/*
 * getfacility ()
 *
 * Convert a facility name to a integer facility value for syslog().
 */
int getfacility (const char *s)
{
      int i;

      assert (s != NULL);

      for (i = 0; i < LOG_NFACILITIES - 2; i++) {
            assert (facilitynames[i].c_name != NULL);
            if (strcmp (facilitynames[i].c_name, s) == 0)
                  return facilitynames[i].c_val;
      }

      return -1;
}

static void usage (void)
{
      fprintf (stderr, "usage: tcpspy [-dp] [-e rule]... [-f rulefile] [-I interval] "
                  "[-U user]\n\t[-G group] [-F facility] [-I interval]\n");
      exit (EXIT_FAILURE);
}

int firstrule = 1;

int main (int argc, char *argv[])
{
      conntable_t old, new;
      unsigned long interval = 1000;
      int ch;
      uid_t dropuser = -1;
      gid_t dropgroup = -1, defgroup = -1;
      int debug = 0;

      log_set_syslog ();

      /*
       * Parse our arguments.
       */
      opterr = 0;
      while ((ch = getopt (argc, argv, "dpe:I:U:G:u:w:i:f:F:")) != -1) {
            switch (ch) {
                  case 'd':
                        debug = 1;
                        log_set_stdout ();
                        break;
                  case 'p':
                        showprocs = 1;
                        break;
                  case 'e':
                        rule_parse (optarg);
                        gotrule = 1;
                        if (firstrule == 0) 
                              /*
                               * Join multiple rules together with
                               * logical OR's.
                               */
                              rule_gen_or ();
                        firstrule = 0;
                        /* Hide rule from `ps' */
                        memset (optarg, '\0', strlen (optarg));
                        break;
                  case 'f':
                  {
                        FILE *fp;

                        if ((fp = fopen (optarg, "r")) == NULL) {
                              fprintf (stderr, "tcpspy: %s: %s\n", optarg, strerror (errno));
                              exit (EXIT_FAILURE);
                        }

                        rule_parse_file (fp);

                        fclose (fp);
                  }
                  break;
                  case 'I':
                        interval = atoi (optarg);
                        if (interval == 0) {
                              fprintf (stderr, "tcpspy: bad interval\n");
                              exit (EXIT_FAILURE);
                        }
                        break;
                  case 'U':
                        {
                        struct passwd *pw;
                        
                        if (isdigit (*optarg)) {
                              dropuser = atoi (optarg);
                              pw = getpwuid (atoi (optarg));
                        } else {
                              if ((pw = getpwnam (optarg)) == NULL) {
                                    fprintf (stderr, "tcpspy: user `%s' unknown\n", optarg);
                                    exit (EXIT_FAILURE);
                              }
                              dropuser = pw->pw_uid;
                        }
                        
                        /*
                         * Use the gid from the password file entry if
                         * possible, as a default.
                         */
                        if (pw != NULL)
                              defgroup = pw->pw_gid;
                        else
                              defgroup = (gid_t) -1;
                        
                        }
                        break;
                  case 'G':
                        if (isdigit (*optarg))
                              dropgroup = atoi (optarg);
                        else {
                              struct group *gr;

                              if ((gr = getgrnam (optarg)) == NULL) {
                                    fprintf (stderr, "tcpspy: group `%s' unknown\n", optarg);
                                    exit (EXIT_FAILURE);
                              }
                              dropgroup = gr->gr_gid;
                        }     
                        break;
                  case 'F':
                        if ((facility = getfacility (optarg)) < 0) {
                              fprintf (stderr, "tcpspy: bad facility name `%s'\n", optarg);
                              exit (EXIT_FAILURE);
                        }
                        break;
                  case 'u': case 'w': case 'i':
                        fprintf (stderr, "tcpspy: -%c option is obsolete\n", ch);
                        /* fall through to usage message */
                  default:
                        usage();
            }
      }

      argc -= optind;
      argv += optind;

      /*
       * Become an unprivileged user for safety purposes if requested.
       */
      if ((dropgroup == (uid_t) -1) && (defgroup != (uid_t) -1))
            dropgroup = defgroup;
      if (dropgroup != (gid_t) -1) {
            if (setgid (dropgroup) < 0) {
                  fprintf (stderr, "tcpspy: setgid: %s\n", strerror (errno));
                  exit (EXIT_FAILURE);
            }
      }
      if (dropuser != (uid_t) -1) {
            if (setuid (dropuser) < 0) {
                  fprintf (stderr, "tcpspy: setuid: %s\n", strerror (errno));
                  exit (EXIT_FAILURE);
            }
      }

      /*
       * Become a daemon by double-forking and detaching completely from
       * the terminal.
       */

      if (debug == 0) {
            pid_t p;

            /* 1st fork */
            p = fork();
            if (p < 0) {
                  fprintf (stderr, "tcpspy: fork: %s\n",
                              strerror (errno));
                  exit (EXIT_FAILURE);
            } else if (p != 0)
                  exit (0);

            /* 2nd fork */
            p = fork();
            if (p < 0) {
                  fprintf (stderr, "tcpspy: fork: %s\n",
                              strerror (errno));
                  exit (EXIT_FAILURE);
            } else if (p != 0) {
                  fprintf (stderr, "tcpspy 1.7d started (pid %d)\n", 
                              (int) p);
                  exit (EXIT_SUCCESS);
            }

            ioctl (STDIN_FILENO, TIOCNOTTY, NULL);
            close (STDIN_FILENO); 
            close (STDOUT_FILENO); 
            close (STDERR_FILENO);
            setpgid (0, 0);
            chdir ("/");
      } else
            fprintf (stderr, "tcpspy 1.7d started (debug)\n");

      signal (SIGTERM, stopsig);
      if (debug != 0) signal (SIGINT, stopsig);

      /*
       * Initialisation's done, start watching for connections.
       */

      openlog ("tcpspy", LOG_PID, facility);
      if (debug == 0)
            /*
             * .. for the benefit of those reading the logs. Useless to
             * people running it in debug mode.
             */
            logmsg ("tcpspy started, monitoring connections");

      if (ct_init (&old) == 0) panic ("ct_init failed");
      if (ct_read (&old) == 0) panic ("ct_read failed");
      
      while (stopped == 0) {
            struct timeval tv1, tv2;
            static double elapsed = 0.0;
            static int slow_warn = 0;

            gettimeofday (&tv1, NULL);

            if (ct_init (&new) == 0) panic ("ct_init failed");
            if (ct_read (&new) == 0) panic ("ct_read failed");
            compare (&old, &new);
            if (ct_free (&old) == 0) panic ("ct_free failed");
            memcpy (&old, &new, sizeof (conntable_t));

            gettimeofday (&tv2, NULL);

            /*
             * If the time taken to poll the currently open connections is longer than
             * the time between checks, emit a warning message.
             */
            elapsed += (double) ((tv2.tv_sec - tv1.tv_sec) * 1000000 + (tv2.tv_usec - tv1.tv_usec));
            elapsed /= 2;
            if ((elapsed > (double)(interval * 1000)) && (slow_warn == 0)) {
                  slow_warn = 1;
                  logmsg ("warning: running slowly, suggest increasing interval with -I option");
            }

            usleep (interval * 1000);
      }

      if (ct_free (&old) == 0) panic ("ct_free failed");
      closelog ();

      return EXIT_SUCCESS;
}

Generated by  Doxygen 1.6.0   Back to index