#!/usr/bin/gawk -f

function fatal(output) {
  printf("[FATAL] %s\n", output) >"/dev/stderr";
}

function debug(output) {
  if (DEBUG) {
    printf("[DEBUG] %s\n", output) >"/dev/stderr";
  }
}

function dump(id, entry,  i, out) {
  # local i    : increment
  # local out  : output string
  out = sprintf("%s:", id);
  for (i = 1; i <= NATTRS; i++)
    out = out sprintf(" %s=<%s>", IATTRS[i], entry[i]);
  printf("%s\n", out) > "/dev/stderr";
}


function rewrite(re, a, new, t, cap) {
  # arg re      : rewrite rule
  # arg a       : array of string to use (return of match())
  # local new   : new string
  # local t     : temporary index
  # local cap   : temporary capture

  new = "";
  while(length(re) > 1) {
    if (!(t = index(re, "\\"))) break;
    if (t > 1) new = new substr(re, 1, t-1);
    if (match(substr(re, t+1), /^(([0-9]+)|\{([0-9]+)\}|\[([0-9]+)\])/, cap)) {
      if (cap[3] != "") {
        # \{xx} where x can be > 9
        new = new a[strtonum(cap[3])];
      }
      else if (cap[4] != "") {
        # \[xx] where x can be > 9, with alias resolution
        new = new (length(ALIAS) > 0 && ALIAS[a[strtonum(cap[4])]] \
          ? ALIAS[a[strtonum(cap[4])]] : a[strtonum(cap[4])])
      }
      else {
        # \x where x is a single digit
        new = new a[strtonum(cap[2])];
      }
      t = t + 1 + cap[0, "length"];
    }
    else {
      new = new "\\";
      t = t + 1 + (substr(re, t+1, 1) == "\\" ? 1 : 0);
    }
    re = substr(re, t);
  }
  new = new re;
  return new;
}

function notify_nagios(entry) {
  printf("[%lu] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%d;%s\n",
         systime(), entry[HOSTNAME], entry[PROGRAM],
         entry[NAGIOS_STATE], entry[MSG]) >> NAGIOS_EXTERNAL_COMMAND_FILE;
  close(NAGIOS_EXTERNAL_COMMAND_FILE);
}

function rsyslog_ack() {
  printf("OK\n");
  fflush();
}

function cleanup_watchers() {
  # local k   : key
  # local now : current time

  # cleanup watch list
  if (length(WATCHERS_TIMEOUT)) {
    now = systime();
    for (k in WATCHERS_TIMEOUT) {
      debug(sprintf("watch(%s): %d < now: %d", k, WATCHERS_TIMEOUT[k], WATCHERS_TIMEOUT[k] < now));
      if (WATCHERS_TIMEOUT[k] < now) {
        # timeout pass, cleanup
        delete WATCHERS_COUNT[k];
        delete WATCHERS_TIMEOUT[k];
      }
    }
  }
}

function aliases(line) {
  while (getline <ALIASES && !ERRNO) {
    line++;

    # bypass coments
    if (substr($0, 1, 1) == "#")
      continue;

    # store each alias
    for (i = 1; $i; i++) {
      ALIAS[$i] = $1;
      debug(sprintf("ALIAS[%s] = %s", $i, $1));
    }
  }
}

function iconv_utf8(s) {
  if (match(s, "[\177-\377]")) {
    s = gensub("\342\200\231", "'", "g", s);
    s = gensub("\302\240", " ", "g", s);
    s = gensub("\302\251", "@", "g", s);
  }
  return s;
}

function iconv_cp1252(s) {
  if (match(s, "[\177-\377]")) {
    s = gensub("\222", "'", "g", s);
    s = gensub("\240", " ", "g", s);
    s = gensub("\251", "@", "g", s);
    s = gensub("\253", "«", "g", s);
    s = gensub("\273", "»", "g", s);
    s = gensub("\265", "û", "g", s);
    s = gensub("\340", "à", "g", s);
    s = gensub("\342", "â", "g", s);
    s = gensub("\347", "ç", "g", s);
    s = gensub("\351", "é", "g", s);
    s = gensub("\311", "É", "g", s);
    s = gensub("\350", "è", "g", s);
    s = gensub("\352", "ê", "g", s);
    s = gensub("\356", "ê", "g", s);
    s = gensub("\364", "ô", "g", s);
    s = gensub("\234", "œ", "g", s);
    s = gensub("\373", "û", "g", s);
  }
  return s;
}

