passenger 4.0.7 → 4.0.8

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.

data.tar.gz.asc CHANGED
@@ -2,11 +2,11 @@
2
2
  Version: GnuPG/MacGPG2 v2.0.17 (Darwin)
3
3
  Comment: GPGTools - http://gpgtools.org
4
4
 
5
- iQEcBAABAgAGBQJR1pxdAAoJECrHRaUKISqMqh4IAINOF8pLt0MCBN50TlXc162j
6
- 2Oco+OJcp9pPxv2id5N4CObL0tVJapvmDxBCcsOKEGalR4ssrUd/mxjYronRcwft
7
- daDDGCEiWH/V6Nh7+m3ENhsucEIz1duOWjm+vKLOBzoTcIRKtNSC1RdTw5HCJCC2
8
- /Z/He4iKHJwfrvelia8vQdhBe0/CYEYKekLnJc3kRvcZabbLmzsWNw/R/4mYk4d0
9
- iTHpU8JgtKy3PiDqpFWcd/R3gEzEAq8J3RE8baEtZgm4DFlNOYncvBZpcQe0EFwe
10
- nsrovBNemt++Im6DeiMG5yuQVT5E/YXpIryhCKZEV1IQGCXIY4rJNLh3mcC7BDg=
11
- =uTYu
5
+ iQEcBAABAgAGBQJR28iYAAoJECrHRaUKISqMPf4IAIGf9BCAw6AwVnoumKhaWkim
6
+ k+hKA1kkPofS9Q7DZPEA5sizgSB1RCW8clalOF5Wb+kCn3KNwfluBENw2Bl974+A
7
+ HubjUnT58//z4bNqx7cEjBxfVWrs8VwINd8nbtEnjVaZG9Xkh+zinGsBwVbvgBE/
8
+ kwwklTV25qOPV3E9eLvWprB4i6lZEVCpsgJzn6HHz+mOxy9OS2VEGJQKZcCLU4WD
9
+ EWiinu8mJmKWGDHAigzNs6xFCfR9i8tRp56y9p5nQrxDqAhYaGdcwPPvD2iNabgh
10
+ dtnid9XFZeEOG5PtNktJ8P/2VX3ziFKnsvq5qYQMrV/lVQdBDeZDlcTfTHBDgqw=
11
+ =V08b
12
12
  -----END PGP SIGNATURE-----
@@ -22,6 +22,7 @@ Erik Ogan
22
22
  Evan Phoenix
23
23
  Gaspard Bucher
24
24
  Goffert van Gool
25
+ Gokulnath Manakkattil
25
26
  Gregory Potamianos
26
27
  Hongli Lai (Phusion)
27
28
  Ian Ehlert
@@ -56,6 +57,7 @@ Philip M. Gollucci
56
57
  Redmar Kerkhoff
57
58
  remi
58
59
  Robin Bowes
60
+ Ryan Schwartz
59
61
  Ryo Onodera
60
62
  Saimon Moore
61
63
  Sam Pohlenz
data/NEWS CHANGED
@@ -1,3 +1,18 @@
1
+ Release 4.0.8
2
+ -------------
3
+
4
+ * Fixed a problem with graceful web server restarts. When you gracefully
5
+ restart the web server, it would cause Phusion Passenger internal sockets
6
+ to be deleted, thus causing Phusion Passenger to go down. This problem
7
+ was introduced in 4.0.6 during the attempt to fix issue #910.
8
+ * The PassengerRestartDir/passenger_restart_dir now accepts relative
9
+ filenames again, just like in Phusion Passenger 3.x. Patch
10
+ contributed by Ryan Schwartz.
11
+ * Documentation updates contributed by Gokulnath Manakkattil.
12
+ * [Enterprise] Fixed a license key checking issue on some operating systems,
13
+ such as CentOS 6.
14
+
15
+
1
16
  Release 4.0.7
2
17
  -------------
3
18
 
@@ -112,6 +112,7 @@ logging_agent_libs = COMMON_LIBRARY.only(:base, :logging_agent, 'AgentsBase.o',
112
112
  'Utils/Base64.o', 'Utils/MD5.o')
113
113
  dependencies = [
114
114
  'ext/common/agents/LoggingAgent/Main.cpp',
115
+ 'ext/common/agents/LoggingAgent/AdminController.h',
115
116
  'ext/common/agents/LoggingAgent/LoggingServer.h',
116
117
  'ext/common/agents/LoggingAgent/RemoteSender.h',
117
118
  'ext/common/agents/LoggingAgent/DataStoreId.h',
@@ -166,6 +166,8 @@ task :contributors do
166
166
  entries.push "Ninh Bui (Phusion)"
167
167
  entries.delete "Tinco Andringa"
168
168
  entries.push "Tinco Andringa (Phusion)"
169
+ entries.delete "Gokulnath"
170
+ entries.push "Gokulnath Manakkattil"
169
171
  File.open("CONTRIBUTORS", "w") do |f|
170
172
  f.puts(entries.sort{ |a, b| a.downcase <=> b.downcase }.join("\n"))
171
173
  end
@@ -2886,6 +2886,7 @@ In an <em>if</em> configuration scope.
2886
2886
  # The web app under www.bar.com/blog will use JRuby 1.7.1
2887
2887
  passenger_base_uri /blog;
2888
2888
  location /blog {
2889
+ passenger_enabled on;
2889
2890
  passenger_ruby /usr/local/rvm/wrappers/jruby-1.7.1/ruby;
2890
2891
  }
2891
2892
  }
@@ -510,6 +510,7 @@ http {
510
510
  # The web app under www.bar.com/blog will use JRuby 1.7.1
511
511
  passenger_base_uri /blog;
512
512
  location /blog {
513
+ passenger_enabled on;
513
514
  passenger_ruby /usr/local/rvm/wrappers/jruby-1.7.1/ruby;
514
515
  }
515
516
  }
@@ -278,7 +278,8 @@ private:
278
278
 
279
279
  if (!connected) {
280
280
  UPDATE_TRACE_POINT();
281
- throw IOException("Cannot connect to the helper agent");
281
+ throw IOException("Cannot connect to the helper agent at " +
282
+ agentsStarter.getRequestSocketFilename());
282
283
  }
