/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DiscoveryService.hxx" #include "Listener.hxx" #include #include "BluetoothServer.hxx" #include "Communicator.hxx" #include "BufferedStreamSocket.hxx" using namespace sd; using namespace ::com::sun::star; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::beans; using namespace ::com::sun::star::container; using namespace ::com::sun::star::lang; using namespace ::osl; using namespace ::comphelper; namespace sd { /** * Used to keep track of clients that have attempted to connect, but haven't * yet been approved. */ struct ClientInfoInternal: ClientInfo { BufferedStreamSocket *mpStreamSocket; OUString mPin; ClientInfoInternal( const OUString& rName, BufferedStreamSocket *pSocket, const OUString& rPin ): ClientInfo( rName, false ), mpStreamSocket( pSocket ), mPin( rPin ) {} }; } RemoteServer::RemoteServer() : Thread( "RemoteServerThread" ) { SAL_INFO( "sdremote", "Instantiated RemoteServer" ); } RemoteServer::~RemoteServer() { } void RemoteServer::execute() { SAL_INFO( "sdremote", "RemoteServer::execute called" ); osl::SocketAddr aAddr( "0.0.0.0", PORT ); if ( !mSocket.bind( aAddr ) ) { SAL_WARN( "sdremote", "bind failed" << mSocket.getErrorAsString() ); spServer = nullptr; return; } if ( !mSocket.listen(3) ) { SAL_WARN( "sdremote", "listen failed" << mSocket.getErrorAsString() ); spServer = nullptr; return; } while ( true ) { StreamSocket aSocket; SAL_INFO( "sdremote", "waiting on accept" ); if ( mSocket.acceptConnection( aSocket ) == osl_Socket_Error ) { SAL_WARN( "sdremote", "accept failed" << mSocket.getErrorAsString() ); spServer = nullptr; return; // Closed, or other issue. } BufferedStreamSocket *pSocket = new BufferedStreamSocket( aSocket); handleAcceptedConnection( pSocket ); } SAL_INFO( "sdremote", "shutting down RemoteServer" ); spServer = nullptr; // Object is destroyed when Thread::execute() ends. } void RemoteServer::handleAcceptedConnection( BufferedStreamSocket *pSocket ) { OString aLine; if ( ! ( pSocket->readLine( aLine) && aLine == "LO_SERVER_CLIENT_PAIR" && pSocket->readLine( aLine ) ) ) { SAL_INFO( "sdremote", "client failed to send LO_SERVER_CLIENT_PAIR, ignoring" ); delete pSocket; return; } OString aName( aLine ); if ( ! pSocket->readLine( aLine ) ) { delete pSocket; return; } OString aPin( aLine ); SocketAddr aClientAddr; pSocket->getPeerAddr( aClientAddr ); do { // Read off any additional non-empty lines // We know that we at least have the empty termination line to read. if ( ! pSocket->readLine( aLine ) ) { delete pSocket; return; } } while ( aLine.getLength() > 0 ); MutexGuard aGuard( sDataMutex ); std::shared_ptr< ClientInfoInternal > pClient = std::make_shared( OStringToOUString( aName, RTL_TEXTENCODING_UTF8 ), pSocket, OStringToOUString( aPin, RTL_TEXTENCODING_UTF8 ) ); mAvailableClients.push_back( pClient ); // Check if we already have this server. Reference< XNameAccess > const xConfig = officecfg::Office::Impress::Misc::AuthorisedRemotes::get(); const Sequence< OUString > aNames = xConfig->getElementNames(); for ( const auto& rName : aNames ) { if ( rName == pClient->mName ) { Reference xSetItem( xConfig->getByName(rName), UNO_QUERY ); Any axPin(xSetItem->getByName("PIN")); OUString sPin; axPin >>= sPin; if ( sPin == pClient->mPin ) { SAL_INFO( "sdremote", "client found on validated list -- connecting" ); connectClient( pClient, sPin ); return; } } } // Pin not found so inform the client. SAL_INFO( "sdremote", "client not found on validated list" ); pSocket->write( "LO_SERVER_VALIDATING_PIN\n\n", strlen( "LO_SERVER_VALIDATING_PIN\n\n" ) ); } RemoteServer *sd::RemoteServer::spServer = nullptr; ::osl::Mutex sd::RemoteServer::sDataMutex; ::std::vector sd::RemoteServer::sCommunicators; void RemoteServer::setup() { if (spServer) return; spServer = new RemoteServer(); spServer->launch(); #ifdef ENABLE_SDREMOTE_BLUETOOTH sd::BluetoothServer::setup( &sCommunicators ); #endif } void RemoteServer::presentationStarted( const css::uno::Reference< css::presentation::XSlideShowController > &rController ) { if ( !spServer ) return; MutexGuard aGuard( sDataMutex ); for ( const auto& rpCommunicator : sCommunicators ) { rpCommunicator->presentationStarted( rController ); } } void RemoteServer::presentationStopped() { if ( !spServer ) return; MutexGuard aGuard( sDataMutex ); for ( const auto& rpCommunicator : sCommunicators ) { rpCommunicator->disposeListener(); } } void RemoteServer::removeCommunicator( Communicator const * mCommunicator ) { if ( !spServer ) return; MutexGuard aGuard( sDataMutex ); auto aIt = std::find(sCommunicators.begin(), sCommunicators.end(), mCommunicator); if (aIt != sCommunicators.end()) sCommunicators.erase( aIt ); } std::vector< std::shared_ptr< ClientInfo > > RemoteServer::getClients() { SAL_INFO( "sdremote", "RemoteServer::getClients() called" ); std::vector< std::shared_ptr< ClientInfo > > aClients; if ( spServer ) { MutexGuard aGuard( sDataMutex ); aClients.assign( spServer->mAvailableClients.begin(), spServer->mAvailableClients.end() ); } else { SAL_INFO( "sdremote", "No remote server instance => no remote clients" ); } // We also need to provide authorised clients (no matter whether or not // they are actually available), so that they can be de-authorised if // necessary. We specifically want these to be at the end of the list // since the user is more likely to be trying to connect a new remote // than removing an existing remote. // We can also be sure that pre-authorised clients will not be on the // available clients list, as they get automatically connected if seen. // TODO: we should probably add some sort of extra labelling to mark // authorised AND connected client. Reference< XNameAccess > const xConfig = officecfg::Office::Impress::Misc::AuthorisedRemotes::get(); const Sequence< OUString > aNames = xConfig->getElementNames(); std::transform(aNames.begin(), aNames.end(), std::back_inserter(aClients), [](const OUString& rName) -> std::shared_ptr { return std::make_shared(rName, true); }); return aClients; } bool RemoteServer::connectClient( const std::shared_ptr< ClientInfo >& pClient, std::u16string_view aPin ) { SAL_INFO( "sdremote", "RemoteServer::connectClient called" ); if ( !spServer ) return false; ClientInfoInternal* apClient = dynamic_cast< ClientInfoInternal* >( pClient.get() ); if ( !apClient ) // could happen if we try to "connect" an already authorised client { return false; } if ( apClient->mPin == aPin ) { // Save in settings first std::shared_ptr< ConfigurationChanges > aChanges = ConfigurationChanges::create(); Reference< XNameContainer > const xConfig = officecfg::Office::Impress::Misc::AuthorisedRemotes::get( aChanges ); Reference xChildFactory ( xConfig, UNO_QUERY); Reference xChild( xChildFactory->createInstance(), UNO_QUERY); Any aValue; if (xChild.is()) { // Check whether the client is already saved Sequence< OUString > aNames = xConfig->getElementNames(); if (comphelper::findValue(aNames, apClient->mName) != -1) xConfig->replaceByName( apClient->mName, Any( xChild ) ); else xConfig->insertByName( apClient->mName, Any( xChild ) ); aValue <<= apClient->mPin; xChild->replaceByName("PIN", aValue); aChanges->commit(); } Communicator* pCommunicator = new Communicator( std::unique_ptr(apClient->mpStreamSocket) ); MutexGuard aGuard( sDataMutex ); sCommunicators.push_back( pCommunicator ); auto aIt = std::find(spServer->mAvailableClients.begin(), spServer->mAvailableClients.end(), pClient); if (aIt != spServer->mAvailableClients.end()) spServer->mAvailableClients.erase( aIt ); pCommunicator->launch(); return true; } else { return false; } } void RemoteServer::deauthoriseClient( const std::shared_ptr< ClientInfo >& pClient ) { // TODO: we probably want to forcefully disconnect at this point too? // But possibly via a separate function to allow just disconnecting from // the UI. SAL_INFO( "sdremote", "RemoteServer::deauthoriseClient called" ); if ( !pClient->mbIsAlreadyAuthorised ) // We can't remove unauthorised clients from the authorised list... { return; } std::shared_ptr< ConfigurationChanges > aChanges = ConfigurationChanges::create(); Reference< XNameContainer > const xConfig = officecfg::Office::Impress::Misc::AuthorisedRemotes::get( aChanges ); xConfig->removeByName( pClient->mName ); aChanges->commit(); } void SdDLL::RegisterRemotes() { SAL_INFO( "sdremote", "SdDLL::RegisterRemotes called" ); // The remote server is likely of no use in headless mode. And as only // one instance of the server can actually own the appropriate ports its // probably best to not even try to do so from our headless instance // (i.e. as to avoid blocking expected usage). // It could perhaps be argued that we would still need the remote // server for tiled rendering of presentations, but even then this // implementation would not be of much use, i.e. would be controlling // the purely imaginary headless presentation -- instead we'd need // to have some sort of mechanism of plugging in our tiled rendering // client to be controlled by the remote server, or provide an // alternative implementation. if ( Application::IsHeadlessModeEnabled() ) return; if ( !officecfg::Office::Impress::Misc::Start::EnableSdremote::get() ) return; sd::RemoteServer::setup(); sd::DiscoveryService::setup(); } void RemoteServer::ensureDiscoverable() { // FIXME: we could also enable listening on our WiFi // socket here to significantly reduce the attack surface. #ifdef ENABLE_SDREMOTE_BLUETOOTH BluetoothServer::ensureDiscoverable(); #endif } void RemoteServer::restoreDiscoverable() { #ifdef ENABLE_SDREMOTE_BLUETOOTH BluetoothServer::restoreDiscoverable(); #endif } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */