1/*
2
3Conversation widget for kdm greeter
4
5Copyright (C) 1997, 1998, 2000 Steffen Hansen <hansen@kde.org>
6Copyright (C) 2000-2004 Oswald Buddenhagen <ossi@kde.org>
7
8
9This program is free software; you can redistribute it and/or modify
10it under the terms of the GNU General Public License as published by
11the Free Software Foundation; either version 2 of the License, or
12(at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with this program; if not, write to the Free Software
21Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
23*/
24
25#include "kgreet_winbind.h"
26
27#include <klocale.h>
28#include <kglobal.h>
29#include <kdebug.h>
30#include <kcombobox.h>
31#include <klineedit.h>
32#include <kuser.h>
33#include <kprocess.h>
34
35#include <QRegExp>
36#include <QLayout>
37#include <QLabel>
38#include <QContextMenuEvent>
39#include <QGridLayout>
40#include <QTextStream>
41
42#include <stdlib.h>
43#include <stdio.h>
44
45static int echoMode;
46
47class KDMPasswordEdit : public KLineEdit {
48public:
49 KDMPasswordEdit(QWidget *parent) : KLineEdit(parent)
50 {
51 if (::echoMode == -1)
52 setPasswordMode(true);
53 else
54 setEchoMode(::echoMode ? Password : NoEcho);
55 setContextMenuPolicy(Qt::NoContextMenu);
56 }
57};
58
59static char separator;
60static QStringList staticDomains;
61static QString defaultDomain;
62
63static void
64splitEntity(const QString &ent, QString &dom, QString &usr)
65{
66 int pos = ent.indexOf(separator);
67 if (pos < 0)
68 dom = "<local>", usr = ent;
69 else
70 dom = ent.left(pos), usr = ent.mid(pos + 1);
71}
72
73KWinbindGreeter::KWinbindGreeter(KGreeterPluginHandler *_handler,
74 QWidget *parent,
75 const QString &_fixedEntity,
76 Function _func, Context _ctx) :
77 QObject(),
78 KGreeterPlugin(_handler),
79 func(_func),
80 ctx(_ctx),
81 exp(-1),
82 pExp(-1),
83 running(false)
84{
85 QGridLayout *grid = 0;
86
87 int line = 0;
88
89 if (!_handler->gplugHasNode("domain-entry") ||
90 !_handler->gplugHasNode("user-entry") ||
91 !_handler->gplugHasNode("pw-entry"))
92 {
93 parent = new QWidget(parent);
94 parent->setObjectName("talker");
95 widgetList << parent;
96 grid = new QGridLayout(parent);
97 grid->setMargin(0);
98 }
99
100 domainLabel = loginLabel = passwdLabel = passwd1Label = passwd2Label = 0;
101 domainCombo = 0;
102 loginEdit = 0;
103 passwdEdit = passwd1Edit = passwd2Edit = 0;
104 if (ctx == ExUnlock || ctx == ExChangeTok)
105 splitEntity(KUser().loginName(), fixedDomain, fixedUser);
106 else
107 splitEntity(_fixedEntity, fixedDomain, fixedUser);
108 if (func != ChAuthTok) {
109 if (fixedUser.isEmpty()) {
110 domainCombo = new KComboBox(parent);
111 connect(domainCombo, SIGNAL(activated(QString)),
112 SLOT(slotChangedDomain(QString)));
113 connect(domainCombo, SIGNAL(activated(QString)),
114 SLOT(slotLoginLostFocus()));
115 connect(domainCombo, SIGNAL(activated(QString)),
116 SLOT(slotChanged()));
117 // should handle loss of focus
118 loginEdit = new KLineEdit(parent);
119 loginEdit->setContextMenuPolicy(Qt::NoContextMenu);
120
121 if (!grid) {
122 loginEdit->setObjectName("user-entry");
123 domainCombo->setObjectName("domain-entry");
124 widgetList << domainCombo << loginEdit;
125 } else {
126 domainLabel = new QLabel(i18n("&Domain:"), parent);
127 domainLabel->setBuddy(domainCombo);
128 loginLabel = new QLabel(i18n("&Username:"), parent);
129 loginLabel->setBuddy(loginEdit);
130 grid->addWidget(domainLabel, line, 0);
131 grid->addWidget(domainCombo, line++, 1);
132 grid->addWidget(loginLabel, line, 0);
133 grid->addWidget(loginEdit, line++, 1);
134 }
135 connect(loginEdit, SIGNAL(editingFinished()), SLOT(slotLoginLostFocus()));
136 connect(loginEdit, SIGNAL(editingFinished()), SLOT(slotChanged()));
137 connect(loginEdit, SIGNAL(textChanged(QString)), SLOT(slotChanged()));
138 connect(loginEdit, SIGNAL(selectionChanged()), SLOT(slotChanged()));
139 domainCombo->addItems(staticDomains);
140 QTimer::singleShot(0, this, SLOT(slotStartDomainList()));
141 } else if (ctx != Login && ctx != Shutdown && grid) {
142 domainLabel = new QLabel(i18n("Domain:"), parent);
143 grid->addWidget(domainLabel, line, 0);
144 grid->addWidget(new QLabel(fixedDomain, parent), line++, 1);
145 loginLabel = new QLabel(i18n("Username:"), parent);
146 grid->addWidget(loginLabel, line, 0);
147 grid->addWidget(new QLabel(fixedUser, parent), line++, 1);
148 }
149 passwdEdit = new KDMPasswordEdit(parent);
150 connect(passwdEdit, SIGNAL(textChanged(QString)),
151 SLOT(slotChanged()));
152 connect(passwdEdit, SIGNAL(editingFinished()), SLOT(slotChanged()));
153
154 if (!grid) {
155 passwdEdit->setObjectName("pw-entry");
156 widgetList << passwdEdit;
157 } else {
158 passwdLabel = new QLabel(func == Authenticate ?
159 i18n("&Password:") :
160 i18n("Current &password:"),
161 parent);
162 passwdLabel->setBuddy(passwdEdit);
163 grid->addWidget(passwdLabel, line, 0);
164 grid->addWidget(passwdEdit, line++, 1);
165 }
166
167 if (loginEdit)
168 loginEdit->setFocus();
169 else
170 passwdEdit->setFocus();
171 }
172 if (func != Authenticate) {
173 passwd1Edit = new KDMPasswordEdit(parent);
174 passwd1Label = new QLabel(i18n("&New password:"), parent);
175 passwd1Label->setBuddy(passwd1Edit);
176 passwd2Edit = new KDMPasswordEdit(parent);
177 passwd2Label = new QLabel(i18n("Con&firm password:"), parent);
178 passwd2Label->setBuddy(passwd2Edit);
179 if (grid) {
180 grid->addWidget(passwd1Label, line, 0);
181 grid->addWidget(passwd1Edit, line++, 1);
182 grid->addWidget(passwd2Label, line, 0);
183 grid->addWidget(passwd2Edit, line, 1);
184 }
185 if (!passwdEdit)
186 passwd1Edit->setFocus();
187 }
188}
189
190// virtual
191KWinbindGreeter::~KWinbindGreeter()
192{
193 abort();
194 qDeleteAll(widgetList);
195}
196
197void
198KWinbindGreeter::slotChangedDomain(const QString &dom)
199{
200 if (!loginEdit->completionObject())
201 return;
202 QStringList users;
203 if (dom == "<local>") {
204 for (QStringList::ConstIterator it = allUsers.constBegin(); it != allUsers.constEnd(); ++it)
205 if ((*it).indexOf(separator) < 0)
206 users << *it;
207 } else {
208 QString st(dom + separator);
209 for (QStringList::ConstIterator it = allUsers.constBegin(); it != allUsers.constEnd(); ++it)
210 if ((*it).startsWith(st))
211 users << (*it).mid(st.length());
212 }
213 loginEdit->completionObject()->setItems(users);
214}
215
216void // virtual
217KWinbindGreeter::loadUsers(const QStringList &users)
218{
219 allUsers = users;
220 KCompletion *userNamesCompletion = new KCompletion;
221 loginEdit->setCompletionObject(userNamesCompletion);
222 loginEdit->setAutoDeleteCompletionObject(true);
223 loginEdit->setCompletionMode(KGlobalSettings::CompletionAuto);
224 slotChangedDomain(defaultDomain);
225}
226
227void // virtual
228KWinbindGreeter::presetEntity(const QString &entity, int field)
229{
230 QString dom, usr;
231 splitEntity(entity, dom, usr);
232 domainCombo->setCurrentItem(dom, true);
233 slotChangedDomain(dom);
234 loginEdit->setText(usr);
235 if (field > 1) {
236 passwdEdit->setFocus();
237 } else if (field == 1 || field == -1) {
238 if (field == -1) {
239 passwdEdit->setText(" ");
240 passwdEdit->setEnabled(false);
241 authTok = false;
242 }
243 loginEdit->setFocus();
244 loginEdit->selectAll();
245 }
246 curUser = entity;
247}
248
249QString // virtual
250KWinbindGreeter::getEntity() const
251{
252 QString dom, usr;
253 if (fixedUser.isEmpty()) {
254 dom = domainCombo->currentText();
255 usr = loginEdit->text().trimmed();
256 loginEdit->setText(usr);
257 } else {
258 dom = fixedDomain;
259 usr = fixedUser;
260 }
261 return dom == "<local>" ? usr : dom + separator + usr;
262}
263
264void // virtual
265KWinbindGreeter::setUser(const QString &user)
266{
267 // assert (fixedUser.isEmpty());
268 curUser = user;
269 QString dom, usr;
270 splitEntity(user, dom, usr);
271 domainCombo->setCurrentItem(dom, true);
272 slotChangedDomain(dom);
273 loginEdit->setText(usr);
274 passwdEdit->setFocus();
275 passwdEdit->selectAll();
276}
277
278void // virtual
279KWinbindGreeter::setEnabled(bool enable)
280{
281 // assert(!passwd1Label);
282 // assert(func == Authenticate && ctx == Shutdown);
283// if (domainCombo)
284// domainCombo->setEnabled(enable);
285// if (loginLabel)
286// loginLabel->setEnabled(enable);
287 passwdLabel->setEnabled(enable);
288 setActive(enable);
289 if (enable)
290 passwdEdit->setFocus();
291}
292
293void // private
294KWinbindGreeter::returnData()
295{
296 switch (exp) {
297 case 0:
298 handler->gplugReturnText(getEntity().toLocal8Bit(),
299 KGreeterPluginHandler::IsUser);
300 break;
301 case 1:
302 handler->gplugReturnText(passwdEdit->text().toLocal8Bit(),
303 KGreeterPluginHandler::IsPassword |
304 KGreeterPluginHandler::IsSecret);
305 break;
306 case 2:
307 handler->gplugReturnText(passwd1Edit->text().toLocal8Bit(),
308 KGreeterPluginHandler::IsSecret);
309 break;
310 default: // case 3:
311 handler->gplugReturnText(passwd2Edit->text().toLocal8Bit(),
312 KGreeterPluginHandler::IsNewPassword |
313 KGreeterPluginHandler::IsSecret);
314 break;
315 }
316}
317
318bool // virtual
319KWinbindGreeter::textMessage(const char *text, bool err)
320{
321 if (!err &&
322 QString(text).indexOf(QRegExp("^Changing password for [^ ]+$")) >= 0)
323 return true;
324 return false;
325}
326
327void // virtual
328KWinbindGreeter::textPrompt(const char *prompt, bool echo, bool nonBlocking)
329{
330 pExp = exp;
331 if (echo) {
332 exp = 0;
333 } else if (!authTok) {
334 exp = 1;
335 } else {
336 QString pr(prompt);
337 if (pr.indexOf(QRegExp("\\b(old|current)\\b", Qt::CaseInsensitive)) >= 0) {
338 handler->gplugReturnText("", KGreeterPluginHandler::IsOldPassword |
339 KGreeterPluginHandler::IsSecret);
340 return;
341 } else if (pr.indexOf(QRegExp("\\b(re-?(enter|type)|again|confirm|repeat)\\b",
342 Qt::CaseInsensitive)) >= 0) {
343 exp = 3;
344 } else if (pr.indexOf(QRegExp("\\bnew\\b", Qt::CaseInsensitive)) >= 0) {
345 exp = 2;
346 } else {
347 handler->gplugMsgBox(QMessageBox::Critical,
348 i18n("Unrecognized prompt \"%1\"", prompt));
349 handler->gplugReturnText(0, 0);
350 exp = -1;
351 return;
352 }
353 }
354
355 if (pExp >= 0 && pExp >= exp) {
356 revive();
357 has = -1;
358 }
359
360 if (has >= exp || nonBlocking)
361 returnData();
362}
363
364bool // virtual
365KWinbindGreeter::binaryPrompt(const char *, bool)
366{
367 // this simply cannot happen ... :}
368 return true;
369}
370
371void // virtual
372KWinbindGreeter::start()
373{
374 authTok = !(passwdEdit && passwdEdit->isEnabled());
375 exp = has = -1;
376 running = true;
377}
378
379void // virtual
380KWinbindGreeter::suspend()
381{
382}
383
384void // virtual
385KWinbindGreeter::resume()
386{
387}
388
389void // virtual
390KWinbindGreeter::next()
391{
392 // assert(running);
393 int pHas = has;
394 if (domainCombo && domainCombo->hasFocus()) {
395 loginEdit->setFocus();
396 } else if (loginEdit && loginEdit->hasFocus()) {
397 passwdEdit->setFocus(); // will cancel running login if necessary
398 has = 0;
399 } else if (passwdEdit && passwdEdit->hasFocus()) {
400 if (passwd1Edit)
401 passwd1Edit->setFocus();
402 has = 1;
403 } else if (passwd1Edit) {
404 if (passwd1Edit->hasFocus()) {
405 passwd2Edit->setFocus();
406 has = 1; // sic!
407 } else {
408 has = 3;
409 }
410 } else {
411 has = 1;
412 }
413 if (exp < 0)
414 handler->gplugStart();
415 else if (has >= exp && has > pHas)
416 returnData();
417}
418
419void // virtual
420KWinbindGreeter::abort()
421{
422 running = false;
423 if (exp >= 0) {
424 exp = -1;
425 handler->gplugReturnText(0, 0);
426 }
427}
428
429void // virtual
430KWinbindGreeter::succeeded()
431{
432 // assert(running || timed_login);
433 if (!authTok) {
434 setActive(false);
435 if (passwd1Edit) {
436 authTok = true;
437 return;
438 }
439 } else {
440 setActive2(false);
441 }
442 exp = -1;
443 running = false;
444}
445
446void // virtual
447KWinbindGreeter::failed()
448{
449 // assert(running || timed_login);
450 setActive(false);
451 setActive2(false);
452 exp = -1;
453 running = false;
454}
455
456void // virtual
457KWinbindGreeter::revive()
458{
459 // assert(!running);
460 setActive2(true);
461 if (authTok) {
462 passwd1Edit->clear();
463 passwd2Edit->clear();
464 passwd1Edit->setFocus();
465 } else {
466 passwdEdit->clear();
467 if (loginEdit && loginEdit->isEnabled()) {
468 passwdEdit->setEnabled(true);
469 } else {
470 setActive(true);
471 if (loginEdit && loginEdit->text().isEmpty())
472 loginEdit->setFocus();
473 else
474 passwdEdit->setFocus();
475 }
476 }
477}
478
479void // virtual
480KWinbindGreeter::clear()
481{
482 // assert(!running && !passwd1Edit);
483 passwdEdit->clear();
484 if (loginEdit) {
485 domainCombo->setCurrentItem(defaultDomain);
486 slotChangedDomain(defaultDomain);
487 loginEdit->clear();
488 loginEdit->setFocus();
489 curUser.clear();
490 } else {
491 passwdEdit->setFocus();
492 }
493}
494
495
496// private
497
498void
499KWinbindGreeter::setActive(bool enable)
500{
501 if (domainCombo)
502 domainCombo->setEnabled(enable);
503 if (loginEdit)
504 loginEdit->setEnabled(enable);
505 if (passwdEdit)
506 passwdEdit->setEnabled(enable);
507}
508
509void
510KWinbindGreeter::setActive2(bool enable)
511{
512 if (passwd1Edit) {
513 passwd1Edit->setEnabled(enable);
514 passwd2Edit->setEnabled(enable);
515 }
516}
517
518void
519KWinbindGreeter::slotLoginLostFocus()
520{
521 if (!running)
522 return;
523 QString ent(getEntity());
524 if (exp > 0) {
525 if (curUser == ent)
526 return;
527 exp = -1;
528 handler->gplugReturnText(0, 0);
529 }
530 curUser = ent;
531 handler->gplugSetUser(curUser);
532}
533
534void
535KWinbindGreeter::slotChanged()
536{
537 if (running)
538 handler->gplugChanged();
539}
540
541void
542KWinbindGreeter::slotStartDomainList()
543{
544 m_domainLister = new KProcess(this);
545 (*m_domainLister) << "wbinfo" << "--own-domain" << "--trusted-domains";
546 m_domainLister->setOutputChannelMode(KProcess::OnlyStdoutChannel);
547 connect(m_domainLister, SIGNAL(finished(int,QProcess::ExitStatus)),
548 SLOT(slotEndDomainList()));
549 m_domainLister->start();
550}
551
552void
553KWinbindGreeter::slotEndDomainList()
554{
555 QStringList domainList;
556
557 while (!m_domainLister->atEnd()) {
558 QString dom = m_domainLister->readLine();
559 dom.chop(1);
560 if (!staticDomains.contains(dom))
561 domainList.append(dom);
562 }
563
564 delete m_domainLister;
565
566 for (int i = domainCombo->count(), min = staticDomains.count(); --i >= min;) {
567 int dli = domainList.indexOf(domainCombo->itemText(i));
568 if (dli < 0) {
569 if (i == domainCombo->currentIndex())
570 domainCombo->setCurrentItem(defaultDomain);
571 domainCombo->removeItem(i);
572 } else {
573 domainList.removeAt(dli);
574 }
575 }
576 domainCombo->addItems(domainList);
577
578 QTimer::singleShot(5 * 1000, this, SLOT(slotStartDomainList()));
579}
580
581// factory
582
583static bool init(const QString &,
584 QVariant(*getConf)(void *, const char *, const QVariant &),
585 void *ctx)
586{
587 echoMode = getConf(ctx, "EchoPasswd", QVariant(-1)).toInt();
588
589 staticDomains = getConf(ctx, "winbind.Domains", QVariant("")).toString().split(':', QString::SkipEmptyParts);
590 if (!staticDomains.size())
591 staticDomains << "<local>";
592 defaultDomain = getConf(ctx, "winbind.DefaultDomain", QVariant(staticDomains.first())).toString();
593 if (!defaultDomain.isEmpty() && !staticDomains.contains(defaultDomain))
594 staticDomains.prepend(defaultDomain);
595 QString sepstr = getConf(ctx, "winbind.Separator", QVariant(QString())).toString();
596 if (sepstr.isNull()) {
597 FILE *sepfile = popen("wbinfo --separator 2>/dev/null", "r");
598 if (sepfile) {
599 QTextStream(sepfile) >> sepstr;
600 if (pclose(sepfile))
601 sepstr = "\\";
602 } else {
603 sepstr = "\\";
604 }
605 }
606 separator = sepstr[0].toLatin1();
607
608 KGlobal::locale()->insertCatalog("kgreet_winbind");
609 return true;
610}
611
612static void done(void)
613{
614 KGlobal::locale()->removeCatalog("kgreet_winbind");
615 // avoid static deletion problems ... hopefully
616 staticDomains.clear();
617 defaultDomain.clear();
618}
619
620static KGreeterPlugin *
621create(KGreeterPluginHandler *handler,
622 QWidget *parent,
623 const QString &fixedEntity,
624 KGreeterPlugin::Function func,
625 KGreeterPlugin::Context ctx)
626{
627 return new KWinbindGreeter(handler, parent, fixedEntity, func, ctx);
628}
629
630KDE_EXPORT KGreeterPluginInfo kgreeterplugin_info = {
631 I18N_NOOP2("@item:inmenu authentication method", "Winbind / Samba"), "classic",
632 KGreeterPluginInfo::Local | KGreeterPluginInfo::Fielded | KGreeterPluginInfo::Presettable,
633 init, done, create
634};
635
636#include "kgreet_winbind.moc"
637