common/Common.hpp | 8 common/Seccomp.cpp | 1 common/SigUtil.cpp | 1 kit/ChildSession.hpp | 6 kit/Kit.cpp | 287 ++++++++++++++++++---------------- loleaflet/src/layer/tile/TileLayer.js | 88 +++++----- loolwsd.xml.in | 4 net/Socket.cpp | 102 ++++++++++++ net/Socket.hpp | 88 +++++----- net/Ssl.cpp | 4 net/SslSocket.hpp | 16 + net/WebSocketHandler.hpp | 177 +++++++++++++++++--- test/Makefile.am | 8 test/WhiteBoxTests.cpp | 2 test/countloolkits.hpp | 4 tools/WebSocketDump.cpp | 82 ++++++++- wsd/Admin.cpp | 71 ++++++++ wsd/Admin.hpp | 5 wsd/DocumentBroker.cpp | 5 wsd/DocumentBroker.hpp | 66 +++---- wsd/LOOLWSD.cpp | 15 - 21 files changed, 723 insertions(+), 317 deletions(-)
New commits: commit 432a1c7d78d027f9986b91dc9e48edd33902a046 Author: Jan Holesovsky <ke...@collabora.com> Date: Fri May 18 13:00:16 2018 +0200 Split close(bool) into close() and terminate(). The bool flag was causing 2 complete separate code paths anyway. Also remove stop(), calling stop() followed by close() made no difference. Change-Id: Ica4c887b0324390d4e006a26eb4119bd5ab08723 diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index e972459af..1006dc2dc 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -1588,10 +1588,7 @@ void DocumentBroker::terminateChild(const std::string& closeReason) { LOG_INF("Terminating child [" << getPid() << "] of doc [" << _docKey << "]."); - // First flag to stop as it might be waiting on our lock - // to process some incoming message. - _childProcess->stop(); - _childProcess->close(false); + _childProcess->close(); } stop(closeReason); diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp index b88e37af6..fc3cb095d 100644 --- a/wsd/DocumentBroker.hpp +++ b/wsd/DocumentBroker.hpp @@ -78,63 +78,57 @@ public: ~ChildProcess() { - if (_pid > 0) - { - LOG_DBG("~ChildProcess dtor [" << _pid << "]."); - close(true); + if (_pid <= 0) + return; + + LOG_DBG("~ChildProcess dtor [" << _pid << "]."); + terminate(); + + // No need for the socket anymore. + _ws.reset(); + _socket.reset(); - // No need for the socket anymore. - _ws.reset(); - _socket.reset(); - } } void setDocumentBroker(const std::shared_ptr<DocumentBroker>& docBroker); std::shared_ptr<DocumentBroker> getDocumentBroker() const { return _docBroker.lock(); } - void stop() + /// Let the child close a nice way. + void close() { - // Request the child to exit. + if (_pid < 0) + return; + try { + LOG_DBG("Closing ChildProcess [" << _pid << "]."); + + // Request the child to exit if (isAlive()) { LOG_DBG("Stopping ChildProcess [" << _pid << "]"); sendTextFrame("exit"); } + + // Shutdown the socket. + if (_ws) + _ws->shutdown(); } - catch (const std::exception&) + catch (const std::exception& ex) { - // Already logged in sendTextFrame. + LOG_ERR("Error while closing child process: " << ex.what()); } + + _pid = -1; } - void close(const bool rude) + /// Kill or abandon the child. + void terminate() { if (_pid < 0) return; - try - { - LOG_DBG("Closing ChildProcess [" << _pid << "]."); - - if (!rude) - { - // First mark to stop the thread so it knows it's intentional. - stop(); - - // Shutdown the socket. - if (_ws) - _ws->shutdown(); - } - } - catch (const std::exception& ex) - { - LOG_ERR("Error while closing child process: " << ex.what()); - } - - // Kill or abandon the child. - if (rude && _pid != -1 && kill(_pid, 0) == 0) + if (::kill(_pid, 0) == 0) { LOG_INF("Killing child [" << _pid << "]."); if (!SigUtil::killChild(_pid)) @@ -179,7 +173,7 @@ public: { try { - return _pid > 1 && _ws && kill(_pid, 0) == 0; + return _pid > 1 && _ws && ::kill(_pid, 0) == 0; } catch (const std::exception&) { diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index 9e48426c8..d7cb6a473 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -2885,7 +2885,7 @@ int LOOLWSD::innerMain() LOG_INF("Requesting child processes to terminate."); for (auto& child : NewChildren) { - child->close(true); + child->terminate(); } #ifndef KIT_IN_PROCESS commit 088acf5e98c32228960b81b43f447de6c384cd17 Author: Jan Holesovsky <ke...@collabora.com> Date: Thu May 17 20:47:37 2018 +0200 Before we kill the child, check it exists, ie. kill(pid, 0) == 0. Also warn when anything was left out. Without this, we leave abandoned children around. Change-Id: I293a530ffceeb7f6bdc0cc775335c782945de6e7 diff --git a/common/SigUtil.cpp b/common/SigUtil.cpp index 856e89fd5..ae6d6b18e 100644 --- a/common/SigUtil.cpp +++ b/common/SigUtil.cpp @@ -284,6 +284,7 @@ namespace SigUtil sigaction(SIGUSR1, &action, nullptr); } + /// Kill the given pid with SIGTERM. Returns true when the pid does not exist any more. bool killChild(const int pid) { LOG_DBG("Killing PID: " << pid); diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp index 443d8ab52..b88e37af6 100644 --- a/wsd/DocumentBroker.hpp +++ b/wsd/DocumentBroker.hpp @@ -134,10 +134,10 @@ public: } // Kill or abandon the child. - if (_pid != -1 && rude && kill(_pid, 0) != 0 && errno != ESRCH) + if (rude && _pid != -1 && kill(_pid, 0) == 0) { LOG_INF("Killing child [" << _pid << "]."); - if (SigUtil::killChild(_pid)) + if (!SigUtil::killChild(_pid)) { LOG_ERR("Cannot terminate lokit [" << _pid << "]. Abandoning."); } commit 9c790d1796c5564875d38d6109ac883a30d3bf2a Author: Jan Holesovsky <ke...@collabora.com> Date: Wed Apr 25 17:38:55 2018 +0200 Paste: Prefer text/rtf mimetype when present. Change-Id: Id4bad2d6b09b3b14e64059a942a50ce61f8f4ea4 diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index 9080acb77..1deb1425a 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -2078,48 +2078,51 @@ L.TileLayer = L.GridLayer.extend({ if (preferInternal === true) { var pasteString = dataTransfer.getData('text/plain'); if (!pasteString) { - pasteString = window.clipboardData.getData('Text'); + pasteString = dataTransfer.getData('Text'); // IE 11 } - if (pasteString === this._selectionTextHash) { + if (pasteString && pasteString === this._selectionTextHash) { this._map._socket.sendMessage('uno .uno:Paste'); - return + return; } } - // handle content var types = dataTransfer.types; - var hasHTML = false; - for (var t = 0; !hasHTML && t < types.length; t++) { - if (types[t] === 'text/html') { - hasHTML = true; - } - } - var handled = false; - for (t = 0; !handled && t < types.length; t++) { - var type = types[t]; - if (type === 'text/html') { - this._map._socket.sendMessage('paste mimetype=text/html\n' + dataTransfer.getData(type)); - handled = true; - } - else if ((type === 'text/plain' || type ==='Text') && !hasHTML) { - this._map._socket.sendMessage('paste mimetype=text/plain;charset=utf-8\n' + dataTransfer.getData(type)); - handled = true; - } - else if (type === 'Files') { + // first try to transfer images + // TODO if we have both Files and a normal mimetype, should we handle + // both, or prefer one or the other? + for (var t = 0; t < types.length; ++t) { + if (types[t] === 'Files') { var files = dataTransfer.files; - for (var i = 0; i < files.length; ++i) { - var file = files[i]; + for (var f = 0; f < files.length; ++f) { + var file = files[f]; if (file.type.match(/image.*/)) { var reader = new FileReader(); reader.onload = this._onFileLoadFunc(file); reader.readAsArrayBuffer(file); - handled = true; } } } } + + // now try various mime types + var mimeTypes = [ + ['text/rtf', 'text/rtf'], + ['text/html', 'text/html'], + ['text/plain', 'text/plain;charset=utf-8'], + ['Text', 'text/plain;charset=utf-8'] + ]; + + for (var i = 0; i < mimeTypes.length; ++i) { + for (t = 0; t < types.length; ++t) { + if (mimeTypes[i][0] === types[t]) { + var blob = new Blob(['paste mimetype=' + mimeTypes[i][1] + '\n', dataTransfer.getData(types[t])]); + this._map._socket.sendMessage(blob); + return; + } + } + } }, _onFileLoadFunc: function(file) { commit 4e4d9719d4bb3144d5385be7be104eefa3390f11 Author: Jan Holesovsky <ke...@collabora.com> Date: Wed Apr 25 16:06:08 2018 +0200 Paste: Share the code with Drop, to allow rich content pasting. Change-Id: I4d80421786369388b8a1a094fe7633d525fa3f08 diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index 06a04f5d8..9080acb77 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -26,16 +26,6 @@ function hex2string(inData) } L.Compatibility = { - clipboardGet: function (event) { - var text = null; - if (event.clipboardData) { // Standard - text = event.clipboardData.getData('text/plain'); - } - else if (window.clipboardData) { // IE 11 - text = window.clipboardData.getData('Text'); - } - return text; - }, clipboardSet: function (event, text) { if (event.clipboardData) { // Standard event.clipboardData.setData('text/plain', text); @@ -2052,16 +2042,12 @@ L.TileLayer = L.GridLayer.extend({ _onPaste: function (e) { e = e.originalEvent; e.preventDefault(); - var pasteString = L.Compatibility.clipboardGet(e); - if (pasteString === 'false' || !pasteString || pasteString === this._selectionTextHash) { - // If there is nothing to paste in clipboard, no harm in - // issuing a .uno:Paste in case there is something internally copied in the document - // or if the content of the clipboard did not change, we surely must do a rich paste - // instead of a normal paste - this._map._socket.sendMessage('uno .uno:Paste'); + + if (e.clipboardData) { // Standard + this._dataTransferToDocument(e.clipboardData, /* preferInternal = */ true); } - else { - this._map._socket.sendMessage('paste mimetype=text/plain;charset=utf-8\n' + pasteString); + else if (window.clipboardData) { // IE 11 + this._dataTransferToDocument(window.clipboardData, /* preferInternal = */ true); } }, @@ -2084,8 +2070,25 @@ L.TileLayer = L.GridLayer.extend({ e = e.originalEvent; e.preventDefault(); + this._dataTransferToDocument(e.dataTransfer, /* preferInternal = */ false); + }, + + _dataTransferToDocument: function (dataTransfer, preferInternal) { + // for the paste, we might prefer the internal LOK's copy/paste + if (preferInternal === true) { + var pasteString = dataTransfer.getData('text/plain'); + if (!pasteString) { + pasteString = window.clipboardData.getData('Text'); + } + + if (pasteString === this._selectionTextHash) { + this._map._socket.sendMessage('uno .uno:Paste'); + return + } + } + // handle content - var types = e.dataTransfer.types; + var types = dataTransfer.types; var hasHTML = false; for (var t = 0; !hasHTML && t < types.length; t++) { if (types[t] === 'text/html') { @@ -2097,15 +2100,15 @@ L.TileLayer = L.GridLayer.extend({ for (t = 0; !handled && t < types.length; t++) { var type = types[t]; if (type === 'text/html') { - this._map._socket.sendMessage('paste mimetype=text/html\n' + e.dataTransfer.getData(type)); + this._map._socket.sendMessage('paste mimetype=text/html\n' + dataTransfer.getData(type)); handled = true; } - else if (type === 'text/plain' && !hasHTML) { - this._map._socket.sendMessage('paste mimetype=text/plain;charset=utf-8\n' + e.dataTransfer.getData(type)); + else if ((type === 'text/plain' || type ==='Text') && !hasHTML) { + this._map._socket.sendMessage('paste mimetype=text/plain;charset=utf-8\n' + dataTransfer.getData(type)); handled = true; } else if (type === 'Files') { - var files = e.dataTransfer.files; + var files = dataTransfer.files; for (var i = 0; i < files.length; ++i) { var file = files[i]; if (file.type.match(/image.*/)) { commit fde34f6e173ceba82c7db40fa36bd38efb368638 Author: Michael Meeks <michael.me...@collabora.com> Date: Mon May 14 10:35:56 2018 +0100 Better debugging of tests. Change-Id: If3b3d2ad862526238bee3c092389c8cc266b24e6 diff --git a/test/countloolkits.hpp b/test/countloolkits.hpp index 90b89e12a..0f7010ff0 100644 --- a/test/countloolkits.hpp +++ b/test/countloolkits.hpp @@ -76,9 +76,11 @@ static std::chrono::steady_clock::time_point TestStartTime; static void testCountHowManyLoolkits() { + const char testname[] = "countHowManyLoolkits "; TestStartTime = std::chrono::steady_clock::now(); InitialLoolKitCount = countLoolKitProcesses(InitialLoolKitCount); + TST_LOG("Initial loolkit count is " << InitialLoolKitCount); CPPUNIT_ASSERT(InitialLoolKitCount > 0); TestStartTime = std::chrono::steady_clock::now(); @@ -86,7 +88,7 @@ static void testCountHowManyLoolkits() static void testNoExtraLoolKitsLeft() { - const char* testname = "noExtraLoolKitsLeft "; + const char testname[] = "noExtraLoolKitsLeft "; const int countNow = countLoolKitProcesses(InitialLoolKitCount); CPPUNIT_ASSERT_EQUAL(InitialLoolKitCount, countNow); commit 071079a6a9aba7ecb7b7da2b2c65d30b58c2f23a Author: Michael Meeks <michael.me...@collabora.com> Date: Sun May 13 12:44:39 2018 +0100 seccomp: allow socket shutdown in kit process. Change-Id: Ie11f5eb278bcba8dcf13d6f095de2ffd6d23fcb3 diff --git a/common/Seccomp.cpp b/common/Seccomp.cpp index 6ae0b2899..fc276a8aa 100644 --- a/common/Seccomp.cpp +++ b/common/Seccomp.cpp @@ -133,7 +133,6 @@ bool lockdown(Type type) KILL_SYSCALL(getitimer), KILL_SYSCALL(setitimer), KILL_SYSCALL(sendfile), - KILL_SYSCALL(shutdown), KILL_SYSCALL(listen), // server sockets KILL_SYSCALL(accept), // server sockets #if 0 commit 6c31b7793c03da7d38754dbef6a9b1f32489c00c Author: Jan Holesovsky <ke...@collabora.com> Date: Fri May 11 19:15:16 2018 +0200 Post the message to the poll thread. Change-Id: Ibd28090a420b5396b64fdfe676bef8cf06991116 diff --git a/kit/Kit.cpp b/kit/Kit.cpp index 59b7dec74..d7f4cc05b 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -753,6 +753,7 @@ public: const std::string& docId, const std::string& url, std::shared_ptr<TileQueue> tileQueue, + SocketPoll& socketPoll, const std::shared_ptr<WebSocketHandler>& websocketHandler) : _loKit(loKit), _jailId(jailId), @@ -760,6 +761,7 @@ public: _docId(docId), _url(url), _tileQueue(std::move(tileQueue)), + _socketPoll(socketPoll), _websocketHandler(websocketHandler), _docPassword(""), _haveDocPassword(false), @@ -794,6 +796,20 @@ public: const std::string& getUrl() const { return _url; } + /// Post the message in the correct thread. + bool postMessage(const std::shared_ptr<std::vector<char>>& message, const WSOpCode code) const + { + LOG_TRC("postMessage called with: " << getAbbreviatedMessage(message->data(), message->size())); + if (!_websocketHandler) + { + LOG_ERR("Child Doc: Bad socket while sending [" << getAbbreviatedMessage(message->data(), message->size()) << "]."); + return false; + } + + _socketPoll.addCallback([=] { _websocketHandler->sendMessage(message->data(), message->size(), code); }); + return true; + } + bool createSession(const std::string& sessionId) { std::unique_lock<std::mutex> lock(_mutex); @@ -908,9 +924,8 @@ public: LOG_INF("setDocumentPassword returned"); } - void renderTile(const std::vector<std::string>& tokens, const std::shared_ptr<WebSocketHandler>& websocketHandler) + void renderTile(const std::vector<std::string>& tokens) { - assert(websocketHandler && "Expected a non-null websocket."); TileDesc tile = TileDesc::parse(tokens); size_t pixmapDataSize = 4 * tile.getWidth() * tile.getHeight(); @@ -964,12 +979,12 @@ public: if (_docWatermark) _docWatermark->blending(pixmap.data(), 0, 0, pixelWidth, pixelHeight, pixelWidth, pixelHeight, mode); - std::vector<char> output; - output.reserve(response.size() + pixmapDataSize); - output.resize(response.size()); - std::memcpy(output.data(), response.data(), response.size()); + std::shared_ptr<std::vector<char>> output = std::make_shared<std::vector<char>>(); + output->reserve(response.size() + pixmapDataSize); + output->resize(response.size()); + std::memcpy(output->data(), response.data(), response.size()); - if (!_pngCache.encodeBufferToPNG(pixmap.data(), tile.getWidth(), tile.getHeight(), output, mode, hash, wid, oldWireId)) + if (!_pngCache.encodeBufferToPNG(pixmap.data(), tile.getWidth(), tile.getHeight(), *output, mode, hash, wid, oldWireId)) { //FIXME: Return error. //sendTextFrame("error: cmd=tile kind=failure"); @@ -978,13 +993,12 @@ public: return; } - LOG_TRC("Sending render-tile response (" << output.size() << " bytes) for: " << response); - websocketHandler->sendMessage(output.data(), output.size(), WSOpCode::Binary); + LOG_TRC("Sending render-tile response (" << output->size() << " bytes) for: " << response); + postMessage(output, WSOpCode::Binary); } - void renderCombinedTiles(const std::vector<std::string>& tokens, const std::shared_ptr<WebSocketHandler>& websocketHandler) + void renderCombinedTiles(const std::vector<std::string>& tokens) { - assert(websocketHandler && "Expected a non-null websocket."); TileCombined tileCombined = TileCombined::parse(tokens); auto& tiles = tileCombined.getTiles(); @@ -1103,12 +1117,12 @@ public: const auto tileMsg = ADD_DEBUG_RENDERID(tileCombined.serialize("tilecombine:")) + "\n"; LOG_TRC("Sending back painted tiles for " << tileMsg); - std::vector<char> response; - response.resize(tileMsg.size() + output.size()); - std::copy(tileMsg.begin(), tileMsg.end(), response.begin()); - std::copy(output.begin(), output.end(), response.begin() + tileMsg.size()); + std::shared_ptr<std::vector<char>> response = std::make_shared<std::vector<char>>(); + response->resize(tileMsg.size() + output.size()); + std::copy(tileMsg.begin(), tileMsg.end(), response->begin()); + std::copy(output.begin(), output.end(), response->begin() + tileMsg.size()); - websocketHandler->sendMessage(response.data(), response.size(), WSOpCode::Binary); + postMessage(response, WSOpCode::Binary); } bool sendTextFrame(const std::string& message) @@ -1120,14 +1134,11 @@ public: { try { - if (!_websocketHandler) - { - LOG_ERR("Child Doc: Bad socket while sending [" << getAbbreviatedMessage(buffer, length) << "]."); - return false; - } + std::shared_ptr<std::vector<char>> message = std::make_shared<std::vector<char>>(); + message->resize(length); + std::memcpy(message->data(), buffer, length); - _websocketHandler->sendMessage(buffer, length, opCode); - return true; + return postMessage(message, opCode); } catch (const Exception& exc) { @@ -1826,11 +1837,11 @@ private: if (tokens[0] == "tile") { - renderTile(tokens, _websocketHandler); + renderTile(tokens); } else if (tokens[0] == "tilecombine") { - renderCombinedTiles(tokens, _websocketHandler); + renderCombinedTiles(tokens); } else if (LOOLProtocol::getFirstToken(tokens[0], '-') == "child") { @@ -1953,6 +1964,7 @@ private: std::shared_ptr<lok::Document> _loKitDocument; std::shared_ptr<TileQueue> _tileQueue; + SocketPoll& _socketPoll; std::shared_ptr<WebSocketHandler> _websocketHandler; PngCache _pngCache; @@ -1995,14 +2007,16 @@ class KitWebSocketHandler final : public WebSocketHandler, public std::enable_sh std::string _socketName; std::shared_ptr<lok::Office> _loKit; std::string _jailId; + SocketPoll& _socketPoll; public: - KitWebSocketHandler(const std::string& socketName, const std::shared_ptr<lok::Office>& loKit, const std::string& jailId) : + KitWebSocketHandler(const std::string& socketName, const std::shared_ptr<lok::Office>& loKit, const std::string& jailId, SocketPoll& socketPoll) : WebSocketHandler(/* isClient = */ true), _queue(std::make_shared<TileQueue>()), _socketName(socketName), _loKit(loKit), - _jailId(jailId) + _jailId(jailId), + _socketPoll(socketPoll) { } @@ -2038,7 +2052,7 @@ protected: if (!document) { - document = std::make_shared<Document>(_loKit, _jailId, docKey, docId, url, _queue, shared_from_this()); + document = std::make_shared<Document>(_loKit, _jailId, docKey, docId, url, _queue, _socketPoll, shared_from_this()); } // Validate and create session. @@ -2336,7 +2350,7 @@ void lokit_main(const std::string& childRoot, SocketPoll mainKit("kit"); - mainKit.insertNewWebSocketSync(uri, std::make_shared<KitWebSocketHandler>("child_ws_" + pid, loKit, jailId)); + mainKit.insertNewWebSocketSync(uri, std::make_shared<KitWebSocketHandler>("child_ws_" + pid, loKit, jailId, mainKit)); LOG_INF("New kit client websocket inserted."); while (!TerminationFlag) commit 93cc4b4548e3abcefc282e05ddf46677f8dd2897 Author: Jan Holesovsky <ke...@collabora.com> Date: Wed May 9 20:25:58 2018 +0200 Use std::shared_ptr consistently. Change-Id: I6bf3ff7de47010fd78fab26a5a318bde21c1f153 diff --git a/net/Socket.hpp b/net/Socket.hpp index cb146f62c..6cd2190d4 100644 --- a/net/Socket.hpp +++ b/net/Socket.hpp @@ -704,11 +704,11 @@ protected: class StreamSocket : public Socket, public std::enable_shared_from_this<StreamSocket> { public: - /// Create a StreamSocket from native FD and take ownership of handler instance. + /// Create a StreamSocket from native FD. StreamSocket(const int fd, bool /* isClient */, - std::shared_ptr<SocketHandlerInterface> socketHandler) : + const std::shared_ptr<SocketHandlerInterface> socketHandler) : Socket(fd), - _socketHandler(std::move(socketHandler)), + _socketHandler(socketHandler), _bytesSent(0), _bytesRecvd(0), _wsState(WSState::HTTP), diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp index 856c1721a..59814b6d5 100644 --- a/net/WebSocketHandler.hpp +++ b/net/WebSocketHandler.hpp @@ -130,7 +130,7 @@ public: if (len == 0) return false; // avoid logging. - LOG_TRC("#" << socket->getFD() << ": Incoming WebSocket data of " << len << " bytes."); + LOG_TRC("#" << socket->getFD() << ": Incoming WebSocket data of " << len << " bytes: " << std::string(socket->_inBuffer.data(), socket->_inBuffer.size())); if (len < 2) // partial read return false; diff --git a/tools/WebSocketDump.cpp b/tools/WebSocketDump.cpp index 202bff3f7..777a2c82e 100644 --- a/tools/WebSocketDump.cpp +++ b/tools/WebSocketDump.cpp @@ -205,9 +205,9 @@ public: { #if ENABLE_SSL if (_isSSL) - return StreamSocket::create<SslStreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); + return StreamSocket::create<SslStreamSocket>(physicalFd, false, std::make_shared<ClientRequestDispatcher>()); #endif - return StreamSocket::create<StreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); + return StreamSocket::create<StreamSocket>(physicalFd, false, std::make_shared<ClientRequestDispatcher>()); } }; diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index 7c44bab3a..9e48426c8 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -2443,8 +2443,7 @@ class PlainSocketFactory final : public SocketFactory std::shared_ptr<Socket> socket = StreamSocket::create<StreamSocket>( - fd, false, std::unique_ptr<SocketHandlerInterface>{ - new ClientRequestDispatcher }); + fd, false, std::make_shared<ClientRequestDispatcher>()); return socket; } @@ -2461,8 +2460,7 @@ class SslSocketFactory final : public SocketFactory fd = Delay::create(SimulatedLatencyMs, physicalFd); return StreamSocket::create<SslStreamSocket>( - fd, false, std::unique_ptr<SocketHandlerInterface>{ - new ClientRequestDispatcher }); + fd, false, std::make_shared<ClientRequestDispatcher>()); } }; #endif @@ -2472,7 +2470,7 @@ class PrisonerSocketFactory final : public SocketFactory std::shared_ptr<Socket> create(const int fd) override { // No local delay. - return StreamSocket::create<StreamSocket>(fd, false, std::unique_ptr<SocketHandlerInterface>{ new PrisonerRequestDispatcher }); + return StreamSocket::create<StreamSocket>(fd, false, std::make_shared<PrisonerRequestDispatcher>()); } }; commit 6996139e5fba0310ddc9d97f2005a3383818ec7a Author: Jan Holesovsky <ke...@collabora.com> Date: Mon May 7 15:09:40 2018 +0200 Use correct path in the client websockets. Change-Id: Ie0bf6646ff3f6e6cf99b505143a416c86a3a33b8 diff --git a/common/Common.hpp b/common/Common.hpp index b73e98249..42e8dd450 100644 --- a/common/Common.hpp +++ b/common/Common.hpp @@ -29,10 +29,10 @@ constexpr long READ_BUFFER_SIZE = 64 * 1024; /// or as intentionally flooding the server. constexpr int MAX_MESSAGE_SIZE = 2 * 1024 * READ_BUFFER_SIZE; -constexpr const char* JAILED_DOCUMENT_ROOT = "/user/docs/"; -constexpr const char* CHILD_URI = "/loolws/child?"; -constexpr const char* NEW_CHILD_URI = "/loolws/newchild?"; -constexpr const char* LO_JAIL_SUBPATH = "lo"; +constexpr const char JAILED_DOCUMENT_ROOT[] = "/user/docs/"; +constexpr const char CHILD_URI[] = "/loolws/child?"; +constexpr const char NEW_CHILD_URI[] = "/loolws/newchild"; +constexpr const char LO_JAIL_SUBPATH[] = "lo"; /// The HTTP response User-Agent. constexpr const char* HTTP_AGENT_STRING = "LOOLWSD HTTP Agent " LOOLWSD_VERSION; diff --git a/kit/Kit.cpp b/kit/Kit.cpp index 88a58e198..59b7dec74 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -2318,25 +2318,24 @@ void lokit_main(const std::string& childRoot, static const std::string pid = std::to_string(Process::id()); - std::string requestUrl = NEW_CHILD_URI; - requestUrl += "pid=" + pid + "&jailid=" + jailId; + Poco::URI uri("ws://127.0.0.1"); + uri.setPort(MasterPortNumber); + uri.setPath(NEW_CHILD_URI); + uri.addQueryParameter("pid", std::to_string(Process::id())); + uri.addQueryParameter("jailid", jailId); + if (queryVersion) { char* versionInfo = loKit->getVersionInfo(); std::string versionString(versionInfo); if (displayVersion) std::cout << "office version details: " << versionString << std::endl; - std::string encodedVersionStr; - URI::encode(versionString, "", encodedVersionStr); - requestUrl += "&version=" + encodedVersionStr; + uri.addQueryParameter("version", versionString); free(versionInfo); } SocketPoll mainKit("kit"); - Poco::URI uri("ws://127.0.0.1"); - uri.setPort(MasterPortNumber); - mainKit.insertNewWebSocketSync(uri, std::make_shared<KitWebSocketHandler>("child_ws_" + pid, loKit, jailId)); LOG_INF("New kit client websocket inserted."); diff --git a/net/Socket.cpp b/net/Socket.cpp index 17ac9166d..4d58ff949 100644 --- a/net/Socket.cpp +++ b/net/Socket.cpp @@ -185,7 +185,7 @@ void SocketPoll::insertNewWebSocketSync(const Poco::URI &uri, const std::shared_ // send Sec-WebSocket-Key: <hmm> ... Sec-WebSocket-Protocol: chat, Sec-WebSocket-Version: 13 std::ostringstream oss; - oss << "GET " << uri.getHost() << " HTTP/1.1\r\n" + oss << "GET " << uri.getPathAndQuery() << " HTTP/1.1\r\n" "Connection:Upgrade\r\n" "User-Foo: Adminbits\r\n" "Sec-WebSocket-Key: GAcwqP21iVOY2yKefQ64c0yVN5M=\r\n" diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index 8a99428a5..7c44bab3a 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -1620,14 +1620,15 @@ private: return; LOG_TRC("Child connection with URI [" << request.getURI() << "]."); - if (request.getURI().find(NEW_CHILD_URI) != 0) + Poco::URI requestURI(request.getURI()); + if (requestURI.getPath() != NEW_CHILD_URI) { LOG_ERR("Invalid incoming URI."); return; } // New Child is spawned. - const Poco::URI::QueryParameters params = Poco::URI(request.getURI()).getQueryParameters(); + const Poco::URI::QueryParameters params = requestURI.getQueryParameters(); Poco::Process::PID pid = -1; std::string jailId; for (const auto& param : params) commit 98ed24257b505b10ac48b2159d12f82cb81b3559 Author: Jan Holesovsky <ke...@collabora.com> Date: Fri May 4 18:47:33 2018 +0200 Move the functionality from connectToMonitor() to SocketPoll. Change-Id: Iab2ac09638323f5e59f7a2ea0d880f52989ad64d diff --git a/kit/ChildSession.hpp b/kit/ChildSession.hpp index 093ca4e66..b1cd90133 100644 --- a/kit/ChildSession.hpp +++ b/kit/ChildSession.hpp @@ -70,7 +70,7 @@ public: virtual std::shared_ptr<TileQueue>& getTileQueue() = 0; - virtual bool sendFrame(const char* buffer, int length, int flags = Poco::Net::WebSocket::FRAME_TEXT) = 0; + virtual bool sendFrame(const char* buffer, int length, WSOpCode opCode = WSOpCode::Text) = 0; }; struct RecordedEvent @@ -161,14 +161,14 @@ public: { const auto msg = "client-" + getId() + ' ' + std::string(buffer, length); const std::unique_lock<std::mutex> lock = getLock(); - return _docManager.sendFrame(msg.data(), msg.size(), Poco::Net::WebSocket::FRAME_TEXT); + return _docManager.sendFrame(msg.data(), msg.size(), WSOpCode::Text); } bool sendBinaryFrame(const char* buffer, int length) override { const auto msg = "client-" + getId() + ' ' + std::string(buffer, length); const std::unique_lock<std::mutex> lock = getLock(); - return _docManager.sendFrame(msg.data(), msg.size(), Poco::Net::WebSocket::FRAME_BINARY); + return _docManager.sendFrame(msg.data(), msg.size(), WSOpCode::Binary); } using Session::sendTextFrame; diff --git a/kit/Kit.cpp b/kit/Kit.cpp index 37c62f10c..88a58e198 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -979,7 +979,7 @@ public: } LOG_TRC("Sending render-tile response (" << output.size() << " bytes) for: " << response); - websocketHandler->sendMessage(output.data(), output.size(), WebSocket::FRAME_BINARY); + websocketHandler->sendMessage(output.data(), output.size(), WSOpCode::Binary); } void renderCombinedTiles(const std::vector<std::string>& tokens, const std::shared_ptr<WebSocketHandler>& websocketHandler) @@ -1108,7 +1108,7 @@ public: std::copy(tileMsg.begin(), tileMsg.end(), response.begin()); std::copy(output.begin(), output.end(), response.begin() + tileMsg.size()); - websocketHandler->sendMessage(response.data(), response.size(), WebSocket::FRAME_BINARY); + websocketHandler->sendMessage(response.data(), response.size(), WSOpCode::Binary); } bool sendTextFrame(const std::string& message) @@ -1116,7 +1116,7 @@ public: return sendFrame(message.data(), message.size()); } - bool sendFrame(const char* buffer, int length, int flags = Poco::Net::WebSocket::FRAME_TEXT) override + bool sendFrame(const char* buffer, int length, WSOpCode opCode = WSOpCode::Text) override { try { @@ -1126,7 +1126,7 @@ public: return false; } - _websocketHandler->sendMessage(buffer, length, flags); + _websocketHandler->sendMessage(buffer, length, opCode); return true; } catch (const Exception& exc) @@ -2334,7 +2334,7 @@ void lokit_main(const std::string& childRoot, SocketPoll mainKit("kit"); - const Poco::URI uri("ws://127.0.0.1"); + Poco::URI uri("ws://127.0.0.1"); uri.setPort(MasterPortNumber); mainKit.insertNewWebSocketSync(uri, std::make_shared<KitWebSocketHandler>("child_ws_" + pid, loKit, jailId)); diff --git a/net/Socket.cpp b/net/Socket.cpp index c9a2968f6..17ac9166d 100644 --- a/net/Socket.cpp +++ b/net/Socket.cpp @@ -20,10 +20,12 @@ #include <Poco/MemoryStream.h> #include <Poco/Net/HTTPRequest.h> #include <Poco/Net/HTTPResponse.h> +#include <Poco/URI.h> #include <SigUtil.hpp> #include "Socket.hpp" #include "ServerSocket.hpp" +#include "SslSocket.hpp" #include "WebSocketHandler.hpp" int SocketPoll::DefaultPollTimeoutMs = 5000; @@ -126,6 +128,98 @@ void SocketPoll::wakeupWorld() wakeup(fd); } +void SocketPoll::insertNewWebSocketSync(const Poco::URI &uri, const std::shared_ptr<SocketHandlerInterface>& websocketHandler) +{ + LOG_INF("Connecting to " << uri.getHost() << " : " << uri.getPort() << " : " << uri.getPath()); + + // FIXME: put this in a ClientSocket class ? + // FIXME: store the address there - and ... (so on) ... + struct addrinfo* ainfo = nullptr; + struct addrinfo hints; + std::memset(&hints, 0, sizeof(hints)); + int rc = getaddrinfo(uri.getHost().c_str(), + std::to_string(uri.getPort()).c_str(), + &hints, &ainfo); + std::string canonicalName; + bool isSSL = uri.getScheme() != "ws"; +#if !ENABLE_SSL + if (isSSL) + { + LOG_ERR("Error: wss for client websocket requested but SSL not compiled in."); + return; + } +#endif + + if (!rc && ainfo) + { + for (struct addrinfo* ai = ainfo; ai; ai = ai->ai_next) + { + if (ai->ai_canonname) + canonicalName = ai->ai_canonname; + + if (ai->ai_addrlen && ai->ai_addr) + { + int fd = socket(ai->ai_addr->sa_family, SOCK_STREAM | SOCK_NONBLOCK, 0); + int res = connect(fd, ai->ai_addr, ai->ai_addrlen); + // FIXME: SSL sockets presumably need some setup, checking etc. and ... =) + if (fd < 0 || (res < 0 && errno != EINPROGRESS)) + { + LOG_ERR("Failed to connect to " << uri.getHost()); + close(fd); + } + else + { + std::shared_ptr<StreamSocket> socket; +#if ENABLE_SSL + if (isSSL) + socket = StreamSocket::create<SslStreamSocket>(fd, true, websocketHandler); +#endif + if (!socket && !isSSL) + socket = StreamSocket::create<StreamSocket>(fd, true, websocketHandler); + + if (socket) + { + LOG_DBG("Connected to client websocket " << uri.getHost() << " #" << socket->getFD()); + + // cf. WebSocketHandler::upgradeToWebSocket (?) + // send Sec-WebSocket-Key: <hmm> ... Sec-WebSocket-Protocol: chat, Sec-WebSocket-Version: 13 + + std::ostringstream oss; + oss << "GET " << uri.getHost() << " HTTP/1.1\r\n" + "Connection:Upgrade\r\n" + "User-Foo: Adminbits\r\n" + "Sec-WebSocket-Key: GAcwqP21iVOY2yKefQ64c0yVN5M=\r\n" + "Upgrade:websocket\r\n" + "Accept-Encoding:gzip, deflate, br\r\n" + "Accept-Language:en\r\n" + "Cache-Control:no-cache\r\n" + "Pragma:no-cache\r\n" + "Sec-WebSocket-Extensions:permessage-deflate; client_max_window_bits\r\n" + "Sec-WebSocket-Key:fxTaWTEMVhq1PkWsMoLxGw==\r\n" + "Sec-WebSocket-Version:13\r\n" + "User-Agent: " << WOPI_AGENT_STRING << "\r\n" + "\r\n"; + socket->send(oss.str()); + websocketHandler->onConnect(socket); + insertNewSocket(socket); + } + else + { + LOG_ERR("Failed to allocate socket for client websocket " << uri.getHost()); + close(fd); + } + + break; + } + } + } + + freeaddrinfo(ainfo); + } + else + LOG_ERR("Failed to lookup client websocket host '" << uri.getHost() << "' skipping"); +} + void ServerSocket::dumpState(std::ostream& os) { os << "\t" << getFD() << "\t<accept>\n"; diff --git a/net/Socket.hpp b/net/Socket.hpp index 373fcc79c..cb146f62c 100644 --- a/net/Socket.hpp +++ b/net/Socket.hpp @@ -46,6 +46,7 @@ namespace Poco class HTTPRequest; class HTTPResponse; } + class URI; } class Socket; @@ -307,6 +308,41 @@ private: std::thread::id _owner; }; +class StreamSocket; + +/// Interface that handles the actual incoming message. +class SocketHandlerInterface +{ +public: + virtual ~SocketHandlerInterface() {} + /// Called when the socket is newly created to + /// set the socket associated with this ResponseClient. + /// Will be called exactly once. + virtual void onConnect(const std::shared_ptr<StreamSocket>& socket) = 0; + + /// Called after successful socket reads. + virtual void handleIncomingMessage(SocketDisposition &disposition) = 0; + + /// Prepare our poll record; adjust @timeoutMaxMs downwards + /// for timeouts, based on current time @now. + /// @returns POLLIN and POLLOUT if output is expected. + virtual int getPollEvents(std::chrono::steady_clock::time_point now, + int &timeoutMaxMs) = 0; + + /// Do we need to handle a timeout ? + virtual void checkTimeout(std::chrono::steady_clock::time_point /* now */) {} + + /// Do some of the queued writing. + virtual void performWrites() = 0; + + /// Called when the is disconnected and will be destroyed. + /// Will be called exactly once. + virtual void onDisconnect() {} + + /// Append pretty printed internal state to a line + virtual void dumpState(std::ostream& os) { os << "\n"; } +}; + /// Handles non-blocking socket event polling. /// Only polls on N-Sockets and invokes callback and /// doesn't manage buffers or client data. @@ -534,6 +570,10 @@ public: } } + /// Inserts a new websocket to be polled. + /// NOTE: The DNS lookup is synchronous. + void insertNewWebSocketSync(const Poco::URI &uri, const std::shared_ptr<SocketHandlerInterface>& websocketHandler); + typedef std::function<void()> CallbackFn; /// Add a callback to be invoked in the polling thread @@ -660,41 +700,6 @@ protected: std::thread::id _owner; }; -class StreamSocket; - -/// Interface that handles the actual incoming message. -class SocketHandlerInterface -{ -public: - virtual ~SocketHandlerInterface() {} - /// Called when the socket is newly created to - /// set the socket associated with this ResponseClient. - /// Will be called exactly once. - virtual void onConnect(const std::shared_ptr<StreamSocket>& socket) = 0; - - /// Called after successful socket reads. - virtual void handleIncomingMessage(SocketDisposition &disposition) = 0; - - /// Prepare our poll record; adjust @timeoutMaxMs downwards - /// for timeouts, based on current time @now. - /// @returns POLLIN and POLLOUT if output is expected. - virtual int getPollEvents(std::chrono::steady_clock::time_point now, - int &timeoutMaxMs) = 0; - - /// Do we need to handle a timeout ? - virtual void checkTimeout(std::chrono::steady_clock::time_point /* now */) {} - - /// Do some of the queued writing. - virtual void performWrites() = 0; - - /// Called when the is disconnected and will be destroyed. - /// Will be called exactly once. - virtual void onDisconnect() {} - - /// Append pretty printed internal state to a line - virtual void dumpState(std::ostream& os) { os << "\n"; } -}; - /// A plain, non-blocking, data streaming socket. class StreamSocket : public Socket, public std::enable_shared_from_this<StreamSocket> { diff --git a/test/Makefile.am b/test/Makefile.am index 5b17f54a2..7e59fa621 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -25,6 +25,10 @@ noinst_LTLIBRARIES = \ MAGIC_TO_FORCE_SHLIB_CREATION = -rpath /dummy AM_LDFLAGS = -pthread -module $(MAGIC_TO_FORCE_SHLIB_CREATION) $(ZLIB_LIBS) +if ENABLE_SSL +AM_LDFLAGS += -lssl -lcrypto +endif + # We work around some of the mess of using the same sources both on # the server side and here in unit tests with conditional compilation # based on BUILDING_TESTS @@ -47,6 +51,10 @@ wsd_sources = \ ../common/Unit.cpp \ ../net/Socket.cpp +if ENABLE_SSL +wsd_sources += ../net/Ssl.cpp +endif + test_base_source = \ TileQueueTests.cpp \ WhiteBoxTests.cpp \ diff --git a/test/WhiteBoxTests.cpp b/test/WhiteBoxTests.cpp index f910aac3d..580df11e3 100644 --- a/test/WhiteBoxTests.cpp +++ b/test/WhiteBoxTests.cpp @@ -390,7 +390,7 @@ public: return _tileQueue; } - bool sendFrame(const char* /*buffer*/, int /*length*/, int /*flags*/) override + bool sendFrame(const char* /*buffer*/, int /*length*/, WSOpCode /*opCode*/) override { return true; } diff --git a/wsd/Admin.cpp b/wsd/Admin.cpp index d0e11aeb8..0350c714d 100644 --- a/wsd/Admin.cpp +++ b/wsd/Admin.cpp @@ -656,95 +656,7 @@ public: void Admin::connectToMonitor(const Poco::URI &uri) { - LOG_INF("Connecting to monitor " << uri.getHost() << " : " << uri.getPort() << " : " << uri.getPath()); - - // FIXME: put this in a ClientSocket class ? - // FIXME: store the address there - and ... (so on) ... - struct addrinfo* ainfo = nullptr; - struct addrinfo hints; - std::memset(&hints, 0, sizeof(hints)); - int rc = getaddrinfo(uri.getHost().c_str(), - std::to_string(uri.getPort()).c_str(), - &hints, &ainfo); - std::string canonicalName; - bool isSSL = uri.getScheme() != "ws"; -#if !ENABLE_SSL - if (isSSL) - { - LOG_ERR("Error: wss for monitor requested but SSL not compiled in."); - return; - } -#endif - - if (!rc && ainfo) - { - for (struct addrinfo* ai = ainfo; ai; ai = ai->ai_next) - { - if (ai->ai_canonname) - canonicalName = ai->ai_canonname; - - if (ai->ai_addrlen && ai->ai_addr) - { - int fd = socket(ai->ai_addr->sa_family, SOCK_STREAM | SOCK_NONBLOCK, 0); - int res = connect(fd, ai->ai_addr, ai->ai_addrlen); - // FIXME: SSL sockets presumably need some setup, checking etc. and ... =) - if (fd < 0 || (res < 0 && errno != EINPROGRESS)) - { - LOG_ERR("Failed to connect to " << uri.getHost()); - close(fd); - } - else - { - std::shared_ptr<StreamSocket> socket; - std::shared_ptr<SocketHandlerInterface> handler = std::make_shared<MonitorSocketHandler>(this); -#if ENABLE_SSL - if (isSSL) - socket = StreamSocket::create<SslStreamSocket>(fd, true, handler); -#endif - if (!socket && !isSSL) - socket = StreamSocket::create<StreamSocket>(fd, true, handler); - - if (socket) - { - LOG_DBG("Connected to monitor " << uri.getHost() << " #" << socket->getFD()); - - // cf. WebSocketHandler::upgradeToWebSocket (?) - // send Sec-WebSocket-Key: <hmm> ... Sec-WebSocket-Protocol: chat, Sec-WebSocket-Version: 13 - - std::ostringstream oss; - oss << "GET " << uri.getHost() << " HTTP/1.1\r\n" - "Connection:Upgrade\r\n" - "User-Foo: Adminbits\r\n" - "Sec-WebSocket-Key: GAcwqP21iVOY2yKefQ64c0yVN5M=\r\n" - "Upgrade:websocket\r\n" - "Accept-Encoding:gzip, deflate, br\r\n" - "Accept-Language:en\r\n" - "Cache-Control:no-cache\r\n" - "Pragma:no-cache\r\n" - "Sec-WebSocket-Extensions:permessage-deflate; client_max_window_bits\r\n" - "Sec-WebSocket-Key:fxTaWTEMVhq1PkWsMoLxGw==\r\n" - "Sec-WebSocket-Version:13\r\n" - "User-Agent: " << WOPI_AGENT_STRING << "\r\n" - "\r\n"; - socket->send(oss.str()); - handler->onConnect(socket); - insertNewSocket(socket); - } - else - { - LOG_ERR("Failed to allocate socket for monitor " << uri.getHost()); - close(fd); - } - - break; - } - } - } - - freeaddrinfo(ainfo); - } - else - LOG_ERR("Failed to lookup monitor host '" << uri.getHost() << "' skipping"); + insertNewWebSocketSync(uri, std::make_shared<MonitorSocketHandler>(this)); } void Admin::start() commit 35ec3649bbf98fc79d28530a0bb8e082156ba831 Author: Jan Holesovsky <ke...@collabora.com> Date: Fri May 4 18:32:42 2018 +0200 Change Kit to use the new client websocket code. Change-Id: Ib4e62ea618da5bd8992b51165b0d7ee955c61637 diff --git a/kit/Kit.cpp b/kit/Kit.cpp index 1451778b3..37c62f10c 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -57,7 +57,6 @@ #include "KitHelper.hpp" #include "Kit.hpp" #include <Protocol.hpp> -#include <LOOLWebSocket.hpp> #include <Log.hpp> #include <Png.hpp> #include <Rectangle.hpp> @@ -754,14 +753,14 @@ public: const std::string& docId, const std::string& url, std::shared_ptr<TileQueue> tileQueue, - const std::shared_ptr<LOOLWebSocket>& ws) + const std::shared_ptr<WebSocketHandler>& websocketHandler) : _loKit(loKit), _jailId(jailId), _docKey(docKey), _docId(docId), _url(url), _tileQueue(std::move(tileQueue)), - _ws(ws), + _websocketHandler(websocketHandler), _docPassword(""), _haveDocPassword(false), _isDocPasswordProtected(false), @@ -909,9 +908,9 @@ public: LOG_INF("setDocumentPassword returned"); } - void renderTile(const std::vector<std::string>& tokens, const std::shared_ptr<LOOLWebSocket>& ws) + void renderTile(const std::vector<std::string>& tokens, const std::shared_ptr<WebSocketHandler>& websocketHandler) { - assert(ws && "Expected a non-null websocket."); + assert(websocketHandler && "Expected a non-null websocket."); TileDesc tile = TileDesc::parse(tokens); size_t pixmapDataSize = 4 * tile.getWidth() * tile.getHeight(); @@ -980,12 +979,12 @@ public: } LOG_TRC("Sending render-tile response (" << output.size() << " bytes) for: " << response); - ws->sendFrame(output.data(), output.size(), WebSocket::FRAME_BINARY); + websocketHandler->sendMessage(output.data(), output.size(), WebSocket::FRAME_BINARY); } - void renderCombinedTiles(const std::vector<std::string>& tokens, const std::shared_ptr<LOOLWebSocket>& ws) + void renderCombinedTiles(const std::vector<std::string>& tokens, const std::shared_ptr<WebSocketHandler>& websocketHandler) { - assert(ws && "Expected a non-null websocket."); + assert(websocketHandler && "Expected a non-null websocket."); TileCombined tileCombined = TileCombined::parse(tokens); auto& tiles = tileCombined.getTiles(); @@ -1109,7 +1108,7 @@ public: std::copy(tileMsg.begin(), tileMsg.end(), response.begin()); std::copy(output.begin(), output.end(), response.begin() + tileMsg.size()); - ws->sendFrame(response.data(), response.size(), WebSocket::FRAME_BINARY); + websocketHandler->sendMessage(response.data(), response.size(), WebSocket::FRAME_BINARY); } bool sendTextFrame(const std::string& message) @@ -1121,13 +1120,13 @@ public: { try { - if (!_ws || _ws->poll(Poco::Timespan(0), Poco::Net::Socket::SelectMode::SELECT_ERROR)) + if (!_websocketHandler) { LOG_ERR("Child Doc: Bad socket while sending [" << getAbbreviatedMessage(buffer, length) << "]."); return false; } - _ws->sendFrame(buffer, length, flags); + _websocketHandler->sendMessage(buffer, length, flags); return true; } catch (const Exception& exc) @@ -1827,11 +1826,11 @@ private: if (tokens[0] == "tile") { - renderTile(tokens, _ws); + renderTile(tokens, _websocketHandler); } else if (tokens[0] == "tilecombine") { - renderCombinedTiles(tokens, _ws); + renderCombinedTiles(tokens, _websocketHandler); } else if (LOOLProtocol::getFirstToken(tokens[0], '-') == "child") { @@ -1954,7 +1953,7 @@ private: std::shared_ptr<lok::Document> _loKitDocument; std::shared_ptr<TileQueue> _tileQueue; - std::shared_ptr<LOOLWebSocket> _ws; + std::shared_ptr<WebSocketHandler> _websocketHandler; PngCache _pngCache; // Document password provided @@ -1990,6 +1989,97 @@ private: Poco::Thread _callbackThread; }; +class KitWebSocketHandler final : public WebSocketHandler, public std::enable_shared_from_this<KitWebSocketHandler> +{ + std::shared_ptr<TileQueue> _queue; + std::string _socketName; + std::shared_ptr<lok::Office> _loKit; + std::string _jailId; + +public: + KitWebSocketHandler(const std::string& socketName, const std::shared_ptr<lok::Office>& loKit, const std::string& jailId) : + WebSocketHandler(/* isClient = */ true), + _queue(std::make_shared<TileQueue>()), + _socketName(socketName), + _loKit(loKit), + _jailId(jailId) + { + } + +protected: + void handleMessage(bool /*fin*/, WSOpCode /*code*/, std::vector<char>& data) override + { + std::string message(data.data(), data.size()); + +#if 0 // FIXME might be needed for unit tests #ifndef KIT_IN_PROCESS + if (UnitKit::get().filterKitMessage(ws, message)) + { + return; + } +#endif + + LOG_DBG(_socketName << ": recv [" << LOOLProtocol::getAbbreviatedMessage(message) << "]."); + std::vector<std::string> tokens = LOOLProtocol::tokenize(message); + + // Note: Syntax or parsing errors here are unexpected and fatal. + if (TerminationFlag) + { + LOG_DBG("Too late, we're going down"); + } + else if (tokens[0] == "session") + { + const std::string& sessionId = tokens[1]; + const std::string& docKey = tokens[2]; + const std::string& docId = tokens[3]; + + std::string url; + URI::decode(docKey, url); + LOG_INF("New session [" << sessionId << "] request on url [" << url << "]."); + + if (!document) + { + document = std::make_shared<Document>(_loKit, _jailId, docKey, docId, url, _queue, shared_from_this()); + } + + // Validate and create session. + if (!(url == document->getUrl() && document->createSession(sessionId))) + { + LOG_DBG("CreateSession failed."); + } + } + else if (tokens[0] == "exit") + { + LOG_TRC("Setting TerminationFlag due to 'exit' command from parent."); + TerminationFlag = true; + } + else if (tokens[0] == "tile" || tokens[0] == "tilecombine" || tokens[0] == "canceltiles" || + tokens[0] == "paintwindow" || + LOOLProtocol::getFirstToken(tokens[0], '-') == "child") + { + if (document) + { + _queue->put(message); + } + else + { + LOG_WRN("No document while processing " << tokens[0] << " request."); + } + } + else if (tokens.size() == 3 && tokens[0] == "setconfig") + { + // Currently onlly rlimit entries are supported. + if (!Rlimit::handleSetrlimitCommand(tokens)) + { + LOG_ERR("Unknown setconfig command: " << message); + } + } + else + { + LOG_ERR("Bad or unknown token [" << tokens[0] << "]"); + } + } +}; + void documentViewCallback(const int type, const char* payload, void* data) { Document::ViewCallback(type, payload, data); @@ -2242,110 +2332,26 @@ void lokit_main(const std::string& childRoot, free(versionInfo); } - // Open websocket connection between the child process and WSD. - HTTPClientSession cs("127.0.0.1", MasterPortNumber); - cs.setTimeout(Poco::Timespan(10, 0)); // 10 second - LOG_DBG("Connecting to Master " << cs.getHost() << ':' << cs.getPort()); - HTTPRequest request(HTTPRequest::HTTP_GET, requestUrl); - HTTPResponse response; - auto ws = std::make_shared<LOOLWebSocket>(cs, request, response); - ws->setReceiveTimeout(0); + SocketPoll mainKit("kit"); - auto queue = std::make_shared<TileQueue>(); + const Poco::URI uri("ws://127.0.0.1"); + uri.setPort(MasterPortNumber); - if (bTraceStartup && LogLevel != "trace") - { - LOG_INF("Setting log-level to [" << LogLevel << "]."); - Log::logger().setLevel(LogLevel); - } - - const std::string socketName = "child_ws_" + pid; - IoUtil::SocketProcessor(ws, socketName, - [&socketName, &ws, &loKit, &jailId, &queue](const std::vector<char>& data) - { - std::string message(data.data(), data.size()); - -#ifndef KIT_IN_PROCESS - if (UnitKit::get().filterKitMessage(ws, message)) - { - return true; - } -#endif - - LOG_DBG(socketName << ": recv [" << LOOLProtocol::getAbbreviatedMessage(message) << "]."); - std::vector<std::string> tokens = LOOLProtocol::tokenize(message); + mainKit.insertNewWebSocketSync(uri, std::make_shared<KitWebSocketHandler>("child_ws_" + pid, loKit, jailId)); + LOG_INF("New kit client websocket inserted."); - // Note: Syntax or parsing errors here are unexpected and fatal. - if (TerminationFlag) - { - LOG_DBG("Too late, we're going down"); - } - else if (tokens[0] == "session") - { - const std::string& sessionId = tokens[1]; - const std::string& docKey = tokens[2]; - const std::string& docId = tokens[3]; - - std::string url; - URI::decode(docKey, url); - LOG_INF("New session [" << sessionId << "] request on url [" << url << "]."); - - if (!document) - { - document = std::make_shared<Document>(loKit, jailId, docKey, docId, url, queue, ws); - } - - // Validate and create session. - if (!(url == document->getUrl() && - document->createSession(sessionId))) - { - LOG_DBG("CreateSession failed."); - } - } - else if (tokens[0] == "exit") - { - LOG_TRC("Setting TerminationFlag due to 'exit' command from parent."); - TerminationFlag = true; - } - else if (tokens[0] == "tile" || tokens[0] == "tilecombine" || tokens[0] == "canceltiles" || - tokens[0] == "paintwindow" || - LOOLProtocol::getFirstToken(tokens[0], '-') == "child") - { - if (document) - { - queue->put(message); - } - else - { - LOG_WRN("No document while processing " << tokens[0] << " request."); - } - } - else if (tokens.size() == 3 && tokens[0] == "setconfig") - { - // Currently onlly rlimit entries are supported. - if (!Rlimit::handleSetrlimitCommand(tokens)) - { - LOG_ERR("Unknown setconfig command: " << message); - } - } - else - { - LOG_ERR("Bad or unknown token [" << tokens[0] << "]"); - } + while (!TerminationFlag) + { + mainKit.poll(SocketPoll::DefaultPollTimeoutMs); - return true; - }, - []() {}, - []() - { - if (document && document->purgeSessions() == 0) - { - LOG_INF("Last session discarded. Terminating."); - TerminationFlag = true; - } + if (document && document->purgeSessions() == 0) + { + LOG_INF("Last session discarded. Terminating."); + TerminationFlag = true; + } + } - return TerminationFlag.load(); - }); + LOG_INF("Kit poll terminated."); // Let forkit handle the jail cleanup. } commit 82f2f2711b2d2b3e1f8099c0a8e8305bc516a303 Author: Jan Holesovsky <ke...@collabora.com> Date: Fri May 4 16:08:32 2018 +0200 websocketdump: Read the port and ssl support from the config. Change-Id: Ifc4566d5e1f2cdba1fd4bd7d53b359d81604083b diff --git a/tools/WebSocketDump.cpp b/tools/WebSocketDump.cpp index 7f13012dd..202bff3f7 100644 --- a/tools/WebSocketDump.cpp +++ b/tools/WebSocketDump.cpp @@ -18,6 +18,7 @@ #include <Poco/Net/HTTPRequest.h> #include <Poco/Net/HTTPResponse.h> #include <Poco/StringTokenizer.h> +#include <Poco/Util/XMLConfiguration.h> #include <Log.hpp> #include <Util.hpp> @@ -194,13 +195,19 @@ private: class DumpSocketFactory final : public SocketFactory { +private: + bool _isSSL = false; + +public: + DumpSocketFactory(bool isSSL) : _isSSL(isSSL) {} + std::shared_ptr<Socket> create(const int physicalFd) override { #if ENABLE_SSL - return StreamSocket::create<SslStreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); -#else - return StreamSocket::create<StreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); + if (_isSSL) + return StreamSocket::create<SslStreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); #endif + return StreamSocket::create<StreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); } }; @@ -212,9 +219,15 @@ namespace Util } } +class LoolConfig final: public Poco::Util::XMLConfiguration +{ +public: + LoolConfig() + {} +}; + int main (int argc, char **argv) { - int port = 9042; (void) argc; (void) argv; if (!UnitWSD::init(UnitWSD::UnitType::Wsd, "")) @@ -225,6 +238,20 @@ int main (int argc, char **argv) Log::initialize("WebSocketDump", "trace", true, false, std::map<std::string, std::string>()); + LoolConfig config; + config.load("loolwsd.xml"); + + // read the port & ssl support + int port = 9042; + bool isSSL = false; + std::string monitorAddress = config.getString("monitors.monitor"); + if (!monitorAddress.empty()) + { + Poco::URI monitorURI(monitorAddress); + port = monitorURI.getPort(); + isSSL = (monitorURI.getScheme() == "wss"); + } + #if ENABLE_SSL // hard coded but easy for now. const std::string ssl_cert_file_path = "etc/cert.pem"; @@ -233,10 +260,11 @@ int main (int argc, char **argv) const std::string ssl_cipher_list = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"; // Initialize the non-blocking socket SSL. - SslContext::initialize(ssl_cert_file_path, - ssl_key_file_path, - ssl_ca_file_path, - ssl_cipher_list); + if (isSSL) + SslContext::initialize(ssl_cert_file_path, + ssl_key_file_path, + ssl_ca_file_path, + ssl_cipher_list); #endif SocketPoll acceptPoll("accept"); @@ -244,7 +272,7 @@ int main (int argc, char **argv) // Setup listening socket with a factory for connected sockets. auto serverSocket = std::make_shared<ServerSocket>( Socket::Type::All, DumpSocketPoll, - std::make_shared<DumpSocketFactory>()); + std::make_shared<DumpSocketFactory>(isSSL)); if (!serverSocket->bind(ServerSocket::Type::Public, port)) { commit 5a94614217b1b577dce071f6fbe797cb7f930cbc Author: Michael Meeks <michael.me...@collabora.com> Date: Thu May 3 18:03:56 2018 +0100 Enable SSL in outbound, client websockets ... Switch SSL context creation to be generic rather than pure server. Change-Id: I1b750b4ddc8c607381f5541a4f4412fa16e457d4 diff --git a/net/Ssl.cpp b/net/Ssl.cpp index b56b73524..74fd75ada 100644 --- a/net/Ssl.cpp +++ b/net/Ssl.cpp @@ -66,9 +66,9 @@ SslContext::SslContext(const std::string& certFilePath, // Create the Context. We only have one, // as we don't expect/support different servers in same process. #if OPENSSL_VERSION_NUMBER >= 0x10100000L - _ctx = SSL_CTX_new(TLS_server_method()); + _ctx = SSL_CTX_new(TLS_method()); #else - _ctx = SSL_CTX_new(SSLv23_server_method()); + _ctx = SSL_CTX_new(SSLv23_method()); #endif // SSL_CTX_set_default_passwd_cb(_ctx, &privateKeyPassphraseCallback); diff --git a/net/SslSocket.hpp b/net/SslSocket.hpp index c19fedea4..44a2fa382 100644 --- a/net/SslSocket.hpp +++ b/net/SslSocket.hpp @@ -46,7 +46,12 @@ public: SSL_set_bio(_ssl, bio, bio); if (isClient) + { SSL_set_connect_state(_ssl); + if (SSL_connect(_ssl) == 0) + LOG_DBG("SslStreamSocket connect #" << getFD() << " failed "); + // else -1 is quite possibly SSL_ERROR_WANT_READ + } else // We are a server-side socket. SSL_set_accept_state(_ssl); } diff --git a/tools/WebSocketDump.cpp b/tools/WebSocketDump.cpp index 74faa310e..7f13012dd 100644 --- a/tools/WebSocketDump.cpp +++ b/tools/WebSocketDump.cpp @@ -196,7 +196,7 @@ class DumpSocketFactory final : public SocketFactory { std::shared_ptr<Socket> create(const int physicalFd) override { -#if 0 && ENABLE_SSL +#if ENABLE_SSL return StreamSocket::create<SslStreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); #else return StreamSocket::create<StreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); @@ -225,6 +225,20 @@ int main (int argc, char **argv) Log::initialize("WebSocketDump", "trace", true, false, std::map<std::string, std::string>()); +#if ENABLE_SSL + // hard coded but easy for now. + const std::string ssl_cert_file_path = "etc/cert.pem"; + const std::string ssl_key_file_path = "etc/key.pem"; + const std::string ssl_ca_file_path = "etc/ca-chain.cert.pem"; + const std::string ssl_cipher_list = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"; + + // Initialize the non-blocking socket SSL. + SslContext::initialize(ssl_cert_file_path, + ssl_key_file_path, + ssl_ca_file_path, + ssl_cipher_list); +#endif + SocketPoll acceptPoll("accept"); // Setup listening socket with a factory for connected sockets. commit 9e7dff79f3cc9140f1a81bca2ce6cf8ddd46f8f6 Author: Michael Meeks <michael.me...@collabora.com> Date: Thu May 3 17:52:35 2018 +0100 re-factor socket factories to take a client parameter. Change-Id: I0be98eb583b4f8081dd8ad23e688e93c55220367 diff --git a/net/Socket.hpp b/net/Socket.hpp index 36d1a0168..373fcc79c 100644 --- a/net/Socket.hpp +++ b/net/Socket.hpp @@ -700,7 +700,8 @@ class StreamSocket : public Socket, public std::enable_shared_from_this<StreamSo { public: /// Create a StreamSocket from native FD and take ownership of handler instance. - StreamSocket(const int fd, std::shared_ptr<SocketHandlerInterface> socketHandler) : + StreamSocket(const int fd, bool /* isClient */, + std::shared_ptr<SocketHandlerInterface> socketHandler) : Socket(fd), _socketHandler(std::move(socketHandler)), _bytesSent(0), @@ -827,10 +828,10 @@ public: /// but we can't have a shared_ptr in the ctor. template <typename TSocket> static - std::shared_ptr<TSocket> create(const int fd, std::shared_ptr<SocketHandlerInterface> handler) + std::shared_ptr<TSocket> create(const int fd, bool isClient, std::shared_ptr<SocketHandlerInterface> handler) { SocketHandlerInterface* pHandler = handler.get(); - auto socket = std::make_shared<TSocket>(fd, std::move(handler)); + auto socket = std::make_shared<TSocket>(fd, isClient, std::move(handler)); pHandler->onConnect(socket); return socket; } diff --git a/net/SslSocket.hpp b/net/SslSocket.hpp index 2f8d45cb6..c19fedea4 100644 --- a/net/SslSocket.hpp +++ b/net/SslSocket.hpp @@ -19,8 +19,9 @@ class SslStreamSocket final : public StreamSocket { public: - SslStreamSocket(const int fd, std::shared_ptr<SocketHandlerInterface> responseClient) : - StreamSocket(fd, std::move(responseClient)), + SslStreamSocket(const int fd, bool isClient, + std::shared_ptr<SocketHandlerInterface> responseClient) : + StreamSocket(fd, isClient, std::move(responseClient)), _ssl(nullptr), _sslWantsTo(SslWantsTo::Neither), _doHandshake(true) @@ -44,8 +45,10 @@ public: SSL_set_bio(_ssl, bio, bio); - // We are a server-side socket. - SSL_set_accept_state(_ssl); + if (isClient) + SSL_set_connect_state(_ssl); + else // We are a server-side socket. + SSL_set_accept_state(_ssl); } ~SslStreamSocket() diff --git a/tools/WebSocketDump.cpp b/tools/WebSocketDump.cpp index bc7a04781..74faa310e 100644 --- a/tools/WebSocketDump.cpp +++ b/tools/WebSocketDump.cpp @@ -140,7 +140,10 @@ private: if (request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) { - socket->setHandler(std::make_shared<DumpSocketHandler>(_socket, request)); + auto dumpHandler = std::make_shared<DumpSocketHandler>(_socket, request); + socket->setHandler(dumpHandler); + dumpHandler->sendMessage("version"); + dumpHandler->sendMessage("documents"); } else { @@ -194,9 +197,9 @@ class DumpSocketFactory final : public SocketFactory std::shared_ptr<Socket> create(const int physicalFd) override { #if 0 && ENABLE_SSL - return StreamSocket::create<SslStreamSocket>(physicalFd, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); + return StreamSocket::create<SslStreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); #else - return StreamSocket::create<StreamSocket>(physicalFd, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); + return StreamSocket::create<StreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); #endif } }; diff --git a/wsd/Admin.cpp b/wsd/Admin.cpp index 26f9243b7..d0e11aeb8 100644 --- a/wsd/Admin.cpp +++ b/wsd/Admin.cpp @@ -699,10 +699,10 @@ void Admin::connectToMonitor(const Poco::URI &uri) std::shared_ptr<SocketHandlerInterface> handler = std::make_shared<MonitorSocketHandler>(this); #if ENABLE_SSL if (isSSL) - socket = StreamSocket::create<SslStreamSocket>(fd, handler); + socket = StreamSocket::create<SslStreamSocket>(fd, true, handler); #endif if (!socket && !isSSL) - socket = StreamSocket::create<StreamSocket>(fd, handler); + socket = StreamSocket::create<StreamSocket>(fd, true, handler); if (socket) { diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index 4ffd8c9df..8a99428a5 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -2442,7 +2442,7 @@ class PlainSocketFactory final : public SocketFactory std::shared_ptr<Socket> socket = StreamSocket::create<StreamSocket>( - fd, std::unique_ptr<SocketHandlerInterface>{ + fd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); return socket; @@ -2459,7 +2459,9 @@ class SslSocketFactory final : public SocketFactory if (SimulatedLatencyMs > 0) fd = Delay::create(SimulatedLatencyMs, physicalFd); - return StreamSocket::create<SslStreamSocket>(fd, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); + return StreamSocket::create<SslStreamSocket>( + fd, false, std::unique_ptr<SocketHandlerInterface>{ + new ClientRequestDispatcher }); } }; #endif @@ -2469,7 +2471,7 @@ class PrisonerSocketFactory final : public SocketFactory std::shared_ptr<Socket> create(const int fd) override { // No local delay. - return StreamSocket::create<StreamSocket>(fd, std::unique_ptr<SocketHandlerInterface>{ new PrisonerRequestDispatcher }); + return StreamSocket::create<StreamSocket>(fd, false, std::unique_ptr<SocketHandlerInterface>{ new PrisonerRequestDispatcher }); } }; commit ca587f407004848937b6c8036aa21b438569b44a Author: Jan Holesovsky <ke...@collabora.com> Date: Thu May 3 17:32:31 2018 +0200 The WebSocketHandler::handleClientUpgrade() needs to handle a Response, not a request. This commit includes some fixes from Michael Meeks too. Change-Id: I25198ded9d354a44d7718071394bcccdcabcdd94 diff --git a/net/Socket.cpp b/net/Socket.cpp index 4faa4f753..c9a2968f6 100644 --- a/net/Socket.cpp +++ b/net/Socket.cpp @@ -297,8 +297,16 @@ bool StreamSocket::parseHeader(const char *clientName, return false; } } + catch (const Poco::Exception& exc) + { + LOG_DBG("parseHeader exception caught: " << exc.displayText()); + // Probably don't have enough data just yet. + // TODO: timeout if we never get enough. + return false; + } catch (const std::exception& exc) { + LOG_DBG("parseHeader exception caught."); // Probably don't have enough data just yet. // TODO: timeout if we never get enough. return false; diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp index cea442e73..856c1721a 100644 --- a/net/WebSocketHandler.hpp +++ b/net/WebSocketHandler.hpp @@ -21,6 +21,7 @@ #include <Poco/MemoryStream.h> #include <Poco/Net/HTTPRequest.h> +#include <Poco/Net/HTTPResponse.h> #include <Poco/Net/WebSocket.h> class WebSocketHandler : public SocketHandlerInterface @@ -494,52 +495,64 @@ protected: { std::shared_ptr<StreamSocket> socket = _socket.lock(); - LOG_TRC("Incoming client websocket upgrade request"); - - Poco::MemoryInputStream message(&socket->_inBuffer[0], - socket->_inBuffer.size());; - Poco::Net::HTTPRequest req; - size_t requestSize = 0; + LOG_TRC("Incoming client websocket upgrade response: " << std::string(&socket->_inBuffer[0], socket->_inBuffer.size())); bool bOk = false; - if (!socket->parseHeader("Monitor", message, req, &requestSize)) - { -// FIXME: grim hack [!] we can't parse the response for some strange reason ... -// we get an exception inside Poco ... -// return; - bOk = true; - } - else if (req.find("Upgrade") != req.end() && Poco::icompare(req["Upgrade"], "websocket") == 0) + size_t responseSize = 0; + + try { - const std::string wsKey = req.get("Sec-WebSocket-Accept", ""); - const std::string wsProtocol = req.get("Sec-WebSocket-Protocol", ""); - if (Poco::icompare(wsProtocol, "chat") != 0) - LOG_ERR("Unknown websocket protocol " << wsProtocol); - else + Poco::MemoryInputStream message(&socket->_inBuffer[0], socket->_inBuffer.size());; + Poco::Net::HTTPResponse response; + + response.read(message); + { - LOG_TRC("Accepted incoming websocket request"); - // FIXME: validate Sec-WebSocket-Accept vs. Sec-WebSocket-Key etc. - bOk = true; + static const std::string marker("\r\n\r\n"); + auto itBody = std::search(socket->_inBuffer.begin(), + socket->_inBuffer.end(), + marker.begin(), marker.end()); + + if (itBody != socket->_inBuffer.end()) + responseSize = itBody - socket->_inBuffer.begin() + marker.size(); } + + if (response.getStatus() == Poco::Net::HTTPResponse::HTTP_SWITCHING_PROTOCOLS && + response.has("Upgrade") && Poco::icompare(response.get("Upgrade"), "websocket") == 0) + { +#if 0 // SAL_DEBUG ... + const std::string wsKey = response.get("Sec-WebSocket-Accept", ""); + const std::string wsProtocol = response.get("Sec-WebSocket-Protocol", ""); + if (Poco::icompare(wsProtocol, "chat") != 0) + LOG_ERR("Unknown websocket protocol " << wsProtocol); + else +#endif + { + LOG_TRC("Accepted incoming websocket response"); + // FIXME: validate Sec-WebSocket-Accept vs. Sec-WebSocket-Key etc. + bOk = true; + } + } + } + catch (const Poco::Exception& exc) + { + LOG_DBG("handleClientUpgrade exception caught: " << exc.displayText()); + } + catch (const std::exception& exc) + { + LOG_DBG("handleClientUpgrade exception caught."); } - if (!bOk) + if (!bOk || responseSize == 0) { - LOG_ERR("Bad websocker server reply: " << req.getURI()); - - // Bad request. - std::ostringstream oss; - oss << "HTTP/1.1 400\r\n" - << "Date: " << Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n" - << "User-Agent: " << WOPI_AGENT_STRING << "\r\n" - << "Content-Length: 0\r\n" - << "\r\n"; - socket->send(oss.str()); + LOG_ERR("Bad websocker server response."); + socket->shutdown(); + return; } setWebSocket(); - socket->eraseFirstInputBytes(requestSize); + socket->eraseFirstInputBytes(responseSize); } void setWebSocket() commit 4cf2ee4fab2ffa86e5047c78bb99a6e408786847 Author: Michael Meeks <michael.me...@collabora.com> Date: Wed May 2 15:40:16 2018 +0100 Get ping/pong handling sorted with more isClient conditionality. Change-Id: I859ed5b5bcc302304e23ad3554247af920de2421 diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp index 7679e50ce..cea442e73 100644 --- a/net/WebSocketHandler.hpp +++ b/net/WebSocketHandler.hpp @@ -197,24 +197,51 @@ public: ", fin? " << fin << ", mask? " << hasMask << ", payload length: " << _wsPayload.size() << ", residual socket data: " << socket->_inBuffer.size() << " bytes."); + bool doClose = false; + switch (code) { case WSOpCode::Pong: { - _pingTimeUs = std::chrono::duration_cast<std::chrono::microseconds> - (std::chrono::steady_clock::now() - _lastPingSentTime).count(); - LOG_TRC("#" << socket->getFD() << ": Pong received: " << _pingTimeUs << " microseconds"); - break; + if (_isClient) + { + LOG_ERR("#" << socket->getFD() << ": Servers should not send pongs, only clients"); + doClose = true; + break; + } + else + { + _pingTimeUs = std::chrono::duration_cast<std::chrono::microseconds> + (std::chrono::steady_clock::now() - _lastPingSentTime).count(); + LOG_TRC("#" << socket->getFD() << ": Pong received: " << _pingTimeUs << " microseconds"); + break; + } } case WSOpCode::Ping: - LOG_ERR("#" << socket->getFD() << ": Clients should not send pings, only servers"); - // drop through -#if defined __clang__ - [[clang::fallthrough]]; -#elif defined __GNUC__ && __GNUC__ >= 7 - [[fallthrough]]; -#endif + if (_isClient) + { + auto now = std::chrono::steady_clock::now(); + _pingTimeUs = std::chrono::duration_cast<std::chrono::microseconds> + (now - _lastPingSentTime).count(); + sendPong(now, &_wsPayload[0], payloadLen, socket); + break; + } + else + { + LOG_ERR("#" << socket->getFD() << ": Clients should not send pings, only servers"); + doClose = true; + } + break; case WSOpCode::Close: + doClose = true; + break; + default: + handleMessage(fin, code, _wsPayload); + break; + } + + if (doClose) + { if (!_shuttingDown) { // Peer-initiated shutdown must be echoed. @@ -239,10 +266,6 @@ public: // TCP Close. socket->closeConnection(); - break; - default: - handleMessage(fin, code, _wsPayload); - break; } _wsPayload.clear(); @@ -270,15 +293,20 @@ public: int getPollEvents(std::chrono::steady_clock::time_point now, int & timeoutMaxMs) override { - const int timeSincePingMs = - std::chrono::duration_cast<std::chrono::milliseconds>(now - _lastPingSentTime).count(); - timeoutMaxMs = std::min(timeoutMaxMs, PingFrequencyMs - timeSincePingMs); + if (!_isClient) + { + const int timeSincePingMs = + std::chrono::duration_cast<std::chrono::milliseconds>(now - _lastPingSentTime).count(); + timeoutMaxMs = std::min(timeoutMaxMs, PingFrequencyMs - timeSincePingMs); + } return POLLIN; } /// Send a ping message - void sendPing(std::chrono::steady_clock::time_point now, - const std::shared_ptr<StreamSocket>& socket) + void sendPingOrPong(std::chrono::steady_clock::time_point now, + const char* data, const size_t len, + const WSOpCode code, + const std::shared_ptr<StreamSocket>& socket) { assert(socket && "Expected a valid socket instance."); @@ -290,15 +318,34 @@ public: return; } - LOG_TRC("#" << socket->getFD() << ": Sending ping."); + LOG_TRC("#" << socket->getFD() << ": Sending " << + (const char *)(code == WSOpCode::Ping ? " ping." : "pong.")); // FIXME: allow an empty payload. - sendMessage("", 1, WSOpCode::Ping, false); + sendMessage(data, len, code, false); _lastPingSentTime = now; } + void sendPing(std::chrono::steady_clock::time_point now, + const std::shared_ptr<StreamSocket>& socket) + { + assert(!_isClient); + sendPingOrPong(now, "", 1, WSOpCode::Ping, socket); + } + + void sendPong(std::chrono::steady_clock::time_point now, + const char* data, const size_t len, + const std::shared_ptr<StreamSocket>& socket) + { + assert(_isClient); + sendPingOrPong(now, data, len, WSOpCode::Pong, socket); + } + /// Do we need to handle a timeout ? void checkTimeout(std::chrono::steady_clock::time_point now) override { + if (_isClient) + return; + const int timeSincePingMs = std::chrono::duration_cast<std::chrono::milliseconds>(now - _lastPingSentTime).count(); if (timeSincePingMs >= PingFrequencyMs) commit 80a13a1e7bddbcd52486994009afaa980838992d Author: Michael Meeks <michael.me...@collabora.com> Date: Tue May 1 17:50:13 2018 +0100 More work on client / Monitor websocket connections. Change-Id: Ic70fe522e24f2b1863c2d9d1dd6941785510758a diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp index f157720b0..7679e50ce 100644 --- a/net/WebSocketHandler.hpp +++ b/net/WebSocketHandler.hpp @@ -19,6 +19,7 @@ #include "common/Unit.hpp" #include "Socket.hpp" +#include <Poco/MemoryStream.h> #include <Poco/Net/HTTPRequest.h> #include <Poco/Net/WebSocket.h> @@ -33,6 +34,7 @@ protected: std::vector<char> _wsPayload; std::atomic<bool> _shuttingDown; + bool _isClient; struct WSFrameMask { @@ -44,10 +46,12 @@ protected: static const int PingFrequencyMs; public: - WebSocketHandler() : + /// Perform upgrade ourselves, or select a client web socket. + WebSocketHandler(bool isClient = false) : _lastPingSentTime(std::chrono::steady_clock::now()), _pingTimeUs(0), - _shuttingDown(false) + _shuttingDown(false), + _isClient(isClient) { } @@ -59,7 +63,8 @@ public: std::chrono::milliseconds(PingFrequencyMs) - std::chrono::milliseconds(InitialPingDelayMs)), _pingTimeUs(0), - _shuttingDown(false) + _shuttingDown(false), + _isClient(false) { upgradeToWebSocket(request); } @@ -253,6 +258,8 @@ public: { LOG_ERR("No socket associated with WebSocketHandler 0x" << std::hex << this << std::dec); } + else if (_isClient && !socket->isWebSocket()) + handleClientUpgrade(); else { while (handleOneIncomingMessage(socket)) @@ -435,6 +442,59 @@ protected: setWebSocket(); } + // Handle incoming upgrade to full socket as client WS. + void handleClientUpgrade() + { + std::shared_ptr<StreamSocket> socket = _socket.lock(); + + LOG_TRC("Incoming client websocket upgrade request"); + + Poco::MemoryInputStream message(&socket->_inBuffer[0], + socket->_inBuffer.size());; + Poco::Net::HTTPRequest req; + size_t requestSize = 0; + + bool bOk = false; + if (!socket->parseHeader("Monitor", message, req, &requestSize)) + { +// FIXME: grim hack [!] we can't parse the response for some strange reason ... +// we get an exception inside Poco ... +// return; + bOk = true; + } + else if (req.find("Upgrade") != req.end() && Poco::icompare(req["Upgrade"], "websocket") == 0) + { + const std::string wsKey = req.get("Sec-WebSocket-Accept", ""); + const std::string wsProtocol = req.get("Sec-WebSocket-Protocol", ""); + if (Poco::icompare(wsProtocol, "chat") != 0) + LOG_ERR("Unknown websocket protocol " << wsProtocol); + else + { + LOG_TRC("Accepted incoming websocket request"); + // FIXME: validate Sec-WebSocket-Accept vs. Sec-WebSocket-Key etc. + bOk = true; + } + } + + if (!bOk) + { + LOG_ERR("Bad websocker server reply: " << req.getURI()); + + // Bad request. + std::ostringstream oss; + oss << "HTTP/1.1 400\r\n" + << "Date: " << Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n" + << "User-Agent: " << WOPI_AGENT_STRING << "\r\n" + << "Content-Length: 0\r\n" + << "\r\n"; + socket->send(oss.str()); + socket->shutdown(); + } + + setWebSocket(); + socket->eraseFirstInputBytes(requestSize); + } + void setWebSocket() { std::shared_ptr<StreamSocket> socket = _socket.lock(); diff --git a/tools/WebSocketDump.cpp b/tools/WebSocketDump.cpp index 87c76048b..bc7a04781 100644 --- a/tools/WebSocketDump.cpp +++ b/tools/WebSocketDump.cpp @@ -214,6 +214,11 @@ int main (int argc, char **argv) int port = 9042; (void) argc; (void) argv; + if (!UnitWSD::init(UnitWSD::UnitType::Wsd, "")) + { + throw std::runtime_error("Failed to load wsd unit test library."); + } + Log::initialize("WebSocketDump", "trace", true, false, std::map<std::string, std::string>()); diff --git a/wsd/Admin.cpp b/wsd/Admin.cpp index a4d2baec5..26f9243b7 100644 --- a/wsd/Admin.cpp +++ b/wsd/Admin.cpp @@ -269,6 +269,7 @@ void AdminSocketHandler::handleMessage(bool /* fin */, WSOpCode /* code */, } } +/// Connection from remote admin socket AdminSocketHandler::AdminSocketHandler(Admin* adminManager, const std::weak_ptr<StreamSocket>& socket, const Poco::Net::HTTPRequest& request) @@ -279,8 +280,9 @@ AdminSocketHandler::AdminSocketHandler(Admin* adminManager, { } +/// Client connection to remote amdin socket AdminSocketHandler::AdminSocketHandler(Admin* adminManager) - : WebSocketHandler(), + : WebSocketHandler(true), _admin(adminManager), _sessionId(0), _isAuthenticated(true) @@ -648,7 +650,6 @@ public: { LOG_TRC("Outbound monitor - connected"); _connecting = false; - setWebSocket(); return AdminSocketHandler::performWrites(); } }; @@ -713,7 +714,8 @@ void Admin::connectToMonitor(const Poco::URI &uri) std::ostringstream oss; oss << "GET " << uri.getHost() << " HTTP/1.1\r\n" "Connection:Upgrade\r\n" - "Sec-WebSocket-Accept:GAcwqP21iVOY2yKefQ64c0yVN5M=\r\n" + "User-Foo: Adminbits\r\n" + "Sec-WebSocket-Key: GAcwqP21iVOY2yKefQ64c0yVN5M=\r\n" "Upgrade:websocket\r\n" "Accept-Encoding:gzip, deflate, br\r\n" "Accept-Language:en\r\n" commit b483f477ddfc20d936a7e202449911c4ec6b525b Author: Michael Meeks <michael.me...@collabora.com> Date: Wed Apr 18 19:20:54 2018 +0100 Allow a 'monitor' to be connected to remotely if configured. So far monitors have the access an permissions of an authenticated admin. Change-Id: I59dfa8a646a60584a5c113ee0521e9afba4f6b76 diff --git a/loolwsd.xml.in b/loolwsd.xml.in index 33fd52c1b..4937055a3 100644 --- a/loolwsd.xml.in +++ b/loolwsd.xml.in @@ -116,4 +116,8 @@ <password desc="The password of the admin console. Deprecated on most platforms. Instead, use PAM or loolconfig to set up a secure password."></password> </admin_console> + <monitors desc="Addresses of servers we connect to on start for monitoring"> + <monitor>ws://localhost:9042/foo</monitor> + </monitors> + </config> diff --git a/net/Socket.hpp b/net/Socket.hpp index 84632a4f2..36d1a0168 100644 --- a/net/Socket.hpp +++ b/net/Socket.hpp @@ -118,7 +118,7 @@ public: } /// Create socket of the given type. - int createSocket(Type type); + static int createSocket(Type type); /// Returns the OS native socket fd. int getFD() const { return _fd; } diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp index b363364fd..f157720b0 100644 --- a/net/WebSocketHandler.hpp +++ b/net/WebSocketHandler.hpp @@ -432,7 +432,12 @@ protected: const std::string res = oss.str(); LOG_TRC("#" << socket->getFD() << ": Sending WS Upgrade response: " << res); socket->send(res); + setWebSocket(); + } + void setWebSocket() + { + std::shared_ptr<StreamSocket> socket = _socket.lock(); socket->setWebSocket(); // No need to ping right upon connection/upgrade, diff --git a/tools/WebSocketDump.cpp b/tools/WebSocketDump.cpp index a72e646c4..87c76048b 100644 --- a/tools/WebSocketDump.cpp +++ b/tools/WebSocketDump.cpp @@ -28,11 +28,15 @@ # include <SslSocket.hpp> #endif +SocketPoll DumpSocketPoll("websocket"); + // Dumps incoming websocket messages and doesn't respond. class DumpSocketHandler : public WebSocketHandler { public: - DumpSocketHandler() + DumpSocketHandler(const std::weak_ptr<StreamSocket>& socket, + const Poco::Net::HTTPRequest& request) : + WebSocketHandler(socket, request) { } @@ -63,7 +67,7 @@ private: } /// Called after successful socket reads. - void handleIncomingMessage(SocketDisposition & /* disposition */) override + void handleIncomingMessage(SocketDisposition &disposition) override { std::shared_ptr<StreamSocket> socket = _socket.lock(); std::vector<char>& in = socket->_inBuffer; @@ -135,7 +139,9 @@ private: Poco::StringTokenizer::TOK_TRIM); if (request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) - socket->setHandler(std::make_shared<DumpSocketHandler>()); + { + socket->setHandler(std::make_shared<DumpSocketHandler>(_socket, request)); + } else { Poco::Net::HTTPResponse response; @@ -143,7 +149,7 @@ private: response.setContentLength(0); LOG_INF("DumpWebSockets bad request"); socket->send(response); - socket->shutdown(); + disposition.setClosed(); } } catch (const std::exception& exc) @@ -187,7 +193,7 @@ class DumpSocketFactory final : public SocketFactory { std::shared_ptr<Socket> create(const int physicalFd) override { -#if ENABLE_SSL +#if 0 && ENABLE_SSL return StreamSocket::create<SslStreamSocket>(physicalFd, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); #else return StreamSocket::create<StreamSocket>(physicalFd, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); @@ -208,12 +214,14 @@ int main (int argc, char **argv) int port = 9042; (void) argc; (void) argv; + Log::initialize("WebSocketDump", "trace", true, false, + std::map<std::string, std::string>()); + SocketPoll acceptPoll("accept"); - SocketPoll DumpSocketPoll("websocket"); // Setup listening socket with a factory for connected sockets. auto serverSocket = std::make_shared<ServerSocket>( - Socket::Type::IPv4, DumpSocketPoll, + Socket::Type::All, DumpSocketPoll, std::make_shared<DumpSocketFactory>()); if (!serverSocket->bind(ServerSocket::Type::Public, port)) @@ -233,7 +241,7 @@ int main (int argc, char **argv) while (true) { - DumpSocketPoll.poll(1000); + DumpSocketPoll.poll(100 * 1000); } } diff --git a/wsd/Admin.cpp b/wsd/Admin.cpp index f05a80281..a4d2baec5 100644 --- a/wsd/Admin.cpp +++ b/wsd/Admin.cpp @@ -34,6 +34,7 @@ #include <Util.hpp> #include <net/Socket.hpp> +#include <net/SslSocket.hpp> #include <net/WebSocketHandler.hpp> #include <common/SigUtil.hpp> @@ -278,6 +279,14 @@ AdminSocketHandler::AdminSocketHandler(Admin* adminManager, { } +AdminSocketHandler::AdminSocketHandler(Admin* adminManager) + : WebSocketHandler(), + _admin(adminManager), + _sessionId(0), + _isAuthenticated(true) +{ +} + void AdminSocketHandler::sendTextFrame(const std::string& message) { UnitWSD::get().onAdminQueryMessage(message); @@ -613,8 +622,156 @@ void Admin::dumpState(std::ostream& os) SocketPoll::dumpState(os); } +class MonitorSocketHandler : public AdminSocketHandler +{ + bool _connecting; +public: + + MonitorSocketHandler(Admin *admin) : + AdminSocketHandler(admin), + _connecting(true) + { + } + int getPollEvents(std::chrono::steady_clock::time_point now, + int &timeoutMaxMs) override + { + if (_connecting) + { + LOG_TRC("Waiting for outbound connection to complete"); + return POLLOUT; + } + else + return AdminSocketHandler::getPollEvents(now, timeoutMaxMs); + } + + void performWrites() override + { + LOG_TRC("Outbound monitor - connected"); + _connecting = false; + setWebSocket(); + return AdminSocketHandler::performWrites(); + } +}; + +void Admin::connectToMonitor(const Poco::URI &uri) +{ + LOG_INF("Connecting to monitor " << uri.getHost() << " : " << uri.getPort() << " : " << uri.getPath()); + + // FIXME: put this in a ClientSocket class ? + // FIXME: store the address there - and ... (so on) ... + struct addrinfo* ainfo = nullptr; + struct addrinfo hints; + std::memset(&hints, 0, sizeof(hints)); + int rc = getaddrinfo(uri.getHost().c_str(), + std::to_string(uri.getPort()).c_str(), + &hints, &ainfo); + std::string canonicalName; + bool isSSL = uri.getScheme() != "ws"; +#if !ENABLE_SSL + if (isSSL) + { + LOG_ERR("Error: wss for monitor requested but SSL not compiled in."); + return; + } +#endif + + if (!rc && ainfo) + { + for (struct addrinfo* ai = ainfo; ai; ai = ai->ai_next) + { + if (ai->ai_canonname) + canonicalName = ai->ai_canonname; + + if (ai->ai_addrlen && ai->ai_addr) + { + int fd = socket(ai->ai_addr->sa_family, SOCK_STREAM | SOCK_NONBLOCK, 0); + int res = connect(fd, ai->ai_addr, ai->ai_addrlen); + // FIXME: SSL sockets presumably need some setup, checking etc. and ... =) + if (fd < 0 || (res < 0 && errno != EINPROGRESS)) + { + LOG_ERR("Failed to connect to " << uri.getHost()); + close(fd); + } + else + { + std::shared_ptr<StreamSocket> socket; + std::shared_ptr<SocketHandlerInterface> handler = std::make_shared<MonitorSocketHandler>(this); +#if ENABLE_SSL + if (isSSL) + socket = StreamSocket::create<SslStreamSocket>(fd, handler); +#endif + if (!socket && !isSSL) + socket = StreamSocket::create<StreamSocket>(fd, handler); + + if (socket) + { + LOG_DBG("Connected to monitor " << uri.getHost() << " #" << socket->getFD()); + + // cf. WebSocketHandler::upgradeToWebSocket (?) + // send Sec-WebSocket-Key: <hmm> ... Sec-WebSocket-Protocol: chat, Sec-WebSocket-Version: 13 + + std::ostringstream oss; + oss << "GET " << uri.getHost() << " HTTP/1.1\r\n" + "Connection:Upgrade\r\n" + "Sec-WebSocket-Accept:GAcwqP21iVOY2yKefQ64c0yVN5M=\r\n" + "Upgrade:websocket\r\n" + "Accept-Encoding:gzip, deflate, br\r\n" + "Accept-Language:en\r\n" + "Cache-Control:no-cache\r\n" + "Pragma:no-cache\r\n" + "Sec-WebSocket-Extensions:permessage-deflate; client_max_window_bits\r\n" + "Sec-WebSocket-Key:fxTaWTEMVhq1PkWsMoLxGw==\r\n" + "Sec-WebSocket-Version:13\r\n" + "User-Agent: " << WOPI_AGENT_STRING << "\r\n" + "\r\n"; + socket->send(oss.str()); ... etc. - the rest is truncated _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits