]> git.saurik.com Git - apple/system_cmds.git/blob - getty.tproj/chat.c
system_cmds-433.8.tar.gz
[apple/system_cmds.git] / getty.tproj / chat.c
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 }