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