diff options
Diffstat (limited to 'tdesu/tdesud')
-rw-r--r-- | tdesu/tdesud/CMakeLists.txt | 35 | ||||
-rw-r--r-- | tdesu/tdesud/Makefile.am | 31 | ||||
-rw-r--r-- | tdesu/tdesud/handler.cpp | 512 | ||||
-rw-r--r-- | tdesu/tdesud/handler.h | 52 | ||||
-rw-r--r-- | tdesu/tdesud/lexer.cpp | 134 | ||||
-rw-r--r-- | tdesu/tdesud/lexer.h | 42 | ||||
-rw-r--r-- | tdesu/tdesud/repo.cpp | 188 | ||||
-rw-r--r-- | tdesu/tdesud/repo.h | 68 | ||||
-rw-r--r-- | tdesu/tdesud/secure.cpp | 80 | ||||
-rw-r--r-- | tdesu/tdesud/secure.h | 52 | ||||
-rw-r--r-- | tdesu/tdesud/tdesud.cpp | 418 |
11 files changed, 1612 insertions, 0 deletions
diff --git a/tdesu/tdesud/CMakeLists.txt b/tdesu/tdesud/CMakeLists.txt new file mode 100644 index 000000000..2696a8197 --- /dev/null +++ b/tdesu/tdesud/CMakeLists.txt @@ -0,0 +1,35 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +# FIXME there is used KDE_USE_FPIE + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### tdesud (executable) ####################### + +tde_add_executable( tdesud + SOURCES tdesud.cpp repo.cpp lexer.cpp handler.cpp secure.cpp + LINK tdesu-shared + DESTINATION ${BIN_INSTALL_DIR} +) + +install( CODE "execute_process( COMMAND chown root:${nogroup} \$ENV{DESTDIR}${_destination}/tdesud )" ) +install( CODE "execute_process( COMMAND chmod 2755 \$ENV{DESTDIR}${_destination}/tdesud )" ) diff --git a/tdesu/tdesud/Makefile.am b/tdesu/tdesud/Makefile.am new file mode 100644 index 000000000..140cbce06 --- /dev/null +++ b/tdesu/tdesud/Makefile.am @@ -0,0 +1,31 @@ +## Makefile.am for tdesud + +INCLUDES = $(all_includes) + +KDE_CXXFLAGS = $(KDE_USE_FPIE) + +bin_PROGRAMS = tdesud +tdesud_SOURCES = tdesud.cpp repo.cpp lexer.cpp handler.cpp secure.cpp +tdesud_LDFLAGS = $(KDE_USE_PIE) $(all_libraries) $(KDE_RPATH) +tdesud_LDADD = $(LIB_KDECORE) -ltdesu $(LIBSOCKET) +noinst_HEADERS = repo.h handler.h lexer.h secure.h + +## tdesud needs to be suid or sgid something +install-data-local: + @echo "********************************************************" + @echo "" + @echo "For security reasons, tdesud is installed setgid nogroup." + @echo "Kdesud is the daemon that implements the password caching." + @echo "" + @echo "You should NOT use the password caching feature if tdesud is" + @echo "not installed setgid nogroup." + @echo "" + @echo "********************************************************" + +install-exec-hook: + @(chown root:@nogroup@ $(DESTDIR)$(bindir)/tdesud && chmod 2755 $(DESTDIR)$(bindir)/tdesud) \ + || echo "Error: Could not install tdesud as setgid nogroup!!\n" \ + "The password caching feature is disabled." + +messages: + $(XGETTEXT) $(tdesud_SOURCES) -o $(podir)/tdesud.pot diff --git a/tdesu/tdesud/handler.cpp b/tdesu/tdesud/handler.cpp new file mode 100644 index 000000000..2a744b217 --- /dev/null +++ b/tdesu/tdesud/handler.cpp @@ -0,0 +1,512 @@ +/* + * This file is part of the KDE project, module tdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * handler.cpp: A connection handler for tdesud. + */ + + +#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 <tqcstring.h> + +#include <kdebug.h> +#include <tdesu/su.h> +#include <tdesu/ssh.h> + +#include "handler.h" +#include "repo.h" +#include "lexer.h" +#include "secure.h" + + +// Global repository +extern Repository *repo; +void tdesud_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; + TQCString 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; +} + +TQCString ConnectionHandler::makeKey(int _namespace, TQCString s1, + TQCString s2, TQCString s3) +{ + TQCString res; + res.setNum(_namespace); + res += "*"; + res += s1 + "*" + s2 + "*" + s3; + return res; +} + +void ConnectionHandler::sendExitCode() +{ + if (!m_needExitCode) + return; + TQCString buf; + buf.setNum(m_exitCode); + buf.prepend("OK "); + buf.append("\n"); + + send(m_Fd, buf.data(), buf.length(), 0); +} + +void ConnectionHandler::respond(int ok, TQCString s) +{ + TQCString 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(TQCString buf) +{ + if ((uid_t) peerUid() != getuid()) + { + kdWarning(1205) << "Peer uid not equal to me\n"; + kdWarning(1205) << "Peer: " << peerUid() << " Me: " << getuid() << endl; + return -1; + } + + TQCString 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" + { + TQCString 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; + TQCString 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(); + } + } + + TQCString 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); + tdesud_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; +} + + + diff --git a/tdesu/tdesud/handler.h b/tdesu/tdesud/handler.h new file mode 100644 index 000000000..8728efe78 --- /dev/null +++ b/tdesu/tdesud/handler.h @@ -0,0 +1,52 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module tdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + */ + +#ifndef __Handler_h_included__ +#define __Handler_h_included__ + +#include <sys/types.h> + +#include <tqcstring.h> +#include "secure.h" + +/** + * A ConnectionHandler handles a client. It is called from the main program + * loop whenever there is data to read from a corresponding socket. + * It keeps reading data until a newline is read. Then, a command is parsed + * and executed. + */ + +class ConnectionHandler: public SocketSecurity +{ + +public: + ConnectionHandler(int fd); + ~ConnectionHandler(); + + /** Handle incoming data. */ + int handle(); + + /* Send back exit code. */ + void sendExitCode(); + +private: + enum Results { Res_OK, Res_NO }; + + int doCommand(TQCString buf); + void respond(int ok, TQCString s=0); + TQCString makeKey(int namspace, TQCString s1, TQCString s2=0, TQCString s3=0); + + int m_Fd, m_Timeout; + int m_Priority, m_Scheduler; + TQCString m_Buf, m_Pass, m_Host; +public: + int m_exitCode; + bool m_hasExitCode; + bool m_needExitCode; + pid_t m_pid; +}; + +#endif diff --git a/tdesu/tdesud/lexer.cpp b/tdesu/tdesud/lexer.cpp new file mode 100644 index 000000000..f1932f07d --- /dev/null +++ b/tdesu/tdesud/lexer.cpp @@ -0,0 +1,134 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module tdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * lexer.cpp: A lexer for the tdesud protocol. See tdesud.cpp for a + * description of the protocol. + */ + +#include <ctype.h> +#include <tqcstring.h> +#include "lexer.h" + + +Lexer::Lexer(const TQCString &input) +{ + m_Input = input; + in = 0; +} + +Lexer::~Lexer() +{ + // Erase buffers + m_Input.fill('x'); + m_Output.fill('x'); +} + +TQCString &Lexer::lval() +{ + return m_Output; +} + +/* + * lex() is the lexer. There is no end-of-input check here so that has to be + * done by the caller. + */ + +int Lexer::lex() +{ + char c; + + c = m_Input[in++]; + m_Output.fill('x'); + m_Output.resize(0); + + while (1) + { + // newline? + if (c == '\n') + return '\n'; + + // No control characters + if (iscntrl(c)) + return Tok_none; + + if (isspace(c)) + while (isspace(c = m_Input[in++])); + + // number? + if (isdigit(c)) + { + m_Output += c; + while (isdigit(c = m_Input[in++])) + m_Output += c; + in--; + return Tok_num; + } + + // quoted string? + if (c == '"') + { + c = m_Input[in++]; + while ((c != '"') && !iscntrl(c)) { + // handle escaped characters + if (c == '\\') + m_Output += m_Input[in++]; + else + m_Output += c; + c = m_Input[in++]; + } + if (c == '"') + return Tok_str; + return Tok_none; + } + + // normal string + while (!isspace(c) && !iscntrl(c)) + { + m_Output += c; + c = m_Input[in++]; + } + in--; + + // command? + if (m_Output.length() <= 4) + { + if (m_Output == "EXEC") + return Tok_exec; + if (m_Output == "PASS") + return Tok_pass; + if (m_Output == "DEL") + return Tok_delCmd; + if (m_Output == "PING") + return Tok_ping; + if (m_Output == "EXIT") + return Tok_exit; + if (m_Output == "STOP") + return Tok_stop; + if (m_Output == "SET") + return Tok_set; + if (m_Output == "GET") + return Tok_get; + if (m_Output == "HOST") + return Tok_host; + if (m_Output == "SCHD") + return Tok_sched; + if (m_Output == "PRIO") + return Tok_prio; + if (m_Output == "DELV") + return Tok_delVar; + if (m_Output == "DELG") + return Tok_delGroup; + if (m_Output == "DELS") + return Tok_delSpecialKey; + if (m_Output == "GETK") + return Tok_getKeys; + if (m_Output == "CHKG") + return Tok_chkGroup; + } + + return Tok_str; + } +} + diff --git a/tdesu/tdesud/lexer.h b/tdesu/tdesud/lexer.h new file mode 100644 index 000000000..d8f529cd8 --- /dev/null +++ b/tdesu/tdesud/lexer.h @@ -0,0 +1,42 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module tdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + */ + +#ifndef __Lexer_h_included__ +#define __Lexer_h_included__ + +class TQCString; + +/** + * This is a lexer for the tdesud protocol. + */ + +class Lexer { +public: + Lexer(const TQCString &input); + ~Lexer(); + + /** Read next token. */ + int lex(); + + /** Return the token's value. */ + TQCString &lval(); + + enum Tokens { + Tok_none, Tok_exec=256, Tok_pass, Tok_delCmd, + Tok_ping, Tok_str, Tok_num , Tok_stop, + Tok_set, Tok_get, Tok_delVar, Tok_delGroup, + Tok_host, Tok_prio, Tok_sched, Tok_getKeys, + Tok_chkGroup, Tok_delSpecialKey, Tok_exit + }; + +private: + TQCString m_Input; + TQCString m_Output; + + int in; +}; + +#endif diff --git a/tdesu/tdesud/repo.cpp b/tdesu/tdesud/repo.cpp new file mode 100644 index 000000000..e923d6411 --- /dev/null +++ b/tdesu/tdesud/repo.cpp @@ -0,0 +1,188 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module tdesu. + * Copyright (C) 1999,2000 Geert Jansen <g.t.jansen@stud.tue.nl> + */ + +#include <time.h> +#include <assert.h> + +#include <tqcstring.h> +#include <tqmap.h> +#include <tqvaluestack.h> +#include <kdebug.h> + +#include "repo.h" + + +Repository::Repository() +{ + head_time = (unsigned) -1; +} + + +Repository::~Repository() +{} + + +void Repository::add(const TQCString &key, Data_entry &data) +{ + RepoIterator it = repo.find(key); + if (it != repo.end()) + remove(key); + if (data.timeout == 0) + data.timeout = (unsigned) -1; + else + data.timeout += time(0L); + head_time = QMIN(head_time, data.timeout); + repo.insert(key, data); +} + +int Repository::remove(const TQCString &key) +{ + if( key.isEmpty() ) + return -1; + + RepoIterator it = repo.find(key); + if (it == repo.end()) + return -1; + it.data().value.fill('x'); + it.data().group.fill('x'); + repo.remove(it); + return 0; +} + +int Repository::removeSpecialKey(const TQCString &key) +{ + int found = -1; + if ( !key.isEmpty() ) + { + TQValueStack<TQCString> rm_keys; + for (RepoCIterator it=repo.begin(); it!=repo.end(); ++it) + { + if ( key.find( static_cast<const char *>(it.data().group) ) == 0 && + it.key().find( static_cast<const char *>(key) ) >= 0 ) + { + rm_keys.push(it.key()); + found = 0; + } + } + while (!rm_keys.isEmpty()) + { + kdDebug(1205) << "Removed key: " << rm_keys.top() << endl; + remove(rm_keys.pop()); + } + } + return found; +} + +int Repository::removeGroup(const TQCString &group) +{ + int found = -1; + if ( !group.isEmpty() ) + { + TQValueStack<TQCString> rm_keys; + for (RepoCIterator it=repo.begin(); it!=repo.end(); ++it) + { + if (it.data().group == group) + { + rm_keys.push(it.key()); + found = 0; + } + } + while (!rm_keys.isEmpty()) + { + kdDebug(1205) << "Removed key: " << rm_keys.top() << endl; + remove(rm_keys.pop()); + } + } + return found; +} + +int Repository::hasGroup(const TQCString &group) const +{ + if ( !group.isEmpty() ) + { + RepoCIterator it; + for (it=repo.begin(); it!=repo.end(); ++it) + { + if (it.data().group == group) + return 0; + } + } + return -1; +} + +TQCString Repository::findKeys(const TQCString &group, const char *sep ) const +{ + TQCString list=""; + if( !group.isEmpty() ) + { + kdDebug(1205) << "Looking for matching key with group key: " << group << endl; + int pos; + TQCString key; + RepoCIterator it; + for (it=repo.begin(); it!=repo.end(); ++it) + { + if (it.data().group == group) + { + key = it.key().copy(); + kdDebug(1205) << "Matching key found: " << key << endl; + pos = key.findRev(sep); + key.truncate( pos ); + key.remove(0, 2); + if (!list.isEmpty()) + { + // Add the same keys only once please :) + if( !list.contains(static_cast<const char *>(key)) ) + { + kdDebug(1205) << "Key added to list: " << key << endl; + list += '\007'; // I do not know + list.append(key); + } + } + else + list = key; + } + } + } + return list; +} + +TQCString Repository::find(const TQCString &key) const +{ + if( key.isEmpty() ) + return 0; + + RepoCIterator it = repo.find(key); + if (it == repo.end()) + return 0; + return it.data().value; +} + + +int Repository::expire() +{ + unsigned current = time(0L); + if (current < head_time) + return 0; + + unsigned t; + TQValueStack<TQCString> keys; + head_time = (unsigned) -1; + RepoIterator it; + for (it=repo.begin(); it!=repo.end(); ++it) + { + t = it.data().timeout; + if (t <= current) + keys.push(it.key()); + else + head_time = QMIN(head_time, t); + } + + int n = keys.count(); + while (!keys.isEmpty()) + remove(keys.pop()); + return n; +} + diff --git a/tdesu/tdesud/repo.h b/tdesu/tdesud/repo.h new file mode 100644 index 000000000..70027793f --- /dev/null +++ b/tdesu/tdesud/repo.h @@ -0,0 +1,68 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module tdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + */ + +#ifndef __Repo_h_included__ +#define __Repo_h_included__ + + +#include <tqmap.h> +#include <tqcstring.h> + + +/** + * Used internally. + */ +struct Data_entry +{ + TQCString value; + TQCString group; + unsigned int timeout; +}; + + +/** + * String repository. + * + * This class implements a string repository with expiration. + */ +class Repository { +public: + Repository(); + ~Repository(); + + /** Remove data elements which are expired. */ + int expire(); + + /** Add a data element */ + void add(const TQCString& key, Data_entry& data); + + /** Delete a data element. */ + int remove(const TQCString& key); + + /** Delete all data entries having the given group. */ + int removeGroup(const TQCString& group); + + /** Delete all data entries based on key. */ + int removeSpecialKey(const TQCString& key ); + + /** Checks for the existence of the specified group. */ + int hasGroup(const TQCString &group) const; + + /** Return a data value. */ + TQCString find(const TQCString& key) const; + + /** Returns the key values for the given group. */ + TQCString findKeys(const TQCString& group, const char *sep= "-") const; + +private: + + TQMap<TQCString,Data_entry> repo; + typedef TQMap<TQCString,Data_entry>::Iterator RepoIterator; + typedef TQMap<TQCString,Data_entry>::ConstIterator RepoCIterator; + unsigned head_time; +}; + +#endif diff --git a/tdesu/tdesud/secure.cpp b/tdesu/tdesud/secure.cpp new file mode 100644 index 000000000..9b30ab4ee --- /dev/null +++ b/tdesu/tdesud/secure.cpp @@ -0,0 +1,80 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module tdesu. + * Copyright (C) 1999,2000 Geert Jansen <g.t.jansen@stud.tue.nl> + * + * secure.cpp: Peer credentials for a UNIX socket. + */ + +#include <config.h> + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> + +#include <kdebug.h> +#include <ksockaddr.h> +#include "secure.h" + + +/** + * Under Linux, Socket_security is supported. + */ + +#if defined(SO_PEERCRED) + +SocketSecurity::SocketSecurity(int sockfd) +{ + ksocklen_t len = sizeof(struct ucred); + if (getsockopt(sockfd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0) { + kdError() << "getsockopt(SO_PEERCRED) " << perror << endl; + return; + } + + ok = true; +} + +#else +# if defined(HAVE_GETPEEREID) +SocketSecurity::SocketSecurity(int sockfd) +{ + uid_t euid; + gid_t egid; + if (getpeereid(sockfd, &euid, &egid) == 0) { + cred.uid = euid; + cred.gid = egid; + cred.pid = -1; + ok = true; + } +} + +# else + + +/** + * The default version does nothing. + */ + +SocketSecurity::SocketSecurity(int sockfd) +{ + static bool warned_him = FALSE; + + if (!warned_him) { + kdWarning() << "Using void socket security. Please add support for your" << endl; + kdWarning() << "platform to tdesu/tdesud/secure.cpp" << endl; + warned_him = TRUE; + } + + // This passes the test made in handler.cpp + cred.uid = getuid(); + ok = true; +} + +# endif +#endif diff --git a/tdesu/tdesud/secure.h b/tdesu/tdesud/secure.h new file mode 100644 index 000000000..edf58d9c6 --- /dev/null +++ b/tdesu/tdesud/secure.h @@ -0,0 +1,52 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module tdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + */ + +#ifndef __Secure_h_included__ +#define __Secure_h_included__ + +#include "config.h" + +#include <sys/types.h> +#include <sys/socket.h> + +#ifndef HAVE_STRUCT_UCRED + +// `struct ucred' is not defined in glibc 2.0. + +struct ucred { + pid_t pid; + uid_t uid; + gid_t gid; +}; + +#endif // HAVE_STRUCT_UCRED + + +/** + * The Socket_security class autheticates the peer for you. It provides + * the process-id, user-id and group-id plus the MD5 sum of the connected + * binary. + */ + +class SocketSecurity { +public: + SocketSecurity(int fd); + + /** Returns the peer's process-id. */ + int peerPid() { if (!ok) return -1; return cred.pid; } + + /** Returns the peer's user-id */ + int peerUid() { if (!ok) return -1; return cred.uid; } + + /** Returns the peer's group-id */ + int peerGid() { if (!ok) return -1; return cred.gid; } + +private: + bool ok; + struct ucred cred; +}; + +#endif diff --git a/tdesu/tdesud/tdesud.cpp b/tdesu/tdesud/tdesud.cpp new file mode 100644 index 000000000..d369aaf9b --- /dev/null +++ b/tdesu/tdesud/tdesud.cpp @@ -0,0 +1,418 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module tdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * + * tdesud.cpp: KDE su daemon. Offers "keep password" functionality to kde su. + * + * The socket $KDEHOME/socket-$(HOSTNAME)/tdesud_$(display) is used for communication with + * client programs. + * + * The protocol: Client initiates the connection. All commands and responses + * are terminated by a newline. + * + * Client Server Description + * ------ ------ ----------- + * + * PASS <pass> <timeout> OK Set password for commands in + * this session. Password is + * valid for <timeout> seconds. + * + * USER <user> OK Set the target user [required] + * + * EXEC <command> OK Execute command <command>. If + * NO <command> has been executed + * before (< timeout) no PASS + * command is needed. + * + * DEL <command> OK Delete password for command + * NO <command>. + * + * PING OK Ping the server (diagnostics). + */ + + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <ctype.h> +#include <string.h> +#include <stdarg.h> +#include <signal.h> +#include <pwd.h> +#include <errno.h> + +#include <sys/prctl.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/resource.h> +#include <sys/wait.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> // Needed on some systems. +#endif + +#include <tqptrvector.h> +#include <tqfile.h> +#include <tqregexp.h> + +#include <kinstance.h> +#include <kdebug.h> +#include <klocale.h> +#include <kcmdlineargs.h> +#include <kstandarddirs.h> +#include <kaboutdata.h> +#include <tdesu/client.h> +#include <tdesu/defaults.h> +#include <ksockaddr.h> + +#include "repo.h" +#include "handler.h" + +#include <X11/X.h> +#include <X11/Xlib.h> + +#ifndef SUN_LEN +#define SUN_LEN(ptr) ((kde_socklen_t) (((struct sockaddr_un *) 0)->sun_path) \ + + strlen ((ptr)->sun_path)) +#endif + +#define ERR strerror(errno) + +// Globals + +Repository *repo; +const char *Version = "1.01"; +TQCString sock; +Display *x11Display; +int pipeOfDeath[2]; + + +void tdesud_cleanup() +{ + unlink(sock); +} + + +// Borrowed from kdebase/kaudio/kaudioserver.cpp + +extern "C" int xio_errhandler(Display *); + +int xio_errhandler(Display *) +{ + kdError(1205) << "Fatal IO error, exiting...\n"; + tdesud_cleanup(); + exit(1); + return 1; //silence compilers +} + +int initXconnection() +{ + x11Display = XOpenDisplay(NULL); + if (x11Display != 0L) + { + XSetIOErrorHandler(xio_errhandler); + XCreateSimpleWindow(x11Display, DefaultRootWindow(x11Display), + 0, 0, 1, 1, 0, + BlackPixelOfScreen(DefaultScreenOfDisplay(x11Display)), + BlackPixelOfScreen(DefaultScreenOfDisplay(x11Display))); + return XConnectionNumber(x11Display); + } else + { + kdWarning(1205) << "Can't connect to the X Server.\n"; + kdWarning(1205) << "Might not terminate at end of session.\n"; + return -1; + } +} + +extern "C" { + void signal_exit(int); + void sigchld_handler(int); +} + +void signal_exit(int sig) +{ + kdDebug(1205) << "Exiting on signal " << sig << "\n"; + tdesud_cleanup(); + exit(1); +} + +void sigchld_handler(int) +{ + char c = ' '; + write(pipeOfDeath[1], &c, 1); +} + +/** + * Creates an AF_UNIX socket in socket resource, mode 0600. + */ + +int create_socket() +{ + int sockfd; + ksocklen_t addrlen; + struct stat s; + + TQCString display(getenv("DISPLAY")); + if (display.isEmpty()) + { + kdWarning(1205) << "$DISPLAY is not set\n"; + return -1; + } + + // strip the screen number from the display + display.replace(TQRegExp("\\.[0-9]+$"), ""); + + sock = TQFile::encodeName(locateLocal("socket", TQString("tdesud_%1").arg(static_cast<const char *>(display)))); + int stat_err=lstat(sock, &s); + if(!stat_err && S_ISLNK(s.st_mode)) { + kdWarning(1205) << "Someone is running a symlink attack on you\n"; + if(unlink(sock)) { + kdWarning(1205) << "Could not delete symlink\n"; + return -1; + } + } + + if (!access(sock, R_OK|W_OK)) + { + KDEsuClient client; + if (client.ping() == -1) + { + kdWarning(1205) << "stale socket exists\n"; + if (unlink(sock)) + { + kdWarning(1205) << "Could not delete stale socket\n"; + return -1; + } + } else + { + kdWarning(1205) << "tdesud is already running\n"; + return -1; + } + + } + + sockfd = socket(PF_UNIX, SOCK_STREAM, 0); + if (sockfd < 0) + { + kdError(1205) << "socket(): " << ERR << "\n"; + return -1; + } + + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, sock, sizeof(addr.sun_path)-1); + addr.sun_path[sizeof(addr.sun_path)-1] = '\000'; + addrlen = SUN_LEN(&addr); + if (bind(sockfd, (struct sockaddr *)&addr, addrlen) < 0) + { + kdError(1205) << "bind(): " << ERR << "\n"; + return -1; + } + + struct linger lin; + lin.l_onoff = lin.l_linger = 0; + if (setsockopt(sockfd, SOL_SOCKET, SO_LINGER, (char *) &lin, + sizeof(linger)) < 0) + { + kdError(1205) << "setsockopt(SO_LINGER): " << ERR << "\n"; + return -1; + } + + int opt = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, + sizeof(opt)) < 0) + { + kdError(1205) << "setsockopt(SO_REUSEADDR): " << ERR << "\n"; + return -1; + } + opt = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, + sizeof(opt)) < 0) + { + kdError(1205) << "setsockopt(SO_KEEPALIVE): " << ERR << "\n"; + return -1; + } + chmod(sock, 0600); + return sockfd; +} + + +/** + * Main program + */ + +int main(int argc, char *argv[]) +{ + prctl(PR_SET_DUMPABLE, 0); + + KAboutData aboutData("tdesud", I18N_NOOP("KDE su daemon"), + Version, I18N_NOOP("Daemon used by tdesu"), + KAboutData::License_Artistic, + "Copyright (c) 1999,2000 Geert Jansen"); + aboutData.addAuthor("Geert Jansen", I18N_NOOP("Author"), + "jansen@kde.org", "http://www.stack.nl/~geertj/"); + KCmdLineArgs::init(argc, argv, &aboutData); + KInstance instance(&aboutData); + + // Set core dump size to 0 + struct rlimit rlim; + rlim.rlim_cur = rlim.rlim_max = 0; + if (setrlimit(RLIMIT_CORE, &rlim) < 0) + { + kdError(1205) << "setrlimit(): " << ERR << "\n"; + exit(1); + } + + // Create the Unix socket. + int sockfd = create_socket(); + if (sockfd < 0) + exit(1); + if (listen(sockfd, 1) < 0) + { + kdError(1205) << "listen(): " << ERR << "\n"; + tdesud_cleanup(); + exit(1); + } + int maxfd = sockfd; + + // Ok, we're accepting connections. Fork to the background. + pid_t pid = fork(); + if (pid == -1) + { + kdError(1205) << "fork():" << ERR << "\n"; + tdesud_cleanup(); + exit(1); + } + if (pid) + exit(0); + + // Make sure we exit when the display gets closed. + int x11Fd = initXconnection(); + maxfd = QMAX(maxfd, x11Fd); + + repo = new Repository; + TQPtrVector<ConnectionHandler> handler; + handler.setAutoDelete(true); + + pipe(pipeOfDeath); + maxfd = QMAX(maxfd, pipeOfDeath[0]); + + // Signal handlers + struct sigaction sa; + sa.sa_handler = signal_exit; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGHUP, &sa, 0L); + sigaction(SIGINT, &sa, 0L); + sigaction(SIGTERM, &sa, 0L); + sigaction(SIGQUIT, &sa, 0L); + + sa.sa_handler = sigchld_handler; + sa.sa_flags = SA_NOCLDSTOP; + sigaction(SIGCHLD, &sa, 0L); + sa.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sa, 0L); + + // Main execution loop + + ksocklen_t addrlen; + struct sockaddr_un clientname; + + fd_set tmp_fds, active_fds; + FD_ZERO(&active_fds); + FD_SET(sockfd, &active_fds); + FD_SET(pipeOfDeath[0], &active_fds); + if (x11Fd != -1) + FD_SET(x11Fd, &active_fds); + + while (1) + { + tmp_fds = active_fds; + if(x11Display) + XFlush(x11Display); + if (select(maxfd+1, &tmp_fds, 0L, 0L, 0L) < 0) + { + if (errno == EINTR) continue; + + kdError(1205) << "select(): " << ERR << "\n"; + exit(1); + } + repo->expire(); + for (int i=0; i<=maxfd; i++) + { + if (!FD_ISSET(i, &tmp_fds)) + continue; + + if (i == pipeOfDeath[0]) + { + char buf[101]; + read(pipeOfDeath[0], buf, 100); + pid_t result; + do + { + int status; + result = waitpid((pid_t)-1, &status, WNOHANG); + if (result > 0) + { + for(int j=handler.size(); j--;) + { + if (handler[j] && (handler[j]->m_pid == result)) + { + handler[j]->m_exitCode = WEXITSTATUS(status); + handler[j]->m_hasExitCode = true; + handler[j]->sendExitCode(); + handler[j]->m_pid = 0; + break; + } + } + } + } + while(result > 0); + } + + if (i == x11Fd) + { + // Discard X events + XEvent event_return; + if (x11Display) + while(XPending(x11Display)) + XNextEvent(x11Display, &event_return); + continue; + } + + if (i == sockfd) + { + // Accept new connection + int fd; + addrlen = 64; + fd = accept(sockfd, (struct sockaddr *) &clientname, &addrlen); + if (fd < 0) + { + kdError(1205) << "accept():" << ERR << "\n"; + continue; + } + if (fd+1 > (int) handler.size()) + handler.resize(fd+1); + handler.insert(fd, new ConnectionHandler(fd)); + maxfd = TQMAX(maxfd, fd); + FD_SET(fd, &active_fds); + continue; + } + + // handle alreay established connection + if (handler[i] && handler[i]->handle() < 0) + { + handler.remove(i); + FD_CLR(i, &active_fds); + } + } + } + kdWarning(1205) << "???\n"; +} + |