2 suPHP - (c)2002-2005 Sebastian Marsching <sebastian@marsching.com>
4 This file is part of suPHP.
6 suPHP is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 suPHP is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with suPHP; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 #include "CommandLine.hpp"
26 #include "Environment.hpp"
27 #include "Exception.hpp"
29 #include "Configuration.hpp"
31 #include "API_Helper.hpp"
33 #include "UserInfo.hpp"
34 #include "GroupInfo.hpp"
37 #include "Application.hpp"
39 using namespace suPHP;
42 suPHP::Application::Application() {
47 int suPHP::Application::run(CommandLine& cmdline, Environment& env) {
49 API& api = API_Helper::getSystemAPI();
50 Logger& logger = api.getSystemLogger();
53 File cfgFile = File(OPT_CONFIGFILE);
55 File cfgFile = File("/etc/suphp.conf");
58 std::string interpreter;
59 TargetMode targetMode;
62 // Begin try block - soft exception cannot really be handled before
65 std::string scriptFilename;
67 // If caller is super-user, print info message and exit
68 if (api.getRealProcessUser().isSuperUser()) {
69 this->printAboutMessage();
72 config.readFromFile(cfgFile);
74 // Check permissions (real uid, effective uid)
75 this->checkProcessPermissions(config);
78 // not done before, because we need super-user privileges for
83 scriptFilename = env.getVar("SCRIPT_FILENAME");
84 } catch (KeyNotFoundException& e) {
85 logger.logError("Environment variable SCRIPT_FILENAME not set");
86 this->printAboutMessage();
90 this->checkScriptFile(scriptFilename, config, env);
92 // Root privileges are needed for chroot()
93 // so do this before changing process permissions
94 if (config.getChrootPath().length() > 0) {
95 api.chroot(config.getChrootPath());
98 this->changeProcessPermissions(scriptFilename, config, env);
100 interpreter = this->getInterpreter(env, config);
101 targetMode = this->getTargetMode(interpreter);
103 // Prepare environment for new process
104 newEnv = this->prepareEnvironment(env, config, targetMode);
106 // Set PATH_TRANSLATED to SCRIPT_FILENAME, otherwise
107 // the PHP interpreter will not be able to find the script
108 if (targetMode == TARGETMODE_PHP && newEnv.hasVar("PATH_TRANSLATED")) {
109 newEnv.setVar("PATH_TRANSLATED", scriptFilename);
112 // Log attempt to execute script
113 logger.logInfo("Executing \"" + scriptFilename + "\" as UID "
114 + Util::intToStr(api.getEffectiveProcessUser().getUid())
117 api.getEffectiveProcessGroup().getGid()));
119 this->executeScript(scriptFilename, interpreter, targetMode, newEnv,
122 // Function should never return
123 // So, if we get here, return with error code
125 } catch (SoftException& e) {
126 if (!config.getErrorsToBrowser()) {
130 std::cout << "Content-Type: text/html\n"
135 << " <title>500 Internal Server Error</title>\n"
138 << " <h1>Internal Server Error</h1>\n"
139 << " <p>" << e.getMessage() << "</p>\n"
141 << " <address>suPHP " << PACKAGE_VERSION << "</address>\n"
148 void suPHP::Application::printAboutMessage() {
149 std::cerr << "suPHP version " << PACKAGE_VERSION << "\n";
150 std::cerr << "(c) 2002-2005 Sebastian Marsching\n";
151 std::cerr << std::endl;
152 std::cerr << "suPHP has to be called by mod_suphp to work." << std::endl;
156 void suPHP::Application::checkProcessPermissions(Configuration& config)
157 throw (SecurityException, LookupException) {
158 API& api = API_Helper::getSystemAPI();
159 if (api.getRealProcessUser() !=
160 api.getUserInfo(config.getWebserverUser())) {
161 throw SecurityException("Calling user is not webserver user!",
165 if (!api.getEffectiveProcessUser().isSuperUser()) {
166 throw SecurityException(
167 "Do not have root privileges. Executable not set-uid root?",
173 void suPHP::Application::checkScriptFile(
174 const std::string& scriptFilename,
175 const Configuration& config,
176 const Environment& environment) const
177 throw (SystemException, SoftException) {
178 Logger& logger = API_Helper::getSystemAPI().getSystemLogger();
179 File scriptFile = File(scriptFilename);
180 File realScriptFile = File(scriptFile.getRealPath());
182 // Check wheter file exists
183 if (!scriptFile.exists()) {
184 std::string error = "File " + scriptFile.getPath() + " does not exist";
185 logger.logWarning(error);
186 throw SoftException(error, __FILE__, __LINE__);
188 if (!realScriptFile.exists()) {
189 std::string error = "File " + realScriptFile.getPath()
190 + " referenced by symlink " +scriptFile.getPath()
192 logger.logWarning(error);
193 throw SoftException(error, __FILE__, __LINE__);
196 // Check wheter script is in docroot
197 if (realScriptFile.getPath().find(config.getDocroot()) != 0) {
198 std::string error = "Script \"" + scriptFile.getPath()
199 + "\" resolving to \"" + realScriptFile.getPath()
200 + "\" not within configured docroot";
201 logger.logWarning(error);
202 throw SoftException(error, __FILE__, __LINE__);
205 // If enabled, check whether script is in the vhost's docroot
206 if (!environment.hasVar("DOCUMENT_ROOT"))
207 throw SoftException("Environment variable DOCUMENT_ROOT not set",
209 if (config.getCheckVHostDocroot()
210 && realScriptFile.getPath().find(environment.getVar("DOCUMENT_ROOT"))
213 std::string error = "File \"" + realScriptFile.getPath()
214 + "\" is not in document root of Vhost \""
215 + environment.getVar("DOCUMENT_ROOT") + "\"";
216 logger.logWarning(error);
217 throw SoftException(error, __FILE__, __LINE__);
219 if (config.getCheckVHostDocroot()
220 && scriptFile.getPath().find(environment.getVar("DOCUMENT_ROOT"))
223 std::string error = "File \"" + scriptFile.getPath()
224 + "\" is not in document root of Vhost \""
225 + environment.getVar("DOCUMENT_ROOT") + "\"";
226 logger.logWarning(error);
227 throw SoftException(error, __FILE__, __LINE__);
230 // Check script permissions
231 // Directories will be checked later
232 if (!realScriptFile.hasUserReadBit()) {
233 std::string error = "File \"" + realScriptFile.getPath()
235 logger.logWarning(error);
236 throw SoftException(error, __FILE__, __LINE__);
240 if (!config.getAllowFileGroupWriteable()
241 && realScriptFile.hasGroupWriteBit()) {
242 std::string error = "File \"" + realScriptFile.getPath()
243 + "\" is writeable by group";
244 logger.logWarning(error);
245 throw SoftException(error, __FILE__, __LINE__);
248 if (!config.getAllowFileOthersWriteable()
249 && realScriptFile.hasOthersWriteBit()) {
250 std::string error = "File \"" + realScriptFile.getPath()
251 + "\" is writeable by others";
252 logger.logWarning(error);
253 throw SoftException(error, __FILE__, __LINE__);
256 // Check UID/GID of symlink is matching target
257 if (scriptFile.getUser() != realScriptFile.getUser()
258 || scriptFile.getGroup() != realScriptFile.getGroup()) {
259 std::string error = "UID or GID of symlink \"" + scriptFile.getPath()
260 + "\" is not matching its target";
261 logger.logWarning(error);
262 throw SoftException(error, __FILE__, __LINE__);
267 void suPHP::Application::changeProcessPermissions(
268 const std::string& scriptFilename,
269 const Configuration& config,
270 const Environment& environment) const
271 throw (SystemException, SoftException, SecurityException) {
273 GroupInfo targetGroup;
275 File scriptFile = File(scriptFilename);
276 File realScriptFile = File(scriptFile.getRealPath());
277 API& api = API_Helper::getSystemAPI();
278 Logger& logger = api.getSystemLogger();
280 // Make sure that exactly one mode is set
282 #if !defined(OPT_USERGROUP_OWNER) && !defined(OPT_USERGROUP_FORCE) && !defined(OPT_USERGROUP_PARANOID)
283 #error "No uid/gid change model specified"
285 #if (defined(OPT_USERGROUP_OWNER) && defined(OPT_USERGROUP_FORCE)) || (defined(OPT_USERGROUP_FORCE) && defined(OPT_USERGROUP_PARANOID)) || (defined(OPT_USERGROUP_OWNER) && defined(OPT_USERGROUP_PARANOID))
286 #error "More than one uid/gid change model specified"
289 // Common code (for all security modes)
291 // Check UID/GID of script
292 if (scriptFile.getUser().getUid() < config.getMinUid()) {
293 std::string error = "UID of script \"" + scriptFilename
294 + "\" is smaller than min_uid";
295 logger.logWarning(error);
296 throw SoftException(error, __FILE__, __LINE__);
298 if (scriptFile.getGroup().getGid() < config.getMinGid()) {
299 std::string error = "GID of script \"" + scriptFilename
300 + "\" is smaller than min_gid";
301 logger.logWarning(error);
302 throw SoftException(error, __FILE__, __LINE__);
305 // Paranoid and force mode
307 #if (defined(OPT_USERGROUP_PARANOID) || defined(OPT_USERGROUP_FORCE))
308 std::string targetUsername, targetGroupname;
310 targetUsername = environment.getVar("SUPHP_USER");
311 targetGroupname = environment.getVar("SUPHP_GROUP");
312 } catch (KeyNotFoundException& e) {
313 throw SecurityException(
314 "Environment variable SUPHP_USER or SUPHP_GROUP not set",
318 if (targetUsername[0] == '#' && targetUsername.find_first_not_of(
319 "0123456789", 1) == std::string::npos) {
320 targetUser = api.getUserInfo(Util::strToInt(targetUsername.substr(1)));
322 targetUser = api.getUserInfo(targetUsername);
325 if (targetGroupname[0] == '#' && targetGroupname.find_first_not_of(
326 "0123456789", 1) == std::string::npos) {
327 targetGroup = api.getGroupInfo(
328 Util::strToInt(targetGroupname.substr(1)));
330 targetGroup = api.getGroupInfo(targetGroupname);
332 #endif // OPT_USERGROUP_PARANOID || OPT_USERGROUP_FORCE
336 #ifdef OPT_USERGROUP_OWNER
337 targetUser = scriptFile.getUser();
338 targetGroup = scriptFile.getGroup();
339 #endif // OPT_USERGROUP_OWNER
341 // Paranoid mode only
343 #ifdef OPT_USERGROUP_PARANOID
344 if (targetUser != scriptFile.getUser()) {
345 std::string error ="Mismatch between target UID ("
346 + Util::intToStr(targetUser.getUid()) + ") and UID ("
347 + Util::intToStr(scriptFile.getUser().getUid()) + ") of file \""
348 + scriptFile.getPath() + "\"";
349 logger.logWarning(error);
350 throw SoftException(error, __FILE__, __LINE__);
353 if (targetGroup != scriptFile.getGroup()) {
354 std::string error ="Mismatch between target GID ("
355 + Util::intToStr(targetGroup.getGid()) + ") and GID ("
356 + Util::intToStr(scriptFile.getGroup().getGid()) + ") of file \""
357 + scriptFile.getPath() + "\"";
358 logger.logWarning(error);
359 throw SoftException(error, __FILE__, __LINE__);
361 #endif // OPT_USERGROUP_PARANOID
363 // Check directory ownership and permissions
364 checkParentDirectories(realScriptFile, targetUser, config);
365 checkParentDirectories(scriptFile, targetUser, config);
367 // Common code used for all modes
369 // Set new group first, because we still need super-user privileges
371 api.setProcessGroup(targetGroup);
374 api.setProcessUser(targetUser);
376 api.setUmask(config.getUmask());
380 Environment suPHP::Application::prepareEnvironment(
381 const Environment& sourceEnv, const Configuration& config, TargetMode mode)
382 throw (KeyNotFoundException) {
383 // Create environment for new process from old environment
384 Environment env = sourceEnv;
386 // Delete unwanted environment variables
387 if (env.hasVar("LD_PRELOAD"))
388 env.deleteVar("LD_PRELOAD");
389 if (env.hasVar("LD_LIBRARY_PATH"))
390 env.deleteVar("LD_LIBRARY_PATH");
391 if (env.hasVar("SUPHP_USER"))
392 env.deleteVar("SUPHP_USER");
393 if (env.hasVar("SUPHP_GROUP"))
394 env.deleteVar("SUPHP_GROUP");
395 if (env.hasVar("SUPHP_HANDLER"))
396 env.deleteVar("SUPHP_HANDLER");
397 if (env.hasVar("SUPHP_AUTH_USER"))
398 env.deleteVar("SUPHP_AUTH_USER");
399 if (env.hasVar("SUPHP_AUTH_PW"))
400 env.deleteVar("SUPHP_AUTH_PW");
401 if (env.hasVar("SUPHP_PHP_CONFIG"))
402 env.deleteVar("SUPHP_PHP_CONFIG");
405 env.putVar("PATH", config.getEnvPath());
407 // If we are in PHP mode, set PHP specific variables
408 if (mode == TARGETMODE_PHP) {
409 if (sourceEnv.hasVar("SUPHP_PHP_CONFIG"))
410 env.putVar("PHPRC", sourceEnv.getVar("SUPHP_PHP_CONFIG"));
411 if (sourceEnv.hasVar("SUPHP_AUTH_USER")
412 && sourceEnv.hasVar("SUPHP_AUTH_PW")) {
413 env.putVar("PHP_AUTH_USER", sourceEnv.getVar("SUPHP_AUTH_USER"));
414 env.putVar("PHP_AUTH_PW", sourceEnv.getVar("SUPHP_AUTH_PW"));
417 // PHP may need this, when compiled with security features
418 if (!env.hasVar("REDIRECT_STATUS")) {
419 env.putVar("REDIRECT_STATUS", "200");
427 std::string suPHP::Application::getInterpreter(
428 const Environment& env, const Configuration& config)
429 throw (SecurityException) {
430 if (!env.hasVar("SUPHP_HANDLER"))
431 throw SecurityException("Environment variable SUPHP_HANDLER not set",
433 std::string handler = env.getVar("SUPHP_HANDLER");
435 std::string interpreter = "";
437 interpreter = config.getInterpreter(handler);
438 } catch (KeyNotFoundException& e) {
439 throw SecurityException ("Handler not found in configuration", e,
447 TargetMode suPHP::Application::getTargetMode(const std::string& interpreter)
448 throw (SecurityException) {
449 if (interpreter.substr(0, 4) == "php:")
450 return TARGETMODE_PHP;
451 else if (interpreter == "execute:!self")
452 return TARGETMODE_SELFEXECUTE;
454 throw SecurityException("Unknown Interpreter: " + interpreter,
459 void suPHP::Application::executeScript(const std::string& scriptFilename,
460 const std::string& interpreter,
462 const Environment& env,
463 const Configuration& config) const
464 throw (SoftException) {
466 // Change working directory to script path
467 API_Helper::getSystemAPI().setCwd(
468 File(scriptFilename).getParentDirectory().getPath());
469 if (mode == TARGETMODE_PHP) {
470 std::string interpreterPath = interpreter.substr(4);
472 cline.putArgument(interpreterPath);
473 API_Helper::getSystemAPI().execute(interpreterPath, cline, env);
474 } else if (mode == TARGETMODE_SELFEXECUTE) {
476 cline.putArgument(scriptFilename);
477 API_Helper::getSystemAPI().execute(scriptFilename, cline, env);
479 } catch (SystemException& e) {
480 throw SoftException("Could not execute script \"" + scriptFilename
481 + "\"", e, __FILE__, __LINE__);
486 void suPHP::Application::checkParentDirectories(const File& file,
487 const UserInfo& owner,
488 const Configuration& config) const throw (SoftException) {
489 File directory = file;
490 Logger& logger = API_Helper::getSystemAPI().getSystemLogger();
492 directory = directory.getParentDirectory();
494 UserInfo directoryOwner = directory.getUser();
495 if (directoryOwner != owner && !directoryOwner.isSuperUser()) {
496 std::string error = "Directory " + directory.getPath()
497 + " is not owned by " + owner.getUsername();
498 logger.logWarning(error);
499 throw SoftException(error, __FILE__, __LINE__);
502 if (!directory.isSymlink()
503 && !config.getAllowDirectoryGroupWriteable()
504 && directory.hasGroupWriteBit()) {
505 std::string error = "Directory \"" + directory.getPath()
506 + "\" is writeable by group";
507 logger.logWarning(error);
508 throw SoftException(error, __FILE__, __LINE__);
511 if (!directory.isSymlink()
512 && !config.getAllowDirectoryOthersWriteable()
513 && directory.hasOthersWriteBit()) {
514 std::string error = "Directory \"" + directory.getPath()
515 + "\" is writeable by others";
516 logger.logWarning(error);
517 throw SoftException(error, __FILE__, __LINE__);
519 } while (directory.getPath() != "/");
523 int main(int argc, char **argv) {
525 API& api = API_Helper::getSystemAPI();
529 for (int i=0; i<argc; i++) {
530 cmdline.putArgument(argv[i]);
532 env = api.getProcessEnvironment();
533 return app.run(cmdline, env);
534 } catch (Exception& e) {