function config( record, i, iattr, value, line) {
  # local record  # current record
  # local i       # increment
  # local iattr   # attribut increment
  # local value   # current value
  # local line    # line number

  record = 1;
  # initialize record to stop
  FLAGS[record] = 0x0002;
  while (getline <CONFIG && !ERRNO) {
    line++;
    # convert UTF8 Right Single Quotation Mark & Left Single Quotation Mark
    $0 = iconv_utf8($0);
    # bypass coments
    if (substr($0, 1, 1) == "#")
      continue;

    # empty lines are new sections
    if (NF == 0) {
      # empty line
      if (RULENAMES[record]) {
        record++;
      }
      # initialize (or re-initialize)
      for (i = 1; i <= NATTRS; i++) {
        PATTERNS[record,i] = "";
        REWRITES[record,i] = "";
      }
      # initialize record to stop
      FLAGS[record] = 0x0002;
      continue;
    }

    # detect & treat keywords
    if (substr($1,1,7) == "PATTERN") {
      if (substr($1,8,1) == ":") {
        iattr = IATTRS[substr($1,9)];
        if (!iattr) {
          fatal(sprintf("%s:%d: bad attr '%s'", CONFIG, line, substr($1,9)));
          exit(1);
        }
      }
      else {
        iattr = MSG;
      }
      value = substr($0, index($0, " ")+1);
      if (substr(value, 1, 1) != "\x22" || substr(value, length(value), 1) != "\x22")
        fatal(sprintf("%s:%d: config error: '%s'", CONFIG, line, value));
      value = substr(value, 2, length(value)-2);
      PATTERNS[record,iattr] = gensub("\\\\\"", "\"", "g", value);
      # debug(sprintf("PATTERNS[%d,%d] = %s", record, iattr, value));
    }
    else if (substr($1,1,7) == "REWRITE") {
      if (substr($1,8,1) == ":") {
        iattr = IATTRS[substr($1,9)];
        if (!iattr) {
          fatal(sprintf("%s:%d: bad attr '%s'", CONFIG, line, substr($1,9)));
          exit(1);
        }
      }
      else {
        iattr = MSG;
      }
      value = substr($0, index($0, " ")+1);
      if (substr(value, 1, 1) != "\x22" || substr(value, length(value), 1) != "\x22")
        fatal(sprintf("%s:%d: config error: '%s'", CONFIG, line, value));
      value = substr(value, 2, length(value)-2);
      REWRITES[record,iattr] = gensub("\\\\\"", "\"", "g", value);
      # debug(sprintf("REWRITES[%d,%d] = %s", record, iattr, value));
    }
    else if (substr($1,1,5) == "ATTR:") {
      iattr = substr($1,6);
      value = substr($0, index($0, " ")+1);
      if (substr(value, 1, 1) != "\x22" || substr(value, length(value), 1) != "\x22")
        fatal(sprintf("%s:%d: config error: '%s'", CONFIG, line, value));
      value = substr(value, 2, length(value)-2);
      RULEATTRS[record,iattr] = gensub("\\\\\"", "\"", "g", value);
      # debug(sprintf("REWRITES[%d,%d] = %s", record, iattr, value));
    }
    else if ($1 == "RULENAME") {
      value = substr($0, index($0, "\x22")+1);
      value = substr(value, 1, index(value, "\x22")-1);
      RULENAMES[record] = value;
    }
    else if ($1 == "THRESHOLD:COUNT") {
      THRESHOLDS_COUNT[record] = $2;
    }
    else if ($1 == "THRESHOLD:TIMEOUT") {
      THRESHOLDS_TIMEOUT[record] = $2;
    }
    else if ($1 == "DISABLE") {
      DISABLED[record] = 1;
    }
    else if ($1 == "CONTINUE") {
      FLAGS[record] = or(and(FLAGS[record],0xfff0),0x0001);
    }
    else if ($1 == "STOP") {
      FLAGS[record] = or(and(FLAGS[record],0xfff0),0x0002);
    }
    else if ($1 == "CRITICAL") {
      FLAGS[record] = or(and(FLAGS[record],0xff0f),0x0010);
    }
    else if ($1 == "WARNING") {
      FLAGS[record] = or(and(FLAGS[record],0xff0f),0x0020);
    }
    else if ($1 == "OK") {
      FLAGS[record] = or(and(FLAGS[record],0xff0f),0x0040);
    }
    else {
      fatal(sprintf("%s,%d: invalid keyword '%s'", CONFIG, line, $1));
    }
    if (!RULENAMES[record])
      RULENAMES[record] = sprintf("#%04d", record);
  }
  if (ERRNO) {
    fatal(sprintf("can't open '%s': %s", CONFIG, ERRNO));
    exit(1);
  }

  # remove potential empty last record
  if (!RULENAMES[record])
    delete RULENAMES[record];
}

