#include "ckcsym.h" #ifndef NOICP #ifndef NOSCRIPT char *loginv = "Script Command, 7.0.030, 21 Oct 1998"; /* C K U S C R -- expect-send script implementation */ /* Copyright (C) 1985, 2000, Trustees of Columbia University in the City of New York. All rights reserved. See the C-Kermit COPYING.TXT file or the copyright text in the ckcmai.c module for disclaimer and permissions. Original (version 1, 1985) author: Herm Fischer, Encino, CA. Contributed to Columbia University in 1985 for inclusion in C-Kermit 4.0. Maintained since 1985 by Frank da Cruz, Columbia University, fdc@columbia.edu. The module takes a UUCP-style script of the "expect send [expect send] ..." format. It is intended to operate similarly to the way the common UUCP L.sys login entries work. Conditional responses are supported: expect[-send-expect[...]], as with UUCP. The send keyword EOT sends a Control-d, and the keyword BREAK sends a break. Letters prefixed by '~' are '~b' backspace, '~s' space, '~n' linefeed, '~r' return, '~x' xon, '~t' tab, '~q' ? (not allowed on kermit command lines), '~' ~, '~'', '~"', '~c' don't append return, '~o[o[o]]' octal character. As with some uucp systems, sent strings are followed by ~r (not ~n) unless they end with ~c. Null expect strings (e.g., ~0 or --) cause a short delay, and are useful for sending sequences requiring slight pauses. This module calls externally defined system-dependent functions for communications i/o, as defined in CKCPLM.DOC, the C-Kermit Program Logic Manual, and thus should be portable to all systems that implement those functions, and where alarm() and signal() work as they do in UNIX. */ #include "ckcdeb.h" #include #ifdef NT #include #else /* NT */ #include #endif /* NT */ #include "ckcasc.h" #include "ckcker.h" #include "ckuusr.h" #include "ckcnet.h" #include "ckcsig.h" _PROTOTYP( VOID flushi, (void) ); _PROTOTYP( static VOID myflsh, (void) ); _PROTOTYP( static int sequenc, (void) ); _PROTOTYP( static VOID recvseq, (void) ); _PROTOTYP( static int outseq, (void) ); #ifdef MAC #define signal msignal #define SIGTYP long #define alarm malarm #define SIG_IGN 0 #define SIGALRM 1 #define SIGINT 2 SIGTYP (*msignal(int type, SIGTYP (*func)(int)))(int); #endif /* MAC */ #ifdef AMIGA #define signal asignal #define alarm aalarm #define SIGALRM (_NUMSIG+1) #define SIGTYP void SIGTYP (*asignal(int type, SIGTYP (*func)(int)))(int); unsigned aalarm(unsigned); #endif /* AMIGA */ #ifdef STRATUS /* VOS doesn't have alarm(), but it does have some things we can work with. */ /* however, we have to catch all the signals in one place to do this, so */ /* we intercept the signal() routine and call it from our own replacement. */ #define signal vsignal #define alarm valarm SIGTYP (*vsignal(int type, SIGTYP (*func)(int)))(int); int valarm(int interval); #endif /* STRATUS */ extern int sessft; extern int local, flow, seslog, mdmtyp, msgflg, duplex, backgrd, secho, quiet; extern int network, ttnproto; extern long speed; extern char ttname[]; #ifdef NTSIG extern int TlsIndex; #endif /* NTSIG */ #ifdef IKSD extern int inserver; #endif /* IKSD */ static int is_tn = 0; /* Do Telnet negotiations */ #ifndef NOSPL #ifdef DCMDBUF extern struct cmdptr *cmdstk; #else extern struct cmdptr cmdstk[]; #endif /* DCMDBUF */ extern int techo, cmdlvl; extern int mecho; #endif /* NOSPL */ static int scr_echo; /* Whether to echo script commands */ static int exp_alrm = 15; /* Time to wait for expect string */ #define SND_ALRM 15 /* Time to allow for sending string */ #define NULL_EXP 2 /* Time to pause on null expect strg*/ #define DEL_MSEC 300 /* Milliseconds to pause on ~d */ #define SBUFL 512 static char seq_buf[SBUFL], *s; /* expect-send sequence buffer */ static int got_it, no_cr; /* Connect state parent/child communication signal handlers */ #ifdef COMMENT #ifdef CK_POSIX_SIG static sigjmp_buf alrmrng; #else static jmp_buf alrmrng; #endif /* CK_POSIX_SIG */ #else static ckjmpbuf alrmrng; #endif /* COMMENT */ static SIGTYP #ifdef CK_ANSIC scrtime(int foo) /* modem read failure handler, */ #else scrtime(foo) int foo; /* Alarm handler */ #endif /* CK_ANSIC */ /* scrtime */ { #ifdef BEBOX #ifdef BE_DR_7 alarm_expired(); #endif /* BE_DR_7 */ #endif /* BEBOX */ #ifdef NTSIG if (foo == SIGALRM) PostAlarmSigSem(); else PostCtrlCSem(); #else /* NTSIG */ #ifdef NT cklongjmp(ckjaddr(alrmrng),1); #else /* NT */ cklongjmp(alrmrng,1); #endif /* NT */ #endif /* NTSIG */ SIGRETURN; } /* Sequence interpreter -- pick up next sequence from command string, decode escapes and place into seq_buf. If string contains a ~d (delay) then sequenc() returns a 1 expecting to be called again after the ~d executes. */ static int sequenc() { int i; char c, oct_char; no_cr = 0; /* output needs cr appended */ for (i = 0; i < SBUFL; ) { if (*s == '\0' || *s == '-' || isspace(*s) ) { /* done */ seq_buf[i] = '\0'; return(0) ; } if (*s == '~') { /* escape character */ s++; switch (c = *s) { case 'n': seq_buf[i++] = LF; break; case 'r': seq_buf[i++] = CR; break; case 't': seq_buf[i++] = '\t'; break; case 'b': seq_buf[i++] = '\b'; break; case 'q': seq_buf[i++] = '?'; break; #ifdef COMMENT /* The default case should catch these now... */ case '~': seq_buf[i++] = '~'; break; case '-': seq_buf[i++] = '-'; break; #endif /* COMMENT */ case '\'': seq_buf[i++] = '\''; break; case '\"': seq_buf[i++] = '\"'; break; case 's': seq_buf[i++] = ' '; break; case 'x': seq_buf[i++] = '\021'; break; case 'c': no_cr = 1; break; case 'd': { /* send what we have & then */ seq_buf[i] = '\0'; /* expect to send rest after */ no_cr = 1; /* sender delays a little */ s++; return(1); } case 'w': { /* wait count */ exp_alrm = 15; /* default to 15 sec */ if (isdigit(*(s+1))) { s++; exp_alrm = *s & 15; if (isdigit(*(s+1)) ) { s++; exp_alrm = exp_alrm * 10 + (*s & 15); } } break; } default: if ( isdigit(c) ) { /* octal character */ oct_char = (char) (c & 7); /* most significant digit */ if (isdigit( *(s+1) ) ) { s++; oct_char = (char) ((oct_char<<3) | ( *s & 7 )); if (isdigit( *(s+1) ) ) { s++; oct_char = (char) ((oct_char<<3) | ( *s & 7 )); } } seq_buf[i++] = oct_char; break; } else seq_buf[i++] = *s; /* Treat ~ as quote */ } } else seq_buf[i++] = *s; /* Plain old character */ s++; } seq_buf[i] = '\0'; return(0); /* end of space, return anyway */ } /* Output buffering for "recvseq" and "flushi" */ #define MAXBURST 256 /* maximum size of input burst */ static CHAR conbuf[MAXBURST]; /* buffer to hold output for console */ static int concnt = 0; /* number of characters buffered */ static CHAR sesbuf[MAXBURST]; /* buffer to hold output for session log */ static int sescnt = 0; /* number of characters buffered */ static VOID myflsh() { if (concnt > 0) { conxo(concnt, (char *) conbuf); concnt = 0; } if (sescnt > 0) { logstr((char *) sesbuf, sescnt); sescnt = 0; } } /* these variables are used to pass data between the recvseq() */ /* and the dorseq(). They are necessary because in some versions */ /* dorseq() is executed in a separate thread and data cannot be */ /* passed by parameter. */ static char *rseqe, * rseqgot, * rseqtrace ; static int rseql; static SIGTYP #ifdef CK_ANSIC dorseq(void * threadinfo) #else /* CK_ANSIC */ dorseq(threadinfo) VOID * threadinfo; #endif /* CK_ANSIC */ /* dorseq */ { int i, x; int burst = 0; /* chars remaining in input burst */ #ifdef NTSIG setint(); if (threadinfo) { /* Thread local storage... */ TlsSetValue(TlsIndex,threadinfo); } #endif /* NTSIG */ #ifdef CK_LOGIN #ifdef NT #ifdef IKSD if (inserver) setntcreds(); #endif /* IKSD */ #endif /* NT */ #endif /* CK_LOGIN */ while (!got_it) { for (i = 0; i < rseql-1; i++) rseqgot[i] = rseqgot[i+1]; x = ttinc(0); /* Read a character */ debug(F101,"recvseq","",x); if (x < 0) { #ifdef NTSIG ckThreadEnd(threadinfo); #endif /* NTSIG */ SIGRETURN; /* Check for error */ } #ifdef NETCONN #ifdef TNCODE /* Check for telnet protocol negotiation */ if (((x & 0xff) == IAC) && is_tn) { /* Telnet negotiation */ myflsh(); burst = 0; switch (tn_doop((CHAR)(x & 0xff),duplex,ttinc)) { case 2: duplex = 0; continue; case 1: duplex = 1; default: continue; } } #endif /* TNCODE */ #endif /* NETCONN */ rseqgot[rseql-1] = (char) (x & 0x7f); /* Got a character */ burst--; /* One less waiting */ if (scr_echo) conbuf[concnt++] = rseqgot[rseql-1]; /* Buffer it */ if (seslog) /* Log it in session log */ #ifdef UNIX if (sessft != 0 || rseqgot[rseql-1] != '\r') #else #ifdef OSK if (sessft != 0 || rseqgot[rseql-1] != '\012') #endif /* OSK */ #endif /* UNIX */ if (rseqgot[rseql-1]) /* Filter out NULs */ sesbuf[sescnt++] = rseqgot[rseql-1]; if ((int)strlen(rseqtrace) < SBUFL-2 ) strcat(rseqtrace,dbchr(rseqgot[rseql-1])); got_it = (!strncmp(rseqe, rseqgot, rseql)); if (burst <= 0) { /* Flush buffered output */ myflsh(); if ((burst = ttchk()) < 0) { /* Get size of next input burst */ #ifdef NTSIG ckThreadEnd(threadinfo); #endif /* NTSIG */ SIGRETURN; } /* prevent overflow of "conbuf" and "sesbuf" */ if (burst > MAXBURST) burst = MAXBURST; } } #ifdef NTSIG ckThreadEnd(threadinfo); #endif /* NTSIG */ SIGRETURN; } static SIGTYP #ifdef CK_ANSIC failrseq(void * threadinfo) #else /* CK_ANSIC */ failrseq(threadinfo) VOID * threadinfo; #endif /* CK_ANSIC */ /* failrseq */ { got_it = 0; /* Timed out here */ SIGRETURN; } /* Receive sequence -- see if expected response comes, return success (or failure) in got_it. */ static VOID recvseq() { char *e, got[7], trace[SBUFL]; int i, l; sequenc(); l = (int)strlen(e=seq_buf); /* no more than 7 chars allowed */ if (l > 7) { e += l-7; l = 7; } tlog(F111,"expecting sequence",e,(long) l); if (l == 0) { /* null sequence, delay a little */ sleep (NULL_EXP); got_it = 1; tlog(F100,"got it (null sequence)","",0L); return; } *trace = '\0'; for (i = 0; i < 7; i++) got[i]='\0'; rseqtrace = trace; rseqe = e; rseqgot = got; rseql = l; alrm_execute(ckjaddr(alrmrng), exp_alrm, scrtime, dorseq, failrseq); tlog(F110,"received sequence: ",trace,0L); tlog(F101,"returning with got-it code","",(long) got_it); myflsh(); /* Flush buffered output */ return; } /* Output A Sequence starting at pointer s, return 0 if okay, 1 if failed to read (modem hangup or whatever) */ static int oseqret = 0; /* Return code for outseq */ /* Out here to prevent clobbering */ /* by longjmp. */ static SIGTYP #ifdef CK_ANSIC dooseq(void * threadinfo) #else /* CK_ANSIC */ dooseq(threadinfo) VOID * threadinfo; #endif /* CK_ANSIC */ { int l; char *sb; #ifdef TCPSOCKET extern int tn_nlm, tn_b_nlm; #endif /* TCPSOCKET */ #ifdef NTSIG setint(); if (threadinfo) { /* Thread local storage... */ TlsSetValue(TlsIndex,threadinfo); } #endif /* NTSIG */ #ifdef CK_LOGIN #ifdef NT #ifdef IKSD if (inserver) setntcreds(); #endif /* IKSD */ #endif /* NT */ #endif /* CK_LOGIN */ l = (int)strlen(seq_buf); tlog(F111,"sending sequence ",seq_buf,(long) l); if (!strcmp(seq_buf,"EOT")) { ttoc(dopar('\004')); if (scr_echo) conol(""); if (seslog && duplex) logstr("",5); } else if (!strcmp(seq_buf,"BREAK") || !strcmp(seq_buf,"\\b") || !strcmp(seq_buf,"\\B")) { ttsndb(); if (scr_echo) conol(""); if (seslog) logstr("{BREAK}",7); } else { if (l > 0) { for ( sb = seq_buf; *sb; sb++) *sb = dopar(*sb); /* add parity */ ttol((CHAR *)seq_buf,l); /* send it */ if (scr_echo && duplex) { #ifdef OS2 { /* Echo to emulator */ char *s = seq_buf; while (*s) { scriptwrtbuf((USHORT)*s); } } #endif /* OS2 */ conxo(l,seq_buf); } if (seslog && duplex) /* log it */ logstr(seq_buf,strlen(seq_buf)); } if (!no_cr) { ttoc( dopar(CR) ); #ifdef TCPSOCKET if (is_tn) { if (!TELOPT_ME(TELOPT_BINARY) && tn_nlm != TNL_CR) ttoc((char)((tn_nlm == TNL_CRLF) ? dopar(LF) : dopar(NUL))); else if (TELOPT_ME(TELOPT_BINARY) && (tn_b_nlm == TNL_CRLF || tn_b_nlm == TNL_CRNUL)) ttoc((char)((tn_b_nlm == TNL_CRLF) ? dopar(LF) : dopar(NUL))); } #endif /* TCPSOCKET */ if (seslog && duplex) logchar(dopar(CR)); } } #ifdef NTSIG ckThreadEnd(threadinfo); #endif /* NTSIG */ SIGRETURN; } SIGTYP #ifdef CK_ANSIC failoseq(void * threadinfo) #else /* CK_ANSIC */ failoseq(threadinfo) VOID * threadinfo; #endif /* CK_ANSIC */ /* failoseq */ { oseqret = -1; /* else -- alarm rang */ SIGRETURN; } static int outseq() { int delay; oseqret = 0; /* Initialize return code */ while(1) { delay = sequenc(); alrm_execute( ckjaddr(alrmrng), SND_ALRM, scrtime, dooseq, failoseq ) ; if (!delay) return(oseqret); #ifndef MAC msleep(DEL_MSEC); /* delay, loop to next send */ #endif /* MAC */ } } /* L O G I N -- (historical misnomer) Execute the SCRIPT command */ int dologin(cmdstr) char *cmdstr; { #ifdef OS2 #ifdef NT SIGTYP (* savealm)(int); /* Save incoming alarm function */ #else /* NT */ SIGTYP (* volatile savealm)(int); /* Save incoming alarm function */ #endif /* NT */ #else /* OS2 */ SIGTYP (*savealm)(); /* Save incoming alarm function */ #endif /* OS2 */ char *e; s = cmdstr; /* Make global to this module */ tlog(F100,loginv,"",0L); if (speed < 0L) speed = ttgspd(); if (ttopen(ttname,&local,mdmtyp,0) < 0) { sprintf(seq_buf,"Sorry, can't open %s",ttname); perror(seq_buf); return(0); } /* Whether to echo script commands ... */ scr_echo = (!quiet && !backgrd && secho); #ifndef NOSPL if (scr_echo && cmdlvl > 1) { if (cmdstk[cmdlvl].src == CMD_TF) scr_echo = techo; if (cmdstk[cmdlvl].src == CMD_MD) scr_echo = mecho; } #endif /* NOSPL */ if (scr_echo) { #ifdef NETCONN if (network) printf("Executing SCRIPT to host %s.\n",ttname); else #endif /* NETCONN */ printf("Executing SCRIPT through %s, speed %ld.\n",ttname,speed); } #ifdef TNCODE /* TELNET input must be scanned for IAC */ is_tn = (local && network && (ttnproto == NP_TELNET)) || (!local && sstelnet); #endif /* TNCODE */ *seq_buf = 0; for (e = s; *e; e++) strcat(seq_buf, dbchr(*e) ); #ifdef COMMENT /* Skip this because it tends to contain a password... */ if (scr_echo) printf("SCRIPT string: %s\n",seq_buf); #endif /* COMMENT */ tlog(F110,"SCRIPT string: ",seq_buf, 0L); /* Condition console terminal and communication line... */ if (ttvt(speed,flow) < 0) { printf("Sorry, Can't condition communication line\n"); return(0); } /* Save initial timer interrupt value */ savealm = signal(SIGALRM,SIG_IGN); flushi(); /* Flush stale input */ /* start expect - send sequence */ while (*s) { /* While not done with buffer */ while (*s && isspace(*s)) s++; /* Skip over separating whitespaces */ /* Gather up expect sequence */ got_it = 0; recvseq(); while (!got_it) { /* Have it yet? */ if (*s++ != '-') /* No, is there a conditional send? */ goto failret; /* No, return failure */ flushi(); /* Yes, flush out input buffer */ if (outseq()) /* If unable to send, */ goto failret; /* return failure. */ if (*s++ != '-') /* If no conditional response here, */ goto failret; /* return failure. */ recvseq(); /* All OK, read response from host. */ } /* Loop back and check got_it */ while (*s && !isspace(*s++) ) ; /* Skip over conditionals */ while (*s && isspace(*s)) s++; /* Skip over separating whitespaces */ flushi(); /* Flush */ if (*s) if (outseq()) goto failret; /* If any */ } signal(SIGALRM,savealm); if (scr_echo) printf("Script successful.\n"); tlog(F100,"Script successful.","",0L); return(1); failret: signal(SIGALRM,savealm); if (scr_echo) printf("Sorry, script failed\n"); tlog(F100,"Script failed","",0L); return(0); } /* F L U S H I -- Flush, but log, SCRIPT input buffer */ VOID flushi() { int n, x; if ( seslog /* Logging session? */ || scr_echo /* Or console echoing? */ #ifdef NETCONN #ifdef TNCODE /* TELNET input must be scanned for IAC */ || is_tn #endif /* TNCODE */ #endif /* NETCONN */ ) { if ((n = ttchk()) < 0) /* Yes, anything in buffer? */ return; if (n > MAXBURST) n = MAXBURST; /* Make sure not too much, */ myflsh(); /* and that buffers are empty. */ while (n-- > 0) { x = ttinc(0); /* Collect a character */ #ifdef NETCONN #ifdef TNCODE /* Check for telnet protocol negotiation */ if (is_tn && ((x & 0xff) == IAC) ) { myflsh(); /* Sync output */ switch (tn_doop((CHAR)(x & 0xff),duplex,ttinc)) { case 2: duplex = 0; break; case 1: duplex = 1; default: break; } /* Recalculate flush count */ if ((n = ttchk()) < 0) return; if (n > MAXBURST) n = MAXBURST; continue; } #endif /* TNCODE */ #endif /* NETCONN */ if (scr_echo) conbuf[concnt++] = (CHAR) x; /* buffer for console */ if (seslog) #ifdef UNIX if (sessft != 0 || x != '\r') #else #ifdef OSK if (sessft != 0 || x != '\012') #endif /* OSK */ #endif /* UNIX */ sesbuf[sescnt++] = (CHAR) x; /* buffer for session log */ } myflsh(); } else ttflui(); /* Otherwise just flush. */ } #else /* NOSCRIPT */ char *loginv = "Script Command Disabled"; #endif /* NOSCRIPT */ #endif /* NOICP */