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 this->changeProcessPermissions(scriptFilename, config, env);
94 interpreter = this->getInterpreter(env, config);
95 targetMode = this->getTargetMode(interpreter);
97 // Prepare environment for new process
98 newEnv = this->prepareEnvironment(env, config, targetMode);
100 // Log attempt to execute script
101 logger.logInfo("Executing \"" + scriptFilename + "\" as UID "
102 + Util::intToStr(api.getEffectiveProcessUser().getUid())
105 api.getEffectiveProcessGroup().getGid()));
107 this->executeScript(scriptFilename, interpreter, targetMode, newEnv,
110 // Function should never return
111 // So, if we get here, return with error code
113 } catch (SoftException& e) {
114 if (!config.getErrorsToBrowser()) {
118 std::cout << "Content-Type: text/html\n"
123 << " <title>500 Internal Server Error</title>\n"
126 << " <h1>Internal Server Error</h1>\n"
127 << " <p>" << e.getMessage() << "</p>\n"
129 << " <address>suPHP " << PACKAGE_VERSION << "</address>\n"
136 void suPHP::Application::printAboutMessage() {
137 std::cerr << "suPHP version " << PACKAGE_VERSION << "\n";
138 std::cerr << "(c) 2002-2005 Sebastian Marsching\n";
139 std::cerr << std::endl;
140 std::cerr << "suPHP has to be called by mod_suphp to work." << std::endl;
144 void suPHP::Application::checkProcessPermissions(Configuration& config)
145 throw (SecurityException, LookupException) {
146 API& api = API_Helper::getSystemAPI();
147 if (api.getRealProcessUser() !=
148 api.getUserInfo(config.getWebserverUser())) {
149 throw SecurityException("Calling user is not webserver user!",
153 if (!api.getEffectiveProcessUser().isSuperUser()) {
154 throw SecurityException(
155 "Do not have root privileges. Executable not set-uid root?",
161 void suPHP::Application::checkScriptFile(
162 const std::string& scriptFilename,
163 const Configuration& config,
164 const Environment& environment) const
165 throw (SystemException, SoftException) {
166 Logger& logger = API_Helper::getSystemAPI().getSystemLogger();
167 File scriptFile = File(scriptFilename);
169 // Check wheter file exists
170 if (!scriptFile.exists()) {
171 std::string error = "File " + scriptFile.getPath() + " does not exist";
172 logger.logWarning(error);
173 throw SoftException(error, __FILE__, __LINE__);
176 // Get full path to script file
178 File realScriptFile = File(scriptFile.getRealPath());
179 File directory = realScriptFile.getParentDirectory();
181 // Check wheter script is in docroot
182 if (realScriptFile.getPath().find(config.getDocroot()) != 0) {
183 std::string error = "Script \"" + scriptFile.getPath()
184 + "\" resolving to \"" + realScriptFile.getPath()
185 + "\" not within configured docroot";
186 logger.logWarning(error);
187 throw SoftException(error, __FILE__, __LINE__);
190 // If enabled, check whether script is in the vhost's docroot
191 if (!environment.hasVar("DOCUMENT_ROOT"))
192 throw SoftException("Environment variable DOCUMENT_ROOT not set",
194 if (config.getCheckVHostDocroot()
195 && realScriptFile.getPath().find(environment.getVar("DOCUMENT_ROOT"))
198 std::string error = "File \"" + realScriptFile.getPath()
199 + "\" is not in document root of Vhost \""
200 + environment.getVar("DOCUMENT_ROOT") + "\"";
201 logger.logWarning(error);
202 throw SoftException(error, __FILE__, __LINE__);
205 // Check script and directory permissions
206 if (!realScriptFile.hasUserReadBit()) {
207 std::string error = "File \"" + realScriptFile.getPath()
209 logger.logWarning(error);
210 throw SoftException(error, __FILE__, __LINE__);
214 if (!config.getAllowFileGroupWriteable()
215 && realScriptFile.hasGroupWriteBit()) {
216 std::string error = "File \"" + realScriptFile.getPath()
217 + "\" is writeable by group";
218 logger.logWarning(error);
219 throw SoftException(error, __FILE__, __LINE__);
222 if (!config.getAllowDirectoryGroupWriteable()
223 && directory.hasGroupWriteBit()) {
224 std::string error = "Directory \"" + directory.getPath()
225 + "\" is writeable by group";
226 logger.logWarning(error);
227 throw SoftException(error, __FILE__, __LINE__);
230 if (!config.getAllowFileOthersWriteable()
231 && realScriptFile.hasOthersWriteBit()) {
232 std::string error = "File \"" + realScriptFile.getPath()
233 + "\" is writeable by others";
234 logger.logWarning(error);
235 throw SoftException(error, __FILE__, __LINE__);
238 if (!config.getAllowDirectoryOthersWriteable()
239 && directory.hasOthersWriteBit()) {
240 std::string error = "Directory \"" + directory.getPath()
241 + "\" is writeable by others";
242 logger.logWarning(error);
243 throw SoftException(error, __FILE__, __LINE__);
246 // Check UID/GID of symlink is matching target
247 if (scriptFile.getUser() != realScriptFile.getUser()
248 || scriptFile.getGroup() != realScriptFile.getGroup()) {
249 std::string error = "UID or GID of symlink \"" + scriptFile.getPath()
250 + "\" is not matching its target";
251 logger.logWarning(error);
252 throw SoftException(error, __FILE__, __LINE__);
257 void suPHP::Application::changeProcessPermissions(
258 const std::string& scriptFilename,
259 const Configuration& config,
260 const Environment& environment) const
261 throw (SystemException, SoftException, SecurityException) {
263 GroupInfo targetGroup;
265 File scriptFile = File(File(scriptFilename).getRealPath());
266 API& api = API_Helper::getSystemAPI();
267 Logger& logger = api.getSystemLogger();
269 // Make sure that exactly one mode is set
271 #if !defined(OPT_USERGROUP_OWNER) && !defined(OPT_USERGROUP_FORCE) && !defined(OPT_USERGROUP_PARANOID)
272 #error "No uid/gid change model specified"
274 #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))
275 #error "More than one uid/gid change model specified"
278 // Common code (for all security modes)
280 // Check UID/GID of script
281 if (scriptFile.getUser().getUid() < config.getMinUid()) {
282 std::string error = "UID of script \"" + scriptFilename
283 + "\" is smaller than min_uid";
284 logger.logWarning(error);
285 throw SoftException(error, __FILE__, __LINE__);
287 if (scriptFile.getGroup().getGid() < config.getMinGid()) {
288 std::string error = "GID of script \"" + scriptFilename
289 + "\" is smaller than min_gid";
290 logger.logWarning(error);
291 throw SoftException(error, __FILE__, __LINE__);
294 // Paranoid and force mode
296 #if (defined(OPT_USERGROUP_PARANOID) || defined(OPT_USERGROUP_FORCE))
297 std::string targetUsername, targetGroupname;
299 targetUsername = environment.getVar("SUPHP_USER");
300 targetGroupname = environment.getVar("SUPHP_GROUP");
301 } catch (KeyNotFoundException& e) {
302 throw SecurityException(
303 "Environment variable SUPHP_USER or SUPHP_GROUP not set",
307 if (targetUsername[0] == '#' && targetUsername.find_first_not_of(
308 "0123456789", 1) == std::string::npos) {
309 targetUser = api.getUserInfo(Util::strToInt(targetUsername.substr(1)));
311 targetUser = api.getUserInfo(targetUsername);
314 if (targetGroupname[0] == '#' && targetGroupname.find_first_not_of(
315 "0123456789", 1) == std::string::npos) {
316 targetGroup = api.getGroupInfo(
317 Util::strToInt(targetGroupname.substr(1)));
319 targetGroup = api.getGroupInfo(targetGroupname);
321 #endif // OPT_USERGROUP_PARANOID || OPT_USERGROUP_FORCE
325 #ifdef OPT_USERGROUP_OWNER
326 targetUser = scriptFile.getUser();
327 targetGroup = scriptFile.getGroup();
328 #endif // OPT_USERGROUP_OWNER
330 // Paranoid mode only
332 #ifdef OPT_USERGROUP_PARANOID
333 if (targetUser != scriptFile.getUser()) {
334 std::string error ="Mismatch between target UID ("
335 + Util::intToStr(targetUser.getUid()) + ") and UID ("
336 + Util::intToStr(scriptFile.getUser().getUid()) + ") of file \""
337 + scriptFile.getPath() + "\"";
338 logger.logWarning(error);
339 throw SoftException(error, __FILE__, __LINE__);
342 if (targetGroup != scriptFile.getGroup()) {
343 std::string error ="Mismatch between target GID ("
344 + Util::intToStr(targetGroup.getGid()) + ") and GID ("
345 + Util::intToStr(scriptFile.getGroup().getGid()) + ") of file \""
346 + scriptFile.getPath() + "\"";
347 logger.logWarning(error);
348 throw SoftException(error, __FILE__, __LINE__);
350 #endif // OPT_USERGROUP_PARANOID
352 // Common code used for all modes
354 // Set new group first, because we still need super-user privileges
356 api.setProcessGroup(targetGroup);
359 api.setProcessUser(targetUser);
361 api.setUmask(config.getUmask());
365 Environment suPHP::Application::prepareEnvironment(
366 const Environment& sourceEnv, const Configuration& config, TargetMode mode)
367 throw (KeyNotFoundException) {
368 // Create environment for new process from old environment
369 Environment env = sourceEnv;
371 // Delete unwanted environment variables
372 if (env.hasVar("LD_PRELOAD"))
373 env.deleteVar("LD_PRELOAD");
374 if (env.hasVar("LD_LIBRARY_PATH"))
375 env.deleteVar("LD_LIBRARY_PATH");
376 if (env.hasVar("SUPHP_USER"))
377 env.deleteVar("SUPHP_USER");
378 if (env.hasVar("SUPHP_GROUP"))
379 env.deleteVar("SUPHP_GROUP");
380 if (env.hasVar("SUPHP_HANDLER"))
381 env.deleteVar("SUPHP_HANDLER");
382 if (env.hasVar("SUPHP_AUTH_USER"))
383 env.deleteVar("SUPHP_AUTH_USER");
384 if (env.hasVar("SUPHP_AUTH_PW"))
385 env.deleteVar("SUPHP_AUTH_PW");
386 if (env.hasVar("SUPHP_PHP_CONFIG"))
387 env.deleteVar("SUPHP_PHP_CONFIG");
390 env.putVar("PATH", config.getEnvPath());
392 // If we are in PHP mode, set PHP specific variables
393 if (mode == TARGETMODE_PHP) {
394 if (sourceEnv.hasVar("SUPHP_PHP_CONFIG"))
395 env.putVar("PHPRC", sourceEnv.getVar("SUPHP_PHP_CONFIG"));
396 if (sourceEnv.hasVar("SUPHP_AUTH_USER")
397 && sourceEnv.hasVar("SUPHP_AUTH_PW")) {
398 env.putVar("PHP_AUTH_USER", sourceEnv.getVar("SUPHP_AUTH_USER"));
399 env.putVar("PHP_AUTH_PW", sourceEnv.getVar("SUPHP_AUTH_PW"));
402 // PHP may need this, when compiled with security features
403 if (!env.hasVar("REDIRECT_STATUS")) {
404 env.putVar("REDIRECT_STATUS", "200");
412 std::string suPHP::Application::getInterpreter(
413 const Environment& env, const Configuration& config)
414 throw (SecurityException) {
415 if (!env.hasVar("SUPHP_HANDLER"))
416 throw SecurityException("Environment variable SUPHP_HANDLER not set",
418 std::string handler = env.getVar("SUPHP_HANDLER");
420 std::string interpreter = "";
422 interpreter = config.getInterpreter(handler);
423 } catch (KeyNotFoundException& e) {
424 throw SecurityException ("Handler not found in configuration", e,
432 TargetMode suPHP::Application::getTargetMode(const std::string& interpreter)
433 throw (SecurityException) {
434 if (interpreter.substr(0, 4) == "php:")
435 return TARGETMODE_PHP;
436 else if (interpreter == "execute:!self")
437 return TARGETMODE_SELFEXECUTE;
439 throw SecurityException("Unknown Interpreter: " + interpreter,
444 void suPHP::Application::executeScript(const std::string& scriptFilename,
445 const std::string& interpreter,
447 const Environment& env,
448 const Configuration& config) const
449 throw (SoftException) {
451 // Change working directory to script path
452 API_Helper::getSystemAPI().setCwd(
453 File(scriptFilename).getParentDirectory().getPath());
454 if (mode == TARGETMODE_PHP) {
455 std::string interpreterPath = interpreter.substr(4);
457 cline.putArgument(interpreterPath);
458 API_Helper::getSystemAPI().execute(interpreterPath, cline, env);
459 } else if (mode == TARGETMODE_SELFEXECUTE) {
461 cline.putArgument(scriptFilename);
462 API_Helper::getSystemAPI().execute(scriptFilename, cline, env);
464 } catch (SystemException& e) {
465 throw SoftException("Could not execute script \"" + scriptFilename
466 + "\"", e, __FILE__, __LINE__);
471 int main(int argc, char **argv) {
473 API& api = API_Helper::getSystemAPI();
477 for (int i=0; i<argc; i++) {
478 cmdline.putArgument(argv[i]);
480 env = api.getProcessEnvironment();
481 return app.run(cmdline, env);
482 } catch (Exception& e) {