File indexing completed on 2025-02-16 13:03:27
0001 /* 0002 This file is part of the KDE project, module kdesu. 0003 SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <jansen@kde.org> 0004 0005 Sudo support added by Jonathan Riddell <jriddell@ ubuntu.com> 0006 SPDX-FileCopyrightText: 2005 Canonical Ltd // krazy:exclude=copyright (no email) 0007 0008 SPDX-License-Identifier: GPL-2.0-only 0009 0010 su.cpp: Execute a program as another user with "class SuProcess". 0011 */ 0012 0013 #include "suprocess.h" 0014 0015 #include "kcookie_p.h" 0016 #include "stubprocess_p.h" 0017 #include <ksu_debug.h> 0018 0019 #include <QFile> 0020 #include <QStandardPaths> 0021 #include <qplatformdefs.h> 0022 0023 #include <KConfig> 0024 #include <KConfigGroup> 0025 #include <KSharedConfig> 0026 #include <kuser.h> 0027 0028 #if defined(KDESU_USE_SUDO_DEFAULT) 0029 #define DEFAULT_SUPER_USER_COMMAND QStringLiteral("sudo") 0030 #elif defined(KDESU_USE_DOAS_DEFAULT) 0031 #define DEFAULT_SUPER_USER_COMMAND QStringLiteral("doas") 0032 #else 0033 #define DEFAULT_SUPER_USER_COMMAND QStringLiteral("su") 0034 #endif 0035 0036 namespace KDESu 0037 { 0038 using namespace KDESuPrivate; 0039 0040 class SuProcessPrivate : public StubProcessPrivate 0041 { 0042 public: 0043 bool isPrivilegeEscalation() const; 0044 QString superUserCommand; 0045 }; 0046 0047 bool SuProcessPrivate::isPrivilegeEscalation() const 0048 { 0049 return (superUserCommand == QLatin1String("sudo") || superUserCommand == QLatin1String("doas")); 0050 } 0051 0052 SuProcess::SuProcess(const QByteArray &user, const QByteArray &command) 0053 : StubProcess(*new SuProcessPrivate) 0054 { 0055 Q_D(SuProcess); 0056 0057 m_user = user; 0058 m_command = command; 0059 0060 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0061 KConfigGroup group(config, "super-user-command"); 0062 d->superUserCommand = group.readEntry("super-user-command", DEFAULT_SUPER_USER_COMMAND); 0063 0064 if (!d->isPrivilegeEscalation() && d->superUserCommand != QLatin1String("su")) { 0065 qCWarning(KSU_LOG) << "unknown super user command."; 0066 d->superUserCommand = DEFAULT_SUPER_USER_COMMAND; 0067 } 0068 } 0069 0070 SuProcess::~SuProcess() = default; 0071 0072 QString SuProcess::superUserCommand() 0073 { 0074 Q_D(SuProcess); 0075 0076 return d->superUserCommand; 0077 } 0078 0079 bool SuProcess::useUsersOwnPassword() 0080 { 0081 Q_D(SuProcess); 0082 0083 if (d->isPrivilegeEscalation() && m_user == "root") { 0084 return true; 0085 } 0086 0087 KUser user; 0088 return user.loginName() == QString::fromUtf8(m_user); 0089 } 0090 0091 int SuProcess::checkInstall(const char *password) 0092 { 0093 return exec(password, Install); 0094 } 0095 0096 int SuProcess::checkNeedPassword() 0097 { 0098 return exec(nullptr, NeedPassword); 0099 } 0100 0101 /* 0102 * Execute a command with su(1). 0103 */ 0104 int SuProcess::exec(const char *password, int check) 0105 { 0106 Q_D(SuProcess); 0107 0108 if (check) { 0109 setTerminal(true); 0110 } 0111 0112 // since user may change after constructor (due to setUser()) 0113 // we need to override sudo with su for non-root here 0114 if (m_user != QByteArray("root")) { 0115 d->superUserCommand = QStringLiteral("su"); 0116 } 0117 0118 QList<QByteArray> args; 0119 if (d->isPrivilegeEscalation()) { 0120 args += "-u"; 0121 } 0122 0123 if (m_scheduler != SchedNormal || m_priority > 50) { 0124 args += "root"; 0125 } else { 0126 args += m_user; 0127 } 0128 0129 if (d->superUserCommand == QLatin1String("su")) { 0130 args += "-c"; 0131 } 0132 // Get the kdesu_stub and su command from a config file if set, used in test 0133 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0134 KConfigGroup group(config, "super-user-command"); 0135 const QString defaultPath = QStringLiteral(KDE_INSTALL_FULL_LIBEXECDIR_KF) + QStringLiteral("/kdesu_stub"); 0136 const QString kdesuStubPath = group.readEntry("kdesu_stub_path", defaultPath); 0137 args += kdesuStubPath.toLocal8Bit(); 0138 args += "-"; // krazy:exclude=doublequote_chars (QList, not QString) 0139 0140 const QString commandString = group.readEntry("command", QStandardPaths::findExecutable(d->superUserCommand)); 0141 const QByteArray command = commandString.toLocal8Bit(); 0142 if (command.isEmpty()) { 0143 return check ? SuNotFound : -1; 0144 } 0145 0146 // Turn echo off for conversion with kdesu_stub. Needs to be done before 0147 // it's started so that sudo copies this option to its internal PTY. 0148 enableLocalEcho(false); 0149 0150 if (StubProcess::exec(command, args) < 0) { 0151 return check ? SuNotFound : -1; 0152 } 0153 0154 SuErrors ret = (SuErrors)converseSU(password); 0155 0156 if (ret == error) { 0157 if (!check) { 0158 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] " 0159 << "Conversation with" << d->superUserCommand << "failed."; 0160 } 0161 return ret; 0162 } 0163 if (check == NeedPassword) { 0164 if (ret == killme) { 0165 if (d->isPrivilegeEscalation()) { 0166 // sudo can not be killed, just return 0167 return ret; 0168 } 0169 if (kill(m_pid, SIGKILL) < 0) { 0170 // FIXME SIGKILL doesn't work for sudo, 0171 // why is this different from su? 0172 // A: because sudo runs as root. Perhaps we could write a Ctrl+C to its stdin, instead? 0173 ret = error; 0174 } else { 0175 int iret = waitForChild(); 0176 if (iret < 0) { 0177 ret = error; 0178 } 0179 } 0180 } 0181 return ret; 0182 } 0183 0184 if (m_erase && password) { 0185 memset(const_cast<char *>(password), 0, qstrlen(password)); 0186 } 0187 0188 if (ret != ok) { 0189 kill(m_pid, SIGKILL); 0190 if (d->isPrivilegeEscalation()) { 0191 waitForChild(); 0192 } 0193 return SuIncorrectPassword; 0194 } 0195 0196 int iret = converseStub(check); 0197 if (iret < 0) { 0198 if (!check) { 0199 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] " 0200 << "Conversation with kdesu_stub failed."; 0201 } 0202 return iret; 0203 } else if (iret == 1) { 0204 kill(m_pid, SIGKILL); 0205 waitForChild(); 0206 return SuIncorrectPassword; 0207 } 0208 0209 if (check == Install) { 0210 waitForChild(); 0211 return 0; 0212 } 0213 0214 iret = waitForChild(); 0215 return iret; 0216 } 0217 0218 /* 0219 * Conversation with su: feed the password. 0220 * Return values: -1 = error, 0 = ok, 1 = kill me, 2 not authorized 0221 */ 0222 int SuProcess::converseSU(const char *password) 0223 { 0224 enum { 0225 WaitForPrompt, 0226 CheckStar, 0227 HandleStub, 0228 } state = WaitForPrompt; 0229 int colon; 0230 unsigned i; 0231 unsigned j; 0232 0233 QByteArray line; 0234 while (true) { 0235 line = readLine(); 0236 // return if problem. sudo checks for a second prompt || su gets a blank line 0237 if ((line.contains(':') && state != WaitForPrompt) || line.isNull()) { 0238 return (state == HandleStub ? notauthorized : error); 0239 } 0240 0241 if (line == "kdesu_stub") { 0242 unreadLine(line); 0243 return ok; 0244 } 0245 0246 switch (state) { 0247 case WaitForPrompt: { 0248 if (waitMS(fd(), 100) > 0) { 0249 // There is more output available, so this line 0250 // couldn't have been a password prompt (the definition 0251 // of prompt being that there's a line of output followed 0252 // by a colon, and then the process waits). 0253 continue; 0254 } 0255 0256 const uint len = line.length(); 0257 // Match "Password: " with the regex ^[^:]+:[\w]*$. 0258 for (i = 0, j = 0, colon = 0; i < len; ++i) { 0259 if (line[i] == ':') { 0260 j = i; 0261 colon++; 0262 continue; 0263 } 0264 if (!isspace(line[i])) { 0265 j++; 0266 } 0267 } 0268 if (colon == 1 && line[j] == ':') { 0269 if (password == nullptr) { 0270 return killme; 0271 } 0272 if (waitSlave()) { 0273 return error; 0274 } 0275 write(fd(), password, strlen(password)); 0276 write(fd(), "\n", 1); 0277 state = CheckStar; 0278 } 0279 break; 0280 } 0281 ////////////////////////////////////////////////////////////////////////// 0282 case CheckStar: { 0283 const QByteArray s = line.trimmed(); 0284 if (s.isEmpty()) { 0285 state = HandleStub; 0286 break; 0287 } 0288 const bool starCond = std::any_of(s.cbegin(), s.cend(), [](const char c) { 0289 return c != '*'; 0290 }); 0291 if (starCond) { 0292 return error; 0293 } 0294 state = HandleStub; 0295 break; 0296 } 0297 ////////////////////////////////////////////////////////////////////////// 0298 case HandleStub: 0299 break; 0300 ////////////////////////////////////////////////////////////////////////// 0301 } // end switch 0302 } // end while (true) 0303 return ok; 0304 } 0305 0306 void SuProcess::virtual_hook(int id, void *data) 0307 { 0308 StubProcess::virtual_hook(id, data); 0309 } 0310 0311 } // namespace KDESu