| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761 | #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)    : 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["prompt_timeout"]) time_ms = CONFIG["prompt_timeout"].as<int>();  keepalive_secs = 45;  if (CONFIG["keepalive"]) keepalive_secs = CONFIG["keepalive"].as<int>();  // Initialize the director  director.to_server = boost::bind(&Session::to_server, this, _1);  director.to_client = boost::bind(&Session::to_client, this, _1);  // 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 = "?";}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);  }}/** * 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) {  BUGZ_LOG(info) << "SL: [" << line << "]";  director.server_line(line);#ifdef DECOUPLE  if (line.find("TradeWars Game Server   ") != std::string::npos) {    to_client("\rTradeWars Proxy v2++ READY (~ or ESC to activate)\n\r");    game = 0;    // reset "active game" -- we're back at the menu  }  /*     ____                             _     _    / ___|  ___ _ ____   _____ _ __  | |   (_)_ __   ___    \___ \ / _ \ '__\ \ / / _ \ '__| | |   | | '_ \ / _ \     ___) |  __/ |   \ V /  __/ |    | |___| | | | |  __/    |____/ \___|_|    \_/ \___|_|    |_____|_|_| |_|\___|     ____                _    |  _ \ __ _ _ __ ___(_)_ __   __ _    | |_) / _` | '__/ __| | '_ \ / _` |    |  __/ (_| | |  \__ \ | | | | (_| |    |_|   \__,_|_|  |___/_|_| |_|\__, |                                 |___/  This is where all of the server lines are gleaned for all the  information that we can get out of them.   */  if (line.find("Selection (? for menu): ") != std::string::npos) {    char ch = line[line.length() - 1];    if (ch >= 'A' && ch < 'Q') {      game = ch;      BUGZ_LOG(warning) << "GAME " << game << " activated!";    }    // not needed (handled by above Game Server check).    if (ch == 'Q') game = 0;  }  // Do I need to run through the tests (below) before calling the parser here?  // Or will the parsers know when they are done processing, and clear?  // Yes, run through the various tests, then call SL_parser.  if ((line.substr(0, 19) == "The shortest path (") ||      (line.substr(0, 7) == "  TO > ")) {    SL_parser = [this](const std::string s) { this->SL_warpline(s); };  } else {    if (line.substr(0, 43) == " Items     Status  Trading % of max OnBoard") {      SL_parser = [this](const std::string s) { this->SL_portline(s); };    } else {      if (line.substr(0, 10) == "<Thievery>") {        SL_parser = [this](const std::string s) { this->SL_thiefline(s); };      } else {        if (line == ": ") {          SL_parser = [this](const std::string s) { this->SL_cimline(s); };        } else {          if (line.substr(0, 1) == "Sector  : ") {            SL_parser = [this](const std::string s) { this->SL_sectorline(s); };          }        }      }    }  }  if (SL_parser) {    SL_parser(line);  }  // should I have an internal emit_server_line for parsing sections?  // rather then having a weird state machine to track where we are?  if (emit_server_line) emit_server_line(line);#endif}/** * 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);}/*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?\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 (show_client) {            line = received.substr(0, rpos + rlen);            to_client(line);          }          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 (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";    }    */    split_lines(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;    }  // 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) {  BUGZ_LOG(warning) << "SP: [" << prompt << "]";  director.server_prompt(prompt, raw_prompt);}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 = clean_string(server_prompt);      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 {          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) {  BUGZ_LOG(info) << "CI: " << input;  director.client_input(input);#ifdef DECOUPLE  // Is "proxy" active  if (active) {    // do something amazing with the user's input.  } else {    if (input == "\x1b" || input == "~") {      std::string prompt = clean_string(get_prompt());      BUGZ_LOG(trace) << "CI: ACTIVATE prompt shows: [" << prompt << "]";      if (prompt == "Selection (? for menu): ") {        to_client(            "\n\rThere's not much we can do here.  Activate in-game at a "            "Command prompt.\n\r");        to_client(get_prompt());        return;      }      // easter-eggs:      if (prompt == "Enter your choice: ") {        to_client(            "\n\r\x1b[1;36mI'd choose \x1b[1;37m`T`\x1b[1;36m, but "            "that's how I was coded.\n\r");        to_client(get_prompt());        return;      }      // easter-egg      if (prompt == "[Pause]") {        to_client(" \x1b[1;36mMeow\x1b[0m\n\r");        to_client(get_prompt());        return;      }      //      // The command prompt that we're looking for:      //      // "Command [TL=00:00:00]:[242] (?=Help)? : "      // the time, and the sector number vary...      if (prompt.substr(0, 9) == "Command [") {        int len = prompt.length();        if (prompt.substr(len - 14) == "] (?=Help)? : ") {          proxy_activate();          /*          to_client("\n\r\x1b[1;34mWELCOME!  This is where the proxy would "                    "activate.\n\r");          // active = true;          // show_client = true;  // because if something comes (unexpected)          // from the server? talk_direct = false;          // but we aren't activating (NNY)          to_client(get_prompt());          */          return;        }      }      // eat this input.      BUGZ_LOG(warning) << "CI: unable to activate, prompt was: [" << prompt                        << "]";      return;    }  }  // as the above code matures, talk_direct might get changed.  // keep this part here (and not above).  if (talk_direct) {    to_server(input);  }  if (emit_client_input) {    emit_client_input(input);  }#endif}/*DispatchSettings Session::save_settings(void) {  DispatchSettings ss{emit_server_line, emit_server_prompt, emit_client_input,                      show_client, talk_direct};  return ss;}void Session::restore_settings(const DispatchSettings &ss) {  emit_server_line = ss.server_line;  emit_server_prompt = ss.server_prompt;  emit_client_input = ss.client_input;  show_client = ss.show_client;  talk_direct = ss.talk_direct;}void Session::proxy_activate(void) {  active = true;  start_keepin_alive();  // kickstart the keepalive timer  main.setNotify([this](void) { this->proxy_deactivate(); });  main.activate();}void Session::proxy_deactivate(void) {  // Ok, how do we return?  active = false;  to_client(get_prompt());  // to_client(" \b");}*/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());  BUGZ_LOG(trace) << "2S: " << message;  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()";          // 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)    : 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 = YAML::Node();  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_)          ->start();    }    if (keep_accepting) {      BUGZ_LOG(info) << "do_accept()";      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;}
 |