From b44faa508cfa39939cebdefd107a0d10af3e682f Mon Sep 17 00:00:00 2001 From: Yorhel Date: Tue, 21 May 2019 14:13:33 +0200 Subject: Add method to create Onion services --- src/err.rs | 2 ++ src/sock.rs | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/err.rs b/src/err.rs index 8a5a2c5..33ae1b3 100644 --- a/src/err.rs +++ b/src/err.rs @@ -15,6 +15,7 @@ pub(crate) enum ErrorImpl { Status(crate::sock::ReplyLine), Parse, // This could be more specific Keyword(String), + OnionKey, } impl std::fmt::Display for Error { @@ -27,6 +28,7 @@ impl std::fmt::Display for Error { Status(s) => write!(f, "Unexpected reply ({})", s), Parse => write!(f, "Error parsing reply"), Keyword(s) => write!(f, "Invalid keyword '{}'", s), + OnionKey => write!(f, "Invalid Onion private key"), } } } diff --git a/src/sock.rs b/src/sock.rs index 15311db..bba3598 100644 --- a/src/sock.rs +++ b/src/sock.rs @@ -1,5 +1,5 @@ use std::io::{self,Read,Write}; -use std::net::{TcpStream,SocketAddr}; +use std::net::{TcpStream,TcpListener,SocketAddr}; use std::process::Child; use std::sync::{Mutex,Condvar}; @@ -502,6 +502,40 @@ impl Tor { self.socks } + /// Open a Onion service at the specified (virtual) port. Returns its address (xyz.onion), + /// private key and listen socket. If no key is given, a new one is generated automatically. + /// The given key should be prefixed by its type (e.g.: `ED25519-V3:xxx`). the returned key + /// will have this prefix. + // TODO: Using a String for the key is ugly and easily confused with the public address, might + // want to use a separate type? (Or, perhaps even better, a return type encompassing all three + // values and having a Drop implementation that calls DEL_ONION). + pub fn listen_onion(&self, virtual_port: u16, key: Option<&str>) -> Result<(String, String, TcpListener)> { + let key = key.unwrap_or("NEW:ED25519-V3"); // tor 0.3.5.8 still defaults to v2 with "NEW:BEST" for some reason, just enforce v3 for now. + if key.contains(|c: char| c.is_whitespace()) { // Just to prevent protocol injection. + return Err(err!(OnionKey)); + } + let listen = TcpListener::bind("127.0.0.1:0")?; + let local_port = listen.local_addr()?.port(); + let r = self.cmd(format!("ADD_ONION {} Port={},127.0.0.1:{}\r\n", key, virtual_port, local_port))?; + + let mut service = None; + let mut rkey = key.to_string(); + for line in r { + if line.status != 250 { + return Err(err!(Parse)); + } + if line.text.starts_with("ServiceID=") { + service = Some(format!("{}.onion", &line.text[10..])); + } else if line.text.starts_with("PrivateKey=") { + rkey = (&line.text[11..]).to_string(); + } + } + match service { + None => return Err(err!(Parse)), + Some(s) => return Ok((s, rkey, listen)) + } + } + /// Returns the most recent bootstrap status. pub fn bootstrap_status(&self) -> Result> { let ret = self.getinfo(&["status/bootstrap-phase"])?; -- cgit v1.2.3