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