Skip to main content

arti_rpcserver/
session.rs

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
6use arti_client::{
7    TorClient,
8    rpc::{ClientConnectionResult, ConnectWithPrefs, ResolvePtrWithPrefs, ResolveWithPrefs},
9};
10use derive_deftly::Deftly;
11use std::{
12    net::IpAddr,
13    sync::{Arc, Mutex},
14};
15use tor_error::into_internal;
16use tor_rtcompat::Runtime;
17
18use 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))]
45pub 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.
59trait 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
67impl<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
77impl 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"))]
110struct GetClient {}
111
112impl rpc::RpcMethod for GetClient {
113    type Output = rpc::SingleIdResponse;
114    type Update = rpc::NoUpdates;
115}
116
117/// Implement GetClient on an RpcSession.
118async 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.
129async 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.)
141async 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.)
154async 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.)
167async 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"))]
187struct GetSuperuserCapability {}
188
189impl rpc::RpcMethod for GetSuperuserCapability {
190    type Output = rpc::SingleIdResponse;
191    type Update = rpc::NoUpdates;
192}
193
194/// Implement `arti::get_superuser_capability` on RpcSession.
195async 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"))]
240struct RemoveSuperuserPermission {}
241
242impl rpc::RpcMethod for RemoveSuperuserPermission {
243    type Output = rpc::Nil;
244    type Update = rpc::NoUpdates;
245}
246
247/// Implement `arti::remove_superuser_permission` on RpcSession.
248async 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
258static_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
270mod 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}