/********************************************************************
 KSld - the KDE Screenlocker Daemon
 This file is part of the KDE project.

Copyright (C) 1999 Martin R. Jones <mjones@kde.org>
Copyright (C) 2002 Luboš Luňák <l.lunak@kde.org>
Copyright (C) 2003 Oswald Buddenhagen <ossi@kde.org>
Copyright (C) 2011 Martin Gräßlin <mgraesslin@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, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "greeter.h"
#include "kscreensaversettings.h"
// workspace
#include <kcheckpass-enums.h>
// KDE
#include <KDE/KDebug>
#include <KDE/KLibrary>
#include <KDE/KLocale>
#include <KDE/KPluginFactory>
#include <KDE/KPluginLoader>
#include <KDE/KStandardDirs>

// Qt
#include <QtCore/QFile>
#include <QtCore/QSocketNotifier>
#include <QtCore/QTimer>
#include <QtDeclarative/QDeclarativeContext>
#include <QtDeclarative/QDeclarativeEngine>
#include <QtGui/QGraphicsProxyWidget>
#include <QtGui/QLineEdit>

// kscreenlocker stuff
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <pwd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>

#include <QPalette>

//Plasma
#include <Plasma/Theme>


namespace ScreenLocker
{

GreeterItem::GreeterItem(QDeclarativeItem *parent)
    : QDeclarativeItem(parent)
    , m_proxy(new QGraphicsProxyWidget(this))
    , m_unlocker(new Greeter(this))
{
    init();
}

GreeterItem::~GreeterItem()
{
}

//#define SHOW_GREETER_BACKGROUND
void GreeterItem::init()
{
    if (!m_unlocker->isValid()) {
        exit(1);
        return;
    }
    m_widget = m_unlocker->greeterWidget();
    
    themeChanged();
    connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(themeChanged()));
#ifndef SHOW_GREETER_BACKGROUND
    m_widget->setAttribute(Qt::WA_TranslucentBackground);
#endif

    m_proxy->setWidget(m_widget);
    m_proxy->setFlag(QGraphicsItem::ItemIsFocusable);
    setFlag(QGraphicsItem::ItemIsFocusable);
    m_proxy->setFocus();
    QGraphicsItem::setFocus();
    connect(m_unlocker, SIGNAL(greeterFailed()), this, SIGNAL(greeterFailed()));
    connect(m_unlocker, SIGNAL(greeterReady()), this, SIGNAL(greeterReady()));
    connect(m_unlocker, SIGNAL(greeterMessage(QString)), this, SIGNAL(greeterMessage(QString)));
    connect(m_unlocker, SIGNAL(greeterAccepted()), this, SIGNAL(greeterAccepted()));
    setWidth(m_proxy->size().width());
    setHeight(m_proxy->size().height());
}

void GreeterItem::themeChanged()
{
    QPalette p = m_widget->palette();
    p.setColor(QPalette::WindowText,
               Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor));
    m_widget->setPalette(p);
}

void GreeterItem::verify()
{
    m_unlocker->verify();
}

void GreeterItem::clear()
{
    m_unlocker->clear();
}

void GreeterItem::focusInEvent(QFocusEvent *event)
{
    QGraphicsItem::focusInEvent(event);
    if (QLineEdit *lineEdit = m_proxy->widget()->findChild<QLineEdit *>()) {
        lineEdit->setFocus(Qt::OtherFocusReason);
    }
}

// KeyboardItem

KeyboardItem::KeyboardItem(QDeclarativeItem *parent)
    : QDeclarativeItem(parent)
    , m_widget(new QWidget())
    , m_proxy(new QGraphicsProxyWidget(this))
{
    m_widget->setAttribute(Qt::WA_TranslucentBackground);
    KPluginFactory *kxkbFactory = KPluginLoader(QLatin1String("keyboard_layout_widget")).factory();
    if (kxkbFactory) {
        kxkbFactory->create<QWidget>(m_widget);
    } else {
        kDebug() << "can't load keyboard layout widget library";
    }

    themeChanged();
    connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(themeChanged()));
    m_proxy->setWidget(m_widget);

    setWidth(m_proxy->size().width());
    setHeight(m_proxy->size().height());
}

KeyboardItem::~KeyboardItem()
{
}

void KeyboardItem::themeChanged()
{
    QPalette p = m_widget->palette();
    p.setColor(QPalette::WindowText,
               Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor));
    m_widget->setPalette(p);
}

Greeter::Greeter(QObject *parent)
    : QObject(parent)
    , m_greeterWidget(new QWidget())
    , m_greet(0)
    , m_valid(false)
    , m_pid(0)
    , m_fd(0)
    , m_notifier(NULL)
    , m_failedLock(false)
{
    m_pluginHandle.library = 0;
    initialize();
    m_valid = loadGreetPlugin();
    if (m_valid) {

        m_greet = m_pluginHandle.info->create(this, m_greeterWidget, QString(),
                                        KGreeterPlugin::Authenticate,
                                        KGreeterPlugin::ExUnlock);
        m_greet->start();
    }
}

Greeter::~Greeter()
{
    if (m_pluginHandle.library) {
        if (m_pluginHandle.info->done) {
            m_pluginHandle.info->done();
        }
        m_pluginHandle.library->unload();
    }
}

void Greeter::initialize()
{
    KScreenSaverSettings::self()->readConfig();
    m_plugins = KScreenSaverSettings::pluginsUnlock();
    if (m_plugins.isEmpty()) {
        m_plugins << QLatin1String( "classic" ) << QLatin1String( "generic" );
    }
    m_pluginOptions = KScreenSaverSettings::pluginOptions();
    const QStringList dmopt =
        QString::fromLatin1( ::getenv( "XDM_MANAGED" )).split(QLatin1Char(','), QString::SkipEmptyParts);
    for (QStringList::ConstIterator it = dmopt.constBegin(); it != dmopt.constEnd(); ++it) {
        if ((*it).startsWith(QLatin1String( "method=" ))) {
            m_method = (*it).mid(7);
        }
    }
}

// standard greeter stuff
// private static
QVariant Greeter::getConf(void *ctx, const char *key, const QVariant &dflt)
{
    Greeter *that = (Greeter *)ctx;
    QString fkey = QLatin1String( key ) % QLatin1Char( '=' );
    for (QStringList::ConstIterator it = that->m_pluginOptions.constBegin();
         it != that->m_pluginOptions.constEnd(); ++it)
        if ((*it).startsWith( fkey ))
            return (*it).mid( fkey.length() );
    return dflt;
}

bool Greeter::loadGreetPlugin()
{
    if (m_pluginHandle.library) {
        //we were locked once before, so all the plugin loading's done already
        //FIXME should I be unloading the plugin on unlock instead?
        return true;
    }
    for (QStringList::ConstIterator it = m_plugins.constBegin(); it != m_plugins.constEnd(); ++it) {
        GreeterPluginHandle plugin;
        KLibrary *lib = new KLibrary( (*it)[0] == QLatin1Char( '/' ) ? *it : QLatin1String( "kgreet_" ) + *it );
        if (lib->fileName().isEmpty()) {
            kWarning() << "GreeterPlugin " << *it << " does not exist" ;
            delete lib;
            continue;
        }
        if (!lib->load()) {
            kWarning() << "Cannot load GreeterPlugin " << *it << " (" << lib->fileName() << ")" ;
            delete lib;
            continue;
        }
        plugin.library = lib;
        plugin.info = (KGreeterPluginInfo *)lib->resolveSymbol( "kgreeterplugin_info" );
        if (!plugin.info ) {
            kWarning() << "GreeterPlugin " << *it << " (" << lib->fileName() << ") is no valid greet widget plugin" ;
            lib->unload();
            delete lib;
            continue;
        }
        if (plugin.info->method && !m_method.isEmpty() && m_method != QLatin1String(  plugin.info->method )) {
            kDebug() << "GreeterPlugin " << *it << " (" << lib->fileName() << ") serves " << plugin.info->method << ", not " << m_method;
            lib->unload();
            delete lib;
            continue;
        }
        if (!plugin.info->init( m_method, getConf, this )) {
            kDebug() << "GreeterPlugin " << *it << " (" << lib->fileName() << ") refuses to serve " << m_method;
            lib->unload();
            delete lib;
            continue;
        }
        kDebug() << "GreeterPlugin " << *it << " (" << plugin.info->method << ", " << plugin.info->name << ") loaded";
        m_pluginHandle = plugin;
        return true;
    }
    return false;
}

void Greeter::verify()
{
    if (m_failedLock) {
        // greeter blocked due to failed unlock attempt
        return;
    }
    m_greet->next();
}

void Greeter::clear()
{
    m_greet->clear();
}

void Greeter::failedTimer()
{
    emit greeterReady();
    m_greet->revive();
    m_greet->start();
    m_failedLock = false;
}

////// kckeckpass interface code

int Greeter::Reader(void *buf, int count)
{
    int ret, rlen;

    for (rlen = 0; rlen < count; ) {
      dord:
        ret = ::read(m_fd, (void *)((char *)buf + rlen), count - rlen);
        if (ret < 0) {
            if (errno == EINTR)
                goto dord;
            if (errno == EAGAIN)
                break;
            return -1;
        }
        if (!ret)
            break;
        rlen += ret;
    }
    return rlen;
}

bool Greeter::GRead(void *buf, int count)
{
    return Reader(buf, count) == count;
}

bool Greeter::GWrite(const void *buf, int count)
{
    return ::write(m_fd, buf, count) == count;
}

bool Greeter::GSendInt(int val)
{
    return GWrite(&val, sizeof(val));
}

bool Greeter::GSendStr(const char *buf)
{
    int len = buf ? ::strlen (buf) + 1 : 0;
    return GWrite(&len, sizeof(len)) && GWrite (buf, len);
}

bool Greeter::GSendArr(int len, const char *buf)
{
    return GWrite(&len, sizeof(len)) && GWrite (buf, len);
}

bool Greeter::GRecvInt(int *val)
{
    return GRead(val, sizeof(*val));
}

bool Greeter::GRecvArr(char **ret)
{
    int len;
    char *buf;

    if (!GRecvInt(&len))
        return false;
    if (!len) {
        *ret = 0;
        return true;
    }
    if (!(buf = (char *)::malloc (len)))
        return false;
    *ret = buf;
    if (GRead (buf, len)) {
        return true;
    } else {
        ::free(buf);
        *ret = 0;
        return false;
    }
}

void Greeter::reapVerify()
{
    m_notifier->setEnabled( false );
    m_notifier->deleteLater();
    m_notifier = 0;
    ::close( m_fd );
    int status;
    while (::waitpid( m_pid, &status, 0 ) < 0)
        if (errno != EINTR) { // This should not happen ...
            cantCheck();
            return;
        }
    if (WIFEXITED(status))
        switch (WEXITSTATUS(status)) {
        case AuthOk:
            m_greet->succeeded();
            emit greeterAccepted();
            return;
        case AuthBad:
            m_greet->failed();
            emit greeterFailed();
            m_failedLock = true;
            QTimer::singleShot(1500, this, SLOT(failedTimer()));
            //KNotification::event( QLatin1String( "unlockfailed" ) );*/
            return;
        case AuthAbort:
            return;
        }
    cantCheck();
}

