File indexing completed on 2024-05-05 03:54:29

0001 /*
0002     This file is part of the KDE project, module kdesu.
0003     SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <jansen@kde.org>
0004 
0005     handler.cpp: A connection handler for kdesud.
0006 */
0007 
0008 #include "handler.h"
0009 
0010 #include <ksud_debug.h>
0011 
0012 #include <assert.h>
0013 #include <cerrno>
0014 #include <signal.h>
0015 #include <stdlib.h>
0016 #include <string.h>
0017 #include <unistd.h>
0018 
0019 #include <sys/socket.h>
0020 
0021 #include <sshprocess.h>
0022 #include <suprocess.h>
0023 
0024 #include "lexer.h"
0025 #include "repo.h"
0026 
0027 using namespace KDESu;
0028 
0029 #define BUF_SIZE 1024
0030 
0031 // Global repository
0032 extern Repository *repo;
0033 void kdesud_cleanup();
0034 
0035 ConnectionHandler::ConnectionHandler(int fd)
0036     : SocketSecurity(fd)
0037     , m_exitCode(0)
0038     , m_hasExitCode(false)
0039     , m_needExitCode(false)
0040     , m_pid(0)
0041 {
0042     m_Fd = fd;
0043     m_Priority = 50;
0044     m_Scheduler = SuProcess::SchedNormal;
0045 }
0046 
0047 ConnectionHandler::~ConnectionHandler()
0048 {
0049     m_Buf.fill('x');
0050     m_Pass.fill('x');
0051     close(m_Fd);
0052 }
0053 
0054 /*
0055  * Handle a connection: make sure we don't block
0056  */
0057 
0058 int ConnectionHandler::handle()
0059 {
0060     int ret;
0061     int nbytes;
0062 
0063     m_Buf.reserve(BUF_SIZE);
0064     nbytes = recv(m_Fd, m_Buf.data() + m_Buf.size(), BUF_SIZE - 1 - m_Buf.size(), 0);
0065 
0066     if (nbytes < 0) {
0067         if (errno == EINTR) {
0068             return 0;
0069         }
0070         // read error
0071         return -1;
0072     } else if (nbytes == 0) {
0073         // eof
0074         return -1;
0075     }
0076 
0077     m_Buf.resize(m_Buf.size() + nbytes);
0078     if (m_Buf.size() == BUF_SIZE - 1) {
0079         qCWarning(KSUD_LOG) << "line too long";
0080         return -1;
0081     }
0082 
0083     // Do we have a complete command yet?
0084     int n;
0085     while ((n = m_Buf.indexOf('\n')) != -1) {
0086         n++;
0087         QByteArray newbuf = QByteArray(m_Buf.data(), n); // ensure new detached buffer for simplicity
0088         int nsize = m_Buf.size() - n;
0089         ::memmove(m_Buf.data(), m_Buf.data() + n, nsize);
0090         ::memset(m_Buf.data() + nsize, 'x', n);
0091         m_Buf.resize(nsize);
0092         ret = doCommand(newbuf);
0093         if (newbuf.isDetached()) { // otherwise somebody else will clear it
0094             newbuf.fill('x');
0095         }
0096         if (ret < 0) {
0097             return ret;
0098         }
0099     }
0100 
0101     return 0;
0102 }
0103 
0104 QByteArray ConnectionHandler::makeKey(int _namespace, const QByteArray &s1, const QByteArray &s2, const QByteArray &s3) const
0105 {
0106     QByteArray res;
0107     res.setNum(_namespace);
0108     res += '*';
0109     res += s1 + '*' + s2 + '*' + s3;
0110     return res;
0111 }
0112 
0113 void ConnectionHandler::sendExitCode()
0114 {
0115     if (!m_needExitCode) {
0116         return;
0117     }
0118     QByteArray buf;
0119     buf.setNum(m_exitCode);
0120     buf.prepend("OK ");
0121     buf.append("\n");
0122 
0123     send(m_Fd, buf.data(), buf.length(), 0);
0124 }
0125 
0126 void ConnectionHandler::respond(int ok, const QByteArray &s)
0127 {
0128     QByteArray buf;
0129 
0130     switch (ok) {
0131     case Res_OK:
0132         buf = "OK";
0133         break;
0134     case Res_NO:
0135     default:
0136         buf = "NO";
0137         break;
0138     }
0139 
0140     if (!s.isEmpty()) {
0141         buf += ' ';
0142         buf += s;
0143     }
0144 
0145     buf += '\n';
0146 
0147     send(m_Fd, buf.data(), buf.length(), 0);
0148 }
0149 
0150 /*
0151  * Parse and do one command. On a parse error, return -1. This will
0152  * close the socket in the main accept loop.
0153  */
0154 
0155 int ConnectionHandler::doCommand(QByteArray buf)
0156 {
0157     if ((uid_t)peerUid() != getuid()) {
0158         qCWarning(KSUD_LOG) << "Peer uid not equal to me\n";
0159         qCWarning(KSUD_LOG) << "Peer: " << peerUid() << " Me: " << getuid();
0160         return -1;
0161     }
0162 
0163     QByteArray key;
0164     QByteArray command;
0165     QByteArray pass;
0166     QByteArray name;
0167     QByteArray user;
0168     QByteArray value;
0169     QByteArray env_check;
0170     Data_entry data;
0171 
0172     Lexer *l = new Lexer(buf);
0173     int tok = l->lex();
0174     switch (tok) {
0175     case Lexer::Tok_pass: // "PASS password:string timeout:int\n"
0176         tok = l->lex();
0177         if (tok != Lexer::Tok_str) {
0178             goto parse_error;
0179         }
0180         m_Pass.fill('x');
0181         m_Pass = l->lval();
0182         tok = l->lex();
0183         if (tok != Lexer::Tok_num) {
0184             goto parse_error;
0185         }
0186         m_Timeout = l->lval().toInt();
0187         if (l->lex() != '\n') {
0188             goto parse_error;
0189         }
0190         if (m_Pass.isNull()) {
0191             m_Pass = "";
0192         }
0193         qCDebug(KSUD_LOG) << "Password set!\n";
0194         respond(Res_OK);
0195         break;
0196 
0197     case Lexer::Tok_host: // "HOST host:string\n"
0198         tok = l->lex();
0199         if (tok != Lexer::Tok_str) {
0200             goto parse_error;
0201         }
0202         m_Host = l->lval();
0203         if (l->lex() != '\n') {
0204             goto parse_error;
0205         }
0206         qCDebug(KSUD_LOG) << "Host set to " << m_Host;
0207         respond(Res_OK);
0208         break;
0209 
0210     case Lexer::Tok_prio: // "PRIO priority:int\n"
0211         tok = l->lex();
0212         if (tok != Lexer::Tok_num) {
0213             goto parse_error;
0214         }
0215         m_Priority = l->lval().toInt();
0216         if (l->lex() != '\n') {
0217             goto parse_error;
0218         }
0219         qCDebug(KSUD_LOG) << "priority set to " << m_Priority;
0220         respond(Res_OK);
0221         break;
0222 
0223     case Lexer::Tok_sched: // "SCHD scheduler:int\n"
0224         tok = l->lex();
0225         if (tok != Lexer::Tok_num) {
0226             goto parse_error;
0227         }
0228         m_Scheduler = l->lval().toInt();
0229         if (l->lex() != '\n') {
0230             goto parse_error;
0231         }
0232         qCDebug(KSUD_LOG) << "Scheduler set to " << m_Scheduler;
0233         respond(Res_OK);
0234         break;
0235 
0236     case Lexer::Tok_exec: // "EXEC command:string user:string [options:string (env:string)*]\n"
0237     {
0238         QByteArray options;
0239         QList<QByteArray> env;
0240         tok = l->lex();
0241         if (tok != Lexer::Tok_str) {
0242             goto parse_error;
0243         }
0244         command = l->lval();
0245         tok = l->lex();
0246         if (tok != Lexer::Tok_str) {
0247             goto parse_error;
0248         }
0249         user = l->lval();
0250         tok = l->lex();
0251         if (tok != '\n') {
0252             if (tok != Lexer::Tok_str) {
0253                 goto parse_error;
0254             }
0255             options = l->lval();
0256             tok = l->lex();
0257             while (tok != '\n') {
0258                 if (tok != Lexer::Tok_str) {
0259                     goto parse_error;
0260                 }
0261                 QByteArray env_str = l->lval();
0262                 env.append(env_str);
0263                 if (strncmp(env_str.constData(), "DESKTOP_STARTUP_ID=", strlen("DESKTOP_STARTUP_ID=")) != 0) {
0264                     env_check += '*' + env_str;
0265                 }
0266                 tok = l->lex();
0267             }
0268         }
0269 
0270         QByteArray auth_user;
0271         if ((m_Scheduler != SuProcess::SchedNormal) || (m_Priority > 50)) {
0272             auth_user = "root";
0273         } else {
0274             auth_user = user;
0275         }
0276         key = makeKey(2, m_Host, auth_user, command);
0277         // We only use the command if the environment is the same.
0278         if (repo->find(key) == env_check) {
0279             key = makeKey(0, m_Host, auth_user, command);
0280             pass = repo->find(key);
0281         }
0282         if (pass.isNull()) // isNull() means no password, isEmpty() can mean empty password
0283         {
0284             if (m_Pass.isNull()) {
0285                 respond(Res_NO);
0286                 break;
0287             }
0288             data.value = env_check;
0289             data.timeout = m_Timeout;
0290             key = makeKey(2, m_Host, auth_user, command);
0291             repo->add(key, data);
0292             data.value = m_Pass;
0293             data.timeout = m_Timeout;
0294             key = makeKey(0, m_Host, auth_user, command);
0295             repo->add(key, data);
0296             pass = m_Pass;
0297         }
0298 
0299         // Execute the command asynchronously
0300         qCDebug(KSUD_LOG) << "Executing command: " << command;
0301         pid_t pid = fork();
0302         if (pid < 0) {
0303             qCDebug(KSUD_LOG) << "fork(): " << strerror(errno);
0304             respond(Res_NO);
0305             break;
0306         } else if (pid > 0) {
0307             m_pid = pid;
0308             respond(Res_OK);
0309             break;
0310         }
0311 
0312         // Ignore SIGCHLD because "class SuProcess" needs waitpid()
0313         signal(SIGCHLD, SIG_DFL);
0314 
0315         int ret;
0316         if (m_Host.isEmpty()) {
0317             SuProcess proc;
0318             proc.setCommand(command);
0319             proc.setUser(user);
0320             if (options.contains('x')) {
0321                 proc.setXOnly(true);
0322             }
0323             proc.setPriority(m_Priority);
0324             proc.setScheduler(m_Scheduler);
0325             proc.setEnvironment(env);
0326             ret = proc.exec(pass.data());
0327         } else {
0328             SshProcess proc;
0329             proc.setCommand(command);
0330             proc.setUser(user);
0331             proc.setHost(m_Host);
0332             ret = proc.exec(pass.data());
0333         }
0334 
0335         qCDebug(KSUD_LOG) << "Command completed: " << command;
0336         _exit(ret);
0337     }
0338 
0339     case Lexer::Tok_delCmd: // "DEL command:string user:string\n"
0340         tok = l->lex();
0341         if (tok != Lexer::Tok_str) {
0342             goto parse_error;
0343         }
0344         command = l->lval();
0345         tok = l->lex();
0346         if (tok != Lexer::Tok_str) {
0347             goto parse_error;
0348         }
0349         user = l->lval();
0350         if (l->lex() != '\n') {
0351             goto parse_error;
0352         }
0353         key = makeKey(0, m_Host, user, command);
0354         if (repo->remove(key) < 0) {
0355             qCDebug(KSUD_LOG) << "Unknown command: " << command;
0356             respond(Res_NO);
0357         } else {
0358             qCDebug(KSUD_LOG) << "Deleted command: " << command << ", user = " << user;
0359             respond(Res_OK);
0360         }
0361         break;
0362 
0363     case Lexer::Tok_delVar: // "DELV name:string \n"
0364     {
0365         tok = l->lex();
0366         if (tok != Lexer::Tok_str) {
0367             goto parse_error;
0368         }
0369         name = l->lval();
0370         tok = l->lex();
0371         if (tok != '\n') {
0372             goto parse_error;
0373         }
0374         key = makeKey(1, name);
0375         if (repo->remove(key) < 0) {
0376             qCDebug(KSUD_LOG) << "Unknown name: " << name;
0377             respond(Res_NO);
0378         } else {
0379             qCDebug(KSUD_LOG) << "Deleted name: " << name;
0380             respond(Res_OK);
0381         }
0382         break;
0383     }
0384 
0385     case Lexer::Tok_delGroup: // "DELG group:string\n"
0386         tok = l->lex();
0387         if (tok != Lexer::Tok_str) {
0388             goto parse_error;
0389         }
0390         name = l->lval();
0391         if (repo->removeGroup(name) < 0) {
0392             qCDebug(KSUD_LOG) << "No keys found under group: " << name;
0393             respond(Res_NO);
0394         } else {
0395             qCDebug(KSUD_LOG) << "Removed all keys under group: " << name;
0396             respond(Res_OK);
0397         }
0398         break;
0399 
0400     case Lexer::Tok_delSpecialKey: // "DELS special_key:string\n"
0401         tok = l->lex();
0402         if (tok != Lexer::Tok_str) {
0403             goto parse_error;
0404         }
0405         name = l->lval();
0406         if (repo->removeSpecialKey(name) < 0) {
0407             respond(Res_NO);
0408         } else {
0409             respond(Res_OK);
0410         }
0411         break;
0412 
0413     case Lexer::Tok_set: // "SET name:string value:string group:string timeout:int\n"
0414         tok = l->lex();
0415         if (tok != Lexer::Tok_str) {
0416             goto parse_error;
0417         }
0418         name = l->lval();
0419         tok = l->lex();
0420         if (tok != Lexer::Tok_str) {
0421             goto parse_error;
0422         }
0423         data.value = l->lval();
0424         tok = l->lex();
0425         if (tok != Lexer::Tok_str) {
0426             goto parse_error;
0427         }
0428         data.group = l->lval();
0429         tok = l->lex();
0430         if (tok != Lexer::Tok_num) {
0431             goto parse_error;
0432         }
0433         data.timeout = l->lval().toInt();
0434         if (l->lex() != '\n') {
0435             goto parse_error;
0436         }
0437         key = makeKey(1, name);
0438         repo->add(key, data);
0439         qCDebug(KSUD_LOG) << "Stored key: " << key;
0440         respond(Res_OK);
0441         break;
0442 
0443     case Lexer::Tok_get: // "GET name:string\n"
0444         tok = l->lex();
0445         if (tok != Lexer::Tok_str) {
0446             goto parse_error;
0447         }
0448         name = l->lval();
0449         if (l->lex() != '\n') {
0450             goto parse_error;
0451         }
0452         key = makeKey(1, name);
0453         qCDebug(KSUD_LOG) << "Request for key: " << key;
0454         value = repo->find(key);
0455         if (!value.isEmpty()) {
0456             respond(Res_OK, value);
0457         } else {
0458             respond(Res_NO);
0459         }
0460         break;
0461 
0462     case Lexer::Tok_getKeys: // "GETK groupname:string\n"
0463         tok = l->lex();
0464         if (tok != Lexer::Tok_str) {
0465             goto parse_error;
0466         }
0467         name = l->lval();
0468         if (l->lex() != '\n') {
0469             goto parse_error;
0470         }
0471         qCDebug(KSUD_LOG) << "Request for group key: " << name;
0472         value = repo->findKeys(name);
0473         if (!value.isEmpty()) {
0474             respond(Res_OK, value);
0475         } else {
0476             respond(Res_NO);
0477         }
0478         break;
0479 
0480     case Lexer::Tok_chkGroup: // "CHKG groupname:string\n"
0481         tok = l->lex();
0482         if (tok != Lexer::Tok_str) {
0483             goto parse_error;
0484         }
0485         name = l->lval();
0486         if (l->lex() != '\n') {
0487             goto parse_error;
0488         }
0489         qCDebug(KSUD_LOG) << "Checking for group key: " << name;
0490         if (repo->hasGroup(name) < 0) {
0491             respond(Res_NO);
0492         } else {
0493             respond(Res_OK);
0494         }
0495         break;
0496 
0497     case Lexer::Tok_ping: // "PING\n"
0498         tok = l->lex();
0499         if (tok != '\n') {
0500             goto parse_error;
0501         }
0502         respond(Res_OK);
0503         break;
0504 
0505     case Lexer::Tok_exit: // "EXIT\n"
0506         tok = l->lex();
0507         if (tok != '\n') {
0508             goto parse_error;
0509         }
0510         m_needExitCode = true;
0511         if (m_hasExitCode) {
0512             sendExitCode();
0513         }
0514         break;
0515 
0516     case Lexer::Tok_stop: // "STOP\n"
0517         tok = l->lex();
0518         if (tok != '\n') {
0519             goto parse_error;
0520         }
0521         qCDebug(KSUD_LOG) << "Stopping by command";
0522         respond(Res_OK);
0523         kdesud_cleanup();
0524         exit(0);
0525 
0526     default:
0527         qCWarning(KSUD_LOG) << "Unknown command: " << l->lval();
0528         respond(Res_NO);
0529         goto parse_error;
0530     }
0531 
0532     delete l;
0533     return 0;
0534 
0535 parse_error:
0536     qCWarning(KSUD_LOG) << "Parse error";
0537     delete l;
0538     return -1;
0539 }