diff -Nru ruby-passenger-4.0.35/build/integration_tests.rb ruby-passenger-4.0.37/build/integration_tests.rb --- ruby-passenger-4.0.35/build/integration_tests.rb 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/build/integration_tests.rb 2013-10-26 22:00:00.000000000 +0000 @@ -59,7 +59,11 @@ require 'shellwords' command << " -e #{Shellwords.escape(grep)}" end - sh "cd test && exec #{command}" + repeat = true + while repeat + sh "cd test && exec #{command}" + repeat = boolean_option('REPEAT') + end end end diff -Nru ruby-passenger-4.0.35/build/packaging.rb ruby-passenger-4.0.37/build/packaging.rb --- ruby-passenger-4.0.35/build/packaging.rb 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/build/packaging.rb 2013-10-26 22:00:00.000000000 +0000 @@ -136,11 +136,13 @@ abort "*** ERROR: Please 'brew install hub' first" end - if boolean_option('HOMEBREW_UPDATE', true) - puts "Updating Homebrew formula..." - Rake::Task['package:update_homebrew'].invoke - else - puts "HOMEBREW_UPDATE set to false, not updating Homebrew formula." + if is_open_source? + if boolean_option('HOMEBREW_UPDATE', true) + puts "Updating Homebrew formula..." + Rake::Task['package:update_homebrew'].invoke + else + puts "HOMEBREW_UPDATE set to false, not updating Homebrew formula." + end end sh "git tag -s #{git_tag} -u 0A212A8C -m 'Release #{version}'" @@ -178,11 +180,13 @@ puts "Building OS X binaries..." Rake::Task['package:build_osx_binaries'].invoke - if boolean_option('HOMEBREW_DRY_RUN', false) - echo "HOMEBREW_DRY_RUN set, not submitting pull request. Please find the repo in /tmp/homebrew." - else - echo "Submitting Homebrew pull request..." - sh "cd #{homebrew_dir} && hub pull-request 'Update passenger to version #{version}' -b Homebrew:master" + if boolean_option('HOMEBREW_UPDATE', true) + if boolean_option('HOMEBREW_DRY_RUN', false) + puts "HOMEBREW_DRY_RUN set, not submitting pull request. Please find the repo in /tmp/homebrew." + else + puts "Submitting Homebrew pull request..." + sh "cd #{homebrew_dir} && hub pull-request 'Update passenger to version #{version}' -b Homebrew:master" + end end puts "--------------" @@ -208,7 +212,8 @@ puts "Initiating building of binaries" command = "cd /srv/passenger_autobuilder/app && " + - "/tools/silence-unless-failed chpst -l /tmp/passenger_autobuilder.lock " + + "/tools/silence-unless-failed -f /tmp/passenger_autobuilder.log " + + "chpst -l /var/cache/passenger_ci/lock " + "./autobuild-with-pbuilder #{enterprise_git_url} passenger-enterprise --tag=#{git_tag}" sh "ssh psg_autobuilder_run@juvia-helper.phusion.nl at now <<<'#{command}'" diff -Nru ruby-passenger-4.0.35/build/test_basics.rb ruby-passenger-4.0.37/build/test_basics.rb --- ruby-passenger-4.0.35/build/test_basics.rb 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/build/test_basics.rb 2013-10-26 22:00:00.000000000 +0000 @@ -1,5 +1,5 @@ # Phusion Passenger - https://www.phusionpassenger.com/ -# Copyright (c) 2010-2013 Phusion +# Copyright (c) 2010-2014 Phusion # # "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. # @@ -77,6 +77,6 @@ end end if boolean_option('NODE_MODULES', default) - sh "npm install mocha should sinon" + sh "npm install mocha should sinon express" end end diff -Nru ruby-passenger-4.0.35/debian/changelog ruby-passenger-4.0.37/debian/changelog --- ruby-passenger-4.0.35/debian/changelog 2014-01-16 15:42:56.000000000 +0000 +++ ruby-passenger-4.0.37/debian/changelog 2014-03-08 18:35:24.000000000 +0000 @@ -1,3 +1,27 @@ +ruby-passenger (4.0.37-2) unstable; urgency=medium + + * Cherry-pick upstream commit to fix CVE-2014-1832. + The fix for CVE-2014-1831 was incomplete. + - Add CVE-2014-1832.patch + + -- Felix Geyer Sat, 08 Mar 2014 19:27:27 +0100 + +ruby-passenger (4.0.37-1) unstable; urgency=medium + + * New upstream release. + - Fixes CVE-2014-1831: insecure use of /tmp. (Closes: #736958) + + [ Cédric Boutillier ] + * Move upstream GPG key into debian/upstream. + + [ Felix Geyer ] + * Make sure the build flags are used for all source files. + - Export CFLAGS and CPPFLAGS as EXTRA_CFLAGS. + * Don't mention nginx in the package description as it isn't actually + supported in this package. + + -- Felix Geyer Sat, 08 Mar 2014 18:15:49 +0100 + ruby-passenger (4.0.35-1) unstable; urgency=low * Team upload diff -Nru ruby-passenger-4.0.35/debian/control ruby-passenger-4.0.37/debian/control --- ruby-passenger-4.0.35/debian/control 2014-01-16 11:45:41.000000000 +0000 +++ ruby-passenger-4.0.37/debian/control 2014-03-08 16:51:31.000000000 +0000 @@ -20,7 +20,7 @@ Suggests: python, rails, ruby-passenger-doc Breaks: libapache2-mod-passenger (<< 3.0.11debian) Replaces: libapache2-mod-passenger (<< 3.0.11debian) -Description: Rails and Rack support for Apache2 and Nginx +Description: Rails and Rack support Phusion Passenger — a.k.a. mod_rails or mod_rack — makes deployment of Ruby web applications, such as those built on the revolutionary Ruby on Rails web framework, a breeze. diff -Nru ruby-passenger-4.0.35/debian/patches/CVE-2014-1832.patch ruby-passenger-4.0.37/debian/patches/CVE-2014-1832.patch --- ruby-passenger-4.0.35/debian/patches/CVE-2014-1832.patch 1970-01-01 00:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/debian/patches/CVE-2014-1832.patch 2014-03-08 18:29:18.000000000 +0000 @@ -0,0 +1,154 @@ +From 94428057c602da3d6d34ef75c78091066ecac5c0 Mon Sep 17 00:00:00 2001 +From: "Hongli Lai (Phusion)" +Date: Wed, 29 Jan 2014 14:19:25 +0100 +Subject: [PATCH] Fix a symlink-related security vulnerability. + +The fix in commit 34b10878 and contained a small attack time window in +between two filesystem operations. This has been fixed. +--- + ext/common/ServerInstanceDir.h | 38 ++++++++++++++++++++++---------------- + ext/common/Utils.cpp | 29 ----------------------------- + ext/common/Utils.h | 6 ------ + 4 files changed, 40 insertions(+), 51 deletions(-) + +diff --git a/ext/common/ServerInstanceDir.h b/ext/common/ServerInstanceDir.h +index 8da3cf3..1315de5 100644 +--- a/ext/common/ServerInstanceDir.h ++++ b/ext/common/ServerInstanceDir.h +@@ -1,6 +1,6 @@ + /* + * Phusion Passenger - https://www.phusionpassenger.com/ +- * Copyright (c) 2010-2013 Phusion ++ * Copyright (c) 2010-2014 Phusion + * + * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. + * +@@ -193,6 +193,9 @@ class ServerInstanceDir: public noncopyable { + + void initialize(const string &path, bool owner) { + TRACE_POINT(); ++ struct stat buf; ++ int ret; ++ + this->path = path; + this->owner = owner; + +@@ -212,18 +215,25 @@ class ServerInstanceDir: public noncopyable { + * rights though, because we want admin tools to be able to list the available + * generations no matter what user they're running as. + */ ++ ++ do { ++ ret = lstat(path.c_str(), &buf); ++ } while (ret == -1 && errno == EAGAIN); + if (owner) { +- switch (getFileTypeNoFollowSymlinks(path)) { +- case FT_NONEXISTANT: ++ if (ret == 0) { ++ if (S_ISDIR(buf.st_mode)) { ++ verifyDirectoryPermissions(path, buf); ++ } else { ++ throw RuntimeException("'" + path + "' already exists, and is not a directory"); ++ } ++ } else if (errno == ENOENT) { + createDirectory(path); +- break; +- case FT_DIRECTORY: +- verifyDirectoryPermissions(path); +- break; +- default: +- throw RuntimeException("'" + path + "' already exists, and is not a directory"); ++ } else { ++ int e = errno; ++ throw FileSystemException("Cannot lstat '" + path + "'", ++ e, path); + } +- } else if (getFileType(path) != FT_DIRECTORY) { ++ } else if (!S_ISDIR(buf.st_mode)) { + throw RuntimeException("Server instance directory '" + path + + "' does not exist"); + } +@@ -259,14 +269,10 @@ class ServerInstanceDir: public noncopyable { + * so that an attacker cannot pre-create a directory with too liberal + * permissions. + */ +- void verifyDirectoryPermissions(const string &path) { ++ void verifyDirectoryPermissions(const string &path, struct stat &buf) { + TRACE_POINT(); +- struct stat buf; + +- if (stat(path.c_str(), &buf) == -1) { +- int e = errno; +- throw FileSystemException("Cannot stat() " + path, e, path); +- } else if (buf.st_mode != (S_IFDIR | parseModeString("u=rwx,g=rx,o=rx"))) { ++ if (buf.st_mode != (S_IFDIR | parseModeString("u=rwx,g=rx,o=rx"))) { + throw RuntimeException("Tried to reuse existing server instance directory " + + path + ", but it has wrong permissions"); + } else if (buf.st_uid != geteuid() || buf.st_gid != getegid()) { +diff --git a/ext/common/Utils.cpp b/ext/common/Utils.cpp +index d1db8d6..1f3dec5 100644 +--- a/ext/common/Utils.cpp ++++ b/ext/common/Utils.cpp +@@ -143,35 +143,6 @@ + } + } + +-FileType +-getFileTypeNoFollowSymlinks(const StaticString &filename) { +- struct stat buf; +- int ret; +- +- ret = lstat(filename.c_str(), &buf); +- if (ret == 0) { +- if (S_ISREG(buf.st_mode)) { +- return FT_REGULAR; +- } else if (S_ISDIR(buf.st_mode)) { +- return FT_DIRECTORY; +- } else if (S_ISLNK(buf.st_mode)) { +- return FT_SYMLINK; +- } else { +- return FT_OTHER; +- } +- } else { +- if (errno == ENOENT) { +- return FT_NONEXISTANT; +- } else { +- int e = errno; +- string message("Cannot lstat '"); +- message.append(filename); +- message.append("'"); +- throw FileSystemException(message, e, filename); +- } +- } +-} +- + void + createFile(const string &filename, const StaticString &contents, mode_t permissions, uid_t owner, + gid_t group, bool overwrite) +diff --git a/ext/common/Utils.h b/ext/common/Utils.h +index 5cfaf92..a04e507 100644 +--- a/ext/common/Utils.h ++++ b/ext/common/Utils.h +@@ -65,8 +65,6 @@ + FT_REGULAR, + /** A directory. */ + FT_DIRECTORY, +- /** A symlink. Only returned by getFileTypeNoFollowSymlinks(), not by getFileType(). */ +- FT_SYMLINK, + /** Something else, e.g. a pipe or a socket. */ + FT_OTHER + } FileType; +@@ -123,10 +121,6 @@ bool fileExists(const StaticString &filename, CachedFileStat *cstat = 0, + */ + FileType getFileType(const StaticString &filename, CachedFileStat *cstat = 0, + unsigned int throttleRate = 0); +-/** +- * Like getFileType(), but does not follow symlinks. +- */ +-FileType getFileTypeNoFollowSymlinks(const StaticString &filename); + + /** + * Create the given file with the given contents, permissions and ownership. +-- +1.8.5.5 + diff -Nru ruby-passenger-4.0.35/debian/patches/series ruby-passenger-4.0.37/debian/patches/series --- ruby-passenger-4.0.35/debian/patches/series 2014-01-16 13:40:19.000000000 +0000 +++ ruby-passenger-4.0.37/debian/patches/series 2014-03-08 18:33:17.000000000 +0000 @@ -1,3 +1,4 @@ fix_install_path.patch no_jsoncpp.patch bin_load_path.patch +CVE-2014-1832.patch diff -Nru ruby-passenger-4.0.35/debian/rules ruby-passenger-4.0.37/debian/rules --- ruby-passenger-4.0.35/debian/rules 2014-01-16 15:41:54.000000000 +0000 +++ ruby-passenger-4.0.37/debian/rules 2014-03-08 17:00:11.000000000 +0000 @@ -2,6 +2,7 @@ #export DH_VERBOSE=1 include /usr/share/dpkg/buildflags.mk +export EXTRA_CFLAGS=$(CFLAGS) $(CPPFLAGS) export EXTRA_CXXFLAGS=$(CXXFLAGS) $(CPPFLAGS) export EXTRA_LDFLAGS=$(LDFLAGS) diff -Nru ruby-passenger-4.0.35/debian/upstream/signing-key.asc ruby-passenger-4.0.37/debian/upstream/signing-key.asc --- ruby-passenger-4.0.35/debian/upstream/signing-key.asc 1970-01-01 00:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/debian/upstream/signing-key.asc 2014-02-02 20:34:31.000000000 +0000 @@ -0,0 +1,66 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +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 +=j0QS +-----END PGP PUBLIC KEY BLOCK----- diff -Nru ruby-passenger-4.0.35/debian/upstream-signing-key.pgp ruby-passenger-4.0.37/debian/upstream-signing-key.pgp --- ruby-passenger-4.0.35/debian/upstream-signing-key.pgp 2014-01-16 11:59:22.000000000 +0000 +++ ruby-passenger-4.0.37/debian/upstream-signing-key.pgp 1970-01-01 00:00:00.000000000 +0000 @@ -1,66 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1 - -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 -=j0QS ------END PGP PUBLIC KEY BLOCK----- diff -Nru ruby-passenger-4.0.35/dev/run_travis.sh ruby-passenger-4.0.37/dev/run_travis.sh --- ruby-passenger-4.0.35/dev/run_travis.sh 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/dev/run_travis.sh 2013-10-26 22:00:00.000000000 +0000 @@ -111,6 +111,7 @@ if [[ "$TEST_NGINX" = 1 ]]; then install_base_test_deps + install_node_and_modules run ./bin/passenger-install-nginx-module --auto --prefix=/tmp/nginx --auto-download run rake test:integration:nginx fi @@ -120,6 +121,7 @@ run sudo apt-get install -y --no-install-recommends \ apache2-mpm-worker apache2-threaded-dev install_base_test_deps + install_node_and_modules run ./bin/passenger-install-apache2-module --auto #--no-update-config run rvmsudo ./bin/passenger-install-apache2-module --auto --no-compile run rake test:integration:apache2 @@ -137,6 +139,7 @@ ruby1.8 ruby1.8-dev ruby1.9.1 ruby1.9.1-dev rubygems libev-dev gdebi-core \ source-highlight install_test_deps_with_doctools + install_node_and_modules run rake debian:dev debian:dev:reinstall run rake test:integration:native_packaging SUDO=1 run env PASSENGER_LOCATION_CONFIGURATION_FILE=/usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini \ diff -Nru ruby-passenger-4.0.35/doc/Users guide Apache.html ruby-passenger-4.0.37/doc/Users guide Apache.html --- ruby-passenger-4.0.35/doc/Users guide Apache.html 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/doc/Users guide Apache.html 2013-10-26 22:00:00.000000000 +0000 @@ -1173,7 +1173,7 @@ - + @@ -3689,7 +3689,20 @@

