chat.c revision 25560
12996Spst/*
22996Spst *	    Written by Toshiharu OHNO (tony-o@iij.ad.jp)
32996Spst *
42996Spst *   Copyright (C) 1993, Internet Initiative Japan, Inc. All rights reserverd.
52996Spst *
62996Spst *  Most of codes are derived from chat.c by Karl Fox (karl@MorningStar.Com).
7 *
8 *	Chat -- a program for automatic session establishment (i.e. dial
9 *		the phone and log in).
10 *
11 *	This software is in the public domain.
12 *
13 *	Please send all bug reports, requests for information, etc. to:
14 *
15 *		Karl Fox <karl@MorningStar.Com>
16 *		Morning Star Technologies, Inc.
17 *		1760 Zollinger Road
18 *		Columbus, OH  43221
19 *		(614)451-1883
20 *
21 * $Id: chat.c,v 1.22 1997/03/13 12:45:28 brian Exp $
22 *
23 *  TODO:
24 *	o Support more UUCP compatible control sequences.
25 *	o Dialing shoud not block monitor process.
26 *	o Reading modem by select should be unified into main.c
27 */
28#include "defs.h"
29#include <ctype.h>
30#include <sys/uio.h>
31#ifndef isblank
32#define	isblank(c)	((c) == '\t' || (c) == ' ')
33#endif
34#include <sys/time.h>
35#include <fcntl.h>
36#include <errno.h>
37#include <signal.h>
38#include <sys/wait.h>
39#include "timeout.h"
40#include "vars.h"
41#include "sig.h"
42#include "chat.h"
43
44#define	IBSIZE 200
45
46static int TimeoutSec;
47static int abort_next, timeout_next;
48static int numaborts;
49char *AbortStrings[50];
50char inbuff[IBSIZE*2+1];
51
52extern int ChangeParity(char *);
53
54#define	MATCH	1
55#define	NOMATCH	0
56#define	ABORT	-1
57
58static char *
59findblank(p, instring)
60char *p;
61int instring;
62{
63  if (instring) {
64    while (*p) {
65      if (*p == '\\') {
66	strcpy(p, p + 1);
67	if (!*p)
68	  break;
69      } else if (*p == '"')
70	return(p);
71      p++;
72    }
73  } else {
74    while (*p) {
75      if (isblank(*p))
76	return(p);
77      p++;
78    }
79  }
80  return p;
81}
82
83int
84MakeArgs(script, pvect, maxargs)
85char *script;
86char **pvect;
87int maxargs;
88{
89  int nargs, nb;
90  int instring;
91
92  nargs = 0;
93  while (*script) {
94    nb = strspn(script, " \t");
95    script += nb;
96    if (*script) {
97      if (*script == '"') {
98	instring = 1;
99	script++;
100	if (*script == '\0')
101	  break; /* Shouldn't return here. Need to null terminate below */
102      } else
103	instring = 0;
104      if (nargs >= maxargs-1)
105	break;
106      *pvect++ = script;
107      nargs++;
108      script = findblank(script, instring);
109      if (*script)
110	*script++ = '\0';
111    }
112  }
113  *pvect = NULL;
114  return nargs;
115}
116
117/*
118 *  \c	don't add a cr
119 *  \d  Sleep a little (delay 2 seconds
120 *  \n  Line feed character
121 *  \P  Auth Key password
122 *  \p  pause 0.25 sec
123 *  \r	Carrige return character
124 *  \s  Space character
125 *  \T  Telephone number(s) (defined via `set phone')
126 *  \t  Tab character
127 *  \U  Auth User
128 */
129char *
130ExpandString(str, result, reslen, sendmode)
131char *str;
132char *result;
133int reslen;
134int sendmode;
135{
136  int addcr = 0;
137  char *phone;
138
139  result[--reslen] = '\0';
140  if (sendmode)
141    addcr = 1;
142  while (*str && reslen > 0) {
143    switch (*str) {
144    case '\\':
145      str++;
146      switch (*str) {
147      case 'c':
148	if (sendmode)
149	  addcr = 0;
150	break;
151      case 'd':		/* Delay 2 seconds */
152        sleep(2); break;
153      case 'p':
154        usleep(250000); break;	/* Pause 0.25 sec */
155      case 'n':
156	*result++ = '\n'; reslen--; break;
157      case 'r':
158	*result++ = '\r'; reslen--; break;
159      case 's':
160	*result++ = ' '; reslen--; break;
161      case 't':
162	*result++ = '\t'; reslen--; break;
163      case 'P':
164        strncpy(result, VarAuthKey, reslen);
165	reslen -= strlen(result);
166	result += strlen(result);
167	break;
168      case 'T':
169	if (VarNextPhone == NULL) {
170	  strcpy(VarPhoneCopy, VarPhoneList);
171	  VarNextPhone = VarPhoneCopy;
172	}
173	phone = strsep(&VarNextPhone, ":");
174	strncpy(result, phone, reslen);
175	reslen -= strlen(result);
176	result += strlen(result);
177	if ((mode & (MODE_INTER|MODE_AUTO)) == MODE_INTER)
178	  fprintf(stderr, "Phone: %s\n", phone);
179	LogPrintf(LOG_PHASE_BIT, "Phone: %s\n", phone);
180	break;
181      case 'U':
182	strncpy(result, VarAuthName, reslen);
183	reslen -= strlen(result);
184	result += strlen(result);
185	break;
186      default:
187	reslen--;
188	*result++ = *str;
189	break;
190      }
191      if (*str)
192          str++;
193      break;
194    case '^':
195      str++;
196      if (*str) {
197	*result++ = *str++ & 0x1f;
198	reslen--;
199      }
200      break;
201    default:
202      *result++ = *str++;
203      reslen--;
204      break;
205    }
206  }
207  if (--reslen > 0) {
208    if (addcr)
209      *result++ = '\r';
210  }
211  if (--reslen > 0)
212    *result++ = '\0';
213  return(result);
214}
215
216#define MAXLOGBUFF 200
217static char logbuff[MAXLOGBUFF];
218static int loglen = 0;
219
220static void clear_log() {
221  memset(logbuff,0,MAXLOGBUFF);
222  loglen = 0;
223}
224
225static void flush_log() {
226  if ((loglevel & LOG_CONNECT_BIT)
227      || ((loglevel & LOG_CARRIER_BIT)
228	  && strstr(logbuff,"CARRIER"))) {
229    LogPrintf(LOG_CONNECT_BIT|LOG_CARRIER_BIT,"Chat: %s\n",logbuff);
230  }
231  clear_log();
232}
233
234static void connect_log(char *str, int single_p) {
235  int space = MAXLOGBUFF - loglen - 1;
236
237  while (space--) {
238    if (*str == '\n') {
239      flush_log();
240    } else {
241      logbuff[loglen++] = *str;
242    }
243    if (single_p || !*++str) break;
244  }
245  if (!space) flush_log();
246}
247
248
249
250int
251WaitforString(estr)
252char *estr;
253{
254  struct timeval timeout;
255  char *s, *str, ch;
256  char *inp;
257  fd_set rfds;
258  int i, nfds, nb, msg;
259  char buff[200];
260
261
262#ifdef SIGALRM
263  int omask;
264  omask = sigblock(sigmask(SIGALRM));
265#endif
266  clear_log();
267  (void) ExpandString(estr, buff, sizeof(buff), 0);
268  LogPrintf(LOG_CHAT_BIT, "Wait for (%d): %s --> %s\n", TimeoutSec, estr, buff);
269  str = buff;
270  inp = inbuff;
271
272  if (strlen(str)>=IBSIZE){
273    str[IBSIZE]=0;
274    LogPrintf(LOG_CHAT_BIT, "Truncating String to %d character: %s\n", IBSIZE, str);
275  }
276
277  nfds = modem + 1;
278  s = str;
279  msg = FALSE;
280  for (;;) {
281    FD_ZERO(&rfds);
282    FD_SET(modem, &rfds);
283    /*
284     *  Because it is not clear whether select() modifies timeout value,
285     *  it is better to initialize timeout values everytime.
286     */
287    timeout.tv_sec = TimeoutSec;
288    timeout.tv_usec = 0;
289    i = select(nfds, &rfds, NULL, NULL, &timeout);
290#ifdef notdef
291    TimerService();
292#endif
293    if (i < 0) {
294#ifdef SIGALRM
295      if (errno == EINTR)
296	continue;
297      sigsetmask(omask);
298#endif
299      perror("select");
300      *inp = 0;
301      return(NOMATCH);
302    } else if (i == 0) { 	/* Timeout reached! */
303      *inp = 0;
304      if (inp != inbuff)
305      LogPrintf(LOG_CHAT_BIT, "got: %s\n", inbuff);
306      LogPrintf(LOG_CHAT_BIT, "can't get (%d).\n", timeout.tv_sec);
307#ifdef SIGALRM
308      sigsetmask(omask);
309#endif
310      return(NOMATCH);
311    }
312    if (FD_ISSET(modem, &rfds)) {	/* got something */
313      if (DEV_IS_SYNC) {
314	int length;
315	if ((length=strlen(inbuff))>IBSIZE){
316	  bcopy(&(inbuff[IBSIZE]),inbuff,IBSIZE+1); /* shuffle down next part*/
317	  length=strlen(inbuff);
318	}
319	nb = read(modem, &(inbuff[length]), IBSIZE);
320	inbuff[nb + length] = 0;
321	connect_log(inbuff,0);
322	if (strstr(inbuff, str)) {
323#ifdef SIGALRM
324          sigsetmask(omask);
325#endif
326	  flush_log();
327	  return(MATCH);
328	}
329	for (i = 0; i < numaborts; i++) {
330	  if (strstr(inbuff, AbortStrings[i])) {
331	    LogPrintf(LOG_CHAT_BIT, "Abort: %s\n", AbortStrings[i]);
332#ifdef SIGALRM
333            sigsetmask(omask);
334#endif
335	    flush_log();
336	    return(ABORT);
337	  }
338	}
339      } else {
340        read(modem, &ch, 1);
341	connect_log(&ch,1);
342        *inp++ = ch;
343        if (ch == *s) {
344	  s++;
345	  if (*s == '\0') {
346#ifdef SIGALRM
347            sigsetmask(omask);
348#endif
349	    *inp = 0;
350	    flush_log();
351	    return(MATCH);
352	  }
353        } else {
354	  s = str;
355	  if (inp == inbuff+ IBSIZE) {
356	    bcopy(inp - 100, inbuff, 100);
357	    inp = inbuff + 100;
358	  }
359	  for (i = 0; i < numaborts; i++) {	/* Look for Abort strings */
360	    int len;
361	    char *s1;
362
363	    s1 = AbortStrings[i];
364	    len = strlen(s1);
365	    if ((len <= inp - inbuff) && (strncmp(inp - len, s1, len) == 0)) {
366	      LogPrintf(LOG_CHAT_BIT, "Abort: %s\n", s1);
367	      *inp = 0;
368#ifdef SIGALRM
369      	      sigsetmask(omask);
370#endif
371	      flush_log();
372	      return(ABORT);
373	    }
374	  }
375        }
376      }
377    }
378  }
379#ifdef SIGALRM
380  sigsetmask(omask);
381#endif
382}
383
384void
385ExecStr(command, out)
386char *command, *out;
387{
388  int pid;
389  int fids[2];
390  char *vector[20];
391  int stat, nb;
392  char *cp;
393  char tmp[300];
394  extern int errno;
395
396  cp = inbuff + strlen(inbuff) - 1;
397  while (cp > inbuff) {
398    if (*cp < ' ' && *cp != '\t') {
399      cp++;
400      break;
401    }
402    cp--;
403  }
404  snprintf(tmp, sizeof tmp, "%s %s", command, cp);
405  (void) MakeArgs(tmp, vector, VECSIZE(vector));
406
407  pipe(fids);
408  pid = fork();
409  if (pid == 0) {
410    TermTimerService();
411    signal(SIGINT, SIG_DFL);
412    signal(SIGQUIT, SIG_DFL);
413    signal(SIGTERM, SIG_DFL);
414    signal(SIGHUP, SIG_DFL);
415    signal(SIGALRM, SIG_DFL);
416    close(fids[0]);
417    dup2(fids[1], 1);
418    close(fids[1]);
419    nb = open("/dev/tty", O_RDWR);
420    dup2(nb, 0);
421    LogPrintf(LOG_CHAT_BIT, "exec: %s\n", command);
422    /* switch back to original privileges */
423    if (setgid(getgid()) < 0) {
424      LogPrintf(LOG_CHAT_BIT, "setgid: %s\n", strerror(errno));
425      exit(1);
426    }
427    if (setuid(getuid()) < 0) {
428      LogPrintf(LOG_CHAT_BIT, "setuid: %s\n", strerror(errno));
429      exit(1);
430    }
431    pid = execvp(command, vector);
432    LogPrintf(LOG_CHAT_BIT, "execvp failed for (%d/%d): %s\n", pid, errno, command);
433    exit(127);
434  } else {
435    close(fids[1]);
436    for (;;) {
437      nb = read(fids[0], out, 1);
438      if (nb <= 0)
439	break;
440      out++;
441    }
442    *out = '\0';
443    close(fids[0]);
444    close(fids[1]);
445    waitpid(pid, &stat, WNOHANG);
446  }
447}
448
449void
450SendString(str)
451char *str;
452{
453  char *cp;
454  int nb, on;
455  char buff[200];
456
457  if (abort_next) {
458    abort_next = 0;
459    ExpandString(str, buff, sizeof(buff), 0);
460    AbortStrings[numaborts++] = strdup(buff);
461  } else if (timeout_next) {
462    timeout_next = 0;
463    TimeoutSec = atoi(str);
464    if (TimeoutSec <= 0)
465      TimeoutSec = 30;
466  } else {
467    if (*str == '!') {
468      (void) ExpandString(str+1, buff+2, sizeof(buff)-2, 0);
469      ExecStr(buff + 2, buff + 2);
470    } else {
471      (void) ExpandString(str, buff+2, sizeof(buff)-2, 1);
472    }
473    if (strstr(str, "\\P")) { /* Do not log the password itself. */
474      LogPrintf(LOG_CHAT_BIT, "sending: %s\n", str);
475    } else {
476      LogPrintf(LOG_CHAT_BIT, "sending: %s\n", buff+2);
477    }
478    cp = buff;
479    if (DEV_IS_SYNC)
480      bcopy("\377\003", buff, 2);	/* Prepend HDLC header */
481    else
482      cp += 2;
483    on = strlen(cp);
484    nb = write(modem, cp, on);
485  }
486}
487
488int
489ExpectString(str)
490char *str;
491{
492  char *minus;
493  int state;
494
495  if (strcmp(str, "ABORT") == 0) {
496    ++abort_next;
497    return(MATCH);
498  }
499  if (strcmp(str, "TIMEOUT") == 0) {
500    ++timeout_next;
501    return(MATCH);
502  }
503  LogPrintf(LOG_CHAT_BIT, "Expecting %s\n", str);
504  while (*str) {
505    /*
506     *  Check whether if string contains sub-send-expect.
507     */
508    for (minus = str; *minus; minus++) {
509      if (*minus == '-') {
510	if (minus == str || minus[-1] != '\\')
511	  break;
512      }
513    }
514    if (*minus == '-') {      /* We have sub-send-expect. */
515      *minus++ = '\0';
516      state = WaitforString(str);
517      if (state != NOMATCH)
518	return(state);
519      /*
520       * Can't get expect string. Sendout send part.
521       */
522      str = minus;
523      for (minus = str; *minus; minus++) {
524	if (*minus == '-') {
525	  if (minus == str || minus[-1] != '\\')
526	    break;
527	}
528      }
529      if (*minus == '-') {
530	*minus++ = '\0';
531	SendString(str);
532	str = minus;
533      } else {
534	SendString(str);
535	return(MATCH);
536      }
537    } else {
538      /*
539       *  Simple case. Wait for string.
540       */
541      return(WaitforString(str));
542    }
543  }
544  return(MATCH);
545}
546
547int
548DoChat(script)
549char *script;
550{
551  char *vector[40];
552  char **argv;
553  int argc, n, state;
554#ifdef DEBUG
555  int i;
556#endif
557
558  timeout_next = abort_next = 0;
559  for (n = 0; AbortStrings[n]; n++) {
560    free(AbortStrings[n]);
561    AbortStrings[n] = NULL;
562  }
563  numaborts = 0;
564
565  bzero(vector, sizeof(vector));
566  n = MakeArgs(script, vector, VECSIZE(vector));
567#ifdef DEBUG
568  logprintf("n = %d\n", n);
569  for (i = 0; i < n; i++)
570    logprintf("  %s\n", vector[i]);
571#endif
572  argc = n;
573  argv = vector;
574  TimeoutSec = 30;
575  while (*argv) {
576    if (strcmp(*argv, "P_ZERO") == 0 ||
577	strcmp(*argv, "P_ODD") == 0 || strcmp(*argv, "P_EVEN") == 0) {
578      ChangeParity(*argv++);
579      continue;
580    }
581    state = ExpectString(*argv++);
582    switch (state) {
583    case MATCH:
584      if (*argv)
585	SendString(*argv++);
586      break;
587    case ABORT:
588#ifdef notdef
589      HangupModem();
590#endif
591    case NOMATCH:
592      return(NOMATCH);
593    }
594  }
595  return(MATCH);
596}
597