#!/usr/bin/perl

use strict;
use warnings;
use File::Path qw(make_path remove_tree);
use File::Spec::Functions qw(catdir catfile);

no strict 'refs';

my $certdir = catdir($ENV{HOME}, ".libvirt/pki");

make_path($certdir, { mode => 0700 });

my $calog = catfile($certdir, "ca.log");
my $cainfo = catfile($certdir, "ca.info");
my $cacert = catfile($certdir, "ca-cert.pem");
my $cakey = catfile($certdir, "ca-key.pem");

die "syntax: $0 COMMAND" unless @ARGV;

my $cmd = shift @ARGV;

my $func = "do_" . lc ($cmd);

&{$func}();

sub do_init {
    die "syntax: $0 init ORG-NAME" unless @ARGV == 1;
    my $orgname = shift @ARGV;

    die "CA already exists" if -f $cacert || -f $cakey;

    print "Generating CA private key\n";
    (system "certtool --generate-privkey --outfile '$cakey' 1>>$calog 2>&1") == 0
	or die "cannot generate CA private key '$cakey'";

    open INFO, ">$cainfo" or die "cannot create $cainfo: $!";
    print INFO <<EOF;
cn = $orgname
ca
cert_signing_key
EOF
    close INFO or die "cannot write $cainfo: $!";

    print "Generating CA certificate\n";
    (system "certtool --generate-self-signed --load-privkey $cakey --template $cainfo --outfile $cacert 1>>$calog 2>&1") == 0
	or die "cannot generate CA certificate '$cacert'";

};

sub do_add_server {
    die "syntax: $0 add_server ORG-NAME HOSTNAME" unless @ARGV == 2;
    my $orgname = shift @ARGV;
    my $hostname = shift @ARGV;

    my $serverlog = catfile($certdir, "server-$hostname.log");
    my $serverinfo = catfile($certdir, "server-$hostname.info");
    my $serverkey = catfile($certdir, "server-key-$hostname.pem");
    my $servercert = catfile($certdir, "server-cert-$hostname.pem");

    die "Server already exists" if -f $serverkey || -f $servercert;

    print "Generating server private key\n";
    (system "certtool --generate-privkey --outfile '$serverkey' 1>>$serverlog 2>&1") == 0
	or die "cannot generate server private key '$serverkey'";

    open INFO, ">$serverinfo" or die "cannot create $serverinfo: $!";
    print INFO <<EOF;
organization = $orgname
cn = $hostname
tls_www_server
encryption_key
signing_key
EOF
    close INFO or die "cannot write $serverinfo: $!";

    print "Generating server certificate\n";
    (system "certtool --generate-certificate --load-privkey $serverkey --load-ca-certificate $cacert --load-ca-privkey $cakey --template $serverinfo --outfile $servercert 1>>$serverlog 2>&1") == 0
	or die "cannot generate server certificate '$servercert'";

}

sub do_add_client {
    die "syntax: $0 add_client ORG-NAME HOSTNAME [COUNTRY STATE LOCALITY]" unless @ARGV == 2 || @ARGV == 5;
    my $orgname = shift @ARGV;
    my $hostname = shift @ARGV;
    my $country = shift @ARGV;
    my $state = shift @ARGV;
    my $locality = shift @ARGV;

    my $clientlog = catfile($certdir, "client-$hostname.log");
    my $clientinfo = catfile($certdir, "client-$hostname.info");
    my $clientkey = catfile($certdir, "client-key-$hostname.pem");
    my $clientcert = catfile($certdir, "client-cert-$hostname.pem");

    die "Client already exists" if -f $clientkey || -f $clientcert;

    print "Generating client private key\n";
    (system "certtool --generate-privkey --outfile '$clientkey' 1>>$clientlog 2>&1") == 0
	or die "cannot generate client private key '$clientkey'";

    open INFO, ">$clientinfo" or die "cannot create $clientinfo: $!";
    print INFO "country = $country\n" if $country;
    print INFO "state = $state\n" if $state;
    print INFO "locality = $locality\n" if $locality;
    print INFO <<EOF;
organization = $orgname
cn = $hostname
tls_www_client
encryption_key
signing_key
EOF
    close INFO or die "cannot write $clientinfo: $!";

    print "Generating client certificate\n";
    (system "certtool --generate-certificate --load-privkey $clientkey --load-ca-certificate $cacert --load-ca-privkey $cakey --template $clientinfo --outfile $clientcert 1>>$clientlog 2>&1") == 0
	or die "cannot generate client certificate '$clientcert'";

}

