File indexing completed on 2024-04-28 15:22:03

0001 /*
0002     This file is part of the KDE project, module kdesu.
0003     SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <jansen@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-only
0006 
0007     client.cpp: A client for kdesud.
0008 */
0009 
0010 #include "client.h"
0011 
0012 #include <config-kdesu.h>
0013 #include <ksu_debug.h>
0014 
0015 #include <cerrno>
0016 #include <sys/socket.h>
0017 #include <sys/un.h>
0018 
0019 #include <QFile>
0020 #include <QProcess>
0021 #include <QRegularExpression>
0022 #include <QStandardPaths>
0023 #include <qplatformdefs.h>
0024 
0025 extern int kdesuDebugArea();
0026 
0027 namespace KDESu
0028 {
0029 class KDEsuClientPrivate
0030 {
0031 public:
0032     KDEsuClientPrivate()
0033         : sockfd(-1)
0034     {
0035     }
0036     QString daemon;
0037     int sockfd;
0038     QByteArray sock;
0039 };
0040 
0041 #ifndef SUN_LEN
0042 #define SUN_LEN(ptr) ((QT_SOCKLEN_T)(((struct sockaddr_un *)0)->sun_path) + strlen((ptr)->sun_path))
0043 #endif
0044 
0045 KDEsuClient::KDEsuClient()
0046     : d(new KDEsuClientPrivate)
0047 {
0048 #if HAVE_X11
0049     QString display = QString::fromLocal8Bit(qgetenv("DISPLAY"));
0050     if (display.isEmpty()) {
0051         // we might be on Wayland
0052         display = QString::fromLocal8Bit(qgetenv("WAYLAND_DISPLAY"));
0053     }
0054     if (display.isEmpty()) {
0055         qCWarning(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
0056                            << "$DISPLAY is not set.";
0057         return;
0058     }
0059 
0060     // strip the screen number from the display
0061     display.remove(QRegularExpression(QStringLiteral("\\.[0-9]+$")));
0062 #else
0063     QString display = QStringLiteral("NODISPLAY");
0064 #endif
0065 
0066     d->sock = QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QStringLiteral("/kdesud_") + display);
0067     connect();
0068 }
0069 
0070 KDEsuClient::~KDEsuClient()
0071 {
0072     if (d->sockfd >= 0) {
0073         close(d->sockfd);
0074     }
0075 }
0076 
0077 int KDEsuClient::connect()
0078 {
0079     if (d->sockfd >= 0) {
0080         close(d->sockfd);
0081     }
0082     if (access(d->sock.constData(), R_OK | W_OK)) {
0083         d->sockfd = -1;
0084         return -1;
0085     }
0086 
0087     d->sockfd = socket(PF_UNIX, SOCK_STREAM, 0);
0088     if (d->sockfd < 0) {
0089         qCWarning(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
0090                            << "socket():" << strerror(errno);
0091         return -1;
0092     }
0093     struct sockaddr_un addr;
0094     addr.sun_family = AF_UNIX;
0095     strcpy(addr.sun_path, d->sock.constData());
0096 
0097     if (QT_SOCKET_CONNECT(d->sockfd, (struct sockaddr *)&addr, SUN_LEN(&addr)) < 0) {
0098         qCWarning(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
0099                            << "connect():" << strerror(errno);
0100         close(d->sockfd);
0101         d->sockfd = -1;
0102         return -1;
0103     }
0104 
0105 #if !defined(SO_PEERCRED) || !HAVE_STRUCT_UCRED
0106 #if HAVE_GETPEEREID
0107     uid_t euid;
0108     gid_t egid;
0109     // Security: if socket exists, we must own it
0110     if (getpeereid(d->sockfd, &euid, &egid) == 0 && euid != getuid()) {
0111         qCWarning(KSU_LOG) << "socket not owned by me! socket uid =" << euid;
0112         close(d->sockfd);
0113         d->sockfd = -1;
0114         return -1;
0115     }
0116 #else
0117 #ifdef __GNUC__
0118 #warning "Using sloppy security checks"
0119 #endif
0120     // We check the owner of the socket after we have connected.
0121     // If the socket was somehow not ours an attacker will be able
0122     // to delete it after we connect but shouldn't be able to
0123     // create a socket that is owned by us.
0124     QT_STATBUF s;
0125     if (QT_LSTAT(d->sock.constData(), &s) != 0) {
0126         qCWarning(KSU_LOG) << "stat failed (" << d->sock << ")";
0127         close(d->sockfd);
0128         d->sockfd = -1;
0129         return -1;
0130     }
0131     if (s.st_uid != getuid()) {
0132         qCWarning(KSU_LOG) << "socket not owned by me! socket uid =" << s.st_uid;
0133         close(d->sockfd);
0134         d->sockfd = -1;
0135         return -1;
0136     }
0137     if (!S_ISSOCK(s.st_mode)) {
0138         qCWarning(KSU_LOG) << "socket is not a socket (" << d->sock << ")";
0139         close(d->sockfd);
0140         d->sockfd = -1;
0141         return -1;
0142     }
0143 #endif
0144 #else
0145     struct ucred cred;
0146     QT_SOCKLEN_T siz = sizeof(cred);
0147 
0148     // Security: if socket exists, we must own it
0149     if (getsockopt(d->sockfd, SOL_SOCKET, SO_PEERCRED, &cred, &siz) == 0 && cred.uid != getuid()) {
0150         qCWarning(KSU_LOG) << "socket not owned by me! socket uid =" << cred.uid;
0151         close(d->sockfd);
0152         d->sockfd = -1;
0153         return -1;
0154     }
0155 #endif
0156 
0157     return 0;
0158 }
0159 
0160 QByteArray KDEsuClient::escape(const QByteArray &str)
0161 {
0162     QByteArray copy;
0163     copy.reserve(str.size() + 4);
0164     copy.append('"');
0165     for (const uchar c : str) {
0166         if (c < 32) {
0167             copy.append('\\');
0168             copy.append('^');
0169             copy.append(c + '@');
0170         } else {
0171             if (c == '\\' || c == '"') {
0172                 copy.append('\\');
0173             }
0174             copy.append(c);
0175         }
0176     }
0177     copy.append('"');
0178     return copy;
0179 }
0180 
0181 int KDEsuClient::command(const QByteArray &cmd, QByteArray *result)
0182 {
0183     if (d->sockfd < 0) {
0184         return -1;
0185     }
0186 
0187     if (send(d->sockfd, cmd.constData(), cmd.length(), 0) != (int)cmd.length()) {
0188         return -1;
0189     }
0190 
0191     char buf[1024];
0192     int nbytes = recv(d->sockfd, buf, 1023, 0);
0193     if (nbytes <= 0) {
0194         qCWarning(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
0195                            << "no reply from daemon.";
0196         return -1;
0197     }
0198     buf[nbytes] = '\000';
0199 
0200     QByteArray reply = buf;
0201     if (reply.left(2) != "OK") {
0202         return -1;
0203     }
0204 
0205     if (result) {
0206         *result = reply.mid(3, reply.length() - 4);
0207     }
0208     return 0;
0209 }
0210 
0211 int KDEsuClient::setPass(const char *pass, int timeout)
0212 {
0213     QByteArray cmd = "PASS ";
0214     cmd += escape(pass);
0215     cmd += ' ';
0216     cmd += QByteArray().setNum(timeout);
0217     cmd += '\n';
0218     return command(cmd);
0219 }
0220 
0221 int KDEsuClient::exec(const QByteArray &prog, const QByteArray &user, const QByteArray &options, const QList<QByteArray> &env)
0222 {
0223     QByteArray cmd;
0224     cmd = "EXEC ";
0225     cmd += escape(prog);
0226     cmd += ' ';
0227     cmd += escape(user);
0228     if (!options.isEmpty() || !env.isEmpty()) {
0229         cmd += ' ';
0230         cmd += escape(options);
0231         for (const auto &var : env) {
0232             cmd += ' ';
0233             cmd += escape(var);
0234         }
0235     }
0236     cmd += '\n';
0237     return command(cmd);
0238 }
0239 
0240 int KDEsuClient::setHost(const QByteArray &host)
0241 {
0242     QByteArray cmd = "HOST ";
0243     cmd += escape(host);
0244     cmd += '\n';
0245     return command(cmd);
0246 }
0247 
0248 int KDEsuClient::setPriority(int prio)
0249 {
0250     QByteArray cmd;
0251     cmd += "PRIO ";
0252     cmd += QByteArray::number(prio);
0253     cmd += '\n';
0254     return command(cmd);
0255 }
0256 
0257 int KDEsuClient::setScheduler(int sched)
0258 {
0259     QByteArray cmd;
0260     cmd += "SCHD ";
0261     cmd += QByteArray::number(sched);
0262     cmd += '\n';
0263     return command(cmd);
0264 }
0265 
0266 int KDEsuClient::delCommand(const QByteArray &key, const QByteArray &user)
0267 {
0268     QByteArray cmd = "DEL ";
0269     cmd += escape(key);
0270     cmd += ' ';
0271     cmd += escape(user);
0272     cmd += '\n';
0273     return command(cmd);
0274 }
0275 int KDEsuClient::setVar(const QByteArray &key, const QByteArray &value, int timeout, const QByteArray &group)
0276 {
0277     QByteArray cmd = "SET ";
0278     cmd += escape(key);
0279     cmd += ' ';
0280     cmd += escape(value);
0281     cmd += ' ';
0282     cmd += escape(group);
0283     cmd += ' ';
0284     cmd += QByteArray().setNum(timeout);
0285     cmd += '\n';
0286     return command(cmd);
0287 }
0288 
0289 QByteArray KDEsuClient::getVar(const QByteArray &key)
0290 {
0291     QByteArray cmd = "GET ";
0292     cmd += escape(key);
0293     cmd += '\n';
0294     QByteArray reply;
0295     command(cmd, &reply);
0296     return reply;
0297 }
0298 
0299 QList<QByteArray> KDEsuClient::getKeys(const QByteArray &group)
0300 {
0301     QByteArray cmd = "GETK ";
0302     cmd += escape(group);
0303     cmd += '\n';
0304     QByteArray reply;
0305     command(cmd, &reply);
0306     int index = 0;
0307     int pos;
0308     QList<QByteArray> list;
0309     if (!reply.isEmpty()) {
0310         while (1) {
0311             pos = reply.indexOf('\007', index);
0312             if (pos == -1) {
0313                 if (index == 0) {
0314                     list.append(reply);
0315                 } else {
0316                     list.append(reply.mid(index));
0317                 }
0318                 break;
0319             } else {
0320                 list.append(reply.mid(index, pos - index));
0321             }
0322             index = pos + 1;
0323         }
0324     }
0325     return list;
0326 }
0327 
0328 bool KDEsuClient::findGroup(const QByteArray &group)
0329 {
0330     QByteArray cmd = "CHKG ";
0331     cmd += escape(group);
0332     cmd += '\n';
0333     if (command(cmd) == -1) {
0334         return false;
0335     }
0336     return true;
0337 }
0338 
0339 int KDEsuClient::delVar(const QByteArray &key)
0340 {
0341     QByteArray cmd = "DELV ";
0342     cmd += escape(key);
0343     cmd += '\n';
0344     return command(cmd);
0345 }
0346 
0347 int KDEsuClient::delGroup(const QByteArray &group)
0348 {
0349     QByteArray cmd = "DELG ";
0350     cmd += escape(group);
0351     cmd += '\n';
0352     return command(cmd);
0353 }
0354 
0355 int KDEsuClient::delVars(const QByteArray &special_key)
0356 {
0357     QByteArray cmd = "DELS ";
0358     cmd += escape(special_key);
0359     cmd += '\n';
0360     return command(cmd);
0361 }
0362 
0363 int KDEsuClient::ping()
0364 {
0365     return command("PING\n");
0366 }
0367 
0368 int KDEsuClient::exitCode()
0369 {
0370     QByteArray result;
0371     if (command("EXIT\n", &result) != 0) {
0372         return -1;
0373     }
0374 
0375     return result.toInt();
0376 }
0377 
0378 int KDEsuClient::stopServer()
0379 {
0380     return command("STOP\n");
0381 }
0382 
0383 static QString findDaemon()
0384 {
0385     QString daemon = QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR_KF "/kdesud");
0386     if (!QFile::exists(daemon)) { // if not in libexec, find it in PATH
0387         daemon = QStandardPaths::findExecutable(QStringLiteral("kdesud"));
0388         if (daemon.isEmpty()) {
0389             qCWarning(KSU_LOG) << "kdesud daemon not found.";
0390         }
0391     }
0392     return daemon;
0393 }
0394 
0395 #if KDESU_BUILD_DEPRECATED_SINCE(5, 99)
0396 bool KDEsuClient::isServerSGID()
0397 {
0398     return true;
0399 }
0400 #endif
0401 
0402 int KDEsuClient::startServer()
0403 {
0404     if (d->daemon.isEmpty()) {
0405         d->daemon = findDaemon();
0406     }
0407     if (d->daemon.isEmpty()) {
0408         return -1;
0409     }
0410 
0411     QProcess proc;
0412     proc.start(d->daemon, QStringList{});
0413     if (!proc.waitForFinished()) {
0414         qCCritical(KSU_LOG) << "Couldn't start kdesud!";
0415         return -1;
0416     }
0417 
0418     connect();
0419     return proc.exitCode();
0420 }
0421 
0422 } // namespace KDESu