1
//! High-level APIs for an RPC session
2
//!
3
//! A "session" is created when a user authenticates on an RPC connection.  It
4
//! is the root for all other RPC capabilities.
5

            
6
use arti_client::{
7
    TorClient,
8
    rpc::{ClientConnectionResult, ConnectWithPrefs, ResolvePtrWithPrefs, ResolveWithPrefs},
9
};
10
use derive_deftly::Deftly;
11
use std::{
12
    net::IpAddr,
13
    sync::{Arc, Mutex},
14
};
15
use tor_error::into_internal;
16
use tor_rtcompat::Runtime;
17

            
18
use tor_rpcbase::{self as rpc, static_rpc_invoke_fn, templates::*};
19

            
20
/// An authenticated RPC session: a capability through which most other RPC functionality is available
21
///
22
/// This relates to [`Connection`](crate::Connection) as follows:
23
///
24
///  * A `Connection` exists prior to authentication;
25
///    whereas an `RpcSession` comes into being as a result of authentication.
26
///
27
///  * The `RpcSession` is principally owned by the `Connection`'s object table.
28
///
29
///  * Typically, after authentication, there is one `RpcSession` for the `Connection`.
30
///    But a client may authenticate more than once; each time produces a new `RpcSession`.
31
///
32
/// ## In the arti rpc system
33
///
34
/// Base type for an authenticated RPC session.
35
///
36
/// Upon successful authentication via `auth:authenticate`,
37
/// a connection will return either a Session object of this type,
38
/// or a Session object that wraps this type.
39
/// All other useful objects are available via an RPC session.
40
///
41
/// This ObjectID for this object can be used as the target of a SOCKS stream.
42
#[derive(Deftly)]
43
#[derive_deftly(Object)]
44
#[deftly(rpc(expose_outside_of_session))]
45
pub struct RpcSession {
46
    /// An inner TorClient object that we use to implement remaining
47
    /// functionality.
48
    #[allow(unused)]
49
    client: Arc<dyn Client>,
50

            
51
    /// A superuser object representing administrative capability.
52
    ///
53
    /// If this object is absent, this session never had this capability,
54
    /// or dropped it.
55
    superuser: Mutex<Option<Arc<dyn rpc::Object>>>,
56
}
57

            
58
/// Type-erased `TorClient`, as used within an RpcSession.
59
trait Client: rpc::Object {
60
    /// Return a new isolated TorClient.
61
    fn isolated_client(&self) -> Arc<dyn rpc::Object>;
62

            
63
    /// Upcast `self` to an rpc::Object.
64
    fn upcast_arc(self: Arc<Self>) -> Arc<dyn rpc::Object>;
65
}
66

            
67
impl<R: Runtime> Client for TorClient<R> {
68
    fn isolated_client(&self) -> Arc<dyn rpc::Object> {
69
        Arc::new(TorClient::isolated_client(self))
70
    }
71

            
72
    fn upcast_arc(self: Arc<Self>) -> Arc<dyn rpc::Object> {
73
        self
74
    }
75
}
76

            
77
impl RpcSession {
78
    /// Create a new session object containing a single client object.
79
    pub fn new_with_client<R: Runtime>(client: Arc<arti_client::TorClient<R>>) -> Arc<Self> {
80
        Arc::new(Self {
81
            client,
82
            superuser: Mutex::new(None),
83
        })
84
    }
85

            
86
    /// Set the superuser object for this session to `superuser`.
87
    ///
88
    /// Calling this function indicates that this session has administrative privilege.
89
    pub fn provide_superuser_permission(&self, superuser: Arc<dyn rpc::Object>) {
90
        let mut su = self.superuser.lock().expect("Poisoned lock");
91
        *su = Some(superuser);
92
    }
93

            
94
    /// Return a view of the client associated with this session, as an `Arc<dyn
95
    /// rpc::Object>.`
96
    fn client_as_object(&self) -> Arc<dyn rpc::Object> {
97
        self.client.clone().upcast_arc()
98
    }
99
}
100

            
101
/// Return the default client for a session.
102
///
103
/// Allocates a new ObjectID,
104
/// but does not create a new underlying client object.
105
///
106
/// The returned ObjectID is a handle to a `TorClient`.
107
#[derive(Debug, serde::Deserialize, serde::Serialize, Deftly)]
108
#[derive_deftly(DynMethod)]
109
#[deftly(rpc(method_name = "arti:get_client"))]
110
struct GetClient {}
111

            
112
impl rpc::RpcMethod for GetClient {
113
    type Output = rpc::SingleIdResponse;
114
    type Update = rpc::NoUpdates;
115
}
116

            
117
/// Implement GetClient on an RpcSession.
118
async fn get_client_on_session(
119
    session: Arc<RpcSession>,
120
    _method: Box<GetClient>,
121
    ctx: Arc<dyn rpc::Context>,
122
) -> Result<rpc::SingleIdResponse, rpc::RpcError> {
123
    Ok(rpc::SingleIdResponse::from(
124
        ctx.register_owned(session.client.clone().upcast_arc()),
125
    ))
126
}
127

            
128
/// Implement IsolatedClient on an RpcSession.
129
async fn isolated_client_on_session(
130
    session: Arc<RpcSession>,
131
    _method: Box<arti_client::rpc::IsolatedClient>,
132
    ctx: Arc<dyn rpc::Context>,
133
) -> Result<rpc::SingleIdResponse, rpc::RpcError> {
134
    let new_client = session.client.isolated_client();
135
    Ok(rpc::SingleIdResponse::from(ctx.register_owned(new_client)))
136
}
137

            
138
/// Implement ConnectWithPrefs on an RpcSession
139
///
140
/// (Delegates to TorClient.)
141
async fn session_connect_with_prefs(
142
    session: Arc<RpcSession>,
143
    method: Box<ConnectWithPrefs>,
144
    ctx: Arc<dyn rpc::Context>,
145
) -> ClientConnectionResult<arti_client::DataStream> {
146
    *rpc::invoke_special_method(ctx, session.client_as_object(), method)
147
        .await
148
        .map_err(|e| Box::new(into_internal!("unable to delegate to TorClient")(e)) as _)?
149
}
150

            
151
/// Implement ResolveWithPrefs on an RpcSession
152
///
153
/// (Delegates to TorClient.)
154
async fn session_resolve_with_prefs(
155
    session: Arc<RpcSession>,
156
    method: Box<ResolveWithPrefs>,
157
    ctx: Arc<dyn rpc::Context>,
158
) -> ClientConnectionResult<Vec<IpAddr>> {
159
    *rpc::invoke_special_method(ctx, session.client_as_object(), method)
160
        .await
161
        .map_err(|e| Box::new(into_internal!("unable to delegate to TorClient")(e)) as _)?
162
}
163

            
164
/// Implement ResolvePtrWithPrefs on an RpcSession
165
///
166
/// (Delegates to TorClient.)
167
async fn session_resolve_ptr_with_prefs(
168
    session: Arc<RpcSession>,
169
    method: Box<ResolvePtrWithPrefs>,
170
    ctx: Arc<dyn rpc::Context>,
171
) -> ClientConnectionResult<Vec<String>> {
172
    *rpc::invoke_special_method(ctx, session.client_as_object(), method)
173
        .await
174
        .map_err(|e| Box::new(into_internal!("unable to delegate to TorClient")(e)) as _)?
175
}
176

            
177
/// Return the superuser capability for a session.
178
///
179
/// Just as a session is the root object proving that
180
/// your program has authenticated
181
///
182
/// Returns an error if this session is not authorized for superuser access,
183
/// or if you have dropped superuser access via `arti:remove_superuser_permission`.
184
#[derive(Debug, serde::Deserialize, serde::Serialize, Deftly)]
185
#[derive_deftly(DynMethod)]
186
#[deftly(rpc(method_name = "arti:get_superuser_capability"))]
187
struct GetSuperuserCapability {}
188

            
189
impl rpc::RpcMethod for GetSuperuserCapability {
190
    type Output = rpc::SingleIdResponse;
191
    type Update = rpc::NoUpdates;
192
}
193

            
194
/// Implement `arti::get_superuser_capability` on RpcSession.
195
async fn get_superuser_capability_on_session(
196
    session: Arc<RpcSession>,
197
    _method: Box<GetSuperuserCapability>,
198
    ctx: Arc<dyn rpc::Context>,
199
) -> Result<rpc::SingleIdResponse, rpc::RpcError> {
200
    let opt_su = session.superuser.lock().expect("Lock poisoned");
201
    match opt_su.as_ref() {
202
        Some(su) => {
203
            let su = Arc::clone(su);
204
            drop(opt_su);
205
            let id = ctx.register_owned(su);
206
            Ok(id.into())
207
        }
208
        None => Err(rpc::RpcError::new(
209
            "Superuser access not permitted on this session".into(),
210
            rpc::RpcErrorKind::RequestError,
211
        )),
212
    }
213
}
214

            
215
/// Remove the superuser permission from a session.
216
///
217
/// Calling this method on a session ensures that future calls to
218
/// `arti:get_superuser_capability` will return an error.`
219
///
220
/// This method does nothing if the session did not have superuser access.
221
///
222
/// This method does not drop existing superuser capability objects
223
/// previously returned from `arti:get_superuser_capability`,
224
/// or other privileged objects derived from them.
225
///
226
/// Additionally, it does not prevent you from from using `auth`
227
/// methods to create a new session from the same connection object.
228
///
229
/// Therefore, to ensure that you cannot acquire new superuser functionality
230
/// on a given connection, you must:
231
/// - Drop any existing superuser capabilities.
232
/// - Invoke this method on the session.
233
///
234
/// To ensure that an _application_ cannot reacquire superuser permission,
235
/// you also must prevent it from opening a new RPC connection to any
236
/// Arti RPC connect point that allows superuser access.
237
#[derive(Debug, serde::Deserialize, serde::Serialize, Deftly)]
238
#[derive_deftly(DynMethod)]
239
#[deftly(rpc(method_name = "arti:remove_superuser_permission"))]
240
struct RemoveSuperuserPermission {}
241

            
242
impl rpc::RpcMethod for RemoveSuperuserPermission {
243
    type Output = rpc::Nil;
244
    type Update = rpc::NoUpdates;
245
}
246

            
247
/// Implement `arti::remove_superuser_permission` on RpcSession.
248
async fn remove_superuser_permission_on_session(
249
    session: Arc<RpcSession>,
250
    _method: Box<RemoveSuperuserPermission>,
251
    _ctx: Arc<dyn rpc::Context>,
252
) -> Result<rpc::Nil, rpc::RpcError> {
253
    let mut opt_su = session.superuser.lock().expect("Lock poisoned");
254
    *opt_su = None;
255
    Ok(rpc::Nil::default())
256
}
257

            
258
static_rpc_invoke_fn! {
259
    get_client_on_session;
260
    isolated_client_on_session;
261
    get_superuser_capability_on_session;
262
    remove_superuser_permission_on_session;
263
    @special session_connect_with_prefs;
264
    @special session_resolve_with_prefs;
265
    @special session_resolve_ptr_with_prefs;
266
}
267

            
268
#[cfg(feature = "describe-methods")]
269
#[allow(clippy::missing_docs_in_private_items)] // TODO
270
mod list_all_methods {
271
    use std::{convert::Infallible, sync::Arc};
272

            
273
    use derive_deftly::Deftly;
274
    use tor_rpcbase::{self as rpc, RpcDispatchInformation, static_rpc_invoke_fn, templates::*};
275

            
276
    /// Return a description of all recognized RPC methods.
277
    ///
278
    /// Note that not every recognized method is necessarily invocable in practice.
279
    /// Depending on the session's access level, you might not be able to
280
    /// access any objects that the method might be invocable upon.
281
    ///
282
    /// **This is an experimental method.**
283
    /// Methods starting with "x_" are extra-unstable.
284
    /// See [`RpcDispatchInformation`] for caveats about type names.
285
    #[derive(Debug, serde::Deserialize, Deftly)]
286
    #[derive_deftly(DynMethod)]
287
    #[deftly(rpc(method_name = "arti:x_list_all_rpc_methods"))]
288
    struct ListAllRpcMethods {}
289

            
290
    impl rpc::RpcMethod for ListAllRpcMethods {
291
        type Output = RpcDispatchInformation;
292
        type Update = rpc::NoUpdates;
293
    }
294

            
295
    /// Implement ListAllRpcMethods on an RpcSession.
296
    async fn session_list_all_rpc_methods(
297
        _session: Arc<super::RpcSession>,
298
        _method: Box<ListAllRpcMethods>,
299
        ctx: Arc<dyn rpc::Context>,
300
    ) -> Result<RpcDispatchInformation, Infallible> {
301
        Ok(ctx
302
            .dispatch_table()
303
            .read()
304
            .expect("poisoned lock")
305
            .dispatch_information())
306
    }
307

            
308
    static_rpc_invoke_fn! { session_list_all_rpc_methods; }
309
}