Opened 11 years ago
Closed 5 years ago
#288 closed enhancement
XEP-0027: OpenPGP in Jabber
| Reported by: | gagern | Owned by: | nwalp |
|---|---|---|---|
| Milestone: | Patches welcome | Component: | XMPP |
| Version: | 2.0 | Keywords: | |
| Cc: | AZ, skoehler, elreydetodo, pva, segler_alex, devurandom |
Description
There was a feature request #936944 on the SourceForge tracker asking to implement XEP-0027, still called JEP-0027 when the request was opened. The request had three comments after the initial post, all in favor of adding this feature. I believe this is still of interest.
The key arguments for implementing this extension are in my opinion:
- interoperability with other Jabber clients
- leveraging the OpenPGP / GnuPG Web of Trust
Change History (21)
comment:1 Changed 11 years ago by lschiere
- Component changed from pidgin (gtk) to libpurple
- Owner set to nwalp
comment:2 Changed 11 years ago by SuperMMX
comment:3 Changed 11 years ago by nwalp
- pending set to 0
No, I have not. As I believe I stated in the old request, I was waiting on the e2e encryption spec for XMPP to work itself out first, since PGP has a LOT of overhead for a medium such as IM. If free time permits, I may pick this up at some point, as that e2e spec seems to have gone nowhere.
comment:4 Changed 11 years ago by doconnor
Is #1079 related/duplicated?
comment:5 Changed 11 years ago by doconnor
Is #1346 a dupe?
comment:6 Changed 11 years ago by gagern
Added comments in those tickets. I think #1079 is remotely related, #1346 a real dupe.
Thinking about related plugins, it might make sense to split an implementation for this ticket here into three parts:
- Encryption UI in message window, suitable for all encryption schemes like this here, gaim-encryption, OTR, and anything that might come along. I only ever used OTR so far and know that they use four icons: unencrypted, encrypted unverified, encrypted verified and terminated. Those might be enough for other encryption schemes as well, but I'm not sure. When activating encryption there should be a submenu to choose which method to use if multiple are available.
- GPG configuration with anything related to gpg binary, keyserver, keyrings etc. to be shared by all encryption schemes based on OpenPGP.
- Encryption scheme that actually implements XEP-0027 on the protocol level and is controlled by above settings, so it can do with no UI elements of its own.
comment:7 Changed 10 years ago by seanegan
- Component changed from libpurple to XMPP
comment:8 Changed 10 years ago by seadog
Is nwalp actively working on this? Is there a delivery schedule?
comment:9 Changed 10 years ago by nwalp
No, I'm not.
comment:10 Changed 10 years ago by liviopl
+1 from me.
comment:11 Changed 10 years ago by jensp
This bug is the only thing from keeping me from switching from kopete to pidgin, I'd really love to see this implemented :)
comment:12 Changed 10 years ago by liviopl
You'll be wondering a long time 'till it's being implemented... Developers here are not listening to users needs - exactly how compiz folks do...
comment:13 Changed 10 years ago by johan
I am also interested in XEP-0027 support for the XMPP module of Pidgin. I cannot promise anything, but I will try to do my own implementation. Is it ok to submit patches here if it will ever be ready? What is your oppinion, nwalp?
comment:14 Changed 10 years ago by deryni
- Milestone set to Patches welcome
Submitting patches here is fine. As is creating a new ticket and referencing this ticket (and might be better since then you would be the owner of that ticket). I believe there has been some work recently on some newer encryption proposals and it might make sense to look at those as well (though I haven't paid them much attention so I have no idea if they are actually usable at the moment). nwalp is currently on hiatus, his tickets just haven't been re-assigned.
comment:15 Changed 9 years ago by Workoft
+1 Pidgin needs support for more Jabber features
comment:16 Changed 9 years ago by AZ
This issue is caused by a third party plugin. We have no control over these plugins. Please report this problem to the authors of this third party plugin.
Here comes a perl plugin for pidgin (gtk2) which implements the encryption part of XEP-0027.
The signing part makes no sense, as it is subject to replay attacks.
Due to difficulties with integrating the plugin into the conversation window, one may now enable encryption by typing ENABLEPGP in the conversation window and disable it by typing DISABLEPGP in the conversation window (no leading spaces allowed). If any other encryption is in place (e.g. OTR or gaim-encryption), you won't be able to enable gpg encryption - as the commands are not detected ;).
By default, this plugin will look for the jid in the gpg database (using the gpg console application in the search path) and use the found key if found. Entering secrets requires gpg-agent to be installed, but gpg-agent is run by default.
Changeing gpg integration requires modification of the plugin source code.
I've written and tested this plugin on an ubuntu intrepid x86 installation.
comment:17 Changed 9 years ago by AZ
# XEP-0027 Plugin for Pidgin (GTK).
#
# This plugin implements encryption and decryption of
# jabber messages according to XEP-0027.
# It does not sign nor verify <presence> or <status> messages,
# as these only indicate that XEP-0027 is present at the remote party.
#
# Configuration:
# * configure gpg-agent manually
# * make sure gpg and gpg-agent are in %PATH%
# * JID => GPG key mapping is done search for jid in gpg,
# but may be overridden using config dialog.
#
# I don't take any liabilitity.
#
# 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/>.
#
# (C) 2008, Michael Braun <michael-dev@fami-braun.de>
use strict;
use File::Temp qw /tempfile tmpnam/;
use Purple;
use File::Touch;
use Config::INI::Simple;
#use Pidgin;
use constant TRUE => 1 ;
use constant FALSE => 0 ;
use constant CONFIGFILENAME => "pidgin-openpgp.ini";
# *********** PLUGIN META DATA *************
our %PLUGIN_INFO = (
perl_api_version => 2,
name => "OpenPGP Plugin",
version => "0.1",
summary => "Send and receive gpg encrypted messages.",
description => "XEP-0027",
author => "Michael Braun <michael-dev\@fami-braun.de>",
url => "http://www.fami-braun.de",
load => "plugin_load",
unload => "plugin_unload",
prefs_info => "prefs_info_cb",
);
my %CONNSTATE = ();
my %GPGMAP = ();
my $gpg = qx(gpg-agent --daemon)."gpg";
sub plugin_init {
return %PLUGIN_INFO;
}
# *********** DECODE received messages **********
# TODO: fork into background, timeout, check return code of gpg, asking for key
# send error message on decryption failure
sub decrypt {
my $ciphertext = shift;
my ($input, $fni) = tempfile();
chmod 0600, $fni;
print $input "-----BEGIN PGP MESSAGE-----\n\n$ciphertext\n-----END PGP MESSAGE-----\n";
close $input;
my $fno = tmpnam();
my $ret = system("$gpg --batch --output $fno --use-agent -q -d $fni");
if ($ret != 0) {
return "*decryption failed*";
}
my $output;
open $output, "<", $fno;
my @plain = <$output>;
close $output;
unlink $fni;
unlink $fno;
return join("",@plain);
}
#******* verify received messages *******
# TODO: implement
# *********** INCOMING handler *************
# TODO: indicate encryption state of message, replace body content,
# OPTIONAL: detect signed presence and status tags and inform the user about
# the remote capabilites
sub conv_receiving_jabber
{
my ($conn, $node, $data) = @_;
my $encrypted_node = $node->get_child_with_namespace("x","jabber:x:encrypted");
my $body = $node->get_child("body");
if (not defined($encrypted_node)) {
Purple::Debug::misc(" * ", "no opengpg message");
if (defined($body)) {
my $newmsg ="?NOGPG?";
$node->get_child("body")->insert_data($newmsg,length($newmsg));
}
} else {
Purple::Debug::misc("opengpg received", $conn->get_display_name().", ".$node->get_attrib("id").", $data\n");
my $crypted = $encrypted_node->get_data();
my $plaintext = decrypt($crypted);
#does not work: results in no message to be shown
#$node->get_child("body")->free();
#$node->new_child("body");
my $newmsg = "?PGP?$plaintext";
$node->get_child("body")->insert_data($newmsg,length($newmsg));
}
@_[2] = $node;
Purple::Debug::misc("opengpg received", $node->to_str(0)."\n");
#return $node;
}
sub conv_receiving_msg
{
my ($account, $from, $message, $conv, $flags, $data) = @_;
my $xm = @_[2];
Purple::Debug::misc("received", "$message\n");
$message =~s/<body>(.*)<\/body>/$1/;
if ($message =~/\?NOGPG\?$/) {
$message =~s/(.*)\?NOGPG\?$/<i><span title="unencrypted">[U]<\/span><\/i> $1/;
} else {
$message =~s/.*\?PGP\?/<i><span title="encrypted">[E]<\/span><\/i> /;
}
$message = "<body>".$message."</body>";
@_[2] = $message;
Purple::Debug::misc("openpgpplugin", "replaced: $message\n");
}
# *********** ENCRYPT outgoing messages ************
# TODO: fork into background, display error message
sub encrypt {
my $plaintext = shift;
my $target = shift;
if (exists($GPGMAP{$target})) {
$target = $GPGMAP{$target};
}
my ($input, $fni) = tempfile();
chmod 0600, $fni;
print $input $plaintext;
close $input;
my $fno = tmpnam();
my $ret = system("$gpg --batch --output $fno --use-agent -q --armor -r \"$target\" -e $fni");
my $output;
open $output, "<", $fno;
my @plain = <$output>;
chomp(@plain);
close $output;
unlink $fni;
unlink $fno;
if ($ret > 0) {
Purple::Debug::misc("openpgp","encryption failed\n");
return "";
}
# find first empty line
for (; not ($plain[0] eq "");) {shift(@plain);};
shift(@plain);
pop(@plain);
Purple::Debug::misc("openpgp","encryption successfull\n");
return join("\n",@plain);
}
# *********** SIGN outgoing messages *********
# TODO: implement
#
# ********** OUTGOING handler ************
# encrypt outgoing message nodes and sign outgoing status and presence nodes
# TODO: implement, configure key to use
sub info_enable_gpg {
my $target = shift;
require Gtk2;
my $frame = Gtk2::Window->new();
my $dialog = Gtk2::MessageDialog->new ($frame,
'destroy-with-parent',
'info', # message type
'ok', # which set of buttons?
"Encryption for $target enabled.");
$dialog->run;
$dialog->destroy;
$frame->destroy;
}
sub info_disable_gpg {
my $target = shift;
require Gtk2;
my $frame = Gtk2::Window->new();
my $dialog = Gtk2::MessageDialog->new ($frame,
'destroy-with-parent',
'info', # message type
'ok', # which set of buttons?
"Encryption for $target disabled.");
$dialog->run;
$dialog->destroy;
$frame->destroy;
}
sub info_err_encrypt {
my $target = shift;
require Gtk2;
my $frame = Gtk2::Window->new();
my $dialog = Gtk2::MessageDialog->new ($frame,
'destroy-with-parent',
'error', # message type
'cancel', # which set of buttons?
"Could not encrypt message for $target.\nPlease check gpg settings and verify gpg-agent is running.");
$dialog->run;
$dialog->destroy;
$frame->destroy;
}
sub conv_sending_msg
{
my ($conn, $node, $data) = @_;
# get text node
my $bnode = $node->get_child("body");
if (not defined($bnode)) { return; }
# fetch target / connid
my $target = $node->get_attrib("to");
$target =~s/\/.*//; # name@host/path => remove path
my $connid = $target;
if (not exists($CONNSTATE{$connid})) {$CONNSTATE{$connid} = 1; } # default off
Purple::Debug::misc("openpgp","sending to $target\n");
# fetch message
my $msg = $bnode->get_data();
# parse commands, decide on encryption
my $do_encrypt = $CONNSTATE{$connid};
Purple::Debug::misc("openpgp","sending message = $msg\n");
if ($msg =~/^ENABLEPGP/) {
Purple::Debug::misc("openpgp","enable pgp\n");
$CONNSTATE{$connid} = 0;
$msg = "The remote party <b>enabled</b> XEP-0027 (OpenPGP) encryption.";
$do_encrypt = 0;
info_enable_gpg($target);
} elsif ($msg =~/^DISABLEPGP/) {
Purple::Debug::misc("openpgp","disable pgp\n");
$CONNSTATE{$connid} = 1;
$msg = "The remote party <b>disabled</b> XEP-0027 (OpenPGP) encryption.";
info_disable_gpg($target);
}
if ($do_encrypt == 1) { return; }
# drop html node
my $htmlbnode = $node->get_child("html");
if (defined($htmlbnode)) { $htmlbnode->free(); }
# encrypt data
my $crypted = encrypt($msg, $target);
if ($crypted eq "") {
Purple::Debug::misc("openpgp","sending error message\n");
info_err_encrypt($target);
#$conn->get_im_data()->write("OpenPGP", "<b>Cannot encrypt last message.</b>", 0, 0);
#$node->free(); -> crashes.
# remove plain data
$msg = "Failed to encrypt message.";
$bnode->free();
$node->new_child("body")->insert_data($msg, length($msg));
@_[1] = $node;
return;
}
# insert encrypted data
my $x = $node->new_child("x");
$x->set_attrib("xmlns","jabber:x:encrypted");
$x->insert_data($crypted, length($crypted));
# remove plain data
$msg = "This is a protected copy.";
$bnode->free();
$node->new_child("body")->insert_data($msg, length($msg));
# ensure new node is used!
@_[1] = $node;
Purple::Debug::misc("openpgp sending new", $node->to_str(0)."\n");
}
#****** modified conversation ******
# here to come: integrate into conversation window
#sub conv_switched {
# Purple::Debug::misc("openpgpplugin", "conv switched\n");
#
#}
#
#sub conv_deleted {
# Purple::Debug::misc("openpgpplugin", "conv deleted:".join(",",@_)."\n");
#
#}
#
#sub conv_created {
# Purple::Debug::misc("openpgpplugin", "conv created:".join(",",@_)."\n");
# my $conv = shift; # PurpleConversation
# init_dialog($conv);
#}
sub init_dialog {
# require Gtk2;
# require Pidgin::IMHtmlToolbar;
# Purple::Debug::misc("openpgpplugin", "conv init\n");
#
# my $conv = shift;
# my $button = Gtk2::Button->new();
# $button->set_relief("GTK_RELIEF_NONE");
# bbox = gtkconv->toolbar;
#
# gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
#
# bwbox = gtk_hbox_new(FALSE, 0);
# gtk_container_add(GTK_CONTAINER(button), bwbox);
# icon = otr_icon(NULL, TRUST_NOT_PRIVATE, 1);
# gtk_box_pack_start(GTK_BOX(bwbox), icon, TRUE, FALSE, 0);
# label = gtk_label_new(NULL);
# gtk_box_pack_start(GTK_BOX(bwbox), label, FALSE, FALSE, 0);
#
# if (prefs.show_otr_button) {
# gtk_widget_show_all(button);
# }
}
# ************ CONFIG handler **********
# configure keys to use per contact and per account / global
# TODO: implement, where to store this information
my $LOCKED = 0;
my %JIDINLINE = ();
my %ITEMS=();
sub info_err_savecfg {
my $target = shift;
require Gtk2;
my $frame = Gtk2::Window->new();
my $dialog = Gtk2::MessageDialog->new ($frame,
'destroy-with-parent',
'error', # message type
'cancel', # which set of buttons?
"Could not save config in $target.");
$dialog->run;
$dialog->destroy;
$frame->destroy;
}
sub SaveCfg {
my $cfgfile = Purple::Prefs::get_string("/plugins/core/openpgp/configfile");
Purple::Debug::misc("openpgpplugin", "save:" .join(",",keys(%GPGMAP))." => ".join(",",values(%GPGMAP))." into $cfgfile\n");
if (not -e $cfgfile) {
touch($cfgfile);
}
if (not -w $cfgfile) {
info_err_savecfg($cfgfile);
return;
}
my $conf = new Config::INI::Simple;
foreach my $key (keys(%GPGMAP)) {
$conf->{default}->{$key} = $GPGMAP{$key};
}
$conf->write($cfgfile);
}
# file content:
# JID=key
sub LoadCfg {
my $conf = new Config::INI::Simple;
my $cfgfile = Purple::Prefs::get_string("/plugins/core/openpgp/configfile");
if (not -r $cfgfile) {
%GPGMAP = ();
return;
}
$conf->read($cfgfile);
use Data::Dumper;
Purple::Debug::misc("openpgpplugin", Dumper($conf->{default})."\n");
%GPGMAP = ();
foreach my $key (keys(%{$conf->{default}})) {
$GPGMAP{$key} = $conf->{default}->{$key};
}
Purple::Debug::misc("openpgpplugin", "load:" .join(",",keys(%GPGMAP))." => ".join(",",values(%GPGMAP))."\n");
}
sub delete_event {
Purple::Debug::misc("openpgpplugin", "closing config window\n");
# closing config window
$LOCKED = 0;
}
sub on_ok {
Purple::Debug::misc("openpgpplugin", "ok pressed\n");
my $self = shift;
my $frame = shift;
$frame->destroy;
}
sub on_add {
Purple::Debug::misc("openpgpplugin", "add pressed\n");
my $self = shift;
my $data = shift;
my $ppref1 = $data->[0];
my $ppref2 = $data->[1];
my $xtable = $data->[2];
my $jid = $ppref1->get_text();
my $key = $ppref2->get_text();
if (exists($GPGMAP{$jid})) {
$GPGMAP{$jid} = $key;
Purple::Debug::misc("openpgpplugin", "replacing $jid => $key\n");
my $i = $JIDINLINE{$jid};
$ITEMS{$i}->[1]->set_text($key);
&SaveCfg;
} else {
my $i = keys(%GPGMAP) +2;
Purple::Debug::misc("openpgpplugin", "adding $jid => $key with i=$i\n");
$GPGMAP{$jid} = $key;
$xtable->resize($i+1, 3);
add_to_table($xtable, $jid, $i);
&SaveCfg;
}
$ppref1->set_text("");
$ppref2->set_text("");
}
sub on_del {
Purple::Debug::misc("openpgpplugin", "del pressed\n");
my $self = shift;
my $data = shift;
my $xtable = $data->[0];
my $i = $data->[1];
my $jid = $ITEMS{$i}->[3];
# remove from GPGMAP
Purple::Debug::misc("openpgpplugin", "deleting entry $i ($jid => $GPGMAP{$jid})\n");
delete($GPGMAP{$jid});
# move all consecutive items up
for (my $j = $i; $j < keys(%ITEMS)+1; $j++) {
my $jid = $ITEMS{$j+1}->[3];
$ITEMS{$j}->[3] = $jid; # move jid down
$ITEMS{$j}->[0]->set_text($ITEMS{$j+1}->[0]->get_text()); # move jid label down
$ITEMS{$j}->[1]->set_text($ITEMS{$j+1}->[1]->get_text()); # move key label down
$JIDINLINE{$jid} = $j;
}
# remove last line
my $j = keys(%ITEMS)+1;
$ITEMS{$j}->[0]->destroy;
$ITEMS{$j}->[1]->destroy;
$ITEMS{$j}->[2]->destroy;
delete($ITEMS{$j});
$xtable->resize(keys(%ITEMS)+2,3);
&SaveCfg;
}
sub add_to_table {
my ($xtable, $jid, $i) = @_;
Purple::Debug::misc("openpgpplugin", "show $jid\n");
my $ppref1 = Gtk2::Label->new("$jid");
$xtable->attach_defaults($ppref1, 0, 1, $i, $i+1);
$ppref1->set_selectable(TRUE);
$ppref1->show;
my $value = $GPGMAP{$jid};
my $ppref2 = Gtk2::Label->new("$value");
$xtable->attach_defaults($ppref2, 1, 2, $i, $i+1);
$ppref2->set_selectable(TRUE);
$ppref2->show;
my $button = Gtk2::Button->new("Del");
$button->signal_connect(clicked => \&on_del, [$xtable, $i]);
$xtable->attach_defaults($button, 2, 3, $i, $i+1);
$button->show;
$JIDINLINE{$jid} = $i;
$ITEMS{$i} = [$ppref1, $ppref2, $button, $jid];
}
sub prefs_info_cb {
Purple::Debug::misc("openpgpplugin", "cb\n");
if ($LOCKED > 0) { return; }
$LOCKED = 1;
# *** JID => GPG-KeyID ***
require Gtk2;
my $frame = Gtk2::Window->new("toplevel");
$frame->set_title("OpenPGP Plugin Konfiguration");
$frame->signal_connect(delete_event => \&delete_event);
my $box1 = Gtk2::VBox->new(FALSE, 0);
$frame->add($box1);
$box1->show;
my $ppref = Gtk2::Label->new("Start gpg-agent first.");
$box1->pack_start($ppref, TRUE, TRUE, 0);
$ppref->show;
my $ppref = Gtk2::Label->new("Use ENABLEPGP in conversation to enable encryption.");
$box1->pack_start($ppref, TRUE, TRUE, 0);
$ppref->show;
my $ppref = Gtk2::Label->new("Use DISABLEPGP in conversation to disable encryption.");
$box1->pack_start($ppref, TRUE, TRUE, 0);
$ppref->show;
my $ppref = Gtk2::Label->new("This plugin will DEFAULT to the jabber-id to lookup the remote gpg key.\nThe gpg binary is searched in the common (OS-dependent) path.");
$box1->pack_start($ppref, TRUE, TRUE, 0);
$ppref->show;
my $separator = Gtk2::HSeparator->new;
$box1->pack_start($separator, TRUE, TRUE, 0);
$separator->show;
# *** maps ***
my $xtable = Gtk2::Table->new(keys(%GPGMAP)+1,3,TRUE);
$box1->pack_start($xtable, TRUE, TRUE, 0);
$xtable->show;
my $i = 0;
my $ppref1 = Gtk2::Label->new("JID");
$xtable->attach_defaults($ppref1, 0, 1, $i, $i+1);
$ppref1->set_selectable(FALSE);
$ppref1->show;
my $ppref2 = Gtk2::Label->new("Key-ID");
$xtable->attach_defaults($ppref2, 1, 2, $i, $i+1);
$ppref2->set_selectable(FALSE);
$ppref2->show;
$i = $i + 1;
my $ppref1 = Gtk2::Entry->new;
$xtable->attach_defaults($ppref1, 0, 1, $i, $i+1);
$ppref1->show;
my $ppref2 = Gtk2::Entry->new;
$xtable->attach_defaults($ppref2, 1, 2, $i, $i+1);
$ppref2->show;
my $button = Gtk2::Button->new("Add");
$button->signal_connect(clicked => \&on_add, [$ppref1, $ppref2, $xtable]);
$xtable->attach_defaults($button, 2, 3, $i, $i+1);
$button->show;
$i = $i + 1;
foreach my $jid (keys(%GPGMAP)) {
add_to_table($xtable, $jid, $i);
$i=$i+1;
}
my $separator = Gtk2::HSeparator->new;
$box1->pack_start($separator, TRUE, TRUE, 0);
$separator->show;
# *** buttons ****
my $button = Gtk2::Button->new("Ok");
$button->signal_connect(clicked => \&on_ok, $frame);
$box1->pack_start($button, TRUE, TRUE, 0);
$button->show;
$frame->show;
return undef;
}
# ****** ONLOAD *******
sub plugin_load {
my $plugin = shift;
Purple::Debug::misc("openpgpplugin", "plugin_load() - OpenPGP Plugin Loaded.\n");
Purple::Prefs::add_none("/plugins/core/openpgp");
Purple::Prefs::add_string("/plugins/core/openpgp/configfile", Purple::Util::user_dir()."/".CONFIGFILENAME);
# A pointer to the handle to which the signal belongs needed by the callback function
my $accounts_handle = Purple::Accounts::get_handle();
my $jabber = Purple::Find::prpl("prpl-jabber");
Purple::Signal::connect($jabber, "jabber-receiving-xmlnode", $plugin, \&conv_receiving_jabber, "receiving jabber node");
Purple::Signal::connect($jabber, "jabber-sending-xmlnode", $plugin, \&conv_sending_msg, "sending jabber node");
my $conv = Purple::Conversations::get_handle();
Purple::Signal::connect($conv, "receiving-im-msg", $plugin, \&conv_receiving_msg, "receiving im message");
# Purple::Signal::connect($conv, "conversation-switched", $plugin, \&conv_switched, "conversation switched");
# Purple::Signal::connect($conv, "deleting-conversation", $plugin, \&conv_deleted, "conversation deleted");
# Purple::Signal::connect($conv, "conversation-created", $plugin, \&conv_created, "conversation created");
&LoadCfg();
}
# ****** ON UNLOAD *******
sub plugin_unload {
my $plugin = shift;
Purple::Debug::misc("openpgpplugin", "plugin_unload() - OpenPGP Plugin Unloaded.\n");
}
comment:19 Changed 7 years ago by segler_alex
please test my new plugin pidgin-gpg with full linux/windows support. https://github.com/segler-alex/Pidgin-GPG/wiki
comment:20 Changed 5 years ago by nwalp
- Status changed from new to pending
Since we have a plugin listed here and on the plugins page that seems to implement this, I'm going to close the ticket. If someone can point to strong demand for mainline inclusion, I'm open to listening to it, but I don't see much demand from my chair.
comment:21 Changed 5 years ago by trac-robot
- Status changed from pending to closed
This ticket was closed automatically by the system. It was previously set to a Pending status and hasn't been updated within 14 days.




Hi, nwalp, have you started this one ? I am interested.