summaryrefslogtreecommitdiffstats
path: root/src/input/pvsCheckPrivileges.cpp
diff options
context:
space:
mode:
authorFabian Schillinger2010-11-01 17:35:27 +0100
committerFabian Schillinger2010-11-01 17:35:27 +0100
commitea3fb17345e5f82db9f2e98a8062e95797700ace (patch)
tree1da0d1a8ec9455364386af78762d0f6fed187824 /src/input/pvsCheckPrivileges.cpp
parentProcess start/stop/view functionality (diff)
parent[PVSGUI] No X required for --help and --version (diff)
downloadpvs-ea3fb17345e5f82db9f2e98a8062e95797700ace.tar.gz
pvs-ea3fb17345e5f82db9f2e98a8062e95797700ace.tar.xz
pvs-ea3fb17345e5f82db9f2e98a8062e95797700ace.zip
Merge branch 'master' of openslx.org:pvs
Conflicts: CMakeLists.txt src/core/pvsConnectionManager.cpp src/pvs.cpp src/pvs.h
Diffstat (limited to 'src/input/pvsCheckPrivileges.cpp')
-rw-r--r--src/input/pvsCheckPrivileges.cpp550
1 files changed, 550 insertions, 0 deletions
diff --git a/src/input/pvsCheckPrivileges.cpp b/src/input/pvsCheckPrivileges.cpp
new file mode 100644
index 0000000..2026c0a
--- /dev/null
+++ b/src/input/pvsCheckPrivileges.cpp
@@ -0,0 +1,550 @@
+/*
+ # Copyright (c) 2009 - OpenSLX Project, Computer Center University of Freiburg
+ #
+ # This program is free software distributed under the GPL version 2.
+ # See http://openslx.org/COPYING
+ #
+ # If you have any feedback please consult http://openslx.org/feedback and
+ # send your suggestions, praise, or complaints to feedback@openslx.org
+ #
+ # General information about OpenSLX can be found at http://openslx.org/
+ # --------------------------------------------------------------------------
+ # pvsCheckPrivileges.cpp:
+ # - A small program that checks whether or not it is called from
+ # a physical seat and conditionally executes the pvs input daemon.
+ # Additional security-relevant conditions should be checked here.
+ #
+ # The program is called with exactly one parameter, specifying the
+ # number of the file descriptor which is to be passed to its child.
+ # --------------------------------------------------------------------------
+ */
+
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <unistd.h>
+#include <iostream>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <QCoreApplication>
+#include <QDir>
+#include <QFileInfo>
+#include <QFileSystemWatcher>
+#include <QSettings>
+#include <QTimer>
+#include <QtDBus/QDBusArgument>
+#include <QtDBus/QDBusConnection>
+#include <QtDBus/QDBusInterface>
+#include <QtDBus/QDBusMetaType>
+#include <QtDBus/QDBusReply>
+#include <QtGlobal>
+#include <QDebug>
+#include <QUuid>
+#include "pvsPrivInputSocket.h"
+#include "pvsCheckPrivileges.h"
+
+using namespace std;
+
+#define TIMEOUT_VALUE 10000 /* Wait for max. 10 seconds */
+
+// We need these classes for PolicyKit access:
+struct PolKitSubject {
+ QString subject_kind;
+ QMap<QString, QVariant> subject_details;
+};
+Q_DECLARE_METATYPE(PolKitSubject);
+
+QDBusArgument& operator<<(QDBusArgument& arg, PolKitSubject const& subj)
+{
+ arg.beginStructure();
+ arg << subj.subject_kind << subj.subject_details;
+ arg.endStructure();
+ return arg;
+}
+
+QDBusArgument const& operator>>(QDBusArgument const& arg, PolKitSubject& subj)
+{
+ arg.beginStructure();
+ arg >> subj.subject_kind >> subj.subject_details;
+ arg.endStructure();
+ return arg;
+}
+
+struct PolKitAuthReply {
+ bool isAuthorized;
+ bool isChallenge;
+ QMap<QString, QString> details;
+};
+Q_DECLARE_METATYPE(PolKitAuthReply);
+
+QDBusArgument& operator<<(QDBusArgument& arg, PolKitAuthReply const& reply)
+{
+ arg.beginStructure();
+ arg << reply.isAuthorized << reply.isChallenge << reply.details;
+ arg.endStructure();
+ return arg;
+}
+
+QDBusArgument const& operator>>(QDBusArgument const& arg, PolKitAuthReply& reply)
+{
+ arg.beginStructure();
+ arg >> reply.isAuthorized >> reply.isChallenge >> reply.details;
+ arg.endStructure();
+ return arg;
+}
+
+// We need to pass QMap<QString, QString> to QVariant:
+typedef QMap<QString, QString> QStringStringMap;
+Q_DECLARE_METATYPE(QStringStringMap)
+
+PVSCheckPrivileges* PVSCheckPrivileges::instance()
+{
+ static PVSCheckPrivileges* static_instance = 0;
+ if(!static_instance)
+ {
+ static_instance = new PVSCheckPrivileges();
+ }
+ return static_instance;
+}
+
+void PVSCheckPrivileges::deleteInstance()
+{
+ delete instance();
+}
+
+PVSCheckPrivileges::PVSCheckPrivileges(QObject* parent)
+ : QObject(parent)
+{
+ // initialise our cache:
+ updateCachedUserDatabase();
+ rereadConfiguration();
+
+ // and make it update itself:
+ QStringList paths;
+ paths << "/etc/passwd" << "/etc/group" << pvsPrivInputGetSettingsPath();
+ _watcher = new QFileSystemWatcher(paths, this);
+ connect(_watcher, SIGNAL(fileChanged(QString const&)), this, SLOT(updateCachedUserDatabase()));
+ connect(_watcher, SIGNAL(fileChanged(QString const&)), this, SLOT(rereadConfiguration()));
+}
+
+PVSCheckPrivileges::~PVSCheckPrivileges()
+{
+}
+
+QString PVSCheckPrivileges::getSessionReference(CachedInputContext const& sender)
+{
+ if(!sender.isValid())
+ {
+ return QString();
+ }
+
+ QString sessionReference = _savedConsoleKitSession.value(sender, QString());
+ if(sessionReference.isNull())
+ {
+ QDBusConnection conn = QDBusConnection::systemBus();
+ // Find the name of the current session:
+ QDBusInterface manager("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", conn);
+ QDBusReply<QDBusObjectPath> replyGetSession = manager.call(QDBus::Block, "GetSessionForUnixProcess", (quint32)sender.pid);
+ if(!replyGetSession.isValid())
+ {
+ qWarning("Reply to GetSessionForUnixProcess is invalid: %s: %s", replyGetSession.error().name().toLocal8Bit().constData(), replyGetSession.error().message().toLocal8Bit().constData());
+ return QString();
+ }
+ _savedConsoleKitSession[sender] = sessionReference = replyGetSession.value().path();
+ }
+ return sessionReference;
+}
+
+PVSCheckPrivileges::SessionKind PVSCheckPrivileges::getSessionKind(CachedInputContext const& sender)
+{
+ if(!sender.isValid())
+ {
+ return SESSION_UNKNOWN;
+ }
+
+ // if the sender is root, we always suppose he works locally.
+ if(sender.uid == 0)
+ {
+ return SESSION_LOCAL;
+ }
+
+ QString sessionReference = getSessionReference(sender);
+ if(sessionReference.isNull())
+ {
+ return SESSION_LOOKUP_FAILURE;
+ }
+
+ QDBusConnection conn = QDBusConnection::systemBus();
+ QDBusInterface session("org.freedesktop.ConsoleKit", sessionReference, "org.freedesktop.ConsoleKit.Session", conn);
+ QDBusReply<bool> replyIsLocal = session.call(QDBus::Block, "IsLocal");
+
+ if(!replyIsLocal.isValid())
+ {
+ qWarning("Unable to find out whether the current session is local: %s: %s", replyIsLocal.error().name().toLocal8Bit().constData(), replyIsLocal.error().message().toLocal8Bit().constData());
+ return SESSION_LOOKUP_FAILURE;
+ }
+ return replyIsLocal.value() ? SESSION_LOCAL : SESSION_NONLOCAL;
+}
+
+PVSCheckPrivileges::UserPrivilege PVSCheckPrivileges::getUserPrivilege(CachedInputContext const& sender)
+{
+ // Always allow root:
+ if(sender.uid == 0)
+ {
+ return USER_PRIVILEGED;
+ }
+
+ // Or if the user is one of those enumerated in the privileged-users configuration value:
+ if(_privilegedUsers.contains(sender.uid))
+ {
+ return USER_PRIVILEGED;
+ }
+
+ // Or if the user is a member of one of the groups enumerated in the privileged-groups configuration value:
+ foreach(gid_t gid, _privilegedGroups)
+ {
+ if(_userGroupMap.contains(sender.uid, gid))
+ {
+ return USER_PRIVILEGED;
+ }
+ }
+
+ // User is not trivially privileged, so try to check with PolicyKit.
+#ifdef HAVE_POLKIT // but only if it is present
+ // For PolKit, we need the start-time of the process.
+ // On Linux, we can get it from /proc:
+ QString procStat = QString("/proc/%1/stat").arg(sender.pid);
+ QFile procStatFile(procStat);
+ if(!procStatFile.exists())
+ {
+ qWarning("Could not look up any info on process %d, its %s file does not exist", sender.pid, procStat.toLocal8Bit().constData());
+ return USER_LOOKUP_FAILURE;
+ }
+ procStatFile.open(QIODevice::ReadOnly);
+ QByteArray procStatBytes = procStatFile.readAll();
+ qDebug() << "Read stat file: " << procStatBytes;
+ QString procStatLine = QString::fromLocal8Bit(procStatBytes.constData(), procStatBytes.length());
+ QStringList procStatFields = procStatLine.split(QRegExp("\\s+"));
+ qDebug() << "Found stat fields: " << procStatFields;
+ bool ok;
+ quint64 startTime = procStatFields[21].toULongLong(&ok);
+ if(!ok)
+ {
+ qWarning("Could not find out start time for process %d", (int)sender.pid);
+ return USER_LOOKUP_FAILURE;
+ }
+
+ // Okay, we got the startTime. Now ask PolicyKit:
+
+ QDBusConnection conn = QDBusConnection::systemBus();
+ QDBusInterface intf("org.freedesktop.PolicyKit1", "/org/freedesktop/PolicyKit1/Authority", "org.freedesktop.PolicyKit1.Authority", conn);
+ PolKitSubject subj;
+ subj.subject_kind = "unix-process";
+ subj.subject_details["pid"] = (quint32)sender.pid;
+ subj.subject_details["start-time"] = startTime;
+ QDBusReply<PolKitAuthReply> reply = intf.call(QDBus::Block,
+ QLatin1String("CheckAuthorization"),
+ QVariant::fromValue(subj),
+ "org.openslx.pvs.privilegedinput",
+ QVariant::fromValue(QMap<QString, QString>()) /* No details */,
+ (quint32)1 /* Allow Interaction */,
+ QUuid::createUuid().toString() /* Cancellation ID */);
+ if(!reply.isValid())
+ {
+ QDBusError err = reply.error();
+
+ qWarning("Reply to CheckAuthorization is invalid: %s: %s", err.name().toLocal8Bit().constData(), err.message().toLocal8Bit().constData());
+ return USER_LOOKUP_FAILURE;
+ }
+ return reply.value().isAuthorized ? USER_PRIVILEGED : USER_UNPRIVILEGED;
+#else
+ return USER_UNPRIVILEGED;
+#endif
+}
+
+QString PVSCheckPrivileges::getX11SessionName(CachedInputContext const& sender)
+{
+ QString sessionReference = getSessionReference(sender);
+ if(sessionReference.isNull())
+ {
+ return QString();
+ }
+
+ QDBusConnection conn = QDBusConnection::systemBus();
+ QDBusInterface intf("org.freedesktop.ConsoleKit", sessionReference, "org.freedesktop.ConsoleKit.Session", conn);
+ QDBusReply<QString> reply = intf.call(QDBus::Block, QLatin1String("GetX11Display"));
+ if(!reply.isValid())
+ {
+ QDBusError err = reply.error();
+
+ qWarning("Reply to GetX11Display is invalid: %s: %s", err.name().toLocal8Bit().constData(), err.message().toLocal8Bit().constData());
+ return QString();
+ }
+
+ return reply.value();
+}
+
+QString PVSCheckPrivileges::getX11DisplayDevice(CachedInputContext const& sender)
+{
+ QString sessionReference = getSessionReference(sender);
+ if(sessionReference.isNull())
+ {
+ return QString();
+ }
+
+ QDBusConnection conn = QDBusConnection::systemBus();
+ QDBusInterface intf("org.freedesktop.ConsoleKit", sessionReference, "org.freedesktop.ConsoleKit.Session", conn);
+ QDBusReply<QString> reply = intf.call(QDBus::Block, QLatin1String("GetX11DisplayDevice"));
+ if(!reply.isValid())
+ {
+ QDBusError err = reply.error();
+
+ qWarning("Reply to GetX11DisplayDevice is invalid: %s: %s", err.name().toLocal8Bit().constData(), err.message().toLocal8Bit().constData());
+ return QString();
+ }
+
+ return reply.value();
+}
+
+bool PVSCheckPrivileges::require(SessionKind sessionKind, CachedInputContext const& sender)
+{
+ SessionKind cachedSessionKind;
+
+ if(sessionKind == SESSION_NONLOCAL)
+ {
+ // All sessions are at least non-local
+ return true;
+ }
+ else if(sessionKind == SESSION_LOCAL)
+ {
+ if((cachedSessionKind = _savedSessionKind.value(sender, SESSION_UNKNOWN)) == SESSION_UNKNOWN)
+ {
+ cachedSessionKind = getSessionKind(sender);
+ if(cachedSessionKind != SESSION_LOOKUP_FAILURE)
+ _savedSessionKind[sender] = cachedSessionKind;
+ qDebug("Got session kind: %s", toString(cachedSessionKind).toLocal8Bit().constData());
+ }
+
+ switch(cachedSessionKind)
+ {
+ case SESSION_LOOKUP_FAILURE:
+ case SESSION_UNKNOWN:
+ {
+ // If we cannot find out the correct session kind, look up what we should do in
+ // the configuration:
+ QSettings* config = pvsPrivInputGetSettings();
+ QVariant assumeLocal = config->value("assume-session-local", false);
+ if(!assumeLocal.canConvert(QVariant::Bool))
+ {
+ qWarning("There is an assume-session-local setting, but cannot convert it to boolean");
+ return false;
+ }
+ return assumeLocal.toBool();
+ }
+ case SESSION_LOCAL:
+ return true;
+ case SESSION_NONLOCAL:
+ return false;
+ default:
+ qWarning("Internal error: Undefined session kind %d", (int)cachedSessionKind);
+ return false;
+ }
+ }
+ else
+ {
+ qWarning("Internal error: It does not make sense to require an unknown session or undefined session kind %d", (int)sessionKind);
+ return false;
+ }
+}
+
+bool PVSCheckPrivileges::require(UserPrivilege userPrivilege, CachedInputContext const& sender)
+{
+ UserPrivilege cachedUserPrivilege;
+
+ if(userPrivilege == USER_UNPRIVILEGED)
+ {
+ // All users are unprivileged
+ return true;
+ }
+ else if(userPrivilege == USER_PRIVILEGED)
+ {
+ if((cachedUserPrivilege = _savedUserPrivilege.value(sender, USER_UNKNOWN)) == USER_UNKNOWN)
+ {
+ cachedUserPrivilege = getUserPrivilege(sender);
+ if(cachedUserPrivilege != USER_LOOKUP_FAILURE)
+ _savedUserPrivilege[sender] = cachedUserPrivilege;
+ qDebug("Got user privilege: %s", toString(cachedUserPrivilege).toLocal8Bit().constData());
+ }
+
+ switch(cachedUserPrivilege)
+ {
+ case USER_LOOKUP_FAILURE:
+ case USER_UNKNOWN:
+ {
+ // If we cannot find out the correct user privilege level, look up what we should do in
+ // the configuration:
+ QSettings* config = pvsPrivInputGetSettings();
+ QVariant assumePrivileged = config->value("assume-user-privileged", false);
+ if(!assumePrivileged.canConvert(QVariant::Bool))
+ {
+ qWarning("There is an assume-session-local setting, but cannot convert it to boolean");
+ return false;
+ }
+ return assumePrivileged.toBool();
+ }
+ case USER_PRIVILEGED:
+ return true;
+ case USER_UNPRIVILEGED:
+ return false;
+ default:
+ qWarning("Internal error: Found undefined user privilege level %d", (int)cachedUserPrivilege);
+ _savedUserPrivilege.remove(sender);
+ return false;
+ }
+ }
+ else
+ {
+ qWarning("Internal error: It does not make sense to require an unknown or undefined user privilege level %d", (int)userPrivilege);
+ return false;
+ }
+}
+
+bool PVSCheckPrivileges::require(SessionKind sessionKind,
+ UserPrivilege userPrivilege,
+ CachedInputContext const& sender)
+{
+ if(!require(sessionKind, sender))
+ return false;
+ if(!require(userPrivilege, sender))
+ return false;
+ return true;
+}
+
+uint qHash(CachedInputContext const& p)
+{
+ return qHash(qMakePair(p.pid, qMakePair(p.uid, p.gid)));
+}
+
+void PVSCheckPrivileges::updateCachedUserDatabase()
+{
+ QHash<QString, uid_t> userNames;
+
+ _userGroupMap.clear();
+
+ // assemble a list of known users and their primary groups:
+ setpwent(); // open the user database
+ struct passwd* userrec;
+ while((userrec = getpwent()))
+ {
+ userNames.insert(userrec->pw_name, userrec->pw_uid);
+ _userGroupMap.insert(userrec->pw_uid, userrec->pw_gid);
+ }
+ endpwent(); // close the database
+
+ // augment with secondary groups:
+ setgrent(); // open the group database
+ struct group* grouprec;
+ while((grouprec = getgrent()))
+ {
+ char** membername = grouprec->gr_mem;
+ while(*membername)
+ {
+ uid_t uid = userNames.value(*membername, (uid_t)-1);
+ if(uid != (uid_t)-1)
+ {
+ _userGroupMap.insert(uid, grouprec->gr_gid);
+ }
+ membername++;
+ }
+ }
+ endgrent();
+
+ // decisions may have changed, so clear the caches:
+ _savedUserPrivilege.clear();
+}
+
+void PVSCheckPrivileges::rereadConfiguration()
+{
+ QSettings* settings = pvsPrivInputReopenSettings();
+
+ _privilegedGroups.clear();
+ QVariant groupList = settings->value("privileged-groups");
+ if(groupList.isValid())
+ {
+ if(!groupList.canConvert(QVariant::StringList))
+ {
+ qWarning("There is a privileged-groups setting, but it cannot be converted to a list of strings.");
+ goto endGroupListScan;
+ }
+
+ QStringList groups = groupList.toStringList();
+ foreach(QString groupName, groups)
+ {
+ bool ok;
+ gid_t gid = groupName.toUInt(&ok);
+ if(ok)
+ {
+ _privilegedGroups.append(gid);
+ }
+ else
+ {
+ // lookup the name:
+ QByteArray groupNameBytes = groupName.toLocal8Bit();
+ struct group* group = getgrnam(groupNameBytes.constData());
+ if(group)
+ {
+ _privilegedGroups.append(group->gr_gid);
+ }
+ else
+ {
+ qWarning("privileged-groups setting contains %s which is neither a numeric GID "
+ "nor a valid group name. Skipping.",
+ groupNameBytes.constData());
+ }
+ }
+ }
+ }
+
+endGroupListScan:
+ _privilegedUsers.clear();
+ QVariant userList = settings->value("privileged-users");
+ if(userList.isValid())
+ {
+ if(!userList.canConvert(QVariant::StringList))
+ {
+ qWarning("There is a privileged-users setting, but it cannot be converted to a list of strings.");
+ goto endUserListScan;
+ }
+
+ QStringList users = userList.toStringList();
+ foreach(QString userName, users)
+ {
+ bool ok;
+ uid_t uid = userName.toUInt(&ok);
+ if(ok)
+ {
+ _privilegedUsers.append(uid);
+ }
+ else
+ {
+ // lookup the name:
+ QByteArray userNameBytes = userName.toLocal8Bit();
+ struct passwd* user = getpwnam(userNameBytes.constData());
+ if(user)
+ {
+ _privilegedUsers.append(user->pw_uid);
+ }
+ else
+ {
+ qWarning("privileged-users setting contains %s which is neither a numeric UID "
+ "nor a valid user name. Skipping.",
+ userNameBytes.constData());
+ }
+ }
+ }
+ }
+
+endUserListScan:
+ return;
+}