cli: fix +ssh-cache IPv6 address validation (#9281)
The host validation code previously expected IPv6 addresses to be enclosed in [brackets], but that's not how ssh(1) expects them. This change removes that requirement and reimplements the host validation routine to check for valid hostnames and IP addresses (IPv4 and IPv6) using standard routines rather than custom logic. Fixes #9251pull/9288/head
commit
c65e837b22
|
|
@ -7,8 +7,9 @@ const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const xdg = @import("../../os/main.zig").xdg;
|
const internal_os = @import("../../os/main.zig");
|
||||||
const TempDir = @import("../../os/main.zig").TempDir;
|
const xdg = internal_os.xdg;
|
||||||
|
const TempDir = internal_os.TempDir;
|
||||||
const Entry = @import("Entry.zig");
|
const Entry = @import("Entry.zig");
|
||||||
|
|
||||||
// 512KB - sufficient for approximately 10k entries
|
// 512KB - sufficient for approximately 10k entries
|
||||||
|
|
@ -334,48 +335,28 @@ fn isValidCacheKey(key: []const u8) bool {
|
||||||
if (std.mem.indexOf(u8, key, "@")) |at_pos| {
|
if (std.mem.indexOf(u8, key, "@")) |at_pos| {
|
||||||
const user = key[0..at_pos];
|
const user = key[0..at_pos];
|
||||||
const hostname = key[at_pos + 1 ..];
|
const hostname = key[at_pos + 1 ..];
|
||||||
return isValidUser(user) and isValidHostname(hostname);
|
return isValidUser(user) and isValidHost(hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
return isValidHostname(key);
|
return isValidHost(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic hostname validation - accepts domains and IPs
|
// Checks if a host is a valid hostname or IP address
|
||||||
// (including IPv6 in brackets)
|
fn isValidHost(host: []const u8) bool {
|
||||||
fn isValidHostname(host: []const u8) bool {
|
// First check for valid hostnames because this is assumed to be the more
|
||||||
if (host.len == 0 or host.len > 253) return false;
|
// likely ssh host format.
|
||||||
|
if (internal_os.hostname.isValid(host)) {
|
||||||
// Handle IPv6 addresses in brackets
|
return true;
|
||||||
if (host.len >= 4 and host[0] == '[' and host[host.len - 1] == ']') {
|
|
||||||
const ipv6_part = host[1 .. host.len - 1];
|
|
||||||
if (ipv6_part.len == 0) return false;
|
|
||||||
var has_colon = false;
|
|
||||||
for (ipv6_part) |c| {
|
|
||||||
switch (c) {
|
|
||||||
'a'...'f', 'A'...'F', '0'...'9' => {},
|
|
||||||
':' => has_colon = true,
|
|
||||||
else => return false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return has_colon;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Standard hostname/domain validation
|
// We also accept valid IP addresses. In practice, IPv4 addresses are also
|
||||||
for (host) |c| {
|
// considered valid hostnames due to their overlapping syntax, so we can
|
||||||
switch (c) {
|
// simplify this check to be IPv6-specific.
|
||||||
'a'...'z', 'A'...'Z', '0'...'9', '.', '-' => {},
|
if (std.net.Address.parseIp6(host, 0)) |_| {
|
||||||
else => return false,
|
return true;
|
||||||
}
|
} else |_| {
|
||||||
}
|
|
||||||
|
|
||||||
// No leading/trailing dots or hyphens, no consecutive dots
|
|
||||||
if (host[0] == '.' or host[0] == '-' or
|
|
||||||
host[host.len - 1] == '.' or host[host.len - 1] == '-')
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return std.mem.indexOf(u8, host, "..") == null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isValidUser(user: []const u8) bool {
|
fn isValidUser(user: []const u8) bool {
|
||||||
|
|
@ -475,42 +456,36 @@ test "disk cache operations" {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
test "hostname validation - valid cases" {
|
|
||||||
const testing = std.testing;
|
|
||||||
try testing.expect(isValidHostname("example.com"));
|
|
||||||
try testing.expect(isValidHostname("sub.example.com"));
|
|
||||||
try testing.expect(isValidHostname("host-name.domain.org"));
|
|
||||||
try testing.expect(isValidHostname("192.168.1.1"));
|
|
||||||
try testing.expect(isValidHostname("a"));
|
|
||||||
try testing.expect(isValidHostname("1"));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "hostname validation - IPv6 addresses" {
|
test isValidHost {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
try testing.expect(isValidHostname("[::1]"));
|
|
||||||
try testing.expect(isValidHostname("[2001:db8::1]"));
|
|
||||||
try testing.expect(!isValidHostname("[fe80::1%eth0]")); // Interface notation not supported
|
|
||||||
try testing.expect(!isValidHostname("[]")); // Empty IPv6
|
|
||||||
try testing.expect(!isValidHostname("[invalid]")); // No colons
|
|
||||||
}
|
|
||||||
|
|
||||||
test "hostname validation - invalid cases" {
|
// Valid hostnames
|
||||||
const testing = std.testing;
|
try testing.expect(isValidHost("localhost"));
|
||||||
try testing.expect(!isValidHostname(""));
|
try testing.expect(isValidHost("example.com"));
|
||||||
try testing.expect(!isValidHostname("host\nname"));
|
try testing.expect(isValidHost("sub.example.com"));
|
||||||
try testing.expect(!isValidHostname(".example.com"));
|
|
||||||
try testing.expect(!isValidHostname("example.com."));
|
|
||||||
try testing.expect(!isValidHostname("host..domain"));
|
|
||||||
try testing.expect(!isValidHostname("-hostname"));
|
|
||||||
try testing.expect(!isValidHostname("hostname-"));
|
|
||||||
try testing.expect(!isValidHostname("host name"));
|
|
||||||
try testing.expect(!isValidHostname("host_name"));
|
|
||||||
try testing.expect(!isValidHostname("host@domain"));
|
|
||||||
try testing.expect(!isValidHostname("host:port"));
|
|
||||||
|
|
||||||
// Too long
|
// IPv4 addresses
|
||||||
const long_host = "a" ** 254;
|
try testing.expect(isValidHost("127.0.0.1"));
|
||||||
try testing.expect(!isValidHostname(long_host));
|
try testing.expect(isValidHost("192.168.1.1"));
|
||||||
|
|
||||||
|
// IPv6 addresses
|
||||||
|
try testing.expect(isValidHost("::1"));
|
||||||
|
try testing.expect(isValidHost("2001:db8::1"));
|
||||||
|
try testing.expect(isValidHost("2001:db8:0:1:1:1:1:1"));
|
||||||
|
try testing.expect(!isValidHost("fe80::1%eth0")); // scopes not supported
|
||||||
|
|
||||||
|
// Invalid hosts
|
||||||
|
try testing.expect(!isValidHost(""));
|
||||||
|
try testing.expect(!isValidHost("host\nname"));
|
||||||
|
try testing.expect(!isValidHost(".example.com"));
|
||||||
|
try testing.expect(!isValidHost("host..domain"));
|
||||||
|
try testing.expect(!isValidHost("-hostname"));
|
||||||
|
try testing.expect(!isValidHost("hostname-"));
|
||||||
|
try testing.expect(!isValidHost("host name"));
|
||||||
|
try testing.expect(!isValidHost("host_name"));
|
||||||
|
try testing.expect(!isValidHost("host@domain"));
|
||||||
|
try testing.expect(!isValidHost("host:port"));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "user validation - valid cases" {
|
test "user validation - valid cases" {
|
||||||
|
|
@ -551,7 +526,7 @@ test "cache key validation - hostname format" {
|
||||||
try testing.expect(isValidCacheKey("example.com"));
|
try testing.expect(isValidCacheKey("example.com"));
|
||||||
try testing.expect(isValidCacheKey("sub.example.com"));
|
try testing.expect(isValidCacheKey("sub.example.com"));
|
||||||
try testing.expect(isValidCacheKey("192.168.1.1"));
|
try testing.expect(isValidCacheKey("192.168.1.1"));
|
||||||
try testing.expect(isValidCacheKey("[::1]"));
|
try testing.expect(isValidCacheKey("::1"));
|
||||||
try testing.expect(!isValidCacheKey(""));
|
try testing.expect(!isValidCacheKey(""));
|
||||||
try testing.expect(!isValidCacheKey(".invalid.com"));
|
try testing.expect(!isValidCacheKey(".invalid.com"));
|
||||||
}
|
}
|
||||||
|
|
@ -563,7 +538,7 @@ test "cache key validation - user@hostname format" {
|
||||||
try testing.expect(isValidCacheKey("test-user@192.168.1.1"));
|
try testing.expect(isValidCacheKey("test-user@192.168.1.1"));
|
||||||
try testing.expect(isValidCacheKey("user_name@host.domain.org"));
|
try testing.expect(isValidCacheKey("user_name@host.domain.org"));
|
||||||
try testing.expect(isValidCacheKey("git@github.com"));
|
try testing.expect(isValidCacheKey("git@github.com"));
|
||||||
try testing.expect(isValidCacheKey("ubuntu@[::1]"));
|
try testing.expect(isValidCacheKey("ubuntu@::1"));
|
||||||
try testing.expect(!isValidCacheKey("@example.com"));
|
try testing.expect(!isValidCacheKey("@example.com"));
|
||||||
try testing.expect(!isValidCacheKey("user@"));
|
try testing.expect(!isValidCacheKey("user@"));
|
||||||
try testing.expect(!isValidCacheKey("user@@host"));
|
try testing.expect(!isValidCacheKey("user@@host"));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue