websocat/
socks5_peer.rs

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                        // ipv4
133                        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                        // ipv6
150                        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                                // still not worth to switch to Cargo.toml to add
155                                // "bytes" dependency, then scroll up for "extern crate",
156                                // then look up docs again to find out where to get that BE thing.
157                                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(); // Version
306    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])?; // SOCKS5, CMD BIND
339    } else {
340        request.write_all(&[0x05, 0x01, 0x00])?; // SOCKS5, CMD CONNECT
341    }
342
343    match desthost {
344        SocksHostAddr::Ip(IpAddr::V4(ip4)) => {
345            request.write_all(&[0x01])?; // ATYP IPv4
346            request.write_all(&ip4.octets())?;
347        }
348        SocksHostAddr::Ip(IpAddr::V6(ip6)) => {
349            request.write_all(&[0x04])?; // ATYP IPv6
350            request.write_all(&ip6.octets())?;
351        }
352        SocksHostAddr::Name(name) => {
353            request.write_all(&[0x03])?; // ATYP DOMAINNAME
354            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}