]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/network/serverpackethandler.cpp
Merge pull request #59 from PrairieAstronomer/readme_irrlicht_change
[dragonfireclient.git] / src / network / serverpackethandler.cpp
index 3db4eb2861718cf92c177d0a68dee2e64be50080..4b9de488c76b168cbb3e6f563177fe823c63e22d 100644 (file)
@@ -56,12 +56,12 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
        session_t peer_id = pkt->getPeerId();
        RemoteClient *client = getClient(peer_id, CS_Created);
 
+       Address addr;
        std::string addr_s;
        try {
-               Address address = getPeerAddress(peer_id);
-               addr_s = address.serializeString();
-       }
-       catch (con::PeerNotFoundException &e) {
+               addr = m_con->GetPeerAddress(peer_id);
+               addr_s = addr.serializeString();
+       } catch (con::PeerNotFoundException &e) {
                /*
                 * no peer for this packet found
                 * most common reason is peer timeout, e.g. peer didn't
@@ -73,19 +73,20 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
                return;
        }
 
-       // If net_proto_version is set, this client has already been handled
        if (client->getState() > CS_Created) {
                verbosestream << "Server: Ignoring multiple TOSERVER_INITs from " <<
                        addr_s << " (peer_id=" << peer_id << ")" << std::endl;
                return;
        }
 
+       client->setCachedAddress(addr);
+
        verbosestream << "Server: Got TOSERVER_INIT from " << addr_s <<
                " (peer_id=" << peer_id << ")" << std::endl;
 
        // Do not allow multiple players in simple singleplayer mode.
        // This isn't a perfect way to do it, but will suffice for now
-       if (m_simple_singleplayer_mode && m_clients.getClientIDs().size() > 1) {
+       if (m_simple_singleplayer_mode && !m_clients.getClientIDs().empty()) {
                infostream << "Server: Not allowing another client (" << addr_s <<
                        ") to connect in simple singleplayer mode" << std::endl;
                DenyAccess(peer_id, SERVER_ACCESSDENIED_SINGLEPLAYER);
@@ -173,6 +174,16 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
                return;
        }
 
+       RemotePlayer *player = m_env->getPlayer(playername);
+
+       // If player is already connected, cancel
+       if (player && player->getPeerId() != PEER_ID_INEXISTENT) {
+               actionstream << "Server: Player with name \"" << playername <<
+                       "\" tried to connect, but player with same name is already connected" << std::endl;
+               DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED);
+               return;
+       }
+
        m_clients.setPlayerName(peer_id, playername);
        //TODO (later) case insensitivity
 
@@ -216,7 +227,7 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
                Compose auth methods for answer
        */
        std::string encpwd; // encrypted Password field for the user
-       bool has_auth = m_script->getAuth(playername, &encpwd, NULL);
+       bool has_auth = m_script->getAuth(playername, &encpwd, nullptr);
        u32 auth_mechs = 0;
 
        client->chosen_mech = AUTH_MECHANISM_NONE;
@@ -316,7 +327,7 @@ void Server::handleCommand_Init2(NetworkPacket* pkt)
        // Send active objects
        {
                PlayerSAO *sao = getPlayerSAO(peer_id);
-               if (client && sao)
+               if (sao)
                        SendActiveObjectRemoveAdd(client, sao);
        }
 
@@ -351,16 +362,15 @@ void Server::handleCommand_RequestMedia(NetworkPacket* pkt)
        session_t peer_id = pkt->getPeerId();
        infostream << "Sending " << numfiles << " files to " <<
                getPlayerName(peer_id) << std::endl;
-       verbosestream << "TOSERVER_REQUEST_MEDIA: " << std::endl;
+       verbosestream << "TOSERVER_REQUEST_MEDIA: requested file(s)" << std::endl;
 
        for (u16 i = 0; i < numfiles; i++) {
                std::string name;
 
                *pkt >> name;
 
-               tosend.push_back(name);
-               verbosestream << "TOSERVER_REQUEST_MEDIA: requested file "
-                               << name << std::endl;
+               tosend.emplace_back(name);
+               verbosestream << "  " << name << std::endl;
        }
 
        sendRequestedMedia(peer_id, tosend);
@@ -370,55 +380,47 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt)
 {
        session_t peer_id = pkt->getPeerId();
 
-       PlayerSAO* playersao = StageTwoClientInit(peer_id);
-
-       if (playersao == NULL) {
-               errorstream << "TOSERVER_CLIENT_READY stage 2 client init failed "
-                       "peer_id=" << peer_id << std::endl;
-               DisconnectPeer(peer_id);
-               return;
-       }
-
-
-       if (pkt->getSize() < 8) {
-               errorstream << "TOSERVER_CLIENT_READY client sent inconsistent data, "
-                       "disconnecting peer_id: " << peer_id << std::endl;
-               DisconnectPeer(peer_id);
-               return;
-       }
-
+       // decode all information first
        u8 major_ver, minor_ver, patch_ver, reserved;
+       u16 formspec_ver = 1; // v1 for clients older than 5.1.0-dev
        std::string full_ver;
+
        *pkt >> major_ver >> minor_ver >> patch_ver >> reserved >> full_ver;
+       if (pkt->getRemainingBytes() >= 2)
+               *pkt >> formspec_ver;
 
        m_clients.setClientVersion(peer_id, major_ver, minor_ver, patch_ver,
                full_ver);
 
-       if (pkt->getRemainingBytes() >= 2)
-               *pkt >> playersao->getPlayer()->formspec_version;
-
-       const std::vector<std::string> &players = m_clients.getPlayerNames();
-       NetworkPacket list_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, peer_id);
-       list_pkt << (u8) PLAYER_LIST_INIT << (u16) players.size();
-       for (const std::string &player: players) {
-               list_pkt <<  player;
+       // Emerge player
+       PlayerSAO* playersao = StageTwoClientInit(peer_id);
+       if (!playersao) {
+               errorstream << "Server: stage 2 client init failed "
+                       "peer_id=" << peer_id << std::endl;
+               DisconnectPeer(peer_id);
+               return;
        }
-       m_clients.send(peer_id, 0, &list_pkt, true);
 
-       NetworkPacket notice_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, PEER_ID_INEXISTENT);
-       // (u16) 1 + std::string represents a pseudo vector serialization representation
-       notice_pkt << (u8) PLAYER_LIST_ADD << (u16) 1 << std::string(playersao->getPlayer()->getName());
-       m_clients.sendToAll(&notice_pkt);
+       playersao->getPlayer()->formspec_version = formspec_ver;
        m_clients.event(peer_id, CSE_SetClientReady);
 
