KhaosBringer icon

ttysniff.c

KhaosBringer | PRO | 05/25/18 03:33:11 AM UTC | 0 ⭐ | 666 👁️ | Never ⏰ | []
C |

17.92 KB

|

None

|

0 👍

/

0 👎

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/select.h>
#include <ctype.h>
#include <getopt.h>
 
#define VERSION "2.6.0"
 
/* values for parity */
#define SERIAL_NO_PARITY    1
#define SERIAL_EVEN_PARITY  2
#define SERIAL_ODD_PARITY   4
 
/* values for data bits */
#define SERIAL_8_DATA_BITS  8 
#define SERIAL_7_DATA_BITS  7 
 
/* values for stop bits */
#define SERIAL_1_STOP_BITS  1 
#define SERIAL_2_STOP_BITS  2 
 
/* flow control */
#define FLOW_CONTROL_NONE     1
#define FLOW_CONTROL_HARDWARE 2
#define FLOW_CONTROL_SOFTWARE 3
 
#define FALSE 0
#define TRUE  1
 
#define DEFAULT_BAUD_RATE  B9600
//#define BAUD_RATE  B19200
//#define BAUD_RATE  B38400
            
#define NON_BLOCKING_SERIAL 
 
char serial_port[FILENAME_MAX+1];
char logfile_name[FILENAME_MAX+1];
 
/* log output to file */
int opt_log_output = FALSE; 
 
/* dump output as hex */
int opt_hex_output = TRUE;  
 
/* strip high bit from all chars sent to stdout */ 
int opt_strip_high_bit = FALSE;  
 
/* default is to transmit CRLF on CR from stdin; when true, only the CR is
 * transmitted to remote 
 */
int opt_bare_cr = FALSE;  
 
speed_t baudrate = DEFAULT_BAUD_RATE;
 
/* at some point, could make these command line configurable */
int parity = SERIAL_NO_PARITY;
//int databits = SERIAL_7_DATA_BITS;
int databits = SERIAL_8_DATA_BITS;
int stopbits = SERIAL_1_STOP_BITS;
//int flow_control = FLOW_CONTROL_SOFTWARE;
//int flow_control = FLOW_CONTROL_HARDWARE;
int flow_control = FLOW_CONTROL_NONE;
 
int main_quit = 0;
 
typedef struct {
    const char *str;
    int len; /* string length of str */
    speed_t speed;
} BaudRate;
 
BaudRate baudrates[] = {
    { "110",  3, B110   },
    { "150",  3, B150   },
    { "300",  3, B300   },
    { "600",  3, B600   },
    { "1200", 4, B1200  },
    { "2400", 4, B2400  },
    { "4800", 4, B4800  },
    { "9600", 4, B9600  },
    { "19200",  5, B19200 },
    { "38400",  5, B38400 },
    { "57600",  5, B57600 },
    { "115200", 6, B115200},
    { "230400", 6, B230400},
};
 
const unsigned char CR = '\r';
const unsigned char LF = '\n';
 
#if defined linux || defined unix || defined __GNUC__
void signal_term( int signum )
{
    fprintf( stderr, "signal!\n" );
    main_quit = 1;
}
 
void init_signals( void )
{
    int uerr;
    struct sigaction sigterm; 
    struct sigaction sigint; 
 
    memset( &sigterm, 0, sizeof(sigterm) );
    sigterm.sa_handler = signal_term;
    uerr = sigaction( SIGTERM, &sigterm, NULL );
    if( uerr != 0 ) {
        perror( "sigaction(SIGTERM)" );
        exit(1);
    }
 
    memset( &sigint, 0, sizeof(sigint) );
    sigint.sa_handler = signal_term;
    uerr = sigaction( SIGINT, &sigint, NULL );
    if( uerr != 0 ) {
        perror( "sigaction(SIGINT)" );
        exit(1);
    }
}
#endif
 
