shodan-ruby 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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