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