]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/network/serverpackethandler.cpp
Fix password changing getting stuck if wrong password is entered once
[dragonfireclient.git] / src / network / serverpackethandler.cpp
index dcbb114bfe9dc7b9cc1467aa630fd76e0dab8739..125e85cabc91755f42dd3b39b770848471db2ae8 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
 
@@ -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
@@ -600,7 +596,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
                << std::endl;
        std::istringstream is(datastring, std::ios_base::binary);
        // Create an action
-       InventoryAction *a = InventoryAction::deSerialize(is);
+       std::unique_ptr<InventoryAction> a(InventoryAction::deSerialize(is));
        if (!a) {
                infostream << "TOSERVER_INVENTORY_ACTION: "
                                << "InventoryAction::deSerialize() returned NULL"
@@ -617,11 +613,45 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
                where the client made a bad prediction.
        */
 
+       const bool player_has_interact = checkPriv(player->getName(), "interact");
+
+       auto check_inv_access = [player, player_has_interact, this] (
+                       const InventoryLocation &loc) -> bool {
+
+               // 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;
+               }
+
+               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;
+               }
+       };
+
        /*
                Handle restrictions and special cases of the move action
        */
        if (a->getType() == IAction::Move) {
-               IMoveAction *ma = (IMoveAction*)a;
+               IMoveAction *ma = (IMoveAction*)a.get();
 
                ma->from_inv.applyCurrentPlayer(player->getName());
                ma->to_inv.applyCurrentPlayer(player->getName());
@@ -630,25 +660,9 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
                if (ma->from_inv != ma->to_inv)
                        m_inventory_mgr->setInventoryModified(ma->to_inv);
 
-               bool from_inv_is_current_player =
-                       (ma->from_inv.type == InventoryLocation::PLAYER) &&
-                       (ma->from_inv.name == player->getName());
-
-               bool to_inv_is_current_player =
-                       (ma->to_inv.type == InventoryLocation::PLAYER) &&
-                       (ma->to_inv.name == player->getName());
-
-               InventoryLocation *remote = from_inv_is_current_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;
-               }
+               if (!check_inv_access(ma->from_inv) ||
+                               !check_inv_access(ma->to_inv))
+                       return;
 
                /*
                        Disable moving items out of craftpreview
@@ -658,7 +672,6 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
                                        << (ma->from_inv.dump()) << ":" << ma->from_list
                                        << " to " << (ma->to_inv.dump()) << ":" << ma->to_list
                                        << " because src is " << ma->from_list << std::endl;
-                       delete a;
                        return;
                }
 
@@ -670,18 +683,6 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
                                        << (ma->from_inv.dump()) << ":" << ma->from_list
                                        << " to " << (ma->to_inv.dump()) << ":" << ma->to_list
                                        << " because dst is " << ma->to_list << std::endl;
-                       delete a;
-                       return;
-               }
-
-               // Disallow moving items in elsewhere than player's inventory
-               // if not allowed to interact
-               if (!checkPriv(player->getName(), "interact") &&
-                               (!from_inv_is_current_player ||
-                               !to_inv_is_current_player)) {
-                       infostream << "Cannot move outside of player's inventory: "
-                                       << "No interact privilege" << std::endl;
-                       delete a;
                        return;
                }
        }
@@ -689,7 +690,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
                Handle restrictions and special cases of the drop action
        */
        else if (a->getType() == IAction::Drop) {
-               IDropAction *da = (IDropAction*)a;
+               IDropAction *da = (IDropAction*)a.get();
 
                da->from_inv.applyCurrentPlayer(player->getName());
 
@@ -702,22 +703,18 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
                        infostream << "Ignoring IDropAction from "
                                        << (da->from_inv.dump()) << ":" << da->from_list
                                        << " because src is " << da->from_list << std::endl;
-                       delete a;
                        return;
                }
 
                // Disallow dropping items if not allowed to interact
-               if (!checkPriv(player->getName(), "interact")) {
-                       delete a;
+               if (!player_has_interact || !check_inv_access(da->from_inv))
                        return;
-               }
 
                // Disallow dropping items if dead
                if (playersao->isDead()) {
                        infostream << "Ignoring IDropAction from "
                                        << (da->from_inv.dump()) << ":" << da->from_list
                                        << " because player is dead." << std::endl;
-                       delete a;
                        return;
                }
        }
