diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 38d2eab..15041ee 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -4,7 +4,7 @@ set(COMMON_SOURCES bnettime.cpp bnettime.h bn_type.cpp bn_type.h bot_protocol.h conf.cpp conf.h d2char_checksum.cpp d2char_checksum.h d2char_file.h d2cs_bnetd_protocol.h d2cs_d2dbs_ladder.h d2cs_d2gs_character.h - d2cs_d2gs_protocol.h d2cs_protocol.h d2game_protocol.h elist.h + d2cs_d2gs_protocol.h d2cs_protocol.h d2dbs_d2gs_protocol.h d2game_protocol.h elist.h eventlog.cpp eventlog.h fdwatch.cpp fdwatch_epoll.cpp fdwatch_epoll.h fdwatch.h fdwatch_kqueue.cpp fdwatch_kqueue.h fdwatch_poll.cpp fdwatch_poll.h fdwatch_select.cpp fdwatch_select.h fdwbackend.cpp diff --git a/src/common/d2dbs_d2gs_protocol.h b/src/common/d2dbs_d2gs_protocol.h new file mode 100644 index 0000000..f803268 --- /dev/null +++ b/src/common/d2dbs_d2gs_protocol.h @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2001 faster (lqx@cic.tsinghua.edu.cn) + * Copyright (C) 2001 sousou (liupeng.cs@263.net) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef INCLUDED_D2DBS_D2GS_PROTOCOL_H +#define INCLUDED_D2DBS_D2GS_PROTOCOL_H + +#include "common/bn_type.h" + + +namespace pvpgn +{ + + typedef struct + { + bn_short size; + bn_short type; + bn_int seqno; + } t_d2dbs_d2gs_header; + + typedef struct + { + t_d2dbs_d2gs_header h; + } t_d2dbs_d2gs_generic; + + typedef struct + { + bn_byte cclass; + } t_d2gs_d2dbs_connect; + + + /******************************************************/ +#define D2GS_D2DBS_SAVE_DATA_REQUEST 0x30 + typedef struct + { + t_d2dbs_d2gs_header h; + bn_short datatype; + bn_short datalen; + /* AccountName */ + /* CharName */ + /* RealmName */ + /* data */ + } t_d2gs_d2dbs_save_data_request; +#define D2GS_DATA_CHARSAVE 0x01 +#define D2GS_DATA_PORTRAIT 0x02 + /******************************************************/ + + + /******************************************************/ +#define D2DBS_D2GS_SAVE_DATA_REPLY 0x30 + typedef struct + { + t_d2dbs_d2gs_header h; + bn_int result; + bn_short datatype; + /* CharName */ + } t_d2dbs_d2gs_save_data_reply; +#define D2DBS_SAVE_DATA_SUCCESS 0 +#define D2DBS_SAVE_DATA_FAILED 1 + /******************************************************/ + + + /******************************************************/ +#define D2GS_D2DBS_GET_DATA_REQUEST 0x31 + typedef struct + { + t_d2dbs_d2gs_header h; + bn_short datatype; + /* AccountName */ + /* CharName */ + /* RealmName */ + } t_d2gs_d2dbs_get_data_request; + /******************************************************/ + + + /******************************************************/ +#define D2DBS_D2GS_GET_DATA_REPLY 0x31 + typedef struct + { + t_d2dbs_d2gs_header h; + bn_int result; + bn_int charcreatetime; + bn_int allowladder; + bn_short datatype; + bn_short datalen; + /* CharName */ + /* data */ + } t_d2dbs_d2gs_get_data_reply; + +#define D2DBS_GET_DATA_SUCCESS 0 +#define D2DBS_GET_DATA_FAILED 1 +#define D2DBS_GET_DATA_CHARLOCKED 2 + /******************************************************/ + + + /******************************************************/ +#define D2GS_D2DBS_UPDATE_LADDER 0x32 + typedef struct + { + t_d2dbs_d2gs_header h; + bn_int charlevel; + bn_int charexplow; + bn_int charexphigh; + bn_short charclass; + bn_short charstatus; + /* CharName */ + /* RealmName */ + } t_d2gs_d2dbs_update_ladder; + /******************************************************/ + + + /******************************************************/ +#define D2GS_D2DBS_CHAR_LOCK 0x33 + typedef struct + { + t_d2dbs_d2gs_header h; + bn_int lockstatus; + /* AccountName */ + /* CharName */ + /* RealmName */ + } t_d2gs_d2dbs_char_lock; + /******************************************************/ + + + /******************************************************/ +#define D2DBS_D2GS_ECHOREQUEST 0x34 + typedef struct + { + t_d2dbs_d2gs_header h; + } t_d2dbs_d2gs_echorequest; + /******************************************************/ + + + /******************************************************/ +#define D2GS_D2DBS_ECHOREPLY 0x34 + typedef struct + { + t_d2dbs_d2gs_header h; + } t_d2gs_d2dbs_echoreply; + /******************************************************/ + +} + +#endif diff --git a/src/common/init_protocol.h b/src/common/init_protocol.h index 58caaec..ecd1a4e 100644 --- a/src/common/init_protocol.h +++ b/src/common/init_protocol.h @@ -64,6 +64,7 @@ namespace pvpgn #define CLIENT_INITCONN_CLASS_D2CS 0x01 #define CLIENT_INITCONN_CLASS_D2GS 0x64 #define CLIENT_INITCONN_CLASS_D2CS_BNETD 0x65 +#define CLIENT_INITCONN_CLASS_D2GS_D2DBS 0x65 #define CLIENT_INITCONN_CLASS_LOCALMACHINE 0x98 /* local computer connecting via 127.0.0.1 */ #endif diff --git a/src/common/packet.cpp b/src/common/packet.cpp index 584d4ac..6004721 100644 --- a/src/common/packet.cpp +++ b/src/common/packet.cpp @@ -45,6 +45,7 @@ namespace pvpgn pclass != packet_class_d2cs && pclass != packet_class_d2gs && pclass != packet_class_d2cs_bnetd && + pclass != packet_class_d2dbs_d2gs && pclass != packet_class_w3route && pclass != packet_class_wolgameres) { @@ -130,6 +131,8 @@ namespace pvpgn return packet_class_d2gs; case packet_class_d2cs_bnetd: return packet_class_d2cs_bnetd; + case packet_class_d2dbs_d2gs: + return packet_class_d2dbs_d2gs; case packet_class_w3route: return packet_class_w3route; case packet_class_wolgameres: @@ -171,6 +174,8 @@ namespace pvpgn return "d2cs_bnetd"; case packet_class_d2cs: return "d2cs"; + case packet_class_d2dbs_d2gs: + return "d2dbs_d2gs"; case packet_class_w3route: return "w3route"; case packet_class_wolgameres: @@ -205,6 +210,7 @@ namespace pvpgn pclass != packet_class_d2cs && pclass != packet_class_d2gs && pclass != packet_class_d2cs_bnetd && + pclass != packet_class_d2dbs_d2gs && pclass != packet_class_w3route && pclass != packet_class_wolgameres) { @@ -287,6 +293,14 @@ namespace pvpgn } return bn_byte_get(packet->u.d2cs_client.h.type); + case packet_class_d2dbs_d2gs: + if (packet_get_size(packet) < sizeof(t_d2dbs_d2gs_header)) + { + eventlog(eventlog_level_error, __FUNCTION__, "d2dbs_d2gs packet is shorter than header (len={})", packet_get_size(packet)); + return 0; + } + return bn_byte_get(packet->u.d2dbs_d2gs.h.type); + case packet_class_w3route: if (packet_get_size(packet) < sizeof(t_w3route_header)) { @@ -557,6 +571,9 @@ namespace pvpgn case packet_class_d2cs_bnetd: return "D2CS_BNETD"; + case packet_class_d2dbs_d2gs: + return "D2DBS_D2GS"; + case packet_class_w3route: if (packet_get_size(packet) < sizeof(t_w3route_header)) { @@ -804,6 +821,9 @@ namespace pvpgn case packet_class_d2cs_bnetd: return "D2CS_BNETD"; + case packet_class_d2dbs_d2gs: + return "D2DBS_D2GS"; + case packet_class_w3route: if (packet_get_size(packet) < sizeof(t_w3route_header)) { @@ -936,6 +956,15 @@ namespace pvpgn bn_byte_set(&packet->u.d2cs_client.h.type, type); return 0; + case packet_class_d2dbs_d2gs: + if (packet_get_size(packet) < sizeof(t_d2dbs_d2gs_header)) + { + eventlog(eventlog_level_error, __FUNCTION__, "d2dbs_d2gs packet is shorter than header (len={})", packet_get_size(packet)); + return -1; + } + bn_short_set(&packet->u.d2dbs_d2gs.h.type, type); + return 0; + case packet_class_w3route: if (packet_get_size(packet) < sizeof(t_w3route_header)) { @@ -1000,6 +1029,9 @@ namespace pvpgn case packet_class_d2cs: size = (unsigned int)bn_short_get(packet->u.d2cs_client.h.size); break; + case packet_class_d2dbs_d2gs: + size = (unsigned int)bn_short_get(packet->u.d2dbs_d2gs.h.size); + break; case packet_class_w3route: size = (unsigned int)bn_short_get(packet->u.w3route.h.size); break; @@ -1093,6 +1125,9 @@ namespace pvpgn case packet_class_d2cs_bnetd: bn_short_set(&packet->u.d2cs_bnetd.h.size, size); return 0; + case packet_class_d2dbs_d2gs: + bn_short_set(&packet->u.d2dbs_d2gs.h.size, size); + return 0; case packet_class_w3route: if (size != 0 && size < sizeof(t_w3route_header)) { @@ -1140,6 +1175,8 @@ namespace pvpgn return sizeof(t_d2cs_d2gs_header); case packet_class_d2cs_bnetd: return sizeof(t_d2cs_bnetd_header); + case packet_class_d2dbs_d2gs: + return sizeof(t_d2dbs_d2gs_header); case packet_class_w3route: return sizeof(t_w3route_header); case packet_class_wolgameres: diff --git a/src/common/packet.h b/src/common/packet.h index e88a20a..b58bf4d 100644 --- a/src/common/packet.h +++ b/src/common/packet.h @@ -31,6 +31,7 @@ # include "d2cs_protocol.h" # include "d2cs_d2gs_protocol.h" # include "d2cs_bnetd_protocol.h" +# include "d2dbs_d2gs_protocol.h" # include "wol_gameres_protocol.h" #else # define JUST_NEED_TYPES @@ -45,6 +46,7 @@ # include "d2cs_protocol.h" # include "d2cs_d2gs_protocol.h" # include "d2cs_bnetd_protocol.h" +# include "d2dbs_d2gs_protocol.h" # include "wol_gameres_protocol.h" # undef JUST_NEED_TYPES #endif @@ -64,6 +66,7 @@ namespace pvpgn packet_class_d2gs, packet_class_d2cs, packet_class_d2cs_bnetd, + packet_class_d2dbs_d2gs, packet_class_w3route, packet_class_wolgameres } t_packet_class; @@ -286,6 +289,16 @@ namespace pvpgn t_client_d2cs_convertcharreq client_d2cs_convertcharreq; t_d2cs_client_convertcharreply d2cs_client_convertcharreply; + t_d2dbs_d2gs_generic d2dbs_d2gs; + t_d2gs_d2dbs_save_data_request d2gs_d2dbs_save_data_request; + t_d2dbs_d2gs_save_data_reply d2dbs_d2gs_save_data_reply; + t_d2gs_d2dbs_get_data_request d2gs_d2dbs_get_data_request; + t_d2dbs_d2gs_get_data_reply d2dbs_d2gs_get_data_reply; + t_d2gs_d2dbs_update_ladder d2gs_d2dbs_update_ladder; + t_d2gs_d2dbs_char_lock d2gs_d2dbs_char_lock; + t_d2dbs_d2gs_echorequest d2dbs_d2gs_echorequest; + t_d2gs_d2dbs_echoreply d2gs_d2dbs_echoreply; + t_client_friendslistreq client_friendslistreq; t_server_friendslistreply server_friendslistreply; t_client_friendinforeq client_friendinforeq; diff --git a/src/d2dbs/CMakeLists.txt b/src/d2dbs/CMakeLists.txt index 2556180..d1230bc 100644 --- a/src/d2dbs/CMakeLists.txt +++ b/src/d2dbs/CMakeLists.txt @@ -1,7 +1,7 @@ set(D2DBS_SOURCES - charlock.cpp charlock.h cmdline.cpp cmdline.h d2ladder.cpp d2ladder.h - dbsdupecheck.cpp dbsdupecheck.h dbserver.cpp dbserver.h dbspacket.cpp - dbspacket.h handle_signal.cpp handle_signal.h main.cpp prefs.cpp + charlock.cpp charlock.h cmdline.cpp cmdline.h connection.cpp connection.h d2ladder.cpp d2ladder.h + dbsdupecheck.cpp dbsdupecheck.h dbserver.cpp dbserver.h handle_d2gs.cpp handle_d2gs.h handle_init.cpp + handle_init.h handle_signal.cpp handle_signal.h main.cpp pgsid.cpp pgsid.h prefs.cpp prefs.h setup.h version.h ../win32/d2dbs_winmain.cpp ../win32/d2dbs_resource.h ../win32/d2dbs_resource.rc) @@ -11,6 +11,12 @@ else(WITH_WIN32_GUI) add_executable(d2dbs ${D2DBS_SOURCES}) endif(WITH_WIN32_GUI) +target_include_directories(d2dbs + PRIVATE + $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include/> + $<INSTALL_INTERFACE:include/> +) + target_link_libraries(d2dbs PRIVATE common compat fmt::fmt win32 ${NETWORK_LIBRARIES}) install(TARGETS d2dbs DESTINATION ${SBINDIR}) if(WIN32 AND MSVC) diff --git a/src/d2dbs/connection.cpp b/src/d2dbs/connection.cpp new file mode 100644 index 0000000..3c359e4 --- /dev/null +++ b/src/d2dbs/connection.cpp @@ -0,0 +1,454 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include "common/setup_before.h" +#include "setup.h" +#define CONNECTION_INTERNAL_ACCESS +#include "connection.h" + +#include <algorithm> +#include <cstring> +#include <list> + +#include "compat/psock.h" + +#include "common/addr.h" +#include "common/eventlog.h" +#include "common/list.h" +#include "common/network.h" +#include "common/xalloc.h" + +#ifdef HAVE_ARPA_INET_H +# include <arpa/inet.h> +#endif +#ifdef HAVE_WS2TCPIP_H +# include <Ws2tcpip.h> +#endif + +#include "charlock.h" +#include "pgsid.h" +#include "prefs.h" + +#include "common/setup_after.h" + + +namespace pvpgn +{ + + namespace d2dbs + { + + static std::list<t_d2dbs_connection*> conn_head = {}; + static std::list<t_d2dbs_connection*> conn_dead = {}; + + + t_d2dbs_connection* conn_create(int sock, unsigned int real_local_addr, unsigned short real_local_port, unsigned int local_addr, unsigned short local_port, unsigned int addr, unsigned short port) + { + t_d2dbs_connection* conn = (t_d2dbs_connection*)xmalloc(sizeof(t_d2dbs_connection)); + if (!conn) + { + return nullptr; + } + std::memset(conn, 0, sizeof(t_d2dbs_connection)); + + conn->sd = sock; + conn->ipaddr = addr; + conn->fdw_idx = -1; + conn->major = 0; + conn->minor = 0; + conn->type = 0; + conn->stats = 0; + conn->serverid = pgsid_get_id(addr); + conn->verified = 0; + { + std::memset(conn->serverip, 0, sizeof(conn->serverip)); + struct in_addr in = {}; + in.s_addr = htonl(addr); + char addrstr[INET_ADDRSTRLEN] = {}; + inet_ntop(AF_INET, &(in), addrstr, sizeof(addrstr)); + std::strncpy((char*)conn->serverip, addrstr, sizeof(conn->serverip) - 1); + } + conn->last_active = std::time(nullptr); + conn->cclass = conn_class_init; + conn->state = conn_state_initial; + conn->queues.outqueue = nullptr; + conn->queues.outsize = 0; + conn->queues.outsizep = 0; + conn->queues.inqueue = nullptr; + conn->queues.insize = 0; + + conn_head.push_back(conn); + + eventlog(eventlog_level_info, __FUNCTION__, "[{}] created connection serverip={} serverid={}", conn->sd, conn->serverip, conn->serverid); + + return conn; + } + + // Caller is responsible for removing connection from desired list + static void conn_destroy(t_d2dbs_connection* c) + { + if (c == nullptr) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return; + } + + eventlog(eventlog_level_info, __FUNCTION__, "[{}] unlock all characters on gs {}({})", c->sd, c->serverip, c->serverid); + eventlog_step(prefs_get_logfile_gs(), eventlog_level_info, __FUNCTION__, "unlock all characters on gs %s(%d)", c->serverip, c->serverid); + eventlog_step(prefs_get_logfile_gs(), eventlog_level_info, __FUNCTION__, "close connection to gs on socket %d", c->sd); + cl_unlock_all_char_by_gsid(c->serverid); + + // make sure the connection is closed + if (c->sd != -1) + { + // -1 means that the socket was already closed by conn_close() + fdwatch_del_fd(c->fdw_idx); + psock_shutdown(c->sd, PSOCK_SHUT_RDWR); + psock_close(c->sd); + } + + // clear out the packet queues + if (c->queues.inqueue) + { + packet_del_ref(c->queues.inqueue); + } + + queue_clear(&c->queues.outqueue); + + eventlog(eventlog_level_info, __FUNCTION__, "[{}] closed {} connection", c->sd, conn_class_get_str(conn_get_class(c))); + + xfree(c); + } + + + const char* conn_class_get_str(t_conn_class cclass) + { + switch (cclass) + { + case conn_class_init: + return "init"; + case conn_class_d2gs: + return "d2gs"; + default: + return "UNKNOWN"; + } + } + + t_conn_class conn_get_class(t_d2dbs_connection* c) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return conn_class_empty; + } + + return c->cclass; + } + + void conn_set_class(t_d2dbs_connection* c, t_conn_class cclass) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return; + } + + c->cclass = cclass; + } + + + const char* conn_state_get_str(t_conn_state state) + { + switch (state) + { + case conn_state_empty: + return "empty"; + case conn_state_initial: + return "initial"; + case conn_state_loggedin: + return "loggedin"; + case conn_state_destroy: + return "destroy"; + default: + return "UNKNOWN"; + } + } + + t_conn_state conn_get_state(t_d2dbs_connection* c) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return conn_state_empty; + } + + return c->state; + } + + void conn_set_state(t_d2dbs_connection* c, t_conn_state state) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return; + } + + // special case for destroying connections, add them to conn_dead list + if (state == conn_state_destroy && c->state != conn_state_destroy) + { + conn_dead.push_back(c); + } + else if (state != conn_state_destroy && c->state == conn_state_destroy) + { + auto it = std::find(conn_dead.begin(), conn_dead.end(), c); + if (it != conn_dead.end()) + { + conn_dead.erase(it); + } + else + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] could not find connection in conn_dead", c->sd); + } + } + + c->state = state; + } + + + void conn_clear_outqueue(t_d2dbs_connection* c) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return; + } + + queue_clear(&c->queues.outqueue); + } + + t_packet* conn_peek_outqueue(t_d2dbs_connection* c) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return nullptr; + } + + if (c->queues.outqueue) + { + return queue_peek_packet((t_queue const* const*)&c->queues.outqueue); + } + else + { + return nullptr; + } + } + + t_packet* conn_pull_outqueue(t_d2dbs_connection* c) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return nullptr; + } + + if (c->queues.outsizep) + { + if (!(--c->queues.outsizep)) fdwatch_update_fd(c->fdw_idx, fdwatch_type_read); + return queue_pull_packet((t_queue**)&c->queues.outqueue); + } + + return nullptr; + } + + + t_packet* conn_get_in_queue(t_d2dbs_connection* c) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return nullptr; + } + + return c->queues.inqueue; + } + + void conn_put_in_queue(t_d2dbs_connection* c, t_packet* packet) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return; + } + + c->queues.inqueue = packet; + } + + + unsigned int conn_get_in_size(t_d2dbs_connection* c) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return 0; + } + + return c->queues.insize; + } + + void conn_set_in_size(t_d2dbs_connection* c, unsigned int size) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return; + } + + c->queues.insize = size; + } + + + unsigned int conn_get_out_size(t_d2dbs_connection* c) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return 0; + } + + return c->queues.outsize; + } + + + void conn_set_out_size(t_d2dbs_connection* c, unsigned int size) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return; + } + + c->queues.outsize = size; + } + + + int conn_push_outqueue(t_d2dbs_connection* c, t_packet* packet) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return -1; + } + + if (!packet) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL packet"); + return -1; + } + + queue_push_packet(&c->queues.outqueue, packet); + if (!c->queues.outsizep++) + { + fdwatch_update_fd(c->fdw_idx, fdwatch_type_read | fdwatch_type_write); + } + + return 0; + } + + + int conn_add_fdwatch(t_d2dbs_connection* c, fdwatch_handler handle) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return -1; + } + + c->fdw_idx = fdwatch_add_fd(c->sd, fdwatch_type_read, handle, c); + return c->fdw_idx; + } + + + void conn_close_read(t_d2dbs_connection* c) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return; + } + + conn_set_state(c, conn_state_destroy); + + /* only if we still got output packets remove the read availability + * from fdwatch, we are NOT allowed to remove all availability or + * remove it completely from fdwatch while handling read, also + * if the connection has no output packets is ok to leave it + * in read availability check cause it will be closed immediately + * in connlist_reap() anyway + */ + if (conn_peek_outqueue(c)) + { + fdwatch_update_fd(c->fdw_idx, fdwatch_type_write); + } + } + + + + const std::list<t_d2dbs_connection*>& connlist() + { + return conn_head; + } + + void connlist_reap() + { + for (auto it = conn_dead.begin(); it != conn_dead.end(); ) + { + t_d2dbs_connection* c = *it; + if (!conn_peek_outqueue(c)) + { + conn_destroy(c); // also removes from fdwatch + + conn_head.remove(c); + it = conn_dead.erase(it); + } + else + { + ++it; + } + } + } + + void connlist_destroy() + { + for (auto it = conn_head.begin(); it != conn_head.end(); ) + { + t_d2dbs_connection* c = *it; + if (!conn_peek_outqueue(c)) + { + conn_destroy(c); // also removes from fdwatch + + conn_dead.remove(c); + it = conn_head.erase(it); + } + else + { + ++it; + } + } + } + + } + +} \ No newline at end of file diff --git a/src/d2dbs/connection.h b/src/d2dbs/connection.h new file mode 100644 index 0000000..9d98306 --- /dev/null +++ b/src/d2dbs/connection.h @@ -0,0 +1,114 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef INCLUDED_CONNECTION_H +#define INCLUDED_CONNECTION_H + +#include <ctime> +#include <list> + +#include "common/queue.h" +#include "common/hashtable.h" +#include "common/packet.h" +#include "common/fdwatch.h" + + +namespace pvpgn +{ + + namespace d2dbs + { + + enum t_conn_class + { + conn_class_empty, + conn_class_init, + conn_class_d2gs + }; + + enum t_conn_state + { + conn_state_empty, + conn_state_initial, + conn_state_loggedin, + conn_state_destroy + }; + + typedef struct + { + int sd; // tcp_sock + unsigned int ipaddr; // tcp_addr + int fdw_idx; + unsigned char major; + unsigned char minor; + unsigned char type; + unsigned char stats; + unsigned int serverid; + unsigned int verified; + unsigned char serverip[16]; + std::time_t last_active; +#ifdef CONNECTION_INTERNAL_ACCESS + t_conn_class cclass; + t_conn_state state; + struct + { + t_queue* outqueue; /* packets waiting to be sent */ + unsigned int outsize; /* amount sent from the current output packet */ + unsigned int outsizep; + t_packet* inqueue; /* packet waiting to be processed */ + unsigned int insize; /* amount received into the current input packet */ + } queues; /* network queues and related data */ +#endif + } t_d2dbs_connection; + + + t_d2dbs_connection* conn_create(int sock, unsigned int real_local_addr, unsigned short real_local_port, unsigned int local_addr, unsigned short local_port, unsigned int addr, unsigned short port); + + const char* conn_class_get_str(t_conn_class cclass); + t_conn_class conn_get_class(t_d2dbs_connection* c); + void conn_set_class(t_d2dbs_connection* c, t_conn_class cclass); + + const char* conn_state_get_str(t_conn_state state); + t_conn_state conn_get_state(t_d2dbs_connection* c); + void conn_set_state(t_d2dbs_connection* c, t_conn_state state); + + void conn_clear_outqueue(t_d2dbs_connection* c); + t_packet* conn_peek_outqueue(t_d2dbs_connection* c); + t_packet* conn_pull_outqueue(t_d2dbs_connection* c); + + t_packet* conn_get_in_queue(t_d2dbs_connection* c); + void conn_put_in_queue(t_d2dbs_connection* c, t_packet* packet); + + unsigned int conn_get_in_size(t_d2dbs_connection* c); + void conn_set_in_size(t_d2dbs_connection* c, unsigned int size); + + unsigned int conn_get_out_size(t_d2dbs_connection* c); + void conn_set_out_size(t_d2dbs_connection* c, unsigned int size); + + int conn_push_outqueue(t_d2dbs_connection* c, t_packet* packet); + + int conn_add_fdwatch(t_d2dbs_connection* c, fdwatch_handler handle); + + void conn_close_read(t_d2dbs_connection* c); + + + const std::list<t_d2dbs_connection*>& connlist(); + void connlist_reap(); + void connlist_destroy(); + } + +} + +#endif diff --git a/src/d2dbs/dbserver.cpp b/src/d2dbs/dbserver.cpp index 0bc1c84..70bc8b2 100644 --- a/src/d2dbs/dbserver.cpp +++ b/src/d2dbs/dbserver.cpp @@ -16,6 +16,7 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "common/setup_before.h" +#define SERVER_INTERNAL_ACCESS #include "setup.h" #include "dbserver.h" @@ -28,15 +29,24 @@ #include "compat/psock.h" #include "compat/strerror.h" -#include "common/eventlog.h" + #include "common/addr.h" -#include "common/xalloc.h" +#include "common/eventlog.h" +#include "common/d2dbs_d2gs_protocol.h" +#include "common/fdwatch.h" #include "common/network.h" -#include "d2ladder.h" -#include "prefs.h" +#include "common/packet.h" +#include "common/xalloc.h" + +#include "connection.h" #include "charlock.h" -#include "dbspacket.h" +#include "d2ladder.h" +#include "handle_d2gs.h" +#include "handle_init.h" #include "handle_signal.h" +#include "pgsid.h" +#include "prefs.h" +#include "version.h" #ifdef HAVE_ARPA_INET_H # include <arpa/inet.h> @@ -57,10 +67,7 @@ namespace pvpgn namespace d2dbs { - static int dbs_packet_gs_id = 0; - static t_preset_d2gsid *preset_d2gsid_head = NULL; - t_list * dbs_server_connection_list = NULL; - int dbs_server_listen_socket = -1; + static std::time_t curr_exittime; /* dbs_server_main * The module's driver function -- we just call other functions and @@ -68,210 +75,573 @@ namespace pvpgn */ static int dbs_handle_timed_events(void); - static void dbs_on_exit(void); - int dbs_server_init(void); - void dbs_server_loop(int ListeningSocket); - int dbs_server_setup_fdsets(fd_set * pReadFDs, fd_set * pWriteFDs, - fd_set * pExceptFDs, int ListeningSocket); - bool dbs_server_read_data(t_d2dbs_connection* conn); - bool dbs_server_write_data(t_d2dbs_connection* conn); - int dbs_server_list_add_socket(int sd, unsigned int ipaddr); - static int setsockopt_keepalive(int sock); - static unsigned int get_preset_d2gsid(unsigned int ipaddr); + static int handle_accept(void* data, t_fdwatch_type rw); + static int handle_tcp(void* data, t_fdwatch_type rw); + + static void _server_mainloop(t_addrlist* laddrs); + static void dbs_check_timeout(); + static void dbs_keepalive(); - int dbs_server_main(void) + static char const* laddr_type_get_str(t_laddr_type laddr_type) { - eventlog(eventlog_level_info, __FUNCTION__, "establishing the listener..."); - dbs_server_listen_socket = dbs_server_init(); - if (dbs_server_listen_socket < 0) { - eventlog(eventlog_level_error, __FUNCTION__, "dbs_server_init error "); - return 3; + switch (laddr_type) + { + case laddr_type_d2gs: + return "d2gs"; + default: + return "UNKNOWN"; } - eventlog(eventlog_level_info, __FUNCTION__, "waiting for connections..."); - dbs_server_loop(dbs_server_listen_socket); - dbs_on_exit(); + } + + + static int sd_accept(t_addr const* curr_laddr, t_laddr_info const* laddr_info, int ssocket) + { + char tempa[32]; + int csocket; + struct sockaddr_in caddr; + psock_t_socklen caddr_len; + unsigned int raddr; + unsigned short rport; + + if (!addr_get_addr_str(curr_laddr, tempa, sizeof(tempa))) + std::strcpy(tempa, "x.x.x.x:x"); + + /* accept the connection */ + std::memset(&caddr, 0, sizeof(caddr)); /* not sure if this is needed... modern systems are ok anyway */ + caddr_len = sizeof(caddr); + if ((csocket = psock_accept(ssocket, (struct sockaddr*)&caddr, &caddr_len)) < 0) + { + /* BSD, POSIX error for aborted connections, SYSV often uses EAGAIN or EPROTO */ + if ( +#ifdef PSOCK_EWOULDBLOCK + psock_errno() == PSOCK_EWOULDBLOCK || +#endif +#ifdef PSOCK_ECONNABORTED + psock_errno() == PSOCK_ECONNABORTED || +#endif +#ifdef PSOCK_EPROTO + psock_errno() == PSOCK_EPROTO || +#endif + 0) + eventlog(eventlog_level_error, __FUNCTION__, "client aborted connection on {} (psock_accept: {})", tempa, pstrerror(psock_errno())); + else /* EAGAIN can mean out of resources _or_ connection aborted :( */ + if ( +#ifdef PSOCK_EINTR + psock_errno() != PSOCK_EINTR && +#endif + 1) + eventlog(eventlog_level_error, __FUNCTION__, "could not accept new connection on {} (psock_accept: {})", tempa, pstrerror(psock_errno())); + return -1; + } + + /* dont accept new connections while shutting down */ + if (curr_exittime) + { + psock_shutdown(csocket, PSOCK_SHUT_RDWR); + psock_close(csocket); + return 0; + } + + char addrstr[INET_ADDRSTRLEN] = {}; + inet_ntop(AF_INET, &(caddr.sin_addr), addrstr, sizeof(addrstr)); + + eventlog(eventlog_level_info, __FUNCTION__, "[{}] accepted connection from {} on {}", csocket, addr_num_to_addr_str(ntohl(caddr.sin_addr.s_addr), ntohs(caddr.sin_port)), tempa); + + { + int optval = 1; + psock_t_socklen optlen = sizeof(optval); + if (psock_setsockopt(csocket, PSOCK_SOL_SOCKET, PSOCK_SO_KEEPALIVE, &optval, optlen)) + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] could not set socket option SO_KEEPALIVE (psock_setsockopt: {})", csocket, pstrerror(psock_errno())); + } + else + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] set KEEPALIVE option", csocket); + } + } + + { + struct sockaddr_in rsaddr; + psock_t_socklen rlen; + + std::memset(&rsaddr, 0, sizeof(rsaddr)); /* not sure if this is needed... modern systems are ok anyway */ + rlen = sizeof(rsaddr); + if (psock_getsockname(csocket, (struct sockaddr*)&rsaddr, &rlen) < 0) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] unable to determine real local port (psock_getsockname: {})", csocket, pstrerror(psock_errno())); + /* not a fatal error */ + raddr = addr_get_ip(curr_laddr); + rport = addr_get_port(curr_laddr); + } + else + { + if (rsaddr.sin_family != PSOCK_AF_INET) + { + eventlog(eventlog_level_error, __FUNCTION__, "local address returned with bad address family {}", (int)rsaddr.sin_family); + /* not a fatal error */ + raddr = addr_get_ip(curr_laddr); + rport = addr_get_port(curr_laddr); + } + else + { + raddr = ntohl(rsaddr.sin_addr.s_addr); + rport = ntohs(rsaddr.sin_port); + } + } + } + + if (psock_ctl(csocket, PSOCK_NONBLOCK) < 0) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] could not set TCP socket to non-blocking mode (closing connection) (psock_ctl: {})", csocket, pstrerror(psock_errno())); + psock_close(csocket); + return -1; + } + + { + t_d2dbs_connection* c; + if (!(c = conn_create(csocket, raddr, rport, addr_get_ip(curr_laddr), addr_get_port(curr_laddr), ntohl(caddr.sin_addr.s_addr), ntohs(caddr.sin_port)))) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] unable to create new connection (closing connection)", csocket); + psock_close(csocket); + return -1; + } + + if (conn_add_fdwatch(c, handle_tcp) < 0) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] unable to add socket to fdwatch pool (max connections?)", csocket); + conn_set_state(c, conn_state_destroy); + return -1; + } + + eventlog(eventlog_level_debug, __FUNCTION__, "[{}] client connected to a {} listening address", csocket, laddr_type_get_str(laddr_info->type)); + } + return 0; } - /* dbs_server_init - * Sets up a listener on the given interface and port, returning the - * listening socket if successful; if not, returns -1. - */ - /* FIXME: No it doesn't! pcAddress is not ever referenced in this - * function. - * CreepLord: Fixed much better way (will accept dns hostnames) - */ - int dbs_server_init(void) - { - int sd; - struct sockaddr_in sinInterface; - int val; - t_addr * servaddr; - dbs_server_connection_list = list_create(); + static int sd_tcpinput(t_d2dbs_connection* c) + { + t_packet* packet = nullptr; + auto csocket = c->sd; + auto currsize = conn_get_in_size(c); + + if (!conn_get_in_queue(c)) + { + switch (conn_get_class(c)) + { + case conn_class_init: + if (!(packet = packet_create(packet_class_init))) + { + eventlog(eventlog_level_error, __FUNCTION__, "could not allocate init packet for input"); + return -1; + } + break; + case conn_class_d2gs: + if (!(packet = packet_create(packet_class_d2dbs_d2gs))) + { + eventlog(eventlog_level_error, __FUNCTION__, "could not allocate d2dbs_d2gs packet for input"); + return -1; + } + break; + default: + eventlog(eventlog_level_error, __FUNCTION__, "[{}] connection has bad class (closing connection)", c->sd); + conn_close_read(c); + return -2; + } + + conn_put_in_queue(c, packet); + currsize = 0; + } + + packet = conn_get_in_queue(c); + switch (net_recv_packet(csocket, packet, &currsize)) + { + case -1: + eventlog(eventlog_level_debug, __FUNCTION__, "[{}] read returned -1 (closing connection)", csocket); + conn_close_read(c); + return -2; + + case 0: /* still working on it */ + /* eventlog(eventlog_level_debug,__FUNCTION__,"[{}] still reading \"{}\" packet ({} of {} bytes so far)",conn_get_socket(c),packet_get_class_str(packet),conn_get_in_size(c),packet_get_size(packet)); */ + conn_set_in_size(c, currsize); + break; + + case 1: /* done reading */ + { + conn_put_in_queue(c, nullptr); + + int ret; + switch (conn_get_class(c)) + { + case conn_class_init: + ret = handle_init_packet(c, packet); + break; + case conn_class_d2gs: + ret = handle_d2gs_packet(c, packet); + break; + default: + ret = -1; + } + + packet_del_ref(packet); + if (ret < 0) + { + packet_del_ref(packet); + conn_close_read(c); + return -2; + } + + conn_set_in_size(c, 0); + } + } + + return 0; + } + + + static int sd_tcpoutput(t_d2dbs_connection* c) + { + unsigned int totsize = 0; + auto csocket = c->sd; + + for (;;) + { + auto currsize = conn_get_out_size(c); + + t_packet* packet = conn_peek_outqueue(c); + if (packet == nullptr) + { + return -2; + } + + switch (net_send_packet(csocket, packet, &currsize)) /* avoid warning */ + { + case -1: + /* marking connection as "destroyed", memory will be freed later */ + conn_clear_outqueue(c); + conn_set_state(c, conn_state_destroy); + return -2; + + case 0: /* still working on it */ + conn_set_out_size(c, currsize); + return 0; /* bail out */ + + case 1: /* done sending */ + packet = conn_pull_outqueue(c); + packet_del_ref(packet); + conn_set_out_size(c, 0); + + /* stop if out of packets or EWOULDBLOCK) */ + if (!conn_peek_outqueue(c)) + { + return 0; + } + + totsize += currsize; + + break; + } + } + + /* not reached */ + } + + + static int handle_accept(void* data, t_fdwatch_type rw) + { + t_laddr_info* laddr_info = (t_laddr_info*)addr_get_data((t_addr*)data).p; + + return sd_accept((t_addr*)data, laddr_info, laddr_info->ssocket); + } + + + static int handle_tcp(void* data, t_fdwatch_type rw) + { + switch (rw) + { + case fdwatch_type_read: return sd_tcpinput((t_d2dbs_connection*)data); + case fdwatch_type_write: return sd_tcpoutput((t_d2dbs_connection*)data); + default: + return -1; + } + } + + + static int _setup_add_addrs(t_addrlist** pladdrs, const char* str, unsigned int defaddr, unsigned short defport, t_laddr_type type) + { + t_addr* curr_laddr; + t_addr_data laddr_data; + t_laddr_info* laddr_info; + t_elem const* acurr; + + if (*pladdrs == NULL) + { + *pladdrs = addrlist_create(str, defaddr, defport); + if (*pladdrs == NULL) return -1; + } + else if (addrlist_append(*pladdrs, str, defaddr, defport)) return -1; + + /* Mark all these laddrs for being classic Battle.net service */ + LIST_TRAVERSE_CONST(*pladdrs, acurr) + { + curr_laddr = (t_addr*)elem_get_data(acurr); + if (addr_get_data(curr_laddr).p) + continue; + laddr_info = (t_laddr_info*)xmalloc(sizeof(t_laddr_info)); + laddr_info->ssocket = -1; + laddr_info->type = type; + laddr_data.p = laddr_info; + if (addr_set_data(curr_laddr, laddr_data) < 0) + { + eventlog(eventlog_level_error, __FUNCTION__, "could not set address data"); + if (laddr_info->ssocket != -1) + { + psock_close(laddr_info->ssocket); + laddr_info->ssocket = -1; + } + return -1; + } + } + + return 0; + } + + + static int _set_reuseaddr(int sock) + { + int val = 1; + + return psock_setsockopt(sock, PSOCK_SOL_SOCKET, PSOCK_SO_REUSEADDR, &val, (psock_t_socklen)sizeof(val)); + } + + + static int _bind_socket(int sock, unsigned addr, short port) + { + struct sockaddr_in saddr; + + std::memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = PSOCK_AF_INET; + saddr.sin_port = htons(port); + saddr.sin_addr.s_addr = htonl(addr); + return psock_bind(sock, (struct sockaddr*)&saddr, (psock_t_socklen)sizeof(saddr)); + } + + + static int _setup_listensock(t_addrlist* laddrs) + { + t_addr* curr_laddr; + t_laddr_info* laddr_info; + t_elem const* acurr; + char tempa[32]; + int fidx; + + LIST_TRAVERSE_CONST(laddrs, acurr) + { + curr_laddr = (t_addr*)elem_get_data(acurr); + if (!(laddr_info = (t_laddr_info*)addr_get_data(curr_laddr).p)) + { + eventlog(eventlog_level_error, __FUNCTION__, "NULL address info"); + goto err; + } + + if (!addr_get_addr_str(curr_laddr, tempa, sizeof(tempa))) + std::strcpy(tempa, "x.x.x.x:x"); + + laddr_info->ssocket = psock_socket(PSOCK_PF_INET, PSOCK_SOCK_STREAM, PSOCK_IPPROTO_TCP); + if (laddr_info->ssocket < 0) + { + eventlog(eventlog_level_error, __FUNCTION__, "could not create a {} listening socket (psock_socket: {})", laddr_type_get_str(laddr_info->type), pstrerror(psock_errno())); + goto err; + } + + if (_set_reuseaddr(laddr_info->ssocket) < 0) + eventlog(eventlog_level_error, __FUNCTION__, "could not set option SO_REUSEADDR on {} socket {} (psock_setsockopt: {})", laddr_type_get_str(laddr_info->type), laddr_info->ssocket, pstrerror(psock_errno())); + /* not a fatal error... */ + + if (_bind_socket(laddr_info->ssocket, addr_get_ip(curr_laddr), addr_get_port(curr_laddr)) < 0) + { + eventlog(eventlog_level_error, __FUNCTION__, "could not bind {} socket to address {} TCP (psock_bind: {})", laddr_type_get_str(laddr_info->type), tempa, pstrerror(psock_errno())); + goto errsock; + } + + /* tell socket to listen for connections */ + if (psock_listen(laddr_info->ssocket, LISTEN_QUEUE) < 0) + { + eventlog(eventlog_level_error, __FUNCTION__, "could not set {} socket {} to listen (psock_listen: {})", laddr_type_get_str(laddr_info->type), laddr_info->ssocket, pstrerror(psock_errno())); + goto errsock; + } + + if (psock_ctl(laddr_info->ssocket, PSOCK_NONBLOCK) < 0) + eventlog(eventlog_level_error, __FUNCTION__, "could not set {} TCP listen socket to non-blocking mode (psock_ctl: {})", laddr_type_get_str(laddr_info->type), pstrerror(psock_errno())); + + /* index not stored persisently because we dont need to refer to it later */ + fidx = fdwatch_add_fd(laddr_info->ssocket, fdwatch_type_read, handle_accept, curr_laddr); + if (fidx < 0) + { + eventlog(eventlog_level_error, __FUNCTION__, "could not add listening socket {} to fdwatch pool (max sockets?)", laddr_info->ssocket); + goto errsock; + } + + eventlog(eventlog_level_info, __FUNCTION__, "listening for {} connections on {} TCP", laddr_type_get_str(laddr_info->type), tempa); + } + + return 0; + + errsock: + psock_close(laddr_info->ssocket); + laddr_info->ssocket = -1; + + err: + return -1; + } + + + static void _server_mainloop(t_addrlist* laddrs) + { + while (1) + { + +#ifdef WIN32 + if (g_ServiceStatus < 0 && kbhit() && getch() == 'q') + d2dbs_signal_quit_wrapper(); + if (g_ServiceStatus == 0) d2dbs_signal_quit_wrapper(); + + while (g_ServiceStatus == 2) Sleep(1000); +#endif + + if (d2dbs_handle_signal() < 0) + { + eventlog(eventlog_level_info, __FUNCTION__, "the server is shutting down ({} connections left)", connlist().size()); + break; + } + + dbs_handle_timed_events(); + + + /* no need to populate the fdwatch structures as they are populated on the fly + * by sd_accept, conn_push_outqueue, conn_pull_outqueue, conn_destory */ + + /* find which sockets need servicing */ + switch (fdwatch(D2DBS_POLL_INTERVAL)) + { + case -1: /* error */ + if ( +#ifdef PSOCK_EINTR + psock_errno() != PSOCK_EINTR && +#endif + 1) + eventlog(eventlog_level_error, __FUNCTION__, "fdwatch() failed (errno: {})", pstrerror(psock_errno())); + case 0: /* timeout... no sockets need checking */ + continue; + } + + /* cycle through the ready sockets and handle them */ + fdwatch_handle(); + + /* reap dead connections */ + connlist_reap(); + + } + } + + + static void _shutdown_addrs(t_addrlist* laddrs) + { + t_addr* curr_laddr; + t_laddr_info* laddr_info; + t_elem const* acurr; + + LIST_TRAVERSE_CONST(laddrs, acurr) + { + curr_laddr = (t_addr*)elem_get_data(acurr); + + if ((laddr_info = (t_laddr_info*)addr_get_data(curr_laddr).p)) + { + if (laddr_info->ssocket != -1) + { + psock_close(laddr_info->ssocket); + } + + xfree(laddr_info); + } + } + + addrlist_destroy(laddrs); + } + + + int pre_server_startup() + { + eventlog(eventlog_level_info, __FUNCTION__, D2DBS_VERSION); if (d2dbs_d2ladder_init() == -1) { - eventlog(eventlog_level_error, __FUNCTION__, "d2ladder_init() failed"); - return -1; + eventlog(eventlog_level_error, __FUNCTION__, "d2dbs_d2ladder_init() failed"); + return STATUS_D2LADDER_FAILURE; } if (cl_init(DEFAULT_HASHTBL_LEN, DEFAULT_GS_MAX) == -1) { eventlog(eventlog_level_error, __FUNCTION__, "cl_init() failed"); - return -1; + return STATUS_CHARLOCK_FAILURE; } - if (psock_init() < 0) + if (fdwatch_init(D2DBS_FDWATCH_MAX_CONNECTIONS)) { - eventlog(eventlog_level_error, __FUNCTION__, "psock_init() failed"); - return -1; + eventlog(eventlog_level_error, __FUNCTION__, "error initilizing fdwatch"); + return STATUS_FDWATCH_FAILURE; } - sd = psock_socket(PSOCK_PF_INET, PSOCK_SOCK_STREAM, PSOCK_IPPROTO_TCP); - if (sd == -1) - { - eventlog(eventlog_level_error, __FUNCTION__, "psock_socket() failed : {}", pstrerror(psock_errno())); - return -1; - } - - val = 1; - if (psock_setsockopt(sd, PSOCK_SOL_SOCKET, PSOCK_SO_REUSEADDR, &val, sizeof(val)) < 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "psock_setsockopt() failed : {}", pstrerror(psock_errno())); - } - - if (!(servaddr = addr_create_str(d2dbs_prefs_get_servaddrs(), INADDR_ANY, DEFAULT_LISTEN_PORT))) - { - eventlog(eventlog_level_error, __FUNCTION__, "could not get servaddr"); - return -1; - } - - sinInterface.sin_family = PSOCK_AF_INET; - sinInterface.sin_addr.s_addr = htonl(addr_get_ip(servaddr)); - sinInterface.sin_port = htons(addr_get_port(servaddr)); - std::memset(sinInterface.sin_zero, 0, sizeof(sinInterface.sin_zero)); - if (psock_bind(sd, (struct sockaddr*)&sinInterface, (psock_t_socklen)sizeof(struct sockaddr_in)) < 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "psock_bind() failed : {}", pstrerror(psock_errno())); - addr_destroy(servaddr); - return -1; - } - if (psock_listen(sd, LISTEN_QUEUE) < 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "psock_listen() failed : {}", pstrerror(psock_errno())); - addr_destroy(servaddr); - return -1; - } - addr_destroy(servaddr); - return sd; + return 0; } - /* dbs_server_setup_fdsets - * Set up the three FD sets used with select() with the sockets in the - * connection list. Also add one for the listener socket, if we have - * one. - */ - - int dbs_server_setup_fdsets(t_psock_fd_set * pReadFDs, t_psock_fd_set * pWriteFDs, t_psock_fd_set * pExceptFDs, int lsocket) + bool server_process() { - t_elem const * elem; - t_d2dbs_connection* it; - int highest_fd; - - PSOCK_FD_ZERO(pReadFDs); - PSOCK_FD_ZERO(pWriteFDs); - PSOCK_FD_ZERO(pExceptFDs); /* FIXME: don't check these... remove this code */ - /* Add the listener socket to the read and except FD sets, if there is one. */ - if (lsocket >= 0) { - PSOCK_FD_SET(lsocket, pReadFDs); - PSOCK_FD_SET(lsocket, pExceptFDs); - } - highest_fd = lsocket; - - LIST_TRAVERSE_CONST(dbs_server_connection_list, elem) + t_addrlist* laddrs = nullptr; + if (_setup_add_addrs(&laddrs, d2dbs_prefs_get_servaddrs(), INADDR_ANY, DEFAULT_LISTEN_PORT, laddr_type_d2gs)) { - if (!(it = (t_d2dbs_connection*)elem_get_data(elem))) continue; - if (it->nCharsInReadBuffer < (kBufferSize - kMaxPacketLength)) { - /* There's space in the read buffer, so pay attention to incoming data. */ - PSOCK_FD_SET(it->sd, pReadFDs); - } - if (it->nCharsInWriteBuffer > 0) { - PSOCK_FD_SET(it->sd, pWriteFDs); - } - PSOCK_FD_SET(it->sd, pExceptFDs); - if (highest_fd < it->sd) highest_fd = it->sd; + eventlog(eventlog_level_error, __FUNCTION__, "could not create {} server address list from \"{}\"", laddr_type_get_str(laddr_type_d2gs), d2dbs_prefs_get_servaddrs()); + return false; } - return highest_fd; - } - /* dbs_server_read_data - * Data came in on a client socket, so read it into the buffer. Returns - * false on failure, or when the client closes its half of the - * connection. (EAGAIN doesn't count as a failure.) - */ - bool dbs_server_read_data(t_d2dbs_connection* conn) - { - int nBytes; + if (_setup_listensock(laddrs)) + { + _shutdown_addrs(laddrs); + return false; + } - nBytes = net_recv(conn->sd, conn->ReadBuf + conn->nCharsInReadBuffer, - kBufferSize - conn->nCharsInReadBuffer); + _server_mainloop(laddrs); + + // cleanup for server shutdown + connlist_destroy(); // equivalent to pvpgn::bnetd::_shutdown_conns() + _shutdown_addrs(laddrs); - if (nBytes < 0) return false; - conn->nCharsInReadBuffer += nBytes; return true; } - /* dbs_server_write_data - * The connection is writable, so send any pending data. Returns - * false on failure. (EAGAIN doesn't count as a failure.) - */ - bool dbs_server_write_data(t_d2dbs_connection* conn) + void post_server_shutdown(int status) { - int nBytes; - - nBytes = net_send(conn->sd, conn->WriteBuf, - conn->nCharsInWriteBuffer > kMaxPacketLength ? kMaxPacketLength : conn->nCharsInWriteBuffer); - - if (nBytes < 0) return false; - - conn->nCharsInWriteBuffer -= nBytes; - if (conn->nCharsInWriteBuffer) - std::memmove(conn->WriteBuf, conn->WriteBuf + nBytes, conn->nCharsInWriteBuffer); - - return true; + switch (status) + { + case 0: + fdwatch_close(); + case STATUS_FDWATCH_FAILURE: + cl_destroy(); + case STATUS_CHARLOCK_FAILURE: + d2dbs_d2ladder_destroy(); + case STATUS_D2LADDER_FAILURE: + pgsid_destroy(); + break; + default: + eventlog(eventlog_level_error, __FUNCTION__, "got bad status \"{}\" during shutdown", status); + } } - int dbs_server_list_add_socket(int sd, unsigned int ipaddr) - { - t_d2dbs_connection *it; - struct in_addr in; - - it = (t_d2dbs_connection*)xmalloc(sizeof(t_d2dbs_connection)); - std::memset(it, 0, sizeof(t_d2dbs_connection)); - it->sd = sd; - it->ipaddr = ipaddr; - it->major = 0; - it->minor = 0; - it->type = 0; - it->stats = 0; - it->verified = 0; - it->serverid = get_preset_d2gsid(ipaddr); - it->last_active = std::time(NULL); - it->nCharsInReadBuffer = 0; - it->nCharsInWriteBuffer = 0; - list_append_data(dbs_server_connection_list, it); - in.s_addr = htonl(ipaddr); - char addrstr[INET_ADDRSTRLEN] = { 0 }; - inet_ntop(AF_INET, &(in), addrstr, sizeof(addrstr)); - std::strncpy((char*)it->serverip, addrstr, sizeof(it->serverip) - 1); - - return 1; - } static int dbs_handle_timed_events(void) { @@ -296,208 +666,52 @@ namespace pvpgn return 0; } - void dbs_server_loop(int lsocket) + + static void dbs_check_timeout() { - struct sockaddr_in sinRemote; - int sd; - fd_set ReadFDs, WriteFDs, ExceptFDs; - t_elem * elem; - t_d2dbs_connection* it; - bool bOK; - const char* pcErrorType; - struct timeval tv; - int highest_fd; - psock_t_socklen nAddrSize = sizeof(sinRemote); + std::time_t now = std::time(nullptr); + unsigned int timeout = d2dbs_prefs_get_idletime(); - while (1) { - -#ifdef WIN32 - if (g_ServiceStatus < 0 && kbhit() && getch() == 'q') - d2dbs_signal_quit_wrapper(); - if (g_ServiceStatus == 0) d2dbs_signal_quit_wrapper(); - - while (g_ServiceStatus == 2) Sleep(1000); -#endif - - if (d2dbs_handle_signal() < 0) break; - - dbs_handle_timed_events(); - highest_fd = dbs_server_setup_fdsets(&ReadFDs, &WriteFDs, &ExceptFDs, lsocket); - - tv.tv_sec = 0; - tv.tv_usec = SELECT_TIME_OUT; - switch (psock_select(highest_fd + 1, &ReadFDs, &WriteFDs, &ExceptFDs, &tv)) { - case -1: - eventlog(eventlog_level_error, __FUNCTION__, "psock_select() failed : {}", pstrerror(psock_errno())); - continue; - case 0: - continue; - default: - break; - } - - if (PSOCK_FD_ISSET(lsocket, &ReadFDs)) { - sd = psock_accept(lsocket, (struct sockaddr*)&sinRemote, &nAddrSize); - if (sd == -1) { - eventlog(eventlog_level_error, __FUNCTION__, "psock_accept() failed : {}", pstrerror(psock_errno())); - return; - } - - char addrstr[INET_ADDRSTRLEN] = { 0 }; - inet_ntop(AF_INET, &(sinRemote.sin_addr), addrstr, sizeof(addrstr)); - eventlog(eventlog_level_info, __FUNCTION__, "accepted connection from {}:{} , socket {} .", - addrstr, ntohs(sinRemote.sin_port), sd); - eventlog_step(prefs_get_logfile_gs(), eventlog_level_info, __FUNCTION__, "accepted connection from %s:%d , socket %d .", - addrstr, ntohs(sinRemote.sin_port), sd); - setsockopt_keepalive(sd); - dbs_server_list_add_socket(sd, ntohl(sinRemote.sin_addr.s_addr)); - if (psock_ctl(sd, PSOCK_NONBLOCK) < 0) { - eventlog(eventlog_level_error, __FUNCTION__, "could not set TCP socket [{}] to non-blocking mode (closing connection) (psock_ctl: {})", sd, pstrerror(psock_errno())); - psock_close(sd); - } - } - else if (PSOCK_FD_ISSET(lsocket, &ExceptFDs)) { - eventlog(eventlog_level_error, __FUNCTION__, "exception on listening socket"); - /* FIXME: exceptions are not errors with TCP, they are out-of-band data */ - return; - } - - LIST_TRAVERSE(dbs_server_connection_list, elem) + for (auto c : connlist()) + { + if (!c) { - bOK = true; - pcErrorType = 0; - - if (!(it = (t_d2dbs_connection*)elem_get_data(elem))) continue; - if (PSOCK_FD_ISSET(it->sd, &ExceptFDs)) { - bOK = false; - pcErrorType = "General socket error"; /* FIXME: no no no no no */ - PSOCK_FD_CLR(it->sd, &ExceptFDs); - } - else { - - if (PSOCK_FD_ISSET(it->sd, &ReadFDs)) { - bOK = dbs_server_read_data(it); - pcErrorType = "Read error"; - PSOCK_FD_CLR(it->sd, &ReadFDs); - } - - if (PSOCK_FD_ISSET(it->sd, &WriteFDs)) { - bOK = dbs_server_write_data(it); - pcErrorType = "Write error"; - PSOCK_FD_CLR(it->sd, &WriteFDs); - } - } - - if (!bOK) { - int err, errno2; - psock_t_socklen errlen; - - err = 0; - errlen = sizeof(err); - errno2 = psock_errno(); - - if (psock_getsockopt(it->sd, PSOCK_SOL_SOCKET, PSOCK_SO_ERROR, &err, &errlen) == 0) { - if (errlen && err != 0) { - err = err ? err : errno2; - eventlog(eventlog_level_error, __FUNCTION__, "data socket error : {}({})", pstrerror(err), err); - } - } - dbs_server_shutdown_connection(it); - list_remove_elem(dbs_server_connection_list, &elem); - } - else { - if (dbs_packet_handle(it) == -1) { - eventlog(eventlog_level_error, __FUNCTION__, "dbs_packet_handle() failed"); - dbs_server_shutdown_connection(it); - list_remove_elem(dbs_server_connection_list, &elem); - } - } + continue; } - } - } - static void dbs_on_exit(void) - { - t_elem * elem; - t_d2dbs_connection * it; - - if (dbs_server_listen_socket >= 0) - psock_close(dbs_server_listen_socket); - dbs_server_listen_socket = -1; - - LIST_TRAVERSE(dbs_server_connection_list, elem) - { - if (!(it = (t_d2dbs_connection*)elem_get_data(elem))) continue; - dbs_server_shutdown_connection(it); - list_remove_elem(dbs_server_connection_list, &elem); - } - cl_destroy(); - d2dbs_d2ladder_destroy(); - list_destroy(dbs_server_connection_list); - if (preset_d2gsid_head) - { - t_preset_d2gsid * curr; - t_preset_d2gsid * next; - - for (curr = preset_d2gsid_head; curr; curr = next) + if (now - c->last_active > timeout) { - next = curr->next; - xfree(curr); + eventlog(eventlog_level_debug, __FUNCTION__, "[{}] connection {} timed out", c->sd, c->serverid); + + conn_set_state(c, conn_state_destroy); } } - eventlog(eventlog_level_info, __FUNCTION__, "dbserver stopped"); } - int dbs_server_shutdown_connection(t_d2dbs_connection* conn) + static void dbs_keepalive() { - psock_shutdown(conn->sd, PSOCK_SHUT_RDWR); - psock_close(conn->sd); - if (conn->verified && conn->type == CONNECT_CLASS_D2GS_TO_D2DBS) { - eventlog(eventlog_level_info, __FUNCTION__, "unlock all characters on gs {}({})", conn->serverip, conn->serverid); - eventlog_step(prefs_get_logfile_gs(), eventlog_level_info, __FUNCTION__, "unlock all characters on gs %s(%d)", conn->serverip, conn->serverid); - eventlog_step(prefs_get_logfile_gs(), eventlog_level_info, __FUNCTION__, "close connection to gs on socket %d", conn->sd); - cl_unlock_all_char_by_gsid(conn->serverid); - } - xfree(conn); - return 1; - } - - static int setsockopt_keepalive(int sock) - { - int optval; - psock_t_socklen optlen; - - optval = 1; - optlen = sizeof(optval); - if (psock_setsockopt(sock, PSOCK_SOL_SOCKET, PSOCK_SO_KEEPALIVE, &optval, optlen)) { - eventlog(eventlog_level_info, __FUNCTION__, "failed set KEEPALIVE for socket {}, errno={}", sock, psock_errno()); - return -1; - } - else { - eventlog(eventlog_level_info, __FUNCTION__, "set KEEPALIVE option for socket {}", sock); - return 0; - } - } - - static unsigned int get_preset_d2gsid(unsigned int ipaddr) - { - t_preset_d2gsid *pgsid; - - pgsid = preset_d2gsid_head; - while (pgsid) + for (auto c : connlist()) { - if (pgsid->ipaddr == ipaddr) - return pgsid->d2gsid; - pgsid = pgsid->next; + if (!c) + { + continue; + } + + t_packet* rpacket = packet_create(packet_class_d2dbs_d2gs); + if (!rpacket) + { + continue; + } + + packet_set_size(rpacket, sizeof(t_d2dbs_d2gs_echorequest)); + packet_set_type(rpacket, D2DBS_D2GS_ECHOREQUEST); + + bn_int_set(&rpacket->u.d2dbs_d2gs_echorequest.h.seqno, 0); + + conn_push_outqueue(c, rpacket); + + packet_del_ref(rpacket); } - /* not found, build a new item */ - pgsid = (t_preset_d2gsid*)xmalloc(sizeof(t_preset_d2gsid)); - pgsid->ipaddr = ipaddr; - pgsid->d2gsid = ++dbs_packet_gs_id; - /* add to list */ - pgsid->next = preset_d2gsid_head; - preset_d2gsid_head = pgsid; - return preset_d2gsid_head->d2gsid; } } diff --git a/src/d2dbs/dbserver.h b/src/d2dbs/dbserver.h index b05ca51..cefdbc0 100644 --- a/src/d2dbs/dbserver.h +++ b/src/d2dbs/dbserver.h @@ -18,7 +18,10 @@ #ifndef INCLUDED_DBSERVER_H #define INCLUDED_DBSERVER_H -#include "common/list.h" +#define STATUS_D2LADDER_FAILURE 20 +#define STATUS_CHARLOCK_FAILURE 30 +#define STATUS_FDWATCH_FAILURE 90 + namespace pvpgn { @@ -26,33 +29,25 @@ namespace pvpgn namespace d2dbs { - typedef struct { - int sd; - unsigned int ipaddr; - unsigned char major; - unsigned char minor; - unsigned char type; - unsigned char stats; - unsigned int serverid; - unsigned int verified; - unsigned char serverip[16]; - int last_active; - int nCharsInReadBuffer; - int nCharsInWriteBuffer; - char ReadBuf[kBufferSize]; - char WriteBuf[kBufferSize]; - } t_d2dbs_connection; +#ifdef SERVER_INTERNAL_ACCESS - typedef struct raw_preset_d2gsid { - unsigned int ipaddr; - unsigned int d2gsid; - struct raw_preset_d2gsid *next; - } t_preset_d2gsid; + enum t_laddr_type + { + laddr_type_d2gs, // d2gs (port 4000) + }; - int dbs_server_main(void); - int dbs_server_shutdown_connection(t_d2dbs_connection* conn); + // listen address structure + struct t_laddr_info + { + int ssocket; // TCP listen socket + t_laddr_type type; + }; - extern t_list * dbs_server_connection_list; +#endif + + int pre_server_startup(); + bool server_process(); + void post_server_shutdown(int status); } diff --git a/src/d2dbs/dbspacket.cpp b/src/d2dbs/dbspacket.cpp deleted file mode 100644 index 0eb80a0..0000000 --- a/src/d2dbs/dbspacket.cpp +++ /dev/null @@ -1,917 +0,0 @@ -/* - * Copyright (C) 2001 sousou (liupeng.cs@263.net) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ -#include "common/setup_before.h" -#include "setup.h" -#include "dbspacket.h" - -#include <cstdio> -#include <cerrno> -#include <cstring> -#include <ctime> -#include <string> - -#include <fmt/format.h> - -#ifdef HAVE_SYS_TYPES_H -# include <sys/types.h> -#endif -#ifdef HAVE_SYS_STAT_H -# include <sys/stat.h> -#endif -#ifdef HAVE_UNISTD_H -# include <unistd.h> -#endif - -#include "compat/strsep.h" -#include "compat/mkdir.h" -#include "compat/rename.h" -#include "compat/access.h" -#include "compat/statmacros.h" -#include "compat/psock.h" -#include "common/xstring.h" -#include "common/eventlog.h" -#include "common/d2cs_d2gs_character.h" -#include "common/d2char_checksum.h" -#include "common/xalloc.h" -#include "common/addr.h" -#include "prefs.h" -#include "charlock.h" -#include "d2ladder.h" -#include "common/setup_after.h" - -namespace pvpgn -{ - - namespace d2dbs - { - - static unsigned int dbs_packet_savedata_charsave(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, unsigned int datalen); - static unsigned int dbs_packet_savedata_charinfo(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, unsigned int datalen); - static unsigned int dbs_packet_getdata_charsave(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, long bufsize); - static unsigned int dbs_packet_getdata_charinfo(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, unsigned int bufsize); - static unsigned int dbs_packet_echoreply(t_d2dbs_connection* conn); - static int dbs_packet_getdata(t_d2dbs_connection* conn); - static int dbs_packet_savedata(t_d2dbs_connection* conn); - static int dbs_packet_charlock(t_d2dbs_connection* conn); - static int dbs_packet_updateladder(t_d2dbs_connection* conn); - static int dbs_verify_ipaddr(char const * addrlist, t_d2dbs_connection * c); - - static int dbs_packet_fix_charinfo(t_d2dbs_connection * conn, char * AccountName, char * CharName, char * charsave); - static void dbs_packet_set_charinfo_level(char * CharName, char * charinfo); - - static unsigned int dbs_packet_savedata_charsave(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, unsigned int datalen) - { - std::FILE * fd; - int checksum_header; - int checksum_calc; - - strtolower(AccountName); - strtolower(CharName); - - //check if checksum is ok - checksum_header = bn_int_get((bn_basic*)&data[D2CHARSAVE_CHECKSUM_OFFSET]); - checksum_calc = d2charsave_checksum((unsigned char *)data, datalen, D2CHARSAVE_CHECKSUM_OFFSET); - - if (checksum_header != checksum_calc) - { - eventlog(eventlog_level_error, __FUNCTION__, "received ({}) and calculated({}) checksum do not match - discarding charsave", checksum_header, checksum_calc); - return 0; - } - - - std::string filename = fmt::format("{}/.{}.tmp", d2dbs_prefs_get_charsave_dir(), CharName); - fd = std::fopen(filename.c_str(), "wb"); - if (!fd) { - eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename); - return 0; - } - - std::size_t curlen = 0; - std::size_t leftlen = datalen; - while (curlen<datalen) - { - std::size_t writelen = leftlen > 2000 ? 2000 : leftlen; - - std::size_t readlen = std::fwrite(data + curlen, 1, writelen, fd); - if (readlen <= 0) { - std::fclose(fd); - eventlog(eventlog_level_error, __FUNCTION__, "write() failed error : {}", std::strerror(errno)); - return 0; - } - curlen += readlen; - leftlen -= readlen; - } - std::fclose(fd); - - std::string bakfile = fmt::format("{}/{}", prefs_get_charsave_bak_dir(), CharName); - std::string savefile = fmt::format("{}/{}", d2dbs_prefs_get_charsave_dir(), CharName); - if (p_rename(savefile.c_str(), bakfile.c_str()) == -1) { - eventlog(eventlog_level_warn, __FUNCTION__, "error std::rename {} to {}", savefile, bakfile); - } - if (p_rename(filename.c_str(), savefile.c_str()) == -1) { - eventlog(eventlog_level_error, __FUNCTION__, "error std::rename {} to {}", filename, savefile); - return 0; - } - eventlog(eventlog_level_info, __FUNCTION__, "saved charsave {}(*{}) for gs {}({})", CharName, AccountName, conn->serverip, conn->serverid); - return datalen; - } - - static unsigned int dbs_packet_savedata_charinfo(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, unsigned int datalen) - { - std::FILE * fd; - struct stat statbuf; - - strtolower(AccountName); - strtolower(CharName); - - std::string filepath = fmt::format("{}/{}", prefs_get_charinfo_bak_dir(), AccountName); - if (stat(filepath.c_str(), &statbuf) == -1) - { - if (p_mkdir(filepath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == 0) - { - eventlog(eventlog_level_info, __FUNCTION__, "created charinfo directory: {}", filepath); - } - else - { - eventlog(eventlog_level_info, __FUNCTION__, "failed to create charinfo directory \"{}\" (errno: {})", filepath, errno); - return 0; - } - - } - - std::string filename = fmt::format("{}/{}/.{}.tmp", d2dbs_prefs_get_charinfo_dir(), AccountName, CharName); - fd = std::fopen(filename.c_str(), "wb"); - if (!fd) { - eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename); - return 0; - } - - std::size_t curlen = 0; - std::size_t leftlen = datalen; - while (curlen < datalen) - { - std::size_t writelen = leftlen > 2000 ? 2000 : leftlen; - - std::size_t readlen = std::fwrite(data + curlen, 1, writelen, fd); - if (readlen <= 0) { - std::fclose(fd); - eventlog(eventlog_level_error, __FUNCTION__, "write() failed error : {}", std::strerror(errno)); - return 0; - } - curlen += readlen; - leftlen -= readlen; - } - std::fclose(fd); - - std::string bakfile = fmt::format("{}/{}/{}", prefs_get_charinfo_bak_dir(), AccountName, CharName); - std::string savefile = fmt::format("{}/{}/{}", d2dbs_prefs_get_charinfo_dir(), AccountName, CharName); - if (p_rename(savefile.c_str(), bakfile.c_str()) == -1) { - eventlog(eventlog_level_info, __FUNCTION__, "error std::rename {} to {}", savefile, bakfile); - } - if (p_rename(filename.c_str(), savefile.c_str()) == -1) { - eventlog(eventlog_level_error, __FUNCTION__, "error std::rename {} to {}", filename, savefile); - return 0; - } - eventlog(eventlog_level_info, __FUNCTION__, "saved charinfo {}(*{}) for gs {}({})", CharName, AccountName, conn->serverip, conn->serverid); - return datalen; - } - - static unsigned int dbs_packet_getdata_charsave(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, long bufsize) - { - std::FILE * fd; - - strtolower(AccountName); - strtolower(CharName); - - std::string filename = fmt::format("{}/{}", d2dbs_prefs_get_charsave_dir(), CharName); - std::string filename_d2closed = fmt::format("{}/{}.d2s", d2dbs_prefs_get_charsave_dir(), CharName); - if ((access(filename.c_str(), F_OK) < 0) && (access(filename_d2closed.c_str(), F_OK) == 0)) - { - if (std::rename(filename_d2closed.c_str(), filename.c_str()) != 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "failed to rename file \"{}\" to \"{}\"", filename_d2closed, filename); - return 0; - } - } - fd = std::fopen(filename.c_str(), "rb"); - if (!fd) { - eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename); - return 0; - } - std::fseek(fd, 0, SEEK_END); - long filesize = std::ftell(fd); - if (filesize == -1L) - { - std::fclose(fd); - eventlog(eventlog_level_error, __FUNCTION__, "ftell() failed"); - return 0; - } - std::rewind(fd); - - if (bufsize < filesize) { - std::fclose(fd); - eventlog(eventlog_level_error, __FUNCTION__, "not enough buffer"); - return 0; - } - - long curlen = 0; - std::size_t leftlen = filesize; - while (curlen < filesize) - { - std::size_t writelen = leftlen > 2000 ? 2000 : leftlen; - - std::size_t readlen = std::fread(data + curlen, 1, writelen, fd); - if (readlen <= 0) { - std::fclose(fd); - eventlog(eventlog_level_error, __FUNCTION__, "read() failed error : {}", std::strerror(errno)); - return 0; - } - leftlen -= readlen; - curlen += readlen; - } - std::fclose(fd); - eventlog(eventlog_level_info, __FUNCTION__, "loaded charsave {}(*{}) for gs {}({})", CharName, AccountName, conn->serverip, conn->serverid); - return filesize; - } - - static unsigned int dbs_packet_getdata_charinfo(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, unsigned int bufsize) - { - std::FILE * fd; - - strtolower(AccountName); - strtolower(CharName); - - std::string filename = fmt::format("{}/{}/{}", d2dbs_prefs_get_charinfo_dir(), AccountName, CharName); - fd = std::fopen(filename.c_str(), "rb"); - if (!fd) { - eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename); - return 0; - } - std::fseek(fd, 0, SEEK_END); - long filesize = std::ftell(fd); - std::rewind(fd); - if (filesize == -1) { - std::fclose(fd); - eventlog(eventlog_level_error, __FUNCTION__, "lseek() failed"); - return 0; - } - if ((signed)bufsize < filesize) { - std::fclose(fd); - eventlog(eventlog_level_error, __FUNCTION__, "not enough buffer"); - return 0; - } - - std::size_t curlen = 0; - std::size_t leftlen = filesize; - while (curlen < filesize) - { - std::size_t writelen = leftlen > 2000 ? 2000 : leftlen; - - std::size_t readlen = std::fread(data + curlen, 1, writelen, fd); - if (readlen <= 0) - { - std::fclose(fd); - eventlog(eventlog_level_error, __FUNCTION__, "read() failed error : {}", std::strerror(errno)); - return 0; - } - leftlen -= readlen; - curlen += readlen; - } - std::fclose(fd); - eventlog(eventlog_level_info, __FUNCTION__, "loaded charinfo {}(*{}) for gs {}({})", CharName, AccountName, conn->serverip, conn->serverid); - return filesize; - } - - static int dbs_packet_savedata(t_d2dbs_connection * conn) - { - unsigned short datatype; - unsigned short datalen; - unsigned int result; - char AccountName[MAX_USERNAME_LEN]; - char CharName[MAX_CHARNAME_LEN]; - char RealmName[MAX_REALMNAME_LEN]; - t_d2gs_d2dbs_save_data_request * savecom; - char* readpos = conn->ReadBuf; - - savecom = (t_d2gs_d2dbs_save_data_request *)readpos; - datatype = bn_short_get(savecom->datatype); - datalen = bn_short_get(savecom->datalen); - - readpos += sizeof(*savecom); - std::strncpy(AccountName, readpos, MAX_USERNAME_LEN); - if (AccountName[MAX_USERNAME_LEN - 1] != 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "max acccount name length exceeded"); - return -1; - } - readpos += std::strlen(AccountName) + 1; - std::strncpy(CharName, readpos, MAX_CHARNAME_LEN); - if (CharName[MAX_CHARNAME_LEN - 1] != 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "max char name length exceeded"); - return -1; - } - readpos += std::strlen(CharName) + 1; - std::strncpy(RealmName, readpos, MAX_REALMNAME_LEN); - if (RealmName[MAX_REALMNAME_LEN - 1] != 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "max realm name length exceeded"); - return -1; - } - readpos += std::strlen(RealmName) + 1; - - if (readpos + datalen != conn->ReadBuf + bn_short_get(savecom->h.size)) { - eventlog(eventlog_level_error, __FUNCTION__, "request packet size error"); - return -1; - } - - if (datatype == D2GS_DATA_CHARSAVE) { - if (dbs_packet_savedata_charsave(conn, AccountName, CharName, readpos, datalen) > 0 && - dbs_packet_fix_charinfo(conn, AccountName, CharName, readpos)) { - result = D2DBS_SAVE_DATA_SUCCESS; - } - else { - datalen = 0; - result = D2DBS_SAVE_DATA_FAILED; - } - } - else if (datatype == D2GS_DATA_PORTRAIT) { - /* if level is > 255 , sets level to 255 */ - dbs_packet_set_charinfo_level(CharName, readpos); - if (dbs_packet_savedata_charinfo(conn, AccountName, CharName, readpos, datalen) > 0) { - result = D2DBS_SAVE_DATA_SUCCESS; - } - else { - datalen = 0; - result = D2DBS_SAVE_DATA_FAILED; - } - } - else { - eventlog(eventlog_level_error, __FUNCTION__, "unknown data type {}", datatype); - return -1; - } - - t_d2dbs_d2gs_save_data_reply* saveret; - std::size_t writelen = sizeof(*saveret) + std::strlen(CharName) + 1; - std::size_t buffer_available_len = sizeof(conn->WriteBuf) - conn->nCharsInWriteBuffer; - - if (buffer_available_len < writelen) - { - eventlog(eventlog_level_error, __FUNCTION__, "[{}] not enough space in write buffer (available space: {}, needed: {})", conn->sd, buffer_available_len, writelen); - return 0; - } - - unsigned char* writepos = reinterpret_cast<unsigned char*>(conn->WriteBuf + conn->nCharsInWriteBuffer); - saveret = reinterpret_cast<t_d2dbs_d2gs_save_data_reply*>(writepos); - bn_short_set(&saveret->h.type, D2DBS_D2GS_SAVE_DATA_REPLY); - bn_short_set(&saveret->h.size, writelen); - bn_int_set(&saveret->h.seqno, bn_int_get(savecom->h.seqno)); - bn_short_set(&saveret->datatype, bn_short_get(savecom->datatype)); - bn_int_set(&saveret->result, result); - writepos += sizeof(*saveret); - std::memcpy(writepos, CharName, std::strlen(CharName) + 1); - - conn->nCharsInWriteBuffer += writelen; - - return 1; - } - - static unsigned int dbs_packet_echoreply(t_d2dbs_connection * conn) - { - conn->last_active = std::time(NULL); - return 1; - } - - static int dbs_packet_getdata(t_d2dbs_connection * conn) - { - unsigned short datatype; - unsigned short datalen; - unsigned int result; - char AccountName[MAX_USERNAME_LEN]; - char CharName[MAX_CHARNAME_LEN]; - char RealmName[MAX_REALMNAME_LEN]; - t_d2gs_d2dbs_get_data_request * getcom; - t_d2dbs_d2gs_get_data_reply * getret; - char * readpos; - char * writepos; - char databuf[kBufferSize]; - t_d2charinfo_file charinfo; - unsigned short charinfolen; - unsigned int gsid; - - readpos = conn->ReadBuf; - getcom = (t_d2gs_d2dbs_get_data_request *)readpos; - datatype = bn_short_get(getcom->datatype); - - readpos += sizeof(*getcom); - std::strncpy(AccountName, readpos, MAX_USERNAME_LEN); - if (AccountName[MAX_USERNAME_LEN - 1] != 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "max account name length exceeded"); - return -1; - } - readpos += std::strlen(AccountName) + 1; - std::strncpy(CharName, readpos, MAX_CHARNAME_LEN); - if (CharName[MAX_CHARNAME_LEN - 1] != 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "max char name length exceeded"); - return -1; - } - readpos += std::strlen(CharName) + 1; - std::strncpy(RealmName, readpos, MAX_REALMNAME_LEN); - if (RealmName[MAX_REALMNAME_LEN - 1] != 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "max realm name length exceeded"); - return -1; - } - readpos += std::strlen(RealmName) + 1; - - if (readpos != conn->ReadBuf + bn_short_get(getcom->h.size)) { - eventlog(eventlog_level_error, __FUNCTION__, "request packet size error"); - return -1; - } - writepos = conn->WriteBuf + conn->nCharsInWriteBuffer; - getret = (t_d2dbs_d2gs_get_data_reply *)writepos; - datalen = 0; - if (datatype == D2GS_DATA_CHARSAVE) { - if (cl_query_charlock_status((unsigned char*)CharName, (unsigned char*)RealmName, &gsid) != 0) { - eventlog(eventlog_level_warn, __FUNCTION__, "char {}(*{})@{} is already locked on gs {}", CharName, AccountName, RealmName, gsid); - result = D2DBS_GET_DATA_CHARLOCKED; - } - else if (cl_lock_char((unsigned char*)CharName, (unsigned char*)RealmName, conn->serverid) != 0) { - eventlog(eventlog_level_error, __FUNCTION__, "failed to lock char {}(*{})@{} for gs {}({})", CharName, AccountName, RealmName, conn->serverip, conn->serverid); - result = D2DBS_GET_DATA_CHARLOCKED; - } - else { - eventlog(eventlog_level_info, __FUNCTION__, "lock char {}(*{})@{} for gs {}({})", CharName, AccountName, RealmName, conn->serverip, conn->serverid); - datalen = dbs_packet_getdata_charsave(conn, AccountName, CharName, databuf, kBufferSize); - if (datalen > 0) { - result = D2DBS_GET_DATA_SUCCESS; - charinfolen = dbs_packet_getdata_charinfo(conn, AccountName, CharName, (char *)&charinfo, sizeof(charinfo)); - if (charinfolen > 0) { - result = D2DBS_GET_DATA_SUCCESS; - } - else { - result = D2DBS_GET_DATA_FAILED; - if (cl_unlock_char((unsigned char*)CharName, (unsigned char*)RealmName, gsid) != 0) { - eventlog(eventlog_level_error, __FUNCTION__, "failed to unlock char {}(*{})@{} for gs {}({})", CharName, \ - AccountName, RealmName, conn->serverip, conn->serverid); - } - else { - eventlog(eventlog_level_info, __FUNCTION__, "unlock char {}(*{})@{} for gs {}({})", CharName, \ - AccountName, RealmName, conn->serverip, conn->serverid); - } - } - } - else { - datalen = 0; - result = D2DBS_GET_DATA_FAILED; - if (cl_unlock_char((unsigned char*)CharName, (unsigned char*)RealmName, gsid) != 0) { - eventlog(eventlog_level_error, __FUNCTION__, "faled to unlock char {}(*{})@{} for gs {}({})", CharName, \ - AccountName, RealmName, conn->serverip, conn->serverid); - } - else { - eventlog(eventlog_level_info, __FUNCTION__, "unlock char {}(*{})@{} for gs {}({})", CharName, \ - AccountName, RealmName, conn->serverip, conn->serverid); - } - - } - } - if (result == D2DBS_GET_DATA_SUCCESS) { - bn_int_set(&getret->charcreatetime, bn_int_get(charinfo.header.create_time)); - /* FIXME: this should be rewritten to support string formatted std::time */ - if (bn_int_get(charinfo.header.create_time) >= prefs_get_ladderinit_time()) { - bn_int_set(&getret->allowladder, 1); - } - else { - bn_int_set(&getret->allowladder, 0); - } - } - else { - bn_int_set(&getret->charcreatetime, 0); - bn_int_set(&getret->allowladder, 0); - } - } - else if (datatype == D2GS_DATA_PORTRAIT) { - datalen = dbs_packet_getdata_charinfo(conn, AccountName, CharName, databuf, kBufferSize); - if (datalen > 0) result = D2DBS_GET_DATA_SUCCESS; - else { - datalen = 0; - result = D2DBS_GET_DATA_FAILED; - } - } - else { - eventlog(eventlog_level_error, __FUNCTION__, "unknown data type {}", datatype); - return -1; - } - - std::size_t buffer_available_len = sizeof(conn->WriteBuf) - conn->nCharsInWriteBuffer; - std::size_t writelen = sizeof(*getret) + std::strlen(CharName) + 1 + datalen; - if (buffer_available_len < writelen) - { - eventlog(eventlog_level_error, __FUNCTION__, "[{}] not enough space in write buffer (available space: {}, needed: {})", conn->sd, buffer_available_len, writelen); - return 0; - } - - bn_short_set(&getret->h.type, D2DBS_D2GS_GET_DATA_REPLY); - bn_short_set(&getret->h.size, writelen); - bn_int_set(&getret->h.seqno, bn_int_get(getcom->h.seqno)); - bn_short_set(&getret->datatype, bn_short_get(getcom->datatype)); - bn_int_set(&getret->result, result); - bn_short_set(&getret->datalen, datalen); - writepos += sizeof(*getret); - - std::memcpy(writepos, CharName, std::strlen(CharName) + 1); - writepos += std::strlen(CharName) + 1; - - if (datalen) - { - std::memcpy(writepos, databuf, datalen); - } - - conn->nCharsInWriteBuffer += writelen; - - return 1; - } - - static int dbs_packet_updateladder(t_d2dbs_connection * conn) - { - char CharName[MAX_CHARNAME_LEN]; - char RealmName[MAX_REALMNAME_LEN]; - t_d2gs_d2dbs_update_ladder * updateladder; - char * readpos; - t_d2ladder_info charladderinfo; - - readpos = conn->ReadBuf; - updateladder = (t_d2gs_d2dbs_update_ladder *)readpos; - - readpos += sizeof(*updateladder); - std::strncpy(CharName, readpos, MAX_CHARNAME_LEN); - if (CharName[MAX_CHARNAME_LEN - 1] != 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "max char name length exceeded"); - return -1; - } - readpos += std::strlen(CharName) + 1; - std::strncpy(RealmName, readpos, MAX_REALMNAME_LEN); - if (RealmName[MAX_REALMNAME_LEN - 1] != 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "max realm name length exceeded"); - return -1; - } - readpos += std::strlen(RealmName) + 1; - if (readpos != conn->ReadBuf + bn_short_get(updateladder->h.size)) { - eventlog(eventlog_level_error, __FUNCTION__, "request packet size error"); - return -1; - } - - std::strcpy(charladderinfo.charname, CharName); - charladderinfo.experience = bn_int_get(updateladder->charexplow); - charladderinfo.level = bn_int_get(updateladder->charlevel); - charladderinfo.status = bn_short_get(updateladder->charstatus); - charladderinfo.chclass = bn_short_get(updateladder->charclass); - eventlog(eventlog_level_info, __FUNCTION__, "update ladder for {}@{} for gs {}({})", CharName, RealmName, conn->serverip, conn->serverid); - d2ladder_update(&charladderinfo); - return 1; - } - - static int dbs_packet_charlock(t_d2dbs_connection * conn) - { - char CharName[MAX_CHARNAME_LEN]; - char AccountName[MAX_USERNAME_LEN]; - char RealmName[MAX_REALMNAME_LEN]; - t_d2gs_d2dbs_char_lock * charlock; - char * readpos; - - readpos = conn->ReadBuf; - charlock = (t_d2gs_d2dbs_char_lock*)readpos; - - readpos += sizeof(*charlock); - std::strncpy(AccountName, readpos, MAX_USERNAME_LEN); - if (AccountName[MAX_USERNAME_LEN - 1] != 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "max account name length exceeded"); - return -1; - } - readpos += std::strlen(AccountName) + 1; - std::strncpy(CharName, readpos, MAX_CHARNAME_LEN); - if (CharName[MAX_CHARNAME_LEN - 1] != 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "max char name length exceeded"); - return -1; - } - readpos += std::strlen(CharName) + 1; - std::strncpy(RealmName, readpos, MAX_REALMNAME_LEN); - if (RealmName[MAX_REALMNAME_LEN - 1] != 0) - { - eventlog(eventlog_level_error, __FUNCTION__, "max realm name length exceeded"); - return -1; - } - readpos += std::strlen(RealmName) + 1; - - if (readpos != conn->ReadBuf + bn_short_get(charlock->h.size)) { - eventlog(eventlog_level_error, __FUNCTION__, "request packet size error"); - return -1; - } - - if (bn_int_get(charlock->lockstatus)) { - if (cl_lock_char((unsigned char*)CharName, (unsigned char*)RealmName, conn->serverid) != 0) { - eventlog(eventlog_level_error, __FUNCTION__, "failed to lock character {}(*{})@{} for gs {}({})", CharName, AccountName, RealmName, conn->serverip, conn->serverid); - } - else { - eventlog(eventlog_level_info, __FUNCTION__, "lock character {}(*{})@{} for gs {}({})", CharName, AccountName, RealmName, conn->serverip, conn->serverid); - } - } - else { - if (cl_unlock_char((unsigned char*)CharName, (unsigned char*)RealmName, conn->serverid) != 0) { - eventlog(eventlog_level_error, __FUNCTION__, "failed to unlock character {}(*{})@{} for gs {}({})", CharName, AccountName, RealmName, conn->serverip, conn->serverid); - } - else { - eventlog(eventlog_level_info, __FUNCTION__, "unlock character {}(*{})@{} for gs {}({})", CharName, AccountName, RealmName, conn->serverip, conn->serverid); - } - } - return 1; - } - - /* - return value: - 1 : process one or more packet - 0 : not get a whole packet,do nothing - -1 : error - */ - extern int dbs_packet_handle(t_d2dbs_connection* conn) - { - unsigned short readlen, writelen; - t_d2dbs_d2gs_header * readhead; - int retval; - - if (conn->stats == 0) { - if (conn->nCharsInReadBuffer < (signed)sizeof(t_d2gs_d2dbs_connect)) { - return 0; - } - conn->stats = 1; - conn->type = conn->ReadBuf[0]; - - if (conn->type == CONNECT_CLASS_D2GS_TO_D2DBS) { - if (dbs_verify_ipaddr(d2dbs_prefs_get_d2gs_list(), conn) < 0) { - eventlog(eventlog_level_error, __FUNCTION__, "d2gs connection from unknown ip address"); - return -1; - } - readlen = 1; - writelen = 0; - eventlog(eventlog_level_info, __FUNCTION__, "set connection type for gs {}({}) on socket {}", conn->serverip, conn->serverid, conn->sd); - eventlog_step(prefs_get_logfile_gs(), eventlog_level_info, __FUNCTION__, "set connection type for gs %s(%d) on socket %d", conn->serverip, conn->serverid, conn->sd); - } - else { - eventlog(eventlog_level_error, __FUNCTION__, "unknown connection type"); - return -1; - } - conn->nCharsInReadBuffer -= readlen; - std::memmove(conn->ReadBuf, conn->ReadBuf + readlen, conn->nCharsInReadBuffer); - } - else if (conn->stats == 1) { - if (conn->type == CONNECT_CLASS_D2GS_TO_D2DBS) { - while (conn->nCharsInReadBuffer >= (signed)sizeof(*readhead)) { - readhead = (t_d2dbs_d2gs_header *)conn->ReadBuf; - readlen = bn_short_get(readhead->size); - if (conn->nCharsInReadBuffer < readlen) break; - switch (bn_short_get(readhead->type)) { - case D2GS_D2DBS_SAVE_DATA_REQUEST: - retval = dbs_packet_savedata(conn); - break; - case D2GS_D2DBS_GET_DATA_REQUEST: - retval = dbs_packet_getdata(conn); - break; - case D2GS_D2DBS_UPDATE_LADDER: - retval = dbs_packet_updateladder(conn); - break; - case D2GS_D2DBS_CHAR_LOCK: - retval = dbs_packet_charlock(conn); - break; - case D2GS_D2DBS_ECHOREPLY: - retval = dbs_packet_echoreply(conn); - break; - default: - eventlog(eventlog_level_error, __FUNCTION__, "unknown request type {}", \ - bn_short_get(readhead->type)); - retval = -1; - } - if (retval != 1) return retval; - conn->nCharsInReadBuffer -= readlen; - std::memmove(conn->ReadBuf, conn->ReadBuf + readlen, conn->nCharsInReadBuffer); - } - } - else { - eventlog(eventlog_level_error, __FUNCTION__, "unknown connection type {}", conn->type); - return -1; - } - } - else { - eventlog(eventlog_level_error, __FUNCTION__, "unknown connection stats"); - return -1; - } - return 1; - } - - /* FIXME: we should save client ipaddr into c->ipaddr after accept */ - static int dbs_verify_ipaddr(char const * addrlist, t_d2dbs_connection * c) - { - char * adlist; - char * s, *temp; - t_elem * elem; - t_d2dbs_connection * tempc; - unsigned int valid; - unsigned int resolveipaddr; - - adlist = xstrdup(addrlist); - temp = adlist; - valid = 0; - while ((s = strsep(&temp, ","))) { - host_lookup(s, &resolveipaddr); - if (resolveipaddr == 0) continue; - - if (c->ipaddr == resolveipaddr) { - valid = 1; - break; - } - } - xfree(adlist); - if (valid) { - eventlog(eventlog_level_info, __FUNCTION__, "ip address {} is valid", addr_num_to_ip_str(c->ipaddr)); - LIST_TRAVERSE(dbs_server_connection_list, elem) - { - if (!(tempc = (t_d2dbs_connection*)elem_get_data(elem))) continue; - if (tempc != c && tempc->ipaddr == c->ipaddr) { - eventlog(eventlog_level_info, __FUNCTION__, "destroying previous connection {}", tempc->serverid); - dbs_server_shutdown_connection(tempc); - list_remove_elem(dbs_server_connection_list, &elem); - } - } - c->verified = 1; - return 0; - } - else { - eventlog(eventlog_level_info, __FUNCTION__, "ip address {} is invalid", addr_num_to_ip_str(c->ipaddr)); - } - return -1; - } - - int dbs_check_timeout(void) - { - t_elem *elem; - t_d2dbs_connection *tempc; - std::time_t now; - int timeout; - - now = std::time(NULL); - timeout = d2dbs_prefs_get_idletime(); - LIST_TRAVERSE(dbs_server_connection_list, elem) - { - if (!(tempc = (t_d2dbs_connection*)elem_get_data(elem))) continue; - if (now - tempc->last_active > timeout) { - eventlog(eventlog_level_debug, __FUNCTION__, "connection {} timed out", tempc->serverid); - dbs_server_shutdown_connection(tempc); - list_remove_elem(dbs_server_connection_list, &elem); - continue; - } - } - return 0; - } - - int dbs_keepalive(void) - { - t_elem *elem; - t_d2dbs_connection *tempc; - t_d2dbs_d2gs_echorequest *echoreq; - unsigned short writelen; - unsigned char *writepos; - std::time_t now; - - writelen = sizeof(t_d2dbs_d2gs_echorequest); - now = std::time(NULL); - LIST_TRAVERSE(dbs_server_connection_list, elem) - { - if (!(tempc = (t_d2dbs_connection*)elem_get_data(elem))) continue; - if (writelen > kBufferSize - tempc->nCharsInWriteBuffer) continue; - writepos = (unsigned char*)(tempc->WriteBuf + tempc->nCharsInWriteBuffer); - echoreq = (t_d2dbs_d2gs_echorequest*)writepos; - bn_short_set(&echoreq->h.type, D2DBS_D2GS_ECHOREQUEST); - bn_short_set(&echoreq->h.size, writelen); - /* FIXME: sequence number not set */ - bn_int_set(&echoreq->h.seqno, 0); - tempc->nCharsInWriteBuffer += writelen; - } - return 0; - } - - /*************************************************************************************/ -#define CHARINFO_SIZE 0xC0 -#define CHARINFO_PORTRAIT_LEVEL_OFFSET 0x89 -#define CHARINFO_PORTRAIT_STATUS_OFFSET 0x8A -#define CHARINFO_SUMMARY_LEVEL_OFFSET 0xB8 -#define CHARINFO_SUMMARY_STATUS_OFFSET 0xB4 -#define CHARINFO_PORTRAIT_GFX_OFFSET 0x72 -#define CHARINFO_PORTRAIT_COLOR_OFFSET 0x7E - -#define CHARSAVE_LEVEL_OFFSET 0x2B -#define CHARSAVE_STATUS_OFFSET 0x24 -#define CHARSAVE_GFX_OFFSET 0x88 -#define CHARSAVE_COLOR_OFFSET 0x98 - -#define charstatus_to_portstatus(status) ((((status & 0xFF00) << 1) | (status & 0x00FF)) | 0x8080) -#define portstatus_to_charstatus(status) (((status & 0x7F00) >> 1) | (status & 0x007F)) - - static void dbs_packet_set_charinfo_level(char * CharName, char * charinfo) - { - if (prefs_get_difficulty_hack()) { /* difficulty hack enabled */ - unsigned int level = bn_int_get((bn_basic*)&charinfo[CHARINFO_SUMMARY_LEVEL_OFFSET]); - unsigned int plevel = bn_byte_get((bn_basic*)&charinfo[CHARINFO_PORTRAIT_LEVEL_OFFSET]); - - /* levels 257 thru 355 */ - if (level != plevel) { - eventlog(eventlog_level_info, __FUNCTION__, "level mis-match for {} ( {} != {} ) setting to 255", CharName, level, plevel); - bn_byte_set((bn_byte *)&charinfo[CHARINFO_PORTRAIT_LEVEL_OFFSET], 255); - bn_int_set((bn_int *)&charinfo[CHARINFO_SUMMARY_LEVEL_OFFSET], 255); - } - } - } - - static int dbs_packet_fix_charinfo(t_d2dbs_connection * conn, char * AccountName, char * CharName, char * charsave) - { - if (prefs_get_difficulty_hack()) { - unsigned char charinfo[CHARINFO_SIZE]; - unsigned int level = bn_byte_get((bn_basic*)&charsave[CHARSAVE_LEVEL_OFFSET]); - unsigned short status = bn_short_get((bn_basic*)&charsave[CHARSAVE_STATUS_OFFSET]); - unsigned short pstatus = charstatus_to_portstatus(status); - int i; - - /* - * charinfo is only updated from level 1 to 99 (d2gs issue) - * from 100 to 256 d2gs does not send it - * when value rolls over (level 256 = 0) - * and charactar reaches level 257 (rolled over to level 1) - * d2gs starts sending it agian until level 356 (rolled over to 100) - * is reached agian. etc. etc. etc. - */ - if (level == 0) /* level 256, 512, 768, etc */ - level = 255; - - if (level < 100) - return 1; /* d2gs will send charinfo - level will be set to 255 at that std::time if needed */ - - eventlog(eventlog_level_info, __FUNCTION__, "level {} > 99 for {}", level, CharName); - - if (!(dbs_packet_getdata_charinfo(conn, AccountName, CharName, (char*)charinfo, CHARINFO_SIZE))) { - eventlog(eventlog_level_error, __FUNCTION__, "unable to get charinfo for {}", CharName); - return 0; - } - - /* if level in charinfo file is already set to 255, - * then is must have been set when d2gs sent the charinfo - * and got a level mis-match (levels 257 - 355) - * or level is actually 255. In eather case we set to 255 - * this should work for any level mod - */ - if (bn_byte_get(&charinfo[CHARINFO_PORTRAIT_LEVEL_OFFSET]) == 255) - level = 255; - - eventlog(eventlog_level_info, __FUNCTION__, "updating charinfo for {} -> level = {} , status = 0x{:04X} , pstatus = 0x{:04X}", CharName, level, status, pstatus); - bn_byte_set((bn_byte *)&charinfo[CHARINFO_PORTRAIT_LEVEL_OFFSET], level); - bn_int_set((bn_int *)&charinfo[CHARINFO_SUMMARY_LEVEL_OFFSET], level); - bn_short_set((bn_short *)&charinfo[CHARINFO_PORTRAIT_STATUS_OFFSET], pstatus); - bn_int_set((bn_int *)&charinfo[CHARINFO_SUMMARY_STATUS_OFFSET], status); - - for (i = 0; i < 11; i++) { - bn_byte_set((bn_byte *)&charinfo[CHARINFO_PORTRAIT_GFX_OFFSET + i], bn_byte_get((bn_basic*)&charsave[CHARSAVE_GFX_OFFSET + i])); - bn_byte_set((bn_byte *)&charinfo[CHARINFO_PORTRAIT_COLOR_OFFSET + i], bn_byte_get((bn_basic*)&charsave[CHARSAVE_GFX_OFFSET + i])); - } - - if (!(dbs_packet_savedata_charinfo(conn, AccountName, CharName, (char*)charinfo, CHARINFO_SIZE))) { - eventlog(eventlog_level_error, __FUNCTION__, "unable to save charinfo for {}", CharName); - return 0; - } - - return 1; /* charinfo updated */ - } - - return 1; /* difficulty hack not enabled */ - } - - } - -} diff --git a/src/d2dbs/dbspacket.h b/src/d2dbs/dbspacket.h deleted file mode 100644 index 8a2f8b7..0000000 --- a/src/d2dbs/dbspacket.h +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2001 faster (lqx@cic.tsinghua.edu.cn) - * Copyright (C) 2001 sousou (liupeng.cs@263.net) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ -#ifndef INCLUDED_DBSPACKET_H -#define INCLUDED_DBSPACKET_H - -#include "common/bn_type.h" -#include "dbserver.h" - -namespace pvpgn -{ - - namespace d2dbs - { - - typedef struct { - bn_short size; - bn_short type; - bn_int seqno; - } t_d2dbs_d2gs_header; - - typedef struct { - bn_byte cclass; - } t_d2gs_d2dbs_connect; -#define CONNECT_CLASS_D2GS_TO_D2DBS 0x65 - -#define D2GS_D2DBS_SAVE_DATA_REQUEST 0x30 - typedef struct { - t_d2dbs_d2gs_header h; - bn_short datatype; - bn_short datalen; - /* AccountName */ - /* CharName */ - /* data */ - } t_d2gs_d2dbs_save_data_request; -#define D2GS_DATA_CHARSAVE 0x01 -#define D2GS_DATA_PORTRAIT 0x02 - -#define D2DBS_D2GS_SAVE_DATA_REPLY 0x30 - typedef struct { - t_d2dbs_d2gs_header h; - bn_int result; - bn_short datatype; - /* CharName */ - } t_d2dbs_d2gs_save_data_reply; -#define D2DBS_SAVE_DATA_SUCCESS 0 -#define D2DBS_SAVE_DATA_FAILED 1 - -#define D2GS_D2DBS_GET_DATA_REQUEST 0x31 - typedef struct { - t_d2dbs_d2gs_header h; - bn_short datatype; - /* AccountName */ - /* CharName */ - } t_d2gs_d2dbs_get_data_request; - -#define D2DBS_D2GS_GET_DATA_REPLY 0x31 - typedef struct { - t_d2dbs_d2gs_header h; - bn_int result; - bn_int charcreatetime; - bn_int allowladder; - bn_short datatype; - bn_short datalen; - /* CharName */ - /* data */ - } t_d2dbs_d2gs_get_data_reply; - -#define D2DBS_GET_DATA_SUCCESS 0 -#define D2DBS_GET_DATA_FAILED 1 -#define D2DBS_GET_DATA_CHARLOCKED 2 - -#define D2GS_D2DBS_UPDATE_LADDER 0x32 - typedef struct { - t_d2dbs_d2gs_header h; - bn_int charlevel; - bn_int charexplow; - bn_int charexphigh; - bn_short charclass; - bn_short charstatus; - /* CharName */ - /* RealmName */ - } t_d2gs_d2dbs_update_ladder; - -#define D2GS_D2DBS_CHAR_LOCK 0x33 - typedef struct { - t_d2dbs_d2gs_header h; - bn_int lockstatus; - /* CharName */ - /* RealmName */ - } t_d2gs_d2dbs_char_lock; - -#define D2DBS_D2GS_ECHOREQUEST 0x34 - typedef struct { - t_d2dbs_d2gs_header h; - } t_d2dbs_d2gs_echorequest; - -#define D2GS_D2DBS_ECHOREPLY 0x34 - typedef struct { - t_d2dbs_d2gs_header h; - } t_d2gs_d2dbs_echoreply; - - - extern int dbs_packet_handle(t_d2dbs_connection * conn); - extern int dbs_keepalive(void); - extern int dbs_check_timeout(void); - - } - -} - -#endif diff --git a/src/d2dbs/handle_d2gs.cpp b/src/d2dbs/handle_d2gs.cpp new file mode 100644 index 0000000..da2b20c --- /dev/null +++ b/src/d2dbs/handle_d2gs.cpp @@ -0,0 +1,915 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "common/setup_before.h" +#include "setup.h" +#include "handle_d2gs.h" + +#include <cstdint> +#include <fstream> +#include <functional> +#include <type_traits> +#include <unordered_map> + +#include <nonstd/optional.hpp> + +#include "common/addr.h" +#include "common/bn_type.h" +#include "common/d2char_checksum.h" +#include "common/d2cs_d2gs_character.h" +#include "common/d2dbs_d2gs_protocol.h" +#include "common/eventlog.h" +#include "common/packet.h" +#include "common/xalloc.h" +#include "common/xstring.h" + +#include "compat/mkdir.h" +#include "compat/rename.h" + +#include "charlock.h" +#include "connection.h" +#include "d2ladder.h" +#include "prefs.h" + +#include "common/setup_after.h" + + +namespace pvpgn +{ + + namespace d2dbs + { + + static unsigned int dbs_packet_savedata_charsave(t_d2dbs_connection* c, const char* account_name, const char* char_name, const char* data, unsigned int datalen); + static unsigned int dbs_packet_savedata_charinfo(t_d2dbs_connection* c, const char* account_name, const char* char_name, const char* data, unsigned int datalen); + static nonstd::optional<std::vector<std::uint8_t>> dbs_packet_getdata_charsave(t_d2dbs_connection* c, const char* account_name, const char* char_name); + static nonstd::optional<std::vector<std::uint8_t>> dbs_packet_getdata_charinfo(t_d2dbs_connection* c, const char* account_name, const char* char_name); + + static int dbs_packet_savedata(t_d2dbs_connection* c, const t_packet* const packet); + static int dbs_packet_getdata(t_d2dbs_connection* c, const t_packet* const packet); + static int dbs_packet_updateladder(t_d2dbs_connection* c, const t_packet* const packet); + static int dbs_packet_charlock(t_d2dbs_connection* c, const t_packet* const packet); + static int dbs_packet_echoreply(t_d2dbs_connection* c, const t_packet* const packet); + + static int dbs_packet_fix_charinfo(t_d2dbs_connection* c, const char* AccountName, const char* CharName, const char* charsave); + static void dbs_packet_set_charinfo_level(const char* char_name, char* charinfo); + + + std::unordered_map<decltype(packet_get_type(std::declval<t_packet*>())), std::function<int(t_d2dbs_connection*, const t_packet* const packet)>> d2gs_packet_table = { + { D2GS_D2DBS_SAVE_DATA_REQUEST, dbs_packet_savedata }, + { D2GS_D2DBS_GET_DATA_REQUEST, dbs_packet_getdata }, + { D2GS_D2DBS_UPDATE_LADDER, dbs_packet_updateladder }, + { D2GS_D2DBS_CHAR_LOCK, dbs_packet_charlock }, + { D2DBS_D2GS_ECHOREQUEST, dbs_packet_echoreply } + }; + + + /* + * return value: + * 1 : process one or more packet + * 0 : not get a whole packet,do nothing + * -1 : error + */ + int handle_d2gs_packet(t_d2dbs_connection* c, const t_packet* const packet) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got NULL connection"); + return -1; + } + + if (!packet) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got NULL packet", c->sd); + return -1; + } + + if (packet_get_class(packet) != packet_class_d2dbs_d2gs) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad packet (class {})", c->sd, static_cast<std::underlying_type<t_packet_class>::type>(packet_get_class(packet))); + return -1; + } + + switch (conn_get_state(c)) + { + case conn_state_loggedin: + switch (d2gs_packet_table.at(packet_get_type(packet))(c, packet)) + { + case 1: + eventlog(eventlog_level_error, __FUNCTION__, "[{}] unknown (logged in) d2gs packet type 0x{:04x}, len {}", c->sd, packet_get_type(packet), packet_get_size(packet)); + break; + case -1: + eventlog(eventlog_level_error, __FUNCTION__, "[{}] (logged in) got error handling packet type 0x{:04x}, len {}", c->sd, packet_get_type(packet), packet_get_size(packet)); + break; + }; + break; + + default: + eventlog(eventlog_level_error, __FUNCTION__, "[{}] invalid login state {}", c->sd, static_cast<std::underlying_type<t_conn_state>::type>(conn_get_state(c))); + }; + + return 1; + } + + + static unsigned int dbs_packet_savedata_charsave(t_d2dbs_connection* c, const char* account_name, const char* char_name, const char* data, unsigned int datalen) + { + char* AccountName = xstrdup(account_name); + if (!AccountName) + { + return 0; + } + + char* CharName = xstrdup(char_name); + if (!CharName) + { + xfree(AccountName); + return 0; + } + + std::FILE* fd; + int checksum_header; + int checksum_calc; + + strtolower(AccountName); + strtolower(CharName); + + //check if checksum is ok + checksum_header = bn_int_get((bn_basic*)&data[D2CHARSAVE_CHECKSUM_OFFSET]); + checksum_calc = d2charsave_checksum((unsigned char*)data, datalen, D2CHARSAVE_CHECKSUM_OFFSET); + + if (checksum_header != checksum_calc) + { + xfree(AccountName); + xfree(CharName); + eventlog(eventlog_level_error, __FUNCTION__, "received ({}) and calculated({}) checksum do not match - discarding charsave", checksum_header, checksum_calc); + return 0; + } + + + std::string filename = fmt::format("{}/.{}.tmp", d2dbs_prefs_get_charsave_dir(), CharName); + fd = std::fopen(filename.c_str(), "wb"); + if (!fd) + { + xfree(AccountName); + xfree(CharName); + eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename); + return 0; + } + + std::size_t curlen = 0; + std::size_t leftlen = datalen; + while (curlen < datalen) + { + std::size_t writelen = leftlen > 2000 ? 2000 : leftlen; + + std::size_t readlen = std::fwrite(data + curlen, 1, writelen, fd); + if (readlen <= 0) + { + std::fclose(fd); + xfree(AccountName); + xfree(CharName); + eventlog(eventlog_level_error, __FUNCTION__, "write() failed error : {}", std::strerror(errno)); + return 0; + } + curlen += readlen; + leftlen -= readlen; + } + std::fclose(fd); + + std::string bakfile = fmt::format("{}/{}", prefs_get_charsave_bak_dir(), CharName); + std::string savefile = fmt::format("{}/{}", d2dbs_prefs_get_charsave_dir(), CharName); + if (p_rename(savefile.c_str(), bakfile.c_str()) == -1) + { + eventlog(eventlog_level_warn, __FUNCTION__, "error std::rename {} to {}", savefile, bakfile); + } + if (p_rename(filename.c_str(), savefile.c_str()) == -1) + { + xfree(AccountName); + xfree(CharName); + eventlog(eventlog_level_error, __FUNCTION__, "error std::rename {} to {}", filename, savefile); + return 0; + } + eventlog(eventlog_level_info, __FUNCTION__, "saved charsave {}(*{}) for gs {}({})", CharName, AccountName, c->serverip, c->serverid); + xfree(AccountName); + xfree(CharName); + return datalen; + } + + static unsigned int dbs_packet_savedata_charinfo(t_d2dbs_connection* c, const char* account_name, const char* char_name, const char* data, unsigned int datalen) + { + char* AccountName = xstrdup(account_name); + if (!AccountName) + { + return 0; + } + + char* CharName = xstrdup(char_name); + if (!CharName) + { + xfree(AccountName); + return 0; + } + + strtolower(AccountName); + strtolower(CharName); + + std::string filepath = fmt::format("{}/{}", prefs_get_charinfo_bak_dir(), AccountName); + { + struct stat statbuf; + if (stat(filepath.c_str(), &statbuf) == -1) + { + if (p_mkdir(filepath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == 0) + { + eventlog(eventlog_level_info, __FUNCTION__, "created charinfo directory: {}", filepath); + } + else + { + eventlog(eventlog_level_info, __FUNCTION__, "failed to create charinfo directory \"{}\" (errno: {})", filepath, errno); + xfree(AccountName); + xfree(CharName); + return 0; + } + } + } + + std::string filename = fmt::format("{}/{}/.{}.tmp", d2dbs_prefs_get_charinfo_dir(), AccountName, CharName); + std::FILE* fd = std::fopen(filename.c_str(), "wb"); + if (!fd) + { + eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename); + xfree(AccountName); + xfree(CharName); + return 0; + } + + std::size_t curlen = 0; + std::size_t leftlen = datalen; + while (curlen < datalen) + { + std::size_t writelen = leftlen > 2000 ? 2000 : leftlen; + + std::size_t readlen = std::fwrite(data + curlen, 1, writelen, fd); + if (readlen <= 0) + { + std::fclose(fd); + xfree(AccountName); + xfree(CharName); + eventlog(eventlog_level_error, __FUNCTION__, "write() failed error : {}", std::strerror(errno)); + return 0; + } + curlen += readlen; + leftlen -= readlen; + } + std::fclose(fd); + + std::string bakfile = fmt::format("{}/{}/{}", prefs_get_charinfo_bak_dir(), AccountName, CharName); + std::string savefile = fmt::format("{}/{}/{}", d2dbs_prefs_get_charinfo_dir(), AccountName, CharName); + if (p_rename(savefile.c_str(), bakfile.c_str()) == -1) + { + eventlog(eventlog_level_info, __FUNCTION__, "error std::rename {} to {}", savefile, bakfile); + } + if (p_rename(filename.c_str(), savefile.c_str()) == -1) + { + eventlog(eventlog_level_error, __FUNCTION__, "error std::rename {} to {}", filename, savefile); + xfree(AccountName); + xfree(CharName); + return 0; + } + eventlog(eventlog_level_info, __FUNCTION__, "saved charinfo {}(*{}) for gs {}({})", CharName, AccountName, c->serverip, c->serverid); + xfree(AccountName); + xfree(CharName); + return datalen; + } + + static nonstd::optional<std::vector<std::uint8_t>> dbs_packet_getdata_charsave(t_d2dbs_connection* c, const char* account_name, const char* char_name) + { + char* AccountName = xstrdup(account_name); + if (!AccountName) + { + return nonstd::nullopt; + } + + char* CharName = xstrdup(char_name); + if (!CharName) + { + xfree(AccountName); + return nonstd::nullopt; + } + + strtolower(AccountName); + strtolower(CharName); + + std::string filename = fmt::format("{}/{}", d2dbs_prefs_get_charsave_dir(), CharName); + std::string filename_d2closed = fmt::format("{}/{}.d2s", d2dbs_prefs_get_charsave_dir(), CharName); + if ((access(filename.c_str(), F_OK) < 0) && (access(filename_d2closed.c_str(), F_OK) == 0)) + { + if (std::rename(filename_d2closed.c_str(), filename.c_str()) != 0) + { + xfree(AccountName); + xfree(CharName); + eventlog(eventlog_level_error, __FUNCTION__, "failed to rename file \"{}\" to \"{}\"", filename_d2closed, filename); + return nonstd::nullopt; + } + } + + std::FILE* fd = std::fopen(filename.c_str(), "rb"); + if (!fd) + { + xfree(AccountName); + xfree(CharName); + eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename); + return nonstd::nullopt; + } + + if (std::fseek(fd, 0, SEEK_END) != 0) + { + std::fclose(fd); + xfree(AccountName); + xfree(CharName); + eventlog(eventlog_level_error, __FUNCTION__, "std::fseek() failed"); + return nonstd::nullopt; + } + + long filesize = std::ftell(fd); + if (filesize == -1L) + { + std::fclose(fd); + xfree(AccountName); + xfree(CharName); + eventlog(eventlog_level_error, __FUNCTION__, "ftell() failed"); + return nonstd::nullopt; + } + + std::rewind(fd); + + std::vector<std::uint8_t> charsave(filesize); + std::size_t readlen = std::fread(charsave.data(), sizeof(decltype(charsave)::value_type), charsave.size(), fd); + if (readlen < filesize) + { + std::fclose(fd); + eventlog(eventlog_level_error, __FUNCTION__, "failed to read charinfo {}(*{}): read {} bytes, expected {} bytes", CharName, AccountName, readlen, filesize); + xfree(AccountName); + xfree(CharName); + return nonstd::nullopt; + } + + std::fclose(fd); + eventlog(eventlog_level_info, __FUNCTION__, "loaded charsave {}(*{}) for gs {}({})", CharName, AccountName, c->serverip, c->serverid); + xfree(AccountName); + xfree(CharName); + + return charsave; + } + + static nonstd::optional<std::vector<std::uint8_t>> dbs_packet_getdata_charinfo(t_d2dbs_connection* c, const char* account_name, const char* char_name) + { + char* AccountName = xstrdup(account_name); + if (!AccountName) + { + return nonstd::nullopt; + } + + char* CharName = xstrdup(char_name); + if (!CharName) + { + xfree(AccountName); + return nonstd::nullopt; + } + + strtolower(AccountName); + strtolower(CharName); + + std::string filename = fmt::format("{}/{}/{}", d2dbs_prefs_get_charinfo_dir(), AccountName, CharName); + std::FILE* fd = std::fopen(filename.c_str(), "rb"); + if (!fd) + { + xfree(AccountName); + xfree(CharName); + eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename); + return nonstd::nullopt; + } + + if (std::fseek(fd, 0, SEEK_END) != 0) + { + std::fclose(fd); + xfree(AccountName); + xfree(CharName); + eventlog(eventlog_level_error, __FUNCTION__, "std::fseek() failed"); + return nonstd::nullopt; + } + + long filesize = std::ftell(fd); + if (filesize == -1) + { + std::fclose(fd); + xfree(AccountName); + xfree(CharName); + eventlog(eventlog_level_error, __FUNCTION__, "std::ftell() failed"); + return nonstd::nullopt; + } + + std::rewind(fd); + + std::vector<std::uint8_t> charinfo(filesize); + std::size_t readlen = std::fread(charinfo.data(), sizeof(decltype(charinfo)::value_type), charinfo.size(), fd); + if (readlen < filesize) + { + std::fclose(fd); + eventlog(eventlog_level_error, __FUNCTION__, "failed to read charinfo {}(*{}): read {} bytes, expected {} bytes", CharName, AccountName, readlen, filesize); + xfree(AccountName); + xfree(CharName); + return nonstd::nullopt; + } + + std::fclose(fd); + eventlog(eventlog_level_info, __FUNCTION__, "loaded charinfo {}(*{}) for gs {}({})", CharName, AccountName, c->serverip, c->serverid); + xfree(AccountName); + xfree(CharName); + + return charinfo; + } + + static int dbs_packet_savedata(t_d2dbs_connection* c, const t_packet* const packet) + { + if (packet_get_size(packet) < sizeof(t_d2gs_d2dbs_save_data_request)) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad savedata packet (expected {} bytes, got {})", c->sd, sizeof(t_d2gs_d2dbs_save_data_request), packet_get_size(packet)); + return -1; + } + + const auto datatype = bn_short_get(packet->u.d2gs_d2dbs_save_data_request.datatype); + const auto datalen = bn_short_get(packet->u.d2gs_d2dbs_save_data_request.datalen); + + const char* const account_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_save_data_request), MAX_USERNAME_LEN); + if (!account_name) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad SAVEDATAREQUEST (missing or too long account name)", c->sd); + return -1; + } + + const char* const char_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_save_data_request) + std::strlen(account_name) + 1, MAX_CHARNAME_LEN); + if (!char_name) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad SAVEDATAREQUEST (missing or too long char name)", c->sd); + return -1; + } + + const char* const realm_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_save_data_request) + std::strlen(account_name) + 1 + std::strlen(char_name) + 1, MAX_REALMNAME_LEN); + if (!realm_name) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad SAVEDATAREQUEST (missing or too long realm name)", c->sd); + return -1; + } + + const void* const data = packet_get_data_const(packet, sizeof(t_d2gs_d2dbs_save_data_request) + std::strlen(account_name) + 1 + std::strlen(char_name) + 1 + std::strlen(realm_name) + 1, datalen); + if (!data) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad SAVEDATAREQUEST (missing or too long data)", c->sd); + return -1; + } + + int result = D2DBS_SAVE_DATA_FAILED; + if (datatype == D2GS_DATA_CHARSAVE) + { + if (dbs_packet_savedata_charsave(c, account_name, char_name, static_cast<const char*>(data), datalen) > 0 && + dbs_packet_fix_charinfo(c, account_name, char_name, static_cast<const char*>(data))) + { + result = D2DBS_SAVE_DATA_SUCCESS; + } + else + { + result = D2DBS_SAVE_DATA_FAILED; + } + } + else if (datatype == D2GS_DATA_PORTRAIT) + { + char* modified_data = static_cast<char*>(xmalloc(datalen)); + if (!modified_data) + { + return -1; + } + + std::memcpy(modified_data, data, datalen); + + /* if level is > 255 , sets level to 255 */ + dbs_packet_set_charinfo_level(char_name, modified_data); + if (dbs_packet_savedata_charinfo(c, account_name, char_name, modified_data, datalen) > 0) + { + result = D2DBS_SAVE_DATA_SUCCESS; + } + else + { + result = D2DBS_SAVE_DATA_FAILED; + } + + xfree(modified_data); + } + else + { + eventlog(eventlog_level_error, __FUNCTION__, "unknown data type {}", datatype); + return -1; + } + + { + t_packet* rpacket = packet_create(packet_class_d2dbs_d2gs); + if (!rpacket) + { + return -1; + } + + packet_set_size(rpacket, sizeof(t_d2dbs_d2gs_save_data_reply)); + packet_set_type(rpacket, D2DBS_D2GS_SAVE_DATA_REPLY); + + bn_int_set(&rpacket->u.d2dbs_d2gs_save_data_reply.h.seqno, bn_int_get(packet->u.d2dbs_d2gs_save_data_reply.h.seqno)); + + bn_int_set(&rpacket->u.d2dbs_d2gs_save_data_reply.result, result); + bn_short_set(&rpacket->u.d2dbs_d2gs_save_data_reply.datatype, datatype); + packet_append_string(rpacket, char_name); + + conn_push_outqueue(c, rpacket); + + packet_del_ref(rpacket); + } + + return 0; + } + + static int dbs_packet_echoreply(t_d2dbs_connection* c, const t_packet* const packet) + { + c->last_active = std::time(nullptr); + + return 0; + } + + static int dbs_packet_getdata(t_d2dbs_connection* c, const t_packet* const packet) + { + if (packet_get_size(packet) < sizeof(t_d2gs_d2dbs_get_data_request)) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad GETDATAREQUEST packet (expected {} bytes, got {})", c->sd, sizeof(t_d2gs_d2dbs_get_data_request), packet_get_size(packet)); + return -1; + } + + const auto datatype = bn_short_get(packet->u.d2gs_d2dbs_save_data_request.datatype); + + const char* const account_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_get_data_request), MAX_USERNAME_LEN); + if (!account_name) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad GETDATAREQUEST (missing or too long account name)", c->sd); + return -1; + } + + const char* const char_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_get_data_request) + std::strlen(account_name) + 1, MAX_CHARNAME_LEN); + if (!char_name) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad GETDATAREQUEST (missing or too long char name)", c->sd); + return -1; + } + + const char* const realm_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_get_data_request) + std::strlen(account_name) + 1 + std::strlen(char_name) + 1, MAX_REALMNAME_LEN); + if (!realm_name) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad GETDATAREQUEST (missing or too long realm name)", c->sd); + return -1; + } + + + nonstd::optional<std::vector<std::uint8_t>> charinfo = nonstd::nullopt; + nonstd::optional<std::vector<std::uint8_t>> charsave = nonstd::nullopt; + unsigned int result; + if (datatype == D2GS_DATA_CHARSAVE) + { + unsigned int gsid = 0; + if (cl_query_charlock_status((unsigned char*)char_name, (unsigned char*)realm_name, &gsid) != 0) + { + eventlog(eventlog_level_warn, __FUNCTION__, "char {}(*{})@{} is already locked on gs {}", char_name, account_name, realm_name, gsid); + result = D2DBS_GET_DATA_CHARLOCKED; + } + else if (cl_lock_char((unsigned char*)char_name, (unsigned char*)realm_name, c->serverid) != 0) + { + eventlog(eventlog_level_error, __FUNCTION__, "failed to lock char {}(*{})@{} for gs {}({})", char_name, account_name, realm_name, c->serverip, c->serverid); + result = D2DBS_GET_DATA_CHARLOCKED; + } + else + { + eventlog(eventlog_level_info, __FUNCTION__, "lock char {}(*{})@{} for gs {}({})", char_name, account_name, realm_name, c->serverip, c->serverid); + + charsave = dbs_packet_getdata_charsave(c, account_name, char_name); + if (charsave.has_value()) + { + charinfo = dbs_packet_getdata_charinfo(c, account_name, char_name); + if (charinfo.has_value()) + { + result = D2DBS_GET_DATA_SUCCESS; + } + else + { + result = D2DBS_GET_DATA_FAILED; + + if (cl_unlock_char((unsigned char*)char_name, (unsigned char*)realm_name, gsid) != 0) + { + eventlog(eventlog_level_error, __FUNCTION__, "failed to unlock char {}(*{})@{} for gs {}({})", char_name, \ + account_name, realm_name, c->serverip, c->serverid); + } + else + { + eventlog(eventlog_level_info, __FUNCTION__, "unlock char {}(*{})@{} for gs {}({})", char_name, \ + account_name, realm_name, c->serverip, c->serverid); + } + } + } + else + { + result = D2DBS_GET_DATA_FAILED; + + if (cl_unlock_char((unsigned char*)char_name, (unsigned char*)realm_name, gsid) != 0) + { + eventlog(eventlog_level_error, __FUNCTION__, "faled to unlock char {}(*{})@{} for gs {}({})", char_name, \ + account_name, realm_name, c->serverip, c->serverid); + } + else + { + eventlog(eventlog_level_info, __FUNCTION__, "unlock char {}(*{})@{} for gs {}({})", char_name, \ + account_name, realm_name, c->serverip, c->serverid); + } + + } + } + } + else if (datatype == D2GS_DATA_PORTRAIT) + { + charinfo = dbs_packet_getdata_charinfo(c, account_name, char_name); + + result = charinfo.has_value() ? D2DBS_GET_DATA_SUCCESS : D2DBS_GET_DATA_FAILED; + } + else + { + eventlog(eventlog_level_error, __FUNCTION__, "unknown data type {}", datatype); + return -1; + } + + { + t_packet* rpacket = packet_create(packet_class_d2dbs_d2gs); + if (!rpacket) + { + return -1; + } + + packet_set_size(rpacket, sizeof(t_d2dbs_d2gs_get_data_reply)); + packet_set_type(rpacket, D2DBS_D2GS_GET_DATA_REPLY); + + bn_int_set(&rpacket->u.d2dbs_d2gs_get_data_reply.h.seqno, bn_int_get(packet->u.d2dbs_d2gs_get_data_reply.h.seqno)); + + bn_int_set(&rpacket->u.d2dbs_d2gs_get_data_reply.result, result); + if (datatype == D2GS_DATA_CHARSAVE && result == D2DBS_GET_DATA_SUCCESS && charinfo.has_value()) + { + bn_int_set(&rpacket->u.d2dbs_d2gs_get_data_reply.charcreatetime, bn_int_get(reinterpret_cast<t_d2charinfo_file*>(charinfo.value().data())->header.create_time)); + + // FIXME: this should be rewritten to support string formatted std::time + if (bn_int_get(reinterpret_cast<t_d2charinfo_file*>(charinfo.value().data())->header.create_time) >= prefs_get_ladderinit_time()) + { + bn_int_set(&rpacket->u.d2dbs_d2gs_get_data_reply.allowladder, 1); + } + else + { + bn_int_set(&rpacket->u.d2dbs_d2gs_get_data_reply.allowladder, 0); + } + } + else + { + bn_int_set(&rpacket->u.d2dbs_d2gs_get_data_reply.charcreatetime, 0); + bn_int_set(&rpacket->u.d2dbs_d2gs_get_data_reply.allowladder, 0); + } + bn_short_set(&rpacket->u.d2dbs_d2gs_get_data_reply.datatype, datatype); + packet_append_string(rpacket, char_name); + if (result == D2DBS_GET_DATA_SUCCESS) + { + if (datatype == D2GS_DATA_CHARSAVE) + { + bn_short_set(&rpacket->u.d2dbs_d2gs_get_data_reply.datalen, charsave.value().size()); + packet_append_data(rpacket, charsave.value().data(), charsave.value().size()); + } + else + { + bn_short_set(&rpacket->u.d2dbs_d2gs_get_data_reply.datalen, charinfo.value().size()); + packet_append_data(rpacket, charinfo.value().data(), charinfo.value().size()); + } + } + else + { + bn_short_set(&rpacket->u.d2dbs_d2gs_get_data_reply.datalen, 0); + } + + + conn_push_outqueue(c, rpacket); + + packet_del_ref(rpacket); + } + + return 0; + } + + static int dbs_packet_updateladder(t_d2dbs_connection* c, const t_packet* const packet) + { + if (packet_get_size(packet) < sizeof(t_d2gs_d2dbs_update_ladder)) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad UPDATELADDER packet (expected {} bytes, got {})", c->sd, sizeof(t_d2gs_d2dbs_update_ladder), packet_get_size(packet)); + return -1; + } + + const char* const char_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_update_ladder), MAX_CHARNAME_LEN); + if (!char_name) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad UPDATELADDER (missing or too long char name)", c->sd); + return -1; + } + + const char* const realm_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_update_ladder) + std::strlen(char_name) + 1, MAX_REALMNAME_LEN); + if (!realm_name) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad UPDATELADDER (missing or too long realm name)", c->sd); + return -1; + } + + t_d2ladder_info charladderinfo = {}; + std::strcpy(charladderinfo.charname, char_name); + charladderinfo.experience = bn_int_get(packet->u.d2gs_d2dbs_update_ladder.charexplow); + charladderinfo.level = bn_int_get(packet->u.d2gs_d2dbs_update_ladder.charlevel); + charladderinfo.status = bn_short_get(packet->u.d2gs_d2dbs_update_ladder.charstatus); + charladderinfo.chclass = bn_short_get(packet->u.d2gs_d2dbs_update_ladder.charclass); + + eventlog(eventlog_level_info, __FUNCTION__, "update ladder for {}@{} for gs {}({})", char_name, realm_name, c->serverip, c->serverid); + d2ladder_update(&charladderinfo); + + return 0; + } + + static int dbs_packet_charlock(t_d2dbs_connection* c, const t_packet* const packet) + { + if (packet_get_size(packet) < sizeof(t_d2gs_d2dbs_char_lock)) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad CHARLOCK packet (expected {} bytes, got {})", c->sd, sizeof(t_d2gs_d2dbs_char_lock), packet_get_size(packet)); + return -1; + } + + const char* const account_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_char_lock), MAX_USERNAME_LEN); + if (!account_name) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad GETDATAREQUEST (missing or too long account name)", c->sd); + return -1; + } + + const char* const char_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_char_lock) + std::strlen(account_name) + 1, MAX_CHARNAME_LEN); + if (!char_name) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad GETDATAREQUEST (missing or too long char name)", c->sd); + return -1; + } + + const char* const realm_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_char_lock) + std::strlen(account_name) + 1 + std::strlen(char_name) + 1, MAX_REALMNAME_LEN); + if (!realm_name) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad GETDATAREQUEST (missing or too long realm name)", c->sd); + return -1; + } + + if (bn_int_get(packet->u.d2gs_d2dbs_char_lock.lockstatus)) + { + if (cl_lock_char((unsigned char*)char_name, (unsigned char*)realm_name, c->serverid) != 0) + { + eventlog(eventlog_level_error, __FUNCTION__, "failed to lock character {}(*{})@{} for gs {}({})", char_name, account_name, realm_name, c->serverip, c->serverid); + } + else + { + eventlog(eventlog_level_info, __FUNCTION__, "lock character {}(*{})@{} for gs {}({})", char_name, account_name, realm_name, c->serverip, c->serverid); + } + } + else + { + if (cl_unlock_char((unsigned char*)char_name, (unsigned char*)realm_name, c->serverid) != 0) + { + eventlog(eventlog_level_error, __FUNCTION__, "failed to unlock character {}(*{})@{} for gs {}({})", char_name, account_name, realm_name, c->serverip, c->serverid); + } + else + { + eventlog(eventlog_level_info, __FUNCTION__, "unlock character {}(*{})@{} for gs {}({})", char_name, account_name, realm_name, c->serverip, c->serverid); + } + } + + return 0; + } + + + /*************************************************************************************/ +#define CHARINFO_SIZE 0xC0 +#define CHARINFO_PORTRAIT_LEVEL_OFFSET 0x89 +#define CHARINFO_PORTRAIT_STATUS_OFFSET 0x8A +#define CHARINFO_SUMMARY_LEVEL_OFFSET 0xB8 +#define CHARINFO_SUMMARY_STATUS_OFFSET 0xB4 +#define CHARINFO_PORTRAIT_GFX_OFFSET 0x72 +#define CHARINFO_PORTRAIT_COLOR_OFFSET 0x7E + +#define CHARSAVE_LEVEL_OFFSET 0x2B +#define CHARSAVE_STATUS_OFFSET 0x24 +#define CHARSAVE_GFX_OFFSET 0x88 +#define CHARSAVE_COLOR_OFFSET 0x98 + +#define charstatus_to_portstatus(status) ((((status & 0xFF00) << 1) | (status & 0x00FF)) | 0x8080) +#define portstatus_to_charstatus(status) (((status & 0x7F00) >> 1) | (status & 0x007F)) + + static void dbs_packet_set_charinfo_level(const char* char_name, char* charinfo) + { + if (prefs_get_difficulty_hack()) + { /* difficulty hack enabled */ + unsigned int level = bn_int_get((bn_basic*)&charinfo[CHARINFO_SUMMARY_LEVEL_OFFSET]); + unsigned int plevel = bn_byte_get((bn_basic*)&charinfo[CHARINFO_PORTRAIT_LEVEL_OFFSET]); + + /* levels 257 thru 355 */ + if (level != plevel) + { + eventlog(eventlog_level_info, __FUNCTION__, "level mis-match for {} ( {} != {} ) setting to 255", char_name, level, plevel); + bn_byte_set((bn_byte*)&charinfo[CHARINFO_PORTRAIT_LEVEL_OFFSET], 255); + bn_int_set((bn_int*)&charinfo[CHARINFO_SUMMARY_LEVEL_OFFSET], 255); + } + } + } + + static int dbs_packet_fix_charinfo(t_d2dbs_connection* c, const char* AccountName, const char* CharName, const char* charsave) + { + if (prefs_get_difficulty_hack()) + { + unsigned int level = bn_byte_get((bn_basic*)&charsave[CHARSAVE_LEVEL_OFFSET]); + unsigned short status = bn_short_get((bn_basic*)&charsave[CHARSAVE_STATUS_OFFSET]); + unsigned short pstatus = charstatus_to_portstatus(status); + int i; + + /* + * charinfo is only updated from level 1 to 99 (d2gs issue) + * from 100 to 256 d2gs does not send it + * when value rolls over (level 256 = 0) + * and charactar reaches level 257 (rolled over to level 1) + * d2gs starts sending it agian until level 356 (rolled over to 100) + * is reached agian. etc. etc. etc. + */ + if (level == 0) /* level 256, 512, 768, etc */ + level = 255; + + if (level < 100) + return 1; /* d2gs will send charinfo - level will be set to 255 at that std::time if needed */ + + eventlog(eventlog_level_info, __FUNCTION__, "level {} > 99 for {}", level, CharName); + + nonstd::optional<std::vector<std::uint8_t>> charinfo = dbs_packet_getdata_charinfo(c, AccountName, CharName); + if (!charinfo.has_value()) + { + eventlog(eventlog_level_error, __FUNCTION__, "unable to get charinfo for {}", CharName); + return 0; + } + + /* if level in charinfo file is already set to 255, + * then is must have been set when d2gs sent the charinfo + * and got a level mis-match (levels 257 - 355) + * or level is actually 255. In eather case we set to 255 + * this should work for any level mod + */ + if (bn_byte_get(&charinfo.value().data()[CHARINFO_PORTRAIT_LEVEL_OFFSET]) == 255) + level = 255; + + eventlog(eventlog_level_info, __FUNCTION__, "updating charinfo for {} -> level = {} , status = 0x{:04X} , pstatus = 0x{:04X}", CharName, level, status, pstatus); + bn_byte_set((bn_byte*)&charinfo.value().data()[CHARINFO_PORTRAIT_LEVEL_OFFSET], level); + bn_int_set((bn_int*)&charinfo.value().data()[CHARINFO_SUMMARY_LEVEL_OFFSET], level); + bn_short_set((bn_short*)&charinfo.value().data()[CHARINFO_PORTRAIT_STATUS_OFFSET], pstatus); + bn_int_set((bn_int*)&charinfo.value().data()[CHARINFO_SUMMARY_STATUS_OFFSET], status); + + for (i = 0; i < 11; i++) + { + bn_byte_set((bn_byte*)&charinfo.value().data()[CHARINFO_PORTRAIT_GFX_OFFSET + i], bn_byte_get((bn_basic*)&charsave[CHARSAVE_GFX_OFFSET + i])); + bn_byte_set((bn_byte*)&charinfo.value().data()[CHARINFO_PORTRAIT_COLOR_OFFSET + i], bn_byte_get((bn_basic*)&charsave[CHARSAVE_GFX_OFFSET + i])); + } + + if (!(dbs_packet_savedata_charinfo(c, AccountName, CharName, reinterpret_cast<const char*>(charinfo.value().data()), charinfo.value().size()))) + { + eventlog(eventlog_level_error, __FUNCTION__, "unable to save charinfo for {}", CharName); + return 0; + } + + return 1; /* charinfo updated */ + } + + return 1; /* difficulty hack not enabled */ + } + } +} \ No newline at end of file diff --git a/src/d2dbs/handle_d2gs.h b/src/d2dbs/handle_d2gs.h new file mode 100644 index 0000000..b75768d --- /dev/null +++ b/src/d2dbs/handle_d2gs.h @@ -0,0 +1,35 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef INCLUDED_HANDLE_D2GS_H +#define INCLUDED_HANDLE_D2GS_H + +#include "connection.h" +#include "common/packet.h" + + +namespace pvpgn +{ + + namespace d2dbs + { + + int handle_d2gs_packet(t_d2dbs_connection* conn, const t_packet* const packet); + + } + +} + +#endif diff --git a/src/d2dbs/handle_init.cpp b/src/d2dbs/handle_init.cpp new file mode 100644 index 0000000..bbc85e8 --- /dev/null +++ b/src/d2dbs/handle_init.cpp @@ -0,0 +1,150 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include "common/setup_before.h" +#include "setup.h" +#include "handle_init.h" + +#include "common/addr.h" +#include "common/bn_type.h" +#include "common/eventlog.h" +#include "common/init_protocol.h" +#include "common/packet.h" +#include "common/xalloc.h" + +#include "compat/strsep.h" + +#include "connection.h" +#include "prefs.h" + +#include "common/setup_after.h" + + +namespace pvpgn +{ + + namespace d2dbs + { + + static bool dbs_verify_ipaddr(t_d2dbs_connection* c); + + + int handle_init_packet(t_d2dbs_connection* c, const t_packet* const packet) + { + if (!c) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); + return -1; + } + + if (!packet) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got NULL packet", c->sd); + return -1; + } + + if (packet_get_class(packet) != packet_class_init) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad packet (class {})", c->sd, (int)packet_get_class(packet)); + return -1; + } + + switch (packet_get_type(packet)) + { + case CLIENT_INITCONN: + switch (bn_byte_get(packet->u.client_initconn.cclass)) + { + case CLIENT_INITCONN_CLASS_D2GS_D2DBS: + eventlog(eventlog_level_info, __FUNCTION__, "[{}] client initiated d2gs connection", c->sd); + + if (!dbs_verify_ipaddr(c)) + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] d2gs connection from unknown ip address {}", c->sd, addr_num_to_ip_str(c->ipaddr)); + return -1; + } + + conn_set_class(c, conn_class_d2gs); + conn_set_state(c, conn_state_loggedin); + + break; + default: + eventlog(eventlog_level_error, __FUNCTION__, "[{}] client requested unknown class 0x{:02x} (length {}) (closing connection)", c->sd, bn_byte_get(packet->u.client_initconn.cclass), packet_get_size(packet)); + return -1; + } + break; + default: + eventlog(eventlog_level_error, __FUNCTION__, "[{}] unknown init packet type 0x{:04x}, len {}", c->sd, packet_get_type(packet), packet_get_size(packet)); + return -1; + } + + return 0; + } + + static bool dbs_verify_ipaddr(t_d2dbs_connection* c) + { + bool c_ipaddr_is_valid = false; + + { + char* adlist = xstrdup(d2dbs_prefs_get_d2gs_list()); + char* temp = adlist; + char* s; + while ((s = strsep(&temp, ","))) + { + unsigned int resolveipaddr = 0; + host_lookup(s, &resolveipaddr); + if (resolveipaddr == 0) continue; + + if (c->ipaddr == resolveipaddr) + { + c_ipaddr_is_valid = true; + break; + } + } + xfree(adlist); + } + + if (c_ipaddr_is_valid) + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] ip address {} is valid", c->sd, addr_num_to_ip_str(c->ipaddr)); + + for (auto tempc : connlist()) + { + if (!tempc) + { + continue; + } + + if (tempc != c && tempc->ipaddr == c->ipaddr) + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] destroying previous connection {} from same ip address", c->sd, tempc->serverid); + conn_set_state(tempc, conn_state_destroy); + } + } + + c->verified = 1; + + return true; + } + else + { + eventlog(eventlog_level_info, __FUNCTION__, "[{}] ip address {} is invalid", c->sd, addr_num_to_ip_str(c->ipaddr)); + } + + return false; + } + + } + +} diff --git a/src/d2dbs/handle_init.h b/src/d2dbs/handle_init.h new file mode 100644 index 0000000..5cbe24b --- /dev/null +++ b/src/d2dbs/handle_init.h @@ -0,0 +1,34 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef INCLUDED_HANDLE_INIT_H +#define INCLUDED_HANDLE_INIT_H + +#include "connection.h" +#include "common/packet.h" + +namespace pvpgn +{ + + namespace d2dbs + { + + int handle_init_packet(t_d2dbs_connection* c, const t_packet* const packet); + + } + +} + +#endif diff --git a/src/d2dbs/handle_signal.cpp b/src/d2dbs/handle_signal.cpp index f0ae4cc..eb72f98 100644 --- a/src/d2dbs/handle_signal.cpp +++ b/src/d2dbs/handle_signal.cpp @@ -79,7 +79,7 @@ namespace pvpgn } if (signal_data.exit_time) { now = std::time(NULL); - if (now >= (signed)signal_data.exit_time) { + if (now >= signal_data.exit_time) { signal_data.exit_time = 0; eventlog(eventlog_level_info, __FUNCTION__, "shutdown server due to std::signal"); return -1; @@ -182,6 +182,11 @@ namespace pvpgn } #endif + std::time_t d2dbs_get_exit_time() + { + return signal_data.exit_time; + } + } } diff --git a/src/d2dbs/handle_signal.h b/src/d2dbs/handle_signal.h index 315efb1..237cba4 100644 --- a/src/d2dbs/handle_signal.h +++ b/src/d2dbs/handle_signal.h @@ -18,6 +18,8 @@ #ifndef INCLUDED_HANDLE_SIGNAL_H #define INCLUDED_HANDLE_SIGNAL_H +#include <ctime> + namespace pvpgn { @@ -35,6 +37,8 @@ namespace pvpgn extern int d2dbs_handle_signal(void); + extern std::time_t d2dbs_get_exit_time(); + } } diff --git a/src/d2dbs/main.cpp b/src/d2dbs/main.cpp index 7cea46c..5fea537 100644 --- a/src/d2dbs/main.cpp +++ b/src/d2dbs/main.cpp @@ -56,7 +56,6 @@ using namespace pvpgn::d2dbs; using namespace pvpgn; -static std::FILE * eventlog_fp; char serviceLongName[] = "d2dbs service"; char serviceName[] = "d2dbs"; @@ -64,8 +63,6 @@ char serviceDescription[] = "Diablo 2 DataBase Server"; int g_ServiceStatus = -1; -static int init(void); -static int cleanup(void); static int config_init(int argc, char * * argv); static int config_cleanup(void); static int setup_daemon(void); @@ -137,16 +134,6 @@ static char * write_to_pidfile(void) return pidfile; } -static int init(void) -{ - return 0; -} - -static int cleanup(void) -{ - return 0; -} - static int config_init(int argc, char * * argv) { char const * levels; @@ -221,8 +208,9 @@ static int config_cleanup(void) { d2dbs_prefs_unload(); cmdline_unload(); +#ifndef WIN32_GUI eventlog_close(); - if (eventlog_fp) std::fclose(eventlog_fp); +#endif return 0; } @@ -230,11 +218,11 @@ static int config_cleanup(void) #ifdef WIN32_GUI extern int app_main(int argc, char ** argv) #else -extern int main(int argc, char ** argv) +extern int main(int argc, char** argv) #endif { int pid; - char * pidfile; + char* pidfile; #ifdef WIN32 // create a dump file whenever the gateway crashes @@ -243,29 +231,36 @@ extern int main(int argc, char ** argv) eventlog_set(stderr); pid = config_init(argc, argv); - if (!(pid == 0)) { + if (!(pid == 0)) + { // if (pid==1) pid=0; return pid; } pidfile = write_to_pidfile(); - eventlog(eventlog_level_info, __FUNCTION__, D2DBS_VERSION); - if (init() < 0) { - eventlog(eventlog_level_error, __FUNCTION__, "failed to init"); - return -1; - } - else { - eventlog(eventlog_level_info, __FUNCTION__, "server initialized"); - } + + #ifndef WIN32 d2dbs_handle_signal_init(); #endif - dbs_server_main(); - cleanup(); + + int startup_status = pre_server_startup(); + + if (startup_status == 0) + { + if (!server_process()) + { + eventlog(eventlog_level_fatal, __FUNCTION__, "failed to initialize network (exiting)"); + } + } + + post_server_shutdown(startup_status); + if (pidfile) { if (std::remove(pidfile) < 0) eventlog(eventlog_level_error, __FUNCTION__, "could not remove pid file \"{}\" (std::remove: {})", pidfile, std::strerror(errno)); xfree((void *)pidfile); /* avoid warning */ } + eventlog(eventlog_level_info, __FUNCTION__, "server has shut down"); config_cleanup(); return 0; -} +} \ No newline at end of file diff --git a/src/d2dbs/pgsid.cpp b/src/d2dbs/pgsid.cpp new file mode 100644 index 0000000..87dfbdd --- /dev/null +++ b/src/d2dbs/pgsid.cpp @@ -0,0 +1,75 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include "common/setup_before.h" +#include "setup.h" +#include "pgsid.h" + +#include "common/xalloc.h" + +#include "common/setup_after.h" + + +namespace pvpgn +{ + + namespace d2dbs + { + + static int dbs_packet_gs_id = 0; + static t_preset_d2gsid* preset_d2gsid_head = nullptr; + + + unsigned int pgsid_get_id(unsigned int ipaddr) + { + t_preset_d2gsid* pgsid; + + pgsid = preset_d2gsid_head; + while (pgsid) + { + if (pgsid->ipaddr == ipaddr) + return pgsid->d2gsid; + pgsid = pgsid->next; + } + + // not found, build a new item + pgsid = (t_preset_d2gsid*)xmalloc(sizeof(t_preset_d2gsid)); + pgsid->ipaddr = ipaddr; + pgsid->d2gsid = ++dbs_packet_gs_id; + + // add to list + pgsid->next = preset_d2gsid_head; + preset_d2gsid_head = pgsid; + return preset_d2gsid_head->d2gsid; + } + + void pgsid_destroy() + { + if (preset_d2gsid_head) + { + t_preset_d2gsid* curr; + t_preset_d2gsid* next; + + for (curr = preset_d2gsid_head; curr; curr = next) + { + next = curr->next; + xfree(curr); + } + } + } + + } + +} \ No newline at end of file diff --git a/src/d2dbs/pgsid.h b/src/d2dbs/pgsid.h new file mode 100644 index 0000000..25a074a --- /dev/null +++ b/src/d2dbs/pgsid.h @@ -0,0 +1,41 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef INCLUDED_PGSID_H +#define INCLUDED_PGSID_H + + +namespace pvpgn +{ + + namespace d2dbs + { + + typedef struct raw_preset_d2gsid + { + unsigned int ipaddr; + unsigned int d2gsid; + struct raw_preset_d2gsid* next; + } t_preset_d2gsid; + + + unsigned int pgsid_get_id(unsigned int ipaddr); + void pgsid_destroy(); + + } + +} + +#endif \ No newline at end of file diff --git a/src/d2dbs/setup.h b/src/d2dbs/setup.h index 52dc69b..29c3ecc 100644 --- a/src/d2dbs/setup.h +++ b/src/d2dbs/setup.h @@ -38,6 +38,8 @@ constexpr long kBufferSize = 1024L * 20L; #ifndef D2DBS_DEFAULT_CONF_FILE # define D2DBS_DEFAULT_CONF_FILE "conf/d2dbs.conf" #endif +#define D2DBS_POLL_INTERVAL 20 /* 20 ms */ +#define D2DBS_FDWATCH_MAX_CONNECTIONS 512 #define DEFAULT_MEMLOG_FILE "/tmp/d2dbs-mem.std::log" #define DEFAULT_LISTEN_PORT 6114 #define D2DBS_SERVER_ADDRS "0.0.0.0"