/*

Shell for kdm conversation plugins

Copyright (C) 1997, 1998, 2000 Steffen Hansen <hansen@kde.org>
Copyright (C) 2000-2004 Oswald Buddenhagen <ossi@kde.org>


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.

*/

#include <config-workspace.h>

#include "kgverify.h"
#include "kdmconfig.h"
#include "kdm_greet.h"

#include "themer/kdmthemer.h"
#include "themer/kdmitem.h"

//#include <KColorScheme>
//#include <kguiitem.h>
#include <QLibrary> //<klibrary.h>
//#include <klocale.h>
#include <QPushButton> //<kpushbutton.h>
//#include <krandom.h>
#include <QFrame> //<kseparator.h>
//#include <KStandardGuiItem>

#include <QAction>
#include <QApplication>
#include <QEvent>
#include <QKeyEvent>
#include <QLabel>
#include <QMenu>
#include <QSocketNotifier>
#include <QX11Info>

#include <X11/Xlib.h> // for updateLockStatus()
#include <fixx11h.h> // ... and make eventFilter() work again

#define FULL_GREET_TO 40 // normal inactivity timeout
#define TIMED_GREET_TO 20 // inactivity timeout when persisting timed login
#define MIN_TIMED_TO 5 // minimal timed login delay
#define DEAD_TIMED_TO 2 // <enter> dead time after re-activating timed login
#define SECONDS 1000 // reduce to 100 to speed up testing

void KGVerifyHandler::verifyClear()
{
}

void KGVerifyHandler::updateStatus(bool, bool, int)
{
}

KGVerify::KGVerify(KGVerifyHandler *_handler,
                   QWidget *_parent, QWidget *_predecessor,
                   const QString &_fixedUser,
                   const PluginList &_pluginList,
                   KGreeterPlugin::Function _func,
                   KGreeterPlugin::Context _ctx)
    : inherited()
    , coreState(CoreIdle)
    , fixedEntity(_fixedUser)
    , pluginList(_pluginList)
    , handler(_handler)
    , parent(_parent)
    , predecessor(_predecessor)
    , plugMenu(nullptr)
    , curPlugin(-1)
    , timedLeft(0)
    , func(_func)
    , ctx(_ctx)
    , enabled(true)
    , running(false)
    , suspended(false)
    , failed(false)
    , isClear(true)
    , greet(nullptr)
{
    sockNot = new QSocketNotifier(rfd, QSocketNotifier::Read, this);
    sockNot->setEnabled(false);
    connect(sockNot, SIGNAL(activated(int)), SLOT(handleVerify()));

    connect(&timer, SIGNAL(timeout()), SLOT(slotTimeout()));
    connect(qApp, SIGNAL(activity()), SLOT(slotActivity()));

    _parent->installEventFilter(this);
}

KGVerify::~KGVerify()
{
    debug("delete %s\n", pName.data());
    if (greet) delete greet;
}

QMenu *
KGVerify::getPlugMenu()
{
    // assert(coreState != CoreBusy);
    if (!plugMenu) {
        uint np = pluginList.count();
        if (np > 1) {
            plugMenu = new QMenu(parent);
            QActionGroup *plugGroup = new QActionGroup(parent);
            connect(plugMenu, SIGNAL(triggered(QAction*)),
                    SLOT(slotPluginSelected(QAction*)));
            for (uint i = 0; i < np; i++) {
                int pid = pluginList[i];
                greetPlugins[pid].action = plugGroup->addAction(
                    /*i18nc("@item:inmenu authentication method",*/
                          QString::fromUtf8(greetPlugins[pid].info->name));
                greetPlugins[pid].action->setData(i);
                greetPlugins[pid].action->setCheckable(true);
            }
            plugMenu->addActions(plugGroup->actions());
        }
    }
    return plugMenu;
}

bool // public
KGVerify::entitiesLocal() const
{
    return greetPlugins[pluginList[curPlugin]].info->flags & KGreeterPluginInfo::Local;
}

bool // public
KGVerify::entitiesFielded() const
{
    return greetPlugins[pluginList[curPlugin]].info->flags & KGreeterPluginInfo::Fielded;
}

bool // public
KGVerify::entityPresettable() const
{
    return greetPlugins[pluginList[curPlugin]].info->flags & KGreeterPluginInfo::Presettable;
}

bool // public
KGVerify::isClassic() const
{
    return !strcmp(greetPlugins[pluginList[curPlugin]].info->method, "classic");
}

QString // public
KGVerify::pluginName() const
{
    QString name(greetPlugins[pluginList[curPlugin]].library->fileName());
    uint st = name.lastIndexOf('/') + 1;
    uint en = name.indexOf('.', st);
    if (en - st > 7 && QString::fromRawData(name.unicode() + st, 7) == QLatin1String("kgreet_"))
        st += 7;
    return name.mid(st, en - st);
}

static void
setTabOrder(QWidget *&pred, const QObjectList &list)
{
    foreach (QObject *o, list)
        if (QWidget *w = qobject_cast<QWidget *>(o)) {
            if (w->focusPolicy() & Qt::TabFocus) {
                QWidget::setTabOrder(pred, w);
                pred = w;
            } else {
                setTabOrder(pred, o->children());
            }
        }
}

