chat.c revision 31953
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.40 1997/12/18 01:10:12 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 "command.h"
47#include "mbuf.h"
48#include "log.h"
49#include "defs.h"
50#include "timer.h"
51#include "loadalias.h"
52#include "vars.h"
53#include "chat.h"
54#include "sig.h"
55#include "modem.h"
56
57#ifndef isblank
58#define	isblank(c)	((c) == '\t' || (c) == ' ')
59#endif
60
61
62#define	IBSIZE LINE_LEN
63
64static int TimeoutSec;
65static int abort_next, timeout_next;
66static int numaborts;
67static char *AbortStrings[50];
68static char inbuff[IBSIZE * 2 + 1];
69
70#define	MATCH	1
71#define	NOMATCH	0
72#define	ABORT	-1
73
74static char *
75findblank(char *p, int instring)
76{
77  if (instring) {
78    while (*p) {
79      if (*p == '\\') {
80	strcpy(p, p + 1);
81	if (!*p)
82	  break;
83      } else if (*p == '"')
84	return (p);
85      p++;
86    }
87  } else {
88    while (*p) {
89      if (isblank(*p))
90	return (p);
91      p++;
92    }
93  }
94  return p;
95}
96
97int
98MakeArgs(char *script, char **pvect, int maxargs)
99{
100  int nargs, nb;
101  int instring;
102
103  nargs = 0;
104  while (*script) {
105    nb = strspn(script, " \t");
106    script += nb;
107    if (*script) {
108      if (*script == '"') {
109	instring = 1;
110	script++;
111	if (*script == '\0')
112	  break;		/* Shouldn't return here. Need to null
113				 * terminate below */
114      } else
115	instring = 0;
116      if (nargs >= maxargs - 1)
117	break;
118      *pvect++ = script;
119      nargs++;
120      script = findblank(script, instring);
121      if (*script)
122	*script++ = '\0';
123    }
124  }
125  *pvect = NULL;
126  return nargs;
127}
128
129/*
130 *  \c	don't add a cr
131 *  \d  Sleep a little (delay 2 seconds
132 *  \n  Line feed character
133 *  \P  Auth Key password
134 *  \p  pause 0.25 sec
135 *  \r	Carrige return character
136 *  \s  Space character
137 *  \T  Telephone number(s) (defined via `set phone')
138 *  \t  Tab character
139 *  \U  Auth User
140 */
141char *
142ExpandString(const char *str, char *result, int reslen, int sendmode)
143{
144  int addcr = 0;
145  char *phone;
146
147  result[--reslen] = '\0';
148  if (sendmode)
149    addcr = 1;
150  while (*str && reslen > 0) {
151    switch (*str) {
152    case '\\':
153      str++;
154      switch (*str) {
155      case 'c':
156	if (sendmode)
157	  addcr = 0;
158	break;
159      case 'd':		/* Delay 2 seconds */
160	nointr_sleep(2);
161	break;
162      case 'p':
163	nointr_usleep(250000);
164	break;			/* Pause 0.25 sec */
165      case 'n':
166	*result++ = '\n';
167	reslen--;
168	break;
169      case 'r':
170	*result++ = '\r';
171	reslen--;
172	break;
173      case 's':
174	*result++ = ' ';
175	reslen--;
176	break;
177      case 't':
178	*result++ = '\t';
179	reslen--;
180	break;
181      case 'P':
182	strncpy(result, VarAuthKey, reslen);
183	reslen -= strlen(result);
184	result += strlen(result);
185	break;
186      case 'T':
187	if (VarAltPhone == NULL) {
188	  if (VarNextPhone == NULL) {
189	    strncpy(VarPhoneCopy, VarPhoneList, sizeof(VarPhoneCopy) - 1);
190	    VarPhoneCopy[sizeof(VarPhoneCopy) - 1] = '\0';
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(void)
244{
245  memset(logbuff, 0, MAXLOGBUFF);
246  loglen = 0;
247}
248
249static void
250flush_log(void)
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(const 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(const 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  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[MAXARGS];
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  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, (char **)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(const 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      ExpandString(str + 1, buff + 2, sizeof(buff) - 2, 0);
502      ExecStr(buff + 2, buff + 2);
503    } else {
504      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';	/* XXX: Cheat with the const string */
548      state = WaitforString(str);
549      *minus = '-';	/* XXX: Cheat with the const string */
550      minus++;
551      if (state != NOMATCH)
552	return (state);
553
554      /*
555       * Can't get expect string. Sendout send part.
556       */
557      str = minus;
558      for (minus = str; *minus; minus++) {
559	if (*minus == '-') {
560	  if (minus == str || minus[-1] != '\\')
561	    break;
562	}
563      }
564      if (*minus == '-') {
565        *minus = '\0';	/* XXX: Cheat with the const string */
566	SendString(str);
567        *minus = '-';	/* XXX: Cheat with the const string */
568	str = ++minus;
569      } else {
570	SendString(str);
571	return (MATCH);
572      }
573    } else {
574
575      /*
576       * Simple case. Wait for string.
577       */
578      return (WaitforString(str));
579    }
580  }
581  return (MATCH);
582}
583
584static jmp_buf ChatEnv;
585static void (*oint) (int);
586
587static void
588StopDial(int sig)
589{
590  LogPrintf(LogPHASE, "DoChat: Caught signal %d, abort connect\n", sig);
591  longjmp(ChatEnv, 1);
592}
593
594int
595DoChat(char *script)
596{
597  char *vector[MAXARGS];
598  char *const *argv;
599  int argc, n, state;
600
601  if (!script || !*script)
602    return MATCH;
603
604  /* While we're chatting, we want an INT to fail us */
605  if (setjmp(ChatEnv)) {
606    signal(SIGINT, oint);
607    return (-1);
608  }
609  oint = signal(SIGINT, StopDial);
610
611  timeout_next = abort_next = 0;
612  for (n = 0; AbortStrings[n]; n++) {
613    free(AbortStrings[n]);
614    AbortStrings[n] = NULL;
615  }
616  numaborts = 0;
617
618  memset(vector, '\0', sizeof(vector));
619  argc = MakeArgs(script, vector, VECSIZE(vector));
620  argv = vector;
621  TimeoutSec = 30;
622  while (*argv) {
623    if (strcmp(*argv, "P_ZERO") == 0 ||
624	strcmp(*argv, "P_ODD") == 0 || strcmp(*argv, "P_EVEN") == 0) {
625      ChangeParity(*argv++);
626      continue;
627    }
628    state = ExpectString(*argv++);
629    switch (state) {
630    case MATCH:
631      if (*argv)
632	SendString(*argv++);
633      break;
634    case ABORT:
635    case NOMATCH:
636      signal(SIGINT, oint);
637      return (NOMATCH);
638    }
639  }
640  signal(SIGINT, oint);
641  return (MATCH);
642}
643