chat.c revision 108470
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 2631331Scharnier#ifndef lint 2731331Scharnierstatic const char rcsid[] = 2850476Speter "$FreeBSD: head/libexec/getty/chat.c 108470 2002-12-30 21:18:15Z schweikh $"; 2931331Scharnier#endif /* not lint */ 3031331Scharnier 3191214Sbde#include <sys/types.h> 3222208Sdavidn#include <sys/ioctl.h> 3322208Sdavidn#include <sys/utsname.h> 3491214Sbde 3531331Scharnier#include <ctype.h> 3622208Sdavidn#include <signal.h> 3722208Sdavidn#include <stdlib.h> 3822208Sdavidn#include <string.h> 3922208Sdavidn#include <syslog.h> 4022208Sdavidn#include <unistd.h> 4122208Sdavidn 4222208Sdavidn#include "extern.h" 4322208Sdavidn 4422208Sdavidn#define PAUSE_CH (unsigned char)'\xff' /* pause kludge */ 4522208Sdavidn 4622208Sdavidn#define CHATDEBUG_RECEIVE 0x01 4722208Sdavidn#define CHATDEBUG_SEND 0x02 4822208Sdavidn#define CHATDEBUG_EXPECT 0x04 4922208Sdavidn#define CHATDEBUG_MISC 0x08 5022208Sdavidn 5122208Sdavidn#define CHATDEBUG_DEFAULT 0 5222208Sdavidn#define CHAT_DEFAULT_TIMEOUT 10 5322208Sdavidn 5422208Sdavidn 5522208Sdavidnstatic int chat_debug = CHATDEBUG_DEFAULT; 5622208Sdavidnstatic int chat_alarm = CHAT_DEFAULT_TIMEOUT; /* Default */ 5722208Sdavidn 5822208Sdavidnstatic volatile int alarmed = 0; 5922208Sdavidn 6022208Sdavidn 6190301Simpstatic void chat_alrm(int); 6290301Simpstatic int chat_unalarm(void); 6390301Simpstatic int getdigit(unsigned char **, int, int); 6490301Simpstatic char **read_chat(char **); 6590301Simpstatic char *cleanchr(char **, unsigned char); 6690301Simpstatic char *cleanstr(const unsigned char *, int); 6790301Simpstatic const char *result(int); 6890301Simpstatic int chat_expect(const char *); 6990301Simpstatic int chat_send(char const *); 7022208Sdavidn 7122208Sdavidn 7222208Sdavidn/* 7322208Sdavidn * alarm signal handler 7422208Sdavidn * handle timeouts in read/write 7522208Sdavidn * change stdin to non-blocking mode to prevent 7622208Sdavidn * possible hang in read(). 7722208Sdavidn */ 7822208Sdavidn 7922208Sdavidnstatic void 8090301Simpchat_alrm(int signo) 8122208Sdavidn{ 8222208Sdavidn int on = 1; 8322208Sdavidn 8422208Sdavidn alarm(1); 8522208Sdavidn alarmed = 1; 8622208Sdavidn signal(SIGALRM, chat_alrm); 8722208Sdavidn ioctl(STDIN_FILENO, FIONBIO, &on); 8822208Sdavidn} 8922208Sdavidn 9022208Sdavidn 9122208Sdavidn/* 9222208Sdavidn * Turn back on blocking mode reset by chat_alrm() 9322208Sdavidn */ 9422208Sdavidn 9522208Sdavidnstatic int 9690301Simpchat_unalarm(void) 9722208Sdavidn{ 9822208Sdavidn int off = 0; 9922208Sdavidn return ioctl(STDIN_FILENO, FIONBIO, &off); 10022208Sdavidn} 10122208Sdavidn 10222208Sdavidn 10322208Sdavidn/* 10422208Sdavidn * convert a string of a given base (octal/hex) to binary 10522208Sdavidn */ 10622208Sdavidn 10722208Sdavidnstatic int 10890301Simpgetdigit(unsigned char **ptr, int base, int max) 10922208Sdavidn{ 11022208Sdavidn int i, val = 0; 11122208Sdavidn char * q; 11222208Sdavidn 11322208Sdavidn static const char xdigits[] = "0123456789abcdef"; 11422208Sdavidn 11522208Sdavidn for (i = 0, q = *ptr; i++ < max; ++q) { 11622208Sdavidn int sval; 11722208Sdavidn const char * s = strchr(xdigits, tolower(*q)); 11822208Sdavidn 11922208Sdavidn if (s == NULL || (sval = s - xdigits) >= base) 12022208Sdavidn break; 12122208Sdavidn val = (val * base) + sval; 12222208Sdavidn } 12322208Sdavidn *ptr = q; 12422208Sdavidn return val; 12522208Sdavidn} 12622208Sdavidn 12722208Sdavidn 12822208Sdavidn/* 12922208Sdavidn * read_chat() 13022208Sdavidn * Convert a whitespace delimtied string into an array 13122208Sdavidn * of strings, being expect/send pairs 13222208Sdavidn */ 13322208Sdavidn 13422208Sdavidnstatic char ** 13590301Simpread_chat(char **chatstr) 13622208Sdavidn{ 13722208Sdavidn char *str = *chatstr; 13822208Sdavidn char **res = NULL; 13922208Sdavidn 14022208Sdavidn if (str != NULL) { 14122208Sdavidn char *tmp = NULL; 14222208Sdavidn int l; 14322208Sdavidn 14422208Sdavidn if ((l=strlen(str)) > 0 && (tmp=malloc(l + 1)) != NULL && 14522208Sdavidn (res=malloc((l / 2 + 1) * sizeof(char *))) != NULL) { 14622208Sdavidn static char ws[] = " \t"; 14722208Sdavidn char * p; 14822208Sdavidn 14922208Sdavidn for (l = 0, p = strtok(strcpy(tmp, str), ws); 15022208Sdavidn p != NULL; 15122208Sdavidn p = strtok(NULL, ws)) 15222208Sdavidn { 15322208Sdavidn unsigned char *q, *r; 15422208Sdavidn 15522208Sdavidn /* Read escapes */ 15622208Sdavidn for (q = r = (unsigned char *)p; *r; ++q) 15722208Sdavidn { 15822208Sdavidn if (*q == '\\') 15922208Sdavidn { 16022208Sdavidn /* handle special escapes */ 16122208Sdavidn switch (*++q) 16222208Sdavidn { 16322208Sdavidn case 'a': /* bell */ 16422208Sdavidn *r++ = '\a'; 16522208Sdavidn break; 16622208Sdavidn case 'r': /* cr */ 16722208Sdavidn *r++ = '\r'; 16822208Sdavidn break; 16922208Sdavidn case 'n': /* nl */ 17022208Sdavidn *r++ = '\n'; 17122208Sdavidn break; 17222208Sdavidn case 'f': /* ff */ 17322208Sdavidn *r++ = '\f'; 17422208Sdavidn break; 17522208Sdavidn case 'b': /* bs */ 17622208Sdavidn *r++ = '\b'; 17722208Sdavidn break; 17822208Sdavidn case 'e': /* esc */ 17922208Sdavidn *r++ = 27; 18022208Sdavidn break; 18122208Sdavidn case 't': /* tab */ 18222208Sdavidn *r++ = '\t'; 18322208Sdavidn break; 18422208Sdavidn case 'p': /* pause */ 18522208Sdavidn *r++ = PAUSE_CH; 18622208Sdavidn break; 18722208Sdavidn case 's': 18822208Sdavidn case 'S': /* space */ 18922208Sdavidn *r++ = ' '; 19022208Sdavidn break; 19122208Sdavidn case 'x': /* hexdigit */ 19222208Sdavidn ++q; 19322208Sdavidn *r++ = getdigit(&q, 16, 2); 19422208Sdavidn --q; 19522208Sdavidn break; 19622208Sdavidn case '0': /* octal */ 19722208Sdavidn ++q; 19822208Sdavidn *r++ = getdigit(&q, 8, 3); 19922208Sdavidn --q; 20022208Sdavidn break; 20122208Sdavidn default: /* literal */ 20222208Sdavidn *r++ = *q; 20322208Sdavidn break; 20422208Sdavidn case 0: /* not past eos */ 20522208Sdavidn --q; 20622208Sdavidn break; 20722208Sdavidn } 20822208Sdavidn } else { 20922208Sdavidn /* copy standard character */ 21029003Sdavidn *r++ = *q; 21122208Sdavidn } 21222208Sdavidn } 21322208Sdavidn 21422208Sdavidn /* Remove surrounding quotes, if any 21522208Sdavidn */ 21622208Sdavidn if (*p == '"' || *p == '\'') { 21722208Sdavidn q = strrchr(p+1, *p); 21822208Sdavidn if (q != NULL && *q == *p && q[1] == '\0') { 21922208Sdavidn *q = '\0'; 22022208Sdavidn strcpy(p, p+1); 22122208Sdavidn } 22222208Sdavidn } 22322208Sdavidn 22422208Sdavidn res[l++] = p; 22522208Sdavidn } 22622208Sdavidn res[l] = NULL; 22722208Sdavidn *chatstr = tmp; 22822208Sdavidn return res; 22922208Sdavidn } 23022208Sdavidn free(tmp); 23122208Sdavidn } 23222208Sdavidn return res; 23322208Sdavidn} 23422208Sdavidn 23522208Sdavidn 23622208Sdavidn/* 23722208Sdavidn * clean a character for display (ctrl/meta character) 23822208Sdavidn */ 23922208Sdavidn 24022208Sdavidnstatic char * 24190301Simpcleanchr(char **buf, unsigned char ch) 24222208Sdavidn{ 24322208Sdavidn int l; 24422208Sdavidn static char tmpbuf[5]; 24522208Sdavidn char * tmp = buf ? *buf : tmpbuf; 24622208Sdavidn 24722208Sdavidn if (ch & 0x80) { 24822208Sdavidn strcpy(tmp, "M-"); 24922208Sdavidn l = 2; 25022208Sdavidn ch &= 0x7f; 25122208Sdavidn } else 25222208Sdavidn l = 0; 25322208Sdavidn 25422208Sdavidn if (ch < 32) { 25522208Sdavidn tmp[l++] = '^'; 25622208Sdavidn tmp[l++] = ch + '@'; 25722208Sdavidn } else if (ch == 127) { 25822208Sdavidn tmp[l++] = '^'; 25922208Sdavidn tmp[l++] = '?'; 26022208Sdavidn } else 26122208Sdavidn tmp[l++] = ch; 26222208Sdavidn tmp[l] = '\0'; 26322208Sdavidn 26422208Sdavidn if (buf) 26522208Sdavidn *buf = tmp + l; 26622208Sdavidn return tmp; 26722208Sdavidn} 26822208Sdavidn 26922208Sdavidn 27022208Sdavidn/* 27122208Sdavidn * clean a string for display (ctrl/meta characters) 27222208Sdavidn */ 27322208Sdavidn 27422208Sdavidnstatic char * 27590301Simpcleanstr(const unsigned char *s, int l) 27622208Sdavidn{ 27722208Sdavidn static unsigned char * tmp = NULL; 27822208Sdavidn static int tmplen = 0; 27922208Sdavidn 28022208Sdavidn if (tmplen < l * 4 + 1) 28122208Sdavidn tmp = realloc(tmp, tmplen = l * 4 + 1); 28222208Sdavidn 28322208Sdavidn if (tmp == NULL) { 28422208Sdavidn tmplen = 0; 28522208Sdavidn return (char *)"(mem alloc error)"; 28622208Sdavidn } else { 28722208Sdavidn int i = 0; 28822208Sdavidn char * p = tmp; 28922208Sdavidn 29022208Sdavidn while (i < l) 29122208Sdavidn cleanchr(&p, s[i++]); 29222208Sdavidn *p = '\0'; 29322208Sdavidn } 29422208Sdavidn 29522208Sdavidn return tmp; 29622208Sdavidn} 29722208Sdavidn 29822208Sdavidn 29922208Sdavidn/* 300108470Sschweikh * return result as a pseudo-english word 30122208Sdavidn */ 30222208Sdavidn 30322208Sdavidnstatic const char * 30490301Simpresult(int r) 30522208Sdavidn{ 30622208Sdavidn static const char * results[] = { 30722208Sdavidn "OK", "MEMERROR", "IOERROR", "TIMEOUT" 30822208Sdavidn }; 30922208Sdavidn return results[r & 3]; 31022208Sdavidn} 31122208Sdavidn 31222208Sdavidn 31322208Sdavidn/* 31422208Sdavidn * chat_expect() 31522208Sdavidn * scan input for an expected string 31622208Sdavidn */ 31722208Sdavidn 31822208Sdavidnstatic int 31990301Simpchat_expect(const char *str) 32022208Sdavidn{ 32122208Sdavidn int len, r = 0; 32222208Sdavidn 32322208Sdavidn if (chat_debug & CHATDEBUG_EXPECT) 32422208Sdavidn syslog(LOG_DEBUG, "chat_expect '%s'", cleanstr(str, strlen(str))); 32522208Sdavidn 32622208Sdavidn if ((len = strlen(str)) > 0) { 32722208Sdavidn int i = 0; 32822208Sdavidn char * got; 32922208Sdavidn 33022208Sdavidn if ((got = malloc(len + 1)) == NULL) 33122208Sdavidn r = 1; 33222208Sdavidn else { 33322208Sdavidn 33422208Sdavidn memset(got, 0, len+1); 33522208Sdavidn alarm(chat_alarm); 33622208Sdavidn alarmed = 0; 33722208Sdavidn 33822208Sdavidn while (r == 0 && i < len) { 33922208Sdavidn if (alarmed) 34022208Sdavidn r = 3; 34122208Sdavidn else { 34222208Sdavidn unsigned char ch; 34322208Sdavidn 34422208Sdavidn if (read(STDIN_FILENO, &ch, 1) == 1) { 34522208Sdavidn 34622208Sdavidn if (chat_debug & CHATDEBUG_RECEIVE) 34722208Sdavidn syslog(LOG_DEBUG, "chat_recv '%s' m=%d", 34822208Sdavidn cleanchr(NULL, ch), i); 34922208Sdavidn 35022208Sdavidn if (ch == str[i]) 35122208Sdavidn got[i++] = ch; 35222208Sdavidn else if (i > 0) { 35322208Sdavidn int j = 1; 35422208Sdavidn 35522208Sdavidn /* See if we can resync on a 35622208Sdavidn * partial match in our buffer 35722208Sdavidn */ 35822208Sdavidn while (j < i && memcmp(got + j, str, i - j) != NULL) 35922208Sdavidn j++; 36022208Sdavidn if (j < i) 36122208Sdavidn memcpy(got, got + j, i - j); 36222208Sdavidn i -= j; 36322208Sdavidn } 36422208Sdavidn } else 36522208Sdavidn r = alarmed ? 3 : 2; 36622208Sdavidn } 36722208Sdavidn } 36822208Sdavidn alarm(0); 36922208Sdavidn chat_unalarm(); 37022208Sdavidn alarmed = 0; 37122208Sdavidn free(got); 37222208Sdavidn } 37322208Sdavidn } 37422208Sdavidn 37522208Sdavidn if (chat_debug & CHATDEBUG_EXPECT) 37622208Sdavidn syslog(LOG_DEBUG, "chat_expect %s", result(r)); 37722208Sdavidn 37822208Sdavidn return r; 37922208Sdavidn} 38022208Sdavidn 38122208Sdavidn 38222208Sdavidn/* 38322208Sdavidn * chat_send() 38422208Sdavidn * send a chat string 38522208Sdavidn */ 38622208Sdavidn 38722208Sdavidnstatic int 38890301Simpchat_send(char const *str) 38922208Sdavidn{ 39022208Sdavidn int r = 0; 39122208Sdavidn 39222208Sdavidn if (chat_debug && CHATDEBUG_SEND) 39322208Sdavidn syslog(LOG_DEBUG, "chat_send '%s'", cleanstr(str, strlen(str))); 39422208Sdavidn 39522208Sdavidn if (*str) { 39622208Sdavidn alarm(chat_alarm); 39722208Sdavidn alarmed = 0; 39822208Sdavidn while (r == 0 && *str) 39922208Sdavidn { 40022208Sdavidn unsigned char ch = (unsigned char)*str++; 40122208Sdavidn 40222208Sdavidn if (alarmed) 40322208Sdavidn r = 3; 40422208Sdavidn else if (ch == PAUSE_CH) 40522208Sdavidn usleep(500000); /* 1/2 second */ 40622208Sdavidn else { 40722208Sdavidn usleep(10000); /* be kind to modem */ 40822208Sdavidn if (write(STDOUT_FILENO, &ch, 1) != 1) 40922208Sdavidn r = alarmed ? 3 : 2; 41022208Sdavidn } 41122208Sdavidn } 41222208Sdavidn alarm(0); 41322208Sdavidn chat_unalarm(); 41422208Sdavidn alarmed = 0; 41522208Sdavidn } 41622208Sdavidn 41722208Sdavidn if (chat_debug & CHATDEBUG_SEND) 41822208Sdavidn syslog(LOG_DEBUG, "chat_send %s", result(r)); 41922208Sdavidn 42022208Sdavidn return r; 42122208Sdavidn} 42222208Sdavidn 42322208Sdavidn 42422208Sdavidn/* 42522208Sdavidn * getty_chat() 42622208Sdavidn * 42722208Sdavidn * Termination codes: 42822208Sdavidn * -1 - no script supplied 42922208Sdavidn * 0 - script terminated correctly 43022208Sdavidn * 1 - invalid argument, expect string too large, etc. 43122208Sdavidn * 2 - error on an I/O operation or fatal error condition 43222208Sdavidn * 3 - timeout waiting for a simple string 43322208Sdavidn * 43422208Sdavidn * Parameters: 43522208Sdavidn * char *scrstr - unparsed chat script 43622208Sdavidn * timeout - seconds timeout 43722208Sdavidn * debug - debug value (bitmask) 43822208Sdavidn */ 43922208Sdavidn 44022208Sdavidnint 44190301Simpgetty_chat(char *scrstr, int timeout, int debug) 44222208Sdavidn{ 44322208Sdavidn int r = -1; 44422208Sdavidn 44522208Sdavidn chat_alarm = timeout ? timeout : CHAT_DEFAULT_TIMEOUT; 44622208Sdavidn chat_debug = debug; 44722208Sdavidn 44822208Sdavidn if (scrstr != NULL) { 44922208Sdavidn char **script; 45022208Sdavidn 45122208Sdavidn if (chat_debug & CHATDEBUG_MISC) 45222208Sdavidn syslog(LOG_DEBUG, "getty_chat script='%s'", scrstr); 45322208Sdavidn 45422208Sdavidn if ((script = read_chat(&scrstr)) != NULL) { 45522491Sdavidn int i = r = 0; 45622491Sdavidn int off = 0; 45722491Sdavidn sig_t old_alarm; 45822208Sdavidn 45922491Sdavidn /* 46022491Sdavidn * We need to be in raw mode for all this 46122491Sdavidn * Rely on caller... 46222491Sdavidn */ 46322208Sdavidn 46422491Sdavidn old_alarm = signal(SIGALRM, chat_alrm); 46522491Sdavidn chat_unalarm(); /* Force blocking mode at start */ 46622208Sdavidn 46722491Sdavidn /* 46822491Sdavidn * This is the send/expect loop 46922491Sdavidn */ 47022491Sdavidn while (r == 0 && script[i] != NULL) 47122491Sdavidn if ((r = chat_expect(script[i++])) == 0 && script[i] != NULL) 47222491Sdavidn r = chat_send(script[i++]); 47322208Sdavidn 47422491Sdavidn signal(SIGALRM, old_alarm); 47522491Sdavidn free(script); 47622491Sdavidn free(scrstr); 47722208Sdavidn 47822491Sdavidn /* 47922491Sdavidn * Ensure stdin is in blocking mode 48022491Sdavidn */ 48122491Sdavidn ioctl(STDIN_FILENO, FIONBIO, &off); 48222208Sdavidn } 48322208Sdavidn 48422208Sdavidn if (chat_debug & CHATDEBUG_MISC) 48522208Sdavidn syslog(LOG_DEBUG, "getty_chat %s", result(r)); 48622208Sdavidn 48722208Sdavidn } 48822208Sdavidn return r; 48922208Sdavidn} 490