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