chat.c 11 KB

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