This is runscript.c in view mode; [Download] [Up]
/* ** Parse and execute dialing script. Ancient history says this was ** based on MMDF, but it wouldn't be recognized now... ** Copyright (c) 1991 Bolt Beranek and Newman, Inc. ** All rights reserved. ** ** Redistribution and use in source and binary forms are permitted ** provided that: (1) source distributions retain this entire copyright ** notice and comment, and (2) distributions including binaries display ** the following acknowledgement: ``This product includes software ** developed by Bolt Beranek and Newman, Inc. and CREN/CSNET'' in the ** documentation or other materials provided with the distribution and in ** all advertising materials mentioning features or use of this software. ** Neither the name of Bolt Beranek and Newman nor CREN/CSNET may be used ** to endorse or promote products derived from this software without ** specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED ** WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include <stdio.h> #include <signal.h> #include <setjmp.h> #include <fcntl.h> #include <ctype.h> #include <errno.h> #include <sys/types.h> #include <sys/param.h> #include <sys/ioctl.h> #include <netinet/in.h> #include "diald.h" #include "dialupip.h" /* Various constants. */ #define MAXFILES 15 #define MAXLINE 256 #define MAXFIELDS 10 /* Maximum number of parameters */ #define MATCHLEN 128 /* Size of text to match in recv */ #define XMITWAIT 60 /* How long to wait on a write */ #define REPBUFSIZE 256 /* Size of replay buffer */ #define SC_SLEEP 0377 /* Char for \x, sleep */ #define SC_BREAK 0376 /* Char for \#, break */ /* Command codes; must not overlap with D_xxx codes */ #define S_ABORT 101 /* Abort the script */ #define S_ALT 102 /* Alternate */ #define S_COMMENT 103 /* Comment */ #define S_GO 104 /* Start the protocol */ #define S_LOG 105 /* Log a message */ #define S_MARK 106 /* Mark the place to start a replay */ #define S_RECV 107 /* Watch for a string */ #define S_REPLAY 108 /* Reuse old input */ #define S_SELEND 109 /* Select end */ #define S_SELST 110 /* Select begin */ #define S_USEFILE 111 /* Read script commands from another file */ #define S_XMIT 112 /* Transmit a string */ #define S_EOF 113 /* EOF in file */ /* Error codes return by commands */ #define D_CONTIN 2 /* Continue with process */ #define D_OK 1 /* Everything is fine */ #define D_NOMATCH 0 /* Input did not match */ #define D_FATAL -1 /* Fatal error */ #define D_NONFATAL -2 /* Possibly-recoverable error */ #define D_INTRPT -3 /* Interrupt during system call */ /* Error codes returned by parsing routines */ #define ERR_NOWORD -1 #define ERR_QUOTE -2 #define ERR_FIELDS -3 #define ERR_BADVAR -4 #define ERR_TOOLONG -5 /* Structure that keeps track of open files. */ typedef struct _OPENFILE { char *scriptname; FILE *scriptfile; int linenumber; int nfields; char *fields[MAXFIELDS]; } OPENFILE; /* Structure that defines a command */ typedef struct _COMMAND { char *Name; int Code; int MinFields; int MaxFields; } COMMAND; static char scriptname[128]; /* Script filename */ static char pushbuffer[MATCHLEN + 2]; /* Pushback buffer for matcher */ static char *pushptr = pushbuffer; /* Pointer into buffer */ static jmp_buf timerest; static int linenumber; /* Line number in script file */ static int use_nfields; /* Number of "use" fields */ static char *use_fields[MAXFIELDS]; /* Current "use" fields */ static OPENFILE *openfiles[MAXFILES]; /* Stack of open files */ static int nopenfiles; /* Number of open files */ static FILE *scriptfile; /* Script file */ static FILE *portfile; /* Modem file */ static FILE *tranfile; /* Transcript file descriptor */ static int transtyle; /* Transcript style */ static int repcount; /* Chars left in replay */ static int repcurr; /* Current replay char */ static int repnext; /* Where to insert next char */ static int repsize; /* Replay buffer size; constant */ static int repfirst; /* First char in replay */ static int replay; /* Doing a replay? */ static int repbuff[REPBUFSIZE]; /* Replay buffer */ static char WHERE[] = "dialscript"; /* Where we are, for logging */ static COMMAND commands[] = { { "abort", S_ABORT, 0, 0 }, { "alternate", S_ALT, 0, 0 }, { "go", S_GO, 0, 0 }, { "log", S_LOG, 1, 1 }, { "mark", S_MARK, 0, 0 }, { "recv", S_RECV, 2, 2 }, { "replay", S_REPLAY, 0, 0 }, { "use", S_USEFILE, 1, 10 }, { "xmit", S_XMIT, 1, 1 }, { "{", S_SELST, 0, 0 }, { "}", S_SELEND, 0, 0 }, { NULL } }; extern char *strerror(); /* ** Report a syntax error. */ static void syntax_error(problem) char *problem; { d_log(DLOG_GENERAL, WHERE, "Syntax error in \"%s\", line %d:\n\t%s\n", scriptname, linenumber, problem); } /* ** Report an I/O error. */ static void io_error(problem) char *problem; { d_log(DLOG_GENERAL, WHERE, "I/O error in \"%s\", line %d:\n\t%s\n", scriptname, linenumber, problem); } /* ** Report a miscellaneous error with errno. */ static void misc_error(format, arg) char *format; char *arg; { char buff[256]; (void)sprintf(buff, "Error in \"%s\", line %d:\n\t%s, %s", scriptname, linenumber, format, strerror(errno)); d_log(DLOG_GENERAL, WHERE, buff, arg); } /* ** Return printable representation of char. */ static char * seechar(c) char c; { static char buff[8]; if (isprint(c) || c == '\n') { buff[0] = c; buff[1] = '\0'; } else (void)sprintf(buff, "\\%03o", c); return buff; } /* ** Parse a C-style escape string. Returns the length of the string ** or -1 on error. */ static int canonstring(in, out) register char *in; register char *out; { int count; char c; char *save; for (save = out; c = *in++; ) if (c != '\\') *out++ = c; else { switch (c = *in++) { default: *out++ = c; break; case 'b': *out++ = '\b'; break; case 'n': *out++ = '\n'; break; case 'r': *out++ = '\r'; break; case 't': *out++ = '\t'; break; case 'x': *out++ = SC_SLEEP; break; case '#': *out++ = SC_BREAK; break; case '\0': return -1; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': for (c -= '0', count = 1; count < 3; count++, in++) { if (*in < '0' || *in > '7') break; c = (c << 3) | (*in - '0'); } *out++ = c; } } *out = '\0'; return out - save; } /* ** Snip the next work out of the buffer. */ static int getword(p, start, next) register char *p; char **start; char **next; { /* Skip leading whitespace, see if there's anything left. */ while (*p == ' ' || *p == '\t') p++; if (*p == '\0') return ERR_NOWORD; if (*p == '"') { for (*start = ++p; *p != '"'; p++) if (*p == '\0') return ERR_QUOTE; *p++ = '\0'; } else { for (*start = p; *p && *p != ' ' && *p != '\t'; ) p++; if (*p) *p++ = '\0'; } *next = p; return D_OK; } #define END (&temp[MAXLINE - 2]) /* ** Handle $-expansion */ static int expanddollars(buff) char *buff; { char temp[MAXLINE]; char *out; char *p; int i; for (out = temp, p = buff; *p; p++) if (*p != '$') { if (out >= END) return ERR_TOOLONG; *out++ = *p; } else if (*++p == '$') { if (out >= END) return ERR_TOOLONG; *out++ = *p; } else { if (!isdigit(*p) || (i = *p - '0') > use_nfields) return ERR_BADVAR; if (out + strlen(use_fields[i]) >= END) return ERR_TOOLONG; out += strlen(strcpy(out, use_fields[i])); } *out++ = '\0'; (void)strcpy(buff, temp); return D_OK; } /* ** Parse a line and break it into fields. Return the number of fields ** or an error. */ static int getfields(buff, maxfields, fields) char *buff; int maxfields; char *fields[]; { char *p; char *next; int i; if (strchr(buff, '$') != NULL && (i = expanddollars(buff)) != D_OK) return i; for (i = 0, p = buff; i < maxfields; i++, p = next) switch (getword(p, &fields[i], &next)) { case ERR_NOWORD: return i; case ERR_QUOTE: return ERR_QUOTE; } return ERR_FIELDS; } /* ** Get a line of text and parse it into a command and fields. Return ** the command number or an error. */ static int getcommand(buff, nfields, fields) char *buff; int *nfields; char *fields[]; { char *p; register COMMAND *cp; do { linenumber++; if (fgets(buff, MAXLINE, scriptfile) == NULL) return S_EOF; if ((p = strchr(buff, '\n')) == NULL) { syntax_error("Line too long"); return D_FATAL; } *p = '\0'; } while (buff[0] == '\0' || buff[0] == '#'); /* Get the first word. */ switch (getword(buff, &fields[0], &p)) { default: return D_FATAL; case ERR_QUOTE: syntax_error("Missing end quote"); return D_FATAL; case D_OK: break; } /* Find the command word. */ for (cp = commands; cp->Name; cp++) if (strcmp(fields[0], cp->Name) == 0) break; if (cp->Name == NULL) { syntax_error("Bad command"); return D_FATAL; } /* Parse the rest of the line. */ *nfields = getfields(p, MAXFIELDS - 1, &fields[1]); if (*nfields < 0) { switch (*nfields) { default: syntax_error("Internal error in parser"); break; case ERR_FIELDS: syntax_error("Too many fields"); break; case ERR_QUOTE: syntax_error("Missing end quote"); break; case ERR_BADVAR: syntax_error("Bad \"$\" variable"); break; case ERR_TOOLONG: syntax_error("Line too long"); break; } return D_FATAL; } /* Make sure the right number of fields are present. */ if (*nfields >= cp->MinFields && *nfields <= cp->MaxFields) return cp->Code; syntax_error("Wrong number of fields"); return D_FATAL; } /* ** Command dispatcher. */ static int dispatch(c, nfields, fields) int c; int nfields; char *fields[]; { int i; switch (c) { default: syntax_error("Unknown command in dispatch"); return D_FATAL; case S_MARK: do_mark(); break; case S_REPLAY: do_replay(); break; case S_LOG: d_log(DLOG_GENERAL, WHERE, "%s", fields[1]); break; case S_COMMENT: break; case S_ABORT: return D_FATAL; case S_GO: return D_OK; case S_ALT: case S_SELST: case S_SELEND: return c; case S_XMIT: return (i = do_xmit(fields[1])) < 0 ? i : D_CONTIN; case S_RECV: return (i = do_recv(fields[1], fields[2])) < 0 ? i : D_CONTIN; case S_USEFILE: return do_use(fields[1], nfields, fields) < 0 ? D_FATAL : D_CONTIN; case S_EOF: /* Unexpected EOF. Revert to previous script if possible. */ return closescript() > 0 ? D_CONTIN : D_FATAL; } return D_CONTIN; } /* ** Execute all commands in a block. */ static int do_block() { char buff[MAXLINE + 2]; char *fields[MAXFIELDS]; int nfields; int c; int i; for ( ; ; ) { if ((c = getcommand(buff, &nfields, fields)) < 0) return c; if ((i = dispatch(c, nfields, fields)) != D_CONTIN) return i; } } /* ** Skip forward until we hit the end of the current select block. */ static int findblockend() { char buff[MAXLINE + 2]; char *fields[MAXFIELDS]; int nfields; int i; for ( ; ; ) { if ((i = getcommand(buff, &nfields, fields)) < 0) return i; switch (i) { case S_SELEND: return S_SELEND; case S_SELST: if ((i = findblockend()) < 0) return i; break; } } } /* ** Find next alternative in current select block. */ static int findnextalt() { char buff[MAXLINE + 2]; char *fields[MAXFIELDS]; int nfields; int i; for ( ; ; ) { if ((i = getcommand(buff, &nfields, fields)) < 0) return i; switch (i) { case S_SELEND: case S_ALT: return i; case S_SELST: if ((findblockend()) < 0) return i; break; } } } static int runloop() { char buff[MAXLINE + 2]; char *fields[MAXFIELDS]; int nfields; int nselect; int i; /* Go for it. */ for (nselect = 0; ; ) switch (i = do_block()) { default: if (i < 0) return i; syntax_error("Internal error in runloop"); return -1; case D_OK: return 0; case D_FATAL: return -1; case D_NONFATAL: /* Possibly-recoverable error. If in a select block, try the * next alternate. */ if (nselect <= 0 || (i = findnextalt()) < 0) return -1; switch (i) { default: syntax_error("Bad syntax in script"); return -1; case S_ALT: continue; case S_SELEND: return -1; } case S_SELST: /* The next line had better be an alternate */ if (getcommand(buff, &nfields, fields) != S_ALT) { syntax_error("Missing \"alternate\" after \"{\""); return -1; } nselect++; continue; case S_SELEND: /* verify that there was a select block being looked at. If so, * then the last alternate was the successful one. Continue * normally. */ if (nselect-- <= 0) { syntax_error("Bad \"}\""); return -1; } continue; case S_ALT: /* First, make sure the context was correct */ if (nselect <= 0) { syntax_error("Bad alternate"); return -1; } /* If we get here, then an alternate within a select was * completed successfully. Move on. */ if ((i = findblockend()) < 0) return -1; nselect--; switch (i) { default: syntax_error("Syntax error in alternate"); return -1; case S_SELEND: continue; } } } /* ** Read lines from the script, parse them, do the actions. */ int runscript(rp, fp, tname) REMOTE *rp; FILE *fp; char *tname; { int i; char *fields[MAXFIELDS]; /* Set up globals. */ portfile = fp; if (tname == NULL) tranfile = NULL; else { tranfile = *tname == '+' ? fopen(++tname, "a") : fopen(tname, "w"); if (tranfile == NULL) { misc_error("Can't create transcript \"%s\"", tname); return -1; } } transtyle = rp->Transtyle; /* Set up the fields; they were parsed as zero-origin, but are * referenced as one-origin. */ for (use_nfields = rp->FieldCount, i = 0; i < use_nfields; i++) fields[i + 1] = use_fields[i + 1] = &rp->FieldData[rp->Fields[i]]; i = do_use(rp->Script, use_nfields, fields) < 0 ? -1 : runloop(); if (tranfile) (void)fclose(tranfile); return i; } /* ** Wait for a given string, with optional timeout. */ static void scripttimeout() { longjmp(timerest, 1); } /* ** Write to the port, with a timeout. */ static int writechar(p) char *p; { int i; if (tranfile && transtyle != TS_LOW) { if (isprint(*p)) (void)fprintf(tranfile, "\tPUT %c (0%03o)\n", *p, *p); else (void)fprintf(tranfile, "\tPUT 0x%02x (0%03o)\n", *p, *p); (void)fflush(tranfile); } (void)signal(SIGALRM, scripttimeout); if (setjmp(timerest) == 0) { (void)alarm(XMITWAIT); i = write(fileno(portfile), p, 1); (void)alarm(0); /* Process the return codes. */ if (i == 1) return D_OK; if (i >= 0) return D_NONFATAL; if (errno != EINTR) return D_FATAL; } io_error("Write time-out"); return D_INTRPT; } /* ** Transmit a string. */ do_xmit(string) char *string; { register int i; register char *p; char buff[MAXLINE]; if (tranfile && transtyle != TS_LOW) { (void)fprintf(tranfile, "XMIT %s\n", string); (void)fflush(tranfile); } /* Make canonical. */ if ((i = canonstring(string, buff)) < 0) { syntax_error("Bad transmit string format"); return D_FATAL; } for (p = buff; --i >= 0; p++) { if ((unsigned char)*p == (unsigned char)SC_SLEEP) { if (tranfile && transtyle != TS_LOW) { (void)fprintf(tranfile, "\tSLEEP\n"); (void)fflush(tranfile); } (void)sleep(1); } else if ((unsigned char)*p == (unsigned char)SC_BREAK) { if (tranfile && transtyle != TS_LOW) { (void)fprintf(tranfile, "\tBREAK\n"); (void)fflush(tranfile); } (void)ioctl(fileno(portfile), TIOCSBRK, (caddr_t)0); (void)sleep(1); (void)ioctl(fileno(portfile), TIOCCBRK, (caddr_t)0); } else if (writechar(p) < 0) return D_FATAL; } return D_OK; } do_recv(p, timestr) char *p; char *timestr; { int length; int i; int timeout; char buff[MAXLINE]; if (tranfile && transtyle != TS_LOW) { (void)fprintf(tranfile, "RECV %s %s\n", p, timestr); (void)fflush(tranfile); } /* Convert the string. */ if ((length = canonstring(p, buff)) < 0) { syntax_error("Bad receive string format"); return D_FATAL; } if (length > MATCHLEN) { syntax_error("Receive string too long"); return D_FATAL; } /* Set up the timer */ if ((timeout = atoi(timestr)) < 1) { syntax_error("Bad timeout value"); return D_FATAL; } (void)signal(SIGALRM, scripttimeout); if (setjmp(timerest) == 0) { /* Do the matching. */ (void)alarm((unsigned int)timeout); while ((i = matchtext(buff)) == D_NOMATCH) (void)getnextchar(); (void)alarm(0); switch (i) { default: return D_FATAL; case D_OK: return D_OK; case D_NONFATAL: io_error("EOF while trying match"); return D_NONFATAL; case D_FATAL: io_error("Read error while trying match"); return D_FATAL; case D_INTRPT: break; } } d_log(DLOG_INFO, WHERE, "No match for \"%s\" after %d seconds", p, timeout); return D_NONFATAL; } #define pushbackchar(c) (*pushptr++ = (c)) /* ** Check for a match between the string and the input stream. */ int matchtext(p) char *p; { int c; int i; if (*p == '\0') return D_OK; if ((c = getnextchar()) < D_OK) return c; if (c == *p) { i = matchtext(++p); if (i < D_OK) pushbackchar(c); return i; } pushbackchar(c); return D_NOMATCH; } /* ** Read characters from the port in raw mode, or the pushback buffer. */ int getnextchar() { int i; char c; /* Use pushback if there is any. */ if (pushptr > pushbuffer) { c = *--pushptr; if (tranfile && transtyle != TS_LOW) { if (isprint(c)) (void)fprintf(tranfile, "\t\tGET %4c (0%03o)\n", c, c); else (void)fprintf(tranfile, "\t\tGET 0x%02x (0%03o)\n", c, c); (void)fflush(tranfile); } return c; } while ((i = readchar()) != EOF) { /* filter out some of the junk characters */ c = toascii(i); if (tranfile && transtyle != TS_LOW) { if (isprint(c)) (void)fprintf(tranfile, "\t\tGET %4c (0%03o)\n", c, c); else (void)fprintf(tranfile, "\t\tGET 0x%02x (0%03o)\n", c, c); (void)fflush(tranfile); } if (c != '\0' && c != '\177') return c; /* Ignore NUL and DEL because they are almost always just noise */ } if (feof(portfile)) return D_NONFATAL; if (errno == EINTR) return D_INTRPT; return D_FATAL; } static char * copystring(p) char *p; { char *new; if ((new = malloc((unsigned int)strlen(p) + 1)) == NULL) { misc_error("Malloc failed; can't copy \"%s\"", p); return NULL; } return strcpy(new, p); } /* ** Open a script. */ int do_use(sname, nfields, fields) char *sname; int nfields; char *fields[]; { register int i; OPENFILE *op; FILE *F; if (nopenfiles > MAXFILES) { syntax_error("Too many \"use\" commands"); return D_FATAL; } /* Get the full filename. */ if (*sname == '/') (void)strcpy(scriptname, sname); else (void)sprintf(scriptname, "%s/%s", CONFIG_DIR, sname); /* Open it. */ if ((F = fopen(scriptname, "r")) == NULL) { misc_error("Can't open script file \"%s\"", scriptname); return D_FATAL; } if (tranfile && transtyle != TS_LOW) { (void)fprintf(tranfile, "USING %s", scriptname); for (i = 0; i < nfields; i++) (void)fprintf(tranfile, " %s", fields[i + 1]); (void)fprintf(tranfile, "\n"); (void)fflush(tranfile); } /* If this isn't the first file, save the current state. */ if (nopenfiles) { if ((op = (OPENFILE *)malloc(sizeof (OPENFILE))) == NULL) { misc_error("No space for openfile \"%s\"", scriptname); return D_FATAL; } op->linenumber = linenumber; op->scriptfile = scriptfile; op->scriptname = copystring(scriptname); for (op->nfields = use_nfields, i = 0; i < use_nfields; i++) op->fields[i] = use_fields[i]; for (use_nfields = nfields - 1, i = 0; i < nfields; i++) use_fields[i] = copystring(fields[i + 1]); openfiles[nopenfiles - 1] = op; } nopenfiles++; scriptfile = F; linenumber = 0; return D_OK; } int closescript() { int i; OPENFILE *op; if (nopenfiles <= 0) return 0; if (scriptfile) (void)fclose(scriptfile); for (i = 0; i < use_nfields; i++) free(use_fields[i]); if (--nopenfiles > 0) { op = openfiles[nopenfiles - 1]; scriptfile = op->scriptfile; (void)strcpy(scriptname, op->scriptname); free(op->scriptname); linenumber = op->linenumber; for (use_nfields = op->nfields, i = 0; i < use_nfields; i++) use_fields[i] = op->fields[i]; free((char *)op); return nopenfiles; } return 0; } /* ** Read characters from the TTY line and store them in a circular ** buffer so that they can be replayed if desired. */ do_replay() { replay = 1; repcurr = repfirst; repcount = repsize; if (tranfile && transtyle != TS_LOW) { (void)fprintf(tranfile, "REPLAY\n"); (void)fflush(tranfile); } } do_mark() { replay = 0; repsize = 0; repfirst = 0; repnext = 0; if (tranfile && transtyle != TS_LOW) { (void)fprintf(tranfile, "SET MARK\n"); (void)fflush(tranfile); } } static int repskipline() { for (; repbuff[repfirst] != '\n' && repsize > 0; repsize--) repfirst++; if (repsize <= 0) do_mark(); else { repfirst++; repsize--; } } int readchar() { register int c; Top: if (replay) { /* Replaying input. Anything left? */ if (repcount == 0) { replay = 0; goto Top; } c = repbuff[repcurr]; if (++repcurr == REPBUFSIZE) repcurr = 0; repcount--; /* Don't replay EOF's. They get in there when a timeout interrupts * a read. */ if (c == EOF) goto Top; } else { if (repsize == REPBUFSIZE) /* Ran out of buffer space. Rather then leave a partial line, * delete the first line in the buffer. */ repskipline(); else repsize++; repbuff[repnext] = c = getc(portfile); if (tranfile) { if (transtyle == TS_LOW) (void)fprintf(tranfile, "%s", seechar(c)); else if (isprint(c)) (void)fprintf(tranfile, "\tREAD %c (0%03o)\n", c, c); else (void)fprintf(tranfile, "\tREAD 0x%02x (0%03o)\n", c, c); (void)fflush(tranfile); } if (++repnext == REPBUFSIZE) repnext = 0; } return c; }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.