# gl_tail.rb - OpenGL visualization of your server traffic
# Copyright 2007 Erlend Simonsen <mr@fudgie.org>
#
# Licensed under the GNU General Public License v2 (see LICENSE)
#

# Parser for pfSense PF Logs, specifically those from pfSense (2.0)
# Jim Pingle (myfirstname@pingle.org)

# Available Blocks
#action: block|pass
#rule: Rule number matched
#ipprotocol: carp|icmp|tcp|udp|ah|igmp|esp|gre you get the idea..
#int: This will be the actual interface (fxp0, vlan2, em1, etc) as the 'friendly' name is not put in the logs.
#sourcehost: source host/IP
#sourceport: source port
#destinationhost: destination host/IP
#destinationport: destination port
#sourcedestination:  source host and port > destination host and port

# Use with command:  /usr/sbin/clog -f /var/log/filter.log | /usr/local/bin/filterparser.php

class PFSense2Parser < Parser
  require 'date'

  def getipandport(hostwithport)

    # Test for IPv6
    if (hostwithport.count(':') > 2)
      if (hostwithport.count('.') == 1)
        thisport = hostwithport.split('.')[1].to_s
        thishost = hostwithport.split('.')[0].to_s
      else
        thishost = hostwithport
        thisport = "none"
      end
    else
      # IPv4
      if (hostwithport.count('.') == 4)
        thisport = hostwithport.split('.')[-1,1].to_s
        thishost = hostwithport.split('.')
        thishost = thishost[0,thishost.length()-1].join('.')
      else
        thishost = hostwithport
        thisport = "none"
      end
    end

    if thisport.include?(':')
      thisport = thisport.split(':')[0]
    end
    if thisport.include?(' ')
      thisport = thisport.split(' ')[0]
    end

    return [thishost, thisport]
  end

  def getport(thisport)
    if thisport == "none"
      return ""
    else
      return ":" + thisport.to_s
    end
  end

  def parse( line )
    lmonth, lday, ltod, action, int, ipprotocol, src, dst = line.split(' ')
    ltime = [ lmonth, lday, ltod ].join(' ')

    # Assume the server is in the same time zone as the viewing client.
    timewithoffset = ltime.to_s + DateTime.now().zone()

    # Alternately, just set it this way to assume UTC/GMT
    #timewithoffset = ltime.to_s

    hours,minutes,seconds,frac = Date.day_fraction_to_time(DateTime.now() - DateTime.parse(timewithoffset))

    # When connecting directly, there is no way to only view the end of the log. The clog program to view
    # circular logs will dump the entire log to the parser, then will tail it showing new messages.
    # Therefore, we can run a simple time check and only view entries from the last 5 minutes, or the
    # "future". On some systems, I have seen the clock show negative (-1hr 59mins) instead of 0, so we
    # can allow "future" messages just to be safe.
    if ((hours == 0) and (minutes < 5)) or (hours < 0)
      # Debug
      # printf("Adding entry from %s hours, %s minutes ago\n", hours.to_s, minutes.to_s)

      sourcehost, sourceport = getipandport(src)

      destinationhost, destinationport = getipandport(dst)

      add_activity(:block => 'action',  :name => action.to_s)
      add_activity(:block => 'int',     :name => int.to_s)
      add_activity(:block => 'ipprotocol',   :name => ipprotocol.to_s)
      add_activity(:block => 'sourcehost', :name => sourcehost.to_s)
      if sourceport != "none"
        add_activity(:block => 'sourceport', :name => sourceport.to_s)
      end
      add_activity(:block => 'destinationhost', :name => destinationhost.to_s, :type => 5)
      if destinationport != "none"
        add_activity(:block => 'destinationport', :name => destinationport.to_s, :type => 5)
      end
      add_activity(:block => 'sourcedestination',  :name => sourcehost.to_s + getport(sourceport) + " > " + destinationhost.to_s + getport(destinationport) + " (" + ipprotocol.to_s + ")")
    else
      # Debug
      # printf("Not adding entry from %s hours, %s minutes ago\n", hours.to_s, minutes.to_s)
    end
  end
end
