| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576 | #include <boost/bind.hpp>#include <iostream>#include <boost/format.hpp>// #include <boost/log/core.hpp>// #include <boost/log/trivial.hpp>#include <regex>#include "config.h"#include "logging.h"#include "session.h"#include <string>// #include <boost/log/attributes/named_scope.hpp>bool replace(std::string &str, const std::string &from, const std::string &to) {  size_t start_pos = str.find(from);  if (start_pos == std::string::npos)    return false;  do {    str.replace(start_pos, from.length(), to);  } while ((start_pos = str.find(from)) != std::string::npos);  return true;}bool replace(std::string &str, const char *from, const char *to) {  size_t start_pos = str.find(from);  if (start_pos == std::string::npos)    return false;  do {    str.replace(start_pos, strlen(from), to);  } while ((start_pos = str.find(from)) != std::string::npos);  return true;}void ansi_clean(std::string &str) {  static std::regex ansi_cleaner("\x1b\[[0-9;]*[A-Zmh]",                                 std::regex_constants::ECMAScript);  str = std::regex_replace(str, ansi_cleaner, "");}void high_ascii(std::string &str) {  static std::regex high_cleaner("[\x80-\xff]+",                                 std::regex_constants::ECMAScript);  str = std::regex_replace(str, high_cleaner, "#");}std::smatch ansi_newline(const std::string &str) {  static std::regex ansi_nl("\x1b\[[0-9;]*[JK]",                            std::regex_constants::ECMAScript);  std::smatch m;  std::regex_search(str, m, ansi_nl);  return m;}std::string clean_string(const std::string &source) {  // BOOST_LOG_NAMED_SCOPE("clean_string");  std::string clean = source;  replace(clean, "\n", "\\n");  replace(clean, "\r", "\\r");  replace(clean, "\b", "\\b");  // ANSI too  ansi_clean(clean);  // BUGZ_LOG(error) << "cleaned: " << clean;  high_ascii(clean);  replace(clean, "\x1b", "^");  return clean;}Session::Session(boost::asio::ip::tcp::socket socket,                 boost::asio::io_service &io_service, std::string hostname,                 std::string port)    : socket_(std::move(socket)), io_service_{io_service},      resolver_{io_service}, server_{io_service}, timer_{io_service},      keep_alive_{io_service}, host{hostname}, port{port} {  // server_sent = 0;  time_ms = stoi(from_config("prompt_timeout", "50"));  keepalive_secs = stoi(from_config("keepalive", "45"));  // director.push(new CoreDispatch((*this)));}void Session::start(void) {  // BOOST_LOG_NAMED_SCOPE();  // If I want the file and line number information, here's how to do it:  // BUGZ_LOG(info) << boost::format("(%1%:%2%) ") % __FILE__ % __LINE__  BUGZ_LOG(info) << "Session::start()";  auto self(shared_from_this());  // read_buffer.reserve(1024);  // do_write("Welcome!\n");  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::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 = "?";}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 (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);    }    server_read();  } else {    // TODO:    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);  }}void Session::dispatch_line(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);  // NOTE:  We get "TradeWars Game Server\n"  (Missing \r)  // We add the \r with our injection line.  if (temp.find("TradeWars Game Server   ") != std::string::npos) {    to_client("\rTradeWars Proxy v2++ READY (~ to activate)\n\r");    // reset "active game" -- we're back at the menu  }  // additional analysis needed here  // state :  where are we, what is this line?  // collect all data we can from the server.  (P1)  if (temp.find("Selection (? for menu): ") != std::string::npos) {    char ch = temp[temp.length() - 1];    if (ch >= 'A' && ch < 'Q') {      BUGZ_LOG(warning) << "GAME " << ch << " activated!";    }  }  // "Selection (? for menu): ?"  // This gives us the current game that we're in.  // (excluding ?, #, ! and Q)  BUGZ_LOG(info) << "SL: " << temp;}/*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 outwhat was just added to server_prompt.What about \r, \b ?  Should that "reset" the server_prompt? */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 (show_client) {            line = received.substr(0, rpos + rlen);            to_client(line);          }          received = rm.suffix();        }        // perform this on the server_prompt line        line = m.prefix();        dispatch_line(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 (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;      */      to_client(line);    }    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";    }    */    dispatch_line(part);  }  // Ok, we have sent all of the \n lines.  if (!received.empty())    if (show_client) {      to_client(received);      // std::string clean = clean_string(received);      // BUGZ_LOG(error) << "show_client/leftovers:" << clean;    }  // 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_timer();  }}void Session::set_timer(void) {  timer_.expires_after(std::chrono::milliseconds(time_ms));  timer_.async_wait(      boost::bind(&Session::on_timer, this, boost::asio::placeholders::error));}void Session::reset_timer(void) { timer_.cancel(); }void Session::on_timer(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 = clean_string(server_prompt);      if (!clean.empty()) {        BUGZ_LOG(warning) << "SP: [" << clean << "]";        // emit      }      // 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 {          BUGZ_LOG(warning) << "S: read_failed: socket.shutdown()";          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) {  if (active) {    // do something amazing with the user's input.  } else {    if (input == "\x1b" || input == "~") {      std::string prompt = clean_string(get_prompt());      BUGZ_LOG(debug) << "CI: ACTIVATE prompt shows: [" << prompt << "]";    }  }  if (talk_direct)    to_server(input);  BUGZ_LOG(info) << "CI: " << 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));            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) << "CI: read_failed";          if (connected) {            BUGZ_LOG(warning) << "Server.shutdown()";            server_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);          }        }      });}void Session::to_client(const std::string &message) {  auto self(shared_from_this());  // output the cleaned string (so I can see what we're sending in the  // logs)  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()";          if (connected) {            BUGZ_LOG(warning) << "Server.shutdown()";            server_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);          }        }      });}void Session::to_server(const std::string &message) {  auto self(shared_from_this());  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()";          socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);        }      });  // 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...    to_server(" ");    BUGZ_LOG(warning) << "Session::stayin_alive()";  }}Server::Server(boost::asio::io_service &io_service,               const boost::asio::ip::tcp::endpoint &endpoint, std::string host,               std::string port)    : io_service_{io_service}, acceptor_{io_service_, endpoint}, host_{host},      port_{port} {  only_one = from_config("one_connection", "0") == "1";  do_accept();}/** * 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_)          ->start();    }    if (!only_one)      do_accept();  });}/** * Clean up the trailing ../ in __FILE__ * * This is used by the logging macro. * * @param filepath * @return const char* */const char *trim_path(const char *filepath) {  if (strncmp(filepath, "../", 3) == 0) {    filepath += 3;  }  return filepath;}
 |