session.cpp 18 KB

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