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