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