-

Please note that this option is completely unrelated to the passenger-config restart command. That command always initiates a blocking restart, unless --rolling-restart is given.

+

If Passenger Enterprise could not rolling restart a process (let’s call it A) because it is unable to spawn a new process (let’s call it B), then Passenger Enterprise will give up trying to rolling restart that particular process A. What happens next depends on whether deployment error resistance is enabled:

+
    +
  • +

    +If deployment error resistance is disabled (the default), then Passenger Enterprise will proceed with trying to restart the remaining processes. +

    +
  • +
  • +

    +If deployment error resistance is enabled, the Passenger Enterprise will give up rolling restarting immediately. The application group will be put into Deployment Error Resistance Mode. +

    +
  • +
+

Please note that PassengerRollingRestarts is completely unrelated to the passenger-config restart-app command. That command always initiates a blocking restart, unless --rolling-restart is given.

This option may occur in the following places:

  • @@ -3716,7 +3729,7 @@

    In each place, it may be specified at most once. The default value is off.

-

8.3.12. PassengerResistDeploymentErrors <on|off>

+

8.3.12. PassengerResistDeploymentErrors <on|off>

This feature is only available in Phusion Passenger Enterprise. It was introduced in version 3.0.0. Buy Phusion Passenger Enterprise here.

Enables or disables resistance against deployment errors.

Suppose you’ve upgraded your application and you’ve issues a command to restart it (by touching restart.txt), but the application code contains an error that prevents Phusion Passenger from successfully spawning a process (e.g. a syntax error). Phusion Passenger would normally display an error message in response to this.

