mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-05-30 08:49:26 -07:00
* client mode added
* Prevent OTA daemons dmclient and upgrade from running and phoning home to Verizon
* Fix workflow
* WIFI changes to support moxee. May need to rebase as delivering refactoring under other PR.
* code changes for rust based wifi client mode docs next
* Doc changes & security fixes
* Added watchdog and recover if crash occurs for wifi.
* Remove changes which were from device UI work (seperate feature which snuck into this branch)
* Add missing wifi and firewall module declarations
* cleaning up the code a bit
* Gate wpa_suplicant in installer and workflow to avoid building binary every push
* fix to check diskspace
* Improved support for subnet colisions, and attempts to rejoin network.
* Add WiFi client support and S01iptables to T-Mobile and Wingtech installers
Both installers now deploy wpa_supplicant, wpa_cli, udhcpc-hook.sh, and
the S01iptables boot-time firewall script. Config generation uses the
shared install_config/install_wifi_creds helpers instead of manual string
replacement.
* Revert "Add WiFi client support and S01iptables to T-Mobile and Wingtech installers"
This reverts commit 944b369c4f.
* Fix build: ignore unused wifi_ssid/wifi_password fields in T-Mobile and Wingtech installers
* Moved to a wifi crate
* Add host route and arp_filter to prevent subnet collisions
* add wakelock so kernel doesn't shut down wifi on battery when wifi is enabled
* Move wifi to external wifi-station crate, remove wifi from installer, extract OTA blocking
* fixed outdated info, moved udhcpc hook to wifi-station crate.
* Update to new version of wifi-station
* Address PR review feedback: replace Docker wpa build, add iw, remove OTA, revert unrelated changes
- Replace Docker-based wpa_supplicant build with shell script (scripts/build-wpa-supplicant.sh)
- Add iw cross-compilation and deployment to Orbic installer
- Skip wifi tool install if binary already exists on device
- Remove OTA daemon blocker (extracted for separate PR)
- Revert unrelated UZ801 and T-Mobile installer changes
- Remove connection.rs test scaffolding
- Rewrite S01iptables init script to read config.toml directly
- Pin url crate to 2.5.4 to fix MSRV
* Fix build script: use bash for parameter substitution
The ${VAR//pattern/replacement} syntax is a bash extension that
doesn't work in dash (Ubuntu's /bin/sh).
* Fix iw build: export PKG_CONFIG_LIBDIR as env var
Passing PKG_CONFIG_LIBDIR as a make variable doesn't export it to
$(shell pkg-config ...) calls. Set it as an environment variable
so pkg-config finds the cross-compiled libnl.
* Point wifi-station to GitHub rev 97c579a
* add comment
* Update daemon/src/config.rs
Add decorators
Co-authored-by: Andrej Walilko <walilkoa@gmail.com>
* Update daemon/src/server.rs
add utopia doc support
Co-authored-by: Andrej Walilko <walilkoa@gmail.com>
* Update daemon/src/server.rs
add utopia doc support
Co-authored-by: Andrej Walilko <walilkoa@gmail.com>
* Update to wifi-station with utoipa doc strings
* add utoipa to wifi-station
* added WPA3 support
* fix firewall port detection, update wifi-station to c267d37
fix ntfy port_or_known_default, comment out ntfy_url in config
template, update wifi-station with resolv.conf bind mount
fallback, udhcpc_bin config, and module path fix for UZ801
* show wifi UI for tmobile and wingtech, add udhcpc_bin config
both devices have wifi hardware and backend support. wingtech
verified on hardware (QCA6174 via PCIe). uz801 excluded for now
due to driver scan limitations with hostapd active.
* install wifi tools from orbic-usb installer, fix DNS default to Quad9, bump wifi-station rev
* fix Modal scroll listener leak, correct file transfer timeout math, document firewall fail-open, clarify UZ801 wifi status
* build-dev.sh: build wifi tools so install-dev works for orbic-family devices
* update Cargo.lock for wifi-station e8ec5b4
* fix setup_timeout_server crypto provider install, apply rustfmt
* Update installer/src/connection.rs
Co-authored-by: Cooper Quintin <cooperq@users.noreply.github.com>
* Update installer/src/orbic.rs
Co-authored-by: Cooper Quintin <cooperq@users.noreply.github.com>
* apply rustfmt to AdbConnection::run_command
---------
Co-authored-by: Andrej Walilko <walilkoa@gmail.com>
Co-authored-by: Cooper Quintin <cooperq@users.noreply.github.com>
229 lines
8.0 KiB
Rust
229 lines
8.0 KiB
Rust
use std::future::Future;
|
|
use std::net::SocketAddr;
|
|
|
|
use anyhow::{Result, bail};
|
|
|
|
use crate::output::{print, println};
|
|
|
|
/// Abstraction for device communication (telnet or ADB)
|
|
pub trait DeviceConnection {
|
|
/// Run a shell command and return its output
|
|
fn run_command(&mut self, command: &str) -> impl Future<Output = Result<String>> + Send;
|
|
|
|
/// Write a file to the device
|
|
fn write_file(&mut self, path: &str, content: &[u8])
|
|
-> impl Future<Output = Result<()>> + Send;
|
|
}
|
|
|
|
/// Check if a file exists using a DeviceConnection
|
|
pub async fn file_exists<C: DeviceConnection>(conn: &mut C, path: &str) -> bool {
|
|
conn.run_command(&format!("test -f '{path}' && echo exists || echo missing"))
|
|
.await
|
|
.map(|output| output.contains("exists"))
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// Shared config installation logic. Installs to /data/rayhunter/config.toml which resolves
|
|
/// through the symlink to the actual data directory.
|
|
pub async fn install_config<C: DeviceConnection>(
|
|
conn: &mut C,
|
|
device_type: &str,
|
|
reset_config: bool,
|
|
) -> Result<()> {
|
|
let config_path = "/data/rayhunter/config.toml";
|
|
if reset_config || !file_exists(conn, config_path).await {
|
|
let config = crate::CONFIG_TOML.replace(
|
|
r#"#device = "orbic""#,
|
|
&format!(r#"device = "{device_type}""#),
|
|
);
|
|
conn.write_file(config_path, config.as_bytes()).await?;
|
|
} else {
|
|
println!("Config file already exists, skipping (use --reset-config to overwrite)");
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Install wifi tools (wpa_supplicant, wpa_cli, iw) to /data/rayhunter/bin.
|
|
///
|
|
/// Skips any binary that is already present on the device (e.g. provided by firmware),
|
|
/// since those may be newer or better-integrated than the bundled versions.
|
|
pub async fn install_wifi_tools<C: DeviceConnection>(
|
|
conn: &mut C,
|
|
wpa_supplicant: &[u8],
|
|
wpa_cli: &[u8],
|
|
iw: &[u8],
|
|
) -> Result<()> {
|
|
let tools: &[(&str, &str, &[u8])] = &[
|
|
(
|
|
"wpa_supplicant",
|
|
"/data/rayhunter/bin/wpa_supplicant",
|
|
wpa_supplicant,
|
|
),
|
|
("wpa_cli", "/data/rayhunter/bin/wpa_cli", wpa_cli),
|
|
("iw", "/data/rayhunter/bin/iw", iw),
|
|
];
|
|
for &(name, dest, payload) in tools {
|
|
if device_has_binary(conn, name).await {
|
|
println!("{name} already on device, skipping");
|
|
} else {
|
|
conn.write_file(dest, payload).await?;
|
|
conn.run_command(&format!("chmod +x {dest}")).await?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn device_has_binary<C: DeviceConnection>(conn: &mut C, name: &str) -> bool {
|
|
// `command -v` is a POSIX shell builtin, so it works on minimal busybox firmware
|
|
// even when /usr/bin/which is absent.
|
|
conn.run_command(&format!(
|
|
"\"command -v {name} >/dev/null 2>&1 && echo FOUND || echo MISSING\""
|
|
))
|
|
.await
|
|
.map(|out| out.contains("FOUND"))
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// Check if a directory exists using a DeviceConnection
|
|
pub async fn dir_exists<C: DeviceConnection>(conn: &mut C, path: &str) -> bool {
|
|
conn.run_command(&format!("test -d '{path}' && echo exists || echo missing"))
|
|
.await
|
|
.map(|output| output.contains("exists"))
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// Check if a path is a symlink using a DeviceConnection
|
|
pub async fn is_symlink<C: DeviceConnection>(conn: &mut C, path: &str) -> bool {
|
|
conn.run_command(&format!("test -L '{path}' && echo yes || echo no"))
|
|
.await
|
|
.map(|output| output.contains("yes"))
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// Read the target of a symlink using a DeviceConnection
|
|
pub async fn readlink<C: DeviceConnection>(conn: &mut C, path: &str) -> Result<String> {
|
|
// Use a prefix marker to find the actual output line, since some shells (TP-Link) echo
|
|
// back the command and run_command appends protocol lines.
|
|
let output = conn
|
|
.run_command(&format!("echo RL:$(readlink '{path}')"))
|
|
.await?;
|
|
|
|
for line in output.lines() {
|
|
if let Some(target) = line.trim().strip_prefix("RL:") {
|
|
return Ok(target.to_string());
|
|
}
|
|
}
|
|
|
|
bail!("unexpected readlink output: {output:?}");
|
|
}
|
|
|
|
/// Set up the data directory at `data_dir` and create a symlink from `/data/rayhunter` to it.
|
|
///
|
|
/// Handles migration from old locations:
|
|
/// - If `/data/rayhunter` is a real directory, moves its contents to `data_dir`
|
|
/// - If `/data/rayhunter` is a symlink to a different location, moves from the old target
|
|
/// - If `/data/rayhunter` doesn't exist, just creates the symlink
|
|
/// - If `/data/rayhunter` is a symlink to `data_dir`, does nothing
|
|
pub async fn setup_data_directory<C: DeviceConnection>(conn: &mut C, data_dir: &str) -> Result<()> {
|
|
if data_dir == "/data/rayhunter" {
|
|
bail!("data_dir must not be /data/rayhunter");
|
|
}
|
|
|
|
if data_dir.contains("'") {
|
|
bail!("data_dir must not contain an apostrophe (')");
|
|
}
|
|
|
|
// Determine where old data lives, if anywhere
|
|
let old_data_source = if is_symlink(conn, "/data/rayhunter").await {
|
|
let current_target = readlink(conn, "/data/rayhunter").await?;
|
|
if current_target == data_dir {
|
|
println!("Data directory already configured at {data_dir}");
|
|
return Ok(());
|
|
}
|
|
conn.run_command("rm -f /data/rayhunter").await?;
|
|
// The old symlink target is where data actually lives
|
|
if dir_exists(conn, ¤t_target).await {
|
|
Some(current_target)
|
|
} else {
|
|
None
|
|
}
|
|
} else if dir_exists(conn, "/data/rayhunter").await {
|
|
if dir_exists(conn, data_dir).await {
|
|
bail!("Both /data/rayhunter and {data_dir} exist and are directories.");
|
|
}
|
|
// Real directory (pre-migration Orbic state)
|
|
Some("/data/rayhunter".to_string())
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// Migrate old data if present
|
|
if let Some(old_source) = &old_data_source {
|
|
// Stop rayhunter-daemon so it doesn't write during migration.
|
|
// The device will be rebooted at the end of installation anyway.
|
|
print!("Stopping rayhunter-daemon ... ");
|
|
let _ = conn
|
|
.run_command("/etc/init.d/rayhunter_daemon stop 2>/dev/null; true")
|
|
.await;
|
|
println!("ok");
|
|
|
|
print!("Migrating data from {old_source} to {data_dir} ... ");
|
|
|
|
// mv old data into its place. If source and destination are on the same filesystem,
|
|
// this is an instant rename.
|
|
// XXX: DeviceConnection::run_command does not expose the exit code of the ran command. It
|
|
// probably should, or a utility for it should exist?
|
|
let mv_output = conn
|
|
.run_command(&format!("mv '{old_source}' '{data_dir}' && echo MV_OK"))
|
|
.await?;
|
|
if mv_output.contains("MV_OK") {
|
|
println!("ok");
|
|
} else {
|
|
bail!("Failed to move data from {old_source} to {data_dir}:\n{mv_output}");
|
|
}
|
|
} else {
|
|
// No migration needed, just ensure the target directory exists
|
|
conn.run_command(&format!("mkdir -p '{data_dir}'")).await?;
|
|
}
|
|
|
|
// Create the symlink
|
|
print!("Creating symlink /data/rayhunter -> {data_dir} ... ");
|
|
conn.run_command("mkdir -p /data").await?;
|
|
conn.run_command(&format!("ln -sf '{data_dir}' /data/rayhunter"))
|
|
.await?;
|
|
println!("ok");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Telnet-based connection wrapper
|
|
pub struct TelnetConnection {
|
|
pub addr: SocketAddr,
|
|
pub wait_for_prompt: bool,
|
|
}
|
|
|
|
impl TelnetConnection {
|
|
pub fn new(addr: SocketAddr, wait_for_prompt: bool) -> Self {
|
|
Self {
|
|
addr,
|
|
wait_for_prompt,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DeviceConnection for TelnetConnection {
|
|
async fn run_command(&mut self, command: &str) -> Result<String> {
|
|
crate::util::telnet_send_command_with_output(
|
|
self.addr,
|
|
command,
|
|
self.wait_for_prompt,
|
|
std::time::Duration::from_secs(10),
|
|
)
|
|
.await
|
|
}
|
|
|
|
async fn write_file(&mut self, path: &str, content: &[u8]) -> Result<()> {
|
|
crate::util::telnet_send_file(self.addr, path, content, self.wait_for_prompt).await
|
|
}
|
|
}
|