/*
* ServerMain.cpp
*
* Copyright (C) 2009-11 by RStudio, Inc.
*
* This program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "ServerAddins.hpp"
#include "ServerAppArmor.hpp"
#include "ServerBrowser.hpp"
#include "ServerOffline.hpp"
#include "ServerPAMAuth.hpp"
#include "ServerSessionProxy.hpp"
#include "ServerREnvironment.hpp"
#include "ServerSessionManager.hpp"
using namespace core ;
using namespace server;
namespace {
bool mainPageFilter(const core::http::Request& request,
core::http::Response* pResponse)
{
return server::browser::supportedBrowserFilter(request, pResponse) &&
auth::handler::mainPageFilter(request, pResponse);
}
http::UriHandlerFunction blockingFileHandler()
{
Options& options = server::options();
return gwt::fileHandlerFunction(options.wwwLocalPath(),
"/",
mainPageFilter);
}
//
// some fancy footwork is required to take the standand blocking file handler
// and make it work within a secure async context.
//
auth::SecureAsyncUriHandlerFunction secureAsyncFileHandler()
{
// create a functor which can adapt a synchronous file handler into
// an asynchronous handler
class FileRequestHandler {
public:
static void handleRequest(
const http::UriHandlerFunction& fileHandlerFunction,
boost::shared_ptr pConnection)
{
fileHandlerFunction(pConnection->request(), &(pConnection->response()));
pConnection->writeResponse();
}
};
// use this functor to generate an async uri handler function from the
// stock blockingFileHandler (defined above)
http::AsyncUriHandlerFunction asyncFileHandler =
boost::bind(FileRequestHandler::handleRequest, blockingFileHandler(), _1);
// finally, adapt this to be a secure async uri handler by binding out the
// first parameter (username, which the gwt file handler knows nothing of)
return boost::bind(asyncFileHandler, _2);
}
// http server
boost::scoped_ptr s_pHttpServer;
Error httpServerInit()
{
// create http server
s_pHttpServer.reset(new http::TcpIpAsyncServer("RStudio"));
// set server options
s_pHttpServer->setAbortOnResourceError(true);
// initialize the http server
Options& options = server::options();
return s_pHttpServer->init(options.wwwAddress(), options.wwwPort());
}
void httpServerAddHandlers()
{
// establish json-rpc handlers
using namespace server::auth;
using namespace server::session_proxy;
uri_handlers::add("/rpc", secureAsyncJsonRpcHandler(proxyRpcRequest));
uri_handlers::add("/events", secureAsyncJsonRpcHandler(proxyEventsRequest));
// establish content handlers
uri_handlers::add("/graphics", secureAsyncHttpHandler(proxyContentRequest));
uri_handlers::add("/upload", secureAsyncUploadHandler(proxyContentRequest));
uri_handlers::add("/export", secureAsyncHttpHandler(proxyContentRequest));
uri_handlers::add("/source", secureAsyncHttpHandler(proxyContentRequest));
uri_handlers::add("/content", secureAsyncHttpHandler(proxyContentRequest));
uri_handlers::add("/diff", secureAsyncHttpHandler(proxyContentRequest));
uri_handlers::add("/file_show", secureAsyncHttpHandler(proxyContentRequest));
uri_handlers::add("/agreement", secureAsyncHttpHandler(proxyContentRequest));
// content handlers which might be accessed outside the context of the
// workbench get secure + authentication when required
uri_handlers::add("/help", secureAsyncHttpHandler(proxyContentRequest, true));
uri_handlers::add("/files", secureAsyncHttpHandler(proxyContentRequest, true));
uri_handlers::add("/custom", secureAsyncHttpHandler(proxyContentRequest, true));
uri_handlers::add("/session", secureAsyncHttpHandler(proxyContentRequest, true));
uri_handlers::add("/docs", secureAsyncHttpHandler(secureAsyncFileHandler(), true));
// establish logging handler
uri_handlers::addBlocking("/log", secureJsonRpcHandler(gwt::handleLogRequest));
// establish progress handler
FilePath wwwLocalPath(server::options().wwwLocalPath());
FilePath progressPagePath = wwwLocalPath.complete("progress.htm");
uri_handlers::addBlocking("/progress",
secureHttpHandler(boost::bind(
core::text::handleSecureTemplateRequest,
_1, progressPagePath, _2, _3)));
// establish browser unsupported handler
using namespace server::browser;
uri_handlers::addBlocking(kBrowserUnsupported,
handleBrowserUnsupportedRequest);
// restrct access to templates directory
uri_handlers::addBlocking("/templates", http::notFoundHandler);
// add default handler for gwt app
uri_handlers::setBlockingDefault(blockingFileHandler());
}
// bogus SIGCHLD handler (never called)
void handleSIGCHLD(int)
{
}
// wait for and handle child exit signals
Error waitForChildExits()
{
// setup bogus handler for SIGCHLD (if we don't do this then
// we can't successfully block/wait for the signal). This also
// allows us to specify SA_NOCLDSTOP
struct sigaction sa;
::memset(&sa, 0, sizeof sa);
sa.sa_handler = handleSIGCHLD;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_NOCLDSTOP;
int result = ::sigaction(SIGCHLD, &sa, NULL);
if (result != 0)
return systemError(errno, ERROR_LOCATION);
// block SIGCHLD (so we can sigwait on it below). note that on OSX
// we also need to wait for termination related signals (otherwise
// they are never delivered)
sigset_t wait_mask;
sigemptyset(&wait_mask);
sigaddset(&wait_mask, SIGCHLD);
#ifdef __APPLE__
sigaddset(&wait_mask, SIGINT);
sigaddset(&wait_mask, SIGQUIT);
sigaddset(&wait_mask, SIGTERM);
#endif
result = ::pthread_sigmask(SIG_BLOCK, &wait_mask, NULL);
if (result != 0)
return systemError(result, ERROR_LOCATION);
// wait for child exits
for(;;)
{
// perform wait
int sig = 0;
int result = ::sigwait(&wait_mask, &sig);
if (result != 0)
return systemError(result, ERROR_LOCATION);
// SIGCHLD
if (sig == SIGCHLD)
{
sessionManager().notifySIGCHLD();
}
#ifdef __APPLE__
else if (sig == SIGINT || sig == SIGQUIT || sig == SIGTERM)
{
::exit(sig);
}
#endif
// Unexpected signal
else
{
LOG_WARNING_MESSAGE("Unexpected signal returned from sigwait: " +
boost::lexical_cast(sig));
}
}
// keep compiler happy (we never get here)
return Success();
}
} // anonymous namespace
// provide global access to handlers
namespace server {
namespace uri_handlers {
void add(const std::string& prefix,
const http::AsyncUriHandlerFunction& handler)
{
s_pHttpServer->addHandler(prefix, handler);
}
void addBlocking(const std::string& prefix,
const http::UriHandlerFunction& handler)
{
s_pHttpServer->addBlockingHandler(prefix, handler);
}
void setDefault(const http::AsyncUriHandlerFunction& handler)
{
s_pHttpServer->setDefaultHandler(handler);
}
// set blocking default handler
void setBlockingDefault(const http::UriHandlerFunction& handler)
{
s_pHttpServer->setBlockingDefaultHandler(handler);
}
} // namespace uri_handlers
} // namespace server
int main(int argc, char * const argv[])
{
try
{
// initialize log
initializeSystemLog("rserver", core::system::kLogLevelWarning);
// read program options
Options& options = server::options();
ProgramStatus status = options.read(argc, argv);
if ( status.exit() )
return status.exitCode() ;
// daemonize if requested
if (options.serverDaemonize())
{
Error error = util::system::daemonize();
if (error)
return core::system::exitFailure(error, ERROR_LOCATION);
error = core::system::ignoreTerminalSignals();
if (error)
return core::system::exitFailure(error, ERROR_LOCATION);
// set file creation mask to 022 (might have inherted 0 from init)
setUMask(util::system::OthersNoWriteMask);
}
// detect R environment variables (calls R (and this forks) so must
// happen after daemonize so that upstart script can correctly track us
std::string errMsg;
bool detected = r_environment::initialize(&errMsg);
if (!detected)
{
program_options::reportError(errMsg, ERROR_LOCATION);
return EXIT_FAILURE;
}
// increase the number of open files allowed (need more files
// so we can supports lots of concurrent connectins)
if (util::system::realUserIsRoot())
{
Error error = setResourceLimit(util::system::FilesLimit, 4096);
if (error)
return core::system::exitFailure(error, ERROR_LOCATION);
}
// set working directory
Error error = FilePath(options.serverWorkingDir()).makeCurrentPath();
if (error)
return core::system::exitFailure(error, ERROR_LOCATION);
// initialize crypto utils
core::system::crypto::initialize();
// initialize secure cookie module
error = auth::secure_cookie::initialize();
if (error)
return core::system::exitFailure(error, ERROR_LOCATION);
// initialize the session proxy
error = session_proxy::initialize();
if (error)
return core::system::exitFailure(error, ERROR_LOCATION);
// initialize http server
error = httpServerInit();
if (error)
return core::system::exitFailure(error, ERROR_LOCATION);
// add handlers and initiliaze addins (offline has distinct behavior)
if (server::options().serverOffline())
{
offline::httpServerAddHandlers();
}
else
{
// add handlers
httpServerAddHandlers();
// initialize addins
error = addins::initialize();
if (error)
return core::system::exitFailure(error, ERROR_LOCATION);
// initialize pam auth if we don't already have an auth handler
if (!auth::handler::isRegistered())
{
error = pam_auth::initialize();
if (error)
return core::system::exitFailure(error, ERROR_LOCATION);
}
}
// enforce restricted mode if we are running under app armor
// note that failure to do this (for whatever unanticipated reason)
// is not considered fatal however it is logged as an error
// so the sys-admin is informed
if (options.serverAppArmorEnabled())
{
error = app_armor::enforceRestricted();
if (error)
LOG_ERROR(error);
}
// give up root privilige if requested
std::string runAsUser = options.serverUser();
if (!runAsUser.empty())
{
// drop root priv
Error error = util::system::temporarilyDropPriv(runAsUser);
if (error)
return core::system::exitFailure(error, ERROR_LOCATION);
}
// run special verify installation mode if requested
if (options.verifyInstallation())
{
Error error = session_proxy::runVerifyInstallationSession();
if (error)
return core::system::exitFailure(error, ERROR_LOCATION);
return EXIT_SUCCESS;
}
// run http server
error = s_pHttpServer->run(options.wwwThreadPoolSize());
if (error)
return core::system::exitFailure(error, ERROR_LOCATION);
// wait for child exits
error = waitForChildExits();
if (error)
return core::system::exitFailure(error, ERROR_LOCATION);
// NOTE: we never get here because waitForChildExits waits forever
return EXIT_SUCCESS;
}
CATCH_UNEXPECTED_EXCEPTION
// if we got this far we had an unexpected exception
return EXIT_FAILURE ;
}