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-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>
# endif
# 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>
# 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-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>
# include <Poco/Net/NetException.h>
# 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-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"
# 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 ;
class WebSocketRequestHandler : public HTTPRequestHandler
/// Handle a WebSocket connection.
{
public :
2015-03-17 18:56:15 -05:00
WebSocketRequestHandler ( )
2015-03-04 17:14:04 -06:00
{
}
void handleRequest ( HTTPServerRequest & request , HTTPServerResponse & response ) override
{
if ( ! ( request . find ( " Upgrade " ) ! = request . end ( ) & & Poco : : icompare ( request [ " Upgrade " ] , " websocket " ) = = 0 ) )
{
response . setStatusAndReason ( HTTPResponse : : HTTP_BAD_REQUEST ) ;
response . setContentLength ( 0 ) ;
response . send ( ) ;
return ;
}
Application & app = Application : : instance ( ) ;
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-04-22 13:35:52 -05:00
std : : shared_ptr < MasterProcessSession > session ;
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-04-21 07:06:41 -05:00
{
session . reset ( new MasterProcessSession ( ws , LOOLSession : : Kind : : ToPrisoner ) ) ;
}
else
{
session . reset ( new MasterProcessSession ( ws , LOOLSession : : Kind : : ToClient ) ) ;
}
// Loop, receiving WebSocket messages either from the
// client, or from the child process (to be forwarded to
// the client).
int flags ;
int n ;
2015-05-28 08:42:38 -05:00
ws - > setReceiveTimeout ( 0 ) ;
2015-04-21 07:06:41 -05:00
do
{
char buffer [ 100000 ] ;
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 )
{
if ( ! session - > handleInput ( buffer , n ) )
n = 0 ;
}
2015-04-21 07:06:41 -05:00
if ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE )
2015-05-18 03:21:30 -05:00
{
std : : string firstLine = getFirstLine ( buffer , n ) ;
StringTokenizer tokens ( firstLine , " " , StringTokenizer : : TOK_IGNORE_EMPTY | StringTokenizer : : TOK_TRIM ) ;
int size ;
if ( tokens . count ( ) = = 2 & & tokens [ 0 ] = = " nextmessage: " & & getTokenInteger ( tokens [ 1 ] , " size " , size ) & & size > 0 )
{
2015-05-18 23:52:37 -05:00
char largeBuffer [ size ] ;
2015-05-18 03:21:30 -05:00
2015-05-28 08:42:38 -05:00
n = ws - > receiveFrame ( largeBuffer , size , flags ) ;
2015-05-18 03:21:30 -05:00
if ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE )
{
2015-05-18 23:52:37 -05:00
if ( ! session - > handleInput ( largeBuffer , 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-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-04-21 07:06:41 -05:00
app . logger ( ) . error ( Util : : logPrefix ( ) + " 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
}
}
2015-05-29 00:49:49 -05:00
catch ( IOException & exc )
2015-03-04 17:14:04 -06:00
{
2015-04-21 07:06:41 -05:00
app . 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
{
Application & app = Application : : instance ( ) ;
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-03-17 18:56:15 -05:00
app . logger ( ) . information ( line ) ;
return new WebSocketRequestHandler ( ) ;
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-12 11:58:51 -05:00
Application & app = Application : : instance ( ) ;
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-03-17 18:56:15 -05:00
char buffer [ 100000 ] ;
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-03-17 18:56:15 -05:00
app . logger ( ) . error ( Util : : logPrefix ( ) + " 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-04-16 11:15:40 -05:00
class Undertaker : public Runnable
{
public :
Undertaker ( )
{
}
void run ( ) override
{
2015-04-16 11:55:49 -05:00
bool someChildrenHaveDied = false ;
2015-04-16 11:15:40 -05:00
2015-04-20 09:43:31 -05:00
while ( ! someChildrenHaveDied | | MasterProcessSession : : _childProcesses . size ( ) > 0 )
2015-04-16 11:15:40 -05:00
{
2015-04-16 12:30:36 -05:00
int status ;
2015-04-24 03:26:41 -05:00
pid_t pid = waitpid ( - 1 , & status , 0 ) ;
if ( pid < 0 )
{
if ( errno = = ECHILD )
{
if ( ! someChildrenHaveDied )
{
// We haven't spawned any children yet, or at least none has died yet. So
// wait a bit and try again
Thread : : sleep ( 1000 ) ;
continue ;
}
else
{
// We have spawned children, and we think that we still have them running,
// but we don't, huh? Something badly messed up, or just a timing glitch,
// like we are at the moment in the process of spawning new children?
// Sleep or return from the function (i.e. finish the Undertaker thread)?
std : : cout < < Util : : logPrefix ( ) < < " No child processes even if we think there should be some!? " < < std : : endl ;
return ;
}
}
}
2015-04-16 12:30:36 -05:00
2015-04-24 05:14:57 -05:00
if ( WIFSIGNALED ( status ) )
Application : : instance ( ) . logger ( ) . error ( Util : : logPrefix ( ) + " Child " + std : : to_string ( pid ) + " killed by signal " + Util : : signalName ( WTERMSIG ( status ) ) ) ;
else
Application : : instance ( ) . logger ( ) . information ( Util : : logPrefix ( ) + " Child " + std : : to_string ( pid ) + " died normally, status: " + std : : to_string ( WEXITSTATUS ( status ) ) ) ;
2015-04-20 09:43:31 -05:00
if ( MasterProcessSession : : _childProcesses . find ( pid ) = = MasterProcessSession : : _childProcesses . end ( ) )
2015-04-16 12:30:36 -05:00
std : : cout < < Util : : logPrefix ( ) < < " (Not one of our known child processes) " < < std : : endl ;
else
2015-04-16 11:55:49 -05:00
{
2015-04-20 09:43:31 -05:00
File jailDir ( MasterProcessSession : : getJailPath ( MasterProcessSession : : _childProcesses [ pid ] ) ) ;
MasterProcessSession : : _childProcesses . erase ( pid ) ;
2015-04-16 12:30:36 -05:00
someChildrenHaveDied = true ;
if ( ! jailDir . exists ( ) | | ! jailDir . isDirectory ( ) )
{
Application : : instance ( ) . logger ( ) . error ( Util : : logPrefix ( ) + " Jail ' " + jailDir . path ( ) + " ' does not exist or is not a directory " ) ;
}
2015-04-16 11:15:40 -05:00
else
{
2015-04-16 12:30:36 -05:00
std : : cout < < Util : : logPrefix ( ) < < " Removing jail tree " < < jailDir . path ( ) < < std : : endl ;
jailDir . remove ( true ) ;
2015-04-16 11:15:40 -05:00
}
}
}
std : : cout < < Util : : logPrefix ( ) < < " All child processes have died (I hope) " < < std : : endl ;
}
} ;
2015-05-08 13:24:46 -05:00
int LOOLWSD : : portNumber = DEFAULT_CLIENT_PORT_NUMBER ;
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-04-28 03:02:29 -05:00
int LOOLWSD : : _numPreSpawnedChildren = 10 ;
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-03-17 18:56:15 -05:00
LOOLWSD : : LOOLWSD ( ) :
_doTest ( false ) ,
_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 ( ) ;
}
void LOOLWSD : : defineOptions ( OptionSet & options )
{
ServerApplication : : defineOptions ( options ) ;
2015-04-15 07:21:17 -05:00
options . addOption ( Option ( " help " , " " , " Display help information on command line arguments. " )
2015-03-17 18:56:15 -05:00
. required ( false )
. repeatable ( false ) ) ;
2015-05-08 13:29:13 -05:00
options . addOption ( Option ( " port " , " " , " Port number to listen to (default: " + std : : to_string ( DEFAULT_CLIENT_PORT_NUMBER ) + " ), "
" must not be " + std : : to_string ( MASTER_PORT_NUMBER ) + " . " )
2015-03-17 18:56:15 -05:00
. required ( false )
. repeatable ( false )
2015-03-27 11:23:27 -05:00
. argument ( " port number " ) ) ;
2015-03-17 18:56:15 -05:00
2015-06-04 09:07:49 -05:00
options . 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 " ) ) ;
2015-04-15 07:21:17 -05:00
options . addOption ( Option ( " systemplate " , " " , " Path to a template tree with shared libraries etc to be used as source for chroot jails for child processes. " )
2015-04-08 09:22:42 -05:00
. required ( false )
. repeatable ( false )
. argument ( " directory " ) ) ;
2015-04-15 07:21:17 -05:00
options . 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. " )
2015-04-08 09:22:42 -05:00
. required ( false )
. repeatable ( false )
. argument ( " directory " ) ) ;
2015-04-15 07:21:17 -05:00
options . 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. " )
2015-04-08 09:22:42 -05:00
. required ( false )
2015-03-17 18:56:15 -05:00
. repeatable ( false )
. argument ( " directory " ) ) ;
2015-04-15 07:21:17 -05:00
options . addOption ( Option ( " losubpath " , " " , " Relative path where the LibreOffice installation will be copied inside a jail (default: ' " + loSubPath + " '). " )
2015-04-08 09:22:42 -05:00
. required ( false )
. repeatable ( false )
. argument ( " relative path " ) ) ;
2015-04-15 07:21:17 -05:00
options . addOption ( Option ( " numprespawns " , " " , " Number of child processes to keep started in advance and waiting for new clients. " )
2015-04-08 09:22:42 -05:00
. required ( false )
. repeatable ( false )
2015-06-01 11:52:48 -05:00
. argument ( " number " ) ) ;
2015-04-08 09:22:42 -05:00
2015-04-15 07:21:17 -05:00
options . addOption ( Option ( " test " , " " , " Interactive testing. " )
2015-03-17 18:56:15 -05:00
. required ( false )
. repeatable ( false ) ) ;
2015-04-15 07:21:17 -05:00
options . addOption ( Option ( " child " , " " , " For internal use only. " )
2015-03-17 18:56:15 -05:00
. required ( false )
. repeatable ( false )
2015-03-27 11:23:27 -05:00
. argument ( " child id " ) ) ;
2015-04-08 09:22:42 -05:00
2015-04-15 07:21:17 -05:00
options . addOption ( Option ( " jail " , " " , " For internal use only. " )
2015-04-08 09:22:42 -05:00
. required ( false )
. repeatable ( false )
. argument ( " directory " ) ) ;
2015-05-13 06:22:19 -05:00
# if ENABLE_DEBUG
options . addOption ( Option ( " uid " , " " , " Uid to assume if running under sudo for debugging purposes. " )
. required ( false )
. repeatable ( false )
. argument ( " uid " ) ) ;
# endif
2015-03-17 18:56:15 -05:00
}
2015-03-25 07:39:58 -05:00
void LOOLWSD : : handleOption ( const std : : string & name , const std : : string & value )
{
ServerApplication : : handleOption ( name , value ) ;
if ( name = = " help " )
{
displayHelp ( ) ;
exit ( Application : : EXIT_OK ) ;
}
else if ( name = = " port " )
portNumber = std : : stoi ( value ) ;
2015-06-04 09:07:49 -05:00
else if ( name = = " cache " )
cache = value ;
2015-04-08 09:22:42 -05:00
else if ( name = = " systemplate " )
sysTemplate = value ;
else if ( name = = " lotemplate " )
loTemplate = value ;
else if ( name = = " childroot " )
childRoot = value ;
else if ( name = = " losubpath " )
loSubPath = value ;
2015-04-15 07:17:15 -05:00
else if ( name = = " numprespawns " )
2015-04-28 03:02:29 -05:00
_numPreSpawnedChildren = std : : stoi ( value ) ;
2015-03-25 07:39:58 -05:00
else if ( name = = " test " )
_doTest = true ;
else if ( name = = " child " )
_childId = std : : stoull ( value ) ;
2015-04-08 09:22:42 -05:00
else if ( name = = " jail " )
jail = value ;
2015-05-13 06:22:19 -05:00
# if ENABLE_DEBUG
else if ( name = = " uid " )
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-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-05-13 05:00:33 -05:00
if ( setuid ( getuid ( ) ) ! = 0 ) {
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-05-22 11:42:36 -05: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-03-17 18:56:15 -05:00
int LOOLWSD : : childMain ( )
{
2015-05-08 04:25:24 -05:00
std : : cout < < Util : : logPrefix ( ) < < " Child here! id= " < < _childId < < std : : endl ;
2015-03-04 17:14:04 -06:00
2015-05-22 04:57:17 -05:00
# ifdef __linux
2015-05-19 05:48:20 -05:00
dropCapability ( CAP_FOWNER ) ;
2015-05-22 04:57:17 -05:00
# endif
2015-04-08 09:22:42 -05:00
// We use the same option set for both parent and child loolwsd,
// so must check options required in the child (but not in the
// parent) separately now. And also for options that are
// meaningless to the child.
if ( jail = = " " )
throw MissingOptionException ( " systemplate " ) ;
if ( sysTemplate ! = " " )
throw IncompatibleOptionsException ( " systemplate " ) ;
if ( loTemplate ! = " " )
throw IncompatibleOptionsException ( " lotemplate " ) ;
if ( childRoot ! = " " )
throw IncompatibleOptionsException ( " childroot " ) ;
if ( chroot ( jail . c_str ( ) ) = = - 1 )
{
logger ( ) . error ( " chroot( \" " + jail + " \" ) failed: " + strerror ( errno ) ) ;
exit ( 1 ) ;
}
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:23:33 -05:00
2015-04-08 09:22:42 -05:00
if ( chdir ( " / " ) = = - 1 )
{
logger ( ) . error ( std : : string ( " chdir( \" / \" ) in jail failed: " ) + strerror ( errno ) ) ;
exit ( 1 ) ;
}
2015-04-23 08:25:59 -05:00
if ( std : : getenv ( " SLEEPFORDEBUGGER " ) )
{
std : : cout < < " Sleeping " < < std : : getenv ( " SLEEPFORDEBUGGER " ) < < " seconds, " < <
2015-05-29 00:49:49 -05:00
" attach process " < < Process : : id ( ) < < " in debugger now. " < < std : : endl ;
2015-04-23 08:25:59 -05:00
Thread : : sleep ( std : : stoul ( std : : getenv ( " SLEEPFORDEBUGGER " ) ) * 1000 ) ;
}
2015-05-08 07:46:10 -05:00
try
2015-03-04 17:14:04 -06: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 " ) ;
return Application : : EXIT_UNAVAILABLE ;
}
2015-03-04 17:14:04 -06: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-05-08 07:46:10 -05:00
ChildProcessSession session ( ws , loKit ) ;
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-05-08 07:46:10 -05:00
std : : string hello ( " child " + std : : to_string ( _childId ) ) ;
session . sendTextFrame ( hello ) ;
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 )
if ( ! session . handleInput ( buffer , n ) )
n = 0 ;
}
while ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE ) ;
}
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-04-30 13:06:47 -05:00
// Safest to just bluntly exit
_Exit ( Application : : EXIT_OK ) ;
2015-03-17 18:56:15 -05:00
}
2015-03-04 17:14:04 -06:00
2015-03-17 18:56:15 -05:00
int LOOLWSD : : main ( const std : : vector < std : : string > & args )
{
2015-04-28 03:17:26 -05:00
if ( childMode ( ) )
return childMain ( ) ;
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-06-04 09:07:49 -05:00
if ( access ( cache . c_str ( ) , R_OK | W_OK | X_OK ) ! = 0 )
2015-04-21 08:51:28 -05:00
{
2015-06-04 09:07:49 -05:00
std : : cout < < " Unable to access " < < cache < <
2015-04-21 08:51:28 -05:00
" , please make sure it exists, and has write permission for this user. " < < std : : endl ;
return Application : : EXIT_UNAVAILABLE ;
}
2015-04-08 09:22:42 -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.
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 " ) ;
2015-05-08 13:24:46 -05:00
if ( portNumber = = MASTER_PORT_NUMBER )
throw IncompatibleOptionsException ( " port " ) ;
2015-04-08 09:22:42 -05:00
2015-04-16 11:15:40 -05:00
// Set up a thread to reap child processes and clean up after them
Undertaker undertaker ;
Thread undertakerThread ;
undertakerThread . start ( undertaker ) ;
2015-05-08 13:24:46 -05:00
// Start a server listening on the port for clients
ServerSocket svs ( portNumber , _numPreSpawnedChildren * 10 ) ;
2015-05-29 00:49:49 -05:00
ThreadPool threadPool ( _numPreSpawnedChildren * 2 , _numPreSpawnedChildren * 5 ) ;
2015-05-07 10:26:03 -05:00
HTTPServer srv ( new RequestHandlerFactory ( ) , threadPool , svs , new HTTPServerParams ) ;
2015-03-04 17:14:04 -06:00
2015-03-17 18:56:15 -05:00
srv . start ( ) ;
2015-03-04 17:14:04 -06:00
2015-05-08 13:24:46 -05:00
// And one on the port for child processes
SocketAddress addr2 ( " 127.0.0.1 " , MASTER_PORT_NUMBER ) ;
ServerSocket svs2 ( addr2 , _numPreSpawnedChildren ) ;
2015-05-29 00:49:49 -05:00
ThreadPool threadPool2 ( _numPreSpawnedChildren * 2 , _numPreSpawnedChildren * 5 ) ;
2015-05-08 13:24:46 -05:00
HTTPServer srv2 ( new RequestHandlerFactory ( ) , threadPool2 , svs2 , new HTTPServerParams ) ;
srv2 . start ( ) ;
2015-04-28 03:01:18 -05:00
if ( _doTest )
2015-04-28 03:02:29 -05:00
_numPreSpawnedChildren = 1 ;
2015-04-28 03:01:18 -05:00
2015-04-28 03:02:29 -05:00
for ( int i = 0 ; i < _numPreSpawnedChildren ; i + + )
2015-04-28 02:57:05 -05:00
MasterProcessSession : : preSpawn ( ) ;
2015-03-17 18:56:15 -05:00
TestInput input ( * this , svs , srv ) ;
2015-04-16 11:15:40 -05:00
Thread inputThread ;
2015-03-17 18:56:15 -05:00
if ( _doTest )
{
2015-04-16 11:15:40 -05:00
inputThread . start ( input ) ;
2015-03-04 17:14:04 -06:00
}
2015-03-17 18:56:15 -05:00
waitForTerminationRequest ( ) ;
2015-04-16 11:15:40 -05:00
// Doing this causes a crash. So just let the process proceed and exit.
// srv.stop();
2015-03-17 18:56:15 -05:00
if ( _doTest )
2015-04-16 11:15:40 -05:00
inputThread . join ( ) ;
// 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 ) ;
}
undertakerThread . join ( ) ;
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: */