shodan-ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ #
2
+ # shodan-ruby - A Ruby interface to SHODAN, a computer search engine.
3
+ #
4
+ # Copyright (c) 2009 Hal Brodigan (postmodern.mod3 at gmail.com)
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
+ #
20
+
21
+ require 'shodan/extensions/uri'
@@ -0,0 +1,231 @@
1
+ #
2
+ # shodan-ruby - A Ruby interface to SHODAN, a computer search engine.
3
+ #
4
+ # Copyright (c) 2009 Hal Brodigan (postmodern.mod3 at gmail.com)
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
+ #
20
+
21
+ module Shodan
22
+ module HasPages
23
+ include Enumerable
24
+
25
+ #
26
+ # The first page.
27
+ #
28
+ # @return [Page]
29
+ # The first page.
30
+ #
31
+ def first_page
32
+ page_cache[1]
33
+ end
34
+
35
+ #
36
+ # The page at a given index.
37
+ #
38
+ # @param [Integer] index
39
+ # The index to request a page for.
40
+ #
41
+ # @return [Page]
42
+ # The page at the specified index.
43
+ #
44
+ def [](index)
45
+ page_cache[index]
46
+ end
47
+
48
+ #
49
+ # The pages at the given indices.
50
+ #
51
+ # @param [Range, Array<Integer>]
52
+ # The indices to request pages for.
53
+ #
54
+ # @return [Array<Page>]
55
+ # The pages at the specified indices.
56
+ #
57
+ def pages(indices)
58
+ indices.map { |index| page_cache[index] }
59
+ end
60
+
61
+ #
62
+ # Iterates over the pages at given indices.
63
+ #
64
+ # @param [Range, Array<Integer>]
65
+ # The indices to request pages for.
66
+ #
67
+ # @yield [page]
68
+ # The given block will be passed each page at one of the specified
69
+ # indices.
70
+ #
71
+ # @yieldparam [Page] page
72
+ # The page at one of the specified indices.
73
+ #
74
+ # @return [self]
75
+ #
76
+ def each_page(indices,&block)
77
+ indices.map { |index| block.call(page_cache[index]) }
78
+ return self
79
+ end
80
+
81
+ #
82
+ # Iterates over every page, until an empty page is encountered.
83
+ #
84
+ # @yield [page]
85
+ # The given block will receive every non-empty page.
86
+ #
87
+ # @yieldparam [Page] page
88
+ # A non-empty page.
89
+ #
90
+ # @return [self]
91
+ #
92
+ def each(&block)
93
+ index = 1
94
+
95
+ until ((next_page = page_cache[index]).empty?) do
96
+ block.call(next_page)
97
+ index = index + 1
98
+ end
99
+
100
+ return self
101
+ end
102
+
103
+ #
104
+ # Iterates over every host.
105
+ #
106
+ # @yield [host]
107
+ # If a block is given, it will be passed every host from every page.
108
+ #
109
+ # @yieldparam [Host] host
110
+ # A host.
111
+ #
112
+ # @return [self]
113
+ #
114
+ def each_host(&block)
115
+ each { |page| page.each(&block) }
116
+ end
117
+
118
+ #
119
+ # Iterates over the hosts in a page at a given index.
120
+ #
121
+ # @param [Integer] index
122
+ # The index of the page to iterate over.
123
+ #
124
+ # @yield [host]
125
+ # If a block is given, it will be passed every host on the page at
126
+ # the specified index.
127
+ #
128
+ # @yieldparam [Host] host
129
+ # A host on the page at the specified index.
130
+ #
131
+ def each_on_page(index,&block)
132
+ page_cache[index].each(&block)
133
+ end
134
+
135
+ #
136
+ # Iterates over each host on the pages at the given indices.
137
+ #
138
+ # @param [Range, Array<Integer>] indices
139
+ # The indices of the pages.
140
+ #
141
+ # @yield [host]
142
+ # If a block is given, it will be passed ever host on the pages
143
+ # at the specified indices.
144
+ #
145
+ # @yieldparam [Host] host
146
+ # A host on one of the pages at the specified indices.
147
+ #
148
+ # @return [self]
149
+ #
150
+ def each_on_pages(indices,&block)
151
+ each_page(indices) { |page| page.each(&block) }
152
+ end
153
+
154
+ #
155
+ # The first host on the first page.
156
+ #
157
+ # @return [Host]
158
+ # The first host.
159
+ #
160
+ def first_host
161
+ first_page.first
162
+ end
163
+
164
+ #
165
+ # Returns the host at a given index.
166
+ #
167
+ # @param [Integer] index
168
+ # The index to request the host at.
169
+ #
170
+ # @return [Host]
171
+ # The host at the given index.
172
+ #
173
+ def host_at(index)
174
+ page(page_index_of(index))[result_index_of(index)]
175
+ end
176
+
177
+ protected
178
+
179
+ #
180
+ # Calculates the page index for a given result rank.
181
+ #
182
+ # @param [Integer]
183
+ # The rank of a result within the total results.
184
+ #
185
+ # @return [Integer]
186
+ # The index of the page that will contain the result, with the
187
+ # specified rank.
188
+ #
189
+ def page_index_of(rank)
190
+ (((rank.to_i - 1) / results_per_page.to_i) + 1)
191
+ end
192
+
193
+ #
194
+ # Calculates the result rank of the first result on the page at the
195
+ # given index.
196
+ #
197
+ # @param [Integer] page_index
198
+ # The index of a page.
199
+ #
200
+ # @return [Integer]
201
+ # The rank of the first result on the page, with the specified index.
202
+ #
203
+ def result_offset_of(page_index)
204
+ ((page_index.to_i - 1) * results_per_page.to_i)
205
+ end
206
+
207
+ #
208
+ # Calculates the in-page index of a given result rank.
209
+ #
210
+ # @param [Integer] rank
211
+ # The result rank.
212
+ #
213
+ # @return [Integer]
214
+ # The index of the result, within the page that will contain the
215
+ # result.
216
+ #
217
+ def result_index_of(rank)
218
+ ((rank.to_i - 1) % results_per_page.to_i)
219
+ end
220
+
221
+ #
222
+ # The page cache.
223
+ #
224
+ # @return [Hash{Integer => Page}]
225
+ # A persistant Hash of pages.
226
+ #
227
+ def page_cache
228
+ @page_cache ||= Hash.new { |hash,key| hash[key] = page(key.to_i) }
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,146 @@
1
+ #
2
+ # shodan-ruby - A Ruby interface to SHODAN, a computer search engine.
3
+ #
4
+ # Copyright (c) 2009 Hal Brodigan (postmodern.mod3 at gmail.com)
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
+ #
20
+
21
+ module Shodan
22
+ class Host
23
+
24
+ # The IP of the host
25
+ attr_reader :ip
26
+
27
+ # The date the host was added
28
+ attr_reader :date
29
+
30
+ # The host-name of the host
31
+ attr_reader :hostname
32
+
33
+ # The response returned by the host
34
+ attr_reader :response
35
+
36
+ # The HTTP version supported by the host
37
+ attr_reader :http_version
38
+
39
+ # The HTTP status code in the response
40
+ attr_reader :http_code
41
+
42
+ # The HTTP status string in the response
43
+ attr_reader :http_status
44
+
45
+ # The HTTP headers included in the response
46
+ attr_reader :http_headers
47
+
48
+ # The body of the HTTP response
49
+ attr_reader :http_body
50
+
51
+ #
52
+ # Creates a new Host object.
53
+ #
54
+ # @param [String] ip
55
+ # The IP address of the host.
56
+ #
57
+ # @param [String] date
58
+ # The date the host was added.
59
+ #
60
+ # @param [String] response
61
+ # The response received from the host.
62
+ #
63
+ # @param [String] hostname
64
+ # The optional name of the host.
65
+ #
66
+ def initialize(ip,date,response,hostname=nil)
67
+ @ip = ip
68
+ @date = Date.parse(date)
69
+ @response = response
70
+ @hostname = hostname
71
+
72
+ @http_version = nil
73
+ @http_code = nil
74
+ @http_status = nil
75
+ @http_headers = {}
76
+ @http_body = nil
77
+
78
+ if response =~ /^HTTP\/?/
79
+ lines = response.split(/[\r\n]/)
80
+ match = lines.first.split(/\s+/,3)
81
+
82
+ if match[0].include?('/')
83
+ @http_version = match[0].split('/').last
84
+ end
85
+
86
+ if match[1]
87
+ @http_code = match[1].to_i
88
+ end
89
+
90
+ @http_status = match[2]
91
+ @http_body = ''
92
+
93
+ lines[1..-1].each_with_index do |line,index|
94
+ if line.empty?
95
+ @http_body = lines[(index+2)..-1].join("\n")
96
+ break
97
+ end
98
+
99
+ name, value = line.chomp.split(/:\s+/,2)
100
+
101
+ @http_headers[name] = value
102
+ end
103
+ end
104
+ end
105
+
106
+ #
107
+ # The server software the host runs.
108
+ #
109
+ # @return [String]
110
+ # The name of the software.
111
+ #
112
+ def server_name
113
+ if (server = @http_headers['Server'])
114
+ return server.split('/',2).first
115
+ end
116
+ end
117
+
118
+ #
119
+ # The server software version.
120
+ #
121
+ # @return [String]
122
+ # The version of the software.
123
+ #
124
+ def server_version
125
+ if (server = @http_headers['Server'])
126
+ return server.split('/',2).last
127
+ end
128
+ end
129
+
130
+ protected
131
+
132
+ #
133
+ # Provides transparent access to the values in +headers+.
134
+ #
135
+ def method_missing(sym,*args,&block)
136
+ if (args.empty? && block.nil?)
137
+ name = sym.id2name.gsub(/_/,'-').capitalize
138
+
139
+ return @http_headers[name] if @http_headers.key?(name)
140
+ end
141
+
142
+ return super(sym,*args,&block)
143
+ end
144
+
145
+ end
146
+ end
@@ -0,0 +1,308 @@
1
+ #
2
+ # shodan-ruby - A Ruby interface to SHODAN, a computer search engine.
3
+ #
4
+ # Copyright (c) 2009 Hal Brodigan (postmodern.mod3 at gmail.com)
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
+ #
20
+
21
+ require 'shodan/host'
22
+
23
+ module Shodan
24
+ class Page < Array
25
+
26
+ #
27
+ # Creates a new Page object.
28
+ #
29
+ # @param [Array] hosts
30
+ # The initial hosts.
31
+ #
32
+ # @yield [page]
33
+ # If a block is given, it will be passed the page.
34
+ #
35
+ # @yieldparam [Page] page
36
+ # The newly created Page object.
37
+ #
38
+ def initialize(hosts=[],&block)
39
+ super(hosts)
40
+
41
+ block.call(self) if block
42
+ end
43
+
44
+ #
45
+ # Maps the hosts within the page.
46
+ #
47
+ # @yield [host]
48
+ # The given block will be used to map each host in the page.
49
+ #
50
+ # @yieldparam [Host] host
51
+ # A host within the page.
52
+ #
53
+ # @return [Array]
54
+ # The resulting mapped Array.
55
+ #
56
+ # @example
57
+ # page.map
58
+ # # => #<Page: ...>
59
+ #
60
+ # @example
61
+ # page.map { |host| host.ip }
62
+ # # => [...]
63
+ #
64
+ def map(&block)
65
+ return self unless block
66
+
67
+ mapped = []
68
+
69
+ each { |element| mapped << block.call(element) }
70
+ return mapped
71
+ end
72
+
73
+ #
74
+ # Selects the hosts within the page.
75
+ #
76
+ # @yield [host]
77
+ # The given block will be used to select hosts from the page.
78
+ #
79
+ # @yieldparam [Host] host
80
+ # A host in the page.
81
+ #
82
+ # @return [Page]
83
+ # A sub-set of the hosts within the page.
84
+ #
85
+ # @example
86
+ # page.select { |host| host.headers['Server'] =~ /IIS/ }
87
+ #
88
+ def select(&block)
89
+ self.class.new(super(&block))
90
+ end
91
+
92
+ alias hosts_with select
93
+
94
+ #
95
+ # Iterates over the IP addresses of every host in the page.
96
+ #
97
+ # @yield [ip]
98
+ # If a block is given, it will be passed the IP addresses of every
99
+ # host.
100
+ #
101
+ # @yieldparam [String] ip
102
+ # An IP address of a host.
103
+ #
104
+ # @return [self]
105
+ #
106
+ def each_ip(&block)
107
+ each do |host|
108
+ block.call(host.ip) if block
109
+ end
110
+ end
111
+
112
+ #
113
+ # Selects the hosts with the matching IP address.
114
+ #
115
+ # @param [Regexp, String] ip
116
+ # The IP address to search for.
117
+ #
118
+ # @yield [host]
119
+ # If a block is also given, it will be passed every matching host.
120
+ #
121
+ # @yieldparam [Host] host
122
+ # A host with the matching IP address.
123
+ #
124
+ # @return [Array<Host>]
125
+ # The hosts with the matching IP address.
126
+ #
127
+ def hosts_with_ip(ip,&block)
128
+ hosts_with do |host|
129
+ if host.ip.match(ip)
130
+ block.call(host) if block
131
+
132
+ true
133
+ end
134
+ end
135
+ end
136
+
137
+ #
138
+ # The IP addresses of the hosts in the page.
139
+ #
140
+ # @return [Array<String>]
141
+ # The IP addresses.
142
+ #
143
+ def ips
144
+ Enumerator.new(self,:each_ip).to_a
145
+ end
146
+
147
+ #
148
+ # Iterates over the host names of every host in the page.
149
+ #
150
+ # @yield [hostname]
151
+ # If a block is given, it will be passed the host names of every host.
152
+ #
153
+ # @yieldparam [String] hostname
154
+ # A host name.
155
+ #
156
+ # @return [self]
157
+ #
158
+ def each_hostname(&block)
159
+ each do |host|
160
+ block.call(host.hostname) if (block && host.hostname)
161
+ end
162
+ end
163
+
164
+ #
165
+ # Selects the hosts with the matching host name.
166
+ #
167
+ # @param [Regexp, String] hostname
168
+ # The host name to search for.
169
+ #
170
+ # @yield [host]
171
+ # If a block is also given, it will be passed every matching host.
172
+ #
173
+ # @yieldparam [Host] host
174
+ # A host with the matching host name.
175
+ #
176
+ # @return [Array<Host>]
177
+ # The hosts with the matching host name.
178
+ #
179
+ def hosts_with_name(name,&block)
180
+ hosts_with do |host|
181
+ if (host.hostname && host.hostname.match(name))
182
+ block.call(host) if block
183
+
184
+ true
185
+ end
186
+ end
187
+ end
188
+
189
+ #
190
+ # The names of the hosts in the page.
191
+ #
192
+ # @return [Array<String>]
193
+ # The host names.
194
+ #
195
+ def hostnames
196
+ Enumerator.new(self,:each_hostname).to_a
197
+ end
198
+
199
+ #
200
+ # Iterates over the dates that each host was added.
201
+ #
202
+ # @yield [date]
203
+ # If a block is given, it will be passed the dates that each host was
204
+ # added.
205
+ #
206
+ # @yieldparam [Date] date
207
+ # A date that a host was added.
208
+ #
209
+ # @return [self]
210
+ #
211
+ def each_date(&block)
212
+ each do |host|
213
+ block.call(host.date) if block
214
+ end
215
+ end
216
+
217
+ #
218
+ # The dates that the hosts were added.
219
+ #
220
+ # @return [Array<Date>]
221
+ # The dates.
222
+ #
223
+ def dates
224
+ Enumerator.new(self,:each_date).to_a
225
+ end
226
+
227
+ #
228
+ # Iterates over the responses of each host in the page.
229
+ #
230
+ # @yield [response]
231
+ # If a block is given, it will be passed the responses of each host.
232
+ #
233
+ # @yieldparam [String] response
234
+ # The initial response of a host.
235
+ #
236
+ # @return [self]
237
+ #
238
+ def each_response(&block)
239
+ each do |host|
240
+ block.call(host.response) if block
241
+ end
242
+ end
243
+
244
+ #
245
+ # Selects the hosts with the matching response.
246
+ #
247
+ # @param [Regexp, String] pattern
248
+ # The response pattern to search for.
249
+ #
250
+ # @yield [host]
251
+ # If a block is also given, it will be passed every matching host.
252
+ #
253
+ # @yieldparam [Host] host
254
+ # A host with the matching response.
255
+ #
256
+ # @return [Array<Host>]
257
+ # The hosts with the matching response.
258
+ #
259
+ def responses_with(pattern,&block)
260
+ hosts_with do |host|
261
+ if host.response.match(pattern)
262
+ block.call(host) if block
263
+
264
+ true
265
+ end
266
+ end
267
+ end
268
+
269
+ #
270
+ # The responses of the hosts in the page.
271
+ #
272
+ # @return [Array<String>]
273
+ # The responses.
274
+ #
275
+ def responses(&block)
276
+ Enumerator.new(self,:each_response).to_a
277
+ end
278
+
279
+ #
280
+ # Itereates over the HTTP headers of each host in the page.
281
+ #
282
+ # @yield [headers]
283
+ # If a block is given, it will be passed the headers from each host
284
+ # in the page.
285
+ #
286
+ # @yieldparam [Hash] headers
287
+ # The headers from a host in the page.
288
+ #
289
+ # @return [self]
290
+ #
291
+ def each_http_headers(&block)
292
+ each do |host|
293
+ block.call(host.http_headers) if block
294
+ end
295
+ end
296
+
297
+ #
298
+ # The HTTP headers from the hosts in the page.
299
+ #
300
+ # @return [Array<Hash>]
301
+ # The HTTP headers.
302
+ #
303
+ def http_headers
304
+ Enumerator.new(self,:each_http_headers).to_a
305
+ end
306
+
307
+ end
308
+ end