Split into modules

This commit is contained in:
Andrey Golovizin 2023-04-09 22:12:15 +02:00
parent 72ed117cb0
commit f663a4f8b4
4 changed files with 158 additions and 137 deletions

61
src/api.rs Normal file
View file

@ -0,0 +1,61 @@
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use log::debug;
use serde::{Deserialize, Serialize};
const BASE_URL: &str = "https://api.azirevpn.com/v2";
#[derive(Serialize, Deserialize, Debug)]
pub(crate) struct Locations {
pub status: String,
pub locations: Vec<Location>,
}
#[derive(Serialize, Deserialize, Debug)]
pub(crate) struct Location {
pub name: String,
pub city: String,
pub country: String,
pub iso: String,
pub pool: String,
pub pubkey: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub(crate) struct Addresses {
pub status: String,
pub ipv4: WireguardConfigIpv4,
pub ipv6: WireguardConfigIpv6,
pub dns: Vec<IpAddr>,
}
#[derive(Serialize, Deserialize, Debug)]
pub(crate) struct WireguardConfigIpv4 {
pub address: Ipv4Addr,
pub netmask: u8,
}
#[derive(Serialize, Deserialize, Debug)]
pub(crate) struct WireguardConfigIpv6 {
pub address: Ipv6Addr,
pub netmask: u8,
}
pub(crate) fn get_locations() -> anyhow::Result<Locations> {
let url = format!("{}/locations", BASE_URL);
let response: Locations = ureq::get(&url).call()?.into_json()?;
debug!("response = {:?}", &response);
Ok(response)
}
pub(crate) fn add_ip(username: &str, token: &str, public_key: &str) -> anyhow::Result<Addresses> {
let url = format!("{}/ip/add", BASE_URL);
let response: Addresses = ureq::post(&url)
.send_form(&[
("username", username),
("token", token),
("key", public_key),
])?
.into_json()?;
debug!("response = {:?}", &response);
Ok(response)
}

18
src/dirs.rs Normal file
View file

@ -0,0 +1,18 @@
use directories::ProjectDirs;
use once_cell::sync::OnceCell;
use std::path::PathBuf;
const QUALIFIER: &str = "com";
const ORGANIZATION: &str = "Ero-sennin";
const APPLICATION: &str = "AzireVPN";
static PROJECT_DIRS: OnceCell<ProjectDirs> = OnceCell::new();
pub(crate) fn get_data_dir() -> PathBuf {
let project_dirs = PROJECT_DIRS.get_or_init(|| {
ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION)
.expect("cannot get project data dir")
});
project_dirs.data_dir().to_path_buf()
}

67
src/keys.rs Normal file
View file

@ -0,0 +1,67 @@
use gethostname::gethostname;
use log::debug;
use std::io::Write;
use std::path::PathBuf;
use std::process;
use crate::dirs::get_data_dir;
#[derive(Debug)]
pub(crate) struct WireguardKeyPair {
pub public_key: String,
pub private_key: String,
}
pub(crate) fn get_keys(machine: Option<&PathBuf>) -> Result<WireguardKeyPair, anyhow::Error> {
let hostname: PathBuf;
let machine_subdir: &PathBuf = if let Some(machine) = machine {
machine
} else {
hostname = PathBuf::from(gethostname());
&hostname
};
let key_path = get_data_dir().join("keys").join(machine_subdir);
debug!("key path = {:?}", &key_path);
std::fs::create_dir_all(&key_path)?;
let private_key_path = key_path.join("key");
let private_key = if private_key_path.is_file() {
std::fs::read_to_string(private_key_path)?
} else {
let key = generate_private_key()?;
std::fs::write(private_key_path, key.as_bytes())?;
key
};
let public_key_path = key_path.join("pubkey");
let public_key = if public_key_path.is_file() {
std::fs::read_to_string(public_key_path)?
} else {
let key = generate_public_key(&private_key)?;
std::fs::write(public_key_path, key.as_bytes())?;
key
};
Ok(WireguardKeyPair {
private_key,
public_key,
})
}
fn generate_private_key() -> anyhow::Result<String> {
let privkey = process::Command::new("wg").arg("genkey").output()?.stdout;
Ok(String::from_utf8(privkey)?.trim_end().to_string())
}
fn generate_public_key(private_key: &str) -> anyhow::Result<String> {
let mut pubkey_cmd = process::Command::new("wg")
.arg("pubkey")
.stdin(process::Stdio::piped())
.stdout(process::Stdio::piped())
.spawn()?;
pubkey_cmd
.stdin
.as_mut()
.expect("no stdin")
.write_all(private_key.as_bytes())?;
let pubkey = pubkey_cmd.wait_with_output()?.stdout;
Ok(String::from_utf8(pubkey)?.trim_end().to_string())
}