@@ -725,48 +722,34 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
                Handle restrictions and special cases of the craft action
        */
        else if (a->getType() == IAction::Craft) {
-               ICraftAction *ca = (ICraftAction*)a;
+               ICraftAction *ca = (ICraftAction*)a.get();
 
                ca->craft_inv.applyCurrentPlayer(player->getName());
 
                m_inventory_mgr->setInventoryModified(ca->craft_inv);
 
-               //bool craft_inv_is_current_player =
-               //      (ca->craft_inv.type == InventoryLocation::PLAYER) &&
-               //      (ca->craft_inv.name == player->getName());
-
                // Disallow crafting if not allowed to interact
-               if (!checkPriv(player->getName(), "interact")) {
+               if (!player_has_interact) {
                        infostream << "Cannot craft: "
                                        << "No interact privilege" << std::endl;
-                       delete a;
                        return;
                }
+
+               if (!check_inv_access(ca->craft_inv))
+                       return;
+       } else {
+               // Unknown action. Ignored.
+               return;
        }
 
        // Do the action
        a->apply(m_inventory_mgr.get(), playersao, this);
-       // Eat the action
-       delete a;
 }
 
 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);
@@ -778,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));
        }
 }
 
@@ -829,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);
        }
 }
 
@@ -863,6 +843,15 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt)
 
        *pkt >> item;
 
+       if (item >= player->getHotbarItemcount()) {
+               actionstream << "Player: " << player->getName()
+                       << " tried to access item=" << item
+                       << " out of hotbar_itemcount="
+                       << player->getHotbarItemcount()
+                       << "; ignoring." << std::endl;
+               return;
+       }
+
        playersao->getPlayer()->setWieldIndex(item);
 }
 
@@ -915,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)
 {
        /*
@@ -978,11 +974,17 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
        v3f player_pos = playersao->getLastGoodPosition();
 
        // Update wielded item
-       playersao->getPlayer()->setWieldIndex(item_i);
 
-       // Get pointed to node (undefined if not POINTEDTYPE_NODE)
-       v3s16 p_under = pointed.node_undersurface;
-       v3s16 p_above = pointed.node_abovesurface;
+       if (item_i >= player->getHotbarItemcount()) {
+               actionstream << "Player: " << player->getName()
+                       << " tried to access item=" << item_i
+                       << " out of hotbar_itemcount="
+                       << player->getHotbarItemcount()
+                       << "; ignoring." << std::endl;
+               return;
+       }
+
+       playersao->getPlayer()->setWieldIndex(item_i);
 
        // Get pointed to object (NULL if not POINTEDTYPE_OBJECT)
        ServerActiveObject *pointed_object = NULL;
@@ -996,17 +998,6 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
 
        }
 
-       v3f pointed_pos_under = player_pos;
-       v3f pointed_pos_above = player_pos;
-       if (pointed.type == POINTEDTHING_NODE) {
-               pointed_pos_under = intToFloat(p_under, BS);
-               pointed_pos_above = intToFloat(p_above, BS);
-       }
-       else if (pointed.type == POINTEDTHING_OBJECT) {
-               pointed_pos_under = pointed_object->getBasePosition();
-               pointed_pos_above = pointed_pos_under;
-       }
-
        /*
                Make sure the player is allowed to do it
        */
@@ -1014,16 +1005,19 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                actionstream << player->getName() << " attempted to interact with " <<
                                pointed.dump() << " without 'interact' privilege" << std::endl;
 
+               if (pointed.type != POINTEDTHING_NODE)
+                       return;
+
                // Re-send block to revert change on client-side
                RemoteClient *client = getClient(peer_id);
                // Digging completed -> under
                if (action == INTERACT_DIGGING_COMPLETED) {
-                       v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
+                       v3s16 blockpos = getNodeBlockPos(pointed.node_undersurface);
                        client->SetBlockNotSent(blockpos);
                }
                // Placement -> above
                else if (action == INTERACT_PLACE) {
-                       v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS));
+                       v3s16 blockpos = getNodeBlockPos(pointed.node_abovesurface);
                        client->SetBlockNotSent(blockpos);
                }
                return;
@@ -1031,7 +1025,6 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
 
        /*
                Check that target is reasonably close
-               (only when digging or placing things)
        */
        static thread_local const bool enable_anticheat =
                        !g_settings->getBool("disable_anticheat");
