如何使用C++/Qt和PolKit-Qt-1创建具有root权限的文件

3

非常抱歉讨论略长,这也是我在StackOverflow上的第一个发布贴,请原谅我的不熟悉。

我通常使用C++/Qt Widget或C++/QML与Linux一起使用。这次,我需要使用root权限创建或写入文件,并使用以下URL(PolKit-Qt-1)创建和测试自己的C++/Qt Widget软件。

https://api.kde.org/polkit-qt-1/html/

我正在使用polkit-qt-gui-1创建软件(C++/Qt),以便在按下按钮时创建并以root权限编写文件。 Software image

然而,由于无法以root身份创建或编写文件,因此会产生权限错误,但是已经作为执行用户创建或编写文件。

也许有一些配置文件中存在错误,或者源代码存在缺失或错误的部分。

我想在按下按钮时使用root权限创建或写入文件。如何使用C ++ / Qt和PolKit-Qt-1创建或写入具有root权限的文件?

感谢您的合作。

下面是我的源代码。 polkit-1的操作文件也在下面。

main.cpp:

#include "mainwindow.h"
#include <QApplication>
    
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   MainWindow w;
   w.show();
   return a.exec();
}

mainwindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
   ui->setupUi(this);

   PolkitQt1::Gui::ActionButton *bt = new PolkitQt1::Gui::ActionButton(ui->pushButton, "org.qt.policykit.examples.write", this);
   bt->setText("Run with administrative privileges");
   bt->setVisible(true, PolkitQt1::Gui::Action::No | PolkitQt1::Gui::Action::Auth | PolkitQt1::Gui::Action::Yes);
   bt->setEnabled(true, PolkitQt1::Gui::Action::No | PolkitQt1::Gui::Action::Auth | PolkitQt1::Gui::Action::Yes);
   connect(bt, SIGNAL(triggered(bool)), this, SLOT(activateAction()));
   connect(bt, SIGNAL(clicked(QAbstractButton*,bool)), bt, SLOT(activate()));
   connect(bt, SIGNAL(authorized()), this, SLOT(onBtnClicked()));
}

MainWindow::~MainWindow()
{
   delete ui;
}

void MainWindow::activateAction()
{
   PolkitQt1::Gui::Action *action = qobject_cast<PolkitQt1::Gui::Action *>(sender());
   action->activate();
}

void MainWindow::onBtnClicked()
{
   PolkitQt1::Gui::Action *action = qobject_cast<PolkitQt1::Gui::Action *>(sender());

   qDebug() << "pretending to be the mechanism for action:" << action->actionId();

   PolkitQt1::UnixProcessSubject subject(static_cast<uint>(QCoreApplication::applicationPid()));
   PolkitQt1::Authority::Result result = PolkitQt1::Authority::instance()->checkAuthorizationSync(action->actionId(), subject,  PolkitQt1::Authority::AllowUserInteraction);
   if (result == PolkitQt1::Authority::Yes)
   {
      // Write /opt/sample.txt file with root privilege.
      writeTextFile("/opt/sample.txt", "foo bar");
   }
   else
   {
      return;
   }

   return;
}

void MainWindow::writeTextFile(QString FileName, QString strOutputData)
{
   QFileInfo FileInfo(FileName);

   QFile File(FileName);
   if(!File.open(QIODevice::WriteOnly))
   {
      QString strErrMsg = "File(" + FileInfo.fileName() + ") Open Error: " + File.errorString();
      qDebug() << strErrMsg;
      return;
   }

   QTextStream OutStream(&File);
   OutStream << strOutputData;

   File.close();

   return;
}

mainwindow.h:

#define MAINWINDOW_H

#include <QMainWindow>
#include <QtWidgets>
#include <QDBusContext>
#include <QDBusMessage>
#include <QDBusConnection>
#include <polkit-qt5-1/polkitqt1-gui-action.h>
#include <polkit-qt5-1/polkitqt1-gui-actionbutton.h>
#include <polkit-qt5-1/polkitqt1-gui-actionbuttons.h>
#include <polkit-qt5-1/polkitqt1-authority.h>
#include <dbus/dbus.h>
#include <QDebug>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow, protected QDBusContext
{
   Q_OBJECT

   private:
      void writeTextFile(QString FileName, QString strOutputData);

   public:
      MainWindow(QWidget *parent = nullptr);
      ~MainWindow();

   private:
      Ui::MainWindow *ui;

   private Q_SLOTS:
      void activateAction();
      void onBtnClicked();
};
#endif // MAINWINDOW_H

/usr/share/polkit-1/actions/org.qt.policykit.examples.policy:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE policyconfig PUBLIC '-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN' 'http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd'>
<policyconfig>
  <vendor>KDE</vendor>
  <vendor_url>http://www.kde.org</vendor_url>

  <action id="org.qt.policykit.examples.write">
    <description>Write</description>
    <message>Prevents PolKit-Qt-1 example from writing</message>
    <defaults>
      <allow_inactive>no</allow_inactive>
      <allow_active>auth_admin_keep</allow_active>
    </defaults>
  </action>