@@ -3734,7 +3747,7 @@
  • -It sets an internal flag so that no processes for this application will be spawned (even when the current traffic would normally result in more processes being spawned) and no processes will be idle cleaned. Processes could still be shutdown because of other events, e.g. because their memory limit have been reached. +It sets an internal flag (Deployment Error Resistance Mode) so that no processes for this application will be spawned (even when the current traffic would normally result in more processes being spawned) and no processes will be idle cleaned. Processes could still be shutdown because of other events, e.g. because their memory limit have been reached. You can see whether the flag is set by invoking passenger-status. If you see the message "Resisting deployment error" then the flag is set.

  • diff -Nru ruby-passenger-4.0.35/doc/Users guide Apache.txt ruby-passenger-4.0.37/doc/Users guide Apache.txt --- ruby-passenger-4.0.35/doc/Users guide Apache.txt 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/doc/Users guide Apache.txt 2013-10-26 22:00:00.000000000 +0000 @@ -822,7 +822,12 @@ been restarted. It is for this reason that you should not use rolling restarts in development, only in production. -Please note that this option is completely unrelated to the `passenger-config restart` command. That command always initiates a blocking restart, unless `--rolling-restart` is given. +If Passenger Enterprise could not rolling restart a process (let's call it 'A') because it is unable to spawn a new process (let's call it 'B'), then Passenger Enterprise will give up trying to rolling restart that particular process 'A'. What happens next depends on whether <> is enabled: + +- If deployment error resistance is disabled (the default), then Passenger Enterprise will proceed with trying to restart the remaining processes. +- If deployment error resistance is enabled, the Passenger Enterprise will give up rolling restarting immediately. The application group will be put into Deployment Error Resistance Mode. + +Please note that `PassengerRollingRestarts` is completely unrelated to the `passenger-config restart-app` command. That command always initiates a blocking restart, unless `--rolling-restart` is given. This option may occur in the following places: @@ -833,6 +838,7 @@ In each place, it may be specified at most once. The default value is 'off'. +[[PassengerResistDeploymentErrors]] ==== PassengerResistDeploymentErrors :version: 3.0.0 include::users_guide_snippets/enterprise_only.txt[] @@ -845,7 +851,7 @@ - It passes the request to one of the existing application processes (that belong to the previous version of the application). The visitor will not see a Phusion Passenger process spawning error message. - It logs the error to the global web server error log file. -- It sets an internal flag so that no processes for this application will be spawned (even when the current traffic would normally result in more processes being spawned) and no processes will be idle cleaned. Processes *could* still be shutdown because of other events, e.g. because their <> have been reached. +- It sets an internal flag (Deployment Error Resistance Mode) so that no processes for this application will be spawned (even when the current traffic would normally result in more processes being spawned) and no processes will be idle cleaned. Processes *could* still be shutdown because of other events, e.g. because their <> have been reached. You can see whether the flag is set by invoking `passenger-status`. If you see the message "Resisting deployment error" then the flag is set. This way, visitors will suffer minimally from deployment errors. Phusion Passenger will attempt to restart the application again next time restart.txt is touched. diff -Nru ruby-passenger-4.0.35/doc/Users guide Nginx.html ruby-passenger-4.0.37/doc/Users guide Nginx.html --- ruby-passenger-4.0.35/doc/Users guide Nginx.html 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/doc/Users guide Nginx.html 2013-10-26 22:00:00.000000000 +0000 @@ -1174,7 +1174,7 @@ - + @@ -2302,7 +2302,7 @@

    2.11. Installing as a normal Nginx module without using the installer

    -

    You can also install Phusion Passenger the way you install any other Nginx module, e.g. with --add-module. This installation mode is useful if you already have an Nginx tarball somewhere, or if you’re using Nginx Plus.

    +

    You can also install Phusion Passenger the way you install any other Nginx module, e.g. with --add-module. This installation mode is useful if you already have an Nginx tarball somewhere.

    You need to run Nginx’s configure script with --add-module=/path-to-passenger-nginx-addon-dir. The right value for /path-to-passenger-nginx-addon-dir can be obtained with the command:

    @@ -3233,8 +3233,8 @@

    8.2.2. passenger_ruby <filename>

    The passenger_ruby option allows one to specify the Ruby interpreter to use. Similarly, the passenger_python and passenger_nodejs options are for specifying the Python interpreter and Node.js commands, respectively.

    -

    In versions prior to 4.0.0, only a single Ruby version was supported for the entire Nginx instance, so passenger_ruby may only occur in the global server configuration. Also, the passenger_python option was not supported.

    -

    Since version 4.0.0, the passenger_python option was added. Also, Phusion Passenger supports multiple Ruby or Python interpreters in the same Nginx instance. And so, since version 4.0.0, this option may occur in the following places:

    +

    In versions prior to 4.0.0, only a single Ruby version was supported for the entire Nginx instance, so passenger_ruby may only occur in the global server configuration. Also, the passenger_python/passenger_nodejs options were not supported.

    +

    Since version 4.0.0, Phusion Passenger supports multiple Ruby interpreters in the same Nginx instance. And so, since version 4.0.0, this option may occur in the following places:

    • @@ -3259,7 +3259,7 @@

    The passenger_ruby in the http block - that is, the one that passenger-install-nginx-module outputs - is used for invoking certain Phusion Passenger tools that are written in Ruby, e.g. the internal helper script used by passenger_pre_start. It is okay if the value refers to a different Ruby interpreter than the one you originally installed Phusion Passenger with. You can learn more about all this in Phusion Passenger and its relationship with Ruby.

    The passenger_ruby directive in the http block is also used as the default Ruby interpreter for Ruby web apps. You don’t have to specify a passenger_ruby in the http block though, because the default is to use the first ruby command found in $PATH.

    -

    The passenger_python and passenger_nodejs options works in a similar manner, but apply to Python and Node.js instead.

    +

    The passenger_python and passenger_nodejs options work in a similar manner, but apply to Python and Node.js instead.

    You can also override passenger_ruby and other directives in specific contexts if you want to use a different interpreter for that web app. For example:

    @@ -3745,7 +3745,20 @@

    -

    Please note that this option is completely unrelated to the passenger-config restart command. That command always initiates a blocking restart, unless --rolling-restart is given.

    +

    If Passenger Enterprise could not rolling restart a process (let’s call it A) because it is unable to spawn a new process (let’s call it B), then Passenger Enterprise will give up trying to rolling restart that particular process A. What happens next depends on whether deployment error resistance is enabled:

    +
      +
    • +

      +If deployment error resistance is disabled (the default), then Passenger Enterprise will proceed with trying to restart the remaining processes. +

      +
    • +
    • +

      +If deployment error resistance is enabled, the Passenger Enterprise will give up rolling restarting immediately. The application group will be put into Deployment Error Resistance Mode. +

      +
    • +
    +

    Please note that passenger_rolling_restarts is completely unrelated to the passenger-config restart-app command. That command always initiates a blocking restart, unless --rolling-restart is given.

    This option may occur in the following places:

    • @@ -3772,7 +3785,7 @@

      In each place, it may be specified at most once. The default value is off.

    -

    8.2.14. passenger_resist_deployment_errors <on|off>

    +

    8.2.14. passenger_resist_deployment_errors <on|off>

    This feature is only available in Phusion Passenger Enterprise. It was introduced in version 3.0.0. Buy Phusion Passenger Enterprise here.

    Enables or disables resistance against deployment errors.

    Suppose you’ve upgraded your application and you’ve issues a command to restart it (by touching restart.txt), but the application code contains an error that prevents Phusion Passenger from successfully spawning a process (e.g. a syntax error). Phusion Passenger would normally display an error message in response to this.

    @@ -3790,7 +3803,7 @@
  • -It sets an internal flag so that no processes for this application will be spawned (even when the current traffic would normally result in more processes being spawned) and no processes will be idle cleaned. Processes could still be shutdown because of other events, e.g. because their memory limit have been reached. +It sets an internal flag so that no processes for this application will be spawned (even when the current traffic would normally result in more processes being spawned) and no processes will be idle cleaned. Processes could still be shutdown because of other events, e.g. because their memory limit have been reached. You can see whether the flag is set by invoking passenger-status. If you see the message "Resisting deployment error" then the flag is set.

  • diff -Nru ruby-passenger-4.0.35/doc/Users guide Nginx.txt ruby-passenger-4.0.37/doc/Users guide Nginx.txt --- ruby-passenger-4.0.35/doc/Users guide Nginx.txt 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/doc/Users guide Nginx.txt 2013-10-26 22:00:00.000000000 +0000 @@ -561,9 +561,9 @@ ==== passenger_ruby The `passenger_ruby` option allows one to specify the Ruby interpreter to use. Similarly, the `passenger_python` and `passenger_nodejs` options are for specifying the Python interpreter and Node.js commands, respectively. -In versions prior to 4.0.0, only a single Ruby version was supported for the entire Nginx instance, so `passenger_ruby` may only occur in the global server configuration. Also, the `passenger_python` option was not supported. +In versions prior to 4.0.0, only a single Ruby version was supported for the entire Nginx instance, so `passenger_ruby` may only occur in the global server configuration. Also, the `passenger_python`/`passenger_nodejs` options were not supported. -Since version 4.0.0, the `passenger_python` option was added. Also, Phusion Passenger supports multiple Ruby or Python interpreters in the same Nginx instance. And so, since version 4.0.0, this option may occur in the following places: +Since version 4.0.0, Phusion Passenger supports multiple Ruby interpreters in the same Nginx instance. And so, since version 4.0.0, this option may occur in the following places: * In the 'http' configuration block. * In a 'server' configuration block. @@ -574,7 +574,7 @@ The `passenger_ruby` directive in the `http` block is also used as the default Ruby interpreter for Ruby web apps. You don't *have* to specify a `passenger_ruby` in the `http` block though, because the default is to use the first `ruby` command found in `$PATH`. -The `passenger_python` and `passenger_nodejs` options works in a similar manner, but apply to Python and Node.js instead. +The `passenger_python` and `passenger_nodejs` options work in a similar manner, but apply to Python and Node.js instead. You can also override `passenger_ruby` and other directives in specific contexts if you want to use a different interpreter for that web app. For example: @@ -823,7 +823,12 @@ been restarted. It is for this reason that you should not use rolling restarts in development, only in production. -Please note that this option is completely unrelated to the `passenger-config restart` command. That command always initiates a blocking restart, unless `--rolling-restart` is given. +If Passenger Enterprise could not rolling restart a process (let's call it 'A') because it is unable to spawn a new process (let's call it 'B'), then Passenger Enterprise will give up trying to rolling restart that particular process 'A'. What happens next depends on whether <> is enabled: + +- If deployment error resistance is disabled (the default), then Passenger Enterprise will proceed with trying to restart the remaining processes. +- If deployment error resistance is enabled, the Passenger Enterprise will give up rolling restarting immediately. The application group will be put into Deployment Error Resistance Mode. + +Please note that `passenger_rolling_restarts` is completely unrelated to the `passenger-config restart-app` command. That command always initiates a blocking restart, unless `--rolling-restart` is given. This option may occur in the following places: @@ -834,6 +839,7 @@ In each place, it may be specified at most once. The default value is 'off'. +[[PassengerResistDeploymentErrors]] ==== passenger_resist_deployment_errors :version: 3.0.0 include::users_guide_snippets/enterprise_only.txt[] @@ -846,7 +852,7 @@ - It passes the request to one of the existing application processes (that belong to the previous version of the application). The visitor will not see a Phusion Passenger process spawning error message. - It logs the error to the global web server error log file. -- It sets an internal flag so that no processes for this application will be spawned (even when the current traffic would normally result in more processes being spawned) and no processes will be idle cleaned. Processes *could* still be shutdown because of other events, e.g. because their <> have been reached. +- It sets an internal flag so that no processes for this application will be spawned (even when the current traffic would normally result in more processes being spawned) and no processes will be idle cleaned. Processes *could* still be shutdown because of other events, e.g. because their <> have been reached. You can see whether the flag is set by invoking `passenger-status`. If you see the message "Resisting deployment error" then the flag is set. This way, visitors will suffer minimally from deployment errors. Phusion Passenger will attempt to restart the application again next time restart.txt is touched. diff -Nru ruby-passenger-4.0.35/doc/users_guide_snippets/installation.txt ruby-passenger-4.0.37/doc/users_guide_snippets/installation.txt --- ruby-passenger-4.0.35/doc/users_guide_snippets/installation.txt 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/doc/users_guide_snippets/installation.txt 2013-10-26 22:00:00.000000000 +0000 @@ -750,7 +750,7 @@ ifdef::nginx[] === Installing as a normal Nginx module without using the installer -You can also install Phusion Passenger the way you install any other Nginx module, e.g. with `--add-module`. This installation mode is useful if you already have an Nginx tarball somewhere, or if you're using link:http://nginx.com/products/[Nginx Plus]. +You can also install Phusion Passenger the way you install any other Nginx module, e.g. with `--add-module`. This installation mode is useful if you already have an Nginx tarball somewhere. You need to run Nginx's configure script with `--add-module=/path-to-passenger-nginx-addon-dir`. The right value for `/path-to-passenger-nginx-addon-dir` can be obtained with the command: @@ -773,6 +773,7 @@ After having modified the Nginx configuration file, restart Nginx. + [[nginx_init_script]] === Creating an Nginx init script diff -Nru ruby-passenger-4.0.35/doc/Users guide Standalone.html ruby-passenger-4.0.37/doc/Users guide Standalone.html --- ruby-passenger-4.0.35/doc/Users guide Standalone.html 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/doc/Users guide Standalone.html 2013-10-26 22:00:00.000000000 +0000 @@ -1128,6 +1128,8 @@ + + @@ -2277,6 +2279,249 @@ passenger start --daemonize --port 80 --user someusername
    +

    To stop Passenger Standalone, run:

    +
    +
    + +
    cd /path-to-your-webapp
    +
    +# If you use RVM, use 'rvmsudo' instead of 'sudo'
    +sudo passenger stop
    +
    +
    +
    +
    +

    5.2. Sharing the same port between multiple Passenger Standalone instances

    +

    If you have multiple applications on your server then it is desirable to have all of them listen on the same port (e.g. port 80), with the HTTP request’s host name determining which Passenger Standalone instance should handle the request. There are three ways to achieve this.

    +
      +
    1. +

      +The first way is to use the Mass deployment feature, which allows Passenger Standalone to directly host multiple applications at the same time. Please refer to that section to learn more. +

      +
    2. +
    3. +

      +The second way is to run multiple Passenger Standalone instances — one for each application — and to put all of them behind a reverse proxy or load balancer. The reverse proxy/load balancer can for example be Nginx, Apache or HAProxy. The reverse proxy/load balancer listens on port 80, determines which Passenger Standalone instance should handle the request, and forwards the request to that instance. +

      +
    4. +
    5. +

      +The third way is to use Phusion Passenger for Nginx or Phusion Passenger for Apache. These are two modes of Phusion Passenger that are distinct from the Standalone mode, which this document describes. In the Nginx and Apache modes, Phusion Passenger integrates directly into Nginx and Apache, and makes it very easy to host multiple applications directly on Nginx or Apache. +

      +

      Compared method 2 — putting Passenger Standalone behind a reverse proxy or load balancer — the Nginx or Apache modes are easier to use and require less configuration. On the other hand, the Nginx modes requires reinstalling or recompiling Nginx, while the Apache mode requires that the Phusion Passenger Apache module is installed.

      +
    6. +
    +

    The rest of this subsection describes method 2.

    +

    Step 1: starting all applications

    +

    Putting Passenger Standalone behind a reverse proxy requires three steps. First, you must start all Passenger Standalone instances that you want. Each one must be listening on a different port, because two applications can’t listen on the same port. Suppose that you have two applications, /webapps/foo and /webapps/bar. Here’s how you may start them:

    +
    +
    + +
    # Start foo on port 4000
    +cd /webapps/foo
    +passenger start --daemonize --address 127.0.0.1 --port 4000
    +
    +# Start bar on port 4010
    +cd /webapps/bar
    +passenger start --daemonize --address 127.0.0.1 --port 4010
    +
    +
    +

    Notice the fact that we pass --address 127.0.0.1, which tells Passenger Standalone to only listen for requests that originate from the local machine. This is because the reverse proxy/load balancer, not Passenger Standalone, is supposed to be responsible for receiving external HTTP requests. The reverse proxy/load balancer will be running on the same machine only, so limiting Passenger Standalone in this manner improves security.

    +

    Step 2: install and configure the reverse proxy/load balancer

    +

    The next step is to install a reverse proxy/load balancer, and to configure it to do the following:

    +
      +
    • +

      +To listen on port 80. +

      +
    • +
    • +

      +To forward requests to either foo or bar, depending on the request’s HTTP host name. +

      +
    • +
    +

    You can use any reverse proxy/load balancer you want, but we’re going to show an example using Nginx because it’s a pretty popular choice. Install Nginx as follows:

    + +++ + + + + + + + + + + + + + + + + + +

    Debian, Ubuntu

    sudo apt-get update
    sudo apt-get install nginx-extras

    Red Hat, CentOS, ScientificLinux, Amazon Linux

    Enable EPEL, then run as root:
    yum install nginx

    Mac OS X (Homebrew)

    brew install nginx

    Other operating systems

    Install Nginx from the Nginx website.

    +

    Open the Nginx configuration file:

    + +++ + + + + + + + + + + + + + + + + + +

    Debian, Ubuntu

    /etc/nginx/nginx.conf

    Red Hat, CentOS, ScientificLinux, Amazon Linux

    /etc/nginx/nginx.conf

    Mac OS X (Homebrew)

    /usr/local/etc/nginx/nginx.conf

    Other operating systems

    It depends on how you installed Nginx, but it’s usually $PREFIX/conf/nginx.conf, where $PREFIX is the prefix you installed Nginx to.

    +

    Add virtual host entries for your applications foo and bar. While making the virtual host entries, you must determine what host names foo and bar should respond to. Let’s say that foo should respond to www.foo.com and bar should respond to www.bar.com. Then the following entries will tell Nginx to listen on port 80, and to handle requests for the domains www.foo.com and www.bar.com differently.

    +
    +
    +
    http {
    +    ...
    +
    +    # These are some "magic" Nginx configuration options that aid in making
    +    # WebSockets work properly with Passenger Standalone. Please learn more
    +    # at http://nginx.org/en/docs/http/websocket.html
    +    map $http_upgrade $connection_upgrade {
    +        default upgrade;
    +        ''      close;
    +    }
    +
    +    server {
    +        listen 80;
    +        server_name www.foo.com;
    +
    +        # Tells Nginx to serve static assets from this directory.
    +        root /webapps/foo/public;
    +
    +        location / {
    +            # Tells Nginx to forward all requests for www.foo.com
    +            # to the Passenger Standalone instance listening on port 4000.
    +            proxy_pass http://127.0.0.1:4000;
    +
    +            # These are "magic" Nginx configuration options that
    +            # should be present in order to make the reverse proxying
    +            # work properly. Also contains some options that make WebSockets
    +            # work properly with Passenger Standalone. Please learn more at
    +            # http://nginx.org/en/docs/http/ngx_http_proxy_module.html
    +            proxy_http_version 1.1;
    +            proxy_set_header Host $http_host;
    +            proxy_set_header Upgrade $http_upgrade;
    +            proxy_set_header Connection $connection_upgrade;
    +            proxy_buffering off;
    +        }
    +    }
    +
    +    # We handle bar in a similar manner.
    +    server {
    +        listen 80;
    +        server_name www.bar.com;
    +
    +        root /webapps/bar/public;
    +
    +        location / {
    +            # bar is listening on port 4010 instead of 4000, we
    +            # change the URL here.
    +            proxy_pass http://127.0.0.1:4010;
    +
    +            proxy_http_version 1.1;
    +            proxy_set_header Host $http_host;
    +            proxy_set_header Upgrade $http_upgrade;
    +            proxy_set_header Connection $connection_upgrade;
    +            proxy_buffering off;
    +        }
    +    }
    +}
    +
    +
    +

    Once you’re done editing the Nginx configuration file, restart Nginx:

    + +++ + + + + + + + + + + + + + + + + + +

    Debian, Ubuntu

    sudo /etc/init.d/nginx restart

    Red Hat, CentOS, ScientificLinux, Amazon Linux

    sudo service nginx restart

    Mac OS X (Homebrew)

    1. Run sudo kill $(cat /usr/local/var/run/nginx.pid)
    + 2. You you installed the Nginx launchd plist that Homebrew provides (see brew info nginx to learn more), then you don’t have to do anything, and launchd will automatically restart Nginx. Otherwise, you have to manually start Nginx again: sudo /usr/local/bin/nginx.

    Other operating systems

    +

    It depends on how you installed Nginx, but it’s usually as follows:

    +

    1. Lookup the PID of the Nginx master process using ps aux.
    + 2. Run sudo kill <PID>
    + 3. Start Nginx again: sudo $PREFIX/sbin/nginx, where $PREFIX is the prefix you installed Nginx to.

    +
    +

    Step 3: testing

    +

    Nginx should now be listening on port 80, and should forward requests to foo and bar respectively. Let’s test it out by accessing http://www.foo.com and http://www.bar.com. But first, we need to ensure that any requests to www.foo.com and www.bar.com, that originate from the local machine, actually end up at the local host, and not at the IP address specified in the DNS records. To do this, edit /etc/hosts and add:

    +
    +
    +
    127.0.0.1  www.foo.com www.bar.com
    +
    +
    +

    Now visit http://www.foo.com and http://www.bar.com, and verify that it works.

    +

    Step 4: making all apps start at system boot

    +

    Once you restart the server, the reverse proxy/load balancer will no longer be able to serve www.foo.com or www.bar.com because the Passenger Standalone instances that host them are no longer running. You must therefore configure the system to start Passenger Standalone at system boot. Please refer to Starting Passenger Standalone at system boot for more information.

    +

    For example, you can put this in /etc/rc.local to make the system start foo and bar at system boot:

    +
    +
    + +
    # If you installed Phusion Passenger from tarball, add its `bin` directory to PATH.
    +#export PATH=/path-to-passenger/bin:$PATH
    +
    +cd /webapps/foo
    +passenger start --daemonize --port 4000 --user someusername1
    +
    +cd /webapps/bar
    +passenger start --daemonize --port 4010 --user someusername2
    +
    +
    +

    Step 5: wrapping up

    +

    Edit /etc/hosts and remove the entry that you added in step 3.

    +
    +
    +

    5.3. Installing Passenger Standalone behind Nginx

    +
    diff -Nru ruby-passenger-4.0.35/doc/Users guide Standalone.idmap.txt ruby-passenger-4.0.37/doc/Users guide Standalone.idmap.txt --- ruby-passenger-4.0.35/doc/Users guide Standalone.idmap.txt 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/doc/Users guide Standalone.idmap.txt 2013-10-26 22:00:00.000000000 +0000 @@ -80,6 +80,10 @@ 5.1. Starting Passenger Standalone at system boot => starting-passenger-standalone-at-system-boot-jpem2b +5.2. Sharing the same port between multiple Passenger Standalone instances => sharing-the-same-port-between-multiple-passenger-standalone-instances-3lcmc5 + +5.3. Installing Passenger Standalone behind Nginx => installing-passenger-standalone-behind-nginx-1xylsfk + 6. Mass deployment => mass-deployment-1xqriy4 7. Troubleshooting => troubleshooting-o7g75o diff -Nru ruby-passenger-4.0.35/doc/Users guide Standalone.txt ruby-passenger-4.0.37/doc/Users guide Standalone.txt --- ruby-passenger-4.0.35/doc/Users guide Standalone.txt 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/doc/Users guide Standalone.txt 2013-10-26 22:00:00.000000000 +0000 @@ -144,6 +144,192 @@ passenger start --daemonize --port 80 --user someusername ------------------------------------------ +To stop Passenger Standalone, run: + +[source,sh] +------------------------------------------ +cd /path-to-your-webapp + +# If you use RVM, use 'rvmsudo' instead of 'sudo' +sudo passenger stop +------------------------------------------ + + +[[sharing_port]] +=== Sharing the same port between multiple Passenger Standalone instances + +If you have multiple applications on your server then it is desirable to have all of them listen on the same port (e.g. port 80), with the HTTP request's host name determining which Passenger Standalone instance should handle the request. There are three ways to achieve this. + + 1. The first way is to use the <> feature, which allows Passenger Standalone to directly host multiple applications at the same time. Please refer to that section to learn more. + 2. The second way is to run multiple Passenger Standalone instances -- one for each application -- and to put all of them behind a *reverse proxy* or *load balancer*. The reverse proxy/load balancer can for example be Nginx, Apache or HAProxy. The reverse proxy/load balancer listens on port 80, determines which Passenger Standalone instance should handle the request, and forwards the request to that instance. + 3. The third way is to use link:Users%20guide%20Nginx.html[Phusion Passenger for Nginx] or link:Users%20guide%20Apache.html[Phusion Passenger for Apache]. These are two modes of Phusion Passenger that are distinct from the Standalone mode, which this document describes. In the Nginx and Apache modes, Phusion Passenger integrates directly into Nginx and Apache, and makes it very easy to host multiple applications directly on Nginx or Apache. ++ +Compared method 2 -- putting Passenger Standalone behind a reverse proxy or load balancer -- the Nginx or Apache modes are easier to use and require less configuration. On the other hand, the Nginx modes requires reinstalling or recompiling Nginx, while the Apache mode requires that the Phusion Passenger Apache module is installed. + +**The rest of this subsection describes method 2.** + +[float] +==== Step 1: starting all applications + +Putting Passenger Standalone behind a reverse proxy requires three steps. First, you must start all Passenger Standalone instances that you want. Each one must be listening on a different port, because two applications can't listen on the same port. Suppose that you have two applications, `/webapps/foo` and `/webapps/bar`. Here's how you may start them: + +[source,sh] +------------------------------ +# Start foo on port 4000 +cd /webapps/foo +passenger start --daemonize --address 127.0.0.1 --port 4000 + +# Start bar on port 4010 +cd /webapps/bar +passenger start --daemonize --address 127.0.0.1 --port 4010 +------------------------------ + +Notice the fact that we pass `--address 127.0.0.1`, which tells Passenger Standalone to only listen for requests that originate from the local machine. This is because the reverse proxy/load balancer, not Passenger Standalone, is supposed to be responsible for receiving external HTTP requests. The reverse proxy/load balancer will be running on the same machine only, so limiting Passenger Standalone in this manner improves security. + +[float] +==== Step 2: install and configure the reverse proxy/load balancer + +The next step is to **install a reverse proxy/load balancer**, and to **configure** it to do the following: + + * To listen on port 80. + * To forward requests to either 'foo' or 'bar', depending on the request's HTTP host name. + +You can use any reverse proxy/load balancer you want, but we're going to show an example using link:http://www.nginx.org/[Nginx] because it's a pretty popular choice. Install Nginx as follows: + +|====================================================================== +| Debian, Ubuntu | `sudo apt-get update` + +`sudo apt-get install nginx-extras` +| Red Hat, CentOS, ScientificLinux, Amazon Linux | Enable link:http://fedoraproject.org/wiki/EPEL[EPEL], then run as root: + +`yum install nginx` +| Mac OS X (Homebrew) | `brew install nginx` +| Other operating systems | Install Nginx from link:http://www.nginx.org/[the Nginx website]. +|====================================================================== + +Open the Nginx configuration file: + +|====================================================================== +| Debian, Ubuntu | `/etc/nginx/nginx.conf` +| Red Hat, CentOS, ScientificLinux, Amazon Linux | `/etc/nginx/nginx.conf` +| Mac OS X (Homebrew) | `/usr/local/etc/nginx/nginx.conf` +| Other operating systems | It depends on how you installed Nginx, but it's usually `$PREFIX/conf/nginx.conf`, where `$PREFIX` is the prefix you installed Nginx to. +|====================================================================== + +Add virtual host entries for your applications foo and bar. While making the virtual host entries, you must determine what host names foo and bar should respond to. Let's say that foo should respond to 'www.foo.com' and bar should respond to 'www.bar.com'. Then the following entries will tell Nginx to listen on port 80, and to handle requests for the domains 'www.foo.com' and 'www.bar.com' differently. + +-------------------------- +http { + ... + + # These are some "magic" Nginx configuration options that aid in making + # WebSockets work properly with Passenger Standalone. Please learn more + # at http://nginx.org/en/docs/http/websocket.html + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + server { + listen 80; + server_name www.foo.com; + + # Tells Nginx to serve static assets from this directory. + root /webapps/foo/public; + + location / { + # Tells Nginx to forward all requests for www.foo.com + # to the Passenger Standalone instance listening on port 4000. + proxy_pass http://127.0.0.1:4000; + + # These are "magic" Nginx configuration options that + # should be present in order to make the reverse proxying + # work properly. Also contains some options that make WebSockets + # work properly with Passenger Standalone. Please learn more at + # http://nginx.org/en/docs/http/ngx_http_proxy_module.html + proxy_http_version 1.1; + proxy_set_header Host $http_host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_buffering off; + } + } + + # We handle bar in a similar manner. + server { + listen 80; + server_name www.bar.com; + + root /webapps/bar/public; + + location / { + # bar is listening on port 4010 instead of 4000, we + # change the URL here. + proxy_pass http://127.0.0.1:4010; + + proxy_http_version 1.1; + proxy_set_header Host $http_host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_buffering off; + } + } +} +-------------------------- + +Once you're done editing the Nginx configuration file, restart Nginx: + +|====================================================================== +| Debian, Ubuntu | `sudo /etc/init.d/nginx restart` +| Red Hat, CentOS, ScientificLinux, Amazon Linux | `sudo service nginx restart` +| Mac OS X (Homebrew) | 1. Run `sudo kill $(cat /usr/local/var/run/nginx.pid)` + + 2. You you installed the Nginx launchd plist that Homebrew provides (see `brew info nginx` to learn more), then you don't have to do anything, and launchd will automatically restart Nginx. Otherwise, you have to manually start Nginx again: `sudo /usr/local/bin/nginx`. +| Other operating systems | It depends on how you installed Nginx, but it's usually as follows: + + 1. Lookup the PID of the Nginx master process using `ps aux`. + + 2. Run `sudo kill ` + + 3. Start Nginx again: `sudo $PREFIX/sbin/nginx`, where `$PREFIX` is the prefix you installed Nginx to. +|====================================================================== + +[float] +==== Step 3: testing + +Nginx should now be listening on port 80, and should forward requests to foo and bar respectively. Let's test it out by accessing http://www.foo.com and http://www.bar.com. But first, we need to ensure that any requests to 'www.foo.com' and 'www.bar.com', that originate from the local machine, actually end up at the local host, and not at the IP address specified in the DNS records. To do this, edit `/etc/hosts` and add: + +-------------------------------- +127.0.0.1 www.foo.com www.bar.com +-------------------------------- + +Now visit http://www.foo.com and http://www.bar.com, and verify that it works. + +[float] +==== Step 4: making all apps start at system boot + +Once you restart the server, the reverse proxy/load balancer will no longer be able to serve 'www.foo.com' or 'www.bar.com' because the Passenger Standalone instances that host them are no longer running. You must therefore configure the system to start Passenger Standalone at system boot. Please refer to <> for more information. + +For example, you can put this in `/etc/rc.local` to make the system start foo and bar at system boot: + +[source,sh] +----------------------------------- +# If you installed Phusion Passenger from tarball, add its `bin` directory to PATH. +#export PATH=/path-to-passenger/bin:$PATH + +cd /webapps/foo +passenger start --daemonize --port 4000 --user someusername1 + +cd /webapps/bar +passenger start --daemonize --port 4010 --user someusername2 +----------------------------------- + +[float] +==== Step 5: wrapping up + +Edit `/etc/hosts` and remove the entry that you added in step 3. + + +=== Installing Passenger Standalone behind Nginx + +This is described in <>. + + [[mass_deployment]] == Mass deployment diff -Nru ruby-passenger-4.0.35/ext/common/agents/HelperAgent/Main.cpp ruby-passenger-4.0.37/ext/common/agents/HelperAgent/Main.cpp --- ruby-passenger-4.0.35/ext/common/agents/HelperAgent/Main.cpp 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/agents/HelperAgent/Main.cpp 2013-10-26 22:00:00.000000000 +0000 @@ -1,6 +1,6 @@ /* * Phusion Passenger - https://www.phusionpassenger.com/ - * Copyright (c) 2010-2013 Phusion + * Copyright (c) 2010-2014 Phusion * * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. * @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -372,6 +373,8 @@ "privilege to that of user '") + username + "': cannot set user ID", e); } + + setenv("HOME", userEntry->pw_dir, 1); } void onSigquit(ev::sig &signal, int revents) { @@ -434,7 +437,7 @@ accountsDatabase->add("_passenger-status", options.adminToolStatusPassword, false, Account::INSPECT_BASIC_INFO | Account::INSPECT_SENSITIVE_INFO | Account::INSPECT_BACKTRACES | Account::INSPECT_REQUESTS | - Account::RESTART); + Account::DETACH | Account::RESTART); accountsDatabase->add("_web_server", options.exitPassword, false, Account::EXIT); messageServer = boost::make_shared( parseUnixSocketAddress(options.adminSocketAddress), accountsDatabase); @@ -551,7 +554,7 @@ poolLoop.start("Pool event loop", 0); requestLoop.start("Request event loop", 0); - + /* Wait until the watchdog closes the feedback fd (meaning it * was killed) or until we receive an exit message. */ @@ -581,6 +584,9 @@ * inaccessible. */ P_DEBUG("Watchdog seems to be killed; forcing shutdown of all subprocesses"); + // We send a SIGTERM first to allow processes to gracefully shut down. + syscalls::killpg(getpgrp(), SIGTERM); + usleep(500000); syscalls::killpg(getpgrp(), SIGKILL); _exit(2); // In case killpg() fails. } else { @@ -589,6 +595,7 @@ */ P_DEBUG("Received command to exit gracefully. " "Waiting until 5 seconds after all clients have disconnected..."); + pool->prepareForShutdown(); requestHandler->resetInactivityTime(); while (requestHandler->inactivityTime() < 5000) { syscalls::usleep(250000); diff -Nru ruby-passenger-4.0.35/ext/common/agents/HelperAgent/RequestHandler.h ruby-passenger-4.0.37/ext/common/agents/HelperAgent/RequestHandler.h --- ruby-passenger-4.0.35/ext/common/agents/HelperAgent/RequestHandler.h 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/agents/HelperAgent/RequestHandler.h 2013-10-26 22:00:00.000000000 +0000 @@ -696,8 +696,17 @@ status, (unsigned long) data.size()); client->clientOutputPipe->write(header, pos - header); + if (!client->connected()) { + return; + } client->clientOutputPipe->write(data.data(), data.size()); + if (!client->connected()) { + return; + } client->clientOutputPipe->end(); + if (!client->connected()) { + return; + } if (client->useUnionStation()) { snprintf(header, end - header, "Status: %d %s", @@ -778,8 +787,17 @@ const string header = str.str(); client->clientOutputPipe->write(header.data(), header.size()); + if (!client->connected()) { + return; + } client->clientOutputPipe->write(data.data(), data.size()); + if (!client->connected()) { + return; + } client->clientOutputPipe->end(); + if (!client->connected()) { + return; + } if (client->useUnionStation()) { client->logMessage("Status: 500 Internal Server Error"); @@ -1094,6 +1112,10 @@ void writeToClientOutputPipe(const ClientPtr &client, const StaticString &data) { bool wasCommittingToDisk = client->clientOutputPipe->isCommittingToDisk(); bool nowCommittingToDisk = !client->clientOutputPipe->write(data.data(), data.size()); + if (!client->connected()) { + // EPIPE/ECONNRESET detected. + return; + } if (!wasCommittingToDisk && nowCommittingToDisk) { RH_TRACE(client, 3, "Buffering response data to disk; temporarily stopping application socket."); client->backgroundOperations++; @@ -2010,6 +2032,7 @@ } void sessionCheckedOut_real(ClientPtr client, const SessionPtr &session, const ExceptionPtr &e) { + RH_LOG_EVENT(client, "sessionCheckedOut"); if (!client->connected()) { return; } @@ -2200,7 +2223,6 @@ data.append(" "); data.append(parser.getHeader("REQUEST_URI")); data.append(" HTTP/1.1\r\n"); - data.append("Connection: close\r\n"); for (it = parser.begin(); it != end; it++) { if (startsWith(it->first, "HTTP_") && it->first != "HTTP_CONNECTION") { @@ -2221,6 +2243,15 @@ } } + StaticString connection = parser.getHeader("HTTP_CONNECTION"); + if (connection == "upgrade" || connection == "Upgrade") { + data.append("Connection: "); + data.append(connection.data(), connection.size()); + data.append("\r\n"); + } else { + data.append("Connection: close\r\n"); + } + StaticString header = parser.getHeader("CONTENT_LENGTH"); if (!header.empty()) { data.append("Content-Length: "); diff -Nru ruby-passenger-4.0.35/ext/common/agents/LoggingAgent/Main.cpp ruby-passenger-4.0.37/ext/common/agents/LoggingAgent/Main.cpp --- ruby-passenger-4.0.35/ext/common/agents/LoggingAgent/Main.cpp 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/agents/LoggingAgent/Main.cpp 2013-10-26 22:00:00.000000000 +0000 @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -205,6 +206,8 @@ "': cannot set user ID: " << strerror(e) << " (" << e << ")"); } + + setenv("HOME", user->pw_dir, 1); } static void diff -Nru ruby-passenger-4.0.35/ext/common/agents/TempDirToucher.c ruby-passenger-4.0.37/ext/common/agents/TempDirToucher.c --- ruby-passenger-4.0.35/ext/common/agents/TempDirToucher.c 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/agents/TempDirToucher.c 2013-10-26 22:00:00.000000000 +0000 @@ -45,13 +45,17 @@ /** * When Passenger Standalone is started with --daemonize, then it will * pass --cleanup to this tool so that this tool is responsible - * for cleaning up the Standalone temp dir. + * for cleaning up the Standalone temp dir. This is because Passenger + * Standalone may be started in daemonize mode, which makes it exit asap + * in order to conserve memory. Passenger Standalone can therefore not + * be responsible for cleaning up the temp dir. */ static int shouldCleanup = 0; static int shouldDaemonize = 0; static const char *pidFile = NULL; static const char *logFile = NULL; static int terminationPipe[2]; +static sig_atomic_t shouldIgnoreNextTermSignal = 0; static void @@ -148,10 +152,19 @@ static void exitHandler(int signo) { - int ret = write(terminationPipe[1], "x", 1); - // We can't do anything about failures, so ignore - // compiler warnings about not using the result. - (void) ret; + if (shouldIgnoreNextTermSignal) { + shouldIgnoreNextTermSignal = 0; + } else { + int ret = write(terminationPipe[1], "x", 1); + // We can't do anything about failures, so ignore + // compiler warnings about not using the result. + (void) ret; + } +} + +static void +ignoreNextTermSignalHandler(int signo) { + shouldIgnoreNextTermSignal = 1; } static void @@ -163,6 +176,10 @@ sigemptyset(&action.sa_mask); sigaction(SIGINT, &action, NULL); sigaction(SIGTERM, &action, NULL); + + action.sa_handler = ignoreNextTermSignalHandler; + action.sa_flags = SA_RESTART; + sigaction(SIGHUP, &action, NULL); } static void diff -Nru ruby-passenger-4.0.35/ext/common/agents/Watchdog/Main.cpp ruby-passenger-4.0.37/ext/common/agents/Watchdog/Main.cpp --- ruby-passenger-4.0.35/ext/common/agents/Watchdog/Main.cpp 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/agents/Watchdog/Main.cpp 2013-10-26 22:00:00.000000000 +0000 @@ -304,6 +304,19 @@ } static void +killCleanupPids(const vector &cleanupPids) { + foreach (pid_t pid, cleanupPids) { + P_DEBUG("Sending SIGTERM to cleanup PID " << pid); + kill(pid, SIGTERM); + } +} + +static void +killCleanupPids(const WorkingObjectsPtr &wo) { + killCleanupPids(readCleanupPids(wo)); +} + +static void cleanupAgentsInBackground(const WorkingObjectsPtr &wo, vector &watchers, char *argv[]) { this_thread::disable_interruption di; this_thread::disable_syscall_interruption dsi; @@ -389,10 +402,8 @@ wo->serverInstanceDir->destroy(); // Notify given PIDs about our shutdown. - foreach (pid_t pid, cleanupPids) { - P_DEBUG("Sending SIGTERM to cleanup PID " << pid); - kill(pid, SIGTERM); - } + killCleanupPids(cleanupPids); + strcpy(argv[0], "PassengerWatchdog (cleaning up 6...)"); _exit(0); } catch (const std::exception &e) { @@ -418,7 +429,7 @@ } static void -forceAllAgentsShutdown(vector &watchers) { +forceAllAgentsShutdown(const WorkingObjectsPtr &wo, vector &watchers) { vector::iterator it; for (it = watchers.begin(); it != watchers.end(); it++) { @@ -428,6 +439,7 @@ for (it = watchers.begin(); it != watchers.end(); it++) { (*it)->forceShutdown(); } + killCleanupPids(wo); } static string @@ -640,7 +652,7 @@ "Watchdog startup error", e.what(), NULL); - forceAllAgentsShutdown(watchers); + forceAllAgentsShutdown(wo, watchers); exit(1); } // Allow other exceptions to propagate and crash the watchdog. @@ -657,7 +669,7 @@ "Watchdog startup error", e.what(), NULL); - forceAllAgentsShutdown(watchers); + forceAllAgentsShutdown(wo, watchers); exit(1); } // Allow other exceptions to propagate and crash the watchdog. @@ -701,6 +713,9 @@ "Watchdog startup error", e.what(), NULL); + if (wo != NULL) { + killCleanupPids(wo); + } return 1; } // Allow other exceptions to propagate and crash the watchdog. @@ -736,7 +751,7 @@ cleanupAgentsInBackground(wo, watchers, argv); } else { UPDATE_TRACE_POINT(); - forceAllAgentsShutdown(watchers); + forceAllAgentsShutdown(wo, watchers); } UPDATE_TRACE_POINT(); runHookScriptAndThrowOnError("after_watchdog_shutdown"); diff -Nru ruby-passenger-4.0.35/ext/common/ApplicationPool2/Group.h ruby-passenger-4.0.37/ext/common/ApplicationPool2/Group.h --- ruby-passenger-4.0.35/ext/common/ApplicationPool2/Group.h 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/ApplicationPool2/Group.h 2013-10-26 22:00:00.000000000 +0000 @@ -1,6 +1,6 @@ /* * Phusion Passenger - https://www.phusionpassenger.com/ - * Copyright (c) 2011-2013 Phusion + * Copyright (c) 2011-2014 Phusion * * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. * @@ -63,7 +63,8 @@ * so only access within ApplicationPool lock. */ class Group: public boost::enable_shared_from_this { -private: +// Actually private, but marked public so that unit tests can access the fields. +public: friend class Pool; friend class SuperGroup; @@ -509,6 +510,7 @@ } else if (&destination == &detachedProcesses) { assert(process->isAlive()); process->enabled = Process::DETACHED; + process->abortLongRunningConnections(); } else { P_BUG("Unknown destination list"); } diff -Nru ruby-passenger-4.0.35/ext/common/ApplicationPool2/Implementation.cpp ruby-passenger-4.0.37/ext/common/ApplicationPool2/Implementation.cpp --- ruby-passenger-4.0.35/ext/common/ApplicationPool2/Implementation.cpp 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/ApplicationPool2/Implementation.cpp 2013-10-26 22:00:00.000000000 +0000 @@ -1,6 +1,6 @@ /* * Phusion Passenger - https://www.phusionpassenger.com/ - * Copyright (c) 2011-2013 Phusion + * Copyright (c) 2011-2014 Phusion * * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. * @@ -35,6 +35,7 @@ #include #include #include +#include namespace Passenger { namespace ApplicationPool2 { @@ -1225,12 +1226,40 @@ } +PoolPtr +Process::getPool() const { + assert(getLifeStatus() != DEAD); + return getGroup()->getPool(); +} + SuperGroupPtr Process::getSuperGroup() const { assert(getLifeStatus() != DEAD); return getGroup()->getSuperGroup(); } +void +Process::sendAbortLongRunningConnectionsMessage(const string &address) { + boost::function func = boost::bind( + realSendAbortLongRunningConnectionsMessage, address); + return getPool()->nonInterruptableThreads.create_thread( + boost::bind(runAndPrintExceptions, func, false), + "Sending detached message to process " + toString(pid), + 256 * 1024); +} + +void +Process::realSendAbortLongRunningConnectionsMessage(string address) { + TRACE_POINT(); + FileDescriptor fd(connectToServer(address)); + unsigned long long timeout = 3000000; + vector args; + + UPDATE_TRACE_POINT(); + args.push_back("abort_long_running_connections"); + writeArrayMessage(fd, args, &timeout); +} + string Process::inspect() const { assert(getLifeStatus() != DEAD); diff -Nru ruby-passenger-4.0.35/ext/common/ApplicationPool2/Options.h ruby-passenger-4.0.37/ext/common/ApplicationPool2/Options.h --- ruby-passenger-4.0.35/ext/common/ApplicationPool2/Options.h 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/ApplicationPool2/Options.h 2013-10-26 22:00:00.000000000 +0000 @@ -146,7 +146,7 @@ } public: - /*********** Spawn options that should be set manually *********** + /*********** Spawn options that should be set by the caller *********** * These are the options that are relevant while spawning an application * process. These options are only used during spawning. */ @@ -297,7 +297,7 @@ bool raiseInternalError; - /*********** Per-group pool options that should be set manually *********** + /*********** Per-group pool options that should be set by the caller *********** * These options dictate how Pool will manage processes, routing, etc. within * a single Group. These options are not process-specific, only group-specific. */ @@ -348,7 +348,11 @@ /*-----------------*/ - /*********** Per-request options that should be set manually ***********/ + /*********** Per-request pool options that should be set by the caller *********** + * These options also dictate how Pool will manage processes, etc. Unlike the + * per-group options, these options are customizable on a per-request basis. + * Their effects also don't persist longer than a single request. + */ /** Current request host name. */ StaticString hostName; @@ -576,6 +580,8 @@ appendKeyValue4(vec, "analytics", analytics); appendKeyValue (vec, "group_secret", groupSecret); + + /*********************************/ } if (fields & PER_GROUP_POOL_OPTIONS) { appendKeyValue3(vec, "min_processes", minProcesses); diff -Nru ruby-passenger-4.0.35/ext/common/ApplicationPool2/Pool.h ruby-passenger-4.0.37/ext/common/ApplicationPool2/Pool.h --- ruby-passenger-4.0.35/ext/common/ApplicationPool2/Pool.h 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/ApplicationPool2/Pool.h 2013-10-26 22:00:00.000000000 +0000 @@ -1,6 +1,6 @@ /* * Phusion Passenger - https://www.phusionpassenger.com/ - * Copyright (c) 2011-2013 Phusion + * Copyright (c) 2011-2014 Phusion * * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. * @@ -149,6 +149,7 @@ enum LifeStatus { ALIVE, + PREPARED_FOR_SHUTDOWN, SHUTTING_DOWN, SHUT_DOWN } lifeStatus; @@ -953,6 +954,7 @@ } } + /** Must be called right after construction. */ void initialize() { LockGuard l(syncher); interruptableThreads.create_thread( @@ -972,10 +974,31 @@ debugSupport = boost::make_shared(); } - void destroy() { + /** Should be called right after the HelperAgent has received + * the message to exit gracefully. This will tell processes to + * abort any long-running connections, e.g. WebSocket connections, + * because the RequestHandler has to wait until all connections are + * finished before proceeding with shutdown. + */ + void prepareForShutdown() { TRACE_POINT(); ScopedLock lock(syncher); assert(lifeStatus == ALIVE); + lifeStatus = PREPARED_FOR_SHUTDOWN; + vector processes = getProcesses(false); + foreach (ProcessPtr process, processes) { + if (process->abortLongRunningConnections()) { + // Ensure that the process is not immediately respawned. + process->getGroup()->options.minProcesses = 0; + } + } + } + + /** Must be called right before destruction. */ + void destroy() { + TRACE_POINT(); + ScopedLock lock(syncher); + assert(lifeStatus == ALIVE || lifeStatus == PREPARED_FOR_SHUTDOWN); lifeStatus = SHUTTING_DOWN; @@ -1004,7 +1027,7 @@ void asyncGet(const Options &options, const GetCallback &callback, bool lockNow = true) { DynamicScopedLock lock(syncher, lockNow); - assert(lifeStatus == ALIVE); + assert(lifeStatus == ALIVE || lifeStatus == PREPARED_FOR_SHUTDOWN); verifyInvariants(); P_TRACE(2, "asyncGet(appGroupName=" << options.getAppGroupName() << ")"); vector actions; diff -Nru ruby-passenger-4.0.35/ext/common/ApplicationPool2/Process.h ruby-passenger-4.0.37/ext/common/ApplicationPool2/Process.h --- ruby-passenger-4.0.35/ext/common/ApplicationPool2/Process.h 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/ApplicationPool2/Process.h 2013-10-26 22:00:00.000000000 +0000 @@ -1,6 +1,6 @@ /* * Phusion Passenger - https://www.phusionpassenger.com/ - * Copyright (c) 2011-2013 Phusion + * Copyright (c) 2011-2014 Phusion * * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. * @@ -113,20 +113,21 @@ * ## Life time * * A Process object lives until the containing Group calls `detach(process)`, - * which indicates that it wants this Process to should down. This causes - * the Process to enter the `detached() == true` state. Processes in this - * state are stored in the `detachedProcesses` collection in the Group and - * are no longer eligible for receiving requests. They will be removed from - * the Group and destroyed when all of the following applies: - * - * 1. the OS process is gone. - * 2. `sessions == 0` + * which indicates that it wants this Process to shut down. This causes + * `signalDetached()` to be called, which may or may not send a message + * to the process. After this, the Process object is stored in the + * `detachedProcesses` collection in the Group and are no longer eligible for + * receiving requests. Once all requests on this Process have finished, + * `triggerShutdown()` will be called, which will send a message to the + * process telling it to shut down. Once the process is gone, `cleanup()` is + * called, and the Process object is removed from the collection. * * This means that a Group outlives all its Processes, a Process outlives all * its Sessions, and a Process also outlives the OS process. */ class Process: public boost::enable_shared_from_this { -private: +// Actually private, but marked public so that unit tests can access the fields. +public: friend class Group; /** A mutex to protect access to `lifeStatus`. */ @@ -147,6 +148,9 @@ /** The handle inside the associated Group's process priority queue. */ PriorityQueue::Handle pqHandle; + void sendAbortLongRunningConnectionsMessage(const string &address); + static void realSendAbortLongRunningConnectionsMessage(string address); + static bool isZombie(pid_t pid) { string filename = "/proc/" + toString(pid) + "/status"; @@ -308,6 +312,7 @@ } oobwStatus; /** Caches whether or not the OS process still exists. */ mutable bool m_osProcessExists; + bool longRunningConnectionsAborted; /** Time at which shutdown began. */ time_t shutdownStartTime; /** Collected by Pool::collectAnalytics(). */ @@ -346,6 +351,7 @@ enabled(ENABLED), oobwStatus(OOBW_NOT_ACTIVE), m_osProcessExists(true), + longRunningConnectionsAborted(false), shutdownStartTime(0) { SpawnerConfigPtr config; @@ -413,6 +419,13 @@ * @pre getLifeState() != DEAD * @post result != NULL */ + PoolPtr getPool() const; + + /** + * Thread-safe. + * @pre getLifeState() != DEAD + * @post result != NULL + */ SuperGroupPtr getSuperGroup() const; // Thread-safe. @@ -439,6 +452,22 @@ return lifeStatus; } + bool abortLongRunningConnections() { + bool sent = false; + if (!longRunningConnectionsAborted) { + SocketList::iterator it, end = sockets->end(); + for (it = sockets->begin(); it != end; it++) { + Socket *socket = &(*it); + if (socket->name == "control") { + sendAbortLongRunningConnectionsMessage(socket->address); + sent = true; + } + } + longRunningConnectionsAborted = true; + } + return sent; + } + bool canTriggerShutdown() const { return getLifeStatus() == ALIVE && sessions == 0; } diff -Nru ruby-passenger-4.0.35/ext/common/Constants.h ruby-passenger-4.0.37/ext/common/Constants.h --- ruby-passenger-4.0.35/ext/common/Constants.h 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/Constants.h 2013-10-26 22:00:00.000000000 +0000 @@ -88,7 +88,7 @@ #define NGINX_DOC_URL "http://www.modrails.com/documentation/Users%20guide%20Nginx.html" - #define PASSENGER_VERSION "4.0.35" + #define PASSENGER_VERSION "4.0.37" #define POOL_HELPER_THREAD_STACK_SIZE 262144 diff -Nru ruby-passenger-4.0.35/ext/common/MultiLibeio.cpp ruby-passenger-4.0.37/ext/common/MultiLibeio.cpp --- ruby-passenger-4.0.35/ext/common/MultiLibeio.cpp 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/MultiLibeio.cpp 2013-10-26 22:00:00.000000000 +0000 @@ -55,6 +55,10 @@ : libev(_libev), callback(_callback) { + // If this assertion fails, then in the context of RequestHandler it means + // that it was operating on a client that has already been disconnected. + // The RequestHandler code is probably missing some necessary checks on + // `client->connected()`. assert(_libev != NULL); } }; diff -Nru ruby-passenger-4.0.35/ext/common/ServerInstanceDir.h ruby-passenger-4.0.37/ext/common/ServerInstanceDir.h --- ruby-passenger-4.0.35/ext/common/ServerInstanceDir.h 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/ServerInstanceDir.h 2013-10-26 22:00:00.000000000 +0000 @@ -213,7 +213,7 @@ * generations no matter what user they're running as. */ if (owner) { - switch (getFileType(path)) { + switch (getFileTypeNoFollowSymlinks(path)) { case FT_NONEXISTANT: createDirectory(path); break; diff -Nru ruby-passenger-4.0.35/ext/common/Utils/BufferedIO.h ruby-passenger-4.0.37/ext/common/Utils/BufferedIO.h --- ruby-passenger-4.0.35/ext/common/Utils/BufferedIO.h 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/Utils/BufferedIO.h 2013-10-26 22:00:00.000000000 +0000 @@ -171,6 +171,19 @@ return output; } + /** + * Reads a line and returns the line including the newline character. Upon + * encountering EOF, the empty string is returned. + * + * The `max` parameter dictates the maximum length of the returned line. + * If the line is longer than this number of characters, then a SecurityException + * is thrown, and the BufferedIO becomes unusable (enters an undefined state). + * + * @throws SystemException + * @throws TimeoutException + * @throws SecurityException + * @throws boost::thread_interrupted + */ string readLine(unsigned int max = 1024, unsigned long long *timeout = NULL) { string output; readUntil(boost::bind(newlineFound, _1, _2, &output, max), timeout); diff -Nru ruby-passenger-4.0.35/ext/common/Utils.cpp ruby-passenger-4.0.37/ext/common/Utils.cpp --- ruby-passenger-4.0.35/ext/common/Utils.cpp 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/Utils.cpp 2013-10-26 22:00:00.000000000 +0000 @@ -143,6 +143,35 @@ } } +FileType +getFileTypeNoFollowSymlinks(const StaticString &filename) { + struct stat buf; + int ret; + + ret = lstat(filename.c_str(), &buf); + if (ret == 0) { + if (S_ISREG(buf.st_mode)) { + return FT_REGULAR; + } else if (S_ISDIR(buf.st_mode)) { + return FT_DIRECTORY; + } else if (S_ISLNK(buf.st_mode)) { + return FT_SYMLINK; + } else { + return FT_OTHER; + } + } else { + if (errno == ENOENT) { + return FT_NONEXISTANT; + } else { + int e = errno; + string message("Cannot lstat '"); + message.append(filename); + message.append("'"); + throw FileSystemException(message, e, filename); + } + } +} + void createFile(const string &filename, const StaticString &contents, mode_t permissions, uid_t owner, gid_t group, bool overwrite) diff -Nru ruby-passenger-4.0.35/ext/common/Utils.h ruby-passenger-4.0.37/ext/common/Utils.h --- ruby-passenger-4.0.35/ext/common/Utils.h 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/ext/common/Utils.h 2013-10-26 22:00:00.000000000 +0000 @@ -65,6 +65,8 @@ FT_REGULAR, /** A directory. */ FT_DIRECTORY, + /** A symlink. Only returned by getFileTypeNoFollowSymlinks(), not by getFileType(). */ + FT_SYMLINK, /** Something else, e.g. a pipe or a socket. */ FT_OTHER } FileType; @@ -110,7 +112,7 @@ /** * Check whether 'filename' exists and what kind of file it is. * - * @param filename The filename to check. + * @param filename The filename to check. It MUST be NULL-terminated. * @param mstat A CachedFileStat object, if you want to use cached statting. * @param throttleRate A throttle rate for cstat. Only applicable if cstat is not NULL. * @return The file type. @@ -121,6 +123,10 @@ */ FileType getFileType(const StaticString &filename, CachedFileStat *cstat = 0, unsigned int throttleRate = 0); +/** + * Like getFileType(), but does not follow symlinks. + */ +FileType getFileTypeNoFollowSymlinks(const StaticString &filename); /** * Create the given file with the given contents, permissions and ownership. diff -Nru ruby-passenger-4.0.35/helper-scripts/meteor-loader.rb ruby-passenger-4.0.37/helper-scripts/meteor-loader.rb --- ruby-passenger-4.0.35/helper-scripts/meteor-loader.rb 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/helper-scripts/meteor-loader.rb 2013-10-26 22:00:00.000000000 +0000 @@ -1,7 +1,7 @@ #!/usr/bin/env ruby # encoding: binary # Phusion Passenger - https://www.phusionpassenger.com/ -# Copyright (c) 2010-2013 Phusion +# Copyright (c) 2010-2014 Phusion # # "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. # @@ -24,6 +24,8 @@ # THE SOFTWARE. require 'socket' +require 'thread' +require 'logger' module PhusionPassenger module App @@ -52,6 +54,13 @@ end end + def self.init_passenger + require "#{options["ruby_libdir"]}/phusion_passenger" + PhusionPassenger.locate_directories(options["passenger_root"]) + PhusionPassenger.require_passenger_lib 'message_channel' + PhusionPassenger.require_passenger_lib 'utils/tmpio' + end + def self.ping_port(port) socket_domain = Socket::Constants::AF_INET sockaddr = Socket.pack_sockaddr_in(port, '127.0.0.1') @@ -77,7 +86,14 @@ end end - def self.load_app + def self.create_control_server + dir = Utils.mktmpdir('meteor') + filename = "#{dir}/control" + server = UNIXServer.new(filename) + return [server, dir, filename] + end + + def self.load_app(control_server) port = nil tries = 0 while port.nil? && tries < 200 @@ -101,34 +117,134 @@ # Meteor its own process group, and sending signals to the # entire process group. Process.setpgrp + control_server.close exec("meteor run -p #{port} #{production}") end $0 = options["process_title"] if options["process_title"] $0 = "#{$0} (#{pid})" return [pid, port] end + + class ExitFlag + def initialize + @mutex = Mutex.new + @cond = ConditionVariable.new + @exit = false + end + + def set + @mutex.synchronize do + @exit = true + @cond.broadcast + end + end + + def wait + @mutex.synchronize do + while !@exit + @cond.wait(@mutex) + end + end + end + end + + # When the HelperAgent is shutting down, it first sends a message (A) to application + # processes through the control socket that this is happening. The HelperAgent then + # waits until all HTTP connections are closed, before sending another message + # to application processes that they should shut down (B). + # Because Meteor opens long-running connections (e.g. for WebSocket), we have to shut + # down the Meteor app when A arrives, otherwise the HelperAgent will never send B. + def self.wait_for_exit_message(control_server) + exit_flag = ExitFlag.new + start_control_server_thread(control_server, exit_flag) + start_stdin_waiter_thread(exit_flag) + exit_flag.wait + end + + def self.start_control_server_thread(control_server, exit_flag) + Thread.new do + Thread.current.abort_on_exception = true + while true + process_next_control_client(control_server, exit_flag) + end + end + end + + def self.process_next_control_client(control_server, exit_flag) + logger = Logger.new(STDERR) + begin + client = control_server.accept + channel = MessageChannel.new(client) + while message = channel.read + process_next_control_message(message, logger, exit_flag) + end + rescue Exception => e + logger.error("#{e} (#{e.class})\n " + e.backtrace.join("\n ")) + ensure + begin + client.close if client + rescue SystemCallError, IOError, SocketError + end + end + end + + def self.process_next_control_message(message, logger, exit_flag) + if message[0] == "abort_long_running_connections" + logger.debug("Aborting long-running connections") + exit_flag.set + else + logger.error("Invalid control message: #{message.inspect}") + end + end + + def self.start_stdin_waiter_thread(exit_flag) + Thread.new do + Thread.current.abort_on_exception = true + begin + STDIN.readline + rescue EOFError + ensure + exit_flag.set + end + end + end + ################## Main code ################## handshake_and_read_startup_request - pid, port = load_app + init_passenger begin + control_server, control_dir, control_filename = create_control_server + pid, port = load_app(control_server) while !ping_port(port) sleep 0.01 end puts "!> Ready" puts "!> socket: main;tcp://127.0.0.1:#{port};http_session;0" + puts "!> socket: control;unix:#{control_filename};control;0" puts "!> " - begin - STDIN.readline - rescue EOFError - end + wait_for_exit_message(control_server) ensure - Process.kill('INT', -pid) rescue nil - Process.waitpid(pid) rescue nil - Process.kill('INT', -pid) rescue nil + if pid + Process.kill('INT', -pid) rescue nil + Process.waitpid(pid) rescue nil + Process.kill('INT', -pid) rescue nil + end + if control_server + control_server.close + begin + File.unlink(control_filename) + rescue SystemCallError + end + require 'fileutils' + begin + FileUtils.remove_entry_secure(control_dir) + rescue SystemCallError + end + end end end # module App diff -Nru ruby-passenger-4.0.35/helper-scripts/node-loader.js ruby-passenger-4.0.37/helper-scripts/node-loader.js --- ruby-passenger-4.0.35/helper-scripts/node-loader.js 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/helper-scripts/node-loader.js 2013-10-26 22:00:00.000000000 +0000 @@ -135,15 +135,17 @@ finalizeStartup(); PhusionPassenger.on('request', function(headers, socket, bodyBegin) { - var req = HttplibEmulation.createIncomingMessage(headers, socket, bodyBegin); - if (req.headers['upgrade']) { + var req, res; + if (headers['HTTP_UPGRADE']) { if (EventEmitter.listenerCount(server, 'upgrade') > 0) { + req = HttplibEmulation.createIncomingMessage(headers, socket, bodyBegin); server.emit('upgrade', req, socket, bodyBegin); } else { socket.destroy(); } } else { - var res = HttplibEmulation.createServerResponse(req); + req = HttplibEmulation.createIncomingMessage(headers, socket, bodyBegin); + res = HttplibEmulation.createServerResponse(req); server.emit('request', req, res); } }); diff -Nru ruby-passenger-4.0.35/helper-scripts/wsgi-loader.py ruby-passenger-4.0.37/helper-scripts/wsgi-loader.py --- ruby-passenger-4.0.35/helper-scripts/wsgi-loader.py 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/helper-scripts/wsgi-loader.py 2013-10-26 22:00:00.000000000 +0000 @@ -106,6 +106,7 @@ if not client: done = True break + socket_hijacked = False try: try: env, input_stream = self.parse_request(client) @@ -113,7 +114,7 @@ if env['REQUEST_METHOD'] == 'ping': self.process_ping(env, input_stream, client) else: - self.process_request(env, input_stream, client) + socket_hijacked = self.process_request(env, input_stream, client) except KeyboardInterrupt: done = True except IOError: @@ -123,16 +124,17 @@ except Exception: logging.exception("WSGI application raised an exception!") finally: - try: - # Shutdown the socket like this just in case the app - # spawned a child process that keeps it open. - client.shutdown(socket.SHUT_WR) - except: - pass - try: - client.close() - except: - pass + if not socket_hijacked: + try: + # Shutdown the socket like this just in case the app + # spawned a child process that keeps it open. + client.shutdown(socket.SHUT_WR) + except: + pass + try: + client.close() + except: + pass except KeyboardInterrupt: pass @@ -232,7 +234,16 @@ headers_set[:] = [status, response_headers] return write + def hijack(): + env['passenger.hijacked_socket'] = output_stream + return output_stream + + env['passenger.hijack'] = hijack + result = self.app(env, start_response) + if 'passenger.hijacked_socket' in env: + # Socket connection hijacked. Don't do anything. + return True try: for data in result: # Don't send headers until body appears. @@ -244,6 +255,7 @@ finally: if hasattr(result, 'close'): result.close() + return False def process_ping(self, env, input_stream, output_stream): output_stream.sendall(b"pong") diff -Nru ruby-passenger-4.0.35/lib/phusion_passenger/config/detach_process_command.rb ruby-passenger-4.0.37/lib/phusion_passenger/config/detach_process_command.rb --- ruby-passenger-4.0.35/lib/phusion_passenger/config/detach_process_command.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/lib/phusion_passenger/config/detach_process_command.rb 2013-10-26 22:00:00.000000000 +0000 @@ -0,0 +1,96 @@ +# Phusion Passenger - https://www.phusionpassenger.com/ +# Copyright (c) 2014 Phusion +# +# "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +require 'optparse' +PhusionPassenger.require_passenger_lib 'constants' +PhusionPassenger.require_passenger_lib 'admin_tools/server_instance' +PhusionPassenger.require_passenger_lib 'config/command' +PhusionPassenger.require_passenger_lib 'config/utils' + +module PhusionPassenger +module Config + +class DetachProcessCommand < Command + include PhusionPassenger::Config::Utils + + def self.description + return "Detach an application process from the process pool" + end + + def run + parse_options + select_passenger_instance + @admin_client = connect_to_passenger_admin_socket(:role => :passenger_status) + perform_detach + end + +private + def self.create_option_parser(options) + OptionParser.new do |opts| + nl = "\n" + ' ' * 37 + opts.banner = "Usage: passenger-config detach-process [OPTIONS] \n" + opts.separator "" + opts.separator " Remove an application process from the #{PROGRAM_NAME} process pool." + opts.separator " Has a similar effect to killing the application process directly with" + opts.separator " `kill `, but killing directly may cause the HTTP client to see an" + opts.separator " error, while using this command guarantees that clients see no errors." + opts.separator "" + + opts.separator "Options:" + opts.on("--instance INSTANCE_PID", Integer, "The #{PROGRAM_NAME} instance to select") do |value| + options[:instance] = value + end + opts.on("-h", "--help", "Show this help") do + options[:help] = true + end + end + end + + def help + puts @parser + end + + def parse_options + super + if @argv.empty? + abort "Please pass a PID. " + + "See --help for more information." + elsif @argv.size == 1 + @pid = @argv[0].to_i + elsif @argv.size > 1 + help + abort + end + end + + def perform_detach + if @admin_client.pool_detach_process(@pid) + puts "Process #{@pid} detached." + else + abort "Could not detach process #{@pid}." + end + end +end + +end # module Config +end # module PhusionPassenger diff -Nru ruby-passenger-4.0.35/lib/phusion_passenger/config/main.rb ruby-passenger-4.0.37/lib/phusion_passenger/config/main.rb --- ruby-passenger-4.0.35/lib/phusion_passenger/config/main.rb 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/lib/phusion_passenger/config/main.rb 2013-10-26 22:00:00.000000000 +0000 @@ -28,6 +28,7 @@ # Core of the `passenger-config` command. Dispatches a subcommand to a specific class. module Config KNOWN_COMMANDS = [ + ["detach-process", "DetachProcessCommand"], ["restart-app", "RestartAppCommand"], ["validate-install", "ValidateInstallCommand"], ["about", "AboutCommand"] diff -Nru ruby-passenger-4.0.35/lib/phusion_passenger/request_handler.rb ruby-passenger-4.0.37/lib/phusion_passenger/request_handler.rb --- ruby-passenger-4.0.35/lib/phusion_passenger/request_handler.rb 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/lib/phusion_passenger/request_handler.rb 2013-10-26 22:00:00.000000000 +0000 @@ -95,6 +95,11 @@ "analytics_logger", "pool_account_username" ) + + @force_http_session = ENV["_PASSENGER_FORCE_HTTP_SESSION"] == "true" + if @force_http_session + @connect_password = nil + end @thread_handler = options["thread_handler"] || ThreadHandler @concurrency = 1 if options["pool_account_password_base64"] @@ -114,7 +119,7 @@ @server_sockets[:main] = { :address => @main_socket_address, :socket => @main_socket, - :protocol => :session, + :protocol => @force_http_session ? :http_session : :session, :concurrency => @concurrency } @@ -125,7 +130,7 @@ :protocol => :http, :concurrency => 1 } - + @owner_pipe = owner_pipe @options = options @previous_signal_handlers = {} @@ -308,7 +313,7 @@ # is still bugged as of version 1.7.0. They can # cause unexplicable freezes when used in combination # with threading. - return ruby_engine != "jruby" + return !@force_http_session && ruby_engine != "jruby" end def create_unix_socket_on_filesystem @@ -400,7 +405,9 @@ main_socket_options = common_options.merge( :server_socket => @main_socket, :socket_name => "main socket", - :protocol => :session + :protocol => @server_sockets[:main][:protocol] == :session ? + :session : + :http ) http_socket_options = common_options.merge( :server_socket => @http_socket, diff -Nru ruby-passenger-4.0.35/lib/phusion_passenger/standalone/command.rb ruby-passenger-4.0.37/lib/phusion_passenger/standalone/command.rb --- ruby-passenger-4.0.35/lib/phusion_passenger/standalone/command.rb 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/lib/phusion_passenger/standalone/command.rb 2013-10-26 22:00:00.000000000 +0000 @@ -183,12 +183,17 @@ def write_nginx_config_file PhusionPassenger.require_passenger_lib 'platform_info/ruby' PhusionPassenger.require_passenger_lib 'utils/tmpio' - @temp_dir = PhusionPassenger::Utils.mktmpdir( + # @temp_dir may already be set because we're redeploying + # using Mass Deployment. + @temp_dir ||= PhusionPassenger::Utils.mktmpdir( "passenger-standalone.") @config_filename = "#{@temp_dir}/config" location_config_filename = "#{@temp_dir}/locations.ini" File.chmod(0755, @temp_dir) - Dir.mkdir("#{@temp_dir}/logs") + begin + Dir.mkdir("#{@temp_dir}/logs") + rescue Errno::EEXIST + end locations_ini_fields = PhusionPassenger::REQUIRED_LOCATIONS_INI_FIELDS + diff -Nru ruby-passenger-4.0.35/lib/phusion_passenger/standalone/start_command.rb ruby-passenger-4.0.37/lib/phusion_passenger/standalone/start_command.rb --- ruby-passenger-4.0.35/lib/phusion_passenger/standalone/start_command.rb 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/lib/phusion_passenger/standalone/start_command.rb 2013-10-26 22:00:00.000000000 +0000 @@ -422,11 +422,7 @@ end def should_wait_until_nginx_has_exited? - return !@options[:daemonize] - end - - def should_cleanup_temp_dir? - return @temp_dir && !@options[:daemonize] + return !@options[:daemonize] || @app_finder.multi_mode? end # Returns the URL that Nginx will be listening on. diff -Nru ruby-passenger-4.0.35/lib/phusion_passenger.rb ruby-passenger-4.0.37/lib/phusion_passenger.rb --- ruby-passenger-4.0.35/lib/phusion_passenger.rb 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/lib/phusion_passenger.rb 2013-10-26 22:00:00.000000000 +0000 @@ -30,7 +30,7 @@ PACKAGE_NAME = 'passenger' # Run 'rake ext/common/Constants.h' after changing this number. - VERSION_STRING = '4.0.35' + VERSION_STRING = '4.0.37' PREFERRED_NGINX_VERSION = '1.4.4' NGINX_SHA256_CHECKSUM = '7c989a58e5408c9593da0bebcd0e4ffc3d892d1316ba5042ddb0be5b0b4102b9' diff -Nru ruby-passenger-4.0.35/NEWS ruby-passenger-4.0.37/NEWS --- ruby-passenger-4.0.35/NEWS 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/NEWS 2013-10-26 22:00:00.000000000 +0000 @@ -1,3 +1,73 @@ +Release 4.0.37 +-------------- + + * Improved Node.js compatibility. Calling on() on the request object + now returns the request object itself. This fixes some issues with + Express, Connect and Formidable. Furthermore, some WebSocket-related + issues have been fixed. + * Improved Meteor support. Meteor application processes are now shut down + quicker. Previously, they linger around for 5 seconds while waiting for + all connections to terminate, but that didn't work well because WebSocket + connections were kept open indefinitely. Also, some WebSocket-related + issues have been fixed. + * Introduced a new tool `passenger-config detach-process` for gracefully + detaching an application process from the process pool. Has a similar + effect to killing the application process directly with `kill `, + but killing directly may cause the HTTP client to see an error, while + using this command guarantees that clients see no errors. + * Fixed a crash occurs when an application fails to spawn, but the HTTP + client disconnects before the error page is generated. Fixes issue #1028. + * Fixed a symlink-related security vulnerability. + + Urgency: low + Scope: local exploit + Summary: writing files to arbitrary directory by hijacking temp directories + Affected versions: 4.0.5 and later + Fixed versions: 4.0.37 + + Description: + Phusion Passenger creates a "server instance directory" in /tmp during startup, + which is a temporary directory that Phusion Passenger uses to store working files. + This directory is deleted after Phusion Passenger exits. For various technical + reasons, this directory must have a semi-predictable filename. If a local attacker + can predict this filename, and precreates a symlink with the same filename that + points to an arbitrary directory with mode 755, owner root and group root, then + the attacker will succeed in making Phusion Passenger write files and create + subdirectories inside that target directory. The following files/subdirectories + are created: + + * control_process.pid + * generation-X, where X is a number. + + If you happen to have a file inside the target directory called `control_process.pid`, + then that file's contents are overwritten. + + These files and directories are deleted during Phusion Passenger exit. The target + directory itself is not deleted, nor are any other contents inside the target + directory, although the symlink is. + + Thanks go to Jakub Wilk for discovering this issue. + + +Release 4.0.36 +-------------- + + * [Enterprise] Fixed some Mass Deployment bugs. + * [Enterprise] Fixed a bug that causes an application group to be put into + Deployment Error Resistance Mode if rolling restarting fails while + deployment error resistance is off. Deployment Error Resistance Mode is + now only activated if it's explicitly turned on. + * Passenger Standalone now gzips JSON responses. + * Fixed some cases in which Passenger Standalone does not to properly cleanup + its temporary files. + + +Release 4.0.35 +-------------- + + * Fixed some unit tests. + + Release 4.0.34 -------------- diff -Nru ruby-passenger-4.0.35/node_lib/phusion_passenger/httplib_emulation.js ruby-passenger-4.0.37/node_lib/phusion_passenger/httplib_emulation.js --- ruby-passenger-4.0.35/node_lib/phusion_passenger/httplib_emulation.js 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/node_lib/phusion_passenger/httplib_emulation.js 2013-10-26 22:00:00.000000000 +0000 @@ -1,6 +1,6 @@ /* * Phusion Passenger - https://www.phusionpassenger.com/ - * Copyright (c) 2013 Phusion + * Copyright (c) 2013-2014 Phusion * * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. * @@ -68,11 +68,15 @@ } } -function mayHaveRequestBody(headers) { - return headers['REQUEST_METHOD'] != 'GET' || headers['HTTP_UPGRADE']; -} - function createIncomingMessage(headers, socket, bodyBegin) { + /* Node's HTTP parser simulates an 'end' event if it determines that + * the request should not have a request body. Currently (Node 0.10.18), + * it thinks GET requests without an Upgrade header should not have a + * request body, even though technically such GET requests are allowed + * to have a request body. For compatibility reasons we implement the + * same behavior as Node's HTTP parser. + */ + var message = new http.IncomingMessage(socket); setHttpHeaders(message.headers, headers); message.cgiHeaders = headers; @@ -81,7 +85,14 @@ message.url = headers['REQUEST_URI']; message.connection.remoteAddress = headers['REMOTE_ADDR']; message.connection.remotePort = parseInt(headers['REMOTE_PORT']); - message._mayHaveRequestBody = mayHaveRequestBody(headers); + message.upgrade = !!headers['HTTP_UPGRADE']; + + if (message.upgrade) { + // Emit end event as described above. + message.push(null); + return message; + } + message._emitEndEvent = IncomingMessage_emitEndEvent; resetIncomingMessageOverridedMethods(message); @@ -95,14 +106,7 @@ message.emit('timeout'); }); - /* Node's HTTP parser simulates an 'end' event if it determines that - * the request should not have a request body. Currently (Node 0.10.18), - * it thinks GET requests without an Upgrade header should not have a - * request body, even though technically such GET requests are allowed - * to have a request body. For compatibility reasons we implement the - * same behavior as Node's HTTP parser. - */ - if (message._mayHaveRequestBody) { + if (headers['REQUEST_METHOD'] != 'GET') { if (bodyBegin.length > 0) { message.push(bodyBegin); } @@ -112,6 +116,7 @@ } } } else { + // Emit end event as described above. message.push(null); } @@ -139,6 +144,7 @@ } this._orig_on.call(this, event, listener); resetIncomingMessageOverridedMethods(this); + return this; } function IncomingMessage_emitEndEvent() { diff -Nru ruby-passenger-4.0.35/README.md ruby-passenger-4.0.37/README.md --- ruby-passenger-4.0.35/README.md 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/README.md 2013-10-26 22:00:00.000000000 +0000 @@ -4,9 +4,11 @@ What makes it so fast and reliable is its **C++** core, its **zero-copy** architecture, its **watchdog** system and its **hybrid** evented, multi-threaded and multi-process design. +
    Phusion Passenger used in Game of Thrones Ascention + **Learn more:** [Website](https://www.phusionpassenger.com/) | [Documentation](https://www.phusionpassenger.com/documentation_and_support) | [Support resources](https://www.phusionpassenger.com/documentation_and_support) | [Github](https://github.com/phusion/passenger) | [Twitter](https://twitter.com/phusion_nl) | [Blog](http://blog.phusion.nl/) -
    Phusion Passenger
    +
    Phusion Passenger
    ## Installation diff -Nru ruby-passenger-4.0.35/resources/templates/standalone/config.erb ruby-passenger-4.0.37/resources/templates/standalone/config.erb --- ruby-passenger-4.0.35/resources/templates/standalone/config.erb 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/resources/templates/standalone/config.erb 2013-10-26 22:00:00.000000000 +0000 @@ -68,7 +68,8 @@ gzip_comp_level 3; gzip_min_length 150; gzip_proxied any; - gzip_types text/plain text/css application/javascript application/x-javascript; + gzip_types text/plain text/css text/json text/javascript \ + application/javascript application/x-javascript application/json; <% if @apps.size > 1 %> # Default server entry. diff -Nru ruby-passenger-4.0.35/test/cxx/RequestHandlerTest.cpp ruby-passenger-4.0.37/test/cxx/RequestHandlerTest.cpp --- ruby-passenger-4.0.35/test/cxx/RequestHandlerTest.cpp 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/test/cxx/RequestHandlerTest.cpp 2013-10-26 22:00:00.000000000 +0000 @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -51,6 +52,7 @@ setPrintAppOutputAsDebuggingMessages(true); agentOptions.passengerRoot = resourceLocator->getRoot(); + agentOptions.defaultRubyCommand = DEFAULT_RUBY; agentOptions.defaultUser = testConfig["default_user"].asString(); agentOptions.defaultGroup = testConfig["default_group"].asString(); root = resourceLocator->getRoot(); @@ -972,6 +974,84 @@ ensure(containsSubstring(response, "Counter: 2\n")); } + TEST_METHOD(53) { + set_test_name("It supports switching protocols when communicating over application session sockets"); + + init(); + connect(); + sendHeaders(defaultHeaders, + "PASSENGER_APP_ROOT", wsgiAppPath.c_str(), + "PATH_INFO", "/switch_protocol", + "HTTP_UPGRADE", "raw", + "HTTP_CONNECTION", "Upgrade", + NULL + ); + + BufferedIO io(connection); + string header; + bool done = false; + + ensure_equals(io.readLine(), "HTTP/1.1 101 Switching Protocols\r\n"); + + do { + string line = io.readLine(); + done = line.empty() || line == "\r\n"; + if (!done) { + header.append(line); + } + } while (!done); + + ensure("(1)", containsSubstring(header, "Upgrade: raw\r\n")); + ensure("(2)", containsSubstring(header, "Connection: Upgrade\r\n")); + + writeExact(connection, "hello\n"); + ensure_equals(io.readLine(), "Echo: hello\n"); + } + + TEST_METHOD(54) { + set_test_name("It supports switching protocols when communication over application http_session sockets"); + + init(); + connect(); + sendHeaders(defaultHeaders, + "_PASSENGER_FORCE_HTTP_SESSION", "true", + "PASSENGER_APP_ROOT", rackAppPath.c_str(), + "PASSENGER_APP_TYPE", "rack", + "REQUEST_URI", "/switch_protocol", + "PATH_INFO", "/switch_protocol", + "HTTP_UPGRADE", "raw", + "HTTP_CONNECTION", "Upgrade", + NULL + ); + + BufferedIO io(connection); + string header; + bool done = false; + vector processes; + + ensure_equals(io.readLine(), "HTTP/1.1 101 Switching Protocols\r\n"); + processes = pool->getProcesses(); + { + LockGuard l(pool->syncher); + ProcessPtr process = processes[0]; + ensure_equals(process->sessionSockets.top()->protocol, "http_session"); + } + + do { + string line = io.readLine(); + done = line.empty() || line == "\r\n"; + if (!done) { + header.append(line); + } + } while (!done); + + ensure("(1)", containsSubstring(header, "Upgrade: raw\r\n")); + ensure("(2)", containsSubstring(header, "Connection: Upgrade\r\n")); + + writeExact(connection, "hello\n"); + ensure_equals(io.readLine(), "Echo: hello\n"); + } + // Test small response buffering. // Test large response buffering. } diff -Nru ruby-passenger-4.0.35/test/integration_tests/apache2_tests.rb ruby-passenger-4.0.37/test/integration_tests/apache2_tests.rb --- ruby-passenger-4.0.35/test/integration_tests/apache2_tests.rb 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/test/integration_tests/apache2_tests.rb 2013-10-26 22:00:00.000000000 +0000 @@ -158,6 +158,63 @@ end end + describe "a Node.js app running on the root URI" do + before :all do + create_apache2_controller + @server = "http://1.passenger.test:#{@apache2.port}" + @stub = NodejsStub.new('node') + @apache2 << "RailsMaxPoolSize 1" + @apache2.set_vhost("1.passenger.test", "#{@stub.full_app_root}/public") + @apache2.start + end + + after :all do + @stub.destroy + @apache2.stop if @apache2 + end + + before :each do + @stub.reset + end + + it_should_behave_like "an example web app" + end + + describe "a Node.js app running in a sub-URI" do + before :all do + create_apache2_controller + @server = "http://1.passenger.test:#{@apache2.port}/subapp" + @stub = NodejsStub.new('node') + @apache2 << "RailsMaxPoolSize 1" + @apache2.set_vhost("1.passenger.test", File.expand_path("stub")) do |vhost| + vhost << %Q{ + Alias /subapp #{@stub.full_app_root}/public + + PassengerBaseURI /subapp + PassengerAppRoot #{@stub.full_app_root} + + } + end + @apache2.start + end + + after :all do + @stub.destroy + @apache2.stop if @apache2 + end + + before :each do + @stub.reset + end + + it_should_behave_like "an example web app" + + it "does not interfere with the root website" do + @server = "http://1.passenger.test:#{@apache2.port}" + get('/').should == "This is the stub directory." + end + end + describe "compatibility with other modules" do before :all do create_apache2_controller diff -Nru ruby-passenger-4.0.35/test/integration_tests/nginx_tests.rb ruby-passenger-4.0.37/test/integration_tests/nginx_tests.rb --- ruby-passenger-4.0.35/test/integration_tests/nginx_tests.rb 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/test/integration_tests/nginx_tests.rb 2013-10-26 22:00:00.000000000 +0000 @@ -163,6 +163,68 @@ end end + describe "a Node.js app running on the root URI" do + before :all do + create_nginx_controller + @server = "http://1.passenger.test:#{@nginx.port}" + @stub = NodejsStub.new('node') + @nginx.add_server do |server| + server[:server_name] = "1.passenger.test" + server[:root] = "#{@stub.full_app_root}/public" + end + @nginx.start + end + + after :all do + @stub.destroy + @nginx.stop if @nginx + end + + before :each do + @stub.reset + end + + it_should_behave_like "an example web app" + end + + describe "a Node.js app running in a sub-URI" do + before :all do + create_nginx_controller + @server = "http://1.passenger.test:#{@nginx.port}/subapp" + @stub = NodejsStub.new('node') + @nginx.add_server do |server| + server[:server_name] = "1.passenger.test" + server[:root] = "#{PhusionPassenger.source_root}/test/stub" + server << %Q{ + location ~ ^/subapp(/.*|$) { + alias #{@stub.full_app_root}/public$1; + passenger_base_uri /subapp; + passenger_document_root #{@stub.full_app_root}/public; + passenger_app_root #{@stub.full_app_root}; + passenger_enabled on; + } + } + end + @nginx.start + end + + after :all do + @stub.destroy + @nginx.stop if @nginx + end + + before :each do + @stub.reset + end + + it_should_behave_like "an example web app" + + it "does not interfere with the root website" do + @server = "http://1.passenger.test:#{@nginx.port}" + get('/').should == "This is the stub directory." + end + end + describe "various features" do before :all do create_nginx_controller diff -Nru ruby-passenger-4.0.35/test/node/httplib_emulation_spec.js ruby-passenger-4.0.37/test/node/httplib_emulation_spec.js --- ruby-passenger-4.0.35/test/node/httplib_emulation_spec.js 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/test/node/httplib_emulation_spec.js 2013-10-26 22:00:00.000000000 +0000 @@ -91,6 +91,52 @@ return result; } + describe('the request object', function() { + beforeEach(function() { + var state = this.state; + state.setup = function(headers, callback) { + if (!callback) { + callback = headers; + headers = {}; + } + state.headers = createHeaders(headers); + state.createSocket(function(serverSocket, client) { + state.req = HttplibEmulation.createIncomingMessage( + state.headers, serverSocket, ""); + callback(); + }); + } + }); + + specify('.on() returns the request object', function(done) { + var state = this.state; + state.setup(function() { + var result = state.req.on('foo', function() {}); + assert.strictEqual(result, state.req); + done(); + }); + }); + + it('sets no "upgrade" flag if there is no Upgrade header', function(done) { + var state = this.state; + state.setup(function() { + assert.ok(!state.req.upgrade); + done(); + }); + }); + + it('sets the "upgrade" flag if there is an Upgrade header', function(done) { + var state = this.state; + var headers = { + 'HTTP_UPGRADE': 'WebSocket' + }; + state.setup(headers, function() { + assert.ok(state.req.upgrade); + done(); + }); + }); + }); + describe('if the request may have a request body', function() { beforeEach(function() { var state = this.state; @@ -105,7 +151,6 @@ state.createSocket(function(serverSocket, client) { state.req = HttplibEmulation.createIncomingMessage( state.headers, serverSocket, ""); - assert.ok(state.req._mayHaveRequestBody); callback(); }); } @@ -165,7 +210,7 @@ }); }); - it("sends data events as data is received", function(done) { + specify("the request object emits data events as data is received", function(done) { var state = this.state; var chunks = []; @@ -182,7 +227,7 @@ }); }); - it("sends the end event after the client closes the socket", function(done) { + specify("the request object emits the end event after the client closes the socket", function(done) { var state = this.state; var finished; @@ -214,7 +259,7 @@ }); }); - it("emits readable events upon receiving data", function(done) { + specify("the request object emits readable events upon receiving data", function(done) { var state = this.state; var readable = 0; state.req.on('readable', function() { @@ -380,7 +425,6 @@ state.createSocket(function(serverSocket, client) { state.req = HttplibEmulation.createIncomingMessage( state.headers, serverSocket, ""); - assert.ok(!state.req._mayHaveRequestBody); callback(); }); } @@ -488,4 +532,92 @@ }); }); }); + + describe('requests with Upgrade header', function() { + beforeEach(function() { + var state = this.state; + state.setup = function(headers, callback) { + if (!callback) { + callback = headers; + headers = { + 'HTTP_UPGRADE': 'websocket' + }; + } + state.headers = createHeaders(headers); + state.createSocket(function(serverSocket, client) { + state.req = HttplibEmulation.createIncomingMessage( + state.headers, serverSocket, ""); + callback(); + }); + } + }); + + specify('the request object emits no data events', function(done) { + var state = this.state; + state.setup(function() { + var hasData = false; + + state.req.on('data', function(data) { + hasData = true; + }); + state.client.write("hello"); + + Helper.shouldNeverHappen(50, function() { + return hasData; + }, done); + }); + }); + + specify('the request object ends immediately', function(done) { + var state = this.state; + state.setup(function() { + var readable = false; + var readData; + var ended = false; + + state.req.on('readable', function() { + readable = true; + readData = state.req.read(100); + }); + state.req.on('end', function() { + ended = true; + }); + + Helper.eventually(50, function() { + return readable && ended; + }, function() { + assert.strictEqual(readData, null); + done(); + }); + }); + }); + + specify('the socket emits data events as data is received', function(done) { + var state = this.state; + state.setup(function() { + var hasData = false; + + state.req.socket.on('data', function(data) { + hasData = true; + }); + state.client.write("hello"); + + Helper.eventually(50, function() { + return hasData; + }, done); + }); + }); + + it('allows reading from the socket', function(done) { + var state = this.state; + state.setup(function() { + state.req.socket.on('readable', function() { + var chunk = state.req.socket.read(5).toString('utf-8'); + chunk.should.eql("hello"); + done(); + }); + state.client.write("hello"); + }); + }); + }); }); diff -Nru ruby-passenger-4.0.35/test/node/spec_helper.js ruby-passenger-4.0.37/test/node/spec_helper.js --- ruby-passenger-4.0.35/test/node/spec_helper.js 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/test/node/spec_helper.js 2013-10-26 22:00:00.000000000 +0000 @@ -46,6 +46,19 @@ assert.fail("Something which should eventually happen never happened"); } }, 10); + }, + + shouldNeverHappen: function(timeout, check, done) { + var startTime = new Date(); + var id = setInterval(function() { + if (check()) { + clearInterval(id); + assert.fail("Something which should never happen, happened anyway"); + } else if (new Date() - startTime > timeout) { + clearInterval(id); + done(); + } + }, 10); } }; Binary files /tmp/CIzZ_7MvZ5/ruby-passenger-4.0.35/test/stub/.DS_Store and /tmp/E8C5wRH7r1/ruby-passenger-4.0.37/test/stub/.DS_Store differ diff -Nru ruby-passenger-4.0.35/test/stub/node/app.js ruby-passenger-4.0.37/test/stub/node/app.js --- ruby-passenger-4.0.35/test/stub/node/app.js 1970-01-01 00:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/test/stub/node/app.js 2013-10-26 22:00:00.000000000 +0000 @@ -0,0 +1,125 @@ +var fs = require('fs'); +var url = require('url'); +var express = require('express'); +var app = express(); +var bodyParser = express.bodyParser(); + +function textResponse(res, content) { + content = String(content); + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Content-Length", content.length); + res.end(content); +} + +function fileExists(filename) { + try { + fs.statSync(filename); + return true; + } catch (e) { + return false; + } +} + +if (process.env.PASSENGER_BASE_URI) { + app.use(process.env.PASSENGER_BASE_URI, app.router); +} + +app.all('/', function(req, res) { + if (fileExists("front_page.txt")) { + textResponse(res, fs.readFileSync("front_page.txt")); + } else { + textResponse(res, "front page"); + } +}); + +app.all('/parameters', function(req, res) { + bodyParser(req, res, function() { + var first = req.query.first || req.body.first; + var second = req.query.second || req.body.second; + textResponse(res, "Method: " + req.method + "\n" + + "First: " + first + "\n" + + "Second: " + second + "\n") + }); +}); + +app.all('/chunked', function(req, res) { + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Transfer-Encoding", "chunked"); + res.write("chunk1\n"); + res.write("chunk2\n"); + res.write("chunk3\n"); + res.end(); +}); + +app.all('/pid', function(req, res) { + textResponse(res, process.pid); +}); + +app.all(/^\/env/, function(req, res) { + var body = ''; + var keys = []; + for (var key in req.cgiHeaders) { + keys.push(key); + } + keys.sort(); + for (var i = 0; i < keys.length; i++) { + var val = req.cgiHeaders[keys[i]]; + if (val === undefined) { + val = ''; + } + body += keys[i] + " = " + val + "\n"; + } + textResponse(res, body); +}); + +app.all('/touch_file', function(req, res) { + bodyParser(req, res, function() { + var filename = req.query.file || req.body.file; + fs.writeFileSync(filename, ""); + textResponse(res, "ok") + }); +}); + +app.all('/extra_header', function(req, res) { + res.setHeader("Content-Type", "text/html"); + res.setHeader("Content-Length", "2"); + res.setHeader("X-Foo", "Bar"); + res.end("ok"); +}); + +app.all('/cached', function(req, res) { + textResponse(res, "This is the uncached version of /cached"); +}); + +app.all('/upload_with_params', function(req, res) { + bodyParser(req, res, function() { + var name1 = req.query.name1 || req.body.name1; + var name2 = req.query.name2 || req.body.name2; + var data = fs.readFileSync(req.files.data.path); + var body = + "name 1 = " + name1 + "\n" + + "name 2 = " + name2 + "\n" + + "data = "; + var bodyBuffer = new Buffer(body); + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Content-Length", bodyBuffer.length + data.length); + res.write(bodyBuffer); + res.write(data); + res.end(); + }); +}); + +app.all('/raw_upload_to_file', function(req, res) { + var filename = req.headers['x-output']; + var stream = fs.createWriteStream(filename); + req.on('data', function(data) { + stream.write(data); + }); + req.on('end', function() { + stream.end(function() { + textResponse(res, "ok"); + }); + }); +}); + +app.listen(3000); diff -Nru ruby-passenger-4.0.35/test/stub/rack/config.ru ruby-passenger-4.0.37/test/stub/rack/config.ru --- ruby-passenger-4.0.35/test/stub/rack/config.ru 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/test/stub/rack/config.ru 2013-10-26 22:00:00.000000000 +0000 @@ -75,6 +75,25 @@ sleep 0.1 # Give HelperAgent the time to process stdout first. STDERR.puts "hello stderr!" text_response("ok") + when '/switch_protocol' + if env['HTTP_UPGRADE'] != 'raw' || env['HTTP_CONNECTION'].downcase != 'upgrade' + return [500, { "Content-Type" => "text/plain" }, ["Invalid headers"]] + end + env['rack.hijack'].call + io = env['rack.hijack_io'] + begin + io.write("Status: 101 Switching Protocols\r\n") + io.write("Upgrade: raw\r\n") + io.write("Connection: Upgrade\r\n") + io.write("\r\n") + while !io.eof? + line = io.readline + io.write("Echo: #{line}") + io.flush + end + ensure + io.close + end else [404, { "Content-Type" => "text/plain" }, ["Unknown URI"]] end diff -Nru ruby-passenger-4.0.35/test/stub/wsgi/passenger_wsgi.py ruby-passenger-4.0.37/test/stub/wsgi/passenger_wsgi.py --- ruby-passenger-4.0.35/test/stub/wsgi/passenger_wsgi.py 2013-10-26 22:00:00.000000000 +0000 +++ ruby-passenger-4.0.37/test/stub/wsgi/passenger_wsgi.py 2013-10-26 22:00:00.000000000 +0000 @@ -1,4 +1,4 @@ -import os, time, cgi +import os, sys, time, cgi def file_exist(filename): try: @@ -7,6 +7,19 @@ except OSError: return False +if sys.version_info[0] >= 3: + def bytes_to_str(b): + return b.decode() + + def str_to_bytes(s): + return s.encode('latin-1') +else: + def bytes_to_str(b): + return b + + def str_to_bytes(s): + return s + def application(env, start_response): status = '200 OK' body = None @@ -131,6 +144,29 @@ elif path == '/oobw': start_response(status, [('Content-Type', 'text/plain'), ('X-Passenger-Request-OOB-Work', 'true')]) return [str(os.getpid())] + elif path == '/switch_protocol': + if env['HTTP_UPGRADE'] != 'raw' or env['HTTP_CONNECTION'].lower() != 'upgrade': + status = '500 Internal Server Error' + body = str('Invalid headers') + start_response(status, [('Content-Type', 'text/plain'), ('Content-Length', len(body))]) + return [body] + socket = env['passenger.hijack']() + io = socket.makefile() + socket.close() + try: + io.write( + b"HTTP/1.1 101 Switching Protocols\r\n" + + b"Upgrade: raw\r\n" + + b"Connection: Upgrade\r\n" + + b"\r\n") + io.flush() + line = io.readline() + while line != "": + io.write("Echo: " + line) + io.flush() + line = io.readline() + finally: + io.close() else: status = "404 Not Found" body = "Unknown URI"