mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-16 22:58:34 +00:00
Servertalk implementation wip, added optional support for pub-key authenticated encryption (via libsodium), not backwards compatible.
This commit is contained in:
+14
-9
@@ -76,8 +76,9 @@ SET(common_sources
|
||||
net/daybreak_connection.cpp
|
||||
net/eqstream.cpp
|
||||
net/packet.cpp
|
||||
net/relay.cpp
|
||||
net/relay_link.cpp
|
||||
net/servertalk_client_connection.cpp
|
||||
net/servertalk_server.cpp
|
||||
net/servertalk_server_connection.cpp
|
||||
net/tcp_connection.cpp
|
||||
net/tcp_server.cpp
|
||||
patches/patches.cpp
|
||||
@@ -222,8 +223,10 @@ SET(common_headers
|
||||
net/endian.h
|
||||
net/eqstream.h
|
||||
net/packet.h
|
||||
net/relay.h
|
||||
net/relay_link.h
|
||||
net/servertalk_client_connection.h
|
||||
net/servertalk_common.h
|
||||
net/servertalk_server.h
|
||||
net/servertalk_server_connection.h
|
||||
net/tcp_connection.h
|
||||
net/tcp_server.h
|
||||
patches/patches.h
|
||||
@@ -301,10 +304,13 @@ SOURCE_GROUP(Net FILES
|
||||
net/eqstream.h
|
||||
net/packet.cpp
|
||||
net/packet.h
|
||||
net/relay.cpp
|
||||
net/relay.h
|
||||
net/relay_link.cpp
|
||||
net/relay_link.h
|
||||
net/servertalk_client_connection.cpp
|
||||
net/servertalk_client_connection.h
|
||||
net/servertalk_common.h
|
||||
net/servertalk_server.cpp
|
||||
net/servertalk_server.h
|
||||
net/servertalk_server_connection.cpp
|
||||
net/servertalk_server_connection.h
|
||||
net/tcp_connection.cpp
|
||||
net/tcp_connection.h
|
||||
net/tcp_server.cpp
|
||||
@@ -414,7 +420,6 @@ INCLUDE_DIRECTORIES(Patches SocketLib StackWalker TinyXML)
|
||||
ADD_LIBRARY(common ${common_sources} ${common_headers})
|
||||
|
||||
IF(UNIX)
|
||||
ADD_DEFINITIONS(-fPIC)
|
||||
SET_SOURCE_FILES_PROPERTIES("SocketLib/Mime.cpp" PROPERTY COMPILE_FLAGS -Wno-unused-result)
|
||||
SET_SOURCE_FILES_PROPERTIES("patches/sod.cpp" "patches/sof.cpp" "patches/rof.cpp" "patches/rof2.cpp" "patches/uf.cpp" PROPERTIES COMPILE_FLAGS -O0)
|
||||
ENDIF(UNIX)
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
#include "relay_link.h"
|
||||
#include "dns.h"
|
||||
#include "../eqemu_logsys.h"
|
||||
#include "../md5.h"
|
||||
#include "../servertalk.h"
|
||||
|
||||
EQ::Net::RelayLink::RelayLink(const std::string &addr, int port, const std::string &identifier, const std::string &password)
|
||||
: m_timer(std::unique_ptr<EQ::Timer>(new EQ::Timer(250, true, std::bind(&EQ::Net::RelayLink::Connect, this)))),
|
||||
m_keepalive(std::unique_ptr<EQ::Timer>(new EQ::Timer(5000, true, std::bind(&EQ::Net::RelayLink::SendKeepAlive, this))))
|
||||
{
|
||||
m_established = false;
|
||||
m_connecting = false;
|
||||
m_port = port;
|
||||
m_identifier = identifier;
|
||||
m_password = password;
|
||||
DNSLookup(addr, port, false, [this](const std::string &address) {
|
||||
m_addr = address;
|
||||
});
|
||||
|
||||
m_opcode_dispatch.insert(std::make_pair(ServerOP_ZAAuthFailed, std::bind(&RelayLink::OnAuthFailed, this, std::placeholders::_1)));
|
||||
}
|
||||
|
||||
EQ::Net::RelayLink::~RelayLink()
|
||||
{
|
||||
}
|
||||
|
||||
void EQ::Net::RelayLink::OnMessageType(uint16 opcode, std::function<void(const EQ::Net::Packet&p)> cb)
|
||||
{
|
||||
if (opcode != ServerOP_ZAAuthFailed) {
|
||||
m_opcode_dispatch.insert(std::make_pair(opcode, cb));
|
||||
}
|
||||
}
|
||||
|
||||
void EQ::Net::RelayLink::SendPacket(uint16 opcode, const EQ::Net::Packet &p)
|
||||
{
|
||||
EQ::Net::WritablePacket packet;
|
||||
packet.PutUInt32(0, p.Length() + 7);
|
||||
packet.PutInt8(4, 0);
|
||||
packet.PutUInt16(5, opcode);
|
||||
if(p.Length() > 0)
|
||||
packet.PutPacket(7, p);
|
||||
|
||||
if (m_connection) {
|
||||
m_connection->Write((const char*)packet.Data(), packet.Length());
|
||||
}
|
||||
else {
|
||||
m_packet_queue.push(packet);
|
||||
}
|
||||
}
|
||||
|
||||
void EQ::Net::RelayLink::Connect()
|
||||
{
|
||||
if (m_addr.length() == 0 || m_port == 0 || m_connection || m_connecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_connecting = true;
|
||||
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
|
||||
if (connection == nullptr) {
|
||||
Log.OutF(Logs::General, Logs::Debug, "Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||
m_connecting = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Log.OutF(Logs::General, Logs::Debug, "Connected to {0}:{1}", m_addr, m_port);
|
||||
m_connection = connection;
|
||||
m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) {
|
||||
Log.OutF(Logs::General, Logs::Debug, "Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||
m_established = false;
|
||||
m_connection.reset();
|
||||
});
|
||||
|
||||
m_connection->OnRead(std::bind(&EQ::Net::RelayLink::ProcessData, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
||||
m_connection->Start();
|
||||
|
||||
SendIdentifier();
|
||||
m_connecting = false;
|
||||
});
|
||||
}
|
||||
|
||||
void EQ::Net::RelayLink::ProcessData(EQ::Net::TCPConnection *c, const unsigned char *data, size_t length)
|
||||
{
|
||||
EQ::Net::ReadOnlyPacket p((void*)data, length);
|
||||
try {
|
||||
Log.OutF(Logs::General, Logs::Debug, "Process data:\n{0}", p.ToString());
|
||||
|
||||
if (m_established) {
|
||||
ProcessPacket(p);
|
||||
}
|
||||
else {
|
||||
std::string msg;
|
||||
if (m_identifier.compare("LOGIN") == 0) {
|
||||
msg = fmt::format("**PACKETMODE**\r");
|
||||
}
|
||||
else {
|
||||
msg = fmt::format("**PACKETMODE{0}**\r", m_identifier);
|
||||
}
|
||||
|
||||
std::string cmp_msg;
|
||||
if (p.GetInt8(0) == '*') {
|
||||
cmp_msg = p.GetString(0, msg.length());
|
||||
}
|
||||
else if (p.GetInt8(1) == '*') {
|
||||
cmp_msg = p.GetString(1, msg.length());
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmp_msg.compare(msg) == 0) {
|
||||
m_established = true;
|
||||
Log.OutF(Logs::General, Logs::Debug, "Established connection of type {0}", m_identifier);
|
||||
SendPassword();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception &ex) {
|
||||
Log.OutF(Logs::General, Logs::Debug, "Error parsing relay link packet: {0}", ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
void EQ::Net::RelayLink::ProcessPacket(const EQ::Net::Packet &p)
|
||||
{
|
||||
char *buffer = (char*)p.Data();
|
||||
m_data_buffer.insert(m_data_buffer.begin() + m_data_buffer.size(), buffer, buffer + p.Length());
|
||||
|
||||
ProcessBuffer();
|
||||
}
|
||||
|
||||
void EQ::Net::RelayLink::ProcessBuffer()
|
||||
{
|
||||
size_t size = 7;
|
||||
size_t base = 0;
|
||||
size_t used = m_data_buffer.size();
|
||||
while ((used - base) >= size) {
|
||||
uint32 packet_size = *(uint32*)&m_data_buffer[base];
|
||||
uint8 packet_flags = *(uint8*)&m_data_buffer[base + 4];
|
||||
uint16 packet_opcode = *(uint16*)&m_data_buffer[base + 5];
|
||||
|
||||
if ((used - base) >= packet_size) {
|
||||
EQ::Net::ReadOnlyPacket p(&m_data_buffer[base], packet_size);
|
||||
|
||||
if (m_opcode_dispatch.count(packet_opcode) > 0) {
|
||||
auto &cb = m_opcode_dispatch[(int)packet_opcode];
|
||||
cb(p);
|
||||
}
|
||||
else {
|
||||
Log.OutF(Logs::General, Logs::Debug, "Unhandled packet of type {0:x}", packet_opcode);
|
||||
}
|
||||
|
||||
base += packet_size;
|
||||
}
|
||||
else {
|
||||
EQ::Net::WritablePacket p;
|
||||
|
||||
if (m_opcode_dispatch.count(packet_opcode) > 0) {
|
||||
auto &cb = m_opcode_dispatch[(int)packet_opcode];
|
||||
cb(p);
|
||||
}
|
||||
else {
|
||||
Log.OutF(Logs::General, Logs::Debug, "Unhandled packet of type {0:x}", packet_opcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (used == base) {
|
||||
m_data_buffer.clear();
|
||||
}
|
||||
else {
|
||||
m_data_buffer.erase(m_data_buffer.begin(), m_data_buffer.begin() + base);
|
||||
}
|
||||
}
|
||||
|
||||
void EQ::Net::RelayLink::ProcessQueue()
|
||||
{
|
||||
if (!m_connection)
|
||||
return;
|
||||
|
||||
while (!m_packet_queue.empty()) {
|
||||
auto &p = m_packet_queue.front();
|
||||
m_connection->Write((const char*)p.Data(), p.Length());
|
||||
m_packet_queue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void EQ::Net::RelayLink::SendIdentifier()
|
||||
{
|
||||
std::string msg;
|
||||
if (m_identifier.compare("LOGIN") == 0) {
|
||||
msg = fmt::format("**PACKETMODE**\r");
|
||||
}
|
||||
else {
|
||||
msg = fmt::format("**PACKETMODE{0}**\r", m_identifier);
|
||||
}
|
||||
EQ::Net::WritablePacket packet;
|
||||
packet.PutData(0, (void*)msg.c_str(), msg.length());
|
||||
SendInternal(packet);
|
||||
}
|
||||
|
||||
void EQ::Net::RelayLink::SendInternal(const EQ::Net::Packet &p)
|
||||
{
|
||||
if (m_connection == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_connection->Write((const char*)p.Data(), p.Length());
|
||||
}
|
||||
|
||||
void EQ::Net::RelayLink::SendPassword()
|
||||
{
|
||||
if (m_password.length() > 0) {
|
||||
char hash[16] = { 0 };
|
||||
MD5::Generate((const uchar*)m_password.c_str(), m_password.length(), (uchar*)&hash[0]);
|
||||
|
||||
EQ::Net::WritablePacket p;
|
||||
p.PutData(0, &hash[0], 16);
|
||||
SendPacket(ServerOP_ZAAuth, p);
|
||||
}
|
||||
}
|
||||
|
||||
void EQ::Net::RelayLink::OnAuthFailed(const EQ::Net::Packet &p)
|
||||
{
|
||||
if (m_connection) {
|
||||
Log.OutF(Logs::General, Logs::Debug, "Authorization failed for server type {0}", m_identifier);
|
||||
m_connection->Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void EQ::Net::RelayLink::SendKeepAlive()
|
||||
{
|
||||
if (!m_connection)
|
||||
return;
|
||||
|
||||
EQ::Net::WritablePacket p;
|
||||
SendPacket(0, p);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "tcp_server.h"
|
||||
#include "packet.h"
|
||||
#include "../types.h"
|
||||
#include "../event/timer.h"
|
||||
#include "../event/event_loop.h"
|
||||
#include <map>
|
||||
#include <queue>
|
||||
|
||||
namespace EQ
|
||||
{
|
||||
namespace Net {
|
||||
class RelayLink
|
||||
{
|
||||
public:
|
||||
RelayLink(const std::string &addr, int port, const std::string &identifier, const std::string &password);
|
||||
~RelayLink();
|
||||
|
||||
void OnMessageType(uint16 opcode, std::function<void(const EQ::Net::Packet &p)> cb);
|
||||
void SendPacket(uint16 opcode, const EQ::Net::Packet &p);
|
||||
bool Connected() const { return m_connection != nullptr; }
|
||||
std::string GetIP() const { return m_addr; }
|
||||
uint16 GetPort() const { return m_port; }
|
||||
private:
|
||||
void Connect();
|
||||
void ProcessData(EQ::Net::TCPConnection *c, const unsigned char *data, size_t length);
|
||||
void ProcessPacket(const EQ::Net::Packet &p);
|
||||
void ProcessBuffer();
|
||||
void ProcessQueue();
|
||||
void SendIdentifier();
|
||||
void SendInternal(const EQ::Net::Packet &p);
|
||||
void SendPassword();
|
||||
void OnAuthFailed(const EQ::Net::Packet &p);
|
||||
void SendKeepAlive();
|
||||
|
||||
std::unique_ptr<EQ::Timer> m_timer;
|
||||
std::unique_ptr<EQ::Timer> m_keepalive;
|
||||
std::string m_addr;
|
||||
std::string m_identifier;
|
||||
std::string m_password;
|
||||
int m_port;
|
||||
std::shared_ptr<EQ::Net::TCPConnection> m_connection;
|
||||
bool m_established;
|
||||
bool m_connecting;
|
||||
std::vector<char> m_data_buffer;
|
||||
std::map<uint16, std::function<void(const EQ::Net::Packet &p)>> m_opcode_dispatch;
|
||||
std::queue<EQ::Net::WritablePacket> m_packet_queue;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
#include "servertalk_client_connection.h"
|
||||
#include "dns.h"
|
||||
#include "../eqemu_logsys.h"
|
||||
|
||||
EQ::Net::ServertalkClient::ServertalkClient(const std::string &addr, int port, bool ipv6, const std::string &identifier, const std::string &credentials)
|
||||
: m_timer(std::unique_ptr<EQ::Timer>(new EQ::Timer(100, true, std::bind(&EQ::Net::ServertalkClient::Connect, this))))
|
||||
{
|
||||
m_port = port;
|
||||
m_ipv6 = ipv6;
|
||||
m_identifier = identifier.empty() ? "Unknown" : identifier;
|
||||
m_credentials = credentials;
|
||||
m_connecting = false;
|
||||
DNSLookup(addr, port, false, [this](const std::string &address) {
|
||||
m_addr = address;
|
||||
});
|
||||
}
|
||||
|
||||
EQ::Net::ServertalkClient::~ServertalkClient()
|
||||
{
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkClient::Connect()
|
||||
{
|
||||
if (m_addr.length() == 0 || m_port == 0 || m_connection || m_connecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_connecting = true;
|
||||
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
|
||||
if (connection == nullptr) {
|
||||
Log.OutF(Logs::General, Logs::TCP_Connection, "Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||
m_connecting = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Log.OutF(Logs::General, Logs::TCP_Connection, "Connected to {0}:{1}", m_addr, m_port);
|
||||
m_connection = connection;
|
||||
m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) {
|
||||
Log.OutF(Logs::General, Logs::TCP_Connection, "Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||
m_connection.reset();
|
||||
m_encrypted = false;
|
||||
});
|
||||
|
||||
m_connection->OnRead(std::bind(&EQ::Net::ServertalkClient::ProcessData, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
||||
m_connection->Start();
|
||||
|
||||
SendHello();
|
||||
m_connecting = false;
|
||||
});
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkClient::ProcessData(EQ::Net::TCPConnection *c, const unsigned char *data, size_t length)
|
||||
{
|
||||
m_buffer.insert(m_buffer.end(), (const char*)data, (const char*)data + length);
|
||||
ProcessReadBuffer();
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkClient::SendHello()
|
||||
{
|
||||
EQ::Net::WritablePacket p;
|
||||
InternalSend(ServertalkClientHello, p);
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkClient::InternalSend(ServertalkPacketType type, EQ::Net::Packet &p)
|
||||
{
|
||||
if (!m_connection)
|
||||
return;
|
||||
|
||||
EQ::Net::WritablePacket out;
|
||||
out.PutUInt32(0, (uint32_t)p.Length());
|
||||
out.PutUInt8(4, (uint8_t)type);
|
||||
if (p.Length() > 0) {
|
||||
out.PutPacket(5, p);
|
||||
}
|
||||
|
||||
m_connection->Write((const char*)out.Data(), out.Length());
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkClient::ProcessReadBuffer()
|
||||
{
|
||||
size_t current = 0;
|
||||
size_t total = m_buffer.size();
|
||||
|
||||
while (current < total) {
|
||||
auto left = total - current;
|
||||
|
||||
/*
|
||||
//header:
|
||||
//uint32 length;
|
||||
//uint8 type;
|
||||
*/
|
||||
size_t length = 0;
|
||||
uint8_t type = 0;
|
||||
if (left < 5) {
|
||||
break;
|
||||
}
|
||||
|
||||
length = *(uint32_t*)&m_buffer[current];
|
||||
type = *(uint8_t*)&m_buffer[current + 4];
|
||||
|
||||
if (current + 5 + length < total) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
EQ::Net::WritablePacket p;
|
||||
switch (type) {
|
||||
case ServertalkServerHello:
|
||||
ProcessHello(p);
|
||||
break;
|
||||
case ServertalkMessage:
|
||||
ProcessMessage(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
EQ::Net::ReadOnlyPacket p(&m_buffer[current + 5], length);
|
||||
switch (type) {
|
||||
case ServertalkServerHello:
|
||||
ProcessHello(p);
|
||||
break;
|
||||
case ServertalkMessage:
|
||||
ProcessMessage(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
current += length + 5;
|
||||
}
|
||||
|
||||
if (current == total) {
|
||||
m_buffer.clear();
|
||||
}
|
||||
else {
|
||||
m_buffer.erase(m_buffer.begin(), m_buffer.begin() + current);
|
||||
}
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkClient::ProcessHello(EQ::Net::Packet &p)
|
||||
{
|
||||
#ifdef ENABLE_SECURITY
|
||||
memset(m_public_key_ours, 0, crypto_box_PUBLICKEYBYTES);
|
||||
memset(m_public_key_theirs, 0, crypto_box_PUBLICKEYBYTES);
|
||||
memset(m_private_key_ours, 0, crypto_box_SECRETKEYBYTES);
|
||||
memset(m_nonce_ours, 0, crypto_box_NONCEBYTES);
|
||||
memset(m_nonce_theirs, 0, crypto_box_NONCEBYTES);
|
||||
m_encrypted = false;
|
||||
|
||||
try {
|
||||
bool enc = p.GetInt8(0) == 1 ? true : false;
|
||||
|
||||
if (enc) {
|
||||
if (p.Length() == (1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES)) {
|
||||
memcpy(m_public_key_theirs, (char*)p.Data() + 1, crypto_box_PUBLICKEYBYTES);
|
||||
memcpy(m_nonce_theirs, (char*)p.Data() + 1 + crypto_box_PUBLICKEYBYTES, crypto_box_NONCEBYTES);
|
||||
m_encrypted = true;
|
||||
|
||||
SendHandshake();
|
||||
}
|
||||
else {
|
||||
Log.OutF(Logs::General, Logs::Error, "Could not process hello, size != {0}", 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES);
|
||||
}
|
||||
}
|
||||
else {
|
||||
SendHandshake();
|
||||
}
|
||||
}
|
||||
catch (std::exception &ex) {
|
||||
Log.OutF(Logs::General, Logs::Error, "Error parsing hello from server: {0}", ex.what());
|
||||
m_connection->Disconnect();
|
||||
}
|
||||
#else
|
||||
try {
|
||||
bool enc = p.GetInt8(0) == 1 ? true : false;
|
||||
|
||||
if (enc) {
|
||||
Log.OutF(Logs::General, Logs::Error, "Server requested encryption but we do not support encryption.");
|
||||
m_connection->Disconnect();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
SendHandshake();
|
||||
}
|
||||
}
|
||||
catch (std::exception &ex) {
|
||||
Log.OutF(Logs::General, Logs::Error, "Error parsing hello from server: {0}", ex.what());
|
||||
m_connection->Disconnect();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkClient::ProcessMessage(EQ::Net::Packet &p)
|
||||
{
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkClient::SendHandshake()
|
||||
{
|
||||
EQ::Net::WritablePacket handshake;
|
||||
#ifdef ENABLE_SECURITY
|
||||
if (m_encrypted) {
|
||||
crypto_box_keypair(m_public_key_ours, m_private_key_ours);
|
||||
randombytes_buf(m_nonce_ours, crypto_box_NONCEBYTES);
|
||||
|
||||
crypto_box_beforenm(m_shared_key, m_public_key_theirs, m_private_key_ours);
|
||||
|
||||
handshake.PutData(0, m_public_key_ours, crypto_box_PUBLICKEYBYTES);
|
||||
handshake.PutData(crypto_box_PUBLICKEYBYTES, m_nonce_ours, crypto_box_NONCEBYTES);
|
||||
|
||||
size_t cipher_length = m_identifier.length() + 1 + m_credentials.length() + 1 + crypto_secretbox_MACBYTES;
|
||||
size_t data_length = m_identifier.length() + 1 + m_credentials.length() + 1;
|
||||
unsigned char *signed_buffer = new unsigned char[cipher_length];
|
||||
unsigned char *data_buffer = new unsigned char[data_length];
|
||||
memset(data_buffer, 0, data_length);
|
||||
memcpy(&data_buffer[0], m_identifier.c_str(), m_identifier.length());
|
||||
memcpy(&data_buffer[1 + m_identifier.length()], m_credentials.c_str(), m_credentials.length());
|
||||
|
||||
crypto_box_easy_afternm(signed_buffer, data_buffer, data_length, m_nonce_ours, m_shared_key);
|
||||
|
||||
(*(uint64_t*)&m_nonce_ours[0])++;
|
||||
|
||||
handshake.PutData(crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES, signed_buffer, cipher_length);
|
||||
|
||||
Log.OutF(Logs::General, Logs::Debug, "Sending {1} bytes handshake:\n{0}", handshake.ToString(), handshake.Length());
|
||||
|
||||
delete[] signed_buffer;
|
||||
delete[] data_buffer;
|
||||
}
|
||||
else {
|
||||
handshake.PutString(0, m_identifier);
|
||||
}
|
||||
#else
|
||||
handshake.PutString(0, m_identifier);
|
||||
#endif
|
||||
InternalSend(ServertalkClientHandshake, handshake);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "tcp_connection.h"
|
||||
#include "../event/timer.h"
|
||||
#include "servertalk_common.h"
|
||||
#include "packet.h"
|
||||
#include <sodium.h>
|
||||
|
||||
namespace EQ
|
||||
{
|
||||
namespace Net
|
||||
{
|
||||
class ServertalkClient
|
||||
{
|
||||
public:
|
||||
ServertalkClient(const std::string &addr, int port, bool ipv6, const std::string &identifier, const std::string &credentials);
|
||||
~ServertalkClient();
|
||||
|
||||
private:
|
||||
void Connect();
|
||||
void ProcessData(EQ::Net::TCPConnection *c, const unsigned char *data, size_t length);
|
||||
void SendHello();
|
||||
void InternalSend(ServertalkPacketType type, EQ::Net::Packet &p);
|
||||
void ProcessReadBuffer();
|
||||
void ProcessHello(EQ::Net::Packet &p);
|
||||
void ProcessMessage(EQ::Net::Packet &p);
|
||||
void SendHandshake();
|
||||
|
||||
std::unique_ptr<EQ::Timer> m_timer;
|
||||
|
||||
std::string m_addr;
|
||||
std::string m_identifier;
|
||||
std::string m_credentials;
|
||||
bool m_connecting;
|
||||
int m_port;
|
||||
bool m_ipv6;
|
||||
bool m_encrypted;
|
||||
std::shared_ptr<EQ::Net::TCPConnection> m_connection;
|
||||
std::vector<char> m_buffer;
|
||||
|
||||
#ifdef ENABLE_SECURITY
|
||||
unsigned char m_public_key_ours[crypto_box_PUBLICKEYBYTES];
|
||||
unsigned char m_private_key_ours[crypto_box_SECRETKEYBYTES];
|
||||
unsigned char m_nonce_ours[crypto_box_NONCEBYTES];
|
||||
|
||||
unsigned char m_public_key_theirs[crypto_box_PUBLICKEYBYTES];
|
||||
unsigned char m_nonce_theirs[crypto_box_NONCEBYTES];
|
||||
|
||||
unsigned char m_shared_key[crypto_box_BEFORENMBYTES];
|
||||
#endif
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
namespace EQ
|
||||
{
|
||||
namespace Net
|
||||
{
|
||||
enum ServertalkPacketType
|
||||
{
|
||||
ServertalkClientHello = 1,
|
||||
ServertalkServerHello,
|
||||
ServertalkClientHandshake,
|
||||
ServertalkMessage,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#include "servertalk_server.h"
|
||||
|
||||
EQ::Net::ServertalkServer::ServertalkServer()
|
||||
{
|
||||
}
|
||||
|
||||
EQ::Net::ServertalkServer::~ServertalkServer()
|
||||
{
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkServer::Listen(const ServertalkServerOptions& opts)
|
||||
{
|
||||
m_encrypted = opts.encrypted;
|
||||
m_credentials = opts.credentials;
|
||||
m_server.reset(new EQ::Net::TCPServer());
|
||||
m_server->Listen(opts.port, opts.ipv6, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
|
||||
m_unident_connections.push_back(std::make_shared<ServertalkServerConnection>(connection, this, m_encrypted));
|
||||
});
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkServer::OnConnectionIdentified(const std::string &type, std::function<void(std::shared_ptr<ServertalkServerConnection>)> cb)
|
||||
{
|
||||
m_on_ident.insert(std::make_pair(type, cb));
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkServer::OnConnectionRemoved(const std::string &type, std::function<void(std::shared_ptr<ServertalkServerConnection>)> cb)
|
||||
{
|
||||
m_on_disc.insert(std::make_pair(type, cb));
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkServer::ConnectionDisconnected(ServertalkServerConnection *conn)
|
||||
{
|
||||
if (conn->GetIdentifier().empty()) {
|
||||
auto iter = m_unident_connections.begin();
|
||||
while (iter != m_unident_connections.end()) {
|
||||
if (conn == iter->get()) {
|
||||
m_unident_connections.erase(iter);
|
||||
return;
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto type = m_ident_connections.find(conn->GetIdentifier());
|
||||
if (type != m_ident_connections.end()) {
|
||||
auto iter = type->second.begin();
|
||||
while (iter != type->second.end()) {
|
||||
if (conn == iter->get()) {
|
||||
auto on_disc = m_on_disc.find(conn->GetIdentifier());
|
||||
if (on_disc != m_on_disc.end()) {
|
||||
on_disc->second(*iter);
|
||||
}
|
||||
type->second.erase(iter);
|
||||
return;
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkServer::ConnectionIdentified(ServertalkServerConnection *conn)
|
||||
{
|
||||
auto iter = m_unident_connections.begin();
|
||||
while (iter != m_unident_connections.end()) {
|
||||
if (conn == iter->get()) {
|
||||
auto on_ident = m_on_ident.find(conn->GetIdentifier());
|
||||
if (on_ident != m_on_ident.end()) {
|
||||
on_ident->second(*iter);
|
||||
}
|
||||
|
||||
if (m_ident_connections.count(conn->GetIdentifier()) > 0) {
|
||||
auto &vec = m_ident_connections[conn->GetIdentifier()];
|
||||
vec.push_back(*iter);
|
||||
}
|
||||
else {
|
||||
std::vector<std::shared_ptr<EQ::Net::ServertalkServerConnection>> vec;
|
||||
vec.push_back(*iter);
|
||||
m_ident_connections.insert(std::make_pair(conn->GetIdentifier(), vec));
|
||||
}
|
||||
|
||||
m_unident_connections.erase(iter);
|
||||
return;
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
bool EQ::Net::ServertalkServer::CheckCredentials(const std::string &credentials)
|
||||
{
|
||||
if (credentials.compare(m_credentials) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "tcp_server.h"
|
||||
#include "servertalk_server_connection.h"
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#ifdef ENABLE_SECURITY
|
||||
#include <sodium.h>
|
||||
#endif
|
||||
|
||||
namespace EQ
|
||||
{
|
||||
namespace Net
|
||||
{
|
||||
struct ServertalkServerOptions
|
||||
{
|
||||
int port;
|
||||
bool ipv6;
|
||||
bool encrypted;
|
||||
std::string credentials;
|
||||
|
||||
ServertalkServerOptions() {
|
||||
#ifdef ENABLE_SECURITY
|
||||
encrypted = true;
|
||||
#endif
|
||||
ipv6 = false;
|
||||
}
|
||||
};
|
||||
|
||||
class ServertalkServer
|
||||
{
|
||||
public:
|
||||
ServertalkServer();
|
||||
~ServertalkServer();
|
||||
|
||||
void Listen(const ServertalkServerOptions& opts);
|
||||
void OnConnectionIdentified(const std::string &type, std::function<void(std::shared_ptr<ServertalkServerConnection>)> cb);
|
||||
void OnConnectionRemoved(const std::string &type, std::function<void(std::shared_ptr<ServertalkServerConnection>)> cb);
|
||||
|
||||
private:
|
||||
void ConnectionDisconnected(ServertalkServerConnection *conn);
|
||||
void ConnectionIdentified(ServertalkServerConnection *conn);
|
||||
bool CheckCredentials(const std::string &credentials);
|
||||
|
||||
std::unique_ptr<EQ::Net::TCPServer> m_server;
|
||||
std::vector<std::shared_ptr<ServertalkServerConnection>> m_unident_connections;
|
||||
std::map<std::string, std::vector<std::shared_ptr<ServertalkServerConnection>>> m_ident_connections;
|
||||
|
||||
std::map<std::string, std::function<void(std::shared_ptr<ServertalkServerConnection>)>> m_on_ident;
|
||||
std::map<std::string, std::function<void(std::shared_ptr<ServertalkServerConnection>)>> m_on_disc;
|
||||
bool m_encrypted;
|
||||
std::string m_credentials;
|
||||
|
||||
friend class ServertalkServerConnection;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
#include "servertalk_server_connection.h"
|
||||
#include "servertalk_server.h"
|
||||
#include "../eqemu_logsys.h"
|
||||
|
||||
EQ::Net::ServertalkServerConnection::ServertalkServerConnection(std::shared_ptr<EQ::Net::TCPConnection> c, EQ::Net::ServertalkServer *parent, bool encrypted)
|
||||
{
|
||||
m_connection = c;
|
||||
m_parent = parent;
|
||||
m_encrypted = encrypted;
|
||||
m_connection->OnRead(std::bind(&ServertalkServerConnection::OnRead, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
||||
m_connection->OnDisconnect(std::bind(&ServertalkServerConnection::OnDisconnect, this, std::placeholders::_1));
|
||||
m_connection->Start();
|
||||
}
|
||||
|
||||
EQ::Net::ServertalkServerConnection::~ServertalkServerConnection()
|
||||
{
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkServerConnection::OnRead(TCPConnection *c, const unsigned char *data, size_t sz)
|
||||
{
|
||||
m_buffer.insert(m_buffer.end(), (const char*)data, (const char*)data + sz);
|
||||
ProcessReadBuffer();
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkServerConnection::ProcessReadBuffer()
|
||||
{
|
||||
size_t current = 0;
|
||||
size_t total = m_buffer.size();
|
||||
|
||||
while (current < total) {
|
||||
auto left = total - current;
|
||||
|
||||
/*
|
||||
//header:
|
||||
//uint32 length;
|
||||
//uint8 type;
|
||||
*/
|
||||
size_t length = 0;
|
||||
uint8_t type = 0;
|
||||
if (left < 5) {
|
||||
break;
|
||||
}
|
||||
|
||||
length = *(uint32_t*)&m_buffer[current];
|
||||
type = *(uint8_t*)&m_buffer[current + 4];
|
||||
|
||||
if (current + 5 + length < total) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
EQ::Net::WritablePacket p;
|
||||
switch (type) {
|
||||
case ServertalkClientHello:
|
||||
{
|
||||
SendHello();
|
||||
}
|
||||
break;
|
||||
case ServertalkClientHandshake:
|
||||
ProcessHandshake(p);
|
||||
break;
|
||||
case ServertalkMessage:
|
||||
ProcessMessage(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
EQ::Net::ReadOnlyPacket p(&m_buffer[current + 5], length);
|
||||
switch (type) {
|
||||
case ServertalkClientHello:
|
||||
{
|
||||
SendHello();
|
||||
}
|
||||
break;
|
||||
case ServertalkClientHandshake:
|
||||
ProcessHandshake(p);
|
||||
break;
|
||||
case ServertalkMessage:
|
||||
ProcessMessage(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
current += length + 5;
|
||||
}
|
||||
|
||||
if (current == total) {
|
||||
m_buffer.clear();
|
||||
}
|
||||
else {
|
||||
m_buffer.erase(m_buffer.begin(), m_buffer.begin() + current);
|
||||
}
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkServerConnection::OnDisconnect(TCPConnection *c)
|
||||
{
|
||||
m_parent->ConnectionDisconnected(this);
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkServerConnection::SendHello()
|
||||
{
|
||||
EQ::Net::WritablePacket hello;
|
||||
|
||||
#ifdef ENABLE_SECURITY
|
||||
memset(m_public_key_ours, 0, crypto_box_PUBLICKEYBYTES);
|
||||
memset(m_public_key_theirs, 0, crypto_box_PUBLICKEYBYTES);
|
||||
memset(m_private_key_ours, 0, crypto_box_SECRETKEYBYTES);
|
||||
memset(m_nonce_ours, 0, crypto_box_NONCEBYTES);
|
||||
memset(m_nonce_theirs, 0, crypto_box_NONCEBYTES);
|
||||
|
||||
if (m_encrypted) {
|
||||
hello.PutInt8(0, 1);
|
||||
|
||||
crypto_box_keypair(m_public_key_ours, m_private_key_ours);
|
||||
randombytes_buf(m_nonce_ours, crypto_box_NONCEBYTES);
|
||||
|
||||
hello.PutData(1, m_public_key_ours, crypto_box_PUBLICKEYBYTES);
|
||||
hello.PutData(1 + crypto_box_PUBLICKEYBYTES, m_nonce_ours, crypto_box_NONCEBYTES);
|
||||
}
|
||||
else {
|
||||
hello.PutInt8(0, 0);
|
||||
}
|
||||
#else
|
||||
hello.PutInt8(0, 0);
|
||||
#endif
|
||||
|
||||
InternalSend(ServertalkServerHello, hello);
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkServerConnection::InternalSend(ServertalkPacketType type, EQ::Net::Packet &p)
|
||||
{
|
||||
if (!m_connection)
|
||||
return;
|
||||
|
||||
EQ::Net::WritablePacket out;
|
||||
out.PutUInt32(0, (uint32_t)p.Length());
|
||||
out.PutUInt8(4, (uint8_t)type);
|
||||
if (p.Length() > 0) {
|
||||
out.PutPacket(5, p);
|
||||
}
|
||||
|
||||
m_connection->Write((const char*)out.Data(), out.Length());
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkServerConnection::ProcessHandshake(EQ::Net::Packet &p)
|
||||
{
|
||||
#ifdef ENABLE_SECURITY
|
||||
if (m_encrypted) {
|
||||
try {
|
||||
if (p.Length() > (crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES)) {
|
||||
memcpy(m_public_key_theirs, (char*)p.Data(), crypto_box_PUBLICKEYBYTES);
|
||||
memcpy(m_nonce_theirs, (char*)p.Data() + crypto_box_PUBLICKEYBYTES, crypto_box_NONCEBYTES);
|
||||
|
||||
crypto_box_beforenm(m_shared_key, m_public_key_theirs, m_private_key_ours);
|
||||
|
||||
size_t cipher_len = p.Length() - crypto_box_PUBLICKEYBYTES - crypto_box_NONCEBYTES;
|
||||
size_t message_len = cipher_len - crypto_secretbox_MACBYTES;
|
||||
unsigned char *decrypted_text = new unsigned char[message_len];
|
||||
if (crypto_box_open_easy_afternm(decrypted_text, (unsigned char*)p.Data() + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES, cipher_len, m_nonce_theirs, m_shared_key))
|
||||
{
|
||||
Log.OutF(Logs::General, Logs::Error, "Error decrypting handshake from client, dropping connection.");
|
||||
m_connection->Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
m_identifier = (const char*)decrypted_text;
|
||||
std::string credentials = (const char*)decrypted_text + (m_identifier.length() + 1);
|
||||
|
||||
if (!m_parent->CheckCredentials(credentials)) {
|
||||
Log.OutF(Logs::General, Logs::Error, "Got incoming connection with invalid credentials during handshake, dropping connection.");
|
||||
m_connection->Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
m_parent->ConnectionIdentified(this);
|
||||
|
||||
(*(uint64_t*)&m_nonce_theirs[0])++;
|
||||
delete[] decrypted_text;
|
||||
}
|
||||
}
|
||||
catch (std::exception &ex) {
|
||||
Log.OutF(Logs::General, Logs::Error, "Error parsing handshake from client: {0}", ex.what());
|
||||
m_connection->Disconnect();
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_identifier.assign((char*)p.Data(), p.Length());
|
||||
}
|
||||
#else
|
||||
m_identifier.assign((char*)p.Data(), p.Length());
|
||||
#endif
|
||||
}
|
||||
|
||||
void EQ::Net::ServertalkServerConnection::ProcessMessage(EQ::Net::Packet &p)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "tcp_connection.h"
|
||||
#include "servertalk_common.h"
|
||||
#include "packet.h"
|
||||
#include <vector>
|
||||
#include <sodium.h>
|
||||
|
||||
namespace EQ
|
||||
{
|
||||
namespace Net
|
||||
{
|
||||
class ServertalkServer;
|
||||
class ServertalkServerConnection
|
||||
{
|
||||
public:
|
||||
ServertalkServerConnection(std::shared_ptr<EQ::Net::TCPConnection> c, ServertalkServer *parent, bool encrypted);
|
||||
~ServertalkServerConnection();
|
||||
|
||||
std::string GetIdentifier() const {
|
||||
return m_identifier;
|
||||
}
|
||||
private:
|
||||
void OnRead(TCPConnection* c, const unsigned char* data, size_t sz);
|
||||
void ProcessReadBuffer();
|
||||
void OnDisconnect(TCPConnection* c);
|
||||
void SendHello();
|
||||
void InternalSend(ServertalkPacketType type, EQ::Net::Packet &p);
|
||||
void ProcessHandshake(EQ::Net::Packet &p);
|
||||
void ProcessMessage(EQ::Net::Packet &p);
|
||||
|
||||
std::shared_ptr<EQ::Net::TCPConnection> m_connection;
|
||||
ServertalkServer *m_parent;
|
||||
|
||||
std::vector<char> m_buffer;
|
||||
std::string m_identifier;
|
||||
|
||||
bool m_encrypted;
|
||||
#ifdef ENABLE_SECURITY
|
||||
unsigned char m_public_key_ours[crypto_box_PUBLICKEYBYTES];
|
||||
unsigned char m_private_key_ours[crypto_box_SECRETKEYBYTES];
|
||||
unsigned char m_nonce_ours[crypto_box_NONCEBYTES];
|
||||
|
||||
unsigned char m_public_key_theirs[crypto_box_PUBLICKEYBYTES];
|
||||
unsigned char m_nonce_theirs[crypto_box_NONCEBYTES];
|
||||
|
||||
unsigned char m_shared_key[crypto_box_BEFORENMBYTES];
|
||||
#endif
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -107,12 +107,17 @@ void EQ::Net::TCPConnection::OnDisconnect(std::function<void(TCPConnection*)> cb
|
||||
void EQ::Net::TCPConnection::Disconnect()
|
||||
{
|
||||
if (m_socket) {
|
||||
uv_close((uv_handle_t*)m_socket, on_close_handle);
|
||||
m_socket->data = this;
|
||||
uv_close((uv_handle_t*)m_socket, [](uv_handle_t* handle) {
|
||||
TCPConnection *connection = (TCPConnection*)handle->data;
|
||||
|
||||
if (connection->m_on_disconnect_cb) {
|
||||
connection->m_on_disconnect_cb(connection);
|
||||
}
|
||||
|
||||
delete handle;
|
||||
});
|
||||
m_socket = nullptr;
|
||||
|
||||
if (m_on_disconnect_cb) {
|
||||
m_on_disconnect_cb(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user