on xenial help sign in
Use PAM to check password validity
6262458Michael Terry
debian/control+1
@@ -14,6 +14,7 @@ Build-Depends: cmake,
14 libglib2.0-dev,
14 libglib2.0-dev,
15 libgsettings-qt-dev,
15 libgsettings-qt-dev,
16 libhud-client2-dev,
16 libhud-client2-dev,
17+ libpam0g-dev,
17 libpulse-dev,
18 libpulse-dev,
18 libqmenumodel-dev (>= 0.2.7),
19 libqmenumodel-dev (>= 0.2.7),
19 libqt5xmlpatterns5-dev,
20 libqt5xmlpatterns5-dev,
plugins/LightDM/plugin.cpp+2
@@ -66,6 +66,8 @@ void LightDMPlugin::registerTypes(const char *uri)
66 qmlRegisterType<UserMetricsOutput::ColorTheme>();
66 qmlRegisterType<UserMetricsOutput::ColorTheme>();
67
67
68 Q_ASSERT(uri == QLatin1String("LightDM"));
68 Q_ASSERT(uri == QLatin1String("LightDM"));
69+ qRegisterMetaType<QLightDM::Greeter::MessageType>("QLightDM::Greeter::MessageType");
70+ qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
69 qmlRegisterSingletonType<Greeter>(uri, 0, 1, "Greeter", greeter_provider);
71 qmlRegisterSingletonType<Greeter>(uri, 0, 1, "Greeter", greeter_provider);
70 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
72 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
71 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
73 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
tests/mocks/LightDM/GreeterPrivate.h+2
@@ -24,6 +24,7 @@
24namespace QLightDM
24namespace QLightDM
25{
25{
26class Greeter;
26class Greeter;
27+class GreeterImpl;
27
28
28class GreeterPrivate
29class GreeterPrivate
29{
30{
@@ -40,6 +41,7 @@ public:
40 void handleRespond(const QString &response);
41 void handleRespond(const QString &response);
41
42
42protected:
43protected:
44+ GreeterImpl *m_impl; // if the backend needs more private data
43 Greeter * const q_ptr;
45 Greeter * const q_ptr;
44
46
45private:
47private:
tests/mocks/LightDM/demo/CMakeLists.txt+4−2
@@ -11,11 +11,13 @@ set(LibLightDM_SOURCES
11add_library(MockLightDM-demo STATIC ${LibLightDM_SOURCES})
11add_library(MockLightDM-demo STATIC ${LibLightDM_SOURCES})
12
12
13include_directories(
13include_directories(
14+ ${CMAKE_CURRENT_BINARY_DIR}
14 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
15 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
15)
16)
16
17
17target_link_libraries(MockLightDM-demo
18target_link_libraries(MockLightDM-demo
18 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
19 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
20+ -lpam
19− )
21+)
20
22
21−qt5_use_modules(MockLightDM-demo Gui)
23+qt5_use_modules(MockLightDM-demo Concurrent Gui)
tests/mocks/LightDM/demo/GreeterPrivate.cpp+178−41
@@ -18,64 +18,201 @@
18
18
19#include "../Greeter.h"
19#include "../Greeter.h"
20#include "../GreeterPrivate.h"
20#include "../GreeterPrivate.h"
21−#include <QtCore/QDir>
21+#include <QFuture>
22−#include <QtCore/QProcess>
22+#include <QFutureInterface>
23−#include <QtCore/QSettings>
23+#include <QFutureWatcher>
24+#include <QQueue>
25+#include <QtConcurrent>
26+#include <QVector>
27+#include <security/pam_appl.h>
24
28
25namespace QLightDM
29namespace QLightDM
26{
30{
27
31
32+class GreeterImpl : public QObject
33+{
34+ Q_OBJECT
35+
36+ typedef QFutureInterface<QString> ResponseFuture;
37+
38+public:
39+ explicit GreeterImpl(Greeter *parent, GreeterPrivate *greeterPrivate)
40+ : QObject(parent),
41+ greeter(parent),
42+ greeterPrivate(greeterPrivate),
43+ pamHandle(NULL)
44+ {
45+ qRegisterMetaType<QLightDM::GreeterImpl::ResponseFuture>("QLightDM::GreeterImpl::ResponseFuture");
46+
47+ connect(&futureWatcher, SIGNAL(finished()),
48+ this, SLOT(finishPam()));
49+ connect(this, SIGNAL(showMessage(QString, QLightDM::Greeter::MessageType)),
50+ greeter, SIGNAL(showMessage(QString, QLightDM::Greeter::MessageType)));
51+ connect(this, SIGNAL(showPrompt(QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)),
52+ this, SLOT(handlePrompt(QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)));
53+ }
54+
55+ void start(QString username)
56+ {
57+ pam_conv conversation;
58+ conversation.conv = converseWithPam;
59+ conversation.appdata_ptr = static_cast<void*>(this);
60+
61+ if (pam_start("lightdm", username.toUtf8(),
62+ &conversation, &pamHandle) == PAM_SUCCESS) {
63+ futureWatcher.setFuture(QtConcurrent::run(authenticateWithPam, pamHandle));
64+ } else {
65+ greeterPrivate->authenticated = false;
66+ Q_EMIT greeter->authenticationComplete();
67+ }
68+ }
69+
70+ static int authenticateWithPam(pam_handle* pamHandle)
71+ {
72+ int pamStatus = pam_authenticate(pamHandle, 0);
73+ if (pamStatus == PAM_SUCCESS)
74+ pamStatus = pam_acct_mgmt(pamHandle, 0);
75+ if (pamStatus == PAM_NEW_AUTHTOK_REQD)
76+ pamStatus = pam_chauthtok(pamHandle, PAM_CHANGE_EXPIRED_AUTHTOK);
77+ if (pamStatus == PAM_SUCCESS)
78+ pam_setcred(pamHandle, PAM_REINITIALIZE_CRED);
79+ return pamStatus;
80+ }
81+
82+ static int converseWithPam(int num_msg, const pam_message** msg,
83+ pam_response** resp, void* appdata_ptr)
84+ {
85+ if (num_msg <= 0)
86+ return PAM_CONV_ERR;
87+
88+ auto* tmp_response = static_cast<pam_response*>(calloc(num_msg, sizeof(pam_response)));
89+ if (!tmp_response)
90+ return PAM_CONV_ERR;
91+
92+ GreeterImpl* impl = static_cast<GreeterImpl*>(appdata_ptr);
93+
94+ bool raise_error = false;
95+ int count;
96+ QVector<ResponseFuture> responses;
97+
98+ for (count = 0; count < num_msg && !raise_error; ++count)
99+ {
100+ switch (msg[count]->msg_style)
101+ {
102+ case PAM_PROMPT_ECHO_ON:
103+ {
104+ QString message(msg[count]->msg);
105+ responses.append(ResponseFuture());
106+ responses.last().reportStarted();
107+ Q_EMIT impl->showPrompt(message, Greeter::PromptTypeQuestion, responses.last());
108+ break;
109+ }
110+ case PAM_PROMPT_ECHO_OFF:
111+ {
112+ QString message(msg[count]->msg);
113+ responses.append(ResponseFuture());
114+ responses.last().reportStarted();
115+ Q_EMIT impl->showPrompt(message, Greeter::PromptTypeSecret, responses.last());
116+ }
117+ case PAM_TEXT_INFO:
118+ {
119+ QString message(msg[count]->msg);
120+ Q_EMIT impl->showMessage(message, Greeter::MessageTypeInfo);
121+ }
122+ default:
123+ {
124+ QString message(msg[count]->msg);
125+ Q_EMIT impl->showMessage(message, Greeter::MessageTypeError);
126+ }
127+ }
128+ }
129+
130+ int i = 0;
131+
132+ for (auto &response : responses)
133+ {
134+ pam_response* resp_item = &tmp_response[i++];
135+ resp_item->resp_retcode = 0;
136+ resp_item->resp = strdup(response.future().result().toUtf8());
137+
138+ if (!resp_item->resp)
139+ {
140+ raise_error = true;
141+ break;
142+ }
143+ }
144+
145+ if (raise_error)
146+ {
147+ for (int i = 0; i < count; ++i)
148+ free(tmp_response[i].resp);
149+
150+ free(tmp_response);
151+ return PAM_CONV_ERR;
152+ }
153+ else
154+ {
155+ *resp = tmp_response;
156+ return PAM_SUCCESS;
157+ }
158+ }
159+
160+public Q_SLOTS:
161+ void respond(QString response)
162+ {
163+ if (!futures.isEmpty()) {
164+ futures.dequeue().reportFinished(&response);
165+ }
166+ }
167+
168+Q_SIGNALS:
169+ void showMessage(QString text, QLightDM::Greeter::MessageType type);
170+ void showPrompt(QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture response);
171+
172+private Q_SLOTS:
173+ void finishPam()
174+ {
175+ int pamStatus = futureWatcher.result();
176+
177+ pam_end(pamHandle, pamStatus);
178+ pamHandle = NULL;
179+
180+ greeterPrivate->authenticated = (pamStatus == PAM_SUCCESS);
181+ Q_EMIT greeter->authenticationComplete();
182+ }
183+
184+ void handlePrompt(QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture future)
185+ {
186+ futures.enqueue(future);
187+ Q_EMIT greeter->showPrompt(text, type);
188+ }
189+
190+private:
191+ Greeter *greeter;
192+ GreeterPrivate *greeterPrivate;
193+ pam_handle* pamHandle;
194+ QFutureWatcher<int> futureWatcher;
195+ QQueue<ResponseFuture> futures;
196+};
197+
28GreeterPrivate::GreeterPrivate(Greeter* parent)
198GreeterPrivate::GreeterPrivate(Greeter* parent)
29 : authenticated(false),
199 : authenticated(false),
30 authenticationUser(),
200 authenticationUser(),
201+ m_impl(new GreeterImpl(parent, this)),
31 q_ptr(parent)
202 q_ptr(parent)
32{
203{
33}
204}
34
205
35void GreeterPrivate::handleAuthenticate()
206void GreeterPrivate::handleAuthenticate()
36{
207{
37− Q_Q(Greeter);
38−
39− QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
40− settings.beginGroup(authenticationUser);
208+ m_impl->start(authenticationUser);
41− QVariant password = settings.value("password", "none");
42−
43− if (password == "pin") {
44− Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
45− } else if (password == "keyboard") {
46− Q_EMIT q->showPrompt("Password", Greeter::PromptTypeSecret);
47− } else {
48− authenticated = true;
49− Q_EMIT q->authenticationComplete();
50− }
51}
209}
52
210
53void GreeterPrivate::handleRespond(const QString &response)
211void GreeterPrivate::handleRespond(const QString &response)
54{
212{
55− Q_Q(Greeter);
213+ m_impl->respond(response);
56−
57− QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
58− settings.beginGroup(authenticationUser);
59− QVariant passwordValue(settings.value("passwordValue", QString()));
60− QStringList passwordParts = passwordValue.toString().split('$', QString::SkipEmptyParts);
61−
62− // We only support passwd type 6 (sha512) for now
63− if (passwordParts.length() == 3 && passwordParts[0] == "6") {
64− QString command = "mkpasswd --method=sha-512 --stdin --salt=" + passwordParts[1];
65−
66− QProcess process;
67− process.start(command);
68− process.write(response.toLatin1());
69− process.closeWriteChannel();
70− process.waitForFinished();
71−
72− QString result = QString(process.readAllStandardOutput()).trimmed();
73− authenticated = result == passwordValue;
74− } else {
75− authenticated = false;
76− }
77−
78− Q_EMIT q->authenticationComplete();
79}
214}
80
215
81}
216}
217+
218+#include "GreeterPrivate.moc"