#
#   Copyright (C) Stonesoft Corporation 2010 - 2012.
#   All rights reserved.
#
#   The StoneGate software, manuals, and technical
#   literature may not be reproduced in any form or
#   by any means except by permission in writing from
#   Stonesoft Corporation.
#

# Predator4 webgui

require "#{$webgui_root}/lib/predator4_module.rb"
require "#{$webgui_root}/lib/ipv4address.rb"
require "#{$webgui_root}/lib/ipv6address.rb"

include IPForge

module P4WebGUI
  class Preset
    attr_accessor :name, :desc, :size, :attack

    def initialize( name, desc, size, attack )
      @name = name
      @desc = desc
      @size = size
      @attack = attack
    end
  end

  class Predator4
    attr_accessor :attacks, :evasions, :presets, :version, :p4_bin_path,
      :attacks_by_name, :interfaces

    def initialize( p4_bin_path="predator4" )
      @attacks = nil
      @evasions = nil
      @presets = nil
      @version = nil

      @interfaces = []
      @attacks_by_name = {}

      @p4_bin_path = p4_bin_path
    end

    def get_all()
      if not get_version()
        return false
      end

      if not get_attacks()
        return false
      end

      get_presets()
      get_interfaces()
      
      return true
    end

    def get_version()
      @version = `#{@p4_bin_path} -v`
      
      if not @version =~ /Predator4 version/ and not @version =~ /Evader version/
        return false
      end

      return true
    end

    def get_attacks()
      @attacks = Predator4Module::Parser.parse( nil, false, @p4_bin_path )

      @attacks.each do |attack|
        @attacks_by_name[ attack.short_name ] = attack
      end
      return true
    end

    def get_interfaces
      hc = Predator4Module::HostConfig.new
      hc.parse
      @interfaces = hc.interfaces.select{|name, int| int.carrier && int.up}.keys
    end

    def get_presets()
      @presets = nil
      result = `ruby preset.rb -p`
      if not result =~ /^Available presets:/
        # Presets missing for some reason
        @presets = nil
        return false
      end

      @presets = []

      lines = result.split( "\n" )[ 1..-1 ]
      lines.each do |line|
        if line =~ /^\t(\w*)\t\t\t([^\t]*)\t(\d+) attacks. Exploit (\w*)/
          preset_name = $1
          preset_desc = $2
          preset_size = $3
          preset_attack = $4

          @presets.push( Preset.new( preset_name, preset_desc, preset_size, preset_attack ) )
        else
          puts "Preset parsing error, unrecognized line: #{line}"
          @presets = nil
          return true;
        end
      end

      return true
    end
  end

  class P4Config
    NET_CFG_UNSET = 0x00
    NET_CFG_WRITABLE = 0x01
    NET_CFG_PRESET = 0x02
    
    class NetworkConfig
      attr_accessor :name, :iface, :src_ip, :src_mask, :dst_ip, :dst_port, :gw, :id

      @@id_num = 0

      def initialize()
        @id = @@id_num
        @@id_num += 1
        
        @name = nil
        @iface = nil
        @src_ip = nil
        @src_mask = 24
        @dst_ip = nil
        @gw = nil
      end

      def to_html()
        return "<option id=\"#{@id}\"> #{@name} </option>"
      end

      def validate()
        if @iface.nil? or @name.nil?
          return false
        end

        begin
          if @src_ip.instance_of? String
            begin
              @src_ip = IPv4Address.new( @src_ip )
            rescue Exception => exc_ipv4
              begin
                @src_ip = IPv6Address.new( @src_ip )
              rescue Exception => exc_ipv6
                puts "Network configuration src_ip #{@src_ip} is not a valid IPv4 or IPv6 address ( #{exc_ipv4} #{exc_ipv6} )"
                return false
              end
            end
          end

          if @src_mask.instance_of? String
            @src_mask = @src_mask.to_i()
          end

          if @dst_ip.instance_of? String
            begin
              @dst_ip = IPv4Address.new( @dst_ip )
            rescue Exception => exc_ipv4
              begin
                @dst_ip = IPv6Address.new( @dst_ip )
              rescue Exception => exc_ipv6
                puts "Network configuration dst_ip #{@dst_ip} is not a valid IPv4 or IPv6 address ( #{exc_ipv4} #{exc_ipv6} )"
                return false
              end
            end
          end
                    
          if @gw.instance_of? String
            @gw = IPv4Address.new( @gw )
          end
        rescue BrokenIPv4Address => exc
          return false
        end
        
        return true
      end
    end

    class HostConfig
      attr_accessor :name, :ip_end, :ipv6_end, :id

      @@id_num = 0

      def initialize()
        @id = @@id_num
        @@id_num += 1
        @name = nil
        @ip_end = nil
        @ipv6_end = nil
      end

      def to_html()
        return "<option id=\"#{@id}\"> #{@name} </option>"
      end

      def validate()
        if @name.nil? or @ip_end.nil?
          puts "HostConfig::validate() - Name or ip_end nil, #{@name.inspect}, #{@ip_end.inspect}"
          return false
        end

        if @ip_end.instance_of? String
          @ip_end = @ip_end.to_i()
        end

        if not @ip_end.instance_of? Fixnum
          puts "HostConfig::validate() - end_ip not Fixnum!"
          return false
        end
        
        if not @ipv6_end.instance_of? Fixnum
          puts "HostConfig::validate() - end_ipv6 not Fixnum!"
          return false
        end

        return true
      end
    end

    attr_accessor :net_cfg_type, :networks, :hosts

    def initialize( cfg_path )
      @config_file = cfg_path
      @net_cfg_type = NET_CFG_UNSET

      # Preset network cfg style settings
      @networks = []
      @hosts = []
    end

    def load_config()
      if @config_file.nil?
        @net_cfg_type = NET_CFG_WRITABLE

        netcfg = NetworkConfig.new()
        netcfg.name = "Preset"

        @networks.push netcfg
        return true
      end

      begin
        cfg_file = File.new( @config_file, "r" )
      rescue Exception => exc
        puts "Failed to open file \"#{@config_file}\" ( #{exc} )"
        return false
      end
      
      content = nil

      cfg_file.each do |line|
        if line =~ /(.*)[^\\]#/
          line = $1
        end

        line.strip!
        if line.empty?
          next
        end

        if line =~ /^Network config type: (.*)$/
          cfg_type = $1
          if cfg_type == "writable"
            @net_cfg_type = NET_CFG_WRITABLE
          elsif cfg_type == "preset"
            @net_cfg_type = NET_CFG_PRESET
          else
            puts "! Invalid network config type \"#{line}\""
            return false
          end
        elsif @net_cfg_type == NET_CFG_WRITABLE and
            line =~ /^Network config: (.*)$/
          cfg = $1

          netcfg = NetworkConfig.new()
          netcfg.name = "Preset"
          
          if cfg =~ /src_ip=([^,]*)/
            netcfg.src_ip = $1
          end
          if cfg =~ /src_iface=([^,]*)/
            netcfg.iface = $1
          end
          if cfg =~ /gw=([^,]*)/
            netcfg.gw = $1
          end
          if cfg =~ /dst_ip=([^,]*)/
            netcfg.dst_ip = $1
          end

          @networks.push netcfg
        elsif @net_cfg_type == NET_CFG_PRESET and
            line =~ /^Network preset:\w*(.*)$/
          cfg = $1

          # We have a new network preset. Configure
          netcfg = NetworkConfig.new()

          if cfg =~ /name=([^,]*)/
            netcfg.name = $1
          end
          if cfg =~ /src_ip=([^,]*)/
            netcfg.src_ip = $1
          end
          if cfg =~ /dst_ip=([^,]*)/
            netcfg.dst_ip = $1
          end
          if cfg =~ /iface=([^,]*)/
            netcfg.iface = $1
          end
          if cfg =~ /src_mask=([^,]*)/
            netcfg.src_mask = $1
          end
          if cfg =~ /gw=([^,]*)/
            netcfg.gw = $1
          end

          if not netcfg.validate()
            puts "Invalid network configuration: #{line}"
            return false
          end

          @networks.push netcfg
        elsif @net_cfg_type == NET_CFG_PRESET and
            line =~ /^Host preset:\w*(.*)$/
          cfg = $1

          hostcfg = HostConfig.new()

          if cfg =~ /name=([^,]*)/
            hostcfg.name = $1
          end
          if cfg =~ /end_ip=([^,]*)/
            hostcfg.ip_end = $1.strip.to_i()
          end
          if cfg =~ /end_ipv6=([^,]*)/
            hostcfg.ipv6_end = $1.strip.to_i(16)
          end

          if not hostcfg.validate()
            puts "Invalid host configuration: #{line}"
            return false
          end

          @hosts.push hostcfg
        else
          puts "Unknown configuration line: #{line}"
          return false
        end
      end

      if @net_cfg_type == NET_CFG_UNSET
        puts "No network config given"
        return false
      end

      if ( @net_cfg_type == NET_CFG_WRITABLE and ( @networks.size() != 1 or @hosts.size() != 0 ) ) or
         ( @net_cfg_type == NET_CFG_PRESET and ( @networks.empty?or @hosts.empty? ) )
         puts "Incorrect preset configuration, need at least one network and one host"
         return false
      end

      return true
    end
  end

  class DataStore
    # Initialize - called at webgui startup.
    def DataStore.load_predator4( p4_bin_path )
      @@predator4 = Predator4.new( p4_bin_path )
      if not @@predator4.get_all()
        return false
      end

      return true
    end

    def DataStore.load_webgui_config( webgui_config_path )
      @@config = P4Config.new( webgui_config_path )
      if not @@config.load_config()
        return false
      end

      return true
    end

    def DataStore.get_predator4()
      return @@predator4
    end

    def DataStore.get_config()
      return @@config
    end
  end
end