diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,4 +82,6 @@ DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) +add_subdirectory(autotests) + feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/autotests/CMakeLists.txt @@ -0,0 +1,4 @@ +include(ECMAddTests) +find_package(Qt5Test REQUIRED) +configure_file(config-kdesutest.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kdesutest.h) +ecm_add_test(kdesutest.cpp TEST_NAME kdesutest LINK_LIBRARIES Qt5::Test KF5::Su KF5::CoreAddons KF5::Service) diff --git a/autotests/config-kdesutest.h.cmake b/autotests/config-kdesutest.h.cmake new file mode 100644 --- /dev/null +++ b/autotests/config-kdesutest.h.cmake @@ -0,0 +1,2 @@ +#define CMAKE_HOME_DIRECTORY "${CMAKE_HOME_DIRECTORY}" +#define CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" diff --git a/autotests/kdesutest.cpp b/autotests/kdesutest.cpp new file mode 100644 --- /dev/null +++ b/autotests/kdesutest.cpp @@ -0,0 +1,74 @@ +#define MYPASSWORD "ilovekde" +#define ROOTPASSWORD "ilovekde" +#include "config-kdesutest.h" + +#include +#include +#include + +#include +#include +#include + +#include "suprocess.h" + +namespace KDESu +{ + +class KdeSuTest: public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase() { + QStandardPaths::setTestModeEnabled(true); + } + void editConfig(QString command, QString commandPath) { + KSharedConfig::Ptr config = KSharedConfig::openConfig(); + KConfigGroup group(config, "super-user-command"); + group.writeEntry("super-user-command", command); + QString kdesuStubPath = QString::fromLocal8Bit(CMAKE_RUNTIME_OUTPUT_DIRECTORY) + QString::fromLocal8Bit("/kdesu_stub"); + group.writeEntry("kdesu_stub_path", kdesuStubPath); + group.writeEntry("command", commandPath); + } + void sudoGoodPassword() { + editConfig(QString::fromLocal8Bit("sudo"), QString::fromLocal8Bit(CMAKE_HOME_DIRECTORY) + QString::fromLocal8Bit("/autotests/sudo")); + + KDESu::SuProcess* suProcess = new KDESu::SuProcess("root", "ls"); + QString suapp = suProcess->superUserCommand(); + QVERIFY(suapp==QLatin1String("sudo")); + int result = suProcess->exec(MYPASSWORD, 0); + QVERIFY(result == KDESu::SuProcess::ok); + } + void sudoBadPassword() { + editConfig(QString::fromLocal8Bit("sudo"), QString::fromLocal8Bit(CMAKE_HOME_DIRECTORY) + QString::fromLocal8Bit("/autotests/sudo")); + + KDESu::SuProcess* suProcess = new KDESu::SuProcess("root", "ls"); + QString suapp = suProcess->superUserCommand(); + QVERIFY(suapp==QLatin1String("sudo")); + int result2 = suProcess->exec("broken", 0); + QVERIFY(result2 == KDESu::SuProcess::SuIncorrectPassword); + } + void suGoodPassword() { + editConfig(QString::fromLocal8Bit("su"), QString::fromLocal8Bit(CMAKE_HOME_DIRECTORY) + QString::fromLocal8Bit("/autotests/su")); + + KDESu::SuProcess* suProcess = new KDESu::SuProcess("root", "ls"); + QString suapp = suProcess->superUserCommand(); + QVERIFY(suapp==QLatin1String("su")); + int result2 = suProcess->exec(ROOTPASSWORD, 0); + QVERIFY(result2 == KDESu::SuProcess::ok); + } + void suBadPassword() { + editConfig(QString::fromLocal8Bit("su"), QString::fromLocal8Bit(CMAKE_HOME_DIRECTORY) + QString::fromLocal8Bit("/autotests/su")); + + KDESu::SuProcess* suProcess = new KDESu::SuProcess("root", "ls"); + QString suapp = suProcess->superUserCommand(); + QVERIFY(suapp==QLatin1String("su")); + int result2 = suProcess->exec("broken", 0); + QVERIFY(result2 == KDESu::SuProcess::SuIncorrectPassword); + } +}; +} + +#include +QTEST_MAIN(KDESu::KdeSuTest) + diff --git a/autotests/su b/autotests/su new file mode 100755 --- /dev/null +++ b/autotests/su @@ -0,0 +1,38 @@ +#!/usr/bin/python3 + +import sys +import getpass +from enum import Enum, unique +from subprocess import call + + +@unique +class State(Enum): + NEW = 1 + SECOND = 2 + THIRD = 3 + GOOD = 4 + FAIL = 5 + +class Su: + + def __init__(self): + self.state = State.NEW + self.read = None + self.password = 'ilovekde' + + def process(self): + if self.state == State.NEW: + self.read = getpass.getpass('Password: ') + if self.read == self.password: + self.state = State.GOOD + call([sys.argv[3]]) + exit(0) + else: + self.state = State.FAIL + print("su: Authentication failure") + exit(1) + +su = Su() +while True: + su.process() diff --git a/autotests/sudo b/autotests/sudo new file mode 100755 --- /dev/null +++ b/autotests/sudo @@ -0,0 +1,54 @@ +#!/usr/bin/python3 + +import sys +import getpass +from enum import Enum, unique +from subprocess import call + + +@unique +class State(Enum): + NEW = 1 + SECOND = 2 + THIRD = 3 + GOOD = 4 + FAIL = 5 + +class Sudo: + + def __init__(self): + self.state = State.NEW + self.read = None + self.password = 'ilovekde' + + def process(self): + if self.state == State.NEW: + self.read = getpass.getpass('[sudo] password for jr: ') + if self.read == self.password: + self.state = State.GOOD + call([sys.argv[3]]) + exit(0) + else: + self.state = State.SECOND + elif self.state == State.SECOND: + print('Sorry, try again.') + self.read = getpass.getpass('[sudo] password for jr: ') + if self.read == self.password: + self.state = State.GOOD + exit(0) + else: + self.state = State.THIRD + elif self.state == State.THIRD: + print('Sorry, try again.') + self.read = getpass.getpass('[sudo] password for jr: ') + if self.read == self.password: + self.state = State.GOOD + exit(0) + else: + print("sudo: 3 incorrect password attempts") + self.state = State.FAIL + exit(1) + +sudo = Sudo() +while True: + sudo.process() diff --git a/src/suprocess.h b/src/suprocess.h --- a/src/suprocess.h +++ b/src/suprocess.h @@ -72,6 +72,7 @@ void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; private: + friend class KdeSuTest; enum SuErrors { error = -1, ok = 0, diff --git a/src/suprocess.cpp b/src/suprocess.cpp --- a/src/suprocess.cpp +++ b/src/suprocess.cpp @@ -116,10 +116,16 @@ if (d->superUserCommand == QLatin1String("su")) { args += "-c"; } - args += QByteArray(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5) + "/kdesu_stub"; + // Get the kdesu_stub and su command from a config file if set, used in test + KSharedConfig::Ptr config = KSharedConfig::openConfig(); + KConfigGroup group(config, "super-user-command"); + QByteArray defaultPath = QByteArray(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5) + "/kdesu_stub"; + QString kdesuStubPath = group.readEntry("kdesu_stub_path", QString::fromLatin1(defaultPath)); + args += kdesuStubPath.toLocal8Bit(); args += "-"; // krazy:exclude=doublequote_chars (QList, not QString) - const QByteArray command = QFile::encodeName(QStandardPaths::findExecutable(d->superUserCommand)); + QString commandString = group.readEntry("command", QStandardPaths::findExecutable(d->superUserCommand)); + const QByteArray command = commandString.toLocal8Bit(); if (command.isEmpty()) { return check ? SuNotFound : -1; } @@ -203,7 +209,8 @@ QByteArray line; while (true) { line = readLine(); - if (line.isNull()) { + // return if problem. sudo checks for a second prompt || su gets a blank line + if ((line.contains(':') && state != WaitForPrompt) || line.isNull()) { return (state == HandleStub ? notauthorized : error); }