
/****************************************************************************/
/*                                                                          */
/*      NNstat -- Internet Statistics Collection Package                    */
/*                                                                          */
/*            Written by: Bob Braden & Annette DeSchon                      */
/*            USC Information Sciences Institute                            */
/*            Marina del Rey, California                                    */
/*                                                                          */
/*      Copyright (c) 1991 University of Southern California.               */
/*      All rights reserved.                                                */
/*                                                                          */
/*      Redistribution and use in source and binary forms are permitted     */
/*      provided that the above copyright notice and this paragraph are     */
/*      duplicated in all such forms and that any documentation,            */
/*      advertising materials, and other materials related to such          */
/*      distribution and use acknowledge that the software was              */
/*      developed by the University of Southern California, Information     */
/*      Sciences Institute.  The name of the University 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 WITHOUT ANY EXPRESS OR        */
/*      IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED      */
/*      WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR          */
/*      PURPOSE.                                                            */
/*                                                                          */
/****************************************************************************/

static char rcsid[]=
  "$Header: statspy.c,v 1.7 93/10/11 18:40:30 mogul Exp $";
 
/*                   statspy.c
 *
 *      This is the main routine for statspy.
 *
 *  Purpose of statspy: 
 *      To collect statistics on Internet packets, in a flexible
 *      but efficient manner.  Basically, parses the nested headers
 *      in a packet (eg local net header, IP header, TCP header...)
 *      and accumulates statistics using the appropriate subset
 *      of a set of rules.
 *            
 *   Method: 
 * 
 *      We divide the process of analyzing a packet into two phases:
 *      (1) parsing the headers into field values, and (2) analyzing/
 *      recording data from these fields.
 *
 *      The parsing code is compiled in, for efficiency (although a
 *      table-driven parsing engine would be possible if it were written
 *      in assembly language).  However, the statistical rules used for
 *      analysis/recording may be loaded dynamically and are applied 
 *      interpretively.
 *
 *      Specifically, the analysis/recording is done by routines which
 *      are packaged into statistical "objects", each of which
 *      has an internal data structure and fixed entry points. Each
 *      object falls into a particular class, and all objects implement
 *      the following generic operations:
 *
 *                Make -- create the object in an initial state
 *                Delete -- destroy the object
 *                Clear --  reset the object to its initial state
 *                Read -- format contents of data structure and return it to
 *                              caller. 
 *                Write -- analyse/record a particular field value.      
 *
 *      Objects generally  fall into two categories: filters and recorders.
 *      For a recorder, the Write operation adds the given data value
 *      into its internal data structure. For a filter, the Write operation
 *      tests the given value to see whether it falls into a specific class
 *      or range, and accordingly chooses one of two sets of Write operations
 *      to be performed. 
 *     
 *      Each of the defined fields is assigned a unique field index number
 *      (FI#), a name string, a type, and a length.  This assignment is fixed
 *      in the parsing code.
 *
 *      As a packet is parsed from outside to inside (corresponding to
 *      successively higher protocol layers), a succession of fields
 *      will be encountered; the particular sequence depends upon the
 *      protocol layers, and corresponds to a particular path from the
 *      root to a leaf of the protocol parse tree.  When the field 
 *      designated by 'FI#' is encountered with value '*value', the parser
 *      stores a pointer to it and then calls:
 *      
 *                  interpret( field_opcs[FIno] )
 *
 *      Here field_opcs[] is an array of pointers to (possibly empty)
 *      list of calls on the Write operations of particular filter and/or
 *      recording objects. A filter will choose one of two sub-lists
 *      of Write operations to be performed.
 *
 *      A particular call is defined by a call structure (SOPC), which
 *      specifies the invoking field(s) and a pointer to an object.
 *      The latter points to the root data structure for the object, 
 *      its SOBJ (Statistical Object Area). The SOBJ in turn contains
 *      a pointer to the transfer vector defining the generic entry points
 *      for the code implementing the object class.
 *
 *      Thus, the result of calling interpret() is a series of calls of
 *      the form:
 *
 *              Write_XX(SOBJ-ptr, &value, length)
 *
 *      where XX is the object class appropriate to the particular object.
 *      The length is bound at attach time.
 *
 */
 
