#!/usr/bin/env perl
# fqdn -- Retrieve a hostname's fully qualified name
#
# Uses forward lookups via AI_CANONNAME (like most tools do). Does not use
# reverse DNS, although that should be added as an option someday.

use warnings;
use strict;
use Getopt::Long qw(:config bundling no_ignore_case);
use Socket qw(AI_CANONNAME getaddrinfo);
use Sys::Hostname;

$::arg0 = (split m!/!, $0)[-1];

sub usage {
	print for
	"Usage: $::arg0 [-s [-d <domains>]] [-v] <name>...\n",
	"\n",
	"Options:\n",
	"  -s            perform a suffix search\n",
	"  -d domains    specify a custom list of domain suffixes\n",
	"  -v            be verbose about suffix expansion\n",
	"\n",
	#12345678........12345678........12345678........12345678........12345678........
	"In default mode, the given name is looked up using system DNS search suffixes\n",
	"and the alias chain is followed to find the canonnical name (like getaddrinfo\n",
	"with AI_CANONNAME).\n",
	"\n",
	"In suffix search mode, the given name is looked up using only the specified\n",
	"suffixes (aliases are NOT followed; each name is merely tested to see if it\n",
	"resolves at all).\n";
}

sub joinhost {
	my ($domain, $suffix) = @_;

	return $domain if $domain =~ /\.$/;
	return $domain if $suffix eq "";
	return $domain if $suffix eq ".";
	return $domain.".".$suffix;
}

sub get_resolv_conf_suffixes {
	if (open(my $fh, "</etc/resolv.conf")) {
		while (my $line = <$fh>) {
			if ($line =~ /^(domain|search) (.+)$/) {
				close $fh;
				return $2;
			}
		}
		close $fh;
		return "";
	}
}

sub get_fqdn_from_suffix {
	my ($host, $suffixes, $v) = @_;

	my @suffixes = @$suffixes;

	# Make sure we can find names which already are qualified.
	# (But don't do so for non-qualified names, to avoid the delay
	# caused by mDNS/LLMNR lookups.)
	push @suffixes, "." if $host =~ /\./;

	for my $suffix (@suffixes) {
		my $fqdn = joinhost($host, $suffix);
		warn "fqdn: trying to resolve '$fqdn'\n" if $v;
		my ($err, @res) = getaddrinfo($fqdn, undef, {});
		if (@res && !$err) {
			# Return $fqdn instead of canonname, because in
			# this case it is not our goal to chase CNAMEs,
			# only to discover the correct domain suffix.
			if ($fqdn =~ /\./) {
				return $fqdn;
			} else {
				$err = "empty suffix was specified";
			}
		}
		warn "fqdn: lookup of '$fqdn' failed: $err\n" if $v;
	}
	warn "fqdn: could not find '$host' with specified suffixes\n";
	return undef;
}

sub get_fqdn_from_cnames {
	my ($host, $v) = @_;

	my ($err, @res) = getaddrinfo($host, undef, {flags => AI_CANONNAME});
	if (@res && !$err) {
		return $res[0]->{canonname};
	}
	warn "fqdn: could not resolve '$host': $err\n";
	return undef;
}

my $search = 0;
my $suffixes = $ENV{LOCALDOMAIN};
my $verbose = 0;

GetOptions(
	"s|search" => \$search,
	"d|domains=s" => \$suffixes,
	"v|verbose" => \$verbose,
	"help" => sub { usage(); exit(0); },
) || exit(2);

if ($search && !length($suffixes)) {
	$suffixes = get_resolv_conf_suffixes();
	warn "fqdn: using system suffixes '$suffixes'\n" if $verbose;
}

my @suffixes = split(/[:, ]/, $suffixes // "");

my @args = @ARGV ? @ARGV : hostname();
my $errs = 0;

for my $arg (@args) {
	my $res;

	if ($search) {
		$res = get_fqdn_from_suffix($arg, \@suffixes, $verbose);
	} else {
		$res = get_fqdn_from_cnames($arg, $verbose);
	}

	if ($res) {
		print "$res\n";
	} else {
		print "$arg\n"; ++$errs;
	}
}

exit(!!$errs);
