From fa3a2a83d247d5b12c3b519e2df6800c68a9c1c1 Mon Sep 17 00:00:00 2001 From: zonyitoo Date: Thu, 25 Nov 2021 15:14:07 +0800 Subject: [PATCH] default configuration file (config.json) for Windows, Linux, macOS - $XDG_CONFIG_PATH/shadowsocks-rust/config.json - $HOME/.config/shadowsocks-rust/config.json - {FOLDERID_RoamingAppData}\shadowsocks\shadowsocks-rust\config\config.json - $HOME/Library/Application Support/org.shadowsocks.shadowsocks-rust/config.json fixes #688 --- Cargo.lock | 58 +++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 2 ++ README.md | 2 ++ bin/common/config.rs | 41 +++++++++++++++++++++++++++++++ bin/common/mod.rs | 1 + bin/sslocal.rs | 16 +++++++++--- bin/ssmanager.rs | 18 ++++++++++---- bin/ssserver.rs | 18 ++++++++++---- 8 files changed, 138 insertions(+), 18 deletions(-) create mode 100644 bin/common/config.rs diff --git a/Cargo.lock b/Cargo.lock index 516e137f..1a2e5a20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -299,6 +299,35 @@ dependencies = [ "generic-array", ] +[[package]] +name = "directories" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dtoa" version = "0.4.8" @@ -1291,6 +1320,16 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall 0.2.10", +] + [[package]] name = "regex" version = "1.5.4" @@ -1524,9 +1563,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19" +checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" dependencies = [ "itoa", "ryu", @@ -1624,6 +1663,7 @@ dependencies = [ "cfg-if", "clap", "daemonize", + "directories", "env_logger", "exitcode", "futures", @@ -1638,6 +1678,7 @@ dependencies = [ "snmalloc-rs", "tcmalloc", "tokio", + "xdg", ] [[package]] @@ -1774,9 +1815,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" dependencies = [ "proc-macro2", "quote", @@ -2438,6 +2479,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "xdg" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a23fe958c70412687039c86f578938b4a0bb50ec788e96bce4d6ab00ddd5803" +dependencies = [ + "dirs", +] + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index 854534fa..34907126 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,6 +128,8 @@ cfg-if = "1" qrcode = { version = "0.12", default-features = false } exitcode = "1" build-time = "0.1" +directories = "4.0" +xdg = "2.4" futures = "0.3" tokio = { version = "1", features = ["rt", "signal"] } diff --git a/README.md b/README.md index d1be183e..7e46c07e 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ Related Projects: * `local-dns` - Allow using dns protocol for `sslocal`, serves as a DNS server proxying queries to local or remote DNS servers by ACL rules +* `local-tun` - [TUN](https://en.wikipedia.org/wiki/TUN/TAP) interface support for `sslocal` + * `stream-cipher` - Enable deprecated stream ciphers. WARN: stream ciphers are UNSAFE! * `aead-cipher-extra` - Enable non-standard AEAD ciphers diff --git a/bin/common/config.rs b/bin/common/config.rs new file mode 100644 index 00000000..75210c7c --- /dev/null +++ b/bin/common/config.rs @@ -0,0 +1,41 @@ +use directories::ProjectDirs; +use std::path::{Path, PathBuf}; + +/// Default configuration file path +pub fn get_default_config_path() -> Option { + // System standard directories + if let Some(project_dirs) = ProjectDirs::from("org", "shadowsocks", "shadowsocks-rust") { + // Linux: $XDG_CONFIG_HOME/shadowsocks-rust/config.json + // $HOME/.config/shadowsocks-rust/config.json + // macOS: $HOME/Library/Application Support/org.shadowsocks.shadowsocks-rust/config.json + // Windows: {FOLDERID_RoamingAppData}/shadowsocks/shadowsocks-rust/config/config.json + + let mut config_path = project_dirs.config_dir().to_path_buf(); + config_path.push("config.json"); + + if config_path.exists() { + return Some(config_path); + } + } + + // UNIX systems, XDG Base Directory + // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + #[cfg(unix)] + if let Ok(base_directories) = xdg::BaseDirectories::with_prefix("shadowsocks-rust") { + // $XDG_CONFIG_HOME/shadowsocks-rust/config.json + // for dir in $XDG_CONFIG_DIRS; $dir/shadowsocks-rust/config.json + if let Some(config_path) = base_directories.find_config_file("config.json") { + return Some(config_path); + } + } + + // UNIX global configuration file + if cfg!(unix) { + let global_config_path = Path::new("/etc/shadowsocks-rust/config.json"); + if global_config_path.exists() { + return Some(global_config_path.to_path_buf()); + } + } + + None +} diff --git a/bin/common/mod.rs b/bin/common/mod.rs index 0fc30f50..e833c833 100644 --- a/bin/common/mod.rs +++ b/bin/common/mod.rs @@ -7,6 +7,7 @@ pub mod daemonize; pub mod logging; pub mod monitor; pub mod validator; +pub mod config; pub const EXIT_CODE_SERVER_EXIT_UNEXPECTEDLY: i32 = exitcode::SOFTWARE; pub const EXIT_CODE_SERVER_ABORTED: i32 = exitcode::SOFTWARE; diff --git a/bin/sslocal.rs b/bin/sslocal.rs index e8648006..986ac6d3 100644 --- a/bin/sslocal.rs +++ b/bin/sslocal.rs @@ -42,7 +42,7 @@ fn main() { (version: VERSION) (about: "A fast tunnel proxy that helps you bypass firewalls.") - (@arg CONFIG: -c --config +takes_value required_unless("SERVER_CONFIG") "Shadowsocks configuration file (https://shadowsocks.org/en/config/quick-guide.html)") + (@arg CONFIG: -c --config +takes_value "Shadowsocks configuration file (https://shadowsocks.org/en/config/quick-guide.html)") (@arg LOCAL_ADDR: -b --("local-addr") +takes_value {validator::validate_server_addr} "Local address, listen only to this address if specified") (@arg UDP_ONLY: -u conflicts_with[TCP_AND_UDP] requires[LOCAL_ADDR] "Server mode UDP_ONLY") @@ -207,11 +207,19 @@ fn main() { } } - let mut config = match matches.value_of("CONFIG") { - Some(cpath) => match Config::load_from_file(cpath, ConfigType::Local) { + let config_path_opt = matches.value_of("CONFIG").map(|c| PathBuf::from(c)).or_else(|| { + if !matches.is_present("SERVER_CONFIG") { + common::config::get_default_config_path() + } else { + None + } + }); + + let mut config = match config_path_opt { + Some(cpath) => match Config::load_from_file(&cpath, ConfigType::Local) { Ok(cfg) => cfg, Err(err) => { - eprintln!("loading config \"{}\", {}", cpath, err); + eprintln!("loading config \"{}\", {}", cpath.display(), err); process::exit(common::EXIT_CODE_LOAD_CONFIG_FAILURE); } }, diff --git a/bin/ssmanager.rs b/bin/ssmanager.rs index 0cb65b75..392c7f55 100644 --- a/bin/ssmanager.rs +++ b/bin/ssmanager.rs @@ -7,7 +7,7 @@ //! *It should be notice that the extended configuration file is not suitable for the server //! side.* -use std::{net::IpAddr, process, time::Duration}; +use std::{net::IpAddr, path::PathBuf, process, time::Duration}; use clap::{clap_app, Arg}; use futures::future::{self, Either}; @@ -44,7 +44,7 @@ fn main() { (@arg UDP_ONLY: -u conflicts_with[TCP_AND_UDP] "Server mode UDP_ONLY") (@arg TCP_AND_UDP: -U conflicts_with[UDP_ONLY] "Server mode TCP_AND_UDP") - (@arg CONFIG: -c --config +takes_value required_unless("MANAGER_ADDR") + (@arg CONFIG: -c --config +takes_value "Shadowsocks configuration file (https://shadowsocks.org/en/config/quick-guide.html), \ the only required fields are \"manager_address\" and \"manager_port\". \ Servers defined will be created when process is started.") @@ -136,11 +136,19 @@ fn main() { } } - let mut config = match matches.value_of("CONFIG") { - Some(cpath) => match Config::load_from_file(cpath, ConfigType::Manager) { + let config_path_opt = matches.value_of("CONFIG").map(|c| PathBuf::from(c)).or_else(|| { + if !matches.is_present("MANAGER_ADDR") { + common::config::get_default_config_path() + } else { + None + } + }); + + let mut config = match config_path_opt { + Some(cpath) => match Config::load_from_file(&cpath, ConfigType::Manager) { Ok(cfg) => cfg, Err(err) => { - eprintln!("loading config \"{}\", {}", cpath, err); + eprintln!("loading config \"{}\", {}", cpath.display(), err); process::exit(common::EXIT_CODE_LOAD_CONFIG_FAILURE); } }, diff --git a/bin/ssserver.rs b/bin/ssserver.rs index bb36715d..83e459fa 100644 --- a/bin/ssserver.rs +++ b/bin/ssserver.rs @@ -7,7 +7,7 @@ //! *It should be notice that the extended configuration file is not suitable for the server //! side.* -use std::{net::IpAddr, process, time::Duration}; +use std::{net::IpAddr, path::PathBuf, process, time::Duration}; use clap::{clap_app, Arg}; use futures::future::{self, Either}; @@ -41,7 +41,7 @@ fn main() { (version: VERSION) (about: "A fast tunnel proxy that helps you bypass firewalls.") - (@arg CONFIG: -c --config +takes_value required_unless("SERVER_ADDR") "Shadowsocks configuration file (https://shadowsocks.org/en/config/quick-guide.html)") + (@arg CONFIG: -c --config +takes_value "Shadowsocks configuration file (https://shadowsocks.org/en/config/quick-guide.html)") (@arg OUTBOUND_BIND_ADDR: -b --("outbound-bind-addr") +takes_value alias("bind-addr") {validator::validate_ip_addr} "Bind address, outbound socket will bind this address") (@arg OUTBOUND_BIND_INTERFACE: --("outbound-bind-interface") +takes_value "Set SO_BINDTODEVICE / IP_BOUND_IF / IP_UNICAST_IF option for outbound socket") @@ -131,11 +131,19 @@ fn main() { } } - let mut config = match matches.value_of("CONFIG") { - Some(cpath) => match Config::load_from_file(cpath, ConfigType::Server) { + let config_path_opt = matches.value_of("CONFIG").map(|c| PathBuf::from(c)).or_else(|| { + if !matches.is_present("SERVER_ADDR") { + common::config::get_default_config_path() + } else { + None + } + }); + + let mut config = match config_path_opt { + Some(cpath) => match Config::load_from_file(&cpath, ConfigType::Server) { Ok(cfg) => cfg, Err(err) => { - eprintln!("loading config \"{}\", {}", cpath, err); + eprintln!("loading config \"{}\", {}", cpath.display(), err); process::exit(common::EXIT_CODE_LOAD_CONFIG_FAILURE); } },