This is a patch to add some support for OpenPGP encryption and signing
to the Jabber protocol in Gaim. It requires libgpgme and is incomplete.

* Copyright (C) 2006 Ted Percival <ted@midg3t.net>
*
* 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.

diff -ruN -x Makefile.in -x Makefile -x '*.la' -x plugins gaim-2.0.0+beta3/configure.ac gaim-new/configure.ac
--- gaim-2.0.0+beta3/configure.ac	2006-03-26 17:45:35.000000000 +1000
+++ gaim-new/configure.ac	2006-08-16 13:47:12.000000000 +1000
@@ -1695,6 +1695,8 @@
 AC_SUBST(enable_doxygen)
 AC_SUBST(enable_dot)
 
+AM_PATH_GPGME
+
 AC_CONFIG_COMMANDS_PRE([
 	if test -e VERSION; then
 		cp -p VERSION VERSION.ac-save
diff -ruN -x Makefile.in -x Makefile -x '*.la' -x plugins gaim-2.0.0+beta3/src/Makefile.am gaim-new/src/Makefile.am
--- gaim-2.0.0+beta3/src/Makefile.am	2006-03-26 01:45:49.000000000 +1000
+++ gaim-new/src/Makefile.am	2006-08-16 13:47:12.000000000 +1000
@@ -342,7 +342,8 @@
 	$(SM_LIBS) \
 	$(INTLLIBS) \
 	$(GTKSPELL_LIBS) \
-	$(STARTUP_NOTIFICATION_LIBS) 
+	$(STARTUP_NOTIFICATION_LIBS) \
+	$(GPGME_LIBS)
 
 AM_CPPFLAGS = \
 	-DBR_PTHREADS=0 \
diff -ruN -x Makefile.in -x Makefile -x '*.la' -x plugins gaim-2.0.0+beta3/src/protocols/jabber/buddy.c gaim-new/src/protocols/jabber/buddy.c
--- gaim-2.0.0+beta3/src/protocols/jabber/buddy.c	2006-03-27 03:45:53.000000000 +1000
+++ gaim-new/src/protocols/jabber/buddy.c	2006-08-16 13:47:12.000000000 +1000
@@ -30,6 +30,7 @@
 
 #include "buddy.h"
 #include "chat.h"
+#include "gpg.h"
 #include "jabber.h"
 #include "iq.h"
 #include "presence.h"
@@ -95,7 +96,7 @@
 }
 
 JabberBuddyResource *jabber_buddy_track_resource(JabberBuddy *jb, const char *resource,
-		int priority, JabberBuddyState state, const char *status)
+		int priority, JabberBuddyState state, const char *status, JabberGpgSignature *sig)
 {
 	JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
 
@@ -111,6 +112,9 @@
 	if(jbr->status)
 		g_free(jbr->status);
 	jbr->status = g_strdup(status);
+	if (jbr->presence_sig)
+		jabber_gpg_sig_free(jbr->presence_sig);
+	jbr->presence_sig = sig;
 
 	return jbr;
 }
@@ -126,6 +130,8 @@
 		g_free(jbr->status);
 	if(jbr->thread_id)
 		g_free(jbr->thread_id);
+	if(jbr->presence_sig)
+		jabber_gpg_sig_free(jbr->presence_sig);
 	g_free(jbr);
 }
 
@@ -1456,3 +1462,21 @@
 			_("Search Directory"), GAIM_CALLBACK(jabber_user_search_ok),
 			_("Cancel"), NULL, js);
 }
+
+#if 0
+
+GList *jabber_buddy_get_signatures(JabberBuddy *jb)
+{
+	GList *sigs = NULL, *resource;
+	JabberGpgSignature *signature;
+
+	g_return_val_if_fail(jb != NULL, NULL);
+	
+	for (resource = jb->resources; resource; resource = resource->next) {
+		
+	}
+
+	sigs = g_list_append
+
+}
+#endif
diff -ruN -x Makefile.in -x Makefile -x '*.la' -x plugins gaim-2.0.0+beta3/src/protocols/jabber/buddy.h gaim-new/src/protocols/jabber/buddy.h
--- gaim-2.0.0+beta3/src/protocols/jabber/buddy.h	2006-01-02 02:25:17.000000000 +1000
+++ gaim-new/src/protocols/jabber/buddy.h	2006-08-16 13:47:12.000000000 +1000
@@ -61,6 +61,7 @@
 	char *status;
 	JabberCapabilities capabilities;
 	char *thread_id;
+	JabberGpgSignature *presence_sig;
 } JabberBuddyResource;
 
 void jabber_buddy_free(JabberBuddy *jb);
@@ -69,7 +70,7 @@
 JabberBuddyResource *jabber_buddy_find_resource(JabberBuddy *jb,
 		const char *resource);
 JabberBuddyResource *jabber_buddy_track_resource(JabberBuddy *jb, const char *resource,
-		int priority, JabberBuddyState state, const char *status);
+		int priority, JabberBuddyState state, const char *status, JabberGpgSignature *sig);
 void jabber_buddy_resource_free(JabberBuddyResource *jbr);
 void jabber_buddy_remove_resource(JabberBuddy *jb, const char *resource);
 const char *jabber_buddy_get_status_msg(JabberBuddy *jb);
@@ -91,4 +92,10 @@
 
 void jabber_user_search_begin(GaimPluginAction *);
 
+/**
+ * Gets a list of presence signatures. The returned list of signatures
+ * are all dynamically alocated and should be freed somehow.
+ */
+GList *jabber_buddy_get_signatures(JabberBuddy *jb);
+
 #endif /* _GAIM_JABBER_BUDDY_H_ */
