use base64::Engine;
use connection::Connection;
use log::{debug, info, warn};
use misc::Message;
use rand::thread_rng;
use sha3::{Digest, Sha3_256};
use std::collections::HashSet;
use std::io::{Error, ErrorKind};
use std::net::ToSocketAddrs;
mod connection;
mod misc;
pub use misc::{AsyncEventHandle, AsyncEventKind, Response};
#[cfg(test)]
mod tests;
const TOR_PORT_CONTROL: u16 = 9051;
const UPLOAD_CONFIRMATIONS: usize = 5;
const EVENT_QUEUE_SIZE: usize = 1024;
pub struct Zwuevi {
sender: tokio::sync::mpsc::Sender<Message>,
receiver: tokio::sync::mpsc::Receiver<Message>,
connection: Connection,
}
impl Zwuevi {
pub async fn default() -> Result<Zwuevi, Error> {
Self::new(TOR_PORT_CONTROL).await
}
pub async fn new(control_port: u16) -> Result<Zwuevi, Error> {
let mut connection = Connection::new(control_port).await?;
connection.authenticate().await?;
let (sender, receiver) = tokio::sync::mpsc::channel(1024);
let mut zwuevi = Self {
sender,
receiver,
connection,
};
let (tx, mut rx) = tokio::sync::mpsc::channel(EVENT_QUEUE_SIZE);
zwuevi
.connection
.add_event_handler(AsyncEventKind::StatusClient, zwuevi.sender.clone(), tx)
.await?;
let response = zwuevi.receive().await?;
if response.code != 250 {
return Err(Error::new(
ErrorKind::Unsupported,
format!(
"Invalid response code {}: {:?}",
response.code, response.data
),
));
}
if !zwuevi
.raw_command("GETINFO dormant")
.await
.map(|response| {
response
.data
.into_iter()
.any(|line| line.contains("dormant=0"))
})?
{
warn!("Tor is currently dormant - try to activate Tor again");
warn!("THIS MIGHT NOT WORK");
let _ = zwuevi.raw_command("SIGNAL active").await?;
}
if zwuevi
.raw_command("GETINFO status/circuit-established")
.await
.map(|response| {
response
.data
.into_iter()
.any(|line| line.contains("circuit-established=0"))
})?
{
while match rx.recv().await {
Some(output) => !std::convert::TryInto::<Response>::try_into(output)
.map_err(|err| {
Error::new(
ErrorKind::Other,
format!("Could not convert Message into Response: {err}"),
)
})?
.data
.into_iter()
.any(|line| line.contains("CIRCUIT_ESTABLISHED")),
_ => true,
} {
info!("Waiting for Tor to establish a connection");
}
}
Ok(zwuevi)
}
pub async fn add_event_handler(
&mut self,
event_kind: AsyncEventKind,
func: impl Fn(Vec<String>) + Send + 'static,
) -> Result<AsyncEventHandle, Error> {
let (tx, mut rx) = tokio::sync::mpsc::channel(EVENT_QUEUE_SIZE);
self.connection
.add_event_handler(event_kind, self.sender.clone(), tx)
.await?;
let response = self.receive().await?;
if response.code != 250 {
return Err(Error::new(
ErrorKind::Unsupported,
format!(
"Invalid response code {}: {:?}",
response.code, response.data
),
));
}
let handle = tokio::spawn(async move {
while let Some(response) = rx.recv().await {
(func)(response.data);
}
});
Ok(AsyncEventHandle::from(handle))
}
pub async fn raw_command(&mut self, command: &str) -> Result<Response, Error> {
self.send(Message::Raw(
format!("{command}\r\n").into(),
self.sender.clone(),
))
.await?;
self.receive().await
}
pub async fn add_onion_v3<S: ToSocketAddrs, I: IntoIterator<Item = (u16, S)>>(
&mut self,
secret_key: &[u8; 32],
listeners: I,
flags: Option<Vec<&str>>,
) -> Result<String, Error> {
let sk = *secret_key as ed25519_dalek::SecretKey;
let esk = ed25519_dalek::hazmat::ExpandedSecretKey::from(&sk);
let esk = [esk.scalar.to_bytes(), esk.hash_prefix].concat();
let addr = Self::get_onion_address(&Self::get_public_key(secret_key)?);
let mut command = format!(
"ADD_ONION ED25519-V3:{} ",
base64::prelude::BASE64_STANDARD.encode(esk)
);
if let Some(flags) = flags {
if !flags.is_empty() {
command.push_str(&format!("Flags={} ", &flags.join(",")));
}
}
let mut service_listeners = HashSet::new();
let mut listeners = listeners.into_iter();
for (port, address) in listeners.by_ref() {
if !service_listeners.is_empty() {
command.push(' ');
}
if service_listeners.contains(&port) {
return Err(Error::new(ErrorKind::Unsupported, "Invalid listeners"));
}
service_listeners.insert(port);
let addr = address.to_socket_addrs()?.next().ok_or_else(|| {
Error::new(ErrorKind::Other, "Could not parse valid socket address")
})?;
command.push_str(&format!("Port={port},{addr}"));
}
if service_listeners.is_empty() {
return Err(Error::new(
ErrorKind::Unsupported,
"Invalid listener specification",
));
}
command.push_str("\r\n");
let (tx, mut rx) = tokio::sync::mpsc::channel(EVENT_QUEUE_SIZE);
self.send(Message::AddEventHandler(
AsyncEventKind::HiddenServiceDescriptors,
self.sender.clone(),
tx,
))
.await?;
let response = self.receive().await?;
if response.code != 250 {
return Err(Error::new(
ErrorKind::Unsupported,
format!(
"Invalid response code {}: {:?}",
response.code, response.data
),
));
}
self.send(Message::AddOnionService(
command.into(),
self.sender.clone(),
))
.await?;
let response = self.receive().await?;
if response.code != 250 {
return Err(Error::new(
ErrorKind::Unsupported,
format!(
"Invalid response code {}: {:?}",
response.code, response.data
),
));
}
let mut uploads = 0;
while uploads < UPLOAD_CONFIRMATIONS {
debug!("Got {uploads} confirmations");
if let Some(response) = rx.recv().await {
if response.code == 650
&& response
.data
.into_iter()
.any(|line| line.contains("UPLOADED") && line.contains(&addr))
{
uploads += 1;
}
} else {
return Err(Error::new(
ErrorKind::UnexpectedEof,
"Cannot receive any more confirmation events because channel was closed",
));
}
}
Ok(addr)
}
pub async fn delete_onion(&mut self, onion_address: &str) -> Result<(), Error> {
let onion_address = onion_address.trim_end_matches(".onion");
if !onion_address.chars().all(|c| {
matches!(c, 'a'..='z' | 'A'..='Z' | '2'..='7' )
}) {
return Err(Error::new(
ErrorKind::InvalidInput,
"Wrong characters in onion address",
));
}
self.send(Message::DeleteOnionService(
format!("DEL_ONION {onion_address}\r\n").into(),
self.sender.clone(),
))
.await?;
let response = self.receive().await?;
if response.code != 250 {
return Err(Error::new(
ErrorKind::Unsupported,
format!(
"Invalid response code {}: {:?}",
response.code, response.data
),
));
}
Ok(())
}
pub fn get_public_key(secret_key: &[u8; 32]) -> Result<[u8; 32], Error> {
let sk = ed25519_dalek::SigningKey::from_bytes(secret_key);
let pk = ed25519_dalek::VerifyingKey::from(&sk);
Ok(pk.to_bytes())
}
pub fn get_onion_address(public_key: &[u8; 32]) -> String {
let mut buf = [0u8; 35];
public_key.iter().copied().enumerate().for_each(|(i, b)| {
buf[i] = b;
});
let mut h = Sha3_256::new();
h.update(b".onion checksum");
h.update(public_key);
h.update(b"\x03");
let res_vec = h.finalize().to_vec();
buf[32] = res_vec[0];
buf[33] = res_vec[1];
buf[34] = 3;
base32::encode(base32::Alphabet::Rfc4648 { padding: false }, &buf).to_ascii_lowercase()
}
pub fn generate_key() -> [u8; 32] {
let sk = ed25519_dalek::SigningKey::generate(&mut thread_rng());
sk.to_bytes()
}
async fn receive(&mut self) -> Result<Response, Error> {
match self.receiver.recv().await {
Some(result) => result
.try_into()
.map_err(|err| Error::new(ErrorKind::Other, err)),
None => Err(Error::new(ErrorKind::Other, "Event-channel was closed")),
}
}
async fn send(&mut self, command: Message) -> Result<(), Error> {
self.connection
.send(command)
.await
.map_err(|err| Error::new(ErrorKind::Other, err))
}
}