5 char *loginv = "Script Command, 9.0.032, 16 Oct 2009";
7 /* C K U S C R -- expect-send script implementation */
10 Copyright (C) 1985, 2009,
11 Trustees of Columbia University in the City of New York.
12 All rights reserved. See the C-Kermit COPYING.TXT file or the
13 copyright text in the ckcmai.c module for disclaimer and permissions.
15 Original (version 1, 1985) author: Herm Fischer, Encino, CA.
16 Contributed to Columbia University in 1985 for inclusion in C-Kermit 4.0.
17 Maintained since 1985 by Frank da Cruz, Columbia University,
20 The module takes a UUCP-style script of the "expect send [expect send] ..."
21 format. It is intended to operate similarly to the way the common
22 UUCP L.sys login entries work. Conditional responses are supported:
23 expect[-send-expect[...]], as with UUCP. The send keyword EOT sends a
24 Control-d, and the keyword BREAK sends a break. Letters prefixed
25 by '~' are '~b' backspace, '~s' space, '~n' linefeed, '~r' return, '~x' xon,
26 '~t' tab, '~q' ? (not allowed on kermit command lines), '~' ~, '~'',
27 '~"', '~c' don't append return, '~o[o[o]]' octal character. As with
28 some uucp systems, sent strings are followed by ~r (not ~n) unless they
29 end with ~c. Null expect strings (e.g., ~0 or --) cause a short
30 delay, and are useful for sending sequences requiring slight pauses.
32 This module calls externally defined system-dependent functions for
33 communications i/o, as defined in ckcplm.txt, the C-Kermit Program Logic
34 Manual, and thus should be portable to all systems that implement those
35 functions, and where alarm() and signal() work as they do in UNIX.
50 _PROTOTYP( VOID flushi, (void) );
51 _PROTOTYP( static VOID myflsh, (void) );
52 _PROTOTYP( static int sequenc, (void) );
53 _PROTOTYP( static VOID recvseq, (void) );
54 _PROTOTYP( static int outseq, (void) );
57 #define signal msignal
63 SIGTYP (*msignal(int type, SIGTYP (*func)(int)))(int);
67 #define signal asignal
69 #define SIGALRM (_NUMSIG+1)
71 SIGTYP (*asignal(int type, SIGTYP (*func)(int)))(int);
72 unsigned aalarm(unsigned);
76 /* VOS doesn't have alarm(), but it does have some things we can work with. */
77 /* however, we have to catch all the signals in one place to do this, so */
78 /* we intercept the signal() routine and call it from our own replacement. */
79 #define signal vsignal
81 SIGTYP (*vsignal(int type, SIGTYP (*func)(int)))(int);
82 int valarm(int interval);
86 extern int local, flow, seslog, mdmtyp, msgflg, duplex, backgrd, secho, quiet;
87 extern int network, nettype, ttnproto;
98 static int is_tn = 0; /* Do Telnet negotiations */
102 extern struct cmdptr *cmdstk;
104 extern struct cmdptr cmdstk[];
106 extern int techo, cmdlvl;
110 static int scr_echo; /* Whether to echo script commands */
112 static int exp_alrm = 15; /* Time to wait for expect string */
113 #define SND_ALRM 15 /* Time to allow for sending string */
114 #define NULL_EXP 2 /* Time to pause on null expect strg*/
115 #define DEL_MSEC 300 /* Milliseconds to pause on ~d */
118 static char seq_buf[SBUFL+2], *s; /* expect-send sequence buffer */
119 static int got_it, no_cr;
121 /* Connect state parent/child communication signal handlers */
125 static sigjmp_buf alrmrng;
127 static jmp_buf alrmrng;
128 #endif /* CK_POSIX_SIG */
130 static ckjmpbuf alrmrng;
135 scrtime(int foo) /* modem read failure handler, */
137 scrtime(foo) int foo; /* Alarm handler */
138 #endif /* CK_ANSIC */
153 cklongjmp(ckjaddr(alrmrng),1);
155 cklongjmp(alrmrng,1);
162 Sequence interpreter -- pick up next sequence from command string,
163 decode escapes and place into seq_buf.
165 If string contains a ~d (delay) then sequenc() returns a 1 expecting
166 to be called again after the ~d executes.
173 no_cr = 0; /* output needs cr appended */
174 for (i = 0; i < SBUFL; ) {
175 if (*s == '\0' || *s == '-' || isspace(*s) ) { /* done */
179 if (*s == '~') { /* escape character */
182 case 'n': seq_buf[i++] = LF; break;
183 case 'r': seq_buf[i++] = CR; break;
184 case 't': seq_buf[i++] = '\t'; break;
185 case 'b': seq_buf[i++] = '\b'; break;
186 case 'q': seq_buf[i++] = '?'; break;
188 /* The default case should catch these now... */
189 case '~': seq_buf[i++] = '~'; break;
190 case '-': seq_buf[i++] = '-'; break;
192 case '\'': seq_buf[i++] = '\''; break;
193 case '\"': seq_buf[i++] = '\"'; break;
194 case 's': seq_buf[i++] = ' '; break;
195 case 'x': seq_buf[i++] = '\021'; break;
196 case 'c': no_cr = 1; break;
197 case 'd': { /* send what we have & then */
198 seq_buf[i] = '\0'; /* expect to send rest after */
199 no_cr = 1; /* sender delays a little */
203 case 'w': { /* wait count */
204 exp_alrm = 15; /* default to 15 sec */
205 if (isdigit(*(s+1))) {
208 if (isdigit(*(s+1)) ) {
210 exp_alrm = exp_alrm * 10 + (*s & 15);
216 if ( isdigit(c) ) { /* octal character */
217 oct_char = (char) (c & 7); /* most significant digit */
218 if (isdigit( *(s+1) ) ) {
220 oct_char = (char) ((oct_char<<3) | ( *s & 7 ));
221 if (isdigit( *(s+1) ) ) {
223 oct_char = (char) ((oct_char<<3) | ( *s & 7 ));
226 seq_buf[i++] = oct_char;
228 } else seq_buf[i++] = *s; /* Treat ~ as quote */
230 } else seq_buf[i++] = *s; /* Plain old character */
234 return(0); /* end of space, return anyway */
238 /* Output buffering for "recvseq" and "flushi" */
240 #define MAXBURST 256 /* maximum size of input burst */
241 static CHAR conbuf[MAXBURST]; /* buffer to hold output for console */
242 static int concnt = 0; /* number of characters buffered */
243 static CHAR sesbuf[MAXBURST]; /* buffer to hold output for session log */
244 static int sescnt = 0; /* number of characters buffered */
249 conxo(concnt, (char *) conbuf);
253 logstr((char *) sesbuf, sescnt);
258 /* these variables are used to pass data between the recvseq() */
259 /* and the dorseq(). They are necessary because in some versions */
260 /* dorseq() is executed in a separate thread and data cannot be */
261 /* passed by parameter. */
263 static char *rseqe, * rseqgot, * rseqtrace ;
268 dorseq(void * threadinfo)
270 dorseq(threadinfo) VOID * threadinfo;
271 #endif /* CK_ANSIC */
274 int burst = 0; /* chars remaining in input burst */
278 if (threadinfo) { /* Thread local storage... */
279 TlsSetValue(TlsIndex,threadinfo);
289 #endif /* CK_LOGIN */
292 for (i = 0; i < rseql-1; i++) rseqgot[i] = rseqgot[i+1];
293 x = ttinc(0); /* Read a character */
294 debug(F101,"recvseq","",x);
297 ckThreadEnd(threadinfo);
299 SIGRETURN; /* Check for error */
303 /* Check for telnet protocol negotiation */
304 if (((x & 0xff) == IAC) && is_tn) { /* Telnet negotiation */
307 switch (tn_doop((CHAR)(x & 0xff),duplex,ttinc)) {
308 case 2: duplex = 0; continue;
315 rseqgot[rseql-1] = (char) (x & 0x7f); /* Got a character */
316 burst--; /* One less waiting */
317 if (scr_echo) conbuf[concnt++] = rseqgot[rseql-1]; /* Buffer it */
318 if (seslog) /* Log it in session log */
320 if (sessft != 0 || rseqgot[rseql-1] != '\r')
323 if (sessft != 0 || rseqgot[rseql-1] != '\012')
326 if (rseqgot[rseql-1]) /* Filter out NULs */
327 sesbuf[sescnt++] = rseqgot[rseql-1];
328 if ((int)strlen(rseqtrace) < SBUFL-2 )
329 strcat(rseqtrace,dbchr(rseqgot[rseql-1]));
330 got_it = (!strncmp(rseqe, rseqgot, rseql));
331 if (burst <= 0) { /* Flush buffered output */
333 if ((burst = ttchk()) < 0) { /* Get size of next input burst */
335 ckThreadEnd(threadinfo);
339 /* prevent overflow of "conbuf" and "sesbuf" */
340 if (burst > MAXBURST)
345 ckThreadEnd(threadinfo);
352 failrseq(void * threadinfo)
354 failrseq(threadinfo) VOID * threadinfo;
355 #endif /* CK_ANSIC */
357 got_it = 0; /* Timed out here */
362 Receive sequence -- see if expected response comes,
363 return success (or failure) in got_it.
367 char *e, got[7], trace[SBUFL];
371 l = (int)strlen(e=seq_buf); /* no more than 7 chars allowed */
376 tlog(F111,"expecting sequence",e,(long) l);
377 if (l == 0) { /* null sequence, delay a little */
380 tlog(F100,"got it (null sequence)","",0L);
384 for (i = 0; i < 7; i++) got[i]='\0';
391 alrm_execute(ckjaddr(alrmrng), exp_alrm, scrtime, dorseq, failrseq);
393 tlog(F110,"received sequence: ",trace,0L);
394 tlog(F101,"returning with got-it code","",(long) got_it);
395 myflsh(); /* Flush buffered output */
400 Output A Sequence starting at pointer s,
402 1 if failed to read (modem hangup or whatever)
404 static int oseqret = 0; /* Return code for outseq */
405 /* Out here to prevent clobbering */
410 dooseq(void * threadinfo)
412 dooseq(threadinfo) VOID * threadinfo;
413 #endif /* CK_ANSIC */
418 extern int tn_nlm, tn_b_nlm;
419 #endif /* TCPSOCKET */
423 if (threadinfo) { /* Thread local storage... */
424 TlsSetValue(TlsIndex,threadinfo);
434 #endif /* CK_LOGIN */
436 l = (int)strlen(seq_buf);
437 tlog(F111,"sending sequence ",seq_buf,(long) l);
439 if (!strcmp(seq_buf,"EOT")) {
441 if (scr_echo) conol("<EOT>");
442 if (seslog && duplex)
444 } else if (!strcmp(seq_buf,"BREAK") ||
445 !strcmp(seq_buf,"\\b") ||
446 !strcmp(seq_buf,"\\B")) {
448 if (scr_echo) conol("<BREAK>");
453 for ( sb = seq_buf; *sb; sb++)
454 *sb = dopar(*sb); /* add parity */
455 ttol((CHAR *)seq_buf,l); /* send it */
456 if (scr_echo && duplex) {
459 { /* Echo to emulator */
462 scriptwrtbuf((USHORT)*s);
469 if (seslog && duplex) /* log it */
470 logstr(seq_buf,strlen(seq_buf));
476 if (!TELOPT_ME(TELOPT_BINARY) && tn_nlm != TNL_CR)
477 ttoc((char)((tn_nlm == TNL_CRLF) ?
478 dopar(LF) : dopar(NUL)));
479 else if (TELOPT_ME(TELOPT_BINARY) &&
480 (tn_b_nlm == TNL_CRLF || tn_b_nlm == TNL_CRNUL))
481 ttoc((char)((tn_b_nlm == TNL_CRLF) ?
482 dopar(LF) : dopar(NUL)));
484 #endif /* TCPSOCKET */
485 if (seslog && duplex)
490 ckThreadEnd(threadinfo);
497 failoseq(void * threadinfo)
499 failoseq(threadinfo) VOID * threadinfo;
500 #endif /* CK_ANSIC */
502 oseqret = -1; /* else -- alarm rang */
510 oseqret = 0; /* Initialize return code */
513 alrm_execute( ckjaddr(alrmrng), SND_ALRM, scrtime, dooseq, failoseq ) ;
518 msleep(DEL_MSEC); /* delay, loop to next send */
524 /* L O G I N -- (historical misnomer) Execute the SCRIPT command */
527 dologin(cmdstr) char *cmdstr; {
531 SIGTYP (* savealm)(int); /* Save incoming alarm function */
533 SIGTYP (* volatile savealm)(int); /* Save incoming alarm function */
536 SIGTYP (*savealm)(); /* Save incoming alarm function */
540 s = cmdstr; /* Make global to this module */
542 tlog(F100,loginv,"",0L);
544 if (speed < 0L) speed = ttgspd();
545 if (ttopen(ttname,&local,mdmtyp,0) < 0) {
546 ckmakmsg(seq_buf,SBUFL,"Sorry, can't open ",ttname,NULL,NULL);
550 /* Whether to echo script commands ... */
551 scr_echo = (!quiet && !backgrd && secho);
553 if (scr_echo && cmdlvl > 1) {
554 if (cmdstk[cmdlvl].src == CMD_TF)
556 if (cmdstk[cmdlvl].src == CMD_MD)
563 printf("Executing SCRIPT to host %s.\n",ttname);
566 printf("Executing SCRIPT through %s, speed %ld.\n",ttname,speed);
569 /* TELNET input must be scanned for IAC */
570 is_tn = (local && network && IS_TELNET()) ||
571 (!local && sstelnet);
575 for (e = s; *e; e++) ckstrncat(seq_buf,dbchr(*e),SBUFL);
577 /* Skip this because it tends to contain a password... */
578 if (scr_echo) printf("SCRIPT string: %s\n",seq_buf);
580 tlog(F110,"SCRIPT string: ",seq_buf, 0L);
582 /* Condition console terminal and communication line... */
584 if (ttvt(speed,flow) < 0) {
585 printf("Sorry, Can't condition communication line\n");
588 /* Save initial timer interrupt value */
589 savealm = signal(SIGALRM,SIG_IGN);
591 flushi(); /* Flush stale input */
593 /* start expect - send sequence */
595 while (*s) { /* While not done with buffer */
597 while (*s && isspace(*s)) s++; /* Skip over separating whitespaces */
598 /* Gather up expect sequence */
602 while (!got_it) { /* Have it yet? */
603 if (*s++ != '-') /* No, is there a conditional send? */
604 goto failret; /* No, return failure */
605 flushi(); /* Yes, flush out input buffer */
606 if (outseq()) /* If unable to send, */
607 goto failret; /* return failure. */
608 if (*s++ != '-') /* If no conditional response here, */
609 goto failret; /* return failure. */
610 recvseq(); /* All OK, read response from host. */
611 } /* Loop back and check got_it */
613 while (*s && !isspace(*s++) ) ; /* Skip over conditionals */
614 while (*s && isspace(*s)) s++; /* Skip over separating whitespaces */
615 flushi(); /* Flush */
616 if (*s) if (outseq()) goto failret; /* If any */
618 signal(SIGALRM,savealm);
619 if (scr_echo) printf("Script successful.\n");
620 tlog(F100,"Script successful.","",0L);
624 signal(SIGALRM,savealm);
625 if (scr_echo) printf("Sorry, script failed\n");
626 tlog(F100,"Script failed","",0L);
630 /* F L U S H I -- Flush, but log, SCRIPT input buffer */
636 seslog /* Logging session? */
637 || scr_echo /* Or console echoing? */
640 /* TELNET input must be scanned for IAC */
645 if ((n = ttchk()) < 0) /* Yes, anything in buffer? */
647 if (n > MAXBURST) n = MAXBURST; /* Make sure not too much, */
648 myflsh(); /* and that buffers are empty. */
650 x = ttinc(0); /* Collect a character */
653 /* Check for telnet protocol negotiation */
654 if (is_tn && ((x & 0xff) == IAC) ) {
655 myflsh(); /* Sync output */
656 switch (tn_doop((CHAR)(x & 0xff),duplex,ttinc)) {
657 case 2: duplex = 0; break;
662 /* Recalculate flush count */
663 if ((n = ttchk()) < 0)
665 if (n > MAXBURST) n = MAXBURST;
670 if (scr_echo) conbuf[concnt++] = (CHAR) x; /* buffer for console */
673 if (sessft != 0 || x != '\r')
676 if (sessft != 0 || x != '\012')
679 sesbuf[sescnt++] = (CHAR) x; /* buffer for session log */
682 } else ttflui(); /* Otherwise just flush. */
686 char *loginv = "Script Command Disabled";
687 #endif /* NOSCRIPT */