diff --git a/Cargo.lock b/Cargo.lock index 5be1548..bb955e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,9 +81,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" @@ -272,7 +272,7 @@ version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf" dependencies = [ - "nix", + "nix 0.23.1", "winapi", ] @@ -722,6 +722,10 @@ name = "insecure_chat" version = "0.1.0" dependencies = [ "mac_address", + "nix 0.25.0", + "ptth_diceware", + "thiserror", + "tokio", ] [[package]] @@ -768,9 +772,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.122" +version = "0.2.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec647867e2bf0772e28c8bcde4f0d19a9216916e890543b5a03ed8ef27b8f259" +checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" [[package]] name = "lock_api" @@ -796,7 +800,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b238e3235c8382b7653c6408ed1b08dd379bdb9fdf990fb0bbae3db2cc0ae963" dependencies = [ - "nix", + "nix 0.23.1", "winapi", ] @@ -852,25 +856,14 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "mio" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", - "miow", - "ntapi", "wasi 0.11.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "windows-sys 0.36.1", ] [[package]] @@ -905,12 +898,17 @@ dependencies = [ ] [[package]] -name = "ntapi" -version = "0.3.6" +name = "nix" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" dependencies = [ - "winapi", + "autocfg", + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", ] [[package]] @@ -1013,7 +1011,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.34.0", ] [[package]] @@ -1985,18 +1983,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.29" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.29" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -2040,16 +2038,16 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.17.0" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ + "autocfg", "bytes", "libc", "memchr", "mio", "num_cpus", - "once_cell", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -2506,11 +2504,24 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", +] + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", ] [[package]] @@ -2519,30 +2530,60 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + [[package]] name = "windows_i686_gnu" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + [[package]] name = "windows_i686_msvc" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + [[package]] name = "windows_x86_64_gnu" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + [[package]] name = "windows_x86_64_msvc" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "winreg" version = "0.10.1" diff --git a/crates/insecure_chat/Cargo.toml b/crates/insecure_chat/Cargo.toml index 4ee1f0b..c3de3af 100644 --- a/crates/insecure_chat/Cargo.toml +++ b/crates/insecure_chat/Cargo.toml @@ -7,3 +7,7 @@ edition = "2021" [dependencies] mac_address = "1.1.4" +nix = "0.25.0" +ptth_diceware = { path = "../ptth_diceware" } +thiserror = "1.0.37" +tokio = { version = "1.21.2", features = ["net", "rt-multi-thread", "fs"] } diff --git a/crates/insecure_chat/src/ip.rs b/crates/insecure_chat/src/ip.rs new file mode 100644 index 0000000..44ab6ed --- /dev/null +++ b/crates/insecure_chat/src/ip.rs @@ -0,0 +1,125 @@ +// IP address module +// Copied from the `lookaround` project + +use std::{ + net::Ipv4Addr, + process::Command, + str::FromStr, +}; + +#[derive (Debug, thiserror::Error)] +pub enum Error { + #[error (transparent)] + Io (#[from] std::io::Error), + #[error (transparent)] + FromUtf8 (#[from] std::string::FromUtf8Error), + #[error ("Self-IP detection is not implemented on Mac OS")] + NotImplementedOnMac, +} + +#[cfg(target_os = "linux")] +pub fn get_ips () -> Result , Error> { + let output = linux::get_ip_addr_output ()?; + + Ok (linux::parse_ip_addr_output (&output)) +} + +#[cfg(target_os = "macos")] +pub fn get_ips () -> Result , Error> { + Err (Error::NotImplementedOnMac) +} + +#[cfg(target_os = "windows")] +pub fn get_ips () -> Result , Error> { + let output = windows::get_ip_config_output ()?; + + Ok (windows::parse_ip_config_output (&output)) +} + +#[cfg(target_os = "linux")] +pub mod linux { + use super::*; + + pub fn get_ip_addr_output () -> Result { + let output = Command::new ("ip") + .arg ("addr") + .output ()?; + let output = output.stdout.as_slice (); + let output = String::from_utf8 (output.to_vec ())?; + Ok (output) + } + + pub fn parse_ip_addr_output (output: &str) -> Vec { + // I wrote this in FP style because I was bored. + + output.lines () + .map (|l| l.trim_start ()) + .filter_map (|l| l.strip_prefix ("inet ")) + .filter_map (|l| l.find ('/').map (|x| &l [0..x])) + .filter_map (|l| Ipv4Addr::from_str (l).ok ()) + .filter (|a| ! a.is_loopback ()) + .collect () + } +} + +#[cfg(target_os = "windows")] +pub mod windows { + use super::*; + + pub fn get_ip_config_output () -> Result { + let output = Command::new ("ipconfig") + .output ()?; + let output = output.stdout.as_slice (); + let output = String::from_utf8 (output.to_vec ())?; + Ok (output) + } + + pub fn parse_ip_config_output (output: &str) -> Vec { + let mut addrs = vec! []; + + for line in output.lines () { + let line = line.trim_start (); + + // Maybe only works on English locales? + if ! line.starts_with ("IPv4 Address") { + continue; + } + let colon_pos = match line.find (':') { + None => continue, + Some (x) => x, + }; + let line = &line [colon_pos + 2..]; + + let addr = match Ipv4Addr::from_str (line) { + Err (_) => continue, + Ok (x) => x, + }; + + addrs.push (addr); + } + + addrs + } + + #[cfg (test)] + mod test { + use super::*; + + #[test] + fn test () { + for (input, expected) in [ + ( + r" + IPv4 Address . . .. . . . : 192.168.1.1 + ", + vec! [ + Ipv4Addr::new (192, 168, 1, 1), + ] + ), + ] { + let actual = parse_ip_config_output (input); + assert_eq! (actual, expected); + } + } + } +} diff --git a/crates/insecure_chat/src/main.rs b/crates/insecure_chat/src/main.rs index abc0ac2..48c440e 100644 --- a/crates/insecure_chat/src/main.rs +++ b/crates/insecure_chat/src/main.rs @@ -1,18 +1,150 @@ use std::{ net::{ - IpAddr, - SocketAddr, + Ipv4Addr, + SocketAddrV4, + UdpSocket, }, }; -fn main () { +mod ip; + +fn main () -> Result <(), Error> +{ let mut args = std::env::args (); let mut bail_unknown = true; - // let mut name = + let mut last_unknown = None; + let mut name = ptth_diceware::passphrase ("_", 3); + let mut subcommand_count = 0; + let mut subcommand = None; - for arg in args.next () { - + args.next (); + while let Some (arg) = args.next () { + if arg == "--ignore-unknown" { + bail_unknown = false; + } + if arg == "--name" { + name = args.next ().unwrap ().to_string (); + } + else if arg == "sender" { + subcommand = Some (Subcommand::Sender); + subcommand_count += 1; + } + else if arg == "spy" { + subcommand = Some (Subcommand::Spy); + subcommand_count += 1; + } + else { + last_unknown = Some (arg); + } } - unimplemented!(); + if bail_unknown { + if let Some (last_unknown) = last_unknown { + eprintln! ("Unknown argument `{}`", last_unknown); + return Err (Error::Args); + } + } + + if subcommand_count >= 2 { + eprintln! ("Detected {} subcommands in arguments", subcommand_count); + return Err (Error::Args) + } + + let rt = tokio::runtime::Runtime::new ()?; + + let params = Params::default (); + + rt.block_on (async { + if let Some (cmd) = subcommand { + return match cmd { + Subcommand::Sender => sender (params).await, + Subcommand::Spy => spy (params), + }; + } + + println! ("Name is `{}`", name); + + Ok::<_, Error> (()) + })?; + + Ok (()) +} + +enum Subcommand { + Sender, + Spy, +} + +struct Params { + multicast_group: (Ipv4Addr, u16), +} + +impl Default for Params { + fn default () -> Self { + let multicast_group = (Ipv4Addr::new (225, 100, 99, 98), 9041); + + Self { + multicast_group, + } + } +} + +async fn sender (params: Params) -> Result <(), Error> +{ + let socket = tokio::net::UdpSocket::bind (SocketAddrV4::new (Ipv4Addr::UNSPECIFIED, 0)).await?; + + let (multicast_addr, multicast_port) = params.multicast_group; + + socket.join_multicast_v4 (multicast_addr, Ipv4Addr::UNSPECIFIED)?; + eprintln! ("Multicast group is {:?}", params.multicast_group); + eprintln! ("Local addr is {}", socket.local_addr ()?); + + socket.send_to (&[], params.multicast_group).await?; + + Ok (()) +} + +fn spy (params: Params) -> Result <(), Error> +{ + let (multicast_addr, multicast_port) = params.multicast_group; + + let socket = match UdpSocket::bind (SocketAddrV4::new (Ipv4Addr::UNSPECIFIED, multicast_port)) { + Ok (x) => x, + Err (e) => if e.kind () == std::io::ErrorKind::AddrInUse { + eprintln! ("Address in use. You can only run 1 instance of Insecure Chat at a time, even in spy mode."); + return Err (Error::AddrInUse); + } + else { + return Err (e.into ()); + } + }; + + for bind_addr in ip::get_ips ()? { + socket.join_multicast_v4 (&multicast_addr, &bind_addr)?; + // eprintln! ("Joined multicast with {}", bind_addr); + } + eprintln! ("Multicast addr is {}", multicast_addr); + eprintln! ("Local addr is {}", socket.local_addr ()?); + + loop { + let mut buf = vec! [0u8; 2048]; + + eprintln! ("Listening for UDP packets..."); + let (bytes_recved, remote_addr) = socket.recv_from (&mut buf)?; + buf.truncate (bytes_recved); + + println! ("Received {} bytes from {}", bytes_recved, remote_addr); + } +} + +#[derive (Debug, thiserror::Error)] +enum Error { + #[error ("Address in use")] + AddrInUse, + #[error ("CLI args")] + Args, + #[error (transparent)] + Io (#[from] std::io::Error), + #[error (transparent)] + Ip (#[from] ip::Error), } diff --git a/crates/ptth_diceware/src/lib.rs b/crates/ptth_diceware/src/lib.rs index 5ec217b..a72c310 100644 --- a/crates/ptth_diceware/src/lib.rs +++ b/crates/ptth_diceware/src/lib.rs @@ -1,15 +1,18 @@ use rand::Rng; pub fn main () { + let passphrase = passphrase (" ", 8); + println! ("{}", passphrase); +} + +pub fn passphrase (separator: &str, len: usize) -> String { let diceware = Diceware::default (); - let random_words: Vec <&str> = (0..8) + let random_words: Vec <&str> = (0..len) .map (|_| diceware.random_word ()) .collect (); - let passphrase = random_words.join (" "); - - println! ("{}", passphrase); + random_words.join (separator) } pub struct Diceware {