Passenger: ServerInstanceDir.h Source File

- Main Page
- Modules
- Namespaces
- Classes
- Files

- File List

# ServerInstanceDir.h

    00001 /*
    00002  *  Phusion Passenger - http://www.modrails.com/
    00003  *  Copyright (c) 2010 Phusion
    00004  *
    00005  *  "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
    00006  *
    00007  *  Permission is hereby granted, free of charge, to any person obtaining a copy
    00008  *  of this software and associated documentation files (the "Software"), to deal
    00009  *  in the Software without restriction, including without limitation the rights
    00010  *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    00011  *  copies of the Software, and to permit persons to whom the Software is
    00012  *  furnished to do so, subject to the following conditions:
    00013  *
    00014  *  The above copyright notice and this permission notice shall be included in
    00015  *  all copies or substantial portions of the Software.
    00016  *
    00017  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    00018  *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    00019  *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    00020  *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    00021  *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    00022  *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    00023  *  THE SOFTWARE.
    00024  */
    00025 #ifndef _PASSENGER_SERVER_INSTANCE_DIR_H_
    00026 #define _PASSENGER_SERVER_INSTANCE_DIR_H_
    00027
    00028 #include <boost/noncopyable.hpp>
    00029 #include <boost/shared_ptr.hpp>
    00030
    00031 #include <sys/types.h>
    00032 #include <dirent.h>
    00033 #include <unistd.h>
    00034 #include <pwd.h>
    00035 #include <grp.h>
    00036 #include <cstdlib>
    00037 #include <cstring>
    00038 #include <string>
    00039
    00040 #include "Exceptions.h"
    00041 #include "Utils.h"
    00042
    00043 namespace Passenger {
    00044
    00045 using namespace std;
    00046 using namespace boost;
    00047
    00048 class ServerInstanceDir: public noncopyable {
    00049 public:
    00050         // Don't forget to update lib/phusion_passenger/admin_tools/server_instance.rb too.
    00051         static const int DIR_STRUCTURE_MAJOR_VERSION = 1;
    00052         static const int DIR_STRUCTURE_MINOR_VERSION = 0;
    00053         static const int GENERATION_STRUCTURE_MAJOR_VERSION = 1;
    00054         static const int GENERATION_STRUCTURE_MINOR_VERSION = 0;
    00055
    00056         class Generation: public noncopyable {
    00057         private:
    00058                 friend class ServerInstanceDir;
    00059
    00060                 string path;
    00061                 unsigned int number;
    00062                 bool owner;
    00063
    00064                 Generation(const string &serverInstanceDir, unsigned int number) {
    00065                         path = serverInstanceDir + "/generation-" + toString(number);
    00066                         this->number = number;
    00067                         owner = false;
    00068                 }
    00069
    00070                 void create(bool userSwitching, const string &defaultUser,
    00071                             const string &defaultGroup, uid_t webServerWorkerUid,
    00072                             gid_t webServerWorkerGid)
    00073                 {
    00074                         bool runningAsRoot = geteuid() == 0;
    00075                         struct passwd *defaultUserEntry;
    00076                         struct group  *defaultGroupEntry;
    00077                         uid_t defaultUid;
    00078                         gid_t defaultGid;
    00079
    00080                         defaultUserEntry = getpwnam(defaultUser.c_str());
    00081                         if (defaultUserEntry == NULL) {
    00082                                 throw NonExistentUserException("Default user '" + defaultUser +
    00083                                         "' does not exist.");
    00084                         }
    00085                         defaultUid = defaultUserEntry->pw_uid;
    00086                         defaultGroupEntry = getgrnam(defaultGroup.c_str());
    00087                         if (defaultGroupEntry == NULL) {
    00088                                 throw NonExistentGroupException("Default group '" + defaultGroup +
    00089                                         "' does not exist.");
    00090                         }
    00091                         defaultGid = defaultGroupEntry->gr_gid;
    00092
    00093                         /* We set a very tight permission here: no read or write access for
    00094                          * anybody except the owner. The individual files and subdirectories
    00095                          * decide for themselves whether they're readable by anybody.
    00096                          */
    00097                         makeDirTree(path, "u=rwxs,g=x,o=x");
    00098
    00099                         /* Write structure version file. */
    00100                         string structureVersionFile = path + "/structure_version.txt";
    00101                         createFile(structureVersionFile,
    00102                                 toString(GENERATION_STRUCTURE_MAJOR_VERSION) + "." +
    00103                                 toString(GENERATION_STRUCTURE_MINOR_VERSION),
    00104                                 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    00105
    00106
    00107                         /* We want the upload buffer directory to be only writable by the web
    00108                          * server's worker processs. Other users may not have any access to this
    00109                          * directory.
    00110                          */
    00111                         if (runningAsRoot) {
    00112                                 makeDirTree(path + "/buffered_uploads", "u=rwxs,g=,o=",
    00113                                         webServerWorkerUid, webServerWorkerGid);
    00114                         } else {
    00115                                 makeDirTree(path + "/buffered_uploads", "u=rwxs,g=,o=");
    00116                         }
    00117
    00118                         /* The web server must be able to directly connect to a backend. */
    00119                         if (runningAsRoot) {
    00120                                 if (userSwitching) {
    00121                                         /* Each backend process may be running as a different user,
    00122                                          * so the backends subdirectory must be world-writable.
    00123                                          * However we don't want everybody to be able to know the
    00124                                          * sockets' filenames, so the directory is not readable.
    00125                                          */
    00126                                         makeDirTree(path + "/backends", "u=rwxs,g=wx,o=wx");
    00127                                 } else {
    00128                                         /* All backend processes are running as defaultUser/defaultGroup,
    00129                                          * so make defaultUser/defaultGroup the owner and group of the
    00130                                          * subdirecory.
    00131                                          *
    00132                                          * The directory is not readable as a security precaution:
    00133                                          * nobody should be able to know the sockets' filenames without
    00134                                          * having access to the application pool.
    00135                                          */
    00136                                         makeDirTree(path + "/backends", "u=rwxs,g=x,o=x", defaultUid, defaultGid);
    00137                                 }
    00138                         } else {
    00139                                 /* All backend processes are running as the same user as the web server,
    00140                                  * so only allow access for this user.
    00141                                  */
    00142                                 makeDirTree(path + "/backends", "u=rwxs,g=,o=");
    00143                         }
    00144
    00145                         /* The helper server (containing the application pool) must be able to access
    00146                          * the spawn server's socket.
    00147                          */
    00148                         if (runningAsRoot) {
    00149                                 if (userSwitching) {
    00150                                         /* Both the helper server and the spawn server are
    00151                                          * running as root.
    00152                                          */
    00153                                         makeDirTree(path + "/spawn-server", "u=rwxs,g=,o=");
    00154                                 } else {
    00155                                         /* Both the helper server and the spawn server are
    00156                                          * running as defaultUser/defaultGroup.
    00157                                          */
    00158                                         makeDirTree(path + "/spawn-server", "u=rwxs,g=,o=",
    00159                                                 defaultUid, defaultGid);
    00160                                 }
    00161                         } else {
    00162                                 makeDirTree(path + "/spawn-server", "u=rwxs,g=,o=");
    00163                         }
    00164
    00165                         owner = true;
    00166                 }
    00167
    00168         public:
    00169                 ~Generation() {
    00170                         if (owner) {
    00171                                 removeDirTree(path);
    00172                         }
    00173                 }
    00174
    00175                 unsigned int getNumber() const {
    00176                         return number;
    00177                 }
    00178
    00179                 string getPath() const {
    00180                         return path;
    00181                 }
    00182
    00183                 void detach() {
    00184                         owner = false;
    00185                 }
    00186         };
    00187
    00188         typedef shared_ptr<Generation> GenerationPtr;
    00189
    00190 private:
    00191         string path;
    00192         bool owner;
    00193
    00194         friend class Generation;
    00195
    00196         void initialize(const string &path, bool owner) {
    00197                 this->path  = path;
    00198                 this->owner = owner;
    00199
    00200                 /* Create the server instance directory. We only need to write to this
    00201                  * directory for these reasons:
    00202                  * 1. Initial population of structure files (structure_version.txt, instance.pid).
    00203                  * 2. Creating/removing a generation directory.
    00204                  * 3. Removing the entire server instance directory (after all
    00205                  *    generations are removed).
    00206                  *
    00207                  * 1 and 2 are done by the helper server during initialization and before lowering
    00208                  * privilege. 3 is done during helper server shutdown by a cleanup process that's
    00209                  * running as the same user the helper server was running as before privilege
    00210                  * lowering.
    00211                  * Therefore, we make the directory only writable by the user the helper server
    00212                  * was running as before privilege is lowered. Everybody else has read and execute
    00213                  * rights though, because we want admin tools to be able to list the available
    00214                  * generations no matter what user they're running as.
    00215                  */
    00216                 makeDirTree(path, "u=rwxs,g=rx,o=rx");
    00217         }
    00218
    00219 public:
    00220         ServerInstanceDir(pid_t webServerPid, const string &parentDir = "", bool owner = true) {
    00221                 string theParentDir;
    00222
    00223                 if (parentDir.empty()) {
    00224                         theParentDir = getSystemTempDir();
    00225                 } else {
    00226                         theParentDir = parentDir;
    00227                 }
    00228
    00229                 /* We embed the super structure version in the server instance directory name
    00230                  * because it's possible to upgrade Phusion Passenger without changing the
    00231                  * web server's PID. This way each incompatible upgrade will use its own
    00232                  * server instance directory.
    00233                  */
    00234                 initialize(theParentDir + "/passenger." +
    00235                         toString(DIR_STRUCTURE_MAJOR_VERSION) + "." +
    00236                         toString(DIR_STRUCTURE_MINOR_VERSION) + "." +
    00237                         toString<unsigned long long>(webServerPid),
    00238                         owner);
    00239
    00240         }
    00241
    00242         ServerInstanceDir(const string &path, bool owner = true) {
    00243                 initialize(path, owner);
    00244         }
    00245
    00246         ~ServerInstanceDir() {
    00247                 if (owner) {
    00248                         GenerationPtr newestGeneration;
    00249                         try {
    00250                                 newestGeneration = getNewestGeneration();
    00251                         } catch (const FileSystemException &e) {
    00252                                 if (e.code() == ENOENT) {
    00253                                         return;
    00254                                 } else {
    00255                                         throw;
    00256                                 }
    00257                         }
    00258                         if (newestGeneration == NULL) {
    00259                                 removeDirTree(path);
    00260                         }
    00261                 }
    00262         }
    00263
    00264         string getPath() const {
    00265                 return path;
    00266         }
    00267
    00268         void detach() {
    00269                 owner = false;
    00270         }
    00271
    00272         GenerationPtr newGeneration(bool userSwitching, const string &defaultUser,
    00273                                     const string &defaultGroup, uid_t webServerWorkerUid,
    00274                                     gid_t webServerWorkerGid)
    00275         {
    00276                 GenerationPtr newestGeneration = getNewestGeneration();
    00277                 unsigned int newNumber;
    00278                 if (newestGeneration != NULL) {
    00279                         newNumber = newestGeneration->getNumber() + 1;
    00280                 } else {
    00281                         newNumber = 0;
    00282                 }
    00283
    00284                 GenerationPtr generation(new Generation(path, newNumber));
    00285                 generation->create(userSwitching, defaultUser, defaultGroup,
    00286                         webServerWorkerUid, webServerWorkerGid);
    00287                 return generation;
    00288         }
    00289
    00290         GenerationPtr getGeneration(unsigned int number) const {
    00291                 return ptr(new Generation(path, number));
    00292         }
    00293
    00294         GenerationPtr getNewestGeneration() const {
    00295                 DIR *dir = opendir(path.c_str());
    00296                 struct dirent *entry;
    00297                 int result = -1;
    00298
    00299                 if (dir == NULL) {
    00300                         int e = errno;
    00301                         throw FileSystemException("Cannot open directory " + path,
    00302                                 e, path);
    00303                 }
    00304                 while ((entry = readdir(dir)) != NULL) {
    00305                         if (entry->d_type == DT_DIR
    00306                          && strncmp(entry->d_name, "generation-", sizeof("generation-") - 1) == 0) {
    00307                                 const char *numberString = entry->d_name + sizeof("generation-") - 1;
    00308                                 int number = atoi(numberString);
    00309                                 if (number >= 0 && number > result) {
    00310                                         result = number;
    00311                                 }
    00312                         }
    00313                 }
    00314                 closedir(dir);
    00315
    00316                 if (result == -1) {
    00317                         return GenerationPtr();
    00318                 } else {
    00319                         return getGeneration(result);
    00320                 }
    00321         }
    00322 };
    00323
    00324 typedef shared_ptr<ServerInstanceDir> ServerInstanceDirPtr;
    00325
    00326 } // namespace Passenger
    00327
    00328 #endif /* _PASSENGER_SERVER_INSTANCE_DIR_H_ */