commit 1535d3d7680a2b59ceb811e0915aeb943bfec65f Author: Tochukwu Nkemdilim Date: Wed Jun 13 18:41:24 2018 +0100 Chore(Stage): Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fea9a2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ + +**/target/* +**/*.rs.bk +Cargo.lock +*.xml +.env +rls +.vscode +.idea +**/.DS_Store +crossbeam-channel \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..260ec49 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "wifi-rs" +version = "0.1.0" +authors = ["Tochukwu Nkemdilim "] + +[dependencies] diff --git a/src/connectivity/handlers/mod.rs b/src/connectivity/handlers/mod.rs new file mode 100644 index 0000000..7d67419 --- /dev/null +++ b/src/connectivity/handlers/mod.rs @@ -0,0 +1,3 @@ +mod xml_profile_handler; + +pub(crate) use self::xml_profile_handler::NetworkXmlProfileHandler; diff --git a/src/connectivity/handlers/xml_profile_handler.rs b/src/connectivity/handlers/xml_profile_handler.rs new file mode 100644 index 0000000..a3cc420 --- /dev/null +++ b/src/connectivity/handlers/xml_profile_handler.rs @@ -0,0 +1,24 @@ +use std::fs; +use std::io; +use connectivity::stubs::windows_wifi_profile; + +pub(crate) struct NetworkXmlProfileHandler { + pub(crate) content: String, +} + +impl NetworkXmlProfileHandler { + pub fn new() -> Self { + NetworkXmlProfileHandler { + content: NetworkXmlProfileHandler::read_from_stub(), + } + } + + pub fn read_from_stub() -> String { + windows_wifi_profile::get_wifi_profile() + } + + /// Recreate the file and dump the processed contents to it + pub fn to_file(&mut self, file_path: &str) -> Result<(), io::Error> { + Ok(fs::write(&file_path, self.content.as_bytes())?) + } +} diff --git a/src/connectivity/mod.rs b/src/connectivity/mod.rs new file mode 100644 index 0000000..6bde318 --- /dev/null +++ b/src/connectivity/mod.rs @@ -0,0 +1,30 @@ +pub mod profile_network; +mod handlers; +mod stubs; +mod providers; + +use std::{fmt, io}; +use std::string::FromUtf8Error; + +pub trait Network { + /// Makes an attempt to connect to a selected wireless network with password specified. + fn connect(&self, password: &str) -> bool; +} + +// Improve upon this. +impl fmt::Debug for Network { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Network") + } +} + +pub enum NetworkType { + WEP, + WPA, + WPA2, +} + +pub enum NetworkTypeParseError { + FromUtf8Error(FromUtf8Error), + IoError(io::Error), +} diff --git a/src/connectivity/profile_network.rs b/src/connectivity/profile_network.rs new file mode 100644 index 0000000..b50fedf --- /dev/null +++ b/src/connectivity/profile_network.rs @@ -0,0 +1,29 @@ +use connectivity::providers::{Machine}; +use connectivity::Network; +use std::io::{Error, ErrorKind}; + +#[derive(Debug)] +pub struct ProfileNetwork { + handler: Box, +} + +/// Profile Network handler responsible to connect to a wireless network. +impl ProfileNetwork { + pub fn new(name: &str) -> Result { + if !(cfg!(target_os = "linux") || cfg!(target_os = "windows")) { + return Err(Error::new( + ErrorKind::Other, + "The Specified OS is not supported", + )); + } + + let handler = Machine::new(name)?; + return Ok(ProfileNetwork { + handler: Box::new(handler), + }); + } + + pub fn connect(&self, password: &str) -> bool { + self.handler.connect(password) + } +} diff --git a/src/connectivity/providers/linux.rs b/src/connectivity/providers/linux.rs new file mode 100644 index 0000000..2606150 --- /dev/null +++ b/src/connectivity/providers/linux.rs @@ -0,0 +1,89 @@ +use connectivity::{Network, NetworkType, NetworkTypeParseError}; +use std::io; +use std::io::{Error, ErrorKind}; +use std::process::{Command, Output}; + +pub struct Linux { + pub name: String, + pub network_type: NetworkType, +} + +impl Linux { + pub fn new(name: String) -> Result { + match Linux::check_if_web_or_wpa(name.clone()) { + Ok(t) => match t { + NetworkType::WEP => Ok(Linux { + name: name.clone(), + network_type: NetworkType::WEP, + }), + _ => Ok(Linux { + name: name.clone(), + network_type: NetworkType::WPA, + }), + }, + Err(_) => Err(Error::new(ErrorKind::Other, "Failed to parse")), // use the NetworkTypeParseError::IoError here + } + } + + /// Detects the network type of a given network. + fn check_if_web_or_wpa(name: String) -> Result { + Command::new("nmcli") + .args(&[ + "con", + "list", + "id", + "\"", + &name, + "\"", + "|", + "awk", + "'/key-mgmt/ {{ print $2 }}'", + ]) + .output() + .map_err(|err| NetworkTypeParseError::IoError(err)) + .and_then(|output| { + String::from_utf8(output.stdout) + .map_err(|err| NetworkTypeParseError::FromUtf8Error(err)) + .and_then(|result| match result.as_ref() { + "wpa-psk" => Ok(NetworkType::WPA), + _ => Ok(NetworkType::WEP), + }) + }) + } + + pub fn connect_to_wep_network(&self, password: &str) -> Result { + Command::new("iwconfig") + .args(&["wlan0", "essid", &self.name, "key", password]) + .output() + } + + pub fn connect_to_wpa_network(&self, password: &str) -> Result { + // Dynamically generate differennt version of file (if running sync) + Command::new("wpa_passphrase") + .args(&[&self.name, password, "wpa.conf"]) + .output()?; + + Ok(Command::new("wpa_supplicant") + .args(&["-Dwext", "-i", "wlan0", "-c/root/wpa.conf"]) + .output()?) + } +} + +impl Network for Linux { + fn connect(&self, password: &str) -> bool { + match self.network_type { + NetworkType::WEP => self + .connect_to_wep_network(password) + .map_err(|_err| false) + .unwrap() + .status + .success(), + _ => self + .connect_to_wpa_network(password) + .map_err(|_err| false) + .unwrap() + .status + .success(), + } + } +} diff --git a/src/connectivity/providers/mod.rs b/src/connectivity/providers/mod.rs new file mode 100644 index 0000000..94c4be5 --- /dev/null +++ b/src/connectivity/providers/mod.rs @@ -0,0 +1,8 @@ +mod linux; +mod windows; + +#[cfg(target_os = "windows")] +pub(crate) use self::windows::Windows as Machine; + +#[cfg(target_os = "linux")] +pub(crate) use self::linux::Linux as Machine; diff --git a/src/connectivity/providers/windows.rs b/src/connectivity/providers/windows.rs new file mode 100644 index 0000000..783b5a4 --- /dev/null +++ b/src/connectivity/providers/windows.rs @@ -0,0 +1,54 @@ +use connectivity::handlers::NetworkXmlProfileHandler; +use connectivity::Network; +use std::io; +use std::process::Command; + +const OUTPUT_XML_FILE_PATH: &str = "output.xml"; + +#[cfg(target_os = "windows")] +pub(crate) struct Windows { + name: String, + pub output_xml_path: String, +} + +impl Windows { + pub fn new(name: &str) -> Result { + let profile_file_name = format!("netsh wlan add profile filename=\"{}\"", name); + + Command::new("cmd") + .args(&["/C", &profile_file_name[..]]) + .output()?; + + Ok(Windows { + name: String::from(name), + output_xml_path: OUTPUT_XML_FILE_PATH.into(), + }) + } +} + +impl Network for Windows { + fn connect(&self, password: &str) -> bool { + { + let mut handler = NetworkXmlProfileHandler::new(); + + handler.content = handler + .content + .replace("{SSID}", &self.name) + .replace("{password}", password); + + // Write details to new xml file + if let Err(err) = handler.to_file(&self.output_xml_path).map_err(|_err| false) { + return err; + } + } + + let output = Command::new("netsh") + .args(&["wlan", "connect", &format!("name = \"{}\"", self.name)]) + .output(); + + match output { + Ok(res) => res.status.success(), + Err(_) => false, + } + } +} diff --git a/src/connectivity/stubs/mod.rs b/src/connectivity/stubs/mod.rs new file mode 100644 index 0000000..c7b4ed5 --- /dev/null +++ b/src/connectivity/stubs/mod.rs @@ -0,0 +1 @@ +pub(crate) mod windows_wifi_profile; diff --git a/src/connectivity/stubs/windows_wifi_profile.rs b/src/connectivity/stubs/windows_wifi_profile.rs new file mode 100644 index 0000000..f39eb15 --- /dev/null +++ b/src/connectivity/stubs/windows_wifi_profile.rs @@ -0,0 +1,35 @@ +pub fn get_wifi_profile() -> String { + String::from( + r#" + + + {SSID} + + + {SSID} + + + ESS + auto + + + + WPA2PSK + AES + false + + + passPhrase + false + {password} + + + + + false + + + "#, + ) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a7d8e82 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,15 @@ +#![allow(dead_code)] + +mod connectivity; +pub use connectivity::profile_network::ProfileNetwork as WiFi; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn connect_to_wifi_failed() { + let wifi = WiFi::new("hello").unwrap(); + assert_eq!(wifi.connect("password"), false); + } +}