1#![allow(clippy::needless_pass_by_value,clippy::cast_lossless,clippy::identity_op)]
2use futures::future::{err, ok, Future};
3
4use std::rc::Rc;
5
6use super::{box_up_err, peer_strerr, BoxedNewPeerFuture, Peer};
7use super::{ConstructParams, L2rUser, PeerConstructor, Specifier};
8use tokio_io::io::{read_exact, write_all};
9use std::io::Write;
10use std::net::{IpAddr, Ipv4Addr};
11use tokio_io::{AsyncRead, AsyncWrite};
12
13use std::ffi::OsString;
14
15#[derive(Debug, Clone)]
16pub enum SocksHostAddr {
17 Ip(IpAddr),
18 Name(String),
19}
20
21#[derive(Debug, Clone)]
22pub struct SocksSocketAddr {
23 pub host: SocksHostAddr,
24 pub port: u16,
25}
26
27#[derive(Debug)]
28pub struct SocksProxy<T: Specifier>(pub T);
29impl<T: Specifier> Specifier for SocksProxy<T> {
30 fn construct(&self, cp: ConstructParams) -> PeerConstructor {
31 let inner = self.0.construct(cp.clone());
32 inner.map(move |p, l2r| {
33 socks5_peer(p, l2r, false, None, &cp.program_options.socks_destination, cp.program_options.socks5_user_pass.clone(), false)
34 })
35 }
36 specifier_boilerplate!(noglobalstate has_subspec);
37 self_0_is_subspecifier!(proxy_is_multiconnect);
38}
39specifier_class!(
40 name = SocksProxyClass,
41 target = SocksProxy,
42 prefixes = ["socks5-connect:"],
43 arg_handling = subspec,
44 overlay = true,
45 StreamOriented,
46 MulticonnectnessDependsOnInnerType,
47 help = r#"
48SOCKS5 proxy client (raw) [A]
49
50Example: connect to a websocket using local `ssh -D` proxy
51
52 websocat -t - ws-c:socks5-connect:tcp:127.0.0.1:1080 --socks5-destination echo.websocket.org:80 --ws-c-uri ws://echo.websocket.org
53
54For a user-friendly solution, see --socks5 command-line option
55"#
56);
57
58#[derive(Debug)]
59pub struct SocksBind<T: Specifier>(pub T);
60impl<T: Specifier> Specifier for SocksBind<T> {
61 fn construct(&self, cp: ConstructParams) -> PeerConstructor {
62 let inner = self.0.construct(cp.clone());
63 inner.map(move |p, l2r| {
64 socks5_peer(
65 p,
66 l2r,
67 true,
68 cp.program_options.socks5_bind_script.clone(),
69 &cp.program_options.socks_destination,
70 cp.program_options.socks5_user_pass.clone(),
71 cp.program_options.announce_listens,
72 )
73 })
74 }
75 specifier_boilerplate!(noglobalstate has_subspec);
76 self_0_is_subspecifier!(proxy_is_multiconnect);
77}
78specifier_class!(
79 name = SocksBindClass,
80 target = SocksBind,
81 prefixes = ["socks5-bind:"],
82 arg_handling = subspec,
83 overlay = true,
84 StreamOriented,
85 MulticonnectnessDependsOnInnerType,
86 help = r#"
87SOCKS5 proxy client (raw, bind command) [A]
88
89Example: bind to a websocket using some remote SOCKS server
90
91 websocat -v -t ws-u:socks5-bind:tcp:132.148.129.183:14124 - --socks5-destination 255.255.255.255:65535
92
93Note that port is typically unpredictable. Use --socks5-bind-script option to know the port.
94See an example in moreexamples.md for more thorough example.
95"#
96);
97
98type RSRRet =
99 Box<dyn Future<Item = (SocksSocketAddr, Peer), Error = Box<dyn (::std::error::Error)>>>;
100fn read_socks_reply(p: Peer) -> RSRRet {
101 let (r, w, hup) = (p.0, p.1, p.2);
102 let reply = [0; 4];
103
104 fn myerr(x: &'static str) -> RSRRet {
105 Box::new(err(x.to_string().into()))
106 }
107
108 Box::new(
109 read_exact(r, reply)
110 .map_err(box_up_err)
111 .and_then(move |(r, reply)| {
112 if reply[0] != b'\x05' {
113 return myerr("Not a SOCKS5 reply 2");
114 }
115 if reply[1] != b'\x00' {
116 let msg = match reply[1] {
117 1 => "SOCKS: General SOCKS server failure",
118 2 => "SOCKS connection not allowed",
119 3 => "SOCKS: network unreachable",
120 4 => "SOCKS: host unreachable",
121 5 => "SOCKS: connection refused",
122 6 => "SOCKS: TTL expired",
123 7 => "SOCKS: Command not supported",
124 8 => "SOCKS: Address type not supported",
125 _ => "SOCKS: Unknown failure",
126 };
127 return myerr(msg);
128 }
129
130 let ret: RSRRet = match reply[3] {
131 b'\x01' => {
132 let addrport = [0; 4 + 2];
134 Box::new(read_exact(r, addrport).map_err(box_up_err).and_then(
135 move |(r, addrport)| {
136 let port = (addrport[4] as u16) * 256 + (addrport[5] as u16);
137 let ip = Ipv4Addr::new(
138 addrport[0],
139 addrport[1],
140 addrport[2],
141 addrport[3],
142 );
143 let host = SocksHostAddr::Ip(IpAddr::V4(ip));
144 ok((SocksSocketAddr { host, port }, Peer(r, w, hup)))
145 },
146 ))
147 }
148 b'\x04' => {
149 let addrport = [0; 16 + 2];
151 Box::new(read_exact(r, addrport).map_err(box_up_err).and_then(
152 move |(r, addrport)| {
153 let port = (addrport[16] as u16) * 256 + (addrport[17] as u16);
154 let mut ip = [0u8; 16];
158 ip.copy_from_slice(&addrport[0..16]);
159 let host = SocksHostAddr::Ip(IpAddr::V6(ip.into()));
160 ok((SocksSocketAddr { host, port }, Peer(r, w, hup)))
161 },
162 ))
163 }
164 b'\x03' => {
165 let alen = [0; 1];
166 Box::new(read_exact(r, alen).map_err(box_up_err).and_then(
167 move |(r, alen)| {
168 let alen = alen[0] as usize;
169 let addrport = vec![0; alen + 2];
170
171 read_exact(r, addrport).map_err(box_up_err).and_then(
172 move |(r, addrport)| {
173 let port = (addrport[alen] as u16) * 256
174 + (addrport[alen + 1] as u16);
175 let host = SocksHostAddr::Name(
176 ::std::str::from_utf8(&addrport[0..alen])
177 .unwrap_or("(invalid hostname)")
178 .to_string(),
179 );
180 ok((SocksSocketAddr { host, port }, Peer(r, w, hup)))
181 },
182 )
183 },
184 ))
185 }
186 _ => {
187 return myerr("SOCKS: bound address type is unknown");
188 }
189 };
190 ret
191 }),
192 )
193}
194
195pub fn socks5_peer(
196 inner_peer: Peer,
197 _l2r: L2rUser,
198 do_bind: bool,
199 bind_script: Option<OsString>,
200 socks_destination: &Option<SocksSocketAddr>,
201 socks5_user_pass: Option<String>,
202 announce_listen: bool,
203) -> BoxedNewPeerFuture {
204 let (desthost, destport) = if let Some(ref sd) = *socks_destination {
205 (sd.host.clone(), sd.port)
206 } else {
207 return peer_strerr(
208 "--socks5-destination is required for socks5-connect: or socks5-bind: overlays",
209 );
210 };
211
212 if let SocksHostAddr::Name(ref n) = desthost {
213 if n.len() > 255 {
214 return peer_strerr("Destination host name too long for SOCKS5");
215 }
216 };
217
218 info!("Connecting to SOCKS server");
219 let (r, w, hup) = (inner_peer.0, inner_peer.1, inner_peer.2);
220 let f = write_all(w, b"\x05\x02\x00\x02")
221 .map_err(box_up_err)
222 .and_then(move |(w, _)| {
223 read_exact(r, [0; 2])
224 .map_err(box_up_err)
225 .and_then(move |(r, reply)| {
226 if reply[0] != 0x05 {
227 return peer_strerr("Not a SOCKS5 reply");
228 }
229
230 let auth_future: BoxedNewPeerFuture = match reply[1] {
231 0x00 => {
232 info!("SOCKS5: Auth method 0, no auth");
233 authenticate_no_auth(r, w)
234 }
235 0x02 => {
236 authenticate_username_password(r, w, &socks5_user_pass)
237 }
238 _ => {
239 return peer_strerr("SOCKS5: Unknown authentication method");
240 }
241 };
242
243 Box::new(auth_future.and_then(move |Peer(r, w, _)| {
244 let request = build_socks5_request(do_bind, &desthost, destport).unwrap();
245 Box::new(
246 write_all(w, request)
247 .map_err(box_up_err)
248 .and_then(move |(w, _)| {
249 read_socks_reply(Peer(r, w, hup)).and_then(move |(addr, Peer(r, w, hup))| {
250 info!("SOCKS5 connect/bind: {:?}", addr);
251
252 if do_bind {
253 if announce_listen {
254 println!("LISTEN proto=tcp,port={}", addr.port);
255 }
256 if let Some(bs) = bind_script {
257 let _ = ::std::process::Command::new(bs)
258 .arg(format!("{}", addr.port))
259 .spawn();
260 }
261
262 Box::new(read_socks_reply(Peer(r, w, hup)).and_then(move |(addr, Peer(r, w, hup))| {
263 info!("SOCKS5 remote connected: {:?}", addr);
264 Box::new(ok(Peer(r, w, hup)))
265 }))
266 as BoxedNewPeerFuture
267 } else {
268 Box::new(ok(Peer(r, w, hup))) as BoxedNewPeerFuture
269 }
270 })
271 }),
272 )
273 }))
274 })
275 });
276
277 Box::new(f) as BoxedNewPeerFuture
278}
279
280fn authenticate_no_auth(
281 r: Box<dyn AsyncRead>,
282 w: Box<dyn AsyncWrite>
283) -> BoxedNewPeerFuture {
284 Box::new(ok(Peer(r, w, None)))
285}
286
287fn authenticate_username_password(
288 r: Box<dyn AsyncRead>,
289 w: Box<dyn AsyncWrite>,
290 socks5_user_pass: &Option<String>,
291) -> BoxedNewPeerFuture {
292 info!("SOCKS5: Auth method 2, sending username/password");
293
294 let (user, pass) = match socks5_user_pass {
295 Some(ref user_pass) => {
296 let parts: Vec<&str> = user_pass.splitn(2, ':').collect();
297 if parts.len() != 2 {
298 return peer_strerr("SOCKS5: Invalid username:password format");
299 }
300 (parts[0].as_bytes(), parts[1].as_bytes())
301 },
302 None => return peer_strerr("SOCKS5: Username and password required but not provided"),
303 };
304 let mut buffer = Vec::with_capacity(1 + 2 + 1 + user.len() + 1 + pass.len());
305 buffer.write_all(&[0x01]).unwrap(); buffer.write_all(&[user.len() as u8]).unwrap();
307 buffer.write_all(user).unwrap();
308 buffer.write_all(&[pass.len() as u8]).unwrap();
309 buffer.write_all(pass).unwrap();
310
311 Box::new(
312 write_all(w, buffer)
313 .map_err(box_up_err)
314 .and_then(move |(w, _)| {
315 read_exact(r, [0; 2])
316 .map_err(box_up_err)
317 .and_then(move |(r, reply)| {
318 if reply[0] != 0x01 || reply[1] != 0x00 {
319 return peer_strerr("SOCKS5: Authentication failed");
320 }
321 Box::new(ok(Peer(
322 Box::new(r) as Box<dyn AsyncRead>,
323 Box::new(w) as Box<dyn AsyncWrite>,
324 None,
325 )))
326 })
327 }),
328 )
329}
330
331fn build_socks5_request(
332 do_bind: bool,
333 desthost: &SocksHostAddr,
334 destport: u16,
335) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
336 let mut request = Vec::with_capacity(20);
337 if do_bind {
338 request.write_all(&[0x05, 0x02, 0x00])?; } else {
340 request.write_all(&[0x05, 0x01, 0x00])?; }
342
343 match desthost {
344 SocksHostAddr::Ip(IpAddr::V4(ip4)) => {
345 request.write_all(&[0x01])?; request.write_all(&ip4.octets())?;
347 }
348 SocksHostAddr::Ip(IpAddr::V6(ip6)) => {
349 request.write_all(&[0x04])?; request.write_all(&ip6.octets())?;
351 }
352 SocksHostAddr::Name(name) => {
353 request.write_all(&[0x03])?; request.write_all(&[name.len() as u8])?;
355 request.write_all(name.as_bytes())?;
356 }
357 }
358
359 request.write_all(&[(destport >> 8) as u8, (destport & 0xFF) as u8])?;
360 Ok(request)
361}