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 // Log attempt to execute script
107 logger.logInfo("Executing \"" + scriptFilename + "\" as UID "
108 + Util::intToStr(api.getEffectiveProcessUser().getUid())
111 api.getEffectiveProcessGroup().getGid()));
113 this->executeScript(scriptFilename, interpreter, targetMode, newEnv,
116 // Function should never return
117 // So, if we get here, return with error code
119 } catch (SoftException& e) {
120 if (!config.getErrorsToBrowser()) {
124 std::cout << "Content-Type: text/html\n"
129 << " <title>500 Internal Server Error</title>\n"
132 << " <h1>Internal Server Error</h1>\n"
133 << " <p>" << e.getMessage() << "</p>\n"
135 << " <address>suPHP " << PACKAGE_VERSION << "</address>\n"
142 void suPHP::Application::printAboutMessage() {
143 std::cerr << "suPHP version " << PACKAGE_VERSION << "\n";
144 std::cerr << "(c) 2002-2005 Sebastian Marsching\n";
145 std::cerr << std::endl;
146 std::cerr << "suPHP has to be called by mod_suphp to work." << std::endl;
150 void suPHP::Application::checkProcessPermissions(Configuration& config)
151 throw (SecurityException, LookupException) {
152 API& api = API_Helper::getSystemAPI();
153 if (api.getRealProcessUser() !=
154 api.getUserInfo(config.getWebserverUser())) {
155 throw SecurityException("Calling user is not webserver user!",
159 if (!api.getEffectiveProcessUser().isSuperUser()) {
160 throw SecurityException(
161 "Do not have root privileges. Executable not set-uid root?",
167 void suPHP::Application::checkScriptFile(
168 const std::string& scriptFilename,
169 const Configuration& config,
170 const Environment& environment) const
171 throw (SystemException, SoftException) {
172 Logger& logger = API_Helper::getSystemAPI().getSystemLogger();
173 File scriptFile = File(scriptFilename);
175 // Check wheter file exists
176 if (!scriptFile.exists()) {
177 std::string error = "File " + scriptFile.getPath() + " does not exist";
178 logger.logWarning(error);
179 throw SoftException(error, __FILE__, __LINE__);
182 // Get full path to script file
184 File realScriptFile = File(scriptFile.getRealPath());
185 File directory = realScriptFile.getParentDirectory();
187 // Check wheter script is in docroot
188 if (realScriptFile.getPath().find(config.getDocroot()) != 0) {
189 std::string error = "Script \"" + scriptFile.getPath()
190 + "\" resolving to \"" + realScriptFile.getPath()
191 + "\" not within configured docroot";
192 logger.logWarning(error);
193 throw SoftException(error, __FILE__, __LINE__);
196 // If enabled, check whether script is in the vhost's docroot
197 if (!environment.hasVar("DOCUMENT_ROOT"))
198 throw SoftException("Environment variable DOCUMENT_ROOT not set",
200 if (config.getCheckVHostDocroot()
201 && realScriptFile.getPath().find(environment.getVar("DOCUMENT_ROOT"))
204 std::string error = "File \"" + realScriptFile.getPath()
205 + "\" is not in document root of Vhost \""
206 + environment.getVar("DOCUMENT_ROOT") + "\"";
207 logger.logWarning(error);
208 throw SoftException(error, __FILE__, __LINE__);
211 // Check script and directory permissions
212 if (!realScriptFile.hasUserReadBit()) {
213 std::string error = "File \"" + realScriptFile.getPath()
215 logger.logWarning(error);
216 throw SoftException(error, __FILE__, __LINE__);
220 if (!config.getAllowFileGroupWriteable()
221 && realScriptFile.hasGroupWriteBit()) {
222 std::string error = "File \"" + realScriptFile.getPath()
223 + "\" is writeable by group";
224 logger.logWarning(error);
225 throw SoftException(error, __FILE__, __LINE__);
228 if (!config.getAllowDirectoryGroupWriteable()
229 && directory.hasGroupWriteBit()) {
230 std::string error = "Directory \"" + directory.getPath()
231 + "\" is writeable by group";
232 logger.logWarning(error);
233 throw SoftException(error, __FILE__, __LINE__);
236 if (!config.getAllowFileOthersWriteable()
237 && realScriptFile.hasOthersWriteBit()) {
238 std::string error = "File \"" + realScriptFile.getPath()
239 + "\" is writeable by others";
240 logger.logWarning(error);
241 throw SoftException(error, __FILE__, __LINE__);
244 if (!config.getAllowDirectoryOthersWriteable()
245 && directory.hasOthersWriteBit()) {
246 std::string error = "Directory \"" + directory.getPath()
247 + "\" is writeable by others";
248 logger.logWarning(error);
249 throw SoftException(error, __FILE__, __LINE__);
252 // Check UID/GID of symlink is matching target
253 if (scriptFile.getUser() != realScriptFile.getUser()
254 || scriptFile.getGroup() != realScriptFile.getGroup()) {
255 std::string error = "UID or GID of symlink \"" + scriptFile.getPath()
256 + "\" is not matching its target";
257 logger.logWarning(error);
258 throw SoftException(error, __FILE__, __LINE__);
263 void suPHP::Application::changeProcessPermissions(
264 const std::string& scriptFilename,
265 const Configuration& config,
266 const Environment& environment) const
267 throw (SystemException, SoftException, SecurityException) {
269 GroupInfo targetGroup;
271 File scriptFile = File(File(scriptFilename).getRealPath());
272 API& api = API_Helper::getSystemAPI();
273 Logger& logger = api.getSystemLogger();
275 // Make sure that exactly one mode is set
277 #if !defined(OPT_USERGROUP_OWNER) && !defined(OPT_USERGROUP_FORCE) && !defined(OPT_USERGROUP_PARANOID)
278 #error "No uid/gid change model specified"
280 #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))
281 #error "More than one uid/gid change model specified"
284 // Common code (for all security modes)
286 // Check UID/GID of script
287 if (scriptFile.getUser().getUid() < config.getMinUid()) {
288 std::string error = "UID of script \"" + scriptFilename
289 + "\" is smaller than min_uid";
290 logger.logWarning(error);
291 throw SoftException(error, __FILE__, __LINE__);
293 if (scriptFile.getGroup().getGid() < config.getMinGid()) {
294 std::string error = "GID of script \"" + scriptFilename
295 + "\" is smaller than min_gid";
296 logger.logWarning(error);
297 throw SoftException(error, __FILE__, __LINE__);
300 // Paranoid and force mode
302 #if (defined(OPT_USERGROUP_PARANOID) || defined(OPT_USERGROUP_FORCE))
303 std::string targetUsername, targetGroupname;
305 targetUsername = environment.getVar("SUPHP_USER");
306 targetGroupname = environment.getVar("SUPHP_GROUP");
307 } catch (KeyNotFoundException& e) {
308 throw SecurityException(
309 "Environment variable SUPHP_USER or SUPHP_GROUP not set",
313 if (targetUsername[0] == '#' && targetUsername.find_first_not_of(
314 "0123456789", 1) == std::string::npos) {
315 targetUser = api.getUserInfo(Util::strToInt(targetUsername.substr(1)));
317 targetUser = api.getUserInfo(targetUsername);
320 if (targetGroupname[0] == '#' && targetGroupname.find_first_not_of(
321 "0123456789", 1) == std::string::npos) {
322 targetGroup = api.getGroupInfo(
323 Util::strToInt(targetGroupname.substr(1)));
325 targetGroup = api.getGroupInfo(targetGroupname);
327 #endif // OPT_USERGROUP_PARANOID || OPT_USERGROUP_FORCE
331 #ifdef OPT_USERGROUP_OWNER
332 targetUser = scriptFile.getUser();
333 targetGroup = scriptFile.getGroup();
334 #endif // OPT_USERGROUP_OWNER
336 // Paranoid mode only
338 #ifdef OPT_USERGROUP_PARANOID
339 if (targetUser != scriptFile.getUser()) {
340 std::string error ="Mismatch between target UID ("
341 + Util::intToStr(targetUser.getUid()) + ") and UID ("
342 + Util::intToStr(scriptFile.getUser().getUid()) + ") of file \""
343 + scriptFile.getPath() + "\"";
344 logger.logWarning(error);
345 throw SoftException(error, __FILE__, __LINE__);
348 if (targetGroup != scriptFile.getGroup()) {
349 std::string error ="Mismatch between target GID ("
350 + Util::intToStr(targetGroup.getGid()) + ") and GID ("
351 + Util::intToStr(scriptFile.getGroup().getGid()) + ") of file \""
352 + scriptFile.getPath() + "\"";
353 logger.logWarning(error);
354 throw SoftException(error, __FILE__, __LINE__);
356 #endif // OPT_USERGROUP_PARANOID
358 // Common code used for all modes
360 // Set new group first, because we still need super-user privileges
362 api.setProcessGroup(targetGroup);
365 api.setProcessUser(targetUser);
367 api.setUmask(config.getUmask());
371 Environment suPHP::Application::prepareEnvironment(
372 const Environment& sourceEnv, const Configuration& config, TargetMode mode)
373 throw (KeyNotFoundException) {
374 // Create environment for new process from old environment
375 Environment env = sourceEnv;
377 // Delete unwanted environment variables
378 if (env.hasVar("LD_PRELOAD"))
379 env.deleteVar("LD_PRELOAD");
380 if (env.hasVar("LD_LIBRARY_PATH"))
381 env.deleteVar("LD_LIBRARY_PATH");
382 if (env.hasVar("SUPHP_USER"))
383 env.deleteVar("SUPHP_USER");
384 if (env.hasVar("SUPHP_GROUP"))
385 env.deleteVar("SUPHP_GROUP");
386 if (env.hasVar("SUPHP_HANDLER"))
387 env.deleteVar("SUPHP_HANDLER");
388 if (env.hasVar("SUPHP_AUTH_USER"))
389 env.deleteVar("SUPHP_AUTH_USER");
390 if (env.hasVar("SUPHP_AUTH_PW"))
391 env.deleteVar("SUPHP_AUTH_PW");
392 if (env.hasVar("SUPHP_PHP_CONFIG"))
393 env.deleteVar("SUPHP_PHP_CONFIG");
396 env.putVar("PATH", config.getEnvPath());
398 // If we are in PHP mode, set PHP specific variables
399 if (mode == TARGETMODE_PHP) {
400 if (sourceEnv.hasVar("SUPHP_PHP_CONFIG"))
401 env.putVar("PHPRC", sourceEnv.getVar("SUPHP_PHP_CONFIG"));
402 if (sourceEnv.hasVar("SUPHP_AUTH_USER")
403 && sourceEnv.hasVar("SUPHP_AUTH_PW")) {
404 env.putVar("PHP_AUTH_USER", sourceEnv.getVar("SUPHP_AUTH_USER"));
405 env.putVar("PHP_AUTH_PW", sourceEnv.getVar("SUPHP_AUTH_PW"));
408 // PHP may need this, when compiled with security features
409 if (!env.hasVar("REDIRECT_STATUS")) {
410 env.putVar("REDIRECT_STATUS", "200");
418 std::string suPHP::Application::getInterpreter(
419 const Environment& env, const Configuration& config)
420 throw (SecurityException) {
421 if (!env.hasVar("SUPHP_HANDLER"))
422 throw SecurityException("Environment variable SUPHP_HANDLER not set",
424 std::string handler = env.getVar("SUPHP_HANDLER");
426 std::string interpreter = "";
428 interpreter = config.getInterpreter(handler);
429 } catch (KeyNotFoundException& e) {
430 throw SecurityException ("Handler not found in configuration", e,
438 TargetMode suPHP::Application::getTargetMode(const std::string& interpreter)
439 throw (SecurityException) {
440 if (interpreter.substr(0, 4) == "php:")
441 return TARGETMODE_PHP;
442 else if (interpreter == "execute:!self")
443 return TARGETMODE_SELFEXECUTE;
445 throw SecurityException("Unknown Interpreter: " + interpreter,
450 void suPHP::Application::executeScript(const std::string& scriptFilename,
451 const std::string& interpreter,
453 const Environment& env,
454 const Configuration& config) const
455 throw (SoftException) {
457 // Change working directory to script path
458 API_Helper::getSystemAPI().setCwd(
459 File(scriptFilename).getParentDirectory().getPath());
460 if (mode == TARGETMODE_PHP) {
461 std::string interpreterPath = interpreter.substr(4);
463 cline.putArgument(interpreterPath);
464 API_Helper::getSystemAPI().execute(interpreterPath, cline, env);
465 } else if (mode == TARGETMODE_SELFEXECUTE) {
467 cline.putArgument(scriptFilename);
468 API_Helper::getSystemAPI().execute(scriptFilename, cline, env);
470 } catch (SystemException& e) {
471 throw SoftException("Could not execute script \"" + scriptFilename
472 + "\"", e, __FILE__, __LINE__);
477 int main(int argc, char **argv) {
479 API& api = API_Helper::getSystemAPI();
483 for (int i=0; i<argc; i++) {
484 cmdline.putArgument(argv[i]);
486 env = api.getProcessEnvironment();
487 return app.run(cmdline, env);
488 } catch (Exception& e) {