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