ssl_scan 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,470 @@
1
+ # -*- coding: binary -*-
2
+ require 'ssl_scan/socket'
3
+
4
+ module SSLScan
5
+ module Socket
6
+
7
+ ###
8
+ #
9
+ # This class provides an interface to enumerating an IP range
10
+ #
11
+ # This class uses start,stop pairs to represent ranges of addresses. This
12
+ # is very efficient for large numbers of consecutive addresses, and not
13
+ # show-stoppingly inefficient when storing a bunch of non-consecutive
14
+ # addresses, which should be a somewhat unusual case.
15
+ #
16
+ # @example
17
+ # r = RangeWalker.new("10.1,3.1-7.1-255")
18
+ # r.include?("10.3.7.255") #=> true
19
+ # r.length #=> 3570
20
+ # r.each do |addr|
21
+ # # do something with the address
22
+ # end
23
+ ###
24
+ class RangeWalker
25
+
26
+ # The total number of IPs within the range
27
+ #
28
+ # @return [Fixnum]
29
+ attr_reader :length
30
+
31
+ # for backwards compatibility
32
+ alias :num_ips :length
33
+
34
+ # A list of the {Range ranges} held in this RangeWalker
35
+ # @return [Array]
36
+ attr_reader :ranges
37
+
38
+ # Initializes a walker instance using the supplied range
39
+ #
40
+ # @param parseme [RangeWalker,String]
41
+ def initialize(parseme)
42
+ if parseme.is_a? RangeWalker
43
+ @ranges = parseme.ranges.dup
44
+ else
45
+ @ranges = parse(parseme)
46
+ end
47
+ reset
48
+ end
49
+
50
+ #
51
+ # Calls the instance method
52
+ #
53
+ # This is basically only useful for determining if a range can be parsed
54
+ #
55
+ # @return (see #parse)
56
+ def self.parse(parseme)
57
+ self.new.parse(parseme)
58
+ end
59
+
60
+ #
61
+ # Turn a human-readable range string into ranges we can step through one address at a time.
62
+ #
63
+ # Allow the following formats:
64
+ # "a.b.c.d e.f.g.h"
65
+ # "a.b.c.d, e.f.g.h"
66
+ # where each chunk is CIDR notation, (e.g. '10.1.1.0/24') or a range in nmap format (see {#expand_nmap})
67
+ #
68
+ # OR this format
69
+ # "a.b.c.d-e.f.g.h"
70
+ # where a.b.c.d and e.f.g.h are single IPs and the second must be
71
+ # bigger than the first.
72
+ #
73
+ # @param parseme [String]
74
+ # @return [self]
75
+ # @return [false] if +parseme+ cannot be parsed
76
+ def parse(parseme)
77
+ return nil if not parseme
78
+ ranges = []
79
+ parseme.split(', ').map{ |a| a.split(' ') }.flatten.each do |arg|
80
+ opts = {}
81
+
82
+ # Handle IPv6 first (support ranges, but not CIDR)
83
+ if arg.include?(":")
84
+ addrs = arg.split('-', 2)
85
+
86
+ # Handle a single address
87
+ if addrs.length == 1
88
+ addr, scope_id = addrs[0].split('%')
89
+ opts[:scope_id] = scope_id if scope_id
90
+ opts[:ipv6] = true
91
+
92
+ return false unless SSLScan::Socket.is_ipv6?(addr)
93
+ addr = SSLScan::Socket.addr_atoi(addr)
94
+ ranges.push(Range.new(addr, addr, opts))
95
+ next
96
+ end
97
+
98
+ addr1, scope_id = addrs[0].split('%')
99
+ opts[:scope_id] = scope_id if scope_id
100
+
101
+ addr2, scope_id = addrs[0].split('%')
102
+ ( opts[:scope_id] ||= scope_id ) if scope_id
103
+
104
+ # Both have to be IPv6 for this to work
105
+ return false unless (SSLScan::Socket.is_ipv6?(addr1) && SSLScan::Socket.is_ipv6?(addr2))
106
+
107
+ # Handle IPv6 ranges in the form of 2001::1-2001::10
108
+ addr1 = SSLScan::Socket.addr_atoi(addr1)
109
+ addr2 = SSLScan::Socket.addr_atoi(addr2)
110
+
111
+ ranges.push(Range.new(addr1, addr2, opts))
112
+ next
113
+
114
+ # Handle IPv4 CIDR
115
+ elsif arg.include?("/")
116
+ # Then it's CIDR notation and needs special case
117
+ return false if arg =~ /[,-]/ # Improper CIDR notation (can't mix with 1,3 or 1-3 style IP ranges)
118
+ return false if arg.scan("/").size > 1 # ..but there are too many slashes
119
+ ip_part,mask_part = arg.split("/")
120
+ return false if ip_part.nil? or ip_part.empty? or mask_part.nil? or mask_part.empty?
121
+ return false if mask_part !~ /^[0-9]{1,2}$/ # Illegal mask -- numerals only
122
+ return false if mask_part.to_i > 32 # This too -- between 0 and 32.
123
+ if ip_part =~ /^\d{1,3}(\.\d{1,3}){1,3}$/
124
+ return false unless ip_part =~ SSLScan::Socket::MATCH_IPV4
125
+ end
126
+ begin
127
+ SSLScan::Socket.getaddress(ip_part) # This allows for "www.metasploit.com/24" which is fun.
128
+ rescue Resolv::ResolvError, ::SocketError, Errno::ENOENT
129
+ return false # Can't resolve the ip_part, so bail.
130
+ end
131
+
132
+ expanded = expand_cidr(arg)
133
+ if expanded
134
+ ranges.push(expanded)
135
+ else
136
+ return false
137
+ end
138
+
139
+ # Handle hostnames
140
+ elsif arg =~ /[^-0-9,.*]/
141
+ # Then it's a domain name and we should send it on to addr_atoi
142
+ # unmolested to force a DNS lookup.
143
+ begin
144
+ ranges += SSLScan::Socket.addr_atoi_list(arg).map { |a| Range.new(a, a, opts) }
145
+ rescue Resolv::ResolvError, ::SocketError, Errno::ENOENT
146
+ return false
147
+ end
148
+
149
+ # Handle IPv4 ranges
150
+ elsif arg =~ /^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})-([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/
151
+
152
+ # Then it's in the format of 1.2.3.4-5.6.7.8
153
+ # Note, this will /not/ deal with DNS names, or the fancy/obscure 10...1-10...2
154
+ begin
155
+ start, stop = SSLScan::Socket.addr_atoi($1), SSLScan::Socket.addr_atoi($2)
156
+ return false if start > stop # The end is greater than the beginning.
157
+ ranges.push(Range.new(start, stop, opts))
158
+ rescue Resolv::ResolvError, ::SocketError, Errno::ENOENT
159
+ return false
160
+ end
161
+ else
162
+ # Returns an array of ranges
163
+ expanded = expand_nmap(arg)
164
+ if expanded
165
+ expanded.each { |r| ranges.push(r) }
166
+ end
167
+ end
168
+ end
169
+
170
+ # Remove any duplicate ranges
171
+ ranges = ranges.uniq
172
+
173
+ return ranges
174
+ end
175
+
176
+ #
177
+ # Resets the subnet walker back to its original state.
178
+ #
179
+ # @return [self]
180
+ def reset
181
+ return false if not valid?
182
+ @curr_range_index = 0
183
+ @curr_addr = @ranges.first.start
184
+ @length = 0
185
+ @ranges.each { |r| @length += r.length }
186
+
187
+ self
188
+ end
189
+
190
+ # Returns the next IP address.
191
+ #
192
+ # @return [String] The next address in the range
193
+ def next_ip
194
+ return false if not valid?
195
+ if (@curr_addr > @ranges[@curr_range_index].stop)
196
+ # Then we are at the end of this range. Grab the next one.
197
+
198
+ # Bail if there are no more ranges
199
+ return nil if (@ranges[@curr_range_index+1].nil?)
200
+
201
+ @curr_range_index += 1
202
+
203
+ @curr_addr = @ranges[@curr_range_index].start
204
+ end
205
+ addr = SSLScan::Socket.addr_itoa(@curr_addr, @ranges[@curr_range_index].ipv6?)
206
+
207
+ if @ranges[@curr_range_index].options[:scope_id]
208
+ addr = addr + '%' + @ranges[@curr_range_index].options[:scope_id]
209
+ end
210
+
211
+ @curr_addr += 1
212
+ return addr
213
+ end
214
+
215
+ alias :next :next_ip
216
+
217
+ # Whether this RangeWalker's ranges are valid
218
+ def valid?
219
+ (@ranges && !@ranges.empty?)
220
+ end
221
+
222
+ # Returns true if the argument is an ip address that falls within any of
223
+ # the stored ranges.
224
+ #
225
+ # @return [true] if this RangeWalker contains +addr+
226
+ # @return [false] if not
227
+ def include?(addr)
228
+ return false if not @ranges
229
+ if (addr.is_a? String)
230
+ addr = SSLScan::Socket.addr_atoi(addr)
231
+ end
232
+ @ranges.map { |r|
233
+ if addr.between?(r.start, r.stop)
234
+ return true
235
+ end
236
+ }
237
+ return false
238
+ end
239
+
240
+ #
241
+ # Returns true if this RangeWalker includes *all* of the addresses in the
242
+ # given RangeWalker
243
+ #
244
+ # @param other [RangeWalker]
245
+ def include_range?(other)
246
+ return false if (!@ranges || @ranges.empty?)
247
+ return false if !other.ranges || other.ranges.empty?
248
+
249
+ # Check that all the ranges in +other+ fall within at least one of
250
+ # our ranges.
251
+ other.ranges.all? do |other_range|
252
+ ranges.any? do |range|
253
+ other_range.start.between?(range.start, range.stop) && other_range.stop.between?(range.start, range.stop)
254
+ end
255
+ end
256
+ end
257
+
258
+ #
259
+ # Calls the given block with each address. This is basically a wrapper for
260
+ # {#next_ip}
261
+ #
262
+ # @return [self]
263
+ def each(&block)
264
+ while (ip = next_ip)
265
+ block.call(ip)
266
+ end
267
+ reset
268
+
269
+ self
270
+ end
271
+
272
+ #
273
+ # Returns an Array with one element, a {Range} defined by the given CIDR
274
+ # block.
275
+ #
276
+ # @see SSLScan::Socket.cidr_crack
277
+ # @param arg [String] A CIDR range
278
+ # @return [Range]
279
+ # @return [false] if +arg+ is not valid CIDR notation
280
+ def expand_cidr(arg)
281
+ start,stop = SSLScan::Socket.cidr_crack(arg)
282
+ if !start or !stop
283
+ return false
284
+ end
285
+ range = Range.new
286
+ range.start = SSLScan::Socket.addr_atoi(start)
287
+ range.stop = SSLScan::Socket.addr_atoi(stop)
288
+ range.options = { :ipv6 => (arg.include?(":")) }
289
+
290
+ return range
291
+ end
292
+
293
+ #
294
+ # Expands an nmap-style host range x.x.x.x where x can be simply "*" which
295
+ # means 0-255 or any combination and repitition of:
296
+ # i,n
297
+ # n-m
298
+ # i,n-m
299
+ # n-m,i
300
+ # ensuring that n is never greater than m.
301
+ #
302
+ # non-unique elements will be removed
303
+ # e.g.:
304
+ # 10.1.1.1-3,2-2,2 => ["10.1.1.1", "10.1.1.2", "10.1.1.3"]
305
+ # 10.1.1.1-3,7 => ["10.1.1.1", "10.1.1.2", "10.1.1.3", "10.1.1.7"]
306
+ #
307
+ # Returns an array of Ranges
308
+ #
309
+ def expand_nmap(arg)
310
+ # Can't really do anything with IPv6
311
+ return false if arg.include?(":")
312
+
313
+ # nmap calls these errors, but it's hard to catch them with our
314
+ # splitting below, so short-cut them here
315
+ return false if arg.include?(",-") or arg.include?("-,")
316
+
317
+ bytes = []
318
+ sections = arg.split('.')
319
+ if sections.length != 4
320
+ # Too many or not enough dots
321
+ return false
322
+ end
323
+ sections.each { |section|
324
+ if section.empty?
325
+ # pretty sure this is an unintentional artifact of the C
326
+ # functions that turn strings into ints, but it sort of makes
327
+ # sense, so why not
328
+ # "10...1" => "10.0.0.1"
329
+ section = "0"
330
+ end
331
+
332
+ if section == "*"
333
+ # I think this ought to be 1-254, but this is how nmap does it.
334
+ section = "0-255"
335
+ elsif section.include?("*")
336
+ return false
337
+ end
338
+
339
+ # Break down the sections into ranges like so
340
+ # "1-3,5-7" => ["1-3", "5-7"]
341
+ ranges = section.split(',', -1)
342
+ sets = []
343
+ ranges.each { |r|
344
+ bounds = []
345
+ if r.include?('-')
346
+ # Then it's an actual range, break it down into start,stop
347
+ # pairs:
348
+ # "1-3" => [ 1, 3 ]
349
+ # if the lower bound is empty, start at 0
350
+ # if the upper bound is empty, stop at 255
351
+ #
352
+ bounds = r.split('-', -1)
353
+ return false if (bounds.length > 2)
354
+
355
+ bounds[0] = 0 if bounds[0].nil? or bounds[0].empty?
356
+ bounds[1] = 255 if bounds[1].nil? or bounds[1].empty?
357
+ bounds.map!{|b| b.to_i}
358
+ return false if bounds[0] > bounds[1]
359
+ else
360
+ # Then it's a single value
361
+ bounds[0] = r.to_i
362
+ end
363
+ return false if bounds[0] > 255 or (bounds[1] and bounds[1] > 255)
364
+ return false if bounds[1] and bounds[0] > bounds[1]
365
+ if bounds[1]
366
+ bounds[0].upto(bounds[1]) do |i|
367
+ sets.push(i)
368
+ end
369
+ elsif bounds[0]
370
+ sets.push(bounds[0])
371
+ end
372
+ }
373
+ bytes.push(sets.sort.uniq)
374
+ }
375
+
376
+ #
377
+ # Combinitorically squish all of the quads together into a big list of
378
+ # ip addresses, stored as ints
379
+ #
380
+ # e.g.:
381
+ # [[1],[1],[1,2],[1,2]]
382
+ # =>
383
+ # [atoi("1.1.1.1"),atoi("1.1.1.2"),atoi("1.1.2.1"),atoi("1.1.2.2")]
384
+ addrs = []
385
+ for a in bytes[0]
386
+ for b in bytes[1]
387
+ for c in bytes[2]
388
+ for d in bytes[3]
389
+ ip = (a << 24) + (b << 16) + (c << 8) + d
390
+ addrs.push ip
391
+ end
392
+ end
393
+ end
394
+ end
395
+
396
+ addrs.sort!
397
+ addrs.uniq!
398
+
399
+ rng = Range.new
400
+ rng.options = { :ipv6 => false }
401
+ rng.start = addrs[0]
402
+
403
+ ranges = []
404
+ 1.upto(addrs.length - 1) do |idx|
405
+ if addrs[idx - 1] + 1 == addrs[idx]
406
+ # Then this address is contained in the current range
407
+ next
408
+ else
409
+ # Then this address is the upper bound for the current range
410
+ rng.stop = addrs[idx - 1]
411
+ ranges.push(rng.dup)
412
+ rng.start = addrs[idx]
413
+ end
414
+ end
415
+ rng.stop = addrs[addrs.length - 1]
416
+ ranges.push(rng.dup)
417
+ return ranges
418
+ end
419
+
420
+ end
421
+
422
+ # A range of IP addresses
423
+ class Range
424
+
425
+ #@!attribute start
426
+ # The first address in this range, as a number
427
+ # @return [Fixnum]
428
+ attr_accessor :start
429
+ #@!attribute stop
430
+ # The last address in this range, as a number
431
+ # @return [Fixnum]
432
+ attr_accessor :stop
433
+ #@!attribute options
434
+ # @return [Hash]
435
+ attr_accessor :options
436
+
437
+ # @param start [Fixnum]
438
+ # @param stop [Fixnum]
439
+ # @param options [Hash] Recognized keys are:
440
+ # * +:ipv6+
441
+ # * +:scope_id+
442
+ def initialize(start=nil, stop=nil, options=nil)
443
+ @start = start
444
+ @stop = stop
445
+ @options = options
446
+ end
447
+
448
+ # Compare attributes with +other+
449
+ # @param other [Range]
450
+ # @return [Boolean]
451
+ def ==(other)
452
+ (other.start == start && other.stop == stop && other.ipv6? == ipv6? && other.options == options)
453
+ end
454
+
455
+ # The number of addresses in this Range
456
+ # @return [Fixnum]
457
+ def length
458
+ stop - start + 1
459
+ end
460
+ alias :count :length
461
+
462
+ # Whether this Range contains IPv6 or IPv4 addresses
463
+ # @return [Boolean]
464
+ def ipv6?
465
+ options[:ipv6]
466
+ end
467
+ end
468
+
469
+ end
470
+ end