/*****************************************************************************
*
* Nagios check_dnssec_trace plugin
*
* License: GPL
* Copyright (c) 2009 Marius Rieder <marius.rieder@durchmesser.ch>
*
* Description:
*
* This file contains the check_dnssec_trace plugin
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*
*****************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <getopt.h>
#include <ldns/ldns.h>

#include "utils.h"

const char *progname = "check_dnssec_trace";
const char *version = PACKAGE_VERSION;
const char *copyright = "2009";
const char *email = "marius.rieder@durchmesser.ch";

int process_arguments(int, char **);
void print_help(void);
void print_usage(void);

/* Global Vars */
char *dns_server;
char *domain_name;
char *domain_trace;
ldns_rr_list *trusted_keys = NULL;
int verbose = 0;
int timeout_interval = DEFAULT_SOCKET_TIMEOUT;
int checkState;

int main(int argc, char **argv) {

    /* C vars */
    int     i;
    int     soa_valid = 0;
    int     ns_valid = 0;

    /* LDNS vars */
    ldns_rdf        *rd_domain;
    ldns_rdf        *rd_trace;
    ldns_rdf        *rd_cdomain;
    ldns_pkt        *pkt;
    ldns_resolver   *res;
    ldns_rr         *rr;
    ldns_rr_list    *rrl;
    ldns_rr_list    *rrl_domain_soa;
    ldns_rr_list    *rrl_domain_soa_rrsig;
    ldns_rr_list    *rrl_domain_ns;
    ldns_rr_list    *rrl_domain_ns_rrsig;
    ldns_rr_list    *rrl_valid_keys;
    ldns_status	    status;

    /* Set signal handling and alarm */
    if (signal(SIGALRM, timeout_alarm_handler) == SIG_ERR) {
        unknown("Cannot catch SIGALRM");
        return checkState;
    }

    /* process commandline arguments */
    i = process_arguments(argc, argv);
    if (i != AP_OK) {
        checkState = STATE_OK;
        free(dns_server);
        free(domain_name);
        if (i == AP_WARN)
            unknown("Could not parse arguments");
        return checkState;
    }

    /* start plugin timeout */
    alarm(timeout_interval);

    /* Check domain validity */
    rd_domain = ldns_dname_new_frm_str(domain_name);
    if (!rd_domain) {
        unknown("Illegal domain name '%s'.", domain_name);
        free(dns_server);
        free(domain_name);
        free(domain_trace);
        ldns_rr_list_deep_free(trusted_keys);
        return checkState;
    }

    /* Check trace start validity */
    rd_trace = ldns_dname_new_frm_str(domain_trace);
    if (!rd_trace) {
        unknown("Illegal trace from name '%s'.", domain_trace);
        free(dns_server);
        free(domain_name);
        free(domain_trace);
        ldns_rr_list_deep_free(trusted_keys);
        ldns_rdf_deep_free(rd_domain);
        return checkState;
    }

    /* Check domain is subdomain from trace start */
    if (!ldns_dname_is_subdomain(rd_domain, rd_trace)) {
        unknown("'%s' is not a subdomain of '%s'.", domain_name,
                                                    domain_trace);
        free(dns_server);
        free(domain_name);
        free(domain_trace);
        ldns_rr_list_deep_free(trusted_keys);
        ldns_rdf_deep_free(rd_domain);
        ldns_rdf_deep_free(rd_trace);
        return checkState;
    }

    /* Add trusted keys for trace domain to rrl_valid_keys. */
    rrl_valid_keys = ldns_rr_list_new();
    for(i = 0; i < ldns_rr_list_rr_count(trusted_keys); i++) {
        rr = ldns_rr_list_rr(trusted_keys, i);
        if (ldns_dname_compare(ldns_rr_owner(rr),rd_trace) == 0)
            ldns_rr_list_push_rr(rrl_valid_keys, ldns_rr_clone(rr));
    }
    ldns_rr_list_deep_free(trusted_keys);

    if (ldns_rr_list_rr_count(rrl_valid_keys) == 0) {
        critical("No trusted key for trace start '%s'", domain_trace);
        free(dns_server);
        free(domain_name);
        free(domain_trace);
        ldns_rdf_deep_free(rd_domain);
        ldns_rdf_deep_free(rd_trace);
        ldns_rr_list_deep_free(rrl_valid_keys);
        return checkState;
    }

    if (verbose >= 2) {
        printf("--[ Trusted keys used ]-------------------------------------\n");
        ldns_rr_list_sort(rrl_valid_keys);
        ldns_rr_list_print(stdout, rrl_valid_keys);
        printf("------------------------------------------------------------\n");
    }

    /* create a new resolver with dns_server or server from /etc/resolv.conf */
    res = createResolver(dns_server);
    if (!res)
        return checkState;
    ldns_resolver_set_dnssec_anchors(res, rrl_valid_keys);
    free(dns_server);

    /* check domain exists */
    pkt = ldns_resolver_query(res, rd_domain, LDNS_RR_TYPE_ANY,
                LDNS_RR_CLASS_IN, LDNS_RD);
    if (!pkt) {
        unknown("error pkt sending");
        free(domain_name);
        free(domain_trace);
        ldns_rdf_deep_free(rd_domain);
        ldns_rdf_deep_free(rd_trace);
        ldns_rr_list_deep_free(rrl_valid_keys);
        ldns_resolver_free(res);
        return checkState;
    }

    rrl_domain_soa = ldns_pkt_rr_list_by_name_and_type(pkt, rd_domain,
                        LDNS_RR_TYPE_SOA, LDNS_SECTION_ANSWER);

    if (!rrl_domain_soa) {
        critical("Domain '%s' not found.", domain_name);
        free(domain_name);
        free(domain_trace);
        ldns_rdf_deep_free(rd_domain);
        ldns_rdf_deep_free(rd_trace);
        ldns_resolver_deep_free(res);
        ldns_pkt_free(pkt);
        return checkState;
    }

    rrl_domain_soa_rrsig = ldns_dnssec_pkt_get_rrsigs_for_name_and_type(pkt,
                                rd_domain, LDNS_RR_TYPE_SOA);

    if (!rrl_domain_soa_rrsig) {
        critical("Domain '%s' not signed.", domain_name);
        free(domain_name);
        free(domain_trace);
        ldns_rdf_deep_free(rd_domain);
        ldns_rdf_deep_free(rd_trace);
        ldns_resolver_deep_free(res);
        ldns_pkt_free(pkt);
        ldns_rr_list_deep_free(rrl_domain_soa);
        return checkState;
    }

    rrl_domain_ns = ldns_pkt_rr_list_by_name_and_type(pkt, rd_domain,
                        LDNS_RR_TYPE_NS, LDNS_SECTION_ANSWER);
    rrl_domain_ns_rrsig = ldns_dnssec_pkt_get_rrsigs_for_name_and_type(pkt,
                                rd_domain, LDNS_RR_TYPE_NS);

    ldns_pkt_free(pkt);

    if (verbose >= 2) {
        printf("--[ Checked Domain ]----------------------------------------\n");
        ldns_rr_list_print(stdout, rrl_domain_soa);
        ldns_rr_list_print(stdout, rrl_domain_soa_rrsig);
        ldns_rr_list_print(stdout, rrl_domain_ns);
        ldns_rr_list_print(stdout, rrl_domain_ns_rrsig);
        printf("------------------------------------------------------------\n");
    }

    /* Fetch valid keys from top down */
    i = ldns_dname_label_count(rd_domain) - ldns_dname_label_count(rd_trace);
    for (; i>=0; i--) {
        rd_cdomain = ldns_dname_clone_from(rd_domain, i);
        if (verbose) {
            char *str = ldns_rdf2str(rd_cdomain);
            printf("Trace: %s\n", str);
            free(str);
        }
        rrl = ldns_fetch_valid_domain_keys(res, rd_cdomain, rrl_valid_keys, &status);

        if (verbose >= 2) {
            printf("--[ Valid Keys ]----------------------------------------\n");
            ldns_rr_list_sort(rrl);
            ldns_rr_list_print(stdout, rrl);
            printf("------------------------------------------------------------\n");
        }


        ldns_rr_list_cat(rrl_valid_keys, rrl);
        ldns_rr_list_free(rrl);

        ldns_rdf_deep_free(rd_cdomain);
    }

    ldns_rdf_deep_free(rd_trace);
    ldns_rdf_deep_free(rd_domain);

    /* Validate SOA */
    for(i = 0; i < ldns_rr_list_rr_count(rrl_domain_soa_rrsig); i++) {
        rr = ldns_rr_list_rr(rrl_domain_soa_rrsig, i);
        status = ldns_verify_rrsig_keylist(rrl_domain_soa, rr, rrl_valid_keys, NULL);
        if (status == LDNS_STATUS_OK)
            soa_valid++;
    }

    ldns_rr_list_deep_free(rrl_domain_soa);
    ldns_rr_list_deep_free(rrl_domain_soa_rrsig);

    if (soa_valid == 0) {
        critical("No valid Signatur for SOA of '%s'", domain_name);
        free(domain_name);
        free(domain_trace);
        ldns_resolver_deep_free(res);
        ldns_rr_list_deep_free(rrl_domain_ns);
        ldns_rr_list_deep_free(rrl_domain_ns_rrsig);
        return checkState;
    }

    /* Validate NS */
    for(i = 0; i < ldns_rr_list_rr_count(rrl_domain_ns_rrsig); i++) {
        rr = ldns_rr_list_rr(rrl_domain_ns_rrsig, i);

        status = ldns_verify_rrsig_keylist(rrl_domain_ns, rr, rrl_valid_keys, NULL);
        if (status == LDNS_STATUS_OK)
            ns_valid++;
    }

    ldns_rr_list_deep_free(rrl_domain_ns);
    ldns_rr_list_deep_free(rrl_domain_ns_rrsig);
    ldns_resolver_deep_free(res);

    if (ns_valid == 0) {
        critical("No valid Signatur for NS of '%s'", domain_name);
        free(domain_name);
        free(domain_trace);
        return checkState;
    }

    ok("Trust for '%s' successfull traces from '%s'", domain_name,
        domain_trace);
    free(domain_name);
    free(domain_trace);
    return checkState;
}

