#!/usr/bin/ruby
#
# Details: Fgen subsystem, Fortinet rule generator
# Authors: Dmitry Maksyoma <dmaks@esphion.com>
# Started: 10/2005
#
# (c) 2005 Esphion Limited
# Copyright in this document, whether in written, electronic or other
# format including any source code or other computer code set forth
# therein or attached thereto and copyright in all parts thereof is owned
# by Esphion Limited, who reserves all rights therein.
# In particular, no part of this document/computer code may be reproduced,
# copied, stored in a retrieval system, used or transmitted by any means
# whatsoever without the prior written consent of Esphion Limited.
#
# All enquiries in relation thereto should be directed to:
#
# Esphion Ltd
# P.O. Box 300496,
# Albany,
# Auckland,
# New Zealand.
#
# Phone: +64 9 415 0227
# Fax:	 +64 9 415 0228
# Email: info@esphion.com

$:.insert(-2, File.join(File.dirname(File.expand_path($0)), "../lib"))
require 'pattern/entities'
require 'fgen/formatters'
require 'fgen/common'
include Entities

def fmt_ip_fortinet(bytes)
  add_cidr(bytes, 3)
end

FORTINET_IP_FLAGS_DESC = {IPHeader::RESERVED_FLAG=>"R",
  IPHeader::DF_FLAG=>"D", IPHeader::MORE_FLAG=>"M"}

FORTINET_TCP_FLAGS_DESC = {TCPHeader::FIN_FLAG=>"F",
  TCPHeader::SYN_FLAG=>"S", TCPHeader::RST_FLAG=>"R",
  TCPHeader::PSH_FLAG=>"P", TCPHeader::ACK_FLAG=>"A",
  TCPHeader::URG_FLAG=>"U", TCPHeader::ECE_FLAG=>"E",
  TCPHeader::CWR_FLAG=>"C"}

FORTINET_PROTOCOL_DESC = {IPHeader::PROTO_ICMP=>"icmp",
  IPHeader::PROTO_TCP=>"tcp", IPHeader::PROTO_UDP=>"udp"}

def format_pattern(pattern, name, src_ips, dst_ips)
  name = purify_name(name)
  ipl = pattern.ip
  ippl = pattern.ipp
  pl = pattern.packetPayload

  ip = ""; tcp = ""; icmp = ""; udp = ""; other = ""
  name = "--name #{name};" if name

  #IP LAYER
  if ipl
    t = nil
    ip << "--ip_version #{$_}; " if $_ = ipl.version
    ip << "--ihl #{$_}; " if $_ = ipl.header_len
    ip << "--tos #{$_}; " if $_ = ipl.tos
    ip << "--ip_id #{$_}; " if $_ = ipl.ip_id
    ip << "--frag_offset #{$_}; " if $_ = ipl.frag_offset
    if byte = ipl.flags
      set_flags = ""; unset_flags = ""
      IPHeader::ALL_FLAGS.each{|f|
	(byte & f > 0 ? set_flags : unset_flags) << FORTINET_IP_FLAGS_DESC[f]
      }	
      ip << "--ip_flag #{set_flags}; " unless set_flags.empty?
      ip << "--ip_flag !#{unset_flags}; " unless unset_flags.empty?
    end
    ip << "--ttl #{$_}; " if $_ = ipl.ttl
    if byte = ipl.proto
      desc = FORTINET_PROTOCOL_DESC[byte]
      ip << "--protocol "
      ip << (desc ? "#{desc}" : "ip; --ip_proto #{byte}") << "; "
    end

    srcip = dstip = nil
    if src_ips
      srcip = bracket_ips(src_ips)
    elsif (data = ipl[:srcip])
      srcip = fmt_ip_fortinet(data)
    end
    ip << "--src_addr #{srcip}; " if srcip
    if dst_ips
      dstip = bracket_ips(dst_ips)
    elsif (data = ipl[:dstip])
      dstip = fmt_ip_fortinet(data)
    end
    ip << "--dst_addr #{dstip}; " if dstip
  end

  #IP PROTOCOL LAYER
  if ippl && ipl.proto
    if (ipl.proto_tcp)
      tcp << "--src_port #{$_}; " if $_ = ippl.srcport
      tcp << "--dst_port #{$_}; " if $_ = ippl.dstport
      tcp << "--seq #{$_}; " if $_ = ippl.seq
      tcp << "--ack #{$_}; " if $_ = ippl.ack
      if byte = ippl.flags
	if byte == 0
	  tcp << "--tcp_flags 0; "
	elsif byte == 255
	  tcp << "--tcp_flags FSRPAUEC; "
	else
	  tf_plus = ""; tf_minus = ""
	  TCPHeader::ALL_FLAGS.each{|f|
	    (byte & f > 0 ? tf_plus : tf_minus) << FORTINET_TCP_FLAGS_DESC[f]
	  }
	  tcp << "--tcp_flags #{tf_plus}; " unless tf_plus.empty?
	  tcp << "--tcp_flags !#{tf_minus}; " unless tf_minus.empty?
	end
      end
      tcp << "--window_size #{$_}; " if $_ = ippl.window
    elsif (ipl.proto_icmp)
      icmp << " --icmp_type #{$_}; " if $_ = ippl.itype
      icmp << " --icmp_code #{$_}; " if $_ = ippl.icode
      icmp << " --icmp_id #{$_}; " if $_ = ippl.iid
      icmp << " --icmp_seq #{$_}; " if $_ = ippl.iseq
    elsif (ipl.proto_udp)
      udp << "--src_port #{$_}; " if $_ = ippl.srcport
      udp << "--dst_port #{$_}; " if $_ = ippl.dstport
    else #unknown protocol
      ippl.layer.keys.sort.each{|key|
	other << "--byte_test 1, =, #{ippl.layer[key]}, #{key}; "
      }
    end
  end

  payload = nil
  #PAYLOAD LAYER
  if pl
    #puts "totalLen: #{totalLen}, headerLen: #{headerLen}, dataOffset: #{dataOffset}"
    baseOffset = (ipl.proto_icmp && ippl && \
      (ippl.itype != 8 && ippl.itype != 0)) ? 4 : 0
    payload = fmt_payload(pl, "--content", "raw", baseOffset)
  end

  ("F-SBID ( #{name} --default_action drop; #{ip} " <<
    " #{tcp} #{icmp} #{udp} #{other} #{payload})").gsub(/\s+/, ' ')
end

do_format
