Skip to content

Commit

Permalink
Finish user subcmd
Browse files Browse the repository at this point in the history
  • Loading branch information
awidegreen committed Oct 6, 2018
1 parent 60eb00f commit c100f40
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 57 deletions.
17 changes: 16 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vmail-cli/Cargo.toml
Expand Up @@ -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"] }
32 changes: 25 additions & 7 deletions vmail-cli/src/cli.rs
Expand Up @@ -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)
Expand Down
57 changes: 49 additions & 8 deletions vmail-cli/src/cmd/user.rs
Expand Up @@ -12,10 +12,14 @@ use utils;

fn query_for_password() -> Option<String> {
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;
}

Expand Down Expand Up @@ -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()
Expand All @@ -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,
Expand All @@ -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(())
Expand Down Expand Up @@ -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<()> {
Expand Down
51 changes: 13 additions & 38 deletions 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<String> {
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<String, CryptError> {
match scheme {
PasswordScheme::Sha512Crypt => {
let params = Sha512Params::default();
let mut pass = "{SHA512-CRYPT}".to_string();
pass += &sha512_simple(&value, &params)?;
Ok(pass)
}
}
}
2 changes: 1 addition & 1 deletion vmail-cli/src/main.rs
Expand Up @@ -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;

Expand Down
7 changes: 6 additions & 1 deletion vmail-lib/src/account.rs
Expand Up @@ -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,
Expand Down Expand Up @@ -147,4 +147,9 @@ impl Account {
false
}
}

pub fn save(conn: &MysqlConnection, account: &Account) -> Result<usize> {
let n = diesel::update(account).set(account).execute(conn)?;
Ok(n)
}
}

0 comments on commit c100f40

Please sign in to comment.