/*******************************************************************
 * KWallet Password Plugin
 *
 * Copyright(C) 2010 Craig Drummond <craig.p.drummond@googlemail.com>
 * 
 * Ported over to DBus, and CMake-ified, from original kwallet_password.c
 * 
 * Copyright(C) 2008, C. Shaun Wagner <cs@kainaw.com>,
 *                     Charles Tullock <crtgwb@mst.edu>,
 *                     Jonathan Blount <jjbnq2@mst.edu>
 *******************************************************************
 * USAGE
 * Create the accounts that you want to use in the chat program that
 * uses libpurple.  Save the passwords and check the "remember
 * passwords" checkbox.
 * In Tools->KWallet Passwords, select Encrypt Passwords.
 * All passwords will be moved from accounts.xml to KWallet.
 *
 * If you create a new account, check the "remember passwords"
 * checkbox on it and select Encrypt Passwords again.  It will
 * encrypt any accounts that have a password saved in accounts.xml.
 *
 * To put the passwords back in accounts.xml, select Decrypt
 * Passwords instead of Encrypt Passwords.  All passwords will be
 * put in the accounts.xml file and removed from KWallet.
 *******************************************************************
 * LICENSE
 * 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
 * 02111-1301, USA.
 *******************************************************************/

#include "config.h"

/* config.h may define PURPLE_PLUGINS; protect the definition here so that we don't get complaints about redefinition when it's not necessary. */
#ifndef PURPLE_PLUGINS
# define PURPLE_PLUGINS
#endif

/* <b style="color: black; background-color: rgb(160, 255, 255);">Libpurple</b> is a glib application. */
#include <glib.h>
#include <dbus/dbus-glib.h>

/* This will prevent compiler errors in some instances and is better explained in the how-to documents on the wiki */
#ifndef G_GNUC_NULL_TERMINATED
# if __GNUC__ >= 4
#  define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__))
# else
#  define G_GNUC_NULL_TERMINATED
# endif
#endif

/* Include the required libpurple headers. */
#include <notify.h>
#include <plugin.h>
#include <version.h>

PurplePlugin *kwallet_plugin = NULL; //A handle to our plugin - will be initiated in plugin_load().
DBusGConnection *connection = NULL;
DBusGProxy *proxy = NULL;

static const char * kwalletFolder="Pidgin";

static gboolean folder_action(int wallet, const char *action)
{
    gboolean b;
    GError   *error = NULL;

    if (!dbus_g_proxy_call(proxy, action, &error,
        G_TYPE_INT, wallet,
        G_TYPE_STRING, kwalletFolder,
        G_TYPE_STRING, "Pidgin", /* Application */
        G_TYPE_INVALID, /* End of arguments.. */
        G_TYPE_BOOLEAN, &b,
        G_TYPE_INVALID /* End of return... */))
    {
        g_printerr("%s - error: %s\n", action, error->message);
        g_error_free(error);
        return FALSE;
    }
    return b;
}

static char * read_password(int wallet, const char *account)
{
    char    *rv;
    GError *error = NULL;

    if (!dbus_g_proxy_call(proxy, "readPassword", &error,
        G_TYPE_INT, wallet,
        G_TYPE_STRING, kwalletFolder,
        G_TYPE_STRING, account,
        G_TYPE_STRING, "Pidgin", /* Application */
        G_TYPE_INVALID, /* End of arguments.. */
        G_TYPE_STRING, &rv,
        G_TYPE_INVALID /* End of return... */))
    {
        g_printerr("readPassword - error: %s\n", error->message);
        g_error_free(error);
        return NULL;
    }
    return rv;
}

static gboolean write_password(int wallet, const char *account, const char *passwd)
{
    int    rv;
    GError *error = NULL;

    if (!dbus_g_proxy_call(proxy, "writePassword", &error,
        G_TYPE_INT, wallet,
        G_TYPE_STRING, kwalletFolder,
        G_TYPE_STRING, account,
        G_TYPE_STRING, passwd,
        G_TYPE_STRING, "Pidgin", /* Application */
        G_TYPE_INVALID, /* End of arguments.. */
        G_TYPE_INT, &rv,
        G_TYPE_INVALID /* End of return... */))
    {
        g_printerr("writePassword - error: %s\n", error->message);
        g_error_free(error);
        return FALSE;
    }
    return 0==rv;
}