void Greeter::handleVerify()
{
    int ret;
    char *arr;

    if (GRecvInt( &ret )) {
        switch (ret) {
        case ConvGetBinary:
            if (!GRecvArr( &arr ))
                break;
            m_greet->binaryPrompt( arr, false );
            if (arr)
                ::free( arr );
            return;
        case ConvGetNormal:
            if (!GRecvArr( &arr ))
                break;
            m_greet->textPrompt( arr, true, false );
            if (arr)
                ::free( arr );
            return;
        case ConvGetHidden:
            if (!GRecvArr( &arr ))
                break;
            m_greet->textPrompt( arr, false, false );
            if (arr)
                ::free( arr );
            return;
        case ConvPutInfo:
            if (!GRecvArr( &arr ))
                break;
            if (!m_greet->textMessage( arr, false ))
                emit greeterMessage(QString::fromLocal8Bit(arr));
            ::free( arr );
            return;
        case ConvPutError:
            if (!GRecvArr( &arr ))
                break;
            if (!m_greet->textMessage( arr, true ))
                emit greeterMessage(QString::fromLocal8Bit(arr));
            ::free( arr );
            return;
        }
    }
    reapVerify();
}

////// greeter plugin callbacks

void Greeter::gplugReturnText( const char *text, int tag )
{
    GSendStr( text );
    if (text)
        GSendInt( tag );
}

