#!/usr/bin/perl
package IkiWiki::Hosting;

no lib '.';
use warnings;
use strict;
use IkiWiki;
use IkiWiki::Hosting;

sub meta_createkey {
	required => [qw{client-hostname}],
	options => [qw{}],
	description => "creates key for a client, and outputs the key filenames",
	section => "primary",
}
sub createkey {
	my $client=shift;

	assert_root();
	assert_client_haskey($client, 0);

	shell("mkdir", "-p", "$config{keydir}/dns");
	shell("chmod", "700", $config{keydir});
	chdir "$config{keydir}/dns" || die "chdir $config{keydir}/dns: $!";
	my $keyid=`dnssec-keygen -b 512 -a HMAC-MD5 -n HOST "$client."`;
	chomp $keyid;
	if (! length $keyid) {
		error "dnssec-keygen failed";
	}
	setupbind();

	return "$config{keydir}/dns/$keyid.key", "$config{keydir}/dns/$keyid.private";
}

sub meta_deletekey {
	required => [qw{client-hostname}],
	options => [qw{}],
	description => "removes key files for a client",
	section => "primary",
}
sub deletekey {
	my $client=shift;

	assert_root();
	assert_client_haskey($client, 1);

	my @keys=(dns_private_keys($client), dns_keys($client));
	shell("rm", "-f", @keys);
	setupbind();
}

sub meta_setupbind {
	required => [qw{}],
	options => [qw{}],
	description => "generates bind config files that allow updates using keys",
	section => "utility",
}
sub setupbind {
	assert_root();

	# use template to generate named.conf file
	my $named_conf="/etc/bind/ikisite.conf";
	my @keys = map { {
		key => dns_keyid($_),
		secret => dns_keysecret($_),
	} } dns_private_keys();

	my @domains;
	foreach my $domain (split ' ', $config{domains}) {
		my $basedir="/etc/bind/$domain";
		my $zonefile="$basedir/zonefile";

		shell("mkdir", "-p", $basedir);

		# bind needs permission to write to the basedir
		shell("chown", "root:bind", $basedir);
		shell("chmod", "770", $basedir);

		if (! -e $zonefile) {
			# use template to generate stub zone file
			outtemplate($zonefile, "zonefile.tmpl",
				domain => $domain,
			);
		}

		push @domains, {
			domain => $domain,
			zonefile => $zonefile,
			keys => \@keys,
		};
	}
	my $template=IkiWiki::Hosting::ikisite_template("named.conf.tmpl");
	$template->param(
		keys => \@keys,
		dns_domains => \@domains,
	);
	open(OUT, ">", $named_conf) || error "$named_conf: $!";
	# contains keys, so needs to be only readable by bind
	shell("chown", "root:bind", $named_conf);
	shell("chmod", "640", $named_conf);
	print OUT $template->output;
	close OUT;
	
	# add include to named.conf.local
	my $named_conf_comment="// automatically added by ikidns";
	open(NAMED_CONF_IN, "<", "/etc/bind/named.conf.local") || error("named.conf.local: $!");
	open(NAMED_CONF_OUT, ">", "/etc/bind/named.conf.local.tmp") || error("named.conf.local.tmp: $!");
	while (<NAMED_CONF_IN>) {
		chomp;
		print NAMED_CONF_OUT "$_\n"
			unless /^include\s+\".*\";\s*\Q$named_conf_comment\E/;
	}
	close NAMED_CONF_IN;
	print NAMED_CONF_OUT "include \"$named_conf\"; $named_conf_comment\n";
	close NAMED_CONF_OUT || error "close named.conf.tmp: $!";
	shell("mv", "-f", "/etc/bind/named.conf.local.tmp", "/etc/bind/named.conf.local");

	shell("service", "bind9", "restart");
}

sub meta_letsencrypt {
	required => [qw{}],
	options => [qw{}],
	description => "use Lets Encrypt to get wildcard https certificates",
	section => "utility",
}
sub letsencrypt {
	assert_root();
	
	my $certdir=$config{wildcard_ssl_cert_dir};
	if (! defined $certdir || ! length $certdir) {
		error "ikiwiki-hosting.conf does not have wildcard_ssl_cert_dir configured";
	}

	# Use a separate bind key for certbot.
	my @keys=dns_private_keys("certbot");
	if (! @keys) {
		createkey("certbot");
		setupbind();
	}
	@keys=dns_private_keys("certbot");
	my $secret = dns_keysecret($keys[0]);

	# This file needs to continue to exist so certbot can use it for
	# renewals.
	my $certbot_credfile="$config{keydir}/dns/certbot.credentials";
	open(OUT, ">", $certbot_credfile) || error "$certbot_credfile: $!";
	# contains keys, so needs to be only readable by root
	shell("chown", "root:root", $certbot_credfile);
	shell("chmod", "600", $certbot_credfile);
	print OUT "dns_rfc2136_server = 127.0.0.1\n";
	print OUT "dns_rfc2136_port = 53\n";
	print OUT "dns_rfc2136_name = certbot.\n";
	print OUT "dns_rfc2136_secret = $secret\n";
	print OUT "dns_rfc2136_algorithm = HMAC-MD5\n";
	close OUT;
	
	foreach my $domain (split ' ', $config{domains}) {
		shell("certbot", "certonly",
			"--text", "--noninteractive", "--quiet", "--keep-until-expiring",
			"--agree-tos", "--email=$config{adminemail}",
			"--dns-rfc2136", 
			"--dns-rfc2136-credentials", $certbot_credfile,
			# use ACMEv2 endpoint
			"--server", "https://acme-v02.api.letsencrypt.org/directory",
			# request cert for top level domain
			"--domain=$domain",
			# request wildcard cert
                        "--domain=*.$domain");
		my $livedir="/etc/letsencrypt/live/$domain";

		# Symlink into place as wildcard cert, so ikisite will use it.
		shell("mkdir", "-p", "$certdir/$domain");
		shell("ln", "-sf", "$livedir/cert.pem", "$certdir/$domain/cert.pem");
		shell("ln", "-sf", "$livedir/privkey.pem", "$certdir/$domain/privkey.pem");
		shell("ln", "-sf", "$livedir/fullchain.pem", "$certdir/$domain/fullchain.pem");
	}
	
	print "Wildcard cert(s) installed, and will be used by ikisite when configuring sites\n";
}

#############################################################################

sub assert_client_haskey {
	my $client=shift;
	my $true=shift;

	my @keys=(dns_private_keys($client), dns_keys($client));
	if (! $true && @keys) {
		error("dns key already exists for $client");
	}
	elsif ($true && ! @keys) {
		error("dns key does not exist for $client");
	}
}

sub dns_private_keys {
	my $client=shift || "*";

	my @list=glob("$config{keydir}/dns/K$client.+*.private");
	return @list;
}

sub dns_keys {
	my $client=shift || "*";

	my @list=glob("$config{keydir}/dns/K$client.+*.key");
	return @list;
}

sub dns_keyid {
	my $keyfile=shift;

	$keyfile=IkiWiki::basename($keyfile);
	$keyfile=~s/^K//;
	$keyfile=~s/\+.*//;
	return $keyfile;
}

sub dns_keysecret {
	my $keyfile=shift;

	my $file=readfile($keyfile);
	my ($key)=$file=~/^Key:\s+(.*)$/m;
	return $key;
}

main();