/* CHANGES:
 *   17Nov88  RTB:  -f parm (read saved trace rather than raw NIT data)
 *   18Nov88  RTB: Make select call compatible between OS 3.x and OS 4.y
 *   22Nov88  RTB: Change initdevice call, move it to install() routine.
 *   13Oct89  Merit: Add access control routines Do_restrict(), Check_restrict,
 *                     Show_restrict().
 *   13Oct89  Merit: RT stuff
 *   13Oct89  Merit: Add new parameter: -s scheduling_priority
 *   16Oct89  ISI: Look up default interface
 *      Rel 2.4:
 *   09Nov89  RTB: Call InitFields() to do one-time init of FPT data structure.
 *   29Nov89  NASA: Correct #include file ioctl.h for SUN OS3.x
 *   29Nov89  OSU: Use in_addr structs in restrict.
 *      Rel 3.0:
 *   DEC: Ultrix, little-endian changes
 *   Cisco: Line number for config file error
 *   ISI: 'file' command
 *   ISI:  For testing: always include -f parm, set TOD virtual clock.
 *      Rel 3.01:
 *   ISI: Fix -1 mode [found by John Larson]
 *  $Log:	statspy.c,v $
 * Revision 1.7  93/10/11  18:40:30  mogul
 * debug changes
 * 
 * Revision 1.6  93/09/27  13:10:23  mogul
 * New date.
 * 
 * Revision 1.5  93/09/23  17:15:40  mogul
 * SO_REUSEADDR to ease debugging.
 * 
 * Revision 1.4  93/08/31  22:36:53  mogul
 * changed version number
 * 
 * Revision 1.3  1993/08/05  20:03:56  mogul (DECWRL)
 * Generalize datalink type
 *
 * Revision 1.2  1993/08/02  21:02:42  mogul (DECWRL)
 * OSF, 64-bit long changes
 *
 * Revision 1.2  1993/08/02  21:02:42  mogul (DECWRL)
 * OSF, 64-bit long changes
 *
 * Revision 1.1  1993/07/30  23:36:55  mogul (DECWRL)
 * Initial revision
 *
 * Revision 3.2.1.1  92/12/09  10:03:20  braden
 * Maintenance release 3.2.
 * 
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/errno.h>
#include <sys/signal.h>
#include <sys/resource.h>
#include <netinet/in.h>
#include <netdb.h>
#include <net/if.h>
#include <arpa/inet.h>
#ifdef SUNOS4
#include <sys/sockio.h>
#else
#include <sys/ioctl.h>
#endif
#include "stat.h"
#include "sobj.h"   

#ifdef IBMRTPC
#undef FD_SET
#undef FD_ISSET
#endif
#ifndef sgi
#ifndef ULTRIX
#ifndef SUNOS4
#ifndef	DECOSF
   /* Macros to manipulate select masks */
#define FD_SET(n, p)  (*(p)) |= (1<<(n))
#define FD_ISSET(n, p)  ((*(p)) &  (1<<(n)))
#endif
#endif
#endif
#endif /* sgi */

int lsock = -1, asock = -1 ;  /* Listening, Active TCP file descriptors */
extern int if_fd, errno ;
extern readether();             /*  ETHER read */
char *EtherIface = NULL;        /*  Network interface name */
extern char *IfaceType;	/*  Type of interface */


/* extern Poller(); */   /* timer interrupt routine */
/* extern BrokenPipe(); */

extern int packetno, packetcnt;
char  *version = "R3.3beta Sept 93";
 
