From c6f2c739399a263085b3a2fede442eb34b352638 Mon Sep 17 00:00:00 2001 From: davidn Date: Sun, 2 Feb 1997 14:24:57 +0000 Subject: Added: ic=expect/send script modem init script ac=expect/send script modem answer script ct#val chat script timeout (seconds) rt#val recycle timeout (seconds) if 'ac' set dc#val debug bitmask for debugging chat scripts hw (boolean) enable crtscts handshaking if=path 'issue' file sent prior login prompt chat.c is a simplistic expect/send chat module. --- libexec/getty/chat.c | 526 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 526 insertions(+) create mode 100644 libexec/getty/chat.c (limited to 'libexec/getty/chat.c') diff --git a/libexec/getty/chat.c b/libexec/getty/chat.c new file mode 100644 index 0000000..97148df --- /dev/null +++ b/libexec/getty/chat.c @@ -0,0 +1,526 @@ +/*- + * Copyright (c) 1997 + * David L Nugent . + * 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. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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) + { + int val; + + 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) { + struct termios tsave_in, tsave_out; + + if (tcgetattr(STDIN_FILENO, &tsave_in) == -1 || + tcgetattr(STDOUT_FILENO, &tsave_out) == -1) { + syslog(LOG_ERR, "tcgetattr() failed in chat: %m"); + r = 2; + } else { + int i = r = 0; + int off = 0; + sig_t old_alarm; + struct termios tneed; + + /* We need to be in raw mode for all this + */ + tneed = tsave_in; + cfmakeraw(&tneed); + tcsetattr(STDIN_FILENO, TCSANOW, &tneed); + tcsetattr(STDOUT_FILENO, TCSANOW, &tneed); + + 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); + + /* Restore flags & previous termios settings + */ + ioctl(STDIN_FILENO, FIONBIO, &off); + tcsetattr(STDIN_FILENO, TCSANOW, &tsave_in); + tcsetattr(STDOUT_FILENO, TCSANOW, &tsave_out); + } + } + + if (chat_debug & CHATDEBUG_MISC) + syslog(LOG_DEBUG, "getty_chat %s", result(r)); + + } + return r; +} -- cgit v1.1