File indexing completed on 2025-02-02 14:11:33
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