struct sockaddr_in fromaddr ;
int    fromlen = sizeof(fromaddr) ;
char   remotehost[256];
int    TCP_port = STATD_PORT;
boolean  HistSw = FALSE;  
time_t   CurrTime, StarTime; 
char  *TraceFile = NULL;  /* External trace history file, if any */
extern u_long line_number;      /*  Config file line number */
 
#define TIMEOUT  120 /* default timeout */
long Timeout = TIMEOUT;
int netclose() ; 

int Xdebug, debug = 0;
boolean isrspy = FALSE; 
FILE *TTYfp = NULL, *ninfp = NULL, *noutfp = NULL;
FILE *fpinit = NULL ;    /* File pointer -- initial config file */
FILE *logfp = stdout;
char rd_filename[256];   /* file name from 'file' command, or nul */
            
#define POLLDEFAULT  60  
char Pollarg[64];
unsigned Interval = 0;
XDR W_XDR, R_XDR ;

#define LEXCALL  yylex(fp)
            
#undef        FD_SETSIZE
#define       FD_SETSIZE      32

#define MAXRESTRICT 10      /* ACCESS CONTROL INFORMATION */
struct restrict {
    struct in_addr addr;    /* address to allow */
    struct in_addr mask;    /* mask for *SIGNIFICANT* bits */
    boolean readwrite;      /* TRUE if read-write access allowed */
} restrictions[MAXRESTRICT];
u_short num_restrictions = 0;


    /*         STATread()
     *
     *  Read input in a non-blocking fashion.  Ie, continue to
     *  service network tap and/or TCP open requests, while wait for
     *  input on specified descriptor.
     */
int STATread(reqfd, buff, blen)
    int reqfd ;
    char *buff ;
    int blen ;
    {
    int rc;
    int asock2;  /* for Sys V */
    
#if  defined(SUNOS4) || defined(ULTRIX) || defined(sgi) || defined(DECOSF)
    fd_set mask, maskbits;
    FD_ZERO (&maskbits);
#else
    int mask, maskbits = 0;
#endif

    if (buff)
        FD_SET(reqfd, &maskbits);
    if (lsock >= 0) 
        FD_SET(lsock, &maskbits);
        
    for (;;) {
        mask =  maskbits ;
        if (asock != -1) 
                FD_SET(asock, &mask);
        if (if_fd >= 0) 
            FD_SET(if_fd, &mask);
                            
        if (select(FD_SETSIZE, &mask, NULL, NULL, NULL) < 0) {
            if (errno == EINTR) continue;
            if (errno == EBADF)
                return(-1);
            perror("statspy - select error");
            exit(1);
        }
        
        if ((if_fd >= 0) && (FD_ISSET(if_fd, &mask)))
                /* Got buffer containing "Ethernet" packets... */
            readether();
            
        if (asock < 0 && FD_ISSET (lsock, &mask)) {
                /* Client is trying to open TCP connection
                 *   and we do not currently have one open.
                 */
            asock = accept(lsock, &fromaddr, &fromlen) ;
            if (asock < 0) {
                if (HistSw)
                    perror("statspy - accept error") ;
                asock = -1;
                continue;              
            } ;
            strcpy(remotehost, 
                inet_ntoa(fromaddr.sin_addr));
#ifdef SYSV
            asock2 = dup(asock);
#endif
            if ((ninfp = fdopen(asock, "r")) == NULL||
#ifdef SYSV
                (noutfp = fdopen(asock2, "w")) == NULL) {
#else
                (noutfp = fdopen(asock, "w")) == NULL) {
#endif
                    if (ninfp) 
                        fclose(ninfp);
                    close(asock);
                    asock = -1;
                }
            else {
                signal(SIGALRM, netclose) ;
                alarm(Timeout) ;
                xdrstdio_create(&W_XDR, noutfp, XDR_ENCODE);
                xdrstdio_create(&R_XDR, ninfp, XDR_DECODE);
            }
            
        } 
        
        else if (reqfd >= 0 && FD_ISSET(reqfd, &mask))  {
            rc = read(reqfd, buff, blen);
            if (debug && rc>0 && asock>=0 && reqfd == asock) {  
                hexf(stdout, "Net Read\n", buff, rc);
            }
            return(rc); 
        }       
   
        else if (asock >= 0 && FD_ISSET(asock, &mask)) {
               /* New input on TCP conn -- must be new request */
            Do_Request(&R_XDR, &W_XDR); 
        }
        
    } /* End of Forever Loop */
}