283
284
  } else {
284
285
  throw;
@@ -62,16 +62,12 @@ public:
62
62
  ALL = ~0,
63
63
  NONE = 0,
64
64
 
65
- // ApplicationPool2::Server rights.
66
- GET = 1 << 0,
67
- CLEAR = 1 << 1,
68
- DETACH = 1 << 2,
69
- GET_PARAMETERS = 1 << 3,
70
- SET_PARAMETERS = 1 << 4,
71
- INSPECT_BASIC_INFO = 1 << 5,
72
- INSPECT_SENSITIVE_INFO = 1 << 6,
73
- //INSPECT_BACKEND_ADDRESSES = 1 << 6,
74
- //INSPECT_DETACH_KEYS = 1 << 7,
65
+ // HelperAgent ApplicationPool rights.
66
+ CLEAR = 1 << 0,
67
+ DETACH = 1 << 1,
68
+ SET_PARAMETERS = 1 << 2,
69
+ INSPECT_BASIC_INFO = 1 << 3,
70
+ INSPECT_SENSITIVE_INFO = 1 << 4,
75
71
 
76
72
  // HelperAgent admin rights.
77
73
  INSPECT_REQUESTS = 1 << 8,
@@ -103,14 +99,10 @@ public:
103
99
  } else if (*it == "none") {
104
100
  result = NONE;
105
101
 
106
- } else if (*it == "get") {
107
- result |= GET;
108
102
  } else if (*it == "clear") {
109
103
  result |= CLEAR;
110
104
  } else if (*it == "detach") {
111
105
  result |= DETACH;
112
- } else if (*it == "get_parameters") {
113
- result |= GET_PARAMETERS;
114
106
  } else if (*it == "set_parameters") {
115
107
  result |= SET_PARAMETERS;
116
108
  } else if (*it == "inspect_basic_info") {
@@ -118,6 +110,8 @@ public:
118
110
  } else if (*it == "inspect_sensitive_info") {
119
111
  result |= INSPECT_SENSITIVE_INFO;
120
112
 
113
+ } else if (*it == "inspect_requests") {
114
+ result |= INSPECT_REQUESTS;
121
115
  } else if (*it == "inspect_backtraces") {
122
116
  result |= INSPECT_BACKTRACES;
123
117
 
@@ -51,10 +51,6 @@ private:
51
51
  unsigned int uniqueNumber;
52
52
 
53
53
  public:
54
- static AccountsDatabasePtr createDefault(const ServerInstanceDir::GenerationPtr &generation,
55
- bool userSwitching, const string &defaultUser,
56
- const string &defaultGroup);
57
-
58
54
  AccountsDatabase() {
59
55
  uniqueNumber = 0;
60
56
  }
@@ -339,9 +339,12 @@ Group::Group(const SuperGroupPtr &_superGroup, const Options &options, const Com
339
339
  if (options.restartDir.empty()) {
340
340
  restartFile = options.appRoot + "/tmp/restart.txt";
341
341
  alwaysRestartFile = options.appRoot + "/always_restart.txt";
342
- } else {
342
+ } else if (options.restartDir[0] == '/') {
343
343
  restartFile = options.restartDir + "/restart.txt";
344
344
  alwaysRestartFile = options.restartDir + "/always_restart.txt";
345
+ } else {
346
+ restartFile = options.appRoot + "/" + options.restartDir + "/restart.txt";
347
+ alwaysRestartFile = options.appRoot + "/" + options.restartDir + "/always_restart.txt";
345
348
  }
346
349
  resetOptions(options);
347
350
 
@@ -70,7 +70,7 @@
70
70
 
71
71
  #define PROCESS_SHUTDOWN_TIMEOUT_DISPLAY "1 minute"
72
72
 
73
- #define PASSENGER_VERSION "4.0.7"
73
+ #define PASSENGER_VERSION "4.0.8"
74
74
 
75
75
  #define SERVER_INSTANCE_DIR_STRUCTURE_MAJOR_VERSION 1
76
76
 
@@ -205,9 +205,18 @@ public:
205
205
  writeArrayMessage(fd, "SecurityException", "Insufficient rights to execute this command.", NULL);
206
206
  throw SecurityException("Insufficient rights to execute this command.");
207
207
  } else {
208
- writeArrayMessage(fd, "Passed security", NULL);
208
+ passSecurity();
209
209
  }
210
210
  }
211
+
212
+ /** Announce to the client that it has passed the security checks.
213
+ *
214
+ * @throws SystemException Something went wrong while communicating with the client.
215
+ * @throws boost::thread_interrupted
216
+ */
217
+ void passSecurity() {
218
+ writeArrayMessage(fd, "Passed security", NULL);
219
+ }
211
220
  };
212
221
 
213
222
  /**
@@ -218,6 +227,26 @@ public:
218
227
  * client is closed.
219
228
  */
220
229
  class Handler {
230
+ protected:
231
+ /** Utility function for checking whether the command name equals `command`,
232
+ * and whether it has exactly `nargs` arguments (excluding command name).
233
+ */
234
+ bool isCommand(const vector<string> &args, const string &command,
235
+ unsigned int nargs = 0) const
236
+ {
237
+ return args.size() == nargs + 1 && args[0] == command;
238
+ }
239
+
240
+ /** Utility function for checking whether the command name equals `command`,
241
+ * and whether it has at least `minargs` and at most `maxargs` arguments
242
+ * (excluding command name), inclusive.
243
+ */
244
+ bool isCommand(const vector<string> &args, const string &command,
245
+ unsigned int minargs, unsigned int maxargs) const
246
+ {
247
+ return args.size() >= minargs + 1 && args.size() <= maxargs + 1 && args[0] == command;
248
+ }
249
+
221
250
  public:
222
251
  virtual ~Handler() { }
223
252
 
@@ -25,6 +25,7 @@
25
25
  #ifndef _PASSENGER_RESOURCE_LOCATOR_H_
26
26
  #define _PASSENGER_RESOURCE_LOCATOR_H_
27
27
 
28
+ #include <boost/shared_ptr.hpp>
28
29
  #include <string>
29
30
  #include <Exceptions.h>
30
31
  #include <Utils.h>
@@ -32,6 +33,7 @@
32
33
 
33
34
  namespace Passenger {
34
35
 
36
+ using namespace std;
35
37
  using namespace boost;
36
38
 
37
39
 
@@ -108,6 +110,8 @@ public:
108
110
  }
109
111
  };
