1 /*
2 * Chat -- a program for automatic session establishment (i.e. dial
3 * the phone and log in).
4 *
5 * Standard termination codes:
6 * 0 - successful completion of the script
7 * 1 - invalid argument, expect string too large, etc.
8 * 2 - error on an I/O operation or fatal error condition.
9 * 3 - timeout waiting for a simple string.
10 * 4 - the first string declared as "ABORT"
11 * 5 - the second string declared as "ABORT"
12 * 6 - ... and so on for successive ABORT strings.
13 *
14 * This software is in the public domain.
15 *
16 * -----------------
17 * added -T and -U option and \T and \U substitution to pass a phone
18 * number into chat script. Two are needed for some ISDN TA applications.
19 * Keith Dart <[email protected]>
20 *
21 *
22 * Added SAY keyword to send output to stderr.
23 * This allows to turn ECHO OFF and to output specific, user selected,
24 * text to give progress messages. This best works when stderr
25 * exists (i.e.: pppd in nodetach mode).
26 *
27 * Added HANGUP directives to allow for us to be called
28 * back. When HANGUP is set to NO, chat will not hangup at HUP signal.
29 * We rely on timeouts in that case.
30 *
31 * Added CLR_ABORT to clear previously set ABORT string. This has been
32 * dictated by the HANGUP above as "NO CARRIER" (for example) must be
33 * an ABORT condition until we know the other host is going to close
34 * the connection for call back. As soon as we have completed the
35 * first stage of the call back sequence, "NO CARRIER" is a valid, non
36 * fatal string. As soon as we got called back (probably get "CONNECT"),
37 * we should re-arm the ABORT "NO CARRIER". Hence the CLR_ABORT command.
38 * Note that CLR_ABORT packs the abort_strings[] array so that we do not
39 * have unused entries not being reclaimed.
40 *
41 * In the same vein as above, added CLR_REPORT keyword.
42 *
43 * Allow for comments. Line starting with '#' are comments and are
44 * ignored. If a '#' is to be expected as the first character, the
45 * expect string must be quoted.
46 *
47 *
48 * Francis Demierre <[email protected]>
49 * Thu May 15 17:15:40 MET DST 1997
50 *
51 *
52 * Added -r "report file" switch & REPORT keyword.
53 * Robert Geer <[email protected]>
54 *
55 * Added -s "use stderr" and -S "don't use syslog" switches.
56 * June 18, 1997
57 * Karl O. Pinc <[email protected]>
58 *
59 *
60 * Added -e "echo" switch & ECHO keyword
61 * Dick Streefland <[email protected]>
62 *
63 *
64 * Considerable updates and modifications by
65 * Al Longyear <[email protected]>
66 * Paul Mackerras <[email protected]>
67 *
68 *
69 * The original author is:
70 *
71 * Karl Fox <[email protected]>
72 * Morning Star Technologies, Inc.
73 * 1760 Zollinger Road
74 * Columbus, OH 43221
75 * (614)451-1883
76 *
77 *
78 */
79
80 #include <sys/cdefs.h>
81 #include <sys/types.h>
82 #include <sys/stat.h>
83 #include <ctype.h>
84 #include <errno.h>
85 #include <fcntl.h>
86 #include <signal.h>
87 #include <stdarg.h>
88 #include <stdio.h>
89 #include <stdlib.h>
90 #include <string.h>
91 #include <syslog.h>
92 #include <termios.h>
93 #include <time.h>
94 #include <unistd.h>
95
96 #define STR_LEN 1024
97
98 #ifndef SIGTYPE
99 #define SIGTYPE void
100 #endif
101
102 #ifndef O_NONBLOCK
103 #define O_NONBLOCK O_NDELAY
104 #endif
105
106 #define MAX_ABORTS 50
107 #define MAX_REPORTS 50
108 #define DEFAULT_CHAT_TIMEOUT 45
109
110 static int echo;
111 static int verbose;
112 static int to_log;
113 static int to_stderr;
114 static int Verbose;
115 static int quiet;
116 static int exit_code;
117 static FILE* report_fp;
118 static char *report_file;
119 static char *chat_file;
120 static char *phone_num;
121 static char *phone_num2;
122 static int timeout = DEFAULT_CHAT_TIMEOUT;
123
124 static char blank[] = "";
125
126 static int have_tty_parameters;
127
128 #define term_parms struct termios
129 #define get_term_param(param) tcgetattr(0, param)
130 #define set_term_param(param) tcsetattr(0, TCSANOW, param)
131 static struct termios saved_tty_parameters;
132
133 static char *abort_string[MAX_ABORTS], *fail_reason, fail_buffer[50];
134 static int n_aborts, abort_next, timeout_next, echo_next;
135 static int clear_abort_next;
136
137 static char *report_string[MAX_REPORTS];
138 static char report_buffer[50];
139 static int n_reports, report_next, report_gathering;
140 static int clear_report_next;
141
142 static int say_next, hup_next;
143
144 void *dup_mem(void *b, size_t c);
145 void *copy_of(char *s);
146 static void usage(void) __dead2;
147 void chat_logf(const char *fmt, ...);
148 void fatal(int code, const char *fmt, ...);
149 SIGTYPE sigalrm(int signo);
150 SIGTYPE sigint(int signo);
151 SIGTYPE sigterm(int signo);
152 SIGTYPE sighup(int signo);
153 void init(void);
154 void set_tty_parameters(void);
155 void echo_stderr(int);
156 void break_sequence(void);
157 void terminate(int status);
158 void do_file(char *chatfile);
159 int get_string(char *string);
160 int put_string(char *s);
161 int write_char(int c);
162 int put_char(int c);
163 int get_char(void);
164 void chat_send(char *s);
165 char *character(int c);
166 void chat_expect(char *s);
167 char *clean(char *s, int sending);
168 void pack_array(char **array, int end);
169 char *expect_strtok(char *, const char *);
170 int vfmtmsg(char *, int, const char *, va_list); /* vsprintf++ */
171
172 void *
dup_mem(void * b,size_t c)173 dup_mem(void *b, size_t c)
174 {
175 void *ans = malloc (c);
176 if (!ans)
177 fatal(2, "memory error!");
178
179 memcpy (ans, b, c);
180 return ans;
181 }
182
183 void *
copy_of(char * s)184 copy_of(char *s)
185 {
186 return dup_mem (s, strlen (s) + 1);
187 }
188
189 /*
190 * chat [-esSvV] [-f chat-file] [-r report-file] [-t timeout]
191 * [-T phone-number] [-U phone-number2] [chat-script]
192 * where chat-script has the form:
193 * [...[[expect[-send[-expect...]] send expect[-send[-expect]] ...]]]
194 *
195 * Perform a UUCP-dialer-like chat script on stdin and stdout.
196 */
197 int
main(int argc,char * argv[])198 main(int argc, char *argv[])
199 {
200 int option;
201
202 tzset();
203
204 while ((option = getopt(argc, argv, "ef:r:sSt:T:U:vV")) != -1) {
205 switch (option) {
206 case 'e':
207 ++echo;
208 break;
209
210 case 'f':
211 if (chat_file != NULL)
212 free(chat_file);
213 chat_file = copy_of(optarg);
214 break;
215
216 case 'r':
217 if (report_fp != NULL)
218 fclose(report_fp);
219 if (report_file != NULL)
220 free(report_file);
221 report_file = copy_of(optarg);
222 report_fp = fopen(report_file, "a");
223 if (report_fp != NULL) {
224 if (verbose)
225 fprintf(report_fp, "Opening \"%s\"...\n", report_file);
226 } else
227 fatal(2, "cannot open \"%s\" for appending", report_file);
228 break;
229
230 case 's':
231 ++to_stderr;
232 break;
233
234 case 'S':
235 to_log = 0;
236 break;
237
238 case 't':
239 timeout = atoi(optarg);
240 break;
241
242 case 'T':
243 if (phone_num != NULL)
244 free(phone_num);
245 phone_num = copy_of(optarg);
246 break;
247
248 case 'U':
249 if (phone_num2 != NULL)
250 free(phone_num2);
251 phone_num2 = copy_of(optarg);
252 break;
253
254 case 'v':
255 ++verbose;
256 break;
257
258 case 'V':
259 ++Verbose;
260 break;
261
262 default:
263 usage();
264 break;
265 }
266 }
267
268 argc -= optind;
269 argv += optind;
270
271 /*
272 * Default the report file to the stderr location
273 */
274 if (report_fp == NULL)
275 report_fp = stderr;
276
277 if (to_log) {
278 openlog("chat", LOG_PID | LOG_NDELAY, LOG_LOCAL2);
279
280 if (verbose)
281 setlogmask(LOG_UPTO(LOG_INFO));
282 else
283 setlogmask(LOG_UPTO(LOG_WARNING));
284 }
285
286 if (chat_file != NULL) {
287 if (*argv != NULL)
288 usage();
289 else {
290 init();
291 do_file(chat_file);
292 }
293 } else {
294 init();
295 while (*argv != NULL && argc > 0) {
296 chat_expect(*argv);
297 argv++;
298 argc--;
299
300 if (*argv != NULL && argc > 0) {
301 chat_send(*argv);
302 argv++;
303 argc--;
304 }
305 }
306 }
307
308 terminate(0);
309 return 0;
310 }
311
312 /*
313 * Process a chat script when read from a file.
314 */
315
316 void
do_file(char * chatfile)317 do_file(char *chatfile)
318 {
319 int linect, sendflg;
320 char *sp, *arg, quote;
321 char buf [STR_LEN];
322 FILE *cfp;
323
324 cfp = fopen (chatfile, "r");
325 if (cfp == NULL)
326 fatal(1, "%s -- open failed: %m", chatfile);
327
328 linect = 0;
329 sendflg = 0;
330
331 while (fgets(buf, STR_LEN, cfp) != NULL) {
332 sp = strchr (buf, '\n');
333 if (sp)
334 *sp = '\0';
335
336 linect++;
337 sp = buf;
338
339 /* lines starting with '#' are comments. If a real '#'
340 is to be expected, it should be quoted .... */
341 if ( *sp == '#' )
342 continue;
343
344 while (*sp != '\0') {
345 if (*sp == ' ' || *sp == '\t') {
346 ++sp;
347 continue;
348 }
349
350 if (*sp == '"' || *sp == '\'') {
351 quote = *sp++;
352 arg = sp;
353 while (*sp != quote) {
354 if (*sp == '\0')
355 fatal(1, "unterminated quote (line %d)", linect);
356
357 if (*sp++ == '\\') {
358 if (*sp != '\0')
359 ++sp;
360 }
361 }
362 }
363 else {
364 arg = sp;
365 while (*sp != '\0' && *sp != ' ' && *sp != '\t')
366 ++sp;
367 }
368
369 if (*sp != '\0')
370 *sp++ = '\0';
371
372 if (sendflg)
373 chat_send (arg);
374 else
375 chat_expect (arg);
376 sendflg = !sendflg;
377 }
378 }
379 fclose (cfp);
380 }
381
382 /*
383 * We got an error parsing the command line.
384 */
385 static void
usage(void)386 usage(void)
387 {
388 fprintf(stderr,
389 "Usage: chat [-esSvV] [-f chat-file] [-r report-file] [-t timeout]\n"
390 " [-T phone-number] [-U phone-number2] [chat-script]\n"
391 "where chat-script has the form:\n"
392 " [...[[expect[-send[-expect...]] send expect[-send[-expect]] ...]]]\n");
393 exit(1);
394 }
395
396 /*
397 * Send a message to syslog and/or stderr.
398 */
399 void
chat_logf(const char * fmt,...)400 chat_logf(const char *fmt, ...)
401 {
402 char line[1024];
403 va_list args;
404
405 va_start(args, fmt);
406 vfmtmsg(line, sizeof(line), fmt, args);
407 va_end(args);
408 if (to_log)
409 syslog(LOG_INFO, "%s", line);
410 if (to_stderr)
411 fprintf(stderr, "%s\n", line);
412 }
413
414 /*
415 * Print an error message and terminate.
416 */
417
418 void
fatal(int code,const char * fmt,...)419 fatal(int code, const char *fmt, ...)
420 {
421 char line[1024];
422 va_list args;
423
424 va_start(args, fmt);
425 vfmtmsg(line, sizeof(line), fmt, args);
426 va_end(args);
427 if (to_log)
428 syslog(LOG_ERR, "%s", line);
429 if (to_stderr)
430 fprintf(stderr, "%s\n", line);
431 terminate(code);
432 }
433
434 static int alarmed;
435
sigalrm(int signo __unused)436 SIGTYPE sigalrm(int signo __unused)
437 {
438 int flags;
439
440 alarm(1);
441 alarmed = 1; /* Reset alarm to avoid race window */
442 signal(SIGALRM, sigalrm); /* that can cause hanging in read() */
443
444 if ((flags = fcntl(0, F_GETFL, 0)) == -1)
445 fatal(2, "Can't get file mode flags on stdin: %m");
446
447 if (fcntl(0, F_SETFL, flags | O_NONBLOCK) == -1)
448 fatal(2, "Can't set file mode flags on stdin: %m");
449
450 if (verbose)
451 chat_logf("alarm");
452 }
453
sigint(int signo __unused)454 SIGTYPE sigint(int signo __unused)
455 {
456 fatal(2, "SIGINT");
457 }
458
sigterm(int signo __unused)459 SIGTYPE sigterm(int signo __unused)
460 {
461 fatal(2, "SIGTERM");
462 }
463
sighup(int signo __unused)464 SIGTYPE sighup(int signo __unused)
465 {
466 fatal(2, "SIGHUP");
467 }
468
init(void)469 void init(void)
470 {
471 signal(SIGINT, sigint);
472 signal(SIGTERM, sigterm);
473 signal(SIGHUP, sighup);
474
475 set_tty_parameters();
476 signal(SIGALRM, sigalrm);
477 alarm(0);
478 alarmed = 0;
479 }
480
set_tty_parameters(void)481 void set_tty_parameters(void)
482 {
483 #if defined(get_term_param)
484 term_parms t;
485
486 if (get_term_param (&t) < 0)
487 fatal(2, "Can't get terminal parameters: %m");
488
489 saved_tty_parameters = t;
490 have_tty_parameters = 1;
491
492 t.c_iflag |= IGNBRK | ISTRIP | IGNPAR;
493 t.c_oflag = 0;
494 t.c_lflag = 0;
495 t.c_cc[VERASE] =
496 t.c_cc[VKILL] = 0;
497 t.c_cc[VMIN] = 1;
498 t.c_cc[VTIME] = 0;
499
500 if (set_term_param (&t) < 0)
501 fatal(2, "Can't set terminal parameters: %m");
502 #endif
503 }
504
break_sequence(void)505 void break_sequence(void)
506 {
507 tcsendbreak (0, 0);
508 }
509
terminate(int status)510 void terminate(int status)
511 {
512 echo_stderr(-1);
513 if (report_file != (char *) 0 && report_fp != (FILE *) NULL) {
514 /*
515 * Allow the last of the report string to be gathered before we terminate.
516 */
517 if (report_gathering) {
518 int c;
519 size_t rep_len;
520
521 rep_len = strlen(report_buffer);
522 while (rep_len + 1 < sizeof(report_buffer)) {
523 alarm(1);
524 c = get_char();
525 alarm(0);
526 if (c < 0 || iscntrl(c))
527 break;
528 report_buffer[rep_len] = c;
529 ++rep_len;
530 }
531 report_buffer[rep_len] = 0;
532 fprintf (report_fp, "chat: %s\n", report_buffer);
533 }
534 if (verbose)
535 fprintf (report_fp, "Closing \"%s\".\n", report_file);
536 fclose (report_fp);
537 report_fp = (FILE *) NULL;
538 }
539
540 #if defined(get_term_param)
541 if (have_tty_parameters) {
542 if (set_term_param (&saved_tty_parameters) < 0)
543 fatal(2, "Can't restore terminal parameters: %m");
544 }
545 #endif
546
547 exit(status);
548 }
549
550 /*
551 * 'Clean up' this string.
552 */
553 char *
clean(char * s,int sending)554 clean(char *s, int sending)
555 {
556 char temp[STR_LEN], cur_chr;
557 char *s1, *phchar;
558 int add_return = sending;
559 #define isoctal(chr) (((chr) >= '0') && ((chr) <= '7'))
560
561 s1 = temp;
562 /* Don't overflow buffer, leave room for chars we append later */
563 while (*s && s1 - temp < (off_t)(sizeof(temp) - 2 - add_return)) {
564 cur_chr = *s++;
565 if (cur_chr == '^') {
566 cur_chr = *s++;
567 if (cur_chr == '\0') {
568 *s1++ = '^';
569 break;
570 }
571 cur_chr &= 0x1F;
572 if (cur_chr != 0) {
573 *s1++ = cur_chr;
574 }
575 continue;
576 }
577
578 if (cur_chr != '\\') {
579 *s1++ = cur_chr;
580 continue;
581 }
582
583 cur_chr = *s++;
584 if (cur_chr == '\0') {
585 if (sending) {
586 *s1++ = '\\';
587 *s1++ = '\\';
588 }
589 break;
590 }
591
592 switch (cur_chr) {
593 case 'b':
594 *s1++ = '\b';
595 break;
596
597 case 'c':
598 if (sending && *s == '\0')
599 add_return = 0;
600 else
601 *s1++ = cur_chr;
602 break;
603
604 case '\\':
605 case 'K':
606 case 'p':
607 case 'd':
608 if (sending)
609 *s1++ = '\\';
610
611 *s1++ = cur_chr;
612 break;
613
614 case 'T':
615 if (sending && phone_num) {
616 for ( phchar = phone_num; *phchar != '\0'; phchar++)
617 *s1++ = *phchar;
618 }
619 else {
620 *s1++ = '\\';
621 *s1++ = 'T';
622 }
623 break;
624
625 case 'U':
626 if (sending && phone_num2) {
627 for ( phchar = phone_num2; *phchar != '\0'; phchar++)
628 *s1++ = *phchar;
629 }
630 else {
631 *s1++ = '\\';
632 *s1++ = 'U';
633 }
634 break;
635
636 case 'q':
637 quiet = 1;
638 break;
639
640 case 'r':
641 *s1++ = '\r';
642 break;
643
644 case 'n':
645 *s1++ = '\n';
646 break;
647
648 case 's':
649 *s1++ = ' ';
650 break;
651
652 case 't':
653 *s1++ = '\t';
654 break;
655
656 case 'N':
657 if (sending) {
658 *s1++ = '\\';
659 *s1++ = '\0';
660 }
661 else
662 *s1++ = 'N';
663 break;
664
665 default:
666 if (isoctal (cur_chr)) {
667 cur_chr &= 0x07;
668 if (isoctal (*s)) {
669 cur_chr <<= 3;
670 cur_chr |= *s++ - '0';
671 if (isoctal (*s)) {
672 cur_chr <<= 3;
673 cur_chr |= *s++ - '0';
674 }
675 }
676
677 if (cur_chr != 0 || sending) {
678 if (sending && (cur_chr == '\\' || cur_chr == 0))
679 *s1++ = '\\';
680 *s1++ = cur_chr;
681 }
682 break;
683 }
684
685 if (sending)
686 *s1++ = '\\';
687 *s1++ = cur_chr;
688 break;
689 }
690 }
691
692 if (add_return)
693 *s1++ = '\r';
694
695 *s1++ = '\0'; /* guarantee closure */
696 *s1++ = '\0'; /* terminate the string */
697 return dup_mem (temp, (size_t) (s1 - temp)); /* may have embedded nuls */
698 }
699
700 /*
701 * A modified version of 'strtok'. This version skips \ sequences.
702 */
703
704 char *
expect_strtok(char * s,const char * term)705 expect_strtok (char *s, const char *term)
706 {
707 static char *str = blank;
708 int escape_flag = 0;
709 char *result;
710
711 /*
712 * If a string was specified then do initial processing.
713 */
714 if (s)
715 str = s;
716
717 /*
718 * If this is the escape flag then reset it and ignore the character.
719 */
720 if (*str)
721 result = str;
722 else
723 result = (char *) 0;
724
725 while (*str) {
726 if (escape_flag) {
727 escape_flag = 0;
728 ++str;
729 continue;
730 }
731
732 if (*str == '\\') {
733 ++str;
734 escape_flag = 1;
735 continue;
736 }
737
738 /*
739 * If this is not in the termination string, continue.
740 */
741 if (strchr (term, *str) == (char *) 0) {
742 ++str;
743 continue;
744 }
745
746 /*
747 * This is the terminator. Mark the end of the string and stop.
748 */
749 *str++ = '\0';
750 break;
751 }
752 return (result);
753 }
754
755 /*
756 * Process the expect string
757 */
758
759 void
chat_expect(char * s)760 chat_expect(char *s)
761 {
762 char *expect;
763 char *reply;
764
765 if (strcmp(s, "HANGUP") == 0) {
766 ++hup_next;
767 return;
768 }
769
770 if (strcmp(s, "ABORT") == 0) {
771 ++abort_next;
772 return;
773 }
774
775 if (strcmp(s, "CLR_ABORT") == 0) {
776 ++clear_abort_next;
777 return;
778 }
779
780 if (strcmp(s, "REPORT") == 0) {
781 ++report_next;
782 return;
783 }
784
785 if (strcmp(s, "CLR_REPORT") == 0) {
786 ++clear_report_next;
787 return;
788 }
789
790 if (strcmp(s, "TIMEOUT") == 0) {
791 ++timeout_next;
792 return;
793 }
794
795 if (strcmp(s, "ECHO") == 0) {
796 ++echo_next;
797 return;
798 }
799
800 if (strcmp(s, "SAY") == 0) {
801 ++say_next;
802 return;
803 }
804
805 /*
806 * Fetch the expect and reply string.
807 */
808 for (;;) {
809 expect = expect_strtok (s, "-");
810 s = (char *) 0;
811
812 if (expect == (char *) 0)
813 return;
814
815 reply = expect_strtok (s, "-");
816
817 /*
818 * Handle the expect string. If successful then exit.
819 */
820 if (get_string (expect))
821 return;
822
823 /*
824 * If there is a sub-reply string then send it. Otherwise any condition
825 * is terminal.
826 */
827 if (reply == (char *) 0 || exit_code != 3)
828 break;
829
830 chat_send (reply);
831 }
832
833 /*
834 * The expectation did not occur. This is terminal.
835 */
836 if (fail_reason)
837 chat_logf("Failed (%s)", fail_reason);
838 else
839 chat_logf("Failed");
840 terminate(exit_code);
841 }
842
843 /*
844 * Translate the input character to the appropriate string for printing
845 * the data.
846 */
847
848 char *
character(int c)849 character(int c)
850 {
851 static char string[10];
852 const char *meta;
853
854 meta = (c & 0x80) ? "M-" : "";
855 c &= 0x7F;
856
857 if (c < 32)
858 sprintf(string, "%s^%c", meta, (int)c + '@');
859 else if (c == 127)
860 sprintf(string, "%s^?", meta);
861 else
862 sprintf(string, "%s%c", meta, c);
863
864 return (string);
865 }
866
867 /*
868 * process the reply string
869 */
870 void
chat_send(char * s)871 chat_send(char *s)
872 {
873 if (say_next) {
874 say_next = 0;
875 s = clean(s,0);
876 write(STDERR_FILENO, s, strlen(s));
877 free(s);
878 return;
879 }
880
881 if (hup_next) {
882 hup_next = 0;
883 if (strcmp(s, "OFF") == 0)
884 signal(SIGHUP, SIG_IGN);
885 else
886 signal(SIGHUP, sighup);
887 return;
888 }
889
890 if (echo_next) {
891 echo_next = 0;
892 echo = (strcmp(s, "ON") == 0);
893 return;
894 }
895
896 if (abort_next) {
897 char *s1;
898
899 abort_next = 0;
900
901 if (n_aborts >= MAX_ABORTS)
902 fatal(2, "Too many ABORT strings");
903
904 s1 = clean(s, 0);
905
906 if (strlen(s1) > strlen(s)
907 || strlen(s1) + 1 > sizeof(fail_buffer))
908 fatal(1, "Illegal or too-long ABORT string ('%v')", s);
909
910 abort_string[n_aborts++] = s1;
911
912 if (verbose)
913 chat_logf("abort on (%v)", s);
914 return;
915 }
916
917 if (clear_abort_next) {
918 char *s1;
919 int i;
920 int old_max;
921 int pack = 0;
922
923 clear_abort_next = 0;
924
925 s1 = clean(s, 0);
926
927 if (strlen(s1) > strlen(s)
928 || strlen(s1) + 1 > sizeof(fail_buffer))
929 fatal(1, "Illegal or too-long CLR_ABORT string ('%v')", s);
930
931 old_max = n_aborts;
932 for (i=0; i < n_aborts; i++) {
933 if ( strcmp(s1,abort_string[i]) == 0 ) {
934 free(abort_string[i]);
935 abort_string[i] = NULL;
936 pack++;
937 n_aborts--;
938 if (verbose)
939 chat_logf("clear abort on (%v)", s);
940 }
941 }
942 free(s1);
943 if (pack)
944 pack_array(abort_string,old_max);
945 return;
946 }
947
948 if (report_next) {
949 char *s1;
950
951 report_next = 0;
952 if (n_reports >= MAX_REPORTS)
953 fatal(2, "Too many REPORT strings");
954
955 s1 = clean(s, 0);
956
957 if (strlen(s1) > strlen(s) || strlen(s1) > sizeof fail_buffer - 1)
958 fatal(1, "Illegal or too-long REPORT string ('%v')", s);
959
960 report_string[n_reports++] = s1;
961
962 if (verbose)
963 chat_logf("report (%v)", s);
964 return;
965 }
966
967 if (clear_report_next) {
968 char *s1;
969 int i;
970 int old_max;
971 int pack = 0;
972
973 clear_report_next = 0;
974
975 s1 = clean(s, 0);
976
977 if (strlen(s1) > strlen(s) || strlen(s1) > sizeof fail_buffer - 1)
978 fatal(1, "Illegal or too-long REPORT string ('%v')", s);
979
980 old_max = n_reports;
981 for (i=0; i < n_reports; i++) {
982 if ( strcmp(s1,report_string[i]) == 0 ) {
983 free(report_string[i]);
984 report_string[i] = NULL;
985 pack++;
986 n_reports--;
987 if (verbose)
988 chat_logf("clear report (%v)", s);
989 }
990 }
991 free(s1);
992 if (pack)
993 pack_array(report_string,old_max);
994
995 return;
996 }
997
998 if (timeout_next) {
999 timeout_next = 0;
1000 timeout = atoi(s);
1001
1002 if (timeout <= 0)
1003 timeout = DEFAULT_CHAT_TIMEOUT;
1004
1005 if (verbose)
1006 chat_logf("timeout set to %d seconds", timeout);
1007
1008 return;
1009 }
1010
1011 if (strcmp(s, "EOT") == 0)
1012 s = strdup("^D\\c");
1013 else if (strcmp(s, "BREAK") == 0)
1014 s = strdup("\\K\\c");
1015
1016 if (!put_string(s))
1017 fatal(1, "Failed");
1018 }
1019
1020 int
get_char(void)1021 get_char(void)
1022 {
1023 int status;
1024 char c;
1025
1026 status = read(STDIN_FILENO, &c, 1);
1027
1028 switch (status) {
1029 case 1:
1030 return ((int)c & 0x7F);
1031
1032 default:
1033 chat_logf("warning: read() on stdin returned %d", status);
1034
1035 case -1:
1036 if ((status = fcntl(0, F_GETFL, 0)) == -1)
1037 fatal(2, "Can't get file mode flags on stdin: %m");
1038
1039 if (fcntl(0, F_SETFL, status & ~O_NONBLOCK) == -1)
1040 fatal(2, "Can't set file mode flags on stdin: %m");
1041
1042 return (-1);
1043 }
1044 }
1045
put_char(int c)1046 int put_char(int c)
1047 {
1048 int status;
1049 char ch = c;
1050
1051 usleep(10000); /* inter-character typing delay (?) */
1052
1053 status = write(STDOUT_FILENO, &ch, 1);
1054
1055 switch (status) {
1056 case 1:
1057 return (0);
1058
1059 default:
1060 chat_logf("warning: write() on stdout returned %d", status);
1061
1062 case -1:
1063 if ((status = fcntl(0, F_GETFL, 0)) == -1)
1064 fatal(2, "Can't get file mode flags on stdin, %m");
1065
1066 if (fcntl(0, F_SETFL, status & ~O_NONBLOCK) == -1)
1067 fatal(2, "Can't set file mode flags on stdin: %m");
1068
1069 return (-1);
1070 }
1071 }
1072
1073 int
write_char(int c)1074 write_char(int c)
1075 {
1076 if (alarmed || put_char(c) < 0) {
1077 alarm(0);
1078 alarmed = 0;
1079
1080 if (verbose) {
1081 if (errno == EINTR || errno == EWOULDBLOCK)
1082 chat_logf(" -- write timed out");
1083 else
1084 chat_logf(" -- write failed: %m");
1085 }
1086 return (0);
1087 }
1088 return (1);
1089 }
1090
1091 int
put_string(char * s)1092 put_string(char *s)
1093 {
1094 quiet = 0;
1095 s = clean(s, 1);
1096
1097 if (verbose)
1098 chat_logf("send (%v)", quiet ? "??????" : s);
1099
1100 alarm(timeout); alarmed = 0;
1101
1102 while (*s) {
1103 char c = *s++;
1104
1105 if (c != '\\') {
1106 if (!write_char (c))
1107 return 0;
1108 continue;
1109 }
1110
1111 c = *s++;
1112 switch (c) {
1113 case 'd':
1114 sleep(1);
1115 break;
1116
1117 case 'K':
1118 break_sequence();
1119 break;
1120
1121 case 'p':
1122 usleep(10000); /* 1/100th of a second (arg is microseconds) */
1123 break;
1124
1125 default:
1126 if (!write_char (c))
1127 return 0;
1128 break;
1129 }
1130 }
1131
1132 alarm(0);
1133 alarmed = 0;
1134 return (1);
1135 }
1136
1137 /*
1138 * Echo a character to stderr.
1139 * When called with -1, a '\n' character is generated when
1140 * the cursor is not at the beginning of a line.
1141 */
1142 void
echo_stderr(int n)1143 echo_stderr(int n)
1144 {
1145 static int need_lf;
1146 char *s;
1147
1148 switch (n) {
1149 case '\r': /* ignore '\r' */
1150 break;
1151 case -1:
1152 if (need_lf == 0)
1153 break;
1154 /* FALLTHROUGH */
1155 case '\n':
1156 write(STDERR_FILENO, "\n", 1);
1157 need_lf = 0;
1158 break;
1159 default:
1160 s = character(n);
1161 write(STDERR_FILENO, s, strlen(s));
1162 need_lf = 1;
1163 break;
1164 }
1165 }
1166
1167 /*
1168 * 'Wait for' this string to appear on this file descriptor.
1169 */
1170 int
get_string(char * string)1171 get_string(char *string)
1172 {
1173 char temp[STR_LEN];
1174 int c;
1175 size_t len, minlen;
1176 char *s = temp, *end = s + STR_LEN;
1177 char *logged = temp;
1178
1179 fail_reason = (char *)0;
1180
1181 if (strlen(string) > STR_LEN) {
1182 chat_logf("expect string is too long");
1183 exit_code = 1;
1184 return 0;
1185 }
1186
1187 string = clean(string, 0);
1188 len = strlen(string);
1189 minlen = (len > sizeof(fail_buffer)? len: sizeof(fail_buffer)) - 1;
1190
1191 if (verbose)
1192 chat_logf("expect (%v)", string);
1193
1194 if (len == 0) {
1195 if (verbose)
1196 chat_logf("got it");
1197 return (1);
1198 }
1199
1200 alarm(timeout);
1201 alarmed = 0;
1202
1203 while ( ! alarmed && (c = get_char()) >= 0) {
1204 int n, abort_len, report_len;
1205
1206 if (echo)
1207 echo_stderr(c);
1208 if (verbose && c == '\n') {
1209 if (s == logged)
1210 chat_logf(""); /* blank line */
1211 else
1212 chat_logf("%0.*v", s - logged, logged);
1213 logged = s + 1;
1214 }
1215
1216 *s++ = c;
1217
1218 if (verbose && s >= logged + 80) {
1219 chat_logf("%0.*v", s - logged, logged);
1220 logged = s;
1221 }
1222
1223 if (Verbose) {
1224 if (c == '\n')
1225 fputc( '\n', stderr );
1226 else if (c != '\r')
1227 fprintf( stderr, "%s", character(c) );
1228 }
1229
1230 if (!report_gathering) {
1231 for (n = 0; n < n_reports; ++n) {
1232 if ((report_string[n] != (char*) NULL) &&
1233 s - temp >= (report_len = strlen(report_string[n])) &&
1234 strncmp(s - report_len, report_string[n], report_len) == 0) {
1235 time_t time_now = time ((time_t*) NULL);
1236 struct tm* tm_now = localtime (&time_now);
1237
1238 strftime (report_buffer, 20, "%b %d %H:%M:%S ", tm_now);
1239 strcat (report_buffer, report_string[n]);
1240
1241 report_string[n] = (char *) NULL;
1242 report_gathering = 1;
1243 break;
1244 }
1245 }
1246 }
1247 else {
1248 if (!iscntrl (c)) {
1249 int rep_len = strlen (report_buffer);
1250 report_buffer[rep_len] = c;
1251 report_buffer[rep_len + 1] = '\0';
1252 }
1253 else {
1254 report_gathering = 0;
1255 fprintf (report_fp, "chat: %s\n", report_buffer);
1256 }
1257 }
1258
1259 if ((size_t)(s - temp) >= len &&
1260 c == string[len - 1] &&
1261 strncmp(s - len, string, len) == 0) {
1262 if (verbose) {
1263 if (s > logged)
1264 chat_logf("%0.*v", s - logged, logged);
1265 chat_logf(" -- got it\n");
1266 }
1267
1268 alarm(0);
1269 alarmed = 0;
1270 return (1);
1271 }
1272
1273 for (n = 0; n < n_aborts; ++n) {
1274 if (s - temp >= (abort_len = strlen(abort_string[n])) &&
1275 strncmp(s - abort_len, abort_string[n], abort_len) == 0) {
1276 if (verbose) {
1277 if (s > logged)
1278 chat_logf("%0.*v", s - logged, logged);
1279 chat_logf(" -- failed");
1280 }
1281
1282 alarm(0);
1283 alarmed = 0;
1284 exit_code = n + 4;
1285 strcpy(fail_reason = fail_buffer, abort_string[n]);
1286 return (0);
1287 }
1288 }
1289
1290 if (s >= end) {
1291 if (logged < s - minlen) {
1292 chat_logf("%0.*v", s - logged, logged);
1293 logged = s;
1294 }
1295 s -= minlen;
1296 memmove(temp, s, minlen);
1297 logged = temp + (logged - s);
1298 s = temp + minlen;
1299 }
1300
1301 if (alarmed && verbose)
1302 chat_logf("warning: alarm synchronization problem");
1303 }
1304
1305 alarm(0);
1306
1307 exit_code = 3;
1308 alarmed = 0;
1309 return (0);
1310 }
1311
1312 void
pack_array(char ** array,int end)1313 pack_array(char **array, int end)
1314 {
1315 int i, j;
1316
1317 for (i = 0; i < end; i++) {
1318 if (array[i] == NULL) {
1319 for (j = i+1; j < end; ++j)
1320 if (array[j] != NULL)
1321 array[i++] = array[j];
1322 for (; i < end; ++i)
1323 array[i] = NULL;
1324 break;
1325 }
1326 }
1327 }
1328
1329 /*
1330 * vfmtmsg - format a message into a buffer. Like vsprintf except we
1331 * also specify the length of the output buffer, and we handle the
1332 * %m (error message) format.
1333 * Doesn't do floating-point formats.
1334 * Returns the number of chars put into buf.
1335 */
1336 #define OUTCHAR(c) (buflen > 0? (--buflen, *buf++ = (c)): 0)
1337
1338 int
vfmtmsg(char * buf,int buflen,const char * fmt,va_list args)1339 vfmtmsg(char *buf, int buflen, const char *fmt, va_list args)
1340 {
1341 int c, i, n;
1342 int width, prec, fillch;
1343 int base, len, neg, quoted;
1344 unsigned long val = 0;
1345 char *str, *buf0;
1346 const char *f;
1347 unsigned char *p;
1348 char num[32];
1349 static char hexchars[] = "0123456789abcdef";
1350
1351 buf0 = buf;
1352 --buflen;
1353 while (buflen > 0) {
1354 for (f = fmt; *f != '%' && *f != 0; ++f)
1355 ;
1356 if (f > fmt) {
1357 len = f - fmt;
1358 if (len > buflen)
1359 len = buflen;
1360 memcpy(buf, fmt, len);
1361 buf += len;
1362 buflen -= len;
1363 fmt = f;
1364 }
1365 if (*fmt == 0)
1366 break;
1367 c = *++fmt;
1368 width = prec = 0;
1369 fillch = ' ';
1370 if (c == '0') {
1371 fillch = '0';
1372 c = *++fmt;
1373 }
1374 if (c == '*') {
1375 width = va_arg(args, int);
1376 c = *++fmt;
1377 } else {
1378 while (isdigit(c)) {
1379 width = width * 10 + c - '0';
1380 c = *++fmt;
1381 }
1382 }
1383 if (c == '.') {
1384 c = *++fmt;
1385 if (c == '*') {
1386 prec = va_arg(args, int);
1387 c = *++fmt;
1388 } else {
1389 while (isdigit(c)) {
1390 prec = prec * 10 + c - '0';
1391 c = *++fmt;
1392 }
1393 }
1394 }
1395 str = NULL;
1396 base = 0;
1397 neg = 0;
1398 ++fmt;
1399 switch (c) {
1400 case 'd':
1401 i = va_arg(args, int);
1402 if (i < 0) {
1403 neg = 1;
1404 val = -i;
1405 } else
1406 val = i;
1407 base = 10;
1408 break;
1409 case 'o':
1410 val = va_arg(args, unsigned int);
1411 base = 8;
1412 break;
1413 case 'x':
1414 val = va_arg(args, unsigned int);
1415 base = 16;
1416 break;
1417 case 'p':
1418 val = (unsigned long) va_arg(args, void *);
1419 base = 16;
1420 neg = 2;
1421 break;
1422 case 's':
1423 str = va_arg(args, char *);
1424 break;
1425 case 'c':
1426 num[0] = va_arg(args, int);
1427 num[1] = 0;
1428 str = num;
1429 break;
1430 case 'm':
1431 str = strerror(errno);
1432 break;
1433 case 'v': /* "visible" string */
1434 case 'q': /* quoted string */
1435 quoted = c == 'q';
1436 p = va_arg(args, unsigned char *);
1437 if (fillch == '0' && prec > 0) {
1438 n = prec;
1439 } else {
1440 n = strlen((char *)p);
1441 if (prec > 0 && prec < n)
1442 n = prec;
1443 }
1444 while (n > 0 && buflen > 0) {
1445 c = *p++;
1446 --n;
1447 if (!quoted && c >= 0x80) {
1448 OUTCHAR('M');
1449 OUTCHAR('-');
1450 c -= 0x80;
1451 }
1452 if (quoted && (c == '"' || c == '\\'))
1453 OUTCHAR('\\');
1454 if (c < 0x20 || (0x7f <= c && c < 0xa0)) {
1455 if (quoted) {
1456 OUTCHAR('\\');
1457 switch (c) {
1458 case '\t': OUTCHAR('t'); break;
1459 case '\n': OUTCHAR('n'); break;
1460 case '\b': OUTCHAR('b'); break;
1461 case '\f': OUTCHAR('f'); break;
1462 default:
1463 OUTCHAR('x');
1464 OUTCHAR(hexchars[c >> 4]);
1465 OUTCHAR(hexchars[c & 0xf]);
1466 }
1467 } else {
1468 if (c == '\t')
1469 OUTCHAR(c);
1470 else {
1471 OUTCHAR('^');
1472 OUTCHAR(c ^ 0x40);
1473 }
1474 }
1475 } else
1476 OUTCHAR(c);
1477 }
1478 continue;
1479 default:
1480 *buf++ = '%';
1481 if (c != '%')
1482 --fmt; /* so %z outputs %z etc. */
1483 --buflen;
1484 continue;
1485 }
1486 if (base != 0) {
1487 str = num + sizeof(num);
1488 *--str = 0;
1489 while (str > num + neg) {
1490 *--str = hexchars[val % base];
1491 val = val / base;
1492 if (--prec <= 0 && val == 0)
1493 break;
1494 }
1495 switch (neg) {
1496 case 1:
1497 *--str = '-';
1498 break;
1499 case 2:
1500 *--str = 'x';
1501 *--str = '0';
1502 break;
1503 }
1504 len = num + sizeof(num) - 1 - str;
1505 } else {
1506 len = strlen(str);
1507 if (prec > 0 && len > prec)
1508 len = prec;
1509 }
1510 if (width > 0) {
1511 if (width > buflen)
1512 width = buflen;
1513 if ((n = width - len) > 0) {
1514 buflen -= n;
1515 for (; n > 0; --n)
1516 *buf++ = fillch;
1517 }
1518 }
1519 if (len > buflen)
1520 len = buflen;
1521 memcpy(buf, str, len);
1522 buf += len;
1523 buflen -= len;
1524 }
1525 *buf = 0;
1526 return buf - buf0;
1527 }
1528