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