Skip to content

Commit

Permalink
IPv6 support
Browse files Browse the repository at this point in the history
  • Loading branch information
allanrbo committed Sep 30, 2021
1 parent 6f774f1 commit e0dfd08
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 31 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.10)

project(filesremote VERSION 1.5 LANGUAGES CXX)
project(filesremote VERSION 1.6 LANGUAGES CXX)

SET(CMAKE_CXX_STANDARD 17)

Expand Down
2 changes: 1 addition & 1 deletion README.DEVELOPER.md
Expand Up @@ -6,7 +6,7 @@ Development
Get and build the 3rd party dependencies:

# Only on Linux:
sudo apt install libssl-dev libsecret-1-dev
sudo apt install libssl-dev libsecret-1-dev libgtk-3-dev

# Only on macOS:
xcode-select --install
Expand Down
18 changes: 15 additions & 3 deletions README.md
Expand Up @@ -35,9 +35,21 @@ Linux:
Usage
-----

Command line usage: `filesremote [[username@]host[:port]]`.

E.g. `filesremote 192.168.1.60` or `filesremote user1@192.168.1.60:22`. Defaults to your local username and port 22.
Command line usage:
```
Usage: filesremote [-h] [-i <str>] [-pw <str>] [[username@]host[:port]]
-h, --help displays help
-i, --identity-file=<str> selects a file from which the identity (private key) for public key authentication is read
-pw, --password=<str> password to use for authentication and sudo (WARNING: Insecure! Will appear in your shell history!)
Example: filesremote example.com
Example: filesremote 192.168.1.60
Example: filesremote user1@192.168.1.60:22
Example: filesremote 2001:db8::1
Example: filesremote [2001:db8::1]
Example: filesremote [2001:db8::1]:2222
```

Defaults to your local username and port 22 if unspecified.

### MacOS specific

Expand Down
63 changes: 54 additions & 9 deletions src/hostdesc.cpp
Expand Up @@ -58,23 +58,49 @@ HostDesc::HostDesc(string host, string identity_file) {
this->host_ = this->host_.substr(i + 1);
}

// Check if there's a port number given.
int colon_count = 0;
string::size_type pos = 0;
while ((pos = this->host_.find(":", pos )) != string::npos) {
colon_count++;
pos++;
}

string port_string = "";
if (colon_count == 1) {
// If there is only one colon, this is probably a port delimiter, and not port of an IPv6 address.
string::size_type i = this->host_.rfind(":");
port_string = string(this->host_.substr(i + 1));
this->host_ = this->host_.substr(0, i);
} else if (colon_count > 1) {
this->is_ipv6_literal_ = true;

// Is this an IPv6 address like "[2001:db8::1]"?
if (this->host_[0] == '[') {
string::size_type i = this->host_.rfind("]");
if (i != string::npos) {
string rest = this->host_.substr(i);
this->host_ = this->host_.substr(1, i - 1);

// Is this an IPv6 address like "[2001:db8::1]:22"?
i = rest.rfind(":");
if (i != string::npos) {
port_string = string(rest.substr(i + 1));
}
}
}
}

