File indexing completed on 2024-05-12 05:46:28

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