ssl_scan 0.0.1

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,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