diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | ce4a32fe52ef09d8f5ff1dd22c001110902b60a2 (patch) | |
tree | 5ac38a06f3dde268dc7927dc155896926aaf7012 /kdesu | |
download | tdelibs-ce4a32fe52ef09d8f5ff1dd22c001110902b60a2.tar.gz tdelibs-ce4a32fe52ef09d8f5ff1dd22c001110902b60a2.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdelibs@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kdesu')
-rw-r--r-- | kdesu/Mainpage.dox | 21 | ||||
-rw-r--r-- | kdesu/Makefile.am | 30 | ||||
-rw-r--r-- | kdesu/README | 13 | ||||
-rw-r--r-- | kdesu/client.cpp | 435 | ||||
-rw-r--r-- | kdesu/client.h | 207 | ||||
-rw-r--r-- | kdesu/configure.in.in | 93 | ||||
-rw-r--r-- | kdesu/defaults.h | 20 | ||||
-rw-r--r-- | kdesu/kcookie.cpp | 225 | ||||
-rw-r--r-- | kdesu/kcookie.h | 90 | ||||
-rw-r--r-- | kdesu/kdesu_pty.cpp | 302 | ||||
-rw-r--r-- | kdesu/kdesu_pty.h | 71 | ||||
-rw-r--r-- | kdesu/kdesu_stub.c | 432 | ||||
-rw-r--r-- | kdesu/libkdesu.nmcheck | 10 | ||||
-rw-r--r-- | kdesu/libkdesu_weak.nmcheck | 2 | ||||
-rw-r--r-- | kdesu/process.cpp | 626 | ||||
-rw-r--r-- | kdesu/process.h | 188 | ||||
-rw-r--r-- | kdesu/ssh.cpp | 279 | ||||
-rw-r--r-- | kdesu/ssh.h | 90 | ||||
-rw-r--r-- | kdesu/stub.cpp | 184 | ||||
-rw-r--r-- | kdesu/stub.h | 139 | ||||
-rw-r--r-- | kdesu/su.cpp | 342 | ||||
-rw-r--r-- | kdesu/su.h | 63 |
22 files changed, 3862 insertions, 0 deletions
diff --git a/kdesu/Mainpage.dox b/kdesu/Mainpage.dox new file mode 100644 index 000000000..030ae3650 --- /dev/null +++ b/kdesu/Mainpage.dox @@ -0,0 +1,21 @@ +/** @mainpage Console-mode authentication + +libkdesu provides functionality for building GUI front ends for +(password asking) console mode programs. For example, kdesu and +kdessh use it to interface with su and ssh respectively. + +@authors +Geert Jansen \<jansen@kde.org\> + +@maintainers +Adriaan de Groot \<groot@kde.org\> + +@licenses +@lgpl + + +*/ + +// DOXYGEN_REFERENCES = kdecore +// DOXYGEN_SET_PROJECT_NAME = KDEsu +// vim:ts=4:sw=4:expandtab:filetype=doxygen diff --git a/kdesu/Makefile.am b/kdesu/Makefile.am new file mode 100644 index 000000000..211d8d422 --- /dev/null +++ b/kdesu/Makefile.am @@ -0,0 +1,30 @@ +## Makefile.am for libkdesu + +INCLUDES = -I$(top_srcdir)/kio/ $(all_includes) + +lib_LTLIBRARIES = libkdesu.la +libkdesu_la_SOURCES = client.cpp process.cpp kcookie.cpp su.cpp ssh.cpp stub.cpp kdesu_pty.cpp +libkdesu_la_LDFLAGS = -version-info 6:0:2 -no-undefined $(all_libraries) +libkdesu_la_LIBADD = $(LIB_KDECORE) $(LIBUTIL) +libkdesu_la_NMCHECK = $(srcdir)/libkdesu.nmcheck +libkdesu_la_NMCHECKWEAK = $(srcdir)/libkdesu_weak.nmcheck $(top_srcdir)/kdecore/libkdecore_weak.nmcheck \ + $(top_srcdir)/dcop/libDCOP_weak.nmcheck $(top_srcdir)/kdecore/libqt-mt_weak.nmcheck + +kdesudir = $(includedir)/kdesu +kdesu_HEADERS = defaults.h client.h process.h kdesu_pty.h kcookie.h su.h ssh.h stub.h + +install-data-hook: + $(mkinstalldirs) $(DESTDIR)$(kdesudir) + -rm -f $(DESTDIR)$(kdesudir)/pty.h + ln -s kdesu_pty.h $(DESTDIR)$(kdesudir)/pty.h + +uninstall-local: + -rm -f $(DESTDIR)$(kdesudir)/pty.h + +bin_PROGRAMS = kdesu_stub +kdesu_stub_SOURCES = kdesu_stub.c +kdesu_stub_LDFLAGS = $(all_libraries) +kdesu_stub_LDADD = ../kdecore/libkdefakes.la + +include $(top_srcdir)/admin/Doxyfile.am + diff --git a/kdesu/README b/kdesu/README new file mode 100644 index 000000000..0a6acd9d3 --- /dev/null +++ b/kdesu/README @@ -0,0 +1,13 @@ +Maintainer: Adriaan de Groot <groot@kde.org> +Maintainer: kdesu was maintained by Alan Eldridge until his + death in 2003. + +README for libkdesu. + +Libkdesu provides functionality for building GUI front ends for +(password asking) console mode programs. For example, kdesu and +kdessh use it to interface with su and ssh respectively. + +libkdesu is Copyright (c) 1999,2000 Geert Jansen <jansen@kde.org> + +Distributed under the GNU Library General Public License, version 2. diff --git a/kdesu/client.cpp b/kdesu/client.cpp new file mode 100644 index 000000000..ae97f0af8 --- /dev/null +++ b/kdesu/client.cpp @@ -0,0 +1,435 @@ +/* vi: ts=8 sts=4 sw=4 + * + * $Id$ + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * This is free software; you can use this library under the GNU Library + * General Public License, version 2. See the file "COPYING.LIB" for the + * exact licensing terms. + * + * client.cpp: A client for kdesud. + */ + +#include <config.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <pwd.h> +#include <errno.h> +#include <string.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/stat.h> + +#include <qglobal.h> +#include <qcstring.h> +#include <qfile.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <kstandarddirs.h> +#include <kapplication.h> +#include <kde_file.h> + +#include "client.h" + +class KDEsuClient::KDEsuClientPrivate { +public: + QString daemon; +}; + +#ifndef SUN_LEN +#define SUN_LEN(ptr) ((socklen_t) (((struct sockaddr_un *) 0)->sun_path) \ + + strlen ((ptr)->sun_path)) +#endif + +KDEsuClient::KDEsuClient() +{ + sockfd = -1; +#ifdef Q_WS_X11 + QCString display(getenv("DISPLAY")); + if (display.isEmpty()) + { + kdWarning(900) << k_lineinfo << "$DISPLAY is not set\n"; + return; + } + + // strip the screen number from the display + display.replace(QRegExp("\\.[0-9]+$"), ""); +#else + QCString display("QWS"); +#endif + + sock = QFile::encodeName(locateLocal("socket", QString("kdesud_%1").arg(display))); + d = new KDEsuClientPrivate; + connect(); +} + + +KDEsuClient::~KDEsuClient() +{ + delete d; + if (sockfd >= 0) + close(sockfd); +} + +int KDEsuClient::connect() +{ + if (sockfd >= 0) + close(sockfd); + if (access(sock, R_OK|W_OK)) + { + sockfd = -1; + return -1; + } + + sockfd = socket(PF_UNIX, SOCK_STREAM, 0); + if (sockfd < 0) + { + kdWarning(900) << k_lineinfo << "socket(): " << perror << "\n"; + return -1; + } + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, sock); + + if (::connect(sockfd, (struct sockaddr *) &addr, SUN_LEN(&addr)) < 0) + { + kdWarning(900) << k_lineinfo << "connect():" << perror << endl; + close(sockfd); sockfd = -1; + return -1; + } + +#if !defined(SO_PEERCRED) || !defined(HAVE_STRUCT_UCRED) +# if defined(HAVE_GETPEEREID) + uid_t euid; + gid_t egid; + // Security: if socket exists, we must own it + if (getpeereid(sockfd, &euid, &egid) == 0) + { + if (euid != getuid()) + { + kdWarning(900) << "socket not owned by me! socket uid = " << euid << endl; + close(sockfd); sockfd = -1; + return -1; + } + } +# else +# ifdef __GNUC__ +# warning "Using sloppy security checks" +# endif + // We check the owner of the socket after we have connected. + // If the socket was somehow not ours an attacker will be able + // to delete it after we connect but shouldn't be able to + // create a socket that is owned by us. + KDE_struct_stat s; + if (KDE_lstat(sock, &s)!=0) + { + kdWarning(900) << "stat failed (" << sock << ")" << endl; + close(sockfd); sockfd = -1; + return -1; + } + if (s.st_uid != getuid()) + { + kdWarning(900) << "socket not owned by me! socket uid = " << s.st_uid << endl; + close(sockfd); sockfd = -1; + return -1; + } + if (!S_ISSOCK(s.st_mode)) + { + kdWarning(900) << "socket is not a socket (" << sock << ")" << endl; + close(sockfd); sockfd = -1; + return -1; + } +# endif +#else + struct ucred cred; + socklen_t siz = sizeof(cred); + + // Security: if socket exists, we must own it + if (getsockopt(sockfd, SOL_SOCKET, SO_PEERCRED, &cred, &siz) == 0) + { + if (cred.uid != getuid()) + { + kdWarning(900) << "socket not owned by me! socket uid = " << cred.uid << endl; + close(sockfd); sockfd = -1; + return -1; + } + } +#endif + + return 0; +} + +QCString KDEsuClient::escape(const QCString &str) +{ + QCString copy = str; + int n = 0; + while ((n = copy.find("\\", n)) != -1) + { + copy.insert(n, '\\'); + n += 2; + } + n = 0; + while ((n = copy.find("\"", n)) != -1) + { + copy.insert(n, '\\'); + n += 2; + } + copy.prepend("\""); + copy.append("\""); + return copy; +} + +int KDEsuClient::command(const QCString &cmd, QCString *result) +{ + if (sockfd < 0) + return -1; + + if (send(sockfd, cmd, cmd.length(), 0) != (int) cmd.length()) + return -1; + + char buf[1024]; + int nbytes = recv(sockfd, buf, 1023, 0); + if (nbytes <= 0) + { + kdWarning(900) << k_lineinfo << "no reply from daemon\n"; + return -1; + } + buf[nbytes] = '\000'; + + QCString reply = buf; + if (reply.left(2) != "OK") + return -1; + + if (result) + *result = reply.mid(3, reply.length()-4); + return 0; +} + +int KDEsuClient::setPass(const char *pass, int timeout) +{ + QCString cmd = "PASS "; + cmd += escape(pass); + cmd += " "; + cmd += QCString().setNum(timeout); + cmd += "\n"; + return command(cmd); +} + +int KDEsuClient::exec(const QCString &prog, const QCString &user, const QCString &options, const QCStringList &env) +{ + QCString cmd; + cmd = "EXEC "; + cmd += escape(prog); + cmd += " "; + cmd += escape(user); + if (!options.isEmpty() || !env.isEmpty()) + { + cmd += " "; + cmd += escape(options); + for(QCStringList::ConstIterator it = env.begin(); + it != env.end(); ++it) + { + cmd += " "; + cmd += escape(*it); + } + } + cmd += "\n"; + return command(cmd); +} + +int KDEsuClient::setHost(const QCString &host) +{ + QCString cmd = "HOST "; + cmd += escape(host); + cmd += "\n"; + return command(cmd); +} + +int KDEsuClient::setPriority(int prio) +{ + QCString cmd; + cmd.sprintf("PRIO %d\n", prio); + return command(cmd); +} + +int KDEsuClient::setScheduler(int sched) +{ + QCString cmd; + cmd.sprintf("SCHD %d\n", sched); + return command(cmd); +} + +int KDEsuClient::delCommand(const QCString &key, const QCString &user) +{ + QCString cmd = "DEL "; + cmd += escape(key); + cmd += " "; + cmd += escape(user); + cmd += "\n"; + return command(cmd); +} +int KDEsuClient::setVar(const QCString &key, const QCString &value, int timeout, + const QCString &group) +{ + QCString cmd = "SET "; + cmd += escape(key); + cmd += " "; + cmd += escape(value); + cmd += " "; + cmd += escape(group); + cmd += " "; + cmd += QCString().setNum(timeout); + cmd += "\n"; + return command(cmd); +} + +QCString KDEsuClient::getVar(const QCString &key) +{ + QCString cmd = "GET "; + cmd += escape(key); + cmd += "\n"; + QCString reply; + command(cmd, &reply); + return reply; +} + +QValueList<QCString> KDEsuClient::getKeys(const QCString &group) +{ + QCString cmd = "GETK "; + cmd += escape(group); + cmd += "\n"; + QCString reply; + command(cmd, &reply); + int index=0, pos; + QValueList<QCString> list; + if( !reply.isEmpty() ) + { + // kdDebug(900) << "Found a matching entry: " << reply << endl; + while (1) + { + pos = reply.find( '\007', index ); + if( pos == -1 ) + { + if( index == 0 ) + list.append( reply ); + else + list.append( reply.mid(index) ); + break; + } + else + { + list.append( reply.mid(index, pos-index) ); + } + index = pos+1; + } + } + return list; +} + +bool KDEsuClient::findGroup(const QCString &group) +{ + QCString cmd = "CHKG "; + cmd += escape(group); + cmd += "\n"; + if( command(cmd) == -1 ) + return false; + return true; +} + +int KDEsuClient::delVar(const QCString &key) +{ + QCString cmd = "DELV "; + cmd += escape(key); + cmd += "\n"; + return command(cmd); +} + +int KDEsuClient::delGroup(const QCString &group) +{ + QCString cmd = "DELG "; + cmd += escape(group); + cmd += "\n"; + return command(cmd); +} + +int KDEsuClient::delVars(const QCString &special_key) +{ + QCString cmd = "DELS "; + cmd += escape(special_key); + cmd += "\n"; + return command(cmd); +} + +int KDEsuClient::ping() +{ + return command("PING\n"); +} + +int KDEsuClient::exitCode() +{ + QCString result; + if (command("EXIT\n", &result) != 0) + return -1; + + return result.toLong(); +} + +int KDEsuClient::stopServer() +{ + return command("STOP\n"); +} + +static QString findDaemon() +{ + QString daemon = locate("bin", "kdesud"); + if (daemon.isEmpty()) // if not in KDEDIRS, rely on PATH + daemon = KStandardDirs::findExe("kdesud"); + + if (daemon.isEmpty()) + { + kdWarning(900) << k_lineinfo << "daemon not found\n"; + } + return daemon; +} + +bool KDEsuClient::isServerSGID() +{ + if (d->daemon.isEmpty()) + d->daemon = findDaemon(); + if (d->daemon.isEmpty()) + return false; + + KDE_struct_stat sbuf; + if (KDE_stat(QFile::encodeName(d->daemon), &sbuf) < 0) + { + kdWarning(900) << k_lineinfo << "stat(): " << perror << "\n"; + return false; + } + return (sbuf.st_mode & S_ISGID); +} + +int KDEsuClient::startServer() +{ + if (d->daemon.isEmpty()) + d->daemon = findDaemon(); + if (d->daemon.isEmpty()) + return -1; + + if (!isServerSGID()) { + kdWarning(900) << k_lineinfo << "kdesud not setgid!\n"; + } + + // kdesud only forks to the background after it is accepting + // connections. + // We start it via kdeinit to make sure that it doesn't inherit + // any fd's from the parent process. + int ret = kapp->kdeinitExecWait(d->daemon); + connect(); + return ret; +} diff --git a/kdesu/client.h b/kdesu/client.h new file mode 100644 index 000000000..00957b1ed --- /dev/null +++ b/kdesu/client.h @@ -0,0 +1,207 @@ +/* vi: ts=8 sts=4 sw=4 + * + * $Id$ + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * This is free software; you can use this library under the GNU Library + * General Public License, version 2. See the file "COPYING.LIB" for the + * exact licensing terms. + * + * client.h: client to access kdesud. + */ + +#ifndef __KDE_su_Client_h_Included__ +#define __KDE_su_Client_h_Included__ + +#include <qglobal.h> +#include <kdelibs_export.h> + +#ifdef Q_OS_UNIX + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <qcstring.h> +#include <qvaluelist.h> + +typedef QValueList<QCString> QCStringList; + +/** + * A client class to access kdesud, the KDE su daemon. Kdesud can assist in + * password caching in two ways: + * + * @li For high security passwords, like for su and ssh, it executes the + * password requesting command for you. It feeds the password to the + * command, without ever returning it to you, the user. The daemon should + * be installed setgid nogroup, in order to be able to act as an inaccessible, + * trusted 3rd party. + * See exec, setPass, delCommand. + * + * @li For lower security passwords, like web and ftp passwords, it can act + * as a persistent storage for string variables. These variables are + * returned to the user, and the daemon doesn't need to be setgid nogroup + * for this. + * See setVar, delVar, delGroup. + */ + +class KDESU_EXPORT KDEsuClient { +public: + KDEsuClient(); + ~KDEsuClient(); + + /** + * Lets kdesud execute a command. If the daemon does not have a password + * for this command, this will fail and you need to call setPass(). + * + * @param command The command to execute. + * @param user The user to run the command as. + * @param options Extra options. + * @param env Extra environment variables. + * @return Zero on success, -1 on failure. + */ + int exec(const QCString &command, const QCString &user, const QCString &options=0, const QCStringList &env=QCStringList()); + + /** + * Wait for the last command to exit and return the exit code. + * @return Exit code of last command, -1 on failure. + */ + int exitCode(); + + /** + * Set root's password, lasts one session. + * + * @param pass Root's password. + * @param timeout The time that a password will live. + * @return Zero on success, -1 on failure. + */ + int setPass(const char *pass, int timeout); + + /** + * Set the target host (optional). + */ + int setHost(const QCString &host); + + /** + * Set the desired priority (optional), see StubProcess. + */ + int setPriority(int priority); + + /** + * Set the desired scheduler (optional), see StubProcess. + */ + int setScheduler(int scheduler); + + /** + * Remove a password for a user/command. + * @param command The command. + * @param user The user. + * @return zero on success, -1 on an error + */ + int delCommand(const QCString &command, const QCString &user); + + /** + * Set a persistent variable. + * @param key The name of the variable. + * @param value Its value. + * @param timeout The timeout in seconds for this key. Zero means + * no timeout. + * @param group Make the key part of a group. See delGroup. + * @return zero on success, -1 on failure. + */ + int setVar(const QCString &key, const QCString &value, int timeout=0, const QCString &group=0); + + /** + * Get a persistent variable. + * @param key The name of the variable. + * @return Its value. + */ + QCString getVar(const QCString &key); + + /** + * Gets all the keys that are membes of the given group. + * @param group the group name of the variables. + * @return a list of the keys in the group. + */ + QValueList<QCString> getKeys(const QCString &group); + + /** + * Returns true if the specified group exists is + * cached. + * + * @param group the group key + * @return true if the group is found + */ + bool findGroup(const QCString &group); + + /** + * Delete a persistent variable. + * @param key The name of the variable. + * @return zero on success, -1 on failure. + */ + int delVar(const QCString &key); + + /** + * Delete all persistent variables with the given key. + * + * A specicalized variant of delVar(QCString) that removes all + * subsets of the cached varaibles given by @p key. In order for all + * cached variables related to this key to be deleted properly, the + * value given to the @p group argument when the setVar function + * was called, must be a subset of the argument given here and the key + * + * @note Simply supplying the group key here WILL not necessarily + * work. If you only have a group key, then use delGroup instead. + * + * @param special_key the name of the variable. + * @return zero on success, -1 on failure. + */ + int delVars(const QCString &special_key); + + /** + * Delete all persistent variables in a group. + * + * @param group the group name. See setVar. + * @return + */ + int delGroup(const QCString &group); + + /** + * Ping kdesud. This can be used for diagnostics. + * @return Zero on success, -1 on failure + */ + int ping(); + + /** + * Stop the daemon. + */ + int stopServer(); + + /** + * Try to start up kdesud + */ + int startServer(); + + /** + * Returns true if the server is safe (installed setgid), false otherwise. + */ + bool isServerSGID(); + +private: + int connect(); + + int sockfd; + QCString sock; + + int command(const QCString &cmd, QCString *result=0L); + QCString escape(const QCString &str); + + class KDEsuClientPrivate; + KDEsuClientPrivate *d; +}; + +#endif //Q_OS_UNIX + +#endif //__KDE_su_Client_h_Included__ diff --git a/kdesu/configure.in.in b/kdesu/configure.in.in new file mode 100644 index 000000000..4b4c6b78f --- /dev/null +++ b/kdesu/configure.in.in @@ -0,0 +1,93 @@ +dnl Check for su +AC_PATH_PROG(path_su, "su", "no") +if test "$path_su" = "no"; then + AC_MSG_WARN(su was not found) +else + AC_DEFINE_UNQUOTED(__PATH_SU, "$path_su", [path to su]) +fi + +dnl Check for sudo +AC_PATH_PROG(path_sudo, "sudo", "no") +if test "$path_sudo" = "no"; then + AC_MSG_WARN(sudo was not found) +else + AC_DEFINE_UNQUOTED(__PATH_SUDO, "$path_sudo", [path to sudo]) +fi + +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" +) + +if test x$use_kdesu_backend = xsudo -a x$path_sudo = xno; then + AC_MSG_ERROR(sudo was chosen as kdesu backend, but was not found in path.) +fi + +AC_DEFINE_UNQUOTED(DEFAULT_SUPER_USER_COMMAND, "$use_kdesu_backend", [Use su or sudo]) + +dnl Check for POSIX.1b scheduling +AC_MSG_CHECKING([POSIX.1b scheduling]) +AC_TRY_LINK([ + #include <sched.h> +], +[ + sched_getscheduler(0); +], +have_rtsched="yes", have_rtsched="no") +if test "$have_rtsched" = "yes"; then + AC_DEFINE(POSIX1B_SCHEDULING, 1, [Define if you have POSIX.1b scheduling]) + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi + +dnl Check for initgroups() +AC_CHECK_FUNCS(initgroups) + +dnl openpty stuff +AC_CHECK_HEADERS(libutil.h util.h pty.h) +AC_CHECK_LIB(util, openpty, [AC_DEFINE_UNQUOTED(HAVE_OPENPTY, 1, [Define if you have openpty in -lutil])]) +AC_CHECK_FUNCS(openpty initgroups setgroups getgroups grantpt setpriority getpt unlockpt ptsname) + +AH_VERBATIM(_OPENPTY, +[ +/* + * Steven Schultz <sms at to.gd-es.com> tells us : + * BSD/OS 4.2 doesn't have a prototype for openpty in its system header files + */ +#ifdef __bsdi__ +__BEGIN_DECLS +int openpty(int *, int *, char *, struct termios *, struct winsize *); +__END_DECLS +#endif +]) + +dnl irix pty stuff +AC_CHECK_FUNCS(_getpty) + +AC_MSG_CHECKING([for struct ucred]) +AC_CACHE_VAL(kde_cv_have_struct_ucred, +[ + AC_TRY_COMPILE( + [ + #include <sys/socket.h> + ], + [ + struct ucred cred; + ], kde_cv_have_struct_ucred=yes, + kde_cv_have_struct_ucred=no) +]) + +AC_MSG_RESULT($kde_cv_have_struct_ucred) +if test "$kde_cv_have_struct_ucred" = yes; then + AC_DEFINE(HAVE_STRUCT_UCRED,1, [Define if struct ucred is present from sys/socket.h]) +fi +AC_CHECK_FUNCS(getpeereid) diff --git a/kdesu/defaults.h b/kdesu/defaults.h new file mode 100644 index 000000000..7610985c2 --- /dev/null +++ b/kdesu/defaults.h @@ -0,0 +1,20 @@ +/* vi: ts=8 sts=4 sw=4 + * + * $Id$ + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * This is free software; you can use this library under the GNU Library + * General Public License, version 2. See the file "COPYING.LIB" for the + * exact licensing terms. + */ + +#ifndef __Defaults_h_included__ +#define __Defaults_h_included__ + +const int defTimeout = 120*60; +const int defEchoMode = 0; +const int defKeep = false; + +#endif diff --git a/kdesu/kcookie.cpp b/kdesu/kcookie.cpp new file mode 100644 index 000000000..355dad82e --- /dev/null +++ b/kdesu/kcookie.cpp @@ -0,0 +1,225 @@ +/* vi: ts=8 sts=4 sw=4 + * + * $Id$ + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * This is free software; you can use this library under the GNU Library + * General Public License, version 2. See the file "COPYING.LIB" for the + * exact licensing terms. + * + * kcookie.cpp: KDE authentication cookies. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> + +#include <qstring.h> +#include <qstringlist.h> +#include <qglobal.h> +#include <qfile.h> + +#include <dcopclient.h> + +#include <kdebug.h> +#include <kprocess.h> +#include "kcookie.h" + + +KCookie::KCookie() +{ +#ifdef Q_WS_X11 + getXCookie(); +#endif + setDcopTransport("local"); +} + +void KCookie::setDcopTransport(const QCString &dcopTransport) +{ + m_dcopTransport = dcopTransport; + m_bHaveDCOPCookies = false; + m_bHaveICECookies = false; + m_DCOPSrv = ""; + m_DCOPAuth = ""; + m_ICEAuth = ""; +} + +QCStringList KCookie::split(const QCString &line, char ch) +{ + QCStringList result; + + int i=0, pos; + while ((pos = line.find(ch, i)) != -1) + { + result += line.mid(i, pos-i); + i = pos+1; + } + if (i < (int) line.length()) + result += line.mid(i); + return result; +} + +void KCookie::blockSigChild() +{ + sigset_t sset; + sigemptyset(&sset); + sigaddset(&sset, SIGCHLD); + sigprocmask(SIG_BLOCK, &sset, 0L); +} + +void KCookie::unblockSigChild() +{ + sigset_t sset; + sigemptyset(&sset); + sigaddset(&sset, SIGCHLD); + sigprocmask(SIG_UNBLOCK, &sset, 0L); +} + +void KCookie::getXCookie() +{ + char buf[1024]; + FILE *f; + +#ifdef Q_WS_X11 + m_Display = getenv("DISPLAY"); +#else + m_Display = getenv("QWS_DISPLAY"); +#endif + if (m_Display.isEmpty()) + { + kdError(900) << k_lineinfo << "$DISPLAY is not set.\n"; + return; + } +#ifdef Q_WS_X11 // No need to mess with X Auth stuff + QCString disp = m_Display; + if (!memcmp(disp.data(), "localhost:", 10)) + disp.remove(0, 9); + + QString cmd = "xauth list "+KProcess::quote(disp); + blockSigChild(); // pclose uses waitpid() + if (!(f = popen(QFile::encodeName(cmd), "r"))) + { + kdError(900) << k_lineinfo << "popen(): " << perror << "\n"; + unblockSigChild(); + return; + } + QCString output = fgets(buf, 1024, f); + if (pclose(f) < 0) + { + kdError(900) << k_lineinfo << "Could not run xauth.\n"; + unblockSigChild(); + return; + } + unblockSigChild(); + output = output.simplifyWhiteSpace(); + if (output.isEmpty()) + { + kdWarning(900) << "No X authentication info set for display " << + m_Display << endl; return; + } + QCStringList lst = split(output, ' '); + if (lst.count() != 3) + { + kdError(900) << k_lineinfo << "parse error.\n"; + return; + } + m_DisplayAuth = (lst[1] + ' ' + lst[2]); +#endif +} + +void KCookie::getICECookie() +{ + FILE *f; + char buf[1024]; + + QCString dcopsrv = getenv("DCOPSERVER"); + if (dcopsrv.isEmpty()) + { + QCString dcopFile = DCOPClient::dcopServerFile(); + if (!(f = fopen(dcopFile, "r"))) + { + kdWarning(900) << k_lineinfo << "Cannot open " << dcopFile << ".\n"; + return; + } + dcopsrv = fgets(buf, 1024, f); + dcopsrv = dcopsrv.stripWhiteSpace(); + fclose(f); + } + QCStringList dcopServerList = split(dcopsrv, ','); + if (dcopServerList.isEmpty()) + { + kdError(900) << k_lineinfo << "No DCOP servers found.\n"; + return; + } + + QCStringList::Iterator it; + for (it=dcopServerList.begin(); it != dcopServerList.end(); ++it) + { + if (strncmp((*it).data(), m_dcopTransport.data(), m_dcopTransport.length()) != 0) + continue; + m_DCOPSrv = *it; + QCString cmd = DCOPClient::iceauthPath()+" list netid="+QFile::encodeName(KProcess::quote(m_DCOPSrv)); + blockSigChild(); + if (!(f = popen(cmd, "r"))) + { + kdError(900) << k_lineinfo << "popen(): " << perror << "\n"; + unblockSigChild(); + break; + } + QCStringList output; + while (fgets(buf, 1024, f)) + output += buf; + if (pclose(f) < 0) + { + kdError(900) << k_lineinfo << "Could not run iceauth.\n"; + unblockSigChild(); + break; + } + unblockSigChild(); + QCStringList::Iterator it2; + for (it2=output.begin(); it2!=output.end(); ++it2) + { + QCStringList lst = split((*it2).simplifyWhiteSpace(), ' '); + if (lst.count() != 5) + { + kdError(900) << "parse error.\n"; + break; + } + if (lst[0] == "DCOP") + m_DCOPAuth = (lst[3] + ' ' + lst[4]); + else if (lst[0] == "ICE") + m_ICEAuth = (lst[3] + ' ' + lst[4]); + else + kdError(900) << k_lineinfo << "unknown protocol: " << lst[0] << "\n"; + } + break; + } + m_bHaveDCOPCookies = true; + m_bHaveICECookies = true; +} + +QCString KCookie::dcopServer() +{ + if (!m_bHaveDCOPCookies) + getICECookie(); + return m_DCOPSrv; +} + +QCString KCookie::dcopAuth() +{ + if (!m_bHaveDCOPCookies) + getICECookie(); + return m_DCOPAuth; +} + +QCString KCookie::iceAuth() +{ + if (!m_bHaveICECookies) + getICECookie(); + return m_ICEAuth; +} diff --git a/kdesu/kcookie.h b/kdesu/kcookie.h new file mode 100644 index 000000000..ed1cbf1d2 --- /dev/null +++ b/kdesu/kcookie.h @@ -0,0 +1,90 @@ +/* vi: ts=8 sts=4 sw=4 + * + * $Id$ + * + * This file is part of the KDE project, module kdesu + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * This is free software; you can use this library under the GNU Library + * General Public License, version 2. See the file "COPYING.LIB" for the + * exact licensing terms. + */ + +#ifndef __KCookie_h_Included__ +#define __KCookie_h_Included__ + +#include <qcstring.h> +#include <qvaluelist.h> + +typedef QValueList<QCString> QCStringList; + + +/** + * Utility class to access the authentication tokens needed to run a KDE + * program (X11 and DCOP cookies). + */ + +class KDESU_EXPORT KCookie +{ +public: + KCookie(); + + /** + * Returns the X11 display. + */ + QCString display() { return m_Display; } + +#ifdef Q_WS_X11 + /** + * Returns the X11 magic cookie, if available. + */ + QCString displayAuth() { return m_DisplayAuth; } +#endif + + /** + * Select the DCOP transport to look for. Default: "local" + */ + void setDcopTransport(const QCString &dcopTransport); + + /** + * Returns the netid where the dcopserver is running + */ + QCString dcopServer(); + + /** + * Returns a list of magic cookies for DCOP protocol authentication. + * The order is the same as in dcopServer(). + */ + QCString dcopAuth(); + + /** + * Returns a list of magic cookies for the ICE protocol. + */ + QCString iceAuth(); + +private: + void getXCookie(); + void getICECookie(); + QCStringList split(const QCString &line, char ch); + + void blockSigChild(); + void unblockSigChild(); + + bool m_bHaveDCOPCookies; + bool m_bHaveICECookies; + + QCString m_Display; +#ifdef Q_WS_X11 + QCString m_DisplayAuth; +#endif + QCString m_DCOPSrv; + QCString m_DCOPAuth; + QCString m_ICEAuth; + QCString m_dcopTransport; + + class KCookiePrivate; + KCookiePrivate *d; +}; + + +#endif // __KCookie_h_Included__ diff --git a/kdesu/kdesu_pty.cpp b/kdesu/kdesu_pty.cpp new file mode 100644 index 000000000..3b0499f58 --- /dev/null +++ b/kdesu/kdesu_pty.cpp @@ -0,0 +1,302 @@ +/* vi: ts=8 sts=4 sw=4 + * + * $Id$ + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * This file contains code from TEShell.C of the KDE konsole. + * Copyright (c) 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + * + * This is free software; you can use this library under the GNU Library + * General Public License, version 2. See the file "COPYING.LIB" for the + * exact licensing terms. + * + * pty.cpp: Access to PTY's on different systems a la UNIX98. + */ + + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* Needed for getpt, ptsname in glibc 2.1.x systems */ +#endif + +#include <config.h> + +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/ioctl.h> +#if defined(__osf__) || defined(__CYGWIN__) +#include <pty.h> +#endif + +#include <qglobal.h> +#include <qcstring.h> + +#include <kdebug.h> +#include <kstandarddirs.h> +#include "kdesu_pty.h" + +// stdlib.h is meant to declare the prototypes but doesn't :( +#ifndef __THROW +#define __THROW +#endif + +#ifdef HAVE_GRANTPT +extern "C" int grantpt(int fd) __THROW; +#endif + +#ifdef HAVE_PTSNAME +extern "C" char * ptsname(int fd) __THROW; +#endif + +#ifdef HAVE_UNLOCKPT +extern "C" int unlockpt(int fd) __THROW; +#endif + +#ifdef HAVE__GETPTY +extern "C" char *_getpty(int *, int, mode_t, int); +#endif + +#ifdef HAVE_PTY_H + #include <pty.h> +#endif + +#include <termios.h> + +#ifdef HAVE_LIBUTIL_H + #include <libutil.h> +#elif defined(HAVE_UTIL_H) + #include <util.h> +#endif + +PTY::PTY() +{ + ptyfd = -1; +} + +PTY::~PTY() +{ + if (ptyfd >= 0) + close(ptyfd); +} + + +// Opens a pty master and returns its filedescriptor. + +int PTY::getpt() +{ + +#if defined(HAVE_GETPT) && defined(HAVE_PTSNAME) + + // 1: UNIX98: preferred way + ptyfd = ::getpt(); + ttyname = ::ptsname(ptyfd); + return ptyfd; + +#elif defined(HAVE_OPENPTY) + // 2: BSD interface + // More preferred than the linux hacks + char name[30]; + int master_fd, slave_fd; + if (openpty(&master_fd, &slave_fd, name, 0L, 0L) != -1) { + ttyname = name; + name[5]='p'; + ptyname = name; + close(slave_fd); // We don't need this yet // Yes, we do. + ptyfd = master_fd; + return ptyfd; + } + ptyfd = -1; + kdDebug(900) << k_lineinfo << "Opening pty failed.\n"; + return -1; + +#elif defined(HAVE__GETPTY) + // 3: Irix interface + int master_fd; + ttyname = _getpty(&master_fd,O_RDWR,0600,0); + if (ttyname) + ptyfd = master_fd; + else{ + ptyfd = -1; + kdDebug(900) << k_lineinfo << "Opening pty failed.error" << errno << '\n'; + } + return ptyfd; + +#else + + // 4: Open terminal device directly + // 4.1: Try /dev/ptmx first. (Linux w/ Unix98 PTYs, Solaris) + + ptyfd = open("/dev/ptmx", O_RDWR); + if (ptyfd >= 0) { + ptyname = "/dev/ptmx"; +#ifdef HAVE_PTSNAME + ttyname = ::ptsname(ptyfd); + return ptyfd; +#elif defined (TIOCGPTN) + int ptyno; + if (ioctl(ptyfd, TIOCGPTN, &ptyno) == 0) { + ttyname.sprintf("/dev/pts/%d", ptyno); + return ptyfd; + } +#endif + close(ptyfd); + } + + // 4.2: Try /dev/pty[p-e][0-f] (Linux w/o UNIX98 PTY's) + + for (const char *c1 = "pqrstuvwxyzabcde"; *c1 != '\0'; c1++) + { + for (const char *c2 = "0123456789abcdef"; *c2 != '\0'; c2++) + { + ptyname.sprintf("/dev/pty%c%c", *c1, *c2); + ttyname.sprintf("/dev/tty%c%c", *c1, *c2); + if (access(ptyname, F_OK) < 0) + goto linux_out; + ptyfd = open(ptyname, O_RDWR); + if (ptyfd >= 0) + return ptyfd; + } + } +linux_out: + + // 4.3: Try /dev/pty%d (SCO, Unixware) + + for (int i=0; i<256; i++) + { + ptyname.sprintf("/dev/ptyp%d", i); + ttyname.sprintf("/dev/ttyp%d", i); + if (access(ptyname, F_OK) < 0) + break; + ptyfd = open(ptyname, O_RDWR); + if (ptyfd >= 0) + return ptyfd; + } + + + // Other systems ?? + ptyfd = -1; + kdDebug(900) << k_lineinfo << "Unknown system or all methods failed.\n"; + return -1; + +#endif // HAVE_GETPT && HAVE_PTSNAME + +} + + +int PTY::grantpt() +{ + if (ptyfd < 0) + return -1; + +#ifdef HAVE_GRANTPT + + return ::grantpt(ptyfd); + +#elif defined(HAVE_OPENPTY) + + // the BSD openpty() interface chowns the devices properly for us, + // no need to do this at all + return 0; + +#else + + // konsole_grantpty only does /dev/pty?? + if (ptyname.left(8) != "/dev/pty") + return 0; + + // Use konsole_grantpty: + if (KStandardDirs::findExe("konsole_grantpty").isEmpty()) + { + kdError(900) << k_lineinfo << "konsole_grantpty not found.\n"; + return -1; + } + + // As defined in konsole_grantpty.c + const int pty_fileno = 3; + + pid_t pid; + if ((pid = fork()) == -1) + { + kdError(900) << k_lineinfo << "fork(): " << perror << "\n"; + return -1; + } + + if (pid) + { + // Parent: wait for child + int ret; + waitpid(pid, &ret, 0); + if (WIFEXITED(ret) && !WEXITSTATUS(ret)) + return 0; + kdError(900) << k_lineinfo << "konsole_grantpty returned with error: " + << WEXITSTATUS(ret) << "\n"; + return -1; + } else + { + // Child: exec konsole_grantpty + if (ptyfd != pty_fileno && dup2(ptyfd, pty_fileno) < 0) + _exit(1); + execlp("konsole_grantpty", "konsole_grantpty", "--grant", (void *)0); + kdError(900) << k_lineinfo << "exec(): " << perror << "\n"; + _exit(1); + } + + // shut up, gcc + return 0; + +#endif // HAVE_GRANTPT +} + + +/** + * Unlock the pty. This allows connections on the slave side. + */ + +int PTY::unlockpt() +{ + if (ptyfd < 0) + return -1; + +#ifdef HAVE_UNLOCKPT + + // (Linux w/ glibc 2.1, Solaris, ...) + + return ::unlockpt(ptyfd); + +#elif defined(TIOCSPTLCK) + + // Unlock pty (Linux w/ UNIX98 PTY's & glibc 2.0) + int flag = 0; + return ioctl(ptyfd, TIOCSPTLCK, &flag); + +#else + + // Other systems (Linux w/o UNIX98 PTY's, ...) + return 0; + +#endif + +} + + +/** + * Return the slave side name. + */ + +QCString PTY::ptsname() +{ + if (ptyfd < 0) + return 0; + + return ttyname; +} + diff --git a/kdesu/kdesu_pty.h b/kdesu/kdesu_pty.h new file mode 100644 index 000000000..c9eadceda --- /dev/null +++ b/kdesu/kdesu_pty.h @@ -0,0 +1,71 @@ +/* vi: ts=8 sts=4 sw=4 + * + * $Id$ + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * This is free software; you can use this library under the GNU Library + * General Public License, version 2. See the file "COPYING.LIB" for the + * exact licensing terms. + */ + + +/** + * PTY compatibility routines. This class tries to emulate a UNIX98 PTY API + * on various platforms. + */ +#ifndef __PTY_h_Included__ +#define __PTY_h_Included__ + +#include <qcstring.h> + +#include <kdelibs_export.h> + +class KDESU_EXPORT PTY { + +public: + /** + * Construct a PTY object. + */ + PTY(); + + /** + * Destructs the object. The PTY is closed if it is still open. + */ + ~PTY(); + + /** + * Allocate a pty. + * @return A filedescriptor to the master side. + */ + int getpt(); + + /** + * Grant access to the slave side. + * @return Zero if succesfull, < 0 otherwise. + */ + int grantpt(); + + /** + * Unlock the slave side. + * @return Zero if successful, < 0 otherwise. + */ + int unlockpt(); + + /** + * Get the slave name. + * @return The slave name. + */ + QCString ptsname(); + +private: + + int ptyfd; + QCString ptyname, ttyname; + + class PTYPrivate; + PTYPrivate *d; +}; + +#endif // __PTY_h_Included__ diff --git a/kdesu/kdesu_stub.c b/kdesu/kdesu_stub.c new file mode 100644 index 000000000..8ef7f503e --- /dev/null +++ b/kdesu/kdesu_stub.c @@ -0,0 +1,432 @@ +/* vi: ts=8 sts=4 sw=4 + * + * $Id$ + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * kdesu_stub.c: KDE su executes this stub through su or ssh. This stub in turn + * executes the target program. Before that, startup parameters + * are sent through stdin. + * + * + * Available parameters: + * + * Parameter Description Format (csl = comma separated list) + * + * - kdesu_stub Header "ok" | "stop" + * - display X11 display string + * - display_auth X11 authentication "type cookie" pair + * - dcopserver KDE dcopserver csl of netids + * - dcop_auth DCOP authentication csl of "type cookie" pairs for DCOP + * - ice_auth ICE authentication csl of "type cookie" pairs for ICE + * - command Command to run string + * - path PATH env. var string + * - build_sycoca Rebuild sycoca? "yes" | "no" + * - user Target user string + * - priority Process priority 0 <= int <= 100 + * - scheduler Process scheduler "fifo" | "normal" + * - app_startup_id DESKTOP_STARTUP_ID string + * - environment Additional envvars strings, last one is empty + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <pwd.h> +#include <termios.h> +#include <signal.h> + +#ifdef HAVE_INITGROUPS +#include <grp.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <sys/resource.h> + +#ifdef POSIX1B_SCHEDULING +#include <sched.h> +#endif + +/** + * Params sent by the peer. + */ + +struct param_struct +{ + const char *name; + char *value; +}; + +struct param_struct params[] = +{ + { "kdesu_stub", 0L }, + { "display", 0L }, + { "display_auth", 0L }, + { "dcopserver", 0L }, + { "dcop_auth", 0L }, + { "ice_auth", 0L }, + { "command", 0L }, + { "path", 0L }, + { "xwindows_only", 0L }, + { "user", 0L }, + { "priority", 0L }, + { "scheduler", 0L }, +/* obsoleted by app_startup_id { "app_start_pid", 0L } */ + { "app_startup_id", 0L } +}; + +#define P_HEADER 0 +#define P_DISPLAY 1 +#define P_DISPLAY_AUTH 2 +#define P_DCOPSERVER 3 +#define P_DCOP_AUTH 4 +#define P_ICE_AUTH 5 +#define P_COMMAND 6 +#define P_PATH 7 +#define P_XWIN_ONLY 8 +#define P_USER 9 +#define P_PRIORITY 10 +#define P_SCHEDULER 11 +#define P_APP_STARTUP_ID 12 +#define P_LAST 13 + +/* Prototypes */ +char *xmalloc(size_t); +char *xrealloc(char *ptr, int size); +int xsetenv(const char *name, const char *value); +char *xstrdup(char *src); +char **xstrsep(char *str); + +/** + * Safe malloc functions. + */ +char *xmalloc(size_t size) +{ + char *ptr = malloc(size); + if (ptr) return ptr; + perror("malloc()"); + exit(1); +} + + +char *xrealloc(char *ptr, int size) +{ + ptr = realloc(ptr, size); + if (ptr) return ptr; + perror("realloc()"); + exit(1); +} + + +/** + * Solaris does not have a setenv()... + */ +int xsetenv(const char *name, const char *value) +{ + char *s = malloc(strlen(name)+strlen(value)+2); + if (!s) return -1; + strcpy(s, name); + strcat(s, "="); + strcat(s, value); + return putenv(s); /* yes: no free()! */ +} + +/** + * Safe strdup and strip newline + */ +char *xstrdup(char *src) +{ + int len = strlen(src); + char *dst = xmalloc(len+1); + strcpy(dst, src); + if (dst[len-1] == '\n') + dst[len-1] = '\000'; + return dst; +} + +/** + * Split comma separated list. + */ +char **xstrsep(char *str) +{ + int i = 0, size = 10; + char **list = (char **) xmalloc(size * sizeof(char *)); + char *ptr = str, *nptr; + while ((nptr = strchr(ptr, ',')) != 0L) + { + if (i > size-2) + list = realloc(list, (size *= 2) * sizeof(char *)); + *nptr = '\000'; + list[i++] = ptr; + ptr = nptr+1; + } + if (*ptr != '\000') + list[i++] = ptr; + list[i] = 0L; + return list; +} + +#define BUFSIZE 8192 + +/** + * The main program + */ + +int main() +{ + char buf[BUFSIZE+1]; +#ifndef QWS + char xauthority[200]; +#endif + char iceauthority[200]; + char **host, **auth; + int i/*, res, sycoca*/, prio; + pid_t pid; + FILE *fout; + struct passwd *pw; + const char* kdesu_lc_all; + + /* Get startup parameters. */ + + for (i=0; i<P_LAST; i++) + { + printf("%s\n", params[i].name); + fflush(stdout); + if (fgets(buf, BUFSIZE, stdin) == 0L) + { + printf("end\n"); fflush(stdout); + perror("kdesu_stub: fgets()"); + exit(1); + } + params[i].value = xstrdup(buf); + /* Installation check? */ + if ((i == 0) && !strcmp(params[i].value, "stop")) + { + printf("end\n"); + exit(0); + } + } + printf("environment\n"); + fflush(stdout); + for(;;) + { + char* tmp; + if (fgets(buf, BUFSIZE, stdin) == 0L) + { + printf("end\n"); fflush(stdout); + perror("kdesu_stub: fgets()"); + exit(1); + } + tmp = xstrdup( buf ); + if( tmp[ 0 ] == '\0' ) /* terminator */ + break; + putenv( xstrdup( buf )); + } + + printf("end\n"); + fflush(stdout); + + xsetenv("PATH", params[P_PATH].value); + xsetenv("DESKTOP_STARTUP_ID", params[P_APP_STARTUP_ID].value); + + kdesu_lc_all = getenv( "KDESU_LC_ALL" ); + if( kdesu_lc_all != NULL ) + xsetenv("LC_ALL",kdesu_lc_all); + else + unsetenv("LC_ALL"); + + /* Do we need to change uid? */ + + pw = getpwnam(params[P_USER].value); + if (pw == 0L) + { + printf("kdesu_stub: user %s does not exist!\n", params[P_USER].value); + exit(1); + } + xsetenv("HOME", pw->pw_dir); + + /* Set scheduling/priority */ + + prio = atoi(params[P_PRIORITY].value); + if (!strcmp(params[P_SCHEDULER].value, "realtime")) + { +#ifdef POSIX1B_SCHEDULING + struct sched_param sched; + int min = sched_get_priority_min(SCHED_FIFO); + int max = sched_get_priority_max(SCHED_FIFO); + sched.sched_priority = min + (int) (((double) prio) * (max - min) / 100 + 0.5); + sched_setscheduler(0, SCHED_FIFO, &sched); +#else + printf("kdesu_stub: realtime scheduling not supported\n"); +#endif + } else + { +#ifdef HAVE_SETPRIORITY + int val = 20 - (int) (((double) prio) * 40 / 100 + 0.5); + setpriority(PRIO_PROCESS, getpid(), val); +#endif + } + + /* Drop privileges (this is permanent) */ + + if (getuid() != pw->pw_uid) + { + if (setgid(pw->pw_gid) == -1) + { + perror("kdesu_stub: setgid()"); + exit(1); + } +#ifdef HAVE_INITGROUPS + if (initgroups(pw->pw_name, pw->pw_gid) == -1) + { + perror("kdesu_stub: initgroups()"); + exit(1); + } +#endif + if (setuid(pw->pw_uid) == -1) + { + perror("kdesu_stub: setuid()"); + exit(1); + } + xsetenv("HOME", pw->pw_dir); + } + + /* Handle display */ + + if (strcmp(params[P_DISPLAY].value, "no")) + { +#ifndef QWS + xsetenv("DISPLAY", params[P_DISPLAY].value); + if (params[P_DISPLAY_AUTH].value[0]) + { + int fd2; + /* + ** save umask and set to 077, so we create those files only + ** readable for root. (if someone else could read them, we + ** are in deep shit). + */ + int oldumask = umask(077); + const char *disp = params[P_DISPLAY].value; + if (strncmp(disp, "localhost:", 10) == 0) + disp += 9; + + strcpy(xauthority, "/tmp/xauth.XXXXXXXXXX"); + fd2 = mkstemp(xauthority); + umask(oldumask); + + if (fd2 == -1) { + perror("kdesu_stub: mkstemp()"); + exit(1); + } else + close(fd2); + xsetenv("XAUTHORITY", xauthority); + + fout = popen("xauth >/dev/null 2>&1","w"); + if (fout == NULL) + { + perror("kdesu_stub: popen(xauth)"); + exit(1); + } + fprintf(fout, "add %s %s\n", disp, + params[P_DISPLAY_AUTH].value); + pclose(fout); + } +#else + xsetenv("DISPLAY", params[P_DISPLAY].value); +#endif + } + + + /* Handle DCOP */ + + if (strcmp(params[P_DCOPSERVER].value, "no")) + { + xsetenv("DCOPSERVER", params[P_DCOPSERVER].value); + host = xstrsep(params[P_DCOPSERVER].value); + auth = xstrsep(params[P_ICE_AUTH].value); + if (host[0]) + { + int fd; + int oldumask = umask(077); + + strcpy(iceauthority, "/tmp/iceauth.XXXXXXXXXX"); + fd = mkstemp(iceauthority); + umask(oldumask); + if (fd == -1) { + perror("kdesu_stub: mkstemp()"); + exit(1); + } else + close(fd); + xsetenv("ICEAUTHORITY", iceauthority); + + fout = popen("iceauth >/dev/null 2>&1", "w"); + if (!fout) { + perror("kdesu_stub: popen iceauth"); + exit(1); + } + for (i=0; host[i]; i++) + fprintf(fout, "add ICE \"\" %s %s\n", host[i], auth[i]); + auth = xstrsep(params[P_DCOP_AUTH].value); + for (i=0; host[i]; i++) + fprintf(fout, "add DCOP \"\" %s %s\n", host[i], auth[i]); + pclose(fout); + } + } + + /* Rebuild the sycoca and start kdeinit? */ + + if (strcmp(params[P_XWIN_ONLY].value, "no")) + { + system("kdeinit --suicide"); + } + + /* Execute the command */ + + pid = fork(); + if (pid == -1) + { + perror("kdesu_stub: fork()"); + exit(1); + } + if (pid) + { + /* Parent: wait for child, delete tempfiles and return. */ + int ret, state, xit = 1; + while (1) + { + ret = waitpid(pid, &state, 0); + if (ret == -1) + { + if (errno == EINTR) + continue; + if (errno != ECHILD) + perror("kdesu_stub: waitpid()"); + break; + } + if (WIFEXITED(state)) + xit = WEXITSTATUS(state); + } + +#ifndef QWS + unlink(xauthority); +#endif + unlink(iceauthority); + exit(xit); + } else + { + setsid(); + /* Child: exec command. */ + sprintf(buf, "%s", params[P_COMMAND].value); + execl("/bin/sh", "sh", "-c", buf, (void *)0); + perror("kdesu_stub: exec()"); + _exit(1); + } +} diff --git a/kdesu/libkdesu.nmcheck b/kdesu/libkdesu.nmcheck new file mode 100644 index 000000000..11712f7d6 --- /dev/null +++ b/kdesu/libkdesu.nmcheck @@ -0,0 +1,10 @@ +# KDE namespace check file + +# kdesu classes +SshProcess:* +PTY::* +SuProcess::* +StubProcess::* +PtyProcess::* +KCookie::* +KDEsuClient::* diff --git a/kdesu/libkdesu_weak.nmcheck b/kdesu/libkdesu_weak.nmcheck new file mode 100644 index 000000000..6fa8e9176 --- /dev/null +++ b/kdesu/libkdesu_weak.nmcheck @@ -0,0 +1,2 @@ +# KDE namespace check file + diff --git a/kdesu/process.cpp b/kdesu/process.cpp new file mode 100644 index 000000000..bb09c309f --- /dev/null +++ b/kdesu/process.cpp @@ -0,0 +1,626 @@ +/* vi: ts=8 sts=4 sw=4 + * + * $Id$ + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * This file contains code from TEShell.C of the KDE konsole. + * Copyright (c) 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + * + * This is free software; you can use this library under the GNU Library + * General Public License, version 2. See the file "COPYING.LIB" for the + * exact licensing terms. + * + * process.cpp: Functionality to build a front end to password asking + * terminal programs. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> +#include <errno.h> +#include <string.h> +#include <termios.h> +#include <signal.h> + +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/ioctl.h> + +#if defined(__SVR4) && defined(sun) +#include <stropts.h> +#include <sys/stream.h> +#endif + +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> // Needed on some systems. +#endif + +#include <qglobal.h> +#include <qcstring.h> +#include <qfile.h> + +#include <kconfig.h> +#include <kdebug.h> +#include <kstandarddirs.h> + +#include "process.h" +#include "kdesu_pty.h" +#include "kcookie.h" + +int PtyProcess::waitMS(int fd,int ms) +{ + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 1000*ms; + + fd_set fds; + FD_ZERO(&fds); + FD_SET(fd,&fds); + return select(fd+1, &fds, 0L, 0L, &tv); +} + +/* +** Basic check for the existence of @p pid. +** Returns true iff @p pid is an extant process. +*/ +bool PtyProcess::checkPid(pid_t pid) +{ + KConfig* config = KGlobal::config(); + config->setGroup("super-user-command"); + QString superUserCommand = config->readEntry("super-user-command", DEFAULT_SUPER_USER_COMMAND); + //sudo does not accept signals from user so we except it + if (superUserCommand == "sudo") { + return true; + } else { + return kill(pid,0) == 0; + } +} + +/* +** Check process exit status for process @p pid. +** On error (no child, no exit), return Error (-1). +** If child @p pid has exited, return its exit status, +** (which may be zero). +** If child @p has not exited, return NotExited (-2). +*/ + +int PtyProcess::checkPidExited(pid_t pid) +{ + int state, ret; + ret = waitpid(pid, &state, WNOHANG); + + if (ret < 0) + { + kdError(900) << k_lineinfo << "waitpid(): " << perror << "\n"; + return Error; + } + if (ret == pid) + { + if (WIFEXITED(state)) + return WEXITSTATUS(state); + return Killed; + } + + return NotExited; +} + + +class PtyProcess::PtyProcessPrivate +{ +public: + QCStringList env; +}; + + +PtyProcess::PtyProcess() +{ + m_bTerminal = false; + m_bErase = false; + m_pPTY = 0L; + d = new PtyProcessPrivate; +} + + +int PtyProcess::init() +{ + delete m_pPTY; + m_pPTY = new PTY(); + m_Fd = m_pPTY->getpt(); + if (m_Fd < 0) + return -1; + if ((m_pPTY->grantpt() < 0) || (m_pPTY->unlockpt() < 0)) + { + kdError(900) << k_lineinfo << "Master setup failed.\n"; + m_Fd = -1; + return -1; + } + m_TTY = m_pPTY->ptsname(); + m_Inbuf.resize(0); + return 0; +} + + +PtyProcess::~PtyProcess() +{ + delete m_pPTY; + delete d; +} + +/** Set additinal environment variables. */ +void PtyProcess::setEnvironment( const QCStringList &env ) +{ + d->env = env; +} + +const QCStringList& PtyProcess::environment() const +{ + return d->env; +} + +/* + * Read one line of input. The terminal is in canonical mode, so you always + * read a line at at time, but it's possible to receive multiple lines in + * one time. + */ + +QCString PtyProcess::readLine(bool block) +{ + int pos; + QCString ret; + + if (!m_Inbuf.isEmpty()) + { + pos = m_Inbuf.find('\n'); + if (pos == -1) + { + ret = m_Inbuf; + m_Inbuf.resize(0); + } else + { + ret = m_Inbuf.left(pos); + m_Inbuf = m_Inbuf.mid(pos+1); + } + return ret; + } + + int flags = fcntl(m_Fd, F_GETFL); + if (flags < 0) + { + kdError(900) << k_lineinfo << "fcntl(F_GETFL): " << perror << "\n"; + return ret; + } + int oflags = flags; + if (block) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + + if ((flags != oflags) && (fcntl(m_Fd, F_SETFL, flags) < 0)) + { + // We get an error here when the child process has closed + // the file descriptor already. + return ret; + } + + int nbytes; + char buf[256]; + while (1) + { + nbytes = read(m_Fd, buf, 255); + if (nbytes == -1) + { + if (errno == EINTR) + continue; + else break; + } + if (nbytes == 0) + break; // eof + + buf[nbytes] = '\000'; + m_Inbuf += buf; + + pos = m_Inbuf.find('\n'); + if (pos == -1) + { + ret = m_Inbuf; + m_Inbuf.resize(0); + } else + { + ret = m_Inbuf.left(pos); + m_Inbuf = m_Inbuf.mid(pos+1); + } + break; + } + + return ret; +} + +QCString PtyProcess::readAll(bool block) +{ + QCString ret; + + if (!m_Inbuf.isEmpty()) + { + // if there is still something in the buffer, we need not block. + // we should still try to read any further output, from the fd, though. + block = false; + ret = m_Inbuf; + m_Inbuf.resize(0); + } + + int flags = fcntl(m_Fd, F_GETFL); + if (flags < 0) + { + kdError(900) << k_lineinfo << "fcntl(F_GETFL): " << perror << "\n"; + return ret; + } + int oflags = flags; + if (block) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + + if ((flags != oflags) && (fcntl(m_Fd, F_SETFL, flags) < 0)) + { + // We get an error here when the child process has closed + // the file descriptor already. + return ret; + } + + int nbytes; + char buf[256]; + while (1) + { + nbytes = read(m_Fd, buf, 255); + if (nbytes == -1) + { + if (errno == EINTR) + continue; + else break; + } + if (nbytes == 0) + break; // eof + + buf[nbytes] = '\000'; + ret += buf; + break; + } + + return ret; +} + + +void PtyProcess::writeLine(const QCString &line, bool addnl) +{ + if (!line.isEmpty()) + write(m_Fd, line, line.length()); + if (addnl) + write(m_Fd, "\n", 1); +} + + +void PtyProcess::unreadLine(const QCString &line, bool addnl) +{ + QCString tmp = line; + if (addnl) + tmp += '\n'; + if (!tmp.isEmpty()) + m_Inbuf.prepend(tmp); +} + +/* + * Fork and execute the command. This returns in the parent. + */ + +int PtyProcess::exec(const QCString &command, const QCStringList &args) +{ + kdDebug(900) << k_lineinfo << "Running `" << command << "'\n"; + + if (init() < 0) + return -1; + + // Open the pty slave before forking. See SetupTTY() + int slave = open(m_TTY, O_RDWR); + if (slave < 0) + { + kdError(900) << k_lineinfo << "Could not open slave pty.\n"; + return -1; + } + + if ((m_Pid = fork()) == -1) + { + kdError(900) << k_lineinfo << "fork(): " << perror << "\n"; + return -1; + } + + // Parent + if (m_Pid) + { + close(slave); + return 0; + } + + // Child + if (SetupTTY(slave) < 0) + _exit(1); + + for(QCStringList::ConstIterator it = d->env.begin(); + it != d->env.end(); it++) + { + putenv((*it).data()); + } + unsetenv("KDE_FULL_SESSION"); + + // set temporarily LC_ALL to C, for su (to be able to parse "Password:") + const char* old_lc_all = getenv( "LC_ALL" ); + if( old_lc_all != NULL ) + setenv( "KDESU_LC_ALL", old_lc_all, 1 ); + else + unsetenv( "KDESU_LC_ALL" ); + setenv("LC_ALL", "C", 1); + + // From now on, terminal output goes through the tty. + + QCString path; + if (command.contains('/')) + path = command; + else + { + QString file = KStandardDirs::findExe(command); + if (file.isEmpty()) + { + kdError(900) << k_lineinfo << command << " not found\n"; + _exit(1); + } + path = QFile::encodeName(file); + } + + const char **argp = (const char **)malloc((args.count()+2)*sizeof(char *)); + int i = 0; + argp[i++] = path; + for (QCStringList::ConstIterator it=args.begin(); it!=args.end(); ++it) + argp[i++] = *it; + + argp[i] = 0L; + + execv(path, (char * const *)argp); + kdError(900) << k_lineinfo << "execv(\"" << path << "\"): " << perror << "\n"; + _exit(1); + return -1; // Shut up compiler. Never reached. +} + + +/* + * Wait until the terminal is set into no echo mode. At least one su + * (RH6 w/ Linux-PAM patches) sets noecho mode AFTER writing the Password: + * prompt, using TCSAFLUSH. This flushes the terminal I/O queues, possibly + * taking the password with it. So we wait until no echo mode is set + * before writing the password. + * Note that this is done on the slave fd. While Linux allows tcgetattr() on + * the master side, Solaris doesn't. + */ + +int PtyProcess::WaitSlave() +{ + int slave = open(m_TTY, O_RDWR); + if (slave < 0) + { + kdError(900) << k_lineinfo << "Could not open slave tty.\n"; + return -1; + } + + kdDebug(900) << k_lineinfo << "Child pid " << m_Pid << endl; + + struct termios tio; + while (1) + { + if (!checkPid(m_Pid)) + { + close(slave); + return -1; + } + if (tcgetattr(slave, &tio) < 0) + { + kdError(900) << k_lineinfo << "tcgetattr(): " << perror << "\n"; + close(slave); + return -1; + } + if (tio.c_lflag & ECHO) + { + kdDebug(900) << k_lineinfo << "Echo mode still on.\n"; + waitMS(slave,100); + continue; + } + break; + } + close(slave); + return 0; +} + + +int PtyProcess::enableLocalEcho(bool enable) +{ + int slave = open(m_TTY, O_RDWR); + if (slave < 0) + { + kdError(900) << k_lineinfo << "Could not open slave tty.\n"; + return -1; + } + struct termios tio; + if (tcgetattr(slave, &tio) < 0) + { + kdError(900) << k_lineinfo << "tcgetattr(): " << perror << "\n"; + close(slave); return -1; + } + if (enable) + tio.c_lflag |= ECHO; + else + tio.c_lflag &= ~ECHO; + if (tcsetattr(slave, TCSANOW, &tio) < 0) + { + kdError(900) << k_lineinfo << "tcsetattr(): " << perror << "\n"; + close(slave); return -1; + } + close(slave); + return 0; +} + + +/* + * Copy output to stdout until the child process exists, or a line of output + * matches `m_Exit'. + * We have to use waitpid() to test for exit. Merely waiting for EOF on the + * pty does not work, because the target process may have children still + * attached to the terminal. + */ + +int PtyProcess::waitForChild() +{ + int retval = 1; + + fd_set fds; + FD_ZERO(&fds); + + while (1) + { + FD_SET(m_Fd, &fds); + int ret = select(m_Fd+1, &fds, 0L, 0L, 0L); + if (ret == -1) + { + if (errno != EINTR) + { + kdError(900) << k_lineinfo << "select(): " << perror << "\n"; + return -1; + } + ret = 0; + } + + if (ret) + { + QCString output = readAll(false); + bool lineStart = true; + while (!output.isNull()) + { + if (!m_Exit.isEmpty()) + { + // match exit string only at line starts + int pos = output.find(m_Exit); + if ((pos >= 0) && ((pos == 0 && lineStart) || (output.at (pos - 1) == '\n'))) + { + kill(m_Pid, SIGTERM); + } + } + if (m_bTerminal) + { + fputs(output, stdout); + fflush(stdout); + } + lineStart = output.at( output.length() - 1 ) == '\n'; + output = readAll(false); + } + } + + ret = checkPidExited(m_Pid); + if (ret == Error) + { + if (errno == ECHILD) retval = 0; + else retval = 1; + break; + } + else if (ret == Killed) + { + retval = 0; + break; + } + else if (ret == NotExited) + { + // keep checking + } + else + { + retval = ret; + break; + } + } + return retval; +} + +/* + * SetupTTY: Creates a new session. The filedescriptor "fd" should be + * connected to the tty. It is closed after the tty is reopened to make it + * our controlling terminal. This way the tty is always opened at least once + * so we'll never get EIO when reading from it. + */ + +int PtyProcess::SetupTTY(int fd) +{ + // Reset signal handlers + for (int sig = 1; sig < NSIG; sig++) + signal(sig, SIG_DFL); + signal(SIGHUP, SIG_IGN); + + // Close all file handles + struct rlimit rlp; + getrlimit(RLIMIT_NOFILE, &rlp); + for (int i = 0; i < (int)rlp.rlim_cur; i++) + if (i != fd) close(i); + + // Create a new session. + setsid(); + + // Open slave. This will make it our controlling terminal + int slave = open(m_TTY, O_RDWR); + if (slave < 0) + { + kdError(900) << k_lineinfo << "Could not open slave side: " << perror << "\n"; + return -1; + } + close(fd); + +#if defined(__SVR4) && defined(sun) + + // Solaris STREAMS environment. + // Push these modules to make the stream look like a terminal. + ioctl(slave, I_PUSH, "ptem"); + ioctl(slave, I_PUSH, "ldterm"); + +#endif + +#ifdef TIOCSCTTY + ioctl(slave, TIOCSCTTY, NULL); +#endif + + // Connect stdin, stdout and stderr + dup2(slave, 0); dup2(slave, 1); dup2(slave, 2); + if (slave > 2) + close(slave); + + // Disable OPOST processing. Otherwise, '\n' are (on Linux at least) + // translated to '\r\n'. + struct termios tio; + if (tcgetattr(0, &tio) < 0) + { + kdError(900) << k_lineinfo << "tcgetattr(): " << perror << "\n"; + return -1; + } + tio.c_oflag &= ~OPOST; + if (tcsetattr(0, TCSANOW, &tio) < 0) + { + kdError(900) << k_lineinfo << "tcsetattr(): " << perror << "\n"; + return -1; + } + + return 0; +} + +void PtyProcess::virtual_hook( int, void* ) +{ /*BASE::virtual_hook( id, data );*/ } diff --git a/kdesu/process.h b/kdesu/process.h new file mode 100644 index 000000000..8a2004d92 --- /dev/null +++ b/kdesu/process.h @@ -0,0 +1,188 @@ +/* vi: ts=8 sts=4 sw=4 + * + * $Id$ + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * This is free software; you can use this library under the GNU Library + * General Public License, version 2. See the file "COPYING.LIB" for the + * exact licensing terms. + */ + +#ifndef __Process_h_Included__ +#define __Process_h_Included__ + +#include <sys/types.h> + +#include <qcstring.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qvaluelist.h> + +#include <kdelibs_export.h> + +class PTY; +typedef QValueList<QCString> QCStringList; + +/** + * Synchronous communication with tty programs. + * + * PtyProcess provides synchronous communication with tty based programs. + * The communications channel used is a pseudo tty (as opposed to a pipe) + * This means that programs which require a terminal will work. + */ + +class KDESU_EXPORT PtyProcess +{ +public: + PtyProcess(); + virtual ~PtyProcess(); + + /** + * Forks off and execute a command. The command's standard in and output + * are connected to the pseudo tty. They are accessible with readLine + * and writeLine. + * @param command The command to execute. + * @param args The arguments to the command. + */ + int exec(const QCString &command, const QCStringList &args); + + /** + * Reads a line from the program's standard out. Depending on the @em block + * parameter, this call blocks until a single, full line is read. + * @param block Block until a full line is read? + * @return The output string. + */ + QCString readLine(bool block=true); + /** + * Read all available output from the program's standard out. + * @param block If no output is in the buffer, should the function block + * @return The output. + */ + QCString readAll(bool block=true); + + /** + * Writes a line of text to the program's standard in. + * @param line The text to write. + * @param addNewline Adds a '\n' to the line. + */ + void writeLine(const QCString &line, bool addNewline=true); + + /** + * Puts back a line of input. + * @param line The line to put back. + * @param addNewline Adds a '\n' to the line. + */ + void unreadLine(const QCString &line, bool addNewline=true); + + /** + * Sets the exit string. If a line of program output matches this, + * waitForChild() will terminate the program and return. + */ + void setExitString(const QCString &exit) { m_Exit = exit; } + + /** + * Waits for the child to exit. See also setExitString. + */ + int waitForChild(); + + /** + * Waits until the pty has cleared the ECHO flag. This is useful + * when programs write a password prompt before they disable ECHO. + * Disabling it might flush any input that was written. + */ + int WaitSlave(); + + /** + * Enables/disables local echo on the pseudo tty. + */ + int enableLocalEcho(bool enable=true); + + /** + * Enables/disables terminal output. Relevant only to some subclasses. + */ + void setTerminal(bool terminal) { m_bTerminal = terminal; } + + /** + * Overwrites the password as soon as it is used. Relevant only to + * some subclasses. + */ + void setErase(bool erase) { m_bErase = erase; } + + /** + * Set additinal environment variables. + */ + void setEnvironment( const QCStringList &env ); + + /** + * Returns the filedescriptor of the process. + */ + int fd() {return m_Fd;} + + /** + * Returns the pid of the process. + */ + int pid() {return m_Pid;} + +public: /* static */ + /* + ** This is a collection of static functions that can be + ** used for process control inside kdesu. I'd suggest + ** against using this publicly. There are probably + ** nicer Qt based ways to do what you want. + */ + + /** + ** Wait @p ms miliseconds (ie. 1/10th of a second is 100ms), + ** using @p fd as a filedescriptor to wait on. Returns + ** select(2)'s result, which is -1 on error, 0 on timeout, + ** or positive if there is data on one of the selected fd's. + ** + ** @p ms must be in the range 0..999 (ie. the maximum wait + ** duration is 999ms, almost one second). + */ + static int waitMS(int fd,int ms); + + + /** + ** Basic check for the existence of @p pid. + ** Returns true iff @p pid is an extant process, + ** (one you could kill - see man kill(2) for signal 0). + */ + static bool checkPid(pid_t pid); + + /** + ** Check process exit status for process @p pid. + ** On error (no child, no exit), return -1. + ** If child @p pid has exited, return its exit status, + ** (which may be zero). + ** If child @p has not exited, return -2. + */ + enum checkPidStatus { Error=-1, NotExited=-2, Killed=-3 } ; + static int checkPidExited(pid_t pid); + + +protected: + const QCStringList& environment() const; + + bool m_bErase, m_bTerminal; + int m_Pid, m_Fd; + QCString m_Command, m_Exit; + +private: + int init(); + int SetupTTY(int fd); + + PTY *m_pPTY; + QCString m_Inbuf, m_TTY; + +protected: + virtual void virtual_hook( int id, void* data ); +private: + class PtyProcessPrivate; + PtyProcessPrivate *d; +}; + + +#endif diff --git a/kdesu/ssh.cpp b/kdesu/ssh.cpp new file mode 100644 index 000000000..a325fb62e --- /dev/null +++ b/kdesu/ssh.cpp @@ -0,0 +1,279 @@ +/* vi: ts=8 sts=4 sw=4 +* +* $Id$ +* +* This file is part of the KDE project, module kdesu. +* Copyright (C) 2000 Geert Jansen <jansen@kde.org> +* +* This is free software; you can use this library under the GNU Library +* General Public License, version 2. See the file "COPYING.LIB" for the +* exact licensing terms. +* +* ssh.cpp: Execute a program on a remote machine using ssh. +*/ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> +#include <signal.h> +#include <time.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <qglobal.h> +#include <qcstring.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kstandarddirs.h> + +#include "ssh.h" +#include "kcookie.h" + + +SshProcess::SshProcess(const QCString &host, const QCString &user, const QCString &command) +{ + m_Host = host; + m_User = user; + m_Command = command; + m_Stub = "kdesu_stub"; + srand(time(0L)); +} + + +SshProcess::~SshProcess() +{ +} + + +void SshProcess::setStub(const QCString &stub) +{ + m_Stub = stub; +} + + +int SshProcess::checkInstall(const char *password) +{ + return exec(password, 1); +} + + +int SshProcess::checkNeedPassword() +{ + return exec(0L, 2); +} + + +int SshProcess::exec(const char *password, int check) +{ + if (check) + setTerminal(true); + + QCStringList args; + args += "-l"; args += m_User; + args += "-o"; args += "StrictHostKeyChecking=no"; + args += m_Host; args += m_Stub; + + if (StubProcess::exec("ssh", args) < 0) + { + return check ? SshNotFound : -1; + } + + int ret = ConverseSsh(password, check); + if (ret < 0) + { + if (!check) + kdError(900) << k_lineinfo << "Conversation with ssh failed\n"; + return ret; + } + if (check == 2) + { + if (ret == 1) + { + kill(m_Pid, SIGTERM); + waitForChild(); + } + return ret; + } + + if (m_bErase && password) + { + char *ptr = const_cast<char *>(password); + const uint plen = strlen(password); + for (unsigned i=0; i < plen; i++) + ptr[i] = '\000'; + } + + ret = ConverseStub(check); + if (ret < 0) + { + if (!check) + kdError(900) << k_lineinfo << "Converstation with kdesu_stub failed\n"; + return ret; + } + else if (ret == 1) + { + kill(m_Pid, SIGTERM); + waitForChild(); + ret = SshIncorrectPassword; + } + + if (check == 1) + { + waitForChild(); + return 0; + } + + setExitString("Waiting for forwarded connections to terminate"); + ret = waitForChild(); + return ret; +} + +/* +* Create a port forwarding for DCOP. For the remote port, we take a pseudo +* random number between 10k and 50k. This is not ok, of course, but I see +* no other way. There is, afaik, no security issue involved here. If the port +* happens to be occupied, ssh will refuse to start. +* +* 14/SEP/2000: DCOP forwarding is not used anymore. +*/ + +QCString SshProcess::dcopForward() +{ + QCString result; + + setDcopTransport("tcp"); + + QCString srv = StubProcess::dcopServer(); + if (srv.isEmpty()) + return result; + + int i = srv.find('/'); + if (i == -1) + return result; + if (srv.left(i) != "tcp") + return result; + int j = srv.find(':', ++i); + if (j == -1) + return result; + QCString host = srv.mid(i, j-i); + bool ok; + int port = srv.mid(++j).toInt(&ok); + if (!ok) + return result; + + m_dcopPort = 10000 + (int) ((40000.0 * rand()) / (1.0 + RAND_MAX)); + result.sprintf("%d:%s:%d", m_dcopPort, host.data(), port); + return result; +} + + +/* +* Conversation with ssh. +* If check is 0, this waits for either a "Password: " prompt, +* or the header of the stub. If a prompt is received, the password is +* written back. Used for running a command. +* If check is 1, operation is the same as 0 except that if a stub header is +* received, the stub is stopped with the "stop" command. This is used for +* checking a password. +* If check is 2, operation is the same as 1, except that no password is +* written. The prompt is saved to m_Prompt. Used for checking the need for +* a password. +*/ + +int SshProcess::ConverseSsh(const char *password, int check) +{ + unsigned i, j, colon; + + QCString line; + int state = 0; + + while (state < 2) + { + line = readLine(); + const uint len = line.length(); + if (line.isNull()) + return -1; + + switch (state) { + case 0: + // Check for "kdesu_stub" header. + if (line == "kdesu_stub") + { + unreadLine(line); + return 0; + } + + // Match "Password: " with the regex ^[^:]+:[\w]*$. + for (i=0,j=0,colon=0; i<len; i++) + { + if (line[i] == ':') + { + j = i; colon++; + continue; + } + if (!isspace(line[i])) + j++; + } + if ((colon == 1) && (line[j] == ':')) + { + if (check == 2) + { + m_Prompt = line; + return SshNeedsPassword; + } + WaitSlave(); + write(m_Fd, password, strlen(password)); + write(m_Fd, "\n", 1); + state++; + break; + } + + // Warning/error message. + m_Error += line; m_Error += "\n"; + if (m_bTerminal) + fprintf(stderr, "ssh: %s\n", line.data()); + break; + + case 1: + if (line.isEmpty()) + { + state++; + break; + } + return -1; + } + } + return 0; +} + + +// Display redirection is handled by ssh natively. +QCString SshProcess::display() +{ + return "no"; +} + + +QCString SshProcess::displayAuth() +{ + return "no"; +} + + +// Return the remote end of the forwarded connection. +QCString SshProcess::dcopServer() +{ + return QCString().sprintf("tcp/localhost:%d", m_dcopPort); +} + +void SshProcess::virtual_hook( int id, void* data ) +{ StubProcess::virtual_hook( id, data ); } diff --git a/kdesu/ssh.h b/kdesu/ssh.h new file mode 100644 index 000000000..23308fd64 --- /dev/null +++ b/kdesu/ssh.h @@ -0,0 +1,90 @@ +/* vi: ts=8 sts=4 sw=4 + * + * $Id$ + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 2000 Geert Jansen <jansen@kde.org> + * + * This is free software; you can use this library under the GNU Library + * General Public License, version 2. See the file "COPYING.LIB" for the + * exact licensing terms. + */ + +#ifndef __SSH_h_Included__ +#define __SSH_h_Included__ + +#include <qcstring.h> + +#include "stub.h" + +#include <kdelibs_export.h> + +/** + * Executes a remote command, using ssh. + */ + +class KDESU_EXPORT SshProcess: public StubProcess +{ +public: + SshProcess(const QCString &host=0, const QCString &user=0, const QCString &command=0); + ~SshProcess(); + + enum Errors { SshNotFound=1, SshNeedsPassword, SshIncorrectPassword }; + + /** + * Sets the target host. + */ + void setHost(const QCString &host) { m_Host = host; } + + /** + * Sets the localtion of the remote stub. + */ + void setStub(const QCString &stub); + + /** + * Checks if the current user\@host needs a password. + * @return The prompt for the password if a password is required. A null + * string otherwise. + * + * @todo The return doc is so obviously wrong that the C code needs to be checked. + */ + int checkNeedPassword(); + + /** + * Checks if the stub is installed and if the password is correct. + * @return Zero if everything is correct, nonzero otherwise. + */ + int checkInstall(const char *password); + + /** + * Executes the command. + */ + int exec(const char *password, int check=0); + + QCString prompt() { return m_Prompt; } + QCString error() { return m_Error; } + +protected: + virtual QCString display(); + virtual QCString displayAuth(); + virtual QCString dcopServer(); + +private: + QCString dcopForward(); + int ConverseSsh(const char *password, int check); + + int m_dcopPort; + int m_dcopSrv; + QCString m_Prompt; + QCString m_Host; + QCString m_Error; + QCString m_Stub; + +protected: + virtual void virtual_hook( int id, void* data ); +private: + class SshProcessPrivate; + SshProcessPrivate *d; +}; + +#endif diff --git a/kdesu/stub.cpp b/kdesu/stub.cpp new file mode 100644 index 000000000..c0e968200 --- /dev/null +++ b/kdesu/stub.cpp @@ -0,0 +1,184 @@ +/* vi: ts=8 sts=4 sw=4 + * + * $Id$ + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * This is free software; you can use this library under the GNU Library + * General Public License, version 2. See the file "COPYING.LIB" for the + * exact licensing terms. + * + * stub.cpp: Conversation with kdesu_stub. + */ + +#include <config.h> +#include <stdlib.h> +#include <unistd.h> + +#include <qglobal.h> +#include <qcstring.h> +#include <kdatastream.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <dcopclient.h> + +#include "stub.h" +#include "kcookie.h" + + +StubProcess::StubProcess() +{ + m_User = "root"; + m_Scheduler = SchedNormal; + m_Priority = 50; + m_pCookie = new KCookie; + m_bXOnly = true; + m_bDCOPForwarding = false; +} + + +StubProcess::~StubProcess() +{ + delete m_pCookie; +} + + +void StubProcess::setPriority(int prio) +{ + if (prio > 100) + m_Priority = 100; + else if (prio < 0) + m_Priority = 0; + else + m_Priority = prio; +} + + +QCString StubProcess::commaSeparatedList(QCStringList lst) +{ + if (lst.count() == 0) + return QCString(""); + + QCStringList::Iterator it = lst.begin(); + QCString str = *it; + for (it++; it!=lst.end(); it++) + { + str += ','; + str += *it; + } + return str; +} + +/* + * Conversation with kdesu_stub. This is how we pass the authentication + * tokens (X11, DCOP) and other stuff to kdesu_stub. + * return values: -1 = error, 0 = ok, 1 = kill me + */ + +int StubProcess::ConverseStub(int check) +{ + QCString line, tmp; + while (1) + { + line = readLine(); + if (line.isNull()) + return -1; + + if (line == "kdesu_stub") + { + // This makes parsing a lot easier. + enableLocalEcho(false); + if (check) writeLine("stop"); + else writeLine("ok"); + } else if (line == "display") { + writeLine(display()); + } else if (line == "display_auth") { +#ifdef Q_WS_X11 + writeLine(displayAuth()); +#else + writeLine(""); +#endif + } else if (line == "dcopserver") { + if (m_bDCOPForwarding) + writeLine(dcopServer()); + else + writeLine("no"); + } else if (line == "dcop_auth") { + if (m_bDCOPForwarding) + writeLine(dcopAuth()); + else + writeLine("no"); + } else if (line == "ice_auth") { + if (m_bDCOPForwarding) + writeLine(iceAuth()); + else + writeLine("no"); + } else if (line == "command") { + writeLine(m_Command); + } else if (line == "path") { + QCString path = getenv("PATH"); + if (!path.isEmpty() && path[0] == ':') + path = path.mid(1); + if (m_User == "root") + if (!path.isEmpty()) + path = "/sbin:/bin:/usr/sbin:/usr/bin:" + path; + else + path = "/sbin:/bin:/usr/sbin:/usr/bin"; + writeLine(path); + } else if (line == "user") { + writeLine(m_User); + } else if (line == "priority") { + tmp.setNum(m_Priority); + writeLine(tmp); + } else if (line == "scheduler") { + if (m_Scheduler == SchedRealtime) writeLine("realtime"); + else writeLine("normal"); + } else if (line == "xwindows_only") { + if (m_bXOnly) writeLine("no"); + else writeLine("yes"); + } else if (line == "app_startup_id") { + QCStringList env = environment(); + QCString tmp; + for( QCStringList::ConstIterator it = env.begin(); + it != env.end(); + ++it ) + { + if( (*it).find( "DESKTOP_STARTUP_ID=" ) == 0 ) + tmp = (*it).mid( strlen( "DESKTOP_STARTUP_ID=" )); + } + if( tmp.isEmpty()) + tmp = "0"; + writeLine(tmp); + } else if (line == "app_start_pid") { // obsolete + tmp.setNum(getpid()); + writeLine(tmp); + } else if (line == "environment") { // additional env vars + QCStringList env = environment(); + for( QCStringList::ConstIterator it = env.begin(); + it != env.end(); + ++it ) + writeLine( *it ); + writeLine( "" ); + } else if (line == "end") { + return 0; + } else + { + kdWarning(900) << k_lineinfo << "Unknown request: -->" << line + << "<--\n"; + return 1; + } + } + + return 0; +} + + +void StubProcess::notifyTaskbar(const QString &) +{ + kdWarning(900) << "Obsolete StubProcess::notifyTaskbar() called!" << endl; +} + +void StubProcess::virtual_hook( int id, void* data ) +{ PtyProcess::virtual_hook( id, data ); } diff --git a/kdesu/stub.h b/kdesu/stub.h new file mode 100644 index 000000000..921db0e1c --- /dev/null +++ b/kdesu/stub.h @@ -0,0 +1,139 @@ +/* vi: ts=8 sts=4 sw=4 + * + * $Id$ + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * This is free software; you can use this library under the GNU Library + * General Public License, version 2. See the file "COPYING.LIB" for the + * exact licensing terms. + */ + +#ifndef __Stub_h_Included__ +#define __Stub_h_Included__ + +#include <qcstring.h> +#include <qvaluelist.h> + +#include "process.h" +#include "kcookie.h" + +#include <kdelibs_export.h> + +typedef QValueList<QCString> QCStringList; + +/** + * Chat with kdesu_stub. + * + * StubProcess extends PtyProcess with functionality to chat with kdesu_stub. + */ + +class KDESU_EXPORT StubProcess: public PtyProcess +{ +public: + StubProcess(); + ~StubProcess(); + + /** + * Specify dcop transport + */ + void setDcopTransport(const QCString &dcopTransport) + { m_pCookie->setDcopTransport(dcopTransport); } + + /** + * Set the command. + */ + void setCommand(const QCString &command) { m_Command = command; } + + /** + * Set the target user. + */ + void setUser(const QCString &user) { m_User = user; } + + /** + * Set to "X only mode": Sycoca is not built and kdeinit is not launched. + */ + void setXOnly(bool xonly) { m_bXOnly = xonly; } + + /** + * Enable DCOP forwarding. + */ + void setDCOPForwarding(bool dcopForwarding) { m_bDCOPForwarding = dcopForwarding; } + + /** + * Set the priority of the process. The priority value must be between 0 + * and 100, 0 being the lowest priority. This value is mapped to the + * scheduler and system dependant priority range of the OS. + */ + void setPriority(int prio); + + /** + * Different schedulers. SchedNormal is the normal Unix timesharing + * scheduler, while SchedRealtime is a POSIX.1b realtime scheduler. + */ + enum Scheduler { SchedNormal, SchedRealtime }; + + /** + * Set the scheduler type. + */ + void setScheduler(int sched) { m_Scheduler = sched; } + +protected: + + /** + * Exchange all parameters with kdesu_stub. + */ + int ConverseStub(int check); + + /** + * Notify the taskbar that a new application has been started. + * @obsolete + */ + // KDE4 remove + void notifyTaskbar(const QString &suffix); + + /** + * This virtual function can be overloaded when special behavior is + * desired. By default, it returns the value returned by KCookie. + */ + virtual QCString display() { return m_pCookie->display(); } +#ifdef Q_WS_X11 + /** + * See display. + */ + virtual QCString displayAuth() { return m_pCookie->displayAuth(); } +#endif + /** + * See display. + */ + virtual QCString dcopServer() { return m_pCookie->dcopServer(); } + /** + * See display. + */ + virtual QCString dcopAuth() { return m_pCookie->dcopAuth(); } + /** + * See display. + */ + virtual QCString iceAuth() { return m_pCookie->iceAuth(); } + + bool m_bXOnly; + bool m_bDCOPForwarding; + int m_Priority; + int m_Scheduler; + QCString m_dcopTransport; + QCString m_Command; + QCString m_User; + KCookie *m_pCookie; + +private: + QCString commaSeparatedList(QCStringList); + +protected: + virtual void virtual_hook( int id, void* data ); +private: + class StubProcessPrivate; + StubProcessPrivate *d; +}; + +#endif // __Stub_h_Included__ diff --git a/kdesu/su.cpp b/kdesu/su.cpp new file mode 100644 index 000000000..344c0f34c --- /dev/null +++ b/kdesu/su.cpp @@ -0,0 +1,342 @@ +/* vi: ts=8 sts=4 sw=4 +* +* $Id$ +* +* This file is part of the KDE project, module kdesu. +* Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> +* +* Sudo support added by Jonathan Riddell <jriddell@ ubuntu.com> +* Copyright (C) 2005 Canonical Ltd +* +* This is free software; you can use this library under the GNU Library +* General Public License, version 2. See the file "COPYING.LIB" for the +* exact licensing terms. +* +* su.cpp: Execute a program as another user with "class SuProcess". +*/ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> +#include <signal.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <qglobal.h> +#include <qcstring.h> +#include <qfile.h> + +#include <kconfig.h> +#include <kdebug.h> +#include <klocale.h> +#include <kstandarddirs.h> + +#include "su.h" +#include "kcookie.h" + + +#ifndef __PATH_SU +#define __PATH_SU "false" +#endif + +#ifndef __PATH_SUDO +#define __PATH_SUDO "false" +#endif + +SuProcess::SuProcess(const QCString &user, const QCString &command) +{ + m_User = user; + m_Command = command; + + KConfig* config = KGlobal::config(); + config->setGroup("super-user-command"); + superUserCommand = config->readEntry("super-user-command", DEFAULT_SUPER_USER_COMMAND); + if ( superUserCommand != "sudo" && superUserCommand != "su" ) { + kdWarning() << "unknown super user command" << endl; + superUserCommand = "su"; + } +} + + +SuProcess::~SuProcess() +{ +} + +int SuProcess::checkInstall(const char *password) +{ + return exec(password, Install); +} + +int SuProcess::checkNeedPassword() +{ + return exec(0L, NeedPassword); +} + +/* +* Execute a command with su(1). +*/ + +int SuProcess::exec(const char *password, int check) +{ + if (check) + setTerminal(true); + + // since user may change after constructor (due to setUser()) + // we need to override sudo with su for non-root here + if (m_User != "root") { + superUserCommand = "su"; + } + + QCStringList args; + if (superUserCommand == "sudo") { + args += "-u"; + } + +#ifdef Q_OS_DARWIN + args += "-c"; + args += "staff"; +#endif + + if ((m_Scheduler != SchedNormal) || (m_Priority > 50)) + args += "root"; + else + args += m_User; + + if (superUserCommand == "su") { + args += "-c"; + } + args += QCString(__KDE_BINDIR) + "/kdesu_stub"; +#ifndef Q_OS_DARWIN + args += "-"; +#endif + + /// QCString command = __PATH_SU; + /// if (::access(__PATH_SU, X_OK) != 0) + QCString command; + if (superUserCommand == "sudo") { + command = __PATH_SUDO; + } else { + command = __PATH_SU; + } + + if (::access(command, X_OK) != 0) + { + /// command = QFile::encodeName(KGlobal::dirs()->findExe("su")); + command = QFile::encodeName( KGlobal::dirs()->findExe(superUserCommand.ascii()) ); + if (command.isEmpty()) + return check ? SuNotFound : -1; + } + + // kdDebug(900) << k_lineinfo << "Call StubProcess::exec()" << endl; + if (StubProcess::exec(command, args) < 0) + { + return check ? SuNotFound : -1; + } + // kdDebug(900) << k_lineinfo << "Done StubProcess::exec()" << endl; + + SuErrors ret = (SuErrors) ConverseSU(password); + // kdDebug(900) << k_lineinfo << "Conversation returned " << ret << endl; + + if (ret == error) + { + if (!check) + kdError(900) << k_lineinfo << "Conversation with " << superUserCommand << " failed\n"; + return ret; + } + if (check == NeedPassword) + { + if (ret == killme) + { + /// if (kill(m_Pid, SIGKILL) < 0) + /// { + /// ret=error; + /// } + if ( superUserCommand == "sudo" ) { + // sudo can not be killed, just return + return ret; + } + if (kill(m_Pid, SIGKILL) < 0) { + kdDebug() << k_funcinfo << "kill < 0" << endl; + //FIXME SIGKILL doesn't work for sudo, + //why is this different from su? + ret=error; + } + else + { + int iret = waitForChild(); + if (iret < 0) ret=error; + else /* nothing */ {} ; + } + } + return ret; + } + + if (m_bErase && password) + { + char *ptr = const_cast<char *>(password); + const uint plen = strlen(password); + for (unsigned i=0; i < plen; i++) + ptr[i] = '\000'; + } + + if (ret == notauthorized) + { + kill(m_Pid, SIGKILL); + if (superUserCommand != "sudo") { + waitForChild(); + } + return SuIncorrectPassword; + } + + int iret = ConverseStub(check); + if (iret < 0) + { + if (!check) + kdError(900) << k_lineinfo << "Converstation with kdesu_stub failed\n"; + return iret; + } + else if (iret == 1) + { + kill(m_Pid, SIGKILL); + waitForChild(); + return SuIncorrectPassword; + } + + if (check == Install) + { + waitForChild(); + return 0; + } + + iret = waitForChild(); + return iret; +} + +/* +* Conversation with su: feed the password. +* Return values: -1 = error, 0 = ok, 1 = kill me, 2 not authorized +*/ + +int SuProcess::ConverseSU(const char *password) +{ + enum { WaitForPrompt, CheckStar, HandleStub } state = WaitForPrompt; + int colon; + unsigned i, j; + // kdDebug(900) << k_lineinfo << "ConverseSU starting." << endl; + + QCString line; + while (true) + { + line = readLine(); + if (line.isNull()) + return ( state == HandleStub ? notauthorized : error); + kdDebug(900) << k_lineinfo << "Read line <" << line << ">" << endl; + + switch (state) + { + ////////////////////////////////////////////////////////////////////////// + case WaitForPrompt: + { + // In case no password is needed. + if (line == "kdesu_stub") + { + unreadLine(line); + return ok; + } + + while(waitMS(m_Fd,100)>0) + { + // There is more output available, so the previous line + // couldn't have been a password prompt (the definition + // of prompt being that there's a line of output followed + // by a colon, and then the process waits). + QCString more = readLine(); + if (more.isEmpty()) + break; + + line = more; + kdDebug(900) << k_lineinfo << "Read line <" << more << ">" << endl; + } + + // Match "Password: " with the regex ^[^:]+:[\w]*$. + const uint len = line.length(); + for (i=0,j=0,colon=0; i<len; i++) + { + if (line[i] == ':') + { + j = i; colon++; + continue; + } + if (!isspace(line[i])) + j++; + } + if ((colon == 1) && (line[j] == ':')) + { + if (password == 0L) + return killme; + if (!checkPid(m_Pid)) + { + kdError(900) << superUserCommand << " has exited while waiting for pwd." << endl; + return error; + } + if ((WaitSlave() == 0) && checkPid(m_Pid)) + { + write(m_Fd, password, strlen(password)); + write(m_Fd, "\n", 1); + state=CheckStar; + } + else + { + return error; + } + } + break; + } + ////////////////////////////////////////////////////////////////////////// + case CheckStar: + { + QCString s = line.stripWhiteSpace(); + if (s.isEmpty()) + { + state=HandleStub; + break; + } + const uint len = line.length(); + for (i=0; i< len; i++) + { + if (s[i] != '*') + return error; + } + state=HandleStub; + break; + } + ////////////////////////////////////////////////////////////////////////// + case HandleStub: + // Read till we get "kdesu_stub" + if (line == "kdesu_stub") + { + unreadLine(line); + return ok; + } else if (superUserCommand == "sudo") { + // sudo gives a "sorry" line so reaches here + // with the wrong password + return notauthorized; + } + break; + ////////////////////////////////////////////////////////////////////////// + } // end switch + } // end while (true) + return ok; +} + +void SuProcess::virtual_hook( int id, void* data ) +{ StubProcess::virtual_hook( id, data ); } + + diff --git a/kdesu/su.h b/kdesu/su.h new file mode 100644 index 000000000..c82e459eb --- /dev/null +++ b/kdesu/su.h @@ -0,0 +1,63 @@ +/* vi: ts=8 sts=4 sw=4 + * + * $Id$ + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * This is free software; you can use this library under the GNU Library + * General Public License, version 2. See the file "COPYING.LIB" for the + * exact licensing terms. + */ + +#ifndef __SU_h_Included__ +#define __SU_h_Included__ + +#include <qcstring.h> + +#include <kdelibs_export.h> + +#include "stub.h" + +/** + * Executes a command under elevated privileges, using su. + */ + +class KDESU_EXPORT SuProcess: public StubProcess +{ +public: + SuProcess(const QCString &user=0, const QCString &command=0); + ~SuProcess(); + + enum Errors { SuNotFound=1, SuNotAllowed, SuIncorrectPassword }; + + /** + * Executes the command. This will wait for the command to finish. + */ + enum checkMode { NoCheck=0, Install=1, NeedPassword=2 } ; + int exec(const char *password, int check=NoCheck); + + /** + * Checks if the stub is installed and the password is correct. + * @return Zero if everything is correct, nonzero otherwise. + */ + int checkInstall(const char *password); + + /** + * Checks if a password is needed. + */ + int checkNeedPassword(); + +private: + enum SuErrors { error=-1, ok=0, killme=1, notauthorized=2 } ; + int ConverseSU(const char *password); + +protected: + virtual void virtual_hook( int id, void* data ); +private: + class SuProcessPrivate; + SuProcessPrivate *d; + QString superUserCommand; +}; + +#endif |