Skip to content

Commit a4ef666

Browse files
committed
continue default shell changes
1 parent 709bece commit a4ef666

File tree

9 files changed

+1107
-46
lines changed

9 files changed

+1107
-46
lines changed

sshdconfig/Cargo.lock

Lines changed: 893 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sshdconfig/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ lto = true
1616
[dependencies]
1717
atty = { version = "0.2" }
1818
chrono = { version = "0.4" }
19+
clap = { version = "4.5", features = ["derive"] }
20+
registry_lib = { path = "../registry_lib" }
1921
schemars = "0.9"
2022
serde = { version = "1.0", features = ["derive"] }
2123
serde_json = { version = "1.0", features = ["preserve_order"] }

sshdconfig/src/args.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use clap::{Parser, Subcommand};
5+
6+
use crate::metadata::RepeatableKeyword;
7+
8+
#[derive(Parser)]
9+
pub struct Args {
10+
#[clap(subcommand)]
11+
pub command: Command,
12+
}
13+
14+
#[derive(Subcommand)]
15+
pub enum Command {
16+
/// Export sshd_config
17+
Export,
18+
/// Set a value in sshd_config (not yet implemented)
19+
Set {
20+
/// The input to set
21+
#[clap(short = 'i', long, help = "input to set in sshd_config")]
22+
input: Option<String>,
23+
#[clap(subcommand, help = "set commands")]
24+
subcommand: Option<SetCommand>,
25+
},
26+
}
27+
28+
#[derive(Clone, Debug, Eq, PartialEq, Subcommand)]
29+
pub enum SetCommand {
30+
/// Set the default shell
31+
DefaultShell {
32+
/// The path to the shell executable
33+
#[clap(short = 's', long, help = "path to the shell executable")]
34+
shell: String,
35+
/// Additional command options
36+
///
37+
#[clap(short = 'c', long, help = "additional command options", default_value = "-c")]
38+
cmd_option: Option<String>,
39+
#[clap(short = 'e', long, help = "skip escaping arguments", default_value = "false")]
40+
escape_arguments: bool,
41+
#[clap(short = 'a', long, help = "additional shell arguments")]
42+
shell_arguments: Option<Vec<String>>,
43+
},
44+
/// Set repeatable keywords
45+
RepeatableKeyworld {
46+
keyword: RepeatableKeyword,
47+
name: String,
48+
value: Option<String>,
49+
}
50+
}

