#include "clientthread.h" #include #include #include #include #include "../../bscript/bobject.h" #include "../../clib/esignal.h" #include "../../clib/fdump.h" #include "../../clib/logfacility.h" #include "../../clib/network/singlepoller.h" #include "../../clib/passert.h" #include "../../clib/spinlock.h" #include "../../clib/stlutil.h" #include "../../plib/systemstate.h" #include "../accounts/account.h" #include "../clib/network/sockets.h" #include "../core.h" #include "../crypt/cryptbase.h" #include "../mobile/charactr.h" #include "../polcfg.h" #include "../polclock.h" #include "../polsem.h" #include "../schedule.h" #include "../scrdef.h" #include "../scrsched.h" #include "../uoscrobj.h" #include "../uworld.h" #include "cgdata.h" // This might not be needed if the client has a clear_gd() method #include "client.h" #include "msgfiltr.h" // Client could also have a method client->is_msg_allowed(), for example. Then this is not needed here. #include "msghandl.h" #include "packethelper.h" #include "packets.h" #include "pktboth.h" #include "pktbothid.h" #include "pktdef.h" #include "pktinid.h" #include #define CLIENT_CHECKPOINT( x ) client->checkpoint = x #ifdef _MSC_VER #pragma warning( disable : 4127 ) // conditional expression is constant, because of FD_SET #endif namespace Pol { namespace Core { // This function below is defined (for now) in pol.cpp. That's ugly. void call_chr_scripts( Mobile::Character* chr, const std::string& root_script_ecl, const std::string& pkg_script_ecl, bool offline = false ); void report_weird_packet( Network::Client* client, const std::string& why ); // Defined below void set_polling_timeouts( Clib::SinglePoller& poller, bool single_threaded_login ) { if ( !single_threaded_login ) { poller.set_timeout( 2, 0 ); } else { poller.set_timeout( 0, Plib::systemstate.config.select_timeout_usecs ); } } bool client_io_thread( Network::Client* client, bool login ) { int checkpoint = 0; int nidle = 0; client->last_packet_at = polclock(); client->last_activity_at = polclock(); if ( !login && Plib::systemstate.config.loglevel >= 11 ) { POLLOG.Format( "Network::Client#{} i/o thread starting\n" ) << client->instance_; } CLIENT_CHECKPOINT( 0 ); try { Clib::SinglePoller clientpoller( client->csocket ); set_polling_timeouts( clientpoller, login ); while ( !Clib::exit_signalled && client->isReallyConnected() ) { CLIENT_CHECKPOINT( 1 ); checkpoint = 1; if ( !clientpoller.prepare( client->have_queued_data() ) ) { POLLOG_INFO.Format( "Client#{}: ERROR - couldn't poll socket={}\n" ) << client->instance_ << client->csocket; if ( client->csocket != INVALID_SOCKET ) client->forceDisconnect(); break; } checkpoint = 2; int res = 0; do { CLIENT_CHECKPOINT( 2 ); res = clientpoller.wait_for_events(); CLIENT_CHECKPOINT( 3 ); } while ( res < 0 && !Clib::exit_signalled && socket_errno == SOCKET_ERRNO( EINTR ) ); checkpoint = 3; if ( res < 0 ) { int sckerr = socket_errno; POLLOG.Format( "Client#{}: select res={}, sckerr={}\n" ) << client->instance_ << res << sckerr; break; } else if ( res == 0 ) { if ( ( !client->chr || client->chr->cmdlevel() < Plib::systemstate.config.min_cmdlvl_ignore_inactivity ) && Plib::systemstate.config.inactivity_warning_timeout && Plib::systemstate.config.inactivity_disconnect_timeout ) { ++nidle; if ( nidle == 30 * Plib::systemstate.config.inactivity_warning_timeout ) { CLIENT_CHECKPOINT( 4 ); PolLock lck; // multithread Network::PktHelper::PacketOut msg; msg->Write( PKTOUT_53_WARN_CHARACTER_IDLE ); CLIENT_CHECKPOINT( 5 ); msg.Send( client ); CLIENT_CHECKPOINT( 18 ); if ( client->pause_count ) client->restart(); } else if ( nidle == 30 * Plib::systemstate.config.inactivity_disconnect_timeout ) { client->forceDisconnect(); } } } CLIENT_CHECKPOINT( 19 ); if ( !client->isReallyConnected() ) break; if ( clientpoller.error() ) { client->forceDisconnect(); break; } // region Speedhack if ( !client->movementqueue.empty() ) // not empty then process the first packet { PolLock lck; // multithread Network::PacketThrottler pkt = client->movementqueue.front(); if ( client->SpeedHackPrevention( false ) ) { if ( client->isReallyConnected() ) { unsigned char msgtype = pkt.pktbuffer[0]; Network::MSG_HANDLER packetHandler = Network::PacketRegistry::find_handler( msgtype, client ); try { INFO_PRINT_TRACE( 10 ) << "Client#" << client->instance_ << ": message 0x" << fmt::hexu( msgtype ) << "\n"; CLIENT_CHECKPOINT( 26 ); packetHandler.func( client, pkt.pktbuffer ); CLIENT_CHECKPOINT( 27 ); restart_all_clients(); } catch ( std::exception& ex ) { POLLOG_ERROR.Format( "Client#{}: Exception in message handler 0x{:X}: {}\n" ) << client->instance_ << (int)msgtype << ex.what(); fmt::Writer tmp; Clib::fdump( tmp, pkt.pktbuffer, 7 ); POLLOG << tmp.str() << "\n"; restart_all_clients(); throw; } } client->movementqueue.pop(); } } // endregion Speedhack if ( clientpoller.incoming() ) { checkpoint = 4; CLIENT_CHECKPOINT( 6 ); if ( process_data( client ) ) { CLIENT_CHECKPOINT( 17 ); PolLock lck; // reset packet timer client->last_packet_at = polclock(); if ( !check_inactivity( client ) ) { nidle = 0; client->last_activity_at = polclock(); } checkpoint = 5; CLIENT_CHECKPOINT( 7 ); send_pulse(); if ( TaskScheduler::is_dirty() ) wake_tasks_thread(); } } checkpoint = 6; polclock_t polclock_now = polclock(); if ( ( ( polclock_now - client->last_packet_at ) / POLCLOCKS_PER_SEC ) >= 120 ) // 2 mins { client->forceDisconnect(); break; } if ( client->have_queued_data() && clientpoller.writable() ) { PolLock lck; CLIENT_CHECKPOINT( 8 ); client->send_queued_data(); } checkpoint = 7; CLIENT_CHECKPOINT( 21 ); if ( login ) break; } } catch ( std::string& str ) { POLLOG_ERROR.Format( "Client#{}: Exception in i/o thread: {}! (checkpoint={})\n" ) << client->instance_ << str << checkpoint; } catch ( const char* msg ) { POLLOG_ERROR.Format( "Client#{}: Exception in i/o thread: {}! (checkpoint={})\n" ) << client->instance_ << msg << checkpoint; } catch ( std::exception& ex ) { POLLOG_ERROR.Format( "Client#{}: Exception in i/o thread: {}! (checkpoint={})\n" ) << client->instance_ << ex.what() << checkpoint; } CLIENT_CHECKPOINT( 20 ); if ( login && client->isConnected() ) return true; POLLOG.Format( "Client#{} ({}): disconnected (account {})\n" ) << client->instance_ << client->ipaddrAsString() << ( ( client->acct != nullptr ) ? client->acct->name() : "unknown" ); try { // if (1) { CLIENT_CHECKPOINT( 9 ); PolLock lck; client->unregister(); INFO_PRINT << "Client disconnected from " << client->ipaddrAsString() << " (" << networkManager.clients.size() << "/" << networkManager.getNumberOfLoginClients() << " connections)\n"; CoreSetSysTrayToolTip( Clib::tostring( networkManager.clients.size() ) + " clients connected", ToolTipPrioritySystem ); } checkpoint = 8; CLIENT_CHECKPOINT( 10 ); if ( client->chr ) { // if (1) int seconds_wait = 0; { CLIENT_CHECKPOINT( 11 ); PolLock lck; if ( client->chr ) { client->chr->disconnect_cleanup(); client->gd->clear(); client->chr->connected( false ); ScriptDef sd; sd.quickconfig( "scripts/misc/logofftest.ecl" ); if ( sd.exists() ) { CLIENT_CHECKPOINT( 12 ); Bscript::BObject bobj( run_script_to_completion( sd, new Module::ECharacterRefObjImp( client->chr ) ) ); if ( bobj.isa( Bscript::BObjectImp::OTLong ) ) { const Bscript::BLong* blong = static_cast( bobj.impptr() ); seconds_wait = blong->value(); } } } } polclock_t when_logoff = client->last_activity_at + seconds_wait * POLCLOCKS_PER_SEC; checkpoint = 9; CLIENT_CHECKPOINT( 13 ); while ( !Clib::exit_signalled ) { CLIENT_CHECKPOINT( 14 ); { PolLock lck; if ( polclock() >= when_logoff ) break; } pol_sleep_ms( 2000 ); } checkpoint = 10; CLIENT_CHECKPOINT( 15 ); // if (1) { PolLock lck; if ( client->chr ) { Mobile::Character* chr = client->chr; CLIENT_CHECKPOINT( 16 ); call_chr_scripts( chr, "scripts/misc/logoff.ecl", "logoff.ecl" ); if ( chr->realm ) { chr->realm->notify_left( *chr ); } } } } } catch ( std::exception& ex ) { POLLOG.Format( "Client#{}: Exception in i/o thread: {}! (checkpoint={}, what={})\n" ) << client->instance_ << checkpoint << ex.what(); } // queue delete of client ptr see method doc for reason Core::networkManager.clientTransmit->QueueDelete( client ); return false; } bool valid_message_length( Network::Client* client, unsigned int length ) { if ( length > sizeof client->buffer ) { handle_humongous_packet( client, client->message_length ); return false; } if ( length < 3 ) { report_weird_packet( client, "Too-short message" ); return false; } return true; } // bool - return true when a message was processed. bool process_data( Network::Client* client ) { // NOTE: This is coded such that for normal messages, which are completely available, // this function will get the type, then the length, then the data, without having // to wait for a second or third call. // Also, the abnormal state, RECV_STATE_CRYPTSEED_WAIT, is handled at the end, so in // normal processing its code doesn't see the code path. passert( client->bufcheck1_AA == 0xAA ); passert( client->bufcheck2_55 == 0x55 ); if ( client->recv_state == Network::Client::RECV_STATE_MSGTYPE_WAIT ) { client->bytes_received = 0; client->recv_remaining( 1 ); CLIENT_CHECKPOINT( 22 ); if ( client->bytes_received < 1 ) // this really should never happen. { client->forceDisconnect(); return false; } unsigned char msgtype = client->buffer[0]; client->last_msgtype = msgtype; // CNXBUG if ( Plib::systemstate.config.verbose ) INFO_PRINT.Format( "Incoming msg type: 0x{:X}\n" ) << (int)msgtype; if ( !Network::PacketRegistry::is_defined( msgtype ) ) { handle_undefined_packet( client ); return false; // remain in RECV_STATE_MSGTYPE_WAIT } Network::MSG_HANDLER packetHandler = Network::PacketRegistry::find_handler( msgtype, client ); if ( packetHandler.msglen == MSGLEN_2BYTELEN_DATA ) { client->recv_state = Network::Client::RECV_STATE_MSGLEN_WAIT; } else { passert( packetHandler.msglen > 0 ); client->recv_state = Network::Client::RECV_STATE_MSGDATA_WAIT; client->message_length = packetHandler.msglen; } } /* endif of RECV_STATE_MSGTYPE_WAIT */ if ( client->recv_state == Network::Client::RECV_STATE_MSGLEN_WAIT ) { client->recv_remaining( 3 ); CLIENT_CHECKPOINT( 23 ); if ( client->bytes_received == 3 ) // the length bytes were available. { // MSG is [MSGTYPE] [LENHI] [LENLO] [DATA ... ] client->message_length = ( client->buffer[1] << 8 ) + client->buffer[2]; if ( !valid_message_length( client, client->message_length ) ) { // If the reported length is too short (less than 3 bytes) or // too big (larger than the client buffer), something very odd // happened. client->forceDisconnect(); return false; } client->recv_state = Network::Client::RECV_STATE_MSGDATA_WAIT; } // else keep waiting. } /* endif of RECV_STATE_MSGLEN_WAIT */ if ( client->recv_state == Network::Client::RECV_STATE_MSGDATA_WAIT ) { CLIENT_CHECKPOINT( 24 ); client->recv_remaining( client->message_length ); CLIENT_CHECKPOINT( 25 ); if ( client->bytes_received == client->message_length ) // we have the whole message { unsigned char msgtype = client->buffer[0]; networkManager.iostats.received[msgtype].count++; networkManager.iostats.received[msgtype].bytes += client->message_length; { Clib::SpinLockGuard guard( client->_fpLog_lock ); if ( !client->fpLog.empty() ) { fmt::Writer tmp; tmp << "Client -> Server: 0x" << fmt::hexu( msgtype ) << ", " << client->message_length << " bytes\n"; Clib::fdump( tmp, &client->buffer, client->message_length ); FLEXLOG( client->fpLog ) << tmp.str() << "\n"; } } if ( Plib::systemstate.config.verbose ) INFO_PRINT.Format( "Message Received: Type 0x{:X}, Length {} bytes\n" ) << (int)msgtype << client->message_length; PolLock lck; // multithread // it can happen that a client gets disconnected while waiting for the lock. if ( client->isConnected() ) { if ( client->msgtype_filter->msgtype_allowed[msgtype] ) { // region Speedhack if ( ( settingsManager.ssopt.speedhack_prevention ) && ( msgtype == PKTIN_02_ID ) ) { if ( !client->SpeedHackPrevention() ) { // client->SpeedHackPrevention() added packet to queue client->recv_state = Network::Client::RECV_STATE_MSGTYPE_WAIT; CLIENT_CHECKPOINT( 28 ); return true; } } // endregion Speedhack Network::MSG_HANDLER packetHandler = Network::PacketRegistry::find_handler( msgtype, client ); passert( packetHandler.msglen != 0 ); try { INFO_PRINT_TRACE( 10 ) << "Client#" << client->instance_ << ": message 0x" << fmt::hexu( msgtype ) << "\n"; CLIENT_CHECKPOINT( 26 ); packetHandler.func( client, client->buffer ); CLIENT_CHECKPOINT( 27 ); restart_all_clients(); } catch ( std::exception& ex ) { POLLOG_ERROR.Format( "Client#{}: Exception in message handler 0x{:X}: {}\n" ) << client->instance_ << (int)msgtype << ex.what(); fmt::Writer tmp; Clib::fdump( tmp, client->buffer, client->bytes_received ); POLLOG << tmp.str() << "\n"; restart_all_clients(); throw; } } else { POLLOG_ERROR.Format( "Client#{} ({}, Acct {}) sent non-allowed message type 0x{:X}.\n" ) << client->instance_ << client->ipaddrAsString() << ( client->acct ? client->acct->name() : "unknown" ) << (int)msgtype; } } client->recv_state = Network::Client::RECV_STATE_MSGTYPE_WAIT; CLIENT_CHECKPOINT( 28 ); return true; } // else keep waiting } /* endif RECV_STATE_MSGDATA_WAIT */ else if ( client->recv_state == Network::Client::RECV_STATE_CRYPTSEED_WAIT ) { // The abnormal case. // The first four bytes after connection are the // crypto seed client->recv_remaining_nocrypt( 4 ); if ( client->bytes_received == 4 ) { /* The first four bytes transmitted are the encryption seed */ unsigned char cstype = client->buffer[0]; if ( ( client->buffer[0] == 0xff ) && ( client->buffer[1] == 0xff ) && ( client->buffer[2] == 0xff ) && ( client->buffer[3] == 0xff ) ) { if ( Plib::systemstate.config.verbose ) { INFO_PRINT.Format( "UOKR Seed Message Received: Type 0x{:X}\n" ) << (int)cstype; } Network::PktHelper::PacketOut msg; msg->WriteFlipped( 77u ); msg->WriteFlipped( 0x03u ); msg->Write( 0x02u ); msg->Write( 0x01u ); msg->Write( 0x03u ); msg->WriteFlipped( 0x13u ); msg->Write( 0x02u ); msg->Write( 0x11u ); msg->Write( 0x00u ); msg->Write( 0xfcu ); msg->Write( 0x2fu ); msg->Write( 0xe3u ); msg->Write( 0x81u ); msg->Write( 0x93u ); msg->Write( 0xcbu ); msg->Write( 0xafu ); msg->Write( 0x98u ); msg->Write( 0xddu ); msg->Write( 0x83u ); msg->Write( 0x13u ); msg->Write( 0xd2u ); msg->Write( 0x9eu ); msg->Write( 0xeau ); msg->Write( 0xe4u ); msg->Write( 0x13u ); msg->WriteFlipped( 0x10u ); msg->Write( 0x78u ); msg->Write( 0x13u ); msg->Write( 0xb7u ); msg->Write( 0x7bu ); msg->Write( 0xceu ); msg->Write( 0xa8u ); msg->Write( 0xd7u ); msg->Write( 0xbcu ); msg->Write( 0x52u ); msg->Write( 0xdeu ); msg->Write( 0x38u ); msg->Write( 0x30u ); msg->Write( 0xeau ); msg->Write( 0xe9u ); msg->Write( 0x1eu ); msg->Write( 0xa3u ); msg->WriteFlipped( 0x20u ); msg->WriteFlipped( 0x10u ); msg->Write( 0x5au ); msg->Write( 0xceu ); msg->Write( 0x3eu ); msg->Write( 0xe3u ); msg->Write( 0x97u ); msg->Write( 0x92u ); msg->Write( 0xe4u ); msg->Write( 0x8au ); msg->Write( 0xf1u ); msg->Write( 0x9au ); msg->Write( 0xd3u ); msg->Write( 0x04u ); msg->Write( 0x41u ); msg->Write( 0x03u ); msg->Write( 0xcbu ); msg->Write( 0x53u ); client->recv_state = Network::Client::RECV_STATE_MSGTYPE_WAIT; client->setClientType( Network::CLIENTTYPE_UOKR ); // UO:KR logging in msg.Send( client ); } else if ( client->buffer[0] == PKTIN_EF_ID ) // new seed since 6.0.5.0 (0xef should never appear in normal ipseed) { if ( Plib::systemstate.config.verbose ) { INFO_PRINT.Format( "6.0.5.0+ Crypt Seed Message Received: Type 0x{:X}\n" ) << (int)cstype; } client->recv_state = Network::Client::RECV_STATE_CLIENTVERSION_WAIT; } else { client->cryptengine->Init( client->buffer, Crypt::CCryptBase::typeAuto ); client->recv_state = Network::Client::RECV_STATE_MSGTYPE_WAIT; } } // Else keep waiting for IP address. } if ( client->recv_state == Network::Client::RECV_STATE_CLIENTVERSION_WAIT ) { client->recv_remaining_nocrypt( 21 ); // receive and send to handler to get directly the version if ( client->bytes_received == 21 ) { client->recv_state = Network::Client::RECV_STATE_MSGTYPE_WAIT; unsigned char tempseed[4]; tempseed[0] = client->buffer[1]; tempseed[1] = client->buffer[2]; tempseed[2] = client->buffer[3]; tempseed[3] = client->buffer[4]; client->cryptengine->Init( tempseed, Crypt::CCryptBase::typeLogin ); Network::PacketRegistry::handle_msg( PKTIN_EF_ID, client, client->buffer ); } } return false; } bool check_inactivity( Network::Client* client ) { switch ( client->buffer[0] ) { case PKTBI_73_ID: // Fallthrough case PKTIN_09_ID: // Fallthrough case PKTBI_D6_IN_ID: return true; case PKTBI_BF_ID: if ( ( client->buffer[3] == 0 ) && ( client->buffer[4] == PKTBI_BF::TYPE_SESPAM ) ) return true; break; default: return false; } return false; } void report_weird_packet( Network::Client* client, const std::string& why ) { fmt::Writer tmp; tmp.Format( "Client#{}: {} type 0x{:X}, {} bytes (IP: {}, Account: {})\n" ) << client->instance_ << why << (int)client->buffer[0] << client->bytes_received << client->ipaddrAsString() << ( ( client->acct != nullptr ) ? client->acct->name() : "None" ); if ( client->bytes_received <= 64 ) { Clib::fdump( tmp, client->buffer, client->bytes_received ); POLLOG_INFO << tmp.str() << "\n"; } else { INFO_PRINT << tmp.str(); Clib::fdump( tmp, client->buffer, client->bytes_received ); POLLOG << tmp.str() << "\n"; } } // Called when a packet size is registered but the // packet has no handler in the core void handle_unknown_packet( Network::Client* client ) { if ( Plib::systemstate.config.display_unknown_packets ) report_weird_packet( client, "Unknown packet" ); } // Called when POL receives an undefined packet. // Those have no registered size, so we must guess. void handle_undefined_packet( Network::Client* client ) { int msgtype = (int)client->buffer[0]; INFO_PRINT.Format( "Undefined message type 0x{:X}\n" ) << msgtype; // Tries to read as much of it out as possible client->recv_remaining( sizeof client->buffer / 2 ); report_weird_packet( client, "Unexpected message" ); } // Handles variable-sized packets whose declared size is much larger than // the receive buffer. This packet is most likely a client error, because // the buffer should be big enough to handle anything sent by the known // clients. void handle_humongous_packet( Network::Client* client, unsigned int reported_size ) { // Tries to read as much of it out as possible // (the client will be disconnected, but this may // be useful for debugging) client->recv_remaining( sizeof client->buffer / 2 ); fmt::Writer tmp; tmp.Format( "Humongous packet (length {})", reported_size ); report_weird_packet( client, tmp.str() ); } } // namespace Core } // namespace Pol