/*
 * Process and check command line args
 */
int process_arguments(int argc, char **argv) {

    int c;
    int opt_index = 0;

    static struct option long_opts[] = {
        // Default args
        {"help", no_argument, 0, 'h'},
        {"version", no_argument, 0, 'V'},
        {"verbose", no_argument, 0, 'v'},
        {"timeout", required_argument, 0, 't'},
        {"hostname", required_argument, 0, 'H'},
        // Plugin specific args
        {"domain", required_argument, 0, 'D'},
        {"trace-from", required_argument, 0, 'T'},
        {"trusted-keys", required_argument, 0, 'k'},
        {0, 0, 0, 0}
    };

    if (argc < 2) {
        print_help();
        exit (STATE_OK);
    }

    while (1) {
        c = getopt_long(argc, argv, "hVvt:H:D:T:k:", long_opts, &opt_index);
        if (c == -1 || c == EOF)
            break;

        switch (c) {
            // Default args
            case 'h': // -h --help
                print_help();
                return AP_EXIT;
            case 'V': // -V --version
                print_version();
                return AP_EXIT;
            case 'v': // -v --verbose
                verbose += 1;
                break;
            case 't': // -t --timeout
                timeout_interval = atoi(optarg);
                break;
            case 'H': // -H --hostname
                setHostname(optarg, &dns_server);
                break;
            // Plugin specific args
            case 'D': // -D --domain
                setDomainname(optarg, &domain_name);
                break;
            case 'T': // -T --trace-from
                setDomainname(optarg, &domain_trace);
                break;
            case 'k': // -k --trusted-keys
                loadKeyfile(optarg, &trusted_keys);
                break;
            default: // unknown args
                print_usage();
                return AP_WARN;
        }
    }

    //validate_arguments
    if(!domain_name) {
        print_usage();
        return AP_WARN;
    }
    if(!domain_trace) {
        domain_trace = malloc(2);
        strncpy(domain_trace,".",2);
    }

    return AP_OK;
}

