1 | /* |
2 | |
3 | Conversation widget for kdm greeter |
4 | |
5 | Copyright (C) 1997, 1998, 2000 Steffen Hansen <hansen@kde.org> |
6 | Copyright (C) 2000-2004 Oswald Buddenhagen <ossi@kde.org> |
7 | |
8 | |
9 | This program is free software; you can redistribute it and/or modify |
10 | it under the terms of the GNU General Public License as published by |
11 | the Free Software Foundation; either version 2 of the License, or |
12 | (at your option) any later version. |
13 | |
14 | This program is distributed in the hope that it will be useful, |
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | GNU General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU General Public License |
20 | along with this program; if not, write to the Free Software |
21 | Foundation, 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 | |
45 | static int echoMode; |
46 | |
47 | class KDMPasswordEdit : public KLineEdit { |
48 | public: |
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 | |
59 | static char separator; |
60 | static QStringList staticDomains; |
61 | static QString defaultDomain; |
62 | |
63 | static void |
64 | splitEntity(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 | |
73 | KWinbindGreeter::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 |
191 | KWinbindGreeter::~KWinbindGreeter() |
192 | { |
193 | abort(); |
194 | qDeleteAll(widgetList); |
195 | } |
196 | |
197 | void |
198 | KWinbindGreeter::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 | |
216 | void // virtual |
217 | KWinbindGreeter::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 | |
227 | void // virtual |
228 | KWinbindGreeter::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 | |
249 | QString // virtual |
250 | KWinbindGreeter::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 | |
264 | void // virtual |
265 | KWinbindGreeter::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 | |
278 | void // virtual |
279 | KWinbindGreeter::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 | |
293 | void // private |
294 | KWinbindGreeter::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 | |
318 | bool // virtual |
319 | KWinbindGreeter::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 | |
327 | void // virtual |
328 | KWinbindGreeter::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 | |
364 | bool // virtual |
365 | KWinbindGreeter::binaryPrompt(const char *, bool) |
366 | { |
367 | // this simply cannot happen ... :} |
368 | return true; |
369 | } |
370 | |
371 | void // virtual |
372 | KWinbindGreeter::start() |
373 | { |
374 | authTok = !(passwdEdit && passwdEdit->isEnabled()); |
375 | exp = has = -1; |
376 | running = true; |
377 | } |
378 | |
379 | void // virtual |
380 | KWinbindGreeter::suspend() |
381 | { |
382 | } |
383 | |
384 | void // virtual |
385 | KWinbindGreeter::resume() |
386 | { |
387 | } |
388 | |
389 | void // virtual |
390 | KWinbindGreeter::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 | |
419 | void // virtual |
420 | KWinbindGreeter::abort() |
421 | { |
422 | running = false; |
423 | if (exp >= 0) { |
424 | exp = -1; |
425 | handler->gplugReturnText(0, 0); |
426 | } |
427 | } |
428 | |
429 | void // virtual |
430 | KWinbindGreeter::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 | |
446 | void // virtual |
447 | KWinbindGreeter::failed() |
448 | { |
449 | // assert(running || timed_login); |
450 | setActive(false); |
451 | setActive2(false); |
452 | exp = -1; |
453 | running = false; |
454 | } |
455 | |
456 | void // virtual |
457 | KWinbindGreeter::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 | |
479 | void // virtual |
480 | KWinbindGreeter::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 | |
498 | void |
499 | KWinbindGreeter::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 | |
509 | void |
510 | KWinbindGreeter::setActive2(bool enable) |
511 | { |
512 | if (passwd1Edit) { |
513 | passwd1Edit->setEnabled(enable); |
514 | passwd2Edit->setEnabled(enable); |
515 | } |
516 | } |
517 | |
518 | void |
519 | KWinbindGreeter::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 | |
534 | void |
535 | KWinbindGreeter::slotChanged() |
536 | { |
537 | if (running) |
538 | handler->gplugChanged(); |
539 | } |
540 | |
541 | void |
542 | KWinbindGreeter::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 | |
552 | void |
553 | KWinbindGreeter::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 | |
583 | static 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 | |
612 | static void done(void) |
613 | { |
614 | KGlobal::locale()->removeCatalog("kgreet_winbind" ); |
615 | // avoid static deletion problems ... hopefully |
616 | staticDomains.clear(); |
617 | defaultDomain.clear(); |
618 | } |
619 | |
620 | static KGreeterPlugin * |
621 | create(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 | |
630 | KDE_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 | |