void // public
KGVerify::selectPlugin(int id)
{
    if (pluginList.isEmpty()) {
        msgBox(errorbox, tr("No greeter widget plugin loaded. Check the configuration."));
        ::exit(EX_UNMANAGE_DPY);
    }
    curPlugin = id;
    if (plugMenu)
        greetPlugins[pluginList[id]].action->setChecked(true);
    pName = ("greet_" + pluginName()).toLatin1();
    debug("new %s\n", pName.data());
    greet = greetPlugins[pluginList[id]].info->create(this, parent, fixedEntity, func, ctx);
    if (QWidget *pred = predecessor)
        setTabOrder(pred, *(const QObjectList *)&greet->getWidgets());
    timeable = _autoLoginDelay && entityPresettable() && isClassic();
}

void // private slot
KGVerify::slotPluginSelected(QAction *action)
{
    if (failed)
        return;
    int id = action->data().toInt();
    if (id != curPlugin) {
        parent->setUpdatesEnabled(false);
        debug("delete %s\n", pName.data());
        if (greet) delete greet;
        greet = nullptr;
        selectPlugin(id);
        handler->verifyPluginChanged(id);
        if (running)
            start();
        parent->setUpdatesEnabled(true);
    }
}

void // public
KGVerify::loadUsers(const QStringList &users)
{
    debug("%s->loadUsers(...)\n", pName.data());
    greet->loadUsers(users);
}

void // public
KGVerify::presetEntity(const QString &entity, int field)
{
    presEnt = entity;
    presFld = field;
}

bool // private
KGVerify::applyPreset()
{
    if (!presEnt.isEmpty()) {
        debug("%s->presetEntity(%\"s, %d)\n", pName.data(),
              qPrintable(presEnt), presFld);
        greet->presetEntity(presEnt, presFld);
        if (entitiesLocal()) {
            curUser = presEnt;
            pamUser.clear();
            handler->verifySetUser(presEnt);
        }
        return true;
    }
    return false;
}

bool // private
KGVerify::scheduleAutoLogin(bool initial)
{
    if (timeable) {
        debug("%s->presetEntity(%\"s, -1)\n", pName.data(),
              qPrintable(_autoLoginUser), -1);
        greet->presetEntity(_autoLoginUser, -1);
        curUser = _autoLoginUser;
        handler->verifySetUser(_autoLoginUser);
        timer.start(1000);
        if (initial) {
            timedLeft = _autoLoginDelay;
            deadTicks = 0;
        } else {
            timedLeft = qMax(_autoLoginDelay - TIMED_GREET_TO, MIN_TIMED_TO);
            deadTicks = DEAD_TIMED_TO;
        }
        updateStatus();
        sockNot->setEnabled(false);
        running = false;
        isClear = true;
        return true;
    }
    return false;
}

void // private
KGVerify::performAutoLogin()
{
//    timer.stop();
    gSendInt(G_AutoLogin);
    coreState = CoreBusy;
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); //alex
    sockNot->setEnabled(true);
}

QString // public
KGVerify::getEntity() const
{
    debug("%s->getEntity()\n", pName.data());
    QString ent = greet->getEntity();
    debug("  entity: %s\n", qPrintable(ent));
    return ent;
}

void
KGVerify::setUser(const QString &user)
{
    // assert(fixedEntity.isEmpty());
    curUser = user;
    debug("%s->setUser(%\"s)\n", pName.data(), qPrintable(user));
    greet->setUser(user);
    talkerEdits();
}

void
KGVerify::start()
{
    authTok = (func == KGreeterPlugin::ChAuthTok);
    if (func == KGreeterPlugin::Authenticate && ctx == KGreeterPlugin::Login) {
        if (scheduleAutoLogin(true)) {
            if (!_autoLoginAgain)
                _autoLoginDelay = 0, timeable = false;
            return;
        } else {
            applyPreset();
        }
        if (_isReserve)
            timer.start(FULL_GREET_TO * SECONDS);
    }
    sockNot->setEnabled(true);
    running = true;
    if (!(func == KGreeterPlugin::Authenticate ||
          ctx == KGreeterPlugin::ChangeTok ||
          ctx == KGreeterPlugin::ExChangeTok))
    {
        coreState = CoreBusy;
        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); //alex
    }
    debug("%s->start()\n", pName.data());
    greet->start();
}

void
KGVerify::abort()
{
    debug("%s->abort()\n", pName.data());
    greet->abort();
    sockNot->setEnabled(false);
    running = false;
}

void
KGVerify::suspend()
{
    // assert(coreState != CoreBusy);
    if (running) {
        debug("%s->abort()\n", pName.data());
        greet->abort();
    }
    suspended = true;
    updateStatus();
    timer.suspend();
    sockNot->setEnabled(false);
}

void
KGVerify::resume()
{
    sockNot->setEnabled(true);
    timer.resume();
    suspended = false;
    updateLockStatus();
    if (running) {
        debug("%s->start()\n", pName.data());
        greet->start();
    } else if (delayed) {
        delayed = false;
        running = true;
        sockNot->setEnabled(true);
        debug("%s->start()\n", pName.data());
        greet->start();
    }
}

void // not a slot - called manually by greeter
KGVerify::accept()
{
    debug("%s->next()\n", pName.data());
    greet->next();
}

void // private
KGVerify::doReject(bool initial)
{
    // assert(coreState != CoreBusy);
    if (running) {
        debug("%s->abort()\n", pName.data());
        greet->abort();
    }
    handler->verifyClear();
    debug("%s->clear()\n", pName.data());
    greet->clear();
    curUser.clear();
    pamUser.clear();
    if (!scheduleAutoLogin(initial)) {
        isClear = !(isClear && applyPreset());
        if (running) {
            debug("%s->start()\n", pName.data());
            greet->start();
        }
        if (!failed)
            timer.stop();
    }
}

