on xenial help sign in
Use PAM to check password validity
commit6262458aa10dffb0cb840a1aa6caff92c52a40bd authorMichael Terry <michael.terry@canonical.com> dateTue 1 Jul 2014, 16:21 (12 years ago) parent7d8c5d5
5 files changed+187−43
debian/control+1 plugins/LightDM/plugin.cpp+2 tests/mocks/LightDM/GreeterPrivate.h+2 tests/mocks/LightDM/demo/CMakeLists.txt+4−2 tests/mocks/LightDM/demo/GreeterPrivate.cpp+178−41
debian/control+1
@@ -14,6 +14,7 @@ Build-Depends: cmake,
1414 libglib2.0-dev,
1515 libgsettings-qt-dev,
1616 libhud-client2-dev,
17+ libpam0g-dev,
1718 libpulse-dev,
1819 libqmenumodel-dev (>= 0.2.7),
1920 libqt5xmlpatterns5-dev,
plugins/LightDM/plugin.cpp+2
@@ -66,6 +66,8 @@ void LightDMPlugin::registerTypes(const char *uri)
6666 qmlRegisterType<UserMetricsOutput::ColorTheme>();
6767
6868 Q_ASSERT(uri == QLatin1String("LightDM"));
69+ qRegisterMetaType<QLightDM::Greeter::MessageType>("QLightDM::Greeter::MessageType");
70+ qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
6971 qmlRegisterSingletonType<Greeter>(uri, 0, 1, "Greeter", greeter_provider);
7072 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
7173 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
tests/mocks/LightDM/GreeterPrivate.h+2
@@ -24,6 +24,7 @@
2424namespace QLightDM
2525{
2626class Greeter;
27+class GreeterImpl;
2728
2829class GreeterPrivate
2930{
@@ -40,6 +41,7 @@ public:
4041 void handleRespond(const QString &response);
4142
4243protected:
44+ GreeterImpl *m_impl; // if the backend needs more private data
4345 Greeter * const q_ptr;
4446
4547private:
tests/mocks/LightDM/demo/CMakeLists.txt+4−2
@@ -11,11 +11,13 @@ set(LibLightDM_SOURCES
1111add_library(MockLightDM-demo STATIC ${LibLightDM_SOURCES})
1212
1313include_directories(
14+ ${CMAKE_CURRENT_BINARY_DIR}
1415 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
1516)
1617
1718target_link_libraries(MockLightDM-demo
1819 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
19− )
20+ -lpam
21+)
2022
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 @@
1818
1919#include "../Greeter.h"
2020#include "../GreeterPrivate.h"
21−#include <QtCore/QDir>
22−#include <QtCore/QProcess>
23−#include <QtCore/QSettings>
21+#include <QFuture>
22+#include <QFutureInterface>
23+#include <QFutureWatcher>
24+#include <QQueue>
25+#include <QtConcurrent>
26+#include <QVector>
27+#include <security/pam_appl.h>
2428
2529namespace QLightDM
2630{
2731
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+
28198GreeterPrivate::GreeterPrivate(Greeter* parent)
29199 : authenticated(false),
30200 authenticationUser(),
201+ m_impl(new GreeterImpl(parent, this)),
31202 q_ptr(parent)
32203{
33204}
34205
35206void GreeterPrivate::handleAuthenticate()
36207{
37− Q_Q(Greeter);
38−
39− QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
40− settings.beginGroup(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− }
208+ m_impl->start(authenticationUser);
51209}
52210
53211void GreeterPrivate::handleRespond(const QString &response)
54212{
55− Q_Q(Greeter);
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();
213+ m_impl->respond(response);
79214}
80215
81216}
217+
218+#include "GreeterPrivate.moc"