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
205 xfunc_error_retval = ERR_IO;
206
207
208 bb_signals(0
209 + (1 << SIGHUP)
210 + (1 << SIGINT)
211 + (1 << SIGTERM)
212 + (1 << SIGPIPE)
213 , signal_handler);
214
215#if ENABLE_FEATURE_CHAT_TTY_HIFI
216
217 tcgetattr(STDIN_FILENO, &tio);
218 tio0 = tio;
219 cfmakeraw(&tio);
220 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio);
221#endif
222
223#if ENABLE_FEATURE_CHAT_SWALLOW_OPTS
224 getopt32(argv, "vVsSE");
225 argv += optind;
226#else
227 argv++;
228#endif
229
230 while (*argv) {
231
232 int key = index_in_strings(
233 "HANGUP\0" "ABORT\0"
234#if ENABLE_FEATURE_CHAT_CLR_ABORT
235 "CLR_ABORT\0"
236#endif
237 "TIMEOUT\0" "ECHO\0" "SAY\0" "RECORD\0"
238 , *argv
239 );
240 if (key >= 0) {
241 bool onoff;
242
243 char *arg = *++argv;
244
245 if (!arg) {
246#if ENABLE_FEATURE_CHAT_TTY_HIFI
247 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
248#endif
249 bb_show_usage();
250 }
251
252 onoff = (0 != strcmp("OFF", arg));
253
254 if (DIR_HANGUP == key) {
255
256 signal(SIGHUP, onoff ? signal_handler : SIG_IGN);
257 } else if (DIR_ABORT == key) {
258
259#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
260 size_t len = strlen(arg);
261 if (len > max_abort_len)
262 max_abort_len = len;
263#endif
264 llist_add_to_end(&aborts, arg);
265#if ENABLE_FEATURE_CHAT_CLR_ABORT
266 } else if (DIR_CLR_ABORT == key) {
267 llist_t *l;
268
269
270# if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
271 max_abort_len = 0;
272# endif
273 for (l = aborts; l; l = l->link) {
274# if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
275 size_t len = strlen(l->data);
276# endif
277 if (strcmp(arg, l->data) == 0) {
278 llist_unlink(&aborts, l);
279 continue;
280 }
281# if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
282 if (len > max_abort_len)
283 max_abort_len = len;
284# endif
285 }
286#endif
287 } else if (DIR_TIMEOUT == key) {
288
289
290 timeout = atoi(arg) * 1000;
291
292
293 if (!timeout)
294 timeout = DEFAULT_CHAT_TIMEOUT;
295 } else if (DIR_ECHO == key) {
296
297
298 echo = onoff;
299 } else if (DIR_RECORD == key) {
300
301
302
303 if (record_fd > 0)
304 close(record_fd);
305
306 record_fd = (onoff) ? xopen(arg, O_WRONLY|O_CREAT|O_TRUNC) : -1;
307 } else if (DIR_SAY == key) {
308
309
310 bb_error_msg("%s", arg);
311 }
312
313 argv++;
314
315 } else {
316
317
318
319 int expect_len;
320 size_t buf_len = 0;
321 size_t max_len = max_abort_len;
322
323 struct pollfd pfd;
324#if ENABLE_FEATURE_CHAT_NOFAIL
325 int nofail = 0;
326#endif
327 char *expect = *argv++;
328
329
330 if (!expect)
331 goto expect_done;
332
333#if ENABLE_FEATURE_CHAT_NOFAIL
334
335 if ('-' == *expect) {
336
337 expect++;
338
339 nofail++;
340 }
341#endif
342
343#ifdef ___TEST___BUF___
344# undef COMMON_BUFSIZE
345# define COMMON_BUFSIZE 6
346#endif
347
348 expect_len = unescape(expect, &expect_len );
349 if (expect_len > max_len)
350 max_len = expect_len;
351
352
353
354 if (!expect_len)
355 goto expect_done;
356 if (max_len >= COMMON_BUFSIZE) {
357 exitcode = ERR_MEM;
358 goto expect_done;
359 }
360
361
362 pfd.fd = STDIN_FILENO;
363 pfd.events = POLLIN;
364 while (!exitcode
365 && poll(&pfd, 1, timeout) > 0
366 && (pfd.revents & POLLIN)
367 ) {
368 llist_t *l;
369 ssize_t delta;
370#define buf bb_common_bufsiz1
371 setup_common_bufsiz();
372
373
374 if (safe_read(STDIN_FILENO, buf+buf_len, 1) > 0) {
375
376 if (record_fd > 0) {
377 full_write(record_fd, buf+buf_len, 1);
378 }
379
380 if (echo) {
381
382
383
384
385 full_write(STDERR_FILENO, buf+buf_len, 1);
386 }
387 buf_len++;
388
389 if (buf_len > COMMON_BUFSIZE) {
390 memmove(buf, buf+buf_len-max_len, max_len);
391 buf_len = max_len;
392 }
393 }
394
395
396
397
398
399
400 for (l = aborts, exitcode = ERR_ABORT; l; l = l->link, ++exitcode) {
401 size_t len = strlen(l->data);
402 delta = buf_len-len;
403 if (delta >= 0 && !memcmp(buf+delta, l->data, len))
404 goto expect_done;
405 }
406 exitcode = ERR_OK;
407
408
409 delta = buf_len - expect_len;
410 if (delta >= 0 && !memcmp(buf+delta, expect, expect_len))
411 goto expect_done;
412#undef buf
413 }
414
415
416 exitcode = ERR_TIMEOUT;
417 expect_done:
418#if ENABLE_FEATURE_CHAT_NOFAIL
419
420
421 if (nofail) {
422 if (!exitcode) {
423
424 while (*argv && argv[1] && '-' == argv[1][0])
425 argv += 2;
426
427
428 if (!*argv++ || !*argv++)
429 break;
430 }
431
432 if (ERR_IO != exitcode)
433 exitcode = ERR_OK;
434 }
435#endif
436
437 if (exitcode)
438 break;
439
440
441
442
443 if (*argv) {
444#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
445 int nocr = 0;
446#endif
447 char *loaded = NULL;
448 size_t len;
449 char *buf = *argv++;
450
451
452
453 if ('@' == *buf) {
454
455 trim(++buf);
456 buf = loaded = xmalloc_xopen_read_close(buf, NULL);
457 }
458
459 len = unescape(buf, &nocr);
460
461
462 alarm(timeout);
463 pfd.fd = STDOUT_FILENO;
464 pfd.events = POLLOUT;
465 while (len && !exitcode
466 && poll(&pfd, 1, -1) > 0
467 && (pfd.revents & POLLOUT)
468 ) {
469#if ENABLE_FEATURE_CHAT_SEND_ESCAPES
470
471
472 char c = *buf;
473 if ('\\' == c) {
474 c = *++buf;
475 if ('d' == c) {
476 sleep(1);
477 len--;
478 continue;
479 }
480 if ('p' == c) {
481 usleep(10000);
482 len--;
483 continue;
484 }
485 if ('K' == c) {
486 tcsendbreak(STDOUT_FILENO, 0);
487 len--;
488 continue;
489 }
490 buf--;
491 }
492 if (safe_write(STDOUT_FILENO, buf, 1) != 1)
493 break;
494 len--;
495 buf++;
496#else
497 len -= full_write(STDOUT_FILENO, buf, len);
498#endif
499 }
500 alarm(0);
501
502
503 if (len)
504 exitcode = ERR_IO;
505
506
507 if (loaded)
508 free(loaded);
509#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
510
511 else if (!nocr)
512 xwrite(STDOUT_FILENO, "\r", 1);
513#endif
514
515 if (exitcode)
516 break;
517 }
518 }
519 }
520
521#if ENABLE_FEATURE_CHAT_TTY_HIFI
522 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
523#endif
524
525 return exitcode;
526}
527