Stand/Server Software/relayserver/relayserver.cpp
2024-10-16 11:20:42 +08:00

260 lines
6.7 KiB
C++

#include <iostream>
#include <HttpRequest.hpp>
#include <netConfig.hpp>
#include <pem.hpp>
#include <Promise.hpp>
#include <Scheduler.hpp>
#include <Server.hpp>
#include <ServerWebService.hpp>
#include <Socket.hpp>
#include <string.hpp>
#include <TlsServerRsaData.hpp>
#include <WebSocketMessage.hpp>
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<soup::Socket*>(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<soup::Socket&>(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<soup::Socket&>(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::Promise<bool>>([](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<std::string>());
soup::HttpRequest req{ "backend-services.stand.sh", std::move(path) };
auto res = req.execute();
if (!res.has_value())
{
return false;
}
return soup::string::toInt<uint8_t>(res->body) >= 2;
}, std::move(key));
soup::PromiseBase* pp = p.get();
s.awaitPromiseCompletion(pp, [](soup::Worker& w, soup::Capture&& cap)
{
auto& p = cap.get<soup::UniquePtr<soup::Promise<bool>>>();
if (p.get() && p->getResult())
{
soup::Socket& s = reinterpret_cast<soup::Socket&>(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();
}