void // not a slot - called manually by greeter
KGVerify::reject()
{
    doReject(true);
}

void
KGVerify::setEnabled(bool on)
{
    debug("%s->setEnabled(%s)\n", pName.data(), on ? "true" : "false");
    greet->setEnabled(on);
    enabled = on;
    updateStatus();
}

void // private
KGVerify::slotTimeout()
{
    if (failed) {
        failed = false;
        updateStatus();
        debug("%s->revive()\n", pName.data());
        greet->revive();
        if (parent) parent->activateWindow(); //alex
        handler->verifyRetry();
        if (suspended) {
            delayed = true;
        } else {
            running = true;
            sockNot->setEnabled(true);
            debug("%s->start()\n", pName.data());
            greet->start();
            slotActivity();
            talkerEdits();
        }
    } else if (timedLeft) {
        deadTicks--;
        if (!--timedLeft)
            performAutoLogin();
        else
            timer.start(1000);
        updateStatus();
    } else if (_isReserve) {
        // assert(ctx == Login && running);
        abort();
        ::exit(EX_RESERVE);
    } else {
        // assert(ctx == Login);
        isClear = true;
        doReject(false);
    }
}

void
KGVerify::slotActivity()
{
    if (timedLeft) {
        // timed login countdown running. cancel and reschedule it.
        debug("%s->revive()\n", pName.data());
        greet->revive();
        if (parent) parent->activateWindow(); //alex
        debug("%s->start()\n", pName.data());
        greet->start();
        sockNot->setEnabled(true);
        running = true;
        timedLeft = 0;
        updateStatus();
        timer.start(TIMED_GREET_TO * SECONDS);
    } else if (timeable) {
        // timed login is possible and thus scheduled. reschedule it.
        timer.start(TIMED_GREET_TO * SECONDS);
    } else if (_isReserve) {
        timer.start(FULL_GREET_TO * SECONDS);
    }
}

void
KGVerify::talkerEdits()
{
    if (func == KGreeterPlugin::Authenticate &&
        ctx == KGreeterPlugin::Login)
    {
        isClear = false;
        if (!timeable)
            timer.start(FULL_GREET_TO * SECONDS);
    }
}


void // private static
KGVerify::vrfMsgBox(QWidget *parent, const QString &user,
                    QMessageBox::Icon type, const QString &mesg)
{
    KFMsgBox::box(parent, type, user.isEmpty() ?
                  mesg : tr("Logging in %1...\n\n").arg(user) + mesg);
}

void // private static
KGVerify::vrfErrBox(QWidget *parent, const QString &user, const char *msg)
{
    QMessageBox::Icon icon;
    QString mesg;
    bool done=false;//alex

    static const char * const msgs[]= {
        "You are required to change your password immediately (password aged).",
        "You are required to change your password immediately (root enforced).",
        "You are not allowed to login at the moment.",
        "Home folder not available.",
        "Logins are not allowed at the moment.\nTry again later.",
        "Your login shell is not listed in /etc/shells.",
        "Root logins are not allowed.",
        "Your account has expired; please contact your system administrator.",
        "Can't create X11 auth file.\nCheck free space and-or rights.",
        "Cannot enter home directory.\nTemporal directory will be used.",
        "Password blocked",
    };
    QString msgsTr[]= {
        tr("You are required to change your password immediately (password aged)."),
        tr("You are required to change your password immediately (root enforced)."),
        tr("You are not allowed to login at the moment."),
        tr("Home folder not available."),
        tr("Logins are not allowed at the moment.\nTry again later."),
        tr("Your login shell is not listed in /etc/shells."),
        tr("Root logins are not allowed."),
        tr("Your account has expired; please contact your system administrator."),
        tr("Can't create X11 auth file.\nCheck free space and-or rights."),
        tr("Cannot enter home directory.\nTemporal directory will be used."),
        tr("Password blocked"),
    };

    static const char * zamokMsg[] = { //alex
					"AUM wasn't connected.",
					"Short circuit on 1-wire bus.",
					"No link with AUM reader.",
					"Wrong medium type connected to reader.",
					"Wrong CRC for medium data.",
					"Connected iButton is NOT valid AUM.",
					"AUM is not registered in device.",
					"AUM is valid CDM.",
					"AUM has been replaced by other one.",
					"AUM read/write failed.",
					"User autologon was disabled.",
					"Wrong device mode.",
					"Authentication required.",
					"Failed to allocate charset descriptor.",
					"Error in converting characters.",
					"Auth error",
					"Locale not defined. Set locale with \"lc=\" param. UTF8 will be used.",
					"Internal pam error",
					"Auth error." };
    QString zamokMsgTr [] = {
					tr("AUM wasn't connected."),
					tr("Short circuit on 1-wire bus."),
					tr("No link with AUM reader."),
					tr("Wrong medium type connected to reader."),
					tr("Wrong CRC for medium data."),
					tr("Connected iButton is NOT valid AUM."),
					tr("AUM is not registered in device."),
					tr("AUM is valid CDM."),
					tr("AUM has been replaced by other one."),
					tr("AUM read/write failed."),
					tr("User autologon was disabled."),
					tr("Wrong device mode."),
					tr("Authentication required."),
					tr("Failed to allocate charset descriptor."),
					tr("Error in converting characters."),
					tr("Auth error"),
					tr("Locale not defined. Set locale with \"lc=\" param. UTF8 will be used."),
					tr("Internal pam error"),
					tr("Auth error.") };

    if (!msg) {
        mesg = tr("A critical error occurred.\n"
                    "Please look at DM's logfile(s) for more information\n"
                    "or contact your system administrator.");
        icon = errorbox;
        done=true;//alex
    } else {
        mesg = QString::fromLocal8Bit(msg);
        QString mesg1 = mesg + '.';
        for (uint i = 0; i < as(msgs); i++) {
            const QString msgi = /*i18n*/QString::fromLocal8Bit(msgs[i]);
            if (mesg == msgi || mesg1 == msgi) {
                mesg = msgsTr[i]; //msgi;
                done=true; //alex
                break;
            }
        }
        icon = sorrybox;
    }
	
    for (int i=0;!done && msg && i<as(zamokMsg);i++) //alex
    {
       if (strstr(msg,zamokMsg[i])) { mesg = zamokMsgTr[i]; done=true; break; }
    }
	
    if (!done) //alex
    {
	    QRegExp rx;
	    rx.setPattern( "^Warning: Cannot open ConsoleKit session" );
	    if (rx.indexIn(mesg)>= 0) mesg=mesg.replace("Warning: Cannot open ConsoleKit session",tr("Warning: Cannot open ConsoleKit session"));
	    else
	    {
	    //to translate more
	    }
    }
	
    vrfMsgBox(parent, user, icon, mesg);
}

