1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89#include "libbb.h"
90#include "common_bufsiz.h"
91
92
93#define DEFAULT_CHAT_TIMEOUT 45*1000
94
95
96#define MAX_ABORT_LEN 50
97
98
99enum {
100 ERR_OK = 0,
101 ERR_MEM,
102 ERR_IO,
103 ERR_TIMEOUT,
104 ERR_ABORT,
105
106
107};
108
109
110#define exitcode bb_got_signal
111
112
113static void signal_handler(UNUSED_PARAM int signo)
114{
115
116 exitcode = ERR_IO;
117}
118
119#if !ENABLE_FEATURE_CHAT_IMPLICIT_CR
120#define unescape(s, nocr) unescape(s)
121#endif
122static size_t unescape(char *s, int *nocr)
123{
124 char *start = s;
125 char *p = s;
126
127 while (*s) {
128 char c = *s;
129
130
131
132 if ('\\' == c) {
133 c = *++s;
134 if (c) {
135#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
136 if ('c' == c) {
137 *nocr = 1;
138 goto next;
139 }
140#endif
141 if ('N' == c) {
142 c = '\0';
143 } else if ('s' == c) {
144 c = ' ';
145#if ENABLE_FEATURE_CHAT_NOFAIL
146
147
148 } else if ('-' == c && (start + 1 == s)) {
149
150#endif
151 } else {
152 c = bb_process_escape_sequence((const char **)&s);
153 s--;
154 }
155 }
156
157 } else if ('^' == c) {
158 c = *++s-'@';
159 }
160
161 *p++ = c;
162#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
163 next:
164#endif
165
166 s++;
167 }
168 *p = '\0';
169
170 return p - start;
171}
172
173int chat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
174int chat_main(int argc UNUSED_PARAM, char **argv)
175{
176 int record_fd = -1;
177 bool echo = 0;
178
179 llist_t *aborts = NULL;
180
181 int timeout = DEFAULT_CHAT_TIMEOUT;
182
183#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
184 size_t max_abort_len = 0;
185#else
186#define max_abort_len MAX_ABORT_LEN
187#endif
188#if ENABLE_FEATURE_CHAT_TTY_HIFI
189 struct termios tio0, tio;
190#endif
191
192 enum {
193 DIR_HANGUP = 0,
194 DIR_ABORT,
195#if ENABLE_FEATURE_CHAT_CLR_ABORT
196 DIR_CLR_ABORT,
197#endif
198 DIR_TIMEOUT,
199 DIR_ECHO,
200 DIR_SAY,
201 DIR_RECORD,
202 };
203
204#define inbuf bb_common_bufsiz1
205 setup_common_bufsiz();
206
207
208 xfunc_error_retval = ERR_IO;
209
210
211 bb_signals(0
212 + (1 << SIGHUP)
213 + (1 << SIGINT)
214 + (1 << SIGTERM)
215 + (1 << SIGPIPE)
216 , signal_handler);
217
218#if ENABLE_FEATURE_CHAT_TTY_HIFI
219
220 tcgetattr(STDIN_FILENO, &tio);
221 tio0 = tio;
222 cfmakeraw(&tio);
223 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio);
224#endif
225
226#if ENABLE_FEATURE_CHAT_SWALLOW_OPTS
227 getopt32(argv, "vVsSE");
228 argv += optind;
229#else
230 argv++;
231#endif
232
233 while (*argv) {
234
235 int key = index_in_strings(
236 "HANGUP\0" "ABORT\0"
237#if ENABLE_FEATURE_CHAT_CLR_ABORT
238 "CLR_ABORT\0"
239#endif
240 "TIMEOUT\0" "ECHO\0" "SAY\0" "RECORD\0"
241 , *argv
242 );
243 if (key >= 0) {
244 bool onoff;
245
246 char *arg = *++argv;
247
248 if (!arg) {
249#if ENABLE_FEATURE_CHAT_TTY_HIFI
250 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
251#endif
252 bb_show_usage();
253 }
254
255 onoff = (0 != strcmp("OFF", arg));
256
257 if (DIR_HANGUP == key) {
258
259 signal(SIGHUP, onoff ? signal_handler : SIG_IGN);
260 } else if (DIR_ABORT == key) {
261
262#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
263 size_t len = strlen(arg);
264 if (len > max_abort_len)
265 max_abort_len = len;
266#endif
267 llist_add_to_end(&aborts, arg);
268#if ENABLE_FEATURE_CHAT_CLR_ABORT
269 } else if (DIR_CLR_ABORT == key) {
270 llist_t *l;
271
272
273# if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
274 max_abort_len = 0;
275# endif
276 for (l = aborts; l; l = l->link) {
277# if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
278 size_t len = strlen(l->data);
279# endif
280 if (strcmp(arg, l->data) == 0) {
281 llist_unlink(&aborts, l);
282 continue;
283 }
284# if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
285 if (len > max_abort_len)
286 max_abort_len = len;
287# endif
288 }
289#endif
290 } else if (DIR_TIMEOUT == key) {
291
292
293 timeout = atoi(arg) * 1000;
294
295
296 if (!timeout)
297 timeout = DEFAULT_CHAT_TIMEOUT;
298 } else if (DIR_ECHO == key) {
299
300
301 echo = onoff;
302 } else if (DIR_RECORD == key) {
303
304
305
306 if (record_fd > 0)
307 close(record_fd);
308
309 record_fd = (onoff) ? xopen(arg, O_WRONLY|O_CREAT|O_TRUNC) : -1;
310 } else if (DIR_SAY == key) {
311
312
313 bb_simple_error_msg(arg);
314 }
315
316 argv++;
317
318 } else {
319
320
321
322 int expect_len;
323 size_t buf_len = 0;
324 size_t max_len = max_abort_len;
325
326 struct pollfd pfd;
327#if ENABLE_FEATURE_CHAT_NOFAIL
328 int nofail = 0;
329#endif
330 char *expect = *argv++;
331
332
333 if (!expect)
334 goto expect_done;
335
336#if ENABLE_FEATURE_CHAT_NOFAIL
337
338 if ('-' == *expect) {
339
340 expect++;
341
342 nofail++;
343 }
344#endif
345
346#ifdef ___TEST___BUF___
347# undef COMMON_BUFSIZE
348# define COMMON_BUFSIZE 6
349#endif
350
351 expect_len = unescape(expect, &expect_len );
352 if (expect_len > max_len)
353 max_len = expect_len;
354
355
356
357 if (!expect_len)
358 goto expect_done;
359 if (max_len >= COMMON_BUFSIZE) {
360 exitcode = ERR_MEM;
361 goto expect_done;
362 }
363
364
365 pfd.fd = STDIN_FILENO;
366 pfd.events = POLLIN;
367 while (exitcode == ERR_OK
368 && poll(&pfd, 1, timeout) > 0
369
370 ) {
371 llist_t *l;
372 ssize_t delta;
373
374
375 if (safe_read(STDIN_FILENO, inbuf + buf_len, 1) <= 0) {
376 exitcode = ERR_IO;
377 goto expect_done;
378 }
379
380
381 if (record_fd > 0) {
382 full_write(record_fd, inbuf + buf_len, 1);
383 }
384
385 if (echo) {
386
387
388
389
390 full_write(STDERR_FILENO, inbuf + buf_len, 1);
391 }
392 buf_len++;
393
394 if (buf_len > COMMON_BUFSIZE) {
395 memmove(inbuf, inbuf + buf_len - max_len, max_len);
396 buf_len = max_len;
397 }
398
399
400
401
402
403
404 for (l = aborts, exitcode = ERR_ABORT; l; l = l->link, ++exitcode) {
405 size_t len = strlen(l->data);
406 delta = buf_len - len;
407 if (delta >= 0 && !memcmp(inbuf + delta, l->data, len))
408 goto expect_done;
409 }
410 exitcode = ERR_OK;
411
412
413 delta = buf_len - expect_len;
414 if (delta >= 0 && memcmp(inbuf + delta, expect, expect_len) == 0)
415 goto expect_done;
416 }
417
418
419
420 exitcode = ERR_TIMEOUT;
421 expect_done:
422#if ENABLE_FEATURE_CHAT_NOFAIL
423
424
425 if (nofail) {
426 if (!exitcode) {
427
428 while (*argv && argv[1] && '-' == argv[1][0])
429 argv += 2;
430
431
432 if (!*argv++ || !*argv++)
433 break;
434 }
435
436 if (ERR_IO != exitcode)
437 exitcode = ERR_OK;
438 }
439#endif
440
441 if (exitcode != ERR_OK)
442 break;
443
444
445
446
447 if (*argv) {
448#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
449 int nocr = 0;
450#endif
451 char *loaded = NULL;
452 size_t len;
453 char *buf = *argv++;
454
455
456
457 if ('@' == *buf) {
458
459 trim(++buf);
460 buf = loaded = xmalloc_xopen_read_close(buf, NULL);
461 }
462
463 len = unescape(buf, &nocr);
464
465
466 alarm(timeout);
467 pfd.fd = STDOUT_FILENO;
468 pfd.events = POLLOUT;
469 while (len && !exitcode
470 && poll(&pfd, 1, -1) > 0
471 && (pfd.revents & POLLOUT)
472 ) {
473#if ENABLE_FEATURE_CHAT_SEND_ESCAPES
474
475
476 char c = *buf;
477 if ('\\' == c) {
478 c = *++buf;
479 if ('d' == c) {
480 sleep1();
481 len--;
482 continue;
483 }
484 if ('p' == c) {
485 msleep(10);
486 len--;
487 continue;
488 }
489 if ('K' == c) {
490 tcsendbreak(STDOUT_FILENO, 0);
491 len--;
492 continue;
493 }
494 buf--;
495 }
496 if (safe_write(STDOUT_FILENO, buf, 1) != 1)
497 break;
498 len--;
499 buf++;
500#else
501 len -= full_write(STDOUT_FILENO, buf, len);
502#endif
503 }
504 alarm(0);
505
506
507 if (len)
508 exitcode = ERR_IO;
509
510
511 if (loaded)
512 free(loaded);
513#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
514
515 else if (!nocr)
516 xwrite_str(STDOUT_FILENO, "\r");
517#endif
518
519 if (exitcode)
520 break;
521 }
522 }
523 }
524
525#if ENABLE_FEATURE_CHAT_TTY_HIFI
526 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
527#endif
528
529 return exitcode;
530}
531