123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644 |
- #include <boost/bind.hpp>
- #include <boost/format.hpp>
- #include <functional>
- #include <iostream>
- // #include <boost/log/core.hpp>
- // #include <boost/log/trivial.hpp>
- #include <regex>
- #include <string>
- #include "config.h"
- #include "galaxy.h"
- #include "logging.h"
- #include "session.h"
- #include "utils.h"
- // #include <boost/log/attributes/named_scope.hpp>
- Session::Session(boost::asio::ip::tcp::socket socket,
- boost::asio::io_service &io_service, std::string hostname,
- std::string port, bool server_telnet_)
- : server_telnet{server_telnet_},
- socket_(std::move(socket)),
- io_service_{io_service},
- resolver_{io_service},
- server_{io_service},
- prompt_timer_{io_service},
- keep_alive_{io_service},
- host{hostname},
- port{port} {
- BUGZ_LOG(info) << "Session::Session()";
- // server_sent = 0;
- time_ms = 50;
- if (CONFIG.contains("prompt_timeout")) time_ms = CONFIG["prompt_timeout"];
- keepalive_secs = 45;
- if (CONFIG.contains("keepalive")) keepalive_secs = CONFIG["keepalive"];
- // Initialize the director
- director.to_server = [this](const std::string &message, const std::string &source) {
- this->to_server(message, source);
- // boost::bind(&Session::to_server, this, _1, _2);
- };
- director.to_client = boost::bind(&Session::to_client, this, _1, true);
- director.post = boost::bind(&Session::post, this, _1);
- // too soon!
- // director.username = rlogin_name;
- // replace emit_ with below: if (director.server_line)
- // director.server_line(s);
- /*
- emit_server_line = [this](const std::string &s) {
- if (director.server_line) {
- director.server_line(s);
- }
- };
- emit_server_prompt = [this](const std::string &s) {
- if (director.server_prompt) {
- director.server_prompt(s);
- }
- };
- emit_client_input ... => director.client_input
- */
- }
- void Session::start(void) {
- BUGZ_LOG(info) << "Session::start()";
- // auto self(shared_from_this());
- client_read();
- }
- Session::~Session() { BUGZ_LOG(info) << "~Session"; }
- /**
- * Returns the current server prompt.
- *
- * NOTE: This is the raw string from the server, so it can contain
- * color codes. Make sure you clean it before trying to test it for
- * any text.
- *
- * @return const std::string&
- */
- const std::string &Session::get_prompt(void) { return server_prompt; }
- void Session::set_prompt(const std::string &prompt) { server_prompt = prompt; }
- void Session::post(notifyFunc nf) {
- if (nf) {
- BUGZ_LOG(info) << "Session::post()";
- io_service_.post(nf);
- } else {
- BUGZ_LOG(error) << "Session::post( nullptr )";
- }
- }
- void Session::parse_auth(void) {
- // how many nulls should I be seeing?
- // \0user\0pass\0terminal/SPEED\0
- // If I don't have a proper rlogin value here, it isn't going
- // to work when I try to connect to the rlogin server.
- if (rlogin_auth.size() > 10)
- rlogin_name = rlogin_auth.c_str() + 1;
- else
- rlogin_name = "?";
- director.username = rlogin_name;
- }
- void Session::on_connect(const boost::system::error_code error) {
- // We've connected to the server! WOOT WOOT!
- // BOOST_LOG_NAMED_SCOPE("Session");
- if (!error) {
- BUGZ_LOG(info) << "Connected to " << host;
- to_client("Connected...\n\r");
- connected = true;
- if (!server_telnet) {
- if (rlogin_auth[0] != 0) {
- // Ok, the rlogin information was junk --
- to_client("Let me make up some fake rlogin data for you...\n\r");
- char temp[] = "\0test\0test\0terminal/9600\0";
- std::string tmp(temp, sizeof(temp));
- to_server(tmp);
- } else {
- to_server(rlogin_auth);
- }
- } else {
- // server_telnet. Step 1: negotiate a telnet connection with TWGS.
- director.show_client = false;
- }
- server_read();
- } else {
- std::string output =
- str(boost::format("Failed to connect: %1%:%2%\n\r") % host % port);
- to_client(output);
- BUGZ_LOG(error) << "Failed to connect to " << host << ":" << port;
- BUGZ_LOG(warning) << "socket.shutdown()";
- socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
- }
- }
- /**
- * Called with the current line received from the server.
- *
- * This will do server parsing. Sector/Ports/Connecting Sectors.
- * Port status/inventory/%.
- *
- * See \ref split_lines()
- * @param line
- */
- void Session::on_server_line(const std::string &line,
- const std::string &raw_line) {
- director.server_line(line, raw_line);
- }
- /**
- * Split server input into lines.
- *
- * @param line
- */
- void Session::split_lines(std::string line) {
- // Does this have \n\r still on it? I don't want them.
- // cleanup backspaces
- size_t pos;
- while ((pos = line.find('\b')) != std::string::npos) {
- // backspace? OK! (unless)
- if (pos == 0) {
- // first character, so there's nothing "extra" to erase.
- line = line.erase(pos, 1);
- } else
- line = line.erase(pos - 1, 2);
- }
- std::string temp = clean_string(line);
- on_server_line(temp, line);
- }
- /*
- Call this with whatever we just received.
- That will allow me to send "just whatever I got"
- this time around, rather then trying to figure out
- what was just added to server_prompt.
- What about \r, \b ? Should that "reset" the server_prompt?
- \r should not, because it is followed by \n (eventually)
- and that completes my line.
- */
- void Session::process_lines(std::string &received) {
- // break server_prompt into lines and send/process one by one.
- size_t pos, rpos;
- server_prompt.append(received);
- // I also need to break on r"\x1b[\[0-9;]*JK", treat these like \n
- while ((pos = server_prompt.find('\n', 0)) != std::string::npos) {
- std::string line;
- std::smatch m = ansi_newline(server_prompt);
- if (!m.empty()) {
- // We found one.
- size_t mpos = m.prefix().length();
- // int mlen = m[0].length();
- if (mpos < pos) {
- // Ok, the ANSI newline is before the \n
- // perform this process with the received line
- std::smatch rm = ansi_newline(received);
- if (!rm.empty()) {
- size_t rpos = rm.prefix().length();
- int rlen = rm[0].length();
- if (director.show_client) {
- line = received.substr(0, rpos + rlen);
- to_client(line, false);
- }
- received = rm.suffix();
- }
- // perform this on the server_prompt line
- line = m.prefix();
- split_lines(line);
- server_prompt = m.suffix();
- // redo this loop -- there's still a \n in there
- continue;
- }
- }
- // process "line" in received
- rpos = received.find('\n', 0);
- // get line to send to the client
- if (director.show_client) {
- // that is, if we're sending to the client!
- line = received.substr(0, rpos + 1);
- /*
- std::string clean = clean_string(line);
- BUGZ_LOG(error) << "rpos/show_client:" << clean;
- */
- if (server_telnet) {
- if (line.find('\xff') != std::string::npos) {
- remove_telnet_commands(line);
- }
- }
- to_client(line, false);
- }
- received = received.substr(rpos + 1);
- // process "line" in server_prompt
- line = server_prompt.substr(0, pos + 1);
- server_prompt = server_prompt.substr(pos + 1);
- // Remove \n for dispatching
- std::string part = line.substr(0, pos);
- /*
- if (server_sent != 0) {
- line = line.substr(server_sent);
- server_sent = 0;
- };
- */
- // display on?
- // to_client(line);
- // How should I handle \r in lines? For now, remove it
- // but LOG that we did.
- replace(part, "\r", "");
- /*
- if (replace(part, "\r", "")) {
- BUGZ_LOG(warning) << "\\r removed from line";
- }
- */
- split_lines(part);
- }
- // Ok, we have sent all of the \n lines.
- if (!received.empty())
- if (director.show_client) {
- to_client(received, false);
- // std::string clean = clean_string(received);
- // BUGZ_LOG(error) << "show_client/leftovers:" << clean;
- }
- // This is eating the entire string. String is partial line
- // portcim line, ending with '\r', this eats the line.
- /*
- // check the server prompt here:
- if ((pos = server_prompt.rfind('\r')) != std::string::npos) {
- // server_prompt contains \r, remove it.
- server_prompt = server_prompt.substr(pos + 1);
- }
- */
- while ((pos = server_prompt.find('\b')) != std::string::npos) {
- // backspace? OK! (unless)
- if (pos == 0) {
- // first character, so there's nothing "extra" to erase.
- server_prompt = server_prompt.erase(pos, 1);
- } else
- server_prompt = server_prompt.erase(pos - 1, 2);
- }
- if (!server_prompt.empty()) {
- // We have something remaining -- start the timer!
- set_prompt_timer();
- }
- }
- void Session::set_prompt_timer(void) {
- prompt_timer_.expires_after(std::chrono::milliseconds(time_ms));
- prompt_timer_.async_wait(boost::bind(&Session::on_prompt_timeout, this,
- boost::asio::placeholders::error));
- }
- void Session::reset_prompt_timer(void) { prompt_timer_.cancel(); }
- // probably no longer needed --
- void Session::on_server_prompt(const std::string &prompt,
- const std::string &raw_prompt) {
- director.server_prompt(prompt, raw_prompt);
- if (server_telnet) {
- std::string ayt = std::string((const char *)"\x00\xff\xfd\xf6", 4);
- std::string ayt_resp = std::string((const char *)"\xff\xfb\x00", 3);
- std::string ayt2 = std::string((const char *)"\xff\xfb\x00", 3);
- std::string ayt2_resp = std::string((const char *)"\xff\xfd\x00", 3);
- /*
- for( const char & c : prompt ) {
- BUGZ_LOG(fatal) << "IS? " << (unsigned int)c << " " << std::hex <<
- (unsigned int)c;
- }
- for( const char & c : ayt ) {
- BUGZ_LOG(fatal) << "AYT? " << (unsigned int)c << " " << std::hex <<
- (unsigned int)c;
- }
- */
- if (prompt == ayt) {
- to_server(ayt_resp);
- BUGZ_LOG(warning) << "AYT?";
- server_prompt.clear();
- }
- if (prompt == ayt2) {
- to_server(ayt2_resp);
- BUGZ_LOG(warning) << "AYT2??";
- server_prompt.clear();
- // let the user see what is happening... We're done negotiating (I think)
- director.show_client = true;
- }
- }
- }
- void Session::on_prompt_timeout(const boost::system::error_code error) {
- if (error != boost::asio::error::operation_aborted) {
- // Ok, VALID timeout
- if (!server_prompt.empty()) {
- // Here's what is happening:
- // SP: [ESC[2JESC[H]
- // which after clean_string is empty.
- std::string clean = server_prompt; // clean_string(server_prompt);
- ansi_clean(clean);
- if (!clean.empty()) {
- on_server_prompt(clean, server_prompt);
- }
- // BUGZ_LOG(trace) << "SP: [" << server_prompt << "]";
- }
- }
- }
- void Session::server_read(void) {
- auto self(shared_from_this());
- boost::asio::async_read(
- server_, boost::asio::buffer(server_buffer, sizeof(server_buffer) - 1),
- boost::asio::transfer_at_least(1),
- [this, self](boost::system::error_code ec, std::size_t length) {
- if (!ec) {
- // server_buffer[length] = 0;
- // server_prompt.append(server_buffer, length);
- std::string received(server_buffer, length);
- process_lines(received);
- /*
- I don't believe I need to consume this,
- I'm not async_reading from a stream.
- */
- /*
- if (length) {
- // std::cout << length << std::endl;
- std::cout << "S: " << server_buffer << std::endl;
- do_write(server_buffer);
- }
- */
- server_read();
- } else {
- if (!server_prompt.empty()) {
- // This re-displays the very last message received (before closing)
- // Unfortunately, it repeats the last prompt.
- // (I'm looking to display the message from the server // like is full.)
- // BUGZ_LOG(trace) << "2C: [" << server_prompt << "]";
- // to_client(server_prompt);
- // server_prompt.clear();
- }
- BUGZ_LOG(warning) << "S: read_failed: socket.shutdown()";
- connected = false;
- socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
- }
- });
- }
- void Session::on_resolve(
- const boost::system::error_code error,
- const boost::asio::ip::tcp::resolver::results_type results) {
- //
- auto self(shared_from_this());
- if (!error) {
- // Take the first endpoint.
- boost::asio::ip::tcp::endpoint const &endpoint = *results;
- server_.async_connect(endpoint,
- boost::bind(&Session::on_connect, this,
- boost::asio::placeholders::error));
- } else {
- // TO DO:
- // BOOST_LOG_NAMED_SCOPE("Session");
- BUGZ_LOG(error) << "Unable to resolve: " << host;
- std::string output =
- str(boost::format("Unable to resolve: %1%\n\r") % host);
- to_client(output);
- BUGZ_LOG(warning) << "socket.shutdown()";
- socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
- }
- }
- void Session::client_input(const std::string &input) {
- std::string temp = repr(input);
- director.client_input(input);
- }
- void Session::client_read(void) {
- auto self(shared_from_this());
- boost::asio::async_read( // why can't I async_read_some here?
- socket_, boost::asio::buffer(read_buffer, sizeof(read_buffer) - 1),
- boost::asio::transfer_at_least(1),
- [this, self](boost::system::error_code ec, std::size_t length) {
- if (!ec) {
- // read_buffer[length] = 0;
- if (rlogin_auth.empty()) {
- // first read should be rlogin information
- rlogin_auth.assign(read_buffer, length);
- // parse authentication information
- parse_auth();
- to_client(std::string(1, 0), false);
- to_client("Welcome, ");
- to_client(rlogin_name);
- to_client("\n\r");
- // Activate the connection to the server
- /* // this fails, and I'm not sure why. I've used code like this
- before. resolver_.async_resolve( host, port, std::bind(
- &Session::on_resolve, this, _1, _2)); */
- // This example shows using boost::bind, which WORKS.
- // https://stackoverflow.com/questions/6025471/bind-resolve-handler-to-resolver-async-resolve-using-boostasio
- resolver_.async_resolve(
- host, port,
- boost::bind(&Session::on_resolve, this,
- boost::asio::placeholders::error,
- boost::asio::placeholders::iterator));
- } else if (length) {
- // Proxy Active?
- // BOOST_LOG_NAMED_SCOPE("Session");
- std::string line(read_buffer, length);
- client_input(line);
- // do_write(output);
- }
- client_read();
- } else {
- BUGZ_LOG(warning) << "C: read_failed " << ec;
- if (connected) {
- BUGZ_LOG(warning) << "Server.shutdown()";
- server_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
- }
- }
- });
- }
- void Session::to_client(const std::string &message, bool log) {
- auto self(shared_from_this());
- // output the cleaned string (so I can see what we're sending in the
- // logs)
- /*
- if (log) {
- std::string clean = clean_string(message);
- BUGZ_LOG(trace) << "2C: " << clean;
- }
- */
-
- boost::asio::async_write(
- socket_, boost::asio::buffer(message),
- [this, self](boost::system::error_code ec, std::size_t /*length*/) {
- if (!ec) {
- } else {
- BUGZ_LOG(warning)
- << "2C: write failed? closed? Server.shutdown()" << ec;
- if (connected) {
- BUGZ_LOG(warning) << "Server.shutdown()";
- server_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
- }
- }
- });
- }
- void Session::to_server(const std::string &message) {
- static std::string empty;
- to_server(message, empty);
- }
- void Session::to_server(const std::string &message, const std::string &source) {
- auto self(shared_from_this());
- std::string work = repr(message);
- if (source.empty())
- BUGZ_LOG(trace) << "2S: " << work;
- else
- BUGZ_LOG(trace) << source << " 2S: " << work;
- boost::asio::async_write(
- server_, boost::asio::buffer(message),
- [this, self](boost::system::error_code ec, std::size_t /*length*/) {
- if (!ec) {
- } else {
- BUGZ_LOG(warning)
- << "S: write failed? closed? socket.shutdown() " << ec;
- // we're no longer connected.
- connected = false;
- socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
- }
- });
- if (director.active) {
- start_keepin_alive();
- }
- }
- void Session::start_keepin_alive(void) {
- // keep alive timer
- keep_alive_.expires_after(std::chrono::seconds(keepalive_secs));
- keep_alive_.async_wait(boost::bind(&Session::stayin_alive, this,
- boost::asio::placeholders::error));
- }
- void Session::stayin_alive(const boost::system::error_code error) {
- if (error != boost::asio::error::operation_aborted) {
- // stayin' alive, stayin' alive...
- if (director.active) {
- to_server(" ");
- BUGZ_LOG(warning) << "Session::stayin_alive()";
- }
- }
- }
- Server::Server(boost::asio::io_service &io_service,
- const boost::asio::ip::tcp::endpoint &endpoint,
- const std::string &host, const std::string &port,
- bool server_telnet_)
- : server_telnet{server_telnet_},
- io_service_{io_service},
- acceptor_{io_service_, endpoint},
- signal_{io_service, SIGUSR1, SIGTERM},
- host_{host},
- port_{port} {
- keep_accepting = true;
- BUGZ_LOG(info) << "Server::Server()";
- signal_.async_wait(boost::bind(&Server::on_signal, this,
- boost::asio::placeholders::error,
- boost::asio::placeholders::signal_number));
- do_accept();
- }
- void Server::on_signal(const boost::system::error_code &ec, int signal) {
- BUGZ_LOG(info) << "on_signal() :" << signal;
- keep_accepting = false;
- boost::system::error_code error;
- acceptor_.cancel(error);
- BUGZ_LOG(info) << "cancel: " << error;
- acceptor_.close(error);
- BUGZ_LOG(info) << "close: " << error;
- }
- Server::~Server() {
- CONFIG = json();
- BUGZ_LOG(info) << "Server::~Server()";
- }
- /**
- * setup async connect accept
- *
- * This creates a session for each connection. Using make_shared allows the
- * session to automatically clean up when it is no longer active/has anything
- * running in the reactor.
- */
- void Server::do_accept(void) {
- acceptor_.async_accept([this](boost::system::error_code ec,
- boost::asio::ip::tcp::socket socket) {
- if (!ec) {
- BUGZ_LOG(info) << "Server::do_accept()";
- std::make_shared<Session>(std::move(socket), io_service_, host_, port_,
- server_telnet)
- ->start();
- }
- if (keep_accepting) {
- BUGZ_LOG(info) << "do_accept()";
- do_accept();
- }
- });
- }
|