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
26216582Scharnier#include <sys/cdefs.h>
27216582Scharnier__FBSDID("$FreeBSD$");
2831331Scharnier
2991214Sbde#include <sys/types.h>
3022208Sdavidn#include <sys/ioctl.h>
3122208Sdavidn#include <sys/utsname.h>
3291214Sbde
3331331Scharnier#include <ctype.h>
3422208Sdavidn#include <signal.h>
3522208Sdavidn#include <stdlib.h>
3622208Sdavidn#include <string.h>
3722208Sdavidn#include <syslog.h>
3822208Sdavidn#include <unistd.h>
3922208Sdavidn
40144716Sstefanf#include "gettytab.h"
4122208Sdavidn#include "extern.h"
4222208Sdavidn
4322208Sdavidn#define	PAUSE_CH		(unsigned char)'\xff'   /* pause kludge */
4422208Sdavidn
4522208Sdavidn#define	CHATDEBUG_RECEIVE	0x01
4622208Sdavidn#define	CHATDEBUG_SEND		0x02
4722208Sdavidn#define	CHATDEBUG_EXPECT	0x04
4822208Sdavidn#define	CHATDEBUG_MISC		0x08
4922208Sdavidn
5022208Sdavidn#define	CHATDEBUG_DEFAULT	0
5122208Sdavidn#define CHAT_DEFAULT_TIMEOUT	10
5222208Sdavidn
5322208Sdavidn
5422208Sdavidnstatic int chat_debug = CHATDEBUG_DEFAULT;
5522208Sdavidnstatic int chat_alarm = CHAT_DEFAULT_TIMEOUT; /* Default */
5622208Sdavidn
5722208Sdavidnstatic volatile int alarmed = 0;
5822208Sdavidn
5922208Sdavidn
6090301Simpstatic void   chat_alrm(int);
6190301Simpstatic int    chat_unalarm(void);
6290301Simpstatic int    getdigit(unsigned char **, int, int);
6390301Simpstatic char   **read_chat(char **);
6490301Simpstatic char   *cleanchr(char **, unsigned char);
6590301Simpstatic char   *cleanstr(const unsigned char *, int);
6690301Simpstatic const char *result(int);
6790301Simpstatic int    chat_expect(const char *);
6890301Simpstatic int    chat_send(char const *);
6922208Sdavidn
7022208Sdavidn
7122208Sdavidn/*
7222208Sdavidn * alarm signal handler
7322208Sdavidn * handle timeouts in read/write
7422208Sdavidn * change stdin to non-blocking mode to prevent
7522208Sdavidn * possible hang in read().
7622208Sdavidn */
7722208Sdavidn
7822208Sdavidnstatic void
79216582Scharnierchat_alrm(int signo __unused)
8022208Sdavidn{
8122208Sdavidn	int on = 1;
8222208Sdavidn
8322208Sdavidn	alarm(1);
8422208Sdavidn	alarmed = 1;
8522208Sdavidn	signal(SIGALRM, chat_alrm);
8622208Sdavidn	ioctl(STDIN_FILENO, FIONBIO, &on);
8722208Sdavidn}
8822208Sdavidn
8922208Sdavidn
9022208Sdavidn/*
9122208Sdavidn * Turn back on blocking mode reset by chat_alrm()
9222208Sdavidn */
9322208Sdavidn
9422208Sdavidnstatic int
9590301Simpchat_unalarm(void)
9622208Sdavidn{
9722208Sdavidn	int off = 0;
9822208Sdavidn	return ioctl(STDIN_FILENO, FIONBIO, &off);
9922208Sdavidn}
10022208Sdavidn
10122208Sdavidn
10222208Sdavidn/*
10322208Sdavidn * convert a string of a given base (octal/hex) to binary
10422208Sdavidn */
10522208Sdavidn
10622208Sdavidnstatic int
10790301Simpgetdigit(unsigned char **ptr, int base, int max)
10822208Sdavidn{
10922208Sdavidn	int i, val = 0;
11022208Sdavidn	char * q;
11122208Sdavidn
11222208Sdavidn	static const char xdigits[] = "0123456789abcdef";
11322208Sdavidn
11422208Sdavidn	for (i = 0, q = *ptr; i++ < max; ++q) {
11522208Sdavidn		int sval;
11622208Sdavidn		const char * s = strchr(xdigits, tolower(*q));
11722208Sdavidn
11822208Sdavidn		if (s == NULL || (sval = s - xdigits) >= base)
11922208Sdavidn			break;
12022208Sdavidn		val = (val * base) + sval;
12122208Sdavidn	}
12222208Sdavidn	*ptr = q;
12322208Sdavidn	return val;
12422208Sdavidn}
12522208Sdavidn
12622208Sdavidn
12722208Sdavidn/*
12822208Sdavidn * read_chat()
12922208Sdavidn * Convert a whitespace delimtied string into an array
13022208Sdavidn * of strings, being expect/send pairs
13122208Sdavidn */
13222208Sdavidn
13322208Sdavidnstatic char **
13490301Simpread_chat(char **chatstr)
13522208Sdavidn{
13622208Sdavidn	char *str = *chatstr;
13722208Sdavidn	char **res = NULL;
13822208Sdavidn
13922208Sdavidn	if (str != NULL) {
14022208Sdavidn		char *tmp = NULL;
14122208Sdavidn		int l;
14222208Sdavidn
14322208Sdavidn		if ((l=strlen(str)) > 0 && (tmp=malloc(l + 1)) != NULL &&
14422208Sdavidn		    (res=malloc((l / 2 + 1) * sizeof(char *))) != NULL) {
14522208Sdavidn			static char ws[] = " \t";
14622208Sdavidn			char * p;
14722208Sdavidn
14822208Sdavidn			for (l = 0, p = strtok(strcpy(tmp, str), ws);
14922208Sdavidn			     p != NULL;
15022208Sdavidn			     p = strtok(NULL, ws))
15122208Sdavidn			{
15222208Sdavidn				unsigned char *q, *r;
15322208Sdavidn
15422208Sdavidn				/* Read escapes */
15522208Sdavidn				for (q = r = (unsigned char *)p; *r; ++q)
15622208Sdavidn				{
15722208Sdavidn					if (*q == '\\')
15822208Sdavidn					{
15922208Sdavidn						/* handle special escapes */
16022208Sdavidn						switch (*++q)
16122208Sdavidn						{
16222208Sdavidn						case 'a': /* bell */
16322208Sdavidn							*r++ = '\a';
16422208Sdavidn							break;
16522208Sdavidn						case 'r': /* cr */
16622208Sdavidn							*r++ = '\r';
16722208Sdavidn							break;
16822208Sdavidn						case 'n': /* nl */
16922208Sdavidn							*r++ = '\n';
17022208Sdavidn							break;
17122208Sdavidn						case 'f': /* ff */
17222208Sdavidn							*r++ = '\f';
17322208Sdavidn							break;
17422208Sdavidn						case 'b': /* bs */
17522208Sdavidn							*r++ = '\b';
17622208Sdavidn							break;
17722208Sdavidn						case 'e': /* esc */
17822208Sdavidn							*r++ = 27;
17922208Sdavidn							break;
18022208Sdavidn						case 't': /* tab */
18122208Sdavidn							*r++ = '\t';
18222208Sdavidn							break;
18322208Sdavidn						case 'p': /* pause */
18422208Sdavidn							*r++ = PAUSE_CH;
18522208Sdavidn							break;
18622208Sdavidn						case 's':
18722208Sdavidn						case 'S': /* space */
18822208Sdavidn							*r++ = ' ';
18922208Sdavidn							break;
19022208Sdavidn						case 'x': /* hexdigit */
19122208Sdavidn							++q;
19222208Sdavidn							*r++ = getdigit(&q, 16, 2);
19322208Sdavidn							--q;
19422208Sdavidn							break;
19522208Sdavidn						case '0': /* octal */
19622208Sdavidn							++q;
19722208Sdavidn							*r++ = getdigit(&q, 8, 3);
19822208Sdavidn							--q;
19922208Sdavidn							break;
20022208Sdavidn						default: /* literal */
20122208Sdavidn							*r++ = *q;
20222208Sdavidn							break;
20322208Sdavidn						case 0: /* not past eos */
20422208Sdavidn							--q;
20522208Sdavidn							break;
20622208Sdavidn						}
20722208Sdavidn					} else {
20822208Sdavidn						/* copy standard character */
20929003Sdavidn						*r++ = *q;
21022208Sdavidn					}
21122208Sdavidn				}
21222208Sdavidn
21322208Sdavidn				/* Remove surrounding quotes, if any
21422208Sdavidn				 */
21522208Sdavidn				if (*p == '"' || *p == '\'') {
21622208Sdavidn					q = strrchr(p+1, *p);
21722208Sdavidn					if (q != NULL && *q == *p && q[1] == '\0') {
21822208Sdavidn						*q = '\0';
21922208Sdavidn						strcpy(p, p+1);
22022208Sdavidn					}
22122208Sdavidn				}
22222208Sdavidn
22322208Sdavidn				res[l++] = p;
22422208Sdavidn			}
22522208Sdavidn			res[l] = NULL;
22622208Sdavidn			*chatstr = tmp;
22722208Sdavidn			return res;
22822208Sdavidn		}
22922208Sdavidn		free(tmp);
23022208Sdavidn	}
23122208Sdavidn	return res;
23222208Sdavidn}
23322208Sdavidn
23422208Sdavidn
23522208Sdavidn/*
23622208Sdavidn * clean a character for display (ctrl/meta character)
23722208Sdavidn */
23822208Sdavidn
23922208Sdavidnstatic char *
24090301Simpcleanchr(char **buf, unsigned char ch)
24122208Sdavidn{
24222208Sdavidn	int l;
24322208Sdavidn	static char tmpbuf[5];
24422208Sdavidn	char * tmp = buf ? *buf : tmpbuf;
24522208Sdavidn
24622208Sdavidn	if (ch & 0x80) {
24722208Sdavidn		strcpy(tmp, "M-");
24822208Sdavidn		l = 2;
24922208Sdavidn		ch &= 0x7f;
25022208Sdavidn	} else
25122208Sdavidn	l = 0;
25222208Sdavidn
25322208Sdavidn	if (ch < 32) {
25422208Sdavidn		tmp[l++] = '^';
25522208Sdavidn		tmp[l++] = ch + '@';
25622208Sdavidn	} else if (ch == 127) {
25722208Sdavidn		tmp[l++] = '^';
25822208Sdavidn		tmp[l++] = '?';
25922208Sdavidn	} else
26022208Sdavidn		tmp[l++] = ch;
26122208Sdavidn	tmp[l] = '\0';
26222208Sdavidn
26322208Sdavidn	if (buf)
26422208Sdavidn		*buf = tmp + l;
26522208Sdavidn	return tmp;
26622208Sdavidn}
26722208Sdavidn
26822208Sdavidn
26922208Sdavidn/*
27022208Sdavidn * clean a string for display (ctrl/meta characters)
27122208Sdavidn */
27222208Sdavidn
27322208Sdavidnstatic char *
27490301Simpcleanstr(const unsigned char *s, int l)
27522208Sdavidn{
27622208Sdavidn	static unsigned char * tmp = NULL;
27722208Sdavidn	static int tmplen = 0;
27822208Sdavidn
27922208Sdavidn	if (tmplen < l * 4 + 1)
28022208Sdavidn		tmp = realloc(tmp, tmplen = l * 4 + 1);
28122208Sdavidn
28222208Sdavidn	if (tmp == NULL) {
28322208Sdavidn		tmplen = 0;
28422208Sdavidn		return (char *)"(mem alloc error)";
28522208Sdavidn	} else {
28622208Sdavidn		int i = 0;
28722208Sdavidn		char * p = tmp;
28822208Sdavidn
28922208Sdavidn		while (i < l)
29022208Sdavidn			cleanchr(&p, s[i++]);
29122208Sdavidn		*p = '\0';
29222208Sdavidn	}
29322208Sdavidn
29422208Sdavidn	return tmp;
29522208Sdavidn}
29622208Sdavidn
29722208Sdavidn
29822208Sdavidn/*
299108470Sschweikh * return result as a pseudo-english word
30022208Sdavidn */
30122208Sdavidn
30222208Sdavidnstatic const char *
30390301Simpresult(int r)
30422208Sdavidn{
30522208Sdavidn	static const char * results[] = {
30622208Sdavidn		"OK", "MEMERROR", "IOERROR", "TIMEOUT"
30722208Sdavidn	};
30822208Sdavidn	return results[r & 3];
30922208Sdavidn}
31022208Sdavidn
31122208Sdavidn
31222208Sdavidn/*
31322208Sdavidn * chat_expect()
31422208Sdavidn * scan input for an expected string
31522208Sdavidn */
31622208Sdavidn
31722208Sdavidnstatic int
31890301Simpchat_expect(const char *str)
31922208Sdavidn{
32022208Sdavidn	int len, r = 0;
32122208Sdavidn
32222208Sdavidn	if (chat_debug & CHATDEBUG_EXPECT)
32322208Sdavidn		syslog(LOG_DEBUG, "chat_expect '%s'", cleanstr(str, strlen(str)));
32422208Sdavidn
32522208Sdavidn	if ((len = strlen(str)) > 0) {
32622208Sdavidn		int i = 0;
32722208Sdavidn		char * got;
32822208Sdavidn
32922208Sdavidn		if ((got = malloc(len + 1)) == NULL)
33022208Sdavidn			r = 1;
33122208Sdavidn		else {
33222208Sdavidn
33322208Sdavidn			memset(got, 0, len+1);
33422208Sdavidn			alarm(chat_alarm);
33522208Sdavidn			alarmed = 0;
33622208Sdavidn
33722208Sdavidn			while (r == 0 && i < len) {
33822208Sdavidn				if (alarmed)
33922208Sdavidn					r = 3;
34022208Sdavidn				else {
34122208Sdavidn					unsigned char ch;
34222208Sdavidn
34322208Sdavidn					if (read(STDIN_FILENO, &ch, 1) == 1) {
34422208Sdavidn
34522208Sdavidn						if (chat_debug & CHATDEBUG_RECEIVE)
34622208Sdavidn							syslog(LOG_DEBUG, "chat_recv '%s' m=%d",
34722208Sdavidn								cleanchr(NULL, ch), i);
34822208Sdavidn
34922208Sdavidn						if (ch == str[i])
35022208Sdavidn							got[i++] = ch;
35122208Sdavidn						else if (i > 0) {
35222208Sdavidn							int j = 1;
35322208Sdavidn
35422208Sdavidn							/* See if we can resync on a
35522208Sdavidn							 * partial match in our buffer
35622208Sdavidn							 */
357126952Sbde							while (j < i && memcmp(got + j, str, i - j) != 0)
35822208Sdavidn								j++;
35922208Sdavidn							if (j < i)
36022208Sdavidn								memcpy(got, got + j, i - j);
36122208Sdavidn							i -= j;
36222208Sdavidn						}
36322208Sdavidn					} else
36422208Sdavidn						r = alarmed ? 3 : 2;
36522208Sdavidn				}
36622208Sdavidn			}
36722208Sdavidn			alarm(0);
36822208Sdavidn        		chat_unalarm();
36922208Sdavidn        		alarmed = 0;
37022208Sdavidn        		free(got);
37122208Sdavidn		}
37222208Sdavidn	}
37322208Sdavidn
37422208Sdavidn	if (chat_debug & CHATDEBUG_EXPECT)
37522208Sdavidn		syslog(LOG_DEBUG, "chat_expect %s", result(r));
37622208Sdavidn
37722208Sdavidn	return r;
37822208Sdavidn}
37922208Sdavidn
38022208Sdavidn
38122208Sdavidn/*
38222208Sdavidn * chat_send()
38322208Sdavidn * send a chat string
38422208Sdavidn */
38522208Sdavidn
38622208Sdavidnstatic int
38790301Simpchat_send(char const *str)
38822208Sdavidn{
38922208Sdavidn	int r = 0;
39022208Sdavidn
391228582Sdim	if (chat_debug & CHATDEBUG_SEND)
39222208Sdavidn		syslog(LOG_DEBUG, "chat_send '%s'", cleanstr(str, strlen(str)));
39322208Sdavidn
39422208Sdavidn	if (*str) {
39522208Sdavidn                alarm(chat_alarm);
39622208Sdavidn                alarmed = 0;
39722208Sdavidn                while (r == 0 && *str)
39822208Sdavidn                {
39922208Sdavidn                        unsigned char ch = (unsigned char)*str++;
40022208Sdavidn
40122208Sdavidn                        if (alarmed)
40222208Sdavidn        			r = 3;
40322208Sdavidn                        else if (ch == PAUSE_CH)
40422208Sdavidn				usleep(500000); /* 1/2 second */
40522208Sdavidn			else  {
40622208Sdavidn				usleep(10000);	/* be kind to modem */
40722208Sdavidn                                if (write(STDOUT_FILENO, &ch, 1) != 1)
40822208Sdavidn        		  		r = alarmed ? 3 : 2;
40922208Sdavidn                        }
41022208Sdavidn                }
41122208Sdavidn                alarm(0);
41222208Sdavidn                chat_unalarm();
41322208Sdavidn                alarmed = 0;
41422208Sdavidn	}
41522208Sdavidn
41622208Sdavidn        if (chat_debug & CHATDEBUG_SEND)
41722208Sdavidn          syslog(LOG_DEBUG, "chat_send %s", result(r));
41822208Sdavidn
41922208Sdavidn        return r;
42022208Sdavidn}
42122208Sdavidn
42222208Sdavidn
42322208Sdavidn/*
42422208Sdavidn * getty_chat()
42522208Sdavidn *
42622208Sdavidn * Termination codes:
42722208Sdavidn * -1 - no script supplied
42822208Sdavidn *  0 - script terminated correctly
42922208Sdavidn *  1 - invalid argument, expect string too large, etc.
43022208Sdavidn *  2 - error on an I/O operation or fatal error condition
43122208Sdavidn *  3 - timeout waiting for a simple string
43222208Sdavidn *
43322208Sdavidn * Parameters:
43422208Sdavidn *  char *scrstr     - unparsed chat script
43522208Sdavidn *  timeout          - seconds timeout
43622208Sdavidn *  debug            - debug value (bitmask)
43722208Sdavidn */
43822208Sdavidn
43922208Sdavidnint
44090301Simpgetty_chat(char *scrstr, int timeout, int debug)
44122208Sdavidn{
44222208Sdavidn        int r = -1;
44322208Sdavidn
44422208Sdavidn        chat_alarm = timeout ? timeout : CHAT_DEFAULT_TIMEOUT;
44522208Sdavidn        chat_debug = debug;
44622208Sdavidn
44722208Sdavidn        if (scrstr != NULL) {
44822208Sdavidn                char **script;
44922208Sdavidn
45022208Sdavidn                if (chat_debug & CHATDEBUG_MISC)
45122208Sdavidn			syslog(LOG_DEBUG, "getty_chat script='%s'", scrstr);
45222208Sdavidn
45322208Sdavidn                if ((script = read_chat(&scrstr)) != NULL) {
45422491Sdavidn                        int i = r = 0;
45522491Sdavidn			int off = 0;
45622491Sdavidn                        sig_t old_alarm;
45722208Sdavidn
45822491Sdavidn                        /*
45922491Sdavidn			 * We need to be in raw mode for all this
46022491Sdavidn			 * Rely on caller...
46122491Sdavidn                         */
46222208Sdavidn
46322491Sdavidn                        old_alarm = signal(SIGALRM, chat_alrm);
46422491Sdavidn                        chat_unalarm(); /* Force blocking mode at start */
46522208Sdavidn
46622491Sdavidn			/*
46722491Sdavidn			 * This is the send/expect loop
46822491Sdavidn			 */
46922491Sdavidn                        while (r == 0 && script[i] != NULL)
47022491Sdavidn				if ((r = chat_expect(script[i++])) == 0 && script[i] != NULL)
47122491Sdavidn					r = chat_send(script[i++]);
47222208Sdavidn
47322491Sdavidn                        signal(SIGALRM, old_alarm);
47422491Sdavidn                        free(script);
47522491Sdavidn                        free(scrstr);
47622208Sdavidn
47722491Sdavidn			/*
47822491Sdavidn			 * Ensure stdin is in blocking mode
47922491Sdavidn			 */
48022491Sdavidn                        ioctl(STDIN_FILENO, FIONBIO, &off);
48122208Sdavidn                }
48222208Sdavidn
48322208Sdavidn                if (chat_debug & CHATDEBUG_MISC)
48422208Sdavidn                  syslog(LOG_DEBUG, "getty_chat %s", result(r));
48522208Sdavidn
48622208Sdavidn        }
48722208Sdavidn        return r;
48822208Sdavidn}
489