diff --git a/Cargo.lock b/Cargo.lock index 0a8593a..0d827d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,11 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "constant_time_eq" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "derive-error-chain" version = "0.10.1" @@ -395,6 +400,15 @@ name = "rustc-demangle" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "sha-crypt" +version = "0.1.0" +dependencies = [ + "constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "sha2" version = "0.7.1" @@ -568,7 +582,7 @@ dependencies = [ "failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "rpassword 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "sha-crypt 0.1.0", "vmail-lib 0.1.0", ] @@ -627,6 +641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum derive-error-chain 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3c9ca9ade651388daad7c993f005d0d20c4f6fe78c1cdc93e95f161c6f5ede4a" "checksum diesel 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "164080ac16a4d1d80a50f0a623e4ddef41cb2779eee85bcc76907d340dfc98cc" "checksum diesel-derive-enum 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "adbaf5e1344edeedc4d1cead2dc3ce6fc4115bb26b3704c533b92717940f2fb5" diff --git a/vmail-cli/Cargo.toml b/vmail-cli/Cargo.toml index bc78e59..869caa5 100644 --- a/vmail-cli/Cargo.toml +++ b/vmail-cli/Cargo.toml @@ -9,5 +9,5 @@ rpassword = "2" vmail-lib = { path = "../vmail-lib" } failure = "0.1" failure_derive = "0.1" -sha2 = "0.7" rand = "0.5" +sha-crypt = { path = "../../password-hashing/sha-crypt", features = ["include_simple"] } diff --git a/vmail-cli/src/cli.rs b/vmail-cli/src/cli.rs index 684f189..812e5d1 100644 --- a/vmail-cli/src/cli.rs +++ b/vmail-cli/src/cli.rs @@ -60,33 +60,51 @@ pub fn build_cli() -> App<'static, 'static> { .help("Userwhich should be removed")) .arg(Arg::with_name("DOMAIN") .required(true) - .help("Domain for the user, e.g. mydomain.tld")) + .help("Domain for the user, e.g. mydomain.tld"))) .subcommand(SubCommand::with_name("password") .about("Change the password for given user") .alias("pw") - .arg(Arg::with_name("USER"))) + .arg(Arg::with_name("USER") + .help("The user name which should be edited") + .required(true)) + .arg(Arg::with_name("DOMAIN") + .required(true) + .help("Domain for the user, e.g. mydomain.tld"))) .subcommand(SubCommand::with_name("edit") .about("Edit a user account entry") .alias("change") + .alias("update") .arg(Arg::with_name("USER") .help("The user name which should be edited") .required(true)) .arg(Arg::with_name("DOMAIN") .required(true) .help("Domain for the user, e.g. mydomain.tld")) - .arg(Arg::with_name("disabled") - .long("disabled") + .arg(Arg::with_name("disable") + .long("disable") .short("d") - .help("Set user to disabled")) + .conflicts_with("enable") + .help("Disable given user")) + .arg(Arg::with_name("enable") + .long("enable") + .short("e") + .conflicts_with("disable") + .help("Enable given user")) .arg(Arg::with_name("sendonly") .long("send-only") .short("s") - .help("Allow the user only to send email")) + .conflicts_with("sendreceive") + .help("Allow user only to send")) + .arg(Arg::with_name("sendreceive") + .long("send-receive") + .short("r") + .conflicts_with("sendonly") + .help("Allow user to send and receive")) .arg(Arg::with_name("quota") .value_name("quota") .long("quota") .short("q") - .help("Quota for user account in MB (Megabyte), 0 is unlimited"))))) + .help("Quota for user account in MB (Megabyte), 0 is unlimited")))) .subcommand(SubCommand::with_name("domain") .about("Manage domains for a vmail database") .setting(AppSettings::SubcommandRequiredElseHelp) diff --git a/vmail-cli/src/cmd/user.rs b/vmail-cli/src/cmd/user.rs index 3583568..ed32474 100644 --- a/vmail-cli/src/cmd/user.rs +++ b/vmail-cli/src/cmd/user.rs @@ -12,10 +12,14 @@ use utils; fn query_for_password() -> Option { let pass = rpassword::prompt_password_stdout("Password: ").unwrap(); + if pass.is_empty() { + eprintln!("Sorry, empty passwords are not allowed!"); + return None; + } let pass_confirm = rpassword::prompt_password_stdout("Confirm Password: ").unwrap(); if pass != pass_confirm { - eprintln!("Sorry, passwords do not match unable to proceed."); + eprintln!("Sorry, passwords do not match, unable to proceed!"); return None; } @@ -49,7 +53,7 @@ fn add(matches: &ArgMatches) -> Result<()> { let enabled = !matches.is_present("disabled"); let sendonly = matches.is_present("sendonly"); let username = matches.value_of("USER").unwrap(); - let domain = matches.value_of("DOMAIN").unwrap_or(""); + let domain = matches.value_of("DOMAIN").unwrap(); let quota = value_t!(matches.value_of("quota"), i32).unwrap_or_else(|e| { eprintln!("Argument 'quota' has to be >= 0"); e.exit() @@ -66,8 +70,7 @@ fn add(matches: &ArgMatches) -> Result<()> { } let pass = query_for_password().unwrap_or_else(|| process::exit(1)); - - let pass = hash(PasswordScheme::SHA512_CRYPT, pass).unwrap(); + let pass = hash(PasswordScheme::Sha512Crypt, pass).unwrap(); let a = NewAccount { username: username, @@ -80,7 +83,6 @@ fn add(matches: &ArgMatches) -> Result<()> { Account::create(&conn, a)?; - // TODO add user if it doesn't exists println!("User account '{}@{}' has been added!", username, domain); Ok(()) @@ -129,17 +131,56 @@ fn remove(matches: &ArgMatches) -> Result<()> { fn password(matches: &ArgMatches) -> Result<()> { let username = matches.value_of("USER").unwrap(); - println!("Set password for '{}'", username); + let domain = matches.value_of("DOMAIN").unwrap(); + + let conn = vmail_lib::establish_connection(); + let mut acc = Account::get(&conn, username, domain)?; + + let user = format!("{}@{}", username, domain); + println!("Set password for '{}'", user); let pass = query_for_password().unwrap_or_else(|| process::exit(1)); + let pass = hash(PasswordScheme::Sha512Crypt, pass).unwrap(); + + acc.password = pass; + Account::save(&conn, &acc)?; - println!("Password for user account '{}' has been changed!", username); + println!("Password has been changed!"); Ok(()) } fn edit(matches: &ArgMatches) -> Result<()> { let username = matches.value_of("USER").unwrap(); + let domain = matches.value_of("DOMAIN").unwrap(); + + let conn = vmail_lib::establish_connection(); + let mut acc = Account::get(&conn, username, domain)?; + + if matches.is_present("enable") { + acc.enabled = Some(true); + } + if matches.is_present("disable") { + acc.enabled = Some(false); + } + if matches.is_present("sendonly") { + acc.sendonly = Some(true); + } + if matches.is_present("sendreceive") { + acc.sendonly = Some(false); + } - Err(format_err!("Edit not implemented, sorry!")) + if matches.is_present("quota") { + let quota = value_t!(matches.value_of("quota"), i32).unwrap_or_else(|e| { + eprintln!("Argument 'quota' has to be >= 0"); + e.exit() + }); + acc.quota = Some(quota); + } + + Account::save(&conn, &acc)?; + + println!("Password has been updated!"); + + Ok(()) } pub fn dispatch(matches: &ArgMatches) -> Result<()> { diff --git a/vmail-cli/src/crypt.rs b/vmail-cli/src/crypt.rs index bdc8b6c..47eb1fe 100644 --- a/vmail-cli/src/crypt.rs +++ b/vmail-cli/src/crypt.rs @@ -1,44 +1,19 @@ -use rand::{OsRng, RngCore}; -use sha2::{Digest, Sha512}; -use std::io; +use sha_crypt::{errors::CryptError, sha512_simple, Sha512Params}; +use std::result::Result; pub enum PasswordScheme { - SHA512_CRYPT, + Sha512Crypt, + // TODO add some more and let the user decide, for now SHA512_CRYPT should + // be alright } -pub fn hash(scheme: PasswordScheme, value: String) -> io::Result { - let mut hasher = match scheme { - SHA512_CRYPT => Sha512::default(), - }; - - let mut rng = OsRng::new()?; - let mut salt = [0u8; 16]; - rng.try_fill_bytes(&mut salt)?; - - hasher.input(value.as_bytes()); - - let out = format!("{:?}", hasher.result().as_slice()); - //let out = format!("out: {:?}", out); - - let out = format!("{{SHA512_CRYPT}}{}", out); - - Ok(out) -} - -#[cfg(test)] -mod test { - use super::{hash, PasswordScheme}; - - #[test] - fn test_sha512() { - // {SHA512-CRYPT}$6$KSlmgPC2Vch4xRCg$y05xTawj81BeVY0a8P00qxVwBP9kCAxaXiyGg02Aj2fdgQS24dhCT07Ud0/z5Aotzqodc6hzBUJkOIskvqHFr1 - println!( - "{}", - hash(PasswordScheme::SHA512_CRYPT, "foobar".to_string()).unwrap() - ); - //assert_eq!( - //None, - //hash(PasswordScheme::SHA512_CRYPT, "foobar".to_string()) - //); +pub fn hash(scheme: PasswordScheme, value: String) -> Result { + match scheme { + PasswordScheme::Sha512Crypt => { + let params = Sha512Params::default(); + let mut pass = "{SHA512-CRYPT}".to_string(); + pass += &sha512_simple(&value, ¶ms)?; + Ok(pass) + } } } diff --git a/vmail-cli/src/main.rs b/vmail-cli/src/main.rs index 4dba407..66f8947 100644 --- a/vmail-cli/src/main.rs +++ b/vmail-cli/src/main.rs @@ -7,7 +7,7 @@ extern crate failure; #[macro_use] extern crate failure_derive; extern crate rand; -extern crate sha2; +extern crate sha_crypt; use std::process; diff --git a/vmail-lib/src/account.rs b/vmail-lib/src/account.rs index 863c332..36b1d87 100644 --- a/vmail-lib/src/account.rs +++ b/vmail-lib/src/account.rs @@ -7,7 +7,7 @@ use std::fmt; use result::{Result, VmailError}; -#[derive(Queryable, PartialEq, Debug)] +#[derive(Identifiable, AsChangeset, Queryable, PartialEq, Debug)] pub struct Account { pub id: i32, pub username: String, @@ -147,4 +147,9 @@ impl Account { false } } + + pub fn save(conn: &MysqlConnection, account: &Account) -> Result { + let n = diesel::update(account).set(account).execute(conn)?; + Ok(n) + } }