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,526 @@
1
+ # -*- coding: binary -*-
2
+ require 'singleton'
3
+ require 'ssl_scan/socket'
4
+ require 'ssl_scan/socket/tcp'
5
+ require 'ssl_scan/socket/ssl_tcp'
6
+ require 'ssl_scan/socket/ssl_tcp_server'
7
+ require 'ssl_scan/socket/udp'
8
+ require 'ssl_scan/socket/ip'
9
+ require 'timeout'
10
+
11
+ ###
12
+ #
13
+ # Local communication class factory.
14
+ #
15
+ ###
16
+ class SSLScan::Socket::Comm::Local
17
+
18
+ include Singleton
19
+ include SSLScan::Socket::Comm
20
+
21
+ #
22
+ # Creates an instance of a socket using the supplied parameters.
23
+ #
24
+ def self.create(param)
25
+
26
+ # Work around jRuby socket implementation issues
27
+ if(RUBY_PLATFORM == 'java')
28
+ return self.create_jruby(param)
29
+ end
30
+
31
+ case param.proto
32
+ when 'tcp'
33
+ return create_by_type(param, ::Socket::SOCK_STREAM, ::Socket::IPPROTO_TCP)
34
+ when 'udp'
35
+ return create_by_type(param, ::Socket::SOCK_DGRAM, ::Socket::IPPROTO_UDP)
36
+ when 'ip'
37
+ return create_ip(param)
38
+ else
39
+ raise SSLScan::UnsupportedProtocol.new(param.proto), caller
40
+ end
41
+ end
42
+
43
+ #
44
+ # Creates an instance of a socket using the supplied parameters.
45
+ # Use various hacks to make this work with jRuby
46
+ #
47
+ def self.create_jruby(param)
48
+ sock = nil
49
+
50
+ # Notify handlers of the before socket create event.
51
+ self.instance.notify_before_socket_create(self, param)
52
+
53
+ case param.proto
54
+ when 'tcp'
55
+ if (param.server?)
56
+ sock = TCPServer.new(param.localport, param.localhost)
57
+ klass = SSLScan::Socket::TcpServer
58
+ if (param.ssl)
59
+ klass = SSLScan::Socket::SslTcpServer
60
+ end
61
+ sock.extend(klass)
62
+
63
+ else
64
+ sock = TCPSocket.new(param.peerhost, param.peerport)
65
+ klass = SSLScan::Socket::Tcp
66
+ if (param.ssl)
67
+ klass = SSLScan::Socket::SslTcp
68
+ end
69
+ sock.extend(klass)
70
+ end
71
+ when 'udp'
72
+ if (param.server?)
73
+ sock = UDPServer.new(param.localport, param.localhost)
74
+ klass = SSLScan::Socket::UdpServer
75
+ sock.extend(klass)
76
+ else
77
+ sock = UDPSocket.new(param.peerhost, param.peerport)
78
+ klass = SSLScan::Socket::Udp
79
+ sock.extend(klass)
80
+ end
81
+ else
82
+ raise SSLScan::UnsupportedProtocol.new(param.proto), caller
83
+ end
84
+
85
+ sock.initsock(param)
86
+ self.instance.notify_socket_created(self, sock, param)
87
+ return sock
88
+ end
89
+
90
+
91
+ #
92
+ # Creates a raw IP socket using the supplied Parameter instance.
93
+ # Special-cased because of how different it is from UDP/TCP
94
+ #
95
+ def self.create_ip(param)
96
+ self.instance.notify_before_socket_create(self, param)
97
+
98
+ sock = ::Socket.open(::Socket::PF_INET, ::Socket::SOCK_RAW, ::Socket::IPPROTO_RAW)
99
+ sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_HDRINCL, 1)
100
+
101
+ # Configure broadcast support
102
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_BROADCAST, true)
103
+
104
+ if (param.bare? == false)
105
+ sock.extend(::SSLScan::Socket::Ip)
106
+ sock.initsock(param)
107
+ end
108
+
109
+ self.instance.notify_socket_created(self, sock, param)
110
+
111
+ sock
112
+ end
113
+
114
+
115
+ #
116
+ # Creates a socket using the supplied Parameter instance.
117
+ #
118
+ def self.create_by_type(param, type, proto = 0)
119
+
120
+ # Whether to use IPv6 addressing
121
+ usev6 = false
122
+
123
+ # Detect IPv6 addresses and enable IPv6 accordingly
124
+ if ( SSLScan::Socket.support_ipv6?())
125
+
126
+ # Allow the caller to force IPv6
127
+ if (param.v6)
128
+ usev6 = true
129
+ end
130
+
131
+ # Force IPv6 mode for non-connected UDP sockets
132
+ if (type == ::Socket::SOCK_DGRAM and not param.peerhost)
133
+ # FreeBSD allows IPv6 socket creation, but throws an error on sendto()
134
+ # Windows 7 SP1 and newer also fail to sendto with IPv6 udp sockets
135
+ unless SSLScan::Compat.is_freebsd or SSLScan::Compat.is_windows
136
+ usev6 = true
137
+ end
138
+ end
139
+
140
+ local = SSLScan::Socket.resolv_nbo(param.localhost) if param.localhost
141
+ peer = SSLScan::Socket.resolv_nbo(param.peerhost) if param.peerhost
142
+
143
+ if (local and local.length == 16)
144
+ usev6 = true
145
+ end
146
+
147
+ if (peer and peer.length == 16)
148
+ usev6 = true
149
+ end
150
+
151
+ if (usev6)
152
+ if (local and local.length == 4)
153
+ if (local == "\x00\x00\x00\x00")
154
+ param.localhost = '::'
155
+ elsif (local == "\x7f\x00\x00\x01")
156
+ param.localhost = '::1'
157
+ else
158
+ param.localhost = '::ffff:' + SSLScan::Socket.getaddress(param.localhost, true)
159
+ end
160
+ end
161
+
162
+ if (peer and peer.length == 4)
163
+ if (peer == "\x00\x00\x00\x00")
164
+ param.peerhost = '::'
165
+ elsif (peer == "\x7f\x00\x00\x01")
166
+ param.peerhost = '::1'
167
+ else
168
+ param.peerhost = '::ffff:' + SSLScan::Socket.getaddress(param.peerhost, true)
169
+ end
170
+ end
171
+
172
+ param.v6 = true
173
+ end
174
+ else
175
+ # No IPv6 support
176
+ param.v6 = false
177
+ end
178
+
179
+ # Notify handlers of the before socket create event.
180
+ self.instance.notify_before_socket_create(self, param)
181
+
182
+ # Create the socket
183
+ sock = nil
184
+ if (param.v6)
185
+ sock = ::Socket.new(::Socket::AF_INET6, type, proto)
186
+ else
187
+ sock = ::Socket.new(::Socket::AF_INET, type, proto)
188
+ end
189
+
190
+ # Bind to a given local address and/or port if they are supplied
191
+ if param.localport or param.localhost
192
+ begin
193
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true)
194
+ sock.bind(SSLScan::Socket.to_sockaddr(param.localhost, param.localport))
195
+
196
+ rescue ::Errno::EADDRNOTAVAIL,::Errno::EADDRINUSE
197
+ sock.close
198
+ raise SSLScan::AddressInUse.new(param.localhost, param.localport), caller
199
+ end
200
+ end
201
+
202
+ # Configure broadcast support for all datagram sockets
203
+ if (type == ::Socket::SOCK_DGRAM)
204
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_BROADCAST, true)
205
+ end
206
+
207
+ # If a server TCP instance is being created...
208
+ if (param.server?)
209
+ sock.listen(256)
210
+
211
+ if (param.bare? == false)
212
+ klass = SSLScan::Socket::TcpServer
213
+ if (param.ssl)
214
+ klass = SSLScan::Socket::SslTcpServer
215
+ end
216
+ sock.extend(klass)
217
+
218
+ sock.initsock(param)
219
+ end
220
+ # Otherwise, if we're creating a client...
221
+ else
222
+ chain = []
223
+
224
+ # If we were supplied with host information
225
+ if (param.peerhost)
226
+
227
+ # A flag that indicates whether we need to try multiple scopes
228
+ retry_scopes = false
229
+
230
+ # Always retry with link-local IPv6 addresses
231
+ if SSLScan::Socket.is_ipv6?( param.peerhost ) and param.peerhost =~ /^fe80::/
232
+ retry_scopes = true
233
+ end
234
+
235
+ # Prepare a list of scope IDs to try when connecting to
236
+ # link-level addresses. Read from /proc if it is available,
237
+ # otherwise increment through the first 255 IDs.
238
+ @@ip6_lla_scopes ||= []
239
+
240
+ if @@ip6_lla_scopes.length == 0 and retry_scopes
241
+
242
+ # Linux specific interface lookup code
243
+ if ::File.exists?( "/proc/self/net/igmp6" )
244
+ ::File.open("/proc/self/net/igmp6") do |fd|
245
+ fd.each_line do |line|
246
+ line = line.strip
247
+ tscope, tint, junk = line.split(/\s+/, 3)
248
+ next if not tint
249
+
250
+ # Specifying lo in any connect call results in the socket
251
+ # being unusable, even if the correct interface is set.
252
+ next if tint == "lo"
253
+
254
+ @@ip6_lla_scopes << tscope
255
+ end
256
+ end
257
+ else
258
+ # Other Unix-like platforms should support a raw scope ID
259
+ [*(1 .. 255)].map{ |x| @@ip6_lla_scopes << x.to_s }
260
+ end
261
+ end
262
+
263
+ ip6_scope_idx = 0
264
+ ip = param.peerhost
265
+ port = param.peerport
266
+
267
+ if param.proxies
268
+ chain = param.proxies.dup
269
+ chain.push(['host',param.peerhost,param.peerport])
270
+ ip = chain[0][1]
271
+ port = chain[0][2].to_i
272
+ end
273
+
274
+ begin
275
+
276
+ begin
277
+ Timeout.timeout(param.timeout) do
278
+ sock.connect(SSLScan::Socket.to_sockaddr(ip, port))
279
+ end
280
+ rescue ::Timeout::Error
281
+ raise ::Errno::ETIMEDOUT
282
+ end
283
+
284
+ rescue ::Errno::EHOSTUNREACH,::Errno::ENETDOWN,::Errno::ENETUNREACH,::Errno::ENETRESET,::Errno::EHOSTDOWN,::Errno::EACCES,::Errno::EINVAL
285
+
286
+ # Rescue errors caused by a bad Scope ID for a link-local address
287
+ if retry_scopes and @@ip6_lla_scopes[ ip6_scope_idx ]
288
+ ip = param.peerhost + "%" + @@ip6_lla_scopes[ ip6_scope_idx ]
289
+ ip6_scope_idx += 1
290
+ retry
291
+ end
292
+
293
+ sock.close
294
+ raise SSLScan::HostUnreachable.new(param.peerhost, param.peerport), caller
295
+
296
+ rescue ::Errno::EADDRNOTAVAIL,::Errno::EADDRINUSE
297
+ sock.close
298
+ raise SSLScan::AddressInUse.new(param.peerhost, param.peerport), caller
299
+
300
+ rescue Errno::ETIMEDOUT
301
+ sock.close
302
+ raise SSLScan::ConnectionTimeout.new(param.peerhost, param.peerport), caller
303
+
304
+ rescue ::Errno::ECONNRESET,::Errno::ECONNREFUSED,::Errno::ENOTCONN,::Errno::ECONNABORTED
305
+ sock.close
306
+ raise SSLScan::ConnectionRefused.new(param.peerhost, param.peerport), caller
307
+ end
308
+ end
309
+
310
+ if (param.bare? == false)
311
+ case param.proto
312
+ when 'tcp'
313
+ klass = SSLScan::Socket::Tcp
314
+ sock.extend(klass)
315
+ sock.initsock(param)
316
+ when 'udp'
317
+ sock.extend(SSLScan::Socket::Udp)
318
+ sock.initsock(param)
319
+ end
320
+ end
321
+
322
+ if chain.size > 1
323
+ chain.each_with_index {
324
+ |proxy, i|
325
+ next_hop = chain[i + 1]
326
+ if next_hop
327
+ proxy(sock, proxy[0], next_hop[1], next_hop[2])
328
+ end
329
+ }
330
+ end
331
+
332
+ # Now extend the socket with SSL and perform the handshake
333
+ if(param.bare? == false and param.ssl)
334
+ klass = SSLScan::Socket::SslTcp
335
+ sock.extend(klass)
336
+ sock.initsock(param)
337
+ end
338
+
339
+
340
+ end
341
+
342
+ # Notify handlers that a socket has been created.
343
+ self.instance.notify_socket_created(self, sock, param)
344
+
345
+ sock
346
+ end
347
+
348
+ def self.proxy(sock, type, host, port)
349
+ case type.downcase
350
+ when 'sapni'
351
+ packet_type = 'NI_ROUTE'
352
+ route_info_version = 2
353
+ ni_version = 39
354
+ num_of_entries = 2
355
+ talk_mode = 1 # ref: http://help.sap.com/saphelp_dimp50/helpdata/En/f8/bb960899d743378ccb8372215bb767/content.htm
356
+ num_rest_nodes = 1
357
+
358
+ shost, sport = sock.peerinfo.split(":")
359
+ first_route_item = [shost, 0, sport, 0, 0].pack("A*CA*cc")
360
+ route_data = [first_route_item.length, first_route_item].pack("NA*")
361
+ route_data << [host, 0, port.to_s, 0, 0].pack("A*CA*cc")
362
+
363
+ ni_packet = [
364
+ packet_type,
365
+ 0,
366
+ route_info_version,
367
+ ni_version,
368
+ num_of_entries,
369
+ talk_mode,
370
+ 0,
371
+ 0,
372
+ num_rest_nodes
373
+ ].pack("A8c8")
374
+ # Add the data block, according to sap documentation:
375
+ # A 4-byte header precedes each data block. These 4 bytes give the
376
+ # length of the data block (length without leading 4 bytes)
377
+ # The data block (the route data)
378
+ ni_packet << [route_data.length - 4].pack('N') + route_data
379
+ # Now that we've built the whole packet, prepend its length before writing it to the wire
380
+ ni_packet = [ni_packet.length].pack('N') + ni_packet
381
+
382
+ size = sock.put(ni_packet)
383
+
384
+ if size != ni_packet.length
385
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller
386
+ end
387
+
388
+ begin
389
+ ret_len = sock.get_once(4, 30).unpack('N')[0]
390
+ if ret_len and ret_len != 0
391
+ ret = sock.get_once(ret_len, 30)
392
+ end
393
+ rescue IOError
394
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Failed to receive a response from the proxy"), caller
395
+ end
396
+
397
+ if ret and ret.length < 4
398
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Failed to receive a complete response from the proxy"), caller
399
+ end
400
+
401
+ if ret =~ /NI_RTERR/
402
+ case ret
403
+ when /timed out/
404
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Connection to remote host #{host} timed out")
405
+ when /refused/
406
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Connection to remote port #{port} closed")
407
+ when /denied/
408
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Connection to #{host}:#{port} blocked by ACL")
409
+ else
410
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Connection to #{host}:#{port} failed (Unknown fail)")
411
+ end
412
+ elsif ret =~ /NI_PONG/
413
+ # success case
414
+ # would like to print this "[*] remote native connection to #{host}:#{port} established\n"
415
+ else
416
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Connection to #{host}:#{port} failed (Unknown fail)")
417
+ end
418
+
419
+ when 'http'
420
+ setup = "CONNECT #{host}:#{port} HTTP/1.0\r\n\r\n"
421
+ size = sock.put(setup)
422
+ if (size != setup.length)
423
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller
424
+ end
425
+
426
+ begin
427
+ ret = sock.get_once(39,30)
428
+ rescue IOError
429
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Failed to receive a response from the proxy"), caller
430
+ end
431
+
432
+ if ret.nil?
433
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Failed to receive a response from the proxy"), caller
434
+ end
435
+
436
+ resp = SSLScan::Proto::Http::Response.new
437
+ resp.update_cmd_parts(ret.split(/\r?\n/)[0])
438
+
439
+ if resp.code != 200
440
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "The proxy returned a non-OK response"), caller
441
+ end
442
+ when 'socks4'
443
+ setup = [4,1,port.to_i].pack('CCn') + Socket.gethostbyname(host)[3] + SSLScan::Text.rand_text_alpha(rand(8)+1) + "\x00"
444
+ size = sock.put(setup)
445
+ if (size != setup.length)
446
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller
447
+ end
448
+
449
+ begin
450
+ ret = sock.get_once(8, 30)
451
+ rescue IOError
452
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Failed to receive a response from the proxy"), caller
453
+ end
454
+
455
+ if (ret.nil? or ret.length < 8)
456
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Failed to receive a complete response from the proxy"), caller
457
+ end
458
+ if ret[1,1] != "\x5a"
459
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Proxy responded with error code #{ret[0,1].unpack("C")[0]}"), caller
460
+ end
461
+ when 'socks5'
462
+ auth_methods = [5,1,0].pack('CCC')
463
+ size = sock.put(auth_methods)
464
+ if (size != auth_methods.length)
465
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller
466
+ end
467
+ ret = sock.get_once(2,30)
468
+ if (ret[1,1] == "\xff")
469
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "The proxy requires authentication"), caller
470
+ end
471
+
472
+ if (SSLScan::Socket.is_ipv4?(host))
473
+ addr = SSLScan::Socket.gethostbyname(host)[3]
474
+ setup = [5,1,0,1].pack('C4') + addr + [port.to_i].pack('n')
475
+ elsif (SSLScan::Socket.support_ipv6? and SSLScan::Socket.is_ipv6?(host))
476
+ # IPv6 stuff all untested
477
+ addr = SSLScan::Socket.gethostbyname(host)[3]
478
+ setup = [5,1,0,4].pack('C4') + addr + [port.to_i].pack('n')
479
+ else
480
+ # Then it must be a domain name.
481
+ # Unfortunately, it looks like the host has always been
482
+ # resolved by the time it gets here, so this code never runs.
483
+ setup = [5,1,0,3].pack('C4') + [host.length].pack('C') + host + [port.to_i].pack('n')
484
+ end
485
+
486
+ size = sock.put(setup)
487
+ if (size != setup.length)
488
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller
489
+ end
490
+
491
+ begin
492
+ response = sock.get_once(10, 30)
493
+ rescue IOError
494
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Failed to receive a response from the proxy"), caller
495
+ end
496
+
497
+ if (response.nil? or response.length < 10)
498
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Failed to receive a complete response from the proxy"), caller
499
+ end
500
+ if response[1,1] != "\x00"
501
+ raise SSLScan::ConnectionProxyError.new(host, port, type, "Proxy responded with error code #{response[1,1].unpack("C")[0]}"), caller
502
+ end
503
+ else
504
+ raise RuntimeError, "The proxy type specified is not valid", caller
505
+ end
506
+ end
507
+
508
+ ##
509
+ #
510
+ # Registration
511
+ #
512
+ ##
513
+
514
+ def self.register_event_handler(handler) # :nodoc:
515
+ self.instance.register_event_handler(handler)
516
+ end
517
+
518
+ def self.deregister_event_handler(handler) # :nodoc:
519
+ self.instance.deregister_event_handler(handler)
520
+ end
521
+
522
+ def self.each_event_handler(handler) # :nodoc:
523
+ self.instance.each_event_handler(handler)
524
+ end
525
+
526
+ end
@@ -0,0 +1,120 @@
1
+ # -*- coding: binary -*-
2
+ require 'ssl_scan/socket'
3
+
4
+ module SSLScan
5
+ module Socket
6
+
7
+ ###
8
+ #
9
+ # This mixin provides the basic interface that a derived class must implement
10
+ # in order to be a compatible comm class. The base comm class also supports
11
+ # registering event handlers that can be notified when sockets are being
12
+ # created and have been created. This allows code to extend sockets on
13
+ # creation from the single point that they are created.
14
+ #
15
+ ###
16
+ module Comm
17
+
18
+ ###
19
+ #
20
+ # This mixin provides stubs for event notification handlers that can be
21
+ # registered with a Comm factory to be called when various events occur,
22
+ # such as socket instantiation.
23
+ #
24
+ ###
25
+ module Events
26
+
27
+ #
28
+ # This callback is notified when a socket is being created and is passed
29
+ # the parameters that will be used to create it.
30
+ #
31
+ def on_before_socket_create(comm, param)
32
+ end
33
+
34
+ #
35
+ # This callback is notified when a new socket is created and the
36
+ # parameters that were used to create it. This provides the callback
37
+ # with a chance to extend or otherwise modify the socket before it's
38
+ # passed on to the actual requestor.
39
+ #
40
+ def on_socket_created(comm, sock, param)
41
+ end
42
+
43
+ end
44
+
45
+ #
46
+ # Creates a compatible socket based on the supplied uniform parameters.
47
+ #
48
+ def self.create(param)
49
+ raise NotImplementedError
50
+ end
51
+
52
+ #
53
+ # Indicates whether or not this comm can be chained with other chainable
54
+ # comms. This is particularly important for things like Proxy Comms that
55
+ # can be proxied through one another. The semantics of this are currently
56
+ # undefined and will probably need some more thought.
57
+ #
58
+ def chainable?
59
+ false
60
+ end
61
+
62
+ #
63
+ # Registers an event handler that implements the SSLScan::Socket::Comm::Event
64
+ # interface in at least some fashion. Event handlers are notified when
65
+ # sockets are created through the Comm instance that they register against.
66
+ #
67
+ def register_event_handler(handler)
68
+ if (handlers == nil)
69
+ self.handlers = []
70
+ end
71
+
72
+ self.handlers << handler
73
+ end
74
+
75
+ #
76
+ # Deregisters a previously registered event handler.
77
+ #
78
+ def deregister_event_handler(handler)
79
+ if (handlers)
80
+ handlers.delete(handler)
81
+ end
82
+ end
83
+
84
+ #
85
+ # Enumerates each registered event handler so that they can be notified of
86
+ # an event.
87
+ #
88
+ def each_event_handler(&block)
89
+ if (handlers)
90
+ handlers.each(&block)
91
+ end
92
+ end
93
+
94
+ #
95
+ # Notifies handlers of the before socket create event.
96
+ #
97
+ def notify_before_socket_create(comm, param)
98
+ each_event_handler() { |handler|
99
+ handler.on_before_socket_create(comm, param)
100
+ }
101
+ end
102
+
103
+ #
104
+ # Notifies handlers of the socket created event.
105
+ #
106
+ def notify_socket_created(comm, sock, param)
107
+ each_event_handler() { |handler|
108
+ handler.on_socket_created(comm, sock, param)
109
+ }
110
+ end
111
+
112
+ protected
113
+
114
+ attr_accessor :handlers # :nodoc:
115
+ attr_accessor :handlers_rwlock # :nodoc:
116
+
117
+ end
118
+
119
+ end
120
+ end