#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/utsname.h>
#include <ctype.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include "gettytab.h"
#include "extern.h"
#define PAUSE_CH (unsigned char)'\xff'
#define CHATDEBUG_RECEIVE 0x01
#define CHATDEBUG_SEND 0x02
#define CHATDEBUG_EXPECT 0x04
#define CHATDEBUG_MISC 0x08
#define CHATDEBUG_DEFAULT 0
#define CHAT_DEFAULT_TIMEOUT 10
static int chat_debug = CHATDEBUG_DEFAULT;
static int chat_alarm = CHAT_DEFAULT_TIMEOUT;
static volatile int alarmed = 0;
static void chat_alrm(int);
static int chat_unalarm(void);
static int getdigit(char **, int, int);
static char **read_chat(char **);
static char *cleanchr(char **, unsigned char);
static const char *cleanstr(const char *, int);
static const char *result(int);
static int chat_expect(const char *);
static int chat_send(char const *);
static void
chat_alrm(int signo __unused)
{
int on = 1;
alarm(1);
alarmed = 1;
signal(SIGALRM, chat_alrm);
ioctl(STDIN_FILENO, FIONBIO, &on);
}
static int
chat_unalarm(void)
{
int off = 0;
return ioctl(STDIN_FILENO, FIONBIO, &off);
}
static int
getdigit(char **ptr, int base, int max)
{
int i, val = 0;
char * q;
static const char xdigits[] = "0123456789abcdef";
for (i = 0, q = *ptr; i++ < max; ++q) {
int sval;
const char * s = strchr(xdigits, tolower(*q));
if (s == NULL || (sval = s - xdigits) >= base)
break;
val = (val * base) + sval;
}
*ptr = q;
return val;
}
static char **
read_chat(char **chatstr)
{
char *str = *chatstr;
char **res = NULL;
if (str != NULL) {
char *tmp = NULL;
int l;
if ((l=strlen(str)) > 0 && (tmp=malloc(l + 1)) != NULL &&
(res=malloc(((l + 1) / 2 + 1) * sizeof(char *))) != NULL) {
static char ws[] = " \t";
char * p;
for (l = 0, p = strtok(strcpy(tmp, str), ws);
p != NULL;
p = strtok(NULL, ws))
{
char *q, *r;
for (q = r = p; *r; ++q)
{
if (*q == '\\')
{
switch (*++q)
{
case 'a':
*r++ = '\a';
break;
case 'r':
*r++ = '\r';
break;
case 'n':
*r++ = '\n';
break;
case 'f':
*r++ = '\f';
break;
case 'b':
*r++ = '\b';
break;
case 'e':
*r++ = 27;
break;
case 't':
*r++ = '\t';
break;
case 'p':
*r++ = PAUSE_CH;
break;
case 's':
case 'S':
*r++ = ' ';
break;
case 'x':
++q;
*r++ = getdigit(&q, 16, 2);
--q;
break;
case '0':
++q;
*r++ = getdigit(&q, 8, 3);
--q;
break;
default:
*r++ = *q;
break;
case 0:
--q;
break;
}
} else {
*r++ = *q;
}
}
if (*p == '"' || *p == '\'') {
q = strrchr(p+1, *p);
if (q != NULL && *q == *p && q[1] == '\0') {
*q = '\0';
p++;
}
}
res[l++] = p;
}
res[l] = NULL;
*chatstr = tmp;
return res;
}
free(tmp);
}
return res;
}
static char *
cleanchr(char **buf, unsigned char ch)
{
int l;
static char tmpbuf[5];
char * tmp = buf ? *buf : tmpbuf;
if (ch & 0x80) {
strcpy(tmp, "M-");
l = 2;
ch &= 0x7f;
} else
l = 0;
if (ch < 32) {
tmp[l++] = '^';
tmp[l++] = ch + '@';
} else if (ch == 127) {
tmp[l++] = '^';
tmp[l++] = '?';
} else
tmp[l++] = ch;
tmp[l] = '\0';
if (buf)
*buf = tmp + l;
return tmp;
}
static const char *
cleanstr(const char *s, int l)
{
static char * tmp = NULL;
static int tmplen = 0;
if (tmplen < l * 4 + 1)
tmp = realloc(tmp, tmplen = l * 4 + 1);
if (tmp == NULL) {
tmplen = 0;
return "(mem alloc error)";
} else {
int i = 0;
char * p = tmp;
while (i < l)
cleanchr(&p, s[i++]);
*p = '\0';
}
return tmp;
}
static const char *
result(int r)
{
static const char * results[] = {
"OK", "MEMERROR", "IOERROR", "TIMEOUT"
};
return results[r & 3];
}
static int
chat_expect(const char *str)
{
int len, r = 0;
if (chat_debug & CHATDEBUG_EXPECT)
syslog(LOG_DEBUG, "chat_expect '%s'", cleanstr(str, strlen(str)));
if ((len = strlen(str)) > 0) {
int i = 0;
char * got;
if ((got = malloc(len + 1)) == NULL)
r = 1;
else {
memset(got, 0, len+1);
alarm(chat_alarm);
alarmed = 0;
while (r == 0 && i < len) {
if (alarmed)
r = 3;
else {
unsigned char ch;
if (read(STDIN_FILENO, &ch, 1) == 1) {
if (chat_debug & CHATDEBUG_RECEIVE)
syslog(LOG_DEBUG, "chat_recv '%s' m=%d",
cleanchr(NULL, ch), i);
if (ch == str[i])
got[i++] = ch;
else if (i > 0) {
int j = 1;
while (j < i && memcmp(got + j, str, i - j) != 0)
j++;
if (j < i)
memcpy(got, got + j, i - j);
i -= j;
}
} else
r = alarmed ? 3 : 2;
}
}
alarm(0);
chat_unalarm();
alarmed = 0;
free(got);
}
}
if (chat_debug & CHATDEBUG_EXPECT)
syslog(LOG_DEBUG, "chat_expect %s", result(r));
return r;
}
static int
chat_send(char const *str)
{
int r = 0;
if (chat_debug & CHATDEBUG_SEND)
syslog(LOG_DEBUG, "chat_send '%s'", cleanstr(str, strlen(str)));
if (*str) {
alarm(chat_alarm);
alarmed = 0;
while (r == 0 && *str)
{
unsigned char ch = (unsigned char)*str++;
if (alarmed)
r = 3;
else if (ch == PAUSE_CH)
usleep(500000);
else {
usleep(10000);
if (write(STDOUT_FILENO, &ch, 1) != 1)
r = alarmed ? 3 : 2;
}
}
alarm(0);
chat_unalarm();
alarmed = 0;
}
if (chat_debug & CHATDEBUG_SEND)
syslog(LOG_DEBUG, "chat_send %s", result(r));
return r;
}
int
getty_chat(char *scrstr, int timeout, int debug)
{
int r = -1;
chat_alarm = timeout ? timeout : CHAT_DEFAULT_TIMEOUT;
chat_debug = debug;
if (scrstr != NULL) {
char **script;
if (chat_debug & CHATDEBUG_MISC)
syslog(LOG_DEBUG, "getty_chat script='%s'", scrstr);
if ((script = read_chat(&scrstr)) != NULL) {
int i = r = 0;
int off = 0;
sig_t old_alarm;
old_alarm = signal(SIGALRM, chat_alrm);
chat_unalarm();
while (r == 0 && script[i] != NULL)
if ((r = chat_expect(script[i++])) == 0 && script[i] != NULL)
r = chat_send(script[i++]);
signal(SIGALRM, old_alarm);
free(script);
free(scrstr);
ioctl(STDIN_FILENO, FIONBIO, &off);
}
if (chat_debug & CHATDEBUG_MISC)
syslog(LOG_DEBUG, "getty_chat %s", result(r));
}
return r;
}