use base64::prelude::*;
use hmac::{Hmac, Mac};
use pbkdf2::pbkdf2_hmac;
use pwhash::*;
use rand::distributions::Alphanumeric;
use rand::thread_rng;
use rand::Rng;
use sha1::{Digest, Sha1};
use sha2::Sha256;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
pub struct Config {
pub number: u32,
pub len: u32,
pub pw_type: Option<u32>,
pub word_list: Option<String>,
pub words: Option<Vec<String>>,
pub username: Option<String>,
pub database: Option<String>,
pub create_database: bool,
pub spaces: bool,
pub digest: Option<String>,
pub servername: Option<String>,
}
impl Config {
pub fn new() -> Config {
Config {
len: 15,
pw_type: None,
number: 20,
word_list: None,
words: None,
username: None,
database: None,
create_database: false,
spaces: true,
digest: None,
servername: None,
}
}
pub fn digest(&self) -> String {
if self.digest.is_none() {
return "sha256".to_string();
}
self.digest.as_ref().unwrap().to_string()
}
}
impl Default for Config {
fn default() -> Self {
Self::new()
}
}
pub enum PwClass {
Num = 1 << 0,
Alpha = 1 << 1,
Ext = 1 << 2,
Lower = 1 << 3,
Upper = 1 << 4,
}
pub fn valid_word(s: &str, word_set: Option<u32>) -> bool {
if word_set.is_some() {
let mut valid = false;
if word_set.as_ref().unwrap() & PwClass::Num as u32 != 0 {
for j in s.chars() {
if j.is_ascii_digit() {
valid = true;
}
}
}
if word_set.as_ref().unwrap() & PwClass::Lower as u32 != 0 {
for j in s.chars() {
if j.is_ascii_lowercase() {
valid = true;
}
}
}
if word_set.as_ref().unwrap() & PwClass::Upper as u32 != 0 {
for j in s.chars() {
if j.is_ascii_uppercase() {
valid = true;
}
}
}
if word_set.as_ref().unwrap() & PwClass::Ext as u32 != 0 {
for j in s.chars() {
if !j.is_ascii_digit() && !j.is_ascii_lowercase() && !j.is_ascii_uppercase() {
valid = true;
}
}
}
return valid;
};
true
}
pub fn prng_string(c: &mut Config) -> String {
let mut set = c.pw_type;
if set.is_none() {
set = Some(
PwClass::Num as u32
| PwClass::Alpha as u32
| PwClass::Lower as u32
| PwClass::Upper as u32,
);
}
let mut rng = rand::thread_rng();
if c.word_list.is_some() {
let word_list = c.word_list.as_ref().unwrap();
if c.words.is_none() {
let f = File::open(word_list);
if f.is_err() {
eprintln!("Cannot open {}: {}", word_list, f.err().unwrap());
std::process::exit(1);
}
let f = f.unwrap();
let mut line = String::new();
let mut br = BufReader::new(f);
let mut wv: Vec<String> = vec![];
loop {
line.clear();
let l = br.read_line(&mut line);
match l {
Ok(i) => {
if i == 0 {
break;
}
}
Err(_) => {
break;
}
}
if line.find('\'').is_some() {
continue;
}
let s = line.clone().trim().to_string();
if s.is_empty() {
continue;
}
if !valid_word(&s, c.pw_type) {
continue;
}
wv.push(s);
}
c.words = Some(wv);
}
let mut phrase = vec![];
let words_len = c.words.as_ref().unwrap().len();
if c.words.as_ref().unwrap().is_empty() {
eprintln!("no words to process");
std::process::exit(1);
}
for _j in 0..c.len {
phrase.push(
c.words.as_ref().unwrap()[rng.gen_range(0..words_len)]
.clone()
.to_string(),
);
}
let mut phrase = phrase.join(if c.spaces { " " } else { "" });
if c.pw_type.is_some() {
let set = c.pw_type.as_ref().unwrap();
if set & PwClass::Lower as u32 != 0 {
phrase = phrase.to_lowercase();
}
if set & PwClass::Upper as u32 != 0 {
phrase = phrase.to_uppercase();
}
}
return phrase;
}
let set = set.unwrap();
let mut chars = "".to_string();
if set & PwClass::Num as u32 != 0 {
chars += "0123456789";
};
if set & PwClass::Alpha as u32 != 0 {
chars += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
};
if set & PwClass::Lower as u32 != 0 {
chars += "abcdefghijklmnopqrstuvwxyz";
};
if set & PwClass::Upper as u32 != 0 {
chars += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
};
if set & PwClass::Ext as u32 != 0 {
chars += r#"!"$%^&*()-={}[]:;@'<>,./\|"#;
};
let one_char = || chars.chars().nth(rng.gen_range(0..chars.len())).unwrap();
std::iter::repeat_with(one_char)
.take(c.len as usize)
.collect()
}
pub fn gen_salt_str(chars: usize) -> String {
let rng = thread_rng();
rng.sample_iter(&Alphanumeric)
.take(chars)
.map(|x| x as char)
.collect()
}
pub fn postgres_pass(pw: &str) -> String {
let salt_size = 16;
let iterations = 4096;
let salt = gen_salt_str(salt_size);
let mut key = [0u8; 32];
pbkdf2_hmac::<Sha256>(pw.as_bytes(), salt.as_bytes(), iterations, &mut key);
let mut client_key = Hmac::<Sha256>::new_from_slice(&key).unwrap();
client_key.update(b"Client Key");
let client_result = client_key.finalize();
let mut server_key = Hmac::<Sha256>::new_from_slice(&key).unwrap();
server_key.update(b"Server Key");
let server_result = server_key.finalize();
let mut stored_key = Sha256::new();
stored_key.update(client_result.clone().into_bytes());
let stored_key = stored_key.finalize();
let bstored_key = BASE64_STANDARD.encode(stored_key);
let bsalt = BASE64_STANDARD.encode(salt);
let bserver_key = BASE64_STANDARD.encode(server_result.clone().into_bytes());
format!("SCRAM-SHA-256${iterations}:{bsalt}${bstored_key}:{bserver_key}")
}
pub fn mysql_pass(pw: &str) -> String {
let mut h = Sha1::new();
h.update(pw);
let r = h.finalize();
let mut h = Sha1::new();
h.update(r);
let r = h.finalize();
format!("*{:x}", r).to_string()
}
pub fn to64(mut v: i32, n: i32) -> String {
let mut index64: Vec<u8> = vec![];
index64.push(b'.');
index64.push(b'/');
for i in 48..58 {
index64.push(i);
}
for i in 65..91 {
index64.push(i);
}
for i in 97..123 {
index64.push(i);
}
let mut result = "".to_string(); let mut nn = n;
while nn > 0 {
nn -= 1;
result.push(index64[v as usize & 0x3f] as char);
v >>= 6;
}
String::from_utf8_lossy(result.as_bytes()).to_string()
}
pub fn mysql_caching_sha2_pass_salt(pw: &str, salt: &str, iterations: i32) -> String {
let num_bytes = 32;
let key = pw.as_bytes();
let salt = salt.as_bytes();
let mut d1: Vec<u8> = vec![];
d1.extend(key);
d1.extend(salt);
d1.extend(key);
let mut stored_key = Sha256::new();
stored_key.update(d1);
let digest_b = stored_key.clone().finalize();
let mut tmp: Vec<u8> = vec![];
tmp.extend(key);
tmp.extend(salt);
for i in (0..=key.len()).rev().step_by(num_bytes) {
tmp.extend(if i > num_bytes {
digest_b.as_slice()
} else {
&digest_b[0..i]
});
}
let mut i = key.len();
while i > 0 {
tmp.extend(if i & 1 != 0 {
digest_b.as_slice()
} else {
key as &[u8]
});
i >>= 1;
}
let mut digest_a_hash = Sha256::new();
digest_a_hash.update(tmp);
let digest_a = digest_a_hash.clone().finalize();
let mut tmp: Vec<u8> = vec![];
for _ in 0..key.len() {
tmp.extend(key);
}
let mut digest_dp_hash = Sha256::new();
digest_dp_hash.update(tmp);
let digest_dp = digest_dp_hash.finalize();
let mut byte_sequence_p: Vec<u8> = vec![];
for i in (0..=key.len()).rev().step_by(num_bytes) {
byte_sequence_p.extend(if i > num_bytes {
digest_dp.as_slice()
} else {
&digest_dp[0..i]
});
}
let mut tmp: Vec<u8> = vec![];
let til = 16 + (digest_a[0] as i32);
for _ in 0..til {
tmp.extend(salt);
}
let mut digest_ds_hash = Sha256::new();
digest_ds_hash.update(tmp);
let digest_ds = digest_ds_hash.finalize();
let mut byte_sequence_s: Vec<u8> = vec![];
for i in (0..=salt.len()).rev().step_by(num_bytes) {
byte_sequence_s.extend(if i > num_bytes {
digest_ds.as_slice()
} else {
&digest_ds[0..i]
});
}
let mut digest_c = digest_a;
for i in 0..iterations * 1000 {
tmp = if i & 1 > 0 {
byte_sequence_p.clone()
} else {
(&digest_c as &[u8]).to_vec()
};
if i % 3 > 0 {
tmp.extend(byte_sequence_s.clone());
}
if i % 7 > 0 {
tmp.extend(byte_sequence_p.clone());
}
tmp.extend(if i & 1 > 0 {
digest_c.as_slice()
} else {
&byte_sequence_p as &[u8]
});
let mut digest_c_hash = Sha256::new();
digest_c_hash.update(tmp);
digest_c = digest_c_hash.finalize();
}
let inc1 = 10;
let inc2 = 21;
let m = 30;
let end = 0;
let mut i = 0;
let mut tmp = "".to_string();
loop {
tmp.push_str(&to64(
((digest_c[i] as i32) << 16)
| (((digest_c[(i + inc1) % m]) as i32) << 8)
| (digest_c[(i + inc1 * 2) % m]) as i32,
4,
));
i = (i + inc2) % m;
if i == end {
break;
}
}
tmp.push_str(&to64(
((digest_c[31] as i32) << 8) | (digest_c[30] as i32),
3,
));
tmp
}
pub fn mysql_caching_sha2_pass(pw: &str) -> String {
let salt_size = 20;
let salt = gen_salt_str(salt_size);
let count = 5;
let st = mysql_caching_sha2_pass_salt(pw, &salt, count);
format!(
"0x{}",
hex::encode(format!("$A${count:>03}${salt}{st}")).to_uppercase()
)
}
pub fn useradd_format(c: &Config) -> String {
let digest = c.digest();
format!("useradd -m -s /bin/bash -p '%{{{digest}}}' %{{username}}").to_string()
}
pub fn usermod_format(c: &Config) -> String {
let digest = c.digest();
format!("usermod -p '%{{{digest}}}' %{{username}}").to_string()
}
pub fn mysql_format() -> String {
"grant all privileges on %{database}.* to %{username}@'%' identified with mysql_native_password as '%{mysql}';".to_string()
}
pub fn mysql_user_format() -> String {
"create user %{username}@'%'; alter user %{username}@'%' identified with 'caching_sha2_password' as %{mysql_caching_sha2}; grant all privileges on %{database}.* to %{username}@'%';".to_string()
}
pub fn postgres_format() -> String {
"create user %{username} password '%{postgres}';".to_string()
}
pub fn htauth_format(c: &Config) -> String {
let digest = if c.digest.is_none() {
"md5".to_string()
} else {
c.digest()
};
format!("%{{username}}:%{{{digest}}}").to_string()
}
#[allow(deprecated)]
pub fn process_format_string(format_string: &mut String, c: &Config, pw: &str) -> String {
if format_string.contains("%{userfmt}") {
*format_string = format_string.replace("%{userfmt}", &useradd_format(c).to_string());
}
if format_string.contains("%{usermodfmt}") {
*format_string = format_string.replace("%{usermodfmt}", &usermod_format(c).to_string());
}
if format_string.contains("%{mysqlfmt}") {
*format_string = format_string.replace("%{mysqlfmt}", &mysql_format().to_string());
}
if format_string.contains("%{mysqluserfmt}") {
*format_string = format_string.replace("%{mysqluserfmt}", &mysql_user_format().to_string());
}
if format_string.contains("%{pgfmt}") {
*format_string = format_string.replace("%{pgfmt}", &postgres_format().to_string());
}
if format_string.contains("%{htauthfmt}") {
*format_string = format_string.replace("%{htauthfmt}", &htauth_format(c).to_string());
}
if format_string.contains("%{md5}") {
*format_string = format_string.replace("%{md5}", &md5_crypt::hash(pw).unwrap().to_string());
}
if format_string.contains("%{bcrypt}") {
*format_string = format_string.replace("%{bcrypt}", &bcrypt::hash(pw).unwrap().to_string());
}
if format_string.contains("%{des}") {
*format_string =
format_string.replace("%{des}", &unix_crypt::hash(pw).unwrap().to_string());
}
if format_string.contains("%{sha1}") {
*format_string =
format_string.replace("%{sha1}", &sha1_crypt::hash(pw).unwrap().to_string());
}
if format_string.contains("%{sha256}") {
*format_string =
format_string.replace("%{sha256}", &sha256_crypt::hash(pw).unwrap().to_string());
}
if format_string.contains("%{sha512}") {
*format_string =
format_string.replace("%{sha512}", &sha512_crypt::hash(pw).unwrap().to_string());
}
if format_string.contains("%{password}") {
*format_string = format_string.replace("%{password}", pw);
}
if format_string.contains("%{username}") && c.username.is_some() {
*format_string = format_string.replace("%{username}", c.username.as_ref().unwrap());
}
if format_string.contains("%{database}") && c.database.is_some() {
*format_string = format_string.replace("%{database}", c.database.as_ref().unwrap());
}
if format_string.contains("%{mysql}") {
*format_string = format_string.replace("%{mysql}", &mysql_pass(pw).to_string());
}
if format_string.contains("%{mysql_caching_sha2}") {
*format_string = format_string.replace(
"%{mysql_caching_sha2}",
&mysql_caching_sha2_pass(pw).to_string(),
);
}
if format_string.contains("%{postgres}") {
*format_string = format_string.replace("%{postgres}", &postgres_pass(pw));
}
if format_string.contains("%{username}") && c.username.is_some() {
*format_string = format_string.replace("%{username}", c.username.as_ref().unwrap());
}
if format_string.contains("%{database}") && c.database.is_some() {
*format_string = format_string.replace("%{database}", c.database.as_ref().unwrap());
}
if format_string.contains("%{mysql}") {
*format_string = format_string.replace("%{mysql}", &mysql_pass(pw));
}
if format_string.contains("%{servername}") && c.servername.is_some() {
*format_string = format_string.replace("%{servername}", c.servername.as_ref().unwrap());
}
*format_string = format_string.replace(r#"\n"#, "\n");
format_string.to_string()
}
pub fn create_pg_database(c: &Config) -> String {
if c.create_database {
let db = c.database.as_ref().unwrap_or(&"".to_string()).to_string();
let username = c.username.as_ref().unwrap_or(&"".to_string()).to_string();
return format!("create database {db}; alter database {db} owner to {username}; ")
.to_string();
}
"".to_string()
}
pub fn create_mysql_database(c: &Config) -> String {
if c.create_database {
return format!(
"create database {}; ",
c.database.as_ref().unwrap_or(&"".to_string())
)
.to_string();
}
"".to_string()
}
pub fn create_nginx_vhost() -> String {
let vhost = r#"
server {
listen 80;
listen [::]:80;
server_name %{servername} www.%{servername};
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
server_name %{servername} www.%{servername};
access_log /var/log/nginx/%{servername}_access.log;
error_log /var/log/nginx/%{servername}_error.log;
include /etc/nginx/snippets/snakeoil.conf;
location / {
}
}
"#;
vhost.to_string()
}
pub fn create_apache_vhost() -> String {
let vhost = r#"
<VirtualHost *:80>
ServerName %{servername}
ServerAlias www.%{servername}
DocumentRoot /home/%{username}/public_html
ErrorLog ${APACHE_LOG_DIR}/%{servername}_error.log
CustomLog ${APACHE_LOG_DIR}/%{servername}_access.log combined
<Directory "/home/%{username}/public_html">
Require all granted
AllowOverride All
</Directory>
</VirtualHost>
"#;
vhost.to_string()
}