diff -ruN -x Makefile.in -x Makefile -x '*.la' -x plugins gaim-2.0.0+beta3/src/protocols/jabber/gpg.c gaim-new/src/protocols/jabber/gpg.c
--- gaim-2.0.0+beta3/src/protocols/jabber/gpg.c	1970-01-01 10:00:00.000000000 +1000
+++ gaim-new/src/protocols/jabber/gpg.c	2006-08-16 13:47:12.000000000 +1000
@@ -0,0 +1,808 @@
+/**
+ * @file gpg.c
+ *
+ * Gaim - OpenPGP enhancements to the Jabber protocol.
+ *
+ * Copyright (C) 2006 Ted Percival <ted@midg3t.net>
+ *
+ * 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.
+ *
+ * This source aims to implement JEP-0027 as documented at
+ * <http://www.jabber.org/jeps/jep-0027.html>.
+ *
+ * If this code seems too slow, or seems to hang, have a look at using
+ * GDK callbacks as per the gpgme manual. In my testing, gpgme has always
+ * been fast enough, so I haven't bothered adding the callbacks.
+ *   -tedp
+ */
+
+#include <gpgme.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <locale.h>
+#include <time.h>
+
+#include "gpg.h"
+#include "internal.h"
+#include "request.h" /* gaim_request_input */
+
+#if !defined(_FILE_OFFSET_BITS) || _FILE_OFFSET_BITS != 64
+#warning FILE_OFFSET_BITS is (probably) wrong or unset
+#endif
+
+static gpgme_ctx_t jabber_gpg_ctx = NULL;
+
+static void
+strip_armor(char *msg);
+
+static char*
+add_signature_armor(const char *msg, const char *sig);
+
+void
+xmlnode_wipe(xmlnode *node);
+
+static char*
+add_crypto_armor(const char *ciphertext);
+
+/*static size_t locksize();*/
+
+#if 0 /* Not yet implemented */
+static gpgme_error_t
+jabber_gpg_passphrase_cb(void *parent, const char *uid_hint,
+		const char *passphrase_info, int prev_was_bad, int fd);
+#endif
+
+static char*
+jabber_gpg_sign(const char *string);
+
+static JabberGpgSignature*
+jabber_gpg_verify_xmlnode(const xmlnode *node, const char *element_name);
+
+static JabberGpgSignature*
+jabber_gpg_verify(const char *status, const char *sig_base64);
+
+static char*
+jabber_gpg_decrypt(const char *ciphertext);
+
+static char*
+jabber_gpg_encrypt(const char *msg, const char *keyid, gpgme_error_t *reason);
+
+
+#if 0
+static size_t locksize() {
+	const size_t pagesize = sysconf(_SC_PAGESIZE);
+	if (pagesize > PASSPHRASE_BUFLEN)
+		return pagesize;
+	if (PASSPHRASE_BUFLEN % pagesize)
+		return (((PASSPHRASE_BUFLEN / pagesize) + 1) * pagesize);
+	return PASSPHRASE_BUFLEN;
+}
+#endif
+
+#if 0 /* Gtk stuff should not be in this file */
+static gpgme_error_t
+jabber_gpg_passphrase_dialog(GtkWidget *parent, const char *prompt, int fd)
+{
+	GtkWidget *dialog, *hbox, *label, *entry,/* *checkbox,*/ *promptlbl;
+	GtkBox *vbox;
+	gint result;
+	const gchar *passphrase;
+
+	dialog = gtk_dialog_new_with_buttons(_("Enter passphrase"),
+			GTK_WINDOW(parent),
+			GTK_DIALOG_DESTROY_WITH_PARENT,
+			GTK_STOCK_OK, GTK_RESPONSE_OK,
+			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+			NULL);
+	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
+	gtk_container_set_border_width(GTK_CONTAINER(dialog), 10);
+	vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);
+	gtk_box_set_spacing(vbox, 10);
+
+	hbox = gtk_hbox_new(FALSE, 10);
+
+	label = gtk_label_new_with_mnemonic(_("_Passphrase:"));
+	promptlbl = gtk_label_new(prompt);
+
+	entry = gtk_entry_new();
+	gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
+	gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+	gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
+
+//	checkbox = gtk_check_button_new_with_mnemonic(_("_Save passphrase (not recommended)"));
+
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10);
+	gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
+	gtk_box_pack_start(vbox, promptlbl, TRUE, FALSE, 0);
+	gtk_box_pack_start(vbox, hbox, TRUE, FALSE, 0);
+//	gtk_box_pack_start(vbox, checkbox, TRUE, FALSE, 0);
+
+	gtk_widget_show_all(GTK_WIDGET(dialog));
+
+	result = gtk_dialog_run(GTK_DIALOG(dialog));
+	if (result == GTK_RESPONSE_OK) {
+		passphrase = gtk_entry_get_text(GTK_ENTRY(entry));
+		write(fd, passphrase, strlen(passphrase));
+		write(fd, "\n", 1);
+	}
+	/* XXX: Is there a way to ensure the passphrase is overwritten in memory? */
+	gtk_widget_destroy(dialog);
+	return (result == GTK_RESPONSE_OK) ? GPG_ERR_NO_ERROR : GPG_ERR_CANCELED;
+}
+#endif
+
+#if 0 /* This might be useful later, but I'll probably just rewrite it. */
+static gpgme_error_t
+jabber_gpg_passphrase_cb(void *parent, const char *uid_hint,
+		const char *passphrase_info, int prev_was_bad, int fd)
+{
+	char *message;
+	int result;
+	message = g_strdup_printf("%s%s%s",
+			prev_was_bad ? _("Wrong passphrase. ") : "",
+			_("Please enter the passphrase for\n"),
+			uid_hint ? uid_hint : _("(unknown key)"));
+	result = jabber_gpg_passphrase_dialog((GtkWidget *)parent,
+			message, fd);
+	g_free(message);
+	return result;
+}
+#endif
+
+/**
+ * Initialise Jabber GPGME context.
+ */
+gboolean
+jabber_gpg_init(void)
+{
+	gboolean success = FALSE;
+	if (gpgme_new(&jabber_gpg_ctx) == GPG_ERR_NO_ERROR) {
+		if (gpgme_set_protocol(jabber_gpg_ctx, GPGME_PROTOCOL_OpenPGP) == GPG_ERR_NO_ERROR) {
+			if (gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP) == GPG_ERR_NO_ERROR) {
+				gpgme_set_armor(jabber_gpg_ctx, 1);
+
+				/* TODO: Set callback */
+/*				gpgme_set_passphrase_cb(jabber_gpg_ctx, callback, NULL);*/
+
+				/* straight out of the gpgme manual */
+				gpgme_check_version(NULL);
+				gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
+				gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
+
+				success = TRUE;
+			}
+		}
+		if (success != TRUE) {
+			gpgme_release(jabber_gpg_ctx);
+			jabber_gpg_ctx = NULL;
+		}
+	}
+	return success;
+}
+
+/**
+ * Clean up after gpgme.
+ * When all is said and done.
+ *
+ * FIXME: As of 2006-07-31, this isn't ever called.
+ * It should be called by a plugin cleanup function.
+ */
+void
+jabber_gpg_finish(void)
+{
+	g_return_if_fail(jabber_gpg_ctx != NULL);
+	gpgme_release(jabber_gpg_ctx);
+}
+
+#define PASSPHRASE_BUFLEN 64
+/* TODO: Determine if there's a maximum signature length, or that the
+ * signature buffer needs to be dynamically allocated. */
+#define SIGBUFLEN 256
+
+/**
+ * TODO: Use the user's chosen key for signing, rather than GPGME's default.
+ */
+static char*
+jabber_gpg_sign(const char *string)
+{
+	gpgme_data_t plaintext, sig;
+	char *output = NULL;
+
+	g_return_val_if_fail(string != NULL, NULL);
+	g_return_val_if_fail(jabber_gpg_ctx != NULL, NULL);
+
+	if (GPG_ERR_NO_ERROR != gpgme_data_new_from_mem(&plaintext, string, strlen(string), 0))
+		return NULL;
+	if (GPG_ERR_NO_ERROR != gpgme_data_new(&sig)) {
+		gpgme_data_release(plaintext);
+		return NULL;
+	}
+
+	if (GPG_ERR_NO_ERROR == gpgme_op_sign(jabber_gpg_ctx, plaintext, sig, GPGME_SIG_MODE_DETACH)) {
+		if (-1 != gpgme_data_seek(sig, 0, SEEK_SET)) {
+			if ((output = malloc(SIGBUFLEN))) {
+				output[SIGBUFLEN-1] = '\0';
+				if (gpgme_data_read(sig, output, SIGBUFLEN-1) > 0) {
+					strip_armor(output);
+				}
+				else {
+					free(output);
+					output = NULL;
+				}
+			}
+		}
+	}
+
+	gpgme_data_release(plaintext);
+	gpgme_data_release(sig);
+	
+	return output;
+}
+
+/**
+ *
+ */
+gboolean
+jabber_gpg_sign_presence(xmlnode *presence)
+{
+	xmlnode *x, *status_node;
+	char *sig_block, *status_msg;
+	
+	g_return_val_if_fail(presence != NULL, FALSE);
+
+	if ((status_node = xmlnode_get_child(presence, "status")))
+		status_msg = xmlnode_get_data(status_node);
+	else
+		status_msg = NULL;
+
+	sig_block = jabber_gpg_sign(status_msg ? status_msg : "");
+	if (status_msg)
+		g_free(status_msg);
+	
+	if (sig_block) {
+		x = xmlnode_new_child(presence, "x");
+		xmlnode_set_attrib(x, "xmlns", "jabber:x:signed");
+		xmlnode_insert_data(x, sig_block, -1);
+		g_free(sig_block);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+/* Point to the first character after the empty line, and put a NUL-terminator at the end of the sig */
+static void
+strip_armor(char *msg)
+{
+	char *str;
+
+	g_return_if_fail(msg != NULL);
+
+	str = strstr(msg, "\n-----END ");
+	if (str)
+		*str = '\0';
+
+	str = strstr(msg, "\n\n");
+	if (str) {
+		str += 2;
+		g_return_if_fail(str < msg + strlen(msg));
+		memmove(msg, str, strlen(str)+1);
+	}
+}
+
+/**
+ * Add the required armor for libgpgme to process the message and signature
+ * as a signed message.
+ */
+static char*
+add_signature_armor(const char *msg, const char *sig)
+{
+	return g_strdup_printf("-----BEGIN PGP SIGNED MESSAGE-----\n"
+			"Hash: SHA1\n"
+			"\n"
+			"%s\n"
+			"-----BEGIN PGP SIGNATURE-----\n"
+			"Version: Teddy Spaghetti's code fusion of Gaim, Jabber, and GPG\n"
+			"\n"
+			"%s\n"
+			"-----END PGP SIGNATURE-----\n",
+			msg, sig);
+}
+
+/**
+ * Adds the required armor for libgpgme to process the message as an encrypted
+ * message.
+ */
+static char*
+add_crypto_armor(const char *ciphertext) {
+	return g_strdup_printf("-----BEGIN PGP MESSAGE-----\n"
+			"Version: Teddy Spaghetti's crypto-fu for Gaim & Jabber\n"
+			"\n"
+			"%s\n"
+			"-----END PGP MESSAGE-----\n",
+			ciphertext);
+}
+
+/**
+ * Check a presence signature.
+ * @param presence Incoming <presence> node.
+ * @return New JabberGpgSignature, or NULL if the presence is unsigned (or error).
+ */
+JabberGpgSignature*
+jabber_gpg_verify_presence(const xmlnode *presence)
+{
+	return jabber_gpg_verify_xmlnode(presence, "status");
+}
+
+/**
+ * Check a message signature.
+ * @param message Incoming <message> node.
+ * @return New JabberGpgSignature, or NULL if the message is unsigned (or error).
+ */
+JabberGpgSignature*
+jabber_gpg_verify_message(const xmlnode *message)
+{
+	return jabber_gpg_verify_xmlnode(message, "body");
+}
+
+/**
+ * Check the signature on an xmlnode.
+ */
+static JabberGpgSignature*
+jabber_gpg_verify_xmlnode(const xmlnode *node, const char *element_name)
+{
+	char *element, *sig_base64;
+	xmlnode *x, *statusnode;
+	JabberGpgSignature *sig;
+
+	g_return_val_if_fail(node != NULL, NULL);
+	g_return_val_if_fail(element_name != NULL, NULL);
+
+	x = xmlnode_get_child_with_namespace(node, "x", "jabber:x:signed");
+	if (!x)
+		return NULL;
+	
+	sig_base64 = xmlnode_get_data(x);
+	statusnode = xmlnode_get_child(node, element_name);
+	if (statusnode)
+		element = xmlnode_get_data(statusnode);
+	else
+		element = NULL;
+	
+	sig = jabber_gpg_verify(element ? element : "", sig_base64);
+	/* Psi always signs an empty string, so try that too */
+	if (sig == NULL && element != NULL)
+		sig = jabber_gpg_verify("", sig_base64);
+
+	g_free(element);
+	g_free(sig_base64);
+	return sig;
+}
+
+/**
+ * Verify a signature.
+ * @return New JabberGpgSignature or NULL if the signature was not verified.
+ * TODO: Add a pass-by-reference parameter for the actual failure reason.
+ */
+JabberGpgSignature*
+jabber_gpg_verify(const char *status, const char *sig_base64)
+{
+	/* The plaintext buffer is not really used, but gpgme requires it. */
+	gpgme_data_t sig_data, plaintext;
+	JabberGpgSignature *jgsig = NULL;
+	gpgme_verify_result_t result;
+	char *armoured;
+
+	g_return_val_if_fail(jabber_gpg_ctx != NULL, NULL);
+	g_return_val_if_fail(status != NULL, NULL);
+	g_return_val_if_fail(sig_base64 != NULL, NULL);
+
+	if ((armoured = add_signature_armor(status, sig_base64))) {
+		if (GPG_ERR_NO_ERROR != gpgme_data_new_from_mem(&sig_data, armoured, strlen(armoured), 1)) {
+			g_free(armoured);
+			return NULL;
+		}
+		g_free(armoured);
+	}
+
+	if (GPG_ERR_NO_ERROR != gpgme_data_new(&plaintext)) {
+		gpgme_data_release(sig_data);
+		return NULL;
+	}
+
+	if (GPG_ERR_NO_ERROR == gpgme_op_verify(jabber_gpg_ctx, sig_data, NULL, plaintext)) {
+		result = gpgme_op_verify_result(jabber_gpg_ctx);
+		/* Only checking the first signature */
+		if (result && result->signatures && (result->signatures->summary & GPGME_SIGSUM_VALID)) {
+			if (result->signatures->validity != GPGME_VALIDITY_NEVER) {
+				jgsig = g_new0(JabberGpgSignature, 1);
+				jgsig->keyid = g_strdup(result->signatures->fpr);
+				jgsig->time = result->signatures->timestamp;
+				jgsig->trusted = result->signatures->validity >= GPGME_VALIDITY_MARGINAL;
+			}
+		}
+	}
+
+	gpgme_data_release(plaintext);
+	gpgme_data_release(sig_data);
+
+	return jgsig;
+}
+
+/**
+ * Free a JabberGpgSignature and all its elements.
+ */
+void jabber_gpg_sig_free(JabberGpgSignature *sig) {
+	g_return_if_fail(sig != NULL);
+	g_free(sig->keyid);
+	g_free(sig);
+}
+
+/**
+ * Decrypt an encrypted string.
+ * @return Decrypted text if successful or NULL on error.
+ *         Callers must free the returned string with g_free().
+ */
+static char*
+jabber_gpg_decrypt(const char *ciphertext)
+{
+	char *decrypted, *armoured, *plaintext = NULL;
+	gpgme_data_t cipher, plain;
+	size_t length;
+
+	g_return_val_if_fail(ciphertext != NULL, NULL);
+	g_return_val_if_fail(jabber_gpg_ctx != NULL, NULL);
+
+	armoured = add_crypto_armor(ciphertext);
+
+	if (GPG_ERR_NO_ERROR != gpgme_data_new_from_mem(&cipher, armoured, strlen(armoured), 1)) {
+		g_free(armoured);
+		return NULL;
+	}
+	g_free(armoured);
+
+	if (GPG_ERR_NO_ERROR != gpgme_data_new(&plain)) {
+		gpgme_data_release(cipher);
+		return NULL;
+	}
+
+	/* The reason for getting the memory from the buffer _then_ duping it is
+	 * to ensure the buffered plaintext is wiped from memory. It also allows
+	 * the string to be NUL-terminated, because the gpgme function doesn't
+	 * NUL-terminate the data for us. */
+
+	if (GPG_ERR_NO_ERROR == gpgme_op_decrypt(jabber_gpg_ctx, cipher, plain)) {
+		decrypted = gpgme_data_release_and_get_mem(plain, &length);
+		plaintext = g_strndup(decrypted, length);
+		memset(decrypted, '\0', length);
+		gpgme_free(decrypted);
+	}
+	else
+		gpgme_data_release(plain);
+
+	gpgme_data_release(cipher);
+	return plaintext;
+}
+
+/**
+ * Decrypt a message if it is encrypted.
+ *
+ * Replaces the message->body->data with decrypted text.
+ * @param message <message> node.
+ * @return TRUE if the message was decrypted, or FALSE if an error occurred
+ *         (including if the message was not encrypted in the first place).
+ * XXX: Return a proper error code if decryption fails?
+ */
+gboolean
+jabber_gpg_decrypt_message(xmlnode *message)
+{
+	char *ciphertext, *plaintext;
+	xmlnode *x, *body;
+	gboolean success = FALSE;
+
+	g_return_val_if_fail(message != NULL, FALSE);
+
+	x = xmlnode_get_child_with_namespace(message, "x", "jabber:x:encrypted");
+	if (!x)
+		return FALSE; /* message not encrypted */
+
+	if ((ciphertext = xmlnode_get_data(x))) {
+		plaintext = jabber_gpg_decrypt(ciphertext);
+		if (plaintext) {
+			xmlnode_free(x);
+			xmlnode_free(xmlnode_get_child(message, "body"));
+			body = xmlnode_new_child(message, "body");
+			xmlnode_insert_data(body, plaintext, -1);
+			gpgme_free(plaintext);
+			success = TRUE;
+		}
+		g_free(ciphertext);
+	}
+	return success;
+}
+
+/**
+ *
+ */
+void
+jabber_gpg_set_passphrase(JabberStream *account, const char *passphrase)
+{
+	size_t len;
+	if (account->gpg_passphrase) {
+		len = strlen(account->gpg_passphrase);
+		memset(account->gpg_passphrase, 0, len);
+		/* Bit o' paranoia... */
+		memset(&len, 0, sizeof len);
+		g_free(account->gpg_passphrase);
+	}
+
+	account->gpg_passphrase = g_strdup(passphrase);
+}
+
+#if 0 /* Implemented using account options instead. */
+/**
+ *
+ */
+void
+jabber_gpg_set_keyid(JabberStream *account, const char *keyid)
+{
+	if (account->gpg_keyid)
+		g_free(gpg_keyid);
+	account->gpg_keyid = g_strdup(keyid);
+}
+#endif
+
+/**
+ * Encrypts the given message.
+ * Callers should wipe the plaintext string after calling this function.
+ * Currently only encrypts to a single recipient.
+ * The returned value must be freed with gpgme_free().
+ *
+ * @param reason Reason encryption failed. Can be NULL.
+ */
+static char*
+jabber_gpg_encrypt(const char *msg, const char *keyid, gpgme_error_t *reason)
+{
+	gpgme_data_t plaintext, ciphertext;
+	gpgme_key_t recipient_keys[2];
+	gpgme_error_t errcode;
+	char *crypt = NULL;
+
+	g_return_val_if_fail(jabber_gpg_ctx != NULL, NULL);
+	g_return_val_if_fail(msg != NULL, NULL);
+	g_return_val_if_fail(keyid != NULL, NULL);
+
+	if (GPG_ERR_NO_ERROR != gpgme_data_new_from_mem(&plaintext, msg, strlen(msg), 0))
+		return NULL;
+	if (GPG_ERR_NO_ERROR != gpgme_data_new(&ciphertext)) {
+		gpgme_data_release(plaintext);
+		return NULL;
+	}
+
+	errcode = gpgme_get_key(jabber_gpg_ctx, keyid, &recipient_keys[0], 0);
+	if (reason)
+		*reason = errcode;
+
+	if (errcode == GPG_ERR_NO_ERROR && recipient_keys[0] != NULL) {
+		memset(recipient_keys + 1, 0, sizeof *recipient_keys);
+		
+		/* Asserting usability over security here (third argument).
+		 * XXX: GPGME_ENCRYPT_ALWAYS_TRUST might actually be unnecessary? */
+		errcode = gpgme_op_encrypt(jabber_gpg_ctx, recipient_keys,
+				GPGME_ENCRYPT_ALWAYS_TRUST, plaintext, ciphertext);
+		if (reason)
+			*reason = errcode;
+
+		if (errcode == GPG_ERR_NO_ERROR) {
+			crypt = gpgme_data_release_and_get_mem(ciphertext, NULL);
+		}
+		
+		/* It might be paranoid, but wipe the key id */
+		memset(recipient_keys + 0, 0, sizeof *recipient_keys);
+	}
+
+	gpgme_data_release(plaintext);
+	if (crypt == NULL)
+		gpgme_data_release(ciphertext);
+	return crypt;
+}
+
+/**
+ * Encrypts a message.
+ * This should be called when the message node is otherwise complete, as it
+ * strips out information leaks like <html> nodes that are likely to contain
+ * a version of the plaintext.
+ *
+ * @reason Optional return-value parameter for failure cause.
+ *
+ * Returns TRUE on success or FALSE on failure.
+ */
+gboolean
+jabber_gpg_encrypt_message(xmlnode *message, const JabberBuddyResource *recipient, gpgme_error_t *reason)
+{
+	char *plaintext, *ciphertext;
+	xmlnode *body, *x, *leaker;
+	gboolean success = FALSE;
+	const char *keyid;
+
+	if (reason)
+		*reason = GPG_ERR_SOURCE_UNKNOWN;
+
+	g_return_val_if_fail(message != NULL, FALSE);
+	g_return_val_if_fail(recipient != NULL, FALSE);
+	/* Trying to encrypt to a user whose presence is unsigned */
+	g_return_val_if_fail(recipient->presence_sig != NULL, FALSE);
+
+	keyid = recipient->presence_sig->keyid;
+
+	/* find the plaintext object */
+	body = xmlnode_get_child(message, "body");
+	g_return_val_if_fail(body != NULL, FALSE);
+
+	if ((plaintext = xmlnode_get_data(body))) {
+		/* encrypt it */
+		if ((ciphertext = jabber_gpg_encrypt(plaintext, keyid, reason))) {
+			/* out with the old... */
+			xmlnode_wipe(body);
+			xmlnode_free(body);
+
+			/* in with the new... */
+			x = xmlnode_new_child(message, "x");
+			xmlnode_set_attrib(x, "xmlns", "jabber:x:encrypted");
+			xmlnode_insert_data(x, ciphertext, -1);
+			gpgme_free(ciphertext);
+			body = xmlnode_new_child(message, "body");
+			xmlnode_insert_data(body, _("This message is encrypted."), -1);
+
+			/* destroy nodes known to leak information */
+			if ((leaker = xmlnode_get_child(message, "html"))) /* JEP-0071 */
+				xmlnode_free(leaker);
+
+			success = TRUE;
+		}
+		memset(plaintext, 0, strlen(plaintext));
+		g_free(plaintext);
+	}
+	return success;
+}
+
+/**
+ * Signs a message.
+ *
+ * @return TRUE on success or FALSE on failure.
+ */
+gboolean
+jabber_gpg_sign_message(xmlnode *message)
+{
+	char *signature, *msg_data;
+	xmlnode *body, *x;
+	gboolean success = FALSE;
+
+	g_return_val_if_fail(message != NULL, FALSE);
+
+	body = xmlnode_get_child(message, "body");
+	g_return_val_if_fail(body != NULL, FALSE);
+
+	if ((msg_data = xmlnode_get_data(body))) {
+		if ((signature = jabber_gpg_sign(msg_data))) {
+			x = xmlnode_new_child(message, "x");
+			xmlnode_set_attrib(x, "xmlns", "jabber:x:signed");
+			xmlnode_insert_data(x, signature, -1);
+			success = TRUE;
+			g_free(signature);
+		}
+		g_free(msg_data);
+	}
+
+	return success;
+}
+
+/**
+ * Determine the nice (short) keyid for the given fingerprint.
+ * This is "0x" followed by the last 8 characters of the fingerprint.
+ * The returned string is statically allocated.
+ */
+const char*
+jabber_gpg_fpr_to_keyid(const char *fpr)
+{
+	size_t length;
+	static char keyid[11] = { '0', 'x' };
+
+	length = strlen(fpr);
+	if (length > 8)
+		strncpy(&keyid[2], fpr + length - 8, 8);
+	else
+		strcpy(&keyid[2], fpr);
+	return keyid;
+}
+
+/**
+ * Overwrite an xmlnode's data with zeroes.
+ * Node data and attribute values are wiped, but attribute names are preserved.
+ *
+ * "What's a gun doing in your trousers?"
+ * "It's for protection."
+ *
+ * This should probably be in xmlnode.{h,c}, feel free to move it there.
+ */
+void
+xmlnode_wipe(xmlnode *node)
+{
+	xmlnode *child;
+	for (child = node->child; child; child = child->next) {
+		if ((child->type == XMLNODE_TYPE_DATA
+					|| child->type == XMLNODE_TYPE_ATTRIB)
+				&& child->data != NULL) {
+			memset(child->data, '\0', child->data_sz);
+		}
+	}
+}
+
+#if 0
+/**
+ * Request's the user's passphrase
+ * The JabberStream's gpg_keyid property must already be set.
+ */
+void
+jabber_gpg_request_passphrase(JabberStream *js)
+{
+	char *primary;
+	
+	g_return_if_fail(js != NULL);
+	g_return_if_fail(js->gpg_keyid != NULL);
+
+	primary = g_strdup_printf(
+			_("Please enter the passphrase for key id %s."),
+			js->gpg_keyid);
+	gaim_request_input(
+			js->gc,
+			_("Enter passphrase"),
+			primary,
+			/* XXX: Show the Jabber ID for this request here... */
+			_("Your private key's passphrase is required for message signing "
+				"and decryption with your Jabber connection"),
+			NULL,
+			FALSE,
+			TRUE,
+			NULL,
+			_("OK"),
+			/* ok_cb */,
+			_("Cancel"),
+			/* cancel_cb */,
+			js /* user_data */
+			);
+
+
+	g_free(primary);
+}
+#endif
+
+/* You know, this code doesn't have enough easter eggs.
+ * It's difficult to hide easter eggs in crypto code, because it tends to be
+ * fairly sensitive. Well, maybe this will do. Congratulations on reading
+ * the end of this source file. I'm afraid I have no ideas for easter eggs,
+ * but if you have one I'd be most appreciative if you could hide it for
+ * somebody to find.
+ *
+ * I hope despite the lack of surprises you'll still enjoy a chocolate treat
+ * every now and then.
+ *
+ * All the best,
+ * -tedp
+ *
+ * P.S., bugs are not easter eggs, but they _are_ fun to track down!
+ */
diff -ruN -x Makefile.in -x Makefile -x '*.la' -x plugins gaim-2.0.0+beta3/src/protocols/jabber/gpg.h gaim-new/src/protocols/jabber/gpg.h
--- gaim-2.0.0+beta3/src/protocols/jabber/gpg.h	1970-01-01 10:00:00.000000000 +1000
+++ gaim-new/src/protocols/jabber/gpg.h	2006-08-16 13:47:12.000000000 +1000
@@ -0,0 +1,74 @@
+/**
+ * @file gpg.h
+ *
+ * Gaim - OpenPGP enhancements to the Jabber protocol.
+ *
+ * Copyright (C) 2006 Ted Percival <ted@midg3t.net>
+ *
+ * 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.
+ */
+
+#ifndef JABBER_GPG_H
+#define JABBER_GPG_H
+
+#include <glib.h>
+#include <gpgme.h>
+
+#include "jabber.h"
+
+#include "buddy.h"
+
+gboolean
+jabber_gpg_init(void);
+
+void
+jabber_gpg_finish(void);
+
+JabberGpgSignature*
+jabber_gpg_verify_presence(const xmlnode *presence);
+
+JabberGpgSignature*
+jabber_gpg_verify_message(const xmlnode *message);
+
+void
+jabber_gpg_sig_free(JabberGpgSignature *sig);
+
+gboolean
+jabber_gpg_decrypt_message(xmlnode *message);
+
+void
+jabber_gpg_set_passphrase(JabberStream *account, const char *passphrase);
+
+#if 0 /* Implemented using account options instead */
+void
+jabber_gpg_set_keyid(JabberStream *account, const char *keyid);
+#endif
+
+gboolean
+jabber_gpg_sign_presence(xmlnode *presence);
+
+gboolean
+jabber_gpg_sign_message(xmlnode *message);
+
+gboolean
+jabber_gpg_encrypt_message(xmlnode *message, const JabberBuddyResource *recipient, gpgme_error_t *reason);
+
+const char*
+jabber_gpg_fpr_to_keyid(const char *fpr);
+
+void
+jabber_gpg_request_passphrase(JabberStream *js);
+
+#endif /* JABBER_GPG_H */
diff -ruN -x Makefile.in -x Makefile -x '*.la' -x plugins gaim-2.0.0+beta3/src/protocols/jabber/GPG_TODO gaim-new/src/protocols/jabber/GPG_TODO
--- gaim-2.0.0+beta3/src/protocols/jabber/GPG_TODO	1970-01-01 10:00:00.000000000 +1000
+++ gaim-new/src/protocols/jabber/GPG_TODO	2006-08-16 13:47:12.000000000 +1000
@@ -0,0 +1,4 @@
+Things that still need doing in the GPG code:
+
+- Menu item to forget the passphrase
+- Wipe passphrase when the connection is closed
diff -ruN -x Makefile.in -x Makefile -x '*.la' -x plugins gaim-2.0.0+beta3/src/protocols/jabber/jabber.c gaim-new/src/protocols/jabber/jabber.c
--- gaim-2.0.0+beta3/src/protocols/jabber/jabber.c	2006-03-21 14:32:44.000000000 +1000
+++ gaim-new/src/protocols/jabber/jabber.c	2006-08-16 13:47:12.000000000 +1000
@@ -40,6 +40,7 @@
 #include "buddy.h"
 #include "chat.h"
 #include "disco.h"
+#include "gpg.h"
 #include "iq.h"
 #include "jutil.h"
 #include "message.h"
@@ -1150,6 +1151,10 @@
 			}
 
 			g_string_append_printf(str, "\n<b>%s%s:</b> %s%s%s",
+					/* nested ternary ifs are way cool */
+					jbr->presence_sig ? jbr->presence_sig->trusted ?
+					_("Authenticated status"):
+					_("Signed status"):
 					_("Status"),
 					res ? res : "",
 					state,
@@ -1935,6 +1940,11 @@
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
 			option);
 
+	option = gaim_account_option_string_new(_("OpenPGP key ID"),
+			"pgp_key", NULL);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+			option);
+
 	my_protocol = plugin;
 
 	gaim_prefs_remove("/plugins/prpl/jabber");
