##########################################################################
# 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 3 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/>.  #
##########################################################################

use strict;
use Purple;
use Net::DBus;

our %PLUGIN_INFO = (
    perl_api_version => 2,
    name             => "KWallet",
    version          => "0.1",
    summary          => "Use KWallet to store passwords.",
    description      => "Use KWallet to store passwords instead of the default plaintext file.\n" .
                        "When this plugin is enabled passwords are saved to KWallet for all accounts, regardless if \"remember password\" is checked or not. " .
                        "If it is checked then the passwords will be *saved in the plaintext file also* which " .
                        "goes a bit against the main purpose of this plugin (storing passwords in a secure manner). But then again, it is the users choice...",
    author           => "Andrei Mihaila <andrei.mihaila\@gmail.com>, crocket <crockabiscuit\@gmail.com>",
    url              => "http://gitorious.org/libpurple-kwallet-plugin/",
    load             => "plugin_load",
    unload           => "plugin_unload"
);

my $folder           = "libpurple";
my $application_name = "Pidgin";

my %engine_data = (
    dbus_object => undef,
    handle      => 0,
);

sub plugin_init {
    return %PLUGIN_INFO;
}

sub get_kwallet_dbus_handle {
    unless ($engine_data{dbus_object}) {
        # thanks to Crocket for this patch - it starts kwalletd if not running
	my $kwallet = eval { Net::DBus->session->get_service('org.kde.kwalletd')->get_object('/modules/kwalletd') };
        if ( $@ ) {
	    system("kwalletd");
	    $kwallet = eval { Net::DBus->session->get_service('org.kde.kwalletd')->get_object('/modules/kwalletd') };
	    if ( $@ ) {
		Purple::Debug::error("kwallet_password", "Error connecting to KWallet via DBus: $@.\n");
		return;
	    }
        }

	unless ($kwallet) {
	    Purple::Debug::error("kwallet_password", "Cannot connect to KWallet via DBus.\n");
	    return;
	}

        $engine_data{dbus_object} = $kwallet;
    }

    return $engine_data{dbus_object};
}

sub open_local_kwallet {
    my $kwallet = get_kwallet_dbus_handle();
    return unless $kwallet;

    unless ($kwallet->isEnabled()) {
        Purple::Debug::error("kwallet_password", "KWallet is not enabled.\n");
        return;
    }

    if ($engine_data{handle} == 0 || !$kwallet->isOpen($engine_data{handle})) {
        my $kwallet_handle = undef;

        my $local_wallet_name = $kwallet->localWallet();
        $kwallet_handle = $kwallet->open($local_wallet_name, 1, $application_name);

        unless ($kwallet_handle) {
            Purple::Debug::error("kwallet_password", "Cannot open the default wallet $local_wallet_name.\n");
            return;
        }

        $engine_data{handle} = $kwallet_handle;
    }

    return $engine_data{handle};
}

sub disconnect_from_kwallet {
    my $kwallet = get_kwallet_dbus_handle();

    return unless $kwallet;

    $kwallet->disconnectApplication($kwallet->localWallet(), $application_name);

    if ($engine_data{handle} != 0 && $kwallet->isOpen($engine_data{handle})) {
        $kwallet->close($engine_data{handle}, undef, $application_name);
    }

    $engine_data{dbus_object} = undef;
    $engine_data{handle} = undef;

    Purple::Debug::info("kwallet_password", "Disconnected $application_name from KWallet.\n");
}

sub fetch_password {
    my $account = shift;

    my $key = Purple::Account::get_protocol_id($account) . ":" .
            Purple::Account::get_username($account);

    my $kwallet = get_kwallet_dbus_handle();
    return unless $kwallet;

    if ($kwallet->folderDoesNotExist($kwallet->localWallet(), $folder)) {
        Purple::Debug::warning("kwallet_password", "Folder $folder does not exist, skipping password fetch for account $key.\n");
        return;
    }

    my $kwallet_handle = open_local_kwallet();
    return unless $kwallet_handle;

    my $password = $kwallet->readPassword($kwallet_handle, $folder, $key, $application_name);
    if ((not defined $password) || (length $password == 0)) {
        Purple::Debug::warning("kwallet_password", "Password for account $key is empty.\n");
    }
    else {
        Purple::Debug::info("kwallet_password", "Setting password for account $key from KWallet.\n");
        Purple::Account::set_password($account, $password);
    }
}

sub store_password {
    my $account = shift;

    my $key = Purple::Account::get_protocol_id($account) . ":" .
            Purple::Account::get_username($account);
    if (length Purple::Account::get_password($account) == 0) {
        Purple::Debug::warning("kwallet_password", "Password for account $key is empty, not storing in KWallet.\n");
        return;
    }

    my $kwallet = get_kwallet_dbus_handle();
    return unless $kwallet;

    my $kwallet_handle = open_local_kwallet();
    return unless $kwallet_handle;

    if ($kwallet->folderDoesNotExist($kwallet->localWallet(), $folder)) {
        if (!$kwallet->createFolder($kwallet_handle, $folder, $application_name)) {
            Purple::Debug::error("kwallet_password", "Could not create folder $folder.\n");
            return;
        }
    }
    if ($kwallet->writePassword($kwallet_handle, $folder, $key, Purple::Account::get_password($account), $application_name) > 0) {
        Purple::Debug::error("kwallet_password", "Could not write password for account $key.\n");
    }
}

sub on_account_connecting {
    my $account = shift;
    store_password($account);
}

sub plugin_load {
    my $plugin = shift;
    foreach my $account (Purple::Accounts::get_all()) {
        fetch_password($account);
    }
    my $accounts_handle = Purple::Accounts::get_handle();
    Purple::Signal::connect($accounts_handle, "account-connecting", $plugin, \&on_account_connecting, undef);
}

sub plugin_unload {
    disconnect_from_kwallet();
}
