From 6a45a5059842aabf3b7f5d06142e3c726314fcbe Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Thu, 7 Aug 2025 09:35:01 -0400 Subject: [PATCH 1/3] feat: libssh2, ssh remote shell works! ci: using libnds fork again though... and it's unavoidable now. i plan to make the libnds console more compliant with ansi escape sequences! --- .github/workflows/ci.yml | 6 + CMakeLists.txt | 23 +++- include/Commands.hpp | 3 +- src/CliPrompt.cpp | 2 +- src/Commands.cpp | 25 ++-- src/Consoles.cpp | 4 +- src/Lexer.cpp | 4 +- src/Parser.cpp | 10 +- src/Shell.cpp | 20 ++-- src/commands/curl.cpp | 15 +-- src/commands/ssh.cpp | 243 +++++++++++++++++++++++++++++++++++++++ src/commands/tcp.cpp | 18 +-- src/commands/wifi.cpp | 28 ++--- src/main.cpp | 10 +- 14 files changed, 335 insertions(+), 76 deletions(-) create mode 100644 src/commands/ssh.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 256a329..5b5286c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,12 @@ jobs: container: devkitpro/devkitarm:latest steps: + - name: Uninstall system libnds, build and install my fork + run: | + sudo dkp-pacman -Rdd libnds --noconfirm + git clone https://github.com/trustytrojan/libnds -b ansi-colors + sudo make install + - name: Checkout code uses: actions/checkout@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e1ecb6..80cd3d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,8 +45,8 @@ set(CURL_DISABLE_INSTALL ON) set(CURL_DISABLE_BINDLOCAL ON) set(CURL_DISABLE_SOCKETPAIR ON) -set(CURL_USE_MBEDTLS ON) # disables openssl by implication -set(MBEDTLS_LIBRARIES mbedx509 mbedcrypto mbedtls) # may not be necessary +set(CURL_USE_MBEDTLS ON) +set(MBEDTLS_LIBRARIES mbedx509 mbedcrypto mbedtls) set(ENABLE_CURL_MANUAL OFF) set(ENABLE_UNIX_SOCKETS OFF) @@ -96,13 +96,26 @@ execute_process( ## sol2 set(SOL2_ENABLE_INSTALL OFF) +add_compile_definitions(SOL_EXCEPTIONS=0) FetchContent_Declare(sol2 URL https://github.com/ThePhD/sol2/archive/develop.zip) FetchContent_MakeAvailable(sol2) +## libssh2 +set(CRYPTO_BACKEND mbedTLS) +set(ENABLE_ZLIB_COMPRESSION OFF) +set(CLEAR_MEMORY OFF) +set(BUILD_EXAMPLES OFF) +set(BUILD_TESTING OFF) +set(MBEDTLS_INCLUDE_DIR ${mbedtls_SOURCE_DIR}/include) +set(MBEDCRYPTO_LIBRARY mbedcrypto) +add_compile_definitions(LIBSSH2_MBEDTLS _3DS) +FetchContent_Declare(libssh2 URL https://github.com/trustytrojan/libssh2/archive/1.11.1-nds.zip) +FetchContent_MakeAvailable(libssh2) + +## nds-shell add_compile_options(-fno-rtti -fno-exceptions) -add_compile_definitions(SOL_EXCEPTIONS=0) -include_directories(include ${curl_SOURCE_DIR}/include) -link_libraries(dswifi9 fat lua_static sol2 libcurl) +include_directories(include) +link_libraries(dswifi9 fat lua_static sol2 libcurl libssh2) file(GLOB_RECURSE SOURCES src/**) add_executable(nds-shell ${SOURCES}) diff --git a/include/Commands.hpp b/include/Commands.hpp index 32c02c2..9632e06 100644 --- a/include/Commands.hpp +++ b/include/Commands.hpp @@ -27,7 +27,8 @@ void wifi(const Context &); void curl(const Context &); void tcp(const Context &); void lua(const Context &); -void source(const Context &); +void ssh(const Context &); +// void source(const Context &); // the smaller commands are defined in Commands.cpp using Fn = void (*)(const Context &); diff --git a/src/CliPrompt.cpp b/src/CliPrompt.cpp index 7d70b33..1a1ce40 100644 --- a/src/CliPrompt.cpp +++ b/src/CliPrompt.cpp @@ -255,7 +255,7 @@ void CliPrompt::setLineHistory(const std::string &filename) std::ifstream lineHistoryFile{filename}; if (!lineHistoryFile) { - std::cerr << "\e[41mfailed to load line history\n\e[39m"; + std::cerr << "\e[91mfailed to load line history\n\e[39m"; return; } diff --git a/src/Commands.cpp b/src/Commands.cpp index 85f34a9..a8f7ac9 100644 --- a/src/Commands.cpp +++ b/src/Commands.cpp @@ -37,7 +37,7 @@ void ls(const Context &ctx) if (!fs::exists(path)) { - ctx.err << "\e[41mpath does not exist\e[39m\n"; + ctx.err << "\e[91mpath does not exist\e[39m\n"; return; } @@ -45,7 +45,7 @@ void ls(const Context &ctx) { const auto filename = entry.path().filename().string(); if (entry.is_directory()) - ctx.out << "\e[44m" << filename << "\e[39m "; + ctx.out << "\e[94m" << filename << "\e[39m "; else ctx.out << filename << ' '; ctx.out << '\n'; @@ -64,7 +64,7 @@ void cd(const Context &ctx) if (!fs::exists(path)) { - ctx.err << "\e[41mpath does not exist\e[39m\n"; + ctx.err << "\e[91mpath does not exist\e[39m\n"; return; } @@ -80,7 +80,7 @@ void cat(const Context &ctx) // this gets you stuck in the cat command, // unless i make stdin nonblocking and use threads // so you can "ctrl+c" by pressing the fold key - ctx.err << "\e[41mcat: not using stdin\e[39m\n"; + ctx.err << "\e[91mcat: not using stdin\e[39m\n"; return; } @@ -91,12 +91,12 @@ void cat(const Context &ctx) std::error_code ec; if (!fs::exists(ctx.args[1], ec) && !ec) { - ctx.err << "\e[41mcat: file does not exist: " << ctx.args[1] << "\e[39m\n"; + ctx.err << "\e[91mcat: file does not exist: " << ctx.args[1] << "\e[39m\n"; return; } else if (ec) { - ctx.err << "\e[41mcat: " << ec.message() << "\e[39m\n"; + ctx.err << "\e[91mcat: " << ec.message() << "\e[39m\n"; return; } @@ -104,7 +104,7 @@ void cat(const Context &ctx) if (!file) { - ctx.err << "\e[41mcat: cannot open file: " << ctx.args[1] << "\e[39m\n"; + ctx.err << "\e[91mcat: cannot open file: " << ctx.args[1] << "\e[39m\n"; return; } @@ -123,7 +123,7 @@ void rm(const Context &ctx) { std::error_code ec; if (!fs::remove(ctx.args[1], ec)) - ctx.err << "\e[41mrm: failed to remove: '" << ctx.args[1] << "': " << ec.message() << "\e[39m\n"; + ctx.err << "\e[91mrm: failed to remove: '" << ctx.args[1] << "': " << ec.message() << "\e[39m\n"; } } @@ -145,7 +145,7 @@ void dns(const Context &ctx) const auto host = gethostbyname(ctx.args[1].c_str()); if (!host) { - ctx.err << "\e[41mdns: gethostbyname: " << strerror(errno) << "\e[39m"; + ctx.err << "\e[91mdns: gethostbyname: " << strerror(errno) << "\e[39m"; return; } @@ -191,7 +191,7 @@ void rename(const Context &ctx) std::error_code ec; fs::rename(ctx.args[1], ctx.args[2], ec); if (ec) - ctx.err << "\e[41mrename: " << ec.message() << "\e[39m\n"; + ctx.err << "\e[91mrename: " << ec.message() << "\e[39m\n"; } void pwd(const Context &ctx) @@ -223,7 +223,7 @@ void history(const Context &ctx) ctx.shell.ClearLineHistory(); std::error_code ec; if (!fs::remove("/.ndsh_history", ec)) - ctx.err << "\e[41mhistory: failed to remove '.ndsh_history': " << ec.message() << "\e[39m\n"; + ctx.err << "\e[91mhistory: failed to remove '.ndsh_history': " << ec.message() << "\e[39m\n"; return; } @@ -283,7 +283,8 @@ const Map MAP{ {"exit", exit}, {"lua", lua}, {"source", source}, - {"poweroff", poweroff}}; + {"poweroff", poweroff}, + {"ssh", ssh}}; void help(const Context &ctx) { diff --git a/src/Consoles.cpp b/src/Consoles.cpp index f3dc0d3..4b14b1d 100644 --- a/src/Consoles.cpp +++ b/src/Consoles.cpp @@ -109,7 +109,7 @@ void InitMulti() // Sanity check if (dot != devoptab_list[STD_ERR]) { - std::cerr << "\e[41mmulticon: stdout & stderr devices are not the same!\e[39m\n"; + std::cerr << "\e[91mmulticon: stdout & stderr devices are not the same!\e[39m\n"; return; } } @@ -132,7 +132,7 @@ void InitMulti() ostr.open(std::string{dot->name} + ':'); if (!ostr) { - std::cerr << "\e[41mmulticon: failed to open " << dot->name << "!\e[39m\n"; + std::cerr << "\e[91mmulticon: failed to open " << dot->name << "!\e[39m\n"; continue; } } diff --git a/src/Lexer.cpp b/src/Lexer.cpp index aeb4111..dffcbe5 100644 --- a/src/Lexer.cpp +++ b/src/Lexer.cpp @@ -80,7 +80,7 @@ bool LexDoubleQuoteString(StrItr &itr, const StrItr &lineEnd, std::string &curre if (itr == lineEnd) { - std::cerr << "\e[41mshell: closing `\"` not found\e[39m\n"; + std::cerr << "\e[91mshell: closing `\"` not found\e[39m\n"; return false; } @@ -95,7 +95,7 @@ bool LexSingleQuoteString(StrItr &itr, const StrItr &lineEnd, std::string &curre if (itr == lineEnd) { - std::cerr << "\e[41mshell: closing `'` not found\e[39m\n"; + std::cerr << "\e[91mshell: closing `'` not found\e[39m\n"; return false; } diff --git a/src/Parser.cpp b/src/Parser.cpp index af85a03..037cc6e 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -12,7 +12,7 @@ bool ParseInputRedirect( if (nextItr == tokensEnd || nextItr->type != Token::Type::STRING) { - std::cerr << "\e[41mshell: filename expected after `<`\e[39m\n"; + std::cerr << "\e[91mshell: filename expected after `<`\e[39m\n"; return false; } @@ -20,7 +20,7 @@ bool ParseInputRedirect( if (!std::filesystem::exists(filename)) { - std::cerr << "\e[41mshell: file `" << filename << "` does not exist\e[39m\n"; + std::cerr << "\e[91mshell: file `" << filename << "` does not exist\e[39m\n"; return false; } @@ -38,7 +38,7 @@ bool ParseInputRedirect( if (prevItr->type != Token::Type::STRING || !std::ranges::all_of(fdStr, isdigit)) { - std::cerr << "\e[41mshell: integer expected before `<`\e[39m\n"; + std::cerr << "\e[91mshell: integer expected before `<`\e[39m\n"; return false; } @@ -57,7 +57,7 @@ bool ParseOutputRedirect( if (nextItr == tokensEnd || nextItr->type != Token::Type::STRING) { - std::cerr << "\e[41mshell: filename expected after `>`\e[39m\n"; + std::cerr << "\e[91mshell: filename expected after `>`\e[39m\n"; return false; } @@ -77,7 +77,7 @@ bool ParseOutputRedirect( if (prevItr->type != Token::Type::STRING || !std::ranges::all_of(fdStr, isdigit)) { - std::cerr << "\e[41mshell: integer expected before `<`\e[39m\n"; + std::cerr << "\e[91mshell: integer expected before `<`\e[39m\n"; return false; } diff --git a/src/Shell.cpp b/src/Shell.cpp index 68710c9..cdab8a1 100644 --- a/src/Shell.cpp +++ b/src/Shell.cpp @@ -39,7 +39,7 @@ void Shell::SourceFile(const std::string &filepath) if (!file) { - ostr << "\e[41mshell: cannot open file: " << filepath << "\e[39m\n"; + ostr << "\e[91mshell: cannot open file: " << filepath << "\e[39m\n"; return; } @@ -67,7 +67,7 @@ void Shell::ProcessLine(std::string_view line) if (env.contains("SHELL_DEBUG")) { // debug tokens - ostr << "\e[40mtokens: "; + ostr << "\e[90mtokens: "; auto itr = tokens.cbegin(); for (; itr < tokens.cend() - 1; ++itr) ostr << *itr << ' '; @@ -83,7 +83,7 @@ void Shell::ProcessLine(std::string_view line) if (args.empty() && envAssigns.empty()) { - ostr << "\e[41mshell: no args or env assigns\e[39m\n"; + ostr << "\e[91mshell: no args or env assigns\e[39m\n"; return; } @@ -97,7 +97,7 @@ void Shell::ProcessLine(std::string_view line) if (env.contains("SHELL_DEBUG")) { // debug args - ostr << "\e[40margs: "; + ostr << "\e[90margs: "; auto itr = args.cbegin(); for (; itr < args.cend() - 1; ++itr) ostr << '\'' << *itr << "' "; @@ -118,7 +118,7 @@ void Shell::ProcessLine(std::string_view line) const auto &command = args[0]; - // ostr << "\e[40mcommand: '" << command << "'\n"; + // ostr << "\e[90mcommand: '" << command << "'\n"; if (const auto withExtension{command + ".ndsh"}; fs::exists(withExtension)) { // Treat .ndsh files as commands! @@ -129,7 +129,7 @@ void Shell::ProcessLine(std::string_view line) if (const auto itr = Commands::MAP.find(command); itr != Commands::MAP.cend()) itr->second({*out, *err, *in, args, commandEnv, *this}); else - ostr << "\e[41mshell: unknown command\e[39m\n"; + ostr << "\e[91mshell: unknown command\e[39m\n"; } void Shell::ResetStreams() @@ -160,7 +160,7 @@ void Shell::RedirectOutput(int fd, const std::string &filename) outf.open(filename); if (!outf) { - ostr << "\e[41mshell: cannot open file for writing: " << filename << "\e[39m\n"; + ostr << "\e[91mshell: cannot open file for writing: " << filename << "\e[39m\n"; return; } out = &outf; @@ -170,7 +170,7 @@ void Shell::RedirectOutput(int fd, const std::string &filename) errf.open(filename); if (!errf) { - ostr << "\e[41mshell: cannot open file for writing: " << filename << "\e[39m\n"; + ostr << "\e[91mshell: cannot open file for writing: " << filename << "\e[39m\n"; return; } err = &errf; @@ -182,7 +182,7 @@ void Shell::RedirectInput(int fd, const std::string &filename) inf.open(filename); if (!inf) { - ostr << "\e[41mshell: cannot open file for reading: " << filename << "\e[39m\n"; + ostr << "\e[91mshell: cannot open file for reading: " << filename << "\e[39m\n"; return; } if (fd == 0) @@ -191,7 +191,7 @@ void Shell::RedirectInput(int fd, const std::string &filename) void Shell::StartPrompt() { - ostr << "\e[46mgithub.com/trustytrojan/nds-shell\e[39m\n\nrun 'help' for help\n\n"; + ostr << "\e[96mgithub.com/trustytrojan/nds-shell\e[39m\n\nrun 'help' for help\n\n"; prompt.prepareForNextLine(); prompt.printFullPrompt(false); diff --git a/src/commands/curl.cpp b/src/commands/curl.cpp index b25bb8b..5a99429 100644 --- a/src/commands/curl.cpp +++ b/src/commands/curl.cpp @@ -7,7 +7,7 @@ static int curl_debug(CURL *, curl_infotype, char *const data, const size_t size, void *userp) { auto &ostr = *reinterpret_cast(userp); - ostr << "\e[40m"; + ostr << "\e[90m"; ostr.write(data, size); ostr << "\e[39m"; return 0; @@ -20,14 +20,8 @@ static curl_socket_t curl_opensocket(void *, curlsocktype, curl_sockaddr *const size_t curl_write(char *const buffer, const size_t size, const size_t nitems, void *userp) { - auto &ostr = *reinterpret_cast(userp); - // manually put chars because putting a char* can cause a memory error here: - // https://github.com/devkitPro/libnds/blob/6194b32d8f94e2ebc8078e64bf213ffc13ba1985/source/arm9/console.c#L223 - // looks like they didn't check for null chars in the loop condition const auto bytes = size * nitems; - const char *const endp = buffer + bytes; - for (const char *p = buffer; *p && p < endp; ++p) - ostr << *p; + reinterpret_cast(userp)->write(buffer, bytes); return bytes; } @@ -52,7 +46,7 @@ void Commands::curl(const Context &ctx) const auto curl = curl_easy_init(); if (!curl) { - ctx.err << "\e[41mhttp: curl_easy_init failed\e[39m\n"; + ctx.err << "\e[91mhttp: curl_easy_init failed\e[39m\n"; return; } @@ -70,6 +64,7 @@ void Commands::curl(const Context &ctx) // write http response to ctx.out curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ctx.out); // we need a custom opensocket callback because of // https://github.com/devkitPro/dswifi/blob/f61bbc661dc7087fc5b354cd5ec9a878636d4cbf/source/sgIP/sgIP_sockets.c#L98 @@ -88,7 +83,7 @@ void Commands::curl(const Context &ctx) } if (const auto res = curl_easy_perform(curl); res != CURLE_OK) - ctx.err << "\e[41mhttp: curl: " << curl_easy_strerror(res) << ": " << curl_errbuf << "\e[39m\n"; + ctx.err << "\e[91mhttp: curl: " << curl_easy_strerror(res) << ": " << curl_errbuf << "\e[39m\n"; curl_easy_cleanup(curl); } diff --git a/src/commands/ssh.cpp b/src/commands/ssh.cpp new file mode 100644 index 0000000..3b60ef9 --- /dev/null +++ b/src/commands/ssh.cpp @@ -0,0 +1,243 @@ +#include "Commands.hpp" +#include +#include + +// For networking +#include +#include +#include +#include +#include +#include +#include + +void Commands::ssh(const Context &ctx) +{ + if (ctx.args.size() < 2) + { + ctx.err << "usage: ssh
" << std::endl; + return; + } + + std::string address_arg = ctx.args[1]; + std::string user; + std::string address; + + size_t at_pos = address_arg.find('@'); + if (at_pos != std::string::npos) + { + user = address_arg.substr(0, at_pos); + address = address_arg.substr(at_pos + 1); + } + else + { + address = address_arg; + } + + int sock = -1; + LIBSSH2_SESSION *session = nullptr; + LIBSSH2_CHANNEL *channel = nullptr; + char *err_msg = nullptr; + + do + { + if (libssh2_init(0)) + { + ctx.err << "libssh2 initialization failed" << std::endl; + break; + } + + // Create socket and connect + struct hostent *host = gethostbyname(address.c_str()); + if (!host) + { + ctx.err << "Could not resolve host: " << address << std::endl; + break; + } + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) + { + ctx.err << "Failed to create socket: " << strerror(errno) << std::endl; + break; + } + + struct sockaddr_in sin; + sin.sin_family = AF_INET; + sin.sin_port = htons(22); + sin.sin_addr = *(struct in_addr *)host->h_addr_list[0]; + + if (connect(sock, (struct sockaddr *)(&sin), sizeof(struct sockaddr_in)) != 0) + { + ctx.err << "Failed to connect to " << address << ": " << strerror(errno) << std::endl; + break; + } + + // Create libssh2 session + session = libssh2_session_init(); + if (!session) + { + ctx.err << "Failed to create libssh2 session" << std::endl; + break; + } + + // Handshake + if (libssh2_session_handshake(session, sock)) + { + libssh2_session_last_error(session, &err_msg, NULL, 0); + ctx.err << "SSH handshake failed: " << err_msg << std::endl; + break; + } + ctx.out << "Connected to " << address << std::endl; + + // Authentication + // IMPORTANT: MbedTLS expects private keys to be in PEM format, NOT OPENSSH FORMAT! + // This works: ssh-keygen -t rsa -b 2048 -m PEM -C "user@nds" -f /mnt/sdcard/.ssh/id_rsa + if (const auto rc = libssh2_userauth_publickey_fromfile_ex( + session, user.c_str(), user.size(), ".ssh/id_rsa.pub", ".ssh/id_rsa", NULL)) + { + libssh2_session_last_error(session, &err_msg, NULL, 0); + ctx.err << "ssh: pubkey auth failed: " << err_msg << " (" << rc << ")\n"; + + // public key failed, check for password + const char *userauthlist = libssh2_userauth_list(session, user.c_str(), user.length()); + if (strstr(userauthlist, "password") == NULL) + { + ctx.err << "ssh: passwd login not allowed" << std::endl; + break; + } + + std::string password; + // a bit of a hack to get a password + { + ctx.out << "Password: "; + std::string pass_buf; + while (pmMainLoop()) + { + threadYield(); + int key = keyboardUpdate(); + if (key > 0) + { + if (key == '\n' || key == '\r') + { + ctx.out << std::endl; + break; + } + else if (key == '\b') + { + if (!pass_buf.empty()) + { + pass_buf.pop_back(); + ctx.out << "\b \b"; + } + } + else + { + pass_buf += (char)key; + ctx.out << '*'; + } + } + } + password = pass_buf; + } + + if (libssh2_userauth_password(session, user.c_str(), password.c_str())) + { + libssh2_session_last_error(session, &err_msg, NULL, 0); + ctx.err << "ssh: Password authentication failed: " << err_msg << std::endl; + break; + } + } + ctx.out << "Authentication successful." << std::endl; + + // Open channel + channel = libssh2_channel_open_session(session); + if (!channel) + { + libssh2_session_last_error(session, &err_msg, NULL, 0); + ctx.err << "Failed to open channel: " << err_msg << std::endl; + break; + } + + // Request PTY + if (libssh2_channel_request_pty(channel, "vanilla")) + { + libssh2_session_last_error(session, &err_msg, NULL, 0); + ctx.err << "Failed to request PTY: " << err_msg << std::endl; + break; + } + + // Request shell + if (libssh2_channel_shell(channel)) + { + libssh2_session_last_error(session, &err_msg, NULL, 0); + ctx.err << "Failed to request shell: " << err_msg << std::endl; + break; + } + ctx.out << "Shell opened." << std::endl; + + libssh2_session_set_blocking(session, 0); + libssh2_channel_set_blocking(channel, 0); + + int flags = 1; + if (ioctl(sock, FIONBIO, &flags) == -1) + { + ctx.err << "ioctl: " << strerror(errno) << '\n'; + break; + } + + while (!libssh2_channel_eof(channel) && pmMainLoop()) + { + threadYield(); + + char buffer[1024]; + ssize_t nbytes; + + // Read from channel + nbytes = libssh2_channel_read(channel, buffer, sizeof(buffer)); + if (nbytes > 0) + { + // ctx.out.write(buffer, nbytes); + for (char c : std::string_view{buffer, (size_t)nbytes}) + ctx.out << c; // prevent console from parsing escape sequences, let us see all the characters + } + else if (nbytes < 0 && nbytes != LIBSSH2_ERROR_EAGAIN) + { + libssh2_session_last_error(session, &err_msg, NULL, 0); + ctx.err << "Error reading from channel: " << err_msg << std::endl; + break; + } + + if (ctx.shell.IsFocused()) + { + // Check for keyboard input and send it + int key = keyboardUpdate(); + if (key > 0) + { + char c = (char)key; + ssize_t nwritten = libssh2_channel_write(channel, &c, 1); + if (nwritten < 0 && nwritten != LIBSSH2_ERROR_EAGAIN) + { + libssh2_session_last_error(session, &err_msg, NULL, 0); + ctx.err << "Error writing to channel: " << err_msg << std::endl; + break; + } + } + } + } + } while (0); + + if (channel) + { + libssh2_channel_close(channel); + libssh2_channel_free(channel); + } + if (session) + { + libssh2_session_disconnect(session, "Normal Shutdown"); + libssh2_session_free(session); + } + if (sock >= 0) + close(sock); + libssh2_exit(); +} diff --git a/src/commands/tcp.cpp b/src/commands/tcp.cpp index 2fa5c9c..59185a5 100644 --- a/src/commands/tcp.cpp +++ b/src/commands/tcp.cpp @@ -27,7 +27,7 @@ void Commands::tcp(const Context &ctx) sockaddr_in sain; if (const auto rc = NetUtils::ParseAddress(sain, ctx.args[1]); rc != NetUtils::Error::NO_ERROR) { - ctx.err << "\e[41mtcp: " << NetUtils::StrError(rc) << "\e[39m\n"; + ctx.err << "\e[91mtcp: " << NetUtils::StrError(rc) << "\e[39m\n"; return; } @@ -35,22 +35,22 @@ void Commands::tcp(const Context &ctx) const auto sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { - ctx.err << "\e[41mtcp: socket: " << strerror(errno) << "\e[39m\n"; + ctx.err << "\e[91mtcp: socket: " << strerror(errno) << "\e[39m\n"; return; } if (debugMessages) - ctx.err << "\e[40mtcp: socket: " << sock << "\n\e[39m"; + ctx.err << "\e[90mtcp: socket: " << sock << "\n\e[39m"; if (connect(sock, (sockaddr *)&sain, sizeof(sockaddr_in)) == -1) { - ctx.err << "\e[41mtcp: connect: " << strerror(errno) << "\e[39m\n"; + ctx.err << "\e[91mtcp: connect: " << strerror(errno) << "\e[39m\n"; close(sock); return; } if (debugMessages) - ctx.err << "\e[40mtcp: connected\e[39m\n"; + ctx.err << "\e[90mtcp: connected\e[39m\n"; // TODO: get rid of the select() impl, use non-blocking sockets instead @@ -84,7 +84,7 @@ void Commands::tcp(const Context &ctx) if (prompt.foldPressed()) { if (debugMessages) - ctx.out << "\r\e[2K\e[40mtcp: fold key pressed\e[39m\n"; + ctx.out << "\r\e[2K\e[90mtcp: fold key pressed\e[39m\n"; break; } @@ -95,7 +95,7 @@ void Commands::tcp(const Context &ctx) switch (send(sock, lineToSend.c_str(), lineToSend.length(), 0)) { case -1: - ctx.err << "\e[41mtcp: send: " << strerror(errno) << "\e[39m\n"; + ctx.err << "\e[91mtcp: send: " << strerror(errno) << "\e[39m\n"; shouldExit = true; break; case 0: @@ -118,7 +118,7 @@ void Commands::tcp(const Context &ctx) switch (const auto bytesRead = recv(sock, buf, sizeof(buf) - 1, 0)) { case -1: - ctx.err << "\e[41mtcp: recv: " << strerror(errno) << "\e[39m\n"; + ctx.err << "\e[91mtcp: recv: " << strerror(errno) << "\e[39m\n"; shouldExit = true; break; case 0: @@ -134,7 +134,7 @@ void Commands::tcp(const Context &ctx) } else if (selectResult == -1) { - ctx.err << "\e[41mtcp: select: " << strerror(errno) << "\e[39m\n"; + ctx.err << "\e[91mtcp: select: " << strerror(errno) << "\e[39m\n"; shouldExit = true; } } diff --git a/src/commands/wifi.cpp b/src/commands/wifi.cpp index 8c6f591..2aec991 100644 --- a/src/commands/wifi.cpp +++ b/src/commands/wifi.cpp @@ -48,7 +48,7 @@ static std::span ScanAPs(const Commands::Context &ctx) { if (!wfcBeginScan(&filter)) { - ctx.err << "\e[41mwifi: GetAPList: wfcBeginScan failed\e[39m\n"; + ctx.err << "\e[91mwifi: GetAPList: wfcBeginScan failed\e[39m\n"; return {}; } @@ -101,7 +101,7 @@ int GetHiddenSSID(const Commands::Context &ctx, WlanBssDesc &ap) if (!ok) { - ctx.err << "\e[41mwifi: Invalid SSID\n"; + ctx.err << "\e[91mwifi: Invalid SSID\n"; prompt.prepareForNextLine(); prompt.printFullPrompt(false); } @@ -164,7 +164,7 @@ int GetPassword(const Commands::Context &ctx, WlanBssDesc &ap) if (!ok) { - ctx.err << "\e[41mwifi: Invalid key!\e[39m\n"; + ctx.err << "\e[91mwifi: Invalid key!\e[39m\n"; prompt.prepareForNextLine(); prompt.printFullPrompt(false); } @@ -219,7 +219,7 @@ int ConnectAP(const Commands::Context &ctx, WlanBssDesc &ap) { const auto rc = GetHiddenSSID(ctx, ap); if (rc == EXIT_FAILURE) - std::cerr << "\e[41mwifi: GetHiddenSSID failed\e[39m\n"; + std::cerr << "\e[91mwifi: GetHiddenSSID failed\e[39m\n"; return EXIT_FAILURE; } @@ -234,14 +234,14 @@ int ConnectAP(const Commands::Context &ctx, WlanBssDesc &ap) if (const auto rc = GetPassword(ctx, ap)) { if (rc == EXIT_FAILURE) - ctx.err << "\e[41mwifi: GetPassword failed\e[39m\n"; + ctx.err << "\e[91mwifi: GetPassword failed\e[39m\n"; return rc; } } if (!wfcBeginConnect(&ap, &auth)) { - ctx.err << "\e[41mwifi: wfcBeginConnect failed\e[39m\n"; + ctx.err << "\e[91mwifi: wfcBeginConnect failed\e[39m\n"; return EXIT_FAILURE; } @@ -264,7 +264,7 @@ void subcommand_connect(const Commands::Context &ctx) if (aplist.empty()) { - ctx.err << "\e[41mwifi: No APs detected\e[39m\n"; + ctx.err << "\e[91mwifi: No APs detected\e[39m\n"; return; } @@ -273,7 +273,7 @@ void subcommand_connect(const Commands::Context &ctx) if (it == aplist.end()) { - ctx.err << "\e[41mwifi: SSID '" << ssid << "' not found\e[39m\n"; + ctx.err << "\e[91mwifi: SSID '" << ssid << "' not found\e[39m\n"; return; } @@ -289,10 +289,10 @@ void subcommand_connect(const Commands::Context &ctx) switch (ConnectAP(ctx, *ap)) { case EXIT_SUCCESS: - ctx.out << "\e[42mwifi: connection successful\e[39m\n"; + ctx.out << "\e[92mwifi: connection successful\e[39m\n"; break; case EXIT_FAILURE: - ctx.err << "\e[41mwifi: connection failed\e[39m\n"; + ctx.err << "\e[91mwifi: connection failed\e[39m\n"; break; case USER_CANCEL: ctx.out << "wifi: connection canceled\n"; @@ -306,10 +306,10 @@ void subcommand_autoconnect(std::ostream &ostr) switch (WaitForConnection(ostr)) { case EXIT_SUCCESS: - ostr << "\e[42mwifi: autoconnect successful\e[39m\n"; + ostr << "\e[92mwifi: autoconnect successful\e[39m\n"; break; case EXIT_FAILURE: - ostr << "\e[41mwifi: autoconnect failed\n\e[39m"; + ostr << "\e[91mwifi: autoconnect failed\n\e[39m"; break; case USER_CANCEL: ostr << "wifi: autoconnect canceled\n"; @@ -338,7 +338,7 @@ void subcommand_scan(const Commands::Context &ctx) { // ScanAPs already prints messages on failure or cancellation, // but not if no APs are found. - ctx.err << "\e[41mwifi: No APs detected\e[39m\n"; + ctx.err << "\e[91mwifi: No APs detected\e[39m\n"; return; } @@ -373,7 +373,7 @@ void Commands::wifi(const Context &ctx) if (subcommand.starts_with("con")) subcommand_connect(ctx); else if (subcommand.starts_with("dis") && Wifi_DisconnectAP()) - ctx.err << "\e[41mwifi: Wifi_DisconnectAP failed\e[39m\n"; + ctx.err << "\e[91mwifi: Wifi_DisconnectAP failed\e[39m\n"; else if (subcommand.starts_with("stat")) subcommand_status(ctx); else if (subcommand.starts_with("auto")) diff --git a/src/main.cpp b/src/main.cpp index 28bfb8a..74ae35b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,19 +17,19 @@ void InitResources() ostr << "initializing filesystem..."; if (!fatInitDefault()) - ostr << "\r\e[2K\e[41mfat init failed: filesystem commands will " + ostr << "\r\e[2K\e[91mfat init failed: filesystem commands will " "not work\e[39m\n"; else - ostr << "\r\e[2K\e[42mfilesystem intialized!\n"; + ostr << "\r\e[2K\e[92mfilesystem intialized!\n"; ostr << "initializing wifi..."; if (!wlmgrInitDefault() || !wfcInit()) - ostr << "\r\e[2K\e[41mwifi init failed: networking commands will not " + ostr << "\r\e[2K\e[91mwifi init failed: networking commands will not " "work\e[39m\n"; else { - ostr << "\r\e[2K\e[42mwifi initialized!\n\e[39mautoconnecting..."; + ostr << "\r\e[2K\e[92mwifi initialized!\n\e[39mautoconnecting..."; subcommand_autoconnect(ostr); } @@ -41,7 +41,7 @@ void PrintGreeting(int console, bool clearScreen = true) auto &ostr = Consoles::GetStream(console); if (clearScreen) ostr << "\e[2J\e[H"; // Clear screen and move cursor to home - ostr << "\e[42mnds-shell (con" << console << ")\e[39m\n\nPress START to start a shell\n\n"; + ostr << "\e[92mnds-shell (con" << console << ")\e[39m\n\nPress START to start a shell\n\n"; } bool running[Consoles::NUM_CONSOLES]{}; From 62fdd4c0f457321a8f79084ff9f2d5448aec5db5 Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:30:43 -0400 Subject: [PATCH 2/3] ci: whoops --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b5286c..d7ac8af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,7 @@ jobs: run: | sudo dkp-pacman -Rdd libnds --noconfirm git clone https://github.com/trustytrojan/libnds -b ansi-colors + cd libnds sudo make install - name: Checkout code From 9d61d45a9febd84647738b65d4024f0e9e7f1116 Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:31:47 -0400 Subject: [PATCH 3/3] ci: we arent root, include env vars in sudo cmd --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7ac8af..ad0319f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: sudo dkp-pacman -Rdd libnds --noconfirm git clone https://github.com/trustytrojan/libnds -b ansi-colors cd libnds - sudo make install + sudo -E make install -j$(nproc) - name: Checkout code uses: actions/checkout@v4