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