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

lanmap.c

/* ex: set tabstop=4 noet: */
/*

Copyright (C) 2005-2006  Ryan Flynn (pizza@parseerror.com)

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; version 2
of the License.

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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/
/* NOTE: this guy's suppsedly compatible with WIN32, so be mindful of what you change */
/* NOTE: Doxygen-style comments */
/* TODO: consolidate reporting logic into one big ugly function instead of sprinkled around...? */
/* FIXME: I'm sure all these bitwise ops are done non-portably */
/* TODO: we should chroot() ourslves for security reasons, running in promiscuous mode requires root... HOWEVER,
 * this hasn't been done because it breaks our shellscripts... we'll need to create a real env we can chroot to... */

#include "lanmap.h"
#include <assert.h> /* assert() */
#include <ctype.h> /* isprint() */
#include <stdio.h>
#include <stdlib.h> /* exit() */
#include <stdarg.h> /* va_* */
#include <string.h> /* memcpy(), memset() */
#include <time.h> /* time() */
#include <signal.h> /* sigprocmask() */
#include <limits.h> /* CHAR_BIT */
#ifndef WIN32
      #include <stdint.h> /* uint32_t and friends */
#endif
#ifdef WIN32
      #include <winsock2.h> /* ntohl() */
      #include <windows.h> /* HANDLE */
#else
      #include <netinet/in.h> /* ntohl() */
#endif
#ifdef WIN32
      #include "getopt.h"
#else
      #include <getopt.h> /* getopt(), optopt, optarg, etc */
#endif
#ifndef WIN32
      #include <unistd.h> /* chroot() */
#endif
#include <errno.h> /* errno */

#include <pcap.h> /* libpcap */
#ifdef WIN32
      #include "Win32-Extensions.h"
#endif

#include "protocols.h"
#include "os_classify.h" /* os_tree_init */
#include "mac_vendor.h"
#include "facts.h"
#include "debug.h"
#include "misc.h"

/**
 * application #defines
 */
#define APPNAME         "lanmap"
#define APPBIN          "lanmap"
#define VERSION         "0.2"
#define AUTHOR          "pizza"
#define EMAIL           "pizza@parseerror.com"
#define WWW             "http://parseerror.com/lanmap/"

#ifndef PACKET_BUFLEN
      /**
       * longest single packet. should be plenty for non-research use...
       */
      #define PACKET_BUFLEN                           65536
#endif

#undef DUMP_TO_STDOUT
#define DUMP_PAYLOAD                                  50


/* * * exported * * */
time_t Dump_Freq = 60; /* how often reports are generated */
int unsigned Iface_Cnt = 0; /* number of interfaces on which we're listening */
int Verbose = 1; /* by default generate some output, 0 == Silence! */
struct ip Ip_Listening[IFACE_MAX];
bpf_u_int32 Dev_Mask[IFACE_MAX] = { 0 };
bpf_u_int32 Dev_Net[IFACE_MAX] = { 0 };

char Image_Type[8] = "png"; /* default img type */
const char *Valid_Image_Types[] = { "svg", "png", "gif" };
char Run_Program[PATH_MAX] = "twopi";
char Output_Dir[PATH_MAX] = "./";


/* * * local * * */
static char Device_Str[IFACE_MAX][IFACE_BUFLEN];
static char *Filter_Str = NULL;
static int Dump = 0;
int Dump_Payload_Bytes = 0; /* exported for protocols.c */
/* our pcap handle, so we can access it in signal_handler... */
static pcap_t *Pcap[IFACE_MAX] = { NULL };
static int Shutdown = 0;


static void on_shutdown(void);
static void sig_handler(int);
static void device_list_and_exit(void);
static void device_find_or_exit(const char *);

/**
 * 
 */
