#!/usr/local/bin/ruby

# created on 2003/11/12
# Copyright 2003 Shin-ya Murakami

# 

# this script suppose next format.
#   Nov  4 10:10:05 faris kernel: ipfw: 65000 Deny TCP 192.168.84.142:2987 192.168.150.29:135 in via tun0
#   Month Day Time hostname kernel: procname: rule_no Action Proto SrcIP:SrcPort DstIP:DstPort InOrOut via INTERFACE
#
# capable Action name:
#  Deny, Unreach, Divert, Tee, SkipTo, Pipe, Queue, Forward to,
#  Accept, Count, Refuse, Reset, Reject, UNKNOWN
#  refer the function in ipfw_log on src/sys/netinet/ip_fw2.c
#
# capable Proto name:
#  TCP, UDP, ICMP
#

require 'ipaddr'
require 'chkdate.rb'

# file path
#HOSTNAMETBL="/root/logchk/hostnametbl"
#SERVICETBL="/etc/services"
HOSTNAMETBL='/home/murashin/work/ipfwlogchk/hostnametbl'
SERVICETBL='/etc/services'

#NETWORK = IPAddr.new("192.168.0.0/25")
#OTHERS = ["192.168.8.211"].collect {|ip|IPAddr.new(ip)} #bar 192.168.8.211
NETWORK = IPAddr.new("172.21.0.0/16")
OTHERS = ["172.21.10.29"].collect {|ip|IPAddr.new(ip)}

class IPFWlog

  @@port2srv_tbl = Hash.new
  @@ip2name_tbl = Hash.new
  @@port2srv = false
  @@ip2name = true
  @@displaytime = true

  re = /^#/
  IO.readlines( HOSTNAMETBL ).each do |line|
    next if re.match(line)
    ip,name = line.split
    @@ip2name_tbl[ip] = name
  end

  IO.readlines( SERVICETBL ).each do |line|
    next if re.match(line)
    name, no_type = line.split
    no, ptype = no_type.split('/')
    @@port2srv_tbl[[no, ptype]] = name
  end
  
  def initialize(line)
    @month, @day, @time, host, proc, chproc, @rule_no, @allow, @ptype, src, dst, @dir, trash, @iface = line.split(' ')
    @srcip, @srcprt = src.split(':')
    @dstip, @dstprt = dst.split(':')
  end

  attr_reader :time, :day, :month, :rule_no, :allow, :ptype 
  attr_reader :srcip, :srcprt, :dstip, :dstprt, :dir, :iface

  def ip(arg)
    if arg == 'src'
      @srcip
    elsif arg == 'dst'
      @dstip
    end
  end

  def IPFWlog.set_port2srv(flag) @@port2srv = flag end
  def IPFWlog.set_ip2name(flag) @@ip2name = flag end
  def IPFWlog.port2srv() @@port2srv end
  def IPFWlog.ip2name() @@ip2name end
  def IPFWlog.ip2name_tbl() @@ip2name_tbl end
  def IPFWlog.displaytime() @@displaytime end
  def IPFWlog.set_displaytime(flag) @@displaytime = flag end

  def prt(arg)

    format1 = "  %#7s   %#5s   %#8s  %#15s  %#8s"
    format2 = "  %#7s   %#5s   %#8s  %#15s %#10s %#8s"

    if arg == 'src'
      if @@ip2name and @@port2srv
	printf format1, @allow, @ptype, @srcprt, @dstip, @dstprt
	printf "  %s %s\n", @day, @time if @@displaytime
	printf format1, '', '', @@port2srv_tbl[[@srcprt, @ptype.downcase]],
	  @@ip2name_tbl[@dstip], @@port2srv_tbl[[@dstprt, @ptype.downcase]]
      elsif @@ip2name and !@@port2srv
	printf format2, @allow, @ptype, @srcprt, @dstip,
	  @@ip2name_tbl[@dstip], @dstprt
	printf "  %s %s", @day, @time if @@displaytime
      end
    elsif arg == 'dst'
      if @@ip2name and @@port2srv
	printf format1, @allow, @ptype, @dstprt, @srcip, @srcprt
	printf "  %s %s\n", @day, @time if @@displaytime
	printf format1, '', '', @@port2srv_tbl[[@dstprt, @ptype.downcase]],
	  @@ip2name_tbl[@srcip], @@port2srv_tbl[[@srcprt, @ptype.downcase]]
      elsif @@ip2name and !@@port2srv
	printf format2, @allow, @ptype, @dstprt, @srcip,
	  @@ip2name_tbl[@srcip], @srcprt
	printf "  %s %s", @day, @time if @@displaytime
      end
    end
    print "\n"
  end

  def date()
    "#{@month} #{@day} #{@time}"
  end

end

def log_preprocess(lineary, range=false)
  ipfwlog_ary = Array.new
  flag = false

  date_start = lineary[0]
  date_end = lineary[-1]

  re = /(repeated)|(newsyslog)/
  lineary.each do |line|
    next if re.match(line)

    ipfwlog = IPFWlog.new(line)
    if !range
      ipfwlog_ary << ipfwlog
      flag = true
    else
      s_t, e_t = range
      if !flag
	flag = true if date_compare(s_t, ipfwlog.date) != 1 #$B=i4|;~9o$r1[$($?(B
      elsif date_compare(e_t,ipfwlog.date) != -1
	#$B",(B $B=i4|;~9o$r1[$($F$$$l$P!"=*N;;~9o$r1[$($F$$$J$$$+D4$Y$k!#(B
	ipfwlog_ary << ipfwlog
      end
    end
  end
  if !flag 
    print "no logs within such range\n"
    exit
  end
  return ipfwlog_ary
