summaryrefslogtreecommitdiff
path: root/src/process.rs
blob: a6f63fd43d7aee252fb25768d0e9708f8b8e92d8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
use std::io::{Write,ErrorKind};
use std::net::SocketAddr;
use std::path::Path;
use std::process::{Child,Command,Stdio};

use crate::sock::Tor;
use crate::err::Result;


fn hex(out: &mut String, bytes: &[u8]) {
    use std::fmt::Write;
    for c in bytes {
        write!(out, "{:02X}", c).is_ok();
    }
}


/// Generate a string suitable for a HashedControlPassword. The first 8 bytes of `pass` are the
/// salt, the remainder is the secret.
// The hash format and algorithm are described in:
// - https://gitweb.torproject.org/torspec.git/blob/control-spec.txt -> Section 5.1
// - https://tools.ietf.org/html/rfc2440 -> Section 3.6, Salted S2K
fn s2k(pass: &[u8]) -> String {
    const C     : u8    = 0x60; // The 'indicator', determines the iteration count
    const COUNT : usize = (16 + (C as usize & 15)) << ((C as usize >> 4) + 6);

    let mut hash = sha1::Sha1::new();
    let mut n = COUNT;
    while n > 0 {
        if n >= pass.len() {
            hash.update(pass);
            n -= pass.len();
        } else {
            hash.update(&pass[..n]);
            n = 0;
        }
    }

    let mut out = String::new();
    out.push_str("16:");
    hex(&mut out, &pass[..8]);
    hex(&mut out, &[C][..]);
    hex(&mut out, &hash.digest().bytes()[..]);
    out
}


/// Returns a newly generated password and a corresponding HashedControlPassword.
pub fn genpass() -> (String, String) {
    let mut rand = [0u8; 8 + 16];
    use rand_os::rand_core::RngCore;
    rand_os::OsRng::new().expect("Unable to obtain OS randomness").fill_bytes(&mut rand);

    let mut pass = String::new();
    hex(&mut pass, &rand[8..]);

    let mut buf = [0u8; 8 + 2*16];
    buf[..8].copy_from_slice(&rand[..8]);
    buf[8..].copy_from_slice(pass.as_bytes());
    (pass, s2k(&buf[..]))
}


/// Hash the given password to a suitable string for HashedControlPassword. Similar to
/// `tor --hash-password`.
pub fn hashpass(pass: &str) -> String {
    let mut buf = vec![0u8; 8];
    use rand_os::rand_core::RngCore;
    rand_os::OsRng::new().expect("Unable to obtain OS randomness").fill_bytes(&mut buf);

    buf.extend_from_slice(pass.as_bytes());
    s2k(&buf[..])
}


fn parse_port(s: String) -> Result<SocketAddr> {
    for line in s.lines() {
        if line.starts_with("PORT=") {
            return (&line[5..]).parse().map_err(|_| err!(Parse))
        }
    }
    Err(err!(Parse))
}


// Wait for the Tor process to start and fetch the control address.
fn wait(child: &mut Child, control_path: &Path) -> Result<SocketAddr> {
    // Wait at most 100*50ms = 5s for Tor to start. We're just waiting for it to create the
    // control socket, this shouldn't rely on any network I/O.
    for _ in 0..100 {
        if let Some(status) = child.try_wait()? {
            return Err(err!(ProcShutdown, status));
        }
        match std::fs::read_to_string(control_path) {
            Err(e) => if e.kind() != ErrorKind::NotFound { return Err(e.into()) },
            Ok(s) => return parse_port(s)
        }
        std::thread::sleep(std::time::Duration::from_millis(50));
    }
    Err(err!(ProcTimeout))
}


/// Spawn a new Tor process and return its control socket. The Tor process will automatically shut
/// down when the socket is closed.
///
/// It is an error to call this function if a Tor process is already running with the same `data_dir`.
pub fn spawn<S: AsRef<std::ffi::OsStr>, P: AsRef<Path>>(bin: S, data_dir: P, conf_add: &str) -> Result<Tor> {
    // We don't really have to create the directory, Tor can do that itself; But we should have a
    // canonical path to pass to Tor, otherwise it will (rightfully) complain.
    std::fs::create_dir_all(&data_dir)?;

    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        std::fs::set_permissions(&data_dir, std::fs::Permissions::from_mode(0o700))?;
    }
    let data_dir = std::fs::canonicalize(data_dir)?;

    let mut control_path = data_dir.clone();
    control_path.push("chifs-control-port");
    std::fs::remove_file(&control_path).is_ok();

    let mut child = Command::new(bin)
        .arg("--hush") // .arg("--Log").arg("notice stdout")
        .arg("--DataDirectory").arg(data_dir)
        .arg("--ControlPort").arg("auto")
        .arg("--ControlPortWriteToFile").arg(&control_path)
        .arg("--__OwningControllerProcess").arg(std::process::id().to_string())
        .arg("--SocksPort").arg("auto")
        .arg("-f").arg("-")
         // TODO: Capture stderr/stdout? That'd be useful if tor exits immediately with an error,
         // but may cause tor to block on writing to the pipes if we don't keep reading from them.
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .stdin(Stdio::piped())
        .spawn()?;

    // Don't send the HashControlPassword as a cli argument, as that might end up in a process
    // listing (Interestingly enough, Tor Browser Bundle does pass it as argument, hmm).
    let (pass, hash) = genpass();
    let to_stdin = format!("HashedControlPassword {}\n{}", hash, conf_add);
    // The stdin.take() will cause the pipe to be dropped and closed, which is what we want.
    child.stdin.take().unwrap().write_all(to_stdin.as_bytes())?;

    let addr = wait(&mut child, &control_path)?;
    let sock = Tor::connect_child(&addr, &pass, child)?;
    Ok(sock)
}


#[cfg(test)]
mod tests {
    #[test]
    fn s2k() {
        // Salt & output generated with 'tor --hash-password'
        assert_eq!(super::s2k(b"\x05\xE9\x77\x25\xCD\xA9\x73\xC0"),      "16:05E97725CDA973C0601B195C450C1663B5A17A64D75C79954C7AC603E4");
        assert_eq!(super::s2k(b"\xB3\xCA\x4B\x45\xCB\x35\xAC\xC7Hello"), "16:B3CA4B45CB35ACC7604AF87AC446DA1BA9070DAB1785E604F584DD85E2");
    }
}