@@ -1943,6 +1953,11 @@
 #ifdef HAVE_CYRUS_SASL
 	sasl_client_init(NULL);
 #endif
+
+	if (jabber_gpg_init() == FALSE)
+		gaim_debug_info("jabber",
+				_("GPGME initialisation failed, continuing anyway\n"));
+
 	jabber_register_commands();
 }
 
diff -ruN -x Makefile.in -x Makefile -x '*.la' -x plugins gaim-2.0.0+beta3/src/protocols/jabber/jabber.h gaim-new/src/protocols/jabber/jabber.h
--- gaim-2.0.0+beta3/src/protocols/jabber/jabber.h	2006-03-03 13:38:36.000000000 +1000
+++ gaim-new/src/protocols/jabber/jabber.h	2006-08-16 13:47:12.000000000 +1000
@@ -114,6 +114,8 @@
 	GaimCircBuffer *write_buffer;
 	guint writeh;
 
+	char *gpg_keyid, *gpg_passphrase;
+
 	/* OK, this stays at the end of the struct, so plugins can depend
 	 * on the rest of the stuff being in the right place
 	 */
@@ -127,6 +129,13 @@
 
 } JabberStream;
 
+typedef struct _JabberGpgSignature {
+	char *keyid;
+	time_t time;
+	gboolean trusted;
+} JabberGpgSignature;
+
+
 void jabber_process_packet(JabberStream *js, xmlnode *packet);
 void jabber_send(JabberStream *js, xmlnode *data);
 void jabber_send_raw(JabberStream *js, const char *data, int len);
