diff options
Diffstat (limited to 'kdesu')
-rw-r--r-- | kdesu/AUTHORS | 2 | ||||
-rw-r--r-- | kdesu/ChangeLog | 128 | ||||
-rw-r--r-- | kdesu/FAQ | 46 | ||||
-rw-r--r-- | kdesu/LICENSE.readme | 125 | ||||
-rw-r--r-- | kdesu/Makefile.am | 1 | ||||
-rw-r--r-- | kdesu/README | 34 | ||||
-rw-r--r-- | kdesu/TODO | 3 | ||||
-rw-r--r-- | kdesu/configure.in.in | 62 | ||||
-rw-r--r-- | kdesu/kdesu/Makefile.am | 15 | ||||
-rw-r--r-- | kdesu/kdesu/kdesu.cpp | 430 | ||||
-rw-r--r-- | kdesu/kdesu/sudlg.cpp | 103 | ||||
-rw-r--r-- | kdesu/kdesu/sudlg.h | 32 | ||||
-rw-r--r-- | kdesu/kdesud/Makefile.am | 31 | ||||
-rw-r--r-- | kdesu/kdesud/handler.cpp | 512 | ||||
-rw-r--r-- | kdesu/kdesud/handler.h | 52 | ||||
-rw-r--r-- | kdesu/kdesud/kdesud.cpp | 415 | ||||
-rw-r--r-- | kdesu/kdesud/lexer.cpp | 134 | ||||
-rw-r--r-- | kdesu/kdesud/lexer.h | 42 | ||||
-rw-r--r-- | kdesu/kdesud/repo.cpp | 188 | ||||
-rw-r--r-- | kdesu/kdesud/repo.h | 68 | ||||
-rw-r--r-- | kdesu/kdesud/secure.cpp | 80 | ||||
-rw-r--r-- | kdesu/kdesud/secure.h | 52 |
22 files changed, 2555 insertions, 0 deletions
diff --git a/kdesu/AUTHORS b/kdesu/AUTHORS new file mode 100644 index 000000000..5cabd8892 --- /dev/null +++ b/kdesu/AUTHORS @@ -0,0 +1,2 @@ +Pietro Iglio <[email protected]> +Geert Jansen <[email protected]> diff --git a/kdesu/ChangeLog b/kdesu/ChangeLog new file mode 100644 index 000000000..7ef6d2de2 --- /dev/null +++ b/kdesu/ChangeLog @@ -0,0 +1,128 @@ +Wed, 26 Jan 00 Geert Jansen <[email protected]> + + * Build sycoca for target process. + * Use standard debug facilities. + * Use KCmdLineArgs for argument parsing. + * Dialog updated to use KDialogBase. + +Sat, 18 Dec 99 Geert Jansen <[email protected]> + + * Add DCOP authentication cookies. + * Fixed SMP install of kdesud. + +Sat, 23 Oct 99 Geert Jansen <[email protected]> + + * STL -> QTL + * Use Qt's standard debug facilities. + +Tue, 28 Sep 99 Geert Jansen <[email protected]> + + * Integrating kdesu into kdebase + +Sat, 25 Sep 99 Geert Jansen <[email protected]> + + * BIG change: su to other user than root. This required a different way + to pass X cookies and stuff to the target process. See + common/kdesu_stub.c for more info. + * Fixed a bug in kdesud/lexer.cpp + +Thu, 23 Sep 99 Geert Jansen <[email protected]> + + * Portability issues (root has a csh on FreeBSD ... is this just me?) + * The execution of processes is more efficient now: 2 processess instead + of 3 and 1 tempfile instead of 2. This way possible by setting the + XAUTHORITY env. var. to $HOME/.Xauthority instead of making a new one. + * Fixed a subtle bug in kdesud. kdesud catches SIGCHLD to handle child + exits but this is incompatible with "class RootProcess", which does a + waitpid() and depends on getting the exit code. + * Only connect to daemon when necessary. + +Wed, 22 Sep 99 Geert Jansen <[email protected]> + + * Changed WaitSlave() to open slave instead of master pty. + * Added French translation (Pierre Dorgueil). + * Updated German translation (Ludwig Nussel). + * If su has terminal output, feed it back to the user. + +Wed, 8 Sep 99 Geert Jansen <[email protected]> + + * If started as root, kdesu does not ask for a password anymore. + +Wed, 1 Sep 99 Geert Jansen <[email protected]> + + * Deferred instantiation of kapp in kdesu.cpp. + * Finished porting to KDE2. + +Sun, 29 Aug 99 Geert Jansen <[email protected]> + + * Fixed a bug that would let you run only one program with the daemon. + * Adapted debug.h macros. + +Tue, 24 Aug 99 Geert Jansen <[email protected]> + + * Version 0.97 + * Fixed the RedHat problem! See the function WaitSlave() in + common/process.cpp. + * Fixed a few GUI bugs. + * Improved password security. Passwords are not kept until + the root program exits, they are deleted right away. + +Fri, 20 Aug 99 Geert Jansen <[email protected]> + + * Fixed the glibc 2.1 compile problem (struct ucred test). + +Tue, 17 Aug 99 Geert Jansen <[email protected]> + + * Fixed debug.h macros. + * Some more source cleanups. + +Mon, 16 Aug 99 Geert Jansen <[email protected]> + + * Added "nogroup" check. + * Updated the HTML documentation. + * Updated the FAQ + +Sun, 15 Aug 99 Geert Jansen <[email protected]> + + * KDE su now supports terminal mode apps! (-t switch, no password + keeping, output only) + +Sat, 14 Aug 99 Geert Jansen <[email protected]> + + * Version 0.94 + * PTY handling is improved. It should work on more systems now. + (tested: Linux w & w/o UNIX98 PTY's w & w/o GLIBC 2.1, Solaris 7) + * Changed behaviour of "Keep Password" setting. + * Added -n option: don't offer the choice to keep password. + * Added -q, -d options: Quit the daemon and delete a key. + * Source cleanups. + * Various small bugfixes. + * Merged most of the KDE2 fixes from the CVS version. + * KDE su now waits for child programs to finish. Use 'command &' to + exit right away. + +Wed, 02 Jun 99 Geert Jansen <[email protected]> + + * Version 0.91 + * The password is passed to su with a pty/tty pair. This should fix the + problems redhat users are experiencing. + * Some portability issues (a.o. AF_LOCAL -> AF_UNIX) + +Thu, 20 May 99 Geert Jansen <[email protected]> + + * I am finally able to release 0.9. + +Fri, 09 Apr 99 Geert Jansen <[email protected]> + + * Backport to KDE 1.1. Release as kdesu-0.9.tar.gz + +Mon, 22 Mar 99 Geert Jansen <[email protected]> + + * Major changes: password keeping, control module, UI changes + * Version 0.9 + +Thu, 25 Feb 99 Geert Jansen <[email protected]> + + * Merge with Pietro Iglio's code. + * Version 0.4 + diff --git a/kdesu/FAQ b/kdesu/FAQ new file mode 100644 index 000000000..e5a60fbeb --- /dev/null +++ b/kdesu/FAQ @@ -0,0 +1,46 @@ +Q: On my SuSE system, KDE su does not compile. I get an error that some Qt + header files cannot be found. +A: Install the package qtcompat. + +Q: Is KDE su safe? +A: No program is 100% safe. However, KDE su is not setuid root and it + handles the password you enter with great care so it should be safe + enough. + +Q: How safe is password keeping? +A: Enabling password keeping is less secure that disabling it. However, the + scheme kdesu uses to keep passwords prevents everyone (including you, the + user) from accessing them. Please see the HTML documentation for a full + description of this scheme. + +Q: Can I execute tty applications with kdesu? +A: No. TTY application will probably never be supported. Use the Unix su for + those. + NOTE: As of version 0.94, tty _output_ _only_ is supported with the `-t' + switch. This disables password keeping, though. + +Q: What systems does KDE su support? +A: Tested are: + * Linux 2.x (Redhat 6.x, Mandrake "Cooker", Debian potato, SuSE 6.1) + * Solaris 7 (intel) + * FreeBSD 3.2 (intel, w/ egcs 1.1.2) + It will probably work on more systems but I cannot test that. + +Q: Why doesn't it support every system that is out there. +A: KDE su needs to setup a pty/tty pair for communicating with `su'. This is + because some `su' implementations refuse to read a password from stdin if + that is not a tty. Setting up a pty/tty pair is not completely portable. + +Q: A good debug tip? +A: If kdesu doesn't fire up your application, use the '-t' switch. + This way, you'll get terminal output. Maybe there is something wrong with + the program you're trying to run. + +Q: I always get the warning: "Terminal output not available on non-terminal". +A: Maybe you're not logged on from a terminal but probably you're using + UNIX98 pty's without glibc 2.1 (Linux). The glibc 2.0 ttyname() function + incorrectly reports that UNIX98 slave pty's are no tty's. + +Q: Why not use DCOP for the communications with the daemon? +A: KDE su needs one instance of the daemon per host, instead of per desktop + session. diff --git a/kdesu/LICENSE.readme b/kdesu/LICENSE.readme new file mode 100644 index 000000000..025de076d --- /dev/null +++ b/kdesu/LICENSE.readme @@ -0,0 +1,125 @@ +kdesu - a KDE front end to su + +Copyright (c) 1998 by Pietro Iglio <[email protected]> +Copyright (c) 1999,2000 by Geert Jansen <[email protected]> + + The "Artistic License" + + Preamble + + The intent of this document is to state the conditions under which a + Package may be copied, such that the Copyright Holder maintains some + semblance of artistic control over the development of the package, + while giving the users of the package the right to use and + distribute the Package in a more-or-less customary fashion, plus the + right to make reasonable modifications. + + Definitions: + + * "Package" refers to the collection of files distributed by the + Copyright Holder, and derivatives of that collection of files + created through textual modification. + + * "Standard Version" refers to such a Package if it has not been + modified, or has been modified in accordance with the wishes of + the Copyright Holder. + + * "Copyright Holder" is whoever is named in the copyright or + copyrights for the package. + + * "You" is you, if you're thinking about copying or distributing + this Package. + + * "Reasonable copying fee" is whatever you can justify on the + basis of media cost, duplication charges, time of people + involved, and so on. (You will not be required to justify it to + the Copyright Holder, but only to the computing community at + large as a market that must bear the fee.) + + * "Freely Available" means that no fee is charged for the item + itself, though there may be fees involved in handling the item. + It also means that recipients of the item may redistribute it + under the same conditions they received it. + + 1. You may make and give away verbatim copies of the source form of + the Standard Version of this Package without restriction, provided + that you duplicate all of the original copyright notices and + associated disclaimers. + + 2. You may apply bug fixes, portability fixes and other + modifications derived from the Public Domain or from the Copyright + Holder. A Package modified in such a way shall still be considered + the Standard Version. + + 3. You may otherwise modify your copy of this Package in any way, + provided that you insert a prominent notice in each changed file + stating how and when you changed that file, and provided that you do + at least ONE of the following: + + a) place your modifications in the Public Domain or + otherwise make them Freely Available, such as by posting + said modifications to Usenet or an equivalent medium, or + placing the modifications on a major archive site such as + ftp.uu.net, or by allowing the Copyright Holder to include + your modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation + or organization. + + c) rename any non-standard executables so the names do not + conflict with standard executables, which must also be + provided, and provide a separate manual page for each + non-standard executable that clearly documents how it + differs from the Standard Version. + + d) make other distribution arrangements with the Copyright + Holder. + + 4. You may distribute the programs of this Package in object code or + executable form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and + library files, together with instructions (in the manual + page or equivalent) on where to get the Standard Version. + + b) accompany the distribution with the machine-readable + source of the Package with your modifications. + + c) accompany any non-standard executables with their + corresponding Standard Version executables, giving the + non-standard executables non-standard names, and clearly + documenting the differences in manual pages (or + equivalent), together with instructions on where to get + the Standard Version. + + d) make other distribution arrangements with the Copyright + Holder. + + 5. You may charge a reasonable copying fee for any distribution of + this Package. You may charge any fee you choose for support of this + Package. You may not charge a fee for this Package itself. However, + you may distribute this Package in aggregate with other (possibly + commercial) programs as part of a larger (possibly commercial) + software distribution provided that you do not advertise this + Package as a product of your own. + + 6. The scripts and library files supplied as input to or produced as + output from the programs of this Package do not automatically fall + under the copyright of this Package, but belong to whomever + generated them, and may be sold commercially, and may be aggregated + with this Package. + + 7. C or perl subroutines supplied by you and linked into this + Package shall not be considered part of this Package. + + 8. The name of the Copyright Holder may not be used to endorse or + promote products derived from this software without specific prior + written permission. + + 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + + The End + + diff --git a/kdesu/Makefile.am b/kdesu/Makefile.am new file mode 100644 index 000000000..cac8b9a8b --- /dev/null +++ b/kdesu/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = kdesu kdesud diff --git a/kdesu/README b/kdesu/README new file mode 100644 index 000000000..6a141b0e7 --- /dev/null +++ b/kdesu/README @@ -0,0 +1,34 @@ + + KDESU: A KDE front end for `su'. + +What is it? + + KDE su is a graphical front end to the Unix `su' utility. It allows you + to run programs as another user by entering their password. + It is _not_ a setuid root program, it runs completely unprivileged. + The system's program `su' is used for acquiring privileges. + +Usage: + + $ kdesu -h + $ kdesu -c konsole + +Please see the HTML documentation! + +Notes and Acknowledgements: + + Credits go to Pietro Iglio. He was the original author of KDE su + (until version 0.3). I was writing a similar program when I + found out that KDE su already existed. We decided to merge our + projects and that I would continue with it. + + If you find any bug of have a suggestion, feel free to contact me + at <[email protected]>. + + +License: + + KDE su comes under the "Artistic License". See the file LICENSE.readme + for the exact terms. + +Alan Eldridge 2002/10/12 <[email protected]> diff --git a/kdesu/TODO b/kdesu/TODO new file mode 100644 index 000000000..1be1aec4b --- /dev/null +++ b/kdesu/TODO @@ -0,0 +1,3 @@ +KDE su TODO list. + +* Currently nothing diff --git a/kdesu/configure.in.in b/kdesu/configure.in.in new file mode 100644 index 000000000..5c0798344 --- /dev/null +++ b/kdesu/configure.in.in @@ -0,0 +1,62 @@ +dnl Check for "struct ucred" +AC_MSG_CHECKING("struct ucred") +AC_TRY_COMPILE( +[ + #define _GNU_SOURCE 1 + #include <sys/socket.h> +], +[ + struct ucred red; +], have_ucred=yes + , have_ucred=no +) +if test "$have_ucred" = "yes"; then + AC_DEFINE(HAVE_STRUCT_UCRED, 1, [Define if you have the struct ucred]) + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi + +dnl Check for the group "nogroup" or "nobody" Use -2 otherwise. +AC_MSG_CHECKING(nogroup) +AC_TRY_RUN([ + #include <grp.h> + #include <sys/types.h> + + int main() + { + struct group *grp = getgrnam("nogroup"); + if (grp) return 0; + return 1; + } +], nogroup=nogroup, +AC_TRY_RUN([ + #include <grp.h> + #include <sys/types.h> + + int main() + { + struct group *grp = getgrnam("nobody"); + if (grp) return 0; + return 1; + } +], nogroup=nobody, +nogroup=65534, nogroup=65534), nogroup=65534) +AC_MSG_RESULT($nogroup) +AC_SUBST(nogroup) +AC_CHECK_FUNCS(getpeereid) + +AC_ARG_WITH(sudo-kdesu-backend, + AC_HELP_STRING([--with-sudo-kdesu-backend], + [use sudo as backend for kdesu (default is su)]), +[ + if test x$withval = xyes; then + use_kdesu_backend="sudo" + else + use_kdesu_backend="su" + fi +], + use_kdesu_backend="su" +) + +AC_DEFINE_UNQUOTED(DEFAULT_SUPER_USER_COMMAND, "$use_kdesu_backend", [Use su or sudo]) diff --git a/kdesu/kdesu/Makefile.am b/kdesu/kdesu/Makefile.am new file mode 100644 index 000000000..6084fc241 --- /dev/null +++ b/kdesu/kdesu/Makefile.am @@ -0,0 +1,15 @@ +## Makefile.am for kdesu by Geert Jansen + +INCLUDES= $(all_includes) + +## kdesu +bin_PROGRAMS = kdesu +kdesu_SOURCES = kdesu.cpp sudlg.cpp +kdesu_LDFLAGS = $(all_libraries) $(KDE_RPATH) +kdesu_LDADD = $(LIB_KIO) -lkdesu +kdesu_METASOURCES = AUTO +noinst_HEADERS = sudlg.h + +## Messages +messages: + $(XGETTEXT) $(kdesu_SOURCES) -o $(podir)/kdesu.pot diff --git a/kdesu/kdesu/kdesu.cpp b/kdesu/kdesu/kdesu.cpp new file mode 100644 index 000000000..f45d68092 --- /dev/null +++ b/kdesu/kdesu/kdesu.cpp @@ -0,0 +1,430 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1998 Pietro Iglio <[email protected]> + * Copyright (C) 1999,2000 Geert Jansen <[email protected]> + */ + +#include <config.h> + +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <pwd.h> +#include <stdlib.h> + +#include <sys/time.h> +#include <sys/resource.h> +#if defined(HAVE_SYS_WAIT_H) +#include <sys/wait.h> +#endif + +#include <qstring.h> +#include <qfileinfo.h> +#include <qglobal.h> +#include <qfile.h> +#include <qdir.h> + +#include <dcopclient.h> + +#include <kdebug.h> +#include <kglobal.h> +#include <kapplication.h> +#include <kstandarddirs.h> +#include <kconfig.h> +#include <klocale.h> +#include <kaboutdata.h> +#include <kcmdlineargs.h> +#include <kmessagebox.h> +#include <krun.h> +#include <kuser.h> + +#include <kdesu/defaults.h> +#include <kdesu/su.h> +#include <kdesu/client.h> + +#include "sudlg.h" + +#define ERR strerror(errno) + +QCString command; +const char *Version = "1.0"; + +// NOTE: if you change the position of the -u switch, be sure to adjust it +// at the beginning of main() +static KCmdLineOptions options[] = { + { "+command", I18N_NOOP("Specifies the command to run"), 0 }, + { "c <command>", I18N_NOOP("Specifies the command to run"), "" }, + { "f <file>", I18N_NOOP("Run command under target uid if <file> is not writable"), "" }, + { "u <user>", I18N_NOOP("Specifies the target uid"), "root" }, + { "n", I18N_NOOP("Do not keep password"), 0 }, + { "s", I18N_NOOP("Stop the daemon (forgets all passwords)"), 0 }, + { "t", I18N_NOOP("Enable terminal output (no password keeping)"), 0 }, + { "p <prio>", I18N_NOOP("Set priority value: 0 <= prio <= 100, 0 is lowest"), "50" }, + { "r", I18N_NOOP("Use realtime scheduling"), 0 }, + { "nonewdcop", I18N_NOOP("Let command use existing dcopserver"), 0 }, + { "noignorebutton", I18N_NOOP("Do not display ignore button"), 0 }, + { "i <icon name>", I18N_NOOP("Specify icon to use in the password dialog"), 0}, + { "d", I18N_NOOP("Do not show the command to be run in the dialog"), 0}, + KCmdLineLastOption +}; + + +QCString dcopNetworkId() +{ + QCString result; + result.resize(1025); + QFile file(DCOPClient::dcopServerFile()); + if (!file.open(IO_ReadOnly)) + return ""; + int i = file.readLine(result.data(), 1024); + if (i <= 0) + return ""; + result.data()[i-1] = '\0'; // strip newline + return result; +} + +static int startApp(); + +int main(int argc, char *argv[]) +{ + // FIXME: this can be considered a poor man's solution, as it's not + // directly obvious to a gui user. :) + // anyway, i vote against removing it even when we have a proper gui + // implementation. -- ossi + const char *duser = ::getenv("ADMIN_ACCOUNT"); + if (duser && duser[0]) + options[3].def = duser; + + KAboutData aboutData("kdesu", I18N_NOOP("KDE su"), + Version, I18N_NOOP("Runs a program with elevated privileges."), + KAboutData::License_Artistic, + "Copyright (c) 1998-2000 Geert Jansen, Pietro Iglio"); + aboutData.addAuthor("Geert Jansen", I18N_NOOP("Maintainer"), + "[email protected]", "http://www.stack.nl/~geertj/"); + aboutData.addAuthor("Pietro Iglio", I18N_NOOP("Original author"), + "[email protected]"); + + KCmdLineArgs::init(argc, argv, &aboutData); + KCmdLineArgs::addCmdLineOptions(options); + KApplication::disableAutoDcopRegistration(); + // kdesu doesn't process SM events, so don't even connect to ksmserver + QCString session_manager = getenv( "SESSION_MANAGER" ); + unsetenv( "SESSION_MANAGER" ); + KApplication app; + // but propagate it to the started app + if (session_manager.data()) + { + setenv( "SESSION_MANAGER", session_manager.data(), 1 ); + } + + { + KStartupInfoId id; + id.initId( kapp->startupId()); + id.setupStartupEnv(); // make DESKTOP_STARTUP_ID env. var. available again + } + + int result = startApp(); + + if (result == 127) + { + KMessageBox::sorry(0, i18n("Command '%1' not found.").arg(command)); + } + + return result; +} + +static int startApp() +{ + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + // Stop daemon and exit? + if (args->isSet("s")) + { + KDEsuClient client; + if (client.ping() == -1) + { + kdError(1206) << "Daemon not running -- nothing to stop\n"; + exit(1); + } + if (client.stopServer() != -1) + { + kdDebug(1206) << "Daemon stopped\n"; + exit(0); + } + kdError(1206) << "Could not stop daemon\n"; + exit(1); + } + + QString icon; + if ( args->isSet("i")) + icon = args->getOption("i"); + + bool prompt = true; + if ( args->isSet("d")) + prompt = false; + + // Get target uid + QCString user = args->getOption("u"); + QCString auth_user = user; + struct passwd *pw = getpwnam(user); + if (pw == 0L) + { + kdError(1206) << "User " << user << " does not exist\n"; + exit(1); + } + bool change_uid = (getuid() != pw->pw_uid); + + // If file is writeable, do not change uid + QString file = QFile::decodeName(args->getOption("f")); + if (change_uid && !file.isEmpty()) + { + if (file.at(0) != '/') + { + KStandardDirs dirs; + dirs.addKDEDefaults(); + file = dirs.findResource("config", file); + if (file.isEmpty()) + { + kdError(1206) << "Config file not found: " << file << "\n"; + exit(1); + } + } + QFileInfo fi(file); + if (!fi.exists()) + { + kdError(1206) << "File does not exist: " << file << "\n"; + exit(1); + } + change_uid = !fi.isWritable(); + } + + // Get priority/scheduler + QCString tmp = args->getOption("p"); + bool ok; + int priority = tmp.toInt(&ok); + if (!ok || (priority < 0) || (priority > 100)) + { + KCmdLineArgs::usage(i18n("Illegal priority: %1").arg(tmp)); + exit(1); + } + int scheduler = SuProcess::SchedNormal; + if (args->isSet("r")) + scheduler = SuProcess::SchedRealtime; + if ((priority > 50) || (scheduler != SuProcess::SchedNormal)) + { + change_uid = true; + auth_user = "root"; + } + + // Get command + if (args->isSet("c")) + { + command = args->getOption("c"); + for (int i=0; i<args->count(); i++) + { + QString arg = QFile::decodeName(args->arg(i)); + KRun::shellQuote(arg); + command += " "; + command += QFile::encodeName(arg); + } + } + else + { + if( args->count() == 0 ) + { + KCmdLineArgs::usage(i18n("No command specified.")); + exit(1); + } + command = args->arg(0); + for (int i=1; i<args->count(); i++) + { + QString arg = QFile::decodeName(args->arg(i)); + KRun::shellQuote(arg); + command += " "; + command += QFile::encodeName(arg); + } + } + + // Don't change uid if we're don't need to. + if (!change_uid) + { + int result = system(command); + result = WEXITSTATUS(result); + return result; + } + + // Check for daemon and start if necessary + bool just_started = false; + bool have_daemon = true; + KDEsuClient client; + if (!client.isServerSGID()) + { + kdWarning(1206) << "Daemon not safe (not sgid), not using it.\n"; + have_daemon = false; + } + else if (client.ping() == -1) + { + if (client.startServer() == -1) + { + kdWarning(1206) << "Could not start daemon, reduced functionality.\n"; + have_daemon = false; + } + just_started = true; + } + + // Try to exec the command with kdesud. + bool keep = !args->isSet("n") && have_daemon; + bool terminal = args->isSet("t"); + bool new_dcop = args->isSet("newdcop"); + bool withIgnoreButton = args->isSet("ignorebutton"); + + QCStringList env; + QCString options; + env << ( "DESKTOP_STARTUP_ID=" + kapp->startupId()); + + if (pw->pw_uid) + { + // Only propagate KDEHOME for non-root users, + // root uses KDEROOTHOME + + // Translate the KDEHOME of this user to the new user. + QString kdeHome = KGlobal::dirs()->relativeLocation("home", KGlobal::dirs()->localkdedir()); + if (kdeHome[0] != '/') + kdeHome.prepend("~/"); + else + kdeHome=QString::null; // Use default + + env << ("KDEHOME="+ QFile::encodeName(kdeHome)); + } + + KUser u; + env << (QCString) ("KDESU_USER=" + u.loginName().local8Bit()); + + if (!new_dcop) + { + QCString ksycoca = "KDESYCOCA="+QFile::encodeName(locateLocal("cache", "ksycoca")); + env << ksycoca; + + options += "xf"; // X-only, dcop forwarding enabled. + } + + if (keep && !terminal && !just_started) + { + client.setPriority(priority); + client.setScheduler(scheduler); + int result = client.exec(command, user, options, env); + if (result == 0) + { + result = client.exitCode(); + return result; + } + } + + // Set core dump size to 0 because we will have + // root's password in memory. + struct rlimit rlim; + rlim.rlim_cur = rlim.rlim_max = 0; + if (setrlimit(RLIMIT_CORE, &rlim)) + { + kdError(1206) << "rlimit(): " << ERR << "\n"; + exit(1); + } + + // Read configuration + KConfig *config = KGlobal::config(); + config->setGroup("Passwords"); + int timeout = config->readNumEntry("Timeout", defTimeout); + + // Check if we need a password + SuProcess proc; + proc.setUser(auth_user); + int needpw = proc.checkNeedPassword(); + if (needpw < 0) + { + QString err = i18n("Su returned with an error.\n"); + KMessageBox::error(0L, err); + exit(1); + } + if (needpw == 0) + { + keep = 0; + kdDebug() << "Don't need password!!\n"; + } + + // Start the dialog + QCString password; + if (needpw) + { + KStartupInfoId id; + id.initId( kapp->startupId()); + KStartupInfoData data; + data.setSilent( KStartupInfoData::Yes ); + KStartupInfo::sendChange( id, data ); + KDEsuDialog dlg(user, auth_user, keep && !terminal,icon, withIgnoreButton); + if (prompt) + dlg.addLine(i18n("Command:"), command); + if ((priority != 50) || (scheduler != SuProcess::SchedNormal)) + { + QString prio; + if (scheduler == SuProcess::SchedRealtime) + prio += i18n("realtime: "); + prio += QString("%1/100").arg(priority); + if (prompt) + dlg.addLine(i18n("Priority:"), prio); + } + int ret = dlg.exec(); + if (ret == KDEsuDialog::Rejected) + { + KStartupInfo::sendFinish( id ); + exit(0); + } + if (ret == KDEsuDialog::AsUser) + change_uid = false; + password = dlg.password(); + keep = dlg.keep(); + data.setSilent( KStartupInfoData::No ); + KStartupInfo::sendChange( id, data ); + } + + // Some events may need to be handled (like a button animation) + kapp->processEvents(); + + // Run command + if (!change_uid) + { + int result = system(command); + result = WEXITSTATUS(result); + return result; + } + else if (keep && have_daemon) + { + client.setPass(password, timeout); + client.setPriority(priority); + client.setScheduler(scheduler); + int result = client.exec(command, user, options, env); + if (result == 0) + { + result = client.exitCode(); + return result; + } + } else + { + SuProcess proc; + proc.setTerminal(terminal); + proc.setErase(true); + proc.setUser(user); + if (!new_dcop) + { + proc.setXOnly(true); + proc.setDCOPForwarding(true); + } + proc.setEnvironment(env); + proc.setPriority(priority); + proc.setScheduler(scheduler); + proc.setCommand(command); + int result = proc.exec(password); + return result; + } + return -1; +} + diff --git a/kdesu/kdesu/sudlg.cpp b/kdesu/kdesu/sudlg.cpp new file mode 100644 index 000000000..d68937372 --- /dev/null +++ b/kdesu/kdesu/sudlg.cpp @@ -0,0 +1,103 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 2000 Geert Jansen <[email protected]> + */ + +#include <config.h> +#include <qstring.h> +#include <kconfig.h> +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> + +#include <kdesu/su.h> +#include "sudlg.h" + +KDEsuDialog::KDEsuDialog(QCString user, QCString auth_user, bool enableKeep,const QString& icon, bool withIgnoreButton) + : KPasswordDialog(Password, enableKeep, (withIgnoreButton ? User1:NoDefault), icon) +{ + KConfig* config = KGlobal::config(); + config->setGroup("super-user-command"); + QString superUserCommand = config->readEntry("super-user-command", DEFAULT_SUPER_USER_COMMAND); + if ( superUserCommand != "sudo" && superUserCommand != "su" ) { + kdWarning() << "unknown super user command" << endl; + superUserCommand = "su"; + } + + m_User = auth_user; + setCaption(i18n("Run as %1").arg(user)); + + QString prompt; + if (superUserCommand == "sudo" && m_User == "root") { + prompt = i18n("Please enter your password." ); + } else { + if (m_User == "root") { + prompt = i18n("The action you requested needs root privileges. " + "Please enter root's password below or click " + "Ignore to continue with your current privileges."); + } else { + prompt = i18n("The action you requested needs additional privileges. " + "Please enter the password for \"%1\" below or click " + "Ignore to continue with your current privileges.").arg(m_User); + } + } + setPrompt(prompt); + + if( withIgnoreButton ) + setButtonText(User1, i18n("&Ignore")); +} + + +KDEsuDialog::~KDEsuDialog() +{ +} + +bool KDEsuDialog::checkPassword(const char *password) +{ + SuProcess proc; + proc.setUser(m_User); + int status = proc.checkInstall(password); + switch (status) + { + case -1: + KMessageBox::sorry(this, i18n("Conversation with su failed.")); + done(Rejected); + return false; + + case 0: + return true; + + case SuProcess::SuNotFound: + KMessageBox::sorry(this, + i18n("The program 'su' is not found;\n" + "make sure your PATH is set correctly.")); + done(Rejected); + return false; + + case SuProcess::SuNotAllowed: + KMessageBox::sorry(this, + i18n("You are not allowed to use 'su';\n" + "on some systems, you need to be in a special " + "group (often: wheel) to use this program.")); + done(Rejected); + return false; + + case SuProcess::SuIncorrectPassword: + KMessageBox::sorry(this, i18n("Incorrect password; please try again.")); + return false; + + default: + KMessageBox::error(this, i18n("Internal error: illegal return from " + "SuProcess::checkInstall()")); + done(Rejected); + return false; + } +} + +void KDEsuDialog::slotUser1() +{ + done(AsUser); +} + +#include "sudlg.moc" diff --git a/kdesu/kdesu/sudlg.h b/kdesu/kdesu/sudlg.h new file mode 100644 index 000000000..5f5340fc2 --- /dev/null +++ b/kdesu/kdesu/sudlg.h @@ -0,0 +1,32 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 2000 Geert Jansen <[email protected]> + */ + +#ifndef __SuDlg_h_Included__ +#define __SuDlg_h_Included__ + +#include <kpassdlg.h> + +class KDEsuDialog + : public KPasswordDialog +{ + Q_OBJECT + +public: + KDEsuDialog(QCString user, QCString auth_user, bool enableKeep, const QString& icon , bool withIgnoreButton=false); + ~KDEsuDialog(); + + enum ResultCodes { AsUser = 10 }; + +protected: + bool checkPassword(const char *password); + void slotUser1(); + +private: + QCString m_User; +}; + + +#endif // __SuDlg_h_Included__ diff --git a/kdesu/kdesud/Makefile.am b/kdesu/kdesud/Makefile.am new file mode 100644 index 000000000..07e11dd15 --- /dev/null +++ b/kdesu/kdesud/Makefile.am @@ -0,0 +1,31 @@ +## Makefile.am for kdesud + +INCLUDES = $(all_includes) + +KDE_CXXFLAGS = $(KDE_USE_FPIE) + +bin_PROGRAMS = kdesud +kdesud_SOURCES = kdesud.cpp repo.cpp lexer.cpp handler.cpp secure.cpp +kdesud_LDFLAGS = $(KDE_USE_PIE) $(all_libraries) $(KDE_RPATH) +kdesud_LDADD = $(LIB_KDECORE) -lkdesu $(LIBSOCKET) +noinst_HEADERS = repo.h handler.h lexer.h secure.h + +## kdesud needs to be suid or sgid something +install-data-local: + @echo "********************************************************" + @echo "" + @echo "For security reasons, kdesud 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 kdesud is" + @echo "not installed setgid nogroup." + @echo "" + @echo "********************************************************" + +install-exec-hook: + @(chown root:@nogroup@ $(DESTDIR)$(bindir)/kdesud && chmod 2755 $(DESTDIR)$(bindir)/kdesud) \ + || echo "Error: Could not install kdesud as setgid nogroup!!\n" \ + "The password caching feature is disabled." + +messages: + $(XGETTEXT) $(kdesud_SOURCES) -o $(podir)/kdesud.pot 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; +} + + + diff --git a/kdesu/kdesud/handler.h b/kdesu/kdesud/handler.h new file mode 100644 index 000000000..acfada51b --- /dev/null +++ b/kdesu/kdesud/handler.h @@ -0,0 +1,52 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <[email protected]> + */ + +#ifndef __Handler_h_included__ +#define __Handler_h_included__ + +#include <sys/types.h> + +#include <qcstring.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(QCString buf); + void respond(int ok, QCString s=0); + QCString makeKey(int namspace, QCString s1, QCString s2=0, QCString s3=0); + + int m_Fd, m_Timeout; + int m_Priority, m_Scheduler; + QCString m_Buf, m_Pass, m_Host; +public: + int m_exitCode; + bool m_hasExitCode; + bool m_needExitCode; + pid_t m_pid; +}; + +#endif diff --git a/kdesu/kdesud/kdesud.cpp b/kdesu/kdesud/kdesud.cpp new file mode 100644 index 000000000..d81b6a0c9 --- /dev/null +++ b/kdesu/kdesud/kdesud.cpp @@ -0,0 +1,415 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <[email protected]> + * + * + * kdesud.cpp: KDE su daemon. Offers "keep password" functionality to kde su. + * + * The socket $KDEHOME/socket-$(HOSTNAME)/kdesud_$(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/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 <qptrvector.h> +#include <qfile.h> +#include <qregexp.h> + +#include <kinstance.h> +#include <kdebug.h> +#include <klocale.h> +#include <kcmdlineargs.h> +#include <kstandarddirs.h> +#include <kaboutdata.h> +#include <kdesu/client.h> +#include <kdesu/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"; +QCString sock; +Display *x11Display; +int pipeOfDeath[2]; + + +void kdesud_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"; + kdesud_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"; + kdesud_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; + + QCString display(getenv("DISPLAY")); + if (display.isEmpty()) + { + kdWarning(1205) << "$DISPLAY is not set\n"; + return -1; + } + + // strip the screen number from the display + display.replace(QRegExp("\\.[0-9]+$"), ""); + + sock = QFile::encodeName(locateLocal("socket", QString("kdesud_%1").arg(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) << "kdesud 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[]) +{ + KAboutData aboutData("kdesud", I18N_NOOP("KDE su daemon"), + Version, I18N_NOOP("Daemon used by kdesu"), + KAboutData::License_Artistic, + "Copyright (c) 1999,2000 Geert Jansen"); + aboutData.addAuthor("Geert Jansen", I18N_NOOP("Author"), + "[email protected]", "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"; + kdesud_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"; + kdesud_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; + QPtrVector<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 = QMAX(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"; +} + diff --git a/kdesu/kdesud/lexer.cpp b/kdesu/kdesud/lexer.cpp new file mode 100644 index 000000000..a05251a58 --- /dev/null +++ b/kdesu/kdesud/lexer.cpp @@ -0,0 +1,134 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <[email protected]> + * + * lexer.cpp: A lexer for the kdesud protocol. See kdesud.cpp for a + * description of the protocol. + */ + +#include <ctype.h> +#include <qcstring.h> +#include "lexer.h" + + +Lexer::Lexer(const QCString &input) +{ + m_Input = input; + in = 0; +} + +Lexer::~Lexer() +{ + // Erase buffers + m_Input.fill('x'); + m_Output.fill('x'); +} + +QCString &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/kdesu/kdesud/lexer.h b/kdesu/kdesud/lexer.h new file mode 100644 index 000000000..e678d746e --- /dev/null +++ b/kdesu/kdesud/lexer.h @@ -0,0 +1,42 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <[email protected]> + */ + +#ifndef __Lexer_h_included__ +#define __Lexer_h_included__ + +class QCString; + +/** + * This is a lexer for the kdesud protocol. + */ + +class Lexer { +public: + Lexer(const QCString &input); + ~Lexer(); + + /** Read next token. */ + int lex(); + + /** Return the token's value. */ + QCString &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: + QCString m_Input; + QCString m_Output; + + int in; +}; + +#endif diff --git a/kdesu/kdesud/repo.cpp b/kdesu/kdesud/repo.cpp new file mode 100644 index 000000000..783a9baca --- /dev/null +++ b/kdesu/kdesud/repo.cpp @@ -0,0 +1,188 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <[email protected]> + */ + +#include <time.h> +#include <assert.h> + +#include <qcstring.h> +#include <qmap.h> +#include <qvaluestack.h> +#include <kdebug.h> + +#include "repo.h" + + +Repository::Repository() +{ + head_time = (unsigned) -1; +} + + +Repository::~Repository() +{} + + +void Repository::add(const QCString &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 QCString &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 QCString &key) +{ + int found = -1; + if ( !key.isEmpty() ) + { + QValueStack<QCString> rm_keys; + for (RepoCIterator it=repo.begin(); it!=repo.end(); ++it) + { + if ( key.find( it.data().group ) == 0 && + it.key().find( 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 QCString &group) +{ + int found = -1; + if ( !group.isEmpty() ) + { + QValueStack<QCString> 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 QCString &group) const +{ + if ( !group.isEmpty() ) + { + RepoCIterator it; + for (it=repo.begin(); it!=repo.end(); ++it) + { + if (it.data().group == group) + return 0; + } + } + return -1; +} + +QCString Repository::findKeys(const QCString &group, const char *sep ) const +{ + QCString list=""; + if( !group.isEmpty() ) + { + kdDebug(1205) << "Looking for matching key with group key: " << group << endl; + int pos; + QCString 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(key) ) + { + kdDebug(1205) << "Key added to list: " << key << endl; + list += '\007'; // I do not know + list.append(key); + } + } + else + list = key; + } + } + } + return list; +} + +QCString Repository::find(const QCString &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; + QValueStack<QCString> 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/kdesu/kdesud/repo.h b/kdesu/kdesud/repo.h new file mode 100644 index 000000000..fde8cd250 --- /dev/null +++ b/kdesu/kdesud/repo.h @@ -0,0 +1,68 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <[email protected]> + */ + +#ifndef __Repo_h_included__ +#define __Repo_h_included__ + + +#include <qmap.h> +#include <qcstring.h> + + +/** + * Used internally. + */ +struct Data_entry +{ + QCString value; + QCString 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 QCString& key, Data_entry& data); + + /** Delete a data element. */ + int remove(const QCString& key); + + /** Delete all data entries having the given group. */ + int removeGroup(const QCString& group); + + /** Delete all data entries based on key. */ + int removeSpecialKey(const QCString& key ); + + /** Checks for the existence of the specified group. */ + int hasGroup(const QCString &group) const; + + /** Return a data value. */ + QCString find(const QCString& key) const; + + /** Returns the key values for the given group. */ + QCString findKeys(const QCString& group, const char *sep= "-") const; + +private: + + QMap<QCString,Data_entry> repo; + typedef QMap<QCString,Data_entry>::Iterator RepoIterator; + typedef QMap<QCString,Data_entry>::ConstIterator RepoCIterator; + unsigned head_time; +}; + +#endif diff --git a/kdesu/kdesud/secure.cpp b/kdesu/kdesud/secure.cpp new file mode 100644 index 000000000..3abda20bc --- /dev/null +++ b/kdesu/kdesud/secure.cpp @@ -0,0 +1,80 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <[email protected]> + * + * 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 kdesu/kdesud/secure.cpp" << endl; + warned_him = TRUE; + } + + // This passes the test made in handler.cpp + cred.uid = getuid(); + ok = true; +} + +# endif +#endif diff --git a/kdesu/kdesud/secure.h b/kdesu/kdesud/secure.h new file mode 100644 index 000000000..8e122fac3 --- /dev/null +++ b/kdesu/kdesud/secure.h @@ -0,0 +1,52 @@ +/* vi: ts=8 sts=4 sw=4 + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <[email protected]> + */ + +#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 |