static gboolean remove_entry(int wallet, const char *account)
{
    int    rv;
    GError *error = NULL;

    if (!dbus_g_proxy_call(proxy, "removeEntry", &error,
        G_TYPE_INT, wallet,
        G_TYPE_STRING, kwalletFolder,
        G_TYPE_STRING, account,
        G_TYPE_STRING, "Pidgin", /* Application */
        G_TYPE_INVALID, /* End of arguments.. */
        G_TYPE_INT, &rv,
        G_TYPE_INVALID /* End of return... */))
    {
        g_printerr("Error: %s\n", error->message);
        g_error_free(error);
        return FALSE;
    }
    printf("result:%d\n", 0==rv);
    return 0==rv;
}

static int open_wallet(gboolean silent)
{
    GError *error = NULL;
    int    wallet = -1;

    if (proxy && !dbus_g_proxy_call(proxy, "open", &error,
        G_TYPE_STRING, "kdewallet",
        G_TYPE_INT64,  (gint64)0, /* TODO! - Window ID */
        G_TYPE_STRING, "Pidgin", /* Application */
        G_TYPE_INVALID, /* End of arguments.. */
        G_TYPE_INT, &wallet,
        G_TYPE_INVALID /* End of return... */))
    {
        g_error_free(error);
        wallet=-1;
    }

    if(wallet<0)
    {
        if(!silent)
            purple_notify_message(kwallet_plugin, PURPLE_NOTIFY_MSG_ERROR, "KWallet Error",
                                  "Could not open the standard wallet.", NULL, NULL, NULL);
        return -1;
    }
    
    if(!folder_action(wallet, "hasFolder") && !folder_action(wallet, "createFolder"))
    {
        if(!silent)
            purple_notify_message(kwallet_plugin, PURPLE_NOTIFY_MSG_ERROR, "KWallet Error",
                                  "Could not create a password folder.", NULL, NULL, NULL);
        return -1;
    }

    return wallet;
}

static const char * key_for_account(PurpleAccount *account)
{
    static char key[1024];

    sprintf(key, "%s:%s", purple_account_get_protocol_id(account), purple_account_get_username(account));
    return key;
}

static void store_password(PurpleAccount *account)
{
    int wallet=open_wallet(FALSE);
    if(wallet>=0)
    {
        if(write_password(wallet, key_for_account(account), purple_account_get_password(account)))
        {
            /* KWallet has the password now - so accounts.xml can forget it. */
            purple_account_set_remember_password(account, FALSE);
        }
        else
            purple_notify_message(kwallet_plugin, PURPLE_NOTIFY_MSG_ERROR, "KWallet Error",
                                  "Could not save password in KWallet.", NULL, NULL, NULL);
    }
}

static void encrypt_password(gpointer data, gpointer user_data)
{
    PurpleAccount *account=(PurpleAccount*)data;

    /* Only save passwords for accounts that are remembering passwords in accounts.xml. */
    if(purple_account_get_remember_password(account))
        store_password(account);
}

static void fetch_password(gpointer data, gpointer user_data)
{
    PurpleAccount *account=(PurpleAccount*)data;

    /* Only fetch passwords for accounts that are not remembering passwords in accounts.xml. */
    if(!purple_account_get_remember_password(account))
    {
        int wallet=open_wallet(FALSE);
        if(wallet>=0)
        {
            char *password=read_password(wallet, key_for_account(account));
            if(!password)
                return;  // Don't print an error here - it could just be that the password isn't saved.
            purple_account_set_password(account, password);
            g_free(password);
        }
    }
}

static void decrypt_password(gpointer data, gpointer user_data)
{
    PurpleAccount *account=(PurpleAccount*)data;

    /* Only decrypt passwords for accounts that are not remembering passwords in accounts.xml. */
    if(!purple_account_get_remember_password(account))
    {
        int wallet=open_wallet(FALSE);
        if(wallet>=0)
        {
            const char *key=key_for_account(account);
            char       *password=read_password(wallet, key);
            if(!password)
                return; // Don't print an error here - it could just be that the password isn't saved.
            purple_account_set_password(account, password);
            purple_account_set_remember_password(account, TRUE);
            remove_entry(wallet, key);
            g_free(password);
        }
    }
}

static void encrypt_all_passwords(PurplePluginAction *action)
{
    GList *accounts = purple_accounts_get_all();
    g_list_foreach(accounts, encrypt_password, NULL);
    // You cannot g_list_free(accounts) here without segfaulting Pidgin.
     purple_notify_message(kwallet_plugin, PURPLE_NOTIFY_MSG_INFO, "KWallet Password", "All saved passwords are now in KWallet.",
                           NULL, NULL, NULL);
}

