diff --git a/src/Main/main.cxx b/src/Main/main.cxx index 5873c17e0..74a61afc6 100644 --- a/src/Main/main.cxx +++ b/src/Main/main.cxx @@ -386,6 +386,26 @@ void fgResetIdleState() fgRegisterIdleHandler( &fgIdleFunction ); } +void fgInitSecureMode() +{ + bool secureMode = true; + if (Options::sharedInstance()->isOptionSet("allow-nasal-from-sockets")) { + SG_LOG(SG_GENERAL, SG_ALERT, "\n!! Network connections allowed to use Nasal !!\n" + "Network connections will be allowed full access to the simulator \n" + "including running arbitrary scripts. Ensure you have adequate security\n" + "(such as a firewall which blocks external connections).\n"); + secureMode = false; + } + + // it's by design that we overwrite any existing property tree value + // here - this prevents an aircraft or add-on setting the property + // value underneath us, eg in their -set.xml + SGPropertyNode_ptr secureFlag = fgGetNode("/sim/secure-flag", true); + secureFlag->setBoolValue(secureMode); + secureFlag->setAttributes(SGPropertyNode::READ | + SGPropertyNode::PRESERVE | + SGPropertyNode::PROTECTED); +} static void upper_case_property(const char *name) { @@ -470,8 +490,7 @@ int fgMainInit( int argc, char **argv ) } std::string version(FLIGHTGEAR_VERSION); - SG_LOG( SG_GENERAL, SG_INFO, "FlightGear: Version " - << version ); + SG_LOG( SG_GENERAL, SG_INFO, "FlightGear: Version " << version ); SG_LOG( SG_GENERAL, SG_INFO, "FlightGear: Build Type " << FG_BUILD_TYPE ); SG_LOG( SG_GENERAL, SG_INFO, "Built with " << SG_COMPILER_STR); SG_LOG( SG_GENERAL, SG_INFO, "Jenkins number/ID " << HUDSON_BUILD_NUMBER << ":" @@ -529,9 +548,11 @@ int fgMainInit( int argc, char **argv ) } #else if (showLauncher) { - SG_LOG(SG_GENERAL, SG_ALERT, "\n!Launcher requested, but FlightGear was compiled without Qt support!"); + SG_LOG(SG_GENERAL, SG_ALERT, "\n!Launcher requested, but FlightGear was compiled without Qt support!\n"); } #endif + + fgInitSecureMode(); fgInitAircraftPaths(false); configResult = fgInitAircraft(false); diff --git a/src/Main/options.cxx b/src/Main/options.cxx index 313dd7ece..9ef12549b 100644 --- a/src/Main/options.cxx +++ b/src/Main/options.cxx @@ -1621,6 +1621,7 @@ struct OptionDesc { {"language", true, OPTION_IGNORE, "", false, "", 0 }, {"console", false, OPTION_FUNC, "", false, "", fgOptConsole }, {"launcher", false, OPTION_IGNORE, "", false, "", 0 }, + {"allow-nasal-from-sockets", false, OPTION_IGNORE, "", false, "", 0 }, {"disable-rembrandt", false, OPTION_BOOL, "/sim/rendering/rembrandt/enabled", false, "", 0 }, {"enable-rembrandt", false, OPTION_BOOL, "/sim/rendering/rembrandt/enabled", true, "", 0 }, {"renderer", true, OPTION_STRING, "/sim/rendering/rembrandt/renderer", false, "", 0 }, diff --git a/src/Network/props.cxx b/src/Network/props.cxx index 24fe28ab7..adb56e596 100644 --- a/src/Network/props.cxx +++ b/src/Network/props.cxx @@ -40,9 +40,11 @@ #include #include #include +#include #include
#include +#include #include @@ -73,13 +75,13 @@ class FGProps::PropsChannel : public simgear::NetChat, public SGPropertyChangeLi /** * Current property node name. */ - string path; + string path= "/"; enum Mode { PROMPT, DATA }; - Mode mode; + Mode mode = PROMPT; public: /** @@ -136,22 +138,23 @@ private: // callback implementations: void subscribe(const ParameterList &p); void unsubscribe(const ParameterList &p); + void beginNasal(const ParameterList &p); FGProps* _owner = nullptr; + bool _colletingNasal = false; }; /** * */ FGProps::PropsChannel::PropsChannel(FGProps* owner) - : buffer(8192), - path("/"), - mode(PROMPT), - _owner(owner) + : buffer(8192) + , _owner(owner) { setTerminator( "\r\n" ); callback_map["subscribe"] = &PropsChannel::subscribe; callback_map["unsubscribe"] = &PropsChannel::unsubscribe; + callback_map["nasal"] = &PropsChannel::beginNasal; } FGProps::PropsChannel::~PropsChannel() @@ -204,6 +207,18 @@ void FGProps::PropsChannel::unsubscribe(const ParameterList ¶m) { } } +void FGProps::PropsChannel::beginNasal(const ParameterList ¶m) +{ + std::string eofMarker = "##EOF##"; + if (param.size() > 1) { + if ((param.at(1) == "eof") && (param.size() >= 3)) { + eofMarker = param.at(2); + } + } // of optional argument parsing + + _colletingNasal = true; + setTerminator(eofMarker); +} //TODO: provide support for different types of subscriptions MODES ? (child added/removed, thesholds, min/max) void FGProps::PropsChannel::valueChanged(SGPropertyNode* ptr) @@ -276,8 +291,35 @@ getValueTypeString( const SGPropertyNode *node ) void FGProps::PropsChannel::foundTerminator() { + if (_colletingNasal) { + std::string nasalSource = buffer.getData(); // make a copy + _colletingNasal = false; + setTerminator("\r\n"); + buffer.remove(); // safe since we copied the source above + + if (globals->get_props()->getBoolValue("sim/secure-flag", true) == true) { + SG_LOG(SG_IO, SG_ALERT, "Telnet connection trying to run Nasal, blocked it.\n" + "Run the simulator with --allow-nasal-from-sockets to allow this."); + error("Simulator running in secure mode, Nasal execution blocked."); + } else { + auto nasal = globals->get_subsystem(); + if (nasal) { + std::string errors, output; + bool ok = nasal->parseAndRunWithOutput(nasalSource, output, errors); + if (!ok) { + error("Nasal error" + errors); + } else if (!output.empty()) { + // success and we have output: push it + push(output.c_str()); + } + } + } + + return; + } + const char* cmd = buffer.getData(); - SG_LOG( SG_IO, SG_INFO, "processing command = \"" << cmd << "\"" ); + SG_LOG( SG_IO, SG_DEBUG, "processing command = \"" << cmd << "\"" ); ParameterList tokens = simgear::strutils::split( cmd ); @@ -506,12 +548,12 @@ FGProps::PropsChannel::foundTerminator() } else if ( command == "prompt" ) { mode = PROMPT; } else if (callback_map.find(command) != callback_map.end() ) { - TelnetCallback t = callback_map[ command ]; - if (t) - (this->*t) (tokens); - else - error("No matching callback found for command:"+command); - } + TelnetCallback t = callback_map[ command ]; + if (t) + (this->*t) (tokens); + else + error("No matching callback found for command:"+command); + } else if ( command == "seti" ) { string value, tmp; if (tokens.size() == 3) { @@ -604,7 +646,9 @@ setf alias for setd\r\n\ seti set Int to a new \r\n\ del delete in \r\n\ subscribe subscribe to property changes \r\n\ -unscubscribe unscubscribe from property changes (var must be the property name/path used by subscribe)\r\n"; +unsubscribe unscubscribe from property changes (var must be the property name/path used by subscribe)\r\n\ +nasal [EOF ] execute arbitrary Nasal code (simulator must be running with Nasal allowed from sockets)\r\n\ +"; push( msg ); } } @@ -615,7 +659,7 @@ unscubscribe unscubscribe from property changes (var must be the property push( getTerminator() ); } - if ( mode == PROMPT ) { + if ( (mode == PROMPT) && !_colletingNasal) { string prompt = node->getPath(); if (prompt.empty()) { prompt = "/"; diff --git a/src/Network/props.hxx b/src/Network/props.hxx index e1cda73ea..be9e29cce 100644 --- a/src/Network/props.hxx +++ b/src/Network/props.hxx @@ -49,7 +49,7 @@ private: /** * Server port to listen on. */ - int port; + int port = 5501; simgear::NetChannelPoller poller; std::vector _activeChannels; @@ -69,22 +69,22 @@ public: /** * Start the telnet server. */ - bool open(); + bool open() override; /** * Process network activity. */ - bool process(); + bool process() override; /** * */ - bool close(); + bool close() override; /** * Accept a new client connection. */ - void handleAccept(); + void handleAccept() override; void removeChannel(PropsChannel* channel); }; diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index e1e156df9..93b65491e 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -298,20 +298,37 @@ FGNasalSys::~FGNasalSys() nasalSys = 0; } -bool FGNasalSys::parseAndRun(const char* sourceCode) +bool FGNasalSys::parseAndRunWithOutput(const std::string& source, std::string& output, std::string& errors) { naContext ctx = naNewContext(); - naRef code = parse(ctx, "FGNasalSys::parseAndRun()", sourceCode, - strlen(sourceCode)); + naRef code = parse(ctx, "FGNasalSys::parseAndRun()", source.c_str(), + source.size(), errors); if(naIsNil(code)) { naFreeContext(ctx); return false; } - callWithContext(ctx, code, 0, 0, naNil()); + naRef result = callWithContext(ctx, code, 0, 0, naNil()); + + // if there was a result value, try to convert it to a string + // value. + if (!naIsNil(result)) { + naRef s = naStringValue(ctx, result); + if (!naIsNil(s)) { + output = naStr_data(s); + } + } + naFreeContext(ctx); return true; } +bool FGNasalSys::parseAndRun(const std::string& source) +{ + std::string errors; + std::string output; + return parseAndRunWithOutput(source, output, errors); +} + #if 0 FGNasalScript* FGNasalSys::parseScript(const char* src, const char* name) { @@ -1208,7 +1225,8 @@ bool FGNasalSys::createModule(const char* moduleName, const char* fileName, int argc, naRef* args) { naContext ctx = naNewContext(); - naRef code = parse(ctx, fileName, src, len); + std::string errors; + naRef code = parse(ctx, fileName, src, len, errors); if(naIsNil(code)) { naFreeContext(ctx); return false; @@ -1257,16 +1275,21 @@ naRef FGNasalSys::getModule(const char* moduleName) return mod; } -naRef FGNasalSys::parse(naContext ctx, const char* filename, const char* buf, int len) +naRef FGNasalSys::parse(naContext ctx, const char* filename, + const char* buf, int len, + std::string& errors) { int errLine = -1; naRef srcfile = naNewString(ctx); naStr_fromdata(srcfile, (char*)filename, strlen(filename)); naRef code = naParseCode(ctx, srcfile, 1, (char*)buf, len, &errLine); if(naIsNil(code)) { - SG_LOG(SG_NASAL, SG_ALERT, - "Nasal parse error: " << naGetError(ctx) << - " in "<< filename <<", line " << errLine); + std::ostringstream errorMessageStream; + errorMessageStream << "Nasal parse error: " << naGetError(ctx) << + " in "<< filename <<", line " << errLine; + errors = errorMessageStream.str(); + SG_LOG(SG_NASAL, SG_ALERT, errors); + return naNil(); } @@ -1281,7 +1304,8 @@ bool FGNasalSys::handleCommand( const char* moduleName, SGPropertyNode* root) { naContext ctx = naNewContext(); - naRef code = parse(ctx, fileName, src, strlen(src)); + std::string errorMessage; + naRef code = parse(ctx, fileName, src, strlen(src), errorMessage); if(naIsNil(code)) { naFreeContext(ctx); return false; diff --git a/src/Scripting/NasalSys.hxx b/src/Scripting/NasalSys.hxx index a25f0d6c5..c5bb7b9b7 100644 --- a/src/Scripting/NasalSys.hxx +++ b/src/Scripting/NasalSys.hxx @@ -48,8 +48,12 @@ public: // indicate successful execution. Does *not* return any Nasal // values, because handling garbage-collected objects from C space // is deep voodoo and violates the "simple hook" idea. - bool parseAndRun(const char* sourceCode); + bool parseAndRun(const std::string& source); + bool parseAndRunWithOutput(const std::string& source, + std::string& output, + std::string& errors); + // Slightly more complicated hook to get a handle to a precompiled // Nasal script that can be invoked via a call() method. The // caller is expected to delete the FGNasalScript returned from @@ -191,7 +195,8 @@ private: void loadScriptDirectory(simgear::Dir nasalDir); void addModule(std::string moduleName, simgear::PathList scripts); static void logError(naContext); - naRef parse(naContext ctx, const char* filename, const char* buf, int len); + naRef parse(naContext ctx, const char* filename, const char* buf, int len, + std::string& errors); naRef genPropsModule(); bool _inited;