</policyconfig>

1
我认为你可能缺少一个作为root运行的辅助进程,用于实际写入。Polkit-qt1示例展示了对于“set”操作的这种情况,通过DBus将请求发送到辅助程序(然后进行身份验证)。否则,由于请求权限并不会给予您的进程root权限,因此它将无法正常工作。 - Hasturkun
谢谢您的评论。 我按照您所说创建了助手项目和 D-Bus 配置文件,已经成功运行。 我现在会上传它的源代码和配置文件。 请稍等片刻。 - presire
1个回答

4

抱歉让您等待这么久。
经过多次尝试,以下源代码允许程序在按下特定按钮时以管理员权限(root)运行。

请原谅这篇文章有点长。

主要可执行文件

主要可执行文件是GUI软件,通过按下按钮发送D-Bus消息。

main.cpp:

main.cpp has the same source code as above.

mainwindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"


MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::onAuthBtnClicked()
{
    QDBusConnection bus = QDBusConnection::systemBus();
    if (!bus.isConnected())
    {
        QMessageBox(QMessageBox::Critical, tr("D-Bus error"), tr("Cannot connect to the D-Bus session bus."), QMessageBox::Close, this).exec();
    }

    // this is our Special Action that after allowed will call the helper
    QDBusMessage message;
    message = QDBusMessage::createMethodCall("org.qt.policykit.examples", "/", "org.qt.policykit.examples", QLatin1String("write"));

    // If a method in a helper file has arguments, enter the arguments.
    //QList<QVariant> ArgsToHelper;
    //ArgsToHelper << QVariant::fromValue("foo") << QVariant::fromValue("bar");
    //message.setArguments(ArgsToHelper);

    // Send a message to DBus. (Execute the helper file.)
    QDBusMessage reply = QDBusConnection::systemBus().call(message);

    // Receive the return value (including arguments) from the helper file.
    // The methods in the helper file have two arguments, so check them.
    if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().size() == 2)
    {
        // the reply can be anything, here we receive a bool
        if (reply.arguments().at(0).toInt() == 0)
        {   // If the helper file method completes successfully after successful authentication
            QMessageBox(QMessageBox::NoIcon, tr("Successed"), tr("The file was written successfully."), QMessageBox::Close, this).exec();
        }
        else if (reply.arguments().at(0).toInt() == -1)
        {   // If the helper file method fails after successful authentication
            QString strErrMsg = reply.arguments().at(1).toString();
            QMessageBox(QMessageBox::Critical, tr("Failed"), tr("Failed to write file.\n") + strErrMsg, QMessageBox::Close, this).exec();
        }
        else
        {   // If the authentication is canceled
            QMessageBox(QMessageBox::NoIcon, tr("Cancelled"), tr("Writing of the file was canceled."), QMessageBox::Close, this).exec();
        }
    }
    else if (reply.type() == QDBusMessage::MethodCallMessage)
    {
        QMessageBox(QMessageBox::Warning, tr("Time out"), tr("Message did not receive a reply (timeout by message bus)."), QMessageBox::Close, this).exec();
    }
    else if (reply.type() == QDBusMessage::ErrorMessage)
    {
        QMessageBox(QMessageBox::Critical, tr("D-Bus error"), tr("Could not send message to D-Bus."), QMessageBox::Close, this).exec();
    }

    return;
}

mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtWidgets>
#include <QtGui>
#include <QDBusContext>
#include <QDBusMessage>
#include <QDBusConnection>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:     // Public Functions
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:    // Private Variables
    Ui::MainWindow *ui;

private Q_SLOTS:
    void onAuthBtnClicked();
};
#endif // MAINWINDOW_H

辅助执行程序

辅助执行程序是一种能够从D-Bus接收消息并以超级用户权限创建文件的软件。


main.cpp:

#include <QCoreApplication>
#include "SampleHelper.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    SampleHelper sample(argc, argv);

    return a.exec();
}

SampleHelper.cpp:

#include <QTimer>
#include <QFile>
#include <QTextStream>
#include <QDateTime>
#include "SampleHelper.h"
#include "SampleAdaptor.h"

#define MINUTE 30000

SampleHelper::SampleHelper(int &argc, char **argv) : QCoreApplication(argc, argv)
{
    (void) new SampleAdaptor(this);

    // Register the DBus service
    if (!QDBusConnection::systemBus().registerService("org.qt.policykit.examples"))
    {
        QTextStream ErrStream(stderr);
        ErrStream << QDBusConnection::systemBus().lastError().message();

        QTimer::singleShot(0, this, SLOT(quit()));
        return;
    }

    if (!QDBusConnection::systemBus().registerObject("/", this))
    {
        QTextStream ErrStream(stderr);
        ErrStream << "unable to register service interface to dbus";

        QTimer::singleShot(0, this, SLOT(quit()));
        return;
    }
    // Normally you will set a timeout so your application can
    // free some resources of the poor client machine ;)
    QTimer::singleShot(MINUTE, this, SLOT(quit()));
}

