3
0

chat.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * bare bones chat utility
  4. * inspired by ppp's chat
  5. *
  6. * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
  7. *
  8. * Licensed under GPLv2, see file LICENSE in this source tree.
  9. */
  10. //usage:#define chat_trivial_usage
  11. //usage: "EXPECT [SEND [EXPECT [SEND...]]]"
  12. //usage:#define chat_full_usage "\n\n"
  13. //usage: "Useful for interacting with a modem connected to stdin/stdout.\n"
  14. //usage: "A script consists of one or more \"expect-send\" pairs of strings,\n"
  15. //usage: "each pair is a pair of arguments. Example:\n"
  16. //usage: "chat '' ATZ OK ATD123456 CONNECT '' ogin: pppuser word: ppppass '~'"
  17. #include "libbb.h"
  18. #include "common_bufsiz.h"
  19. // default timeout: 45 sec
  20. #define DEFAULT_CHAT_TIMEOUT 45*1000
  21. // max length of "abort string",
  22. // i.e. device reply which causes termination
  23. #define MAX_ABORT_LEN 50
  24. // possible exit codes
  25. enum {
  26. ERR_OK = 0, // all's well
  27. ERR_MEM, // read too much while expecting
  28. ERR_IO, // signalled or I/O error
  29. ERR_TIMEOUT, // timed out while expecting
  30. ERR_ABORT, // first abort condition was met
  31. // ERR_ABORT2, // second abort condition was met
  32. // ...
  33. };
  34. // exit code
  35. #define exitcode bb_got_signal
  36. // trap for critical signals
  37. static void signal_handler(UNUSED_PARAM int signo)
  38. {
  39. // report I/O error condition
  40. exitcode = ERR_IO;
  41. }
  42. #if !ENABLE_FEATURE_CHAT_IMPLICIT_CR
  43. #define unescape(s, nocr) unescape(s)
  44. #endif
  45. static size_t unescape(char *s, int *nocr)
  46. {
  47. char *start = s;
  48. char *p = s;
  49. while (*s) {
  50. char c = *s;
  51. // do we need special processing?
  52. // standard escapes + \s for space and \N for \0
  53. // \c inhibits terminating \r for commands and is noop for expects
  54. if ('\\' == c) {
  55. c = *++s;
  56. if (c) {
  57. #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
  58. if ('c' == c) {
  59. *nocr = 1;
  60. goto next;
  61. }
  62. #endif
  63. if ('N' == c) {
  64. c = '\0';
  65. } else if ('s' == c) {
  66. c = ' ';
  67. #if ENABLE_FEATURE_CHAT_NOFAIL
  68. // unescape leading dash only
  69. // TODO: and only for expect, not command string
  70. } else if ('-' == c && (start + 1 == s)) {
  71. //c = '-';
  72. #endif
  73. } else {
  74. c = bb_process_escape_sequence((const char **)&s);
  75. s--;
  76. }
  77. }
  78. // ^A becomes \001, ^B -- \002 and so on...
  79. } else if ('^' == c) {
  80. c = *++s-'@';
  81. }
  82. // put unescaped char
  83. *p++ = c;
  84. #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
  85. next:
  86. #endif
  87. // next char
  88. s++;
  89. }
  90. *p = '\0';
  91. return p - start;
  92. }
  93. int chat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  94. int chat_main(int argc UNUSED_PARAM, char **argv)
  95. {
  96. int record_fd = -1;
  97. bool echo = 0;
  98. // collection of device replies which cause unconditional termination
  99. llist_t *aborts = NULL;
  100. // inactivity period
  101. int timeout = DEFAULT_CHAT_TIMEOUT;
  102. // maximum length of abort string
  103. #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
  104. size_t max_abort_len = 0;
  105. #else
  106. #define max_abort_len MAX_ABORT_LEN
  107. #endif
  108. #if ENABLE_FEATURE_CHAT_TTY_HIFI
  109. struct termios tio0, tio;
  110. #endif
  111. // directive names
  112. enum {
  113. DIR_HANGUP = 0,
  114. DIR_ABORT,
  115. #if ENABLE_FEATURE_CHAT_CLR_ABORT
  116. DIR_CLR_ABORT,
  117. #endif
  118. DIR_TIMEOUT,
  119. DIR_ECHO,
  120. DIR_SAY,
  121. DIR_RECORD,
  122. };
  123. // make x* functions fail with correct exitcode
  124. xfunc_error_retval = ERR_IO;
  125. // trap vanilla signals to prevent process from being killed suddenly
  126. bb_signals(0
  127. + (1 << SIGHUP)
  128. + (1 << SIGINT)
  129. + (1 << SIGTERM)
  130. + (1 << SIGPIPE)
  131. , signal_handler);
  132. #if ENABLE_FEATURE_CHAT_TTY_HIFI
  133. tcgetattr(STDIN_FILENO, &tio);
  134. tio0 = tio;
  135. cfmakeraw(&tio);
  136. tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio);
  137. #endif
  138. #if ENABLE_FEATURE_CHAT_SWALLOW_OPTS
  139. getopt32(argv, "vVsSE");
  140. argv += optind;
  141. #else
  142. argv++; // goto first arg
  143. #endif
  144. // handle chat expect-send pairs
  145. while (*argv) {
  146. // directive given? process it
  147. int key = index_in_strings(
  148. "HANGUP\0" "ABORT\0"
  149. #if ENABLE_FEATURE_CHAT_CLR_ABORT
  150. "CLR_ABORT\0"
  151. #endif
  152. "TIMEOUT\0" "ECHO\0" "SAY\0" "RECORD\0"
  153. , *argv
  154. );
  155. if (key >= 0) {
  156. // cache directive value
  157. char *arg = *++argv;
  158. // OFF -> 0, anything else -> 1
  159. bool onoff = (0 != strcmp("OFF", arg));
  160. // process directive
  161. if (DIR_HANGUP == key) {
  162. // turn SIGHUP on/off
  163. signal(SIGHUP, onoff ? signal_handler : SIG_IGN);
  164. } else if (DIR_ABORT == key) {
  165. // append the string to abort conditions
  166. #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
  167. size_t len = strlen(arg);
  168. if (len > max_abort_len)
  169. max_abort_len = len;
  170. #endif
  171. llist_add_to_end(&aborts, arg);
  172. #if ENABLE_FEATURE_CHAT_CLR_ABORT
  173. } else if (DIR_CLR_ABORT == key) {
  174. llist_t *l;
  175. // remove the string from abort conditions
  176. // N.B. gotta refresh maximum length too...
  177. # if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
  178. max_abort_len = 0;
  179. # endif
  180. for (l = aborts; l; l = l->link) {
  181. # if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
  182. size_t len = strlen(l->data);
  183. # endif
  184. if (strcmp(arg, l->data) == 0) {
  185. llist_unlink(&aborts, l);
  186. continue;
  187. }
  188. # if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
  189. if (len > max_abort_len)
  190. max_abort_len = len;
  191. # endif
  192. }
  193. #endif
  194. } else if (DIR_TIMEOUT == key) {
  195. // set new timeout
  196. // -1 means OFF
  197. timeout = atoi(arg) * 1000;
  198. // 0 means default
  199. // >0 means value in msecs
  200. if (!timeout)
  201. timeout = DEFAULT_CHAT_TIMEOUT;
  202. } else if (DIR_ECHO == key) {
  203. // turn echo on/off
  204. // N.B. echo means dumping device input/output to stderr
  205. echo = onoff;
  206. } else if (DIR_RECORD == key) {
  207. // turn record on/off
  208. // N.B. record means dumping device input to a file
  209. // close previous record_fd
  210. if (record_fd > 0)
  211. close(record_fd);
  212. // N.B. do we have to die here on open error?
  213. record_fd = (onoff) ? xopen(arg, O_WRONLY|O_CREAT|O_TRUNC) : -1;
  214. } else if (DIR_SAY == key) {
  215. // just print argument verbatim
  216. // TODO: should we use full_write() to avoid unistd/stdio conflict?
  217. bb_error_msg("%s", arg);
  218. }
  219. // next, please!
  220. argv++;
  221. // ordinary expect-send pair!
  222. } else {
  223. //-----------------------
  224. // do expect
  225. //-----------------------
  226. int expect_len;
  227. size_t buf_len = 0;
  228. size_t max_len = max_abort_len;
  229. struct pollfd pfd;
  230. #if ENABLE_FEATURE_CHAT_NOFAIL
  231. int nofail = 0;
  232. #endif
  233. char *expect = *argv++;
  234. // sanity check: shall we really expect something?
  235. if (!expect)
  236. goto expect_done;
  237. #if ENABLE_FEATURE_CHAT_NOFAIL
  238. // if expect starts with -
  239. if ('-' == *expect) {
  240. // swallow -
  241. expect++;
  242. // and enter nofail mode
  243. nofail++;
  244. }
  245. #endif
  246. #ifdef ___TEST___BUF___ // test behaviour with a small buffer
  247. # undef COMMON_BUFSIZE
  248. # define COMMON_BUFSIZE 6
  249. #endif
  250. // expand escape sequences in expect
  251. expect_len = unescape(expect, &expect_len /*dummy*/);
  252. if (expect_len > max_len)
  253. max_len = expect_len;
  254. // sanity check:
  255. // we should expect more than nothing but not more than input buffer
  256. // TODO: later we'll get rid of fixed-size buffer
  257. if (!expect_len)
  258. goto expect_done;
  259. if (max_len >= COMMON_BUFSIZE) {
  260. exitcode = ERR_MEM;
  261. goto expect_done;
  262. }
  263. // get reply
  264. pfd.fd = STDIN_FILENO;
  265. pfd.events = POLLIN;
  266. while (!exitcode
  267. && poll(&pfd, 1, timeout) > 0
  268. && (pfd.revents & POLLIN)
  269. ) {
  270. llist_t *l;
  271. ssize_t delta;
  272. #define buf bb_common_bufsiz1
  273. setup_common_bufsiz();
  274. // read next char from device
  275. if (safe_read(STDIN_FILENO, buf+buf_len, 1) > 0) {
  276. // dump device input if RECORD fname
  277. if (record_fd > 0) {
  278. full_write(record_fd, buf+buf_len, 1);
  279. }
  280. // dump device input if ECHO ON
  281. if (echo) {
  282. // if (buf[buf_len] < ' ') {
  283. // full_write(STDERR_FILENO, "^", 1);
  284. // buf[buf_len] += '@';
  285. // }
  286. full_write(STDERR_FILENO, buf+buf_len, 1);
  287. }
  288. buf_len++;
  289. // move input frame if we've reached higher bound
  290. if (buf_len > COMMON_BUFSIZE) {
  291. memmove(buf, buf+buf_len-max_len, max_len);
  292. buf_len = max_len;
  293. }
  294. }
  295. // N.B. rule of thumb: values being looked for can
  296. // be found only at the end of input buffer
  297. // this allows to get rid of strstr() and memmem()
  298. // TODO: make expect and abort strings processed uniformly
  299. // abort condition is met? -> bail out
  300. for (l = aborts, exitcode = ERR_ABORT; l; l = l->link, ++exitcode) {
  301. size_t len = strlen(l->data);
  302. delta = buf_len-len;
  303. if (delta >= 0 && !memcmp(buf+delta, l->data, len))
  304. goto expect_done;
  305. }
  306. exitcode = ERR_OK;
  307. // expected reply received? -> goto next command
  308. delta = buf_len - expect_len;
  309. if (delta >= 0 && !memcmp(buf+delta, expect, expect_len))
  310. goto expect_done;
  311. #undef buf
  312. } /* while (have data) */
  313. // device timed out or unexpected reply received
  314. exitcode = ERR_TIMEOUT;
  315. expect_done:
  316. #if ENABLE_FEATURE_CHAT_NOFAIL
  317. // on success and when in nofail mode
  318. // we should skip following subsend-subexpect pairs
  319. if (nofail) {
  320. if (!exitcode) {
  321. // find last send before non-dashed expect
  322. while (*argv && argv[1] && '-' == argv[1][0])
  323. argv += 2;
  324. // skip the pair
  325. // N.B. do we really need this?!
  326. if (!*argv++ || !*argv++)
  327. break;
  328. }
  329. // nofail mode also clears all but IO errors (or signals)
  330. if (ERR_IO != exitcode)
  331. exitcode = ERR_OK;
  332. }
  333. #endif
  334. // bail out unless we expected successfully
  335. if (exitcode)
  336. break;
  337. //-----------------------
  338. // do send
  339. //-----------------------
  340. if (*argv) {
  341. #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
  342. int nocr = 0; // inhibit terminating command with \r
  343. #endif
  344. char *loaded = NULL; // loaded command
  345. size_t len;
  346. char *buf = *argv++;
  347. // if command starts with @
  348. // load "real" command from file named after @
  349. if ('@' == *buf) {
  350. // skip the @ and any following white-space
  351. trim(++buf);
  352. buf = loaded = xmalloc_xopen_read_close(buf, NULL);
  353. }
  354. // expand escape sequences in command
  355. len = unescape(buf, &nocr);
  356. // send command
  357. alarm(timeout);
  358. pfd.fd = STDOUT_FILENO;
  359. pfd.events = POLLOUT;
  360. while (len && !exitcode
  361. && poll(&pfd, 1, -1) > 0
  362. && (pfd.revents & POLLOUT)
  363. ) {
  364. #if ENABLE_FEATURE_CHAT_SEND_ESCAPES
  365. // "\\d" means 1 sec delay, "\\p" means 0.01 sec delay
  366. // "\\K" means send BREAK
  367. char c = *buf;
  368. if ('\\' == c) {
  369. c = *++buf;
  370. if ('d' == c) {
  371. sleep(1);
  372. len--;
  373. continue;
  374. }
  375. if ('p' == c) {
  376. usleep(10000);
  377. len--;
  378. continue;
  379. }
  380. if ('K' == c) {
  381. tcsendbreak(STDOUT_FILENO, 0);
  382. len--;
  383. continue;
  384. }
  385. buf--;
  386. }
  387. if (safe_write(STDOUT_FILENO, buf, 1) != 1)
  388. break;
  389. len--;
  390. buf++;
  391. #else
  392. len -= full_write(STDOUT_FILENO, buf, len);
  393. #endif
  394. } /* while (can write) */
  395. alarm(0);
  396. // report I/O error if there still exists at least one non-sent char
  397. if (len)
  398. exitcode = ERR_IO;
  399. // free loaded command (if any)
  400. if (loaded)
  401. free(loaded);
  402. #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
  403. // or terminate command with \r (if not inhibited)
  404. else if (!nocr)
  405. xwrite(STDOUT_FILENO, "\r", 1);
  406. #endif
  407. // bail out unless we sent command successfully
  408. if (exitcode)
  409. break;
  410. } /* if (*argv) */
  411. }
  412. } /* while (*argv) */
  413. #if ENABLE_FEATURE_CHAT_TTY_HIFI
  414. tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
  415. #endif
  416. return exitcode;
  417. }