helper.cpp 2.58 KB
Newer Older
Harald Sitter's avatar
import  
Harald Sitter committed
1
2
3
4
5
6
7
8
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
// SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>

#include "helper.h"

#include <QDebug>
#include <QProcess>
#include <QFileInfo>
9
#include <QScopeGuard>
Harald Sitter's avatar
import  
Harald Sitter committed
10

11
12
13
14
15
16
17
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
18
#include <errno.h>
19
20
21

// Append name to /dev/ and ensure it is a trustable block device.
static QString nameToPath(const QString &name)
Harald Sitter's avatar
import  
Harald Sitter committed
22
{
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
    if (name.isEmpty()) {
        return {};
    }

    // This also excludes relative path shenanigans as they'd all need to contain a separator.
    if (name.contains(QLatin1Char('/'))) {
        qWarning() << "Device names must not contain slashes";
        return {};
    }

    const QString path = QStringLiteral("/dev/%1").arg(name);

    int blockFD = open(QFile::encodeName(path), O_PATH | O_NOFOLLOW);
    auto blockFDClose = qScopeGuard([blockFD] { close(blockFD); });
    if (blockFD == -1) {
        const int err = errno;
        qWarning() << "Failed to open block device" << name << strerror(err);
        return {};
    }

    struct stat sb;
    if (fstat(blockFD, &sb) == -1) {
        const int err = errno;
        qWarning() << "Failed to stat block device" << name << strerror(err);
        return {};
    }

    if (!S_ISBLK(sb.st_mode)) {
        qWarning() << "Device is not actually a block device" << name;
        return {};
    }

    if (sb.st_uid != 0) {
        qWarning() << "Device is not owned by root" << name;
        return {};
    }

    return path;
Harald Sitter's avatar
import  
Harald Sitter committed
61
62
63
64
}

ActionReply SMARTHelper::smartctl(const QVariantMap &args)
{
65
66
67
68
69
    // For security reasons we only accept fully resolved device names which
    // we use to construct the final /dev/$name path.
    const QString name = args.value(QStringLiteral("deviceName")).toString();
    const QString devicePath = nameToPath(name);
    if (devicePath.isEmpty()) {
Harald Sitter's avatar
import  
Harald Sitter committed
70
71
72
73
74
75
76
77
78
79
80
81
82
        return ActionReply::HelperErrorReply();
    }

    // PATH is super minimal when invoked through dbus
    setenv("PATH", "/usr/sbin:/sbin", 1);
    QProcess p;
    // json=c is badly documented and means "gimme json but don't pretty print"
    p.start(QStringLiteral("smartctl"),
            { QStringLiteral("--all"), QStringLiteral("--json=c"), devicePath },
            QProcess::ReadOnly);
    p.waitForFinished();

    ActionReply reply;
83
    reply.addData(QStringLiteral("exitCode"), p.exitCode());
Harald Sitter's avatar
import  
Harald Sitter committed
84
85
86
87
88
    reply.addData(QStringLiteral("data"), p.readAllStandardOutput());
    return reply;
}

KAUTH_HELPER_MAIN("org.kde.kded.smart", SMARTHelper)