KDESu

handler.cpp
1 /*
2  This file is part of the KDE project, module kdesu.
3  SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <[email protected]>
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 
27 using namespace KDESu;
28 
29 #define BUF_SIZE 1024
30 
31 // Global repository
32 extern Repository *repo;
33 void kdesud_cleanup();
34 
35 ConnectionHandler::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 
47 ConnectionHandler::~ConnectionHandler()
48 {
49  m_Buf.fill('x');
50  m_Pass.fill('x');
51  close(m_Fd);
52 }
53 
54 /*
55  * Handle a connection: make sure we don't block
56  */
57 
59 {
60  int ret;
61  int nbytes;
62 
63  m_Buf.reserve(BUF_SIZE);
64  nbytes = recv(m_Fd, m_Buf.data() + m_Buf.size(), BUF_SIZE - 1 - m_Buf.size(), 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(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('\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(m_Buf.data(), m_Buf.data() + n, nsize);
90  ::memset(m_Buf.data() + nsize, 'x', n);
91  m_Buf.resize(nsize);
92  ret = doCommand(newbuf);
93  if (newbuf.isDetached()) { // otherwise somebody else will clear it
94  newbuf.fill('x');
95  }
96  if (ret < 0) {
97  return ret;
98  }
99  }
100 
101  return 0;
102 }
103 
104 QByteArray ConnectionHandler::makeKey(int _namespace, const QByteArray &s1, const QByteArray &s2, const QByteArray &s3) const
105 {
106  QByteArray res;
107  res.setNum(_namespace);
108  res += '*';
109  res += s1 + '*' + s2 + '*' + s3;
110  return res;
111 }
112 
113 void ConnectionHandler::sendExitCode()
114 {
115  if (!m_needExitCode) {
116  return;
117  }
118  QByteArray buf;
119  buf.setNum(m_exitCode);
120  buf.prepend("OK ");
121  buf.append("\n");
122 
123  send(m_Fd, buf.data(), buf.length(), 0);
124 }
125 
126 void 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(m_Fd, buf.data(), buf.length(), 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 
155 int 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;
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('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(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(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(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(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(env_str);
263  if (strncmp(env_str.constData(), "DESKTOP_STARTUP_ID=", strlen("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(2, m_Host, auth_user, command);
277  // We only use the command if the environment is the same.
278  if (repo->find(key) == env_check) {
279  key = makeKey(0, m_Host, auth_user, 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(Res_NO);
286  break;
287  }
288  data.value = env_check;
289  data.timeout = m_Timeout;
290  key = makeKey(2, m_Host, auth_user, command);
291  repo->add(key, data);
292  data.value = m_Pass;
293  data.timeout = m_Timeout;
294  key = makeKey(0, m_Host, auth_user, 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(Res_NO);
305  break;
306  } else if (pid > 0) {
307  m_pid = pid;
308  respond(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('x')) {
321  proc.setXOnly(true);
322  }
323  proc.setPriority(m_Priority);
324  proc.setScheduler(m_Scheduler);
325  proc.setEnvironment(env);
326  ret = proc.exec(pass.data());
327  } else {
328  SshProcess proc;
329  proc.setCommand(command);
330  proc.setUser(user);
331  proc.setHost(m_Host);
332  ret = proc.exec(pass.data());
333  }
334 
335  qCDebug(KSUD_LOG) << "Command completed: " << command;
336  _exit(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(0, m_Host, user, command);
354  if (repo->remove(key) < 0) {
355  qCDebug(KSUD_LOG) << "Unknown command: " << command;
356  respond(Res_NO);
357  } else {
358  qCDebug(KSUD_LOG) << "Deleted command: " << command << ", user = " << user;
359  respond(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(1, name);
375  if (repo->remove(key) < 0) {
376  qCDebug(KSUD_LOG) << "Unknown name: " << name;
377  respond(Res_NO);
378  } else {
379  qCDebug(KSUD_LOG) << "Deleted name: " << name;
380  respond(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(name) < 0) {
392  qCDebug(KSUD_LOG) << "No keys found under group: " << name;
393  respond(Res_NO);
394  } else {
395  qCDebug(KSUD_LOG) << "Removed all keys under group: " << name;
396  respond(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(name) < 0) {
407  respond(Res_NO);
408  } else {
409  respond(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(1, name);
438  repo->add(key, data);
439  qCDebug(KSUD_LOG) << "Stored key: " << key;
440  respond(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(1, name);
453  qCDebug(KSUD_LOG) << "Request for key: " << key;
454  value = repo->find(key);
455  if (!value.isEmpty()) {
456  respond(Res_OK, value);
457  } else {
458  respond(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(name);
473  if (!value.isEmpty()) {
474  respond(Res_OK, value);
475  } else {
476  respond(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(name) < 0) {
491  respond(Res_NO);
492  } else {
493  respond(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(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(Res_OK);
523  kdesud_cleanup();
524  exit(0);
525 
526  default:
527  qCWarning(KSUD_LOG) << "Unknown command: " << l->lval();
528  respond(Res_NO);
529  goto parse_error;
530  }
531 
532  delete l;
533  return 0;
534 
535 parse_error:
536  qCWarning(KSUD_LOG) << "Parse error";
537  delete l;
538  return -1;
539 }
void append(const T &value)
int hasGroup(const QByteArray &group) const
Checks for the existence of the specified group.
Definition: repo.cpp:94
bool isNull() const const
int indexOf(char ch, int from) const const
QByteArray & append(char ch)
void setEnvironment(const QList< QByteArray > &env)
Set additinal environment variables.
Definition: ptyprocess.cpp:143
QByteArray & lval()
Return the token's value.
Definition: lexer.cpp:27
QByteArray & setNum(short n, int base)
void add(const QByteArray &key, Data_entry &data)
Add a data element.
Definition: repo.cpp:25
const QList< QKeySequence > & close()
QByteArray & prepend(char ch)
void setHost(const QByteArray &host)
Sets the target host.
Definition: sshprocess.cpp:50
void setPriority(int prio)
Set the priority of the process.
Definition: stubprocess.cpp:60
void setScheduler(int sched)
Set the scheduler type.
Definition: stubprocess.cpp:71
This is a lexer for the kdesud protocol.
Definition: lexer.h:16
void setXOnly(bool xonly)
Set to "X only mode": Sycoca is not built and kdeinit is not launched.
Definition: stubprocess.cpp:55
void setCommand(const QByteArray &command)
Set the command.
Definition: stubprocess.cpp:45
bool contains(char ch) const const
QByteArray & fill(char ch, int size)
int exec(const char *password, int check=0)
Executes the command.
Definition: sshprocess.cpp:74
The Socket_security class authenticates the peer for you.
Definition: secure.h:19
String repository.
Definition: repo.h:27
int lex()
Read next token.
Definition: lexer.cpp:37
int toInt(bool *ok, int base) const const
Used internally.
Definition: repo.h:16
int handle()
Handle incoming data.
Definition: handler.cpp:58
bool isEmpty() const const
void resize(int size)
int remove(const QByteArray &key)
Delete a data element.
Definition: repo.cpp:40
const char * constData() const const
QByteArray find(const QByteArray &key) const
Return a data value.
Definition: repo.cpp:138
QString name(StandardShortcut id)
QByteArray findKeys(const QByteArray &group, const char *sep="-") const
Returns the key values for the given group.
Definition: repo.cpp:107
void reserve(int size)
int peerUid() const
Returns the peer's user-id.
Definition: secure.h:31
int removeSpecialKey(const QByteArray &key)
Delete all data entries based on key.
Definition: repo.cpp:56
int size() const const
int removeGroup(const QByteArray &group)
Delete all data entries having the given group.
Definition: repo.cpp:75
int length() const const
char * data()
void setUser(const QByteArray &user)
Set the target user.
Definition: stubprocess.cpp:50
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon May 8 2023 04:03:16 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.