static void print_help(void)
{
            /*0--------1---------2----------3----------4----------5----------6---------7---------8*/
      printf("Usage: %s [options]\n", APPBIN);
      printf("%s", "Options:\n");
      printf("%s", " -v ...................... verbose mode, up to 3 levels (-vv, -vvv)\n");
      printf("%s", " -i [?|*wildcard*|iface] . interface to use; 'all' for all\n");
      printf("%s", "                            ?: list all devices and exit\n");
      printf("%s", "                            *3Com*: use the first NIC with \"3Com\" in it \n");
      printf("%s", " -r # .................... generate a report every # seconds. default: 60\n");
      printf("%s", " -D [#|all|raw] .......... debug mode, tons of output. use with caution.\n");
      printf("%s", "                            #: payload bytes to dump (default: 0)\n");
#if 0
      printf("%s", " -q ...................... quiet mode, no output generated\n");
#endif
      printf("%s", " -f str .................. traffic filter; libpcap syntax\n");
      printf("%s", " -T [png|gif|svg] ........ output image format (default: png)\n");
      printf("%s", " -e program .............. program to run to generate graph (default: twopi)\n");
      printf("%s", " -o directory ............ map destination (default ./)\n");
      printf("%s", " -V ...................... program version info\n");
      printf("%s", " -h ...................... this handy help message\n");
#ifdef WIN32
      printf("%s", "Example: lanmap -i *3Com* -r 30 -T png -o /tmp/\n");
#else
      printf("%s", "Example: lanmap -i eth0 -r 30 -T png -o /tmp/\n");
#endif
      printf("More info: <URL:%s>\n", WWW);
}

/**
 * the last stuff we do before the program ends
 */
static void on_shutdown(void)
{
      VV { printf("generating final report...\n"); };
      VVV{ dump_world(); }
      V { printf("done.\n"); }
}

/**
 *
 * @todo use sigprocmask instead...
 */
static void sig_handler(int sig)
{
      /* re-install self... */
#ifndef WIN32
      signal(SIGHUP, sig_handler);
#endif
      signal(SIGINT, sig_handler);
      signal(SIGTERM, sig_handler);

      switch (sig) {
      case SIGINT:
      case SIGTERM:
            Shutdown = 1;
            V { printf("received signal %d, quitting...\n", sig); }
            exit(EXIT_SUCCESS);
            break;
#ifndef WIN32
      case SIGHUP:
            V { printf("received HUP, reloading config files...\n"); }
            /* FIXME: this probably shouldn't be in the signal handler... */
            mac_vend_reload();
            break;
#endif
      default:
            V { printf("received signal %d, ignoring...\n", sig); }
            break;
      }
}

/**
 * list all network devices and exit
 */
static void device_list_and_exit(void)
{
      pcap_if_t *alldevs, *dev;
      int i;
      char errbuf[PCAP_ERRBUF_SIZE];

      if (-1 == pcap_findalldevs(&alldevs, errbuf)){
            fprintf(stderr,"Error finding devices: %s\n", errbuf);
            exit(EXIT_FAILURE);
      }
      
      printf("Devices\n------------------------------------------------------------------------------\n");
      for (i = 0, dev = alldevs; dev != NULL; dev = dev->next, i++) {
            printf("%s (%s)\n", dev->name,
                  (NULL == dev->description ? "No description available" : dev->description));
      }
      
      if (0 == i) {
#ifdef WIN32
            const char err[] = "Make sure WinPcap is installed.";
#else
            const char err[] = "Are you root?";
#endif
            fprintf(stderr, "No interfaces found! %s\n", err);
            exit(EXIT_FAILURE);
      }

      pcap_freealldevs(alldevs);
      exit(EXIT_SUCCESS);
}

/**
 * try to find a device whose name or description matches 'search'
 * @param search
 * @note on win32 pcap device names are huge incomprehensible names that no one will ever type in. we
 * need a wildcard
 */