void // private static
KGVerify::vrfInfoBox(QWidget *parent, const QString &user, const char *msg)
{
    QString mesg = QString::fromLocal8Bit(msg);
    QRegExp rx("^Warning: your account will expire in (\\d+) day");
    if (rx.indexIn(mesg) >= 0) {
        int expire = rx.cap(1).toInt();
        mesg = expire ?
            /*i18np("Your account expires tomorrow.",*/
                  tr("Your account expires in %1 days.").arg(expire) :
            tr("Your account expires today.");
    } else {
        rx.setPattern("^Warning: your password will expire in (\\d+) day");
        if (rx.indexIn(mesg) >= 0) {
            int expire = rx.cap(1).toInt();
            mesg = expire ?
                /*i18np("Your password expires tomorrow.",*/
                     tr( "Your password expires in %1 days.").arg(expire) :
                tr("Your password expires today.");
        }
        else { /*alex:---------------------*/
			rx.setPattern( "^Password expired.  You must change it now." );
			if (rx.indexIn( mesg ) >= 0) mesg=tr("Password expired. You must change it now.");
			else
			{
				rx.setPattern( "^Enter new password" );
				if (rx.indexIn( mesg ) >= 0) mesg=tr("Enter new password");
				else
				{
					rx.setPattern( "^Enter it again" );
					if (rx.indexIn( mesg ) >= 0) mesg=tr("Enter it again");

					rx.setPattern( "^Password mismatch.  Please try again." );
					if (rx.indexIn( mesg ) >= 0) mesg=tr("Password mismatch. Please try again.");
					else
					{
						rx.setPattern( "^Password change rejected: New password was used previously. Please " );
						if (rx.indexIn( mesg ) >= 0) mesg=tr("Password change rejected: New password was used previously. Please try again.");
						else
						{
							rx.setPattern( "^Sorry, password do not match" );
							if (rx.indexIn( mesg ) >= 0) mesg=tr("Sorry, password do not match");
							else
							{
							  rx.setPattern( "^Bad: new and old password are too similar" );
							  if (rx.indexIn( mesg ) >= 0) mesg=tr("Bad: new and old password are too similar");
							  else
							  {
							    rx.setPattern( "^Bad: new password is too simple" );
							    if (rx.indexIn( mesg ) >= 0) mesg=tr("Bad: new password is too simple");
							    else
							    {
								rx.setPattern( "^Creating directory" );
								if (rx.indexIn( mesg ) >= 0) mesg=mesg.replace("Creating directory",tr("Creating directory"));
								else
								{
								    //to translate more
								}
							    }
							  }
							}
						}
					}
				}
			}
	}
    /*--------------------------------*/
    }
    vrfMsgBox(parent, user, infobox, mesg);
}

