Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_shield/src/app/repl.rs

13.4 KiB, 56 runs

created by r1870400018:4070, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1use crate::{
2 app::{
3 cfg::AppConfig,
4 constant as app_const,
5 server,
6 tui::AppStatus,
7 },
8};
9
10use oxedyne_fe2o3_core::{
11 prelude::*,
12 log::{
13 bot::FileConfig,
14 console::{
15 switch_to_logger_console,
16 StdoutLoggerConsole,
17 },
18 },
19 path::NormalPath,
20};
21use oxedyne_fe2o3_crypto::{
22 enc::EncryptionScheme,
23 keystore::Wallet,
24};
25use oxedyne_fe2o3_hash::{
26 csum::ChecksumScheme,
27 hash::HashScheme,
28 kdf::KeyDerivationScheme,
29};
30use oxedyne_fe2o3_iop_crypto::{
31 keys::KeyManager,
32 enc::Encrypter,
33};
34use oxedyne_fe2o3_iop_hash::kdf::KeyDeriver;
35use oxedyne_fe2o3_jdat::{
36 prelude::*,
37 file::JdatFile,
38 string::enc::EncoderConfig,
39};
40use oxedyne_fe2o3_net::id;
41use oxedyne_fe2o3_o3db_sync::O3db;
42use oxedyne_fe2o3_syntax::{
43 core::SyntaxRef,
44 help::Help,
45 msg::{
46 Msg,
47 MsgCmd,
48 },
49 opt::OptionRefVec,
50};
51use oxedyne_fe2o3_text::base2x;
52use oxedyne_fe2o3_tui::lib_tui::{
53 cmds,
54 repl::{
55 Evaluation,
56 Shell,
57 ShellConfig,
58 ShellContext,
59 Splitters,
60 },
61 input::UserInput,
62};
63use oxedyne_fe2o3_namex::InNamex;
64
65use std::{
66 collections::BTreeMap,
67 path::{
68 Path,
69 PathBuf,
70 },
71};
72
73use secrecy::{
74 ExposeSecret,
75 Secret,
76};
77use zeroize::Zeroize;
78
79
80#[derive(Clone)]
81pub struct AppShellContext {
82 pub stat: AppStatus,
83 pub app_cfg: AppConfig,
84 pub syntax: SyntaxRef,
85 pub ws: BTreeMap<Dat, Dat>,
86 pub db: O3db<
87 { id::UID_LEN },
88 id::Uid,
89 EncryptionScheme,
90 HashScheme,
91 HashScheme,
92 ChecksumScheme,
93 >,
94 pub wallet: Wallet,
95 //server: Server,
96}
97
98impl ShellContext for AppShellContext {
99 fn eval(
100 &mut self,
101 input: &String,
102 cfg: &ShellConfig,
103 splitters: &Splitters,
104 )
105 -> Outcome<Vec<Evaluation>>
106 {
107 for expr in splitters.command.split(input).into_iter() {
108 let parts = splitters.assignment.split(expr.val_ref());
109 // 1. try state manipulation
110 match parts.len() {
111 0 => unreachable!(),
112 1 => { // evaluation
113 //let lhs = Dat::decode_string(parts[0].val_ref())?;
114 ////if lhs.kind() != Kind::Str {
115 //// return Err(Error::Local{
116 //// tags: vec![ErrTag::Input, ErrTag::Mismatch],
117 //// kind: ErrKind::Unexpected,
118 //// msg: errmsg!(
119 //// "The left hand side of the assignment is a {:?} but must be a Kind::Str.",
120 //// lhs.kind(),
121 //// )});
122 ////}
123 //if let Some(rhs) = state.get_recursive(&lhs) {
124 // println!("{} = {:?}", lhs, rhs);
125 //} else {
126 // println!("{:?}", lhs);
127 //}
128 //continue;
129 },
130 2 => { // assignment lhs = rhs
131 let lhs = res!(Dat::decode_string(parts[0].val_ref()));
132 let rhs = res!(Dat::decode_string(parts[1].val_ref()));
133 if lhs.kind() != Kind::Str {
134 return Err(err!(
135 "The left hand side of the assignment is a {:?} but must be a Kind::Str.",
136 lhs.kind();
137 Input, Mismatch));
138 }
139 self.ws.insert(lhs, rhs);
140 continue;
141 },
142 _ => return Err(err!(
143 "Only single assignment such as a = b is permitted.";
144 Input, Mismatch)),
145 }
146 // 2. Try syntax command
147 // Split into words and downgrade from phrases to string iterator.
148 let mut parts = splitters.word
149 .split(expr.val_ref())
150 .into_iter()
151 .map(|x| x.to_val())
152 .peekable();
153 // Currently the "echo" command is not in the syntax and therefore not in the help.
154 if let Some("echo") = parts.peek().map(|s| s.as_ref()) {
155 return Ok(vec![Evaluation::Output(input.clone())]);
156 }
157 return self.execute(parts, &cfg);
158 }
159 Ok(vec![Evaluation::None])
160 }
161}
162
163impl AppShellContext {
164
165 pub fn execute<I: IntoIterator<Item=String>>(
166 &mut self,
167 parts: I,
168 shell_cfg: &ShellConfig,
169 )
170 -> Outcome<Vec<Evaluation>>
171 {
172 let mut evals = Vec::new();
173 let msgrx = Msg::new(self.syntax.clone());
174 let msgrx = res!(msgrx.rx_text_iter(
175 parts,
176 Some(app_const::SYNTAX_CMD_SIMILARITY_THRESHOLD),
177 ));
178 for (cmd_key, cmd) in &msgrx.cmds {
179 match cmd_key.as_str() {
180 "help" => {
181 let help = Help::default(); // TODO consider creating only once?
182 for line in res!(help.to_lines(&self.syntax)) {
183 println!("{}", line);
184 }
185 },
186 // Control
187 "exit" => evals.push(res!(cmds::exit_shell(&shell_cfg.exit_msg))),
188 "server" => {
189 let root_path = Path::new(&self.app_cfg.app_root)
190 .normalise() // Now a NormPathBuf.
191 .absolute();
192 // ┌───────────────────────┐
193 // │ Reconfigure logging. │
194 // └───────────────────────┘
195 info!("Reconfiguring log to stdout and file...");
196 res!(switch_to_logger_console::<StdoutLoggerConsole<_>>());
197 let mut log_cfg = log_get_config!();
198 log_cfg.file = Some(FileConfig::new(
199 PathBuf::from(&root_path).join("www").join("logs"),
200 self.app_cfg.app_name.clone(),
201 "log".to_string(),
202 0,
203 Some(1_048_576), // Activate multiple log file archiving using this max size.
204 ));
205 (log_cfg.level, _) = res!(self.app_cfg.server_log_level());
206 log_set_config!(log_cfg);
207 info!("Server now logging at {:?}", log_get_file_path!());
208 info!("┌───────────────────────┐");
209 info!("│ New server session. │");
210 info!("└───────────────────────┘");
211
212 let rt = res!(tokio::runtime::Runtime::new());
213 let (eval, _cmd_chan) = res!(rt.block_on(
214 server::start_server(
215 &self.app_cfg,
216 &self.stat,
217 self.db.clone(),
218 Some(cmd),
219 None,
220 )
221 ));
222 evals.push(eval);
223 }
224 "shell" => evals.push(res!(self.start_shell(&shell_cfg, Some(cmd)))),
225 // Filesystem
226 "cd" => evals.push(res!(cmds::change_directory(cmd))),
227 "ls" => evals.push(res!(cmds::list_directory_contents(cmd))),
228 "pwd" => evals.push(res!(cmds::print_working_directory())),
229 // Wallet
230 "secrets" => evals.push(res!(self.secrets(&shell_cfg, Some(cmd)))),
231 _ => (), // Not implemented yet.
232 }
233 }
234 Ok(evals)
235 }
236
237 pub fn start_shell(
238 &mut self,
239 shell_cfg: &ShellConfig,
240 _cmd: Option<&MsgCmd>,
241 )
242 -> Outcome<Evaluation>
243 {
244 let mut shell = res!(Shell::new(
245 shell_cfg.clone(),
246 self.clone(),
247 ));
248 res!(shell.start());
249 Ok(Evaluation::None)
250 }
251
252 pub fn secrets(
253 &mut self,
254 _shell_cfg: &ShellConfig,
255 cmd: Option<&MsgCmd>,
256 )
257 -> Outcome<Evaluation>
258 {
259 if let Some(msg_cmd) = cmd {
260 if msg_cmd.has_args() {
261 if res!(msg_cmd.has_only_arg("create")) {
262 let vals = res!(msg_cmd.get_arg_vals("create").with_len(1));
263 let name = &vals[0];
264 let pass = res!(UserInput::ask_for_secret(None));
265 let mut kdf = res!(KeyDerivationScheme::from_str(&self.app_cfg.kdf_name));
266 let key = res!(UserInput::derive_key(&mut kdf, pass));
267 if self.wallet.enc_secs().get(name).is_some() {
268 if res!(UserInput::ask(
269 fmt!("Encrypted secret '{}' already exists, replace? (Y/N): ", name).as_str(),
270 )).to_lowercase().as_str() != "y" {
271 return Ok(Evaluation::Output(fmt!("Creation of encrypted secret aborted.")));
272 }
273 }
274 let mut map = DaticleMap::new();
275 map.insert(dat!("kdf_name"), dat!(fmt!("{}", kdf)));
276 map.insert(dat!("kdf_nid"), dat!(fmt!("{}", res!(kdf.name_id()))));
277 map.insert(dat!("kdf_cfg"), dat!(res!(kdf.encode_cfg_to_string())));
278 let enc = res!(EncryptionScheme::new_aes_256_gcm_with_key(&key));
279 map.insert(dat!("enc_name"), dat!(fmt!("{:?}", enc)));
280 map.insert(dat!("enc_nid"), dat!(fmt!("{}", res!(enc.name_id()))));
281 let sec = res!(UserInput::ask_for_secret(
282 Some("Enter the secret you want to encrypt: ")
283 ));
284 let enc_sec = res!(enc.encrypt(sec.expose_secret().as_bytes()));
285 let base2x = base2x::HEMATITE64;
286 let b2x_sec = base2x.to_string(&enc_sec);
287 map.insert(dat!("enc_sec"), dat!(b2x_sec));
288 if let Some(enc_sec_map) = self.wallet.enc_secs_mut().get_mut(name) {
289 *enc_sec_map = dat!(map);
290 } else {
291 self.wallet.enc_secs_mut().insert(name.clone(), dat!(map));
292 }
293 let wallet_path = Path::new("./").join(app_const::WALLET_NAME);
294 res!(self.wallet.save_secret(
295 &wallet_path, " ", Some(EncoderConfig::<(), ()>::default()),
296 ));
297 } else if res!(msg_cmd.has_only_arg("recover")) {
298 let vals = res!(msg_cmd.get_arg_vals("recover").with_len(1));
299 let name = &vals[0];
300 let enc_sec_dat = match self.wallet.enc_secs().get(name) {
301 Some(map_dat) => map_dat,
302 None => return Ok(Evaluation::Output(
303 fmt!("Secret '{}' not found in wallet.", name)
304 )),
305 };
306 // Derive the encryption key from the wallet passphrase using the kdf
307 // configuration. Drop the pass as soon as we can.
308 let key = {
309 let pass = res!(UserInput::ask_for_secret(None));
310 let pass = pass.expose_secret();
311
312 let kdf_name = try_extract_dat!(
313 res!(enc_sec_dat.map_get_type_must(&dat!("kdf_name"), &[&Kind::Str])),
314 Str,
315 );
316 let mut kdf = res!(KeyDerivationScheme::from_str(kdf_name));
317 let kdf_cfg = try_extract_dat!(
318 res!(enc_sec_dat.map_get_type_must(&dat!("kdf_cfg"), &[&Kind::Str])),
319 Str,
320 );
321 res!(kdf.decode_cfg_from_string(&kdf_cfg));
322 res!(kdf.derive(pass.as_bytes()));
323 res!(kdf.get_hash()).to_vec()
324 };
325
326 let enc_name = try_extract_dat!(
327 res!(enc_sec_dat.map_get_type_must(&dat!("enc_name"), &[&Kind::Str])),
328 Str,
329 );
330 let mut enc = res!(EncryptionScheme::from_str(enc_name));
331 enc = res!(enc.set_secret_key(Some(&key)));
332 let enc_sec_base2x = try_extract_dat!(
333 res!(enc_sec_dat.map_get_type_must(&dat!("enc_sec"), &[&Kind::Str])),
334 Str,
335 );
336 let base2x = base2x::HEMATITE64;
337 let enc_sec_byts = res!(base2x.from_str(&enc_sec_base2x));
338 let sec_byts = res!(enc.decrypt(&enc_sec_byts));
339 let mut sec_str = res!(String::from_utf8(sec_byts));
340 res!(UserInput::show_and_clear(
341 Secret::new(fmt!("Press enter to clear: secret is '{}'", sec_str))
342 ));
343 sec_str.zeroize();
344 }
345 } else {
346 return Err(err!("Missing message command."; Invalid, Input, Missing));
347 }
348 }
349 Ok(Evaluation::None)
350 }
351}