sshdconfig/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ use thiserror::Error;
77
pub enum SshdConfigError {
88
#[error("Command: {0}")]
99
CommandError(String),
10+
#[error("Invalid Input: {0}")]
11+
IoError(String),
12+
#[error("IO: {0}")]
13+
InvalidInput(String),
1014
#[error("JSON: {0}")]
1115
Json(#[from] serde_json::Error),
1216
#[error("Language: {0}")]
@@ -15,4 +19,6 @@ pub enum SshdConfigError {
1519
ParserError(String),
1620
#[error("Parser Int: {0}")]
1721
ParseIntError(#[from] std::num::ParseIntError),
22+
#[error("Registry: {0}")]
23+
RegistryError(#[from] registry_lib::error::RegistryError),
1824
}

sshdconfig/src/main.rs

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,41 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
use schemars::schema_for;
5-
use serde_json::to_string_pretty;
6-
use std::{env::args, process::exit};
7-
8-
use crate::export::invoke_export;
9-
use crate::parser::SshdConfigParser;
4+
use args::{Args, Command};
5+
use clap::{Parser};
6+
use export::invoke_export;
7+
use set::invoke_set;
108

9+
mod args;
1110
mod error;
1211
mod export;
1312
mod metadata;
1413
mod parser;
14+
mod set;
1515
mod util;
1616

1717
fn main() {
18-
19-
// TODO: add support for other commands and use clap for argument parsing
20-
let args: Vec<String> = args().collect();
21-
22-
if args.len() != 2 || (args[1] != "export" && args[1] != "schema") {
23-
eprintln!("Usage: {} <export|schema>", args[0]);
24-
exit(1);
25-
}
26-
27-
if args[1] == "schema" {
28-
// for dsc tests on linux/mac
29-
let schema = schema_for!(SshdConfigParser);
30-
println!("{}", to_string_pretty(&schema).unwrap());
31-
return;
32-
}
33-
34-
// only supports export for sshdconfig for now
35-
match invoke_export() {
36-
Ok(result) => {
37-
println!("{result}");
18+
let args = Args::parse();
19+
match &args.command {
20+
Command::Export => {
21+
match invoke_export() {
22+
Ok(result) => {
23+
println!("{result}");
24+
}
25+
Err(e) => {
26+
eprintln!("Error exporting sshd_config: {e:?}");
27+
}
28+
}
3829
}
39-
Err(e) => {
40-
eprintln!("Error exporting SSHD config: {e:?}");
30+
Command::Set { input , subcommand} => {
31+
match invoke_set(input, subcommand) {
32+
Ok(()) => {
33+
println!("success");
34+
}
35+
Err(e) => {
36+
eprintln!("Error setting sshd_config: {e:?}");
37+
}
38+
}
4139
}
4240
}
4341
}

sshdconfig/src/metadata.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
use clap::ValueEnum;
5+
46
// TODO: ensure lists are complete
57

68
// keywords that can be repeated over multiple lines and should be represented as arrays
@@ -13,6 +15,16 @@ pub const REPEATABLE_KEYWORDS: [&str; 6] = [
1315
"subsystem"
1416
];
1517

18+
#[derive(Clone, Debug, Eq, PartialEq, ValueEnum)]
19+
pub enum RepeatableKeyword {
20+
HostKey,
21+
Include,
22+
ListenAddress,
23+
Port,
24+
SetEnv,
25+
Subsystem,
26+
}
27+
1628
// keywords that can have multiple argments per line and should be represented as arrays
1729
// but cannot be repeated over multiple lines, as subsequent entries are ignored
1830
pub const MULTI_ARG_KEYWORDS: [&str; 7] = [

sshdconfig/src/set.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use registry_lib::{config::{Registry, RegistryValueData}, RegistryHelper};
5+
use serde_json::{json, Value};
6+
use std::{path::Path, process::Command};
7+
8+
use crate::args::SetCommand;
9+
use crate::error::SshdConfigError;
10+
use crate::metadata::RepeatableKeyword;
11+
12+
/// Invoke the set command.
13+
///
14+
/// # Errors
15+
///
16+
/// This function will return an error if the sshd_config is not updated with the desired settings.
17+
pub fn invoke_set(input: &Option<String>, subcommand: &Option<SetCommand>) -> Result<(), SshdConfigError> {
18+
if let Some(subcommand) = subcommand {
19+
match subcommand {
20+
SetCommand::DefaultShell { shell, cmd_option, escape_arguments, shell_arguments } => {
21+
set_default_shell(shell, cmd_option, escape_arguments, shell_arguments)
22+
}
23+
SetCommand::RepeatableKeyworld { keyword, name, value } => {
24+
set_repeatable_keyword(keyword, name, value)
25+
}
26+
}
27+
} else {
28+
set_regular_keywords(input)
29+
}
30+
}
31+
32+
fn set_default_shell(shell: &String, cmd_option: &Option<String>, escape_arguments: &bool, shell_arguments: &Option<Vec<String>>) -> Result<(), SshdConfigError> {
33+
let shell_path = Path::new(shell);
34+
if shell_path.is_relative() {
35+
if shell_path.components().any(|c| c == std::path::Component::ParentDir) {
36+
return Err(SshdConfigError::InvalidInput("shell path must not be relative".to_string()));
37+
}
38+
}
39+
// TODO check if binary exists when it is not an absolute path
40+
if !shell_path.exists() {
41+
return Err(SshdConfigError::InvalidInput(format!("shell path does not exist: {}", shell)));
42+
}
43+
44+
let mut shell_data = shell.clone();
45+
if let Some(shell_args) = shell_arguments {
46+
let args_str = shell_args.join(" ");
47+
shell_data = format!("{} {}", shell, args_str);
48+
}
49+
50+
set_registry_default_shell("DefaultShell", RegistryValueData::String(shell_data))?;
51+
52+
if let Some(cmd_option) = cmd_option {
53+
set_registry_default_shell("DefaultShellCommandOption", RegistryValueData::String(cmd_option.clone()))?;
54+
}
55+
else {
56+
return Err(SshdConfigError::InvalidInput("cmd_option is required".to_string()));
57+
}
58+
59+
let mut escape_data = 0;
60+
if *escape_arguments {
61+
escape_data = 1;
62+
}
63+
set_registry_default_shell("DefaultShellEscapeArguments ", RegistryValueData::DWord(escape_data))?;
64+
65+
Ok(())
66+
}
67+
68+
pub fn set_repeatable_keyword(_keyword: &RepeatableKeyword, _name: &String, _value: &Option<String>) -> Result<(), SshdConfigError> {
69+
// TODO: handle repeat keywords like subsystem
70+
Ok(())
71+
}
72+
73+
pub fn set_regular_keywords(_input: &Option<String>) -> Result<(), SshdConfigError> {
74+
// modify or add all values in the input file
75+
// have a banner that we are managing the file and check that before modifying
76+
// if the file is managed by this tool, we can modify it
77+
// if the file is not managed by the tool, we should back it up
78+
// get existing sshd_config settings from sshd -T
79+
// get default sshd_config settings from sshd -T
80+
// save explicit settings
81+
// for each of the input keys, update it with the new value
82+
// or insert it if it doesn't exist (above any match statements)
83+
// write the updated settings back to the sshd_config file
84+
// ensure sshd -T is valid after the update?
85+
Ok(())
86+
}
87+
88+
fn set_registry_default_shell(name: &str, data: RegistryValueData) -> Result<(), SshdConfigError> {
89+
let registry_helper = RegistryHelper::new("HKLM\\SOFTWARE\\OpenSSH", Some(name.to_string()), Some(data))?;
90+
registry_helper.set()?;
91+
Ok(())
92+
}

sshdconfig/sshd.dsc.resource.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json",
3+
"type": "Microsoft.Windows.OpenSSH/SSHD",
4+
"description": "Manage SSH Server Configuration Global Settings",
5+
"version": "0.1.0",
6+
"get": {
7+
"executable": "sshdconfig",
8+
"args": [
9+
"get"
10+
]
11+
},
12+
"set": {
13+
"executable": "sshdconfig",
14+
"args": [
15+
"set"
16+
]
17+
},
18+
"schema": {
19+
"command": {
20+
"executable": "sshdconfig",
21+
"args": [
22+
"schema"
23+
]
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)