mod proxy;
mod tor;
use anyhow::Context;
use anyhow::Result;
use clap::{Arg, ArgAction, CommandFactory, FromArgMatches};
use onionize::args::Args;
use onionize::keygen;
use qrcode::QrCode;
use qrcode::render::unicode;
use safelog::DisplayRedacted;
use tokio::signal;
use tor_rtcompat::PreferredRuntime;
use tracing::{debug, error, info};
use rust_i18n::t;
rust_i18n::i18n!("./locales");
#[tokio::main]
async fn main() -> Result<()> {
onionize::utils::setup_locale();
let mut command: clap::Command = Args::command();
command = command.about(t!("cli.about"));
let help_template = format!(
"{{before-help}}{{name}} {{version}}\n{{about-with-newline}}\n{usage_title}: {{usage}}\n\n{options_title}:\n{{options}}\n\n{{after-help}}",
usage_title = t!("cli.usage"),
options_title = t!("cli.options")
);
command = command
.help_template(help_template)
.disable_help_flag(true)
.disable_version_flag(true)
.arg(
Arg::new("help")
.long("help")
.short('h')
.action(ArgAction::Help)
.help(t!("cli.help_info"))
.global(true),
)
.arg(
Arg::new("version")
.long("version")
.short('V')
.action(ArgAction::Version)
.help(t!("cli.version"))
.global(true),
)
.mut_arg("port", |arg| arg.help(t!("cli.port_help")))
.mut_arg("qr", |arg| arg.help(t!("cli.qr_help")))
.mut_arg("auth", |arg| arg.help(t!("cli.auth_help")))
.mut_arg("verbose", |arg| arg.help(t!("cli.verbose_help")))
.mut_arg("host", |arg| arg.help(t!("cli.host_help")))
.mut_arg("restricted", |arg| arg.help(t!("cli.restricted_help")))
.mut_arg("nickname", |arg| arg.help(t!("cli.nickname_help")));
let mut matches: clap::ArgMatches = command.get_matches();
let args = Args::from_arg_matches_mut(&mut matches)?;
let filter = if args.verbose {
"debug"
} else {
"warn,arti_onion_proxy=info"
};
tracing_subscriber::fmt().with_env_filter(filter).init();
debug!("{:?}", rust_i18n::available_locales!());
info!("{}", t!("main.starting"));
if args.keygen {
keygen::print_new_keypair()?;
return Ok(());
}
let (auth_config, generated_client_key) = if args.restricted {
info!("🔐 Generating ephemeral keys for restricted mode...");
let keys = keygen::generate_keys();
(Some(keys.server_string), Some(keys.client_string))
} else {
(args.auth.clone(), None)
};
let host = args.get_normalized_host();
let nickname = args.get_effective_nickname();
let target_address = format!("{}:{}", host, args.port);
info!("{}", t!("main.target_address", addr = target_address));
if args.host != host {
info!("{}", t!("main.localhost_conversion"));
}
info!("{}", t!("main.nickname_server", nickname = nickname));
let runtime = PreferredRuntime::current()?;
let tor_client = tor::start_tor_client(runtime.clone(), None).await?;
let (service, requests) =
tor::launch_onion_service(&tor_client, &nickname, auth_config).await?;
let o_addr = service
.onion_address()
.ok_or(anyhow::anyhow!(t!("main.notgen")))?;
if args.qr {
let code = QrCode::new(format!("http://{}", o_addr.display_unredacted()))
.context(t!("main.long"))?;
let image = code
.render::<unicode::Dense1x2>()
.dark_color(unicode::Dense1x2::Light)
.light_color(unicode::Dense1x2::Dark)
.build();
println!("{}", t!("main.qrcode", image = image));
}
info!(
"{}",
t!("main.o_created", o_addr = o_addr.display_unredacted())
);
if let Some(client_key) = generated_client_key {
info!("{}", t!("main.restricted_info"));
info!("{}", t!("main.restricted_client", client_key = client_key));
}
info!("{}", t!("main.redirecting_to", addr = target_address));
tokio::select! {
_ = proxy::run_proxy_loop(runtime, requests, &target_address) => {
error!("{}", t!("main.errors.loop_crashed"));
}
_ = signal::ctrl_c() => {
info!("{}", t!("main.quit"));
}
}
Ok(())
}