diff --git a/plugins/MumblePlugin.h b/plugins/MumblePlugin.h index 8a70696d410..15dfc7b8f6c 100644 --- a/plugins/MumblePlugin.h +++ b/plugins/MumblePlugin.h @@ -43,7 +43,7 @@ # define MUMBLE_PLUGIN_API_MAJOR_MACRO 1 # endif # ifndef MUMBLE_PLUGIN_API_MINOR_MACRO -# define MUMBLE_PLUGIN_API_MINOR_MACRO 2 +# define MUMBLE_PLUGIN_API_MINOR_MACRO 3 # endif # ifndef MUMBLE_PLUGIN_API_PATCH_MACRO # define MUMBLE_PLUGIN_API_PATCH_MACRO 0 @@ -1515,6 +1515,41 @@ struct MUMBLE_API_STRUCT_NAME { mumble_channelid_t channelID, const char **description); +# if SELECTED_API_VERSION >= MUMBLE_PLUGIN_VERSION_CHECK(1, 3, 0) + + /** + * Checks whether the two channels are linked to each other. + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param firstID The ID of the first channel + * @param secondID The ID of the second channel + * @param[out] linked A pointer to where the result of the check shall be written to + * @returns The error code. If everything went well, STATUS_OK will be returned. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *isChannelLinkedTo)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t firstID, + mumble_channelid_t secondID, bool *linked); + + /** + * Gets the set of channels the given channel is linked to. + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param channelID The ID of the channel + * @param[out] linkedChannels The set of channel IDs linked to the channelID + * @param[out] linkCount The amount of linked channels + * @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointers + * may be accessed. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *getLinkedChannels)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t channelID, + mumble_channelid_t **linkedChannels, + size_t *linkCount); + +# endif // -------- Request functions -------- @@ -1608,7 +1643,98 @@ struct MUMBLE_API_STRUCT_NAME { mumble_connection_t connection, const char *comment); +# if SELECTED_API_VERSION >= MUMBLE_PLUGIN_VERSION_CHECK(1, 3, 0) + + /** + * Requests Mumble to link all channels in the given set to each other. + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param channelSet The set of channel IDs to link + * @param channelCount The number of elements in the channel list + * @returns The error code. If everything went well, STATUS_OK will be returned. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *requestLinkChannels)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t *channelSet, + size_t channelCount); + /** + * Requests Mumble to remove any existing links between the provided channel and channels in the provided set. + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param channelID The ID of the channel to unlink + * @param channelSet The set of channel IDs to remove link from the channelID + * @param channelCount The number of elements in the channel set + * @returns The error code. If everything went well, STATUS_OK will be returned. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *requestUnlinkChannels)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t channelID, + mumble_channelid_t *channelSet, + size_t channelCount); + + + /** + * Requests Mumble to remove all links between the provided channels. + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param channelSet The set of channel IDs to remove link + * @param channelCount The number of elements in the channel set + * @returns The error code. If everything went well, STATUS_OK will be returned. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *requestUnlinkChannelSet)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t *channelSet, + size_t channelCount); + + + /** + * Starts to listen channel set + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param channelSet The set of channel IDs to listen + * @param channelCount The number of elements in the channel set + * @returns The error code. If everything went well, STATUS_OK will be returned. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *requestStartListeningToChannels)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t *channelSet, + size_t channelCount); + + /** + * Stops to listen channel set + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param channelSet The set of channel IDs to stop listen + * @param channelCount The number of elements in the channel set + * @returns The error code. If everything went well, STATUS_OK will be returned. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *requestStopListeningToChannels)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t *channelSet, + size_t channelCount); + + /** + * Send text message to the given users + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param users List of IDs of the users to send the message to + * @param userAmount The amount of users contained in the users parameter + * @param message The message to send (UTF-8 encoded) + * @param messageSize The size (in bytes) of the message + * @returns The error code. If everything went well, STATUS_OK will be returned. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *requestSendUserTextMessage)( + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t *users, std::size_t userAmount, + const char *message, std::size_t messageSize); + +# endif // -------- Find functions -------- diff --git a/src/Mumble.proto b/src/Mumble.proto index 169c18a6925..60ac9771782 100644 --- a/src/Mumble.proto +++ b/src/Mumble.proto @@ -123,6 +123,8 @@ message ServerSync { // a channel has been removed and clients should be notified. message ChannelRemove { required uint32 channel_id = 1; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 2; } // Used to communicate channel properties between the client and the server. @@ -159,6 +161,8 @@ message ChannelState { optional bool is_enter_restricted = 12; // Whether the receiver of this msg is considered to be able to enter this channel optional bool can_enter = 13; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 14; } // Used to communicate user leaving or being kicked. May be sent by the client @@ -175,6 +179,8 @@ message UserRemove { optional string reason = 3; // True if the kick should result in a ban. optional bool ban = 4; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 5; } // Sent by the server when it communicates new and changed users to client. @@ -266,6 +272,8 @@ message BanList { // True if the server should return the list, false if it should replace old // ban list with the one provided. optional bool query = 2 [default = false]; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 3; } // Used to send and broadcast text messages. @@ -282,6 +290,8 @@ message TextMessage { repeated uint32 tree_id = 4; // The UTF-8 encoded message. May be HTML if the server allows. required string message = 5; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 6; } message PermissionDenied { @@ -374,6 +384,8 @@ message ACL { repeated ChanACL acls = 4; // True if the message is a query for ACLs instead of setting them. optional bool query = 5 [default = false]; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 6; } // Client may use this message to refresh its registered user information. The @@ -449,6 +461,8 @@ message UserList { } // A list of registered users. repeated User users = 1; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 2; } // Sent by the client when it wants to register or clear whisper targets. @@ -553,6 +567,8 @@ message UserStats { // True if the user has a strong certificate. optional bool strong_certificate = 18 [default = false]; optional bool opus = 19 [default = false]; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 20; } // Used by the client to request binary data from the server. By default large diff --git a/src/mumble/API.h b/src/mumble/API.h index a1d0ebdc45d..8f6ad6692c5 100644 --- a/src/mumble/API.h +++ b/src/mumble/API.h @@ -107,6 +107,9 @@ public slots: void isLocalUserMuted_v_1_0_x(mumble_plugin_id_t callerID, bool *muted, std::shared_ptr< api_promise_t > promise); void isLocalUserDeafened_v_1_0_x(mumble_plugin_id_t callerID, bool *deafened, std::shared_ptr< api_promise_t > promise); + void isChannelLinkedTo_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t firstID, mumble_channelid_t secondID, bool *linked, + std::shared_ptr< api_promise_t > promise); void getUserHash_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t userID, const char **hash, std::shared_ptr< api_promise_t > promise); void getServerHash_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, const char **hash, @@ -119,6 +122,9 @@ public slots: void getChannelDescription_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t channelID, const char **description, std::shared_ptr< api_promise_t > promise); + void getLinkedChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t channelID, mumble_channelid_t **linkedChannels, + std::size_t *linkCount, std::shared_ptr< api_promise_t > promise); void requestUserMove_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t userID, mumble_channelid_t channelID, const char *password, std::shared_ptr< api_promise_t > promise); @@ -132,6 +138,24 @@ public slots: std::shared_ptr< api_promise_t > promise); void requestSetLocalUserComment_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, const char *comment, std::shared_ptr< api_promise_t > promise); + void requestLinkChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *channelList, std::size_t channelCount, + std::shared_ptr< api_promise_t > promise); + void requestUnlinkChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t channelID, mumble_channelid_t *unlinkList, + std::size_t unlinkCount, std::shared_ptr< api_promise_t > promise); + void requestUnlinkChannelSet_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *unlinkList, std::size_t unlinkCount, + std::shared_ptr< api_promise_t > promise); + void requestStartListeningToChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *channelList, std::size_t channelCount, + std::shared_ptr< api_promise_t > promise); + void requestStopListeningToChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *channelList, std::size_t channelCount, + std::shared_ptr< api_promise_t > promise); + void requestSendUserTextMessage_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_userid_t *users, std::size_t userAmount, const char *message, + std::size_t messageSize, std::shared_ptr< api_promise_t > promise); void findUserByName_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, const char *userName, mumble_userid_t *userID, std::shared_ptr< api_promise_t > promise); void findChannelByName_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, const char *channelName, @@ -174,6 +198,9 @@ MumbleAPI_v_1_0_x getMumbleAPI_v_1_0_x(); /// @returns The Mumble API struct (v1.2.x) MumbleAPI_v_1_2_x getMumbleAPI_v_1_2_x(); +/// @returns The Mumble API struct (v1.3.x) +MumbleAPI_v_1_3_x getMumbleAPI_v_1_3_x(); + /// Converts from the Qt key-encoding to the API's key encoding. /// /// @param keyCode The Qt key-code that shall be converted diff --git a/src/mumble/API_v_1_x_x.cpp b/src/mumble/API_v_1_x_x.cpp index 894b3a6b3d0..433b0de09b7 100644 --- a/src/mumble/API_v_1_x_x.cpp +++ b/src/mumble/API_v_1_x_x.cpp @@ -47,12 +47,22 @@ EXIT_WITH(MUMBLE_EC_INVALID_PLUGIN_ID); \ } +#define VERIFY_CHANNEL_ID(id) \ + if (!Channel::get((static_cast< unsigned int >(id)))) { \ + EXIT_WITH(MUMBLE_EC_CHANNEL_NOT_FOUND); \ + } + // Right now there can only be one connection managed by the current ServerHandler #define VERIFY_CONNECTION(connection) \ if (!Global::get().sh || Global::get().sh->getConnectionID() != connection) { \ EXIT_WITH(MUMBLE_EC_CONNECTION_NOT_FOUND); \ } +#define VERIFY_USER_ID(id) \ + if (!ClientUser::get(id)) { \ + EXIT_WITH(MUMBLE_EC_USER_NOT_FOUND); \ + } + // Right now whether or not a connection has finished synchronizing is indicated by Global::get().uiSession. If it is // zero, synchronization is not done yet (or there is no connection to begin with). The connection parameter in the // macro is only present in case it will be needed in the future @@ -658,6 +668,42 @@ void MumbleAPI::isLocalUserDeafened_v_1_0_x(mumble_plugin_id_t callerID, bool *d EXIT_WITH(MUMBLE_STATUS_OK); } +void MumbleAPI::isChannelLinkedTo_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t firstID, mumble_channelid_t secondID, bool *linked, + std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "isChannelLinkedTo_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_channelid_t, firstID), Q_ARG(mumble_channelid_t, secondID), + Q_ARG(bool *, linked), Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + const Channel *channel = Channel::get(static_cast< unsigned int >(firstID)); + if (!channel) { + EXIT_WITH(MUMBLE_EC_CHANNEL_NOT_FOUND); + } + + Channel *linkedChannel = Channel::get(static_cast< unsigned int >(secondID)); + if (!linkedChannel) { + EXIT_WITH(MUMBLE_EC_CHANNEL_NOT_FOUND); + } + + *linked = channel->isLinked(linkedChannel); + EXIT_WITH(MUMBLE_STATUS_OK); +} + void MumbleAPI::getUserHash_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t userID, const char **hash, std::shared_ptr< api_promise_t > promise) { if (QThread::currentThread() != thread()) { @@ -894,6 +940,52 @@ void MumbleAPI::getChannelDescription_v_1_0_x(mumble_plugin_id_t callerID, mumbl EXIT_WITH(MUMBLE_STATUS_OK); } +void MumbleAPI::getLinkedChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t channelID, mumble_channelid_t **linkedChannels, + std::size_t *linkCount, std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "getLinkedChannels_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_channelid_t, channelID), Q_ARG(mumble_channelid_t **, linkedChannels), + Q_ARG(std::size_t *, linkCount), Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + Channel *channel = Channel::get(static_cast< unsigned int >(channelID)); + + if (!channel) { + EXIT_WITH(MUMBLE_EC_CHANNEL_NOT_FOUND); + } + + const QSet< Channel * > channelSet = channel->allLinks(); + + auto amount = static_cast< std::size_t >(channelSet.size()); + auto *channelIDs = reinterpret_cast< mumble_channelid_t * >(malloc(sizeof(mumble_channelid_t) * amount)); + + std::size_t index = 0; + for (const Channel *linkedChannel : channelSet) { + channelIDs[index++] = static_cast< mumble_channelid_t >(linkedChannel->iId); + } + + m_curator.m_entries.insert({ channelIDs, { defaultDeleter, callerID, "getLinkedChannels" } }); + + *linkedChannels = channelIDs; + *linkCount = amount; + + EXIT_WITH(MUMBLE_STATUS_OK); +} + void MumbleAPI::requestUserMove_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t userID, mumble_channelid_t channelID, const char *password, std::shared_ptr< api_promise_t > promise) { @@ -1093,6 +1185,226 @@ void MumbleAPI::requestSetLocalUserComment_v_1_0_x(mumble_plugin_id_t callerID, EXIT_WITH(MUMBLE_STATUS_OK); } +void MumbleAPI::requestLinkChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *channelSet, std::size_t channelCount, + std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "requestLinkChannels_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_channelid_t *, channelSet), Q_ARG(std::size_t, channelCount), + Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + if (channelCount < 2) { + EXIT_WITH(MUMBLE_STATUS_OK); + } + + for (std::size_t i = 0; i < channelCount; i++) { + VERIFY_CHANNEL_ID(channelSet[i]); + } + + Global::get().sh->addChannelLinks(static_cast< unsigned int >(channelCount), + reinterpret_cast< const unsigned int * >(channelSet)); + + EXIT_WITH(MUMBLE_STATUS_OK); +} + +void MumbleAPI::requestUnlinkChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t channelID, mumble_channelid_t *channelSet, + std::size_t channelCount, std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "requestUnlinkChannels_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_channelid_t, channelID), Q_ARG(mumble_channelid_t *, channelSet), + Q_ARG(std::size_t, channelCount), Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + if (channelCount < 1) { + EXIT_WITH(MUMBLE_STATUS_OK); + } + + VERIFY_CHANNEL_ID(channelID); + + for (std::size_t i = 0; i < channelCount; i++) { + VERIFY_CHANNEL_ID(channelSet[i]); + } + + Global::get().sh->removeChannelLinks(static_cast< unsigned int >(channelID), + static_cast< unsigned int >(channelCount), + reinterpret_cast< const unsigned int * >(channelSet)); + + EXIT_WITH(MUMBLE_STATUS_OK); +} + +void MumbleAPI::requestUnlinkChannelSet_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *channelSet, std::size_t channelCount, + std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "requestUnlinkChannelSet_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_channelid_t *, channelSet), Q_ARG(std::size_t, channelCount), + Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + if (channelCount < 1) { + EXIT_WITH(MUMBLE_STATUS_OK); + } + + for (std::size_t i = 0; i < channelCount; i++) { + VERIFY_CHANNEL_ID(channelSet[i]); + } + + Global::get().sh->removeChannelLinks(static_cast< unsigned int >(channelCount), + reinterpret_cast< const unsigned int * >(channelSet)); + + EXIT_WITH(MUMBLE_STATUS_OK); +} + +void MumbleAPI::requestStartListeningToChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *channelSet, std::size_t channelCount, + std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "requestStartListeningToChannels_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_channelid_t *, channelSet), Q_ARG(std::size_t, channelCount), + Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + if (channelCount < 1) { + EXIT_WITH(MUMBLE_STATUS_OK); + } + + QList< unsigned int > channelList; + for (std::size_t i = 0; i < channelCount; i++) { + VERIFY_CHANNEL_ID(channelSet[i]); + channelList.append(static_cast< unsigned int >(channelSet[i])); + } + + Global::get().sh->startListeningToChannels(channelList); + + EXIT_WITH(MUMBLE_STATUS_OK); +} + +void MumbleAPI::requestStopListeningToChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *channelSet, std::size_t channelCount, + std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "requestStopListeningToChannels_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_channelid_t *, channelSet), Q_ARG(std::size_t, channelCount), + Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + if (channelCount < 1) { + EXIT_WITH(MUMBLE_STATUS_OK); + } + + QList< unsigned int > channelList; + for (std::size_t i = 0; i < channelCount; i++) { + VERIFY_CHANNEL_ID(channelSet[i]); + channelList.append(static_cast< unsigned int >(channelSet[i])); + } + + Global::get().sh->stopListeningToChannels(channelList); + + EXIT_WITH(MUMBLE_STATUS_OK); +} + +void MumbleAPI::requestSendUserTextMessage_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_userid_t *users, std::size_t userAmount, const char *message, + std::size_t messageSize, std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "requestSendUserTextMessage_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_userid_t *, users), Q_ARG(std::size_t, userAmount), + Q_ARG(const char *, message), Q_ARG(std::size_t, messageSize), + Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + ENSURE_CONNECTION_SYNCHRONIZED(connection); + + for (std::size_t i = 0; i < userAmount; i++) { + VERIFY_USER_ID(users[i]); + const ClientUser *user = ClientUser::get(users[i]); + Global::get().sh->sendUserTextMessage(user->uiSession, + QString::fromUtf8(message, static_cast< int >(messageSize))); + } + + EXIT_WITH(MUMBLE_STATUS_OK); +} + void MumbleAPI::findUserByName_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, const char *userName, mumble_userid_t *userID, std::shared_ptr< api_promise_t > promise) { @@ -1767,6 +2079,14 @@ C_WRAPPER(isLocalUserDeafened_v_1_0_x) #undef TYPED_ARGS #undef ARG_NAMES +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t firstID, \ + mumble_channelid_t secondID, bool *linked +#define ARG_NAMES callerID, connection, firstID, secondID, linked +C_WRAPPER(isChannelLinkedTo_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + #define TYPED_ARGS \ mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t userID, const char **hash #define ARG_NAMES callerID, connection, userID, hash @@ -1800,6 +2120,14 @@ C_WRAPPER(getChannelDescription_v_1_0_x) #undef TYPED_ARGS #undef ARG_NAMES +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t channelID, \ + mumble_channelid_t **linkedChannels, std::size_t *linkCount +#define ARG_NAMES callerID, connection, channelID, linkedChannels, linkCount +C_WRAPPER(getLinkedChannels_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + #define TYPED_ARGS \ mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t userID, mumble_channelid_t channelID, \ const char *password @@ -1838,6 +2166,53 @@ C_WRAPPER(requestSetLocalUserComment_v_1_0_x) #undef TYPED_ARGS #undef ARG_NAMES +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t *channelList, \ + std::size_t channelCount +#define ARG_NAMES callerID, connection, channelList, channelCount +C_WRAPPER(requestLinkChannels_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t channelID, \ + mumble_channelid_t *unlinkList, std::size_t unlinkCount +#define ARG_NAMES callerID, connection, channelID, unlinkList, unlinkCount +C_WRAPPER(requestUnlinkChannels_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t *unlinkList, std::size_t unlinkCount +#define ARG_NAMES callerID, connection, unlinkList, unlinkCount +C_WRAPPER(requestUnlinkChannelSet_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t *channelSet, \ + std::size_t channelCount +#define ARG_NAMES callerID, connection, channelSet, channelCount +C_WRAPPER(requestStartListeningToChannels_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t *channelSet, \ + std::size_t channelCount +#define ARG_NAMES callerID, connection, channelSet, channelCount +C_WRAPPER(requestStopListeningToChannels_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t *users, std::size_t userAmount, \ + const char *message, std::size_t messageSize +#define ARG_NAMES callerID, connection, users, userAmount, message, messageSize +C_WRAPPER(requestSendUserTextMessage_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + #define TYPED_ARGS \ mumble_plugin_id_t callerID, mumble_connection_t connection, const char *userName, mumble_userid_t *userID #define ARG_NAMES callerID, connection, userName, userID @@ -2016,6 +2391,55 @@ MumbleAPI_v_1_2_x getMumbleAPI_v_1_2_x() { playSample_v_1_2_x }; } +MumbleAPI_v_1_3_x getMumbleAPI_v_1_3_x() { + return { freeMemory_v_1_0_x, + getActiveServerConnection_v_1_0_x, + isConnectionSynchronized_v_1_0_x, + getLocalUserID_v_1_0_x, + getUserName_v_1_0_x, + getChannelName_v_1_0_x, + getAllUsers_v_1_0_x, + getAllChannels_v_1_0_x, + getChannelOfUser_v_1_0_x, + getUsersInChannel_v_1_0_x, + getLocalUserTransmissionMode_v_1_0_x, + isUserLocallyMuted_v_1_0_x, + isLocalUserMuted_v_1_0_x, + isLocalUserDeafened_v_1_0_x, + getUserHash_v_1_0_x, + getServerHash_v_1_0_x, + getUserComment_v_1_0_x, + getChannelDescription_v_1_0_x, + isChannelLinkedTo_v_1_3_x, + getLinkedChannels_v_1_3_x, + requestLocalUserTransmissionMode_v_1_0_x, + requestUserMove_v_1_0_x, + requestMicrophoneActivationOverwrite_v_1_0_x, + requestLocalMute_v_1_0_x, + requestLocalUserMute_v_1_0_x, + requestLocalUserDeaf_v_1_0_x, + requestSetLocalUserComment_v_1_0_x, + requestLinkChannels_v_1_3_x, + requestUnlinkChannels_v_1_3_x, + requestUnlinkChannelSet_v_1_3_x, + requestStartListeningToChannels_v_1_3_x, + requestStopListeningToChannels_v_1_3_x, + requestSendUserTextMessage_v_1_3_x, + findUserByName_v_1_0_x, + findChannelByName_v_1_0_x, + getMumbleSetting_bool_v_1_0_x, + getMumbleSetting_int_v_1_0_x, + getMumbleSetting_double_v_1_0_x, + getMumbleSetting_string_v_1_0_x, + setMumbleSetting_bool_v_1_0_x, + setMumbleSetting_int_v_1_0_x, + setMumbleSetting_double_v_1_0_x, + setMumbleSetting_string_v_1_0_x, + sendData_v_1_0_x, + log_v_1_0_x, + playSample_v_1_2_x }; +} + #define MAP(qtName, apiName) \ case Qt::Key_##qtName: \ return MUMBLE_KC_##apiName diff --git a/src/mumble/MumbleAPI_structs.h b/src/mumble/MumbleAPI_structs.h index c606a030e73..12999ba561e 100644 --- a/src/mumble/MumbleAPI_structs.h +++ b/src/mumble/MumbleAPI_structs.h @@ -15,6 +15,17 @@ // First, include the latest plugin API header file completely #include "MumblePlugin.h" +// Now, include all older API structs for backward compatibility +// Re-include the API definition +#undef EXTERNAL_MUMBLE_PLUGIN_MUMBLE_API_ +// But this time, overwrite the version +#undef MUMBLE_PLUGIN_API_MAJOR_MACRO +#define MUMBLE_PLUGIN_API_MAJOR_MACRO 1 +#undef MUMBLE_PLUGIN_API_MINOR_MACRO +#define MUMBLE_PLUGIN_API_MINOR_MACRO 2 + +#include "MumblePlugin.h" + // Re-include the API definition #undef EXTERNAL_MUMBLE_PLUGIN_MUMBLE_API_ diff --git a/src/mumble/Plugin.cpp b/src/mumble/Plugin.cpp index af5253f2aa6..e5e35325c32 100644 --- a/src/mumble/Plugin.cpp +++ b/src/mumble/Plugin.cpp @@ -329,6 +329,9 @@ mumble_error_t Plugin::init() { } else if (apiVersion >= mumble_version_t({ 1, 2, 0 }) && apiVersion < mumble_version_t({ 1, 3, 0 })) { MumbleAPI_v_1_2_x api = API::getMumbleAPI_v_1_2_x(); registerAPIFunctions(&api); + } else if (apiVersion >= mumble_version_t({ 1, 3, 0 }) && apiVersion < mumble_version_t({ 1, 4, 0 })) { + MumbleAPI_v_1_3_x api = API::getMumbleAPI_v_1_3_x(); + registerAPIFunctions(&api); } else { // The API version could not be obtained -> this is an invalid plugin that shouldn't have been loaded in the // first place diff --git a/src/mumble/ServerHandler.cpp b/src/mumble/ServerHandler.cpp index 3a98b0a44b7..2cc36c75528 100644 --- a/src/mumble/ServerHandler.cpp +++ b/src/mumble/ServerHandler.cpp @@ -879,17 +879,16 @@ bool ServerHandler::isStrong() const { return bStrong; } -void ServerHandler::requestUserStats(unsigned int uiSession, bool statsOnly) { +void ServerHandler::requestUserStats(unsigned int uiSession, bool statsOnly, const QStringList &temporaryAccessTokens) { MumbleProto::UserStats mpus; mpus.set_session(uiSession); mpus.set_stats_only(statsOnly); - sendMessage(mpus); -} -void ServerHandler::joinChannel(unsigned int uiSession, unsigned int channel) { - static const QStringList EMPTY; + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } - joinChannel(uiSession, channel, EMPTY); + sendMessage(mpus); } void ServerHandler::joinChannel(unsigned int uiSession, unsigned int channel, @@ -898,18 +897,19 @@ void ServerHandler::joinChannel(unsigned int uiSession, unsigned int channel, mpus.set_session(uiSession); mpus.set_channel_id(channel); - foreach (const QString &tmpToken, temporaryAccessTokens) { - mpus.add_temporary_access_tokens(tmpToken.toUtf8().constData()); + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); } sendMessage(mpus); } -void ServerHandler::startListeningToChannel(unsigned int channel) { - startListeningToChannels({ channel }); +void ServerHandler::startListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens) { + startListeningToChannels({ channel }, temporaryAccessTokens); } -void ServerHandler::startListeningToChannels(const QList< unsigned int > &channelIDs) { +void ServerHandler::startListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens) { if (channelIDs.isEmpty()) { return; } @@ -923,14 +923,19 @@ void ServerHandler::startListeningToChannels(const QList< unsigned int > &channe mpus.add_listening_channel_add(currentChannel); } + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpus); } -void ServerHandler::stopListeningToChannel(unsigned int channel) { - stopListeningToChannels({ channel }); +void ServerHandler::stopListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens) { + stopListeningToChannels({ channel }, temporaryAccessTokens); } -void ServerHandler::stopListeningToChannels(const QList< unsigned int > &channelIDs) { +void ServerHandler::stopListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens) { if (channelIDs.isEmpty()) { return; } @@ -944,11 +949,16 @@ void ServerHandler::stopListeningToChannels(const QList< unsigned int > &channel mpus.add_listening_channel_remove(currentChannel); } + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpus); } void ServerHandler::createChannel(unsigned int parent_id, const QString &name, const QString &description, - unsigned int position, bool temporary, unsigned int maxUsers) { + unsigned int position, bool temporary, unsigned int maxUsers, + const QStringList &temporaryAccessTokens) { MumbleProto::ChannelState mpcs; mpcs.set_parent(parent_id); mpcs.set_name(u8(name)); @@ -956,71 +966,121 @@ void ServerHandler::createChannel(unsigned int parent_id, const QString &name, c mpcs.set_position(static_cast< int >(position)); mpcs.set_temporary(temporary); mpcs.set_max_users(maxUsers); + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpcs); } -void ServerHandler::requestBanList() { +void ServerHandler::requestBanList(const QStringList &temporaryAccessTokens) { MumbleProto::BanList mpbl; mpbl.set_query(true); + + for (const QString &token : temporaryAccessTokens) { + mpbl.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpbl); } -void ServerHandler::requestUserList() { +void ServerHandler::requestUserList(const QStringList &temporaryAccessTokens) { MumbleProto::UserList mpul; + + for (const QString &token : temporaryAccessTokens) { + mpul.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpul); } -void ServerHandler::requestACL(unsigned int channel) { +void ServerHandler::requestACL(unsigned int channel, const QStringList &temporaryAccessTokens) { MumbleProto::ACL mpacl; mpacl.set_channel_id(channel); mpacl.set_query(true); + + for (const QString &token : temporaryAccessTokens) { + mpacl.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpacl); } -void ServerHandler::registerUser(unsigned int uiSession) { +void ServerHandler::registerUser(unsigned int uiSession, const QStringList &temporaryAccessTokens) { MumbleProto::UserState mpus; mpus.set_session(uiSession); mpus.set_user_id(0); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpus); } -void ServerHandler::kickBanUser(unsigned int uiSession, const QString &reason, bool ban) { +void ServerHandler::kickBanUser(unsigned int uiSession, const QString &reason, bool ban, + const QStringList &temporaryAccessTokens) { MumbleProto::UserRemove mpur; mpur.set_session(uiSession); mpur.set_reason(u8(reason)); mpur.set_ban(ban); + + for (const QString &token : temporaryAccessTokens) { + mpur.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpur); } -void ServerHandler::sendUserTextMessage(unsigned int uiSession, const QString &message_) { +void ServerHandler::sendUserTextMessage(unsigned int uiSession, const QString &message, + const QStringList &temporaryAccessTokens) { MumbleProto::TextMessage mptm; mptm.add_session(uiSession); - mptm.set_message(u8(message_)); + mptm.set_message(u8(message)); + + for (const QString &token : temporaryAccessTokens) { + mptm.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mptm); } -void ServerHandler::sendChannelTextMessage(unsigned int channel, const QString &message_, bool tree) { +void ServerHandler::sendChannelTextMessage(unsigned int channel, const QString &message, bool tree, + const QStringList &temporaryAccessTokens) { MumbleProto::TextMessage mptm; if (tree) { mptm.add_tree_id(channel); } else { mptm.add_channel_id(channel); - if (message_ == QString::fromUtf8(Global::get().ccHappyEaster + 10)) + if (message == QString::fromUtf8(Global::get().ccHappyEaster + 10)) Global::get().bHappyEaster = true; } - mptm.set_message(u8(message_)); + mptm.set_message(u8(message)); + + for (const QString &token : temporaryAccessTokens) { + mptm.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mptm); } -void ServerHandler::setUserComment(unsigned int uiSession, const QString &comment) { +void ServerHandler::setUserComment(unsigned int uiSession, const QString &comment, + const QStringList &temporaryAccessTokens) { MumbleProto::UserState mpus; mpus.set_session(uiSession); mpus.set_comment(u8(comment)); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpus); } -void ServerHandler::setUserTexture(unsigned int uiSession, const QByteArray &qba) { +void ServerHandler::setUserTexture(unsigned int uiSession, const QByteArray &qba, + const QStringList &temporaryAccessTokens) { QByteArray texture; if ((m_version >= Version::fromComponents(1, 2, 2)) || qba.isEmpty()) { @@ -1070,6 +1130,11 @@ void ServerHandler::setUserTexture(unsigned int uiSession, const QByteArray &qba MumbleProto::UserState mpus; mpus.set_session(uiSession); mpus.set_texture(blob(texture)); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpus); if (!texture.isEmpty()) { @@ -1084,23 +1149,94 @@ void ServerHandler::setTokens(const QStringList &tokens) { sendMessage(msg); } -void ServerHandler::removeChannel(unsigned int channel) { +void ServerHandler::removeChannel(unsigned int channel, const QStringList &temporaryAccessTokens) { MumbleProto::ChannelRemove mpcr; mpcr.set_channel_id(channel); + + for (const QString &token : temporaryAccessTokens) { + mpcr.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpcr); } -void ServerHandler::addChannelLink(unsigned int channel, unsigned int link) { +void ServerHandler::addChannelLink(unsigned int channel, unsigned int link, const QStringList &temporaryAccessTokens) { MumbleProto::ChannelState mpcs; mpcs.set_channel_id(channel); mpcs.add_links_add(link); + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + +void ServerHandler::addChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens) { + if (channelCount < 2) { + return; + } + MumbleProto::ChannelState mpcs; + mpcs.set_channel_id(channelSet[0]); + for (unsigned int i = 1; i < channelCount; i++) { + mpcs.add_links_add(channelSet[i]); + } + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpcs); } -void ServerHandler::removeChannelLink(unsigned int channel, unsigned int link) { +void ServerHandler::removeChannelLink(unsigned int channel, unsigned int link, + const QStringList &temporaryAccessTokens) { MumbleProto::ChannelState mpcs; mpcs.set_channel_id(channel); mpcs.add_links_remove(link); + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + +void ServerHandler::removeChannelLinks(unsigned int channel, const unsigned int channelCount, + const unsigned int *channelSet, const QStringList &temporaryAccessTokens) { + if (channelCount < 1) { + return; + } + MumbleProto::ChannelState mpcs; + mpcs.set_channel_id(channel); + for (unsigned int i = 0; i < channelCount; i++) { + mpcs.add_links_remove(channelSet[i]); + } + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + +void ServerHandler::removeChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens) { + if (channelCount < 2) { + return; + } + MumbleProto::ChannelState mpcs; + unsigned int link = channelSet[0]; + for (unsigned int i = 1; i < channelCount; i++) { + mpcs.set_channel_id(link); + mpcs.add_links_remove(channelSet[i]); + } + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpcs); } diff --git a/src/mumble/ServerHandler.h b/src/mumble/ServerHandler.h index 3c3c4244168..43da62230c1 100644 --- a/src/mumble/ServerHandler.h +++ b/src/mumble/ServerHandler.h @@ -152,28 +152,38 @@ class ServerHandler : public QThread { MUMBLE_ALL_TCP_MESSAGES #undef PROCESS_MUMBLE_TCP_MESSAGE - void requestUserStats(unsigned int uiSession, bool statsOnly); - void joinChannel(unsigned int uiSession, unsigned int channel); - void joinChannel(unsigned int uiSession, unsigned int channel, const QStringList &temporaryAccessTokens); - void startListeningToChannel(unsigned int channel); - void startListeningToChannels(const QList< unsigned int > &channelIDs); - void stopListeningToChannel(unsigned int channel); - void stopListeningToChannels(const QList< unsigned int > &channelIDs); + void requestUserStats(unsigned int uiSession, bool statsOnly, const QStringList &temporaryAccessTokens = {}); + void joinChannel(unsigned int uiSession, unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void startListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void startListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens = {}); + void stopListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void stopListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens = {}); void createChannel(unsigned int parent_id, const QString &name, const QString &description, unsigned int position, - bool temporary, unsigned int maxUsers); - void requestBanList(); - void requestUserList(); - void requestACL(unsigned int channel); - void registerUser(unsigned int uiSession); - void kickBanUser(unsigned int uiSession, const QString &reason, bool ban); - void sendUserTextMessage(unsigned int uiSession, const QString &message_); - void sendChannelTextMessage(unsigned int channel, const QString &message_, bool tree); - void setUserComment(unsigned int uiSession, const QString &comment); - void setUserTexture(unsigned int uiSession, const QByteArray &qba); + bool temporary, unsigned int maxUsers, const QStringList &temporaryAccessTokens = {}); + void requestBanList(const QStringList &temporaryAccessTokens = {}); + void requestUserList(const QStringList &temporaryAccessTokens = {}); + void requestACL(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void registerUser(unsigned int uiSession, const QStringList &temporaryAccessTokens = {}); + void kickBanUser(unsigned int uiSession, const QString &reason, bool ban, + const QStringList &temporaryAccessTokens = {}); + void sendUserTextMessage(unsigned int uiSession, const QString &message_, + const QStringList &temporaryAccessTokens = {}); + void sendChannelTextMessage(unsigned int channel, const QString &message_, bool tree, + const QStringList &temporaryAccessTokens = {}); + void setUserComment(unsigned int uiSession, const QString &comment, const QStringList &temporaryAccessTokens = {}); + void setUserTexture(unsigned int uiSession, const QByteArray &qba, const QStringList &temporaryAccessTokens = {}); void setTokens(const QStringList &tokens); - void removeChannel(unsigned int channel); - void addChannelLink(unsigned int channel, unsigned int link); - void removeChannelLink(unsigned int channel, unsigned int link); + void removeChannel(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void addChannelLink(unsigned int channel, unsigned int link, const QStringList &temporaryAccessTokens = {}); + void addChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens = {}); + void removeChannelLink(unsigned int channel, unsigned int link, const QStringList &temporaryAccessTokens = {}); + void removeChannelLinks(const unsigned int channel, const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens = {}); + void removeChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens = {}); void requestChannelPermissions(unsigned int channel); void setSelfMuteDeafState(bool mute, bool deaf); void announceRecordingState(bool recording); diff --git a/src/mumble/back.cpp b/src/mumble/back.cpp new file mode 100644 index 00000000000..2cc36c75528 --- /dev/null +++ b/src/mumble/back.cpp @@ -0,0 +1,1278 @@ +// Copyright The Mumble Developers. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file at the root of the +// Mumble source tree or at . + +#include +#include + +#ifdef Q_OS_WIN +# include "win.h" +#endif + +#include "ServerHandler.h" + +#include "AudioInput.h" +#include "AudioOutput.h" +#include "Cert.h" +#include "Connection.h" +#include "Database.h" +#include "HostAddress.h" +#include "MainWindow.h" +#include "Net.h" +#include "NetworkConfig.h" +#include "OSInfo.h" +#include "PacketDataStream.h" +#include "ProtoUtils.h" +#include "RichTextEditor.h" +#include "SSL.h" +#include "ServerResolver.h" +#include "ServerResolverRecord.h" +#include "User.h" +#include "Utils.h" +#include "Global.h" + +#include +#include +#include +#include +#include + +#include + +#include + +#ifdef Q_OS_WIN +// is not protected with an include guard on MinGW, resulting in +// redefinitions if the PCH header is used. +// The workaround consists in including the header only if _DELAY_IMP_VER +// (defined in the header) is not defined. +# ifndef _DELAY_IMP_VER +# include +# endif +# include +# include +# include +#else +# if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) +# include +# endif +# include +# include +#endif + +// Init ServerHandler::nextConnectionID +int ServerHandler::nextConnectionID = -1; +QMutex ServerHandler::nextConnectionIDMutex; + +ServerHandlerMessageEvent::ServerHandlerMessageEvent(const QByteArray &msg, Mumble::Protocol::TCPMessageType type, + bool flush) + : QEvent(static_cast< QEvent::Type >(SERVERSEND_EVENT)) { + qbaMsg = msg; + this->type = type; + bFlush = flush; +} + +#ifdef Q_OS_WIN +static HANDLE loadQoS() { + HANDLE hQoS = nullptr; + + HRESULT hr = E_FAIL; + +// We don't support delay-loading QoS on MinGW. Only enable it for MSVC. +# ifdef _MSC_VER + __try { + hr = __HrLoadAllImportsForDll("qwave.dll"); + } + + __except (EXCEPTION_EXECUTE_HANDLER) { + hr = E_FAIL; + } +# endif + + if (!SUCCEEDED(hr)) { + qWarning("ServerHandler: Failed to load qWave.dll, no QoS available"); + } else { + QOS_VERSION qvVer; + qvVer.MajorVersion = 1; + qvVer.MinorVersion = 0; + + if (!QOSCreateHandle(&qvVer, &hQoS)) { + qWarning("ServerHandler: Failed to create QOS2 handle"); + hQoS = nullptr; + } else { + qWarning("ServerHandler: QOS2 loaded"); + } + } + return hQoS; +} +#endif + +ServerHandler::ServerHandler() : database(new Database(QLatin1String("ServerHandler"))) { + cConnection.reset(); + qusUdp = nullptr; + bStrong = false; + usPort = 0; + bUdp = true; + tConnectionTimeoutTimer = nullptr; + m_version = Version::UNKNOWN; + iInFlightTCPPings = 0; + + // assign connection ID + { + QMutexLocker lock(&nextConnectionIDMutex); + nextConnectionID++; + connectionID = nextConnectionID; + } + + // Historically, the qWarning line below initialized OpenSSL for us. + // It used to have this comment: + // + // "For some strange reason, on Win32, we have to call + // supportsSsl before the cipher list is ready." + // + // Now, OpenSSL is initialized in main() via MumbleSSL::initialize(), + // but since it's handy to have the OpenSSL version available, we + // keep this one around as well. + qWarning("OpenSSL Support: %d (%s)", QSslSocket::supportsSsl(), SSLeay_version(SSLEAY_VERSION)); + + MumbleSSL::addSystemCA(); + + { + QList< QSslCipher > ciphers = MumbleSSL::ciphersFromOpenSSLCipherString(Global::get().s.qsSslCiphers); + if (ciphers.isEmpty()) { + qFatal("Invalid 'net/sslciphers' config option. Either the cipher string is invalid or none of the ciphers " + "are available:: \"%s\"", + qPrintable(Global::get().s.qsSslCiphers)); + } + + QSslConfiguration config = QSslConfiguration::defaultConfiguration(); + config.setCiphers(ciphers); + QSslConfiguration::setDefaultConfiguration(config); + + QStringList pref; + foreach (QSslCipher c, ciphers) { pref << c.name(); } + qWarning("ServerHandler: TLS cipher preference is \"%s\"", qPrintable(pref.join(QLatin1String(":")))); + } + +#ifdef Q_OS_WIN + hQoS = loadQoS(); + if (hQoS) + Connection::setQoS(hQoS); +#endif + + connect(this, SIGNAL(pingRequested()), this, SLOT(sendPingInternal()), Qt::QueuedConnection); +} + +ServerHandler::~ServerHandler() { + wait(); + cConnection.reset(); +#ifdef Q_OS_WIN + if (hQoS) { + QOSCloseHandle(hQoS); + Connection::setQoS(nullptr); + } +#endif +} + +void ServerHandler::customEvent(QEvent *evt) { + if (evt->type() != SERVERSEND_EVENT) + return; + + ServerHandlerMessageEvent *shme = static_cast< ServerHandlerMessageEvent * >(evt); + + ConnectionPtr connection(cConnection); + if (connection) { + if (shme->qbaMsg.size() > 0) { + connection->sendMessage(shme->qbaMsg); + if (shme->bFlush) + connection->forceFlush(); + } else { + exit(0); + } + } +} + +int ServerHandler::getConnectionID() const { + return connectionID; +} + +void ServerHandler::setProtocolVersion(Version::full_t version) { + m_version = version; + + m_udpPingEncoder.setProtocolVersion(version); + m_udpDecoder.setProtocolVersion(version); + m_tcpTunnelDecoder.setProtocolVersion(version); +} + +void ServerHandler::udpReady() { + while (qusUdp->hasPendingDatagrams()) { + char encrypted[Mumble::Protocol::MAX_UDP_PACKET_SIZE]; + unsigned int buflen = static_cast< unsigned int >(qusUdp->pendingDatagramSize()); + + if (buflen > Mumble::Protocol::MAX_UDP_PACKET_SIZE) { + // Discard datagrams that exceed our buffer's size as we'd have to trim them down anyways and it is not very + // likely that the data is valid in the trimmed down form. + // As we're using a maxSize of 0 it is okay to pass nullptr as the data buffer. Qt's docs (5.15) ensures + // that a maxSize of 0 means discarding the datagram. + qusUdp->readDatagram(nullptr, 0); + continue; + } + + QHostAddress senderAddr; + quint16 senderPort; + qusUdp->readDatagram(encrypted, buflen, &senderAddr, &senderPort); + + if (!(HostAddress(senderAddr) == HostAddress(qhaRemote)) || (senderPort != usResolvedPort)) + continue; + + ConnectionPtr connection(cConnection); + if (!connection) + continue; + + if (!connection->csCrypt->isValid()) + continue; + + if (buflen < 5) + continue; + + gsl::span< Mumble::Protocol::byte > buffer = m_udpDecoder.getBuffer(); + + // 4 bytes is the overhead of the encryption + assert(buffer.size() >= buflen - 4); + + if (!connection->csCrypt->decrypt(reinterpret_cast< const unsigned char * >(encrypted), buffer.data(), + buflen)) { + if (connection->csCrypt->tLastGood.elapsed() > 5000000ULL) { + if (connection->csCrypt->tLastRequest.elapsed() > 5000000ULL) { + connection->csCrypt->tLastRequest.restart(); + MumbleProto::CryptSetup mpcs; + sendMessage(mpcs); + } + } + continue; + } + + if (m_udpDecoder.decode(buffer.subspan(0, buflen - 4))) { + switch (m_udpDecoder.getMessageType()) { + case Mumble::Protocol::UDPMessageType::Ping: { + const Mumble::Protocol::PingData pingData = m_udpDecoder.getPingData(); + + accUDP(static_cast< double >(tTimestamp.elapsed() - pingData.timestamp) / 1000.0); + + break; + } + case Mumble::Protocol::UDPMessageType::Audio: { + const Mumble::Protocol::AudioData audioData = m_udpDecoder.getAudioData(); + + handleVoicePacket(audioData); + break; + }; + } + } + } +} + +void ServerHandler::handleVoicePacket(const Mumble::Protocol::AudioData &audioData) { + if (audioData.usedCodec != Mumble::Protocol::AudioCodec::Opus) { + qWarning("Dropping audio packet using invalid codec (not Opus): %d", static_cast< int >(audioData.usedCodec)); + return; + } + + ClientUser *sender = ClientUser::get(audioData.senderSession); + + AudioOutputPtr ao = Global::get().ao; + if (ao && sender + && !((audioData.targetOrContext == Mumble::Protocol::AudioContext::WHISPER) && Global::get().s.bWhisperFriends + && sender->qsFriendName.isEmpty())) { + ao->addFrameToBuffer(sender, audioData); + } +} + +void ServerHandler::sendMessage(const unsigned char *data, int len, bool force) { + static std::vector< unsigned char > crypto; + crypto.resize(static_cast< std::size_t >(len + 4)); + + QMutexLocker qml(&qmUdp); + + if (!qusUdp) + return; + + ConnectionPtr connection(cConnection); + if (!connection || !connection->csCrypt->isValid()) + return; + + if (!force && (NetworkConfig::TcpModeEnabled() || !bUdp)) { + QByteArray qba; + + qba.resize(len + 6); + unsigned char *uc = reinterpret_cast< unsigned char * >(qba.data()); + *reinterpret_cast< quint16 * >(&uc[0]) = + qToBigEndian(static_cast< quint16 >(Mumble::Protocol::TCPMessageType::UDPTunnel)); + *reinterpret_cast< quint32 * >(&uc[2]) = qToBigEndian(static_cast< quint32 >(len)); + memcpy(uc + 6, data, static_cast< std::size_t >(len)); + + QApplication::postEvent(this, + new ServerHandlerMessageEvent(qba, Mumble::Protocol::TCPMessageType::UDPTunnel, true)); + } else { + if (!connection->csCrypt->encrypt(reinterpret_cast< const unsigned char * >(data), crypto.data(), + static_cast< unsigned int >(len))) { + return; + } + qusUdp->writeDatagram(reinterpret_cast< const char * >(crypto.data()), len + 4, qhaRemote, usResolvedPort); + } +} + +void ServerHandler::sendProtoMessage(const ::google::protobuf::Message &msg, Mumble::Protocol::TCPMessageType type) { + QByteArray qba; + + if (QThread::currentThread() != thread()) { + Connection::messageToNetwork(msg, type, qba); + ServerHandlerMessageEvent *shme = new ServerHandlerMessageEvent(qba, type, false); + QApplication::postEvent(this, shme); + } else { + ConnectionPtr connection(cConnection); + if (!connection) + return; + + connection->sendMessage(msg, type, qba); + } +} + +bool ServerHandler::isConnected() const { + // If the digest isn't empty, then we are currently connected to a server (the digest being a hash + // of the server's certificate) + return !qbaDigest.isEmpty(); +} + +bool ServerHandler::hasSynchronized() const { + return serverSynchronized; +} + +void ServerHandler::setServerSynchronized(bool synchronized) { + serverSynchronized = synchronized; +} + +void ServerHandler::hostnameResolved() { + ServerResolver *sr = qobject_cast< ServerResolver * >(QObject::sender()); + QList< ServerResolverRecord > records = sr->records(); + + // Exit the ServerHandler thread's event loop with an + // error code in case our hostname lookup failed. + if (records.isEmpty()) { + exit(-1); + return; + } + + // Create the list of target host:port pairs + // that the ServerHandler should try to connect to. + QList< ServerAddress > ql; + QHash< ServerAddress, QString > qh; + foreach (ServerResolverRecord record, records) { + foreach (HostAddress addr, record.addresses()) { + auto sa = ServerAddress(addr, record.port()); + ql.append(sa); + qh[sa] = record.hostname(); + } + } + qlAddresses = ql; + qhHostnames = qh; + + // Exit the event loop with 'success' status code, + // to continue connecting to the server. + exit(0); +} + +void ServerHandler::run() { + // Resolve the hostname... + { + ServerResolver sr; + QObject::connect(&sr, SIGNAL(resolved()), this, SLOT(hostnameResolved())); + sr.resolve(qsHostName, usPort); + int ret = exec(); + if (ret < 0) { + qWarning("ServerHandler: failed to resolve hostname"); + emit error(QAbstractSocket::HostNotFoundError, tr("Unable to resolve hostname")); + return; + } + } + + QList< ServerAddress > targetAddresses(qlAddresses); + bool shouldTryNextTargetServer = true; + do { + saTargetServer = qlAddresses.takeFirst(); + + tConnectionTimeoutTimer = nullptr; + qbaDigest = QByteArray(); + bStrong = true; + qtsSock = new QSslSocket(this); + qtsSock->setPeerVerifyName(qhHostnames[saTargetServer]); + + if (!Global::get().s.bSuppressIdentity && CertWizard::validateCert(Global::get().s.kpCertificate)) { + qtsSock->setPrivateKey(Global::get().s.kpCertificate.second); + qtsSock->setLocalCertificate(Global::get().s.kpCertificate.first.at(0)); + QSslConfiguration config = qtsSock->sslConfiguration(); + QList< QSslCertificate > certs = config.caCertificates(); + certs << Global::get().s.kpCertificate.first; + config.setCaCertificates(certs); + qtsSock->setSslConfiguration(config); + } + + { + ConnectionPtr connection(new Connection(this, qtsSock)); + cConnection = connection; + + // Technically it isn't necessary to reset this flag here since a ServerHandler will not be used + // for multiple connections in a row but just in case that at some point it will, we'll reset the + // flag here. + serverSynchronized = false; + + qlErrors.clear(); + qscCert.clear(); + + connect(qtsSock, &QSslSocket::encrypted, this, &ServerHandler::serverConnectionConnected); + connect(qtsSock, &QSslSocket::stateChanged, this, &ServerHandler::serverConnectionStateChanged); + connect(connection.get(), &Connection::connectionClosed, this, &ServerHandler::serverConnectionClosed); + connect(connection.get(), &Connection::message, this, &ServerHandler::message); + connect(connection.get(), &Connection::handleSslErrors, this, &ServerHandler::setSslErrors); + } + bUdp = false; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0) + qtsSock->setProtocol(QSsl::TlsV1_2OrLater); +#else + qtsSock->setProtocol(QSsl::TlsV1_0OrLater); +#endif + + qtsSock->connectToHost(saTargetServer.host.toAddress(), saTargetServer.port); + + tTimestamp.restart(); + + // Setup ping timer; + QTimer *ticker = new QTimer(this); + connect(ticker, SIGNAL(timeout()), this, SLOT(sendPing())); + ticker->start(Global::get().s.iPingIntervalMsec); + + Global::get().mw->rtLast = MumbleProto::Reject_RejectType_None; + + accUDP = accTCP = accClean; + + m_version = Version::UNKNOWN; + qsRelease = QString(); + qsOS = QString(); + qsOSVersion = QString(); + + int ret = exec(); + if (ret == -2) { + shouldTryNextTargetServer = true; + } else { + shouldTryNextTargetServer = false; + } + + if (qusUdp) { + QMutexLocker qml(&qmUdp); + +#ifdef Q_OS_WIN + if (hQoS) { + if (!QOSRemoveSocketFromFlow(hQoS, 0, dwFlowUDP, 0)) { + qWarning("ServerHandler: Failed to remove UDP from QoS. QOSRemoveSocketFromFlow() failed with " + "error %lu!", + GetLastError()); + } + + dwFlowUDP = 0; + } +#endif + delete qusUdp; + qusUdp = nullptr; + } + + ticker->stop(); + + ConnectionPtr cptr(cConnection); + if (cptr) { + cptr->disconnectSocket(true); + } + + cConnection.reset(); + while (!cptr.unique()) { + msleep(100); + } + delete qtsSock; + delete tConnectionTimeoutTimer; + } while (shouldTryNextTargetServer && !qlAddresses.isEmpty()); +} + +#ifdef Q_OS_WIN +extern DWORD WinVerifySslCert(const QByteArray &cert); +#endif + +void ServerHandler::setSslErrors(const QList< QSslError > &errors) { + ConnectionPtr connection(cConnection); + if (!connection) + return; + + qscCert = connection->peerCertificateChain(); + QList< QSslError > newErrors = errors; + +#ifdef Q_OS_WIN + bool bRevalidate = false; + QList< QSslError > errorsToRemove; + foreach (const QSslError &e, errors) { + switch (e.error()) { + case QSslError::UnableToGetLocalIssuerCertificate: + case QSslError::SelfSignedCertificateInChain: + bRevalidate = true; + errorsToRemove << e; + break; + default: + break; + } + } + + if (bRevalidate) { + QByteArray der = qscCert.first().toDer(); + DWORD errorStatus = WinVerifySslCert(der); + if (errorStatus == CERT_TRUST_NO_ERROR) { + foreach (const QSslError &e, errorsToRemove) { newErrors.removeOne(e); } + } + if (newErrors.isEmpty()) { + connection->proceedAnyway(); + return; + } + } +#endif + + bStrong = false; + if ((qscCert.size() > 0) + && (QString::fromLatin1(qscCert.at(0).digest(QCryptographicHash::Sha1).toHex()) + == database->getDigest(qsHostName, usPort))) + connection->proceedAnyway(); + else + qlErrors = newErrors; +} + +void ServerHandler::sendPing() { + emit pingRequested(); +} + +void ServerHandler::sendPingInternal() { + ConnectionPtr connection(cConnection); + if (!connection) + return; + + if (qtsSock->state() != QAbstractSocket::ConnectedState) { + return; + } + + // Ensure the TLS handshake has completed before sending pings. + if (!qtsSock->isEncrypted()) { + return; + } + + if (Global::get().s.iMaxInFlightTCPPings > 0 && iInFlightTCPPings >= Global::get().s.iMaxInFlightTCPPings) { + serverConnectionClosed(QAbstractSocket::UnknownSocketError, tr("Server is not responding to TCP pings")); + return; + } + + quint64 t = tTimestamp.elapsed(); + + if (qusUdp) { + Mumble::Protocol::PingData pingData; + pingData.timestamp = t; + pingData.requestAdditionalInformation = false; + + m_udpPingEncoder.setProtocolVersion(m_version); + gsl::span< const Mumble::Protocol::byte > encodedPacket = m_udpPingEncoder.encodePingPacket(pingData); + + sendMessage(encodedPacket.data(), static_cast< int >(encodedPacket.size()), true); + } + + MumbleProto::Ping mpp; + + mpp.set_timestamp(t); + mpp.set_good(connection->csCrypt->uiGood); + mpp.set_late(connection->csCrypt->uiLate); + mpp.set_lost(connection->csCrypt->uiLost); + mpp.set_resync(connection->csCrypt->uiResync); + + + if (boost::accumulators::count(accUDP)) { + mpp.set_udp_ping_avg(static_cast< float >(boost::accumulators::mean(accUDP))); + mpp.set_udp_ping_var(static_cast< float >(boost::accumulators::variance(accUDP))); + } + mpp.set_udp_packets(static_cast< unsigned int >(boost::accumulators::count(accUDP))); + + if (boost::accumulators::count(accTCP)) { + mpp.set_tcp_ping_avg(static_cast< float >(boost::accumulators::mean(accTCP))); + mpp.set_tcp_ping_var(static_cast< float >(boost::accumulators::variance(accTCP))); + } + mpp.set_tcp_packets(static_cast< unsigned int >(boost::accumulators::count(accTCP))); + + sendMessage(mpp); + + iInFlightTCPPings += 1; +} + +void ServerHandler::message(Mumble::Protocol::TCPMessageType type, const QByteArray &qbaMsg) { + const char *ptr = qbaMsg.constData(); + if (type == Mumble::Protocol::TCPMessageType::UDPTunnel) { + // audio tunneled through tcp. + // since it could happen that we are receiving udp and tcp messages at the same time (e.g. the server used to + // send us packages via TCP but has now switched to UDP again and the first UDP packages arrive at the same time + // as the last TCP ones), we want to use a dedicated decoder for this (to make sure there is no concurrent + // access to the decoder's internal buffer). + if (m_tcpTunnelDecoder.decode( + { reinterpret_cast< const Mumble::Protocol::byte * >(ptr), static_cast< std::size_t >(qbaMsg.size()) }) + && m_tcpTunnelDecoder.getMessageType() == Mumble::Protocol::UDPMessageType::Audio) { + handleVoicePacket(m_tcpTunnelDecoder.getAudioData()); + } + } else if (type == Mumble::Protocol::TCPMessageType::Ping) { + MumbleProto::Ping msg; + if (msg.ParseFromArray(qbaMsg.constData(), static_cast< int >(qbaMsg.size()))) { + ConnectionPtr connection(cConnection); + if (!connection) + return; + + // Reset in-flight TCP ping counter to 0. + // We've received a ping. That means the + // connection is still OK. + iInFlightTCPPings = 0; + + connection->csCrypt->uiRemoteGood = msg.good(); + connection->csCrypt->uiRemoteLate = msg.late(); + connection->csCrypt->uiRemoteLost = msg.lost(); + connection->csCrypt->uiRemoteResync = msg.resync(); + accTCP(static_cast< double >(tTimestamp.elapsed() - msg.timestamp()) / 1000.0); + + if (((connection->csCrypt->uiRemoteGood == 0) || (connection->csCrypt->uiGood == 0)) && bUdp + && (tTimestamp.elapsed() > 20000000ULL)) { + bUdp = false; + if (!NetworkConfig::TcpModeEnabled()) { + if ((connection->csCrypt->uiRemoteGood == 0) && (connection->csCrypt->uiGood == 0)) + Global::get().mw->msgBox( + tr("UDP packets cannot be sent to or received from the server. Switching to TCP mode.")); + else if (connection->csCrypt->uiRemoteGood == 0) + Global::get().mw->msgBox( + tr("UDP packets cannot be sent to the server. Switching to TCP mode.")); + else + Global::get().mw->msgBox( + tr("UDP packets cannot be received from the server. Switching to TCP mode.")); + + database->setUdp(qbaDigest, false); + } + } else if (!bUdp && (connection->csCrypt->uiRemoteGood > 3) && (connection->csCrypt->uiGood > 3)) { + bUdp = true; + if (!NetworkConfig::TcpModeEnabled()) { + Global::get().mw->msgBox( + tr("UDP packets can be sent to and received from the server. Switching back to UDP mode.")); + + database->setUdp(qbaDigest, true); + } + } + } + } else { + ServerHandlerMessageEvent *shme = new ServerHandlerMessageEvent(qbaMsg, type, false); + QApplication::postEvent(Global::get().mw, shme); + } +} + +void ServerHandler::disconnect() { + // Actual TCP object is in a different thread, so signal it + // The actual type of this event doesn't matter as we are only abusing the event mechanism to signal the thread to + // exit. + QByteArray qbaBuffer; + ServerHandlerMessageEvent *shme = + new ServerHandlerMessageEvent(qbaBuffer, Mumble::Protocol::TCPMessageType::Ping, false); + QApplication::postEvent(this, shme); +} + +void ServerHandler::serverConnectionClosed(QAbstractSocket::SocketError err, const QString &reason) { + Connection *c = cConnection.get(); + if (!c) + return; + if (c->bDisconnectedEmitted) + return; + c->bDisconnectedEmitted = true; + + AudioOutputPtr ao = Global::get().ao; + if (ao) + ao->wipe(); + + // Try next server in the list if possible. + // Otherwise, emit disconnect and exit with + // a normal status code. + if (!qlAddresses.isEmpty()) { + if (err == QAbstractSocket::ConnectionRefusedError || err == QAbstractSocket::SocketTimeoutError) { + qWarning("ServerHandler: connection attempt to %s:%i failed: %s (%li); trying next server....", + qPrintable(saTargetServer.host.toString()), static_cast< int >(saTargetServer.port), + qPrintable(reason), static_cast< long >(err)); + exit(-2); + return; + } + } + + // Having 2 signals here that basically fire at the same time is wanted behavior! + // See the documentation of "aboutToDisconnect" for an explanation. + emit aboutToDisconnect(err, reason); + emit disconnected(err, reason); + + exit(0); +} + +void ServerHandler::serverConnectionTimeoutOnConnect() { + ConnectionPtr connection(cConnection); + if (connection) + connection->disconnectSocket(true); + + serverConnectionClosed(QAbstractSocket::SocketTimeoutError, tr("Connection timed out")); +} + +void ServerHandler::serverConnectionStateChanged(QAbstractSocket::SocketState state) { + if (state == QAbstractSocket::ConnectingState) { + // Start timer for connection timeout during connect after resolving is completed + tConnectionTimeoutTimer = new QTimer(); + connect(tConnectionTimeoutTimer, SIGNAL(timeout()), this, SLOT(serverConnectionTimeoutOnConnect())); + tConnectionTimeoutTimer->setSingleShot(true); + tConnectionTimeoutTimer->start(Global::get().s.iConnectionTimeoutDurationMsec); + } else if (state == QAbstractSocket::ConnectedState) { + // Start TLS handshake + qtsSock->startClientEncryption(); + } +} + +void ServerHandler::serverConnectionConnected() { + ConnectionPtr connection(cConnection); + if (!connection) + return; + + // The ephemeralServerKey property is only a non-null key, if forward secrecy is used. + // See also https://doc.qt.io/qt-5/qsslconfiguration.html#ephemeralServerKey + connectionUsesPerfectForwardSecrecy = !qtsSock->sslConfiguration().ephemeralServerKey().isNull(); + + iInFlightTCPPings = 0; + + tConnectionTimeoutTimer->stop(); + + if (Global::get().s.bQoS) + connection->setToS(); + + qscCert = connection->peerCertificateChain(); + qscCipher = connection->sessionCipher(); + + if (!qscCert.isEmpty()) { + // Get the server's immediate SSL certificate + const QSslCertificate &qsc = qscCert.first(); + qbaDigest = sha1(qsc.publicKey().toDer()); + bUdp = database->getUdp(qbaDigest); + } else { + // Shouldn't reach this + qCritical("Server must have a certificate. Dropping connection"); + disconnect(); + return; + } + + MumbleProto::Version mpv; + mpv.set_release(u8(Version::getRelease())); + MumbleProto::setVersion(mpv, Version::get()); + + if (!Global::get().s.bHideOS) { + mpv.set_os(u8(OSInfo::getOS())); + mpv.set_os_version(u8(OSInfo::getOSDisplayableVersion())); + } + + sendMessage(mpv); + + MumbleProto::Authenticate mpa; + mpa.set_username(u8(qsUserName)); + mpa.set_password(u8(qsPassword)); + + QStringList tokens = database->getTokens(qbaDigest); + foreach (const QString &qs, tokens) + mpa.add_tokens(u8(qs)); + + mpa.set_opus(true); + sendMessage(mpa); + + { + QMutexLocker qml(&qmUdp); + + qhaRemote = connection->peerAddress(); + qhaLocal = connection->localAddress(); + usResolvedPort = connection->peerPort(); + if (qhaLocal.isNull()) { + qFatal("ServerHandler: qhaLocal is unexpectedly a null addr"); + } + + qusUdp = new QUdpSocket(this); + if (!qusUdp) { + qFatal("ServerHandler: qusUdp is unexpectedly a null addr"); + } + if (Global::get().s.bUdpForceTcpAddr) { + qusUdp->bind(qhaLocal, 0); + } else { + if (qhaRemote.protocol() == QAbstractSocket::IPv6Protocol) { + qusUdp->bind(QHostAddress(QHostAddress::AnyIPv6), 0); + } else { + qusUdp->bind(QHostAddress(QHostAddress::Any), 0); + } + } + + connect(qusUdp, SIGNAL(readyRead()), this, SLOT(udpReady())); + + if (Global::get().s.bQoS) { +#if defined(Q_OS_UNIX) + int val = 0xe0; + if (setsockopt(static_cast< int >(qusUdp->socketDescriptor()), IPPROTO_IP, IP_TOS, &val, sizeof(val))) { + val = 0x80; + if (setsockopt(static_cast< int >(qusUdp->socketDescriptor()), IPPROTO_IP, IP_TOS, &val, sizeof(val))) + qWarning("ServerHandler: Failed to set TOS for UDP Socket"); + } +# if defined(SO_PRIORITY) + socklen_t optlen = sizeof(val); + if (getsockopt(static_cast< int >(qusUdp->socketDescriptor()), SOL_SOCKET, SO_PRIORITY, &val, &optlen) + == 0) { + if (val == 0) { + val = 6; + setsockopt(static_cast< int >(qusUdp->socketDescriptor()), SOL_SOCKET, SO_PRIORITY, &val, + sizeof(val)); + } + } +# endif +#elif defined(Q_OS_WIN) + if (hQoS) { + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(usPort); + addr.sin_addr.s_addr = htonl(qhaRemote.toIPv4Address()); + + dwFlowUDP = 0; + if (!QOSAddSocketToFlow(hQoS, qusUdp->socketDescriptor(), reinterpret_cast< sockaddr * >(&addr), + QOSTrafficTypeVoice, QOS_NON_ADAPTIVE_FLOW, + reinterpret_cast< PQOS_FLOWID >(&dwFlowUDP))) + qWarning("ServerHandler: Failed to add UDP to QOS"); + } +#endif + } + } + + emit connected(); +} + +void ServerHandler::setConnectionInfo(const QString &host, unsigned short port, const QString &username, + const QString &pw) { + qsHostName = host; + usPort = port; + qsUserName = username; + qsPassword = pw; +} + +void ServerHandler::getConnectionInfo(QString &host, unsigned short &port, QString &username, QString &pw) const { + host = qsHostName; + port = usPort; + username = qsUserName; + pw = qsPassword; +} + +bool ServerHandler::isStrong() const { + return bStrong; +} + +void ServerHandler::requestUserStats(unsigned int uiSession, bool statsOnly, const QStringList &temporaryAccessTokens) { + MumbleProto::UserStats mpus; + mpus.set_session(uiSession); + mpus.set_stats_only(statsOnly); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpus); +} + +void ServerHandler::joinChannel(unsigned int uiSession, unsigned int channel, + const QStringList &temporaryAccessTokens) { + MumbleProto::UserState mpus; + mpus.set_session(uiSession); + mpus.set_channel_id(channel); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpus); +} + +void ServerHandler::startListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens) { + startListeningToChannels({ channel }, temporaryAccessTokens); +} + +void ServerHandler::startListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens) { + if (channelIDs.isEmpty()) { + return; + } + + MumbleProto::UserState mpus; + mpus.set_session(Global::get().uiSession); + + for (unsigned int currentChannel : channelIDs) { + // The naming of the function is a bit unfortunate but what this does is to add + // the channel ID to the message field listening_channel_add + mpus.add_listening_channel_add(currentChannel); + } + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpus); +} + +void ServerHandler::stopListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens) { + stopListeningToChannels({ channel }, temporaryAccessTokens); +} + +void ServerHandler::stopListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens) { + if (channelIDs.isEmpty()) { + return; + } + + MumbleProto::UserState mpus; + mpus.set_session(Global::get().uiSession); + + for (unsigned int currentChannel : channelIDs) { + // The naming of the function is a bit unfortunate but what this does is to add + // the channel ID to the message field listening_channel_remove + mpus.add_listening_channel_remove(currentChannel); + } + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpus); +} + +void ServerHandler::createChannel(unsigned int parent_id, const QString &name, const QString &description, + unsigned int position, bool temporary, unsigned int maxUsers, + const QStringList &temporaryAccessTokens) { + MumbleProto::ChannelState mpcs; + mpcs.set_parent(parent_id); + mpcs.set_name(u8(name)); + mpcs.set_description(u8(description)); + mpcs.set_position(static_cast< int >(position)); + mpcs.set_temporary(temporary); + mpcs.set_max_users(maxUsers); + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + +void ServerHandler::requestBanList(const QStringList &temporaryAccessTokens) { + MumbleProto::BanList mpbl; + mpbl.set_query(true); + + for (const QString &token : temporaryAccessTokens) { + mpbl.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpbl); +} + +void ServerHandler::requestUserList(const QStringList &temporaryAccessTokens) { + MumbleProto::UserList mpul; + + for (const QString &token : temporaryAccessTokens) { + mpul.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpul); +} + +void ServerHandler::requestACL(unsigned int channel, const QStringList &temporaryAccessTokens) { + MumbleProto::ACL mpacl; + mpacl.set_channel_id(channel); + mpacl.set_query(true); + + for (const QString &token : temporaryAccessTokens) { + mpacl.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpacl); +} + +void ServerHandler::registerUser(unsigned int uiSession, const QStringList &temporaryAccessTokens) { + MumbleProto::UserState mpus; + mpus.set_session(uiSession); + mpus.set_user_id(0); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpus); +} + +void ServerHandler::kickBanUser(unsigned int uiSession, const QString &reason, bool ban, + const QStringList &temporaryAccessTokens) { + MumbleProto::UserRemove mpur; + mpur.set_session(uiSession); + mpur.set_reason(u8(reason)); + mpur.set_ban(ban); + + for (const QString &token : temporaryAccessTokens) { + mpur.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpur); +} + +void ServerHandler::sendUserTextMessage(unsigned int uiSession, const QString &message, + const QStringList &temporaryAccessTokens) { + MumbleProto::TextMessage mptm; + mptm.add_session(uiSession); + mptm.set_message(u8(message)); + + for (const QString &token : temporaryAccessTokens) { + mptm.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mptm); +} + +void ServerHandler::sendChannelTextMessage(unsigned int channel, const QString &message, bool tree, + const QStringList &temporaryAccessTokens) { + MumbleProto::TextMessage mptm; + if (tree) { + mptm.add_tree_id(channel); + } else { + mptm.add_channel_id(channel); + + if (message == QString::fromUtf8(Global::get().ccHappyEaster + 10)) + Global::get().bHappyEaster = true; + } + mptm.set_message(u8(message)); + + for (const QString &token : temporaryAccessTokens) { + mptm.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mptm); +} + +void ServerHandler::setUserComment(unsigned int uiSession, const QString &comment, + const QStringList &temporaryAccessTokens) { + MumbleProto::UserState mpus; + mpus.set_session(uiSession); + mpus.set_comment(u8(comment)); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpus); +} + +void ServerHandler::setUserTexture(unsigned int uiSession, const QByteArray &qba, + const QStringList &temporaryAccessTokens) { + QByteArray texture; + + if ((m_version >= Version::fromComponents(1, 2, 2)) || qba.isEmpty()) { + texture = qba; + } else { + QByteArray raw = qba; + + QBuffer qb(&raw); + qb.open(QIODevice::ReadOnly); + + QImageReader qir; + qir.setDecideFormatFromContent(false); + + QByteArray fmt; + if (!RichTextImage::isValidImage(qba, fmt)) { + return; + } + + qir.setFormat(fmt); + qir.setDevice(&qb); + + QSize sz = qir.size(); + const int TEX_MAX_WIDTH = 600; + const int TEX_MAX_HEIGHT = 60; + const int TEX_RGBA_SIZE = TEX_MAX_WIDTH * TEX_MAX_HEIGHT * 4; + sz.scale(TEX_MAX_WIDTH, TEX_MAX_HEIGHT, Qt::KeepAspectRatio); + qir.setScaledSize(sz); + + QImage tex = qir.read(); + if (tex.isNull()) { + return; + } + + raw = QByteArray(TEX_RGBA_SIZE, 0); + QImage img(reinterpret_cast< unsigned char * >(raw.data()), TEX_MAX_WIDTH, TEX_MAX_HEIGHT, + QImage::Format_ARGB32); + + QPainter imgp(&img); + imgp.setRenderHint(QPainter::Antialiasing); + imgp.setRenderHint(QPainter::TextAntialiasing); + imgp.setCompositionMode(QPainter::CompositionMode_SourceOver); + imgp.drawImage(0, 0, tex); + + texture = qCompress(QByteArray(reinterpret_cast< const char * >(img.bits()), TEX_RGBA_SIZE)); + } + + MumbleProto::UserState mpus; + mpus.set_session(uiSession); + mpus.set_texture(blob(texture)); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpus); + + if (!texture.isEmpty()) { + database->setBlob(sha1(texture), texture); + } +} + +void ServerHandler::setTokens(const QStringList &tokens) { + MumbleProto::Authenticate msg; + foreach (const QString &qs, tokens) + msg.add_tokens(u8(qs)); + sendMessage(msg); +} + +void ServerHandler::removeChannel(unsigned int channel, const QStringList &temporaryAccessTokens) { + MumbleProto::ChannelRemove mpcr; + mpcr.set_channel_id(channel); + + for (const QString &token : temporaryAccessTokens) { + mpcr.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcr); +} + +void ServerHandler::addChannelLink(unsigned int channel, unsigned int link, const QStringList &temporaryAccessTokens) { + MumbleProto::ChannelState mpcs; + mpcs.set_channel_id(channel); + mpcs.add_links_add(link); + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + +void ServerHandler::addChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens) { + if (channelCount < 2) { + return; + } + MumbleProto::ChannelState mpcs; + mpcs.set_channel_id(channelSet[0]); + for (unsigned int i = 1; i < channelCount; i++) { + mpcs.add_links_add(channelSet[i]); + } + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + +void ServerHandler::removeChannelLink(unsigned int channel, unsigned int link, + const QStringList &temporaryAccessTokens) { + MumbleProto::ChannelState mpcs; + mpcs.set_channel_id(channel); + mpcs.add_links_remove(link); + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + +void ServerHandler::removeChannelLinks(unsigned int channel, const unsigned int channelCount, + const unsigned int *channelSet, const QStringList &temporaryAccessTokens) { + if (channelCount < 1) { + return; + } + MumbleProto::ChannelState mpcs; + mpcs.set_channel_id(channel); + for (unsigned int i = 0; i < channelCount; i++) { + mpcs.add_links_remove(channelSet[i]); + } + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + +void ServerHandler::removeChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens) { + if (channelCount < 2) { + return; + } + MumbleProto::ChannelState mpcs; + unsigned int link = channelSet[0]; + for (unsigned int i = 1; i < channelCount; i++) { + mpcs.set_channel_id(link); + mpcs.add_links_remove(channelSet[i]); + } + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + +void ServerHandler::requestChannelPermissions(unsigned int channel) { + MumbleProto::PermissionQuery mppq; + mppq.set_channel_id(channel); + sendMessage(mppq); +} + +void ServerHandler::setSelfMuteDeafState(bool mute, bool deaf) { + MumbleProto::UserState mpus; + mpus.set_self_mute(mute); + mpus.set_self_deaf(deaf); + sendMessage(mpus); +} + +void ServerHandler::announceRecordingState(bool recording) { + MumbleProto::UserState mpus; + mpus.set_recording(recording); + sendMessage(mpus); +} + +QUrl ServerHandler::getServerURL(bool withPassword) const { + QUrl url; + + url.setScheme(QLatin1String("mumble")); + url.setHost(qsHostName); + if (usPort != DEFAULT_MUMBLE_PORT) { + url.setPort(usPort); + } + + url.setUserName(qsUserName); + + if (withPassword && !qsPassword.isEmpty()) { + url.setPassword(qsPassword); + } + + return url; +} diff --git a/src/mumble/back.h b/src/mumble/back.h new file mode 100644 index 00000000000..43da62230c1 --- /dev/null +++ b/src/mumble/back.h @@ -0,0 +1,223 @@ +// Copyright The Mumble Developers. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file at the root of the +// Mumble source tree or at . + +#ifndef MUMBLE_MUMBLE_SERVERHANDLER_H_ +#define MUMBLE_MUMBLE_SERVERHANDLER_H_ + +#include + +#ifdef Q_OS_WIN +# include "win.h" +#endif + +#ifndef Q_MOC_RUN +# include +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SERVERSEND_EVENT 3501 + +#include "Mumble.pb.h" +#include "MumbleProtocol.h" +#include "ServerAddress.h" +#include "Timer.h" + +class Connection; +class Database; +class PacketDataStream; +class QUdpSocket; +class QSslSocket; +class VoiceRecorder; + +class ServerHandlerMessageEvent : public QEvent { +public: + Mumble::Protocol::TCPMessageType type; + QByteArray qbaMsg; + bool bFlush; + ServerHandlerMessageEvent(const QByteArray &msg, Mumble::Protocol::TCPMessageType type, bool flush = false); +}; + +typedef boost::shared_ptr< Connection > ConnectionPtr; + +class ServerHandler : public QThread { +private: + Q_OBJECT + Q_DISABLE_COPY(ServerHandler) + + Database *database; + + static QMutex nextConnectionIDMutex; + static int nextConnectionID; + +protected: + QString qsHostName; + QString qsUserName; + QString qsPassword; + unsigned short usPort; + unsigned short usResolvedPort; + bool bUdp; + bool bStrong; + int connectionID; + Mumble::Protocol::UDPPingEncoder< Mumble::Protocol::Role::Client > m_udpPingEncoder; + Mumble::Protocol::UDPDecoder< Mumble::Protocol::Role::Client > m_udpDecoder; + Mumble::Protocol::UDPDecoder< Mumble::Protocol::Role::Client > m_tcpTunnelDecoder; + + /// Flag indicating whether the server we are currently connected to has + /// finished synchronizing already. + bool serverSynchronized = false; + +#ifdef Q_OS_WIN + HANDLE hQoS; + DWORD dwFlowUDP; +#endif + + QHostAddress qhaRemote; + QHostAddress qhaLocal; + QUdpSocket *qusUdp; + QMutex qmUdp; + + void handleVoicePacket(const Mumble::Protocol::AudioData &audioData); + +public: + Timer tTimestamp; + int iInFlightTCPPings; + QTimer *tConnectionTimeoutTimer; + QList< QSslError > qlErrors; + QList< QSslCertificate > qscCert; + QSslCipher qscCipher; + ConnectionPtr cConnection; + QByteArray qbaDigest; + boost::shared_ptr< VoiceRecorder > recorder; + QSslSocket *qtsSock; + QList< ServerAddress > qlAddresses; + QHash< ServerAddress, QString > qhHostnames; + ServerAddress saTargetServer; + + Version::full_t m_version; + QString qsRelease; + QString qsOS; + QString qsOSVersion; + + /** + * A flag indicating whether this connection makes use of PFS or not. Note that this flag only has meaning, if the + * used version of Qt is >= 5.7. + */ + bool connectionUsesPerfectForwardSecrecy = false; + + boost::accumulators::accumulator_set< + double, boost::accumulators::stats< boost::accumulators::tag::mean, boost::accumulators::tag::variance, + boost::accumulators::tag::count > > + accTCP, accUDP, accClean; + + ServerHandler(); + ~ServerHandler(); + void setConnectionInfo(const QString &host, unsigned short port, const QString &username, const QString &pw); + void getConnectionInfo(QString &host, unsigned short &port, QString &username, QString &pw) const; + bool isStrong() const; + void customEvent(QEvent *evt) Q_DECL_OVERRIDE; + int getConnectionID() const; + + void setProtocolVersion(Version::full_t version); + + void sendProtoMessage(const ::google::protobuf::Message &msg, Mumble::Protocol::TCPMessageType type); + void sendMessage(const unsigned char *data, int len, bool force = false); + + /// @returns Whether this handler is currently connected to a server. + bool isConnected() const; + + /// @returns Whether the server this handler is currently connected to, has finished + /// synchronizing yet. + bool hasSynchronized() const; + + /// @param synchronized Whether the server has finished synchronization + void setServerSynchronized(bool synchronized); + +#define PROCESS_MUMBLE_TCP_MESSAGE(name, value) \ + void sendMessage(const MumbleProto::name &msg) { sendProtoMessage(msg, Mumble::Protocol::TCPMessageType::name); } + MUMBLE_ALL_TCP_MESSAGES +#undef PROCESS_MUMBLE_TCP_MESSAGE + + void requestUserStats(unsigned int uiSession, bool statsOnly, const QStringList &temporaryAccessTokens = {}); + void joinChannel(unsigned int uiSession, unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void startListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void startListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens = {}); + void stopListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void stopListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens = {}); + void createChannel(unsigned int parent_id, const QString &name, const QString &description, unsigned int position, + bool temporary, unsigned int maxUsers, const QStringList &temporaryAccessTokens = {}); + void requestBanList(const QStringList &temporaryAccessTokens = {}); + void requestUserList(const QStringList &temporaryAccessTokens = {}); + void requestACL(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void registerUser(unsigned int uiSession, const QStringList &temporaryAccessTokens = {}); + void kickBanUser(unsigned int uiSession, const QString &reason, bool ban, + const QStringList &temporaryAccessTokens = {}); + void sendUserTextMessage(unsigned int uiSession, const QString &message_, + const QStringList &temporaryAccessTokens = {}); + void sendChannelTextMessage(unsigned int channel, const QString &message_, bool tree, + const QStringList &temporaryAccessTokens = {}); + void setUserComment(unsigned int uiSession, const QString &comment, const QStringList &temporaryAccessTokens = {}); + void setUserTexture(unsigned int uiSession, const QByteArray &qba, const QStringList &temporaryAccessTokens = {}); + void setTokens(const QStringList &tokens); + void removeChannel(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void addChannelLink(unsigned int channel, unsigned int link, const QStringList &temporaryAccessTokens = {}); + void addChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens = {}); + void removeChannelLink(unsigned int channel, unsigned int link, const QStringList &temporaryAccessTokens = {}); + void removeChannelLinks(const unsigned int channel, const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens = {}); + void removeChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens = {}); + void requestChannelPermissions(unsigned int channel); + void setSelfMuteDeafState(bool mute, bool deaf); + void announceRecordingState(bool recording); + + /// Return connection information as a URL + QUrl getServerURL(bool withPassword = false) const; + + void disconnect(); + void run() Q_DECL_OVERRIDE; +signals: + void error(QAbstractSocket::SocketError, QString reason); + // This signal is basically the same as disconnected but it will be emitted + // *right before* disconnected is emitted. Thus this can be used by slots + // that need to block the disconnected signal from being emitted (using a + // direct connection) before they're done. + void aboutToDisconnect(QAbstractSocket::SocketError, QString reason); + void disconnected(QAbstractSocket::SocketError, QString reason); + void connected(); + void pingRequested(); +protected slots: + void message(Mumble::Protocol::TCPMessageType type, const QByteArray &); + void serverConnectionConnected(); + void serverConnectionTimeoutOnConnect(); + void serverConnectionStateChanged(QAbstractSocket::SocketState); + void serverConnectionClosed(QAbstractSocket::SocketError, const QString &); + void setSslErrors(const QList< QSslError > &); + void udpReady(); + void hostnameResolved(); +private slots: + void sendPingInternal(); +public slots: + void sendPing(); +}; + +typedef boost::shared_ptr< ServerHandler > ServerHandlerPtr; + +#endif diff --git a/src/murmur/Messages.cpp b/src/murmur/Messages.cpp index afb96c4b70c..b83dd148839 100644 --- a/src/murmur/Messages.cpp +++ b/src/murmur/Messages.cpp @@ -101,6 +101,10 @@ class TemporaryAccessTokenHelper { public: TemporaryAccessTokenHelper(ServerUser *affectedUser, const QStringList &tokens, Server *server) : affectedUser(affectedUser), qslTemporaryTokens(tokens), server(server) { + if (tokens.empty()) { + return; + } + // Add the temporary tokens QMutableStringListIterator it(this->qslTemporaryTokens); @@ -655,6 +659,13 @@ void Server::msgBanList(ServerUser *uSource, MumbleProto::BanList &msg) { MSG_SETUP(ServerUser::Authenticated); + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + QSet< Ban > previousBans, newBans; if (!hasPermission(uSource, qhChannels.value(0), ChanACL::Ban)) { PERM_DENIED(uSource, qhChannels.value(0), ChanACL::Ban); @@ -1193,6 +1204,13 @@ void Server::msgUserRemove(ServerUser *uSource, MumbleProto::UserRemove &msg) { MSG_SETUP(ServerUser::Authenticated); VICTIM_SETUP; + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + msg.set_actor(uSource->uiSession); bool ban = msg.has_ban() && msg.ban(); @@ -1243,6 +1261,13 @@ void Server::msgChannelState(ServerUser *uSource, MumbleProto::ChannelState &msg RATELIMIT(uSource); } + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + // Check if the parent exists if (msg.has_parent()) { p = qhChannels.value(msg.parent()); @@ -1517,6 +1542,13 @@ void Server::msgChannelRemove(ServerUser *uSource, MumbleProto::ChannelRemove &m MSG_SETUP(ServerUser::Authenticated); + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + Channel *c = qhChannels.value(msg.channel_id()); if (!c) return; @@ -1535,6 +1567,14 @@ void Server::msgTextMessage(ServerUser *uSource, MumbleProto::TextMessage &msg) ZoneScoped; MSG_SETUP(ServerUser::Authenticated); + + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + QMutexLocker qml(&qmCache); // For signal userTextMessage (RPC consumers) @@ -1751,6 +1791,13 @@ void Server::msgACL(ServerUser *uSource, MumbleProto::ACL &msg) { if (!c) return; + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + // For changing channel properties (the 'Write') ACL we allow two things: // 1) As per regular ACL propagating mechanism, we check if the user has been // granted Write in the channel they try to edit @@ -2102,6 +2149,13 @@ void Server::msgUserList(ServerUser *uSource, MumbleProto::UserList &msg) { MSG_SETUP(ServerUser::Authenticated); + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + // The register permission is required on the root channel to be allowed to // view the registered users. if (!hasPermission(uSource, qhChannels.value(0), ChanACL::Register)) { @@ -2248,6 +2302,13 @@ void Server::msgUserStats(ServerUser *uSource, MumbleProto::UserStats &msg) { bool extend = (uSource == pDstServerUser) || hasPermission(uSource, qhChannels.value(0), ChanACL::Register); + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + if (!extend && !hasPermission(uSource, pDstServerUser->cChannel, ChanACL::Enter)) { PERM_DENIED(uSource, pDstServerUser->cChannel, ChanACL::Enter); return;