/* Mantra - http://www.neuromancy.net/mantra * (c) 2007-2009 - The Neuromancy Society * * This code is released under the Artistic License 2.0. If you have not * received a copy of the license with this source code, you may find * this license at: * http://www.opensource.org/licenses/artistic-license-2.0.php * * $Id: telnet_session.cpp 618 2010-03-11 13:23:03Z prez $ */ #include #include #include #include #include #include #include #include #include namespace pt = boost::property_tree; namespace mantra { namespace admin { struct telnet_port { std::string address; unsigned short port; bool open; bool ssl; bool operator<(const telnet_port &in) const { MT_FB(in); if (in.address < address) MT_RET(false); if (address < in.address) MT_RET(true); if (in.port < port) MT_RET(false); if (port < in.port) MT_RET(true); bool rv = (!ssl && in.ssl); MT_FE(rv); } telnet_port(const telnet_multiplexor::basic_socket_acceptor &a) : address(a.local().address().to_string()), port(a.local().port()), open(a.is_open()), #ifdef HAVE_SSL ssl(dynamic_cast(&a) != NULL) #else ssl(false) #endif { } telnet_port(const base_telnet_session &t) : address(t.local().address().to_string()), port(t.local().port()), open(true), #ifdef HAVE_SSL ssl(dynamic_cast(&t) != NULL) #else ssl(false) #endif { } }; class command_telnet_status : public command { telnet_multiplexor &mplx; public: command_telnet_status(telnet_multiplexor &m) : command(INTL_N("List open telnet ports."), MANTRA_DOMAIN), mplx(m) {} command::return_type operator()(const boost::shared_ptr &sess, std::vector &prev, std::string &rest, const boost::shared_ptr &ctx) { MT_FB(sess << prev << rest << ctx); command::return_type rv = std::make_pair(command::no_error, boost::shared_ptr()); if (mplx.login_timeout()) sess->out(SFORMAT(SMINTL("Login timeout is %1$s seconds.")) % mplx.login_timeout()); else sess->out(SMINTL("Users are not required to login.")); unsigned int pending = mplx.pending_.size(); if (pending) sess->out(SFORMAT(SMINTL_P("%1$s pending session.", "%1$s pending sessions.", pending)) % pending); std::map ports; boost::ptr_vector::const_iterator i; for (i = mplx.acceptors_.begin(); i != mplx.acceptors_.end(); ++i) ports.insert(std::make_pair(telnet_port(*i), 0u)); if (ports.empty()) { sess->out(SMINTL("No active telnet ports.")); MT_RET(rv); } std::list >::const_iterator j; for (j = mplx.sessions_.begin(); j != mplx.sessions_.end(); ++j) { std::map::iterator k = ports.find(telnet_port(**j)); if (k != ports.end()) ++k->second; } std::vector cols; cols.push_back(column(SMINTL("HOST"), 15, 10, mantra::column::left, 25)); cols.push_back(column(SMINTL("OPEN"), 1, 1, mantra::column::middle, 1)); cols.push_back(column(SMINTL("SSL"), 1, 1, mantra::column::middle, 1)); cols.push_back(column(SMINTL("COUNT"), 5, 5, mantra::column::right, 10)); mantra::table t(cols); std::map::iterator k; for (k = ports.begin(); k != ports.end(); ++k) { mantra::table::row_type row; row.push_back(k->first.address + ":" + boost::lexical_cast(k->first.port)); row.push_back(k->first.open ? 'Y' : 'N'); row.push_back(k->first.ssl ? 'Y' : 'N'); row.push_back(k->second); t.add_row(row); } sess->out(SMINTL("Connections by port:")); sess->out(t); MT_FE(rv); } command::return_type help(const boost::shared_ptr &sess, std::vector &prev, std::string &rest, const boost::shared_ptr &ctx) { MT_FB(sess << prev << rest << ctx); sess->out(assemble_cmd(prev)); sess->out(""); sess->out(SMINTL("Report some of the configured settings, how many logins are " "currently pending, and which ports we are currently listening " "on (including how many sessions are active for these ports.")); command::return_type rv = std::make_pair(command::no_error, boost::shared_ptr()); MT_FE(rv); } }; class command_telnet_list : public command { telnet_multiplexor &mplx; public: command_telnet_list(telnet_multiplexor &m) : command(INTL_N("List connected telnet sessions."), MANTRA_DOMAIN), mplx(m) {} command::return_type operator()(const boost::shared_ptr &sess, std::vector &prev, std::string &rest, const boost::shared_ptr &ctx) { MT_FB(sess << prev << rest << ctx); command::return_type rv = std::make_pair(command::no_error, boost::shared_ptr()); if (mplx.sessions_.empty()) { sess->out(SMINTL("No telnet sessions active.")); MT_RET(rv); } std::vector cols; cols.push_back(column(SMINTL("ID"), 3, 3, mantra::column::right, 3)); cols.push_back(column(SMINTL("START"), 20, 20, mantra::column::middle, 20)); cols.push_back(column(SMINTL("LOCAL"), 15, 10, mantra::column::left, 25)); cols.push_back(column(SMINTL("REMOTE"), 15, 10, mantra::column::left, 25)); cols.push_back(column(SMINTL("SSL"), 1, 1, mantra::column::middle, 1)); cols.push_back(column(SMINTL("USER"), 15, 10, mantra::column::left, 25)); mantra::table t(cols); std::list >::iterator i; for (i = mplx.sessions_.begin(); i != mplx.sessions_.end(); ++i) { mantra::table::row_type row; if (!(*i)->is_open()) continue; row.push_back((*i)->id()); row.push_back(boost::posix_time::to_simple_string((*i)->start())); row.push_back((*i)->local().address().to_string() + ":" + boost::lexical_cast((*i)->local().port())); row.push_back((*i)->remote().address().to_string() + ":" + boost::lexical_cast((*i)->remote().port())); #ifdef HAVE_SSL if (dynamic_cast(i->get())) row.push_back('Y'); else #endif // HAVE_SSL row.push_back('N'); std::string name; if (*i == sess) name += "*"; if ((*i)->identity()) name += (*i)->identity()->name(); row.push_back(name); t.add_row(row); } sess->out(t); MT_FE(rv); } command::return_type help(const boost::shared_ptr &sess, std::vector &prev, std::string &rest, const boost::shared_ptr &ctx) { MT_FB(sess << prev << rest << ctx); sess->out(assemble_cmd(prev)); sess->out(""); sess->out(SMINTL("List all active sessions. This list includes the remote host " "and port of each session, as well as the name of the user logged " "in. This list does not include pending (not logged in) sessions.")); command::return_type rv = std::make_pair(command::no_error, boost::shared_ptr()); MT_FE(rv); } }; class command_telnet_kill : public command { telnet_multiplexor &mplx; public: command_telnet_kill(telnet_multiplexor &m) : command(INTL_N("Terminate a telnet session."), MANTRA_DOMAIN), mplx(m) {} command::return_type operator()(const boost::shared_ptr &sess, std::vector &prev, std::string &rest, const boost::shared_ptr &ctx) { MT_FB(sess << prev << rest << ctx); command::return_type rv = std::make_pair(command::no_error, boost::shared_ptr()); boost::optional word = (*admin::default_param_extractor<0>())(rest); if (!word) { rv.first = mantra::admin::command::incomplete_cmd; MT_RET(rv); } else if (word->empty()) { rv.first = mantra::admin::command::not_enough_args; MT_RET(rv); } session::id_type id; try { id = boost::lexical_cast(*word); } catch (boost::bad_lexical_cast &) { rv.first = mantra::admin::command::invalid_arg; MT_RET(rv); } std::list >::iterator i; for (i = mplx.sessions_.begin(); i != mplx.sessions_.end(); ++i) { if ((*i)->id() == id) { (*i)->close(); sess->out(SFORMAT(SMINTL("Session #%1$d closed.")) % id); MT_RET(rv); } } sess->out(SFORMAT(SMINTL("Session #%1$d does not exist or is not the correct session type.")) % id); MT_FE(rv); } command::return_type help(const boost::shared_ptr &sess, std::vector &prev, std::string &rest, const boost::shared_ptr &ctx) { MT_FB(sess << prev << rest << ctx); sess->out(SFORMAT(SMINTL("%1$s ")) % assemble_cmd(prev)); sess->out(""); sess->out(SMINTL("Disconnect a connected session. The logged in user will not be " "given any notification, their socket will just be closed.")); command::return_type rv = std::make_pair(command::no_error, boost::shared_ptr()); MT_FE(rv); } }; base_telnet_session::base_telnet_session(int fd, const std::string &app, size_t histsz, const std::string &prompt, const std::string &cprompt) : editline_session(app, fdopen(fd, "rb"), fdopen(fd, "ab"), fdopen(fd, "ab"), histsz, prompt, cprompt), start_(boost::posix_time::microsec_clock::universal_time()) { MT_FB(fd << app << histsz << prompt << cprompt); FILE *fp; el_get(el(), EL_GETFP, 1, &fp); setlinebuf(fp); el_get(el(), EL_GETFP, 2, &fp); setlinebuf(fp); MT_FE(); } base_telnet_session::~base_telnet_session() { MT_FB(); FILE *fp; el_get(el(), EL_GETFP, 0, &fp); fclose(fp); el_get(el(), EL_GETFP, 1, &fp); fclose(fp); el_get(el(), EL_GETFP, 2, &fp); fclose(fp); MT_FE(); } size_t base_telnet_session::parse_iac(const char *buf, size_t sz, int pty) { MT_FB(buf << sz << pty); size_t adjsz = partiac_.size() + sz; if (adjsz < 2) MT_RET(0); size_t rv = 1; unsigned char c = (partiac_.size() > 1 ? partiac_[1] : buf[1 - partiac_.size()]); switch (c) { case SE: if (adjsz < 2) MT_RET(0); rv = 2 - partiac_.size(); break; case DO: case DONT: case WILL: case WONT: if (adjsz < 3) MT_RET(0); rv = 3 - partiac_.size(); break; case SB: if (adjsz < 3) MT_RET(0); c = (partiac_.size() > 2 ? partiac_[2] : buf[2 - partiac_.size()]); switch (c) { case TELOPT_NAWS: if (adjsz < 7) MT_RET(0); { std::string opt = partiac_; opt.append(buf, 7 - partiac_.size()); int xsz = opt[3] << 8; xsz |= opt[4]; int ysz = opt[5] << 8; ysz |= opt[6]; winsize ws; ws.ws_col = xsz; ws.ws_row = ysz; ioctl(pty, TIOCSWINSZ, &ws); } rv = 7 - partiac_.size(); break; } break; } if (!rv) partiac_.append(buf, sz); else partiac_.erase(); MT_FE(rv); } void base_telnet_session::add_iac(std::string &in, unsigned char cmd, unsigned char op) { MT_FB(in << cmd << op); char buf[3] = { IAC, cmd, op }; in.append(buf, 3); MT_FE(); } std::string telnet_multiplexor::enable_echo() { MT_FB(); std::string rv; base_telnet_session::add_iac(rv, WONT, TELOPT_ECHO); MT_FE(rv); } std::string telnet_multiplexor::disable_echo() { MT_FB(); std::string rv; base_telnet_session::add_iac(rv, WILL, TELOPT_ECHO); MT_FE(rv); } telnet_multiplexor::telnet_multiplexor(const std::string &app, size_t histsz, time_t login_timeout, const std::string &prompt, const std::string &cprompt, const std::string &dir, const std::string &banner) : editline_multiplexor(app, histsz, login_timeout, prompt, cprompt, dir, banner) { MT_FB(app << histsz << login_timeout << prompt << cprompt << dir << banner); MT_FE(); } telnet_multiplexor::basic_socket_acceptor::basic_socket_acceptor(telnet_multiplexor *m, const std::string &host, const std::string &port) : mplx(m), acceptor(m->svc()) { MT_FB(m << host << port); boost::asio::ip::tcp::resolver resolver(mplx->svc()); boost::asio::ip::tcp::resolver::query query(host, port); endpoint = *resolver.resolve(query); MT_FE(); } void telnet_multiplexor::basic_socket_acceptor::start() { MT_FB(); acceptor.open(endpoint.protocol()); acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); acceptor.bind(endpoint); acceptor.listen(); MT_LISTEN(acceptor); MT_FE(); } void telnet_multiplexor::basic_socket_acceptor::stop() { MT_FB(); MT_CLOSE(acceptor); acceptor.close(); MT_FE(); } telnet_multiplexor::socket_acceptor::socket_acceptor(telnet_multiplexor *m, const std::string &host, const std::string &port) : basic_socket_acceptor(m, host, port), next_connection(NULL) { MT_FB(m << host << port); MT_FE(); } void telnet_multiplexor::socket_acceptor::start() { MT_FB(); basic_socket_acceptor::start(); if (!next_connection) next_connection = new telnet_session::socket_type(mplx->svc()); acceptor.async_accept(next_connection->lowest_layer(), boost::bind(&telnet_multiplexor::socket_acceptor::handle_accept, this, boost::asio::placeholders::error)); MT_FE(); } void telnet_multiplexor::socket_acceptor::stop() { MT_FB(); basic_socket_acceptor::stop(); if (next_connection) { delete next_connection; next_connection = NULL; } MT_FE(); } void telnet_multiplexor::socket_acceptor::handle_accept(const boost::system::error_code& error) { MT_FB(error); if (error) MT_RET(); complete(next_connection); next_connection = NULL; next_connection = new telnet_session::socket_type(mplx->svc()); acceptor.async_accept(next_connection->lowest_layer(), boost::bind(&telnet_multiplexor::socket_acceptor::handle_accept, this, boost::asio::placeholders::error)); MT_FE(); } #ifdef HAVE_SSL telnet_multiplexor::ssl_socket_acceptor::ssl_socket_acceptor(telnet_multiplexor *m, const std::string &host, const std::string &port, const std::string &cert_file, bool chain, const std::string &key_file, bool rsaonly, const std::string &dh_file, const std::string &pass, bool pw_file) : basic_socket_acceptor(m, host, port), context(m->svc(), boost::asio::ssl::context::sslv23), next_connection(NULL) { MT_FB(m << host << port << cert_file << chain << key_file << rsaonly << dh_file << pass << pw_file); context.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | (dh_file.empty() ? 0 : boost::asio::ssl::context::single_dh_use)); context.set_password_callback(boost::bind(&telnet_multiplexor::ssl_socket_acceptor::get_key_password, this)); if (chain) context.use_certificate_chain_file(cert_file); else context.use_certificate_file(cert_file, boost::asio::ssl::context::pem); if (rsaonly) context.use_rsa_private_key_file(key_file, boost::asio::ssl::context::pem); else context.use_private_key_file(key_file, boost::asio::ssl::context::pem); if (!dh_file.empty()) context.use_tmp_dh_file(dh_file); if (!pass.empty()) { if (pw_file) { std::ifstream ifs(pass.c_str()); while (ifs) { std::string line; std::getline(ifs, line); password.append(line); } } else password = pass; } MT_FE(); } void telnet_multiplexor::ssl_socket_acceptor::start() { MT_FB(); basic_socket_acceptor::start(); if (!next_connection) next_connection = new stelnet_session::socket_type(mplx->svc(), context); acceptor.async_accept(next_connection->lowest_layer(), boost::bind(&telnet_multiplexor::ssl_socket_acceptor::handle_accept, this, boost::asio::placeholders::error)); MT_FE(); } void telnet_multiplexor::ssl_socket_acceptor::stop() { MT_FB(); basic_socket_acceptor::stop(); if (next_connection) { delete next_connection; next_connection = NULL; } MT_FE(); } void telnet_multiplexor::ssl_socket_acceptor::handle_accept(const boost::system::error_code& error) { MT_FB(error); if (error) MT_RET(); next_connection->async_handshake(boost::asio::ssl::stream_base::server, boost::bind(&telnet_multiplexor::ssl_socket_acceptor::handle_handshake, this, next_connection, boost::asio::placeholders::error)); next_connection = NULL; next_connection = new stelnet_session::socket_type(mplx->svc(), context); acceptor.async_accept(next_connection->lowest_layer(), boost::bind(&telnet_multiplexor::ssl_socket_acceptor::handle_accept, this, boost::asio::placeholders::error)); MT_FE(); } std::string telnet_multiplexor::ssl_socket_acceptor::get_key_password() { return password; } void telnet_multiplexor::ssl_socket_acceptor::handle_handshake(stelnet_session::socket_type *sock, const boost::system::error_code& error) { MT_FB(sock << error); if (error) { delete sock; MT_RET(); } complete(sock); MT_FE(); } #endif // HAVE_SSL void telnet_multiplexor::populate(boost::shared_ptr &cmd, const boost::shared_ptr &selector) { MT_FB(cmd << selector); selector->add("L(IST|S)", "list", boost::shared_ptr(new command_telnet_list(*this))); selector->add("KILL|CLOSE", "kill", boost::shared_ptr(new command_telnet_kill(*this))); selector->add("STATUS|INFO", "status", boost::shared_ptr(new command_telnet_status(*this))); MT_FE(); } struct telnet_force_check_exception {}; static void force_check(const boost::system::error_code& error) { if (error != boost::asio::error::operation_aborted) throw telnet_force_check_exception(); } void telnet_multiplexor::start() { MT_FB(); editline_multiplexor::start(); for (size_t i = 0; i < acceptors_.size(); ++i) acceptors_[i].start(); MT_FE(); } void telnet_multiplexor::stop() { MT_FB(); // Stop accepting new connections. for (size_t i = 0; i < acceptors_.size(); ++i) acceptors_[i].stop(); // Close any pending sessions std::set::iterator k; for (k = pending_.begin(); k != pending_.end(); ++k) (*k)->abort(); // Close any established sessions. std::list >::iterator i; for (i = sessions_.begin(); i != sessions_.end(); ++i) (*i)->close(); // Let the asio io_service get everything out of it's system ;) editline_multiplexor::stop(); // Clean up our data structures ... pending_.clear(); sessions_.clear(); new_sessions_.clear(); MT_FE(); } boost::shared_ptr telnet_multiplexor::wait(const boost::function &check) { MT_FB(check); boost::shared_ptr s; boost::asio::deadline_timer checkevent(svc()); checkevent.expires_from_now(boost::posix_time::milliseconds(10)); checkevent.async_wait(&force_check); while (check()) { if (!new_sessions_.empty()) { s = new_sessions_.front(); new_sessions_.pop_front(); break; } try { svc().run_one(); } catch (telnet_force_check_exception &) { std::list >::iterator i = sessions_.begin(); while (i != sessions_.end()) { if (!(*i)->is_open()) sessions_.erase(i++); else ++i; } checkevent.expires_from_now(boost::posix_time::milliseconds(10)); checkevent.async_wait(&force_check); } MT_FLUSH(); } checkevent.cancel(); MT_FE(s); } static session_multiplexor *create_telnet_multiplexor(const pt::iptree &cfg) { MT_FB(cfg); std::string app = cfg.get("application", std::string()); size_t histsz = cfg.get("history_size", 100u); time_t login_timeout = cfg.get("login_timeout", time_t(0u)); std::string prompt = cfg.get("prompt", app + "$ "); std::string cprompt = cfg.get("cprompt", "...> "); std::string banner = cfg.get("banner", std::string()); std::string dir = cfg.get("directory", std::string()); telnet_multiplexor *rv = new telnet_multiplexor(app, histsz, login_timeout, prompt, cprompt, dir, banner); pt::iptree::const_iterator i; for (i = cfg.begin(); i != cfg.end(); ++i) { if (boost::algorithm::iequals(i->first, "listen")) { std::string host = i->second.get(".host", i->second.get("host", std::string())); std::string port = i->second.get(".port", i->second.get("port")); pt::iptree::const_assoc_iterator j = i->second.find("ssl"); if (j == i->second.not_found()) rv->add_acceptor(new telnet_multiplexor::socket_acceptor(rv, host, port)); else { #ifdef HAVE_SSL std::string cert_file = j->second.get("cert_file", std::string()); std::string cert_chain_file = j->second.get("cert_chain_file", std::string()); std::string key_file = j->second.get("key_file", std::string()); bool rsa_only = j->second.get("rsa_only", false); std::string password = j->second.get("password", std::string()); std::string password_file = j->second.get("password_file", std::string()); std::string dh_file = j->second.get("dh_file", std::string()); if (!(cert_file.empty() ^ cert_chain_file.empty())) continue; if (!password.empty() && !password_file.empty()) continue; rv->add_acceptor(new telnet_multiplexor::ssl_socket_acceptor(rv, host, port, cert_chain_file.empty() ? cert_file : cert_chain_file, cert_chain_file.empty() ? false : true, key_file, rsa_only, dh_file, password_file.empty() ? password : password_file, password_file.empty() ? false : true)); #else // HAVE_SSL // throw? log? what? #endif // HAVE_SSL } } } MT_FE(rv); } static mantra::anonymous_registry::registrar register_telnet_multiplexor( session_multiplexor::registry(), "telnet", &create_telnet_multiplexor); extern "C" { MANTRA_API std::string config_telnet_session(const boost::property_tree::iptree &cfg) { char rv[20]; snprintf(rv, 20, "%d.%d.%d", (MANTRA_VERSION / 100000), ((MANTRA_VERSION / 100) % 1000), (MANTRA_VERSION % 100)); return rv; } } // extern "C" } } // namespace mantra::admin