chat.c revision 23584
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.19 1997/03/08 12:15:58 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 <sys/cdefs.h>
38#include <signal.h>
39#include <sys/wait.h>
40#include "timeout.h"
41#include "vars.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    signal(SIGINT, SIG_DFL);
407    signal(SIGQUIT, SIG_DFL);
408    signal(SIGTERM, SIG_DFL);
409    signal(SIGHUP, SIG_DFL);
410    close(fids[0]);
411    dup2(fids[1], 1);
412    close(fids[1]);
413    nb = open("/dev/tty", O_RDWR);
414    dup2(nb, 0);
415    LogPrintf(LOG_CHAT_BIT, "exec: %s\n", command);
416    /* switch back to original privileges */
417    if (setgid(getgid()) < 0) {
418      LogPrintf(LOG_CHAT_BIT, "setgid: %s\n", strerror(errno));
419      exit(1);
420    }
421    if (setuid(getuid()) < 0) {
422      LogPrintf(LOG_CHAT_BIT, "setuid: %s\n", strerror(errno));
423      exit(1);
424    }
425    pid = execvp(command, vector);
426    LogPrintf(LOG_CHAT_BIT, "execvp failed for (%d/%d): %s\n", pid, errno, command);
427    exit(127);
428  } else {
429    close(fids[1]);
430    for (;;) {
431      nb = read(fids[0], out, 1);
432      if (nb <= 0)
433	break;
434      out++;
435    }
436    *out = '\0';
437    close(fids[0]);
438    close(fids[1]);
439    waitpid(pid, &stat, WNOHANG);
440  }
441}
442
443void
444SendString(str)
445char *str;
446{
447  char *cp;
448  int nb, on;
449  char buff[200];
450
451  if (abort_next) {
452    abort_next = 0;
453    ExpandString(str, buff, sizeof(buff), 0);
454    AbortStrings[numaborts++] = strdup(buff);
455  } else if (timeout_next) {
456    timeout_next = 0;
457    TimeoutSec = atoi(str);
458    if (TimeoutSec <= 0)
459      TimeoutSec = 30;
460  } else {
461    if (*str == '!') {
462      (void) ExpandString(str+1, buff+2, sizeof(buff)-2, 0);
463      ExecStr(buff + 2, buff + 2);
464    } else {
465      (void) ExpandString(str, buff+2, sizeof(buff)-2, 1);
466    }
467    if (strstr(str, "\\P")) { /* Do not log the password itself. */
468      LogPrintf(LOG_CHAT_BIT, "sending: %s\n", str);
469    } else {
470      LogPrintf(LOG_CHAT_BIT, "sending: %s\n", buff+2);
471    }
472    cp = buff;
473    if (DEV_IS_SYNC)
474      bcopy("\377\003", buff, 2);	/* Prepend HDLC header */
475    else
476      cp += 2;
477    on = strlen(cp);
478    nb = write(modem, cp, on);
479  }
480}
481
482int
483ExpectString(str)
484char *str;
485{
486  char *minus;
487  int state;
488
489  if (strcmp(str, "ABORT") == 0) {
490    ++abort_next;
491    return(MATCH);
492  }
493  if (strcmp(str, "TIMEOUT") == 0) {
494    ++timeout_next;
495    return(MATCH);
496  }
497  LogPrintf(LOG_CHAT_BIT, "Expecting %s\n", str);
498  while (*str) {
499    /*
500     *  Check whether if string contains sub-send-expect.
501     */
502    for (minus = str; *minus; minus++) {
503      if (*minus == '-') {
504	if (minus == str || minus[-1] != '\\')
505	  break;
506      }
507    }
508    if (*minus == '-') {      /* We have sub-send-expect. */
509      *minus++ = '\0';
510      state = WaitforString(str);
511      if (state != NOMATCH)
512	return(state);
513      /*
514       * Can't get expect string. Sendout send part.
515       */
516      str = minus;
517      for (minus = str; *minus; minus++) {
518	if (*minus == '-') {
519	  if (minus == str || minus[-1] != '\\')
520	    break;
521	}
522      }
523      if (*minus == '-') {
524	*minus++ = '\0';
525	SendString(str);
526	str = minus;
527      } else {
528	SendString(str);
529	return(MATCH);
530      }
531    } else {
532      /*
533       *  Simple case. Wait for string.
534       */
535      return(WaitforString(str));
536    }
537  }
538  return(MATCH);
539}
540
541int
542DoChat(script)
543char *script;
544{
545  char *vector[20];
546  char **argv;
547  int argc, n, state;
548#ifdef DEBUG
549  int i;
550#endif
551
552  timeout_next = abort_next = 0;
553  for (n = 0; AbortStrings[n]; n++) {
554    free(AbortStrings[n]);
555    AbortStrings[n] = NULL;
556  }
557  numaborts = 0;
558
559  bzero(vector, sizeof(vector));
560  n = MakeArgs(script, &vector);
561#ifdef DEBUG
562  logprintf("n = %d\n", n);
563  for (i = 0; i < n; i++)
564    logprintf("  %s\n", vector[i]);
565#endif
566  argc = n;
567  argv = vector;
568  TimeoutSec = 30;
569  while (*argv) {
570    if (strcmp(*argv, "P_ZERO") == 0 ||
571	strcmp(*argv, "P_ODD") == 0 || strcmp(*argv, "P_EVEN") == 0) {
572      ChangeParity(*argv++);
573      continue;
574    }
575    state = ExpectString(*argv++);
576    switch (state) {
577    case MATCH:
578      if (*argv)
579	SendString(*argv++);
580      break;
581    case ABORT:
582#ifdef notdef
583      HangupModem();
584#endif
585    case NOMATCH:
586      return(NOMATCH);
587    }
588  }
589  return(MATCH);
590}
591