diff --git a/Cargo.lock b/Cargo.lock index d135573..7a40914 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,17 +1,35 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "ascii_utils" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "base64" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" +dependencies = [ + "byteorder", + "safemem", +] + [[package]] name = "base64" version = "0.10.1" @@ -57,6 +75,28 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + [[package]] name = "core-foundation" version = "0.9.1" @@ -73,6 +113,21 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +[[package]] +name = "email" +version = "0.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91549a51bb0241165f13d57fc4c72cef063b4088fb078b019ecbf464a45f22e4" +dependencies = [ + "base64 0.9.3", + "chrono", + "encoding", + "lazy_static", + "rand 0.4.6", + "time", + "version_check", +] + [[package]] name = "encoding" version = "0.2.33" @@ -161,6 +216,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "futures" version = "0.3.14" @@ -313,7 +374,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86ed8677138975b573ab4949c35613931a4addeadd0a8a6aa0327e2a979660de" dependencies = [ - "base64", + "base64 0.10.1", "bufstream", "fast_chemail", "hostname", @@ -325,6 +386,20 @@ dependencies = [ "serde_json", ] +[[package]] +name = "lettre_email" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd02480f8dcf48798e62113974d6ccca2129a51d241fa20f1ea349c8a42559d5" +dependencies = [ + "base64 0.10.1", + "email", + "lettre", + "mime", + "time", + "uuid", +] + [[package]] name = "libc" version = "0.2.93" @@ -348,15 +423,22 @@ checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "mention_2_mail" -version = "0.1.0" +version = "0.2.0" dependencies = [ "irc-proto", "irc_stream", "lettre", + "lettre_email", "native-tls", "toml", ] +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "native-tls" version = "0.2.7" @@ -385,6 +467,25 @@ dependencies = [ "version_check", ] +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg 1.0.1", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg 1.0.1", +] + [[package]] name = "once_cell" version = "1.7.2" @@ -417,7 +518,7 @@ version = "0.9.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "313752393519e876837e09e1fa183ddef0be7735868dced3196f4472d536277f" dependencies = [ - "autocfg", + "autocfg 1.0.1", "cc", "libc", "pkg-config", @@ -478,6 +579,38 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.7", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc 0.1.0", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi", +] + [[package]] name = "rand" version = "0.8.3" @@ -485,9 +618,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ "libc", - "rand_chacha", - "rand_core", - "rand_hc", + "rand_chacha 0.3.0", + "rand_core 0.6.2", + "rand_hc 0.3.0", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.3.1", ] [[package]] @@ -497,9 +640,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.2", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.6.2" @@ -509,13 +667,84 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "rand_hc" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" dependencies = [ - "rand_core", + "rand_core 0.6.2", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", ] [[package]] @@ -542,6 +771,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + [[package]] name = "schannel" version = "0.1.19" @@ -628,7 +863,7 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if", "libc", - "rand", + "rand 0.8.3", "redox_syscall", "remove_dir_all", "winapi", @@ -654,13 +889,23 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "tokio" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" dependencies = [ - "autocfg", + "autocfg 1.0.1", "pin-project-lite", ] @@ -693,6 +938,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "uuid" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" +dependencies = [ + "rand 0.6.5", +] + [[package]] name = "vcpkg" version = "0.2.12" diff --git a/Cargo.toml b/Cargo.toml index 74a8f5d..3ffb5b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ edition = "2018" irc-proto = "0.15" irc_stream = { path = "irc-stream", version = "0.1" } lettre = "0.9" +lettre_email = "0.9" native-tls = "0.2" toml = "0.5" diff --git a/src/config.rs b/src/config.rs index 187fb9f..fc036a4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -72,6 +72,9 @@ fn parse_server_dir(mut config: Value, server_config_path: PathBuf) } else { config.insert("channels".to_owned(), channels); } + + let channel_config = parse_toml_to_table(entry.path())?; + config = merge_config_maps(config, channel_config)?; }, _ => (), } diff --git a/src/main.rs b/src/main.rs index 729b0d8..6f334ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,9 +27,15 @@ use std::{ thread, }; use lettre::{ - SendmailTransport, - Transport + Transport, + SmtpClient, + smtp::authentication::{ + Credentials, + Mechanism, + }, }; +use lettre_email::EmailBuilder; + fn main() { let mut server_conf_flag = false; @@ -57,53 +63,81 @@ fn main() { None => config = config::get_main_config(PathBuf::from("/etc/Mention2Mail/config.toml")) .expect("Could not get default config in /etc/Mention2Mail/default.toml"), } + + let mail_config = &config.get("email").expect("No email config found."); let server_configs; match server_conf_path { - Some(p) => server_configs = config::get_server_configs(config, PathBuf::from(p)) + Some(p) => server_configs = config::get_server_configs(config.clone(), PathBuf::from(p)) .expect("Could not get server config."), - None => server_configs = vec![config], + None => server_configs = vec![config.clone()], } - - let (tx,rx) = channel(); + + // TODO: This line has many problems… + let (tx,rx): (std::sync::mpsc::Sender<(String, String, String, Vec>)>, std::sync::mpsc::Receiver<(String, String, String, Vec>)>)= channel(); + let mut server_threads = vec![]; for s_conf in server_configs { let t = tx.clone(); + + let addr = s_conf.get("server").unwrap().as_str() + .ok_or("Could not get server adress from config").unwrap(); server_threads.push(thread::Builder::new() - .name("server name here".to_string()) + .name(addr.to_string()) .spawn(move || { - servers::handle_server(s_conf, t).unwrap(); + servers::handle_server(s_conf, t, 5).unwrap(); }) ); } - - let mut mailer = SendmailTransport::new(); + + let smtp_address = mail_config.get("address") + .expect("No SMTP server address found.") + .as_str() + .unwrap(); + let smpt_user = mail_config.get("user") + .expect("No SMTP user name found.") + .as_str() + .unwrap(); + let smtp_pass = mail_config.get("pass") + .expect("No SMTP password found.") + .as_str() + .unwrap(); + + let mail_client = SmtpClient::new_simple(smtp_address).unwrap(); + let mail_client = mail_client.credentials( + Credentials::new(smpt_user.to_owned(), smtp_pass.to_owned()) + ); + let mail_client = mail_client.authentication_mechanism(Mechanism::Plain); + let mut mailer = mail_client.transport(); loop { match rx.recv() { - Ok(data) => { - println!("Sending mail to: {}", data[0]); - let id = "notarealiid@mention2mail".to_owned(); - let body = format!("Subject: You were mentioned in {} on {}\n{} wrote: {}", - data[2], data[1], data[3], data[4]).into_bytes(); - let mail = lettre::SendableEmail::new( - lettre::Envelope::new( - Some( - lettre::EmailAddress::new("m2m@chaostreff-alzey.de".to_owned()) - .unwrap() - ), - vec![ - lettre::EmailAddress::new(data[0].clone()).unwrap()] - ).unwrap(), - id, - body - ); + Ok((email_address, server, channel, context)) => { + println!("Sending mail to: {}", email_address); - match mailer.send(mail) { + let mut body = "".to_owned(); + for opt in context.iter() { + match opt{ + Some([sender, msg]) => { + body.push_str(&format!("{} wrote: {}\n", sender, msg)) + }, + None => (), + } + } + + let mail = EmailBuilder::new() + .to((email_address.clone(), "Someone")) + .from("m2m@chaostreff-alzey.de") + .subject(format!("You were mentioned in {} on {}", channel, server)) + .text(body) + .build() + .unwrap(); + + match mailer.send(mail.into()) { Ok(_) => println!("Email sent successfully!"), - Err(e) => panic!("Could not send email: {:?}", e), + Err(e) => println!("Could not send email: {:?}", e), } } Err(_e) => (), diff --git a/src/servers.rs b/src/servers.rs index 47fe91d..7e3f580 100644 --- a/src/servers.rs +++ b/src/servers.rs @@ -17,6 +17,7 @@ use std::{ collections::VecDeque, + collections::HashMap, sync::mpsc::Sender, }; use irc_proto::command::{ @@ -30,6 +31,55 @@ use toml::map::{ use irc_proto::message::Message; use toml::value::Value; +/// A static implementation of a ringbuffer. +/// It holds arrays of three strings each, +/// which are meant to represent the author and message respectively. +pub struct MessageRingBuf { + buf: Vec>, + ptr: usize, +} + +impl MessageRingBuf { + pub fn new(size: usize) -> Self { + + Self{ + buf: vec!(None; size), + ptr: 0 + } + } + + /// Adds a message to the end of the buffer, + /// overwriting the first value if the buffer is full. + pub fn push(&mut self, val: T) { + self.ptr = (self.ptr + 1) % self.buf.len(); + self.buf[self.ptr] = Some(val); + } + + /// Returns the last element of the buffer, + /// replacing it with none. + pub fn pop(&mut self) -> Option { + let l = self.buf.len(); + self.ptr = (self.ptr + l - 1) % l; + self.buf[self.ptr].take() + } + + /// Returns the contained buffer in the correct order. + pub fn get_queue(& self) -> Vec> { + let l = self.buf.len(); + let mut queue = self.buf.clone(); + + for i in self.ptr + 1 .. l { + queue[i - self.ptr - 1] = self.buf[i].clone(); + } + + for i in 0 .. self.ptr + 1{ + queue[l - self.ptr - 1 + i] = self.buf[i].clone(); + } + + return queue + } +} + /// Constructs a VecDeque with the messages /// required to identify with an IRC server. /// Uses the credentials set in -`config.toml`. @@ -82,30 +132,32 @@ fn get_irc_identify_messages (config: &Value) /// Appends a given VecDeque to include the messages /// used to join the channels defined in `config.toml`. -fn get_irc_join_messages(config: &Value, queue: VecDeque) +fn get_irc_join_messages(channels: &Vec, queue: VecDeque) -> Result, Box> { let mut queue = queue; - match config.get("channels") { - Some(c) => { - for channel in c.as_array().ok_or("Could not parse channels.")? { - queue.push_back(Message::from( - Command::JOIN( - String::from(channel.as_str().ok_or("Could not parse one of the channels")?), - None, - None - ) - )) - } - }, - None => () + for channel in channels { + queue.push_back(Message::from( + Command::JOIN( + String::from(channel.as_str().ok_or("Could not parse one of the channels")?), + None, + None + ) + )) } Ok(queue) } -fn handle_message(message: Message, nick: &String, client_names: Keys) - -> (Option, Option<[String; 4]>) { +/// Checks messages for mentions of users or commands in private messages. +/// Returns either one of: +/// - None and None in the case no mention or command is recognized +/// - The appropriate answer as Message and None for a command +/// - None and the mentioned user and channel as String and the context as a MessageRingBuf for mentions. +fn handle_message (message: Message, nick: &String, + client_names: Keys, contexts: &mut HashMap>) + -> (Option, Option<(String, String, Vec>)>) { + let sender = match message.clone().source_nickname() { Some(s) => String::from(s), None => String::from("anonymous") @@ -132,14 +184,23 @@ fn handle_message(message: Message, nick: &String, client_names: Keys) ), None ) - } - for c_name in client_names { - if msg.contains(c_name) { - return (None, Some([c_name.clone(), - rec.clone(), - sender.clone(), - msg.clone() - ])) + } else { + match contexts.get_mut(&rec.clone()) { + Some(ref mut buf) => { + buf.push([sender.clone(), msg.clone()]); + for c_name in client_names { + if msg.contains(c_name) { + return (None, Some(( + c_name.clone(), + rec.clone(), + buf.get_queue(), + ))) + } + } + } + None => { + println!("Channel not recognized: {}", rec.clone()); + } } } }, @@ -148,18 +209,36 @@ fn handle_message(message: Message, nick: &String, client_names: Keys) return (None, None) } -pub fn handle_server(config: Value, tx: Sender<[String; 5]>) +/// Connects to a server and indefinitely iterates the messages received. +/// Calls handle_message on each one and pushes +/// email, server, channel as Strings +/// and the context as MessageRingBuf to the given stream. +pub fn handle_server + (config: Value, tx: Sender<(String, String, String, Vec>)>,context_size: usize) -> Result<(), Box> { + let server_name = config.get("server").unwrap().as_str() .ok_or("Could not get server adress from config")?; let nick = config.get("nickname").unwrap().as_str() .ok_or("Could not get nickname from config")?.to_owned(); - + let channels = config.get("channels").unwrap().as_array() + .ok_or("Could not get channels from config")?; + let clients; match config.get("clients").unwrap().as_table() { Some(t) => clients = t.clone(), None => clients = Map::new(), } + + let mut chat_contexts = HashMap::new(); + for channel in channels { + chat_contexts.insert( + channel.as_str() + .ok_or("Could not parse one of the channels")? + .to_owned(), + MessageRingBuf::new(context_size), + ); + } let mut stream = super::connect::connect_irc(&config).unwrap(); let mut message_queue = get_irc_identify_messages(&config).unwrap(); @@ -168,7 +247,7 @@ pub fn handle_server(config: Value, tx: Sender<[String; 5]>) stream.write(message).unwrap(); } - message_queue = get_irc_join_messages(&config, message_queue).unwrap(); + message_queue = get_irc_join_messages(&channels, message_queue).unwrap(); // Wait for first ping and join channels after sending pong. loop { @@ -190,24 +269,28 @@ pub fn handle_server(config: Value, tx: Sender<[String; 5]>) // Handle all incoming messages after joining the channels. loop { let message = stream.read()?; - let (answer, data) = handle_message(message, &nick, clients.keys()); + let (answer, data) = handle_message( + message, + &nick, + clients.keys(), + &mut chat_contexts + ); + match answer { Some(a) => stream.write(a).unwrap(), None => () } match data { - Some(d) => match clients.get(&d[0]) { + Some((client, channel, context)) => match clients.get(&client) { Some(addr) => { - // There must be a better way to do this… - let out = [ + let out = ( addr.as_str() .ok_or("Could not parse email address.")? .to_owned(), server_name.to_owned(), - d[1].clone(), - d[2].clone(), - d[3].clone(), - ]; + channel.clone(), + context.clone(), + ); tx.send(out)? }, None => (),