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