diff -ruN -x Makefile.in -x Makefile -x '*.la' -x plugins gaim-2.0.0+beta3/src/protocols/jabber/Makefile.am gaim-new/src/protocols/jabber/Makefile.am
--- gaim-2.0.0+beta3/src/protocols/jabber/Makefile.am	2005-12-17 12:24:05.000000000 +1000
+++ gaim-new/src/protocols/jabber/Makefile.am	2006-08-16 13:47:12.000000000 +1000
@@ -13,6 +13,8 @@
 			  chat.h \
 			  disco.c \
 			  disco.h \
+			  gpg.c \
+			  gpg.h \
 			  iq.c \
 			  iq.h \
 			  jabber.c \
@@ -34,7 +36,6 @@
 			  xdata.c \
 			  xdata.h
 
-AM_CFLAGS = $(st)
 
 libjabber_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) $(SASL_LIBS)
 
@@ -60,4 +61,7 @@
 AM_CPPFLAGS = \
 	-I$(top_srcdir)/src \
 	$(DEBUG_CFLAGS) \
+	$(GLIB_CFLAGS) \
+	$(GPGME_CFLAGS) \
+	$(GTK_CFLAGS) \
 	$(GLIB_CFLAGS)
diff -ruN -x Makefile.in -x Makefile -x '*.la' -x plugins gaim-2.0.0+beta3/src/protocols/jabber/message.c gaim-new/src/protocols/jabber/message.c
--- gaim-2.0.0+beta3/src/protocols/jabber/message.c	2006-03-10 07:03:13.000000000 +1000
+++ gaim-new/src/protocols/jabber/message.c	2006-08-16 13:47:12.000000000 +1000
@@ -21,6 +21,7 @@
 #include "internal.h"
 
 #include "debug.h"
