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