/* suPHP - (c)2002-2008 Sebastian Marsching This file is part of suPHP. suPHP is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. suPHP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with suPHP; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include "Environment.hpp" #include "UserInfo.hpp" #include "GroupInfo.hpp" #include "API_Linux_Logger.hpp" #include "Util.hpp" #include "API_Linux.hpp" extern char **environ; using namespace suPHP; SmartPtr suPHP::API_Linux::logger; bool suPHP::API_Linux::isSymlink(const std::string path) const throw (SystemException) { struct stat temp; if (lstat(path.c_str(), &temp) == -1) { throw SystemException(std::string("Could not stat \"") + path + "\": " + ::strerror(errno), __FILE__, __LINE__); } if ((temp.st_mode & S_IFLNK) == S_IFLNK) { return true; } else { return false; } } std::string suPHP::API_Linux::readSymlink(const std::string path) const throw (SystemException) { char buf[1024] = {0}; if (::readlink(path.c_str(), buf, 1023) == -1) { throw SystemException(std::string("Could not read symlink \"") + path + "\": " + ::strerror(errno), __FILE__, __LINE__); } if (buf[0] == '/') { return std::string(buf); } else { if (path.rfind('/') == std::string::npos) return std::string(buf); return path.substr(0, path.rfind('/') + 1) + std::string(buf); } } Environment suPHP::API_Linux::getProcessEnvironment() { Environment env; char **entry = ::environ; while (*entry != NULL) { std::string estr = std::string(*entry); int eqpos = estr.find("="); std::string name = estr.substr(0, eqpos); std::string content = estr.substr(eqpos + 1); env.putVar(name, content); entry++; } return env; } UserInfo suPHP::API_Linux::getUserInfo(const std::string username) throw (LookupException) { struct passwd *tmpuser = ::getpwnam(username.c_str()); if (tmpuser == NULL) { throw LookupException(std::string("Could not lookup username \"") + username + "\"", __FILE__, __LINE__); } return UserInfo(tmpuser->pw_uid); } UserInfo suPHP::API_Linux::getUserInfo(const int uid) { return UserInfo(uid); } GroupInfo suPHP::API_Linux::getGroupInfo(const std::string groupname) throw (LookupException) { struct group *tmpgroup = ::getgrnam(groupname.c_str()); if (tmpgroup == NULL) { throw LookupException(std::string("Could not lookup groupname \"") + groupname + "\"", __FILE__, __LINE__); } return GroupInfo(tmpgroup->gr_gid); } GroupInfo suPHP::API_Linux::getGroupInfo(const int gid) { return GroupInfo(gid); } UserInfo suPHP::API_Linux::getEffectiveProcessUser() { return UserInfo(::geteuid()); } UserInfo suPHP::API_Linux::getRealProcessUser() { return UserInfo(getuid()); } GroupInfo suPHP::API_Linux::getEffectiveProcessGroup() { return GroupInfo(getegid()); } GroupInfo suPHP::API_Linux::getRealProcessGroup() { return GroupInfo(getgid()); } Logger& suPHP::API_Linux::getSystemLogger() { if (suPHP::API_Linux::logger.get() == NULL) { suPHP::API_Linux::logger.reset(new API_Linux_Logger()); } return *(suPHP::API_Linux::logger); } void suPHP::API_Linux::setProcessUser(const UserInfo& user) const throw (SystemException) { // Reset supplementary groups if (::setgroups(0, NULL) == -1) { throw SystemException(std::string("setgroups() failed: ") + ::strerror(errno), __FILE__, __LINE__); } try { if (::initgroups(user.getUsername().c_str(), user.getGroupInfo().getGid()) == -1) { throw SystemException(std::string("initgroups() failed: ") + ::strerror(errno), __FILE__, __LINE__); } } catch (LookupException &e) { // Ignore this exception // If we have a UID, which does not exist in /etc/passwd // we simply cannot use supplementary groups } if (::setuid(user.getUid()) == -1) { throw SystemException(std::string("setuid() failed: ") + ::strerror(errno), __FILE__, __LINE__); } } void suPHP::API_Linux::setProcessGroup(const GroupInfo& group) const throw (SystemException) { if (::setgid(group.getGid()) == -1) { throw SystemException(std::string("setgid() failed: ") + ::strerror(errno), __FILE__, __LINE__); } } std::string suPHP::API_Linux::UserInfo_getUsername(const UserInfo& uinfo) const throw (LookupException) { struct passwd *tmpuser = ::getpwuid(uinfo.getUid()); if (tmpuser == NULL) { throw LookupException(std::string("Could not lookup UID ") + Util::intToStr(uinfo.getUid()), __FILE__, __LINE__); } return std::string(tmpuser->pw_name); } GroupInfo suPHP::API_Linux::UserInfo_getGroupInfo(const UserInfo& uinfo) const throw (LookupException) { struct passwd *tmpuser = NULL; tmpuser = getpwuid(uinfo.getUid()); if (tmpuser == NULL) { throw LookupException(std::string("Could not lookup UID ") + Util::intToStr(uinfo.getUid()), __FILE__, __LINE__); } return GroupInfo(tmpuser->pw_gid); } std::string suPHP::API_Linux::UserInfo_getHomeDirectory(const UserInfo& uinfo) const throw (LookupException) { struct passwd *tmpuser = NULL; tmpuser = getpwuid(uinfo.getUid()); if (tmpuser == NULL) { throw LookupException(std::string("Could not lookup UID ") + Util::intToStr(uinfo.getUid()), __FILE__, __LINE__); } return tmpuser->pw_dir; } bool suPHP::API_Linux::UserInfo_isSuperUser(const UserInfo& uinfo) const { if (uinfo.getUid() == 0) return true; else return false; } std::string suPHP::API_Linux::GroupInfo_getGroupname(const GroupInfo& ginfo) const throw (LookupException) { struct group *tmpgroup = ::getgrgid(ginfo.getGid()); if (tmpgroup == NULL) { throw LookupException(std::string("Could not lookup GID ") + Util::intToStr(ginfo.getGid()), __FILE__, __LINE__); } return std::string(tmpgroup->gr_name); } bool suPHP::API_Linux::File_exists(const File& file) const { struct stat dummy; if (::lstat(file.getPath().c_str(), &dummy) == 0) return true; else return false; } std::string suPHP::API_Linux::File_getRealPath(const File& file) const throw (SystemException) { std::string currentpath = file.getPath(); std::string resolvedpath = ""; bool failed = true; if ((currentpath.size() == 0) || (currentpath.at(0) != '/')) { currentpath = this->getCwd() + std::string("/") + currentpath; } // Limit iterations to avoid infinite symlink loops for (int i=0; i<512; i++) { // If nothing is left, we have finished if (currentpath.size() == 0) { resolvedpath = ("/" + resolvedpath); failed = false; break; } if (this->isSymlink(currentpath)) { currentpath = this->readSymlink(currentpath); } else { // We know last part is not a symlink, so it is resolved std::string part1 = currentpath.substr(0, currentpath.rfind('/')); std::string part2 = currentpath.substr(currentpath.rfind('/')+1); currentpath = part1; if (resolvedpath.size() == 0) resolvedpath = part2; else resolvedpath = part2 + "/" + resolvedpath; } } if (failed) { throw SystemException("Could not resolve path \"" + file.getPath() + "\"", __FILE__, __LINE__); } while (resolvedpath.find("/./") != std::string::npos) { int pos = resolvedpath.find("/./"); resolvedpath = resolvedpath.substr(0, pos) + resolvedpath.substr(pos + 2); } while (resolvedpath.find("/../") != std::string::npos) { int pos = resolvedpath.find("/../"); int pos2 = resolvedpath.rfind('/', pos-1); resolvedpath = resolvedpath.substr(0, pos2) + resolvedpath.substr(pos + 3); } if (resolvedpath.find("/..", resolvedpath.size() - 3) != std::string::npos) { resolvedpath = resolvedpath.substr(0, resolvedpath.size() - 3); resolvedpath = resolvedpath.substr(0, resolvedpath.rfind('/')); } if (resolvedpath.find("/.", resolvedpath.size() - 2) != std::string::npos) { resolvedpath = resolvedpath.substr(0, resolvedpath.size() - 2); } if (resolvedpath.size() == 0) resolvedpath = "/"; return resolvedpath; } bool suPHP::API_Linux::File_hasPermissionBit(const File& file, FileMode perm) const throw (SystemException) { struct stat temp; if (lstat(file.getPath().c_str(), &temp) == -1) { throw SystemException(std::string("Could not stat \"") + file.getPath() + "\": " + ::strerror(errno), __FILE__, __LINE__); } switch (perm) { case FILEMODE_USER_READ: if ((temp.st_mode & S_IRUSR) == S_IRUSR) return true; break; case FILEMODE_USER_WRITE: if ((temp.st_mode & S_IWUSR) == S_IWUSR) return true; break; case FILEMODE_USER_EXEC: if ((temp.st_mode & S_IXUSR) == S_IXUSR) return true; break; case FILEMODE_GROUP_READ: if ((temp.st_mode & S_IRGRP) == S_IRGRP) return true; break; case FILEMODE_GROUP_WRITE: if ((temp.st_mode & S_IWGRP) == S_IWGRP) return true; break; case FILEMODE_GROUP_EXEC: if ((temp.st_mode & S_IXGRP) == S_IXGRP) return true; break; case FILEMODE_OTHERS_READ: if ((temp.st_mode & S_IROTH) == S_IROTH) return true; break; case FILEMODE_OTHERS_WRITE: if ((temp.st_mode & S_IWOTH) == S_IWOTH) return true; break; case FILEMODE_OTHERS_EXEC: if ((temp.st_mode & S_IXOTH) == S_IXOTH) return true; break; } return false; } UserInfo suPHP::API_Linux::File_getUser(const File& file) const throw (SystemException) { struct stat temp; if (lstat(file.getPath().c_str(), &temp) == -1) { throw SystemException(std::string("Could not stat \"") + file.getPath() + "\": " + ::strerror(errno), __FILE__, __LINE__); } return UserInfo(temp.st_uid); } GroupInfo suPHP::API_Linux::File_getGroup(const File& file) const throw (SystemException) { struct stat temp; if (lstat(file.getPath().c_str(), &temp) == -1) { throw SystemException(std::string("Could not stat \"") + file.getPath() + "\": " + ::strerror(errno), __FILE__, __LINE__); } return GroupInfo(temp.st_gid); } bool suPHP::API_Linux::File_isSymlink(const File& file) const throw (SystemException) { return this->isSymlink(file.getPath()); } void suPHP::API_Linux::execute(std::string program, const CommandLine& cline, const Environment& env) const throw (SystemException) { char **sysCline = NULL; char **sysEnv = NULL; char **p = NULL; char *sysProgram = NULL; std::map map; int i; // Construct commandline sysCline = new char*[cline.size() + 1]; for (i=0; i::iterator pos = map.begin(); pos != map.end(); pos++) { std::string var; var = pos->first + "=" + pos->second; *p = new char[var.size()+1]; ::strncpy(*p, var.c_str(), var.size()+1); p++; } *p = NULL; // Make sure target program name is on heap sysProgram = new char[program.size() + 1]; ::strncpy(sysProgram, program.c_str(), program.size()+1); if (execve(sysProgram, sysCline, sysEnv) == -1) { throw SystemException("execve() for program \"" + program + "\" failed: " + ::strerror(errno), __FILE__, __LINE__); } // We are still here? This cannot be good.. throw SystemException("execve() for program \"" + program + "\" failed because of unknown reason", __FILE__, __LINE__); } std::string suPHP::API_Linux::getCwd() const throw (SystemException) { char buf[4096] = {0}; if (::getcwd(buf, 4095) == NULL) throw SystemException(std::string("getcwd() failed: ") + ::strerror(errno), __FILE__, __LINE__); return std::string(buf); } void suPHP::API_Linux::setCwd(const std::string& dir) const throw (SystemException) { if(::chdir(dir.c_str())) { throw SystemException(std::string("chdir() failed: ") + ::strerror(errno), __FILE__, __LINE__); } } void suPHP::API_Linux::setUmask(int mode) const throw (SystemException) { ::umask(mode); } void suPHP::API_Linux::chroot(const std::string& dir) const throw (SystemException) { if (::chroot(dir.c_str())) { throw SystemException(std::string("chroot() failed: ") + ::strerror(errno), __FILE__, __LINE__); } }