function main(line, ea, e, r, ir, ia, i, a, notify, matches) {
  # local line    : current line
  # local ea      : entry array
  # local e       : current entry
  # local ir      : record increment
  # local ia      : attribut increment
  # local i       : local increment
  # local a       : some array
  # local matches : keep matches
  # local notify  : should notify flag

  # read record separated by Tabs
  F = "([^\\t]*)\\t"
  if (!match(line, "^" F F F F F F F " ?(.*)", ea)) {
    rsyslog_ack();
    return;
  }

  if (DEBUG >= 2) dump("parser", ea);

  # look for known patterns
  for (ir = 1; ir <= length(RULENAMES); ir++) {
    # by pass disabled rules
    if (DISABLED[ir]) continue;

    # need notification ?
    notify = 0;
    delete matches;
    delete e;

    # loop on each rules attr
    for (ia = 1; ia <= NATTRS; ia++) {
      e[ia] = ea[ia];
      if (!PATTERNS[ir,ia]) {
        if (ia == HOSTNAME && ALIAS[e[ia]]) {
          # rewrite HOSTNAME to aliases even if pattern empty
          e[ia] = ALIAS[e[ia]];
        }
        continue;
      }

      if (DEBUG >= 5) debug(sprintf("%s: match(%s,%s,%s) ?", RULENAMES[ir], IATTRS[ia], e[ia], PATTERNS[ir,ia]));

      if (match(e[ia], PATTERNS[ir,ia], a)) {
        # pattern found !
        notify = 1;
        if (DEBUG >= 5) dump("attr-match", e);

        # backup all matches
        for (i = 1; a[i, "start"]; i++) {
          matches[length(matches)+1] = a[i];
        }

        if (REWRITES[ir,ia]) {
          e[ia] = rewrite(REWRITES[ir,ia], a);
          # dump("rewrite", e);
        }
      }
      else {
        notify = 0;
        break;
      }
    }

    if (notify == 0) continue;

    if (DEBUG >= 2) dump("match", ea);
    if (DEBUG >= 2) dump("rewrite", e);

    # compute new attrs
    e[RULENAME] = RULENAMES[ir];
    if (RULEATTRS[ir,"NAGIOS_FORCED_HOST"]) {
      if (index(RULEATTRS[ir,"NAGIOS_FORCED_HOST"], "\\")) {
        e[HOSTNAME] = rewrite(RULEATTRS[ir,"NAGIOS_FORCED_HOST"], matches);
      }
      else {
        e[HOSTNAME] = RULEATTRS[ir,"NAGIOS_FORCED_HOST"];
      }
    }
    if (RULEATTRS[ir,"NAGIOS_FORCED_SERVICE"]) {
      if (index(RULEATTRS[ir,"NAGIOS_FORCED_SERVICE"], "\\")) {
        e[PROGRAM] = rewrite(RULEATTRS[ir,"NAGIOS_FORCED_SERVICE"], matches);
      }
      else {
        e[PROGRAM] = RULEATTRS[ir,"NAGIOS_FORCED_SERVICE"];
      }
    }

    e[NAGIOS_STATE] = NAGIOS_STATES[and(FLAGS[ir],0x00f0)]

    if (THRESHOLDS_COUNT[ir]) {
      # cleanup watchers before adding new ones
      cleanup_watchers();
      # initialize watcher with fixe threshold timeout, default 60sec
      if (!WATCHERS_COUNT[e[HOSTNAME],e[PROGRAM]])
        WATCHERS_TIMEOUT[e[HOSTNAME],e[PROGRAM]] = systime() + \
          (THRESHOLDS_TIMEOUT[ir]?THRESHOLDS_TIMEOUT[ir]:60);
      # increment counter
      WATCHERS_COUNT[e[HOSTNAME],e[PROGRAM]]++;
      # increment task and check for threshold
      if (WATCHERS_COUNT[e[HOSTNAME],e[PROGRAM]] < THRESHOLDS_COUNT[ir])
        notify = 0;
      else
        delete WATCHERS_COUNT[e[HOSTNAME],e[PROGRAM]];
    }

    if (notify == 0) continue;

    # start actions
    if (DEBUG >= 1) dump("notify", e);

    if (NAGIOS_EXTERNAL_COMMAND_FILE) {
      notify_nagios(e);
    }

    # next event
    if (and(FLAGS[ir],0x0002)) {
      if (DEBUG >= 2) debug("Rule => stop");
      break;
    }
    if (DEBUG >= 2) debug("Rule => continue");
  }

  rsyslog_ack();
  return;
}

