// Copyright (c) 2022 Robin Davies // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // shim file to allow shutdown to be called from a service. // (Gets suid root permissions during install, since otherwise // non-interactive services are not allowed to execute) #include "pch.h" #include "CommandLineParser.hpp" #include #include #include #include #include #include #include #include #include #include "JackServerSettings.hpp" #include #include "Lv2Log.hpp" #include "Lv2SystemdLogger.hpp" #include "WifiConfigSettings.hpp" #include "SetWifiConfig.hpp" #include "SysExec.hpp" #include using namespace std; using namespace pipedal; using namespace boost; using namespace boost::asio; using ip::tcp; const size_t MAX_LENGTH = 128; static bool startsWith(const std::string&s, const char*text) { if (s.length() < strlen(text)) return false; const char*sp = s.c_str(); while (*text) { if (*text++ != *sp++) return false; } return true; } static std::vector tokenize(std::string value) { std::vector result; std::stringstream s(value); std::string item; while (std::getline(s,item,' ')) { if (item.length() != 0) result.push_back(item); } return result; } static void CaptureAccessPoint(const std::string gatewayAddress) { } static void ReleaseAccessPoint(const std::string gatewayAddress) { } void delayedRestartProc() { sleep(1); // give a chance for websocket messages to propagate. Lv2Log::error("Delayed Restart"); std::string pipedalConfigPath = std::filesystem::path(getSelfExePath()).parent_path() / "pipedalconfig"; std::stringstream s; s << pipedalConfigPath.c_str() << " --restart --excludeShutdownService"; if (::system(s.str().c_str())!= EXIT_SUCCESS) { Lv2Log::error("Delayed Restart failed."); } } bool setJackConfiguration(JackServerSettings serverSettings) { bool success = true; serverSettings.Write(); silentSysExec("/usr/bin/systemctl unmask jack"); silentSysExec("/usr/bin/systemctl enable jack"); std::thread delayedRestartThread(delayedRestartProc); delayedRestartThread.detach(); return true; } class Reader : public std::enable_shared_from_this { private: tcp::socket socket; int readAvailable = MAX_LENGTH; char data[MAX_LENGTH]; char *writePointer; ssize_t writeLength; std::stringstream message; size_t writePosition = 0; std::string response; public: Reader(asio::io_service &ios) : socket(ios) { } static std::shared_ptr Create(asio::io_service &ios) { return std::make_shared(ios); } tcp::socket &Socket() { return socket; } void Start() { ReadSome(); } private: void ReadSome() { socket.async_read_some( boost::asio::buffer(data, readAvailable), boost::bind(&Reader::HandleRead, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } void WriteSome() { if (writePosition == response.size()) { socket.close(); return; } socket.async_write_some( boost::asio::buffer(response.c_str()+ writePosition, response.length()-writePosition), boost::bind(&Reader::HandleWrite, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } private: void HandleWrite(const boost::system::error_code &err, size_t bytes_transferred) { writePosition += bytes_transferred; WriteSome(); } void HandleRead(const boost::system::error_code &err, size_t bytes_transferred) { if (!err) { for (size_t i = 0; i < bytes_transferred; ++i) { char c = data[i]; if (c == '\n') { std::string command = message.str(); cout << "Received command: " << command << endl; HandleCommand(command); socket.close(); return; } else { message.put(data[i]); } } ReadSome(); } else { socket.close(); } } void HandleCommand(const std::string &s) { int result = -1; try { if (startsWith(s,"WifiConfigSettings ")) { std::string json = s.substr(strlen("WifiConfigSettings ")); std::stringstream ss(json); WifiConfigSettings settings; try { json_reader reader(ss); reader.read(&settings); } catch (const std::exception &e) { throw PiPedalArgumentException("Invalid arguments."); } pipedal::SetWifiConfig(settings); result = 0; } else if (startsWith(s,"release_ap ")) { std::vector argv = tokenize(s); if (argv.size() == 2) { ReleaseAccessPoint(argv[1]); result = 0; } else { throw PiPedalArgumentException("Invalid arguments."); } } else if (startsWith(s,"capture_ap ")) { std::vector argv = tokenize(s); if (argv.size() == 2) { CaptureAccessPoint(argv[1]); result = 0; } else { throw PiPedalArgumentException("Invalid arguments."); } } else if (s == "shutdown") { result = sysExec("/usr/sbin/shutdown -P now"); } else if (s == "restart") { result = sysExec("/usr/sbin/shutdown -r now"); } else if (startsWith(s,"setJackConfiguration ")) { auto remainder = s.substr(strlen("setJackConfiguration ")); std::stringstream input(remainder); JackServerSettings serverSettings; json_reader reader(input); reader.read(&serverSettings); if (input.fail()) { result = -1; } else { result = setJackConfiguration(serverSettings) ? 0: -1; } } } catch (const std::exception &e) { std::stringstream t; t << "-2 " << e.what() << "\n"; this->response = t.str(); this->writePosition = 0; WriteSome(); return; } std::stringstream t; t << result << "\n"; this->response = t.str(); this->writePosition = 0; WriteSome(); } }; class Server { private: tcp::acceptor acceptor_; boost::asio::io_service &io_service; void HandleAccept(std::shared_ptr connection, const boost::system::error_code &err) { if (!err) { connection->Start(); } StartAccept(); } void StartAccept() { // socket std::shared_ptr reader = Reader::Create(io_service); // asynchronous accept operation and wait for a new connection. acceptor_.async_accept(reader->Socket(), boost::bind(&Server::HandleAccept, this, reader, boost::asio::placeholders::error)); } public: //constructor for accepting connection from client Server(boost::asio::io_service &io_service, const tcp::endpoint &endpoint) : acceptor_(io_service, endpoint), io_service(io_service) { StartAccept(); } ~Server() { } }; void runServer(uint16_t port) { asio::ip::tcp::endpoint endpoint(ip::make_address_v4("127.0.0.1"), port); asio::io_service ios; try { Server server(ios,endpoint); ios.run(); cout << "Processing terminated." << endl; } catch (const std::exception & e) { cout << "Error: " << e.what() << endl; } } int main(int argc, char **argv) { Lv2Log::set_logger(MakeLv2SystemdLogger()); CommandLineParser parser; uint16_t port = 0; std::string captureAccessPoint; std::string releaseAccessPoint; parser.AddOption("-port", &port); parser.AddOption("-captureAP",&captureAccessPoint); parser.AddOption("-releaseAP",&releaseAccessPoint); try { parser.Parse(argc, (const char **)argv); if (parser.Arguments().size() != 0) { throw PiPedalException("Invalid argument."); } } catch (const std::exception &e) { cout << "Error: " << e.what() << endl; return EXIT_FAILURE; } if (captureAccessPoint.length() != 0) { try{ CaptureAccessPoint(captureAccessPoint); } catch (const std::exception &e) { std::cerr << "Error: " << e.what() << std::endl; return EXIT_FAILURE; } std::cout << "Access point captured." << std::endl; } else if (releaseAccessPoint.length() != 0) { try{ ReleaseAccessPoint(releaseAccessPoint); } catch (const std::exception &e) { std::cerr << "Error: " << e.what() << std::endl; return EXIT_FAILURE; } std::cout << "Access point released." << std::endl; } else { if (port == 0) { std::cerr << "Error: port not specified." << std::endl; return EXIT_FAILURE; } runServer(port); } return EXIT_SUCCESS; }