165 lines
4.2 KiB
Rust
165 lines
4.2 KiB
Rust
use std::io::Write;
|
|
use std::net::ToSocketAddrs;
|
|
use std::process;
|
|
|
|
use log::debug;
|
|
|
|
use clap::Clap;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
const BASE_URL: &str = "https://api.azirevpn.com/v1";
|
|
|
|
#[derive(Clap, Debug)]
|
|
struct Opts {
|
|
#[clap(subcommand)]
|
|
command: Command,
|
|
}
|
|
|
|
#[derive(Clap, Debug)]
|
|
struct EndpointsOpts {
|
|
#[clap(short, long)]
|
|
json: bool,
|
|
}
|
|
|
|
#[derive(Clap, Debug)]
|
|
struct ConfigOpts {
|
|
location: String,
|
|
username: String,
|
|
token: String,
|
|
}
|
|
|
|
#[derive(Clap, Debug)]
|
|
enum Command {
|
|
Endpoints(EndpointsOpts),
|
|
Config(ConfigOpts),
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
struct Endpoints {
|
|
wireguard: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
struct Location {
|
|
name: String,
|
|
city: String,
|
|
country: String,
|
|
iso: String,
|
|
endpoints: Endpoints,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
struct Locations {
|
|
locations: Vec<Location>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
struct WireguardConfig {
|
|
status: String,
|
|
data: WireguardConfigData,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
#[serde(rename_all = "PascalCase")]
|
|
struct WireguardConfigData {
|
|
public_key: String,
|
|
address: String,
|
|
endpoint: String,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct WireguardKeyPair {
|
|
public_key: String,
|
|
private_key: String,
|
|
}
|
|
|
|
fn main() -> Result<(), anyhow::Error> {
|
|
env_logger::init();
|
|
let opts = Opts::parse();
|
|
match &opts.command {
|
|
Command::Endpoints(list_opts) => list(&list_opts)?,
|
|
Command::Config(get_config_opts) => get_config(&get_config_opts)?,
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn list(opts: &EndpointsOpts) -> Result<(), anyhow::Error> {
|
|
let locations: Locations = get_locations()?;
|
|
if opts.json {
|
|
println!("{}", serde_json::to_string(&locations)?);
|
|
} else {
|
|
for (i, location) in locations.locations.iter().enumerate() {
|
|
if i > 0 {
|
|
println!()
|
|
};
|
|
println!("Name: {}", location.name);
|
|
println!("City: {}", location.city);
|
|
println!("Country: {}", location.country);
|
|
println!("Country code: {}", location.iso);
|
|
println!("WireGuard endpoint: {}", location.endpoints.wireguard);
|
|
}
|
|
}
|
|
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_config(opts: &ConfigOpts) -> Result<(), anyhow::Error> {
|
|
let locations = get_locations()?;
|
|
let location = locations
|
|
.locations
|
|
.iter()
|
|
.find(|location| location.name == opts.location)
|
|
.ok_or_else(|| anyhow::anyhow!("no such location: {}", opts.location))?;
|
|
debug!("location = {:?}", &location);
|
|
let keys = generage_keys()?;
|
|
debug!("keys = {:?}", &keys);
|
|
let config: WireguardConfig = ureq::post(&location.endpoints.wireguard)
|
|
.send_form(&[
|
|
("username", &opts.username),
|
|
("token", &opts.token),
|
|
("pubkey", &keys.public_key),
|
|
])?
|
|
.into_json()?;
|
|
debug!("config = {:?}", &config);
|
|
let mut endpoint_addrs = config.data.endpoint.to_socket_addrs()?;
|
|
let endpoint_addr = endpoint_addrs
|
|
.next()
|
|
.ok_or_else(|| anyhow::anyhow!("no endpoint address received"))?;
|
|
debug!("endpoint_addr = {:?}", &endpoint_addr);
|
|
println!(
|
|
r"[Interface]
|
|
PrivateKey = {}
|
|
Address = {}
|
|
|
|
[Peer]
|
|
PublicKey = {}
|
|
Endpoint = {}
|
|
AllowedIPs = 0.0.0.0/0 #, ::/0",
|
|
&keys.private_key, &config.data.address, &config.data.public_key, &endpoint_addr
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
fn generage_keys() -> Result<WireguardKeyPair, anyhow::Error> {
|
|
let privkey = process::Command::new("wg").arg("genkey").output()?.stdout;
|
|
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(&privkey)?;
|
|
let pubkey = pubkey_cmd.wait_with_output()?.stdout;
|
|
Ok(WireguardKeyPair {
|
|
private_key: String::from_utf8(privkey)?.trim_end().to_string(),
|
|
public_key: String::from_utf8(pubkey)?.trim_end().to_string(),
|
|
})
|
|
}
|