freebsd-src/libexec/getty/chat.c

512 lines
10 KiB
C

/*-
* Copyright (c) 1997
* David L Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
*
* Redistribution and use in source and binary forms, with or without
* modification, is permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice immediately at the beginning of the file, without modification,
* this list of conditions, and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. This work was done expressly for inclusion into FreeBSD. Other use
* is permitted provided this notation is included.
* 4. Absolutely no warranty of function or purpose is made by the authors.
* 5. Modifications may be freely made to this file providing the above
* conditions are met.
*
* Modem chat module - send/expect style functions for getty
* For semi-intelligent modem handling.
*/
#ifndef lint
static const char rcsid[] =
"$Id$";
#endif /* not lint */
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/resource.h>
#include <sys/ttydefaults.h>
#include <sys/utsname.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <libutil.h>
#include <locale.h>
#include <setjmp.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <termios.h>
#include <unistd.h>
#include <sys/socket.h>
#include "extern.h"
#define PAUSE_CH (unsigned char)'\xff' /* pause kludge */
#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; /* Default */
static volatile int alarmed = 0;
static void chat_alrm __P((int));
static int chat_unalarm __P((void));
static int getdigit __P((unsigned char **, int, int));
static char **read_chat __P((char **));
static char *cleanchr __P((char **, unsigned char));
static char *cleanstr __P((const unsigned char *, int));
static const char *result __P((int));
static int chat_expect __P((const char *));
static int chat_send __P((char const *));
/*
* alarm signal handler
* handle timeouts in read/write
* change stdin to non-blocking mode to prevent
* possible hang in read().
*/
static void
chat_alrm(signo)
int signo;
{
int on = 1;
alarm(1);
alarmed = 1;
signal(SIGALRM, chat_alrm);
ioctl(STDIN_FILENO, FIONBIO, &on);
}
/*
* Turn back on blocking mode reset by chat_alrm()
*/
static int
chat_unalarm()
{
int off = 0;
return ioctl(STDIN_FILENO, FIONBIO, &off);
}
/*
* convert a string of a given base (octal/hex) to binary
*/
static int
getdigit(ptr, base, max)
unsigned char **ptr;
int base, 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;
}
/*
* read_chat()
* Convert a whitespace delimtied string into an array
* of strings, being expect/send pairs
*/
static char **
read_chat(chatstr)
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 / 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))
{
unsigned char *q, *r;
/* Read escapes */
for (q = r = (unsigned char *)p; *r; ++q)
{
if (*q == '\\')
{
/* handle special escapes */
switch (*++q)
{
case 'a': /* bell */
*r++ = '\a';
break;
case 'r': /* cr */
*r++ = '\r';
break;
case 'n': /* nl */
*r++ = '\n';
break;
case 'f': /* ff */
*r++ = '\f';
break;
case 'b': /* bs */
*r++ = '\b';
break;
case 'e': /* esc */
*r++ = 27;
break;
case 't': /* tab */
*r++ = '\t';
break;
case 'p': /* pause */
*r++ = PAUSE_CH;
break;
case 's':
case 'S': /* space */
*r++ = ' ';
break;
case 'x': /* hexdigit */
++q;
*r++ = getdigit(&q, 16, 2);
--q;
break;
case '0': /* octal */
++q;
*r++ = getdigit(&q, 8, 3);
--q;
break;
default: /* literal */
*r++ = *q;
break;
case 0: /* not past eos */
--q;
break;
}
} else {
/* copy standard character */
*r++ = *q;
}
}
/* Remove surrounding quotes, if any
*/
if (*p == '"' || *p == '\'') {
q = strrchr(p+1, *p);
if (q != NULL && *q == *p && q[1] == '\0') {
*q = '\0';
strcpy(p, p+1);
}
}
res[l++] = p;
}
res[l] = NULL;
*chatstr = tmp;
return res;
}
free(tmp);
}
return res;
}
/*
* clean a character for display (ctrl/meta character)
*/
static char *
cleanchr(buf, ch)
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;
}
/*
* clean a string for display (ctrl/meta characters)
*/
static char *
cleanstr(s, l)
const unsigned char *s;
int l;
{
static unsigned 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 (char *)"(mem alloc error)";
} else {
int i = 0;
char * p = tmp;
while (i < l)
cleanchr(&p, s[i++]);
*p = '\0';
}
return tmp;
}
/*
* return result as an pseudo-english word
*/
static const char *
result(r)
int r;
{
static const char * results[] = {
"OK", "MEMERROR", "IOERROR", "TIMEOUT"
};
return results[r & 3];
}
/*
* chat_expect()
* scan input for an expected string
*/
static int
chat_expect(str)
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;
/* See if we can resync on a
* partial match in our buffer
*/
while (j < i && memcmp(got + j, str, i - j) != NULL)
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;
}
/*
* chat_send()
* send a chat string
*/
static int
chat_send(str)
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); /* 1/2 second */
else {
usleep(10000); /* be kind to modem */
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;
}
/*
* getty_chat()
*
* Termination codes:
* -1 - no script supplied
* 0 - script terminated correctly
* 1 - invalid argument, expect string too large, etc.
* 2 - error on an I/O operation or fatal error condition
* 3 - timeout waiting for a simple string
*
* Parameters:
* char *scrstr - unparsed chat script
* timeout - seconds timeout
* debug - debug value (bitmask)
*/
int
getty_chat(scrstr, timeout, debug)
char *scrstr;
int timeout, 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;
/*
* We need to be in raw mode for all this
* Rely on caller...
*/
old_alarm = signal(SIGALRM, chat_alrm);
chat_unalarm(); /* Force blocking mode at start */
/*
* This is the send/expect loop
*/
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);
/*
* Ensure stdin is in blocking mode
*/
ioctl(STDIN_FILENO, FIONBIO, &off);
}
if (chat_debug & CHATDEBUG_MISC)
syslog(LOG_DEBUG, "getty_chat %s", result(r));
}
return r;
}