diff --git a/CMakeLists.txt b/CMakeLists.txt index a3d38e1bd..1dce60a9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -259,6 +259,7 @@ ENDIF(EQEMU_ENABLE_BOTS) OPTION(EQEMU_BUILD_SERVER "Build the game server." ON) OPTION(EQEMU_BUILD_LOGIN "Build the login server." OFF) OPTION(EQEMU_BUILD_HC "Build the headless client." OFF) +OPTION(EQEMU_BUILD_LP "Build the login lookup proxy." OFF) OPTION(EQEMU_BUILD_TESTS "Build utility tests." OFF) OPTION(EQEMU_BUILD_PERL "Build Perl parser." ON) OPTION(EQEMU_BUILD_LUA "Build Lua parser." ON) @@ -360,10 +361,10 @@ INCLUDE_DIRECTORIES(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/libs/format") INCLUDE_DIRECTORIES(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/libs/recast/detour/include") INCLUDE_DIRECTORIES(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/libs/recast/recast/include") -IF(EQEMU_BUILD_SERVER OR EQEMU_BUILD_LOGIN OR EQEMU_BUILD_TESTS OR EQEMU_BUILD_HC) +IF(EQEMU_BUILD_SERVER OR EQEMU_BUILD_LOGIN OR EQEMU_BUILD_TESTS OR EQEMU_BUILD_HC OR EQEMU_BUILD_LP) ADD_SUBDIRECTORY(common) ADD_SUBDIRECTORY(libs) -ENDIF(EQEMU_BUILD_SERVER OR EQEMU_BUILD_LOGIN OR EQEMU_BUILD_TESTS OR EQEMU_BUILD_HC) +ENDIF(EQEMU_BUILD_SERVER OR EQEMU_BUILD_LOGIN OR EQEMU_BUILD_TESTS OR EQEMU_BUILD_HC OR EQEMU_BUILD_LP) IF(EQEMU_BUILD_SERVER) ADD_SUBDIRECTORY(shared_memory) ADD_SUBDIRECTORY(world) @@ -380,6 +381,10 @@ IF(EQEMU_BUILD_HC) ADD_SUBDIRECTORY(hc) ENDIF(EQEMU_BUILD_HC) +IF(EQEMU_BUILD_LP) + ADD_SUBDIRECTORY(lp) +ENDIF(EQEMU_BUILD_LP) + IF(EQEMU_BUILD_TESTS) ADD_SUBDIRECTORY(tests) ENDIF(EQEMU_BUILD_TESTS) diff --git a/lp/CMakeLists.txt b/lp/CMakeLists.txt new file mode 100644 index 000000000..cf648a54b --- /dev/null +++ b/lp/CMakeLists.txt @@ -0,0 +1,20 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.8) + +SET(lp_sources + main.cpp +) + +SET(hc_headers +) + +FIND_PACKAGE(OpenSSL REQUIRED) + +INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) + +ADD_EXECUTABLE(lp ${lp_sources} ${lp_headers}) + +INSTALL(TARGETS lp RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) + +TARGET_LINK_LIBRARIES(lp ${SERVER_LIBS} ${OPENSSL_LIBRARIES}) + +SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) diff --git a/lp/main.cpp b/lp/main.cpp new file mode 100644 index 000000000..b8b8eab45 --- /dev/null +++ b/lp/main.cpp @@ -0,0 +1,176 @@ +#include +#include +#include +#include + +#include "../common/eqemu_logsys.h" +#include "../common/json_config.h" +#include "../common/net/daybreak_connection.h" +#include "../common/net/dns.h" +#include "../common/event/event_loop.h" +#include "../common/event/timer.h" + +EQEmuLogSys LogSys; + +const char* eqcrypt_block(const char *buffer_in, size_t buffer_in_sz, char* buffer_out, bool enc) { + DES_key_schedule k; + DES_cblock v; + + memset(&k, 0, sizeof(DES_key_schedule)); + memset(&v, 0, sizeof(DES_cblock)); + + if (!enc && buffer_in_sz && buffer_in_sz % 8 != 0) { + return nullptr; + } + + DES_ncbc_encrypt((const unsigned char*)buffer_in, (unsigned char*)buffer_out, (long)buffer_in_sz, &k, &v, enc); + return buffer_out; +} + +bool connected = false; +bool running = true; +std::unique_ptr m_login_connection_manager; +std::shared_ptr m_login_connection; +std::string m_user; +std::string m_pass; + +void LoginSendSessionReady() +{ + EQ::Net::DynamicPacket p; + p.PutUInt16(0, 1); //OP_SessionReady + p.PutUInt32(2, 2); + + m_login_connection->QueuePacket(p); +} + +void LoginSendLogin() +{ + size_t buffer_len = m_user.length() + m_pass.length() + 2; + std::unique_ptr buffer(new char[buffer_len]); + + strcpy(&buffer[0], m_user.c_str()); + strcpy(&buffer[m_user.length() + 1], m_pass.c_str()); + + size_t encrypted_len = buffer_len; + + if (encrypted_len % 8 > 0) { + encrypted_len = ((encrypted_len / 8) + 1) * 8; + } + + EQ::Net::DynamicPacket p; + p.Resize(12 + encrypted_len); + p.PutUInt16(0, 2); //OP_Login + p.PutUInt32(2, 3); + + eqcrypt_block(&buffer[0], buffer_len, (char*)p.Data() + 12, true); + + m_login_connection->QueuePacket(p); +} + +void LoginProcessLoginResponse(const EQ::Net::Packet & p) +{ + auto encrypt_size = p.Length() - 12; + if (encrypt_size % 8 > 0) { + encrypt_size = (encrypt_size / 8) * 8; + } + + std::unique_ptr decrypted(new char[encrypt_size]); + + eqcrypt_block((char*)p.Data() + 12, encrypt_size, &decrypted[0], false); + + EQ::Net::StaticPacket sp(&decrypted[0], encrypt_size); + auto response_error = sp.GetUInt16(1); + + if (response_error > 101) { + printf("Error logging in response code: %u\n", response_error); + m_login_connection->Close(); + } + else { + auto m_dbid = sp.GetUInt32(8); + + printf("Logged in successfully with dbid %u", m_dbid); + m_login_connection->Close(); + } +} + +void LoginOnNewConnection(std::shared_ptr connection) +{ + m_login_connection = connection; +} + +void LoginOnStatusChange(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) +{ + if (to == EQ::Net::StatusConnected) { + connected = true; + LoginSendSessionReady(); + } + + if (to == EQ::Net::StatusDisconnected) { + running = false; + } +} + +void LoginOnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p) { + auto opcode = p.GetUInt16(0); + switch (opcode) { + case 0x0017: //OP_ChatMessage + LoginSendLogin(); + break; + case 0x0018: + LoginProcessLoginResponse(p); + break; + } +} + +int main(int argc, char **argv) { + LogSys.LoadLogSettingsDefaults(); + + if (argc != 3) { + printf("Usage: lp [username] [password]\n"); + return -1; + } + + m_user = argv[1]; + m_pass = argv[2]; + + std::string address; + int port; + EQ::Timer t(5000U, false, [&](EQ::Timer *timer) { + if (!connected) { + printf("Connection failure.\n"); + running = false; + } + }); + + try { + auto config = EQ::JsonConfigFile::Load("eqhost.json"); + address = config.GetVariableString("login", "address", "login.eqemulator.net"); + port = config.GetVariableInt("login", "port", 5999); + } + catch (std::exception) { + printf("Unable to parse eqhost.json\n"); + return -1; + } + + EQ::Net::DNSLookup(address, port, false, [&](const std::string &addr) { + if (addr.empty()) { + printf("DNS Error for %s\n", address.c_str()); + running = false; + return; + } + + m_login_connection_manager.reset(new EQ::Net::DaybreakConnectionManager()); + + m_login_connection_manager->OnNewConnection(std::bind(&LoginOnNewConnection, std::placeholders::_1)); + m_login_connection_manager->OnConnectionStateChange(std::bind(&LoginOnStatusChange, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + m_login_connection_manager->OnPacketRecv(std::bind(&LoginOnPacketRecv, std::placeholders::_1, std::placeholders::_2)); + + m_login_connection_manager->Connect(addr, port); + }); + + while (running) { + EQ::EventLoop::Get().Process(); + } + + return 0; +} \ No newline at end of file