+       // Send player list to this client
+       {
+               const std::vector<std::string> &players = m_clients.getPlayerNames();
+               NetworkPacket list_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, peer_id);
+               list_pkt << (u8) PLAYER_LIST_INIT << (u16) players.size();
+               for (const auto &player : players)
+                       list_pkt << player;
+               Send(peer_id, &list_pkt);
+       }
+
        s64 last_login;
        m_script->getAuth(playersao->getPlayer()->getName(), nullptr, nullptr, &last_login);
        m_script->on_joinplayer(playersao, last_login);
 
        // Send shutdown timer if shutdown has been scheduled
-       if (m_shutdown_state.isTimerRunning()) {
+       if (m_shutdown_state.isTimerRunning())
                SendChatMessage(peer_id, m_shutdown_state.getShutdownTimerMessage());
-       }
 }
 
 void Server::handleCommand_GotBlocks(NetworkPacket* pkt)
@@ -437,13 +439,14 @@ void Server::handleCommand_GotBlocks(NetworkPacket* pkt)
        u8 count;
        *pkt >> count;
 
-       RemoteClient *client = getClient(pkt->getPeerId());
-
        if ((s16)pkt->getSize() < 1 + (int)count * 6) {
                throw con::InvalidIncomingDataException
                                ("GOTBLOCKS length is too short");
        }
 
