diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1d6d117..e1c4b4b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,7 +17,6 @@ set(DAEMON_SOURCES daemon/PowerManager.cpp daemon/Seat.cpp daemon/SeatManager.cpp - daemon/Session.cpp daemon/SignalHandler.cpp daemon/SocketServer.cpp daemon/xdmcp/Packet.cpp @@ -32,11 +31,6 @@ if(USE_QT5) add_executable(sddm ${DAEMON_SOURCES}) target_link_libraries(sddm ${LIBXCB_LIBRARIES}) - if(PAM_FOUND) - target_link_libraries(sddm ${PAM_LIBRARIES}) - else() - target_link_libraries(sddm crypt) - endif() qt5_use_modules(sddm DBus Network) else() set(QT_USE_QTNETWORK TRUE) @@ -58,6 +49,43 @@ endif() install(TARGETS sddm DESTINATION ${BIN_INSTALL_DIR}) +## AUTHENTICATOR ## + +set(AUTHENTICATOR_SOURCES + auth/AuthenticatorApp.cpp + auth/Method.cpp + auth/Session.cpp + common/Configuration.cpp +) +if(PAM_FOUND) + set(AUTHENTICATOR_SOURCES + ${AUTHENTICATOR_SOURCES} + auth/PAM.cpp + ) +endif() + +if(USE_QT5) + add_executable(sddm-auth ${AUTHENTICATOR_SOURCES}) + target_link_libraries(sddm-auth ${LIBXCB_LIBRARIES}) + if(PAM_FOUND) + target_link_libraries(sddm-auth ${PAM_LIBRARIES}) + else() + target_link_libraries(sddm-auth crypt) + endif() +else() + include(${QT_USE_FILE}) + + add_executable(sddm-auth ${AUTHENTICATOR_SOURCES}) + target_link_libraries(sddm-auth ${LIBXCB_LIBRARIES} ${QT_LIBRARIES}) + if(PAM_FOUND) + target_link_libraries(sddm-auth ${PAM_LIBRARIES}) + else() + target_link_libraries(sddm-auth crypt) + endif() +endif() + +install(TARGETS sddm-auth DESTINATION ${BIN_INSTALL_DIR}) + ## GREETER ## set(GREETER_SOURCES diff --git a/src/auth/AuthenticatorApp.cpp b/src/auth/AuthenticatorApp.cpp new file mode 100644 index 0000000..dc4f039 --- /dev/null +++ b/src/auth/AuthenticatorApp.cpp @@ -0,0 +1,177 @@ +/* + * + * Copyright (C) 2013 Martin Bříza + * + * 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 "AuthenticatorApp.h" +#include "Configuration.h" +#include "Constants.h" +#include "Method.h" +#include "PAM.h" +#include "Session.h" + +#include +#include +#include +#include + +#include +#include + +namespace SDDM { + AuthenticatorApp *AuthenticatorApp::self = nullptr; + + AuthenticatorApp::AuthenticatorApp(int argc, char **argv) + : QCoreApplication(argc, argv) + , m_method(new Method(this)) + , m_notifier(new QSocketNotifier(fileno(stdin), QSocketNotifier::Read, this)) { + if (!self) + self = this; + new Configuration(CONFIG_FILE, this); + connect(this, SIGNAL(started(QString,QString,QString,bool)), m_method, SLOT(start(QString,QString,QString,bool))); + connect(this, SIGNAL(stopped()), m_method, SLOT(stop())); + connect(m_method, SIGNAL(loginFailed()), this, SLOT(slotLoginFailed())); + connect(m_method, SIGNAL(loginSucceeded(QString)), this, SLOT(slotLoginSucceeded(QString))); + connect(m_method, SIGNAL(sessionTerminated()), this, SLOT(quit())); + connect(m_notifier, SIGNAL(activated(int)), this, SLOT(readFromParent(int))); + m_notifier->setEnabled(true); + + m_input.open(fileno(stdin), QIODevice::ReadOnly | QIODevice::Unbuffered); + m_output.open(fileno(stdout), QIODevice::WriteOnly | QIODevice::Unbuffered); + + qCritical() << " AUTH: Started"; + } + + AuthenticatorApp* AuthenticatorApp::instance() { + return self; + } + + void AuthenticatorApp::readFromParent(int fd) { + qDebug() << " AUTH: Message received" << m_input.bytesAvailable(); + QDataStream inStream(&m_input); + quint32 command = quint32(AuthMessages::AuthNone); + inStream >> command; + qDebug() << "Command" << command; + handleMessage(AuthMessages(command)); + } + + void AuthenticatorApp::handleMessage(AuthMessages command) { + QDataStream inStream(&m_input); + QDataStream outStream(&m_output); + switch (command) { + case AuthMessages::Start: { + QString user, session, password; + bool passwordless; + inStream >> user >> session >> password >> passwordless; + emit started(user, session, password, passwordless); + break; + } + case AuthMessages::End: + emit stopped(); + break; + default: + break; + } + } + + QProcessEnvironment AuthenticatorApp::requestEnvironment(const QString &user) { + qDebug() << " AUTH: requestEnvironment start"; + QDataStream inStream(&m_input); + QDataStream outStream(&m_output); + quint32 command = quint32(AuthMessages::AuthNone); + int count; + QProcessEnvironment env; + + qDebug() << "Requesting environment for user" << user; + outStream << quint32(AuthMessages::RequestEnv) << user; + + inStream >> command; + if (command != quint32(AuthMessages::Env)) { + qDebug() << " AUTH: Received out of order message" << command << "when waiting for Env"; + handleMessage(AuthMessages(command)); + return env; + } + + inStream >> count; + while (count--) { + QString entry; + inStream >> entry; + env.insert(entry.left(entry.indexOf("=")), entry.mid(entry.indexOf("=") + 1)); + } + + return env; + } + + int AuthenticatorApp::requestSessionId() { + qDebug() << " AUTH: requestSessionId start"; + QDataStream inStream(&m_input); + QDataStream outStream(&m_output); + quint32 command = quint32(AuthMessages::AuthNone); + int id; + + outStream << quint32(AuthMessages::RequestSessionID); + + inStream >> command; + if (command != quint32(AuthMessages::SessionID)) { + qDebug() << " AUTH: Received out of order message" << command << "when waiting for SessionID"; + handleMessage(AuthMessages(command)); + return -1; + } + inStream >> id; + + qDebug() << " AUTH: requestSessionId end"; + return id; + } + + bool AuthenticatorApp::requestCookieTo(const QString& path, const QString &user) { + qDebug() << " AUTH: requestCookieTo start"; + QDataStream inStream(&m_input); + QDataStream outStream(&m_output); + quint32 command = quint32(AuthMessages::AuthNone); + qDebug() << " AUTH: Requesting Cookie to path" << path << "for user" << user; + + outStream << quint32(AuthMessages::RequestCookieLink) << path << user; + + inStream >> command; + if (command != quint32(AuthMessages::CookieLink)) { + qDebug() << " AUTH: Received out of order message" << command << "when waiting for SessionID"; + handleMessage(AuthMessages(command)); + return false; + } + + qDebug() << " AUTH: requestCookieTo end"; + return true; + } + + void AuthenticatorApp::slotLoginFailed() { + QDataStream outStream(&m_output); + outStream << quint32(AuthMessages::LoginFailed); + } + + void AuthenticatorApp::slotLoginSucceeded(QString user) { + QDataStream outStream(&m_output); + outStream << quint32(AuthMessages::LoginSucceeded) << m_method->name() << user; + } +} + +int main(int argc, char **argv) { + SDDM::AuthenticatorApp app(argc, argv); + return app.exec(); +} + +#include "AuthenticatorApp.moc" diff --git a/src/auth/AuthenticatorApp.h b/src/auth/AuthenticatorApp.h new file mode 100644 index 0000000..d0ea73b --- /dev/null +++ b/src/auth/AuthenticatorApp.h @@ -0,0 +1,68 @@ +/* + * + * Copyright (C) 2013 Martin Bříza + * + * 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. + * + */ + +#ifndef AUTHENTICATORAPP_H +#define AUTHENTICATORAPP_H + +#include "Messages.h" + +#include +#include +#include +#include +#include + +namespace SDDM { + + class Method; + class AuthenticatorApp : public QCoreApplication + { + Q_OBJECT + public: + explicit AuthenticatorApp(int argc, char **argv); + static AuthenticatorApp *instance(); + + QProcessEnvironment requestEnvironment(const QString &user); + int requestSessionId(); + bool requestCookieTo(const QString &path, const QString &user); + + signals: + void started(const QString &user, const QString &session, const QString &password, bool passwordless); + void stopped(); + + public slots: + void slotLoginSucceeded(QString user); + void slotLoginFailed(); + + private slots: + void readFromParent(int fd); + void handleMessage(AuthMessages command); + + private: + static AuthenticatorApp *self; + + Method *m_method { nullptr }; + QSocketNotifier *m_notifier { nullptr }; + QFile m_input { }; + QFile m_output { }; + }; +} + +#endif // AUTHENTICATORAPP_H diff --git a/src/auth/Method.cpp b/src/auth/Method.cpp new file mode 100644 index 0000000..5e1c758 --- /dev/null +++ b/src/auth/Method.cpp @@ -0,0 +1,328 @@ +/* + * + * Copyright (C) 2013 Martin Bříza + * + * 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 "Method.h" +#include "Configuration.h" +#include "Session.h" +#include "AuthenticatorApp.h" + +#include +#include +#include + +#ifndef USE_PAM +#include +#include +#else +#include "PAM.h" +#endif + +#include +#include +#include + +namespace SDDM { + Method::Method(QObject* parent) + : QObject(parent) { + + } + + QString Method::name() const { + if (m_started) + return m_process->name(); + else + return QString(); + } + + void Method::start(const QString& user, const QString& session, const QString& password, bool passwordless) { + if (doStart(user, session, password, passwordless)) + emit loginSucceeded(user); + else + emit loginFailed(); + } + + void Method::buildSessionName(const QString& session) { + if (session.endsWith(".desktop")) { + // session directory + QDir dir(Configuration::instance()->sessionsDir()); + + // session file + QFile file(dir.absoluteFilePath(session)); + + // open file + if (file.open(QIODevice::ReadOnly)) { + + // read line-by-line + QTextStream in(&file); + while (!in.atEnd()) { + QString line = in.readLine(); + + // line starting with Exec + if (line.startsWith("Exec=")) + m_sessionCommand = line.mid(5); + } + + // close file + file.close(); + } + + // remove extension + m_sessionName = QString(session.left(session.lastIndexOf("."))); + } else { + m_sessionCommand = QString(session); + m_sessionName = QString(session); + } + } + + bool Method::authenticate() { +#ifdef USE_PAM + if (m_pam) + delete m_pam; + + m_pam = new PamService(m_user, m_password, m_passwordless); + + if (!m_pam) + return false; +/* + // set tty + if (!m_pam->setItem(PAM_TTY, ":0")) + return false; + + // set display name + if (!m_pam->setItem(PAM_XDISPLAY, ":0")) + return false; +*/ + // set username + if (!m_pam->setItem(PAM_USER, qPrintable(m_user))) + return false; + + // authenticate the applicant + if (!m_pam->authenticate()) + return false; + + // get mapped user name; PAM may have changed it + const char *mapped = (const char *) m_pam->getItem(PAM_USER); + if (mapped == NULL) + return false; + // TODO: Find out if PAM changing the name is a good or a bad thing + //m_user = QString(mapped); + + if (!m_pam->acctMgmt()) + return false; +#else + if (!m_passwordless) { + // user name + struct passwd *pw; + if ((pw = getpwnam(qPrintable(m_user))) == nullptr) { + // log error + qCritical() << " AUTH: Failed to get user entry."; + + // return fail + return false; + } + + struct spwd *sp; + if ((sp = getspnam(pw->pw_name)) == nullptr) { + // log error + qCritical() << " AUTH: Failed to get shadow entry."; + + // return fail + return false; + } + + // check if password is not empty + if (sp->sp_pwdp && sp->sp_pwdp[0]) { + + // encrypt password + char *encrypted = crypt(qPrintable(m_password), sp->sp_pwdp); + + if (strcmp(encrypted, sp->sp_pwdp)) + return false; + } + } + + char *mapped = strdup(qPrintable(m_user)); + m_user = QString(mapped); + free(mapped); +#endif + return true; + } + + bool Method::setupUser() { + // user name + struct passwd *pw; + if ((pw = getpwnam(qPrintable(m_user))) == nullptr) { + // log error + qCritical() << " AUTH: Failed to get user name."; + + // return fail + return false; + } + + if (pw->pw_shell[0] == '\0') { + setusershell(); + strcpy(pw->pw_shell, getusershell()); + endusershell(); + } + + // set session m_process params + m_process->setUser(pw->pw_name); + m_process->setDir(pw->pw_dir); + m_process->setUid(pw->pw_uid); + m_process->setGid(pw->pw_gid); + // redirect error output to ~/.xession-errors + m_process->setStandardErrorFile(QString("%1/.xsession-errors").arg(pw->pw_dir)); + if (!AuthenticatorApp::instance()->requestCookieTo(QString("%1/.Xauthority").arg(pw->pw_dir), m_user)) + return false; + return true; + } + + QProcessEnvironment Method::setupEnvironment() { // set m_process environment + QProcessEnvironment env = AuthenticatorApp::instance()->requestEnvironment(m_user); +#ifdef USE_PAM + env.insert(m_pam->getEnv()); + m_pam->putEnv(env); +#endif + env.insert("DESKTOP_SESSION", m_sessionName); + env.insert("GDMSESSION", m_sessionName); + return env; + } + + bool Method::startSession() { +#ifdef USE_PAM + // set credentials + if (!m_pam->setCred(PAM_ESTABLISH_CRED)) + return false; + + // open session + if (!m_pam->openSession()) + return false; + + // set credentials + if (!m_pam->setCred(PAM_REINITIALIZE_CRED)) + return false; +#endif + // start session + m_process->start(Configuration::instance()->sessionCommand(), { m_sessionCommand }); + return true; + } + + + bool Method::doStart(const QString &user, const QString &session, const QString &password, bool passwordless) { + m_user = user; + m_password = password; + m_passwordless = passwordless; + // check flag + if (m_started) + return false; + + buildSessionName(session); + + if (m_sessionCommand.isEmpty()) { + // log error + qCritical() << " AUTH: Failed to find command for session:" << session; + + // return fail + return false; + } + + if (!authenticate()) + return false; + + qDebug() << " AUTH: Authenticated for user" << m_user; + + // create user session m_process + m_process = new Session(QString("Session%1").arg(AuthenticatorApp::instance()->requestSessionId()), this); + connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(finished())); + + if (!setupUser()) + return false; + + m_process->setProcessEnvironment(setupEnvironment()); + + if (!startSession()) + return false; + + qDebug() << " AUTH: User session" << m_sessionName << "set up."; + + // wait for started + if (!m_process->waitForStarted()) { + // log error + qDebug() << " AUTH: Failed to start user session."; + + // return fail + return false; + } + + // log message + qDebug() << " AUTH: User session started."; + + // register to the display manager FIXME + // daemonApp->displayManager()->AddSession(m_process->name(), seat->name(), pw->pw_name); + + // set flag + m_started = true; + + // return success + return true; + } + + void Method::stop() { + // check flag + if (!m_started) + return; + + // log message + qDebug() << " AUTH: User session stopping..."; + + // terminate m_process + m_process->terminate(); + + // wait for finished + if (!m_process->waitForFinished(5000)) + m_process->kill(); + } + + void Method::finished() { + // check flag + if (!m_started) + return; + + // reset flag + m_started = false; + + // log message + qDebug() << " AUTH: User session ended."; + + // delete session process + m_process->deleteLater(); + m_process = nullptr; + +#ifdef USE_PAM + delete m_pam; + m_pam = nullptr; +#endif + + emit sessionTerminated(); + } + +}; + +#include "Method.moc" diff --git a/src/auth/Method.h b/src/auth/Method.h new file mode 100644 index 0000000..e13133a --- /dev/null +++ b/src/auth/Method.h @@ -0,0 +1,73 @@ +/* + * + * Copyright (C) 2013 Martin Bříza + * + * 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. + * + */ + +#ifndef METHOD_H +#define METHOD_H + +#include +#include + +namespace SDDM { +#ifdef USE_PAM + class PamService; +#endif + class Session; + class Method : public QObject + { + Q_OBJECT + public: + explicit Method(QObject *parent = nullptr); + QString name() const; + + protected: + virtual bool authenticate(); + virtual bool setupUser(); + virtual QProcessEnvironment setupEnvironment(); + virtual bool startSession(); + + signals: + void loginSucceeded(QString); + void loginFailed(); + void sessionTerminated(); + + public slots: + void start(const QString &user, const QString &session, const QString &password, bool passwordless); + void stop(); + void finished(); + + private: + bool doStart(const QString &user, const QString &session, const QString &password, bool passwordless); + void buildSessionName(const QString& session); + + bool m_passwordless { false }; + QString m_user { }; + QString m_password { }; + QString m_sessionName { }; + QString m_sessionCommand { }; + + Session *m_process { nullptr }; + bool m_started { false }; +#ifdef USE_PAM + PamService *m_pam { nullptr }; +#endif + }; +} + +#endif // METHOD_H diff --git a/src/auth/PAM.cpp b/src/auth/PAM.cpp new file mode 100644 index 0000000..d9845d3 --- /dev/null +++ b/src/auth/PAM.cpp @@ -0,0 +1,260 @@ +/* + * PAM Authenticator backend + * + * Based on the work of: + * SDDM Authenticator implementation: Abdurrahman AVCI + * SLiM PAM implementation: Martin Parm + * + * Copyright (C) 2013 Martin Bříza + * + * 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 "PAM.h" + +#include + +namespace SDDM { + + bool PamService::putEnv(const QProcessEnvironment& env) { + foreach (const QString& s, env.toStringList()) { + result = pam_putenv(handle, s.toAscii()); + if (result != PAM_SUCCESS) { + qWarning() << " AUTH: PAM: putEnv:" << pam_strerror(handle, result); + return false; + } + } + return true; + } + + QProcessEnvironment PamService::getEnv() { + QProcessEnvironment env; + // get pam environment + char **envlist = pam_getenvlist(handle); + if (envlist == NULL) { + qWarning() << " AUTH: PAM: getEnv: Returned NULL"; + return env; + } + + // copy it to the env map + for (int i = 0; envlist[i] != nullptr; ++i) { + QString s(envlist[i]); + + // find equal sign + int index = s.indexOf('='); + + // add to the hash + if (index != -1) + env.insert(s.left(index), s.mid(index + 1)); + + free(envlist[i]); + } + free(envlist); + return env; + } + + bool PamService::chAuthTok(int flags) { + result = pam_chauthtok(handle, flags | m_silent); + if (result != PAM_SUCCESS) { + qWarning() << " AUTH: PAM: chAuthTok:" << pam_strerror(handle, result); + } + return result == PAM_SUCCESS; + } + + bool PamService::acctMgmt(int flags) { + result = pam_acct_mgmt(handle, flags | m_silent); + if (result == PAM_NEW_AUTHTOK_REQD) { + // TODO see if this should really return the value or just true regardless of the outcome + return chAuthTok(PAM_CHANGE_EXPIRED_AUTHTOK); + } + else if (result != PAM_SUCCESS) { + qWarning() << " AUTH: PAM: acctMgmt:" << pam_strerror(handle, result); + return false; + } + return true; + } + + bool PamService::authenticate(int flags) { + result = pam_authenticate(handle, flags | m_silent); + if (result != PAM_SUCCESS) { + qWarning() << " AUTH: PAM: authenticate:" << pam_strerror(handle, result); + } + return result == PAM_SUCCESS; + } + + bool PamService::setCred(int flags) { + result = pam_setcred(handle, flags | m_silent); + if (result != PAM_SUCCESS) { + qWarning() << " AUTH: PAM: setCred:" << pam_strerror(handle, result); + } + return result == PAM_SUCCESS; + } + + bool PamService::openSession() { + result = pam_open_session(handle, m_silent); + if (result != PAM_SUCCESS) { + qWarning() << " AUTH: PAM: openSession:" << pam_strerror(handle, result); + } + return result == PAM_SUCCESS; + } + + bool PamService::closeSession() { + result = pam_close_session(handle, m_silent); + if (result != PAM_SUCCESS) { + qWarning() << " AUTH: PAM: closeSession:" << pam_strerror(handle, result); + } + return result == PAM_SUCCESS; + } + + bool PamService::setItem(int item_type, const void* item) { + result = pam_set_item(handle, item_type, item); + if (result != PAM_SUCCESS) { + qWarning() << " AUTH: PAM: setItem:" << pam_strerror(handle, result); + } + return result == PAM_SUCCESS; + } + + const void* PamService::getItem(int item_type) { + const void *item; + result = pam_get_item(handle, item_type, &item); + if (result != PAM_SUCCESS) { + qWarning() << " AUTH: PAM: getItem:" << pam_strerror(handle, result); + } + return item; + } + + int PamService::converse(int n, const struct pam_message **msg, struct pam_response **resp, void *data) { + PamService *c = static_cast(data); + return c->doConverse(n, msg, resp); + } + + int PamService::doConverse(int n, const struct pam_message **msg, struct pam_response **resp) { + struct pam_response *aresp; + + // check size of the message buffer + if ((n <= 0) || (n > PAM_MAX_NUM_MSG)) + return PAM_CONV_ERR; + + // create response buffer + if ((aresp = (struct pam_response *) calloc(n, sizeof(struct pam_response))) == nullptr) + return PAM_BUF_ERR; + + bool failed = false; + + // if we don't require password, bail on any request from PAM + if (passwordless) { + for (int i = 0; i < n; ++i) { + switch(msg[i]->msg_style) { + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + qDebug() << " AUTH: PAM: Message" << msg[i]->msg; + break; + case PAM_PROMPT_ECHO_OFF: + case PAM_PROMPT_ECHO_ON: + default: + failed = true; + break; + } + } + } + // else, respond to the messages + else { + for (int i = 0; i < n; ++i) { + aresp[i].resp_retcode = 0; + aresp[i].resp = nullptr; + switch (msg[i]->msg_style) { + case PAM_PROMPT_ECHO_OFF: + // set password - WARNING this is just assumption it's a password, beware! + aresp[i].resp = strdup(qPrintable(password)); + if (aresp[i].resp == nullptr) + failed = true; + // clear password + password = ""; + break; + case PAM_PROMPT_ECHO_ON: + // set user - WARNING again, just an assumption, in more complicated environments this won't suffice! + aresp[i].resp = strdup(qPrintable(user)); + if (aresp[i].resp == nullptr) + failed = true; + // clear user + user = ""; + break; + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + qDebug() << " AUTH: PAM: Message:" << msg[i]->msg; + break; + default: + failed = true; + } + } + } + + if (failed) { + for (int i = 0; i < n; ++i) { + if (aresp[i].resp != nullptr) { + memset(aresp[i].resp, 0, strlen(aresp[i].resp)); + free(aresp[i].resp); + } + } + memset(aresp, 0, n * sizeof(struct pam_response)); + free(aresp); + *resp = nullptr; + return PAM_CONV_ERR; + } + + *resp = aresp; + return PAM_SUCCESS; + } + + bool PamService::start(const char *service_name, const char *user, const struct pam_conv *pam_conversation) { + result = pam_start(service_name, user, pam_conversation, &handle); + if (result != PAM_SUCCESS) { + qWarning() << " AUTH: PAM: start" << pam_strerror(handle, result); + return false; + } + else { + qDebug() << " AUTH: PAM: Starting..."; + } + return true; + } + + bool PamService::end(int flags) { + result = pam_end(handle, result | flags); + if (result != PAM_SUCCESS) { + qWarning() << " AUTH: PAM: end:" << pam_strerror(handle, result); + return false; + } + else { + qDebug() << " AUTH: PAM: Ended."; + } + return true; + } + + PamService::PamService(const QString &user, const QString &password, bool passwordless, QObject *parent) + : user(user), password(password), passwordless(passwordless) { + // create context + m_converse = { &PamService::converse, this }; + // start service + if (passwordless) + start("sddm-passwordless", nullptr, &m_converse); + else + start("sddm", nullptr, &m_converse); + } + + PamService::~PamService() { + // stop service + end(); + } +}; + diff --git a/src/auth/PAM.h b/src/auth/PAM.h new file mode 100644 index 0000000..f885ab5 --- /dev/null +++ b/src/auth/PAM.h @@ -0,0 +1,216 @@ +/* + * PAM Authenticator backend + * Copyright (C) 2013 Martin Bříza + * + * 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. + * + */ + +#ifndef PAM_H +#define PAM_H +#ifdef USE_PAM + +#include +#include + +#include + +namespace SDDM { + /** + * Class wrapping the standard Linux-PAM library calls + * + * Almost everything is left the same except the following things: + * + * Mainly, state returns - if you call pam_start, pam_open_session and then + * pam_start again, the session will get closed and the conversation closed + * + * You don't need to pass PAM_SILENT to every call if you want PAM to be quiet. + * You can set the flag globally by using the \ref setSilence method. + * + * \ref acctMgmt doesn't require you to handle the PAM_NEW_AUTHTOK_REQD condition, + * it calls chAuthTok on its own. + * + * Error messages are automatically reported to qDebug + */ + class PamService { + + public: + /** + * ctor + * + * \param service PAM service name, e.g. "sddm" + * \param user username + * \param password user's password + * \param passwordless true if no password is required + * \param parent parent QObject + */ + explicit PamService(const QString &user, const QString &password, bool passwordless, QObject *parent = 0); + + virtual ~PamService(); + + /** + * Conversation function for the pam_conv structure + * + * Calls ((PamService*)pam_conv.appdata_ptr)->doConverse() with its parameters + */ + static int converse(int n, const struct pam_message **msg, struct pam_response **resp, void *data); + + /** + * The main conversation method + * + * This method relates to this particular session / login attempt only, it's called on the object + * that it belongs to. + * + * So far its architecture is quite silly and relies both on previously entered username and password. + * In the future, I'm intending to use it to request information from the greeter by calling its methods to + * supply the required information. (And this won't be just passwords, we'd like to have the possibility to + * log in using just a USB key, fingerprint reader, whatever you want.) + * + * I hope this task will be easier to implement by moving the whole thing into a class. + */ + int doConverse(int n, const struct pam_message **msg, struct pam_response **resp); + + /** + * pam_set_item - set and update PAM informations + * + * \param item_type PAM item type + * \param item item pointer + * + * \return true on success + */ + bool setItem(int item_type, const void *item); + + /** + * pam_get_item - getting PAM informations + * + * \param item_type + * + * \return item pointer or NULL on failure + */ + const void *getItem(int item_type); + + /** + * pam_open_session - start PAM session management + * + * \return true on success + */ + bool openSession(); + + /** + * pam_close_session - terminate PAM session management + * + * \return true on success + */ + bool closeSession(); + + /** + * pam_setcred - establish / delete user credentials + * + * \param flags PAM flag(s) + * + * \return true on success + */ + bool setCred(int flags = 0); + + /** + * pam_authenticate - account authentication + * + * \param flags PAM flag(s) + * + * \return true on success + */ + bool authenticate(int flags = 0); + + /** + * pam_acct_mgmt - PAM account validation management + * + * @note Automatically calls setCred if the password is expired + * + * \param flags PAM flag(s) + * + * \return true on success + */ + bool acctMgmt(int flags = 0); + + /** + * pam_chauthtok - updating authentication tokens + * + * \param flags PAM flag(s) + * + * \return true on success + */ + bool chAuthTok(int flags = 0); + + /** + * pam_getenv - get PAM environment + * + * \return Complete process environment + */ + QProcessEnvironment getEnv(); + + /** + * pam_putenv - set or change PAM environment + * + * \param env environment to be merged into the PAM one + * + * \return true on success + */ + bool putEnv(const QProcessEnvironment& env); + + /** + * pam_end - termination of PAM transaction + * + * \param flags to be OR'd with the status (PAM_DATA_SILENT) + * \return true on success + */ + bool end(int flags = 0); + + /** + * pam_start - initialization of PAM transaction + * + * \param service PAM service name, e.g. "sddm" + * \param user username + * \param pam_conversation pointer to the PAM conversation structure to be used + * + * \return true on success + */ + bool start(const char *service_name, const char *user, const struct pam_conv *pam_conversation); + + /** + * Set PAM_SILENT upon the contained calls + * \param silent true if silent + */ + inline void setSilence(bool silent){ + if (silent) + m_silent |= PAM_SILENT; + else + m_silent &= (~PAM_SILENT); + } + + private: + int m_silent { 0 }; ///< flag mask for silence of the contained calls + + struct pam_conv m_converse; ///< the current conversation + pam_handle_t *handle { nullptr }; ///< PAM handle + int result { PAM_SUCCESS }; ///< PAM result + + QString user { "" }; ///< user + QString password { "" }; ///< password + bool passwordless { false }; ///< true if doesn't require password + }; +}; + +#endif // USE_PAM +#endif // PAM_H diff --git a/src/auth/Session.cpp b/src/auth/Session.cpp new file mode 100644 index 0000000..138948a --- /dev/null +++ b/src/auth/Session.cpp @@ -0,0 +1,107 @@ +/*************************************************************************** +* Copyright (c) 2013 Abdurrahman AVCI +* +* 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 "Session.h" + +#include "Configuration.h" + +#include + +#include +#include +#include + +namespace SDDM { + Session::Session(const QString &name, QObject *parent) : QProcess(parent), m_name(name) { + } + + const QString &Session::name() const { + return m_name; + } + + void Session::setUser(const QString &user) { + m_user = user; + } + + void Session::setDir(const QString &dir) { + m_dir = dir; + } + + void Session::setUid(int uid) { + m_uid = uid; + } + + void Session::setGid(int gid) { + m_gid = gid; + } + + void Session::setupChildProcess() { + if (initgroups(qPrintable(m_user), m_gid)) { + qCritical() << " AUTH: Failed to initialize user groups."; + + // emit signal + emit finished(EXIT_FAILURE, QProcess::NormalExit); + + // exit + exit(EXIT_FAILURE); + } + + if (setsid() < 0) { + qCritical() << " AUTH: Can't create a new session."; + emit finished(EXIT_FAILURE, QProcess::NormalExit); + exit(EXIT_FAILURE); + } + + if (setgid(m_gid)) { + qCritical() << " AUTH: Failed to set group id."; + + // emit signal + emit finished(EXIT_FAILURE, QProcess::NormalExit); + + // exit + exit(EXIT_FAILURE); + } + + if (setuid(m_uid)) { + qCritical() << " AUTH: Failed to set user id."; + + // emit signal + emit finished(EXIT_FAILURE, QProcess::NormalExit); + + // exit + exit(EXIT_FAILURE); + + } +/* FIXME + // add cookie + Display *display = qobject_cast(authenticator->parent()); + display->addCookie(QString("%1/.Xauthority").arg(m_dir)); +*/ + // change to user home dir + if (chdir(qPrintable(m_dir))) { + qCritical() << " AUTH: Failed to change dir to user home."; + + // emit signal + emit finished(EXIT_FAILURE, QProcess::NormalExit); + + // exit + exit(EXIT_FAILURE); + } + } +} diff --git a/src/auth/Session.h b/src/auth/Session.h new file mode 100644 index 0000000..1f075d3 --- /dev/null +++ b/src/auth/Session.h @@ -0,0 +1,51 @@ +/*************************************************************************** +* Copyright (c) 2013 Abdurrahman AVCI +* +* 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. +***************************************************************************/ + +#ifndef SDDM_SESSION_H +#define SDDM_SESSION_H + +#include + +namespace SDDM { + class Session : public QProcess { + Q_OBJECT + Q_DISABLE_COPY(Session) + public: + explicit Session(const QString &name, QObject *parent = 0); + + const QString &name() const; + + void setUser(const QString &user); + void setDir(const QString &dir); + void setUid(int uid); + void setGid(int gid); + + protected: + void setupChildProcess(); + + private: + QString m_name { "" }; + QString m_user { "" }; + QString m_dir { "" }; + int m_uid { 0 }; + int m_gid { 0 }; + }; +} + +#endif // SDDM_SESSION_H diff --git a/src/common/Messages.h b/src/common/Messages.h index c779791..00f27de 100644 --- a/src/common/Messages.h +++ b/src/common/Messages.h @@ -40,6 +40,20 @@ namespace SDDM { LoginFailed }; + enum class AuthMessages { + AuthNone = 0, + Start, + End, + RequestEnv, + Env, + LoginFailed, + LoginSucceeded, + RequestSessionID, + SessionID, + RequestCookieLink, + CookieLink + }; + enum Capability { None = 0x0000, PowerOff = 0x0001, diff --git a/src/daemon/Authenticator.cpp b/src/daemon/Authenticator.cpp index bd7a88c..4db3f42 100644 --- a/src/daemon/Authenticator.cpp +++ b/src/daemon/Authenticator.cpp @@ -18,408 +18,123 @@ ***************************************************************************/ #include "Authenticator.h" - -#include "Configuration.h" #include "DaemonApp.h" #include "Display.h" #include "DisplayManager.h" +#include "SeatManager.h" #include "Seat.h" -#include "Session.h" - -#include -#include -#include -#include +#include "Constants.h" -#ifdef USE_PAM -#include -#else -#include -#include -#endif +#include +#include -#include #include #include namespace SDDM { -#ifdef USE_PAM - class PamService { - public: - PamService(const char *service, const QString &user, const QString &password, bool passwordless); - ~PamService(); - - struct pam_conv m_converse; - pam_handle_t *handle { nullptr }; - int result { PAM_SUCCESS }; - - QString user { "" }; - QString password { "" }; - bool passwordless { false }; - }; - - int converse(int n, const struct pam_message **msg, struct pam_response **resp, void *data) { - struct pam_response *aresp; - - // check size of the message buffer - if ((n <= 0) || (n > PAM_MAX_NUM_MSG)) - return PAM_CONV_ERR; - - // create response buffer - if ((aresp = (struct pam_response *) calloc(n, sizeof(struct pam_response))) == nullptr) - return PAM_BUF_ERR; - - // respond to the messages - bool failed = false; - for (int i = 0; i < n; ++i) { - aresp[i].resp_retcode = 0; - aresp[i].resp = nullptr; - switch (msg[i]->msg_style) { - case PAM_PROMPT_ECHO_OFF: { - PamService *c = static_cast(data); - // set password - aresp[i].resp = strdup(qPrintable(c->password)); - if (aresp[i].resp == nullptr) - failed = true; - // clear password - c->password = ""; - } - break; - case PAM_PROMPT_ECHO_ON: { - PamService *c = static_cast(data); - // set user - aresp[i].resp = strdup(qPrintable(c->user)); - if (aresp[i].resp == nullptr) - failed = true; - // clear user - c->user = ""; - } - break; - case PAM_ERROR_MSG: - case PAM_TEXT_INFO: - break; - default: - failed = true; - } - } - - if (failed) { - for (int i = 0; i < n; ++i) { - if (aresp[i].resp != nullptr) { - memset(aresp[i].resp, 0, strlen(aresp[i].resp)); - free(aresp[i].resp); - } - } - memset(aresp, 0, n * sizeof(struct pam_response)); - free(aresp); - *resp = nullptr; - return PAM_CONV_ERR; + Authenticator::Authenticator(Display *parent) + : QProcess(parent) + , m_display(parent) { + connect(this, SIGNAL(finished(int)), this, SIGNAL(stopped())); + setReadChannel(QProcess::StandardOutput); + connect(this, SIGNAL(readyRead()), this, SLOT(readFromChild())); + connect(this, SIGNAL(readyReadStandardError()), SLOT(forwardErrorOutput())); + + QProcess::start(QString("%1/sddm-auth").arg(BIN_INSTALL_DIR)); + if (!waitForStarted()) { + qCritical() << " DAEMON: Failed to start authenticator process:" << errorString(); + return; } - *resp = aresp; - return PAM_SUCCESS; - } - - PamService::PamService(const char *service, const QString &user, const QString &password, bool passwordless) : user(user), password(password), passwordless(passwordless) { - // create context - m_converse = { &converse, this }; - - // start service - pam_start(service, nullptr, &m_converse, &handle); - } - - PamService::~PamService() { - // stop service - pam_end(handle, result); - } -#endif - - Authenticator::Authenticator(QObject *parent) : QObject(parent) { - } - - Authenticator::~Authenticator() { - stop(); + m_started = true; + qDebug() << " DAEMON: Started the authenticator process."; } - bool Authenticator::start(const QString &user, const QString &session) { - return doStart(user, QString(), session, true); + void Authenticator::forwardErrorOutput() { + QFile err; + err.open(fileno(stderr), QIODevice::WriteOnly | QIODevice::Unbuffered); + err.write(this->readAllStandardError()); } - bool Authenticator::start(const QString &user, const QString &password, const QString &session) { - return doStart(user, password, session, false); + void Authenticator::readFromChild() { + QDataStream stream(this); + while (this->bytesAvailable()) { + quint32 command; + stream >> command; + qDebug() << " DAEMON: Received message" << command << "from the authenticator"; + handleMessage(AuthMessages(command)); + if (stream.status() != QDataStream::Ok) + break; + } } - bool Authenticator::doStart(const QString &user, const QString &password, const QString &session, bool passwordless) { - // check flag - if (m_started) - return false; - - // convert session to command - QString sessionName = ""; - QString command = ""; - - if (session.endsWith(".desktop")) { - // session directory - QDir dir(daemonApp->configuration()->sessionsDir()); - - // session file - QFile file(dir.absoluteFilePath(session)); - - // open file - if (file.open(QIODevice::ReadOnly)) { - - // read line-by-line - QTextStream in(&file); - while (!in.atEnd()) { - QString line = in.readLine(); - - // line starting with Exec - if (line.startsWith("Exec=")) - command = line.mid(5); + void Authenticator::handleMessage(AuthMessages command) { + QDataStream stream(this); + switch (command) { + case AuthMessages::RequestEnv: { + QString user; + stream >> user; + QStringList env = m_display->sessionEnv(user).toStringList(); + stream << quint32(AuthMessages::Env) << env.count(); + foreach (const QString &s, env) { + stream << s; } - - // close file - file.close(); + break; } - - // remove extension - sessionName = session.left(session.lastIndexOf(".")); - } else { - command = session; - sessionName = session; - } - - if (command.isEmpty()) { - // log error - qCritical() << " DAEMON: Failed to find command for session:" << session; - - // return fail - return false; - } - - // get display and display - Display *display = qobject_cast(parent()); - Seat *seat = qobject_cast(display->parent()); - -#ifdef USE_PAM - if (m_pam) - delete m_pam; - - m_pam = new PamService("sddm", user, password, passwordless); - - if (!m_pam) - return false; - - if (!passwordless) { - // authenticate the applicant - if ((m_pam->result = pam_authenticate(m_pam->handle, 0)) != PAM_SUCCESS) - return false; - - if ((m_pam->result = pam_acct_mgmt(m_pam->handle, 0)) == PAM_NEW_AUTHTOK_REQD) - m_pam->result = pam_chauthtok(m_pam->handle, PAM_CHANGE_EXPIRED_AUTHTOK); - - if (m_pam->result != PAM_SUCCESS) - return false; - } - - // set username - if ((m_pam->result = pam_set_item(m_pam->handle, PAM_USER, qPrintable(user))) != PAM_SUCCESS) - return false; - - // set credentials - if ((m_pam->result = pam_setcred(m_pam->handle, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) - return false; - - // set tty - if ((m_pam->result = pam_set_item(m_pam->handle, PAM_TTY, qPrintable(display->name()))) != PAM_SUCCESS) - return false; - - // set display name - if ((m_pam->result = pam_set_item(m_pam->handle, PAM_XDISPLAY, qPrintable(display->name()))) != PAM_SUCCESS) - return false; - - // open session - if ((m_pam->result = pam_open_session(m_pam->handle, 0)) != PAM_SUCCESS) - return false; - - // get mapped user name; PAM may have changed it - char *mapped; - if ((m_pam->result = pam_get_item(m_pam->handle, PAM_USER, (const void **)&mapped)) != PAM_SUCCESS) - return false; -#else - if (!passwordless) { - // user name - struct passwd *pw; - if ((pw = getpwnam(qPrintable(user))) == nullptr) { - // log error - qCritical() << " DAEMON: Failed to get user entry."; - - // return fail - return false; + case AuthMessages::LoginFailed: + emit loginFailed(m_parentSocket); + break; + case AuthMessages::LoginSucceeded: { + QString user; + stream >> m_name >> user; + daemonApp->displayManager()->AddSession(m_name, qobject_cast(m_display->parent())->name(), user); + emit loginSucceeded(m_parentSocket); + break; } - - struct spwd *sp; - if ((sp = getspnam(pw->pw_name)) == nullptr) { - // log error - qCritical() << " DAEMON: Failed to get shadow entry."; - - // return fail - return false; + case AuthMessages::RequestSessionID: + stream << quint32(AuthMessages::SessionID) << int(DaemonApp::instance()->newSessionId()); + break; + case AuthMessages::RequestCookieLink: { + QString path, user; + struct passwd *pw; + stream >> path >> user; + qDebug() << "The path is" << path << "and the user" << user; + m_display->addCookie(path); + pw = getpwnam(qPrintable(user)); + if(pw) + chown(qPrintable(path), pw->pw_uid, pw->pw_gid); + stream << quint32(AuthMessages::CookieLink); + break; } - - // check if password is not empty - if (sp->sp_pwdp && sp->sp_pwdp[0]) { - - // encrypt password - char *encrypted = crypt(qPrintable(password), sp->sp_pwdp); - - if (strcmp(encrypted, sp->sp_pwdp)) - return false; - } - } - - char *mapped = strdup(qPrintable(user)); -#endif - - // user name - struct passwd *pw; - if ((pw = getpwnam(mapped)) == nullptr) { - // log error - qCritical() << " DAEMON: Failed to get user name."; - - // return fail - return false; - } - - if (pw->pw_shell[0] == '\0') { - setusershell(); - strcpy(pw->pw_shell, getusershell()); - endusershell(); - } - - // create user session process - process = new Session(QString("Session%1").arg(daemonApp->newSessionId()), this); - - // set session process params - process->setUser(pw->pw_name); - process->setDir(pw->pw_dir); - process->setUid(pw->pw_uid); - process->setGid(pw->pw_gid); - - // set process environment - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); -#ifdef USE_PAM - // get pam environment - char **envlist = pam_getenvlist(m_pam->handle); - - // copy it to the env map - for (int i = 0; envlist[i] != nullptr; ++i) { - QString s(envlist[i]); - - // find equal sign - int index = s.indexOf('='); - - // add to the hash - if (index != -1) - env.insert(s.left(index), s.mid(index + 1)); - } -#else - // we strdup'd the string before in this branch - free(mapped); -#endif - env.insert("HOME", pw->pw_dir); - env.insert("PWD", pw->pw_dir); - env.insert("SHELL", pw->pw_shell); - env.insert("USER", pw->pw_name); - env.insert("LOGNAME", pw->pw_name); - env.insert("PATH", daemonApp->configuration()->defaultPath()); - env.insert("DISPLAY", display->name()); - env.insert("XAUTHORITY", QString("%1/.Xauthority").arg(pw->pw_dir)); - env.insert("XDG_SEAT", seat->name()); - env.insert("XDG_SEAT_PATH", daemonApp->displayManager()->seatPath(seat->name())); - env.insert("XDG_SESSION_PATH", daemonApp->displayManager()->sessionPath(process->name())); - env.insert("XDG_VTNR", QString::number(display->terminalId())); - env.insert("DESKTOP_SESSION", sessionName); - env.insert("GDMSESSION", sessionName); - process->setProcessEnvironment(env); - - // redirect error output to ~/.xession-errors - process->setStandardErrorFile(QString("%1/.xsession-errors").arg(pw->pw_dir)); - - // start session - process->start(daemonApp->configuration()->sessionCommand(), { command }); - - // connect signal - connect(process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(finished())); - - // wait for started - if (!process->waitForStarted()) { - // log error - qDebug() << " DAEMON: Failed to start user session."; - - // return fail - return false; + default: + qWarning() << " DAEMON: Child sent message type" << quint32(command) << "which cannot be handled."; + break; } - - // log message - qDebug() << " DAEMON: User session started."; - - // register to the display manager - daemonApp->displayManager()->AddSession(process->name(), seat->name(), pw->pw_name); - - // set flag - m_started = true; - - // return success - return true; } - void Authenticator::stop() { - // check flag + void Authenticator::start(QLocalSocket *socket, const QString& user, const QString& session, const QString& password, bool passwordless) { if (!m_started) return; - // log message - qDebug() << " DAEMON: User session stopping..."; + m_parentSocket = socket; - // terminate process - process->terminate(); + QDataStream stream(this); + stream << quint32(AuthMessages::Start) << user << session << password << passwordless; - // wait for finished - if (!process->waitForFinished(5000)) - process->kill(); + qDebug() << " DAEMON: Starting authentication for user" << user; } - void Authenticator::finished() { - // check flag + void Authenticator::stop() { if (!m_started) return; - // reset flag - m_started = false; - - // log message - qDebug() << " DAEMON: User session ended."; - // unregister from the display manager - daemonApp->displayManager()->RemoveSession(process->name()); + daemonApp->displayManager()->RemoveSession(m_name); - // delete session process - process->deleteLater(); - process = nullptr; + QDataStream stream(this); + stream << quint32(AuthMessages::End); -#ifdef USE_PAM - if (m_pam) { - m_pam->result = pam_close_session(m_pam->handle, 0); - m_pam->result = pam_setcred(m_pam->handle, PAM_DELETE_CRED); - // for some reason this has to be called here too - pam_end(m_pam->handle, m_pam->result); - delete m_pam; - m_pam = nullptr; - } -#endif - - // emit signal - emit stopped(); + m_started = false; + qDebug() << " DAEMON: Stopped the authenticator process"; } } diff --git a/src/daemon/Authenticator.h b/src/daemon/Authenticator.h index 23e91ec..4989af4 100644 --- a/src/daemon/Authenticator.h +++ b/src/daemon/Authenticator.h @@ -20,42 +20,40 @@ #ifndef SDDM_AUTHENTICATOR_H #define SDDM_AUTHENTICATOR_H +#include "Messages.h" + #include +#include +class QLocalSocket; namespace SDDM { -#ifdef USE_PAM - class PamService; -#endif - class Session; - class AuthenticatorPrivate; - class Authenticator : public QObject { + class Display; + class Authenticator : public QProcess { Q_OBJECT Q_DISABLE_COPY(Authenticator) public: - Authenticator(QObject *parent = 0); - ~Authenticator(); + explicit Authenticator(Display *parent = nullptr); - public slots: - bool start(const QString &user, const QString &session); - bool start(const QString &user, const QString &password, const QString &session); + private slots: + void readFromChild(); + void handleMessage(AuthMessages command); + void forwardErrorOutput(); + public slots: + void start(QLocalSocket *socket, const QString& user, const QString& session, const QString& password, bool passwordless); void stop(); - void finished(); signals: + void loginFailed(QLocalSocket*); + void loginSucceeded(QLocalSocket*); void stopped(); private: - bool doStart(const QString &user, const QString &password, const QString &session, bool passwordless); - + QString m_name; + Display *m_display { nullptr }; + QLocalSocket *m_parentSocket { nullptr }; // to be got rid of soon bool m_started { false }; - -#ifdef USE_PAM - PamService *m_pam { nullptr }; -#endif - - Session *process { nullptr }; }; } diff --git a/src/daemon/Display.cpp b/src/daemon/Display.cpp index 80aa95a..d473fdb 100644 --- a/src/daemon/Display.cpp +++ b/src/daemon/Display.cpp @@ -23,9 +23,11 @@ #include "Configuration.h" #include "DaemonApp.h" #include "DisplayServer.h" +#include "DisplayManager.h" #include "Seat.h" #include "SocketServer.h" #include "Greeter.h" +#include #include #include @@ -156,6 +124,32 @@ namespace SDDM { return cookie; } + QProcessEnvironment Display::sessionEnv(const QString& user) const { + struct passwd *pw; + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + Seat *seat = qobject_cast(parent()); + + if ((pw = getpwnam(qPrintable(user))) == nullptr) { + // log error + qCritical() << " DAEMON: Failed to get user name."; + + // return fail + return QProcessEnvironment(); + } + env.insert("HOME", pw->pw_dir); + env.insert("PWD", pw->pw_dir); + env.insert("SHELL", pw->pw_shell); + env.insert("USER", pw->pw_name); + env.insert("LOGNAME", pw->pw_name); + env.insert("PATH", daemonApp->configuration()->defaultPath()); + env.insert("DISPLAY", name()); + env.insert("XAUTHORITY", QString("%1/.Xauthority").arg(pw->pw_dir)); + env.insert("XDG_SEAT", seat->name()); + env.insert("XDG_SEAT_PATH", daemonApp->displayManager()->seatPath(seat->name())); + env.insert("XDG_VTNR", QString::number(terminalId())); + return env; + } + void Display::addCookie(const QString &file) { // log message qDebug() << " DAEMON: Adding cookie to" << file; @@ -183,6 +167,29 @@ namespace SDDM { if (m_started) return; + // generate cookie + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, 15); + + // resever 32 bytes + m_cookie.reserve(32); + + // create a random hexadecimal number + const char *digits = "0123456789abcdef"; + for (int i = 0; i < 32; ++i) + m_cookie[i] = digits[dis(gen)]; + + // generate auth file + addCookie(m_authPath); + + // set display server params + m_displayServer->setDisplay(m_display); + m_displayServer->setAuthPath(m_authPath); + + // start display server + m_displayServer->start(); + if (m_displayServer != nullptr) { // set display server params m_displayServer->setDisplay(m_display); @@ -201,7 +199,7 @@ namespace SDDM { m_started = true; // start session - m_authenticator->start(daemonApp->configuration()->autoUser(), daemonApp->configuration()->lastSession()); + m_authenticator->start(nullptr, daemonApp->configuration()->autoUser(), daemonApp->configuration()->lastSession(), QString(), true); // return return; @@ -264,20 +260,13 @@ namespace SDDM { void Display::login(QLocalSocket *socket, const QString &user, const QString &password, const QString &session) { // start session - if (!m_authenticator->start(user, password, session)) { - // emit signal - emit loginFailed(socket); - - // return - return; - } - - // save last user and last session - daemonApp->configuration()->setLastUser(user); - daemonApp->configuration()->setLastSession(session); - daemonApp->configuration()->save(); - - // emit signal - emit loginSucceeded(socket); + connect(m_authenticator, SIGNAL(loginFailed(QLocalSocket*)), this, SIGNAL(loginFailed(QLocalSocket*))); + connect(m_authenticator, SIGNAL(loginSucceeded(QLocalSocket*)), this, SIGNAL(loginSucceeded(QLocalSocket*))); + m_authenticator->start(socket, user, session, password, false); + + // save last user and last session FIXME +// daemonApp->configuration()->setLastUser(user); +// daemonApp->configuration()->setLastSession(session); +// daemonApp->configuration()->save(); } } diff --git a/src/daemon/Display.h b/src/daemon/Display.h index 9c475a9..5c22f8b 100644 --- a/src/daemon/Display.h +++ b/src/daemon/Display.h @@ -21,6 +21,7 @@ #define SDDM_DISPLAY_H #include +#include class QLocalSocket; @@ -40,6 +41,7 @@ namespace SDDM { const int displayId() const; const int terminalId() const; + QProcessEnvironment sessionEnv(const QString& user) const; const QString &name() const; diff --git a/src/daemon/Session.cpp b/src/daemon/Session.cpp deleted file mode 100644 index de86c64..0000000 --- a/src/daemon/Session.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/*************************************************************************** -* Copyright (c) 2013 Abdurrahman AVCI -* -* 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 "Session.h" - -#include "Authenticator.h" -#include "Configuration.h" -#include "DaemonApp.h" -#include "Display.h" - -#include - -#include -#include -#include - -namespace SDDM { - Session::Session(const QString &name, QObject *parent) : QProcess(parent), m_name(name) { - } - - const QString &Session::name() const { - return m_name; - } - - void Session::setUser(const QString &user) { - m_user = user; - } - - void Session::setDir(const QString &dir) { - m_dir = dir; - } - - void Session::setUid(int uid) { - m_uid = uid; - } - - void Session::setGid(int gid) { - m_gid = gid; - } - - void Session::setupChildProcess() { - if (daemonApp->configuration()->testing) - return; - - Authenticator *authenticator = qobject_cast(parent()); - - if (initgroups(qPrintable(m_user), m_gid)) { - qCritical() << " DAEMON: Failed to initialize user groups."; - - // emit signal - emit finished(EXIT_FAILURE, QProcess::NormalExit); - - // exit - exit(EXIT_FAILURE); - } - - if (setgid(m_gid)) { - qCritical() << " DAEMON: Failed to set group id."; - - // emit signal - emit finished(EXIT_FAILURE, QProcess::NormalExit); - - // exit - exit(EXIT_FAILURE); - } - - if (setuid(m_uid)) { - qCritical() << " DAEMON: Failed to set user id."; - - // emit signal - emit finished(EXIT_FAILURE, QProcess::NormalExit); - - // exit - exit(EXIT_FAILURE); - - } - - // add cookie - Display *display = qobject_cast(authenticator->parent()); - display->addCookie(QString("%1/.Xauthority").arg(m_dir)); - - // change to user home dir - if (chdir(qPrintable(m_dir))) { - qCritical() << " DAEMON: Failed to change dir to user home."; - - // emit signal - emit finished(EXIT_FAILURE, QProcess::NormalExit); - - // exit - exit(EXIT_FAILURE); - } - } -} diff --git a/src/daemon/Session.h b/src/daemon/Session.h deleted file mode 100644 index 1f075d3..0000000 --- a/src/daemon/Session.h +++ /dev/null @@ -1,51 +0,0 @@ -/*************************************************************************** -* Copyright (c) 2013 Abdurrahman AVCI -* -* 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. -***************************************************************************/ - -#ifndef SDDM_SESSION_H -#define SDDM_SESSION_H - -#include - -namespace SDDM { - class Session : public QProcess { - Q_OBJECT - Q_DISABLE_COPY(Session) - public: - explicit Session(const QString &name, QObject *parent = 0); - - const QString &name() const; - - void setUser(const QString &user); - void setDir(const QString &dir); - void setUid(int uid); - void setGid(int gid); - - protected: - void setupChildProcess(); - - private: - QString m_name { "" }; - QString m_user { "" }; - QString m_dir { "" }; - int m_uid { 0 }; - int m_gid { 0 }; - }; -} - -#endif // SDDM_SESSION_H