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