SampleHelper::~SampleHelper()
{
}

int SampleHelper::write(QString &strErrMsg)
{
    // message().service() is the service name of the caller
    // We can check if the caller is authorized to the following action
    PolkitQt1::Authority::Result result;
    PolkitQt1::SystemBusNameSubject subject(message().service());

    result = PolkitQt1::Authority::instance()->checkAuthorizationSync("org.qt.policykit.examples.write", subject , PolkitQt1::Authority::AllowUserInteraction);
    if (result == PolkitQt1::Authority::Yes)
    {   // Caller is authorized so we can perform the action
        return writeValue(strErrMsg);
    }
    else
    {   // Caller is not authorized so the action can't be performed
        return 1;
    }
}

int SampleHelper::writeValue(QString &strErrMsg)
{
    // This action must be authorized first. It will set the implicit
    // authorization for the Shout action by editing the .policy file
    try
    {
        QFileInfo FileInfo("/opt/sample.txt");

        QFile File("/opt/sample.txt");
        if(!File.open(QIODevice::WriteOnly))
        {
           strErrMsg = "File(" + FileInfo.fileName() + ") Open Error: " + File.errorString();
           return -1;
        }

        QDateTime current_date_time =QDateTime::currentDateTime();
        QString current_date = current_date_time.toString("yyyy.MM.dd hh:mm:ss.zzz ddd");
        QTextStream OutStream(&File);
        OutStream << current_date;

        File.close();
    }
    catch (QException &err)
    {
        strErrMsg = err.what();
    }

    return 0;
}

SampleHelper.h:

#ifndef SAMPLE_HELPER_H
#define SAMPLE_HELPER_H

#include <QDBusConnection>
#include <QDBusContext>
#include <QDBusMessage>
#include <QCoreApplication>
#include <polkit-qt5-1/polkitqt1-authority.h>

class SampleHelper : public QCoreApplication, protected QDBusContext
{
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "org.qt.policykit.examples")

private:
    int writeValue(QString &strErrMsg);

public:
    SampleHelper(int &argc, char **argv);
    ~SampleHelper() override;

public Q_SLOTS:
    int write(QString &strErrMsg);
};

#endif

执行qdbusxml2cpp命令(使用D-Bus接口文件),生成助手的适配器源代码文件和头文件。
qdbusxml2cpp -a SampleAdaptor -c SampleAdaptor -i SampleHelper.h -l SampleHelper org.qt.policykit.examples.xml

在这个例子中,生成了SampleAdaptor.cpp和SampleAdaptor.h文件。将该文件添加到辅助可执行文件的项目中。
如果是自动完成的话,在Qt .pro文件中会写入以下命令。
system(qdbusxml2cpp -a SampleAdaptor -c SampleAdaptor -i SampleHelper.h -l SampleHelper org.qt.policykit.examples.xml)

CMake文件中,以下命令已被编写。
qt_add_dbus_adaptor(
    SampleAdaptor_SRC
    org.qt.policykit.examples.xml
    SampleHelper.h
    SampleHelper
    SampleAdaptor
    SampleAdaptor
)

创建Polkit策略文件

/usr/share/polkit-1/actions/org.qt.policykit.examples.policy:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE policyconfig PUBLIC '-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN' 'http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd'>
<policyconfig>
  <vendor>presire</vendor>
  <vendor_url></vendor_url>

  <action id="org.qt.policykit.examples.write">
    <description>Write</description>
    <message>Prevents PolKit-Qt-1 example from writing</message>
    <defaults>
      <allow_inactive>no</allow_inactive>
      <allow_active>auth_admin</allow_active>
    </defaults>
  </action>
</policyconfig>

创建 D-Bus 配置文件

/usr/share/dbus-1/interfaces/org.qt.policykit.examples.xml:

<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
   <interface name="org.qt.policykit.examples">
       <method name="write" >
           <!-- OUT: whether the user gained the authorization -->
           <arg direction="out" type="i" name="code" />
           <arg direction="out" type="s" name="msg" />
       </method>
   </interface>
</node>

/usr/share/dbus-1/system-services/org.qt.policykit.examples.service:

[D-BUS Service]
Name=org.qt.policykit.examples
Exec=/<Path>/<to>/<Helper executable file>
User=root

/etc/dbus-1/system-local.conf:
or
/etc/dbus-1/system.d/org.qt.policykit.examples.conf:
or
/usr/share/dbus-1/system.d/org.qt.policykit.examples.conf:



<!DOCTYPE busconfig PUBLIC
 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>

  <!-- This configuration file specifies the required security policies
       for the PolicyKit examples to work. -->

  <!-- Only user root can own the PackageKit service -->
  <policy user="root">
    <allow own="org.qt.policykit.examples"/>
  </policy>

  <!-- Allow anyone to call into the service - we'll reject callers using PolicyKit -->
  <policy context="default">
    <allow send_destination="org.qt.policykit.examples"/>
  </policy>

</busconfig>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接