bool // public static
KGVerify::handleFailVerify(QWidget *parent, bool showUser)
{
    char *msg;
    QString user;

    debug("handleFailVerify ...\n");

    if (showUser) {
        msg = gRecvStr();
        user = QString::fromLocal8Bit(msg);
        free(msg);
    }

    for (;;) {
        int ret = gRecvInt();

        // non-terminal status
        switch (ret) {
        /* case V_PUT_USER: cannot happen - we are in "classic" mode */
        /* case V_PRE_OK: cannot happen - not in ChTok dialog */
        /* case V_CHTOK: cannot happen - called by non-interactive verify */
        case V_CHTOK_AUTH:
            debug(" V_CHTOK_AUTH\n");
            {
                QStringList pgs(_pluginsLogin);
                pgs += _pluginsShutdown;
                foreach (const QString& pg, pgs)
                    if (pg == "classic" || pg == "modern") {
                        pgs = QStringList(pg);
                        goto gotit;
                    } else if (pg == "generic") {
                        pgs = QStringList("modern");
                        goto gotit;
                    }
                pgs = QStringList("classic");
              gotit:
                KGChTok chtok(parent, user, init(pgs), 0,
                              KGreeterPlugin::AuthChAuthTok,
                              KGreeterPlugin::Login);
                return chtok.exec();
            }
        case V_MSG_ERR:
            debug(" V_MSG_ERR\n");
            msg = gRecvStr();
            debug("  message %\"s\n", msg);
            vrfErrBox(parent, user, msg);
            free(msg);
            gSendInt(0);
            continue;
        case V_MSG_INFO_AUTH: // should not happen
        case V_MSG_INFO:
            debug(" V_MSG_INFO\n");
            msg = gRecvStr();
            debug("  message %\"s\n", msg);
            vrfInfoBox(parent, user, msg);
            free(msg);
            gSendInt(0);
            continue;
        }

        // terminal status
        switch (ret) {
        case V_OK:
            debug(" V_OK\n");
            return true;
        case V_AUTH:
            debug(" V_AUTH\n");
            vrfMsgBox(parent, user, sorrybox, tr("Authentication failed"));
            return false;
        case V_FAIL:
            debug(" V_FAIL\n");
            return false;
        default:
            logPanic("Unknown V_xxx code %d from core\n", ret);
        }
    }
}

void // private
KGVerify::handleVerify()
{
    debug("handleVerify ...\n");
    QString user;
    char *msg;
    int ret, echo, ndelay;
    KGreeterPlugin::Function nfunc;

    ret = gRecvInt();

    // requests
    coreState = CorePrompting;
    QApplication::restoreOverrideCursor(); //alex
    switch (ret) {
    case V_GET_TEXT:
        debug(" V_GET_TEXT\n");
        msg = gRecvStr();
        debug("  prompt %\"s\n", msg);
        echo = gRecvInt();
        debug("  echo = %d\n", echo);
        ndelay = gRecvInt();
        debug("  ndelay = %d\n%s->textPrompt(...)\n", ndelay, pName.data());
        greet->textPrompt(msg, echo, ndelay);
        free(msg);
        return;
    case V_GET_BINARY:
        debug(" V_GET_BINARY\n");
        msg = gRecvArr(&ret);
        debug("  %d bytes prompt\n", ret);
        ndelay = gRecvInt();
        debug("  ndelay = %d\n%s->binaryPrompt(...)\n", ndelay, pName.data());
        greet->binaryPrompt(msg, ndelay);
        free(msg);
        return;
    }

    // non-terminal status
    coreState = CoreBusy;
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); //alex
    switch (ret) {
    case V_PUT_USER:
        debug(" V_PUT_USER\n");
        msg = gRecvStr();
        curUser = pamUser = QString::fromLocal8Bit(msg);
        // greet needs this to be able to return something useful from
        // getEntity(). but the backend is still unable to tell a domain ...
        debug("  %s->setUser(%\"s)\n", pName.data(), qPrintable(user));
        greet->setUser(curUser);
        handler->verifySetUser(curUser);
        free(msg);
        QApplication::restoreOverrideCursor(); //alex
        return;
    case V_PRE_OK: // this is only for func == AuthChAuthTok
        debug(" V_PRE_OK\n");
        // With the "classic" method, the wrong user simply cannot be
        // authenticated, even with the generic plugin. Other methods
        // could do so, but this applies only to ctx == ChangeTok, which
        // is not implemented yet.
        authTok = true;
        debug("%s->succeeded()\n", pName.data());
        greet->succeeded();
        QApplication::restoreOverrideCursor(); //alex
        return;
    case V_MSG_ERR:
        debug(" V_MSG_ERR\n");
        timer.suspend();
        msg = gRecvStr();
        debug("  %s->textMessage(%\"s, true)\n", pName.data(), msg);
        if (!greet->textMessage(msg, true)) { // XXX little point in filtering
            debug("  message passed\n");
            vrfErrBox(parent, pamUser, msg);
        } else {
            debug("  message swallowed\n");
        }
        free(msg);
        gSendInt(0);
        timer.resume();
        QApplication::restoreOverrideCursor(); //alex
        return;
    case V_MSG_INFO_AUTH:
        debug(" V_MSG_INFO_AUTH\n");
        timer.suspend();
        msg = gRecvStr();
        debug("  %s->textMessage(%\"s, false)\n", pName.data(), msg);
        if (!greet->textMessage(msg, false)) {
            debug("  message passed\n");
            vrfInfoBox(parent, pamUser, msg);
        } else {
            debug("  message swallowed\n");
        }
        free(msg);
        gSendInt(0);
        timer.resume();
        QApplication::restoreOverrideCursor(); //alex
        return;
    case V_MSG_INFO:
        debug(" V_MSG_INFO\n");
        timer.suspend();
        msg = gRecvStr();
        debug("  display %\"s\n", msg);
        vrfInfoBox(parent, pamUser, msg);
        free(msg);
        gSendInt(0);
        timer.resume();
        QApplication::restoreOverrideCursor(); //alex
        return;
    }

    // terminal status
    coreState = CoreIdle;
    QApplication::restoreOverrideCursor(); //alex
    running = false;
    sockNot->setEnabled(false);
    timer.stop();

    // These codes are not really terminal as far as the core is concerned,
    // but the branches as a whole are.
    if (ret == V_CHTOK_AUTH) {
        debug(" V_CHTOK_AUTH\n");
        nfunc = KGreeterPlugin::AuthChAuthTok;
        user = curUser;
        goto dchtok;
    } else if (ret == V_CHTOK) {
        debug(" V_CHTOK\n");
        nfunc = KGreeterPlugin::ChAuthTok;
        user.clear();
      dchtok:
        debug("%s->succeeded()\n", pName.data());
        greet->succeeded();
        KGChTok chtok(parent, user, pluginList, curPlugin, nfunc, KGreeterPlugin::Login);
        if (!chtok.exec()) {
            //pam will ask password again and again in spite of cancel, so restart completely
            ::exit(EX_RESERVER_DPY);
            goto retry;
        }
        handler->verifyOk();
        return;
    }

    if (ret == V_OK) {
        debug(" V_OK\n");
        if (!fixedEntity.isEmpty()) {
            debug("  %s->getEntity()\n", pName.data());
            QString ent = greet->getEntity();
            debug("  entity %\"s\n", qPrintable(ent));
            if (ent != fixedEntity) {
                debug("%s->failed()\n", pName.data());
                greet->failed();
                msgBox(sorrybox,
                       tr("Authenticated user (%1) does not match requested user (%2).\n").arg(
                            ent).arg( fixedEntity));
                goto retry;
            }
        }
        debug("%s->succeeded()\n", pName.data());
        greet->succeeded();
        handler->verifyOk();
        return;
    }

    debug("%s->failed()\n", pName.data());
    greet->failed();

    if (ret == V_AUTH) {
        debug(" V_AUTH\n");
        failed = true;
        updateStatus();
        handler->verifyFailed();
        timer.start(1500 + /*KRandom::random()*/rand()/(RAND_MAX/1000));
        return;
    }
    if (ret != V_FAIL)
        logPanic("Unknown V_xxx code %d from core\n", ret);
    debug(" V_FAIL\n");
  retry:
    debug("%s->revive()\n", pName.data());
    greet->revive();
    if (parent) parent->activateWindow(); //alex
    sockNot->setEnabled(true);
    running = true;
    debug("%s->start()\n", pName.data());
    greet->start();
    slotActivity();
    talkerEdits();
}

