diff --git a/Cargo.lock b/Cargo.lock index ae9f69708ab8130c9352d388dbb3b91435fc5f6f..3980db1ac196879e5f34ce65d1f84d86fd5bfc50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,37 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +[[package]] +name = "aes" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" +dependencies = [ + "aes-soft", + "aesni", + "cipher", +] + +[[package]] +name = "aes-soft" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" +dependencies = [ + "cipher", + "opaque-debug", +] + +[[package]] +name = "aesni" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" +dependencies = [ + "cipher", + "opaque-debug", +] + [[package]] name = "aho-corasick" version = "0.7.15" @@ -75,6 +106,22 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-modes" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0" +dependencies = [ + "block-padding", + "cipher", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "bumpalo" version = "3.6.0" @@ -125,6 +172,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "cipher" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +dependencies = [ + "generic-array", +] + [[package]] name = "command_attr" version = "0.3.3" @@ -1678,8 +1734,10 @@ checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" name = "ucc-discord-bot" version = "0.1.0" dependencies = [ + "aes", "async-trait", "base64 0.13.0", + "block-modes", "chrono", "diesel", "guard", @@ -1688,7 +1746,6 @@ dependencies = [ "lazy_static", "ldap3", "log", - "openssl", "rand 0.8.3", "rayon", "regex", diff --git a/Cargo.toml b/Cargo.toml index a3b8c832606bc37e2f6f1aaa85b85907ccf4251b..430f008f4b52bb57a2527351a7644c8a62189ebc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,10 @@ authors = ["tec <tec@ucc.gu.uwa.edu.au>"] edition = "2018" [dependencies] +aes = "0.6" async-trait = "0.1.42" base64 = "0.13.0" +block-modes = "0.7" chrono = "0.4.19" diesel = { version = "1.4.5", features = ["sqlite"] } guard = "0.5.0" @@ -15,7 +17,6 @@ indexmap = { version = "1.6.1", features = ["serde-1"] } lazy_static = "1.4.0" ldap3 = "0.9.1" log = "0.4.11" -openssl = "0.10.32" rand = "0.8.1" rayon = "1.5.0" regex = "1.4.3" diff --git a/src/token_management.rs b/src/token_management.rs index f6d21f6caef515118fb40c45623721148272ce99..badad22ebde7d1c5908fd2e359d22992e046e3e1 100644 --- a/src/token_management.rs +++ b/src/token_management.rs @@ -1,30 +1,31 @@ +use aes::Aes128; use base64; +use block_modes::block_padding::Pkcs7; +use block_modes::{BlockMode, Cbc}; use chrono::{prelude::Utc, DateTime}; -use openssl::symm::{decrypt, encrypt, Cipher}; use rand::Rng; use serenity::model::user::User; use std::str; +type Aes128Cbc = Cbc<Aes128, Pkcs7>; pub static TOKEN_LIFETIME: i64 = 300; // 5 minutes lazy_static! { static ref KEY: [u8; 32] = rand::thread_rng().gen::<[u8; 32]>(); - static ref CIPHER: Cipher = Cipher::aes_256_cbc(); + static ref IV: [u8; 16] = [0; 16]; + static ref CIPHER: Aes128Cbc = Aes128Cbc::new_var(KEY.as_ref(), IV.as_ref()).unwrap(); } fn text_encrypt(plaintext: &str) -> String { - let iv: &[u8; 16] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - let encrypted_vec = - encrypt(*CIPHER, &*KEY, Some(iv), plaintext.as_bytes()).expect("encryption failed"); - base64::encode(encrypted_vec.as_slice()) + base64::encode(CIPHER.encrypt_vec(plaintext.as_bytes())) } + fn text_decrypt(ciphertext: &str) -> Option<String> { - let iv: &[u8; 16] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; guard!(let Ok(cipher_vec) = base64::decode(ciphertext) else { warn!("Unable to decode base64 text"); return None }); - guard!(let Ok(decrypted_vec) = decrypt(*CIPHER, &*KEY, Some(iv), &cipher_vec) else { + guard!(let Ok(decrypted_vec) = CIPHER.decrypt_vec(&cipher_vec) else { warn!("Text decryption failed"); return None }); @@ -75,7 +76,7 @@ pub fn parse_token(discord_user: &User, encrypted_token: &str) -> Result<String, let token_username = token_components[2]; if token_discord_user != discord_user.id.0.to_string() { warn!("... attempt failed : DiscordID mismatch"); - return Err(TokenError::DiscordIdMismatch) + return Err(TokenError::DiscordIdMismatch); } let time_delta_seconds = Utc::now().timestamp() - token_timestamp.timestamp(); if time_delta_seconds > TOKEN_LIFETIME { @@ -83,7 +84,7 @@ pub fn parse_token(discord_user: &User, encrypted_token: &str) -> Result<String, "... attempt failed : token expired ({} seconds old)", time_delta_seconds ); - return Err(TokenError::TokenExpired) + return Err(TokenError::TokenExpired); } info!( "... verification successful (token {} seconds old)", diff --git a/src/user_management.rs b/src/user_management.rs index d932f0c9e14f09c11510a5c20af9551bc262bae2..ad3ceb0300b9c11e8a4289fe77755740e331db06 100644 --- a/src/user_management.rs +++ b/src/user_management.rs @@ -1,5 +1,3 @@ -use async_trait::async_trait; -use rand::seq::SliceRandom; use regex::Regex; use serenity::{ model::{channel::Message, guild::Member, id::RoleId}, @@ -89,9 +87,8 @@ impl Commands { send_message!( msg.channel_id, &ctx.http, - RANDOM_SASS - .choose(&mut rand::thread_rng()) - .expect("We couldn't get any sass") + RANDOM_SASS[0] //.choose(&mut rand::thread_rng()) + //.expect("We couldn't get any sass") ); return; } @@ -161,7 +158,7 @@ impl Commands { return // Err() }); guard!(let Ok(mut discord_member) = serenity::model::id::GuildId(CONFIG.server_id) - .member(ctx.http.clone(), msg.author.id) else { + .member(ctx.http.clone(), msg.author.id).await else { return // Err() }); @@ -177,7 +174,7 @@ impl Commands { continue; } if discord_member.roles.contains(&RoleId::from(role)) - && discord_member.remove_role(&ctx.http, role).is_err() + && discord_member.remove_role(&ctx.http, role).await.is_err() { return; // Err() } @@ -186,7 +183,10 @@ impl Commands { if !discord_member .roles .contains(&RoleId::from(registered_role)) - && discord_member.add_role(&ctx.http, registered_role).is_err() + && discord_member + .add_role(&ctx.http, registered_role) + .await + .is_err() { return; // Err() } @@ -197,41 +197,46 @@ impl Commands { pub async fn verify(ctx: Context, msg: Message, token: &str) { match parse_token(&msg.author, token) { Ok(name) => { - e!( - "Unable to get member: {:?}", - serenity::model::id::GuildId(CONFIG.server_id) - .member(ctx.http.clone(), msg.author.id) - .map(|mut member| async { - let full_member = database::add_member(&msg.author.id.0, &name); - e!( - "Unable to remove role: {:?}", - member.remove_role(&ctx.http, CONFIG.unregistered_member_role) - ); - guard!(let Some(member_role) = Commands::get_registered_role(name) else { - send_message!(msg.channel_id, ctx.http.clone(), "Couldn't find you in LDAP!"); - return - }); - e!( - "Unable to add role: {:?}", - member.add_role(&ctx.http, member_role) - ); - e!( - "Unable to edit nickname: {:?}", - member.edit(&ctx.http, |m| { - m.nickname(member_nickname(&full_member)); - m - }) - ); - let mut verification_message = MessageBuilder::new(); - verification_message.push(format!("Great, {}! Verification was successful. To provide a friendly introduction to yourself, consider doing ", &full_member.username)); - verification_message.push_mono(format!("{}set bio <info>", CONFIG.command_prefix)); - send_message!( - msg.channel_id, - ctx.http.clone(), - verification_message.build() - ); - }) - ); + if let Ok(mut member) = serenity::model::id::GuildId(CONFIG.server_id) + .member(ctx.http.clone(), msg.author.id) + .await + { + let full_member = database::add_member(&msg.author.id.0, &name); + e!( + "Unable to remove role: {:?}", + member + .remove_role(&ctx.http, CONFIG.unregistered_member_role) + .await + ); + guard!(let Some(member_role) = Commands::get_registered_role(name) else { + send_message!(msg.channel_id, ctx.http.clone(), "Couldn't find you in LDAP!"); + return + }); + e!( + "Unable to add role: {:?}", + member.add_role(&ctx.http, member_role).await + ); + e!( + "Unable to edit nickname: {:?}", + member + .edit(&ctx.http, |m| { + m.nickname(member_nickname(&full_member)); + m + }) + .await + ); + let mut verification_message = MessageBuilder::new(); + verification_message.push(format!("Great, {}! Verification was successful. To provide a friendly introduction to yourself, consider doing ", &full_member.username)); + verification_message + .push_mono(format!("{}set bio <info>", CONFIG.command_prefix)); + send_message!( + msg.channel_id, + ctx.http.clone(), + verification_message.build() + ); + } else { + error!("Unable to get member: {:?}", name) + } } Err(reason) => send_message!( msg.channel_id, @@ -282,15 +287,17 @@ impl Commands { } let member = possible_member.unwrap(); info!("Found matching profile, UCC username: {}", &member.username); + let user = match ctx.http.get_user(member.discord_id as _).await { + Ok(u) => u, + Err(e) => { + error!("Couldn't find matching Discord ID for username!"); + return; + } + }; let result = msg.channel_id.send_message(&ctx.http, |m| { m.embed(|embed| { embed.colour(serenity::utils::Colour::LIGHTER_GREY); - embed.footer(|f| async { - let user = &ctx - .http - .get_user(member.discord_id.clone() as u64) - .await - .expect("We expected this user to exist... they didn't ;("); + embed.footer(|f| { f.text(&user.name); f.icon_url( user.static_avatar_url()