35#include <nawarun/nawarun.h>
41using namespace nawarun;
45 unique_ptr<RequestHandler> requestHandlerPtr;
46 optional<string> configFile;
47 atomic<bool> readyToReconfigure(
false);
49 unsigned int terminationTimeout = 10;
52 void shutdown(
int signum) {
53 NLOG_INFO(logger,
"Terminating on signal " << signum)
56 if (requestHandlerPtr) {
58 requestHandlerPtr->stop();
61 sleep(terminationTimeout);
62 if (requestHandlerPtr && signum != SIGUSR1) {
63 NLOG_INFO(logger,
"Enforcing termination now, ignoring pending requests.")
64 requestHandlerPtr->terminate();
72 void* loadAppSymbol(
void* appOpen,
char const* symbolName,
string const& error) {
73 void* symbol = dlsym(appOpen, symbolName);
74 auto dlsymError = dlerror();
76 throw Exception(__FUNCTION__, 11, error, dlsymError);
82 void closeApp(
void* appOpen) {
90 void setTerminationTimeout(
Config const& config) {
91 string terminationTimeoutStr = config[{
"system",
"termination_timeout"}];
92 if (!terminationTimeoutStr.empty()) {
94 auto newTerminationTimeout = stoul(terminationTimeoutStr);
95 terminationTimeout = newTerminationTimeout;
96 }
catch (invalid_argument& e) {
97 NLOG_WARNING(logger,
"WARNING: Invalid termination timeout given in configuration, default value "
98 "or previous value will be used.")
107 optional<Config>
loadConfig(
bool reload =
false) {
110 return Config(*configFile);
114 NLOG_WARNING(logger, "WARNING: App will not be reloaded as well")
129 void doPrivilegeDowngrade(PrivilegeDowngradeData const& data) {
130 auto const& [uid, gid, supplementaryGroups] = data;
131 if (uid != 0 && gid != 0 && setgroups(supplementaryGroups.size(), &supplementaryGroups[0]) != 0) {
132 NLOG_ERROR(logger,
"Fatal Error: Could not set supplementary groups during privilege downgrade.")
135 if (setgid(gid) != 0 || setuid(uid) != 0) {
136 NLOG_ERROR(logger,
"Fatal Error: Could not set privileges during privilege downgrade.")
141 void printHelpAndExit() {
142 cout <<
"nawarun is the runner for NAWA web applications.\n\n"
143 "Usage: nawarun [<overrides>] [<config-file> | --no-config-file]\n\n"
144 "Format for configuration overrides: --<category>:<key>=<value>\n\n"
145 "If no config file is given, nawarun will try to use config.ini from the current\n"
146 "working directory, unless the --no-config-file option is given. The config file\n"
147 "as well as --no-config-file are only accepted as the last command line argument\n"
148 "after the overrides.\n";
152 void setUpSignalHandlers() {
153 signal(SIGINT, shutdown);
154 signal(SIGTERM, shutdown);
155 signal(SIGUSR1, shutdown);
156 signal(SIGHUP, reload);
159 void setUpLogging(
Config const& config) {
160 auto configuredLogLevel = config[{
"logging",
"level"}];
161 if (configuredLogLevel ==
"off") {
162 Log::setOutputLevel(Log::Level::OFF);
163 }
else if (configuredLogLevel ==
"error") {
164 Log::setOutputLevel(Log::Level::ERROR);
165 }
else if (configuredLogLevel ==
"warning") {
166 Log::setOutputLevel(Log::Level::WARNING);
167 }
else if (configuredLogLevel ==
"debug") {
168 Log::setOutputLevel(Log::Level::DEBUG);
170 if (config[{
"logging",
"extended"}] ==
"on") {
171 Log::setExtendedFormat(
true);
177unsigned int nawarun::getConcurrency(
Config const& config) {
180 cReal = config.
isSet({
"system",
"threads"})
181 ? stod(config[{
"system",
"threads"}])
183 }
catch (invalid_argument& e) {
184 NLOG_WARNING(logger,
"WARNING: Invalid value given for system/concurrency given in the config file.")
187 if (config[{
"system",
"concurrency"}] ==
"hardware") {
188 cReal = max(1.0, thread::hardware_concurrency() * cReal);
190 return static_cast<unsigned int>(cReal);
193pair<init_t*, shared_ptr<HandleRequestFunctionWrapper>> nawarun::loadAppFunctions(
Config const& config) {
195 string appPath = config[{
"application",
"path"}];
196 if (appPath.empty()) {
197 throw Exception(__FUNCTION__, 1,
"Application path not set in config file.");
199 void* appOpen = dlopen(appPath.c_str(), RTLD_LAZY);
201 throw Exception(__FUNCTION__, 2,
"Application file could not be loaded.", dlerror());
210 string appVersionError =
"Could not read nawa version from application.";
211 auto appNawaVersionMajor = (
int*) loadAppSymbol(appOpen,
"nawa_version_major", appVersionError);
212 auto appNawaVersionMinor = (
int*) loadAppSymbol(appOpen,
"nawa_version_minor", appVersionError);
214 throw Exception(__FUNCTION__, 3,
"App has been compiled against another version of NAWA.");
216 auto appInit = (init_t*) loadAppSymbol(appOpen,
"init",
"Could not load init function from application.");
217 auto appHandleRequest = (handleRequest_t*) loadAppSymbol(appOpen,
"handleRequest",
218 "Could not load handleRequest function from application.");
219 return {appInit, make_shared<HandleRequestFunctionWrapper>(appHandleRequest, appOpen, closeApp)};
222void nawarun::reload(
int signum) {
224 NLOG_WARNING(logger,
"WARNING: Reloading is not supported without config file and will therefore not "
229 if (requestHandlerPtr && readyToReconfigure) {
230 NLOG_INFO(logger,
"Reloading config and app on signal " << signum)
231 readyToReconfigure =
false;
235 readyToReconfigure =
true;
240 setTerminationTimeout(*config);
243 shared_ptr<HandleRequestFunctionWrapper> appHandleRequest;
245 tie(appInit, appHandleRequest) = loadAppFunctions(*config);
248 NLOG_DEBUG(logger, "Debug info: " << e.getDebugMessage())
249 NLOG_WARNING(logger, "WARNING: Configuration will be reloaded anyway")
252 requestHandlerPtr->reconfigure(nullopt, nullopt, config);
253 readyToReconfigure = true;
258 AppInit appInitStruct(*config, getConcurrency(*config));
259 auto initReturn = appInit(appInitStruct);
262 if (initReturn != 0) {
264 "ERROR: App init function returned " << initReturn <<
" -- cancelling reload of app.")
265 NLOG_WARNING(logger, "WARNING: Configuration will be reloaded anyway")
268 requestHandlerPtr->reconfigure(nullopt, nullopt, config);
269 readyToReconfigure = true;
274 requestHandlerPtr->reconfigure(appHandleRequest, appInitStruct.accessFilters(), appInitStruct.config());
275 readyToReconfigure = true;
280optional<PrivilegeDowngradeData> nawarun::preparePrivilegeDowngrade(
Config const& config) {
281 auto initialUID = getuid();
284 vector<gid_t> supplementaryGroups;
286 if (initialUID == 0) {
287 if (!config.isSet({
"privileges",
"user"}) || !config.isSet({
"privileges",
"group"})) {
289 "Running as root and user or group for privilege downgrade is not set in the configuration.");
291 string username = config[{
"privileges",
"user"}];
292 string groupname = config[{
"privileges",
"group"}];
295 privUser = getpwnam(username.c_str());
296 privGroup = getgrnam(groupname.c_str());
297 if (privUser ==
nullptr || privGroup ==
nullptr) {
299 "The user or group name for privilege downgrade given in the configuration is invalid.");
301 privUID = privUser->pw_uid;
302 privGID = privGroup->gr_gid;
303 if (privUID == 0 || privGID == 0) {
304 NLOG_WARNING(logger,
"WARNING: nawarun will be running as user or group root. Security risk!")
308 getgrouplist(username.c_str(), privGID,
nullptr, &n);
309 supplementaryGroups.resize(n, 0);
310 if (getgrouplist(username.c_str(), privGID, oss::getGIDPtrForGetgrouplist(&supplementaryGroups[0]), &n) != n) {
311 NLOG_WARNING(logger,
"WARNING: Could not get supplementary groups for user " << username)
312 supplementaryGroups = {privGID};
315 return make_tuple(privUID, privGID, supplementaryGroups);
318 NLOG_WARNING(logger,
"WARNING: Not starting as root, cannot set privileges.")
322void nawarun::replaceLogger(
Log const& log) {
333Parameters nawarun::parseCommandLine(
int argc,
char** argv) {
336 optional<string> configPath;
337 vector<pair<pair<string, string>,
string>> overrides;
338 bool noConfigFile =
false;
339 for (
size_t i = 1; i < argc; ++i) {
340 string currentArg(argv[i]);
342 if (i == 1 && (currentArg ==
"--help" || currentArg ==
"-h")) {
346 if (currentArg.substr(0, 2) ==
"--") {
348 if (idAndVal.size() == 2) {
350 string const& value = idAndVal.at(1);
351 if (categoryAndKey.size() == 2) {
352 string const& category = categoryAndKey.at(0);
353 string const& key = categoryAndKey.at(1);
354 overrides.push_back({{category, key}, value});
363 if (currentArg !=
"--no-config-file") {
364 configPath = currentArg;
369 NLOG_WARNING(logger,
"WARNING: Invalid command line argument \"" << currentArg <<
"\" will be ignored")
374 if (!configPath && !noConfigFile) {
375 configPath =
"config.ini";
378 return {configPath, overrides};
381int nawarun::run(Parameters
const& parameters) {
382 setUpSignalHandlers();
384 configFile = parameters.configFile;
388 config->override(parameters.configOverrides);
390 setTerminationTimeout(*config);
391 setUpLogging(*config);
394 optional<PrivilegeDowngradeData> privilegeDowngradeData;
396 privilegeDowngradeData = preparePrivilegeDowngrade(*config);
399 NLOG_DEBUG(logger, "Debug info: " << e.getDebugMessage())
407 tie(appInit, appHandleRequest) = loadAppFunctions(*config);
410 NLOG_DEBUG(logger, "Debug info: " << e.getDebugMessage())
416 auto concurrency = getConcurrency(*config);
418 requestHandlerPtr = RequestHandler::newRequestHandler(appHandleRequest, *config, concurrency);
421 NLOG_DEBUG(logger, "Debug info: " << e.getDebugMessage())
426 if (privilegeDowngradeData) {
427 doPrivilegeDowngrade(*privilegeDowngradeData);
432 AppInit appInitStruct(*config, concurrency);
433 auto initReturn = appInit(appInitStruct);
436 if (initReturn != 0) {
437 NLOG_ERROR(logger,
"Fatal Error: App init function returned " << initReturn <<
" -- exiting.")
442 requestHandlerPtr->reconfigure(nullopt, appInitStruct.accessFilters(), appInitStruct.config());
446 requestHandlerPtr->start();
447 readyToReconfigure =
true;
450 NLOG_DEBUG(logger, "Debug info: " << e.getDebugMessage())
453 requestHandlerPtr->join();
456 requestHandlerPtr.reset(
nullptr);
This file will be configured by CMake and contains the necessary properties to ensure that a loaded a...
const int nawa_version_major
const int nawa_version_minor
Reader for config files and accessor to config values.
Exception class that can be used by apps to catch errors resulting from nawa function calls.
Simple class for (not (yet) thread-safe) logging to stderr or to any other output stream.
#define NLOG_WARNING(Logger, Message)
#define NLOG_INFO(Logger, Message)
#define NLOG_DEBUG(Logger, Message)
#define NLOG_ERROR(Logger, Message)
Handles and serves incoming requests via the NAWA app.
bool isSet(std::pair< std::string, std::string > const &key) const
virtual std::string getMessage() const noexcept
virtual std::string getDebugMessage() const noexcept
std::vector< std::string > splitString(std::string str, char delimiter, bool ignoreEmpty=false)
Contains useful functions that improve the readability and facilitate maintenance of the NAWA code.