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