@@ -1039,13 +1032,27 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
        if ((action == INTERACT_START_DIGGING || action == INTERACT_DIGGING_COMPLETED ||
                        action == INTERACT_PLACE || action == INTERACT_USE) &&
                        enable_anticheat && !isSingleplayer()) {
-               float d = playersao->getEyePosition().getDistanceFrom(pointed_pos_under);
+               v3f target_pos = player_pos;
+               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);
 
                if (!checkInteractDistance(player, d, pointed.dump())) {
-                       // Re-send block to revert change on client-side
-                       RemoteClient *client = getClient(peer_id);
-                       v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
-                       client->SetBlockNotSent(blockpos);
+                       if (pointed.type == POINTEDTHING_NODE) {
+                               // Re-send block to revert change on client-side
+                               RemoteClient *client = getClient(peer_id);
+                               v3s16 blockpos = getNodeBlockPos(pointed.node_undersurface);
+                               client->SetBlockNotSent(blockpos);
+                       }
                        return;
                }
        }
@@ -1056,20 +1063,20 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
        RollbackScopeActor rollback_scope(m_rollback,
                        std::string("player:")+player->getName());
 
-       /*
-               0: start digging or punch object
-       */
-       if (action == INTERACT_START_DIGGING) {
+       switch (action) {
+       // Start digging or punch object
+       case INTERACT_START_DIGGING: {
                if (pointed.type == POINTEDTHING_NODE) {
                        MapNode n(CONTENT_IGNORE);
                        bool pos_ok;
 
+                       v3s16 p_under = pointed.node_undersurface;
                        n = m_env->getMap().getNode(p_under, &pos_ok);
                        if (!pos_ok) {
                                infostream << "Server: Not punching: Node not found. "
                                        "Adding block to emerge queue." << std::endl;
-                               m_emerge->enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above),
-                                       false);
+                               m_emerge->enqueueBlockEmerge(peer_id,
+                                       getNodeBlockPos(pointed.node_abovesurface), false);
                        }
 
                        if (n.getContent() != CONTENT_IGNORE)
@@ -1077,167 +1084,152 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
 
                        // Cheat prevention
                        playersao->noCheatDigStart(p_under);
+
+                       return;
                }
-               else if (pointed.type == POINTEDTHING_OBJECT) {
-                       // Skip if object can't be interacted with anymore
-                       if (pointed_object->isGone())
-                               return;
 
-                       ItemStack selected_item, hand_item;
-                       ItemStack tool_item = playersao->getWieldedItem(&selected_item, &hand_item);
-                       ToolCapabilities toolcap =
-                                       tool_item.getToolCapabilities(m_itemdef);
-                       v3f dir = (pointed_object->getBasePosition() -
-                                       (playersao->getBasePosition() + playersao->getEyeOffset())
-                                               ).normalize();
-                       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);
-
-                       // Callback may have changed item, so get it again
-                       playersao->getWieldedItem(&selected_item);
-                       bool changed = selected_item.addWear(wear, m_itemdef);
-                       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));
-                       }
+               // Skip if the object can't be interacted with anymore
+               if (pointed.type != POINTEDTHING_OBJECT || pointed_object->isGone())
+                       return;
 
-                       // If the puncher is a player and its HP changed
-                       if (dst_origin_hp != playersao->getHP())
-                               SendPlayerHPOrDie(playersao,
-                                               PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, pointed_object));
-               }
+               ItemStack selected_item, hand_item;
+               ItemStack tool_item = playersao->getWieldedItem(&selected_item, &hand_item);
+               ToolCapabilities toolcap =
+                               tool_item.getToolCapabilities(m_itemdef);
+               v3f dir = (pointed_object->getBasePosition() -
+                               (playersao->getBasePosition() + playersao->getEyeOffset())
+                                       ).normalize();
+               float time_from_last_punch =
+                       playersao->resetTimeFromLastPunch();
+
+               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);
+               bool changed = selected_item.addWear(wear, m_itemdef);
+               if (changed)
+                       playersao->setWieldedItem(selected_item);
 
+               return;
        } // action == INTERACT_START_DIGGING
 
-       /*
-               1: stop digging
-       */
-       else if (action == INTERACT_STOP_DIGGING) {
-       } // action == INTERACT_STOP_DIGGING
+       case INTERACT_STOP_DIGGING:
+               // Nothing to do
+               return;
 
