#################################################################
# IP Lookup Plugin
# ----------------------------
# by Chris Gahan (chris@ill-logic.com)
#
# Purpose:
# ------------------
# Lets you lookup the owner and their address for any IP address
# or IRC user.
#
#################################################################

require 'socket'
require 'resolv'

#################################################################
## ARIN Whois module...
##

module ArinWhois

  class Chunk < Hash
    def customer?
      keys.grep(/^(City|Address|StateProv|(Org|Cust)Name)$/).any?
    end

    def network?
      keys.grep(/^(CIDR|NetHandle|Parent)$/).any?
    end

    def contact?
      keys.grep(/^(R|Org)(Tech|Abuse)(Handle|Name|Phone|Email)$/).any?
    end

    def valid?
      customer? or network? or contact?
    end

    def owner
      self[keys.grep(/^(Org|Cust)Name$/).first]
    end

    def location
      [ self['City'], self['StateProv'], self['Country'] ].compact.join(', ')
    end

    def address
      [ self['Address'], location, self['PostalCode'] ].compact.join(', ')
    end

  end

  class ArinWhoisParser

    def initialize(data)
      @data = data
    end

    def split_array_at(a, &block)
      return a unless a.any?
      a = a.to_a

      results = []
      last_cutpoint = 0

      a.each_with_index do |el,i|
        if block.call(el)
          unless i == 0
            results << a[last_cutpoint...i]
            last_cutpoint = i
          end
        end
      end

      if last_cutpoint < a.size or last_cutpoint == 0
        results << a[last_cutpoint..-1]
      end

      results
    end

    # Whois output format
    # ------------------------
    # Owner info block:
    #   {Org,Cust}Name
    #   Address
    #   City
    #   StateProv
    #   PostalCode
    #   Country (2-digit)
    #
    # Network Information:
    #   CIDR (69.195.25.0/25)
    #   NetHandle (NET-72-14-192-0-1)
    #   Parent (NET-72-0-0-0-0)
    #
    # Contacts:
    #   ({R,Org}{Tech,Abuse}{Handle,Name,Phone,Email})*

    def parse_chunks
      return if @data =~ /^No match found /
      chunks = @data.gsub(/^# ARIN WHOIS database, last updated.+/m, '').scan(/(([^\n]+\n)+\n)/m)
      chunks = chunks.map do |chunk|
        result = Chunk.new

        chunk[0].scan(/([A-Za-z]+?):(.*)/).each do |tuple|
          tuple[1].strip!
          result[tuple[0]] = tuple[1].empty? ? nil : tuple[1]
        end

        result
      end
      chunks.reject(&:empty?)
    end


    def get_parsed_data
      return unless chunks = parse_chunks

      results = split_array_at(chunks) {|chunk|chunk.customer?}
      results.map do |data|
        {
          :customer => data.select{|x|x.customer?}[0],
          :net      => data.select{|x|x.network?}[0],
          :contacts => data.select{|x|x.contact?}
        }
      end
    end

    # Return a hash with :customer, :net, and :contacts info filled in.
    def get_most_specific_owner
      return unless datas = get_parsed_data

      datas_with_bitmasks = datas.map do |data|
        next unless data[:customer]
        next unless net_data = data[:net]
        bitmask = net_data['CIDR'].split('/')[1].to_i
        [bitmask, data]
      end
      datas_with_bitmasks.compact!
      #datas_with_bitmasks.sort.each{|x|puts x[0]}
      winner = datas_with_bitmasks.sort[-1][1]
    end

  end # of class ArinWhoisParser

module_function

  def raw_whois(query_string, host)
    s = TCPSocket.open(host, 43)
    s.write(query_string+"\n")
    ret = s.read
    s.close
    ret
  end

  def lookup(ip)
    data = raw_whois("+ n #{ip}", 'whois.arin.net')
    arin = ArinWhoisParser.new data
    arin.get_most_specific_owner
  end

  def lookup_location(ip)
    result = lookup(ip)
    result[:customer].location
  end

  def lookup_address(ip)
    result = lookup(ip)
    result[:customer].address
  end

  def lookup_info(ip)
    if result = lookup(ip)
      "#{result[:net]['CIDR']} => #{result[:customer].owner} (#{result[:customer].address})"
    else
      "Address not found."
    end
  end

end



#################################################################
## The Plugin
##

class IPLookupPlugin < Plugin
  def help(plugin, topic="")
    "iplookup [ip address / domain name] => lookup info about the owner of the IP address from the ARIN whois database"
  end

  def iplookup(m, params)
    reply = ""
    if params[:domain].match(/^#{Regexp::Irc::HOSTADDR}$/)
      ip = params[:domain]
    else
      begin
        ip = Resolv.getaddress(params[:domain])
        reply << "#{params[:domain]} | "
      rescue => e
        m.reply "#{e.message}"
        return
      end
    end

    reply << ArinWhois.lookup_info(ip)

    m.reply reply
  end

  def userip(m, params)
    m.reply "not implemented yet"
    #users = @channels[m.channel].users
    #m.reply "users = #{users.inspect}"
    #m.reply @bot.sendq("WHO #{params[:user]}")
  end

end

plugin = IPLookupPlugin.new
plugin.map 'iplookup :domain', :action => 'iplookup', :thread => true
plugin.map 'userip :user', :action => 'userip', :requirements => {:user => /\w+/}, :thread => true


if __FILE__ == $0
  include ArinWhois
  data = open('whoistest.txt').read
  c = ArinWhoisParser.new data
  puts c.get_parsed_data.inspect
end
