1/*
2 *  ***********
3 *  * XCHAT.C *
4 *  ***********
5 *
6 * Extended chat processor for Taylor UUCP. See accompanying documentation.
7 *
8 * Written by:
9 *   Bob Denny (denny@alisa.com)
10 *   Based on code in DECUS UUCP (for VAX/VMS)
11 *
12 * Small modification by:
13 *   Daniel Hagerty (hag@eddie.mit.edu)
14 *
15 * History:
16 *   Version 1.0 shipped with Taylor 1.03. No configuration info inside.
17 *
18 *   Bob Denny - Sun Aug 30 18:41:30 1992
19 *     V1.1 - long overdue changes for other systems. Rip out interval
20 *            timer code, use timer code from Taylor UUCP, use select()
21 *            for timed reads. Use Taylor UUCP "conf.h" file to set
22 *            configuration for this program. Add defaulting of script
23 *            and log file paths.
24 *
25 *   Daniel Hagerty - Mon Nov 22 18:17:38 1993
26 *     V1.2 - Added a new opcode to xchat. "expectstr" is a cross between
27 *            sendstr and expect, looking for a parameter supplied string.
28 *            Useful where a prompt could change for different dial in
29 *            lines and such.
30 *
31 * Bugs:
32 *   Does not support BSD terminal I/O. Anyone care to add it?
33 */
34
35#include <sys/types.h>
36#include <stdio.h>
37#include <string.h>
38#include <ctype.h>
39#include <signal.h>
40#include <time.h>
41#include <sys/ioctl.h>
42#include <sys/termio.h>
43
44#include "xc-conf.h"
45
46/*
47 * Pick a timing routine to use, as done in Taylor UUCP.
48 */
49#if HAVE_USLEEP || HAVE_NAP || HAVE_NAPMS || HAVE_POLL
50#define USE_SELECT_TIMER 0
51#else
52#define USE_SELECT_TIMER HAVE_SELECT
53#if USE_SELECT_TIMER
54#include <sys/time.h>
55#endif
56#endif
57
58#if HAVE_USLEEP || HAVE_NAP || HAVE_NAPMS
59#undef HAVE_POLL
60#define HAVE_POLL 0
61#endif
62
63#if HAVE_USLEEP || HAVE_NAP
64#undef HAVE_NAPMS
65#define HAVE_NAPMS 0
66#endif
67
68#if HAVE_USLEEP
69#undef HAVE_NAP
70#define HAVE_NAP 0
71#endif
72
73static int ttblind();
74static int ttcd();
75
76/* script entry -- "compiled" form of dial, hangup, or login script */
77
78struct script {
79	struct	script	*next;	/* pointer to next entry, or null */
80	int		 opcode;	/* numeric opcode */
81	char		*strprm;	/* pointer to string param */
82	long		 intprm;	/* integer parameter */
83	char		*newstate;	/* new state name */
84};
85
86/* opcode definition array element -- one for each possible opcode */
87
88struct script_opdef {
89	char	*opname;
90	int	 opcode;	/* numeric opcode -- same as array index */
91	int	 prmtype;	/* one of SC_NONE, SC_STR, SC_XSTR, SC_INT */
92	int	 newstate;	/* one of SC_NONE, SC_NWST */
93};
94
95	/* values for opcode */
96
97#define	SC_LABEL 0	/* "label" (state name) */
98#define	SC_CDLY	1	/* set char output delay in msec */
99#define	SC_PCHR	2	/* pause char for dial string (from P in input) */
100#define	SC_PTIM	3	/* seconds to allow for pause char */
101#define	SC_WCHR	4	/* wait char for dial string (from W in input) */
102#define	SC_WTIM	5	/* seconds to allow for wait char */
103#define	SC_ZERO	6	/* zero counter */
104#define	SC_INCR	7	/* increment counter */
105#define SC_IFGT	8	/* change state if counter > int param */
106#define	SC_WAIT	9	/* wait for int param seconds */
107#define	SC_GOTO	10	/* unconditional change to new state */
108#define	SC_SEND	11	/* send strparam (after sprintf substitutions) */
109#define	SC_BRK	12	/* send a break */
110#define	SC_HANG	13	/* drop DTR */
111#define	SC_DIAL	14	/* send telno string (after subst PCHR & WCHR) */
112#define	SC_DTIM	15	/* time in msec per digit (for timeout calculations) */
113			/* default = 100 (one tenth second) */
114#define	SC_CTIM	16	/* additional time (in seconds) to wait for carrier */
115			/* default = 45 seconds */
116#define	SC_EXIT	17	/* script done, success */
117#define	SC_FAIL	18	/* script done, failure */
118#define	SC_LOG	19	/* write strparam to uucp.log */
119#define	SC_LOGE	20	/* write strparam to uucp.log w/error ind */
120#define	SC_DBG	21	/* write strparam to debug log if debug lvl = LGI */
121#define	SC_DBGE	22	/* write strparam to debug log if debug lvl = LGIE */
122#define	SC_DBST	23	/* 'or' intparam into debug mask */
123#define	SC_DBCL	24	/* 'bicl' intparam into debug mask */
124#define	SC_TIMO	25	/* newstate if no match in intparam secs */
125			/* (uses calculated dial time if intparam is 0) */
126#define	SC_XPCT	26	/* wait for strparam, goto _newstate if found */
127#define	SC_CARR	27	/* goto _newstate if carrier detected */
128#define	SC_FLSH	28	/* flush typeahead buffer */
129#define	SC_IFBL	29	/* change state if controller is blind w/o CD */
130#define	SC_IFBG	30	/* chg state if ctlr is blind and counter > intprm */
131#define	SC_SNDP	31	/* send parameter n */
132#define	SC_IF1P	32	/* if parameter n present */
133#define	SC_IF0P	33	/* if parameter n absent */
134#define SC_DBOF 34	/* open debugging file */
135#define SC_TELN 35	/* Set telno from parameter n */
136#define SC_7BIT 36	/* Set port to 7-bit stripping */
137#define SC_8BIT 37	/* Set port for 8-bit characters */
138#define SC_PNON 38	/* Set port for 8-bit, no parity */
139#define SC_PEVN 39	/* Set port for 7-bit, even parity */
140#define SC_PODD 40	/* Set port for 7-bit, odd parity */
141#define SC_HUPS 41	/* Change state on HUP signal */
142#define SC_XPST	42	/* Expect a param string */
143#define	SC_END	43	/* end of array */
144
145	/* values for prmtype, prm2type */
146
147#define	SC_NONE	0		/* no parameter */
148#define	SC_STR	1		/* simple string */
149#define	SC_INT	2		/* integer */
150#define	SC_NWST	3		/* new state name */
151#define	SC_XSTR	4		/* translated string */
152
153/* opcode definition table for dial/login/hangup scripts */
154
155static struct	script_opdef	sc_opdef[] =
156      {
157	{"label",	SC_LABEL,	SC_NONE,	SC_NONE},
158	{"chrdly",	SC_CDLY,	SC_INT,		SC_NONE},
159	{"pchar",	SC_PCHR,	SC_STR,		SC_NONE},
160	{"ptime",	SC_PTIM,	SC_INT,		SC_NONE},
161	{"wchar",	SC_WCHR,	SC_STR,		SC_NONE},
162	{"wtime",	SC_WTIM,	SC_INT,		SC_NONE},
163	{"zero",	SC_ZERO,	SC_NONE,	SC_NONE},
164	{"count",	SC_INCR,	SC_NONE,	SC_NONE},
165	{"ifgtr",	SC_IFGT,	SC_INT,		SC_NWST},
166	{"sleep",	SC_WAIT,	SC_INT,		SC_NONE},
167	{"goto",	SC_GOTO,	SC_NONE,	SC_NWST},
168	{"send",	SC_SEND,	SC_XSTR,	SC_NONE},
169	{"break",	SC_BRK,		SC_NONE,	SC_NONE},
170	{"hangup",	SC_HANG,	SC_NONE,	SC_NONE},
171	{"7bit",	SC_7BIT,	SC_NONE,	SC_NONE},
172	{"8bit",	SC_8BIT,	SC_NONE,	SC_NONE},
173	{"nopar",	SC_PNON,	SC_NONE,	SC_NONE},
174	{"evenpar",	SC_PEVN,	SC_NONE,	SC_NONE},
175	{"oddpar",	SC_PODD,	SC_NONE,	SC_NONE},
176	{"telno",	SC_TELN,	SC_INT,		SC_NONE},
177	{"dial",	SC_DIAL,	SC_NONE,	SC_NONE},
178	{"dgttime",	SC_DTIM,	SC_INT,		SC_NONE},
179	{"ctime",	SC_CTIM,	SC_INT,		SC_NONE},
180	{"success",	SC_EXIT,	SC_NONE,	SC_NONE},
181	{"failed",	SC_FAIL,	SC_NONE,	SC_NONE},
182	{"log",		SC_LOG,		SC_XSTR,	SC_NONE},
183	{"logerr",	SC_LOGE,	SC_XSTR,	SC_NONE},
184	{"debug",	SC_DBG,		SC_XSTR,	SC_NONE},
185	{"debuge",	SC_DBGE,	SC_XSTR,	SC_NONE},
186	{"dbgset",	SC_DBST,	SC_INT,		SC_NONE},
187	{"dbgclr",	SC_DBCL,	SC_INT,		SC_NONE},
188	{"dbgfile",	SC_DBOF,	SC_XSTR,	SC_NONE},
189	{"timeout",	SC_TIMO,	SC_INT,		SC_NWST},
190	{"expect",	SC_XPCT,	SC_XSTR,	SC_NWST},
191	{"ifcarr",	SC_CARR,	SC_NONE,	SC_NWST},
192	{"ifhang",	SC_HUPS,	SC_NONE,	SC_NWST},
193	{"flush",	SC_FLSH,	SC_NONE,	SC_NONE},
194	{"ifblind",	SC_IFBL,	SC_NONE,	SC_NWST},
195	{"ifblgtr",	SC_IFBG,	SC_INT,		SC_NWST},
196	{"sendstr",	SC_SNDP,	SC_INT,		SC_NONE},
197	{"ifstr",	SC_IF1P,	SC_INT,		SC_NWST},
198	{"ifnstr",	SC_IF0P,	SC_INT,		SC_NWST},
199	{"expectstr",	SC_XPST,	SC_INT,		SC_NWST},
200	{"table end",	SC_END,		SC_NONE,	SC_NONE}
201      };
202
203#define SUCCESS 0
204#define	FAIL	1
205#define ERROR	-1
206#define MAX_SCLINE	255	/* max length of a line in a script file */
207#define MAX_EXPCT	127	/* max length of an expect string */
208#define	CTL_DELIM	" \t\n\r" /* Delimiters for tokens */
209#define	SAME		0	/* if (strcmp(a,b) == SAME) ... */
210#define	SLOP		10	/* Slop space on arrays */
211#define	MAX_STRING	200	/* Max length string to send/expect */
212
213#define	DEBUG_LEVEL(level) \
214	   (Debug & (1 << level))
215
216#define	DB_LOG	0	/* error messages and a copy of the LOGFILE output */
217#define	DB_LGIE	1	/* dial,login,init trace -- errors only */
218#define	DB_LGI	2	/* dial,login,init trace -- nonerrors (incl chr I/O) */
219#define	DB_LGII	3	/* script processing internals */
220
221#define TRUE    1
222#define FALSE   0
223
224#define NONE	0
225#define EVEN	1
226#define ODD	2
227
228#define logit(m, p1) fprintf(stderr, "%s %s\n", m, p1)
229
230static char **paramv;		/* Parameter vector */
231static int paramc;		/* Parameter count */
232static char telno[64];		/* Telephone number w/meta-chars */
233static int Debug;
234static int fShangup = FALSE;	/* TRUE if HUP signal received */
235static FILE  *dbf = NULL;
236static struct termio old, new;
237
238extern int usignal();
239extern int uhup();
240
241static struct siglist
242{
243  int signal;
244  int (*o_catcher) ();
245  int (*n_catcher) ();
246} sigtbl[] = {
247             { SIGHUP,   NULL, uhup },
248             { SIGINT,   NULL, usignal },
249	     { SIGIOT,   NULL, usignal },
250             { SIGQUIT,  NULL, usignal },
251             { SIGTERM,  NULL, usignal },
252             { SIGALRM,  NULL, usignal },
253             { 0,        NULL, NULL    }    /* Table end */
254           };
255
256extern struct script *read_script();
257extern void msleep();
258extern char xgetc();
259extern void charlog();
260extern void setup_tty();
261extern void restore_tty();
262extern void ttoslow();
263extern void ttflui();
264extern void tthang();
265extern void ttbreak();
266extern void tt7bit();
267extern void ttpar();
268extern void DEBUG();
269
270extern void *malloc();
271
272
273/*
274 * **********************************
275 * * BEGIN EXECUTION - MAIN PROGRAM *
276 * **********************************
277 *
278 * This program is called by Taylor UUCP with a list of
279 * arguments in argc/argv, and stdin/stdout mapped to the
280 * tty device, and stderr mapped to the Taylor logfile, where
281 * anything written to stdout will be logged as an error.
282 *
283 */
284int main(argc, argv)
285int argc;
286char *argv[];
287{
288  int i, stat;
289  FILE *sf;
290  char sfname[256];
291  struct script *script;
292  struct siglist *sigs;
293
294  /*
295   * The following is needed because my cpp does not have the
296   * #error directive...
297   */
298#if ! HAVE_SELECT
299  no_select_sorry();		/* Sad way to fail make */
300#endif
301
302  paramv = &argv[2];		/* Parameters start at 2nd arg */
303  paramc = argc - 2;		/* Number of live parameters */
304
305  telno[0] = '\0';
306
307  if (argc < 2)
308    {
309      fprintf(stderr, "%s: no script file supplied\n", argv[0]);
310      exit(FAIL);
311    }
312
313  /*
314   * If the script file argument begins with '/', then we assume
315   * it is an absolute pathname, otherwise, we prepend the
316   * SCRIPT_DIR path.
317   */
318  *sfname = '\0';		/* Empty name string */
319  if(argv[1][0] != '/')		/* If relative path */
320    strcat(sfname, SCRIPT_DIR); /* Prepend the default dir. */
321  strcat(sfname, argv[1]);	/* Add the script file name */
322
323  /*
324   * Now open the script file.
325   */
326  if ((sf = fopen(sfname, "r")) == NULL)
327    {
328      fprintf(stderr, "%s: Failed to open script %s\n", argv[0], sfname);
329      perror(" ");
330      exit(FAIL);
331    }
332
333  /*
334   * COMPILE SCRIPT
335   */
336  if ((script = read_script(sf)) == NULL)
337    {
338      fprintf(stderr, "%s: script error in \"%s\"\n", argv[0], argv[1]);
339      exit(FAIL);
340    }
341
342  /*
343   * Set up a signal catcher so the line can be returned to
344   * it's current state if something nasty happens.
345   */
346  sigs = &sigtbl[0];
347  while(sigs->signal)
348    {
349      sigs->o_catcher = (int (*) ())signal(sigs->signal, sigs->n_catcher);
350      sigs += 1;
351    }
352
353  /*
354   * Save current tty settings, then set up raw, single
355   * character input processing, with 7-bit stripping.
356   */
357  setup_tty();
358
359  /*
360   * EXECUTE SCRIPT
361   */
362  if ((stat = do_script(script)) != SUCCESS)
363    fprintf(stderr, "%s: script %s failed.\n", argv[0], argv[1]);
364
365  /*
366   * Clean up and exit.
367   */
368  restore_tty();
369#ifdef FIXSIGS
370  sigs = &sigtbl[0];
371  while(sigs->signal)
372    if(sigs->o_catcher != -1)
373      signal(sigs->signal, sigs->o_catcher);
374#endif
375  exit(stat);
376}
377
378/*
379 * deal_script - deallocate a script and all strings it points to
380 */
381int deal_script(loc)
382struct script *loc;
383{
384  /*
385   * If pointer is null, just exit
386   */
387  if (loc == (struct script *)NULL)
388    return SUCCESS;
389
390  /*
391   * Deallocate the rest of the script
392   */
393  deal_script(loc->next);
394
395  /*
396   * Deallocate the string parameter, if any
397   */
398  if (loc->strprm != (char *)NULL)
399    free(loc->strprm);
400
401  /*
402   * Deallocate the new state name parameter, if any
403   */
404  if (loc->newstate != (char *)NULL)
405    free(loc->newstate);
406
407  /*
408   * Deallocate this entry
409   */
410  free(loc);
411
412  return SUCCESS;
413}
414
415
416/*
417 * read_script
418 *
419 * Read & compile a script, return pointer to first entry, or null if bad
420 */
421struct script *read_script(fd)
422     FILE *fd;
423{
424  struct script	*this = NULL;
425  struct script	*prev = NULL;
426  struct script	*first = NULL;
427  long len, i;
428  char inpline[MAX_SCLINE];
429  char inpcopy[MAX_SCLINE];
430  char *c, *cln, *opc, *cp;
431
432  /*
433   * MAIN COMPILATION LOOP
434   */
435  while ((c = fgets(inpline, (sizeof inpline - 1), fd)) != (char *)NULL)
436    {
437      /*
438       * Skip comments and blank lines
439       */
440      if (*c == '#' || *c == '\n')
441	continue;
442
443      /*
444       * Get rid of the trailing newline, and copy the string
445       */
446      inpline[strlen(inpline)-1] = '\0';
447      strcpy(inpcopy, inpline);
448
449      /*
450       * Look for text starting in the first col (a label)
451       */
452      if ((!isspace(inpline[0])) &&
453	  (cln = strchr (inpline, ':')) != (char *)NULL) {
454	this = (struct script *)malloc (sizeof (struct script));
455	if (prev != (struct script *)NULL)
456	  prev->next = this;
457	prev = this;
458	if (first == (struct script *)NULL)
459	  first = this;
460	this->next = (struct script *)NULL;
461	this->opcode = SC_LABEL;
462	len = cln - c;
463	this->strprm = (char *)malloc(len+1);
464	strncpy(this->strprm, c, len);
465	(this->strprm)[len] = '\0';
466	this->intprm = 0;
467	this->newstate = (char *)NULL;
468	c = cln + 1;
469      }
470
471      /*
472       * Now handle the opcode. Fold it to lower case.
473       */
474      opc = strtok(c, CTL_DELIM);
475      if (opc == (char *)NULL)	/* If no opcode... */
476	continue;			/* ...read the next line */
477      cp = opc;
478      while(*cp)
479	tolower(*cp++);
480
481      /*
482       * If we have an opcode but we haven't seen anything
483       * else (like a label) yet, i.e., this is the first
484       * entry, and there was no label.  We need to
485       * cobble up a label so that read_script is happy
486       */
487      if (first == (struct script *)NULL)
488	{
489	  this = (struct script *)malloc (sizeof (struct script));
490	  prev = this;
491	  first = this;
492	  this->next = (struct script *)NULL;
493	  this->opcode = SC_LABEL;
494	  this->strprm = (char *)malloc(2);
495	  strcpy(this->strprm, ":");
496	  this->intprm = 0;
497	  this->newstate = (char *)NULL;
498	}
499
500      /*
501       * Find opcode - ndex through the opcode definition table
502       */
503      for (i=1; sc_opdef[i].opcode != SC_END; i++)
504	if (strcmp(opc, sc_opdef[i].opname) == SAME)
505	  break;
506      if ((sc_opdef[i].opcode) == SC_END)
507	{
508	  logit ("Bad opcode in script", opc);
509	  deal_script(first);
510	  return (struct script *)NULL;
511        }
512
513      /*
514       * Found opcode. Allocate a new command node and initialize
515       */
516      this = (struct script *)malloc(sizeof (struct script));
517      prev->next = this;
518      prev = this;
519      this->next = (struct script *)NULL;
520      this->opcode = sc_opdef[i].opcode;
521      this->strprm = (char *)NULL;
522      this->intprm = 0;
523      this->newstate = (char *)NULL;
524
525      /*
526       * Pick up new state parameter, if any
527       */
528      if (sc_opdef[i].newstate == SC_NWST)
529	{
530	  c = strtok((char *)NULL, CTL_DELIM);
531	  if (c == (char *)NULL)
532	    {
533	      logit("Missing new state", opc);
534	      deal_script(first);
535	      return (struct script *)NULL;
536	    }
537	  else
538	    {
539	      this->newstate = (char *)malloc(strlen(c)+1);
540	      strcpy(this->newstate, c);
541	    }
542	}
543
544      /*
545       * Pick up the string or integer parameter. Handle missing
546       * parameter gracefully.
547       */
548      switch (sc_opdef[i].prmtype)
549	{
550	/*
551	 * INT parameter - convert and store in node
552	 */
553	case SC_INT:
554	  c = strtok((char *)NULL, CTL_DELIM);
555	  if (c == (char *)NULL)
556	    {
557	      logit("Missing script param", opc);
558	      deal_script(first);
559	      return (struct script *)NULL;
560	    }
561	  /*
562	   * If this is the parameter to DBST or DBCL, force
563           * base-10 conversion, else convert per parameter.
564	   */
565	  if (sc_opdef[i].opcode == SC_DBST ||
566	      sc_opdef[i].opcode == SC_DBCL)
567	    this->intprm = strtol(c, (char **)NULL, 0);
568	  else
569	    this->intprm = strtol(c, (char **)NULL, 10);
570	  break;
571
572	/*
573	 * STR/XSTR strings.
574	 */
575	case SC_STR:
576	case SC_XSTR:
577	  c = strtok((char *)NULL, CTL_DELIM);
578	  if (c == (char *)NULL)
579	    {
580	      logit("Missing script param", opc);
581	      deal_script(first);
582	      return (struct script *)NULL;
583	    }
584	  /*
585	   * For XSTR opcode, use c to find out where
586	   * the string param begins in the copy of the
587	   * input line, and pick up all that's left of
588	   * the line (to allow imbedded blanks, etc.).
589	   */
590	  if (sc_opdef[i].prmtype == SC_XSTR)
591	    c = &inpcopy[0] + (c - &inpline[0]);
592
593	  /*
594	   * Allocate a buffer for the string parameter
595	   */
596	  this->strprm = (char *)malloc(strlen(c)+1);
597
598	  /*
599	   * For XSTR, Translate the string and store its
600	   * length. Note that, after escape sequences are
601	   * compressed, the resulting string may well be a
602	   * few bytes shorter than the input string (whose
603	   * length was the basis for the malloc above),
604	   * but it will never be longer.
605	   */
606	  if (sc_opdef[i].prmtype == SC_XSTR)
607	    {
608	      this->intprm = xlat_str(this->strprm, c);
609	      this->strprm[this->intprm] = '\0';
610	    }
611	  else
612	    strcpy(this->strprm, c);
613	  break;
614
615	}
616    }
617
618  /*
619   * EOF
620   */
621  return first;
622}
623
624
625/*
626 * xlat_str
627 *
628 * Translate embedded escape characters in a "send" or "expect" string.
629 *
630 * Called by read_script(), above.
631 *
632 * Returns the actual length of the resulting string.  Note that imbedded
633 * nulls (specified by \000 in the input) ARE allowed in the result.
634 */
635xlat_str(out, in)
636     char *out, *in;
637{
638  register int i = 0, j = 0;
639  int byte, k;
640
641  while (in[i])
642    {
643      if (in[i] != '\\')
644	{
645	  out[j++] = in[i++];
646	}
647      else
648	{
649	  switch (in[++i])
650	    {
651	    case 'd':		/* EOT */
652	      out[j++] = 0x04;
653	      break;
654	    case 'N':		/* null */
655	      out[j++] = 0x00;
656	      break;
657	    case 'n':		/* line feed */
658	      out[j++] = 0x0a;
659	      break;
660	    case 'r':		/* carriage return */
661	      out[j++] = 0x0d;
662	      break;
663	    case 's':		/* space */
664	      out[j++] = ' ';
665	      break;
666	    case 't':		/* tab */
667	      out[j++] = '\t';
668	      break;
669	    case '-':		/* hyphen */
670	      out[j++] = '-';
671	      break;
672	    case '\\':		/* back slash */
673	      out[j++] = '\\';
674	      break;
675	    case '0':		/* '\nnn' format */
676	    case '1':
677	    case '2':
678	    case '3':
679	    case '4':
680	    case '5':
681	    case '6':
682	    case '7':
683	      byte = in[i] - '0';
684	      k = 0;
685
686	      while (3 > ++k)
687		if ((in[i+1] < '0') || (in[i+1] > '7'))
688		  break;
689		else
690		  {
691		    byte = (byte<<3) + in[i+1] - '0';
692		    ++i;
693		  }
694	      out[j++] = byte;
695	      break;
696	    default:            /* don't know so skip it */
697	      break;
698	    }
699	  ++i;
700	}
701    }
702  return j;
703}
704
705
706/* find a state within a script */
707
708struct script *
709  find_state(begin, newstate)
710struct script *begin;
711char *newstate;
712{
713  struct script *here;
714
715  for (here=begin; here != (struct script *)NULL; here=here->next) {
716    if (here->opcode == SC_LABEL &&
717	strcmp(here->strprm, newstate) == SAME)
718      return here;
719  }
720  return (struct script *)NULL;
721}
722
723
724/*
725 * do_script() - execute a script
726 */
727int do_script(begin)
728     struct script *begin;
729{
730  struct script *curstate, *newstate, *curscr;
731  int	 dbgsave;
732  char	 tempstr[MAX_SCLINE];
733  char   dfname[256];
734  char	*c, chr;
735  int	 prmlen;
736  int    dbfd;
737
738  time_t sc_carrtime = 45000;	/* time to wf carr after dial */
739  time_t sc_chrdly   = 100;	/* delay time for ttoslow */
740  time_t sc_ptime    = 2000;	/* time to allow for pause char */
741  time_t sc_wtime    = 10000;	/* time to allow for wait char */
742  time_t sc_dtime    = 100;	/* time to allow for each digit */
743  time_t sc_dtmo;		/* total time to dial number */
744  int    sc_counter;		/* random counter */
745  char   sc_pchar    = ',';	/* modem pause character */
746  char   sc_wchar    = 'W';	/* modem wait-for-dialtone character */
747  time_t sc_begwait;		/* time at beg of wait */
748  time_t sc_secs;		/* timeout period */
749
750  int    expcnt;
751  int    expin;
752  static char expbuf[MAX_EXPCT];
753
754  dbgsave = Debug;
755  curstate = begin;
756
757  if (curstate == (struct script *)NULL)
758    return SUCCESS;
759
760  _newstate:
761  /*
762   * do all of curstate's actions.  Enter with curstate pointing
763   * to a label entry
764   */
765  expin = 0;
766
767  for (curscr = curstate->next; /* point to 1st scr after label */
768       (curscr != (struct script *)NULL) &&  /* do until end of scr */
769       (curscr->opcode != SC_LABEL);		/* or next label */
770       curscr = curscr->next)
771    {
772      expcnt = 0;
773      switch (curscr->opcode)
774	{
775	case SC_LABEL:
776	  logit("Script proc err", curstate->strprm);
777	  return FAIL;
778
779	case SC_FLSH:
780	  DEBUG(DB_LGII, "Flushing typeahead buffer\n", 0);
781	  ttflui();
782	  break;
783
784	case SC_CDLY:
785	  sc_chrdly = curscr->intprm;
786	  DEBUG(DB_LGII, "Set chrdly to %d\n", sc_chrdly);
787	  break;
788
789	case SC_PCHR:
790	  sc_pchar = *(curscr->strprm);
791	  DEBUG(DB_LGII, "Set pause char to %c\n", sc_pchar);
792	  break;
793
794	case SC_PTIM:
795	  sc_ptime = curscr->intprm;
796	  DEBUG(DB_LGII, "Set pause time to %d\n", sc_ptime);
797	  break;
798
799	case SC_WCHR:
800	  sc_wchar = *(curscr->strprm);
801	  DEBUG(DB_LGII, "Set wait char to %c\n", sc_wchar);
802	  break;
803
804	case SC_WTIM:
805	  sc_wtime = curscr->intprm;
806	  DEBUG(DB_LGII, "Set wait time to %d\n", sc_wtime);
807	  break;
808
809	case SC_ZERO:
810	  sc_counter = 0;
811	  DEBUG(DB_LGII, "Set counter to %d\n", sc_counter);
812	  break;
813
814	case SC_INCR:
815	  sc_counter++;
816	  DEBUG(DB_LGII, "Incr counter to %d\n", sc_counter);
817	  break;
818
819	case SC_WAIT:
820	  DEBUG(DB_LGII, "Sleeping %d tenth-secs\n", curscr->intprm);
821	  msleep(curscr->intprm);
822	  break;
823
824	case SC_DTIM:
825	  sc_dtime = curscr->intprm;
826	  DEBUG(DB_LGII, "Digit time is %d\n", sc_dtime);
827	  break;
828
829	case SC_CTIM:
830	  sc_carrtime = curscr->intprm;
831	  DEBUG(DB_LGII, "Carrier time is %d\n", sc_carrtime);
832	  break;
833
834	case SC_EXIT:
835	  Debug = dbgsave;
836	  DEBUG(DB_LGI, "Script ended successfully\n", 0);
837	  return SUCCESS;
838
839	case SC_FAIL:
840	  Debug = dbgsave;
841	  if (DEBUG_LEVEL(DB_LGI) && dbf != NULL)
842	    fprintf(dbf, "Script failed\n");
843	  else if (expin)
844	    charlog(expbuf, expin, DB_LOG,
845		    "Script failed.  Last received data");
846	  return FAIL;
847
848	case SC_LOG:
849	  logit(curscr->strprm, "");
850	  break;
851
852	case SC_LOGE:
853	  logit("ERROR: ", curscr->strprm);
854	  break;
855
856	case SC_DBOF:
857	  /*
858	   * If the debug file name does not begin with "/", then
859	   * we prepend the LOG_DIR to the string. Then CREATE the
860	   * file. This WIPES OUT previous logs.
861	   */
862	  *dfname = '\0';	/* Zero name string */
863	  if(curscr->strprm[0] != '/')
864	    strcat(dfname, LOG_DIR); /* Prepend default directory */
865	  strcat(dfname, curscr->strprm); /* Add given string */
866	  DEBUG(DB_LGII, "Open debug file %s\n", dfname);
867	  if ((dbfd = creat (dfname, 0600)) <= 0)
868	    {
869	      logit("Failed to create debug log %s", dfname);
870	      perror("");
871	      return FAIL;
872	    }
873	  if ((dbf = fdopen(dbfd, "w")) == NULL)
874	    {
875	      logit("Failed to open debug log fildes.", "");
876	      perror("");
877	      return FAIL;
878	    }
879	  break;
880
881	case SC_DBG:
882	  DEBUG(DB_LGI, "<%s>\n", curscr->strprm);
883	  break;
884
885	case SC_DBGE:
886	  DEBUG(DB_LGIE, "ERROR: <%s>\n", curscr->strprm);
887	  break;
888
889	case SC_DBST:
890	  Debug |= curscr->intprm;
891	  DEBUG(DB_LGII, "Debug mask set to %04o (octal)\n", Debug);
892	  break;
893
894	case SC_DBCL:
895	  Debug &= ~(curscr->intprm);
896	  DEBUG(DB_LGII, "Debug mask set to %04o (octal)\n", Debug);
897	  break;
898
899	case SC_BRK:
900	  DEBUG(DB_LGI, "Sending break\n", 0);
901	  ttbreak();
902	  break;
903
904	case SC_HANG:
905	  DEBUG(DB_LGI, "Dropping DTR\n", 0);
906	  tthang();
907	  break;
908
909	case SC_7BIT:
910	  DEBUG(DB_LGI, "Enabling 7-bit stripping\n", 0);
911	  tt7bit(TRUE);
912	  break;
913
914	case SC_8BIT:
915	  DEBUG(DB_LGI, "Disabling 7-bit stripping\n", 0);
916	  tt7bit(FALSE);
917	  break;
918
919	case SC_PNON:
920	  DEBUG(DB_LGI, "Setting 8-bit, no parity\n", 0);
921	  ttpar(NONE);
922	  break;
923
924	case SC_PEVN:
925	  DEBUG(DB_LGI, "Setting 7-bit, even parity\n", 0);
926	  ttpar(EVEN);
927	  break;
928
929	case SC_PODD:
930	  DEBUG(DB_LGI, "Setting 7-bit, odd parity\n", 0);
931	  ttpar(ODD);
932	  break;
933
934	case SC_IFBL:
935	  if (ttblind())
936	    {
937	      DEBUG(DB_LGI, "Blind mux,\n", 0);
938	      goto _chgstate;
939	    }
940	  break;
941
942	case SC_IFBG:
943	  if (ttblind() && sc_counter > curscr->intprm)
944	    {
945	      DEBUG(DB_LGI, "Blind mux & ctr > %d\n",
946		    curscr->intprm);
947	      goto _chgstate;
948	    }
949	  break;
950
951	case SC_IFGT:
952	  if (sc_counter > curscr->intprm)
953	    {
954	      DEBUG(DB_LGI, "Counter > %d\n", curscr->intprm);
955	      goto _chgstate;
956	    }
957	  break;
958
959	case SC_GOTO:
960	  _chgstate:
961	  DEBUG(DB_LGI, "Changing to state %s\n",
962		curscr->newstate);
963	  curstate = find_state(begin, curscr->newstate);
964	  if (curstate == NULL)
965	    {
966	      logit("New state not found",
967		    curscr->newstate);
968	      return FAIL;
969	    }
970	  goto _newstate;
971
972	case SC_SEND:
973	  ttoslow(curscr->strprm, curscr->intprm, sc_chrdly);
974	  break;
975
976	case SC_TELN:
977	  if (curscr->intprm > paramc - 1)
978	    {
979	      sprintf(tempstr, "telno - param #%d", curscr->intprm);
980	      logit(tempstr, " not present");
981	      return FAIL;
982	    }
983	  strcpy(telno, paramv[curscr->intprm]);
984	  DEBUG(DB_LGII, "telno set to %s\n", telno);
985	  break;
986
987	case SC_SNDP:
988	  if (curscr->intprm > paramc - 1)
989	    {
990	      sprintf(tempstr, "sendstr - param #%d", curscr->intprm);
991	      logit(tempstr, " not present");
992	      return FAIL;
993	    }
994	  prmlen = xlat_str(tempstr, paramv[curscr->intprm]);
995	  ttoslow(tempstr, prmlen, sc_chrdly);
996	  break;
997
998	case SC_IF1P:
999	  if (curscr->intprm < paramc)
1000	    goto _chgstate;
1001	  break;
1002
1003	case SC_IF0P:
1004	  if (curscr->intprm >= paramc)
1005	    goto _chgstate;
1006	  break;
1007
1008	case SC_DIAL:
1009	  if(telno[0] == '\0')
1010	    {
1011	      logit("telno not set", "");
1012	      return(FAIL);
1013	    }
1014	  /*
1015	   * Compute and set a default timeout for the 'timeout'
1016	   * command. Some parameters in this computation may be
1017	   * changed by the script. See the man page xchat(8) for
1018	   * details.
1019	   */
1020	  sc_dtmo = (sc_dtime+sc_chrdly)*strlen(telno)
1021	    + sc_carrtime;
1022	  c=strcpy(tempstr, telno);
1023	  for (; *c!='\0'; c++)
1024	    {
1025	      if (*c == 'W')
1026		{
1027		  *c = sc_wchar;
1028		  sc_dtmo += sc_wtime;
1029		}
1030	      else if (*c == 'P')
1031		{
1032		  *c = sc_pchar;
1033		  sc_dtmo += sc_ptime;
1034		}
1035	    }
1036	  DEBUG(DB_LGI, "Dialing, default timeout is %d millisecs\n", sc_dtmo);
1037	  ttoslow(tempstr, 0, sc_chrdly);
1038	  break;
1039
1040	case SC_TIMO:	/* these are "expects", don't bother */
1041	case SC_XPCT:	/* with them yet, other than noting that */
1042	case SC_CARR:	/* they exist */
1043	case SC_XPST:
1044	  expcnt++;
1045	  break;
1046	}
1047
1048    }
1049
1050  /* we've done the current state's actions, now do its expects, if any */
1051
1052  if (expcnt == 0)
1053    {
1054      if (curscr != (struct script *)NULL &&
1055	  (curscr->opcode == SC_LABEL))
1056	{
1057	  curstate = curscr;
1058	  DEBUG(DB_LGI, "Fell through to state %s\n",
1059		curstate->strprm);
1060	  goto _newstate;
1061	}
1062      else
1063	{
1064	  logit("No way out of state", curstate->strprm);
1065	  return FAIL;
1066	}
1067    }
1068
1069  time(&sc_begwait);	/* log time at beg of expect */
1070  DEBUG(DB_LGI, "Doing expects for state %s\n", curstate->strprm);
1071  charlog((char *)NULL, 0, DB_LGI, "Received");
1072
1073  while (1)
1074    {
1075      chr = xgetc(1);		/* Returns upon char input or 1 sec. tmo */
1076
1077      charlog(&chr, 1, DB_LGI, (char *)NULL);
1078
1079      if (chr != EOF)
1080	{
1081	  if (expin < MAX_EXPCT)
1082	    {
1083	      expbuf[expin++] = chr & 0x7f;
1084	    }
1085	  else
1086	    {
1087	      strncpy(expbuf, &expbuf[1], MAX_EXPCT-1);
1088	      expbuf[MAX_EXPCT-1] = chr & 0x7f;
1089	    }
1090	}
1091
1092      /* for each entry in the current state... */
1093
1094      for (curscr = curstate->next;
1095	   (curscr != (struct script *)NULL) &&
1096	   (curscr->opcode != SC_LABEL);
1097	   curscr = curscr->next)
1098	{
1099
1100	  switch (curscr->opcode)
1101	    {
1102	    case SC_TIMO:
1103	      sc_secs = curscr->intprm;
1104	      if (sc_secs == 0)
1105		sc_secs = sc_dtmo;
1106	      sc_secs /= 1000;
1107	      if (time(NULL)-sc_begwait > sc_secs)
1108		{
1109		  DEBUG(DB_LGI,
1110			"\nTimed out (%d secs)\n", sc_secs);
1111		  goto _chgstate;
1112		}
1113	      break;
1114
1115	    case SC_CARR:
1116	      if (ttcd())
1117		{
1118		  DEBUG(DB_LGI, "\nGot carrier\n", 0);
1119		  goto _chgstate;
1120		}
1121	      break;
1122
1123	    case SC_HUPS:
1124	      if (fShangup)
1125		{
1126		  DEBUG(DB_LGI, "\nGot data set hangup\n", 0);
1127		  goto _chgstate;
1128		}
1129	      break;
1130
1131	    case SC_XPCT:
1132	      if ((expin >= curscr->intprm) &&
1133		  (strncmp(curscr->strprm,
1134			   &expbuf[expin - curscr->intprm],
1135			   curscr->intprm) == SAME))
1136		{
1137		  charlog(curscr->strprm, curscr->intprm,
1138			  DB_LGI, "Matched");
1139		  goto _chgstate;
1140		}
1141	      break;
1142
1143	      /* New opcode added by hag@eddie.mit.edu for expecting a
1144		 parameter supplied string */
1145	     case SC_XPST:
1146	      if(curscr->intprm >paramc-1)
1147	      {
1148		sprintf(tempstr,"expectstr - param#%d",curscr->intprm);
1149		logit(tempstr, " not present");
1150		return(FAIL);
1151	      }
1152	      prmlen=xlat_str(tempstr,paramv[curscr->intprm]);
1153	      if((expin >= prmlen) &&
1154		 (strncmp(tempstr,&expbuf[expin-prmlen],
1155			  prmlen) == SAME))
1156	      {
1157		charlog(tempstr,prmlen,DB_LGI, "Matched");
1158		goto _chgstate;
1159	      }
1160	      break;
1161	    }
1162	}
1163    }
1164}
1165
1166/*
1167 * SIGNAL HANDLERS
1168 */
1169
1170/*
1171 * usignal - generic signal catcher
1172 */
1173static int usignal(isig)
1174     int isig;
1175{
1176  DEBUG(DB_LOG, "Caught signal %d. Exiting...\n", isig);
1177  restore_tty();
1178  exit(FAIL);
1179}
1180
1181/*
1182 * uhup - HUP catcher
1183 */
1184static int uhup(isig)
1185     int isig;
1186{
1187  DEBUG(DB_LOG, "Data set hangup.\n");
1188  fShangup = TRUE;
1189}
1190
1191/*
1192 * TERMINAL I/O ROUTINES
1193 */
1194
1195/*
1196 * xgetc - get a character with timeout
1197 *
1198 * Assumes that stdin is opened on a terminal or TCP socket
1199 * with O_NONBLOCK.
1200 */
1201static char xgetc(tmo)
1202int tmo;			/* Timeout, seconds */
1203{
1204  char c;
1205  struct timeval s;
1206  int f = 1;			/* Select on stdin */
1207  int result;
1208
1209  if(read(0, &c, 1)  <= 0)	/* If no data available */
1210    {
1211      s.tv_sec = (long)tmo;
1212      s.tv_usec = 0L;
1213      if(select (1, &f, (int *) NULL, &f, &s) == 1)
1214	read(0, &c, 1);
1215      else
1216	c = '\377';
1217    }
1218
1219  return(c);
1220}
1221
1222/*
1223 * Pause for an interval in milliseconds
1224 */
1225void msleep(msec)
1226long msec;
1227{
1228
1229#if HAVE_USLEEP
1230  if(msec == 0)			/* Skip all of this if delay = 0 */
1231    return;
1232  usleep (msec * (long)1000);
1233#endif /* HAVE_USLEEP */
1234
1235#if HAVE_NAPMS
1236  if(msec == 0)			/* Skip all of this if delay = 0 */
1237    return;
1238  napms (msec);
1239#endif /* HAVE_NAPMS */
1240
1241#if HAVE_NAP
1242  if(msec == 0)			/* Skip all of this if delay = 0 */
1243    return;
1244  nap (msec);
1245#endif /* HAVE_NAP */
1246
1247#if HAVE_POLL
1248  struct pollfd sdummy;
1249
1250  if(msec == 0)
1251    return;
1252  /*
1253   * We need to pass an unused pollfd structure because poll checks
1254   * the address before checking the number of elements.
1255   */
1256  poll (&sdummy, 0, msec);
1257#endif /* HAVE_POLL */
1258
1259#if USE_SELECT_TIMER
1260  struct timeval s;
1261
1262  if(msec == 0)
1263    return;
1264  s.tv_sec = msec / 1000L;
1265  s.tv_usec = (msec % 1000L) * 1000L;
1266  select (0, (int *) NULL, (int *) NULL, (int *) NULL, &s);
1267#endif /* USE_SELECT_TIMER */
1268
1269#if ! HAVE_NAPMS && ! HAVE_NAP && ! HAVE_USLEEP && \
1270    ! HAVE_POLL && ! USE_SELECT_TIMER
1271  if(msec == 0)
1272    return;
1273  sleep (1);			/* Sleep for a whole second (UGH!) */
1274#endif /* HAVE_ and USE_ nothing */
1275}
1276
1277/*
1278 * Debugging output
1279 */
1280static void DEBUG(level, msg1, msg2)
1281int level;
1282char *msg1, *msg2;
1283{
1284  if ((dbf != NULL) && DEBUG_LEVEL(level))
1285    fprintf(dbf, msg1, msg2);
1286}
1287
1288/*
1289 * charlog - log a string of characters
1290 *
1291 * SPECIAL CASE: msg=NULL, len=1 and msg[0]='\377' gets logged
1292 *               when read does its 1 sec. timeout. Log "<1 sec.>"
1293 *               so user can see elapsed time
1294 */
1295static void charlog(buf, len, mask, msg)
1296char *buf;
1297int len, mask;
1298char *msg;
1299{
1300  char tbuf[256];
1301
1302  if (DEBUG_LEVEL(mask) && dbf != NULL)
1303    {
1304      if(msg == (char *)NULL)
1305	msg = "";
1306      strncpy(tbuf, buf, len);
1307      tbuf[len] = '\0';
1308      if(len == 1 && tbuf[0] == '\377')
1309	strcpy(tbuf, "<1 sec.>");
1310      fprintf(dbf, "%s %s\n", msg, tbuf);
1311    }
1312}
1313
1314/*
1315 * setup_tty()
1316 *
1317 * Save current tty settings, then set up raw, single
1318 * character input processing, with 7-bit stripping.
1319 */
1320static void setup_tty()
1321{
1322  register int i;
1323
1324  ioctl(0, TCGETA, &old);
1325
1326  new = old;
1327
1328  for(i = 0; i < 7; i++)
1329    new.c_cc[i] = '\0';
1330  new.c_cc[VMIN] = 0;		/* MIN = 0, use requested count */
1331  new.c_cc[VTIME] = 10;		/* TIME = 1 sec. */
1332  new.c_iflag = ISTRIP;		/* Raw mode, 7-bit stripping */
1333  new.c_lflag = 0;		/* No special line discipline */
1334
1335  ioctl(0, TCSETA, &new);
1336}
1337
1338/*
1339 * restore_tty() - restore signal handlers and tty modes on exit.
1340 */
1341static void restore_tty(sig)
1342int sig;
1343{
1344  ioctl(0, TCSETA, &old);
1345  return;
1346}
1347
1348/*
1349 * ttoslow() - Send characters with pacing delays
1350 */
1351static void ttoslow(s, len, delay)
1352     char *s;
1353     int len;
1354     time_t delay;
1355{
1356  int i;
1357
1358  if (len == 0)
1359    len = strlen(s);
1360
1361  charlog (s, len, DB_LGI, "Sending slowly");
1362
1363  for (i = 0; i < len; i++, s++)
1364    {
1365      write(1, s, 1);
1366      msleep(delay);
1367    }
1368}
1369
1370/*
1371 * ttflui - flush input buffer
1372 */
1373static void ttflui()
1374{
1375  if(isatty(0))
1376    (void) ioctl ( 0, TCFLSH, 0);
1377}
1378
1379/*
1380 * ttcd - Test if carrier is present
1381 *
1382 * NOT IMPLEMENTED. I don't know how!!!
1383 */
1384static int ttcd()
1385{
1386  return TRUE;
1387}
1388
1389/*
1390 * tthang - Force DTR low for 1-2 sec.
1391 */
1392static void tthang()
1393{
1394  if(!isatty())
1395    return;
1396
1397#ifdef TCCLRDTR
1398  (void) ioctl (1, TCCLRDTR, 0);
1399  sleep (2);
1400  (void) ioctl (1, TCSETDTR, 0);
1401#endif
1402
1403  return;
1404}
1405
1406/*
1407 * ttbreak - Send a "break" on the line
1408 */
1409static void ttbreak()
1410{
1411  (void) ioctl (1, TCSBRK, 0);
1412}
1413
1414/*
1415 * ttblind - return TRUE if tty is "blind"
1416 *
1417 * NOT IMPLEMENTED - Don't know how!!!
1418 */
1419static int ttblind()
1420{
1421  return FALSE;
1422}
1423
1424/*
1425 * tt7bit - enable/disable 7-bit stripping on line
1426 */
1427static void tt7bit(enable)
1428     int enable;
1429{
1430  if(enable)
1431    new.c_iflag |= ISTRIP;
1432  else
1433    new.c_iflag &= ~ISTRIP;
1434
1435  ioctl(0, TCSETA, &new);
1436}
1437
1438/*
1439 * ttpar - Set parity mode on line. Ignore parity errors on input.
1440 */
1441static void ttpar(mode)
1442     int mode;
1443{
1444  switch(mode)
1445    {
1446    case NONE:
1447      new.c_iflag &= ~(INPCK | IGNPAR);
1448      new.c_cflag &= ~(CSIZE | PARENB | PARODD);
1449      new.c_cflag |= CS8;
1450      break;
1451
1452    case EVEN:
1453      new.c_iflag |= (INPCK | IGNPAR);
1454      new.c_cflag &= ~(CSIZE | PARODD);
1455      new.c_cflag |= (CS7 | PARENB);
1456
1457      break;
1458
1459    case ODD:
1460      new.c_iflag |= (INPCK | IGNPAR);
1461      new.c_cflag &= ~(CSIZE);
1462      new.c_cflag |= (CS7 | PARENB | PARODD);
1463      break;
1464    }
1465
1466  ioctl(0, TCSETA, &new);
1467}
1468
1469
1470
1471
1472
1473
1474
1475