voynich 0.1.1

Library for creating anonymous, end-to-end encrypted and authenticated chat applications
Documentation
use crate::config::TorAuthConfig;
use crate::onion_service::{OnionService, OnionType};
use crate::util::{get_onion_address, get_onion_service, save_onion_service};
use anyhow::{anyhow, Result};
use rpassword::read_password;
use std::io::Write;
use std::str::FromStr;
use tokio::net::ToSocketAddrs;
use tor_client_lib::{
    auth::TorAuthentication,
    control_connection::{
        OnionAddress, OnionServiceListener, OnionServiceMapping, TorControlConnection,
        TorSocketAddr,
    },
};

pub async fn connect_to_tor<A: ToSocketAddrs>(
    control_address: A,
    authentication: Option<TorAuthConfig>,
    hashed_password: Option<String>,
    cookie: Option<Vec<u8>>,
) -> Result<TorControlConnection> {
    let mut control_connection = match TorControlConnection::connect(control_address).await {
        Ok(control_connection) => control_connection,
        Err(error) => {
            return Err(anyhow!(
                "Error connecting to Tor control connection: {}",
                error
            ));
        }
    };

    let tor_authentication = match authentication {
        Some(TorAuthConfig::HashedPassword) => match hashed_password {
            Some(password) => TorAuthentication::HashedPassword(password),
            None => {
                print!("Type a password: ");
                std::io::stdout().flush().unwrap();
                let password = read_password().unwrap();
                TorAuthentication::HashedPassword(password)
            }
        },
        Some(TorAuthConfig::SafeCookie) => match cookie {
            Some(cookie) => TorAuthentication::SafeCookie(Some(cookie)),
            None => TorAuthentication::SafeCookie(None),
        },
        None => TorAuthentication::Null,
    };

    if let Err(error) = control_connection.authenticate(tor_authentication).await {
        return Err(anyhow!(
            "Error authenticating to Tor control connection: {}",
            error
        ));
    }

    Ok(control_connection)
}

pub async fn create_transient_onion_service(
    control_connection: &mut TorControlConnection,
    service_port: u16,
    listen_address: &TorSocketAddr,
) -> Result<OnionService> {
    match control_connection
        .create_onion_service(
            &[OnionServiceMapping::new(
                service_port,
                Some(listen_address.clone()),
            )],
            true,
            None,
        )
        .await
    {
        Ok(service) => Ok(service.into()),
        Err(error) => Err(anyhow!("{}", error)),
    }
}

pub async fn create_persistent_onion_service(
    control_connection: &mut TorControlConnection,
    name: &str,
    service_port: u16,
    listen_address: &TorSocketAddr,
) -> Result<OnionService> {
    match control_connection
        .create_onion_service(
            &[OnionServiceMapping::new(
                service_port,
                Some(listen_address.clone()),
            )],
            false,
            None,
        )
        .await
    {
        Ok(service) => {
            let onion_service = OnionService::new(name, service);
            save_onion_service(&onion_service, service_port)?;
            Ok(onion_service)
        }
        Err(error) => Err(anyhow!("{}", error)),
    }
}

pub async fn use_persistent_onion_service(
    control_connection: &mut TorControlConnection,
    onion_service: &OnionService,
) -> Result<()> {
    let service_ids = match control_connection.get_info("onions/detached").await {
        Ok(service_ids) => service_ids,
        Err(error) => {
            return Err(anyhow!("{}", error));
        }
    };

    if !service_ids.contains(&onion_service.service_id().to_string()) {
        match control_connection
            .create_onion_service(
                onion_service.ports(),
                false,
                Some(onion_service.signing_key()),
            )
            .await
        {
            Ok(_) => Ok(()),
            Err(error) => Err(anyhow!("{}", error)),
        }
    } else {
        Ok(())
    }
}

pub async fn create_onion_service(
    control_connection: &mut TorControlConnection,
    onion_type: OnionType,
    service_port: Option<u16>,
    listen_address: Option<TorSocketAddr>,
) -> Result<(OnionService, OnionAddress, OnionServiceListener)> {
    match onion_type {
        OnionType::Transient => {
            let service_port = match service_port {
                Some(port) => port,
                None => {
                    return Err(anyhow!(
                        "Error: No service port specified for transient onion service"
                    ));
                }
            };
            let listen_address = match listen_address.clone() {
                Some(listen_address) => listen_address,
                None => TorSocketAddr::from_str(&format!("127.0.0.1:{}", service_port)).unwrap(),
            };
            let service =
                create_transient_onion_service(control_connection, service_port, &listen_address)
                    .await?;
            let listener = OnionServiceListener::bind(listen_address.clone()).await?;
            let onion_service_address =
                OnionAddress::new(service.service_id().clone(), service_port);
            Ok((service, onion_service_address, listener))
        }
        OnionType::Persistent { name, create } => {
            if create {
                let listen_address = match listen_address.clone() {
                    Some(listen_address) => listen_address,
                    None => {
                        TorSocketAddr::from_str(&format!("127.0.0.1:{}", service_port.unwrap()))
                            .unwrap()
                    }
                };
                let onion_service = create_persistent_onion_service(
                    control_connection,
                    &name,
                    service_port.unwrap(),
                    &listen_address,
                )
                .await?;
                let listener = OnionServiceListener::bind(listen_address.clone()).await?;
                let onion_service_address =
                    OnionAddress::new(onion_service.service_id().clone(), service_port.unwrap());
                Ok((onion_service, onion_service_address, listener))
            } else {
                let onion_address = get_onion_address(&name)?;
                let listen_address = match listen_address.clone() {
                    Some(listen_address) => listen_address,
                    None => TorSocketAddr::from_str(&format!(
                        "127.0.0.1:{}",
                        onion_address.service_port()
                    ))
                    .unwrap(),
                };
                let onion_service = get_onion_service(&name, &onion_address, &listen_address)?;
                use_persistent_onion_service(control_connection, &onion_service).await?;
                let listener = OnionServiceListener::bind(listen_address.clone()).await?;
                Ok((onion_service, onion_address, listener))
            }
        }
    }
}