#include #include #include #include #include #include #include #include #include #include #include #include struct RelayClientData { bool is_mod = false; std::string key{}; std::string line_buf{}; }; static soup::Server serv{}; [[nodiscard]] static soup::Socket* getSocket(bool mod, const std::string& key) { for (const auto& w : serv.workers) { auto s = reinterpret_cast(w.get()); RelayClientData& cd = s->custom_data.getStructFromMap(RelayClientData); if (cd.is_mod == mod && cd.key == key ) { return s; } } return nullptr; } [[nodiscard]] static soup::Socket* getMod(const std::string& key) { return getSocket(true, key); } [[nodiscard]] static soup::Socket* getWeb(const std::string& key) { return getSocket(false, key); } static void handleDisconnect(soup::Socket& s) { RelayClientData& cd = s.custom_data.getStructFromMap(RelayClientData); if (!cd.key.empty()) { if (cd.is_mod) { if (auto ws = getWeb(cd.key)) { soup::ServerWebService::wsSend(*ws, "lost_mod", true); } } else { if (auto ms = getMod(cd.key)) { ms->send("lost_web\n"); } } } } static void modRecvLoop(soup::Socket& s) SOUP_EXCAL { s.recv([](soup::Socket& s, std::string&& data, soup::Capture&&) SOUP_EXCAL { RelayClientData& cd = s.custom_data.getStructFromMap(RelayClientData); if (!cd.key.empty()) { cd.line_buf.append(data); auto off = cd.line_buf.find('\n'); if (off != std::string::npos) { if (auto ws = getWeb(cd.key)) { do { soup::ServerWebService::wsSend(*ws, cd.line_buf.substr(0, off), true); cd.line_buf.erase(0, off + 1); } while (off = cd.line_buf.find('\n'), off != std::string::npos); } } modRecvLoop(s); } }); } static soup::TlsServerRsaData server_rsa_data; static void cert_selector(soup::TlsServerRsaData& out, const std::string& server_name) SOUP_EXCAL { out = server_rsa_data; } int main() { server_rsa_data.der_encoded_certchain = soup::pem::decodeChain(soup::string::fromFile("cert/certplusissuer.pem")); server_rsa_data.private_key = soup::RsaPrivateKey::fromPem(soup::string::fromFile("cert/privkey.pem")); serv.on_work_done = [](soup::Worker& w, soup::Scheduler&) { soup::Socket& s = reinterpret_cast(w); std::cout << s.peer.toString() << " - work done" << std::endl; handleDisconnect(s); }; serv.on_connection_lost = [](soup::Socket& s, soup::Scheduler&) { std::cout << s.peer.toString() << " - connection lost" << std::endl; handleDisconnect(s); }; serv.on_exception = [](soup::Worker& w, const std::exception& e, soup::Scheduler&) { soup::Socket& s = reinterpret_cast(w); std::cout << s.peer.toString() << " - exception: " << e.what() << std::endl; handleDisconnect(s); }; soup::ServerService stand_srv{ [](soup::Socket& s, soup::ServerService&, soup::Server&) SOUP_EXCAL { std::cout << s.peer.toString() << " + mod connection established" << std::endl; s.send("Go ahead\n"); s.recv([](soup::Socket& s, std::string&& data, soup::Capture&&) SOUP_EXCAL { std::cout << s.peer.toString() << " > " << data << std::endl; if (data.substr(0, 6) == "Stand:") { if (data.substr(6, 2) != "1:") { s.send("notify_above_map Your version of Stand is not supported by this relay.\n"); } else { std::string key = data.substr(8); if (auto ms = getMod(key)) { ms->custom_data.getStructFromMap(RelayClientData).key.clear(); ms->close(); } RelayClientData& cd = s.custom_data.getStructFromMap(RelayClientData); cd.is_mod = true; cd.key = key; auto p = soup::make_unique>([](soup::Capture&& cap) -> bool { // This takes too long if we actually validate the cert soup::netConfig::get().certchain_validator = &soup::Socket::certchain_validator_none; std::string path = "internal_get_privilege/?0="; path.append(cap.get()); soup::HttpRequest req{ "backend-services.stand.sh", std::move(path) }; auto res = req.execute(); if (!res.has_value()) { return false; } return soup::string::toInt(res->body) >= 2; }, std::move(key)); soup::PromiseBase* pp = p.get(); s.awaitPromiseCompletion(pp, [](soup::Worker& w, soup::Capture&& cap) { auto& p = cap.get>>(); if (p.get() && p->getResult()) { soup::Socket& s = reinterpret_cast(w); if (auto ws = getWeb(s.custom_data.getStructFromMap(RelayClientData).key)) { s.send("got_web\n"); } modRecvLoop(s); } }, std::move(p)); } } }); } }; soup::ServerWebService web_srv{ [](soup::Socket& s, soup::HttpRequest&& req, soup::ServerWebService&) { soup::ServerWebService::disableKeepAlive(s); soup::ServerWebService::sendText(s, ":)"); } }; web_srv.on_connection_established = [](soup::Socket& s, soup::ServerService&, soup::Server&) SOUP_EXCAL { std::cout << s.peer.toString() << " + web connection established" << std::endl; }; web_srv.should_accept_websocket_connection = [](soup::Socket& s, const soup::HttpRequest& req, soup::ServerWebService&) SOUP_EXCAL { std::cout << s.peer.toString() << " > WEBSOCKET " << req.path << std::endl; return true; }; web_srv.on_websocket_message = [](soup::WebSocketMessage& msg, soup::Socket& s, soup::ServerWebService&) { RelayClientData& cd = s.custom_data.getStructFromMap(RelayClientData); if (cd.key.empty()) { if (msg.data.substr(0, 8) == "license ") { auto key = msg.data.substr(8); if (auto ws = getWeb(key)) { ws->custom_data.getStructFromMap(RelayClientData).key.clear(); ws->close(); } cd.key = std::move(key); if (auto ms = getMod(cd.key)) { ms->send("got_web\n"); } else { soup::ServerWebService::wsSend(s, "no_mod", true); } return; } } else { if (msg.data.substr(0, 2) == "p " || msg.data.substr(0, 2) == "k " || msg.data.substr(0, 2) == "c " || msg.data.substr(0, 2) == "s " ) { if (auto ms = getMod(cd.key)) { msg.data.push_back('\n'); ms->send(std::move(msg.data)); return; } } } s.close(); }; if (!serv.bind(25769, &stand_srv)) { std::cout << "Failed to bind to port 25769." << std::endl; return 1; } if (!serv.bindCrypto(4269, &web_srv, &cert_selector)) { std::cout << "Failed to bind to port 4269." << std::endl; return 2; } std::cout << "Listening on ports 25769 and 4269." << std::endl; serv.run(); }