View file

@ -1,22 +1,15 @@
mod api;
use std::net::IpAddr;
mod dirs;
mod keys;
use std::io::Write; use std::io::Write;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::path::PathBuf; use std::path::PathBuf;
use std::process;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use directories::ProjectDirs;
use gethostname::gethostname;
use log::debug; use log::debug;
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};
const BASE_URL: &str = "https://api.azirevpn.com/v2"; use crate::keys::{get_keys, WireguardKeyPair};
const QUALIFIER: &str = "com";
const ORGANIZATION: &str = "Ero-sennin";
const APPLICATION: &str = "AzireVPN";
static PROJECT_DIRS: OnceCell<ProjectDirs> = OnceCell::new();
/// AzireVPN client /// AzireVPN client
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -55,47 +48,6 @@ enum Command {
Config(ConfigOpts), Config(ConfigOpts),
} }
#[derive(Serialize, Deserialize, Debug)]
struct Locations {
status: String,
locations: Vec<Location>,
}
#[derive(Serialize, Deserialize, Debug)]
struct Location {
name: String,
city: String,
country: String,
iso: String,
pool: String,
pubkey: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct WireguardConfig {
status: String,
ipv4: WireguardConfigIpv4,
ipv6: WireguardConfigIpv6,
dns: Vec<IpAddr>,
}
#[derive(Serialize, Deserialize, Debug)]
struct WireguardConfigIpv4 {
address: Ipv4Addr,
netmask: u8,
}
#[derive(Serialize, Deserialize, Debug)]
struct WireguardConfigIpv6 {
address: Ipv6Addr,
netmask: u8,
}
#[derive(Debug)]
struct WireguardKeyPair {
public_key: String,
private_key: String,
}
fn main() -> Result<(), anyhow::Error> { fn main() -> Result<(), anyhow::Error> {
env_logger::init(); env_logger::init();
let opts = Opts::parse(); let opts = Opts::parse();
@ -106,16 +58,8 @@ fn main() -> Result<(), anyhow::Error> {
Ok(()) Ok(())
} }
fn get_data_dir() -> PathBuf {
let project_dirs = PROJECT_DIRS.get_or_init(|| {
ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION)
.expect("cannot get project data dir")
});
project_dirs.data_dir().to_path_buf()
}
fn list_locations(opts: &Opts) -> Result<(), anyhow::Error> { fn list_locations(opts: &Opts) -> Result<(), anyhow::Error> {
let locations: Locations = get_locations()?; let locations = api::get_locations()?;
if opts.json { if opts.json {
println!("{}", serde_json::to_string(&locations)?); println!("{}", serde_json::to_string(&locations)?);
} else { } else {
@ -135,7 +79,7 @@ fn list_locations(opts: &Opts) -> Result<(), anyhow::Error> {
} }
fn get_config(_opts: &Opts, config_opts: &ConfigOpts) -> Result<(), anyhow::Error> { fn get_config(_opts: &Opts, config_opts: &ConfigOpts) -> Result<(), anyhow::Error> {
let locations = get_locations()?; let locations = api::get_locations()?;
let location = locations let location = locations
.locations .locations
.iter() .iter()
@ -144,22 +88,13 @@ fn get_config(_opts: &Opts, config_opts: &ConfigOpts) -> Result<(), anyhow::Erro
debug!("location = {:?}", &location); debug!("location = {:?}", &location);
let keys = get_keys(config_opts.machine.as_ref())?; let keys = get_keys(config_opts.machine.as_ref())?;
debug!("keys = {:?}", &keys); debug!("keys = {:?}", &keys);
let url = format!("{}/ip/add", BASE_URL); let addresses = api::add_ip(&config_opts.username, &config_opts.token, &keys.public_key)?;
let config: WireguardConfig = ureq::post(&url)
.send_form(&[
("username", &config_opts.username),
("token", &config_opts.token),
("key", &keys.public_key),
])?
.into_json()?;
debug!("response = {:?}", &config);
debug!("config = {:?}", &config);
write_config( write_config(
&mut std::io::stdout().lock(), &mut std::io::stdout().lock(),
config_opts, config_opts,
location, location,
&config, &addresses,
&keys, &keys,
) )
} }
@ -167,8 +102,8 @@ fn get_config(_opts: &Opts, config_opts: &ConfigOpts) -> Result<(), anyhow::Erro
fn write_config( fn write_config(
output: &mut dyn Write, output: &mut dyn Write,
config_opts: &ConfigOpts, config_opts: &ConfigOpts,
location: &Location, location: &api::Location,
config: &WireguardConfig, config: &api::Addresses,
keys: &WireguardKeyPair, keys: &WireguardKeyPair,
) -> Result<(), anyhow::Error> { ) -> Result<(), anyhow::Error> {
writeln!(output, "[Interface]")?; writeln!(output, "[Interface]")?;
@ -193,7 +128,6 @@ fn write_config(
writeln!(output, "[Peer]")?; writeln!(output, "[Peer]")?;
writeln!(output, "PublicKey = {}", &location.pubkey)?; writeln!(output, "PublicKey = {}", &location.pubkey)?;
writeln!(output, "Endpoint = {}:51820", &location.pool)?; writeln!(output, "Endpoint = {}:51820", &location.pool)?;
let allowed_ips: &[&str] = if config_opts.no_ipv6 { let allowed_ips: &[&str] = if config_opts.no_ipv6 {
@ -221,62 +155,3 @@ where
writeln!(output)?; writeln!(output)?;
Ok(()) Ok(())
} }
fn get_locations() -> Result<Locations, anyhow::Error> {
let url = format!("{}/locations", BASE_URL);
let locations: Locations = ureq::get(&url).call()?.into_json()?;
Ok(locations)
}
fn get_keys(machine: Option<&PathBuf>) -> Result<WireguardKeyPair, anyhow::Error> {
let hostname: PathBuf;
let machine_subdir: &PathBuf = if let Some(machine) = machine {
machine
} else {
hostname = PathBuf::from(gethostname());
&hostname
};
let key_path = get_data_dir().join("keys").join(machine_subdir);
debug!("key path = {:?}", &key_path);
std::fs::create_dir_all(&key_path)?;
let private_key_path = key_path.join("key");
let private_key = if private_key_path.is_file() {
std::fs::read_to_string(private_key_path)?
} else {
let key = generate_private_key()?;
std::fs::write(private_key_path, key.as_bytes())?;
key
};
let public_key_path = key_path.join("pubkey");
let public_key = if public_key_path.is_file() {
std::fs::read_to_string(public_key_path)?
} else {
let key = generate_public_key(&private_key)?;
std::fs::write(public_key_path, key.as_bytes())?;
key
};
Ok(WireguardKeyPair {
private_key,
public_key,
})
}
fn generate_private_key() -> anyhow::Result<String> {
let privkey = process::Command::new("wg").arg("genkey").output()?.stdout;
Ok(String::from_utf8(privkey)?.trim_end().to_string())
}
fn generate_public_key(private_key: &str) -> anyhow::Result<String> {
let mut pubkey_cmd = process::Command::new("wg")
.arg("pubkey")
.stdin(process::Stdio::piped())
.stdout(process::Stdio::piped())
.spawn()?;
pubkey_cmd
.stdin
.as_mut()
.expect("no stdin")
.write_all(private_key.as_bytes())?;
let pubkey = pubkey_cmd.wait_with_output()?.stdout;
Ok(String::from_utf8(pubkey)?.trim_end().to_string())
}