sub do_delete_server {
    die "syntax: $0 delete_server HOSTNAME" unless @ARGV == 1;
    my $hostname = shift @ARGV;

    my $serverlog = catfile($certdir, "server-$hostname.log");
    my $serverinfo = catfile($certdir, "server-$hostname.info");
    my $serverkey = catfile($certdir, "server-key-$hostname.pem");
    my $servercert = catfile($certdir, "server-cert-$hostname.pem");

    unlink $serverlog;
    unlink $serverinfo;
    unlink $serverkey;
    unlink $servercert;
}

sub do_delete_client {
    die "syntax: $0 delete_client HOSTNAME" unless @ARGV == 1;
    my $hostname = shift @ARGV;

    my $clientlog = catfile($certdir, "client-$hostname.log");
    my $clientinfo = catfile($certdir, "client-$hostname.info");
    my $clientkey = catfile($certdir, "client-key-$hostname.pem");
    my $clientcert = catfile($certdir, "client-cert-$hostname.pem");

    unlink $clientlog;
    unlink $clientinfo;
    unlink $clientkey;
    unlink $clientcert;
}


sub do_deploy_server {
    die "syntax: $0 deploy_server HOSTNAME" unless @ARGV == 1;
    my $hostname = shift @ARGV;

    my $serverkey = catfile($certdir, "server-key-$hostname.pem");
    my $servercert = catfile($certdir, "server-cert-$hostname.pem");

    (system "ssh root\@$hostname mkdir -p /etc/pki/{CA,libvirt-ca,libvirt/private,libvirt-vnc,libvirt-spice}") == 0
	or die "cannot create pki directories on $hostname";

    (system "ssh root\@$hostname chmod go-rwx /etc/pki/libvirt/private") == 0
	or die "cannot chmod pki directories on $hostname";

    (system "scp $cacert $servercert $serverkey root\@$hostname:/etc/pki/libvirt-ca/") == 0
	or die "cannot copy $cacert $servercert $serverkey to $hostname:/etc/pki/libvirt-ca/";

    (system "ssh root\@$hostname '" .
     # libvirtd locations
     "ln -f -s /etc/pki/libvirt-ca/ca-cert.pem /etc/pki/CA/cacert.pem && " .
     "ln -f -s /etc/pki/libvirt-ca/server-key-$hostname.pem /etc/pki/libvirt/private/serverkey.pem && " .
     "ln -f -s /etc/pki/libvirt-ca/server-cert-$hostname.pem /etc/pki/libvirt/servercert.pem && " .

     # QEMU VNC locations
     "ln -f -s /etc/pki/libvirt-ca/ca-cert.pem /etc/pki/libvirt-vnc/ca-cert.pem && " .
     "ln -f -s /etc/pki/libvirt-ca/server-key-$hostname.pem /etc/pki/libvirt-vnc/server-key.pem && " .
     "ln -f -s /etc/pki/libvirt-ca/server-cert-$hostname.pem /etc/pki/libvirt-vnc/server-cert.pem && " .

     # QEMU SPICE locations
     "ln -f -s /etc/pki/libvirt-ca/ca-cert.pem /etc/pki/libvirt-spice/ca-cert.pem && " .
     "ln -f -s /etc/pki/libvirt-ca/server-key-$hostname.pem /etc/pki/libvirt-spice/server-key.pem && " .
     "ln -f -s /etc/pki/libvirt-ca/server-cert-$hostname.pem /etc/pki/libvirt-spice/server-cert.pem && " .

     # QEMU permissions
     "setfacl -m u:qemu:r /etc/pki/libvirt-ca/ca-cert.pem && " .
     "setfacl -m u:qemu:r /etc/pki/libvirt-ca/server-key-$hostname.pem && " .
     "setfacl -m u:qemu:r /etc/pki/libvirt-ca/server-cert-$hostname.pem '"
    ) == 0
	or die "cannot setup PKI symlinsk";

}

