Группа :: Графические оболочки/KDE
Пакет: sddm
Главная Изменения Спек Патчи Sources Загрузить Gear Bugs and FR Repocop
Патч: alt-expired-password-handling.patch
Скачать
Скачать
diff --git a/components/2.0/PasswordBox.qml b/components/2.0/PasswordBox.qml
index 8973eb2..76ddd6c 100644
--- a/components/2.0/PasswordBox.qml
+++ b/components/2.0/PasswordBox.qml
@@ -37,6 +37,8 @@ FocusScope {
property alias textColor: txtMain.textColor
property alias echoMode: txtMain.echoMode
property alias text: txtMain.text
+ property alias validator: txtMain.validator
+ property alias maximumLength: txtMain.maximumLength
property alias image: img.source
property double imageFadeIn: 300
@@ -47,6 +49,8 @@ FocusScope {
property alias tooltipFG: tooltipText.color
property alias tooltipBG: tooltip.color
+ signal accepted
+
TextConstants {
id: textConstants
}
@@ -57,8 +61,12 @@ FocusScope {
font.pixelSize: 14
echoMode: TextInput.Password
+ // default validator
+ validator: RegExpValidator { regExp: /[][!"б╖#$%&'()*+,./:;<=>?@\^_б╢`{|}~a-zA-Z0-9-]*/ }
focus: true
+
+ onAccepted: container.accepted()
}
Image {
diff --git a/components/2.0/PasswordConnections.qml b/components/2.0/PasswordConnections.qml
new file mode 100644
index 0000000..d38d8b3
--- /dev/null
+++ b/components/2.0/PasswordConnections.qml
@@ -0,0 +1,120 @@
+/***************************************************************************
+* Copyright (c) 2017 Thomas Hoehn <thomas_hoehn@gmx.net>
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without restriction,
+* including without limitation the rights to use, copy, modify, merge,
+* publish, distribute, sublicense, and/or sell copies of the Software,
+* and to permit persons to whom the Software is furnished to do so,
+* subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+* OR OTHER DEALINGS IN THE SOFTWARE.
+*
+* Note: container for password renewal logic
+*
+***************************************************************************/
+
+import QtQuick 2.0
+import SddmComponents 2.0
+
+Item {
+
+ property var pwdItem
+ property var renewalDialog
+
+ // if provided supposed to have text property
+ property var errMsg
+ property var txtMsg
+
+ // gets focus when password renewal dialog closes
+ property var getsBackFocus
+
+ TextConstants { id: textConstants }
+
+ function clearPwd() {
+ if(typeof pwdItem.password !== "undefined")
+ pwdItem.password = ""
+ else if(typeof pwdItem.text !== "undefined")
+ pwdItem.text = ""
+ }
+
+ // handles password renewal events
+ Connections {
+
+ target: renewalDialog
+
+ onOk: {
+ sddm.pamResponse(renewalDialog.password)
+ }
+
+ onCancel: {
+ sddm.cancelPamConv()
+ }
+
+ onError: {
+ // unlikely: invalid prompt,
+ // i.e. new/repeat prompt for dialog not found in request
+ if(typeof errMsg !== "undefined")
+ errMsg.text = msg
+ }
+
+ onVisibleChanged: {
+ renewalDialogTimer.stop()
+
+ if(!renewalDialog.visible) {
+ renewalDialog.clear() // clear pam infos
+ clearPwd()
+
+ // last selected user or input field gets focus back
+ if(typeof getsBackFocus !== "undefined")
+ getsBackFocus.forceActiveFocus()
+ }
+ }
+ }
+
+ Connections {
+
+ target: sddm
+
+ onLoginSucceeded: {
+ // ...will not be reached as greeter stops after successfull login...
+ }
+
+ onLoginFailed: {
+ if(typeof txtMsg !== "undefined")
+ {
+ txtMsg.text = textConstants.loginFailed
+ // filter out login failure details
+ if(err_msg != "Authentication failure" &&
+ err_msg != "Password change aborted." &&
+ err_msg != "Authentication token manipulation error")
+ txtMsg.text += "\n" + err_msg
+ }
+ renewalDialog.visible = false
+ clearPwd()
+ }
+
+ // show messages from pam conversation (for expired passwords)
+ onPamConvMsg: {
+ // from signal pamConvMsg(pam_msg)
+ renewalDialog.append(pam_msg)
+ }
+
+ // new pam request arrived, e.g. for expired password,
+ onPamRequest: {
+ // open password renewalDialog dialog and block other GUI
+ renewalDialog.show(request.findNewPwdMessage(),
+ request.findRepeatPwdMessage())
+ }
+ }
+}
diff --git a/components/2.0/PasswordRenewal.qml b/components/2.0/PasswordRenewal.qml
new file mode 100644
index 0000000..72c4890
--- /dev/null
+++ b/components/2.0/PasswordRenewal.qml
@@ -0,0 +1,276 @@
+/***************************************************************************
+* Copyright (c) 2017 Thomas Hoehn <Hoehn.Thomas@heidenhain.de>
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without restriction,
+* including without limitation the rights to use, copy, modify, merge,
+* publish, distribute, sublicense, and/or sell copies of the Software,
+* and to permit persons to whom the Software is furnished to do so,
+* subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+* OR OTHER DEALINGS IN THE SOFTWARE.
+*
+* Note: simple password input dialog for user with expired password (pam conversation)
+*
+***************************************************************************/
+
+import QtQuick 2.0
+import QtQuick.Layouts 1.2
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+
+FocusScope {
+ id: container
+ implicitHeight: dialogHeight
+ implicitWidth: infosWidth+2*margins
+
+ readonly property string password: pwd1Input.text
+
+ // prompt messages from pam
+ property alias prompt1: prompt1Txt.text
+ property alias prompt2: prompt2Txt.text
+
+ // dialog window
+ property int dialogHeight: 180
+ property int margins: 8
+
+ // infos text area with pam messages
+ property int infosWidth: 520
+ property int infosHeight: 10
+
+ property color promptColor: "black"
+ property color infosColor: "red"
+ property alias color: dialog.color // dialog background
+ property alias radius: dialog.radius
+ property alias border: dialog.border
+
+ // error texts
+ property string noMatchWarning: qsTr("Passwords do not match!")
+ property string samePwdWarning: qsTr("Password same as before. Please change!")
+ property string pwdEmptyWarning: qsTr("Empty password. Please enter password!")
+
+ signal ok // password input completed
+ signal cancel // dialog canceled
+ signal error(string msg) // invalid prompts
+
+ // set prompts and show dialog
+ function show(newPwdTxt, repeatPwdTxt) {
+ if(newPwdTxt != "" && repeatPwdTxt != "")
+ {
+ prompt1Txt.text = newPwdTxt
+ prompt2Txt.text = repeatPwdTxt
+ container.visible = true
+ } else {
+ // todo: prompts unknown (new/repeat password)
+ error("unknown pam message prompts!\n" +
+ "prompt1=" + newPwdTxt +
+ "prompt2=" + repeatPwdTxt)
+ }
+ }
+
+ // clear all text
+ function clear() {
+ infoarea.text = ""
+ pwd1Input.text = ""
+ pwd2Input.text = ""
+ }
+
+ // append new pam error/info
+ function append(msg) {
+ infoarea.append(msg)
+ }
+
+ // internal
+
+ function resetEmptyWarning() {
+ emptyWarningShown = false
+ }
+
+ // flag: warning about empty password already shown?
+ property bool emptyWarningShown: false
+ // previously typed password (refused by pam)
+ property string previousPwd: ""
+
+ function validatePwds() {
+ if(pwd1Input.text.length == 0 ||
+ pwd2Input.text.length == 0)
+ {
+ if(emptyWarningShown==false)
+ {
+ infoarea.append(pwdEmptyWarning)
+ emptyWarningShown = true
+ }
+ return
+ }
+
+ if(pwd1Input.text.length>0)
+ {
+ resetEmptyWarning()
+
+ if(pwd1Input.text != pwd2Input.text) {
+ infoarea.append(noMatchWarning)
+ return
+ }
+
+ if(previousPwd == pwd1Input.text) {
+ // if pam refuses new password (e.g. too similar)
+ // and this is presented to pam several times pam
+ // seems pissed off and stops conversation, so avoid
+ infoarea.append(samePwdWarning)
+ return
+ }
+
+ // new password ok
+ previousPwd = pwd1Input.text
+ ok()
+ }
+ }
+
+ onVisibleChanged: {
+ if(visible == true)
+ pwd1Input.text = ""
+ pwd2Input.text = ""
+ pwd1Input.focus = true
+ focus = true
+ }
+
+ Rectangle {
+ id: dialog
+ anchors.fill: parent
+
+ GridLayout {
+ id: contentGrid
+ anchors.fill: parent
+ columns: 2
+
+ Item {
+ width: infosWidth
+ height: infosHeight+36
+ Layout.columnSpan: 2
+ Layout.minimumWidth: infosWidth
+ Layout.margins: margins
+
+ // show info/error from pam conv()
+ TextArea {
+ id: infoarea
+ anchors.fill: parent
+ viewport.implicitHeight: infosHeight
+ backgroundVisible: false
+ activeFocusOnPress: false
+ readOnly: true
+ textColor: infosColor
+ frameVisible: false
+ verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff
+ }
+ }
+
+ // shows message (prompt) from pam conv()
+ Text {
+ id: prompt1Txt
+ Layout.minimumWidth: 64
+ Layout.alignment: Qt.AlignRight
+ color: promptColor
+ text: qsTr("New password:")
+ }
+
+ PasswordBox {
+ id: pwd1Input
+ implicitWidth: 150
+ maximumLength: 64
+ KeyNavigation.down: pwd2Input
+ KeyNavigation.tab: pwd2Input
+ Keys.onEnterPressed:pwd2Input.forceActiveFocus()
+ Keys.onReturnPressed: pwd2Input.forceActiveFocus()
+ onAccepted: {
+ if(pwd1Input.text != "" && pwd2Input.text != "" )
+ validatePwds()
+ }
+ }
+
+ // shows message (prompt) from pam conv()
+ Text {
+ id: prompt2Txt
+ Layout.minimumWidth: 64
+ Layout.alignment: Qt.AlignRight
+ color: promptColor
+ text: qsTr("Repeat password:")
+ }
+
+ PasswordBox {
+ id: pwd2Input
+ implicitWidth: 150
+ maximumLength: 64
+ KeyNavigation.down: cancelBtn
+ KeyNavigation.tab: cancelBtn
+ Keys.onEnterPressed: if(pwd1Input == "") pwd1Input.forceActiveFocus()
+ Keys.onReturnPressed: if(pwd1Input == "") pwd1Input.forceActiveFocus()
+ onAccepted: validatePwds()
+ }
+
+ RowLayout {
+
+ Layout.columnSpan: 2
+ Layout.bottomMargin: 32
+ Layout.topMargin: margins
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignCenter
+ spacing: 8
+
+ Button {
+ id: cancelBtn
+ text: qsTr("Cancel")
+ activeFocusOnTab : true
+ Keys.onEnterPressed: clicked()
+ Keys.onReturnPressed: clicked()
+ KeyNavigation.right: resetBtn
+ onClicked: {
+ container.visible = false
+ resetEmptyWarning()
+ cancel()
+ }
+ }
+
+ Button {
+ id: resetBtn
+ text: qsTr("Reset")
+ activeFocusOnTab : true
+ enabled: pwd1Input.text != "" || pwd2Input.text != ""
+ Keys.onEnterPressed: clicked()
+ Keys.onReturnPressed: clicked()
+ KeyNavigation.right: okBtn
+ KeyNavigation.left: cancelBtn
+ KeyNavigation.up: pwd2Input
+ onClicked: {
+ pwd1Input.text = ""
+ pwd2Input.text = ""
+ resetEmptyWarning()
+ pwd1Input.forceActiveFocus()
+ }
+ }
+
+ Button {
+ id: okBtn
+ text: qsTr("Ok")
+ activeFocusOnTab : true
+ enabled: pwd1Input.text != "" && pwd2Input.text != ""
+ Keys.onEnterPressed: clicked()
+ Keys.onReturnPressed: clicked()
+ KeyNavigation.left: resetBtn
+ KeyNavigation.tab: pwd1Input
+ KeyNavigation.up: pwd2Input
+ onClicked: validatePwds()
+ }
+ }
+ }
+ }
+}
diff --git a/components/2.0/TextBox.qml b/components/2.0/TextBox.qml
index a8d89af..c452738 100644
--- a/components/2.0/TextBox.qml
+++ b/components/2.0/TextBox.qml
@@ -37,6 +37,10 @@ FocusScope {
property alias textColor: txtMain.color
property alias echoMode: txtMain.echoMode
property alias text: txtMain.text
+ property alias validator: txtMain.validator
+ property alias maximumLength: txtMain.maximumLength
+
+ signal accepted
Rectangle {
id: main
@@ -85,5 +89,7 @@ FocusScope {
focus: true
passwordCharacter: "\u25cf"
+
+ onAccepted: container.accepted()
}
}
--- a/components/common/qmldir
+++ b/components/common/qmldir
@@ -8,6 +8,8 @@ ImageButton 2.0 ImageButton.qml
LayoutBox 2.0 LayoutBox.qml
Menu 2.0 Menu.qml
PasswordBox 2.0 PasswordBox.qml
+PasswordRenewal 2.0 PasswordRenewal.qml
+PasswordConnections 2.0 PasswordConnections.qml
PictureBox 2.0 PictureBox.qml
TextBox 2.0 TextBox.qml
TextConstants 2.0 TextConstants.qml
diff --git a/data/themes/elarun/Main.qml b/data/themes/elarun/Main.qml
index 37a97a0..a943e87 100644
--- a/data/themes/elarun/Main.qml
+++ b/data/themes/elarun/Main.qml
@@ -35,13 +35,13 @@ Rectangle {
TextConstants { id: textConstants }
- Connections {
- target: sddm
- onLoginSucceeded: {
- }
- onLoginFailed: {
- pw_entry.text = ""
- }
+ // container for password renewal logic
+ PasswordConnections {
+ renewalDialog: renewal
+ pwdItem: pw_entry // use PasswordBox.text
+ getsBackFocus: pw_entry
+ //errMsg: none
+ //txtMsg: none
}
Background {
@@ -60,9 +60,22 @@ Rectangle {
color: "transparent"
//visible: primaryScreen
+ PasswordRenewal {
+ id: renewal
+ anchors.horizontalCenter: rectangle.horizontalCenter
+ anchors.top: rectangle.bottom
+ anchors.topMargin: 32
+ visible: false
+ radius: 8
+ color: "#22888888"
+ infosColor: "lightcoral"
+ }
+
Rectangle {
+ id: rectangle
width: 416; height: 262
color: "#00000000"
+ enabled: !renewal.visible
anchors.centerIn: parent
diff --git a/data/themes/maldives/Main.qml b/data/themes/maldives/Main.qml
index 00feea4..286ad80 100644
--- a/data/themes/maldives/Main.qml
+++ b/data/themes/maldives/Main.qml
@@ -35,19 +35,13 @@ Rectangle {
TextConstants { id: textConstants }
- Connections {
- target: sddm
-
- onLoginSucceeded: {
- errorMessage.color = "steelblue"
- errorMessage.text = textConstants.loginSucceeded
- }
-
- onLoginFailed: {
- password.text = ""
- errorMessage.color = "red"
- errorMessage.text = textConstants.loginFailed
- }
+ // container for password renewal logic
+ PasswordConnections {
+ renewalDialog: renewal
+ pwdItem: password // use PasswordBox.text
+ getsBackFocus: password
+ errMsg: errorMessage
+ txtMsg: errorMessage
}
Background {
@@ -76,10 +70,31 @@ Rectangle {
}
Image {
+ id: renewalImg
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.margins: 64
+ width: renewal.width+32
+ height: renewal.height+32
+ visible: renewal.visible
+
+ source: "rectangle.png"
+ }
+
+ PasswordRenewal {
+ id: renewal
+ anchors.centerIn: renewalImg
+ visible: false
+ radius: 8
+ color: "transparent"
+ }
+
+ Image {
id: rectangle
anchors.centerIn: parent
width: Math.max(320, mainColumn.implicitWidth + 50)
height: Math.max(320, mainColumn.implicitHeight + 50)
+ enabled: !renewal.visible
source: "rectangle.png"
diff --git a/docs/EXPIRED-PASSWORD.md b/docs/EXPIRED-PASSWORD.md
new file mode 100755
index 0000000..e988488
--- /dev/null
+++ b/docs/EXPIRED-PASSWORD.md
@@ -0,0 +1,83 @@
+## Introduction
+
+SDDM can handle expired passwords during login.
+A password renewal dialog is provided to change the expired password.
+The greeter frontend will talk with the backend to handle the pam conversation.
+Support for password renewal is provided with two components used in greeter themes:
+
+* ``PasswordRenewal.qml`` basic dialog for password renewal, for password input and confirmation
+* ``PasswordConnections.qml`` container for Connections, hides signal handling from themes
+
+## Usage
+
+For new themes the two components have to be included in the ``Main.qml``.
+See the built-in greeter theme ``Main.qml`` for an example,
+and add this to ``Main.qml``:
+
+```
+// container with password renewal logic
+PasswordConnections {
+ renewalDialog: renewal // PasswordRenewal dialog id (see below)
+ pwdItem: listView.currentItem // gets password input from currentItem.password
+ getsBackFocus: listView // item where focus falls back after dialog closes
+ errMsg: errMessage // if set defines (text) item which shows errors
+ txtMsg: txtMessage // if set (text) item which shows pam infos
+}
+
+...
+
+ Item {
+ id: usersContainer
+ // block other input during password renewal
+ enabled: !renewal.visible
+
+...
+
+ // password renewal dialog
+ PasswordRenewal {
+ id: renewal
+ // customize here e.g.:
+ //anchors.horizontalCenter: parent.horizontalCenter
+ //anchors.bottom: usersContainer.top
+ //visible: false
+ //color: "#22888888"
+ //promptColor: "white"
+ //infosColor: "lightcoral"
+ }
+```
+
+## Some more details
+
+The following qml objects are available in ``Main.qml``,
+they are used for password renewal and pam conversation:
+
+### Signals
+
+Signals coming from daemon backend:
+
+* ``pamConvMsg(pam_msg)``
+Provides infos/errors from (pam) backend conversation to present to user.
+
+* ``pamRequest()``
+New request from (pam) backend, user response is required.
+
+### Property
+
+* ``request`` (type AuthRequest)
+Prompts from (pam) backend with messages from pam_conv.
+
+### Response
+
+Responses from greeter frontend to the backend:
+
+* ``sddm.enablePwdRenewal()``
+The theme tells the greeter it can handle password renewal (has a dialog and logic as described above).
+For themes which do not have a password renewal dialog, this method is not called.
+In that case the pam conversation is just canceled (otherwise pam_conv user session sits there waiting for password response).
+This will keep compatibility to (older) themes which do not support expired passwords yet.
+
+* ``sddm.pamResponse(password)``
+Send password response to (pam) backend, i.e. pam_conv.
+
+* ``sddm.cancelPamConv()``
+Cancel pam conversation with pam_conv.
diff --git a/src/auth/Auth.cpp b/src/auth/Auth.cpp
index d5dbca5..9bf3bb9 100644
--- a/src/auth/Auth.cpp
+++ b/src/auth/Auth.cpp
@@ -58,6 +58,7 @@ namespace SDDM {
void dataPending();
void childExited(int exitCode, QProcess::ExitStatus exitStatus);
void childError(QProcess::ProcessError error);
+ void cancelPamConv();
void requestFinished();
public:
AuthRequest *request { nullptr };
@@ -133,6 +134,7 @@ namespace SDDM {
child->setProcessEnvironment(env);
connect(child, QOverload<int,QProcess::ExitStatus>::of(&QProcess::finished), this, &Auth::Private::childExited);
connect(child, QOverload<QProcess::ProcessError>::of(&QProcess::error), this, &Auth::Private::childError);
+ connect(request, &AuthRequest::canceled, this, &Auth::Private::cancelPamConv);
connect(request, &AuthRequest::finished, this, &Auth::Private::requestFinished);
connect(request, &AuthRequest::promptsChanged, parent, &Auth::requestChanged);
}
@@ -148,61 +150,67 @@ namespace SDDM {
connect(socket, &QLocalSocket::readyRead, this, &Auth::Private::dataPending);
}
+ // from (PAM) Backend to daemon
void Auth::Private::dataPending() {
Auth *auth = qobject_cast<Auth*>(parent());
Msg m = MSG_UNKNOWN;
SafeDataStream str(socket);
- str.receive();
- str >> m;
- switch (m) {
- case ERROR: {
- QString message;
- Error type = ERROR_NONE;
- str >> message >> type;
- Q_EMIT auth->error(message, type);
- break;
- }
- case INFO: {
- QString message;
- Info type = INFO_NONE;
- str >> message >> type;
- Q_EMIT auth->info(message, type);
- break;
- }
- case REQUEST: {
- Request r;
- str >> r;
- request->setRequest(&r);
- break;
- }
- case AUTHENTICATED: {
- QString user;
- str >> user;
- if (!user.isEmpty()) {
- auth->setUser(user);
- Q_EMIT auth->authentication(user, true);
+ do {
+ str.receive();
+ str >> m;
+ switch (m) {
+ case ERROR: {
+ QString message;
+ Error type = ERROR_NONE;
+ str >> message >> type;
+ Q_EMIT auth->error(message, type);
+ break;
+ }
+ case INFO: {
+ QString message;
+ Info type = INFO_NONE;
+ str >> message >> type;
+ Q_EMIT auth->info(message, type);
+ break;
+ }
+ // request from (PAM) Backend
+ case REQUEST: {
+ Request r;
+ str >> r;
+ request->setRequest(&r);
+ qDebug() << "Auth: Request received";
+ break;
+ }
+ case AUTHENTICATED: {
+ QString user;
+ str >> user;
+ if (!user.isEmpty()) {
+ auth->setUser(user);
+ Q_EMIT auth->authentication(user, true);
+ str.reset();
+ str << AUTHENTICATED << environment << cookie;
+ str.send();
+ }
+ else {
+ Q_EMIT auth->authentication(user, false);
+ }
+ break;
+ }
+ case SESSION_STATUS: {
+ bool status;
+ str >> status;
+ Q_EMIT auth->sessionStarted(status);
str.reset();
- str << AUTHENTICATED << environment << cookie;
+ str << SESSION_STATUS;
str.send();
+ break;
}
- else {
- Q_EMIT auth->authentication(user, false);
+ default: {
+ qWarning("Auth: dataPending: Unknown message received: %d", m);
+ Q_EMIT auth->error(QStringLiteral("Auth: Unexpected value received: %1").arg(m), ERROR_INTERNAL);
}
- break;
- }
- case SESSION_STATUS: {
- bool status;
- str >> status;
- Q_EMIT auth->sessionStarted(status);
- str.reset();
- str << SESSION_STATUS;
- str.send();
- break;
}
- default: {
- Q_EMIT auth->error(QStringLiteral("Auth: Unexpected value received: %1").arg(m), ERROR_INTERNAL);
- }
- }
+ } while(str.available());
}
void Auth::Private::childExited(int exitCode, QProcess::ExitStatus exitStatus) {
@@ -224,6 +232,16 @@ namespace SDDM {
Q_EMIT qobject_cast<Auth*>(parent())->error(child->errorString(), ERROR_INTERNAL);
}
+ // from daemon to (PAM) backend
+ void Auth::Private::cancelPamConv() {
+ SafeDataStream str(socket);
+ qDebug() << "Auth: cancelPamConv, send CANCEL to backend";
+ str << CANCEL;
+ str.send();
+ request->setRequest();
+ }
+
+ // from daemon to (PAM) backend
void Auth::Private::requestFinished() {
SafeDataStream str(socket);
Request r = request->request();
@@ -232,7 +250,6 @@ namespace SDDM {
request->setRequest();
}
-
Auth::Auth(const QString &user, const QString &session, bool autologin, QObject *parent, bool verbose)
: QObject(parent)
, d(new Private(this)) {
diff --git a/src/auth/Auth.h b/src/auth/Auth.h
index 9c26b95..cb7a736 100644
--- a/src/auth/Auth.h
+++ b/src/auth/Auth.h
@@ -21,8 +21,8 @@
#ifndef SDDM_AUTH_H
#define SDDM_AUTH_H
-#include "AuthRequest.h"
#include "AuthPrompt.h"
+#include "AuthRequest.h"
#include <QtCore/QObject>
#include <QtCore/QProcessEnvironment>
@@ -66,6 +66,7 @@ namespace SDDM {
enum Info {
INFO_NONE = 0,
INFO_UNKNOWN,
+ INFO_PAM_CONV,
INFO_PASS_CHANGE_REQUIRED,
_INFO_LAST
};
diff --git a/src/auth/AuthBase.h b/src/auth/AuthBase.h
new file mode 100644
index 0000000..ca09864
--- /dev/null
+++ b/src/auth/AuthBase.h
@@ -0,0 +1,137 @@
+/*
+ * PAM request and message IDs shared between greeter, daemon, helper
+ * Copyright (C) 2013 Martin Bе≥ц╜za <mbriza@redhat.com>
+ *
+ * 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 AUTHBASE_H
+#define AUTHBASE_H
+
+#include <QtCore/QDataStream>
+
+// AuthPrompt::Type
+#include "AuthPrompt.h"
+
+namespace SDDM {
+ class Prompt {
+ public:
+ Prompt() { }
+ Prompt(AuthPrompt::Type type, QString message, bool hidden)
+ : type(type), message(message), hidden(hidden) { }
+ Prompt(const Prompt &o)
+ : type(o.type), response(o.response), message(o.message), hidden(o.hidden) { }
+ ~Prompt() {
+ clear();
+ }
+ Prompt& operator=(const Prompt &o) {
+ type = o.type;
+ response = o.response;
+ message = o.message;
+ hidden = o.hidden;
+ return *this;
+ }
+ bool operator==(const Prompt &o) const {
+ return type == o.type && response == o.response && message == o.message && hidden == o.hidden;
+ }
+ bool valid() const {
+ return !(type == AuthPrompt::NONE && response.isEmpty() && message.isEmpty());
+ }
+ void clear() {
+ type = AuthPrompt::NONE;
+ // overwrite the whole thing with zeroes before clearing
+ memset(response.data(), 0, response.length());
+ response.clear();
+ message.clear();
+ hidden = false;
+ }
+
+ AuthPrompt::Type type { AuthPrompt::NONE };
+ QByteArray response { };
+ QString message { };
+ bool hidden { false };
+ };
+
+ class Request {
+ public:
+ Request() { }
+ Request(QList<Prompt> prompts)
+ : prompts(prompts) { }
+ Request(const Request &o)
+ : prompts(o.prompts) { }
+ Request& operator=(const Request &o) {
+ prompts = QList<Prompt>(o.prompts);
+ return *this;
+ }
+ bool operator==(const Request &o) const {
+ return prompts == o.prompts;
+ }
+ bool valid() const {
+ return !(prompts.isEmpty());
+ }
+ void clear() {
+ prompts.clear();
+ }
+
+ QList<Prompt> prompts { };
+ };
+
+ inline QDataStream& operator<<(QDataStream &s, const Prompt &m) {
+ s << qint32(m.type) << m.message << m.hidden << m.response;
+ return s;
+ }
+
+ inline QDataStream& operator>>(QDataStream &s, Prompt &m) {
+ qint32 type;
+ QString message;
+ bool hidden;
+ QByteArray response;
+ s >> type >> message >> hidden >> response;
+ m.type = AuthPrompt::Type(type);
+ m.message = message;
+ m.hidden = hidden;
+ m.response = response;
+ return s;
+ }
+
+ inline QDataStream& operator<<(QDataStream &s, const Request &m) {
+ qint32 length = m.prompts.length();
+ s << length;
+ for(const Prompt &p : qAsConst(m.prompts)) {
+ s << p;
+ }
+ return s;
+ }
+
+ inline QDataStream& operator>>(QDataStream &s, Request &m) {
+ QList<Prompt> prompts;
+ qint32 length;
+ s >> length;
+ for (int i = 0; i < length; i++) {
+ Prompt p;
+ s >> p;
+ prompts << p;
+ }
+ if (prompts.length() != length) {
+ s.setStatus(QDataStream::ReadCorruptData);
+ return s;
+ }
+ m.prompts = prompts;
+ return s;
+ }
+}
+
+#endif // AUTHBASE_H
diff --git a/src/auth/AuthMessages.h b/src/auth/AuthMessages.h
index ec16d00..00dd714 100644
--- a/src/auth/AuthMessages.h
+++ b/src/auth/AuthMessages.h
@@ -26,68 +26,10 @@
#include "Auth.h"
-namespace SDDM {
- class Prompt {
- public:
- Prompt() { }
- Prompt(AuthPrompt::Type type, QString message, bool hidden)
- : type(type), message(message), hidden(hidden) { }
- Prompt(const Prompt &o)
- : type(o.type), response(o.response), message(o.message), hidden(o.hidden) { }
- ~Prompt() {
- clear();
- }
- Prompt& operator=(const Prompt &o) {
- type = o.type;
- response = o.response;
- message = o.message;
- hidden = o.hidden;
- return *this;
- }
- bool operator==(const Prompt &o) const {
- return type == o.type && response == o.response && message == o.message && hidden == o.hidden;
- }
- bool valid() const {
- return !(type == AuthPrompt::NONE && response.isEmpty() && message.isEmpty());
- }
- void clear() {
- type = AuthPrompt::NONE;
- // overwrite the whole thing with zeroes before clearing
- memset(response.data(), 0, response.length());
- response.clear();
- message.clear();
- hidden = false;
- }
-
- AuthPrompt::Type type { AuthPrompt::NONE };
- QByteArray response { };
- QString message { };
- bool hidden { false };
- };
+// Request and Prompt classes
+#include "AuthBase.h"
- class Request {
- public:
- Request() { }
- Request(QList<Prompt> prompts)
- : prompts(prompts) { }
- Request(const Request &o)
- : prompts(o.prompts) { }
- Request& operator=(const Request &o) {
- prompts = QList<Prompt>(o.prompts);
- return *this;
- }
- bool operator==(const Request &o) const {
- return prompts == o.prompts;
- }
- bool valid() const {
- return !(prompts.isEmpty());
- }
- void clear() {
- prompts.clear();
- }
-
- QList<Prompt> prompts { };
- };
+namespace SDDM {
enum Msg {
MSG_UNKNOWN = 0,
@@ -95,6 +37,7 @@ namespace SDDM {
ERROR,
INFO,
REQUEST,
+ CANCEL,
AUTHENTICATED,
SESSION_STATUS,
MSG_LAST,
@@ -166,49 +109,6 @@ namespace SDDM {
return s;
}
- inline QDataStream& operator<<(QDataStream &s, const Prompt &m) {
- s << qint32(m.type) << m.message << m.hidden << m.response;
- return s;
- }
-
- inline QDataStream& operator>>(QDataStream &s, Prompt &m) {
- qint32 type;
- QString message;
- bool hidden;
- QByteArray response;
- s >> type >> message >> hidden >> response;
- m.type = AuthPrompt::Type(type);
- m.message = message;
- m.hidden = hidden;
- m.response = response;
- return s;
- }
-
- inline QDataStream& operator<<(QDataStream &s, const Request &m) {
- qint32 length = m.prompts.length();
- s << length;
- for(const Prompt &p : qAsConst(m.prompts)) {
- s << p;
- }
- return s;
- }
-
- inline QDataStream& operator>>(QDataStream &s, Request &m) {
- QList<Prompt> prompts;
- qint32 length;
- s >> length;
- for (int i = 0; i < length; i++) {
- Prompt p;
- s >> p;
- prompts << p;
- }
- if (prompts.length() != length) {
- s.setStatus(QDataStream::ReadCorruptData);
- return s;
- }
- m.prompts = prompts;
- return s;
- }
}
#endif // MESSAGES_H
diff --git a/src/auth/AuthPrompt.cpp b/src/auth/AuthPrompt.cpp
index 7434ede..96b8034 100644
--- a/src/auth/AuthPrompt.cpp
+++ b/src/auth/AuthPrompt.cpp
@@ -19,8 +19,8 @@
*/
#include "AuthPrompt.h"
-#include "Auth.h"
-#include "AuthMessages.h"
+#include "AuthBase.h"
+#include "AuthRequest.h"
namespace SDDM {
class AuthPrompt::Private : public Prompt {
@@ -69,4 +69,37 @@ namespace SDDM {
bool AuthPrompt::hidden() const {
return d->hidden;
}
+
+ // @internal for debug logging purposes
+ const QString &AuthPrompt::typeToString(int type) {
+
+ static const QString stringList[] = {
+ QStringLiteral("NONE"),
+ QStringLiteral("UNKNOWN"),
+ QStringLiteral("CHANGE_CURRENT"),
+ QStringLiteral("CHANGE_NEW"),
+ QStringLiteral("CHANGE_REPEAT"),
+ QStringLiteral("LOGIN_USER"),
+ QStringLiteral("LOGIN_PASSWORD"),
+ };
+
+ switch(type) {
+ case AuthPrompt::NONE:
+ return stringList[0]; break;
+ case AuthPrompt::CHANGE_CURRENT:
+ return stringList[2]; break;
+ case AuthPrompt::CHANGE_NEW:
+ return stringList[3]; break;
+ case AuthPrompt::CHANGE_REPEAT:
+ return stringList[4]; break;
+ case AuthPrompt::LOGIN_USER:
+ return stringList[5]; break;
+ case AuthPrompt::LOGIN_PASSWORD:
+ return stringList[6]; break;
+ default: break;
+ }
+
+ // AuthPrompt::UNKNOWN
+ return stringList[1];
+ }
}
diff --git a/src/auth/AuthPrompt.h b/src/auth/AuthPrompt.h
index 8ed394d..2f1b0f0 100644
--- a/src/auth/AuthPrompt.h
+++ b/src/auth/AuthPrompt.h
@@ -24,7 +24,6 @@
#include <QtCore/QObject>
namespace SDDM {
- class Auth;
class AuthRequest;
class Prompt;
/**
@@ -85,6 +84,12 @@ namespace SDDM {
* @param r data entered by the user
*/
void setResponse(const QByteArray &r);
+ /**
+ * @brief get string representation of AuthPrompt type, see enum @ref Type
+ * @note for debug logging
+ */
+ static const QString &typeToString(int type);
+
Q_SIGNALS:
/**
* Emitted when the response was entered by the user
@@ -99,4 +104,4 @@ namespace SDDM {
};
}
-#endif //PROMPT_H
\ No newline at end of file
+#endif //PROMPT_H
diff --git a/src/auth/AuthRequest.cpp b/src/auth/AuthRequest.cpp
index 2c4764a..0d82503 100644
--- a/src/auth/AuthRequest.cpp
+++ b/src/auth/AuthRequest.cpp
@@ -19,7 +19,6 @@
*/
#include "AuthRequest.h"
-#include "Auth.h"
#include "AuthMessages.h"
namespace SDDM {
@@ -46,11 +45,11 @@ namespace SDDM {
qobject_cast<AuthRequest*>(parent())->done();
}
- AuthRequest::AuthRequest(Auth *parent)
+ AuthRequest::AuthRequest(QObject *parent)
: QObject(parent)
, d(new Private(this)) { }
- void AuthRequest::setRequest(const Request *request) {
+ void AuthRequest::setRequest(const Request * const request) {
QList<AuthPrompt*> promptsCopy(d->prompts);
d->prompts.clear();
if (request != nullptr) {
@@ -72,7 +71,7 @@ namespace SDDM {
return d->prompts;
}
- QQmlListProperty<AuthPrompt> AuthRequest::promptsDecl() {
+ QQmlListProperty<AuthPrompt> AuthRequest::promptsRead() {
return QQmlListProperty<AuthPrompt>(this, d->prompts);
}
@@ -83,6 +82,14 @@ namespace SDDM {
}
}
+ // send to (pam) backend
+ void AuthRequest::cancel() {
+ if (!d->finished) {
+ d->finished = true;
+ }
+ Q_EMIT canceled();
+ }
+
bool AuthRequest::finishAutomatically() {
return d->finishAutomatically;
}
@@ -106,6 +113,47 @@ namespace SDDM {
}
return r;
}
+
+ QString AuthRequest::findNewPwdMessage() {
+ for(const AuthPrompt *qap : qAsConst(d->prompts)) {
+ if(qap->type()==AuthPrompt::CHANGE_NEW)
+ return qap->message();
+ }
+ return QString();
+ }
+
+ QString AuthRequest::findRepeatPwdMessage() {
+ for(const AuthPrompt *qap : qAsConst(d->prompts)) {
+ if(qap->type()==AuthPrompt::CHANGE_REPEAT)
+ return qap->message();
+ }
+ return QString();
+ }
+
+ AuthPrompt *AuthRequest::findPrompt(AuthPrompt::Type type) const {
+ for(AuthPrompt *qap : d->prompts) {
+ if(qap->type()==type)
+ return qap;
+ }
+ return NULL;
+ }
+
+ void AuthRequest::setChangeResponse(const QString ¤tPwd, const QString &newPassword) {
+ AuthPrompt* prompt;
+
+ if(!currentPwd.isNull()) {
+ prompt = findPrompt(AuthPrompt::CHANGE_CURRENT);
+ if(prompt) prompt->setResponse(qPrintable(currentPwd));
+ }
+
+ if(!newPassword.isNull()) {
+ prompt = findPrompt(AuthPrompt::CHANGE_NEW);
+ if(prompt) prompt->setResponse(qPrintable(newPassword));
+
+ prompt = findPrompt(AuthPrompt::CHANGE_REPEAT);
+ if(prompt) prompt->setResponse(qPrintable(newPassword));
+ }
+ }
}
#include "AuthRequest.moc"
diff --git a/src/auth/AuthRequest.h b/src/auth/AuthRequest.h
index b8ef43c..113fd10 100644
--- a/src/auth/AuthRequest.h
+++ b/src/auth/AuthRequest.h
@@ -23,11 +23,12 @@
#include <QtCore/QObject>
+#include <AuthPrompt.h>
+
#include <QtQml/QQmlListProperty>
namespace SDDM {
- class Auth;
- class AuthPrompt;
+ //class AuthPrompt;
class Request;
/**
* \brief
@@ -48,12 +49,28 @@ namespace SDDM {
*
* \todo Decide if it's sane to use the info messages from PAM or to somehow parse them
* and make the password changing message into a Request::Type of some kind
+ *
+ * \note AuthRequest (and AuthPrompt) is mainly used in (Pam)Backend
+ * and closely bundled with the Auth.cpp parent which handles user session
+ * after authentication etc.
+ *
+ * But AuthRequest is also secondly used as pure (pam_conv) message container in greeter
+ * without the Auth parent overhead (but still with the signal/slot amenities) - depending on
+ * the constructor used. In this case it just hands over the pam_conv() messages and possibly
+ * user (password) responses between daemon, greeter and qml.
*/
class AuthRequest : public QObject {
Q_OBJECT
- Q_PROPERTY(QQmlListProperty<AuthPrompt> prompts READ promptsDecl NOTIFY promptsChanged)
+ Q_PROPERTY(QQmlListProperty<SDDM::AuthPrompt> prompts READ promptsRead NOTIFY promptsChanged)
Q_PROPERTY(bool finishAutomatically READ finishAutomatically WRITE setFinishAutomatically NOTIFY finishAutomaticallyChanged)
+
public:
+ /** @brief for AuthRequest without using Auth parent.
+ *
+ * when used as parameter container to hand over pam request (prompts with pam
+ * messages and user pwd responses) between daemon and greeter (proxy) to qml view.
+ */
+ AuthRequest(QObject *parent = 0);
/**
* @return list of the contained prompts
*/
@@ -62,30 +79,68 @@ namespace SDDM {
* For QML apps
* @return list of the contained prompts
*/
- QQmlListProperty<AuthPrompt> promptsDecl();
+ QQmlListProperty<AuthPrompt> promptsRead();
+ /**
+ * @brief Find pam message of type CHANGE_NEW in prompts list
+ * @return pam message string
+ */
+ Q_INVOKABLE QString findNewPwdMessage();
+ /**
+ * @brief Find pam message of type CHANGE_REPEAT in prompts list
+ * @return pam message string
+ */
+ Q_INVOKABLE QString findRepeatPwdMessage();
+ /**
+ * @brief Write password responses into request for pam conv(),
+ * for AuthPrompt::CHANGE_NEW, CHANGE_REPEAT, CHANGE_CURRENT
+ * @param currentPwd Current user password (expired password)
+ * @param newPassword New user password (replaces expired password)
+ */
+ Q_INVOKABLE void setChangeResponse(const QString ¤tPwd, const QString &newPassword);
static AuthRequest *empty();
bool finishAutomatically();
+ /**
+ * @brief Trigger slot done() automaticly when all AuthPrompt responses (user passwords) are set.
+ * @param value if true done() automaticly called, if false user calls done() after specifying all responses
+ */
void setFinishAutomatically(bool value);
+ /**
+ * @brief Fill AuthRequest with Prompts from Request
+ * @param Request Prompts with possible responses
+ */
+ void setRequest(const Request * const request = nullptr);
+ /**
+ * @brief convert AuthRequest to simple Request type
+ * @return Request with Prompts
+ */
+ Request request() const;
+ /**
+ * @brief find prompt with specified type
+ * @param type \ref AuthPrompt::Type
+ * @return pointer to prompt of that type
+ */
+ AuthPrompt *findPrompt(AuthPrompt::Type type) const;
+
public Q_SLOTS:
/**
- * Call this slot when all prompts has been filled to your satisfaction
+ * Call this slot when all prompts has been filled (with responses) to your satisfaction
*/
void done();
+ /**
+ * Call this slot when user canceled password dialog
+ */
+ void cancel();
Q_SIGNALS:
/**
* Emitted when \ref done was called
*/
void finished();
-
+ void canceled();
void finishAutomaticallyChanged();
void promptsChanged();
- private:
- AuthRequest(Auth *parent);
- void setRequest(const Request *request = nullptr);
- Request request() const;
- friend class Auth;
+ protected:
class Private;
Private *d { nullptr };
};
diff --git a/src/common/Messages.h b/src/common/Messages.h
index c779791..a587bb4 100644
--- a/src/common/Messages.h
+++ b/src/common/Messages.h
@@ -23,9 +23,12 @@
#include <QFlags>
namespace SDDM {
+ /** daemon to greeter messages */
enum class GreeterMessages {
Connect = 0,
Login,
+ PamResponse,
+ PamCancel,
PowerOff,
Reboot,
Suspend,
@@ -33,9 +36,12 @@ namespace SDDM {
HybridSleep
};
+ /** greeter to daemon messages */
enum class DaemonMessages {
HostName,
Capabilities,
+ PamConvMsg,
+ PamRequest,
LoginSucceeded,
LoginFailed
};
diff --git a/src/common/SafeDataStream.cpp b/src/common/SafeDataStream.cpp
index f62a074..332e6a0 100644
--- a/src/common/SafeDataStream.cpp
+++ b/src/common/SafeDataStream.cpp
@@ -74,6 +74,15 @@ namespace SDDM {
}
}
+ bool SafeDataStream::available() {
+ if (!m_device->isOpen()) {
+ qCritical() << " Auth: SafeDataStream: Could not read from the device";
+ return false;
+ }
+
+ return m_device->bytesAvailable();
+ }
+
void SafeDataStream::reset() {
m_data.clear();
device()->reset();
diff --git a/src/common/SafeDataStream.h b/src/common/SafeDataStream.h
index 8bbbe8e..1f77920 100644
--- a/src/common/SafeDataStream.h
+++ b/src/common/SafeDataStream.h
@@ -29,6 +29,7 @@ namespace SDDM {
SafeDataStream(QIODevice* device);
void send();
void receive();
+ bool available();
void reset();
private:
diff --git a/src/common/SocketWriter.cpp b/src/common/SocketWriter.cpp
index 32db235..3f8d0cf 100644
--- a/src/common/SocketWriter.cpp
+++ b/src/common/SocketWriter.cpp
@@ -49,4 +49,10 @@ namespace SDDM {
return *this;
}
+
+ SocketWriter &SocketWriter::operator << (const Request &r) {
+ *output << r;
+
+ return *this;
+ }
}
diff --git a/src/common/SocketWriter.h b/src/common/SocketWriter.h
index 9b4036f..3c7a977 100644
--- a/src/common/SocketWriter.h
+++ b/src/common/SocketWriter.h
@@ -25,6 +25,7 @@
#include <QLocalSocket>
#include "Session.h"
+#include "AuthBase.h" // for Request
namespace SDDM {
class SocketWriter {
@@ -36,6 +37,7 @@ namespace SDDM {
SocketWriter &operator << (const quint32 &u);
SocketWriter &operator << (const QString &s);
SocketWriter &operator << (const Session &s);
+ SocketWriter &operator << (const Request &r);
private:
QByteArray data;
diff --git a/src/daemon/Display.cpp b/src/daemon/Display.cpp
index ab84329..5066dd2 100644
--- a/src/daemon/Display.cpp
+++ b/src/daemon/Display.cpp
@@ -56,6 +56,8 @@ namespace SDDM {
m_socketServer(new SocketServer(this)),
m_greeter(new Greeter(this)) {
+ connect(m_greeter, SIGNAL(stopped()), this, SLOT(slotGreeterStopped()));
+
// respond to authentication requests
m_auth->setVerbose(true);
connect(m_auth, &Auth::requestChanged, this, &Display::slotRequestChanged);
@@ -72,10 +74,17 @@ namespace SDDM {
// connect login signal
connect(m_socketServer, &SocketServer::login, this, &Display::login);
+ // pam responses and conversation cancel
+ connect(m_socketServer, SIGNAL(sendPamResponse(const QString&)), this, SLOT(setPamResponse(const QString&)));
+ connect(m_socketServer, SIGNAL(cancelPamConv()), this, SLOT(cancelPamConv()));
// connect login result signals
- connect(this, SIGNAL(loginFailed(QLocalSocket*)), m_socketServer, SLOT(loginFailed(QLocalSocket*)));
+ connect(this, SIGNAL(loginFailed(QLocalSocket*,const QString&)), m_socketServer, SLOT(loginFailed(QLocalSocket*,const QString&)));
connect(this, SIGNAL(loginSucceeded(QLocalSocket*)), m_socketServer, SLOT(loginSucceeded(QLocalSocket*)));
+ // pam (conversation) info/error shown in greeter
+ connect(this, SIGNAL(pamConvMsg(QLocalSocket*,const QString&)), m_socketServer, SLOT(pamConvMsg(QLocalSocket*,const QString&)));
+ // new request from pam for e.g. password renewal (expired password)
+ connect(this, SIGNAL(pamRequest(QLocalSocket*,const AuthRequest * const)), m_socketServer, SLOT(pamRequest(QLocalSocket*,const AuthRequest * const)));
}
Display::~Display() {
@@ -221,7 +230,9 @@ namespace SDDM {
void Display::login(QLocalSocket *socket,
const QString &user, const QString &password,
const Session &session) {
+
m_socket = socket;
+ m_failed = false;
//the SDDM user has special privileges that skip password checking so that we can load the greeter
//block ever trying to log in as the SDDM user
@@ -233,6 +244,20 @@ namespace SDDM {
startAuth(user, password, session);
}
+ // password renewal, got response from greeter
+ void Display::setPamResponse(const QString &password) {
+ qDebug() << "Display: set pam response with new password";
+ m_auth->request()->setChangeResponse(m_passPhrase, password);
+ if(m_auth->request()->finishAutomatically() == false)
+ m_auth->request()->done();
+ }
+
+ // cancel pam (password renewal) conversation
+ // because user canceled password dialog in greeter
+ void Display::cancelPamConv() {
+ m_auth->request()->cancel();
+ }
+
QString Display::findGreeterTheme() const {
QString themeName = mainConfig.Theme.Current.get();
@@ -351,7 +376,7 @@ namespace SDDM {
void Display::slotAuthenticationFinished(const QString &user, bool success) {
if (success) {
- qDebug() << "Authenticated successfully";
+ qDebug() << "Authenticated successfully" << user;
m_auth->setCookie(qobject_cast<XorgDisplayServer *>(m_displayServer)->cookie());
@@ -372,28 +397,54 @@ namespace SDDM {
if (m_socket)
emit loginSucceeded(m_socket);
- } else if (m_socket) {
+ } else {
qDebug() << "Authentication failure";
- emit loginFailed(m_socket);
+
+ if (!m_socket) {
+ qWarning() << "Greeter socket down!";
+ return;
+ }
+
+ // avoid to emit loginFailed twice
+ if(!m_failed) {
+ emit loginFailed(m_socket, QString());
+ m_failed = true;
+ }
}
- m_socket = nullptr;
}
+ // presentable to the user
void Display::slotAuthInfo(const QString &message, Auth::Info info) {
- // TODO: presentable to the user, eventually
- Q_UNUSED(info);
qWarning() << "Authentication information:" << message;
+
+ if (!m_socket) {
+ qWarning() << "Greeter socket down!";
+ return;
+ }
+
+ if(info == Auth::INFO_PASS_CHANGE_REQUIRED ||
+ info == Auth::INFO_PAM_CONV)
+ {
+ // send pam conversation message to greeter
+ emit pamConvMsg(m_socket, message);
+ }
}
void Display::slotAuthError(const QString &message, Auth::Error error) {
// TODO: handle more errors
qWarning() << "Authentication error:" << message;
- if (!m_socket)
+ if (!m_socket) {
+ qWarning() << "Greeter socket down!";
return;
+ }
- if (error == Auth::ERROR_AUTHENTICATION)
- emit loginFailed(m_socket);
+ // avoid to emit loginFailed twice
+ if (error == Auth::ERROR_AUTHENTICATION && !m_failed)
+ {
+ m_failed = true;
+ emit loginFailed(m_socket, message);
+ }
}
void Display::slotHelperFinished(Auth::HelperExitStatus status) {
@@ -406,18 +457,62 @@ namespace SDDM {
stop();
}
+ /* got new request (with prompts) from pam conv(), e.g. for expired pwd
+ * which requires response from greeter UI (new password) */
void Display::slotRequestChanged() {
- if (m_auth->request()->prompts().length() == 1) {
+
+ int n_prompts = m_auth->request()->prompts().count();
+ AuthPrompt::Type type_prompt0(AuthPrompt::NONE);
+
+ // get type if we have a prompt
+ if(n_prompts>0)
+ type_prompt0 = m_auth->request()->prompts()[0]->type();
+ // ignore empty requests
+ else return;
+
+ // see what we got from pam conv() and will be send to greeter
+ qDebug() << "Display: requestChanged with " << n_prompts << " prompts from Auth";
+
+ // respond to login password request
+ if (n_prompts == 1 && type_prompt0 == AuthPrompt::LOGIN_PASSWORD) {
m_auth->request()->prompts()[0]->setResponse(qPrintable(m_passPhrase));
m_auth->request()->done();
- } else if (m_auth->request()->prompts().length() == 2) {
+ return;
+ }
+
+ // handle request with user name and password prompts
+ if (n_prompts == 2 && type_prompt0 == AuthPrompt::LOGIN_USER )
+ {
m_auth->request()->prompts()[0]->setResponse(qPrintable(m_auth->user()));
m_auth->request()->prompts()[1]->setResponse(qPrintable(m_passPhrase));
m_auth->request()->done();
+ return;
+ }
+
+ // handle password renewal case (gets response from greeter),
+ // will finish with request->done() later in setPamResponse()
+ if (n_prompts >= 2 &&
+ m_auth->request()->findPrompt(AuthPrompt::CHANGE_NEW) &&
+ m_auth->request()->findPrompt(AuthPrompt::CHANGE_REPEAT))
+ {
+ if(m_socket)
+ // send password renewal request to greeter (via SocketServer)
+ emit pamRequest(m_socket, m_auth->request());
+
+ return;
}
+
+ qWarning() << "Display: Unable to handle Auth request!";
}
void Display::slotSessionStarted(bool success) {
qDebug() << "Session started";
}
+
+ // inform daemon if greeter stopped, so we dont forward
+ // errors or infos etc. through invalid socket
+ void Display::slotGreeterStopped() {
+ qDebug() << "Display: Greeter was stopped";
+ m_socket = nullptr;
+ }
}
diff --git a/src/daemon/Display.h b/src/daemon/Display.h
index 09d3cf9..a05d2ab 100644
--- a/src/daemon/Display.h
+++ b/src/daemon/Display.h
@@ -60,14 +60,18 @@ namespace SDDM {
void login(QLocalSocket *socket,
const QString &user, const QString &password,
const Session &session);
+ void setPamResponse(const QString &password);
+ void cancelPamConv();
bool attemptAutologin();
void displayServerStarted();
signals:
void stopped();
- void loginFailed(QLocalSocket *socket);
+ void loginFailed(QLocalSocket *socket, const QString &message);
void loginSucceeded(QLocalSocket *socket);
+ void pamConvMsg(QLocalSocket *socket, const QString &message);
+ void pamRequest(QLocalSocket *socket, const AuthRequest * const request);
private:
QString findGreeterTheme() const;
@@ -78,6 +82,7 @@ namespace SDDM {
bool m_relogin { true };
bool m_started { false };
+ bool m_failed { false };
int m_terminalId { 7 };
@@ -100,6 +105,7 @@ namespace SDDM {
void slotHelperFinished(Auth::HelperExitStatus status);
void slotAuthInfo(const QString &message, Auth::Info info);
void slotAuthError(const QString &message, Auth::Error error);
+ void slotGreeterStopped();
};
}
diff --git a/src/daemon/Greeter.cpp b/src/daemon/Greeter.cpp
index 0240085..064703c 100644
--- a/src/daemon/Greeter.cpp
+++ b/src/daemon/Greeter.cpp
@@ -223,6 +223,8 @@ namespace SDDM {
if (!m_process->waitForFinished(5000))
m_process->kill();
}
+
+ emit stopped();
}
void Greeter::finished() {
@@ -239,6 +241,8 @@ namespace SDDM {
// clean up
m_process->deleteLater();
m_process = nullptr;
+
+ emit stopped();
}
void Greeter::onRequestChanged() {
@@ -266,6 +270,8 @@ namespace SDDM {
// clean up
m_auth->deleteLater();
m_auth = nullptr;
+
+ emit stopped();
}
void Greeter::onReadyReadStandardError()
diff --git a/src/daemon/Greeter.h b/src/daemon/Greeter.h
index 7391a35..6fcf613 100644
--- a/src/daemon/Greeter.h
+++ b/src/daemon/Greeter.h
@@ -43,6 +43,9 @@ namespace SDDM {
void setSocket(const QString &socket);
void setTheme(const QString &theme);
+ signals:
+ void stopped();
+
public slots:
bool start();
void stop();
diff --git a/src/daemon/SocketServer.cpp b/src/daemon/SocketServer.cpp
index 40d72cc..4df2612 100644
--- a/src/daemon/SocketServer.cpp
+++ b/src/daemon/SocketServer.cpp
@@ -26,6 +26,8 @@
#include "SocketWriter.h"
#include "Utils.h"
+#include "AuthBase.h"
+
#include <QLocalServer>
namespace SDDM {
@@ -99,9 +101,13 @@ namespace SDDM {
connect(socket, &QLocalSocket::disconnected, socket, &QLocalSocket::deleteLater);
}
+ // from greeter (to backend)
+
void SocketServer::readyRead() {
QLocalSocket *socket = qobject_cast<QLocalSocket *>(sender());
+ static const char *logPrefix = "SocketServer: Message received from greeter:";
+
// check socket
if (!socket)
return;
@@ -116,7 +122,7 @@ namespace SDDM {
switch (GreeterMessages(message)) {
case GreeterMessages::Connect: {
// log message
- qDebug() << "Message received from greeter: Connect";
+ qDebug() << logPrefix << "Connect";
// send capabilities
SocketWriter(socket) << quint32(DaemonMessages::Capabilities) << quint32(daemonApp->powerManager()->capabilities());
@@ -130,7 +136,7 @@ namespace SDDM {
break;
case GreeterMessages::Login: {
// log message
- qDebug() << "Message received from greeter: Login";
+ qDebug() << logPrefix << "Login";
// read username, pasword etc.
QString user, password, filename;
@@ -141,58 +147,85 @@ namespace SDDM {
emit login(socket, user, password, session);
}
break;
+ case GreeterMessages::PamResponse: {
+ qDebug() << logPrefix << "PamResponse";
+
+ // new password from greeter dialog
+ QString newPassword;
+ input >> newPassword;
+
+ // emit signal
+ emit sendPamResponse(newPassword);
+ }
+ break;
+ case GreeterMessages::PamCancel: {
+ qDebug() << logPrefix << "PamCancel";
+
+ // emit signal
+ emit cancelPamConv();
+ }
+ break;
case GreeterMessages::PowerOff: {
// log message
- qDebug() << "Message received from greeter: PowerOff";
+ qDebug() << logPrefix << "PowerOff";
// power off
daemonApp->powerManager()->powerOff();
}
break;
case GreeterMessages::Reboot: {
- // log message
- qDebug() << "Message received from greeter: Reboot";
+ qDebug() << logPrefix << "Reboot";
// reboot
daemonApp->powerManager()->reboot();
}
break;
case GreeterMessages::Suspend: {
- // log message
- qDebug() << "Message received from greeter: Suspend";
+ qDebug() << logPrefix << "Suspend";
// suspend
daemonApp->powerManager()->suspend();
}
break;
case GreeterMessages::Hibernate: {
- // log message
- qDebug() << "Message received from greeter: Hibernate";
+ qDebug() << logPrefix << "Hibernate";
// hibernate
daemonApp->powerManager()->hibernate();
}
break;
case GreeterMessages::HybridSleep: {
- // log message
- qDebug() << "Message received from greeter: HybridSleep";
+ qDebug() << logPrefix << "HybridSleep";
// hybrid sleep
daemonApp->powerManager()->hybridSleep();
}
break;
default: {
- // log message
- qWarning() << "Unknown message" << message;
+ qWarning() << logPrefix << "Unknown message" << message;
}
}
}
- void SocketServer::loginFailed(QLocalSocket *socket) {
- SocketWriter(socket) << quint32(DaemonMessages::LoginFailed);
+ // from (pam) backend to greeter
+
+ void SocketServer::loginFailed(QLocalSocket *socket, const QString &message) {
+ SocketWriter(socket) << quint32(DaemonMessages::LoginFailed) << message;
}
void SocketServer::loginSucceeded(QLocalSocket *socket) {
SocketWriter(socket) << quint32(DaemonMessages::LoginSucceeded);
}
+
+ void SocketServer::pamConvMsg(QLocalSocket *socket, const QString &message) {
+ // send pam converse message to greeter
+ SocketWriter(socket) << quint32(DaemonMessages::PamConvMsg) << message;
+ }
+
+ void SocketServer::pamRequest(QLocalSocket *socket, const AuthRequest * const pam_request) {
+ // convert AuthRequest to simple Request
+ Request request = pam_request->request();
+ // send pam request to greeter
+ SocketWriter(socket) << quint32(DaemonMessages::PamRequest) << request;
+ }
}
diff --git a/src/daemon/SocketServer.h b/src/daemon/SocketServer.h
index 0d12acb..d3cd85b 100644
--- a/src/daemon/SocketServer.h
+++ b/src/daemon/SocketServer.h
@@ -25,6 +25,7 @@
#include <QString>
#include "Session.h"
+#include "AuthRequest.h"
class QLocalServer;
class QLocalSocket;
@@ -45,14 +46,20 @@ namespace SDDM {
void newConnection();
void readyRead();
- void loginFailed(QLocalSocket *socket);
+ // from (pam) backend to greeter
+ void loginFailed(QLocalSocket *socket, const QString &message);
void loginSucceeded(QLocalSocket *socket);
+ void pamConvMsg(QLocalSocket *socket, const QString &message);
+ void pamRequest(QLocalSocket *socket, const AuthRequest * const request);
signals:
+ // from greeter to (pam) backend
void login(QLocalSocket *socket,
const QString &user, const QString &password,
const Session &session);
void connected();
+ void sendPamResponse(const QString &newPassword);
+ void cancelPamConv();
private:
QLocalServer *m_server { nullptr };
diff --git a/src/greeter/CMakeLists.txt b/src/greeter/CMakeLists.txt
index 12fc0b5..083dca0 100644
--- a/src/greeter/CMakeLists.txt
+++ b/src/greeter/CMakeLists.txt
@@ -1,6 +1,7 @@
include_directories(
"${CMAKE_SOURCE_DIR}/src/common"
"${CMAKE_BINARY_DIR}/src/common"
+ "${CMAKE_SOURCE_DIR}/src/auth"
"${LIBXCB_INCLUDE_DIR}"
)
@@ -11,6 +12,8 @@ set(GREETER_SOURCES
${CMAKE_SOURCE_DIR}/src/common/SocketWriter.cpp
${CMAKE_SOURCE_DIR}/src/common/ThemeConfig.cpp
${CMAKE_SOURCE_DIR}/src/common/ThemeMetadata.cpp
+ ${CMAKE_SOURCE_DIR}/src/auth/AuthPrompt.cpp
+ ${CMAKE_SOURCE_DIR}/src/auth/AuthRequest.cpp
GreeterApp.cpp
GreeterProxy.cpp
KeyboardLayout.cpp
diff --git a/src/greeter/GreeterApp.cpp b/src/greeter/GreeterApp.cpp
index 5fb70ea..91bb057 100644
--- a/src/greeter/GreeterApp.cpp
+++ b/src/greeter/GreeterApp.cpp
@@ -28,6 +28,8 @@
#include "ThemeMetadata.h"
#include "UserModel.h"
#include "KeyboardModel.h"
+#include "AuthRequest.h"
+#include "AuthPrompt.h"
#include "MessageHandler.h"
@@ -180,10 +182,15 @@ namespace SDDM {
view->rootContext()->setContextProperty(QStringLiteral("userModel"), m_userModel);
view->rootContext()->setContextProperty(QStringLiteral("config"), *m_themeConfig);
view->rootContext()->setContextProperty(QStringLiteral("sddm"), m_proxy);
+ view->rootContext()->setContextProperty(QStringLiteral("request"), m_proxy->getRequest());
view->rootContext()->setContextProperty(QStringLiteral("keyboard"), m_keyboard);
view->rootContext()->setContextProperty(QStringLiteral("primaryScreen"), QGuiApplication::primaryScreen() == screen);
view->rootContext()->setContextProperty(QStringLiteral("__sddm_errors"), QString());
+ // register AuthRequest and AuthPrompt type for pamRequest signal
+ qmlRegisterUncreatableType<AuthRequest>("SddmAuth", 1, 0, "AuthRequest", QStringLiteral("AuthRequest object creation not allowed"));
+ qmlRegisterUncreatableType<AuthPrompt>("SddmAuth", 1, 0, "AuthPrompt", QStringLiteral("AuthPrompt object creation not allowed"));
+
// get theme main script
QString mainScript = QStringLiteral("%1/%2").arg(m_themePath).arg(m_metadata->mainScript());
QUrl mainScriptUrl;
diff --git a/src/greeter/GreeterProxy.cpp b/src/greeter/GreeterProxy.cpp
index 8759e74..e6991a2 100644
--- a/src/greeter/GreeterProxy.cpp
+++ b/src/greeter/GreeterProxy.cpp
@@ -24,6 +24,8 @@
#include "Messages.h"
#include "SessionModel.h"
#include "SocketWriter.h"
+#include "AuthBase.h"
+#include "AuthRequest.h"
#include <QLocalSocket>
@@ -32,6 +34,7 @@ namespace SDDM {
public:
SessionModel *sessionModel { nullptr };
QLocalSocket *socket { nullptr };
+ AuthRequest *request { nullptr };
QString hostName;
bool canPowerOff { false };
bool canReboot { false };
@@ -47,7 +50,8 @@ namespace SDDM {
connect(d->socket, &QLocalSocket::disconnected, this, &GreeterProxy::disconnected);
connect(d->socket, &QLocalSocket::readyRead, this, &GreeterProxy::readyRead);
connect(d->socket, QOverload<QLocalSocket::LocalSocketError>::of(&QLocalSocket::error), this, &GreeterProxy::error);
-
+ // AuthRequest for exchange of PAM conversation prompts/responses with qml
+ d->request = new AuthRequest(this);
// connect to server
d->socket->connectToServer(socket);
}
@@ -64,6 +68,17 @@ namespace SDDM {
d->sessionModel = model;
}
+ /** AuthRequest for exchange of PAM prompts/responses
+ * between GreeterProxy and greeter GUI
+ */
+ AuthRequest *GreeterProxy::getRequest() {
+ return d->request;
+ }
+
+ void GreeterProxy::setRequest(Request *r) {
+ d->request->setRequest(r);
+ }
+
bool GreeterProxy::canPowerOff() const {
return d->canPowerOff;
}
@@ -112,8 +127,6 @@ namespace SDDM {
if (!d->sessionModel) {
// log error
qCritical() << "Session model is not set.";
-
- // return
return;
}
@@ -127,6 +140,18 @@ namespace SDDM {
SocketWriter(d->socket) << quint32(GreeterMessages::Login) << user << password << session;
}
+ // todo: mind session index like for login
+ void GreeterProxy::pamResponse(const QString &newPassword) {
+ // send new (renewal) password to daemon for pam conv
+ SocketWriter(d->socket) << quint32(GreeterMessages::PamResponse) << newPassword;
+ }
+
+ // todo: mind session index like for login
+ void GreeterProxy::cancelPamConv() {
+ // send command to daemon (to pam conv() in backend)
+ SocketWriter(d->socket) << quint32(GreeterMessages::PamCancel);
+ }
+
void GreeterProxy::connected() {
// log connection
qDebug() << "Connected to the daemon.";
@@ -197,11 +222,44 @@ namespace SDDM {
}
break;
case DaemonMessages::LoginFailed: {
+ QString m;
+ // read pam conv() message (info/error) from daemon
+ input >> m;
// log message
- qDebug() << "Message received from daemon: LoginFailed";
-
+ qDebug() << "Message received from daemon: LoginFailed, " << m;
// emit signal
- emit loginFailed();
+ emit loginFailed(m);
+ }
+ break;
+ // pam messages from conv() following login, e.g. for expired pwd
+ // conv() msg with msg_style: PAM_ERROR_MSG or PAM_TEXT_INFO
+ case DaemonMessages::PamConvMsg: {
+ QString m;
+ // read pam conv() message (info/error) from daemon
+ input >> m;
+ qDebug() << "PAM conversation message from daemon: " << m;
+ // send message to GUI
+ emit pamConvMsg(m);
+ }
+ break;
+
+ // new request with prompts from conv() after login, e.g. due to expired pwd
+ // request contains prompt (message) from conv() with msg_style:
+ // PAM_PROMPT_ECHO_ON (user name) or PAM_PROMPT_ECHO_OFF (passwords)
+ case DaemonMessages::PamRequest: {
+ Request r;
+ // read request from daemon (Display->SocketServer)
+ input >> r;
+ // set pam request for qml and convert Request to AuthRequest
+ setRequest(&r);
+ // log prompts
+ qDebug() << "PAM request with " << r.prompts.length() << " prompts from daemon";
+ for (const Prompt &p : r.prompts)
+ qDebug() << "GreeterProxy: Prompt message=" << p.message << ", hidden=" << p.hidden
+ << ", type =" << AuthPrompt::typeToString(p.type) << "(" << p.type << ")";
+
+ // send pam msg's to GUI
+ emit pamRequest();
}
break;
default: {
diff --git a/src/greeter/GreeterProxy.h b/src/greeter/GreeterProxy.h
index ba2533f..76f09c9 100644
--- a/src/greeter/GreeterProxy.h
+++ b/src/greeter/GreeterProxy.h
@@ -25,8 +25,9 @@
class QLocalSocket;
namespace SDDM {
+ class Request;
+ class AuthRequest;
class SessionModel;
-
class GreeterProxyPrivate;
class GreeterProxy : public QObject {
Q_OBJECT
@@ -54,6 +55,7 @@ namespace SDDM {
bool isConnected() const;
void setSessionModel(SessionModel *model);
+ AuthRequest *getRequest();
public slots:
void powerOff();
@@ -62,7 +64,10 @@ namespace SDDM {
void hibernate();
void hybridSleep();
+ // toward (pam) backend
void login(const QString &user, const QString &password, const int sessionIndex) const;
+ void pamResponse(const QString &newPassword);
+ void cancelPamConv();
private slots:
void connected();
@@ -78,11 +83,15 @@ namespace SDDM {
void canHibernateChanged(bool canHibernate);
void canHybridSleepChanged(bool canHybridSleep);
- void loginFailed();
+ // toward qml gui
+ void loginFailed(const QString err_msg);
void loginSucceeded();
+ void pamConvMsg(const QString pam_msg);
+ void pamRequest();
private:
GreeterProxyPrivate *d { nullptr };
+ void setRequest(Request *r);
};
}
diff --git a/src/greeter/theme/Main.qml b/src/greeter/theme/Main.qml
index 5052688..6543bd5 100644
--- a/src/greeter/theme/Main.qml
+++ b/src/greeter/theme/Main.qml
@@ -38,15 +38,23 @@ Rectangle {
TextConstants { id: textConstants }
+ function resetTxtMsg() {
+ txtMessage.text = textConstants.promptSelectUser
+ txtMessage.color = "white"
+ }
+
+ // container for password renewal logic
+ PasswordConnections {
+ renewalDialog: renewal
+ pwdItem: listView.currentItem // use listView.currentItem.password
+ getsBackFocus: listView
+ errMsg: errMessage
+ txtMsg: txtMessage
+ }
+
Connections {
target: sddm
- onLoginSucceeded: {
- }
-
- onLoginFailed: {
- txtMessage.text = textConstants.loginFailed
- listView.currentItem.password = ""
- }
+ onLoginFailed: txtMessage.color = "red"
}
Background {
@@ -117,10 +125,21 @@ Rectangle {
color: "#22000000"
clip: true
+ PasswordRenewal {
+ id: renewal
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: usersContainer.top
+ visible: false
+ color: "#22888888"
+ promptColor: "white"
+ infosColor: "lightcoral"
+ }
+
Item {
id: usersContainer
width: parent.width; height: 300
anchors.verticalCenter: parent.verticalCenter
+ enabled: !renewal.visible
ImageButton {
id: prevUser
@@ -149,7 +168,7 @@ Rectangle {
delegate: userDelegate
orientation: ListView.Horizontal
currentIndex: userModel.lastIndex
-
+ onCurrentIndexChanged: resetTxtMsg()
KeyNavigation.backtab: prevUser; KeyNavigation.tab: nextUser
}
@@ -188,7 +207,9 @@ Rectangle {
font.pixelSize: 20
visible: __sddm_errors !== ""
}
+
}
+
}
Rectangle {
diff --git a/src/helper/HelperApp.cpp b/src/helper/HelperApp.cpp
index d942dcc..47a113b 100644
--- a/src/helper/HelperApp.cpp
+++ b/src/helper/HelperApp.cpp
@@ -164,18 +164,32 @@ namespace SDDM {
m_socket->waitForBytesWritten();
}
- Request HelperApp::request(const Request& request) {
+ Request HelperApp::request(const Request& request, bool &cancel) {
Msg m = Msg::MSG_UNKNOWN;
Request response;
SafeDataStream str(m_socket);
str << Msg::REQUEST << request;
str.send();
+ m_socket->waitForBytesWritten();
str.receive();
- str >> m >> response;
- if (m != REQUEST) {
- response = Request();
- qCritical() << "Received a wrong opcode instead of REQUEST:" << m;
- }
+ str >> m;
+ switch(m) {
+ // user response from daemon (greeter)
+ case REQUEST:
+ str >> response;
+ qDebug() << "HelperApp: daemon response received";
+ break;
+ // password renewal canceled in greeter
+ case CANCEL:
+ cancel = true;
+ qDebug() << "HelperApp: Message received from daemon: CANCEL";
+ // terminate user session in Auth (QProcess child)
+ m_session->terminate();
+ break;
+ default:
+ response = Request();
+ qCritical() << "HelperApp: Received a wrong opcode instead of REQUEST or CANCEL:" << m;
+ } // switch
return response;
}
@@ -185,6 +199,7 @@ namespace SDDM {
SafeDataStream str(m_socket);
str << Msg::AUTHENTICATED << user;
str.send();
+ m_socket->waitForBytesWritten();
if (user.isEmpty())
return env;
str.receive();
@@ -202,6 +217,7 @@ namespace SDDM {
SafeDataStream str(m_socket);
str << Msg::SESSION_STATUS << success;
str.send();
+ m_socket->waitForBytesWritten();
str.receive();
str >> m;
if (m != SESSION_STATUS) {
diff --git a/src/helper/HelperApp.h b/src/helper/HelperApp.h
index b83eade..e07185c 100644
--- a/src/helper/HelperApp.h
+++ b/src/helper/HelperApp.h
@@ -18,8 +18,8 @@
*
*/
-#ifndef Auth_H
-#define Auth_H
+#ifndef HELPERAPP_H
+#define HELPERAPP_H
#include <QtCore/QCoreApplication>
#include <QtCore/QProcessEnvironment>
@@ -43,7 +43,7 @@ namespace SDDM {
const QString &cookie() const;
public slots:
- Request request(const Request &request);
+ Request request(const Request &request, bool &cancel);
void info(const QString &message, Auth::Info type);
void error(const QString &message, Auth::Error type);
QProcessEnvironment authenticated(const QString &user);
@@ -52,7 +52,6 @@ namespace SDDM {
private slots:
void setUp();
void doAuth();
-
void sessionFinished(int status);
private:
@@ -66,4 +65,4 @@ namespace SDDM {
};
}
-#endif // Auth_H
+#endif // HELPERAPP_H
diff --git a/src/helper/backend/PamBackend.cpp b/src/helper/backend/PamBackend.cpp
index 69cbd2c..4d1a1d0 100644
--- a/src/helper/backend/PamBackend.cpp
+++ b/src/helper/backend/PamBackend.cpp
@@ -106,11 +106,14 @@ namespace SDDM {
/*
* Expects an empty prompt list if the previous request has been processed
+ *
+ * @return true if new prompt was inserted or prompt message was set
+ * and false if prompt was found and already sent
*/
bool PamData::insertPrompt(const struct pam_message* msg, bool predict) {
Prompt &p = findPrompt(msg);
- // first, check if we already have stored this propmpt
+ // first, check if we already have stored this prompt
if (p.valid()) {
// we have a response already - do nothing
if (m_sent)
@@ -152,13 +155,54 @@ namespace SDDM {
return true;
}
- Auth::Info PamData::handleInfo(const struct pam_message* msg, bool predict) {
- if (QString::fromLocal8Bit(msg->msg).indexOf(QRegExp(QStringLiteral("^Changing password for [^ ]+$")))) {
- if (predict)
+ /*
+ * Distinguish types of pam conversation infos.
+ *
+ * Currently different info types are not really used (see \ref Display::slotAuthInfo which
+ * just fires one signal for all info types). Just builds new request for "changing" keyword.
+ */
+ Auth::Info PamData::handleInfo(const struct pam_message* msg, bool &newRequest, bool predict) {
+ if (QString::fromLocal8Bit(msg->msg).indexOf(QRegExp(QStringLiteral("^Changing password for [^ ]+$"))) >= 0)
+ {
+ if (predict) {
m_currentRequest = Request(changePassRequest);
+ newRequest = true;
+ m_sent = false;
+ }
return Auth::INFO_PASS_CHANGE_REQUIRED;
}
- return Auth::INFO_UNKNOWN;
+ return Auth::INFO_PAM_CONV;
+ }
+
+ /* Figure out if pam (chAuthTok) sent an error or just an info during conversation
+ *
+ * Real error message e.g. "Password change aborted." But most are just info messages
+ * e.g. "change your password ... (administrator enforced)" with type PAM_ERROR_MSG.
+ */
+ Auth::Error PamData::handleErr(const struct pam_message* msg, bool &newRequest, bool predict) {
+ // bad password during password renewal (for expired password)
+ if (QString::fromLocal8Bit(msg->msg).startsWith(QStringLiteral("BAD PASSWORD:")))
+ {
+ if (predict)
+ {
+ // prepare new (empty) request, new password prompt will follow
+ m_currentRequest = Request();
+ newRequest = false;
+ m_sent = false;
+ }
+ return Auth::ERROR_NONE;
+ }
+
+ if(QString::fromLocal8Bit(msg->msg).startsWith(QStringLiteral("Password change aborted."))) {
+ return Auth::ERROR_AUTHENTICATION;
+ }
+
+ // first login for user, home dir will be created, ignore error message (which would shutdown sddm)
+ if(QString::fromLocal8Bit(msg->msg).startsWith(QStringLiteral("Unable to create and initialize directory"))) {
+ return Auth::ERROR_NONE;
+ }
+
+ return Auth::ERROR_NONE;
}
/*
@@ -172,6 +216,7 @@ namespace SDDM {
return response;
}
+ /* As long as m_currentRequest is not sent (m_sent is false) return current request */
const Request& PamData::getRequest() const {
if (!m_sent)
return m_currentRequest;
@@ -179,6 +224,7 @@ namespace SDDM {
return invalidRequest;
}
+ /* Use new request if prompts are equal to current one and set sent true. */
void PamData::completeRequest(const Request& request) {
if (request.prompts.length() != m_currentRequest.prompts.length()) {
qWarning() << "[PAM] Different request/response list length, ignoring";
@@ -232,6 +278,7 @@ namespace SDDM {
}
bool PamBackend::authenticate() {
+ m_convCanceled = false; // reset for converse()
if (!m_pam->authenticate()) {
m_app->error(m_pam->errorString(), Auth::ERROR_AUTHENTICATION);
return false;
@@ -291,6 +338,32 @@ namespace SDDM {
return QString::fromLocal8Bit((const char*) m_pam->getItem(PAM_USER));
}
+ /** @internal for debug log: get string representation of pam message msg_style */
+ const QString &PamBackend::msgStyleString(int msg_style)
+ {
+ static const QString msgStyle[] = {
+ QStringLiteral("PAM_PROMPT_ECHO_OFF"),
+ QStringLiteral("PAM_PROMPT_ECHO_ON"),
+ QStringLiteral("PAM_ERROR_MSG"),
+ QStringLiteral("PAM_TEXT_INFO"),
+ QStringLiteral("UNKNOWN"),
+ };
+
+ switch(msg_style) {
+ case PAM_PROMPT_ECHO_OFF:
+ return msgStyle[0]; break;
+ case PAM_PROMPT_ECHO_ON:
+ return msgStyle[1]; break;
+ case PAM_ERROR_MSG:
+ return msgStyle[2]; break;
+ case PAM_TEXT_INFO:
+ return msgStyle[3]; break;
+ default: break;
+ }
+
+ return msgStyle[4];
+ }
+
int PamBackend::converse(int n, const struct pam_message **msg, struct pam_response **resp) {
qDebug() << "[PAM] Conversation with" << n << "messages";
@@ -299,34 +372,69 @@ namespace SDDM {
if (n <= 0 || n > PAM_MAX_NUM_MSG)
return PAM_CONV_ERR;
+ // see whats going on in pam_conv
for (int i = 0; i < n; i++) {
+ qDebug() << "[PAM] pam_conv: style=" << msgStyleString(msg[i]->msg_style)
+ << "(" << msg[i]->msg_style << "), msg[" << i << "]=" << QString::fromLocal8Bit(msg[i]->msg);
+ }
+
+ for (int i = 0; i < n; i++) {
+
+ QString convMsg = QString::fromLocal8Bit(msg[i]->msg);
+ Auth::Error convErr;
+
switch(msg[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
case PAM_PROMPT_ECHO_ON:
newRequest = m_data->insertPrompt(msg[i], n == 1);
break;
case PAM_ERROR_MSG:
- m_app->error(QString::fromLocal8Bit(msg[i]->msg), Auth::ERROR_AUTHENTICATION);
+ convErr = m_data->handleErr(msg[i], newRequest, n == 1);
+ // upon pwd change pam sends error message and then starts conversation,
+ // so treat most of them actually as infos
+ if(convErr==Auth::ERROR_NONE)
+ m_app->info(convMsg, Auth::INFO_PAM_CONV);
+ else {
+ qDebug() << "[PAM] PamBackend: pam error message, msg=" << convMsg;
+ m_app->error(convMsg, Auth::ERROR_AUTHENTICATION /* convErr */);
+ return PAM_CONV_ERR;
+ }
break;
case PAM_TEXT_INFO:
+ // send pam conversation msg to greeter via HelperApp, SocketServer, Display
// if there's only the info message, let's predict the prompts too
- m_app->info(QString::fromLocal8Bit(msg[i]->msg), m_data->handleInfo(msg[i], n == 1));
+ m_app->info(convMsg, m_data->handleInfo(msg[i], newRequest, n == 1));
break;
default:
break;
}
}
+ // pam (chauthtok) does not shut up after canceled with PAM_CONV_ERR in previous pam_conv
+ if(m_convCanceled) {
+ qDebug() << "[PAM] PamBackend: conversation canceled, dump trailing messages";
+ return PAM_CONV_ERR;
+ }
+
if (newRequest) {
- Request sent = m_data->getRequest();
+ // get current request
+ Request send = m_data->getRequest();
Request received;
- if (sent.valid()) {
- received = m_app->request(sent);
+ // any prompt?
+ if (send.valid()) {
+ // send request to daemon (ask for a response)
+ received = m_app->request(send, m_convCanceled);
+
+ // password input canceled in greeter
+ if (m_convCanceled) {
+ return PAM_CONV_ERR;
+ }
if (!received.valid())
return PAM_CONV_ERR;
+ // compare request with response
m_data->completeRequest(received);
}
}
diff --git a/src/helper/backend/PamBackend.h b/src/helper/backend/PamBackend.h
index 4c8b4b3..8784f21 100644
--- a/src/helper/backend/PamBackend.h
+++ b/src/helper/backend/PamBackend.h
@@ -37,7 +37,8 @@ namespace SDDM {
PamData();
bool insertPrompt(const struct pam_message *msg, bool predict = true);
- Auth::Info handleInfo(const struct pam_message *msg, bool predict);
+ Auth::Info handleInfo(const struct pam_message *msg, bool &newRequest, bool predict);
+ Auth::Error handleErr(const struct pam_message *msg, bool &newRequest, bool predict);
const Request& getRequest() const;
void completeRequest(const Request& request);
@@ -71,8 +72,10 @@ namespace SDDM {
virtual QString userName();
private:
+ bool m_convCanceled { false };
PamData *m_data { nullptr };
PamHandle *m_pam { nullptr };
+ static const QString &msgStyleString(int msg_style);
};
}
diff --git a/src/helper/backend/PasswdBackend.cpp b/src/helper/backend/PasswdBackend.cpp
index 8a0b8c9..27e7435 100644
--- a/src/helper/backend/PasswdBackend.cpp
+++ b/src/helper/backend/PasswdBackend.cpp
@@ -55,7 +55,9 @@ namespace SDDM {
r.prompts << Prompt(AuthPrompt::LOGIN_USER, QStringLiteral("Login"), false);
r.prompts << Prompt(AuthPrompt::LOGIN_PASSWORD, QStringLiteral("Password"), true);
- Request response = m_app->request(r);
+ bool canceled = false;
+ Request response = m_app->request(r,canceled);
+ if(canceled==true) return false; // canceled in greeter
for(const Prompt &p : qAsConst(response.prompts)) {
switch (p.type) {
case AuthPrompt::LOGIN_USER: