1
//! Functionality for accessing and modifying configuration over RPC
2

            
3
use std::sync::Arc;
4

            
5
use tor_rpcbase as rpc;
6
use tor_rtcompat::Runtime;
7

            
8
use crate::rpc::RpcSuperuser;
9

            
10
/// A set of configuration options maintained by the RPC subsystem.
11
///
12
/// These are applied after all loaded configuration values.
13
#[derive(Clone, Debug, serde::Serialize)]
14
#[serde(transparent)]
15
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
16
pub(crate) struct ConfigSettings(serde_json::Value);
17

            
18
impl Default for ConfigSettings {
19
1
    fn default() -> Self {
20
1
        Self(serde_json::Value::Object(Default::default()))
21
1
    }
22
}
23

            
24
impl ConfigSettings {
25
    /// Replace the value at `key` within this object with `value`.
26
    ///
27
    /// `key` is interpreted as a dot-separated sequence of dictionary keys.
28
    ///
29
    /// Either "." or "" can be used to refer to the root of the tree.
30
    ///
31
    /// Note this function can easily create an invalid configuration.
32
    fn apply_key_value(&mut self, key: &str, value: serde_json::Value) {
33
        use serde_json::{Map, Value};
34

            
35
        if ["", "."].contains(&key) {
36
            self.0 = value;
37
            return;
38
        }
39

            
40
        let mut v: &mut Value = &mut self.0;
41
        for path_elt in key.split('.') {
42
            if v.is_object() {
43
                let map = v.as_object_mut().expect("No longer an object");
44
                v = map.entry(path_elt).or_insert(Value::Null);
45
            } else {
46
                let mut map: Map<String, Value> = Default::default();
47
                map.insert(path_elt.to_string(), Value::Null);
48
                *v = Value::Object(map);
49
                v = v
50
                    .as_object_mut()
51
                    .expect("value stopped being an object")
52
                    .get_mut(path_elt)
53
                    .expect("entry in object disappeared");
54
            }
55
        }
56

            
57
        *v = value;
58
    }
59
}
60

            
61
/// One or more configuration settings returned by the RPC subsystem.
62
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
63
#[serde(transparent)]
64
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
65
pub(crate) struct ConfigValue(serde_json::Value);
66

            
67
/// Change the value of a part of the configuration tree.
68
///
69
/// This method requires superuser capability,
70
/// since it affects all Arti sessions.
71
///
72
/// ## Semantics
73
///
74
/// Arti takes its configuration from the following sources:
75
///
76
/// 1. A set of default values
77
/// 2. Configuration files on disk
78
/// 3. Command-line configuration arguments
79
/// 4. Options provided via RPC
80
///
81
/// These sources are applied in order,
82
/// with later options possibly overriding earlier ones.
83
/// (When a set of options is stored in a map, the maps are merged,
84
/// with the later map getting precedence.)
85
/// The set of options forms a tree.
86
/// In the RPC system, we represent this tree as a JSON Object.
87
///
88
/// > TODO: Explain all of this more clearly.
89
///
90
/// _This RPC method_ lets you change the set of options provided via RPC.
91
/// (source 4 in the list above).
92
///
93
/// When you use this method, it _replaces_ part the the RPC configuration
94
/// options at the path in the tree represented by `key`
95
/// with the provided `value`.
96
///
97
/// ## Error behavior
98
///
99
/// This method will fail if:
100
///
101
/// - Any provided option is invalid
102
/// - The transition from the current set of options
103
///   to the new set is not allowed
104
/// - Or something goes wrong when trying to transition.
105
///
106
/// If this method fails,
107
/// and the configuration will (if possible[^original-state])
108
/// stay in its original state,
109
/// and the method will return an error.
110
///
111
/// > The current error type contains no machine-readable elements
112
/// > describing how it failed;
113
/// > we do intend to add some in the future.
114
///
115
/// **NOTE**: This method currently **will not fail** for any unrecognized options.
116
/// We will, later, add options to require only recognized options,
117
/// or to list unrecognized options.
118
///
119
/// [^original-state]: If arti can reject the configuration changes
120
///    before it starts trying to apply them, we guarantee that
121
///    the configuration will stay in its original state.
122
///    Otherwise, arti will try its best to apply configuration
123
///    changes on an all-or-nothing basis,
124
///    but bugs or system limitations may prevent this from working completely.
125
///
126
/// ## Examples
127
///
128
/// ### Setting a single option
129
///
130
/// This invocation will disable client connections to "local"
131
/// addresses over the Tor network.  It will override any
132
/// value for this option set in any other configuration source.
133
///
134
/// ```json
135
/// { "key": "address_filter.allow_local_addrs", "value": false }
136
/// ```
137
///
138
/// ### Setting several options at a given path.
139
///
140
/// This invocation will disable client connections to local
141
/// addresses.
142
///
143
/// This replaces the whole sub-tree of RPC-provided options at `address_filter`.
144
/// Therefore, if there are any other RPC-provided options for `address_filter.*`,
145
/// **this invocation will remove them**.
146
///
147
/// > We might add optional "merge" semantics (instead of replace) in the future.
148
///
149
/// ```json
150
/// { "key": "address_filter",
151
///   "value": { "allow_local_addrs": false } }
152
/// ```
153
///
154
/// ### Clearing all RPC-provided options
155
///
156
/// This invocation will restore the configuration to the state
157
/// of having _no_ RPC-provided options:
158
///
159
/// ```json
160
/// { "key": '', "value": {} }
161
/// ```
162
///
163
/// It works by replacing the root of the RPC option tree with an empty Object,
164
/// so that only options from the other sources will remain.
165
#[derive(Debug, serde::Deserialize, derive_deftly::Deftly)]
166
#[derive_deftly(rpc::DynMethod)]
167
#[deftly(rpc(method_name = "arti:set_config"))]
168
pub(super) struct SetConfig {
169
    /// A path in the RPC configuration tree to override.
170
    ///
171
    /// This path is either the empty string,
172
    /// or a sequence of period-separated configuration identifiers.
173
    /// The root of the tree can be represented as "." or as "".
174
    ///
175
    /// No validation is done to ensure that the identifiers are actually
176
    /// a recognized configuration option.
177
    key: String,
178

            
179
    /// A value to replace part of the RPC configuration tree.
180
    ///
181
    /// See the method description for an overview of the semantics
182
    /// of the RPC configuration tree.
183
    /// Notably, this method _replaces_ parts of the RPC configuration tree,
184
    /// and the RPC configuration tree is _merged into_
185
    /// the configuration from other sources.
186
    value: ConfigValue,
187
}
188

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

            
194
/// Return the value of part of the configuration tree.
195
///
196
/// This method requires superuser access, since some options
197
/// (like onion service configurations) can be sensitive.
198
///
199
/// This method can return either the value for a single option,
200
/// or multiple values of the tree.
201
///
202
/// It returns the actual configuration values _as used_:
203
///
204
/// - All defaults are filled in.
205
/// - Any renamed options are replaced with their real names.
206
/// - Unrecognized options are omitted.
207
#[derive(Debug, serde::Deserialize, derive_deftly::Deftly)]
208
#[derive_deftly(rpc::DynMethod)]
209
#[deftly(rpc(method_name = "arti:get_config"))]
210
pub(super) struct GetConfig {
211
    /// A path in the RPC configuration tree to retrieve.
212
    ///
213
    /// This path is either the empty string,
214
    /// or a sequence of period-separated configuration identifiers.
215
    /// The root of the tree can be represented as "." or as "".
216
    key: String,
217
}
218

            
219
/// The result from a call to `arti:get_config`
220
#[derive(Clone, Debug, serde::Serialize)]
221
pub(super) struct GetConfigResult {
222
    /// A JSON value representing part of the configuration tree,
223
    /// or `null` if there is no value at that position in the tree.
224
    value: Option<ConfigValue>,
225
}
226

            
227
impl rpc::RpcMethod for GetConfig {
228
    type Output = GetConfigResult;
229
    type Update = rpc::NoUpdates;
230
}
231

            
232
/// RPC method implementation: invoke `arti:set_config` on a superuser session.
233
pub(super) async fn set_config_on_rpcsuperuser<R: Runtime>(
234
    session: Arc<RpcSuperuser<R>>,
235
    method: Box<SetConfig>,
236
    _ctx: Arc<dyn rpc::Context>,
237
) -> Result<rpc::Nil, rpc::RpcError> {
238
    let cfg_mgr = &session.cfg_mgr;
239
    cfg_mgr.try_modify_cfg(
240
        |cfg| {
241
            cfg.apply_key_value(method.key.as_str(), method.value.0);
242
            Ok(())
243
        },
244
        tor_config::Reconfigure::AllOrNothing,
245
    )?;
246
    Ok(rpc::Nil::default())
247
}
248

            
249
/// RPC method implementation: invoke `arti:get_config` on a supuruser session.
250
pub(super) async fn get_config_on_rpcsuperuser<R: Runtime>(
251
    session: Arc<RpcSuperuser<R>>,
252
    method: Box<GetConfig>,
253
    _ctx: Arc<dyn rpc::Context>,
254
) -> Result<GetConfigResult, rpc::RpcError> {
255
    let cfg_mgr = &session.cfg_mgr;
256
    let value = cfg_mgr
257
        .get_cfg_setting(method.key.as_str())
258
        .map_err(|e| rpc::RpcError::new(e.to_string(), rpc::RpcErrorKind::RequestError))?;
259
    Ok(GetConfigResult { value })
260
}