1/*
2 This file is part of the KDE project, module kdesu.
3 SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <jansen@kde.org>
4
5 handler.cpp: A connection handler for kdesud.
6*/
7
8#include "handler.h"
9
10#include <ksud_debug.h>
11
12#include <assert.h>
13#include <cerrno>
14#include <signal.h>
15#include <stdlib.h>
16#include <string.h>
17#include <unistd.h>
18
19#include <sys/socket.h>
20
21#include <sshprocess.h>
22#include <suprocess.h>
23
24#include "lexer.h"
25#include "repo.h"
26
27using namespace KDESu;
28
29#define BUF_SIZE 1024
30
31// Global repository
32extern Repository *repo;
33void kdesud_cleanup();
34
35ConnectionHandler::ConnectionHandler(int fd)
36 : SocketSecurity(fd)
37 , m_exitCode(0)
38 , m_hasExitCode(false)
39 , m_needExitCode(false)
40 , m_pid(0)
41{
42 m_Fd = fd;
43 m_Priority = 50;
44 m_Scheduler = SuProcess::SchedNormal;
45}
46
47ConnectionHandler::~ConnectionHandler()
48{
49 m_Buf.fill(c: 'x');
50 m_Pass.fill(c: 'x');
51 close(fd: m_Fd);
52}
53
54/*
55 * Handle a connection: make sure we don't block
56 */
57
58int ConnectionHandler::handle()
59{
60 int ret;
61 int nbytes;
62
63 m_Buf.reserve(BUF_SIZE);
64 nbytes = recv(fd: m_Fd, buf: m_Buf.data() + m_Buf.size(), BUF_SIZE - 1 - m_Buf.size(), flags: 0);
65
66 if (nbytes < 0) {
67 if (errno == EINTR) {
68 return 0;
69 }
70 // read error
71 return -1;
72 } else if (nbytes == 0) {
73 // eof
74 return -1;
75 }
76
77 m_Buf.resize(size: m_Buf.size() + nbytes);
78 if (m_Buf.size() == BUF_SIZE - 1) {
79 qCWarning(KSUD_LOG) << "line too long";
80 return -1;
81 }
82
83 // Do we have a complete command yet?
84 int n;
85 while ((n = m_Buf.indexOf(c: '\n')) != -1) {
86 n++;
87 QByteArray newbuf = QByteArray(m_Buf.data(), n); // ensure new detached buffer for simplicity
88 int nsize = m_Buf.size() - n;
89 ::memmove(dest: m_Buf.data(), src: m_Buf.data() + n, n: nsize);
90 ::memset(s: m_Buf.data() + nsize, c: 'x', n: n);
91 m_Buf.resize(size: nsize);
92 ret = doCommand(buf: newbuf);
93 if (newbuf.isDetached()) { // otherwise somebody else will clear it
94 newbuf.fill(c: 'x');
95 }
96 if (ret < 0) {
97 return ret;
98 }
99 }
100
101 return 0;
102}
103
104QByteArray ConnectionHandler::makeKey(int _namespace, const QByteArray &s1, const QByteArray &s2, const QByteArray &s3) const
105{
106 QByteArray res;
107 res.setNum(n: _namespace);
108 res += '*';
109 res += s1 + '*' + s2 + '*' + s3;
110 return res;
111}
112
113void ConnectionHandler::sendExitCode()
114{
115 if (!m_needExitCode) {
116 return;
117 }
118 QByteArray buf;
119 buf.setNum(n: m_exitCode);
120 buf.prepend(s: "OK ");
121 buf.append(s: "\n");
122
123 send(fd: m_Fd, buf: buf.data(), n: buf.length(), flags: 0);
124}
125
126void ConnectionHandler::respond(int ok, const QByteArray &s)
127{
128 QByteArray buf;
129
130 switch (ok) {
131 case Res_OK:
132 buf = "OK";
133 break;
134 case Res_NO:
135 default:
136 buf = "NO";
137 break;
138 }
139
140 if (!s.isEmpty()) {
141 buf += ' ';
142 buf += s;
143 }
144
145 buf += '\n';
146
147 send(fd: m_Fd, buf: buf.data(), n: buf.length(), flags: 0);
148}
149
150/*
151 * Parse and do one command. On a parse error, return -1. This will
152 * close the socket in the main accept loop.
153 */
154
155int ConnectionHandler::doCommand(QByteArray buf)
156{
157 if ((uid_t)peerUid() != getuid()) {
158 qCWarning(KSUD_LOG) << "Peer uid not equal to me\n";
159 qCWarning(KSUD_LOG) << "Peer: " << peerUid() << " Me: " << getuid();
160 return -1;
161 }
162
163 QByteArray key;
164 QByteArray command;
165 QByteArray pass;
166 QByteArray name;
167 QByteArray user;
168 QByteArray value;
169 QByteArray env_check;
170 Data_entry data;
171
172 Lexer *l = new Lexer(buf);
173 int tok = l->lex();
174 switch (tok) {
175 case Lexer::Tok_pass: // "PASS password:string timeout:int\n"
176 tok = l->lex();
177 if (tok != Lexer::Tok_str) {
178 goto parse_error;
179 }
180 m_Pass.fill(c: 'x');
181 m_Pass = l->lval();
182 tok = l->lex();
183 if (tok != Lexer::Tok_num) {
184 goto parse_error;
185 }
186 m_Timeout = l->lval().toInt();
187 if (l->lex() != '\n') {
188 goto parse_error;
189 }
190 if (m_Pass.isNull()) {
191 m_Pass = "";
192 }
193 qCDebug(KSUD_LOG) << "Password set!\n";
194 respond(ok: Res_OK);
195 break;
196
197 case Lexer::Tok_host: // "HOST host:string\n"
198 tok = l->lex();
199 if (tok != Lexer::Tok_str) {
200 goto parse_error;
201 }
202 m_Host = l->lval();
203 if (l->lex() != '\n') {
204 goto parse_error;
205 }
206 qCDebug(KSUD_LOG) << "Host set to " << m_Host;
207 respond(ok: Res_OK);
208 break;
209
210 case Lexer::Tok_prio: // "PRIO priority:int\n"
211 tok = l->lex();
212 if (tok != Lexer::Tok_num) {
213 goto parse_error;
214 }
215 m_Priority = l->lval().toInt();
216 if (l->lex() != '\n') {
217 goto parse_error;
218 }
219 qCDebug(KSUD_LOG) << "priority set to " << m_Priority;
220 respond(ok: Res_OK);
221 break;
222
223 case Lexer::Tok_sched: // "SCHD scheduler:int\n"
224 tok = l->lex();
225 if (tok != Lexer::Tok_num) {
226 goto parse_error;
227 }
228 m_Scheduler = l->lval().toInt();
229 if (l->lex() != '\n') {
230 goto parse_error;
231 }
232 qCDebug(KSUD_LOG) << "Scheduler set to " << m_Scheduler;
233 respond(ok: Res_OK);
234 break;
235
236 case Lexer::Tok_exec: // "EXEC command:string user:string [options:string (env:string)*]\n"
237 {
238 QByteArray options;
239 QList<QByteArray> env;
240 tok = l->lex();
241 if (tok != Lexer::Tok_str) {
242 goto parse_error;
243 }
244 command = l->lval();
245 tok = l->lex();
246 if (tok != Lexer::Tok_str) {
247 goto parse_error;
248 }
249 user = l->lval();
250 tok = l->lex();
251 if (tok != '\n') {
252 if (tok != Lexer::Tok_str) {
253 goto parse_error;
254 }
255 options = l->lval();
256 tok = l->lex();
257 while (tok != '\n') {
258 if (tok != Lexer::Tok_str) {
259 goto parse_error;
260 }
261 QByteArray env_str = l->lval();
262 env.append(t: env_str);
263 if (strncmp(s1: env_str.constData(), s2: "DESKTOP_STARTUP_ID=", n: strlen(s: "DESKTOP_STARTUP_ID=")) != 0) {
264 env_check += '*' + env_str;
265 }
266 tok = l->lex();
267 }
268 }
269
270 QByteArray auth_user;
271 if ((m_Scheduler != SuProcess::SchedNormal) || (m_Priority > 50)) {
272 auth_user = "root";
273 } else {
274 auth_user = user;
275 }
276 key = makeKey(namespace: 2, s1: m_Host, s2: auth_user, s3: command);
277 // We only use the command if the environment is the same.
278 if (repo->find(key) == env_check) {
279 key = makeKey(namespace: 0, s1: m_Host, s2: auth_user, s3: command);
280 pass = repo->find(key);
281 }
282 if (pass.isNull()) // isNull() means no password, isEmpty() can mean empty password
283 {
284 if (m_Pass.isNull()) {
285 respond(ok: Res_NO);
286 break;
287 }
288 data.value = env_check;
289 data.timeout = m_Timeout;
290 key = makeKey(namespace: 2, s1: m_Host, s2: auth_user, s3: command);
291 repo->add(key, data);
292 data.value = m_Pass;
293 data.timeout = m_Timeout;
294 key = makeKey(namespace: 0, s1: m_Host, s2: auth_user, s3: command);
295 repo->add(key, data);
296 pass = m_Pass;
297 }
298
299 // Execute the command asynchronously
300 qCDebug(KSUD_LOG) << "Executing command: " << command;
301 pid_t pid = fork();
302 if (pid < 0) {
303 qCDebug(KSUD_LOG) << "fork(): " << strerror(errno);
304 respond(ok: Res_NO);
305 break;
306 } else if (pid > 0) {
307 m_pid = pid;
308 respond(ok: Res_OK);
309 break;
310 }
311
312 // Ignore SIGCHLD because "class SuProcess" needs waitpid()
313 signal(SIGCHLD, SIG_DFL);
314
315 int ret;
316 if (m_Host.isEmpty()) {
317 SuProcess proc;
318 proc.setCommand(command);
319 proc.setUser(user);
320 if (options.contains(c: 'x')) {
321 proc.setXOnly(true);
322 }
323 proc.setPriority(m_Priority);
324 proc.setScheduler(m_Scheduler);
325 proc.setEnvironment(env);
326 ret = proc.exec(password: pass.data());
327 } else {
328 SshProcess proc;
329 proc.setCommand(command);
330 proc.setUser(user);
331 proc.setHost(m_Host);
332 ret = proc.exec(password: pass.data());
333 }
334
335 qCDebug(KSUD_LOG) << "Command completed: " << command;
336 _exit(status: ret);
337 }
338
339 case Lexer::Tok_delCmd: // "DEL command:string user:string\n"
340 tok = l->lex();
341 if (tok != Lexer::Tok_str) {
342 goto parse_error;
343 }
344 command = l->lval();
345 tok = l->lex();
346 if (tok != Lexer::Tok_str) {
347 goto parse_error;
348 }
349 user = l->lval();
350 if (l->lex() != '\n') {
351 goto parse_error;
352 }
353 key = makeKey(namespace: 0, s1: m_Host, s2: user, s3: command);
354 if (repo->remove(key) < 0) {
355 qCDebug(KSUD_LOG) << "Unknown command: " << command;
356 respond(ok: Res_NO);
357 } else {
358 qCDebug(KSUD_LOG) << "Deleted command: " << command << ", user = " << user;
359 respond(ok: Res_OK);
360 }
361 break;
362
363 case Lexer::Tok_delVar: // "DELV name:string \n"
364 {
365 tok = l->lex();
366 if (tok != Lexer::Tok_str) {
367 goto parse_error;
368 }
369 name = l->lval();
370 tok = l->lex();
371 if (tok != '\n') {
372 goto parse_error;
373 }
374 key = makeKey(namespace: 1, s1: name);
375 if (repo->remove(key) < 0) {
376 qCDebug(KSUD_LOG) << "Unknown name: " << name;
377 respond(ok: Res_NO);
378 } else {
379 qCDebug(KSUD_LOG) << "Deleted name: " << name;
380 respond(ok: Res_OK);
381 }
382 break;
383 }
384
385 case Lexer::Tok_delGroup: // "DELG group:string\n"
386 tok = l->lex();
387 if (tok != Lexer::Tok_str) {
388 goto parse_error;
389 }
390 name = l->lval();
391 if (repo->removeGroup(group: name) < 0) {
392 qCDebug(KSUD_LOG) << "No keys found under group: " << name;
393 respond(ok: Res_NO);
394 } else {
395 qCDebug(KSUD_LOG) << "Removed all keys under group: " << name;
396 respond(ok: Res_OK);
397 }
398 break;
399
400 case Lexer::Tok_delSpecialKey: // "DELS special_key:string\n"
401 tok = l->lex();
402 if (tok != Lexer::Tok_str) {
403 goto parse_error;
404 }
405 name = l->lval();
406 if (repo->removeSpecialKey(key: name) < 0) {
407 respond(ok: Res_NO);
408 } else {
409 respond(ok: Res_OK);
410 }
411 break;
412
413 case Lexer::Tok_set: // "SET name:string value:string group:string timeout:int\n"
414 tok = l->lex();
415 if (tok != Lexer::Tok_str) {
416 goto parse_error;
417 }
418 name = l->lval();
419 tok = l->lex();
420 if (tok != Lexer::Tok_str) {
421 goto parse_error;
422 }
423 data.value = l->lval();
424 tok = l->lex();
425 if (tok != Lexer::Tok_str) {
426 goto parse_error;
427 }
428 data.group = l->lval();
429 tok = l->lex();
430 if (tok != Lexer::Tok_num) {
431 goto parse_error;
432 }
433 data.timeout = l->lval().toInt();
434 if (l->lex() != '\n') {
435 goto parse_error;
436 }
437 key = makeKey(namespace: 1, s1: name);
438 repo->add(key, data);
439 qCDebug(KSUD_LOG) << "Stored key: " << key;
440 respond(ok: Res_OK);
441 break;
442
443 case Lexer::Tok_get: // "GET name:string\n"
444 tok = l->lex();
445 if (tok != Lexer::Tok_str) {
446 goto parse_error;
447 }
448 name = l->lval();
449 if (l->lex() != '\n') {
450 goto parse_error;
451 }
452 key = makeKey(namespace: 1, s1: name);
453 qCDebug(KSUD_LOG) << "Request for key: " << key;
454 value = repo->find(key);
455 if (!value.isEmpty()) {
456 respond(ok: Res_OK, s: value);
457 } else {
458 respond(ok: Res_NO);
459 }
460 break;
461
462 case Lexer::Tok_getKeys: // "GETK groupname:string\n"
463 tok = l->lex();
464 if (tok != Lexer::Tok_str) {
465 goto parse_error;
466 }
467 name = l->lval();
468 if (l->lex() != '\n') {
469 goto parse_error;
470 }
471 qCDebug(KSUD_LOG) << "Request for group key: " << name;
472 value = repo->findKeys(group: name);
473 if (!value.isEmpty()) {
474 respond(ok: Res_OK, s: value);
475 } else {
476 respond(ok: Res_NO);
477 }
478 break;
479
480 case Lexer::Tok_chkGroup: // "CHKG groupname:string\n"
481 tok = l->lex();
482 if (tok != Lexer::Tok_str) {
483 goto parse_error;
484 }
485 name = l->lval();
486 if (l->lex() != '\n') {
487 goto parse_error;
488 }
489 qCDebug(KSUD_LOG) << "Checking for group key: " << name;
490 if (repo->hasGroup(group: name) < 0) {
491 respond(ok: Res_NO);
492 } else {
493 respond(ok: Res_OK);
494 }
495 break;
496
497 case Lexer::Tok_ping: // "PING\n"
498 tok = l->lex();
499 if (tok != '\n') {
500 goto parse_error;
501 }
502 respond(ok: Res_OK);
503 break;
504
505 case Lexer::Tok_exit: // "EXIT\n"
506 tok = l->lex();
507 if (tok != '\n') {
508 goto parse_error;
509 }
510 m_needExitCode = true;
511 if (m_hasExitCode) {
512 sendExitCode();
513 }
514 break;
515
516 case Lexer::Tok_stop: // "STOP\n"
517 tok = l->lex();
518 if (tok != '\n') {
519 goto parse_error;
520 }
521 qCDebug(KSUD_LOG) << "Stopping by command";
522 respond(ok: Res_OK);
523 kdesud_cleanup();
524 exit(status: 0);
525
526 default:
527 qCWarning(KSUD_LOG) << "Unknown command: " << l->lval();
528 respond(ok: Res_NO);
529 goto parse_error;
530 }
531
532 delete l;
533 return 0;
534
535parse_error:
536 qCWarning(KSUD_LOG) << "Parse error";
537 delete l;
538 return -1;
539}
540

source code of kdesu/src/kdesud/handler.cpp