-       /*
-               2: Digging completed
-       */
-       else if (action == INTERACT_DIGGING_COMPLETED) {
+       case INTERACT_DIGGING_COMPLETED: {
                // Only digging of nodes
-               if (pointed.type == POINTEDTHING_NODE) {
-                       bool pos_ok;
-                       MapNode n = m_env->getMap().getNode(p_under, &pos_ok);
-                       if (!pos_ok) {
-                               infostream << "Server: Not finishing digging: Node not found. "
-                                       "Adding block to emerge queue." << std::endl;
-                               m_emerge->enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above),
-                                       false);
-                       }
+               if (pointed.type != POINTEDTHING_NODE)
+                       return;
+               bool pos_ok;
+               v3s16 p_under = pointed.node_undersurface;
+               MapNode n = m_env->getMap().getNode(p_under, &pos_ok);
+               if (!pos_ok) {
+                       infostream << "Server: Not finishing digging: Node not found. "
+                               "Adding block to emerge queue." << std::endl;
+                       m_emerge->enqueueBlockEmerge(peer_id,
+                               getNodeBlockPos(pointed.node_abovesurface), false);
+               }
 
-                       /* Cheat prevention */
-                       bool is_valid_dig = true;
-                       if (enable_anticheat && !isSingleplayer()) {
-                               v3s16 nocheat_p = playersao->getNoCheatDigPos();
-                               float nocheat_t = playersao->getNoCheatDigTime();
-                               playersao->noCheatDigEnd();
-                               // If player didn't start digging this, ignore dig
-                               if (nocheat_p != p_under) {
-                                       infostream << "Server: " << player->getName()
-                                                       << " started digging "
-                                                       << PP(nocheat_p) << " and completed digging "
-                                                       << PP(p_under) << "; not digging." << std::endl;
-                                       is_valid_dig = false;
-                                       // Call callbacks
-                                       m_script->on_cheat(playersao, "finished_unknown_dig");
-                               }
-
-                               // Get player's wielded item
-                               // See also: Game::handleDigging
-                               ItemStack selected_item, hand_item;
-                               playersao->getPlayer()->getWieldedItem(&selected_item, &hand_item);
-
-                               // Get diggability and expected digging time
-                               DigParams params = getDigParams(m_nodedef->get(n).groups,
-                                               &selected_item.getToolCapabilities(m_itemdef));
-                               // If can't dig, try hand
-                               if (!params.diggable) {
-                                       params = getDigParams(m_nodedef->get(n).groups,
-                                               &hand_item.getToolCapabilities(m_itemdef));
-                               }
-                               // If can't dig, ignore dig
-                               if (!params.diggable) {
-                                       infostream << "Server: " << player->getName()
-                                                       << " completed digging " << PP(p_under)
-                                                       << ", which is not diggable with tool; not digging."
-                                                       << std::endl;
-                                       is_valid_dig = false;
-                                       // Call callbacks
-                                       m_script->on_cheat(playersao, "dug_unbreakable");
-                               }
-                               // Check digging time
-                               // If already invalidated, we don't have to
-                               if (!is_valid_dig) {
-                                       // Well not our problem then
-                               }
-                               // Clean and long dig
-                               else if (params.time > 2.0 && nocheat_t * 1.2 > params.time) {
-                                       // All is good, but grab time from pool; don't care if
-                                       // it's actually available
-                                       playersao->getDigPool().grab(params.time);
-                               }
-                               // Short or laggy dig
-                               // Try getting the time from pool
-                               else if (playersao->getDigPool().grab(params.time)) {
-                                       // All is good
-                               }
-                               // Dig not possible
-                               else {
-                                       infostream << "Server: " << player->getName()
-                                                       << " completed digging " << PP(p_under)
-                                                       << "too fast; not digging." << std::endl;
-                                       is_valid_dig = false;
-                                       // Call callbacks
-                                       m_script->on_cheat(playersao, "dug_too_fast");
-                               }
+               /* Cheat prevention */
+               bool is_valid_dig = true;
+               if (enable_anticheat && !isSingleplayer()) {
+                       v3s16 nocheat_p = playersao->getNoCheatDigPos();
+                       float nocheat_t = playersao->getNoCheatDigTime();
+                       playersao->noCheatDigEnd();
+                       // If player didn't start digging this, ignore dig
+                       if (nocheat_p != p_under) {
+                               infostream << "Server: " << player->getName()
+                                               << " started digging "
+                                               << PP(nocheat_p) << " and completed digging "
+                                               << PP(p_under) << "; not digging." << std::endl;
+                               is_valid_dig = false;
+                               // Call callbacks
+                               m_script->on_cheat(playersao, "finished_unknown_dig");
                        }
 
-                       /* Actually dig node */
-
-                       if (is_valid_dig && n.getContent() != CONTENT_IGNORE)
-                               m_script->node_on_dig(p_under, n, playersao);
-
-                       v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
-                       RemoteClient *client = getClient(peer_id);
-                       // Send unusual result (that is, node not being removed)
-                       if (m_env->getMap().getNode(p_under).getContent() != CONTENT_AIR) {
-                               // Re-send block to revert change on client-side
-                               client->SetBlockNotSent(blockpos);
+                       // Get player's wielded item
+                       // See also: Game::handleDigging
+                       ItemStack selected_item, hand_item;
+                       playersao->getPlayer()->getWieldedItem(&selected_item, &hand_item);
+
+                       // Get diggability and expected digging time
+                       DigParams params = getDigParams(m_nodedef->get(n).groups,
+                                       &selected_item.getToolCapabilities(m_itemdef),
+                                       selected_item.wear);
+                       // If can't dig, try hand
+                       if (!params.diggable) {
+                               params = getDigParams(m_nodedef->get(n).groups,
+                                       &hand_item.getToolCapabilities(m_itemdef));
+                       }
+                       // If can't dig, ignore dig
+                       if (!params.diggable) {
+                               infostream << "Server: " << player->getName()
+                                               << " completed digging " << PP(p_under)
+                                               << ", which is not diggable with tool; not digging."
+                                               << std::endl;
+                               is_valid_dig = false;
+                               // Call callbacks
+                               m_script->on_cheat(playersao, "dug_unbreakable");
                        }
+                       // Check digging time
+                       // If already invalidated, we don't have to
+                       if (!is_valid_dig) {
+                               // Well not our problem then
+                       }
+                       // Clean and long dig
+                       else if (params.time > 2.0 && nocheat_t * 1.2 > params.time) {
+                               // All is good, but grab time from pool; don't care if
+                               // it's actually available
+                               playersao->getDigPool().grab(params.time);
+                       }
+                       // Short or laggy dig
+                       // Try getting the time from pool
+                       else if (playersao->getDigPool().grab(params.time)) {
+                               // All is good
+                       }
+                       // Dig not possible
                        else {
-                               client->ResendBlockIfOnWire(blockpos);
+                               infostream << "Server: " << player->getName()
+                                               << " completed digging " << PP(p_under)
+                                               << "too fast; not digging." << std::endl;
+                               is_valid_dig = false;
+                               // Call callbacks
+                               m_script->on_cheat(playersao, "dug_too_fast");
                        }
                }
+
+               /* Actually dig node */
+
+               if (is_valid_dig && n.getContent() != CONTENT_IGNORE)
+                       m_script->node_on_dig(p_under, n, playersao);
+
+               v3s16 blockpos = getNodeBlockPos(p_under);
+               RemoteClient *client = getClient(peer_id);
+               // Send unusual result (that is, node not being removed)
+               if (m_env->getMap().getNode(p_under).getContent() != CONTENT_AIR)
+                       // Re-send block to revert change on client-side
+                       client->SetBlockNotSent(blockpos);
+               else
+                       client->ResendBlockIfOnWire(blockpos);
+
+               return;
        } // action == INTERACT_DIGGING_COMPLETED
 
-       /*
-               3: place block or right-click object
-       */
-       else if (action == INTERACT_PLACE) {
-               ItemStack selected_item;
-               playersao->getWieldedItem(&selected_item, nullptr);
+       // Place block or right-click object
+       case INTERACT_PLACE: {
+               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
 
@@ -1250,90 +1242,79 @@ 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);
-               } else if (m_script->item_OnPlace(
-                               selected_item, playersao, pointed)) {
+               } else if (m_script->item_OnPlace(selected_item, playersao, pointed)) {
                        // 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);
-                       }
                }
 
+               if (pointed.type != POINTEDTHING_NODE)
+                       return;
+
                // If item has node placement prediction, always send the
                // blocks to make sure the client knows what exactly happened
                RemoteClient *client = getClient(peer_id);
-               v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS));
-               v3s16 blockpos2 = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
-               if (!selected_item.getDefinition(m_itemdef).node_placement_prediction.empty()) {
+               v3s16 blockpos = getNodeBlockPos(pointed.node_abovesurface);
+               v3s16 blockpos2 = getNodeBlockPos(pointed.node_undersurface);
+               if (had_prediction) {
                        client->SetBlockNotSent(blockpos);
-                       if (blockpos2 != blockpos) {
+                       if (blockpos2 != blockpos)
                                client->SetBlockNotSent(blockpos2);
-                       }
-               }
-               else {
+               } else {
                        client->ResendBlockIfOnWire(blockpos);
-                       if (blockpos2 != blockpos) {
+                       if (blockpos2 != blockpos)
                                client->ResendBlockIfOnWire(blockpos2);
-                       }
                }
+
+               return;
        } // action == INTERACT_PLACE
 
