Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions resources/sshdconfig/src/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::canonical_properties::CanonicalProperty;
use crate::error::SshdConfigError;
use crate::inputs::{CommandInfo, SSHD_CONFIG_FILEPATH};
use crate::parser::parse_text_to_map;
use crate::repeat_keyword::MULTI_ARG_KEYWORDS_SPACE_SEP;
use crate::util::{
build_command_info,
extract_sshd_defaults,
Expand Down Expand Up @@ -133,6 +134,12 @@ pub fn get_sshd_settings(cmd_info: &CommandInfo, is_get: bool) -> Result<Map<Str
result.insert("match".to_string(), match_value.clone());
}

// sshd -T normalizes space-separated list keywords by stripping the quotes that preserve
// values containing spaces (e.g. Windows group names like "openssh users"), splitting them
// into separate entries. Prefer the value parsed directly from the config file, which retains
// the quoting, for these keywords when they are explicitly set.
prefer_explicit_space_sep_lists(&mut result, &explicit_settings);

if cmd_info.include_defaults {
// get default from SSHD -T with empty config
let mut defaults = extract_sshd_defaults()?;
Expand Down Expand Up @@ -171,3 +178,65 @@ pub fn get_sshd_settings(cmd_info: &CommandInfo, is_get: bool) -> Result<Map<Str
}
Ok(result)
}

fn prefer_explicit_space_sep_lists(result: &mut Map<String, Value>, explicit_settings: &Map<String, Value>) {
for keyword in MULTI_ARG_KEYWORDS_SPACE_SEP {
if let Some(value) = explicit_settings.get(keyword) {
result.insert((*keyword).to_string(), value.clone());
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;

#[test]
fn overrides_split_group_list_with_quoted_file_value() {
// sshd -T stripped the quotes and split "openssh users" into two entries.
let mut result = Map::new();
result.insert("allowgroups".to_string(), json!(["administrators", "openssh", "users"]));

// The raw config file parse preserved the quoted grouping.
let mut explicit_settings = Map::new();
explicit_settings.insert("allowgroups".to_string(), json!(["administrators", "openssh users"]));

prefer_explicit_space_sep_lists(&mut result, &explicit_settings);

assert_eq!(
result.get("allowgroups").unwrap(),
&json!(["administrators", "openssh users"])
);
}

#[test]
fn leaves_keyword_absent_from_file_untouched() {
// allowgroups is present in sshd -T output but not explicitly set in the config file.
let mut result = Map::new();
result.insert("allowgroups".to_string(), json!(["administrators", "openssh", "users"]));

let explicit_settings = Map::new();

prefer_explicit_space_sep_lists(&mut result, &explicit_settings);

assert_eq!(
result.get("allowgroups").unwrap(),
&json!(["administrators", "openssh", "users"])
);
}

#[test]
fn leaves_non_space_sep_keyword_untouched() {
// port is not a space-separated list keyword and must not be overridden.
let mut result = Map::new();
result.insert("port".to_string(), json!([22]));

let mut explicit_settings = Map::new();
explicit_settings.insert("port".to_string(), json!([2222]));

prefer_explicit_space_sep_lists(&mut result, &explicit_settings);

assert_eq!(result.get("port").unwrap(), &json!([22]));
}
}
32 changes: 32 additions & 0 deletions resources/sshdconfig/tests/sshdconfig.get.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ PasswordAuthentication no
"@
$TestConfigPathWithInclude = Join-Path $TestDrive 'test_sshd_config_include'
$configWithInclude | Set-Content -Path $TestConfigPathWithInclude
$configWithQuotedGroups = @"
PasswordAuthentication no
AllowGroups administrators "openssh users"
"@
$TestConfigPathWithQuotedGroups = Join-Path $TestDrive 'test_sshd_config_quoted_groups'
$configWithQuotedGroups | Set-Content -Path $TestConfigPathWithQuotedGroups
}

AfterAll {
Expand All @@ -53,6 +59,9 @@ PasswordAuthentication no
if (Test-Path $TestConfigPathWithInclude) {
Remove-Item -Path $TestConfigPathWithInclude -Force -ErrorAction SilentlyContinue
}
if (Test-Path $TestConfigPathWithQuotedGroups) {
Remove-Item -Path $TestConfigPathWithQuotedGroups -Force -ErrorAction SilentlyContinue
}
}

It '<Command> command <Description>' -TestCases @(
Expand Down Expand Up @@ -142,6 +151,29 @@ PasswordAuthentication no
Remove-Item -Path $stderrFile -Force -ErrorAction SilentlyContinue
}

It '<Command> command preserves quoted group names containing spaces' -TestCases @(
@{ Command = 'get' }
@{ Command = 'export' }
) {
param($Command)

$inputData = @{
sshd_config_filepath = $TestConfigPathWithQuotedGroups
} | ConvertTo-Json

if ($Command -eq 'get') {
$result = sshdconfig $Command --input $inputData -s sshd-config 2>$null | ConvertFrom-Json
}
else {
$result = sshdconfig $Command --input $inputData 2>$null | ConvertFrom-Json
}

# "openssh users" must remain a single entry rather than being split into "openssh" and "users".
$result.AllowGroups.Count | Should -Be 2
$result.AllowGroups[0] | Should -Be "administrators"
$result.AllowGroups[1] | Should -Be "openssh users"
}

It 'Should fail without creating target config when file does not exist' {
$nonExistentPath = Join-Path $TestDrive 'nonexistent_sshd_config'

Expand Down
Loading