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