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