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-05-22 04:57:17 -05:00
# include <pwd.h>
2015-04-08 09:22:42 -05:00
# include <unistd.h>
2015-04-16 11:15:40 -05:00
# ifdef __linux
2015-04-27 13:55:36 -05:00
# include <sys/capability.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
# endif
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
# define LOK_USE_UNSTABLE_API
# include <LibreOfficeKit/LibreOfficeKitInit.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>
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>
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>
2015-05-18 03:21:30 -05:00
# include <Poco/StringTokenizer.h>
2015-05-29 00:49:49 -05:00
# include <Poco/ThreadPool.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>
2015-07-13 09:13:06 -05:00
# include <Poco/Mutex.h>
# include <Poco/Net/DialogSocket.h>
# include <Poco/Net/Net.h>
# include <Poco/ThreadLocal.h>
# include <Poco/NamedMutex.h>
2015-07-19 15:49:11 -05:00
# include <Poco/FileStream.h>
2015-10-20 08:00:05 -05:00
# include <Poco/TemporaryFile.h>
# include <Poco/StreamCopier.h>
2015-11-17 12:06:59 -06:00
# include <Poco/URI.h>
2015-07-13 09:13:06 -05:00
2015-03-04 17:14:04 -06:00
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"
2015-11-09 04:36:37 -06:00
# include "MessageQueue.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 ;
2015-05-29 00:49:49 -05:00
using Poco : : Exception ;
2015-04-16 11:15:40 -05:00
using Poco : : File ;
2015-05-29 00:49:49 -05:00
using Poco : : IOException ;
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 ;
using Poco : : Net : : ServerSocket ;
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 ;
2015-03-16 06:59:40 -05:00
using Poco : : Runnable ;
2015-05-18 03:21:30 -05:00
using Poco : : StringTokenizer ;
2015-03-16 06:59:40 -05:00
using Poco : : Thread ;
2015-05-29 00:49:49 -05:00
using Poco : : ThreadPool ;
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 ;
2015-07-13 09:13:06 -05:00
using Poco : : Net : : DialogSocket ;
using Poco : : FastMutex ;
using Poco : : Net : : Socket ;
using Poco : : ThreadLocal ;
using Poco : : Random ;
using Poco : : NamedMutex ;
2015-11-17 12:06:59 -06:00
using Poco : : URI ;
2015-03-04 17:14:04 -06:00
2015-06-09 10:04:46 -05:00
class QueueHandler : public Runnable
2015-06-05 08:12:06 -05:00
{
public :
2015-11-09 04:36:37 -06:00
QueueHandler ( MessageQueue & queue ) :
2015-06-05 08:12:06 -05:00
_queue ( queue )
{
}
2015-06-09 10:04:46 -05:00
void setSession ( std : : shared_ptr < LOOLSession > session )
2015-06-05 08:12:06 -05:00
{
_session = session ;
}
void run ( ) override
{
2015-08-08 06:55:54 -05:00
# ifdef __linux
if ( prctl ( PR_SET_NAME , reinterpret_cast < unsigned long > ( " queue_handler " ) , 0 , 0 , 0 ) ! = 0 )
std : : cout < < Util : : logPrefix ( ) < < " Cannot set thread name : " < < strerror ( errno ) < < std : : endl ;
# endif
2015-06-05 08:12:06 -05:00
while ( true )
{
std : : string input = _queue . get ( ) ;
if ( input = = " eof " )
break ;
if ( ! _session - > handleInput ( input . c_str ( ) , input . size ( ) ) )
break ;
}
}
private :
2015-06-09 10:04:46 -05:00
std : : shared_ptr < LOOLSession > _session ;
2015-11-09 04:36:37 -06:00
MessageQueue & _queue ;
2015-06-05 08:12:06 -05:00
} ;
2015-10-16 10:38:24 -05:00
/// Handles the filename part of the convert-to POST request payload.
class ConvertToPartHandler : public Poco : : Net : : PartHandler
{
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
{
}
2015-10-19 09:03:16 -05:00
virtual void handlePart ( const Poco : : Net : : 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 ;
Poco : : Net : : NameValueCollection params ;
if ( header . has ( " Content-Disposition " ) )
{
std : : string cd = header . get ( " Content-Disposition " ) ;
Poco : : Net : : MessageHeader : : splitParameters ( cd , disp , params ) ;
}
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
2015-10-20 08:00:05 -05:00
Path tempPath = Path : : forDirectory ( Poco : : TemporaryFile ( ) . tempName ( ) + Path : : separator ( ) ) ;
File ( tempPath ) . createDirectories ( ) ;
tempPath . setFileName ( params . get ( " filename " ) ) ;
_filename = tempPath . toString ( ) ;
// Copy the stream to _filename.
std : : ofstream fileStream ;
fileStream . open ( _filename ) ;
Poco : : StreamCopier : : copyStream ( stream , fileStream ) ;
fileStream . close ( ) ;
2015-10-16 10:38:24 -05:00
}
} ;
2015-10-09 07:55:49 -05:00
class RequestHandler : public HTTPRequestHandler
/// Handle a WebSocket connection or a simple HTTP request.
2015-03-04 17:14:04 -06:00
{
public :
2015-10-09 07:55:49 -05:00
RequestHandler ( )
2015-03-04 17:14:04 -06:00
{
}
void handleRequest ( HTTPServerRequest & request , HTTPServerResponse & response ) override
{
2015-08-08 06:55:54 -05:00
# ifdef __linux
std : : string thread_name ;
if ( request . serverAddress ( ) . port ( ) = = LOOLWSD : : MASTER_PORT_NUMBER )
thread_name = " prision_socket " ;
else
thread_name = " client_socket " ;
if ( prctl ( PR_SET_NAME , reinterpret_cast < unsigned long > ( thread_name . c_str ( ) ) , 0 , 0 , 0 ) ! = 0 )
std : : cout < < Util : : logPrefix ( ) < < " Cannot set thread name : " < < strerror ( errno ) < < std : : endl ;
# endif
2015-03-04 17:14:04 -06:00
if ( ! ( request . find ( " Upgrade " ) ! = request . end ( ) & & Poco : : icompare ( request [ " Upgrade " ] , " websocket " ) = = 0 ) )
{
2015-10-16 11:45:57 -05:00
StringTokenizer tokens ( request . getURI ( ) , " /? " ) ;
if ( tokens . count ( ) > = 2 & & tokens [ 1 ] = = " convert-to " )
2015-10-16 10:38:24 -05:00
{
2015-10-20 08:00:05 -05:00
std : : string fromPath ;
ConvertToPartHandler handler ( fromPath ) ;
2015-10-16 10:38:24 -05:00
Poco : : Net : : HTMLForm form ( request , request . stream ( ) , handler ) ;
std : : string format ;
if ( form . has ( " format " ) )
format = form . get ( " format " ) ;
2015-10-20 08:00:05 -05:00
if ( ! fromPath . empty ( ) & & ! format . empty ( ) )
2015-10-16 10:38:24 -05:00
{
2015-10-20 08:41:42 -05:00
// Load the document.
std : : shared_ptr < WebSocket > ws ;
LOOLSession : : Kind kind = LOOLSession : : Kind : : ToClient ;
std : : shared_ptr < MasterProcessSession > session ( new MasterProcessSession ( ws , kind ) ) ;
const std : : string filePrefix ( " file:// " ) ;
std : : string load = " load url= " + filePrefix + fromPath ;
session - > handleInput ( load . data ( ) , load . size ( ) ) ;
// Convert it to the requested format.
Path toPath ( fromPath ) ;
toPath . setExtension ( format ) ;
std : : string toJailURL = filePrefix + LOOLSession : : jailDocumentURL + Path : : separator ( ) + toPath . getFileName ( ) ;
std : : string saveas = " saveas url= " + toJailURL + " format= " + format + " options= " ;
session - > handleInput ( saveas . data ( ) , saveas . size ( ) ) ;
std : : string toURL = session - > getSaveAs ( ) ;
// Send it back to the client.
std : : string mimeType = " application/octet-stream " ;
if ( toURL . find ( filePrefix ) = = 0 )
toURL = toURL . substr ( filePrefix . length ( ) ) ;
response . sendFile ( toURL , mimeType ) ;
2015-10-20 08:35:43 -05:00
}
else
{
response . setStatus ( HTTPResponse : : HTTP_BAD_REQUEST ) ;
response . setContentLength ( 0 ) ;
response . send ( ) ;
2015-10-16 10:38:24 -05:00
}
2015-10-20 08:35:43 -05:00
// Clean up the temporary directory the HTMLForm ctor created.
Path tempDirectory ( fromPath ) ;
tempDirectory . setFileName ( " " ) ;
File ( tempDirectory ) . remove ( /*recursive=*/ true ) ;
2015-10-16 10:38:24 -05:00
}
2015-10-22 10:27:29 -05:00
else if ( tokens . count ( ) > = 2 & & tokens [ 1 ] = = " insertfile " )
{
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 " ) ;
std : : string tmpPath ;
ConvertToPartHandler handler ( tmpPath ) ;
Poco : : Net : : HTMLForm form ( request , request . stream ( ) , handler ) ;
if ( form . has ( " childid " ) & & form . has ( " name " ) )
{
std : : string dirPath = LOOLWSD : : childRoot + Path : : separator ( ) + form . get ( " childid " ) + LOOLSession : : jailDocumentURL +
Path : : separator ( ) + " insertfile " ;
File ( dirPath ) . createDirectory ( ) ;
std : : string fileName = dirPath + Path : : separator ( ) + form . get ( " name " ) ;
File ( tmpPath ) . moveTo ( fileName ) ;
response . setStatus ( HTTPResponse : : HTTP_OK ) ;
response . send ( ) ;
}
else
{
response . setStatus ( HTTPResponse : : HTTP_BAD_REQUEST ) ;
response . send ( ) ;
}
}
2015-10-16 11:45:57 -05:00
else if ( tokens . count ( ) > = 4 )
2015-10-09 07:55:49 -05:00
{
2015-10-16 10:38:24 -05:00
// The user might request a file to download
2015-10-16 08:47:39 -05:00
std : : string dirPath = LOOLWSD : : childRoot + " / " + tokens [ 1 ] + LOOLSession : : jailDocumentURL + " / " + tokens [ 2 ] ;
2015-11-17 12:06:59 -06:00
std : : string fileName ;
URI : : decode ( tokens [ 3 ] , fileName ) ;
std : : string filePath = dirPath + " / " + fileName ;
2015-10-16 08:47:39 -05:00
std : : cout < < Util : : logPrefix ( ) < < " HTTP request for: " < < filePath < < std : : endl ;
File file ( filePath ) ;
if ( file . exists ( ) )
{
response . set ( " Access-Control-Allow-Origin " , " * " ) ;
2015-10-16 11:45:57 -05:00
Poco : : Net : : HTMLForm form ( request ) ;
std : : string mimeType = " application/octet-stream " ;
if ( form . has ( " mime_type " ) )
mimeType = form . get ( " mime_type " ) ;
response . sendFile ( filePath , mimeType ) ;
2015-10-16 08:47:39 -05:00
File dir ( dirPath ) ;
dir . remove ( true ) ;
}
else
{
response . setStatus ( HTTPResponse : : HTTP_NOT_FOUND ) ;
response . setContentLength ( 0 ) ;
response . send ( ) ;
}
2015-10-09 07:55:49 -05:00
}
else
{
2015-10-16 08:47:39 -05:00
response . setStatus ( HTTPResponse : : HTTP_BAD_REQUEST ) ;
2015-10-09 07:55:49 -05:00
response . setContentLength ( 0 ) ;
response . send ( ) ;
}
2015-03-04 17:14:04 -06:00
return ;
}
2015-11-09 04:36:37 -06:00
BasicTileQueue queue ;
2015-06-05 08:12:06 -05:00
Thread queueHandlerThread ;
2015-06-09 10:04:46 -05:00
QueueHandler handler ( queue ) ;
2015-06-05 08:12:06 -05:00
2015-03-04 17:14:04 -06:00
try
{
2015-04-21 07:06:41 -05:00
try
2015-04-20 09:43:31 -05:00
{
2015-05-28 08:42:38 -05:00
std : : shared_ptr < WebSocket > ws ( new WebSocket ( request , response ) ) ;
2015-04-21 07:06:41 -05:00
2015-06-05 08:12:06 -05:00
LOOLSession : : Kind kind ;
2015-04-21 07:06:41 -05:00
2015-05-08 13:24:46 -05:00
if ( request . getURI ( ) = = LOOLWSD : : CHILD_URI & & request . serverAddress ( ) . port ( ) = = LOOLWSD : : MASTER_PORT_NUMBER )
2015-06-05 08:12:06 -05:00
kind = LOOLSession : : Kind : : ToPrisoner ;
2015-04-21 07:06:41 -05:00
else
2015-06-05 08:12:06 -05:00
kind = LOOLSession : : Kind : : ToClient ;
std : : shared_ptr < MasterProcessSession > session ( new MasterProcessSession ( ws , kind ) ) ;
// 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.
if ( kind = = LOOLSession : : Kind : : ToClient )
2015-04-21 07:06:41 -05:00
{
2015-06-05 08:12:06 -05:00
handler . setSession ( session ) ;
queueHandlerThread . start ( handler ) ;
2015-04-21 07:06:41 -05:00
}
2015-06-05 08:12:06 -05:00
// Loop, receiving WebSocket messages either from the client, or from the child
// process (to be forwarded to the client).
2015-04-21 07:06:41 -05:00
int flags ;
int n ;
2015-05-28 08:42:38 -05:00
ws - > setReceiveTimeout ( 0 ) ;
2015-04-21 07:06:41 -05:00
do
{
2015-10-16 08:23:49 -05:00
char buffer [ 200000 ] ;
2015-05-28 08:42:38 -05:00
n = ws - > receiveFrame ( buffer , sizeof ( buffer ) , flags ) ;
2015-04-21 07:06:41 -05:00
2015-05-28 07:25:08 -05:00
if ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE )
{
2015-06-09 10:04:46 -05:00
std : : string firstLine = getFirstLine ( buffer , n ) ;
StringTokenizer tokens ( firstLine , " " , StringTokenizer : : TOK_IGNORE_EMPTY | StringTokenizer : : TOK_TRIM ) ;
2015-06-05 08:12:06 -05:00
if ( kind = = LOOLSession : : Kind : : ToClient & & firstLine . size ( ) = = static_cast < std : : string : : size_type > ( n ) )
2015-05-18 03:21:30 -05:00
{
2015-11-09 04:36:37 -06:00
queue . put ( firstLine ) ;
2015-06-05 08:12:06 -05:00
}
else
{
// Check if it is a "nextmessage:" and in that case read the large
// follow-up message separately, and handle that only.
int size ;
if ( tokens . count ( ) = = 2 & & tokens [ 0 ] = = " nextmessage: " & & getTokenInteger ( tokens [ 1 ] , " size " , size ) & & size > 0 )
{
char largeBuffer [ size ] ;
2015-05-18 03:21:30 -05:00
2015-06-05 08:12:06 -05:00
n = ws - > receiveFrame ( largeBuffer , size , flags ) ;
if ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE )
{
if ( ! session - > handleInput ( largeBuffer , n ) )
n = 0 ;
}
}
else
2015-05-18 03:21:30 -05:00
{
2015-06-05 08:12:06 -05:00
if ( ! session - > handleInput ( buffer , n ) )
2015-05-18 03:21:30 -05:00
n = 0 ;
}
}
}
2015-04-21 07:06:41 -05:00
}
while ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE ) ;
2015-11-19 05:32:48 -06:00
queue . clear ( ) ;
queue . put ( " eof " ) ;
queueHandlerThread . join ( ) ;
2015-04-20 09:43:31 -05:00
}
2015-04-21 07:06:41 -05:00
catch ( WebSocketException & exc )
2015-03-04 17:14:04 -06:00
{
2015-10-16 08:23:49 -05:00
Application : : instance ( ) . logger ( ) . error ( Util : : logPrefix ( ) + " RequestHandler::handleRequest(), WebSocketException: " + exc . message ( ) ) ;
2015-04-21 07:06:41 -05:00
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
}
}
2015-05-29 00:49:49 -05:00
catch ( IOException & exc )
2015-03-04 17:14:04 -06:00
{
2015-06-09 10:22:09 -05:00
Application : : instance ( ) . logger ( ) . error ( Util : : logPrefix ( ) + " IOException: " + exc . message ( ) ) ;
2015-03-04 17:14:04 -06:00
}
}
} ;
class RequestHandlerFactory : public HTTPRequestHandlerFactory
{
public :
2015-03-17 18:56:15 -05:00
RequestHandlerFactory ( )
2015-03-04 17:14:04 -06:00
{
}
HTTPRequestHandler * createRequestHandler ( const HTTPServerRequest & request ) override
{
2015-03-17 18:56:15 -05:00
std : : string line = ( Util : : logPrefix ( ) + " 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-03-17 18:56:15 -05:00
line + = " / " + it - > first + " : " + it - > second ;
2015-03-04 17:14:04 -06:00
}
2015-03-07 05:23:46 -06:00
2015-06-09 10:22:09 -05:00
Application : : instance ( ) . logger ( ) . information ( line ) ;
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 ) ;
if ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE )
{
2015-03-17 18:56:15 -05:00
std : : cout < <
2015-04-14 09:50:38 -05:00
Util : : logPrefix ( ) < <
" Client got " < < n < < " bytes: " < < getAbbreviatedMessage ( buffer , n ) < <
2015-03-17 18:56:15 -05:00
std : : endl ;
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 ) ;
}
catch ( WebSocketException & exc )
{
2015-10-16 08:23:49 -05:00
Application : : instance ( ) . logger ( ) . error ( Util : : logPrefix ( ) + " 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-05-08 13:24:46 -05:00
int LOOLWSD : : portNumber = DEFAULT_CLIENT_PORT_NUMBER ;
2015-07-24 13:10:24 -05:00
int LOOLWSD : : timeoutCounter = 0 ;
2015-06-04 09:07:49 -05:00
std : : string LOOLWSD : : cache = LOOLWSD_CACHEDIR ;
2015-04-08 09:22:42 -05:00
std : : string LOOLWSD : : sysTemplate ;
std : : string LOOLWSD : : loTemplate ;
std : : string LOOLWSD : : childRoot ;
std : : string LOOLWSD : : loSubPath = " lo " ;
std : : string LOOLWSD : : jail ;
2015-07-13 09:13:06 -05:00
std : : mutex LOOLWSD : : _rngMutex ;
Random LOOLWSD : : _rng ;
2015-07-18 11:35:16 -05:00
Poco : : NamedMutex LOOLWSD : : _namedMutexLOOL ( " loolwsd " ) ;
2015-07-17 13:02:25 -05:00
Poco : : SharedMemory LOOLWSD : : _sharedForkChild ( " loolwsd " , sizeof ( bool ) , Poco : : SharedMemory : : AM_WRITE ) ;
2015-07-13 09:13:06 -05:00
2015-04-28 03:02:29 -05:00
int LOOLWSD : : _numPreSpawnedChildren = 10 ;
2015-08-05 19:01:39 -05:00
bool LOOLWSD : : doTest = false ;
2015-05-22 08:34:21 -05:00
# if ENABLE_DEBUG
bool LOOLWSD : : runningAsRoot = false ;
2015-05-22 11:42:36 -05:00
int LOOLWSD : : uid = 0 ;
2015-05-22 08:34:21 -05:00
# endif
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 " ;
2015-08-05 19:20:05 -05:00
const std : : string LOOLWSD : : LOKIT_PIDLOG = " /tmp/lokit.pid " ;
2015-03-17 18:56:15 -05:00
LOOLWSD : : LOOLWSD ( ) :
_childId ( 0 )
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-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 " ) ) ;
optionSet . addOption ( Option ( " losubpath " , " " , " Relative path where the LibreOffice installation will be copied inside a jail (default: ' " + loSubPath + " '). " )
. 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 ) ) ;
optionSet . addOption ( Option ( " child " , " " , " For internal use only. " )
. required ( false )
. repeatable ( false )
. argument ( " child id " ) ) ;
optionSet . addOption ( Option ( " jail " , " " , " For internal use only. " )
. required ( false )
. repeatable ( false )
. argument ( " directory " ) ) ;
2015-05-13 06:22:19 -05:00
# if ENABLE_DEBUG
2015-10-28 04:55:03 -05:00
optionSet . addOption ( Option ( " uid " , " " , " Uid to assume if running under sudo for debugging purposes. " )
. required ( false )
. repeatable ( false )
. argument ( " uid " ) ) ;
2015-05-13 06:22:19 -05:00
# endif
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 ( ) ;
exit ( Application : : EXIT_OK ) ;
}
2015-10-28 04:55:03 -05:00
else if ( optionName = = " port " )
2015-03-25 07:39:58 -05:00
portNumber = std : : stoi ( value ) ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " cache " )
2015-06-04 09:07:49 -05:00
cache = value ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " systemplate " )
2015-04-08 09:22:42 -05:00
sysTemplate = value ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " lotemplate " )
2015-04-08 09:22:42 -05:00
loTemplate = value ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " childroot " )
2015-04-08 09:22:42 -05:00
childRoot = value ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " losubpath " )
2015-04-08 09:22:42 -05:00
loSubPath = value ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " numprespawns " )
2015-04-28 03:02:29 -05:00
_numPreSpawnedChildren = std : : stoi ( value ) ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " test " )
2015-08-05 19:01:39 -05:00
LOOLWSD : : doTest = true ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " child " )
2015-03-25 07:39:58 -05:00
_childId = std : : stoull ( value ) ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " jail " )
2015-04-08 09:22:42 -05:00
jail = value ;
2015-05-13 06:22:19 -05:00
# if ENABLE_DEBUG
2015-10-28 04:55:03 -05:00
else if ( optionName = = " uid " )
2015-05-13 06:22:19 -05:00
uid = std : : stoull ( value ) ;
# endif
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-04-27 13:55:36 -05:00
namespace
{
2015-07-13 09:13:06 -05:00
ThreadLocal < std : : string > sourceForLinkOrCopy ;
ThreadLocal < Path > destinationForLinkOrCopy ;
int linkOrCopyFunction ( const char * fpath ,
2015-10-16 10:45:03 -05:00
const struct stat * /*sb*/ ,
2015-07-13 09:13:06 -05:00
int typeflag ,
2015-10-16 10:45:03 -05:00
struct FTW * /*ftwbuf*/ )
2015-07-13 09:13:06 -05:00
{
if ( strcmp ( fpath , sourceForLinkOrCopy - > c_str ( ) ) = = 0 )
return 0 ;
assert ( fpath [ strlen ( sourceForLinkOrCopy - > c_str ( ) ) ] = = ' / ' ) ;
const char * relativeOldPath = fpath + strlen ( sourceForLinkOrCopy - > c_str ( ) ) + 1 ;
# ifdef __APPLE__
if ( strcmp ( relativeOldPath , " PkgInfo " ) = = 0 )
return 0 ;
# endif
Path newPath ( * destinationForLinkOrCopy , Path ( relativeOldPath ) ) ;
switch ( typeflag )
{
case FTW_F :
File ( newPath . parent ( ) ) . createDirectories ( ) ;
if ( link ( fpath , newPath . toString ( ) . c_str ( ) ) = = - 1 )
{
Application : : instance ( ) . logger ( ) . error ( Util : : logPrefix ( ) +
" link( \" " + fpath + " \" , \" " + newPath . toString ( ) + " \" ) failed: " +
strerror ( errno ) ) ;
exit ( 1 ) ;
}
break ;
case FTW_DP :
{
struct stat st ;
if ( stat ( fpath , & st ) = = - 1 )
{
Application : : instance ( ) . logger ( ) . error ( Util : : logPrefix ( ) +
" stat( \" " + fpath + " \" ) failed: " +
strerror ( errno ) ) ;
return 1 ;
}
File ( newPath ) . createDirectories ( ) ;
struct utimbuf ut ;
ut . actime = st . st_atime ;
ut . modtime = st . st_mtime ;
if ( utime ( newPath . toString ( ) . c_str ( ) , & ut ) = = - 1 )
{
Application : : instance ( ) . logger ( ) . error ( Util : : logPrefix ( ) +
" utime( \" " + newPath . toString ( ) + " \" , &ut) failed: " +
strerror ( errno ) ) ;
return 1 ;
}
}
break ;
case FTW_DNR :
Application : : instance ( ) . logger ( ) . error ( Util : : logPrefix ( ) +
" Cannot read directory ' " + fpath + " ' " ) ;
return 1 ;
case FTW_NS :
Application : : instance ( ) . logger ( ) . error ( Util : : logPrefix ( ) +
" nftw: stat failed for ' " + fpath + " ' " ) ;
return 1 ;
case FTW_SLN :
Application : : instance ( ) . logger ( ) . information ( Util : : logPrefix ( ) +
" nftw: symlink to nonexistent file: ' " + fpath + " ', ignored " ) ;
break ;
default :
assert ( false ) ;
}
return 0 ;
}
void linkOrCopy ( const std : : string & source , const Path & destination )
{
* sourceForLinkOrCopy = source ;
if ( sourceForLinkOrCopy - > back ( ) = = ' / ' )
sourceForLinkOrCopy - > pop_back ( ) ;
* destinationForLinkOrCopy = destination ;
if ( nftw ( source . c_str ( ) , linkOrCopyFunction , 10 , FTW_DEPTH ) = = - 1 )
Application : : instance ( ) . logger ( ) . error ( Util : : logPrefix ( ) +
" linkOrCopy: nftw() failed for ' " + source + " ' " ) ;
}
2015-05-22 04:57:17 -05:00
void dropCapability (
# ifdef __linux
cap_value_t capability
# endif
)
2015-04-27 13:55:36 -05:00
{
2015-05-04 12:59:42 -05:00
# ifdef __linux
2015-04-27 13:55:36 -05:00
cap_t caps ;
2015-05-19 05:48:20 -05:00
cap_value_t cap_list [ ] = { capability } ;
2015-04-27 13:55:36 -05:00
caps = cap_get_proc ( ) ;
if ( caps = = NULL )
{
Application : : instance ( ) . logger ( ) . error ( Util : : logPrefix ( ) + " cap_get_proc() failed: " + strerror ( errno ) ) ;
exit ( 1 ) ;
}
if ( cap_set_flag ( caps , CAP_EFFECTIVE , sizeof ( cap_list ) / sizeof ( cap_list [ 0 ] ) , cap_list , CAP_CLEAR ) = = - 1 | |
cap_set_flag ( caps , CAP_PERMITTED , sizeof ( cap_list ) / sizeof ( cap_list [ 0 ] ) , cap_list , CAP_CLEAR ) = = - 1 )
{
Application : : instance ( ) . logger ( ) . error ( Util : : logPrefix ( ) + " cap_set_flag() failed: " + strerror ( errno ) ) ;
exit ( 1 ) ;
}
if ( cap_set_proc ( caps ) = = - 1 )
{
Application : : instance ( ) . logger ( ) . error ( std : : string ( " cap_set_proc() failed: " ) + strerror ( errno ) ) ;
exit ( 1 ) ;
}
2015-05-19 05:52:42 -05:00
char * capText = cap_to_text ( caps , NULL ) ;
Application : : instance ( ) . logger ( ) . information ( Util : : logPrefix ( ) + " Capabilities now: " + capText ) ;
cap_free ( capText ) ;
2015-04-27 13:55:36 -05:00
cap_free ( caps ) ;
2015-05-04 12:59:42 -05:00
# endif
2015-05-19 05:48:20 -05:00
// We assume that on non-Linux we don't need to be root to be able to hardlink to files we
// don't own, so drop root.
2015-05-07 10:27:37 -05:00
if ( geteuid ( ) = = 0 & & getuid ( ) ! = 0 )
{
// The program is setuid root. Not normal on Linux where we use setcap, but if this
// needs to run on non-Linux Unixes, setuid root is what it will bneed to be to be able
// to do chroot().
2015-11-24 02:41:59 -06:00
if ( setuid ( getuid ( ) ) ! = 0 )
{
2015-05-13 05:00:33 -05:00
Application : : instance ( ) . logger ( ) . error ( std : : string ( " setuid() failed: " ) + strerror ( errno ) ) ;
}
2015-05-07 10:27:37 -05:00
}
2015-05-13 06:22:19 -05:00
# if ENABLE_DEBUG
if ( geteuid ( ) = = 0 & & getuid ( ) = = 0 )
{
2015-05-28 08:42:38 -05:00
# ifdef __linux
// Argh, awful hack
if ( capability = = CAP_FOWNER )
return ;
# endif
2015-05-13 06:22:19 -05:00
// Running under sudo, probably because being debugged? Let's drop super-user rights.
2015-05-22 08:34:21 -05:00
LOOLWSD : : runningAsRoot = true ;
2015-05-22 11:42:36 -05:00
if ( LOOLWSD : : uid = = 0 )
2015-05-13 06:22:19 -05:00
{
struct passwd * nobody = getpwnam ( " nobody " ) ;
if ( nobody )
2015-05-22 11:42:36 -05:00
LOOLWSD : : uid = nobody - > pw_uid ;
2015-05-13 06:22:19 -05:00
else
2015-05-22 11:42:36 -05:00
LOOLWSD : : uid = 65534 ;
2015-05-13 06:22:19 -05:00
}
2015-11-24 02:41:59 -06:00
if ( setuid ( LOOLWSD : : uid ) ! = 0 )
{
2015-05-15 01:23:21 -05:00
Application : : instance ( ) . logger ( ) . error ( std : : string ( " setuid() failed: " ) + strerror ( errno ) ) ;
}
2015-05-13 06:22:19 -05:00
}
# endif
2015-04-27 13:55:36 -05:00
}
}
2015-07-13 09:13:06 -05:00
// Writer, Impress or Calc
void LOOLWSD : : componentMain ( )
2015-03-17 18:56:15 -05:00
{
2015-08-08 06:55:54 -05:00
# ifdef __linux
if ( prctl ( PR_SET_NAME , reinterpret_cast < unsigned long > ( " libreofficekit " ) , 0 , 0 , 0 ) ! = 0 )
std : : cout < < Util : : logPrefix ( ) < < " Cannot set thread name : " < < strerror ( errno ) < < std : : endl ;
# endif
2015-05-08 07:46:10 -05:00
try
2015-03-04 17:14:04 -06:00
{
2015-07-18 11:35:16 -05:00
_namedMutexLOOL . lock ( ) ;
2015-07-13 09:13:06 -05:00
2015-05-22 08:40:09 -05:00
# ifdef __APPLE__
LibreOfficeKit * loKit ( lok_init_2 ( ( " / " + loSubPath + " /Frameworks " ) . c_str ( ) , " file:///user " ) ) ;
# else
2015-05-08 07:46:10 -05:00
LibreOfficeKit * loKit ( lok_init_2 ( ( " / " + loSubPath + " /program " ) . c_str ( ) , " file:///user " ) ) ;
2015-05-22 08:40:09 -05:00
# endif
2015-05-08 07:46:10 -05:00
if ( ! loKit )
{
logger ( ) . fatal ( Util : : logPrefix ( ) + " LibreOfficeKit initialisation failed " ) ;
2015-07-13 09:13:06 -05:00
exit ( Application : : EXIT_UNAVAILABLE ) ;
2015-05-08 07:46:10 -05:00
}
2015-03-04 17:14:04 -06:00
2015-07-18 11:35:16 -05:00
_namedMutexLOOL . unlock ( ) ;
2015-07-13 09:13:06 -05:00
2015-05-08 07:46:10 -05:00
// Open websocket connection between the child process and the
// parent. The parent forwards us requests that it can't handle.
2015-03-04 17:14:04 -06:00
2015-05-08 13:24:46 -05:00
HTTPClientSession cs ( " 127.0.0.1 " , MASTER_PORT_NUMBER ) ;
cs . setTimeout ( 0 ) ;
2015-05-08 07:46:10 -05:00
HTTPRequest request ( HTTPRequest : : HTTP_GET , LOOLWSD : : CHILD_URI ) ;
HTTPResponse response ;
2015-05-28 08:42:38 -05:00
std : : shared_ptr < WebSocket > ws ( new WebSocket ( cs , request , response ) ) ;
2015-03-04 17:14:04 -06:00
2015-10-09 07:55:49 -05:00
std : : shared_ptr < ChildProcessSession > session ( new ChildProcessSession ( ws , loKit , std : : to_string ( _childId ) ) ) ;
2015-03-17 18:56:15 -05:00
2015-05-28 08:42:38 -05:00
ws - > setReceiveTimeout ( 0 ) ;
2015-04-08 09:22:42 -05:00
2015-08-05 19:20:05 -05:00
std : : string hello ( " child " + std : : to_string ( _childId ) + " " + std : : to_string ( Process : : id ( ) ) ) ;
2015-06-09 10:04:46 -05:00
session - > sendTextFrame ( hello ) ;
2015-11-09 04:36:37 -06:00
TileQueue queue ;
2015-06-09 10:04:46 -05:00
Thread queueHandlerThread ;
QueueHandler handler ( queue ) ;
handler . setSession ( session ) ;
queueHandlerThread . start ( handler ) ;
2015-03-04 17:14:04 -06:00
2015-05-08 07:46:10 -05:00
int flags ;
int n ;
do
{
char buffer [ 1024 ] ;
2015-05-28 08:42:38 -05:00
n = ws - > receiveFrame ( buffer , sizeof ( buffer ) , flags ) ;
2015-03-04 17:14:04 -06:00
2015-05-08 07:46:10 -05:00
if ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE )
2015-06-09 10:04:46 -05:00
{
std : : string firstLine = getFirstLine ( buffer , n ) ;
StringTokenizer tokens ( firstLine , " " , StringTokenizer : : TOK_IGNORE_EMPTY | StringTokenizer : : TOK_TRIM ) ;
// The only kind of messages a child process receives are the single-line ones (?)
assert ( firstLine . size ( ) = = static_cast < std : : string : : size_type > ( n ) ) ;
2015-11-09 04:36:37 -06:00
queue . put ( firstLine ) ;
2015-06-09 10:04:46 -05:00
}
2015-05-08 07:46:10 -05:00
}
while ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE ) ;
2015-06-09 10:04:46 -05:00
queue . clear ( ) ;
queue . put ( " eof " ) ;
queueHandlerThread . join ( ) ;
2015-08-08 07:20:16 -05:00
// Destroy LibreOfficeKit
loKit - > pClass - > destroy ( loKit ) ;
2015-05-08 07:46:10 -05:00
}
2015-05-29 00:49:49 -05:00
catch ( Exception & exc )
2015-05-08 07:46:10 -05:00
{
logger ( ) . log ( Util : : logPrefix ( ) + " Exception: " + exc . what ( ) ) ;
}
catch ( std : : exception & exc )
{
logger ( ) . error ( Util : : logPrefix ( ) + " Exception: " + exc . what ( ) ) ;
2015-03-17 18:56:15 -05:00
}
2015-03-04 17:14:04 -06:00
2015-07-13 09:13:06 -05:00
exit ( Application : : EXIT_OK ) ;
2015-03-17 18:56:15 -05:00
}
2015-03-04 17:14:04 -06:00
2015-07-13 09:13:06 -05:00
int LOOLWSD : : createComponent ( )
2015-03-17 18:56:15 -05:00
{
2015-07-13 09:13:06 -05:00
int pid ;
if ( ( pid = fork ( ) ) = = - 1 )
{
std : : cout < < " Fork failed. " < < std : : endl ;
return Application : : EXIT_UNAVAILABLE ;
}
if ( ! pid )
{
componentMain ( ) ;
}
MasterProcessSession : : _childProcesses [ pid ] = pid ;
return Application : : EXIT_OK ;
}
void LOOLWSD : : startupComponent ( int nComponents )
{
for ( int nCntr = nComponents ; nCntr ; nCntr - - )
{
if ( createComponent ( ) < 0 )
break ;
}
}
void LOOLWSD : : desktopMain ( )
{
2015-08-08 06:55:54 -05:00
# ifdef __linux
if ( prctl ( PR_SET_NAME , reinterpret_cast < unsigned long > ( " loolbroker " ) , 0 , 0 , 0 ) ! = 0 )
std : : cout < < Util : : logPrefix ( ) < < " Cannot set thread name : " < < strerror ( errno ) < < std : : endl ;
# endif
2015-07-13 09:13:06 -05:00
// Initialization
std : : unique_lock < std : : mutex > rngLock ( _rngMutex ) ;
_childId = ( ( ( Poco : : UInt64 ) _rng . next ( ) ) < < 32 ) | _rng . next ( ) | 1 ;
rngLock . unlock ( ) ;
2015-11-19 04:23:45 -06:00
Path jailPath = Path : : forDirectory ( LOOLWSD : : childRoot + Path : : separator ( ) + std : : to_string ( _childId ) ) ;
File ( jailPath ) . createDirectory ( ) ;
2015-07-13 09:13:06 -05:00
2015-11-19 04:23:45 -06:00
Path jailLOInstallation ( jailPath , LOOLWSD : : loSubPath ) ;
2015-07-13 09:13:06 -05:00
jailLOInstallation . makeDirectory ( ) ;
File ( jailLOInstallation ) . createDirectory ( ) ;
// Copy (link) LO installation and other necessary files into it from the template
2015-11-19 04:23:45 -06:00
linkOrCopy ( LOOLWSD : : sysTemplate , jailPath ) ;
2015-07-13 09:13:06 -05:00
linkOrCopy ( LOOLWSD : : loTemplate , jailLOInstallation ) ;
2015-09-04 10:43:47 -05:00
// We need this because sometimes the hostname is not resolved
2015-09-08 05:03:14 -05:00
std : : vector < std : : string > networkFiles = { " /etc/host.conf " , " /etc/hosts " , " /etc/nsswitch.conf " , " /etc/resolv.conf " } ;
2015-09-08 03:31:49 -05:00
for ( std : : vector < std : : string > : : iterator it = networkFiles . begin ( ) ; it ! = networkFiles . end ( ) ; + + it )
2015-09-04 10:43:47 -05:00
{
2015-09-08 03:31:49 -05:00
File networkFile ( * it ) ;
if ( networkFile . exists ( ) )
{
2015-11-19 04:23:45 -06:00
networkFile . copyTo ( Path ( jailPath , " /etc " ) . toString ( ) ) ;
2015-09-08 03:31:49 -05:00
}
2015-09-07 07:56:47 -05:00
}
2015-07-13 09:13:06 -05:00
# ifdef __linux
// Create the urandom and random devices
2015-11-19 04:23:45 -06:00
File ( Path ( jailPath , " /dev " ) ) . createDirectory ( ) ;
if ( mknod ( ( jailPath . toString ( ) + " /dev/random " ) . c_str ( ) ,
2015-07-13 09:13:06 -05:00
S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ,
makedev ( 1 , 8 ) ) ! = 0 )
{
Application : : instance ( ) . logger ( ) . error ( Util : : logPrefix ( ) +
2015-11-19 04:23:45 -06:00
" mknod( " + jailPath . toString ( ) + " /dev/random) failed: " +
2015-07-13 09:13:06 -05:00
strerror ( errno ) ) ;
}
2015-11-19 04:23:45 -06:00
if ( mknod ( ( jailPath . toString ( ) + " /dev/urandom " ) . c_str ( ) ,
2015-07-13 09:13:06 -05:00
S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ,
makedev ( 1 , 9 ) ) ! = 0 )
{
Application : : instance ( ) . logger ( ) . error ( Util : : logPrefix ( ) +
2015-11-19 04:23:45 -06:00
" mknod( " + jailPath . toString ( ) + " /dev/urandom) failed: " +
2015-07-13 09:13:06 -05:00
strerror ( errno ) ) ;
}
# endif
2015-11-19 04:23:45 -06:00
Application : : instance ( ) . logger ( ) . information ( " desktopMain -> chroot( \" " + jailPath . toString ( ) + " \" ) " ) ;
if ( chroot ( jailPath . toString ( ) . c_str ( ) ) = = - 1 )
2015-07-13 09:13:06 -05:00
{
2015-11-19 04:23:45 -06:00
logger ( ) . error ( " chroot( \" " + jailPath . toString ( ) + " \" ) failed: " + strerror ( errno ) ) ;
2015-07-13 09:13:06 -05:00
exit ( Application : : EXIT_UNAVAILABLE ) ;
}
if ( chdir ( " / " ) = = - 1 )
{
logger ( ) . error ( std : : string ( " chdir( \" / \" ) in jail failed: " ) + strerror ( errno ) ) ;
exit ( Application : : EXIT_UNAVAILABLE ) ;
}
2015-04-28 03:17:26 -05:00
2015-05-22 04:57:17 -05:00
# ifdef __linux
2015-05-19 05:48:20 -05:00
dropCapability ( CAP_SYS_CHROOT ) ;
2015-05-22 04:57:17 -05:00
# else
dropCapability ( ) ;
# endif
2015-04-28 03:17:26 -05:00
2015-07-13 09:13:06 -05:00
if ( std : : getenv ( " SLEEPFORDEBUGGER " ) )
2015-04-21 08:51:28 -05:00
{
2015-07-13 09:13:06 -05:00
std : : cout < < " Sleeping " < < std : : getenv ( " SLEEPFORDEBUGGER " ) < < " seconds, " < <
" attach process " < < Process : : id ( ) < < " in debugger now. " < < std : : endl ;
Thread : : sleep ( std : : stoul ( std : : getenv ( " SLEEPFORDEBUGGER " ) ) * 1000 ) ;
2015-04-21 08:51:28 -05:00
}
2015-07-13 09:13:06 -05:00
startupComponent ( _numPreSpawnedChildren ) ;
2015-04-16 11:15:40 -05:00
2015-07-13 09:13:06 -05:00
while ( MasterProcessSession : : _childProcesses . size ( ) > 0 )
{
int status ;
pid_t pid = waitpid ( - 1 , & status , WUNTRACED | WNOHANG ) ;
2015-07-16 17:05:28 -05:00
if ( pid > 0 )
2015-07-13 09:13:06 -05:00
{
2015-07-16 17:05:28 -05:00
if ( MasterProcessSession : : _childProcesses . find ( pid ) ! = MasterProcessSession : : _childProcesses . end ( ) )
2015-07-13 09:13:06 -05:00
{
2015-07-16 17:05:28 -05:00
if ( ( WIFEXITED ( status ) | | WIFSIGNALED ( status ) | | WTERMSIG ( status ) ) )
{
std : : cout < < Util : : logPrefix ( ) < < " One of our known child processes died : " < < std : : to_string ( pid ) < < std : : endl ;
MasterProcessSession : : _childProcesses . erase ( pid ) ;
}
if ( WCOREDUMP ( status ) )
std : : cout < < Util : : logPrefix ( ) < < " The child produced a core dump. " < < std : : endl ;
if ( WIFSTOPPED ( status ) )
std : : cout < < Util : : logPrefix ( ) < < " The child process was stopped by delivery of a signal. " < < std : : endl ;
2015-04-28 02:57:05 -05:00
2015-07-16 17:05:28 -05:00
if ( WSTOPSIG ( status ) )
std : : cout < < Util : : logPrefix ( ) < < " The child process was stopped. " < < std : : endl ;
if ( WIFCONTINUED ( status ) )
std : : cout < < Util : : logPrefix ( ) < < " The child process was resumed. " < < std : : endl ;
}
else
{
std : : cout < < Util : : logPrefix ( ) < < " None of our known child processes died : " < < std : : to_string ( pid ) < < std : : endl ;
}
}
else if ( pid < 0 )
std : : cout < < Util : : logPrefix ( ) < < " Child error: " < < strerror ( errno ) ;
2015-07-13 09:13:06 -05:00
2015-08-05 17:19:51 -05:00
if ( _sharedForkChild . begin ( ) [ 0 ] > 0 )
2015-07-13 09:13:06 -05:00
{
2015-08-05 17:19:51 -05:00
_namedMutexLOOL . lock ( ) ;
_sharedForkChild . begin ( ) [ 0 ] = _sharedForkChild . begin ( ) [ 0 ] - 1 ;
std : : cout < < Util : : logPrefix ( ) < < " Create child session, fork new one " < < std : : endl ;
_namedMutexLOOL . unlock ( ) ;
2015-07-13 09:13:06 -05:00
if ( createComponent ( ) < 0 )
break ;
}
2015-07-24 13:10:24 -05:00
+ + timeoutCounter ;
if ( timeoutCounter = = INTERVAL_PROBES )
{
timeoutCounter = 0 ;
sleep ( MAINTENANCE_INTERVAL ) ;
}
2015-07-13 09:13:06 -05:00
}
// Terminate child processes
for ( auto i : MasterProcessSession : : _childProcesses )
2015-03-17 18:56:15 -05:00
{
2015-07-13 09:13:06 -05:00
logger ( ) . information ( Util : : logPrefix ( ) + " Requesting child process " + std : : to_string ( i . first ) + " to terminate " ) ;
Process : : requestTermination ( i . first ) ;
2015-03-04 17:14:04 -06:00
}
2015-07-13 09:13:06 -05:00
exit ( Application : : EXIT_OK ) ;
}
2015-03-17 18:56:15 -05:00
2015-07-13 09:13:06 -05:00
int LOOLWSD : : createDesktop ( )
{
int pid ;
if ( ( pid = fork ( ) ) = = - 1 )
{
std : : cout < < " createDesktop fork failed. " < < std : : endl ;
return Application : : EXIT_UNAVAILABLE ;
}
if ( ! pid )
{
desktopMain ( ) ;
}
MasterProcessSession : : _childProcesses [ pid ] = pid ;
return Application : : EXIT_OK ;
}
void LOOLWSD : : startupDesktop ( int nDesktops )
{
for ( int nCntr = nDesktops ; nCntr ; nCntr - - )
{
if ( createDesktop ( ) < 0 )
break ;
}
}
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-10-13 12:05:42 -05:00
# ifdef __linux
char * locale = setlocale ( LC_ALL , NULL ) ;
if ( locale = = NULL | | std : : strcmp ( locale , " C " ) = = 0 )
setlocale ( LC_ALL , " en_US.utf8 " ) ;
# endif
2015-07-17 16:55:27 -05:00
if ( access ( cache . c_str ( ) , R_OK | W_OK | X_OK ) ! = 0 )
{
std : : cout < < " Unable to access " < < cache < <
" , please make sure it exists, and has write permission for this user. " < < std : : endl ;
return Application : : EXIT_UNAVAILABLE ;
}
// 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.
if ( sysTemplate = = " " )
throw MissingOptionException ( " systemplate " ) ;
if ( loTemplate = = " " )
throw MissingOptionException ( " lotemplate " ) ;
if ( childRoot = = " " )
throw MissingOptionException ( " childroot " ) ;
if ( _childId ! = 0 )
throw IncompatibleOptionsException ( " child " ) ;
if ( jail ! = " " )
throw IncompatibleOptionsException ( " jail " ) ;
if ( portNumber = = MASTER_PORT_NUMBER )
throw IncompatibleOptionsException ( " port " ) ;
2015-08-05 19:01:39 -05:00
if ( LOOLWSD : : doTest )
2015-07-17 16:55:27 -05:00
_numPreSpawnedChildren = 1 ;
2015-07-19 15:49:11 -05:00
// log pid information
{
Poco : : FileOutputStream filePID ( LOOLWSD : : PIDLOG ) ;
if ( filePID . good ( ) )
filePID < < Process : : id ( ) ;
}
2015-07-13 09:13:06 -05:00
std : : unique_lock < std : : mutex > rngLock ( _rngMutex ) ;
_childId = ( ( ( Poco : : UInt64 ) _rng . next ( ) ) < < 32 ) | _rng . next ( ) | 1 ;
rngLock . unlock ( ) ;
2015-07-18 11:35:16 -05:00
_namedMutexLOOL . lock ( ) ;
2015-07-17 13:02:25 -05:00
2015-07-13 09:13:06 -05:00
startupDesktop ( 1 ) ;
# ifdef __linux
dropCapability ( CAP_SYS_CHROOT ) ;
# else
dropCapability ( ) ;
# endif
2015-07-17 13:02:25 -05:00
// Start a server listening on the port for clients
ServerSocket svs ( portNumber , _numPreSpawnedChildren * 10 ) ;
ThreadPool threadPool ( _numPreSpawnedChildren * 2 , _numPreSpawnedChildren * 5 ) ;
HTTPServer srv ( new RequestHandlerFactory ( ) , threadPool , svs , new HTTPServerParams ) ;
srv . start ( ) ;
// And one on the port for child processes
SocketAddress addr2 ( " 127.0.0.1 " , MASTER_PORT_NUMBER ) ;
ServerSocket svs2 ( addr2 , _numPreSpawnedChildren ) ;
ThreadPool threadPool2 ( _numPreSpawnedChildren * 2 , _numPreSpawnedChildren * 5 ) ;
HTTPServer srv2 ( new RequestHandlerFactory ( ) , threadPool2 , svs2 , new HTTPServerParams ) ;
srv2 . start ( ) ;
2015-07-18 11:35:16 -05:00
_namedMutexLOOL . unlock ( ) ;
2015-07-17 13:02:25 -05:00
2015-09-11 15:31:43 -05:00
TestInput input ( * this , svs , srv ) ;
Thread inputThread ;
if ( LOOLWSD : : doTest )
{
inputThread . start ( input ) ;
waitForTerminationRequest ( ) ;
}
while ( ! LOOLWSD : : doTest & & MasterProcessSession : : _childProcesses . size ( ) > 0 )
2015-07-17 13:02:25 -05:00
{
int status ;
pid_t pid = waitpid ( - 1 , & status , WUNTRACED | WNOHANG ) ;
if ( pid > 0 )
{
if ( MasterProcessSession : : _childProcesses . find ( pid ) ! = MasterProcessSession : : _childProcesses . end ( ) )
{
if ( ( WIFEXITED ( status ) | | WIFSIGNALED ( status ) | | WTERMSIG ( status ) ) )
{
std : : cout < < Util : : logPrefix ( ) < < " One of our known child processes died : " < < std : : to_string ( pid ) < < std : : endl ;
MasterProcessSession : : _childProcesses . erase ( pid ) ;
}
if ( WCOREDUMP ( status ) )
std : : cout < < Util : : logPrefix ( ) < < " The child produced a core dump. " < < std : : endl ;
if ( WIFSTOPPED ( status ) )
std : : cout < < Util : : logPrefix ( ) < < " The child process was stopped by delivery of a signal. " < < std : : endl ;
if ( WSTOPSIG ( status ) )
std : : cout < < Util : : logPrefix ( ) < < " The child process was stopped. " < < std : : endl ;
if ( WIFCONTINUED ( status ) )
std : : cout < < Util : : logPrefix ( ) < < " The child process was resumed. " < < std : : endl ;
}
else
{
std : : cout < < Util : : logPrefix ( ) < < " None of our known child processes died : " < < std : : to_string ( pid ) < < std : : endl ;
}
}
else if ( pid < 0 )
std : : cout < < Util : : logPrefix ( ) < < " Child error: " < < strerror ( errno ) ;
2015-07-24 13:10:24 -05:00
+ + timeoutCounter ;
if ( timeoutCounter = = INTERVAL_PROBES )
{
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
2015-09-11 15:31:43 -05:00
if ( LOOLWSD : : doTest )
inputThread . join ( ) ;
2015-04-16 11:15:40 -05:00
// Terminate child processes
2015-04-20 09:43:31 -05:00
for ( auto i : MasterProcessSession : : _childProcesses )
2015-04-16 11:15:40 -05:00
{
logger ( ) . information ( Util : : logPrefix ( ) + " Requesting child process " + std : : to_string ( i . first ) + " to terminate " ) ;
Process : : requestTermination ( i . first ) ;
}
2015-03-17 18:56:15 -05:00
return Application : : EXIT_OK ;
}
bool LOOLWSD : : childMode ( ) const
{
return _childId ! = 0 ;
}
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: */