2015-04-13 04:09:02 -05:00
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2015-03-04 17:14:04 -06:00
/*
* 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/.
*
* Parts of this file is covered by :
Boost Software License - Version 1.0 - August 17 th , 2003
Permission is hereby granted , free of charge , to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license ( the " Software " ) to use , reproduce , display , distribute ,
execute , and transmit the Software , and to prepare derivative works of the
Software , and to permit third - parties to whom the Software is furnished to
do so , all subject to the following :
The copyright notices in the Software and this entire statement , including
the above license grant , this restriction and the following disclaimer ,
must be included in all copies of the Software , in whole or in part , and
all derivative works of the Software , unless such copies or derivative
works are solely in the form of machine - executable object code generated by
a source language processor .
THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
FITNESS FOR A PARTICULAR PURPOSE , TITLE AND NON - INFRINGEMENT . IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY , WHETHER IN CONTRACT , TORT OR OTHERWISE ,
ARISING FROM , OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE .
*/
2015-04-21 08:51:28 -05:00
# include "config.h"
2015-04-10 07:20:04 -05:00
// This is the main source for the loolwsd program. LOOL uses several loolwsd processes: one main
// parent process that listens on the TCP port and accepts connections from LOOL clients, and a
// number of child processes, each which handles a viewing (editing) session for one document.
2015-04-16 11:15:40 -05:00
# include <errno.h>
2015-10-13 12:05:42 -05:00
# include <locale.h>
2015-04-08 09:22:42 -05:00
# include <unistd.h>
2015-04-16 11:15:40 -05:00
# include <sys/types.h>
# include <sys/wait.h>
2015-08-08 06:55:54 -05:00
# include <sys/prctl.h>
2015-04-16 11:15:40 -05:00
2015-07-13 09:13:06 -05:00
# include <ftw.h>
# include <utime.h>
2015-04-16 11:15:40 -05:00
# include <cassert>
2015-03-28 06:55:35 -05:00
# include <cstdlib>
2015-04-14 09:50:38 -05:00
# include <cstring>
2015-03-04 17:14:04 -06:00
# include <iostream>
2015-07-13 09:13:06 -05:00
# include <sstream>
# include <mutex>
2015-03-04 17:14:04 -06:00
2016-02-24 01:39:23 -06:00
# include <Poco/Environment.h>
2015-05-08 07:46:10 -05:00
# include <Poco/Exception.h>
2015-04-16 11:15:40 -05:00
# include <Poco/File.h>
2016-02-24 01:39:23 -06:00
# include <Poco/FileStream.h>
# include <Poco/Mutex.h>
2015-10-16 10:38:24 -05:00
# include <Poco/Net/HTMLForm.h>
2015-03-04 17:14:04 -06:00
# include <Poco/Net/HTTPClientSession.h>
# include <Poco/Net/HTTPRequest.h>
# include <Poco/Net/HTTPRequestHandler.h>
# include <Poco/Net/HTTPRequestHandlerFactory.h>
# include <Poco/Net/HTTPServer.h>
# include <Poco/Net/HTTPServerParams.h>
# include <Poco/Net/HTTPServerParams.h>
# include <Poco/Net/HTTPServerRequest.h>
# include <Poco/Net/HTTPServerResponse.h>
2015-10-16 10:38:24 -05:00
# include <Poco/Net/MessageHeader.h>
2016-02-24 01:39:23 -06:00
# include <Poco/Net/Net.h>
2015-03-04 17:14:04 -06:00
# include <Poco/Net/NetException.h>
2015-10-16 10:38:24 -05:00
# include <Poco/Net/PartHandler.h>
2015-03-04 17:14:04 -06:00
# include <Poco/Net/ServerSocket.h>
2015-05-08 13:24:46 -05:00
# include <Poco/Net/SocketAddress.h>
2015-03-04 17:14:04 -06:00
# include <Poco/Net/WebSocket.h>
2015-04-16 11:15:40 -05:00
# include <Poco/Path.h>
2015-03-27 11:23:27 -05:00
# include <Poco/Process.h>
2016-02-24 01:39:23 -06:00
# include <Poco/StreamCopier.h>
2015-05-18 03:21:30 -05:00
# include <Poco/StringTokenizer.h>
2016-02-24 01:39:23 -06:00
# include <Poco/TemporaryFile.h>
# include <Poco/ThreadLocal.h>
2015-05-29 00:49:49 -05:00
# include <Poco/ThreadPool.h>
2016-02-24 01:39:23 -06:00
# include <Poco/URI.h>
2015-03-04 17:14:04 -06:00
# include <Poco/Util/HelpFormatter.h>
# include <Poco/Util/Option.h>
2015-04-08 09:22:42 -05:00
# include <Poco/Util/OptionException.h>
2015-03-04 17:14:04 -06:00
# include <Poco/Util/OptionSet.h>
# include <Poco/Util/ServerApplication.h>
2016-02-15 17:05:24 -06:00
# include "Admin.hpp"
# include "Auth.hpp"
2016-02-24 01:39:23 -06:00
# include "ChildProcessSession.hpp"
# include "Common.hpp"
2015-04-14 09:50:38 -05:00
# include "LOOLProtocol.hpp"
2015-03-09 03:01:30 -05:00
# include "LOOLSession.hpp"
2015-03-17 18:56:15 -05:00
# include "LOOLWSD.hpp"
2016-02-24 01:39:23 -06:00
# include "MasterProcessSession.hpp"
2016-01-06 07:38:21 -06:00
# include "QueueHandler.hpp"
2015-03-17 18:56:15 -05:00
# include "Util.hpp"
2015-03-04 17:14:04 -06:00
2015-04-14 09:50:38 -05:00
using namespace LOOLProtocol ;
2016-02-24 01:39:23 -06:00
using Poco : : Environment ;
2015-05-29 00:49:49 -05:00
using Poco : : Exception ;
2015-04-16 11:15:40 -05:00
using Poco : : File ;
2016-02-24 01:39:23 -06:00
using Poco : : FileOutputStream ;
2015-05-29 00:49:49 -05:00
using Poco : : IOException ;
2016-02-24 01:39:23 -06:00
using Poco : : Net : : HTMLForm ;
2015-03-04 17:14:04 -06:00
using Poco : : Net : : HTTPClientSession ;
using Poco : : Net : : HTTPRequest ;
using Poco : : Net : : HTTPRequestHandler ;
using Poco : : Net : : HTTPRequestHandlerFactory ;
using Poco : : Net : : HTTPResponse ;
using Poco : : Net : : HTTPServer ;
using Poco : : Net : : HTTPServerParams ;
using Poco : : Net : : HTTPServerRequest ;
using Poco : : Net : : HTTPServerResponse ;
2016-02-24 01:39:23 -06:00
using Poco : : Net : : MessageHeader ;
using Poco : : Net : : NameValueCollection ;
using Poco : : Net : : PartHandler ;
2015-03-04 17:14:04 -06:00
using Poco : : Net : : ServerSocket ;
2016-02-24 01:39:23 -06:00
using Poco : : Net : : Socket ;
2015-05-08 13:24:46 -05:00
using Poco : : Net : : SocketAddress ;
2015-03-04 17:14:04 -06:00
using Poco : : Net : : WebSocket ;
using Poco : : Net : : WebSocketException ;
2015-04-16 11:15:40 -05:00
using Poco : : Path ;
using Poco : : Process ;
2016-02-24 01:39:23 -06:00
using Poco : : ProcessHandle ;
using Poco : : Random ;
2015-03-16 06:59:40 -05:00
using Poco : : Runnable ;
2016-02-24 01:39:23 -06:00
using Poco : : StreamCopier ;
2015-05-18 03:21:30 -05:00
using Poco : : StringTokenizer ;
2016-02-24 01:39:23 -06:00
using Poco : : Timespan ;
using Poco : : TemporaryFile ;
2015-03-16 06:59:40 -05:00
using Poco : : Thread ;
2016-02-24 01:39:23 -06:00
using Poco : : ThreadLocal ;
2015-05-29 00:49:49 -05:00
using Poco : : ThreadPool ;
2016-02-24 01:39:23 -06:00
using Poco : : URI ;
2015-03-04 17:14:04 -06:00
using Poco : : Util : : Application ;
using Poco : : Util : : HelpFormatter ;
2015-04-08 09:22:42 -05:00
using Poco : : Util : : IncompatibleOptionsException ;
using Poco : : Util : : MissingOptionException ;
2015-03-04 17:14:04 -06:00
using Poco : : Util : : Option ;
using Poco : : Util : : OptionSet ;
using Poco : : Util : : ServerApplication ;
2016-01-06 23:16:47 -06:00
// Document management mutex.
2016-01-12 17:11:04 -06:00
std : : mutex DocumentURI : : DocumentURIMutex ;
2016-01-06 23:16:47 -06:00
2015-10-16 10:38:24 -05:00
/// Handles the filename part of the convert-to POST request payload.
2016-02-24 01:39:23 -06:00
class ConvertToPartHandler : public PartHandler
2015-10-16 10:38:24 -05:00
{
2015-10-19 09:03:16 -05:00
std : : string & _filename ;
2015-10-16 10:38:24 -05:00
public :
2015-10-20 08:00:05 -05:00
ConvertToPartHandler ( std : : string & filename )
: _filename ( filename )
2015-10-16 10:38:24 -05:00
{
}
2016-02-24 01:39:23 -06:00
virtual void handlePart ( const MessageHeader & header , std : : istream & stream ) override
2015-10-16 10:38:24 -05:00
{
2015-10-20 08:00:05 -05:00
// Extract filename and put it to a temporary directory.
2015-10-19 09:03:16 -05:00
std : : string disp ;
2016-02-24 01:39:23 -06:00
NameValueCollection params ;
2015-10-19 09:03:16 -05:00
if ( header . has ( " Content-Disposition " ) )
{
std : : string cd = header . get ( " Content-Disposition " ) ;
2016-02-24 01:39:23 -06:00
MessageHeader : : splitParameters ( cd , disp , params ) ;
2015-10-19 09:03:16 -05:00
}
2015-10-22 10:27:29 -05:00
2015-10-20 08:00:05 -05:00
if ( ! params . has ( " filename " ) )
return ;
2015-10-19 09:03:16 -05:00
2016-02-24 01:39:23 -06:00
Path tempPath = Path : : forDirectory ( TemporaryFile ( ) . tempName ( ) + Path : : separator ( ) ) ;
2015-10-20 08:00:05 -05:00
File ( tempPath ) . createDirectories ( ) ;
tempPath . setFileName ( params . get ( " filename " ) ) ;
_filename = tempPath . toString ( ) ;
// Copy the stream to _filename.
std : : ofstream fileStream ;
fileStream . open ( _filename ) ;
2016-02-24 01:39:23 -06:00
StreamCopier : : copyStream ( stream , fileStream ) ;
2015-10-20 08:00:05 -05:00
fileStream . close ( ) ;
2015-10-16 10:38:24 -05:00
}
} ;
2016-01-05 20:29:12 -06:00
// Synchronously process WebSocket requests and dispatch to handler.
// Handler returns false to end.
void SocketProcessor ( std : : shared_ptr < WebSocket > ws ,
HTTPServerResponse & response ,
2016-01-05 21:32:58 -06:00
std : : function < bool ( const char * data , const int size , const bool singleLine ) > handler )
2015-03-04 17:14:04 -06:00
{
2016-01-05 21:32:58 -06:00
Log : : info ( " Starting Socket Processor. " ) ;
2016-02-24 01:39:23 -06:00
const Timespan waitTime ( POLL_TIMEOUT_MS * 1000 ) ;
2016-01-05 20:29:12 -06:00
try
{
2016-01-05 21:32:58 -06:00
int flags = 0 ;
int n = 0 ;
2016-01-05 20:29:12 -06:00
ws - > setReceiveTimeout ( 0 ) ;
do
{
char buffer [ 200000 ] ; //FIXME: Dynamic?
2016-01-21 07:54:56 -06:00
if ( ws - > poll ( waitTime , Socket : : SELECT_READ ) )
2016-01-05 20:29:12 -06:00
{
n = ws - > receiveFrame ( buffer , sizeof ( buffer ) , flags ) ;
if ( ( flags & WebSocket : : FRAME_OP_BITMASK ) = = WebSocket : : FRAME_OP_PING )
{
2016-01-21 08:04:16 -06:00
// Echo back the ping payload as pong.
2016-01-23 16:35:16 -06:00
// Technically, we should send back a PONG control frame.
// However Firefox (probably) or Node.js (possibly) doesn't
// like that and closes the socket when we do.
// Echoing the payload as a normal frame works with Firefox.
ws - > sendFrame ( buffer , n /*, WebSocket::FRAME_OP_PONG*/ ) ;
2016-01-05 20:29:12 -06:00
}
else if ( ( flags & WebSocket : : FRAME_OP_BITMASK ) = = WebSocket : : FRAME_OP_PONG )
{
2016-01-21 08:04:16 -06:00
// In case we do send pings in the future.
2016-01-05 20:29:12 -06:00
}
2016-01-21 08:00:58 -06:00
else if ( n < = 0 )
2016-01-05 20:29:12 -06:00
{
2016-01-21 08:00:58 -06:00
// Connection closed.
2016-01-23 16:35:16 -06:00
Log : : warn ( ) < < " Received " < < n
< < " bytes. Connection closed. Flags: "
< < std : : hex < < flags < < Log : : end ;
2016-01-21 08:00:58 -06:00
break ;
}
2016-02-23 12:03:43 -06:00
else if ( ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE )
2016-01-21 08:00:58 -06:00
{
assert ( n > 0 ) ;
2016-01-05 20:29:12 -06:00
const std : : string firstLine = getFirstLine ( buffer , n ) ;
2016-01-27 03:05:54 -06:00
StringTokenizer tokens ( firstLine , " " , StringTokenizer : : TOK_IGNORE_EMPTY | StringTokenizer : : TOK_TRIM ) ;
int size ;
2016-01-05 20:29:12 -06:00
if ( firstLine = = " eof " )
2016-01-05 21:32:58 -06:00
{
2016-01-23 16:32:09 -06:00
Log : : info ( " Received EOF. Finishing. " ) ;
2016-01-05 20:29:12 -06:00
break ;
2016-01-05 21:32:58 -06:00
}
2016-01-05 20:29:12 -06:00
2016-01-18 09:54:22 -06:00
if ( ( flags & WebSocket : : FrameFlags : : FRAME_FLAG_FIN ) ! = WebSocket : : FrameFlags : : FRAME_FLAG_FIN )
{
// One WS message split into multiple frames.
std : : vector < char > message ( buffer , buffer + n ) ;
while ( true )
{
n = ws - > receiveFrame ( buffer , sizeof ( buffer ) , flags ) ;
2016-02-23 12:03:43 -06:00
if ( n < = 0 | | ( flags & WebSocket : : FRAME_OP_BITMASK ) = = WebSocket : : FRAME_OP_CLOSE )
break ;
2016-01-18 09:54:22 -06:00
message . insert ( message . end ( ) , buffer , buffer + n ) ;
if ( ( flags & WebSocket : : FrameFlags : : FRAME_FLAG_FIN ) = = WebSocket : : FrameFlags : : FRAME_FLAG_FIN )
{
// No more frames: invoke the handler. Assume
// for now that this is always a multi-line
// message.
handler ( message . data ( ) , message . size ( ) , false ) ;
break ;
}
}
}
2016-01-27 03:05:54 -06:00
else if ( tokens . count ( ) = = 2 & &
tokens [ 0 ] = = " nextmessage: " & & getTokenInteger ( tokens [ 1 ] , " size " , size ) & & size > 0 )
2016-01-05 20:29:12 -06:00
{
// Check if it is a "nextmessage:" and in that case read the large
// follow-up message separately, and handle that only.
2016-01-05 21:32:58 -06:00
2016-01-27 03:05:54 -06:00
char largeBuffer [ size ] ; //FIXME: Security risk! Flooding may segfault us.
2016-01-05 20:29:12 -06:00
2016-01-27 03:05:54 -06:00
n = ws - > receiveFrame ( largeBuffer , size , flags ) ;
2016-02-23 12:03:43 -06:00
if ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE & & ! handler ( largeBuffer , n , false ) )
2016-01-05 20:29:12 -06:00
{
2016-01-27 03:05:54 -06:00
Log : : info ( " Socket handler flagged for finishing. " ) ;
break ;
2016-01-05 20:29:12 -06:00
}
}
2016-01-27 03:05:54 -06:00
else if ( firstLine . size ( ) = = static_cast < std : : string : : size_type > ( n ) )
{
handler ( firstLine . c_str ( ) , firstLine . size ( ) , true ) ;
}
else if ( ! handler ( buffer , n , false ) )
{
Log : : info ( " Socket handler flagged for finishing. " ) ;
break ;
}
2016-01-05 20:29:12 -06:00
}
}
}
while ( ! TerminationFlag & &
2016-01-06 07:56:50 -06:00
( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE ) ;
2016-01-05 21:32:58 -06:00
Log : : debug ( ) < < " Finishing SocketProcessor. TerminationFlag: " < < TerminationFlag
2016-01-21 07:54:56 -06:00
< < " , payload size: " < < n
2016-01-12 16:47:50 -06:00
< < " , flags: " < < std : : hex < < flags < < Log : : end ;
2016-01-05 20:29:12 -06:00
}
catch ( const WebSocketException & exc )
2015-03-04 17:14:04 -06:00
{
2016-01-05 20:29:12 -06:00
Log : : error ( " RequestHandler::handleRequest(), WebSocketException: " + exc . message ( ) ) ;
switch ( exc . code ( ) )
{
case WebSocket : : WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION :
response . set ( " Sec-WebSocket-Version " , WebSocket : : WEBSOCKET_VERSION ) ;
// fallthrough
case WebSocket : : WS_ERR_NO_HANDSHAKE :
case WebSocket : : WS_ERR_HANDSHAKE_NO_VERSION :
case WebSocket : : WS_ERR_HANDSHAKE_NO_KEY :
response . setStatusAndReason ( HTTPResponse : : HTTP_BAD_REQUEST ) ;
response . setContentLength ( 0 ) ;
response . send ( ) ;
break ;
}
2015-03-04 17:14:04 -06:00
}
2016-01-05 21:32:58 -06:00
Log : : info ( " Finished Socket Processor. " ) ;
2016-01-05 20:29:12 -06:00
}
/// Handle a public connection from a client.
class ClientRequestHandler : public HTTPRequestHandler
{
2016-01-24 13:48:09 -06:00
private :
2015-03-04 17:14:04 -06:00
2016-01-24 13:48:09 -06:00
void handlePostRequest ( HTTPServerRequest & request , HTTPServerResponse & response , const std : : string & id )
2015-03-04 17:14:04 -06:00
{
2016-01-24 13:48:09 -06:00
Log : : info ( " Post request. " ) ;
StringTokenizer tokens ( request . getURI ( ) , " /? " ) ;
if ( tokens . count ( ) > = 2 & & tokens [ 1 ] = = " convert-to " )
2015-03-04 17:14:04 -06:00
{
2016-01-24 13:48:09 -06:00
Log : : info ( " Conversion request. " ) ;
std : : string fromPath ;
ConvertToPartHandler handler ( fromPath ) ;
2016-02-24 01:39:23 -06:00
HTMLForm form ( request , request . stream ( ) , handler ) ;
2016-01-24 13:48:09 -06:00
std : : string format ;
if ( form . has ( " format " ) )
format = form . get ( " format " ) ;
bool sent = false ;
if ( ! fromPath . empty ( ) )
2015-10-16 10:38:24 -05:00
{
2016-01-24 13:48:09 -06:00
if ( ! format . empty ( ) )
2015-10-16 10:38:24 -05:00
{
2016-01-24 13:48:09 -06:00
// Load the document.
std : : shared_ptr < WebSocket > ws ;
const LOOLSession : : Kind kind = LOOLSession : : Kind : : ToClient ;
auto session = std : : make_shared < MasterProcessSession > ( id , kind , ws ) ;
const std : : string filePrefix ( " file:// " ) ;
std : : string encodedFrom ;
2016-02-22 08:30:42 -06:00
URI : : encode ( filePrefix + fromPath , " " , encodedFrom ) ;
2016-01-24 13:48:09 -06:00
const std : : string load = " load url= " + encodedFrom ;
session - > handleInput ( load . data ( ) , load . size ( ) ) ;
// Convert it to the requested format.
Path toPath ( fromPath ) ;
toPath . setExtension ( format ) ;
std : : string toJailURL = filePrefix + JailedDocumentRoot + toPath . getFileName ( ) ;
std : : string encodedTo ;
2016-02-22 08:30:42 -06:00
URI : : encode ( toJailURL , " " , encodedTo ) ;
2016-01-23 18:41:01 -06:00
std : : string saveas = " saveas url= " + encodedTo + " format= " + format + " options= " ;
2016-01-24 13:48:09 -06:00
session - > handleInput ( saveas . data ( ) , saveas . size ( ) ) ;
2016-01-23 18:41:01 -06:00
2016-01-24 13:48:09 -06:00
std : : string toURL = session - > getSaveAs ( ) ;
std : : string resultingURL ;
URI : : decode ( toURL , resultingURL ) ;
2016-01-23 18:41:01 -06:00
2016-01-24 13:48:09 -06:00
// Send it back to the client.
if ( resultingURL . find ( filePrefix ) = = 0 )
resultingURL = resultingURL . substr ( filePrefix . length ( ) ) ;
if ( ! resultingURL . empty ( ) )
{
const std : : string mimeType = " application/octet-stream " ;
response . sendFile ( resultingURL , mimeType ) ;
sent = true ;
2016-01-23 18:41:01 -06:00
}
2015-10-20 08:35:43 -05:00
}
2016-01-23 18:41:01 -06:00
2016-01-24 13:48:09 -06:00
// Clean up the temporary directory the HTMLForm ctor created.
Path tempDirectory ( fromPath ) ;
tempDirectory . setFileName ( " " ) ;
Util : : removeFile ( tempDirectory , /*recursive=*/ true ) ;
2015-10-16 10:38:24 -05:00
}
2016-01-24 13:48:09 -06:00
if ( ! sent )
2015-10-22 10:27:29 -05:00
{
2016-01-24 13:48:09 -06:00
response . setStatus ( HTTPResponse : : HTTP_BAD_REQUEST ) ;
response . setContentLength ( 0 ) ;
response . send ( ) ;
}
}
else if ( tokens . count ( ) > = 2 & & tokens [ 1 ] = = " insertfile " )
{
Log : : info ( " Insert file request. " ) ;
response . set ( " Access-Control-Allow-Origin " , " * " ) ;
response . set ( " Access-Control-Allow-Methods " , " GET, POST, OPTIONS " ) ;
response . set ( " Access-Control-Allow-Headers " , " Origin, X-Requested-With, Content-Type, Accept " ) ;
2015-10-22 10:27:29 -05:00
2016-01-24 13:48:09 -06:00
std : : string tmpPath ;
ConvertToPartHandler handler ( tmpPath ) ;
2016-02-24 01:39:23 -06:00
HTMLForm form ( request , request . stream ( ) , handler ) ;
2015-10-22 10:27:29 -05:00
2016-01-24 13:48:09 -06:00
bool goodRequest = form . has ( " childid " ) & & form . has ( " name " ) ;
std : : string formChildid ( form . get ( " childid " ) ) ;
std : : string formName ( form . get ( " name " ) ) ;
2016-01-13 09:35:55 -06:00
2016-01-24 13:48:09 -06:00
// protect against attempts to inject something funny here
if ( goodRequest & & formChildid . find ( ' / ' ) ! = std : : string : : npos & & formName . find ( ' / ' ) ! = std : : string : : npos )
goodRequest = false ;
2016-01-13 09:35:55 -06:00
2016-01-24 13:48:09 -06:00
if ( goodRequest )
{
try
2016-01-13 09:35:55 -06:00
{
2016-01-24 13:48:09 -06:00
Log : : info ( ) < < " Perform insertfile: " < < formChildid < < " , " < < formName < < Log : : end ;
const std : : string dirPath = LOOLWSD : : ChildRoot + formChildid
+ JailedDocumentRoot + " insertfile " ;
File ( dirPath ) . createDirectories ( ) ;
std : : string fileName = dirPath + Path : : separator ( ) + form . get ( " name " ) ;
File ( tmpPath ) . moveTo ( fileName ) ;
response . setStatus ( HTTPResponse : : HTTP_OK ) ;
response . send ( ) ;
2015-10-22 10:27:29 -05:00
}
2016-01-24 13:48:09 -06:00
catch ( const IOException & exc )
2015-10-22 10:27:29 -05:00
{
2016-01-24 13:48:09 -06:00
Log : : info ( ) < < " IOException: " < < exc . message ( ) < < Log : : end ;
2015-10-22 10:27:29 -05:00
response . setStatus ( HTTPResponse : : HTTP_BAD_REQUEST ) ;
response . send ( ) ;
}
}
2016-01-24 13:48:09 -06:00
else
2015-10-09 07:55:49 -05:00
{
2016-01-24 13:48:09 -06:00
response . setStatus ( HTTPResponse : : HTTP_BAD_REQUEST ) ;
response . send ( ) ;
}
}
else if ( tokens . count ( ) > = 4 )
{
Log : : info ( " File download request. " ) ;
// The user might request a file to download
const std : : string dirPath = LOOLWSD : : ChildRoot + tokens [ 1 ]
+ JailedDocumentRoot + tokens [ 2 ] ;
std : : string fileName ;
URI : : decode ( tokens [ 3 ] , fileName ) ;
const std : : string filePath = dirPath + Path : : separator ( ) + fileName ;
Log : : info ( " HTTP request for: " + filePath ) ;
File file ( filePath ) ;
if ( file . exists ( ) )
{
response . set ( " Access-Control-Allow-Origin " , " * " ) ;
2016-02-24 01:39:23 -06:00
HTMLForm form ( request ) ;
2016-01-24 13:48:09 -06:00
std : : string mimeType = " application/octet-stream " ;
if ( form . has ( " mime_type " ) )
mimeType = form . get ( " mime_type " ) ;
response . sendFile ( filePath , mimeType ) ;
Util : : removeFile ( dirPath , true ) ;
2015-10-09 07:55:49 -05:00
}
else
{
2016-01-24 13:48:09 -06:00
response . setStatus ( HTTPResponse : : HTTP_NOT_FOUND ) ;
2015-10-09 07:55:49 -05:00
response . setContentLength ( 0 ) ;
response . send ( ) ;
}
2015-03-04 17:14:04 -06:00
}
2016-01-24 13:48:09 -06:00
else
2015-03-04 17:14:04 -06:00
{
2016-01-24 13:48:09 -06:00
Log : : info ( " Bad request. " ) ;
response . setStatus ( HTTPResponse : : HTTP_BAD_REQUEST ) ;
response . setContentLength ( 0 ) ;
response . send ( ) ;
}
}
2015-04-21 07:06:41 -05:00
2016-02-15 17:05:24 -06:00
bool authenticate ( HTTPServerRequest & request , HTTPServerResponse & response , const std : : string & id )
{
( void ) response ;
Log : : info ( " Authenticating Get request processor for session [ " + id + " ]. " ) ;
std : : string token ;
2016-02-24 01:39:23 -06:00
for ( auto & pair : URI ( request . getURI ( ) ) . getQueryParameters ( ) )
2016-02-15 17:05:24 -06:00
{
if ( pair . first = = " token " )
{
token = pair . second ;
break ;
}
}
//TODO:
//AuthAgent.verify(token);
return true ;
}
2016-01-24 13:48:09 -06:00
void handleGetRequest ( HTTPServerRequest & request , HTTPServerResponse & response , const std : : string & id )
{
2016-01-26 07:53:01 -06:00
Log : : info ( " Starting Get request processor for session [ " + id + " ]. " ) ;
2016-02-15 17:05:24 -06:00
//TODO: Authenticate the caller.
// authenticate(request, response);
2016-02-24 01:39:23 -06:00
NameValueCollection cookies ;
2016-02-15 17:05:24 -06:00
request . getCookies ( cookies ) ;
Log : : info ( " Cookie: " + cookies . get ( " PHPSESSID " , " " ) ) ;
2016-01-24 13:48:09 -06:00
auto ws = std : : make_shared < WebSocket > ( request , response ) ;
auto session = std : : make_shared < MasterProcessSession > ( id , LOOLSession : : Kind : : ToClient , ws ) ;
2016-01-06 07:38:21 -06:00
2016-01-24 13:48:09 -06:00
// For ToClient sessions, we store incoming messages in a queue and have a separate
// thread that handles them. This is so that we can empty the queue when we get a
// "canceltiles" message.
BasicTileQueue queue ;
QueueHandler handler ( queue , session , " wsd_queue_ " + session - > getId ( ) ) ;
2016-01-05 20:29:12 -06:00
2016-01-24 13:48:09 -06:00
Thread queueHandlerThread ;
queueHandlerThread . start ( handler ) ;
SocketProcessor ( ws , response , [ & session , & queue ] ( const char * data , const int size , const bool singleLine )
{
// FIXME: There is a race here when a request A gets in the queue and
// is processed _after_ a later request B, because B gets processed
// synchronously and A is waiting in the queue thread.
// The fix is to push everything into the queue
// (i.e. change MessageQueue to vector<char>).
const std : : string firstLine = getFirstLine ( data , size ) ;
if ( singleLine | | firstLine . find ( " paste " ) = = 0 )
2015-12-27 21:47:39 -06:00
{
2016-01-24 13:48:09 -06:00
queue . put ( std : : string ( data , size ) ) ;
return true ;
}
else
{
return session - > handleInput ( data , size ) ;
}
} ) ;
2015-06-05 08:12:06 -05:00
2016-01-26 07:53:01 -06:00
Log : : info ( " Get request processor for session [ " + id + " ] finished. Clearing and joining the queue. " ) ;
2016-01-24 13:48:09 -06:00
queue . clear ( ) ;
queue . put ( " eof " ) ;
queueHandlerThread . join ( ) ;
}
public :
void handleRequest ( HTTPServerRequest & request , HTTPServerResponse & response ) override
{
const auto id = LOOLWSD : : GenSessionId ( ) ;
const std : : string thread_name = " client_ws_ " + id ;
if ( prctl ( PR_SET_NAME , reinterpret_cast < unsigned long > ( thread_name . c_str ( ) ) , 0 , 0 , 0 ) ! = 0 )
Log : : error ( " Cannot set thread name to " + thread_name + " . " ) ;
2016-02-29 06:25:12 -06:00
2016-01-24 13:48:09 -06:00
Log : : debug ( " Thread [ " + thread_name + " ] started. " ) ;
try
{
if ( ! ( request . find ( " Upgrade " ) ! = request . end ( ) & & Poco : : icompare ( request [ " Upgrade " ] , " websocket " ) = = 0 ) )
{
handlePostRequest ( request , response , id ) ;
}
else
{
2016-02-15 17:05:24 -06:00
//authenticate(request, response, id);
2016-01-24 13:48:09 -06:00
handleGetRequest ( request , response , id ) ;
}
2016-01-05 20:29:12 -06:00
}
2016-01-06 08:13:21 -06:00
catch ( const Exception & exc )
2016-01-05 20:29:12 -06:00
{
2016-01-06 08:13:21 -06:00
Log : : error ( ) < < " Error: " < < exc . displayText ( )
< < ( exc . nested ( ) ? " ( " + exc . nested ( ) - > displayText ( ) + " ) " : " " )
< < Log : : end ;
}
catch ( const std : : exception & exc )
{
Log : : error ( std : : string ( " Exception: " ) + exc . what ( ) ) ;
}
catch ( . . . )
{
Log : : error ( " Unexpected Exception. " ) ;
2016-01-05 20:29:12 -06:00
}
2015-04-21 07:06:41 -05:00
2016-01-05 20:29:12 -06:00
Log : : debug ( " Thread [ " + thread_name + " ] finished. " ) ;
}
} ;
2015-11-25 20:23:08 -06:00
2016-01-05 20:29:12 -06:00
/// Handle requests from prisoners (internal).
class PrisonerRequestHandler : public HTTPRequestHandler
{
public :
2015-04-21 07:06:41 -05:00
2016-01-05 20:29:12 -06:00
void handleRequest ( HTTPServerRequest & request , HTTPServerResponse & response ) override
{
assert ( request . serverAddress ( ) . port ( ) = = MASTER_PORT_NUMBER ) ;
2016-01-10 09:35:28 -06:00
assert ( request . getURI ( ) . find ( LOOLWSD : : CHILD_URI ) = = 0 ) ;
2015-06-09 10:04:46 -05:00
2016-01-10 09:35:28 -06:00
std : : string thread_name = " prison_ws_ " ;
try
{
const auto index = request . getURI ( ) . find_last_of ( ' / ' ) ;
const auto id = request . getURI ( ) . substr ( index + 1 ) ;
thread_name + = id ;
2016-01-05 20:29:12 -06:00
2016-01-10 09:35:28 -06:00
if ( prctl ( PR_SET_NAME , reinterpret_cast < unsigned long > ( thread_name . c_str ( ) ) , 0 , 0 , 0 ) ! = 0 )
Log : : error ( " Cannot set thread name to " + thread_name + " . " ) ;
2016-02-29 06:25:12 -06:00
2016-01-10 09:35:28 -06:00
Log : : debug ( " Thread [ " + thread_name + " ] started. " ) ;
2015-12-30 11:10:31 -06:00
2016-01-05 20:29:12 -06:00
auto ws = std : : make_shared < WebSocket > ( request , response ) ;
auto session = std : : make_shared < MasterProcessSession > ( id , LOOLSession : : Kind : : ToPrisoner , ws ) ;
2016-01-05 21:32:58 -06:00
SocketProcessor ( ws , response , [ & session ] ( const char * data , const int size , bool )
2015-04-21 07:06:41 -05:00
{
2016-01-05 20:29:12 -06:00
return session - > handleInput ( data , size ) ;
} ) ;
2015-03-04 17:14:04 -06:00
}
2016-01-06 08:13:21 -06:00
catch ( const Exception & exc )
{
Log : : error ( ) < < " Error: " < < exc . displayText ( )
< < ( exc . nested ( ) ? " ( " + exc . nested ( ) - > displayText ( ) + " ) " : " " )
< < Log : : end ;
}
catch ( const std : : exception & exc )
{
Log : : error ( std : : string ( " Exception: " ) + exc . what ( ) ) ;
}
catch ( . . . )
2015-03-04 17:14:04 -06:00
{
2016-01-06 08:13:21 -06:00
Log : : error ( " Unexpected Exception. " ) ;
2015-03-04 17:14:04 -06:00
}
2015-12-26 10:33:01 -06:00
Log : : debug ( " Thread [ " + thread_name + " ] finished. " ) ;
2015-03-04 17:14:04 -06:00
}
} ;
2016-01-05 20:29:12 -06:00
template < class RequestHandler >
2015-03-04 17:14:04 -06:00
class RequestHandlerFactory : public HTTPRequestHandlerFactory
{
public :
HTTPRequestHandler * createRequestHandler ( const HTTPServerRequest & request ) override
{
2016-01-23 18:41:01 -06:00
if ( prctl ( PR_SET_NAME , reinterpret_cast < unsigned long > ( " request_handler " ) , 0 , 0 , 0 ) ! = 0 )
Log : : error ( " Cannot set thread name to request_handler. " ) ;
2016-01-19 19:38:43 -06:00
2015-12-25 11:35:23 -06:00
auto logger = Log : : info ( ) ;
logger < < " Request from " < < request . clientAddress ( ) . toString ( ) < < " : "
< < request . getMethod ( ) < < " " < < request . getURI ( ) < < " "
< < request . getVersion ( ) ;
2015-03-07 05:23:46 -06:00
2015-03-04 17:14:04 -06:00
for ( HTTPServerRequest : : ConstIterator it = request . begin ( ) ; it ! = request . end ( ) ; + + it )
{
2015-12-25 11:35:23 -06:00
logger < < " / " < < it - > first < < " : " < < it - > second ;
2015-03-04 17:14:04 -06:00
}
2015-03-07 05:23:46 -06:00
2015-12-25 11:35:23 -06:00
logger < < Log : : end ;
2015-10-09 07:55:49 -05:00
return new RequestHandler ( ) ;
2015-03-04 17:14:04 -06:00
}
} ;
2015-05-29 00:49:49 -05:00
class TestOutput : public Runnable
2015-03-04 17:14:04 -06:00
{
public :
TestOutput ( WebSocket & ws ) :
_ws ( ws )
{
}
void run ( ) override
{
int flags ;
int n ;
2015-03-27 09:53:33 -05:00
_ws . setReceiveTimeout ( 0 ) ;
2015-03-12 11:58:51 -05:00
try
2015-03-04 17:14:04 -06:00
{
2015-03-12 11:58:51 -05:00
do
2015-03-04 17:14:04 -06:00
{
2015-10-16 08:23:49 -05:00
char buffer [ 200000 ] ;
2015-03-12 11:58:51 -05:00
n = _ws . receiveFrame ( buffer , sizeof ( buffer ) , flags ) ;
2016-02-23 12:03:43 -06:00
if ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE )
2015-03-12 11:58:51 -05:00
{
2015-12-25 12:50:53 -06:00
Log : : trace ( ) < < " Client got " < < n < < " bytes: "
< < getAbbreviatedMessage ( buffer , n ) < < Log : : end ;
2015-03-12 11:58:51 -05:00
}
2015-03-04 17:14:04 -06:00
}
2015-03-12 11:58:51 -05:00
while ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE ) ;
}
2015-12-25 12:50:53 -06:00
catch ( const WebSocketException & exc )
2015-03-12 11:58:51 -05:00
{
2015-12-20 11:59:26 -06:00
Log : : error ( " TestOutput::run(), WebSocketException: " + exc . message ( ) ) ;
2015-03-12 11:58:51 -05:00
_ws . close ( ) ;
2015-03-04 17:14:04 -06:00
}
}
private :
WebSocket & _ws ;
} ;
2015-05-29 00:49:49 -05:00
class TestInput : public Runnable
2015-03-04 17:14:04 -06:00
{
public :
TestInput ( ServerApplication & main , ServerSocket & svs , HTTPServer & srv ) :
_main ( main ) ,
_svs ( svs ) ,
_srv ( srv )
{
}
void run ( ) override
{
2015-04-23 10:52:16 -05:00
HTTPClientSession cs ( " 127.0.0.1 " , _svs . address ( ) . port ( ) ) ;
2015-03-04 17:14:04 -06:00
HTTPRequest request ( HTTPRequest : : HTTP_GET , " /ws " ) ;
HTTPResponse response ;
WebSocket ws ( cs , request , response ) ;
Thread thread ;
TestOutput output ( ws ) ;
thread . start ( output ) ;
2015-03-17 18:56:15 -05:00
if ( isatty ( 0 ) )
{
std : : cout < < std : : endl ;
std : : cout < < " Enter LOOL WS requests, one per line. Enter EOF to finish. " < < std : : endl ;
}
2015-03-04 17:14:04 -06:00
while ( ! std : : cin . eof ( ) )
{
std : : string line ;
std : : getline ( std : : cin , line ) ;
ws . sendFrame ( line . c_str ( ) , line . size ( ) ) ;
}
thread . join ( ) ;
_srv . stopAll ( ) ;
_main . terminate ( ) ;
}
private :
ServerApplication & _main ;
ServerSocket & _svs ;
HTTPServer & _srv ;
} ;
2015-12-27 21:47:39 -06:00
std : : atomic < unsigned > LOOLWSD : : NextSessionId ;
2015-12-28 15:34:21 -06:00
int LOOLWSD : : BrokerWritePipe = - 1 ;
2016-01-15 02:40:54 -06:00
std : : string LOOLWSD : : Cache = LOOLWSD_CACHEDIR ;
std : : string LOOLWSD : : SysTemplate ;
std : : string LOOLWSD : : LoTemplate ;
std : : string LOOLWSD : : ChildRoot ;
std : : string LOOLWSD : : LoSubPath = " lo " ;
2015-07-13 09:13:06 -05:00
2015-12-28 15:34:21 -06:00
int LOOLWSD : : NumPreSpawnedChildren = 10 ;
2016-01-15 02:40:54 -06:00
bool LOOLWSD : : DoTest = false ;
2015-04-20 09:43:31 -05:00
const std : : string LOOLWSD : : CHILD_URI = " /loolws/child/ " ;
2015-07-19 15:49:11 -05:00
const std : : string LOOLWSD : : PIDLOG = " /tmp/loolwsd.pid " ;
2016-02-13 11:22:15 -06:00
const std : : string LOOLWSD : : FIFO_PATH = " pipe " ;
const std : : string LOOLWSD : : FIFO_LOOLWSD = " loolwsdfifo " ;
2015-03-17 18:56:15 -05:00
2016-02-15 17:05:24 -06:00
// Demo Site Verification URL.
static const std : : string DemoAuthVerificationUrl = " http://ec2-54-216-97-44.eu-west-1.compute.amazonaws.com/cloudsuite-demo/verify.php?type&token= " ;
2015-11-25 21:17:08 -06:00
LOOLWSD : : LOOLWSD ( )
2015-03-04 17:14:04 -06:00
{
2015-03-17 18:56:15 -05:00
}
2015-03-07 05:23:46 -06:00
2015-03-17 18:56:15 -05:00
LOOLWSD : : ~ LOOLWSD ( )
{
}
2015-03-04 17:14:04 -06:00
2015-03-17 18:56:15 -05:00
void LOOLWSD : : initialize ( Application & self )
{
ServerApplication : : initialize ( self ) ;
}
2015-03-07 05:23:46 -06:00
2015-03-17 18:56:15 -05:00
void LOOLWSD : : uninitialize ( )
{
ServerApplication : : uninitialize ( ) ;
}
2015-10-28 04:55:03 -05:00
void LOOLWSD : : defineOptions ( OptionSet & optionSet )
2015-03-17 18:56:15 -05:00
{
2015-10-28 04:55:03 -05:00
ServerApplication : : defineOptions ( optionSet ) ;
2015-03-17 18:56:15 -05:00
2015-10-28 04:55:03 -05:00
optionSet . addOption ( Option ( " help " , " " , " Display help information on command line arguments. " )
. required ( false )
. repeatable ( false ) ) ;
2015-03-17 18:56:15 -05:00
2015-12-19 12:38:44 -06:00
optionSet . addOption ( Option ( " version " , " " , " Display version information. " )
. required ( false )
. repeatable ( false ) ) ;
2015-10-28 04:55:03 -05:00
optionSet . addOption ( Option ( " port " , " " , " Port number to listen to (default: " + std : : to_string ( DEFAULT_CLIENT_PORT_NUMBER ) + " ), "
2015-05-08 13:29:13 -05:00
" must not be " + std : : to_string ( MASTER_PORT_NUMBER ) + " . " )
2015-10-28 04:55:03 -05:00
. required ( false )
. repeatable ( false )
. argument ( " port number " ) ) ;
optionSet . addOption ( Option ( " cache " , " " , " Path to a directory where to keep the persistent tile cache (default: " + std : : string ( LOOLWSD_CACHEDIR ) + " ). " )
. required ( false )
. repeatable ( false )
. argument ( " directory " ) ) ;
optionSet . addOption ( Option ( " systemplate " , " " , " Path to a template tree with shared libraries etc to be used as source for chroot jails for child processes. " )
. required ( false )
. repeatable ( false )
. argument ( " directory " ) ) ;
optionSet . addOption ( Option ( " lotemplate " , " " , " Path to a LibreOffice installation tree to be copied (linked) into the jails for child processes. Should be on the same file system as systemplate. " )
. required ( false )
. repeatable ( false )
. argument ( " directory " ) ) ;
optionSet . addOption ( Option ( " childroot " , " " , " Path to the directory under which the chroot jails for the child processes will be created. Should be on the same file system as systemplate and lotemplate. " )
. required ( false )
. repeatable ( false )
. argument ( " directory " ) ) ;
2016-01-15 02:40:54 -06:00
optionSet . addOption ( Option ( " losubpath " , " " , " Relative path where the LibreOffice installation will be copied inside a jail (default: ' " + LoSubPath + " '). " )
2015-10-28 04:55:03 -05:00
. required ( false )
. repeatable ( false )
. argument ( " relative path " ) ) ;
optionSet . addOption ( Option ( " numprespawns " , " " , " Number of child processes to keep started in advance and waiting for new clients. " )
. required ( false )
. repeatable ( false )
. argument ( " number " ) ) ;
optionSet . addOption ( Option ( " test " , " " , " Interactive testing. " )
. required ( false )
. repeatable ( false ) ) ;
2015-03-17 18:56:15 -05:00
}
2015-10-28 04:55:03 -05:00
void LOOLWSD : : handleOption ( const std : : string & optionName , const std : : string & value )
2015-03-25 07:39:58 -05:00
{
2015-10-28 04:55:03 -05:00
ServerApplication : : handleOption ( optionName , value ) ;
2015-03-25 07:39:58 -05:00
2015-10-28 04:55:03 -05:00
if ( optionName = = " help " )
2015-03-25 07:39:58 -05:00
{
displayHelp ( ) ;
2016-03-02 00:47:13 -06:00
std : : exit ( Application : : EXIT_OK ) ;
2015-03-25 07:39:58 -05:00
}
2015-12-19 12:38:44 -06:00
else if ( optionName = = " version " )
{
displayVersion ( ) ;
2016-03-02 00:47:13 -06:00
std : : exit ( Application : : EXIT_OK ) ;
2015-12-19 12:38:44 -06:00
}
2015-10-28 04:55:03 -05:00
else if ( optionName = = " port " )
2015-12-27 16:46:42 -06:00
ClientPortNumber = std : : stoi ( value ) ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " cache " )
2016-01-15 02:40:54 -06:00
Cache = value ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " systemplate " )
2016-01-15 02:40:54 -06:00
SysTemplate = value ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " lotemplate " )
2016-01-15 02:40:54 -06:00
LoTemplate = value ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " childroot " )
2016-01-15 02:40:54 -06:00
ChildRoot = value ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " losubpath " )
2016-01-15 02:40:54 -06:00
LoSubPath = value ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " numprespawns " )
2015-12-28 15:34:21 -06:00
NumPreSpawnedChildren = std : : stoi ( value ) ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " test " )
2016-01-15 02:40:54 -06:00
LOOLWSD : : DoTest = true ;
2015-03-25 07:39:58 -05:00
}
void LOOLWSD : : displayHelp ( )
{
HelpFormatter helpFormatter ( options ( ) ) ;
helpFormatter . setCommand ( commandName ( ) ) ;
helpFormatter . setUsage ( " OPTIONS " ) ;
helpFormatter . setHeader ( " LibreOffice On-Line WebSocket server. " ) ;
helpFormatter . format ( std : : cout ) ;
}
2015-03-17 18:56:15 -05:00
2015-12-19 12:38:44 -06:00
void LOOLWSD : : displayVersion ( )
{
std : : cout < < LOOLWSD_VERSION < < std : : endl ;
}
2016-02-24 01:39:23 -06:00
Process : : PID LOOLWSD : : createBroker ( )
2015-07-13 09:13:06 -05:00
{
2015-12-19 19:09:48 -06:00
Process : : Args args ;
2016-01-15 02:40:54 -06:00
args . push_back ( " --losubpath= " + LOOLWSD : : LoSubPath ) ;
args . push_back ( " --systemplate= " + SysTemplate ) ;
args . push_back ( " --lotemplate= " + LoTemplate ) ;
args . push_back ( " --childroot= " + ChildRoot ) ;
2015-12-28 15:34:21 -06:00
args . push_back ( " --numprespawns= " + std : : to_string ( NumPreSpawnedChildren ) ) ;
2015-12-27 16:46:42 -06:00
args . push_back ( " --clientport= " + std : : to_string ( ClientPortNumber ) ) ;
2015-12-19 19:09:48 -06:00
2015-12-28 16:23:05 -06:00
const std : : string brokerPath = Path ( Application : : instance ( ) . commandPath ( ) ) . parent ( ) . toString ( ) + " loolbroker " ;
2015-12-19 19:09:48 -06:00
2016-01-25 19:07:10 -06:00
Log : : info ( " Launching Broker #1: " + brokerPath + " " +
2015-12-24 21:19:50 -06:00
Poco : : cat ( std : : string ( " " ) , args . begin ( ) , args . end ( ) ) ) ;
2015-12-19 19:09:48 -06:00
2015-12-28 16:23:05 -06:00
ProcessHandle child = Process : : launch ( brokerPath , args ) ;
2015-12-19 19:09:48 -06:00
2016-01-25 19:07:10 -06:00
return child . id ( ) ;
2015-07-13 09:13:06 -05:00
}
2015-10-16 10:45:03 -05:00
int LOOLWSD : : main ( const std : : vector < std : : string > & /*args*/ )
2015-07-13 09:13:06 -05:00
{
2015-12-28 17:24:29 -06:00
Log : : initialize ( " wsd " ) ;
2016-02-29 06:25:12 -06:00
2016-02-29 06:06:48 -06:00
if ( geteuid ( ) = = 0 )
{
Log : : error ( " Don't run this as root " ) ;
return Application : : EXIT_USAGE ;
}
2016-02-29 06:25:12 -06:00
2016-02-24 01:39:23 -06:00
//Environment::set("LOK_FORK", "1");
//Environment::set("LD_BIND_NOW", "1");
//Environment::set("LOK_VIEW_CALLBACK", "1");
2015-12-13 12:28:01 -06:00
2015-12-29 19:34:53 -06:00
char * locale = setlocale ( LC_ALL , nullptr ) ;
if ( locale = = nullptr | | std : : strcmp ( locale , " C " ) = = 0 )
2015-10-13 12:05:42 -05:00
setlocale ( LC_ALL , " en_US.utf8 " ) ;
2016-02-01 19:26:19 -06:00
Util : : setTerminationSignals ( ) ;
Util : : setFatalSignals ( ) ;
2016-01-09 12:51:55 -06:00
2016-01-15 02:40:54 -06:00
if ( access ( Cache . c_str ( ) , R_OK | W_OK | X_OK ) ! = 0 )
2015-07-17 16:55:27 -05:00
{
2016-01-15 02:40:54 -06:00
Log : : error ( " Unable to access cache [ " + Cache +
2015-12-25 12:50:53 -06:00
" ] please make sure it exists, and has write permission for this user. " ) ;
2016-02-01 19:51:42 -06:00
return Application : : EXIT_SOFTWARE ;
2015-07-17 16:55:27 -05:00
}
// We use the same option set for both parent and child loolwsd,
// so must check options required in the parent (but not in the
// child) separately now. Also check for options that are
// meaningless for the parent.
2016-01-15 02:40:54 -06:00
if ( SysTemplate . empty ( ) )
2015-07-17 16:55:27 -05:00
throw MissingOptionException ( " systemplate " ) ;
2016-01-15 02:40:54 -06:00
if ( LoTemplate . empty ( ) )
2015-07-17 16:55:27 -05:00
throw MissingOptionException ( " lotemplate " ) ;
2016-01-04 15:15:02 -06:00
2016-01-15 02:40:54 -06:00
if ( ChildRoot . empty ( ) )
2015-07-17 16:55:27 -05:00
throw MissingOptionException ( " childroot " ) ;
2016-01-15 02:40:54 -06:00
else if ( ChildRoot [ ChildRoot . size ( ) - 1 ] ! = Path : : separator ( ) )
ChildRoot + = Path : : separator ( ) ;
2015-07-17 16:55:27 -05:00
2015-12-27 16:46:42 -06:00
if ( ClientPortNumber = = MASTER_PORT_NUMBER )
2015-07-17 16:55:27 -05:00
throw IncompatibleOptionsException ( " port " ) ;
2016-01-15 02:40:54 -06:00
if ( LOOLWSD : : DoTest )
2015-12-28 15:34:21 -06:00
NumPreSpawnedChildren = 1 ;
2015-07-17 16:55:27 -05:00
2015-07-19 15:49:11 -05:00
// log pid information
{
2016-02-24 01:39:23 -06:00
FileOutputStream filePID ( LOOLWSD : : PIDLOG ) ;
2015-07-19 15:49:11 -05:00
if ( filePID . good ( ) )
filePID < < Process : : id ( ) ;
}
2016-02-13 11:22:15 -06:00
const Path pipePath = Path : : forDirectory ( ChildRoot + Path : : separator ( ) + FIFO_PATH ) ;
2016-02-16 15:04:21 -06:00
if ( ! File ( pipePath ) . exists ( ) & & ! File ( pipePath ) . createDirectory ( ) )
2015-12-13 12:28:01 -06:00
{
2016-02-13 11:22:15 -06:00
Log : : error ( " Error: Failed to create pipe directory [ " + pipePath . toString ( ) + " ]. " ) ;
return Application : : EXIT_SOFTWARE ;
}
const std : : string pipeLoolwsd = Path ( pipePath , FIFO_LOOLWSD ) . toString ( ) ;
2016-02-16 15:11:33 -06:00
if ( mkfifo ( pipeLoolwsd . c_str ( ) , 0666 ) < 0 & & errno ! = EEXIST )
2016-02-13 11:22:15 -06:00
{
Log : : error ( " Error: Failed to create pipe FIFO [ " + pipeLoolwsd + " ]. " ) ;
2016-02-01 19:51:42 -06:00
return Application : : EXIT_SOFTWARE ;
2015-12-13 12:28:01 -06:00
}
2016-02-24 10:58:05 -06:00
// Open notify pipe
int pipeFlags = O_RDONLY | O_NONBLOCK ;
int notifyPipe = - 1 ;
const std : : string pipeNotify = Path ( pipePath , FIFO_NOTIFY ) . toString ( ) ;
if ( mkfifo ( pipeNotify . c_str ( ) , 0666 ) < 0 & & errno ! = EEXIST )
{
Log : : error ( " Error: Failed to create pipe FIFO [ " + FIFO_NOTIFY + " ]. " ) ;
exit ( Application : : EXIT_SOFTWARE ) ;
}
if ( ( notifyPipe = open ( pipeNotify . c_str ( ) , pipeFlags ) ) < 0 )
{
Log : : error ( " Error: pipe opened for reading. " ) ;
exit ( Application : : EXIT_SOFTWARE ) ;
}
if ( ( pipeFlags = fcntl ( notifyPipe , F_GETFL , 0 ) ) < 0 )
{
Log : : error ( " Error: failed to get pipe flags [ " + FIFO_NOTIFY + " ]. " ) ;
exit ( Application : : EXIT_SOFTWARE ) ;
}
pipeFlags & = ~ O_NONBLOCK ;
if ( fcntl ( notifyPipe , F_SETFL , pipeFlags ) < 0 )
{
Log : : error ( " Error: failed to set pipe flags [ " + FIFO_NOTIFY + " ]. " ) ;
exit ( Application : : EXIT_SOFTWARE ) ;
}
2016-02-24 01:43:58 -06:00
const Process : : PID brokerPid = createBroker ( ) ;
if ( brokerPid < 0 )
2016-01-06 21:17:30 -06:00
{
Log : : error ( " Failed to spawn loolBroker. " ) ;
2016-02-01 19:51:42 -06:00
return Application : : EXIT_SOFTWARE ;
2016-01-06 21:17:30 -06:00
}
2015-07-13 09:13:06 -05:00
2016-01-31 21:34:18 -06:00
// Configure the Server.
// Note: TCPServer internally uses the default
// ThreadPool to dispatch connections.
// The capacity of the default ThreadPool
// is increased to match MaxThreads.
// We must have sufficient available threads
// in the default ThreadPool to dispatch
// connections, otherwise we will deadlock.
2016-02-01 19:47:40 -06:00
auto params1 = new HTTPServerParams ( ) ;
params1 - > setMaxThreads ( MAX_SESSIONS ) ;
auto params2 = new HTTPServerParams ( ) ;
params2 - > setMaxThreads ( MAX_SESSIONS ) ;
2016-01-31 21:34:18 -06:00
2015-07-17 13:02:25 -05:00
// Start a server listening on the port for clients
2016-01-31 21:34:18 -06:00
ServerSocket svs ( ClientPortNumber ) ;
ThreadPool threadPool ( NumPreSpawnedChildren * 6 , MAX_SESSIONS * 2 ) ;
2016-02-01 19:47:40 -06:00
HTTPServer srv ( new RequestHandlerFactory < ClientRequestHandler > ( ) , threadPool , svs , params1 ) ;
2015-07-17 13:02:25 -05:00
srv . start ( ) ;
// And one on the port for child processes
SocketAddress addr2 ( " 127.0.0.1 " , MASTER_PORT_NUMBER ) ;
2016-01-31 21:34:18 -06:00
ServerSocket svs2 ( addr2 ) ;
2016-02-01 19:47:40 -06:00
HTTPServer srv2 ( new RequestHandlerFactory < PrisonerRequestHandler > ( ) , threadPool , svs2 , params2 ) ;
2015-07-17 13:02:25 -05:00
srv2 . start ( ) ;
2016-02-13 11:22:15 -06:00
if ( ( BrokerWritePipe = open ( pipeLoolwsd . c_str ( ) , O_WRONLY ) ) < 0 )
2015-12-19 19:09:48 -06:00
{
2016-02-13 11:22:15 -06:00
Log : : error ( " Error: failed to open pipe [ " + pipeLoolwsd + " ] write only. " ) ;
2016-02-01 19:51:42 -06:00
return Application : : EXIT_SOFTWARE ;
2015-12-19 19:09:48 -06:00
}
2016-02-24 10:58:05 -06:00
// Start the Admin manager.
Admin admin ( BrokerWritePipe , notifyPipe ) ;
threadPool . start ( admin ) ;
2015-09-11 15:31:43 -05:00
TestInput input ( * this , svs , srv ) ;
Thread inputThread ;
2016-01-15 02:40:54 -06:00
if ( LOOLWSD : : DoTest )
2015-09-11 15:31:43 -05:00
{
inputThread . start ( input ) ;
waitForTerminationRequest ( ) ;
}
2015-12-28 17:24:29 -06:00
int status = 0 ;
2015-12-28 15:34:21 -06:00
unsigned timeoutCounter = 0 ;
2016-01-25 19:07:10 -06:00
while ( ! TerminationFlag & & ! LOOLWSD : : DoTest )
2015-07-17 13:02:25 -05:00
{
2016-02-24 01:43:58 -06:00
const pid_t pid = waitpid ( brokerPid , & status , WUNTRACED | WNOHANG ) ;
2015-07-17 13:02:25 -05:00
if ( pid > 0 )
{
2016-02-24 01:43:58 -06:00
if ( brokerPid = = pid )
2015-07-17 13:02:25 -05:00
{
2016-01-14 21:31:02 -06:00
if ( WIFEXITED ( status ) )
2015-07-17 13:02:25 -05:00
{
2016-01-14 21:31:02 -06:00
Log : : info ( ) < < " Child process [ " < < pid < < " ] exited with code: "
< < WEXITSTATUS ( status ) < < " . " < < Log : : end ;
2016-01-25 19:07:10 -06:00
break ;
2015-07-17 13:02:25 -05:00
}
2016-01-14 21:31:02 -06:00
else
if ( WIFSIGNALED ( status ) )
{
std : : string fate = " died " ;
# ifdef WCOREDUMP
if ( WCOREDUMP ( status ) )
fate = " core-dumped " ;
# endif
Log : : error ( ) < < " Child process [ " < < pid < < " ] " < < fate
< < " with " < < Util : : signalName ( WTERMSIG ( status ) )
2016-02-03 16:08:16 -06:00
< < " signal: " < < strsignal ( WTERMSIG ( status ) )
< < Log : : end ;
2015-07-17 13:02:25 -05:00
2016-01-25 19:07:10 -06:00
break ;
2016-01-14 21:31:02 -06:00
}
else if ( WIFSTOPPED ( status ) )
{
Log : : info ( ) < < " Child process [ " < < pid < < " ] stopped with "
< < Util : : signalName ( WSTOPSIG ( status ) )
2016-02-03 16:08:16 -06:00
< < " signal: " < < strsignal ( WTERMSIG ( status ) )
< < Log : : end ;
2016-01-14 21:31:02 -06:00
}
else if ( WIFCONTINUED ( status ) )
{
Log : : info ( ) < < " Child process [ " < < pid < < " ] resumed with SIGCONT. "
< < Log : : end ;
}
else
{
Log : : warn ( ) < < " Unknown status returned by waitpid: "
< < std : : hex < < status < < " . " < < Log : : end ;
}
2015-07-17 13:02:25 -05:00
}
else
{
2015-12-24 21:19:50 -06:00
Log : : error ( " None of our known child processes died. PID: " + std : : to_string ( pid ) ) ;
2015-07-17 13:02:25 -05:00
}
}
else if ( pid < 0 )
2016-02-03 15:43:04 -06:00
{
2016-01-14 21:31:02 -06:00
Log : : error ( " Error: waitpid failed. " ) ;
2016-02-03 15:43:04 -06:00
// No child processes
if ( errno = = ECHILD )
{
TerminationFlag = true ;
continue ;
}
}
2015-07-17 13:02:25 -05:00
2015-12-28 15:34:21 -06:00
if ( timeoutCounter + + = = INTERVAL_PROBES )
2015-07-24 13:10:24 -05:00
{
timeoutCounter = 0 ;
2015-07-24 14:17:46 -05:00
sleep ( MAINTENANCE_INTERVAL * 2 ) ;
2015-07-24 13:10:24 -05:00
}
2015-07-17 13:02:25 -05:00
}
2015-04-16 11:15:40 -05:00
2016-01-15 02:40:54 -06:00
if ( LOOLWSD : : DoTest )
2015-09-11 15:31:43 -05:00
inputThread . join ( ) ;
2015-11-25 20:59:24 -06:00
// stop the service, no more request
srv . stop ( ) ;
srv2 . stop ( ) ;
2015-12-02 18:15:39 -06:00
2015-11-25 20:59:24 -06:00
// close all websockets
threadPool . joinAll ( ) ;
2015-12-19 19:09:48 -06:00
// Terminate child processes
2016-01-26 19:22:07 -06:00
Util : : writeFIFO ( LOOLWSD : : BrokerWritePipe , " eof \r \n " ) ;
2016-02-24 01:43:58 -06:00
Log : : info ( " Requesting child process " + std : : to_string ( brokerPid ) + " to terminate " ) ;
Util : : requestTermination ( brokerPid ) ;
2015-12-02 18:15:39 -06:00
2015-11-25 20:59:24 -06:00
// wait broker process finish
2016-02-24 01:43:58 -06:00
waitpid ( brokerPid , & status , WUNTRACED ) ;
2015-03-17 18:56:15 -05:00
2016-01-21 08:26:34 -06:00
close ( BrokerWritePipe ) ;
2016-01-15 02:40:54 -06:00
Log : : info ( " Cleaning up childroot directory [ " + ChildRoot + " ]. " ) ;
2016-01-04 15:15:02 -06:00
std : : vector < std : : string > jails ;
2016-01-15 02:40:54 -06:00
File ( ChildRoot ) . list ( jails ) ;
2016-01-04 15:15:02 -06:00
for ( auto & jail : jails )
{
2016-01-15 02:40:54 -06:00
const auto path = ChildRoot + jail ;
2016-01-04 15:15:02 -06:00
Log : : info ( " Removing jail [ " + path + " ]. " ) ;
Util : : removeFile ( path , true ) ;
}
2015-12-28 17:24:29 -06:00
2015-12-28 22:39:25 -06:00
Log : : info ( " Process [loolwsd] finished. " ) ;
2015-11-25 21:17:08 -06:00
return Application : : EXIT_OK ;
2015-03-17 18:56:15 -05:00
}
2015-03-04 17:14:04 -06:00
2015-03-09 10:34:11 -05:00
POCO_SERVER_MAIN ( LOOLWSD )
2015-03-04 17:14:04 -06:00
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */