/* * Phusion Passenger - https://www.phusionpassenger.com/ * Copyright (c) 2014-2017 Phusion Holding B.V. * * "Passenger", "Phusion Passenger" and "Union Station" are registered * trademarks of Phusion Holding B.V. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef _PASSENGER_INSTANCE_DIRECTORY_H_ #define _PASSENGER_INSTANCE_DIRECTORY_H_ #ifdef USE_SELINUX #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Passenger { using namespace std; class InstanceDirectory { public: struct CreationOptions { string prefix; uid_t originalUid; bool userSwitching; uid_t defaultUid; gid_t defaultGid; Json::Value properties; CreationOptions() : prefix("passenger"), originalUid(geteuid()), userSwitching(true), defaultUid(USER_NOT_GIVEN), defaultGid(GROUP_NOT_GIVEN) { } }; private: const string path; bool owner; static string createUniquePath(const string ®istryDir, const string &prefix) { RandomGenerator generator; for (int i = 0; i < 250; i++) { string suffix = generator.generateAsciiString(7); string path = registryDir + "/" + prefix + "." + suffix; if (createPath(registryDir, path)) { return path; } } throw RuntimeException("Unable to create a unique directory inside " " instance registry directory " + registryDir + ", even after 250 tries"); } static bool createPath(const string ®istryDir, const string &path) { if (mkdir(path.c_str(), parseModeString("u=rwx,g=rx,o=rx")) == -1) { if (errno == EEXIST) { return false; } else { int e = errno; throw FileSystemException("Cannot create a subdirectory inside" " instance registry directory " + registryDir, e, registryDir); } } // Explicitly chmod the directory in case the umask is interfering. if (chmod(path.c_str(), parseModeString("u=rwx,g=rx,o=rx")) == -1) { int e = errno; throw FileSystemException("Cannot set permissions on instance directory " + path, e, path); } // The parent directory may have the setgid bit enabled, so we // explicitly chown it. if (chown(path.c_str(), geteuid(), getegid()) == -1) { int e = errno; throw FileSystemException("Cannot change the permissions of the instance " "directory " + path, e, path); } return true; } void initializeInstanceDirectory(const CreationOptions &options) { createPropertyFile(options); createWebServerInfoSubdir(options); createAgentSocketsSubdir(options); createAppSocketsSubdir(options); createLockFile(); } bool runningAsRoot(const CreationOptions &options) const { return options.originalUid == 0; } #ifdef USE_SELINUX void selinuxRelabel(const string &path, const char *newLabel) { security_context_t currentCon; string newCon; int e; if (getfilecon(path.c_str(), ¤tCon) == -1) { e = errno; P_DEBUG("Unable to obtain SELinux context for file " << path <<": " << strerror(e) << " (errno=" << e << ")"); return; } P_DEBUG("SELinux context for " << path << ": " << currentCon); if (strstr(currentCon, ":object_r:passenger_instance_content_t:") == NULL) { goto cleanup; } newCon = replaceString(currentCon, ":object_r:passenger_instance_content_t:", StaticString(":object_r:") + newLabel + ":"); P_DEBUG("Relabeling " << path << " to: " << newCon); if (setfilecon(path.c_str(), (security_context_t) newCon.c_str()) == -1) { e = errno; P_WARN("Cannot set SELinux context for " << path << " to " << newCon << ": " << strerror(e) << " (errno=" << e << ")"); goto cleanup; } cleanup: freecon(currentCon); } #endif void createWebServerInfoSubdir(const CreationOptions &options) { makeDirTree(path + "/web_server_info", "u=rwx,g=rx,o=rx"); #ifdef USE_SELINUX // We relabel the directory here instead of using setfscreatecon() // for thread-safety. It isn't specified whether InstanceDirectory // should be thread-safe, but let's do it this way to prevent // future problems. selinuxRelabel(path + "/web_server_info", "passenger_instance_httpd_dir_t"); #endif } void createAgentSocketsSubdir(const CreationOptions &options) { if (runningAsRoot(options)) { /* The server socket must be accessible by the web server * and by the apps, which may run as complete different users, * so this subdirectory must be world-accessible. */ makeDirTree(path + "/agents.s", "u=rwx,g=rx,o=rx"); } else { makeDirTree(path + "/agents.s", "u=rwx,g=,o="); } } void createAppSocketsSubdir(const CreationOptions &options) { if (runningAsRoot(options)) { if (options.userSwitching) { /* Each app may be running as a different user, * so the apps.s subdirectory must be world-writable. * However we don't want everybody to be able to know the * sockets' filenames, so the directory is not readable. */ makeDirTree(path + "/apps.s", "u=rwx,g=wx,o=wx,+t"); } else { /* All apps are running as defaultUser/defaultGroup, * so make defaultUser/defaultGroup the owner and group of the * subdirecory. * * The directory is not readable as a security precaution: * nobody should be able to know the sockets' filenames without * having access to the application pool. */ makeDirTree(path + "/apps.s", "u=rwx,g=x,o=x", options.defaultUid, options.defaultGid); } } else { /* All apps are running as the same user as the web server, * so only allow access for this user. */ makeDirTree(path + "/apps.s", "u=rwx,g=,o="); } } void createPropertyFile(const CreationOptions &options) { Json::Value props; props["instance_dir"]["major_version"] = SERVER_INSTANCE_DIR_STRUCTURE_MAJOR_VERSION; props["instance_dir"]["minor_version"] = SERVER_INSTANCE_DIR_STRUCTURE_MINOR_VERSION; props["instance_dir"]["created_at"] = (Json::Int64) time(NULL); props["instance_dir"]["created_at_monotonic_usec"] = (Json::UInt64) SystemTime::getMonotonicUsec(); props["passenger_version"] = PASSENGER_VERSION; props["watchdog_pid"] = (Json::UInt64) getpid(); Json::Value::Members members = options.properties.getMemberNames(); Json::Value::Members::const_iterator it, end = members.end(); for (it = members.begin(); it != end; it++) { props[*it] = options.properties.get(*it, Json::Value()); } createFile(path + "/properties.json", props.toStyledString()); } void createLockFile() { createFile(path + "/lock", ""); } public: InstanceDirectory(const CreationOptions &options) : path(createUniquePath(getSystemTempDir(), options.prefix)), owner(true) { initializeInstanceDirectory(options); } InstanceDirectory(const CreationOptions &options, const string ®istryDir) : path(createUniquePath(registryDir, options.prefix)), owner(true) { initializeInstanceDirectory(options); } InstanceDirectory(const string &dir) : path(dir), owner(false) { } ~InstanceDirectory() { if (owner) { destroy(); } } void finalizeCreation() { assert(owner); createFile(path + "/creation_finalized", ""); } // The 'const string &' here is on purpose. The WatchdogLauncher C // functions return the string pointer directly. const string &getPath() const { return path; } void detach() { owner = false; } bool isOwner() const { return owner; } void destroy() { assert(owner); owner = false; removeDirTree(path); } }; typedef boost::shared_ptr InstanceDirectoryPtr; } // namespace Passenger #endif /* _PASSENGER_INSTANCE_DIRECTORY_H_ */