1use anyhow::anyhow;
4use arti_client::TorClientConfig;
5use clap::{ArgMatches, Args, FromArgMatches, Parser, Subcommand, ValueEnum};
6use tor_hsservice::{HsId, HsNickname, OnionService};
7
8use crate::{ArtiConfig, Result, TorClient};
9
10#[derive(Parser, Debug)]
12pub(crate) enum HssSubcommands {
13 Hss(Hss),
15}
16
17#[derive(Debug, Parser)]
18pub(crate) struct Hss {
19 #[command(flatten)]
21 common: CommonArgs,
22
23 #[command(subcommand)]
25 command: HssSubcommand,
26}
27
28#[derive(Subcommand, Debug, Clone)]
29pub(crate) enum HssSubcommand {
30 OnionAddress(OnionAddressArgs),
32
33 #[command(hide = true)] OnionName(OnionAddressArgs),
36}
37
38#[derive(Debug, Clone, Args)]
40pub(crate) struct OnionAddressArgs {
41 #[arg(
43 long,
44 default_value_t = GenerateKey::No,
45 value_enum
46 )]
47 generate: GenerateKey,
48}
49
50#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, ValueEnum)]
52enum GenerateKey {
53 #[default]
55 No,
56 IfNeeded,
58}
59
60#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
62enum KeyType {
63 OnionAddress,
65}
66
67#[derive(Debug, Clone, Args)]
69pub(crate) struct CommonArgs {
70 #[arg(short, long)]
72 nickname: HsNickname,
73}
74
75pub(crate) fn run(
77 hss_matches: &ArgMatches,
78 config: &ArtiConfig,
79 client_config: &TorClientConfig,
80) -> Result<()> {
81 let hss = Hss::from_arg_matches(hss_matches).expect("Could not parse hss subcommand");
82
83 match hss.command {
84 HssSubcommand::OnionAddress(args) => {
85 run_onion_address(&hss.common, &args, config, client_config)
86 }
87 HssSubcommand::OnionName(args) => {
88 eprintln!(
89 "warning: using deprecated command 'onion-name', (hint: use 'onion-address' instead)"
90 );
91 run_onion_address(&hss.common, &args, config, client_config)
92 }
93 }
94}
95
96fn create_svc(
98 nickname: &HsNickname,
99 config: &ArtiConfig,
100 client_config: &TorClientConfig,
101) -> Result<OnionService> {
102 let Some(svc_config) = config
103 .onion_services
104 .iter()
105 .find(|(n, _)| *n == nickname)
106 .map(|(_, cfg)| cfg.svc_cfg.clone())
107 else {
108 return Err(anyhow!("Service {nickname} is not configured"));
109 };
110
111 Ok(
118 TorClient::<tor_rtcompat::PreferredRuntime>::create_onion_service(
119 client_config,
120 svc_config,
121 )?,
122 )
123}
124
125fn display_onion_address(nickname: &HsNickname, hsid: Option<HsId>) -> Result<()> {
127 if let Some(onion) = hsid {
130 println!("{onion}");
131 } else {
132 return Err(anyhow!(
133 "Service {nickname} does not exist, or does not have an K_hsid yet"
134 ));
135 }
136
137 Ok(())
138}
139
140fn onion_address(
142 args: &CommonArgs,
143 config: &ArtiConfig,
144 client_config: &TorClientConfig,
145) -> Result<()> {
146 let onion_svc = create_svc(&args.nickname, config, client_config)?;
147 let hsid = onion_svc.onion_address();
148 display_onion_address(&args.nickname, hsid)?;
149
150 Ok(())
151}
152
153fn get_or_generate_onion_address(
155 args: &CommonArgs,
156 config: &ArtiConfig,
157 client_config: &TorClientConfig,
158) -> Result<()> {
159 let svc = create_svc(&args.nickname, config, client_config)?;
160 let hsid = svc.onion_address();
161 match hsid {
162 Some(hsid) => display_onion_address(&args.nickname, Some(hsid)),
163 None => {
164 let selector = Default::default();
165 let hsid = svc.generate_identity_key(selector)?;
166 display_onion_address(&args.nickname, Some(hsid))
167 }
168 }
169}
170
171fn run_onion_address(
173 args: &CommonArgs,
174 get_key_args: &OnionAddressArgs,
175 config: &ArtiConfig,
176 client_config: &TorClientConfig,
177) -> Result<()> {
178 match get_key_args.generate {
179 GenerateKey::No => onion_address(args, config, client_config),
180 GenerateKey::IfNeeded => get_or_generate_onion_address(args, config, client_config),
181 }
182}