chat.c revision 108470
122208Sdavidn/*-
222208Sdavidn * Copyright (c) 1997
322208Sdavidn *	David L Nugent <davidn@blaze.net.au>.
422208Sdavidn *	All rights reserved.
522208Sdavidn *
622208Sdavidn *
722208Sdavidn * Redistribution and use in source and binary forms, with or without
822208Sdavidn * modification, is permitted provided that the following conditions
922208Sdavidn * are met:
1022208Sdavidn * 1. Redistributions of source code must retain the above copyright
1122208Sdavidn *    notice immediately at the beginning of the file, without modification,
1222208Sdavidn *    this list of conditions, and the following disclaimer.
1322208Sdavidn * 2. Redistributions in binary form must reproduce the above copyright
1422208Sdavidn *    notice, this list of conditions and the following disclaimer in the
1522208Sdavidn *    documentation and/or other materials provided with the distribution.
1622208Sdavidn * 3. This work was done expressly for inclusion into FreeBSD.  Other use
1722208Sdavidn *    is permitted provided this notation is included.
1822208Sdavidn * 4. Absolutely no warranty of function or purpose is made by the authors.
1922208Sdavidn * 5. Modifications may be freely made to this file providing the above
2022208Sdavidn *    conditions are met.
2122208Sdavidn *
2222208Sdavidn * Modem chat module - send/expect style functions for getty
2322208Sdavidn * For semi-intelligent modem handling.
2422208Sdavidn */
2522208Sdavidn
2631331Scharnier#ifndef lint
2731331Scharnierstatic const char rcsid[] =
2850476Speter  "$FreeBSD: head/libexec/getty/chat.c 108470 2002-12-30 21:18:15Z schweikh $";
2931331Scharnier#endif /* not lint */
3031331Scharnier
3191214Sbde#include <sys/types.h>
3222208Sdavidn#include <sys/ioctl.h>
3322208Sdavidn#include <sys/utsname.h>
3491214Sbde
3531331Scharnier#include <ctype.h>
3622208Sdavidn#include <signal.h>
3722208Sdavidn#include <stdlib.h>
3822208Sdavidn#include <string.h>
3922208Sdavidn#include <syslog.h>
4022208Sdavidn#include <unistd.h>
4122208Sdavidn
4222208Sdavidn#include "extern.h"
4322208Sdavidn
4422208Sdavidn#define	PAUSE_CH		(unsigned char)'\xff'   /* pause kludge */
4522208Sdavidn
4622208Sdavidn#define	CHATDEBUG_RECEIVE	0x01
4722208Sdavidn#define	CHATDEBUG_SEND		0x02
4822208Sdavidn#define	CHATDEBUG_EXPECT	0x04
4922208Sdavidn#define	CHATDEBUG_MISC		0x08
5022208Sdavidn
5122208Sdavidn#define	CHATDEBUG_DEFAULT	0
5222208Sdavidn#define CHAT_DEFAULT_TIMEOUT	10
5322208Sdavidn
5422208Sdavidn
5522208Sdavidnstatic int chat_debug = CHATDEBUG_DEFAULT;
5622208Sdavidnstatic int chat_alarm = CHAT_DEFAULT_TIMEOUT; /* Default */
5722208Sdavidn
5822208Sdavidnstatic volatile int alarmed = 0;
5922208Sdavidn
6022208Sdavidn
6190301Simpstatic void   chat_alrm(int);
6290301Simpstatic int    chat_unalarm(void);
6390301Simpstatic int    getdigit(unsigned char **, int, int);
6490301Simpstatic char   **read_chat(char **);
6590301Simpstatic char   *cleanchr(char **, unsigned char);
6690301Simpstatic char   *cleanstr(const unsigned char *, int);
6790301Simpstatic const char *result(int);
6890301Simpstatic int    chat_expect(const char *);
6990301Simpstatic int    chat_send(char const *);
7022208Sdavidn
7122208Sdavidn
7222208Sdavidn/*
7322208Sdavidn * alarm signal handler
7422208Sdavidn * handle timeouts in read/write
7522208Sdavidn * change stdin to non-blocking mode to prevent
7622208Sdavidn * possible hang in read().
7722208Sdavidn */
7822208Sdavidn
7922208Sdavidnstatic void
8090301Simpchat_alrm(int signo)
8122208Sdavidn{
8222208Sdavidn	int on = 1;
8322208Sdavidn
8422208Sdavidn	alarm(1);
8522208Sdavidn	alarmed = 1;
8622208Sdavidn	signal(SIGALRM, chat_alrm);
8722208Sdavidn	ioctl(STDIN_FILENO, FIONBIO, &on);
8822208Sdavidn}
8922208Sdavidn
9022208Sdavidn
9122208Sdavidn/*
9222208Sdavidn * Turn back on blocking mode reset by chat_alrm()
9322208Sdavidn */
9422208Sdavidn
9522208Sdavidnstatic int
9690301Simpchat_unalarm(void)
9722208Sdavidn{
9822208Sdavidn	int off = 0;
9922208Sdavidn	return ioctl(STDIN_FILENO, FIONBIO, &off);
10022208Sdavidn}
10122208Sdavidn
10222208Sdavidn
10322208Sdavidn/*
10422208Sdavidn * convert a string of a given base (octal/hex) to binary
10522208Sdavidn */
10622208Sdavidn
10722208Sdavidnstatic int
10890301Simpgetdigit(unsigned char **ptr, int base, int max)
10922208Sdavidn{
11022208Sdavidn	int i, val = 0;
11122208Sdavidn	char * q;
11222208Sdavidn
11322208Sdavidn	static const char xdigits[] = "0123456789abcdef";
11422208Sdavidn
11522208Sdavidn	for (i = 0, q = *ptr; i++ < max; ++q) {
11622208Sdavidn		int sval;
11722208Sdavidn		const char * s = strchr(xdigits, tolower(*q));
11822208Sdavidn
11922208Sdavidn		if (s == NULL || (sval = s - xdigits) >= base)
12022208Sdavidn			break;
12122208Sdavidn		val = (val * base) + sval;
12222208Sdavidn	}
12322208Sdavidn	*ptr = q;
12422208Sdavidn	return val;
12522208Sdavidn}
12622208Sdavidn
12722208Sdavidn
12822208Sdavidn/*
12922208Sdavidn * read_chat()
13022208Sdavidn * Convert a whitespace delimtied string into an array
13122208Sdavidn * of strings, being expect/send pairs
13222208Sdavidn */
13322208Sdavidn
13422208Sdavidnstatic char **
13590301Simpread_chat(char **chatstr)
13622208Sdavidn{
13722208Sdavidn	char *str = *chatstr;
13822208Sdavidn	char **res = NULL;
13922208Sdavidn
14022208Sdavidn	if (str != NULL) {
14122208Sdavidn		char *tmp = NULL;
14222208Sdavidn		int l;
14322208Sdavidn
14422208Sdavidn		if ((l=strlen(str)) > 0 && (tmp=malloc(l + 1)) != NULL &&
14522208Sdavidn		    (res=malloc((l / 2 + 1) * sizeof(char *))) != NULL) {
14622208Sdavidn			static char ws[] = " \t";
14722208Sdavidn			char * p;
14822208Sdavidn
14922208Sdavidn			for (l = 0, p = strtok(strcpy(tmp, str), ws);
15022208Sdavidn			     p != NULL;
15122208Sdavidn			     p = strtok(NULL, ws))
15222208Sdavidn			{
15322208Sdavidn				unsigned char *q, *r;
15422208Sdavidn
15522208Sdavidn				/* Read escapes */
15622208Sdavidn				for (q = r = (unsigned char *)p; *r; ++q)
15722208Sdavidn				{
15822208Sdavidn					if (*q == '\\')
15922208Sdavidn					{
16022208Sdavidn						/* handle special escapes */
16122208Sdavidn						switch (*++q)
16222208Sdavidn						{
16322208Sdavidn						case 'a': /* bell */
16422208Sdavidn							*r++ = '\a';
16522208Sdavidn							break;
16622208Sdavidn						case 'r': /* cr */
16722208Sdavidn							*r++ = '\r';
16822208Sdavidn							break;
16922208Sdavidn						case 'n': /* nl */
17022208Sdavidn							*r++ = '\n';
17122208Sdavidn							break;
17222208Sdavidn						case 'f': /* ff */
17322208Sdavidn							*r++ = '\f';
17422208Sdavidn							break;
17522208Sdavidn						case 'b': /* bs */
17622208Sdavidn							*r++ = '\b';
17722208Sdavidn							break;
17822208Sdavidn						case 'e': /* esc */
17922208Sdavidn							*r++ = 27;
18022208Sdavidn							break;
18122208Sdavidn						case 't': /* tab */
18222208Sdavidn							*r++ = '\t';
18322208Sdavidn							break;
18422208Sdavidn						case 'p': /* pause */
18522208Sdavidn							*r++ = PAUSE_CH;
18622208Sdavidn							break;
18722208Sdavidn						case 's':
18822208Sdavidn						case 'S': /* space */
18922208Sdavidn							*r++ = ' ';
19022208Sdavidn							break;
19122208Sdavidn						case 'x': /* hexdigit */
19222208Sdavidn							++q;
19322208Sdavidn							*r++ = getdigit(&q, 16, 2);
19422208Sdavidn							--q;
19522208Sdavidn							break;
19622208Sdavidn						case '0': /* octal */
19722208Sdavidn							++q;
19822208Sdavidn							*r++ = getdigit(&q, 8, 3);
19922208Sdavidn							--q;
20022208Sdavidn							break;
20122208Sdavidn						default: /* literal */
20222208Sdavidn							*r++ = *q;
20322208Sdavidn							break;
20422208Sdavidn						case 0: /* not past eos */
20522208Sdavidn							--q;
20622208Sdavidn							break;
20722208Sdavidn						}
20822208Sdavidn					} else {
20922208Sdavidn						/* copy standard character */
21029003Sdavidn						*r++ = *q;
21122208Sdavidn					}
21222208Sdavidn				}
21322208Sdavidn
21422208Sdavidn				/* Remove surrounding quotes, if any
21522208Sdavidn				 */
21622208Sdavidn				if (*p == '"' || *p == '\'') {
21722208Sdavidn					q = strrchr(p+1, *p);
21822208Sdavidn					if (q != NULL && *q == *p && q[1] == '\0') {
21922208Sdavidn						*q = '\0';
22022208Sdavidn						strcpy(p, p+1);
22122208Sdavidn					}
22222208Sdavidn				}
22322208Sdavidn
22422208Sdavidn				res[l++] = p;
22522208Sdavidn			}
22622208Sdavidn			res[l] = NULL;
22722208Sdavidn			*chatstr = tmp;
22822208Sdavidn			return res;
22922208Sdavidn		}
23022208Sdavidn		free(tmp);
23122208Sdavidn	}
23222208Sdavidn	return res;
23322208Sdavidn}
23422208Sdavidn
23522208Sdavidn
23622208Sdavidn/*
23722208Sdavidn * clean a character for display (ctrl/meta character)
23822208Sdavidn */
23922208Sdavidn
24022208Sdavidnstatic char *
24190301Simpcleanchr(char **buf, unsigned char ch)
24222208Sdavidn{
24322208Sdavidn	int l;
24422208Sdavidn	static char tmpbuf[5];
24522208Sdavidn	char * tmp = buf ? *buf : tmpbuf;
24622208Sdavidn
24722208Sdavidn	if (ch & 0x80) {
24822208Sdavidn		strcpy(tmp, "M-");
24922208Sdavidn		l = 2;
25022208Sdavidn		ch &= 0x7f;
25122208Sdavidn	} else
25222208Sdavidn	l = 0;
25322208Sdavidn
25422208Sdavidn	if (ch < 32) {
25522208Sdavidn		tmp[l++] = '^';
25622208Sdavidn		tmp[l++] = ch + '@';
25722208Sdavidn	} else if (ch == 127) {
25822208Sdavidn		tmp[l++] = '^';
25922208Sdavidn		tmp[l++] = '?';
26022208Sdavidn	} else
26122208Sdavidn		tmp[l++] = ch;
26222208Sdavidn	tmp[l] = '\0';
26322208Sdavidn
26422208Sdavidn	if (buf)
26522208Sdavidn		*buf = tmp + l;
26622208Sdavidn	return tmp;
26722208Sdavidn}
26822208Sdavidn
26922208Sdavidn
27022208Sdavidn/*
27122208Sdavidn * clean a string for display (ctrl/meta characters)
27222208Sdavidn */
27322208Sdavidn
27422208Sdavidnstatic char *
27590301Simpcleanstr(const unsigned char *s, int l)
27622208Sdavidn{
27722208Sdavidn	static unsigned char * tmp = NULL;
27822208Sdavidn	static int tmplen = 0;
27922208Sdavidn
28022208Sdavidn	if (tmplen < l * 4 + 1)
28122208Sdavidn		tmp = realloc(tmp, tmplen = l * 4 + 1);
28222208Sdavidn
28322208Sdavidn	if (tmp == NULL) {
28422208Sdavidn		tmplen = 0;
28522208Sdavidn		return (char *)"(mem alloc error)";
28622208Sdavidn	} else {
28722208Sdavidn		int i = 0;
28822208Sdavidn		char * p = tmp;
28922208Sdavidn
29022208Sdavidn		while (i < l)
29122208Sdavidn			cleanchr(&p, s[i++]);
29222208Sdavidn		*p = '\0';
29322208Sdavidn	}
29422208Sdavidn
29522208Sdavidn	return tmp;
29622208Sdavidn}
29722208Sdavidn
29822208Sdavidn
29922208Sdavidn/*
300108470Sschweikh * return result as a pseudo-english word
30122208Sdavidn */
30222208Sdavidn
30322208Sdavidnstatic const char *
30490301Simpresult(int r)
30522208Sdavidn{
30622208Sdavidn	static const char * results[] = {
30722208Sdavidn		"OK", "MEMERROR", "IOERROR", "TIMEOUT"
30822208Sdavidn	};
30922208Sdavidn	return results[r & 3];
31022208Sdavidn}
31122208Sdavidn
31222208Sdavidn
31322208Sdavidn/*
31422208Sdavidn * chat_expect()
31522208Sdavidn * scan input for an expected string
31622208Sdavidn */
31722208Sdavidn
31822208Sdavidnstatic int
31990301Simpchat_expect(const char *str)
32022208Sdavidn{
32122208Sdavidn	int len, r = 0;
32222208Sdavidn
32322208Sdavidn	if (chat_debug & CHATDEBUG_EXPECT)
32422208Sdavidn		syslog(LOG_DEBUG, "chat_expect '%s'", cleanstr(str, strlen(str)));
32522208Sdavidn
32622208Sdavidn	if ((len = strlen(str)) > 0) {
32722208Sdavidn		int i = 0;
32822208Sdavidn		char * got;
32922208Sdavidn
33022208Sdavidn		if ((got = malloc(len + 1)) == NULL)
33122208Sdavidn			r = 1;
33222208Sdavidn		else {
33322208Sdavidn
33422208Sdavidn			memset(got, 0, len+1);
33522208Sdavidn			alarm(chat_alarm);
33622208Sdavidn			alarmed = 0;
33722208Sdavidn
33822208Sdavidn			while (r == 0 && i < len) {
33922208Sdavidn				if (alarmed)
34022208Sdavidn					r = 3;
34122208Sdavidn				else {
34222208Sdavidn					unsigned char ch;
34322208Sdavidn
34422208Sdavidn					if (read(STDIN_FILENO, &ch, 1) == 1) {
34522208Sdavidn
34622208Sdavidn						if (chat_debug & CHATDEBUG_RECEIVE)
34722208Sdavidn							syslog(LOG_DEBUG, "chat_recv '%s' m=%d",
34822208Sdavidn								cleanchr(NULL, ch), i);
34922208Sdavidn
35022208Sdavidn						if (ch == str[i])
35122208Sdavidn							got[i++] = ch;
35222208Sdavidn						else if (i > 0) {
35322208Sdavidn							int j = 1;
35422208Sdavidn
35522208Sdavidn							/* See if we can resync on a
35622208Sdavidn							 * partial match in our buffer
35722208Sdavidn							 */
35822208Sdavidn							while (j < i && memcmp(got + j, str, i - j) != NULL)
35922208Sdavidn								j++;
36022208Sdavidn							if (j < i)
36122208Sdavidn								memcpy(got, got + j, i - j);
36222208Sdavidn							i -= j;
36322208Sdavidn						}
36422208Sdavidn					} else
36522208Sdavidn						r = alarmed ? 3 : 2;
36622208Sdavidn				}
36722208Sdavidn			}
36822208Sdavidn			alarm(0);
36922208Sdavidn        		chat_unalarm();
37022208Sdavidn        		alarmed = 0;
37122208Sdavidn        		free(got);
37222208Sdavidn		}
37322208Sdavidn	}
37422208Sdavidn
37522208Sdavidn	if (chat_debug & CHATDEBUG_EXPECT)
37622208Sdavidn		syslog(LOG_DEBUG, "chat_expect %s", result(r));
37722208Sdavidn
37822208Sdavidn	return r;
37922208Sdavidn}
38022208Sdavidn
38122208Sdavidn
38222208Sdavidn/*
38322208Sdavidn * chat_send()
38422208Sdavidn * send a chat string
38522208Sdavidn */
38622208Sdavidn
38722208Sdavidnstatic int
38890301Simpchat_send(char const *str)
38922208Sdavidn{
39022208Sdavidn	int r = 0;
39122208Sdavidn
39222208Sdavidn	if (chat_debug && CHATDEBUG_SEND)
39322208Sdavidn		syslog(LOG_DEBUG, "chat_send '%s'", cleanstr(str, strlen(str)));
39422208Sdavidn
39522208Sdavidn	if (*str) {
39622208Sdavidn                alarm(chat_alarm);
39722208Sdavidn                alarmed = 0;
39822208Sdavidn                while (r == 0 && *str)
39922208Sdavidn                {
40022208Sdavidn                        unsigned char ch = (unsigned char)*str++;
40122208Sdavidn
40222208Sdavidn                        if (alarmed)
40322208Sdavidn        			r = 3;
40422208Sdavidn                        else if (ch == PAUSE_CH)
40522208Sdavidn				usleep(500000); /* 1/2 second */
40622208Sdavidn			else  {
40722208Sdavidn				usleep(10000);	/* be kind to modem */
40822208Sdavidn                                if (write(STDOUT_FILENO, &ch, 1) != 1)
40922208Sdavidn        		  		r = alarmed ? 3 : 2;
41022208Sdavidn                        }
41122208Sdavidn                }
41222208Sdavidn                alarm(0);
41322208Sdavidn                chat_unalarm();
41422208Sdavidn                alarmed = 0;
41522208Sdavidn	}
41622208Sdavidn
41722208Sdavidn        if (chat_debug & CHATDEBUG_SEND)
41822208Sdavidn          syslog(LOG_DEBUG, "chat_send %s", result(r));
41922208Sdavidn
42022208Sdavidn        return r;
42122208Sdavidn}
42222208Sdavidn
42322208Sdavidn
42422208Sdavidn/*
42522208Sdavidn * getty_chat()
42622208Sdavidn *
42722208Sdavidn * Termination codes:
42822208Sdavidn * -1 - no script supplied
42922208Sdavidn *  0 - script terminated correctly
43022208Sdavidn *  1 - invalid argument, expect string too large, etc.
43122208Sdavidn *  2 - error on an I/O operation or fatal error condition
43222208Sdavidn *  3 - timeout waiting for a simple string
43322208Sdavidn *
43422208Sdavidn * Parameters:
43522208Sdavidn *  char *scrstr     - unparsed chat script
43622208Sdavidn *  timeout          - seconds timeout
43722208Sdavidn *  debug            - debug value (bitmask)
43822208Sdavidn */
43922208Sdavidn
44022208Sdavidnint
44190301Simpgetty_chat(char *scrstr, int timeout, int debug)
44222208Sdavidn{
44322208Sdavidn        int r = -1;
44422208Sdavidn
44522208Sdavidn        chat_alarm = timeout ? timeout : CHAT_DEFAULT_TIMEOUT;
44622208Sdavidn        chat_debug = debug;
44722208Sdavidn
44822208Sdavidn        if (scrstr != NULL) {
44922208Sdavidn                char **script;
45022208Sdavidn
45122208Sdavidn                if (chat_debug & CHATDEBUG_MISC)
45222208Sdavidn			syslog(LOG_DEBUG, "getty_chat script='%s'", scrstr);
45322208Sdavidn
45422208Sdavidn                if ((script = read_chat(&scrstr)) != NULL) {
45522491Sdavidn                        int i = r = 0;
45622491Sdavidn			int off = 0;
45722491Sdavidn                        sig_t old_alarm;
45822208Sdavidn
45922491Sdavidn                        /*
46022491Sdavidn			 * We need to be in raw mode for all this
46122491Sdavidn			 * Rely on caller...
46222491Sdavidn                         */
46322208Sdavidn
46422491Sdavidn                        old_alarm = signal(SIGALRM, chat_alrm);
46522491Sdavidn                        chat_unalarm(); /* Force blocking mode at start */
46622208Sdavidn
46722491Sdavidn			/*
46822491Sdavidn			 * This is the send/expect loop
46922491Sdavidn			 */
47022491Sdavidn                        while (r == 0 && script[i] != NULL)
47122491Sdavidn				if ((r = chat_expect(script[i++])) == 0 && script[i] != NULL)
47222491Sdavidn					r = chat_send(script[i++]);
47322208Sdavidn
47422491Sdavidn                        signal(SIGALRM, old_alarm);
47522491Sdavidn                        free(script);
47622491Sdavidn                        free(scrstr);
47722208Sdavidn
47822491Sdavidn			/*
47922491Sdavidn			 * Ensure stdin is in blocking mode
48022491Sdavidn			 */
48122491Sdavidn                        ioctl(STDIN_FILENO, FIONBIO, &off);
48222208Sdavidn                }
48322208Sdavidn
48422208Sdavidn                if (chat_debug & CHATDEBUG_MISC)
48522208Sdavidn                  syslog(LOG_DEBUG, "getty_chat %s", result(r));
48622208Sdavidn
48722208Sdavidn        }
48822208Sdavidn        return r;
48922208Sdavidn}
490