mirror of
https://github.com/shadowsocks/shadowsocks-rust.git
synced 2026-02-09 01:59:16 +08:00
SOCKS5 RFC1929 Username/Password Authentication (#788)
This commit is contained in:
43
Cargo.lock
generated
43
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<String>,
|
||||
|
||||
/// SOCKS5
|
||||
#[cfg(feature = "local")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
socks5_auth_config_path: Option<String>,
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
@@ -199,6 +199,7 @@ pub async fn create(config: Config) -> io::Result<Server> {
|
||||
|
||||
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);
|
||||
|
||||
141
crates/shadowsocks-service/src/local/socks/config.rs
Normal file
141
crates/shadowsocks-service/src/local/socks/config.rs
Normal file
@@ -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<SSSocks5AuthPasswordUserConfig>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct SSSocks5AuthConfig {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
password: Option<SSSocks5AuthPasswordConfig>,
|
||||
}
|
||||
|
||||
/// 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<P: AsRef<Path> + ?Sized>(filename: &P) -> io::Result<Socks5AuthConfig> {
|
||||
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<String, String>,
|
||||
}
|
||||
|
||||
impl Socks5AuthPasswdConfig {
|
||||
/// Create an empty `Passwd` configuration
|
||||
pub fn new() -> Socks5AuthPasswdConfig {
|
||||
Socks5AuthPasswdConfig { passwd: HashMap::new() }
|
||||
}
|
||||
|
||||
/// Add a user with password
|
||||
pub fn add_user<U, P>(&mut self, user_name: U, password: P)
|
||||
where
|
||||
U: Into<String>,
|
||||
P: Into<String>,
|
||||
{
|
||||
self.passwd.insert(user_name.into(), password.into());
|
||||
}
|
||||
|
||||
/// Check if `user_name` exists and validate `password`
|
||||
pub fn check_user<U, P>(&self, user_name: U, password: P) -> bool
|
||||
where
|
||||
U: AsRef<str>,
|
||||
P: AsRef<str>,
|
||||
{
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Duration>,
|
||||
udp_capacity: Option<usize>,
|
||||
udp_bind_addr: Option<ServerAddr>,
|
||||
socks5_auth: Arc<Socks5AuthConfig>,
|
||||
}
|
||||
|
||||
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<Socks5AuthConfig>,
|
||||
) -> 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
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Arc<ServerAddr>>,
|
||||
balancer: PingBalancer,
|
||||
mode: Mode,
|
||||
auth: Arc<Socks5AuthConfig>,
|
||||
}
|
||||
|
||||
impl Socks5TcpHandler {
|
||||
@@ -47,12 +52,132 @@ impl Socks5TcpHandler {
|
||||
udp_bind_addr: Option<Arc<ServerAddr>>,
|
||||
balancer: PingBalancer,
|
||||
mode: Mode,
|
||||
auth: Arc<Socks5AuthConfig>,
|
||||
) -> 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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<u8>,
|
||||
pub passwd: Vec<u8>,
|
||||
}
|
||||
|
||||
impl PasswordAuthenticationInitialRequest {
|
||||
/// Create a Username/Password Authentication Request
|
||||
pub fn new<U, P>(uname: U, passwd: P) -> PasswordAuthenticationInitialRequest
|
||||
where
|
||||
U: Into<Vec<u8>>,
|
||||
P: Into<Vec<u8>>,
|
||||
{
|
||||
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>(r: &mut R) -> Result<PasswordAuthenticationInitialRequest, Error>
|
||||
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<W>(&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<B: BufMut>(&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>(r: &mut R) -> Result<PasswordAuthenticationResponse, Error>
|
||||
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<W>(&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<B: BufMut>(&self, buf: &mut B) {
|
||||
buf.put_u8(0x01);
|
||||
buf.put_u8(self.status);
|
||||
}
|
||||
|
||||
/// Length in bytes
|
||||
#[inline]
|
||||
pub fn serialized_len(&self) -> usize {
|
||||
2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user