KDESu

client.cpp
1 /*
2  This file is part of the KDE project, module kdesu.
3  SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <[email protected]>
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 
25 extern int kdesuDebugArea();
26 
27 namespace KDESu
28 {
29 class KDEsuClientPrivate
30 {
31 public:
32  KDEsuClientPrivate()
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 
45 KDEsuClient::KDEsuClient()
46  : d(new KDEsuClientPrivate)
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 
70 KDEsuClient::~KDEsuClient()
71 {
72  if (d->sockfd >= 0) {
73  close(d->sockfd);
74  }
75 }
76 
77 int KDEsuClient::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 
160 QByteArray KDEsuClient::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 
181 int KDEsuClient::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 
211 int KDEsuClient::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 
221 int KDEsuClient::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 
241 {
242  QByteArray cmd = "HOST ";
243  cmd += escape(host);
244  cmd += '\n';
245  return command(cmd);
246 }
247 
248 int KDEsuClient::setPriority(int prio)
249 {
250  QByteArray cmd;
251  cmd += "PRIO ";
252  cmd += QByteArray::number(prio);
253  cmd += '\n';
254  return command(cmd);
255 }
256 
257 int KDEsuClient::setScheduler(int sched)
258 {
259  QByteArray cmd;
260  cmd += "SCHD ";
261  cmd += QByteArray::number(sched);
262  cmd += '\n';
263  return command(cmd);
264 }
265 
266 int KDEsuClient::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 }
275 int KDEsuClient::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 
289 QByteArray KDEsuClient::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 
299 QList<QByteArray> KDEsuClient::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 
328 bool KDEsuClient::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 
339 int KDEsuClient::delVar(const QByteArray &key)
340 {
341  QByteArray cmd = "DELV ";
342  cmd += escape(key);
343  cmd += '\n';
344  return command(cmd);
345 }
346 
347 int KDEsuClient::delGroup(const QByteArray &group)
348 {
349  QByteArray cmd = "DELG ";
350  cmd += escape(group);
351  cmd += '\n';
352  return command(cmd);
353 }
354 
355 int KDEsuClient::delVars(const QByteArray &special_key)
356 {
357  QByteArray cmd = "DELS ";
358  cmd += escape(special_key);
359  cmd += '\n';
360  return command(cmd);
361 }
362 
363 int KDEsuClient::ping()
364 {
365  return command("PING\n");
366 }
367 
368 int KDEsuClient::exitCode()
369 {
370  QByteArray result;
371  if (command("EXIT\n", &result) != 0) {
372  return -1;
373  }
374 
375  return result.toInt();
376 }
377 
378 int KDEsuClient::stopServer()
379 {
380  return command("STOP\n");
381 }
382 
383 static 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 
395 bool KDEsuClient::isServerSGID()
396 {
397  if (d->daemon.isEmpty()) {
398  d->daemon = findDaemon();
399  }
400  if (d->daemon.isEmpty()) {
401  return false;
402  }
403 
404  QT_STATBUF sbuf;
405  if (QT_STAT(QFile::encodeName(d->daemon).constData(), &sbuf) < 0) {
406  qCWarning(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
407  << "stat():" << strerror(errno);
408  return false;
409  }
410  return (sbuf.st_mode & S_ISGID);
411 }
412 
413 int KDEsuClient::startServer()
414 {
415  if (d->daemon.isEmpty()) {
416  d->daemon = findDaemon();
417  }
418  if (d->daemon.isEmpty()) {
419  return -1;
420  }
421 
422  if (!isServerSGID()) {
423  qCWarning(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
424  << "kdesud not setgid!";
425  }
426 
427  QProcess proc;
428  proc.start(d->daemon, QStringList{});
429  if (!proc.waitForFinished()) {
430  qCCritical(KSU_LOG) << "Couldn't start kdesud!";
431  return -1;
432  }
433 
434  connect();
435  return proc.exitCode();
436 }
437 
438 } // namespace KDESu
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
void append(const T &value)
QString escape(const QString &plain)
bool waitForFinished(int msecs)
int indexOf(char ch, int from) const const
QByteArray encodeName(const QString &fileName)
QByteArray number(int n, int base)
QByteArray & setNum(short n, int base)
QString writableLocation(QStandardPaths::StandardLocation type)
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
void setHost(ScriptableExtension *host)
bool exists() const const
QString findExecutable(const QString &executableName, const QStringList &paths)
QAction * copy(const QObject *recvr, const char *slot, QObject *parent)
QString fromLocal8Bit(const char *str, int size)
QByteArray mid(int pos, int len) const const
bool isEmpty() const const
QAction * close(const QObject *recvr, const char *slot, QObject *parent)
bool isEmpty() const const
QString & remove(int position, int n)
ScriptableExtension * host() const
int toInt(bool *ok, int base) const const
QByteArray left(int len) const const
bool isEmpty() const const
const char * constData() const const
int size() const const
int length() const const
int exitCode() const const
int access(const QString &path, int mode)
QString decodeName(const QByteArray &localFileName)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sun Jun 26 2022 03:48:58 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.