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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +51 -0
- data/Rakefile +1 -0
- data/bin/ssl_scan +4 -0
- data/lib/ssl_scan/client.rb +0 -0
- data/lib/ssl_scan/compat.rb +388 -0
- data/lib/ssl_scan/exceptions.rb +274 -0
- data/lib/ssl_scan/io/bidirectional_pipe.rb +161 -0
- data/lib/ssl_scan/io/datagram_abstraction.rb +35 -0
- data/lib/ssl_scan/io/ring_buffer.rb +369 -0
- data/lib/ssl_scan/io/stream.rb +312 -0
- data/lib/ssl_scan/io/stream_abstraction.rb +209 -0
- data/lib/ssl_scan/io/stream_server.rb +221 -0
- data/lib/ssl_scan/result.rb +165 -0
- data/lib/ssl_scan/scanner.rb +241 -0
- data/lib/ssl_scan/socket/comm/local.rb +526 -0
- data/lib/ssl_scan/socket/comm.rb +120 -0
- data/lib/ssl_scan/socket/ip.rb +131 -0
- data/lib/ssl_scan/socket/parameters.rb +363 -0
- data/lib/ssl_scan/socket/range_walker.rb +470 -0
- data/lib/ssl_scan/socket/ssl_tcp.rb +345 -0
- data/lib/ssl_scan/socket/ssl_tcp_server.rb +188 -0
- data/lib/ssl_scan/socket/subnet_walker.rb +76 -0
- data/lib/ssl_scan/socket/switch_board.rb +289 -0
- data/lib/ssl_scan/socket/tcp.rb +79 -0
- data/lib/ssl_scan/socket/tcp_server.rb +67 -0
- data/lib/ssl_scan/socket/udp.rb +165 -0
- data/lib/ssl_scan/socket.rb +773 -0
- data/lib/ssl_scan/sync/thread_safe.rb +83 -0
- data/lib/ssl_scan/version.rb +9 -0
- data/lib/ssl_scan.rb +11 -0
- data/sslscan.gemspec +23 -0
- metadata +107 -0
@@ -0,0 +1,773 @@
|
|
1
|
+
# -*- coding: binary -*-
|
2
|
+
require 'socket'
|
3
|
+
require 'thread'
|
4
|
+
require 'resolv'
|
5
|
+
require 'ssl_scan/exceptions'
|
6
|
+
|
7
|
+
module SSLScan
|
8
|
+
|
9
|
+
###
|
10
|
+
#
|
11
|
+
# Base class for all sockets.
|
12
|
+
#
|
13
|
+
###
|
14
|
+
module Socket
|
15
|
+
|
16
|
+
module Comm
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'ssl_scan/socket/parameters'
|
20
|
+
require 'ssl_scan/socket/tcp'
|
21
|
+
require 'ssl_scan/socket/tcp_server'
|
22
|
+
|
23
|
+
require 'ssl_scan/socket/comm'
|
24
|
+
require 'ssl_scan/socket/comm/local'
|
25
|
+
|
26
|
+
require 'ssl_scan/socket/switch_board'
|
27
|
+
require 'ssl_scan/socket/subnet_walker'
|
28
|
+
require 'ssl_scan/socket/range_walker'
|
29
|
+
|
30
|
+
##
|
31
|
+
#
|
32
|
+
# Factory methods
|
33
|
+
#
|
34
|
+
##
|
35
|
+
|
36
|
+
#
|
37
|
+
# Create a socket instance using the supplied parameter hash.
|
38
|
+
#
|
39
|
+
def self.create(opts = {})
|
40
|
+
return create_param(SSLScan::Socket::Parameters.from_hash(opts))
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Create a socket using the supplied SSLScan::Socket::Parameter instance.
|
45
|
+
#
|
46
|
+
def self.create_param(param)
|
47
|
+
return param.comm.create(param)
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Create a TCP socket using the supplied parameter hash.
|
52
|
+
#
|
53
|
+
def self.create_tcp(opts = {})
|
54
|
+
return create_param(SSLScan::Socket::Parameters.from_hash(opts.merge('Proto' => 'tcp')))
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Create a TCP server socket using the supplied parameter hash.
|
59
|
+
#
|
60
|
+
def self.create_tcp_server(opts = {})
|
61
|
+
return create_tcp(opts.merge('Server' => true))
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Create a UDP socket using the supplied parameter hash.
|
66
|
+
#
|
67
|
+
def self.create_udp(opts = {})
|
68
|
+
return create_param(SSLScan::Socket::Parameters.from_hash(opts.merge('Proto' => 'udp')))
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Create a IP socket using the supplied parameter hash.
|
73
|
+
#
|
74
|
+
def self.create_ip(opts = {})
|
75
|
+
return create_param(SSLScan::Socket::Parameters.from_hash(opts.merge('Proto' => 'ip')))
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
#
|
80
|
+
# Common Regular Expressions
|
81
|
+
#
|
82
|
+
|
83
|
+
MATCH_IPV6 = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/
|
84
|
+
|
85
|
+
MATCH_IPV4 = /^\s*(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))\s*$/
|
86
|
+
|
87
|
+
MATCH_IPV4_PRIVATE = /^\s*(?:10\.|192\.168|172.(?:1[6-9]|2[0-9]|3[01])\.|169\.254)/
|
88
|
+
|
89
|
+
##
|
90
|
+
#
|
91
|
+
# Serialization
|
92
|
+
#
|
93
|
+
##
|
94
|
+
|
95
|
+
|
96
|
+
# Cache our IPv6 support flag
|
97
|
+
@@support_ipv6 = nil
|
98
|
+
|
99
|
+
#
|
100
|
+
# Determine whether we support IPv6
|
101
|
+
#
|
102
|
+
def self.support_ipv6?
|
103
|
+
return @@support_ipv6 if not @@support_ipv6.nil?
|
104
|
+
|
105
|
+
@@support_ipv6 = false
|
106
|
+
|
107
|
+
if (::Socket.const_defined?('AF_INET6'))
|
108
|
+
begin
|
109
|
+
s = ::Socket.new(::Socket::AF_INET6, ::Socket::SOCK_DGRAM, ::Socket::IPPROTO_UDP)
|
110
|
+
s.close
|
111
|
+
@@support_ipv6 = true
|
112
|
+
rescue
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
return @@support_ipv6
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Determine whether this is an IPv4 address
|
121
|
+
#
|
122
|
+
def self.is_ipv4?(addr)
|
123
|
+
( addr =~ MATCH_IPV4 ) ? true : false
|
124
|
+
end
|
125
|
+
|
126
|
+
#
|
127
|
+
# Determine whether this is an IPv6 address
|
128
|
+
#
|
129
|
+
def self.is_ipv6?(addr)
|
130
|
+
( addr =~ MATCH_IPV6 ) ? true : false
|
131
|
+
end
|
132
|
+
|
133
|
+
#
|
134
|
+
# Checks to see if the supplied address is in "dotted" form
|
135
|
+
#
|
136
|
+
def self.dotted_ip?(addr)
|
137
|
+
# Match IPv6
|
138
|
+
return true if (support_ipv6? and addr =~ MATCH_IPV6)
|
139
|
+
|
140
|
+
# Match IPv4
|
141
|
+
return true if (addr =~ MATCH_IPV4)
|
142
|
+
|
143
|
+
false
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
# Return true if +addr+ is within the ranges specified in RFC1918, or
|
148
|
+
# RFC5735/RFC3927
|
149
|
+
#
|
150
|
+
def self.is_internal?(addr)
|
151
|
+
if self.dotted_ip?(addr)
|
152
|
+
addr =~ MATCH_IPV4_PRIVATE
|
153
|
+
else
|
154
|
+
false
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Get the first address returned by a DNS lookup for +hostname+.
|
159
|
+
#
|
160
|
+
# @see .getaddresses
|
161
|
+
#
|
162
|
+
# @param (see .getaddresses)
|
163
|
+
# @return [String] ASCII IP address
|
164
|
+
def self.getaddress(hostname, accept_ipv6 = true)
|
165
|
+
getaddresses(hostname, accept_ipv6).first
|
166
|
+
end
|
167
|
+
|
168
|
+
#
|
169
|
+
# Wrapper for +::Socket.gethostbyname+ that takes special care to see if the
|
170
|
+
# supplied address is already an ASCII IP address. This is necessary to
|
171
|
+
# prevent blocking while waiting on a DNS reverse lookup when we already
|
172
|
+
# have what we need.
|
173
|
+
#
|
174
|
+
# @param hostname [String] A hostname or ASCII IP address
|
175
|
+
# @return [Array<String>]
|
176
|
+
def self.getaddresses(hostname, accept_ipv6 = true)
|
177
|
+
if hostname =~ MATCH_IPV4 or (accept_ipv6 and hostname =~ MATCH_IPV6)
|
178
|
+
return [hostname]
|
179
|
+
end
|
180
|
+
|
181
|
+
res = ::Socket.gethostbyname(hostname)
|
182
|
+
return [] if not res
|
183
|
+
|
184
|
+
# Shift the first three elements out, leaving just the list of
|
185
|
+
# addresses
|
186
|
+
res.shift # name
|
187
|
+
res.shift # alias hostnames
|
188
|
+
res.shift # address_family
|
189
|
+
|
190
|
+
# Rubinius has a bug where gethostbyname returns dotted quads instead of
|
191
|
+
# NBO, but that's what we want anyway, so just short-circuit here.
|
192
|
+
if res[0] =~ MATCH_IPV4 || res[0] =~ MATCH_IPV6
|
193
|
+
unless accept_ipv6
|
194
|
+
res.reject!{ |ascii| ascii =~ MATCH_IPV6 }
|
195
|
+
end
|
196
|
+
else
|
197
|
+
unless accept_ipv6
|
198
|
+
res.reject!{ |nbo| nbo.length != 4 }
|
199
|
+
end
|
200
|
+
res.map!{ |nbo| self.addr_ntoa(nbo) }
|
201
|
+
end
|
202
|
+
|
203
|
+
res
|
204
|
+
end
|
205
|
+
|
206
|
+
#
|
207
|
+
# Wrapper for Socket.gethostbyname which takes into account whether or not
|
208
|
+
# an IP address is supplied. If it is, then reverse DNS resolution does
|
209
|
+
# not occur. This is done in order to prevent delays, such as would occur
|
210
|
+
# on Windows.
|
211
|
+
#
|
212
|
+
def self.gethostbyname(host)
|
213
|
+
if (is_ipv4?(host))
|
214
|
+
return [ host, [], 2, host.split('.').map{ |c| c.to_i }.pack("C4") ]
|
215
|
+
end
|
216
|
+
|
217
|
+
if is_ipv6?(host)
|
218
|
+
# pop off the scopeid since gethostbyname isn't smart enough to
|
219
|
+
# deal with it.
|
220
|
+
host, _ = host.split('%', 2)
|
221
|
+
end
|
222
|
+
|
223
|
+
::Socket.gethostbyname(host)
|
224
|
+
end
|
225
|
+
|
226
|
+
#
|
227
|
+
# Create a sockaddr structure using the supplied IP address, port, and
|
228
|
+
# address family
|
229
|
+
#
|
230
|
+
def self.to_sockaddr(ip, port)
|
231
|
+
|
232
|
+
if (ip == '::ffff:0.0.0.0')
|
233
|
+
ip = support_ipv6?() ? '::' : '0.0.0.0'
|
234
|
+
end
|
235
|
+
|
236
|
+
return ::Socket.pack_sockaddr_in(port, ip)
|
237
|
+
end
|
238
|
+
|
239
|
+
#
|
240
|
+
# Returns the address family, host, and port of the supplied sockaddr as
|
241
|
+
# [ af, host, port ]
|
242
|
+
#
|
243
|
+
def self.from_sockaddr(saddr)
|
244
|
+
port, host = ::Socket::unpack_sockaddr_in(saddr)
|
245
|
+
af = ::Socket::AF_INET
|
246
|
+
if (support_ipv6?() and is_ipv6?(host))
|
247
|
+
af = ::Socket::AF_INET6
|
248
|
+
end
|
249
|
+
return [ af, host, port ]
|
250
|
+
end
|
251
|
+
|
252
|
+
#
|
253
|
+
# Resolves a host to raw network-byte order.
|
254
|
+
#
|
255
|
+
def self.resolv_nbo(host)
|
256
|
+
self.gethostbyname( SSLScan::Socket.getaddress(host, true) )[3]
|
257
|
+
end
|
258
|
+
|
259
|
+
#
|
260
|
+
# Resolves a host to raw network-byte order.
|
261
|
+
#
|
262
|
+
def self.resolv_nbo_list(host)
|
263
|
+
SSLScan::Socket.getaddresses(host).map{|addr| self.gethostbyname(addr)[3] }
|
264
|
+
end
|
265
|
+
|
266
|
+
#
|
267
|
+
# Resolves a host to a network-byte order ruby integer.
|
268
|
+
#
|
269
|
+
def self.resolv_nbo_i(host)
|
270
|
+
addr_ntoi(resolv_nbo(host))
|
271
|
+
end
|
272
|
+
|
273
|
+
#
|
274
|
+
# Resolves a host to a list of network-byte order ruby integers.
|
275
|
+
#
|
276
|
+
def self.resolv_nbo_i_list(host)
|
277
|
+
resolv_nbo_list(host).map{|addr| addr_ntoi(addr) }
|
278
|
+
end
|
279
|
+
|
280
|
+
#
|
281
|
+
# Converts an ASCII IP address to a CIDR mask. Returns
|
282
|
+
# nil if it's not convertable.
|
283
|
+
#
|
284
|
+
def self.addr_atoc(mask)
|
285
|
+
mask_i = resolv_nbo_i(mask)
|
286
|
+
cidr = nil
|
287
|
+
0.upto(32) do |i|
|
288
|
+
if ((1 << i)-1) << (32-i) == mask_i
|
289
|
+
cidr = i
|
290
|
+
break
|
291
|
+
end
|
292
|
+
end
|
293
|
+
return cidr
|
294
|
+
end
|
295
|
+
|
296
|
+
#
|
297
|
+
# Resolves a CIDR bitmask into a dotted-quad. Returns
|
298
|
+
# nil if it's not convertable.
|
299
|
+
#
|
300
|
+
def self.addr_ctoa(cidr)
|
301
|
+
return nil unless (0..32) === cidr.to_i
|
302
|
+
addr_itoa(((1 << cidr)-1) << 32-cidr)
|
303
|
+
end
|
304
|
+
|
305
|
+
#
|
306
|
+
# Resolves a host to a dotted address.
|
307
|
+
#
|
308
|
+
def self.resolv_to_dotted(host)
|
309
|
+
addr_ntoa(addr_aton(host))
|
310
|
+
end
|
311
|
+
|
312
|
+
#
|
313
|
+
# Converts a ascii address into an integer
|
314
|
+
#
|
315
|
+
def self.addr_atoi(addr)
|
316
|
+
resolv_nbo_i(addr)
|
317
|
+
end
|
318
|
+
|
319
|
+
#
|
320
|
+
# Converts a ascii address into a list of addresses
|
321
|
+
#
|
322
|
+
def self.addr_atoi_list(addr)
|
323
|
+
resolv_nbo_i_list(addr)
|
324
|
+
end
|
325
|
+
|
326
|
+
#
|
327
|
+
# Converts an integer address into ascii
|
328
|
+
#
|
329
|
+
# @param (see #addr_iton)
|
330
|
+
# @return (see #addr_ntoa)
|
331
|
+
def self.addr_itoa(addr, v6=false)
|
332
|
+
nboa = addr_iton(addr, v6)
|
333
|
+
|
334
|
+
addr_ntoa(nboa)
|
335
|
+
end
|
336
|
+
|
337
|
+
#
|
338
|
+
# Converts a ascii address to network byte order
|
339
|
+
#
|
340
|
+
def self.addr_aton(addr)
|
341
|
+
resolv_nbo(addr)
|
342
|
+
end
|
343
|
+
|
344
|
+
#
|
345
|
+
# Converts a network byte order address to ascii
|
346
|
+
#
|
347
|
+
# @param addr [String] Packed network-byte-order address
|
348
|
+
# @return [String] Human readable IP address.
|
349
|
+
def self.addr_ntoa(addr)
|
350
|
+
# IPv4
|
351
|
+
if (addr.length == 4)
|
352
|
+
return addr.unpack('C4').join('.')
|
353
|
+
end
|
354
|
+
|
355
|
+
# IPv6
|
356
|
+
if (addr.length == 16)
|
357
|
+
return compress_address(addr.unpack('n8').map{ |c| "%x" % c }.join(":"))
|
358
|
+
end
|
359
|
+
|
360
|
+
raise RuntimeError, "Invalid address format"
|
361
|
+
end
|
362
|
+
|
363
|
+
#
|
364
|
+
# Implement zero compression for IPv6 addresses.
|
365
|
+
# Uses the compression method from Marco Ceresa's IPAddress GEM
|
366
|
+
#
|
367
|
+
# @see https://github.com/bluemonk/ipaddress/blob/master/lib/ipaddress/ipv6.rb
|
368
|
+
#
|
369
|
+
# @param addr [String] Human readable IPv6 address
|
370
|
+
# @return [String] Human readable IPv6 address with runs of 0s removed
|
371
|
+
def self.compress_address(addr)
|
372
|
+
return addr unless is_ipv6?(addr)
|
373
|
+
addr = addr.dup
|
374
|
+
while true
|
375
|
+
break if addr.sub!(/\A0:0:0:0:0:0:0:0\Z/, '::')
|
376
|
+
break if addr.sub!(/\b0:0:0:0:0:0:0\b/, ':')
|
377
|
+
break if addr.sub!(/\b0:0:0:0:0:0\b/, ':')
|
378
|
+
break if addr.sub!(/\b0:0:0:0:0\b/, ':')
|
379
|
+
break if addr.sub!(/\b0:0:0:0\b/, ':')
|
380
|
+
break if addr.sub!(/\b0:0:0\b/, ':')
|
381
|
+
break if addr.sub!(/\b0:0\b/, ':')
|
382
|
+
break
|
383
|
+
end
|
384
|
+
addr.sub(/:{3,}/, '::')
|
385
|
+
end
|
386
|
+
|
387
|
+
#
|
388
|
+
# Converts a network byte order address to an integer
|
389
|
+
#
|
390
|
+
def self.addr_ntoi(addr)
|
391
|
+
|
392
|
+
bits = addr.unpack("N*")
|
393
|
+
|
394
|
+
if (bits.length == 1)
|
395
|
+
return bits[0]
|
396
|
+
end
|
397
|
+
|
398
|
+
if (bits.length == 4)
|
399
|
+
val = 0
|
400
|
+
bits.each_index { |i| val += ( bits[i] << (96 - (i * 32)) ) }
|
401
|
+
return val
|
402
|
+
end
|
403
|
+
|
404
|
+
raise RuntimeError, "Invalid address format"
|
405
|
+
end
|
406
|
+
|
407
|
+
#
|
408
|
+
# Converts an integer into a network byte order address
|
409
|
+
#
|
410
|
+
# @param addr [Numeric] The address as a number
|
411
|
+
# @param v6 [Boolean] Whether +addr+ is IPv6
|
412
|
+
def self.addr_iton(addr, v6=false)
|
413
|
+
if(addr < 0x100000000 && !v6)
|
414
|
+
return [addr].pack('N')
|
415
|
+
else
|
416
|
+
w = []
|
417
|
+
w[0] = (addr >> 96) & 0xffffffff
|
418
|
+
w[1] = (addr >> 64) & 0xffffffff
|
419
|
+
w[2] = (addr >> 32) & 0xffffffff
|
420
|
+
w[3] = addr & 0xffffffff
|
421
|
+
return w.pack('N4')
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
#
|
426
|
+
# Converts a colon-delimited MAC address into a 6-byte binary string
|
427
|
+
#
|
428
|
+
def self.eth_aton(mac)
|
429
|
+
mac.split(":").map{|c| c.to_i(16) }.pack("C*")
|
430
|
+
end
|
431
|
+
|
432
|
+
#
|
433
|
+
# Converts a 6-byte binary string into a colon-delimited MAC address
|
434
|
+
#
|
435
|
+
def self.eth_ntoa(bin)
|
436
|
+
bin.unpack("C6").map{|x| "%.2x" % x }.join(":").upcase
|
437
|
+
end
|
438
|
+
|
439
|
+
#
|
440
|
+
# Converts a CIDR subnet into an array (base, bcast)
|
441
|
+
#
|
442
|
+
def self.cidr_crack(cidr, v6=false)
|
443
|
+
tmp = cidr.split('/')
|
444
|
+
|
445
|
+
tst,scope = tmp[0].split("%",2)
|
446
|
+
scope = "%" + scope if scope
|
447
|
+
scope ||= ""
|
448
|
+
|
449
|
+
addr = addr_atoi(tst)
|
450
|
+
|
451
|
+
bits = 32
|
452
|
+
mask = 0
|
453
|
+
use6 = false
|
454
|
+
|
455
|
+
if (addr > 0xffffffff or v6 or cidr =~ /:/)
|
456
|
+
use6 = true
|
457
|
+
bits = 128
|
458
|
+
end
|
459
|
+
|
460
|
+
mask = (2 ** bits) - (2 ** (bits - tmp[1].to_i))
|
461
|
+
base = addr & mask
|
462
|
+
|
463
|
+
stop = base + (2 ** (bits - tmp[1].to_i)) - 1
|
464
|
+
return [self.addr_itoa(base, use6) + scope, self.addr_itoa(stop, use6) + scope]
|
465
|
+
end
|
466
|
+
|
467
|
+
#
|
468
|
+
# Converts a netmask (255.255.255.240) into a bitmask (28). This is the
|
469
|
+
# lame kid way of doing it.
|
470
|
+
#
|
471
|
+
def self.net2bitmask(netmask)
|
472
|
+
|
473
|
+
nmask = resolv_nbo(netmask)
|
474
|
+
imask = addr_ntoi(nmask)
|
475
|
+
bits = 32
|
476
|
+
|
477
|
+
if (imask > 0xffffffff)
|
478
|
+
bits = 128
|
479
|
+
end
|
480
|
+
|
481
|
+
0.upto(bits-1) do |bit|
|
482
|
+
p = 2 ** bit
|
483
|
+
return (bits - bit) if ((imask & p) == p)
|
484
|
+
end
|
485
|
+
|
486
|
+
0
|
487
|
+
end
|
488
|
+
|
489
|
+
#
|
490
|
+
# Converts a bitmask (28) into a netmask (255.255.255.240)
|
491
|
+
#
|
492
|
+
def self.bit2netmask(bitmask, ipv6=false)
|
493
|
+
if bitmask > 32 or ipv6
|
494
|
+
i = ((~((2 ** (128 - bitmask)) - 1)) & (2**128-1))
|
495
|
+
n = SSLScan::Socket.addr_iton(i, true)
|
496
|
+
return SSLScan::Socket.addr_ntoa(n)
|
497
|
+
else
|
498
|
+
[ (~((2 ** (32 - bitmask)) - 1)) & 0xffffffff ].pack('N').unpack('CCCC').join('.')
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
|
503
|
+
def self.portspec_crack(pspec)
|
504
|
+
portspec_to_portlist(pspec)
|
505
|
+
end
|
506
|
+
|
507
|
+
#
|
508
|
+
# Converts a port specification like "80,21-23,443" into a sorted,
|
509
|
+
# unique array of valid port numbers like [21,22,23,80,443]
|
510
|
+
#
|
511
|
+
def self.portspec_to_portlist(pspec)
|
512
|
+
ports = []
|
513
|
+
|
514
|
+
# Build ports array from port specification
|
515
|
+
pspec.split(/,/).each do |item|
|
516
|
+
start, stop = item.split(/-/).map { |p| p.to_i }
|
517
|
+
|
518
|
+
start ||= 0
|
519
|
+
stop ||= item.match(/-/) ? 65535 : start
|
520
|
+
|
521
|
+
start, stop = stop, start if stop < start
|
522
|
+
|
523
|
+
start.upto(stop) { |p| ports << p }
|
524
|
+
end
|
525
|
+
|
526
|
+
# Sort, and remove dups and invalid ports
|
527
|
+
ports.sort.uniq.delete_if { |p| p < 1 or p > 65535 }
|
528
|
+
end
|
529
|
+
|
530
|
+
#
|
531
|
+
# Converts a port list like [1,2,3,4,5,100] into a
|
532
|
+
# range specification like "1-5,100"
|
533
|
+
#
|
534
|
+
def self.portlist_to_portspec(parr)
|
535
|
+
ranges = []
|
536
|
+
range = []
|
537
|
+
lastp = nil
|
538
|
+
|
539
|
+
parr.uniq.sort{|a,b| a<=>b}.map{|a| a.to_i}.each do |n|
|
540
|
+
next if (n < 1 or n > 65535)
|
541
|
+
if not lastp
|
542
|
+
range = [n]
|
543
|
+
lastp = n
|
544
|
+
next
|
545
|
+
end
|
546
|
+
|
547
|
+
if lastp == n - 1
|
548
|
+
range << n
|
549
|
+
else
|
550
|
+
ranges << range
|
551
|
+
range = [n]
|
552
|
+
end
|
553
|
+
lastp = n
|
554
|
+
end
|
555
|
+
|
556
|
+
ranges << range
|
557
|
+
ranges.delete(nil)
|
558
|
+
ranges.uniq.map{|x| x.length == 1 ? "#{x[0]}" : "#{x[0]}-#{x[-1]}"}.join(",")
|
559
|
+
end
|
560
|
+
|
561
|
+
##
|
562
|
+
#
|
563
|
+
# Utility class methods
|
564
|
+
#
|
565
|
+
##
|
566
|
+
|
567
|
+
#
|
568
|
+
# This method does NOT send any traffic to the destination, instead, it uses a
|
569
|
+
# "bound" UDP socket to determine what source address we would use to
|
570
|
+
# communicate with the specified destination. The destination defaults to
|
571
|
+
# Google's DNS server to make the standard behavior determine which IP
|
572
|
+
# we would use to communicate with the internet.
|
573
|
+
#
|
574
|
+
def self.source_address(dest='8.8.8.8', comm = ::SSLScan::Socket::Comm::Local)
|
575
|
+
begin
|
576
|
+
s = self.create_udp(
|
577
|
+
'PeerHost' => dest,
|
578
|
+
'PeerPort' => 31337,
|
579
|
+
'Comm' => comm
|
580
|
+
)
|
581
|
+
r = s.getsockname[1]
|
582
|
+
s.close
|
583
|
+
|
584
|
+
# Trim off the trailing interface ID for link-local IPv6
|
585
|
+
return r.split('%').first
|
586
|
+
rescue ::Exception
|
587
|
+
return '127.0.0.1'
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
#
|
592
|
+
# Identifies the link-local address of a given interface (if IPv6 is enabled)
|
593
|
+
#
|
594
|
+
def self.ipv6_link_address(intf)
|
595
|
+
r = source_address("FF02::1%#{intf}")
|
596
|
+
return nil if r.nil? || r !~ /^fe80/i
|
597
|
+
r
|
598
|
+
end
|
599
|
+
|
600
|
+
#
|
601
|
+
# Identifies the mac address of a given interface (if IPv6 is enabled)
|
602
|
+
#
|
603
|
+
def self.ipv6_mac(intf)
|
604
|
+
r = ipv6_link_address(intf)
|
605
|
+
return if not r
|
606
|
+
raw = addr_aton(r)[-8, 8]
|
607
|
+
(raw[0,3] + raw[5,3]).unpack("C*").map{|c| "%.2x" % c}.join(":")
|
608
|
+
end
|
609
|
+
|
610
|
+
#
|
611
|
+
# Create a TCP socket pair.
|
612
|
+
#
|
613
|
+
# sf: This create a socket pair using native ruby sockets and will work
|
614
|
+
# on Windows where ::Socket.pair is not implemented.
|
615
|
+
# Note: OpenSSL requires native ruby sockets for its io.
|
616
|
+
#
|
617
|
+
# Note: Even though sub-threads are smashing the parent threads local, there
|
618
|
+
# is no concurrent use of the same locals and this is safe.
|
619
|
+
def self.tcp_socket_pair
|
620
|
+
lsock = nil
|
621
|
+
rsock = nil
|
622
|
+
laddr = '127.0.0.1'
|
623
|
+
lport = 0
|
624
|
+
threads = []
|
625
|
+
mutex = ::Mutex.new
|
626
|
+
|
627
|
+
threads << SSLScan::ThreadFactory.spawn('TcpSocketPair', false) {
|
628
|
+
server = nil
|
629
|
+
mutex.synchronize {
|
630
|
+
threads << SSLScan::ThreadFactory.spawn('TcpSocketPairClient', false) {
|
631
|
+
mutex.synchronize {
|
632
|
+
rsock = ::TCPSocket.new( laddr, lport )
|
633
|
+
}
|
634
|
+
}
|
635
|
+
server = ::TCPServer.new(laddr, 0)
|
636
|
+
if (server.getsockname =~ /127\.0\.0\.1:/)
|
637
|
+
# JRuby ridiculousness
|
638
|
+
caddr, lport = server.getsockname.split(":")
|
639
|
+
caddr = caddr[1,caddr.length]
|
640
|
+
lport = lport.to_i
|
641
|
+
else
|
642
|
+
# Sane implementations where Socket#getsockname returns a
|
643
|
+
# sockaddr
|
644
|
+
lport, caddr = ::Socket.unpack_sockaddr_in( server.getsockname )
|
645
|
+
end
|
646
|
+
}
|
647
|
+
lsock, _ = server.accept
|
648
|
+
server.close
|
649
|
+
}
|
650
|
+
|
651
|
+
threads.each { |t| t.join }
|
652
|
+
|
653
|
+
return [lsock, rsock]
|
654
|
+
end
|
655
|
+
|
656
|
+
#
|
657
|
+
# Create a UDP socket pair using native ruby UDP sockets.
|
658
|
+
#
|
659
|
+
def self.udp_socket_pair
|
660
|
+
laddr = '127.0.0.1'
|
661
|
+
|
662
|
+
lsock = ::UDPSocket.new
|
663
|
+
lsock.bind( laddr, 0 )
|
664
|
+
|
665
|
+
rsock = ::UDPSocket.new
|
666
|
+
rsock.bind( laddr, 0 )
|
667
|
+
|
668
|
+
rsock.connect( *lsock.addr.values_at(3,1) )
|
669
|
+
|
670
|
+
lsock.connect( *rsock.addr.values_at(3,1) )
|
671
|
+
|
672
|
+
return [lsock, rsock]
|
673
|
+
end
|
674
|
+
|
675
|
+
|
676
|
+
##
|
677
|
+
#
|
678
|
+
# Class initialization
|
679
|
+
#
|
680
|
+
##
|
681
|
+
|
682
|
+
#
|
683
|
+
# Initialize general socket parameters.
|
684
|
+
#
|
685
|
+
def initsock(params = nil)
|
686
|
+
if (params)
|
687
|
+
self.peerhost = params.peerhost
|
688
|
+
self.peerport = params.peerport
|
689
|
+
self.localhost = params.localhost
|
690
|
+
self.localport = params.localport
|
691
|
+
self.context = params.context || {}
|
692
|
+
self.ipv = params.v6 ? 6 : 4
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
#
|
697
|
+
# By default, all sockets are themselves selectable file descriptors.
|
698
|
+
#
|
699
|
+
def fd
|
700
|
+
self
|
701
|
+
end
|
702
|
+
|
703
|
+
#
|
704
|
+
# Returns local connection information.
|
705
|
+
#
|
706
|
+
def getsockname
|
707
|
+
Socket.from_sockaddr(super)
|
708
|
+
end
|
709
|
+
|
710
|
+
#
|
711
|
+
# Wrapper around getsockname
|
712
|
+
#
|
713
|
+
def getlocalname
|
714
|
+
getsockname
|
715
|
+
end
|
716
|
+
|
717
|
+
#
|
718
|
+
# Return peer connection information.
|
719
|
+
#
|
720
|
+
def getpeername
|
721
|
+
return Socket.from_sockaddr(super)
|
722
|
+
end
|
723
|
+
|
724
|
+
#
|
725
|
+
# Returns a string that indicates the type of the socket, such as 'tcp'.
|
726
|
+
#
|
727
|
+
def type?
|
728
|
+
raise NotImplementedError, "Socket type is not supported."
|
729
|
+
end
|
730
|
+
|
731
|
+
#
|
732
|
+
# The peer host of the connected socket.
|
733
|
+
#
|
734
|
+
attr_reader :peerhost
|
735
|
+
#
|
736
|
+
# The peer port of the connected socket.
|
737
|
+
#
|
738
|
+
attr_reader :peerport
|
739
|
+
#
|
740
|
+
# The local host of the connected socket.
|
741
|
+
#
|
742
|
+
attr_reader :localhost
|
743
|
+
#
|
744
|
+
# The local port of the connected socket.
|
745
|
+
#
|
746
|
+
attr_reader :localport
|
747
|
+
#
|
748
|
+
# The IP version of the socket
|
749
|
+
#
|
750
|
+
attr_reader :ipv
|
751
|
+
#
|
752
|
+
# Contextual information that describes the source and other
|
753
|
+
# instance-specific attributes. This comes from the param.context
|
754
|
+
# attribute.
|
755
|
+
#
|
756
|
+
attr_reader :context
|
757
|
+
|
758
|
+
protected
|
759
|
+
|
760
|
+
attr_writer :peerhost, :peerport, :localhost, :localport # :nodoc:
|
761
|
+
attr_writer :context # :nodoc:
|
762
|
+
attr_writer :ipv # :nodoc:
|
763
|
+
|
764
|
+
end
|
765
|
+
|
766
|
+
end
|
767
|
+
|
768
|
+
#
|
769
|
+
# Globalized socket constants
|
770
|
+
#
|
771
|
+
SHUT_RDWR = ::Socket::SHUT_RDWR
|
772
|
+
SHUT_RD = ::Socket::SHUT_RD
|
773
|
+
SHUT_WR = ::Socket::SHUT_WR
|