/* * Phusion Passenger - http://www.modrails.com/ * Copyright (c) 2008, 2009 Phusion * * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include "CachedFileStat.hpp" #include "Exceptions.h" #include "Utils.h" #define SPAWN_SERVER_SCRIPT_NAME "passenger-spawn-server" namespace Passenger { static string passengerTempDir; int atoi(const string &s) { return ::atoi(s.c_str()); } long atol(const string &s) { return ::atol(s.c_str()); } void split(const string &str, char sep, vector &output) { string::size_type start, pos; start = 0; output.clear(); while ((pos = str.find(sep, start)) != string::npos) { output.push_back(str.substr(start, pos - start)); start = pos + 1; } output.push_back(str.substr(start)); } bool fileExists(const char *filename, CachedFileStat *cstat, unsigned int throttleRate) { return getFileType(filename, cstat, throttleRate) == FT_REGULAR; } FileType getFileType(const char *filename, CachedFileStat *cstat, unsigned int throttleRate) { struct stat buf; int ret; if (cstat != NULL) { ret = cstat->stat(filename, &buf, throttleRate); } else { ret = stat(filename, &buf); } if (ret == 0) { if (S_ISREG(buf.st_mode)) { return FT_REGULAR; } else if (S_ISDIR(buf.st_mode)) { return FT_DIRECTORY; } else { return FT_OTHER; } } else { if (errno == ENOENT) { return FT_NONEXISTANT; } else { int e = errno; string message("Cannot stat '"); message.append(filename); message.append("'"); throw FileSystemException(message, e, filename); } } } string findSpawnServer(const char *passengerRoot) { if (passengerRoot != NULL) { string root(passengerRoot); if (root.at(root.size() - 1) != '/') { root.append(1, '/'); } string path(root); path.append("bin/passenger-spawn-server"); if (fileExists(path.c_str())) { return path; } else { path.assign(root); path.append("lib/phusion_passenger/passenger-spawn-server"); return path; } return path; } else { const char *path = getenv("PATH"); if (path == NULL) { return ""; } vector paths; split(getenv("PATH"), ':', paths); for (vector::const_iterator it(paths.begin()); it != paths.end(); it++) { if (!it->empty() && (*it).at(0) == '/') { string filename(*it); filename.append("/" SPAWN_SERVER_SCRIPT_NAME); if (fileExists(filename.c_str())) { return filename; } } } return ""; } } string findApplicationPoolServer(const char *passengerRoot) { assert(passengerRoot != NULL); string root(passengerRoot); if (root.at(root.size() - 1) != '/') { root.append(1, '/'); } string path(root); path.append("ext/apache2/ApplicationPoolServerExecutable"); if (fileExists(path.c_str())) { return path; } else { path.assign(root); path.append("lib/phusion_passenger/ApplicationPoolServerExecutable"); return path; } } string canonicalizePath(const string &path) { #ifdef __GLIBC__ // We're using a GNU extension here. See the 'BUGS' // section of the realpath(3) Linux manpage for // rationale. char *tmp = realpath(path.c_str(), NULL); if (tmp == NULL) { int e = errno; string message; message = "Cannot resolve the path '"; message.append(path); message.append("'"); throw FileSystemException(message, e, path); } else { string result(tmp); free(tmp); return result; } #else char tmp[PATH_MAX]; if (realpath(path.c_str(), tmp) == NULL) { int e = errno; string message; message = "Cannot resolve the path '"; message.append(path); message.append("'"); throw FileSystemException(message, e, path); } else { return tmp; } #endif } string resolveSymlink(const string &path) { char buf[PATH_MAX]; ssize_t size; size = readlink(path.c_str(), buf, sizeof(buf) - 1); if (size == -1) { if (errno == EINVAL) { return path; } else { int e = errno; string message = "Cannot resolve possible symlink '"; message.append(path); message.append("'"); throw FileSystemException(message, e, path); } } else { buf[size] = '\0'; if (buf[0] == '\0') { string message = "The file '"; message.append(path); message.append("' is a symlink, and it refers to an empty filename. This is not allowed."); throw FileSystemException(message, ENOENT, path); } else if (buf[0] == '/') { // Symlink points to an absolute path. return buf; } else { return extractDirName(path) + "/" + buf; } } } string extractDirName(const string &path) { char *path_copy = strdup(path.c_str()); char *result = dirname(path_copy); string result_string(result); free(path_copy); return result_string; } string escapeForXml(const string &input) { string result(input); string::size_type input_pos = 0; string::size_type input_end_pos = input.size(); string::size_type result_pos = 0; while (input_pos < input_end_pos) { const unsigned char ch = input[input_pos]; if ((ch >= 'A' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '/' || ch == ' ' || ch == '_' || ch == '.') { // This is an ASCII character. Ignore it and // go to next character. result_pos++; } else { // Not an ASCII character; escape it. char escapedCharacter[sizeof("ÿ") + 1]; int size; size = snprintf(escapedCharacter, sizeof(escapedCharacter) - 1, "&#%d;", (int) ch); if (size < 0) { throw std::bad_alloc(); } escapedCharacter[sizeof(escapedCharacter) - 1] = '\0'; result.replace(result_pos, 1, escapedCharacter, size); result_pos += size; } input_pos++; } return result; } string getProcessUsername() { struct passwd pwd, *result; char strings[1024]; int ret; result = (struct passwd *) NULL; do { ret = getpwuid_r(getuid(), &pwd, strings, sizeof(strings), &result); } while (ret == -1 && errno == EINTR); if (ret == -1) { result = (struct passwd *) NULL; } if (result == (struct passwd *) NULL) { snprintf(strings, sizeof(strings), "UID %lld", (long long) getuid()); strings[sizeof(strings) - 1] = '\0'; return strings; } else { return result->pw_name; } } void determineLowestUserAndGroup(const string &user, uid_t &uid, gid_t &gid) { struct passwd *ent; ent = getpwnam(user.c_str()); if (ent == NULL) { ent = getpwnam("nobody"); } if (ent == NULL) { uid = (uid_t) -1; gid = (gid_t) -1; } else { uid = ent->pw_uid; gid = ent->pw_gid; } } const char * getSystemTempDir() { const char *temp_dir = getenv("TMPDIR"); if (temp_dir == NULL || *temp_dir == '\0') { temp_dir = "/tmp"; } return temp_dir; } string getPassengerTempDir(bool bypassCache, const string &parentDir) { if (!bypassCache && !passengerTempDir.empty()) { return passengerTempDir; } else { string theParentDir; char buffer[PATH_MAX]; if (parentDir.empty()) { theParentDir = getSystemTempDir(); } else { theParentDir = parentDir; } snprintf(buffer, sizeof(buffer), "%s/passenger.%lu", theParentDir.c_str(), (unsigned long) getpid()); buffer[sizeof(buffer) - 1] = '\0'; passengerTempDir = buffer; return passengerTempDir; } } void setPassengerTempDir(const string &dir) { passengerTempDir = dir; } /* Creates a non-writable FIFO file in order to prevent /tmp cleaners from removing * passenger temp dir subdirectories. See http://code.google.com/p/phusion-passenger/issues/detail?id=365 * for details. */ static void createNonWritableFifo(const string &filename) { int ret, e; do { ret = mkfifo(filename.c_str(), 0); } while (ret == -1 && errno == EINTR); if (ret == -1 && errno != EEXIST) { e = errno; throw FileSystemException("Cannot create FIFO file " + filename, e, filename); } do { ret = chmod(filename.c_str(), 0); } while (ret == -1 && errno == EINTR); if (ret == -1) { e = errno; throw FileSystemException("Cannot set permissions on file " + filename, e, filename); } } void createPassengerTempDir(const string &parentDir, bool userSwitching, const string &lowestUser, uid_t workerUid, gid_t workerGid) { string tmpDir(getPassengerTempDir(false, parentDir)); uid_t lowestUid; gid_t lowestGid; determineLowestUserAndGroup(lowestUser, lowestUid, lowestGid); /* Create the temp directory with the current user as owner (which * is root if the web server was started as root). Only the owner * may write to this directory. Everybody else may only access the * directory. The permissions on the subdirectories will determine * whether a user may access that specific subdirectory. */ makeDirTree(tmpDir, "u=wxs,g=x,o=x"); createNonWritableFifo(tmpDir + "/.guard"); /* We want this upload buffer directory to be only accessible by the web server's * worker processs. * * It only makes sense to chown webserver_private to workerUid and workerGid if the web server * is actually able to change the user of the worker processes. That is, if the web server * is running as root. */ if (geteuid() == 0) { makeDirTree(tmpDir + "/webserver_private", "u=wxs,g=,o=", workerUid, workerGid); } else { makeDirTree(tmpDir + "/webserver_private", "u=wxs,g=,o="); } createNonWritableFifo(tmpDir + "/webserver_private/.guard"); /* If the web server is running as root (i.e. user switching is possible to begin with) * but user switching is off... */ if (geteuid() == 0 && !userSwitching) { /* ...then the 'info' subdirectory must be owned by lowestUser, so that only root * or lowestUser can query Phusion Passenger information. */ makeDirTree(tmpDir + "/info", "u=rwxs,g=,o=", lowestUid, lowestGid); } else { /* Otherwise just use the current user and the directory's owner. * This way, only the user that the web server's control process * is running as will be able to query information. */ makeDirTree(tmpDir + "/info", "u=rwxs,g=,o="); } createNonWritableFifo(tmpDir + "/info/.guard"); if (geteuid() == 0) { if (userSwitching) { makeDirTree(tmpDir + "/master", "u=wxs,g=,o=", workerUid, workerGid); } else { makeDirTree(tmpDir + "/master", "u=wxs,g=x,o=x", lowestUid, lowestGid); } } else { makeDirTree(tmpDir + "/master", "u=wxs,g=,o="); } createNonWritableFifo(tmpDir + "/master/.guard"); if (geteuid() == 0) { if (userSwitching) { /* If user switching is possible and turned on, then each backend * process may be running as a different user, so the backends * 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, not even by its owner. */ makeDirTree(tmpDir + "/backends", "u=wxs,g=wx,o=wx"); } else { /* If user switching is off then all backend processes will be * running as lowestUser, so make lowestUser the owner of the * directory. Nobody else (except root) may access this directory. * * 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(tmpDir + "/backends", "u=wxs,g=,o=", lowestUid, lowestGid); } } else { /* If user switching is not possible then all backend processes will * be running as the same user as the web server. So we'll make the * backends subdirectory only writable by this user. Nobody else * (except root) may access this subdirectory. * * 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(tmpDir + "/backends", "u=wxs,g=,o="); } createNonWritableFifo(tmpDir + "/backends/.guard"); } void makeDirTree(const string &path, const char *mode, uid_t owner, gid_t group) { char command[PATH_MAX + 10]; struct stat buf; if (stat(path.c_str(), &buf) == 0) { return; } snprintf(command, sizeof(command), "mkdir -p -m \"%s\" \"%s\"", mode, path.c_str()); command[sizeof(command) - 1] = '\0'; int result; do { result = system(command); } while (result == -1 && errno == EINTR); if (result != 0) { char message[1024]; int e = errno; snprintf(message, sizeof(message) - 1, "Cannot create directory '%s'", path.c_str()); message[sizeof(message) - 1] = '\0'; if (result == -1) { throw SystemException(message, e); } else { throw IOException(message); } } if (owner != (uid_t) -1 && group != (gid_t) -1) { do { result = chown(path.c_str(), owner, group); } while (result == -1 && errno == EINTR); if (result != 0) { char message[1024]; int e = errno; snprintf(message, sizeof(message) - 1, "Cannot change the directory '%s' its UID to %lld and GID to %lld", path.c_str(), (long long) owner, (long long) group); message[sizeof(message) - 1] = '\0'; throw FileSystemException(message, e, path); } } } void removeDirTree(const string &path) { char command[PATH_MAX + 30]; int result; snprintf(command, sizeof(command), "chmod -R u+rwx \"%s\" 2>/dev/null", path.c_str()); command[sizeof(command) - 1] = '\0'; do { result = system(command); } while (result == -1 && errno == EINTR); snprintf(command, sizeof(command), "rm -rf \"%s\"", path.c_str()); command[sizeof(command) - 1] = '\0'; do { result = system(command); } while (result == -1 && errno == EINTR); if (result == -1) { char message[1024]; int e = errno; snprintf(message, sizeof(message) - 1, "Cannot remove directory '%s'", path.c_str()); message[sizeof(message) - 1] = '\0'; throw FileSystemException(message, e, path); } } bool verifyRailsDir(const string &dir, CachedFileStat *cstat, unsigned int throttleRate) { string temp(dir); temp.append("/config/environment.rb"); return fileExists(temp.c_str(), cstat, throttleRate); } bool verifyRackDir(const string &dir, CachedFileStat *cstat, unsigned int throttleRate) { string temp(dir); temp.append("/config.ru"); return fileExists(temp.c_str(), cstat, throttleRate); } bool verifyWSGIDir(const string &dir, CachedFileStat *cstat, unsigned int throttleRate) { string temp(dir); temp.append("/passenger_wsgi.py"); return fileExists(temp.c_str(), cstat, throttleRate); } int createUnixServer(const char *filename, unsigned int backlogSize, bool autoDelete) { struct sockaddr_un addr; int fd, ret; if (strlen(filename) > sizeof(addr.sun_path) - 1) { string message = "Cannot create Unix socket '"; message.append(filename); message.append("': filename is too long."); throw RuntimeException(message); } fd = syscalls::socket(PF_UNIX, SOCK_STREAM, 0); if (fd == -1) { throw SystemException("Cannot create a Unix socket file descriptor", errno); } addr.sun_family = AF_UNIX; strncpy(addr.sun_path, filename, sizeof(addr.sun_path)); addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; if (autoDelete) { do { ret = unlink(filename); } while (ret == -1 && errno == EINTR); } try { ret = syscalls::bind(fd, (const struct sockaddr *) &addr, sizeof(addr)); } catch (...) { do { ret = close(fd); } while (ret == -1 && errno == EINTR); throw; } if (ret == -1) { int e = errno; string message = "Cannot bind Unix socket '"; message.append(filename); message.append("'"); do { ret = close(fd); } while (ret == -1 && errno == EINTR); throw SystemException(message, e); } if (backlogSize == 0) { backlogSize = 1024; } try { ret = syscalls::listen(fd, backlogSize); } catch (...) { do { ret = close(fd); } while (ret == -1 && errno == EINTR); throw; } if (ret == -1) { int e = errno; string message = "Cannot listen on Unix socket '"; message.append(filename); message.append("'"); do { ret = close(fd); } while (ret == -1 && errno == EINTR); throw SystemException(message, e); } return fd; } int connectToUnixServer(const char *filename) { int fd, ret; struct sockaddr_un addr; if (strlen(filename) > sizeof(addr.sun_path) - 1) { string message = "Cannot connect to Unix socket '"; message.append(filename); message.append("': filename is too long."); throw RuntimeException(message); } do { fd = syscalls::socket(PF_UNIX, SOCK_STREAM, 0); } while (fd == -1 && errno == EINTR); if (fd == -1) { throw SystemException("Cannot create a Unix socket file descriptor", errno); } addr.sun_family = AF_UNIX; strncpy(addr.sun_path, filename, sizeof(addr.sun_path)); addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; try { ret = syscalls::connect(fd, (const sockaddr *) &addr, sizeof(addr)); } catch (...) { do { ret = close(fd); } while (ret == -1 && errno == EINTR); throw; } if (ret == -1) { int e = errno; string message("Cannot connect to Unix socket '"); message.append(filename); message.append("'"); do { ret = close(fd); } while (ret == -1 && errno == EINTR); throw SystemException(message, e); } return fd; } } // namespace Passenger