int serial_open_port( void )
{
    struct termios oldtio, newtio;
    int fd;
 
    /* stupid human check -- I've been running this thing 
     *  as root (very very dumb) so I don't want to accidently
     *  write DNP to /dev/hda1 or something equally tragic.
     */
//    if( strncmp( path, "/dev/ttyS", 9 ) != 0 ) {
//        LOG_MESSAGE1( LOG_ERR, "\"%s\" not a serial port.", path );        
//        return ERR_FAIL;
//    }
 
    /* davep 29-Apr-2009 ; temp debug */
    fprintf( stderr, "%s opening %s\n", __FUNCTION__, serial_port );
 
    /* open serial port */
#ifdef NON_BLOCKING_SERIAL
    fd = open( serial_port, O_RDWR|O_NOCTTY|O_NONBLOCK );
#else
    fd = open( serial_port, O_RDWR|O_NOCTTY );
#endif
    if( fd < 0 ) {
        fprintf( stderr, "open() of %s failed : %s", 
                serial_port, strerror(errno) );
        return -1;
    }
 
    /* davep 29-Apr-2009 ; temp debug */
    fprintf( stderr, "%s opened %s successfully\n", __FUNCTION__, serial_port );
 
    tcgetattr( fd, &oldtio );
 
    memset( &newtio, 0, sizeof(newtio) );
 
    newtio.c_cflag = CLOCAL | CREAD ;
 
    /* parity */
    switch( parity ) {
        case SERIAL_NO_PARITY :
            /* nothing -- default is no parity */
            break;
 
        case SERIAL_EVEN_PARITY :
            newtio.c_cflag |= PARENB;
            break;
 
        case SERIAL_ODD_PARITY :
            newtio.c_cflag |= PARENB | PARODD;
            break;
 
        default :
            assert( 0 );
    }
 
    /* data bits */
    if( databits == SERIAL_8_DATA_BITS ) {
        newtio.c_cflag |= CS8;
    }
    else if( databits == SERIAL_7_DATA_BITS ) {
        newtio.c_cflag |= CS7;
    }
    else {
        assert( 0 );
    }
 
    /* stop bits */
    if( stopbits == SERIAL_1_STOP_BITS ) {
        /* nothing -- default is 1 stop bit */
    }
    else if( stopbits == SERIAL_2_STOP_BITS ) {
        newtio.c_cflag |= CSTOPB;
    }
    else {
        assert( 0 );
    }
 
    /* hardware flow control? */
    if( flow_control==FLOW_CONTROL_HARDWARE ) {
        newtio.c_cflag |= CRTSCTS;
    }
 
    newtio.c_lflag = 0;
    /* no additional lflags */
 
    newtio.c_iflag = 0; // | IXON | IXANY | IXOFF | IMAXBEL;
 
    /* software flow control? */
    if( flow_control==FLOW_CONTROL_SOFTWARE ) {
        newtio.c_iflag |= (IXON | IXOFF | IXANY);
    }
 
    newtio.c_oflag = 0;
    /* no additional oflags */
 
    cfsetospeed( &newtio, baudrate );
    cfsetispeed( &newtio, baudrate );
 
#ifndef NON_BLOCKING_SERIAL
    newtio.c_cc[VMIN]  = 1; /* block until this many chars recvd */
    newtio.c_cc[VTIME] = 0; /* inter character timer unused */
#endif
 
    tcflush( fd, TCIFLUSH );
    tcsetattr( fd, TCSANOW, &newtio );
 
    return fd;
}
 
int baud_string_to_baud( char *baudstring, speed_t *baudrate )
{
    int i;
 
    for( i=0 ; i<sizeof(baudrates)/sizeof(BaudRate) ; i++ ) {
        if(strncasecmp(baudrates[i].str,baudstring,baudrates[i].len )==0) {
            /* found it */
            *baudrate = baudrates[i].speed;
            return 0;
        }
    }
 
    return -1;
}
 