void
KGVerify::gplugReturnText(const char *text, int tag)
{
    if (coreState == CoreBusy) //alex: to test with zamok and without
    {
      debug("%s: gplugReturnText(%\"s, %d) - won't be sent due to NON IDLE coreState=%s\n", pName.data(),
          tag & V_IS_SECRET ? "<masked>" : text, tag,coreState==CorePrompting?"CorePrompting":"CoreBusy");
      return;
    }
    debug("%s: gplugReturnText(%\"s, %d)\n", pName.data(),
          tag & V_IS_SECRET ? "<masked>" : text, tag);
    gSendStr(text);
    if (text) {
        gSendInt(tag);
        coreState = CoreBusy;
        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); //alex
    } else {
        coreState = CoreIdle;
        QApplication::restoreOverrideCursor(); //alex
    }
}

void
KGVerify::gplugReturnBinary(const char *data)
{
    if (data) {
        unsigned const char *up = (unsigned const char *)data;
        int len = up[3] | (up[2] << 8) | (up[1] << 16) | (up[0] << 24);
        debug("%s: gplugReturnBinary(%d bytes)\n", pName.data(), len);
        gSendArr(len, data);
        coreState = CoreBusy;
        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); //alex
    } else {
        debug("%s: gplugReturnBinary(NULL)\n", pName.data());
        gSendArr(0, nullptr);
        coreState = CoreIdle;
        QApplication::restoreOverrideCursor(); //alex
    }
}

void
KGVerify::gplugSetUser(const QString &user)
{
    debug("%s: gplugSetUser(%\"s)\n", pName.data(), qPrintable(user));
    curUser = user;
    handler->verifySetUser(user);
    debug("%s: gplugSetUser(%\"s) done\n", pName.data(), qPrintable(user));
}

void
KGVerify::gplugStart()
{
    // XXX handle func != Authenticate
    if (coreState != CoreIdle)
        return;
    debug("%s: gplugStart()\n", pName.data());
    gSendInt(ctx == KGreeterPlugin::Shutdown ? G_VerifyRootOK : G_Verify);
    gSendStr(greetPlugins[pluginList[curPlugin]].info->method);
    coreState = CoreBusy;
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); //alex
}

void
KGVerify::gplugChanged()
{
    debug("%s: gplugChanged()\n", pName.data());
    if (parent->isActiveWindow())
        talkerEdits();
}

void
KGVerify::gplugActivity()
{
    debug("%s: gplugActivity()\n", pName.data());
    slotActivity();
}

void
KGVerify::gplugMsgBox(QMessageBox::Icon type, const QString &text)
{
    debug("%s: gplugMsgBox(%d, %\"s)\n", pName.data(), type, qPrintable(text));
    msgBox(type, text);
}

bool
KGVerify::eventFilter(QObject *o, QEvent *e)
{
    switch (e->type()) {
    case QEvent::KeyPress:
        if (timedLeft) {
            QKeyEvent *ke = (QKeyEvent *)e;
            if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
                if (deadTicks <= 0) {
                    timedLeft = 0;
                    performAutoLogin();
                }
                return true;
            }
        }
        /* fall through */
    case QEvent::KeyRelease:
        updateLockStatus();
        /* fall through */
    default:
        break;
    }
    return inherited::eventFilter(o, e);
}

