1 /*-
2 * Copyright (c) 1997
3 * David L Nugent <[email protected]>.
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
chat_alrm(int signo __unused)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
chat_unalarm(void)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
getdigit(char ** ptr,int base,int max)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 **
read_chat(char ** chatstr)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 *
cleanchr(char ** buf,unsigned char ch)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 *
cleanstr(const char * s,int l)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 *
result(int r)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
chat_expect(const char * str)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
chat_send(char const * str)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
getty_chat(char * scrstr,int timeout,int debug)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