From c0e077d0927ff2444bb22f38cb1f899097cad926 Mon Sep 17 00:00:00 2001 From: James Short Date: Thu, 13 Oct 2022 10:33:01 -0700 Subject: [PATCH] ipv6 abbreviated address support This will support ipv6 addresses that are abbreviated with `::` such as ::1 (for 0:0:0:0:0:0:0:1) with some caveats around specifying the port in the host positional argument. Help output: ``` $> ./et --help Remote shell for the busy and impatient Usage: et [OPTION...] [user@]host[:port] Note that 'host' can be a hostname or ipv4 address with or without a port or an ipv6 address. If the ipv6 address is abbreviated with :: then it must be specfied without a port (use -p,--port). -h, --help Print help --version Print version -u, --username Username --host arg Remote host name ... ``` Tests: ``` $> ./et ::1 -p 8080 --macserver $> ./et 0:0:0:0:0:0:0:1 -p 8080 --macserver $> ./et 0:0:0:0:0:0:0:1:8080 --macserver $> ./et ::1 -p 8080 --macserver # fails because defaults to port 2022 ``` Fixes #537 --- .circleci/config.yml | 6 ++ src/terminal/TerminalClientMain.cpp | 93 ++++++++++++++++++----------- 2 files changed, 63 insertions(+), 36 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 42f5e6f07..fad233f18 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -38,6 +38,12 @@ jobs: - run: name: Connect new -> old (ipv6 with port in host) command: build/et -c "ls" 0:0:0:0:0:0:0:1:2022 --logtostdout --verbose=9 + - run: + name: Connect new -> old (ipv6 abbreviated) + command: build/et -c "ls" ::1 --logtostdout --verbose=9 + - run: + name: Connect new -> old (ipv6 abbreviated with port arg) + command: build/et -c "ls" ::1 --port 2022 --logtostdout --verbose=9 - run: name: Kill server command: sudo pkill etserver diff --git a/src/terminal/TerminalClientMain.cpp b/src/terminal/TerminalClientMain.cpp index 38979c8ea..5b511da35 100644 --- a/src/terminal/TerminalClientMain.cpp +++ b/src/terminal/TerminalClientMain.cpp @@ -38,60 +38,65 @@ int main(int argc, char** argv) { // Parse command line arguments cxxopts::Options options("et", "Remote shell for the busy and impatient"); try { - options.positional_help("[user@]hostname[:port]").show_positional_help(); options.allow_unrecognised_options(); - - options.add_options() // - ("h,help", "Print help") // - ("version", "Print version") // - ("u,username", "Username") // + options.positional_help(""); + options.custom_help("[OPTION...] [user@]host[:port]\n\n" + " Note that 'host' can be a hostname or ipv4 address with or without a port\n" + " or an ipv6 address. If the ipv6 address is abbreviated with :: then it must\n" + " be specfied without a port (use -p,--port)." + ); + + options.add_options() + ("h,help", "Print help") + ("version", "Print version") + ("u,username", "Username") ("host", "Remote host name", - cxxopts::value()) // - ("p,port", "Remote machine port", - cxxopts::value()->default_value("2022")) // + cxxopts::value()) + ("p,port", "Remote machine etserver port", + cxxopts::value()->default_value("2022")) ("c,command", "Run command on connect", - cxxopts::value()) // + cxxopts::value()) ("terminal-path", "Path to etterminal on server side. " "Use if etterminal is not on the system path.", - cxxopts::value()) // + cxxopts::value()) ("t,tunnel", "Tunnel: Array of source:destination ports or " "srcStart-srcEnd:dstStart-dstEnd (inclusive) port ranges (e.g. " "10080:80,10443:443, 10090-10092:8000-8002)", - cxxopts::value()) // + cxxopts::value()) ("r,reversetunnel", "Reverse Tunnel: Array of source:destination ports or " "srcStart-srcEnd:dstStart-dstEnd (inclusive) port ranges", - cxxopts::value()) // + cxxopts::value()) ("jumphost", "jumphost between localhost and destination", - cxxopts::value()) // + cxxopts::value()) ("jport", "Jumphost machine port", - cxxopts::value()->default_value("2022")) // + cxxopts::value()->default_value("2022")) ("x,kill-other-sessions", - "kill all old sessions belonging to the user") // + "kill all old sessions belonging to the user") ("macserver", "Set when connecting to an macOS server. Sets " - "--terminal-path=/usr/local/bin/etterminal") // + "--terminal-path=/usr/local/bin/etterminal") ("v,verbose", "Enable verbose logging", - cxxopts::value()->default_value("0")) // + cxxopts::value()->default_value("0")) ("k,keepalive", "Client keepalive duration in seconds", - cxxopts::value()) // - ("logtostdout", "Write log to stdout") // - ("silent", "Disable logging") // - ("N,no-terminal", "Do not create a terminal") // - ("f,forward-ssh-agent", "Forward ssh-agent socket") // + cxxopts::value()) + ("logtostdout", "Write log to stdout") + ("silent", "Disable logging") + ("N,no-terminal", "Do not create a terminal") + ("f,forward-ssh-agent", "Forward ssh-agent socket") ("ssh-socket", "The ssh-agent socket to forward", - cxxopts::value()) // + cxxopts::value()) ("telemetry", "Allow et to anonymously send errors to guide future improvements", - cxxopts::value()->default_value("true")) // + cxxopts::value()->default_value("true")) ("serverfifo", - "If set, communicate to etserver on the matching fifo name", // - cxxopts::value()->default_value("")) // + "If set, communicate to etserver on the matching fifo name", + cxxopts::value()->default_value("")) ("ssh-option", "Options to pass down to `ssh -o`", cxxopts::value>()); - options.parse_positional({"host", "positional"}); + options.parse_positional({"host"}); auto result = options.parse(argc, argv); if (result.count("help")) { @@ -154,24 +159,40 @@ int main(int argc, char** argv) { if (host_arg.find(':') != string::npos) { int colon_count = std::count(host_arg.begin(), host_arg.end(), ':'); - if (colon_count == 1 || colon_count == 8) { - // ipv4 or hostname or ipv6 with port specified + if (colon_count == 1) { + // ipv4 or hostname with port specified int port_colon_pos = host_arg.rfind(':'); destinationPort = stoi(host_arg.substr(port_colon_pos + 1)); host_arg = host_arg.substr(0, port_colon_pos); - } else if (colon_count == 7) { - // ipv6 without port specified } else { - CLOG(INFO, "stdout") << "Invalid host positional arg: " - << result["host"].as() << endl; - exit(1); + // maybe ipv6 (colon_count >= 2) + if(host_arg.find("::") != string::npos) { + // ipv6 with double colon zero abbreviation and no port + // leave host_arg as is + } else { + if (colon_count == 7) { + // ipv6, fully expanded, without port + } else if (colon_count == 8) { + // ipv6, fully expanded, with port + int port_colon_pos = host_arg.rfind(':'); + destinationPort = stoi(host_arg.substr(port_colon_pos + 1)); + host_arg = host_arg.substr(0, port_colon_pos); + } else { + CLOG(INFO, "stdout") << "Invalid host positional arg: " + << result["host"].as() << endl; + exit(1); + } + } } } destinationHost = host_arg; + // host_alias is used for the initiating ssh call, if sshd runs on a port + // other than 22, either configure your .ssh/config with an alias with an + // overridden port or pass --ssh-option Port= + string host_alias = destinationHost; string jumphost = result.count("jumphost") ? result["jumphost"].as() : ""; - string host_alias = destinationHost; int keepaliveDuration = result.count("keepalive") ? result["keepalive"].as() : MAX_CLIENT_KEEP_ALIVE_DURATION; if (keepaliveDuration < 1 || keepaliveDuration > MAX_CLIENT_KEEP_ALIVE_DURATION) { CLOG(INFO, "stdout") << "Keep-alive duration must between 1 and " << MAX_CLIENT_KEEP_ALIVE_DURATION << " seconds" << endl;