xref: /freebsd/libexec/getty/chat.c (revision 3e0f6b97b257a96f7275e4442204263e44b16686)
1 /*-
2  * Copyright (c) 1997
3  *	David L Nugent <davidn@blaze.net.au>.
4  *	All rights reserved.
5  *
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, is permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice immediately at the beginning of the file, without modification,
12  *    this list of conditions, and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. This work was done expressly for inclusion into FreeBSD.  Other use
17  *    is permitted provided this notation is included.
18  * 4. Absolutely no warranty of function or purpose is made by the authors.
19  * 5. Modifications may be freely made to this file providing the above
20  *    conditions are met.
21  *
22  * Modem chat module - send/expect style functions for getty
23  * For semi-intelligent modem handling.
24  *
25  *	$FreeBSD$
26  */
27 
28 #include <sys/param.h>
29 #include <sys/stat.h>
30 #include <sys/ioctl.h>
31 #include <sys/resource.h>
32 #include <sys/ttydefaults.h>
33 #include <sys/utsname.h>
34 #include <errno.h>
35 #include <signal.h>
36 #include <fcntl.h>
37 #include <time.h>
38 #include <ctype.h>
39 #include <fcntl.h>
40 #include <libutil.h>
41 #include <locale.h>
42 #include <setjmp.h>
43 #include <signal.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <syslog.h>
47 #include <termios.h>
48 #include <time.h>
49 #include <unistd.h>
50 #include <sys/socket.h>
51 
52 #include "extern.h"
53 
54 #define	PAUSE_CH		(unsigned char)'\xff'   /* pause kludge */
55 
56 #define	CHATDEBUG_RECEIVE	0x01
57 #define	CHATDEBUG_SEND		0x02
58 #define	CHATDEBUG_EXPECT	0x04
59 #define	CHATDEBUG_MISC		0x08
60 
61 #define	CHATDEBUG_DEFAULT	0
62 #define CHAT_DEFAULT_TIMEOUT	10
63 
64 
65 static int chat_debug = CHATDEBUG_DEFAULT;
66 static int chat_alarm = CHAT_DEFAULT_TIMEOUT; /* Default */
67 
68 static volatile int alarmed = 0;
69 
70 
71 static void   chat_alrm __P((int));
72 static int    chat_unalarm __P((void));
73 static int    getdigit __P((unsigned char **, int, int));
74 static char   **read_chat __P((char **));
75 static char   *cleanchr __P((char **, unsigned char));
76 static char   *cleanstr __P((const unsigned char *, int));
77 static const char *result __P((int));
78 static int    chat_expect __P((const char *));
79 static int    chat_send __P((char const *));
80 
81 
82 /*
83  * alarm signal handler
84  * handle timeouts in read/write
85  * change stdin to non-blocking mode to prevent
86  * possible hang in read().
87  */
88 
89 static void
90 chat_alrm(signo)
91 	int signo;
92 {
93 	int on = 1;
94 
95 	alarm(1);
96 	alarmed = 1;
97 	signal(SIGALRM, chat_alrm);
98 	ioctl(STDIN_FILENO, FIONBIO, &on);
99 }
100 
101 
102 /*
103  * Turn back on blocking mode reset by chat_alrm()
104  */
105 
106 static int
107 chat_unalarm()
108 {
109 	int off = 0;
110 	return ioctl(STDIN_FILENO, FIONBIO, &off);
111 }
112 
113 
114 /*
115  * convert a string of a given base (octal/hex) to binary
116  */
117 
118 static int
119 getdigit(ptr, base, max)
120 	unsigned char **ptr;
121 	int base, max;
122 {
123 	int i, val = 0;
124 	char * q;
125 
126 	static const char xdigits[] = "0123456789abcdef";
127 
128 	for (i = 0, q = *ptr; i++ < max; ++q) {
129 		int sval;
130 		const char * s = strchr(xdigits, tolower(*q));
131 
132 		if (s == NULL || (sval = s - xdigits) >= base)
133 			break;
134 		val = (val * base) + sval;
135 	}
136 	*ptr = q;
137 	return val;
138 }
139 
140 
141 /*
142  * read_chat()
143  * Convert a whitespace delimtied string into an array
144  * of strings, being expect/send pairs
145  */
146 
147 static char **
148 read_chat(chatstr)
149 	char **chatstr;
150 {
151 	char *str = *chatstr;
152 	char **res = NULL;
153 
154 	if (str != NULL) {
155 		char *tmp = NULL;
156 		int l;
157 
158 		if ((l=strlen(str)) > 0 && (tmp=malloc(l + 1)) != NULL &&
159 		    (res=malloc((l / 2 + 1) * sizeof(char *))) != NULL) {
160 			static char ws[] = " \t";
161 			char * p;
162 
163 			for (l = 0, p = strtok(strcpy(tmp, str), ws);
164 			     p != NULL;
165 			     p = strtok(NULL, ws))
166 			{
167 				unsigned char *q, *r;
168 
169 				/* Read escapes */
170 				for (q = r = (unsigned char *)p; *r; ++q)
171 				{
172 					int val;
173 
174 					if (*q == '\\')
175 					{
176 						/* handle special escapes */
177 						switch (*++q)
178 						{
179 						case 'a': /* bell */
180 							*r++ = '\a';
181 							break;
182 						case 'r': /* cr */
183 							*r++ = '\r';
184 							break;
185 						case 'n': /* nl */
186 							*r++ = '\n';
187 							break;
188 						case 'f': /* ff */
189 							*r++ = '\f';
190 							break;
191 						case 'b': /* bs */
192 							*r++ = '\b';
193 							break;
194 						case 'e': /* esc */
195 							*r++ = 27;
196 							break;
197 						case 't': /* tab */
198 							*r++ = '\t';
199 							break;
200 						case 'p': /* pause */
201 							*r++ = PAUSE_CH;
202 							break;
203 						case 's':
204 						case 'S': /* space */
205 							*r++ = ' ';
206 							break;
207 						case 'x': /* hexdigit */
208 							++q;
209 							*r++ = getdigit(&q, 16, 2);
210 							--q;
211 							break;
212 						case '0': /* octal */
213 							++q;
214 							*r++ = getdigit(&q, 8, 3);
215 							--q;
216 							break;
217 						default: /* literal */
218 							*r++ = *q;
219 							break;
220 						case 0: /* not past eos */
221 							--q;
222 							break;
223 						}
224 					} else {
225 						/* copy standard character */
226 						*r++ == *q;
227 					}
228 				}
229 
230 				/* Remove surrounding quotes, if any
231 				 */
232 				if (*p == '"' || *p == '\'') {
233 					q = strrchr(p+1, *p);
234 					if (q != NULL && *q == *p && q[1] == '\0') {
235 						*q = '\0';
236 						strcpy(p, p+1);
237 					}
238 				}
239 
240 				res[l++] = p;
241 			}
242 			res[l] = NULL;
243 			*chatstr = tmp;
244 			return res;
245 		}
246 		free(tmp);
247 	}
248 	return res;
249 }
250 
251 
252 /*
253  * clean a character for display (ctrl/meta character)
254  */
255 
256 static char *
257 cleanchr(buf, ch)
258 	char **buf;
259 	unsigned char ch;
260 {
261 	int l;
262 	static char tmpbuf[5];
263 	char * tmp = buf ? *buf : tmpbuf;
264 
265 	if (ch & 0x80) {
266 		strcpy(tmp, "M-");
267 		l = 2;
268 		ch &= 0x7f;
269 	} else
270 	l = 0;
271 
272 	if (ch < 32) {
273 		tmp[l++] = '^';
274 		tmp[l++] = ch + '@';
275 	} else if (ch == 127) {
276 		tmp[l++] = '^';
277 		tmp[l++] = '?';
278 	} else
279 		tmp[l++] = ch;
280 	tmp[l] = '\0';
281 
282 	if (buf)
283 		*buf = tmp + l;
284 	return tmp;
285 }
286 
287 
288 /*
289  * clean a string for display (ctrl/meta characters)
290  */
291 
292 static char *
293 cleanstr(s, l)
294 	const unsigned char *s;
295 	int l;
296 {
297 	static unsigned char * tmp = NULL;
298 	static int tmplen = 0;
299 
300 	if (tmplen < l * 4 + 1)
301 		tmp = realloc(tmp, tmplen = l * 4 + 1);
302 
303 	if (tmp == NULL) {
304 		tmplen = 0;
305 		return (char *)"(mem alloc error)";
306 	} else {
307 		int i = 0;
308 		char * p = tmp;
309 
310 		while (i < l)
311 			cleanchr(&p, s[i++]);
312 		*p = '\0';
313 	}
314 
315 	return tmp;
316 }
317 
318 
319 /*
320  * return result as an pseudo-english word
321  */
322 
323 static const char *
324 result(r)
325 	int r;
326 {
327 	static const char * results[] = {
328 		"OK", "MEMERROR", "IOERROR", "TIMEOUT"
329 	};
330 	return results[r & 3];
331 }
332 
333 
334 /*
335  * chat_expect()
336  * scan input for an expected string
337  */
338 
339 static int
340 chat_expect(str)
341 	const char *str;
342 {
343 	int len, r = 0;
344 
345 	if (chat_debug & CHATDEBUG_EXPECT)
346 		syslog(LOG_DEBUG, "chat_expect '%s'", cleanstr(str, strlen(str)));
347 
348 	if ((len = strlen(str)) > 0) {
349 		int i = 0;
350 		char * got;
351 
352 		if ((got = malloc(len + 1)) == NULL)
353 			r = 1;
354 		else {
355 
356 			memset(got, 0, len+1);
357 			alarm(chat_alarm);
358 			alarmed = 0;
359 
360 			while (r == 0 && i < len) {
361 				if (alarmed)
362 					r = 3;
363 				else {
364 					unsigned char ch;
365 
366 					if (read(STDIN_FILENO, &ch, 1) == 1) {
367 
368 						if (chat_debug & CHATDEBUG_RECEIVE)
369 							syslog(LOG_DEBUG, "chat_recv '%s' m=%d",
370 								cleanchr(NULL, ch), i);
371 
372 						if (ch == str[i])
373 							got[i++] = ch;
374 						else if (i > 0) {
375 							int j = 1;
376 
377 							/* See if we can resync on a
378 							 * partial match in our buffer
379 							 */
380 							while (j < i && memcmp(got + j, str, i - j) != NULL)
381 								j++;
382 							if (j < i)
383 								memcpy(got, got + j, i - j);
384 							i -= j;
385 						}
386 					} else
387 						r = alarmed ? 3 : 2;
388 				}
389 			}
390 			alarm(0);
391         		chat_unalarm();
392         		alarmed = 0;
393         		free(got);
394 		}
395 	}
396 
397 	if (chat_debug & CHATDEBUG_EXPECT)
398 		syslog(LOG_DEBUG, "chat_expect %s", result(r));
399 
400 	return r;
401 }
402 
403 
404 /*
405  * chat_send()
406  * send a chat string
407  */
408 
409 static int
410 chat_send(str)
411 	char const *str;
412 {
413 	int r = 0;
414 
415 	if (chat_debug && CHATDEBUG_SEND)
416 		syslog(LOG_DEBUG, "chat_send '%s'", cleanstr(str, strlen(str)));
417 
418 	if (*str) {
419                 alarm(chat_alarm);
420                 alarmed = 0;
421                 while (r == 0 && *str)
422                 {
423                         unsigned char ch = (unsigned char)*str++;
424 
425                         if (alarmed)
426         			r = 3;
427                         else if (ch == PAUSE_CH)
428 				usleep(500000); /* 1/2 second */
429 			else  {
430 				usleep(10000);	/* be kind to modem */
431                                 if (write(STDOUT_FILENO, &ch, 1) != 1)
432         		  		r = alarmed ? 3 : 2;
433                         }
434                 }
435                 alarm(0);
436                 chat_unalarm();
437                 alarmed = 0;
438 	}
439 
440         if (chat_debug & CHATDEBUG_SEND)
441           syslog(LOG_DEBUG, "chat_send %s", result(r));
442 
443         return r;
444 }
445 
446 
447 /*
448  * getty_chat()
449  *
450  * Termination codes:
451  * -1 - no script supplied
452  *  0 - script terminated correctly
453  *  1 - invalid argument, expect string too large, etc.
454  *  2 - error on an I/O operation or fatal error condition
455  *  3 - timeout waiting for a simple string
456  *
457  * Parameters:
458  *  char *scrstr     - unparsed chat script
459  *  timeout          - seconds timeout
460  *  debug            - debug value (bitmask)
461  */
462 
463 int
464 getty_chat(scrstr, timeout, debug)
465 	char *scrstr;
466 	int timeout, debug;
467 {
468         int r = -1;
469 
470         chat_alarm = timeout ? timeout : CHAT_DEFAULT_TIMEOUT;
471         chat_debug = debug;
472 
473         if (scrstr != NULL) {
474                 char **script;
475 
476                 if (chat_debug & CHATDEBUG_MISC)
477 			syslog(LOG_DEBUG, "getty_chat script='%s'", scrstr);
478 
479                 if ((script = read_chat(&scrstr)) != NULL) {
480                         int i = r = 0;
481 			int off = 0;
482                         sig_t old_alarm;
483                         struct termios tneed;
484 
485                         /*
486 			 * We need to be in raw mode for all this
487 			 * Rely on caller...
488                          */
489 
490                         old_alarm = signal(SIGALRM, chat_alrm);
491                         chat_unalarm(); /* Force blocking mode at start */
492 
493 			/*
494 			 * This is the send/expect loop
495 			 */
496                         while (r == 0 && script[i] != NULL)
497 				if ((r = chat_expect(script[i++])) == 0 && script[i] != NULL)
498 					r = chat_send(script[i++]);
499 
500                         signal(SIGALRM, old_alarm);
501                         free(script);
502                         free(scrstr);
503 
504 			/*
505 			 * Ensure stdin is in blocking mode
506 			 */
507                         ioctl(STDIN_FILENO, FIONBIO, &off);
508                 }
509 
510                 if (chat_debug & CHATDEBUG_MISC)
511                   syslog(LOG_DEBUG, "getty_chat %s", result(r));
512 
513         }
514         return r;
515 }
516