1 /* Copyright (C) 2003 GFRN systems
2
3 This program is free software; you can redistribute it and/or
4 modify it under the terms of the GNU General Public License as
5 published by the Free Software Foundation; either version 2 of the
6 License, or (at your option) any later version.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
16 02111-1307, USA.
17
18 The latest version of this program may be found at
19 http://CQiNet.sourceforge.net
20
21 $Log: tbdcmd.c,v $
22 Revision 1.19 2009/09/13 23:14:36 wb6ymh
23 Added support for .rxlevel command.
24
25 Revision 1.18 2009/04/04 16:29:21 wb6ymh
26 1. Added code to set port from the TLB_CMDPORT environment variable if present.
27 2. Added code to set the maximum wait time for play emumlation fomr the
28 TLB_MAX_PLAY environment variable if present. The default timeout is
29 30 seconds.
30
31 Revision 1.17 2009/02/08 16:43:46 wb6ymh
32 Added missing break in -x switch in EmulateImike.
33
34 Revision 1.16 2008/08/15 17:30:27 wb6ymh
35 Added include for sys/types.h before sys/socket.h as needed by OSX.
36 Thanks to Wyatt KJ4CTD for the fix.
37
38 Revision 1.15 2008/07/23 15:30:35 wb6ymh
39 1. Added full duplex (-x) and uLaw (-n) support to the iMike enulation.
40 2. Added dtmfregen script emulation profile.
41
42 Revision 1.14 2008/05/28 18:04:50 wb6ymh
43 1. TLBPORT -> TLB_PORT.
44 2. Added code to Read/save history file to ~./.tlbcmd_history rather than
45 from the current directory.
46
47 Revision 1.13 2008/04/12 14:27:06 wb6ymh
48 Moved TlbPort assignment past the last declaration at the top of main.
49 GCC 4.x allows declarations anywhere within a block in C mode, but most other
50 complilers (and earlier versions of GCC) do not.
51
52 Revision 1.12 2008/03/08 18:10:51 wb6ymh
53 Added TLBPORT support for imike, ispeaker and play emulations.
54
55 Revision 1.11 2008/03/08 06:40:18 wb6ymh
56 1. Modified EmulatePlay to add a -c (send completion status) optoin to the
57 say commadn and wait for an TBD_SAY_COMPLETE to be returned before exiting.
58 2. Added code to prefix commands with port <TLBPORT>; when the TLBPORT env
59 variable is set. This variable is set to the port that issued the
60 command that caused the script to be executed. In many cases external
61 scripts can be blissfully ignorant of the port selection and just rely
62 on the man behind the curtain to do the right thing.
63 3. Added the -b (bare) command line switch to disable the automatic port
64 selection described above.
65 4. Removed the signal handler for SIGTERM and SIGINT for the play emulation
66 so play can be aborted by SIGTERM.
67
68 Revision 1.10 2008/02/26 18:16:16 wb6ymh
69 1. Modified code to check if stdout is a tty and bypass readline if not.
70 2. Added check for NULL returned by readline() to avoid segfault when
71 user uses ^D to exit.
72
73 Revision 1.9 2007/12/14 22:44:04 wb6ymh
74 1. Added GNU readline support.
75 2. Added support for the -p <port> iMike switch to EmulateImike.
76 3. Corrected log filename generation (thelinkbox builds).
77 4. Modified code to put logs into $TLB_HOME.
78
79 Revision 1.8 2007/12/07 23:15:23 wb6ymh
80 Added emulation of play, imike, and ispeaker.
81
82 Revision 1.7 2007/07/02 13:43:35 wb6ymh
83 Corrected C99 C declaration mixed with code in tbdcmd.c that's not accepted
84 by pre C89 compilers. This is the first time I've run into code gcc 4.x
85 liked that other compilers don't, it's usually the other way around.
86
87 Revision 1.6 2007/06/29 14:30:22 wb6ymh
88 1. Modified code to use 5199 by default instead of 5198 when involked as
89 tbdchat, tlbchat, or chat.
90 2. Modified all hard coded references to "tbdcmd" to use the current
91 program alias.
92 3. Added code to suppress stations lists received from conference bridges.
93 4. Added -S command line switch to disable station list suppression logic.
94
95 Revision 1.5 2006/08/21 16:32:33 wb6ymh
96 Added code to set rlim_max to RLIM_INFINITY. Fixes ages old "Unable to
97 enable core dumps" warning which occured on recent versions of the Linux
98 kernel.
99
100 Revision 1.4 2005/01/09 00:00:30 wb6ymh
101 1. Added -t option to enable display of a timestamp before each line
102 of chat text.
103
104 2. Fix typo and add "warning" to error message about failure to
105 enable core (not "code") dumps. Apparently Fedora has changed
106 something as this always fails with "Invalid argument".
107
108 3. Modified new tbdcmd misfeature that suppressed "error" codes of
109 zero to only suppress them when tbdcmd is run interactively.
110
111 4. Corrected bug caused by uninitialized select timeout structure in
112 tbdcmd. This bug only occurred on a few systems, thanks to ke4tte
113 for providing access to one where it did so I could debug it.
114
115 Revision 1.3 2004/12/01 14:30:27 wb6ymh
116 1. Don't watch stdin unless it's a tty!
117 2. Don't suppress "error" codes of zero unless we're running interactively.
118
119 Revision 1.2 2004/11/29 00:38:50 wb6ymh
120 1. Added timeout to avoid hanging if thebridge doesn't respond.
121 2. Added support for text chat.
122
123 Revision 1.1 2003/08/16 14:27:27 wb6ymh
124 Initial import: command line utility to send commands to thebridge.
125
126 */
127
128 #include "common.h"
129
130 #ifndef _WIN32
131 // FreeBSD, Linux, etc..
132 #include <stdio.h>
133 #ifdef HAVE_LIBREADLINE
134 #include <readline/readline.h>
135 #include <readline/history.h>
136 #endif
137 #include <stdarg.h>
138 #ifdef HAVE_UNISTD_H
139 #include <unistd.h>
140 #endif
141
142 #ifdef STDC_HEADERS
143 #include <stdlib.h>
144 #endif
145
146 #ifdef TIME_WITH_SYS_TIME
147 #include <sys/time.h>
148 #include <time.h>
149 #else
150 #ifdef HAVE_SYS_TIME_H
151 #include <sys/time.h>
152 #else
153 #include <time.h>
154 #endif
155 #endif
156 #include <errno.h>
157
158 #ifdef HAVE_STRINGS_H
159 #include <string.h>
160 #endif
161 #include <assert.h>
162 #include <sys/types.h>
163 #include <sys/socket.h>
164 #include <netinet/in.h>
165 #include <sys/resource.h>
166 #include <arpa/inet.h>
167 #include <string.h>
168 #include <signal.h>
169 #include <termios.h>
170 #else
171 // Windoze
172 #include <stdio.h>
173 #include <winsock2.h>
174 #include <string.h>
175 #include <conio.h>
176 #include <time.h>
177 #endif
178
179 #include "ilink.h"
180 #include "tbd.h"
181
182 #ifndef FALSE
183 #define FALSE 0
184 #endif
185
186 #ifndef TRUE
187 #define TRUE !FALSE
188 #endif
189
190 #undef LOG_NORM
191 #define LOG_NORM(x) Log x
192
193 #undef LOG_ERROR
194 #define LOG_ERROR(x) Log x
195
196 #define RESP_TO 10 // wait for 10 seconds from responses from tbd
197
198 typedef union {
199 struct sockaddr s;
200 struct sockaddr_in i;
201 #define ADDR i.sin_addr.s_addr
202 #define PORT i.sin_port
203 } IPAdrUnion;
204
205
206 #ifdef _WIN32
207 // Windoze
208 WSADATA wsad;
209 #endif
210
211 int bQuiet = 0;
212 int bSlient = 0;
213 int bTimeStamp = 0;
214 int bAllowStationList = FALSE;
215 int Port = ILINK_RTP_PORT;
216 int bUseReadline = FALSE;
217
218 #ifdef LINK_BOX
219 #define PROMPT "tlb> "
220
221 int bShutdown = FALSE;
222 int LogFileRolloverType = -1; // no rollover, single logfile
223 FILE *LogFp = NULL;
224
225 static char *LogNames[] = {
226 "sun.log",
227 "mon.log",
228 "tue.log",
229 "wed.log",
230 "thr.log",
231 "fri.log",
232 "sat.log"
233 };
234
235 static char *MonthNames[] = {
236 "Jan",
237 "Feb",
238 "Mar",
239 "Apr",
240 "May",
241 "Jun",
242 "Jul",
243 "Aug",
244 "Sep",
245 "Oct",
246 "Nov",
247 "Dec"
248 };
249 #else
250 #define PROMPT "tbd> "
251 #endif
252
253 char *AppName = NULL;
254 char *TlbPort = NULL;
255 char *HistoryPath = NULL;
256 int MaxPlayWait = 30;
257
258 #ifndef HAVE_GETOPT
259 // globals needed by getopt
260 int optind = 1;
261 int optopt;
262 char *optarg;
263 int getopt(int argc,char **argv,char *opts);
264 #endif
265
266 char *TimeT2String(time_t time);
267 int IsStationList(char *Line);
268
269 #ifdef LINK_BOX
270 void SigHandler(int Signal);
271 int EmulateImike(char **argv,int argc);
272 int EmulateIspeaker(char **argv,int argc);
273 int EmulatePlay(char **argv,int argc);
274 int EmulateDtmfRegen(char **argv,int argc);
275
276 #endif
277
278 void SetTerminalMode(int mode);
279
Usage(char * Command)280 void Usage(char *Command)
281 {
282 printf("Usage: %s [-b] [-p<port>] [-q] [-s] [-S] [-t] [commands] \n",Command);
283 printf("\t-b - Bare, do not prepend port <TLBPORT>; to commands.\n");
284 printf("\t-p - Specify port (default %d).\n",Port);
285 printf("\t-q - Quiet, numeric result code only.\n");
286 printf("\t-s - Slient, suppress banner.\n");
287 printf("\t-S - Allow conference bridge's station list (default is suppress).\n");
288 printf("\t-t - Add timestamps to text chat messages.\n");
289 }
290
main(int argc,char * argv[])291 int main(int argc, char* argv[])
292 {
293 int Ret = 0;
294 int Option;
295 SOCKET MySocket;
296 IPAdrUnion HisAdr;
297 char Line[1024];
298 char *cp;
299 char *cp1;
300 int BytesRxed;
301 int BytesSent;
302 int FirstArg = 1;
303 int ArgCount = argc;
304 int WritePoint;
305 int Wrote;
306 int i;
307 fd_set ReadFDset;
308 struct timeval SelTO;
309 int Selret;
310 int bResponseWait = FALSE;
311 int bDisplayPrompt = TRUE;
312 int bNeedNewline = FALSE;
313 int bInteractive = FALSE;
314 int bCleanupCmd = FALSE;
315 time_t TimeNow;
316 time_t TimeCmdSent;
317 int bChat = FALSE;
318 int bBare = FALSE;
319 int Response = 0;
320
321 #ifndef _WIN32
322 struct rlimit rlim;
323
324 // On some systems core dumps are disabled by default,
325 // enable them so we can debug this thing !
326
327 rlim.rlim_cur = RLIM_INFINITY;
328 rlim.rlim_max = RLIM_INFINITY;
329 if(setrlimit(RLIMIT_CORE, &rlim)) {
330 perror("Warning - Unable to enable core dumps: ");
331 }
332 #else
333 /* Initialize Winsock*/
334 if(WSAStartup(0x0101, &wsad) != 0) {
335 printf("Fatal error: unable to initialize Winsock\n");
336 exit(3);
337 }
338 #endif
339
340 TlbPort = getenv("TLB_PORT");
341 AppName = strrchr(argv[0],PATH_SEP);
342
343 if(AppName != NULL) {
344 AppName++;
345 }
346 else {
347 AppName = argv[0];
348 }
349
350 if(strcmp(AppName,"tbdchat") == 0 ||
351 strcmp(AppName,"tlbchat") == 0 ||
352 strcmp(AppName,"chat") == 0)
353 {
354 Port = ILINK_RTCP_PORT;
355 bChat = TRUE;
356 }
357
358 if((cp = getenv("TLB_CMDPORT")) != NULL) {
359 if(sscanf(cp,"%d",&Port) != 1) {
360 printf("Warning: invalid TLB_CMDPORT \"%s\" ignored.\n",cp);
361 }
362 }
363
364 if((cp = getenv("TLB_MAX_PLAY")) != NULL) {
365 if(sscanf(cp,"%d",&MaxPlayWait) != 1) {
366 printf("Warning: invalid TLB_MAX_PLAY \"%s\" ignored.\n",cp);
367 }
368 }
369
370 if(isatty(fileno(stdin))) {
371 bUseReadline = TRUE;
372 }
373
374 #ifdef LINK_BOX
375 if(strcmp(AppName,"imike") == 0) {
376 return EmulateImike(argv,argc);
377 }
378 else if(strcmp(AppName,"ispeaker") == 0) {
379 return EmulateIspeaker(argv,argc);
380 }
381 else if(strcmp(AppName,"play") == 0) {
382 return EmulatePlay(argv,argc);
383 }
384 else if(strcmp(AppName,"dtmfregen") == 0) {
385 return EmulateDtmfRegen(argv,argc);
386 }
387 #endif
388
389 while((Option = getopt(argc, argv, "bp:qtsS")) != -1 && !Ret) {
390 FirstArg++;
391 ArgCount--;
392 switch(Option) {
393 case 'b': // bare
394 bBare = TRUE;
395 break;
396
397 case 'p': // port
398 if(sscanf(optarg,"%d",&Port) != 1 || Port <= 0 || Port > 0xffff) {
399 printf("%s: Error invalid port number \"%s\"\n",AppName,optarg);
400 exit(3);
401 }
402 break;
403
404 case 'q': // quiet
405 bQuiet = TRUE;
406 break;
407
408 case 's': // slient
409 bSlient = TRUE;
410 break;
411
412 case 'S': // Allow station list
413 bAllowStationList = TRUE;
414 break;
415
416 case 't': // Timestamps
417 bTimeStamp = TRUE;
418 break;
419
420 case '?':
421 Usage(AppName);
422 exit(3);
423 }
424 }
425
426 if(ArgCount == 1) {
427 bInteractive = TRUE;
428 #ifdef HAVE_LIBREADLINE
429 if(bUseReadline) {
430 char *HistoryFilename = ".tlbcmd_history";
431 char *HomeDir = getenv("HOME");
432 int PathLen = strlen(HistoryFilename) + 1;
433
434 if(HomeDir != NULL) {
435 PathLen += strlen(HomeDir) + 1;
436 }
437
438 HistoryPath = malloc(PathLen);
439
440 if(HistoryPath != NULL) {
441 if(HomeDir != NULL) {
442 strcpy(HistoryPath,HomeDir);
443 strcat(HistoryPath,"/");
444 strcat(HistoryPath,HistoryFilename);
445 }
446 else {
447 strcpy(HistoryPath,HistoryFilename);
448 }
449 read_history(HistoryPath);
450 }
451 }
452 #endif
453 }
454
455 if(!bQuiet && !bSlient && !Ret) {
456 printf("%s Version " VERSION " compiled "__DATE__ " " __TIME__"\n",
457 AppName);
458 }
459
460 if(ArgCount > 1) {
461 WritePoint = 0;
462 for(i = 0; i < ArgCount -1 ; i++) {
463 Wrote = snprintf(&Line[WritePoint],sizeof(Line)-WritePoint-1,"%s%s",
464 WritePoint == 0 ? "" : " ",argv[FirstArg+i]);
465 if(Wrote > 0 && WritePoint + Wrote < sizeof(Line)) {
466 WritePoint += Wrote;
467 }
468 else {
469 // Buffer overflow
470 break;
471 }
472 }
473 Line[WritePoint] = 0;
474 }
475 else {
476 Line[0] = 0;
477 }
478
479 if((MySocket = socket(AF_INET,SOCK_DGRAM,0)) == SOCKET_ERROR) {
480 printf("Socket() failed.\n");
481 }
482 else {
483 HisAdr.i.sin_family = AF_INET;
484 HisAdr.PORT = htons((u_short) Port);
485 HisAdr.ADDR = inet_addr("127.0.0.1");
486
487 SetTerminalMode(0);
488
489 do {
490 if(bInteractive && bDisplayPrompt && Line[0] == 0) {
491 bDisplayPrompt = FALSE;
492 printf(bChat ? "chat> " : PROMPT);
493 fflush(stdout);
494 }
495
496 if(strlen(Line) > 0) {
497 if(!bBare && !bInteractive && TlbPort != NULL) {
498 // prefix commands with tlb port selection from the environment
499 char *Temp = strdup(Line);
500 snprintf(Line,sizeof(Line),"port %s;%s",TlbPort,Temp);
501 free(Temp);
502 }
503 BytesSent = sendto(MySocket,Line,strlen(Line)+1,0,
504 &HisAdr.s,sizeof(HisAdr));
505
506 if(BytesSent == SOCKET_ERROR) {
507 Ret = ERROR_CODE;
508 printf("Error: sendto() failed (%d)\n",Ret);
509 break;
510 }
511 Line[0] = 0;
512 if(bCleanupCmd) {
513 break;
514 }
515 else {
516 bResponseWait = TRUE;
517 time(&TimeCmdSent);
518 }
519 }
520
521 FD_ZERO(&ReadFDset);
522 FD_SET(MySocket,&ReadFDset);
523
524 #ifdef _WIN32
525 // Windoze can't select on standard in 'cuz it ain't so standard
526 // so wake up every 100 milliseconds to poll the keyboard
527
528 SelTO.tv_sec = 0;
529 SelTO.tv_usec = 100000;
530 Selret = select(FD_SETSIZE,&ReadFDset,NULL,NULL,&SelTO);
531 #else
532 if(bInteractive) {
533 FD_SET((SOCKET) fileno(stdin),&ReadFDset);
534 }
535 if(bResponseWait) {
536 // Wait a second for the response
537 SelTO.tv_sec = 1;
538 SelTO.tv_usec = 0;
539 Selret = select(FD_SETSIZE,&ReadFDset,NULL,NULL,&SelTO);
540 }
541 else {
542 Selret = select(FD_SETSIZE,&ReadFDset,NULL,NULL,NULL);
543 }
544 #endif
545 time(&TimeNow);
546
547
548 #ifndef _WIN32
549 if(FD_ISSET(fileno(stdin),&ReadFDset))
550 #else
551 if(kbhit())
552 #endif
553 {
554 #ifdef HAVE_LIBREADLINE
555 if(bUseReadline) {
556 if(bNeedNewline) {
557 bNeedNewline = FALSE;
558 printf("\n%s",bChat ? "chat> " : PROMPT);
559 }
560 SetTerminalMode(2);
561 rl_already_prompted = 1;
562 cp = readline(bChat ? "chat> " : PROMPT);
563 if(cp != NULL) {
564 add_history(cp);
565 memcpy(Line,cp,sizeof(Line));
566 free(cp);
567 Line[sizeof(Line)-1] = 0;
568 }
569 else {
570 // EOF read by readline
571 printf("\n");
572 Line[0] = 0;
573 }
574 SetTerminalMode(1);
575 }
576 else {
577 #endif
578 if(bNeedNewline) {
579 bNeedNewline = FALSE;
580 printf("\n%s",bChat ? "chat> " : PROMPT);
581 }
582 if(fgets(Line,sizeof(Line),stdin) == NULL) {
583 break;
584 }
585 #ifdef HAVE_LIBREADLINE
586 }
587 #endif
588
589 if((cp = strchr(Line,'\n')) != NULL) {
590 *cp = 0;
591 }
592
593 bDisplayPrompt = TRUE;
594 bResponseWait = TRUE;
595 time(&TimeCmdSent);
596 if(strlen(Line) == 0) {
597 // exit on empty line
598 if(Response != TBD_RX_LEVEL_RPT) {
599 break;
600 }
601 else {
602 // We're receiving continous level reports, send a final
603 // .rxlevel command to silence them! (You think this is a
604 // kludge? You betcha !)
605 bCleanupCmd = TRUE;
606 strcpy(Line,".rxlevel");
607 continue;
608 }
609 }
610 }
611
612 if(FD_ISSET(MySocket,&ReadFDset)) {
613 BytesRxed = recv(MySocket,Line,sizeof(Line)-1,0);
614 if(BytesRxed == SOCKET_ERROR) {
615 Ret = ERROR_CODE;
616 printf("Error: recv() failed (%d)\n",Ret);
617 break;
618 }
619
620 Line[BytesRxed] = 0;
621 bDisplayPrompt = TRUE;
622
623 Ret = atoi(Line);
624 Response = Ret;
625 if(Ret != TBD_CHAT_TEXT && Ret != TBD_RX_LEVEL_RPT) {
626 bResponseWait = FALSE;
627 }
628
629 if(bQuiet) {
630 if((cp = strchr(Line,'\n')) != NULL) {
631 // Only want a single line, truncate the output
632 cp[1] = 0;
633 }
634 }
635 if(bInteractive) {
636 // Interactive mode, overwrite the prompt
637 printf("\r \r");
638 }
639
640 // Remove any extra line feeds at the end of the line. Thebridge's
641 // output contains a single <lf>, but text chat messages have two.
642
643 if((cp = strrchr(Line,'\n')) != NULL && cp[-1] == '\n') {
644 // Only want a single linefeed, remove the second one
645 *cp = 0;
646 }
647
648 time(&TimeNow);
649
650 if(Ret == TBD_CHAT_TEXT && bTimeStamp) {
651 printf("%s: ",TimeT2String(TimeNow));
652 }
653
654 if(Ret == TBD_CHAT_TEXT_SENT && bInteractive) {
655 // Interactive mode and this is just an ack for a message we
656 // sent, don't display it
657 }
658 else if(!bAllowStationList && bInteractive && IsStationList(Line)) {
659 // Suppress station list.
660 }
661 else if(bInteractive && Ret == TBD_RX_LEVEL_RPT &&
662 (cp = strchr(Line,'\n')) != NULL)
663 {
664 // Rx levels, supress return code and remove newline
665 cp++;
666 if((cp1 = strchr(cp,'\n')) != NULL) {
667 *cp1 = 0;
668 }
669 if(!bResponseWait) {
670 printf("\r%s",cp);
671 fflush(stdout);
672 bDisplayPrompt = FALSE;
673 bNeedNewline = TRUE;
674 }
675 }
676 else if(bInteractive &&
677 (Ret == 0 || Ret == TBD_CHAT_TEXT) &&
678 (cp = strchr(Line,'\n')) != NULL)
679 { // return code is not interesting, don't display it
680 printf("%s",&cp[1]);
681 }
682 else {
683 printf("%s",Line);
684 }
685 Line[0] = 0;
686 }
687
688 if(bResponseWait && (TimeNow - TimeCmdSent) >= RESP_TO) {
689 // Timeout waiting for a response from tbd
690
691 Ret = TBD_ERR_TIMEOUT;
692 printf("%d\n",Ret);
693 break;
694 }
695 } while(bInteractive);
696 }
697
698 SetTerminalMode(3);
699
700 if(HistoryPath != NULL) {
701 free(HistoryPath);
702 }
703 return Ret;
704 }
705
TimeT2String(time_t time)706 char *TimeT2String(time_t time)
707 {
708 struct tm *pTm;
709 static char TimeString[] = "mm/dd hh:mm";
710
711 if(time != 0) {
712 pTm = localtime(&time);
713 if(pTm != NULL) {
714 snprintf(TimeString,sizeof(TimeString),"%2d/%02d %2d:%02d",
715 pTm->tm_mon + 1,pTm->tm_mday,pTm->tm_hour,pTm->tm_min);
716 }
717 else {
718 return "";
719 }
720 }
721 else {
722 return "";
723 }
724 return TimeString;
725 }
726
727
728 // If we have ">CONF " on the first line then assume this is a station list
IsStationList(char * Line)729 int IsStationList(char *Line)
730 {
731 char *cp;
732 int Ret = FALSE;
733
734 if((cp = strchr(Line,'\n')) != NULL && (cp = strchr(cp+1,'\n')) != NULL) {
735 *cp = 0;
736 if(strstr(Line,">CONF ") != NULL) {
737 Ret = TRUE;
738 }
739 *cp = '\n';
740 }
741 return Ret;
742 }
743
744 #ifdef LINK_BOX
SendCommand(char * Cmd)745 int SendCommand(char *Cmd)
746 {
747 static int MySocket = -1;
748 int BytesSent;
749 int BytesRxed;
750 int Ret = -1;
751 int Selret;
752 IPAdrUnion HisAdr;
753 fd_set ReadFDset;
754 struct timeval SelTO;
755 char Line[1024];
756
757 do {
758 if(MySocket == -1) {
759 if((MySocket = socket(AF_INET,SOCK_DGRAM,0)) == SOCKET_ERROR) {
760 Ret = errno;
761 LOG_ERROR(("%s: socket() failed - %s\n",__FUNCTION__,
762 strerror(errno)));
763 break;
764 }
765 }
766
767 HisAdr.i.sin_family = AF_INET;
768 HisAdr.PORT = htons((u_short) Port);
769 HisAdr.ADDR = inet_addr("127.0.0.1");
770
771 if(Cmd != NULL) {
772 LOG_ERROR(("%s: sending command \"%s\"\n",__FUNCTION__,Cmd));
773 if(TlbPort != NULL) {
774 // prefix commands with tlb port selection from the environment
775 char *Temp = strdup(Cmd);
776 snprintf(Line,sizeof(Line),"port %s;%s",TlbPort,Temp);
777 free(Temp);
778 Cmd = Line;
779 }
780 BytesSent = sendto(MySocket,Cmd,strlen(Cmd)+1,0,&HisAdr.s,
781 sizeof(HisAdr));
782
783 if(BytesSent == SOCKET_ERROR) {
784 Ret = errno;
785 LOG_ERROR(("%s: sendto() failed - %s\n",__FUNCTION__,
786 strerror(errno)));
787 break;
788 }
789 }
790 Line[0] = 0;
791
792 FD_ZERO(&ReadFDset);
793 FD_SET(MySocket,&ReadFDset);
794
795 // Wait a second for the response
796 SelTO.tv_sec = 1;
797 SelTO.tv_usec = 0;
798 Selret = select(FD_SETSIZE,&ReadFDset,NULL,NULL,&SelTO);
799
800 if(FD_ISSET(MySocket,&ReadFDset)) {
801 BytesRxed = recv(MySocket,Line,sizeof(Line)-1,0);
802 if(BytesRxed == SOCKET_ERROR) {
803 Ret = errno;
804 LOG_ERROR(("%s: recv() failed - %s\n",__FUNCTION__,
805 strerror(errno)));
806 }
807 else {
808 int LineLen;
809
810 Line[BytesRxed] = 0;
811 LineLen = strlen(Line);
812 if(LineLen > 0) {
813 if(Line[LineLen-1] == '\n') {
814 // remove terminating linefeed if present
815 LineLen--;
816 }
817
818 if(Line[LineLen-1] == '\n') {
819 // remove second line if it is blank
820 LineLen--;
821 }
822 Line[LineLen] = 0;
823 }
824 LOG_ERROR(("%s: tlb returned %s\n",__FUNCTION__,Line));
825 Ret = atoi(Line);
826 }
827 }
828 } while(FALSE);
829 return Ret;
830 }
831
InstallSigHandler()832 void InstallSigHandler()
833 {
834 struct sigaction SigAction;
835
836 // Setup signal handlers using sigaction() rather than the signal() quagmire!
837
838 SigAction.sa_handler = SigHandler;
839 SigAction.sa_flags = 0;
840 sigemptyset(&SigAction.sa_mask);
841
842 sigaction(SIGTERM,&SigAction,NULL);
843 sigaction(SIGINT,&SigAction,NULL);
844 }
845
LogCmdLine(char ** argv,int argc)846 void LogCmdLine(char **argv,int argc)
847 {
848 char Line[120];
849 int BytesLeft = sizeof(Line) - 1;
850 char *cp = Line;
851 int i;
852 int Count;
853
854 for(i = 0; i < argc; i++) {
855 Count = snprintf(cp,BytesLeft,"%s ",argv[i]);
856
857 if(Count == -1 || Count >= BytesLeft) {
858 // Pre-C99 style snprintf returns -1 on buffer overflow.
859 // C99 style snprintf returns the number of characters that would
860 // have been written on buffer overflow.
861 // In either case the output was truncated and p->Count is bogus.
862
863 Count = BytesLeft;
864 }
865 cp += Count;
866 if((BytesLeft -= Count) == 0) {
867 break;
868 }
869 }
870 LOG_NORM(("CmdLine: %s\n",Line));
871 }
872
GetIRLPCall()873 char *GetIRLPCall()
874 {
875 char *Ret = NULL;
876 char *Local = getenv("LOCAL");
877 char Temp[1024];
878 FILE *fp;
879 char *cp;
880
881 if(Local != NULL) {
882 snprintf(Temp,sizeof(Temp),"%s/active",Local);
883 if((fp = fopen(Temp,"r")) != NULL) {
884 fgets(Temp,sizeof(Temp),fp);
885 if((cp = strchr(Temp,'\n')) != NULL) {
886 *cp = 0;
887 }
888
889 Ret = strdup(Temp);
890 fclose(fp);
891 }
892 else {
893 LOG_ERROR(("%s: Couldn't open \"%s\" - %s\n",__FUNCTION__,Temp,
894 strerror(errno)));
895 }
896 }
897 else {
898 LOG_ERROR(("%s: Error environment variable \"LOCAL\" not set.\n",
899 __FUNCTION__));
900 }
901 return Ret;
902 }
903
904 /* Usage: imike hostname[:port] [options] [ file1 / . ]...
905 -A Always transmit
906 -B Push to talk using keyboard
907 -CELP CELP compression
908 -D Enable debug output
909 -F ADPCM compression
910 -H Disables RADIO COS mode (monitor only)
911 -L Remote loopback
912 -N No compression
913 -Phostname[:port] Party line, add host to list
914 * -Q Disable debug output
915 * -T Telephone (GSM) compression
916 -TD Debug: send GSM with swapped byte order
917 -U Print this message
918 -x Full duplex audio
919 -Yindev[:ctldev] Override default audio device file name or specify open #fd
920 -8 Set audio device to 8-bit mu-law
921 */
EmulateImike(char ** argv,int argc)922 int EmulateImike(char **argv,int argc)
923 {
924 int i;
925 char *Hostname = NULL;
926 char *cp;
927 int bFullDuplex = FALSE;
928 int Port = 2074; // default audio port
929 char CmdLine[120];
930 char *Station = NULL;
931 char *CodecOption = "";
932
933 InstallSigHandler();
934 LogCmdLine(argv,argc);
935
936 for(i = 1; i < argc; i++) {
937 if(argv[i][0] == '-') {
938 switch(argv[i][1]) {
939 case 't':
940 case 'T':
941 LOG_ERROR(("%s: set GSM mode\n",__FUNCTION__));
942 CodecOption = "";
943 break;
944
945 case 'x':
946 case 'X':
947 bFullDuplex = TRUE;
948 break;
949
950 case 'n':
951 case 'N':
952 CodecOption = "-u ";
953 break;
954
955 case 'f':
956 case 'F':
957 CodecOption = "-a ";
958 break;
959
960 default:
961 LOG_ERROR(("%s: ignoring switch \"%s\"\n",__FUNCTION__,argv[i]));
962 break;
963 }
964 }
965 else {
966 // Not a switch, hostname hopefully
967 Hostname = argv[i];
968 LOG_ERROR(("%s: hostname \"%s\"\n",__FUNCTION__,Hostname));
969 }
970 }
971
972 do {
973 if(Hostname == NULL) {
974 break;
975 }
976
977 if((cp = strchr(Hostname,':')) != NULL) {
978 // Port number specified
979 *cp++ = 0;
980 if(sscanf(cp,"%d",&Port) != 1) {
981 LOG_ERROR(("%s: unable to parse port \"%s\"\n",__FUNCTION__,cp));
982 }
983 }
984
985 if(strcmp(Hostname,"127.0.0.1") != 0) {
986 if((Station = GetIRLPCall()) == NULL) {
987 Station = "IRLP";
988 }
989
990 snprintf(CmdLine,sizeof(CmdLine),"connect -p %d -s %s%s%s %s",
991 Port,CodecOption,bFullDuplex ? "-f " : "",
992 Hostname,Station);
993
994 SendCommand(CmdLine);
995 }
996
997 while(!bShutdown) {
998 sleep(1000);
999 }
1000
1001 if(strcmp(Hostname,"127.0.0.1") != 0) {
1002 // EchoIRLP connects to localhost to talk to tbd, ignore it
1003 snprintf(CmdLine,sizeof(CmdLine),".disconnect %s",Station);
1004 SendCommand(CmdLine);
1005 }
1006 } while(FALSE);
1007
1008 return 0;
1009 }
1010
1011 /*
1012 Options:
1013 -D Force debug output
1014 -fip_address Sets firewall to only allow from ip_address
1015 -Jwait,idle Jitter delay wait and idle in milliseconds
1016 -Mhost/ip Join multicast to given name or IP address
1017 -pport Listen on given port
1018 -Q Prevent debug output
1019 -U Print this message
1020 -x Full duplex audio
1021 -Youtdev[:ctldev] Audio device names
1022 -8 Set audio device to 8-bit mu-law
1023 */
EmulateIspeaker(char ** argv,int argc)1024 int EmulateIspeaker(char **argv,int argc)
1025 {
1026 InstallSigHandler();
1027 LogCmdLine(argv,argc);
1028
1029 while(!bShutdown) {
1030 sleep(1000);
1031 }
1032 printf("Exiting\n");
1033
1034 return 0;
1035 }
1036
1037 #define MAX_PLAY_WAIT 30
1038
EmulatePlay(char ** argv,int argc)1039 int EmulatePlay(char **argv,int argc)
1040 {
1041 int i;
1042 int j;
1043 char CmdLine[120];
1044 int Response;
1045
1046 LogCmdLine(argv,argc);
1047
1048 for(i = 1; i < argc; i++) {
1049 snprintf(CmdLine,sizeof(CmdLine),"say -c %s",argv[i]);
1050 if((Response = SendCommand(CmdLine)) != TBD_OK) {
1051 LOG_ERROR(("%s: tlb returned %d for \"%s\"\n",__FUNCTION__,
1052 Response,CmdLine));
1053 break;
1054 }
1055
1056 for(j = 0; j < MaxPlayWait; j++) {
1057 Response = SendCommand(NULL);
1058 if(Response == TBD_SAY_COMPLETE) {
1059 break;
1060 }
1061 else if(Response != -1) {
1062 LOG_ERROR(("%s: tlb returned %d for \"%s\"\n",__FUNCTION__,
1063 Response,CmdLine));
1064 }
1065 }
1066
1067 if(j == MaxPlayWait) {
1068 LOG_ERROR(("%s: timed out waiting for TBD_SAY_COMPLETE\n",__FUNCTION__));
1069 }
1070 }
1071
1072 return 0;
1073 }
1074
Substitute(char * String,char From,char To)1075 void Substitute(char *String,char From,char To)
1076 {
1077 char *cp = String;
1078
1079 while((cp = strchr(cp,From)) != NULL) {
1080 *cp++ = To;
1081 }
1082 }
1083
1084 //
EmulateDtmfRegen(char ** argv,int argc)1085 int EmulateDtmfRegen(char **argv,int argc)
1086 {
1087 char CmdLine[120];
1088 char *Station = NULL;
1089 char *Dtmf;
1090 int i;
1091
1092 LogCmdLine(argv,argc);
1093
1094 if((Station = GetIRLPCall()) == NULL) {
1095 Station = "IRLP";
1096 }
1097
1098 if(argc > 1) {
1099 Dtmf = strdup(argv[1]);
1100
1101 Substitute(Dtmf,'P','#');
1102 Substitute(Dtmf,'p','#');
1103 Substitute(Dtmf,'S','*');
1104 Substitute(Dtmf,'s','*');
1105
1106 snprintf(CmdLine,sizeof(CmdLine),"port %s; dtmfdecode %s",Station,Dtmf);
1107
1108 // EchoIRLP connects to localhost to talk to tbd, ignore it
1109 SendCommand(CmdLine);
1110 }
1111
1112 if(argc != 2) {
1113 LOG_ERROR(("Unexpected argument count (%d):\n",argc));
1114 for(i = 1; i < argc; i++) {
1115 LOG_ERROR((" %d: %s\n",i,argv[i]));
1116 }
1117 }
1118
1119 return 0;
1120 }
1121
Log(char * fmt,...)1122 void Log(char *fmt, ...)
1123 {
1124 int WriteLen;
1125 char Temp[1024];
1126 char Temp1[1024];
1127 time_t ltime;
1128 struct tm *tm;
1129 static int LastLogDay = -1;
1130 static int LastLogType;
1131 char *LogDir;
1132 va_list args;
1133 va_start(args,fmt);
1134
1135 time(<ime);
1136 tm = localtime(<ime);
1137
1138 if(LogFileRolloverType != 0 &&
1139 (LogFp == NULL ||
1140 tm->tm_mday != LastLogDay ||
1141 LastLogType != LogFileRolloverType))
1142 {
1143 LastLogType = LogFileRolloverType;
1144
1145 if(LogFp != NULL) {
1146 fclose(LogFp);
1147 LogFp = NULL;
1148 }
1149
1150 do {
1151 if((LogDir = getenv("TLB_HOME")) != NULL) {
1152 break;
1153 }
1154
1155 if((LogDir = getenv("LOG")) != NULL) {
1156 break;
1157 }
1158 LogDir = ".";
1159 } while(FALSE);
1160
1161 // Assume the name will be tbd.log
1162 snprintf(Temp,sizeof(Temp),"%s/%s.log",LogDir,AppName);
1163
1164 switch(LogFileRolloverType) {
1165 case 2: // daily log
1166 snprintf(Temp,sizeof(Temp),"%s/%s%02d%02d%02d.log",LogDir,AppName,
1167 tm->tm_mon+1,tm->tm_mday,tm->tm_year % 100);
1168 break;
1169
1170 case 3: // daily log
1171 if(LastLogDay != -1 && tm->tm_mday != LastLogDay) {
1172 // A new day is born, rename the old log to .bak and open a new log
1173 snprintf(Temp1,sizeof(Temp1),"%s/%s.bak",LogDir,AppName);
1174 unlink(Temp1);
1175 rename(Temp,Temp1);
1176 }
1177 break;
1178
1179 case 4: // weekly
1180 snprintf(Temp,sizeof(Temp1),"%s/%s",LogDir,
1181 LogNames[tm->tm_wday]);
1182 if(LastLogDay != -1 && tm->tm_mday != LastLogDay) {
1183 // delete last weeks log
1184 unlink(Temp);
1185 }
1186 break;
1187 }
1188
1189 LastLogDay = tm->tm_mday;
1190 LogFp = fopen(Temp,"a");
1191 }
1192
1193 WriteLen = vsnprintf(Temp,sizeof(Temp),fmt,args);
1194 va_end(args);
1195
1196 if(LogFp != NULL) {
1197 if(LogFileRolloverType == 1 || LogFileRolloverType == -1) {
1198 // one big file log date & time
1199 fprintf(LogFp,"%s %d %d:%02d:%02d %s",MonthNames[tm->tm_mon],
1200 tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec,Temp);
1201 }
1202 else {
1203 // Not one big file, just log time
1204 fprintf(LogFp,"%d:%02d:%02d %s",tm->tm_hour,tm->tm_min,
1205 tm->tm_sec,Temp);
1206 }
1207 fflush(LogFp);
1208
1209 if(ftell(LogFp) > 10000000) {
1210 // shit, get out of dodge
1211 fprintf(LogFp,"LOG FILE OVERFLOW, shutting down !\n");
1212 fclose(LogFp);
1213 LogFp = NULL;
1214 if(isatty(fileno(stdin))) {
1215 printf("LOG FILE OVERFLOW, shutting down !\n");
1216 }
1217 }
1218 }
1219 }
1220
SigHandler(int Signal)1221 void SigHandler(int Signal)
1222 {
1223 switch(Signal) {
1224 case SIGINT:
1225 LOG_NORM(("Received SIGINT, shutting down\n"));
1226 break;
1227
1228 case SIGTERM:
1229 LOG_NORM(("Received SIGTERM, shutting down\n"));
1230 break;
1231 }
1232
1233 if(Signal == SIGINT || Signal == SIGTERM) {
1234 // Try an orderly shutdown
1235 bShutdown = TRUE;
1236 }
1237 }
1238 #endif
1239
1240 // Mode:
1241 // 0 - startup, save initial settings, turn off ICANON and ECHO
1242 // 1 - disable ECHO, ICANON
1243 // 2 - enable ECHO
1244 // 3 - restore original terminal settings for exit
SetTerminalMode(int mode)1245 void SetTerminalMode(int mode)
1246 {
1247 #ifdef HAVE_LIBREADLINE
1248 struct termios options;
1249 static struct termios InitialMode;
1250
1251 if(bUseReadline) do {
1252 if(tcgetattr(fileno(stdin),&options) < 0) {
1253 printf("%s: Unable to read port configuration: %s",__FUNCTION__,
1254 strerror(errno));
1255 break;
1256 }
1257
1258 switch(mode) {
1259 case 0:
1260 InitialMode = options;
1261 // intentional fall through
1262
1263 case 1:
1264 options.c_lflag &= ~(ICANON | ECHO);
1265 break;
1266
1267 case 2:
1268 options.c_lflag |= ECHO;
1269 break;
1270
1271 case 3:
1272 if(HistoryPath != NULL) {
1273 write_history(HistoryPath);
1274 }
1275 options = InitialMode;
1276 break;
1277
1278 }
1279
1280 if(tcsetattr(fileno(stdin),TCSANOW,&options) < 0) {
1281 printf("%s: tcsetattr() failed: %s",__FUNCTION__,strerror(errno));
1282 }
1283 } while(FALSE);
1284 #endif
1285 }
1286
1287 #ifndef HAVE_GETOPT
1288
1289 /*
1290 * getopt a wonderful little function that handles the command line.
1291 * available courtesy of AT&T.
1292 */
getopt(int argc,char ** argv,char * opts)1293 int getopt(int argc,char **argv,char *opts)
1294 {
1295 static int sp = 1;
1296 register int c;
1297 register char *cp;
1298
1299 if(sp == 1)
1300 if(optind >= argc ||
1301 argv[optind][0] != '-' || argv[optind][1] == '\0')
1302 return(EOF);
1303 else if(strcmp(argv[optind], "--") == 0) {
1304 optind++;
1305 return(EOF);
1306 }
1307 optopt = c = argv[optind][sp];
1308 if(c == ':' || (cp=strchr(opts, c)) == NULL) {
1309 printf("%s: illegal option -- ", argv[0],c);
1310 if(argv[optind][++sp] == '\0') {
1311 optind++;
1312 sp = 1;
1313 }
1314 return('?');
1315 }
1316 if(*++cp == ':') {
1317 if(argv[optind][sp+1] != '\0')
1318 optarg = &argv[optind++][sp+1];
1319 else if(++optind >= argc) {
1320 printf("%s: option requires an argument -- ", argv[0],c);
1321 sp = 1;
1322 return('?');
1323 }
1324 else
1325 optarg = argv[optind++];
1326 sp = 1;
1327 }
1328 else {
1329 if(argv[optind][++sp] == '\0') {
1330 sp = 1;
1331 optind++;
1332 }
1333 optarg = NULL;
1334 }
1335 return(c);
1336 }
1337 #endif
1338