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