/* This file is part of the KDE project, module kdesu. SPDX-FileCopyrightText: 1999, 2000 Geert Jansen handler.cpp: A connection handler for kdesud. */ #include "handler.h" #include #include #include #include #include #include #include #include #include #include #include "lexer.h" #include "repo.h" using namespace KDESu; #define BUF_SIZE 1024 // 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; int nbytes; m_Buf.reserve(BUF_SIZE); nbytes = recv(m_Fd, m_Buf.data() + m_Buf.size(), BUF_SIZE - 1 - m_Buf.size(), 0); if (nbytes < 0) { if (errno == EINTR) { return 0; } // read error return -1; } else if (nbytes == 0) { // eof return -1; } m_Buf.resize(m_Buf.size() + nbytes); if (m_Buf.size() == BUF_SIZE - 1) { qCWarning(KSUD_LOG) << "line too long"; return -1; } // Do we have a complete command yet? int n; while ((n = m_Buf.indexOf('\n')) != -1) { n++; QByteArray newbuf = QByteArray(m_Buf.data(), n); // ensure new detached buffer for simplicity int nsize = m_Buf.size() - n; ::memmove(m_Buf.data(), m_Buf.data() + n, nsize); ::memset(m_Buf.data() + nsize, 'x', n); m_Buf.resize(nsize); ret = doCommand(newbuf); if (newbuf.isDetached()) { // otherwise somebody else will clear it newbuf.fill('x'); } if (ret < 0) { return ret; } } return 0; } QByteArray ConnectionHandler::makeKey(int _namespace, const QByteArray &s1, const QByteArray &s2, const QByteArray &s3) const { QByteArray res; res.setNum(_namespace); res += '*'; res += s1 + '*' + s2 + '*' + s3; return res; } void ConnectionHandler::sendExitCode() { if (!m_needExitCode) { return; } QByteArray buf; buf.setNum(m_exitCode); buf.prepend("OK "); buf.append("\n"); send(m_Fd, buf.data(), buf.length(), 0); } void ConnectionHandler::respond(int ok, const QByteArray &s) { QByteArray 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(QByteArray buf) { if ((uid_t)peerUid() != getuid()) { qCWarning(KSUD_LOG) << "Peer uid not equal to me\n"; qCWarning(KSUD_LOG) << "Peer: " << peerUid() << " Me: " << getuid(); return -1; } QByteArray key; QByteArray command; QByteArray pass; QByteArray name; QByteArray user; QByteArray value; QByteArray 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 = ""; } qCDebug(KSUD_LOG) << "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; } qCDebug(KSUD_LOG) << "Host set to " << m_Host; 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; } qCDebug(KSUD_LOG) << "priority set to " << m_Priority; 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; } qCDebug(KSUD_LOG) << "Scheduler set to " << m_Scheduler; respond(Res_OK); break; case Lexer::Tok_exec: // "EXEC command:string user:string [options:string (env:string)*]\n" { QByteArray options; QList 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; } QByteArray env_str = l->lval(); env.append(env_str); if (strncmp(env_str.constData(), "DESKTOP_STARTUP_ID=", strlen("DESKTOP_STARTUP_ID=")) != 0) { env_check += '*' + env_str; } tok = l->lex(); } } QByteArray 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 qCDebug(KSUD_LOG) << "Executing command: " << command; pid_t pid = fork(); if (pid < 0) { qCDebug(KSUD_LOG) << "fork(): " << strerror(errno); 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); } 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()); } qCDebug(KSUD_LOG) << "Command completed: " << command; _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) { qCDebug(KSUD_LOG) << "Unknown command: " << command; respond(Res_NO); } else { qCDebug(KSUD_LOG) << "Deleted command: " << command << ", user = " << user; 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) { qCDebug(KSUD_LOG) << "Unknown name: " << name; respond(Res_NO); } else { qCDebug(KSUD_LOG) << "Deleted name: " << name; 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) { qCDebug(KSUD_LOG) << "No keys found under group: " << name; respond(Res_NO); } else { qCDebug(KSUD_LOG) << "Removed all keys under group: " << name; 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); qCDebug(KSUD_LOG) << "Stored key: " << key; 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); qCDebug(KSUD_LOG) << "Request for key: " << key; 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; } qCDebug(KSUD_LOG) << "Request for group key: " << name; 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; } qCDebug(KSUD_LOG) << "Checking for group key: " << name; 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; } qCDebug(KSUD_LOG) << "Stopping by command"; respond(Res_OK); kdesud_cleanup(); exit(0); default: qCWarning(KSUD_LOG) << "Unknown command: " << l->lval(); respond(Res_NO); goto parse_error; } delete l; return 0; parse_error: qCWarning(KSUD_LOG) << "Parse error"; delete l; return -1; }