http://www.azstarnet.com/~ymg/files/slsnif-0.4.0.tar.gz
[slsnif.git] / src / slsnif.c
1
2 /*  slsnif.c
3  *  Copyright (C) 2001 Yan "Warrior" Gurtovoy (ymg@azstarnet.com)
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #include "ascii.h"
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include "slsnif.h"
27
28 void copyright() {
29     printf("\n\nSerial Line Sniffer. Version %s\n", VERSION);
30     printf("\tCopyright (C) 2001 Yan \"Warrior\" Gurtovoy (ymg@azstarnet.com)\n\n");
31 }
32
33 void usage() {
34     copyright();
35     printf("Usage: slsnif [options] <port>\n\n");
36     printf("REQUIRED PARAMETERS:\n");
37     printf("  <port>     - serial port to use (i.e /dev/ttyS0, /dev/ttyS1, etc.)\n\n");
38     printf("OPTIONS:\n");
39     printf("  -h (--help)             - displays this help.\n");
40     printf("  -b (--bytes)            - print number of bytes transmitted on every read.\n");
41     printf("  -n (--nolock)           - don't try to lock the port.\n");
42     printf("  -t (--timestamp)        - print timestamp for every transmission.\n");
43     printf("  -l (--log) <logfile>    - file to store output in, defaults to stdout.\n");
44     printf("  -s (--speed) <speed>    - baudrate to use, defaults to 9600 baud.\n");
45     printf("  -p (--port2) <port2>    - serial port to use instead of pty.\n");
46     printf("  --color      <color>    - color to use for normal output.\n");
47     printf("  --timecolor  <color>    - color to use for timestamp.\n");
48     printf("  --bytescolor <color>    - color to use for number of bytes transmitted.\n\n");
49     printf("Following names are valid colors:\n");
50     printf(" \tblack, red, green, yellow, blue, magenta, cyan, white,\n");
51     printf("\tbrightblack,brightred, brightgreen, brightyellow,\n");
52     printf("\tbrightblue, brightmagenta, brightcyan, brightwhite\n\n");
53     printf("Example: slsnif -l log.txt -s 2400 /dev/ttyS1\n\n");
54 }
55
56 void fatalError(char *msg) {
57     perror(msg);
58     _exit(-1);
59 }
60
61 int setRaw(int fd, struct termios *ttystate_orig) {
62 /* set tty into raw mode */
63
64     struct termios    tty_state;
65         
66     if (tcgetattr(fd, &tty_state) < 0) return 0;
67     /* save off original settings */
68     *ttystate_orig = tty_state;
69     /* set raw mode */
70     tty_state.c_lflag &= ~(ICANON | IEXTEN | ISIG | ECHO);
71     tty_state.c_iflag &= ~(ICRNL | INPCK | ISTRIP | IXON | BRKINT);
72     tty_state.c_oflag &= ~OPOST;
73     tty_state.c_cflag |= CS8;   
74     tty_state.c_cc[VMIN]  = 1;
75     tty_state.c_cc[VTIME] = 0;
76     cfsetispeed(&tty_state, tty_data.baudrate);
77     cfsetospeed(&tty_state, tty_data.baudrate); 
78     if (tcsetattr(fd, TCSAFLUSH, &tty_state) < 0) return 0;
79     return 1;
80 }
81
82 void fmtData(unsigned char *in, char *out, int in_size) {
83 /* format data */
84
85     char    charbuf[15];
86     int     i;
87
88     /* flush output buffer */
89     out[0] = 0;
90     for (i = 0; i < in_size; i++) {
91         if (in[i] == 127) {
92             /* it's a DEL character */
93             sprintf(charbuf, "%s (%03i) ", DEL, 127);
94         } else {
95             if (in[i] < 33)
96                 /* it's a control character or space */
97                 sprintf(charbuf, "%s (%03i) ", ascii_chars[(int) in[i]], in[i]);
98             else
99                 /* it's a printable character */
100                 sprintf(charbuf, "%c (%03i) ", in[i], in[i]);
101         }
102         /* put formatted data into output buffer */
103         strcat(out, charbuf);
104     }
105 }
106 void setColor(int out, char *color) {
107 /* changes color on the terminal */
108
109     if (color[0]) {
110         write(out, color, 7);
111     } else {
112         write(out, colors[WHITE].color, 7);
113     }
114 }
115
116 void writeData(int in, int out, int aux, int mode) {
117 /* reads data from `in`, formats it, writes it to `out` and `aux`.
118  * mode 0 - read from pipe
119  * mode 1 - read from port
120  * mode 2 - read from pty
121  */
122
123     unsigned char   buffer[BUFFSIZE];
124     char            outbuf[BUFFSIZE * 16 + 1];
125     int             n;
126 #ifdef HAVE_SYS_TIMEB_H
127     struct timeb    tstamp;
128     char            tbuf[29];
129     char            tmp[25];
130     char            tmp1[4];
131 #else
132 #ifdef HAVE_TIME_H
133     time_t          tstamp;
134 #endif
135 #endif
136     if ((n = read(in, buffer, BUFFSIZE)) < 0) {
137         if (mode)
138             if (errno == EIO)
139                 sleep(1);
140             else
141                 perror(mode == 1 ? RPORTFAIL : RPTYFAIL);
142         else
143             perror(RPIPEFAIL);
144     } else {
145         if (n > 0) {
146             if (mode) {
147                 write(out, buffer, n);
148                 write(aux, buffer, n);                    
149             } else {
150                 /* print timestamp if necessary */
151                 if (tty_data.tstamp) {
152                     if (out == STDOUT_FILENO) setColor(out, tty_data.tclr);
153                     write(out, "\n\n", 2);
154 #ifdef HAVE_SYS_TIMEB_H
155                     ftime(&tstamp);
156                     tmp[0] = tmp1[0] = tbuf[0] = 0;
157                     strncat(tmp, ctime(&(tstamp.time)), 24);
158                     strncat(tbuf, tmp, 19);
159                     sprintf(tmp1, ".%2ui", tstamp.millitm);
160                     strncat(tbuf, tmp1, 3);
161                     strcat(tbuf, tmp + 19);
162                     write(out, tbuf, 28);
163 #else
164 #ifdef HAVE_TIME_H
165                     time(&tstamp);
166                     write(out, ctime(&tstamp), 24);
167 #endif
168 #endif                    
169                 } else {
170                     write(out, "\n", 1);
171                 }
172                 if (out == STDOUT_FILENO) setColor(out, tty_data.clr);
173                 /* print prefix */
174                 write(out, aux ? PORT_IN : PORT_OUT, PRFXSIZE);
175                 /* format data */
176                 fmtData(buffer, outbuf, n);
177                 /* print data */
178                 write(out, outbuf, strlen(outbuf));
179                 /* print total number of bytes if necessary */
180                 if (tty_data.dspbytes) {
181                     buffer[0] = 0;
182                     sprintf(buffer, "\n%s %i", TOTALBYTES, n);
183                     if (out == STDOUT_FILENO) setColor(out, tty_data.bclr);
184                     write (out, buffer, strlen(buffer));
185                 }
186             }
187         }
188     }
189 }
190
191 void pipeReader() {
192 /* get data drom pipes */
193
194     int             maxfd;
195     fd_set          read_set;
196
197     maxfd = max(tty_data.ptypipefd[0], tty_data.portpipefd[0]);
198     while (TRUE) {
199         FD_ZERO(&read_set);
200         FD_SET(tty_data.ptypipefd[0], &read_set);
201         FD_SET(tty_data.portpipefd[0], &read_set);
202         if (select(maxfd + 1, &read_set, NULL, NULL, NULL) < 0) {
203             perror(SELFAIL);
204             return;
205         }
206         if (FD_ISSET(tty_data.ptypipefd[0], &read_set))
207             writeData(tty_data.ptypipefd[0], tty_data.logfd, 0, 0);
208         else
209             if (FD_ISSET(tty_data.portpipefd[0], &read_set))
210                 writeData(tty_data.portpipefd[0], tty_data.logfd, 1, 0);
211     }
212 }
213
214 void closeAll() {
215 /* close all opened file descriptors */
216     /* unlock the port(s) if necessary */
217     if (!tty_data.nolock) {
218         if (tty_data.portName && tty_data.portName[0])
219             dev_unlock(tty_data.portName);
220         /* this pointer should be NULL if pty is used */
221         if (tty_data.ptyName) dev_unlock(tty_data.ptyName);
222     }
223     /* restore color */
224     if (tty_data.logfd == STDOUT_FILENO) 
225            setColor(tty_data.logfd, colors[WHITE].color);
226     /* restore settings on pty */
227     if (tty_data.ptyraw)
228         tcsetattr(tty_data.ptyfd, TCSAFLUSH, &tty_data.ptystate_orig);
229     /* close pty */
230     if (tty_data.ptyfd >= 0) close(tty_data.ptyfd);
231     /* restore settings on port */
232     if (tty_data.portraw)
233         tcsetattr(tty_data.portfd, TCSAFLUSH, &tty_data.portstate_orig);
234     /* close port */
235     if (tty_data.portfd >= 0) close(tty_data.portfd);
236     /* close log file */
237     write(tty_data.logfd, "\n", 1);
238     if (tty_data.logfd != STDOUT_FILENO && tty_data.logfd >= 0)
239         if ((close(tty_data.logfd)) < 0) perror(CLOSEFAIL);
240     /* close write pipes */
241     if (tty_data.ptypipefd[1] >= 0) close(tty_data.ptypipefd[1]);
242     if (tty_data.portpipefd[1] >= 0) close(tty_data.portpipefd[1]);
243     /* free allocated memory for portName */
244     if (tty_data.portName) free(tty_data.portName);
245     if (pid >= 0) kill(pid, SIGINT);
246 }
247
248 RETSIGTYPE sigintP(int sig) {
249 /*parent signal handler for SIGINT */
250     closeAll();
251     _exit(1);
252 }
253
254 RETSIGTYPE sigintC(int sig) {
255 /* child signal handler for SIGINT */
256     /* close read pipes */
257     if (tty_data.ptypipefd[0] >= 0) close(tty_data.ptypipefd[0]);
258     if (tty_data.portpipefd[0] >= 0) close(tty_data.portpipefd[0]);    
259     _exit(1);    
260 }
261
262 RETSIGTYPE sigchldP(int sig) {
263 /* signal handler for SIGCHLD */
264
265     int status;
266     
267     wait(&status);
268 }
269
270 char *getColor(char *name) {
271 /* returns escape sequence that corresponds to a given color name */
272
273     int i = 0;
274
275     while (colors[i].name && strcmp(name, colors[i].name)) i++;
276     if (colors[i].name) return colors[i].color;
277     return NULL;
278 }
279
280 int main(int argc, char *argv[]) {
281     
282     int             i, j, maxfd, optret, needsync = 1;
283     char            *logName = NULL, baudstr[7], *ptr1, *ptr2;
284     struct termios  tty_state;
285     fd_set          rset;
286     
287 #ifdef HAVE_GETOPT_LONG
288     struct option longopts[] = {
289         {"help",       0, NULL, 'h'},
290         {"log",        1, NULL, 'l'},
291         {"nolock",     0, NULL, 'n'},
292         {"port2",      1, NULL, 'p'},
293         {"speed",      1, NULL, 's'},
294         {"bytes",      0, NULL, 'b'},
295         {"timestamp",  0, NULL, 't'},
296         {"color",      1, NULL, 'x'},
297         {"timecolor",  1, NULL, 'y'},
298         {"bytescolor", 1, NULL, 'z'},
299         {NULL,         0, NULL,   0}
300     };
301 #endif
302     /* initialize variables */
303     baudstr[0] = 0;
304     tty_data.portName = tty_data.ptyName = NULL;
305     tty_data.ptyfd = tty_data.portfd = tty_data.logfd = -1;
306     tty_data.ptyraw = tty_data.portraw = tty_data.nolock = FALSE;
307     tty_data.dspbytes = tty_data.tstamp = FALSE;
308     tty_data.ptypipefd[0] = tty_data.ptypipefd[1] = -1;
309     tty_data.portpipefd[0] = tty_data.portpipefd[1] = -1;
310     tty_data.baudrate = DEFBAUDRATE;
311     tty_data.clr[0] = tty_data.bclr[0] = tty_data.tclr[0] = 0;
312     /* parse rc-file */
313     readRC(&tty_data);
314     /* activate signal handlers */
315     signal(SIGINT, sigintP);
316     signal(SIGCHLD, sigchldP);
317     /* register closeAll() function to be called on normal termination */
318     atexit(closeAll);
319     /* process command line arguments */
320 #ifdef HAVE_GETOPT_LONG
321     while ((optret = getopt_long(argc, argv, OPTSTR, longopts, NULL)) != -1) {
322 #else
323     while ((optret = getopt(argc, argv, OPTSTR)) != -1) {
324 #endif
325         switch (optret) {
326             case 'b':
327                 tty_data.dspbytes = TRUE;
328                 break;
329             case 't':
330                 tty_data.tstamp = TRUE;
331                 break;
332             case 'n':
333                 tty_data.nolock = TRUE;
334                 break;
335             case 'l':
336                 logName = (optarg[0] == '=' ? optarg + 1 : optarg);
337                 break;
338             case 's':
339                 i = 0;
340                 while (baudrates[i].spdstr &&
341                         strcmp(optarg, baudrates[i].spdstr)) i++;
342                 if (baudrates[i].spdstr) {
343                     tty_data.baudrate = baudrates[i].speed;
344                     strcat(baudstr, baudrates[i].spdstr);
345                 }
346                 break;
347             case 'p':
348                 tty_data.ptyName = (optarg[0] == '=' ? optarg + 1 : optarg);
349                 break;
350             case 'x':
351                 ptr1 = getColor(optarg);
352                 if (ptr1) {
353                     tty_data.clr[0] = 0;
354                     strcat(tty_data.clr, ptr1);
355                 }
356                 break;
357             case 'y':
358                 ptr1 = getColor(optarg);
359                 if (ptr1) {
360                     tty_data.tclr[0] = 0;
361                     strcat(tty_data.tclr, ptr1);
362                 }
363                 break;
364             case 'z':
365                 ptr1 = getColor(optarg);
366                 if (ptr1) {
367                     tty_data.bclr[0] = 0;
368                     strcat(tty_data.bclr, ptr1);
369                 }
370                 break;
371             case 'h':
372             case '?':
373             default :
374                 usage();
375                 tty_data.ptyName = NULL;
376                 return -1;
377         }
378     }
379     if (optind < argc) {
380            if (!(tty_data.portName = malloc(PORTNAMELEN))) fatalError(MEMFAIL);
381            ptr1 = argv[optind];
382            ptr2 = tty_data.portName;
383            while((*ptr1 == '/' || isalnum(*ptr1))
384                 && ptr2 - tty_data.portName < PORTNAMELEN - 1) *ptr2++ = *ptr1++;
385            *ptr2 = 0;
386     }
387     if (!tty_data.portName || !tty_data.portName[0]) {
388         usage();
389         tty_data.ptyName = NULL;
390         return -1;
391     }
392     copyright();
393     /* open logfile */
394     if (!logName)
395         tty_data.logfd = STDOUT_FILENO;
396     else {
397         if((tty_data.logfd = open(logName,
398                     O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR)) < 0) {
399             perror(LOGFAIL);
400             tty_data.logfd = STDOUT_FILENO;
401         } else {
402             printf("Started logging data into file '%s'.\n", logName);
403         }
404     }
405     /* create pipe */
406     if (pipe(tty_data.ptypipefd) < 0 || pipe(tty_data.portpipefd) < 0) {
407         perror(PIPEFAIL);
408         return -1;
409     }
410     /* fork child process */
411     switch (pid = fork()) {
412     case 0:
413         /* child process */
414         /* close write pipe */
415         close(tty_data.ptypipefd[1]);
416         close(tty_data.portpipefd[1]);
417         signal(SIGINT, sigintC);
418         pipeReader(&tty_data);
419         break;
420     case -1:
421         /* fork() failed */
422         perror(FORKFAIL);
423         return -1;
424     default:
425         /* parent process */
426         /* close read pipe */
427         close(tty_data.ptypipefd[0]);
428         close(tty_data.portpipefd[0]);
429         break;
430     }
431     if (!tty_data.ptyName) {
432         /* open master side of the pty */
433         /* Search for a free pty */
434         if (!(tty_data.ptyName = strdup(DEFPTRNAME))) fatalError(MEMFAIL);
435         for(i = 0; i < 16 && tty_data.ptyfd < 0; i++) {
436             tty_data.ptyName[8] = "pqrstuvwxyzPQRST"[i];
437             for(j = 0; j < 16 && tty_data.ptyfd < 0; j++) {
438                 tty_data.ptyName[9] = "0123456789abcdef"[j];
439                 /* try to open master side */
440                 tty_data.ptyfd = open(tty_data.ptyName, O_RDWR|O_NONBLOCK|O_NOCTTY);
441             } 
442         }
443         if (tty_data.ptyfd < 0) {
444             /* failed to find an available pty */
445             free(tty_data.ptyName);
446             /*set pointer to NULL as it will be checked in closeAll() */
447             tty_data.ptyName = NULL;
448             perror(PTYFAIL);
449             return -1;
450         }
451         /* create the name of the slave pty */
452         tty_data.ptyName[5] = 't';
453         printf("Opened pty: %s\n", tty_data.ptyName);
454         free(tty_data.ptyName);
455         /*set pointer to NULL as it will be checked in closeAll() */
456         tty_data.ptyName = NULL;
457     } else {
458         /* open port2 instead of pty */
459         /* lock port2 */
460         if (!tty_data.nolock && dev_setlock(tty_data.ptyName) == -1) {
461             /* couldn't lock the device */
462             return -1;
463         }
464         /* try to open port2 */
465         if ((tty_data.ptyfd = open(tty_data.ptyName, O_RDWR|O_NONBLOCK)) < 0) {
466             perror(PORTFAIL);
467             return -1;
468         }
469         printf("Opened port: %s\n", tty_data.ptyName);
470     }
471     /* set raw mode on pty */
472     if(!setRaw(tty_data.ptyfd, &tty_data.ptystate_orig)) {
473         perror(RAWFAIL);
474         return -1;
475     }
476     tty_data.ptyraw = TRUE;
477     /* lock port */
478     if (!tty_data.nolock && dev_setlock(tty_data.portName) == -1) {
479         /* couldn't lock the device */
480         return -1;
481     }
482     /* try to open port */
483     if ((tty_data.portfd = open(tty_data.portName, O_RDWR|O_NONBLOCK)) < 0) {
484         perror(PORTFAIL);
485         return -1;
486     }
487     printf("Opened port: %s\n", tty_data.portName);
488     /* set raw mode on port */
489     if (!setRaw(tty_data.portfd, &tty_data.portstate_orig)) {
490         perror(RAWFAIL);
491         return -1;
492     }
493     tty_data.portraw = TRUE;
494     printf("Baudrate is set to %s baud%s.\n",
495                 baudstr[0] ? baudstr : "9600",
496                 baudstr[0] ? "" : " (default)");
497     /* start listening to the slave and port */
498     maxfd = max(tty_data.ptyfd, tty_data.portfd);
499     while (TRUE) {
500         FD_ZERO(&rset);
501         FD_SET(tty_data.ptyfd, &rset);
502         FD_SET(tty_data.portfd, &rset);
503         if (select(maxfd + 1, &rset, NULL, NULL, NULL) < 0) {
504             perror(SELFAIL);
505             return -1;
506         }
507         if (FD_ISSET(tty_data.portfd, &rset)) {
508             /* data coming from device */
509             writeData(tty_data.portfd, tty_data.ptyfd, tty_data.portpipefd[1], 1);
510         } else {
511             if (FD_ISSET(tty_data.ptyfd, &rset)) {
512                 /* data coming from host */
513                 if (needsync) {
514                     printf("Syncronizing ports...");
515                     if (tcgetattr(tty_data.ptyfd, &tty_state) ||
516                             tcsetattr(tty_data.portfd, TCSANOW, &tty_state)) {
517                         perror(SYNCFAIL);
518                         return -1;
519                     }
520                     needsync = 0;
521                     printf("Done!\n");
522                 }
523                 writeData(tty_data.ptyfd, tty_data.portfd, tty_data.ptypipefd[1], 2);
524             }
525         }
526     }
527     return 1;
528 }