110
112
 
113
+ typedef shared_ptr<ResourceLocator> ResourceLocatorPtr;
114
+
111
115
 
112
116
  }
113
117
 
@@ -40,6 +40,7 @@
40
40
  #include <string>
41
41
 
42
42
  #include <Constants.h>
43
+ #include <Logging.h>
43
44
  #include <Exceptions.h>
44
45
  #include <Utils.h>
45
46
  #include <Utils/StrIntUtils.h>
@@ -49,6 +50,15 @@ namespace Passenger {
49
50
  using namespace std;
50
51
  using namespace boost;
51
52
 
53
+ /* TODO: I think we should move away from generation dirs in the future.
54
+ * That way we can become immune to existing-directory-in-tmp denial of
55
+ * service attacks. To achieve the same functionality as we do now, each
56
+ * server instance directory is tagged with the control process's PID
57
+ * and a creation timestamp. passenger-status should treat the server instance
58
+ * directory with the most recent creation timestamp as the one to query.
59
+ * For now, the current code does not lead to an exploit.
60
+ */
61
+
52
62
  class ServerInstanceDir: public noncopyable {
53
63
  public:
54
64
  class Generation: public noncopyable {
@@ -221,8 +231,7 @@ private:
221
231
  createDirectory(path);
222
232
  break;
223
233
  case FT_DIRECTORY:
224
- removeDirTree(path);
225
- createDirectory(path);
234
+ verifyDirectoryPermissions(path);
226
235
  break;
227
236
  default:
228
237
  throw RuntimeException("'" + path + "' already exists, and is not a directory");
@@ -242,6 +251,43 @@ private:
242
251
  throw FileSystemException("Cannot create server instance directory '" +
243
252
  path + "'", e, path);
244
253
  }
254
+ // verifyDirectoryPermissions() checks for the owner/group so we must make
255
+ // sure the server instance directory has that owner/group, even when the
256
+ // parent directory has setgid on.
257
+ if (chown(path.c_str(), geteuid(), getegid()) == -1) {
258
+ int e = errno;
259
+ throw FileSystemException("Cannot change the permissions of the server "
260
+ "instance directory '" + path + "'", e, path);
261
+ }
262
+ }
263
+
264
+ /**
265
+ * When reusing an existing server instance directory, check permissions
266
+ * so that an attacker cannot pre-create a directory with too liberal
267
+ * permissions.
268
+ */
269
+ void verifyDirectoryPermissions(const string &path) {
270
+ TRACE_POINT();
271
+ struct stat buf;
272
+
273
+ if (stat(path.c_str(), &buf) == -1) {
274
+ int e = errno;
275
+ throw FileSystemException("Cannot stat() " + path, e, path);
276
+ } else if (buf.st_mode != (S_IFDIR | parseModeString("u=rwx,g=rx,o=rx"))) {
277
+ throw RuntimeException("Tried to reuse existing server instance directory " +
278
+ path + ", but it has wrong permissions");
279
+ } else if (buf.st_uid != geteuid() || buf.st_gid != getegid()) {
280
+ /* The server instance directory is always created by the Watchdog. Its UID/GID never
281
+ * changes because:
282
+ * 1. Disabling user switching only lowers the privilege of the HelperAgent.
283
+ * 2. For the UID/GID to change, the web server must be completely restarted
284
+ * (not just graceful reload) so that the control process can change its UID/GID.
285
+ * This causes the PID to change, so that an entirely new server instance
286
+ * directory is created.
287
+ */
288
+ throw RuntimeException("Tried to reuse existing server instance directory " +
289
+ path + ", but it has wrong owner and group");
290
+ }
245
291
  }
246
292
 
247
293
  bool isDirectory(const string &dir, struct dirent *entry) const {
@@ -181,17 +181,17 @@ public:
181
181
  {
182
182
  SpecificContext *specificContext = (SpecificContext *) _specificContext.get();
183
183
  try {
184
- if (args[0] == "detach_process" && args.size() == 2) {
184
+ if (isCommand(args, "detach_process", 1)) {
185
185
  processDetachProcess(commonContext, specificContext, args);
186
- } else if (args[0] == "detach_process_by_key" && args.size() == 2) {
186
+ } else if (isCommand(args, "detach_process_by_key", 1)) {
187
187
  processDetachProcessByKey(commonContext, specificContext, args);
188
188
  } else if (args[0] == "inspect") {
189
189
  return processInspect(commonContext, specificContext, args);
190
- } else if (args[0] == "toXml" && args.size() == 2) {
190
+ } else if (isCommand(args, "toXml", 1)) {
191
191
  processToXml(commonContext, specificContext, args);
192
- } else if (args[0] == "backtraces") {
192
+ } else if (isCommand(args, "backtraces", 0)) {
193
193
  processBacktraces(commonContext, specificContext, args);
194
- } else if (args[0] == "requests") {
194
+ } else if (isCommand(args, "requests", 0)) {
195
195
  processRequests(commonContext, specificContext, args);
196
196
  } else {
197
197
  return false;
@@ -44,26 +44,25 @@ private:
44
44
 
45
45
  typedef MessageServer::CommonClientContext CommonClientContext;
46
46
 
47
- const LoggingServer *server;
47
+ LoggingServerPtr server;
48
48
 
49
49
 
50
50
  /*********************************************
51
51
  * Message handler methods
52
52
  *********************************************/
53
53
 
54
- void processStatus(CommonClientContext &commonContext, SpecificContext *specificContext, const vector<string> &args) {
54
+ void processStatus(CommonClientContext &commonContext, SpecificContext *specificContext,
55
+ const vector<string> &args)
56
+ {
55
57
  TRACE_POINT();
58
+ commonContext.passSecurity();
56
59
  stringstream stream;
57
60
  server->dump(stream);
58
61
  writeScalarMessage(commonContext.fd, stream.str());
59
62
  }
60
63
 
61
- bool isCommand(const vector<string> &args, const string &command, unsigned int nargs = 0) const {
62
- return args.size() == nargs + 1 && args[0] == command;
63
- }
64
-
65
64
  public:
66
- AdminController(const LoggingServer *server) {
65
+ AdminController(const LoggingServerPtr &server) {
67
66
  this->server = server;
68
67
  }
69
68
 
@@ -76,10 +75,16 @@ public:
76
75
  const vector<string> &args)
77
76
  {
78
77
  SpecificContext *specificContext = (SpecificContext *) _specificContext.get();
79
- if (isCommand(args, "status", 0)) {
80
- processStatus(commonContext, specificContext, args);
81
- } else {
82
- return false;
78
+ try {
79
+ if (isCommand(args, "status", 0)) {
80
+ processStatus(commonContext, specificContext, args);
81
+ } else {
82
+ return false;
83
+ }
84
+ } catch (const SecurityException &) {
85
+ /* Client does not have enough rights to perform a certain action.
86
+ * It has already been notified of this; ignore exception and move on.
87
+ */
83
88
  }
84
89
  return true;
85
90
  }
@@ -1098,7 +1098,7 @@ public:
1098
1098
  LoggingServer(struct ev_loop *loop,
1099
1099
  FileDescriptor fd,
1100
1100
  const AccountsDatabasePtr &accountsDatabase,
1101
- const VariantMap &options = VariantMap()/**/)
1101
+ const VariantMap &options = VariantMap())
1102
1102
  : EventedMessageServer(loop, fd, accountsDatabase),
1103
1103
  remoteSender(
1104
1104
  options.get("union_station_gateway_address", false, DEFAULT_UNION_STATION_GATEWAY_ADDRESS),
@@ -55,33 +55,128 @@ using namespace oxt;
55
55
  using namespace Passenger;
56
56
 
57
57
 
58
- static struct ev_loop *eventLoop;
59
- static LoggingServer *loggingServer;
60
- static int exitCode = 0;
58
+ /***** Agent options *****/
59
+
60
+ static VariantMap agentsOptions;
61
+ static string passengerRoot;
62
+ static string socketAddress;
63
+ static string adminSocketAddress;
64
+ static string password;
65
+ static string username;
66
+ static string groupname;
67
+ static string adminToolStatusPassword;
68
+
69
+ /***** Constants and working objects *****/
61
70
 
62
71
  static const int MESSAGE_SERVER_THREAD_STACK_SIZE = 128 * 1024;
63
72
 
73
+ struct WorkingObjects {
74
+ ResourceLocatorPtr resourceLocator;
75
+ FileDescriptor serverSocketFd;
76
+ AccountsDatabasePtr adminAccountsDatabase;
77
+ MessageServerPtr adminServer;
78
+ shared_ptr<oxt::thread> adminServerThread;
79
+ AccountsDatabasePtr accountsDatabase;
80
+ LoggingServerPtr loggingServer;
64
81
 
65
- static struct ev_loop *
66
- createEventLoop() {
67
- struct ev_loop *loop;
68
-
69
- // libev doesn't like choosing epoll and kqueue because the author thinks they're broken,
70
- // so let's try to force it.
71
- loop = ev_default_loop(EVBACKEND_EPOLL);
72
- if (loop == NULL) {
73
- loop = ev_default_loop(EVBACKEND_KQUEUE);
82
+ ~WorkingObjects() {
83
+ // Stop thread before destroying anything else.
84
+ if (adminServerThread != NULL) {
85
+ adminServerThread->interrupt_and_join();
86
+ }
74
87
  }
75
- if (loop == NULL) {
76
- loop = ev_default_loop(0);
88
+ };
89
+
90
+ static struct ev_loop *eventLoop = NULL;
91
+ static LoggingServer *loggingServer = NULL;
92
+ static int exitCode = 0;
93
+
94
+
95
+ /***** Functions *****/
96
+
97
+ void
98
+ feedbackFdBecameReadable(ev::io &watcher, int revents) {
99
+ /* This event indicates that the watchdog has been killed.
100
+ * In this case we'll kill all descendant
101
+ * processes and exit. There's no point in keeping this agent
102
+ * running because we can't detect when the web server exits,
103
+ * and because this agent doesn't own the server instance
104
+ * directory. As soon as passenger-status is run, the server
105
+ * instance directory will be cleaned up, making this agent's
106
+ * services inaccessible.
107
+ */
108
+ syscalls::killpg(getpgrp(), SIGKILL);
109
+ _exit(2); // In case killpg() fails.
110
+ }
111
+
112
+ static string
113
+ myself() {
114
+ struct passwd *entry = getpwuid(geteuid());
115
+ if (entry != NULL) {
116
+ return entry->pw_name;
117
+ } else {
118
+ throw NonExistentUserException(string("The current user, UID ") +
119
+ toString(geteuid()) + ", doesn't have a corresponding " +
120
+ "entry in the system's user database. Please fix your " +
121
+ "system's user database first.");
77
122
  }
78
- if (loop == NULL) {
79
- throw RuntimeException("Cannot create an event loop");
123
+ }
124
+
125
+ static void
126
+ initializeBareEssentials(int argc, char *argv[]) {
127
+ agentsOptions = initializeAgent(argc, argv, "PassengerLoggingAgent");
128
+ curl_global_init(CURL_GLOBAL_ALL);
129
+ }
130
+
131
+ static string
132
+ findUnionStationGatewayCert(const ResourceLocator &locator,
133
+ const string &cert)
134
+ {
135
+ if (cert.empty()) {
136
+ return locator.getResourcesDir() + "/union_station_gateway.crt";
137
+ } else if (cert != "-") {
138
+ return cert;
80
139
  } else {
81
- return loop;
140
+ return "";
82
141
  }
83
142
  }
84
143
 
144
+ static void
145
+ initializeOptions(WorkingObjects &wo) {
146
+ passengerRoot = agentsOptions.get("passenger_root");
147
+ socketAddress = agentsOptions.get("logging_agent_address");
148
+ adminSocketAddress = agentsOptions.get("logging_agent_admin_address");
149
+ password = agentsOptions.get("logging_agent_password");
150
+ username = agentsOptions.get("analytics_log_user", false, myself());
151
+ groupname = agentsOptions.get("analytics_log_group", false);
152
+ adminToolStatusPassword = agentsOptions.get("admin_tool_status_password");
153
+
154
+ wo.resourceLocator = make_shared<ResourceLocator>(passengerRoot);
155
+ agentsOptions.set("union_station_gateway_cert", findUnionStationGatewayCert(
156
+ *wo.resourceLocator, agentsOptions.get("union_station_gateway_cert", false)));
157
+ }
158
+
159
+ static void
160
+ initializePrivilegedWorkingObjects(WorkingObjects &wo) {
161
+ wo.serverSocketFd = createServer(socketAddress.c_str());
162
+ if (getSocketAddressType(socketAddress) == SAT_UNIX) {
163
+ int ret;
164
+
165
+ do {
166
+ ret = chmod(parseUnixSocketAddress(socketAddress).c_str(),
167
+ S_ISVTX |
168
+ S_IRUSR | S_IWUSR | S_IXUSR |
169
+ S_IRGRP | S_IWGRP | S_IXGRP |
170
+ S_IROTH | S_IWOTH | S_IXOTH);
171
+ } while (ret == -1 && errno == EINTR);
172
+ }
173
+
174
+ wo.adminAccountsDatabase = make_shared<AccountsDatabase>();
175
+ wo.adminAccountsDatabase->add("_passenger-status", adminToolStatusPassword, false);
176
+ wo.adminServer = make_shared<MessageServer>(parseUnixSocketAddress(adminSocketAddress),
177
+ wo.adminAccountsDatabase);
178
+ }
179
+
85
180
  static void
86
181
  lowerPrivilege(const string &username, const struct passwd *user, const struct group *group) {
87
182
  int e;
@@ -108,24 +203,99 @@ lowerPrivilege(const string &username, const struct passwd *user, const struct g
108
203
  }
109
204
  }
110
205
 
111
- void
112
- feedbackFdBecameReadable(ev::io &watcher, int revents) {
113
- /* This event indicates that the watchdog has been killed.
114
- * In this case we'll kill all descendant
115
- * processes and exit. There's no point in keeping this agent
116
- * running because we can't detect when the web server exits,
117
- * and because this agent doesn't own the server instance
118
- * directory. As soon as passenger-status is run, the server
119
- * instance directory will be cleaned up, making this agent's
120
- * services inaccessible.
121
- */
122
- syscalls::killpg(getpgrp(), SIGKILL);
123
- _exit(2); // In case killpg() fails.
206
+ static void
207
+ maybeLowerPrivilege() {
208
+ struct passwd *user;
209
+ struct group *group;
210
+
211
+ /* Sanity check user accounts. */
212
+
213
+ user = getpwnam(username.c_str());
214
+ if (user == NULL) {
215
+ throw NonExistentUserException(string("The configuration option ") +
216
+ "'PassengerAnalyticsLogUser' (Apache) or " +
217
+ "'passenger_analytics_log_user' (Nginx) was set to '" +
218
+ username + "', but this user doesn't exist. Please fix " +
219
+ "the configuration option.");
220
+ }
221
+
222
+ if (groupname.empty()) {
223
+ group = getgrgid(user->pw_gid);
224
+ if (group == NULL) {
225
+ throw NonExistentGroupException(string("The configuration option ") +
226
+ "'PassengerAnalyticsLogGroup' (Apache) or " +
227
+ "'passenger_analytics_log_group' (Nginx) wasn't set, " +
228
+ "so PassengerLoggingAgent tried to use the default group " +
229
+ "for user '" + username + "' - which is GID #" +
230
+ toString(user->pw_gid) + " - as the group for the analytics " +
231
+ "log dir, but this GID doesn't exist. " +
232
+ "You can solve this problem by explicitly " +
233
+ "setting PassengerAnalyticsLogGroup (Apache) or " +
234
+ "passenger_analytics_log_group (Nginx) to a group that " +
235
+ "does exist. In any case, it looks like your system's user " +
236
+ "database is broken; Phusion Passenger can work fine even " +
237
+ "with this broken user database, but you should still fix it.");
238
+ } else {
239
+ groupname = group->gr_name;
240
+ }
241
+ } else {
242
+ group = getgrnam(groupname.c_str());
243
+ if (group == NULL) {
244
+ throw NonExistentGroupException(string("The configuration option ") +
245
+ "'PassengerAnalyticsLogGroup' (Apache) or " +
246
+ "'passenger_analytics_log_group' (Nginx) was set to '" +
247
+ groupname + "', but this group doesn't exist. Please fix " +
248
+ "the configuration option.");
249
+ }
250
+ }
251
+
252
+ /* Now's a good time to lower the privilege. */
253
+ if (geteuid() == 0) {
254
+ lowerPrivilege(username, user, group);
255
+ }
256
+ }
257
+
258
+ static struct ev_loop *
259
+ createEventLoop() {
260
+ struct ev_loop *loop;
261
+
262
+ // libev doesn't like choosing epoll and kqueue because the author thinks they're broken,
263
+ // so let's try to force it.
264
+ loop = ev_default_loop(EVBACKEND_EPOLL);
265
+ if (loop == NULL) {
266
+ loop = ev_default_loop(EVBACKEND_KQUEUE);
267
+ }
268
+ if (loop == NULL) {
269
+ loop = ev_default_loop(0);
270
+ }
271
+ if (loop == NULL) {
272
+ throw RuntimeException("Cannot create an event loop");
273
+ } else {
274
+ return loop;
275
+ }
276
+ }
277
+
278
+ static void
279
+ initializeUnprivilegedWorkingObjects(WorkingObjects &wo) {
280
+ eventLoop = createEventLoop();
281
+ wo.accountsDatabase = make_shared<AccountsDatabase>();
282
+ wo.accountsDatabase->add("logging", password, false);
283
+
284
+ wo.loggingServer = make_shared<LoggingServer>(eventLoop, wo.serverSocketFd,
285
+ wo.accountsDatabase, agentsOptions);
286
+ loggingServer = wo.loggingServer.get();
287
+
288
+ wo.adminServer->addHandler(make_shared<AdminController>(wo.loggingServer));
289
+ function<void ()> adminServerFunc = boost::bind(&MessageServer::mainLoop, wo.adminServer.get());
290
+ wo.adminServerThread = make_shared<oxt::thread>(
291
+ boost::bind(runAndPrintExceptions, adminServerFunc, true),
292
+ "AdminServer thread", MESSAGE_SERVER_THREAD_STACK_SIZE
293
+ );
124
294
  }
125
295
 
126
296
  void
127
297
  caughtExitSignal(ev::sig &watcher, int revents) {
128
- P_DEBUG("Caught signal, exiting...");
298
+ P_INFO("Caught signal, exiting...");
129
299
  ev_break(eventLoop, EVBREAK_ONE);
130
300
  /* We only consider the "exit" command to be a graceful way to shut down
131
301
  * the logging agent, so upon receiving an exit signal we want to return
@@ -143,162 +313,43 @@ printInfo(ev::sig &watcher, int revents) {
143
313
  cerr << "---------- End LoggingAgent status ----------\n";
144
314
  }
145
315
 
146
- static string
147
- myself() {
148
- struct passwd *entry = getpwuid(geteuid());
149
- if (entry != NULL) {
150
- return entry->pw_name;
151
- } else {
152
- throw NonExistentUserException(string("The current user, UID ") +
153
- toString(geteuid()) + ", doesn't have a corresponding " +
154
- "entry in the system's user database. Please fix your " +
155
- "system's user database first.");
156
- }
157
- }
158
-
159
- static string
160
- findUnionStationGatewayCert(const ResourceLocator &locator,
161
- const string &cert)
162
- {
163
- if (cert.empty()) {
164
- return locator.getResourcesDir() + "/union_station_gateway.crt";
165
- } else if (cert != "-") {
166
- return cert;
167
- } else {
168
- return "";
316
+ static void
317
+ runMainLoop(WorkingObjects &wo) {
318
+ ev::io feedbackFdWatcher(eventLoop);
319
+ ev::sig sigintWatcher(eventLoop);
320
+ ev::sig sigtermWatcher(eventLoop);
321
+ ev::sig sigquitWatcher(eventLoop);
322
+
323
+ sigintWatcher.set<&caughtExitSignal>();
324
+ sigintWatcher.start(SIGINT);
325
+ sigtermWatcher.set<&caughtExitSignal>();
326
+ sigtermWatcher.start(SIGTERM);
327
+ sigquitWatcher.set<&printInfo>();
328
+ sigquitWatcher.start(SIGQUIT);
329
+
330
+ P_WARN("PassengerLoggingAgent online, listening at " << socketAddress);
331
+ if (feedbackFdAvailable()) {
332
+ feedbackFdWatcher.set<&feedbackFdBecameReadable>();
333
+ feedbackFdWatcher.start(FEEDBACK_FD, ev::READ);
334
+ writeArrayMessage(FEEDBACK_FD, "initialized", NULL);
169
335
  }
336
+ ev_run(eventLoop, 0);
170
337
  }
171
338
 
172
339
  int
173
340
  main(int argc, char *argv[]) {
174
- VariantMap options = initializeAgent(argc, argv, "PassengerLoggingAgent");
175
- string passengerRoot = options.get("passenger_root");
176
- string socketAddress = options.get("logging_agent_address");
177
- string adminSocketAddress = options.get("logging_agent_admin_address");
178
- string password = options.get("logging_agent_password");
179
- string username = options.get("analytics_log_user", false, myself());
180
- string groupname = options.get("analytics_log_group", false);
181
- string adminToolStatusPassword = options.get("admin_tool_status_password");
182
-
183
- curl_global_init(CURL_GLOBAL_ALL);
184
-
341
+ initializeBareEssentials(argc, argv);
342
+ P_DEBUG("Starting PassengerLoggingAgent...");
343
+
185
344
  try {
186
- /********** Now begins the real initialization **********/
187
-
188
- /* Create all the necessary objects and sockets... */
189
- ResourceLocator resourceLocator(passengerRoot);
190
- AccountsDatabasePtr accountsDatabase, adminAccountsDatabase;
191
- FileDescriptor serverSocketFd;
192
- struct passwd *user;
193
- struct group *group;
194
- int ret;
195
-
196
- options.set("union_station_gateway_cert", findUnionStationGatewayCert(
197
- resourceLocator, options.get("union_station_gateway_cert", false)));
198
-
199
- eventLoop = createEventLoop();
200
- accountsDatabase = make_shared<AccountsDatabase>();
201
- adminAccountsDatabase = make_shared<AccountsDatabase>();
202
- serverSocketFd = createServer(socketAddress.c_str());
203
- if (getSocketAddressType(socketAddress) == SAT_UNIX) {
204
- do {
205
- ret = chmod(parseUnixSocketAddress(socketAddress).c_str(),
206
- S_ISVTX |
207
- S_IRUSR | S_IWUSR | S_IXUSR |
208
- S_IRGRP | S_IWGRP | S_IXGRP |
209
- S_IROTH | S_IWOTH | S_IXOTH);
210
- } while (ret == -1 && errno == EINTR);
211
- }
212
-
213
- /* Sanity check user accounts. */
214
-
215
- user = getpwnam(username.c_str());
216
- if (user == NULL) {
217
- throw NonExistentUserException(string("The configuration option ") +
218
- "'PassengerAnalyticsLogUser' (Apache) or " +
219
- "'passenger_analytics_log_user' (Nginx) was set to '" +
220
- username + "', but this user doesn't exist. Please fix " +
221
- "the configuration option.");
222
- }
223
-
224
- if (groupname.empty()) {
225
- group = getgrgid(user->pw_gid);
226
- if (group == NULL) {
227
- throw NonExistentGroupException(string("The configuration option ") +
228
- "'PassengerAnalyticsLogGroup' (Apache) or " +
229
- "'passenger_analytics_log_group' (Nginx) wasn't set, " +
230
- "so PassengerLoggingAgent tried to use the default group " +
231
- "for user '" + username + "' - which is GID #" +
232
- toString(user->pw_gid) + " - as the group for the analytics " +
233
- "log dir, but this GID doesn't exist. " +
234
- "You can solve this problem by explicitly " +
235
- "setting PassengerAnalyticsLogGroup (Apache) or " +
236
- "passenger_analytics_log_group (Nginx) to a group that " +
237
- "does exist. In any case, it looks like your system's user " +
238
- "database is broken; Phusion Passenger can work fine even " +
239
- "with this broken user database, but you should still fix it.");
240
- } else {
241
- groupname = group->gr_name;
242
- }
243
- } else {
244
- group = getgrnam(groupname.c_str());
245
- if (group == NULL) {
246
- throw NonExistentGroupException(string("The configuration option ") +
247
- "'PassengerAnalyticsLogGroup' (Apache) or " +
248
- "'passenger_analytics_log_group' (Nginx) was set to '" +
249
- groupname + "', but this group doesn't exist. Please fix " +
250
- "the configuration option.");
251
- }
252
- }
345
+ TRACE_POINT();
346
+ WorkingObjects wo;
253
347
 
254
- /* Setup the admin server right before lowering privilege. */
255
- adminAccountsDatabase->add("_passenger-status", adminToolStatusPassword, false);
256
- MessageServer adminServer(parseUnixSocketAddress(adminSocketAddress),
257
- adminAccountsDatabase);
258
-
259
- /* Now's a good time to lower the privilege. */
260
- if (geteuid() == 0) {
261
- lowerPrivilege(username, user, group);
262
- }
263
-
264
- /* Now setup the actual logging server. */
265
- accountsDatabase->add("logging", password, false);
266
- LoggingServer server(eventLoop, serverSocketFd,
267
- accountsDatabase, options);
268
- loggingServer = &server;
269
-
270
- /* Continue setting up the admin server. */
271
- adminServer.addHandler(make_shared<AdminController>(&server));
272
- function<void ()> adminServerFunc = boost::bind(&MessageServer::mainLoop, &adminServer);
273
- oxt::thread adminServerThread(
274
- boost::bind(runAndPrintExceptions, adminServerFunc, true),
275
- "AdminServer thread", MESSAGE_SERVER_THREAD_STACK_SIZE
276
- );
277
-
278
-
279
- ev::io feedbackFdWatcher(eventLoop);
280
- ev::sig sigintWatcher(eventLoop);
281
- ev::sig sigtermWatcher(eventLoop);
282
- ev::sig sigquitWatcher(eventLoop);
283
-
284
- if (feedbackFdAvailable()) {
285
- feedbackFdWatcher.set<&feedbackFdBecameReadable>();
286
- feedbackFdWatcher.start(FEEDBACK_FD, ev::READ);
287
- writeArrayMessage(FEEDBACK_FD, "initialized", NULL);
288
- }
289
- sigintWatcher.set<&caughtExitSignal>();
290
- sigintWatcher.start(SIGINT);
291
- sigtermWatcher.set<&caughtExitSignal>();
292
- sigtermWatcher.start(SIGTERM);
293
- sigquitWatcher.set<&printInfo>();
294
- sigquitWatcher.start(SIGQUIT);
295
-
296
-
297
- /********** Initialized! Enter main loop... **********/
298
-
299
- P_WARN("PassengerLoggingAgent online, listening at " << socketAddress);
300
- ev_run(eventLoop, 0);
301
- adminServerThread.interrupt_and_join();
348
+ initializeOptions(wo);
349
+ initializePrivilegedWorkingObjects(wo);
350
+ maybeLowerPrivilege();
351
+ initializeUnprivilegedWorkingObjects(wo);
352
+ runMainLoop(wo);
302
353
  P_DEBUG("Logging agent exiting with code " << exitCode << ".");
303
354
  return exitCode;
304
355
  } catch (const tracable_exception &e) {
@@ -30,7 +30,7 @@ module PhusionPassenger
30
30
 
31
31
  PACKAGE_NAME = 'passenger'
32
32
  # Run 'rake ext/common/Constants.h' after changing this number.
33
- VERSION_STRING = '4.0.7'
33
+ VERSION_STRING = '4.0.8'
34
34
 
35
35
  PREFERRED_NGINX_VERSION = '1.4.1'
36
36
  NGINX_SHA256_CHECKSUM = 'bca5d1e89751ba29406185e1736c390412603a7e6b604f5b4575281f6565d119'
@@ -394,15 +394,6 @@ COMMON_LIBRARY = CommonLibraryBuilder.new do
394
394
  Utils/StrIntUtils.h
395
395
  Utils/CachedFileStat.h
396
396
  )
397
- define_component 'AccountsDatabase.o',
398
- :source => 'AccountsDatabase.cpp',
399
- :category => :other,
400
- :deps => %w(
401
- AccountsDatabase.h
402
- RandomGenerator.h
403
- Constants.h
404
- Utils.h
405
- )
406
397
  define_component 'AgentsStarter.o',
407
398
  :source => 'AgentsStarter.cpp',
408
399
  :category => :other,
@@ -131,6 +131,7 @@ class MessageClient
131
131
 
132
132
  def logging_agent_status
133
133
  write("status")
134
+ check_security_response
134
135
  return read_scalar
135
136
  end
136
137
 
@@ -108,7 +108,10 @@ module PreloaderSharedHelpers
108
108
  puts "!> "
109
109
 
110
110
  while true
111
- ios = select([server, STDIN])[0]
111
+ # We call ::select just in case someone overwrites the global select()
112
+ # function by including ActionView::Helpers in the wrong place.
113
+ # https://code.google.com/p/phusion-passenger/issues/detail?id=915
114
+ ios = Kernel.select([server, STDIN])[0]
112
115
  if ios.include?(server)
113
116
  result, client = accept_and_process_next_client(server)
114
117
  if result == :forked
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: passenger
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.7
4
+ version: 4.0.8
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-07-05 00:00:00.000000000 Z
12
+ date: 2013-07-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -285,7 +285,6 @@ files:
285
285
  - helper-scripts/system-memory-stats.py
286
286
  - helper-scripts/wsgi-loader.py
287
287
  - helper-scripts/wsgi-preloader.py
288
- - ext/common/AccountsDatabase.cpp
289
288
  - ext/common/agents/Base.cpp
290
289
  - ext/common/agents/HelperAgent/Main.cpp
291
290
  - ext/common/agents/HelperAgent/RequestHandler.cpp
metadata.gz.asc CHANGED
@@ -2,11 +2,11 @@
2
2
  Version: GnuPG/MacGPG2 v2.0.17 (Darwin)
3
3
  Comment: GPGTools - http://gpgtools.org
4
4
 
5
- iQEcBAABAgAGBQJR1pxdAAoJECrHRaUKISqMiqwH/3LCgw5ztPSGL4tTd1bRtx3i
6
- jRu4rb7iHj45z8vbbNSebe981xBlxxcNGVEmRnENNVErYM4C0CMDyHH8xyDjOn7D
7
- 9xYfBEhfZl3odLoI4eXoiWbkKxY+dKFr04ZCvC22EwTWiq89xT0Mxj46/tsbhG4H
8
- cU05PFmIlQX8bQQRAAdxqqMWCCBllwgYh05qmegSs/VYxsZbMiPisEunAsc+uRbF
9
- bC5T6Vjv4mvHxEKEXByc7bmP6nUnGHWHH8Vd1cdb+g5RxWKZOVFGnzzCWhAXKS1C
10
- fTcbZT0f2hG8ghcbHrEx4tB470IZ1DriocKPdGZRHqjZmsS/Kq8R/gS5FH+8tds=
11
- =Auh+
5
+ iQEcBAABAgAGBQJR28iYAAoJECrHRaUKISqMQI4H/07+WJ2+hUyYOccJ6FxrH/7e
6
+ 59ofy0PbGFEctyrfH7+TilJixj+xzKqn9q75q7eBNX8bK8gGD8zX1AwvgILvUWby
7
+ OU23wRacnuhYbJrRK6aeftCDNDsoLpLGvfYBr05aUE72q4lUGuoE7A9Yc+1JVJPN
8
+ DEpwf5BhcF0MGCtTZtuujCMBpHP6Dc+i1Ty9vhNYI4lvZgRNEwwU5MDyjH0ZJeyL
9
+ pK8+h7zhLL4ryS+h+g3HXRjEcvkth2ANaK/UhB74pWpnbqTqyMazpsSSK4XypHgh
10
+ 7ynPAf74eO8qfVZ1uPfwtm+53FXQIl5ix0MGg84osGJW3KUoyG1f5I1aUcAanZs=
11
+ =dSK/
12
12
  -----END PGP SIGNATURE-----
@@ -1,81 +0,0 @@
1
- /*
2
- * Phusion Passenger - https://www.phusionpassenger.com/
3
- * Copyright (c) 2010 Phusion
4
- *
5
- * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
6
- *
7
- * Permission is hereby granted, free of charge, to any person obtaining a copy
8
- * of this software and associated documentation files (the "Software"), to deal
9
- * in the Software without restriction, including without limitation the rights
10
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- * copies of the Software, and to permit persons to whom the Software is
12
- * furnished to do so, subject to the following conditions:
13
- *
14
- * The above copyright notice and this permission notice shall be included in
15
- * all copies or substantial portions of the Software.
16
- *
17
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- * THE SOFTWARE.
24
- */
25
-
26
- #include <sys/types.h>
27
- #include <sys/stat.h>
28
- #include <unistd.h>
29
- #include <pwd.h>
30
- #include <grp.h>
31
- #include "AccountsDatabase.h"
32
- #include "RandomGenerator.h"
33
- #include "Exceptions.h"
34
- #include "Constants.h"
35
- #include "Utils.h"
36
-
37
- namespace Passenger {
38
-
39
- AccountsDatabasePtr
40
- AccountsDatabase::createDefault(const ServerInstanceDir::GenerationPtr &generation,
41
- bool userSwitching, const string &defaultUser,
42
- const string &defaultGroup)
43
- {
44
- AccountsDatabasePtr database(new AccountsDatabase());
45
- struct passwd *defaultUserEntry;
46
- struct group *defaultGroupEntry;
47
- uid_t defaultUid;
48
- gid_t defaultGid;
49
- RandomGenerator random;
50
- string passengerStatusPassword = random.generateByteString(MESSAGE_SERVER_MAX_PASSWORD_SIZE);
51
-
52
- defaultUserEntry = getpwnam(defaultUser.c_str());
53
- if (defaultUserEntry == NULL) {
54
- throw NonExistentUserException("Default user '" + defaultUser +
55
- "' does not exist.");
56
- }
57
- defaultUid = defaultUserEntry->pw_uid;
58
- defaultGroupEntry = getgrnam(defaultGroup.c_str());
59
- if (defaultGroupEntry == NULL) {
60
- throw NonExistentGroupException("Default group '" + defaultGroup +
61
- "' does not exist.");
62
- }
63
- defaultGid = defaultGroupEntry->gr_gid;
64
-
65
- // An account for the 'passenger-status' command. Its password is only readable by
66
- // root, or (if user switching is turned off) only by the web server's user.
67
- database->add("_passenger-status", passengerStatusPassword, false,
68
- Account::INSPECT_BASIC_INFO | Account::INSPECT_SENSITIVE_INFO |
69
- Account::INSPECT_BACKTRACES);
70
- if (geteuid() == 0 && !userSwitching) {
71
- createFile(generation->getPath() + "/passenger-status-password.txt",
72
- passengerStatusPassword, S_IRUSR, defaultUid, defaultGid);
73
- } else {
74
- createFile(generation->getPath() + "/passenger-status-password.txt",
75
- passengerStatusPassword, S_IRUSR | S_IWUSR);
76
- }
77
-
78
- return database;
79
- }
80
-
81
- } // namespace Passenger