diff --git a/CMakeLists.txt b/CMakeLists.txt index ef2f0b8..c7700e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,231 +1,245 @@ set(PROJECT_VERSION "5.10.0") set(PROJECT_VERSION_MAJOR 5) cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(QT_MIN_VERSION "5.4.0") set(KF5_MIN_VERSION "5.15.0") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Widgets Quick QuickWidgets Test) find_package(ECM 1.8.0 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMSetupVersion) include(ECMGenerateHeaders) include(CheckIncludeFiles) include(ECMMarkNonGuiExecutable) include(ECMPackageConfigHelpers) include(FeatureSummary) include(GenerateExportHeader) include(CheckIncludeFile) include(CheckSymbolExists) check_include_file("sys/prctl.h" HAVE_SYS_PRCTL_H) check_symbol_exists(PR_SET_DUMPABLE "sys/prctl.h" HAVE_PR_SET_DUMPABLE) check_include_file("sys/procctl.h" HAVE_SYS_PROCCTL_H) check_symbol_exists(PROC_TRACE_CTL "sys/procctl.h" HAVE_PROC_TRACE_CTL) if (HAVE_PR_SET_DUMPABLE OR HAVE_PROC_TRACE_CTL) set(CAN_DISABLE_PTRACE TRUE) endif () add_feature_info("prctl/procctl tracing control" CAN_DISABLE_PTRACE "Required for disallowing ptrace on greeter and kcheckpass process") +check_include_file("sys/signalfd.h" HAVE_SIGNALFD_H) +if (NOT HAVE_SIGNALFD_H) + check_include_file("sys/event.h" HAVE_EVENT_H) +endif () +if (NOT (HAVE_SIGNALFD_H OR HAVE_EVENT_H)) + message(FATAL_ERROR "kcheckpass either needs signalfd() or kevent()&sigtimedwait() to work") +endif () +add_feature_info("sys/signalfd.h" + HAVE_SIGNALFD_H + "Use the signalfd() api for signalhandling") +add_feature_info("sys/event.h" + HAVE_EVENT_H + "Use the kevent() and sigwaitinfo() api for signalhandling") + find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Crash Declarative GlobalAccel I18n IdleTime KCMUtils Notifications Solid TextWidgets WindowSystem XmlGui ) find_package(X11) set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" URL "http://www.x.org" TYPE REQUIRED PURPOSE "Required for building the X11 based workspace") find_package(XCB MODULE REQUIRED COMPONENTS XCB KEYSYMS XTEST) set_package_properties(XCB PROPERTIES TYPE REQUIRED) add_feature_info("XInput" X11_Xinput_FOUND "Required for grabbing XInput2 devices in the screen locker") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS X11Extras) find_package(KF5Wayland CONFIG REQUIRED) set_package_properties(KF5Wayland PROPERTIES TYPE REQUIRED PURPOSE "Required for building screenlocker") find_package(WaylandScanner) find_package(Wayland 1.3 COMPONENTS Client Server) set_package_properties(Wayland PROPERTIES TYPE REQUIRED PURPOSE "Required for building screenlocker") find_package(loginctl) set_package_properties(loginctl PROPERTIES URL "https://www.freedesktop.org/software/systemd/man/loginctl.html" DESCRIPTION "Send control commands to the login manager" TYPE RUNTIME PURPOSE "Needed for emergency unlock in case that the greeter is broken. In case your distribution does not provide loginctl please contact plasma-devel@kde.org to discuss alternatives." ) option(PAM_REQUIRED "Require building with PAM" ON) find_package(Seccomp) set_package_properties(Seccomp PROPERTIES TYPE OPTIONAL PURPOSE "Used for putting the look'n'feel package in the greeter into a sandbox." ) set(HAVE_SECCOMP ${Seccomp_FOUND}) include(ConfigureChecks.cmake) configure_file(config-workspace.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-workspace.h) configure_file(config-unix.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-unix.h ) configure_file(config-X11.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-X11.h) # adjusting CMAKE_C_FLAGS to get wayland protocols to compile set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu90") ecm_setup_version(${PROJECT_VERSION} VARIABLE_PREFIX KSCREENLOCKER VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kscreenlocker_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KScreenLockerConfigVersion.cmake" SOVERSION 5) configure_file(config-kscreenlocker.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kscreenlocker.h) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(KSLD_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}/KScreenLocker") add_subdirectory(kcheckpass) add_subdirectory(greeter) add_subdirectory(kcm) add_definitions(-DTRANSLATION_DOMAIN=\"kscreenlocker\") set(screensaver_dbusXML dbus/org.freedesktop.ScreenSaver.xml) set(kscreensaver_dbusXML dbus/org.kde.screensaver.xml) set(powerdevilpolicyagent_xml dbus/kf5_org.kde.Solid.PowerManagement.PolicyAgent.xml) set(ksld_SRCS abstractlocker.cpp ksldapp.cpp interface.cpp globalaccel.cpp x11locker.cpp waylandlocker.cpp logind.cpp waylandserver.cpp powermanagement.cpp powermanagement_inhibition.cpp ) qt5_add_dbus_adaptor(ksld_SRCS ${screensaver_dbusXML} interface.h ScreenLocker::Interface) qt5_add_dbus_adaptor(ksld_SRCS ${kscreensaver_dbusXML} interface.h ScreenLocker::Interface kscreensaveradaptor KScreenSaverAdaptor) kconfig_add_kcfg_files(ksld_SRCS kcfg/kscreensaversettings.kcfgc) qt5_add_dbus_interface(ksld_SRCS ${powerdevilpolicyagent_xml} powerdevilpolicyagent) ecm_add_wayland_server_protocol(ksld_SRCS PROTOCOL protocols/ksld.xml BASENAME ksld ) add_library(KScreenLocker SHARED ${ksld_SRCS}) target_link_libraries(KScreenLocker PUBLIC Qt5::Core Qt5::X11Extras PRIVATE KF5::I18n KF5::IdleTime KF5::GlobalAccel KF5::Notifications KF5::CoreAddons KF5::ConfigGui KF5::WindowSystem ${X11_LIBRARIES} XCB::XCB XCB::KEYSYMS KF5::WaylandServer Wayland::Server ) if (X11_Xinput_FOUND) target_link_libraries(KScreenLocker PRIVATE ${X11_Xinput_LIB}) endif() target_include_directories(KScreenLocker INTERFACE "$") # Needed to compile on Arm target. set_target_properties(KScreenLocker PROPERTIES COMPILE_FLAGS "-fPIC") add_library(PW::KScreenLocker ALIAS KScreenLocker) generate_export_header(KScreenLocker BASE_NAME KScreenLocker EXPORT_MACRO_NAME KSCREENLOCKER_EXPORT EXPORT_FILE_NAME KScreenLocker/kscreenlocker_export.h ) ecm_configure_package_config_file(ScreenSaverDBusInterfaceConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/ScreenSaverDBusInterfaceConfig.cmake PATH_VARS KDE_INSTALL_DBUSINTERFACEDIR INSTALL_DESTINATION "${CMAKECONFIG_INSTALL_PREFIX}/ScreenSaverDBusInterface") set_target_properties(KScreenLocker PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ScreenSaverDBusInterfaceConfig.cmake DESTINATION "${CMAKECONFIG_INSTALL_PREFIX}/ScreenSaverDBusInterface") ecm_configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KScreenLockerConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KScreenLockerConfig.cmake" INSTALL_DESTINATION "${CMAKECONFIG_INSTALL_PREFIX}/KScreenLocker") install(TARGETS KScreenLocker EXPORT KScreenLockerTargets ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY) install(EXPORT KScreenLockerTargets DESTINATION "${CMAKECONFIG_INSTALL_PREFIX}/KScreenLocker" FILE KScreenLockerTargets.cmake NAMESPACE PW::) ecm_generate_headers(KScreenLocker_CamelCase_HEADERS HEADER_NAMES KsldApp REQUIRED_HEADERS KScreenLocker_HEADERS) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/KScreenLocker/kscreenlocker_export.h ${KScreenLocker_CamelCase_HEADERS} ${KScreenLocker_HEADERS} DESTINATION ${KSLD_INCLUDEDIR}/KScreenLocker COMPONENT Devel) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KScreenLockerConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KScreenLockerConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_PREFIX}/KScreenLocker" COMPONENT Devel) install(FILES kscreenlocker.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFY5RCDIR} RENAME ksmserver.notifyrc) install(FILES ${screensaver_dbusXML} DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} RENAME kf5_org.freedesktop.ScreenSaver.xml) install(FILES updaters/kscreenlocker.upd DESTINATION ${KDE_INSTALL_DATADIR}/kconf_update) install(PROGRAMS updaters/ksreenlocker_5_3_separate_autologin.pl DESTINATION ${KDE_INSTALL_DATADIR}/kconf_update) add_subdirectory(autotests) add_subdirectory(tests) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/config-kscreenlocker.h.cmake b/config-kscreenlocker.h.cmake index 276e272..8cd536a 100644 --- a/config-kscreenlocker.h.cmake +++ b/config-kscreenlocker.h.cmake @@ -1,15 +1,17 @@ #ifdef KSCREENLOCKER_TEST_APPS #define KCHECKPASS_BIN "${CMAKE_BINARY_DIR}/kcheckpass/kcheckpass" #elif defined(KSCREENLOCKER_UNIT_TEST) #define KCHECKPASS_BIN "${CMAKE_CURRENT_BINARY_DIR}/greeter/autotests/fakekcheckpass" #else #define KCHECKPASS_BIN "${CMAKE_INSTALL_FULL_LIBEXECDIR}/kcheckpass" #endif #define KSCREENLOCKER_GREET_BIN "${CMAKE_INSTALL_FULL_LIBEXECDIR}/kscreenlocker_greet" #cmakedefine01 HAVE_SYS_PRCTL_H #cmakedefine01 HAVE_PR_SET_DUMPABLE #cmakedefine01 HAVE_SYS_PROCCTL_H #cmakedefine01 HAVE_PROC_TRACE_CTL #cmakedefine01 HAVE_SECCOMP +#cmakedefine01 HAVE_SIGNALFD_H +#cmakedefine01 HAVE_EVENT_H diff --git a/greeter/authenticator.cpp b/greeter/authenticator.cpp index 63b18ce..7b1c24f 100644 --- a/greeter/authenticator.cpp +++ b/greeter/authenticator.cpp @@ -1,330 +1,331 @@ /******************************************************************** KSld - the KDE Screenlocker Daemon This file is part of the KDE project. Copyright (C) 1999 Martin R. Jones Copyright (C) 2002 Luboš Luňák Copyright (C) 2003 Oswald Buddenhagen Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "authenticator.h" #include #include // Qt #include #include #include #include // system #include #include #include #include #include +#include Authenticator::Authenticator(AuthenticationMode mode, QObject *parent) : QObject(parent) , m_graceLockTimer(new QTimer(this)) , m_checkPass(nullptr) { m_graceLockTimer->setSingleShot(true); m_graceLockTimer->setInterval(3000); connect(m_graceLockTimer, &QTimer::timeout, this, &Authenticator::graceLockedChanged); if (mode == AuthenticationMode::Delayed) { m_checkPass = new KCheckPass(AuthenticationMode::Delayed, this); setupCheckPass(); } } Authenticator::~Authenticator() = default; void Authenticator::tryUnlock(const QString &password) { if (isGraceLocked()) { emit failed(); return; } m_graceLockTimer->start(); emit graceLockedChanged(); if (!m_checkPass) { m_checkPass = new KCheckPass(AuthenticationMode::Direct, this); m_checkPass->setPassword(password); setupCheckPass(); } else { if (!m_checkPass->isReady()) { emit failed(); return; } m_checkPass->setPassword(password); m_checkPass->startAuth(); } } void Authenticator::setupCheckPass() { connect(m_checkPass, &KCheckPass::succeeded, this, &Authenticator::succeeded); connect(m_checkPass, &KCheckPass::failed, this, &Authenticator::failed); connect(m_checkPass, &KCheckPass::message, this, &Authenticator::message); connect(m_checkPass, &KCheckPass::error, this, &Authenticator::error); connect(m_checkPass, &KCheckPass::destroyed, this, [this] { m_checkPass = nullptr; } ); m_checkPass->start(); } bool Authenticator::isGraceLocked() const { return m_graceLockTimer->isActive(); } KCheckPass::KCheckPass(AuthenticationMode mode, QObject *parent) : QObject(parent) , m_notifier(nullptr) , m_pid(0) , m_fd(0) , m_mode(mode) { if (mode == AuthenticationMode::Direct) { connect(this, &KCheckPass::succeeded, this, &QObject::deleteLater); connect(this, &KCheckPass::failed, this, &QObject::deleteLater); } } KCheckPass::~KCheckPass() { reapVerify(); } void KCheckPass::start() { int sfd[2]; char fdbuf[16]; if (m_notifier) return; if (::socketpair(AF_LOCAL, SOCK_STREAM, 0, sfd)) { cantCheck(); return; } if ((m_pid = ::fork()) < 0) { ::close(sfd[0]); ::close(sfd[1]); cantCheck(); return; } if (!m_pid) { ::close(sfd[0]); sprintf(fdbuf, "%d", sfd[1]); execlp(QFile::encodeName(QStringLiteral(KCHECKPASS_BIN)).data(), "kcheckpass", "-m", "classic", "-S", fdbuf, (char *)0); _exit(20); } ::close(sfd[1]); m_fd = sfd[0]; m_notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); connect(m_notifier, &QSocketNotifier::activated, this, &KCheckPass::handleVerify); } ////// kckeckpass interface code int KCheckPass::Reader(void *buf, int count) { int ret, rlen; for (rlen = 0; rlen < count; ) { dord: ret = ::read(m_fd, (void *)((char *)buf + rlen), count - rlen); if (ret < 0) { if (errno == EINTR) goto dord; if (errno == EAGAIN) break; return -1; } if (!ret) break; rlen += ret; } return rlen; } bool KCheckPass::GRead(void *buf, int count) { return Reader(buf, count) == count; } bool KCheckPass::GWrite(const void *buf, int count) { return ::write(m_fd, buf, count) == count; } bool KCheckPass::GSendInt(int val) { return GWrite(&val, sizeof(val)); } bool KCheckPass::GSendStr(const char *buf) { int len = buf ? ::strlen (buf) + 1 : 0; return GWrite(&len, sizeof(len)) && GWrite (buf, len); } bool KCheckPass::GSendArr(int len, const char *buf) { return GWrite(&len, sizeof(len)) && GWrite (buf, len); } bool KCheckPass::GRecvInt(int *val) { return GRead(val, sizeof(*val)); } bool KCheckPass::GRecvArr(char **ret) { int len; char *buf; if (!GRecvInt(&len)) return false; if (!len) { *ret = 0; return true; } if (!(buf = (char *)::malloc (len))) return false; *ret = buf; if (GRead (buf, len)) { return true; } else { ::free(buf); *ret = 0; return false; } } void KCheckPass::handleVerify() { m_ready = false; int ret; char *arr; if (GRecvInt( &ret )) { switch (ret) { case ConvGetBinary: if (!GRecvArr( &arr )) break; // FIXME: not supported cantCheck(); if (arr) ::free( arr ); return; case ConvGetNormal: if (!GRecvArr( &arr )) break; GSendStr(m_password.toUtf8().constData()); if (!m_password.isEmpty()) { // IsSecret GSendInt(1); } if (arr) ::free( arr ); return; case ConvGetHidden: if (!GRecvArr( &arr )) break; GSendStr(m_password.toUtf8().constData()); if (!m_password.isEmpty()) { // IsSecret GSendInt(1); } if (arr) ::free( arr ); return; case ConvPutInfo: if (!GRecvArr( &arr )) break; emit message(QString::fromLocal8Bit(arr)); ::free( arr ); return; case ConvPutError: if (!GRecvArr( &arr )) break; emit error(QString::fromLocal8Bit(arr)); ::free( arr ); return; case ConvPutAuthSucceeded: emit succeeded(); return; case ConvPutAuthFailed: emit failed(); return; case ConvPutAuthError: cantCheck(); return; case ConvPutAuthAbort: // what to do here? return; case ConvPutReadyForAuthentication: m_ready = true; if (m_mode == AuthenticationMode::Direct) { ::kill(m_pid, SIGUSR1); } return; } } if (m_mode == AuthenticationMode::Direct) { reapVerify(); } else { // we broke, let's restart the greeter // error code 1 will result in a restart through the system qApp->exit(1); } } void KCheckPass::reapVerify() { m_notifier->setEnabled( false ); m_notifier->deleteLater(); m_notifier = nullptr; ::close( m_fd ); int status; ::kill(m_pid, SIGUSR2); while (::waitpid( m_pid, &status, 0 ) < 0) if (errno != EINTR) { // This should not happen ... cantCheck(); return; } } void KCheckPass::cantCheck() { // TODO: better signal? emit failed(); } void KCheckPass::startAuth() { ::kill(m_pid, SIGUSR1); } diff --git a/kcheckpass/kcheckpass.c b/kcheckpass/kcheckpass.c index bd18d0b..b07031c 100644 --- a/kcheckpass/kcheckpass.c +++ b/kcheckpass/kcheckpass.c @@ -1,423 +1,522 @@ /***************************************************************** * * kcheckpass - Simple password checker * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * * kcheckpass is a simple password checker. Just invoke and * send it the password on stdin. * * If the password was accepted, the program exits with 0; * if it was rejected, it exits with 1. Any other exit * code signals an error. * * It's hopefully simple enough to allow it to be setuid * root. * * Compile with -DHAVE_VSYSLOG if you have vsyslog(). * Compile with -DHAVE_PAM if you have a PAM system, * and link with -lpam -ldl. * Compile with -DHAVE_SHADOW if you have a shadow * password system. * * Copyright (C) 1998, Caldera, Inc. * Released under the GNU General Public License * * Olaf Kirch General Framework and PAM support * Christian Esken Shadow and /etc/passwd support * Roberto Teixeira other user (-U) support * Oswald Buddenhagen Binary server mode * * Other parts were taken from kscreensaver's passwd.cpp. * *****************************************************************/ #include "kcheckpass.h" #include #include #include #include #include #include #include #include #include -#include #include #include #if HAVE_SYS_PRCTL_H #include #endif #if HAVE_SYS_PROCCTL_H #include #include #endif +#if HAVE_SIGNALFD_H +#include +#endif +#if HAVE_EVENT_H +#include +#include +#include +#endif #define THROTTLE 3 static int havetty, sfd = -1, nullpass; static int Reader (void *buf, int count) { int ret, rlen; for (rlen = 0; rlen < count; ) { dord: ret = read (sfd, (void *)((char *)buf + rlen), count - rlen); if (ret < 0) { if (errno == EINTR) goto dord; if (errno == EAGAIN) break; return -1; } if (!ret) break; rlen += ret; } return rlen; } static void GRead (void *buf, int count) { if (Reader (buf, count) != count) { message ("Communication breakdown on read\n"); exit(15); } } static void GWrite (const void *buf, int count) { if (write (sfd, buf, count) != count) { message ("Communication breakdown on write\n"); exit(15); } } static void GSendInt (int val) { GWrite (&val, sizeof(val)); } static void GSendStr (const char *buf) { unsigned len = buf ? strlen (buf) + 1 : 0; GWrite (&len, sizeof(len)); GWrite (buf, len); } static void GSendArr (int len, const char *buf) { GWrite (&len, sizeof(len)); GWrite (buf, len); } static int GRecvInt (void) { int val; GRead (&val, sizeof(val)); return val; } static char * GRecvStr (void) { unsigned len; char *buf; if (!(len = GRecvInt())) return (char *)0; if (len > 0x1000 || !(buf = malloc (len))) { message ("No memory for read buffer\n"); exit(15); } GRead (buf, len); buf[len - 1] = 0; /* we're setuid ... don't trust "them" */ return buf; } static char * GRecvArr (void) { unsigned len; char *arr; unsigned const char *up; if (!(len = (unsigned) GRecvInt())) return (char *)0; if (len < 4) { message ("Too short binary authentication data block\n"); exit(15); } if (len > 0x10000 || !(arr = malloc (len))) { message ("No memory for read buffer\n"); exit(15); } GRead (arr, len); up = (unsigned const char *)arr; if (len != (unsigned)(up[3] | (up[2] << 8) | (up[1] << 16) | (up[0] << 24))) { message ("Mismatched binary authentication data block size\n"); exit(15); } return arr; } static char * conv_server (ConvRequest what, const char *prompt) { GSendInt (what); switch (what) { case ConvGetBinary: { unsigned const char *up = (unsigned const char *)prompt; int len = up[3] | (up[2] << 8) | (up[1] << 16) | (up[0] << 24); GSendArr (len, prompt); return GRecvArr (); } case ConvGetNormal: case ConvGetHidden: { char *msg; GSendStr (prompt); msg = GRecvStr (); if (msg && (GRecvInt() & IsPassword) && !*msg) nullpass = 1; return msg; } case ConvPutAuthSucceeded: case ConvPutAuthFailed: case ConvPutAuthError: case ConvPutAuthAbort: case ConvPutReadyForAuthentication: return 0; case ConvPutInfo: case ConvPutError: default: GSendStr (prompt); return 0; } } void message(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } #ifndef O_NOFOLLOW # define O_NOFOLLOW 0 #endif static void ATTR_NORETURN usage(int exitval) { message( "usage: kcheckpass {-h|[-c caller] [-m method] -S handle}\n" " options:\n" " -h this help message\n" " -S handle operate in binary server mode on file descriptor handle\n" " -m method use the specified authentication method (default: \"classic\")\n" " exit codes:\n" " 0 success\n" " 1 invalid password\n" " 2 cannot read password database\n" " Anything else tells you something's badly hosed.\n" ); exit(exitval); } int main(int argc, char **argv) { const char *method = "classic"; const char *username = 0; char *p; struct passwd *pw; int c, nfd; uid_t uid; AuthReturn ret; sigset_t signalMask; +#if HAVE_SIGNALFD_H int signalFd; struct signalfd_siginfo fdsi; ssize_t sigReadSize; +#endif +#if HAVE_EVENT_H + /* Event Queue */ + int keventQueue; + /* Listen for two events: SIGUSR1 and SIGUSR2 */ + struct kevent keventEvent[2]; + int keventData; +#endif pid_t parentPid; parentPid = getppid(); // disable ptrace on kcheckpass #if HAVE_PR_SET_DUMPABLE prctl(PR_SET_DUMPABLE, 0); #endif #if HAVE_PROC_TRACE_CTL int mode = PROC_TRACE_CTL_DISABLE; procctl(P_PID, getpid(), PROC_TRACE_CTL, &mode); #endif // prevent becoming an orphan while waiting for SIGUSR2 #if HAVE_PR_SET_DUMPABLE prctl(PR_SET_PDEATHSIG, SIGUSR2); #endif /* Make sure stdout/stderr are open */ for (c = 1; c <= 2; c++) { if (fcntl(c, F_GETFL) == -1) { if ((nfd = open("/dev/null", O_WRONLY)) < 0) { message("cannot open /dev/null: %s\n", strerror(errno)); exit(10); } if (c != nfd) { dup2(nfd, c); close(nfd); } } } havetty = isatty(0); while ((c = getopt(argc, argv, "hm:S:")) != -1) { switch (c) { case 'h': usage(0); break; case 'm': method = optarg; break; case 'S': sfd = atoi(optarg); break; default: message("Command line option parsing error\n"); usage(10); } } if (sfd == -1) { message("Only binary protocol supported\n"); return AuthError; } uid = getuid(); if (!(p = getenv("LOGNAME")) || !(pw = getpwnam(p)) || pw->pw_uid != uid) if (!(p = getenv("USER")) || !(pw = getpwnam(p)) || pw->pw_uid != uid) if (!(pw = getpwuid(uid))) { message("Cannot determinate current user\n"); return AuthError; } if (!(username = strdup(pw->pw_name))) { message("Out of memory\n"); return AuthError; } // setup signals sigemptyset(&signalMask); sigaddset(&signalMask, SIGUSR1); sigaddset(&signalMask, SIGUSR2); // block them if (sigprocmask(SIG_BLOCK, &signalMask, NULL) == -1) { message("Block signal failed\n"); conv_server(ConvPutAuthError, 0); return 1; } +#if HAVE_SIGNALFD_H signalFd = signalfd(-1, &signalMask, SFD_CLOEXEC); if (signalFd == -1) { message("Signal fd failed\n"); conv_server(ConvPutAuthError, 0); return 1; } +#endif +#if HAVE_EVENT_H + /* Setup the kequeu */ + keventQueue = kqueue(); + if (keventQueue == -1) { + message("Failed to create kqueue for SIGUSR1\n"); + conv_server(ConvPutAuthError, 0); + return 1; + } + /* Setup the events */ + EV_SET(&keventEvent[0], SIGUSR1, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); + EV_SET(&keventEvent[1], SIGUSR2, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); + int setupResult = kevent(keventQueue, &keventEvent, 2, NULL, 0, NULL); + if (setupResult == -1) { + message("Failed to attach event to the kqueue\n"); + conv_server(ConvPutAuthError, 0); + return 1; + } + if (keventEvent[0].flags & EV_ERROR) { + message("Error in kevent for SIGUSR1: %s\n", strerror(keventEvent[0].data)); + conv_server(ConvPutAuthError, 0); + return 1; + } + if (keventEvent[1].flags & EV_ERROR) { + message("Error in kevent for SIGUSR2: %s\n", strerror(keventEvent[1].data)); + conv_server(ConvPutAuthError, 0); + return 1; + } + + /* signal_info for sigwaitinfo() */ + siginfo_t signalInfo; + +#endif // now lets block on the fd for (;;) { conv_server(ConvPutReadyForAuthentication, 0); +#if HAVE_SIGNALFD_H sigReadSize = read(signalFd, &fdsi, sizeof(struct signalfd_siginfo)); if (sigReadSize != sizeof(struct signalfd_siginfo)) { message("Read wrong size\n"); return 1; } if (fdsi.ssi_signo == SIGUSR1) { if (fdsi.ssi_pid != parentPid) { message("signal from wrong process\n"); continue; } +#endif +#if HAVE_EVENT_H + keventData = kevent(keventQueue, NULL, 0, keventEvent, 1, NULL); + if ( keventData == -1 ) { + /* Let's figure this out in the future, shall we */ + message("kevent() failed with %d\n", errno); + return 1; + } + else if ( keventData == 0 ) { + /* Do we need to handle timeouts? */ + message("kevent timeout\n"); + continue; + } + // We know we got a SIGUSR1 or SIGUSR2, so fetch it via sigwaitinfo() + // (otherwise, we could have used sigtimedwait() ) + int signalReturn = sigwaitinfo(&signalMask, &signalInfo); + if (signalReturn < 0) { + if (errno == EINTR) { + message("sigawaitinfo() interrupted by unblocked caught signal"); + continue; + } + else if (errno == EAGAIN) { + /* This should not happen, as kevent notified us about such a signal */ + message("no signal of type USR1 or USR2 pending."); + continue; + } + else { + message("Unhandled error in sigwaitinfo()"); + conv_server(ConvPutAuthError, 0); + return 1; + } + } + if (signalReturn == SIGUSR1) { + if (signalInfo.si_pid != parentPid) { + message("signal from wrong process\n"); + continue; + } +#endif /* Now do the fandango */ ret = Authenticate(method, username, conv_server); if (ret == AuthBad) { message("Authentication failure\n"); if (!nullpass) { openlog("kcheckpass", LOG_PID, LOG_AUTH); syslog(LOG_NOTICE, "Authentication failure for %s (invoked by uid %d)", username, uid); } } switch (ret) { case AuthOk: conv_server(ConvPutAuthSucceeded, 0); break; case AuthBad: conv_server(ConvPutAuthFailed, 0); break; case AuthError: conv_server(ConvPutAuthError, 0); break; case AuthAbort: conv_server(ConvPutAuthAbort, 0); default: break; } if (uid != geteuid()) { // we don't support multiple auth for setuid kcheckpass break; } +#if HAVE_SIGNALFD_H } else if (fdsi.ssi_signo == SIGUSR2) { if (fdsi.ssi_pid != parentPid) { message("signal from wrong process\n"); continue; } break; +#endif +#if HAVE_EVENT_H + } else if (signalReturn == SIGUSR2 ) { + if (signalInfo.si_pid != parentPid) { + message("signal from wrong process\n"); + continue; + } + break; +#endif } else { message("unexpected signal\n"); } } return 0; } void dispose(char *str) { memset(str, 0, strlen(str)); free(str); } /***************************************************************** The real authentication methods are in separate source files. Look in checkpass_*.c *****************************************************************/