diff options
Diffstat (limited to 'kdesu/kdesud/handler.cpp')
-rw-r--r-- | kdesu/kdesud/handler.cpp | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/kdesu/kdesud/handler.cpp b/kdesu/kdesud/handler.cpp new file mode 100644 index 000000000..f18a874da --- /dev/null +++ b/kdesu/kdesud/handler.cpp @@ -0,0 +1,512 @@ +/* + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <[email protected]> + * + * handler.cpp: A connection handler for kdesud. + */ + + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> +#include <string.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/types.h> + +#include <qcstring.h> + +#include <kdebug.h> +#include <kdesu/su.h> +#include <kdesu/ssh.h> + +#include "handler.h" +#include "repo.h" +#include "lexer.h" +#include "secure.h" + + +// Global repository +extern Repository *repo; +void kdesud_cleanup(); + +ConnectionHandler::ConnectionHandler(int fd) + : SocketSecurity(fd), m_exitCode(0), m_hasExitCode(false), m_needExitCode(false), m_pid(0) +{ + m_Fd = fd; + m_Priority = 50; + m_Scheduler = SuProcess::SchedNormal; +} + +ConnectionHandler::~ConnectionHandler() +{ + m_Buf.fill('x'); + m_Pass.fill('x'); + close(m_Fd); +} + +/* + * Handle a connection: make sure we don't block + */ + +int ConnectionHandler::handle() +{ + int ret, nbytes; + + // Add max 100 bytes to connection buffer + + char tmpbuf[100]; + nbytes = recv(m_Fd, tmpbuf, 99, 0); + + if (nbytes < 0) + { + if (errno == EINTR) + return 0; + // read error + return -1; + } else if (nbytes == 0) + { + // eof + return -1; + } + tmpbuf[nbytes] = '\000'; + + if (m_Buf.length()+nbytes > 1024) + { + kdWarning(1205) << "line too long"; + return -1; + } + + m_Buf.append(tmpbuf); + memset(tmpbuf, 'x', nbytes); + + // Do we have a complete command yet? + int n; + QCString newbuf; + while ((n = m_Buf.find('\n')) != -1) + { + newbuf = m_Buf.left(n+1); + m_Buf.fill('x', n+1); + m_Buf.remove(0, n+1); + ret = doCommand(newbuf); + if (ret < 0) + return ret; + } + + return 0; +} + +QCString ConnectionHandler::makeKey(int _namespace, QCString s1, + QCString s2, QCString s3) +{ + QCString res; + res.setNum(_namespace); + res += "*"; + res += s1 + "*" + s2 + "*" + s3; + return res; +} + +void ConnectionHandler::sendExitCode() +{ + if (!m_needExitCode) + return; + QCString buf; + buf.setNum(m_exitCode); + buf.prepend("OK "); + buf.append("\n"); + + send(m_Fd, buf.data(), buf.length(), 0); +} + +void ConnectionHandler::respond(int ok, QCString s) +{ + QCString buf; + + switch (ok) { + case Res_OK: + buf = "OK"; + break; + case Res_NO: + default: + buf = "NO"; + break; + } + + if (!s.isEmpty()) + { + buf += ' '; + buf += s; + } + + buf += '\n'; + + send(m_Fd, buf.data(), buf.length(), 0); +} + +/* + * Parse and do one command. On a parse error, return -1. This will + * close the socket in the main accept loop. + */ + +int ConnectionHandler::doCommand(QCString buf) +{ + if ((uid_t) peerUid() != getuid()) + { + kdWarning(1205) << "Peer uid not equal to me\n"; + kdWarning(1205) << "Peer: " << peerUid() << " Me: " << getuid() << endl; + return -1; + } + + QCString key, command, pass, name, user, value, env_check; + Data_entry data; + + Lexer *l = new Lexer(buf); + int tok = l->lex(); + switch (tok) + { + case Lexer::Tok_pass: // "PASS password:string timeout:int\n" + tok = l->lex(); + if (tok != Lexer::Tok_str) + goto parse_error; + m_Pass.fill('x'); + m_Pass = l->lval(); + tok = l->lex(); + if (tok != Lexer::Tok_num) + goto parse_error; + m_Timeout = l->lval().toInt(); + if (l->lex() != '\n') + goto parse_error; + if (m_Pass.isNull()) + m_Pass = ""; + kdDebug(1205) << "Password set!\n"; + respond(Res_OK); + break; + + case Lexer::Tok_host: // "HOST host:string\n" + tok = l->lex(); + if (tok != Lexer::Tok_str) + goto parse_error; + m_Host = l->lval(); + if (l->lex() != '\n') + goto parse_error; + kdDebug(1205) << "Host set to " << m_Host << endl; + respond(Res_OK); + break; + + case Lexer::Tok_prio: // "PRIO priority:int\n" + tok = l->lex(); + if (tok != Lexer::Tok_num) + goto parse_error; + m_Priority = l->lval().toInt(); + if (l->lex() != '\n') + goto parse_error; + kdDebug(1205) << "priority set to " << m_Priority << endl; + respond(Res_OK); + break; + + case Lexer::Tok_sched: // "SCHD scheduler:int\n" + tok = l->lex(); + if (tok != Lexer::Tok_num) + goto parse_error; + m_Scheduler = l->lval().toInt(); + if (l->lex() != '\n') + goto parse_error; + kdDebug(1205) << "Scheduler set to " << m_Scheduler << endl; + respond(Res_OK); + break; + + case Lexer::Tok_exec: // "EXEC command:string user:string [options:string (env:string)*]\n" + { + QCString options; + QCStringList env; + tok = l->lex(); + if (tok != Lexer::Tok_str) + goto parse_error; + command = l->lval(); + tok = l->lex(); + if (tok != Lexer::Tok_str) + goto parse_error; + user = l->lval(); + tok = l->lex(); + if (tok != '\n') + { + if (tok != Lexer::Tok_str) + goto parse_error; + options = l->lval(); + tok = l->lex(); + while (tok != '\n') + { + if (tok != Lexer::Tok_str) + goto parse_error; + QCString env_str = l->lval(); + env.append(env_str); + if (strncmp(env_str, "DESKTOP_STARTUP_ID=", strlen("DESKTOP_STARTUP_ID=")) != 0) + env_check += "*"+env_str; + tok = l->lex(); + } + } + + QCString auth_user; + if ((m_Scheduler != SuProcess::SchedNormal) || (m_Priority > 50)) + auth_user = "root"; + else + auth_user = user; + key = makeKey(2, m_Host, auth_user, command); + // We only use the command if the environment is the same. + if (repo->find(key) == env_check) + { + key = makeKey(0, m_Host, auth_user, command); + pass = repo->find(key); + } + if (pass.isNull()) // isNull() means no password, isEmpty() can mean empty password + { + if (m_Pass.isNull()) + { + respond(Res_NO); + break; + } + data.value = env_check; + data.timeout = m_Timeout; + key = makeKey(2, m_Host, auth_user, command); + repo->add(key, data); + data.value = m_Pass; + data.timeout = m_Timeout; + key = makeKey(0, m_Host, auth_user, command); + repo->add(key, data); + pass = m_Pass; + } + + // Execute the command asynchronously + kdDebug(1205) << "Executing command: " << command << endl; + pid_t pid = fork(); + if (pid < 0) + { + kdDebug(1205) << "fork(): " << strerror(errno) << endl; + respond(Res_NO); + break; + } else if (pid > 0) + { + m_pid = pid; + respond(Res_OK); + break; + } + + // Ignore SIGCHLD because "class SuProcess" needs waitpid() + signal(SIGCHLD, SIG_DFL); + + int ret; + if (m_Host.isEmpty()) + { + SuProcess proc; + proc.setCommand(command); + proc.setUser(user); + if (options.contains('x')) + proc.setXOnly(true); + if (options.contains('f')) + proc.setDCOPForwarding(true); + proc.setPriority(m_Priority); + proc.setScheduler(m_Scheduler); + proc.setEnvironment(env); + ret = proc.exec(pass.data()); + } else + { + SshProcess proc; + proc.setCommand(command); + proc.setUser(user); + proc.setHost(m_Host); + ret = proc.exec(pass.data()); + } + + kdDebug(1205) << "Command completed: " << command << endl; + _exit(ret); + } + + case Lexer::Tok_delCmd: // "DEL command:string user:string\n" + tok = l->lex(); + if (tok != Lexer::Tok_str) + goto parse_error; + command = l->lval(); + tok = l->lex(); + if (tok != Lexer::Tok_str) + goto parse_error; + user = l->lval(); + if (l->lex() != '\n') + goto parse_error; + key = makeKey(0, m_Host, user, command); + if (repo->remove(key) < 0) { + kdDebug(1205) << "Unknown command: " << command << endl; + respond(Res_NO); + } + else { + kdDebug(1205) << "Deleted command: " << command << ", user = " + << user << endl; + respond(Res_OK); + } + break; + + case Lexer::Tok_delVar: // "DELV name:string \n" + { + tok = l->lex(); + if (tok != Lexer::Tok_str) + goto parse_error; + name = l->lval(); + tok = l->lex(); + if (tok != '\n') + goto parse_error; + key = makeKey(1, name); + if (repo->remove(key) < 0) + { + kdDebug(1205) << "Unknown name: " << name << endl; + respond(Res_NO); + } + else { + kdDebug(1205) << "Deleted name: " << name << endl; + respond(Res_OK); + } + break; + } + + case Lexer::Tok_delGroup: // "DELG group:string\n" + tok = l->lex(); + if (tok != Lexer::Tok_str) + goto parse_error; + name = l->lval(); + if (repo->removeGroup(name) < 0) + { + kdDebug(1205) << "No keys found under group: " << name << endl; + respond(Res_NO); + } + else + { + kdDebug(1205) << "Removed all keys under group: " << name << endl; + respond(Res_OK); + } + break; + + case Lexer::Tok_delSpecialKey: // "DELS special_key:string\n" + tok = l->lex(); + if (tok != Lexer::Tok_str) + goto parse_error; + name = l->lval(); + if (repo->removeSpecialKey(name) < 0) + respond(Res_NO); + else + respond(Res_OK); + break; + + case Lexer::Tok_set: // "SET name:string value:string group:string timeout:int\n" + tok = l->lex(); + if (tok != Lexer::Tok_str) + goto parse_error; + name = l->lval(); + tok = l->lex(); + if (tok != Lexer::Tok_str) + goto parse_error; + data.value = l->lval(); + tok = l->lex(); + if (tok != Lexer::Tok_str) + goto parse_error; + data.group = l->lval(); + tok = l->lex(); + if (tok != Lexer::Tok_num) + goto parse_error; + data.timeout = l->lval().toInt(); + if (l->lex() != '\n') + goto parse_error; + key = makeKey(1, name); + repo->add(key, data); + kdDebug(1205) << "Stored key: " << key << endl; + respond(Res_OK); + break; + + case Lexer::Tok_get: // "GET name:string\n" + tok = l->lex(); + if (tok != Lexer::Tok_str) + goto parse_error; + name = l->lval(); + if (l->lex() != '\n') + goto parse_error; + key = makeKey(1, name); + kdDebug(1205) << "Request for key: " << key << endl; + value = repo->find(key); + if (!value.isEmpty()) + respond(Res_OK, value); + else + respond(Res_NO); + break; + + case Lexer::Tok_getKeys: // "GETK groupname:string\n" + tok = l->lex(); + if (tok != Lexer::Tok_str) + goto parse_error; + name = l->lval(); + if (l->lex() != '\n') + goto parse_error; + kdDebug(1205) << "Request for group key: " << name << endl; + value = repo->findKeys(name); + if (!value.isEmpty()) + respond(Res_OK, value); + else + respond(Res_NO); + break; + + case Lexer::Tok_chkGroup: // "CHKG groupname:string\n" + tok = l->lex(); + if (tok != Lexer::Tok_str) + goto parse_error; + name = l->lval(); + if (l->lex() != '\n') + goto parse_error; + kdDebug(1205) << "Checking for group key: " << name << endl; + if ( repo->hasGroup( name ) < 0 ) + respond(Res_NO); + else + respond(Res_OK); + break; + + case Lexer::Tok_ping: // "PING\n" + tok = l->lex(); + if (tok != '\n') + goto parse_error; + respond(Res_OK); + break; + + case Lexer::Tok_exit: // "EXIT\n" + tok = l->lex(); + if (tok != '\n') + goto parse_error; + m_needExitCode = true; + if (m_hasExitCode) + sendExitCode(); + break; + + case Lexer::Tok_stop: // "STOP\n" + tok = l->lex(); + if (tok != '\n') + goto parse_error; + kdDebug(1205) << "Stopping by command" << endl; + respond(Res_OK); + kdesud_cleanup(); + exit(0); + + default: + kdWarning(1205) << "Unknown command: " << l->lval() << endl; + respond(Res_NO); + goto parse_error; + } + + delete l; + return 0; + +parse_error: + kdWarning(1205) << "Parse error" << endl; + delete l; + return -1; +} + + + |