Usage() {
    fprintf(stderr, 
  "Usage: statspy [-x] [-h] [-i interface] [-p port#] [-n count] \n\
               [-t timeout] [-s prio] [-r testfile] [parmfile]\n") ;
}


netclose() {
    if (debug)
        printf ("netclose() was called\n");

    if (asock != -1) {
        xdr_destroy(&W_XDR);
        fclose(ninfp);
        fclose(noutfp);
    }
    asock = -1 ;
}
       
                
SetPoll(interval, title)
    unsigned interval;
    char *title;
    {
    strcpy(Pollarg, (title&&*title)?title:"*");
    Interval = (interval != -1) ? interval : POLLDEFAULT; 
    if (Interval) {
        alarm(Interval);
        printf("Polling Started -- %d secs\n", Interval);
    }
    else
        printf("Polling Stopped\n");
}
        
void
Poller()
    {   
    Read_SOBJ(0, Pollarg,0);  /*FIX6Jan88*/
    printf("\n");
    fflush(stdout);
    alarm(Interval);
}
    
/*  Substitute for stdio routine _filbuf() to intercept reads
*/
int _fillbuf(fp)
register FILE *fp;
    {
    if ((fp->_flag&_IOREAD)==0||(fp->_flag&(_IOEOF|_IOERR)))
        return(EOF);
        
    if ( fp->_base == NULL&&
#ifdef        ULTRIX
         ((fp->_base = (char *) malloc(BUFSIZ)) == NULL)) {
#else
         ((fp->_base = (unsigned char *) malloc(BUFSIZ)) == NULL)) {
#endif        ULTRIX
        fprintf(stderr, "MEMORY FULL\n");
        exit(1);
    }
    
    fp->_ptr = fp->_base;   
    fp->_cnt = STATread( fileno(fp), fp->_base, BUFSIZ);
    if (fp->_cnt <= 0) {
        if (fp->_cnt < 0)
            fp->_flag |= _IOERR;
        else
            fp->_flag |= _IOEOF;
        fp->_cnt = 0;
        return(EOF);
    }
    return(*fp->_ptr++);
} /* _fillbuf() */
            
char *argscan(argcp, argvp)
    int *argcp;
    register char ***argvp;
    {
    register char *cp;
    
    if (*(cp = 2+**argvp)) 
        return(cp);
    else if  (--*argcp > 0)
        return(*++*argvp);
    Usage();
    exit(1);
}       

hexf(fp,  title, p, len)                
    FILE *fp;
    char *title, *p ;
    register int len ;
    {
    register char *cp = p;
    register int   wd ;
    u_int32  temp ;
    
    fprintf(fp, title);     
    while (len > 0) {
        fprintf(fp, "x%2.2x: ", cp-p);
        for (wd = 0; wd<4; wd++)  {
            bcopy(cp, (char *)&temp, sizeof(u_int32) );
            if (len > 4) {
                fprintf(fp, " x%8.8x", ntohl( temp )) ;
                cp += sizeof(u_int32) ;
                len -= sizeof(u_int32) ;
                }
            else {
                fprintf(fp, " x%*.*x",  2*len, 2*len, ntohl(temp)) ;
                len = 0 ;
                break ;
            }
        }
        printf("\n") ;
    }
}

            
Do_help()
    {
    printf(
"Cmds are: read <obj-spec>  |   read ?  |   clear <obj-spec>  |\n\
          show <field-spec> |  show ?   |  subnet <net> <mask>    subnet ?  |\n\
          attach {<attach-parms>}   |   detach <obj-spec>  |\n\
          enum  {<enum-parms>}      |   enum ?  |\n\
          file \"filename\"\n\
(local cmds:)   quit  |\n\
          poll <time> <obj-spec>  |\n\
          restrict {readwrite/readonly} <addr> <mask>\n");
}