end

def log_compile(lineary, range=false)
  #$B%m%0$rFI$_=P$7$FA4$F(BIPFWlog$B%*%V%8%'%/%H$GI=8=$9$k!#(B
  ipfwlog_ary = log_preprocess(lineary, range)
  
  #$B;XDj$5$l$?HO0O$N(BIP$B$G%m%0$KB8:_$9$k(BIP$B$r(B
  #SRC$B$H(BDST$B$H$KJ,$1$FD4$Y!"%j%9%H$r:n$k!#(B
  logged_ip = {"src"=>[], "dst"=>[]}
  
  ipfwlog_ary.each do |ipfwlog|
    [[ipfwlog.srcip, "src"], [ipfwlog.dstip, "dst"]].each do |ip, key|
      ip_obj = IPAddr.new(ip)
      if NETWORK.include? ip_obj
	logged_ip[key] << ip
      else
	OTHERS.each do |oip|
	 logged_ip[key] << ip  if oip == ip_obj
	end
      end
    end
  end
  
  logged_ip["src"].uniq!
  logged_ip["dst"].uniq!
  
  shortfmt = "  %#7s   %#5s   %#8s  %#15s  %#8s"
  longfmt = "  %#7s   %#5s   %#8s  %#15s %#10s %#8s"

  #$B;XDj$5$l$?HO0O$N(BIP$B$G%m%0$N(BSRC$B$KB8:_$9$k(BIP$B$K$D$$$F(B
  #$B$=$N(BSRCPORT$B$H(BDST{IP,PORT}$B$H$r=PNO$9$k!#(B
  [['src', 'srcport', 'dstip', 'dstport'],
    ['dst', 'dstport', 'srcip', 'srcport']].each do |ary|
    dstorsrc, arg1, arg2, arg3 = ary
    logged_ip[dstorsrc].each do |ip|
      print "\n#{dstorsrc.upcase} IP: #{ip}"
      
      if IPFWlog.ip2name and IPFWlog.port2srv
	print " #{IPFWlog.ip2name_tbl[ip]}\n"
	printf shortfmt, '', 'PType', arg1, arg2, arg3
      elsif IPFWlog.ip2name and !IPFWlog.port2srv
	print " #{IPFWlog.ip2name_tbl[ip]}\n"
	printf longfmt, '', 'PType', arg1, arg2, '', arg3
      else
	print "\n"
	printf longfmt, '', 'PType', arg1, arg2, arg3
      end
      
      print "\n"

      ipfwlog_ary.each do |ipfwlog|
	ipfwlog.prt(dstorsrc) if ip == ipfwlog.ip(dstorsrc)
      end
    end
  end
end

def usage()
  usage = <<-"EOF"
ipfwlogchk.rb -- ipfw log compilation tool
usage: ./ipfwlogchk.rb [switches] (- | filename)
  -r "starttime" "endtime"  : specify range.
      timeformat example 'Nov 14 18:20:12 2003'.
  -[n]t : display with[without] time.    (default: -t)
  -[n]p : translate portnumber to portname. (default: -np)
  -[n]i : translate IP address to hostname. (default: -i)
  -     : read stdin
  EOF
  print usage
end

range = false
leapcounter = 0
lineary = false

#$B0z?t$,;XDj$5$l$?>l9g$O!";XDj$5$l$?%U%!%$%k$rFI$`!#(B
#$B;XDj$5$l$J$$>l9g$OI8=`F~NO$rFI$`!#(B

if ARGV.size != 0 
  ARGV.size.times do |i|
    case ARGV[i]
    when '-h'
      usage()
      exit
    when '-r'
      s_t = ARGV[i+1]
      e_t = ARGV[i+2]
      #      lineary = rangeddata(s_t, e_t).split("\n")
      range = [s_t, e_t]
      leapcounter = 2
      print "logs within #{range[0]} to #{range[1]}\n"
    when '-o'
      
    when '-t'
      IPFWlog.set_displaytime(true)
    when '-nt'
      IPFWlog.set_displaytime(false)
    when '-p'
      IPFWlog.set_port2srv(true)
    when '-np'
      IPFWlog.set_port2srv(false)
    when '-i'
      IPFWlog.set_ip2name(true)
    when '-ni'
      IPFWlog.set_ip2name(false)
    when '-'
      lineary = STDIN.readlines
    when /^-/
      print "error has occured.\n"
      print "  unrecognized options #{ARGV[i]}\n"
      usage()
      exit
    else
      if leapcounter == 0
        lineary = open(ARGV[i]).readlines
      else
        leapcounter -= 1
      end
    end
  end
  if !lineary
    if range
      lineary = rangeddata(range[0], range[1]).split("\n")
    end
  end
else
  usage()
  exit()
end

log_compile(lineary, range)
