diff --git a/Changes b/Changes index ddb7c1d..8484237 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,7 @@ +1.03 - 2024-XX-YY: + - added --both option, and include all options in --help output. + - simplified implementation of display() using guard clauses + 1.02 - 2024-06-07: - update to support Net::RDAP 0.25 and display unstructured postal addresses. diff --git a/README.md b/README.md index 2d55c0a..2647d7a 100644 --- a/README.md +++ b/README.md @@ -58,26 +58,24 @@ but you must use the `--type=nameserver` argument to disambiguate from domain names. The RDAP server of the parent domain's registry will be queried. -# ADDITIONAL ARGUMENTS - -- `--registrar` - follow referral to the registrar's RDAP record -(if any) which will be displayed instead of the registry record. -- `--reverse` - if you provide an IP address or CIDR prefix, then -this option causes `rdapper` to display the record of the corresponding +- `--registrar` - follow referral to the registrar's RDAP record (if any) +which will be displayed instead of the registry record. +- `--both` - display both the registry and (if any) registrar RDAP +records (implies `--registrar`). +- `--reverse` - if you provide an IP address or CIDR prefix, then this +option causes `rdapper` to display the record of the corresponding `in-addr.arpa` or `ip6.arpa` domain. -- `--type=TYPE` - explicitly set the object type. `rdapper` -will guess the type by pattern matching the value of `OBJECT` but -you can override this by explicitly setting the `--type` argument -to one of : `ip`, `autnum`, `domain`, `nameserver`, `entity` -or `url`. - - If `--type=url` is used, `rdapper` will directly fetch the - specified URL and attempt to process it as an RDAP response. If the URL - path ends with `/help` then the response will be treated as a "help" - query response (if you want to see the record for the .help TLD, use - `--type=tld help`). - - If `--type=entity` is used, `OBJECT` must be a a string - containing a "tagged" handle, such as `ABC123-EXAMPLE`, as per - RFC 8521. +- `--type=TYPE` - explicitly set the object type. `rdapper` will guess +the type by pattern matching the value of `OBJECT` but you can override this by +explicitly setting the `--type` argument to one of : `ip`, `autnum`, +`domain`, `nameserver`, `entity` or `url`. + - If `--type=url` is used, `rdapper` will directly fetch the specified + URL and attempt to process it as an RDAP response. If the URL path ends with + `/help` then the response will be treated as a "help" query response (if you + want to see the record for the .help TLD, use `--type=tld help`). + - If `--type=entity` is used, `OBJECT` must be a a string containing a + "tagged" handle, such as `ABC123-EXAMPLE`, as per [RFC + 8521](https://datatracker.ietf.org/doc/html/rfc8521). - `--help` - display help message. - `--version` - display package and version. - `--raw` - print the raw JSON rather than parsing it. diff --git a/lib/App/rdapper.pm b/lib/App/rdapper.pm index f48cb0a..3e46695 100644 --- a/lib/App/rdapper.pm +++ b/lib/App/rdapper.pm @@ -7,7 +7,7 @@ use Net::ASN; use Net::DNS::Domain; use Net::IP; use Net::RDAP::EPPStatusMap; -use Net::RDAP 0.25; +use Net::RDAP 0.26; use Pod::Usage; use Term::ANSIColor; use Term::Size; @@ -31,7 +31,8 @@ $VERSION = '1.02'; # # global arg variables (note: nopager is now ignored) # -my ($type, $object, $help, $short, $bypass, $auth, $nopager, $raw, $registrar, $nocolor, $reverse, $version); +my ($type, $object, $help, $short, $bypass, $auth, $nopager, $raw, $both, + $registrar, $nocolor, $reverse, $version); # # options spec for Getopt::Long @@ -45,6 +46,7 @@ my %opts = ( 'auth:s' => \$auth, 'nopager' => \$nopager, 'raw' => \$raw, + 'both' => \$both, 'registrar' => \$registrar, 'nocolor' => \$nocolor, 'reverse' => \$reverse, @@ -60,7 +62,9 @@ my %funcs = ( 'help' => sub { 1 }, # help only contains generic properties ); -my @ROLE_DISPLAY_NAMES_ORDER = qw(registrant administrative technical billing abuse registrar reseller sponsor proxy notifications noc); +my @ROLE_DISPLAY_NAMES_ORDER = qw(registrant administrative technical billing + abuse registrar reseller sponsor proxy notifications noc); + my %ROLE_DISPLAY_NAMES = ('noc' => 'NOC'); my @EVENTS = ( @@ -120,6 +124,8 @@ sub main { 'cache_ttl' => 300, ); + $registrar ||= $both; + $object = shift(@_) if (!$object); $package->show_usage if ($help || length($object) < 1); @@ -217,116 +223,126 @@ sub display { if ($object->isa('Net::RDAP::Error')) { if ($nofatal) { $package->warning('%03u (%s)', $object->errorCode, $object->title); - return undef; } else { $package->error('%03u (%s)', $object->errorCode, $object->title); } + return undef; + } - } elsif ($registrar) { + if ($registrar) { # avoid recursing infinitely $registrar = undef; - my $link = (grep { 'related' eq $_->rel && 'application/rdap+json' eq $_->type } $object->links)[0]; - - if ($link && $package->display($rdap->fetch($link->href), $indent, 1)) { - return 1; + my $link = (grep { 'related' eq $_->rel && $_->is_rdap } $object->links)[0]; - } else { + if (!$link) { $package->warning('No registrar link found, displaying the registry record...'); return $package->display($object, $indent); - } - } elsif ($raw) { - $out->print(to_json({%{$object}})); + my $result = $rdap->fetch($link); - return 1; + if ($result->isa('Net::RDAP::Error')) { + return $package->display($result, $indent, 1); - } elsif (!defined($funcs{$object->class})) { - $package->print_kv('Unknown object type', $object->class || '(missing objectClassName)', $indent); + $package->warning('Unable to retrieve registrar record, displaying the registry record...'); + return $package->display($object, $indent); + } - return undef; + if ($both) { + $package->display($object, $indent, 1); + } - } else { - # - # generic properties - # - $package->print_kv('Object type', $object->class, $indent) if ($indent < 1); - $package->print_kv('URL', u($object->self->href), $indent) if ($indent < 1 && $object->self); + return $package->display($result, $indent); + } - if ($object->can('name')) { - my $name = $object->name; + if ($raw) { + $out->print(to_json({%{$object}})); - if ($name) { - my $xname; + return 1; + } - if ('Net::DNS::Domain' eq ref($name)) { - $xname = $name->xname; - $name = $name->name; + $package->error("JSON response does not include the 'objectClassName' properties") unless ($object->class); + $package->error(sprintf("Unknown object type '%s'", $object->class)) unless ($funcs{$object->class}); - } else { - $xname = $name; + # + # generic properties + # + $package->print_kv('Object type', $object->class, $indent) if ($indent < 1); + $package->print_kv('URL', u($object->self->href), $indent) if ($indent < 1 && $object->self); - } + if ($object->can('name')) { + my $name = $object->name; - if ($xname ne $name) { - $package->print_kv('Name', sprintf('%s (%s)', uc($xname), uc($name))); + if ($name) { + my $xname; - } else { - $package->print_kv('Name', uc($name)); + if ('Net::DNS::Domain' eq ref($name)) { + $xname = $name->xname; + $name = $name->name; - } - } - } + } else { + $xname = $name; - # - # object-specific properties - # - &{$funcs{$object->class}}($object, $indent); + } - # - # more generic properties - # - $package->print_events($object, $indent); - $package->print_status($object, $indent, ('domain' eq $object->class)); + if ($xname ne $name) { + $package->print_kv('Name', sprintf('%s (%s)', uc($xname), uc($name))); - $package->print_entities($object, $indent); + } else { + $package->print_kv('Name', uc($name)); - # - # links, remarks, notices and redactions, unless --short has been passed - # - if (!$short) { - foreach my $link (grep { 'self' ne $_->rel } $object->links) { - $package->print_link($link, $indent); } + } + } - foreach my $remark ($object->remarks) { - $package->print_remark_or_notice($remark, $indent); - } + # + # object-specific properties + # + &{$funcs{$object->class}}($object, $indent); + + # + # more generic properties + # + $package->print_events($object, $indent); + $package->print_status($object, $indent, ('domain' eq $object->class)); + + $package->print_entities($object, $indent); + + # + # links, remarks, notices and redactions, unless --short has been passed + # + if (!$short) { + foreach my $link (grep { 'self' ne $_->rel } $object->links) { + $package->print_link($link, $indent); + } - foreach my $notice ($object->notices) { - $package->print_remark_or_notice($notice, $indent); - } + foreach my $remark ($object->remarks) { + $package->print_remark_or_notice($remark, $indent); + } - my @fields = $object->redactions; - if (scalar(@fields) > 0) { - $package->print_kv('Redacted Fields', '', $indent); - foreach my $field (@fields) { - $out->print(wrap( - (INDENT x ($indent + 1)), - (INDENT x ($indent + 2)), - sprintf("%s %s (reason: %s)\n", b('*'), $field->name, $field->reason) - )); - } + foreach my $notice ($object->notices) { + $package->print_remark_or_notice($notice, $indent); + } + + my @fields = $object->redactions; + if (scalar(@fields) > 0) { + $package->print_kv('Redacted Fields', '', $indent); + foreach my $field (@fields) { + $out->print(wrap( + (INDENT x ($indent + 1)), + (INDENT x ($indent + 2)), + sprintf("%s %s (reason: %s)\n", b('*'), $field->name, $field->reason) + )); } } + } - $out->print("\n") if ($indent < 1); + $out->print("\n") if ($indent < 1); - return 1; - } + return 1; } sub print_ip { @@ -604,7 +620,7 @@ __END__ =head1 NAME -App::rdapper - a simple console-based RDAP client. +App::rdapper - a simple console-based L client. =head1 INSTALLATION @@ -629,15 +645,10 @@ Alternatively, you can pull the L is a simple RDAP client. It uses L to retrieve -data about internet resources (domain names, IP addresses, and -autonymous systems) and outputs the information in a human-readable -format. If you want to consume this data in your own program you -should use L directly. - -C was originally conceived as a full RDAP client (back -when the RDAP specification was still in draft form) but is now -just a very thin front-end to L. +C is a simple RDAP client. It uses L to retrieve data about +internet resources (domain names, IP addresses, and autonymous systems) and +outputs the information in a human-readable format. If you want to consume this +data in your own program you should use L directly. =head1 OPTIONS @@ -651,55 +662,53 @@ You can pass any internet resource as an argument; this may be: =item * a "reverse" domain name such as C<168.192.in-addr.arpa>; -=item * a IPv4 or IPv6 address or CIDR prefix, such as C<192.168.0.1> -or C<2001:DB8::/32>; +=item * a IPv4 or IPv6 address or CIDR prefix, such as C<192.168.0.1> or +C<2001:DB8::/32>; =item * an Autonymous System Number such as C. =item * the URL of an RDAP resource such as C. -=item * the "tagged" handle of an entity, such as an LIR, registrar, -or domain admin/tech contact. Because these handles are difficult -to distinguish from domain names, you must use the C<--type> argument -to explicitly tell C that you want to perform an entity query, -.e.g C. +=item * the "tagged" handle of an entity, such as an LIR, registrar, or domain +admin/tech contact. Because these handles are difficult to distinguish from +domain names, you must use the C<--type> argument to explicitly tell C +that you want to perform an entity query, .e.g C. =back -C also implements limited support for in-bailiwick nameservers, -but you must use the C<--type=nameserver> argument to disambiguate -from domain names. The RDAP server of the parent domain's registry will -be queried. - -=head1 ADDITIONAL ARGUMENTS +C also implements limited support for in-bailiwick nameservers, but you +must use the C<--type=nameserver> argument to disambiguate from domain names. The +RDAP server of the parent domain's registry will be queried. =over -=item * C<--registrar> - follow referral to the registrar's RDAP record -(if any) which will be displayed instead of the registry record. +=item * C<--registrar> - follow referral to the registrar's RDAP record (if any) +which will be displayed instead of the registry record. + +=item * C<--both> - display both the registry and (if any) registrar RDAP +records (implies C<--registrar>). -=item * C<--reverse> - if you provide an IP address or CIDR prefix, then -this option causes C to display the record of the corresponding +=item * C<--reverse> - if you provide an IP address or CIDR prefix, then this +option causes C to display the record of the corresponding C or C domain. -=item * C<--type=TYPE> - explicitly set the object type. C -will guess the type by pattern matching the value of C but -you can override this by explicitly setting the C<--type> argument -to one of : C, C, C, C, C -or C. +=item * C<--type=TYPE> - explicitly set the object type. C will guess +the type by pattern matching the value of C but you can override this by +explicitly setting the C<--type> argument to one of : C, C, +C, C, C or C. =over -=item * If C<--type=url> is used, C will directly fetch the -specified URL and attempt to process it as an RDAP response. If the URL -path ends with C then the response will be treated as a "help" -query response (if you want to see the record for the .help TLD, use -C<--type=tld help>). +=item * If C<--type=url> is used, C will directly fetch the specified +URL and attempt to process it as an RDAP response. If the URL path ends with +C then the response will be treated as a "help" query response (if you +want to see the record for the .help TLD, use C<--type=tld help>). -=item * If C<--type=entity> is used, C must be a a string -containing a "tagged" handle, such as C, as per -RFC 8521. +=item * If C<--type=entity> is used, C must be a a string containing a +"tagged" handle, such as C, as per L. =back @@ -713,10 +722,10 @@ RFC 8521. =item * C<--bypass-cache> - disable local cache of RDAP objects. -=item * C<--auth=USER:PASS> - HTTP Basic Authentication credentials -to be used when accessing the specified resource. This option -B be used unless you explicitly specify a URL, otherwise -your credentials may be sent to servers you aren't expecting them to. +=item * C<--auth=USER:PASS> - HTTP Basic Authentication credentials to be used +when accessing the specified resource. This option B be used unless +you explicitly specify a URL, otherwise your credentials may be sent to servers +you aren't expecting them to. =item * C<--nocolor> - disable ANSI colors in the formatted output.