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