+#include "gpg.h"
 #include "notify.h"
 #include "server.h"
 #include "util.h"
@@ -41,6 +42,7 @@
 	g_free(jm->body);
 	g_free(jm->xhtml);
 	g_free(jm->password);
+	g_free(jm->signature);
 	g_list_free(jm->etc);
 
 	g_free(jm);
@@ -338,6 +340,10 @@
 
 					jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE;
 				}
+			} else if (xmlns && !strcmp(xmlns, "jabber:x:signed")) {
+				jm->signature = jabber_gpg_verify_message(packet);
+			} else if (xmlns && !strcmp(xmlns, "jabber:x:encrypted")) {
+				jm->encrypted = jabber_gpg_decrypt_message(packet);
 			} else {
 				jm->etc = g_list_append(jm->etc, child);
 			}
@@ -426,9 +432,11 @@
 	if(jm->body) {
 		child = xmlnode_new_child(message, "body");
 		xmlnode_insert_data(child, jm->body, -1);
+		/* TODO: Sign message if user wants to */
+			/*jabber_gpg_sign_message(message);*/
 	}
 
-	if(jm->xhtml) {
+	if(jm->xhtml/* && !jm->sig*/) {
 		child = xmlnode_from_str(jm->xhtml, -1);
 		if(child) {
 			xmlnode_insert_child(message, child);
diff -ruN -x Makefile.in -x Makefile -x '*.la' -x plugins gaim-2.0.0+beta3/src/protocols/jabber/message.h gaim-new/src/protocols/jabber/message.h
--- gaim-2.0.0+beta3/src/protocols/jabber/message.h	2006-03-10 07:03:13.000000000 +1000
+++ gaim-new/src/protocols/jabber/message.h	2006-08-16 13:47:12.000000000 +1000
@@ -38,6 +38,7 @@
 	} type;
 	time_t sent;
 	gboolean delayed;
+	gboolean encrypted;
 	char *id;
 	char *from;
 	char *to;
@@ -47,6 +48,7 @@
 	char *password;
 	char *error;
 	char *thread_id;
+	JabberGpgSignature *signature;
 	enum {
 		JABBER_MESSAGE_EVENT_COMPOSING = 1 << 1
 	} events;
diff -ruN -x Makefile.in -x Makefile -x '*.la' -x plugins gaim-2.0.0+beta3/src/protocols/jabber/presence.c gaim-new/src/protocols/jabber/presence.c
--- gaim-2.0.0+beta3/src/protocols/jabber/presence.c	2006-03-14 04:48:49.000000000 +1000
+++ gaim-new/src/protocols/jabber/presence.c	2006-08-16 13:47:12.000000000 +1000
@@ -22,6 +22,7 @@
 
 #include "cipher.h"
 #include "debug.h"
+#include "gpg.h"
 #include "notify.h"
 #include "request.h"
 #include "server.h"
@@ -34,7 +35,7 @@
 #include "iq.h"
 #include "jutil.h"
 #include "xmlnode.h"
-
+#include "gtkblist.h"
 
 static void chats_send_presence_foreach(gpointer key, gpointer val,
 		gpointer user_data)
@@ -74,7 +75,8 @@
 			if (state == JABBER_BUDDY_STATE_UNAVAILABLE || state == JABBER_BUDDY_STATE_UNKNOWN) {
 				jabber_buddy_remove_resource(jb, js->user->resource);
 			} else {
-				jabber_buddy_track_resource(jb, js->user->resource, priority, state, msg);
+				/* FIXME: Fake the GPG sig if presence is signed */
+				jabber_buddy_track_resource(jb, js->user->resource, priority, state, msg, NULL);
 			}
 			if((jbr = jabber_buddy_find_resource(jb, NULL))) {
 				gaim_prpl_got_user_status(js->gc->account, my_base_jid, jabber_buddy_state_get_status_id(jbr->state), "priority", jbr->priority, jbr->status ? "message" : NULL, jbr->status, NULL);
@@ -153,11 +155,14 @@
 		xmlnode_insert_data(show, show_string, -1);
 	}
 
-	if(msg) {
+	if (msg) {
 		status = xmlnode_new_child(presence, "status");
 		xmlnode_insert_data(status, msg, -1);
 	}
 
+	/* XXX: if(config_option) ... */
+	jabber_gpg_sign_presence(presence);
+
 	if(priority) {
 		char *pstr = g_strdup_printf("%d", priority);
 		pri = xmlnode_new_child(presence, "priority");
@@ -289,7 +294,7 @@
 	xmlnode *y;
 	gboolean muc = FALSE;
 	char *avatar_hash = NULL;
-
+	JabberGpgSignature *gpgsig = NULL;
 
 	if(!(jb = jabber_buddy_find(js, from, TRUE)))
 		return;
@@ -410,6 +415,8 @@
 						g_free(avatar_hash);
 					avatar_hash = xmlnode_get_data(photo);
 				}
+			} else if (xmlns && !strcmp(xmlns, "jabber:x:signed")) {
+				gpgsig = jabber_gpg_verify_presence(packet);
 			}
 		}
 	}
@@ -518,7 +525,7 @@
 			}
 
 			jabber_buddy_track_resource(jb, jid->resource, priority, state,
-					status);
+					status, gpgsig);
 
 			jabber_chat_track_handle(chat, jid->resource, real_jid, affiliation, role);
 
@@ -579,7 +586,7 @@
 
 		} else {
 			jbr = jabber_buddy_track_resource(jb, jid->resource, priority,
-					state, status);
+					state, status, gpgsig);
 		}
 
 		if((found_jbr = jabber_buddy_find_resource(jb, NULL))) {
