mirror of
https://github.com/Fare-spec/Oxylos.git
synced 2025-12-08 02:30:38 +00:00
Started project
This commit is contained in:
33
src/cli.rs
Normal file
33
src/cli.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::config::{DEFAULT_PRIVKEY, DEFAULT_PUBKEY};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about=None)]
|
||||
pub struct Args {
|
||||
#[arg(short = 'e', long = "encrypt", conflicts_with_all = ["decrypt", "inbox"])]
|
||||
pub encrypt: bool,
|
||||
|
||||
#[arg(short = 'd', long = "decrypt", conflicts_with_all = ["encrypt", "inbox"])]
|
||||
pub decrypt: bool,
|
||||
|
||||
#[arg(long = "inbox", conflicts_with_all = ["encrypt", "decrypt"])]
|
||||
pub inbox: bool,
|
||||
|
||||
#[arg(short = 'p', long = "path")]
|
||||
pub path: Option<PathBuf>,
|
||||
|
||||
#[arg(short = 'g', long = "generate-new-keys", conflicts_with_all = ["encrypt", "decrypt", "inbox"])]
|
||||
pub generate_new_keys: bool,
|
||||
|
||||
#[arg(long = "public-key", default_value = DEFAULT_PUBKEY)]
|
||||
pub public_key: PathBuf,
|
||||
|
||||
#[arg(long = "private-key", default_value = DEFAULT_PRIVKEY)]
|
||||
pub private_key: PathBuf,
|
||||
|
||||
#[arg(long = "passphrase")]
|
||||
pub passphrase: Option<String>,
|
||||
}
|
||||
|
||||
19
src/commands/decrypt.rs
Normal file
19
src/commands/decrypt.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::io::{self, Write};
|
||||
|
||||
use crate::cli::Args;
|
||||
use crate::crypto::rsa::decrypt_with_priv;
|
||||
|
||||
pub fn run(args: &Args) -> Result<()> {
|
||||
let private_pem = std::fs::read_to_string(&args.private_key).context("read private key failed")?;
|
||||
let pass = args.passphrase.as_ref().map(|s| s.as_bytes());
|
||||
let in_path = match args.path.as_ref() {
|
||||
Some(p) => p,
|
||||
None => bail!("--path is required for --decrypt"),
|
||||
};
|
||||
let ciphertext = std::fs::read(in_path).context("read ciphertext failed")?;
|
||||
let plain = decrypt_with_priv(&ciphertext, &private_pem, pass)?;
|
||||
io::stdout().write_all(&plain)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
33
src/commands/encrypt.rs
Normal file
33
src/commands/encrypt.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use anyhow::{Context, Result};
|
||||
use chrono::Utc;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::cli::Args;
|
||||
use crate::config::DEFAULT_OUT_MSG;
|
||||
use crate::crypto::rsa::encrypt_with_pub;
|
||||
use crate::storage::fsio::write_all;
|
||||
|
||||
pub fn run(args: &Args) -> Result<()> {
|
||||
let _t = Utc::now();
|
||||
let uuid = Uuid::new_v4();
|
||||
let out = format!("{DEFAULT_OUT_MSG}{uuid}");
|
||||
let out_path = args.path.as_deref().unwrap_or(Path::new(&out));
|
||||
|
||||
let mut author = String::new();
|
||||
println!("enter your name/pseudonyme below:");
|
||||
io::stdin().read_line(&mut author).context("stdin author")?;
|
||||
|
||||
let mut content = String::new();
|
||||
println!("enter your text below:");
|
||||
io::stdin()
|
||||
.read_line(&mut content)
|
||||
.context("stdin content")?;
|
||||
|
||||
let pk_pem = std::fs::read_to_string(&args.public_key).context("read public key failed")?;
|
||||
let payload = format!("Author: {author}\n{content}");
|
||||
let cipher_text = encrypt_with_pub(payload.trim_end().as_bytes(), &pk_pem)?;
|
||||
write_all(out_path, &cipher_text)?;
|
||||
Ok(())
|
||||
}
|
||||
18
src/commands/inbox.rs
Normal file
18
src/commands/inbox.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use anyhow::{Context, Result};
|
||||
use std::io::{self, Write};
|
||||
|
||||
use crate::cli::Args;
|
||||
use crate::message::scan::get_intended_messages_with_pass;
|
||||
|
||||
pub fn run(args: &Args) -> Result<()> {
|
||||
let private_pem = std::fs::read_to_string(&args.private_key).context("read private key failed")?;
|
||||
let pass = args.passphrase.as_deref().map(str::as_bytes);
|
||||
let items = get_intended_messages_with_pass(&private_pem, pass)?;
|
||||
for (path, plain) in items {
|
||||
println!("{}\n---", path.display());
|
||||
io::stdout().write_all(&plain)?;
|
||||
println!();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
28
src/commands/mod.rs
Normal file
28
src/commands/mod.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::cli::Args;
|
||||
|
||||
pub mod decrypt;
|
||||
pub mod encrypt;
|
||||
pub mod inbox;
|
||||
|
||||
pub fn dispatch(args: Args) -> Result<()> {
|
||||
if args.generate_new_keys {
|
||||
crate::crypto::rsa::generate_keys(
|
||||
crate::config::DEFAULT_PRIVKEY,
|
||||
crate::config::DEFAULT_PUBKEY,
|
||||
)?;
|
||||
return Ok(());
|
||||
}
|
||||
if args.encrypt {
|
||||
return encrypt::run(&args);
|
||||
}
|
||||
if args.decrypt {
|
||||
return decrypt::run(&args);
|
||||
}
|
||||
if args.inbox {
|
||||
return inbox::run(&args);
|
||||
}
|
||||
let _ = crate::message::scan::search_messages()?;
|
||||
Ok(())
|
||||
}
|
||||
4
src/config.rs
Normal file
4
src/config.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub const DEFAULT_PUBKEY: &str = "storage/keys/public.pem";
|
||||
pub const DEFAULT_PRIVKEY: &str = "storage/keys/private.pem";
|
||||
pub const DEFAULT_OUT_MSG: &str = "storage/messages/";
|
||||
|
||||
2
src/crypto/mod.rs
Normal file
2
src/crypto/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod rsa;
|
||||
|
||||
111
src/crypto/rsa.rs
Normal file
111
src/crypto/rsa.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use openssl::encrypt::{Decrypter, Encrypter};
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use openssl::rsa::{Padding, Rsa};
|
||||
|
||||
use crate::errors::CryptoError;
|
||||
use crate::storage::fsio::write_all;
|
||||
use std::path::Path;
|
||||
|
||||
fn map_err(ctx: &str, e: ErrorStack) -> CryptoError {
|
||||
CryptoError::OpenSsl(format!("{ctx}: {e}"))
|
||||
}
|
||||
|
||||
pub fn load_public_key(pem: &str) -> Result<PKey<openssl::pkey::Public>, CryptoError> {
|
||||
if pem.contains("BEGIN RSA PUBLIC KEY") {
|
||||
let rsa =
|
||||
Rsa::public_key_from_pem_pkcs1(pem.as_bytes()).map_err(|e| map_err("pkcs1", e))?;
|
||||
PKey::from_rsa(rsa).map_err(|e| map_err("pkey from rsa", e))
|
||||
} else {
|
||||
PKey::public_key_from_pem(pem.as_bytes()).map_err(|e| map_err("public_key_from_pem", e))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_private_key(pem: &str, pass: Option<&[u8]>) -> Result<PKey<Private>, CryptoError> {
|
||||
if let Some(p) = pass {
|
||||
PKey::private_key_from_pem_passphrase(pem.as_bytes(), p)
|
||||
.map_err(|e| map_err("private+pass", e))
|
||||
} else {
|
||||
PKey::private_key_from_pem(pem.as_bytes()).map_err(|e| map_err("private", e))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypt_with_pub(plain: &[u8], pub_pem: &str) -> Result<Vec<u8>, CryptoError> {
|
||||
let pkey = load_public_key(pub_pem)?;
|
||||
let mut enc = Encrypter::new(&pkey).map_err(|e| map_err("enc new", e))?;
|
||||
enc.set_rsa_padding(Padding::PKCS1_OAEP)
|
||||
.map_err(|e| map_err("set padding", e))?;
|
||||
enc.set_rsa_oaep_md(MessageDigest::sha256())
|
||||
.map_err(|e| map_err("oaep md", e))?;
|
||||
enc.set_rsa_mgf1_md(MessageDigest::sha256())
|
||||
.map_err(|e| map_err("mgf1 md", e))?;
|
||||
let mut out = vec![
|
||||
0;
|
||||
enc.encrypt_len(plain)
|
||||
.map_err(|e| map_err("encrypt_len", e))?
|
||||
];
|
||||
let n = enc
|
||||
.encrypt(plain, &mut out)
|
||||
.map_err(|e| map_err("encrypt", e))?;
|
||||
out.truncate(n);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn decrypt_with_priv(
|
||||
cipher: &[u8],
|
||||
priv_pem: &str,
|
||||
pass: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, CryptoError> {
|
||||
let pkey = load_private_key(priv_pem, pass)?;
|
||||
let mut dec = Decrypter::new(&pkey).map_err(|e| map_err("dec new", e))?;
|
||||
dec.set_rsa_padding(Padding::PKCS1_OAEP)
|
||||
.map_err(|e| map_err("set padding", e))?;
|
||||
dec.set_rsa_oaep_md(MessageDigest::sha256())
|
||||
.map_err(|e| map_err("oaep md", e))?;
|
||||
dec.set_rsa_mgf1_md(MessageDigest::sha256())
|
||||
.map_err(|e| map_err("mgf1 md", e))?;
|
||||
let mut out = vec![
|
||||
0;
|
||||
dec.decrypt_len(cipher)
|
||||
.map_err(|e| map_err("decrypt_len", e))?
|
||||
];
|
||||
let n = dec
|
||||
.decrypt(cipher, &mut out)
|
||||
.map_err(|e| map_err("decrypt", e))?;
|
||||
out.truncate(n);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn generate_keys(priv_out: &str, pub_out: &str) -> Result<(), CryptoError> {
|
||||
let rsa = Rsa::generate(3072).map_err(|e| map_err("rsa gen", e))?;
|
||||
let pkey = PKey::from_rsa(rsa).map_err(|e| map_err("to pkey", e))?;
|
||||
let sk_pem = pkey
|
||||
.private_key_to_pem_pkcs8()
|
||||
.map_err(|e| map_err("pem pkcs8", e))?;
|
||||
let pk_pem = pkey
|
||||
.public_key_to_pem()
|
||||
.map_err(|e| map_err("pub pem", e))?;
|
||||
write_all(Path::new(priv_out), &sk_pem)
|
||||
.map_err(|e| CryptoError::OpenSsl(format!("write priv: {e}")))?;
|
||||
write_all(Path::new(pub_out), &pk_pem)
|
||||
.map_err(|e| CryptoError::OpenSsl(format!("write pub: {e}")))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn roundtrip_encrypt_decrypt_pkcs8() {
|
||||
let rsa = Rsa::generate(2048).unwrap();
|
||||
let pkey = PKey::from_rsa(rsa).unwrap();
|
||||
let pk_pem = String::from_utf8(pkey.public_key_to_pem().unwrap()).unwrap();
|
||||
let sk_pem = String::from_utf8(pkey.private_key_to_pem_pkcs8().unwrap()).unwrap();
|
||||
let msg = b"hello, cryptoworld";
|
||||
let cipher = encrypt_with_pub(msg, &pk_pem).unwrap();
|
||||
let plain = decrypt_with_priv(&cipher, &sk_pem, None).unwrap();
|
||||
assert_eq!(msg, &plain[..]);
|
||||
}
|
||||
}
|
||||
8
src/errors.rs
Normal file
8
src/errors.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CryptoError {
|
||||
#[error("{0}")]
|
||||
OpenSsl(String),
|
||||
}
|
||||
|
||||
8
src/lib.rs
Normal file
8
src/lib.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
pub mod errors;
|
||||
pub mod commands;
|
||||
pub mod crypto;
|
||||
pub mod storage;
|
||||
pub mod message;
|
||||
|
||||
10
src/main.rs
Normal file
10
src/main.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = oxylos::cli::Args::parse();
|
||||
oxylos::storage::init::ensure_storage_or_init()?;
|
||||
oxylos::storage::init::ensure_os_dropboxes()?;
|
||||
let _ = oxylos::message::sync::auto_sync()?;
|
||||
oxylos::commands::dispatch(args)
|
||||
}
|
||||
20
src/message/hash.rs
Normal file
20
src/message/hash.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use openssl::sha::Sha256;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read};
|
||||
use std::path::Path;
|
||||
|
||||
pub fn sha256_file(path: &Path) -> io::Result<[u8; 32]> {
|
||||
let file = File::open(path)?;
|
||||
let mut reader = std::io::BufReader::new(file);
|
||||
let mut hasher = Sha256::new();
|
||||
let mut buf = [0u8; 4096];
|
||||
loop {
|
||||
let n = reader.read(&mut buf)?;
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
hasher.update(&buf[..n]);
|
||||
}
|
||||
Ok(hasher.finish())
|
||||
}
|
||||
|
||||
4
src/message/mod.rs
Normal file
4
src/message/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod scan;
|
||||
pub mod sync;
|
||||
pub mod hash;
|
||||
|
||||
112
src/message/scan.rs
Normal file
112
src/message/scan.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
use std::fs::read_dir;
|
||||
use std::fs::DirEntry;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::crypto::rsa::decrypt_with_priv;
|
||||
use crate::storage::fsio::is_valid_dir;
|
||||
use crate::storage::paths::{persistent_dir, ram_dir};
|
||||
|
||||
pub fn search_messages() -> io::Result<Vec<DirEntry>> {
|
||||
search_messages_in(persistent_dir(), ram_dir())
|
||||
}
|
||||
|
||||
pub fn search_messages_in(persistent: &str, ram: &str) -> io::Result<Vec<DirEntry>> {
|
||||
if !is_valid_dir(persistent) {
|
||||
eprintln!("Couldn't find persistent folder at {persistent}");
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
if !is_valid_dir(ram) {
|
||||
eprintln!("Couldn't find RAM folder at {ram}");
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let ram_paths = read_dir(ram)?;
|
||||
let persistent_paths = read_dir(persistent)?;
|
||||
let mut all = Vec::new();
|
||||
all.extend(ram_paths.filter_map(Result::ok));
|
||||
all.extend(persistent_paths.filter_map(Result::ok));
|
||||
Ok(all)
|
||||
}
|
||||
|
||||
pub fn get_intended_messages(private_pem: &str) -> io::Result<Vec<(PathBuf, Vec<u8>)>> {
|
||||
get_intended_messages_with_pass(private_pem, None)
|
||||
}
|
||||
|
||||
pub fn get_intended_messages_with_pass(
|
||||
private_pem: &str,
|
||||
pass: Option<&[u8]>,
|
||||
) -> io::Result<Vec<(PathBuf, Vec<u8>)>> {
|
||||
get_intended_messages_in_with_pass(private_pem, pass, persistent_dir(), ram_dir())
|
||||
}
|
||||
|
||||
pub fn get_intended_messages_in_with_pass(
|
||||
private_pem: &str,
|
||||
pass: Option<&[u8]>,
|
||||
persistent: &str,
|
||||
ram: &str,
|
||||
) -> io::Result<Vec<(PathBuf, Vec<u8>)>> {
|
||||
let entries = search_messages_in(persistent, ram)?;
|
||||
let mut found = Vec::new();
|
||||
for entry in entries {
|
||||
let p = entry.path();
|
||||
if !p.is_file() {
|
||||
continue;
|
||||
}
|
||||
if let Ok(cipher) = std::fs::read(&p) {
|
||||
if let Ok(plain) = decrypt_with_priv(&cipher, private_pem, pass) {
|
||||
found.push((p, plain));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(found)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::crypto::rsa::encrypt_with_pub;
|
||||
use openssl::pkey::PKey;
|
||||
use openssl::rsa::Rsa;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn search_messages_missing_dirs_yields_empty_ok() -> io::Result<()> {
|
||||
let list = search_messages_in("/this/does/not/exist", "/neither/does/this")?;
|
||||
assert!(list.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_intended_messages_returns_only_decryptable() -> io::Result<()> {
|
||||
let rsa = Rsa::generate(2048).unwrap();
|
||||
let pkey = PKey::from_rsa(rsa).unwrap();
|
||||
let pk_pem = String::from_utf8(pkey.public_key_to_pem().unwrap()).unwrap();
|
||||
let sk_pem = String::from_utf8(pkey.private_key_to_pem_pkcs8().unwrap()).unwrap();
|
||||
let d_persistent = tempdir()?;
|
||||
let d_ram = tempdir()?;
|
||||
let msg1 = b"alpha";
|
||||
let msg2 = b"bravo";
|
||||
let c1 = encrypt_with_pub(msg1, &pk_pem).unwrap();
|
||||
let c2 = encrypt_with_pub(msg2, &pk_pem).unwrap();
|
||||
let p1 = d_persistent.path().join("m1.msg");
|
||||
let p2 = d_ram.path().join("m2.msg");
|
||||
std::fs::write(&p1, &c1)?;
|
||||
std::fs::write(&p2, &c2)?;
|
||||
let p_bad = d_persistent.path().join("noise.bin");
|
||||
std::fs::write(&p_bad, b"\x01\x02\x03\x04\x05")?;
|
||||
let list = get_intended_messages_in_with_pass(
|
||||
&sk_pem,
|
||||
None,
|
||||
d_persistent.path().to_str().unwrap(),
|
||||
d_ram.path().to_str().unwrap(),
|
||||
)?;
|
||||
assert_eq!(list.len(), 2);
|
||||
let plains: Vec<String> = list
|
||||
.iter()
|
||||
.map(|(_, v)| String::from_utf8_lossy(v).to_string())
|
||||
.collect();
|
||||
assert!(plains.contains(&"alpha".to_string()));
|
||||
assert!(plains.contains(&"bravo".to_string()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
72
src/message/sync.rs
Normal file
72
src/message/sync.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::message::hash::sha256_file;
|
||||
use crate::storage::fsio::{copy_msg, list_files};
|
||||
|
||||
pub struct SyncReport {
|
||||
pub copied_to_remote: usize,
|
||||
pub copied_to_local: usize,
|
||||
}
|
||||
|
||||
pub fn bidirectional_sync(
|
||||
local_outbox: &[PathBuf],
|
||||
remote_dirs: &[Vec<PathBuf>],
|
||||
local_dest: &str,
|
||||
remote_dest: &str,
|
||||
) -> io::Result<SyncReport> {
|
||||
let mut own_map = HashMap::new();
|
||||
for f in local_outbox {
|
||||
let digest = sha256_file(f)?;
|
||||
own_map.insert(digest, f);
|
||||
}
|
||||
let mut remote_map = HashMap::new();
|
||||
for group in remote_dirs {
|
||||
for f in group {
|
||||
let digest = sha256_file(f)?;
|
||||
remote_map.insert(digest, f);
|
||||
}
|
||||
}
|
||||
|
||||
let commons: Vec<[u8; 32]> = own_map
|
||||
.keys()
|
||||
.filter(|k| remote_map.contains_key(*k))
|
||||
.copied()
|
||||
.collect();
|
||||
for k in commons {
|
||||
own_map.remove(&k);
|
||||
remote_map.remove(&k);
|
||||
}
|
||||
|
||||
let mut copied_to_remote = 0usize;
|
||||
for msg in own_map.values() {
|
||||
let name = uuid::Uuid::new_v4().to_string();
|
||||
copy_msg(msg.to_str().unwrap(), &format!("{remote_dest}{name}"))?;
|
||||
copied_to_remote += 1;
|
||||
}
|
||||
|
||||
let mut copied_to_local = 0usize;
|
||||
for msg in remote_map.values() {
|
||||
let name = uuid::Uuid::new_v4().to_string();
|
||||
copy_msg(msg.to_str().unwrap(), &format!("{local_dest}{name}"))?;
|
||||
copied_to_local += 1;
|
||||
}
|
||||
|
||||
Ok(SyncReport {
|
||||
copied_to_remote,
|
||||
copied_to_local,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn auto_sync() -> io::Result<SyncReport> {
|
||||
let local = list_files(crate::config::DEFAULT_OUT_MSG)?;
|
||||
let remote_p = list_files(crate::storage::paths::persistent_dir())?;
|
||||
let remote_r = list_files(crate::storage::paths::ram_dir())?;
|
||||
bidirectional_sync(
|
||||
&local,
|
||||
&[remote_p, remote_r],
|
||||
crate::config::DEFAULT_OUT_MSG,
|
||||
crate::storage::paths::persistent_dir(),
|
||||
)
|
||||
}
|
||||
47
src/storage/fsio.rs
Normal file
47
src/storage/fsio.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use filetime::{set_file_times, FileTime};
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn neutralize_metadata(path: &Path) -> io::Result<()> {
|
||||
let perms = fs::Permissions::from_mode(0o600);
|
||||
let _ = fs::set_permissions(path, perms);
|
||||
let zero = FileTime::from_unix_time(0, 0);
|
||||
let _ = set_file_times(path, zero, zero);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_all(path: &Path, content: &[u8]) -> io::Result<()> {
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
let mut f = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(path)?;
|
||||
let _ = fs::set_permissions(path, fs::Permissions::from_mode(0o600));
|
||||
f.write_all(content)?;
|
||||
f.sync_all()?;
|
||||
neutralize_metadata(path)
|
||||
}
|
||||
|
||||
pub fn list_files(directory_path: &str) -> io::Result<Vec<PathBuf>> {
|
||||
let mut paths = Vec::new();
|
||||
for entry in fs::read_dir(directory_path)? {
|
||||
let entry = entry?;
|
||||
paths.push(entry.path());
|
||||
}
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
pub fn is_valid_dir(path: &str) -> bool {
|
||||
Path::new(path).is_dir()
|
||||
}
|
||||
|
||||
pub fn copy_msg(source: &str, destination: &str) -> io::Result<()> {
|
||||
let mut buf = Vec::new();
|
||||
File::open(source)?.read_to_end(&mut buf)?;
|
||||
write_all(Path::new(destination), &buf)
|
||||
}
|
||||
38
src/storage/init.rs
Normal file
38
src/storage/init.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use colored::Colorize;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn ensure_storage_or_init() -> io::Result<()> {
|
||||
let path = Path::new("storage");
|
||||
if !path.exists() {
|
||||
init()?;
|
||||
std::process::exit(0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_persistent_storage() -> io::Result<()> {
|
||||
std::fs::create_dir_all("storage")?;
|
||||
std::fs::create_dir_all("storage/keys")?;
|
||||
std::fs::create_dir_all("storage/index")?;
|
||||
std::fs::create_dir_all("storage/messages")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init() -> io::Result<()> {
|
||||
let color_text = "Olyxos -h";
|
||||
println!(
|
||||
"Thank you for using Olyxos. This is the initialization of the program; it creates 4 directories. The first one is storage: this is where the program stores everything. If you already have a pair of cryptographic keys and you want to use them, please put them in storage/keys/ under the names 'private.pem' and 'public.pem'. You can see the arguments to use by running {}",
|
||||
color_text.red()
|
||||
);
|
||||
create_persistent_storage()
|
||||
}
|
||||
|
||||
pub fn ensure_os_dropboxes() -> io::Result<()> {
|
||||
use std::fs::create_dir_all;
|
||||
let p = crate::storage::paths::persistent_dir();
|
||||
let r = crate::storage::paths::ram_dir();
|
||||
let _ = create_dir_all(p);
|
||||
let _ = create_dir_all(r);
|
||||
Ok(())
|
||||
}
|
||||
4
src/storage/mod.rs
Normal file
4
src/storage/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod init;
|
||||
pub mod paths;
|
||||
pub mod fsio;
|
||||
|
||||
29
src/storage/paths.rs
Normal file
29
src/storage/paths.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn persistent_dir() -> &'static str {
|
||||
r"C:\ProgramData\Oxylos\"
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn ram_dir() -> &'static str {
|
||||
r"Global\Oxylos"
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub fn persistent_dir() -> &'static str {
|
||||
"/tmp/Oxylos/"
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub fn ram_dir() -> &'static str {
|
||||
"/dev/shm/Oxylos/"
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn persistent_dir() -> &'static str {
|
||||
"/tmp/Oxylos/"
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn ram_dir() -> &'static str {
|
||||
"/tmp/Oxylos.shm/"
|
||||
}
|
||||
Reference in New Issue
Block a user