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