diff --git a/Cargo.lock b/Cargo.lock index 9d5b97ca..c12eeb0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -357,9 +357,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +checksum = "fdbfe11fe19ff083c48923cf179540e8cd0535903dc35e178a1fdeeb59aef51f" dependencies = [ "cfg-if", "crossbeam-utils", @@ -367,9 +367,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if", "lazy_static", @@ -1066,9 +1066,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.119" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" +checksum = "ad5c14e80759d0939d013e6ca49930e59fc53dd8e5009132f76240c179380c09" [[package]] name = "libmimalloc-sys" @@ -1219,19 +1219,6 @@ dependencies = [ "libmimalloc-sys", ] -[[package]] -name = "mio" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" -dependencies = [ - "libc", - "log", - "miow", - "ntapi", - "winapi", -] - [[package]] name = "mio" version = "0.8.1" @@ -1288,9 +1275,9 @@ dependencies = [ [[package]] name = "notify" -version = "5.0.0-pre.13" +version = "5.0.0-pre.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245d358380e2352c2d020e8ee62baac09b3420f1f6c012a31326cfced4ad487d" +checksum = "d13c22db70a63592e098fb51735bab36646821e6389a0ba171f3549facdf0b74" dependencies = [ "bitflags", "crossbeam-channel", @@ -1299,7 +1286,7 @@ dependencies = [ "inotify", "kqueue", "libc", - "mio 0.7.14", + "mio", "walkdir", "winapi", ] @@ -2011,7 +1998,7 @@ dependencies = [ [[package]] name = "shadowsocks" -version = "1.14.0" +version = "1.14.1" dependencies = [ "arc-swap 1.5.0", "async-trait", @@ -2066,7 +2053,7 @@ dependencies = [ [[package]] name = "shadowsocks-rust" -version = "1.14.1" +version = "1.14.2" dependencies = [ "build-time", "byte_string", @@ -2098,7 +2085,7 @@ dependencies = [ [[package]] name = "shadowsocks-service" -version = "1.14.1" +version = "1.14.2" dependencies = [ "arc-swap 1.5.0", "async-trait", @@ -2262,9 +2249,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.86" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +checksum = "ebd69e719f31e88618baa1eaa6ee2de5c9a1c004f1e9ecdb58e8352a13f20a01" dependencies = [ "proc-macro2", "quote", @@ -2393,7 +2380,7 @@ dependencies = [ "bytes", "libc", "memchr", - "mio 0.8.1", + "mio", "num_cpus", "once_cell", "parking_lot 0.12.0", diff --git a/Cargo.toml b/Cargo.toml index bf6a64fa..ac4d245c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shadowsocks-rust" -version = "1.14.1" +version = "1.14.2" authors = ["Shadowsocks Contributors"] description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls." repository = "https://github.com/shadowsocks/shadowsocks-rust" diff --git a/README.md b/README.md index 3901bcdd..a9927503 100644 --- a/README.md +++ b/README.md @@ -464,7 +464,9 @@ Example configuration: "local_port": 1080, // OPTIONAL. Setting the `mode` for this specific local server instance. // If not set, it will derive from the outer `mode` - "mode": "tcp_and_udp" + "mode": "tcp_and_udp", + // OPTIONAL. Authentication configuration file + "socks5_auth_config_path": "/path/to/auth.json" }, { // SOCKS5, SOCKS4/4a local server diff --git a/crates/shadowsocks-service/Cargo.toml b/crates/shadowsocks-service/Cargo.toml index 5eaf300f..2edab86f 100644 --- a/crates/shadowsocks-service/Cargo.toml +++ b/crates/shadowsocks-service/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shadowsocks-service" -version = "1.14.1" +version = "1.14.2" authors = ["Shadowsocks Contributors"] description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls." repository = "https://github.com/shadowsocks/shadowsocks-rust" diff --git a/crates/shadowsocks-service/src/config.rs b/crates/shadowsocks-service/src/config.rs index 2643be51..86fa37d6 100644 --- a/crates/shadowsocks-service/src/config.rs +++ b/crates/shadowsocks-service/src/config.rs @@ -75,6 +75,8 @@ use trust_dns_resolver::config::{NameServerConfig, Protocol, ResolverConfig}; use crate::acl::AccessControl; #[cfg(feature = "local-dns")] use crate::local::dns::NameServerAddr; +#[cfg(feature = "local")] +use crate::local::socks::config::Socks5AuthConfig; #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] @@ -246,6 +248,11 @@ struct SSLocalExtConfig { #[cfg(feature = "local-tun")] #[serde(skip_serializing_if = "Option::is_none")] tun_interface_address: Option, + + /// SOCKS5 + #[cfg(feature = "local")] + #[serde(skip_serializing_if = "Option::is_none")] + socks5_auth_config_path: Option, } #[derive(Serialize, Deserialize, Debug)] @@ -823,6 +830,10 @@ pub struct LocalConfig { /// Set `IPV6_V6ONLY` for listener socket pub ipv6_only: bool, + + /// SOCKS5 Authentication configuration + #[cfg(feature = "local")] + pub socks5_auth: Socks5AuthConfig, } impl LocalConfig { @@ -859,6 +870,9 @@ impl LocalConfig { tun_device_fd_from_path: None, ipv6_only: false, + + #[cfg(feature = "local")] + socks5_auth: Socks5AuthConfig::default(), } } @@ -1420,6 +1434,11 @@ impl Config { local_config.tun_interface_name = Some(tun_interface_name); } + #[cfg(feature = "local")] + if let Some(socks5_auth_config_path) = local.socks5_auth_config_path { + local_config.socks5_auth = Socks5AuthConfig::load_from_file(&socks5_auth_config_path)?; + } + nconfig.local.push(local_config); } } @@ -2118,6 +2137,9 @@ impl fmt::Display for Config { tun_interface_name: local.tun_interface_name.clone(), #[cfg(feature = "local-tun")] tun_interface_address: local.tun_interface_address.as_ref().map(ToString::to_string), + + #[cfg(feature = "local")] + socks5_auth_config_path: None, }; jlocals.push(jlocal); } diff --git a/crates/shadowsocks-service/src/local/mod.rs b/crates/shadowsocks-service/src/local/mod.rs index 4fa1c5d3..f14e560b 100644 --- a/crates/shadowsocks-service/src/local/mod.rs +++ b/crates/shadowsocks-service/src/local/mod.rs @@ -199,6 +199,7 @@ pub async fn create(config: Config) -> io::Result { let mut server = Socks::with_context(context.clone()); server.set_mode(local_config.mode); + server.set_socks5_auth(local_config.socks5_auth); if let Some(c) = config.udp_max_associations { server.set_udp_capacity(c); diff --git a/crates/shadowsocks-service/src/local/socks/config.rs b/crates/shadowsocks-service/src/local/socks/config.rs new file mode 100644 index 00000000..f07e9975 --- /dev/null +++ b/crates/shadowsocks-service/src/local/socks/config.rs @@ -0,0 +1,141 @@ +//! SOCK protocol configuration + +use std::{ + collections::HashMap, + fs::OpenOptions, + io::{self, ErrorKind, Read}, + path::Path, +}; + +use log::trace; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +struct SSSocks5AuthPasswordUserConfig { + user_name: String, + password: String, +} + +#[derive(Deserialize, Debug)] +struct SSSocks5AuthPasswordConfig { + users: Vec, +} + +#[derive(Deserialize, Debug)] +struct SSSocks5AuthConfig { + #[serde(skip_serializing_if = "Option::is_none")] + password: Option, +} + +/// SOCKS5 Authentication method +#[derive(Debug, Clone)] +pub struct Socks5AuthConfig { + pub passwd: Socks5AuthPasswdConfig, +} + +impl Socks5AuthConfig { + /// Create a new SOCKS5 Authentication configuration + pub fn new() -> Socks5AuthConfig { + Socks5AuthConfig { + passwd: Socks5AuthPasswdConfig::new(), + } + } + + /// Load from configuration file + /// + /// ```json + /// { + /// "password": { + /// "users": [ + /// { + /// "user_name": "USER_NAME", + /// "password": "PASSWORD" + /// } + /// ] + /// } + /// } + pub fn load_from_file + ?Sized>(filename: &P) -> io::Result { + let filename = filename.as_ref(); + + trace!( + "loading socks5 authentication configuration from {}", + filename.display() + ); + + let mut reader = OpenOptions::new().read(true).open(filename)?; + let mut content = String::new(); + reader.read_to_string(&mut content)?; + + let jconf: SSSocks5AuthConfig = match json5::from_str(&content) { + Ok(c) => c, + Err(err) => return Err(io::Error::new(ErrorKind::Other, err)), + }; + + let mut passwd = Socks5AuthPasswdConfig::new(); + if let Some(p) = jconf.password { + for user in p.users { + passwd.add_user(user.user_name, user.password); + } + } + + Ok(Socks5AuthConfig { passwd }) + } + + /// Check if authentication is required + pub fn auth_required(&self) -> bool { + self.passwd.total_users() > 0 + } +} + +impl Default for Socks5AuthConfig { + fn default() -> Socks5AuthConfig { + Socks5AuthConfig::new() + } +} + +/// SOCKS5 server User/Password Authentication configuration +/// +/// RFC1929 https://datatracker.ietf.org/doc/html/rfc1929 +#[derive(Debug, Clone)] +pub struct Socks5AuthPasswdConfig { + passwd: HashMap, +} + +impl Socks5AuthPasswdConfig { + /// Create an empty `Passwd` configuration + pub fn new() -> Socks5AuthPasswdConfig { + Socks5AuthPasswdConfig { passwd: HashMap::new() } + } + + /// Add a user with password + pub fn add_user(&mut self, user_name: U, password: P) + where + U: Into, + P: Into, + { + self.passwd.insert(user_name.into(), password.into()); + } + + /// Check if `user_name` exists and validate `password` + pub fn check_user(&self, user_name: U, password: P) -> bool + where + U: AsRef, + P: AsRef, + { + match self.passwd.get(user_name.as_ref()) { + Some(pwd) => pwd == password.as_ref(), + None => false, + } + } + + /// Total users + pub fn total_users(&self) -> usize { + self.passwd.len() + } +} + +impl Default for Socks5AuthPasswdConfig { + fn default() -> Socks5AuthPasswdConfig { + Socks5AuthPasswdConfig::new() + } +} diff --git a/crates/shadowsocks-service/src/local/socks/mod.rs b/crates/shadowsocks-service/src/local/socks/mod.rs index 5add5a61..4c60e048 100644 --- a/crates/shadowsocks-service/src/local/socks/mod.rs +++ b/crates/shadowsocks-service/src/local/socks/mod.rs @@ -3,6 +3,7 @@ pub use self::server::Socks; pub mod client; +pub mod config; pub mod server; #[cfg(feature = "local-socks4")] pub mod socks4; diff --git a/crates/shadowsocks-service/src/local/socks/server/mod.rs b/crates/shadowsocks-service/src/local/socks/server/mod.rs index d5120499..c9ce3cb0 100644 --- a/crates/shadowsocks-service/src/local/socks/server/mod.rs +++ b/crates/shadowsocks-service/src/local/socks/server/mod.rs @@ -13,6 +13,8 @@ use crate::local::{context::ServiceContext, loadbalancing::PingBalancer}; use self::socks4::Socks4TcpHandler; use self::socks5::{Socks5TcpHandler, Socks5UdpServer}; +use super::config::Socks5AuthConfig; + #[cfg(feature = "local-socks4")] mod socks4; mod socks5; @@ -24,6 +26,7 @@ pub struct Socks { udp_expiry_duration: Option, udp_capacity: Option, udp_bind_addr: Option, + socks5_auth: Arc, } impl Default for Socks { @@ -47,6 +50,7 @@ impl Socks { udp_expiry_duration: None, udp_capacity: None, udp_bind_addr: None, + socks5_auth: Arc::new(Socks5AuthConfig::default()), } } @@ -73,6 +77,11 @@ impl Socks { self.udp_bind_addr = Some(a); } + /// Set SOCKS5 Username/Password Authentication configuration + pub fn set_socks5_auth(&mut self, p: Socks5AuthConfig) { + self.socks5_auth = Arc::new(p); + } + /// Start serving pub async fn run(self, client_config: &ServerAddr, balancer: PingBalancer) -> io::Result<()> { let mut vfut = Vec::new(); @@ -130,6 +139,7 @@ impl Socks { let context = self.context.clone(); let udp_bind_addr = udp_bind_addr.clone(); let mode = self.mode; + let socks5_auth = self.socks5_auth.clone(); tokio::spawn(Socks::handle_tcp_client( context, @@ -138,6 +148,7 @@ impl Socks { balancer, peer_addr, mode, + socks5_auth, )); } } @@ -150,6 +161,7 @@ impl Socks { balancer: PingBalancer, peer_addr: SocketAddr, mode: Mode, + socks5_auth: Arc, ) -> io::Result<()> { use std::io::ErrorKind; @@ -166,7 +178,7 @@ impl Socks { } 0x05 => { - let handler = Socks5TcpHandler::new(context, udp_bind_addr, balancer, mode); + let handler = Socks5TcpHandler::new(context, udp_bind_addr, balancer, mode, socks5_auth); handler.handle_socks5_client(stream, peer_addr).await } diff --git a/crates/shadowsocks-service/src/local/socks/server/socks5/tcprelay.rs b/crates/shadowsocks-service/src/local/socks/server/socks5/tcprelay.rs index 26abe4cd..8663d5b2 100644 --- a/crates/shadowsocks-service/src/local/socks/server/socks5/tcprelay.rs +++ b/crates/shadowsocks-service/src/local/socks/server/socks5/tcprelay.rs @@ -3,6 +3,7 @@ use std::{ io::{self, ErrorKind}, net::{Ipv4Addr, SocketAddr}, + str, sync::Arc, }; @@ -16,6 +17,8 @@ use shadowsocks::{ Error as Socks5Error, HandshakeRequest, HandshakeResponse, + PasswordAuthenticationInitialRequest, + PasswordAuthenticationResponse, Reply, TcpRequestHeader, TcpResponseHeader, @@ -29,6 +32,7 @@ use crate::{ context::ServiceContext, loadbalancing::PingBalancer, net::AutoProxyClientStream, + socks::config::Socks5AuthConfig, utils::establish_tcp_tunnel, }, net::utils::ignore_until_end, @@ -39,6 +43,7 @@ pub struct Socks5TcpHandler { udp_bind_addr: Option>, balancer: PingBalancer, mode: Mode, + auth: Arc, } impl Socks5TcpHandler { @@ -47,12 +52,132 @@ impl Socks5TcpHandler { udp_bind_addr: Option>, balancer: PingBalancer, mode: Mode, + auth: Arc, ) -> Socks5TcpHandler { Socks5TcpHandler { context, udp_bind_addr, balancer, mode, + auth, + } + } + + async fn check_auth(&self, stream: &mut TcpStream, handshake_req: &HandshakeRequest) -> io::Result<()> { + use std::io::Error; + + let allow_none = !self.auth.auth_required(); + + for method in handshake_req.methods.iter() { + match *method { + socks5::SOCKS5_AUTH_METHOD_PASSWORD => { + let resp = HandshakeResponse::new(socks5::SOCKS5_AUTH_METHOD_PASSWORD); + trace!("reply handshake {:?}", resp); + resp.write_to(stream).await?; + + return self.check_auth_password(stream).await; + } + socks5::SOCKS5_AUTH_METHOD_NONE => { + if !allow_none { + trace!("none authentication method is not allowed"); + } else { + let resp = HandshakeResponse::new(socks5::SOCKS5_AUTH_METHOD_NONE); + trace!("reply handshake {:?}", resp); + resp.write_to(stream).await?; + + return Ok(()); + } + } + _ => { + trace!("unsupported authentication method {}", method); + } + } + } + + let resp = HandshakeResponse::new(socks5::SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE); + resp.write_to(stream).await?; + + trace!("reply handshake {:?}", resp); + + return Err(Error::new( + ErrorKind::Other, + "currently shadowsocks-rust does not support authentication", + )); + } + + async fn check_auth_password(&self, stream: &mut TcpStream) -> io::Result<()> { + use std::io::Error; + + const PASSWORD_AUTH_STATUS_FAILURE: u8 = 255; + + // Read initiation negociation + + let init = match PasswordAuthenticationInitialRequest::read_from(stream).await { + Ok(i) => i, + Err(err) => { + let rsp = PasswordAuthenticationResponse::new(err.as_reply().as_u8()); + let _ = rsp.write_to(stream).await; + + return Err(Error::new( + ErrorKind::Other, + format!("Username/Password Authentication Initial request failed: {}", err), + )); + } + }; + + let user_name = match str::from_utf8(&init.uname) { + Ok(u) => u, + Err(..) => { + let rsp = PasswordAuthenticationResponse::new(PASSWORD_AUTH_STATUS_FAILURE); + let _ = rsp.write_to(stream).await; + + return Err(Error::new( + ErrorKind::Other, + "Username/Password Authentication Initial request uname contains invaid characters", + )); + } + }; + + let password = match str::from_utf8(&init.passwd) { + Ok(u) => u, + Err(..) => { + let rsp = PasswordAuthenticationResponse::new(PASSWORD_AUTH_STATUS_FAILURE); + let _ = rsp.write_to(stream).await; + + return Err(Error::new( + ErrorKind::Other, + "Username/Password Authentication Initial request passwd contains invaid characters", + )); + } + }; + + if self.auth.passwd.check_user(user_name, password) { + trace!( + "socks5 authenticated with Username/Password method, user: {}, password: {}", + user_name, + password + ); + + let rsp = PasswordAuthenticationResponse::new(0); + rsp.write_to(stream).await?; + + Ok(()) + } else { + let rsp = PasswordAuthenticationResponse::new(PASSWORD_AUTH_STATUS_FAILURE); + rsp.write_to(stream).await?; + + error!( + "socks5 rejected Username/Password user: {}, password: {}", + user_name, password + ); + + return Err(Error::new( + ErrorKind::Other, + format!( + "Username/Password Authentication failed, user: {}, password: {}", + user_name, password + ), + )); } } @@ -72,23 +197,7 @@ impl Socks5TcpHandler { }; trace!("socks5 {:?}", handshake_req); - - if !handshake_req.methods.contains(&socks5::SOCKS5_AUTH_METHOD_NONE) { - use std::io::Error; - - let resp = HandshakeResponse::new(socks5::SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE); - resp.write_to(&mut stream).await?; - - return Err(Error::new( - ErrorKind::Other, - "currently shadowsocks-rust does not support authentication", - )); - } else { - // Reply to client - let resp = HandshakeResponse::new(socks5::SOCKS5_AUTH_METHOD_NONE); - trace!("reply handshake {:?}", resp); - resp.write_to(&mut stream).await?; - } + self.check_auth(&mut stream, &handshake_req).await?; // 2. Fetch headers let header = match TcpRequestHeader::read_from(&mut stream).await { diff --git a/crates/shadowsocks/Cargo.toml b/crates/shadowsocks/Cargo.toml index 4633fb5a..f07375a6 100644 --- a/crates/shadowsocks/Cargo.toml +++ b/crates/shadowsocks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shadowsocks" -version = "1.14.0" +version = "1.14.1" authors = ["Shadowsocks Contributors"] description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls." repository = "https://github.com/shadowsocks/shadowsocks-rust" diff --git a/crates/shadowsocks/src/relay/socks5.rs b/crates/shadowsocks/src/relay/socks5.rs index d01c9cf7..9c3eb2f0 100644 --- a/crates/shadowsocks/src/relay/socks5.rs +++ b/crates/shadowsocks/src/relay/socks5.rs @@ -103,7 +103,7 @@ pub enum Reply { impl Reply { #[inline] #[rustfmt::skip] - fn as_u8(self) -> u8 { + pub fn as_u8(self) -> u8 { match self { Reply::Succeeded => consts::SOCKS5_REPLY_SUCCEEDED, Reply::GeneralFailure => consts::SOCKS5_REPLY_GENERAL_FAILURE, @@ -120,7 +120,7 @@ impl Reply { #[inline] #[rustfmt::skip] - fn from_u8(code: u8) -> Reply { + pub fn from_u8(code: u8) -> Reply { match code { consts::SOCKS5_REPLY_SUCCEEDED => Reply::Succeeded, consts::SOCKS5_REPLY_GENERAL_FAILURE => Reply::GeneralFailure, @@ -793,3 +793,150 @@ impl UdpAssociateHeader { 3 + self.address.serialized_len() } } + +/// Username/Password Authentication Inittial Negociation +/// +/// https://datatracker.ietf.org/doc/html/rfc1929 +/// +/// ```plain +/// +----+------+----------+------+----------+ +/// |VER | ULEN | UNAME | PLEN | PASSWD | +/// +----+------+----------+------+----------+ +/// | 1 | 1 | 1 to 255 | 1 | 1 to 255 | +/// +----+------+----------+------+----------+ +/// ``` +pub struct PasswordAuthenticationInitialRequest { + pub uname: Vec, + pub passwd: Vec, +} + +impl PasswordAuthenticationInitialRequest { + /// Create a Username/Password Authentication Request + pub fn new(uname: U, passwd: P) -> PasswordAuthenticationInitialRequest + where + U: Into>, + P: Into>, + { + let uname = uname.into(); + let passwd = passwd.into(); + assert!( + uname.len() > 0 && uname.len() <= u8::MAX as usize && passwd.len() > 0 && passwd.len() <= u8::MAX as usize + ); + + PasswordAuthenticationInitialRequest { uname, passwd } + } + + /// Read from a reader + pub async fn read_from(r: &mut R) -> Result + where + R: AsyncRead + Unpin, + { + let mut ver_buf = [0u8; 1]; + let _ = r.read_exact(&mut ver_buf).await?; + + // The only valid subnegociation version + if ver_buf[0] != 0x01 { + return Err(Error::Reply(Reply::GeneralFailure)); + } + + let mut ulen_buf = [0u8; 1]; + let _ = r.read_exact(&mut ulen_buf).await?; + + let ulen = ulen_buf[0] as usize; + if ulen == 0 { + return Err(Error::Reply(Reply::GeneralFailure)); + } + + let mut uname = vec![0u8; ulen]; + if ulen > 0 { + let _ = r.read_exact(&mut uname).await?; + } + + let mut plen_buf = [0u8; 1]; + let _ = r.read_exact(&mut plen_buf).await?; + + let plen = plen_buf[0] as usize; + if plen == 0 { + return Err(Error::Reply(Reply::GeneralFailure)); + } + + let mut passwd = vec![0u8; plen]; + if plen > 0 { + let _ = r.read_exact(&mut passwd).await?; + } + + Ok(PasswordAuthenticationInitialRequest { uname, passwd }) + } + + /// Write to a writer + pub async fn write_to(&self, w: &mut W) -> io::Result<()> + where + W: AsyncWrite + Unpin, + { + let mut buf = BytesMut::with_capacity(self.serialized_len()); + self.write_to_buf(&mut buf); + w.write_all(&buf).await + } + + /// Write to buffer + fn write_to_buf(&self, buf: &mut B) { + buf.put_u8(0x01); + buf.put_u8(self.uname.len() as u8); + buf.put_slice(&self.uname); + buf.put_u8(self.passwd.len() as u8); + buf.put_slice(&self.passwd); + } + + /// Length in bytes + #[inline] + pub fn serialized_len(&self) -> usize { + 1 + 1 + self.uname.len() + 1 + self.passwd.len() + } +} + +pub struct PasswordAuthenticationResponse { + pub status: u8, +} + +impl PasswordAuthenticationResponse { + pub fn new(status: u8) -> PasswordAuthenticationResponse { + PasswordAuthenticationResponse { status } + } + + /// Read from a reader + pub async fn read_from(r: &mut R) -> Result + where + R: AsyncRead + Unpin, + { + let mut buf = [0u8; 2]; + let _ = r.read_exact(&mut buf); + + if buf[0] != 0x01 { + return Err(Error::Reply(Reply::GeneralFailure)); + } + + Ok(PasswordAuthenticationResponse { status: buf[1] }) + } + + /// Write to a writer + pub async fn write_to(&self, w: &mut W) -> io::Result<()> + where + W: AsyncWrite + Unpin, + { + let mut buf = BytesMut::with_capacity(self.serialized_len()); + self.write_to_buf(&mut buf); + w.write_all(&buf).await + } + + /// Write to buffer + fn write_to_buf(&self, buf: &mut B) { + buf.put_u8(0x01); + buf.put_u8(self.status); + } + + /// Length in bytes + #[inline] + pub fn serialized_len(&self) -> usize { + 2 + } +} diff --git a/src/service/local.rs b/src/service/local.rs index b9ad3868..5d18a466 100644 --- a/src/service/local.rs +++ b/src/service/local.rs @@ -27,7 +27,8 @@ use shadowsocks_service::{ use crate::logging; use crate::{ config::{Config as ServiceConfig, RuntimeMode}, - monitor, validator, + monitor, + validator, }; /// Defines command line options