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);
181 // Check wheter file exists
182 if (!scriptFile.exists()) {
183 std::string error = "File " + scriptFile.getPath() + " does not exist";
184 logger.logWarning(error);
185 throw SoftException(error, __FILE__, __LINE__);
188 // Get full path to script file
190 File realScriptFile = File(scriptFile.getRealPath());
191 File directory = realScriptFile.getParentDirectory();
193 // Check wheter script is in docroot
194 if (realScriptFile.getPath().find(config.getDocroot()) != 0) {
195 std::string error = "Script \"" + scriptFile.getPath()
196 + "\" resolving to \"" + realScriptFile.getPath()
197 + "\" not within configured docroot";
198 logger.logWarning(error);
199 throw SoftException(error, __FILE__, __LINE__);
202 // If enabled, check whether script is in the vhost's docroot
203 if (!environment.hasVar("DOCUMENT_ROOT"))
204 throw SoftException("Environment variable DOCUMENT_ROOT not set",
206 if (config.getCheckVHostDocroot()
207 && realScriptFile.getPath().find(environment.getVar("DOCUMENT_ROOT"))
210 std::string error = "File \"" + realScriptFile.getPath()
211 + "\" is not in document root of Vhost \""
212 + environment.getVar("DOCUMENT_ROOT") + "\"";
213 logger.logWarning(error);
214 throw SoftException(error, __FILE__, __LINE__);
217 // Check script and directory permissions
218 if (!realScriptFile.hasUserReadBit()) {
219 std::string error = "File \"" + realScriptFile.getPath()
221 logger.logWarning(error);
222 throw SoftException(error, __FILE__, __LINE__);
226 if (!config.getAllowFileGroupWriteable()
227 && realScriptFile.hasGroupWriteBit()) {
228 std::string error = "File \"" + realScriptFile.getPath()
229 + "\" is writeable by group";
230 logger.logWarning(error);
231 throw SoftException(error, __FILE__, __LINE__);
234 if (!config.getAllowDirectoryGroupWriteable()
235 && directory.hasGroupWriteBit()) {
236 std::string error = "Directory \"" + directory.getPath()
237 + "\" is writeable by group";
238 logger.logWarning(error);
239 throw SoftException(error, __FILE__, __LINE__);
242 if (!config.getAllowFileOthersWriteable()
243 && realScriptFile.hasOthersWriteBit()) {
244 std::string error = "File \"" + realScriptFile.getPath()
245 + "\" is writeable by others";
246 logger.logWarning(error);
247 throw SoftException(error, __FILE__, __LINE__);
250 if (!config.getAllowDirectoryOthersWriteable()
251 && directory.hasOthersWriteBit()) {
252 std::string error = "Directory \"" + directory.getPath()
253 + "\" is writeable by others";
254 logger.logWarning(error);
255 throw SoftException(error, __FILE__, __LINE__);
258 // Check UID/GID of symlink is matching target
259 if (scriptFile.getUser() != realScriptFile.getUser()
260 || scriptFile.getGroup() != realScriptFile.getGroup()) {
261 std::string error = "UID or GID of symlink \"" + scriptFile.getPath()
262 + "\" is not matching its target";
263 logger.logWarning(error);
264 throw SoftException(error, __FILE__, __LINE__);
269 void suPHP::Application::changeProcessPermissions(
270 const std::string& scriptFilename,
271 const Configuration& config,
272 const Environment& environment) const
273 throw (SystemException, SoftException, SecurityException) {
275 GroupInfo targetGroup;
277 File scriptFile = File(File(scriptFilename).getRealPath());
278 API& api = API_Helper::getSystemAPI();
279 Logger& logger = api.getSystemLogger();
281 // Make sure that exactly one mode is set
283 #if !defined(OPT_USERGROUP_OWNER) && !defined(OPT_USERGROUP_FORCE) && !defined(OPT_USERGROUP_PARANOID)
284 #error "No uid/gid change model specified"
286 #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))
287 #error "More than one uid/gid change model specified"
290 // Common code (for all security modes)
292 // Check UID/GID of script
293 if (scriptFile.getUser().getUid() < config.getMinUid()) {
294 std::string error = "UID of script \"" + scriptFilename
295 + "\" is smaller than min_uid";
296 logger.logWarning(error);
297 throw SoftException(error, __FILE__, __LINE__);
299 if (scriptFile.getGroup().getGid() < config.getMinGid()) {
300 std::string error = "GID of script \"" + scriptFilename
301 + "\" is smaller than min_gid";
302 logger.logWarning(error);
303 throw SoftException(error, __FILE__, __LINE__);
306 // Paranoid and force mode
308 #if (defined(OPT_USERGROUP_PARANOID) || defined(OPT_USERGROUP_FORCE))
309 std::string targetUsername, targetGroupname;
311 targetUsername = environment.getVar("SUPHP_USER");
312 targetGroupname = environment.getVar("SUPHP_GROUP");
313 } catch (KeyNotFoundException& e) {
314 throw SecurityException(
315 "Environment variable SUPHP_USER or SUPHP_GROUP not set",
319 if (targetUsername[0] == '#' && targetUsername.find_first_not_of(
320 "0123456789", 1) == std::string::npos) {
321 targetUser = api.getUserInfo(Util::strToInt(targetUsername.substr(1)));
323 targetUser = api.getUserInfo(targetUsername);
326 if (targetGroupname[0] == '#' && targetGroupname.find_first_not_of(
327 "0123456789", 1) == std::string::npos) {
328 targetGroup = api.getGroupInfo(
329 Util::strToInt(targetGroupname.substr(1)));
331 targetGroup = api.getGroupInfo(targetGroupname);
333 #endif // OPT_USERGROUP_PARANOID || OPT_USERGROUP_FORCE
337 #ifdef OPT_USERGROUP_OWNER
338 targetUser = scriptFile.getUser();
339 targetGroup = scriptFile.getGroup();
340 #endif // OPT_USERGROUP_OWNER
342 // Paranoid mode only
344 #ifdef OPT_USERGROUP_PARANOID
345 if (targetUser != scriptFile.getUser()) {
346 std::string error ="Mismatch between target UID ("
347 + Util::intToStr(targetUser.getUid()) + ") and UID ("
348 + Util::intToStr(scriptFile.getUser().getUid()) + ") of file \""
349 + scriptFile.getPath() + "\"";
350 logger.logWarning(error);
351 throw SoftException(error, __FILE__, __LINE__);
354 if (targetGroup != scriptFile.getGroup()) {
355 std::string error ="Mismatch between target GID ("
356 + Util::intToStr(targetGroup.getGid()) + ") and GID ("
357 + Util::intToStr(scriptFile.getGroup().getGid()) + ") of file \""
358 + scriptFile.getPath() + "\"";
359 logger.logWarning(error);
360 throw SoftException(error, __FILE__, __LINE__);
362 #endif // OPT_USERGROUP_PARANOID
364 // Common code used for all modes
366 // Set new group first, because we still need super-user privileges
368 api.setProcessGroup(targetGroup);
371 api.setProcessUser(targetUser);
373 api.setUmask(config.getUmask());
377 Environment suPHP::Application::prepareEnvironment(
378 const Environment& sourceEnv, const Configuration& config, TargetMode mode)
379 throw (KeyNotFoundException) {
380 // Create environment for new process from old environment
381 Environment env = sourceEnv;
383 // Delete unwanted environment variables
384 if (env.hasVar("LD_PRELOAD"))
385 env.deleteVar("LD_PRELOAD");
386 if (env.hasVar("LD_LIBRARY_PATH"))
387 env.deleteVar("LD_LIBRARY_PATH");
388 if (env.hasVar("SUPHP_USER"))
389 env.deleteVar("SUPHP_USER");
390 if (env.hasVar("SUPHP_GROUP"))
391 env.deleteVar("SUPHP_GROUP");
392 if (env.hasVar("SUPHP_HANDLER"))
393 env.deleteVar("SUPHP_HANDLER");
394 if (env.hasVar("SUPHP_AUTH_USER"))
395 env.deleteVar("SUPHP_AUTH_USER");
396 if (env.hasVar("SUPHP_AUTH_PW"))
397 env.deleteVar("SUPHP_AUTH_PW");
398 if (env.hasVar("SUPHP_PHP_CONFIG"))
399 env.deleteVar("SUPHP_PHP_CONFIG");
402 env.putVar("PATH", config.getEnvPath());
404 // If we are in PHP mode, set PHP specific variables
405 if (mode == TARGETMODE_PHP) {
406 if (sourceEnv.hasVar("SUPHP_PHP_CONFIG"))
407 env.putVar("PHPRC", sourceEnv.getVar("SUPHP_PHP_CONFIG"));
408 if (sourceEnv.hasVar("SUPHP_AUTH_USER")
409 && sourceEnv.hasVar("SUPHP_AUTH_PW")) {
410 env.putVar("PHP_AUTH_USER", sourceEnv.getVar("SUPHP_AUTH_USER"));
411 env.putVar("PHP_AUTH_PW", sourceEnv.getVar("SUPHP_AUTH_PW"));
414 // PHP may need this, when compiled with security features
415 if (!env.hasVar("REDIRECT_STATUS")) {
416 env.putVar("REDIRECT_STATUS", "200");
424 std::string suPHP::Application::getInterpreter(
425 const Environment& env, const Configuration& config)
426 throw (SecurityException) {
427 if (!env.hasVar("SUPHP_HANDLER"))
428 throw SecurityException("Environment variable SUPHP_HANDLER not set",
430 std::string handler = env.getVar("SUPHP_HANDLER");
432 std::string interpreter = "";
434 interpreter = config.getInterpreter(handler);
435 } catch (KeyNotFoundException& e) {
436 throw SecurityException ("Handler not found in configuration", e,
444 TargetMode suPHP::Application::getTargetMode(const std::string& interpreter)
445 throw (SecurityException) {
446 if (interpreter.substr(0, 4) == "php:")
447 return TARGETMODE_PHP;
448 else if (interpreter == "execute:!self")
449 return TARGETMODE_SELFEXECUTE;
451 throw SecurityException("Unknown Interpreter: " + interpreter,
456 void suPHP::Application::executeScript(const std::string& scriptFilename,
457 const std::string& interpreter,
459 const Environment& env,
460 const Configuration& config) const
461 throw (SoftException) {
463 // Change working directory to script path
464 API_Helper::getSystemAPI().setCwd(
465 File(scriptFilename).getParentDirectory().getPath());
466 if (mode == TARGETMODE_PHP) {
467 std::string interpreterPath = interpreter.substr(4);
469 cline.putArgument(interpreterPath);
470 API_Helper::getSystemAPI().execute(interpreterPath, cline, env);
471 } else if (mode == TARGETMODE_SELFEXECUTE) {
473 cline.putArgument(scriptFilename);
474 API_Helper::getSystemAPI().execute(scriptFilename, cline, env);
476 } catch (SystemException& e) {
477 throw SoftException("Could not execute script \"" + scriptFilename
478 + "\"", e, __FILE__, __LINE__);
483 int main(int argc, char **argv) {
485 API& api = API_Helper::getSystemAPI();
489 for (int i=0; i<argc; i++) {
490 cmdline.putArgument(argv[i]);
492 env = api.getProcessEnvironment();
493 return app.run(cmdline, env);
494 } catch (Exception& e) {