This is an automated email from the ASF dual-hosted git repository.

twolf pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git


The following commit(s) were added to refs/heads/master by this push:
     new c66429a4b GH-865: replace %h in HostName SSH config
c66429a4b is described below

commit c66429a4b2269ec6cfcd8a632e7d124dce655411
Author: Thomas Wolf <[email protected]>
AuthorDate: Wed Jan 7 20:28:12 2026 +0100

    GH-865: replace %h in HostName SSH config
    
    OpenSSH does so. It enables for instance to write an SSH host entry
    
        Host foo*
        HostName %h.example.com
    
    to resolve "foobar" to "foobar.example.com" and "foobaz" to
    "foobaz.example.com".
---
 CHANGES.md                                         |  1 +
 .../sshd/client/config/hosts/HostConfigEntry.java  | 34 ++++++++++++++++++++++
 .../client/config/hosts/HostConfigEntryTest.java   | 14 +++++----
 3 files changed, 43 insertions(+), 6 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 7a085eaf6..59d7a19b9 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -44,6 +44,7 @@
 
 * [GH-814](https://github.com/apache/mina-sshd/pull/814) Include a fix for 
CVE-2020-36843 in optional dependency net.i2p.crypto:eddsa:0.3.0: perform the 
missing range check in Apache MINA SSHD before delegating to the signature 
verification in net.i2p.crypto:eddsa:0.3.0. This means that using 
net.i2p.crypto:eddsa:0.3.0 in Apache MINA SSHD is
 safe despite that CVE in the dependency.
+* [GH-865](https://github.com/apache/mina-sshd/issues/865) replace `%h` in 
`HostName` SSH config
 
 ## Potential Compatibility Issues
 
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
 
b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
index 6e3608b83..205dedce6 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
@@ -647,6 +647,8 @@ public class HostConfigEntry extends HostPatternsHolder 
implements MutableUserHo
                 String temp = entry.getHostName();  // Remember that this was 
null above.
                 if (temp == null || temp.isEmpty()) {
                     entry.setHostName(host);
+                } else {
+                    entry.setHostName(substituteHostName(temp, host));
                 }
                 temp = entry.getUsername();
                 if (temp == null || temp.isEmpty()) {
@@ -919,6 +921,38 @@ public class HostConfigEntry extends HostPatternsHolder 
implements MutableUserHo
         return sb.toString();
     }
 
+    private static String substituteHostName(String host, String 
originalHostName) {
+        int len = host.length();
+        int j = host.indexOf(PATH_MACRO_CHAR);
+        if (j < 0) {
+            return host;
+        }
+        StringBuilder result = new StringBuilder(len);
+        result.append(host.substring(0, j));
+        for (int i = j; i < len; i++) {
+            char ch = host.charAt(i);
+            if (ch == PATH_MACRO_CHAR) {
+                i++;
+                ValidateUtils.checkTrue(i < len, "Missing macro modifier in 
hostname %s", host);
+                ch = host.charAt(i);
+                switch (ch) {
+                    case PATH_MACRO_CHAR:
+                        result.append(ch);
+                        break;
+                    case REMOTE_HOST_MACRO:
+                        result.append(originalHostName);
+                        break;
+                    default:
+                        ValidateUtils.throwIllegalArgumentException("Bad 
modifier '%s' in hostname %s", String.valueOf(ch),
+                                host);
+                }
+            } else {
+                result.append(ch);
+            }
+        }
+        return result.toString();
+    }
+
     /**
      * @return The default {@link Path} location of the OpenSSH hosts entries 
configuration file
      */
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java
index c53fd518d..16c123785 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java
@@ -36,12 +36,6 @@ import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestMethodOrder;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
 /**
  * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
  */
@@ -95,6 +89,14 @@ public class HostConfigEntryTest extends JUnitTestSupport {
         expect("foo.example.com", 2022, "testuser", resolved);
     }
 
+    @Test
+    void substituteHostname() throws Exception {
+        HostConfigEntry entry = new HostConfigEntry("foo*", "b%%ar%h.org", -1, 
"test");
+        HostConfigEntry resolved = 
HostConfigEntry.toHostConfigEntryResolver(Collections.singleton(entry))
+                .resolveEffectiveHost("fooby", 0, null, "", null, null);
+        expect("b%arfooby.org", 22, "test", resolved);
+    }
+
     @Test
     void defaults() throws Exception {
         HostConfigEntry entry = new HostConfigEntry("foo*", "bar.example.com", 
22, "test");

Reply via email to