chat.c revision 22989
11556Srgrimes/*-
21556Srgrimes * Copyright (c) 1997
31556Srgrimes *	David L Nugent <davidn@blaze.net.au>.
41556Srgrimes *	All rights reserved.
51556Srgrimes *
61556Srgrimes *
71556Srgrimes * Redistribution and use in source and binary forms, with or without
81556Srgrimes * modification, is permitted provided that the following conditions
91556Srgrimes * are met:
101556Srgrimes * 1. Redistributions of source code must retain the above copyright
111556Srgrimes *    notice immediately at the beginning of the file, without modification,
121556Srgrimes *    this list of conditions, and the following disclaimer.
131556Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
141556Srgrimes *    notice, this list of conditions and the following disclaimer in the
151556Srgrimes *    documentation and/or other materials provided with the distribution.
161556Srgrimes * 3. This work was done expressly for inclusion into FreeBSD.  Other use
171556Srgrimes *    is permitted provided this notation is included.
181556Srgrimes * 4. Absolutely no warranty of function or purpose is made by the authors.
191556Srgrimes * 5. Modifications may be freely made to this file providing the above
201556Srgrimes *    conditions are met.
211556Srgrimes *
221556Srgrimes * Modem chat module - send/expect style functions for getty
231556Srgrimes * For semi-intelligent modem handling.
241556Srgrimes *
251556Srgrimes *	$Id$
261556Srgrimes */
271556Srgrimes
281556Srgrimes#include <sys/param.h>
291556Srgrimes#include <sys/stat.h>
301556Srgrimes#include <sys/ioctl.h>
311556Srgrimes#include <sys/resource.h>
3217987Speter#include <sys/ttydefaults.h>
3350471Speter#include <sys/utsname.h>
341556Srgrimes#include <errno.h>
351556Srgrimes#include <signal.h>
361556Srgrimes#include <fcntl.h>
371556Srgrimes#include <time.h>
381556Srgrimes#include <ctype.h>
391556Srgrimes#include <fcntl.h>
401556Srgrimes#include <libutil.h>
4120425Ssteve#include <locale.h>
4220425Ssteve#include <setjmp.h>
43155303Sschweikh#include <signal.h>
44155303Sschweikh#include <stdlib.h>
4520425Ssteve#include <string.h>
4620425Ssteve#include <syslog.h>
4720425Ssteve#include <termios.h>
48216870Sjilles#include <time.h>
49223024Sjilles#include <unistd.h>
501556Srgrimes#include <sys/socket.h>
511556Srgrimes
521556Srgrimes#include "extern.h"
531556Srgrimes
5420425Ssteve#define	PAUSE_CH		(unsigned char)'\xff'   /* pause kludge */
55221668Sjilles
5620425Ssteve#define	CHATDEBUG_RECEIVE	0x01
5790111Simp#define	CHATDEBUG_SEND		0x02
5820425Ssteve#define	CHATDEBUG_EXPECT	0x04
5920425Ssteve#define	CHATDEBUG_MISC		0x08
601556Srgrimes
611556Srgrimes#define	CHATDEBUG_DEFAULT	0
621556Srgrimes#define CHAT_DEFAULT_TIMEOUT	10
631556Srgrimes
6420425Ssteve
6520425Sstevestatic int chat_debug = CHATDEBUG_DEFAULT;
6620425Sstevestatic int chat_alarm = CHAT_DEFAULT_TIMEOUT; /* Default */
6720425Ssteve
681556Srgrimesstatic volatile int alarmed = 0;
691556Srgrimes
701556Srgrimes
711556Srgrimesstatic void   chat_alrm __P((int));
72223024Sjillesstatic int    chat_unalarm __P((void));
731556Srgrimesstatic int    getdigit __P((unsigned char **, int, int));
741556Srgrimesstatic char   **read_chat __P((char **));
751556Srgrimesstatic char   *cleanchr __P((char **, unsigned char));
761556Srgrimesstatic char   *cleanstr __P((const unsigned char *, int));
771556Srgrimesstatic const char *result __P((int));
7897689Stjrstatic int    chat_expect __P((const char *));
791556Srgrimesstatic int    chat_send __P((char const *));
801556Srgrimes
81159632Sstefanf
82230998Sjilles/*
8320425Ssteve * alarm signal handler
8420425Ssteve * handle timeouts in read/write
85208755Sjilles * change stdin to non-blocking mode to prevent
8620425Ssteve * possible hang in read().
871556Srgrimes */
88221559Sjilles
89221669Sjillesstatic void
90221669Sjilleschat_alrm(signo)
91221559Sjilles	int signo;
921556Srgrimes{
931556Srgrimes	int on = 1;
941556Srgrimes
951556Srgrimes	alarm(1);
961556Srgrimes	alarmed = 1;
971556Srgrimes	signal(SIGALRM, chat_alrm);
981556Srgrimes	ioctl(STDIN_FILENO, FIONBIO, &on);
9938887Stegge}
1001556Srgrimes
1011556Srgrimes
1021556Srgrimes/*
1031556Srgrimes * Turn back on blocking mode reset by chat_alrm()
1041556Srgrimes */
105159632Sstefanf
10620425Sstevestatic int
10720425Sstevechat_unalarm()
10820425Ssteve{
109208755Sjilles	int off = 0;
11020425Ssteve	return ioctl(STDIN_FILENO, FIONBIO, &off);
1111556Srgrimes}
1121556Srgrimes
113230998Sjilles
1141556Srgrimes/*
11590111Simp * convert a string of a given base (octal/hex) to binary
116200956Sjilles */
11790111Simp
1181556Srgrimesstatic int
119216870Sjillesgetdigit(ptr, base, max)
120200956Sjilles	unsigned char **ptr;
121200956Sjilles	int base, max;
122207678Sjilles{
123207678Sjilles	int i, val = 0;
124221559Sjilles	char * q;
125221669Sjilles
12690111Simp	static const char xdigits[] = "0123456789abcdef";
12790111Simp
12890111Simp	for (i = 0, q = *ptr; i++ < max; ++q) {
12990111Simp		int sval;
130200956Sjilles		const char * s = strchr(xdigits, tolower(*q));
131200956Sjilles
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					int val;
173
174					if (*q == '\\')
175					{
176						/* handle special escapes */
177						switch (*++q)
178						{
179						case 'a': /* bell */
180							*r++ = '\a';
181							break;
182						case 'r': /* cr */
183							*r++ = '\r';
184							break;
185						case 'n': /* nl */
186							*r++ = '\n';
187							break;
188						case 'f': /* ff */
189							*r++ = '\f';
190							break;
191						case 'b': /* bs */
192							*r++ = '\b';
193							break;
194						case 'e': /* esc */
195							*r++ = 27;
196							break;
197						case 't': /* tab */
198							*r++ = '\t';
199							break;
200						case 'p': /* pause */
201							*r++ = PAUSE_CH;
202							break;
203						case 's':
204						case 'S': /* space */
205							*r++ = ' ';
206							break;
207						case 'x': /* hexdigit */
208							++q;
209							*r++ = getdigit(&q, 16, 2);
210							--q;
211							break;
212						case '0': /* octal */
213							++q;
214							*r++ = getdigit(&q, 8, 3);
215							--q;
216							break;
217						default: /* literal */
218							*r++ = *q;
219							break;
220						case 0: /* not past eos */
221							--q;
222							break;
223						}
224					} else {
225						/* copy standard character */
226						*r++ == *q;
227					}
228				}
229
230				/* Remove surrounding quotes, if any
231				 */
232				if (*p == '"' || *p == '\'') {
233					q = strrchr(p+1, *p);
234					if (q != NULL && *q == *p && q[1] == '\0') {
235						*q = '\0';
236						strcpy(p, p+1);
237					}
238				}
239
240				res[l++] = p;
241			}
242			res[l] = NULL;
243			*chatstr = tmp;
244			return res;
245		}
246		free(tmp);
247	}
248	return res;
249}
250
251
252/*
253 * clean a character for display (ctrl/meta character)
254 */
255
256static char *
257cleanchr(buf, ch)
258	char **buf;
259	unsigned char ch;
260{
261	int l;
262	static char tmpbuf[5];
263	char * tmp = buf ? *buf : tmpbuf;
264
265	if (ch & 0x80) {
266		strcpy(tmp, "M-");
267		l = 2;
268		ch &= 0x7f;
269	} else
270	l = 0;
271
272	if (ch < 32) {
273		tmp[l++] = '^';
274		tmp[l++] = ch + '@';
275	} else if (ch == 127) {
276		tmp[l++] = '^';
277		tmp[l++] = '?';
278	} else
279		tmp[l++] = ch;
280	tmp[l] = '\0';
281
282	if (buf)
283		*buf = tmp + l;
284	return tmp;
285}
286
287
288/*
289 * clean a string for display (ctrl/meta characters)
290 */
291
292static char *
293cleanstr(s, l)
294	const unsigned char *s;
295	int l;
296{
297	static unsigned char * tmp = NULL;
298	static int tmplen = 0;
299
300	if (tmplen < l * 4 + 1)
301		tmp = realloc(tmp, tmplen = l * 4 + 1);
302
303	if (tmp == NULL) {
304		tmplen = 0;
305		return (char *)"(mem alloc error)";
306	} else {
307		int i = 0;
308		char * p = tmp;
309
310		while (i < l)
311			cleanchr(&p, s[i++]);
312		*p = '\0';
313	}
314
315	return tmp;
316}
317
318
319/*
320 * return result as an pseudo-english word
321 */
322
323static const char *
324result(r)
325	int r;
326{
327	static const char * results[] = {
328		"OK", "MEMERROR", "IOERROR", "TIMEOUT"
329	};
330	return results[r & 3];
331}
332
333
334/*
335 * chat_expect()
336 * scan input for an expected string
337 */
338
339static int
340chat_expect(str)
341	const char *str;
342{
343	int len, r = 0;
344
345	if (chat_debug & CHATDEBUG_EXPECT)
346		syslog(LOG_DEBUG, "chat_expect '%s'", cleanstr(str, strlen(str)));
347
348	if ((len = strlen(str)) > 0) {
349		int i = 0;
350		char * got;
351
352		if ((got = malloc(len + 1)) == NULL)
353			r = 1;
354		else {
355
356			memset(got, 0, len+1);
357			alarm(chat_alarm);
358			alarmed = 0;
359
360			while (r == 0 && i < len) {
361				if (alarmed)
362					r = 3;
363				else {
364					unsigned char ch;
365
366					if (read(STDIN_FILENO, &ch, 1) == 1) {
367
368						if (chat_debug & CHATDEBUG_RECEIVE)
369							syslog(LOG_DEBUG, "chat_recv '%s' m=%d",
370								cleanchr(NULL, ch), i);
371
372						if (ch == str[i])
373							got[i++] = ch;
374						else if (i > 0) {
375							int j = 1;
376
377							/* See if we can resync on a
378							 * partial match in our buffer
379							 */
380							while (j < i && memcmp(got + j, str, i - j) != NULL)
381								j++;
382							if (j < i)
383								memcpy(got, got + j, i - j);
384							i -= j;
385						}
386					} else
387						r = alarmed ? 3 : 2;
388				}
389			}
390			alarm(0);
391        		chat_unalarm();
392        		alarmed = 0;
393        		free(got);
394		}
395	}
396
397	if (chat_debug & CHATDEBUG_EXPECT)
398		syslog(LOG_DEBUG, "chat_expect %s", result(r));
399
400	return r;
401}
402
403
404/*
405 * chat_send()
406 * send a chat string
407 */
408
409static int
410chat_send(str)
411	char const *str;
412{
413	int r = 0;
414
415	if (chat_debug && CHATDEBUG_SEND)
416		syslog(LOG_DEBUG, "chat_send '%s'", cleanstr(str, strlen(str)));
417
418	if (*str) {
419                alarm(chat_alarm);
420                alarmed = 0;
421                while (r == 0 && *str)
422                {
423                        unsigned char ch = (unsigned char)*str++;
424
425                        if (alarmed)
426        			r = 3;
427                        else if (ch == PAUSE_CH)
428				usleep(500000); /* 1/2 second */
429			else  {
430				usleep(10000);	/* be kind to modem */
431                                if (write(STDOUT_FILENO, &ch, 1) != 1)
432        		  		r = alarmed ? 3 : 2;
433                        }
434                }
435                alarm(0);
436                chat_unalarm();
437                alarmed = 0;
438	}
439
440        if (chat_debug & CHATDEBUG_SEND)
441          syslog(LOG_DEBUG, "chat_send %s", result(r));
442
443        return r;
444}
445
446
447/*
448 * getty_chat()
449 *
450 * Termination codes:
451 * -1 - no script supplied
452 *  0 - script terminated correctly
453 *  1 - invalid argument, expect string too large, etc.
454 *  2 - error on an I/O operation or fatal error condition
455 *  3 - timeout waiting for a simple string
456 *
457 * Parameters:
458 *  char *scrstr     - unparsed chat script
459 *  timeout          - seconds timeout
460 *  debug            - debug value (bitmask)
461 */
462
463int
464getty_chat(scrstr, timeout, debug)
465	char *scrstr;
466	int timeout, debug;
467{
468        int r = -1;
469
470        chat_alarm = timeout ? timeout : CHAT_DEFAULT_TIMEOUT;
471        chat_debug = debug;
472
473        if (scrstr != NULL) {
474                char **script;
475
476                if (chat_debug & CHATDEBUG_MISC)
477			syslog(LOG_DEBUG, "getty_chat script='%s'", scrstr);
478
479                if ((script = read_chat(&scrstr)) != NULL) {
480                        int i = r = 0;
481			int off = 0;
482                        sig_t old_alarm;
483                        struct termios tneed;
484
485                        /*
486			 * We need to be in raw mode for all this
487			 * Rely on caller...
488                         */
489
490                        old_alarm = signal(SIGALRM, chat_alrm);
491                        chat_unalarm(); /* Force blocking mode at start */
492
493			/*
494			 * This is the send/expect loop
495			 */
496                        while (r == 0 && script[i] != NULL)
497				if ((r = chat_expect(script[i++])) == 0 && script[i] != NULL)
498					r = chat_send(script[i++]);
499
500                        signal(SIGALRM, old_alarm);
501                        free(script);
502                        free(scrstr);
503
504			/*
505			 * Ensure stdin is in blocking mode
506			 */
507                        ioctl(STDIN_FILENO, FIONBIO, &off);
508                }
509
510                if (chat_debug & CHATDEBUG_MISC)
511                  syslog(LOG_DEBUG, "getty_chat %s", result(r));
512
513        }
514        return r;
515}
516