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.

Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +30 -1
  3. data/CONTRIBUTING.md +1 -1
  4. data/CONTRIBUTORS +2 -0
  5. data/bin/passenger-install-nginx-module +18 -13
  6. data/build/agent.rb +1 -0
  7. data/build/basics.rb +1 -0
  8. data/build/cxx_tests.rb +6 -1
  9. data/build/misc.rb +3 -0
  10. data/build/packaging.rb +5 -17
  11. data/build/support/cxx_dependency_map.rb +100 -0
  12. data/build/support/vendor/cxxcodebuilder/lib/cxxcodebuilder/builder.rb +4 -1
  13. data/build/test_basics.rb +12 -2
  14. data/dev/ci/run_travis.sh +6 -2
  15. data/doc/Users guide Apache.html +7 -2
  16. data/doc/Users guide Apache.txt +4 -0
  17. data/resources/templates/error_layout.css +70 -84
  18. data/resources/templates/error_layout.html.template +84 -93
  19. data/resources/templates/standalone/http.erb +17 -13
  20. data/resources/templates/standalone/server.erb +2 -1
  21. data/resources/templates/undisclosed_error.html.template +52 -51
  22. data/resources/update_check_client_cert.p12 +0 -0
  23. data/resources/update_check_client_cert.pem +89 -0
  24. data/resources/update_check_server_pubkey.pem +14 -0
  25. data/src/agent/Core/ApplicationPool/ErrorRenderer.h +15 -1
  26. data/src/agent/Core/Controller.h +3 -2
  27. data/src/agent/Core/Controller/CheckoutSession.cpp +5 -4
  28. data/src/agent/Core/Controller/ForwardResponse.cpp +1 -1
  29. data/src/agent/Core/Controller/InitRequest.cpp +2 -0
  30. data/src/agent/Core/Controller/InitializationAndShutdown.cpp +1 -0
  31. data/src/agent/Core/Controller/Request.h +1 -0
  32. data/src/agent/Core/CoreMain.cpp +99 -2
  33. data/src/agent/Core/OptionParser.h +18 -1
  34. data/src/agent/Core/SecurityUpdateChecker.h +559 -0
  35. data/src/agent/Shared/Base.cpp +6 -1
  36. data/src/agent/TempDirToucher/TempDirToucherMain.cpp +52 -0
  37. data/src/agent/Watchdog/InstanceDirToucher.cpp +1 -2
  38. data/src/agent/Watchdog/WatchdogMain.cpp +31 -40
  39. data/src/apache2_module/Configuration.cpp +12 -0
  40. data/src/apache2_module/Configuration.hpp +5 -0
  41. data/src/apache2_module/ConfigurationCommands.cpp +19 -19
  42. data/src/apache2_module/ConfigurationCommands.cpp.cxxcodebuilder +2 -2
  43. data/src/apache2_module/ConfigurationFields.hpp +19 -19
  44. data/src/apache2_module/ConfigurationFields.hpp.cxxcodebuilder +2 -2
  45. data/src/apache2_module/ConfigurationSetters.cpp +19 -19
  46. data/src/apache2_module/ConfigurationSetters.cpp.cxxcodebuilder +2 -2
  47. data/src/apache2_module/CreateDirConfig.cpp +19 -19
  48. data/src/apache2_module/CreateDirConfig.cpp.cxxcodebuilder +2 -2
  49. data/src/apache2_module/Hooks.cpp +10 -1
  50. data/src/apache2_module/MergeDirConfig.cpp +19 -19
  51. data/src/apache2_module/MergeDirConfig.cpp.cxxcodebuilder +2 -2
  52. data/src/apache2_module/SetHeaders.cpp +19 -19
  53. data/src/apache2_module/SetHeaders.cpp.cxxcodebuilder +2 -2
  54. data/src/cxx_supportlib/Constants.h +22 -22
  55. data/src/cxx_supportlib/Constants.h.cxxcodebuilder +4 -1
  56. data/src/cxx_supportlib/Crypto.cpp +977 -0
  57. data/src/cxx_supportlib/Crypto.h +147 -0
  58. data/src/cxx_supportlib/InstanceDirectory.h +55 -2
  59. data/src/cxx_supportlib/Utils/Curl.h +24 -10
  60. data/src/cxx_supportlib/Utils/JsonUtils.h +1 -1
  61. data/src/cxx_supportlib/oxt/detail/spin_lock_darwin.hpp +2 -0
  62. data/src/cxx_supportlib/vendor-modified/boost/system/error_code.hpp +3 -3
  63. data/src/cxx_supportlib/vendor-modified/jsoncpp/json-forwards.h +167 -92
  64. data/src/cxx_supportlib/vendor-modified/jsoncpp/json.h +1827 -1542
  65. data/src/cxx_supportlib/vendor-modified/jsoncpp/jsoncpp.cpp +4705 -3652
  66. data/src/cxx_supportlib/vendor-modified/libev/Changes +46 -15
  67. data/src/cxx_supportlib/vendor-modified/libev/LICENSE +1 -1
  68. data/src/cxx_supportlib/vendor-modified/libev/Makefile.in +215 -128
  69. data/src/cxx_supportlib/vendor-modified/libev/aclocal.m4 +466 -275
  70. data/src/cxx_supportlib/vendor-modified/libev/config.guess +312 -418
  71. data/src/cxx_supportlib/vendor-modified/libev/config.sub +246 -105
  72. data/src/cxx_supportlib/vendor-modified/libev/configure +276 -72
  73. data/src/cxx_supportlib/vendor-modified/libev/configure.ac +2 -1
  74. data/src/cxx_supportlib/vendor-modified/libev/depcomp +346 -185
  75. data/src/cxx_supportlib/vendor-modified/libev/ev++.h +1 -1
  76. data/src/cxx_supportlib/vendor-modified/libev/ev.c +530 -190
  77. data/src/cxx_supportlib/vendor-modified/libev/ev.h +23 -14
  78. data/src/cxx_supportlib/vendor-modified/libev/ev_epoll.c +12 -6
  79. data/src/cxx_supportlib/vendor-modified/libev/ev_kqueue.c +9 -5
  80. data/src/cxx_supportlib/vendor-modified/libev/ev_poll.c +6 -3
  81. data/src/cxx_supportlib/vendor-modified/libev/ev_port.c +8 -4
  82. data/src/cxx_supportlib/vendor-modified/libev/ev_select.c +4 -2
  83. data/src/cxx_supportlib/vendor-modified/libev/ev_vars.h +3 -2
  84. data/src/cxx_supportlib/vendor-modified/libev/ev_win32.c +3 -4
  85. data/src/cxx_supportlib/vendor-modified/libev/install-sh +433 -219
  86. data/src/cxx_supportlib/vendor-modified/libev/libev.m4 +6 -6
  87. data/src/cxx_supportlib/vendor-modified/libev/ltmain.sh +2 -2
  88. data/src/cxx_supportlib/vendor-modified/libev/missing +167 -288
  89. data/src/cxx_supportlib/vendor-modified/libev/mkinstalldirs +72 -21
  90. data/src/cxx_supportlib/vendor-modified/modp_b64.cpp +4 -106
  91. data/src/cxx_supportlib/vendor-modified/modp_b64_data.h +37 -1
  92. data/src/cxx_supportlib/vendor-modified/modp_b64_strict_aliasing.cpp +119 -0
  93. data/src/helper-scripts/node-loader.js +72 -1
  94. data/src/nginx_module/CacheLocationConfig.c +52 -19
  95. data/src/nginx_module/CacheLocationConfig.c.cxxcodebuilder +2 -2
  96. data/src/nginx_module/Configuration.c +26 -1
  97. data/src/nginx_module/Configuration.h +2 -0
  98. data/src/nginx_module/ConfigurationCommands.c +35 -19
  99. data/src/nginx_module/ConfigurationCommands.c.cxxcodebuilder +2 -2
  100. data/src/nginx_module/ContentHandler.c +1 -1
  101. data/src/nginx_module/CreateLocationConfig.c +22 -19
  102. data/src/nginx_module/CreateLocationConfig.c.cxxcodebuilder +2 -2
  103. data/src/nginx_module/LocationConfig.h +21 -19
  104. data/src/nginx_module/LocationConfig.h.cxxcodebuilder +2 -2
  105. data/src/nginx_module/MergeLocationConfig.c +25 -19
  106. data/src/nginx_module/MergeLocationConfig.c.cxxcodebuilder +2 -2
  107. data/src/nginx_module/ngx_http_passenger_module.c +8 -4
  108. data/src/ruby_supportlib/phusion_passenger.rb +9 -4
  109. data/src/ruby_supportlib/phusion_passenger/admin_tools/instance.rb +2 -2
  110. data/src/ruby_supportlib/phusion_passenger/admin_tools/instance_registry.rb +1 -1
  111. data/src/ruby_supportlib/phusion_passenger/common_library.rb +13 -0
  112. data/src/ruby_supportlib/phusion_passenger/config/nginx_engine_compiler.rb +5 -2
  113. data/src/ruby_supportlib/phusion_passenger/constants.rb +1 -1
  114. data/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb +15 -3
  115. data/src/ruby_supportlib/phusion_passenger/platform_info/crypto.rb +51 -0
  116. data/src/ruby_supportlib/phusion_passenger/platform_info/depcheck_specs/apache2.rb +7 -0
  117. data/src/ruby_supportlib/phusion_passenger/standalone/config_options_list.rb +17 -0
  118. data/src/ruby_supportlib/phusion_passenger/standalone/start_command.rb +4 -2
  119. data/src/ruby_supportlib/phusion_passenger/standalone/start_command/builtin_engine.rb +4 -0
  120. data/src/ruby_supportlib/phusion_passenger/standalone/start_command/nginx_engine.rb +5 -0
  121. data/src/ruby_supportlib/phusion_passenger/vendor/crash_watch/app.rb +19 -10
  122. data/src/ruby_supportlib/phusion_passenger/vendor/crash_watch/base.rb +25 -0
  123. data/src/ruby_supportlib/phusion_passenger/vendor/crash_watch/gdb_controller.rb +38 -103
  124. data/src/ruby_supportlib/phusion_passenger/vendor/crash_watch/lldb_controller.rb +178 -0
  125. data/src/ruby_supportlib/phusion_passenger/vendor/crash_watch/utils.rb +94 -0
  126. data/src/ruby_supportlib/phusion_passenger/vendor/crash_watch/version.rb +2 -2
  127. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core.rb +2 -2
  128. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/version_data.rb +2 -2
  129. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/ruby_versions.yml.travis +5 -3
  130. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/ruby_versions.yml.travis-with-sudo +9 -7
  131. 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
- printf(" given memory limit (Enterprise only)\n");
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_ */