1 #[cfg(feature = "multi-user")]
2 use std::collections::HashMap;
3 
4 use std::{
5     convert::TryFrom,
6     path::PathBuf,
7     str::{from_utf8_unchecked, FromStr},
8     {env, fmt},
9 };
10 
11 use bytes::Bytes;
12 use clap::{App, Arg, ArgMatches, SubCommand};
13 
14 use rpki::{
15     repository::{
16         aspa::{DuplicateProviderAs, ProviderAs},
17         crypto::KeyIdentifier,
18         x509::Time,
19     },
20     uri,
21 };
22 
23 use crate::{
24     cli::report::{ReportError, ReportFormat},
25     commons::{
26         api::{
27             AddChildRequest, AspaCustomer, AspaDefinition, AspaDefinitionFormatError, AspaProvidersUpdate,
28             AuthorizationFmtError, CertAuthInit, ChildHandle, Handle, ParentCaContact, ParentCaReq, ParentHandle,
29             PublicationServerUris, PublisherHandle, RepositoryContact, ResourceSet, ResourceSetError, RoaDefinition,
30             RoaDefinitionUpdates, RtaName, Token, UpdateChildRequest,
31         },
32         crypto::{IdCert, SignSupport},
33         error::KrillIoError,
34         remote::rfc8183,
35         util::file,
36     },
37     constants::*,
38     daemon::ca::{ResourceTaggedAttestation, RtaContentRequest, RtaPrepareRequest},
39 };
40 
41 struct GeneralArgs {
42     server: uri::Https,
43     token: Token,
44     format: ReportFormat,
45     api: bool,
46 }
47 
48 impl GeneralArgs {
from_matches(matches: &ArgMatches) -> Result<Self, Error>49     fn from_matches(matches: &ArgMatches) -> Result<Self, Error> {
50         let server = {
51             let mut server = match env::var(KRILL_CLI_SERVER_ENV) {
52                 Ok(server_str) => Some(uri::Https::try_from(server_str)?),
53                 Err(_) => None,
54             };
55 
56             if let Some(server_str) = matches.value_of(KRILL_CLI_SERVER_ARG) {
57                 server = Some(uri::Https::from_str(server_str)?);
58             }
59 
60             server.unwrap_or_else(|| uri::Https::from_str(KRILL_CLI_SERVER_DFLT).unwrap())
61         };
62 
63         let token = {
64             let mut token = env::var(KRILL_CLI_TOKEN_ENV).ok().map(Token::from);
65 
66             if let Some(token_str) = matches.value_of(KRILL_CLI_ADMIN_TOKEN_ARG) {
67                 token = Some(Token::from(token_str));
68             }
69 
70             token.ok_or_else(|| Error::missing_arg_with_env(KRILL_CLI_ADMIN_TOKEN_ARG, KRILL_CLI_TOKEN_ENV))?
71         };
72 
73         let format = {
74             let mut format = match env::var(KRILL_CLI_FORMAT_ENV) {
75                 Ok(fmt_str) => Some(ReportFormat::from_str(&fmt_str)?),
76                 Err(_) => None,
77             };
78 
79             if let Some(fmt_str) = matches.value_of(KRILL_CLI_FORMAT_ARG) {
80                 format = Some(ReportFormat::from_str(fmt_str)?);
81             }
82 
83             format.unwrap_or(ReportFormat::Text)
84         };
85 
86         let api = env::var(KRILL_CLI_API_ENV).is_ok() || matches.is_present(KRILL_CLI_API_ARG);
87 
88         Ok(GeneralArgs {
89             server,
90             token,
91             format,
92             api,
93         })
94     }
95 }
96 
97 impl Default for GeneralArgs {
default() -> Self98     fn default() -> Self {
99         GeneralArgs {
100             server: uri::Https::from_str(KRILL_CLI_SERVER_DFLT).unwrap(),
101             token: Token::from(""),
102             format: ReportFormat::Text,
103             api: false,
104         }
105     }
106 }
107 
108 /// This type holds all the necessary data to connect to a Krill daemon, and
109 /// authenticate, and perform a specific action.
110 pub struct Options {
111     pub server: uri::Https,
112     pub token: Token,
113     pub format: ReportFormat,
114     pub api: bool,
115     pub command: Command,
116 }
117 
118 impl Options {
make(general: GeneralArgs, command: Command) -> Self119     fn make(general: GeneralArgs, command: Command) -> Self {
120         Options {
121             server: general.server,
122             token: general.token,
123             format: general.format,
124             api: general.api,
125             command,
126         }
127     }
128 
format(&self) -> ReportFormat129     pub fn format(&self) -> ReportFormat {
130         self.format
131     }
132 
133     /// Creates a new Options explicitly (useful for testing)
new(server: uri::Https, token: &str, format: ReportFormat, command: Command) -> Self134     pub fn new(server: uri::Https, token: &str, format: ReportFormat, command: Command) -> Self {
135         Options {
136             server,
137             token: Token::from(token),
138             format,
139             api: false,
140             command,
141         }
142     }
143 
add_general_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>144     fn add_general_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
145         app.arg(
146             Arg::with_name(KRILL_CLI_SERVER_ARG)
147                 .short("s")
148                 .long(KRILL_CLI_SERVER_ARG)
149                 .value_name("URI")
150                 .help("The full URI to the krill server. Or set env: KRILL_CLI_SERVER")
151                 .required(false),
152         )
153         .arg(
154             Arg::with_name(KRILL_CLI_ADMIN_TOKEN_ARG)
155                 .short("t")
156                 .long(KRILL_CLI_ADMIN_TOKEN_ARG)
157                 .value_name("string")
158                 .help("The secret token for the krill server. Or set env: KRILL_CLI_TOKEN")
159                 .required(false),
160         )
161         .arg(
162             Arg::with_name(KRILL_CLI_FORMAT_ARG)
163                 .short("f")
164                 .long(KRILL_CLI_FORMAT_ARG)
165                 .value_name("type")
166                 .help("Report format: none|json|text (default). Or set env: KRILL_CLI_FORMAT")
167                 .required(false),
168         )
169         .arg(
170             Arg::with_name(KRILL_CLI_API_ARG)
171                 .long(KRILL_CLI_API_ARG)
172                 .help("Only show the API call and exit. Or set env: KRILL_CLI_API=1")
173                 .required(false),
174         )
175     }
176 
add_my_ca_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>177     fn add_my_ca_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
178         app.arg(
179             Arg::with_name(KRILL_CLI_MY_CA_ARG)
180                 .value_name("name")
181                 .short("c")
182                 .long(KRILL_CLI_MY_CA_ARG)
183                 .help("The name of the CA you wish to control. Or set env: KRILL_CLI_MY_CA")
184                 .required(false),
185         )
186     }
187 
add_child_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>188     fn add_child_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
189         app.arg(
190             Arg::with_name("child")
191                 .value_name("name")
192                 .long("child")
193                 .help("The name of the child CA you wish to control")
194                 .required(true),
195         )
196     }
197 
add_resource_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>198     fn add_resource_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
199         app.arg(
200             Arg::with_name("asn")
201                 .short("a")
202                 .long("asn")
203                 .value_name("AS resources")
204                 .help("The AS resources: e.g. AS1, AS3-4")
205                 .required(false),
206         )
207         .arg(
208             Arg::with_name("ipv4")
209                 .short("4")
210                 .long("ipv4")
211                 .value_name("IPv4 resources")
212                 .help("The IPv4 resources: e.g. 192.168.0.0/16")
213                 .required(false),
214         )
215         .arg(
216             Arg::with_name("ipv6")
217                 .short("6")
218                 .long("ipv6")
219                 .value_name("IPv6 resources")
220                 .help("The IPv6 resources: e.g. 2001:db8::/32")
221                 .required(false),
222         )
223     }
224 
add_parent_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>225     fn add_parent_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
226         app.arg(
227             Arg::with_name("parent")
228                 .long("parent")
229                 .short("p")
230                 .value_name("name")
231                 .help("The local name by which the CA refers to this parent")
232                 .required(true),
233         )
234     }
235 
make_config_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>236     fn make_config_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
237         let mut config_sub =
238             SubCommand::with_name("config").about("Creates a configuration file for krill and prints it to STDOUT");
239 
240         fn add_data_dir_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
241             app.arg(
242                 Arg::with_name("data")
243                     .long("data")
244                     .short("d")
245                     .value_name("path")
246                     .help("Override the default path (./data/) for the data directory (must end with '/')")
247                     .required(false),
248             )
249         }
250 
251         fn add_log_file_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
252             app.arg(
253                 Arg::with_name("logfile")
254                     .long("logfile")
255                     .short("l")
256                     .value_name("path")
257                     .help("Override the default path (./krill.log) for the log file")
258                     .required(false),
259             )
260         }
261 
262         #[cfg(feature = "multi-user")]
263         fn add_id_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
264             app.arg(
265                 Arg::with_name("id")
266                     .long("id")
267                     .value_name("id")
268                     .help("Specify the id (e.g. username, email) to generate configuration for")
269                     .required(true),
270             )
271         }
272 
273         #[cfg(feature = "multi-user")]
274         fn add_attr_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
275             app.arg(
276                 Arg::with_name("attr")
277                     .short("a")
278                     .long("attribute")
279                     .value_name("attr")
280                     .help("Specify key=value pair attributes to give the user")
281                     .required(false)
282                     .multiple(true),
283             )
284         }
285 
286         let mut simple = SubCommand::with_name("simple").about("Use a 3rd party repository for publishing");
287 
288         simple = Self::add_general_args(simple);
289         simple = add_data_dir_arg(simple);
290         simple = add_log_file_arg(simple);
291 
292         config_sub = config_sub.subcommand(simple);
293 
294         #[cfg(feature = "multi-user")]
295         {
296             let mut with_user =
297                 SubCommand::with_name("user").about("Generate a user authentication configuration file fragment");
298 
299             with_user = Self::add_general_args(with_user);
300             with_user = add_id_arg(with_user);
301             with_user = add_attr_arg(with_user);
302 
303             config_sub = config_sub.subcommand(with_user);
304         }
305 
306         app.subcommand(config_sub)
307     }
308 
make_cas_list_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>309     fn make_cas_list_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
310         let sub = SubCommand::with_name("list").about("List the current CAs");
311 
312         let sub = Self::add_general_args(sub);
313 
314         app.subcommand(sub)
315     }
316 
make_cas_show_ca_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>317     fn make_cas_show_ca_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
318         let mut sub = SubCommand::with_name("show").about("Show details of a CA");
319 
320         sub = Self::add_general_args(sub);
321         sub = Self::add_my_ca_arg(sub);
322 
323         app.subcommand(sub)
324     }
325 
make_cas_show_history_details_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>326     fn make_cas_show_history_details_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
327         let mut sub = SubCommand::with_name("details").about("Show details for a command in the history of a CA");
328 
329         sub = Self::add_general_args(sub);
330         sub = Self::add_my_ca_arg(sub);
331 
332         sub = sub.arg(
333             Arg::with_name("key")
334                 .long("key")
335                 .value_name("command key string")
336                 .help("The command key as shown in 'history commands'")
337                 .required(true),
338         );
339 
340         app.subcommand(sub)
341     }
342 
make_cas_show_history_list_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>343     fn make_cas_show_history_list_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
344         let mut sub = SubCommand::with_name("commands").about("Show the commands sent to a CA");
345 
346         sub = Self::add_general_args(sub);
347         sub = Self::add_my_ca_arg(sub);
348 
349         sub = sub.arg(
350             Arg::with_name("rows")
351                 .long("rows")
352                 .help("Number of rows (max 250)")
353                 .value_name("<number>")
354                 .required(false),
355         );
356 
357         sub = sub.arg(
358             Arg::with_name("offset")
359                 .long("offset")
360                 .help("Number of results to skip")
361                 .value_name("<number>")
362                 .required(false),
363         );
364 
365         sub = sub.arg(
366             Arg::with_name("after")
367                 .long("after")
368                 .help("Show commands issued after date/time in RFC 3339 format, e.g. 2020-04-09T19:37:02Z")
369                 .value_name("<RFC 3339 DateTime>")
370                 .required(false),
371         );
372 
373         sub = sub.arg(
374             Arg::with_name("before")
375                 .long("before")
376                 .help("Show commands issued after date/time in RFC 3339 format, e.g. 2020-04-09T19:37:02Z")
377                 .value_name("<RFC 3339 DateTime>")
378                 .required(false),
379         );
380 
381         app.subcommand(sub)
382     }
383 
make_cas_show_history_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>384     fn make_cas_show_history_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
385         let mut sub = SubCommand::with_name("history").about("Show the history of a CA");
386 
387         sub = Self::make_cas_show_history_list_sc(sub);
388         sub = Self::make_cas_show_history_details_sc(sub);
389 
390         app.subcommand(sub)
391     }
392 
make_cas_add_ca_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>393     fn make_cas_add_ca_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
394         let mut sub = SubCommand::with_name("add").about("Add a new CA");
395 
396         sub = Self::add_general_args(sub);
397         sub = Self::add_my_ca_arg(sub);
398 
399         app.subcommand(sub)
400     }
401 
make_cas_delete_ca_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>402     fn make_cas_delete_ca_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
403         let mut sub = SubCommand::with_name("delete")
404             .about("Delete a CA and let it withdraw its objects and request revocation. WARNING: Irreversible!");
405 
406         sub = Self::add_general_args(sub);
407         sub = Self::add_my_ca_arg(sub);
408 
409         app.subcommand(sub)
410     }
411 
make_cas_children_add_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>412     fn make_cas_children_add_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
413         let mut sub = SubCommand::with_name("add").about("Add a child to a CA");
414 
415         sub = Self::add_general_args(sub);
416         sub = Self::add_my_ca_arg(sub);
417         sub = Self::add_child_arg(sub);
418         sub = Self::add_resource_args(sub);
419         let sub = sub.arg(
420             Arg::with_name("request")
421                 .long("request")
422                 .short("r")
423                 .help("The location of the RFC8183 Child Request XML file")
424                 .value_name("<XML file>")
425                 .required(true),
426         );
427 
428         app.subcommand(sub)
429     }
430 
make_cas_children_update_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>431     fn make_cas_children_update_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
432         let mut sub = SubCommand::with_name("update").about("Update an existing child of a CA");
433 
434         sub = Self::add_general_args(sub);
435         sub = Self::add_my_ca_arg(sub);
436         sub = Self::add_child_arg(sub);
437         sub = Self::add_resource_args(sub);
438         sub = sub.arg(
439             Arg::with_name("idcert")
440                 .long("idcert")
441                 .help("The child's updated ID certificate")
442                 .value_name("DER encoded certificate")
443                 .required(false),
444         );
445 
446         app.subcommand(sub)
447     }
448 
make_cas_children_response_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>449     fn make_cas_children_response_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
450         let mut sub = SubCommand::with_name("response").about("Show the RFC8183 Parent Response XML");
451 
452         sub = Self::add_general_args(sub);
453         sub = Self::add_my_ca_arg(sub);
454         sub = Self::add_child_arg(sub);
455 
456         app.subcommand(sub)
457     }
458 
make_cas_children_info_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>459     fn make_cas_children_info_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
460         let mut sub = SubCommand::with_name("info").about("Show info for a child (id and resources)");
461 
462         sub = Self::add_general_args(sub);
463         sub = Self::add_my_ca_arg(sub);
464         sub = Self::add_child_arg(sub);
465 
466         app.subcommand(sub)
467     }
468 
make_cas_children_remove_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>469     fn make_cas_children_remove_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
470         let mut sub = SubCommand::with_name("remove").about("Remove an existing child from a CA");
471 
472         sub = Self::add_general_args(sub);
473         sub = Self::add_my_ca_arg(sub);
474         sub = Self::add_child_arg(sub);
475 
476         app.subcommand(sub)
477     }
478 
make_cas_children_connections_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>479     fn make_cas_children_connections_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
480         let mut sub = SubCommand::with_name("connections").about("Show connections stats for children of a CA");
481 
482         sub = Self::add_general_args(sub);
483         sub = Self::add_my_ca_arg(sub);
484 
485         app.subcommand(sub)
486     }
487 
make_cas_children_suspend_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>488     fn make_cas_children_suspend_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
489         let mut sub = SubCommand::with_name("suspend").about("Suspend a child CA: hide certificate(s) issued to child");
490 
491         sub = Self::add_general_args(sub);
492         sub = Self::add_my_ca_arg(sub);
493         sub = Self::add_child_arg(sub);
494 
495         app.subcommand(sub)
496     }
497 
make_cas_children_unsuspend_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>498     fn make_cas_children_unsuspend_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
499         let mut sub =
500             SubCommand::with_name("unsuspend").about("Suspend a child CA: republish certificate(s) issued to child");
501 
502         sub = Self::add_general_args(sub);
503         sub = Self::add_my_ca_arg(sub);
504         sub = Self::add_child_arg(sub);
505 
506         app.subcommand(sub)
507     }
508 
make_cas_children_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>509     fn make_cas_children_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
510         let mut sub = SubCommand::with_name("children").about("Manage children for a CA");
511 
512         sub = Self::make_cas_children_add_sc(sub);
513         sub = Self::make_cas_children_update_sc(sub);
514         sub = Self::make_cas_children_info_sc(sub);
515         sub = Self::make_cas_children_remove_sc(sub);
516         sub = Self::make_cas_children_response_sc(sub);
517         sub = Self::make_cas_children_connections_sc(sub);
518         sub = Self::make_cas_children_suspend_sc(sub);
519         sub = Self::make_cas_children_unsuspend_sc(sub);
520 
521         app.subcommand(sub)
522     }
523 
make_cas_parents_request_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>524     fn make_cas_parents_request_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
525         let mut sub = SubCommand::with_name("request").about("Show RFC8183 Child Request XML");
526 
527         sub = Self::add_general_args(sub);
528         sub = Self::add_my_ca_arg(sub);
529 
530         app.subcommand(sub)
531     }
532 
make_cas_parents_add_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>533     fn make_cas_parents_add_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
534         let mut sub = SubCommand::with_name("add").about("Add a parent to, or update a parent of a CA");
535 
536         sub = Self::add_general_args(sub);
537         sub = Self::add_my_ca_arg(sub);
538         sub = Self::add_parent_arg(sub);
539         sub = sub.arg(
540             Arg::with_name("response")
541                 .long("response")
542                 .short("r")
543                 .help("The location of the RFC8183 Parent Response XML file")
544                 .value_name("<XML file>")
545                 .required(true),
546         );
547 
548         app.subcommand(sub)
549     }
550 
make_cas_parents_statuses_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>551     fn make_cas_parents_statuses_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
552         let mut sub = SubCommand::with_name("statuses").about("Show overview of all parent statuses of a CA");
553 
554         sub = Self::add_general_args(sub);
555         sub = Self::add_my_ca_arg(sub);
556 
557         app.subcommand(sub)
558     }
559 
make_cas_parents_contact_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>560     fn make_cas_parents_contact_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
561         let mut sub = SubCommand::with_name("contact").about("Show contact information for a parent of a CA");
562 
563         sub = Self::add_general_args(sub);
564         sub = Self::add_my_ca_arg(sub);
565         sub = Self::add_parent_arg(sub);
566 
567         app.subcommand(sub)
568     }
569 
make_cas_parents_remove_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>570     fn make_cas_parents_remove_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
571         let mut sub = SubCommand::with_name("remove").about("Remove an existing parent from a CA");
572 
573         sub = Self::add_general_args(sub);
574         sub = Self::add_my_ca_arg(sub);
575         sub = Self::add_parent_arg(sub);
576 
577         app.subcommand(sub)
578     }
579 
make_cas_parents_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>580     fn make_cas_parents_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
581         let mut sub = SubCommand::with_name("parents").about("Manage parents for a CA");
582 
583         sub = Self::make_cas_parents_request_sc(sub);
584         sub = Self::make_cas_parents_add_sc(sub);
585         sub = Self::make_cas_parents_contact_sc(sub);
586         sub = Self::make_cas_parents_statuses_sc(sub);
587         sub = Self::make_cas_parents_remove_sc(sub);
588 
589         app.subcommand(sub)
590     }
591 
make_cas_keyroll_init_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>592     fn make_cas_keyroll_init_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
593         let mut sub = SubCommand::with_name("init").about("Initialize roll for all keys held by a CA");
594 
595         sub = Self::add_general_args(sub);
596         sub = Self::add_my_ca_arg(sub);
597 
598         app.subcommand(sub)
599     }
600 
make_cas_keyroll_activate_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>601     fn make_cas_keyroll_activate_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
602         let mut sub = SubCommand::with_name("activate").about("Finish roll for all keys held by a CA");
603 
604         sub = Self::add_general_args(sub);
605         sub = Self::add_my_ca_arg(sub);
606 
607         app.subcommand(sub)
608     }
609 
make_cas_keyroll_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>610     fn make_cas_keyroll_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
611         let mut sub = SubCommand::with_name("keyroll").about("Perform a manual key rollover for a CA");
612 
613         sub = Self::make_cas_keyroll_init_sc(sub);
614         sub = Self::make_cas_keyroll_activate_sc(sub);
615 
616         app.subcommand(sub)
617     }
618 
make_cas_routes_list_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>619     fn make_cas_routes_list_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
620         let mut sub = SubCommand::with_name("list").about("Show current authorizations");
621 
622         sub = Self::add_general_args(sub);
623         sub = Self::add_my_ca_arg(sub);
624 
625         app.subcommand(sub)
626     }
627 
make_cas_routes_update_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>628     fn make_cas_routes_update_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
629         let mut sub = SubCommand::with_name("update").about("Update authorizations");
630 
631         sub = Self::add_general_args(sub);
632         sub = Self::add_my_ca_arg(sub);
633 
634         sub = sub.arg(
635             Arg::with_name("delta")
636                 .long("delta")
637                 .help(concat!(
638                     "Provide a delta file using the following format:\n",
639                     "# Some comment\n",
640                     "  # Indented comment\n",
641                     "\n", // empty line
642                     "A: 192.168.0.0/16 => 64496 # inline comment\n",
643                     "A: 192.168.1.0/24 => 64496\n",
644                     "R: 192.168.3.0/24 => 64496\n",
645                 ))
646                 .value_name("<file>")
647                 .required(false),
648         );
649 
650         sub = sub.arg(
651             Arg::with_name("add")
652                 .long("add")
653                 .help("One or more ROAs to add, e.g.: 192.168.0.0/16 => 64496")
654                 .value_name("<roa definition>")
655                 .multiple(true)
656                 .required(false),
657         );
658 
659         sub = sub.arg(
660             Arg::with_name("remove")
661                 .long("remove")
662                 .help("One or more ROAs to remove, e.g.: 192.168.0.0/16 => 64496")
663                 .value_name("<roa definition>")
664                 .multiple(true)
665                 .required(false),
666         );
667 
668         sub = sub.arg(
669             Arg::with_name("dryrun")
670                 .long("dryrun")
671                 .help("Perform a dry run of the update and return the BGP analysis for the scoped to the update")
672                 .required(false),
673         );
674 
675         sub = sub.arg(
676             Arg::with_name("try")
677                 .long("try")
678                 .help("Try to perform the update, advice in case it would result in errors or invalids")
679                 .required(false),
680         );
681 
682         app.subcommand(sub)
683     }
684 
make_cas_routes_bgp_full_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>685     fn make_cas_routes_bgp_full_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
686         let mut sub = SubCommand::with_name("analyze").about("Show full report of ROAs vs known BGP announcements");
687 
688         sub = Self::add_general_args(sub);
689         sub = Self::add_my_ca_arg(sub);
690         app.subcommand(sub)
691     }
692 
make_cas_routes_bgp_suggestions_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>693     fn make_cas_routes_bgp_suggestions_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
694         let mut sub = SubCommand::with_name("suggest").about("Show ROA suggestions based on known BGP announcements");
695 
696         sub = Self::add_general_args(sub);
697         sub = Self::add_my_ca_arg(sub);
698 
699         sub = sub
700             .arg(
701                 Arg::with_name("ipv4")
702                     .short("4")
703                     .long("ipv4")
704                     .value_name("IPv4 resources")
705                     .help("Scope to these IPv4 resources")
706                     .required(false),
707             )
708             .arg(
709                 Arg::with_name("ipv6")
710                     .short("6")
711                     .long("ipv6")
712                     .value_name("IPv6 resources")
713                     .help("Scope to these IPv6 resources")
714                     .required(false),
715             );
716 
717         app.subcommand(sub)
718     }
719 
make_cas_routes_bgp_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>720     fn make_cas_routes_bgp_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
721         let mut sub =
722             SubCommand::with_name("bgp").about("Show current authorizations in relation to known announcements");
723 
724         sub = Self::make_cas_routes_bgp_full_sc(sub);
725         sub = Self::make_cas_routes_bgp_suggestions_sc(sub);
726 
727         app.subcommand(sub)
728     }
729 
make_cas_routes_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>730     fn make_cas_routes_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
731         let mut sub = SubCommand::with_name("roas").about("Manage ROAs for a CA");
732 
733         sub = Self::make_cas_routes_list_sc(sub);
734         sub = Self::make_cas_routes_update_sc(sub);
735         sub = Self::make_cas_routes_bgp_sc(sub);
736 
737         app.subcommand(sub)
738     }
739 
740     #[cfg(feature = "aspa")]
make_cas_aspas_add_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>741     fn make_cas_aspas_add_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
742         let mut sub = SubCommand::with_name("add").about("Add or replace an ASPA configuration");
743 
744         sub = Self::add_general_args(sub);
745         sub = Self::add_my_ca_arg(sub);
746 
747         sub = sub.arg(
748             Arg::with_name("aspa")
749                 .long("aspa")
750                 .help("ASPA formatted like: 65000 => 65001, 65002(v4), 65003(v6)")
751                 .value_name("definition")
752                 .required(true),
753         );
754 
755         app.subcommand(sub)
756     }
757 
758     #[cfg(feature = "aspa")]
make_cas_aspas_remove_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>759     fn make_cas_aspas_remove_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
760         let mut sub = SubCommand::with_name("remove").about("Remove the ASPA for a customer ASN");
761 
762         sub = Self::add_general_args(sub);
763         sub = Self::add_my_ca_arg(sub);
764 
765         sub = sub.arg(
766             Arg::with_name("customer")
767                 .long("customer")
768                 .help("Customer ASN for an existing ASPA definition")
769                 .value_name("ASN")
770                 .required(true),
771         );
772 
773         app.subcommand(sub)
774     }
775 
776     #[cfg(feature = "aspa")]
make_cas_aspas_update_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>777     fn make_cas_aspas_update_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
778         let mut sub = SubCommand::with_name("update").about("Update an existing ASPA configuration");
779 
780         sub = Self::add_general_args(sub);
781         sub = Self::add_my_ca_arg(sub);
782 
783         sub = sub.arg(
784             Arg::with_name("customer")
785                 .long("customer")
786                 .help("Customer ASN for an existing ASPA definition")
787                 .value_name("ASN")
788                 .required(true),
789         );
790 
791         sub = sub.arg(
792             Arg::with_name("add")
793                 .long("add")
794                 .help("Provider ASNs to add (multiple allowed)")
795                 .value_name("<Provider AS>")
796                 .multiple(true)
797                 .required(false),
798         );
799 
800         sub = sub.arg(
801             Arg::with_name("remove")
802                 .long("remove")
803                 .help("Provider ASNs to remove (multiple allowed)")
804                 .value_name("<Provider AS>")
805                 .multiple(true)
806                 .required(false),
807         );
808 
809         app.subcommand(sub)
810     }
811 
812     #[cfg(feature = "aspa")]
make_cas_aspas_list_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>813     fn make_cas_aspas_list_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
814         let mut sub = SubCommand::with_name("list").about("Show current ASPA configurations");
815 
816         sub = Self::add_general_args(sub);
817         sub = Self::add_my_ca_arg(sub);
818 
819         app.subcommand(sub)
820     }
821 
822     #[cfg(feature = "aspa")]
make_cas_aspas_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>823     fn make_cas_aspas_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
824         let mut sub = SubCommand::with_name("aspas").about("Manage ASPAs for a CA (experimental)");
825 
826         sub = Self::make_cas_aspas_add_sc(sub);
827         sub = Self::make_cas_aspas_remove_sc(sub);
828         sub = Self::make_cas_aspas_update_sc(sub);
829         sub = Self::make_cas_aspas_list_sc(sub);
830 
831         app.subcommand(sub)
832     }
833 
make_cas_repo_request_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>834     fn make_cas_repo_request_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
835         let mut sub = SubCommand::with_name("request").about("Show RFC8183 Publisher Request XML");
836 
837         sub = Self::add_general_args(sub);
838         sub = Self::add_my_ca_arg(sub);
839 
840         app.subcommand(sub)
841     }
842 
make_cas_repo_show_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>843     fn make_cas_repo_show_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
844         let mut sub = SubCommand::with_name("show").about("Show current repo config");
845 
846         sub = Self::add_general_args(sub);
847         sub = Self::add_my_ca_arg(sub);
848 
849         app.subcommand(sub)
850     }
851 
make_cas_repo_status_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>852     fn make_cas_repo_status_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
853         let mut sub = SubCommand::with_name("status").about("Show current repo status");
854 
855         sub = Self::add_general_args(sub);
856         sub = Self::add_my_ca_arg(sub);
857 
858         app.subcommand(sub)
859     }
860 
make_cas_repo_configure_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>861     fn make_cas_repo_configure_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
862         let mut sub = SubCommand::with_name("configure").about("Configure which repository a CA uses");
863 
864         sub = Self::add_general_args(sub);
865         sub = Self::add_my_ca_arg(sub);
866         sub = sub.arg(
867             Arg::with_name("response")
868                 .value_name("file")
869                 .long("response")
870                 .short("r")
871                 .help("The location of the RFC8183 Publisher Response XML file")
872                 .required(true),
873         );
874 
875         app.subcommand(sub)
876     }
877 
make_cas_repo_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>878     fn make_cas_repo_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
879         let mut sub = SubCommand::with_name("repo").about("Manage the repository for a CA");
880 
881         sub = Self::make_cas_repo_request_sc(sub);
882         sub = Self::make_cas_repo_show_sc(sub);
883         sub = Self::make_cas_repo_status_sc(sub);
884         sub = Self::make_cas_repo_configure_sc(sub);
885 
886         app.subcommand(sub)
887     }
888 
make_cas_issues_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>889     fn make_cas_issues_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
890         let mut sub = SubCommand::with_name("issues").about("Show issues for a CA");
891 
892         sub = Self::add_general_args(sub);
893         sub = Self::add_my_ca_arg(sub);
894 
895         app.subcommand(sub)
896     }
897 
898     #[cfg(feature = "rta")]
make_cas_rta_list<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>899     fn make_cas_rta_list<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
900         let mut sub = SubCommand::with_name("list").about("List RTAs");
901 
902         sub = Self::add_general_args(sub);
903         sub = Self::add_my_ca_arg(sub);
904 
905         app.subcommand(sub)
906     }
907 
908     #[cfg(feature = "rta")]
make_cas_rta_show<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>909     fn make_cas_rta_show<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
910         let mut sub = SubCommand::with_name("show").about("Show RTA");
911 
912         sub = Self::add_general_args(sub);
913         sub = Self::add_my_ca_arg(sub);
914 
915         sub = sub.arg(
916             Arg::with_name("name")
917                 .long("name")
918                 .short("n")
919                 .value_name("string")
920                 .help("Your local name for this RTA")
921                 .required(true),
922         );
923 
924         sub = sub.arg(
925             Arg::with_name("out")
926                 .long("out")
927                 .short("o")
928                 .value_name("path")
929                 .help("File to write RTA to")
930                 .required(true),
931         );
932 
933         app.subcommand(sub)
934     }
935 
936     #[cfg(feature = "rta")]
make_cas_rta_sign_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>937     fn make_cas_rta_sign_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
938         let mut sub = SubCommand::with_name("sign").about("Create an RTA signed by a CA");
939 
940         sub = Self::add_general_args(sub);
941         sub = Self::add_my_ca_arg(sub);
942 
943         sub = Self::add_resource_args(sub);
944 
945         sub = sub.arg(
946             Arg::with_name("days")
947                 .long("days")
948                 .short("d")
949                 .value_name("number of days")
950                 .help("Validity time of the RTA in days")
951                 .required(true),
952         );
953 
954         sub = sub.arg(
955             Arg::with_name("in")
956                 .long("in")
957                 .short("i")
958                 .value_name("path")
959                 .help("Content which needs to be signed")
960                 .required(true),
961         );
962 
963         sub = sub.arg(
964             Arg::with_name("name")
965                 .long("name")
966                 .short("n")
967                 .value_name("string")
968                 .help("Your local name for this RTA")
969                 .required(true),
970         );
971 
972         sub = sub.arg(
973             Arg::with_name("keys")
974                 .long("keys")
975                 .short("k")
976                 .value_name("hex encoded key identifiers")
977                 .multiple(true)
978                 .help("Optional additional keys to include in this RTA")
979                 .required(false),
980         );
981 
982         app.subcommand(sub)
983     }
984 
985     #[cfg(feature = "rta")]
make_cas_rta_multi_prep_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>986     fn make_cas_rta_multi_prep_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
987         let mut sub = SubCommand::with_name("prep").about("Prepare keys for multi-signed RTA");
988 
989         sub = Self::add_general_args(sub);
990         sub = Self::add_my_ca_arg(sub);
991 
992         sub = Self::add_resource_args(sub);
993 
994         sub = sub.arg(
995             Arg::with_name("days")
996                 .long("days")
997                 .short("d")
998                 .value_name("number of days")
999                 .help("Validity time of the RTA in days")
1000                 .required(true),
1001         );
1002 
1003         sub = sub.arg(
1004             Arg::with_name("name")
1005                 .long("name")
1006                 .short("n")
1007                 .value_name("string")
1008                 .help("Your local name for this RTA")
1009                 .required(true),
1010         );
1011 
1012         app.subcommand(sub)
1013     }
1014 
1015     #[cfg(feature = "rta")]
make_cas_rta_multi_sign_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1016     fn make_cas_rta_multi_sign_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1017         let mut sub = SubCommand::with_name("cosign").about("Co-sign an existing (prepared) RTA");
1018 
1019         sub = Self::add_general_args(sub);
1020         sub = Self::add_my_ca_arg(sub);
1021 
1022         sub = sub.arg(
1023             Arg::with_name("name")
1024                 .long("name")
1025                 .short("n")
1026                 .value_name("string")
1027                 .help("Your local name for this RTA")
1028                 .required(true),
1029         );
1030 
1031         sub = sub.arg(
1032             Arg::with_name("in")
1033                 .long("in")
1034                 .short("i")
1035                 .value_name("path")
1036                 .help("RTA which needs to be co-signed")
1037                 .required(true),
1038         );
1039 
1040         app.subcommand(sub)
1041     }
1042 
1043     #[cfg(feature = "rta")]
make_cas_rta_multi_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1044     fn make_cas_rta_multi_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1045         let mut sub = SubCommand::with_name("multi").about("Manage RTA signed by multiple parties");
1046 
1047         sub = Self::make_cas_rta_multi_prep_sc(sub);
1048         sub = Self::make_cas_rta_multi_sign_sc(sub);
1049 
1050         app.subcommand(sub)
1051     }
1052 
1053     #[cfg(feature = "rta")]
make_cas_rta_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1054     fn make_cas_rta_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1055         let mut sub = SubCommand::with_name("rta").about("Manage Resource Tagged Attestations");
1056         sub = Self::make_cas_rta_list(sub);
1057         sub = Self::make_cas_rta_show(sub);
1058         sub = Self::make_cas_rta_sign_sc(sub);
1059         sub = Self::make_cas_rta_multi_sc(sub);
1060         app.subcommand(sub)
1061     }
1062 
make_bulk_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1063     fn make_bulk_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1064         let mut sub = SubCommand::with_name("bulk").about("Manually trigger refresh/republish/resync for all CAs");
1065 
1066         let mut refresh =
1067             SubCommand::with_name("refresh").about("Force that all CAs ask their parents for updated certificates");
1068         refresh = Self::add_general_args(refresh);
1069 
1070         let mut republish = SubCommand::with_name("publish")
1071             .about("Force that all CAs create new objects if needed (in which case they will also sync)");
1072         republish = Self::add_general_args(republish);
1073 
1074         let mut resync = SubCommand::with_name("sync").about("Force that all CAs sync with their repo server");
1075         resync = Self::add_general_args(resync);
1076 
1077         sub = sub.subcommand(refresh).subcommand(republish).subcommand(resync);
1078 
1079         app.subcommand(sub)
1080     }
1081 
make_health_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1082     fn make_health_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1083         let health = SubCommand::with_name("health").about("Perform an authenticated health check");
1084         let health = Self::add_general_args(health);
1085         app.subcommand(health)
1086     }
1087 
make_info_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1088     fn make_info_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1089         let info = SubCommand::with_name("info").about("Show server info");
1090         let info = Self::add_general_args(info);
1091         app.subcommand(info)
1092     }
1093 
make_publishers_list_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1094     fn make_publishers_list_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1095         let mut sub = SubCommand::with_name("list").about("List all publishers");
1096         sub = Options::add_general_args(sub);
1097         app.subcommand(sub)
1098     }
1099 
make_publishers_stale_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1100     fn make_publishers_stale_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1101         let mut sub = SubCommand::with_name("stale").about("List all publishers which have not published in a while");
1102         sub = Options::add_general_args(sub);
1103         sub = sub.arg(
1104             Arg::with_name("seconds")
1105                 .value_name("seconds")
1106                 .long("seconds")
1107                 .help("The number of seconds since last publication")
1108                 .required(true),
1109         );
1110         app.subcommand(sub)
1111     }
1112 
add_publisher_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1113     fn add_publisher_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1114         app.arg(
1115             Arg::with_name("publisher")
1116                 .value_name("handle")
1117                 .short("p")
1118                 .long("publisher")
1119                 .help("The handle (name) of the publisher")
1120                 .required(true),
1121         )
1122     }
1123 
add_rsync_base_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1124     fn add_rsync_base_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1125         app.arg(
1126             Arg::with_name("rsync")
1127                 .long("rsync")
1128                 .value_name("uri")
1129                 .help("Specify the base rsync URI for the repository, must end with '/'")
1130                 .required(true),
1131         )
1132     }
1133 
add_rrdp_base_uri_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1134     fn add_rrdp_base_uri_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1135         app.arg(
1136             Arg::with_name("rrdp")
1137                 .long("rrdp")
1138                 .value_name("uri")
1139                 .help(
1140                     "Specify the base https URI for the RRDP (excluding notification.xml), \
1141                     must \
1142                     end with '/'",
1143                 )
1144                 .required(true),
1145         )
1146     }
1147 
make_publishers_add_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1148     fn make_publishers_add_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1149         let mut sub = SubCommand::with_name("add").about("Add a publisher");
1150         sub = Options::add_general_args(sub);
1151 
1152         sub = sub
1153             .arg(
1154                 Arg::with_name("request")
1155                     .value_name("file")
1156                     .long("request")
1157                     .short("r")
1158                     .help("The location of the RFC8183 Publisher Request XML file")
1159                     .required(true),
1160             )
1161             .arg(
1162                 Arg::with_name("publisher")
1163                     .value_name("handle")
1164                     .short("p")
1165                     .long("publisher")
1166                     .help("Override the publisher handle in the XML")
1167                     .required(false),
1168             );
1169 
1170         app.subcommand(sub)
1171     }
1172 
make_publishers_remove_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1173     fn make_publishers_remove_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1174         let mut sub = SubCommand::with_name("remove").about("Remove a publisher");
1175         sub = Options::add_general_args(sub);
1176         sub = Self::add_publisher_arg(sub);
1177         app.subcommand(sub)
1178     }
1179 
make_publishers_show_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1180     fn make_publishers_show_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1181         let mut sub = SubCommand::with_name("show").about("Show details for a publisher");
1182         sub = Options::add_general_args(sub);
1183         sub = Self::add_publisher_arg(sub);
1184         app.subcommand(sub)
1185     }
1186 
make_publishers_response_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1187     fn make_publishers_response_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1188         let mut sub = SubCommand::with_name("response").about("Show RFC8183 Repository Response XML");
1189         sub = Options::add_general_args(sub);
1190         sub = Self::add_publisher_arg(sub);
1191         app.subcommand(sub)
1192     }
1193 
make_publication_server_stats_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1194     fn make_publication_server_stats_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1195         let mut sub = SubCommand::with_name("stats").about("Show publication server stats");
1196         sub = Options::add_general_args(sub);
1197         app.subcommand(sub)
1198     }
1199 
make_publication_server_init_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1200     fn make_publication_server_init_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1201         let mut sub = SubCommand::with_name("init").about("Initialize publication server");
1202         sub = Options::add_general_args(sub);
1203 
1204         sub = Self::add_rsync_base_arg(sub);
1205         sub = Self::add_rrdp_base_uri_arg(sub);
1206 
1207         app.subcommand(sub)
1208     }
1209 
make_publication_server_clear_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1210     fn make_publication_server_clear_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1211         let mut sub = SubCommand::with_name("clear").about("Clear the publication server so it can re-initialized");
1212         sub = Options::add_general_args(sub);
1213         app.subcommand(sub)
1214     }
1215 
make_publication_server_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1216     fn make_publication_server_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1217         let mut sub = SubCommand::with_name("server").about("Manage the Publication Server (init/stats)");
1218         sub = Self::make_publication_server_stats_sc(sub);
1219         sub = Self::make_publication_server_init_sc(sub);
1220         sub = Self::make_publication_server_clear_sc(sub);
1221         app.subcommand(sub)
1222     }
1223 
make_publishers_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1224     fn make_publishers_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1225         let mut sub = SubCommand::with_name("publishers").about("Manage the publishers in your Publication Server");
1226 
1227         sub = Self::make_publishers_list_sc(sub);
1228         sub = Self::make_publishers_stale_sc(sub);
1229         sub = Self::make_publishers_add_sc(sub);
1230         sub = Self::make_publishers_remove_sc(sub);
1231         sub = Self::make_publishers_show_sc(sub);
1232         sub = Self::make_publishers_response_sc(sub);
1233 
1234         app.subcommand(sub)
1235     }
1236 
make_pubserver_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>1237     fn make_pubserver_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
1238         let mut sub = SubCommand::with_name("pubserver")
1239             .about("Manage your Publication Server (only needed if you run your own)");
1240 
1241         sub = Self::make_publishers_sc(sub);
1242         sub = Self::make_publication_server_sc(sub);
1243 
1244         app.subcommand(sub)
1245     }
1246 
make_matches<'a>() -> ArgMatches<'a>1247     fn make_matches<'a>() -> ArgMatches<'a> {
1248         let mut app = App::new(KRILL_CLIENT_APP).version(KRILL_VERSION);
1249 
1250         app = Self::make_config_sc(app);
1251         app = Self::make_cas_list_sc(app);
1252         app = Self::make_cas_show_ca_sc(app);
1253         app = Self::make_cas_show_history_sc(app);
1254         app = Self::make_cas_add_ca_sc(app);
1255         app = Self::make_cas_delete_ca_sc(app);
1256         app = Self::make_cas_children_sc(app);
1257         app = Self::make_cas_parents_sc(app);
1258         app = Self::make_cas_keyroll_sc(app);
1259         app = Self::make_cas_routes_sc(app);
1260         app = Self::make_cas_repo_sc(app);
1261         app = Self::make_cas_issues_sc(app);
1262         app = Self::make_pubserver_sc(app);
1263 
1264         #[cfg(feature = "aspa")]
1265         {
1266             app = Self::make_cas_aspas_sc(app);
1267         }
1268 
1269         #[cfg(feature = "rta")]
1270         {
1271             app = Self::make_cas_rta_sc(app);
1272         }
1273 
1274         app = Self::make_health_sc(app);
1275 
1276         app = Self::make_info_sc(app);
1277 
1278         app = Self::make_bulk_sc(app);
1279 
1280         app.get_matches()
1281     }
1282 
1283     //---------------------- Parsing
1284 
read_file_arg(path: &str) -> Result<Bytes, Error>1285     fn read_file_arg(path: &str) -> Result<Bytes, Error> {
1286         let path = PathBuf::from(path);
1287         file::read(&path).map_err(Error::IoError)
1288     }
1289 
parse_my_ca(matches: &ArgMatches) -> Result<Handle, Error>1290     fn parse_my_ca(matches: &ArgMatches) -> Result<Handle, Error> {
1291         let my_ca = {
1292             let mut my_ca = None;
1293 
1294             if let Ok(my_ca_env) = env::var(KRILL_CLI_MY_CA_ENV) {
1295                 my_ca = Some(Handle::from_str(&my_ca_env).map_err(|_| Error::InvalidHandle)?);
1296             }
1297 
1298             if let Some(my_ca_str) = matches.value_of(KRILL_CLI_MY_CA_ARG) {
1299                 my_ca = Some(Handle::from_str(my_ca_str).map_err(|_| Error::InvalidHandle)?);
1300             }
1301 
1302             my_ca.ok_or_else(|| Error::missing_arg_with_env(KRILL_CLI_MY_CA_ARG, KRILL_CLI_MY_CA_ENV))?
1303         };
1304 
1305         Ok(my_ca)
1306     }
1307 
parse_resource_args(matches: &ArgMatches) -> Result<Option<ResourceSet>, Error>1308     fn parse_resource_args(matches: &ArgMatches) -> Result<Option<ResourceSet>, Error> {
1309         let asn = matches.value_of("asn");
1310         let v4 = matches.value_of("ipv4");
1311         let v6 = matches.value_of("ipv6");
1312 
1313         if asn.is_some() || v4.is_some() || v6.is_some() {
1314             let asn = asn.unwrap_or("");
1315             let v4 = v4.unwrap_or("");
1316             let v6 = v6.unwrap_or("");
1317 
1318             Ok(Some(ResourceSet::from_strs(asn, v4, v6)?))
1319         } else {
1320             Ok(None)
1321         }
1322     }
1323 
parse_matches_simple_config(matches: &ArgMatches) -> Result<Options, Error>1324     fn parse_matches_simple_config(matches: &ArgMatches) -> Result<Options, Error> {
1325         let general_args = GeneralArgs::from_matches(matches)?;
1326 
1327         #[cfg(not(feature = "multi-user"))]
1328         let mut details = KrillInitDetails::default();
1329 
1330         #[cfg(feature = "multi-user")]
1331         let mut details = KrillInitDetails::multi_user_dflt();
1332 
1333         if let Some(data) = matches.value_of("data") {
1334             if !data.ends_with('/') {
1335                 return Err(Error::general("Path for --data MUST end with a '/'"));
1336             }
1337             details.with_data_dir(data);
1338         }
1339         if let Some(log_file) = matches.value_of("logfile") {
1340             details.with_log_file(log_file);
1341         }
1342 
1343         let command = Command::Init(details);
1344         Ok(Options::make(general_args, command))
1345     }
1346 
1347     #[cfg(feature = "multi-user")]
parse_matches_user_config(matches: &ArgMatches) -> Result<Options, Error>1348     fn parse_matches_user_config(matches: &ArgMatches) -> Result<Options, Error> {
1349         let general_args = GeneralArgs::default();
1350         let mut details = KrillUserDetails::default();
1351         if let Some(id) = matches.value_of("id") {
1352             details.with_id(id.to_string());
1353         }
1354         if let Some(attr_iter) = matches.values_of("attr") {
1355             for attr in attr_iter {
1356                 let mut iter = attr.split('=');
1357                 let k = iter
1358                     .next()
1359                     .ok_or_else(|| Error::general(&format!("attribute '{}' must be of the form key=value", attr)))?;
1360                 let v = iter
1361                     .next()
1362                     .ok_or_else(|| Error::general(&format!("attribute '{}' must be of the form key=value", attr)))?;
1363                 details.with_attr(k.to_string(), v.to_string());
1364             }
1365         }
1366         let command = Command::User(details);
1367         Ok(Options::make(general_args, command))
1368     }
1369 
1370     #[cfg(not(feature = "multi-user"))]
parse_matches_user_config(_: &ArgMatches) -> Result<Options, Error>1371     fn parse_matches_user_config(_: &ArgMatches) -> Result<Options, Error> {
1372         Err(Error::UnrecognizedSubCommand)
1373     }
1374 
parse_matches_config(matches: &ArgMatches) -> Result<Options, Error>1375     fn parse_matches_config(matches: &ArgMatches) -> Result<Options, Error> {
1376         if let Some(m) = matches.subcommand_matches("simple") {
1377             Self::parse_matches_simple_config(m)
1378         } else if let Some(m) = matches.subcommand_matches("user") {
1379             Self::parse_matches_user_config(m)
1380         } else {
1381             Err(Error::UnrecognizedSubCommand)
1382         }
1383     }
1384 
parse_matches_cas_list(matches: &ArgMatches) -> Result<Options, Error>1385     fn parse_matches_cas_list(matches: &ArgMatches) -> Result<Options, Error> {
1386         let general_args = GeneralArgs::from_matches(matches)?;
1387         let command = Command::CertAuth(CaCommand::List);
1388         Ok(Options::make(general_args, command))
1389     }
1390 
parse_matches_cas_add(matches: &ArgMatches) -> Result<Options, Error>1391     fn parse_matches_cas_add(matches: &ArgMatches) -> Result<Options, Error> {
1392         let general_args = GeneralArgs::from_matches(matches)?;
1393         let my_ca = Self::parse_my_ca(matches)?;
1394 
1395         let init = CertAuthInit::new(my_ca);
1396 
1397         let command = Command::CertAuth(CaCommand::Init(init));
1398 
1399         Ok(Options::make(general_args, command))
1400     }
1401 
parse_matches_cas_delete(matches: &ArgMatches) -> Result<Options, Error>1402     fn parse_matches_cas_delete(matches: &ArgMatches) -> Result<Options, Error> {
1403         let general_args = GeneralArgs::from_matches(matches)?;
1404         let my_ca = Self::parse_my_ca(matches)?;
1405 
1406         let command = Command::CertAuth(CaCommand::Delete(my_ca));
1407 
1408         Ok(Options::make(general_args, command))
1409     }
1410 
parse_matches_cas_show(matches: &ArgMatches) -> Result<Options, Error>1411     fn parse_matches_cas_show(matches: &ArgMatches) -> Result<Options, Error> {
1412         let general_args = GeneralArgs::from_matches(matches)?;
1413         let my_ca = Self::parse_my_ca(matches)?;
1414 
1415         let command = Command::CertAuth(CaCommand::Show(my_ca));
1416         Ok(Options::make(general_args, command))
1417     }
1418 
parse_matches_cas_history_details(matches: &ArgMatches) -> Result<Options, Error>1419     fn parse_matches_cas_history_details(matches: &ArgMatches) -> Result<Options, Error> {
1420         let general_args = GeneralArgs::from_matches(matches)?;
1421         let my_ca = Self::parse_my_ca(matches)?;
1422         let key = matches.value_of("key").unwrap();
1423 
1424         let command = Command::CertAuth(CaCommand::ShowHistoryDetails(my_ca, key.to_string()));
1425         Ok(Options::make(general_args, command))
1426     }
1427 
parse_matches_cas_history_commands(matches: &ArgMatches) -> Result<Options, Error>1428     fn parse_matches_cas_history_commands(matches: &ArgMatches) -> Result<Options, Error> {
1429         let general_args = GeneralArgs::from_matches(matches)?;
1430         let my_ca = Self::parse_my_ca(matches)?;
1431 
1432         let mut options = HistoryOptions::default();
1433 
1434         if let Some(offset) = matches.value_of("offset") {
1435             let offset =
1436                 u64::from_str(offset).map_err(|e| Error::general(&format!("invalid number: {}", e.to_string())))?;
1437             options.offset = offset
1438         }
1439 
1440         if let Some(rows) = matches.value_of("rows") {
1441             let rows =
1442                 u64::from_str(rows).map_err(|e| Error::general(&format!("invalid number: {}", e.to_string())))?;
1443             if rows > 250 {
1444                 return Err(Error::general("No more than 250 rows allowed in history"));
1445             }
1446             options.rows = rows
1447         }
1448 
1449         if let Some(after) = matches.value_of("after") {
1450             let time = Time::from_str(after)
1451                 .map_err(|e| Error::general(&format!("invalid date format: {}", e.to_string())))?;
1452             options.after = Some(time);
1453         }
1454 
1455         if let Some(after) = matches.value_of("before") {
1456             let time = Time::from_str(after)
1457                 .map_err(|e| Error::general(&format!("invalid date format: {}", e.to_string())))?;
1458             options.before = Some(time);
1459         }
1460 
1461         let command = Command::CertAuth(CaCommand::ShowHistoryCommands(my_ca, options));
1462         Ok(Options::make(general_args, command))
1463     }
1464 
parse_matches_cas_history(matches: &ArgMatches) -> Result<Options, Error>1465     fn parse_matches_cas_history(matches: &ArgMatches) -> Result<Options, Error> {
1466         if let Some(m) = matches.subcommand_matches("commands") {
1467             Self::parse_matches_cas_history_commands(m)
1468         } else if let Some(m) = matches.subcommand_matches("details") {
1469             Self::parse_matches_cas_history_details(m)
1470         } else {
1471             Err(Error::UnrecognizedSubCommand)
1472         }
1473     }
1474 
parse_matches_cas_children_add(matches: &ArgMatches) -> Result<Options, Error>1475     fn parse_matches_cas_children_add(matches: &ArgMatches) -> Result<Options, Error> {
1476         let path = matches.value_of("request").unwrap();
1477         let bytes = Self::read_file_arg(path)?;
1478         let child_request = rfc8183::ChildRequest::validate(bytes.as_ref())?;
1479 
1480         let general_args = GeneralArgs::from_matches(matches)?;
1481         let my_ca = Self::parse_my_ca(matches)?;
1482 
1483         let child = matches.value_of("child").unwrap();
1484         let child = Handle::from_str(child).map_err(|_| Error::InvalidHandle)?;
1485 
1486         let resources = Self::parse_resource_args(matches)?.ok_or(Error::MissingResources)?;
1487 
1488         let (_, _, id_cert) = child_request.unpack();
1489         let add_child_request = AddChildRequest::new(child, resources, id_cert);
1490         let command = Command::CertAuth(CaCommand::ChildAdd(my_ca, add_child_request));
1491         Ok(Options::make(general_args, command))
1492     }
1493 
parse_matches_cas_children_update(matches: &ArgMatches) -> Result<Options, Error>1494     fn parse_matches_cas_children_update(matches: &ArgMatches) -> Result<Options, Error> {
1495         let general_args = GeneralArgs::from_matches(matches)?;
1496         let my_ca = Self::parse_my_ca(matches)?;
1497 
1498         let child = matches.value_of("child").unwrap();
1499         let child = Handle::from_str(child).map_err(|_| Error::InvalidHandle)?;
1500 
1501         let id_cert = {
1502             if let Some(path) = matches.value_of("idcert") {
1503                 let bytes = Self::read_file_arg(path)?;
1504                 let id_cert = IdCert::decode(bytes.as_ref()).map_err(|_| Error::InvalidChildIdCert)?;
1505                 Some(id_cert)
1506             } else {
1507                 None
1508             }
1509         };
1510         let resources = Self::parse_resource_args(matches)?;
1511 
1512         let update = UpdateChildRequest::new(id_cert, resources, None);
1513 
1514         let command = Command::CertAuth(CaCommand::ChildUpdate(my_ca, child, update));
1515         Ok(Options::make(general_args, command))
1516     }
1517 
parse_matches_cas_children_info(matches: &ArgMatches) -> Result<Options, Error>1518     fn parse_matches_cas_children_info(matches: &ArgMatches) -> Result<Options, Error> {
1519         let general_args = GeneralArgs::from_matches(matches)?;
1520         let my_ca = Self::parse_my_ca(matches)?;
1521 
1522         let child = matches.value_of("child").unwrap();
1523         let child = Handle::from_str(child).map_err(|_| Error::InvalidHandle)?;
1524 
1525         let command = Command::CertAuth(CaCommand::ChildInfo(my_ca, child));
1526         Ok(Options::make(general_args, command))
1527     }
1528 
parse_matches_cas_children_response(matches: &ArgMatches) -> Result<Options, Error>1529     fn parse_matches_cas_children_response(matches: &ArgMatches) -> Result<Options, Error> {
1530         let general_args = GeneralArgs::from_matches(matches)?;
1531         let my_ca = Self::parse_my_ca(matches)?;
1532 
1533         let child = matches.value_of("child").unwrap();
1534         let child = Handle::from_str(child).map_err(|_| Error::InvalidHandle)?;
1535 
1536         let command = Command::CertAuth(CaCommand::ParentResponse(my_ca, child));
1537         Ok(Options::make(general_args, command))
1538     }
1539 
parse_matches_cas_children_remove(matches: &ArgMatches) -> Result<Options, Error>1540     fn parse_matches_cas_children_remove(matches: &ArgMatches) -> Result<Options, Error> {
1541         let general_args = GeneralArgs::from_matches(matches)?;
1542         let my_ca = Self::parse_my_ca(matches)?;
1543 
1544         let child = matches.value_of("child").unwrap();
1545         let child = Handle::from_str(child).map_err(|_| Error::InvalidHandle)?;
1546 
1547         let command = Command::CertAuth(CaCommand::ChildDelete(my_ca, child));
1548         Ok(Options::make(general_args, command))
1549     }
1550 
parse_matches_cas_children_connections(matches: &ArgMatches) -> Result<Options, Error>1551     fn parse_matches_cas_children_connections(matches: &ArgMatches) -> Result<Options, Error> {
1552         let general_args = GeneralArgs::from_matches(matches)?;
1553         let my_ca = Self::parse_my_ca(matches)?;
1554 
1555         let command = Command::CertAuth(CaCommand::ChildConnections(my_ca));
1556         Ok(Options::make(general_args, command))
1557     }
1558 
parse_matches_cas_children_suspend(matches: &ArgMatches) -> Result<Options, Error>1559     fn parse_matches_cas_children_suspend(matches: &ArgMatches) -> Result<Options, Error> {
1560         let general_args = GeneralArgs::from_matches(matches)?;
1561         let my_ca = Self::parse_my_ca(matches)?;
1562 
1563         let child = matches.value_of("child").unwrap();
1564         let child = Handle::from_str(child).map_err(|_| Error::InvalidHandle)?;
1565 
1566         let update = UpdateChildRequest::suspend();
1567 
1568         let command = Command::CertAuth(CaCommand::ChildUpdate(my_ca, child, update));
1569         Ok(Options::make(general_args, command))
1570     }
1571 
parse_matches_cas_children_unsuspend(matches: &ArgMatches) -> Result<Options, Error>1572     fn parse_matches_cas_children_unsuspend(matches: &ArgMatches) -> Result<Options, Error> {
1573         let general_args = GeneralArgs::from_matches(matches)?;
1574         let my_ca = Self::parse_my_ca(matches)?;
1575 
1576         let child = matches.value_of("child").unwrap();
1577         let child = Handle::from_str(child).map_err(|_| Error::InvalidHandle)?;
1578 
1579         let update = UpdateChildRequest::unsuspend();
1580 
1581         let command = Command::CertAuth(CaCommand::ChildUpdate(my_ca, child, update));
1582         Ok(Options::make(general_args, command))
1583     }
1584 
parse_matches_cas_children(matches: &ArgMatches) -> Result<Options, Error>1585     fn parse_matches_cas_children(matches: &ArgMatches) -> Result<Options, Error> {
1586         if let Some(m) = matches.subcommand_matches("add") {
1587             Self::parse_matches_cas_children_add(m)
1588         } else if let Some(m) = matches.subcommand_matches("response") {
1589             Self::parse_matches_cas_children_response(m)
1590         } else if let Some(m) = matches.subcommand_matches("info") {
1591             Self::parse_matches_cas_children_info(m)
1592         } else if let Some(m) = matches.subcommand_matches("update") {
1593             Self::parse_matches_cas_children_update(m)
1594         } else if let Some(m) = matches.subcommand_matches("remove") {
1595             Self::parse_matches_cas_children_remove(m)
1596         } else if let Some(m) = matches.subcommand_matches("connections") {
1597             Self::parse_matches_cas_children_connections(m)
1598         } else if let Some(m) = matches.subcommand_matches("suspend") {
1599             Self::parse_matches_cas_children_suspend(m)
1600         } else if let Some(m) = matches.subcommand_matches("unsuspend") {
1601             Self::parse_matches_cas_children_unsuspend(m)
1602         } else {
1603             Err(Error::UnrecognizedSubCommand)
1604         }
1605     }
1606 
parse_matches_cas_parents_request(matches: &ArgMatches) -> Result<Options, Error>1607     fn parse_matches_cas_parents_request(matches: &ArgMatches) -> Result<Options, Error> {
1608         let general_args = GeneralArgs::from_matches(matches)?;
1609         let my_ca = Self::parse_my_ca(matches)?;
1610 
1611         let command = Command::CertAuth(CaCommand::ChildRequest(my_ca));
1612 
1613         Ok(Options::make(general_args, command))
1614     }
1615 
parse_matches_cas_parents_add(matches: &ArgMatches) -> Result<Options, Error>1616     fn parse_matches_cas_parents_add(matches: &ArgMatches) -> Result<Options, Error> {
1617         let path = matches.value_of("response").unwrap();
1618         let bytes = Self::read_file_arg(path)?;
1619         let response = rfc8183::ParentResponse::validate(bytes.as_ref())?;
1620 
1621         let general_args = GeneralArgs::from_matches(matches)?;
1622         let my_ca = Self::parse_my_ca(matches)?;
1623 
1624         let parent = matches.value_of("parent").unwrap();
1625         let parent = Handle::from_str(parent).map_err(|_| Error::InvalidHandle)?;
1626         let contact = ParentCaContact::for_rfc6492(response);
1627         let parent_req = ParentCaReq::new(parent, contact);
1628 
1629         let command = Command::CertAuth(CaCommand::AddParent(my_ca, parent_req));
1630         Ok(Options::make(general_args, command))
1631     }
1632 
parse_matches_cas_parents_info(matches: &ArgMatches) -> Result<Options, Error>1633     fn parse_matches_cas_parents_info(matches: &ArgMatches) -> Result<Options, Error> {
1634         let general_args = GeneralArgs::from_matches(matches)?;
1635         let my_ca = Self::parse_my_ca(matches)?;
1636         let parent = matches.value_of("parent").unwrap();
1637         let parent = Handle::from_str(parent).map_err(|_| Error::InvalidHandle)?;
1638 
1639         let command = Command::CertAuth(CaCommand::MyParentCaContact(my_ca, parent));
1640         Ok(Options::make(general_args, command))
1641     }
1642 
parse_matches_cas_parents_statuses(matches: &ArgMatches) -> Result<Options, Error>1643     fn parse_matches_cas_parents_statuses(matches: &ArgMatches) -> Result<Options, Error> {
1644         let general_args = GeneralArgs::from_matches(matches)?;
1645         let my_ca = Self::parse_my_ca(matches)?;
1646 
1647         let command = Command::CertAuth(CaCommand::ParentStatuses(my_ca));
1648         Ok(Options::make(general_args, command))
1649     }
1650 
parse_matches_cas_parents_remove(matches: &ArgMatches) -> Result<Options, Error>1651     fn parse_matches_cas_parents_remove(matches: &ArgMatches) -> Result<Options, Error> {
1652         let general_args = GeneralArgs::from_matches(matches)?;
1653         let my_ca = Self::parse_my_ca(matches)?;
1654         let parent = matches.value_of("parent").unwrap();
1655         let parent = Handle::from_str(parent).map_err(|_| Error::InvalidHandle)?;
1656 
1657         let command = Command::CertAuth(CaCommand::RemoveParent(my_ca, parent));
1658         Ok(Options::make(general_args, command))
1659     }
1660 
parse_matches_cas_parents(matches: &ArgMatches) -> Result<Options, Error>1661     fn parse_matches_cas_parents(matches: &ArgMatches) -> Result<Options, Error> {
1662         if let Some(m) = matches.subcommand_matches("request") {
1663             Self::parse_matches_cas_parents_request(m)
1664         } else if let Some(m) = matches.subcommand_matches("add") {
1665             Self::parse_matches_cas_parents_add(m)
1666         } else if let Some(m) = matches.subcommand_matches("contact") {
1667             Self::parse_matches_cas_parents_info(m)
1668         } else if let Some(m) = matches.subcommand_matches("statuses") {
1669             Self::parse_matches_cas_parents_statuses(m)
1670         } else if let Some(m) = matches.subcommand_matches("remove") {
1671             Self::parse_matches_cas_parents_remove(m)
1672         } else {
1673             Err(Error::UnrecognizedSubCommand)
1674         }
1675     }
1676 
parse_matches_cas_keyroll_init(matches: &ArgMatches) -> Result<Options, Error>1677     fn parse_matches_cas_keyroll_init(matches: &ArgMatches) -> Result<Options, Error> {
1678         let general_args = GeneralArgs::from_matches(matches)?;
1679         let my_ca = Self::parse_my_ca(matches)?;
1680 
1681         let command = Command::CertAuth(CaCommand::KeyRollInit(my_ca));
1682 
1683         Ok(Options::make(general_args, command))
1684     }
1685 
parse_matches_cas_keyroll_activate(matches: &ArgMatches) -> Result<Options, Error>1686     fn parse_matches_cas_keyroll_activate(matches: &ArgMatches) -> Result<Options, Error> {
1687         let general_args = GeneralArgs::from_matches(matches)?;
1688         let my_ca = Self::parse_my_ca(matches)?;
1689 
1690         let command = Command::CertAuth(CaCommand::KeyRollActivate(my_ca));
1691 
1692         Ok(Options::make(general_args, command))
1693     }
1694 
parse_matches_cas_keyroll(matches: &ArgMatches) -> Result<Options, Error>1695     fn parse_matches_cas_keyroll(matches: &ArgMatches) -> Result<Options, Error> {
1696         if let Some(m) = matches.subcommand_matches("init") {
1697             Self::parse_matches_cas_keyroll_init(m)
1698         } else if let Some(m) = matches.subcommand_matches("activate") {
1699             Self::parse_matches_cas_keyroll_activate(m)
1700         } else {
1701             Err(Error::UnrecognizedSubCommand)
1702         }
1703     }
1704 
parse_matches_cas_routes_list(matches: &ArgMatches) -> Result<Options, Error>1705     fn parse_matches_cas_routes_list(matches: &ArgMatches) -> Result<Options, Error> {
1706         let general_args = GeneralArgs::from_matches(matches)?;
1707         let my_ca = Self::parse_my_ca(matches)?;
1708 
1709         let command = Command::CertAuth(CaCommand::RouteAuthorizationsList(my_ca));
1710 
1711         Ok(Options::make(general_args, command))
1712     }
1713 
parse_matches_cas_routes_update(matches: &ArgMatches) -> Result<Options, Error>1714     fn parse_matches_cas_routes_update(matches: &ArgMatches) -> Result<Options, Error> {
1715         let general_args = GeneralArgs::from_matches(matches)?;
1716         let my_ca = Self::parse_my_ca(matches)?;
1717 
1718         let updates = if let Some(path) = matches.value_of("delta") {
1719             if matches.is_present("add") || matches.is_present("remove") {
1720                 return Err(Error::general("Cannot use --add or --remove if --delta is specified"));
1721             }
1722 
1723             let bytes = Self::read_file_arg(path)?;
1724             let updates_str = unsafe { from_utf8_unchecked(&bytes) };
1725             RoaDefinitionUpdates::from_str(updates_str)?
1726         } else {
1727             let mut added = vec![];
1728             let mut removed = vec![];
1729 
1730             if let Some(add) = matches.values_of("add") {
1731                 for roa_str in add {
1732                     let roa: RoaDefinition = RoaDefinition::from_str(roa_str)?;
1733                     added.push(roa);
1734                 }
1735             }
1736 
1737             if let Some(remove) = matches.values_of("remove") {
1738                 for roa_str in remove {
1739                     let roa: RoaDefinition = RoaDefinition::from_str(roa_str)?;
1740                     removed.push(roa);
1741                 }
1742             }
1743 
1744             if added.is_empty() && removed.is_empty() {
1745                 return Err(Error::general(
1746                     "You MUST specify either --delta, or --add and/or --remove",
1747                 ));
1748             }
1749 
1750             RoaDefinitionUpdates::new(added, removed)
1751         };
1752 
1753         if matches.is_present("dryrun") && matches.is_present("try") {
1754             return Err(Error::general("You cannot use both --dryrun and --try"));
1755         }
1756 
1757         let command = if matches.is_present("dryrun") {
1758             Command::CertAuth(CaCommand::RouteAuthorizationsDryRunUpdate(my_ca, updates))
1759         } else if matches.is_present("try") {
1760             Command::CertAuth(CaCommand::RouteAuthorizationsTryUpdate(my_ca, updates))
1761         } else {
1762             Command::CertAuth(CaCommand::RouteAuthorizationsUpdate(my_ca, updates))
1763         };
1764 
1765         Ok(Options::make(general_args, command))
1766     }
1767 
parse_matches_cas_routes_bgp_full(matches: &ArgMatches) -> Result<Options, Error>1768     fn parse_matches_cas_routes_bgp_full(matches: &ArgMatches) -> Result<Options, Error> {
1769         let general_args = GeneralArgs::from_matches(matches)?;
1770         let my_ca = Self::parse_my_ca(matches)?;
1771         Ok(Options::make(
1772             general_args,
1773             Command::CertAuth(CaCommand::BgpAnalysisFull(my_ca)),
1774         ))
1775     }
1776 
parse_matches_cas_routes_bgp_suggest(matches: &ArgMatches) -> Result<Options, Error>1777     fn parse_matches_cas_routes_bgp_suggest(matches: &ArgMatches) -> Result<Options, Error> {
1778         let general_args = GeneralArgs::from_matches(matches)?;
1779         let my_ca = Self::parse_my_ca(matches)?;
1780 
1781         let v4 = matches.value_of("ipv4").unwrap_or("");
1782         let v6 = matches.value_of("ipv6").unwrap_or("");
1783 
1784         let resources = ResourceSet::from_strs("", v4, v6)
1785             .map_err(|e| Error::GeneralArgumentError(format!("Could not parse IP resources: {}", e)))?;
1786 
1787         let resources = if resources.is_empty() { None } else { Some(resources) };
1788 
1789         Ok(Options::make(
1790             general_args,
1791             Command::CertAuth(CaCommand::BgpAnalysisSuggest(my_ca, resources)),
1792         ))
1793     }
1794 
parse_matches_cas_routes_bgp(matches: &ArgMatches) -> Result<Options, Error>1795     fn parse_matches_cas_routes_bgp(matches: &ArgMatches) -> Result<Options, Error> {
1796         if let Some(m) = matches.subcommand_matches("analyze") {
1797             Self::parse_matches_cas_routes_bgp_full(m)
1798         } else if let Some(m) = matches.subcommand_matches("suggest") {
1799             Self::parse_matches_cas_routes_bgp_suggest(m)
1800         } else {
1801             Err(Error::UnrecognizedSubCommand)
1802         }
1803     }
1804 
parse_matches_cas_routes(matches: &ArgMatches) -> Result<Options, Error>1805     fn parse_matches_cas_routes(matches: &ArgMatches) -> Result<Options, Error> {
1806         if let Some(m) = matches.subcommand_matches("list") {
1807             Self::parse_matches_cas_routes_list(m)
1808         } else if let Some(m) = matches.subcommand_matches("update") {
1809             Self::parse_matches_cas_routes_update(m)
1810         } else if let Some(m) = matches.subcommand_matches("bgp") {
1811             Self::parse_matches_cas_routes_bgp(m)
1812         } else {
1813             Err(Error::UnrecognizedSubCommand)
1814         }
1815     }
1816 
parse_matches_cas_aspas_add(matches: &ArgMatches) -> Result<Options, Error>1817     fn parse_matches_cas_aspas_add(matches: &ArgMatches) -> Result<Options, Error> {
1818         let general_args = GeneralArgs::from_matches(matches)?;
1819         let my_ca = Self::parse_my_ca(matches)?;
1820 
1821         let aspa_config_str = matches.value_of("aspa").unwrap(); // required argument
1822         let aspa = AspaDefinition::from_str(aspa_config_str)?;
1823 
1824         let command = Command::CertAuth(CaCommand::AspasAddOrReplace(my_ca, aspa));
1825 
1826         Ok(Options::make(general_args, command))
1827     }
1828 
parse_matches_cas_aspas_remove(matches: &ArgMatches) -> Result<Options, Error>1829     fn parse_matches_cas_aspas_remove(matches: &ArgMatches) -> Result<Options, Error> {
1830         let general_args = GeneralArgs::from_matches(matches)?;
1831         let my_ca = Self::parse_my_ca(matches)?;
1832         let customer_str = matches.value_of("customer").unwrap();
1833         let customer = AspaCustomer::from_str(customer_str).map_err(|_| Error::invalid_asn(customer_str))?;
1834 
1835         let command = Command::CertAuth(CaCommand::AspasRemove(my_ca, customer));
1836 
1837         Ok(Options::make(general_args, command))
1838     }
1839 
parse_matches_cas_aspas_update(matches: &ArgMatches) -> Result<Options, Error>1840     fn parse_matches_cas_aspas_update(matches: &ArgMatches) -> Result<Options, Error> {
1841         let general_args = GeneralArgs::from_matches(matches)?;
1842         let my_ca = Self::parse_my_ca(matches)?;
1843 
1844         let mut added = vec![];
1845         let mut removed = vec![];
1846 
1847         let customer_str = matches.value_of("customer").unwrap();
1848         let customer = AspaCustomer::from_str(customer_str).map_err(|_| Error::invalid_asn(customer_str))?;
1849 
1850         if let Some(add) = matches.values_of("add") {
1851             for provider_str in add {
1852                 let provider = ProviderAs::from_str(provider_str).map_err(|_| Error::invalid_asn(provider_str))?;
1853                 added.push(provider);
1854             }
1855         }
1856 
1857         if let Some(remove) = matches.values_of("remove") {
1858             for provider_as_str in remove {
1859                 let provider_as =
1860                     ProviderAs::from_str(provider_as_str).map_err(|_| Error::invalid_asn(provider_as_str))?;
1861 
1862                 if added.iter().any(|added| added.provider() == provider_as.provider()) {
1863                     return Err(Error::general("Do not add and remove the same AS in a single update."));
1864                 }
1865 
1866                 removed.push(provider_as);
1867             }
1868         }
1869 
1870         let update = AspaProvidersUpdate::new(added, removed);
1871         if update.is_empty() {
1872             return Err(Error::general("You MUST specify at least one of --add or --remove"));
1873         }
1874 
1875         let command = Command::CertAuth(CaCommand::AspasUpdate(my_ca, customer, update));
1876 
1877         Ok(Options::make(general_args, command))
1878     }
1879 
parse_matches_cas_aspas_list(matches: &ArgMatches) -> Result<Options, Error>1880     fn parse_matches_cas_aspas_list(matches: &ArgMatches) -> Result<Options, Error> {
1881         let general_args = GeneralArgs::from_matches(matches)?;
1882         let my_ca = Self::parse_my_ca(matches)?;
1883 
1884         let command = Command::CertAuth(CaCommand::AspasList(my_ca));
1885 
1886         Ok(Options::make(general_args, command))
1887     }
1888 
parse_matches_cas_aspas(matches: &ArgMatches) -> Result<Options, Error>1889     fn parse_matches_cas_aspas(matches: &ArgMatches) -> Result<Options, Error> {
1890         if let Some(m) = matches.subcommand_matches("add") {
1891             Self::parse_matches_cas_aspas_add(m)
1892         } else if let Some(m) = matches.subcommand_matches("remove") {
1893             Self::parse_matches_cas_aspas_remove(m)
1894         } else if let Some(m) = matches.subcommand_matches("update") {
1895             Self::parse_matches_cas_aspas_update(m)
1896         } else if let Some(m) = matches.subcommand_matches("list") {
1897             Self::parse_matches_cas_aspas_list(m)
1898         } else {
1899             Err(Error::UnrecognizedSubCommand)
1900         }
1901     }
1902 
parse_matches_cas_repo_request(matches: &ArgMatches) -> Result<Options, Error>1903     fn parse_matches_cas_repo_request(matches: &ArgMatches) -> Result<Options, Error> {
1904         let general_args = GeneralArgs::from_matches(matches)?;
1905         let my_ca = Self::parse_my_ca(matches)?;
1906 
1907         let command = Command::CertAuth(CaCommand::RepoPublisherRequest(my_ca));
1908 
1909         Ok(Options::make(general_args, command))
1910     }
1911 
parse_matches_cas_repo_details(matches: &ArgMatches) -> Result<Options, Error>1912     fn parse_matches_cas_repo_details(matches: &ArgMatches) -> Result<Options, Error> {
1913         let general_args = GeneralArgs::from_matches(matches)?;
1914         let my_ca = Self::parse_my_ca(matches)?;
1915 
1916         let command = Command::CertAuth(CaCommand::RepoDetails(my_ca));
1917 
1918         Ok(Options::make(general_args, command))
1919     }
1920 
parse_matches_cas_repo_status(matches: &ArgMatches) -> Result<Options, Error>1921     fn parse_matches_cas_repo_status(matches: &ArgMatches) -> Result<Options, Error> {
1922         let general_args = GeneralArgs::from_matches(matches)?;
1923         let my_ca = Self::parse_my_ca(matches)?;
1924 
1925         let command = Command::CertAuth(CaCommand::RepoStatus(my_ca));
1926 
1927         Ok(Options::make(general_args, command))
1928     }
1929 
parse_matches_cas_repo_configure(matches: &ArgMatches) -> Result<Options, Error>1930     fn parse_matches_cas_repo_configure(matches: &ArgMatches) -> Result<Options, Error> {
1931         let general_args = GeneralArgs::from_matches(matches)?;
1932         let my_ca = Self::parse_my_ca(matches)?;
1933 
1934         let path = matches.value_of("response").unwrap();
1935         let bytes = Self::read_file_arg(path)?;
1936         let response = rfc8183::RepositoryResponse::validate(bytes.as_ref())?;
1937 
1938         let repo_contact = RepositoryContact::new(response);
1939         let command = Command::CertAuth(CaCommand::RepoUpdate(my_ca, repo_contact));
1940         Ok(Options::make(general_args, command))
1941     }
1942 
parse_matches_cas_repo(matches: &ArgMatches) -> Result<Options, Error>1943     fn parse_matches_cas_repo(matches: &ArgMatches) -> Result<Options, Error> {
1944         if let Some(m) = matches.subcommand_matches("request") {
1945             Self::parse_matches_cas_repo_request(m)
1946         } else if let Some(m) = matches.subcommand_matches("show") {
1947             Self::parse_matches_cas_repo_details(m)
1948         } else if let Some(m) = matches.subcommand_matches("status") {
1949             Self::parse_matches_cas_repo_status(m)
1950         } else if let Some(m) = matches.subcommand_matches("configure") {
1951             Self::parse_matches_cas_repo_configure(m)
1952         } else {
1953             Err(Error::UnrecognizedSubCommand)
1954         }
1955     }
1956 
parse_matches_cas_issues(matches: &ArgMatches) -> Result<Options, Error>1957     fn parse_matches_cas_issues(matches: &ArgMatches) -> Result<Options, Error> {
1958         let general = GeneralArgs::from_matches(matches)?;
1959         let command = if let Ok(ca) = Self::parse_my_ca(matches) {
1960             Command::CertAuth(CaCommand::Issues(Some(ca)))
1961         } else {
1962             Command::CertAuth(CaCommand::Issues(None))
1963         };
1964         Ok(Options::make(general, command))
1965     }
1966 
parse_matches_cas_rta_list(matches: &ArgMatches) -> Result<Options, Error>1967     fn parse_matches_cas_rta_list(matches: &ArgMatches) -> Result<Options, Error> {
1968         let general_args = GeneralArgs::from_matches(matches)?;
1969         let ca = Self::parse_my_ca(matches)?;
1970         let command = Command::CertAuth(CaCommand::RtaList(ca));
1971         Ok(Options::make(general_args, command))
1972     }
1973 
parse_matches_cas_rta_show(matches: &ArgMatches) -> Result<Options, Error>1974     fn parse_matches_cas_rta_show(matches: &ArgMatches) -> Result<Options, Error> {
1975         let general_args = GeneralArgs::from_matches(matches)?;
1976         let ca = Self::parse_my_ca(matches)?;
1977         let name = matches.value_of("name").unwrap().to_string();
1978 
1979         let out_file = matches.value_of("out").unwrap();
1980         let out_file = PathBuf::from_str(out_file)
1981             .map_err(|_| Error::GeneralArgumentError(format!("Invalid filename: {}", out_file)))?;
1982 
1983         file::save(&[], &out_file).map_err(|e| {
1984             Error::GeneralArgumentError(format!(
1985                 "Cannot save to file: {}, error: {}",
1986                 out_file.to_string_lossy(),
1987                 e
1988             ))
1989         })?;
1990 
1991         let command = Command::CertAuth(CaCommand::RtaShow(ca, name, Some(out_file)));
1992         Ok(Options::make(general_args, command))
1993     }
1994 
parse_matches_cas_rta_sign(matches: &ArgMatches) -> Result<Options, Error>1995     fn parse_matches_cas_rta_sign(matches: &ArgMatches) -> Result<Options, Error> {
1996         let general_args = GeneralArgs::from_matches(matches)?;
1997         let ca = Self::parse_my_ca(matches)?;
1998 
1999         let days = matches.value_of("days").unwrap();
2000         let days =
2001             i64::from_str(days).map_err(|e| Error::GeneralArgumentError(format!("Invalid number of days: {}", e)))?;
2002 
2003         let in_file = matches.value_of("in").unwrap();
2004         let in_file = PathBuf::from_str(in_file)
2005             .map_err(|_| Error::GeneralArgumentError(format!("Invalid filename: {}", in_file)))?;
2006 
2007         let content = file::read(&in_file).map_err(|e| {
2008             Error::GeneralArgumentError(format!(
2009                 "Can't read file '{}', error: {}",
2010                 in_file.to_string_lossy().to_string(),
2011                 e,
2012             ))
2013         })?;
2014 
2015         let name = matches.value_of("name").unwrap().to_string();
2016 
2017         let validity = SignSupport::sign_validity_days(days);
2018 
2019         let resources = Self::parse_resource_args(matches)?
2020             .ok_or_else(|| Error::general("You must specify at least one of --ipv4, --ipv6 or --asn"))?;
2021 
2022         let keys = if let Some(keys) = matches.values_of("keys") {
2023             let mut res = vec![];
2024             for key_str in keys {
2025                 let ki = KeyIdentifier::from_str(key_str).map_err(|_| Error::general("Invalid key identifier"))?;
2026                 res.push(ki)
2027             }
2028             res
2029         } else {
2030             vec![]
2031         };
2032 
2033         let request = RtaContentRequest::new(resources, validity, keys, content);
2034         let command = Command::CertAuth(CaCommand::RtaSign(ca, name, request));
2035         Ok(Options::make(general_args, command))
2036     }
2037 
parse_matches_cas_rta_multi_sign(matches: &ArgMatches) -> Result<Options, Error>2038     fn parse_matches_cas_rta_multi_sign(matches: &ArgMatches) -> Result<Options, Error> {
2039         let general_args = GeneralArgs::from_matches(matches)?;
2040         let ca = Self::parse_my_ca(matches)?;
2041         let name = matches.value_of("name").unwrap().to_string();
2042 
2043         let in_file = matches.value_of("in").unwrap();
2044         let in_file = PathBuf::from_str(in_file)
2045             .map_err(|_| Error::GeneralArgumentError(format!("Invalid filename: {}", in_file)))?;
2046 
2047         let content = file::read(&in_file).map_err(|e| {
2048             Error::GeneralArgumentError(format!(
2049                 "Can't read file '{}', error: {}",
2050                 in_file.to_string_lossy().to_string(),
2051                 e,
2052             ))
2053         })?;
2054 
2055         let rta = ResourceTaggedAttestation::new(content);
2056 
2057         let command = Command::CertAuth(CaCommand::RtaMultiCoSign(ca, name, rta));
2058         Ok(Options::make(general_args, command))
2059     }
2060 
parse_matches_cas_rta_multi_prep(matches: &ArgMatches) -> Result<Options, Error>2061     fn parse_matches_cas_rta_multi_prep(matches: &ArgMatches) -> Result<Options, Error> {
2062         let general_args = GeneralArgs::from_matches(matches)?;
2063         let ca = Self::parse_my_ca(matches)?;
2064 
2065         let name = matches.value_of("name").unwrap().to_string();
2066         let resources = Self::parse_resource_args(matches)?
2067             .ok_or_else(|| Error::general("You must specify at least one of --ipv4, --ipv6 or --asn"))?;
2068 
2069         let days = matches.value_of("days").unwrap();
2070         let days =
2071             i64::from_str(days).map_err(|e| Error::GeneralArgumentError(format!("Invalid number of days: {}", e)))?;
2072         let validity = SignSupport::sign_validity_days(days);
2073 
2074         let request = RtaPrepareRequest::new(resources, validity);
2075 
2076         let command = Command::CertAuth(CaCommand::RtaMultiPrep(ca, name, request));
2077         Ok(Options::make(general_args, command))
2078     }
2079 
parse_matches_cas_rta_multi(matches: &ArgMatches) -> Result<Options, Error>2080     fn parse_matches_cas_rta_multi(matches: &ArgMatches) -> Result<Options, Error> {
2081         if let Some(m) = matches.subcommand_matches("prep") {
2082             Self::parse_matches_cas_rta_multi_prep(m)
2083         } else if let Some(m) = matches.subcommand_matches("cosign") {
2084             Self::parse_matches_cas_rta_multi_sign(m)
2085         } else {
2086             Err(Error::UnrecognizedSubCommand)
2087         }
2088     }
2089 
parse_matches_cas_rta(matches: &ArgMatches) -> Result<Options, Error>2090     fn parse_matches_cas_rta(matches: &ArgMatches) -> Result<Options, Error> {
2091         if let Some(m) = matches.subcommand_matches("list") {
2092             Self::parse_matches_cas_rta_list(m)
2093         } else if let Some(m) = matches.subcommand_matches("show") {
2094             Self::parse_matches_cas_rta_show(m)
2095         } else if let Some(m) = matches.subcommand_matches("sign") {
2096             Self::parse_matches_cas_rta_sign(m)
2097         } else if let Some(m) = matches.subcommand_matches("multi") {
2098             Self::parse_matches_cas_rta_multi(m)
2099         } else {
2100             Err(Error::UnrecognizedSubCommand)
2101         }
2102     }
2103 
parse_matches_bulk(matches: &ArgMatches) -> Result<Options, Error>2104     fn parse_matches_bulk(matches: &ArgMatches) -> Result<Options, Error> {
2105         if let Some(m) = matches.subcommand_matches("publish") {
2106             let general_args = GeneralArgs::from_matches(m)?;
2107             let command = Command::Bulk(BulkCaCommand::Publish);
2108             Ok(Options::make(general_args, command))
2109         } else if let Some(m) = matches.subcommand_matches("refresh") {
2110             let general_args = GeneralArgs::from_matches(m)?;
2111             let command = Command::Bulk(BulkCaCommand::Refresh);
2112             Ok(Options::make(general_args, command))
2113         } else if let Some(m) = matches.subcommand_matches("sync") {
2114             let general_args = GeneralArgs::from_matches(m)?;
2115             let command = Command::Bulk(BulkCaCommand::Sync);
2116             Ok(Options::make(general_args, command))
2117         } else {
2118             Err(Error::UnrecognizedSubCommand)
2119         }
2120     }
2121 
parse_matches_health(matches: &ArgMatches) -> Result<Options, Error>2122     fn parse_matches_health(matches: &ArgMatches) -> Result<Options, Error> {
2123         let general_args = GeneralArgs::from_matches(matches)?;
2124         let command = Command::Health;
2125         Ok(Options::make(general_args, command))
2126     }
2127 
parse_matches_info(matches: &ArgMatches) -> Result<Options, Error>2128     fn parse_matches_info(matches: &ArgMatches) -> Result<Options, Error> {
2129         let general_args = GeneralArgs::from_matches(matches)?;
2130         let command = Command::Info;
2131         Ok(Options::make(general_args, command))
2132     }
2133 
parse_publisher_arg(matches: &ArgMatches) -> Result<PublisherHandle, Error>2134     fn parse_publisher_arg(matches: &ArgMatches) -> Result<PublisherHandle, Error> {
2135         let publisher_str = matches.value_of("publisher").unwrap();
2136         PublisherHandle::from_str(publisher_str).map_err(|_| Error::InvalidHandle)
2137     }
2138 
parse_matches_publishers_list(matches: &ArgMatches) -> Result<Options, Error>2139     fn parse_matches_publishers_list(matches: &ArgMatches) -> Result<Options, Error> {
2140         let general_args = GeneralArgs::from_matches(matches)?;
2141         let command = Command::PubServer(PubServerCommand::PublisherList);
2142         Ok(Options::make(general_args, command))
2143     }
2144 
parse_matches_publishers_stale(matches: &ArgMatches) -> Result<Options, Error>2145     fn parse_matches_publishers_stale(matches: &ArgMatches) -> Result<Options, Error> {
2146         let general_args = GeneralArgs::from_matches(matches)?;
2147         let seconds = i64::from_str(matches.value_of("seconds").unwrap()).map_err(|_| Error::InvalidSeconds)?;
2148         let command = Command::PubServer(PubServerCommand::StalePublishers(seconds));
2149         Ok(Options::make(general_args, command))
2150     }
2151 
parse_matches_publishers_add(matches: &ArgMatches) -> Result<Options, Error>2152     fn parse_matches_publishers_add(matches: &ArgMatches) -> Result<Options, Error> {
2153         let general_args = GeneralArgs::from_matches(matches)?;
2154 
2155         let path = matches.value_of("request").unwrap();
2156         let path = PathBuf::from(path);
2157         let bytes = file::read(&path)?;
2158         let mut req = rfc8183::PublisherRequest::validate(bytes.as_ref())?;
2159 
2160         if let Some(publisher_str) = matches.value_of("publisher") {
2161             let publisher = PublisherHandle::from_str(publisher_str).map_err(|_| Error::InvalidHandle)?;
2162             let (tag, _, cert) = req.unpack();
2163             req = rfc8183::PublisherRequest::new(tag, publisher, cert);
2164         }
2165 
2166         let command = Command::PubServer(PubServerCommand::AddPublisher(req));
2167         Ok(Options::make(general_args, command))
2168     }
2169 
parse_matches_publishers_remove(matches: &ArgMatches) -> Result<Options, Error>2170     fn parse_matches_publishers_remove(matches: &ArgMatches) -> Result<Options, Error> {
2171         let general_args = GeneralArgs::from_matches(matches)?;
2172         let publisher = Self::parse_publisher_arg(matches)?;
2173         let command = Command::PubServer(PubServerCommand::RemovePublisher(publisher));
2174         Ok(Options::make(general_args, command))
2175     }
2176 
parse_matches_publishers_show(matches: &ArgMatches) -> Result<Options, Error>2177     fn parse_matches_publishers_show(matches: &ArgMatches) -> Result<Options, Error> {
2178         let general_args = GeneralArgs::from_matches(matches)?;
2179         let publisher = Self::parse_publisher_arg(matches)?;
2180         let command = Command::PubServer(PubServerCommand::ShowPublisher(publisher));
2181         Ok(Options::make(general_args, command))
2182     }
2183 
parse_matches_publishers_repo_response(matches: &ArgMatches) -> Result<Options, Error>2184     fn parse_matches_publishers_repo_response(matches: &ArgMatches) -> Result<Options, Error> {
2185         let general_args = GeneralArgs::from_matches(matches)?;
2186         let publisher = Self::parse_publisher_arg(matches)?;
2187         let command = Command::PubServer(PubServerCommand::RepositoryResponse(publisher));
2188         Ok(Options::make(general_args, command))
2189     }
2190 
parse_matches_publication_server_stats(matches: &ArgMatches) -> Result<Options, Error>2191     fn parse_matches_publication_server_stats(matches: &ArgMatches) -> Result<Options, Error> {
2192         let general_args = GeneralArgs::from_matches(matches)?;
2193         let command = Command::PubServer(PubServerCommand::RepositoryStats);
2194         Ok(Options::make(general_args, command))
2195     }
2196 
parse_matches_publication_server_init(matches: &ArgMatches) -> Result<Options, Error>2197     fn parse_matches_publication_server_init(matches: &ArgMatches) -> Result<Options, Error> {
2198         let general_args = GeneralArgs::from_matches(matches)?;
2199 
2200         let rsync_str = matches.value_of("rsync").unwrap();
2201         let rrdp_str = matches.value_of("rrdp").unwrap();
2202 
2203         if !rsync_str.ends_with('/') {
2204             return Err(Error::general("rsync base URI must end with '/'"));
2205         }
2206 
2207         if !rrdp_str.ends_with('/') {
2208             return Err(Error::general("RRDP base URI must end with '/'"));
2209         }
2210 
2211         let rsync = uri::Rsync::from_str(rsync_str)
2212             .map_err(|e| Error::GeneralArgumentError(format!("Invalid rsync URI: {}", e)))?;
2213 
2214         let rrdp = uri::Https::from_str(rrdp_str)
2215             .map_err(|e| Error::GeneralArgumentError(format!("Invalid RRDP URI: {}", e)))?;
2216 
2217         let uris = PublicationServerUris::new(rrdp, rsync);
2218 
2219         let command = Command::PubServer(PubServerCommand::RepositoryInit(uris));
2220         Ok(Options::make(general_args, command))
2221     }
2222 
parse_matches_publication_server_clear(matches: &ArgMatches) -> Result<Options, Error>2223     fn parse_matches_publication_server_clear(matches: &ArgMatches) -> Result<Options, Error> {
2224         let general_args = GeneralArgs::from_matches(matches)?;
2225         let command = Command::PubServer(PubServerCommand::RepositoryClear);
2226         Ok(Options::make(general_args, command))
2227     }
2228 
parse_matches_publication_server(matches: &ArgMatches) -> Result<Options, Error>2229     fn parse_matches_publication_server(matches: &ArgMatches) -> Result<Options, Error> {
2230         if let Some(m) = matches.subcommand_matches("stats") {
2231             Self::parse_matches_publication_server_stats(m)
2232         } else if let Some(m) = matches.subcommand_matches("init") {
2233             Self::parse_matches_publication_server_init(m)
2234         } else if let Some(m) = matches.subcommand_matches("clear") {
2235             Self::parse_matches_publication_server_clear(m)
2236         } else {
2237             Err(Error::UnrecognizedSubCommand)
2238         }
2239     }
2240 
parse_matches_publishers(matches: &ArgMatches) -> Result<Options, Error>2241     fn parse_matches_publishers(matches: &ArgMatches) -> Result<Options, Error> {
2242         if let Some(m) = matches.subcommand_matches("list") {
2243             Self::parse_matches_publishers_list(m)
2244         } else if let Some(m) = matches.subcommand_matches("stale") {
2245             Self::parse_matches_publishers_stale(m)
2246         } else if let Some(m) = matches.subcommand_matches("add") {
2247             Self::parse_matches_publishers_add(m)
2248         } else if let Some(m) = matches.subcommand_matches("remove") {
2249             Self::parse_matches_publishers_remove(m)
2250         } else if let Some(m) = matches.subcommand_matches("show") {
2251             Self::parse_matches_publishers_show(m)
2252         } else if let Some(m) = matches.subcommand_matches("response") {
2253             Self::parse_matches_publishers_repo_response(m)
2254         } else {
2255             Err(Error::UnrecognizedSubCommand)
2256         }
2257     }
2258 
parse_matches_pubserver(matches: &ArgMatches) -> Result<Options, Error>2259     fn parse_matches_pubserver(matches: &ArgMatches) -> Result<Options, Error> {
2260         if let Some(m) = matches.subcommand_matches("publishers") {
2261             Self::parse_matches_publishers(m)
2262         } else if let Some(m) = matches.subcommand_matches("server") {
2263             Self::parse_matches_publication_server(m)
2264         } else {
2265             Err(Error::UnrecognizedSubCommand)
2266         }
2267     }
2268 
parse_matches(matches: ArgMatches) -> Result<Options, Error>2269     fn parse_matches(matches: ArgMatches) -> Result<Options, Error> {
2270         if let Some(m) = matches.subcommand_matches("config") {
2271             Self::parse_matches_config(m)
2272         } else if let Some(m) = matches.subcommand_matches("list") {
2273             Self::parse_matches_cas_list(m)
2274         } else if let Some(m) = matches.subcommand_matches("add") {
2275             Self::parse_matches_cas_add(m)
2276         } else if let Some(m) = matches.subcommand_matches("delete") {
2277             Self::parse_matches_cas_delete(m)
2278         } else if let Some(m) = matches.subcommand_matches("show") {
2279             Self::parse_matches_cas_show(m)
2280         } else if let Some(m) = matches.subcommand_matches("history") {
2281             Self::parse_matches_cas_history(m)
2282         } else if let Some(m) = matches.subcommand_matches("children") {
2283             Self::parse_matches_cas_children(m)
2284         } else if let Some(m) = matches.subcommand_matches("parents") {
2285             Self::parse_matches_cas_parents(m)
2286         } else if let Some(m) = matches.subcommand_matches("keyroll") {
2287             Self::parse_matches_cas_keyroll(m)
2288         } else if let Some(m) = matches.subcommand_matches("roas") {
2289             Self::parse_matches_cas_routes(m)
2290         } else if let Some(m) = matches.subcommand_matches("aspas") {
2291             Self::parse_matches_cas_aspas(m)
2292         } else if let Some(m) = matches.subcommand_matches("repo") {
2293             Self::parse_matches_cas_repo(m)
2294         } else if let Some(m) = matches.subcommand_matches("issues") {
2295             Self::parse_matches_cas_issues(m)
2296         } else if let Some(m) = matches.subcommand_matches("rta") {
2297             Self::parse_matches_cas_rta(m)
2298         } else if let Some(m) = matches.subcommand_matches("bulk") {
2299             Self::parse_matches_bulk(m)
2300         } else if let Some(m) = matches.subcommand_matches("health") {
2301             Self::parse_matches_health(m)
2302         } else if let Some(m) = matches.subcommand_matches("info") {
2303             Self::parse_matches_info(m)
2304         } else if let Some(m) = matches.subcommand_matches("pubserver") {
2305             Self::parse_matches_pubserver(m)
2306         } else {
2307             Err(Error::UnrecognizedSubCommand)
2308         }
2309     }
2310 
from_args() -> Result<Options, Error>2311     pub fn from_args() -> Result<Options, Error> {
2312         let matches = Self::make_matches();
2313         Self::parse_matches(matches)
2314     }
2315 }
2316 
2317 #[derive(Clone, Debug, Eq, PartialEq)]
2318 #[allow(clippy::large_enum_variant)]
2319 pub enum Command {
2320     NotSet,
2321     Health,
2322     Info,
2323     Bulk(BulkCaCommand),
2324     CertAuth(CaCommand),
2325     PubServer(PubServerCommand),
2326     Init(KrillInitDetails),
2327     #[cfg(feature = "multi-user")]
2328     User(KrillUserDetails),
2329 }
2330 
2331 #[derive(Clone, Debug, Eq, PartialEq)]
2332 #[allow(clippy::large_enum_variant)]
2333 pub enum CaCommand {
2334     Init(CertAuthInit), // Initialize a CA
2335     UpdateId(Handle),   // Update CA id
2336     Delete(Handle),     // Delete the CA -> let it withdraw and request revocation as well
2337 
2338     // Publishing
2339     RepoPublisherRequest(Handle), // Get the RFC8183 publisher request
2340     RepoDetails(Handle),
2341     RepoUpdate(Handle, RepositoryContact),
2342     RepoStatus(Handle),
2343 
2344     // Parents (to this CA)
2345     ChildRequest(Handle), // Get the RFC8183 child request
2346     AddParent(Handle, ParentCaReq),
2347     MyParentCaContact(Handle, ParentHandle),
2348     ParentStatuses(Handle),
2349     RemoveParent(Handle, ParentHandle),
2350     Refresh(Handle), // Refresh with all parents
2351 
2352     // Children
2353     ParentResponse(Handle, ChildHandle), // Get an RFC8183 parent response for a child
2354     ChildInfo(Handle, ChildHandle),
2355     ChildAdd(Handle, AddChildRequest),
2356     ChildUpdate(Handle, ChildHandle, UpdateChildRequest),
2357     ChildDelete(Handle, ChildHandle),
2358     ChildConnections(Handle),
2359 
2360     // Key Management
2361     KeyRollInit(Handle),
2362     KeyRollActivate(Handle),
2363 
2364     // Authorizations
2365     RouteAuthorizationsList(Handle),
2366     RouteAuthorizationsUpdate(Handle, RoaDefinitionUpdates),
2367     RouteAuthorizationsTryUpdate(Handle, RoaDefinitionUpdates),
2368     RouteAuthorizationsDryRunUpdate(Handle, RoaDefinitionUpdates),
2369     BgpAnalysisFull(Handle),
2370     BgpAnalysisSuggest(Handle, Option<ResourceSet>),
2371 
2372     // ASPAs
2373     AspasList(Handle),
2374     AspasAddOrReplace(Handle, AspaDefinition),
2375     AspasUpdate(Handle, AspaCustomer, AspaProvidersUpdate),
2376     AspasRemove(Handle, AspaCustomer),
2377 
2378     // Show details for this CA
2379     Show(Handle),
2380     ShowHistoryCommands(Handle, HistoryOptions),
2381     ShowHistoryDetails(Handle, String),
2382     Issues(Option<Handle>),
2383 
2384     // RTA
2385     RtaList(Handle),
2386     RtaShow(Handle, RtaName, Option<PathBuf>),
2387     RtaSign(Handle, RtaName, RtaContentRequest),
2388     RtaMultiPrep(Handle, RtaName, RtaPrepareRequest),
2389     RtaMultiCoSign(Handle, RtaName, ResourceTaggedAttestation),
2390 
2391     // List all CAs
2392     List,
2393 }
2394 
2395 #[derive(Clone, Debug, Eq, PartialEq)]
2396 pub struct HistoryOptions {
2397     pub offset: u64,
2398     pub rows: u64,
2399     pub after: Option<Time>,
2400     pub before: Option<Time>,
2401 }
2402 
2403 impl Default for HistoryOptions {
default() -> Self2404     fn default() -> Self {
2405         HistoryOptions {
2406             offset: 0,
2407             rows: 100,
2408             after: None,
2409             before: None,
2410         }
2411     }
2412 }
2413 
2414 impl HistoryOptions {
url_path_parameters(&self) -> String2415     pub fn url_path_parameters(&self) -> String {
2416         if let Some(before) = self.before {
2417             let after = self.after.map(|t| t.timestamp()).unwrap_or_else(|| 0);
2418             format!("{}/{}/{}/{}", self.rows, self.offset, after, before.timestamp())
2419         } else if let Some(after) = self.after {
2420             format!("{}/{}/{}", self.rows, self.offset, after.timestamp())
2421         } else if self.offset != 0 {
2422             format!("{}/{}", self.rows, self.offset)
2423         } else if self.rows != 100 {
2424             format!("{}", self.rows)
2425         } else {
2426             "".to_string()
2427         }
2428     }
2429 }
2430 
2431 #[derive(Clone, Debug, Eq, PartialEq)]
2432 pub enum BulkCaCommand {
2433     Refresh,
2434     Publish,
2435     Sync,
2436 }
2437 
2438 #[derive(Clone, Debug, Eq, PartialEq)]
2439 pub struct KrillInitDetails {
2440     data_dir: Option<String>,
2441     log_file: Option<String>,
2442     multi_user: bool,
2443 }
2444 
2445 impl KrillInitDetails {
multi_user_dflt() -> Self2446     pub fn multi_user_dflt() -> Self {
2447         KrillInitDetails {
2448             data_dir: None,
2449             log_file: None,
2450             multi_user: true,
2451         }
2452     }
2453 
with_data_dir(&mut self, data_dir: &str)2454     pub fn with_data_dir(&mut self, data_dir: &str) {
2455         self.data_dir = Some(data_dir.to_string())
2456     }
2457 
with_log_file(&mut self, log_file: &str)2458     pub fn with_log_file(&mut self, log_file: &str) {
2459         self.log_file = Some(log_file.to_string())
2460     }
2461 
data_dir(&self) -> Option<&String>2462     pub fn data_dir(&self) -> Option<&String> {
2463         self.data_dir.as_ref()
2464     }
2465 
log_file(&self) -> Option<&String>2466     pub fn log_file(&self) -> Option<&String> {
2467         self.log_file.as_ref()
2468     }
2469 
multi_user(&self) -> bool2470     pub fn multi_user(&self) -> bool {
2471         self.multi_user
2472     }
2473 }
2474 
2475 impl Default for KrillInitDetails {
default() -> Self2476     fn default() -> Self {
2477         KrillInitDetails {
2478             data_dir: None,
2479             log_file: None,
2480             multi_user: false,
2481         }
2482     }
2483 }
2484 
2485 #[cfg(feature = "multi-user")]
2486 #[derive(Clone, Debug, Eq, PartialEq)]
2487 pub struct KrillUserDetails {
2488     id: String,
2489     attrs: HashMap<String, String>,
2490 }
2491 
2492 #[cfg(feature = "multi-user")]
2493 impl KrillUserDetails {
with_id(&mut self, id: String)2494     pub fn with_id(&mut self, id: String) {
2495         self.id = id;
2496     }
with_attr(&mut self, attr: String, value: String)2497     pub fn with_attr(&mut self, attr: String, value: String) {
2498         self.attrs.insert(attr, value);
2499     }
id(&self) -> &String2500     pub fn id(&self) -> &String {
2501         &self.id
2502     }
2503 
attrs(&self) -> HashMap<String, String>2504     pub fn attrs(&self) -> HashMap<String, String> {
2505         self.attrs.clone()
2506     }
2507 }
2508 
2509 #[cfg(feature = "multi-user")]
2510 impl Default for KrillUserDetails {
default() -> Self2511     fn default() -> Self {
2512         KrillUserDetails {
2513             id: String::new(),
2514             attrs: HashMap::new(),
2515         }
2516     }
2517 }
2518 
2519 #[derive(Clone, Debug, Eq, PartialEq)]
2520 #[allow(clippy::large_enum_variant)]
2521 pub enum PubServerCommand {
2522     AddPublisher(rfc8183::PublisherRequest),
2523     ShowPublisher(PublisherHandle),
2524     RemovePublisher(PublisherHandle),
2525     RepositoryResponse(PublisherHandle),
2526     StalePublishers(i64),
2527     PublisherList,
2528     RepositoryStats,
2529     RepositoryInit(PublicationServerUris),
2530     RepositoryClear,
2531 }
2532 
2533 //------------ Error ---------------------------------------------------------
2534 
2535 #[derive(Debug)]
2536 pub enum Error {
2537     UriError(uri::Error),
2538     IoError(KrillIoError),
2539     ReportError(ReportError),
2540     Rfc8183(rfc8183::Error),
2541     ResSetErr(ResourceSetError),
2542     InvalidRouteDelta(AuthorizationFmtError),
2543     InvalidAsn(String),
2544     DuplicateAspaProvider(DuplicateProviderAs),
2545     InvalidAspaConfig(AspaDefinitionFormatError),
2546     InvalidHandle,
2547     InvalidSeconds,
2548     MissingArgWithEnv(String, String),
2549     MissingResources,
2550     InvalidChildIdCert,
2551     UnrecognizedSubCommand,
2552     GeneralArgumentError(String),
2553 }
2554 
2555 impl Error {
invalid_asn(asn: &str) -> Self2556     fn invalid_asn(asn: &str) -> Self {
2557         Error::InvalidAsn(asn.to_string())
2558     }
2559 }
2560 
2561 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result2562     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2563         match self {
2564             Error::UriError(e) => e.fmt(f),
2565             Error::IoError(e) => e.fmt(f),
2566             Error::ReportError(e) => e.fmt(f),
2567             Error::Rfc8183(e) => write!(f, "Invalid RFC8183 XML: {}", e),
2568             Error::ResSetErr(e) => write!(f, "Invalid resources requested: {}", e),
2569             Error::InvalidRouteDelta(e) => e.fmt(f),
2570             Error::InvalidAsn(s) => write!(f, "Invalid ASN format. Expected 'AS#', got: {}", s),
2571             Error::DuplicateAspaProvider(e) => e.fmt(f),
2572             Error::InvalidAspaConfig(e) => e.fmt(f),
2573             Error::InvalidHandle => write!(
2574                 f,
2575                 "The publisher handle may only contain -_A-Za-z0-9, (\\ /) see issue #83"
2576             ),
2577             Error::InvalidSeconds => write!(f, "Use a number of 0 or more seconds"),
2578             Error::MissingArgWithEnv(arg, var) => write!(
2579                 f,
2580                 "Missing argument: --{}, alternatively you may use env var: {}",
2581                 arg, var
2582             ),
2583             Error::MissingResources => write!(f, "You must specify resources when adding a CA (--asn, --ipv4, --ipv6)"),
2584             Error::InvalidChildIdCert => write!(f, "Invalid ID cert for child"),
2585             Error::UnrecognizedSubCommand => write!(f, "Unrecognized sub-command. Use 'help'"),
2586             Error::GeneralArgumentError(s) => s.fmt(f),
2587         }
2588     }
2589 }
2590 
2591 impl Error {
missing_arg_with_env(arg: &str, env_var: &str) -> Self2592     fn missing_arg_with_env(arg: &str, env_var: &str) -> Self {
2593         Error::MissingArgWithEnv(arg.to_string(), env_var.to_string())
2594     }
2595 
general(msg: &str) -> Self2596     fn general(msg: &str) -> Self {
2597         Error::GeneralArgumentError(msg.to_string())
2598     }
2599 }
2600 
2601 impl From<rfc8183::Error> for Error {
from(e: rfc8183::Error) -> Self2602     fn from(e: rfc8183::Error) -> Self {
2603         Error::Rfc8183(e)
2604     }
2605 }
2606 
2607 impl From<uri::Error> for Error {
from(e: uri::Error) -> Self2608     fn from(e: uri::Error) -> Self {
2609         Error::UriError(e)
2610     }
2611 }
2612 
2613 impl From<KrillIoError> for Error {
from(e: KrillIoError) -> Self2614     fn from(e: KrillIoError) -> Self {
2615         Error::IoError(e)
2616     }
2617 }
2618 
2619 impl From<ReportError> for Error {
from(e: ReportError) -> Self2620     fn from(e: ReportError) -> Self {
2621         Error::ReportError(e)
2622     }
2623 }
2624 
2625 impl From<ResourceSetError> for Error {
from(e: ResourceSetError) -> Self2626     fn from(e: ResourceSetError) -> Self {
2627         Error::ResSetErr(e)
2628     }
2629 }
2630 
2631 impl From<AuthorizationFmtError> for Error {
from(e: AuthorizationFmtError) -> Self2632     fn from(e: AuthorizationFmtError) -> Self {
2633         Error::InvalidRouteDelta(e)
2634     }
2635 }
2636 
2637 impl From<AspaDefinitionFormatError> for Error {
from(e: AspaDefinitionFormatError) -> Self2638     fn from(e: AspaDefinitionFormatError) -> Self {
2639         Error::InvalidAspaConfig(e)
2640     }
2641 }
2642 
2643 impl From<DuplicateProviderAs> for Error {
from(e: DuplicateProviderAs) -> Self2644     fn from(e: DuplicateProviderAs) -> Self {
2645         Error::DuplicateAspaProvider(e)
2646     }
2647 }
2648