+       ClientInterface::AutoLock lock(m_clients);
+       RemoteClient *client = m_clients.lockedGetClientNoEx(pkt->getPeerId());
+
        for (u16 i = 0; i < count; i++) {
                v3s16 p;
                *pkt >> p;
@@ -470,7 +473,6 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao,
        f32 yaw = (f32)f32yaw / 100.0f;
        u32 keyPressed = 0;
 
-       // default behavior (in case an old client doesn't send these)
        f32 fov = 0;
        u8 wanted_range = 0;
 
@@ -485,24 +487,18 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao,
        pitch = modulo360f(pitch);
        yaw = wrapDegrees_0_360(yaw);
 
-       playersao->setBasePosition(position);
-       player->setSpeed(speed);
+       if (!playersao->isAttached()) {
+               // Only update player positions when moving freely
+               // to not interfere with attachment handling
+               playersao->setBasePosition(position);
+               player->setSpeed(speed);
+       }
        playersao->setLookPitch(pitch);
        playersao->setPlayerYaw(yaw);
        playersao->setFov(fov);
        playersao->setWantedRange(wanted_range);
 
-       player->keyPressed = keyPressed;
-       player->control.up    = (keyPressed & (0x1 << 0));
-       player->control.down  = (keyPressed & (0x1 << 1));
-       player->control.left  = (keyPressed & (0x1 << 2));
-       player->control.right = (keyPressed & (0x1 << 3));
-       player->control.jump  = (keyPressed & (0x1 << 4));
-       player->control.aux1  = (keyPressed & (0x1 << 5));
-       player->control.sneak = (keyPressed & (0x1 << 6));
-       player->control.dig   = (keyPressed & (0x1 << 7));
-       player->control.place = (keyPressed & (0x1 << 8));
-       player->control.zoom  = (keyPressed & (0x1 << 9));
+       player->control.unpackKeysPressed(keyPressed);
 
        if (playersao->checkMovementCheat()) {
                // Call callbacks
@@ -619,21 +615,36 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
 
        const bool player_has_interact = checkPriv(player->getName(), "interact");
 
-       auto check_inv_access = [player, player_has_interact] (
+       auto check_inv_access = [player, player_has_interact, this] (
                        const InventoryLocation &loc) -> bool {
-               if (loc.type == InventoryLocation::CURRENT_PLAYER)
-                       return false; // Only used internally on the client, never sent
-               if (loc.type == InventoryLocation::PLAYER) {
-                       // Allow access to own inventory in all cases
-                       return loc.name == player->getName();
-               }
 
-               if (!player_has_interact) {
+               // Players without interact may modify their own inventory
+               if (!player_has_interact && loc.type != InventoryLocation::PLAYER) {
                        infostream << "Cannot modify foreign inventory: "
                                        << "No interact privilege" << std::endl;
                        return false;
                }
-               return true;
+
+               switch (loc.type) {
+               case InventoryLocation::CURRENT_PLAYER:
+                       // Only used internally on the client, never sent
+                       return false;
+               case InventoryLocation::PLAYER:
+                       // Allow access to own inventory in all cases
+                       return loc.name == player->getName();
+               case InventoryLocation::NODEMETA:
+                       {
+                               // Check for out-of-range interaction
+                               v3f node_pos   = intToFloat(loc.p, BS);
+                               v3f player_pos = player->getPlayerSAO()->getEyePosition();
+                               f32 d = player_pos.getDistanceFrom(node_pos);
+                               return checkInteractDistance(player, d, "inventory");
+                       }
+               case InventoryLocation::DETACHED:
+                       return getInventoryMgr()->checkDetachedInventoryAccess(loc, player->getName());
+               default:
+                       return false;
+               }
        };
 
        /*
@@ -653,18 +664,6 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
                                !check_inv_access(ma->to_inv))
                        return;
 
-               InventoryLocation *remote = ma->from_inv.type == InventoryLocation::PLAYER ?
-                       &ma->to_inv : &ma->from_inv;
-
-               // Check for out-of-range interaction
-               if (remote->type == InventoryLocation::NODEMETA) {
-                       v3f node_pos   = intToFloat(remote->p, BS);
-                       v3f player_pos = player->getPlayerSAO()->getEyePosition();
-                       f32 d = player_pos.getDistanceFrom(node_pos);
-                       if (!checkInteractDistance(player, d, "inventory"))
-                               return;
-               }
-
                /*
                        Disable moving items out of craftpreview
                */
@@ -749,21 +748,8 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
 
 void Server::handleCommand_ChatMessage(NetworkPacket* pkt)
 {
-       /*
-               u16 command
-               u16 length
-               wstring message
-       */
-       u16 len;
-       *pkt >> len;
-
        std::wstring message;
-       for (u16 i = 0; i < len; i++) {
-               u16 tmp_wchar;
-               *pkt >> tmp_wchar;
-
-               message += (wchar_t)tmp_wchar;
-       }
+       *pkt >> message;
 
        session_t peer_id = pkt->getPeerId();
        RemotePlayer *player = m_env->getPlayer(peer_id);
@@ -775,15 +761,13 @@ void Server::handleCommand_ChatMessage(NetworkPacket* pkt)
                return;
        }
 
-       // Get player name of this client
        std::string name = player->getName();
-       std::wstring wname = narrow_to_wide(name);
 
-       std::wstring answer_to_sender = handleChat(name, wname, message, true, player);
+       std::wstring answer_to_sender = handleChat(name, message, true, player);
        if (!answer_to_sender.empty()) {
                // Send the answer to sender
-               SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_NORMAL,
-                       answer_to_sender, wname));
+               SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
+                       answer_to_sender));
        }
 }
 
