From 0e36014d15a0e4c2ad8403722363213ca4bc08bb Mon Sep 17 00:00:00 2001 From: reionwong Date: Tue, 27 Jul 2021 18:45:09 +0800 Subject: [PATCH] Update --- CMakeLists.txt | 66 ++++ ConfigureChecks.cmake | 41 +++ README.md | 33 +- checkpass/CMakeLists.txt | 33 ++ checkpass/checkpass-enums.h | 75 ++++ checkpass/checkpass.c | 508 ++++++++++++++++++++++++++++ checkpass/checkpass.h | 105 ++++++ checkpass/checkpass_pam.c | 204 +++++++++++ checkpass/checkpass_shadow.c | 85 +++++ checkpass/config-ccheckpass.h.cmake | 3 + cmake/FindConsoleKit.cmake | 36 ++ cmake/FindPAM.cmake | 74 ++++ cmake/Findloginctl.cmake | 34 ++ cmake/UnixAuth.cmake | 56 +++ config-X11.h.cmake | 2 + config-screenlocker.h.cmake | 16 + config-unix.h.cmake | 32 ++ config-workspace.h.cmake | 141 ++++++++ screenlocker/CMakeLists.txt | 28 ++ screenlocker/application.cpp | 299 ++++++++++++++++ screenlocker/application.h | 44 +++ screenlocker/authenticator.cpp | 333 ++++++++++++++++++ screenlocker/authenticator.h | 111 ++++++ screenlocker/fixx11h.h | 278 +++++++++++++++ screenlocker/kcheckpass-enums.h | 75 ++++ screenlocker/main.cpp | 9 + screenlocker/qml.qrc | 6 + screenlocker/qml/Greeter.qml | 5 + screenlocker/qml/LockScreen.qml | 117 +++++++ screenlocker/wallpaper.cpp | 24 ++ screenlocker/wallpaper.h | 28 ++ 31 files changed, 2900 insertions(+), 1 deletion(-) create mode 100644 CMakeLists.txt create mode 100644 ConfigureChecks.cmake create mode 100644 checkpass/CMakeLists.txt create mode 100644 checkpass/checkpass-enums.h create mode 100644 checkpass/checkpass.c create mode 100644 checkpass/checkpass.h create mode 100644 checkpass/checkpass_pam.c create mode 100644 checkpass/checkpass_shadow.c create mode 100644 checkpass/config-ccheckpass.h.cmake create mode 100644 cmake/FindConsoleKit.cmake create mode 100644 cmake/FindPAM.cmake create mode 100644 cmake/Findloginctl.cmake create mode 100644 cmake/UnixAuth.cmake create mode 100644 config-X11.h.cmake create mode 100644 config-screenlocker.h.cmake create mode 100644 config-unix.h.cmake create mode 100644 config-workspace.h.cmake create mode 100644 screenlocker/CMakeLists.txt create mode 100644 screenlocker/application.cpp create mode 100644 screenlocker/application.h create mode 100644 screenlocker/authenticator.cpp create mode 100644 screenlocker/authenticator.h create mode 100644 screenlocker/fixx11h.h create mode 100644 screenlocker/kcheckpass-enums.h create mode 100644 screenlocker/main.cpp create mode 100644 screenlocker/qml.qrc create mode 100644 screenlocker/qml/Greeter.qml create mode 100644 screenlocker/qml/LockScreen.qml create mode 100644 screenlocker/wallpaper.cpp create mode 100644 screenlocker/wallpaper.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b0f071e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.16) + +project(screenlocker) +set(CMAKE_C_STANDARD 99) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# set(CMAKE_AUTOUIC ON) +# set(CMAKE_AUTOMOC ON) +# set(CMAKE_AUTORCC ON) + +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + +include(CheckIncludeFile) +include(CheckIncludeFiles) +include(CheckSymbolExists) +include(FeatureSummary) + +find_package(Qt5 COMPONENTS Core DBus Widgets X11Extras Quick REQUIRED) +find_package(X11) + +# ----------- Check -------------------- + +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_files("sys/types.h;sys/event.h" HAVE_EVENT_H) +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") + +# -------------------------------------- + +option(PAM_REQUIRED "Require building with PAM" ON) + +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) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu90") + +configure_file(config-screenlocker.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-screenlocker.h) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +add_subdirectory(screenlocker) +add_subdirectory(checkpass) + +feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake new file mode 100644 index 0000000..06fb375 --- /dev/null +++ b/ConfigureChecks.cmake @@ -0,0 +1,41 @@ +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH} ) +include(UnixAuth) +set_package_properties(PAM PROPERTIES DESCRIPTION "PAM Libraries" + URL "https://www.kernel.org/pub/linux/libs/pam/" + TYPE OPTIONAL + PURPOSE "Required for screen unlocking and optionally used by the KDM log in manager" + ) +if(PAM_REQUIRED) +set_package_properties(PAM PROPERTIES TYPE REQUIRED) +endif() +include(CheckTypeSize) +include(FindPkgConfig) + +if (PAM_FOUND) + set(KDE4_COMMON_PAM_SERVICE "kde" CACHE STRING "The PAM service to use unless overridden for a particular app.") + + macro(define_pam_service APP) + string(TOUPPER ${APP}_PAM_SERVICE var) + set(cvar KDE4_${var}) + set(${cvar} "${KDE4_COMMON_PAM_SERVICE}" CACHE STRING "The PAM service for ${APP}.") + mark_as_advanced(${cvar}) + set(${var} "\"${${cvar}}\"") + endmacro(define_pam_service) + + define_pam_service(kscreensaver) +endif (PAM_FOUND) + +check_function_exists(getpassphrase HAVE_GETPASSPHRASE) +check_function_exists(vsyslog HAVE_VSYSLOG) +check_function_exists(statvfs HAVE_STATVFS) + +check_include_files(limits.h HAVE_LIMITS_H) +check_include_files(sys/time.h HAVE_SYS_TIME_H) # ksmserver, ksplashml, sftp +check_include_files(sys/param.h HAVE_SYS_PARAM_H) +check_include_files(unistd.h HAVE_UNISTD_H) +check_include_files(malloc.h HAVE_MALLOC_H) +check_function_exists(statfs HAVE_STATFS) + +set(CMAKE_EXTRA_INCLUDE_FILES sys/socket.h) + +check_function_exists(setpriority HAVE_SETPRIORITY) # kscreenlocker \ No newline at end of file diff --git a/README.md b/README.md index 5072fc9..89a8035 100644 --- a/README.md +++ b/README.md @@ -1 +1,32 @@ -# screenlocker \ No newline at end of file +# Screen locker + +## Third Party Code + +kcheckpass + +## Dependencies + +### Debian/Ubuntu + +``` +sudo apt install libpam0g-dev libx11-dev -y +``` + +## Build + +```shell +mkdir build +cd build +cmake .. +make +``` + +## Install + +```shell +sudo make install +``` + +## License + +This project has been licensed by GPLv3. diff --git a/checkpass/CMakeLists.txt b/checkpass/CMakeLists.txt new file mode 100644 index 0000000..7dbcb75 --- /dev/null +++ b/checkpass/CMakeLists.txt @@ -0,0 +1,33 @@ +include_directories( ${UNIXAUTH_INCLUDE_DIRS} ) +check_include_files(paths.h HAVE_PATHS_H) +configure_file (config-ccheckpass.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ccheckpass.h ) + +set(ccheckpass_SRCS + checkpass.h + checkpass.c + checkpass_pam.c + checkpass_shadow.c +) + +# find_package(ECM REQUIRED NO_MODULE) +# set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ) +# include(ECMMarkNonGuiExecutable) + +add_executable(ccheckpass ${ccheckpass_SRCS}) +# ecm_mark_nongui_executable(ccheckpass) + +set_property(TARGET ccheckpass APPEND_STRING PROPERTY COMPILE_FLAGS " -U_REENTRANT") +target_link_libraries(ccheckpass ${UNIXAUTH_LIBRARIES} ${SOCKET_LIBRARIES}) + +if (PAM_FOUND) + set(checkpass_suid "") +else() + set(checkpass_suid "SETUID") + message(STATUS "PAM not found, will install a SUID-kcheckpass") +endif() +install(TARGETS ccheckpass + DESTINATION /usr/bin + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE ${checkpass_suid} + ) + +#EXTRA_DIST = README \ No newline at end of file diff --git a/checkpass/checkpass-enums.h b/checkpass/checkpass-enums.h new file mode 100644 index 0000000..5716d16 --- /dev/null +++ b/checkpass/checkpass-enums.h @@ -0,0 +1,75 @@ +/***************************************************************** + * + *· kcheckpass + * + *· 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. + * + * + * 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. + * + *· 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 + *· Oswald Buddenhagen Binary server mode + * + * Other parts were taken from kscreensaver's passwd.cpp + *****************************************************************/ + +#ifndef KCHECKPASS_ENUMS_H +#define KCHECKPASS_ENUMS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* these must match kcheckpass' exit codes */ +typedef enum { + AuthOk = 0, + AuthBad = 1, + AuthError = 2, + AuthAbort = 3, +} AuthReturn; + +typedef enum { + ConvGetBinary, + ConvGetNormal, + ConvGetHidden, + ConvPutInfo, + ConvPutError, + ConvPutAuthSucceeded, + ConvPutAuthFailed, + ConvPutAuthError, + ConvPutAuthAbort, + ConvPutReadyForAuthentication, +} ConvRequest; + +/* these must match the defs in kgreeterplugin.h */ +typedef enum { + IsUser = 1, /* unused in kcheckpass */ + IsPassword = 2, +} DataTag; + +#ifdef __cplusplus +} +#endif + +#endif /* KCHECKPASS_ENUMS_H */ diff --git a/checkpass/checkpass.c b/checkpass/checkpass.c new file mode 100644 index 0000000..e3bb384 --- /dev/null +++ b/checkpass/checkpass.c @@ -0,0 +1,508 @@ +/***************************************************************** + * + * 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 "checkpass.h" + +#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 + *****************************************************************/ diff --git a/checkpass/checkpass.h b/checkpass/checkpass.h new file mode 100644 index 0000000..ffc047f --- /dev/null +++ b/checkpass/checkpass.h @@ -0,0 +1,105 @@ +/***************************************************************** + * + * kcheckpass + * + * 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. + * + * + * 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. + * + * 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 + * Oswald Buddenhagen Binary server mode + * + * Other parts were taken from kscreensaver's passwd.cpp + *****************************************************************/ + +#ifndef KCHECKPASS_H_ +#define KCHECKPASS_H_ + +#include +#include +#include + +#ifdef HAVE_CRYPT_H +#include +#endif + +#ifdef HAVE_PATHS_H +#include +#endif + +#include +#include + +#ifndef _PATH_TMP +#define _PATH_TMP "/tmp/" +#endif + +#ifdef ultrix +#include +#endif + +#include + +/* Make sure there is only one! */ +#if defined(HAVE_PAM) +#else +#define HAVE_SHADOW +#endif + +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) +#define ATTR_UNUSED __attribute__((unused)) +#define ATTR_NORETURN __attribute__((noreturn)) +#define ATTR_PRINTFLIKE(fmt, var) __attribute__((format(printf, fmt, var))) +#else +#define ATTR_UNUSED +#define ATTR_NORETURN +#define ATTR_PRINTFLIKE(fmt, var) +#endif + +#include "checkpass-enums.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************************************************** + * Authenticates user + *****************************************************************/ +AuthReturn Authenticate(const char *method, const char *user, char *(*conv)(ConvRequest, const char *)); + +/***************************************************************** + * Output a message to stderr + *****************************************************************/ +void message(const char *, ...) ATTR_PRINTFLIKE(1, 2); + +/***************************************************************** + * Overwrite and free the passed string + *****************************************************************/ +void dispose(char *); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/checkpass/checkpass_pam.c b/checkpass/checkpass_pam.c new file mode 100644 index 0000000..3d056f3 --- /dev/null +++ b/checkpass/checkpass_pam.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) 1998 Caldera, Inc. + * Copyright (C) 2003 Oswald Buddenhagen + * + * 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. + */ + +#include "checkpass.h" + +#ifdef HAVE_PAM + +#include +#include +#include +#include + +#ifdef HAVE_PAM_PAM_APPL_H +#include +#else +#include +#endif + +struct pam_data { + char *(*conv)(ConvRequest, const char *); + int abort : 1; + int classic : 1; +}; + +#ifdef PAM_MESSAGE_CONST +typedef const struct pam_message pam_message_type; +typedef const void *pam_gi_type; +#else +typedef struct pam_message pam_message_type; +typedef void *pam_gi_type; +#endif + +static int PAM_conv(int num_msg, pam_message_type **msg, struct pam_response **resp, void *appdata_ptr) +{ + int count; + struct pam_response *repl; + struct pam_data *pd = (struct pam_data *)appdata_ptr; + + if (!(repl = calloc(num_msg, sizeof(struct pam_response)))) { + return PAM_CONV_ERR; + } + + for (count = 0; count < num_msg; count++) { + switch (msg[count]->msg_style) { + case PAM_TEXT_INFO: + pd->conv(ConvPutInfo, msg[count]->msg); + break; + case PAM_ERROR_MSG: + pd->conv(ConvPutError, msg[count]->msg); + break; + default: + switch (msg[count]->msg_style) { + case PAM_PROMPT_ECHO_ON: + repl[count].resp = pd->conv(ConvGetNormal, msg[count]->msg); + break; + case PAM_PROMPT_ECHO_OFF: + repl[count].resp = pd->conv(ConvGetHidden, pd->classic ? 0 : msg[count]->msg); + break; +#ifdef PAM_BINARY_PROMPT + case PAM_BINARY_PROMPT: + repl[count].resp = pd->conv(ConvGetBinary, msg[count]->msg); + break; +#endif + default: + /* Must be an error of some sort... */ + goto conv_err; + } + if (!repl[count].resp) { + pd->abort = 1; + goto conv_err; + } + repl[count].resp_retcode = PAM_SUCCESS; + break; + } + } + *resp = repl; + return PAM_SUCCESS; + +conv_err: + for (; count >= 0; count--) { + if (repl[count].resp) { + switch (msg[count]->msg_style) { + case PAM_PROMPT_ECHO_OFF: + dispose(repl[count].resp); + break; +#ifdef PAM_BINARY_PROMPT + case PAM_BINARY_PROMPT: /* handle differently? */ +#endif + case PAM_PROMPT_ECHO_ON: + free(repl[count].resp); + break; + } + } + } + free(repl); + return PAM_CONV_ERR; +} + +static struct pam_data PAM_data; + +static struct pam_conv PAM_conversation = { + &PAM_conv, + &PAM_data, +}; + +#ifdef PAM_FAIL_DELAY +static void fail_delay(int retval ATTR_UNUSED, unsigned usec_delay ATTR_UNUSED, void *appdata_ptr ATTR_UNUSED) +{ +} +#endif + +AuthReturn Authenticate(const char *method, const char *user, char *(*conv)(ConvRequest, const char *)) +{ + const char *tty; + pam_handle_t *pamh; + pam_gi_type pam_item; + const char *pam_service; + char pservb[64]; + int pam_error; + + openlog("kcheckpass", LOG_PID, LOG_AUTH); + + PAM_data.conv = conv; + if (strcmp(method, "classic")) { + sprintf(pservb, "%.31s-%.31s", KSCREENSAVER_PAM_SERVICE, method); + pam_service = pservb; + } else { + /* PAM_data.classic = 1; */ + pam_service = KSCREENSAVER_PAM_SERVICE; + } + pam_error = pam_start(pam_service, user, &PAM_conversation, &pamh); + if (pam_error != PAM_SUCCESS) { + return AuthError; + } + + tty = ttyname(0); + if (!tty) { + tty = getenv("DISPLAY"); + } + + pam_error = pam_set_item(pamh, PAM_TTY, tty); + if (pam_error != PAM_SUCCESS) { + pam_end(pamh, pam_error); + return AuthError; + } + +#ifdef PAM_FAIL_DELAY + pam_set_item(pamh, PAM_FAIL_DELAY, (void *)fail_delay); +#endif + + pam_error = pam_authenticate(pamh, 0); + if (pam_error != PAM_SUCCESS) { + if (PAM_data.abort) { + PAM_data.abort = 0; + pam_end(pamh, pam_error); + return AuthAbort; + } + pam_end(pamh, pam_error); + switch (pam_error) { + case PAM_USER_UNKNOWN: + case PAM_AUTH_ERR: + case PAM_MAXTRIES: /* should handle this better ... */ + case PAM_AUTHINFO_UNAVAIL: /* returned for unknown users ... bogus */ + return AuthBad; + default: + return AuthError; + } + } + + /* just in case some module is stupid enough to ignore a preset PAM_USER */ + pam_error = pam_get_item(pamh, PAM_USER, &pam_item); + if (pam_error != PAM_SUCCESS) { + pam_end(pamh, pam_error); + return AuthError; + } + if (strcmp((const char *)pam_item, user)) { + pam_end(pamh, PAM_SUCCESS); /* maybe use PAM_AUTH_ERR? */ + return AuthBad; + } + + pam_error = pam_setcred(pamh, PAM_REFRESH_CRED); + /* ignore errors on refresh credentials. If this did not work we use the old ones. */ + + pam_end(pamh, PAM_SUCCESS); + return AuthOk; +} + +#endif diff --git a/checkpass/checkpass_shadow.c b/checkpass/checkpass_shadow.c new file mode 100644 index 0000000..c56d04b --- /dev/null +++ b/checkpass/checkpass_shadow.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 1998 Christian Esken + * Copyright (C) 2003 Oswald Buddenhagen + * + * This is a modified version of checkpass_shadow.cpp + * + * Modifications made by Thorsten Kukuk + * Mathias Kettner + * + * ------------------------------------------------------------ + * + * 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. + */ + +#include "checkpass.h" + +/******************************************************************* + * This is the authentication code for Shadow-Passwords + *******************************************************************/ + +#ifdef HAVE_SHADOW +#include +#include +#include + +#ifndef __hpux +#include +#endif + +AuthReturn Authenticate(const char *method, const char *login, char *(*conv)(ConvRequest, const char *)) +{ + char *typed_in_password; + char *crpt_passwd; + char *password; + struct passwd *pw; + struct spwd *spw; + + if (strcmp(method, "classic")) + return AuthError; + + if (!(pw = getpwnam(login))) + return AuthAbort; + + spw = getspnam(login); + password = spw ? spw->sp_pwdp : pw->pw_passwd; + + if (!*password) + return AuthOk; + + if (!(typed_in_password = conv(ConvGetHidden, 0))) + return AuthAbort; + +#if defined(__linux__) && defined(HAVE_PW_ENCRYPT) + crpt_passwd = pw_encrypt(typed_in_password, password); /* (1) */ +#else + crpt_passwd = crypt(typed_in_password, password); +#endif + + if (crpt_passwd && !strcmp(password, crpt_passwd)) { + dispose(typed_in_password); + return AuthOk; /* Success */ + } + dispose(typed_in_password); + return AuthBad; /* Password wrong or account locked */ +} + +/* + (1) Deprecated - long passwords have known weaknesses. Also, + pw_encrypt is non-standard (requires libshadow.a) while + everything else you need to support shadow passwords is in + the standard (ELF) libc. + */ +#endif diff --git a/checkpass/config-ccheckpass.h.cmake b/checkpass/config-ccheckpass.h.cmake new file mode 100644 index 0000000..a8a4ee5 --- /dev/null +++ b/checkpass/config-ccheckpass.h.cmake @@ -0,0 +1,3 @@ +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PATHS_H 1 + diff --git a/cmake/FindConsoleKit.cmake b/cmake/FindConsoleKit.cmake new file mode 100644 index 0000000..4554a32 --- /dev/null +++ b/cmake/FindConsoleKit.cmake @@ -0,0 +1,36 @@ +#============================================================================= +# Copyright 2017 Tobias C. Berner +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= +find_program(cklistsessions_EXECUTABLE NAMES ck-list-sessions) +find_program(qdbus_EXECUTABLE NAMES qdbus) +find_package_handle_standard_args(ConsoleKit + FOUND_VAR + ConsoleKit_FOUND + REQUIRED_VARS + cklistsessions_EXECUTABLE + qdbus_EXECUTABLE +) +mark_as_advanced(ConsoleKit_FOUND cklistsessions_EXECUTABLE qdbus_EXECUTABLE) diff --git a/cmake/FindPAM.cmake b/cmake/FindPAM.cmake new file mode 100644 index 0000000..3499836 --- /dev/null +++ b/cmake/FindPAM.cmake @@ -0,0 +1,74 @@ +# - Try to find the PAM libraries +# Once done this will define +# +# PAM_FOUND - system has pam +# PAM_INCLUDE_DIR - the pam include directory +# PAM_LIBRARIES - libpam library + +if (PAM_INCLUDE_DIR AND PAM_LIBRARY) + # Already in cache, be silent + set(PAM_FIND_QUIETLY TRUE) +endif (PAM_INCLUDE_DIR AND PAM_LIBRARY) + +find_path(PAM_INCLUDE_DIR NAMES security/pam_appl.h pam/pam_appl.h) +find_library(PAM_LIBRARY pam) +find_library(DL_LIBRARY dl) + +if (PAM_INCLUDE_DIR AND PAM_LIBRARY) + set(PAM_FOUND TRUE) + if (DL_LIBRARY) + set(PAM_LIBRARIES ${PAM_LIBRARY} ${DL_LIBRARY}) + else (DL_LIBRARY) + set(PAM_LIBRARIES ${PAM_LIBRARY}) + endif (DL_LIBRARY) + + if (EXISTS ${PAM_INCLUDE_DIR}/pam/pam_appl.h) + # darwin claims to be something special + set(HAVE_PAM_PAM_APPL_H 1) + endif (EXISTS ${PAM_INCLUDE_DIR}/pam/pam_appl.h) + + if (NOT DEFINED PAM_MESSAGE_CONST) + include(CheckCXXSourceCompiles) + # XXX does this work with plain c? + check_cxx_source_compiles(" +#if ${HAVE_PAM_PAM_APPL_H}+0 +# include +#else +# include +#endif + +static int PAM_conv( + int num_msg, + const struct pam_message **msg, /* this is the culprit */ + struct pam_response **resp, + void *ctx) +{ + return 0; +} + +int main(void) +{ + struct pam_conv PAM_conversation = { + &PAM_conv, /* this bombs out if the above does not match */ + 0 + }; + + return 0; +} +" PAM_MESSAGE_CONST) + endif (NOT DEFINED PAM_MESSAGE_CONST) + set(PAM_MESSAGE_CONST ${PAM_MESSAGE_CONST} CACHE BOOL "PAM expects a conversation function with const pam_message") + +endif (PAM_INCLUDE_DIR AND PAM_LIBRARY) + +if (PAM_FOUND) + if (NOT PAM_FIND_QUIETLY) + message(STATUS "Found PAM: ${PAM_LIBRARIES}") + endif (NOT PAM_FIND_QUIETLY) +else (PAM_FOUND) + if (PAM_FIND_REQUIRED) + message(FATAL_ERROR "PAM was not found") + endif(PAM_FIND_REQUIRED) +endif (PAM_FOUND) + +mark_as_advanced(PAM_INCLUDE_DIR PAM_LIBRARY DL_LIBRARY PAM_MESSAGE_CONST) diff --git a/cmake/Findloginctl.cmake b/cmake/Findloginctl.cmake new file mode 100644 index 0000000..56061ab --- /dev/null +++ b/cmake/Findloginctl.cmake @@ -0,0 +1,34 @@ +#============================================================================= +# Copyright 2016 Martin Gräßlin +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= +find_program(loginctl_EXECUTABLE NAMES loginctl) +find_package_handle_standard_args(loginctl + FOUND_VAR + loginctl_FOUND + REQUIRED_VARS + loginctl_EXECUTABLE +) +mark_as_advanced(loginctl_FOUND loginctl_EXECUTABLE) diff --git a/cmake/UnixAuth.cmake b/cmake/UnixAuth.cmake new file mode 100644 index 0000000..6928310 --- /dev/null +++ b/cmake/UnixAuth.cmake @@ -0,0 +1,56 @@ +find_package(PAM) + +include(CheckFunctionExists) +include(CheckLibraryExists) +include(CheckIncludeFiles) +include(CMakePushCheckState) + +set(UNIXAUTH_LIBRARIES) +set(UNIXAUTH_INCLUDE_DIRS) + +set(SHADOW_LIBRARIES) +check_function_exists(getspnam found_getspnam) +if (found_getspnam) + set(HAVE_GETSPNAM 1) +else (found_getspnam) + cmake_push_check_state() + set(CMAKE_REQUIRED_LIBRARIES -lshadow) + check_function_exists(getspnam found_getspnam_shadow) + if (found_getspnam_shadow) + set(HAVE_GETSPNAM 1) + set(SHADOW_LIBRARIES shadow) + check_function_exists(pw_encrypt HAVE_PW_ENCRYPT) # ancient Linux shadow + else (found_getspnam_shadow) + set(CMAKE_REQUIRED_LIBRARIES -lgen) # UnixWare + check_function_exists(getspnam found_getspnam_gen) + if (found_getspnam_gen) + set(HAVE_GETSPNAM 1) + set(SHADOW_LIBRARIES gen) + endif (found_getspnam_gen) + endif (found_getspnam_shadow) + cmake_pop_check_state() +endif (found_getspnam) + +set(CRYPT_LIBRARIES) +check_library_exists(crypt crypt "" HAVE_CRYPT) +if (HAVE_CRYPT) + set(CRYPT_LIBRARIES crypt) + check_include_files(crypt.h HAVE_CRYPT_H) +endif (HAVE_CRYPT) + +if (PAM_FOUND) + + set(HAVE_PAM 1) + set(UNIXAUTH_LIBRARIES ${PAM_LIBRARIES}) + set(UNIXAUTH_INCLUDE_DIRS ${PAM_INCLUDE_DIR}) + +else (PAM_FOUND) + + if (HAVE_GETSPNAM) + set(UNIXAUTH_LIBRARIES ${SHADOW_LIBRARIES}) + endif (HAVE_GETSPNAM) + if (NOT HAVE_PW_ENCRYPT) + set(UNIXAUTH_LIBRARIES ${UNIXAUTH_LIBRARIES} ${CRYPT_LIBRARIES}) + endif (NOT HAVE_PW_ENCRYPT) + +endif (PAM_FOUND) diff --git a/config-X11.h.cmake b/config-X11.h.cmake new file mode 100644 index 0000000..5d6483a --- /dev/null +++ b/config-X11.h.cmake @@ -0,0 +1,2 @@ +/* Define if you have the XInput extension */ +#cmakedefine X11_Xinput_FOUND 1 diff --git a/config-screenlocker.h.cmake b/config-screenlocker.h.cmake new file mode 100644 index 0000000..db2287a --- /dev/null +++ b/config-screenlocker.h.cmake @@ -0,0 +1,16 @@ +#ifdef KSCREENLOCKER_TEST_APPS +#define KCHECKPASS_BIN "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/kcheckpass" +#elif defined(KSCREENLOCKER_UNIT_TEST) +#define KCHECKPASS_BIN "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/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_SIGNALFD_H +#cmakedefine01 HAVE_EVENT_H \ No newline at end of file diff --git a/config-unix.h.cmake b/config-unix.h.cmake new file mode 100644 index 0000000..274f6fa --- /dev/null +++ b/config-unix.h.cmake @@ -0,0 +1,32 @@ +/* Defines if you have PAM (Pluggable Authentication Modules) */ +#cmakedefine HAVE_PAM 1 + +/* Define if your PAM headers are in pam/ instead of security/ */ +#cmakedefine HAVE_PAM_PAM_APPL_H 1 + +/* Define if your PAM expects a conversation function with const pam_message (Solaris) */ +#cmakedefine PAM_MESSAGE_CONST 1 + +/* The PAM service to be used by kscreensaver */ +#cmakedefine KSCREENSAVER_PAM_SERVICE ${KSCREENSAVER_PAM_SERVICE} + +/* Defines if your system has the getspnam function */ +#cmakedefine HAVE_GETSPNAM 1 + +/* Defines if your system has the crypt function */ +#cmakedefine HAVE_CRYPT 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_CRYPT_H 1 + +/* Define to 1 if you have the `pw_encrypt' function. */ +#cmakedefine HAVE_PW_ENCRYPT 1 + +/* Define to 1 if you have the `getpassphrase' function. */ +#cmakedefine HAVE_GETPASSPHRASE 1 + +/* Define to 1 if you have the `vsyslog' function. */ +#cmakedefine HAVE_VSYSLOG 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LIMITS_H 1 diff --git a/config-workspace.h.cmake b/config-workspace.h.cmake new file mode 100644 index 0000000..4eead4e --- /dev/null +++ b/config-workspace.h.cmake @@ -0,0 +1,141 @@ +/* config-workspace.h. Generated by cmake from config-workspace.h.cmake */ + +/* Define if you have DPMS support */ +#cmakedefine HAVE_DPMS 1 + +/* Define if you have the DPMSCapable prototype in */ +#cmakedefine HAVE_DPMSCAPABLE_PROTO 1 + +/* Define if you have the DPMSInfo prototype in */ +#cmakedefine HAVE_DPMSINFO_PROTO 1 + +/* Define if you have gethostname */ +#cmakedefine HAVE_GETHOSTNAME 1 + +/* Define if you have the gethostname prototype */ +#cmakedefine HAVE_GETHOSTNAME_PROTO 1 + +/* Define if you have long long as datatype */ +#cmakedefine HAVE_LONG_LONG 1 + +/* Define to 1 if you have the `nice' function. */ +#cmakedefine HAVE_NICE 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SASL_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SASL_SASL_H 1 + +/* Define to 1 if you have the `setpriority' function. */ +#cmakedefine HAVE_SETPRIORITY 1 + +/* Define to 1 if you have the `sigaction' function. */ +#cmakedefine HAVE_SIGACTION 1 + +/* Define to 1 if you have the `sigset' function. */ +#cmakedefine HAVE_SIGSET 1 + +/* Define to 1 if you have statvfs */ +#cmakedefine HAVE_STATVFS 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STRING_H 1 + +/* Define if you have the struct ucred */ +#cmakedefine HAVE_STRUCT_UCRED 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_LOADAVG_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_MOUNT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_STATFS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_STATVFS_H 1 + +/* Define to 1 if you have statfs(). */ +#cmakedefine HAVE_STATFS 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_VFS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_WAIT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UNISTD_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_MALLOC_H 1 + +/* Define if you have unsetenv */ +#cmakedefine HAVE_UNSETENV 1 + +/* Define if you have the unsetenv prototype */ +#cmakedefine HAVE_UNSETENV_PROTO 1 + +/* Define if you have usleep */ +#cmakedefine HAVE_USLEEP 1 + +/* Define if you have the usleep prototype */ +#cmakedefine HAVE_USLEEP_PROTO 1 + +/* Define to 1 if you have the `vsnprintf' function. */ +#cmakedefine HAVE_VSNPRINTF 1 + +/* Define to 1 if you have the Wayland libraries. */ +#cmakedefine WAYLAND_FOUND 1 + +/* KDE's default home directory */ +#cmakedefine KDE_DEFAULT_HOME "${KDE_DEFAULT_HOME}" + +/* KDE's binaries directory */ +#define KDE_BINDIR "${BIN_INSTALL_DIR}" + +/* KDE's configuration directory */ +#define KDE_CONFDIR "${CONFIG_INSTALL_DIR}" + +/* KDE's static data directory */ +#define KDE_DATADIR "${DATA_INSTALL_DIR}" + +/* Define to 1 if you can safely include both and . */ +#cmakedefine TIME_WITH_SYS_TIME 1 + +/* X binaries directory */ +#cmakedefine XBINDIR "${XBINDIR}" + +/* X libraries directory */ +#cmakedefine XLIBDIR "${XLIBDIR}" + +/* Number of bits in a file offset, on hosts where this is settable. */ +#define _FILE_OFFSET_BITS 64 + +/* + * On HP-UX, the declaration of vsnprintf() is needed every time ! + */ + +/* type to use in place of socklen_t if not defined */ +#define kde_socklen_t socklen_t + diff --git a/screenlocker/CMakeLists.txt b/screenlocker/CMakeLists.txt new file mode 100644 index 0000000..2e609d0 --- /dev/null +++ b/screenlocker/CMakeLists.txt @@ -0,0 +1,28 @@ +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(PROJECT_SOURCES + main.cpp + application.cpp + authenticator.cpp + wallpaper.cpp + kcheckpass-enums.h + fixx11h.h + qml.qrc +) + +add_executable(screenlocker + ${PROJECT_SOURCES} +) + +target_link_libraries(screenlocker + PRIVATE + Qt5::Core + Qt5::DBus + Qt5::Widgets + Qt5::X11Extras + Qt5::Quick + + ${X11_LIBRARIES} +) \ No newline at end of file diff --git a/screenlocker/application.cpp b/screenlocker/application.cpp new file mode 100644 index 0000000..5199cd5 --- /dev/null +++ b/screenlocker/application.cpp @@ -0,0 +1,299 @@ +#include "application.h" +#include "wallpaper.h" + +// Qt Core +#include +#include +#include +#include + +// Qt Quick +#include +#include +#include +#include + +// X11 +#include +#include +#include "fixx11h.h" + +// Xcb +#include + +// this is usable to fake a "screensaver" installation for testing +// *must* be "0" for every public commit! +#define TEST_SCREENSAVER 0 + +class FocusOutEventFilter : public QAbstractNativeEventFilter +{ +public: + bool nativeEventFilter(const QByteArray &eventType, void *message, long int *result) override { + Q_UNUSED(result) + if (qstrcmp(eventType, "xcb_generic_event_t") != 0) { + return false; + } + + xcb_generic_event_t *event = reinterpret_cast(message); + if ((event->response_type & ~0x80) == XCB_FOCUS_OUT) { + return true; + } + + return false; + } +}; + +Application::Application(int &argc, char **argv) + : QGuiApplication(argc, argv) + , m_authenticator(new Authenticator(AuthenticationMode::Direct, this)) +{ + // It's a queued connection to give the QML part time to eventually execute code connected to Authenticator::succeeded if any + connect(m_authenticator, &Authenticator::succeeded, this, &QCoreApplication::quit, Qt::QueuedConnection); + + installEventFilter(this); + + // Screens + connect(this, &Application::screenAdded, this, &Application::onScreenAdded); + connect(this, &Application::screenRemoved, this, &Application::desktopResized); + + if (QX11Info::isPlatformX11()) { + installNativeEventFilter(new FocusOutEventFilter); + } +} + +Application::~Application() +{ + // workaround QTBUG-55460 + // will be fixed when themes port to QQC2 + for (auto view : qAsConst(m_views)) { + if (QQuickItem *focusItem = view->activeFocusItem()) { + focusItem->setFocus(false); + } + } + qDeleteAll(m_views); +} + +void Application::initialViewSetup() +{ + for (QScreen *screen : screens()) { + connect(screen, &QScreen::geometryChanged, this, [this, screen](const QRect &geo) { + screenGeometryChanged(screen, geo); + }); + } + + desktopResized(); +} + +void Application::desktopResized() +{ + const int nScreens = screens().count(); + // remove useless views and savers + while (m_views.count() > nScreens) { + m_views.takeLast()->deleteLater(); + } + + // extend views and savers to current demand + for (int i = m_views.count(); i < nScreens; ++i) { + // create the view + auto *view = new QQuickView; + view->create(); + + // engine stuff + QQmlContext *context = view->engine()->rootContext(); + context->setContextProperty(QStringLiteral("authenticator"), m_authenticator); + context->setContextProperty(QStringLiteral("wallpaper"), new Wallpaper); + + view->setSource(QUrl("qrc:/qml/LockScreen.qml")); + view->setResizeMode(QQuickView::SizeRootObjectToView); + + view->setColor(Qt::black); + auto screen = QGuiApplication::screens()[i]; + view->setGeometry(screen->geometry()); + + if (!m_testing) { + if (QX11Info::isPlatformX11()) { + view->setFlags(Qt::X11BypassWindowManagerHint); + } else { + view->setFlags(Qt::FramelessWindowHint); + } + } + + // overwrite the factory set by kdeclarative + // auto oldFactory = view->engine()->networkAccessManagerFactory(); + // view->engine()->setNetworkAccessManagerFactory(nullptr); + // delete oldFactory; + // view->engine()->setNetworkAccessManagerFactory(new NoAccessNetworkAccessManagerFactory); + + view->setGeometry(screen->geometry()); + + connect(view, &QQuickView::frameSwapped, this, [=] { markViewsAsVisible(view); }, Qt::QueuedConnection); + + m_views << view; + } + + // update geometry of all views and savers + for (int i = 0; i < nScreens; ++i) { + auto *view = m_views.at(i); + auto screen = QGuiApplication::screens()[i]; + view->setScreen(screen); + + // on Wayland we may not use fullscreen as that puts all windows on one screen + if (m_testing || QX11Info::isPlatformX11()) { + view->show(); + } else { + view->showFullScreen(); + } + + view->raise(); + } +} + +void Application::onScreenAdded(QScreen *screen) +{ + // Lambda connections can not have uniqueness constraints, ensure + // geometry change signals are only connected once + connect(screen, &QScreen::geometryChanged, this, [this, screen](const QRect &geo) { + screenGeometryChanged(screen, geo); + }); + + desktopResized(); +} + +void Application::getFocus() +{ + QWindow *activeScreen = getActiveScreen(); + + if (!activeScreen) { + return; + } + + // this loop is required to make the qml/graphicsscene properly handle the shared keyboard input + // ie. "type something into the box of every greeter" + for (QQuickView *view : qAsConst(m_views)) { + if (!m_testing) { + view->setKeyboardGrabEnabled(true); // TODO - check whether this still works in master! + } + } + + // activate window and grab input to be sure it really ends up there. + // focus setting is still required for proper internal QWidget state (and eg. visual reflection) + if (!m_testing) { + activeScreen->setKeyboardGrabEnabled(true); // TODO - check whether this still works in master! + } + + activeScreen->requestActivate(); +} + +void Application::markViewsAsVisible(QQuickView *view) +{ + disconnect(view, &QQuickWindow::frameSwapped, this, nullptr); + QQmlProperty showProperty(view->rootObject(), QStringLiteral("viewVisible")); + showProperty.write(true); + + // random state update, actually rather required on init only + QMetaObject::invokeMethod(this, "getFocus", Qt::QueuedConnection); +} + +bool Application::eventFilter(QObject *obj, QEvent *event) +{ + if (obj != this && event->type() == QEvent::Show) { + QQuickView *view = nullptr; + for (QQuickView *v : qAsConst(m_views)) { + if (v == obj) { + view = v; + break; + } + } + if (view && view->winId() && QX11Info::isPlatformX11()) { + // showing greeter view window, set property + static Atom tag = XInternAtom(QX11Info::display(), "_KDE_SCREEN_LOCKER", False); + XChangeProperty(QX11Info::display(), view->winId(), tag, tag, 32, PropModeReplace, nullptr, 0); + } + // no further processing + return false; + } + + if (event->type() == QEvent::MouseButtonPress && QX11Info::isPlatformX11()) { + if (getActiveScreen()) { + getActiveScreen()->requestActivate(); + } + return false; + } + + if (event->type() == QEvent::KeyPress) { // react if saver is visible + shareEvent(event, qobject_cast(obj)); + return false; // we don't care + } else if (event->type() == QEvent::KeyRelease) { // conditionally reshow the saver + QKeyEvent *ke = static_cast(event); + if (ke->key() != Qt::Key_Escape) { + shareEvent(event, qobject_cast(obj)); + return false; // irrelevant + } + return true; // don't pass + } + + return false; +} + +QWindow *Application::getActiveScreen() +{ + QWindow *activeScreen = nullptr; + + if (m_views.isEmpty()) { + return activeScreen; + } + + for (QQuickView *view : qAsConst(m_views)) { + if (view->geometry().contains(QCursor::pos())) { + activeScreen = view; + break; + } + } + if (!activeScreen) { + activeScreen = m_views.first(); + } + + return activeScreen; +} + +void Application::shareEvent(QEvent *e, QQuickView *from) +{ + // from can be NULL any time (because the parameter is passed as qobject_cast) + // m_views.contains(from) is atm. supposed to be true but required if any further + // QQuickView are added (which are not part of m_views) + // this makes "from" an optimization (nullptr check aversion) + if (from && m_views.contains(from)) { + // NOTICE any recursion in the event sharing will prevent authentication on multiscreen setups! + // Any change in regarded event processing shall be tested thoroughly! + removeEventFilter(this); // prevent recursion! + const bool accepted = e->isAccepted(); // store state + for (QQuickView *view : qAsConst(m_views)) { + if (view != from) { + QCoreApplication::sendEvent(view, e); + e->setAccepted(accepted); + } + } + installEventFilter(this); + } +} + +void Application::screenGeometryChanged(QScreen *screen, const QRect &geo) +{ + // We map screens() to m_views by index and Qt is free to + // reorder screens, so pointer to pointer connections + // may not remain matched by index, perform index + // mapping in the change event itself + const int screenIndex = QGuiApplication::screens().indexOf(screen); + if (screenIndex < 0) { + qWarning() << "Screen not found, not updating geometry" << screen; + return; + } + + if (screenIndex >= m_views.size()) { + qWarning() << "Screen index out of range, not updating geometry" << screenIndex; + return; + } + + QQuickView *view = m_views[screenIndex]; + view->setGeometry(geo); +} diff --git a/screenlocker/application.h b/screenlocker/application.h new file mode 100644 index 0000000..5dc6bd5 --- /dev/null +++ b/screenlocker/application.h @@ -0,0 +1,44 @@ +#ifndef APPLICATION_H +#define APPLICATION_H + +#include +#include +#include "authenticator.h" + +struct org_kde_ksld; + +class Application : public QGuiApplication +{ + Q_OBJECT + +public: + explicit Application(int &argc, char **argv); + ~Application(); + + void initialViewSetup(); + +public slots: + void desktopResized(); + void onScreenAdded(QScreen *screen); + +private slots: + void getFocus(); + void markViewsAsVisible(QQuickView *view); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + +private: + QWindow *getActiveScreen(); + void shareEvent(QEvent *e, QQuickView *from); + void screenGeometryChanged(QScreen *screen, const QRect &geo); + +private: + Authenticator *m_authenticator; + QList m_views; + + org_kde_ksld *m_ksldInterface = nullptr; + bool m_testing = true; +}; + +#endif // APPLICATION_H diff --git a/screenlocker/authenticator.cpp b/screenlocker/authenticator.cpp new file mode 100644 index 0000000..01a8fd7 --- /dev/null +++ b/screenlocker/authenticator.cpp @@ -0,0 +1,333 @@ +/******************************************************************** + 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 "kcheckpass-enums.h" + +// 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()) { + Q_EMIT failed(); + return; + } + m_graceLockTimer->start(); + Q_EMIT graceLockedChanged(); + + if (!m_checkPass) { + m_checkPass = new KCheckPass(AuthenticationMode::Direct, this); + m_checkPass->setPassword(password); + setupCheckPass(); + } else { + if (!m_checkPass->isReady()) { + Q_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("ccheckpass")).data(), "kcheckpass", "-m", "classic", "-S", fdbuf, (char *)nullptr); + _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 = nullptr; + return true; + } + if (!(buf = (char *)::malloc(len))) { + return false; + } + *ret = buf; + if (GRead(buf, len)) { + return true; + } else { + ::free(buf); + *ret = nullptr; + 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: + case ConvGetHidden: { + if (!GRecvArr(&arr)) { + break; + } + + if (m_password.isNull()) { + GSendStr(nullptr); + } else { + QByteArray utf8pass = m_password.toUtf8(); + GSendStr(utf8pass.constData()); + GSendInt(IsPassword); + } + + m_password.clear(); + + if (arr) { + ::free(arr); + } + return; + } + case ConvPutInfo: + if (!GRecvArr(&arr)) { + break; + } + Q_EMIT message(QString::fromLocal8Bit(arr)); + ::free(arr); + return; + case ConvPutError: + if (!GRecvArr(&arr)) { + break; + } + Q_EMIT error(QString::fromLocal8Bit(arr)); + ::free(arr); + return; + case ConvPutAuthSucceeded: + Q_EMIT succeeded(); + return; + case ConvPutAuthFailed: + Q_EMIT failed(); + return; + case ConvPutAuthError: + case ConvPutAuthAbort: + cantCheck(); + 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? + Q_EMIT failed(); +} + +void KCheckPass::startAuth() +{ + ::kill(m_pid, SIGUSR1); +} diff --git a/screenlocker/authenticator.h b/screenlocker/authenticator.h new file mode 100644 index 0000000..1feac4a --- /dev/null +++ b/screenlocker/authenticator.h @@ -0,0 +1,111 @@ +/******************************************************************** + KSld - the KDE Screenlocker Daemon + This file is part of the KDE project. + +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 . +*********************************************************************/ +#ifndef AUTHENTICATOR_H +#define AUTHENTICATOR_H + +#include + +class QSocketNotifier; +class QTimer; +class KCheckPass; + +enum class AuthenticationMode { + Delayed, + Direct, +}; + +class Authenticator : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool graceLocked READ isGraceLocked NOTIFY graceLockedChanged) +public: + explicit Authenticator(AuthenticationMode mode = AuthenticationMode::Direct, QObject *parent = nullptr); + ~Authenticator() override; + + bool isGraceLocked() const; + +public Q_SLOTS: + void tryUnlock(const QString &password); + +Q_SIGNALS: + void failed(); + void succeeded(); + void graceLockedChanged(); + void message(const QString &msg); // don't remove the "msg" param, used in QML!!! + void error(const QString &err); // don't remove the "err" param, used in QML!!! + +private: + void setupCheckPass(); + QTimer *m_graceLockTimer; + KCheckPass *m_checkPass; +}; + +class KCheckPass : public QObject +{ + Q_OBJECT +public: + explicit KCheckPass(AuthenticationMode mode, QObject *parent = nullptr); + ~KCheckPass() override; + + void start(); + + bool isReady() const + { + return m_ready; + } + + void setPassword(const QString &password) + { + m_password = password; + } + + void startAuth(); + +Q_SIGNALS: + void failed(); + void succeeded(); + void message(const QString &); + void error(const QString &); + +private Q_SLOTS: + void handleVerify(); + +private: + void cantCheck(); + void reapVerify(); + // kcheckpass interface + int Reader(void *buf, int count); + bool GRead(void *buf, int count); + bool GWrite(const void *buf, int count); + bool GSendInt(int val); + bool GSendStr(const char *buf); + bool GSendArr(int len, const char *buf); + bool GRecvInt(int *val); + bool GRecvArr(char **buf); + + QString m_password; + QSocketNotifier *m_notifier; + int m_pid; + int m_fd; + bool m_ready = false; + AuthenticationMode m_mode; +}; + +#endif diff --git a/screenlocker/fixx11h.h b/screenlocker/fixx11h.h new file mode 100644 index 0000000..f3cbed2 --- /dev/null +++ b/screenlocker/fixx11h.h @@ -0,0 +1,278 @@ +/* + SPDX-FileCopyrightText: 2003 Lubos Lunak + + SPDX-License-Identifier: MIT +*/ + +//#ifdef don't do this, this file is supposed to be included +//#define multiple times + +#include + +/* Usage: + + If you get compile errors caused by X11 includes (the line + where first error appears contains word like None, Unsorted, + Below, etc.), put #include in the .cpp file + (not .h file!) between the place where X11 headers are + included and the place where the file with compile + error is included (or the place where the compile error + in the .cpp file occurs). + + This file remaps X11 #defines to const variables or + inline functions. The side effect may be that these + symbols may now refer to different variables + (e.g. if X11 #defined NoButton, after this file + is included NoButton would no longer be X11's + NoButton, but Qt::NoButton instead). At this time, + there's no conflict known that could cause problems. + + The original X11 symbols are still accessible + (e.g. for None) as X::None, XNone, and also still + None, unless name lookup finds different None + first (in the current class, etc.) + + Use 'Unsorted', 'Bool' and 'index' as templates. + +*/ + +namespace X +{ +// template ---> +// Affects: Should be without side effects. +#ifdef Unsorted +#ifndef FIXX11H_Unsorted +#define FIXX11H_Unsorted +const int XUnsorted = Unsorted; +#undef Unsorted +const int Unsorted = XUnsorted; +#endif +#undef Unsorted +#endif +// template <--- + +// Affects: Should be without side effects. +#ifdef None +#ifndef FIXX11H_None +#define FIXX11H_None +const XID XNone = None; +#undef None +const XID None = XNone; +#endif +#undef None +#endif + +// template ---> +// Affects: Should be without side effects. +#ifdef Bool +#ifndef FIXX11H_Bool +#define FIXX11H_Bool +#ifdef _XTYPEDEF_BOOL /* Xdefs.h has typedef'ed Bool already */ +#undef Bool +#else +typedef Bool XBool; +#undef Bool +typedef XBool Bool; +#endif +#endif +#undef Bool +#define _XTYPEDEF_BOOL +#endif +// template <--- + +// Affects: Should be without side effects. +#ifdef KeyPress +#ifndef FIXX11H_KeyPress +#define FIXX11H_KeyPress +const int XKeyPress = KeyPress; +#undef KeyPress +const int KeyPress = XKeyPress; +#endif +#undef KeyPress +#endif + +// Affects: Should be without side effects. +#ifdef KeyRelease +#ifndef FIXX11H_KeyRelease +#define FIXX11H_KeyRelease +const int XKeyRelease = KeyRelease; +#undef KeyRelease +const int KeyRelease = XKeyRelease; +#endif +#undef KeyRelease +#endif + +// Affects: Should be without side effects. +#ifdef Above +#ifndef FIXX11H_Above +#define FIXX11H_Above +const int XAbove = Above; +#undef Above +const int Above = XAbove; +#endif +#undef Above +#endif + +// Affects: Should be without side effects. +#ifdef Below +#ifndef FIXX11H_Below +#define FIXX11H_Below +const int XBelow = Below; +#undef Below +const int Below = XBelow; +#endif +#undef Below +#endif + +// Affects: Should be without side effects. +#ifdef FocusIn +#ifndef FIXX11H_FocusIn +#define FIXX11H_FocusIn +const int XFocusIn = FocusIn; +#undef FocusIn +const int FocusIn = XFocusIn; +#endif +#undef FocusIn +#endif + +// Affects: Should be without side effects. +#ifdef FocusOut +#ifndef FIXX11H_FocusOut +#define FIXX11H_FocusOut +const int XFocusOut = FocusOut; +#undef FocusOut +const int FocusOut = XFocusOut; +#endif +#undef FocusOut +#endif + +// Affects: Should be without side effects. +#ifdef Always +#ifndef FIXX11H_Always +#define FIXX11H_Always +const int XAlways = Always; +#undef Always +const int Always = XAlways; +#endif +#undef Always +#endif + +// Affects: Should be without side effects. +#ifdef Expose +#ifndef FIXX11H_Expose +#define FIXX11H_Expose +const int XExpose = Expose; +#undef Expose +const int Expose = XExpose; +#endif +#undef Expose +#endif + +// Affects: Should be without side effects. +#ifdef Success +#ifndef FIXX11H_Success +#define FIXX11H_Success +const int XSuccess = Success; +#undef Success +const int Success = XSuccess; +#endif +#undef Success +#endif + +// Affects: Should be without side effects. +#ifdef GrayScale +#ifndef FIXX11H_GrayScale +#define FIXX11H_GrayScale +const int XGrayScale = GrayScale; +#undef GrayScale +const int GrayScale = XGrayScale; +#endif +#undef GrayScale +#endif + +// Affects: Should be without side effects. +#ifdef Status +#ifndef FIXX11H_Status +#define FIXX11H_Status +typedef Status XStatus; +#undef Status +typedef XStatus Status; +#endif +#undef Status +#endif + +// template ---> +// Affects: Should be without side effects. +#ifdef CursorShape +#ifndef FIXX11H_CursorShape +#define FIXX11H_CursorShape +const int XCursorShape = CursorShape; +#undef CursorShape +const int CursorShape = XCursorShape; +#endif +#undef CursorShape +#endif +// template <--- + +// template ---> +// Affects: Should be without side effects. +#ifdef FontChange +#ifndef FIXX11H_FontChange +#define FIXX11H_FontChange +const int XFontChange = FontChange; +#undef FontChange +const int FontChange = XFontChange; +#endif +#undef FontChange +#endif +// template <--- + +// Affects: Should be without side effects. +#ifdef NormalState +#ifndef FIXX11H_NormalState +#define FIXX11H_NormalState +const int XNormalState = NormalState; +#undef NormalState +const int NormalState = XNormalState; +#endif +#undef NormalState +#endif + +// template ---> +// Affects: Should be without side effects. +#ifdef index +#ifndef FIXX11H_index +#define FIXX11H_index +inline const char *Xindex(const char *s, int c) +{ + return index(s, c); +} +#undef index +inline const char *index(const char *s, int c) +{ + return Xindex(s, c); +} +#endif +#undef index +#endif +// template <--- + +#ifdef rindex +// Affects: Should be without side effects. +#ifndef FIXX11H_rindex +#define FIXX11H_rindex +inline const char *Xrindex(const char *s, int c) +{ + return rindex(s, c); +} +#undef rindex +inline const char *rindex(const char *s, int c) +{ + return Xrindex(s, c); +} +#endif +#undef rindex +#endif +} + +using namespace X; diff --git a/screenlocker/kcheckpass-enums.h b/screenlocker/kcheckpass-enums.h new file mode 100644 index 0000000..5716d16 --- /dev/null +++ b/screenlocker/kcheckpass-enums.h @@ -0,0 +1,75 @@ +/***************************************************************** + * + *· kcheckpass + * + *· 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. + * + * + * 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. + * + *· 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 + *· Oswald Buddenhagen Binary server mode + * + * Other parts were taken from kscreensaver's passwd.cpp + *****************************************************************/ + +#ifndef KCHECKPASS_ENUMS_H +#define KCHECKPASS_ENUMS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* these must match kcheckpass' exit codes */ +typedef enum { + AuthOk = 0, + AuthBad = 1, + AuthError = 2, + AuthAbort = 3, +} AuthReturn; + +typedef enum { + ConvGetBinary, + ConvGetNormal, + ConvGetHidden, + ConvPutInfo, + ConvPutError, + ConvPutAuthSucceeded, + ConvPutAuthFailed, + ConvPutAuthError, + ConvPutAuthAbort, + ConvPutReadyForAuthentication, +} ConvRequest; + +/* these must match the defs in kgreeterplugin.h */ +typedef enum { + IsUser = 1, /* unused in kcheckpass */ + IsPassword = 2, +} DataTag; + +#ifdef __cplusplus +} +#endif + +#endif /* KCHECKPASS_ENUMS_H */ diff --git a/screenlocker/main.cpp b/screenlocker/main.cpp new file mode 100644 index 0000000..22fbf04 --- /dev/null +++ b/screenlocker/main.cpp @@ -0,0 +1,9 @@ +#include "application.h" + +int main(int argc, char *argv[]) +{ + Application app(argc, argv); + app.setQuitOnLastWindowClosed(false); + app.initialViewSetup(); + return app.exec(); +} diff --git a/screenlocker/qml.qrc b/screenlocker/qml.qrc new file mode 100644 index 0000000..081bbed --- /dev/null +++ b/screenlocker/qml.qrc @@ -0,0 +1,6 @@ + + + qml/LockScreen.qml + qml/Greeter.qml + + diff --git a/screenlocker/qml/Greeter.qml b/screenlocker/qml/Greeter.qml new file mode 100644 index 0000000..9c36e13 --- /dev/null +++ b/screenlocker/qml/Greeter.qml @@ -0,0 +1,5 @@ +import QtQuick 2.0 + +Item { + +} diff --git a/screenlocker/qml/LockScreen.qml b/screenlocker/qml/LockScreen.qml new file mode 100644 index 0000000..11af041 --- /dev/null +++ b/screenlocker/qml/LockScreen.qml @@ -0,0 +1,117 @@ +import QtQuick 2.0 +import QtQuick.Window 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtGraphicalEffects 1.0 + +import FishUI 1.0 as FishUI + +Item { + id: root + + property alias notification: message.text + + Image { + id: wallpaperImage + anchors.fill: parent + source: "file://" + wallpaper.path + sourceSize: Qt.size(width * Screen.devicePixelRatio, + height * Screen.devicePixelRatio) + fillMode: Image.PreserveAspectCrop + asynchronous: false + clip: true + cache: false + smooth: true + } + + FastBlur { + id: wallpaperBlur + anchors.fill: parent + radius: 64 + source: wallpaperImage + cached: true + visible: true + } + + ColumnLayout { + anchors.fill: parent + spacing: FishUI.Units.largeSpacing * 1.5 + + Item { + Layout.fillHeight: true + } + + Label { + Layout.alignment: Qt.AlignHCenter + text: "cutefish" + color: "white" + } + + TextField { + id: password + Layout.alignment: Qt.AlignHCenter + Layout.preferredHeight: 40 + Layout.preferredWidth: 240 + placeholderText: qsTr("Password") + focus: true + + echoMode: TextInput.Password + + background: Rectangle { + color: "white" + opacity: 0.4 + radius: FishUI.Theme.bigRadius + } + + Keys.onEnterPressed: authenticator.tryUnlock(password.text) + Keys.onReturnPressed: authenticator.tryUnlock(password.text) + Keys.onEscapePressed: password.text = "" + } + + Button { + Layout.alignment: Qt.AlignHCenter + Layout.preferredHeight: 40 + Layout.preferredWidth: 240 + text: qsTr("Unlock") + onClicked: authenticator.tryUnlock(password.text) + } + + Label { + id: message + text: "" + Layout.alignment: Qt.AlignHCenter + font.bold: true + color: "white" + Behavior on opacity { + NumberAnimation { + duration: 250 + } + } + opacity: text == "" ? 0 : 1 + } + + Item { + Layout.fillHeight: true + } + } + + Connections { + target: authenticator + function onFailed() { + root.notification = qsTr("Unlocking failed") + } + function onGraceLockedChanged() { + if (!authenticator.graceLocked) { + root.notification = "" + password.selectAll() + password.focus = true + } + } + function onMessage(text) { + root.notification = text + } + function onError(text) { + root.notification = text + } + } +} diff --git a/screenlocker/wallpaper.cpp b/screenlocker/wallpaper.cpp new file mode 100644 index 0000000..99d0a63 --- /dev/null +++ b/screenlocker/wallpaper.cpp @@ -0,0 +1,24 @@ +#include "wallpaper.h" + +Wallpaper::Wallpaper(QObject *parent) + : QObject(parent) + , m_interface("org.cutefish.Settings", + "/Theme", "org.cutefish.Theme", + QDBusConnection::sessionBus(), this) +{ + if (m_interface.isValid()) { + connect(&m_interface, SIGNAL(wallpaperChanged(QString)), this, SLOT(onPathChanged(QString))); + } +} + +QString Wallpaper::path() const +{ + return m_interface.property("wallpaper").toString(); +} + +void Wallpaper::onPathChanged(QString path) +{ + Q_UNUSED(path); + + emit pathChanged(); +} diff --git a/screenlocker/wallpaper.h b/screenlocker/wallpaper.h new file mode 100644 index 0000000..a5cc372 --- /dev/null +++ b/screenlocker/wallpaper.h @@ -0,0 +1,28 @@ +#ifndef WALLPAPER_H +#define WALLPAPER_H + +#include +#include + +class Wallpaper : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString path READ path NOTIFY pathChanged) + +public: + explicit Wallpaper(QObject *parent = nullptr); + + QString path() const; + +signals: + void pathChanged(); + +private slots: + void onPathChanged(QString path); + +private: + QDBusInterface m_interface; + QString m_wallpaper; +}; + +#endif // WALLPAPER_H