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