freebsd-dev/libexec/getty/chat.c
Stefan Eßer 71bc4af6ed Fix potential buffer overflow and undefined behavior.
The buffer allocated in read_chat() could be 1 element too short, if the
chatstr parameter passed in is 1 or 3 charachters long (e.g. "a" or "a b").
The allocation of the pointer array does not account for the terminating
NULL pointer in that case.

Overlapping source and destination strings are undefined in strcpy().
Instead of moving a string to the left by one character just increment the
char pointer before it is assigned to the results array.

MFC after:	2 weeks
2019-01-26 20:43:28 +00:00

489 lines
9.9 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.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#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' /* 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(int);
static int chat_unalarm(void);
static int getdigit(unsigned char **, int, int);
static char **read_chat(char **);
static char *cleanchr(char **, unsigned char);
static const char *cleanstr(const unsigned char *, int);
static const char *result(int);
static int chat_expect(const char *);
static int chat_send(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(int signo __unused)
{
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(void)
{
int off = 0;
return ioctl(STDIN_FILENO, FIONBIO, &off);
}
/*
* convert a string of a given base (octal/hex) to binary
*/
static int
getdigit(unsigned 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;
}
/*
* read_chat()
* Convert a whitespace delimtied string into an array
* of strings, being expect/send pairs
*/
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))
{
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';
p++;
}
}
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(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 const char *
cleanstr(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 "(mem alloc error)";
} else {
int i = 0;
char * p = tmp;
while (i < l)
cleanchr(&p, s[i++]);
*p = '\0';
}
return tmp;
}
/*
* return result as a pseudo-english word
*/
static const char *
result(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(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) != 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;
}
/*
* chat_send()
* send a chat string
*/
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); /* 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(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;
/*
* 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;
}