@@ -10,30 +10,53 @@ use {
1010
1111use rust_i18n:: t;
1212use serde_json:: { Map , Value } ;
13+ use std:: { fmt:: Write , string:: String } ;
14+ use tracing:: { debug, info, warn} ;
1315
14- use crate :: args:: DefaultShell ;
16+ use crate :: args:: { DefaultShell , Setting } ;
1517use crate :: error:: SshdConfigError ;
18+ use crate :: inputs:: { CommandInfo , SshdCommandArgs } ;
19+ use crate :: metadata:: { SSHD_CONFIG_HEADER , SSHD_CONFIG_HEADER_VERSION , SSHD_CONFIG_HEADER_WARNING } ;
20+ use crate :: util:: { build_command_info, get_default_sshd_config_path, invoke_sshd_config_validation} ;
1621
1722/// Invoke the set command.
1823///
1924/// # Errors
2025///
2126/// This function will return an error if the desired settings cannot be applied.
22- pub fn invoke_set ( input : & str ) -> Result < Map < String , Value > , SshdConfigError > {
23- match serde_json:: from_str :: < DefaultShell > ( input) {
24- Ok ( default_shell) => {
25- set_default_shell ( default_shell. shell , default_shell. cmd_option , default_shell. escape_arguments ) ?;
26- Ok ( Map :: new ( ) )
27+ pub fn invoke_set ( input : & str , setting : & Setting ) -> Result < Map < String , Value > , SshdConfigError > {
28+ match setting {
29+ Setting :: SshdConfig => {
30+ debug ! ( "{} {:?}" , t!( "set.settingSshdConfig" ) . to_string( ) , setting) ;
31+ let cmd_info = build_command_info ( Some ( & input. to_string ( ) ) , false ) ?;
32+ match set_sshd_config ( & cmd_info) {
33+ Ok ( ( ) ) => Ok ( Map :: new ( ) ) ,
34+ Err ( e) => Err ( e) ,
35+ }
2736 } ,
28- Err ( e) => {
29- Err ( SshdConfigError :: InvalidInput ( t ! ( "set.failedToParseInput" , error = e) . to_string ( ) ) )
37+ Setting :: WindowsGlobal => {
38+ debug ! ( "{} {:?}" , t!( "set.settingDefaultShell" ) . to_string( ) , setting) ;
39+ match serde_json:: from_str :: < DefaultShell > ( input) {
40+ Ok ( default_shell) => {
41+ debug ! ( "{}" , t!( "set.defaultShellDebug" , shell = format!( "{:?}" , default_shell) ) ) ;
42+ // if default_shell.shell is Some, we should pass that into set default shell
43+ // otherwise pass in an empty string
44+ let shell: String = default_shell. shell . clone ( ) . unwrap_or_default ( ) ;
45+ set_default_shell ( shell, default_shell. cmd_option , default_shell. escape_arguments ) ?;
46+ Ok ( Map :: new ( ) )
47+ } ,
48+ Err ( e) => Err ( SshdConfigError :: InvalidInput ( t ! ( "set.failedToParseDefaultShell" , error = e) . to_string ( ) ) ) ,
49+ }
3050 }
3151 }
3252}
3353
3454#[ cfg( windows) ]
35- fn set_default_shell ( shell : Option < String > , cmd_option : Option < String > , escape_arguments : Option < bool > ) -> Result < ( ) , SshdConfigError > {
36- if let Some ( shell) = shell {
55+ fn set_default_shell ( shell : String , cmd_option : Option < String > , escape_arguments : Option < bool > ) -> Result < ( ) , SshdConfigError > {
56+ debug ! ( "{}" , t!( "set.settingDefaultShell" ) ) ;
57+ if shell. is_empty ( ) {
58+ remove_registry ( DEFAULT_SHELL ) ?;
59+ } else {
3760 // TODO: if shell contains quotes, we need to remove them
3861 let shell_path = Path :: new ( & shell) ;
3962 if shell_path. is_relative ( ) && shell_path. components ( ) . any ( |c| c == std:: path:: Component :: ParentDir ) {
@@ -42,13 +65,9 @@ fn set_default_shell(shell: Option<String>, cmd_option: Option<String>, escape_a
4265 if !shell_path. exists ( ) {
4366 return Err ( SshdConfigError :: InvalidInput ( t ! ( "set.shellPathDoesNotExist" , shell = shell) . to_string ( ) ) ) ;
4467 }
45-
4668 set_registry ( DEFAULT_SHELL , RegistryValueData :: String ( shell) ) ?;
47- } else {
48- remove_registry ( DEFAULT_SHELL ) ?;
4969 }
5070
51-
5271 if let Some ( cmd_option) = cmd_option {
5372 set_registry ( DEFAULT_SHELL_CMD_OPTION , RegistryValueData :: String ( cmd_option. clone ( ) ) ) ?;
5473 } else {
@@ -69,7 +88,7 @@ fn set_default_shell(shell: Option<String>, cmd_option: Option<String>, escape_a
6988}
7089
7190#[ cfg( not( windows) ) ]
72- fn set_default_shell ( _shell : Option < String > , _cmd_option : Option < String > , _escape_arguments : Option < bool > ) -> Result < ( ) , SshdConfigError > {
91+ fn set_default_shell ( _shell : String , _cmd_option : Option < String > , _escape_arguments : Option < bool > ) -> Result < ( ) , SshdConfigError > {
7392 Err ( SshdConfigError :: InvalidInput ( t ! ( "get.windowsOnly" ) . to_string ( ) ) )
7493}
7594
@@ -86,3 +105,79 @@ fn remove_registry(name: &str) -> Result<(), SshdConfigError> {
86105 registry_helper. remove ( ) ?;
87106 Ok ( ( ) )
88107}
108+
109+ fn set_sshd_config ( cmd_info : & CommandInfo ) -> Result < ( ) , SshdConfigError > {
110+ // this should be its own helper function that checks that the value makes sense for the key type
111+ // i.e. if the key can be repeated or have multiple values, etc.
112+ // or if the value is something besides a string (like an object to convert back into a comma-separated list)
113+ debug ! ( "{}" , t!( "set.writingTempConfig" ) ) ;
114+ let mut config_text = SSHD_CONFIG_HEADER . to_string ( ) + "\n " + SSHD_CONFIG_HEADER_VERSION + "\n " + SSHD_CONFIG_HEADER_WARNING + "\n " ;
115+ if cmd_info. clobber {
116+ for ( key, value) in & cmd_info. input {
117+ if let Some ( value_str) = value. as_str ( ) {
118+ writeln ! ( & mut config_text, "{key} {value_str}" ) ?;
119+ } else {
120+ return Err ( SshdConfigError :: InvalidInput ( t ! ( "set.valueMustBeString" , key = key) . to_string ( ) ) ) ;
121+ }
122+ }
123+ } else {
124+ /* TODO: preserve existing settings that are not in input, probably need to call get */
125+ return Err ( SshdConfigError :: InvalidInput ( t ! ( "set.clobberFalseUnsupported" ) . to_string ( ) ) ) ;
126+ }
127+
128+ // Write input to a temporary file and validate it with SSHD -T
129+ let temp_file = tempfile:: Builder :: new ( )
130+ . prefix ( "sshd_config_temp_" )
131+ . suffix ( ".tmp" )
132+ . tempfile ( ) ?;
133+ let temp_path = temp_file. path ( ) . to_path_buf ( ) ;
134+ let ( file, path) = temp_file. keep ( ) ?;
135+ debug ! ( "{}" , t!( "set.tempFileCreated" , path = temp_path. display( ) ) ) ;
136+ std:: fs:: write ( & temp_path, & config_text)
137+ . map_err ( |e| SshdConfigError :: CommandError ( e. to_string ( ) ) ) ?;
138+ drop ( file) ;
139+
140+ let args = Some (
141+ SshdCommandArgs {
142+ filepath : Some ( temp_path) ,
143+ additional_args : None ,
144+ }
145+ ) ;
146+
147+ debug ! ( "{}" , t!( "set.validatingTempConfig" ) ) ;
148+ let result = invoke_sshd_config_validation ( args) ;
149+ // Always cleanup temp file, regardless of result success or failure
150+ if let Err ( e) = std:: fs:: remove_file ( & path) {
151+ warn ! ( "{}" , t!( "set.cleanupFailed" , path = path. display( ) , error = e) ) ;
152+ }
153+ // Propagate failure, if any
154+ result?;
155+
156+ let sshd_config_path = get_default_sshd_config_path ( cmd_info. metadata . filepath . clone ( ) ) ?;
157+
158+ if sshd_config_path. exists ( ) {
159+ let mut sshd_config_content = String :: new ( ) ;
160+ if let Ok ( mut file) = std:: fs:: OpenOptions :: new ( ) . read ( true ) . open ( & sshd_config_path) {
161+ use std:: io:: Read ;
162+ file. read_to_string ( & mut sshd_config_content)
163+ . map_err ( |e| SshdConfigError :: CommandError ( e. to_string ( ) ) ) ?;
164+ } else {
165+ return Err ( SshdConfigError :: CommandError ( t ! ( "set.sshdConfigReadFailed" , path = sshd_config_path. display( ) ) . to_string ( ) ) ) ;
166+ }
167+ if !sshd_config_content. starts_with ( SSHD_CONFIG_HEADER ) {
168+ // If config file is not already managed by this resource, create a backup of the existing file
169+ debug ! ( "{}" , t!( "set.backingUpConfig" ) ) ;
170+ let backup_path = format ! ( "{}_backup" , sshd_config_path. display( ) ) ;
171+ std:: fs:: write ( & backup_path, & sshd_config_content)
172+ . map_err ( |e| SshdConfigError :: CommandError ( e. to_string ( ) ) ) ?;
173+ info ! ( "{}" , t!( "set.backupCreated" , path = backup_path) ) ;
174+ }
175+ } else {
176+ debug ! ( "{}" , t!( "set.configDoesNotExist" ) ) ;
177+ }
178+
179+ std:: fs:: write ( & sshd_config_path, & config_text)
180+ . map_err ( |e| SshdConfigError :: CommandError ( e. to_string ( ) ) ) ?;
181+
182+ Ok ( ( ) )
183+ }
0 commit comments