chat.c revision 126952
139212Sgibbs/*-
239212Sgibbs * Copyright (c) 1997
339212Sgibbs *	David L Nugent <davidn@blaze.net.au>.
439212Sgibbs *	All rights reserved.
539212Sgibbs *
639212Sgibbs *
739212Sgibbs * Redistribution and use in source and binary forms, with or without
839212Sgibbs * modification, is permitted provided that the following conditions
939212Sgibbs * are met:
1039212Sgibbs * 1. Redistributions of source code must retain the above copyright
1139212Sgibbs *    notice immediately at the beginning of the file, without modification,
1239212Sgibbs *    this list of conditions, and the following disclaimer.
1339212Sgibbs * 2. Redistributions in binary form must reproduce the above copyright
1439212Sgibbs *    notice, this list of conditions and the following disclaimer in the
1539212Sgibbs *    documentation and/or other materials provided with the distribution.
1639212Sgibbs * 3. This work was done expressly for inclusion into FreeBSD.  Other use
1739212Sgibbs *    is permitted provided this notation is included.
1839212Sgibbs * 4. Absolutely no warranty of function or purpose is made by the authors.
1939212Sgibbs * 5. Modifications may be freely made to this file providing the above
2039212Sgibbs *    conditions are met.
2139212Sgibbs *
2239212Sgibbs * Modem chat module - send/expect style functions for getty
2339212Sgibbs * For semi-intelligent modem handling.
2439212Sgibbs */
2539212Sgibbs
2639212Sgibbs#ifndef lint
2739212Sgibbsstatic const char rcsid[] =
2839212Sgibbs  "$FreeBSD: head/libexec/getty/chat.c 126952 2004-03-14 05:27:26Z bde $";
2939212Sgibbs#endif /* not lint */
3039212Sgibbs
3139212Sgibbs#include <sys/types.h>
3239212Sgibbs#include <sys/ioctl.h>
3339212Sgibbs#include <sys/utsname.h>
3439212Sgibbs
3539212Sgibbs#include <ctype.h>
3639212Sgibbs#include <signal.h>
3739212Sgibbs#include <stdlib.h>
3839212Sgibbs#include <string.h>
3939212Sgibbs#include <syslog.h>
4039212Sgibbs#include <unistd.h>
4139212Sgibbs
4239212Sgibbs#include "extern.h"
4339212Sgibbs
4439212Sgibbs#define	PAUSE_CH		(unsigned char)'\xff'   /* pause kludge */
4539212Sgibbs
4639212Sgibbs#define	CHATDEBUG_RECEIVE	0x01
4739212Sgibbs#define	CHATDEBUG_SEND		0x02
4839212Sgibbs#define	CHATDEBUG_EXPECT	0x04
4939212Sgibbs#define	CHATDEBUG_MISC		0x08
5039212Sgibbs
5139212Sgibbs#define	CHATDEBUG_DEFAULT	0
5239212Sgibbs#define CHAT_DEFAULT_TIMEOUT	10
5339212Sgibbs
5439212Sgibbs
5539212Sgibbsstatic int chat_debug = CHATDEBUG_DEFAULT;
5639212Sgibbsstatic int chat_alarm = CHAT_DEFAULT_TIMEOUT; /* Default */
5739212Sgibbs
5839212Sgibbsstatic volatile int alarmed = 0;
5939212Sgibbs
6039212Sgibbs
6139212Sgibbsstatic void   chat_alrm(int);
6239212Sgibbsstatic int    chat_unalarm(void);
6339212Sgibbsstatic int    getdigit(unsigned char **, int, int);
6439212Sgibbsstatic char   **read_chat(char **);
6539212Sgibbsstatic char   *cleanchr(char **, unsigned char);
6639212Sgibbsstatic char   *cleanstr(const unsigned char *, int);
6739212Sgibbsstatic const char *result(int);
6839212Sgibbsstatic int    chat_expect(const char *);
6939212Sgibbsstatic int    chat_send(char const *);
7039212Sgibbs
7139212Sgibbs
7239212Sgibbs/*
7339212Sgibbs * alarm signal handler
7439212Sgibbs * handle timeouts in read/write
7539212Sgibbs * change stdin to non-blocking mode to prevent
7639212Sgibbs * possible hang in read().
7739212Sgibbs */
7839212Sgibbs
7939212Sgibbsstatic void
8039212Sgibbschat_alrm(int signo)
8139212Sgibbs{
8239212Sgibbs	int on = 1;
8339212Sgibbs
8439212Sgibbs	alarm(1);
8539212Sgibbs	alarmed = 1;
8639212Sgibbs	signal(SIGALRM, chat_alrm);
8739212Sgibbs	ioctl(STDIN_FILENO, FIONBIO, &on);
8839212Sgibbs}
8939212Sgibbs
9039212Sgibbs
9139212Sgibbs/*
9239212Sgibbs * Turn back on blocking mode reset by chat_alrm()
9339212Sgibbs */
9439212Sgibbs
9539212Sgibbsstatic int
9639212Sgibbschat_unalarm(void)
9739212Sgibbs{
9839212Sgibbs	int off = 0;
9939212Sgibbs	return ioctl(STDIN_FILENO, FIONBIO, &off);
10039212Sgibbs}
10139212Sgibbs
10239212Sgibbs
10339212Sgibbs/*
10439212Sgibbs * convert a string of a given base (octal/hex) to binary
10539212Sgibbs */
10639212Sgibbs
10739212Sgibbsstatic int
10839212Sgibbsgetdigit(unsigned char **ptr, int base, int max)
10939212Sgibbs{
11039212Sgibbs	int i, val = 0;
11139212Sgibbs	char * q;
11239212Sgibbs
11339212Sgibbs	static const char xdigits[] = "0123456789abcdef";
11439212Sgibbs
11539212Sgibbs	for (i = 0, q = *ptr; i++ < max; ++q) {
11639212Sgibbs		int sval;
11739212Sgibbs		const char * s = strchr(xdigits, tolower(*q));
11839212Sgibbs
11939212Sgibbs		if (s == NULL || (sval = s - xdigits) >= base)
12039212Sgibbs			break;
12139212Sgibbs		val = (val * base) + sval;
12239212Sgibbs	}
12339212Sgibbs	*ptr = q;
12439212Sgibbs	return val;
12539212Sgibbs}
12639212Sgibbs
12739212Sgibbs
12839212Sgibbs/*
12939212Sgibbs * read_chat()
13039212Sgibbs * Convert a whitespace delimtied string into an array
13139212Sgibbs * of strings, being expect/send pairs
13239212Sgibbs */
13339212Sgibbs
13439212Sgibbsstatic char **
13539212Sgibbsread_chat(char **chatstr)
13639212Sgibbs{
13739212Sgibbs	char *str = *chatstr;
13839212Sgibbs	char **res = NULL;
139
140	if (str != NULL) {
141		char *tmp = NULL;
142		int l;
143
144		if ((l=strlen(str)) > 0 && (tmp=malloc(l + 1)) != NULL &&
145		    (res=malloc((l / 2 + 1) * sizeof(char *))) != NULL) {
146			static char ws[] = " \t";
147			char * p;
148
149			for (l = 0, p = strtok(strcpy(tmp, str), ws);
150			     p != NULL;
151			     p = strtok(NULL, ws))
152			{
153				unsigned char *q, *r;
154
155				/* Read escapes */
156				for (q = r = (unsigned char *)p; *r; ++q)
157				{
158					if (*q == '\\')
159					{
160						/* handle special escapes */
161						switch (*++q)
162						{
163						case 'a': /* bell */
164							*r++ = '\a';
165							break;
166						case 'r': /* cr */
167							*r++ = '\r';
168							break;
169						case 'n': /* nl */
170							*r++ = '\n';
171							break;
172						case 'f': /* ff */
173							*r++ = '\f';
174							break;
175						case 'b': /* bs */
176							*r++ = '\b';
177							break;
178						case 'e': /* esc */
179							*r++ = 27;
180							break;
181						case 't': /* tab */
182							*r++ = '\t';
183							break;
184						case 'p': /* pause */
185							*r++ = PAUSE_CH;
186							break;
187						case 's':
188						case 'S': /* space */
189							*r++ = ' ';
190							break;
191						case 'x': /* hexdigit */
192							++q;
193							*r++ = getdigit(&q, 16, 2);
194							--q;
195							break;
196						case '0': /* octal */
197							++q;
198							*r++ = getdigit(&q, 8, 3);
199							--q;
200							break;
201						default: /* literal */
202							*r++ = *q;
203							break;
204						case 0: /* not past eos */
205							--q;
206							break;
207						}
208					} else {
209						/* copy standard character */
210						*r++ = *q;
211					}
212				}
213
214				/* Remove surrounding quotes, if any
215				 */
216				if (*p == '"' || *p == '\'') {
217					q = strrchr(p+1, *p);
218					if (q != NULL && *q == *p && q[1] == '\0') {
219						*q = '\0';
220						strcpy(p, p+1);
221					}
222				}
223
224				res[l++] = p;
225			}
226			res[l] = NULL;
227			*chatstr = tmp;
228			return res;
229		}
230		free(tmp);
231	}
232	return res;
233}
234
235
236/*
237 * clean a character for display (ctrl/meta character)
238 */
239
240static char *
241cleanchr(char **buf, unsigned char ch)
242{
243	int l;
244	static char tmpbuf[5];
245	char * tmp = buf ? *buf : tmpbuf;
246
247	if (ch & 0x80) {
248		strcpy(tmp, "M-");
249		l = 2;
250		ch &= 0x7f;
251	} else
252	l = 0;
253
254	if (ch < 32) {
255		tmp[l++] = '^';
256		tmp[l++] = ch + '@';
257	} else if (ch == 127) {
258		tmp[l++] = '^';
259		tmp[l++] = '?';
260	} else
261		tmp[l++] = ch;
262	tmp[l] = '\0';
263
264	if (buf)
265		*buf = tmp + l;
266	return tmp;
267}
268
269
270/*
271 * clean a string for display (ctrl/meta characters)
272 */
273
274static char *
275cleanstr(const unsigned char *s, int l)
276{
277	static unsigned char * tmp = NULL;
278	static int tmplen = 0;
279
280	if (tmplen < l * 4 + 1)
281		tmp = realloc(tmp, tmplen = l * 4 + 1);
282
283	if (tmp == NULL) {
284		tmplen = 0;
285		return (char *)"(mem alloc error)";
286	} else {
287		int i = 0;
288		char * p = tmp;
289
290		while (i < l)
291			cleanchr(&p, s[i++]);
292		*p = '\0';
293	}
294
295	return tmp;
296}
297
298
299/*
300 * return result as a pseudo-english word
301 */
302
303static const char *
304result(int r)
305{
306	static const char * results[] = {
307		"OK", "MEMERROR", "IOERROR", "TIMEOUT"
308	};
309	return results[r & 3];
310}
311
312
313/*
314 * chat_expect()
315 * scan input for an expected string
316 */
317
318static int
319chat_expect(const char *str)
320{
321	int len, r = 0;
322
323	if (chat_debug & CHATDEBUG_EXPECT)
324		syslog(LOG_DEBUG, "chat_expect '%s'", cleanstr(str, strlen(str)));
325
326	if ((len = strlen(str)) > 0) {
327		int i = 0;
328		char * got;
329
330		if ((got = malloc(len + 1)) == NULL)
331			r = 1;
332		else {
333
334			memset(got, 0, len+1);
335			alarm(chat_alarm);
336			alarmed = 0;
337
338			while (r == 0 && i < len) {
339				if (alarmed)
340					r = 3;
341				else {
342					unsigned char ch;
343
344					if (read(STDIN_FILENO, &ch, 1) == 1) {
345
346						if (chat_debug & CHATDEBUG_RECEIVE)
347							syslog(LOG_DEBUG, "chat_recv '%s' m=%d",
348								cleanchr(NULL, ch), i);
349
350						if (ch == str[i])
351							got[i++] = ch;
352						else if (i > 0) {
353							int j = 1;
354
355							/* See if we can resync on a
356							 * partial match in our buffer
357							 */
358							while (j < i && memcmp(got + j, str, i - j) != 0)
359								j++;
360							if (j < i)
361								memcpy(got, got + j, i - j);
362							i -= j;
363						}
364					} else
365						r = alarmed ? 3 : 2;
366				}
367			}
368			alarm(0);
369        		chat_unalarm();
370        		alarmed = 0;
371        		free(got);
372		}
373	}
374
375	if (chat_debug & CHATDEBUG_EXPECT)
376		syslog(LOG_DEBUG, "chat_expect %s", result(r));
377
378	return r;
379}
380
381
382/*
383 * chat_send()
384 * send a chat string
385 */
386
387static int
388chat_send(char const *str)
389{
390	int r = 0;
391
392	if (chat_debug && CHATDEBUG_SEND)
393		syslog(LOG_DEBUG, "chat_send '%s'", cleanstr(str, strlen(str)));
394
395	if (*str) {
396                alarm(chat_alarm);
397                alarmed = 0;
398                while (r == 0 && *str)
399                {
400                        unsigned char ch = (unsigned char)*str++;
401
402                        if (alarmed)
403        			r = 3;
404                        else if (ch == PAUSE_CH)
405				usleep(500000); /* 1/2 second */
406			else  {
407				usleep(10000);	/* be kind to modem */
408                                if (write(STDOUT_FILENO, &ch, 1) != 1)
409        		  		r = alarmed ? 3 : 2;
410                        }
411                }
412                alarm(0);
413                chat_unalarm();
414                alarmed = 0;
415	}
416
417        if (chat_debug & CHATDEBUG_SEND)
418          syslog(LOG_DEBUG, "chat_send %s", result(r));
419
420        return r;
421}
422
423
424/*
425 * getty_chat()
426 *
427 * Termination codes:
428 * -1 - no script supplied
429 *  0 - script terminated correctly
430 *  1 - invalid argument, expect string too large, etc.
431 *  2 - error on an I/O operation or fatal error condition
432 *  3 - timeout waiting for a simple string
433 *
434 * Parameters:
435 *  char *scrstr     - unparsed chat script
436 *  timeout          - seconds timeout
437 *  debug            - debug value (bitmask)
438 */
439
440int
441getty_chat(char *scrstr, int timeout, int debug)
442{
443        int r = -1;
444
445        chat_alarm = timeout ? timeout : CHAT_DEFAULT_TIMEOUT;
446        chat_debug = debug;
447
448        if (scrstr != NULL) {
449                char **script;
450
451                if (chat_debug & CHATDEBUG_MISC)
452			syslog(LOG_DEBUG, "getty_chat script='%s'", scrstr);
453
454                if ((script = read_chat(&scrstr)) != NULL) {
455                        int i = r = 0;
456			int off = 0;
457                        sig_t old_alarm;
458
459                        /*
460			 * We need to be in raw mode for all this
461			 * Rely on caller...
462                         */
463
464                        old_alarm = signal(SIGALRM, chat_alrm);
465                        chat_unalarm(); /* Force blocking mode at start */
466
467			/*
468			 * This is the send/expect loop
469			 */
470                        while (r == 0 && script[i] != NULL)
471				if ((r = chat_expect(script[i++])) == 0 && script[i] != NULL)
472					r = chat_send(script[i++]);
473
474                        signal(SIGALRM, old_alarm);
475                        free(script);
476                        free(scrstr);
477
478			/*
479			 * Ensure stdin is in blocking mode
480			 */
481                        ioctl(STDIN_FILENO, FIONBIO, &off);
482                }
483
484                if (chat_debug & CHATDEBUG_MISC)
485                  syslog(LOG_DEBUG, "getty_chat %s", result(r));
486
487        }
488        return r;
489}
490