hharry.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. #include "terminal.h"
  2. #include "utils.h"
  3. #include "wordplay.h"
  4. #include <ctype.h>
  5. #include <fcntl.h>
  6. #include <fstream>
  7. #include <iomanip>
  8. #include <iostream>
  9. #include <pty.h>
  10. #include <sstream>
  11. #include <stdio.h>
  12. #include <stdlib.h> // random()
  13. #include <string.h>
  14. #include <string>
  15. #include <strings.h> // strcasecmp
  16. #include <sys/select.h>
  17. #include <sys/wait.h>
  18. #include <termios.h>
  19. #include <time.h>
  20. #include <unistd.h>
  21. struct console_details console;
  22. /*
  23. https://softwareengineering.stackexchange.com/questions/141973/how-do-you-achieve-a-numeric-versioning-scheme-with-git
  24. Use tags to mark commits with version numbers:
  25. git tag -a v2.5 -m 'Version 2.5'
  26. Push tags upstream—this is not done by default:
  27. git push --tags
  28. Then use the describe command:
  29. git describe --tags --long
  30. */
  31. std::string version = HHVERSION;
  32. /*
  33. We get the username/fullname from tailing the node logs,
  34. and reading the user.dat file.
  35. */
  36. std::string username;
  37. std::string fullname;
  38. // #include <signal.h> // handle Ctrl-C/SIGINT
  39. /* Log level guideline:
  40. * - ZF_LOG_FATAL - happened something impossible and absolutely unexpected.
  41. * Process can't continue and must be terminated.
  42. * Example: division by zero, unexpected modifications from other thread.
  43. * - ZF_LOG_ERROR - happened something possible, but highly unexpected. The
  44. * process is able to recover and continue execution.
  45. * Example: out of memory (could also be FATAL if not handled properly).
  46. * - ZF_LOG_WARN - happened something that *usually* should not happen and
  47. * significantly changes application behavior for some period of time.
  48. * Example: configuration file not found, auth error.
  49. * - ZF_LOG_INFO - happened significant life cycle event or major state
  50. * transition.
  51. * Example: app started, user logged in.
  52. * - ZF_LOG_DEBUG - minimal set of events that could help to reconstruct the
  53. * execution path. Usually disabled in release builds.
  54. * - ZF_LOG_VERBOSE - all other events. Usually disabled in release builds.
  55. *
  56. * *Ideally*, log file of debugged, well tested, production ready application
  57. * should be empty or very small. Choosing a right log level is as important as
  58. * providing short and self descriptive log message.
  59. */
  60. /*
  61. #define ZF_LOG_VERBOSE 1
  62. #define ZF_LOG_DEBUG 2
  63. #define ZF_LOG_INFO 3
  64. #define ZF_LOG_WARN 4
  65. #define ZF_LOG_ERROR 5
  66. #define ZF_LOG_FATAL 6
  67. */
  68. // When debugging low-level, use this:
  69. // #define ZF_LOG_LEVEL ZF_LOG_VERBOSE
  70. // Except this doesn't work. It needs to be anywere the
  71. // zf_log.h is included.
  72. // LOGGING with file output
  73. #include "zf_log.h"
  74. FILE *g_log_file;
  75. static void file_output_callback(const zf_log_message *msg, void *arg) {
  76. (void)arg;
  77. *msg->p = '\n';
  78. fwrite(msg->buf, msg->p - msg->buf + 1, 1, g_log_file);
  79. fflush(g_log_file);
  80. }
  81. static void file_output_close(void) { fclose(g_log_file); }
  82. static int file_output_open(const char *const log_path) {
  83. g_log_file = fopen(log_path, "a");
  84. if (!g_log_file) {
  85. ZF_LOGW("Failed to open log file %s", log_path);
  86. return 0;
  87. }
  88. atexit(file_output_close);
  89. zf_log_set_output_v(ZF_LOG_PUT_STD, 0, file_output_callback);
  90. return 1;
  91. }
  92. void log_flush(void) { fflush(g_log_file); }
  93. // END LOGGING
  94. /*
  95. What is the name of the actual, real Mystic executable
  96. that we'll be executing and mangling?
  97. */
  98. #define TARGET "./mySTIC"
  99. // Size of our input and output buffers.
  100. #define BSIZE 1024
  101. /*
  102. These are harry "timeout" events.
  103. These happen when we've been sitting around awhile.
  104. */
  105. int node;
  106. /*
  107. This only works for those few idiots that use the
  108. horribly broken SSH crap that Mystic uses.
  109. */
  110. int locate_user(const char *alias) {
  111. FILE *user;
  112. char buffer[0x600];
  113. char temp[100];
  114. user = fopen("data/users.dat", "rb");
  115. if (user == NULL)
  116. return 0;
  117. // Carry on!
  118. while (fread(buffer, 0x600, 1, user) == 1) {
  119. pcopy(buffer + 0x6d, temp);
  120. if (strcasecmp(temp, username.c_str()) == 0) {
  121. pcopy(buffer + 0x8c, temp);
  122. // Assuming the user record isn't screwed up with len=0.
  123. fullname.assign(temp);
  124. fclose(user);
  125. return 1;
  126. // break;
  127. }
  128. /*
  129. printf("Alias: %s\n", temp);
  130. pcopy(buffer + 0x8c, temp );
  131. printf("Full Name: %s\n", temp );
  132. */
  133. }
  134. fclose(user);
  135. return 0;
  136. }
  137. std::ifstream logfile;
  138. std::streampos log_pos;
  139. void open_mystic_log(void) {
  140. std::string mystic_logfile;
  141. {
  142. std::ostringstream buffer;
  143. buffer << "logs/node" << node << ".log";
  144. mystic_logfile = buffer.str();
  145. };
  146. logfile.open(mystic_logfile, std::ios_base::in | std::ios_base::ate);
  147. // Ok, we're at the end of the file. Or should be.
  148. if (logfile.is_open()) {
  149. ZF_LOGD("Log %s open", (const char *)mystic_logfile.c_str());
  150. log_pos = logfile.tellg();
  151. } else {
  152. ZF_LOGE("Failed to open: %s", (const char *)mystic_logfile.c_str());
  153. }
  154. }
  155. void scan_mystic_log(void) {
  156. if (logfile.is_open()) {
  157. int again = 0;
  158. do {
  159. std::string line = find_new_text(logfile, log_pos);
  160. if (line.empty())
  161. return;
  162. again = 1;
  163. ZF_LOGD("mystic log: %s", (const char *)line.c_str());
  164. // Ok, we have a line, look for interesting details
  165. if (line.find("New user application") != std::string::npos) {
  166. ZF_LOGE("New User");
  167. }
  168. size_t pos;
  169. pos = line.find("Created Account: ");
  170. if (pos != std::string::npos) {
  171. pos += 18 - 1;
  172. // Ok, find the end '#'
  173. size_t len = line.find('#', pos);
  174. if (len != std::string::npos) {
  175. username = line.substr(pos, len - pos - 1);
  176. ZF_LOGE("New User: %s", (const char *)username.c_str());
  177. // once we know this works -- lookup user's record
  178. locate_user(username.c_str());
  179. ZF_LOGE("Username: [%s] A.K.A. [%s]", (const char *)username.c_str(),
  180. (const char *)fullname.c_str());
  181. }
  182. }
  183. pos = line.find(" logged in");
  184. if (pos != std::string::npos) {
  185. --pos;
  186. size_t len = 20;
  187. // position 20 is right after date/time
  188. username = line.substr(20, pos + 1 - len);
  189. ZF_LOGE("User: %s", (const char *)username.c_str());
  190. // verify this works, lookup
  191. locate_user(username.c_str());
  192. ZF_LOGE("Username: [%s] A.K.A. [%s]", (const char *)username.c_str(),
  193. (const char *)fullname.c_str());
  194. }
  195. } while (again);
  196. }
  197. }
  198. /*
  199. This is done. :D My buffering system works with stack'em.
  200. TO FIX: Stop using c strings, must use char * buffer + int length.
  201. MAY CONTAIN NULL VALUES.
  202. Rework some things here.
  203. Here's the "plan":
  204. if buffer is EMPTY:
  205. time_idle = 1;
  206. // setup for "random timeout value mess"
  207. // we're in luck! The last parameter is time interval/timeout. :D
  208. timeout.tv_sec = 10; // randrange(10-25)
  209. timeout.tv_usec = 0;
  210. NOT EMPTY:
  211. // we're in luck! The last parameter is time interval/timeout. :D
  212. timeout.tv_sec = 0;
  213. timeout.tv_usec = 10; // Wild Guess Here? Maybe higher, maybe
  214. lower? time_idle = 0;
  215. ON READ:
  216. read/append to current buffer.
  217. We can't use nulls -- what if they are using ZModem, there's nulls in
  218. the file! Look for trailing / the very last "\r\n".
  219. (I could mangle/chunk it line by line. But I'm not sure I'd need to do
  220. that.)
  221. Optional "mangle" buffer up to that very point -- and send up to that
  222. point.
  223. Option #2: Maybe we send everything if program has been running for
  224. under 20 seconds. This would allow the ANSI detect to not get screwed up by
  225. this new idea.
  226. ON TIMEOUT:
  227. if time_idle:
  228. Activate funny harry timeout events.
  229. else:
  230. Ok, we *STILL* haven't received any more characters into the buffer --
  231. even after waiting. (Maybe we haven't waited long enough?)
  232. send the pending information in the buffer and clear it out.
  233. Maybe this is a prompt, and there won't be a \r\n.
  234. This allows for cleaner process of "lines" of buffer. We shouldn't break
  235. in the midDLE OF A WORD. Downside is that we sit on buffer contents a
  236. little while / some amount of time -- which will add some lag to prompts
  237. showing up.
  238. (LAG? Are you kidding?)
  239. ZModem:
  240. start: "rz^M**"...
  241. 05-12 18:12:15.916 >> rz^M**^XB00000000000000^M<8A>^Q
  242. 05-12 18:12:15.928 << **\x18B0100000023be50\r\n\x11
  243. 05-12 18:12:15.928 >> *^XC^D
  244. 05-12 18:12:15.939 << **\x18B0900000000a87c\r\n\x11
  245. 05-12 18:12:15.940 >> *^XC
  246. # Start of PK zipfile.
  247. 05-12 18:12:15.941 >> PK^C^D^T
  248. end:
  249. 05-12 18:26:38.700 << **\x18B0100000023be50\r\n\x11
  250. 05-12 18:26:38.700 >> **^XB0823a77600344c^M<8A>
  251. 05-12 18:26:38.711 << **\x18B0800000000022d\r\n
  252. 05-12 18:26:38.712 >> OO^MESC[0m
  253. */
  254. // TODO: Get everything above this -- into another file.
  255. int main(int argc, char *argv[]) {
  256. int master;
  257. pid_t pid;
  258. node = -1;
  259. tzset();
  260. init_harry();
  261. srandom(time(NULL));
  262. // ./mystic -TID7 -IP192.168.0.1 -HOSTUnknown -ML1 -SL0 -ST2 -CUnknown
  263. // -Ubugz -PUWISHPASSWORD
  264. // ./mystic -TID7 -IP192.168.0.1 -HOSTUnknown -ML0 -SL0 -ST0 -CUnknown
  265. // ./mystic -TID7 -IP192.168.0.1 -HOSTUnknown -ML1 -SL0 -ST2 -CUnknown
  266. // -Ubugz -PUWISH
  267. // ./mystic -TID7 -IP192.168.0.1 -HOSTUnknown -ML0 -SL0 -ST0 -CUnknown
  268. // ./mystic -TID7 -IP192.168.0.1 -HOSTUnknown -ML0 -SL0 -ST0 -CUnknown
  269. // ./mystic -TID9 -IP192.168.0.1 -HOSTUnknown -ML0 -SL1 -ST0 -CUnknown
  270. // ./mystic -TID7 -IP192.168.0.1 -HOSTUnknown -ML1 -SL0 -ST2 -CUnknown
  271. // -Ubugz -PDUMBWAYTODOTHIS
  272. // ./mystic -TID9 -IP192.168.0.1 -HOSTUnknown -ML1 -SL1 -ST2 -CUnknown
  273. // -Ubugz -PIDONTUSEPASCAL
  274. // SSH: -ML1 -ST2
  275. // Telnet: -ML0 -ST0
  276. // Locate username (if given) in the command line
  277. // -U<username>
  278. for (int x = 0; x < argc; x++) {
  279. /*
  280. // This doesn't work: You can give username + wrong password.
  281. // You will be identified as the wrong user at the login screen!
  282. if (strncmp("-U", argv[x], 2) == 0) {
  283. username.assign(argv[x] + 2);
  284. }
  285. */
  286. /* Changed in latest version
  287. if (strncmp("-SL", argv[x], 3) == 0) {
  288. node = atoi(argv[x] + 3) + 1;
  289. }
  290. */
  291. // -TID8, -TID10, -TID12 for node 1, 2, 3
  292. if (strncmp("-TID", argv[x], 4) == 0) {
  293. node = (atoi(argv[x] + 4) - 6) / 2;
  294. }
  295. }
  296. if (node == -1) {
  297. // likely this is someone trying to run something
  298. char *args[20]; // max 20 args
  299. int x;
  300. char new_exec[] = TARGET;
  301. // build new args list
  302. args[0] = new_exec;
  303. for (x = 1; x < argc; x++) {
  304. args[x] = argv[x];
  305. };
  306. // null term the list
  307. args[x] = NULL;
  308. // run Mystic, run!
  309. execvp(TARGET, args);
  310. return 2;
  311. }
  312. std::string logfile;
  313. {
  314. std::ostringstream buffer;
  315. time_t now = time(NULL);
  316. struct tm *tmp;
  317. tmp = localtime(&now);
  318. // tmp->tm_mon
  319. buffer << "horrible-harry-" << tmp->tm_year + 1900 << "-"
  320. << std::setfill('0') << std::setw(2) << tmp->tm_mon + 1 << "-"
  321. << std::setfill('0') << std::setw(2) << tmp->tm_mday << "-" << node
  322. << ".log";
  323. logfile = buffer.str();
  324. };
  325. if (!file_output_open((const char *)logfile.c_str()))
  326. return 2;
  327. ZF_LOGE("Horrible Harry %s", version.c_str());
  328. for (auto cit = CONFIG.begin(); cit != CONFIG.end(); ++cit) {
  329. ZF_LOGD("Config {%s}:{%s}", (const char *)cit->first.c_str(),
  330. (const char *)cit->second.c_str());
  331. }
  332. ZF_LOGI("Node: %d", node);
  333. if (!username.empty()) {
  334. locate_user(username.c_str());
  335. ZF_LOGD("Username: [%s] A.K.A. [%s]", (const char *)username.c_str(),
  336. (const char *)fullname.c_str());
  337. }
  338. open_mystic_log();
  339. pid = forkpty(&master, NULL, NULL, NULL);
  340. // impossible to fork
  341. if (pid < 0) {
  342. return 1;
  343. }
  344. // child
  345. else if (pid == 0) {
  346. char *args[20]; // max 20 args
  347. int x;
  348. char new_exec[] = TARGET;
  349. // build new args list
  350. args[0] = new_exec;
  351. for (x = 1; x < argc; x++) {
  352. args[x] = argv[x];
  353. };
  354. // null term the list
  355. args[x] = NULL;
  356. // run Mystic, run!
  357. execvp(TARGET, args);
  358. }
  359. // parent
  360. else {
  361. struct termios tios, orig1;
  362. struct timeval timeout;
  363. time_t last_logscan = time(NULL);
  364. ZF_LOGD("starting");
  365. tcgetattr(master, &tios);
  366. tios.c_lflag &= ~(ECHO | ECHONL | ICANON);
  367. /*
  368. tios.c_iflag &= ~(ICRNL | IXON | BRKINT);
  369. tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
  370. tios.c_oflag &= ~(OPOST);
  371. */
  372. tcsetattr(master, TCSAFLUSH, &tios);
  373. tcgetattr(1, &orig1);
  374. tios = orig1;
  375. tios.c_iflag &= ~(ICRNL | IXON | BRKINT);
  376. tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
  377. tios.c_oflag &= ~(OPOST);
  378. // https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
  379. tcsetattr(1, TCSAFLUSH, &tios);
  380. /*
  381. This doesn't need to be static -- because it is part of
  382. main. Once main ends, we're done.
  383. */
  384. std::string buffer;
  385. buffer.reserve(BSIZE * 2);
  386. std::string play;
  387. play.reserve(4096);
  388. int zmodem = 0;
  389. // int size = 0; // use buffer.size() instead
  390. for (;;) {
  391. int time_idle;
  392. // define estruturas para o select, que serve para verificar qual
  393. // se tornou "pronto pra uso"
  394. fd_set read_fd;
  395. fd_set write_fd;
  396. fd_set except_fd;
  397. // inicializa as estruturas
  398. FD_ZERO(&read_fd);
  399. FD_ZERO(&write_fd);
  400. FD_ZERO(&except_fd);
  401. // atribui o descritor master, obtido pelo forkpty, ao read_fd
  402. FD_SET(master, &read_fd);
  403. // atribui o stdin ao read_fd
  404. FD_SET(STDIN_FILENO, &read_fd);
  405. // o descritor tem que ser unico para o programa, a documentacao
  406. // recomenda um calculo entre os descritores sendo usados + 1
  407. /*
  408. TODO: Figure out how this would work.
  409. I'm thinking something like timeouts 30-50 seconds?
  410. And as we get closer, 15-25 seconds.
  411. if zmodem, buffer will always be empty -- we won't hold anything.
  412. */
  413. if (buffer.size() == 0) {
  414. // buffer is empty
  415. if (zmodem) {
  416. timeout.tv_sec = 5;
  417. } else {
  418. timeout.tv_sec = randrange(10, 20);
  419. };
  420. timeout.tv_usec = 0;
  421. time_idle = 1;
  422. } else {
  423. // buffer is not empty
  424. timeout.tv_sec = 0;
  425. timeout.tv_usec = 1;
  426. time_idle = 0;
  427. }
  428. if (last_logscan < time(NULL)) {
  429. scan_mystic_log();
  430. if (username.empty())
  431. last_logscan = time(NULL) + 2;
  432. else
  433. last_logscan = time(NULL) + 10;
  434. }
  435. if (select(master + 1, &read_fd, &write_fd, &except_fd, &timeout) == 0) {
  436. ZF_LOGI("TIMEOUT");
  437. // This means timeout!
  438. if (time_idle) {
  439. if (harry_level() && !zmodem)
  440. harry_idle_event(STDOUT_FILENO);
  441. } else {
  442. ZF_LOGV("TIMEOUT buffer: %s", logrepr(buffer.c_str()));
  443. /*
  444. ZF_LOGI_MEM(buffer.data(), buffer.size(), "TIMEOUT buffer size=%lu",
  445. buffer.size());
  446. */
  447. play.assign(buffer);
  448. if (harry_level())
  449. mangle(STDOUT_FILENO, play);
  450. else {
  451. write(STDOUT_FILENO, play.data(), play.size());
  452. console_receive(&console, play);
  453. }
  454. /*
  455. ZF_LOGI("console_receive");
  456. console_receive(&console, buffer);
  457. ZF_LOGI("write buffer");
  458. write(STDOUT_FILENO, buffer.data(), buffer.size());
  459. */
  460. ZF_LOGI("buffer clear");
  461. buffer.clear();
  462. // size = 0;
  463. // buffer is empty now
  464. }
  465. }
  466. // read_fd esta atribuido com read_fd?
  467. if (FD_ISSET(master, &read_fd)) {
  468. // leia o que bc esta mandando
  469. // ZF_LOGD("read (%d) %d bytes", size, BSIZE - size);
  470. char read_buffer[BSIZE + 1];
  471. int total;
  472. // We may adjust this later on (adjusting read length).
  473. if ((total = read(master, read_buffer, BSIZE)) != -1) {
  474. // Ok, we've read more into the buffer.
  475. ZF_LOGV("Read %d bytes", total);
  476. buffer.append(read_buffer, total);
  477. if (zmodem) {
  478. // Ok, we're zmodem mode -- is it time to exit?
  479. size_t zend = buffer.find("\x1b[0m");
  480. if (zend != std::string::npos)
  481. zmodem = 0;
  482. zend = buffer.find("\x1b[1;1H");
  483. if (zend != std::string::npos)
  484. zmodem = 0;
  485. if (!zmodem)
  486. ZF_LOGD("Zmodem end");
  487. } else {
  488. // Should we be in zmodem mode?
  489. size_t zstart = buffer.find("**\x18"
  490. "B0");
  491. if (zstart != std::string::npos) {
  492. zmodem = 1;
  493. ZF_LOGD("Zmodem start");
  494. }
  495. }
  496. if (zmodem) {
  497. // ZF_LOGI("Buffer %lu bytes, zmodem...", buffer.size());
  498. write(STDOUT_FILENO, buffer.data(), buffer.size());
  499. // console_receive(&console, buffer);
  500. buffer.clear();
  501. } else {
  502. // ZF_LOGV_MEM(buffer + size, total, "Read %d bytes:", total);
  503. // size += total;
  504. // ZF_LOGV_MEM(buffer, size, "Buffer now:");
  505. size_t pos = buffer.rfind("\r\n");
  506. // rstrnstr(buffer, size, "\r\n");
  507. // >= 0) {
  508. if (pos != std::string::npos) {
  509. // found something!
  510. pos += 2;
  511. // play = buffer.substr() wipes out play's reserve.
  512. // play = buffer.substr(0, pos);
  513. play.assign(buffer, 0, pos);
  514. ZF_LOGI("play %lu size, %lu cap", play.size(), play.capacity());
  515. // play.copy(buffer.data(), pos);
  516. //) = buffer.substr(0, pos);
  517. buffer.erase(0, pos);
  518. mangle(STDOUT_FILENO, play);
  519. // ZF_LOGD_MEM(buffer, pos, "mangle buffer %d bytes:", pos);
  520. // mangle(STDOUT_FILENO, buffer, pos);
  521. // memmove(buffer, buffer + pos, size - pos);
  522. // size -= pos;
  523. // } else {
  524. // ZF_LOGV("position of /r/n not found.");
  525. }
  526. // Ok, we failed to find CR+NL. What's the buffer size at?
  527. if (buffer.size() > BSIZE) {
  528. // Ok, there's something going on, and it doesn't look good
  529. // unsure if I want to feed this into the console
  530. // my guess at this point would be zmodem xfer
  531. ZF_LOGI("Buffer %lu bytes, write only...", buffer.size());
  532. write(STDOUT_FILENO, buffer.data(), buffer.size());
  533. console_receive(&console, buffer);
  534. buffer.clear();
  535. }
  536. }
  537. } else
  538. break;
  539. }
  540. // read_fd esta atribuido com a entrada padrao?
  541. if (FD_ISSET(STDIN_FILENO, &read_fd)) {
  542. // leia a entrada padrao
  543. char input[BSIZE];
  544. int r = read(STDIN_FILENO, &input, BSIZE);
  545. input[r] = 0;
  546. // e escreva no bc
  547. if (!zmodem) {
  548. if (r > 50) {
  549. ZF_LOGV("<< %d bytes", r);
  550. } else {
  551. ZF_LOGV("<< %s", repr(input));
  552. }
  553. }
  554. write(master, &input, r);
  555. // This is INPUT from the USER
  556. // ZF_LOGI_MEM( input, strlen(input), "<< ");
  557. }
  558. }
  559. // Restore terminal
  560. tcsetattr(1, TCSAFLUSH, &orig1);
  561. ZF_LOGD("exit");
  562. }
  563. return 0;
  564. }