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 | |
39 | extern int kdesuDebugArea(); |
40 | |
41 | namespace KDESu { |
42 | |
43 | class KDEsuClient::KDEsuClientPrivate { |
44 | public: |
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 | |
56 | KDEsuClient::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 | |
81 | KDEsuClient::~KDEsuClient() |
82 | { |
83 | if (d->sockfd >= 0) |
84 | close(d->sockfd); |
85 | delete d; |
86 | } |
87 | |
88 | int 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 | |
176 | QByteArray 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 | |
197 | int 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 | |
223 | int 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 | |
233 | int 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 | |
254 | int KDEsuClient::setHost(const QByteArray &host) |
255 | { |
256 | QByteArray cmd = "HOST " ; |
257 | cmd += escape(host); |
258 | cmd += '\n'; |
259 | return command(cmd); |
260 | } |
261 | |
262 | int 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 | |
271 | int 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 | |
280 | int 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 | } |
289 | int 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 | |
304 | QByteArray 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 | |
314 | QList<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 | |
347 | bool 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 | |
357 | int KDEsuClient::delVar(const QByteArray &key) |
358 | { |
359 | QByteArray cmd = "DELV " ; |
360 | cmd += escape(key); |
361 | cmd += '\n'; |
362 | return command(cmd); |
363 | } |
364 | |
365 | int KDEsuClient::delGroup(const QByteArray &group) |
366 | { |
367 | QByteArray cmd = "DELG " ; |
368 | cmd += escape(group); |
369 | cmd += '\n'; |
370 | return command(cmd); |
371 | } |
372 | |
373 | int 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 | |
381 | int KDEsuClient::ping() |
382 | { |
383 | return command("PING\n" ); |
384 | } |
385 | |
386 | int KDEsuClient::exitCode() |
387 | { |
388 | QByteArray result; |
389 | if (command("EXIT\n" , &result) != 0) |
390 | return -1; |
391 | |
392 | return result.toInt(); |
393 | } |
394 | |
395 | int KDEsuClient::stopServer() |
396 | { |
397 | return command("STOP\n" ); |
398 | } |
399 | |
400 | static 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 | |
413 | bool 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 | |
429 | int 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 | |