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