1
//! Administrative RPC functionality.
2
//!
3
//! In general, RPC function is "administrative", and requires superuser access,
4
//! whenever it can affect other applications.
5
//!
6
//! This is not a perfect sandbox: applications can _always_ interfere with one another's traffic by
7
//! consuming resources (like bandwidth or CPU) in a way that introduces side channels.
8

            
9
use arti_client::{TorClient, rpc::ClientStatusInfo};
10
use derive_deftly::Deftly;
11
use futures::{FutureExt as _, SinkExt as _, StreamExt as _, select_biased};
12
use std::sync::Arc;
13
use tor_rpcbase::{self as rpc};
14
use tor_rtcompat::Runtime;
15

            
16
use crate::reload_cfg::{CfgMgr, LaunchableTorClient};
17

            
18
/// An object representing superuser access to Arti over an RPC session.
19
///
20
/// In general, RPC function is "administrative", and requires superuser access,
21
/// whenever it can affect other applications.
22
#[derive(Deftly)]
23
#[derive_deftly(rpc::Object)]
24
pub(super) struct RpcSuperuser<R: Runtime> {
25
    /// A view of the underlying TorClient managed by this RpcSuperuser object.
26
    tor_client: Arc<TorClient<R>>,
27

            
28
    /// A wrapper around `tor_client` with the ability to launch a deferred-bootstrap client.
29
    launchable: Arc<LaunchableTorClient<R>>,
30

            
31
    /// A handle to the manager for configuration information.
32
    pub(super) cfg_mgr: Arc<CfgMgr<R>>,
33
}
34

            
35
impl<R: Runtime> RpcSuperuser<R> {
36
    /// Construct a new RpcSuperuser object.
37
    pub(super) fn new(
38
        tor_client: Arc<TorClient<R>>,
39
        launchable: Arc<LaunchableTorClient<R>>,
40
        cfg_mgr: Arc<CfgMgr<R>>,
41
    ) -> Self {
42
        RpcSuperuser {
43
            tor_client,
44
            launchable,
45
            cfg_mgr,
46
        }
47
    }
48

            
49
    /// Ensure that every RPC method is registered for this instantiation of TorClient.
50
    ///
51
    /// We can't use [`rpc::static_rpc_invoke_fn`] for these, since TorClient is
52
    /// parameterized.
53
    pub(super) fn rpc_methods() -> Vec<rpc::dispatch::InvokerEnt> {
54
        rpc::invoker_ent_list![
55
            enter_dormant_mode_on_rpcsuperuser::<R>,
56
            bootstrap_client_on_rpcsuperuser::<R>,
57
            crate::rpc::configuration::set_config_on_rpcsuperuser::<R>,
58
            crate::rpc::configuration::get_config_on_rpcsuperuser::<R>,
59
        ]
60
    }
61
}
62

            
63
/// Enter "dormant mode".
64
///
65
/// Currently, the only available dormant mode is "soft dormant mode",
66
/// which suspends most background operations until any client request
67
/// is received.
68
///
69
/// Since this method affects all applications using the Arti process,
70
/// it requires administrative permissions.
71
///
72
/// ## Limitations
73
///
74
/// As of 2026 March, this functionality is not perfectly implemented,
75
/// and likely does not interact well with onion services.
76
/// Additionally, there are likely background operations that
77
/// this operation doesn't cover.
78
///
79
/// This method returns a reply immediately, but it may take a little
80
/// while before all of the background tasks finish their work and stop.
81
#[derive(Debug, serde::Deserialize, serde::Serialize, Deftly)]
82
#[derive_deftly(rpc::DynMethod)]
83
#[deftly(rpc(method_name = "arti:enter_dormant_mode"))]
84
struct EnterDormantMode {}
85

            
86
impl rpc::RpcMethod for EnterDormantMode {
87
    type Output = rpc::Nil;
88
    type Update = rpc::NoUpdates;
89
}
90

            
91
/// Implementation for [`EnterDormantMode`] on [`RpcSuperuser`].
92
async fn enter_dormant_mode_on_rpcsuperuser<R: Runtime>(
93
    session: Arc<RpcSuperuser<R>>,
94
    _method: Box<EnterDormantMode>,
95
    _ctx: Arc<dyn rpc::Context>,
96
) -> Result<rpc::Nil, rpc::RpcError> {
97
    use arti_client::DormantMode;
98
    session.tor_client.set_dormant(DormantMode::Soft);
99
    Ok(rpc::Nil::default())
100
}
101

            
102
/// Tell a client to connect to the network and bootstrap itself.
103
///
104
/// There is no need to invoke this method unless
105
/// was started with the `application.defer_bootstrap` option set to true.
106
/// By default, clients will automatically connect to the network and bootstrap
107
/// themselves.
108
///
109
/// Since this method affects all applications using the Arti process,
110
/// it requires administrative permissions.  We may someday relax this
111
/// property.
112
#[derive(Debug, serde::Deserialize, serde::Serialize, Deftly)]
113
#[derive_deftly(rpc::DynMethod)]
114
#[deftly(rpc(method_name = "arti:bootstrap_client"))]
115
struct BootstrapClient {}
116

            
117
impl rpc::RpcMethod for BootstrapClient {
118
    type Output = rpc::Nil;
119
    type Update = ClientStatusInfo;
120
}
121

            
122
/// Implementation for [`BootstrapClient`] on [`RpcSuperuser`].
123
async fn bootstrap_client_on_rpcsuperuser<R: Runtime>(
124
    session: Arc<RpcSuperuser<R>>,
125
    _method: Box<BootstrapClient>,
126
    _ctx: Arc<dyn rpc::Context>,
127
    mut updates: rpc::UpdateSink<ClientStatusInfo>,
128
) -> Result<rpc::Nil, rpc::RpcError> {
129
    let mut events = session.tor_client.bootstrap_events().fuse();
130
    // Send the initial status unconditionally.
131
    updates
132
        .send(session.tor_client.bootstrap_status().into())
133
        .await?;
134

            
135
    let mut bootstrap = Box::pin(session.launchable.bootstrap()).fuse();
136

            
137
    loop {
138
        select_biased! {
139
            outcome = bootstrap => {
140
                 let () = outcome?;
141
                 return Ok(rpc::Nil::default());
142
            }
143
            e = events.next() => {
144
                // (If this returns None, then the `outcome` is about to fail.)
145
                if let Some(e) = e {
146
                    let status = e.into();
147
                    let _ignore_failure = updates.send(status).await;
148
                }
149
            }
150
        };
151
    }
152
}