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