session.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. #include <boost/bind.hpp>
  2. #include <boost/format.hpp>
  3. #include <functional>
  4. #include <iostream>
  5. // #include <boost/log/core.hpp>
  6. // #include <boost/log/trivial.hpp>
  7. #include <regex>
  8. #include <string>
  9. #include "config.h"
  10. #include "galaxy.h"
  11. #include "logging.h"
  12. #include "session.h"
  13. #include "utils.h"
  14. // #include <boost/log/attributes/named_scope.hpp>
  15. Session::Session(boost::asio::ip::tcp::socket socket,
  16. boost::asio::io_service &io_service, std::string hostname,
  17. std::string port, bool server_telnet_)
  18. : server_telnet{server_telnet_},
  19. socket_(std::move(socket)),
  20. io_service_{io_service},
  21. resolver_{io_service},
  22. server_{io_service},
  23. prompt_timer_{io_service},
  24. keep_alive_{io_service},
  25. host{hostname},
  26. port{port} {
  27. BUGZ_LOG(info) << "Session::Session()";
  28. // server_sent = 0;
  29. time_ms = 50;
  30. if (CONFIG.contains("prompt_timeout")) time_ms = CONFIG["prompt_timeout"];
  31. keepalive_secs = 45;
  32. if (CONFIG.contains("keepalive")) keepalive_secs = CONFIG["keepalive"];
  33. // Initialize the director
  34. director.to_server = [this](const std::string &message, const std::string &source) {
  35. this->to_server(message, source);
  36. // boost::bind(&Session::to_server, this, _1, _2);
  37. };
  38. director.to_client = boost::bind(&Session::to_client, this, _1, true);
  39. director.post = boost::bind(&Session::post, this, _1);
  40. // too soon!
  41. // director.username = rlogin_name;
  42. // replace emit_ with below: if (director.server_line)
  43. // director.server_line(s);
  44. /*
  45. emit_server_line = [this](const std::string &s) {
  46. if (director.server_line) {
  47. director.server_line(s);
  48. }
  49. };
  50. emit_server_prompt = [this](const std::string &s) {
  51. if (director.server_prompt) {
  52. director.server_prompt(s);
  53. }
  54. };
  55. emit_client_input ... => director.client_input
  56. */
  57. }
  58. void Session::start(void) {
  59. BUGZ_LOG(info) << "Session::start()";
  60. // auto self(shared_from_this());
  61. client_read();
  62. }
  63. Session::~Session() { BUGZ_LOG(info) << "~Session"; }
  64. /**
  65. * Returns the current server prompt.
  66. *
  67. * NOTE: This is the raw string from the server, so it can contain
  68. * color codes. Make sure you clean it before trying to test it for
  69. * any text.
  70. *
  71. * @return const std::string&
  72. */
  73. const std::string &Session::get_prompt(void) { return server_prompt; }
  74. void Session::set_prompt(const std::string &prompt) { server_prompt = prompt; }
  75. void Session::post(notifyFunc nf) {
  76. if (nf) {
  77. BUGZ_LOG(info) << "Session::post()";
  78. io_service_.post(nf);
  79. } else {
  80. BUGZ_LOG(error) << "Session::post( nullptr )";
  81. }
  82. }
  83. void Session::parse_auth(void) {
  84. // how many nulls should I be seeing?
  85. // \0user\0pass\0terminal/SPEED\0
  86. // If I don't have a proper rlogin value here, it isn't going
  87. // to work when I try to connect to the rlogin server.
  88. if (rlogin_auth.size() > 10)
  89. rlogin_name = rlogin_auth.c_str() + 1;
  90. else
  91. rlogin_name = "?";
  92. director.username = rlogin_name;
  93. }
  94. void Session::on_connect(const boost::system::error_code error) {
  95. // We've connected to the server! WOOT WOOT!
  96. // BOOST_LOG_NAMED_SCOPE("Session");
  97. if (!error) {
  98. BUGZ_LOG(info) << "Connected to " << host;
  99. to_client("Connected...\n\r");
  100. connected = true;
  101. if (!server_telnet) {
  102. if (rlogin_auth[0] != 0) {
  103. // Ok, the rlogin information was junk --
  104. to_client("Let me make up some fake rlogin data for you...\n\r");
  105. char temp[] = "\0test\0test\0terminal/9600\0";
  106. std::string tmp(temp, sizeof(temp));
  107. to_server(tmp);
  108. } else {
  109. to_server(rlogin_auth);
  110. }
  111. } else {
  112. // server_telnet. Step 1: negotiate a telnet connection with TWGS.
  113. director.show_client = false;
  114. }
  115. server_read();
  116. } else {
  117. std::string output =
  118. str(boost::format("Failed to connect: %1%:%2%\n\r") % host % port);
  119. to_client(output);
  120. BUGZ_LOG(error) << "Failed to connect to " << host << ":" << port;
  121. BUGZ_LOG(warning) << "socket.shutdown()";
  122. socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
  123. }
  124. }
  125. /**
  126. * Called with the current line received from the server.
  127. *
  128. * This will do server parsing. Sector/Ports/Connecting Sectors.
  129. * Port status/inventory/%.
  130. *
  131. * See \ref split_lines()
  132. * @param line
  133. */
  134. void Session::on_server_line(const std::string &line,
  135. const std::string &raw_line) {
  136. director.server_line(line, raw_line);
  137. }
  138. /**
  139. * Split server input into lines.
  140. *
  141. * @param line
  142. */
  143. void Session::split_lines(std::string line) {
  144. // Does this have \n\r still on it? I don't want them.
  145. // cleanup backspaces
  146. size_t pos;
  147. while ((pos = line.find('\b')) != std::string::npos) {
  148. // backspace? OK! (unless)
  149. if (pos == 0) {
  150. // first character, so there's nothing "extra" to erase.
  151. line = line.erase(pos, 1);
  152. } else
  153. line = line.erase(pos - 1, 2);
  154. }
  155. std::string temp = clean_string(line);
  156. on_server_line(temp, line);
  157. }
  158. /*
  159. Call this with whatever we just received.
  160. That will allow me to send "just whatever I got"
  161. this time around, rather then trying to figure out
  162. what was just added to server_prompt.
  163. What about \r, \b ? Should that "reset" the server_prompt?
  164. \r should not, because it is followed by \n (eventually)
  165. and that completes my line.
  166. */
  167. void Session::process_lines(std::string &received) {
  168. // break server_prompt into lines and send/process one by one.
  169. size_t pos, rpos;
  170. server_prompt.append(received);
  171. // I also need to break on r"\x1b[\[0-9;]*JK", treat these like \n
  172. while ((pos = server_prompt.find('\n', 0)) != std::string::npos) {
  173. std::string line;
  174. std::smatch m = ansi_newline(server_prompt);
  175. if (!m.empty()) {
  176. // We found one.
  177. size_t mpos = m.prefix().length();
  178. // int mlen = m[0].length();
  179. if (mpos < pos) {
  180. // Ok, the ANSI newline is before the \n
  181. // perform this process with the received line
  182. std::smatch rm = ansi_newline(received);
  183. if (!rm.empty()) {
  184. size_t rpos = rm.prefix().length();
  185. int rlen = rm[0].length();
  186. if (director.show_client) {
  187. line = received.substr(0, rpos + rlen);
  188. to_client(line, false);
  189. }
  190. received = rm.suffix();
  191. }
  192. // perform this on the server_prompt line
  193. line = m.prefix();
  194. split_lines(line);
  195. server_prompt = m.suffix();
  196. // redo this loop -- there's still a \n in there
  197. continue;
  198. }
  199. }
  200. // process "line" in received
  201. rpos = received.find('\n', 0);
  202. // get line to send to the client
  203. if (director.show_client) {
  204. // that is, if we're sending to the client!
  205. line = received.substr(0, rpos + 1);
  206. /*
  207. std::string clean = clean_string(line);
  208. BUGZ_LOG(error) << "rpos/show_client:" << clean;
  209. */
  210. if (server_telnet) {
  211. if (line.find('\xff') != std::string::npos) {
  212. remove_telnet_commands(line);
  213. }
  214. }
  215. to_client(line, false);
  216. }
  217. received = received.substr(rpos + 1);
  218. // process "line" in server_prompt
  219. line = server_prompt.substr(0, pos + 1);
  220. server_prompt = server_prompt.substr(pos + 1);
  221. // Remove \n for dispatching
  222. std::string part = line.substr(0, pos);
  223. /*
  224. if (server_sent != 0) {
  225. line = line.substr(server_sent);
  226. server_sent = 0;
  227. };
  228. */
  229. // display on?
  230. // to_client(line);
  231. // How should I handle \r in lines? For now, remove it
  232. // but LOG that we did.
  233. replace(part, "\r", "");
  234. /*
  235. if (replace(part, "\r", "")) {
  236. BUGZ_LOG(warning) << "\\r removed from line";
  237. }
  238. */
  239. split_lines(part);
  240. }
  241. // Ok, we have sent all of the \n lines.
  242. if (!received.empty())
  243. if (director.show_client) {
  244. to_client(received, false);
  245. // std::string clean = clean_string(received);
  246. // BUGZ_LOG(error) << "show_client/leftovers:" << clean;
  247. }
  248. // This is eating the entire string. String is partial line
  249. // portcim line, ending with '\r', this eats the line.
  250. /*
  251. // check the server prompt here:
  252. if ((pos = server_prompt.rfind('\r')) != std::string::npos) {
  253. // server_prompt contains \r, remove it.
  254. server_prompt = server_prompt.substr(pos + 1);
  255. }
  256. */
  257. while ((pos = server_prompt.find('\b')) != std::string::npos) {
  258. // backspace? OK! (unless)
  259. if (pos == 0) {
  260. // first character, so there's nothing "extra" to erase.
  261. server_prompt = server_prompt.erase(pos, 1);
  262. } else
  263. server_prompt = server_prompt.erase(pos - 1, 2);
  264. }
  265. if (!server_prompt.empty()) {
  266. // We have something remaining -- start the timer!
  267. set_prompt_timer();
  268. }
  269. }
  270. void Session::set_prompt_timer(void) {
  271. prompt_timer_.expires_after(std::chrono::milliseconds(time_ms));
  272. prompt_timer_.async_wait(boost::bind(&Session::on_prompt_timeout, this,
  273. boost::asio::placeholders::error));
  274. }
  275. void Session::reset_prompt_timer(void) { prompt_timer_.cancel(); }
  276. // probably no longer needed --
  277. void Session::on_server_prompt(const std::string &prompt,
  278. const std::string &raw_prompt) {
  279. director.server_prompt(prompt, raw_prompt);
  280. if (server_telnet) {
  281. std::string ayt = std::string((const char *)"\x00\xff\xfd\xf6", 4);
  282. std::string ayt_resp = std::string((const char *)"\xff\xfb\x00", 3);
  283. std::string ayt2 = std::string((const char *)"\xff\xfb\x00", 3);
  284. std::string ayt2_resp = std::string((const char *)"\xff\xfd\x00", 3);
  285. /*
  286. for( const char & c : prompt ) {
  287. BUGZ_LOG(fatal) << "IS? " << (unsigned int)c << " " << std::hex <<
  288. (unsigned int)c;
  289. }
  290. for( const char & c : ayt ) {
  291. BUGZ_LOG(fatal) << "AYT? " << (unsigned int)c << " " << std::hex <<
  292. (unsigned int)c;
  293. }
  294. */
  295. if (prompt == ayt) {
  296. to_server(ayt_resp);
  297. BUGZ_LOG(warning) << "AYT?";
  298. server_prompt.clear();
  299. }
  300. if (prompt == ayt2) {
  301. to_server(ayt2_resp);
  302. BUGZ_LOG(warning) << "AYT2??";
  303. server_prompt.clear();
  304. // let the user see what is happening... We're done negotiating (I think)
  305. director.show_client = true;
  306. }
  307. }
  308. }
  309. void Session::on_prompt_timeout(const boost::system::error_code error) {
  310. if (error != boost::asio::error::operation_aborted) {
  311. // Ok, VALID timeout
  312. if (!server_prompt.empty()) {
  313. // Here's what is happening:
  314. // SP: [ESC[2JESC[H]
  315. // which after clean_string is empty.
  316. std::string clean = server_prompt; // clean_string(server_prompt);
  317. ansi_clean(clean);
  318. if (!clean.empty()) {
  319. on_server_prompt(clean, server_prompt);
  320. }
  321. // BUGZ_LOG(trace) << "SP: [" << server_prompt << "]";
  322. }
  323. }
  324. }
  325. void Session::server_read(void) {
  326. auto self(shared_from_this());
  327. boost::asio::async_read(
  328. server_, boost::asio::buffer(server_buffer, sizeof(server_buffer) - 1),
  329. boost::asio::transfer_at_least(1),
  330. [this, self](boost::system::error_code ec, std::size_t length) {
  331. if (!ec) {
  332. // server_buffer[length] = 0;
  333. // server_prompt.append(server_buffer, length);
  334. std::string received(server_buffer, length);
  335. process_lines(received);
  336. /*
  337. I don't believe I need to consume this,
  338. I'm not async_reading from a stream.
  339. */
  340. /*
  341. if (length) {
  342. // std::cout << length << std::endl;
  343. std::cout << "S: " << server_buffer << std::endl;
  344. do_write(server_buffer);
  345. }
  346. */
  347. server_read();
  348. } else {
  349. if (!server_prompt.empty()) {
  350. // This re-displays the very last message received (before closing)
  351. // Unfortunately, it repeats the last prompt.
  352. // (I'm looking to display the message from the server // like is full.)
  353. // BUGZ_LOG(trace) << "2C: [" << server_prompt << "]";
  354. // to_client(server_prompt);
  355. // server_prompt.clear();
  356. }
  357. BUGZ_LOG(warning) << "S: read_failed: socket.shutdown()";
  358. connected = false;
  359. socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
  360. }
  361. });
  362. }
  363. void Session::on_resolve(
  364. const boost::system::error_code error,
  365. const boost::asio::ip::tcp::resolver::results_type results) {
  366. //
  367. auto self(shared_from_this());
  368. if (!error) {
  369. // Take the first endpoint.
  370. boost::asio::ip::tcp::endpoint const &endpoint = *results;
  371. server_.async_connect(endpoint,
  372. boost::bind(&Session::on_connect, this,
  373. boost::asio::placeholders::error));
  374. } else {
  375. // TO DO:
  376. // BOOST_LOG_NAMED_SCOPE("Session");
  377. BUGZ_LOG(error) << "Unable to resolve: " << host;
  378. std::string output =
  379. str(boost::format("Unable to resolve: %1%\n\r") % host);
  380. to_client(output);
  381. BUGZ_LOG(warning) << "socket.shutdown()";
  382. socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
  383. }
  384. }
  385. void Session::client_input(const std::string &input) {
  386. std::string temp = repr(input);
  387. director.client_input(input);
  388. }
  389. void Session::client_read(void) {
  390. auto self(shared_from_this());
  391. boost::asio::async_read( // why can't I async_read_some here?
  392. socket_, boost::asio::buffer(read_buffer, sizeof(read_buffer) - 1),
  393. boost::asio::transfer_at_least(1),
  394. [this, self](boost::system::error_code ec, std::size_t length) {
  395. if (!ec) {
  396. // read_buffer[length] = 0;
  397. if (rlogin_auth.empty()) {
  398. // first read should be rlogin information
  399. rlogin_auth.assign(read_buffer, length);
  400. // parse authentication information
  401. parse_auth();
  402. to_client(std::string(1, 0), false);
  403. to_client("Welcome, ");
  404. to_client(rlogin_name);
  405. to_client("\n\r");
  406. // Activate the connection to the server
  407. /* // this fails, and I'm not sure why. I've used code like this
  408. before. resolver_.async_resolve( host, port, std::bind(
  409. &Session::on_resolve, this, _1, _2)); */
  410. // This example shows using boost::bind, which WORKS.
  411. // https://stackoverflow.com/questions/6025471/bind-resolve-handler-to-resolver-async-resolve-using-boostasio
  412. resolver_.async_resolve(
  413. host, port,
  414. boost::bind(&Session::on_resolve, this,
  415. boost::asio::placeholders::error,
  416. boost::asio::placeholders::iterator));
  417. } else if (length) {
  418. // Proxy Active?
  419. // BOOST_LOG_NAMED_SCOPE("Session");
  420. std::string line(read_buffer, length);
  421. client_input(line);
  422. // do_write(output);
  423. }
  424. client_read();
  425. } else {
  426. BUGZ_LOG(warning) << "C: read_failed " << ec;
  427. if (connected) {
  428. BUGZ_LOG(warning) << "Server.shutdown()";
  429. server_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
  430. }
  431. }
  432. });
  433. }
  434. void Session::to_client(const std::string &message, bool log) {
  435. auto self(shared_from_this());
  436. // output the cleaned string (so I can see what we're sending in the
  437. // logs)
  438. /*
  439. if (log) {
  440. std::string clean = clean_string(message);
  441. BUGZ_LOG(trace) << "2C: " << clean;
  442. }
  443. */
  444. boost::asio::async_write(
  445. socket_, boost::asio::buffer(message),
  446. [this, self](boost::system::error_code ec, std::size_t /*length*/) {
  447. if (!ec) {
  448. } else {
  449. BUGZ_LOG(warning)
  450. << "2C: write failed? closed? Server.shutdown()" << ec;
  451. if (connected) {
  452. BUGZ_LOG(warning) << "Server.shutdown()";
  453. server_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
  454. }
  455. }
  456. });
  457. }
  458. void Session::to_server(const std::string &message) {
  459. static std::string empty;
  460. to_server(message, empty);
  461. }
  462. void Session::to_server(const std::string &message, const std::string &source) {
  463. auto self(shared_from_this());
  464. std::string work = repr(message);
  465. if (source.empty())
  466. BUGZ_LOG(trace) << "2S: " << work;
  467. else
  468. BUGZ_LOG(trace) << source << " 2S: " << work;
  469. boost::asio::async_write(
  470. server_, boost::asio::buffer(message),
  471. [this, self](boost::system::error_code ec, std::size_t /*length*/) {
  472. if (!ec) {
  473. } else {
  474. BUGZ_LOG(warning)
  475. << "S: write failed? closed? socket.shutdown() " << ec;
  476. // we're no longer connected.
  477. connected = false;
  478. socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
  479. }
  480. });
  481. if (director.active) {
  482. start_keepin_alive();
  483. }
  484. }
  485. void Session::start_keepin_alive(void) {
  486. // keep alive timer
  487. keep_alive_.expires_after(std::chrono::seconds(keepalive_secs));
  488. keep_alive_.async_wait(boost::bind(&Session::stayin_alive, this,
  489. boost::asio::placeholders::error));
  490. }
  491. void Session::stayin_alive(const boost::system::error_code error) {
  492. if (error != boost::asio::error::operation_aborted) {
  493. // stayin' alive, stayin' alive...
  494. if (director.active) {
  495. to_server(" ");
  496. BUGZ_LOG(warning) << "Session::stayin_alive()";
  497. }
  498. }
  499. }
  500. Server::Server(boost::asio::io_service &io_service,
  501. const boost::asio::ip::tcp::endpoint &endpoint,
  502. const std::string &host, const std::string &port,
  503. bool server_telnet_)
  504. : server_telnet{server_telnet_},
  505. io_service_{io_service},
  506. acceptor_{io_service_, endpoint},
  507. signal_{io_service, SIGUSR1, SIGTERM},
  508. host_{host},
  509. port_{port} {
  510. keep_accepting = true;
  511. BUGZ_LOG(info) << "Server::Server()";
  512. signal_.async_wait(boost::bind(&Server::on_signal, this,
  513. boost::asio::placeholders::error,
  514. boost::asio::placeholders::signal_number));
  515. do_accept();
  516. }
  517. void Server::on_signal(const boost::system::error_code &ec, int signal) {
  518. BUGZ_LOG(info) << "on_signal() :" << signal;
  519. keep_accepting = false;
  520. boost::system::error_code error;
  521. acceptor_.cancel(error);
  522. BUGZ_LOG(info) << "cancel: " << error;
  523. acceptor_.close(error);
  524. BUGZ_LOG(info) << "close: " << error;
  525. }
  526. Server::~Server() {
  527. CONFIG = json();
  528. BUGZ_LOG(info) << "Server::~Server()";
  529. }
  530. /**
  531. * setup async connect accept
  532. *
  533. * This creates a session for each connection. Using make_shared allows the
  534. * session to automatically clean up when it is no longer active/has anything
  535. * running in the reactor.
  536. */
  537. void Server::do_accept(void) {
  538. acceptor_.async_accept([this](boost::system::error_code ec,
  539. boost::asio::ip::tcp::socket socket) {
  540. if (!ec) {
  541. BUGZ_LOG(info) << "Server::do_accept()";
  542. std::make_shared<Session>(std::move(socket), io_service_, host_, port_,
  543. server_telnet)
  544. ->start();
  545. }
  546. if (keep_accepting) {
  547. BUGZ_LOG(info) << "do_accept()";
  548. do_accept();
  549. }
  550. });
  551. }