static void device_find_or_exit(const char *wildcard)
{
      pcap_if_t *alldevs, *dev;
      int match = 0;
      char errbuf[PCAP_ERRBUF_SIZE];
      char search[512]; /* FIXME: could magic size here be a problem? */

#ifdef _DEBUG
      assert(NULL != search);
#endif

      strcpy(search, wildcard);

      if ('*' == search[0])
            memcpy(search, search + 1, strlen(search) + 1); /* copy \0 */

      if ('*' == search[MAX(strlen(search), 1) - 1])
            search[MAX(strlen(search), 1) - 1] = '\0';

      if (-1 == pcap_findalldevs(&alldevs, errbuf)){
            fprintf(stderr,"Error finding devices: %s\n", errbuf);
            exit(EXIT_FAILURE);
      }
      
      for (dev = alldevs; dev != NULL; dev = dev->next) {
            if (NULL != strstr(dev->name, search) || NULL != strstr(dev->description, search)) {
                  V { printf("device wildcard '%s' matched device %s (%s)...\n",
                        wildcard, dev->name, dev->description); }
                  if (Iface_Cnt >= sizeof Device_Str / sizeof Device_Str[0]) {
                        ERRF("couldn't find an open slot (maximum slots: %u)", sizeof Device_Str / sizeof Device_Str[0]);
                        exit(EXIT_FAILURE);
                  }
                  strlcpy(Device_Str[Iface_Cnt++], dev->name, IFACE_BUFLEN);
                  match = 1;
                  break;
            }
      }
      
      if (0 == match) {
            fprintf(stderr, "No device matches wildcard '%s'(%s). Quitting.\n",
                  wildcard, search);
            exit(EXIT_FAILURE);
      }

      pcap_freealldevs(alldevs);
}

/**
 * parse cmdline options
 */
void parse_cmdline(int argc, char *argv[])
{
      int opt;
      const char opts[] = "Vvqi:r:df:D:T:e:o:h";

      while (-1 != (opt = getopt(argc, argv, opts))) {
            switch (opt) {
            case 'V': /* version */
                  printf("%s %s by %s (%s)\n", APPNAME, VERSION, AUTHOR, EMAIL);
#ifdef _DEBUG
                  printf("Compiled %s %s\n" ,__DATE__, __TIME__);
#endif
                  printf("More info <URL:%s>\n", WWW);
                  exit(EXIT_SUCCESS);
                  break;
            case 'v': /* verbose */
                  if (0 == Verbose) {
                        printf("Warning: 'quiet' and 'verbose' both being used...\n");
                  }
                  if (Verbose < VERBOSE_MAX)
                        Verbose++;
                  break;
            case 'i': /* interface */
                  if (NULL != optarg && '\0' != optarg[0]) {
                        if ('?' == optarg[0]) {
                              device_list_and_exit();
                        } else if ('*' == optarg[0]) {
                              device_find_or_exit(optarg);
                        } else {
                              strlcpy(Device_Str[Iface_Cnt++], optarg, IFACE_BUFLEN);
                        }
                  }
                  break;
            case 'r': /* report */
                  if (NULL != optarg) {
                        Dump_Freq = strtoul(optarg, NULL, 10);
                        if (errno) {
                              perror("strtoul");
                              ERRF("invalid report frequency '%s', positive integer required. quitting.\n",
                                    optarg);
                              exit(EXIT_FAILURE);
                        }
                  }
                  break;
            case 'q': /* quiet mode */
                  if (Verbose > 1) { /* detect 'q' and 'v' used together */
                        printf("warning: 'quiet' and 'verbose' both being used...\n");
                  }
                  Verbose = 0;
                  break;
            case 'D': /* debug [n] payload bytes */
                  Dump = 1;
                  Verbose = VERBOSE_MAX; /* */
                  if (NULL != optarg) {
                        if (0 == strcmp("all", optarg)) {
                              Dump_Payload_Bytes = -1;
                        } else if (0 == strcmp("raw", optarg)) {
                              Dump_Payload_Bytes = -2;
                        } else {
                              Dump_Payload_Bytes = (int)strtol(optarg, NULL, 10);
                              if (errno) {
                                    ERRF("invalid argument '%s', integer required. quitting....\n",
                                          optarg);
                                    exit(EXIT_FAILURE);
                              }
                        }
                  }
                  break;
            case 'd': /* daemon */
                  /* TODO */
                  break;
            case 'f': /* filter */
                  Filter_Str = optarg;
                  break;
            case 'T': /* image Type */
            {
                  int unsigned i, n = sizeof Valid_Image_Types / sizeof Valid_Image_Types[0];
                  assert(optarg);
                  /* ensure img type is valid and supported */
                  for (i = 0; i < n; i++)
                        if (0 == strcmp(optarg, Valid_Image_Types[i]))
                              break;
                  if (i == n) {
                        fprintf(stderr, "'T' argument is invalid image type, see -h\n");
                        exit(EXIT_FAILURE);
                  }
                  strlcpy(Image_Type, optarg, sizeof Image_Type);
            }
                  break;
            case 'e': /* what cmd to execute */
                  assert(optarg);
                  if (strlcpy(Run_Program, optarg, sizeof Run_Program) != strlen(optarg)) {
                        /* do not allow this argument to be truncated */
                        fprintf(stderr, "'e' arg is too long (max: %lu), quitting.\n", (long unsigned)(sizeof Run_Program - 1));
                        exit(EXIT_FAILURE);
                  }
                  break;
            case 'o': /* output dest */
            {
                  size_t len;
                  assert(optarg);
                  len = strlcpy(Output_Dir, optarg, sizeof Output_Dir);
                  if (len != strlen(optarg)) {
                        /* do not allow this argument to be truncated */
                        fprintf(stderr, "'o' arg is too long (max: %lu), quitting.\n", (long unsigned)(sizeof Output_Dir - 1));
                        exit(EXIT_FAILURE);
                  } else if (len > 0) {
                        /* ensure / termination */
                        if (Output_Dir[len - 1] != LANMAP_PATH_SEP_CHR) {
                              if (len == sizeof Output_Dir) {
                                    fprintf(stderr, "'o' arg must end in '%c'! quitting.\n", LANMAP_PATH_SEP_CHR);
                                    exit(EXIT_FAILURE);
                              }
                              Output_Dir[len] = LANMAP_PATH_SEP_CHR;
                              Output_Dir[len + 1] = '\0';
                        }
                  }
            }
                  break;
            case 'h': /* help */
                  print_help();
#ifdef _DEBUG
                  printf("Compiled %s %s\n" ,__DATE__, __TIME__);
#endif
                  exit(EXIT_SUCCESS);
                  break;
            default:
                  ERRF("invalid %soption '%c', see -h for options. quitting...\n",
                              (NULL != strchr(opts, optopt) ? "use of " : ""), optopt);
                  exit(EXIT_FAILURE);
                  break;
            }
      }

      VV {
            OUTF("verbosity level %d\n", Verbose - 1);

            if (Dump) {
                  char buf[32];
                  switch (Dump_Payload_Bytes) {
                  case -1: 
                        strlcpy(buf, "entire payload", sizeof buf);
                        break;
                  case -2:
                        strlcpy(buf, "entire payload plus raw", sizeof buf);
                        break;
                  default:
                        snprintf(buf, sizeof buf, "%d bytes", Dump_Payload_Bytes);
                        break;
                  }
                  OUTF("dump on, %s...\n", buf);
            }

            {
                  int unsigned i;
                  OUTF("using devices");
                  for (i = 0; i < Iface_Cnt; i++)
                        OUTF(" %s", Device_Str[i]);
                  OUTF("...\n");

            }

            if (NULL != Filter_Str)
                  OUTF("using filter '%s'...\n", Filter_Str);

            OUTF("reporting every %lu seconds...\n", Dump_Freq);
      }
}

