chat.c revision 31331
1/*-
2 * Copyright (c) 1997
3 *	David L Nugent <davidn@blaze.net.au>.
4 *	All rights reserved.
5 *
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, is permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice immediately at the beginning of the file, without modification,
12 *    this list of conditions, and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. This work was done expressly for inclusion into FreeBSD.  Other use
17 *    is permitted provided this notation is included.
18 * 4. Absolutely no warranty of function or purpose is made by the authors.
19 * 5. Modifications may be freely made to this file providing the above
20 *    conditions are met.
21 *
22 * Modem chat module - send/expect style functions for getty
23 * For semi-intelligent modem handling.
24 */
25
26#ifndef lint
27static const char rcsid[] =
28	"$Id$";
29#endif /* not lint */
30
31#include <sys/param.h>
32#include <sys/stat.h>
33#include <sys/ioctl.h>
34#include <sys/resource.h>
35#include <sys/ttydefaults.h>
36#include <sys/utsname.h>
37#include <ctype.h>
38#include <errno.h>
39#include <fcntl.h>
40#include <libutil.h>
41#include <locale.h>
42#include <setjmp.h>
43#include <signal.h>
44#include <stdlib.h>
45#include <string.h>
46#include <syslog.h>
47#include <time.h>
48#include <termios.h>
49#include <unistd.h>
50#include <sys/socket.h>
51
52#include "extern.h"
53
54#define	PAUSE_CH		(unsigned char)'\xff'   /* pause kludge */
55
56#define	CHATDEBUG_RECEIVE	0x01
57#define	CHATDEBUG_SEND		0x02
58#define	CHATDEBUG_EXPECT	0x04
59#define	CHATDEBUG_MISC		0x08
60
61#define	CHATDEBUG_DEFAULT	0
62#define CHAT_DEFAULT_TIMEOUT	10
63
64
65static int chat_debug = CHATDEBUG_DEFAULT;
66static int chat_alarm = CHAT_DEFAULT_TIMEOUT; /* Default */
67
68static volatile int alarmed = 0;
69
70
71static void   chat_alrm __P((int));
72static int    chat_unalarm __P((void));
73static int    getdigit __P((unsigned char **, int, int));
74static char   **read_chat __P((char **));
75static char   *cleanchr __P((char **, unsigned char));
76static char   *cleanstr __P((const unsigned char *, int));
77static const char *result __P((int));
78static int    chat_expect __P((const char *));
79static int    chat_send __P((char const *));
80
81
82/*
83 * alarm signal handler
84 * handle timeouts in read/write
85 * change stdin to non-blocking mode to prevent
86 * possible hang in read().
87 */
88
89static void
90chat_alrm(signo)
91	int signo;
92{
93	int on = 1;
94
95	alarm(1);
96	alarmed = 1;
97	signal(SIGALRM, chat_alrm);
98	ioctl(STDIN_FILENO, FIONBIO, &on);
99}
100
101
102/*
103 * Turn back on blocking mode reset by chat_alrm()
104 */
105
106static int
107chat_unalarm()
108{
109	int off = 0;
110	return ioctl(STDIN_FILENO, FIONBIO, &off);
111}
112
113
114/*
115 * convert a string of a given base (octal/hex) to binary
116 */
117
118static int
119getdigit(ptr, base, max)
120	unsigned char **ptr;
121	int base, max;
122{
123	int i, val = 0;
124	char * q;
125
126	static const char xdigits[] = "0123456789abcdef";
127
128	for (i = 0, q = *ptr; i++ < max; ++q) {
129		int sval;
130		const char * s = strchr(xdigits, tolower(*q));
131
132		if (s == NULL || (sval = s - xdigits) >= base)
133			break;
134		val = (val * base) + sval;
135	}
136	*ptr = q;
137	return val;
138}
139
140
141/*
142 * read_chat()
143 * Convert a whitespace delimtied string into an array
144 * of strings, being expect/send pairs
145 */
146
147static char **
148read_chat(chatstr)
149	char **chatstr;
150{
151	char *str = *chatstr;
152	char **res = NULL;
153
154	if (str != NULL) {
155		char *tmp = NULL;
156		int l;
157
158		if ((l=strlen(str)) > 0 && (tmp=malloc(l + 1)) != NULL &&
159		    (res=malloc((l / 2 + 1) * sizeof(char *))) != NULL) {
160			static char ws[] = " \t";
161			char * p;
162
163			for (l = 0, p = strtok(strcpy(tmp, str), ws);
164			     p != NULL;
165			     p = strtok(NULL, ws))
166			{
167				unsigned char *q, *r;
168
169				/* Read escapes */
170				for (q = r = (unsigned char *)p; *r; ++q)
171				{
172					if (*q == '\\')
173					{
174						/* handle special escapes */
175						switch (*++q)
176						{
177						case 'a': /* bell */
178							*r++ = '\a';
179							break;
180						case 'r': /* cr */
181							*r++ = '\r';
182							break;
183						case 'n': /* nl */
184							*r++ = '\n';
185							break;
186						case 'f': /* ff */
187							*r++ = '\f';
188							break;
189						case 'b': /* bs */
190							*r++ = '\b';
191							break;
192						case 'e': /* esc */
193							*r++ = 27;
194							break;
195						case 't': /* tab */
196							*r++ = '\t';
197							break;
198						case 'p': /* pause */
199							*r++ = PAUSE_CH;
200							break;
201						case 's':
202						case 'S': /* space */
203							*r++ = ' ';
204							break;
205						case 'x': /* hexdigit */
206							++q;
207							*r++ = getdigit(&q, 16, 2);
208							--q;
209							break;
210						case '0': /* octal */
211							++q;
212							*r++ = getdigit(&q, 8, 3);
213							--q;
214							break;
215						default: /* literal */
216							*r++ = *q;
217							break;
218						case 0: /* not past eos */
219							--q;
220							break;
221						}
222					} else {
223						/* copy standard character */
224						*r++ = *q;
225					}
226				}
227
228				/* Remove surrounding quotes, if any
229				 */
230				if (*p == '"' || *p == '\'') {
231					q = strrchr(p+1, *p);
232					if (q != NULL && *q == *p && q[1] == '\0') {
233						*q = '\0';
234						strcpy(p, p+1);
235					}
236				}
237
238				res[l++] = p;
239			}
240			res[l] = NULL;
241			*chatstr = tmp;
242			return res;
243		}
244		free(tmp);
245	}
246	return res;
247}
248
249
250/*
251 * clean a character for display (ctrl/meta character)
252 */
253
254static char *
255cleanchr(buf, ch)
256	char **buf;
257	unsigned char ch;
258{
259	int l;
260	static char tmpbuf[5];
261	char * tmp = buf ? *buf : tmpbuf;
262
263	if (ch & 0x80) {
264		strcpy(tmp, "M-");
265		l = 2;
266		ch &= 0x7f;
267	} else
268	l = 0;
269
270	if (ch < 32) {
271		tmp[l++] = '^';
272		tmp[l++] = ch + '@';
273	} else if (ch == 127) {
274		tmp[l++] = '^';
275		tmp[l++] = '?';
276	} else
277		tmp[l++] = ch;
278	tmp[l] = '\0';
279
280	if (buf)
281		*buf = tmp + l;
282	return tmp;
283}
284
285
286/*
287 * clean a string for display (ctrl/meta characters)
288 */
289
290static char *
291cleanstr(s, l)
292	const unsigned char *s;
293	int l;
294{
295	static unsigned char * tmp = NULL;
296	static int tmplen = 0;
297
298	if (tmplen < l * 4 + 1)
299		tmp = realloc(tmp, tmplen = l * 4 + 1);
300
301	if (tmp == NULL) {
302		tmplen = 0;
303		return (char *)"(mem alloc error)";
304	} else {
305		int i = 0;
306		char * p = tmp;
307
308		while (i < l)
309			cleanchr(&p, s[i++]);
310		*p = '\0';
311	}
312
313	return tmp;
314}
315
316
317/*
318 * return result as an pseudo-english word
319 */
320
321static const char *
322result(r)
323	int r;
324{
325	static const char * results[] = {
326		"OK", "MEMERROR", "IOERROR", "TIMEOUT"
327	};
328	return results[r & 3];
329}
330
331
332/*
333 * chat_expect()
334 * scan input for an expected string
335 */
336
337static int
338chat_expect(str)
339	const char *str;
340{
341	int len, r = 0;
342
343	if (chat_debug & CHATDEBUG_EXPECT)
344		syslog(LOG_DEBUG, "chat_expect '%s'", cleanstr(str, strlen(str)));
345
346	if ((len = strlen(str)) > 0) {
347		int i = 0;
348		char * got;
349
350		if ((got = malloc(len + 1)) == NULL)
351			r = 1;
352		else {
353
354			memset(got, 0, len+1);
355			alarm(chat_alarm);
356			alarmed = 0;
357
358			while (r == 0 && i < len) {
359				if (alarmed)
360					r = 3;
361				else {
362					unsigned char ch;
363
364					if (read(STDIN_FILENO, &ch, 1) == 1) {
365
366						if (chat_debug & CHATDEBUG_RECEIVE)
367							syslog(LOG_DEBUG, "chat_recv '%s' m=%d",
368								cleanchr(NULL, ch), i);
369
370						if (ch == str[i])
371							got[i++] = ch;
372						else if (i > 0) {
373							int j = 1;
374
375							/* See if we can resync on a
376							 * partial match in our buffer
377							 */
378							while (j < i && memcmp(got + j, str, i - j) != NULL)
379								j++;
380							if (j < i)
381								memcpy(got, got + j, i - j);
382							i -= j;
383						}
384					} else
385						r = alarmed ? 3 : 2;
386				}
387			}
388			alarm(0);
389        		chat_unalarm();
390        		alarmed = 0;
391        		free(got);
392		}
393	}
394
395	if (chat_debug & CHATDEBUG_EXPECT)
396		syslog(LOG_DEBUG, "chat_expect %s", result(r));
397
398	return r;
399}
400
401
402/*
403 * chat_send()
404 * send a chat string
405 */
406
407static int
408chat_send(str)
409	char const *str;
410{
411	int r = 0;
412
413	if (chat_debug && CHATDEBUG_SEND)
414		syslog(LOG_DEBUG, "chat_send '%s'", cleanstr(str, strlen(str)));
415
416	if (*str) {
417                alarm(chat_alarm);
418                alarmed = 0;
419                while (r == 0 && *str)
420                {
421                        unsigned char ch = (unsigned char)*str++;
422
423                        if (alarmed)
424        			r = 3;
425                        else if (ch == PAUSE_CH)
426				usleep(500000); /* 1/2 second */
427			else  {
428				usleep(10000);	/* be kind to modem */
429                                if (write(STDOUT_FILENO, &ch, 1) != 1)
430        		  		r = alarmed ? 3 : 2;
431                        }
432                }
433                alarm(0);
434                chat_unalarm();
435                alarmed = 0;
436	}
437
438        if (chat_debug & CHATDEBUG_SEND)
439          syslog(LOG_DEBUG, "chat_send %s", result(r));
440
441        return r;
442}
443
444
445/*
446 * getty_chat()
447 *
448 * Termination codes:
449 * -1 - no script supplied
450 *  0 - script terminated correctly
451 *  1 - invalid argument, expect string too large, etc.
452 *  2 - error on an I/O operation or fatal error condition
453 *  3 - timeout waiting for a simple string
454 *
455 * Parameters:
456 *  char *scrstr     - unparsed chat script
457 *  timeout          - seconds timeout
458 *  debug            - debug value (bitmask)
459 */
460
461int
462getty_chat(scrstr, timeout, debug)
463	char *scrstr;
464	int timeout, debug;
465{
466        int r = -1;
467
468        chat_alarm = timeout ? timeout : CHAT_DEFAULT_TIMEOUT;
469        chat_debug = debug;
470
471        if (scrstr != NULL) {
472                char **script;
473
474                if (chat_debug & CHATDEBUG_MISC)
475			syslog(LOG_DEBUG, "getty_chat script='%s'", scrstr);
476
477                if ((script = read_chat(&scrstr)) != NULL) {
478                        int i = r = 0;
479			int off = 0;
480                        sig_t old_alarm;
481
482                        /*
483			 * We need to be in raw mode for all this
484			 * Rely on caller...
485                         */
486
487                        old_alarm = signal(SIGALRM, chat_alrm);
488                        chat_unalarm(); /* Force blocking mode at start */
489
490			/*
491			 * This is the send/expect loop
492			 */
493                        while (r == 0 && script[i] != NULL)
494				if ((r = chat_expect(script[i++])) == 0 && script[i] != NULL)
495					r = chat_send(script[i++]);
496
497                        signal(SIGALRM, old_alarm);
498                        free(script);
499                        free(scrstr);
500
501			/*
502			 * Ensure stdin is in blocking mode
503			 */
504                        ioctl(STDIN_FILENO, FIONBIO, &off);
505                }
506
507                if (chat_debug & CHATDEBUG_MISC)
508                  syslog(LOG_DEBUG, "getty_chat %s", result(r));
509
510        }
511        return r;
512}
513