const char *baud_to_baud_string( speed_t baudrate )
{
    int i;
 
    for( i=0 ; i<sizeof(baudrates)/sizeof(BaudRate) ; i++ ) {
        if( baudrates[i].speed == baudrate ) {
            /* found it */
            return baudrates[i].str;
        }
    }
 
    return NULL;
}
 
void print_usage( void )
{
    int i;
 
    printf( "ttysniff %s (%s) - read and dump data from serial port.\n", 
            VERSION, __DATE__ );
    printf( "usage: ttysniff [-options] [-l logfile] [-b baud] [-f flowcontrol] path\n" );
    printf( "  -h                   show this help\n" );
    printf( "  -p                   print data as readable (default is to print as hex)\n" );
    printf( "  -l logfile           log traffic to binary file\n" );
    printf( "  -b baudrate          set baud rate (default=%s)\n", 
            baud_to_baud_string(DEFAULT_BAUD_RATE) );
    printf( "  -7                   7 data bits\n" );
    printf( "  -8                   8 data bits (default)\n" );
    printf( "  -f [hard|soft]       enable hardware (RTS/CTS) or software (XON/XOFF) flow control\n" );
    printf( "                       (default is no flow control)\n" );
    printf( "  --strip-high-bit     remove high bit (1<<8) from chars before printing\n" );
    printf( "                       (doesn't effect logging to file or printing as hex)\n" );
    printf( "  --bare-cr            don't send CRLF when reading CR from stdin\n" );
    printf( "                       (default is to send CRLF when reading CR on input)\n" );
    printf( "  path                 path to serial port (e.g., /dev/ttyS0)\n" );
    printf( "\n" );
    printf( "Available baud rates are: " );
 
    for( i=0 ; i<sizeof(baudrates)/sizeof(BaudRate) ; i++ ) {
        printf( "%s ", baudrates[i].str );
    }
    printf( "\n" );
}
 
static int str_match( const char *s1, const char *s2, size_t maxlen )
{
    /* Strings must exactly match and must be exactly maxlen. The maxlen+1
     * finds strings longer than maxlen
     */
    if( strnlen(s1,maxlen+1)<=maxlen &&
        strnlen(s2,maxlen+1)<=maxlen &&
        strncmp(s1,s2,maxlen)==0 ){
        return TRUE;
    }
    return FALSE;
}
 
static int parse_long_option( const char *long_opt_name, 
                              const char *long_opt_value )
{
    if( str_match( long_opt_name, "strip-high-bit", 14 ) ) {
        opt_strip_high_bit = TRUE;
    }
    else if( str_match( long_opt_name, "bare-cr", 8 ) ) {
        /* don't send a LF to remote side when read CR from stdin */
        opt_bare_cr = TRUE;
    }
 
    return 0;
}
 
