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