1
//! Structures that represent SOCKS messages
2

            
3
use crate::{Error, Result};
4

            
5
use caret::caret_int;
6
use std::convert::TryFrom;
7
use std::fmt;
8
use std::net::IpAddr;
9

            
10
use tor_error::bad_api_usage;
11

            
12
/// A supported SOCKS version.
13
18
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
14
#[non_exhaustive]
15
pub enum SocksVersion {
16
    /// Socks v4.
17
    V4,
18
    /// Socks v5.
19
    V5,
20
}
21

            
22
impl From<SocksVersion> for u8 {
23
    fn from(v: SocksVersion) -> u8 {
24
        match v {
25
            SocksVersion::V4 => 4,
26
            SocksVersion::V5 => 5,
27
        }
28
    }
29
}
30

            
31
impl TryFrom<u8> for SocksVersion {
32
    type Error = Error;
33
14
    fn try_from(v: u8) -> Result<SocksVersion> {
34
14
        match v {
35
3
            4 => Ok(SocksVersion::V4),
36
11
            5 => Ok(SocksVersion::V5),
37
            _ => Err(Error::BadProtocol(v)),
38
        }
39
14
    }
40
}
41

            
42
/// A completed SOCKS request, as negotiated on a SOCKS connection.
43
///
44
/// Once this request is done, we know where to connect.  Don't
45
/// discard this object immediately: Use it to report success or
46
/// failure.
47
#[derive(Clone, Debug)]
48
pub struct SocksRequest {
49
    /// Negotiated SOCKS protocol version.
50
    version: SocksVersion,
51
    /// The command requested by the SOCKS client.
52
    cmd: SocksCmd,
53
    /// The target address.
54
    addr: SocksAddr,
55
    /// The target port.
56
    port: u16,
57
    /// Authentication information.
58
    ///
59
    /// (Tor doesn't believe in SOCKS authentication, since it cannot
60
    /// possibly secure.  Instead, we use it for circuit isolation.)
61
    auth: SocksAuth,
62
}
63

            
64
/// An address sent or received as part of a SOCKS handshake
65
2
#[derive(Clone, Debug, PartialEq, Eq)]
66
#[allow(clippy::exhaustive_enums)]
67
pub enum SocksAddr {
68
    /// A regular DNS hostname.
69
    Hostname(SocksHostname),
70
    /// An IP address.  (Tor doesn't like to receive these during SOCKS
71
    /// handshakes, since they usually indicate that the hostname lookup
72
    /// happened somewhere else.)
73
    Ip(IpAddr),
74
}
75

            
76
/// A hostname for use with SOCKS.  It is limited in length.
77
#[derive(Clone, Debug, PartialEq, Eq)]
78
pub struct SocksHostname(String);
79

            
80
/// Provided authentication from a SOCKS handshake
81
11
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
82
#[non_exhaustive]
83
pub enum SocksAuth {
84
    /// No authentication was provided
85
    NoAuth,
86
    /// Socks4 authentication (a string) was provided.
87
    Socks4(Vec<u8>),
88
    /// Socks5 username/password authentication was provided.
89
    Username(Vec<u8>, Vec<u8>),
90
}
91

            
92
caret_int! {
93
    /// Command from the socks client telling us what to do.
94
    pub struct SocksCmd(u8) {
95
        /// Connect to a remote TCP address:port.
96
        CONNECT = 1,
97
        /// Not supported in Tor.
98
        BIND = 2,
99
        /// Not supported in Tor.
100
        UDP_ASSOCIATE = 3,
101

            
102
        /// Lookup a hostname, return an IP address. (Tor only.)
103
        RESOLVE = 0xF0,
104
        /// Lookup an IP address, return a hostname. (Tor only.)
105
        RESOLVE_PTR = 0xF1,
106
    }
107
}
108

            
109
caret_int! {
110
    /// Possible reply status values from a SOCKS5 handshake.
111
    ///
112
    /// Note that the documentation for these values is kind of scant,
113
    /// and is limited to what the RFC says.  Note also that SOCKS4
114
    /// only represents success and failure.
115
    pub struct SocksStatus(u8) {
116
        /// RFC 1928: "succeeded"
117
        SUCCEEDED = 0x00,
118
        /// RFC 1928: "general SOCKS server failure"
119
        GENERAL_FAILURE = 0x01,
120
        /// RFC 1928: "connection not allowable by ruleset"
121
        ///
122
        /// (This is the only occurrence of 'ruleset' or even 'rule'
123
        /// in RFC 1928.)
124
        NOT_ALLOWED = 0x02,
125
        /// RFC 1928: "Network unreachable"
126
        NETWORK_UNREACHABLE = 0x03,
127
        /// RFC 1928: "Host unreachable"
128
        HOST_UNREACHABLE = 0x04,
129
        /// RFC 1928: "Connection refused"
130
        CONNECTION_REFUSED = 0x05,
131
        /// RFC 1928: "TTL expired"
132
        ///
133
        /// (This is the only occurrence of 'TTL' in RFC 1928.)
134
        TTL_EXPIRED = 0x06,
135
        /// RFC 1929: "Command not supported"
136
        COMMAND_NOT_SUPPORTED = 0x07,
137
        /// RFC 1929: "Address type not supported"
138
        ADDRTYPE_NOT_SUPPORTED = 0x08,
139
    }
140
}
141

            
142
impl SocksCmd {
143
    /// Return true if this is a supported command.
144
9
    fn recognized(self) -> bool {
145
1
        matches!(
146
9
            self,
147
            SocksCmd::CONNECT | SocksCmd::RESOLVE | SocksCmd::RESOLVE_PTR
148
        )
149
9
    }
150

            
151
    /// Return true if this is a command for which we require a port.
152
1
    fn requires_port(self) -> bool {
153
        matches!(
154
1
            self,
155
            SocksCmd::CONNECT | SocksCmd::BIND | SocksCmd::UDP_ASSOCIATE
156
        )
157
1
    }
158
}
159

            
160
impl SocksStatus {
161
    /// Convert this status into a value for use with SOCKS4 or SOCKS4a.
162
2
    pub(crate) fn into_socks4_status(self) -> u8 {
163
2
        match self {
164
1
            SocksStatus::SUCCEEDED => 0x5A,
165
1
            _ => 0x5B,
166
        }
167
2
    }
168
}
169

            
170
impl TryFrom<String> for SocksHostname {
171
    type Error = Error;
172
4
    fn try_from(s: String) -> Result<SocksHostname> {
173
4
        if s.len() > 255 {
174
            Err(bad_api_usage!("hostname too long").into())
175
        } else {
176
4
            Ok(SocksHostname(s))
177
        }
178
4
    }
179
}
180

            
181
impl AsRef<str> for SocksHostname {
182
1
    fn as_ref(&self) -> &str {
183
1
        self.0.as_ref()
184
1
    }
185
}
186

            
187
impl From<SocksHostname> for String {
188
    fn from(s: SocksHostname) -> String {
189
        s.0
190
    }
191
}
192

            
193
impl SocksRequest {
194
    /// Create a SocksRequest with a given set of fields.
195
    ///
196
    /// Return an error if the inputs aren't supported or valid.
197
9
    pub(crate) fn new(
198
9
        version: SocksVersion,
199
9
        cmd: SocksCmd,
200
9
        addr: SocksAddr,
201
9
        port: u16,
202
9
        auth: SocksAuth,
203
9
    ) -> Result<Self> {
204
9
        if !cmd.recognized() {
205
1
            return Err(Error::NotImplemented);
206
8
        }
207
8
        if port == 0 && cmd.requires_port() {
208
1
            return Err(Error::Syntax);
209
7
        }
210
7

            
211
7
        Ok(SocksRequest {
212
7
            version,
213
7
            cmd,
214
7
            addr,
215
7
            port,
216
7
            auth,
217
7
        })
218
9
    }
219

            
220
    /// Return the negotiated version (4 or 5).
221
9
    pub fn version(&self) -> SocksVersion {
222
9
        self.version
223
9
    }
224

            
225
    /// Return the command that the client requested.
226
6
    pub fn command(&self) -> SocksCmd {
227
6
        self.cmd
228
6
    }
229

            
230
    /// Return the 'authentication' information from this request.
231
5
    pub fn auth(&self) -> &SocksAuth {
232
5
        &self.auth
233
5
    }
234

            
235
    /// Return the requested port.
236
8
    pub fn port(&self) -> u16 {
237
8
        self.port
238
8
    }
239

            
240
    /// Return the requested address.
241
7
    pub fn addr(&self) -> &SocksAddr {
242
7
        &self.addr
243
7
    }
244
}
245

            
246
impl fmt::Display for SocksAddr {
247
    /// Format a string (a hostname or IP address) corresponding to this
248
    /// SocksAddr.
249
8
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250
8
        match self {
251
5
            SocksAddr::Ip(a) => write!(f, "{}", a),
252
3
            SocksAddr::Hostname(h) => write!(f, "{}", h.0),
253
        }
254
8
    }
