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