extern crate anyhow; extern crate clokwerk; extern crate lettre; extern crate lettre_email; extern crate log; extern crate native_tls; extern crate serde; extern crate simplelog; extern crate toml; extern crate trust_dns_resolver; extern crate tokio; use self::native_tls::{Protocol, TlsConnector}; use anyhow::{bail, Result}; use clokwerk::{Scheduler, TimeUnits}; use lettre::smtp::authentication::Credentials; use lettre::{ClientSecurity, ClientTlsParameters, SmtpClient, Transport}; use lettre_email::EmailBuilder; use log::{error, info}; use serde::Deserialize; use simplelog::{ConfigBuilder, LevelFilter, WriteLogger}; use std::fs::{self, OpenOptions}; use std::net::{Ipv4Addr, Ipv6Addr}; use std::{thread, time::Duration}; use trust_dns_resolver::{config::*, Name, Resolver}; use query_external_ip::Consensus; use tokio::runtime::{Builder, Runtime}; fn main() -> Result<()> { // Init log let logfile = OpenOptions::new() .append(true) .create(true) .open("log.txt") .unwrap(); WriteLogger::init( LevelFilter::Info, ConfigBuilder::new() .set_time_format_str("%d/%m/%Y %H:%M:%S") .set_time_to_local(true) .build(), logfile, ) .unwrap(); // Init tester info!("Init tester"); let tester = match Tester::new() { Err(e) => { error!("Error {}", e); bail!("Cannot init tester"); } Ok(tester) => tester, }; // Initial check if let Err(e) = tester.check() { error!("Error {}", e); bail!("Cannot get initial IP"); } if let Some(false) = tester.config.test { // Init scheduler info!("Init scheduler"); let mut scheduler = Scheduler::new(); scheduler.every(1.day()).at("06:00").run(move || { if let Err(e) = tester.check() { error!("Error {}", e); } }); // Run loop { scheduler.run_pending(); thread::sleep(Duration::from_secs(60)); } } Ok(()) } struct Tester { config: Config, dns_resolver: Resolver, async_runtime: Runtime } impl Tester { fn new() -> Result { let mut resolver_opt = ResolverOpts::default(); resolver_opt.ip_strategy = LookupIpStrategy::Ipv4AndIpv6; Ok(Tester { config: toml::from_str(&fs::read_to_string("config.toml")?)?, dns_resolver: Resolver::new(ResolverConfig::default(), resolver_opt)?, async_runtime: Builder::new_multi_thread() .enable_all() .build() .unwrap() }) } fn check(&self) -> Result<()> { info!("Check"); match self.get_ip() { Ok((ipv4, ipv6)) => { self.test_domains(ipv4, ipv6)?; } Err(err) => { let msg = format!("Cannot get IP ({})", err); error!("{}", msg); self.send_mail("Error on getting IP".to_owned(), msg)?; } } Ok(()) } fn get_ip(&self) -> Result<(Ipv4Addr, Ipv6Addr)> { let cons = self.async_runtime.block_on(Consensus::get())?; let ipv4 = cons.v4().unwrap(); let ipv6 = cons.v6().unwrap(); info!("Current IPv4 is {} and IPv6 {}", ipv4, ipv6); Ok((ipv4, ipv6)) } fn test_domains(&self, ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Result<()> { for domain in self.config.server.domains.iter() { let address = self.dns_resolver.lookup_ip(domain.clone())?; for dns_ip in address.iter() { if dns_ip.is_ipv4() { if dns_ip != ipv4 { let msg = format!( "Wrong IPV4 for {} (DNS: {}, current: {})", domain, dns_ip, ipv4 ); info!("{}", msg); self.send_mail("Wrong IP".to_owned(), msg)?; } } if dns_ip.is_ipv6() { if dns_ip != ipv6 { let msg = format!( "Wrong IPV6 for {} (DNS: {}, current: {})", domain, dns_ip, ipv6 ); info!("{}", msg); self.send_mail("Wrong IP".to_owned(), msg)?; } } } } Ok(()) } fn send_mail(&self, subject: String, body: String) -> Result<()> { info!("Send mail {}", subject); if let Some(false) = self.config.test { let email = EmailBuilder::new() .to(self.config.mail.from.clone()) .from(self.config.mail.to.clone()) .subject(subject) .text(body) .build()?; // Create transport let creds = Credentials::new( self.config.mail.username.clone(), self.config.mail.password.clone(), ); let mut tls_builder = TlsConnector::builder(); tls_builder.min_protocol_version(Some(Protocol::Sslv3)); let tls_parameters = ClientTlsParameters::new( self.config.mail.server.clone(), tls_builder.build().unwrap(), ); let mut mailer = SmtpClient::new( (self.config.mail.server.clone(), self.config.mail.port), ClientSecurity::Required(tls_parameters), )? .credentials(creds) .transport(); mailer.send(email.into())?; } else { println!("subject: {} - body: {}", subject, body); }; Ok(()) } } #[derive(Debug, Deserialize)] struct Config { mail: MailConfig, server: ServerConfig, test: Option, } #[derive(Debug, Deserialize)] struct ServerConfig { domains: Vec, } #[derive(Debug, Deserialize)] struct MailConfig { server: String, port: u16, username: String, password: String, from: (String, String), to: String, }