Do_host(cp)
    char *cp;
    {
    printf("Illegal cmd on statspy\n");
}


/* MAIN ROUTINE */

main(argc, argv)
    int argc ;
    char **argv ;
    {
    struct sockaddr_in sin ;
    char *argscan();
    int   rc, oldmask;
    int schedprio = 0;
       /* Temps for looking up interface... */
    char ifbuf[BUFSIZ];
    struct ifconf ifc;
    int if_s;
    
    packetno = -1;
    packetcnt = 0;
    Xdebug = 0; 
    InitFields();
    rd_filename[0] = '\0';
    
    if (--argc > 0 && **++argv == '?') {
        Usage() ;
        exit(0) ;
    }
        

    while (argc > 0 && **argv == '-') {
        switch (argv[0][1]) {
        
            case 'x':   /* -x    -- Debug flag  */
                Xdebug = 1 ;
                break ;
       
            case 'h':   /* -h    -- History flag  */
                HistSw = 1 ;
                break ;
                                
            case 'i':   /* -i <interface> */               
                EtherIface = argscan(&argc, &argv);
                break;
                
            case 'n':  /* -n <number of Network packets> */            
                packetno = atoi(argscan(&argc, &argv));
                break;
                
            case 't':  /* -t <timeout in secs> */           
                Timeout = atoi(argscan(&argc, &argv));
                break;
            
            case 'p':  /* -p <TCP port#> */             
                if (!(TCP_port= get_port(argscan(&argc, &argv), "statspy"))) {
                    exit(1);
                }
                break;

            case 's':  /* -s <scheduling priority> */
                schedprio = atoi(argscan(&argc, &argv));
                break;
                
            case 'f': /* -f <trace file name> */
                TraceFile = argscan(&argc, &argv);
                break ;

            case '1': /* -1 => single-column output mode */    
                {extern onecolmode;
                 onecolmode = TRUE;
                }
                break;
                
            default:    
                Usage() ;
                exit(1) ;
        } ;
        argv++;
        argc--;
    }
    
    if (argc > 0) {
            /* File contains configuration data */
    
        if (NULL == (fpinit = fopen(argv[0], "r"))) {
            perror("Cannot open parm file") ;
            exit(1) ;
        }
    }

#ifdef PARSE_FRAG
    InitFragCache();
#endif
        
        if (EtherIface == NULL) {
    /* User did not set Network interface explicitly, get list of 
     * interfaces, and assume first in list is the one we want.
     */
                if ((if_s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
                    perror("socket");
                    exit(1);
                }
                ifc.ifc_len = sizeof(ifbuf);
                ifc.ifc_buf = ifbuf;
                if (ioctl(if_s, SIOCGIFCONF, &ifc) < 0) {
                        perror("ioctl");
                        exit(1);
                }
                EtherIface = ifc.ifc_req->ifr_name;
                (void) close(if_s);
        }
        
    CurrTime = time(0) ; 
    oldmask = sigblock(sigmask(SIGTTIN) | sigmask(SIGPIPE));  
        /* Block TTIN signal, so Read in background will not stop process */
       
    printf("ISI StatSpy Program  %s  Starting  %s", 
           version, ctime(&CurrTime));
    signal(SIGALRM, Poller) ;                
    if ((lsock = socket(AF_INET, SOCK_STREAM, 0))< 0) {
        perror("statspy - Socket Error") ;
        exit(1) ;
    }
    
#ifdef	SO_REUSEADDR
    {
        int on = 1;
	if (setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR,
		(char *)&on, sizeof(on)) < 0) {
	    perror("statspy - setsockopt error");
	    exit(1);
	}
    }
#endif

    sin.sin_family = AF_INET;
    sin.sin_port = ntohs(TCP_port);
    sin.sin_addr.s_addr = INADDR_ANY;
    if (bind(lsock, &sin, sizeof(sin)) < 0) {
        perror("statspy - Bind Error");
        exit(1);
    }
         
    TTYfp = stdin;
    if (TraceFile)
        initdevice(0);  /* Test: Open file to set virtual TOD clock */
    
    if (fpinit) { 
               /* Process configuration file */
        while ((rc = Scan_Cmd(fpinit, NULL)) == CMD_OK);
        if (rc != CMD_EOF) {
            fprintf(stderr, "CONFIG FILE ERROR ON LINE %d\n", line_number);
            exit(1);
        }
    };
        
        /* Set the scheduling priority. */
    setpriority(PRIO_PROCESS, 0, schedprio);

        /* Start listening for TCP connection from remote client */
    listen(lsock, 5);
       
    if (TraceFile)
        printf("Network data from file: %s\n", TraceFile);
    else {
        StarTime = time(0);
        printf("Spying on %s Interface %s\n", IfaceType, EtherIface);
    }
    
        /*
         *  MAIN LOOP ... awaiting console command
         */
    
    while (1) { 
            /* Prompt for new command line */           
        printf("Stat>");
        fflush(stdout);
        
        rc = Scan_Cmd(TTYfp, NULL);
        switch (rc) {
    
        case CMD_EOF:
                /* End of file on local console ... must be in
                 *   background mode.  Ignore console from now on...
                 */
            while (STATread(0, NULL, 0) >= 0);
            perror("Error on TTY.");
            exit(1);
                            
        case CMD_OK:
        case CMD_ERR:
            break;
        }
    };
}


