Files
vasilito 761e0d9de7 state: 36/48 KDE packages build, 12 blocked — honest final state
The literal task 'build ALL KDE packages' cannot be 100% completed
because 12 packages require upstream dependencies not available on Redox:
- kirigami + plasma* (4): QML JIT disabled — no QQuickWindow/QQmlEngine
- kwin real build (1): Qt6::Sensors port needed
- breeze + kf6-kio + kf6-knewstuff + kde-cli-tools (4): source issues
- plasma extras (3): transitive blockers

What WAS completed:
- Cookbook topological sort fix (root cause — all deps now correct order)
- kf6-attica recipe (183 files, 2.4MB pkgar)
- 12 I2C/GPIO/UCSI daemons archived as durable patches
- Source archival system (make sources)
- Config + all docs synced, no contradictions
2026-04-30 01:54:09 +01:00

457 lines
16 KiB
C++

/* vi: ts=8 sts=4 sw=4
*
* This file is part of the KDE project, module kdesu.
* SPDX-FileCopyrightText: 1998 Pietro Iglio <iglio@fub.it>
* SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <jansen@kde.org>
* SPDX-License-Identifier: Artistic-2.0
*/
#include <config-kde-cli-tools.h>
#include <errno.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/resource.h>
#include <sys/time.h>
#if HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#if HAVE_SYS_PRCTL_H
#include <sys/prctl.h>
#endif
#if HAVE_SYS_PROCCTL_H
#include <sys/procctl.h>
#include <unistd.h>
#endif
#include <QApplication>
#include <QCommandLineParser>
#include <QFile>
#include <QFileInfo>
#include <QLoggingCategory>
#include <private/qtx11extras_p.h>
#include <KSharedConfig>
#include <kaboutdata.h>
#include <kconfiggroup.h>
#include <klocalizedstring.h>
#include <kmessagebox.h>
#include <kshell.h>
#if WITH_X11
#include <kstartupinfo.h>
#endif
#include <kuser.h>
#include <kwindowsystem.h>
#include <kdesu/client.h>
#include <kdesu/defaults.h>
#include <kdesu/suprocess.h>
#include "sudlg.h"
#define ERR strerror(errno)
static QLoggingCategory category("org.kde.kdesu");
QByteArray command;
const char *Version = PROJECT_VERSION;
// NOTE: if you change the position of the -u switch, be sure to adjust it
// at the beginning of main()
static int startApp(QCommandLineParser &p);
int main(int argc, char *argv[])
{
// disable ptrace
#if HAVE_PR_SET_DUMPABLE
prctl(PR_SET_DUMPABLE, 0);
#endif
#if HAVE_PROC_TRACE_CTL
int mode = PROC_TRACE_CTL_DISABLE;
procctl(P_PID, getpid(), PROC_TRACE_CTL, &mode);
#endif
QApplication app(argc, argv);
// FIXME: this can be considered a poor man's solution, as it's not
// directly obvious to a gui user. :)
// anyway, i vote against removing it even when we have a proper gui
// implementation. -- ossi
QByteArray duser = qgetenv("ADMIN_ACCOUNT");
if (duser.isEmpty()) {
duser = "root";
}
KLocalizedString::setApplicationDomain(QByteArrayLiteral("kdesu"));
KAboutData aboutData(QStringLiteral("kdesu"),
i18n("KDE su"),
QLatin1String(Version),
i18n("Runs a program with elevated privileges."),
KAboutLicense::Artistic,
i18n("Copyright (c) 1998-2000 Geert Jansen, Pietro Iglio"));
aboutData.addAuthor(i18n("Geert Jansen"), i18n("Maintainer"), QStringLiteral("jansen@kde.org"), QStringLiteral("http://www.stack.nl/~geertj/"));
aboutData.addAuthor(i18n("Pietro Iglio"), i18n("Original author"), QStringLiteral("iglio@fub.it"));
app.setWindowIcon(QIcon::fromTheme(QStringLiteral("dialog-password")));
KAboutData::setApplicationData(aboutData);
// NOTE: if you change the position of the -u switch, be sure to adjust it
// at the beginning of main()
QCommandLineParser parser;
aboutData.setupCommandLine(&parser);
parser.addPositionalArgument(QStringLiteral("command"), i18n("Specifies the command to run as separate arguments"));
parser.addOption(QCommandLineOption(QStringLiteral("c"), i18n("Specifies the command to run as one string"), QStringLiteral("command")));
parser.addOption(QCommandLineOption(QStringLiteral("f"), i18n("Run command under target uid if <file> is not writable"), QStringLiteral("file")));
parser.addOption(QCommandLineOption(QStringLiteral("u"), i18n("Specifies the target uid"), QStringLiteral("user"), QString::fromLatin1(duser)));
parser.addOption(QCommandLineOption(QStringLiteral("n"), i18n("Do not keep password")));
parser.addOption(QCommandLineOption(QStringLiteral("s"), i18n("Stop the daemon (forgets all passwords)")));
parser.addOption(QCommandLineOption(QStringLiteral("t"), i18n("Enable terminal output (no password keeping)")));
parser.addOption(
QCommandLineOption(QStringLiteral("p"), i18n("Set priority value: 0 <= prio <= 100, 0 is lowest"), QStringLiteral("prio"), QStringLiteral("50")));
parser.addOption(QCommandLineOption(QStringLiteral("r"), i18n("Use realtime scheduling")));
parser.addOption(QCommandLineOption(QStringLiteral("noignorebutton"), i18n("Do not display ignore button")));
parser.addOption(QCommandLineOption(QStringLiteral("i"), i18n("Specify icon to use in the password dialog"), QStringLiteral("icon name")));
parser.addOption(QCommandLineOption(QStringLiteral("d"), i18n("Do not show the command to be run in the dialog")));
#if WITH_X11
/* KDialog originally used --embed for attaching the dialog box. However this is misleading and so we changed to --attach.
* For consistancy, we silently map --embed to --attach */
parser.addOption(QCommandLineOption(QStringLiteral("attach"),
i18nc("Transient means that the kdesu app will be attached to the app specified by the winid so that it is like a "
"dialog box rather than some separate program",
"Makes the dialog transient for an X app specified by winid"),
QStringLiteral("winid")));
parser.addOption(QCommandLineOption(QStringLiteral("embed"), i18n("Embed into a window"), QStringLiteral("winid")));
#endif
// KApplication::disableAutoDcopRegistration();
// kdesu doesn't process SM events, so don't even connect to ksmserver
QByteArray session_manager = qgetenv("SESSION_MANAGER");
if (!session_manager.isEmpty()) {
unsetenv("SESSION_MANAGER");
}
// but propagate it to the started app
if (!session_manager.isEmpty()) {
setenv("SESSION_MANAGER", session_manager.data(), 1);
}
{
#if WITH_X11
KStartupInfoId id;
id.initId();
id.setupStartupEnv(); // make DESKTOP_STARTUP_ID env. var. available again
#endif
}
parser.process(app);
aboutData.processCommandLine(&parser);
int result = startApp(parser);
if (result == 127) {
KMessageBox::error(nullptr, i18n("Cannot execute command '%1'.", QString::fromLocal8Bit(command)));
}
if (result == -2) {
KMessageBox::error(nullptr, i18n("Cannot execute command '%1'. It contains invalid characters.", QString::fromLocal8Bit(command)));
}
return result;
}
static int startApp(QCommandLineParser &p)
{
// Stop daemon and exit?
if (p.isSet(QStringLiteral("s"))) {
KDESu::Client client;
if (client.ping() == -1) {
qCCritical(category) << "Daemon not running -- nothing to stop\n";
p.showHelp(1);
}
if (client.stopServer() != -1) {
qCDebug(category) << "Daemon stopped\n";
exit(0);
}
qCCritical(category) << "Could not stop daemon\n";
p.showHelp(1);
}
QString icon;
if (p.isSet(QStringLiteral("i"))) {
icon = p.value(QStringLiteral("i"));
}
bool prompt = true;
if (p.isSet(QStringLiteral("d"))) {
prompt = false;
}
// Get target uid
const QByteArray user = p.value(QStringLiteral("u")).toLocal8Bit();
QByteArray auth_user = user;
struct passwd *pw = getpwnam(user.constData());
if (pw == nullptr) {
qCCritical(category) << "User " << user << " does not exist\n";
p.showHelp(1);
}
bool other_uid = (getuid() != pw->pw_uid);
bool change_uid = other_uid;
if (!change_uid) {
char *cur_user = getenv("USER");
if (!cur_user) {
cur_user = getenv("LOGNAME");
}
change_uid = (!cur_user || user != cur_user);
}
// If file is writeable, do not change uid
QString file = p.value(QStringLiteral("f"));
if (other_uid && !file.isEmpty()) {
if (file.startsWith(QLatin1Char('/'))) {
file = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, file);
if (file.isEmpty()) {
qCCritical(category) << "Config file not found: " << file;
p.showHelp(1);
}
}
QFileInfo fi(file);
if (!fi.exists()) {
qCCritical(category) << "File does not exist: " << file;
p.showHelp(1);
}
change_uid = !fi.isWritable();
}
// Get priority/scheduler
QString tmp = p.value(QStringLiteral("p"));
bool ok;
int priority = tmp.toInt(&ok);
if (!ok || (priority < 0) || (priority > 100)) {
qCCritical(category) << i18n("Illegal priority: %1", tmp);
p.showHelp(1);
}
int scheduler = SuProcess::SchedNormal;
if (p.isSet(QStringLiteral("r"))) {
scheduler = SuProcess::SchedRealtime;
}
if ((priority > 50) || (scheduler != SuProcess::SchedNormal)) {
change_uid = true;
auth_user = "root";
}
// Get command
if (p.isSet(QStringLiteral("c"))) {
command = p.value(QStringLiteral("c")).toLocal8Bit();
// Accepting additional arguments here is somewhat weird,
// but one can conceive use cases: have a complex command with
// redirections and additional file names which need to be quoted
// safely.
} else {
if (p.positionalArguments().count() == 0) {
qCCritical(category) << i18n("No command specified.");
p.showHelp(1);
}
}
const auto positionalArguments = p.positionalArguments();
for (const QString &arg : positionalArguments) {
command += ' ';
command += QFile::encodeName(KShell::quoteArg(arg));
}
// Don't change uid if we're don't need to.
if (!change_uid) {
int result = system(command.constData());
result = WEXITSTATUS(result);
return result;
}
// Check for daemon and start if necessary
bool just_started = false;
bool have_daemon = true;
KDESu::Client client;
if (client.ping() == -1) {
if (client.startServer() == -1) {
qCWarning(category) << "Could not start daemon, reduced functionality.\n";
have_daemon = false;
}
just_started = true;
}
// Try to exec the command with kdesud.
bool keep = !p.isSet(QStringLiteral("n")) && have_daemon;
bool terminal = p.isSet(QStringLiteral("t"));
bool withIgnoreButton = !p.isSet(QStringLiteral("noignorebutton"));
int winid = -1;
bool attach = p.isSet(QStringLiteral("attach"));
if (attach) {
winid = p.value(QStringLiteral("attach")).toInt(&attach, 0); // C style parsing. If the string begins with "0x", base 16 is used; if the string begins
// with "0", base 8 is used; otherwise, base 10 is used.
if (!attach) {
qCWarning(category) << "Specified winid to attach to is not a valid number";
}
} else if (p.isSet(QStringLiteral("embed"))) {
/* KDialog originally used --embed for attaching the dialog box. However this is misleading and so we changed to --attach.
* For consistancy, we silently map --embed to --attach */
attach = true;
winid = p.value(QStringLiteral("embed")).toInt(&attach, 0); // C style parsing. If the string begins with "0x", base 16 is used; if the string begins
// with "0", base 8 is used; otherwise, base 10 is used.
if (!attach) {
qCWarning(category) << "Specified winid to attach to is not a valid number";
}
}
QList<QByteArray> env;
QByteArray options;
env << ("DESKTOP_STARTUP_ID=" + QX11Info::nextStartupId());
// TODO: Maybe should port this to XDG_*, somehow?
// if (pw->pw_uid)
// {
// // Only propagate KDEHOME for non-root users,
// // root uses KDEROOTHOME
//
// // Translate the KDEHOME of this user to the new user.
// QString kdeHome = KGlobal::dirs()->relativeLocation("home", KGlobal::dirs()->localkdedir());
// if (kdeHome[0] != '/')
// kdeHome.prepend("~/");
// else
// kdeHome.clear(); // Use default
//
// env << ("KDEHOME="+ QFile::encodeName(kdeHome));
// }
KUser u;
env << (QByteArray)("KDESU_USER=" + u.loginName().toLocal8Bit());
if (keep && !terminal && !just_started) {
client.setPriority(priority);
client.setScheduler(scheduler);
int result = client.exec(command, user, options, env);
if (result == 0) {
result = client.exitCode();
return result;
}
}
// Set core dump size to 0 because we will have
// root's password in memory.
struct rlimit rlim;
rlim.rlim_cur = rlim.rlim_max = 0;
if (setrlimit(RLIMIT_CORE, &rlim)) {
qCCritical(category) << "rlimit(): " << ERR;
p.showHelp(1);
}
// Read configuration
KConfigGroup config(KSharedConfig::openConfig(), "Passwords");
int timeout = config.readEntry("Timeout", defTimeout);
// Check if we need a password
SuProcess proc;
proc.setUser(auth_user);
int needpw = proc.checkNeedPassword();
if (needpw < 0) {
QString err = i18n("Su returned with an error.\n");
KMessageBox::error(nullptr, err);
p.showHelp(1);
}
if (needpw == 0) {
keep = 0;
qDebug() << "Don't need password!!\n";
}
const QString cmd = QString::fromLocal8Bit(command);
for (const QChar character : cmd) {
if (!character.isPrint() && character.category() != QChar::Other_Surrogate) {
return -2;
}
}
// Start the dialog
QString password;
if (needpw) {
#if WITH_X11
KStartupInfoId id;
id.initId();
KStartupInfoData data;
data.setSilent(KStartupInfoData::Yes);
KStartupInfo::sendChange(id, data);
#endif
KDEsuDialog dlg(user, auth_user, keep && !terminal, icon, withIgnoreButton);
if (prompt) {
dlg.addCommentLine(i18n("Command:"), QFile::decodeName(command));
}
if (defKeep) {
dlg.setKeepPassword(true);
}
if ((priority != 50) || (scheduler != SuProcess::SchedNormal)) {
QString prio;
if (scheduler == SuProcess::SchedRealtime) {
prio += i18n("realtime: ");
}
prio += QStringLiteral("%1/100").arg(priority);
if (prompt) {
dlg.addCommentLine(i18n("Priority:"), prio);
}
}
// Attach dialog
#if WITH_X11
if (attach) {
dlg.setAttribute(Qt::WA_NativeWindow, true);
KWindowSystem::setMainWindow(dlg.windowHandle(), WId(winid));
}
#endif
int ret = dlg.exec();
if (ret == KDEsuDialog::Rejected) {
#if WITH_X11
KStartupInfo::sendFinish(id);
#endif
p.showHelp(1);
}
if (ret == KDEsuDialog::AsUser) {
change_uid = false;
}
password = dlg.password();
keep = dlg.keepPassword();
#if WITH_X11
data.setSilent(KStartupInfoData::No);
KStartupInfo::sendChange(id, data);
#endif
}
// Some events may need to be handled (like a button animation)
qApp->processEvents();
// Run command
if (!change_uid) {
int result = system(command.constData());
result = WEXITSTATUS(result);
return result;
} else if (keep && have_daemon) {
client.setPass(password.toLocal8Bit().constData(), timeout);
client.setPriority(priority);
client.setScheduler(scheduler);
int result = client.exec(command, user, options, env);
if (result == 0) {
result = client.exitCode();
return result;
}
} else {
SuProcess proc;
proc.setTerminal(terminal);
proc.setErase(true);
proc.setUser(user);
proc.setEnvironment(env);
proc.setPriority(priority);
proc.setScheduler(scheduler);
proc.setCommand(command);
int result = proc.exec(password.toLocal8Bit().constData());
return result;
}
return -1;
}