aboutsummaryrefslogtreecommitdiff
path: root/tools/sendsms/src
diff options
context:
space:
mode:
Diffstat (limited to 'tools/sendsms/src')
-rw-r--r--tools/sendsms/src/config.rs23
-rwxr-xr-xtools/sendsms/src/main.rs85
-rwxr-xr-xtools/sendsms/src/message.rs76
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())
+ }
+}