ssh config: do environment variable replacement

OpenSSH 8.4 has introduced simple environment variable substitution
for some keys. Implement that feature in our ssh config file parser,
too.

Bug: 572103
Change-Id: I360f2c5510eea4ec3329aeedf3d29dfefc9163f0
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
This commit is contained in:
Thomas Wolf 2021-03-20 18:46:13 +01:00
parent 8edde18c8c
commit 1de2a9fbe7
2 changed files with 76 additions and 29 deletions

View File

@ -58,6 +58,7 @@ public void setUp() throws Exception {
FileUtils.mkdir(configFile.getParentFile()); FileUtils.mkdir(configFile.getParentFile());
mockSystemReader.setProperty(Constants.OS_USER_NAME_KEY, "jex_junit"); mockSystemReader.setProperty(Constants.OS_USER_NAME_KEY, "jex_junit");
mockSystemReader.setProperty("TST_VAR", "TEST");
osc = new OpenSshConfig(home, configFile); osc = new OpenSshConfig(home, configFile);
} }
@ -497,4 +498,23 @@ public void testEolComments() throws Exception {
assertEquals("^ssh-rsa", assertEquals("^ssh-rsa",
c.getValue(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS)); c.getValue(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS));
} }
@Test
public void testEnVarSubstitution() throws Exception {
config("Host orcz\nIdentityFile /tmp/${TST_VAR}\n"
+ "CertificateFile /tmp/${}/foo\nUser ${TST_VAR}\nIdentityAgent /tmp/${TST_VAR/bar");
Host h = osc.lookup("orcz");
assertNotNull(h);
Config c = h.getConfig();
assertEquals("/tmp/TEST",
c.getValue(SshConstants.IDENTITY_FILE));
// No variable name
assertEquals("/tmp/${}/foo", c.getValue(SshConstants.CERTIFICATE_FILE));
// User doesn't get env var substitution:
assertEquals("${TST_VAR}", c.getValue(SshConstants.USER));
assertEquals("${TST_VAR}", h.getUser());
// Unterminated:
assertEquals("/tmp/${TST_VAR/bar",
c.getValue(SshConstants.IDENTITY_AGENT));
}
} }

View File