/**
 * main loop
 */
static int listen_and_learn(void)
{
      char errbuf[PCAP_ERRBUF_SIZE];
      char *dev;
      struct bpf_program filter;
      int unsigned i;

      /* listen loop */
      do { 
            int promisc = 1;
            
            if (0 == Iface_Cnt) { /* no devs specified, find one */
                  dev = pcap_lookupdev(errbuf);
                  if (NULL == dev) {
                        ERRF("Couldn't find default device: %s\n", errbuf);
                        return -1;
                  }
                  strlcpy(Device_Str[Iface_Cnt++], dev, IFACE_BUFLEN);
                  V { OUTF("using device %s...\n", dev); }
            }

            /* set up each iface */
            for (i = 0; i < Iface_Cnt; i++) {
#ifndef WIN32
            if (0 == geteuid()) {
                  V { printf("opening %s in promiscuous mode...\n", Device_Str[i]); };
            } else {
                  V { printf("you are not root, opening %s in normal mode...\n", Device_Str[i]); }
                  promisc = 0;
            }
#endif

                  Pcap[i] = pcap_open_live(Device_Str[i], PACKET_BUFLEN, promisc, 0, errbuf);
                  if (NULL == Pcap[i]) {
                        ERRF("Can't open device '%s'! %s\n", Device_Str[i], errbuf);
                        return -1;
                  }
            
                  if (-1 == pcap_lookupnet(Device_Str[i], Dev_Net + i, Dev_Mask + i, errbuf)) {
                        ERRF("Can't get netmask for device '%s': %s, continuing...\n",
                              Device_Str[i], errbuf);
                        /* no ip address up?! keep on truckin... */
                  } else {
                        Ip_Listening[i].version = IPV4;
#if __BYTE_ORDER == __LITTLE_ENDIAN
                        Ip_Listening[i].addr.v4[3] = (Dev_Net[i] >> 24) & 0xFF;
                        Ip_Listening[i].addr.v4[2] = (Dev_Net[i] >> 16) & 0xFF;
                        Ip_Listening[i].addr.v4[1] = (Dev_Net[i] >> 8) & 0xFF;
                        Ip_Listening[i].addr.v4[0] = Dev_Net[i] & 0xFF;
#elif __BYTE_ORDER == __BIG_ENDIAN
                        Ip_Listening[i].addr.v4[0] = (Dev_Net[i] >> 24) & 0xFF;
                        Ip_Listening[i].addr.v4[1] = (Dev_Net[i] >> 16) & 0xFF;
                        Ip_Listening[i].addr.v4[2] = (Dev_Net[i] >> 8) & 0xFF;
                        Ip_Listening[i].addr.v4[3] = Dev_Net[i] & 0xFF;
#else
      #error "unexpected or undefined __BYTE_ORDER!"
#endif
                  }
      
                  /* TODO: human-readable output, perhaps? */
                  VV {
                        OUTF("device '%s' net: 0x%08X, mask: 0x%08X\n", Device_Str[i], Dev_Net[i], Dev_Mask[i]);
                  }
            
                  /* set filter if we've been supplied one and we've got an ip addres... */
                  if (NULL != Filter_Str && 0 != Dev_Net[i] && 0 != Dev_Mask[i]) {
                        if (-1 == pcap_compile(Pcap[i], &filter, Filter_Str, 0, Dev_Net[i])) {
                              ERRF("Can't compile filter '%s': %s. quitting\n",
                                    Filter_Str, pcap_geterr(Pcap[i]));
                              return -1;
                        }
                        
                        if (-1 == pcap_setfilter(Pcap[i], &filter)) {
                              ERRF("Can't install filter '%s': %s. quitting\n",
                                    Filter_Str, pcap_geterr(Pcap[i]));
                              return -1;
                        }
                  }
            }

            /* all ifaces set up... */

            /* FIXME: how do i deal with one iface going down and the others staying up? */

            /* network loop */
            {
                  struct pcap_pkthdr *header;
                  const u_char *packet;
                  int unsigned i, sel, cap;
                  struct timeval tv;
#ifdef WIN32
                  HANDLE handles[IFACE_MAX];
#else
                  fd_set rd;
                  int fds[IFACE_MAX];
                  int fdmax = 0;
#endif

                  /* initial setup for select()ing */
                  tv.tv_sec = (long)Dump_Freq;
                  tv.tv_usec = 0;

                  for (i = 0; i < Iface_Cnt; i++) {
#ifdef WIN32
                        handles[i] = pcap_getevent(Pcap[i]);
#else
                        fds[i] = pcap_get_selectable_fd(Pcap[i]);
                        fdmax = MAX(fdmax, fds[i]);
#endif
                  }

                  while (0 == Shutdown) {
#ifdef WIN32
                        sel = WaitForMultipleObjects((DWORD)Iface_Cnt, handles, TRUE, (long)Dump_Freq * 1000);
#else
                        FD_ZERO(&rd);
                        for (i = 0; i < Iface_Cnt; i++)
                              FD_SET(fds[i], &rd);

                        sel = select(fdmax + 1, &rd, NULL, NULL, NULL);
#endif

                        switch (sel) {
#ifdef WIN32
                        case WAIT_FAILED:
                        
#else
                        case -1: /* error */
#endif
                              perror("select");
                              break;

#ifdef WIN32
                        case WAIT_TIMEOUT:
#else
                        case 0: /* timeout */
#endif
                        
                              ERRF("timeout...\n");
                              continue;
                              break;

                        default: /* one or more readable */
                              for (i = 0; i < Iface_Cnt; i++) {
#ifdef WIN32
                                    if (WAIT_OBJECT_0 + i != sel) {
#else
                                    if (!FD_ISSET(fds[i], &rd)) {
                                          
#endif
                                          continue;
                                    }
                                    /* ok, there should be a packet waiting for us... */
                                    cap = pcap_next_ex(Pcap[i], &header, &packet);
                                    switch (cap) {
                                    case -1: /* err */
                                          break;
                                    case 0: /* timeout */
                                          /* nothing, keep going */
                                          break;
                                    case 1: /* ok */
                                    { /* case scope */
                                          protonode_t *node = node_new_parse(packet, header->len, pcap_datalink(Pcap[i]));
                                          if (NULL != node) {
                                                if (-2 == Dump_Payload_Bytes) { /* -2 == DUMP EVERYTHING */
                                                      size_t offset = 0;
                                                      printf("<%u bytes>\n", header->len);
                                                      while (offset < header->len)
                                                            offset = bytes_dump_hex_line(packet, offset, header->len);
                                                }
                                                /* if we get at least one layer beyond the logical we dump */
#if 0 && defined(_DEBUG)
                                                /* dump what we don't understand */
                                                {
                                                      protonode_t *p = node->child;
                                                      while (NULL != p && NULL != p->child)
                                                            p = p->child;
                                                      if (PROT_UNKNOWN == p->prot_id && (NULL == p->parent || PROT_DNS != p->parent->prot_id)) {
                                                            /* DNS parsing is woefully incomplete, don't dump it... */                                                  
                                                            prot_dump(node);
                                                      }
                                                }
#else
                                                if (Dump && NULL != node->child)
                                                      prot_dump(node);
#endif
                                                node_report(node);
                                                node_free(node);
                                          }
                                    } /* case scope */
                                          break;
                                    default:
                                          break;
                                    }
                                    check_dump(); /* after each packet check if it's time to generate a report */
#ifdef WIN32
                                    break; /* WaitForMultipleObjects only returns othe first available, not all */
#endif
                              }
                              break;
                        } /* switch */
                  } /* read loop */

                  VV {
                        printf("devices");
                        for (i = 0; i < Iface_Cnt; i++)
                              printf(" %s", Device_Str[i]);
                        printf(" down...\n");
                  }

            }

      } while (0 == Shutdown); /* keep bringing everything back up... */

      return 0;
}

/**
 *
 */
void test_hexdump(void)
{
      register u_int j;
      size_t offset;
      u_char buf[1024];

      srand((int unsigned)time(NULL));

      while (1) {
            for (j = 0; j < sizeof buf; j++)
                  buf[j] = (unsigned char)(rand() % (2 << CHAR_BIT));
            offset = 0;
            while (offset < sizeof buf)
                  offset = bytes_dump_hex_line(buf, offset, sizeof buf);
      }
}


/**
 * para todos los frijoles
 */
int main(int argc, char *argv[])
{

      /* since most systems need superuser to put interface in promiscuous, chroot for basic safety */
#if THIS_SCREWS_UP_OUR_SHELLSCRIPTS
#ifndef WIN32
      if (0 != chroot(".")) {
            perror("chroot(\".\")");
      }
#endif
#endif

      /* TODO: sigaction instead! */
#ifndef WIN32
      signal(SIGHUP, sig_handler);
#endif
      signal(SIGINT, sig_handler);
      signal(SIGTERM, sig_handler);

#ifdef _DEBUG
      printf("protonode_t is %u bytes\n", sizeof(protonode_t));
#endif

      endian_init(); /* initialize endian test */
      parse_cmdline(argc, argv);

#if 0
      test_hexdump();
#else

      /* initialize our structures */
      if (0 != mac_vend_init()) {
            FATALF(__FILE__, __LINE__, "error loading mac->vendor data");
            exit(EXIT_FAILURE);
      }

      (void)protocol_init();
      (void)reporting_init();
      (void)os_tree_init();
      
      if (0 != atexit(on_shutdown)) {
            ERRF("error registering shutdown function, continuing...\n");
      }

      /* run main loop */
      if (-1 == listen_and_learn()) {
            /* fatal error occurred */
            exit(EXIT_FAILURE);
      }

#endif
      return 0;
}




Generated by  Doxygen 1.6.0   Back to index