瀏覽代碼

Merge branch 'master' of gitlab.com:timvisee/ffsend

timvisee 6 年之前
父節點
當前提交
0a6696d912

+ 39 - 1
Cargo.lock

@@ -130,6 +130,14 @@ name = "cfg-if"
 version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "chbs"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "chrono"
 version = "0.4.3"
@@ -175,6 +183,14 @@ dependencies = [
  "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "cloudabi"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "colored"
 version = "1.6.0"
@@ -403,8 +419,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "ffsend"
-version = "0.0.7"
+version = "0.0.8"
 dependencies = [
+ "chbs 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "chrono 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "clipboard 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1011,6 +1028,23 @@ dependencies = [
  "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "rand"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "redox_syscall"
 version = "0.1.40"
@@ -1748,10 +1782,12 @@ dependencies = [
 "checksum bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7dd32989a66957d3f0cba6588f15d4281a733f4e9ffc43fcd2385f57d3bf99ff"
 "checksum cc 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "49ec142f5768efb5b7622aebc3fdbdbb8950a4b9ba996393cb76ef7466e8747d"
 "checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18"
+"checksum chbs 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7580cf614cddea9bbf9de26bb17faa88aba720c745e65bd96fdfd794d2bf30ee"
 "checksum chrono 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a81892f0d5a53f46fc05ef0b917305a81c13f1f13bb59ac91ff595817f0764b1"
 "checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536"
 "checksum clipboard 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b9b4623b47d8637fc9d47564583d4cc01eb8c8e34e26b2bf348bf4b036acb657"
 "checksum clipboard-win 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "289da2fc09ab964a4948a63287c94fcb4698fa823c46da84c3792928c9d36110"
+"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
 "checksum colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0aa3473e85a3161b59845d6096b289bb577874cafeaf75ea1b1beaa6572c7fc"
 "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
 "checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67"
@@ -1845,6 +1881,8 @@ dependencies = [
 "checksum quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e44651a0dc4cdd99f71c83b561e221f714912d11af1a4dff0631f923d53af035"
 "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
 "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
+"checksum rand 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "12397506224b2f93e6664ffc4f664b29be8208e5157d3d90b44f09b5fae470ea"
+"checksum rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "edecf0f94da5551fc9b492093e30b041a891657db7940ee221f9d2f66e82eef2"
 "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
 "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
 "checksum regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75ecf88252dce580404a22444fc7d626c01815debba56a7f4f536772a5ff19d3"

+ 2 - 1
Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "ffsend"
-version = "0.0.7"
+version = "0.0.8"
 authors = ["Tim Visee <timvisee@gmail.com>"]
 license = "GPL-3.0"
 readme = "README.md"
@@ -61,6 +61,7 @@ history = []
 no-color = ["colored/no-color"]
 
 [dependencies]
+chbs = "0.0.1"
 chrono = "0.4"
 clap = "2.31"
 colored = "1.6"

+ 3 - 3
README.md

@@ -52,7 +52,7 @@ _Note: this tool is currently in the alpha phase_
 - Fully featured and friendly command line tool
 - Upload and download files and directories securely
 - Always encrypted on the client
-- Additional password protection and configurable download limits
+- Additional password protection, generation and configurable download limits
 - Built-in file and directory archiving and extraction
 - History tracking your files for easy management
 - Ability to use your own Send host
@@ -325,8 +325,8 @@ documentation [here][send-encryption].
 ```
 $ ffsend help
 
-ffsend 0.0.7
-Tim Visee <https://timvisee.com/>
+ffsend 0.0.8
+Tim Visee <timvisee@gmail.com>
 Easily and securely share files from the command line.
 A fully featured Firefox Send client.
 

+ 2 - 2
res/asciinema-demo.json

@@ -4,7 +4,7 @@
   "height": 19,
   "duration": 78.431135,
   "command": null,
-  "title": "ffsend v0.0.1 demo",
+  "title": "ffsend v0.0.8 demo",
   "env": {
     "TERM": "xterm-256color",
     "SHELL": "/bin/bash"
@@ -44,7 +44,7 @@
     ],
     [
       0.004048,
-      "ffsend 0.0.1\r\nUsage: ffsend [FLAGS] <SUBCOMMAND> ...\r\n\r\nSecurely and easily share files from the command line.\r\nA fully featured Firefox Send client.\r\n\r\nMissing subcommand. Here are the most used:\r\n"
+      "ffsend 0.0.7\r\nUsage: ffsend [FLAGS] <SUBCOMMAND> ...\r\n\r\nSecurely and easily share files from the command line.\r\nA fully featured Firefox Send client.\r\n\r\nMissing subcommand. Here are the most used:\r\n"
     ],
     [
       0.000148,

+ 21 - 1
src/action/password.rs

@@ -5,6 +5,12 @@ use ffsend_api::action::password::{
 };
 use ffsend_api::file::remote_file::RemoteFile;
 use ffsend_api::reqwest::Client;
+use prettytable::{
+    cell::Cell,
+    format::FormatBuilder,
+    row::Row,
+    Table,
+};
 
 use cmd::matcher::{
     main::MainMatcher,
@@ -50,10 +56,13 @@ impl<'a> Password<'a> {
         // Ensure the owner token is set
         ensure_owner_token(file.owner_token_mut(), &matcher_main);
 
+        // Get the password to use and whether it was generated
+        let (password, password_generated) = matcher_password.password();
+
         // Execute an password action
         let result = ApiPassword::new(
             &file,
-            &matcher_password.password(),
+            &password,
             None,
         ).invoke(&client);
         if let Err(PasswordError::Expired) = result {
@@ -67,6 +76,17 @@ impl<'a> Password<'a> {
         #[cfg(feature = "history")]
         history_tool::add(&matcher_main, file, true);
 
+        // Print the passphrase if one was generated
+        if password_generated {
+            let mut table = Table::new();
+            table.set_format(FormatBuilder::new().padding(0, 2).build());
+            table.add_row(Row::new(vec![
+                Cell::new("Passphrase:"),
+                Cell::new(&password),
+            ]));
+            table.printstd();
+        }
+
         // Print a success message
         print_success("Password set");
 

+ 13 - 1
src/action/upload.rs

@@ -208,12 +208,18 @@ impl<'a> Upload<'a> {
         // Build the progress reporter
         let progress_reporter: Arc<Mutex<ProgressReporter>> = progress_bar;
 
+        // Get the password to use and whether it was generated
+        let password = matcher_upload.password();
+        let (password, password_generated) = password
+            .map(|(p, g)| (Some(p), g))
+            .unwrap_or((None, false));
+
         // Execute an upload action
         let file = ApiUpload::new(
             host,
             path.clone(),
             file_name,
-            matcher_upload.password(),
+            password.clone(),
             params,
         ).invoke(&client, &progress_reporter)?;
 
@@ -225,6 +231,12 @@ impl<'a> Upload<'a> {
             Cell::new("Share link:"),
             Cell::new(url.as_str()),
         ]));
+        if password_generated {
+            table.add_row(Row::new(vec![
+                Cell::new("Passphrase:"),
+                Cell::new(&password.unwrap_or("?".into())),
+            ]));
+        }
         if matcher_main.verbose() {
             table.add_row(Row::new(vec![
                 Cell::new("Owner token:"),

+ 36 - 0
src/cmd/arg/gen_passphrase.rs

@@ -0,0 +1,36 @@
+use clap::Arg;
+use chbs;
+
+use super::{CmdArg, CmdArgFlag};
+
+/// The number of words the passphrase must consist of.
+const PASSPHRASE_WORDS: usize = 4;
+
+/// The passphrase generation argument.
+pub struct ArgGenPassphrase { }
+
+impl ArgGenPassphrase {
+    /// Generate a cryptographically secure passphrase that is easily
+    /// rememberable using diceware.
+    pub fn gen_passphrase() -> String {
+        chbs::passphrase(PASSPHRASE_WORDS)
+    }
+}
+
+impl CmdArg for ArgGenPassphrase {
+    fn name() -> &'static str {
+        "gen-passphrase"
+    }
+
+    fn build<'b, 'c>() -> Arg<'b, 'c> {
+        Arg::with_name("gen-passphrase")
+            .long("gen-passphrase")
+            .alias("gen-password")
+            .alias("generate-passphrase")
+            .alias("generate-password")
+            .short("P")
+            .help("Protect the file with a generated passphrase")
+    }
+}
+
+impl CmdArgFlag for ArgGenPassphrase { }

+ 2 - 0
src/cmd/arg/mod.rs

@@ -1,4 +1,5 @@
 pub mod download_limit;
+pub mod gen_passphrase;
 pub mod host;
 pub mod owner;
 pub mod password;
@@ -6,6 +7,7 @@ pub mod url;
 
 // Reexport to arg module
 pub use self::download_limit::ArgDownloadLimit;
+pub use self::gen_passphrase::ArgGenPassphrase;
 pub use self::host::ArgHost;
 pub use self::owner::ArgOwner;
 pub use self::password::ArgPassword;

+ 18 - 3
src/cmd/matcher/password.rs

@@ -2,7 +2,14 @@ use clap::ArgMatches;
 use ffsend_api::url::Url;
 use rpassword::prompt_password_stderr;
 
-use cmd::arg::{ArgOwner, ArgPassword, ArgUrl, CmdArgOption};
+use cmd::arg::{
+    ArgGenPassphrase,
+    ArgOwner,
+    ArgPassword,
+    ArgUrl,
+    CmdArgFlag,
+    CmdArgOption,
+};
 use cmd::matcher::{MainMatcher, Matcher};
 use util::check_empty_password;
 
@@ -29,7 +36,15 @@ impl<'a: 'b, 'b> PasswordMatcher<'a> {
     }
 
     /// Get the password.
-    pub fn password(&'a self) -> String {
+    ///
+    /// The password is returned in the following format:
+    /// `(password, generated)`
+    pub fn password(&'a self) -> (String, bool) {
+        // Generate a passphrase if requested
+        if ArgGenPassphrase::is_present(self.matches) {
+            return (ArgGenPassphrase::gen_passphrase(), true);
+        }
+
         // Get the password, or prompt for it
         let password = match ArgPassword::value(self.matches) {
             Some(password) => password,
@@ -48,7 +63,7 @@ impl<'a: 'b, 'b> PasswordMatcher<'a> {
         // Check for empty passwords
         check_empty_password(&password, &matcher_main);
 
-        password
+        (password, false)
     }
 }
 

+ 25 - 3
src/cmd/matcher/upload.rs

@@ -4,7 +4,14 @@ use ffsend_api::action::params::{
 };
 use ffsend_api::url::Url;
 
-use cmd::arg::{ArgDownloadLimit, ArgHost, ArgPassword, CmdArgOption};
+use cmd::arg::{
+    ArgDownloadLimit,
+    ArgGenPassphrase,
+    ArgHost,
+    ArgPassword,
+    CmdArgFlag,
+    CmdArgOption,
+};
 use super::Matcher;
 use util::{env_var_present, ErrorHintsBuilder, quit_error_msg};
 
@@ -55,9 +62,24 @@ impl<'a: 'b, 'b> UploadMatcher<'a> {
     }
 
     /// Get the password.
-    /// `None` is returned if no password was specified.
-    pub fn password(&'a self) -> Option<String> {
+    /// A generated passphrase will be returned if the user requested so,
+    /// otherwise the specified password is returned.
+    /// If no password was set, `None` is returned instead.
+    ///
+    /// The password is returned in the following format:
+    /// `(password, generated)`
+    pub fn password(&'a self) -> Option<(String, bool)> {
+        // Generate a passphrase if requested
+        if ArgGenPassphrase::is_present(self.matches) {
+            return Some((
+                ArgGenPassphrase::gen_passphrase(),
+                true,
+            ));
+        }
+
+        // Use a specified password or use nothing
         ArgPassword::value(self.matches)
+            .map(|password| (password, false))
     }
 
     /// Get the download limit.

+ 2 - 1
src/cmd/subcmd/password.rs

@@ -1,6 +1,6 @@
 use clap::{App, SubCommand};
 
-use cmd::arg::{ArgOwner, ArgPassword, ArgUrl, CmdArg};
+use cmd::arg::{ArgGenPassphrase, ArgOwner, ArgPassword, ArgUrl, CmdArg};
 
 /// The password command definition.
 pub struct CmdPassword;
@@ -14,6 +14,7 @@ impl CmdPassword {
             .arg(ArgUrl::build())
             .arg(ArgPassword::build()
                  .help("Specify a password, do not prompt"))
+            .arg(ArgGenPassphrase::build())
             .arg(ArgOwner::build())
     }
 }

+ 9 - 2
src/cmd/subcmd/upload.rs

@@ -3,9 +3,15 @@ use ffsend_api::action::params::{
     PARAMS_DEFAULT_DOWNLOAD_STR as DOWNLOAD_DEFAULT,
 };
 
-use cmd::arg::{ArgDownloadLimit, ArgHost, ArgPassword, CmdArg};
+use cmd::arg::{
+    ArgDownloadLimit,
+    ArgGenPassphrase,
+    ArgHost,
+    ArgPassword,
+    CmdArg,
+};
 
-/// The uplaod command definition.
+/// The upload command definition.
 pub struct CmdUpload;
 
 impl CmdUpload {
@@ -22,6 +28,7 @@ impl CmdUpload {
                 .multiple(false))
             .arg(ArgPassword::build()
                  .help("Protect the file with a password"))
+            .arg(ArgGenPassphrase::build())
             .arg(ArgDownloadLimit::build()
                  .default_value(DOWNLOAD_DEFAULT))
             .arg(ArgHost::build())

+ 1 - 0
src/main.rs

@@ -1,3 +1,4 @@
+extern crate chbs;
 extern crate chrono;
 #[macro_use]
 extern crate clap;