[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [Git][tpo/applications/tor-browser][tor-browser-153.4.0esr-16.0-1] 4 commits: fixup! TB 44806: Implement the tor integration in Rust.



Title: GitLab

Pier Angelo Vendrame pushed to branch tor-browser-153.4.0esr-16.0-1 at The Tor Project / Applications / Tor Browser

Commits:

  • e8c774f3
    by Elena at 2026-09-28T19:10:00+02:00
    fixup! TB 44806: Implement the tor integration in Rust.
    
    TB 44930: Implement the commands on the Rust control port
    
    Implemented bridge line parsing.
    
  • 1b15f99e
    by Elena at 2026-09-28T19:10:00+02:00
    fixup! TB 44806: Implement the tor integration in Rust.
    
    TB 44930: Implement the commands on the Rust control port
    
    Implemented the parsers to get PTs.
    
  • eb5427bb
    by Elena at 2026-09-28T19:10:01+02:00
    fixup! TB 44806: Implement the tor integration in Rust.
    
    TB 44930: Implement the commands on the Rust control port
    
    Implemented the GETCONF commands.
    
  • 343a6dfa
    by Elena at 2026-09-28T19:10:02+02:00
    fixup! TB 44806: Implement the tor integration in Rust.
    
    TB 44930: Implement the commands on the Rust control port
    
    Implemented the SETCONF.
    

12 changed files:

Changes:

  • Cargo.lock
    ... ... @@ -8173,8 +8173,12 @@ version = "0.1.0"
    8173 8173
     dependencies = [
    
    8174 8174
      "bytes",
    
    8175 8175
      "hex",
    
    8176
    + "itoa",
    
    8177
    + "lazy_static",
    
    8176 8178
      "log",
    
    8177 8179
      "memchr",
    
    8180
    + "regex",
    
    8181
    + "serde",
    
    8178 8182
      "thiserror 2.0.12",
    
    8179 8183
     ]
    
    8180 8184
     
    

  • toolkit/components/tor-integration/tor_provider/Cargo.toml
    ... ... @@ -7,6 +7,10 @@ edition = "2021"
    7 7
     [dependencies]
    
    8 8
     bytes = "1.4.0"
    
    9 9
     hex = "0.4.3"
    
    10
    +itoa = "1.0.15"
    
    11
    +lazy_static = "1"
    
    10 12
     log = "0.4"
    
    11 13
     memchr = "2.7.4"
    
    14
    +regex = "1"
    
    15
    +serde = { version = "1.0", features = ["derive"] }
    
    12 16
     thiserror = "2"

  • toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/get_conf.rs
    1
    +// Licensed under the Apache License, Version 2.0,
    
    2
    +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
    
    3
    +// <http://opensource.org/licenses/MIT>, at your option. This file may not be
    
    4
    +// copied, modified, or distributed except according to those terms.
    
    5
    +
    
    6
    +use std::iter::once;
    
    7
    +
    
    8
    +use super::Command;
    
    9
    +use crate::ctor::{
    
    10
    +    controller::{parsers::*, types::*, ControllerError},
    
    11
    +    reply_parser::{DetailReplyLine, Reply},
    
    12
    +};
    
    13
    +
    
    14
    +pub fn bridges() -> Result<Command<Vec<Bridge>>, ControllerError> {
    
    15
    +    get_conf("Bridge", |line| {
    
    16
    +        parse_bridge_line(line).map_err(|e| ControllerError::MalformedReply(e.to_string()))
    
    17
    +    })
    
    18
    +}
    
    19
    +
    
    20
    +pub fn client_transport_plugins() -> Result<Command<Vec<PluggableTransport>>, ControllerError> {
    
    21
    +    get_conf("ClientTransportPlugin", parse_client_transport_plugin)
    
    22
    +}
    
    23
    +
    
    24
    +fn get_conf<F, T>(key: &'static str, converter: F) -> Result<Command<Vec<T>>, ControllerError>
    
    25
    +where
    
    26
    +    F: Fn(&[u8]) -> Result<T, ControllerError> + 'static,
    
    27
    +{
    
    28
    +    Ok(Command {
    
    29
    +        command: format!("GETCONF {}\r\n", key),
    
    30
    +        handler: Box::new(move |reply| parse_get_conf(key.as_bytes(), &converter, reply)),
    
    31
    +    })
    
    32
    +}
    
    33
    +
    
    34
    +/// Parse and convert the output of the GETCONF command for a single keyword.
    
    35
    +///
    
    36
    +/// We currently query and parse only one key at the time, but this parser
    
    37
    +/// tolerates keywords we did not ask (it silently ignores them).
    
    38
    +fn parse_get_conf<F, T>(key: &[u8], converter: &F, reply: Reply) -> Result<Vec<T>, ControllerError>
    
    39
    +where
    
    40
    +    F: Fn(&[u8]) -> Result<T, ControllerError> + 'static,
    
    41
    +{
    
    42
    +    if let Some(e) = ControllerError::from_reply(&reply) {
    
    43
    +        return Err(e);
    
    44
    +    }
    
    45
    +
    
    46
    +    let check_code = |code| {
    
    47
    +        if code != 250 {
    
    48
    +            Err(ControllerError::MalformedReply(format!(
    
    49
    +                "Expected the 250 status code, got '{}'",
    
    50
    +                code
    
    51
    +            )))
    
    52
    +        } else {
    
    53
    +            Ok(())
    
    54
    +        }
    
    55
    +    };
    
    56
    +
    
    57
    +    check_code(reply.end_line().code)?;
    
    58
    +
    
    59
    +    // Special case: "default value semantically different from an empty string"
    
    60
    +    // is just "250 KEYWORD CRLF" without '=' (see the specs).
    
    61
    +    if reply.details().is_empty() && reply.end_line().line.eq_ignore_ascii_case(key) {
    
    62
    +        return Ok(Vec::new());
    
    63
    +    }
    
    64
    +
    
    65
    +    // Key followed by '='.
    
    66
    +    let prefix_len = key.len() + 1;
    
    67
    +
    
    68
    +    let details = reply.details().iter().map(|l| match l {
    
    69
    +        DetailReplyLine::MidReplyLine { code, line } => {
    
    70
    +            check_code(*code)?;
    
    71
    +            Ok(line.as_ref())
    
    72
    +        }
    
    73
    +        DetailReplyLine::DataReplyLine { .. } => Err(ControllerError::MalformedReply(
    
    74
    +            String::from("GETCONF does not allow data lines"),
    
    75
    +        )),
    
    76
    +    });
    
    77
    +
    
    78
    +    let all_lines = details.chain(once(Ok(reply.end_line().line.as_ref())));
    
    79
    +
    
    80
    +    all_lines
    
    81
    +        .filter_map(|line| match line {
    
    82
    +            Ok(l) => {
    
    83
    +                if l.len() >= prefix_len
    
    84
    +                    && l[0..key.len()].eq_ignore_ascii_case(key)
    
    85
    +                    && l[key.len()] == b'='
    
    86
    +                {
    
    87
    +                    Some(converter(&l[prefix_len..]))
    
    88
    +                } else {
    
    89
    +                    None
    
    90
    +                }
    
    91
    +            }
    
    92
    +            Err(e) => Some(Err(e)),
    
    93
    +        })
    
    94
    +        .collect()
    
    95
    +}
    
    96
    +
    
    97
    +#[cfg(test)]
    
    98
    +mod tests {
    
    99
    +    use std::assert_matches;
    
    100
    +
    
    101
    +    use super::*;
    
    102
    +    use crate::ctor::reply_parser::make_reply;
    
    103
    +
    
    104
    +    fn converter(v: &[u8]) -> Result<String, ControllerError> {
    
    105
    +        Ok(String::from_utf8_lossy(v).into_owned())
    
    106
    +    }
    
    107
    +
    
    108
    +    #[test]
    
    109
    +    fn simple() {
    
    110
    +        assert_eq!(
    
    111
    +            parse_get_conf(b"test", &converter, make_reply(b"250 test=value\r\n")).unwrap(),
    
    112
    +            vec!["value"]
    
    113
    +        );
    
    114
    +    }
    
    115
    +
    
    116
    +    #[test]
    
    117
    +    fn case_insensitivity() {
    
    118
    +        assert_eq!(
    
    119
    +            parse_get_conf(b"test", &converter, make_reply(b"250 TEST=value\r\n")).unwrap(),
    
    120
    +            vec!["value"]
    
    121
    +        );
    
    122
    +        assert_eq!(
    
    123
    +            parse_get_conf(b"Test", &converter, make_reply(b"250 TEST=value\r\n")).unwrap(),
    
    124
    +            vec!["value"]
    
    125
    +        );
    
    126
    +        assert_eq!(
    
    127
    +            parse_get_conf(b"TeSt", &converter, make_reply(b"250 tESt=value\r\n")).unwrap(),
    
    128
    +            vec!["value"]
    
    129
    +        );
    
    130
    +    }
    
    131
    +
    
    132
    +    #[test]
    
    133
    +    fn array() {
    
    134
    +        assert_eq!(
    
    135
    +            parse_get_conf(
    
    136
    +                b"test",
    
    137
    +                &converter,
    
    138
    +                make_reply(b"250-test=value 1\r\n250 test=value 2\r\n")
    
    139
    +            )
    
    140
    +            .unwrap(),
    
    141
    +            vec!["value 1", "value 2"]
    
    142
    +        );
    
    143
    +
    
    144
    +        assert_eq!(
    
    145
    +            parse_get_conf(
    
    146
    +                b"Test",
    
    147
    +                &converter,
    
    148
    +                make_reply(b"250-test=VALUE 1\r\n250 TEST=value 2\r\n")
    
    149
    +            )
    
    150
    +            .unwrap(),
    
    151
    +            vec!["VALUE 1", "value 2"]
    
    152
    +        );
    
    153
    +    }
    
    154
    +
    
    155
    +    #[test]
    
    156
    +    fn empty() {
    
    157
    +        assert!(
    
    158
    +            parse_get_conf(b"test", &converter, make_reply(b"250 test\r\n"))
    
    159
    +                .unwrap()
    
    160
    +                .is_empty(),
    
    161
    +        );
    
    162
    +        assert!(
    
    163
    +            parse_get_conf(b"Test", &converter, make_reply(b"250 TEST\r\n"))
    
    164
    +                .unwrap()
    
    165
    +                .is_empty(),
    
    166
    +        );
    
    167
    +    }
    
    168
    +
    
    169
    +    #[test]
    
    170
    +    fn other_keys() {
    
    171
    +        assert_eq!(
    
    172
    +            parse_get_conf(
    
    173
    +                b"test",
    
    174
    +                &converter,
    
    175
    +                make_reply(b"250-test 1=a\r\n250-test=b\r\n250-test=c\r\n250 test 2=d\r\n")
    
    176
    +            )
    
    177
    +            .unwrap(),
    
    178
    +            vec!["b", "c"]
    
    179
    +        );
    
    180
    +
    
    181
    +        assert!(parse_get_conf(
    
    182
    +            b"C",
    
    183
    +            &converter,
    
    184
    +            make_reply(b"250-A=Apple\r\n250 B=Banana\r\n"),
    
    185
    +        )
    
    186
    +        .unwrap()
    
    187
    +        .is_empty(),);
    
    188
    +    }
    
    189
    +
    
    190
    +    #[test]
    
    191
    +    fn tor_error() {
    
    192
    +        assert_eq!(
    
    193
    +            parse_get_conf(
    
    194
    +                b"test",
    
    195
    +                &converter,
    
    196
    +                make_reply(b"552 Unrecognized configuration key \"test\"\r\n")
    
    197
    +            )
    
    198
    +            .unwrap_err(),
    
    199
    +            ControllerError::TorError {
    
    200
    +                code: 552,
    
    201
    +                message: String::from("Unrecognized configuration key \"test\"")
    
    202
    +            }
    
    203
    +        );
    
    204
    +    }
    
    205
    +
    
    206
    +    #[test]
    
    207
    +    fn no_250() {
    
    208
    +        assert_matches!(
    
    209
    +            parse_get_conf(
    
    210
    +                b"test",
    
    211
    +                &converter,
    
    212
    +                make_reply(b"251 test=value\r\n")
    
    213
    +            )
    
    214
    +            .unwrap_err(),
    
    215
    +            ControllerError::MalformedReply(_)
    
    216
    +        );
    
    217
    +        assert_matches!(
    
    218
    +            parse_get_conf(
    
    219
    +                b"test",
    
    220
    +                &converter,
    
    221
    +                make_reply(b"251-test1=value\r\n250 test2=value2\r\n")
    
    222
    +            )
    
    223
    +            .unwrap_err(),
    
    224
    +            ControllerError::MalformedReply(_)
    
    225
    +        );
    
    226
    +        assert_matches!(
    
    227
    +            parse_get_conf(
    
    228
    +                b"test",
    
    229
    +                &converter,
    
    230
    +                make_reply(b"250-test1=value\r\n251 test1=value2\r\n")
    
    231
    +            )
    
    232
    +            .unwrap_err(),
    
    233
    +            ControllerError::MalformedReply(_)
    
    234
    +        );
    
    235
    +    }
    
    236
    +
    
    237
    +    #[test]
    
    238
    +    fn no_multiline_getconf() {
    
    239
    +        assert_matches!(
    
    240
    +            parse_get_conf(
    
    241
    +                b"test",
    
    242
    +                &converter,
    
    243
    +                make_reply(b"250+test=\r\nsome value\r\n.\r\n250 OK\r\n")
    
    244
    +            )
    
    245
    +            .unwrap_err(),
    
    246
    +            ControllerError::MalformedReply(_)
    
    247
    +        );
    
    248
    +    }
    
    249
    +
    
    250
    +    #[test]
    
    251
    +    fn converter_error() {
    
    252
    +        assert_eq!(
    
    253
    +            parse_get_conf::<_, String>(
    
    254
    +                b"test",
    
    255
    +                &(|_| Err(ControllerError::WrongFormat(String::from(
    
    256
    +                    "Something is fishy"
    
    257
    +                )))),
    
    258
    +                make_reply(b"250 test=value\r\n")
    
    259
    +            )
    
    260
    +            .unwrap_err(),
    
    261
    +            ControllerError::WrongFormat(String::from("Something is fishy"))
    
    262
    +        );
    
    263
    +    }
    
    264
    +}

  • toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/mod.rs
    ... ... @@ -5,8 +5,10 @@
    5 5
     
    
    6 6
     mod authenticate;
    
    7 7
     mod command;
    
    8
    +pub mod get_conf;
    
    8 9
     mod reset_conf;
    
    9 10
     mod save_conf;
    
    11
    +mod set_conf;
    
    10 12
     mod set_events;
    
    11 13
     mod signal;
    
    12 14
     mod take_ownership;
    
    ... ... @@ -15,6 +17,7 @@ pub use authenticate::authenticate;
    15 17
     pub use command::Command;
    
    16 18
     pub use reset_conf::reset_owning_controller_process;
    
    17 19
     pub use save_conf::save_conf;
    
    20
    +pub use set_conf::set_conf;
    
    18 21
     pub use set_events::set_events;
    
    19 22
     pub use signal::signal_newnym;
    
    20 23
     pub use take_ownership::take_ownership;

  • toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/set_conf.rs
    1
    +// Licensed under the Apache License, Version 2.0,
    
    2
    +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
    
    3
    +// <http://opensource.org/licenses/MIT>, at your option. This file may not be
    
    4
    +// copied, modified, or distributed except according to those terms.
    
    5
    +
    
    6
    +use lazy_static::lazy_static;
    
    7
    +use regex::Regex;
    
    8
    +
    
    9
    +use super::Command;
    
    10
    +use crate::ctor::controller::{parsers::*, ConfValue, ControllerError};
    
    11
    +
    
    12
    +lazy_static! {
    
    13
    +    // The SETCONF command's documentation lists `keyword` for keys, and
    
    14
    +    // keyword is defined as `1*ALPHA`, so it should not include numbers.
    
    15
    +    // However, as a matter of fact, several settings (including the ones we
    
    16
    +    // use) have numbers, therefore we also accept them.
    
    17
    +    static ref KEY_REGEX: Regex =
    
    18
    +        Regex::new("^[a-zA-Z0-9_]+$").expect("The regex is hardcoded, it should be good.");
    
    19
    +}
    
    20
    +
    
    21
    +pub fn set_conf(values: &[(&str, ConfValue)]) -> Result<Command<u16>, ControllerError> {
    
    22
    +    Ok(Command {
    
    23
    +        command: make_setconf_command(values)?,
    
    24
    +        handler: Box::new(parse_ack),
    
    25
    +    })
    
    26
    +}
    
    27
    +
    
    28
    +fn make_setconf_command(values: &[(&str, ConfValue)]) -> Result<String, ControllerError> {
    
    29
    +    let mut res = String::from("SETCONF");
    
    30
    +    res.reserve(256);
    
    31
    +    for (key, value) in values.iter() {
    
    32
    +        if !KEY_REGEX.is_match(key) {
    
    33
    +            return Err(ControllerError::InvalidArgument(format!(
    
    34
    +                "invalid key: '{}'",
    
    35
    +                key
    
    36
    +            )));
    
    37
    +        }
    
    38
    +        match value {
    
    39
    +            ConfValue::Bool(v) => push_kv(key, if *v { "1" } else { "0" }, false, &mut res),
    
    40
    +            ConfValue::Int(i) => {
    
    41
    +                push_k(key, &mut res);
    
    42
    +                res.push('=');
    
    43
    +                let mut buf = itoa::Buffer::new();
    
    44
    +                res.push_str(buf.format(*i));
    
    45
    +            }
    
    46
    +            ConfValue::String(s) => push_kv(key, s, true, &mut res),
    
    47
    +            ConfValue::Array(a) => {
    
    48
    +                if a.is_empty() {
    
    49
    +                    push_k(key, &mut res);
    
    50
    +                } else {
    
    51
    +                    for v in a.iter() {
    
    52
    +                        push_kv(key, v, true, &mut res);
    
    53
    +                    }
    
    54
    +                }
    
    55
    +            }
    
    56
    +            ConfValue::Null => push_k(key, &mut res),
    
    57
    +        }
    
    58
    +    }
    
    59
    +    res.push_str("\r\n");
    
    60
    +    Ok(res)
    
    61
    +}
    
    62
    +
    
    63
    +fn push_k(key: &str, dest: &mut String) {
    
    64
    +    dest.push(' ');
    
    65
    +    dest.push_str(key);
    
    66
    +}
    
    67
    +
    
    68
    +fn push_kv(key: &str, val: &str, escape: bool, dest: &mut String) {
    
    69
    +    push_k(key, dest);
    
    70
    +    dest.push('=');
    
    71
    +    if escape {
    
    72
    +        tor_escape_into(val, dest);
    
    73
    +    } else {
    
    74
    +        dest.push_str(val);
    
    75
    +    }
    
    76
    +}
    
    77
    +
    
    78
    +#[cfg(test)]
    
    79
    +mod tests {
    
    80
    +    use std::assert_matches;
    
    81
    +
    
    82
    +    use super::*;
    
    83
    +
    
    84
    +    #[test]
    
    85
    +    fn test_bool() {
    
    86
    +        let vals = &[
    
    87
    +            ("test", ConfValue::Bool(true)),
    
    88
    +            ("AA", ConfValue::Bool(false)),
    
    89
    +        ];
    
    90
    +        assert_eq!(
    
    91
    +            make_setconf_command(vals).unwrap(),
    
    92
    +            "SETCONF test=1 AA=0\r\n"
    
    93
    +        );
    
    94
    +    }
    
    95
    +
    
    96
    +    #[test]
    
    97
    +    fn test_int() {
    
    98
    +        let vals = &[
    
    99
    +            ("aaa", ConfValue::Int(42)),
    
    100
    +            ("bBbB", ConfValue::Int(0)),
    
    101
    +            ("cc", ConfValue::Int(-3)),
    
    102
    +        ];
    
    103
    +        assert_eq!(
    
    104
    +            make_setconf_command(vals).unwrap(),
    
    105
    +            "SETCONF aaa=42 bBbB=0 cc=-3\r\n"
    
    106
    +        );
    
    107
    +    }
    
    108
    +
    
    109
    +    #[test]
    
    110
    +    fn test_string() {
    
    111
    +        let vals = &[("Test123", ConfValue::String("a b\\c"))];
    
    112
    +        assert_eq!(
    
    113
    +            make_setconf_command(vals).unwrap(),
    
    114
    +            "SETCONF Test123=\"a b\\\\c\"\r\n"
    
    115
    +        );
    
    116
    +    }
    
    117
    +
    
    118
    +    #[test]
    
    119
    +    fn test_array() {
    
    120
    +        let vals = &[
    
    121
    +            ("empty", ConfValue::Array(vec![])),
    
    122
    +            ("list", ConfValue::Array(vec!["one", "two two"])),
    
    123
    +        ];
    
    124
    +        assert_eq!(
    
    125
    +            make_setconf_command(vals).unwrap(),
    
    126
    +            "SETCONF empty list=\"one\" list=\"two two\"\r\n"
    
    127
    +        );
    
    128
    +    }
    
    129
    +
    
    130
    +    #[test]
    
    131
    +    fn test_null() {
    
    132
    +        let vals = &[("test", ConfValue::Null)];
    
    133
    +        assert_eq!(make_setconf_command(vals).unwrap(), "SETCONF test\r\n");
    
    134
    +    }
    
    135
    +
    
    136
    +    #[test]
    
    137
    +    fn mixed_values() {
    
    138
    +        let vals = &[
    
    139
    +            ("b", ConfValue::Bool(false)),
    
    140
    +            ("a", ConfValue::Int(-7)),
    
    141
    +            ("c", ConfValue::String("x y")),
    
    142
    +            ("d", ConfValue::Null),
    
    143
    +        ];
    
    144
    +        assert_eq!(
    
    145
    +            make_setconf_command(vals).unwrap(),
    
    146
    +            "SETCONF b=0 a=-7 c=\"x y\" d\r\n"
    
    147
    +        );
    
    148
    +    }
    
    149
    +
    
    150
    +    #[test]
    
    151
    +    fn repeated_key() {
    
    152
    +        let vals = &[
    
    153
    +            ("bridge", ConfValue::String("obfs4")),
    
    154
    +            ("bridge", ConfValue::String("snowflake")),
    
    155
    +            ("bridge", ConfValue::String("meek")),
    
    156
    +        ];
    
    157
    +        assert_eq!(
    
    158
    +            make_setconf_command(vals).unwrap(),
    
    159
    +            "SETCONF bridge=\"obfs4\" bridge=\"snowflake\" bridge=\"meek\"\r\n"
    
    160
    +        );
    
    161
    +    }
    
    162
    +
    
    163
    +    #[test]
    
    164
    +    fn realistic_configs() {
    
    165
    +        // No bridges
    
    166
    +        assert_eq!(
    
    167
    +            make_setconf_command(&[
    
    168
    +                ("UseBridges", ConfValue::Bool(false)),
    
    169
    +                ("Bridge", ConfValue::Null),
    
    170
    +            ])
    
    171
    +            .unwrap(),
    
    172
    +            "SETCONF UseBridges=0 Bridge\r\n"
    
    173
    +        );
    
    174
    +
    
    175
    +        // One bridge
    
    176
    +        assert_eq!(
    
    177
    +            make_setconf_command(&[
    
    178
    +                ("UseBridges", ConfValue::Bool(true)),
    
    179
    +                ("Bridge", ConfValue::Array(vec!["1.2.3.4:443"]))
    
    180
    +            ])
    
    181
    +            .unwrap(),
    
    182
    +            "SETCONF UseBridges=1 Bridge=\"1.2.3.4:443\"\r\n"
    
    183
    +        );
    
    184
    +
    
    185
    +        // Some bridges
    
    186
    +        assert_eq!(
    
    187
    +            make_setconf_command(&[
    
    188
    +                ("UseBridges", ConfValue::Bool(true)),
    
    189
    +                (
    
    190
    +                    "Bridge",
    
    191
    +                    ConfValue::Array(vec![
    
    192
    +                        "1.2.3.4:443",
    
    193
    +                        "obfs4 5.6.7.8:9999 0123456789012345678901234567890123456789 iat-mode=0",
    
    194
    +                    ]),
    
    195
    +                ),
    
    196
    +            ])
    
    197
    +            .unwrap(),
    
    198
    +            concat!(
    
    199
    +                "SETCONF ",
    
    200
    +                "UseBridges=1 ",
    
    201
    +                "Bridge=\"1.2.3.4:443\" ",
    
    202
    +                "Bridge=\"obfs4 5.6.7.8:9999 0123456789012345678901234567890123456789 iat-mode=0\"",
    
    203
    +                "\r\n"
    
    204
    +            )
    
    205
    +        );
    
    206
    +
    
    207
    +        // No firewall
    
    208
    +        assert_eq!(
    
    209
    +            make_setconf_command(&[("ReachableAddresses", ConfValue::Null)]).unwrap(),
    
    210
    +            "SETCONF ReachableAddresses\r\n"
    
    211
    +        );
    
    212
    +
    
    213
    +        // Firewall that only allows 80 and 443
    
    214
    +        assert_eq!(
    
    215
    +            make_setconf_command(&[("ReachableAddresses", ConfValue::String("*:80,*:443"))])
    
    216
    +                .unwrap(),
    
    217
    +            "SETCONF ReachableAddresses=\"*:80,*:443\"\r\n"
    
    218
    +        );
    
    219
    +
    
    220
    +        // No proxies
    
    221
    +        assert_eq!(
    
    222
    +            make_setconf_command(&[
    
    223
    +                ("Socks4Proxy", ConfValue::Null),
    
    224
    +                ("Socks5Proxy", ConfValue::Null),
    
    225
    +                ("Socks5ProxyUsername", ConfValue::Null),
    
    226
    +                ("Socks5ProxyPassword", ConfValue::Null),
    
    227
    +                ("HTTPSProxy", ConfValue::Null),
    
    228
    +                ("HTTPSProxyAuthenticator", ConfValue::Null),
    
    229
    +            ])
    
    230
    +            .unwrap(),
    
    231
    +            "SETCONF Socks4Proxy Socks5Proxy Socks5ProxyUsername Socks5ProxyPassword HTTPSProxy HTTPSProxyAuthenticator\r\n"
    
    232
    +        );
    
    233
    +    }
    
    234
    +
    
    235
    +    #[test]
    
    236
    +    fn invalid_key() {
    
    237
    +        let val = &[("invalid\r\nkey", ConfValue::Null)];
    
    238
    +        assert_matches!(
    
    239
    +            make_setconf_command(val).unwrap_err(),
    
    240
    +            ControllerError::InvalidArgument(_)
    
    241
    +        );
    
    242
    +    }
    
    243
    +}

  • toolkit/components/tor-integration/tor_provider/src/ctor/controller/controller.rs
    ... ... @@ -8,6 +8,7 @@ use bytes::Bytes;
    8 8
     use super::{
    
    9 9
         commands::{self, Command},
    
    10 10
         error::ControllerError,
    
    11
    +    types::*,
    
    11 12
     };
    
    12 13
     use crate::ctor::{
    
    13 14
         control_port::{ControlPortInterface, ControlSocketError},
    
    ... ... @@ -102,16 +103,35 @@ impl<CP: ControlPortInterface> TorController<CP> {
    102 103
     
    
    103 104
         // Connection management
    
    104 105
     
    
    106
    +    pub fn set_conf(
    
    107
    +        &self,
    
    108
    +        values: &[(&str, ConfValue)],
    
    109
    +        handler: Box<dyn FnOnce(Result<u16, ControllerError>)>,
    
    110
    +    ) {
    
    111
    +        self.send_command(commands::set_conf(values), handler);
    
    112
    +    }
    
    113
    +
    
    105 114
         pub fn save_conf(&self, handler: Box<dyn FnOnce(Result<u16, ControllerError>)>) {
    
    106 115
             self.send_command(commands::save_conf(), handler);
    
    107 116
         }
    
    108 117
     
    
    118
    +    pub fn get_bridges(&self, handler: Box<dyn FnOnce(Result<Vec<Bridge>, ControllerError>)>) {
    
    119
    +        self.send_command(commands::get_conf::bridges(), handler);
    
    120
    +    }
    
    121
    +
    
    109 122
         // Circuit display
    
    110 123
     
    
    111 124
         // Onion authentication
    
    112 125
     
    
    113 126
         // Miscellaneous
    
    114 127
     
    
    128
    +    pub fn get_pluggable_transports(
    
    129
    +        &self,
    
    130
    +        handler: Box<dyn FnOnce(Result<Vec<PluggableTransport>, ControllerError>)>,
    
    131
    +    ) {
    
    132
    +        self.send_command(commands::get_conf::client_transport_plugins(), handler);
    
    133
    +    }
    
    134
    +
    
    115 135
         pub fn signal_newnym(&self, handler: Box<dyn FnOnce(Result<u16, ControllerError>)>) {
    
    116 136
             self.send_command(commands::signal_newnym(), handler);
    
    117 137
         }
    

  • toolkit/components/tor-integration/tor_provider/src/ctor/controller/error.rs
    ... ... @@ -32,6 +32,8 @@ pub enum ControllerError {
    32 32
         KeyNotFound(String),
    
    33 33
         #[error("malformed reply: {0}")]
    
    34 34
         MalformedReply(String),
    
    35
    +    #[error("the argument passed to the command was not valid: {0}")]
    
    36
    +    InvalidArgument(String),
    
    35 37
     }
    
    36 38
     
    
    37 39
     impl ControllerError {
    

  • toolkit/components/tor-integration/tor_provider/src/ctor/controller/mod.rs
    ... ... @@ -7,6 +7,8 @@ mod commands;
    7 7
     mod controller;
    
    8 8
     mod error;
    
    9 9
     mod parsers;
    
    10
    +mod types;
    
    10 11
     
    
    11 12
     pub use controller::*;
    
    12 13
     pub use error::*;
    
    14
    +pub use types::*;

  • toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/bridge_line.rs
    1
    +// Licensed under the Apache License, Version 2.0,
    
    2
    +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
    
    3
    +// <http://opensource.org/licenses/MIT>, at your option. This file may not be
    
    4
    +// copied, modified, or distributed except according to those terms.
    
    5
    +
    
    6
    +use hex;
    
    7
    +use lazy_static::lazy_static;
    
    8
    +use regex::bytes::Regex;
    
    9
    +use std::net::SocketAddr;
    
    10
    +use thiserror::Error;
    
    11
    +
    
    12
    +use crate::ctor::controller::types::Bridge;
    
    13
    +
    
    14
    +lazy_static! {
    
    15
    +    // In C-tor's parse_bridge_line (src/app/config/config.c), the transport is
    
    16
    +    // parsed as a potential C identifier (starts with letter/underscores and
    
    17
    +    // can contain any additional number of letters, numbers and underscores,
    
    18
    +    // as defined in src/lib/string/util_string.c).
    
    19
    +    //
    
    20
    +    // The logic to distinguish between the fingerprint and args is the
    
    21
    +    // following one (from C-tor's source code):
    
    22
    +    //  - if the bridge does not use a transport, it cannot have arguments.
    
    23
    +    //    If after the address there is anything, it must be the fingerprint;
    
    24
    +    //  - otherwise, if there is a = sign, there is not a fingerprint.
    
    25
    +    //
    
    26
    +    // We do not use arguments in any way, so we do not enforce their format.
    
    27
    +    // However, if we see a valid hex sequence, we still need to validate its
    
    28
    +    // length, to make sure it actually is a fingerprint.
    
    29
    +    //
    
    30
    +    // We are not strict on spaces in case this is used with data provided
    
    31
    +    // directly by users.
    
    32
    +    static ref BRIDGE_REGEX: Regex = Regex::new(r"^ *(?:(?<transport>[A-Za-z_][A-Za-z0-9_]*) +)?(?<addr>[0-9a-fA-F\.\[\]\:]+:\d{1,5})(?: +(?<fingerprint>[0-9a-fA-F]+))?(?: +(?<args>.+?))? *$")
    
    33
    +        .expect("The regex is hardcoded, it should be good.");
    
    34
    +}
    
    35
    +
    
    36
    +#[derive(Error, Debug, Clone, PartialEq, Eq)]
    
    37
    +pub enum BridgeParseError {
    
    38
    +    #[error("the bridge line has a wrong format and could not be parsed")]
    
    39
    +    InvalidFormat,
    
    40
    +    #[error("invalid address")]
    
    41
    +    InvalidAddress,
    
    42
    +    #[error("port 0 is not valid for bridges")]
    
    43
    +    InvalidPort,
    
    44
    +    #[error("invalid length of the bridge fingerprint")]
    
    45
    +    InvalidFingerprint,
    
    46
    +    #[error("arguments can be supplied only when a transport is in use")]
    
    47
    +    ArgsWithoutTransport,
    
    48
    +}
    
    49
    +
    
    50
    +pub fn parse_bridge_line(value: &[u8]) -> Result<Bridge, BridgeParseError> {
    
    51
    +    let caps = BRIDGE_REGEX
    
    52
    +        .captures(value)
    
    53
    +        .ok_or(BridgeParseError::InvalidFormat)?;
    
    54
    +
    
    55
    +    let transport = caps
    
    56
    +        .name("transport")
    
    57
    +        .map(|m| String::from_utf8_lossy(m.as_bytes()).into_owned());
    
    58
    +
    
    59
    +    let address: SocketAddr = caps
    
    60
    +        .name("addr")
    
    61
    +        .and_then(|a| str::from_utf8(a.as_bytes()).ok())
    
    62
    +        // This cannot really happen since the regex matched...
    
    63
    +        .ok_or(BridgeParseError::InvalidFormat)?
    
    64
    +        .parse()
    
    65
    +        // Rust validation is very generic, so it is not even worth to
    
    66
    +        // include in our error.
    
    67
    +        .map_err(|_| BridgeParseError::InvalidAddress)?;
    
    68
    +    if address.port() == 0 {
    
    69
    +        return Err(BridgeParseError::InvalidPort);
    
    70
    +    }
    
    71
    +
    
    72
    +    let fingerprint = caps
    
    73
    +        .name("fingerprint")
    
    74
    +        .and_then(|m| {
    
    75
    +            let mut fp = [0u8; 20];
    
    76
    +            // We match only hex characters in the regex, so this should error
    
    77
    +            // only if the size is wrong.
    
    78
    +            match hex::decode_to_slice(m.as_bytes(), &mut fp[..]) {
    
    79
    +                Ok(_) => Some(Ok(fp)),
    
    80
    +                Err(_) => Some(Err(BridgeParseError::InvalidFingerprint)),
    
    81
    +            }
    
    82
    +        })
    
    83
    +        .transpose()?;
    
    84
    +
    
    85
    +    let args = caps.name("args");
    
    86
    +    if transport.is_none() && args.is_some() {
    
    87
    +        // src/app/config/config.c, parse_bridge_line: "If transports are
    
    88
    +        // disabled, next field must be a fingerprint.".
    
    89
    +        return Err(BridgeParseError::ArgsWithoutTransport);
    
    90
    +    }
    
    91
    +
    
    92
    +    Ok(Bridge {
    
    93
    +        transport,
    
    94
    +        address,
    
    95
    +        fingerprint,
    
    96
    +        args: args.map(|m| m.as_bytes().to_vec()),
    
    97
    +    })
    
    98
    +}
    
    99
    +
    
    100
    +#[cfg(test)]
    
    101
    +mod tests {
    
    102
    +    use std::{
    
    103
    +        assert_eq,
    
    104
    +        net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6},
    
    105
    +    };
    
    106
    +
    
    107
    +    use super::*;
    
    108
    +
    
    109
    +    #[test]
    
    110
    +    fn vanilla() {
    
    111
    +        {
    
    112
    +            let bridge = parse_bridge_line(b"192.168.1.4:443").unwrap();
    
    113
    +            assert_eq!(bridge.transport, None);
    
    114
    +            assert_eq!(
    
    115
    +                bridge.address,
    
    116
    +                SocketAddrV4::new(Ipv4Addr::new(192, 168, 1, 4), 443).into()
    
    117
    +            );
    
    118
    +            assert_eq!(bridge.fingerprint, None);
    
    119
    +            assert_eq!(bridge.args, None);
    
    120
    +        }
    
    121
    +        {
    
    122
    +            let bridge = parse_bridge_line(b"[dead:BEEF::1234]:443").unwrap();
    
    123
    +            assert_eq!(bridge.transport, None);
    
    124
    +            assert_eq!(
    
    125
    +                bridge.address,
    
    126
    +                SocketAddrV6::new(
    
    127
    +                    Ipv6Addr::new(0xdead, 0xbeef, 0, 0, 0, 0, 0, 0x1234),
    
    128
    +                    443,
    
    129
    +                    0,
    
    130
    +                    0
    
    131
    +                )
    
    132
    +                .into()
    
    133
    +            );
    
    134
    +            assert_eq!(bridge.fingerprint, None);
    
    135
    +            assert_eq!(bridge.args, None);
    
    136
    +        }
    
    137
    +    }
    
    138
    +
    
    139
    +    #[test]
    
    140
    +    fn transport() {
    
    141
    +        {
    
    142
    +            let bridge = parse_bridge_line(b"test 1.2.3.4:567").unwrap();
    
    143
    +            assert_eq!(bridge.transport, Some(String::from("test")));
    
    144
    +            assert_eq!(
    
    145
    +                bridge.address,
    
    146
    +                SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 567).into()
    
    147
    +            );
    
    148
    +            assert_eq!(bridge.fingerprint, None);
    
    149
    +            assert_eq!(bridge.args, None);
    
    150
    +        }
    
    151
    +        {
    
    152
    +            let bridge = parse_bridge_line(b"test [cafe:CAFE::BaBe]:567").unwrap();
    
    153
    +            assert_eq!(bridge.transport, Some(String::from("test")));
    
    154
    +            assert_eq!(
    
    155
    +                bridge.address,
    
    156
    +                SocketAddrV6::new(
    
    157
    +                    Ipv6Addr::new(0xcafe, 0xcafe, 0, 0, 0, 0, 0, 0xbabe),
    
    158
    +                    567,
    
    159
    +                    0,
    
    160
    +                    0
    
    161
    +                )
    
    162
    +                .into()
    
    163
    +            );
    
    164
    +            assert_eq!(bridge.fingerprint, None);
    
    165
    +            assert_eq!(bridge.args, None);
    
    166
    +        }
    
    167
    +    }
    
    168
    +
    
    169
    +    #[test]
    
    170
    +    fn transport_fingerprint() {
    
    171
    +        {
    
    172
    +            let bridge = parse_bridge_line(
    
    173
    +                b"obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D",
    
    174
    +            )
    
    175
    +            .unwrap();
    
    176
    +            assert_eq!(bridge.transport, Some(String::from("obfs4")));
    
    177
    +            assert_eq!(
    
    178
    +                bridge.address,
    
    179
    +                SocketAddrV4::new(Ipv4Addr::new(37, 218, 245, 14), 38224).into()
    
    180
    +            );
    
    181
    +            assert_eq!(
    
    182
    +                bridge.fingerprint.as_ref().map(|fp| &fp[..]),
    
    183
    +                Some(
    
    184
    +                    hex::decode("D9A82D2F9C2F65A18407B1D2B764F130847F8B5D")
    
    185
    +                        .unwrap()
    
    186
    +                        .as_slice()
    
    187
    +                )
    
    188
    +            );
    
    189
    +            assert_eq!(bridge.args, None);
    
    190
    +        }
    
    191
    +        {
    
    192
    +            let bridge = parse_bridge_line(
    
    193
    +                b"obfs4 [1234:56:789::abcd]:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D",
    
    194
    +            )
    
    195
    +            .unwrap();
    
    196
    +            assert_eq!(bridge.transport, Some(String::from("obfs4")));
    
    197
    +            assert_eq!(
    
    198
    +                bridge.address,
    
    199
    +                SocketAddrV6::new(
    
    200
    +                    Ipv6Addr::new(0x1234, 0x56, 0x789, 0, 0, 0, 0, 0xabcd),
    
    201
    +                    38224,
    
    202
    +                    0,
    
    203
    +                    0
    
    204
    +                )
    
    205
    +                .into()
    
    206
    +            );
    
    207
    +            assert_eq!(
    
    208
    +                bridge.fingerprint.as_ref().map(|fp| &fp[..]),
    
    209
    +                Some(
    
    210
    +                    hex::decode("D9A82D2F9C2F65A18407B1D2B764F130847F8B5D")
    
    211
    +                        .unwrap()
    
    212
    +                        .as_slice()
    
    213
    +                )
    
    214
    +            );
    
    215
    +            assert_eq!(bridge.args, None);
    
    216
    +        }
    
    217
    +    }
    
    218
    +
    
    219
    +    #[test]
    
    220
    +    fn all() {
    
    221
    +        {
    
    222
    +            let bridge = parse_bridge_line(
    
    223
    +                b"obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D iat-mode=0",
    
    224
    +            )
    
    225
    +            .unwrap();
    
    226
    +            assert_eq!(bridge.transport, Some(String::from("obfs4")));
    
    227
    +            assert_eq!(
    
    228
    +                bridge.address,
    
    229
    +                SocketAddrV4::new(Ipv4Addr::new(37, 218, 245, 14), 38224).into()
    
    230
    +            );
    
    231
    +            assert_eq!(
    
    232
    +                bridge.fingerprint.as_ref().map(|fp| &fp[..]),
    
    233
    +                Some(
    
    234
    +                    hex::decode("D9A82D2F9C2F65A18407B1D2B764F130847F8B5D")
    
    235
    +                        .unwrap()
    
    236
    +                        .as_slice()
    
    237
    +                )
    
    238
    +            );
    
    239
    +            assert_eq!(&bridge.args.unwrap(), b"iat-mode=0");
    
    240
    +        }
    
    241
    +        {
    
    242
    +            let bridge = parse_bridge_line(
    
    243
    +                b"obfs4 [1234:56:789:0a:00b:000c:d:eeee]:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D iat-mode=0",
    
    244
    +            )
    
    245
    +            .unwrap();
    
    246
    +            assert_eq!(bridge.transport, Some(String::from("obfs4")));
    
    247
    +            assert_eq!(
    
    248
    +                bridge.address,
    
    249
    +                SocketAddrV6::new(
    
    250
    +                    Ipv6Addr::new(0x1234, 0x56, 0x789, 0xa, 0xb, 0xc, 0xd, 0xeeee),
    
    251
    +                    38224,
    
    252
    +                    0,
    
    253
    +                    0
    
    254
    +                )
    
    255
    +                .into()
    
    256
    +            );
    
    257
    +            assert_eq!(
    
    258
    +                bridge.fingerprint.as_ref().map(|fp| &fp[..]),
    
    259
    +                Some(
    
    260
    +                    hex::decode("D9A82D2F9C2F65A18407B1D2B764F130847F8B5D")
    
    261
    +                        .unwrap()
    
    262
    +                        .as_slice()
    
    263
    +                )
    
    264
    +            );
    
    265
    +            assert_eq!(&bridge.args.unwrap(), b"iat-mode=0");
    
    266
    +        }
    
    267
    +    }
    
    268
    +
    
    269
    +    #[test]
    
    270
    +    fn no_fingerprint() {
    
    271
    +        let bridge = parse_bridge_line(b"test 1.2.3.4:567 iat-mode=0").unwrap();
    
    272
    +        assert_eq!(bridge.transport.unwrap(), "test");
    
    273
    +        assert_eq!(
    
    274
    +            bridge.address,
    
    275
    +            SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 567).into()
    
    276
    +        );
    
    277
    +        assert_eq!(bridge.fingerprint, None);
    
    278
    +        assert_eq!(&bridge.args.unwrap(), b"iat-mode=0");
    
    279
    +    }
    
    280
    +
    
    281
    +    #[test]
    
    282
    +    fn multiple_args_and_spaces() {
    
    283
    +        let bridge = parse_bridge_line(
    
    284
    +            b"   obfs4    37.218.245.14:38224      D9A82D2F9C2F65A18407B1D2B764F130847F8B5D     iat-mode=0 other-arg spaces-at-the-end  ",
    
    285
    +        )
    
    286
    +        .unwrap();
    
    287
    +        assert_eq!(
    
    288
    +            &bridge.args.unwrap(),
    
    289
    +            b"iat-mode=0 other-arg spaces-at-the-end"
    
    290
    +        );
    
    291
    +    }
    
    292
    +
    
    293
    +    #[test]
    
    294
    +    fn failures() {
    
    295
    +        // Only IP address without a port. Does not even match the regex.
    
    296
    +        assert_eq!(
    
    297
    +            parse_bridge_line(b"192.168.1.4").unwrap_err(),
    
    298
    +            BridgeParseError::InvalidFormat,
    
    299
    +        );
    
    300
    +        // Invalid port (and again, does not match the regex).
    
    301
    +        assert_eq!(
    
    302
    +            parse_bridge_line(b"192.168.1.4:100000").unwrap_err(),
    
    303
    +            BridgeParseError::InvalidFormat,
    
    304
    +        );
    
    305
    +        // No address
    
    306
    +        assert_eq!(
    
    307
    +            parse_bridge_line(b"obfs4 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D").unwrap_err(),
    
    308
    +            BridgeParseError::InvalidFormat,
    
    309
    +        );
    
    310
    +        // No address and bad fingerprint (bad no address prevails).
    
    311
    +        assert_eq!(
    
    312
    +            parse_bridge_line(b"obfs4 aaa1234").unwrap_err(),
    
    313
    +            BridgeParseError::InvalidFormat,
    
    314
    +        );
    
    315
    +
    
    316
    +        assert_eq!(
    
    317
    +            parse_bridge_line(b"192.168.1.375:1234").unwrap_err(),
    
    318
    +            BridgeParseError::InvalidAddress,
    
    319
    +        );
    
    320
    +        assert_eq!(
    
    321
    +            parse_bridge_line(b"192.168.1.4:0").unwrap_err(),
    
    322
    +            BridgeParseError::InvalidPort,
    
    323
    +        );
    
    324
    +        assert_eq!(
    
    325
    +            parse_bridge_line(b"192.168.1.4:99999").unwrap_err(),
    
    326
    +            BridgeParseError::InvalidAddress,
    
    327
    +        );
    
    328
    +        assert_eq!(
    
    329
    +            parse_bridge_line(b"192.168.1.4:65536").unwrap_err(),
    
    330
    +            BridgeParseError::InvalidAddress,
    
    331
    +        );
    
    332
    +
    
    333
    +        assert_eq!(
    
    334
    +            parse_bridge_line(b"1.2.3.4:443 aaaaaaaaa").unwrap_err(),
    
    335
    +            BridgeParseError::InvalidFingerprint
    
    336
    +        );
    
    337
    +        assert_eq!(
    
    338
    +            parse_bridge_line(
    
    339
    +                b"1.2.3.4:443 0123456789012345678901234567890123456789aaaaa"
    
    340
    +            )
    
    341
    +            .unwrap_err(),
    
    342
    +            BridgeParseError::InvalidFingerprint
    
    343
    +        );
    
    344
    +
    
    345
    +        assert_eq!(
    
    346
    +            parse_bridge_line(
    
    347
    +                b"1.2.3.4:443 0123456789012345678901234567890123456789 key=value"
    
    348
    +            ).unwrap_err(),
    
    349
    +            BridgeParseError::ArgsWithoutTransport,
    
    350
    +        );
    
    351
    +        assert_eq!(
    
    352
    +            parse_bridge_line(
    
    353
    +                b"1.2.3.4:443 key=value"
    
    354
    +            ).unwrap_err(),
    
    355
    +            BridgeParseError::ArgsWithoutTransport,
    
    356
    +        );
    
    357
    +    }
    
    358
    +}

  • toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/client_transport_plugin.rs
    1
    +// Licensed under the Apache License, Version 2.0,
    
    2
    +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
    
    3
    +// <http://opensource.org/licenses/MIT>, at your option. This file may not be
    
    4
    +// copied, modified, or distributed except according to those terms.
    
    5
    +
    
    6
    +use lazy_static::lazy_static;
    
    7
    +use regex::bytes::{Captures, Regex};
    
    8
    +
    
    9
    +use super::tor_unescape;
    
    10
    +use crate::ctor::controller::{ClientTransportPlugin, ControllerError, PluggableTransport};
    
    11
    +
    
    12
    +lazy_static! {
    
    13
    +    // man 1 tor: ClientTransportPlugin transport socks4|socks5 IP:PORT
    
    14
    +    static ref SOCKS_REGEX: Regex =
    
    15
    +        Regex::new(r"^(?<transport>[A-Za-z_][A-Za-z0-9_,]*) (?<protocol>socks[45]) (?<address>(?:[\d\.]{7,15}|\[[\da-fA-F:]+\]):\d{1,5})$")
    
    16
    +            .expect("The regex is hardcoded, it should be good.");
    
    17
    +    // man 1 tor: transport exec path-to-binary [options]
    
    18
    +    static ref EXEC_REGEX: Regex = Regex::new(r#"^(?<transport>[A-Za-z_][A-Za-z0-9_,]*) exec (?<path>"(?:[^"\\]|\\.)*"|[^ ]+)(?: (?<options>.*?) *)?$"#)
    
    19
    +        .expect("The regex is hardcoded, it should be good.");
    
    20
    +}
    
    21
    +
    
    22
    +pub fn parse_client_transport_plugin(line: &[u8]) -> Result<PluggableTransport, ControllerError> {
    
    23
    +    if let Some(socks_line) = SOCKS_REGEX.captures(line) {
    
    24
    +        parse_socks(&socks_line)
    
    25
    +    } else if let Some(exec_line) = EXEC_REGEX.captures(line) {
    
    26
    +        parse_exec(&exec_line)
    
    27
    +    } else {
    
    28
    +        Err(ControllerError::WrongFormat(
    
    29
    +            String::from_utf8_lossy(line).into_owned(),
    
    30
    +        ))
    
    31
    +    }
    
    32
    +}
    
    33
    +
    
    34
    +fn get_transports(c: &Captures) -> Result<Vec<String>, ControllerError> {
    
    35
    +    let transports = c
    
    36
    +        .name("transport")
    
    37
    +        .ok_or(ControllerError::KeyNotFound(String::from("transport")))?
    
    38
    +        .as_bytes()
    
    39
    +        .split(|b| *b == b',')
    
    40
    +        .map(|t| String::from_utf8_lossy(t).into_owned())
    
    41
    +        .collect::<Vec<_>>();
    
    42
    +    if transports.is_empty() || transports.iter().any(|t| t.is_empty()) {
    
    43
    +        return Err(ControllerError::MalformedReply(String::from(
    
    44
    +            "empty transports",
    
    45
    +        )));
    
    46
    +    }
    
    47
    +    Ok(transports)
    
    48
    +}
    
    49
    +
    
    50
    +fn parse_socks(socks_line: &Captures) -> Result<PluggableTransport, ControllerError> {
    
    51
    +    let address = str::from_utf8(
    
    52
    +        socks_line
    
    53
    +            .name("address")
    
    54
    +            .ok_or(ControllerError::KeyNotFound(String::from("address")))?
    
    55
    +            .as_bytes(),
    
    56
    +    )
    
    57
    +    .expect("The regex matches only ASCII characters (hence valid UTF-8)")
    
    58
    +    .parse()
    
    59
    +    .map_err(|e| ControllerError::MalformedReply(format!("{}", e)))?;
    
    60
    +    let plugin = match socks_line
    
    61
    +        .name("protocol")
    
    62
    +        .ok_or(ControllerError::KeyNotFound(String::from("protocol")))?
    
    63
    +        .as_bytes()
    
    64
    +    {
    
    65
    +        b"socks4" => ClientTransportPlugin::Socks4(address),
    
    66
    +        b"socks5" => ClientTransportPlugin::Socks5(address),
    
    67
    +        _ => unreachable!("We validated this with the regex"),
    
    68
    +    };
    
    69
    +    Ok(PluggableTransport {
    
    70
    +        transports: get_transports(&socks_line)?,
    
    71
    +        plugin,
    
    72
    +    })
    
    73
    +}
    
    74
    +
    
    75
    +fn parse_exec(exec_line: &Captures) -> Result<PluggableTransport, ControllerError> {
    
    76
    +    let options = exec_line
    
    77
    +        .name("options")
    
    78
    +        .map(|o| o.as_bytes())
    
    79
    +        .and_then(|s| if s.is_empty() { None } else { Some(s.into()) });
    
    80
    +    let path = tor_unescape(
    
    81
    +        exec_line
    
    82
    +            .name("path")
    
    83
    +            .ok_or(ControllerError::KeyNotFound(String::from("path")))?
    
    84
    +            .as_bytes(),
    
    85
    +    )
    
    86
    +    .map_err(|e| ControllerError::MalformedReply(e.to_string()))?
    
    87
    +    .into_owned();
    
    88
    +    if path.is_empty() {
    
    89
    +        return Err(ControllerError::MalformedReply(String::from("empty path")));
    
    90
    +    }
    
    91
    +    Ok(PluggableTransport {
    
    92
    +        transports: get_transports(&exec_line)?,
    
    93
    +        plugin: ClientTransportPlugin::Executable { path, options },
    
    94
    +    })
    
    95
    +}
    
    96
    +
    
    97
    +#[cfg(test)]
    
    98
    +mod tests {
    
    99
    +    use std::{
    
    100
    +        assert_matches,
    
    101
    +        net::{Ipv4Addr, SocketAddrV4},
    
    102
    +    };
    
    103
    +
    
    104
    +    use super::*;
    
    105
    +
    
    106
    +    #[test]
    
    107
    +    fn socks() {
    
    108
    +        {
    
    109
    +            let pt =
    
    110
    +                parse_client_transport_plugin(b"mytransport,mypt2 socks4 127.0.0.1:9052").unwrap();
    
    111
    +            assert_eq!(pt.transports, vec!["mytransport", "mypt2"]);
    
    112
    +            assert_eq!(
    
    113
    +                pt.plugin,
    
    114
    +                ClientTransportPlugin::Socks4(
    
    115
    +                    SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 9052).into()
    
    116
    +                )
    
    117
    +            );
    
    118
    +        }
    
    119
    +        {
    
    120
    +            let pt = parse_client_transport_plugin(b"pt3,pt_4 socks5 10.0.1.2:9052").unwrap();
    
    121
    +            assert_eq!(pt.transports, vec!["pt3", "pt_4"]);
    
    122
    +            assert_eq!(
    
    123
    +                pt.plugin,
    
    124
    +                ClientTransportPlugin::Socks5(
    
    125
    +                    SocketAddrV4::new(Ipv4Addr::new(10, 0, 1, 2), 9052).into()
    
    126
    +                )
    
    127
    +            );
    
    128
    +        }
    
    129
    +        {
    
    130
    +            let pt = parse_client_transport_plugin(
    
    131
    +                b"mytransport,mypt2 socks4 [38e5:09fc:2080:673d:9ab8:4545:08db:36c2]:1234",
    
    132
    +            )
    
    133
    +            .unwrap();
    
    134
    +            assert_eq!(pt.transports, vec!["mytransport", "mypt2"]);
    
    135
    +            assert_eq!(
    
    136
    +                pt.plugin,
    
    137
    +                ClientTransportPlugin::Socks4(
    
    138
    +                    "[38e5:09fc:2080:673d:9ab8:4545:08db:36c2]:1234"
    
    139
    +                        .parse()
    
    140
    +                        .unwrap()
    
    141
    +                )
    
    142
    +            );
    
    143
    +        }
    
    144
    +        {
    
    145
    +            let pt = parse_client_transport_plugin(
    
    146
    +                b"s5pt socks5 [dad7:ddf4:7fc4:ef92:4bb2:ceb9:05b9:9161]:5555",
    
    147
    +            )
    
    148
    +            .unwrap();
    
    149
    +            assert_eq!(pt.transports, vec!["s5pt"]);
    
    150
    +            assert_eq!(
    
    151
    +                pt.plugin,
    
    152
    +                ClientTransportPlugin::Socks5(
    
    153
    +                    "[dad7:ddf4:7fc4:ef92:4bb2:ceb9:05b9:9161]:5555"
    
    154
    +                        .parse()
    
    155
    +                        .unwrap()
    
    156
    +                )
    
    157
    +            );
    
    158
    +        }
    
    159
    +    }
    
    160
    +
    
    161
    +    #[test]
    
    162
    +    fn exec_multiple_transports() {
    
    163
    +        let pt = parse_client_transport_plugin(b"obfs4,meek exec lyrebird").unwrap();
    
    164
    +        assert_eq!(
    
    165
    +            pt.transports,
    
    166
    +            vec![String::from("obfs4"), String::from("meek")]
    
    167
    +        );
    
    168
    +        assert_eq!(
    
    169
    +            pt.plugin,
    
    170
    +            ClientTransportPlugin::Executable {
    
    171
    +                path: Vec::from(b"lyrebird"),
    
    172
    +                options: None,
    
    173
    +            }
    
    174
    +        );
    
    175
    +    }
    
    176
    +
    
    177
    +    #[test]
    
    178
    +    fn exec_with_spaces() {
    
    179
    +        let pt =
    
    180
    +            parse_client_transport_plugin(b"snowflake exec \"pluggable transports/snowflake\"")
    
    181
    +                .unwrap();
    
    182
    +        assert_eq!(pt.transports, vec![String::from("snowflake")]);
    
    183
    +        assert_eq!(
    
    184
    +            pt.plugin,
    
    185
    +            ClientTransportPlugin::Executable {
    
    186
    +                path: Vec::from("pluggable transports/snowflake"),
    
    187
    +                options: None,
    
    188
    +            }
    
    189
    +        )
    
    190
    +    }
    
    191
    +
    
    192
    +    #[test]
    
    193
    +    fn exec_windows_path() {
    
    194
    +        let pt = parse_client_transport_plugin(
    
    195
    +            b"snowflake exec \"C:\\\\Program Files\\\\Tor Project \\\\snowflake.exe\"",
    
    196
    +        )
    
    197
    +        .unwrap();
    
    198
    +        assert_eq!(pt.transports, vec![String::from("snowflake")]);
    
    199
    +        assert_eq!(
    
    200
    +            pt.plugin,
    
    201
    +            ClientTransportPlugin::Executable {
    
    202
    +                path: Vec::from("C:\\Program Files\\Tor Project \\snowflake.exe"),
    
    203
    +                options: None,
    
    204
    +            }
    
    205
    +        )
    
    206
    +    }
    
    207
    +
    
    208
    +    #[test]
    
    209
    +    fn exec_with_options() {
    
    210
    +        let pt = parse_client_transport_plugin(
    
    211
    +            b"conjure,coupdetat exec conjure-client -registerURL https://registration.refraction.network/api     ",
    
    212
    +        )
    
    213
    +        .unwrap();
    
    214
    +        assert_eq!(
    
    215
    +            pt.transports,
    
    216
    +            vec![String::from("conjure"), String::from("coupdetat")]
    
    217
    +        );
    
    218
    +        assert_eq!(
    
    219
    +            pt.plugin,
    
    220
    +            ClientTransportPlugin::Executable {
    
    221
    +                path: Vec::from("conjure-client"),
    
    222
    +                options: Some(b"-registerURL https://registration.refraction.network/api".into())
    
    223
    +            }
    
    224
    +        )
    
    225
    +    }
    
    226
    +
    
    227
    +    #[test]
    
    228
    +    fn invalid() {
    
    229
    +        assert_matches!(
    
    230
    +            parse_client_transport_plugin(b"transport socks2 127.0.0.1:9052").unwrap_err(),
    
    231
    +            ControllerError::WrongFormat(_)
    
    232
    +        );
    
    233
    +    }
    
    234
    +
    
    235
    +    #[test]
    
    236
    +    fn socks_errors() {
    
    237
    +        assert_matches!(
    
    238
    +            parse_client_transport_plugin(b"transport socks4").unwrap_err(),
    
    239
    +            ControllerError::WrongFormat(_)
    
    240
    +        );
    
    241
    +
    
    242
    +        assert_matches!(
    
    243
    +            parse_client_transport_plugin(b"transport socks4 123.45.6.7.8:123456").unwrap_err(),
    
    244
    +            ControllerError::WrongFormat(_)
    
    245
    +        );
    
    246
    +
    
    247
    +        assert_matches!(
    
    248
    +            parse_client_transport_plugin(b", socks4 123.45.6.7.8:1234").unwrap_err(),
    
    249
    +            ControllerError::WrongFormat(_)
    
    250
    +        );
    
    251
    +
    
    252
    +        assert_matches!(
    
    253
    +            parse_client_transport_plugin(b"transport socks4 1234.5.6.7.8:123").unwrap_err(),
    
    254
    +            ControllerError::MalformedReply(_)
    
    255
    +        );
    
    256
    +
    
    257
    +        assert_matches!(
    
    258
    +            parse_client_transport_plugin(b"transport socks4 273.12.34.56:789").unwrap_err(),
    
    259
    +            ControllerError::MalformedReply(_)
    
    260
    +        );
    
    261
    +
    
    262
    +        assert_matches!(
    
    263
    +            parse_client_transport_plugin(b"transport socks4 123.12.34.56:78999").unwrap_err(),
    
    264
    +            ControllerError::MalformedReply(_)
    
    265
    +        );
    
    266
    +    }
    
    267
    +
    
    268
    +    #[test]
    
    269
    +    fn exec_errors() {
    
    270
    +        assert_matches!(
    
    271
    +            parse_client_transport_plugin(b"transport exec").unwrap_err(),
    
    272
    +            ControllerError::WrongFormat(_)
    
    273
    +        );
    
    274
    +
    
    275
    +        assert_matches!(
    
    276
    +            parse_client_transport_plugin(b"transport exec \"\"").unwrap_err(),
    
    277
    +            ControllerError::MalformedReply(_)
    
    278
    +        );
    
    279
    +
    
    280
    +        assert_matches!(
    
    281
    +            parse_client_transport_plugin(b"transport exec \"unterminated").unwrap_err(),
    
    282
    +            ControllerError::MalformedReply(_)
    
    283
    +        );
    
    284
    +
    
    285
    +        assert_matches!(
    
    286
    +            parse_client_transport_plugin(b"transport exec \"invalid hex \\xAz\"").unwrap_err(),
    
    287
    +            ControllerError::MalformedReply(_)
    
    288
    +        );
    
    289
    +
    
    290
    +        assert_matches!(
    
    291
    +            parse_client_transport_plugin(b", exec mypt.exe").unwrap_err(),
    
    292
    +            ControllerError::WrongFormat(_)
    
    293
    +        );
    
    294
    +        assert_matches!(
    
    295
    +            parse_client_transport_plugin(b"garbage trans,port exec mypt.exe").unwrap_err(),
    
    296
    +            ControllerError::WrongFormat(_)
    
    297
    +        );
    
    298
    +    }
    
    299
    +}

  • toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/mod.rs
    ... ... @@ -4,6 +4,8 @@
    4 4
     // copied, modified, or distributed except according to those terms.
    
    5 5
     
    
    6 6
     mod ack;
    
    7
    +mod bridge_line;
    
    8
    +mod client_transport_plugin;
    
    7 9
     mod escape;
    
    8 10
     mod unescape;
    
    9 11
     
    
    ... ... @@ -11,5 +13,7 @@ mod unescape;
    11 13
     mod tests;
    
    12 14
     
    
    13 15
     pub use ack::parse_ack;
    
    16
    +pub use bridge_line::parse_bridge_line;
    
    17
    +pub use client_transport_plugin::parse_client_transport_plugin;
    
    14 18
     pub use escape::tor_escape_into;
    
    15 19
     pub use unescape::tor_unescape;

  • toolkit/components/tor-integration/tor_provider/src/ctor/controller/types.rs
    1
    +// Licensed under the Apache License, Version 2.0,
    
    2
    +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
    
    3
    +// <http://opensource.org/licenses/MIT>, at your option. This file may not be
    
    4
    +// copied, modified, or distributed except according to those terms.
    
    5
    +
    
    6
    +use serde::Serialize;
    
    7
    +use std::net::SocketAddr;
    
    8
    +
    
    9
    +#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
    
    10
    +pub struct Bridge {
    
    11
    +    pub transport: Option<String>,
    
    12
    +    pub address: SocketAddr,
    
    13
    +    pub fingerprint: Option<[u8; 20]>,
    
    14
    +    pub args: Option<Vec<u8>>,
    
    15
    +}
    
    16
    +
    
    17
    +#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
    
    18
    +pub enum ClientTransportPlugin {
    
    19
    +    Executable {
    
    20
    +        path: Vec<u8>,
    
    21
    +        options: Option<Vec<u8>>,
    
    22
    +    },
    
    23
    +    Socks4(SocketAddr),
    
    24
    +    Socks5(SocketAddr),
    
    25
    +}
    
    26
    +
    
    27
    +#[derive(Debug, Clone)]
    
    28
    +pub enum ConfValue<'a> {
    
    29
    +    Bool(bool),
    
    30
    +    Int(i32),
    
    31
    +    String(&'a str),
    
    32
    +    Array(Vec<&'a str>),
    
    33
    +    Null,
    
    34
    +}
    
    35
    +
    
    36
    +#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
    
    37
    +pub struct PluggableTransport {
    
    38
    +    pub transports: Vec<String>,
    
    39
    +    pub plugin: ClientTransportPlugin,
    
    40
    +}

  • _______________________________________________
    tor-commits mailing list -- tor-commits@xxxxxxxxxxxxxxxxxxxx
    To unsubscribe send an email to tor-commits-leave@xxxxxxxxxxxxxxxxxxxx