void
KGVerify::updateLockStatus()
{
    unsigned int lmask;
    Window dummy1, dummy2;
    int dummy3, dummy4, dummy5, dummy6;
    XQueryPointer(QX11Info::display(), DefaultRootWindow(QX11Info::display()),
                  &dummy1, &dummy2, &dummy3, &dummy4, &dummy5, &dummy6,
                  &lmask);
    capsLocked = lmask & LockMask;
    updateStatus();
}

void
KGVerify::msgBox(QMessageBox::Icon typ, const QString &msg)
{
    timer.suspend();
    KFMsgBox::box(parent, typ, msg);
    timer.resume();
}


QVariant // public static
KGVerify::getConf(void *, const char *key, const QVariant &dflt)
{
    if (!qstrcmp(key, "EchoPasswd")) {
        return QVariant(_echoPasswd);
    } else {
        QString fkey = QString::fromLatin1(key) + '=';
        foreach (const QString& pgo, _pluginOptions)
            if (pgo.startsWith(fkey))
                return pgo.mid(fkey.length());
        return dflt;
    }
}

QVector<GreeterPluginHandle> KGVerify::greetPlugins;

PluginList
KGVerify::init(const QStringList &plugins)
{
    PluginList pluginList;

    foreach (const QString& pg, plugins) {
        GreeterPluginHandle plugin;
        QLibrary *lib = new QLibrary(pg[0] == '/' ? pg : "fly-dmgreet_" + pg);
        if (lib->fileName().isEmpty()) {
            logError("GreeterPlugin %s does not exist\n", qPrintable(pg));
            delete lib;
            continue;
        }
        uint i, np = greetPlugins.count();
        for (i = 0; i < np; i++)
            if (greetPlugins[i].library->fileName() == lib->fileName()) {
                delete lib;
                goto next;
            }
        if (!lib->load()) {
            logError("Cannot load GreeterPlugin %s (%s)\n",
                     qPrintable(pg), qPrintable(lib->fileName()));
            delete lib;
            continue;
        }
        plugin.library = lib;
        plugin.info = (KGreeterPluginInfo *)lib->resolve/*Symbol*/("kgreeterplugin_info");
        if (!plugin.info) {
            logError("GreeterPlugin %s (%s) is no valid greet widget plugin\n",
                     qPrintable(pg), qPrintable(lib->fileName()));
            lib->unload();
            delete lib;
            continue;
        }

        if (!plugin.info->init(QString(), getConf, nullptr)) {
            logError("GreeterPlugin %s (%s) refuses to serve\n",
                     qPrintable(pg), qPrintable(lib->fileName()));
            lib->unload();
            delete lib;
            continue;
        }
        debug("GreeterPlugin %s (%s) loaded\n", qPrintable(pg), plugin.info->name);
        plugin.action = nullptr;
        greetPlugins.append(plugin);
      next:
        pluginList.append(i);
    }
    return pluginList;
}

void
KGVerify::done()
{
    for (int i = 0; i < greetPlugins.count(); i++) {
        if (greetPlugins[i].info->done)
            greetPlugins[i].info->done();
        greetPlugins[i].library->unload();
    }
}


KGStdVerify::KGStdVerify(KGVerifyHandler *_handler, QWidget *_parent,
                         QWidget *_predecessor, const QString &_fixedUser,
                         const PluginList &_pluginList,
                         KGreeterPlugin::Function _func,
                         KGreeterPlugin::Context _ctx)
    : inherited(_handler, _parent, _predecessor, _fixedUser,
                _pluginList, _func, _ctx)
    , failedLabelState(0)
{
    grid = new QGridLayout;
    grid->setAlignment(Qt::AlignCenter);

    failedLabel = new QLabel(parent);
    failedLabel->setFont(*_failFont);
    grid->addWidget(failedLabel, 1, 0, Qt::AlignCenter);

    updateLockStatus();
}

KGStdVerify::~KGStdVerify()
{
}

bool
KGStdVerify::gplugHasNode(const QString &)
{
    return false;
}

void // public
KGStdVerify::selectPlugin(int id)
{
    inherited::selectPlugin(id);
    QWidget *w = greet->getWidgets().first();
    grid->addWidget(w, 0, 0);
    w->show();
}

void
KGStdVerify::updateStatus()
{
    int nfls;

    if (!enabled)
        nfls = 1;
    else if (failed)
        nfls = 2;
    else if (timedLeft)
        nfls = -timedLeft;
    else if (!suspended && capsLocked)
        nfls = 3;
    else
        nfls = 1;

    if (failedLabelState != nfls) {
        failedLabelState = nfls;
        QPalette p;
        if (nfls < 0) {
            failedLabel->setText(/*i18np("Automatic login in 1 second...",*/
                                       tr("Automatic login in %1 seconds...").arg(
                                       timedLeft));
        } else {
            switch (nfls) {
            default:
                failedLabel->clear();
                break;
            case 3:
                /*p.setBrush( QPalette::WindowText, alex: TODO
                    KColorScheme(QPalette::Active, KColorScheme::Window)
                        .foreground(KColorScheme::NegativeText));*/
                failedLabel->setText(tr("Warning: Caps Lock is on"));
                break;
            case 2:
                failedLabel->setText(authTok ?
                                         tr("Change failed") :
                                         fixedEntity.isEmpty() ?
                                            tr("Login failed") :
                                            tr("Authentication failed"));
                break;
            }
        }
        failedLabel->setPalette(p);
    }
}

