/*****************************************************************************
*
* Nagios check_dnssec_expiration plugin
*
* License: GPL
* Copyright (c) 2009 Marius Rieder <marius.rieder@durchmesser.ch>
*
* Description:
*
* This file contains the check_dnssec_expiration 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_expiration";
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;
ldns_rr_list *trusted_keys = NULL;
int verbose = 0;
int usevc = 0;
int timeout_interval = DEFAULT_SOCKET_TIMEOUT;
int warn = 172800;
int crit = 86400;
int checkState;

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

    /* C vars */
    int         i;
    int         longest = 0;
    uint32_t    exp, ttl, now;

    /* LDNS vars */
    ldns_rdf        *rd_domain;
    ldns_pkt        *pkt;
    ldns_resolver   *res;
    ldns_rdf        *rd;
    ldns_rr         *rr;
    ldns_rr         *rr_newest = NULL;
    ldns_rr_list    *rrl_keys;
    ldns_rr_list    *rrl_soa;
    ldns_rr_list    *rrl_soa_rrsig;
    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);

    rd_domain = ldns_dname_new_frm_str(domain_name);
    if (!rd_domain) {
        free(dns_server);
        free(domain_name);
        ldns_rdf_deep_free(rd_domain);
        unknown("Illegal domain name '%s'.", domain_name);
        return checkState;
    }

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

    ldns_resolver_set_usevc(res, usevc);

    free(dns_server);

    if (!res) {
        free(domain_name);
        ldns_rdf_deep_free(rd_domain);
        return checkState;
    }

    /* query for soa */
    pkt = ldns_resolver_query(res, rd_domain, LDNS_RR_TYPE_SOA,
                              LDNS_RR_CLASS_IN, LDNS_RD);
    if (!pkt) {
        unknown("error pkt sending");
        free(domain_name);
        ldns_rdf_deep_free(rd_domain);
        ldns_resolver_deep_free(res);
        return checkState;
    }

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

    if (!rrl_soa) {
        unknown("Invalid answer after SOA query for '%s'", domain_name);
        free(domain_name);
        ldns_rdf_deep_free(rd_domain);
        ldns_resolver_deep_free(res);
        ldns_pkt_free(pkt);
        return checkState;
    }

    rrl_soa_rrsig = ldns_dnssec_pkt_get_rrsigs_for_name_and_type(pkt,
                        rd_domain, LDNS_RR_TYPE_SOA);

    if (!rrl_soa_rrsig) {
        critical("SOA for '%s' is not signed.", domain_name);
        free(domain_name);
        ldns_rdf_deep_free(rd_domain);
        ldns_resolver_deep_free(res);
        ldns_pkt_free(pkt);
        ldns_rr_list_deep_free(rrl_soa);
        return checkState;
    }

    if (verbose >= 2) {
        printf("--[ Checked Domain ]----------------------------------------\n");
        ldns_rr_list_print(stdout, rrl_soa);
        ldns_rr_list_print(stdout, rrl_soa_rrsig);
        printf("------------------------------------------------------------\n");
    }

    ldns_pkt_free(pkt);

    /* query for keys */
    if (ldns_rr_list_rr_count(trusted_keys) > 0) {
        rrl_keys = ldns_validate_domain_dnskey(res, rd_domain, trusted_keys);
        if (ldns_rr_list_rr_count(rrl_keys) == 0) {
            rrl_keys = NULL;
        }
    } else {
        pkt = ldns_resolver_query(res, rd_domain, LDNS_RR_TYPE_DNSKEY, LDNS_RR_CLASS_IN, LDNS_RD);
        if (!pkt) {
            unknown("error pkt sending");
            free(domain_name);
            ldns_rdf_deep_free(rd_domain);
            ldns_resolver_deep_free(res);
            ldns_rr_list_deep_free(rrl_soa);
            ldns_rr_list_deep_free(rrl_soa_rrsig);
            return checkState;
        }

        rrl_keys = ldns_pkt_rr_list_by_type(pkt, LDNS_RR_TYPE_DNSKEY, LDNS_SECTION_ANSWER);
        ldns_pkt_free(pkt);
    }

    if (!rrl_keys) {
        critical("%s has no DNSKEYs.", domain_name);
        free(domain_name);
        ldns_rdf_deep_free(rd_domain);
        ldns_resolver_deep_free(res);
        ldns_rr_list_deep_free(rrl_soa);
        ldns_rr_list_deep_free(rrl_soa_rrsig);
        return checkState;
    }

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

    /* free rd_domain and resolver */
    ldns_rdf_deep_free(rd_domain);
    ldns_resolver_deep_free(res);

    /* search longest valid rrsig */
    for(i = 0; i < ldns_rr_list_rr_count(rrl_soa_rrsig); i++) {
        rr = ldns_rr_list_rr(rrl_soa_rrsig, i);

        /* check rrsigs */
        status = ldns_verify_rrsig_keylist(rrl_soa, rr, rrl_keys, NULL);
        if (status != LDNS_STATUS_OK)
            continue;

        ttl = ldns_rr_ttl(rr);

        rd = ldns_rr_rrsig_expiration(rr);
        exp = ldns_rdf2native_int32(rd);

        if ((exp-ttl) > longest) {
            if (rr_newest)
                ldns_rr_free(rr_newest);
            rr_newest = ldns_rr_clone(rr);
            longest = exp-ttl;
        }
    }

    /* free lists */
    ldns_rr_list_deep_free(rrl_soa);
    ldns_rr_list_deep_free(rrl_soa_rrsig);
    ldns_rr_list_deep_free(rrl_keys);

    if (!rr_newest) {
        critical("SOA has no valid RRSIG.");
        free(domain_name);
        return checkState;
    }

    now = time(0);
    ttl = ldns_rr_ttl(rr_newest);
    rd = ldns_rr_rrsig_expiration(rr_newest);
    exp = ldns_rdf2native_int32(rd);
    char *exp_str = ldns_rdf2str(rd);
    ldns_rr_free(rr_newest);

    if ((now+crit) > (exp-ttl))
        critical("RRSIG for %s expires to soon (%s).", domain_name, exp_str);
    else if ((now+warn) > (exp-ttl))
        warning("RRSIG for %s expires soon (%s).", domain_name, exp_str);
    else
        ok("RRSIG for %s valid till %s.", domain_name, exp_str);
    free(domain_name);
    free(exp_str);

    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'},
        {"tcp", no_argument, 0, 'T'},
        // Plugin specific args
        {"domain", required_argument, 0, 'D'},
        {"trusted-keys", required_argument, 0, 'k'},
        {"warning", required_argument, 0, 'w'},
        {"critical", required_argument, 0, 'c'},
        {0, 0, 0, 0}
    };

    if (argc < 2) {
        print_help();
	    return STATE_UNKNOWN;
    }

    while (1) {
        c = getopt_long(argc, argv, "hVvt:H:TD:k:w:c:", 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;
            case 'T': // -t --tcp
                usevc = 1;
                break;
            // Plugin specific args
            case 'D': // -D --domain
                setDomainname(optarg, &domain_name);
                break;
            case 'k': // -k --trusted-keys
                loadKeyfile(optarg, &trusted_keys);
                break;
            case 'w':
                warn = getTime(optarg);
                if (!warn) {
                    print_usage();
                    return AP_WARN;
                }
                break;
            case 'c':
                crit = getTime(optarg);
                if (!crit) {
                    print_usage();
                    return AP_WARN;
                }
                break;
            default: // unknown args
                print_usage();
                return AP_WARN;
        }
    }

    //validate_arguments
    if(!domain_name) {
        print_usage();
        return AP_WARN;
    }
    if(warn<=crit) {
        print_usage();
        return AP_WARN;
    }

    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 RRSIG expiration date.\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 (" -T, --tcp\n");
  printf ("    Use TCP for dns querys.\n");
  printf (" -D, --domain=DOMAIN\n");
  printf ("    The name of the domain to check.\n");
  printf (" -k, --trusted-keys=FILE\n");
  printf ("    File to read trust-anchors from.\n");
  printf (" -w, --warning=time[d|h|m|s]\n");
  printf ("    Return warning if elapsed time exceeds value. Default 2d\n");
  printf (" -c, --critical=time[d|h|m|s]\n");
  printf ("    Return critical if elapsed time exceeds value. Default 1d\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 [-k file] [-t timeout] [-w warn] [-c crit]\n",
          progname);
}

// vim: ts=4 expandtab filetype=c
