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