-       /*
-               4: use
-       */
-       else if (action == INTERACT_USE) {
-               ItemStack selected_item;
-               playersao->getWieldedItem(&selected_item, nullptr);
+       case INTERACT_USE: {
+               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)) {
+               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);
-                       }
                }
 
-       } // action == INTERACT_USE
+               return;
+       }
 
-       /*
-               5: rightclick air
-       */
-       else if (action == INTERACT_ACTIVATE) {
-               ItemStack selected_item;
-               playersao->getWieldedItem(&selected_item, nullptr);
+       // Rightclick air
+       case INTERACT_ACTIVATE: {
+               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)) {
+               if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) {
+                       // Apply returned ItemStack
+                       if (selected_item.has_value() && playersao->setWieldedItem(*selected_item))
                                SendInventory(playersao, true);
-                       }
                }
-       } // action == INTERACT_ACTIVATE
 
+               return;
+       }
+
+       default:
+               warningstream << "Server: Invalid action " << action << std::endl;
 
-       /*
-               Catch invalid actions
-       */
-       else {
-               warningstream << "Server: Invalid action "
-                               << action << std::endl;
        }
 }
 
@@ -1494,6 +1475,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)) {
@@ -1504,9 +1488,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);
@@ -1514,8 +1496,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);
 
@@ -1528,6 +1521,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) {
@@ -1637,6 +1639,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
                        << std::endl;
                if (wantSudo) {
                        DenySudoAccess(peer_id);
+                       client->resetChosenMech();
                        return;
                }
 
@@ -1659,19 +1662,18 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt)
 
        bool wantSudo = (cstate == CS_Active);
 
