File add-support-for-disabling-TLSv1.0.patch of Package rubygem-puma.15815
From 923576c57b9afe02c3fa92c1f368a1cc9fd5222e Mon Sep 17 00:00:00 2001
From: dmaiocchi <dmaiocchi@suse.com>
Date: Wed, 24 Jun 2020 20:35:12 +0200
Subject: [PATCH] add support for disabling TLSv1.0
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Many organizations run their applications using in environments that fall into
scope of PCI-DSS compliance audits. One of the requirements set out by standard
is to migrate to more secure protocols if possible.
PCI Security Standards council has advised to migrate away from TLSv1.0 over
last few years and recently set a migration deadline of 30 June 2018 (see [1]
for more details).
Change proposed in this commit gives an user option to disable `TLSv1.0` during
bind, while still leaving the `TLSv1.1` and `TLSv1.2` enabled. `SSLv2` and
`SSLv3` are permanently disabled (as they should).
Default behaviour is not changed if the `no_tls` option is not defined.
[1]: https://blog.pcisecuritystandards.org/are-you-ready-for-30-june-2018-sayin-goodbye-to-ssl-early-tls
---
README.md | 333 ++++++++++----------
ext/puma_http11/mini_ssl.c | 93 +++++-
ext/puma_http11/org/jruby/puma/MiniSSL.java | 8 +-
lib/puma/binder.rb | 133 +++++---
lib/puma/dsl.rb | 249 ++++++++++++---
lib/puma/minissl.rb | 101 +++++-
test/test_binder.rb | 101 ++++++
7 files changed, 748 insertions(+), 270 deletions(-)
create mode 100644 test/test_binder.rb
diff --git a/README.md b/README.md
index 10f624fe..4190d37e 100644
--- a/README.md
+++ b/README.md
@@ -1,274 +1,259 @@
-# Puma: A Ruby Web Server Built For Concurrency
+<p align="center">
+ <img src="http://puma.io/images/logos/puma-logo-large.png">
+</p>
-[](https://gitter.im/puma/puma?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](http://travis-ci.org/puma/puma) [](https://gemnasium.com/puma/puma) <a href="https://codeclimate.com/github/puma/puma"><img src="https://codeclimate.com/github/puma/puma.png" /></a>
+# Puma: A Ruby Web Server Built For Concurrency
-## Description
+[](https://gitter.im/puma/puma?utm\_source=badge&utm\_medium=badge&utm\_campaign=pr-badge)
+[](http://travis-ci.org/puma/puma)
+[](https://ci.appveyor.com/project/nateberkopec/puma)
+[](https://gemnasium.com/puma/puma)
+[](https://codeclimate.com/github/puma/puma)
-Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications. Puma is intended for use in both development and production environments. In order to get the best throughput, it is highly recommended that you use a Ruby implementation with real threads like Rubinius or JRuby.
+Puma is a **simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications** in development and production.
## Built For Speed & Concurrency
-Puma is a simple, fast, and highly concurrent HTTP 1.1 server for Ruby web applications. It can be used with any application that supports Rack, and is considered the replacement for Webrick and Mongrel. It was designed to be the go-to server for [Rubinius](http://rubini.us), but also works well with JRuby and MRI. Puma is intended for use in both development and production environments.
+Under the hood, Puma processes requests using a C-optimized Ragel extension (inherited from Mongrel) that provides fast, accurate HTTP 1.1 protocol parsing in a portable way. Puma then serves the request in a thread from an internal thread pool. Since each request is served in a separate thread, truly concurrent Ruby implementations (JRuby, Rubinius) will use all available CPU cores.
-Under the hood, Puma processes requests using a C-optimized Ragel extension (inherited from Mongrel) that provides fast, accurate HTTP 1.1 protocol parsing in a portable way. Puma then serves the request in a thread from an internal thread pool (which you can control). This allows Puma to provide real concurrency for your web application!
+Puma was designed to be the go-to server for [Rubinius](http://rubini.us), but also works well with JRuby and MRI.
-With Rubinius 2.0, Puma will utilize all cores on your CPU with real threads, meaning you won't have to spawn multiple processes to increase throughput. You can expect to see a similar benefit from JRuby.
-
-On MRI, there is a Global Interpreter Lock (GIL) that ensures only one thread can be run at a time. But if you're doing a lot of blocking IO (such as HTTP calls to external APIs like Twitter), Puma still improves MRI's throughput by allowing blocking IO to be run concurrently (EventMachine-based servers such as Thin turn off this ability, requiring you to use special libraries). Your mileage may vary. In order to get the best throughput, it is highly recommended that you use a Ruby implementation with real threads like [Rubinius](http://rubini.us) or [JRuby](http://jruby.org).
+On MRI, there is a Global VM Lock (GVL) that ensures only one thread can run Ruby code at a time. But if you're doing a lot of blocking IO (such as HTTP calls to external APIs like Twitter), Puma still improves MRI's throughput by allowing blocking IO to be run concurrently.
## Quick Start
-The easiest way to get started with Puma is to install it via RubyGems. You can do this easily:
-
- $ gem install puma
-
-Now you should have the `puma` command available in your PATH, so just do the following in the root folder of your Rack application:
-
- $ puma app.ru
-
-## Advanced Setup
-
-### Sinatra
-
-You can run your Sinatra application with Puma from the command line like this:
-
- $ ruby app.rb -s Puma
-
-Or you can configure your application to always use Puma:
-
- require 'sinatra'
- configure { set :server, :puma }
+```
+$ gem install puma
+$ puma <any rackup (*.ru) file>
+```
-If you use Bundler, make sure you add Puma to your Gemfile (see below).
+## Frameworks
### Rails
-First, make sure Puma is in your Gemfile:
-
- gem 'puma'
+Puma is the default server for Rails, and should already be included in your Gemfile.
Then start your server with the `rails` command:
- $ rails s Puma
+```
+$ rails s
+```
-### Rackup
+Many configuration options are not available when using `rails s`. It is recommended that you use Puma's executable instead:
-You can pass it as an option to `rackup`:
+```
+$ bundle exec puma
+```
- $ rackup -s Puma
+### Sinatra
+
+You can run your Sinatra application with Puma from the command line like this:
-Alternatively, you can modify your `config.ru` to choose Puma by default, by adding the following as the first line:
+```
+$ ruby app.rb -s Puma
+```
- #\ -s puma
+Or you can configure your application to always use Puma:
+
+```ruby
+require 'sinatra'
+configure { set :server, :puma }
+```
## Configuration
-Puma provides numerous options for controlling the operation of the server. Consult `puma -h` (or `puma --help`) for a full list.
+Puma provides numerous options. Consult `puma -h` (or `puma --help`) for a full list of CLI options, or see [dsl.rb](https://github.com/puma/puma/blob/master/lib/puma/dsl.rb).
### Thread Pool
-Puma utilizes a dynamic thread pool which you can modify. You can set the minimum and maximum number of threads that are available in the pool with the `-t` (or `--threads`) flag:
+Puma uses a thread pool. You can set the minimum and maximum number of threads that are available in the pool with the `-t` (or `--threads`) flag:
- $ puma -t 8:32
+```
+$ puma -t 8:32
+```
-Puma will automatically scale the number of threads based on how much traffic is present. The current default is `0:16`. Feel free to experiment, but be careful not to set the number of maximum threads to a very large number, as you may exhaust resources on the system (or hit resource limits).
+Puma will automatically scale the number of threads, from the minimum until it caps out at the maximum, based on how much traffic is present. The current default is `0:16`. Feel free to experiment, but be careful not to set the number of maximum threads to a large number, as you may exhaust resources on the system (or hit resource limits).
-### Clustered mode
+Be aware that additionally Puma creates threads on its own for internal purposes (e.g. handling slow clients). So even if you specify -t 1:1, expect around 7 threads created in your application.
-Puma 2 offers clustered mode, allowing you to use forked processes to handle multiple incoming requests concurrently, in addition to threads already provided. You can tune the number of workers with the `-w` (or `--workers`) flag:
+### Clustered mode
- $ puma -t 8:32 -w 3
+Puma also offers "clustered mode". Clustered mode `fork`s workers from a master process. Each child process still has its own thread pool. You can tune the number of workers with the `-w` (or `--workers`) flag:
-On a ruby implementation that offers native threads, you should tune this number to match the number of cores available.
-Note that threads are still used in clustered mode, and the `-t` thread flag setting is per worker, so `-w 2 -t 16:16` will be 32 threads.
+```
+$ puma -t 8:32 -w 3
+```
-If you're running in Clustered Mode you can optionally choose to preload your application before starting up the workers. This is necessary in order to take advantage of the [Copy on Write](http://en.wikipedia.org/wiki/Copy-on-write) feature introduced in [MRI Ruby 2.0](https://blog.heroku.com/archives/2013/3/6/matz_highlights_ruby_2_0_at_waza). To do this simply specify the `--preload` flag in invocation:
+Note that threads are still used in clustered mode, and the `-t` thread flag setting is per worker, so `-w 2 -t 16:16` will spawn 32 threads in total.
- # CLI invocation
- $ puma -t 8:32 -w 3 --preload
+In clustered mode, Puma may "preload" your application. This loads all the application code *prior* to forking. Preloading reduces total memory usage of your application via an operating system feature called [copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write) (Ruby 2.0+ only). Use the `--preload` flag from the command line:
-If you're using a configuration file, use the `preload_app!` method, and be sure to specify your config file's location with the `-C` flag:
+```
+$ puma -w 3 --preload
+```
- $ puma -C config/puma.rb
+If you're using a configuration file, use the `preload_app!` method:
- # config/puma.rb
- threads 8,32
- workers 3
- preload_app!
+```ruby
+# config/puma.rb
+workers 3
+preload_app!
+```
Additionally, you can specify a block in your configuration file that will be run on boot of each worker:
- # config/puma.rb
- on_worker_boot do
- # configuration here
- end
+```ruby
+# config/puma.rb
+on_worker_boot do
+ # configuration here
+end
+```
This code can be used to setup the process before booting the application, allowing
you to do some Puma-specific things that you don't want to embed in your application.
For instance, you could fire a log notification that a worker booted or send something to statsd.
-This can be called multiple times to add hooks.
-
-If you're preloading your application and using ActiveRecord, it's recommend you setup your connection pool here:
-
- # config/puma.rb
- on_worker_boot do
- ActiveSupport.on_load(:active_record) do
- ActiveRecord::Base.establish_connection
- end
- end
-
-On top of that, you can specify a block in your configuration file that will be run before workers are forked
-
- # config/puma.rb
- before_fork do
- # configuration here
- end
-
-This code can be used to clean up before forking to clients, allowing
-you to do some Puma-specific things that you don't want to embed in your application.
-
-If you're preloading your application and using ActiveRecord, it's recommend you close any connections to the database here to prevent connection leakage:
-
- # config/puma.rb
- before_fork do
- ActiveRecord::Base.connection_pool.disconnect!
- end
-
-This rule applies to any connections to external services (Redis, databases, memcache, ...) that might be started automatically by the framework.
-
-When you use preload_app, your new code goes all in the master process, and is then copied in the workers (meaning it’s only compatible with cluster mode). General rule is to use preload_app when your workers die often and need fast starts. If you don’t have many workers, you probably should not use preload_app.
+This can be called multiple times.
-Note that preload_app can’t be used with phased restart, since phased restart kills and restarts workers one-by-one, and preload_app is all about copying the code of master into the workers.
+If you're preloading your application and using ActiveRecord, it's recommended that you setup your connection pool here:
-### Error handler for low-level errors
+```ruby
+# config/puma.rb
+on_worker_boot do
+ ActiveSupport.on_load(:active_record) do
+ ActiveRecord::Base.establish_connection
+ end
+end
+```
-If puma encounters an error outside of the context of your application, it will respond with a 500 and a simple
-textual error message (see `lowlevel_error` in [this file](https://github.com/puma/puma/blob/master/lib/puma/server.rb)).
-You can specify custom behavior for this scenario. For example, you can report the error to your third-party
-error-tracking service (in this example, [rollbar](http://rollbar.com)):
+On top of that, you can specify a block in your configuration file that will be run before workers are forked:
```ruby
-lowlevel_error_handler do |e|
- Rollbar.critical(e)
- [500, {}, ["An error has occurred, and engineers have been informed. Please reload the page. If you continue to have problems, contact support@example.com\n"]]
+# config/puma.rb
+before_fork do
+ # configuration here
end
```
+Preloading can’t be used with phased restart, since phased restart kills and restarts workers one-by-one, and preload_app copies the code of master into the workers.
+
### Binding TCP / Sockets
In contrast to many other server configs which require multiple flags, Puma simply uses one URI parameter with the `-b` (or `--bind`) flag:
- $ puma -b tcp://127.0.0.1:9292
+```
+$ puma -b tcp://127.0.0.1:9292
+```
-Want to use UNIX Sockets instead of TCP (which can provide a 5-10% performance boost)? No problem!
+Want to use UNIX Sockets instead of TCP (which can provide a 5-10% performance boost)?
- $ puma -b unix:///var/run/puma.sock
+```
+$ puma -b unix:///var/run/puma.sock
+```
If you need to change the permissions of the UNIX socket, just add a umask parameter:
- $ puma -b 'unix:///var/run/puma.sock?umask=0111'
-
-Need a bit of security? Use SSL sockets!
-
- $ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert'
-
-### Control/Status Server
-
-Puma comes with a builtin status/control app that can be used query and control Puma itself. Here is an example of starting Puma with the control server:
-
- $ puma --control tcp://127.0.0.1:9293 --control-token foo
-
-This directs Puma to start the control server on localhost port 9293. Additionally, all requests to the control server will need to include `token=foo` as a query parameter. This allows for simple authentication. Check out [status.rb](https://github.com/puma/puma/blob/master/lib/puma/app/status.rb) to see what the app has available.
-
-### Configuration file
-
-You can also provide a configuration file which Puma will use with the `-C` (or `--config`) flag:
-
- $ puma -C /path/to/config
-
-By default, if no configuration file is specified, Puma will look for a configuration file at config/puma.rb. If an environment is specified, either via the `-e` and `--environment` flags, or through the `RACK_ENV` environment variable, the default file location will be config/puma/environment_name.rb.
-
-If you want to prevent Puma from looking for a configuration file in those locations, provide a dash as the argument to the `-C` (or `--config`) flag:
+```
+$ puma -b 'unix:///var/run/puma.sock?umask=0111'
+```
- $ puma -C "-"
+Need a bit of security? Use SSL sockets:
+```
+$ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert'
+```
+#### Controlling SSL Cipher Suites
+Need to use or avoid specific SSL cipher suites? Use ssl_cipher_filter or ssl_cipher_list options.
+#####Ruby:
+```
+$ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert&ssl_cipher_filter=!aNULL:AES+SHA'
+```
+#####JRuby:
+```
+$ puma -b 'ssl://127.0.0.1:9292?keystore=path_to_keystore&keystore-pass=keystore_password&ssl_cipher_list=TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA'
+```
+See https://www.openssl.org/docs/man1.0.2/apps/ciphers.html for cipher filter format and full list of cipher suites.
-Take the following [sample configuration](https://github.com/puma/puma/blob/master/examples/config.rb) as inspiration or check out [configuration.rb](https://github.com/puma/puma/blob/master/lib/puma/configuration.rb) to see all available options.
+Don't want to use insecure TLSv1.0 ?
-## Restart
+```
+$ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert&no_tlsv1=true'
+```
-Puma includes the ability to restart itself allowing easy upgrades to new versions. When available (MRI, Rubinius, JRuby), Puma performs a "hot restart". This is the same functionality available in *unicorn* and *nginx* which keep the server sockets open between restarts. This makes sure that no pending requests are dropped while the restart is taking place.
+### Control/Status Server
-To perform a restart, there are 2 builtin mechanisms:
+Puma has a built-in status/control app that can be used to query and control Puma itself.
- * Send the `puma` process the `SIGUSR2` signal
- * Use the status server and issue `/restart`
+```
+$ puma --control-url tcp://127.0.0.1:9293 --control-token foo
+```
-No code is shared between the current and restarted process, so it should be safe to issue a restart any place where you would manually stop Puma and start it again.
+Puma will start the control server on localhost port 9293. All requests to the control server will need to include `token=foo` as a query parameter. This allows for simple authentication. Check out [status.rb](https://github.com/puma/puma/blob/master/lib/puma/app/status.rb) to see what the app has available.
-If the new process is unable to load, it will simply exit. You should therefore run Puma under a supervisor when using it in production.
+You can also interact with the control server via `pumactl`. This command will restart Puma:
-### Normal vs Hot vs Phased Restart
+```
+$ pumactl --control-url 'tcp://127.0.0.1:9293' --control-token foo restart
+```
-A hot restart means that no requests while deploying your new code will be lost, since the server socket is kept open between restarts.
+To see a list of `pumactl` options, use `pumactl --help`.
-But beware, hot restart does not mean that the incoming requests won’t hang for multiple seconds while your new code has not fully deployed. If you need a zero downtime and zero hanging requests deploy, you must use phased restart.
+### Configuration File
-When you run pumactl phased-restart, Puma kills workers one-by-one, meaning that at least another worker is still available to serve requests, which lead in zero hanging request (yay!).
+You can also provide a configuration file which Puma will use with the `-C` (or `--config`) flag:
-But again beware, upgrading an application sometimes involves upgrading the database schema. With phased restart, there may be a moment during the deployment where processes belonging to the previous version and processes belonging to the new version both exist at the same time. Any database schema upgrades you perform must therefore be backwards-compatible with the old application version.
+```
+$ puma -C /path/to/config
+```
-if you perform a lot of database migrations, you probably should not use phased restart and use a normal/hot restart instead (pumactl restart). That way, no code is shared while deploying (in that case, preload_app might help for quicker deployment, see below).
+If no configuration file is specified, Puma will look for a configuration file at `config/puma.rb`. If an environment is specified, either via the `-e` and `--environment` flags, or through the `RACK_ENV` environment variable, the default file location will be `config/puma/environment_name.rb`.
+If you want to prevent Puma from looking for a configuration file in those locations, provide a dash as the argument to the `-C` (or `--config`) flag:
-### Cleanup Code
+```
+$ puma -C "-"
+```
-Puma isn't able to understand all the resources that your app may use, so it provides a hook in the configuration file you pass to `-C` called `on_restart`. The block passed to `on_restart` will be called, unsurprisingly, just before Puma restarts itself.
+Take the following [sample configuration](https://github.com/puma/puma/blob/master/examples/config.rb) as inspiration or check out [dsl.rb](https://github.com/puma/puma/blob/master/lib/puma/dsl.rb) to see all available options.
-You should place code to close global log files, redis connections, etc in this block so that their file descriptors don't leak into the restarted process. Failure to do so will result in slowly running out of descriptors and eventually obscure crashes as the server is restart many times.
+## Restart
-### Platform Constraints
+Puma includes the ability to restart itself. When available (MRI, Rubinius, JRuby), Puma performs a "hot restart". This is the same functionality available in *Unicorn* and *NGINX* which keep the server sockets open between restarts. This makes sure that no pending requests are dropped while the restart is taking place.
-Because of various platforms not being implement certain things, the following differences occur when Puma is used on different platforms:
+For more, see the [restart documentation](https://github.com/puma/puma/blob/master/docs/restart.md).
- * **JRuby**, **Windows**: server sockets are not seamless on restart, they must be closed and reopened. These platforms have no way to pass descriptors into a new process that is exposed to ruby
- * **JRuby**, **Windows**: cluster mode is not supported due to a lack of fork(2)
- * **Windows**: daemon mode is not supported due to a lack of fork(2)
+## Signals
-## pumactl
+Puma responds to several signals. A detailed guide to using UNIX signals with Puma can be found in the [signals documentation](https://github.com/puma/puma/blob/master/docs/signals.md).
-`pumactl` is a simple CLI frontend to the control/status app described above. Please refer to `pumactl --help` for available commands.
+## Platform Constraints
-## Managing multiple Pumas / init.d / upstart scripts
+Some platforms do not support all Puma features.
-If you want an easy way to manage multiple scripts at once check [tools/jungle](https://github.com/puma/puma/tree/master/tools/jungle) for init.d and upstart scripts.
+ * **JRuby**, **Windows**: server sockets are not seamless on restart, they must be closed and reopened. These platforms have no way to pass descriptors into a new process that is exposed to Ruby. Also, cluster mode is not supported due to a lack of fork(2).
+ * **Windows**: daemon mode is not supported due to a lack of fork(2).
-## Capistrano deployment
+## Known Bugs
-Puma has support for Capistrano3 with an [external gem](https://github.com/seuros/capistrano-puma), you just need require that in Gemfile:
+For MRI versions 2.2.7, 2.2.8, 2.2.9, 2.2.10 2.3.4 and 2.4.1, you may see ```stream closed in another thread (IOError)```. It may be caused by a [Ruby bug](https://bugs.ruby-lang.org/issues/13632). It can be fixed with the gem https://rubygems.org/gems/stopgap_13632:
```ruby
-gem 'capistrano3-puma'
-```
-And then execute:
-
-```bash
-bundle
+if %w(2.2.7 2.2.8 2.2.9 2.2.10 2.3.4 2.4.1).include? RUBY_VERSION
+ begin
+ require 'stopgap_13632'
+ rescue LoadError
+ end
+end
```
-Then add to Capfile
+## Deployment
-```ruby
-require 'capistrano/puma'
-```
+Puma has support for Capistrano with an [external gem](https://github.com/seuros/capistrano-puma).
-and then
+It is common to use process monitors with Puma. Modern process monitors like systemd or upstart
+provide continuous monitoring and restarts for increased
+reliability in production environments:
-```bash
-$ bundle exec cap puma:start
-$ bundle exec cap puma:restart
-$ bundle exec cap puma:stop
-$ bundle exec cap puma:phased-restart
-```
+* [tools/jungle](https://github.com/puma/puma/tree/master/tools/jungle) for sysvinit (init.d) and upstart
+* [docs/systemd](https://github.com/puma/puma/blob/master/docs/systemd.md)
## Contributing
@@ -281,4 +266,4 @@ $ bundle exec rake
## License
-Puma is copyright 2014 Evan Phoenix and contributors. It is licensed under the BSD 3-Clause license. See the include LICENSE file for details.
+Puma is copyright Evan Phoenix and contributors, licensed under the BSD 3-Clause license. See the included LICENSE file for details.
diff --git a/ext/puma_http11/mini_ssl.c b/ext/puma_http11/mini_ssl.c
index 8b562164..b2549cf1 100644
--- a/ext/puma_http11/mini_ssl.c
+++ b/ext/puma_http11/mini_ssl.c
@@ -1,7 +1,13 @@
#define RSTRING_NOT_MODIFIED 1
#include <ruby.h>
+#include <ruby/version.h>
+
+#if RUBY_API_VERSION_MAJOR == 1
#include <rubyio.h>
+#else
+#include <ruby/io.h>
+#endif
#ifdef HAVE_OPENSSL_BIO_H
@@ -81,6 +87,8 @@ DH *get_dh1024() {
DH *dh;
dh = DH_new();
+
+#if OPENSSL_VERSION_NUMBER < 0x10100005L || defined(LIBRESSL_VERSION_NUMBER)
dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL);
dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL);
@@ -88,6 +96,18 @@ DH *get_dh1024() {
DH_free(dh);
return NULL;
}
+#else
+ BIGNUM *p, *g;
+ p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL);
+ g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL);
+
+ if (p == NULL || g == NULL || !DH_set0_pqg(dh, p, NULL, g)) {
+ DH_free(dh);
+ BN_free(p);
+ BN_free(g);
+ return NULL;
+ }
+#endif
return dh;
}
@@ -122,21 +142,33 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
VALUE obj;
SSL_CTX* ctx;
SSL* ssl;
+ int ssl_options;
ms_conn* conn = engine_alloc(self, &obj);
ID sym_key = rb_intern("key");
VALUE key = rb_funcall(mini_ssl_ctx, sym_key, 0);
+ StringValue(key);
+
ID sym_cert = rb_intern("cert");
VALUE cert = rb_funcall(mini_ssl_ctx, sym_cert, 0);
+ StringValue(cert);
+
ID sym_ca = rb_intern("ca");
VALUE ca = rb_funcall(mini_ssl_ctx, sym_ca, 0);
ID sym_verify_mode = rb_intern("verify_mode");
VALUE verify_mode = rb_funcall(mini_ssl_ctx, sym_verify_mode, 0);
+ ID sym_ssl_cipher_filter = rb_intern("ssl_cipher_filter");
+ VALUE ssl_cipher_filter = rb_funcall(mini_ssl_ctx, sym_ssl_cipher_filter, 0);
+
+ ID sym_no_tlsv1 = rb_intern("no_tlsv1");
+ VALUE no_tlsv1 = rb_funcall(mini_ssl_ctx, sym_no_tlsv1, 0);
+
+
ctx = SSL_CTX_new(SSLv23_server_method());
conn->ctx = ctx;
@@ -144,13 +176,25 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
SSL_CTX_use_PrivateKey_file(ctx, RSTRING_PTR(key), SSL_FILETYPE_PEM);
if (!NIL_P(ca)) {
+ StringValue(ca);
SSL_CTX_load_verify_locations(ctx, RSTRING_PTR(ca), NULL);
}
-
- SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_COMPRESSION);
+
+ ssl_options = SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_COMPRESSION;
+
+ if(RTEST(no_tlsv1)) {
+ ssl_options |= SSL_OP_NO_TLSv1;
+ }
+ SSL_CTX_set_options(ctx, ssl_options);
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
- SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL@STRENGTH");
+ if (!NIL_P(ssl_cipher_filter)) {
+ StringValue(ssl_cipher_filter);
+ SSL_CTX_set_cipher_list(ctx, RSTRING_PTR(ssl_cipher_filter));
+ }
+ else {
+ SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL@STRENGTH");
+ }
DH *dh = get_dh1024();
SSL_CTX_set_tmp_dh(ctx, dh);
@@ -219,7 +263,7 @@ void raise_error(SSL* ssl, int result) {
const char* err_str;
int err = errno;
int ssl_err = SSL_get_error(ssl, result);
- int verify_err = SSL_get_verify_result(ssl);
+ int verify_err = (int) SSL_get_verify_result(ssl);
if(SSL_ERROR_SYSCALL == ssl_err) {
snprintf(msg, sizeof(msg), "System error: %s - %d", strerror(err), err);
@@ -232,7 +276,7 @@ void raise_error(SSL* ssl, int result) {
err_str, verify_err);
} else {
- err = ERR_get_error();
+ err = (int) ERR_get_error();
ERR_error_string_n(err, buf, sizeof(buf));
snprintf(msg, sizeof(msg), "OpenSSL error: %s - %d", buf, err);
@@ -316,6 +360,30 @@ VALUE engine_extract(VALUE self) {
return Qnil;
}
+VALUE engine_shutdown(VALUE self) {
+ ms_conn* conn;
+ int ok;
+
+ Data_Get_Struct(self, ms_conn, conn);
+
+ ERR_clear_error();
+
+ ok = SSL_shutdown(conn->ssl);
+ if (ok == 0) {
+ return Qfalse;
+ }
+
+ return Qtrue;
+}
+
+VALUE engine_init(VALUE self) {
+ ms_conn* conn;
+
+ Data_Get_Struct(self, ms_conn, conn);
+
+ return SSL_in_init(conn->ssl) ? Qtrue : Qfalse;
+}
+
VALUE engine_peercert(VALUE self) {
ms_conn* conn;
X509* cert;
@@ -347,7 +415,7 @@ VALUE engine_peercert(VALUE self) {
}
}
- rb_cert_buf = rb_str_new(buf, bytes);
+ rb_cert_buf = rb_str_new((const char*)(buf), bytes);
if(!cert_buf) {
OPENSSL_free(buf);
}
@@ -362,11 +430,16 @@ VALUE noop(VALUE self) {
void Init_mini_ssl(VALUE puma) {
VALUE mod, eng;
+/* Fake operation for documentation (RDoc, YARD) */
+#if 0 == 1
+ puma = rb_define_module("Puma");
+#endif
+
SSL_library_init();
OpenSSL_add_ssl_algorithms();
SSL_load_error_strings();
ERR_load_crypto_strings();
-
+
mod = rb_define_module_under(puma, "MiniSSL");
eng = rb_define_class_under(mod, "Engine", rb_cObject);
@@ -383,6 +456,10 @@ void Init_mini_ssl(VALUE puma) {
rb_define_method(eng, "write", engine_write, 1);
rb_define_method(eng, "extract", engine_extract, 0);
+ rb_define_method(eng, "shutdown", engine_shutdown, 0);
+
+ rb_define_method(eng, "init?", engine_init, 0);
+
rb_define_method(eng, "peercert", engine_peercert, 0);
}
@@ -394,7 +471,7 @@ VALUE raise_error(VALUE self) {
}
void Init_mini_ssl(VALUE puma) {
- VALUE mod, eng;
+ VALUE mod;
mod = rb_define_module_under(puma, "MiniSSL");
rb_define_class_under(mod, "SSLError", rb_eStandardError);
diff --git a/ext/puma_http11/org/jruby/puma/MiniSSL.java b/ext/puma_http11/org/jruby/puma/MiniSSL.java
index 3655df78..6390ceba 100644
--- a/ext/puma_http11/org/jruby/puma/MiniSSL.java
+++ b/ext/puma_http11/org/jruby/puma/MiniSSL.java
@@ -155,7 +155,13 @@ public class MiniSSL extends RubyObject {
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
engine = sslCtx.createSSLEngine();
- String[] protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
+ String[] protocols;
+ if(miniSSLContext.callMethod(threadContext, "no_tlsv1").isTrue()) {
+ protocols = new String[] { "TLSv1.1", "TLSv1.2" };
+ } else {
+ protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
+ }
+
engine.setEnabledProtocols(protocols);
engine.setUseClientMode(false);
diff --git a/lib/puma/binder.rb b/lib/puma/binder.rb
index a52dbd90..33864b9a 100644
--- a/lib/puma/binder.rb
+++ b/lib/puma/binder.rb
@@ -1,4 +1,8 @@
+require 'uri'
+require 'socket'
+
require 'puma/const'
+require 'puma/util'
module Puma
class Binder
@@ -10,6 +14,7 @@ module Puma
@events = events
@listeners = []
@inherited_fds = {}
+ @activated_sockets = {}
@unix_paths = []
@proto_env = {
@@ -21,13 +26,13 @@ module Puma
"SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
# I'd like to set a default CONTENT_TYPE here but some things
- # depend on their not being a default set and infering
+ # depend on their not being a default set and inferring
# it from the content. And so if i set it here, it won't
# infer properly.
"QUERY_STRING".freeze => "",
SERVER_PROTOCOL => HTTP_11,
- SERVER_SOFTWARE => PUMA_VERSION,
+ SERVER_SOFTWARE => PUMA_SERVER_STRING,
GATEWAY_INTERFACE => CGI_VER
}
@@ -54,24 +59,23 @@ module Puma
fd, url = v.split(":", 2)
@inherited_fds[url] = fd.to_i
remove << k
- end
- if k =~ /LISTEN_FDS/ && ENV['LISTEN_PID'].to_i == $$
+ elsif k == 'LISTEN_FDS' && ENV['LISTEN_PID'].to_i == $$
v.to_i.times do |num|
fd = num + 3
sock = TCPServer.for_fd(fd)
begin
- url = "unix://" + Socket.unpack_sockaddr_un(sock.getsockname)
+ key = [ :unix, Socket.unpack_sockaddr_un(sock.getsockname) ]
rescue ArgumentError
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
if addr =~ /\:/
addr = "[#{addr}]"
end
- url = "tcp://#{addr}:#{port}"
+ key = [ :tcp, addr, port ]
end
- @inherited_fds[url] = sock
+ @activated_sockets[key] = sock
+ @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
end
- ENV.delete k
- ENV.delete 'LISTEN_PID'
+ remove << k << 'LISTEN_PID'
end
end
@@ -88,6 +92,9 @@ module Puma
if fd = @inherited_fds.delete(str)
logger.log "* Inherited #{str}"
io = inherit_tcp_listener uri.host, uri.port, fd
+ elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
+ logger.log "* Activated #{str}"
+ io = inherit_tcp_listener uri.host, uri.port, sock
else
params = Util.parse_query uri.query
@@ -98,18 +105,22 @@ module Puma
io = add_tcp_listener uri.host, uri.port, opt, bak
end
- @listeners << [str, io]
+ @listeners << [str, io] if io
when "unix"
path = "#{uri.host}#{uri.path}".gsub("%20", " ")
if fd = @inherited_fds.delete(str)
logger.log "* Inherited #{str}"
io = inherit_unix_listener path, fd
+ elsif sock = @activated_sockets.delete([ :unix, path ])
+ logger.log "* Activated #{str}"
+ io = inherit_unix_listener path, sock
else
logger.log "* Listening on #{str}"
umask = nil
mode = nil
+ backlog = 1024
if uri.query
params = Util.parse_query uri.query
@@ -121,18 +132,22 @@ module Puma
if u = params['mode']
mode = Integer('0'+u)
end
+
+ if u = params['backlog']
+ backlog = Integer(u)
+ end
end
- io = add_unix_listener path, umask, mode
+ io = add_unix_listener path, umask, mode, backlog
end
@listeners << [str, io]
when "ssl"
- MiniSSL.check
-
params = Util.parse_query uri.query
require 'puma/minissl'
+ MiniSSL.check
+
ctx = MiniSSL::Context.new
if defined?(JRUBY_VERSION)
@@ -147,6 +162,7 @@ module Puma
end
ctx.keystore_pass = params['keystore-pass']
+ ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
else
unless params['key']
@events.error "Please specify the SSL key via 'key='"
@@ -165,33 +181,39 @@ module Puma
@events.error "Please specify the SSL ca via 'ca='"
end
end
-
+
ctx.ca = params['ca'] if params['ca']
+ ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
+ end
- if params['verify_mode']
- ctx.verify_mode = case params['verify_mode']
- when "peer"
- MiniSSL::VERIFY_PEER
- when "force_peer"
- MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
- when "none"
- MiniSSL::VERIFY_NONE
- else
- @events.error "Please specify a valid verify_mode="
- MiniSSL::VERIFY_NONE
- end
- end
+ ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
+
+ if params['verify_mode']
+ ctx.verify_mode = case params['verify_mode']
+ when "peer"
+ MiniSSL::VERIFY_PEER
+ when "force_peer"
+ MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
+ when "none"
+ MiniSSL::VERIFY_NONE
+ else
+ @events.error "Please specify a valid verify_mode="
+ MiniSSL::VERIFY_NONE
+ end
end
if fd = @inherited_fds.delete(str)
logger.log "* Inherited #{str}"
- io = inherited_ssl_listener fd, ctx
+ io = inherit_ssl_listener fd, ctx
+ elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
+ logger.log "* Activated #{str}"
+ io = inherit_ssl_listener sock, ctx
else
logger.log "* Listening on #{str}"
io = add_ssl_listener uri.host, uri.port, ctx
end
- @listeners << [str, io]
+ @listeners << [str, io] if io
else
logger.error "Invalid URI: #{str}"
end
@@ -203,12 +225,7 @@ module Puma
logger.log "* Closing unused inherited connection: #{str}"
begin
- if fd.kind_of? TCPServer
- fd.close
- else
- IO.for_fd(fd).close
- end
-
+ IO.for_fd(fd).close
rescue SystemCallError
end
@@ -220,6 +237,22 @@ module Puma
end
end
+ # Also close any unused activated sockets
+ @activated_sockets.each do |key, sock|
+ logger.log "* Closing unused activated socket: #{key.join ':'}"
+ begin
+ sock.close
+ rescue SystemCallError
+ end
+ # We have to unlink a unix socket path that's not being used
+ File.unlink key[1] if key[0] == :unix
+ end
+ end
+
+ def loopback_addresses
+ Socket.ip_address_list.select do |addrinfo|
+ addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
+ end.map { |addrinfo| addrinfo.ip_address }.uniq
end
# Tell the server to listen on host +host+, port +port+.
@@ -230,6 +263,13 @@ module Puma
# allow to accumulate before returning connection refused.
#
def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
+ if host == "localhost"
+ loopback_addresses.each do |addr|
+ add_tcp_listener addr, port, optimize_for_latency, backlog
+ end
+ return
+ end
+
host = host[1..-2] if host and host[0..0] == '['
s = TCPServer.new(host, port)
if optimize_for_latency
@@ -237,10 +277,14 @@ module Puma
end
s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
s.listen backlog
+ @connected_port = s.addr[1]
+
@ios << s
s
end
+ attr_reader :connected_port
+
def inherit_tcp_listener(host, port, fd)
if fd.kind_of? TCPServer
s = fd
@@ -258,6 +302,13 @@ module Puma
MiniSSL.check
+ if host == "localhost"
+ loopback_addresses.each do |addr|
+ add_ssl_listener addr, port, ctx, optimize_for_latency, backlog
+ end
+ return
+ end
+
host = host[1..-2] if host[0..0] == '['
s = TCPServer.new(host, port)
if optimize_for_latency
@@ -266,6 +317,7 @@ module Puma
s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
s.listen backlog
+
ssl = MiniSSL::Server.new s, ctx
env = @proto_env.dup
env[HTTPS_KEY] = HTTPS
@@ -275,11 +327,15 @@ module Puma
s
end
- def inherited_ssl_listener(fd, ctx)
+ def inherit_ssl_listener(fd, ctx)
require 'puma/minissl'
MiniSSL.check
- s = TCPServer.for_fd(fd)
+ if fd.kind_of? TCPServer
+ s = fd
+ else
+ s = TCPServer.for_fd(fd)
+ end
ssl = MiniSSL::Server.new(s, ctx)
env = @proto_env.dup
@@ -293,7 +349,7 @@ module Puma
# Tell the server to listen on +path+ as a UNIX domain socket.
#
- def add_unix_listener(path, umask=nil, mode=nil)
+ def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
@unix_paths << path
# Let anyone connect by default
@@ -314,6 +370,7 @@ module Puma
end
s = UNIXServer.new(path)
+ s.listen backlog
@ios << s
ensure
File.umask old_mask
diff --git a/lib/puma/dsl.rb b/lib/puma/dsl.rb
index 7235fd1b..8f78afba 100644
--- a/lib/puma/dsl.rb
+++ b/lib/puma/dsl.rb
@@ -1,23 +1,72 @@
module Puma
# The methods that are available for use inside the config file.
+ # These same methods are used in Puma cli and the rack handler
+ # internally.
#
+ # Used manually (via CLI class):
+ #
+ # config = Configuration.new({}) do |user_config|
+ # user_config.port 3001
+ # end
+ # config.load
+ #
+ # puts config.options[:binds]
+ # "tcp://127.0.0.1:3001"
+ #
+ # Used to load file:
+ #
+ # $ cat puma_config.rb
+ # port 3002
+ #
+ # config = Configuration.new(config_file: "puma_config.rb")
+ # config.load
+ #
+ # puts config.options[:binds]
+ # # => "tcp://127.0.0.1:3002"
+ #
+ # Detailed docs can be found in `examples/config.rb`
class DSL
include ConfigDefault
- def self.load(options, path)
- new(options).tap do |obj|
- obj._load_from(path)
+ def initialize(options, config)
+ @config = config
+ @options = options
+
+ @plugins = []
+ end
+
+ def _load_from(path)
+ if path
+ @path = path
+ instance_eval(File.read(path), path, 1)
+ end
+ ensure
+ _offer_plugins
+ end
+
+ def _offer_plugins
+ @plugins.each do |o|
+ if o.respond_to? :config
+ @options.shift
+ o.config self
+ end
end
- options
+ @plugins.clear
end
- def initialize(options)
- @options = options
+ def inject(&blk)
+ instance_eval(&blk)
end
- def _load_from(path)
- instance_eval(File.read(path), path, 1) if path
+ def get(key,default=nil)
+ @options[key.to_sym] || default
+ end
+
+ # Load the named plugin for use by this configuration
+ #
+ def plugin(name)
+ @plugins << @config.load_plugin(name)
end
# Use +obj+ or +block+ as the Rack app. This allows a config file to
@@ -34,29 +83,76 @@ module Puma
# Start the Puma control rack app on +url+. This app can be communicated
# with to control the main server.
#
- def activate_control_app(url="auto", opts=nil)
- @options[:control_url] = url
+ def activate_control_app(url="auto", opts={})
+ if url == "auto"
+ path = Configuration.temp_path
+ @options[:control_url] = "unix://#{path}"
+ @options[:control_url_temp] = path
+ else
+ @options[:control_url] = url
+ end
- if opts
+ if opts[:no_token]
+ auth_token = :none
+ else
auth_token = opts[:auth_token]
- @options[:control_auth_token] = auth_token if auth_token
-
- @options[:control_auth_token] = :none if opts[:no_token]
- @options[:control_url_umask] = opts[:umask] if opts[:umask]
+ auth_token ||= Configuration.random_token
end
+
+ @options[:control_auth_token] = auth_token
+ @options[:control_url_umask] = opts[:umask] if opts[:umask]
+ end
+
+ # Load additional configuration from a file
+ # Files get loaded later via Configuration#load
+ def load(file)
+ @options[:config_files] ||= []
+ @options[:config_files] << file
end
- # Bind the server to +url+. tcp:// and unix:// are the only accepted
- # protocols.
+ # Adds a binding for the server to +url+. tcp://, unix://, and ssl:// are the only accepted
+ # protocols. Use query parameters within the url to specify options.
+ #
+ # @note multiple urls can be bound to, calling `bind` does not overwrite previous bindings.
+ #
+ # @example Explicitly the socket backlog depth (default is 1024)
+ # bind('unix:///var/run/puma.sock?backlog=2048')
#
+ # @example Set up ssl cert
+ # bind('ssl://127.0.0.1:9292?key=key.key&cert=cert.pem')
+ #
+ # @example Prefer low-latency over higher throughput (via `Socket::TCP_NODELAY`)
+ # bind('tcp://0.0.0.0:9292?low_latency=true')
+ #
+ # @example Set socket permissions
+ # bind('unix:///var/run/puma.sock?umask=0111')
def bind(url)
+ @options[:binds] ||= []
@options[:binds] << url
end
+ def clear_binds!
+ @options[:binds] = []
+ end
+
# Define the TCP port to bind to. Use +bind+ for more advanced options.
#
- def port(port)
- @options[:binds] << "tcp://#{Configuration::DefaultTCPHost}:#{port}"
+ def port(port, host=nil)
+ host ||= Configuration::DefaultTCPHost
+ bind "tcp://#{host}:#{port}"
+ end
+
+ # Define how long persistent connections can be idle before puma closes
+ # them
+ #
+ def persistent_timeout(seconds)
+ @options[:persistent_timeout] = Integer(seconds)
+ end
+
+ # Define how long the tcp socket stays open, if no data has been received
+ #
+ def first_data_timeout(seconds)
+ @options[:first_data_timeout] = Integer(seconds)
end
# Work around leaky apps that leave garbage in Thread locals
@@ -73,7 +169,7 @@ module Puma
end
# When shutting down, drain the accept socket of pending
- # connections and proces them. This loops over the accept
+ # connections and process them. This loops over the accept
# socket until there are no more read events and then stops
# looking and waits for the requests to finish.
def drain_on_shutdown(which=true)
@@ -85,12 +181,33 @@ module Puma
@options[:environment] = environment
end
+ # How long to wait for threads to stop when shutting them
+ # down. Defaults to :forever. Specifying :immediately will cause
+ # Puma to kill the threads immediately. Otherwise the value
+ # is the number of seconds to wait.
+ #
+ # Puma always waits a few seconds after killing a thread for it to try
+ # to finish up it's work, even in :immediately mode.
+ def force_shutdown_after(val=:forever)
+ i = case val
+ when :forever
+ -1
+ when :immediately
+ 0
+ else
+ Integer(val)
+ end
+
+ @options[:force_shutdown_after] = i
+ end
+
# Code to run before doing a restart. This code should
# close logfiles, database connections, etc.
#
# This can be called multiple times to add code each time.
#
def on_restart(&block)
+ @options[:on_restart] ||= []
@options[:on_restart] << block
end
@@ -99,18 +216,30 @@ module Puma
# to puma, as those are the same as the original process.
#
def restart_command(cmd)
- @options[:restart_cmd] = cmd
+ @options[:restart_cmd] = cmd.to_s
end
# Store the pid of the server in the file at +path+.
def pidfile(path)
- @options[:pidfile] = path
+ @options[:pidfile] = path.to_s
end
# Disable request logging.
#
- def quiet
- @options[:quiet] = true
+ def quiet(which=true)
+ @options[:log_requests] = !which
+ end
+
+ # Enable request logging
+ #
+ def log_requests(which=true)
+ @options[:log_requests] = which
+ end
+
+ # Show debugging info
+ #
+ def debug
+ @options[:debug] = true
end
# Load +path+ as a rackup file.
@@ -119,6 +248,16 @@ module Puma
@options[:rackup] = path.to_s
end
+ # Run Puma in TCP mode
+ #
+ def tcp_mode!
+ @options[:mode] = :tcp
+ end
+
+ def early_hints(answer=true)
+ @options[:early_hints] = answer
+ end
+
# Redirect STDOUT and STDERR to files specified.
def stdout_redirect(stdout=nil, stderr=nil, append=false)
@options[:redirect_stdout] = stdout
@@ -136,16 +275,23 @@ module Puma
raise "The minimum (#{min}) number of threads must be less than or equal to the max (#{max})"
end
+ if max < 1
+ raise "The maximum number of threads (#{max}) must be greater than 0"
+ end
+
@options[:min_threads] = min
@options[:max_threads] = max
end
def ssl_bind(host, port, opts)
+ verify = opts.fetch(:verify_mode, 'none')
+ no_tlsv1 = opts.fetch(:no_tlsv1, 'false')
+
if defined?(JRUBY_VERSION)
keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
- @options[:binds] << "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}"
+ bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}"
else
- @options[:binds] << "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}"
+ bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}"
end
end
@@ -172,9 +318,20 @@ module Puma
# This can be called multiple times to add hooks.
#
def before_fork(&block)
+ @options[:before_fork] ||= []
@options[:before_fork] << block
end
+ # *Cluster mode only* Code to run in a worker when it boots to setup
+ # the process before booting the app.
+ #
+ # This can be called multiple times to add hooks.
+ #
+ def on_worker_boot(&block)
+ @options[:before_worker_boot] ||= []
+ @options[:before_worker_boot] << block
+ end
+
# *Cluster mode only* Code to run immediately before a worker shuts
# down (after it has finished processing HTTP requests). These hooks
# can block if necessary to wait for background operations unknown
@@ -183,40 +340,41 @@ module Puma
# This can be called multiple times to add hooks.
#
def on_worker_shutdown(&block)
+ @options[:before_worker_shutdown] ||= []
@options[:before_worker_shutdown] << block
end
- # *Cluster mode only* Code to run when a worker boots to setup
- # the process before booting the app.
- #
- # This can be called multiple times to add hooks.
- #
- def on_worker_boot(&block)
- @options[:before_worker_boot] << block
- end
-
- # *Cluster mode only* Code to run when a master process is
+ # *Cluster mode only* Code to run in the master when it is
# about to create the worker by forking itself.
#
# This can be called multiple times to add hooks.
#
def on_worker_fork(&block)
+ @options[:before_worker_fork] ||= []
@options[:before_worker_fork] << block
end
- # *Cluster mode only* Code to run when a worker boots to setup
- # the process after booting the app.
+ # *Cluster mode only* Code to run in the master after it starts
+ # a worker.
#
# This can be called multiple times to add hooks.
#
- def after_worker_boot(&block)
- @options[:after_worker_boot] << block
+ def after_worker_fork(&block)
+ @options[:after_worker_fork] ||= []
+ @options[:after_worker_fork] = block
end
+ alias_method :after_worker_boot, :after_worker_fork
+
# The directory to operate out of.
def directory(dir)
@options[:directory] = dir.to_s
- @options[:worker_directory] = dir.to_s
+ end
+
+ # DEPRECATED: The directory to operate out of.
+ def worker_directory(dir)
+ $stderr.puts "worker_directory is deprecated. Please use `directory`"
+ directory dir
end
# Run the app as a raw TCP app instead of an HTTP rack app
@@ -259,7 +417,7 @@ module Puma
# Additional text to display in process listing
def tag(string)
- @options[:tag] = string
+ @options[:tag] = string.to_s
end
# *Cluster mode only* Set the timeout for workers in seconds
@@ -267,17 +425,17 @@ module Puma
# that have not checked in within the given +timeout+.
# This mitigates hung processes. Default value is 60 seconds.
def worker_timeout(timeout)
- @options[:worker_timeout] = timeout
+ @options[:worker_timeout] = Integer(timeout)
end
# *Cluster mode only* Set the timeout for workers to boot
def worker_boot_timeout(timeout)
- @options[:worker_boot_timeout] = timeout
+ @options[:worker_boot_timeout] = Integer(timeout)
end
# *Cluster mode only* Set the timeout for worker shutdown
def worker_shutdown_timeout(timeout)
- @options[:worker_shutdown_timeout] = timeout
+ @options[:worker_shutdown_timeout] = Integer(timeout)
end
# When set to true (the default), workers accept all requests
@@ -344,5 +502,6 @@ module Puma
raise "Invalid value for set_remote_address - #{val}"
end
end
+
end
end
diff --git a/lib/puma/minissl.rb b/lib/puma/minissl.rb
index 03bee97f..364005f9 100644
--- a/lib/puma/minissl.rb
+++ b/lib/puma/minissl.rb
@@ -1,3 +1,8 @@
+begin
+ require 'io/wait'
+ rescue LoadError
+end
+
module Puma
module MiniSSL
class Socket
@@ -11,6 +16,10 @@ module Puma
@socket
end
+ def closed?
+ @socket.closed?
+ end
+
def readpartial(size)
while true
output = @engine.read
@@ -36,12 +45,29 @@ module Puma
output
end
- def read_nonblock(size)
+ def read_nonblock(size, *_)
+ # *_ is to deal with keyword args that were added
+ # at some point (and being used in the wild)
while true
output = engine_read_all
return output if output
- data = @socket.read_nonblock(size)
+ begin
+ data = @socket.read_nonblock(size, exception: false)
+ if data == :wait_readable || data == :wait_writable
+ if @socket.to_io.respond_to?(data)
+ @socket.to_io.__send__(data)
+ elsif data == :wait_readable
+ IO.select([@socket.to_io])
+ else
+ IO.select(nil, [@socket.to_io])
+ end
+ elsif !data
+ return nil
+ else
+ break
+ end
+ end while true
@engine.inject(data)
output = engine_read_all
@@ -55,6 +81,8 @@ module Puma
end
def write(data)
+ return 0 if data.empty?
+
need = data.bytesize
while true
@@ -75,13 +103,52 @@ module Puma
end
alias_method :syswrite, :write
+ alias_method :<<, :write
+
+ # This is a temporary fix to deal with websockets code using
+ # write_nonblock. The problem with implementing it properly
+ # is that it means we'd have to have the ability to rewind
+ # an engine because after we write+extract, the socket
+ # write_nonblock call might raise an exception and later
+ # code would pass the same data in, but the engine would think
+ # it had already written the data in. So for the time being
+ # (and since write blocking is quite rare), go ahead and actually
+ # block in write_nonblock.
+ def write_nonblock(data, *_)
+ write data
+ end
def flush
@socket.flush
end
+ def read_and_drop(timeout = 1)
+ return :timeout unless IO.select([@socket], nil, nil, timeout)
+ return :eof unless read_nonblock(1024)
+ :drop
+ rescue Errno::EAGAIN
+ # do nothing
+ :eagain
+ end
+
+ def should_drop_bytes?
+ @engine.init? || !@engine.shutdown
+ end
+
def close
- @socket.close
+ begin
+ # Read any drop any partially initialized sockets and any received bytes during shutdown.
+ # Don't let this socket hold this loop forever.
+ # If it can't send more packets within 1s, then give up.
+ while should_drop_bytes?
+ return if [:timeout, :eof].include?(read_and_drop(1))
+ end
+ rescue IOError, SystemCallError
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
+ # nothing
+ ensure
+ @socket.close
+ end
end
def peeraddr
@@ -108,21 +175,33 @@ module Puma
class Context
attr_accessor :verify_mode
+ attr_reader :no_tlsv1
+
+ def initialize
+ @no_tlsv1 = false
+ end
if defined?(JRUBY_VERSION)
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
attr_reader :keystore
attr_accessor :keystore_pass
+ attr_accessor :ssl_cipher_list
def keystore=(keystore)
raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
@keystore = keystore
end
+
+ def check
+ raise "Keystore not configured" unless @keystore
+ end
+
else
# non-jruby Context properties
attr_reader :key
attr_reader :cert
attr_reader :ca
+ attr_accessor :ssl_cipher_filter
def key=(key)
raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
@@ -138,7 +217,19 @@ module Puma
raise ArgumentError, "No such ca file '#{ca}'" unless File.exist? ca
@ca = ca
end
+
+
+ def check
+ raise "Key not configured" unless @key
+ raise "Cert not configured" unless @cert
+ end
end
+
+ def no_tlsv1=(tlsv1)
+ raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
+ @no_tlsv1 = tlsv1
+ end
+
end
VERIFY_NONE = 0
@@ -156,6 +247,7 @@ module Puma
end
def accept
+ @ctx.check
io = @socket.accept
engine = Engine.server @ctx
@@ -163,6 +255,7 @@ module Puma
end
def accept_nonblock
+ @ctx.check
io = @socket.accept_nonblock
engine = Engine.server @ctx
@@ -170,7 +263,7 @@ module Puma
end
def close
- @socket.close
+ @socket.close unless @socket.closed? # closed? call is for Windows
end
end
end
diff --git a/test/test_binder.rb b/test/test_binder.rb
new file mode 100644
index 00000000..f03f8cc5
--- /dev/null
+++ b/test/test_binder.rb
@@ -0,0 +1,101 @@
+require_relative "helper"
+
+require "puma/binder"
+require "puma/puma_http11"
+
+class TestBinder < Minitest::Test
+ def setup
+ @events = Puma::Events.null
+ @binder = Puma::Binder.new(@events)
+ end
+
+ def test_localhost_addresses_dont_alter_listeners_for_tcp_addresses
+ skip_on_jruby
+
+ @binder.parse(["tcp://localhost:10001"], @events)
+
+ assert_equal [], @binder.listeners
+ end
+
+ def test_localhost_addresses_dont_alter_listeners_for_ssl_addresses
+ skip_on_appveyor
+ skip_on_jruby
+
+ key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__
+ cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__
+
+ @binder.parse(["ssl://localhost:10002?key=#{key}&cert=#{cert}"], @events)
+
+ assert_equal [], @binder.listeners
+ end
+
+ def test_binder_parses_ssl_cipher_filter
+ skip_on_appveyor
+ skip_on_jruby
+
+ key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__
+ cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__
+ ssl_cipher_filter = "AES@STRENGTH"
+
+ @binder.parse(["ssl://0.0.0.0?key=#{key}&cert=#{cert}&ssl_cipher_filter=#{ssl_cipher_filter}"], @events)
+
+ ssl = @binder.instance_variable_get(:@ios)[0]
+ ctx = ssl.instance_variable_get(:@ctx)
+ assert_equal(ssl_cipher_filter, ctx.ssl_cipher_filter)
+ end
+
+ def test_binder_parses_jruby_ssl_options
+ skip unless Puma.jruby?
+
+ keystore = File.expand_path "../../examples/puma/keystore.jks", __FILE__
+ ssl_cipher_list = "TLS_DHE_RSA_WITH_DES_CBC_SHA,TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA"
+ @binder.parse(["ssl://0.0.0.0?keystore=#{keystore}&ssl_cipher_list=#{ssl_cipher_list}"], @events)
+
+ ssl= @binder.instance_variable_get(:@ios)[0]
+ ctx = ssl.instance_variable_get(:@ctx)
+ assert_equal(keystore, ctx.keystore)
+ assert_equal(ssl_cipher_list, ctx.ssl_cipher_list)
+ end
+
+ def test_binder_parses_tlsv1_disabled
+ skip_on_appveyor
+ skip_on_jruby
+
+ key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__
+ cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__
+
+ @binder.parse(["ssl://0.0.0.0?key=#{key}&cert=#{cert}&no_tlsv1=true"], @events)
+
+ ssl = @binder.instance_variable_get(:@ios).first
+ ctx = ssl.instance_variable_get(:@ctx)
+ assert_equal(true, ctx.no_tlsv1)
+ end
+
+ def test_binder_parses_tlsv1_enabled
+ skip_on_appveyor
+ skip_on_jruby
+
+ key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__
+ cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__
+
+ @binder.parse(["ssl://0.0.0.0?key=#{key}&cert=#{cert}&no_tlsv1=false"], @events)
+
+ ssl = @binder.instance_variable_get(:@ios).first
+ ctx = ssl.instance_variable_get(:@ctx)
+ refute(ctx.no_tlsv1)
+ end
+
+ def test_binder_parses_tlsv1_unspecified_defaults_to_enabled
+ skip_on_appveyor
+ skip_on_jruby
+
+ key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__
+ cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__
+
+ @binder.parse(["ssl://0.0.0.0?key=#{key}&cert=#{cert}"], @events)
+
+ ssl = @binder.instance_variable_get(:@ios).first
+ ctx = ssl.instance_variable_get(:@ctx)
+ refute(ctx.no_tlsv1)
+ end
+end
--
2.26.2