tlspretense 0.6.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.
Files changed (81) hide show
  1. data/.document +6 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +41 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.rdoc +231 -0
  8. data/Rakefile +44 -0
  9. data/bin/makeder.sh +6 -0
  10. data/bin/tlspretense +7 -0
  11. data/bin/view.sh +3 -0
  12. data/doc/general_setup.rdoc +288 -0
  13. data/doc/linux_setup.rdoc +64 -0
  14. data/lib/certmaker.rb +61 -0
  15. data/lib/certmaker/certificate_factory.rb +106 -0
  16. data/lib/certmaker/certificate_suite_generator.rb +120 -0
  17. data/lib/certmaker/ext_core/hash_indifferent_fetch.rb +12 -0
  18. data/lib/certmaker/runner.rb +27 -0
  19. data/lib/certmaker/tasks.rb +20 -0
  20. data/lib/packetthief.rb +167 -0
  21. data/lib/packetthief/handlers.rb +14 -0
  22. data/lib/packetthief/handlers/abstract_ssl_handler.rb +249 -0
  23. data/lib/packetthief/handlers/proxy_redirector.rb +26 -0
  24. data/lib/packetthief/handlers/ssl_client.rb +87 -0
  25. data/lib/packetthief/handlers/ssl_server.rb +174 -0
  26. data/lib/packetthief/handlers/ssl_smart_proxy.rb +143 -0
  27. data/lib/packetthief/handlers/ssl_transparent_proxy.rb +225 -0
  28. data/lib/packetthief/handlers/transparent_proxy.rb +183 -0
  29. data/lib/packetthief/impl.rb +11 -0
  30. data/lib/packetthief/impl/ipfw.rb +140 -0
  31. data/lib/packetthief/impl/manual.rb +54 -0
  32. data/lib/packetthief/impl/netfilter.rb +109 -0
  33. data/lib/packetthief/impl/pf_divert.rb +168 -0
  34. data/lib/packetthief/impl/pf_rdr.rb +192 -0
  35. data/lib/packetthief/logging.rb +49 -0
  36. data/lib/packetthief/redirect_rule.rb +29 -0
  37. data/lib/packetthief/util.rb +36 -0
  38. data/lib/ssl_test.rb +21 -0
  39. data/lib/ssl_test/app_context.rb +17 -0
  40. data/lib/ssl_test/certificate_manager.rb +33 -0
  41. data/lib/ssl_test/config.rb +79 -0
  42. data/lib/ssl_test/ext_core/io_raw_input.rb +31 -0
  43. data/lib/ssl_test/input_handler.rb +35 -0
  44. data/lib/ssl_test/runner.rb +110 -0
  45. data/lib/ssl_test/runner_options.rb +68 -0
  46. data/lib/ssl_test/ssl_test_case.rb +46 -0
  47. data/lib/ssl_test/ssl_test_report.rb +24 -0
  48. data/lib/ssl_test/ssl_test_result.rb +30 -0
  49. data/lib/ssl_test/test_listener.rb +140 -0
  50. data/lib/ssl_test/test_manager.rb +116 -0
  51. data/lib/tlspretense.rb +13 -0
  52. data/lib/tlspretense/app.rb +52 -0
  53. data/lib/tlspretense/init_runner.rb +115 -0
  54. data/lib/tlspretense/skel/ca/goodcacert.pem +19 -0
  55. data/lib/tlspretense/skel/ca/goodcakey.pem +27 -0
  56. data/lib/tlspretense/skel/config.yml +523 -0
  57. data/lib/tlspretense/version.rb +3 -0
  58. data/packetthief_examples/em_ssl_test.rb +73 -0
  59. data/packetthief_examples/redirector.rb +29 -0
  60. data/packetthief_examples/setup_iptables.sh +24 -0
  61. data/packetthief_examples/ssl_client_simple.rb +27 -0
  62. data/packetthief_examples/ssl_server_simple.rb +44 -0
  63. data/packetthief_examples/ssl_smart_proxy.rb +115 -0
  64. data/packetthief_examples/ssl_transparent_proxy.rb +97 -0
  65. data/packetthief_examples/transparent_proxy.rb +56 -0
  66. data/spec/packetthief/impl/ipfw_spec.rb +98 -0
  67. data/spec/packetthief/impl/manual_spec.rb +65 -0
  68. data/spec/packetthief/impl/netfilter_spec.rb +66 -0
  69. data/spec/packetthief/impl/pf_divert_spec.rb +82 -0
  70. data/spec/packetthief/impl/pf_rdr_spec.rb +133 -0
  71. data/spec/packetthief/logging_spec.rb +78 -0
  72. data/spec/packetthief_spec.rb +47 -0
  73. data/spec/spec_helper.rb +53 -0
  74. data/spec/ssl_test/certificate_manager_spec.rb +222 -0
  75. data/spec/ssl_test/config_spec.rb +76 -0
  76. data/spec/ssl_test/runner_spec.rb +360 -0
  77. data/spec/ssl_test/ssl_test_case_spec.rb +113 -0
  78. data/spec/ssl_test/test_listener_spec.rb +199 -0
  79. data/spec/ssl_test/test_manager_spec.rb +324 -0
  80. data/tlspretense.gemspec +35 -0
  81. metadata +262 -0