sub do_deploy_client {
    die "syntax: $0 deploy_client HOSTNAME" unless @ARGV == 1;
    my $hostname = shift @ARGV;

    my $clientkey = catfile($certdir, "client-key-$hostname.pem");
    my $clientcert = catfile($certdir, "client-cert-$hostname.pem");

    (system "ssh root\@$hostname mkdir -p /etc/pki/{CA,libvirt-ca,libvirt/private,libvirt-vnc,libvirt-spice}") == 0
	or die "cannot create pki directories on $hostname";

    (system "ssh root\@$hostname chmod go-rwx /etc/pki/libvirt/private") == 0
	or die "cannot chmod pki directories on $hostname";

    (system "scp $cacert $clientcert $clientkey root\@$hostname:/etc/pki/libvirt-ca/") == 0
	or die "cannot copy $cacert $clientcert $clientkey to $hostname:/etc/pki/libvirt-ca/";

    (system "ssh root\@$hostname '" .
     # libvirtd locations
     "ln -f -s /etc/pki/libvirt-ca/ca-cert.pem /etc/pki/CA/cacert.pem && " .
     "ln -f -s /etc/pki/libvirt-ca/client-key-$hostname.pem /etc/pki/libvirt/private/clientkey.pem && " .
     "ln -f -s /etc/pki/libvirt-ca/client-cert-$hostname.pem /etc/pki/libvirt/clientcert.pem && " .

     # QEMU VNC locations
     "ln -f -s /etc/pki/libvirt-ca/ca-cert.pem /etc/pki/libvirt-vnc/ca-cert.pem && " .
     "ln -f -s /etc/pki/libvirt-ca/client-key-$hostname.pem /etc/pki/libvirt-vnc/client-key.pem && " .
     "ln -f -s /etc/pki/libvirt-ca/client-cert-$hostname.pem /etc/pki/libvirt-vnc/client-cert.pem && " .

     # QEMU SPICE locations
     "ln -f -s /etc/pki/libvirt-ca/ca-cert.pem /etc/pki/libvirt-spice/ca-cert.pem && " .
     "ln -f -s /etc/pki/libvirt-ca/client-key-$hostname.pem /etc/pki/libvirt-spice/client-key.pem && " .
     "ln -f -s /etc/pki/libvirt-ca/client-cert-$hostname.pem /etc/pki/libvirt-spice/client-cert.pem '"
    ) == 0
	or die "cannot setup PKI symlinsk";

}


sub do_deploy_user_client {
    die "syntax: $0 deploy_client HOSTNAME" unless @ARGV == 1;
    my $hostname = shift @ARGV;

    my $clientkey = catfile($certdir, "client-key-$hostname.pem");
    my $clientcert = catfile($certdir, "client-cert-$hostname.pem");

    (system "ssh $hostname mkdir -p ~/{.pki/{CA,libvirt-ca,libvirt/private},.spicec}") == 0
	or die "cannot create pki directories on $hostname";

    (system "scp $cacert $clientcert $clientkey $hostname:~/.pki/libvirt-ca/") == 0
	or die "cannot copy $cacert $clientcert $clientkey to $hostname:~/.pki/libvirt-ca/";

    (system "ssh $hostname '" .
     # libvirtd locations
     "ln -f -s ~/.pki/libvirt-ca/ca-cert.pem ~/.pki/libvirt/cacert.pem && " .
     "ln -f -s ~/.pki/libvirt-ca/client-key-$hostname.pem ~/.pki/libvirt/clientkey.pem && " .
     "ln -f -s ~/.pki/libvirt-ca/client-cert-$hostname.pem ~/.pki/libvirt/clientcert.pem && " .

     # GTK-VNC locations
     "ln -f -s ~/.pki/libvirt-ca/ca-cert.pem ~/.pki/CA/cacert.pem && " .
     "ln -f -s ~/.pki/libvirt-ca/client-key-$hostname.pem ~/.pki/libvirt/private/clientkey.pem && " .

     # SPICE-GTK location
     "ln -f -s ~/.pki/libvirt-ca/ca-cert.pem ~/.spicec/spice_truststore.pem '"
    ) == 0
	or die "cannot setup PKI symlinsk";

}


sub do_purge {
    remove_tree($certdir);
}
