/*- * 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: src/libexec/getty/chat.c,v 1.6 1999/08/28 00:09:34 peter Exp $ * $DragonFly: src/libexec/getty/chat.c,v 1.5 2005/05/07 20:00:20 corecode Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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) { 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 / 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(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(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(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) != 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(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; }