void Greeter::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);
        if (!len)
            GSendArr( 4, data );
        else
            GSendArr( len, data );
    } else
        GSendArr( 0, 0 );
}

void Greeter::gplugSetUser( const QString & )
{
    // ignore ...
}

void Greeter::cantCheck()
{
    m_greet->failed();
    emit greeterMessage(i18n("Cannot unlock the session because the authentication system failed to work!"));
    m_greet->revive();
}

//---------------------------------------------------------------------------
//
// Starts the kcheckpass process to check the user's password.
//
void Greeter::gplugStart()
{
    int sfd[2];
    char fdbuf[16];

    if (m_notifier)
        return;
    if (::socketpair(AF_LOCAL, SOCK_STREAM, 0, sfd)) {
        cantCheck();
        return;
    }
    if ((m_pid = ::fork()) < 0) {
        ::close(sfd[0]);
        ::close(sfd[1]);
        cantCheck();
        return;
    }
    if (!m_pid) {
        ::close(sfd[0]);
        sprintf(fdbuf, "%d", sfd[1]);
        execlp(QFile::encodeName(KStandardDirs::findExe(QLatin1String( "kcheckpass" ))).data(),
               "kcheckpass",
               "-m", m_pluginHandle.info->method,
               "-S", fdbuf,
               (char *)0);
        _exit(20);
    }
    ::close(sfd[1]);
    m_fd = sfd[0];
    m_notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
    connect(m_notifier, SIGNAL(activated(int)), SLOT(handleVerify()));
}

void Greeter::gplugChanged()
{
}

void Greeter::gplugActivity()
{
    // ignore
}

void Greeter::gplugMsgBox(QMessageBox::Icon type, const QString &text)
{
    Q_UNUSED(type)
    emit greeterMessage(text);
}

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

} // end namespace
#include "greeter.moc"
