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