@ -708,10 +708,10 @@ void merge(HostEntry entry) {
} }
private List<String> substitute(List<String> values, String allowed, private List<String> substitute(List<String> values, String allowed,
Replacer r) { Replacer r, boolean withEnv) {
List<String> result = new ArrayList<>(values.size()); List<String> result = new ArrayList<>(values.size());
for (String value : values) { for (String value : values) {
result.add(r.substitute(value, allowed)); result.add(r.substitute(value, allowed, withEnv));
} }
return result; return result;
} }
@ -743,7 +743,7 @@ void substitute(String originalHostName, int port, String userName,
if (hostName == null || hostName.isEmpty()) { if (hostName == null || hostName.isEmpty()) {
options.put(SshConstants.HOST_NAME, originalHostName); options.put(SshConstants.HOST_NAME, originalHostName);
} else { } else {
hostName = r.substitute(hostName, "h"); //$NON-NLS-1$ hostName = r.substitute(hostName, "h", false); //$NON-NLS-1$
options.put(SshConstants.HOST_NAME, hostName); options.put(SshConstants.HOST_NAME, hostName);
r.update('h', hostName); r.update('h', hostName);
} }
@ -752,13 +752,13 @@ void substitute(String originalHostName, int port, String userName,
List<String> values = multiOptions List<String> values = multiOptions
.get(SshConstants.IDENTITY_FILE); .get(SshConstants.IDENTITY_FILE);
if (values != null) { if (values != null) {
values = substitute(values, "dhlru", r); //$NON-NLS-1$ values = substitute(values, "dhlru", r, true); //$NON-NLS-1$
values = replaceTilde(values, home); values = replaceTilde(values, home);
multiOptions.put(SshConstants.IDENTITY_FILE, values); multiOptions.put(SshConstants.IDENTITY_FILE, values);
} }
values = multiOptions.get(SshConstants.CERTIFICATE_FILE); values = multiOptions.get(SshConstants.CERTIFICATE_FILE);
if (values != null) { if (values != null) {
values = substitute(values, "dhlru", r); //$NON-NLS-1$ values = substitute(values, "dhlru", r, true); //$NON-NLS-1$
values = replaceTilde(values, home); values = replaceTilde(values, home);
multiOptions.put(SshConstants.CERTIFICATE_FILE, values); multiOptions.put(SshConstants.CERTIFICATE_FILE, values);
} }
@ -775,29 +775,29 @@ void substitute(String originalHostName, int port, String userName,
// HOSTNAME already done above // HOSTNAME already done above
String value = options.get(SshConstants.IDENTITY_AGENT); String value = options.get(SshConstants.IDENTITY_AGENT);
if (value != null) { if (value != null) {
value = r.substitute(value, "dhlru"); //$NON-NLS-1$ value = r.substitute(value, "dhlru", true); //$NON-NLS-1$
value = toFile(value, home).getPath(); value = toFile(value, home).getPath();
options.put(SshConstants.IDENTITY_AGENT, value); options.put(SshConstants.IDENTITY_AGENT, value);
} }
value = options.get(SshConstants.CONTROL_PATH); value = options.get(SshConstants.CONTROL_PATH);
if (value != null) { if (value != null) {
value = r.substitute(value, "ChLlnpru"); //$NON-NLS-1$ value = r.substitute(value, "ChLlnpru", true); //$NON-NLS-1$
value = toFile(value, home).getPath(); value = toFile(value, home).getPath();
options.put(SshConstants.CONTROL_PATH, value); options.put(SshConstants.CONTROL_PATH, value);
} }
value = options.get(SshConstants.LOCAL_COMMAND); value = options.get(SshConstants.LOCAL_COMMAND);
if (value != null) { if (value != null) {
value = r.substitute(value, "CdhlnprTu"); //$NON-NLS-1$ value = r.substitute(value, "CdhlnprTu", false); //$NON-NLS-1$
options.put(SshConstants.LOCAL_COMMAND, value); options.put(SshConstants.LOCAL_COMMAND, value);
} }
value = options.get(SshConstants.REMOTE_COMMAND); value = options.get(SshConstants.REMOTE_COMMAND);
if (value != null) { if (value != null) {
value = r.substitute(value, "Cdhlnpru"); //$NON-NLS-1$ value = r.substitute(value, "Cdhlnpru", false); //$NON-NLS-1$
options.put(SshConstants.REMOTE_COMMAND, value); options.put(SshConstants.REMOTE_COMMAND, value);
} }
value = options.get(SshConstants.PROXY_COMMAND); value = options.get(SshConstants.PROXY_COMMAND);
if (value != null) { if (value != null) {
value = r.substitute(value, "hpr"); //$NON-NLS-1$ value = r.substitute(value, "hpr", false); //$NON-NLS-1$
options.put(SshConstants.PROXY_COMMAND, value); options.put(SshConstants.PROXY_COMMAND, value);
} }
} }
@ -871,7 +871,7 @@ public Replacer(String host, int port, String user,
replacements.put(Character.valueOf('r'), user == null ? "" : user); //$NON-NLS-1$ replacements.put(Character.valueOf('r'), user == null ? "" : user); //$NON-NLS-1$
replacements.put(Character.valueOf('u'), localUserName); replacements.put(Character.valueOf('u'), localUserName);
replacements.put(Character.valueOf('C'), replacements.put(Character.valueOf('C'),
substitute("%l%h%p%r", "hlpr")); //$NON-NLS-1$ //$NON-NLS-2$ substitute("%l%h%p%r", "hlpr", false)); //$NON-NLS-1$ //$NON-NLS-2$
replacements.put(Character.valueOf('T'), "NONE"); //$NON-NLS-1$ replacements.put(Character.valueOf('T'), "NONE"); //$NON-NLS-1$
} }
@ -879,36 +879,63 @@ public void update(char key, String value) {
replacements.put(Character.valueOf(key), value); replacements.put(Character.valueOf(key), value);
if ("lhpr".indexOf(key) >= 0) { //$NON-NLS-1$ if ("lhpr".indexOf(key) >= 0) { //$NON-NLS-1$
replacements.put(Character.valueOf('C'), replacements.put(Character.valueOf('C'),
substitute("%l%h%p%r", "hlpr")); //$NON-NLS-1$ //$NON-NLS-2$ substitute("%l%h%p%r", "hlpr", false)); //$NON-NLS-1$ //$NON-NLS-2$
} }
} }
public String substitute(String input, String allowed) { public String substitute(String input, String allowed,
boolean withEnv) {
if (input == null || input.length() <= 1 if (input == null || input.length() <= 1
|| input.indexOf('%') < 0) { || input.indexOf('%') < 0
&& (!withEnv || input.indexOf("${") < 0)) { //$NON-NLS-1$
return input; return input;
} }
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
int start = 0; int start = 0;
int length = input.length(); int length = input.length();
while (start < length) { while (start < length) {
int percent = input.indexOf('%', start); char ch = input.charAt(start);
if (percent < 0 || percent + 1 >= length) { switch (ch) {
builder.append(input.substring(start)); case '%':
if (start + 1 >= length) {
break;
}
String replacement = null;
ch = input.charAt(start + 1);
if (ch == '%' || allowed.indexOf(ch) >= 0) {
replacement = replacements.get(Character.valueOf(ch));
}
if (replacement == null) {
builder.append('%').append(ch);
} else {
builder.append(replacement);
}
start += 2;
continue;
case '$':
if (!withEnv || start + 2 >= length) {
break;
}
ch = input.charAt(start + 1);
if (ch == '{') {
int close = input.indexOf('}', start + 2);
if (close > start + 2) {
String variable = SystemReader.getInstance()
.getenv(input.substring(start + 2, close));
if (!StringUtils.isEmptyOrNull(variable)) {
builder.append(variable);
}
start = close + 1;
continue;
}
}
ch = '$';
break;
default:
break; break;
} }
String replacement = null; builder.append(ch);
char ch = input.charAt(percent + 1); start++;
if (ch == '%' || allowed.indexOf(ch) >= 0) {
replacement = replacements.get(Character.valueOf(ch));
}
if (replacement == null) {
builder.append(input.substring(start, percent + 2));
} else {
builder.append(input.substring(start, percent))
.append(replacement);
}
start = percent + 2;
} }
return builder.toString(); return builder.toString();
} }