void print_help(void) {
  printf ("%s %s\n", progname, version);
  printf ("Copyright (c) 2009 Marius Rieder <marius.rieder@durchmesser.ch>\n\n");

  printf ("This plugin uses the ldns library to check the trust chain.\n\n");

  print_usage ();

  printf ("Options:\n");
  printf (" -h, --help\n");
  printf ("    Print detailed help screen.\n");
  printf (" -V, --version\n");
  printf ("    Print version information.\n");
  printf (" -v, --verbose\n");
  printf ("    Increase verbosity.\n");
  printf (" -t, --timeout=INTEGER\n");
  printf ("    Seconds before connection times out. (default: %d)\n",
    DEFAULT_SOCKET_TIMEOUT );
  printf (" -H, --hostname=HOST\n");
  printf ("    The name or address of your DNS server you want to use for the lookup.\n");
  printf (" -D, --domain=DOMAIN\n");
  printf ("    The name of the domain to check.\n");
  printf (" -T, --trace-from=DOMAIN\n");
  printf ("    The name of the domain to trace from. (default: .)\n");
  printf (" -k, --trusted-keys=FILE\n");
  printf ("    File to read trust-anchors from.\n\n");

  printf ("Send email to marius.rieder@durchmesser.ch if you have questions\n");
  printf ("regarding use of this software.\n\n");
}

void print_usage(void) {
  printf ("Usage: %s [-H host] -D domain [-T domain] [-k file] [-t timeout]\n",
          progname);
}

// vim: ts=4 expandtab filetype=c
