blob: 98d2fd67ddca2d89db5d56933f6154f8a89fac3b [file] [log] [blame]
#include "ckcsym.h"
#ifndef NOICP
#ifndef NOSCRIPT
char *loginv = "Script Command, 8.0.032, 20 Dec 2001";
/* C K U S C R -- expect-send script implementation */
/*
Copyright (C) 1985, 2004,
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.txt, 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 <signal.h>
#ifdef NT
#include <setjmpex.h>
#else /* NT */
#include <setjmp.h>
#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, nettype, 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+2], *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("<EOT>");
if (seslog && duplex)
logstr("<EOT>",5);
} else if (!strcmp(seq_buf,"BREAK") ||
!strcmp(seq_buf,"\\b") ||
!strcmp(seq_buf,"\\B")) {
ttsndb();
if (scr_echo) conol("<BREAK>");
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) {
#ifndef NOLOCAL
#ifdef OS2
{ /* Echo to emulator */
char *s = seq_buf;
while (*s) {
scriptwrtbuf((USHORT)*s);
}
}
#endif /* OS2 */
#endif /* NOLOCAL */
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) {
ckmakmsg(seq_buf,SBUFL,"Sorry, can't open ",ttname,NULL,NULL);
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 && IS_TELNET()) ||
(!local && sstelnet);
#endif /* TNCODE */
*seq_buf = 0;
for (e = s; *e; e++) ckstrncat(seq_buf,dbchr(*e),SBUFL);
#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 */