int parse_args( int argc, char *argv[] )
{
    int retcode;
    char c;
    static struct option long_options[] = {
        { "strip-high-bit", 0, 0, 0 },   /* strip high bit (c&0x7f) */
        { "bare-cr", 0, 0, 0 }, /* don't send extra LF on reading CR from stdin */
        { 0, 0, 0, 0 },
    };
    int long_index;
 
    if( argc == 1 ) {
        print_usage();
        exit(1);
    }
 
    baudrate = DEFAULT_BAUD_RATE;
 
    while( 1 ) {
        c = getopt_long( argc, argv, "hpl:b:78f:", long_options, &long_index );
 
        if( c==-1 )
            break;
 
        switch( c ) {
            case 0 : 
                /* handle long option */
                retcode = parse_long_option( long_options[long_index].name, optarg );
                if( retcode != 0 ) {
                    exit(1);
                }
                break;
 
            case 'h' :
                print_usage();
                exit(1);
                break;
 
            case 'l' :
                /* save output file name */
                strncpy( logfile_name, optarg, FILENAME_MAX );
                opt_log_output = TRUE;
                break;
 
            case 'p' :
                opt_hex_output = FALSE;
                break;
 
            case 'b' :
                /* convert baud rate string to baud rate number */
                if( baud_string_to_baud( optarg, &baudrate ) != 0 ) {
                    fprintf( stderr, "Invalid baud rate \"%s\"\n", optarg );
                    exit(1);
                }
                break;
 
            case '7' :
                /* 7 data bits */
                databits = SERIAL_7_DATA_BITS;
                break;
 
            case '8' :
                /* 8 data bits */
                databits = SERIAL_8_DATA_BITS;
                break;
 
            case 'f' :
                if( strncmp( optarg, "hard", 4 ) == 0 ) {
                    flow_control = FLOW_CONTROL_HARDWARE;
                }
                else if( strncmp( optarg, "soft", 4 ) == 0 ) {
                    flow_control = FLOW_CONTROL_SOFTWARE;
                }
                else {
                    fprintf( stderr, "Invalid flow control \"%s\"\n", optarg );
                    exit(1);
                }
                break;
 
            case '?' :
                print_usage();
                exit(1);
                break;
 
            default :
                break;
        }
    }
 
    
    /* get serial port name */
    if (optind < argc) {
        strncpy( serial_port, argv[optind++], FILENAME_MAX );
    }
    else {
        fprintf( stderr, "You must specify a serial port.\n" );
        print_usage();
        exit(1);
    }
 
    /* XXX temp debug */
//    fprintf( stderr, "baud=%ld port=%s\n", baudrate, serial_port );
 
    return 0;
}
 
int
terminal_to_raw( struct termios *save_tios )
{
    struct termios newtios;
    int stdin_fileno;
    int retcode;
 
    stdin_fileno = fileno(stdin);
 
    /* get a copy well save until we restore stdin at exit */
    retcode = tcgetattr( stdin_fileno, save_tios );
    if( retcode < 0 ) {
        fprintf( stderr, "tcgetattr() failed to get stdin term attributes: %d %s\n", 
                errno, strerror(errno));
        return -1;
    }
 
    /* get them again so we can muck with them */
    retcode = tcgetattr( stdin_fileno, &newtios );
    if( retcode < 0 ) {
        fprintf( stderr, "tcgetattr() failed to get stdin term attributes: %d %s\n", 
                errno, strerror(errno));
        return -1;
    }
 
    cfmakeraw( &newtios );
 
    /* set stdin to raw */
    retcode = tcsetattr( stdin_fileno, TCSANOW, &newtios );
    if( retcode < 0 ) {
        fprintf( stderr, "tcsetattr() failed to set stdin term attributes: %d %s\n", 
                errno, strerror(errno));
        return -1;
    }
 
    return 0;
}
 
