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