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