BEGIN {

  if (DEBUG >= 5) {
    printf("DEBUG=%s\n", DEBUG) >>"/dev/stderr";
    printf("CONFIG=%s, ALIASES=%s\n", CONFIG, ALIASES) >>"/dev/stderr";
    printf("NAGIOS_EXTERNAL_COMMAND_FILE=%s\n", NAGIOS_EXTERNAL_COMMAND_FILE) >>"/dev/stderr";
  }

  # read timeout on stding : 1000ms
  PROCINFO["/dev/stdin", "READ_TIMEOUT"] = 1000;
  PROCINFO["/dev/stdin", "RETRY"] = 1;

  IATTRS["TIMEGENERATED"] = TIMEGENERATED = 1;
  IATTRS["TIMEREPORTED"] = TIMEREPORTED = 2;
  IATTRS["FACILITY"] = FACILITY = 3;
  IATTRS["SEVERITY"] = SEVERITY = 4;
  IATTRS["HOSTNAME"] = HOSTNAME = 5;
  IATTRS["HOSTADDR"] = HOSTADDR = 6;
  IATTRS["PROGRAM"] = PROGRAM = 7;
  IATTRS["MSG"] = MSG = 8;
  IATTRS["NAGIOS_STATE"] = NAGIOS_STATE = 9;
  IATTRS["RULENAME"] = RULENAME = 10;
  for (attr in IATTRS) {
    IATTRS[IATTRS[attr]] = attr;
    NATTRS = (NATTRS < IATTRS[attr]) ? IATTRS[attr] : NATTRS;
  }

  NAGIOS_STATES[0x0010] = 2; # CRITICAL
  NAGIOS_STATES[0x0020] = 1; # WARNING
  NAGIOS_STATES[0x0040] = 0; # OK
  NAGIOS_STATES[0x0000] = 3; # UNKNOWN

  DIGIT_STRING = "0123456789";
  for (i = 1; i < length(DIGIT_STRING); i++)
    DIGITS[substr(DIGIT_STRING, i, 1)] = i;

  # initialize potentialy empty array
  delete RULENAMES;
  delete WATCHERS_TIMEOUT;
  delete ALIAS;

  if (CONFIG) {
    config();
  }

  if (ALIASES) {
    aliases();
  }

  for (ir = 1; ir <= length(RULENAMES); ir++) {
    debug(sprintf("CONFIG %s: pattern[msg]=<%s> rewrite[msg]=<%s> flag=0x%04x",
                  RULENAMES[ir],PATTERNS[ir,MSG],REWRITES[ir,MSG],FLAGS[ir]));
  }
  # PATTERNS[1,MSG] = "(..)t";
  # REWRITES[1,MSG] = "\\1toto-\\1-toto";
  # FLAGS[1,CRITICAL] = 1;
  # FLAGS[1,CONTINUE] = 1;

  rsyslog_ack();

  ##########################################
  # main loop
  while (ret = getline line < "/dev/stdin") {

    if (ret < 0) {
      if (DEBUG >=2) debug(sprintf("read timeout: ret=%d", ret));
      # timeout
      cleanup_watchers();
      continue;
    }

    if (DEBUG >=2) debug(sprintf("read: line=<%s>", line));

    line = iconv_utf8(iconv_cp1252(line));

    main(line);
  }
  exit 0;
}

