passenger 5.0.30 → 5.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of passenger might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG +30 -1
- data/CONTRIBUTING.md +1 -1
- data/CONTRIBUTORS +2 -0
- data/bin/passenger-install-nginx-module +18 -13
- data/build/agent.rb +1 -0
- data/build/basics.rb +1 -0
- data/build/cxx_tests.rb +6 -1
- data/build/misc.rb +3 -0
- data/build/packaging.rb +5 -17
- data/build/support/cxx_dependency_map.rb +100 -0
- data/build/support/vendor/cxxcodebuilder/lib/cxxcodebuilder/builder.rb +4 -1
- data/build/test_basics.rb +12 -2
- data/dev/ci/run_travis.sh +6 -2
- data/doc/Users guide Apache.html +7 -2
- data/doc/Users guide Apache.txt +4 -0
- data/resources/templates/error_layout.css +70 -84
- data/resources/templates/error_layout.html.template +84 -93
- data/resources/templates/standalone/http.erb +17 -13
- data/resources/templates/standalone/server.erb +2 -1
- data/resources/templates/undisclosed_error.html.template +52 -51
- data/resources/update_check_client_cert.p12 +0 -0
- data/resources/update_check_client_cert.pem +89 -0
- data/resources/update_check_server_pubkey.pem +14 -0
- data/src/agent/Core/ApplicationPool/ErrorRenderer.h +15 -1
- data/src/agent/Core/Controller.h +3 -2
- data/src/agent/Core/Controller/CheckoutSession.cpp +5 -4
- data/src/agent/Core/Controller/ForwardResponse.cpp +1 -1
- data/src/agent/Core/Controller/InitRequest.cpp +2 -0
- data/src/agent/Core/Controller/InitializationAndShutdown.cpp +1 -0
- data/src/agent/Core/Controller/Request.h +1 -0
- data/src/agent/Core/CoreMain.cpp +99 -2
- data/src/agent/Core/OptionParser.h +18 -1
- data/src/agent/Core/SecurityUpdateChecker.h +559 -0
- data/src/agent/Shared/Base.cpp +6 -1
- data/src/agent/TempDirToucher/TempDirToucherMain.cpp +52 -0
- data/src/agent/Watchdog/InstanceDirToucher.cpp +1 -2
- data/src/agent/Watchdog/WatchdogMain.cpp +31 -40
- data/src/apache2_module/Configuration.cpp +12 -0
- data/src/apache2_module/Configuration.hpp +5 -0
- data/src/apache2_module/ConfigurationCommands.cpp +19 -19
- data/src/apache2_module/ConfigurationCommands.cpp.cxxcodebuilder +2 -2
- data/src/apache2_module/ConfigurationFields.hpp +19 -19
- data/src/apache2_module/ConfigurationFields.hpp.cxxcodebuilder +2 -2
- data/src/apache2_module/ConfigurationSetters.cpp +19 -19
- data/src/apache2_module/ConfigurationSetters.cpp.cxxcodebuilder +2 -2
- data/src/apache2_module/CreateDirConfig.cpp +19 -19
- data/src/apache2_module/CreateDirConfig.cpp.cxxcodebuilder +2 -2
- data/src/apache2_module/Hooks.cpp +10 -1
- data/src/apache2_module/MergeDirConfig.cpp +19 -19
- data/src/apache2_module/MergeDirConfig.cpp.cxxcodebuilder +2 -2
- data/src/apache2_module/SetHeaders.cpp +19 -19
- data/src/apache2_module/SetHeaders.cpp.cxxcodebuilder +2 -2
- data/src/cxx_supportlib/Constants.h +22 -22
- data/src/cxx_supportlib/Constants.h.cxxcodebuilder +4 -1
- data/src/cxx_supportlib/Crypto.cpp +977 -0
- data/src/cxx_supportlib/Crypto.h +147 -0
- data/src/cxx_supportlib/InstanceDirectory.h +55 -2
- data/src/cxx_supportlib/Utils/Curl.h +24 -10
- data/src/cxx_supportlib/Utils/JsonUtils.h +1 -1
- data/src/cxx_supportlib/oxt/detail/spin_lock_darwin.hpp +2 -0
- data/src/cxx_supportlib/vendor-modified/boost/system/error_code.hpp +3 -3
- data/src/cxx_supportlib/vendor-modified/jsoncpp/json-forwards.h +167 -92
- data/src/cxx_supportlib/vendor-modified/jsoncpp/json.h +1827 -1542
- data/src/cxx_supportlib/vendor-modified/jsoncpp/jsoncpp.cpp +4705 -3652
- data/src/cxx_supportlib/vendor-modified/libev/Changes +46 -15
- data/src/cxx_supportlib/vendor-modified/libev/LICENSE +1 -1
- data/src/cxx_supportlib/vendor-modified/libev/Makefile.in +215 -128
- data/src/cxx_supportlib/vendor-modified/libev/aclocal.m4 +466 -275
- data/src/cxx_supportlib/vendor-modified/libev/config.guess +312 -418
- data/src/cxx_supportlib/vendor-modified/libev/config.sub +246 -105
- data/src/cxx_supportlib/vendor-modified/libev/configure +276 -72
- data/src/cxx_supportlib/vendor-modified/libev/configure.ac +2 -1
- data/src/cxx_supportlib/vendor-modified/libev/depcomp +346 -185
- data/src/cxx_supportlib/vendor-modified/libev/ev++.h +1 -1
- data/src/cxx_supportlib/vendor-modified/libev/ev.c +530 -190
- data/src/cxx_supportlib/vendor-modified/libev/ev.h +23 -14
- data/src/cxx_supportlib/vendor-modified/libev/ev_epoll.c +12 -6
- data/src/cxx_supportlib/vendor-modified/libev/ev_kqueue.c +9 -5
- data/src/cxx_supportlib/vendor-modified/libev/ev_poll.c +6 -3
- data/src/cxx_supportlib/vendor-modified/libev/ev_port.c +8 -4
- data/src/cxx_supportlib/vendor-modified/libev/ev_select.c +4 -2
- data/src/cxx_supportlib/vendor-modified/libev/ev_vars.h +3 -2
- data/src/cxx_supportlib/vendor-modified/libev/ev_win32.c +3 -4
- data/src/cxx_supportlib/vendor-modified/libev/install-sh +433 -219
- data/src/cxx_supportlib/vendor-modified/libev/libev.m4 +6 -6
- data/src/cxx_supportlib/vendor-modified/libev/ltmain.sh +2 -2
- data/src/cxx_supportlib/vendor-modified/libev/missing +167 -288
- data/src/cxx_supportlib/vendor-modified/libev/mkinstalldirs +72 -21
- data/src/cxx_supportlib/vendor-modified/modp_b64.cpp +4 -106
- data/src/cxx_supportlib/vendor-modified/modp_b64_data.h +37 -1
- data/src/cxx_supportlib/vendor-modified/modp_b64_strict_aliasing.cpp +119 -0
- data/src/helper-scripts/node-loader.js +72 -1
- data/src/nginx_module/CacheLocationConfig.c +52 -19
- data/src/nginx_module/CacheLocationConfig.c.cxxcodebuilder +2 -2
- data/src/nginx_module/Configuration.c +26 -1
- data/src/nginx_module/Configuration.h +2 -0
- data/src/nginx_module/ConfigurationCommands.c +35 -19
- data/src/nginx_module/ConfigurationCommands.c.cxxcodebuilder +2 -2
- data/src/nginx_module/ContentHandler.c +1 -1
- data/src/nginx_module/CreateLocationConfig.c +22 -19
- data/src/nginx_module/CreateLocationConfig.c.cxxcodebuilder +2 -2
- data/src/nginx_module/LocationConfig.h +21 -19
- data/src/nginx_module/LocationConfig.h.cxxcodebuilder +2 -2
- data/src/nginx_module/MergeLocationConfig.c +25 -19
- data/src/nginx_module/MergeLocationConfig.c.cxxcodebuilder +2 -2
- data/src/nginx_module/ngx_http_passenger_module.c +8 -4
- data/src/ruby_supportlib/phusion_passenger.rb +9 -4
- data/src/ruby_supportlib/phusion_passenger/admin_tools/instance.rb +2 -2
- data/src/ruby_supportlib/phusion_passenger/admin_tools/instance_registry.rb +1 -1
- data/src/ruby_supportlib/phusion_passenger/common_library.rb +13 -0
- data/src/ruby_supportlib/phusion_passenger/config/nginx_engine_compiler.rb +5 -2
- data/src/ruby_supportlib/phusion_passenger/constants.rb +1 -1
- data/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb +15 -3
- data/src/ruby_supportlib/phusion_passenger/platform_info/crypto.rb +51 -0
- data/src/ruby_supportlib/phusion_passenger/platform_info/depcheck_specs/apache2.rb +7 -0
- data/src/ruby_supportlib/phusion_passenger/standalone/config_options_list.rb +17 -0
- data/src/ruby_supportlib/phusion_passenger/standalone/start_command.rb +4 -2
- data/src/ruby_supportlib/phusion_passenger/standalone/start_command/builtin_engine.rb +4 -0
- data/src/ruby_supportlib/phusion_passenger/standalone/start_command/nginx_engine.rb +5 -0
- data/src/ruby_supportlib/phusion_passenger/vendor/crash_watch/app.rb +19 -10
- data/src/ruby_supportlib/phusion_passenger/vendor/crash_watch/base.rb +25 -0
- data/src/ruby_supportlib/phusion_passenger/vendor/crash_watch/gdb_controller.rb +38 -103
- data/src/ruby_supportlib/phusion_passenger/vendor/crash_watch/lldb_controller.rb +178 -0
- data/src/ruby_supportlib/phusion_passenger/vendor/crash_watch/utils.rb +94 -0
- data/src/ruby_supportlib/phusion_passenger/vendor/crash_watch/version.rb +2 -2
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core.rb +2 -2
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/version_data.rb +2 -2
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/ruby_versions.yml.travis +5 -3
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/ruby_versions.yml.travis-with-sudo +9 -7
- metadata +14 -4
@@ -87,6 +87,12 @@ coreUsage() {
|
|
87
87
|
printf(" --default-group NAME Default group to start apps as, when user\n");
|
88
88
|
printf(" switching is disabled. Default: the default\n");
|
89
89
|
printf(" user's primary group\n");
|
90
|
+
printf(" --disable-security-update-check\n");
|
91
|
+
printf(" Disable the periodic check and notice about\n");
|
92
|
+
printf(" important security updates\n");
|
93
|
+
printf(" --security-update-check-proxy PROXY\n");
|
94
|
+
printf(" Use http/SOCKS proxy for the security update check:\n");
|
95
|
+
printf(" scheme://user:password@proxy_host:proxy_port\n");
|
90
96
|
printf("\n");
|
91
97
|
printf("Application serving options (optional):\n");
|
92
98
|
printf(" -e, --environment NAME Default framework environment name to use.\n");
|
@@ -142,9 +148,11 @@ coreUsage() {
|
|
142
148
|
printf(" requests per process\n");
|
143
149
|
printf(" --min-instances N Minimum number of application processes. Default: 1\n");
|
144
150
|
printf(" --memory-limit MB Restart application processes that go over the\n");
|
145
|
-
|
151
|
+
printf(" given memory limit (Enterprise only)\n");
|
146
152
|
printf("\n");
|
147
153
|
printf("Request handling options (optional):\n");
|
154
|
+
printf(" --max-requests Restart application processes that have handled\n");
|
155
|
+
printf(" the specified maximum number of requests\n");
|
148
156
|
printf(" --max-request-time Abort requests that take too much time (Enterprise\n");
|
149
157
|
printf(" only)\n");
|
150
158
|
printf(" --max-request-queue-size NUMBER\n");
|
@@ -268,6 +276,12 @@ parseCoreOption(int argc, const char *argv[], int &i, VariantMap &options) {
|
|
268
276
|
} else if (p.isValueFlag(argc, i, argv[i], '\0', "--default-group")) {
|
269
277
|
options.set("default_group", argv[i + 1]);
|
270
278
|
i += 2;
|
279
|
+
} else if (p.isFlag(argv[i], '\0', "--disable-security-update-check")) {
|
280
|
+
options.setBool("disable_security_update_check", true);
|
281
|
+
i += 2;
|
282
|
+
} else if (p.isValueFlag(argc, i, argv[i], '\0', "--security-update-check-proxy")) {
|
283
|
+
options.set("security_update_check_proxy", argv[i + 1]);
|
284
|
+
i += 2;
|
271
285
|
} else if (p.isValueFlag(argc, i, argv[i], '\0', "--max-pool-size")) {
|
272
286
|
options.setInt("max_pool_size", atoi(argv[i + 1]));
|
273
287
|
i += 2;
|
@@ -316,6 +330,9 @@ parseCoreOption(int argc, const char *argv[], int &i, VariantMap &options) {
|
|
316
330
|
} else if (p.isFlag(argv[i], '\0', "--disable-friendly-error-pages")) {
|
317
331
|
options.setBool("friendly_error_pages", false);
|
318
332
|
i++;
|
333
|
+
} else if (p.isValueFlag(argc, i, argv[i], '\0', "--max-requests")) {
|
334
|
+
options.setInt("max_requests", atoi(argv[i + 1]));
|
335
|
+
i += 2;
|
319
336
|
} else if (p.isValueFlag(argc, i, argv[i], '\0', "--max-request-time")) {
|
320
337
|
options.setInt("max_request_time", atoi(argv[i + 1]));
|
321
338
|
i += 2;
|
@@ -0,0 +1,559 @@
|
|
1
|
+
/*
|
2
|
+
* Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
+
* Copyright (c) 2011-2016 Phusion Holding B.V.
|
4
|
+
*
|
5
|
+
* "Passenger", "Phusion Passenger" and "Union Station" are registered
|
6
|
+
* trademarks of Phusion Holding B.V.
|
7
|
+
*
|
8
|
+
* See LICENSE file for license information.
|
9
|
+
*/
|
10
|
+
#ifndef _PASSENGER_UPDATE_CHECKER_H_
|
11
|
+
#define _PASSENGER_UPDATE_CHECKER_H_
|
12
|
+
|
13
|
+
#include <string>
|
14
|
+
#include <oxt/thread.hpp>
|
15
|
+
#include <oxt/backtrace.hpp>
|
16
|
+
|
17
|
+
#include <Crypto.h>
|
18
|
+
#include <Utils/Curl.h>
|
19
|
+
#include <modp_b64.h>
|
20
|
+
|
21
|
+
namespace Passenger {
|
22
|
+
|
23
|
+
using namespace std;
|
24
|
+
using namespace oxt;
|
25
|
+
|
26
|
+
#define CHECK_HOST_DEFAULT "securitycheck.phusionpassenger.com"
|
27
|
+
|
28
|
+
#define CHECK_URL_DEFAULT "https://" CHECK_HOST_DEFAULT ":443/v1/check.json"
|
29
|
+
#define MIN_CHECK_BACKOFF_SEC 12 * 60 * 60
|
30
|
+
#define MAX_CHECK_BACKOFF_SEC 7 * 24 * 60 * 60
|
31
|
+
|
32
|
+
// Password for the .p12 client certificate (because .p12 is required to be pwd protected on some
|
33
|
+
// implementations). We're OK with hardcoding because the certs are not secret anyway, and they're not used
|
34
|
+
// for client id/auth (just to easily deflect unrelated probes from the server endpoint).
|
35
|
+
#define CLIENT_CERT_PWD "p6PBhK8KtorrhMxHnH855MvF"
|
36
|
+
#define CLIENT_CERT_LABEL "Phusion Passenger Open Source"
|
37
|
+
|
38
|
+
#define POSSIBLE_MITM_RESOLUTION "(if this error persists check your connection security or try upgrading " SHORT_PROGRAM_NAME ")"
|
39
|
+
/**
|
40
|
+
* If started, this class periodically (default: daily, immediate start) checks whether there are any important
|
41
|
+
* security updates available (updates that don't fix security issues are not reported). The result is logged
|
42
|
+
* (level 3:notice if no update, level 1:error otherwise), and all further action is left to the user (there is
|
43
|
+
* no auto-update mechanism).
|
44
|
+
*/
|
45
|
+
class SecurityUpdateChecker {
|
46
|
+
|
47
|
+
private:
|
48
|
+
oxt::thread *updateCheckThread;
|
49
|
+
long checkIntervalSec;
|
50
|
+
string clientCertPath; // client cert (PKCS#12), checked by server
|
51
|
+
string serverPubKeyPath; // for checking signature
|
52
|
+
string proxyAddress;
|
53
|
+
string serverIntegration;
|
54
|
+
string serverVersion;
|
55
|
+
CurlProxyInfo proxyInfo;
|
56
|
+
Crypto *crypto;
|
57
|
+
|
58
|
+
void threadMain() {
|
59
|
+
TRACE_POINT();
|
60
|
+
while (!this_thread::interruption_requested()) {
|
61
|
+
UPDATE_TRACE_POINT();
|
62
|
+
int backoffMin = 0;
|
63
|
+
try {
|
64
|
+
backoffMin = checkAndLogSecurityUpdate();
|
65
|
+
} catch (const tracable_exception &e) {
|
66
|
+
P_ERROR(e.what() << "\n" << e.backtrace());
|
67
|
+
}
|
68
|
+
UPDATE_TRACE_POINT();
|
69
|
+
long backoffSec = checkIntervalSec + (backoffMin * 60);
|
70
|
+
if (backoffSec < MIN_CHECK_BACKOFF_SEC) {
|
71
|
+
backoffSec = MIN_CHECK_BACKOFF_SEC;
|
72
|
+
}
|
73
|
+
if (backoffSec > MAX_CHECK_BACKOFF_SEC) {
|
74
|
+
backoffSec = MAX_CHECK_BACKOFF_SEC;
|
75
|
+
}
|
76
|
+
boost::this_thread::sleep_for(boost::chrono::seconds(backoffSec));
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
|
81
|
+
void logUpdateFailCurl(CURLcode code) {
|
82
|
+
// At this point anything could be wrong, from unloadable certificates to server not found, etc.
|
83
|
+
// Let's try to enrich the log message in case there are known solutions or workarounds (e.g. "use proxy").
|
84
|
+
string error = curl_easy_strerror(code);
|
85
|
+
|
86
|
+
switch (code) {
|
87
|
+
case CURLE_SSL_CERTPROBLEM:
|
88
|
+
error.append(" at " + clientCertPath + " (try upgrading or reinstalling " SHORT_PROGRAM_NAME ")");
|
89
|
+
break;
|
90
|
+
|
91
|
+
case CURLE_COULDNT_RESOLVE_HOST:
|
92
|
+
error.append(" while connecting to " CHECK_HOST_DEFAULT " (check your DNS)");
|
93
|
+
break;
|
94
|
+
|
95
|
+
case CURLE_COULDNT_CONNECT:
|
96
|
+
if (proxyAddress.empty()) {
|
97
|
+
error.append(" for " CHECK_URL_DEFAULT " " POSSIBLE_MITM_RESOLUTION);
|
98
|
+
} else {
|
99
|
+
error.append(" for " CHECK_URL_DEFAULT " using proxy " + proxyAddress +
|
100
|
+
" (if this error persists check your firewall and/or proxy settings)");
|
101
|
+
}
|
102
|
+
break;
|
103
|
+
|
104
|
+
case CURLE_COULDNT_RESOLVE_PROXY:
|
105
|
+
error.append(" for proxy address " + proxyAddress);
|
106
|
+
break;
|
107
|
+
|
108
|
+
case CURLE_SSL_CACERT:
|
109
|
+
// Peer certificate cannot be authenticated with given / known CA certificates. This would happen
|
110
|
+
// for MITM but could also be a truststore issue.
|
111
|
+
case CURLE_PEER_FAILED_VERIFICATION:
|
112
|
+
// The remote server's SSL certificate or SSH md5 fingerprint was deemed not OK.
|
113
|
+
error.append(" while connecting to " CHECK_HOST_DEFAULT "; check that your connection is secure and that the "
|
114
|
+
"truststore is valid. If the problem persists, you can also try upgrading or reinstalling " SHORT_PROGRAM_NAME);
|
115
|
+
break;
|
116
|
+
|
117
|
+
case CURLE_SSL_CACERT_BADFILE:
|
118
|
+
error.append(" while connecting to " CHECK_URL_DEFAULT " " +
|
119
|
+
(proxyAddress.empty() ? "" : "using proxy " + proxyAddress) + "; this might happen if the nss backend "
|
120
|
+
"is installed for libcurl instead of gnutls or openssl. If the problem persists, you can also try upgrading "
|
121
|
+
"or reinstalling " SHORT_PROGRAM_NAME);
|
122
|
+
break;
|
123
|
+
|
124
|
+
// Fallthroughs to default:
|
125
|
+
case CURLE_SSL_CONNECT_ERROR:
|
126
|
+
// A problem occurred somewhere in the SSL/TLS handshake. Not sure what's up, but in this case the
|
127
|
+
// error buffer (printed in DEBUG) should pinpoint the problem slightly more.
|
128
|
+
case CURLE_OPERATION_TIMEDOUT:
|
129
|
+
// This is not a normal connect timeout, there are some refs to it occuring while downloading large
|
130
|
+
// files, but we don't do that so fall through to default.
|
131
|
+
default:
|
132
|
+
error.append(" while connecting to " CHECK_URL_DEFAULT " " +
|
133
|
+
(proxyAddress.empty() ? "" : "using proxy " + proxyAddress) + " " POSSIBLE_MITM_RESOLUTION);
|
134
|
+
break;
|
135
|
+
}
|
136
|
+
|
137
|
+
logUpdateFail(error);
|
138
|
+
|
139
|
+
#if !BOOST_OS_MACOS
|
140
|
+
unsigned long cryptoErrorCode = ERR_get_error();
|
141
|
+
if (cryptoErrorCode == 0) {
|
142
|
+
logUpdateFailAdditional("CURLcode" + to_string(code));
|
143
|
+
} else {
|
144
|
+
char buf[500];
|
145
|
+
ERR_error_string(cryptoErrorCode, buf);
|
146
|
+
logUpdateFailAdditional("CURLcode: " + to_string(code) + ", Crypto: " + to_string(cryptoErrorCode) + " " + buf);
|
147
|
+
}
|
148
|
+
#endif
|
149
|
+
}
|
150
|
+
|
151
|
+
void logUpdateFailHttp(int httpCode) {
|
152
|
+
string error;
|
153
|
+
|
154
|
+
switch (httpCode) {
|
155
|
+
case 404:
|
156
|
+
error.append("url not found: " CHECK_URL_DEFAULT " " POSSIBLE_MITM_RESOLUTION);
|
157
|
+
break;
|
158
|
+
case 403:
|
159
|
+
error.append("connection denied by server " POSSIBLE_MITM_RESOLUTION);
|
160
|
+
break;
|
161
|
+
case 503:
|
162
|
+
error.append("server temporarily unavailable, try again later");
|
163
|
+
break;
|
164
|
+
case 429:
|
165
|
+
error.append("rate limit hit for your IP, try again later");
|
166
|
+
break;
|
167
|
+
case 400:
|
168
|
+
error.append("request corrupted or not understood " POSSIBLE_MITM_RESOLUTION);
|
169
|
+
break;
|
170
|
+
case 422:
|
171
|
+
error.append("request content was corrupted or not understood " POSSIBLE_MITM_RESOLUTION);
|
172
|
+
break;
|
173
|
+
default:
|
174
|
+
error = "HTTP " + to_string(httpCode) + " while connecting to " CHECK_URL_DEFAULT " " POSSIBLE_MITM_RESOLUTION;
|
175
|
+
break;
|
176
|
+
}
|
177
|
+
logUpdateFail(error);
|
178
|
+
}
|
179
|
+
|
180
|
+
void logUpdateFailResponse(string error, string responseData) {
|
181
|
+
logUpdateFail("error in server response (" + error +
|
182
|
+
"). If this error persists, check your connection security and try upgrading " SHORT_PROGRAM_NAME);
|
183
|
+
logUpdateFailAdditional(responseData);
|
184
|
+
}
|
185
|
+
|
186
|
+
|
187
|
+
/**
|
188
|
+
* POST a bodyJsonString using a client certificate, and receive the response in responseData.
|
189
|
+
*
|
190
|
+
* May allocate chunk data for setting Content-Type, receiver should deallocate with curl_slist_free_all().
|
191
|
+
*/
|
192
|
+
CURLcode prepareCurlPOST(CURL *curl, string &bodyJsonString, string *responseData, struct curl_slist **chunk) {
|
193
|
+
CURLcode code;
|
194
|
+
|
195
|
+
// Hint for advanced debugging: curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
196
|
+
|
197
|
+
if (CURLE_OK != (code = curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1))) {
|
198
|
+
return code;
|
199
|
+
}
|
200
|
+
if (CURLE_OK != (code = curl_easy_setopt(curl, CURLOPT_URL, CHECK_URL_DEFAULT))) {
|
201
|
+
return code;
|
202
|
+
}
|
203
|
+
if (CURLE_OK != (code = curl_easy_setopt(curl, CURLOPT_HTTPGET, 0))) {
|
204
|
+
return code;
|
205
|
+
}
|
206
|
+
if (CURLE_OK != (code = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, bodyJsonString.c_str()))) {
|
207
|
+
return code;
|
208
|
+
}
|
209
|
+
if (CURLE_OK != (code = curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, bodyJsonString.length()))) {
|
210
|
+
return code;
|
211
|
+
}
|
212
|
+
*chunk = curl_slist_append(NULL, "Content-Type: application/json");
|
213
|
+
if (CURLE_OK != (code = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, *chunk))) {
|
214
|
+
return code;
|
215
|
+
}
|
216
|
+
|
217
|
+
#if BOOST_OS_MACOS
|
218
|
+
if (!crypto->preAuthKey(clientCertPath.c_str(), CLIENT_CERT_PWD, CLIENT_CERT_LABEL)) {
|
219
|
+
return CURLE_SSL_CERTPROBLEM;
|
220
|
+
}
|
221
|
+
if (CURLE_OK != (code = curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "P12"))) {
|
222
|
+
return code;
|
223
|
+
}
|
224
|
+
if (CURLE_OK != (code = curl_easy_setopt(curl, CURLOPT_SSLCERTPASSWD, CLIENT_CERT_PWD))) {
|
225
|
+
return code;
|
226
|
+
}
|
227
|
+
#else
|
228
|
+
if (CURLE_OK != (code = curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM"))) {
|
229
|
+
return code;
|
230
|
+
}
|
231
|
+
#endif
|
232
|
+
|
233
|
+
if (CURLE_OK != (code = curl_easy_setopt(curl, CURLOPT_SSLCERT, clientCertPath.c_str()))) {
|
234
|
+
return code;
|
235
|
+
}
|
236
|
+
|
237
|
+
// These should be on by default, but make sure.
|
238
|
+
if (CURLE_OK != (code = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L))) {
|
239
|
+
return code;
|
240
|
+
}
|
241
|
+
if (CURLE_OK != (code = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L))) {
|
242
|
+
return code;
|
243
|
+
}
|
244
|
+
|
245
|
+
// Technically we could use CURLOPT_SSL_VERIFYSTATUS to check for server cert revocation, but
|
246
|
+
// we want to support older versions. We don't trust the server purely based on the server cert
|
247
|
+
// anyway (it needs to prove by signature later on).
|
248
|
+
|
249
|
+
if (CURLE_OK != (code = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveResponseBytes))) {
|
250
|
+
return code;
|
251
|
+
}
|
252
|
+
if (CURLE_OK != (code = curl_easy_setopt(curl, CURLOPT_WRITEDATA, responseData))) {
|
253
|
+
return code;
|
254
|
+
}
|
255
|
+
if (CURLE_OK != (code = setCurlProxy(curl, proxyInfo))) {
|
256
|
+
return code;
|
257
|
+
}
|
258
|
+
|
259
|
+
// setopt failure(s) below don't abort the check.
|
260
|
+
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 180);
|
261
|
+
|
262
|
+
return CURLE_OK;
|
263
|
+
}
|
264
|
+
|
265
|
+
bool verifyFileReadable(char *filename) {
|
266
|
+
FILE *fhnd = fopen(filename, "rb");
|
267
|
+
if (fhnd == NULL) {
|
268
|
+
return false;
|
269
|
+
}
|
270
|
+
fclose(fhnd);
|
271
|
+
return true;
|
272
|
+
}
|
273
|
+
|
274
|
+
public:
|
275
|
+
|
276
|
+
/**
|
277
|
+
* proxy is optional and should be in the form: scheme://user:password@proxy_host:proxy_port
|
278
|
+
*
|
279
|
+
* serverIntegration should be one of { nginx, apache, standalone nginx, standalone builtin }, whereby
|
280
|
+
* serverVersion is the version of Nginx or Apache, if relevant (otherwise empty)
|
281
|
+
*/
|
282
|
+
SecurityUpdateChecker(const ResourceLocator &locator, const string &proxy, const string &serverIntegration, const string &serverVersion) {
|
283
|
+
crypto = new Crypto();
|
284
|
+
updateCheckThread = NULL;
|
285
|
+
checkIntervalSec = 0;
|
286
|
+
#if BOOST_OS_MACOS
|
287
|
+
clientCertPath = locator.getResourcesDir() + "/update_check_client_cert.p12";
|
288
|
+
#else
|
289
|
+
clientCertPath = locator.getResourcesDir() + "/update_check_client_cert.pem";
|
290
|
+
#endif
|
291
|
+
serverPubKeyPath = locator.getResourcesDir() + "/update_check_server_pubkey.pem";
|
292
|
+
proxyAddress = proxy;
|
293
|
+
this->serverIntegration = serverIntegration;
|
294
|
+
this->serverVersion = serverVersion;
|
295
|
+
try {
|
296
|
+
proxyInfo = prepareCurlProxy(proxyAddress);
|
297
|
+
} catch (const ArgumentException &e) {
|
298
|
+
assert(!proxyInfo.valid);
|
299
|
+
proxyAddress = "Invalid proxy address for security update check: \"" +
|
300
|
+
proxyAddress + "\": " + e.what();
|
301
|
+
}
|
302
|
+
}
|
303
|
+
|
304
|
+
virtual ~SecurityUpdateChecker() {
|
305
|
+
if (updateCheckThread != NULL) {
|
306
|
+
updateCheckThread->interrupt_and_join();
|
307
|
+
delete updateCheckThread;
|
308
|
+
updateCheckThread = NULL;
|
309
|
+
}
|
310
|
+
if (crypto) {
|
311
|
+
delete crypto;
|
312
|
+
}
|
313
|
+
}
|
314
|
+
|
315
|
+
/**
|
316
|
+
* Starts a periodic check at every checkIntervalSec. For each check, the server may increase/decrease
|
317
|
+
* (within limits) the period until the next check (using the backoff parameter in the response).
|
318
|
+
*
|
319
|
+
* Assumes curl_global_init() was already performed.
|
320
|
+
*/
|
321
|
+
void start(long checkIntervalSec) {
|
322
|
+
this->checkIntervalSec = checkIntervalSec;
|
323
|
+
|
324
|
+
assert(checkIntervalSec >= MIN_CHECK_BACKOFF_SEC && checkIntervalSec <= MAX_CHECK_BACKOFF_SEC);
|
325
|
+
updateCheckThread = new oxt::thread(
|
326
|
+
boost::bind(&SecurityUpdateChecker::threadMain, this),
|
327
|
+
"Security update checker",
|
328
|
+
1024 * 512
|
329
|
+
);
|
330
|
+
}
|
331
|
+
|
332
|
+
/**
|
333
|
+
* All error log methods eventually lead here, except for the additional below.
|
334
|
+
*/
|
335
|
+
virtual void logUpdateFail(string error) {
|
336
|
+
P_ERROR("Security update check failed: " << error << " (next check in " << (checkIntervalSec / (60*60)) << " hours)");
|
337
|
+
}
|
338
|
+
|
339
|
+
/**
|
340
|
+
* Logs additional information at a lower loglevel so that it only spams when explicitely requested via loglevel.
|
341
|
+
*/
|
342
|
+
virtual void logUpdateFailAdditional(string additional) {
|
343
|
+
P_DEBUG(additional);
|
344
|
+
}
|
345
|
+
|
346
|
+
virtual void logUpdateSuccess(int update, string success) {
|
347
|
+
if (update == 0) {
|
348
|
+
P_NOTICE(success);
|
349
|
+
} else {
|
350
|
+
P_ERROR(success);
|
351
|
+
}
|
352
|
+
}
|
353
|
+
|
354
|
+
virtual void logUpdateSuccessAdditional(string additional) {
|
355
|
+
P_ERROR(additional);
|
356
|
+
}
|
357
|
+
|
358
|
+
virtual CURLcode sendAndReceive(CURL *curl, string *responseData, long *responseCode) {
|
359
|
+
CURLcode code;
|
360
|
+
if (CURLE_OK != (code = curl_easy_perform(curl))) {
|
361
|
+
return code;
|
362
|
+
}
|
363
|
+
return curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, responseCode);
|
364
|
+
}
|
365
|
+
|
366
|
+
virtual bool fillNonce(string &nonce) {
|
367
|
+
return crypto->generateAndAppendNonce(nonce);
|
368
|
+
}
|
369
|
+
|
370
|
+
/**
|
371
|
+
* Sends POST to CHECK_URL_DEFAULT (via SSL, with client cert) containing:
|
372
|
+
* {"version":"<passenger version>", "nonce":"<random nonce>"}
|
373
|
+
* The response will be:
|
374
|
+
* {"data":base64(data), "signature":base64(signature)}, where:
|
375
|
+
* - signature should be from server we trust and match base64(data),
|
376
|
+
* - data is {"nonce":"<reflected>", "update":0 or 1, "version":"<version>", "log": "<log msg>", "backoff":"<backoff>"}
|
377
|
+
* - reflected nonce should match what we POSTed
|
378
|
+
* - if update is 1 then <version> is logged as the recommended version to upgrade to
|
379
|
+
* - <log msg> (if present) is written to the log
|
380
|
+
* - <backoff> (minutes) is added to our default next check time
|
381
|
+
*/
|
382
|
+
int checkAndLogSecurityUpdate() {
|
383
|
+
int backoffMin = 0;
|
384
|
+
|
385
|
+
// 1. Assemble data to send
|
386
|
+
Json::Value bodyJson;
|
387
|
+
|
388
|
+
bodyJson["passenger_version"] = PASSENGER_VERSION;
|
389
|
+
|
390
|
+
bodyJson["server_integration"] = serverIntegration;
|
391
|
+
bodyJson["server_version"] = serverVersion;
|
392
|
+
|
393
|
+
string nonce;
|
394
|
+
if (!fillNonce(nonce)) {
|
395
|
+
logUpdateFail("fillNonce() error");
|
396
|
+
return backoffMin;
|
397
|
+
}
|
398
|
+
bodyJson["nonce"] = nonce; // against replay attacks
|
399
|
+
|
400
|
+
// 2. Send and get response
|
401
|
+
CURL *curl = curl_easy_init();
|
402
|
+
if (curl == NULL) {
|
403
|
+
logUpdateFail("curl_easy_init() error");
|
404
|
+
return backoffMin;
|
405
|
+
}
|
406
|
+
|
407
|
+
struct curl_slist *chunk = NULL;
|
408
|
+
char *signatureChars = NULL;
|
409
|
+
char *dataChars = NULL;
|
410
|
+
do { // for resource cleanup
|
411
|
+
string responseData;
|
412
|
+
long responseCode;
|
413
|
+
CURLcode code;
|
414
|
+
|
415
|
+
if (!verifyFileReadable((char *) clientCertPath.c_str())) {
|
416
|
+
logUpdateFail("File not readable: " + clientCertPath);
|
417
|
+
break;
|
418
|
+
}
|
419
|
+
// string localApprovedCert = "/your/ca.crt"; // for testing against a local server
|
420
|
+
// curl_easy_setopt(curl, CURLOPT_CAINFO, localApprovedCert.c_str());
|
421
|
+
|
422
|
+
if (!proxyInfo.valid) {
|
423
|
+
// special case: delayed error in proxyAddress
|
424
|
+
logUpdateFail(proxyAddress);
|
425
|
+
break;
|
426
|
+
}
|
427
|
+
|
428
|
+
string bodyJsonString = bodyJson.toStyledString();
|
429
|
+
if (CURLE_OK != (code = prepareCurlPOST(curl, bodyJsonString, &responseData, &chunk))) {
|
430
|
+
logUpdateFailCurl(code);
|
431
|
+
break;
|
432
|
+
}
|
433
|
+
|
434
|
+
if (CURLE_OK != (code = sendAndReceive(curl, &responseData, &responseCode))) {
|
435
|
+
logUpdateFailCurl(code);
|
436
|
+
break;
|
437
|
+
}
|
438
|
+
|
439
|
+
// 3a. Verify response: HTTP code
|
440
|
+
if (responseCode != 200) {
|
441
|
+
logUpdateFailHttp((int) responseCode);
|
442
|
+
break;
|
443
|
+
}
|
444
|
+
|
445
|
+
Json::Reader reader;
|
446
|
+
Json::Value responseJson;
|
447
|
+
if (!reader.parse(responseData, responseJson, false)) {
|
448
|
+
logUpdateFailResponse("json parse", responseData);
|
449
|
+
break;
|
450
|
+
}
|
451
|
+
|
452
|
+
// 3b. Verify response: signature
|
453
|
+
if (!responseJson.isObject() || !responseJson["data"].isString() || !responseJson["signature"].isString()) {
|
454
|
+
logUpdateFailResponse("missing response fields", responseData);
|
455
|
+
break;
|
456
|
+
}
|
457
|
+
|
458
|
+
string signature64 = responseJson["signature"].asString();
|
459
|
+
string data64 = responseJson["data"].asString();
|
460
|
+
|
461
|
+
signatureChars = (char *)malloc(signature64.length() + 1);
|
462
|
+
dataChars = (char *)malloc(signature64.length() + 1);
|
463
|
+
if (signatureChars == NULL || dataChars == NULL) {
|
464
|
+
logUpdateFailResponse("out of memory", responseData);
|
465
|
+
break;
|
466
|
+
}
|
467
|
+
int signatureLen;
|
468
|
+
signatureLen = modp_b64_decode(signatureChars, signature64.c_str(), signature64.length());
|
469
|
+
if (signatureLen <= 0) {
|
470
|
+
logUpdateFailResponse("corrupted signature", responseData);
|
471
|
+
break;
|
472
|
+
}
|
473
|
+
|
474
|
+
if (!crypto->verifySignature(serverPubKeyPath, signatureChars, signatureLen, data64)) {
|
475
|
+
logUpdateFailResponse("untrusted or forged signature", responseData);
|
476
|
+
break;
|
477
|
+
}
|
478
|
+
|
479
|
+
// 3c. Verify response: check required fields, nonce
|
480
|
+
int dataLen;
|
481
|
+
dataLen = modp_b64_decode(dataChars, data64.c_str(), data64.length());
|
482
|
+
if (dataLen <= 0) {
|
483
|
+
logUpdateFailResponse("corrupted data", responseData);
|
484
|
+
break;
|
485
|
+
}
|
486
|
+
dataChars[dataLen] = '\0';
|
487
|
+
|
488
|
+
Json::Value responseDataJson;
|
489
|
+
if (!reader.parse(dataChars, responseDataJson, false)) {
|
490
|
+
logUpdateFailResponse("unparseable data", responseData);
|
491
|
+
break;
|
492
|
+
}
|
493
|
+
|
494
|
+
if (!responseDataJson.isObject() || !responseDataJson["update"].isInt() || !responseDataJson["nonce"].isString()) {
|
495
|
+
logUpdateFailResponse("missing data fields", responseData);
|
496
|
+
break;
|
497
|
+
}
|
498
|
+
|
499
|
+
if (nonce != responseDataJson["nonce"].asString()) {
|
500
|
+
logUpdateFailResponse("nonce mismatch, possible replay attack", responseData);
|
501
|
+
break;
|
502
|
+
}
|
503
|
+
|
504
|
+
// 4. The main point: is there an update, and when is the next check?
|
505
|
+
int update = responseDataJson["update"].asInt();
|
506
|
+
|
507
|
+
if (responseDataJson["backoff"].isInt()) {
|
508
|
+
backoffMin = responseDataJson["backoff"].asInt();
|
509
|
+
}
|
510
|
+
|
511
|
+
if (update == 1 && !responseDataJson["version"].isString()) {
|
512
|
+
logUpdateFailResponse("update available, but version field missing", responseData);
|
513
|
+
break;
|
514
|
+
}
|
515
|
+
|
516
|
+
if (update == 0) {
|
517
|
+
logUpdateSuccess(update, "Security update check: no update found (next check in " + toString(checkIntervalSec / (60*60)) + " hours)");
|
518
|
+
} else {
|
519
|
+
logUpdateSuccess(update, "A security update is available for your version (" PASSENGER_VERSION
|
520
|
+
") of Passenger, we strongly recommend upgrading to version " +
|
521
|
+
responseDataJson["version"].asString() + ".");
|
522
|
+
}
|
523
|
+
|
524
|
+
// 5. Shown independently of whether there is an update so that the server can provide general warnings
|
525
|
+
// (e.g. about server-side detected MITM attack)
|
526
|
+
if (responseDataJson["log"].isString()) {
|
527
|
+
string additional = responseDataJson["log"].asString();
|
528
|
+
if (additional.length() > 0) {
|
529
|
+
logUpdateSuccessAdditional(" Additional information: " + additional);
|
530
|
+
}
|
531
|
+
}
|
532
|
+
} while (0);
|
533
|
+
|
534
|
+
#if BOOST_OS_MACOS
|
535
|
+
crypto->killKey(CLIENT_CERT_LABEL);
|
536
|
+
#endif
|
537
|
+
|
538
|
+
if (signatureChars) {
|
539
|
+
free(signatureChars);
|
540
|
+
}
|
541
|
+
if (dataChars) {
|
542
|
+
free(dataChars);
|
543
|
+
}
|
544
|
+
curl_slist_free_all(chunk);
|
545
|
+
curl_easy_cleanup(curl);
|
546
|
+
|
547
|
+
return backoffMin;
|
548
|
+
}
|
549
|
+
|
550
|
+
static size_t receiveResponseBytes(void *buffer, size_t size, size_t nmemb, void *userData) {
|
551
|
+
string *responseData = (string *) userData;
|
552
|
+
responseData->append((const char *) buffer, size * nmemb);
|
553
|
+
return size * nmemb;
|
554
|
+
}
|
555
|
+
|
556
|
+
};
|
557
|
+
}
|
558
|
+
|
559
|
+
#endif /* _PASSENGER_UPDATE_CHECKER_H_ */
|