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