@@ -826,8 +810,7 @@ void Server::handleCommand_Damage(NetworkPacket* pkt)
                                << std::endl;
 
                PlayerHPChangeReason reason(PlayerHPChangeReason::FALL);
-               playersao->setHP((s32)playersao->getHP() - (s32)damage, reason);
-               SendPlayerHPOrDie(playersao, reason);
+               playersao->setHP((s32)playersao->getHP() - (s32)damage, reason, true);
        }
 }
 
@@ -921,6 +904,13 @@ bool Server::checkInteractDistance(RemotePlayer *player, const f32 d, const std:
        return true;
 }
 
+// Tiny helper to retrieve the selected item into an Optional
+static inline void getWieldedItem(const PlayerSAO *playersao, Optional<ItemStack> &ret)
+{
+       ret = ItemStack();
+       playersao->getWieldedItem(&(*ret));
+}
+
 void Server::handleCommand_Interact(NetworkPacket *pkt)
 {
        /*
@@ -1046,6 +1036,12 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                if (pointed.type == POINTEDTHING_NODE) {
                        target_pos = intToFloat(pointed.node_undersurface, BS);
                } else if (pointed.type == POINTEDTHING_OBJECT) {
+                       if (playersao->getId() == pointed_object->getId()) {
+                               actionstream << "Server: " << player->getName()
+                                       << " attempted to interact with themselves" << std::endl;
+                               m_script->on_cheat(playersao, "interacted_with_self");
+                               return;
+                       }
                        target_pos = pointed_object->getBasePosition();
                }
                float d = playersao->getEyePosition().getDistanceFrom(target_pos);
@@ -1106,11 +1102,8 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                float time_from_last_punch =
                        playersao->resetTimeFromLastPunch();
 
-               u16 src_original_hp = pointed_object->getHP();
-               u16 dst_origin_hp = playersao->getHP();
-
-               u16 wear = pointed_object->punch(dir, &toolcap, playersao,
-                               time_from_last_punch);
+               u32 wear = pointed_object->punch(dir, &toolcap, playersao,
+                               time_from_last_punch, tool_item.wear);
 
                // Callback may have changed item, so get it again
                playersao->getWieldedItem(&selected_item);
@@ -1118,18 +1111,6 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                if (changed)
                        playersao->setWieldedItem(selected_item);
 
-               // If the object is a player and its HP changed
-               if (src_original_hp != pointed_object->getHP() &&
-                               pointed_object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
-                       SendPlayerHPOrDie((PlayerSAO *)pointed_object,
-                                       PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, playersao));
-               }
-
-               // If the puncher is a player and its HP changed
-               if (dst_origin_hp != playersao->getHP())
-                       SendPlayerHPOrDie(playersao,
-                                       PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, pointed_object));
-
                return;
        } // action == INTERACT_START_DIGGING
 
@@ -1175,7 +1156,8 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
 
                        // Get diggability and expected digging time
                        DigParams params = getDigParams(m_nodedef->get(n).groups,
-                                       &selected_item.getToolCapabilities(m_itemdef));
+                                       &selected_item.getToolCapabilities(m_itemdef),
+                                       selected_item.wear);
                        // If can't dig, try hand
                        if (!params.diggable) {
                                params = getDigParams(m_nodedef->get(n).groups,
@@ -1237,14 +1219,17 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
 
        // Place block or right-click object
        case INTERACT_PLACE: {
-               ItemStack selected_item;
-               playersao->getWieldedItem(&selected_item, nullptr);
+               Optional<ItemStack> selected_item;
+               getWieldedItem(playersao, selected_item);
 
                // Reset build time counter
                if (pointed.type == POINTEDTHING_NODE &&
-                               selected_item.getDefinition(m_itemdef).type == ITEM_NODE)
+                               selected_item->getDefinition(m_itemdef).type == ITEM_NODE)
                        getClient(peer_id)->m_time_from_building = 0.0;
 
+               const bool had_prediction = !selected_item->getDefinition(m_itemdef).
+                       node_placement_prediction.empty();
+
                if (pointed.type == POINTEDTHING_OBJECT) {
                        // Right click object
 
@@ -1257,11 +1242,9 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                                        << pointed_object->getDescription() << std::endl;
 
                        // Do stuff
-                       if (m_script->item_OnSecondaryUse(
-                                       selected_item, playersao, pointed)) {
-                               if (playersao->setWieldedItem(selected_item)) {
+                       if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) {
+                               if (selected_item.has_value() && playersao->setWieldedItem(*selected_item))
                                        SendInventory(playersao, true);
-                               }
                        }
 
                        pointed_object->rightClick(playersao);
@@ -1269,7 +1252,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                        // Placement was handled in lua
 
                        // Apply returned ItemStack
-                       if (playersao->setWieldedItem(selected_item))
+                       if (selected_item.has_value() && playersao->setWieldedItem(*selected_item))
                                SendInventory(playersao, true);
                }
 
@@ -1281,8 +1264,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                RemoteClient *client = getClient(peer_id);
                v3s16 blockpos = getNodeBlockPos(pointed.node_abovesurface);
                v3s16 blockpos2 = getNodeBlockPos(pointed.node_undersurface);
-               if (!selected_item.getDefinition(m_itemdef
-                               ).node_placement_prediction.empty()) {
+               if (had_prediction) {
                        client->SetBlockNotSent(blockpos);
                        if (blockpos2 != blockpos)
                                client->SetBlockNotSent(blockpos2);
@@ -1296,15 +1278,15 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
        } // action == INTERACT_PLACE
 
        case INTERACT_USE: {
-               ItemStack selected_item;
-               playersao->getWieldedItem(&selected_item, nullptr);
+               Optional<ItemStack> selected_item;
+               getWieldedItem(playersao, selected_item);
 
-               actionstream << player->getName() << " uses " << selected_item.name
+               actionstream << player->getName() << " uses " << selected_item->name
                                << ", pointing at " << pointed.dump() << std::endl;
 
                if (m_script->item_OnUse(selected_item, playersao, pointed)) {
                        // Apply returned ItemStack
-                       if (playersao->setWieldedItem(selected_item))
+                       if (selected_item.has_value() && playersao->setWieldedItem(*selected_item))
                                SendInventory(playersao, true);
                }
 
@@ -1313,16 +1295,17 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
 
        // Rightclick air
        case INTERACT_ACTIVATE: {
-               ItemStack selected_item;
-               playersao->getWieldedItem(&selected_item, nullptr);
+               Optional<ItemStack> selected_item;
+               getWieldedItem(playersao, selected_item);
 
                actionstream << player->getName() << " activates "
-                               << selected_item.name << std::endl;
+                               << selected_item->name << std::endl;
 
                pointed.type = POINTEDTHING_NOTHING; // can only ever be NOTHING
 
                if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) {
-                       if (playersao->setWieldedItem(selected_item))
+                       // Apply returned ItemStack
+                       if (selected_item.has_value() && playersao->setWieldedItem(*selected_item))
                                SendInventory(playersao, true);
                }
 
@@ -1478,11 +1461,9 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
        session_t peer_id = pkt->getPeerId();
        RemoteClient *client = getClient(peer_id, CS_Invalid);
        ClientState cstate = client->getState();
+       const std::string playername = client->getName();
 
-       std::string playername = client->getName();
-
-       std::string salt;
-       std::string verification_key;
+       std::string salt, verification_key;
 
        std::string addr_s = getPeerAddress(peer_id).serializeString();
        u8 is_empty;
@@ -1492,6 +1473,9 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
        verbosestream << "Server: Got TOSERVER_FIRST_SRP from " << addr_s
                << ", with is_empty=" << (is_empty == 1) << std::endl;
 
+       const bool empty_disallowed = !isSingleplayer() && is_empty == 1 &&
+               g_settings->getBool("disallow_empty_password");
+
        // Either this packet is sent because the user is new or to change the password
        if (cstate == CS_HelloSent) {
                if (!client->isMechAllowed(AUTH_MECHANISM_FIRST_SRP)) {
@@ -1502,9 +1486,7 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
                        return;
                }
 
-               if (!isSingleplayer() &&
-                               g_settings->getBool("disallow_empty_password") &&
-                               is_empty == 1) {
+               if (empty_disallowed) {
                        actionstream << "Server: " << playername
                                        << " supplied empty password from " << addr_s << std::endl;
                        DenyAccess(peer_id, SERVER_ACCESSDENIED_EMPTY_PASSWORD);
@@ -1512,8 +1494,19 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
                }
 
                std::string initial_ver_key;
-
                initial_ver_key = encode_srp_verifier(verification_key, salt);
+
+               // It is possible for multiple connections to get this far with the same
+               // player name. In the end only one player with a given name will be emerged
+               // (see Server::StateTwoClientInit) but we still have to be careful here.
+               if (m_script->getAuth(playername, nullptr, nullptr)) {
+                       // Another client beat us to it
+                       actionstream << "Server: Client from " << addr_s
+                               << " tried to register " << playername << " a second time."
+                               << std::endl;
+                       DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED);
+                       return;
+               }
                m_script->createAuth(playername, initial_ver_key);
                m_script->on_authplayer(playername, addr_s, true);
 
@@ -1526,6 +1519,15 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
                        return;
                }
                m_clients.event(peer_id, CSE_SudoLeave);
+
+               if (empty_disallowed) {
+                       actionstream << "Server: " << playername
+                                       << " supplied empty password" << std::endl;
+                       SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
+                               L"Changing to an empty password is not allowed."));
+                       return;
+               }
+
                std::string pw_db_field = encode_srp_verifier(verification_key, salt);
                bool success = m_script->setPassword(playername, pw_db_field);
                if (success) {
@@ -1547,8 +1549,6 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
        RemoteClient *client = getClient(peer_id, CS_Invalid);
        ClientState cstate = client->getState();
 
-       bool wantSudo = (cstate == CS_Active);
-
        if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) {
                actionstream << "Server: got SRP _A packet in wrong state " << cstate <<
                        " from " << getPeerAddress(peer_id).serializeString() <<
@@ -1556,6 +1556,8 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
                return;
        }
 
+       const bool wantSudo = (cstate == CS_Active);
+
        if (client->chosen_mech != AUTH_MECHANISM_NONE) {
                actionstream << "Server: got SRP _A packet, while auth is already "
                        "going on with mech " << client->chosen_mech << " from " <<
@@ -1602,8 +1604,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
 
        client->chosen_mech = chosen;
 
-       std::string salt;
-       std::string verifier;
+       std::string salt, verifier;
 
        if (based_on == 0) {
 
@@ -1635,6 +1636,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
                        << std::endl;
                if (wantSudo) {
                        DenySudoAccess(peer_id);
+                       client->resetChosenMech();
                        return;
                }
 
@@ -1652,10 +1654,10 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt)
        session_t peer_id = pkt->getPeerId();
        RemoteClient *client = getClient(peer_id, CS_Invalid);
        ClientState cstate = client->getState();
-       std::string addr_s = getPeerAddress(pkt->getPeerId()).serializeString();
-       std::string playername = client->getName();
+       const std::string addr_s = client->getAddress().serializeString();
+       const std::string playername = client->getName();
 
-       bool wantSudo = (cstate == CS_Active);
+       const bool wantSudo = (cstate == CS_Active);
 
        verbosestream << "Server: Received TOSERVER_SRP_BYTES_M." << std::endl;
 
@@ -1701,6 +1703,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt)
                                << " tried to change their password, but supplied wrong"
                                << " (SRP) password for authentication." << std::endl;
                        DenySudoAccess(peer_id);
+                       client->resetChosenMech();
                        return;
                }
 
@@ -1714,8 +1717,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt)
        if (client->create_player_on_auth_success) {
                m_script->createAuth(playername, client->enc_pwd);
 
-               std::string checkpwd; // not used, but needed for passing something
-               if (!m_script->getAuth(playername, &checkpwd, NULL)) {
+               if (!m_script->getAuth(playername, nullptr, nullptr)) {
                        errorstream << "Server: " << playername <<
                                " cannot be authenticated (auth handler does not work?)" <<
                                std::endl;
@@ -1810,3 +1812,30 @@ void Server::handleCommand_ModChannelMsg(NetworkPacket *pkt)
 
        broadcastModChannelMessage(channel_name, channel_msg, peer_id);
 }
+
+void Server::handleCommand_HaveMedia(NetworkPacket *pkt)
+{
+       std::vector<u32> tokens;
+       u8 numtokens;
+
+       *pkt >> numtokens;
+       for (u16 i = 0; i < numtokens; i++) {
+               u32 n;
+               *pkt >> n;
+               tokens.emplace_back(n);
+       }
+
+       const session_t peer_id = pkt->getPeerId();
+       auto player = m_env->getPlayer(peer_id);
+
+       for (const u32 token : tokens) {
+               auto it = m_pending_dyn_media.find(token);
+               if (it == m_pending_dyn_media.end())
+                       continue;
+               if (it->second.waiting_players.count(peer_id)) {
+                       it->second.waiting_players.erase(peer_id);
+                       if (player)
+                               getScriptIface()->on_dynamic_media_added(token, player->getName());
+               }
+       }
+}