mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
chore: remove tor from startos core
Tor is being moved from a built-in OS feature to a service. This removes the Arti-based Tor client, onion address management, hidden service creation, and all related code from the core backend, frontend, and SDK. - Delete core/src/net/tor/ module (~2060 lines) - Remove OnionAddress, TorSecretKey, TorController from all consumers - Remove HostnameInfo::Onion and HostAddress::Onion variants - Remove onion CRUD RPC endpoints and tor subcommand - Remove tor key handling from account and backup/restore - Remove ~12 tor-related Cargo dependencies (arti-client, torut, etc.) - Remove tor UI components, API methods, mock data, and routes - Remove OnionHostname and tor patterns/regexes from SDK - Add v0_4_0_alpha_20 database migration to strip onion data - Bump version to 0.4.0-alpha.20
This commit is contained in:
@@ -8,7 +8,6 @@ use openssl::x509::X509;
|
||||
use crate::db::model::DatabaseModel;
|
||||
use crate::hostname::{Hostname, generate_hostname, generate_id};
|
||||
use crate::net::ssl::{gen_nistp256, make_root_cert};
|
||||
use crate::net::tor::TorSecretKey;
|
||||
use crate::prelude::*;
|
||||
use crate::util::serde::Pem;
|
||||
|
||||
@@ -26,7 +25,6 @@ pub struct AccountInfo {
|
||||
pub server_id: String,
|
||||
pub hostname: Hostname,
|
||||
pub password: String,
|
||||
pub tor_keys: Vec<TorSecretKey>,
|
||||
pub root_ca_key: PKey<Private>,
|
||||
pub root_ca_cert: X509,
|
||||
pub ssh_key: ssh_key::PrivateKey,
|
||||
@@ -36,7 +34,6 @@ impl AccountInfo {
|
||||
pub fn new(password: &str, start_time: SystemTime) -> Result<Self, Error> {
|
||||
let server_id = generate_id();
|
||||
let hostname = generate_hostname();
|
||||
let tor_key = vec![TorSecretKey::generate()];
|
||||
let root_ca_key = gen_nistp256()?;
|
||||
let root_ca_cert = make_root_cert(&root_ca_key, &hostname, start_time)?;
|
||||
let ssh_key = ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::random(
|
||||
@@ -48,7 +45,6 @@ impl AccountInfo {
|
||||
server_id,
|
||||
hostname,
|
||||
password: hash_password(password)?,
|
||||
tor_keys: tor_key,
|
||||
root_ca_key,
|
||||
root_ca_cert,
|
||||
ssh_key,
|
||||
@@ -61,17 +57,6 @@ impl AccountInfo {
|
||||
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
||||
let password = db.as_private().as_password().de()?;
|
||||
let key_store = db.as_private().as_key_store();
|
||||
let tor_addrs = db
|
||||
.as_public()
|
||||
.as_server_info()
|
||||
.as_network()
|
||||
.as_host()
|
||||
.as_onions()
|
||||
.de()?;
|
||||
let tor_keys = tor_addrs
|
||||
.into_iter()
|
||||
.map(|tor_addr| key_store.as_onion().get_key(&tor_addr))
|
||||
.collect::<Result<_, _>>()?;
|
||||
let cert_store = key_store.as_local_certs();
|
||||
let root_ca_key = cert_store.as_root_key().de()?.0;
|
||||
let root_ca_cert = cert_store.as_root_cert().de()?.0;
|
||||
@@ -82,7 +67,6 @@ impl AccountInfo {
|
||||
server_id,
|
||||
hostname,
|
||||
password,
|
||||
tor_keys,
|
||||
root_ca_key,
|
||||
root_ca_cert,
|
||||
ssh_key,
|
||||
@@ -97,17 +81,6 @@ impl AccountInfo {
|
||||
server_info
|
||||
.as_pubkey_mut()
|
||||
.ser(&self.ssh_key.public_key().to_openssh()?)?;
|
||||
server_info
|
||||
.as_network_mut()
|
||||
.as_host_mut()
|
||||
.as_onions_mut()
|
||||
.ser(
|
||||
&self
|
||||
.tor_keys
|
||||
.iter()
|
||||
.map(|tor_key| tor_key.onion_address())
|
||||
.collect(),
|
||||
)?;
|
||||
server_info.as_password_hash_mut().ser(&self.password)?;
|
||||
db.as_private_mut().as_password_mut().ser(&self.password)?;
|
||||
db.as_private_mut()
|
||||
@@ -117,9 +90,6 @@ impl AccountInfo {
|
||||
.as_developer_key_mut()
|
||||
.ser(Pem::new_ref(&self.developer_key))?;
|
||||
let key_store = db.as_private_mut().as_key_store_mut();
|
||||
for tor_key in &self.tor_keys {
|
||||
key_store.as_onion_mut().insert_key(tor_key)?;
|
||||
}
|
||||
let cert_store = key_store.as_local_certs_mut();
|
||||
if cert_store.as_root_cert().de()?.0 != self.root_ca_cert {
|
||||
cert_store
|
||||
@@ -148,11 +118,5 @@ impl AccountInfo {
|
||||
self.hostname.no_dot_host_name(),
|
||||
self.hostname.local_domain_name(),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(
|
||||
self.tor_keys
|
||||
.iter()
|
||||
.map(|k| InternedString::from_display(&k.onion_address())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,7 @@ use ssh_key::private::Ed25519Keypair;
|
||||
|
||||
use crate::account::AccountInfo;
|
||||
use crate::hostname::{Hostname, generate_hostname, generate_id};
|
||||
use crate::net::tor::TorSecretKey;
|
||||
use crate::prelude::*;
|
||||
use crate::util::crypto::ed25519_expand_key;
|
||||
use crate::util::serde::{Base32, Base64, Pem};
|
||||
|
||||
pub struct OsBackup {
|
||||
@@ -85,10 +83,6 @@ impl OsBackupV0 {
|
||||
&mut ssh_key::rand_core::OsRng::default(),
|
||||
ssh_key::Algorithm::Ed25519,
|
||||
)?,
|
||||
tor_keys: TorSecretKey::from_bytes(self.tor_key.0)
|
||||
.ok()
|
||||
.into_iter()
|
||||
.collect(),
|
||||
developer_key: ed25519_dalek::SigningKey::generate(
|
||||
&mut ssh_key::rand_core::OsRng::default(),
|
||||
),
|
||||
@@ -119,10 +113,6 @@ impl OsBackupV1 {
|
||||
root_ca_key: self.root_ca_key.0,
|
||||
root_ca_cert: self.root_ca_cert.0,
|
||||
ssh_key: ssh_key::PrivateKey::from(Ed25519Keypair::from_seed(&self.net_key.0)),
|
||||
tor_keys: TorSecretKey::from_bytes(ed25519_expand_key(&self.net_key.0))
|
||||
.ok()
|
||||
.into_iter()
|
||||
.collect(),
|
||||
developer_key: ed25519_dalek::SigningKey::from_bytes(&self.net_key),
|
||||
},
|
||||
ui: self.ui,
|
||||
@@ -140,7 +130,6 @@ struct OsBackupV2 {
|
||||
root_ca_key: Pem<PKey<Private>>, // PEM Encoded OpenSSL Key
|
||||
root_ca_cert: Pem<X509>, // PEM Encoded OpenSSL X509 Certificate
|
||||
ssh_key: Pem<ssh_key::PrivateKey>, // PEM Encoded OpenSSH Key
|
||||
tor_keys: Vec<TorSecretKey>, // Base64 Encoded Ed25519 Expanded Secret Key
|
||||
compat_s9pk_key: Pem<ed25519_dalek::SigningKey>, // PEM Encoded ED25519 Key
|
||||
ui: Value, // JSON Value
|
||||
}
|
||||
@@ -154,7 +143,6 @@ impl OsBackupV2 {
|
||||
root_ca_key: self.root_ca_key.0,
|
||||
root_ca_cert: self.root_ca_cert.0,
|
||||
ssh_key: self.ssh_key.0,
|
||||
tor_keys: self.tor_keys,
|
||||
developer_key: self.compat_s9pk_key.0,
|
||||
},
|
||||
ui: self.ui,
|
||||
@@ -167,7 +155,6 @@ impl OsBackupV2 {
|
||||
root_ca_key: Pem(backup.account.root_ca_key.clone()),
|
||||
root_ca_cert: Pem(backup.account.root_ca_cert.clone()),
|
||||
ssh_key: Pem(backup.account.ssh_key.clone()),
|
||||
tor_keys: backup.account.tor_keys.clone(),
|
||||
compat_s9pk_key: Pem(backup.account.developer_key.clone()),
|
||||
ui: backup.ui.clone(),
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ impl Public {
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
onions: account.tor_keys.iter().map(|k| k.onion_address()).collect(),
|
||||
public_domains: BTreeMap::new(),
|
||||
private_domains: BTreeSet::new(),
|
||||
hostname_info: BTreeMap::new(),
|
||||
|
||||
@@ -42,11 +42,11 @@ pub enum ErrorKind {
|
||||
ParseUrl = 19,
|
||||
DiskNotAvailable = 20,
|
||||
BlockDevice = 21,
|
||||
InvalidOnionAddress = 22,
|
||||
// InvalidOnionAddress = 22,
|
||||
Pack = 23,
|
||||
ValidateS9pk = 24,
|
||||
DiskCorrupted = 25, // Remove
|
||||
Tor = 26,
|
||||
// Tor = 26,
|
||||
ConfigGen = 27,
|
||||
ParseNumber = 28,
|
||||
Database = 29,
|
||||
@@ -126,11 +126,11 @@ impl ErrorKind {
|
||||
ParseUrl => t!("error.parse-url"),
|
||||
DiskNotAvailable => t!("error.disk-not-available"),
|
||||
BlockDevice => t!("error.block-device"),
|
||||
InvalidOnionAddress => t!("error.invalid-onion-address"),
|
||||
// InvalidOnionAddress => t!("error.invalid-onion-address"),
|
||||
Pack => t!("error.pack"),
|
||||
ValidateS9pk => t!("error.validate-s9pk"),
|
||||
DiskCorrupted => t!("error.disk-corrupted"), // Remove
|
||||
Tor => t!("error.tor"),
|
||||
// Tor => t!("error.tor"),
|
||||
ConfigGen => t!("error.config-gen"),
|
||||
ParseNumber => t!("error.parse-number"),
|
||||
Database => t!("error.database"),
|
||||
@@ -370,17 +370,6 @@ impl From<reqwest::Error> for Error {
|
||||
Error::new(e, kind)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "arti")]
|
||||
impl From<arti_client::Error> for Error {
|
||||
fn from(e: arti_client::Error) -> Self {
|
||||
Error::new(e, ErrorKind::Tor)
|
||||
}
|
||||
}
|
||||
impl From<torut::control::ConnError> for Error {
|
||||
fn from(e: torut::control::ConnError) -> Self {
|
||||
Error::new(e, ErrorKind::Tor)
|
||||
}
|
||||
}
|
||||
impl From<zbus::Error> for Error {
|
||||
fn from(e: zbus::Error) -> Self {
|
||||
Error::new(e, ErrorKind::DBus)
|
||||
|
||||
@@ -12,7 +12,6 @@ use crate::context::{CliContext, RpcContext};
|
||||
use crate::db::model::DatabaseModel;
|
||||
use crate::net::acme::AcmeProvider;
|
||||
use crate::net::host::{HostApiKind, all_hosts};
|
||||
use crate::net::tor::OnionAddress;
|
||||
use crate::prelude::*;
|
||||
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
||||
|
||||
@@ -21,9 +20,6 @@ use crate::util::serde::{HandlerExtSerde, display_serializable};
|
||||
#[serde(rename_all_fields = "camelCase")]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum HostAddress {
|
||||
Onion {
|
||||
address: OnionAddress,
|
||||
},
|
||||
Domain {
|
||||
address: InternedString,
|
||||
public: Option<PublicDomainConfig>,
|
||||
@@ -38,18 +34,7 @@ pub struct PublicDomainConfig {
|
||||
}
|
||||
|
||||
fn handle_duplicates(db: &mut DatabaseModel) -> Result<(), Error> {
|
||||
let mut onions = BTreeSet::<OnionAddress>::new();
|
||||
let mut domains = BTreeSet::<InternedString>::new();
|
||||
let check_onion = |onions: &mut BTreeSet<OnionAddress>, onion: OnionAddress| {
|
||||
if onions.contains(&onion) {
|
||||
return Err(Error::new(
|
||||
eyre!("onion address {onion} is already in use"),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
onions.insert(onion);
|
||||
Ok(())
|
||||
};
|
||||
let check_domain = |domains: &mut BTreeSet<InternedString>, domain: InternedString| {
|
||||
if domains.contains(&domain) {
|
||||
return Err(Error::new(
|
||||
@@ -68,9 +53,6 @@ fn handle_duplicates(db: &mut DatabaseModel) -> Result<(), Error> {
|
||||
not_in_use.push(host);
|
||||
continue;
|
||||
}
|
||||
for onion in host.as_onions().de()? {
|
||||
check_onion(&mut onions, onion)?;
|
||||
}
|
||||
let public = host.as_public_domains().keys()?;
|
||||
for domain in &public {
|
||||
check_domain(&mut domains, domain.clone())?;
|
||||
@@ -82,16 +64,11 @@ fn handle_duplicates(db: &mut DatabaseModel) -> Result<(), Error> {
|
||||
}
|
||||
}
|
||||
for host in not_in_use {
|
||||
host.as_onions_mut()
|
||||
.mutate(|o| Ok(o.retain(|o| !onions.contains(o))))?;
|
||||
host.as_public_domains_mut()
|
||||
.mutate(|d| Ok(d.retain(|d, _| !domains.contains(d))))?;
|
||||
host.as_private_domains_mut()
|
||||
.mutate(|d| Ok(d.retain(|d| !domains.contains(d))))?;
|
||||
|
||||
for onion in host.as_onions().de()? {
|
||||
check_onion(&mut onions, onion)?;
|
||||
}
|
||||
let public = host.as_public_domains().keys()?;
|
||||
for domain in &public {
|
||||
check_domain(&mut domains, domain.clone())?;
|
||||
@@ -159,29 +136,6 @@ pub fn address_api<C: Context, Kind: HostApiKind>()
|
||||
)
|
||||
.with_inherited(Kind::inheritance),
|
||||
)
|
||||
.subcommand(
|
||||
"onion",
|
||||
ParentHandler::<C, Empty, Kind::Inheritance>::new()
|
||||
.subcommand(
|
||||
"add",
|
||||
from_fn_async(add_onion::<Kind>)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.with_inherited(|_, a| a)
|
||||
.no_display()
|
||||
.with_about("about.add-address-to-host")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"remove",
|
||||
from_fn_async(remove_onion::<Kind>)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.with_inherited(|_, a| a)
|
||||
.no_display()
|
||||
.with_about("about.remove-address-from-host")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.with_inherited(Kind::inheritance),
|
||||
)
|
||||
.subcommand(
|
||||
"list",
|
||||
from_fn_async(list_addresses::<Kind>)
|
||||
@@ -199,9 +153,6 @@ pub fn address_api<C: Context, Kind: HostApiKind>()
|
||||
table.add_row(row![bc => "ADDRESS", "PUBLIC", "ACME PROVIDER"]);
|
||||
for address in &res {
|
||||
match address {
|
||||
HostAddress::Onion { address } => {
|
||||
table.add_row(row![address, true, "N/A"]);
|
||||
}
|
||||
HostAddress::Domain {
|
||||
address,
|
||||
public: Some(PublicDomainConfig { gateway, acme }),
|
||||
@@ -351,55 +302,6 @@ pub async fn remove_private_domain<Kind: HostApiKind>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct OnionParams {
|
||||
#[arg(help = "help.arg.onion-address")]
|
||||
pub onion: String,
|
||||
}
|
||||
|
||||
pub async fn add_onion<Kind: HostApiKind>(
|
||||
ctx: RpcContext,
|
||||
OnionParams { onion }: OnionParams,
|
||||
inheritance: Kind::Inheritance,
|
||||
) -> Result<(), Error> {
|
||||
let onion = onion.parse::<OnionAddress>()?;
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_private().as_key_store().as_onion().get_key(&onion)?;
|
||||
|
||||
Kind::host_for(&inheritance, db)?
|
||||
.as_onions_mut()
|
||||
.mutate(|a| Ok(a.insert(onion)))?;
|
||||
handle_duplicates(db)
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
|
||||
Kind::sync_host(&ctx, inheritance).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_onion<Kind: HostApiKind>(
|
||||
ctx: RpcContext,
|
||||
OnionParams { onion }: OnionParams,
|
||||
inheritance: Kind::Inheritance,
|
||||
) -> Result<(), Error> {
|
||||
let onion = onion.parse::<OnionAddress>()?;
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
Kind::host_for(&inheritance, db)?
|
||||
.as_onions_mut()
|
||||
.mutate(|a| Ok(a.remove(&onion)))
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
|
||||
Kind::sync_host(&ctx, inheritance).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_addresses<Kind: HostApiKind>(
|
||||
ctx: RpcContext,
|
||||
_: Empty,
|
||||
|
||||
@@ -15,7 +15,6 @@ use crate::net::forward::AvailablePorts;
|
||||
use crate::net::host::address::{HostAddress, PublicDomainConfig, address_api};
|
||||
use crate::net::host::binding::{BindInfo, BindOptions, binding};
|
||||
use crate::net::service_interface::HostnameInfo;
|
||||
use crate::net::tor::OnionAddress;
|
||||
use crate::prelude::*;
|
||||
use crate::{HostId, PackageId};
|
||||
|
||||
@@ -28,8 +27,6 @@ pub mod binding;
|
||||
#[ts(export)]
|
||||
pub struct Host {
|
||||
pub bindings: BTreeMap<u16, BindInfo>,
|
||||
#[ts(type = "string[]")]
|
||||
pub onions: BTreeSet<OnionAddress>,
|
||||
pub public_domains: BTreeMap<InternedString, PublicDomainConfig>,
|
||||
pub private_domains: BTreeSet<InternedString>,
|
||||
/// COMPUTED: NetService::update
|
||||
@@ -46,19 +43,13 @@ impl Host {
|
||||
Self::default()
|
||||
}
|
||||
pub fn addresses<'a>(&'a self) -> impl Iterator<Item = HostAddress> + 'a {
|
||||
self.onions
|
||||
self.public_domains
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|address| HostAddress::Onion { address })
|
||||
.chain(
|
||||
self.public_domains
|
||||
.iter()
|
||||
.map(|(address, config)| HostAddress::Domain {
|
||||
address: address.clone(),
|
||||
public: Some(config.clone()),
|
||||
private: self.private_domains.contains(address),
|
||||
}),
|
||||
)
|
||||
.map(|(address, config)| HostAddress::Domain {
|
||||
address: address.clone(),
|
||||
public: Some(config.clone()),
|
||||
private: self.private_domains.contains(address),
|
||||
})
|
||||
.chain(
|
||||
self.private_domains
|
||||
.iter()
|
||||
@@ -112,22 +103,7 @@ pub fn host_for<'a>(
|
||||
.as_hosts_mut(),
|
||||
)
|
||||
}
|
||||
let tor_key = if host_info(db, package_id)?.as_idx(host_id).is_none() {
|
||||
Some(
|
||||
db.as_private_mut()
|
||||
.as_key_store_mut()
|
||||
.as_onion_mut()
|
||||
.new_key()?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
host_info(db, package_id)?.upsert(host_id, || {
|
||||
let mut h = Host::new();
|
||||
h.onions
|
||||
.insert(tor_key.or_not_found("generated tor key")?.onion_address());
|
||||
Ok(h)
|
||||
})
|
||||
host_info(db, package_id)?.upsert(host_id, || Ok(Host::new()))
|
||||
}
|
||||
|
||||
pub fn all_hosts(db: &mut DatabaseModel) -> impl Iterator<Item = Result<&mut Model<Host>, Error>> {
|
||||
|
||||
@@ -3,28 +3,21 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::account::AccountInfo;
|
||||
use crate::net::acme::AcmeCertStore;
|
||||
use crate::net::ssl::CertStore;
|
||||
use crate::net::tor::OnionStore;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[model = "Model<Self>"]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KeyStore {
|
||||
pub onion: OnionStore,
|
||||
pub local_certs: CertStore,
|
||||
#[serde(default)]
|
||||
pub acme: AcmeCertStore,
|
||||
}
|
||||
impl KeyStore {
|
||||
pub fn new(account: &AccountInfo) -> Result<Self, Error> {
|
||||
let mut res = Self {
|
||||
onion: OnionStore::new(),
|
||||
Ok(Self {
|
||||
local_certs: CertStore::new(account)?,
|
||||
acme: AcmeCertStore::new(),
|
||||
};
|
||||
for tor_key in account.tor_keys.iter().cloned() {
|
||||
res.onion.insert(tor_key);
|
||||
}
|
||||
Ok(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ pub mod socks;
|
||||
pub mod ssl;
|
||||
pub mod static_server;
|
||||
pub mod tls;
|
||||
pub mod tor;
|
||||
pub mod tunnel;
|
||||
pub mod utils;
|
||||
pub mod vhost;
|
||||
@@ -23,7 +22,6 @@ pub mod wifi;
|
||||
|
||||
pub fn net_api<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand("tor", tor::tor_api::<C>().with_about("about.tor-commands"))
|
||||
.subcommand(
|
||||
"acme",
|
||||
acme::acme_api::<C>().with_about("about.setup-acme-certificate"),
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use imbl::{OrdMap, vector};
|
||||
use imbl::vector;
|
||||
use imbl_value::InternedString;
|
||||
use ipnet::IpNet;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -19,24 +19,22 @@ use crate::net::dns::DnsController;
|
||||
use crate::net::forward::{InterfacePortForwardController, START9_BRIDGE_IFACE, add_iptables_rule};
|
||||
use crate::net::gateway::{
|
||||
AndFilter, DynInterfaceFilter, IdFilter, InterfaceFilter, NetworkInterfaceController, OrFilter,
|
||||
PublicFilter, SecureFilter, TypeFilter,
|
||||
PublicFilter, SecureFilter,
|
||||
};
|
||||
use crate::net::host::address::HostAddress;
|
||||
use crate::net::host::binding::{AddSslOptions, BindId, BindOptions};
|
||||
use crate::net::host::{Host, Hosts, host_for};
|
||||
use crate::net::service_interface::{GatewayInfo, HostnameInfo, IpHostname, OnionHostname};
|
||||
use crate::net::service_interface::{GatewayInfo, HostnameInfo, IpHostname};
|
||||
use crate::net::socks::SocksController;
|
||||
use crate::net::tor::{OnionAddress, TorController, TorSecretKey};
|
||||
use crate::net::utils::ipv6_is_local;
|
||||
use crate::net::vhost::{AlpnInfo, DynVHostTarget, ProxyTarget, VHostController};
|
||||
use crate::prelude::*;
|
||||
use crate::service::effects::callbacks::ServiceCallbacks;
|
||||
use crate::util::serde::MaybeUtf8String;
|
||||
use crate::{GatewayId, HOST_IP, HostId, OptionExt, PackageId};
|
||||
use crate::{HOST_IP, HostId, OptionExt, PackageId};
|
||||
|
||||
pub struct NetController {
|
||||
pub(crate) db: TypedPatchDb<Database>,
|
||||
pub(super) tor: TorController,
|
||||
pub(super) vhost: VHostController,
|
||||
pub(super) tls_client_config: Arc<TlsClientConfig>,
|
||||
pub(crate) net_iface: Arc<NetworkInterfaceController>,
|
||||
@@ -54,8 +52,7 @@ impl NetController {
|
||||
socks_listen: SocketAddr,
|
||||
) -> Result<Self, Error> {
|
||||
let net_iface = Arc::new(NetworkInterfaceController::new(db.clone()));
|
||||
let tor = TorController::new()?;
|
||||
let socks = SocksController::new(socks_listen, tor.clone())?;
|
||||
let socks = SocksController::new(socks_listen)?;
|
||||
let crypto_provider = Arc::new(tokio_rustls::rustls::crypto::ring::default_provider());
|
||||
let tls_client_config = Arc::new(crate::net::tls::client_config(
|
||||
crypto_provider.clone(),
|
||||
@@ -87,7 +84,6 @@ impl NetController {
|
||||
.await?;
|
||||
Ok(Self {
|
||||
db: db.clone(),
|
||||
tor,
|
||||
vhost: VHostController::new(db.clone(), net_iface.clone(), crypto_provider),
|
||||
tls_client_config,
|
||||
dns: DnsController::init(db, &net_iface.watcher).await?,
|
||||
@@ -168,7 +164,6 @@ struct HostBinds {
|
||||
forwards: BTreeMap<u16, (SocketAddrV4, DynInterfaceFilter, Arc<()>)>,
|
||||
vhosts: BTreeMap<(Option<InternedString>, u16), (ProxyTarget, Arc<()>)>,
|
||||
private_dns: BTreeMap<InternedString, Arc<()>>,
|
||||
tor: BTreeMap<OnionAddress, (OrdMap<u16, SocketAddr>, Vec<Arc<()>>)>,
|
||||
}
|
||||
|
||||
pub struct NetServiceData {
|
||||
@@ -260,8 +255,6 @@ impl NetServiceData {
|
||||
let mut forwards: BTreeMap<u16, (SocketAddrV4, DynInterfaceFilter)> = BTreeMap::new();
|
||||
let mut vhosts: BTreeMap<(Option<InternedString>, u16), ProxyTarget> = BTreeMap::new();
|
||||
let mut private_dns: BTreeSet<InternedString> = BTreeSet::new();
|
||||
let mut tor: BTreeMap<OnionAddress, (TorSecretKey, OrdMap<u16, SocketAddr>)> =
|
||||
BTreeMap::new();
|
||||
let mut hostname_info: BTreeMap<u16, Vec<HostnameInfo>> = BTreeMap::new();
|
||||
let binds = self.binds.entry(id.clone()).or_default();
|
||||
|
||||
@@ -308,29 +301,6 @@ impl NetServiceData {
|
||||
}
|
||||
for address in host.addresses() {
|
||||
match address {
|
||||
HostAddress::Onion { address } => {
|
||||
let hostname = InternedString::from_display(&address);
|
||||
if hostnames.insert(hostname.clone()) {
|
||||
vhosts.insert(
|
||||
(Some(hostname), external),
|
||||
ProxyTarget {
|
||||
filter: OrFilter(
|
||||
TypeFilter(NetworkInterfaceType::Loopback),
|
||||
IdFilter(GatewayId::from(InternedString::from(
|
||||
START9_BRIDGE_IFACE,
|
||||
))),
|
||||
)
|
||||
.into_dyn(),
|
||||
acme: None,
|
||||
addr,
|
||||
add_x_forwarded_headers: ssl.add_x_forwarded_headers,
|
||||
connect_ssl: connect_ssl
|
||||
.clone()
|
||||
.map(|_| ctrl.tls_client_config.clone()),
|
||||
},
|
||||
); // TODO: wrap onion ssl stream directly in tor ctrl
|
||||
}
|
||||
}
|
||||
HostAddress::Domain {
|
||||
address,
|
||||
public,
|
||||
@@ -615,66 +585,6 @@ impl NetServiceData {
|
||||
}
|
||||
}
|
||||
|
||||
struct TorHostnamePorts {
|
||||
non_ssl: Option<u16>,
|
||||
ssl: Option<u16>,
|
||||
}
|
||||
let mut tor_hostname_ports = BTreeMap::<u16, TorHostnamePorts>::new();
|
||||
let mut tor_binds = OrdMap::<u16, SocketAddr>::new();
|
||||
for (internal, info) in &host.bindings {
|
||||
if !info.enabled {
|
||||
continue;
|
||||
}
|
||||
tor_binds.insert(
|
||||
info.options.preferred_external_port,
|
||||
SocketAddr::from((self.ip, *internal)),
|
||||
);
|
||||
if let (Some(ssl), Some(ssl_internal)) =
|
||||
(&info.options.add_ssl, info.net.assigned_ssl_port)
|
||||
{
|
||||
tor_binds.insert(
|
||||
ssl.preferred_external_port,
|
||||
SocketAddr::from(([127, 0, 0, 1], ssl_internal)),
|
||||
);
|
||||
tor_hostname_ports.insert(
|
||||
*internal,
|
||||
TorHostnamePorts {
|
||||
non_ssl: Some(info.options.preferred_external_port)
|
||||
.filter(|p| *p != ssl.preferred_external_port),
|
||||
ssl: Some(ssl.preferred_external_port),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
tor_hostname_ports.insert(
|
||||
*internal,
|
||||
TorHostnamePorts {
|
||||
non_ssl: Some(info.options.preferred_external_port),
|
||||
ssl: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for tor_addr in host.onions.iter() {
|
||||
let key = peek
|
||||
.as_private()
|
||||
.as_key_store()
|
||||
.as_onion()
|
||||
.get_key(tor_addr)?;
|
||||
tor.insert(key.onion_address(), (key, tor_binds.clone()));
|
||||
for (internal, ports) in &tor_hostname_ports {
|
||||
let mut bind_hostname_info = hostname_info.remove(internal).unwrap_or_default();
|
||||
bind_hostname_info.push(HostnameInfo::Onion {
|
||||
hostname: OnionHostname {
|
||||
value: InternedString::from_display(tor_addr),
|
||||
port: ports.non_ssl,
|
||||
ssl_port: ports.ssl,
|
||||
},
|
||||
});
|
||||
hostname_info.insert(*internal, bind_hostname_info);
|
||||
}
|
||||
}
|
||||
|
||||
let all = binds
|
||||
.forwards
|
||||
.keys()
|
||||
@@ -763,48 +673,9 @@ impl NetServiceData {
|
||||
}
|
||||
ctrl.dns.gc_private_domains(&rm)?;
|
||||
|
||||
let all = binds
|
||||
.tor
|
||||
.keys()
|
||||
.chain(tor.keys())
|
||||
.cloned()
|
||||
.collect::<BTreeSet<_>>();
|
||||
for onion in all {
|
||||
let mut prev = binds.tor.remove(&onion);
|
||||
if let Some((key, tor_binds)) = tor.remove(&onion).filter(|(_, b)| !b.is_empty()) {
|
||||
prev = prev.filter(|(b, _)| b == &tor_binds);
|
||||
binds.tor.insert(
|
||||
onion,
|
||||
if let Some(prev) = prev {
|
||||
prev
|
||||
} else {
|
||||
let service = ctrl.tor.service(key)?;
|
||||
let rcs = service.proxy_all(tor_binds.iter().map(|(k, v)| (*k, *v)));
|
||||
(tor_binds, rcs)
|
||||
},
|
||||
);
|
||||
} else {
|
||||
if let Some((_, rc)) = prev {
|
||||
drop(rc);
|
||||
ctrl.tor.gc(Some(onion)).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let res = ctrl
|
||||
.db
|
||||
.mutate(|db| {
|
||||
host_for(db, self.id.as_ref(), &id)?
|
||||
.as_hostname_info_mut()
|
||||
.ser(&hostname_info)
|
||||
})
|
||||
.await;
|
||||
res.result?;
|
||||
if let Some(pkg_id) = self.id.as_ref() {
|
||||
if res.revision.is_some() {
|
||||
if let Some(cbs) = ctrl.callbacks.get_host_info(&(pkg_id.clone(), id)) {
|
||||
cbs.call(vector![]).await?;
|
||||
}
|
||||
if let Some(cbs) = ctrl.callbacks.get_host_info(&(pkg_id.clone(), id)) {
|
||||
cbs.call(vector![]).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -17,15 +17,11 @@ pub enum HostnameInfo {
|
||||
public: bool,
|
||||
hostname: IpHostname,
|
||||
},
|
||||
Onion {
|
||||
hostname: OnionHostname,
|
||||
},
|
||||
}
|
||||
impl HostnameInfo {
|
||||
pub fn to_san_hostname(&self) -> InternedString {
|
||||
match self {
|
||||
Self::Ip { hostname, .. } => hostname.to_san_hostname(),
|
||||
Self::Onion { hostname } => hostname.to_san_hostname(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,21 +35,6 @@ pub struct GatewayInfo {
|
||||
pub public: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OnionHostname {
|
||||
#[ts(type = "string")]
|
||||
pub value: InternedString,
|
||||
pub port: Option<u16>,
|
||||
pub ssl_port: Option<u16>,
|
||||
}
|
||||
impl OnionHostname {
|
||||
pub fn to_san_hostname(&self) -> InternedString {
|
||||
self.value.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
@@ -8,7 +8,6 @@ use socks5_impl::server::{AuthAdaptor, ClientConnection, Server};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use crate::HOST_IP;
|
||||
use crate::net::tor::TorController;
|
||||
use crate::prelude::*;
|
||||
use crate::util::actor::background::BackgroundJobQueue;
|
||||
use crate::util::future::NonDetachingJoinHandle;
|
||||
@@ -22,7 +21,7 @@ pub struct SocksController {
|
||||
_thread: NonDetachingJoinHandle<()>,
|
||||
}
|
||||
impl SocksController {
|
||||
pub fn new(listen: SocketAddr, tor: TorController) -> Result<Self, Error> {
|
||||
pub fn new(listen: SocketAddr) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
_thread: tokio::spawn(async move {
|
||||
let auth: AuthAdaptor<()> = Arc::new(NoAuth);
|
||||
@@ -45,7 +44,6 @@ impl SocksController {
|
||||
loop {
|
||||
match server.accept().await {
|
||||
Ok((stream, _)) => {
|
||||
let tor = tor.clone();
|
||||
bg.add_job(async move {
|
||||
if let Err(e) = async {
|
||||
match stream
|
||||
@@ -57,40 +55,6 @@ impl SocksController {
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?
|
||||
{
|
||||
ClientConnection::Connect(
|
||||
reply,
|
||||
Address::DomainAddress(domain, port),
|
||||
) if domain.ends_with(".onion") => {
|
||||
if let Ok(mut target) = tor
|
||||
.connect_onion(&domain.parse()?, port)
|
||||
.await
|
||||
{
|
||||
let mut sock = reply
|
||||
.reply(
|
||||
Reply::Succeeded,
|
||||
Address::unspecified(),
|
||||
)
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
tokio::io::copy_bidirectional(
|
||||
&mut sock,
|
||||
&mut target,
|
||||
)
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
} else {
|
||||
let mut sock = reply
|
||||
.reply(
|
||||
Reply::HostUnreachable,
|
||||
Address::unspecified(),
|
||||
)
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
sock.shutdown()
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
}
|
||||
}
|
||||
ClientConnection::Connect(reply, addr) => {
|
||||
if let Ok(mut target) = match addr {
|
||||
Address::DomainAddress(domain, port) => {
|
||||
|
||||
@@ -1,964 +0,0 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::net::SocketAddr;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use arti_client::config::onion_service::OnionServiceConfigBuilder;
|
||||
use arti_client::{TorClient, TorClientConfig};
|
||||
use base64::Engine;
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use imbl_value::InternedString;
|
||||
use itertools::Itertools;
|
||||
use rpc_toolkit::{Context, Empty, HandlerExt, ParentHandler, from_fn_async};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::Notify;
|
||||
use tor_cell::relaycell::msg::Connected;
|
||||
use tor_hscrypto::pk::{HsId, HsIdKeypair};
|
||||
use tor_hsservice::status::State as ArtiOnionServiceState;
|
||||
use tor_hsservice::{HsNickname, RunningOnionService};
|
||||
use tor_keymgr::config::ArtiKeystoreKind;
|
||||
use tor_proto::client::stream::IncomingStreamRequest;
|
||||
use tor_rtcompat::tokio::TokioRustlsRuntime;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::prelude::*;
|
||||
use crate::util::actor::background::BackgroundJobQueue;
|
||||
use crate::util::future::{NonDetachingJoinHandle, Until};
|
||||
use crate::util::io::ReadWriter;
|
||||
use crate::util::serde::{
|
||||
BASE64, Base64, HandlerExtSerde, WithIoFormat, deserialize_from_str, display_serializable,
|
||||
serialize_display,
|
||||
};
|
||||
use crate::util::sync::{SyncMutex, SyncRwLock, Watch};
|
||||
|
||||
const BOOTSTRAP_PROGRESS_TIMEOUT: Duration = Duration::from_secs(300);
|
||||
const HS_BOOTSTRAP_TIMEOUT: Duration = Duration::from_secs(300);
|
||||
const RETRY_COOLDOWN: Duration = Duration::from_secs(15);
|
||||
const HEALTH_CHECK_FAILURE_ALLOWANCE: usize = 5;
|
||||
const HEALTH_CHECK_COOLDOWN: Duration = Duration::from_secs(120);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct OnionAddress(pub HsId);
|
||||
impl std::fmt::Display for OnionAddress {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
safelog::DisplayRedacted::fmt_unredacted(&self.0, f)
|
||||
}
|
||||
}
|
||||
impl FromStr for OnionAddress {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(
|
||||
if s.ends_with(".onion") {
|
||||
Cow::Borrowed(s)
|
||||
} else {
|
||||
Cow::Owned(format!("{s}.onion"))
|
||||
}
|
||||
.parse::<HsId>()
|
||||
.with_kind(ErrorKind::Tor)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
impl Serialize for OnionAddress {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serialize_display(self, serializer)
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for OnionAddress {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserialize_from_str(deserializer)
|
||||
}
|
||||
}
|
||||
impl PartialEq for OnionAddress {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.as_ref() == other.0.as_ref()
|
||||
}
|
||||
}
|
||||
impl Eq for OnionAddress {}
|
||||
impl PartialOrd for OnionAddress {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
self.0.as_ref().partial_cmp(other.0.as_ref())
|
||||
}
|
||||
}
|
||||
impl Ord for OnionAddress {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.0.as_ref().cmp(other.0.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TorSecretKey(pub HsIdKeypair);
|
||||
impl TorSecretKey {
|
||||
pub fn onion_address(&self) -> OnionAddress {
|
||||
OnionAddress(HsId::from(self.0.as_ref().public().to_bytes()))
|
||||
}
|
||||
pub fn from_bytes(bytes: [u8; 64]) -> Result<Self, Error> {
|
||||
Ok(Self(
|
||||
tor_llcrypto::pk::ed25519::ExpandedKeypair::from_secret_key_bytes(bytes)
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("{}", t!("net.tor.invalid-ed25519-key")),
|
||||
ErrorKind::Tor,
|
||||
)
|
||||
})?
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
pub fn generate() -> Self {
|
||||
Self(
|
||||
tor_llcrypto::pk::ed25519::ExpandedKeypair::from(
|
||||
&tor_llcrypto::pk::ed25519::Keypair::generate(&mut rand::rng()),
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl Clone for TorSecretKey {
|
||||
fn clone(&self) -> Self {
|
||||
Self(HsIdKeypair::from(
|
||||
tor_llcrypto::pk::ed25519::ExpandedKeypair::from_secret_key_bytes(
|
||||
self.0.as_ref().to_secret_key_bytes(),
|
||||
)
|
||||
.unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for TorSecretKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
BASE64.encode(self.0.as_ref().to_secret_key_bytes())
|
||||
)
|
||||
}
|
||||
}
|
||||
impl FromStr for TorSecretKey {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Self::from_bytes(Base64::<[u8; 64]>::from_str(s)?.0)
|
||||
}
|
||||
}
|
||||
impl Serialize for TorSecretKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serialize_display(self, serializer)
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for TorSecretKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserialize_from_str(deserializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub struct OnionStore(BTreeMap<OnionAddress, TorSecretKey>);
|
||||
impl Map for OnionStore {
|
||||
type Key = OnionAddress;
|
||||
type Value = TorSecretKey;
|
||||
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
||||
Self::key_string(key)
|
||||
}
|
||||
fn key_string(key: &Self::Key) -> Result<imbl_value::InternedString, Error> {
|
||||
Ok(InternedString::from_display(key))
|
||||
}
|
||||
}
|
||||
impl OnionStore {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn insert(&mut self, key: TorSecretKey) {
|
||||
self.0.insert(key.onion_address(), key);
|
||||
}
|
||||
}
|
||||
impl Model<OnionStore> {
|
||||
pub fn new_key(&mut self) -> Result<TorSecretKey, Error> {
|
||||
let key = TorSecretKey::generate();
|
||||
self.insert(&key.onion_address(), &key)?;
|
||||
Ok(key)
|
||||
}
|
||||
pub fn insert_key(&mut self, key: &TorSecretKey) -> Result<(), Error> {
|
||||
self.insert(&key.onion_address(), &key)
|
||||
}
|
||||
pub fn get_key(&self, address: &OnionAddress) -> Result<TorSecretKey, Error> {
|
||||
self.as_idx(address)
|
||||
.or_not_found(lazy_format!("private key for {address}"))?
|
||||
.de()
|
||||
}
|
||||
}
|
||||
impl std::fmt::Debug for OnionStore {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
struct OnionStoreMap<'a>(&'a BTreeMap<OnionAddress, TorSecretKey>);
|
||||
impl<'a> std::fmt::Debug for OnionStoreMap<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct KeyFor(#[allow(unused)] OnionAddress);
|
||||
let mut map = f.debug_map();
|
||||
for (k, v) in self.0 {
|
||||
map.key(k);
|
||||
map.value(&KeyFor(v.onion_address()));
|
||||
}
|
||||
map.finish()
|
||||
}
|
||||
}
|
||||
f.debug_tuple("OnionStore")
|
||||
.field(&OnionStoreMap(&self.0))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tor_api<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand(
|
||||
"list-services",
|
||||
from_fn_async(list_services)
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|handle, result| display_services(handle.params, result))
|
||||
.with_about("about.display-tor-v3-onion-addresses")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"reset",
|
||||
from_fn_async(reset)
|
||||
.no_display()
|
||||
.with_about("about.reset-tor-daemon")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"key",
|
||||
key::<C>().with_about("about.manage-onion-service-key-store"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn key<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand(
|
||||
"generate",
|
||||
from_fn_async(generate_key)
|
||||
.with_about("about.generate-onion-service-key-add-to-store")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"add",
|
||||
from_fn_async(add_key)
|
||||
.with_about("about.add-onion-service-key-to-store")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"list",
|
||||
from_fn_async(list_keys)
|
||||
.with_custom_display_fn(|_, res| {
|
||||
for addr in res {
|
||||
println!("{addr}");
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.with_about("about.list-onion-services-with-keys-in-store")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn generate_key(ctx: RpcContext) -> Result<OnionAddress, Error> {
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
Ok(db
|
||||
.as_private_mut()
|
||||
.as_key_store_mut()
|
||||
.as_onion_mut()
|
||||
.new_key()?
|
||||
.onion_address())
|
||||
})
|
||||
.await
|
||||
.result
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct AddKeyParams {
|
||||
#[arg(help = "help.arg.onion-secret-key")]
|
||||
pub key: Base64<[u8; 64]>,
|
||||
}
|
||||
|
||||
pub async fn add_key(
|
||||
ctx: RpcContext,
|
||||
AddKeyParams { key }: AddKeyParams,
|
||||
) -> Result<OnionAddress, Error> {
|
||||
let key = TorSecretKey::from_bytes(key.0)?;
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_private_mut()
|
||||
.as_key_store_mut()
|
||||
.as_onion_mut()
|
||||
.insert_key(&key)
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
Ok(key.onion_address())
|
||||
}
|
||||
|
||||
pub async fn list_keys(ctx: RpcContext) -> Result<BTreeSet<OnionAddress>, Error> {
|
||||
ctx.db
|
||||
.peek()
|
||||
.await
|
||||
.into_private()
|
||||
.into_key_store()
|
||||
.into_onion()
|
||||
.keys()
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ResetParams {
|
||||
#[arg(
|
||||
name = "wipe-state",
|
||||
short = 'w',
|
||||
long = "wipe-state",
|
||||
help = "help.arg.wipe-tor-state"
|
||||
)]
|
||||
wipe_state: bool,
|
||||
}
|
||||
|
||||
pub async fn reset(ctx: RpcContext, ResetParams { wipe_state }: ResetParams) -> Result<(), Error> {
|
||||
ctx.net_controller.tor.reset(wipe_state).await
|
||||
}
|
||||
|
||||
pub fn display_services(
|
||||
params: WithIoFormat<Empty>,
|
||||
services: BTreeMap<OnionAddress, OnionServiceInfo>,
|
||||
) -> Result<(), Error> {
|
||||
use prettytable::*;
|
||||
|
||||
if let Some(format) = params.format {
|
||||
return display_serializable(format, services);
|
||||
}
|
||||
|
||||
let mut table = Table::new();
|
||||
table.add_row(row![bc => "ADDRESS", "STATE", "BINDINGS"]);
|
||||
for (service, info) in services {
|
||||
let row = row![
|
||||
&service.to_string(),
|
||||
&format!("{:?}", info.state),
|
||||
&info
|
||||
.bindings
|
||||
.into_iter()
|
||||
.map(|(port, addr)| lazy_format!("{port} -> {addr}"))
|
||||
.join("; ")
|
||||
];
|
||||
table.add_row(row);
|
||||
}
|
||||
table.print_tty(false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum OnionServiceState {
|
||||
Shutdown,
|
||||
Bootstrapping,
|
||||
DegradedReachable,
|
||||
DegradedUnreachable,
|
||||
Running,
|
||||
Recovering,
|
||||
Broken,
|
||||
}
|
||||
impl From<ArtiOnionServiceState> for OnionServiceState {
|
||||
fn from(value: ArtiOnionServiceState) -> Self {
|
||||
match value {
|
||||
ArtiOnionServiceState::Shutdown => Self::Shutdown,
|
||||
ArtiOnionServiceState::Bootstrapping => Self::Bootstrapping,
|
||||
ArtiOnionServiceState::DegradedReachable => Self::DegradedReachable,
|
||||
ArtiOnionServiceState::DegradedUnreachable => Self::DegradedUnreachable,
|
||||
ArtiOnionServiceState::Running => Self::Running,
|
||||
ArtiOnionServiceState::Recovering => Self::Recovering,
|
||||
ArtiOnionServiceState::Broken => Self::Broken,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OnionServiceInfo {
|
||||
pub state: OnionServiceState,
|
||||
pub bindings: BTreeMap<u16, SocketAddr>,
|
||||
}
|
||||
|
||||
pub async fn list_services(
|
||||
ctx: RpcContext,
|
||||
_: Empty,
|
||||
) -> Result<BTreeMap<OnionAddress, OnionServiceInfo>, Error> {
|
||||
ctx.net_controller.tor.list_services().await
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TorController(Arc<TorControllerInner>);
|
||||
struct TorControllerInner {
|
||||
client: Watch<(usize, TorClient<TokioRustlsRuntime>)>,
|
||||
_bootstrapper: NonDetachingJoinHandle<()>,
|
||||
services: SyncMutex<BTreeMap<OnionAddress, OnionService>>,
|
||||
reset: Arc<Notify>,
|
||||
}
|
||||
impl TorController {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
let mut config = TorClientConfig::builder();
|
||||
config
|
||||
.storage()
|
||||
.keystore()
|
||||
.primary()
|
||||
.kind(ArtiKeystoreKind::Ephemeral.into());
|
||||
let client = Watch::new((
|
||||
0,
|
||||
TorClient::with_runtime(TokioRustlsRuntime::current()?)
|
||||
.config(config.build().with_kind(ErrorKind::Tor)?)
|
||||
.local_resource_timeout(Duration::from_secs(0))
|
||||
.create_unbootstrapped()?,
|
||||
));
|
||||
let reset = Arc::new(Notify::new());
|
||||
let bootstrapper_reset = reset.clone();
|
||||
let bootstrapper_client = client.clone();
|
||||
let bootstrapper = tokio::spawn(async move {
|
||||
loop {
|
||||
let (epoch, client): (usize, _) = bootstrapper_client.read();
|
||||
if let Err(e) = Until::new()
|
||||
.with_async_fn(|| bootstrapper_reset.notified().map(Ok))
|
||||
.run(async {
|
||||
let mut events = client.bootstrap_events();
|
||||
let bootstrap_fut =
|
||||
client.bootstrap().map(|res| res.with_kind(ErrorKind::Tor));
|
||||
let failure_fut = async {
|
||||
let mut prev_frac = 0_f32;
|
||||
let mut prev_inst = Instant::now();
|
||||
while let Some(event) =
|
||||
tokio::time::timeout(BOOTSTRAP_PROGRESS_TIMEOUT, events.next())
|
||||
.await
|
||||
.with_kind(ErrorKind::Tor)?
|
||||
{
|
||||
if event.ready_for_traffic() {
|
||||
return Ok::<_, Error>(());
|
||||
}
|
||||
let frac = event.as_frac();
|
||||
if frac == prev_frac {
|
||||
if prev_inst.elapsed() > BOOTSTRAP_PROGRESS_TIMEOUT {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"{}",
|
||||
t!(
|
||||
"net.tor.bootstrap-no-progress",
|
||||
duration = crate::util::serde::Duration::from(
|
||||
BOOTSTRAP_PROGRESS_TIMEOUT
|
||||
)
|
||||
.to_string()
|
||||
)
|
||||
),
|
||||
ErrorKind::Tor,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
prev_frac = frac;
|
||||
prev_inst = Instant::now();
|
||||
}
|
||||
}
|
||||
futures::future::pending().await
|
||||
};
|
||||
if let Err::<(), Error>(e) = tokio::select! {
|
||||
res = bootstrap_fut => res,
|
||||
res = failure_fut => res,
|
||||
} {
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("net.tor.bootstrap-error", error = e.to_string())
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
} else {
|
||||
bootstrapper_client.send_modify(|_| ());
|
||||
|
||||
for _ in 0..HEALTH_CHECK_FAILURE_ALLOWANCE {
|
||||
if let Err::<(), Error>(e) = async {
|
||||
loop {
|
||||
let (bg, mut runner) = BackgroundJobQueue::new();
|
||||
runner
|
||||
.run_while(async {
|
||||
const PING_BUF_LEN: usize = 8;
|
||||
let key = TorSecretKey::generate();
|
||||
let onion = key.onion_address();
|
||||
let (hs, stream) = client
|
||||
.launch_onion_service_with_hsid(
|
||||
OnionServiceConfigBuilder::default()
|
||||
.nickname(
|
||||
onion
|
||||
.to_string()
|
||||
.trim_end_matches(".onion")
|
||||
.parse::<HsNickname>()
|
||||
.with_kind(ErrorKind::Tor)?,
|
||||
)
|
||||
.build()
|
||||
.with_kind(ErrorKind::Tor)?,
|
||||
key.clone().0,
|
||||
)
|
||||
.with_kind(ErrorKind::Tor)?;
|
||||
bg.add_job(async move {
|
||||
if let Err(e) = async {
|
||||
let mut stream =
|
||||
tor_hsservice::handle_rend_requests(
|
||||
stream,
|
||||
);
|
||||
while let Some(req) = stream.next().await {
|
||||
let mut stream = req
|
||||
.accept(Connected::new_empty())
|
||||
.await
|
||||
.with_kind(ErrorKind::Tor)?;
|
||||
let mut buf = [0; PING_BUF_LEN];
|
||||
stream.read_exact(&mut buf).await?;
|
||||
stream.write_all(&buf).await?;
|
||||
stream.flush().await?;
|
||||
stream.shutdown().await?;
|
||||
}
|
||||
Ok::<_, Error>(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!(
|
||||
"net.tor.health-error",
|
||||
error = e.to_string()
|
||||
)
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
});
|
||||
|
||||
tokio::time::timeout(HS_BOOTSTRAP_TIMEOUT, async {
|
||||
let mut status = hs.status_events();
|
||||
while let Some(status) = status.next().await {
|
||||
if status.state().is_fully_reachable() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(Error::new(
|
||||
eyre!(
|
||||
"{}",
|
||||
t!("net.tor.status-stream-ended")
|
||||
),
|
||||
ErrorKind::Tor,
|
||||
))
|
||||
})
|
||||
.await
|
||||
.with_kind(ErrorKind::Tor)??;
|
||||
|
||||
let mut stream = client
|
||||
.connect((onion.to_string(), 8080))
|
||||
.await?;
|
||||
let mut ping_buf = [0; PING_BUF_LEN];
|
||||
rand::fill(&mut ping_buf);
|
||||
stream.write_all(&ping_buf).await?;
|
||||
stream.flush().await?;
|
||||
let mut ping_res = [0; PING_BUF_LEN];
|
||||
stream.read_exact(&mut ping_res).await?;
|
||||
ensure_code!(
|
||||
ping_buf == ping_res,
|
||||
ErrorKind::Tor,
|
||||
"ping buffer mismatch"
|
||||
);
|
||||
stream.shutdown().await?;
|
||||
|
||||
Ok::<_, Error>(())
|
||||
})
|
||||
.await?;
|
||||
tokio::time::sleep(HEALTH_CHECK_COOLDOWN).await;
|
||||
}
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("net.tor.client-health-error", error = e.to_string())
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
}
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!(
|
||||
"net.tor.health-check-failed-recycling",
|
||||
count = HEALTH_CHECK_FAILURE_ALLOWANCE
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("net.tor.bootstrapper-error", error = e.to_string())
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
if let Err::<(), Error>(e) = async {
|
||||
tokio::time::sleep(RETRY_COOLDOWN).await;
|
||||
bootstrapper_client.send((
|
||||
epoch.wrapping_add(1),
|
||||
TorClient::with_runtime(TokioRustlsRuntime::current()?)
|
||||
.config(config.build().with_kind(ErrorKind::Tor)?)
|
||||
.local_resource_timeout(Duration::from_secs(0))
|
||||
.create_unbootstrapped_async()
|
||||
.await?,
|
||||
));
|
||||
tracing::debug!("TorClient recycled");
|
||||
Ok(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("net.tor.client-creation-error", error = e.to_string())
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
}
|
||||
})
|
||||
.into();
|
||||
Ok(Self(Arc::new(TorControllerInner {
|
||||
client,
|
||||
_bootstrapper: bootstrapper,
|
||||
services: SyncMutex::new(BTreeMap::new()),
|
||||
reset,
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn service(&self, key: TorSecretKey) -> Result<OnionService, Error> {
|
||||
self.0.services.mutate(|s| {
|
||||
use std::collections::btree_map::Entry;
|
||||
let addr = key.onion_address();
|
||||
match s.entry(addr) {
|
||||
Entry::Occupied(e) => Ok(e.get().clone()),
|
||||
Entry::Vacant(e) => Ok(e
|
||||
.insert(OnionService::launch(self.0.client.clone(), key)?)
|
||||
.clone()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn gc(&self, addr: Option<OnionAddress>) -> Result<(), Error> {
|
||||
if let Some(addr) = addr {
|
||||
if let Some(s) = self.0.services.mutate(|s| {
|
||||
let rm = if let Some(s) = s.get(&addr) {
|
||||
!s.gc()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if rm { s.remove(&addr) } else { None }
|
||||
}) {
|
||||
s.shutdown().await
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
for s in self.0.services.mutate(|s| {
|
||||
let mut rm = Vec::new();
|
||||
s.retain(|_, s| {
|
||||
if s.gc() {
|
||||
true
|
||||
} else {
|
||||
rm.push(s.clone());
|
||||
false
|
||||
}
|
||||
});
|
||||
rm
|
||||
}) {
|
||||
s.shutdown().await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn reset(&self, wipe_state: bool) -> Result<(), Error> {
|
||||
self.0.reset.notify_waiters();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_services(&self) -> Result<BTreeMap<OnionAddress, OnionServiceInfo>, Error> {
|
||||
Ok(self
|
||||
.0
|
||||
.services
|
||||
.peek(|s| s.iter().map(|(a, s)| (a.clone(), s.info())).collect()))
|
||||
}
|
||||
|
||||
pub async fn connect_onion(
|
||||
&self,
|
||||
addr: &OnionAddress,
|
||||
port: u16,
|
||||
) -> Result<Box<dyn ReadWriter + Unpin + Send + Sync + 'static>, Error> {
|
||||
if let Some(target) = self.0.services.peek(|s| {
|
||||
s.get(addr).and_then(|s| {
|
||||
s.0.bindings.peek(|b| {
|
||||
b.get(&port).and_then(|b| {
|
||||
b.iter()
|
||||
.find(|(_, rc)| rc.strong_count() > 0)
|
||||
.map(|(a, _)| *a)
|
||||
})
|
||||
})
|
||||
})
|
||||
}) {
|
||||
let tcp_stream = TcpStream::connect(target)
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
if let Err(e) = socket2::SockRef::from(&tcp_stream).set_keepalive(true) {
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("net.tor.failed-to-set-tcp-keepalive", error = e.to_string())
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
Ok(Box::new(tcp_stream))
|
||||
} else {
|
||||
let mut client = self.0.client.clone();
|
||||
client
|
||||
.wait_for(|(_, c)| c.bootstrap_status().ready_for_traffic())
|
||||
.await;
|
||||
let stream = client
|
||||
.read()
|
||||
.1
|
||||
.connect((addr.to_string(), port))
|
||||
.await
|
||||
.with_kind(ErrorKind::Tor)?;
|
||||
Ok(Box::new(stream))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OnionService(Arc<OnionServiceData>);
|
||||
struct OnionServiceData {
|
||||
service: Arc<SyncMutex<Option<Arc<RunningOnionService>>>>,
|
||||
bindings: Arc<SyncRwLock<BTreeMap<u16, BTreeMap<SocketAddr, Weak<()>>>>>,
|
||||
_thread: NonDetachingJoinHandle<()>,
|
||||
}
|
||||
impl OnionService {
|
||||
fn launch(
|
||||
mut client: Watch<(usize, TorClient<TokioRustlsRuntime>)>,
|
||||
key: TorSecretKey,
|
||||
) -> Result<Self, Error> {
|
||||
let service = Arc::new(SyncMutex::new(None));
|
||||
let bindings = Arc::new(SyncRwLock::new(BTreeMap::<
|
||||
u16,
|
||||
BTreeMap<SocketAddr, Weak<()>>,
|
||||
>::new()));
|
||||
Ok(Self(Arc::new(OnionServiceData {
|
||||
service: service.clone(),
|
||||
bindings: bindings.clone(),
|
||||
_thread: tokio::spawn(async move {
|
||||
let (bg, mut runner) = BackgroundJobQueue::new();
|
||||
runner
|
||||
.run_while(async {
|
||||
loop {
|
||||
if let Err(e) = async {
|
||||
client.wait_for(|(_,c)| c.bootstrap_status().ready_for_traffic()).await;
|
||||
let epoch = client.peek(|(e, c)| {
|
||||
ensure_code!(c.bootstrap_status().ready_for_traffic(), ErrorKind::Tor, "TorClient recycled");
|
||||
Ok::<_, Error>(*e)
|
||||
})?;
|
||||
let addr = key.onion_address();
|
||||
let (new_service, stream) = client.peek(|(_, c)| {
|
||||
c.launch_onion_service_with_hsid(
|
||||
OnionServiceConfigBuilder::default()
|
||||
.nickname(
|
||||
addr
|
||||
.to_string()
|
||||
.trim_end_matches(".onion")
|
||||
.parse::<HsNickname>()
|
||||
.with_kind(ErrorKind::Tor)?,
|
||||
)
|
||||
.build()
|
||||
.with_kind(ErrorKind::Tor)?,
|
||||
key.clone().0,
|
||||
)
|
||||
.with_kind(ErrorKind::Tor)
|
||||
})?;
|
||||
let mut status_stream = new_service.status_events();
|
||||
let mut status = new_service.status();
|
||||
if status.state().is_fully_reachable() {
|
||||
tracing::debug!("{addr} is fully reachable");
|
||||
} else {
|
||||
tracing::debug!("{addr} is not fully reachable");
|
||||
}
|
||||
bg.add_job(async move {
|
||||
while let Some(new_status) = status_stream.next().await {
|
||||
if status.state().is_fully_reachable() && !new_status.state().is_fully_reachable() {
|
||||
tracing::debug!("{addr} is no longer fully reachable");
|
||||
} else if !status.state().is_fully_reachable() && new_status.state().is_fully_reachable() {
|
||||
tracing::debug!("{addr} is now fully reachable");
|
||||
}
|
||||
status = new_status;
|
||||
// TODO: health daemon?
|
||||
}
|
||||
});
|
||||
service.replace(Some(new_service));
|
||||
let mut stream = tor_hsservice::handle_rend_requests(stream);
|
||||
while let Some(req) = tokio::select! {
|
||||
req = stream.next() => req,
|
||||
_ = client.wait_for(|(e, _)| *e != epoch) => None
|
||||
} {
|
||||
bg.add_job({
|
||||
let bg = bg.clone();
|
||||
let bindings = bindings.clone();
|
||||
async move {
|
||||
if let Err(e) = async {
|
||||
let IncomingStreamRequest::Begin(begin) =
|
||||
req.request()
|
||||
else {
|
||||
return req
|
||||
.reject(tor_cell::relaycell::msg::End::new_with_reason(
|
||||
tor_cell::relaycell::msg::EndReason::DONE,
|
||||
))
|
||||
.await
|
||||
.with_kind(ErrorKind::Tor);
|
||||
};
|
||||
let Some(target) = bindings.peek(|b| {
|
||||
b.get(&begin.port()).and_then(|a| {
|
||||
a.iter()
|
||||
.find(|(_, rc)| rc.strong_count() > 0)
|
||||
.map(|(addr, _)| *addr)
|
||||
})
|
||||
}) else {
|
||||
return req
|
||||
.reject(tor_cell::relaycell::msg::End::new_with_reason(
|
||||
tor_cell::relaycell::msg::EndReason::DONE,
|
||||
))
|
||||
.await
|
||||
.with_kind(ErrorKind::Tor);
|
||||
};
|
||||
bg.add_job(async move {
|
||||
if let Err(e) = async {
|
||||
let mut outgoing =
|
||||
TcpStream::connect(target)
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
if let Err(e) = socket2::SockRef::from(&outgoing).set_keepalive(true) {
|
||||
tracing::error!("{}", t!("net.tor.failed-to-set-tcp-keepalive", error = e.to_string()));
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
let mut incoming = req
|
||||
.accept(Connected::new_empty())
|
||||
.await
|
||||
.with_kind(ErrorKind::Tor)?;
|
||||
if let Err(e) =
|
||||
tokio::io::copy_bidirectional(
|
||||
&mut outgoing,
|
||||
&mut incoming,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::trace!("Tor Stream Error: {e}");
|
||||
tracing::trace!("{e:?}");
|
||||
}
|
||||
|
||||
Ok::<_, Error>(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::trace!("Tor Stream Error: {e}");
|
||||
tracing::trace!("{e:?}");
|
||||
}
|
||||
});
|
||||
Ok::<_, Error>(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::trace!("Tor Request Error: {e}");
|
||||
tracing::trace!("{e:?}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Ok::<_, Error>(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("{}", t!("net.tor.client-error", error = e.to_string()));
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
})
|
||||
.into(),
|
||||
})))
|
||||
}
|
||||
|
||||
pub async fn proxy_all<Rcs: FromIterator<Arc<()>>>(
|
||||
&self,
|
||||
bindings: impl IntoIterator<Item = (u16, SocketAddr)>,
|
||||
) -> Result<Rcs, Error> {
|
||||
Ok(self.0.bindings.mutate(|b| {
|
||||
bindings
|
||||
.into_iter()
|
||||
.map(|(port, target)| {
|
||||
let entry = b.entry(port).or_default().entry(target).or_default();
|
||||
if let Some(rc) = entry.upgrade() {
|
||||
rc
|
||||
} else {
|
||||
let rc = Arc::new(());
|
||||
*entry = Arc::downgrade(&rc);
|
||||
rc
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn gc(&self) -> bool {
|
||||
self.0.bindings.mutate(|b| {
|
||||
b.retain(|_, targets| {
|
||||
targets.retain(|_, rc| rc.strong_count() > 0);
|
||||
!targets.is_empty()
|
||||
});
|
||||
!b.is_empty()
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn shutdown(self) -> Result<(), Error> {
|
||||
self.0.service.replace(None);
|
||||
self.0._thread.abort();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn state(&self) -> OnionServiceState {
|
||||
self.0
|
||||
.service
|
||||
.peek(|s| s.as_ref().map(|s| s.status().state().into()))
|
||||
.unwrap_or(OnionServiceState::Bootstrapping)
|
||||
}
|
||||
|
||||
pub fn info(&self) -> OnionServiceInfo {
|
||||
OnionServiceInfo {
|
||||
state: self.state(),
|
||||
bindings: self.0.bindings.peek(|b| {
|
||||
b.iter()
|
||||
.filter_map(|(port, b)| {
|
||||
b.iter()
|
||||
.find(|(_, rc)| rc.strong_count() > 0)
|
||||
.map(|(addr, _)| (*port, *addr))
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +0,0 @@
|
||||
#[cfg(feature = "arti")]
|
||||
mod arti;
|
||||
|
||||
#[cfg(not(feature = "arti"))]
|
||||
mod ctor;
|
||||
|
||||
#[cfg(feature = "arti")]
|
||||
pub use arti::{OnionAddress, OnionStore, TorController, TorSecretKey, tor_api};
|
||||
#[cfg(not(feature = "arti"))]
|
||||
pub use ctor::{OnionAddress, OnionStore, TorController, TorSecretKey, tor_api};
|
||||
@@ -55,11 +55,9 @@ pub async fn get_ssl_certificate(
|
||||
.map(|(_, m)| m.as_hosts().as_entries())
|
||||
.flatten_ok()
|
||||
.map_ok(|(_, m)| {
|
||||
Ok(m.as_onions()
|
||||
.de()?
|
||||
.iter()
|
||||
.map(InternedString::from_display)
|
||||
.chain(m.as_public_domains().keys()?)
|
||||
Ok(m.as_public_domains()
|
||||
.keys()?
|
||||
.into_iter()
|
||||
.chain(m.as_private_domains().de()?)
|
||||
.chain(
|
||||
m.as_hostname_info()
|
||||
@@ -68,7 +66,7 @@ pub async fn get_ssl_certificate(
|
||||
.flatten()
|
||||
.map(|h| h.to_san_hostname()),
|
||||
)
|
||||
.collect::<Vec<_>>())
|
||||
.collect::<Vec<InternedString>>())
|
||||
})
|
||||
.map(|a| a.and_then(|a| a))
|
||||
.flatten_ok()
|
||||
@@ -181,11 +179,9 @@ pub async fn get_ssl_key(
|
||||
.map(|m| m.as_hosts().as_entries())
|
||||
.flatten_ok()
|
||||
.map_ok(|(_, m)| {
|
||||
Ok(m.as_onions()
|
||||
.de()?
|
||||
.iter()
|
||||
.map(InternedString::from_display)
|
||||
.chain(m.as_public_domains().keys()?)
|
||||
Ok(m.as_public_domains()
|
||||
.keys()?
|
||||
.into_iter()
|
||||
.chain(m.as_private_domains().de()?)
|
||||
.chain(
|
||||
m.as_hostname_info()
|
||||
@@ -194,7 +190,7 @@ pub async fn get_ssl_key(
|
||||
.flatten()
|
||||
.map(|h| h.to_san_hostname()),
|
||||
)
|
||||
.collect::<Vec<_>>())
|
||||
.collect::<Vec<InternedString>>())
|
||||
})
|
||||
.map(|a| a.and_then(|a| a))
|
||||
.flatten_ok()
|
||||
|
||||
@@ -59,8 +59,9 @@ mod v0_4_0_alpha_16;
|
||||
mod v0_4_0_alpha_17;
|
||||
mod v0_4_0_alpha_18;
|
||||
mod v0_4_0_alpha_19;
|
||||
mod v0_4_0_alpha_20;
|
||||
|
||||
pub type Current = v0_4_0_alpha_19::Version; // VERSION_BUMP
|
||||
pub type Current = v0_4_0_alpha_20::Version; // VERSION_BUMP
|
||||
|
||||
impl Current {
|
||||
#[instrument(skip(self, db))]
|
||||
@@ -181,7 +182,8 @@ enum Version {
|
||||
V0_4_0_alpha_16(Wrapper<v0_4_0_alpha_16::Version>),
|
||||
V0_4_0_alpha_17(Wrapper<v0_4_0_alpha_17::Version>),
|
||||
V0_4_0_alpha_18(Wrapper<v0_4_0_alpha_18::Version>),
|
||||
V0_4_0_alpha_19(Wrapper<v0_4_0_alpha_19::Version>), // VERSION_BUMP
|
||||
V0_4_0_alpha_19(Wrapper<v0_4_0_alpha_19::Version>),
|
||||
V0_4_0_alpha_20(Wrapper<v0_4_0_alpha_20::Version>), // VERSION_BUMP
|
||||
Other(exver::Version),
|
||||
}
|
||||
|
||||
@@ -243,7 +245,8 @@ impl Version {
|
||||
Self::V0_4_0_alpha_16(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_4_0_alpha_17(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_4_0_alpha_18(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_4_0_alpha_19(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
||||
Self::V0_4_0_alpha_19(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_4_0_alpha_20(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
||||
Self::Other(v) => {
|
||||
return Err(Error::new(
|
||||
eyre!("unknown version {v}"),
|
||||
@@ -297,7 +300,8 @@ impl Version {
|
||||
Version::V0_4_0_alpha_16(Wrapper(x)) => x.semver(),
|
||||
Version::V0_4_0_alpha_17(Wrapper(x)) => x.semver(),
|
||||
Version::V0_4_0_alpha_18(Wrapper(x)) => x.semver(),
|
||||
Version::V0_4_0_alpha_19(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
||||
Version::V0_4_0_alpha_19(Wrapper(x)) => x.semver(),
|
||||
Version::V0_4_0_alpha_20(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
||||
Version::Other(x) => x.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::BTreeMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -23,17 +23,14 @@ use crate::disk::mount::filesystem::cifs::Cifs;
|
||||
use crate::disk::mount::util::unmount;
|
||||
use crate::hostname::Hostname;
|
||||
use crate::net::forward::AvailablePorts;
|
||||
use crate::net::host::Host;
|
||||
use crate::net::keys::KeyStore;
|
||||
use crate::net::tor::{OnionAddress, TorSecretKey};
|
||||
use crate::notifications::Notifications;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||
use crate::ssh::{SshKeys, SshPubKey};
|
||||
use crate::util::Invoke;
|
||||
use crate::util::crypto::ed25519_expand_key;
|
||||
use crate::util::serde::Pem;
|
||||
use crate::{DATA_DIR, HostId, Id, PACKAGE_DATA, PackageId, ReplayId};
|
||||
use crate::{DATA_DIR, PACKAGE_DATA, PackageId, ReplayId};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref V0_3_6_alpha_0: exver::Version = exver::Version::new(
|
||||
@@ -146,12 +143,7 @@ pub struct Version;
|
||||
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_5_2::Version;
|
||||
type PreUpRes = (
|
||||
AccountInfo,
|
||||
SshKeys,
|
||||
CifsTargets,
|
||||
BTreeMap<PackageId, BTreeMap<HostId, TorSecretKey>>,
|
||||
);
|
||||
type PreUpRes = (AccountInfo, SshKeys, CifsTargets);
|
||||
fn semver(self) -> exver::Version {
|
||||
V0_3_6_alpha_0.clone()
|
||||
}
|
||||
@@ -166,20 +158,18 @@ impl VersionT for Version {
|
||||
|
||||
let cifs = previous_cifs(&pg).await?;
|
||||
|
||||
let tor_keys = previous_tor_keys(&pg).await?;
|
||||
|
||||
Command::new("systemctl")
|
||||
.arg("stop")
|
||||
.arg("postgresql@*.service")
|
||||
.invoke(crate::ErrorKind::Database)
|
||||
.await?;
|
||||
|
||||
Ok((account, ssh_keys, cifs, tor_keys))
|
||||
Ok((account, ssh_keys, cifs))
|
||||
}
|
||||
fn up(
|
||||
self,
|
||||
db: &mut Value,
|
||||
(account, ssh_keys, cifs, tor_keys): Self::PreUpRes,
|
||||
(account, ssh_keys, cifs): Self::PreUpRes,
|
||||
) -> Result<Value, Error> {
|
||||
let prev_package_data = db["package-data"].clone();
|
||||
|
||||
@@ -242,11 +232,7 @@ impl VersionT for Version {
|
||||
"ui": db["ui"],
|
||||
});
|
||||
|
||||
let mut keystore = KeyStore::new(&account)?;
|
||||
for key in tor_keys.values().flat_map(|v| v.values()) {
|
||||
assert!(key.is_valid());
|
||||
keystore.onion.insert(key.clone());
|
||||
}
|
||||
let keystore = KeyStore::new(&account)?;
|
||||
|
||||
let private = {
|
||||
let mut value = json!({});
|
||||
@@ -350,20 +336,6 @@ impl VersionT for Version {
|
||||
false
|
||||
};
|
||||
|
||||
let onions = input[&*id]["installed"]["interface-addresses"]
|
||||
.as_object()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|(id, addrs)| {
|
||||
addrs["tor-address"].as_str().map(|addr| {
|
||||
Ok((
|
||||
HostId::from(Id::try_from(id.clone())?),
|
||||
addr.parse::<OnionAddress>()?,
|
||||
))
|
||||
})
|
||||
})
|
||||
.collect::<Result<BTreeMap<_, _>, Error>>()?;
|
||||
|
||||
if let Err(e) = async {
|
||||
let package_s9pk = tokio::fs::File::open(path).await?;
|
||||
let file = MultiCursorFile::open(&package_s9pk).await?;
|
||||
@@ -381,11 +353,8 @@ impl VersionT for Version {
|
||||
.await?
|
||||
.await?;
|
||||
|
||||
let to_sync = ctx
|
||||
.db
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
let mut to_sync = BTreeSet::new();
|
||||
|
||||
let package = db
|
||||
.as_public_mut()
|
||||
.as_package_data_mut()
|
||||
@@ -396,29 +365,11 @@ impl VersionT for Version {
|
||||
.as_tasks_mut()
|
||||
.remove(&ReplayId::from("needs-config"))?;
|
||||
}
|
||||
for (id, onion) in onions {
|
||||
package
|
||||
.as_hosts_mut()
|
||||
.upsert(&id, || Ok(Host::new()))?
|
||||
.as_onions_mut()
|
||||
.mutate(|o| {
|
||||
o.clear();
|
||||
o.insert(onion);
|
||||
Ok(())
|
||||
})?;
|
||||
to_sync.insert(id);
|
||||
}
|
||||
Ok(to_sync)
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
|
||||
if let Some(service) = &*ctx.services.get(&id).await {
|
||||
for host_id in to_sync {
|
||||
service.sync_host(host_id.clone()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok::<_, Error>(())
|
||||
}
|
||||
.await
|
||||
@@ -481,33 +432,6 @@ async fn previous_account_info(pg: &sqlx::Pool<sqlx::Postgres>) -> Result<Accoun
|
||||
password: account_query
|
||||
.try_get("password")
|
||||
.with_ctx(|_| (ErrorKind::Database, "password"))?,
|
||||
tor_keys: vec![TorSecretKey::from_bytes(
|
||||
if let Some(bytes) = account_query
|
||||
.try_get::<Option<Vec<u8>>, _>("tor_key")
|
||||
.with_ctx(|_| (ErrorKind::Database, "tor_key"))?
|
||||
{
|
||||
<[u8; 64]>::try_from(bytes).map_err(|e| {
|
||||
Error::new(
|
||||
eyre!("expected vec of len 64, got len {}", e.len()),
|
||||
ErrorKind::ParseDbField,
|
||||
)
|
||||
})?
|
||||
} else {
|
||||
ed25519_expand_key(
|
||||
&<[u8; 32]>::try_from(
|
||||
account_query
|
||||
.try_get::<Vec<u8>, _>("network_key")
|
||||
.with_kind(ErrorKind::Database)?,
|
||||
)
|
||||
.map_err(|e| {
|
||||
Error::new(
|
||||
eyre!("expected vec of len 32, got len {}", e.len()),
|
||||
ErrorKind::ParseDbField,
|
||||
)
|
||||
})?,
|
||||
)
|
||||
},
|
||||
)?],
|
||||
server_id: account_query
|
||||
.try_get("server_id")
|
||||
.with_ctx(|_| (ErrorKind::Database, "server_id"))?,
|
||||
@@ -579,68 +503,3 @@ async fn previous_ssh_keys(pg: &sqlx::Pool<sqlx::Postgres>) -> Result<SshKeys, E
|
||||
Ok(ssh_keys)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn previous_tor_keys(
|
||||
pg: &sqlx::Pool<sqlx::Postgres>,
|
||||
) -> Result<BTreeMap<PackageId, BTreeMap<HostId, TorSecretKey>>, Error> {
|
||||
let mut res = BTreeMap::<PackageId, BTreeMap<HostId, TorSecretKey>>::new();
|
||||
let net_key_query = sqlx::query(r#"SELECT * FROM network_keys"#)
|
||||
.fetch_all(pg)
|
||||
.await
|
||||
.with_kind(ErrorKind::Database)?;
|
||||
|
||||
for row in net_key_query {
|
||||
let package_id: PackageId = row
|
||||
.try_get::<String, _>("package")
|
||||
.with_ctx(|_| (ErrorKind::Database, "network_keys::package"))?
|
||||
.parse()?;
|
||||
let interface_id: HostId = row
|
||||
.try_get::<String, _>("interface")
|
||||
.with_ctx(|_| (ErrorKind::Database, "network_keys::interface"))?
|
||||
.parse()?;
|
||||
let key = TorSecretKey::from_bytes(ed25519_expand_key(
|
||||
&<[u8; 32]>::try_from(
|
||||
row.try_get::<Vec<u8>, _>("key")
|
||||
.with_ctx(|_| (ErrorKind::Database, "network_keys::key"))?,
|
||||
)
|
||||
.map_err(|e| {
|
||||
Error::new(
|
||||
eyre!("expected vec of len 32, got len {}", e.len()),
|
||||
ErrorKind::ParseDbField,
|
||||
)
|
||||
})?,
|
||||
))?;
|
||||
res.entry(package_id).or_default().insert(interface_id, key);
|
||||
}
|
||||
|
||||
let tor_key_query = sqlx::query(r#"SELECT * FROM tor"#)
|
||||
.fetch_all(pg)
|
||||
.await
|
||||
.with_kind(ErrorKind::Database)?;
|
||||
|
||||
for row in tor_key_query {
|
||||
let package_id: PackageId = row
|
||||
.try_get::<String, _>("package")
|
||||
.with_ctx(|_| (ErrorKind::Database, "tor::package"))?
|
||||
.parse()?;
|
||||
let interface_id: HostId = row
|
||||
.try_get::<String, _>("interface")
|
||||
.with_ctx(|_| (ErrorKind::Database, "tor::interface"))?
|
||||
.parse()?;
|
||||
let key = TorSecretKey::from_bytes(
|
||||
<[u8; 64]>::try_from(
|
||||
row.try_get::<Vec<u8>, _>("key")
|
||||
.with_ctx(|_| (ErrorKind::Database, "tor::key"))?,
|
||||
)
|
||||
.map_err(|e| {
|
||||
Error::new(
|
||||
eyre!("expected vec of len 64, got len {}", e.len()),
|
||||
ErrorKind::ParseDbField,
|
||||
)
|
||||
})?,
|
||||
)?;
|
||||
res.entry(package_id).or_default().insert(interface_id, key);
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use super::v0_3_5::V0_3_0_COMPAT;
|
||||
use super::{VersionT, v0_3_6_alpha_9};
|
||||
use crate::GatewayId;
|
||||
use crate::net::host::address::PublicDomainConfig;
|
||||
use crate::net::tor::OnionAddress;
|
||||
use crate::prelude::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@@ -22,7 +21,7 @@ lazy_static::lazy_static! {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(tag = "kind")]
|
||||
enum HostAddress {
|
||||
Onion { address: OnionAddress },
|
||||
Onion { address: String },
|
||||
Domain { address: InternedString },
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use exver::{PreReleaseSegment, VersionRange};
|
||||
use imbl_value::InternedString;
|
||||
|
||||
use super::v0_3_5::V0_3_0_COMPAT;
|
||||
use super::{VersionT, v0_4_0_alpha_11};
|
||||
use crate::net::tor::TorSecretKey;
|
||||
use crate::prelude::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@@ -33,48 +29,6 @@ impl VersionT for Version {
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
||||
let mut err = None;
|
||||
let onion_store = db["private"]["keyStore"]["onion"]
|
||||
.as_object_mut()
|
||||
.or_not_found("private.keyStore.onion")?;
|
||||
onion_store.retain(|o, v| match from_value::<TorSecretKey>(v.clone()) {
|
||||
Ok(k) => k.is_valid() && &InternedString::from_display(&k.onion_address()) == o,
|
||||
Err(e) => {
|
||||
err = Some(e);
|
||||
true
|
||||
}
|
||||
});
|
||||
if let Some(e) = err {
|
||||
return Err(e);
|
||||
}
|
||||
let allowed_addresses = onion_store.keys().cloned().collect::<BTreeSet<_>>();
|
||||
let fix_host = |host: &mut Value| {
|
||||
Ok::<_, Error>(
|
||||
host["onions"]
|
||||
.as_array_mut()
|
||||
.or_not_found("host.onions")?
|
||||
.retain(|addr| {
|
||||
addr.as_str()
|
||||
.map(|s| allowed_addresses.contains(s))
|
||||
.unwrap_or(false)
|
||||
}),
|
||||
)
|
||||
};
|
||||
for (_, pde) in db["public"]["packageData"]
|
||||
.as_object_mut()
|
||||
.or_not_found("public.packageData")?
|
||||
.iter_mut()
|
||||
{
|
||||
for (_, host) in pde["hosts"]
|
||||
.as_object_mut()
|
||||
.or_not_found("public.packageData[].hosts")?
|
||||
.iter_mut()
|
||||
{
|
||||
fix_host(host)?;
|
||||
}
|
||||
}
|
||||
fix_host(&mut db["public"]["serverInfo"]["network"]["host"])?;
|
||||
|
||||
if db["private"]["keyStore"]["localCerts"].is_null() {
|
||||
db["private"]["keyStore"]["localCerts"] =
|
||||
db["private"]["keyStore"]["local_certs"].clone();
|
||||
|
||||
110
core/src/version/v0_4_0_alpha_20.rs
Normal file
110
core/src/version/v0_4_0_alpha_20.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use exver::{PreReleaseSegment, VersionRange};
|
||||
|
||||
use super::v0_3_5::V0_3_0_COMPAT;
|
||||
use super::{VersionT, v0_4_0_alpha_19};
|
||||
use crate::prelude::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref V0_4_0_alpha_20: exver::Version = exver::Version::new(
|
||||
[0, 4, 0],
|
||||
[PreReleaseSegment::String("alpha".into()), 20.into()]
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Version;
|
||||
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_4_0_alpha_19::Version;
|
||||
type PreUpRes = ();
|
||||
|
||||
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn semver(self) -> exver::Version {
|
||||
V0_4_0_alpha_20.clone()
|
||||
}
|
||||
fn compat(self) -> &'static VersionRange {
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
||||
// Remove onions and tor-related fields from server host
|
||||
if let Some(host) = db
|
||||
.get_mut("public")
|
||||
.and_then(|p| p.get_mut("serverInfo"))
|
||||
.and_then(|s| s.get_mut("network"))
|
||||
.and_then(|n| n.get_mut("host"))
|
||||
.and_then(|h| h.as_object_mut())
|
||||
{
|
||||
host.remove("onions");
|
||||
}
|
||||
|
||||
// Remove onions from all package hosts
|
||||
if let Some(packages) = db
|
||||
.get_mut("public")
|
||||
.and_then(|p| p.get_mut("packageData"))
|
||||
.and_then(|p| p.as_object_mut())
|
||||
{
|
||||
for (_, package) in packages.iter_mut() {
|
||||
if let Some(hosts) = package.get_mut("hosts").and_then(|h| h.as_object_mut()) {
|
||||
for (_, host) in hosts.iter_mut() {
|
||||
if let Some(host_obj) = host.as_object_mut() {
|
||||
host_obj.remove("onions");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove onion entries from hostnameInfo in server host
|
||||
remove_onion_hostname_info(
|
||||
db.get_mut("public")
|
||||
.and_then(|p| p.get_mut("serverInfo"))
|
||||
.and_then(|s| s.get_mut("network"))
|
||||
.and_then(|n| n.get_mut("host")),
|
||||
);
|
||||
|
||||
// Remove onion entries from hostnameInfo in all package hosts
|
||||
if let Some(packages) = db
|
||||
.get_mut("public")
|
||||
.and_then(|p| p.get_mut("packageData"))
|
||||
.and_then(|p| p.as_object_mut())
|
||||
{
|
||||
for (_, package) in packages.iter_mut() {
|
||||
if let Some(hosts) = package.get_mut("hosts").and_then(|h| h.as_object_mut()) {
|
||||
for (_, host) in hosts.iter_mut() {
|
||||
remove_onion_hostname_info(Some(host));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove onion store from private keyStore
|
||||
if let Some(key_store) = db
|
||||
.get_mut("private")
|
||||
.and_then(|p| p.get_mut("keyStore"))
|
||||
.and_then(|k| k.as_object_mut())
|
||||
{
|
||||
key_store.remove("onion");
|
||||
}
|
||||
|
||||
Ok(Value::Null)
|
||||
}
|
||||
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_onion_hostname_info(host: Option<&mut Value>) {
|
||||
if let Some(hostname_info) = host
|
||||
.and_then(|h| h.get_mut("hostnameInfo"))
|
||||
.and_then(|h| h.as_object_mut())
|
||||
{
|
||||
for (_, infos) in hostname_info.iter_mut() {
|
||||
if let Some(arr) = infos.as_array_mut() {
|
||||
arr.retain(|info| info.get("kind").and_then(|k| k.as_str()) != Some("onion"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user