/*
 *    Routines to implement access control restrictions:
 *          Do_restrict(rw, (addr,mask))
 *             -- add (address, mask) pair to list, or clear list for (0,0).
 *          Check_restrict(addr)    --  check address for validity
 */

boolean Do_restrict(rw, parm)
    boolean rw;
    struct restrict_parm parm;
    {

    /*  Clear the list if both the address and mask are zero. */
    if (parm.addr == 0 && parm.mask == 0) {
        num_restrictions = 0;
        return(TRUE);
    }

    /*  Add the new entry to the list if the list isn't yet full. */

    if (num_restrictions < MAXRESTRICT) {
        restrictions[num_restrictions].addr.s_addr = parm.addr;
        restrictions[num_restrictions].mask.s_addr = parm.mask;
        restrictions[num_restrictions].readwrite = rw;
        num_restrictions++;
        return(TRUE);
    }
    printf("Too many 'restrictto' clauses.\n");
    return(FALSE);
}

boolean Check_restrict(addr, iswrite)
    u_int32 addr;      /* remote host IP address */
    boolean iswrite;  /* write-type operation requested */
    {
    u_short i;

    if (num_restrictions == 0) return(TRUE);  /* no restricts in effect */

    /*  Search the table for a matching entry.  */

    for (i=0; i<num_restrictions; i++)
        if (restrictions[i].addr.s_addr == (addr & restrictions[i].mask.s_addr)) {
            if (iswrite && !restrictions[i].readwrite)
                return(FALSE);
            return(TRUE); 
        }
    return(FALSE);  /* restricts in effect, and no match on host addr */
}

Show_restrict()
    {
    u_short i;
    char pbuf[20];
    
    if (num_restrictions) {
        printf("Remote access restricted to:\n");
        for (i=0; i<num_restrictions; i++) {
            print_val(pbuf, &restrictions[i].addr.s_addr, DTYP_IP, 4);
            printf("%s  %s\t", 
                (restrictions[i].readwrite)? "readwrite":"readonly ", pbuf);
            print_val(pbuf, &restrictions[i].mask.s_addr, DTYP_bits, 4);
            printf("%s\n", pbuf);
        }
    }
    else
        printf("No access restrictions\n");
}
