diff options
Diffstat (limited to '')
-rw-r--r-- | tools/sendsms/src/config.rs | 23 | ||||
-rwxr-xr-x | tools/sendsms/src/main.rs | 85 | ||||
-rwxr-xr-x | tools/sendsms/src/message.rs | 76 |
3 files changed, 184 insertions, 0 deletions
diff --git a/tools/sendsms/src/config.rs b/tools/sendsms/src/config.rs new file mode 100644 index 0000000..da9435a --- /dev/null +++ b/tools/sendsms/src/config.rs @@ -0,0 +1,23 @@ +use serde::Deserialize; +use std::path::PathBuf; + +#[derive(Deserialize, Debug)] +pub struct Config { + pub to: String, + pub from: String, + pub account_sid: String, + pub auth_token: String, + pub reboot: RebootConfig, +} + +#[derive(Deserialize, Debug)] +pub struct RebootConfig { + pub ifname: String, +} + +impl Config { + pub fn load_from_file(filename: &PathBuf) -> std::io::Result<Config> { + let content = std::fs::read_to_string(filename)?; + Ok(toml::from_str(&content)?) + } +} diff --git a/tools/sendsms/src/main.rs b/tools/sendsms/src/main.rs new file mode 100755 index 0000000..30e92ff --- /dev/null +++ b/tools/sendsms/src/main.rs @@ -0,0 +1,85 @@ +#![warn(rust_2018_idioms)] + +mod config; +mod message; + +use clap::{crate_version, Parser}; +use gethostname::gethostname; +use log::{error, info}; +use std::net::IpAddr; +use std::path::PathBuf; +use std::process::exit; + +#[derive(Parser, Debug)] +#[clap(name = "sendsms")] +#[clap(author = "Franck Cuny <franck@fcuny.net>")] +#[clap(version = crate_version!())] +#[clap(propagate_version = true)] +struct Args { + #[clap(short, long, value_parser)] + config: PathBuf, + + #[clap(subcommand)] + subcmd: SubCommand, +} + +#[derive(Parser, Debug)] +enum SubCommand { + Reboot, +} + +fn main() { + env_logger::init(); + let args = Args::parse(); + + let config: config::Config = match config::Config::load_from_file(&args.config) { + Ok(r) => r, + Err(e) => { + error!( + "unable to load data from {}: {}", + args.config.display(), + e.to_string() + ); + exit(1); + } + }; + + let body = match args.subcmd { + SubCommand::Reboot => reboot(&config.reboot), + }; + + let msg = message::Message { + from: config.from.to_owned(), + to: config.to.to_owned(), + body, + }; + + match msg.send(&config) { + Ok(_) => info!("message sent successfully"), + Err(error) => { + error!("failed to send the message: {}", error); + exit(1); + } + } +} + +fn reboot(config: &config::RebootConfig) -> String { + let ipaddr_v4 = if_addrs::get_if_addrs() + .unwrap_or_default() + .into_iter() + .find(|iface| iface.name == config.ifname) + .and_then(|iface| match iface.ip() { + IpAddr::V4(addr) => Some(addr), + IpAddr::V6(_) => None, + }) + .expect("there should be an ipv4 address"); + + let hostname = gethostname() + .into_string() + .expect("failed to get the hostname"); + + format!( + "{} has rebooted. The IP address for the interface {} is {}.", + hostname, config.ifname, ipaddr_v4 + ) +} diff --git a/tools/sendsms/src/message.rs b/tools/sendsms/src/message.rs new file mode 100755 index 0000000..9aa94a4 --- /dev/null +++ b/tools/sendsms/src/message.rs @@ -0,0 +1,76 @@ +use crate::config::Config; +use reqwest::blocking::Client; +use serde::Deserialize; +use std::collections::HashMap; +use std::fmt::{self, Display, Formatter}; + +const TWILIO_BASE_URL: &str = "https://api.twilio.com/2010-04-01/Accounts"; + +#[derive(Deserialize, Debug)] +pub struct Message { + pub from: String, + pub to: String, + pub body: String, +} + +// list of possible values: https://www.twilio.com/docs/sms/api/message-resource#message-status-values +#[derive(Debug, Deserialize, Clone)] +#[allow(non_camel_case_types)] +pub enum MessageStatus { + accepted, + scheduled, + queued, + sending, + sent, + receiving, + received, + delivered, + undelivered, + failed, + read, + canceled, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct MessageResponse { + pub status: Option<MessageStatus>, +} + +#[derive(Debug)] +pub enum TwilioError { + HTTPError(reqwest::StatusCode), +} + +impl Display for TwilioError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match *self { + TwilioError::HTTPError(ref s) => write!(f, "Invalid HTTP status code: {}", s), + } + } +} + +impl Message { + pub fn send(&self, config: &Config) -> Result<MessageResponse, TwilioError> { + let url = format!("{}/{}/Messages.json", TWILIO_BASE_URL, config.account_sid); + + let mut form = HashMap::new(); + form.insert("From", &self.from); + form.insert("To", &self.to); + form.insert("Body", &self.body); + + let client = Client::new(); + let response = client + .post(url) + .basic_auth(&config.account_sid, Some(&config.auth_token)) + .form(&form) + .send() + .unwrap(); + + match response.status() { + reqwest::StatusCode::CREATED | reqwest::StatusCode::OK => {} + other => return Err(TwilioError::HTTPError(other)), + }; + + Ok(response.json().unwrap()) + } +} |