diff --git a/greeter/authenticator.cpp b/greeter/authenticator.cpp index 1389c6d..5b8a56c 100644 --- a/greeter/authenticator.cpp +++ b/greeter/authenticator.cpp @@ -1,279 +1,279 @@ /******************************************************************** 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 // system #include #include #include #include #include Authenticator::Authenticator(QObject *parent) : QObject(parent) , m_graceLockTimer(new QTimer(this)) { m_graceLockTimer->setSingleShot(true); m_graceLockTimer->setInterval(3000); connect(m_graceLockTimer, &QTimer::timeout, this, &Authenticator::graceLockedChanged); } Authenticator::~Authenticator() = default; void Authenticator::tryUnlock(const QString &password) { if (isGraceLocked()) { emit failed(); return; } m_graceLockTimer->start(); emit graceLockedChanged(); KCheckPass *checkPass = new KCheckPass(password, this); connect(checkPass, &KCheckPass::succeeded, this, &Authenticator::succeeded); connect(checkPass, &KCheckPass::failed, this, &Authenticator::failed); connect(checkPass, &KCheckPass::message, this, &Authenticator::message); connect(checkPass, &KCheckPass::error, this, &Authenticator::error); checkPass->start(); } bool Authenticator::isGraceLocked() const { return m_graceLockTimer->isActive(); } KCheckPass::KCheckPass(const QString &password, QObject *parent) : QObject(parent) , m_password(password) , m_notifier(nullptr) , m_pid(0) , m_fd(0) { connect(this, &KCheckPass::succeeded, this, &QObject::deleteLater); connect(this, &KCheckPass::failed, this, &QObject::deleteLater); } KCheckPass::~KCheckPass() = default; 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() { 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; } } reapVerify(); } void KCheckPass::reapVerify() { m_notifier->setEnabled( false ); m_notifier->deleteLater(); m_notifier = nullptr; ::close( m_fd ); int status; while (::waitpid( m_pid, &status, 0 ) < 0) if (errno != EINTR) { // This should not happen ... cantCheck(); return; } - if (WIFEXITED(status)) - switch (WEXITSTATUS(status)) { - case AuthOk: - emit succeeded(); - return; - case AuthBad: - emit failed(); - return; - case AuthAbort: - return; - } - cantCheck(); } void KCheckPass::cantCheck() { // TODO: better signal? emit failed(); } diff --git a/greeter/autotests/fakekcheckpass.c b/greeter/autotests/fakekcheckpass.c index 159f69b..fb5ce0d 100644 --- a/greeter/autotests/fakekcheckpass.c +++ b/greeter/autotests/fakekcheckpass.c @@ -1,266 +1,267 @@ /******************************************************************** 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 . This test is based on kcheckpass, written by: 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 *********************************************************************/ #include #include #include #include #include #include #include #include #include #include #include static int havetty, sfd = -1, nullpass; #ifdef __cplusplus extern "C" { #endif # define ATTR_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var))) void message(const char *, ...) ATTR_PRINTFLIKE(1, 2); #ifdef __cplusplus } #endif 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 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 int main(int argc, char **argv) { const char *method = "classic"; int c, nfd; #ifdef HAVE_OSF_C2_PASSWD initialize_osf_security(argc, argv); #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 'm': method = optarg; break; case 'S': sfd = atoi(optarg); break; default: message("Command line option parsing error\n"); } } /* Now do the fandango */ const char *password = conv_server(ConvGetHidden, 0); if (strcmp(password, "testpassword") == 0) { - return AuthOk; + conv_server(ConvPutAuthSucceeded, 0); } else if (strcmp(password, "info") == 0) { conv_server(ConvPutInfo, "You wanted some info, here you have it"); - return AuthOk; + conv_server(ConvPutAuthSucceeded, 0); } else if (strcmp(password, "error") == 0) { conv_server(ConvPutError, "The world is going to explode"); - return AuthError; + conv_server(ConvPutAuthFailed, 0); } else if (strcmp(password, "") == 0) { conv_server(ConvPutError, "Hey, don't give me an empty password"); - return AuthError; + } else { + conv_server(ConvPutAuthFailed, 0); } - return AuthBad; + return 0; } diff --git a/kcheckpass/kcheckpass-enums.h b/kcheckpass/kcheckpass-enums.h index 58387ff..66389ee 100644 --- a/kcheckpass/kcheckpass-enums.h +++ b/kcheckpass/kcheckpass-enums.h @@ -1,70 +1,74 @@ /***************************************************************** * *· 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 + ConvPutError, + ConvPutAuthSucceeded, + ConvPutAuthFailed, + ConvPutAuthError, + ConvPutAuthAbort } 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/kcheckpass/kcheckpass.c b/kcheckpass/kcheckpass.c index 5b071ae..bb80146 100644 --- a/kcheckpass/kcheckpass.c +++ b/kcheckpass/kcheckpass.c @@ -1,345 +1,365 @@ /***************************************************************** * * 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 #if HAVE_SYS_PRCTL_H #include #endif #if HAVE_SYS_PROCCTL_H #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: + 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; // 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 /* 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; } /* 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; + } - return ret; + 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 *****************************************************************/