diff --git a/src/client.h b/src/client.h index 2c489ba..7645d8e 100644 --- a/src/client.h +++ b/src/client.h @@ -1,203 +1,203 @@ /* * This file is part of the KDE project, module kdesu. * Copyright (C) 1999,2000 Geert Jansen * * 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 KDESUCLIENT_H #define KDESUCLIENT_H #include #include #include #ifdef Q_OS_UNIX namespace KDESu { /** \class KDEsuClient client.h kdesu/client.h * 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 QByteArray &command, const QByteArray &user, - const QByteArray &options = 0, + const QByteArray &options = nullptr, const QList &env = QList()); /** * 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 QByteArray &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 QByteArray &command, const QByteArray &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 QByteArray &key, const QByteArray &value, int timeout = 0, const QByteArray &group = 0); + int setVar(const QByteArray &key, const QByteArray &value, int timeout = 0, const QByteArray &group = nullptr); /** * Get a persistent variable. * @param key The name of the variable. * @return Its value. */ QByteArray getVar(const QByteArray &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. */ QList getKeys(const QByteArray &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 QByteArray &group); /** * Delete a persistent variable. * @param key The name of the variable. * @return zero on success, -1 on failure. */ int delVar(const QByteArray &key); /** * Delete all persistent variables with the given key. * * A specicalized variant of delVar(QByteArray) 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 QByteArray &special_key); /** * Delete all persistent variables in a group. * * @param group the group name. See setVar. * @return */ int delGroup(const QByteArray &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 command(const QByteArray &cmd, QByteArray *result = 0L); + int command(const QByteArray &cmd, QByteArray *result = nullptr); QByteArray escape(const QByteArray &str); class KDEsuClientPrivate; KDEsuClientPrivate *const d; }; } //END namespace KDESu #endif //Q_OS_UNIX #endif //KDESUCLIENT_H diff --git a/src/kdesud/kdesud.cpp b/src/kdesud/kdesud.cpp index c9a105e..1d556b5 100644 --- a/src/kdesud/kdesud.cpp +++ b/src/kdesud/kdesud.cpp @@ -1,452 +1,452 @@ /* vi: ts=8 sts=4 sw=4 * * This file is part of the KDE project, module kdesu. * Copyright (C) 1999,2000 Geert Jansen * * * 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 OK Set password for commands in * this session. Password is * valid for seconds. * * USER OK Set the target user [required] * * EXEC OK Execute command . If * NO has been executed * before (< timeout) no PASS * command is needed. * * DEL OK Delete password for command * NO . * * PING OK Ping the server (diagnostics). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include // Needed on some systems. #endif #include #include #include #include #include #include #include #include #include #include #include #include "repo.h" #include "handler.h" #if HAVE_X11 #include #include #endif #ifndef SUN_LEN #define SUN_LEN(ptr) ((socklen_t) \ (offsetof(struct sockaddr_un, sun_path) + strlen ((ptr)->sun_path))) #endif #define ERR strerror(errno) static QLoggingCategory category("org.kde.kdesud"); using namespace KDESu; // Globals Repository *repo; QString Version(QStringLiteral("1.01")); QByteArray sock; #if HAVE_X11 Display *x11Display; #endif int pipeOfDeath[2]; void kdesud_cleanup() { unlink(sock.constData()); } // Borrowed from kdebase/kaudio/kaudioserver.cpp #if HAVE_X11 extern "C" int xio_errhandler(Display *); int xio_errhandler(Display *) { qCritical() << "Fatal IO error, exiting...\n"; kdesud_cleanup(); exit(1); return 1; //silence compilers } int initXconnection() { - x11Display = XOpenDisplay(NULL); - if (x11Display != 0L) + x11Display = XOpenDisplay(nullptr); + if (x11Display != nullptr) { XSetIOErrorHandler(xio_errhandler); XCreateSimpleWindow(x11Display, DefaultRootWindow(x11Display), 0, 0, 1, 1, 0, BlackPixelOfScreen(DefaultScreenOfDisplay(x11Display)), BlackPixelOfScreen(DefaultScreenOfDisplay(x11Display))); return XConnectionNumber(x11Display); } else { qCWarning(category) << "Can't connect to the X Server.\n"; qCWarning(category) << "Might not terminate at end of session.\n"; return -1; } } #endif extern "C" { void signal_exit(int); void sigchld_handler(int); } void signal_exit(int sig) { qCDebug(category) << "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; socklen_t addrlen; struct stat s; QString display = QString::fromLocal8Bit(qgetenv("DISPLAY")); if (display.isEmpty()) { qCWarning(category) << "$DISPLAY is not set\n"; return -1; } // strip the screen number from the display display.replace(QRegExp(QStringLiteral("\\.[0-9]+$")), QString()); sock = QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QStringLiteral("/kdesud_%1").arg(display)); int stat_err=lstat(sock.constData(), &s); if(!stat_err && S_ISLNK(s.st_mode)) { qCWarning(category) << "Someone is running a symlink attack on you\n"; if(unlink(sock.constData())) { qCWarning(category) << "Could not delete symlink\n"; return -1; } } if (!access(sock.constData(), R_OK|W_OK)) { KDEsuClient client; if (client.ping() == -1) { qCWarning(category) << "stale socket exists\n"; if (unlink(sock.constData())) { qCWarning(category) << "Could not delete stale socket\n"; return -1; } } else { qCWarning(category) << "kdesud is already running\n"; return -1; } } sockfd = socket(PF_UNIX, SOCK_STREAM, 0); if (sockfd < 0) { qCritical() << "socket(): " << ERR << "\n"; return -1; } // Ensure socket closed on error struct fd_ScopeGuard { fd_ScopeGuard(int fd) : _fd(fd) { } ~fd_ScopeGuard() { if(_fd >= 0) { close(_fd); } } void reset() { _fd = -1; } int _fd; } guard(sockfd); struct sockaddr_un addr; addr.sun_family = AF_UNIX; strncpy(addr.sun_path, sock.constData(), 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) { qCritical() << "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) { qCritical() << "setsockopt(SO_LINGER): " << ERR << "\n"; return -1; } int opt = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt)) < 0) { qCritical() << "setsockopt(SO_REUSEADDR): " << ERR << "\n"; return -1; } opt = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, sizeof(opt)) < 0) { qCritical() << "setsockopt(SO_KEEPALIVE): " << ERR << "\n"; return -1; } chmod(sock.constData(), 0600); guard.reset(); return sockfd; } /** * Main program */ int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); KAboutData aboutData( QStringLiteral("kdesud") /* componentName */, i18n("KDE su daemon"), Version, i18n("Daemon used by kdesu"), KAboutLicense::Artistic, i18n("Copyright (c) 1999,2000 Geert Jansen")); aboutData.addAuthor(i18n("Geert Jansen"), i18n("Author"), QStringLiteral("jansen@kde.org"), QStringLiteral("http://www.stack.nl/~geertj/")); KAboutData::setApplicationData(aboutData); QCommandLineParser parser; aboutData.setupCommandLine(&parser); parser.addHelpOption(); parser.addVersionOption(); parser.process(app); aboutData.processCommandLine(&parser); // Set core dump size to 0 struct rlimit rlim; rlim.rlim_cur = rlim.rlim_max = 0; if (setrlimit(RLIMIT_CORE, &rlim) < 0) { qCritical() << "setrlimit(): " << ERR << "\n"; exit(1); } // Create the Unix socket. int sockfd = create_socket(); if (sockfd < 0) exit(1); if (listen(sockfd, 10) < 0) { qCritical() << "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) { qCritical() << "fork():" << ERR << "\n"; kdesud_cleanup(); exit(1); } if (pid) _exit(0); #if HAVE_X11 // Make sure we exit when the display gets closed. int x11Fd = initXconnection(); maxfd = qMax(maxfd, x11Fd); #endif repo = new Repository; QVector handler; 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); + sigaction(SIGHUP, &sa, nullptr); + sigaction(SIGINT, &sa, nullptr); + sigaction(SIGTERM, &sa, nullptr); + sigaction(SIGQUIT, &sa, nullptr); sa.sa_handler = sigchld_handler; sa.sa_flags = SA_NOCLDSTOP; - sigaction(SIGCHLD, &sa, 0L); + sigaction(SIGCHLD, &sa, nullptr); sa.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &sa, 0L); + sigaction(SIGPIPE, &sa, nullptr); // Main execution loop socklen_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 HAVE_X11 if (x11Fd != -1) FD_SET(x11Fd, &active_fds); #endif while (1) { tmp_fds = active_fds; #if HAVE_X11 if(x11Display) XFlush(x11Display); #endif - if (select(maxfd+1, &tmp_fds, 0L, 0L, 0L) < 0) + if (select(maxfd+1, &tmp_fds, nullptr, nullptr, nullptr) < 0) { if (errno == EINTR) continue; qCritical() << "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 HAVE_X11 if (i == x11Fd) { // Discard X events XEvent event_return; if (x11Display) while(XPending(x11Display)) XNextEvent(x11Display, &event_return); continue; } #endif if (i == sockfd) { // Accept new connection int fd; addrlen = 64; fd = accept(sockfd, (struct sockaddr *) &clientname, &addrlen); if (fd < 0) { qCritical() << "accept():" << ERR << "\n"; continue; } while (fd+1 > (int) handler.size()) - handler.append(0); + handler.append(nullptr); delete handler[fd]; handler[fd] = new ConnectionHandler(fd); maxfd = qMax(maxfd, fd); FD_SET(fd, &active_fds); fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); continue; } // handle already established connection if (handler[i] && handler[i]->handle() < 0) { delete handler[i]; - handler[i] = 0; + handler[i] = nullptr; FD_CLR(i, &active_fds); } } } qCWarning(category) << "???\n"; } diff --git a/src/kdesud/repo.cpp b/src/kdesud/repo.cpp index a8f5caf..a0d2b01 100644 --- a/src/kdesud/repo.cpp +++ b/src/kdesud/repo.cpp @@ -1,188 +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 */ #include "repo.h" #include #include #include #include #include static QLoggingCategory category("org.kde.kdesud"); Repository::Repository() { head_time = (unsigned) -1; } Repository::~Repository() {} void Repository::add(const QByteArray &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); + data.timeout += time(nullptr); head_time = qMin(head_time, data.timeout); repo.insert(key, data); } int Repository::remove(const QByteArray &key) { if( key.isEmpty() ) return -1; RepoIterator it = repo.find(key); if (it == repo.end()) return -1; it.value().value.fill('x'); it.value().group.fill('x'); repo.erase(it); return 0; } int Repository::removeSpecialKey(const QByteArray &key) { int found = -1; if ( !key.isEmpty() ) { QStack rm_keys; for (RepoCIterator it=repo.constBegin(); it!=repo.constEnd(); ++it) { if ( key.indexOf( it.value().group ) == 0 && it.key().indexOf( key ) >= 0 ) { rm_keys.push(it.key()); found = 0; } } while (!rm_keys.isEmpty()) { qCDebug(category) << "Removed key: " << rm_keys.top(); remove(rm_keys.pop()); } } return found; } int Repository::removeGroup(const QByteArray &group) { int found = -1; if ( !group.isEmpty() ) { QStack rm_keys; for (RepoCIterator it=repo.constBegin(); it!=repo.constEnd(); ++it) { if (it.value().group == group) { rm_keys.push(it.key()); found = 0; } } while (!rm_keys.isEmpty()) { qCDebug(category) << "Removed key: " << rm_keys.top(); remove(rm_keys.pop()); } } return found; } int Repository::hasGroup(const QByteArray &group) const { if ( !group.isEmpty() ) { RepoCIterator it; for (it=repo.begin(); it!=repo.end(); ++it) { if (it.value().group == group) return 0; } } return -1; } QByteArray Repository::findKeys(const QByteArray &group, const char *sep ) const { QByteArray list=""; if( !group.isEmpty() ) { qCDebug(category) << "Looking for matching key with group key: " << group; int pos; QByteArray key; RepoCIterator it; for (it=repo.begin(); it!=repo.end(); ++it) { if (it.value().group == group) { key = it.key(); qCDebug(category) << "Matching key found: " << key; pos = key.lastIndexOf(sep); key.truncate( pos ); key.remove(0, 2); if (!list.isEmpty()) { // Add the same keys only once please :) if( !list.contains(key) ) { qCDebug(category) << "Key added to list: " << key; list += '\007'; // I do not know list.append(key); } } else list = key; } } } return list; } QByteArray Repository::find(const QByteArray &key) const { if( key.isEmpty() ) - return 0; + return nullptr; RepoCIterator it = repo.find(key); if (it == repo.end()) - return 0; + return nullptr; return it.value().value; } int Repository::expire() { - unsigned current = time(0L); + unsigned current = time(nullptr); if (current < head_time) return 0; unsigned t; QStack keys; head_time = (unsigned) -1; RepoIterator it; for (it=repo.begin(); it!=repo.end(); ++it) { t = it.value().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/src/ptyprocess.cpp b/src/ptyprocess.cpp index 2a775ab..48b387e 100644 --- a/src/ptyprocess.cpp +++ b/src/ptyprocess.cpp @@ -1,529 +1,529 @@ /* * This file is part of the KDE project, module kdesu. * Copyright (C) 1999,2000 Geert Jansen * * This file contains code from TEShell.C of the KDE konsole. * Copyright (c) 1997,1998 by Lars Doelle * * 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 "ptyprocess.h" #include "kcookie_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_SYS_SELECT_H #include // Needed on some systems. #endif #include #include #include #include #include extern int kdesuDebugArea(); namespace KDESu { using namespace KDESuPrivate; /* ** Wait for @p ms miliseconds ** @param fd file descriptor ** @param ms time to wait in miliseconds ** @return */ 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); + return select(fd + 1, &fds, nullptr, nullptr, &tv); } // XXX this function is nonsense: // - for our child, we could use waitpid(). // - the configurability at this place it *complete* braindamage /* ** Basic check for the existence of @p pid. ** Returns true iff @p pid is an extant process. */ bool PtyProcess::checkPid(pid_t pid) { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup cg(config, "super-user-command"); QString superUserCommand = cg.readEntry("super-user-command", "sudo"); //sudo does not accept signals from user so we except it if (superUserCommand == QLatin1String("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) { qCritical() << "[" << __FILE__ << ":" << __LINE__ << "] " << "waitpid():" << strerror(errno); return Error; } if (ret == pid) { if (WIFEXITED(state)) { return WEXITSTATUS(state); } return Killed; } return NotExited; } class PtyProcess::PtyProcessPrivate { public: - PtyProcessPrivate() : pty(0L) {} + PtyProcessPrivate() : pty(nullptr) {} ~PtyProcessPrivate() { delete pty; } QList env; KPty *pty; QByteArray inputBuffer; }; PtyProcess::PtyProcess() : d(new PtyProcessPrivate) { m_terminal = false; m_erase = false; } PtyProcess::~PtyProcess() { delete d; } int PtyProcess::init() { delete d->pty; d->pty = new KPty(); if (!d->pty->open()) { qCritical() << "[" << __FILE__ << ":" << __LINE__ << "] " << "Failed to open PTY."; return -1; } d->inputBuffer.resize(0); return 0; } /** Set additional environment variables. */ void PtyProcess::setEnvironment(const QList &env) { d->env = env; } int PtyProcess::fd() const { return d->pty ? d->pty->masterFd() : -1; } int PtyProcess::pid() const { return m_pid; } /** Returns the additional environment variables set by setEnvironment() */ QList PtyProcess::environment() const { return d->env; } QByteArray PtyProcess::readAll(bool block) { QByteArray ret; if (!d->inputBuffer.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 = d->inputBuffer; d->inputBuffer.resize(0); } int flags = fcntl(fd(), F_GETFL); if (flags < 0) { qCritical() << "[" << __FILE__ << ":" << __LINE__ << "] " << "fcntl(F_GETFL):" << strerror(errno); return ret; } int oflags = flags; if (block) { flags &= ~O_NONBLOCK; } else { flags |= O_NONBLOCK; } if ((flags != oflags) && (fcntl(fd(), F_SETFL, flags) < 0)) { // We get an error here when the child process has closed // the file descriptor already. return ret; } while (1) { ret.reserve(ret.size() + 0x8000); int nbytes = read(fd(), ret.data() + ret.size(), 0x8000); if (nbytes == -1) { if (errno == EINTR) { continue; } else { break; } } if (nbytes == 0) { break; // nothing available / eof } ret.resize(ret.size() + nbytes); break; } return ret; } QByteArray PtyProcess::readLine(bool block) { d->inputBuffer = readAll(block); int pos; QByteArray ret; if (!d->inputBuffer.isEmpty()) { pos = d->inputBuffer.indexOf('\n'); if (pos == -1) { // NOTE: this means we return something even if there in no full line! ret = d->inputBuffer; d->inputBuffer.resize(0); } else { ret = d->inputBuffer.left(pos); d->inputBuffer.remove(0, pos + 1); } } return ret; } void PtyProcess::writeLine(const QByteArray &line, bool addnl) { if (!line.isEmpty()) { write(fd(), line.constData(), line.length()); } if (addnl) { write(fd(), "\n", 1); } } void PtyProcess::unreadLine(const QByteArray &line, bool addnl) { QByteArray tmp = line; if (addnl) { tmp += '\n'; } if (!tmp.isEmpty()) { d->inputBuffer.prepend(tmp); } } void PtyProcess::setExitString(const QByteArray &exit) { m_exitString = exit; } /* * Fork and execute the command. This returns in the parent. */ int PtyProcess::exec(const QByteArray &command, const QList &args) { int i; if (init() < 0) { return -1; } if ((m_pid = fork()) == -1) { qCritical() << "[" << __FILE__ << ":" << __LINE__ << "] " << "fork():" << strerror(errno); return -1; } // Parent if (m_pid) { d->pty->closeSlave(); return 0; } // Child if (setupTTY() < 0) { _exit(1); } for (i = 0; i < d->env.count(); ++i) { putenv(const_cast(d->env.at(i).constData())); } unsetenv("KDE_FULL_SESSION"); // for : Qt: Session management error unsetenv("SESSION_MANAGER"); // QMutex::lock , deadlocks without that. // you cannot connect to the user's session bus from another UID unsetenv("DBUS_SESSION_BUS_ADDRESS"); // set temporarily LC_ALL to C, for su (to be able to parse "Password:") const QByteArray old_lc_all = qgetenv("LC_ALL"); if (!old_lc_all.isEmpty()) { qputenv("KDESU_LC_ALL", old_lc_all); } else { unsetenv("KDESU_LC_ALL"); } qputenv("LC_ALL", "C"); // From now on, terminal output goes through the tty. QByteArray path; if (command.contains('/')) { path = command; } else { QString file = QStandardPaths::findExecutable(QFile::decodeName(command)); if (file.isEmpty()) { qCritical() << "[" << __FILE__ << ":" << __LINE__ << "] " << command << "not found."; _exit(1); } path = QFile::encodeName(file); } const char **argp = (const char **)malloc((args.count() + 2) * sizeof(char *)); i = 0; argp[i++] = path.constData(); for (QList::ConstIterator it = args.begin(); it != args.end(); ++it, ++i) { argp[i] = (*it).constData(); } - argp[i] = NULL; + argp[i] = nullptr; execv(path.constData(), const_cast(argp)); qCritical() << "[" << __FILE__ << ":" << __LINE__ << "] " << "execv(" << path << "):" << strerror(errno); _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() { struct termios tio; while (1) { if (!checkPid(m_pid)) { qCritical() << "process has exited while waiting for password."; return -1; } if (!d->pty->tcGetAttr(&tio)) { qCritical() << "[" << __FILE__ << ":" << __LINE__ << "] " << "tcgetattr():" << strerror(errno); return -1; } if (tio.c_lflag & ECHO) { // qDebug() << "[" << __FILE__ << ":" << __LINE__ << "] " << "Echo mode still on."; usleep(10000); continue; } break; } return 0; } int PtyProcess::enableLocalEcho(bool enable) { return d->pty->setEcho(enable) ? 0 : -1; } void PtyProcess::setTerminal(bool terminal) { m_terminal = terminal; } void PtyProcess::setErase(bool erase) { m_erase = erase; } /* * Copy output to stdout until the child process exits, or a line of output * matches `m_exitString'. * 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() { fd_set fds; FD_ZERO(&fds); QByteArray remainder; while (1) { FD_SET(fd(), &fds); // specify timeout to make sure select() does not block, even if the // process is dead / non-responsive. It does not matter if we abort too // early. In that case 0 is returned, and we'll try again in the next // iteration. (As long as we don't consitently time out in each iteration) timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 100000; - int ret = select(fd() + 1, &fds, 0L, 0L, &timeout); + int ret = select(fd() + 1, &fds, nullptr, nullptr, &timeout); if (ret == -1) { if (errno != EINTR) { qCritical() << "[" << __FILE__ << ":" << __LINE__ << "] " << "select():" << strerror(errno); return -1; } ret = 0; } if (ret) { forever { QByteArray output = readAll(false); if (output.isEmpty()) { break; } if (m_terminal) { fwrite(output.constData(), output.size(), 1, stdout); fflush(stdout); } if (!m_exitString.isEmpty()) { // match exit string only at line starts remainder += output; while (remainder.length() >= m_exitString.length()) { if (remainder.startsWith(m_exitString)) { kill(m_pid, SIGTERM); remainder.remove(0, m_exitString.length()); } int off = remainder.indexOf('\n'); if (off < 0) { break; } remainder.remove(0, off + 1); } } } } ret = checkPidExited(m_pid); if (ret == Error) { if (errno == ECHILD) { return 0; } else { return 1; } } else if (ret == Killed) { return 0; } else if (ret == NotExited) { continue; // keep checking } else { return ret; } } } /* * 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() { // Reset signal handlers for (int sig = 1; sig < NSIG; sig++) { signal(sig, SIG_DFL); } signal(SIGHUP, SIG_IGN); d->pty->setCTty(); // Connect stdin, stdout and stderr int slave = d->pty->slaveFd(); dup2(slave, 0); dup2(slave, 1); dup2(slave, 2); // Close all file handles // XXX this caused problems in KProcess - not sure why anymore. -- ??? // Because it will close the start notification pipe. -- ossi struct rlimit rlp; getrlimit(RLIMIT_NOFILE, &rlp); for (int i = 3; i < (int)rlp.rlim_cur; i++) { close(i); } // Disable OPOST processing. Otherwise, '\n' are (on Linux at least) // translated to '\r\n'. struct ::termios tio; if (tcgetattr(0, &tio) < 0) { qCritical() << "[" << __FILE__ << ":" << __LINE__ << "] " << "tcgetattr():" << strerror(errno); return -1; } tio.c_oflag &= ~OPOST; if (tcsetattr(0, TCSANOW, &tio) < 0) { qCritical() << "[" << __FILE__ << ":" << __LINE__ << "] " << "tcsetattr():" << strerror(errno); return -1; } return 0; } void PtyProcess::virtual_hook(int id, void *data) { Q_UNUSED(id); Q_UNUSED(data); /*BASE::virtual_hook( id, data );*/ } } // namespace KDESu diff --git a/src/sshprocess.cpp b/src/sshprocess.cpp index ad302f9..10020f7 100644 --- a/src/sshprocess.cpp +++ b/src/sshprocess.cpp @@ -1,237 +1,237 @@ /* * This file is part of the KDE project, module kdesu. * Copyright (C) 2000 Geert Jansen * * 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 "sshprocess.h" #include "kcookie_p.h" #include #include #include #include extern int kdesuDebugArea(); namespace KDESu { using namespace KDESuPrivate; class SshProcess::SshProcessPrivate { public: SshProcessPrivate(const QByteArray &host) : host(host) , stub("kdesu_stub") {} QByteArray prompt; QByteArray host; QByteArray error; QByteArray stub; }; SshProcess::SshProcess(const QByteArray &host, const QByteArray &user, const QByteArray &command) : d(new SshProcessPrivate(host)) { m_user = user; m_command = command; - srand(time(0L)); + srand(time(nullptr)); } SshProcess::~SshProcess() { delete d; } void SshProcess::setHost(const QByteArray &host) { d->host = host; } void SshProcess::setStub(const QByteArray &stub) { d->stub = stub; } int SshProcess::checkInstall(const char *password) { return exec(password, 1); } int SshProcess::checkNeedPassword() { - return exec(0L, 2); + return exec(nullptr, 2); } int SshProcess::exec(const char *password, int check) { if (check) { setTerminal(true); } QList args; args += "-l"; args += m_user; args += "-o"; args += "StrictHostKeyChecking=no"; args += d->host; args += d->stub; if (StubProcess::exec("ssh", args) < 0) { return check ? SshNotFound : -1; } int ret = converseSsh(password, check); if (ret < 0) { if (!check) { qCritical() << "[" << __FILE__ << ":" << __LINE__ << "] " << "Conversation with ssh failed."; } return ret; } if (check == 2) { if (ret == 1) { kill(m_pid, SIGTERM); waitForChild(); } return ret; } if (m_erase && password) { memset(const_cast(password), 0, qstrlen(password)); } ret = converseStub(check); if (ret < 0) { if (!check) { qCritical() << "[" << __FILE__ << ":" << __LINE__ << "] " << "Conversation with kdesu_stub failed."; } 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; } QByteArray SshProcess::prompt() const { return d->prompt; } QByteArray SshProcess::error() const { return d->error; } /* * 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 prompt. Used for checking the need for * a password. */ int SshProcess::converseSsh(const char *password, int check) { unsigned i, j, colon; QByteArray 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) { d->prompt = line; return SshNeedsPassword; } if (waitSlave()) { return -1; } write(fd(), password, strlen(password)); write(fd(), "\n", 1); state++; break; } // Warning/error message. d->error += line; d->error += '\n'; if (m_terminal) { fprintf(stderr, "ssh: %s\n", line.constData()); } break; case 1: if (line.isEmpty()) { state++; break; } return -1; } } return 0; } // Display redirection is handled by ssh natively. QByteArray SshProcess::display() { return "no"; } QByteArray SshProcess::displayAuth() { return "no"; } void SshProcess::virtual_hook(int id, void *data) { StubProcess::virtual_hook(id, data); } } // namespace KDESu diff --git a/src/stubprocess.cpp b/src/stubprocess.cpp index 2435726..73a9afe 100644 --- a/src/stubprocess.cpp +++ b/src/stubprocess.cpp @@ -1,246 +1,246 @@ /* * This file is part of the KDE project, module kdesu. * Copyright (C) 1999,2000 Geert Jansen * * 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. * * stubprocess.cpp: Conversation with kdesu_stub. */ #include "stubprocess.h" #include "kcookie_p.h" #include #include #include extern int kdesuDebugArea(); namespace KDESu { using namespace KDESuPrivate; StubProcess::StubProcess() - : d(0) + : d(nullptr) { m_user = "root"; m_scheduler = SchedNormal; m_priority = 50; m_cookie = new KCookie; m_XOnly = true; } StubProcess::~StubProcess() { delete m_cookie; } void StubProcess::setCommand(const QByteArray &command) { m_command = command; } void StubProcess::setUser(const QByteArray &user) { m_user = user; } void StubProcess::setXOnly(bool xonly) { m_XOnly = xonly; } void StubProcess::setPriority(int prio) { if (prio > 100) { m_priority = 100; } else if (prio < 0) { m_priority = 0; } else { m_priority = prio; } } void StubProcess::setScheduler(int sched) { m_scheduler = sched; } QByteArray StubProcess::commaSeparatedList(const QList &lst) { QByteArray str; for (int i = 0; i < lst.count(); ++i) { str += ','; str += lst.at(i); } return str; } void StubProcess::writeString(const QByteArray &str) { QByteArray out; out.reserve(str.size() + 8); for (int i = 0; i < str.size(); i++) { uchar c = str.at(i); if (c < 32) { out.append('\\'); out.append(c + '@'); } else if (c == '\\') { out.append('\\'); out.append('/'); } else { out.append(c); } } writeLine(out); } /* * Map pid_t to a signed integer type that makes sense for QByteArray; * only the most common sizes 16 bit and 32 bit are special-cased. */ template struct PIDType { typedef pid_t PID_t; }; template<> struct PIDType<2> { typedef qint16 PID_t; }; template<> struct PIDType<4> { typedef qint32 PID_t; }; /* * Conversation with kdesu_stub. This is how we pass the authentication * tokens (X11) and other stuff to kdesu_stub. * return values: -1 = error, 0 = ok, 1 = kill me */ int StubProcess::converseStub(int check) { QByteArray 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"); } break; } } while (1) { line = readLine(); if (line.isNull()) { return -1; } if (line == "display") { writeLine(display()); } else if (line == "display_auth") { #if HAVE_X11 writeLine(displayAuth()); #else writeLine(""); #endif } else if (line == "command") { writeString(m_command); } else if (line == "path") { QByteArray path = qgetenv("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_XOnly) { writeLine("no"); } else { writeLine("yes"); } } else if (line == "app_startup_id") { QList env = environment(); QByteArray tmp; for (int i = 0; i < env.count(); ++i) { const char startup_env[] = "DESKTOP_STARTUP_ID="; if (env.at(i).startsWith(startup_env)) { tmp = env.at(i).mid(sizeof(startup_env) - 1); } } if (tmp.isEmpty()) { tmp = "0"; // krazy:exclude=doublequote_chars } writeLine(tmp); } else if (line == "app_start_pid") { // obsolete // Force the pid_t returned from getpid() into // something QByteArray understands; avoids ambiguity // between short and unsigned short in particular. tmp.setNum((PIDType::PID_t)(getpid())); writeLine(tmp); } else if (line == "environment") { // additional env vars QList env = environment(); for (int i = 0; i < env.count(); ++i) { writeString(env.at(i)); } writeLine(""); } else if (line == "end") { return 0; } else { qWarning() << "[" << __FILE__ << ":" << __LINE__ << "] " << "Unknown request:" << line; return 1; } } return 0; } QByteArray StubProcess::display() { return m_cookie->display(); } QByteArray StubProcess::displayAuth() { #if HAVE_X11 return m_cookie->displayAuth(); #else return QByteArray(); #endif } void StubProcess::virtual_hook(int id, void *data) { PtyProcess::virtual_hook(id, data); } } // namespace KDESu diff --git a/src/suprocess.cpp b/src/suprocess.cpp index a4a9c78..daa367d 100644 --- a/src/suprocess.cpp +++ b/src/suprocess.cpp @@ -1,296 +1,296 @@ /* * This file is part of the KDE project, module kdesu. * Copyright (C) 1999,2000 Geert Jansen * * Sudo support added by Jonathan Riddell * Copyright (C) 2005 Canonical Ltd // krazy:exclude=copyright (no email) * * 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 "suprocess.h" #include "kcookie_p.h" #include #include #include #include #include #include #include #include #ifndef __PATH_SU #define __PATH_SU "false" #endif #ifndef __PATH_SUDO #define __PATH_SUDO "false" #endif #ifdef KDESU_USE_SUDO_DEFAULT # define DEFAULT_SUPER_USER_COMMAND QStringLiteral("sudo") #else # define DEFAULT_SUPER_USER_COMMAND QStringLiteral("su") #endif namespace KDESu { using namespace KDESuPrivate; class SuProcess::SuProcessPrivate { public: QString superUserCommand; }; SuProcess::SuProcess(const QByteArray &user, const QByteArray &command) : d(new SuProcessPrivate) { m_user = user; m_command = command; KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group(config, "super-user-command"); d->superUserCommand = group.readEntry("super-user-command", DEFAULT_SUPER_USER_COMMAND); if (d->superUserCommand != QLatin1String("sudo") && d->superUserCommand != QLatin1String("su")) { qWarning() << "unknown super user command."; d->superUserCommand = DEFAULT_SUPER_USER_COMMAND; } } SuProcess::~SuProcess() { delete d; } QString SuProcess::superUserCommand() { return d->superUserCommand; } bool SuProcess::useUsersOwnPassword() { if (superUserCommand() == QLatin1String("sudo") && m_user == "root") { return true; } KUser user; return user.loginName() == QString::fromUtf8(m_user); } int SuProcess::checkInstall(const char *password) { return exec(password, Install); } int SuProcess::checkNeedPassword() { - return exec(0L, NeedPassword); + return exec(nullptr, 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 != QByteArray("root")) { d->superUserCommand = QStringLiteral("su"); } QList args; if (d->superUserCommand == QLatin1String("sudo")) { args += "-u"; } if (m_scheduler != SchedNormal || m_priority > 50) { args += "root"; } else { args += m_user; } if (d->superUserCommand == QLatin1String("su")) { args += "-c"; } args += QByteArray(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5) + "/kdesu_stub"; args += "-"; // krazy:exclude=doublequote_chars (QList, not QString) QByteArray command; if (d->superUserCommand == QLatin1String("sudo")) { command = __PATH_SUDO; } else { command = __PATH_SU; } if (QT_ACCESS(command.constData(), X_OK) != 0) { command = QFile::encodeName(QStandardPaths::findExecutable(d->superUserCommand)); if (command.isEmpty()) { return check ? SuNotFound : -1; } } if (StubProcess::exec(command, args) < 0) { return check ? SuNotFound : -1; } SuErrors ret = (SuErrors)converseSU(password); if (ret == error) { if (!check) { qCritical() << "[" << __FILE__ << ":" << __LINE__ << "] " << "Conversation with su failed."; } return ret; } if (check == NeedPassword) { if (ret == killme) { if (d->superUserCommand == QLatin1String("sudo")) { // sudo can not be killed, just return return ret; } if (kill(m_pid, SIGKILL) < 0) { //FIXME SIGKILL doesn't work for sudo, //why is this different from su? //A: because sudo runs as root. Perhaps we could write a Ctrl+C to its stdin, instead? ret = error; } else { int iret = waitForChild(); if (iret < 0) { ret = error; } } } return ret; } if (m_erase && password) { memset(const_cast(password), 0, qstrlen(password)); } if (ret != ok) { kill(m_pid, SIGKILL); if (d->superUserCommand != QLatin1String("sudo")) { waitForChild(); } return SuIncorrectPassword; } int iret = converseStub(check); if (iret < 0) { if (!check) { qCritical() << "[" << __FILE__ << ":" << __LINE__ << "] " << "Conversation with kdesu_stub failed."; } 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; QByteArray line; while (true) { line = readLine(); if (line.isNull()) { return (state == HandleStub ? notauthorized : error); } if (line == "kdesu_stub") { unreadLine(line); return ok; } switch (state) { case WaitForPrompt: { if (waitMS(fd(), 100) > 0) { // There is more output available, so this 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). continue; } const uint len = line.length(); // 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 (password == 0L) { + if (password == nullptr) { return killme; } if (waitSlave()) { return error; } write(fd(), password, strlen(password)); write(fd(), "\n", 1); state = CheckStar; } break; } ////////////////////////////////////////////////////////////////////////// case CheckStar: { QByteArray s = line.trimmed(); 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: break; ////////////////////////////////////////////////////////////////////////// } // end switch } // end while (true) return ok; } void SuProcess::virtual_hook(int id, void *data) { StubProcess::virtual_hook(id, data); } } // namespace KDESu diff --git a/src/suprocess.h b/src/suprocess.h index 1fbf98e..8a17a83 100644 --- a/src/suprocess.h +++ b/src/suprocess.h @@ -1,90 +1,90 @@ /* * This file is part of the KDE project, module kdesu. * Copyright (C) 1999,2000 Geert Jansen * * 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 KDESUSU_H #define KDESUSU_H #include #include "stubprocess.h" namespace KDESu { /** \class SuProcess kdesu/suprocess.h * Executes a command under elevated privileges, using su. */ class KDESU_EXPORT SuProcess : public StubProcess { public: 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 }; - explicit SuProcess(const QByteArray &user = 0, const QByteArray &command = 0); + explicit SuProcess(const QByteArray &user = nullptr, const QByteArray &command = nullptr); ~SuProcess(); 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(); /** * Checks what the default super user command is, e.g. sudo, su, etc * @return the default super user command */ QString superUserCommand(); /** * Checks whether or not the user's password is being asked for or another * user's password. Due to usage of systems such as sudo, even when attempting * to switch to another user one may need to enter their own password. */ bool useUsersOwnPassword(); protected: void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; private: enum SuErrors { error = -1, ok = 0, killme = 1, notauthorized = 2 }; int converseSU(const char *password); class SuProcessPrivate; SuProcessPrivate *const d; }; } #endif //KDESUSU_H