-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/.
-- Copyright (c) 2014 Mozilla Corporation
--
-- Contributors:
-- Anthony Verez averez@mozilla.com
-- Jeff Bryner jbryner@mozilla.com
-- Michal Purzynski mpurzynski@mozilla.com

local cjson = require "cjson"
-- local dt = require "date_time"

-- {
--    "timestamp": "2009-11-24T21:27:09.534255",
--    "event_type": "alert",
--    "src_ip": "192.168.2.7",
--    "src_port": 1041,
--    "dest_ip": "x.x.250.50",
--    "dest_port": 80,
--    "proto": "TCP",
--    "alert": {
--        "action": "allowed",
--        "gid": 1,
--        "signature_id" :2001999,
--        "rev": 9,
--        "signature": "ET MALWARE BTGrab.com Spyware Downloading Ads",
--        "category": "A Network Trojan was detected",
--        "severity": 1
--    }
-- }

local l=require "lpeg"
local string=require "string"
local math=require "math"
l.locale(l) --add locale entries in the lpeg table
local space = l.space^0  --define a space constant
local sep = l.P"\t"
local elem = l.C((1-sep)^0)
grammar = l.Ct(elem * (sep * elem)^0) -- split on tabs, return as table
local os=require "os"
-- set timezone defaults
-- local tz = os.date("%z") doesn't work
-- since heka sets sandbox env to UTC
-- override if not in UTC with heka.toml config like so:
-- [systemslogs_transform_decoder.config]
-- tzoffset="-0700"
local offset = "([+-])(%d%d)(%d%d)"
local tz= "-0000"
-- if read_config("tzoffset") then
--     tz=read_config("tzoffset")
-- end
local sign, hour, min  = tz:match(offset)

--date definitions:
date_mabbr = l.Cg(
    l.P"Jan"   /"1"
    + l.P"Feb" /"2"
    + l.P"Mar" /"3"
    + l.P"Apr" /"4"
    + l.P"May" /"5"
    + l.P"Jun" /"6"
    + l.P"Jul" /"7"
    + l.P"Aug" /"8"
    + l.P"Sep" /"9"
    + l.P"Oct" /"10"
    + l.P"Nov" /"11"
    + l.P"Dec" /"12"
    , "month")

date_fullyear = l.Cg(l.digit * l.digit * l.digit * l.digit, "year")

date_month = l.Cg(l.P"0" * l.R"19"
                     + "1" * l.R"02", "month")
date_mday = l.Cg(l.P"0" * l.R"19"
                    + l.R"12" * l.R"09"
                    + "3" * l.R"01", "day")

time_hour = l.Cg(l.R"01" * l.digit
                    + "2" * l.R"03", "hour")

time_minute = l.Cg(l.R"05" * l.digit, "min")

time_second = l.Cg(l.R"05" * l.digit
                           + "60", "sec") -- include leap second

time_secfrac = l.Cg(l.P"," * l.digit^1 / tonumber, "sec_frac")

rfc3339_partial_time    = time_hour * ":" * time_minute * ":" * time_second * time_secfrac^-1
dategrammer=l.Ct(date_fullyear *"-"* date_month  *"-"* date_mday * "T" * rfc3339_partial_time)

function time_to_ns(t)
    if not t then return 0 end

    local offset = 0
    if t.offset_hour then
        offset = (t.offset_hour * 60 * 60) + (t.offset_min * 60)
        if t.offset_sign == "+" then offset = offset * -1 end
    end

    local frac = 0
    if t.sec_frac then
        frac = t.sec_frac
    end
    return (os.time(t) + frac + offset) * 1e9
end

function toString(value)
    if value == "-" then
        return nil
    end
    return value
end

function nilToString(value)
    if value == nil then
        return ""
    end
    return value
end

function toNumber(value)
    if value == "-" then
        return nil
    end
    return tonumber(value)
end

function lastField(value)
    -- remove last "\n" if there's one
    if value ~= nil and string.len(value) > 1 and string.sub(value, -2) == "\n" then
        return string.sub(value, 1, -2)
    end
    return value
end

function process_message()
   local log = read_message("Payload")
   local ok, json = pcall(cjson.decode, log)
   if not ok then
      return 0 -- when plain text is found, ship it in it's raw form
   end

    --set a default msg that heka's
    --message matcher will ignore
    -- this is a message skeleton that we will add fields to
    -- the initialization must be local to isolate variable's content between messages
    local msg = {
        Type = "IGNORE",
        Fields={}
    }
    --inject a random value for use in the message matcher
    --for poor mans load balancing
    msg.Fields['Random']=math.random(0,10)	

    local eventdate = dategrammer:match(json['timestamp'])
    if not eventdate then
        --no date/time, likely a stack trace
         inject_message(msg)
        return 0
    else
        --no year in the date, add current year. 
        eventdate.year=os.date('%Y')
        -- set the timezone
        eventdate.offset_hour=hour
        eventdate.offset_sign=sign
        eventdate.offset_min=min
    end

   msg['Type'] = 'suricata_event_log'
   msg['Logger'] = 'nsm'
   msg['severity'] = 'INFO'
   msg['category'] = 'suricata_event'
   msg['tags'] = 'nsm,suricata,event'
   msg['Timestamp'] = time_to_ns(eventdate)
   msg.Fields['Type'] = 'suricata_event_log'
   msg.Fields['flow_id'] = toNumber(json['flow_id'])
   msg.Fields['in_iface'] = json['in_iface']
   msg.Fields['vlan'] = toNumber(json['vlan'])
   msg.Fields['sourceipaddress'] = json['src_ip']
   msg.Fields['sourceport'] = toNumber(json['src_port'])
   msg.Fields['destinationipaddress'] = json['dest_ip']
   msg.Fields['destinationport'] = toNumber(json['dest_port'])
   msg.Fields['proto'] = json['proto']
   msg.Fields['gid'] = toNumber(json.alert['gid'])
   msg.Fields['signature_id'] = toNumber(json.alert['signature_id'])
   msg.Fields['rev'] = toNumber(json.alert['rev'])
   msg.Fields['signature'] = json.alert['signature']
   msg.Fields['category'] = json.alert['category']
   msg.Fields['severity'] = json.alert['severity']
   msg.Fields['tx_id'] = toNumber(json.alert['tx_id'])
   if json.http then
       msg.Fields['http_hostname'] = json.http['hostname']
       msg.Fields['http_url'] = json.http['url']
       msg.Fields['http_user_agent'] = json.http['http_user_agent']
       msg.Fields['http_content_type'] = json.http['http_content_type']
       msg.Fields['http_method'] = json.http['http_method']
       msg.Fields['http_protocol'] = json.http['protocol']
       msg.Fields['http_status'] = toNumber(json.http['status'])
       msg.Fields['http_length'] = toNumber(json.http['length'])
   end
   msg.Fields['payload'] = json['payload']
   msg.Fields['payload_printable'] = json['payload_printable']
   msg.Fields['stream'] = toNumber(json['stream'])
   msg.Fields['packet'] = lastField(json['packet'])
   inject_message(msg)
   return 0
end