-       verbosestream << "Server: Received TOCLIENT_SRP_BYTES_M." << std::endl;
+       verbosestream << "Server: Received TOSERVER_SRP_BYTES_M." << std::endl;
 
        if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) {
-               actionstream << "Server: got SRP _M packet in wrong state "
-                       << cstate << " from " << addr_s
-                       << ". Ignoring." << std::endl;
+               warningstream << "Server: got SRP_M packet in wrong state "
+                       << cstate << " from " << addr_s << ". Ignoring." << std::endl;
                return;
        }
 
        if (client->chosen_mech != AUTH_MECHANISM_SRP &&
                        client->chosen_mech != AUTH_MECHANISM_LEGACY_PASSWORD) {
-               actionstream << "Server: got SRP _M packet, while auth"
-                       << "is going on with mech " << client->chosen_mech << " from "
+               warningstream << "Server: got SRP_M packet, while auth "
+                       "is going on with mech " << client->chosen_mech << " from "
                        << addr_s << " (wantSudo=" << wantSudo << "). Denying." << std::endl;
                if (wantSudo) {
                        DenySudoAccess(peer_id);
@@ -1704,6 +1706,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;
                }
 
@@ -1719,7 +1722,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt)
 
                std::string checkpwd; // not used, but needed for passing something
                if (!m_script->getAuth(playername, &checkpwd, NULL)) {
-                       actionstream << "Server: " << playername <<
+                       errorstream << "Server: " << playername <<
                                " cannot be authenticated (auth handler does not work?)" <<
                                std::endl;
                        DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL);
@@ -1813,3 +1816,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());
+               }
+       }
+}