KDESu

client.cpp
1/*
2 This file is part of the KDE project, module kdesu.
3 SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <jansen@kde.org>
4
5 SPDX-License-Identifier: GPL-2.0-only
6
7 client.cpp: A client for kdesud.
8*/
9
10#include "client.h"
11
12#include <config-kdesu.h>
13#include <ksu_debug.h>
14
15#include <cerrno>
16#include <sys/socket.h>
17#include <sys/un.h>
18
19#include <QFile>
20#include <QProcess>
21#include <QRegularExpression>
22#include <QStandardPaths>
23#include <qplatformdefs.h>
24
25extern int kdesuDebugArea();
26
27namespace KDESu
28{
29class ClientPrivate
30{
31public:
32 ClientPrivate()
33 : sockfd(-1)
34 {
35 }
36 QString daemon;
37 int sockfd;
38 QByteArray sock;
39};
40
41#ifndef SUN_LEN
42#define SUN_LEN(ptr) ((QT_SOCKLEN_T)(((struct sockaddr_un *)0)->sun_path) + strlen((ptr)->sun_path))
43#endif
44
45Client::Client()
46 : d(new ClientPrivate)
47{
48#if HAVE_X11
49 QString display = QString::fromLocal8Bit(qgetenv("DISPLAY"));
50 if (display.isEmpty()) {
51 // we might be on Wayland
52 display = QString::fromLocal8Bit(qgetenv("WAYLAND_DISPLAY"));
53 }
54 if (display.isEmpty()) {
55 qCWarning(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
56 << "$DISPLAY is not set.";
57 return;
58 }
59
60 // strip the screen number from the display
61 display.remove(QRegularExpression(QStringLiteral("\\.[0-9]+$")));
62#else
63 QString display = QStringLiteral("NODISPLAY");
64#endif
65
66 d->sock = QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QStringLiteral("/kdesud_") + display);
67 connect();
68}
69
70Client::~Client()
71{
72 if (d->sockfd >= 0) {
73 close(d->sockfd);
74 }
75}
76
77int Client::connect()
78{
79 if (d->sockfd >= 0) {
80 close(d->sockfd);
81 }
82 if (access(d->sock.constData(), R_OK | W_OK)) {
83 d->sockfd = -1;
84 return -1;
85 }
86
87 d->sockfd = socket(PF_UNIX, SOCK_STREAM, 0);
88 if (d->sockfd < 0) {
89 qCWarning(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
90 << "socket():" << strerror(errno);
91 return -1;
92 }
93 struct sockaddr_un addr;
94 addr.sun_family = AF_UNIX;
95 strcpy(addr.sun_path, d->sock.constData());
96
97 if (QT_SOCKET_CONNECT(d->sockfd, (struct sockaddr *)&addr, SUN_LEN(&addr)) < 0) {
98 qCWarning(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
99 << "connect():" << strerror(errno);
100 close(d->sockfd);
101 d->sockfd = -1;
102 return -1;
103 }
104
105#if !defined(SO_PEERCRED) || !HAVE_STRUCT_UCRED
106#if HAVE_GETPEEREID
107 uid_t euid;
108 gid_t egid;
109 // Security: if socket exists, we must own it
110 if (getpeereid(d->sockfd, &euid, &egid) == 0 && euid != getuid()) {
111 qCWarning(KSU_LOG) << "socket not owned by me! socket uid =" << euid;
112 close(d->sockfd);
113 d->sockfd = -1;
114 return -1;
115 }
116#else
117#ifdef __GNUC__
118#warning "Using sloppy security checks"
119#endif
120 // We check the owner of the socket after we have connected.
121 // If the socket was somehow not ours an attacker will be able
122 // to delete it after we connect but shouldn't be able to
123 // create a socket that is owned by us.
124 QT_STATBUF s;
125 if (QT_LSTAT(d->sock.constData(), &s) != 0) {
126 qCWarning(KSU_LOG) << "stat failed (" << d->sock << ")";
127 close(d->sockfd);
128 d->sockfd = -1;
129 return -1;
130 }
131 if (s.st_uid != getuid()) {
132 qCWarning(KSU_LOG) << "socket not owned by me! socket uid =" << s.st_uid;
133 close(d->sockfd);
134 d->sockfd = -1;
135 return -1;
136 }
137 if (!S_ISSOCK(s.st_mode)) {
138 qCWarning(KSU_LOG) << "socket is not a socket (" << d->sock << ")";
139 close(d->sockfd);
140 d->sockfd = -1;
141 return -1;
142 }
143#endif
144#else
145 struct ucred cred;
146 QT_SOCKLEN_T siz = sizeof(cred);
147
148 // Security: if socket exists, we must own it
149 if (getsockopt(d->sockfd, SOL_SOCKET, SO_PEERCRED, &cred, &siz) == 0 && cred.uid != getuid()) {
150 qCWarning(KSU_LOG) << "socket not owned by me! socket uid =" << cred.uid;
151 close(d->sockfd);
152 d->sockfd = -1;
153 return -1;
154 }
155#endif
156
157 return 0;
158}
159
160QByteArray Client::escape(const QByteArray &str)
161{
163 copy.reserve(str.size() + 4);
164 copy.append('"');
165 for (const uchar c : str) {
166 if (c < 32) {
167 copy.append('\\');
168 copy.append('^');
169 copy.append(c + '@');
170 } else {
171 if (c == '\\' || c == '"') {
172 copy.append('\\');
173 }
174 copy.append(c);
175 }
176 }
177 copy.append('"');
178 return copy;
179}
180
181int Client::command(const QByteArray &cmd, QByteArray *result)
182{
183 if (d->sockfd < 0) {
184 return -1;
185 }
186
187 if (send(d->sockfd, cmd.constData(), cmd.length(), 0) != (int)cmd.length()) {
188 return -1;
189 }
190
191 char buf[1024];
192 int nbytes = recv(d->sockfd, buf, 1023, 0);
193 if (nbytes <= 0) {
194 qCWarning(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
195 << "no reply from daemon.";
196 return -1;
197 }
198 buf[nbytes] = '\000';
199
200 QByteArray reply = buf;
201 if (reply.left(2) != "OK") {
202 return -1;
203 }
204
205 if (result) {
206 *result = reply.mid(3, reply.length() - 4);
207 }
208 return 0;
209}
210
211int Client::setPass(const char *pass, int timeout)
212{
213 QByteArray cmd = "PASS ";
214 cmd += escape(pass);
215 cmd += ' ';
216 cmd += QByteArray().setNum(timeout);
217 cmd += '\n';
218 return command(cmd);
219}
220
221int Client::exec(const QByteArray &prog, const QByteArray &user, const QByteArray &options, const QList<QByteArray> &env)
222{
223 QByteArray cmd;
224 cmd = "EXEC ";
225 cmd += escape(prog);
226 cmd += ' ';
227 cmd += escape(user);
228 if (!options.isEmpty() || !env.isEmpty()) {
229 cmd += ' ';
230 cmd += escape(options);
231 for (const auto &var : env) {
232 cmd += ' ';
233 cmd += escape(var);
234 }
235 }
236 cmd += '\n';
237 return command(cmd);
238}
239
240int Client::setHost(const QByteArray &host)
241{
242 QByteArray cmd = "HOST ";
243 cmd += escape(host);
244 cmd += '\n';
245 return command(cmd);
246}
247
248int Client::setPriority(int prio)
249{
250 QByteArray cmd;
251 cmd += "PRIO ";
252 cmd += QByteArray::number(prio);
253 cmd += '\n';
254 return command(cmd);
255}
256
257int Client::setScheduler(int sched)
258{
259 QByteArray cmd;
260 cmd += "SCHD ";
261 cmd += QByteArray::number(sched);
262 cmd += '\n';
263 return command(cmd);
264}
265
266int Client::delCommand(const QByteArray &key, const QByteArray &user)
267{
268 QByteArray cmd = "DEL ";
269 cmd += escape(key);
270 cmd += ' ';
271 cmd += escape(user);
272 cmd += '\n';
273 return command(cmd);
274}
275int Client::setVar(const QByteArray &key, const QByteArray &value, int timeout, const QByteArray &group)
276{
277 QByteArray cmd = "SET ";
278 cmd += escape(key);
279 cmd += ' ';
280 cmd += escape(value);
281 cmd += ' ';
282 cmd += escape(group);
283 cmd += ' ';
284 cmd += QByteArray().setNum(timeout);
285 cmd += '\n';
286 return command(cmd);
287}
288
289QByteArray Client::getVar(const QByteArray &key)
290{
291 QByteArray cmd = "GET ";
292 cmd += escape(key);
293 cmd += '\n';
294 QByteArray reply;
295 command(cmd, &reply);
296 return reply;
297}
298
299QList<QByteArray> Client::getKeys(const QByteArray &group)
300{
301 QByteArray cmd = "GETK ";
302 cmd += escape(group);
303 cmd += '\n';
304 QByteArray reply;
305 command(cmd, &reply);
306 int index = 0;
307 int pos;
309 if (!reply.isEmpty()) {
310 while (1) {
311 pos = reply.indexOf('\007', index);
312 if (pos == -1) {
313 if (index == 0) {
314 list.append(reply);
315 } else {
316 list.append(reply.mid(index));
317 }
318 break;
319 } else {
320 list.append(reply.mid(index, pos - index));
321 }
322 index = pos + 1;
323 }
324 }
325 return list;
326}
327
328bool Client::findGroup(const QByteArray &group)
329{
330 QByteArray cmd = "CHKG ";
331 cmd += escape(group);
332 cmd += '\n';
333 if (command(cmd) == -1) {
334 return false;
335 }
336 return true;
337}
338
339int Client::delVar(const QByteArray &key)
340{
341 QByteArray cmd = "DELV ";
342 cmd += escape(key);
343 cmd += '\n';
344 return command(cmd);
345}
346
347int Client::delGroup(const QByteArray &group)
348{
349 QByteArray cmd = "DELG ";
350 cmd += escape(group);
351 cmd += '\n';
352 return command(cmd);
353}
354
355int Client::delVars(const QByteArray &special_key)
356{
357 QByteArray cmd = "DELS ";
358 cmd += escape(special_key);
359 cmd += '\n';
360 return command(cmd);
361}
362
363int Client::ping()
364{
365 return command("PING\n");
366}
367
368int Client::exitCode()
369{
370 QByteArray result;
371 if (command("EXIT\n", &result) != 0) {
372 return -1;
373 }
374
375 return result.toInt();
376}
377
378int Client::stopServer()
379{
380 return command("STOP\n");
381}
382
383static QString findDaemon()
384{
385 QString daemon = QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR_KF "/kdesud");
386 if (!QFile::exists(daemon)) { // if not in libexec, find it in PATH
387 daemon = QStandardPaths::findExecutable(QStringLiteral("kdesud"));
388 if (daemon.isEmpty()) {
389 qCWarning(KSU_LOG) << "kdesud daemon not found.";
390 }
391 }
392 return daemon;
393}
394
395int Client::startServer()
396{
397 if (d->daemon.isEmpty()) {
398 d->daemon = findDaemon();
399 }
400 if (d->daemon.isEmpty()) {
401 return -1;
402 }
403
404 QProcess proc;
405 proc.start(d->daemon, QStringList{});
406 if (!proc.waitForFinished()) {
407 qCCritical(KSU_LOG) << "Couldn't start kdesud!";
408 return -1;
409 }
410
411 connect();
412 return proc.exitCode();
413}
414
415} // namespace KDESu
KIOCORE_EXPORT QStringList list(const QString &fileClass)
const QList< QKeySequence > & close()
const QList< QKeySequence > & copy()
const char * constData() const const
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
QByteArray left(qsizetype len) const const
qsizetype length() const const
QByteArray mid(qsizetype pos, qsizetype len) const const
QByteArray number(double n, char format, int precision)
QByteArray & setNum(double n, char format, int precision)
qsizetype size() const const
int toInt(bool *ok, int base) const const
QString decodeName(const QByteArray &localFileName)
QByteArray encodeName(const QString &fileName)
bool exists() const const
void append(QList< T > &&value)
bool isEmpty() const const
int exitCode() const const
void start(OpenMode mode)
bool waitForFinished(int msecs)
QString findExecutable(const QString &executableName, const QStringList &paths)
QString writableLocation(StandardLocation type)
QString fromLocal8Bit(QByteArrayView str)
bool isEmpty() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:57:38 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.