File: country.rb

package info (click to toggle)
whatweb 0.6.3-1
  • links: PTS
  • area: main
  • in suites: forky, sid
  • size: 24,000 kB
  • sloc: ruby: 44,049; sh: 213; makefile: 41
file content (133 lines) | stat: -rw-r--r-- 4,146 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
##
# This file is part of WhatWeb and may be subject to
# redistribution and commercial restrictions. Please see the WhatWeb
# web site for more information on licensing and terms of use.
# https://morningstarsecurity.com/research/whatweb
##
Plugin.define do
name "Country"
authors [
  "Andrew Horton",
  # v0.2 # display full country names
  # v0.3 # fix crash with ipv6 addresses, update comments, update database
  "Code0x58", # v0.4 # new plugin behaviour
]
version "0.4"
description "Shows the country the IPv4 address belongs to. This uses the GeoIP IP2Country database from http://software77.net/geo-ip/. Instructions on updating the database are in the plugin comments."

# Lookup code developed by Matthias Wachter for rubyquiz.com and used with permission.
# Local IPv4 addresses are represented as ZZ according to an ISO convention. 

# How to Update Database
# ----------------------
# rm plugins/country-ips.dat plugins/IpToCountry.csv
# wget software77.net/geo-ip/?DL=1 -O plugins/IpToCountry.csv.gz
# gzip -d plugins/IpToCountry.csv.gz
# then run whatweb on a URL, it will automatically make the country-ips.dat file


def startup
# ok, set up rfile. open once.
	whatweb_folder = File.expand_path(File.dirname(__FILE__))	
	country_db = whatweb_folder + "/country-ips.dat"

	if File.exist?(country_db)
		rfile = File.open(country_db, "rb")
	else		
		if File.exist?(whatweb_folder + "/IpToCountry.csv")
			# pack that file & do it once
			last_start = nil
			last_end = nil
			last_country = nil
			File.open(whatweb_folder + "/country-ips.dat","wb") do |wfile|
				IO.foreach(whatweb_folder + "/IpToCountry.csv") do |line|
					next if line !~ /^"/
					s, e, _, _, co = line.delete!("\"").split(",")
					s,e = s.to_i, e.to_i
					if !last_start
						# initialize with first entry
						last_start,last_end,last_country = s, e, co
					else
						if s == last_end + 1 and co == last_country
							# squeeze if successive ranges have zero gap
							last_end = e
						else
							# append last entry, remember new one
							wfile << [last_start, last_end, last_country].pack("NNa2")
							last_start, last_end, last_country = s, e, co
						end
					end
				end
				# print last entry
				if last_start
					wfile << [last_start, last_end, last_country].pack("NNa2")
				end
			end
			# open the DB now
			rfile = File.open(country_db, "rb")
		end
	end

	f = whatweb_folder + "/country-codes.txt"
	ccnames = {}
	File.open(f, "r:UTF-8").readlines.each do |line|
		country, code, _, _ = line.split(",")
		ccnames[code] = country
	end

  # TODO: load rfile into memory as it is under 1MiB and spares a load of seeks
  @variables = {
    rfile: rfile,
    ccnames: ccnames,
    mutex: Mutex.new, # for access to rfile
  }

end

passive do
	m = []
	rfile = @variables[:rfile]
  ccnames = @variables[:ccnames]
  mutex = @variables[:mutex]

  if rfile and @ip and @ip =~ /^([0-9]{1,3}\.){3}[0-9]{1,3}$/
  	mutex.synchronize do
		  rfile.seek(0, IO::SEEK_END)
		  record_max = rfile.pos / 10 - 1

	    # build a 4-char string representation of IP address
	    # in network byte order so it can be a string compare below
	    ipstr = @ip.split(".").map {|x| x.to_i.chr}.join

	    # low/high water marks initialized
	    low, high = 0, record_max
	    loop do
	      mid = (low + high) / 2       # binary search median
	      rfile.seek(10 * mid)     # one record is 10 byte, seek to position
	      str = rfile.read(8)      # for range matching, we need only 8 bytes
	      # at comparison, values are big endian, i.e. packed("N")
	      if ipstr >= str[0, 4]     # is this IP not below the current range?
					if ipstr <= str[4, 4]   # is this IP not above the current range?
					  #puts  # a perfect match, voila!
					  cc = rfile.read(2)
					  m << {string: ccnames[cc], module: cc}
					  break
					else
		  			low = mid + 1          # binary search: raise lower limit
					end
	      else
					high = mid - 1           # binary search: reduce upper limit
	      end

	      if low > high            # no entries left? nothing found		
					#m << {:string=>"No country"}	
					break
	      end

	    end
  	end
	end
	m
end
end