bool port_given = false;
if (this->host_.find(":") != string::npos) {
if (!port_string.empty()) {
port_given = true;

int i = this->host_.find(":");

string ps = string(this->host_.substr(i + 1));
if (!all_of(ps.begin(), ps.end(), ::isdigit)) {
if (!all_of(port_string.begin(), port_string.end(), ::isdigit)) {
throw invalid_argument("non-digit port number");
}
this->port_ = stoi(string(ps));
this->port_ = stoi(string(port_string));
if (!(0 < this->port_ && this->port_ < 65536)) {
throw invalid_argument("invalid port number");
}

this->host_ = this->host_.substr(0, i);
}

// The "Host"-lines in ~/.ssh/config may differ from the actual DNS name or IP in the "HostName" field.
Expand Down Expand Up @@ -173,20 +199,39 @@ HostDesc::HostDesc(string host, string identity_file) {

string HostDesc::ToString() {
string s = this->username_ + "@" + this->host_ + ":" + to_string(this->port_);

if (this->is_ipv6_literal_) {
s = this->username_ + "@[" + this->host_ + "]:" + to_string(this->port_);
}

if (this->host_ != this->display_host_) {
s += " (" + this->display_host_ + ")";
}
return s;
}

string HostDesc::ToStringNoCol() {
if (this->is_ipv6_literal_) {
string h = std::regex_replace(this->host_, std::regex(":"), ".");
return this->username_ + "@[" + h + "]_" + to_string(this->port_);
}

return this->username_ + "@" + this->host_ + "_" + to_string(this->port_);
}

string HostDesc::ToStringNoUser() {
if (this->is_ipv6_literal_) {
return "[" + this->host_ + "]:" + to_string(this->port_);
}

return this->host_ + ":" + to_string(this->port_);
}

string HostDesc::ToStringNoUserNoCol() {
if (this->is_ipv6_literal_) {
string h = std::regex_replace(this->host_, std::regex(":"), ".");
return "[" + h + "]_" + to_string(this->port_);
}

return this->host_ + "_" + to_string(this->port_);
}
1 change: 1 addition & 0 deletions src/hostdesc.h
Expand Up @@ -16,6 +16,7 @@ class HostDesc {
string display_host_; // This is what will be in the "Host"-line of ~/.ssh/config.
string username_;
int port_ = 22;
bool is_ipv6_literal_ = false;
vector<string> identity_files_;

HostDesc() {}
Expand Down
10 changes: 8 additions & 2 deletions src/main.cpp
Expand Up @@ -89,7 +89,7 @@ static void cleanUpOrphanedTmpDirs(string local_tmp) {
}
}
closedir(d);
for (int i = 0; i < to_delete.size(); ++i) {
for (int i = 0 ; i < to_delete.size() ; ++i) {
try {
remove_all(localPathUnicode(to_delete[i]));
} catch (...) {
Expand Down Expand Up @@ -180,11 +180,17 @@ class FilesRemoteApp : public wxApp {
"password to use for authentication and sudo (WARNING: Insecure! Will appear in your shell history!)",
wxCMD_LINE_VAL_STRING,
wxCMD_LINE_PARAM_OPTIONAL);
parser.AddUsageText("Example: filesremote example.com");
parser.AddUsageText("Example: filesremote 192.168.1.60");
parser.AddUsageText("Example: filesremote user1@192.168.1.60:22");
parser.AddUsageText("Example: filesremote 2001:db8::1");
parser.AddUsageText("Example: filesremote [2001:db8::1]");
parser.AddUsageText("Example: filesremote [2001:db8::1]:2222");
}

virtual bool OnCmdLineParsed(wxCmdLineParser &parser) { // NOLINT: wxWidgets legacy
auto args = parser.GetArguments();
for (auto it = args.begin(); it != args.end(); it++) {
for (auto it = args.begin() ; it != args.end() ; it++) {
if (it->GetKind() == wxCMD_LINE_OPTION && it->GetLongName() == "identity-file") {
this->identity_file_ = it->GetStrVal();
if (!exists(this->identity_file_)) {
Expand Down
41 changes: 26 additions & 15 deletions src/sftpconnection.cpp
Expand Up @@ -5,6 +5,7 @@
#ifdef __WXMSW__

#include <winsock2.h>
#include <ws2tcpip.h>

#else

Expand Down Expand Up @@ -124,25 +125,35 @@ SftpConnection::SftpConnection(HostDesc host_desc) {
throw ConnectionError("libssh2_init failed. " + this->GetLastErrorMsg());
}

this->sock_ = socket(AF_INET, SOCK_STREAM, 0);
struct addrinfo *result;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6.
hints.ai_socktype = SOCK_STREAM;

struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(this->host_desc_.port_);
sin.sin_addr.s_addr = inet_addr(this->host_desc_.host_.c_str());
if (sin.sin_addr.s_addr == INADDR_NONE) {
struct hostent *remote_host = gethostbyname(this->host_desc_.host_.c_str());
if (!remote_host) {
throw ConnectionError(
"failed to resolve hostname " + this->host_desc_.host_ + " (gethostbyname failed)");
rc = getaddrinfo(this->host_desc_.host_.c_str(), to_string(this->host_desc_.port_).c_str(), &hints, &result);
if (rc != 0) {
throw ConnectionError("failed to resolve hostname " + this->host_desc_.host_);
}

struct addrinfo *rp;
for (rp = result ; rp != NULL ; rp = rp->ai_next) {
this->sock_ = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (this->sock_ == -1) {
continue;
}

sin.sin_addr.s_addr = *reinterpret_cast<u_long *>(remote_host->h_addr_list[0]);
if (connect(this->sock_, rp->ai_addr, rp->ai_addrlen) != -1) {
break; // Success
}

close(this->sock_);
}

if (connect(this->sock_, (struct sockaddr *) (&sin), sizeof(struct sockaddr_in)) != 0) {
freeaddrinfo(result);
if (rp == NULL) {
throw ConnectionError(
"socket connect failed on " + this->host_desc_.host_ + ":" + to_string(this->host_desc_.port_));
"could not connect to " + this->host_desc_.host_ + " on port " + to_string(this->host_desc_.port_));
}

this->session_ = libssh2_session_init();
Expand All @@ -162,7 +173,7 @@ SftpConnection::SftpConnection(HostDesc host_desc) {
int hostkey_algos[3]{LIBSSH2_HOSTKEY_HASH_SHA256, LIBSSH2_HOSTKEY_HASH_SHA1, LIBSSH2_HOSTKEY_HASH_MD5};
string hostkey_algo_names[3]{"SHA256", "SHA1", "MD5"};
int hostkey_algo_keylen[3]{32, 20, 16};
for (int i = 0; i < 3; ++i) {
for (int i = 0 ; i < 3 ; ++i) {
const char *fingerprint = libssh2_hostkey_hash(this->session_, hostkey_algos[i]);
if (fingerprint == NULL) {
continue;
Expand Down Expand Up @@ -815,7 +826,7 @@ void SftpConnection::SudoEnter(bool needs_passwd_again) {
"/usr/libexec/openssh/sftp-server"
};
string sftp_server_path;
for (int i = 0; i < sftp_server_paths.size(); ++i) {
for (int i = 0 ; i < sftp_server_paths.size() ; ++i) {
if (this->Stat(sftp_server_paths[i]).has_value()) {
sftp_server_path = sftp_server_paths[i];
break;
Expand Down

0 comments on commit e0dfd08

Please sign in to comment.