static void decrypt_all_passwords(PurplePluginAction *action)
{
    GList *accounts = purple_accounts_get_all();
    g_list_foreach(accounts, decrypt_password, NULL);
    // You cannot g_list_free(accounts) here without segfaulting Pidgin.
    purple_notify_message(kwallet_plugin, PURPLE_NOTIFY_MSG_INFO, "KWallet Password",
                          "All saved passwords are now in accounts.xml as plain text.", NULL, NULL, NULL);
}

#ifdef AUTOSAVE_PASSWORDS
static void account_added(gpointer data, gpointer user_data)
{
    store_password((PurpleAccount*)data);
}

static void account_removed(gpointer data, gpointer user_data)
{
    PurpleAccount *account=(PurpleAccount*)data;
    int           wallet=open_wallet(TRUE);

    if(wallet>=0)
    {
        const char *key=key_for_account(account);
        char       *password=read_password(wallet, key);

        if(password)
        {
            remove_entry(wallet, key);
            g_free(password);
        }
    }
}

static void account_changed(gpointer data, gpointer user_data)
{
    // TODO: Is there a way to detect when an account has changed?????
    PurpleAccount *account=(PurpleAccount*)data;
    printf("Account changed: %s -> %s\n", key_for_account(account), password ? password : "<>");

    //store_password((PurpleAccount*)data);
}
#endif

/* Register plugin actions */
static GList *plugin_actions(PurplePlugin *plugin, gpointer context)
{
    GList *list = NULL; // The action list.
    PurplePluginAction *action = NULL; // A action temp pointer.

    action = purple_plugin_action_new("Encrypt Passwords", encrypt_all_passwords);
    list = g_list_append(list, action);
    action = purple_plugin_action_new("Decrypt Passwords", decrypt_all_passwords);
    list = g_list_append(list, action);

    return list;
}

/* Called when the plugin loads(after plugin_init()) */
static gboolean plugin_load(PurplePlugin *plugin)
{
    kwallet_plugin = plugin; /* assign this here so we have a valid handle later */

    /* Set the passwords for the accounts before they try to connect. */
    GList *accounts = NULL;
    accounts = purple_accounts_get_all();
    g_list_foreach(accounts, fetch_password, NULL);
    // You cannot g_list_free(accounts) here without segfaulting Pidgin.


#ifdef AUTOSAVE_PASSWORDS
    void *accounts_handle=purple_accounts_get_handle();
    purple_signal_connect(accounts_handle, "account-added", plugin,
                          PURPLE_CALLBACK(account_added), NULL);
    purple_signal_connect(accounts_handle, "account-removed", plugin,
                          PURPLE_CALLBACK(account_removed), NULL);
    purple_signal_connect(accounts_handle, "account-set-info", plugin,
                          PURPLE_CALLBACK(account_changed), NULL);
#endif
    return TRUE;
}

/* For specific notes on the meanings of each of these members, consult the C Plugin Howto on the website. */
static PurplePluginInfo info = {
    PURPLE_PLUGIN_MAGIC,
    PURPLE_MAJOR_VERSION,
    PURPLE_MINOR_VERSION,
    PURPLE_PLUGIN_STANDARD,
    NULL,
    0,
    NULL,
    PURPLE_PRIORITY_DEFAULT,
    "core-kwallet",
    "KWallet",
    PLUGIN_VERSION,
    "Use KWallet to store passwords.", /* Summary */
    "Store passwords encrypted within the KDE wallet.", /* Description */
    "Craig Drummond <craig.p.drummond@googlemail.com>",
    NULL,
    plugin_load,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    plugin_actions, /* this tells libpurple the address of the function to call to get the list of plugin actions. */
    NULL,
    NULL,
    NULL,
    NULL
};

static void init_plugin(PurplePlugin *plugin)
{
    GError *error = NULL;

    g_type_init();

    connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
    if(NULL==connection)
    {
        g_printerr("Failed to open connection to bus: %s\n", error->message);
        g_error_free(error);
        return;
    }

    proxy = dbus_g_proxy_new_for_name(connection, "org.kde.kwalletd", "/modules/kwalletd",  "org.kde.KWallet");

    if (!proxy)
    {
        g_error("Failed to get name owner");
        return;
    }
}

PURPLE_INIT_PLUGIN(kwallet_world, init_plugin, info)
