]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/iplookup.rb
e629d233b986e21bb2ac3e43b85a53019a446554
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / iplookup.rb
1 #################################################################
2 # IP Lookup Plugin
3 # ----------------------------
4 # by Chris Gahan (chris@ill-logic.com)
5 #
6 # Purpose:
7 # ------------------
8 # Lets you lookup the owner and their address for any IP address
9 # or IRC user.
10 #
11 #################################################################
12
13 require 'socket'
14 require 'resolv'
15
16 #################################################################
17 ## ARIN Whois module...
18 ##
19
20 module ArinWhois
21
22   class Chunk < Hash
23     def customer?
24       keys.grep(/^(City|Address|StateProv|(Org|Cust)Name)$/).any?
25     end
26
27     def network?
28       keys.grep(/^(CIDR|NetHandle|Parent)$/).any?
29     end
30
31     def contact?
32       keys.grep(/^(R|Org)(Tech|Abuse)(Handle|Name|Phone|Email)$/).any?
33     end
34
35     def valid?
36       customer? or network? or contact?
37     end
38
39     def owner
40       self[keys.grep(/^(Org|Cust)Name$/).first]
41     end
42
43     def location
44       [ self['City'], self['StateProv'], self['Country'] ].compact.join(', ')
45     end
46
47     def address
48       [ self['Address'], location, self['PostalCode'] ].compact.join(', ')
49     end
50
51   end
52
53   class ArinWhoisParser
54
55     def initialize(data)
56       @data = data
57     end
58
59     def split_array_at(a, &block)
60       return a unless a.any?
61       a = a.to_a
62
63       results = []
64       last_cutpoint = 0
65
66       a.each_with_index do |el,i|
67         if block.call(el)
68           unless i == 0
69             results << a[last_cutpoint...i]
70             last_cutpoint = i
71           end
72         end
73       end
74
75       if last_cutpoint < a.size or last_cutpoint == 0
76         results << a[last_cutpoint..-1]
77       end
78
79       results
80     end
81
82     # Whois output format
83     # ------------------------
84     # Owner info block:
85     #   {Org,Cust}Name
86     #   Address
87     #   City
88     #   StateProv
89     #   PostalCode
90     #   Country (2-digit)
91     #
92     # Network Information:
93     #   CIDR (69.195.25.0/25)
94     #   NetHandle (NET-72-14-192-0-1)
95     #   Parent (NET-72-0-0-0-0)
96     #
97     # Contacts:
98     #   ({R,Org}{Tech,Abuse}{Handle,Name,Phone,Email})*
99
100     def parse_chunks
101       return if @data =~ /^No match found /
102       chunks = @data.gsub(/^# ARIN WHOIS database, last updated.+/m, '').scan(/(([^\n]+\n)+\n)/m)
103       chunks = chunks.map do |chunk|
104         result = Chunk.new
105
106         chunk[0].scan(/([A-Za-z]+?):(.*)/).each do |tuple|
107           tuple[1].strip!
108           result[tuple[0]] = tuple[1].empty? ? nil : tuple[1]
109         end
110
111         result
112       end
113       chunks.reject(&:empty?)
114     end
115
116
117     def get_parsed_data
118       return unless chunks = parse_chunks
119
120       results = split_array_at(chunks) {|chunk|chunk.customer?}
121       results.map do |data|
122         {
123           :customer => data.select{|x|x.customer?}[0],
124           :net      => data.select{|x|x.network?}[0],
125           :contacts => data.select{|x|x.contact?}
126         }
127       end
128     end
129
130     # Return a hash with :customer, :net, and :contacts info filled in.
131     def get_most_specific_owner
132       return unless datas = get_parsed_data
133
134       datas_with_bitmasks = datas.map do |data|
135         next unless data[:customer]
136         next unless net_data = data[:net]
137         bitmask = net_data['CIDR'].split('/')[1].to_i
138         [bitmask, data]
139       end
140       datas_with_bitmasks.compact!
141       #datas_with_bitmasks.sort.each{|x|puts x[0]}
142       winner = datas_with_bitmasks.sort[-1][1]
143     end
144
145   end # of class ArinWhoisParser
146
147 module_function
148
149   def raw_whois(query_string, host)
150     s = TCPSocket.open(host, 43)
151     s.write(query_string+"\n")
152     ret = s.read
153     s.close
154     ret
155   end
156
157   def lookup(ip)
158     data = raw_whois("+ n #{ip}", 'whois.arin.net')
159     arin = ArinWhoisParser.new data
160     arin.get_most_specific_owner
161   end
162
163   def lookup_location(ip)
164     result = lookup(ip)
165     result[:customer].location
166   end
167
168   def lookup_address(ip)
169     result = lookup(ip)
170     result[:customer].address
171   end
172
173   def lookup_info(ip)
174     if result = lookup(ip)
175       "#{result[:net]['CIDR']} => #{result[:customer].owner} (#{result[:customer].address})"
176     else
177       "Address not found."
178     end
179   end
180
181 end
182
183
184
185 #################################################################
186 ## The Plugin
187 ##
188
189 class IPLookupPlugin < Plugin
190   def help(plugin, topic="")
191     "iplookup [ip address / domain name] => lookup info about the owner of the IP address from the ARIN whois database"
192   end
193
194   def iplookup(m, params)
195     reply = ""
196     if params[:domain].match(/^#{Regexp::Irc::HOSTADDR}$/)
197       ip = params[:domain]
198     else
199       begin
200         ip = Resolv.getaddress(params[:domain])
201         reply << "#{params[:domain]} | "
202       rescue => e
203         m.reply "#{e.message}"
204         return
205       end
206     end
207
208     reply << ArinWhois.lookup_info(ip)
209
210     m.reply reply
211   end
212
213   def userip(m, params)
214     m.reply "not implemented yet"
215     #users = @channels[m.channel].users
216     #m.reply "users = #{users.inspect}"
217     #m.reply @bot.sendq("WHO #{params[:user]}")
218   end
219
220 end
221
222 plugin = IPLookupPlugin.new
223 plugin.map 'iplookup :domain', :action => 'iplookup', :thread => true
224 plugin.map 'userip :user', :action => 'userip', :requirements => {:user => /\w+/}, :thread => true
225
226
227 if __FILE__ == $0
228   include ArinWhois
229   data = open('whoistest.txt').read
230   c = ArinWhoisParser.new data
231   puts c.get_parsed_data.inspect
232 end