1/* vi: ts=8 sts=4 sw=4
2 *
3 * This file is part of the KDE project, module kdesu.
4 * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org>
5 *
6 * This is free software; you can use this library under the GNU Library
7 * General Public License, version 2. See the file "COPYING.LIB" for the
8 * exact licensing terms.
9 *
10 * client.cpp: A client for kdesud.
11 */
12
13#include "client.h"
14
15#include <config.h>
16#include <config-kdesu.h>
17
18#include <stdio.h>
19#include <unistd.h>
20#include <stdlib.h>
21#include <pwd.h>
22#include <errno.h>
23#include <string.h>
24
25#include <sys/types.h>
26#include <sys/socket.h>
27#include <sys/un.h>
28#include <sys/stat.h>
29
30#include <QtCore/QBool>
31#include <QtCore/QFile>
32#include <QtCore/QRegExp>
33
34#include <kdebug.h>
35#include <kstandarddirs.h>
36#include <ktoolinvocation.h>
37#include <kde_file.h>
38
39extern int kdesuDebugArea();
40
41namespace KDESu {
42
43class KDEsuClient::KDEsuClientPrivate {
44public:
45 KDEsuClientPrivate() : sockfd(-1) {}
46 QString daemon;
47 int sockfd;
48 QByteArray sock;
49};
50
51#ifndef SUN_LEN
52#define SUN_LEN(ptr) ((socklen_t) (((struct sockaddr_un *) 0)->sun_path) \
53 + strlen ((ptr)->sun_path))
54#endif
55
56KDEsuClient::KDEsuClient()
57 :d(new KDEsuClientPrivate)
58{
59#ifdef Q_WS_X11
60 QString display = QString::fromLatin1(qgetenv("DISPLAY"));
61 if (display.isEmpty())
62 {
63 kWarning(kdesuDebugArea()) << k_lineinfo << "$DISPLAY is not set.";
64 return;
65 }
66
67 // strip the screen number from the display
68 display.remove(QRegExp("\\.[0-9]+$"));
69#elif defined(Q_WS_QWS)
70 QByteArray display("QWS");
71#else
72 QByteArray display("NODISPLAY");
73#endif
74
75 d->sock = QFile::encodeName( KStandardDirs::locateLocal("socket",
76 QString("kdesud_").append(display)));
77 connect();
78}
79
80
81KDEsuClient::~KDEsuClient()
82{
83 if (d->sockfd >= 0)
84 close(d->sockfd);
85 delete d;
86}
87
88int KDEsuClient::connect()
89{
90 if (d->sockfd >= 0)
91 close(d->sockfd);
92 if (access(d->sock, R_OK|W_OK))
93 {
94 d->sockfd = -1;
95 return -1;
96 }
97
98 d->sockfd = socket(PF_UNIX, SOCK_STREAM, 0);
99 if (d->sockfd < 0)
100 {
101 kWarning(kdesuDebugArea()) << k_lineinfo << "socket():" << perror;
102 return -1;
103 }
104 struct sockaddr_un addr;
105 addr.sun_family = AF_UNIX;
106 strcpy(addr.sun_path, d->sock);
107
108 if (::connect(d->sockfd, (struct sockaddr *) &addr, SUN_LEN(&addr)) < 0)
109 {
110 kWarning(kdesuDebugArea()) << k_lineinfo << "connect():" << perror;
111 close(d->sockfd); d->sockfd = -1;
112 return -1;
113 }
114
115#if !defined(SO_PEERCRED) || !defined(HAVE_STRUCT_UCRED)
116# if defined(HAVE_GETPEEREID)
117 uid_t euid;
118 gid_t egid;
119 // Security: if socket exists, we must own it
120 if (getpeereid(d->sockfd, &euid, &egid) == 0)
121 {
122 if (euid != getuid())
123 {
124 kWarning(kdesuDebugArea()) << "socket not owned by me! socket uid =" << euid;
125 close(d->sockfd); d->sockfd = -1;
126 return -1;
127 }
128 }
129# else
130# ifdef __GNUC__
131# warning "Using sloppy security checks"
132# endif
133 // We check the owner of the socket after we have connected.
134 // If the socket was somehow not ours an attacker will be able
135 // to delete it after we connect but shouldn't be able to
136 // create a socket that is owned by us.
137 KDE_struct_stat s;
138 if (KDE_lstat(d->sock, &s)!=0)
139 {
140 kWarning(kdesuDebugArea()) << "stat failed (" << d->sock << ")";
141 close(d->sockfd); d->sockfd = -1;
142 return -1;
143 }
144 if (s.st_uid != getuid())
145 {
146 kWarning(kdesuDebugArea()) << "socket not owned by me! socket uid =" << s.st_uid;
147 close(d->sockfd); d->sockfd = -1;
148 return -1;
149 }
150 if (!S_ISSOCK(s.st_mode))
151 {
152 kWarning(kdesuDebugArea()) << "socket is not a socket (" << d->sock << ")";
153 close(d->sockfd); d->sockfd = -1;
154 return -1;
155 }
156# endif
157#else
158 struct ucred cred;
159 socklen_t siz = sizeof(cred);
160
161 // Security: if socket exists, we must own it
162 if (getsockopt(d->sockfd, SOL_SOCKET, SO_PEERCRED, &cred, &siz) == 0)
163 {
164 if (cred.uid != getuid())
165 {
166 kWarning(kdesuDebugArea()) << "socket not owned by me! socket uid =" << cred.uid;
167 close(d->sockfd); d->sockfd = -1;
168 return -1;
169 }
170 }
171#endif
172
173 return 0;
174}
175
176QByteArray KDEsuClient::escape(const QByteArray &str)
177{
178 QByteArray copy;
179 copy.reserve(str.size() + 4);
180 copy.append('"');
181 for (int i = 0; i < str.size(); i++) {
182 uchar c = str.at(i);
183 if (c < 32) {
184 copy.append('\\');
185 copy.append('^');
186 copy.append(c + '@');
187 } else {
188 if (c == '\\' || c == '"')
189 copy.append('\\');
190 copy.append(c);
191 }
192 }
193 copy.append('"');
194 return copy;
195}
196
197int KDEsuClient::command(const QByteArray &cmd, QByteArray *result)
198{
199 if (d->sockfd < 0)
200 return -1;
201
202 if (send(d->sockfd, cmd, cmd.length(), 0) != (int) cmd.length())
203 return -1;
204
205 char buf[1024];
206 int nbytes = recv(d->sockfd, buf, 1023, 0);
207 if (nbytes <= 0)
208 {
209 kWarning(kdesuDebugArea()) << k_lineinfo << "no reply from daemon.";
210 return -1;
211 }
212 buf[nbytes] = '\000';
213
214 QByteArray reply = buf;
215 if (reply.left(2) != "OK")
216 return -1;
217
218 if (result)
219 *result = reply.mid(3, reply.length()-4);
220 return 0;
221}
222
223int KDEsuClient::setPass(const char *pass, int timeout)
224{
225 QByteArray cmd = "PASS ";
226 cmd += escape(pass);
227 cmd += ' ';
228 cmd += QByteArray().setNum(timeout);
229 cmd += '\n';
230 return command(cmd);
231}
232
233int KDEsuClient::exec(const QByteArray &prog, const QByteArray &user, const QByteArray &options, const QList<QByteArray> &env)
234{
235 QByteArray cmd;
236 cmd = "EXEC ";
237 cmd += escape(prog);
238 cmd += ' ';
239 cmd += escape(user);
240 if (!options.isEmpty() || !env.isEmpty())
241 {
242 cmd += ' ';
243 cmd += escape(options);
244 for (int i = 0; i < env.count(); ++i)
245 {
246 cmd += ' ';
247 cmd += escape(env.at(i));
248 }
249 }
250 cmd += '\n';
251 return command(cmd);
252}
253
254int KDEsuClient::setHost(const QByteArray &host)
255{
256 QByteArray cmd = "HOST ";
257 cmd += escape(host);
258 cmd += '\n';
259 return command(cmd);
260}
261
262int KDEsuClient::setPriority(int prio)
263{
264 QByteArray cmd;
265 cmd += "PRIO ";
266 cmd += QByteArray::number(prio);
267 cmd += '\n';
268 return command(cmd);
269}
270
271int KDEsuClient::setScheduler(int sched)
272{
273 QByteArray cmd;
274 cmd += "SCHD ";
275 cmd += QByteArray::number(sched);
276 cmd += '\n';
277 return command(cmd);
278}
279
280int KDEsuClient::delCommand(const QByteArray &key, const QByteArray &user)
281{
282 QByteArray cmd = "DEL ";
283 cmd += escape(key);
284 cmd += ' ';
285 cmd += escape(user);
286 cmd += '\n';
287 return command(cmd);
288}
289int KDEsuClient::setVar(const QByteArray &key, const QByteArray &value, int timeout,
290 const QByteArray &group)
291{
292 QByteArray cmd = "SET ";
293 cmd += escape(key);
294 cmd += ' ';
295 cmd += escape(value);
296 cmd += ' ';
297 cmd += escape(group);
298 cmd += ' ';
299 cmd += QByteArray().setNum(timeout);
300 cmd += '\n';
301 return command(cmd);
302}
303
304QByteArray KDEsuClient::getVar(const QByteArray &key)
305{
306 QByteArray cmd = "GET ";
307 cmd += escape(key);
308 cmd += '\n';
309 QByteArray reply;
310 command(cmd, &reply);
311 return reply;
312}
313
314QList<QByteArray> KDEsuClient::getKeys(const QByteArray &group)
315{
316 QByteArray cmd = "GETK ";
317 cmd += escape(group);
318 cmd += '\n';
319 QByteArray reply;
320 command(cmd, &reply);
321 int index=0, pos;
322 QList<QByteArray> list;
323 if( !reply.isEmpty() )
324 {
325 // kDebug(kdesuDebugArea()) << "Found a matching entry:" << reply;
326 while (1)
327 {
328 pos = reply.indexOf( '\007', index );
329 if( pos == -1 )
330 {
331 if( index == 0 )
332 list.append( reply );
333 else
334 list.append( reply.mid(index) );
335 break;
336 }
337 else
338 {
339 list.append( reply.mid(index, pos-index) );
340 }
341 index = pos+1;
342 }
343 }
344 return list;
345}
346
347bool KDEsuClient::findGroup(const QByteArray &group)
348{
349 QByteArray cmd = "CHKG ";
350 cmd += escape(group);
351 cmd += '\n';
352 if( command(cmd) == -1 )
353 return false;
354 return true;
355}
356
357int KDEsuClient::delVar(const QByteArray &key)
358{
359 QByteArray cmd = "DELV ";
360 cmd += escape(key);
361 cmd += '\n';
362 return command(cmd);
363}
364
365int KDEsuClient::delGroup(const QByteArray &group)
366{
367 QByteArray cmd = "DELG ";
368 cmd += escape(group);
369 cmd += '\n';
370 return command(cmd);
371}
372
373int KDEsuClient::delVars(const QByteArray &special_key)
374{
375 QByteArray cmd = "DELS ";
376 cmd += escape(special_key);
377 cmd += '\n';
378 return command(cmd);
379}
380
381int KDEsuClient::ping()
382{
383 return command("PING\n");
384}
385
386int KDEsuClient::exitCode()
387{
388 QByteArray result;
389 if (command("EXIT\n", &result) != 0)
390 return -1;
391
392 return result.toInt();
393}
394
395int KDEsuClient::stopServer()
396{
397 return command("STOP\n");
398}
399
400static QString findDaemon()
401{
402 QString daemon = KStandardDirs::locate("bin", "kdesud");
403 if (daemon.isEmpty()) // if not in KDEDIRS, rely on PATH
404 daemon = KStandardDirs::findExe("kdesud");
405
406 if (daemon.isEmpty())
407 {
408 kWarning(kdesuDebugArea()) << k_lineinfo << "daemon not found.";
409 }
410 return daemon;
411}
412
413bool KDEsuClient::isServerSGID()
414{
415 if (d->daemon.isEmpty())
416 d->daemon = findDaemon();
417 if (d->daemon.isEmpty())
418 return false;
419
420 KDE_struct_stat sbuf;
421 if (KDE::stat(d->daemon, &sbuf) < 0)
422 {
423 kWarning(kdesuDebugArea()) << k_lineinfo << "stat():" << perror;
424 return false;
425 }
426 return (sbuf.st_mode & S_ISGID);
427}
428
429int KDEsuClient::startServer()
430{
431 if (d->daemon.isEmpty())
432 d->daemon = findDaemon();
433 if (d->daemon.isEmpty())
434 return -1;
435
436 if (!isServerSGID()) {
437 kWarning(kdesuDebugArea()) << k_lineinfo << "kdesud not setgid!";
438 }
439
440 // kdesud only forks to the background after it is accepting
441 // connections.
442 // We start it via kdeinit to make sure that it doesn't inherit
443 // any fd's from the parent process.
444 int ret = KToolInvocation::kdeinitExecWait(d->daemon);
445 connect();
446 return ret;
447}
448
449}
450