From 688744997a27d0248b7fe0a6d83e3a0c83912157 Mon Sep 17 00:00:00 2001 From: Milan Lenco Date: Thu, 6 Aug 2026 09:24:31 +0200 Subject: [PATCH] zedUpload/sftp: accept a scheme-qualified host as well as bare host:port getSftpClient assumed host was always a bare "host:port" and extracted the hostname for its pre-flight DNS check via a naive strings.Split(host, ":"), taking the first element. Since https://github.com/lf-edge/eve/pull/5588, EVE's datastore Fqdn is always scheme-qualified for every datastore type, including SFTP (e.g. "sftp://host:port"), which broke that assumption: the first element became the literal scheme "sftp" instead of the real host, causing the SFTP download to fail with: sftpclient failed for sftp://244.244.244.244:22: lookup sftp on 127.0.0.1:53: no such host (i.e. trying to resolve "sftp") Detect which form was given via url.Parse: a real scheme-qualified authority-form URL parses with a non-empty Host, whereas a bare host:port also "succeeds" but only as an opaque URI (empty Host, with the part before the port's colon landing in Scheme/Opaque instead). Use u.Host when present, and fall back to the original bare host:port otherwise, so existing callers outside EVE are unaffected. Also replaces the naive colon-split used for the hostname-only DNS pre-check with net.SplitHostPort, which additionally fixes handling of a bracketed IPv6 literal (e.g. "[::1]:22") in both forms. Signed-off-by: Milan Lenco --- zedUpload/sftputil/sftp.go | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/zedUpload/sftputil/sftp.go b/zedUpload/sftputil/sftp.go index e8e8d36..e90690c 100644 --- a/zedUpload/sftputil/sftp.go +++ b/zedUpload/sftputil/sftp.go @@ -8,6 +8,7 @@ import ( "io" "log" "net" + "net/url" "os" "strings" "time" @@ -46,14 +47,31 @@ func getSftpClient(host, user, pass string) (*sftp.Client, error) { Timeout: time.Duration(10) * time.Second, } + // host may be a bare "host:port" (the original contract, still used by + // older EVE versions and potentially callers outside EVE) or a scheme-qualified + // URL such as "sftp://host:port", which newer EVE versions now require + // (since https://github.com/lf-edge/eve/pull/5588). + // url.Parse "succeeds" on a bare host:port too, but only as an opaque URI + // (an empty Host, with the part before the port's colon landing in Scheme/Opaque + // instead), so u.Host is what actually distinguishes the two: non-empty only for a + // real scheme-qualified authority-form URL. + dialAddr := host + if u, err := url.Parse(host); err == nil && u.Host != "" { + dialAddr = u.Host + } + // We break this up into a DNS lookup and a Dial only to be able to detect - // the errors better - args := strings.Split(host, ":") - if _, err := net.LookupHost(args[0]); err != nil { + // the errors better. net.SplitHostPort (rather than a naive split on ":") + // also correctly handles a bracketed IPv6 literal. + hostname, _, err := net.SplitHostPort(dialAddr) + if err != nil { + hostname = dialAddr + } + if _, err := net.LookupHost(hostname); err != nil { log.Printf("LookupHost error: %s", err) return nil, err } - client, err := ssh.Dial("tcp", host, clientConfig) + client, err := ssh.Dial("tcp", dialAddr, clientConfig) if err != nil { return nil, err }