int main( int argc, char *argv[] )
{
    int err;
    FILE *binfile=NULL;
    int stdin_fileno, fd;
    unsigned char databyte;
    int count;
    fd_set read_fds;
    int max_fd;
    struct termios stdin_tios;
    
    parse_args( argc, argv );
 
    init_signals();
 
    if( opt_log_output ) {
        binfile = fopen( logfile_name, "a" );
        if( binfile == NULL ) {
            fprintf( stderr, "Unable to open log file \"%s\" : %s\n", 
                    logfile_name, strerror(errno) );
            exit(1);
        }
        setbuf( binfile, NULL );
    }
 
    fd = serial_open_port();
    if( fd < 0 ) {
        /* serial_open_port() displays error */
        exit(1);
    }
 
    /* set the terminal to raw so we can get single keystrokes */
    fprintf( stderr, "setting stdin to raw...\n" );
    memset( &stdin_tios, 0, sizeof(struct termios) );
    terminal_to_raw( &stdin_tios );
 
    /* davep 20-July-06 ; XXX TEMPORARY FOR DEBUGGING kick off some activity */
//    write( fd, "scan var print\r\n", 16 );
 
    setbuf( stdout, NULL );
    count = 0;
    stdin_fileno = fileno(stdin);
    while( !main_quit ) {
 
//        fprintf( stderr, "stdin=%d\n", fileno(stdin));
 
        FD_ZERO( &read_fds );
        FD_SET( stdin_fileno, &read_fds );
        FD_SET( fd, &read_fds );
 
        max_fd = stdin_fileno;
        if( fd > max_fd ) {
            max_fd = fd;
        }
 
        err = select( max_fd+1, &read_fds, NULL, NULL, NULL );
        if( err<0 ) {
            fprintf( stderr, "select err=%d errno=%d (%s)\r\n", err, errno,
                    strerror(errno) );
            continue;
        }
        
        if( FD_ISSET( stdin_fileno, &read_fds ) ) {
            err = read( stdin_fileno, &databyte, 1 );
            if( err <= 0 ) {
                fprintf( stderr, "read stdin err=%d errno=%d (%s)\r\n", 
                        err, errno, strerror(errno) );
                break;
            }
 
            if( databyte == 0x03 ) {
                /* Ctrl-C? */
                break;
            }
 
            write( fd, &databyte, 1 );
            if( databyte==CR && !opt_bare_cr ) {
                write( fd, &LF, 1 );
            }
        }
 
        if( FD_ISSET( fd, &read_fds ) ) {
            err = read( fd, &databyte, 1 );
            if( err <= 0 ) {
                fprintf( stderr, "read fd err=%d errno=%d (%s)\r\n", 
                        err, errno, strerror(errno) );
                break;
            }
 
            if( opt_hex_output ) {
                /* There is something weird in printf().  I want to see 0x00 and it
                 * prints "0" instead.  Messes up my pretty screen alignment.
                 */
                if( databyte ) {
#ifdef PRINT_0X
                    printf( "%#04x ", databyte );
                }
                else {
                    printf( "0x00 " );
                }
#else
                    printf( "%02x ", databyte );
                }
                else {
                    printf( "00 " );
                }
#endif
            }
            else {
                /* buyer beware -- just print whatever we get */
 
                /* added some simple "filtering"; TODO add regex filtering so
                 * really annoying stuff never hits human eyeballs
                 */
                int printit = 1;
 
                /* davep 10-May-2012 ; bell pissing me off */
                if( databyte==0x07 ) {
                    printit = 0;
                }
 
                /* davep 19-aug-2011 ; found a platform that uses \n\r not \r\n
                 * (buh?) so filter out the 0x0d 
                 */
                if( databyte==0x0a ) {
                    printf( "%c", 0x0d );
                }
                if( databyte==0x0d ) {
                    printit = 0;
                }
 
                /* davep 27-Oct-2011 ; quiet down people doing obnoxious stuff like:
                 *
                 *  ******** color_pipe_set_config ENTRY config: 0x5fae64
                 *
                 */
//                if( databyte == '*' ) {
//                    printit = 0;
//                }
 
                /* davep 01-Aug-2010 ; I did the 0x7f a while ago (strips the
                 * 8th bit) because our newer hardware has issues and the 8th
                 * bit flips up and down randomly.
                 */
                if( printit ) {
                    if( opt_strip_high_bit ) {
                        printf( "%c", databyte&0x7f );
                    }
                    else {
                        printf( "%c", databyte );
                    }
                }
 
            }
 
            if( opt_log_output ) {
                fwrite( &databyte, 1, 1, binfile );
                fflush( binfile );
            }
        }
 
        count++;
    }
 
    close( fd );
    
    if( opt_log_output ) {
        fclose( binfile );
    }
 
    /* set stdin back to starting point */  
    fprintf( stderr, "restoring stdin to previous state\r\n" );
    err = tcsetattr( stdin_fileno, TCSANOW, &stdin_tios );
    if( err < 0 ) {
        fprintf( stderr, "tcsetattr() failed to set stdin term attributes: %d %s\n", 
                errno, strerror(errno));
        /* keep going and hope for the best */
    }
 
    printf( "\nbye!\n" );
    return 0;
}

Comments