KGThemedVerify::KGThemedVerify(KGVerifyHandler *_handler,
                               KdmThemer *_themer,
                               QWidget *_parent, QWidget *_predecessor,
                               const QString &_fixedUser,
                               const PluginList &_pluginList,
                               KGreeterPlugin::Function _func,
                               KGreeterPlugin::Context _ctx)
    : inherited(_handler, _parent, _predecessor, _fixedUser,
                _pluginList, _func, _ctx)
    , themer(_themer)
{
    updateLockStatus();
}

KGThemedVerify::~KGThemedVerify()
{
}

bool
KGThemedVerify::gplugHasNode(const QString &id)
{
    return themer->findNode(id) != nullptr;
}

void // public
KGThemedVerify::selectPlugin(int id)
{
    if (curPlugin != -1)
        themer->setTypeVisible(QString("plugin-specific-").append(pluginName()), false);
    inherited::selectPlugin(id);
    themer->setTypeVisible(QString("plugin-specific-").append(pluginName()), true);
    QSet<QString> oldTypes = showTypes;
    showTypes.clear();
    foreach (QWidget *w, greet->getWidgets())
        if (KdmItem *n = themer->findNode(w->objectName())) {
            QString tn(QString("plugin-").append(w->objectName()));
            themer->setTypeVisible(tn, true);
            showTypes.insert(tn);
            oldTypes.remove(tn);
            n->setWidget(w);
        } else {
            msgBox(errorbox,
                   tr("Theme not usable with authentication method '%1'.").arg(
                        QString::fromUtf8(greetPlugins[pluginList[id]].info->name)));
            break;
        }
    foreach (const QString &t, oldTypes)
        themer->setTypeVisible(t, false);
}

void
KGThemedVerify::updateStatus()
{
    handler->updateStatus(enabled && failed,
                          enabled && !suspended && capsLocked,
                          timedLeft);
}


KGChTok::KGChTok(QWidget *_parent, const QString &user,
                 const PluginList &pluginList, int curPlugin,
                 KGreeterPlugin::Function func,
                 KGreeterPlugin::Context ctx)
    : inherited(_parent)
    , verify(nullptr)
{
    QSizePolicy fp(QSizePolicy::Fixed, QSizePolicy::Fixed);
    okButton = new QPushButton(/*KStandardGuiItem::ok()*/tr("Ok"), this);
    okButton->setSizePolicy(fp);
    okButton->setDefault(true);
    cancelButton = new QPushButton(/*KStandardGuiItem::cancel()*/tr("Cancel"), this);
    cancelButton->setSizePolicy(fp);

    verify = new KGStdVerify(this, this, cancelButton, user, pluginList, func, ctx);
    verify->selectPlugin(curPlugin);

    QVBoxLayout *box = new QVBoxLayout(this);

    box->addWidget(new QLabel(/*i18nc("@title:window",*/tr(
                                    "<qt><b>Changing authentication token</b></qt>"),
                              this), 0, Qt::AlignHCenter | Qt::AlignTop);

    box->addLayout(verify->getLayout());

	QFrame *sep=new QFrame(this); sep->setFrameStyle(QFrame::HLine);
    box->addWidget(sep/*new KSeparator( Qt::Horizontal, this )*/);

    QHBoxLayout *hlay = new QHBoxLayout();
    box->addLayout(hlay);
    hlay->addStretch(1);
    hlay->addWidget(okButton);
    hlay->addStretch(1);
    hlay->addWidget(cancelButton);
    hlay->addStretch(1);

    connect(okButton, SIGNAL(clicked()), SLOT(accept()));
    connect(cancelButton, SIGNAL(clicked()), SLOT(reject()));

    QTimer::singleShot(0, verify, SLOT(start()));
}

KGChTok::~KGChTok()
{
    hide();
    if (verify) delete verify;
}

void
KGChTok::accept()
{
    verify->accept();
}

void
KGChTok::verifyPluginChanged(int)
{
    // cannot happen
}

void
KGChTok::verifyOk()
{
    inherited::accept();
}

void
KGChTok::verifyFailed()
{
    okButton->setEnabled(false);
    cancelButton->setEnabled(false);
}

void
KGChTok::verifyRetry()
{
    okButton->setEnabled(true);
    cancelButton->setEnabled(true);
}

void
KGChTok::verifySetUser(const QString &)
{
    // cannot happen
}


////// helper class, nuke when qtimer supports suspend()/resume()

QXTimer::QXTimer()
    : inherited(nullptr)
    , left(-1)
{
    connect(&timer, SIGNAL(timeout()), SLOT(slotTimeout()));
}

void
QXTimer::start(int msec)
{
    left = msec;
    timer.setSingleShot(true);
    timer.start(left);
    gettimeofday(&stv, nullptr);
}

void
QXTimer::stop()
{
    timer.stop();
    left = -1;
}

void
QXTimer::suspend()
{
    if (timer.isActive()) {
        timer.stop();
        struct timeval tv;
        gettimeofday(&tv, nullptr);
        left -= (tv.tv_sec - stv.tv_sec) * 1000 + (tv.tv_usec - stv.tv_usec) / 1000;
        if (left < 0)
            left = 0;
    }
}

void
QXTimer::resume()
{
    if (left >= 0 && !timer.isActive()) {
        timer.setSingleShot(true);
        timer.start(left);
        gettimeofday(&stv, nullptr);
    }
}

void
QXTimer::slotTimeout()
{
    left = -1;
    emit timeout();
}


//#include "kgverify.moc"