255
}
256

            
257
#[cfg(test)]
258
mod test {
259
    #![allow(clippy::unwrap_used)]
260
    use super::*;
261
    use std::convert::TryInto;
262

            
263
    #[test]
264
    fn display_sa() {
265
        let a = SocksAddr::Ip(IpAddr::V4("127.0.0.1".parse().unwrap()));
266
        assert_eq!(a.to_string(), "127.0.0.1");
267

            
268
        let a = SocksAddr::Ip(IpAddr::V6("f00::9999".parse().unwrap()));
269
        assert_eq!(a.to_string(), "f00::9999");
270

            
271
        let a = SocksAddr::Hostname("www.torproject.org".to_string().try_into().unwrap());
272
        assert_eq!(a.to_string(), "www.torproject.org");
273
    }
274

            
275
    #[test]
276
    fn ok_request() {
277
        let localhost_v4 = SocksAddr::Ip(IpAddr::V4("127.0.0.1".parse().unwrap()));
278
        let r = SocksRequest::new(
279
            SocksVersion::V4,
280
            SocksCmd::CONNECT,
281
            localhost_v4.clone(),
282
            1024,
283
            SocksAuth::NoAuth,
284
        )
285
        .unwrap();
286
        assert_eq!(r.version(), SocksVersion::V4);
287
        assert_eq!(r.command(), SocksCmd::CONNECT);
288
        assert_eq!(r.addr(), &localhost_v4);
289
        assert_eq!(r.auth(), &SocksAuth::NoAuth);
290
    }
291

            
292
    #[test]
293
    fn bad_request() {
294
        let localhost_v4 = SocksAddr::Ip(IpAddr::V4("127.0.0.1".parse().unwrap()));
295

            
296
        let e = SocksRequest::new(
297
            SocksVersion::V4,
298
            SocksCmd::BIND,
299
            localhost_v4.clone(),
300
            1024,
301
            SocksAuth::NoAuth,
302
        );
303
        assert!(matches!(e, Err(Error::NotImplemented)));
304

            
305
        let e = SocksRequest::new(
306
            SocksVersion::V4,
307
            SocksCmd::CONNECT,
308
            localhost_v4,
309
            0,
310
            SocksAuth::NoAuth,
311
        );
312
        assert!(matches!(e, Err(Error::Syntax)));
313
    }
314
}