@@ -0,0 +1,14 @@
1
+ require 'eventmachine'
2
+
3
+ module PacketThief
4
+ module Handlers
5
+ autoload :AbstractSSLHandler, 'packetthief/handlers/abstract_ssl_handler'
6
+ autoload :SSLClient, 'packetthief/handlers/ssl_client'
7
+ autoload :SSLServer, 'packetthief/handlers/ssl_server'
8
+ autoload :SSLSmartProxy, 'packetthief/handlers/ssl_smart_proxy'
9
+ autoload :SSLTransparentProxy, 'packetthief/handlers/ssl_transparent_proxy'
10
+ autoload :TransparentProxy, 'packetthief/handlers/transparent_proxy'
11
+ autoload :ProxyRedirector, 'packetthief/handlers/proxy_redirector'
12
+ end
13
+ end
14
+
@@ -0,0 +1,249 @@
1
+ require 'openssl'
2
+
3
+ module PacketThief
4
+ module Handlers
5
+
6
+ # Parent class for both SSLServer and SSLClient.
7
+ #
8
+ # TODO: get_peer_cert, get_peername, etc.
9
+ class AbstractSSLHandler < ::EM::Connection
10
+ include Logging
11
+
12
+ # The OpenSSL::SSL::SSLContext. Modify this in post_init or in the
13
+ # initializing code block to add certificates, etc.
14
+ attr_accessor :ctx
15
+
16
+ # The TCPSocket that the SSLSocket will be created from. It is added by
17
+ # #initialize.
18
+ attr_accessor :tcpsocket
19
+
20
+ # The SSLSocket. It is not available until #tls_begin creates it, after
21
+ # post_init and the initializing code block.
22
+ attr_accessor :sslsocket
23
+
24
+ # (Used by SSLClient only) The hostname that the SNI TLS extension should
25
+ # request. Set it in post_init or in the initializing code block --- it
26
+ # is applied to the SSLSocket during #tls_begin.
27
+ attr_accessor :sni_hostname
28
+
29
+ def initialize(tcpsocket, logger=nil)
30
+ @logger = logger
31
+ logdebug "initialize"
32
+ # Set up initial values
33
+ @tcpsocket = tcpsocket
34
+ @ctx = OpenSSL::SSL::SSLContext.new
35
+
36
+ @close_after_writing = false
37
+ @state = :new
38
+ end
39
+
40
+ # Creates _sslsocket_ from _tcpsocket_ and _ctx_, and initializes the
41
+ # handler's internal state. Called from the class method that creates the
42
+ # object, after post_init and the optional code block.
43
+ #
44
+ # @note (SSLClient only) If @sni_hostname exists on the handler at this
45
+ # point, it will be added to the SSLSocket in order to enable sending a
46
+ # hostname in the SNI TLS extension.
47
+ def tls_begin
48
+ logdebug "tls begin", :sni_hostname => @sni_hostname
49
+ @sslsocket = OpenSSL::SSL::SSLSocket.new(@tcpsocket, @ctx)
50
+ if @sni_hostname
51
+ if @sslsocket.respond_to? :hostname
52
+ @sslsocket.hostname = @sni_hostname
53
+ else
54
+ logwarn "#{@sslsocket.class} does not support setting an SNI hostname! This requires Ruby 1.9.x built against OpenSSL with SNI support.",
55
+ :ruby_version => RUBY_VERSION
56
+ end
57
+ end
58
+ @state = :initialized
59
+ end
60
+
61
+ # Calls accept_nonblock/connect_nonblock, read_nonblock, or
62
+ # write_nonblock based on the current state of the connection.
63
+ def notify_readable
64
+ logdebug "notify_readable", :state => @state
65
+ case @state
66
+ when :initialized
67
+ attempt_connection
68
+ when :ready_to_read
69
+ attempt_read
70
+ when :write_needs_to_read
71
+ attempt_write
72
+ end
73
+ end
74
+
75
+ # We only care about notify_writable if we are waiting to write for some
76
+ # reason.
77
+ def notify_writable
78
+ logdebug "notify_writable", :state => @state
79
+ notify_writable = false # disable it now. if we still need it, we'll renabled it.
80
+ case @state
81
+ when :initialized
82
+ attempt_connection
83
+ when :read_needs_to_write
84
+ attempt_read
85
+ when :write_needs_to_write
86
+ attempt_write
87
+ end
88
+
89
+ # if we waiting to close and are not longer waiting to write, we can flush and close the connection.
90
+ if @close_after_writing and not notify_writable?
91
+ @sslsock.flush
92
+ close_connection
93
+ end
94
+ end
95
+
96
+ private
97
+ def attempt_connection
98
+ begin
99
+ # Client usess connect_nonblock, while server uses accept_nonblock.
100
+ connection_action
101
+ @state = :ready_to_read
102
+ tls_successful_handshake
103
+ attempt_write if write_buffer.length > 0
104
+ rescue IO::WaitReadable
105
+ # accept_nonblock needs to wait until it can read again.
106
+ notify_readable = true
107
+ rescue IO::WaitWritable
108
+ # accept_nonblock needs to wait until it can write again.
109
+ notify_writable = true
110
+ rescue OpenSSL::SSL::SSLError, Errno::ECONNREFUSED => e
111
+ # ssl handshake failed. Likely due to client rejecting our certificate!
112
+ tls_failed_handshake(e)
113
+ close_connection
114
+ end
115
+ end
116
+
117
+ private
118
+ def attempt_read
119
+ begin
120
+ data = @sslsocket.read_nonblock 4096 # much more than a network packet...
121
+ receive_data(data)
122
+ notify_writable = false
123
+ rescue EOFError, Errno::ECONNRESET
124
+ # remote closed. time to wrap up
125
+ close_connection
126
+ rescue IO::WaitReadable
127
+ # we had no data to read.
128
+ notify_readable = true
129
+ rescue IO::WaitWritable
130
+ # we ran out of buffer to send (yes, SSLSocket#read_nonblock can
131
+ # trigger this)
132
+ @state = :read_needs_to_write
133
+ notify_writable = true
134
+ rescue OpenSSL::SSL::SSLError => e
135
+ logerror "attempt_read: #{e} (#{e.class})"
136
+ close_connection
137
+ else
138
+ @state = :ready_to_read
139
+ end
140
+ end
141
+
142
+ public
143
+ def write_buffer
144
+ @write_buffer ||= ""
145
+ end
146
+
147
+ public
148
+ def write_buffer=(rhs)
149
+ @write_buffer = rhs
150
+ end
151
+
152
+ private
153
+ def attempt_write(data=nil)
154
+ logdebug "attempt_write"
155
+ write_buffer << data if data
156
+ # do not attempt to write until we are ready!
157
+ return if @state == :initialized or @state == :new
158
+ begin
159
+ count_written = @sslsocket.write_nonblock write_buffer
160
+ rescue IO::WaitWritable
161
+ notify_writable = true
162
+ rescue IO::WaitReadable
163
+ @state = :write_needs_to_read
164
+ rescue OpenSSL::SSL::SSLError, IOError => e
165
+ logerror "attempt_write: #{e} (#{e.class})"
166
+ close_connection
167
+ else
168
+ # shrink the buf
169
+ #
170
+ # byteslice was added in ruby 1.9.x. in ruby 1.8.7, bytesize is
171
+ # aliased to length, implying that a character coresponds to a
172
+ # byte.
173
+ @write_buffer = if write_buffer.respond_to?(:byteslice)
174
+ write_buffer.byteslice(count_written..-1)
175
+ else
176
+ write_buffer.slice(count_written..-1)
177
+ end
178
+ # if we didn't write everything, wait for writable.
179
+ notify_writable = true if write_buffer.bytesize > 0
180
+ end
181
+ end
182
+
183
+ ####
184
+
185
+ public
186
+
187
+ # Call this to send data to the other end of the connection.
188
+ def send_data(data)
189
+ logdebug "send_data:", :data => data
190
+ attempt_write(data)
191
+ end
192
+
193
+ def close_connection
194
+ detach
195
+ @sslsocket.close if @sslsocket and not @sslsocket.closed?
196
+ @tcpsocket.close if not @tcpsocket.closed?
197
+ # unbind
198
+ end
199
+
200
+ def close_connection_after_writing
201
+ @close_after_writing = true
202
+ # if we aren't waiting to write, then we can flush and close.
203
+ if not notify_writable?
204
+ @sslsocket.flush
205
+ close_connection
206
+ end
207
+
208
+ end
209
+
210
+
211
+ # Note that post_init dos not have access to the _sslsocket_. The
212
+ # _sslsocket_ is not added until tls_begin is called, after the code
213
+ # block.
214
+ #
215
+ # #post_init gives you a chance to manipulate the SSLContext.
216
+ def post_init
217
+ end
218
+
219
+
220
+ # Called right after the SSL handshake succeeds. This is your "new"
221
+ # #post_init.
222
+ def tls_successful_handshake
223
+ logdebug "Succesful handshake!"
224
+ end
225
+
226
+ # Called right after accept_nonblock fails for some unknown reason. The
227
+ # only parameter contains the OpenSSL::SSL::SSLError object that was
228
+ # thrown.
229
+ #
230
+ # The connection will be closed after this.
231
+ def tls_failed_handshake(e)
232
+ logerror "tls_failed_handshake: Failed to accept: #{e} (#{e.class})"
233
+ end
234
+
235
+ # Override this to do something with the unecrypted data.
236
+ def receive_data(data)
237
+ logdebug "receive_data:", :data => data
238
+ end
239
+
240
+ # Override this to do something when the socket is finished.
241
+ def unbind
242
+ logdebug "unbind"
243
+ end
244
+
245
+ end
246
+ end
247
+ end
248
+
249
+
@@ -0,0 +1,26 @@
1
+ module PacketThief
2
+ module Handlers
3
+
4
+ # Instead of forwarding the connection to the original host, forwards it to
5
+ # a configured host instead.
6
+ class ProxyRedirector < TransparentProxy
7
+
8
+ def initialize(proxy_host, proxy_port, log=nil)
9
+ super(log)
10
+
11
+ @proxy_host = proxy_host
12
+ @proxy_port = proxy_port
13
+ end
14
+
15
+ # Instead of using the original destination, use the configured destination.
16
+ def client_connected
17
+ @dest_host = @proxy_host
18
+ @dest_port = @proxy_port
19
+ connect_to_dest
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
26
+
@@ -0,0 +1,87 @@
1
+ module PacketThief
2
+ module Handlers
3
+
4
+ # Basic SSL/TLS Client built on Ruby's OpenSSL objects instead of on
5
+ # EventMachine's start_tls. This allows you to manipulate the SSLContext
6
+ # and other details of the connection that EM normally doesn't let you
7
+ # touch.
8
+ #
9
+ # Subclass it and override any of the methods in the following example to
10
+ # use the the functionality.
11
+ #
12
+ # You can #send_data to send encrypted data to the other side, and
13
+ # #receive_data will be called when there is data for the handler.
14
+ #
15
+ # EM.run {
16
+ # SSLClient.connect "www.isecpartners.com", 443 do |p|
17
+ #
18
+ # # Note: this code block is actually too late to set up a new
19
+ # # #post_init since it runs just after post_init. You can use
20
+ # # #post_init on a subclass though.
21
+ # def p.post_init
22
+ # # modify p.ctx to configure your certificates, key, etc.
23
+ # end
24
+ #
25
+ # # The following makes more sense for the initialization block.
26
+ # h.ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
27
+ #
28
+ # def p.tls_successful_handshake
29
+ # # the handshake succeeded
30
+ # end
31
+ #
32
+ # def p.tls_failed_handshake(e)
33
+ # # the ssl handshake failed, probably due to the client rejecting
34
+ # # your certificate. =)
35
+ # end
36
+ #
37
+ # def p.unbind
38
+ # # unbind handler, called regardless of handshake success
39
+ # end
40
+ #
41
+ # def p.receive_data(data)
42
+ # # do something with the unencrypted stream
43
+ # p.send_data("some message") # data to be encrypted then sent to the client
44
+ # end
45
+ #
46
+ # end
47
+ # }
48
+ #
49
+ # Note: During #initialize and #post_init, this class
50
+ # does not have access to its socket yet. Instead, use #tls_pre_start or
51
+ # the code block you pass to .start to initialize the SSLContext, and use
52
+ # #tls_successful_handshake to do anything once the SSL handshake has
53
+ # completed.
54
+ class SSLClient < AbstractSSLHandler
55
+
56
+ def self.connect(host, port, *args, &block)
57
+ ssl_class = self
58
+
59
+ sock = TCPSocket.new host, port
60
+
61
+ ::EM.watch sock, ssl_class, sock, *args do |h|
62
+ h.notify_readable = true
63
+ # h.notify_writable = true
64
+ block.call(h) if block
65
+ h.tls_begin
66
+ end
67
+ end
68
+
69
+ ####
70
+
71
+ private
72
+ # SSLClient uses connect_nonblock instead of accept_nonblock.
73
+ def connection_action
74
+ @sslsocket.connect_nonblock
75
+ end
76
+
77
+ ####
78
+
79
+ public
80
+ def tls_begin
81
+ super
82
+ attempt_connection
83
+ end
84
+
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,174 @@
1
+ module PacketThief
2
+ module Handlers
3
+
4
+ # Basic SSL/TLS Server built on Ruby's OpenSSL objects instead of on
5
+ # EventMachine's start_tls. This allows you to manipulate the SSLContext
6
+ # and other details of the connection that EM normally doesn't let you
7
+ # touch.
8
+ #
9
+ # Subclass it and override any of the methods in the following example to
10
+ # use the the functionality.
11
+ #
12
+ # You can #send_data to send encrypted data to the other side, and
13
+ # #receive_data will be called when there is data for the handler.
14
+ #
15
+ # EM.run {
16
+ # # Leave the hostname blank for Linux's netfilter.
17
+ # SSLServer.start '', 54321 do |p|
18
+ #
19
+ # # Note: this code block is actually too late to set up a new
20
+ # # #post_init since it runs just after post_init. Instead, you would
21
+ # # use post_init in a subclass.
22
+ # def p.post_init
23
+ # # modify p.ctx to configure your certificates, key, etc.
24
+ # end
25
+ #
26
+ # # In this example, the following would work in this initialization
27
+ # # block:
28
+ # h.ctx.cert = cert
29
+ # h.ctx.extra_chain_cert = chain
30
+ # h.ctx.key = key
31
+ #
32
+ # def servername_cb(sock, hostname)
33
+ # # implement your own SNI handling callback. The default will
34
+ # # return the originally configured context.
35
+ # end
36
+ #
37
+ # def p.tls_successful_handshake
38
+ # # the handshake succeeded
39
+ # end
40
+ #
41
+ # def p.tls_failed_handshake(e)
42
+ # # the ssl handshake failed, probably due to the client rejecting
43
+ # # your certificate. =)
44
+ # end
45
+ #
46
+ # def p.unbind
47
+ # # unbind handler, called regardless of handshake success
48
+ # end
49
+ #
50
+ # def p.receive_data(data)
51
+ # # do something with the unencrypted stream
52
+ # p.send_data("some message") # data to be encrypted then sent to the client
53
+ # end
54
+ #
55
+ # end
56
+ # }
57
+ #
58
+ # Note: During #initialize and #post_init, this class
59
+ # does not have access to its socket yet. Instead, use #tls_pre_start or
60
+ # the code block you pass to .start to initialize the SSLContext, and use
61
+ # #tls_post_accept to do anything once the SSL handshake has completed. You
62
+ # can also override #servername_cb to perform the SNI callback.
63
+ class SSLServer < AbstractSSLHandler
64
+
65
+ # reference to the InitialServer that created the current handler. exists
66
+ # so you can call #stop_server or do something else to it directly.
67
+ attr_accessor :server_handler
68
+
69
+ def self.start(host, port, *args, &block)
70
+ ssl_class = self
71
+
72
+ serv = TCPServer.new host, port
73
+
74
+ # We use InitialServer to listen for incoming connections. It will then
75
+ # create the actual SSLServer.
76
+ initialserver = ::EM.watch serv, InitialServer, serv, ssl_class, args, block do |h|
77
+ h.notify_readable = true
78
+ end
79
+
80
+ initialserver
81
+ end
82
+
83
+ ####
84
+
85
+ # Handles the initial listening socket. We can't seem to use
86
+ # EM.start_server -> EM.detach -> em.watch without triggering
87
+ # (in EventMachine 1.0.0):
88
+ #
89
+ # Assertion failed: (sd != INVALID_SOCKET), function _RunSelectOnce, file em.cpp, line 893.
90
+ #
91
+ # So we handle the server muckery ourselves.
92
+ module InitialServer
93
+ include Logging
94
+
95
+ def initialize(servsocket, ssl_class, args, block)
96
+ @servsocket = servsocket
97
+ @ssl_class = ssl_class
98
+ @args = args
99
+ @block = block
100
+ end
101
+
102
+ def notify_readable
103
+ logdebug "(#{@ssl_class}): Received a new connection, spawning a #{@ssl_class}"
104
+ sock = @servsocket.accept_nonblock
105
+
106
+ ::EM.watch sock, @ssl_class, sock, *@args, @logger do |h|
107
+ logdebug "after initialize"
108
+ h.server_handler = self
109
+ h.notify_readable = true
110
+ h.logger = @logger
111
+ # Now call the caller's block.
112
+ @block.call(h) if @block
113
+ # And finally finish initialization by applying the context to an
114
+ # SSLSocket, and setting the internal state.
115
+ h.tls_begin unless h.tcpsocket.closed?
116
+ end
117
+
118
+ end
119
+
120
+ def notify_writable
121
+ logdebug "(#{@ssl_class}): Server socket notify writable"
122
+ end
123
+
124
+ # This must be called explicitly. EM doesn't seem to have a callback for when the EM::run call ends.
125
+ def stop_server
126
+ unless @servsocket.closed?
127
+ detach
128
+ @servsocket.close
129
+ end
130
+ end
131
+
132
+ def unbind
133
+ logdebug "(#{@ssl_class}): Stopping server socket"
134
+ end
135
+ end
136
+
137
+ ####
138
+
139
+ private
140
+ # SSLServer uses accept_nonblock instead of connect_nonblock.
141
+ def connection_action
142
+ @sslsocket.accept_nonblock
143
+ end
144
+
145
+ ####
146
+
147
+ public
148
+ def initialize(tcpsocket,logger=nil)
149
+ super(tcpsocket, logger)
150
+ @ctx.servername_cb = proc {|sslsocket, hostname| self.servername_cb(sslsocket, hostname) }
151
+ end
152
+
153
+
154
+ # Called when the client sends a hostname using the SNI TLS extension.
155
+ #
156
+ # This method should return an OpenSSL::SSL::SSLContext. It gives you an
157
+ # opportunity to pick or generate a different server certificate or
158
+ # certificate chain based on the hostname requested by the client.
159
+ #
160
+ # The default implementation does nothing by just returning the original
161
+ # SSLContext.
162
+ def servername_cb(sslsock, hostname)
163
+ sslsock.context
164
+ end
165
+
166
+ # Stops the InitialListener sever handler that spawned this handler. Due
167
+ # to our use of EM.watch, we can't rely on EM to close the socket.
168
+ def stop_server
169
+ @server_handler.stop_server
170
+ end
171
+
172
+ end
173
+ end
174
+ end