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